input子系统分析一(input设备注册)

1 input设备初始化

我们以一个遥控器的设备为例,分析input设备的注册流程:

1.1 申请input设备

remote_dev = input_allocate_device();

1.2 初始化设备结构体

  remote_dev->name = XXX_NAME;
  remote_dev->phys = XXX_PHYS;
  remote_dev->id.vendor = XXX_VENDOR_ID;
  remote_dev->id.product = XXX_PRODUCT_ID;
  remote_dev->id.version = XXX_CTL_VERSION;

1.3 配置设备支持的事件类型

  set_bit(EV_KEY, remote_dev->evbit);
  set_bit(EV_REP, remote_dev->evbit);

1.4 配置设备支持的按键值

  remote_dev->keycode = xxx_key_tab;
  remote_dev->keycodesize = sizeof(int);
  remote_dev->keycodemax = ARRAY_SIZE(xxx_key_tab);

1.5 注册input设备

input_register_device(remote_dev);

2 input设备的注册流程

2.1 input_register_device函数

根据代码中注释来分析注册流程:

 int input_register_device(struct input_dev *dev)
  {
      struct input_devres *devres = NULL;
      struct input_handler *handler;
      unsigned int packet_size;
      const char *path;
      int error;
  ​
      if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) { // 绝对设备,却没有坐标信息,拒绝注册
          return -EINVAL;
      }
      __set_bit(EV_SYN, dev->evbit);  // 每个输入设备都会生成EV_SYN/SYN_REPORT事件 
      error = device_add(&dev->dev);
      if (error)
          goto err_free_vals;
  ​
      path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
      pr_info("%s as %s\n",
          dev->name ? dev->name : "Unspecified device",
          path ? path : "N/A");
      kfree(path);
  ​
      error = mutex_lock_interruptible(&input_mutex);
      if (error)
          goto err_device_del;
  ​
      list_add_tail(&dev->node, &input_dev_list);  // 将设备插入input_dev_list链尾
      list_for_each_entry(handler, &input_handler_list, node) // 遍历所有input事件驱动,匹配设备
          input_attach_handler(dev, handler);  // 下面细说
      input_wakeup_procfs_readers();  // poll事件,这里不细说
      mutex_unlock(&input_mutex);
  }

2.2 input设备匹配input handler的过程

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
  {
      const struct input_device_id *id;
      int error;
  ​
      id = input_match_device(handler, dev);
      if (!id)
          return -ENODEV;
  ​
      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;
  }

好,划重点,我们来好好分析下输入子系统的匹配过程!

  1. 事实上,当每一个 dev 和 handler 注册到内核进来,都会调用到 input_attach_handler 函数进行对比匹配。

    当Input dev注册时,遍历所有handler,调用input_attach_handler匹配。

    当handler注册时,遍历所有Input dev,调用input_attach_handler匹配。

  2. 在遍历的过程中,即使有一个匹配成功了,也会继续匹配下去。

    所以Input dev和handler是多对多的关系。

    举例:学习时用的按键输入子系统驱动实验,会发现按键上报的数据可以从tty1 中 cat 到,也可以从 eventx 中 cat 到。

  3. 真正进行匹配的是handler->id_table 和 dev。

下面我们分析下内核中常见的几种handler和我们的remote_dev匹配的过程:

2.2.1 remote_dev与evdev handler匹配

evdev.c -> handler -> id_table

static const struct input_device_id evbug_ids[] = {
      { .driver_info = 1 },   /* Matches all devices */
      { },                   /* Terminating zero entry */
  };

remote_dev->evbit

set_bit(EV_KEY, remote_dev->evbit); // 按键事件
set_bit(EV_REP, remote_dev->evbit); // 重复事件

匹配函数input_match_device_id

bool input_match_device_id(const struct input_dev *dev,
                 const struct input_device_id *id)
  {
      if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
          if (id->bustype != dev->id.bustype)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
          if (id->vendor != dev->id.vendor)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
          if (id->product != dev->id.product)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
          if (id->version != dev->id.version)
              return false;
      /* __bitmap_subset(A, B): 判断A位图是否是B位图的子集*/
      if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
          !bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
          !bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
          !bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
          !bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
          !bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
          !bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
          !bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
          !bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
          !bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
          return false;
      }
      return true;
  }

evdev.c -> handler -> id_table除了driver_info都为空,所以以上if判断中都进不去,程序直接跳到28行返回真。

这也说明了"evdev事件驱动可以适用于任何的 input_dev."

