23、IMX6ULL学习笔记-中断下半部


一、原理图

在这里插入图片描述

二、中断表

在这里插入图片描述

三、设备树

在这里插入图片描述

四、说明

本次实验是在按键中断的基础上修改,原理图和设备树不变,使用中断下半部机制,介绍work和tasklet两种实现下半部的方式。
方式一:tasklet
在这里插入图片描述
方式二:
在这里插入图片描述

五、tasklet方式实现下半部的驱动程序

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
300
301
302
303
304
305
306
307
308
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#define DeviceName      "devicetree-key0-interrupt-tasklet" //设备驱动名称
#define DeviceNodes     1                        //设备节点个数
#define KEY_NUM         0X01            
#define KEY0VALUE       0X01
#define INVAKEY         0XFF
struct irq_keydesc{
    int gpio_num;           /* io编号 */
    int irq_num;            /* 中断号 */
    unsigned char value;    /* 键值 */
    char name[25];          /* 名字 */
    irqreturn_t (*handler) (int, void *);  /* 中断处理函数 */
    struct tasklet_struct tasklet;
};
/* 定义一个设备结构体 */
struct mod_struct {
    dev_t devid;                 /* 设备号 */
    int major;                   /* 主设备号 */
    int minor;                   /* 次设备号 */
    struct cdev cdev;            /* cdev */
    struct class *class;         /* 类 */
    struct device *device;       /* 设备 */
    struct device_node *nd;      /* 设备节点 */
    struct irq_keydesc irqkey0;  /* 按键中断 */
    struct timer_list timer;     /* 内核定时器 */
   
    atomic_t keyvalue;           /* 原子变量1->整形*/
    atomic_t releasekey;         /* 原子变量2->整形*/
};
struct mod_struct mod_device;    /* 定义一个设备 */
/*
 * @description     : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return          : 0 成功;其他 失败
 */
static int key0_interrupt_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &mod_device; /* 设置私有数据 */
    return 0;
}
/*
 * @description     : 从设备读取数据
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key0_interrupt_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    struct mod_struct *dev = filp->private_data;

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

    if(releasekey) {        /* 有效按键 */
        if(keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    } else {
        goto data_error;
    }


    return ret;

data_error:
    return -EINVAL;
}
/*
 * @description     : 向设备写数据
 * @param - filp    : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key0_interrupt_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{

    return 0;
}
/*
 * @description     : 关闭/释放设备
 * @param - filp    : 要关闭的设备文件(文件描述符)
 * @return          : 0 成功;其他 失败
 */

static int key0_interrupt_release(struct inode *inode, struct file *filp)
{
    return 0;
}
/* 设备操作函数 */
static struct file_operations beep_fops = {
    .owner      =  THIS_MODULE,
    .open       =  key0_interrupt_open,
    .read       =  key0_interrupt_read,
    .write      =  key0_interrupt_write,
    .release    =  key0_interrupt_release,
};
/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct mod_struct *dev = dev_id;
    #if 0
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
    #endif
    tasklet_schedule(&dev->irqkey0.tasklet);
    return IRQ_HANDLED;
}
/* tasklet (下半部)*/
static void key_tasklet(unsigned long data)
{
    struct mod_struct *dev = (struct mod_struct*)data;
    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
}


/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct mod_struct *dev = (struct mod_struct*)arg;

    value = gpio_get_value(dev->irqkey0.gpio_num);
    if(value == 0)   {          /* 按下 */
        atomic_set(&dev->keyvalue, dev->irqkey0.value);/*=0x01*/
    } else if(value == 1) {     /* 释放 */
        atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey0.value));
        atomic_set(&dev->releasekey, 1);  /* 完成的按键过程 */
    }
}
/*
 * @description : 按键初始化函数
 * @param       : dev
 * @return      : 无
 */
