RK3588的实时内核,这里测试验证其指标,用于后续评估判断RK3588相关的实时指标作为参考借鉴。测试结果或有偏差,最终以实际测试为准
RT内核两笔补丁需要修改,如下:
diff --git a/include/trace/hooks/dtask.h b/include/trace/hooks/dtask.h index 9890bfe5c41d..fc2a51ce547e 100644 --- a/include/trace/hooks/dtask.h +++ b/include/trace/hooks/dtask.h @@ -58,7 +58,7 @@ DECLARE_HOOK(android_vh_sched_show_task, TP_ARGS(task)); DECLARE_HOOK(android_vh_alter_mutex_list_add, TP_PROTO(struct mutex *lock, - struct mutex_waiter *waiter, + struct rt_mutex_waiter *waiter, struct list_head *list, bool *already_on_list), TP_ARGS(lock, waiter, list, already_on_list)); diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index a2506b92e0c1..8d794db5e4d6 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -67,6 +67,7 @@ #ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER #include <clocksource/arm_arch_timer.h> +#if 0 static u64 get_local_clock(void) { u64 ns; @@ -76,6 +77,7 @@ static u64 get_local_clock(void) return ns; } +#endif #else static inline u64 get_local_clock(void) {
编译命令:
export ARCH=arm64 export CROSS_COMPILE=aarch64-none-linux-gnu- export PATH=$PATH:/path/to/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin make rockchip_linux_defconfig rockchip_rt.config make kylin.img -j123
这里项目有kylin.dts,故编译为kylin.img的内核,其他项目自行修改
#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdlib.h> #include <string.h> #include <inttypes.h> #include <unistd.h> #include <stdio.h> #include <limits.h> #include <sched.h> #include <signal.h> #include <netinet/if_ether.h> #include <netinet/ip.h> #include <stdbool.h> #include <pthread.h> //#include <lock.h> #include <fcntl.h> #include <time.h> #include <mqueue.h> #include <semaphore.h> #include <stdarg.h> #include <sys/mman.h> #define TESTMQ_NAME "/testmsg" #define MSG_SIZE 8 char *testmsg = "Testing"; static int print_mode, TestCount; mqd_t sendsyncmq, recvsyncmq, testmq; struct timespec tv_s[10], tv_e[10]; static int over; struct mq_attr mqstat; pthread_t send_tid, recv_tid, send_recv_tid; static int tracemark_fd = -1; #define FILE_TRACEMARK "/sys/kernel/debug/tracing/trace_marker" static void open_tracemark() { if ((tracemark_fd = open(FILE_TRACEMARK, O_WRONLY)) == -1) printf("unable to open trace_marker file: %s\n", FILE_TRACEMARK); } static void close_tracemark() { close(tracemark_fd); } #define TRACEBUFSIZ 1024 static __thread char tracebuf[TRACEBUFSIZ]; static void tracemark(char *fmt, ...) __attribute__((format(printf, 1, 2))); static void tracemark(char *fmt, ...) { va_list ap; int len; /* bail out if we're not tracing */ /* or if the kernel doesn't support trace_mark */ if (tracemark_fd < 0) return; va_start(ap, fmt); len = vsnprintf(tracebuf, TRACEBUFSIZ, fmt, ap); va_end(ap); write(tracemark_fd, tracebuf, len); } /* * * * 终止ftrace,这样可以在ftrace的ringbuffer的底部保留问题现场 * * */ static int trace_fd = -1; #define FILE_TRACEON "/sys/kernel/debug/tracing/tracing_on" static void open_tracingon() { if ((trace_fd = open(FILE_TRACEON, O_WRONLY)) == -1) printf("unable to open trace_on file: %s\n", FILE_TRACEON); } static void close_tracingon() { close(trace_fd); } static void disable_trace() { if (trace_fd < 0) return; write(trace_fd, "0", 1); } pthread_t test_task_create(char *name, void *thread_func, size_t stack_size, int prio, int core) { int ret; struct sched_param param; pthread_attr_t attr; pthread_t thread_id; cpu_set_t mask; ret = pthread_attr_init(&attr); if (ret) { printf("ERROR: pthread_attr_init failed\n"); return -1; } ret = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); if (ret) { printf("ERROR: pthread_attr_setscope failed\n"); goto out; } ret = pthread_attr_setstacksize(&attr, stack_size); if (ret) { printf("ERROR: pthread_attr_setstacksize failed\n"); goto out; } ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); if (ret) { printf("ERROR: pthread_attr_setinheritsched failed\n"); goto out; } /* ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (ret) { printf("ERROR: pthread_attr_setdetachstate failed\n"); goto out; } */ ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); if (ret) { printf("ERROR: pthread_attr_setschedpolicy failed\n"); goto out; } param.sched_priority = prio; ret = pthread_attr_setschedparam(&attr, ¶m); if (ret) { printf("ERROR: pthread_attr_setschedparam failed\n"); goto out; } ret = pthread_create(&thread_id, &attr, thread_func, NULL); if (ret) { printf("ERROR: pthread_create %s failed\n", name); goto out; } CPU_ZERO(&mask); CPU_SET(core, &mask); if (pthread_setaffinity_np(thread_id, sizeof(mask), &mask) == -1) printf("ERROR: %s pthread_setaffinity_np failed, cpu_core = %d\n", name, core); return thread_id; out: pthread_attr_destroy(&attr); return -1; } struct timespec send_tv, recv_tv; bool flag = false; bool flag_send = false; void send_task_pthread(int core) { int i, ret; struct timespec tv1, tv2; unsigned int send_diff, send_sum; unsigned int send_mindiff, send_avg, send_maxdiff; char recvtestmsg[MSG_SIZE]; send_sum = 0; send_avg =0; send_mindiff = 100000000; send_maxdiff = 0; for (i = 0; i < TestCount; i++) { if (over) break; /* * send the test massege and calculate the cost time */ if (i > 1) { send_diff = (recv_tv.tv_sec * 1000000000 + recv_tv.tv_nsec) - (send_tv.tv_sec * 1000000000 + send_tv.tv_nsec); send_sum += send_diff; send_avg = send_sum / (i + 1); if (send_diff < send_mindiff) { send_mindiff = send_diff; } /* * 从第三次开始计入最大时间 * 原因:第二次发送消息时,如果接受线程未创建成功,mq_send阻塞, * 等待接受线程创建,第二次计算时间并不准确。 * 所以等接收线程接收一次消息后,开始统计最大时间 */ if ((send_diff > send_maxdiff) && (i > 1)) { send_maxdiff = send_diff; } // if (((i % 10) == 0)) printf("Task Switch Time, loop: %d, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", i, send_mindiff, send_diff, send_avg, send_maxdiff); } flag_send = false; ret = mq_send(testmq, testmsg, strlen(testmsg), 1); clock_gettime(CLOCK_REALTIME, &send_tv); flag_send = true; flag = false; while (!flag) { usleep(10000); } // if (!flag){ // usleep(10000); // flag = true; // printf("ERROR: send test message failed\n"); // sleep(1); // goto retry; // } } over = 1; printf("mq_send test count: %d \n", i); printf("Final result, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", send_mindiff, send_diff, send_avg, send_maxdiff); /* close_tracemark(); close_tracingon(); */ pthread_kill(recv_tid, SIGTERM); return; } void recv_task_pthread(int core) { int i, ret; ssize_t recv_length; struct timespec tv1, tv2; unsigned int recv_diff, recv_sum; unsigned int recv_mindiff, recv_avg, recv_maxdiff; char recvsyncmsg[MSG_SIZE]; char recvtestmsg[MSG_SIZE]; while (!over) { /* * receive the test massege and calculate the cost time */ recv_length = mq_receive(testmq, recvtestmsg, MSG_SIZE, NULL); while (!flag_send) { } clock_gettime(CLOCK_REALTIME, &recv_tv); flag = true; if ( recv_length != strlen(testmsg)) { perror("could not receive test message"); over = 1; } } return; } void send_recv_pthread(int core) { int i, ret; ssize_t recv_length; struct timespec tv1, tv2; unsigned int recv_diff, recv_sum; unsigned int recv_mindiff, recv_avg, recv_maxdiff; char recvtestmsg[MSG_SIZE]; recv_sum = 0; recv_avg =0; // recv_mindiff = UINT_MAX; recv_mindiff = 100000000; recv_maxdiff = 0; for (i = 0; i < TestCount; i++) { /* * send the test massege */ ret = mq_send(testmq, testmsg, strlen(testmsg), 1); if (ret < 0) { printf("ERROR: send test message failed\n"); over = 1; } usleep(200); /* * receive the test massege and calculate the cost time */ clock_gettime(CLOCK_REALTIME, &tv1); recv_length = mq_receive(testmq, recvtestmsg, MSG_SIZE, NULL); clock_gettime(CLOCK_REALTIME, &tv2); if ( recv_length != strlen(testmsg)) { perror("could not receive test message"); over = 1; } recv_diff = (tv2.tv_sec * 1000000000 + tv2.tv_nsec) - (tv1.tv_sec * 1000000000 + tv1.tv_nsec); recv_sum += recv_diff; recv_avg = recv_sum / (i + 1); if (recv_diff < recv_mindiff) { recv_mindiff = recv_diff; } if ((recv_diff > recv_maxdiff) && (i > 0)) { recv_maxdiff = recv_diff; } if (((i % 10) == 0)) printf("MQ Receive Time,loop: %d, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", i, recv_mindiff, recv_diff, recv_avg, recv_maxdiff); usleep(100); } printf("mq_receive test count: %d \n", i); printf("Final result, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", recv_mindiff, recv_diff, recv_avg, recv_maxdiff); return; } int main(int argc, char **argv) { int SendCore, SendThreadPrio, RecvCore, RecvThreadPrio, mode; if (argc != 7) { printf("Wrong CMD, check the parameters, please!\n"); printf("./mq_task_switch 1 90 2 90 10000 1\n"); return -1; } if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { perror("mlockall"); return -1; } SendCore = atoi(argv[1]); SendThreadPrio = atoi(argv[2]); RecvCore = atoi(argv[3]); RecvThreadPrio = atoi(argv[4]); TestCount = atoi(argv[5]); mode = atoi(argv[6]); over = 0; memset(&mqstat, 0, sizeof(mqstat)); mqstat.mq_maxmsg = 1; mqstat.mq_msgsize = 8; mqstat.mq_flags = 0; mq_unlink(TESTMQ_NAME); testmq = mq_open(TESTMQ_NAME, O_RDWR | O_CREAT, 0666, &mqstat); if (testmq < 0) { printf("open test message queue failed\n"); return -1; } if (mode == 0) { send_recv_tid = test_task_create("SndRECVTask", send_recv_pthread, 262144, SendThreadPrio, SendCore); if (send_recv_tid < 0) { printf("ERROR: Create send_recv_pthread failed!\n"); return -1; } pthread_join(send_recv_tid, NULL); } else { send_tid = test_task_create("sndTask", send_task_pthread, 262144, SendThreadPrio, SendCore); if (recv_tid < 0) { printf("ERROR: Create send_task_pthread failed!\n"); return -1; } usleep(10); recv_tid = test_task_create("RcvTask", recv_task_pthread, 262144, RecvThreadPrio, RecvCore); if (recv_tid < 0) { printf("ERROR: Create recv_task_pthread failed!\n"); return -1; } pthread_join(recv_tid, NULL); pthread_join(send_tid, NULL); } munlockall(); return 0; }
编译得到二进制mq_task_switch,开始测试
./mq_task_switch 1 90 2 90 10000 1
输出结果
Final result, Min: 10208 ns, Cur: 11084 ns, Avg: 11113 ns, Max: 24791 ns
这里可以确定最大值是24791ns ,平时值是11113ns.
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> #include <sched.h> #include <sys/types.h> #include <unistd.h> //pipe() int main() { int x, i, fd[2], p[2]; int tv[2]; //用于进程间数据通信 char send = 's'; char receive; pipe(fd); pipe(p); pipe(tv); struct timeval tv_start,tv_end; struct sched_param param; param.sched_priority = 0; while ((x = fork()) == -1); if (x==0) { //子进程 sched_setscheduler(getpid(), SCHED_FIFO, ¶m); gettimeofday(&tv_start, NULL); write(tv[1], &tv_start, sizeof(tv_start));//保存数据,以便传递到父进程中进行计算 for (i = 0; i < 10000; i++) { read(fd[0], &receive, 1); //printf("Child read!\n"); write(p[1], &send, 1); //printf("Child write!\n"); } exit(0); } else { //父进程 sched_setscheduler(getpid(), SCHED_FIFO, ¶m); for (i = 0; i < 10000; i++) { write(fd[1], &send, 1); //printf("Parent write!\n"); read(p[0], &receive, 1); //printf("Parent read!\n"); } gettimeofday(&tv_end, NULL); } read(tv[0], &tv_start, sizeof(tv_start)); printf("Task Switch Time: %f us\n", (1000000*(tv_end.tv_sec-tv_start.tv_sec) + tv_end.tv_usec - tv_start.tv_usec)/20000.0); return 0; }
编译:
gcc task_switch_pipe.c -o task_switch_pipe
运行:
root@kylin:~# while((1));do ./task_switch_pipe ; done Task Switch Time: 7.925000 us Task Switch Time: 7.978100 us Task Switch Time: 7.542050 us Task Switch Time: 9.470300 us Task Switch Time: 7.518850 us Task Switch Time: 8.346700 us Task Switch Time: 8.005150 us Task Switch Time: 7.560750 us Task Switch Time: 7.625400 us Task Switch Time: 8.143850 us Task Switch Time: 6.504850 us Task Switch Time: 8.422350 us Task Switch Time: 8.029650 us Task Switch Time: 7.612400 us Task Switch Time: 7.706200 us Task Switch Time: 6.831750 us Task Switch Time: 6.637150 us Task Switch Time: 9.136150 us Task Switch Time: 7.259700 us Task Switch Time: 9.774700 us Task Switch Time: 6.781400 us Task Switch Time: 7.425350 us Task Switch Time: 7.738900 us Task Switch Time: 8.812100 us Task Switch Time: 7.271150 us Task Switch Time: 8.677250 us Task Switch Time: 7.514600 us Task Switch Time: 6.683600 us Task Switch Time: 7.421900 us Task Switch Time: 8.019550 us Task Switch Time: 8.250450 us Task Switch Time: 8.571800 us Task Switch Time: 7.957400 us Task Switch Time: 7.837050 us Task Switch Time: 7.161250 us Task Switch Time: 8.998750 us Task Switch Time: 7.160500 us Task Switch Time: 6.629200 us Task Switch Time: 7.082150 us Task Switch Time: 7.570450 us Task Switch Time: 8.423500 us Task Switch Time: 7.531950 us Task Switch Time: 7.856900 us Task Switch Time: 8.980750 us Task Switch Time: 7.072300 us Task Switch Time: 9.112650 us Task Switch Time: 7.871100 us Task Switch Time: 7.168500 us Task Switch Time: 7.774850 us Task Switch Time: 7.308050 us Task Switch Time: 6.532000 us Task Switch Time: 6.994950 us Task Switch Time: 7.805700 us Task Switch Time: 7.980750 us Task Switch Time: 7.990150 us Task Switch Time: 8.017550 us Task Switch Time: 6.781300 us Task Switch Time: 6.755500 us Task Switch Time: 6.418300 us Task Switch Time: 8.239600 us Task Switch Time: 8.661350 us Task Switch Time: 8.688850 us Task Switch Time: 7.245000 us Task Switch Time: 8.793250 us Task Switch Time: 7.628300 us Task Switch Time: 6.593100 us Task Switch Time: 8.685100 us Task Switch Time: 6.575550 us Task Switch Time: 8.380500 us Task Switch Time: 8.607750 us Task Switch Time: 6.581850 us Task Switch Time: 6.862200 us Task Switch Time: 6.401400 us Task Switch Time: 6.690150 us Task Switch Time: 7.945100 us Task Switch Time: 7.820750 us Task Switch Time: 7.935500 us Task Switch Time: 8.156100 us Task Switch Time: 7.198700 us Task Switch Time: 7.230950 us Task Switch Time: 7.623400 us Task Switch Time: 7.438650 us Task Switch Time: 7.172000 us Task Switch Time: 8.439000 us Task Switch Time: 6.927050 us Task Switch Time: 8.215500 us Task Switch Time: 7.361300 us Task Switch Time: 7.231800 us Task Switch Time: 7.303450 us Task Switch Time: 8.140700 us Task Switch Time: 8.331800 us Task Switch Time: 7.962950 us Task Switch Time: 6.757600 us Task Switch Time: 7.251000 us
这里测试最大延迟为9.774700 us
安装测试包
apt install rt-tests
测试命令:
pmqtest -p 90 -t 2 -i 1000 -l 7200000
测试结果
root@kylin:~# pmqtest -p 90 -t 2 -i 1000 -l 7200000 #0: ID2607, P90, CPU5, I1000; #1: ID2608, P90, CPU4, TO 0, Cycles 782505 #2: ID2609, P89, CPU6, I1500; #3: ID2610, P89, CPU7, TO 0, Cycles 524798 #1 -> #0, Min 3, Cur 6, Avg 6, Max 27 #3 -> #2, Min 3, Cur 6, Avg 7, Max 36
这里可以确定最小值3us,平均值7us,最大值36us
测试命令:
cyclictest -p 90 -m -c 0 -i 1000 -t 4 -l 7200000
测试结果:
root@kylin:~# cyclictest -p 90 -m -c 0 -i 1000 -t 4 -l 7200000 policy: fifo: loadavg: 2.61 1.48 0.68 1/288 257policy: fifo: loadavg: 2.91 2.71 2.14 1/285 2595 # /dev/cpu_dma_latency set to 0us T: 0 ( 2567) P:90 I:1000 C:1217830 Min: 4 Act: 4 Avg: 4 Max: 19 T: 1 ( 2568) P:90 I:1500 C: 811877 Min: 4 Act: 4 Avg: 4 Max: 15 T: 2 ( 2569) P:90 I:2000 C: 608904 Min: 4 Act: 5 Avg: 4 Max: 14 T: 3 ( 2570) P:90 I:2500 C: 487120 Min: 4 Act: 4 Avg: 4 Max: 13
这里可以确定最大延迟19us,平均延迟4us
当前测试结论如下:
3588任务上下文切换最大时间为:24.791us,通过pipe令牌测试最大延迟为9.774700 us
3588消息传递最大延迟为:36us
3588中断响应最大延迟为:19us
当前测试结果不代表实际测试结果,并且测试结果仅供参考,具体情况根据系统负载,硬件情况决定。
RK最近实现了从上层处理的硬件光标层,仓库地址为 https://github.com/JeffyCN/drm-cursor 。以往的如RK3399的硬件光标都是走的libdrm的通用接口,但在新的平台例如RK3568和RK3588上,内核貌似没有单独设置鼠标层。
如果需要在平台上使用硬件光标,可以使用这个库实现。
这里实现的drm curosr 鼠标指针,是通过hook的方式,把drm设置鼠标的相关函数通过EGL的接口实现,避免通过libdrm走到内核上面去。
关于什么是光标,这里给出了解释:
Cursors Similar to planes, many hardware also supports cursors. A cursor is a very small buffer with an image that is blended over the CRTC framebuffer. You can set a different cursor for each CRTC with drmModeSetCursor(3) and move it on the screen with drmModeMoveCursor(3). This allows to move the cursor on the screen without rerendering. If no hardware cursors are supported, you need to rerender for each frame the cursor is moved.
drmModeSetCursor xf86drmMode.c +412 drm_public int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { struct drm_mode_cursor arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = crtcId; arg.width = width; arg.height = height; arg.handle = bo_handle; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER),
drmModeMoveCursor drm_public int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { struct drm_mode_cursor arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_MOVE; arg.crtc_id = crtcId; arg.x = x; arg.y = y; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER),
drmModeSetCursor2 drm_public int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) { struct drm_mode_cursor2 arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = crtcId; arg.width = width; arg.height = height; arg.handle = bo_handle; arg.hot_x = hot_x; arg.hot_y = hot_y; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR2, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR2, drm_mode_cursor2_ioctl, DRM_MASTER|DRM_UNLOCKED)
对应Xorg中实现光标的地方如下
hw/xfree86/drivers/modesetting/drmmode_display.c
drmmode_set_cursor_position
static void drmmode_set_cursor_position(xf86CrtcPtr crtc, int x, int y) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; drmModeMoveCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, x, y); }
drmmode_show_cursor
static Bool drmmode_show_cursor(xf86CrtcPtr crtc) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_crtc->cursor_up = TRUE; return drmmode_set_cursor(crtc); }
ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, handle, ms->cursor_width, ms->cursor_height, cursor->bits->xhot, cursor->bits->yhot); /* -EINVAL can mean that an old kernel supports drmModeSetCursor but * not drmModeSetCursor2, though it can mean other things too. */ if (ret == -EINVAL) ret = drmModeSetCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, handle, ms->cursor_width, ms->cursor_height); /* -ENXIO normally means that the current drm driver supports neither * cursor_set nor cursor_set2. Disable hardware cursor support for * the rest of the session in that case. */ if (ret == -ENXIO) { xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn); xf86CursorInfoPtr cursor_info = xf86_config->cursor_info; cursor_info->MaxWidth = cursor_info->MaxHeight = 0; drmmode_crtc->drmmode->sw_cursor = TRUE; }
如果内核的IOCTL为 -ENXIO 则Xorg实现软光标
drmmode_crtc->drmmode->sw_cursor = TRUE;
int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { /* Init log file */ drm_get_ctx(fd); return drm_set_cursor(fd, crtcId, bo_handle, width, height); } int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { return drm_move_cursor(fd, crtcId, x, y); } int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) { return -EINVAL; }
这个仓库最终生成了 libdrm-cursor.so.1.0.0 的库文件,但是光标的实现函数在libdrm的so里面。这里RK使用了ld的preload的配置
方式是在debian/libdrm-cursor.postinst 内添加 echo /usr/lib/*/libdrm-cursor.so.1 >> /etc/ld.so.preload
Xorg的链接情况如下,说明钩子已生效
kylin@kylin:~$ ldd /usr/lib/xorg/Xorg | grep drm /usr/lib/aarch64-linux-gnu/libdrm-cursor.so.1 (0x0000007fb9a31000) libdrm.so.2 => /lib/aarch64-linux-gnu/libdrm.so.2 (0x0000007fb97d1000)
drm_get_ctx(485) atomic drm API enabled drm_get_ctx(518) max fps: 60 drm_get_ctx(559) found 0 CRTC: 64(0) (1920x1200) prefer plane: 0 drm_get_ctx(559) found 1 CRTC: 83(1) (0x0) prefer plane: 0 drm_get_ctx(567) found 2 CRTCs drm_get_ctx(616) found plane: 58[primary] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 61[cursor ] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 65[overlay] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 68[overlay] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 80[primary] crtcs: 0x2 (ARGB) drm_get_ctx(616) found plane: 84[overlay] crtcs: 0x2 (ARGB) drm_get_ctx(625) using libdrm-cursor (1.3.0~20211201) drmModeSetCursor(1185) fd: 11 crtc: 64 handle: 0 size: 0x0 drm_crtc_bind_plane(692) CRTC[64]: using cursor plane drm_crtc_bind_plane(700) CRTC[64]: bind plane: 61 drm_set_cursor(1097) CRTC[64]: request setting new cursor 0 (0x0) drm_crtc_thread_fn(866) CRTC[64]: thread started drm_crtc_thread_fn(924) CRTC[64]: set new cursor 0 (0x0) drmModeMoveCursor(1192) fd: 11 crtc: 64 position: 956,596 drm_move_cursor(1154) CRTC[64]: request moving cursor to (956,596) in (1920x1200)
可以看出的信息为,1.解析配置文件,2.找到drm支持的crtc,3.找到对应的plane,4.将配置指定的plane配置为光标使用的plane
查看内核drm光标这块的日志,需要打开drm相关的配置宏
drm的sys节点为: /sys/module/drm/parameters/debug
对应的可选值如下:
Bit 0 (0x01) will enable CORE messages (drm core code) Bit 1 (0x02) will enable DRIVER messages (drm controller code) Bit 2 (0x04) will enable KMS messages (modesetting code) Bit 3 (0x08) will enable PRIME messages (prime code) Bit 4 (0x10) will enable ATOMIC messages (atomic code) Bit 5 (0x20) will enable VBL messages (vblank code) (int)
因为光标通过atomic的api实现的,通过借助本身调试atomic的debug,可以提供一点有用的信息。
echo 0x10 > /sys/module/drm/parameters/debug 打开drm ATOMIC 调试信息
日志如下:
[drm:drm_atomic_state_init] Allocated atomic state 000000009ea89924 [drm:drm_atomic_get_plane_state] Added [PLANE:71:Cluster0-win0] 000000001d2516b5 state to 000000009ea89924 [drm:drm_atomic_get_crtc_state] Added [CRTC:85:video_port0] 000000002526233c state to 000000009ea89924 [drm:drm_atomic_set_fb_for_plane] Set [FB:202] for [PLANE:71:Cluster0-win0] state 000000001d2516b5 [drm:drm_atomic_check_only] checking 000000009ea89924 [drm:drm_atomic_commit] committing 000000009ea89924 [drm:drm_atomic_state_default_clear] Clearing atomic state 000000009ea89924 [drm:__drm_atomic_state_free] Freeing atomic state 000000009ea89924
可以发现,默认使用plane 71,crtc 85,fb202
查看sys下的信息,是否正常对应
1.cat /sys/kernel/debug/dri/0/framebuffer | grep drm-cursor -B 1
framebuffer[202]: allocated by = drm-cursor[85]
2.cat /sys/kernel/debug/dri/0/state | grep drm-cursor -B 3
plane[71]: Cluster0-win0 crtc=video_port0 fb=208 allocated by = drm-cursor[85]
3.cat /sys/kernel/debug/dri/0/summary
Cluster0-win0: ACTIVE win_id: 4 format: AB24 little-endian (0x34324241)[AFBC] SDR[0] color_space[0] glb_alpha[0xff] rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 csc: y2r[0] r2y[1] csc mode[1] zpos: 7 src: pos[0, 0] rect[64 x 64] dst: pos[0, 0] rect[64 x 64] buf[0]: addr: 0x0000000001825000 pitch: 256 offset: 0
由此,可以确定,硬件光标正常启动,大小为64x64
# debug=1 # log-file= # hide=1 # hide cursors # atomic=0 # disable atomic drm API # max-fps=60 # allow-overlay=1 # allowing overlay planes # prefer-afbc=0 # prefer plane with AFBC modifier supported # num-surfaces=8 # num of egl surfaces to avoid edge moving corruption # prefer-plane=65 # prefer-planes=61,65 # crtc-blocklist=64,83
debug=1 默认会将log输出在 /var/log/drm-cursor.log log-file=/tmp/drm-cursor.log 手动指定log文件 hide=1 是否隐藏鼠标 atomic=1 这一项没什么用,drm cursor默认使用atomic 的api max-fps=60 光标指针的显示帧数。 allow-overlay=1 允许使用overlay层。如果不设置,则默认不使用overlay,也就是只使用cursor层 prefer-afbc=0 是否使用arm的 afbc功能的overlay层 num-surfaces=8 eglCreateWindowSurface 创建的离屏缓冲区的数量 prefer-plane=65 默认设置的plane prefer-planes=61,65 可选的多个plane crtc-blocklist=83 阻止在哪些crtc上允许光标层
安装libdrm-tests
modeprint rockchip Crtc id : 64 x : 0 y : 0 width : 1920 height : 1200 mode : 0x55ad634f8c gamma size : 1024
可以看到当前显示挂的crtc在64上
modetest -M rockchip -p 查看plane 61 64 117 0,0 0,0 0 0x00000001 formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 props: 7 type: flags: immutable enum enums: Overlay=0 Primary=1 Cursor=2 value: 2 8 SHARE_ID: flags: immutable range values: 0 4294967295 value: 61 63 rotation: flags: bitmask values: rotate-0=0x1 reflect-y=0x20 value: 1 56 FEATURE: flags: immutable bitmask values: scale=0x1 alpha=0x2 hdr2sdr=0x4 sdr2hdr=0x8 afbdc=0x10 pdaf_pos=0x20 value: 18
可以看到plane 61 是挂载crtc64上的cursor层
测试硬件光标验证:
systemctl stop lightdm modetest -M rockchip -s 91@64:1920x1200 -C
如果出现颜色图,则说明libdrm能够正常使用硬件光标
指定plane
modetest -M rockchip -P 61@64:1920x1200
如果出现颜色图,则说明libdrm能够正常指定plane
首先需要在内核打开硬件光标层,找到你需要配置硬光标的vp,添加 cursor-win-id,例如我的显示是在vp2上面
然后需要安装libdrm-cursor软件和libdrm-tests软件
首先使用libdrm-tests软件查看实际使用中的crtc层
命令:modetest
这里可以看到68、85、102是未使用的crtc,实际连接到fb正在使用的crtc是133
所以我们需要在软件中配置屏蔽掉前面三个crtc层,让鼠标层能够正确匹配到133上面
命令:vim /etc/drm-cursor.conf
修改前
修改后
这里我们把硬光标层打开了,所以就不需要overlay层,把它注释掉,然后打开crtc-blocklist选项,把其他不用的ctrc层屏蔽掉,重启机器就能正常使用硬件光标了。crtc层具体的id需要根据实际情况使用modetest命令确定。
《Linux DRM Developer's Guide》https://landley.net/kdocs/htmldocs/drm.html
《Linux Kernel Doc》 https://www.kernel.org/doc/
《DRM-wikipedia》 https://en.wikipedia.org/wiki/Direct_Rendering_Manager 《Linux GPU Driver Developer's Guide》https://01.org/linuxgraphics/gfx-docs/drm/gpu/index.html
《DRM-KMS Doc》 https://kernel.readthedocs.io/en/latest/gpu/drm-kms.html#mode-setting 《KMS简介》 https://events.static.linuxfound.org/sites/events/files/slides/brezillon-drm-kms.pdf
《EGL Reference Pages》https://www.khronos.org/registry/EGL/sdk/docs/man/
《Linux User and Programmer's Manual》https://www.systutorials.com/docs/linux/man/3-drmModeGetResources/
《Nvidia drm api参考》https://docs.nvidia.com/jetson/l4t-graphics/group__direct__rendering__manager.html
《Linux How to的KMS简介》https://www.linuxhowtos.org/manpages/7/drm-kms.htm
《freedesktop.org的kms文档》https://dri.freedesktop.org/docs/drm/gpu/drm-kms.html
在RK的sdk中有制作update的工具二进制,但是这些二进制是只能运行在x86平台上。而我们的工作环境大部分情况下是在arm64上的,本篇文章提供了在arm64下能够打包update的方法。
https://gitlab2.kylin.com/shanghai-team/create-rootfs/-/tree/kybuild/tool/src
如上是工具的源码,可以通过make的方式在各个平台上编译生成afptool和img_maker两个二进制
目录附带了一个mkupdate.sh脚本,通过此脚本可以自动打包固件,如下为脚本内容
#!/bin/bash # Self Test CHIP=$1 usage() { echo "Usage: ./$(basename $0) rk3399 ./$(basename $0) rk3568 ./$(basename $0) rk3588" } if ! [ "${CHIP}" = "rk3399" ] && ! [ "${CHIP}" = "rk3568" ] && ! [ "${CHIP}" = "rk3588" ] then echo -e "\e[32mUnsupported Chip\e[0m" usage exit fi [ ! -d ./Image ] && echo "Cannot found Image directory" && exit ./afptool -pack ./ firmware_afp.img <<! # rk3399 ./img_maker -rk3399 ./Image/MiniLoaderAll.bin firmware_afp.img update.img # rk3568 ./img_maker -rk3588 ./Image/MiniLoaderAll.bin firmware_afp.img update.img # rk3588 ./img_maker -rk3568 ./Image/MiniLoaderAll.bin firmware_afp.img update.img ! ./img_maker -${CHIP} ./Image/MiniLoaderAll.bin firmware_afp.img update.img
在mkupdate.sh的当前目录,需要存在一个打包文件,里面描述了需要打包固件的名字和路径,该文件有自行编写,示例如下:
package-file package-file bootloader Image/MiniLoaderAll.bin parameter Image/parameter.txt trust Image/trust.img uboot Image/uboot.img boot Image/boot.img rootfs Image/rootfs.img
上述描述了需要打包的内容如,自身package-file,bootloader,分区表parameter,trust固件,uboot固件,内核固件boot,根文件系统rootfs
mkupdate.sh需要一个Image目录,其中存放打包中需要的文件,我们要将需要打包的二进制手动拷贝到当前目录的Image目录内后方可运行测试脚本,其文件对应package-file的指定内容
分区文件主要指定了固件的分区细节,该文件根据平台自定义,rk3588的分区文件示例如下
FIRMWARE_VER: 1.0 MACHINE_MODEL: RK3588 MACHINE_ID: 007 MANUFACTURER: RK3588 MAGIC: 0x5041524B ATAG: 0x00200800 MACHINE: 0xffffffff CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 TYPE: GPT CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00040000@0x00008000(boot:bootable),0x01508000@0x00048000(rootfs),0x00600000@0x01550000(data),0x00600000@0x01b50000(work),-@0x02150000(userdata:grow) uuid:rootfs=614e0000-0000-4b53-8000-1d28000054a9 uuid:boot=7A3F0000-0000-446A-8000-702F00006273
一般情况下只修改分区文件的cmdline中的分区地址,其他无需改变
mkupdate.sh会在本地目录生成一个update.img,该文件即rk平台的固件格式,可以通过RK的工具进行整包烧录
https://gitlab2.kylin.com/shanghai-team/create-rootfs/-/tree/kybuild/tool/src/windows-tool
上述提供了2.92版本的烧录工具,我们自行开发的arm64的打包工具在此版本正常工作,其他版本尚未验证,无法保障。因为工具为自行修改,和RK的实现不一致,故可能出现windows烧录工具的版本不一样导致出现的烧录异常问题。
如RK更新了超过2.92版本的烧录工具,后续我们会再自行验证和兼容。
随着AI智能的发展,继传统的CPU和GPU之后,又推出了许多更适用于AI学习的芯片,本文就NPU芯片如何在RK平台提供的SDK基础上开发做一个介绍。
NPU全称为Neural network Processing Unit,即神经网络处理器。
可以在110服务器获取 /home/develop/rk/project/Rockchip_RK356X_RKNN_SDK_V0.7.0_20210402
这里 RKNN_API_for_RK356X 里面主要包含了linux和android系统对于如何使用模型文件调用NPU进行AI运算的demo。
rknn-toolkit 是用于将其他神经网络训练架构模型如何转化为 rknn 架构模型的demo,它分为了docker和nodocker版本,建议使用docker版,因为nodeocker版本对于python-3和pip软件版本的要求比较严格,环境不太好搭建。
这里我使用含docker版本举例。
正常加载后,就可以进入docker环境 如下,
docker run -t -i --privileged -v /home/kylin/:/home/kylin/ rknn-toolkit2:0.7.0 /bin/bash
执行后命令行的前缀会改变。
进入docker环境后,进入 rknn-toolkit2-0.7.0/examples/
目录下,可以看到包含了一些以比较常见的神经网络训练框架命名的文件夹。
这里我们以caffe为例,将 caffemodule 转换为 rknnmodule。
进入 caffe/vgg-ssd/ 文件夹,其是转换 caffemodule 的一个demo 。里面使用了 deploy_rm_detection_output.prototxt 文件中提供的图层模型,把 caffemodule 通过 rknn.api 转换为了 rknnmodule 。重点包含以下几个函数:
可以看到流程和RK给出的文档中一致,按照步骤操作即可。
这里会需要一个 caffemodule 的训练模型,可以通过
https://eyun.baidu.com/s/3jJhPRzo , password is rknn
进行下载。
然后执行 python test.py
运行脚本,执行成功后就可以看到文件夹中多了 .rknn 为后缀的文件,它就是rk平台的模型。
这里就完成了模型的转换,过程中可能会根据模型效率/准确度的需求修改一些参数,可以参考SDK中的文档进行调整。
上面我们已经得到了后缀为 .rknn 的模型文件,现在我们就来讲解如何在RK平台使用模型通过NPU进行图像的解析操作。其实上面在转换模型时已经使用python脚本进行了图像解析的示例,本章介绍demo中通过C语言调用RKNN接口的过程。
进入 RKNN_API_for_RK356X_v0.7_20210402/examples/rknn_ssd_demo/
文件夹,可以看到
我们先看看源码调用流程
跟文档流程一致,其中多了一步 postProcessSSD ,这是将结果中的值转换为图片中的坐标点,用于后面画框所用。
看完了源代码我们可以编译应用。这里的环境是rk3568开发板+麒麟v10系统,所以直接执行
build-linux.sh
脚本。
完成后可以看到生成了 rknn_ssd_demo 的二进制文件,接下来我们使用它看看效果。
执行
./build/build_linux_aarch64/rknn_ssd_demo ./model/ssd_inception_v2.rknn ./model/road.bmp
第一个参数是我们生成的二进制文件
第二个参数是 .rknn 为后缀的module模型文件
第三个参数是要被检测的图片。执行后在本地文件夹中生成了 out.jpg 图片
输出如下
可以看到,模型在上图中成功识别到了行人和车辆的位置。
如何判断是否调用到了NPU去处理的上面图像呢。我们可以通过中断:
如果调用到NPU,中断号会增加。
内核中还提供了调整NPU频率的接口:
查看NPU支持的模式,默认为 simple_ondemand
查看NPU当前的频率
echo performance > /sys/devices/platform/fde40000.npu/devfreq/fde40000.npu/governor
调整NPU的工作模式为性能模式(保持最大频率)
查看NPU当前工作状态,@之前为占用率,之后为当前频率
根据内核log,可以判断NPU是否被加载成功,从中也可以看出NPU包含了iommu模块,对内存读写进行了优化。
瑞芯微平台通常通过烧录的方式来更新系统,但是在一些项目评估的过程中,客户经常需要要求能够安装系统,为了让RK平台支持U盘安装,需要稍微在uboot进行修改,使得uboot阶段能够正常的加载到U盘的uImage,uInitrd,dtb文件即可。主要介绍如下
这里是U盘安装内U盘文件内容,主要分为
boot目录 内包含uboot启动所需要的uImage和uInitrd以及sysboot启动需要的extlinux.conf。主要用于启动安装系统 casper目录 内包含rockchip平台所需要的镜像文件 logo.bmp和logo_kernel.bmp 是安装中所需要的开机图片,如果需要使用其他开机图片,可以单独修改替换此图片即可 autorun.inf 和 kylin.ico 是U盘自动运行文件和U盘图标文件
文件内容如下
default install menu title Installer prompt 0 timeout 10 label install menu label kylinos installer linux /boot/uImage initrd /boot/uInitrd append console=tty0 loglevel=0
这里设置默认启动了uImage和uInitrd
文件内容如下
FIRMWARE_VER: 1.0 MACHINE_MODEL: RK3588 MACHINE_ID: 007 MANUFACTURER: RK3588 MAGIC: 0x5041524B ATAG: 0x00200800 MACHINE: 0xffffffff CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 TYPE: GPT CMDLINE: mtdparts=rk29xxnand:0x00004000@0x00004000(uboot),0x00002000@0x00008000(misc),0x00080000@0x0000a000(boot:bootable),0x00800000@0x0008a000(backup),0x01400000@0x0088a000(rootfs),-@0x01c8a000(userdata:grow) uuid:rootfs=614e0000-0000-4b53-8000-1d28000054a9
这里是安装过程中,需要对系统分区的描述文件。同RK平台的parameter.txt文件
这是支持机器的uboot通过默认usb启动的uboot镜像,如果需要使用U盘安装,则需要更新原先uboot镜像为此镜像。此镜像基于uboot额外做了如下措施
uboot内置设备树,确保uboot下usb功能正常 uboot内默认usb启动优先 uboot内默认支持u盘内开机图片显示
这是RK平台的loader 二进制文件,这里需要放置两个文件,安装时是用的idblock.bin,烧录的时候是用的rk3588_spl_loader_xxx.bin。
diff --git a/configs/rockchip-linux.config b/configs/rockchip-linux.config index 3003d81..cee40ce 100644 --- a/configs/rockchip-linux.config +++ b/configs/rockchip-linux.config @@ -5,3 +5,6 @@ CONFIG_DM_PCA953X=y CONFIG_SPL_FIT_IMAGE_KB=4096 CONFIG_CHECK_VERSION_CHOOSE_DTB=y CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY=y +CONFIG_EMBED_KERNEL_DTB=y +CONFIG_EMBED_KERNEL_DTB_PATH="dts/rockchip-evb_rk3588.dtb" +CONFIG_EMBED_KERNEL_DTB_ALWAYS=n 2.2 开机显示特定logo diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h index 9ed6b98..c1ec5e2 100644 --- a/include/config_distro_bootcmd.h +++ b/include/config_distro_bootcmd.h @@ -29,6 +29,7 @@ #define BOOTENV_SHARED_BLKDEV_BODY(devtypel) \ "if " #devtypel " dev ${devnum}; then " \ "setenv devtype " #devtypel "; " \ + "rockchip_show_logo;" \ "run scan_dev_for_boot_part; " \ "fi\0"
diff --git a/include/configs/rockchip-common.h b/include/configs/rockchip-common.h index 002dcd6..0566ece 100644 --- a/include/configs/rockchip-common.h +++ b/include/configs/rockchip-common.h @@ -162,7 +162,14 @@ "setenv devtype ramdisk; setenv devnum 0;" \ "fi; \0" -#if defined(CONFIG_AVB_VBMETA_PUBLIC_KEY_VALIDATE) +#if defined(CONFIG_EMBED_KERNEL_DTB) +#define RKIMG_BOOTCOMMAND \ + "run usb_boot;" \ + "boot_android ${devtype} ${devnum};" \ + "boot_fit;" \ + "bootrkp;" \ + "run distro_bootcmd;" +#elif defined(CONFIG_AVB_VBMETA_PUBLIC_KEY_VALIDATE)
修改: drivers/tty/vt/selection.c 修改: drivers/tty/vt/vt.c 修改: drivers/video/fbdev/core/bitblit.c 修改: drivers/video/fbdev/core/fbcon.c 修改: drivers/video/fbdev/core/fbcon.h 修改: drivers/video/fbdev/core/fbcon_ccw.c 修改: drivers/video/fbdev/core/fbcon_cw.c 修改: drivers/video/fbdev/core/fbcon_rotate.c 修改: drivers/video/fbdev/core/fbcon_ud.c 修改: include/linux/fb.h 修改: include/linux/font.h 修改: lib/fonts/Kconfig 修改: lib/fonts/Makefile 新文件: lib/fonts/font_cjk_16x16.c 新文件: lib/fonts/font_cjk_16x16.h 新文件: lib/fonts/font_cjk_32x32.c 新文件: lib/fonts/font_cjk_32x32.h 修改: lib/fonts/fonts.c
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-pc.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-pc.dtsi index e49f5fa..ff182b1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-pc.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-pc.dtsi @@ -10,7 +10,7 @@ / { chosen: chosen { - bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 root=PARTLABEL=rootfs rootfstype=ext4 ro rootwait overlayroot=device:dev=PARTLABEL=userdata,fstype=ext4,mkfs=1 coherent_pool=1m systemd.gpt_auto=0 cgroup_enable=memory swapaccount=1 net.ifnames=0"; + bootargs = "earlycon=uart8250,mmio32,0xfeb50000 irqchip.gicv3_pseudo_nmi=0 root=PARTLABEL=rootfs rootfstype=ext4 ro rootwait overlayroot=device:dev=PARTLABEL=userdata,fstype=ext4,mkfs=1 coherent_pool=1m systemd.gpt_auto=0 cgroup_enable=memory swapaccount=1 net.ifnames=0"; };
diff --git a/arch/arm64/configs/linux.config b/arch/arm64/configs/linux.config index 9a23fc2..6c569cd 100644 --- a/arch/arm64/configs/linux.config +++ b/arch/arm64/configs/linux.config @@ -6,6 +6,7 @@ CONFIG_TOUCHSCREEN_HX83102=y CONFIG_CAN=y CONFIG_CANFD_ROCKCHIP=y CONFIG_OVERLAY_FS=y +CONFIG_EXFAT_FS=y CONFIG_SND_SOC_FIREFLY_MULTICODECS=y
diff --git a/boot.its b/boot.its index 755005c..885bfd1 100644 --- a/boot.its +++ b/boot.its @@ -45,6 +45,21 @@ algo = "sha256"; }; }; + + ramdisk { + description = "Compressed Initramfs"; + data = /incbin/("ramdisk"); + type = "ramdisk"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x00000000>; + entry = <0x00000000>; + + hash { + algo = "sha256"; + }; + }; }; configurations { @@ -55,6 +70,7 @@ fdt = "fdt"; kernel = "kernel"; multi = "resource"; + ramdisk = "ramdisk";
参考仓库 https://gitlab2.kylin.com/shanghai-team/ramdisk/-/tree/ramdisk-trial
开机运行如下
function start_install() { parse_cmdline prepare_iso if [ "${TRIAL}" == "true" ];then overlay_start return 0 fi while true do clear info "感谢使用麒麟系统,请选择安装模式" info "1) 试用" info "2) 安装" info "3) 调试" info "4) 重启" info "请输入:" read -s -n1 -t 30 -r option case ${option} in 1) overlay_start return 0 ;; 2) _start_install ;; 3) debug_shell ;; 4) echo b > /proc/sysrq-trigger info "你的系统不支持重启" ;; *) [ -z ${option} ] && _start_install ;; esac done }
至此,已经可以获得基于RK3588上使用U盘安装系统的全套方案。客户如果需要设备支持U盘安装,则只需要设备的uboot代码和kernel代码即可正常配置。下面链接版本是支持一款板卡的uboot安装ISO镜像。