驱动程序开发:使用中断处理按键事件(普通中断、任务、工作队列)

本文详细介绍了Linux内核中断处理机制,包括普通中断的处理流程,以及使用tasklet和工作队列作为中断下半部的两种实现方式。在程序开发中,通过设备树配置中断,然后在内核模块中注册中断处理函数,利用tasklet或工作队列来延迟执行耗时操作,确保中断快速响应。同时展示了如何初始化和注册这些机制,以及用户空间应用程序如何与驱动交互读取按键状态。
摘要由CSDN通过智能技术生成

一、知识简介(记录学习要点)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、程序开发流程

1、使用普通中断,大致流程图

在这里插入图片描述

设备树文件.dts文件
/* DJW 2022.4.29 /
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = “atkalpha-key”;
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /
key0 */
status = “okay”;
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};

keyirq.c文件

/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_value  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set

#define KEY_NUM     1       //按键个数
#define KEYVALUE    0X01    //按键值
#define INVAKEY     0XFF    //无效的按键值

/* 4.1.1 按键key行为结构体 */
struct irq_keydescri{
    int gpio;                           /* io编号 */
    int irqnum;                         /* 中断号 */
    unsigned char value;                /* 按键值 */
    char name[10];                      /* 名字 */
    irqreturn_t (*handler)(int,void *)  /* 中断处理函数 */
};

/* 1.5 keyirq设备结构体 */  
struct keyirq_dev {  
    dev_t                   devid;          /* 设备号 */  
    int                     major;          /* 主设备号 */  
    int                     minor;          /* 次设备号 */  
    char                    *name;          /* 设备名称 */  
    int                     dev_count;      /* 设备个数 */  
    struct  cdev            cdev;           /* 注册设备结构体 */  
    struct  class           *class;         /* 类 */  
    struct  device          *device;        /* 设备 */  
    struct  device_node     *nd;            /* 设备节点 */  
    struct  irq_keydescri   irqkey[KEY_NUM];/* 按键key行为结构体 */
    struct  timer_list      timer;          /* 定时器结构体 */
    atomic_t                keyvalue;       /* 按键值 */
    atomic_t                release;        /* 按键释放标志 */
};  
struct keyirq_dev keyirq;           /* 定义keyirq设备结构体 */  
  
/* 
 * inode: 传递给驱动的inode 
 * filp : 要进行操作的设备文件(文件描述符) 
 * buf  : 数据缓冲区 
 * cnt  : 数据长度 
 * ppos : 相对于文件首地址的偏移 
 */  
/* 2.1 打开字符设备文件 */  
static int keyirq_open(struct inode *inode, struct file *filp) {  
	int ret = 0;  
	/* 设置私有类数据 */  
	filp->private_data = &keyirq;  
	return ret;  
}  
	  
/* 2.2 关闭字符设备文件 */  
static int keyirq_release(struct inode *inode, struct file *filp) {  
	int ret = 0;  
	return ret;  
}  
  
/* 2.3 向字符设备文件读出数据 */  
static ssize_t keyirq_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {  
    int ret = 0;  
    unsigned char keyvalue;
    unsigned char release;
    /* 提取私有属性 */  
    struct keyirq_dev *dev = filp->private_data;  

    keyvalue = atomic_read(&dev->keyvalue);
    release = atomic_read(&dev->release);

    if(release) {
        if(keyvalue & 0X80) {
            keyvalue &= ~0X80;
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->release,0);    // 按下标志清零
    } else {
        goto data_error;
    }

    return ret; 
data_error:
    return -EINVAL;
}  
  
/* 2.4 向字符设备文件写入数据 */  
static ssize_t keyirq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {  
    int ret = 0;  
    return ret;  
}  
  
/* 2.5 keyirq设备操作集 */  
static const struct file_operations keyirq_fops = {  
    .owner      =   THIS_MODULE,  
    .open       =   keyirq_open,  
    .release    =   keyirq_release,  
    .read       =   keyirq_read,  
    .write      =   keyirq_write,  
};  

/* 4.4.1 定时器处理函数,定时器10ms溢出触发定时器中断,进入定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct keyirq_dev *dev = (struct keyirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0) {    /* 按下 */
        atomic_set(&dev->keyvalue,dev->irqkey[0].value);    /* 哪个按键被按下了 */
        printk("KEY0 press!\r\n");
    } else if(value == 1) { /* 释放 */
        atomic_set(&dev->keyvalue,0X80 | dev->irqkey[0].value); /* 按键激活 */
        atomic_set(&dev->release,1);    /* 完整的按键过程 */
        printk("KEY0 release!\r\n");
    }
}

/* 4.2.1 按键中断处理函数,param传参是keyirq */
static irqreturn_t key_handler(int irq, void *param)
{
    struct keyirq_dev *dev = param;

    dev->timer.data = (volatile unsigned long)param;    //传递值,会变的
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));  //20ms定时

	return IRQ_HANDLED;
}