2.2.2 remote_dev与keyboard handler匹配

我们继续来看keyboard handler

keyboard -> handler -> id_table

bool input_match_device_id(const struct input_dev *dev,
                 const struct input_device_id *id)
  {
      if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
          if (id->bustype != dev->id.bustype)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
          if (id->vendor != dev->id.vendor)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
          if (id->product != dev->id.product)
              return false;
      if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
          if (id->version != dev->id.version)
              return false;
      /* __bitmap_subset(A, B): 判断A位图是否是B位图的子集*/
      if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
          !bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
          !bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
          !bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
          !bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
          !bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
          !bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
          !bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
          !bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
          !bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
          return false;
      }
      return true;
  }

remote_dev->evbit

  set_bit(EV_KEY, remote_dev->evbit); // 按键事件
  set_bit(EV_REP, remote_dev->evbit); // 重复事件

keyboard handler的flag=INPUT_DEVICE_ID_MATCH_EVBIT,所以input_match_device_id中前4条if条件也不成立。然后evbit位图中置位了EV_KEY和EV_SND. 而remote_dev的evbit位图置位了EV_KEY和EV_REP.

显然,keyboard handler->evbit是remote_dev->evbit的子集,说明keyboard驱动支持我们的remote_dev

2.2.3 remote_dev与mousedev_handler handler匹配

mousedev -> handler -> id_table

static const struct input_device_id mousedev_ids[] = {
      {
          .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                  INPUT_DEVICE_ID_MATCH_KEYBIT |
                  INPUT_DEVICE_ID_MATCH_RELBIT,
          .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
          .keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
          .relbit = { BIT_MASK(REL_X) | BIT_MASK(REL_Y) },
      },  /* A mouse like device, at least one button,
             two relative axes */
      {
          .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                  INPUT_DEVICE_ID_MATCH_RELBIT,
          .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
          .relbit = { BIT_MASK(REL_WHEEL) },
      },  /* A separate scrollwheel */
      {
          .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                  INPUT_DEVICE_ID_MATCH_KEYBIT |
                  INPUT_DEVICE_ID_MATCH_ABSBIT,
          .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
          .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
          .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
      },  /* A tablet like device, at least touch detection,
             two absolute axes */
      {
          .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                  INPUT_DEVICE_ID_MATCH_KEYBIT |
                  INPUT_DEVICE_ID_MATCH_ABSBIT,
          .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
          .keybit = { [BIT_WORD(BTN_TOOL_FINGER)] =
                  BIT_MASK(BTN_TOOL_FINGER) },
          .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
                  BIT_MASK(ABS_PRESSURE) |
                  BIT_MASK(ABS_TOOL_WIDTH) },
      },  /* A touchpad */
      {
          .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
              INPUT_DEVICE_ID_MATCH_KEYBIT |
              INPUT_DEVICE_ID_MATCH_ABSBIT,
          .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
          .keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
          .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
      },  /* Mouse-like device with absolute X and Y but ordinary
             clicks, like hp ILO2 High Performance mouse */
  ​
      { },    /* Terminating entry */
  };

支持以上事件的就会和 mousedev事件驱动匹配,这里不再细说。

2.3 Input设备与handler建立连接

dev和handler匹配成功后,会调用handler->connect(handler, dev, id);

2.3.1 evdev事件驱动

如果是evdev事件驱动,会调用evdev_connect接口:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
               const struct input_device_id *id)
  {
      struct evdev *evdev;
      int minor;
      int dev_no;
      int error;
  ​
      minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
      if (minor < 0) {
          error = minor;
          pr_err("failed to reserve new minor: %d\n", error);
          return error;
      }
  ​
      evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
      if (!evdev) {
          error = -ENOMEM;
          goto err_free_minor;
      }
  ​
      INIT_LIST_HEAD(&evdev->client_list);
      spin_lock_init(&evdev->client_lock);
      mutex_init(&evdev->mutex);
      evdev->exist = true;
  ​
      dev_no = minor;
      /* Normalize device number if it falls into legacy range */
      if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
          dev_no -= EVDEV_MINOR_BASE;
      dev_set_name(&evdev->dev, "event%d", dev_no);
  ​
      evdev->handle.dev = input_get_device(dev);
      evdev->handle.name = dev_name(&evdev->dev);
      evdev->handle.handler = handler;
      evdev->handle.private = evdev;
  ​
      evdev->dev.devt = MKDEV(INPUT_MAJOR, 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);
      if (error)
          goto err_free_evdev;
  ​
      cdev_init(&evdev->cdev, &evdev_fops);
  ​
      error = cdev_device_add(&evdev->cdev, &evdev->dev);
      if (error)
          goto err_cleanup_evdev;
  ​
      return 0;
  ​
   err_cleanup_evdev:
      evdev_cleanup(evdev);
      input_unregister_handle(&evdev->handle);
   err_free_evdev:
      put_device(&evdev->dev);
   err_free_minor:
      input_free_minor(minor);
      return error;
  }