static int key_init(struct mod_struct *dev)
{
    int ret = 0;
    /* 1、获取设备节点:/devicetree-leds-pincrl */
    dev->nd = of_find_node_by_path("/devicetree-key0-interrupt");
    if(dev->nd == NULL) {
            printk("devicetree-key0-interrupt node not find!\r\n");
            ret = -EINVAL;
            goto fail_fd;
    } else {
        printk("devicetree-key0-interrupt node find!\r\n");
    }
    /* 2、从设备节点下的led-gpio属性里获取gpio编号 */
    dev->irqkey0.gpio_num = of_get_named_gpio(dev->nd,"key-gpio", 0);
    if(dev->irqkey0.gpio_num < 0) {
            printk("can't get key-gpio\r\n");
            ret = -EINVAL;
            goto fail_gpio;
    }
    printk("key-gpio num = %d\r\n", dev->irqkey0.gpio_num);
    /*3、申请gpio(检查是否被占用)*/
    sprintf(dev->irqkey0.name,"%s","key0-gpio-interrupt");
    ret = gpio_request(dev->irqkey0.gpio_num,dev->irqkey0.name);
    if(ret)
    {
        ret = -EBUSY;
        printk("IO %d busy,can't request!\r\n",dev->irqkey0.gpio_num);
        goto fail_ioreq;
    }
    /*4、设置key0为输入 */
    ret = gpio_direction_input(dev->irqkey0.gpio_num);
    if(ret < 0) {
            printk("can't set gpio!\r\n");
            goto fail_ioset;
    }
    /*5、根据IO编号获取其中断号*/
    dev->irqkey0.irq_num=gpio_to_irq(dev->irqkey0.gpio_num);
#if 0
    dev->irqkey0.irq_num = irq_of_parse_and_map(dev->nd,0);
#endif
    /*6、设置中断函数*/
    dev->irqkey0.handler = key0_handler;
    dev->irqkey0.value  = KEY0VALUE;
    /*7、注册中断*/
    ret = request_irq(dev->irqkey0.irq_num,
                      dev->irqkey0.handler,
                      IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
                      dev->irqkey0.name,
                      &mod_device);
    if(ret){
        printk("irq %d request failed!\r\n", dev->irqkey0.irq_num);
        goto fail_irq;
    }
    tasklet_init(&dev->irqkey0.tasklet, key_tasklet, (unsigned long)dev);
    /*8、初始化定时器*/
    init_timer(&mod_device.timer);
    mod_device.timer.function = timer_func;
    return 0;
fail_irq:
fail_ioset:
    gpio_free(dev->irqkey0.gpio_num);
fail_ioreq:
fail_gpio:
fail_fd:
    return ret;
}
/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static int __init key0_interrupt_init(void)
{
    int ret = 0;
/*一、 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (mod_device.major)
    {      
        mod_device.devid = MKDEV(mod_device.major,0);//设备号起始点(major,0)
        //设备号起始点开始,申请DeviceNodes个设备号
        ret = register_chrdev_region(mod_device.devid,DeviceNodes,DeviceName);
    }
    else {
        ret = alloc_chrdev_region(&mod_device.devid,0,DeviceNodes,DeviceName);  /* 申请设备号 */
        mod_device.major = MAJOR(mod_device.devid); /* 获取分配号的主设备号 */
        mod_device.minor = MINOR(mod_device.devid); /* 获取分配号的次设备号 */   
    }
    if(ret < 0){ printk("设备号申请失败\r\n");goto fail_chrdev; }
    printk("mod_device major=%d,minor=%d\r\n",mod_device.major,mod_device.minor);  
    /* 2、初始化cdev并添加cdev */
    mod_device.cdev.owner = THIS_MODULE;
    cdev_init(&mod_device.cdev,&beep_fops);
    ret = cdev_add(&mod_device.cdev,mod_device.devid,DeviceNodes);
    if(ret < 0){ printk("添加cdev失败!\r\n");goto fail_cdev; }
    /* 3、创建类 */
    mod_device.class = class_create(THIS_MODULE,DeviceName);
    if (IS_ERR(mod_device.class)) { ret = PTR_ERR(mod_device.class);goto fail_class; }
    /* 4、创建设备节点*/
    mod_device.device = device_create(mod_device.class,NULL,mod_device.devid,NULL,"DeviceName");
    if (IS_ERR(mod_device.device)) { ret = PTR_ERR(mod_device.device);goto fail_device; }
    /* 5、初始化IO */
    ret = key_init(&mod_device);
    if(ret < 0) {
        goto fail_keyinit;
    }
    /* 6、初始化原子变量 */
    atomic_set(&mod_device.keyvalue, INVAKEY);/*mod_device.keyvalue=INVAKEY*/
    atomic_set(&mod_device.releasekey, 0);/*mod_device.releasekey=0*/
