瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第五期_中断_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
在上一章节中对工作队列以及共享工作队列知识进行了学习,并使用共享队列进行了实验。共享队列是由内核管理的全局工作队列,自定义工作队列是由内核或驱动程序创建的特定工作队列,用于处理特定的任务。下面就让我们一起来进行自定义工作队列的学习吧。
在Linux内核中,结构体struct work_struct描述的是要延迟执行的工作项,定义在include/linux/workqueue.h当中,如下所示
- struct work_struct {
- atomic_long_t data; // 工作项的数据字段
- struct list_head entry; // 工作项在工作队列中的链表节点
- work_func_t func; // 工作项的处理函数
- #ifdef CONFIG_LOCKDEP
- struct lockdep_map lockdep_map; // 锁依赖性映射
- #endif
- };
这些工作组织成工作队列,内核使用struct workqueue_struct结构体描述一个工作队列,定义在include/linux/workqueue.h 当中,如下所示:
- struct workqueue_struct {
- struct list_head pwqs; // 工作队列上的挂起工作项列表
- struct list_head delayed_works; // 延迟执行的工作项列表
- struct delayed_work_timer dwork_timer; // 延迟工作项的定时器
- struct workqueue_attrs *unbound_attrs; // 无绑定工作队列的属性
- struct pool_workqueue *dfl_pwq; // 默认的池化工作队列
- ...
- };
在Linux内核中,create_workqueue函数用于创建一个工作队列,函数原型如下所示:
struct workqueue_struct *create_workqueue(const char *name);
参数name是创建的工作队列的名字。使用这个函数可以给每个CPU都创建一个CPU相关的工作队列。创建成功返回一个struct workqueue_struct类型指针,创建失败返回NULL。
如果只给一个CPU创建一个CPU相关的工作队列,使用以下函数。
#define create_singlethread_workqueue(name) \ alloc_workqueue("%s", WQ_SINGLE_THREAD, 1, name)
参数name是创建的工作队列的名字。使用这个函数只会给一个CPU创建一个CPU相关的工作队列。创建成功之后返回一个struct workqueue_struct 类型指针,创建失败返回NULL。
当工作队列创建好之后,需要将要延迟执行的工作项放在工作队列上,调度工作队列,使用queue_work_on函数,函数原型如下所示:
bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
该函数有三个参数,第一个参数是一个整数cpu,第二个参数是一个指向struct workqueue_struct的指针wq,第三个参数是一个指向struct work_struct的指针work。
该函数的返回类型是布尔值,表示是否成功调度工作队列。 queue_work_on函数还有其他变种,比如queue_work函数,这里略过,其实思路是一致的,用于将定义好的工作项立即添加到工作队列中,并在工作队列可用时立即执行。
如果要取消一个已经调度的工作,使用函数bool cancel_work_sync,函数原型如下所示:
bool cancel_work_sync(struct work_struct *work);
函数的作用是取消一个已经调度的工作,如果被取消的工作已经正在执行,则会等待他执行完成再返回。
在Linux 内核中,使用flush_workqueue函数将刷新该工作队列中所有已提交但未执行的工作项。函数原型如下所示:
void flush_workqueue(struct workqueue_struct *wq);
该函数参数是一个指向struct workqueue_struct类型的指针wq。函数的作用是刷新工作队列,告诉内核尽快处理工作队列上的工作。
如果要删除自定义的工作队列,使用destroy_workqueue函数,函数原型如下所示:
void destroy_workqueue(struct workqueue_struct *wq);
该函数参数是一个指向struct workqueue_struct类型的指针wq。
在下一小节中将使用上述工作队列API函数进行相应的实验。
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\35_workqueue_share\module。
本实验将实现注册显示屏触摸中断,每按当触摸LCD显示屏就会触发中断服务函数,在中断服务函数中提交工作项到工作队列中,打印“This id test_interrupt”和“This is test_work”。
在驱动程序中的模块初始化函数中,我们将GPIO转换为中断号,并使用request_irq函数请求中断,然后创建自定义工作队列,初始化工作项。当中断被触发时,中断处理函数被调用,并将工作项提交到自定义工作队列中,最终由工作项处理函数异步执行。编写完成的interrupt.c代码如下所示,添加的代码已加粗表示。
- #include
- #include
- #include
- #include
- #include
- #include
-
- int irq;
- struct workqueue_struct *test_workqueue;
- struct work_struct test_workqueue_work;
-
- // 工作项处理函数
- void test_work(struct work_struct *work)
- {
- msleep(1000);
- printk("This is test_work\n");
- }
-
- // 中断处理函数
- irqreturn_t test_interrupt(int irq, void *args)
- {
- printk("This is test_interrupt\n");
- // 提交工作项到自定义工作队列
- queue_work(test_workqueue, &test_workqueue_work);
- return IRQ_RETVAL(IRQ_HANDLED);
- }
-
- static int interrupt_irq_init(void)
- {
- int ret;
- irq = gpio_to_irq(101); // 将GPIO映射为中断号
- printk("irq is %d\n", irq);
-
- // 请求中断
- ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
- if (ret < 0)
- {
- printk("request_irq is error\n");
- return -1;
- }
-
- test_workqueue = create_workqueue("test_workqueue"); // 创建工作队列
- INIT_WORK(&test_workqueue_work, test_work); // 初始化工作项
-
- return 0;
- }
-
- static void interrupt_irq_exit(void)
- {
- free_irq(irq, NULL); // 释放中断
- cancel_work_sync(&test_workqueue_work); // 取消工作项
- flush_workqueue(test_workqueue); // 刷新工作队列
- destroy_workqueue(test_workqueue); // 销毁工作队列
- printk("bye bye\n");
- }
-
- module_init(interrupt_irq_init);
- module_exit(interrupt_irq_exit);
-
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("topeet");
在上一小节中的interrupt.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:
- export ARCH=arm64#设置平台架构
- export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
- obj-m += interrupt.o #此处要和你的驱动源文件同名
- KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
- PWD ?= $(shell pwd)
- all:
- make -C $(KDIR) M=$(PWD) modules #make操作
- clean:
- make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放interrupt.c和Makefile文件目录下,如下图(图45-1)所示:
然后使用命令“make”进行驱动的编译,编译完成如下图(图45-2)所示:
编译完生成interrupt.ko目标文件,如下图(图45-3)所示:
至此驱动模块就编译成功了,接下来进行测试。
开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图45-4)所示:
insmod interrupt.ko
加载驱动之后,可以看到申请的中断号被打印了出来,然后用手触摸连接的LVDS 7寸屏幕,打印如下图(45-5)所示:
在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”被打印了俩次,说明触发了2次中断上文,那么中断上文会调度2次中断下文,所以也会打印2次工作项处理函数中添加的打印“This is test_work”。
在按屏幕之后,立即输入ps -aux|grep test_workqueue命令可以查看自己创建的工作队列,如下(图 45-6)所示:
最后可以使用以下命令进行驱动的卸载,如下图(图45-7)所示:
rmmod interrupt
至此,自定义工作队列实验就完成了。