• linux驱动移植输入子系统案例


    在上一节我们介绍了linux系统的输入子系统的基本框架,并进行了源码分析。

    这一节我们将尝试向input子系统注册设备驱动,这里我们编写按键驱动,通过MIni2440上的6个按键来模拟键盘中的A、B、C、D、E、F。

    一、按键硬件资源

    1.1 硬件接线

    查看Mini2440原理图、S3C2440数据手册,了解如何读取按键的状态。这里粗略介绍一下Mini2440 K1~K6的接线方式,以及寄存器的设置,这里简单说一下,就不具体介绍了:

    • K1~K6依次对应引脚GPG0、GPG3、GPG5、GPG6、GPG7、GPG11,以K1为例;
    • 按键按下引脚输入低电平、按键松开引脚输入高电平;
    • 配置控制寄存器GPGCON(0x56000060)的bit[1:0]=00,使GPB5引脚为输入模式;
    • 读取配置数据寄存器GPGDAT(0x56000064)的bit0的电位;

    二、input子系统设备驱动

    2.1 input子系统设备驱动编写流程

    在上一节我们简单介绍了向input子系统注册设备驱动过程,这里我们以按键驱动为例:

    • 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;
    • 通过input_set_capability设置input设备可以上报哪些输入事件;
    • 初始化dev成员,设计所要实现的操作,比如 open、close、event、flush函数,;
    • 然后调用input_register_device注册这个设备;
    • 初始化定时器和中断,并编写中断处理函数,以及定时器超时函数;
    • 在出口函数中卸载中断,删除定时器,卸载驱动;

    2.2 相关结构声明

    在/work/sambashare/drivers下新建9.input_button_dev文件夹,用来编写我们的按键中断程序。

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/io.h>
    #include <linux/uaccess.h>
    #include <linux/gpio.h>
    #include <linux/irq.h>        // 包含了mach/irqs.h
    #include <linux/interrupt.h>
    #include <linux/gpio/machine.h>
    #include <mach/gpio-samsung.h>
    #include <linux/input.h>
    #include <linux/timer.h>
    
    /*
     全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在
     定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用
     */
    #define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING
    #define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING
    #define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW
    #define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH
    #define IRQT_NOEDGE (0)
    #define IRQT_RISING (__IRQT_RISEDGE)
    #define IRQT_FALLING (__IRQT_FALEDGE)
    #define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE)
    #define IRQT_LOW (__IRQT_LOWLVL)
    #define IRQT_HIGH (__IRQT_HIGHLVL)
    #define IRQT_PROBE IRQ_TYPE_PROBE
    
    
    /* 引脚信息 */
    struct pin_desc{
        int irq;                   // 中断编号
        unsigned int  irq_ctl;     //触发中断状态
        char *name;                // 引脚名称
        unsigned int pin;          // 引脚编号
        unsigned int key_val;      // 对应键盘的A,B,C,D,E,F
    };
    
    /*
     * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h
     */
    static struct pin_desc pins_desc[6] = {
        {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A},
        {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B},
        {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C},
        {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D},
        {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E},
        {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F},
    };
    
    
    /* 定义一个input_dev结构体 */
    struct input_dev *button_dev;
    /* 保存dev_id,在定时器中用 */
    struct pin_desc *button_id;
    /* 定时器 */
    static struct timer_list button_timer;

    2.3 注册中断

    GPG0、GPG3、GPG5、GPG6、GPG7、GPG11对应的外部中断依次为EINT8、EINT11、EINT13、EINT14、EINT15、EINT19。

    这里通过request_irq函数注册GPG0、GPG3、GPG5、GPG6、GPG7、GPG11为外部中断,触发方式为双边沿。

    /* 
       GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 
       IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h
    */
    static int button_open(struct input_dev *dev)
    {
        printk("register irq\n");
        int i,j,err;
        /* 注册中断 */
        for(i=0;i<6;i++){
           err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]);
           if(err < 0){
             for(j=0;j<i;j++){
                free_irq(pins_desc[j].irq, &pins_desc[j]);
             }
           }
        }
    
        return err;
    }

    这里我们EINT8、EINT11、EINT13、EINT14、EINT15、EINT19中断共用一个中断处理程序,因此我们将dev_id字段设置为一个结构体,用来标识唯一设备。

    /*
     * 中断处理服务
     */
    static irqreturn_t button_irq(int irq, void *dev_id)
    {
        //保存当前的dev_id
        button_id =(struct ping_desc *)dev_id;
        //设置定时器值 10ms后执行,用于防止按键抖动
        mod_timer(&button_timer, jiffies+HZ/100 );
     
        return IRQ_RETVAL(IRQ_HANDLED);
    }

    2.4 释放中断

    我们在.close函数中通过free_irq函数进行释放中断资源:

    /*
     * 卸载中断
     */
    int button_close(struct input_dev *dev)
    {
        printk("unregister irq\n");
        int i=0;
        for(i=0;i<6;i++){
           free_irq(pins_desc[i].irq, &pins_desc[i]);
        }
    
        return 0;
    }

    2.5 定时器超时函数

    这里为了防止按键抖动,加入了定时器,并设置定时器超时函数:

    /*
     * 定时器超时函数
     * 将输入转换为转换为统一事件形式
     */
    static void button_timer_timeout(struct timer_list *t)
    {
         //获取引脚电平
         int val = gpio_get_value(button_id->pin);
          if(val)        {
              /* 高电平,松开 上报事件*/
              input_event(button_dev, EV_KEY, button_id->key_val,  0);  //上报EV_KEY类型,按键值,0(没按下)
              input_sync(button_dev);         // 上传同步事件,告诉系统有事件出现
          }
          else  {
              /*  低电平,按下 上报事件*/
              input_event(button_dev, EV_KEY, button_id->key_val, 1);  //上报EV_KEY类型,按键值,1(按下)
              input_sync(button_dev);       // 上传同步事件,告诉系统有事件出现
          }
    }

    这里当有按键按下时,我们利用input_event函数将输入转换为转换为统一事件形式,向输入核心层汇报。关于input_event函数介绍可以参考:input_event 详解-Touch Screen

    2.6 注册button驱动程序

    /*
     * 入口函数
     */
    static int button_init(void)
    {
        int err;
        printk("button driver init\n");
    
        /* 向内核 申请input_dev结构体 */
        button_dev = input_allocate_device();
    
        /* 设置input_dev */
        input_set_capability(button_dev,EV_KEY,KEY_A);   //支持按键 A
        input_set_capability(button_dev,EV_KEY,KEY_B);   //支持按键 B
        input_set_capability(button_dev,EV_KEY,KEY_C);   //支持按键 C
        input_set_capability(button_dev,EV_KEY,KEY_D);   //支持按键 D
        input_set_capability(button_dev,EV_KEY,KEY_E);   //支持按键 E
        input_set_capability(button_dev,EV_KEY,KEY_F);   //支持按键 F
        set_bit(EV_REP,button_dev->evbit);       //支持键盘重复按事件
        button_dev->open = button_open;          // 注册中断
        button_dev->close = button_close;        // 卸载中断
    
        /* 注册input_dev */
        err = input_register_device(button_dev);
        if (err) {
           printk("input button driver registration failed\n");
           /* 释放驱动结构体 */
           input_free_device(button_dev);
           return err;
        } else {
            printk("input button driver registered successfully\n");
        }
    
        /* 初始化定时器 */
        timer_setup(&button_timer,button_timer_timeout,0);
        add_timer(&button_timer);
    
        return 0;
    }

    2.7 卸载button驱动程序

    /*
     * 出口函数
     */
    static void __exit button_exit(void)
    {
        printk("button driver exit\n");
    
         /* 删除定时器 */
        del_timer(&button_timer);
         /* 卸载类下的驱动设备 */
        input_unregister_device(button_dev);
        /* 释放驱动结构体 */
        input_free_device(button_dev);
        return;
    }

    2.8 完整代码

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/io.h>
    #include <linux/uaccess.h>
    #include <linux/gpio.h>
    #include <linux/irq.h>        // 包含了mach/irqs.h
    #include <linux/interrupt.h>
    #include <linux/gpio/machine.h>
    #include <mach/gpio-samsung.h>
    #include <linux/input.h>
    #include <linux/timer.h>
    
    /*
     全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在
     定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用
     */
    #define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING
    #define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING
    #define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW
    #define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH
    #define IRQT_NOEDGE (0)
    #define IRQT_RISING (__IRQT_RISEDGE)
    #define IRQT_FALLING (__IRQT_FALEDGE)
    #define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE)
    #define IRQT_LOW (__IRQT_LOWLVL)
    #define IRQT_HIGH (__IRQT_HIGHLVL)
    #define IRQT_PROBE IRQ_TYPE_PROBE
    
    
    /* 引脚信息 */
    struct pin_desc{
        int irq;                   // 中断编号
        unsigned int  irq_ctl;     //触发中断状态
        char *name;                // 引脚名称
        unsigned int pin;          // 引脚编号
        unsigned int key_val;      // 对应键盘的A,B,C,D,E,F
    };
    
    /*
     * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h
     */
    static struct pin_desc pins_desc[6] = {
        {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A},
        {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B},
        {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C},
        {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D},
        {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E},
        {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F},
    };
    
    
    /* 定义一个input_dev结构体 */
    struct input_dev *button_dev;
    /* 保存dev_id,在定时器中用 */
    struct pin_desc *button_id;
    /* 定时器 */
    static struct timer_list button_timer;
    
    /*
     * 中断处理服务
     */
    static irqreturn_t button_irq(int irq, void *dev_id)
    {
        //保存当前的dev_id
        button_id =(struct ping_desc *)dev_id;
        //设置定时器值 10ms后执行,用于防止按键抖动
        mod_timer(&button_timer, jiffies+HZ/100 );
    
        return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    /*
     * 定时器超时函数
     * 将输入转换为转换为统一事件形式
     */
    static void button_timer_timeout(struct timer_list *t)
    {
         //获取引脚电平
         int val = gpio_get_value(button_id->pin);
          if(val)        {
              /* 高电平,松开 上报事件*/
              input_event(button_dev, EV_KEY, button_id->key_val,  0);  //上报EV_KEY类型,按键值,0(没按下)
              input_sync(button_dev);         // 上传同步事件,告诉系统有事件出现
          }
          else  {
              /*  低电平,按下 上报事件*/
              input_event(button_dev, EV_KEY, button_id->key_val, 1);  //上报EV_KEY类型,按键值,1(按下)
              input_sync(button_dev);       // 上传同步事件,告诉系统有事件出现
          }
    }
    
    /* 
       GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 
       IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h
    */
    static int button_open(struct input_dev *dev)
    {
        printk("register irq\n");
        int i,j,err;
        /* 注册中断 */
        for(i=0;i<6;i++){
           err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]);
           if(err < 0){
             for(j=0;j<i;j++){
                free_irq(pins_desc[j].irq, &pins_desc[j]);
             }
           }
        }
    
        return err;
    }
    
    
    /*
     * 卸载中断
     */
    int button_close(struct input_dev *dev)
    {
        printk("unregister irq\n");
        int i=0;
        for(i=0;i<6;i++){
           free_irq(pins_desc[i].irq, &pins_desc[i]);
        }
    
        return 0;
    }
    
    /*
     * 入口函数
     */
    static int button_init(void)
    {
        int err;
        printk("button driver init\n");
    
        /* 向内核 申请input_dev结构体 */
        button_dev = input_allocate_device();
    
        /* 设置input_dev */
        input_set_capability(button_dev,EV_KEY,KEY_A);   //支持按键 A
        input_set_capability(button_dev,EV_KEY,KEY_B);   //支持按键 B
        input_set_capability(button_dev,EV_KEY,KEY_C);   //支持按键 C
        input_set_capability(button_dev,EV_KEY,KEY_D);   //支持按键 D
        input_set_capability(button_dev,EV_KEY,KEY_E);   //支持按键 E
        input_set_capability(button_dev,EV_KEY,KEY_F);   //支持按键 F
        set_bit(EV_REP,button_dev->evbit);       //支持键盘重复按事件
        button_dev->open = button_open;          // 注册中断
        button_dev->close = button_close;        // 卸载中断
    
        /* 注册input_dev */
        err = input_register_device(button_dev);
        if (err) {
           printk("input button driver registration failed\n");
           /* 释放驱动结构体 */
           input_free_device(button_dev);
           return err;
        } else {
            printk("input button driver registered successfully\n");
        }
    
        /* 初始化定时器 */
        timer_setup(&button_timer,button_timer_timeout,0);
        add_timer(&button_timer);
    
        return 0;
    }
    
    /*
     * 出口函数
     */
    static void __exit button_exit(void)
    {
        printk("button driver exit\n");
    
         /* 删除定时器 */
        del_timer(&button_timer);
         /* 卸载类下的驱动设备 */
        input_unregister_device(button_dev);
        /* 释放驱动结构体 */
        input_free_device(button_dev);
        return;
    }
    
    module_init(button_init);
    module_exit(button_exit);
    MODULE_LICENSE("GPL");
    View Code

    2.9 Makefile

    KERN_DIR :=/work/sambashare/linux-5.2.8
    all:
        make -C $(KERN_DIR) M=`pwd` modules 
    clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
    
    obj-m += button_dev.o

    三、input子系统设备驱动测试

    3.1 编译驱动

    执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:

    cd /work/sambashare/drivers/9.input_button_dev
    make
    cp /work/sambashare/drivers/9.input_button_dev/button_dev.ko /work/nfs_root/rootfs

    安装驱动:

    [root@zy:/]# insmod button_dev.ko
    button_dev: loading out-of-tree module taints kernel.
    button driver init
    input: Unspecified device as /devices/virtual/input/input0
    register irq

    查看设备节点文件:

    [root@zy:/]# ls /dev/input -l
    total 0
    crw-rw----    1 0        0          13,  64 Jan  1 00:00 1 /dev/input/event0

    可以看到设备节点的的主设备号为13,次设备号为64,和我们上一节介绍的一致。

    3.2 中断查看

    运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:

    [root@zy:/]# cat /proc/interrupts
               CPU0       
     29:      16393       s3c  13 Edge      samsung_time_irq
     32:          0       s3c  16 Edge      s3c2410-lcd
     42:          0       s3c  26 Edge      ohci_hcd:usb1
     43:          0       s3c  27 Edge      s3c2440-i2c.0
     55:       1607   s3c-ext   7 Edge      eth0
     56:          0   s3c-ext   8 Edge      K1
     59:          0   s3c-ext  11 Edge      K2
     61:          0   s3c-ext  13 Edge      K3
     62:          0   s3c-ext  14 Edge      K4
     63:          0   s3c-ext  15 Edge      K5
     67:          0   s3c-ext  19 Edge      K6
     74:         48  s3c-level   0 Edge      s3c2440-uart
     75:        192  s3c-level   1 Edge      s3c2440-uart
     87:          0  s3c-level  13 Edge      s3c2410-wdt
    Err:          0

    可以看到我们注册的外部中断8、11、13、14、15、19.

    3.3 测试驱动

    在开发版上执行:

    [root@zy:/]# cat /dev/tty1

    此命令表示显示输出tty1。按下开发板的K1~K6,此时串口终端结果如下:

    [root@zy:/]# cat /dev/tty1 
    accdef

    按键虚拟键盘键值正确被系统接收到了,说明我们的驱动成功了。

    3.4 卸载驱动

    通过用lsmod可以查看当前安装了哪些驱动:

    [root@zy:/]# lsmod
    button_dev 2098 0 - Live 0xbf000000 (O)

    卸载时直接运行:

    rmmod button_dev

    四、总结

    4.1 程序执行流程

    这里简单介绍一下程序的执行流程:

    • 在button_init初始化函数中分配一个input_dev结构体,设置并注册它;
    • 当按键按下或松开时,中断发生,调用中断函数button_irq;
    • 该函数里在按键事件10ms后调用button_timer_timeout定时器函数;
    • 在该函数里面针对中断获取到的不同键值调用input_event函数上报。

    而我们的程序里并没有创建设备节点那一套操作,是由input_register_device替我们完成的。

    五、代码下载

    Young / s3c2440_project[drivers]

    参考文章

    [1]十、Linux驱动之输入子系统使用

    [2]13.Linux键盘按键驱动 (详解)

  • 相关阅读:
    MySQL复制延时排查
    SQL优化之【类型转换】
    Twemproxy 介绍与使用
    Redis Cluster 3.0搭建与使用
    unauthenticated user reading from net
    XtraBackup之踩过的坑
    Redis学习之实现优先级消息队列
    如何保证接口的幂等性
    Redis缓存网页及数据行
    Rabbitmq 消费者的推模式与拉模式(go语言版本)
  • 原文地址:https://www.cnblogs.com/zyly/p/16123998.html
Copyright © 2020-2023  润新知