驱动开发 —— 异步通知

按键驱动方式对比

  1. 查询:一直读,耗资源
  2. 中断: 没有超时机制,当没有中断,read函数一直休眠
  3. poll机制,加入超时机制

以上3种,都是让应用程序主动去读,本节我们学习异步通知,它的作用就是当驱动层有数据时,主动告诉应用程序,然后应用程序再来读, 这样,应用程序就可以干其它的事情,不必一直读。

  当有数据到时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写。

  1、应用·——处理信号,读写数据

     void catch_signale(int signo)
    {
        if(signo == SIGIO)
        {
            printf("we got sigal SIGIO");
            // 读取数据
            read(fd, &event, sizeof(struct key_event));
            if(event.code == KEY_ENTER)
            {
                if(event.value)
                {
                    printf("APP__ key enter pressed\n");
                }else
                {
                    printf("APP__ key enter up\n");
                }
            }
        }

    }

    // 1,设置信号处理方法
    signal(SIGIO,catch_signale);
    // 2,将当前进程设置成SIGIO的属主进程
    fcntl(fd, F_SETOWN, getpid());

    // 3,将io模式设置成异步模式
    int flags  = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC );

 

  2、示例:

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/of.h>
  4 #include <linux/of_irq.h>
  5 #include <linux/interrupt.h>
  6 #include <linux/slab.h>
  7 #include <linux/fs.h>
  8 #include <linux/device.h>
  9 #include <linux/kdev_t.h>
 10 #include <linux/err.h>
 11 #include <linux/device.h>
 12 #include <asm/io.h>
 13 #include <asm/uaccess.h>
 14 #include <linux/wait.h>
 15 #include <linux/sched.h>
 16 #include <linux/poll.h>
 17 
 18 
 19 #define GPXCON_REG 0X11000C20   //不可以从数据寄存器开始映射,要配置寄存器
 20 #define KEY_ENTER  28
 21 
 22 //0、设计一个描述按键的数据的对象
 23 struct key_event{
 24     int code;    //按键类型:home,esc,enter
 25     int value;   //表状态,按下,松开
 26 };
 27 
 28 //1、设计一个全局对象——— 描述key的信息
 29 struct key_desc{
 30     unsigned int dev_major;
 31     int irqno;  //中断号
 32     struct class  *cls;
 33     struct device *dev;
 34     void *reg_base;
 35     struct key_event event;
 36     wait_queue_head_t wq_head;
 37     int key_state;   //表示是否有数据
 38     struct fasync_struct *faysnc;
 39 };
 40 
 41 struct key_desc *key_dev;
 42 
 43 
 44 irqreturn_t key_irq_handler(int irqno, void *devid)
 45 {
 46     printk("----------%s---------",__FUNCTION__);
 47 
 48     int value;
 49     //读取按键状态
 50     value = readl(key_dev->reg_base + 4) & (0x01<<2);
 51     
 52     if(value){
 53         printk("key3 up\n");
 54         key_dev->event.code  = KEY_ENTER;
 55         key_dev->event.value = 0;
 56     }else{
 57         printk("key3 down\n");
 58         key_dev->event.code  = KEY_ENTER;
 59         key_dev->event.value = 1;
 60     }
 61 
 62     //表示有数据,唤醒等待队列中的等待项
 63     wake_up_interruptible(&key_dev->wq_head);
 64     
 65     //同时设置标志位,表示有数据
 66     key_dev->key_state = 1;
 67 
 68     //发送信号
 69     kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
 70     
 71     return IRQ_HANDLED;
 72 }
 73 
 74 
 75 //获取中断号
 76 int get_irqno_from_node(void)
 77 {
 78     int irqno;
 79     //获取设备树中的节点
 80     struct device_node *np = of_find_node_by_path("/key_int_node");
 81     if(np){
 82         printk("find node success\n");
 83     }else{
 84         printk("find node failed\n");
 85     }
 86 
 87     //通过节点去获取中断号
 88     irqno = irq_of_parse_and_map(np, 0);
 89     printk("iqrno = %d",key_dev->irqno);
 90 
 91     return irqno;
 92 }
 93 
 94 ssize_t key_drv_read (struct file * filp, char __user * buf, size_t count, loff_t * fops)
 95 {
 96     //printk("----------%s---------",__FUNCTION__);
 97     int ret;
 98 
 99     //在没有数据时,进行休眠
100         //key_state在zalloc初始化空间后,为0,则阻塞
101     wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
102     
103     ret = copy_to_user(buf, &key_dev->event, count);
104     if(ret > 0)
105     {
106         printk("copy_to_user error\n");
107         return -EFAULT;
108     }
109 
110     
111     //传递给用户数据后,将数据清除,否则APP每次读都是第一次的数据
112     memset(&key_dev->event, 0, sizeof(key_dev->event));
113     key_dev->key_state = 0;
114     
115     return count;
116 }
117 
118 ssize_t key_drv_write (struct file *filp, const char __user * buf, size_t count, loff_t * fops)
119 {
120     printk("----------%s---------",__FUNCTION__);
121     return 0;
122 }
123 
124 int key_drv_open (struct inode * inode, struct file *filp)
125 {
126     printk("----------%s---------",__FUNCTION__);
127     return 0;
128 }
129 
130 int key_drv_close (struct inode *inode, struct file *filp)
131 {
132     printk("----------%s---------",__FUNCTION__);
133     return 0;
134 }
135 
136 unsigned int key_drv_poll (struct file * filp, struct poll_table_struct *pts)
137 {
138     //返回一个mask值
139     unsigned int mask = 0;
140     
141     //调用poll_wait,将当前的等待队列注册到系统中
142     poll_wait(filp, &key_dev->wq_head, pts);
143     //1、当没有数据的时候返回0
144     if(!key_dev->key_state)
145     {
146         mask = 0;
147     }
148     //2、当有数据的时候返回POLLIN
149     if(key_dev->key_state)
150         mask |= POLLIN;
151 
152     return mask;
153 }
154 
155 int key_drv_fasync (int fd, struct file *filp, int on)
156 {
157     //只需要调用一个函数记录信号该发送给谁
158     return fasync_helper(fd, filp, on, &key_dev->faysnc);
159 }
160 
161 
162 const struct file_operations key_fops = {
163     .open    = key_drv_open,
164     .read    = key_drv_read,
165     .write   = key_drv_write,
166     .release = key_drv_close,
167     .poll    = key_drv_poll,
168     .fasync  = key_drv_fasync,
169 
170 };
171 
172 
173 
174 static int __init key_drv_init(void)
175 {
176     //演示如何获取到中断号
177     int ret;
178     
179     //1、设定全局设备对象并分配空间
180     key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);  //GFP_KERNEL表正常分配内存
181                           //kzalloc相比于kmalloc,不仅分配连续空间,还会将内存初始化清零
182 
183     //2、动态申请设备号
184     key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
185 
186     //3、创建设备节点文件
187     key_dev->cls = class_create(THIS_MODULE, "key_cls");
188     key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 0), NULL, "key0");
189 
190     //4、硬件初始化 -- 地址映射或中断申请    
191     
192     key_dev->reg_base = ioremap(GPXCON_REG,8);
193 
194     key_dev->irqno = get_irqno_from_node();
195     
196     ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
197         "key3_eint10", NULL);
198     if(ret != 0)
199     {
200         printk("request_irq error\n");
201         return ret;
202     }
203 
204     //初始化等待队列头
205     init_waitqueue_head(&key_dev->wq_head);  //wait_queue_head_t *q
206     
207     
208     
209     return 0;
210 }
211 
212 static void __exit key_drv_exit(void)
213 {
214     iounmap(GPXCON_REG);
215     free_irq(key_dev->irqno, NULL);  //free_irq与request_irq的最后一个参数一致
216     device_destroy(key_dev->cls, MKDEV(key_dev->dev_major, 0));
217     class_destroy(key_dev->cls);
218     unregister_chrdev(key_dev->dev_major, "key_drv");
219     kfree(key_dev);
220 }
221 
222 
223 
224 module_init(key_drv_init);
225 module_exit(key_drv_exit);
226 
227 MODULE_LICENSE("GPL");
key_drv.c
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <fcntl.h>
 8 #include <poll.h>
 9 #include <signal.h>
