Linux input子系统

一、Input子系统分层思想

    input子系统是典型的字符设备。首先分析输入子系统的工作机理。底层设备(按键、触摸等)发生动作时,产生一个事件(抽象),CPU读取事件数据放入缓冲区,字符设备驱动管理该缓冲区。不同的输入事件的缓冲管理及字符设备驱动的file_operations接口对输入设备是通用的。所有linux内核就引入了输入子系统,由核心层统一关系这些公共的部分,这就是Linux内核的归类思想和分层思想,把同等类型的驱动进行归类并在内核中以子系统的方式统一管理。

    输入子系统由输入子系统驱动层(Input Driver),核心层(Input Core)和事件处理层(Event Handler)三部分组成。框架图如下图:

             

    其中驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;核心层主要是对下提供了设备驱动层的编程接口,对上提供了事件处理层的编程接口;而事件处理层为用户空间的应用提供了统一访问设备的接口和驱动层提交上来的事件报告给用户。所以这使得输入设备的驱动部分不需要关心对设备文件的操作,而只需要关心对各硬件寄存器的操作和所提交的输入事件。

    各层之间通信的方式抽象为一种事件。而事件有三种属性:类型(type),编码(code),值(value)。Input子系统支持的事件都定义在include/linux/input.h中,包括所有支持的类型,所属类型支持的编码等。

事件传递方向:

             驱动层-->核心层-->事件处理层-->用户层

二、三个重要的结构体

  1input_dev结构体

   input_dev对应着实际的设备端,定义并规定了各种设备的信息等。

 
    struct input_dev {  
        const char *name;    //设备名,将在sys/class/input/XXX/name phys 中保存
        const char *phys;     //设备系统层的物理路径将在sys/class/input/XXX/phys 中保存  
        const char *uniq;    
        struct input_id id;   //输入设备id 总线类型;厂商编号,产品id,产品版本与input_hander匹配时会用到  
        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //事件类型标志位  
        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //按键事件支持的子事件,按键类型  
        unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   //相对位移事件标志位  
        unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   //绝对位移事件标志位  
        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //杂项事件标志位  
        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //led指示灯标志位  
        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //声音事件  
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //强制反馈事件  
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //开关事件标志位  
  
        unsigned int hint_events_per_packet;  
        unsigned int keycodemax;  //键盘码表大小  
        unsigned int keycodesize; //键盘码大小  
        void *keycode;            //键盘码表指针  
  
        int (*setkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int keycode);//设置键盘码  
        int (*getkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int *keycode);//获取键盘码  
        int (*setkeycode_new)(struct input_dev *dev,const struct input_keymap_entry *ke,unsigned int *old_keycode);   
        int (*getkeycode_new)(struct input_dev *dev,struct input_keymap_entry *ke);  
  
        struct ff_device *ff;   //强制反馈设备  
        unsigned int repeat_key;    //重复按键标志位  
        struct timer_list timer;    //定时器  
        int rep[REP_CNT];       //重复次数  
        struct input_mt_slot *mt;  
        int mtsize;  
        int slot;  
        struct input_absinfo *absinfo;  
        unsigned long key[BITS_TO_LONGS(KEY_CNT)];  //  
        unsigned long led[BITS_TO_LONGS(LED_CNT)];  //  
        unsigned long snd[BITS_TO_LONGS(SND_CNT)];  //  
        unsigned long sw[BITS_TO_LONGS(SW_CNT)];    //  
  
        int (*open)(struct input_dev *dev);     //open方法  
        void (*close)(struct input_dev *dev);   //close方法  
        int (*flush)(struct input_dev *dev, struct file *file);  
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  
        struct input_handle __rcu *grab;  
        spinlock_t event_lock;  
        struct mutex mutex;  
        unsigned int users;  
        bool going_away;  
        bool sync;  
        struct device dev;      //设备文件  
        struct list_head    h_list; //input_handler处理器链表头  
        struct list_head    node;   //input_device设备链表头  
    }; 

  unsigned long evbit[BITS_TO_LONGS(EV_CNT)]数组,代表这个设备支持那类事件。事件类型:

     #define EV_SYN 0x00  //同步事件

     #define EV_KEY 0x01  //按键类型

     #define EV_REL 0x02  //相对位移类型

     #define EV_ABS 0x03  //绝对位移类型

     #define EV_MSC 0x04

     #define EV_SW 0x05

         #define EV_LED 0x11

     #define EV_SND 0x12

     #define EV_REP 0x14  //重复类型

     #define EV_FF 0x15  

     #define EV_PWR 0x16

     #define EV_FF_STATUS  0x17

     #define EV_MAX 0x1f

     #define CNT (EV_MAX+1)

  unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];代表这个设备支持哪些按键。按键类型:

    #define KEY_RESERVED 0

    #define KEY_ESC 1

    #define KEY_1 2

    ...

    #define KEY_A 30

    #define KEY_S 31

    ...

详细的内容见include/linux/input.h

