[关闭]
@zifehng 2017-06-05T08:47:02.000000Z 字数 4567 阅读 1525

input子系统浅析

kernel input


kernel version: linux-4.9.13


1. 概述

input子系统分为三层:

设备驱动层包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报;
核心层根据输入设备种类,分发事件至不同的事件处理器;
事件处理层包含:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。


input_list

2. 数据结构

2.1 输入设备 —— input_dev

每个输入设备驱动中都实例化了一个input_dev对象,用于记录设备硬件相关信息、事件位图设置(支持的事件类型)以及其他参数。

  1. struct input_dev {
  2. const char *name; // 设备名称
  3. /*
  4. struct input_id {
  5. __u16 bustype; // 总线
  6. __u16 vendor; // 厂商
  7. __u16 product; // 产品
  8. __u16 version; // 版本
  9. };
  10. */
  11. struct input_id id; // 设备id,用于与input_handler匹配
  12. ......
  13. unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 事件位图
  14. unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 按键事件位图
  15. unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 相对位移事件位图
  16. unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 绝对位移事件位图
  17. ......
  18. struct device dev;
  19. struct list_head h_list; // 内核链表头
  20. struct list_head node; // 内核链表节点
  21. ......
  22. };

2.2 事件处理器 —— input_handler

同样,每个事件处理器也都实例化了input_handler对象,如evdev_handler、mousedev_handler、joydev_handler等等,input_handler类中提供了事件处理、与输入设备匹配连接相关的接口。

  1. struct input_handler {
  2. void *private;
  3. /* 事件处理接口 */
  4. void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
  5. void (*events)(struct input_handle *handle,
  6. const struct input_value *vals, unsigned int count);
  7. bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
  8. /* 输入设备与事件处理器匹配接口 */
  9. bool (*match)(struct input_handler *handler, struct input_dev *dev);
  10. /* 输入设备与事件处理器建立连接接口,匹配成功后调用 */
  11. int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
  12. void (*disconnect)(struct input_handle *handle);
  13. void (*start)(struct input_handle *handle);
  14. bool legacy_minors;
  15. int minor;
  16. const char *name;
  17. /* 输入设备支持列表,用于与input_dev匹配 */
  18. const struct input_device_id *id_table;
  19. struct list_head h_list; // 内核链表头
  20. struct list_head node; // 内核链表节点
  21. };

3. 输入设备与事件处理器的匹配连接

input子系统维护了两条重要链表:

两条链表在核心层input.c中静态声明并初始化:

  1. static LIST_HEAD(input_dev_list);
  2. static LIST_HEAD(input_handler_list);


input_list

当一个输入设备注册时,它会被加入到输入设备链表(input_dev_list),同时遍历事件处理器链表(input_handler_list)进行匹配:

  1. int input_register_device(struct input_dev *dev)
  2. {
  3. ......
  4. // 将输入设备加入内核链表
  5. list_add_tail(&dev->node, &input_dev_list);
  6. // 遍历事件处理器链接匹配
  7. list_for_each_entry(handler, &input_handler_list, node)
  8. input_attach_handler(dev, handler);
  9. ......
  10. }

匹配过程以事件处理器的id_table为判断条件[1],如果输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,以通用事件处理器(evdev)为例,最终调用evdev_connect():

  1. static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
  2. const struct input_device_id *id)
  3. {
  4. ......
  5. int minor;
  6. int dev_no;
  7. // 获取次设备号
  8. minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
  9. if (minor < 0) {
  10. error = minor;
  11. pr_err("failed to reserve new minor: %d\n", error);
  12. return error;
  13. }
  14. // 设置设备节点名字,根据注册顺序依次为event0、event1......
  15. dev_no = minor;
  16. /* Normalize device number if it falls into legacy range */
  17. if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
  18. dev_no -= EVDEV_MINOR_BASE;
  19. dev_set_name(&evdev->dev, "event%d", dev_no);
  20. // 以主、次设备号合成设备号
  21. evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
  22. // 关联input设备类
  23. evdev->dev.class = &input_class;
  24. // 继承input_dev父类
  25. evdev->dev.parent = &dev->dev;
  26. evdev->dev.release = evdev_free;
  27. device_initialize(&evdev->dev);
  28. // 字符设备初始化及注册
  29. cdev_init(&evdev->cdev, &evdev_fops);
  30. evdev->cdev.kobj.parent = &evdev->dev.kobj;
  31. error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
  32. if (error)
  33. goto err_unregister_handle;
  34. error = device_add(&evdev->dev);
  35. if (error)
  36. goto err_cleanup_evdev;
  37. return 0;
  38. }

在上述代码中可以看到,输入设备调用input_register_device()函数注册时,如果与通用事件处理器(evdev)成功配对,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。

4. 输入事件的上报流程

输入事件的传递以input_event为基本单位:

  1. struct input_event {
  2. struct timeval time; // 时间戳
  3. __u16 type; // 事件总类型
  4. __u16 code; // 事件子类型
  5. __s32 value; // 事件值
  6. };


input_event

在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;事件处理器给输入事件加上时间戳,最后传递至上层用户空间。

整个上报流程的关键点在于事件处理器与用户空间的交互,以通用事件处理器(evdev)为例,其与用户程序组成了一对多的C/S[2]通信模型:


evdev_client

通用事件处理器(evdev)代表服务器,每一个打开设备节点(/dev/input/eventN)的用户程序则代表一个连接的客户端。

在evdev.c中有着对应描述的结构体:

  1. // 服务器
  2. struct evdev {
  3. int open;
  4. struct input_handle handle;
  5. wait_queue_head_t wait;
  6. struct evdev_client __rcu *grab;
  7. struct list_head client_list; // 客户端连接列表
  8. spinlock_t client_lock;
  9. struct mutex mutex;
  10. struct device dev;
  11. struct cdev cdev;
  12. bool exist;
  13. };
  14. // 客户端
  15. struct evdev_client {
  16. unsigned int head;
  17. unsigned int tail;
  18. unsigned int packet_head;
  19. spinlock_t buffer_lock;
  20. struct wake_lock wake_lock;
  21. bool use_wake_lock;
  22. char name[28];
  23. struct fasync_struct *fasync;
  24. struct evdev *evdev;
  25. struct list_head node;
  26. int clkid;
  27. unsigned int bufsize;
  28. struct input_event buffer[]; // 输入事件环形缓冲区
  29. };

设备驱动上报事件并不是直接传递给用户程序,在通用事件处理器(evdev)中,事件被缓存在缓冲区中,当缓冲区不为空时,用户程序主动读取缓冲区获取事件。


producer-consumer

这个缓冲区就是在evdev_client中定义的环形缓冲区,也就是说每个打开设备节点的用户程序,在建立客户端连接的同时,也构建了自己的缓冲区用以获取事件,不同的客户端之间互不干涉。

关于缓冲区的详细分析请参考:input子系统事件处理层(evdev)的环形缓冲区


[1] 通用事件处理器(evdev)的id_table设置为匹配所有输入设备
[2] Client/Server,客户端与服务器架构
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注