10 
11 
12 #define KEY_ENTER  28
13 
14 //0、设计一个描述按键的数据的对象
15 struct key_event{
16     int code;    //按键类型:home,esc,enter
17     int value;   //表状态,按下,松开
18 };
19 
20 static int fd;
21 static struct key_event event;
22 
23 
24 
25 void catch_signale(int signo)
26 {
27     if(signo == SIGIO)
28     {
29         printf("I Got A Signal\n");
30         //读取数据
31         read(fd, &event, sizeof(struct key_event));  //每次读必有数据
32                 if(event.code == KEY_ENTER)
33                 {
34                     if(event.value)
35                     {
36                         printf("APP__ key enter down\n");
37                     }else{
38 
39                         printf("APP__ key enter up\n");
40                     }
41                 }
42         
43     }
44 }
45 
46 int main(int argc, char *argv[])
47 {
48     
49     int ret;    
50     fd = open("/dev/key0", O_RDWR);
51     if(fd < 0)
52     {
53         perror("open");
54         exit(1);
55     }
56 
57     //1、设置信号处理方法
58     signal(SIGIO, catch_signale);  //
59     
60     //2、将当前进程设置成SIGIO的属主进程
61     fcntl(fd, F_SETOWN, getpid());
62     
63     //3、将IO模式设置成异步模式
64     int flags = fcntl(fd, F_GETFL);  //获取信号
65     fcntl(fd, F_SETFL, flags | FASYNC);  //设置信号为FASYNC
66 
67     
68     while(1)
69     {
70         //设置异步通知后,APP可以去做其他事了
71         printf("I am waiting .....\n");
72         sleep(1);
73     }
74     
75     
76     close(fd);
77 
78     return 0;
79 }
key_test.c

  

  3、中断下半部

 

 

 中断处理的两个原则:

