• Linux input


    Linux input

    输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系统也支持自动创建设备文件,这些文件采用阻塞的IO读写方式,被创建在"/dev/input/"下。如下图所示。内核中的输入子系统自底向上分为设备驱动层,输入核心层,事件处理层。由于每种输入的设备上报的事件都各有不同,所以为了应用层能够很好识别上报的事件,内核中也为应用层封装了标准的接口来描述一个事件,这些接口在"/include/upai/linux/input"中。

    • 设备驱动层是具体硬件相关的实现,也是驱动开发中主要完成的部分,
    • 输入核心层主要提供一些API供设备驱动层调用,通过这些API设备驱动层上报的数据就可以传递到事件处理层,
    • 事件处理层负责创建设备文件以及将上报的事件传递到用户空间,

    input的使用

    input对象描述了一个输入设备,包括它可能上报的事件,这些事件使用位图来描述,内核提供的相应的工具帮助我们构建一个input对象,大家可以参考内核文档"Documentation/input/input-programming.txt",里面对于input子系统的使用有详细的描述。

    //input设备对象
    121 struct input_dev {
    122         const char *name;
    129         unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
    130         unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
    131         unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
    132         unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
    133         unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
    134         unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
    135         unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    136         unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    137         unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
    155 
    162         unsigned long key[BITS_TO_LONGS(KEY_CNT)];
    163         unsigned long led[BITS_TO_LONGS(LED_CNT)];
    164         unsigned long snd[BITS_TO_LONGS(SND_CNT)];
    165         unsigned long sw[BITS_TO_LONGS(SW_CNT)];
    166 
    172         struct input_handle __rcu *grab;
    179 
    180         struct device dev;
    181 
    182         struct list_head        h_list;
    183         struct list_head        node;
    190 };

    struct input_dev
    --122--> 这个name不是设备名,input子系统的设备名在子系统源码中指定的,不是这。
    --129--> 设备支持的输入事件位图,EV_KEY,EV_REL, etc
    --130--> 对于按键事件,设备支持的输入子事件位图
    --132--> 对于相对坐标事件,设备支持的相对坐标子事件位图
    --133--> 对于绝对坐标事件,设备支持的绝对坐标子事件位图
    --134--> 混杂设备的支持的子事件位图
    --180-->表示这是一个device。
    --182-->h_list是用来链接相关handle的链表
    --183-->node用来链接其他input_dev的链表

    分配/释放

    //drivers/input/input.c
    //创建一个input对象
    struct input_dev *input_allocate_device(void);
    
    //释放一个input对象
    void input_free_device(struct input_dev *dev);

    初始化

    初始化一个input对象是使用input子系统编写驱动的主要工作,内核在头文件"include/uapi/linux/input.h"中规定了一些常见输入设备的常见的输入事件,这些宏和数组就是我们初始化input对象的工具。这些宏同时用在用户空间的事件解析和驱动的事件注册,可以看作是驱动和用户空间的通信协议,所以理解其中的意义十分重要。在input子系统中,每一个事件的发生都使用事件(type)->子事件(code)->值(value)三级来描述,比如,按键事件->按键F1子事件->按键F1子事件触发的值是高电平1。注意,事件和子事件和值是相辅相成的,只有注册了事件EV_KEY,才可以注册子事件BTN_0,也只有这样做才是有意义的。
    下面就是内核约定的事件类型,对应应用层的事件对象的type域

    下面这些是按键子事件的类型,可以看到对PC键值的定义

    除了对常用的事件进行描述,内核同样提供了工具将这些事件正确的填充到input对象中描述事件的位图中。

    //第一种
    //这种方式非常适合同时注册多个事件
    button_dev->evbit[0] = BIT_MASK(EV_KEY);            
    button_dev->keybit[BIT_WORD(BTN_0|BTN_1)] = BIT_MASK(BTN_0|BTN_1); 
    //第二种
    //通常用于只注册一个事件
    set_bit(EV_KEY,button_dev.evbit);
    set_bit(BTN_0,button_dev.keybit);

    注册/注销

    初始化好了一个input对象,接下来就需要将其注册到内核

    //注册input对象到内核
    int input_register_device(struct input_dev *dev);
    
    //从内核注销一个input对象
    void input_unregister_device(struct input_dev *dev);

    驱动层报告事件

    在合适的时机(由于输入最终是中断表示的,所以通常在驱动的中断处理函数中)驱动可以将注册好的事件上报,且可以同时上报多个事件,下面是内核提供的API

    //上报指定的事件+子事件+值
    void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);
    
    //上报键值
    void input_report_key(struct input_dev *dev,unsigned int code,int value);
    
    //上报绝对坐标
    void input_report_abs(struct input_dev *dev,unsigned int code,int value);
    
    //报告同步事件
    void input_report_rel(struct input_dev *dev,unsigned int code,int value);
    
    //同步所有的上报
    void input_sync(struct input_dev *dev);

    上报事件有2点需要注意:

    1. report函数们并不会真的上报,只是准备上报,sync才会真的将刚刚report的事件真的上报搭input核心
    2. input核心会进行裁决再上报的事件处理层,所以对于按键事件,一定要先报1再报0(或者反过来),不能只report 1或0, 这样核心会认为是一个事件被误触发了多次而只上报一次,虽然我们真的按下了多次。

    应用层解析

    事件处理层最终会将驱动sync一次时所有report的事件组织成一个struct input_value[]的形式上报到应用层,在应用层从相应的设备文件中获取上报的事件的时候,需要注意:

    1. 收到数组元素的数量会比底层多一个空元素,类似于写of_device_id[]时最后的空元素,这点应用层在解析的时候需要注意。
    2. 事件处理层并不会缓存收到的事件,如果有新的事件到来,即使旧的事件没有被读取,也会被覆盖,所以应用程序需要及时读取。

    前文已经说过,"include/uapi/linux/input.h"中的宏是应用层和驱动层共用的通信协议,所以应用层在解析收到的struct input_value对象的时候,只需要"include <linux/input.h>"即可使用其中的宏。

    /*
     * The event structure itself
     */
    
    struct input_event {
        struct timeval time;
        __u16 type;
        __u16 code;
        __s32 value;
    };

    input分析

    上文已经说过,input子系统使用三层结构来实现驱动事件到应用层的传递。具体的,这三个层次每一个层次都由一条结构体链表组成,在设备驱动层,核心结构体是input_dev;在input核心层,是input_handle;在事件处理层,是input_handler。内核通过链表和指针将三者结合到一起,最终实现了input_dev和input_handler的多对多的映射关系,这种关系可用下图简单描述。

    模板

    下面的这个模板首先使用input子系统上报按键事件,然后在应用层读取。

    input按键设备驱动

    /{
               key@26{
                          compatible = "xj4412,key";
                          interrupt-parent = <&gpx1>;
                          interrupts = <2 2>;
               };
    };

    static struct input_dev *button_dev;
    static int button_irq;
    static int irqflags;
    
    static irqreturn_t button_interrupt(int irq, void *dummy)
    {
        input_report_key(button_dev, BTN_0, 0);
        input_report_key(button_dev, BTN_0, 1);
        input_sync(button_dev);
        return IRQ_HANDLED;
    }
     
    static int button_init(void)
    {
        request_irq(button_irq, button_interrupt,irqflags, "button", NULL)) ;
        
        button_dev = input_allocate_device();
        button_dev->name = "button";
        button_dev->evbit[0] = BIT_MASK(EV_KEY);
        button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
        
        input_register_device(button_dev);
        return 0;
    }
    
    static int button_exit(void)
    {
        input_free_device(button_dev);
        free_irq(button_irq, button_interrupt);
        return 0;   
    }
    static int key_probe(struct platform_device *pdev)
    {
        struct resource *irq_res;
        irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if(irq_res){
            button_irq = irq_res->start;
            irqflags = irq_res->flags & IRQF_TRIGGER_MASK;
        }else{
            return -EINVAL;     
        }
        return button_init();
    }
    
    static int key_remove(struct platform_device *dev)
    {
        return button_exit();
    }
    
    struct of_device_id of_tbl[] = {
        {.compatible = "xj4412,key",},
        {},
    };
    MODULE_DEVICE_TABLE(of, of_tbl);
    struct platform_driver key_drv = {
        .probe = key_probe,
        .remove = key_remove,
        .driver.name = "keydrv",
        .driver.of_match_table = of_tbl,
    };
    module_platform_driver_register(key_drv);
    MODULE_LICENSE("GPL");

    应用层获取键值

    #include <linux/input.h>
    struct input_event {
        struct timeval time;
        unsigned short type;
        unsigned short code;
        int value;
    };
    int main(int argc, char * const argv[])
    {
        int fd = 0;
        struct input_event event[3] = {0};      //3!!!,驱动上传了2个事件,第三个用来装空元素 
        int ret = 0;
        fd = open(argv[1],O_RDONLY);
        while(1){
            ret = read(fd,&event,sizeof(event));
            printf("ret:%d,val0:%d,val1:%d,val12:%d
    ",ret,event[0].value,event[1].value,event[2].value);          //2!!!,最后一个是空
            sleep(1);
        }
        return 0;
    }

    

     
  • 相关阅读:
    python06
    python05
    Python02
    pythonday01
    python04
    Mac下如何安装pip
    更改pip源至国内镜像,显著提升下载速度
    login登录加密
    虚拟机安装Centos6.5之后的网络配置
    git常用的问题
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6423086.html
Copyright © 2020-2023  润新知