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


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

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

    二、程序开发流程

    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        //MODULE_LICENSE,MODULE_AUTHOR  
    #include          //module_init,module_exit  
    #include        //printk  
    #include            //struct file_operations  
    #include          //kmalloc, kfree  
    #include       //copy_to_user,copy_from_user  
    #include            //ioremap,iounmap  
    #include          //struct cdev,cdev_init,cdev_add,cdev_del  
    #include        //class  
    #include            //of_find_node_by_path  
    #include       //of_get_named_gpio  
    #include          //gpio_request,gpio_direction_output,gpio_set_value  
    #include        //atomic_t  
    #include        //irq_of_parse_and_map
    #include     //request_irq
    #include         //timer_list
    #include       //jiffies
    #include        //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");  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299

    keyirqAPP.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /*
     * argc:应用程序参数个数 
     * argv[]:具体打参数内容,字符串形式 
     * ./keyirqAPP 
     * ./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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    3、使用工作队列

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

  • 相关阅读:
    【代码随想录】二刷-栈和队列
    【从0到1设计一个网关】自研网关的架构搭建
    强化学习知多少?
    1. 测度论-概率空间的基本概念
    10.10作业
    【无标题】
    代码随想录笔记--贪心算法篇
    (ICCV-2021)TransReID:基于transformer的目标重识别
    BUG 随想录 - Java: 程序包 com.example.xxx 不存在
    CSDN编程竞赛-第六期(下)
  • 原文地址:https://blog.csdn.net/morecrazylove/article/details/125886873