1、中断处理不能嵌套:

  中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。

2、中断处理越快越好:

 通常,我们希望中断处理的时间(中断上半部)越短越好,例如。进程正在访问临界资源,此时中断来了,前去处理,但处理时间太长。其他需要访问该临界资源的线程就会被阻塞,导致饥饿。但是,有些时候中断处理的时间就是比较长,例如,网卡接收数据的中断处理中,会先从硬件读取数据,拆包,再往上层传送,若数据量很大,那么处理过程就会很长。

  并且进程调度是依靠定时器中断实现的,由于原则1,在中断的处理过程中,该CPU是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理。

  当中断处理时间没有办法减少,却要求缩短中断处理时间,该如何处理?由此引入中断下半部的概念,就是将一些耗时的操作延后执行。例如,网卡处理接收数据时,会先将耗时的数据放在全局数据区,延后处理,网卡进程先退出中断处理,返回去继续执行。

  如果中断处理的时间够短,可以不用下半部。先看一下中断下半部的实现方法:

1,softirq: 处理比较快,但是内核级别的机制,需要修改整个内核源码,不推荐也不常用
2,tasklet: 内部实现实际调用了softirq
                a.在内核线程中以链表方式组织
                b.运行在中断上下文当中,不可执行休眠的函数
3, workqueue: 工作队列
                a.在内核线程中已队列方式组织
                b.运行在进程上下文当中,可执行休眠的函数

  引用:简单地说,一般的驱动程序的编写者需要做两个选择。 首先,你是不是需要一个可调度的实体来执行需要推后完成的工作――从根本上来说,有休眠的需要吗?要是有,工作队列就是你的惟一选择。 否则最好用tasklet。要是必须专注于性能的提高,那么就考虑softirq。

