@zifehng
2017-06-05T08:47:02.000000Z
字数 4567
阅读 1907
kernel input
kernel version: linux-4.9.13
input子系统分为三层:
设备驱动层包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报;
核心层根据输入设备种类,分发事件至不同的事件处理器;
事件处理层包含:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。
每个输入设备驱动中都实例化了一个input_dev对象,用于记录设备硬件相关信息、事件位图设置(支持的事件类型)以及其他参数。
struct input_dev {const char *name; // 设备名称/*struct input_id {__u16 bustype; // 总线__u16 vendor; // 厂商__u16 product; // 产品__u16 version; // 版本};*/struct input_id id; // 设备id,用于与input_handler匹配......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)]; // 绝对位移事件位图......struct device dev;struct list_head h_list; // 内核链表头struct list_head node; // 内核链表节点......};
同样,每个事件处理器也都实例化了input_handler对象,如evdev_handler、mousedev_handler、joydev_handler等等,input_handler类中提供了事件处理、与输入设备匹配连接相关的接口。
struct input_handler {void *private;/* 事件处理接口 */void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);void (*events)(struct input_handle *handle,const struct input_value *vals, unsigned int count);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);bool legacy_minors;int minor;const char *name;/* 输入设备支持列表,用于与input_dev匹配 */const struct input_device_id *id_table;struct list_head h_list; // 内核链表头struct list_head node; // 内核链表节点};
input子系统维护了两条重要链表:
两条链表在核心层input.c中静态声明并初始化:
static LIST_HEAD(input_dev_list);static LIST_HEAD(input_handler_list);
当一个输入设备注册时,它会被加入到输入设备链表(input_dev_list),同时遍历事件处理器链表(input_handler_list)进行匹配:
int input_register_device(struct input_dev *dev){......// 将输入设备加入内核链表list_add_tail(&dev->node, &input_dev_list);// 遍历事件处理器链接匹配list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);......}
匹配过程以事件处理器的id_table为判断条件[1],如果输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,以通用事件处理器(evdev)为例,最终调用evdev_connect():
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id){......int minor;int dev_no;// 获取次设备号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;}// 设置设备节点名字,根据注册顺序依次为event0、event1......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->dev.devt = MKDEV(INPUT_MAJOR, minor);// 关联input设备类evdev->dev.class = &input_class;// 继承input_dev父类evdev->dev.parent = &dev->dev;evdev->dev.release = evdev_free;device_initialize(&evdev->dev);// 字符设备初始化及注册cdev_init(&evdev->cdev, &evdev_fops);evdev->cdev.kobj.parent = &evdev->dev.kobj;error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);if (error)goto err_unregister_handle;error = device_add(&evdev->dev);if (error)goto err_cleanup_evdev;return 0;}
在上述代码中可以看到,输入设备调用input_register_device()函数注册时,如果与通用事件处理器(evdev)成功配对,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。
输入事件的传递以input_event为基本单位:
struct input_event {struct timeval time; // 时间戳__u16 type; // 事件总类型__u16 code; // 事件子类型__s32 value; // 事件值};
在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;事件处理器给输入事件加上时间戳,最后传递至上层用户空间。
整个上报流程的关键点在于事件处理器与用户空间的交互,以通用事件处理器(evdev)为例,其与用户程序组成了一对多的C/S[2]通信模型:
通用事件处理器(evdev)代表服务器,每一个打开设备节点(/dev/input/eventN)的用户程序则代表一个连接的客户端。
在evdev.c中有着对应描述的结构体:
// 服务器struct evdev {int open;struct input_handle handle;wait_queue_head_t wait;struct evdev_client __rcu *grab;struct list_head client_list; // 客户端连接列表spinlock_t client_lock;struct mutex mutex;struct device dev;struct cdev cdev;bool exist;};// 客户端struct evdev_client {unsigned int head;unsigned int tail;unsigned int packet_head;spinlock_t buffer_lock;struct wake_lock wake_lock;bool use_wake_lock;char name[28];struct fasync_struct *fasync;struct evdev *evdev;struct list_head node;int clkid;unsigned int bufsize;struct input_event buffer[]; // 输入事件环形缓冲区};
设备驱动上报事件并不是直接传递给用户程序,在通用事件处理器(evdev)中,事件被缓存在缓冲区中,当缓冲区不为空时,用户程序主动读取缓冲区获取事件。
这个缓冲区就是在evdev_client中定义的环形缓冲区,也就是说每个打开设备节点的用户程序,在建立客户端连接的同时,也构建了自己的缓冲区用以获取事件,不同的客户端之间互不干涉。
关于缓冲区的详细分析请参考:input子系统事件处理层(evdev)的环形缓冲区