/*X、模块加载失败处理部分*/
fail_keyinit:
    device_destroy(mod_device.class,mod_device.devid);
fail_device:   
    class_destroy(mod_device.class);
fail_class:
    cdev_del(&mod_device.cdev);
fail_cdev:
    unregister_chrdev_region(mod_device.devid,DeviceNodes);
fail_chrdev:
    return ret;
}
/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static void __exit key0_interrupt_exit(void)/*按照相反顺序注销*/
{
    /*1、释放中断*/
    free_irq(mod_device.irqkey0.irq_num, &mod_device);
    /*2、内核定定时器注销部分*/
    del_timer_sync(&mod_device.timer);/* 删除timer */
    /*3、LED设备注销部分*/
    gpio_free(mod_device.irqkey0.gpio_num);/*注销gpio*/      
    /*4、设备驱动注销部分*/           
    device_destroy(mod_device.class,mod_device.devid);/*注销设备节点*/
    class_destroy(mod_device.class);/*注销类*/
    cdev_del(&mod_device.cdev);/*注销cdev*/
    unregister_chrdev_region(mod_device.devid,DeviceNodes);/*注销chrdev*/
}
module_init(key0_interrupt_init);
module_exit(key0_interrupt_exit);
MODULE_LICENSE("GPL");

六、work方式实现下半部的驱动程序

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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#define DeviceName      "devicetree-key0-interrupt-work" //设备驱动名称
#define DeviceNodes     1                        //设备节点个数
#define KEY_NUM         0X01            
#define KEY0VALUE       0X01
#define INVAKEY         0XFF
struct irq_keydesc{
    int gpio_num;           /* io编号 */
    int irq_num;            /* 中断号 */
    unsigned char value;    /* 键值 */
    char name[25];          /* 名字 */
    irqreturn_t (*handler) (int, void *);  /* 中断处理函数 */
    struct tasklet_struct tasklet;
};
/* 定义一个设备结构体 */
struct mod_struct {
    dev_t devid;                 /* 设备号 */
    int major;                   /* 主设备号 */
    int minor;                   /* 次设备号 */
    struct cdev cdev;            /* cdev */
    struct class *class;         /* 类 */
    struct device *device;       /* 设备 */
    struct device_node *nd;      /* 设备节点 */
    struct irq_keydesc irqkey0;  /* 按键中断 */
    struct timer_list timer;     /* 内核定时器 */
    struct work_struct work;     /* 工作队列 */
   
    atomic_t keyvalue;           /* 原子变量1->整形*/
    atomic_t releasekey;         /* 原子变量2->整形*/
};
struct mod_struct mod_device;    /* 定义一个设备 */
/*
 * @description     : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return          : 0 成功;其他 失败
 */
static int key0_interrupt_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &mod_device; /* 设置私有数据 */
    return 0;
}
/*
 * @description     : 从设备读取数据
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key0_interrupt_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    struct mod_struct *dev = filp->private_data;

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

    if(releasekey) {        /* 有效按键 */
        if(keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    } else {
        goto data_error;
    }


    return ret;

data_error:
    return -EINVAL;
}
/*
 * @description     : 向设备写数据
 * @param - filp    : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key0_interrupt_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{

    return 0;
}
/*
 * @description     : 关闭/释放设备
 * @param - filp    : 要关闭的设备文件(文件描述符)
 * @return          : 0 成功;其他 失败
 */