(中断下半部实现详见:tasklet与workqueue的区别及底层实现区别

 

  4、中断下半部实现方法 —— tasklet

 1     struct tasklet_struct
 2     {
 3         struct tasklet_struct *next;
 4         unsigned long state;
 5         atomic_t count;
 6         void (*func)(unsigned long); // 下半部的实现逻辑
 7         unsigned long data; // 传递给func
 8     };
 9 
10     a, 初始化(在驱动模块入口,即init中初始化,中断申请之前)
11         struct tasklet_struct mytasklet;
12 
13         tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
14 
15         例子:
16         void key_tasklet_half_irq(unsigned long data)
17         {
18             // 表示有数据,需要去唤醒整个进程/等待队列
19             wake_up_interruptible(&key_dev->wq_head);
20             //同时设置标志位
21             key_dev->key_state  = 1;
22 
23             //发送信号
24             kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
25         }
26 
27         tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
28 
29 
30     b,在上半部中放入到内核线程中--启动
31         // 启动下半步
32         tasklet_schedule(&key_dev->mytasklet);
33 
34     c,模块卸载的时候:
35         tasklet_kill(&key_dev->mytasklet);

    测试代码:

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/of.h>
  4 #include <linux/of_irq.h>
  5 #include <linux/interrupt.h>
  6 #include <linux/slab.h>
  7 #include <linux/fs.h>
  8 #include <linux/device.h>
  9 #include <linux/kdev_t.h>
 10 #include <linux/err.h>
 11 #include <linux/device.h>
 12 #include <asm/io.h>
 13 #include <asm/uaccess.h>
 14 #include <linux/wait.h>
 15 #include <linux/sched.h>
 16 #include <linux/poll.h>
 17 
 18 
 19 #define GPXCON_REG 0X11000C20   //不可以从数据寄存器开始映射,要配置寄存器
 20 #define KEY_ENTER  28
 21 
 22 //0、设计一个描述按键的数据的对象
 23 struct key_event{
 24     int code;    //按键类型:home,esc,enter
 25     int value;   //表状态,按下,松开
 26 };
 27 
 28 //1、设计一个全局对象——— 描述key的信息
 29 struct key_desc{
 30     unsigned int dev_major;
 31     int irqno;  //中断号
 32     struct class  *cls;
 33     struct device *dev;
 34     void *reg_base;
 35     struct key_event event;
 36     wait_queue_head_t wq_head;
 37     int key_state;   //表示是否有数据
 38     struct fasync_struct *faysnc;
 39     struct tasklet_struct mytasklet; 
 40 };
 41 
 42 struct key_desc *key_dev;
 43 
 44 void key_tasklet_half_irq(unsigned long data)
 45 {
 46     printk("-------------%s-----------\n",__FUNCTION__);
 47     //表示有数据,唤醒等待队列中的等待项
 48     wake_up_interruptible(&key_dev->wq_head);
 49     
 50     //同时设置标志位,表示有数据
 51     key_dev->key_state = 1;
 52 
 53     //发送信号
 54     kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
 55 }
 56 
 57 irqreturn_t key_irq_handler(int irqno, void *devid)
 58 {
 59     printk("--------------%s-----------\n",__FUNCTION__);
 60 
 61     int value;
 62     //读取按键状态
 63     value = readl(key_dev->reg_base + 4) & (0x01<<2);
 64     
 65     if(value){
 66         printk("key3 up\n");
 67         key_dev->event.code  = KEY_ENTER;
 68         key_dev->event.value = 0;
 69     }else{
 70         printk("key3 down\n");
 71         key_dev->event.code  = KEY_ENTER;
 72         key_dev->event.value = 1;
 73     }
 74 
 75 
 76     //将下半部放到内核线程中启动
 77     tasklet_schedule(&key_dev->mytasklet);
 78     
 79     return IRQ_HANDLED;
 80 }
 81 
 82 
 83 //获取中断号
 84 int get_irqno_from_node(void)
 85 {
 86     int irqno;
 87     //获取设备树中的节点
 88     struct device_node *np = of_find_node_by_path("/key_int_node");
 89     if(np){
 90         printk("find node success\n");
 91     }else{
 92         printk("find node failed\n");
 93     }
 94 
 95     //通过节点去获取中断号
 96     irqno = irq_of_parse_and_map(np, 0);
 97     printk("iqrno = %d",key_dev->irqno);
 98 
 99     return irqno;
100 }
101 
102 ssize_t key_drv_read (struct file * filp, char __user * buf, size_t count, loff_t * fops)
103 {
104     //printk("----------%s---------",__FUNCTION__);
105     int ret;
106 
107     //在没有数据时,进行休眠
108         //key_state在zalloc初始化空间后,为0,则阻塞
109     wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
110     
111     ret = copy_to_user(buf, &key_dev->event, count);
112     if(ret > 0)
113     {
114         printk("copy_to_user error\n");
115         return -EFAULT;
116     }
117 
118     
119     //传递给用户数据后,将数据清除,否则APP每次读都是第一次的数据
120     memset(&key_dev->event, 0, sizeof(key_dev->event));
121     key_dev->key_state = 0;
122     
123     return count;
124 }
125 
126 ssize_t key_drv_write (struct file *filp, const char __user * buf, size_t count, loff_t * fops)
127 {
128     printk("----------%s---------",__FUNCTION__);
129     return 0;
130 }
131 
132 int key_drv_open (struct inode * inode, struct file *filp)
133 {
134     printk("----------%s---------",__FUNCTION__);
135     return 0;
136 }
137 
138 int key_drv_close (struct inode *inode, struct file *filp)
139 {
140     printk("----------%s---------",__FUNCTION__);
141     return 0;
142 }
143 
144 unsigned int key_drv_poll (struct file * filp, struct poll_table_struct *pts)
145 {
146     //返回一个mask值
147     unsigned int mask = 0;
148     
149     //调用poll_wait,将当前的等待队列注册到系统中
150     poll_wait(filp, &key_dev->wq_head, pts);
151     //1、当没有数据的时候返回0
152     if(!key_dev->key_state)
153     {
154         mask = 0;
155     }
156     //2、当有数据的时候返回POLLIN
157     if(key_dev->key_state)
158         mask |= POLLIN;
159 
160     return mask;
161 }
162 
163 int key_drv_fasync (int fd, struct file *filp, int on)
164 {
165     //只需要调用一个函数记录信号该发送给谁
166     return fasync_helper(fd, filp, on, &key_dev->faysnc);
167 }
168 
169 
170 const struct file_operations key_fops = {
171     .open    = key_drv_open,
172     .read    = key_drv_read,
173     .write   = key_drv_write,
174     .release = key_drv_close,
175     .poll    = key_drv_poll,
176     .fasync  = key_drv_fasync,
177 
178 };
179 
180 
181 
182 static int __init key_drv_init(void)
183 {
184     //演示如何获取到中断号
185     int ret;
186 
187     
188     //1、设定全局设备对象并分配空间
189     key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);  //GFP_KERNEL表正常分配内存
190                           //kzalloc相比于kmalloc,不仅分配连续空间,还会将内存初始化清零
191 
192     //2、动态申请设备号
193     key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
194 
195     //3、创建设备节点文件
196     key_dev->cls = class_create(THIS_MODULE, "key_cls");
197     key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 0), NULL, "key0");
198 
199     //4、硬件初始化 -- 地址映射或中断申请    
200     
201     key_dev->reg_base = ioremap(GPXCON_REG,8);
202 
203     key_dev->irqno = get_irqno_from_node();
204     
205     ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
206         "key3_eint10", NULL);
207     if(ret != 0)
208     {
209         printk("request_irq error\n");
210         return ret;
211     }
212 
213     //初始化等待队列头
214     init_waitqueue_head(&key_dev->wq_head);  //wait_queue_head_t *q
215 
216     //初始化tasklet
217     tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 66);
218     
219     return 0;
220 }
221 
222 static void __exit key_drv_exit(void)
223 {
224     tasklet_kill(&key_dev->mytasklet);
225     iounmap(GPXCON_REG);
226     free_irq(key_dev->irqno, NULL);  //free_irq与request_irq的最后一个参数一致
227     device_destroy(key_dev->cls, MKDEV(key_dev->dev_major, 0));
228     class_destroy(key_dev->cls);
229     unregister_chrdev(key_dev->dev_major, "key_drv");
230     kfree(key_dev);
231 }
232 
233 
234 
235 module_init(key_drv_init);
236 module_exit(key_drv_exit);
237 
238 MODULE_LICENSE("GPL");
key_drv.c

 

  5、工作队列实现下半部