我们来重点分析下evdev_connect做了哪些事情:

step1:创建一个evdev设备,分配一个次设备号,该设备号决定了/dev/input/eventX中的X的值。

step2:初始化evdev->handle,这个handle不是evdev->handler,它们两有完全不同的用途。

evdev->handle用来绑定匹配成功的Input_dev和evdev->handler

step3:将evdev->handle注册进内核. 见44行。

事实上就是将evdev->handle挂在了input_dev_list和input_handler_list的h_list上。从此,建立好了三者 的铁三角关系,通过input_handler_list和input_dev_list以及input_hande中任何一方,都可以找到彼此。

借用大神的一张图,直观的感受下这个三角关系:

step4:将step1创建的evdev设备注册到内核,见48-50行。app访问我们的input_dev设备就是通过这个新建的evdev设备节点来访问的。

3 总结

        回到我们的remote_dev,至此remote_dev的注册,匹配,连接过程就完成了。设备/dev/input/eventX被创建,app在访问remote_dev时,我们最终就能够通过基于evdev->fops的open、read、write等方式在应用层获取数据.

  1. evdev事件驱动可以支持任何类型的dev,并且会在/dev/input/eventX下创建eventX设备节点。

  2. 如果dev支持按键类事件,会匹配keyboard事件驱动,会将数据上送到tty层。

  3. 如果 dev 支持鼠标的一些事件,会匹配 mousedev的事件驱动,具体还会要分析id_table,并且会创建杂项设备节点。

4 遗留问题

        至此,我们已经分析完了遥控器设备的注册流程,回过头想想,其实还有很多问题没有解决,比如:

  1. 遥控器数据是如何上报的?app怎样高效得到遥控器上报的数据?app怎么辨别用户按下的是什么键?
  2. app open一个input节点,最终怎么操作到我们remote设备?read和write又是如何访问设备的?
  3. evdev匹配这很多input_dev,它是如何区分这些设备的?如何管理这个设备的数据?

至于app如何访问input_dev的这些细节,这个也比较复杂,下回剖析~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
开发输入系统设备驱动时,通常遵循以下步骤: 1. 包含必要的头文件:在驱动程序的源文件中,你需要包含一些必要的头文件,例如`linux/input.h`、`linux/module.h`、`linux/init.h`等。 2. 定义驱动模块:使用`module_init`宏定义一个初始化函数,用来加载驱动程序。例如: ```c static int __init myinput_init(void) { // 驱动初始化逻辑 return 0; } module_init(myinput_init); ``` 3. 注册输入设备:在初始化函数中,你需要创建一个输入设备并进行注册。你可以使用`input_allocate_device`函数分配一个输入设备结构体,并设置一些属性,例如设备名称、支持的事件类型等。然后,使用`input_register_device`函数注册输入设备。例如: ```c static int __init myinput_init(void) { struct input_dev *myinput_dev; myinput_dev = input_allocate_device(); if (!myinput_dev) { pr_err("Failed to allocate input device\n"); return -ENOMEM; } // 设置设备名称、支持的事件类型等 input_register_device(myinput_dev); return 0; } ``` 4. 处理输入事件:注册完输入设备后,你需要实现一个中断处理函数或者定时器处理函数,用来处理输入事件。当触发输入事件时,驱动程序会调用该函数进行处理。你可以使用`input_report_*`系列函数上报输入事件,例如鼠标移动、按键按下等。例如: ```c static irqreturn_t myinput_interrupt(int irq, void *dev_id) { // 处理输入事件的逻辑 input_report_key(myinput_dev, KEY_A, 1); // 模拟按下 A 键 input_sync(myinput_dev); // 同步输入事件 return IRQ_HANDLED; } ``` 这只是一个简单的示例,实际的输入系统设备驱动可能还需要处理更多的细节和特定的硬件接口。更详细的编写方法和实现细节可以参考Linux内核源码中的驱动示例和相关文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值