static int key0_interrupt_release(struct inode *inode, struct file *filp)
{
    return 0;
}
/* 设备操作函数 */
static struct file_operations beep_fops = {
    .owner      =  THIS_MODULE,
    .open       =  key0_interrupt_open,
    .read       =  key0_interrupt_read,
    .write      =  key0_interrupt_write,
    .release    =  key0_interrupt_release,
};
/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct mod_struct *dev = dev_id;
    #if 0
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
    #endif
    //tasklet_schedule(&dev->irqkey0.tasklet);
    schedule_work(&dev->work);
    return IRQ_HANDLED;
}
/* tasklet (下半部方法1)*/
static void key_tasklet(unsigned long data)
{
    struct mod_struct *dev = (struct mod_struct*)data;
    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
}
/* work (下半部方法2)*/
static void key_work(struct work_struct *work)
{
    struct mod_struct *dev = container_of(work, struct mod_struct, work);
    dev->timer.data = (unsigned long)dev;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
}
/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct mod_struct *dev = (struct mod_struct*)arg;

    value = gpio_get_value(dev->irqkey0.gpio_num);
    if(value == 0)   {          /* 按下 */
        atomic_set(&dev->keyvalue, dev->irqkey0.value);/*=0x01*/
    } else if(value == 1) {     /* 释放 */
        atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey0.value));
        atomic_set(&dev->releasekey, 1);  /* 完成的按键过程 */
    }
}
/*
 * @description : 按键初始化函数
 * @param       : dev
 * @return      : 无
 */