在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟整个中断的处理还没走完,这期间APP是无法执行的。

假设下半部要执行1、2分钟,在这1、2分钟里APP都是无法响应的。

 

所以,如果中断要做的事情实在太耗时,那就不能用中断下半部来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和APP都一样竞争执行,APP有机会执行,系统不会卡顿。

 

这个内核线程是系统帮我们创建的,一般是kworker线程,内核中有很多这样的线程:

 

 

 kworker线程要去“工作队列”(work queue)上取出一个一个“工作”(work),来执行它里面的函数

    typedef void (*work_func_t)(struct work_struct *work);

    struct work_struct {
        atomic_long_t data;
        struct list_head entry;
        work_func_t func;
    };

    a, 初始化
        
        void work_irq_half(struct work_struct *work)
        {
            printk("-------%s-------------\n", __FUNCTION__);
            // 表示有数据,需要去唤醒整个进程/等待队列
            wake_up_interruptible(&key_dev->wq_head);
            //同时设置标志位
            key_dev->key_state  = 1;

            //发送信号
            kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
            
        }
        struct work_struct mywork;

        INIT_WORK(struct work_struct *work, work_func_t func);

    b, 在上半部中放入到内核线程中--启动

        schedule_work(&key_dev->mywork);

  测试代码:

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/of.h>
  4 #include <linux/of_irq.h>
  5 #include <linux/interrupt.h>
  6 #include <linux/slab.h>
  7 #include <linux/fs.h>
  8 #include <linux/device.h>
  9 #include <linux/kdev_t.h>
 10 #include <linux/err.h>
 11 #include <linux/device.h>
 12 #include <asm/io.h>
 13 #include <asm/uaccess.h>
 14 #include <linux/wait.h>
 15 #include <linux/sched.h>
 16 #include <linux/poll.h>
 17 
 18 
 19 #define GPXCON_REG 0X11000C20   //不可以从数据寄存器开始映射,要配置寄存器
 20 #define KEY_ENTER  28
 21 
 22 //0、设计一个描述按键的数据的对象
 23 struct key_event{
 24     int code;    //按键类型:home,esc,enter
 25     int value;   //表状态,按下,松开
 26 };
 27 
 28 //1、设计一个全局对象——— 描述key的信息
 29 struct key_desc{
 30     unsigned int dev_major;
 31     int irqno;  //中断号
 32     struct class  *cls;
 33     struct device *dev;
 34     void *reg_base;
 35     struct key_event event;
 36     wait_queue_head_t wq_head;
 37     int key_state;   //表示是否有数据
 38     struct fasync_struct *faysnc;
 39     struct work_struct mywork; 
 40 };
 41 
 42 struct key_desc *key_dev;
 43 
 44 void key_work_half_irq(struct work_struct *work)
 45 {
 46     printk("-------------%s-----------\n",__FUNCTION__);
 47     //表示有数据,唤醒等待队列中的等待项
 48     wake_up_interruptible(&key_dev->wq_head);
 49     
 50     //同时设置标志位,表示有数据
 51     key_dev->key_state = 1;
 52 
 53     //发送信号
 54     kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
 55 }
 56 
 57 irqreturn_t key_irq_handler(int irqno, void *devid)
 58 {
 59     printk("--------------%s-----------\n",__FUNCTION__);
 60 
 61     int value;
 62     //读取按键状态
 63     value = readl(key_dev->reg_base + 4) & (0x01<<2);
 64     
 65     if(value){
 66         printk("key3 up\n");
 67         key_dev->event.code  = KEY_ENTER;
 68         key_dev->event.value = 0;
 69     }else{
 70         printk("key3 down\n");
 71         key_dev->event.code  = KEY_ENTER;
 72         key_dev->event.value = 1;
 73     }
 74 
 75 
 76     //将下半部放到内核线程中启动
 77     schedule_work(&key_dev->mywork);
 78     
 79     return IRQ_HANDLED;
 80 }
 81 
 82 
 83 //获取中断号
 84 int get_irqno_from_node(void)
 85 {
 86     int irqno;
 87     //获取设备树中的节点
 88     struct device_node *np = of_find_node_by_path("/key_int_node");
 89     if(np){
 90         printk("find node success\n");
 91     }else{
 92         printk("find node failed\n");
 93     }
 94 
 95     //通过节点去获取中断号
 96     irqno = irq_of_parse_and_map(np, 0);
 97     printk("iqrno = %d",key_dev->irqno);
 98 
 99     return irqno;
100 }
101 
102 ssize_t key_drv_read (struct file * filp, char __user * buf, size_t count, loff_t * fops)
103 {
104     //printk("----------%s---------",__FUNCTION__);
105     int ret;
106 
107     //在没有数据时,进行休眠
108         //key_state在zalloc初始化空间后,为0,则阻塞
109     wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
110     
111     ret = copy_to_user(buf, &key_dev->event, count);
112     if(ret > 0)
113     {
114         printk("copy_to_user error\n");
115         return -EFAULT;
116     }
117 
118     
119     //传递给用户数据后,将数据清除,否则APP每次读都是第一次的数据
120     memset(&key_dev->event, 0, sizeof(key_dev->event));
121     key_dev->key_state = 0;
122     
123     return count;
124 }
125 
126 ssize_t key_drv_write (struct file *filp, const char __user * buf, size_t count, loff_t * fops)
127 {
128     printk("----------%s---------",__FUNCTION__);
129     return 0;
130 }
131 
132 int key_drv_open (struct inode * inode, struct file *filp)
133 {
134     printk("----------%s---------",__FUNCTION__);
135     return 0;
136 }
137 
138 int key_drv_close (struct inode *inode, struct file *filp)
139 {
140     printk("----------%s---------",__FUNCTION__);
141     return 0;
142 }
143 
144 unsigned int key_drv_poll (struct file * filp, struct poll_table_struct *pts)
145 {
146     //返回一个mask值
147     unsigned int mask = 0;
148     
149     //调用poll_wait,将当前的等待队列注册到系统中
150     poll_wait(filp, &key_dev->wq_head, pts);
151     //1、当没有数据的时候返回0
152     if(!key_dev->key_state)
153     {
154         mask = 0;
155     }
156     //2、当有数据的时候返回POLLIN
157     if(key_dev->key_state)
158         mask |= POLLIN;
159 
160     return mask;
161 }
162 
163 int key_drv_fasync (int fd, struct file *filp, int on)
164 {
165     //只需要调用一个函数记录信号该发送给谁
166     return fasync_helper(fd, filp, on, &key_dev->faysnc);
167 }
168 
169 
170 const struct file_operations key_fops = {
171     .open    = key_drv_open,
172     .read    = key_drv_read,
173     .write   = key_drv_write,
174     .release = key_drv_close,
175     .poll    = key_drv_poll,
176     .fasync  = key_drv_fasync,
177 
178 };
179 
180 
181 
182 static int __init key_drv_init(void)
183 {
184     //演示如何获取到中断号
185     int ret;
186 
187     
188     //1、设定全局设备对象并分配空间
189     key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);  //GFP_KERNEL表正常分配内存
190                           //kzalloc相比于kmalloc,不仅分配连续空间,还会将内存初始化清零
191 
192     //2、动态申请设备号
193     key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
194 
195     //3、创建设备节点文件
196     key_dev->cls = class_create(THIS_MODULE, "key_cls");
197     key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 0), NULL, "key0");
198 
199     //4、硬件初始化 -- 地址映射或中断申请    
200     
201     key_dev->reg_base = ioremap(GPXCON_REG,8);
202 
203     key_dev->irqno = get_irqno_from_node();
204     
205     ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
206         "key3_eint10", NULL);
207     if(ret != 0)
208     {
209         printk("request_irq error\n");
210         return ret;
211     }
212 
213     //初始化等待队列头
214     init_waitqueue_head(&key_dev->wq_head);  //wait_queue_head_t *q
215 
216     //初始化workqueue
217     INIT_WORK(&key_dev->mywork, key_work_half_irq);
218 
219     
220     return 0;
221 }
222 
223 static void __exit key_drv_exit(void)
224 {
225     free_irq(key_dev->irqno, NULL);  //free_irq与request_irq的最后一个参数一致
226     iounmap(key_dev->reg_base);
227     device_destroy(key_dev->cls, MKDEV(key_dev->dev_major, 0));
228     class_destroy(key_dev->cls);
229     unregister_chrdev(key_dev->dev_major, "key_drv");
230     kfree(key_dev);
231 }
232 
233 
234 
235 module_init(key_drv_init);
236 module_exit(key_drv_exit);
237 
238 MODULE_LICENSE("GPL");
key_drv.c

 

推荐文章:https://mp.weixin.qq.com/s/Qm1p8GFQbIIl0bqZLJ76sQ

 

posted @ 2020-03-02 11:37  朱果果  阅读(293)  评论(0编辑  收藏  举报