2、input_handler结构体

      input_handler属于输入子系统的事件层。当事件处理器接收到来自字符设备传来的事件时,调用用来处理事件。

  struct input_handler {  
      void *private;  //私有数据  
      void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); //事件处理  
      bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value); //过滤器  
      bool (*match)(struct input_handler *handler, struct input_dev *dev); //设备匹配  
      int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); //设备连接  
      void (*disconnect)(struct input_handle *handle);    //设备断开连接  
      void (*start)(struct input_handle *handle);  
      const struct file_operations *fops; //输入操作函数集  
      int minor;  //次设备号  
      const char *name;   //设备名  
      const struct input_device_id *id_table; //输入设备 id表  
      struct list_head    h_list; //input_handler处理器链表头  
      struct list_head    node;   //input_device设备链表头  
  };

3、input_handle结构体

    input_handle为事件input_devinput_handler之间的沟通者。每个配对的事件处理器都会分配一个对应的设备结构。 

    struct input_handle{
        void *private;
        int open;
        const char *name;
 
        struct input_dev   *dev;  //指向input_dev结构体
        struct input_handler   *handler;  //指向input_handler结构体
 
        struct list_head   d_node; //通过d_node连接到input_dev上的h_list链表上
        struct list_head   h_node; //通过h_node连接到input_handler上的h_list链表上
    }

三、编写Input设备驱动的步骤

   1、分配一个输入设备。

   2、注册一个输入设备。

   3、设置输入设备事件类型,主要是input_dev结构中的evbit和keybit。

   4、驱动事件报告。

   5、释放和注销input设备。

  附上一个简单的按键驱动,按键采用中断的方式。

#include...
static struct input_dev *button_dev;
 
static irqreturn_t button_interrupt(int irq, void *dummy)
{
    input_report_key(button_dev, BTN_0, 1);
    input_sync(button_dev);
    return IRQ_HANDLED;
}
 
static int __init button_init(void)
{
    int err;
    if(err = request_irq(IRQ_EINT(16), button_interrupt, 0,”BUTTON_0”, NULL) )
    {
        return -EBUSY;  
    }
 
    button_dev = input_allocate_device(); //分配一个input_dev结构体
  
    set_bit(EV_KEY, button_dev->evbit); //支持按键类事件
    set_bit(EV_REP, button_dev->evbit); //支持重复类事件
    set_bit(BTN_0, button_dev->keybit); 
 
    err = input_register_device(button_dev); //注册一个输入设备
    if(err)
    {
        goto fail;
    }
    return 0;
fail:
    input_free_device(button_dev);
    return err;
}
static void button_exit(void)
{
    free_irq(IRQ_EINT(16), button_interrupt);
    input_unregister_device(button_dev);
}
 
modult_init(button_init);
modult_exit(button_exit);

二、重要函数分析

    Input子系统的核心在drivers/input/input.c和evdev.c中,详细内容需要自己去解读。

  1、input_allocate_device()

struct input_dev *input_allocate_device(void)
{
    struct input_dev *dev;
    //分配一个input_dev结构体,并初始化为0
    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
    if(dev)
    {
        dev->dev.type = &input_dev_type; /*初始化设备类型*/
        dev->dev.class = &input_class;  /*设置输入设备类*/
        device_initialize(&dev->dev);  /*初始化device 结构*/
        mutex_init(&dev->mutex); /*初始化互斥锁*/
        spin_lock_init(&dev->event_lock); /*初始化事件自旋锁*/
        INIT_LIST_HEAD(&dev->h_list); /*初始化链表 */
        INIT_LIST_HEAD(&dev->node); /*初始化链表*/<pre name="code" class="csharp">static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    /*输入设备的指针,该结构体表示设备的标识,标识中存储了设备的信息*/
    const struct input_device_id *id;
    int error;
    
    /*根据input_handler的id_table判断能否支持这个设备*/
    id = input_match_device(handler, dev);
    if(!id)
        return -ENODEV;
    /*调用input_handler的connect函数,建立连接*/
    error = handler->connect(handler, dev, id); /*连接设备和处理函数*/
    if(error && error != -ENODEV)
        pr_err(“failed to attach handler %s to device %s, error: %d\n”,handler->name, kobject_name(&dev->dev.kobj), error);
    return error;
}

__module_get(THIS_MODULE); } return dev;}
 

  2、input_register_device函数

    初始化一些默认的值,将自己的结构体添加到linux设备模型中,将input_dev添加到input_dev_list链表中。然后调用input_attach_handler根据input_handler的id_table判断是否支持这个设备。

