RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验

瑞芯微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主板


第41章 中断下文tasklet实验 

在上一个章节中,我们申请GPIO中断,使用的是request_irq,但是request_irq绑定的中断服务程序指的是中断上文。在之前的中断视频中讲解了:中断分为俩个部分——中断上文和中断下文。本章节我们来学习中断下文的一种实现方式——tasklet。

41.1 什么是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结构体包含以下成员:

  • next:指向下一个tasklet的指针,用于形成链表结构,以便内核中可以同时管理多个tasklet。
  • state:表示tasklet的当前状态。
  • count:用于引用计数,用于确保tasklet在多个地方调度或取消调度时的正确处理。
  • func:指向tasklet绑定的函数的指针,该函数将在tasklet执行时被调用。
  • data:传递给tasklet绑定函数的参数

此外,为了方便,还定义了tasklet_t类型作为struct tasklet_struct的别名。这样我们可以使用tasklet_t来声明tasklet变量,而不是直接使用struct tasklet_struct。

41.2 tasklet相关接口函数

41.2.1 静态初始化函数

在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函数进行动态初始化和销毁,接下来我们来学习动态初始化函数。

41.2.2 动态初始化函数

  在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函数进行销毁,以释放相关资源。

41.2.3 关闭函数

在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。

41.2.4 使能函数

在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。

41.2.5 调度函数

在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的处理函数。实际的执行时间取决于内核的调度和处理机制。

41.2.6 销毁函数

在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函数相关接口函数进行相应的实验。

41.3 实验程序的编写

41.3.1 驱动程序编写

本实验对应的网盘路径为: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"); // 模块的作者

41.4 运行测试

41.4.1 编译驱动程序

在上一小节中的interrupt.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:对于Makefile的内容注释已在上图添加,保存退出之后,来到存放interrupt.c和Makefile文件目录下,如下图(图41-1)所示:

图 41-1

然后使用命令“make”进行驱动的编译,编译完成如下图(图41-2)所示:

RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第1张图片

图 41-2

编译完生成interrupt.ko目标文件,如下图(图41-3)所示:

至此驱动模块就编译成功了。

41.4.2 运行测试

开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图 41-4)所示:

insmod interrupt.ko

图 41-4

看到驱动加载之后,可以看到申请的中断号(113)被打印了出来,然后用手触摸连接的LVDS 7寸屏幕,触发中断服务程序,打印如下图(41-5)所示:

RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第2张图片

图 41-5

在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”和tasklet处理函数中添加的打印“data is 1”,说明成功执行了中断下文tasklet处理函数。

最后可以使用以下命令进行驱动的卸载,如下图(图图 41-6)所示:

rmmod interrupt

之前的理论章节我们强调说tasklet函数中不能调用休眠的函数,在此我们在上述驱动实验的基础上实验一下,驱动文件中添加休眠函数,如下(图 41-7)所示:

RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第3张图片

图 41-7

同理,进行编译驱动模块,卸载掉之前的驱动模块后,加载新编译的驱动模块,如下图(图 41-8)所示:

RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第4张图片

图 41-8

然后用手触摸连接的LVDS 7寸屏幕,打印如下图(41-9)所示,内核会崩溃。

RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第5张图片

图 41-9

至此,中断下文tasklet实验就完成了。


RK3568驱动指南|第五期-中断-第41章中断下文tasklet实验_第6张图片

你可能感兴趣的:(驱动开发,linux)