驱动开发,基于中断子系统完成按键的中断驱动,引入中断底半部

一.引入linux内核中断目的

引入linux内核中断之前,内核访问设备要不断轮询访问;

引入linux内核中断便于内核对设备的访问,当设备事件发生后主动通知内核,内核再去访问设备;

二.linux内核中断实现过程框图

根据软中断号回调当前中断的 中断函数 过程:

中断注册进内核之后,中断信息会保存在一个struct irq_desc对象中,内核中存在一个struct irq_desc类型的数组,软中断号就是数组的下标,数组中每一个成员都是保存了一个注册进内核的设备中断信息,类型为struct irqaction,struct irqaction对象里有中断处理函数的函数指针,指向自定义中断处理函数;

三.添加按键的设备树节点

在stm32mp157a-fsmp1a.dts文件的根节点内部添加如下内容:

myirq

{

compatible="myirq"; interrupt-parent=<&gpiof>; interrupts=<9 0>,<7 0>,<8 0>;

};

四.中断底半部

1.引入目的(解决问题)

当一个中断被触发以后,会关闭抢占,一个单核CPU处理当前中断任务时,当前CPU无法处理其他任务,所有的CPU都会关闭当前中断线。在这种情况下,如果一个中断中有延时、耗时甚至休眠操作,最终会导致整个系统功能的延迟。所以一般在中断处理过程中不允许有延时、耗时甚至休眠的操作。但是有的时候又必须在中断的处理程序中进行一些耗时任务。

这样就会产生一个冲突:中断不允许有耗时但是有时候需要耗时的冲突;

2.解决上面冲突,引入中断底半部

将一个中断处理得分过程分为了中断顶半部和中断底半部,中断顶半部就是通过 request_irq注册的中断处理函数,**在顶半部中主要进行一些重要的、不耗时的任务;中断底半部则是区进行一些耗时,不紧急的任务。**在执行中断底半部时,会将执行中断顶半部时关闭的中断线启用以及抢占开启,这样进程以及其他的中断就可以正常的工作了。

3.中断底半部的实现机制

实现机制有softirq(软中断)、tasklet以及工作队列;

1)软中断机制

当顶半部即将执行结束时开启软中断,在软中断处理函数中取处理当前中断里的耗时任务。软中断存在数量限制(32个),一般留给内核开发者使用。

2)tasklet机制

  • 基于软中断工作原理进行的;
  • tasklet没有使用数量的限制;
  • 中断底半部可以进行耗时任务,但不可以进行休眠操作;
  • 工作于中断上下文,不用于进程上下文;

3)工作队列机制

内核中存在工作队列对应的内核线程,这个线程从内核启动就存在,处于休眠态。当有任务需要执行时,只需要将任务提交到工作队列中,然后唤醒休眠的内核线程,由内核线程去处理对应的任务即可。工作队列既可以用于中断,也可以用于进程。

4)tasklet机制驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

struct device_node *dnode;
unsigned int irqno[3];
int i;
struct tasklet_struct tasklet;  //分配tasklet对象

//定义底半部处理函数
void key_callback(struct tasklet_struct *t)
{
    int i;
    //耗时事件
    for(i=0; i<100; i++)
    {
        printk("i=%d\n",i);
    }
    
}

// 定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{
    int witch = (int)dev;
    switch (witch)
    {
    case 0:
        printk("KEY1_INTERRUPT\n");
        break;
    case 1:
        printk("KEY2_INTERRUPT\n");
        break;
    case 2:
        printk("KEY3_INTERRUPT\n");
        break;
    }

    //开启底半部
    tasklet_schedule(&tasklet);
    return IRQ_HANDLED;
}

static int __init mycdev_init(void)
{
    // 1解析按键的设备树节点
    dnode = of_find_compatible_node(NULL, NULL, "myirq");
    if (dnode == NULL)
    {
        printk("解析设备树节点失败\n");
        return -ENXIO;
    }
    printk("解析设备树节点成功\n");

    // 2解析按键的软中断号
    for (i = 0; i < 3; i++)
    {
        irqno[i] = irq_of_parse_and_map(dnode, i);
        if (!irqno[i])
        {
            printk("解析按键%d软中断号失败\n", i);
            return -ENXIO;
        }
        printk("解析按键%d软中断号成功%d\n", i, irqno[i]);

        // 3注册按键1中断
        int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
        if (ret < 0)
        {
            printk("注册按键%d中断失败\n", i);
            return ret;
        }
    }
    printk("注册按键中断成功\n");

    //初始化底半部
    tasklet_setup(&tasklet,key_callback);

    return 0;
}
static void __exit mycdev_exit(void)
{
    // 注销中断
    int i;
    for (i = 0; i < 3; i++)
    {
        free_irq(irqno[i], (void *)i);
    }
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

现象:

5)工作队列机制驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

struct device_node *dnode;
unsigned int irqno[3];
int i;
struct work_struct work;  //分配工作队列对象

//定义底半部处理函数
void key_work(struct work_struct *t)
{
    int i;
    //耗时事件
    for(i=0; i<100; i++)
    {
        printk("i=%d\n",i);
    }
    
}

// 定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{
    int witch = (int)dev;
    switch (witch)
    {
    case 0:
        printk("KEY1_INTERRUPT\n");
        break;
    case 1:
        printk("KEY2_INTERRUPT\n");
        break;
    case 2:
        printk("KEY3_INTERRUPT\n");
        break;
    }

    //开启底半部
    schedule_work(&work);
    return IRQ_HANDLED;
}

static int __init mycdev_init(void)
{
    // 1解析按键的设备树节点
    dnode = of_find_compatible_node(NULL, NULL, "myirq");
    if (dnode == NULL)
    {
        printk("解析设备树节点失败\n");
        return -ENXIO;
    }
    printk("解析设备树节点成功\n");

    // 2解析按键的软中断号
    for (i = 0; i < 3; i++)
    {
        irqno[i] = irq_of_parse_and_map(dnode, i);
        if (!irqno[i])
        {
            printk("解析按键%d软中断号失败\n", i);
            return -ENXIO;
        }
        printk("解析按键%d软中断号成功%d\n", i, irqno[i]);

        // 3注册按键1中断
        int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
        if (ret < 0)
        {
            printk("注册按键%d中断失败\n", i);
            return ret;
        }
    }
    printk("注册按键中断成功\n");

    //初始化队列项
    INIT_WORK(&work,key_work);
    

    return 0;
}
static void __exit mycdev_exit(void)
{
    // 注销中断
    int i;
    for (i = 0; i < 3; i++)
    {
        free_irq(irqno[i], (void *)i);
    }
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

现象:

相关推荐
hhhhhhh_hhhhhh_1 小时前
rk3568制冷项目驱动开发流程汇总(只适用于部分模块CIF DVP等,自用)
驱动开发
日记跟新中1 小时前
Ubuntu20.04 修改root密码
linux·运维·服务器
码农君莫笑1 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
BUG 4041 小时前
Linux——Shell
linux·运维·服务器
大霞上仙2 小时前
Linux 多命令执行
linux·运维·服务器
晨欣2 小时前
Kibana:LINUX_X86_64 和 DEB_X86_64两种可选下载方式的区别
linux·运维·服务器
AI青年志2 小时前
【服务器】linux服务器管理员查看用户使用内存情况
linux·运维·服务器
爱吃西瓜的小菜鸡3 小时前
【C语言】判断回文
c语言·学习·算法
dessler3 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
PyAIGCMaster3 小时前
ubuntu装P104驱动
linux·运维·ubuntu