zoukankan      html  css  js  c++  java
  • 驱动开发 —— 异步通知

    按键驱动方式对比

    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
    ");
                    }else
                    {
                        printf("APP__ key enter up
    ");
                    }
                }
            }
    
        }
    
        // 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
    ");
     54         key_dev->event.code  = KEY_ENTER;
     55         key_dev->event.value = 0;
     56     }else{
     57         printk("key3 down
    ");
     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
    ");
     83     }else{
     84         printk("find node failed
    ");
     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
    ");
    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
    ");
    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
    ");
    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
    ");
    37                     }else{
    38 
    39                         printf("APP__ key enter up
    ");
    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 .....
    ");
    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-----------
    ",__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-----------
    ",__FUNCTION__);
     60 
     61     int value;
     62     //读取按键状态
     63     value = readl(key_dev->reg_base + 4) & (0x01<<2);
     64     
     65     if(value){
     66         printk("key3 up
    ");
     67         key_dev->event.code  = KEY_ENTER;
     68         key_dev->event.value = 0;
     69     }else{
     70         printk("key3 down
    ");
     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
    ");
     91     }else{
     92         printk("find node failed
    ");
     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
    ");
    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
    ");
    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-------------
    ", __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-----------
    ",__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-----------
    ",__FUNCTION__);
     60 
     61     int value;
     62     //读取按键状态
     63     value = readl(key_dev->reg_base + 4) & (0x01<<2);
     64     
     65     if(value){
     66         printk("key3 up
    ");
     67         key_dev->event.code  = KEY_ENTER;
     68         key_dev->event.value = 0;
     69     }else{
     70         printk("key3 down
    ");
     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
    ");
     91     }else{
     92         printk("find node failed
    ");
     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
    ");
    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
    ");
    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

  • 相关阅读:
    ASP.NET MVC之从控制器传递数据到视图
    ASP.NET MVC之从视图传递数据到控制器
    JQuery和ASP.NET之间的传值
    ASP.NET前台html页面AJAX提交数据后台ashx页面接收数据
    Variadic Templates 可变参数模板
    C++类型转换
    C++优化string,实现写时拷贝
    C++实现一个线程安全的单例模式
    C++ 实现简单的string
    C++实现简单的线程安全队列
  • 原文地址:https://www.cnblogs.com/y4247464/p/12393531.html
Copyright © 2011-2022 走看看