int input_register_device(struct input_dev *dev)
{
    ...
    struct input_handler *handler;
    ...
    int error;
    __set_bit(EV_SYN, dev->evbit);  //设置输入设备支持同步类事件
    ...
    error = device_add(&dev->dev);  //将device添加到linux设备模型中
    ...
    list_add_tail(&dev->node, &input_dev_list); //把input_dev放入到input_dev_list链表
    ...
    list_for_each_entry(handler,&input_handler_list, node)
        input_attach_handler(dev,handler);
    /*对于input_handler_list链表的每一项,都调用input_attach_handler,根据input_handler的id_table判断是否支持input_dev*/
}

   3、input_attach_handler

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    /*输入设备的指针,该结构体表示设备的标识,标识中存储了设备的信息*/
    const struct input_device_id *id;
    int error;
    
    /*根据input_handler的id_table判断能否支持这个设备*/
    id = input_match_device(handler, dev);
    if(!id)
        return -ENODEV;
    /*调用input_handler的connect函数,建立连接*/
    error = handler->connect(handler, dev, id); /*连接设备和处理函数*/
    if(error && error != -ENODEV)
        pr_err(“failed to attach handler %s to device %s, error: %d\n”,handler->name, kobject_name(&dev->dev.kobj), error);
    return error;
}

  4、evdev_connect函数

    evdev_connect函数在driver/input/evdev.c中。主要用来连接input_dev和input_handler,把事件建立起来,这样事件才能知道被谁处理,或者处理后将推向谁返回结果。

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)  
{  
        struct evdev *evdev;  
        int minor;  
        int error;  
        /*第 07~13 行,for 循环中的 EVDEV_MINORS 定义为 32,表示 evdev_handler 所表示的 32 个设备文件。 evdev_talbe 是一个 struct evdev 类型的数组,struct evdev 是模块使用的封装结构,与具体的输入设备有关。第 08 行,这一段代码的在 evdev_talbe 找到为空的那一项,当找到为空的一项,便结束 for 循环。这时,minor 就是数组中第一项为空的序号。第 10 到13 行,如果没有空闲的表项,则退出。*/  
      for (minor = 0; minor < EVDEV_MINORS; minor++)  
        if (!evdev_table[minor])  
                break;  
  
        if (minor == EVDEV_MINORS) {  
                printk(KERN_ERR "evdev: no more free evdev devices\n");  
                return -ENFILE;  
        }  
  
        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);/*第 14~16 行,分配一个 struct evdev 的空间,如果分配失败,则退出。*/  
        if (!evdev)  
                return -ENOMEM;  
/*第 17~20 行,对分配的 evdev 结构进行初始化,主要对链表、互斥锁和等待队列做必要的封装了一个 handle 结构,这个结构与 handler 是不同的。可以把 handle初始化。 evdev 中,在这个结构用来联系匹配成功的 handler 和 input 看成是 handler 和 input device 的信息集合体,device。*/  
        INIT_LIST_HEAD(&evdev->client_list);  
        spin_lock_init(&evdev->client_lock);  
        mutex_init(&evdev->mutex);  
        init_waitqueue_head(&evdev->wait);  
        /*第 21 行,对 evdev 命一个名字,这个设备的名字形如 eventx, 如 event1、 event2 和 event3等。最大有 32 个设备,这个设备将在/dev/input/目录下显示。*/  
       dev_set_name(&evdev->dev, "event%d", minor);  
       evdev->exist = 1;  
        /*第 23~27 行,对 evdev 进行必要的初始化。其中,主要对 handle 进行初始化,这些初始化的目的是使 input_dev 和 input_handler 联系起来。*/  
        evdev->minor = minor;  
  
        evdev->handle.dev = input_get_device(dev);  
        evdev->handle.name = dev_name(&evdev->dev);  
        evdev->handle.handler = handler;  
        evdev->handle.private = evdev;  
        /*第 28~33 行,在设备驱动模型中注册一个 evdev->dev 的设备,并初始化一个 evdev->dev 的设备。这里,使 evdev->dev 所属的类指向 input_class。这样在/sysfs 中创建的设备目录就会在/sys/class/input/下显示。*/  
        evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);  
        evdev->dev.class = &input_class;  
        evdev->dev.parent = &dev->dev;  
        evdev->dev.release = evdev_free;  
        device_initialize(&evdev->dev);  
  
        error = input_register_handle(&evdev->handle);/*第 34 行,调用 input_register_handle()函数注册一个 input_handle 结构体。*/  
        if (error)  
        goto err_free_evdev;  
  
        error = evdev_install_chrdev(evdev);/*第 37 行,注册 handle,如果成功,那么调用 evdev_install_chrdev 将 evdev_table 的 minor项指向 evdev.。*/  
        if (error)  
                goto err_unregister_handle;  
  
        error = device_add(&evdev->dev);/*将 evdev->device 注册到 sysfs 文件系统中。*/  
        /*第 41~50 行,进行一些必要的错误处理。*/  
        if (error)  
                goto err_cleanup_evdev;  
  
        return 0;  
  
        err_cleanup_evdev:  
        evdev_cleanup(evdev);  
        err_unregister_handle:  
        input_unregister_handle(&evdev->handle);  
        err_free_evdev:  
        put_device(&evdev->dev);  
        return error;  
}  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值