瑞芯微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主板
在上一个章节中,我们申请GPIO中断,使用的是request_irq,但是request_irq绑定的中断服务程序指的是中断上文。在之前的中断视频中讲解了:中断分为俩个部分——中断上文和中断下文。本章节我们来学习中断下文的一种实现方式——tasklet。
在Linux内核中,tasklet是一种特殊的软中断机制,被广泛用于处理中断下文相关的任务。它是一种常见且有效的方法,在多核处理系统上可以避免并发问题。Tasklet绑定的函数在同一时间只能在一个CPU上运行,因此不会出现并发冲突。然而,需要注意的是,tasklet绑定的函数中不能调用可能导致休眠的函数,否则可能引起内核异常。
在Linux内核中,tasklet结构体的定义位于include/linux/interrupt.h头文件中。其原型如下:
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
typedef struct tasklet_struct tasklet_t;
tasklet_struct结构体包含以下成员:
此外,为了方便,还定义了tasklet_t类型作为struct tasklet_struct的别名。这样我们可以使用tasklet_t来声明tasklet变量,而不是直接使用struct tasklet_struct。
在Linux内核中,有一个用于静态初始化tasklet的宏函数:DECLARE_TASKLET。这个宏函数可以帮助我们更方便地进行tasklet的静态初始化。
宏函数的原型如下:
#define DECLARE_TASKLET(name,func,data) \
struct tasklet_struct name = { NULL,0,ATOMIC_INIT(0),func,data}
其中,name是tasklet的名称,func是tasklet的处理函数,data是传递给处理函数的参数。
初始化状态为使能状态。
如果tasklet初始化函数为非使能状态,使用以下宏定义:
#define DECLARE_TASKLET_DISABLED(name,func,data) \
struct tasklet_struct name = { NULL,0,ATOMIC_INIT(1),func,data}
其中,name是tasklet的名称,func是tasklet的处理函数,data是传递给处理函数的参数。
初始化状态为非使能状态。
下面是一个示例,展示了如何使用DECLARE_TASKLET宏函数进行tasklet的静态初始化:
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 静态初始化tasklet
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码
在上述示例中,my_tasklet是tasklet的名称,my_tasklet_handler是tasklet的处理函数,0是传递给处理函数的参数。但是需要注意的是,使用DECLARE_TASKLET静态初始化的tasklet无法在运行时动态销毁,因此在不需要tasklet时,应该避免使用此方法。如果需要在运行时销毁tasklet,应使用tasklet_init和tasklet_kill函数进行动态初始化和销毁,接下来我们来学习动态初始化函数。
在Linux内核中,可以使用tasklet_init函数对tasklet进行动态初始化。该函数原型为:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
其中,t是指向tasklet结构体的指针,func是tasklet的处理函数,data是传递给处理函数的参数
以下是一个示例,tasklet_init函数进行动态初始化如下所示:
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 声明tasklet结构体
static struct tasklet_struct my_tasklet;
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码
在示例中,我们首先定义了my_tasklet_handler作为tasklet的处理函数。然后,声明了一个名为my_tasklet的tasklet结构体。接下来,通过调用tasklet_init函数,进行动态初始化。
通过使用tasklet_init函数,我们可以在运行时动态创建和初始化tasklet。这样,我们可以根据需要灵活地管理和控制tasklet的生命周期。在不再需要tasklet时,可以使用tasklet_kill函数进行销毁,以释放相关资源。
在Linux内核中,可以使用tasklet_disabled函数来关闭一个已经初始化的tasklet。该函数的原型如下:
void tasklet_disable(struct tasklet_struct *t);
其中,t是指向tasklet结构体的指针。
以下是一个示例,使用tasklet_disable函数来关闭tasklet。
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 声明tasklet结构体
static struct tasklet_struct my_tasklet;
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 关闭tasklet
tasklet_disable(&my_tasklet);
// 驱动程序的其他代码
在上述示例中,我们首先定义了my_tasklet_handler作为tasklet的处理函数。然后,声明了一个名为my_tasklet的tasklet结构体,并使用tasklet_init函数对其进行初始化。最后,通过调用tasklet_disable函数,我们关闭了my_tasklet。
关闭tasklet后,即使调用tasklet_schedule函数触发tasklet,tasklet的处理函数也不会再被执行。这可以用于临时暂停或停止tasklet的执行,直到再次启用(通过调用tasklet_enable函数)。
需要注意的是,关闭tasklet并不会销毁tasklet结构体,因此可以随时通过调用tasklet_enable函数重新启用tasklet,或者调用tasklet_kill函数来销毁tasklet。
在Linux内核中,可以使用tasklet_enable函数来使能(启用)一个已经初始化的tasklet。该函数的原型如下
void tasklet_disable(struct tasklet_struct *t);
其中,t是指向tasklet结构体的指针。
以下是一个示例,展示如何使用tasklet_enable函数来使能tasklet:
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 声明tasklet结构体
static struct tasklet_struct my_tasklet;
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 使能tasklet
tasklet_enable(&my_tasklet);
// 驱动程序的其他代码
在上述示例中,我们首先定义了my_tasklet_handler作为tasklet的处理函数。然后,声明了一个名为my_tasklet的tasklet结构体,并使用tasklet_init函数对其进行初始化。最后,通过调用tasklet_enable函数,我们使能(启用)了my_tasklet。
使能tasklet后,如果调用tasklet_schedule函数触发tasklet,则tasklet的处理函数将会被执行。这样,tasklet将开始按计划执行其处理逻辑。
需要注意的是,使能tasklet并不会自动触发tasklet的执行,而是通过调用tasklet_schedule函数来触发。同时,可以使用tasklet_disable函数来临时暂停或停止tasklet的执行。如果需要永久停止tasklet的执行并释放相关资源,则应调用tasklet_kill函数来销毁tasklet。
在Linux内核中,可以使用tasklet_schedule函数来调度(触发)一个已经初始化的tasklet执行。该函数的原型如下:
void tasklet_schedule(struct tasklet_struct *t);
其中,t是指向tasklet结构体的指针。
以下是一个示例,展示如何使用tasklet_schedule函数来调度tasklet执行:
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 声明tasklet结构体
static struct tasklet_struct my_tasklet;
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 调度tasklet执行
tasklet_schedule(&my_tasklet);
// 驱动程序的其他代码
在上述示例中,我们首先定义了my_tasklet_handler作为tasklet的处理函数。然后,声明了一个名为my_tasklet的tasklet结构体,并使用tasklet_init函数对其进行初始化。最后,通过调用tasklet_schedule函数,我们调度(触发)了my_tasklet的执行。
需要注意的是,调度tasklet只是将tasklet标记为需要执行,并不会立即执行tasklet的处理函数。实际的执行时间取决于内核的调度和处理机制。
在Linux内核中,可以使用tasklet_kill函数来销毁一个已经初始化的tasklet,释放相关资源。该函数的原型如下:
void tasklet_kill(struct tasklet_struct *t);
其中,t是指向tasklet结构体的指针。
以下是一个示例,展示如何使用tasklet_kill函数来销毁tasklet:
#include
// 定义tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet处理逻辑
// ...
}
// 声明tasklet结构体
static struct tasklet_struct my_tasklet;
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
tasklet_disable(&my_tasklet);
// 销毁tasklet
tasklet_kill(&my_tasklet);
// 驱动程序的其他代码
在上述示例中,我们首先定义了my_tasklet_handler作为tasklet的处理函数。然后,声明了一个名为my_tasklet的tasklet结构体,并使用tasklet_init函数对其进行初始化。最后,通过调用tasklet_kill函数,我们销毁了my_tasklet。
调用tasklet_kill函数会释放tasklet所占用的资源,并将tasklet标记为无效。因此,销毁后的tasklet不能再被使用。
需要注意的是,在销毁tasklet之前,应该确保该tasklet已经被停止(通过调用tasklet_disable函数)。否则,销毁一个正在执行的tasklet可能导致内核崩溃或其他错误。
一旦销毁了tasklet,如果需要再次使用tasklet,需要重新进行初始化(通过调用tasklet_init函数)。在下一小节中我们将使用上述tasklet函数相关接口函数进行相应的实验。
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\32_tasklet\module。
本实验将实现注册显示屏触摸中断,每按当触摸LCD显示屏就会触发中断服务函数,在中断服务函数中调度中断下文tasklet处理函数,打印“This id test_interrupt”和“data is 1”。
在驱动程序中的模块初始化函数中,我们将GPIO转换为中断号,并使用request_irq函数请求中断,然后对tasklet进行初始化。在中断处理函数中,我们调度tasklet执行,使得当中断触发时,tasklet会被调度执行。在模块退出函数中,我们释放中断资源,并使能tasklet销毁tasklet。
编写完成的interrupt.c代码如下所示,添加的代码已加粗表示。
#include
#include
#include
#include
// #include
int irq;
struct tasklet_struct mytasklet;
// 定义tasklet处理函数
void mytasklet_func(unsigned long data)
{
printk("data is %ld\n", data);
// msleep(3000);
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This id test_interrupt\n");
tasklet_schedule(&mytasklet); // 调度tasklet执行
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;
}
// 初始化tasklet
tasklet_init(&mytasklet, mytasklet_func, 1);
return 0;
}
// 模块退出函数
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL);
tasklet_enable(&mytasklet); // 使能tasklet(可选)
tasklet_kill(&mytasklet); // 销毁tasklet
printk("bye bye\n");
}
module_init(interrupt_irq_init); // 指定模块的初始化函数
module_exit(interrupt_irq_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("topeet"); // 模块的作者
在上一小节中的interrupt.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:对于Makefile的内容注释已在上图添加,保存退出之后,来到存放interrupt.c和Makefile文件目录下,如下图(图41-1)所示:
图 41-1
然后使用命令“make”进行驱动的编译,编译完成如下图(图41-2)所示:
图 41-2
编译完生成interrupt.ko目标文件,如下图(图41-3)所示:
至此驱动模块就编译成功了。
开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图 41-4)所示:
insmod interrupt.ko
图 41-4
看到驱动加载之后,可以看到申请的中断号(113)被打印了出来,然后用手触摸连接的LVDS 7寸屏幕,触发中断服务程序,打印如下图(41-5)所示:
图 41-5
在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”和tasklet处理函数中添加的打印“data is 1”,说明成功执行了中断下文tasklet处理函数。
最后可以使用以下命令进行驱动的卸载,如下图(图图 41-6)所示:
rmmod interrupt
之前的理论章节我们强调说tasklet函数中不能调用休眠的函数,在此我们在上述驱动实验的基础上实验一下,驱动文件中添加休眠函数,如下(图 41-7)所示:
图 41-7
同理,进行编译驱动模块,卸载掉之前的驱动模块后,加载新编译的驱动模块,如下图(图 41-8)所示:
图 41-8
然后用手触摸连接的LVDS 7寸屏幕,打印如下图(41-9)所示,内核会崩溃。
图 41-9
至此,中断下文tasklet实验就完成了。