前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux驱动之Input子系统剖析

Linux驱动之Input子系统剖析

作者头像
菜菜cc
发布2022-11-15 21:27:28
2.2K0
发布2022-11-15 21:27:28
举报
文章被收录于专栏:菜菜的技术博客

为了对多种不同类型的输入设备进行统一的处理,内核在字符设备驱动上抽象出一层,即输入子系统。

输入子系统由三部分组成:

  • 事件驱动层
  • 输入核心层
  • 设备驱动层

事件驱动层负责处理和应用程序的接口,向应用程序提供简单的、统一的事件接口。

设备驱动层负责与底层输入设备的通信。

输入核心层负责各个结构体的注册以及事件驱动层与设备驱动层的数据传递。

事件驱动层是内核提供的,对所有输入类设备都是通用的,内核里已经支持所有的事件驱动。而驱动开发则只需针对具体输入设备实现设备驱动。

三个重要结构体

都定义在include/linux/input.h中。

  • struct input_dev: 对输入设备的抽象描述,继承自struct device
  • struct input_handler: 代表输入设备的处理方法
  • struct input_handle: 用来关联某个input_dev和input_handler

struct input_dev

input_dev 代表底层的输入设备,比如按键或鼠标,所有输入设备的input_dev对象保存在一个全局的input_dev链表里。

struct input_handler

input_handler 代表某个输入设备的处理方法,比如evdev就是专门处理输入设备产生的事件,所有的input_handler对象保存在一个全局的input_handler链表里。

struct input_handle

一个input_dev可以有多个input_handler,比如鼠标可以由evdev和mousedev来处理它产生的输入;同样,一个input_handler可以用于多种输入设备的事件处理。由于这种多对多关系的存在,所以需要将input_dev和input_handler关联起来,而input_handle就是用来关联两者的。每个input_handle都会产生一个设备文件节点,比如/dev/input 下的四个文件event0~3。通过input_handle就可以找到对应的input_dev和input_handler。

源码分析

笔者会大体上对input子系统的源码进行分析,如若分析的有出入,还望指出。在分析之前,以一张input整体架构图来呈现整个输入设备到用户空间的数据传递。

事件驱动层

内核在事件驱动层中实现了一个输入设备通用的事件驱动,即evdev,其实现在driver/input/evdev.c中。无论是按键、触摸屏还是鼠标,都会通过evdev进行输入事件的处理。比如鼠标,如果用户空间读取的是evdev提供的设备节点,则上报的是一个未经处理的通用于所有输入设备的事件,而mousedev则会对输入事件进行处理从而上报的是鼠标特有的事件。笔者从evdev.c入手分析。

代码语言:javascript
复制
static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);
}

通过调用input_register_handler函数进行了evdev_handler的注册。evdev_handlerstruct input_handler的实例对象。

代码语言:javascript
复制
static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },   /* Matches all devices */
    { },            /* Terminating zero entry */
};

static struct input_handler evdev_handler = {
    .event      = evdev_event,
    .connect    = evdev_connect,
    .disconnect = evdev_disconnect,
    .fops       = &evdev_fops,
    .minor      = EVDEV_MINOR_BASE,
    .name       = "evdev",
    .id_table   = evdev_ids,
};

evdev_handler中描述了一些输入的处理函数以及与设备匹配用的id_table,在接下去的源码里会使用到。

现在进到input_register_handler函数里进行分析,以下是该函数所有源码,接下去会拆开分析。