/* 4. 按键初始化,这里分别 初始化IO,初始化中断,初始化定时器 */
static int key_init(struct keyirq_dev *dev) {
    int ret = 0;
    int i;
    /* 4.1.2 按键IO初始化 */
    dev->nd = of_find_node_by_path("/key");   //获取设备节点
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }
    for(i=0; i<KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpio",i);  //获取GPIO编号
        if(dev->irqkey[i].gpio < 0) {
            ret = -EINVAL;
            goto fail_gpio;
        }
        memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name,"KEY%d",i); 
        ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);    //请求GPIO
        if(ret) {
            ret = -EBUSY;
            printk("IO %d can't request!\r\n",dev->irqkey[i].gpio);
            goto fail_request;
        }
        ret = gpio_direction_input(dev->irqkey[i].gpio);                //设置GPIO
        if(ret < 0) {
            ret = -EINVAL;
            goto fail_input;
        }     
    }

    /* 4.2 按键中断初始化 */
    dev->irqkey[0].handler = key_handler;       //初始化中断处理函数
    dev->irqkey[0].value = KEYVALUE;            //按键值设置0X01
    for (i = 0; i < KEY_NUM; i++) {
        // dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);      //获取中断号

        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);        //通用的获取中断号方法
        if (dev->irqkey[i].irqnum < 0) {
		    printk("%s: Cannot find interrupt.\n", dev->irqkey[i].name);
		    goto fail_irq;
        }

        ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[0].handler,
                            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                            dev->irqkey[i].name,&keyirq);
        if(ret) {
            printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /* 4.4 初始化定时器 */
    init_timer(&keyirq.timer);  //初始化定时器
    keyirq.timer.function = timer_func; //添加定时器处理函数
    
    return 0;

fail_irq:
fail_input:
    gpio_free(dev->irqkey[i].gpio);
fail_request:
fail_gpio:
fail_nd:
    return ret;
}


/* 1.1 驱动模块入口函数 */  
static int __init keyirq_init(void) {  
    int ret = 0;  
    printk("keyirq_init!\r\n");  
    /***********************************************************************************************/  
    /* 3.1 配置keyirq结构体参数 */  
    keyirq.dev_count = 1;                  //设置设备号个数  
    keyirq.name  = "keyirq";                  //设置设备名字  
    keyirq.major = 0;                      //0为系统分配设备号, 1为自定义设备号  
    if(keyirq.major) {  
        keyirq.devid = MKDEV(keyirq.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中  
        ret = register_chrdev_region(keyirq.devid, keyirq.dev_count, keyirq.name);   //注册设备号  
    } else {  
        alloc_chrdev_region(&keyirq.devid, 0, keyirq.dev_count, keyirq.name);    //注册设备号,系统自动分配  
        keyirq.major = MAJOR(keyirq.devid);  
        keyirq.minor = MINOR(keyirq.devid);  
    }  
    if(ret < 0) {  
	    goto fail_devid;                //注册设备号失败  
	}  
    printk("keyirq major = %d, minor = %d\r\n",keyirq.major,keyirq.minor);   //注册设备号成功了,就打印主次设备号  
  
	/* 3.2 注册或者叫添加字符设备 */  
	cdev_init(&keyirq.cdev, &keyirq_fops);    //初始化cdev结构体  
	ret = cdev_add(&keyirq.cdev, keyirq.devid, keyirq.dev_count);    //添加字符设备  
	if(ret < 0) {  
	    goto fail_cdev;                 //注册或者叫添加设备失败  
	}  
	  
	/* 3.3 自动创建设备节点 */  
	keyirq.class = class_create(THIS_MODULE, keyirq.name);        //创建类  
	if(IS_ERR(keyirq.class)) {  
	    ret = PTR_ERR(keyirq.class);  
	    goto fail_class;  
    }  
    keyirq.device = device_create(keyirq.class, NULL, keyirq.devid, NULL, keyirq.name); //创建设备  
    if(IS_ERR(keyirq.device)) {  
        ret = PTR_ERR(keyirq.device);  
        goto fail_device;  
    }  
    /***********************************************************************************************/  
    
    /* 5. 初始化原子变量 */
    atomic_set(&keyirq.keyvalue,INVAKEY);       //设置为无效的按键值
    atomic_set(&keyirq.release,0);              //0表示按键没有被释放

    /* 4.3 调用KEY IO初始化函数初始化IO */
    ret = key_init(&keyirq);
    if(ret < 0) {
        goto fail_keyinit;
    }

    return ret;  

fail_keyinit:
fail_device:  
    class_destroy(keyirq.class);  
fail_class:  
    cdev_del(&keyirq.cdev);  
fail_cdev:  
    unregister_chrdev_region(keyirq.devid, keyirq.dev_count);  
fail_devid:  
    return ret;  
}  
	  
/* 1.2 驱动模块出口函数 */  
static void __exit keyirq_exit(void) {  
    int i;
    del_timer_sync(&keyirq.timer);                                  //删除定时器
    /* 最后一一添加 , 注销字符设备驱动 */  
    for(i=0;i<KEY_NUM;i++) {
        free_irq(keyirq.irqkey[i].irqnum,&keyirq);
        gpio_free(keyirq.irqkey[i].gpio);
    }
    device_destroy(keyirq.class, keyirq.devid);                //摧毁设备  
    class_destroy(keyirq.class);                               //摧毁类  
    cdev_del(&keyirq.cdev);                                    //注销字符设备结构体  
    unregister_chrdev_region(keyirq.devid, keyirq.dev_count);  //注销设备号  
}  
	  
/* 1.3 注册驱动模块 */  
module_init(keyirq_init);  
module_exit(keyirq_exit);  
	  
/* 1.4 驱动许可和个人信息 */  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

keyirqAPP.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>

/*
 * argc:应用程序参数个数 
 * argv[]:具体打参数内容,字符串形式 
 * ./keyirqAPP <filename>
 * ./keyirqAPP /dev/keyirq
 */


/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char data;


    /* 判断输入的元素个数 */
    if(argc != 2) {
        printf("ERROR USAGE!\r\n");
        return -1;
    }

    filename = argv[1];     //获取驱动文件的路径
    fd = open(filename,O_RDWR); //根据文件路径以读写方式打开文件
    if(fd < 0) {
        printf("file %s open failed!\r\n",filename);
        return -1;
    }

    /* 循环读取按键值 */
    while(1) {
        ret = read(fd,&data,sizeof(data));
        if (ret < 0)
        {
        
        } else {
            if(data) {
                printf("key value = %#x\r\n",data);
            }
        }
        
    }

    close(fd);
    return 0;
}

