bluetoothctl是bluez协议的调试工具,用于前期的蓝牙基本功能的验证和测试。本文基于bluetoothctl工具,介绍了如何使用其进行蓝牙的基本通讯
root@kylin:/home/kylin# dpkg -l | grep bluez ii bluez 5.53-0kylin3k4 arm64 Bluetooth tools and daemons ii bluez-obexd 5.53-0kylin3k4 arm64 bluez obex daemon
root@kylin:/home/kylin# hciconfig hci0: Type: Primary Bus: UART BD Address: 9C:71:D9:BC:DD:BF ACL MTU: 1021:8 SCO MTU: 64:1 UP RUNNING PSCAN RX bytes:441417 acl:200 sco:0 events:10560 errors:0 TX bytes:12117 acl:184 sco:0 commands:173 errors:0
如果蓝牙状态不是 “UP RUNNING PSCAN” 则需要手动启动蓝牙
hciconfig hci0 up
root@kylin:/home/kylin# rfkill list 0: bt_default: Bluetooth Soft blocked: no Hard blocked: no 1: phy0: Wireless LAN Soft blocked: no Hard blocked: no 2: brcmfmac-wifi: Wireless LAN Soft blocked: no Hard blocked: no 3: hci0: Bluetooth Soft blocked: no Hard blocked: no
如果蓝牙被rfkill block,则需要通过rfkill命令unblock,否则蓝牙功能仍是被系统关闭的
rfkill unblock hci0
hcitool lescan LE Scan ... 21:0E:38:14:CE:18 (unknown) 5F:5A:FC:85:1C:05 (unknown)
如果能够探测到低功耗蓝牙设备,代表蓝牙的基本功能正常
root@kylin:/home/kylin# l2ping 54:09:10:38:28:97 Ping: 54:09:10:38:28:97 from 9C:71:D9:BC:DD:BF (data size 44) ... 0 bytes from 54:09:10:38:28:97 id 0 time 9.91ms 0 bytes from 54:09:10:38:28:97 id 1 time 8.55ms 0 bytes from 54:09:10:38:28:97 id 2 time 8.48ms
root@kylin:/home/kylin# bluetoothctl Agent registered [bluetooth]#
[bluetooth]# scan on
等到扫到自己的蓝牙设备之后,可以关闭蓝牙扫描
[bluetooth]# scan off
[bluetooth]# devices Device 54:09:10:38:28:97 iPhone
由上可以发现,设备已经搜到蓝牙名字为iPhone,MAC为 54:09:10:38:28:97 的设备
[bluetooth]# agent on Agent is already registered [bluetooth]# default-agent Default agent request successful
[bluetooth]# trust 54:09:10:38:28:97 [CHG] Device 54:09:10:38:28:97 Trusted: yes Changing 54:09:10:38:28:97 trust succeeded [bluetooth]# pair 54:09:10:38:28:97 Attempting to pair with 54:09:10:38:28:97 [CHG] Device 54:09:10:38:28:97 Connected: yes Request confirmation [agent] Confirm passkey 886360 (yes/no): yes
如果之前之前配对过,就无需配对,如果仍选择配对,会报如下错误,但不影响使用
[bluetooth]# pair 54:09:10:38:28:97 Attempting to pair with 54:09:10:38:28:97 Failed to pair: org.bluez.Error.AlreadyExists
查看已经配对过的设备
[iPhone]# paired-devices Device 54:09:10:38:28:97 iPhone
[bluetooth]# connect 54:09:10:38:28:97 Attempting to connect to 54:09:10:38:28:97 [CHG] Device 54:09:10:38:28:97 Connected: yes [iPhone]#
如果蓝牙正常连接成功,会出现蓝牙名字的终端,如 [iPhone]
menu gatt [Blank]# list-attributes Characteristic (Handle 0x9240) /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0014/char0015 00002222-0000-1000-8000-00805f9b34fb Unknown
select-attribute /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0023/char0024 [iPhone:/service0014/char0015]# attribute-info Characteristic - Unknown UUID: 00002222-0000-1000-8000-00805f9b34fb Service: /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0014 Flags: read Flags: write Flags: extended-properties Flags: reliable-write
[iPhone:/service0023/char0024]# write 1 Attempting to write /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0023/char0024 [iPhone:/service0023/char0024]# read Attempting to read /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0023/char0024 [CHG] Attribute /org/bluez/hci0/dev_60_B7_4C_51_AC_D7/service0023/char0024 Value: 01 . 01 .
1.下载LightBlue 2.选择Virtual Devices 3.点击+ 4.选择默认的profile,这里默认可以是blank 5.点击Blank,进去服务 6.进入默认的uuid 2222 7.修改uuid,修改描述,修改属性 8.利用bluetoothctl进行读写操作验证
最近在某个项目上,需要使用字符终端的显示,并且还需要显示中文。通过网络发现,Framebuffer Console支持中文的功能默认却没在linux的主分支,但是github上有人在维护这个patch。https://github.com/zhmars/cjktty-patches 。鉴于此,本文介绍一下Framebuffer Console的配置方法以及cjktty的配置步骤
# # Console display driver support # CONFIG_DUMMY_CONSOLE=y CONFIG_DUMMY_CONSOLE_COLUMNS=80 CONFIG_DUMMY_CONSOLE_ROWS=25 CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
这样就打开了内核的字符终端
对应defconfig打开CONFIG_FRAMEBUFFER_CONSOLE即可
make bindeb-pkg -j64
这里编译出来会产生如下包,安装即可
../linux-headers-5.10.66_5.10.66-13_arm64.deb ../linux-headers-5.10.66_5.10.66_arm64.deb ../linux-image-5.10.66_5.10.66-13_arm64.deb ../linux-image-5.10.66_5.10.66_arm64.deb ../linux-image-5.10.66-dbg_5.10.66-13_arm64.deb ../linux-image-5.10.66-dbg_5.10.66_arm64.deb ../linux-libc-dev_5.10.66-13_arm64.deb ../linux-libc-dev_5.10.66_arm64.deb
如果使用rk平台默认boot编译,可使用如下编译方法
export ARCH=arm64 export CROSS_COMPILE=aarch64-none-linux-gnu- export PATH=$PATH:$YOURSDK/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/ make rockchip_linux_defconfig make rk3588-evb1-lp4-v10-linux.img -j64
编译完成将生成boot.img
在正常进入系统之后,通过组合按键
Ctrl+Alt+F{1-6} 即可进入字符终端界面
但是,默认的字符界面,没办法正常的显示中文。原系统的中文的文件显示乱码。于是找到了cjktty
cjktty的patch仓库地址在 https://github.com/zhmars/cjktty-patches ,通过其README.md 可以发现其已经不断支持到最新内核上了
因为我们的内核版本是5.10,故拉取5.10的patch
wget https://raw.githubusercontent.com/zhmars/cjktty-patches/master/v5.x/cjktty-5.10.patch
打补丁就简单了
patch -p1 < cjktty-5.10.patch
CONFIG_FONT_CJK_16x16=y CONFIG_FONT_CJK_32x32=y
编译方法同上,使用时可以发现,该作者实际就是把两个原英文字符叠加成一个中文字符,从而显示了的中文字符
在使用shell环境的时候,难免会遇到对文本进行操作的情况,shell提供了一些功能强大的文本操作命令,这里做了简单汇总,有助于了解和学习这些工具,提高工作中处理文本的效率
grep是经常使用的文本查找工具,下面列举grep常用的一些命令。当然,也有比grep更好的工具ripgrep。这个可以后续探索
grep -vE "^#|^$" /etc/ssh/sshd_config
-v是选中不匹配的行,-E是明确使用正则 ^$ 是没有数据的行 ^#是以#开头的行
dmesg | grep -i cfg80211 -A 5 dmesg | grep -i cfg80211 -B 5 dmesg | grep -i cfg80211 -C 5
A after 之后的5行, B before 之前的5行, C 是A+B的合计,前后5行
-i 忽略大小写
cut是一个字符截取命令,其命令在处理一些字符串上,可以方便的对一些字符串直接截取。如果文本内容格式比较规律的话,也可以用来筛选指定分隔符下的内容
ifconfig | grep -vE "^ |^$" | cut -d ' ' -f 1 | cut -d ":" -f 1
-d 是指定分隔符, -f 是指定某个域。
-d ' ' -f 1 是以单个空格为分隔符找到第一个域
cut -d ":" -f 1 是以冒号为分隔符找到第一个域
dmesg | cut -b -15
sed是重要的字符替换工具,也就流编辑器,可以方便的直接替换一些文本内容,从而不用打开文本去处理,这使得在shell内对文本替换来说十分方便
-e :添加一个命令,多个sed命令可以用这个 -i :直接修改文件内容 -n :取消自动打印,只打印模式匹配的行 -E :支持扩展表达式;
cat filename | sed -e“s/text/replace/g”-e“s/text/replace/g”
-e 允许运行多个命令,s///g是替换的语法, s指的搜索,g指的全局。也就是搜索全局,然后统一替换
cat filename | sed -e "4a help"
在文本第4行后添加help作为新的一行
cat filename | sed -e "4i help"
在文本第4行前添加help作为新的一行
cat filename | sed -e "/^$/d"
删除空格行
cat filename | sed "s/[a-z]\|[A-Z]/[&]/g"
对文本中的所有字符加上[] 。&指的是上一次搜索到的值。 | 是或,意思可以多条规则匹配
echo a b | sed -E -e "s/([a-z]) ([a-z])/\2 \1/"
()可以声明这是一个字串,()声明的字串,可以通过\1 \2等通配来引用。
-E 代表使用正则,如果不加-E 则使用()的时候需要转换成 () 那么匹配一个a-z的字符需要这样表示 ([a-z])
那么不加-E 的效果如下
echo a b c | sed -e "s/\([a-z]\) \([a-z]\) \(c\)/\3 \2 \1/"
sort是重要的字符和数字排序工具,可以直接对文本进行升序和降序操作
grep -n '.' /etc/os-release | sort -t ':' -k1 -nru
-t 指定分隔符
-k 指定分隔符划分的域
-n 指定排序方式为数字(默认为字符)
-r 指定反序
cat /etc/os-release | sort -t '=' -k 1.2 -u
-u 去掉重复的排序行
uniq是找重复的工具,可以快速的识别出文本中完全重复的行,并记录次数
cat Readme.md | uniq -dci
cat Readme.md | uniq -u
-d 是找到完全重复的行
-c 是统计重复次数
-i 是忽略大小写
-u 是打印不重复的行
awk模式是模式扫描及处理语言,也就是说文本处理规则太多了,我定义一个语言,你们想怎么匹配就怎么匹配
NF 一条记录的字段的数目 NR 行号,当前处理的文本行的行号 FS 定义间隔符 OFS 定义输出字段分隔符,默认空格 RS 输入记录分隔符,默认换行
awk默认格式为
awk '{pattern + action}' {filenames}
这里'{}'里面就是代码域,用于编写awk代码的地方。
最简单的打印可以如下
echo | awk '{print "Hello World"}'
直接打印hello world
echo Hello World | awk '{print $1" "$2}'
打印传过来的参数
cat /etc/passwd | awk 'BEGIN{count=0;print "begin is:" count} {count=count+1;print count} END{print "end is:"count}'
这里统计了passwd的文件行数
awk不仅默认{}里面填写代码,也可以在BEGIN和END宏里面填写代码,BEGIN填写的代码,在{}前运行,END填写的代码在{}后运行
ll |awk 'BEGIN {byte=0;print "统计当前目录占用字节数"} {byte=byte+$5;} END{print "当前目录字节数为:",byte}'
这里统计了当前目录的字节数,不包括下级目录
cat /etc/passwd | awk '{if(NR>=10 && NR<=20) print $1}'
这里取passwd文件的第10行到20行内容
'{}'的if语句可以和C语言一样,print可以和python语言一样。
cat /etc/passwd | awk -F '' 'BEGIN{count=0} {for(i=1; i<=NF; i++) count++} END{print count}'
这里对每个字符做计数,最后打印存在的字符总数。
echo abcdefg | awk -F '' '{i=1; while(i<=length($0)) {print $i;i++}}'
这里对一行abcdefg转换成一个字母多行
echo "1 2,3:4" | awk -F [" ",:] '{print $1$2$3$4}'
以空格,逗号,冒号作为分隔符打印第1,2,3,4个匹配的内容
echo "1 2,3:4" | awk BEGIN{'FS="[ ,:]"} {print $1$2$3$4}'
FS宏也可以代替-F参数,需要注意的是,FS参数需要在后面加上"" 双引号
这里介绍了grep sort uniq awk sed cut一共六个命令,每个命令都可以搭配起来使用。这里简要介绍一下在哪些情况下可以用哪些命令
1.搜索一些匹配单词的时候,可以用grep,例如 grep -nr text filename 2.面临排序问题的时候,可以用sort, 例如 sort -r 3.去重复行的时候,可以用uniq, 例如 uniq -u 4.批量替换的时候,可以用sed,例如 sed -i “s/text/replace/g” 5.对特定格式字符串进行截取的时候,可以用cut ,例如 cut b 1-3 6.在对一些列的域做划分的时候,可以用awk , 例如 awk -F ‘:’ '{print $1}' 7.对文本进行高级编辑操作的时候,awk都能完成.
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