input_register_handler函数 int input_register_handler(struct input_handler *handler) { struct input_dev *dev; int retval; retval = mutex_lock_interruptible(&input_mutex); if (retval) return retval; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) { // 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32) // 所以需要除于32来得到在input_table中的索引 if (input_tablehandler->minor >> 5) { // 重复注册,错误 retval = -EBUSY; goto out; } // 将handler放入input_table input_tablehandler->minor >> 5 = handler; } // 将handler放入input_handler_list链表中,表示注册了该handler list_add_tail(&handler->node, &input_handler_list); // 遍历已经注册的设备,匹配device和handler, // 匹配成功则调用handler->connect函数将device和handler关联成handle, // 然后进行设备的注册 list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); out: mutex_unlock(&input_mutex); return retval; }

input_register_handler函数定义在input.c中,即现在进入到了输入核心层。

代码语言:javascript
复制
if (handler->fops != NULL) {
    // 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32)
    // 所以需要除于32来得到在input_table中的索引
    if (input_table[handler->minor >> 5]) {  
        // 重复注册,错误
        retval = -EBUSY;
        goto out;
    }
    // 将handler放入input_table
    input_table[handler->minor >> 5] = handler;
}

// 将handler放入input_handler_list链表中,表示注册了该handler
list_add_tail(&handler->node, &input_handler_list);

evdev_handler的定义中可以看到handler->fops是有定义的,所以进入到子语句。这里解释一下handler->minor >> 5,内核中对每个事件驱动所支持的次设备号范围规定是[32 * n, 32 * n + 32),比如mousedev的次设备号范围是[32, 64)、evdev的次设备号范围是[64, 96)等,相当于将次设备号以32个为一组对各种事件驱动进行了分类。该段代码就是找到正确的位置将handler放入input_table中,然后将handler放入input_handler_list链表中,表示注册了该handler。

代码语言:javascript
复制
list_for_each_entry(dev, &input_dev_list, node)
    input_attach_handler(dev, handler);

这段代码是在遍历已经注册的设备,在input_attach_handler函数里匹配device和handler,匹配成功则调用handler->connect函数将device和handler关联成handle,然后进行设备的注册,然后input_register_handler函数基本上执行完毕。

进到input_attach_handler函数里进行分析。

input_attach_handler函数

代码语言:javascript
复制
int input_register_handler(struct input_handler *handler)
{
   struct input_dev *dev;
   int retval;

   retval = mutex_lock_interruptible(&input_mutex);
   if (retval)
      return retval;

   INIT_LIST_HEAD(&handler->h_list);

   if (handler->fops != NULL) {
      // 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32)
      // 所以需要除于32来得到在input_table中的索引
      if (input_table[handler->minor >> 5]) {  
          // 重复注册,错误
          retval = -EBUSY;
          goto out;
      }
      // 将handler放入input_table
      input_table[handler->minor >> 5] = handler;
   }

   // 将handler放入input_handler_list链表中,表示注册了该handler
   list_add_tail(&handler->node, &input_handler_list);

   // 遍历已经注册的设备,匹配device和handler,
   // 匹配成功则调用handler->connect函数将device和handler关联成handle,
   // 然后进行设备的注册
   list_for_each_entry(dev, &input_dev_list, node)
      input_attach_handler(dev, handler);

   input_wakeup_procfs_readers();

   out:
   mutex_unlock(&input_mutex);
   return retval;
}

从代码中可以看出先是匹配input_handlerinput_dev,匹配成功后则调用connect函数进行连接。

input_match_device函数

代码语言:javascript
复制
if (handler->fops != NULL) {
    // 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32)
    // 所以需要除于32来得到在input_table中的索引
    if (input_table[handler->minor >> 5]) {  
        // 重复注册,错误
        retval = -EBUSY;
        goto out;
    }
    // 将handler放入input_table
    input_table[handler->minor >> 5] = handler;
}

// 将handler放入input_handler_list链表中,表示注册了该handler
list_add_tail(&handler->node, &input_handler_list);

evdev_handler中的id_table的定义可以知道并没有定义任何flag和bit,所以这些严格匹配在evdev中都不会进行。而且handler->match为NULL,所以对于evdev而言这个函数并没有做什么,而是直接将id返回了。

回到input_attach_handler函数,最后在匹配成功后调用了handler->connect函数。这个connect函数实现在事件驱动层,所以回到evdev.c。

evdev_connect函数

代码语言:javascript
复制
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
   const struct input_device_id *id;
   int error;

   // 匹配input_handler和input_dev
   id = input_match_device(handler, dev);
   if (!id)
      return -ENODEV;

   // 匹配成功后,调用handler->connect将input_handler和input_dev绑定成input_handle
   error = handler->connect(handler, dev, id);
   if (error && error != -ENODEV)
      printk(KERN_ERR
          "input: failed to attach handler %s to device %s, "
          "error: %d\n",
          handler->name, kobject_name(&dev->dev.kobj), error);

   return error;
}

实例化了一个struct evdev对象,该结构体是对一个完整的evdev事件驱动的抽象描述。初始化struct evdev,将input_handlerinput_dev关联起来形成input_handle,然后赋给evdev->handle,生成设备的设备号,向内核注册input_handle,最后注册设备以及创建设备节点。至此evdev的注册就结束了。

设备驱动层

以usbmouse.c为例分析鼠标的设备驱动,鼠标是挂载在usb总线下,笔者在这里将usb相关的代码忽略,只关心输入子系统有关的代码。根据Linux设备模型的原理,直接进入到usb_mouse_probe函数进行分析。

代码语言:javascript
复制
struct input_dev *input_dev;
input_dev = input_allocate_device();
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;

input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
    BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
    BIT_MASK(BTN_EXTRA);
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);

input_set_drvdata(input_dev, mouse);

input_dev->open = usb_mouse_open;
input_dev->close = usb_mouse_close;

error = input_register_device(mouse->dev);

先实例化一个struct input_dev对象,然后进行相关初始化,struct input_dev成员中定义了一些位图,如下

代码语言:javascript
复制
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)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

所以在初始化中还对evbitkeybit等成员进行了初始化,表示鼠标所支持的事件类型。最后调用input_register_device函数完成了鼠标设备的注册。

usb_mouse_irq函数中进行事件的上报。

代码语言:javascript
复制
// data[0] & 0x01 取出最后一位,1表示按下,0表示未按下
input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);

input_report_rel(dev, REL_X,     data[1]);
input_report_rel(dev, REL_Y,     data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);

input_sync(dev);