2、使用任务(tasklet 是利用软中断来实现的另外一种下半部机制)

在这里插入图片描述

/* key 任务处理函数 */static void key_tasklet(unsigned long data) {
    struct keyirq_dev *dev = (struct keyirq_dev*)data;

    dev->timer.data = (volatile unsigned long)data;    //传递值,会变的
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));  //20ms定时
}

/* 4.2.1 按键中断处理函数,param传参是keyirq */
static irqreturn_t key_handler(int irq, void *param)
{
    struct keyirq_dev *dev = param;tasklet_schedule(&dev->irqkey[0].tasklet);

	return IRQ_HANDLED;
}

/* 4. 按键初始化,这里分别 初始化IO,初始化中断,初始化定时器 */
static int key_init(struct keyirq_dev *dev) {
    int ret = 0;
    int i;
    /* 4.1.2 按键IO初始化 */
    dev->nd = of_find_node_by_path("/key");   //获取设备节点
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }
    for(i=0; i<KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpio",i);  //获取GPIO编号
        if(dev->irqkey[i].gpio < 0) {
            ret = -EINVAL;
            goto fail_gpio;
        }
        memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name,"KEY%d",i); 
        ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);    //请求GPIO
        if(ret) {
            ret = -EBUSY;
            printk("IO %d can't request!\r\n",dev->irqkey[i].gpio);
            goto fail_request;
        }
        ret = gpio_direction_input(dev->irqkey[i].gpio);                //设置GPIO
        if(ret < 0) {
            ret = -EINVAL;
            goto fail_input;
        }     
    }

    /* 4.2 按键中断初始化 */
    dev->irqkey[0].handler = key_handler;       //初始化中断处理函数
    dev->irqkey[0].value = KEYVALUE;            //按键值设置0X01
    for (i = 0; i < KEY_NUM; i++) {
        // dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);      //获取中断号

        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);        //通用的获取中断号方法
        if (dev->irqkey[i].irqnum < 0) {
		    printk("%s: Cannot find interrupt.\n", dev->irqkey[i].name);
		    goto fail_irq;
        }

        ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[0].handler,
                            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                            dev->irqkey[i].name,&keyirq);
        if(ret) {
            printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
            goto fail_irq;
        }tasklet_init(&dev->irqkey[i].tasklet,key_tasklet,(unsigned long)dev);
    }

    /* 4.4 初始化定时器 */
    init_timer(&keyirq.timer);  //初始化定时器
    keyirq.timer.function = timer_func; //添加定时器处理函数
    
    return 0;

fail_irq:
fail_input:
    gpio_free(dev->irqkey[i].gpio);
fail_request:
fail_gpio:
fail_nd:
    return ret;
}

3、使用工作队列

作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。
在这里插入图片描述
程序与任务tasklet类似。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值