static int key_init(struct mod_struct *dev)
{
    int ret = 0;
    /* 1、获取设备节点:/devicetree-leds-pincrl */
    dev->nd = of_find_node_by_path("/devicetree-key0-interrupt");
    if(dev->nd == NULL) {
            printk("devicetree-key0-interrupt node not find!\r\n");
            ret = -EINVAL;
            goto fail_fd;
    } else {
        printk("devicetree-key0-interrupt node find!\r\n");
    }
    /* 2、从设备节点下的led-gpio属性里获取gpio编号 */
    dev->irqkey0.gpio_num = of_get_named_gpio(dev->nd,"key-gpio", 0);
    if(dev->irqkey0.gpio_num < 0) {
            printk("can't get key-gpio\r\n");
            ret = -EINVAL;
            goto fail_gpio;
    }
    printk("key-gpio num = %d\r\n", dev->irqkey0.gpio_num);
    /*3、申请gpio(检查是否被占用)*/
    sprintf(dev->irqkey0.name,"%s","key0-gpio-interrupt");
    ret = gpio_request(dev->irqkey0.gpio_num,dev->irqkey0.name);
    if(ret)
    {
        ret = -EBUSY;
        printk("IO %d busy,can't request!\r\n",dev->irqkey0.gpio_num);
        goto fail_ioreq;
    }
    /*4、设置key0为输入 */
    ret = gpio_direction_input(dev->irqkey0.gpio_num);
    if(ret < 0) {
            printk("can't set gpio!\r\n");
            goto fail_ioset;
    }
    /*5、根据IO编号获取其中断号*/
    dev->irqkey0.irq_num=gpio_to_irq(dev->irqkey0.gpio_num);
#if 0
    dev->irqkey0.irq_num = irq_of_parse_and_map(dev->nd,0);
#endif
    /*6、设置中断函数*/
    dev->irqkey0.handler = key0_handler;
    dev->irqkey0.value  = KEY0VALUE;
    /*7、注册中断*/
    ret = request_irq(dev->irqkey0.irq_num,
                      dev->irqkey0.handler,
                      IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
                      dev->irqkey0.name,
                      &mod_device);
    if(ret){
        printk("irq %d request failed!\r\n", dev->irqkey0.irq_num);
        goto fail_irq;
    }
    INIT_WORK(&dev->work,key_work);
    //tasklet_init(&dev->irqkey0.tasklet, key_tasklet, (unsigned long)dev);
    /*8、初始化定时器*/
    init_timer(&mod_device.timer);
    mod_device.timer.function = timer_func;
    return 0;
fail_irq:
fail_ioset:
    gpio_free(dev->irqkey0.gpio_num);
fail_ioreq:
fail_gpio:
fail_fd:
    return ret;
}
/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static int __init key0_interrupt_init(void)
{
    int ret = 0;
/*一、 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (mod_device.major)
    {      
        mod_device.devid = MKDEV(mod_device.major,0);//设备号起始点(major,0)
        //设备号起始点开始,申请DeviceNodes个设备号
        ret = register_chrdev_region(mod_device.devid,DeviceNodes,DeviceName);
    }
    else {
        ret = alloc_chrdev_region(&mod_device.devid,0,DeviceNodes,DeviceName);  /* 申请设备号 */
        mod_device.major = MAJOR(mod_device.devid); /* 获取分配号的主设备号 */
        mod_device.minor = MINOR(mod_device.devid); /* 获取分配号的次设备号 */   
    }
    if(ret < 0){ printk("设备号申请失败\r\n");goto fail_chrdev; }
    printk("mod_device major=%d,minor=%d\r\n",mod_device.major,mod_device.minor);  
    /* 2、初始化cdev并添加cdev */
    mod_device.cdev.owner = THIS_MODULE;
    cdev_init(&mod_device.cdev,&beep_fops);
    ret = cdev_add(&mod_device.cdev,mod_device.devid,DeviceNodes);
    if(ret < 0){ printk("添加cdev失败!\r\n");goto fail_cdev; }
    /* 3、创建类 */
    mod_device.class = class_create(THIS_MODULE,DeviceName);
    if (IS_ERR(mod_device.class)) { ret = PTR_ERR(mod_device.class);goto fail_class; }
    /* 4、创建设备节点*/
    mod_device.device = device_create(mod_device.class,NULL,mod_device.devid,NULL,"DeviceName");
    if (IS_ERR(mod_device.device)) { ret = PTR_ERR(mod_device.device);goto fail_device; }
    /* 5、初始化IO */
    ret = key_init(&mod_device);
    if(ret < 0) {
        goto fail_keyinit;
    }
    /* 6、初始化原子变量 */
    atomic_set(&mod_device.keyvalue, INVAKEY);/*mod_device.keyvalue=INVAKEY*/
    atomic_set(&mod_device.releasekey, 0);/*mod_device.releasekey=0*/
/*X、模块加载失败处理部分*/
fail_keyinit:
    device_destroy(mod_device.class,mod_device.devid);
fail_device:   
    class_destroy(mod_device.class);
fail_class:
    cdev_del(&mod_device.cdev);
fail_cdev:
    unregister_chrdev_region(mod_device.devid,DeviceNodes);
fail_chrdev:
    return ret;
}
/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static void __exit key0_interrupt_exit(void)/*按照相反顺序注销*/
{
    /*1、释放中断*/
    free_irq(mod_device.irqkey0.irq_num, &mod_device);
    /*2、内核定定时器注销部分*/
    del_timer_sync(&mod_device.timer);/* 删除timer */
    /*3、LED设备注销部分*/
    gpio_free(mod_device.irqkey0.gpio_num);/*注销gpio*/      
    /*4、设备驱动注销部分*/           
    device_destroy(mod_device.class,mod_device.devid);/*注销设备节点*/
    class_destroy(mod_device.class);/*注销类*/
    cdev_del(&mod_device.cdev);/*注销cdev*/
    unregister_chrdev_region(mod_device.devid,DeviceNodes);/*注销chrdev*/
}
module_init(key0_interrupt_init);
module_exit(key0_interrupt_exit);
MODULE_LICENSE("GPL");

七、APP测试程序

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
#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[]:具体的参数内容,字符串形式
 *./imx6uirqAPP  <filename>  
 * ./imx6uirqAPP /dev/mx6uirq
 */
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、点个赞呗
在这里插入图片描述