关键部分就是调用input_report_key函数来上报按键信息,调用input_report_rel上报鼠标的相对位移,最后调用input_sync 来提交同步事件,告知input子系统,该设备已经提交了一个完整报告。

事件传递流程

设备驱动通过一系列input_report_xxx函数来上报事件,以input_report_key函数为例进行分析。

代码语言:javascript
复制
// 提交按键事件
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

input_report_key函数调用的input_event函数,其实一系列上报函数(包括input_sync函数)都是调用的input_event函数。

代码语言:javascript
复制
void input_event(struct input_dev *dev,
         unsigned int type, unsigned int code, int value)
{
    unsigned long flags;

    if (is_event_supported(type, dev->evbit, EV_MAX)) {

        spin_lock_irqsave(&dev->event_lock, flags);
        add_input_randomness(type, code, value);
        input_handle_event(dev, type, code, value);
        spin_unlock_irqrestore(&dev->event_lock, flags);
    }
}

关键就是调用了input_handle_event函数,而input_handle_event函数中的关键就是下面代码

代码语言:javascript
复制
if (disposition & INPUT_PASS_TO_HANDLERS)
    input_pass_event(dev, type, code, value);

进到input_pass_event函数

代码语言:javascript
复制
static void input_pass_event(struct input_dev *dev,
                 unsigned int type, unsigned int code, int value)
{
    struct input_handler *handler;
    struct input_handle *handle;

    rcu_read_lock();

    handle = rcu_dereference(dev->grab);
    if (handle)
        handle->handler->event(handle, type, code, value);
    else {
        bool filtered = false;

        list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
            if (!handle->open)
                continue;

            handler = handle->handler;
            if (!handler->filter) {
                if (filtered)
                    break;

                handler->event(handle, type, code, value);

            } else if (handler->filter(handle, type, code, value))
                filtered = true;
        }
    }

    rcu_read_unlock();
}

可以看到核心就是调用handler->event函数,以evdev为例,回到evdev.c中,进入到evdev_event函数中

代码语言:javascript
复制
client = rcu_dereference(evdev->grab);
if (client)
    evdev_pass_event(client, &event);
else
    list_for_each_entry_rcu(client, &evdev->client_list, node)
    evdev_pass_event(client, &event);

需要关心的部分是从evdev对象中取出了client对象(两者的挂接是在open时完成的),然后执行了evdev_pass_event(client, &event);(其中evdevstruct evdev的实例对象,是对一个完整的evdev事件驱动的抽象描述,其中struct evdev_client *grab成员管理该事件驱动下的所有clientclientstruct evdev_client的实例对象,对于同一个设备,每打开一次就会实例化出一个该结构体的对象)

代码语言:javascript
复制
static void evdev_pass_event(struct evdev_client *client,
                 struct input_event *event)
{
    /*
     * Interrupts are disabled, just acquire the lock
     */
    spin_lock(&client->buffer_lock);
    wake_lock_timeout(&client->wake_lock, 5 * HZ);
    client->buffer[client->head++] = *event;
    client->head &= EVDEV_BUFFER_SIZE - 1;
    spin_unlock(&client->buffer_lock);

    if (event->type == EV_SYN)
        kill_fasync(&client->fasync, SIGIO, POLL_IN);
}

从上面代码可以看到将struct input_event的实例对象存进了client中的buffer里。struct evdev_client的定义如下

代码语言:javascript
复制
// 每打开一次设备就会实例化出该结构体
struct evdev_client {
    // buffer用来存储从设备驱动中提交上来的事件,
    // 当应用程序read设备文件时,事件驱动会把该buffer传递给应用层
    struct input_event buffer[EVDEV_BUFFER_SIZE]; 
    int head;
    int tail;
    spinlock_t buffer_lock; /* protects access to buffer, head and tail */
    struct fasync_struct *fasync;
    struct evdev *evdev;
    struct list_head node;
    struct wake_lock wake_lock;
    char name[28];
};

所以到此就清晰了事件从底层设备如何传递到事件驱动层的,事件驱动层的cline->buffer就是用来中转数据的,接下来我们关心事件是如何从事件驱动层传递给应用层。

以evdev.c为例,进入到handler中的fops中的open和read函数。

evdev_open函数主要做的是根据次设备号减去基地址得到索引,从evdev_table中取出evdev对象,然后实例化出一个client对象,将clinet对象绑定到evdev对象中。

evdev_read函数的核心部分如下

代码语言:javascript
复制
while (retval + input_event_size() <= count &&
       evdev_fetch_next_event(client, &event)) {

    if (input_event_to_user(buffer + retval, &event))
        return -EFAULT;

    retval += input_event_size();
}

evdev_fetch_next_event函数从client->buffer中取出从底层设备提交上来的事件赋给event,然后调用input_event_to_user函数将这个event传递给从用户层传下来的buffer,完成从事件驱动层到用户空间的数据传递。

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164589

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 三个重要结构体
    • struct input_dev
      • struct input_handler
        • struct input_handle
        • 源码分析
          • 事件驱动层
            • 设备驱动层
              • 事件传递流程
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档