电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
Linux 驱动|input 子系统详解
分 享
扫描二维码分享
Linux 驱动|input 子系统详解
linux
驱动
嵌入式
一口Linux
关注
发布时间: 2021-09-03
丨
阅读: 1743
# 1. 模块概述 ## 1.1.相关资料和代码研究 ```c drivers/input/ include/uapi/linux/input-event-codes.h ``` # 2. 模块功能 linux核心的输入框架 # 3. 模块学习 ## 3.1.概述 Linux输入设备种类繁杂,常见的包括触摸屏、键盘、鼠标、摇杆等;这些输入设备属于字符设备,而linux将这些设备的共同特性抽象出来,Linux input 子系统就产生了。 ## 3.2.软件架构 输入子系统是由设备驱动层(input driver)、输入核心层(input core)、输入事件处理层(input event handle)组成,具体架构如图4.1所示: ![图4.1 Linux input 子系统架构 ](https://img-blog.csdnimg.cn/68f71983d3534275ade0ce74e94c0233.png) * (1)input设备驱动层:负责具体的硬件设备,将底层的硬件输入转化为统一的事件形式,向input核心层和汇报; *(2)input核心层:连接input设备驱动层与input事件处理层,向下提供驱动层的接口,向上提供事件处理层的接口; *(3)input事件处理层:为不同硬件类型提供了用户访问以及处理接口,将硬件驱动层传来的事件报告给用户程序。 在input子系统中,每个事件的发生都使用事件(type)->子事件(code)->值(value) 所有的输入设备的主设备号都是13,input-core通过次设备来将输入设备进行分类,如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。 Linux输入子系统支持的数据类型 |时间类型 |编码 |含义| |--|--|--| | EV_SYN | 0x00 | 同步事件| | EV_KEY | 0x01 | 按键事件(鼠标,键盘等)| | EV_REL | 0x02 | 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)| | EV_ABS | 0x03 | 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)| | EV_MSC | 0x04 | 其它| | EV_SW | 0x05 | 开关| | EV_LED | 0x11 | 按键/设备灯| | EV_SND | 0x12 | 声音/警报| | EV_REP | 0x14 | 重复| | EV_FF | 0x15 | 力反馈| | EV_PWR | 0x16 | 电源| | EV_FF_STATUS | 0x17 | 力反馈状态| | EV_MAX | 0x1f | 事件类型最大个数和提供位掩码支持| 定义的按键值 ```c #define KEY_RESERVED 0 #define KEY_ESC 1 #define KEY_1 2 #define KEY_2 3 #define KEY_3 4 #define KEY_4 5 #define KEY_5 6 #define KEY_6 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_0 11 ... ``` ## 3.3.数据结构 三个数据结构input_dev,input_handle,input_handler之间的关系如图4.2、4.3所示 ![图4.2 三个数据结构之间的关系](https://img-blog.csdnimg.cn/fca5176570ad4134b062b76811aef872.png)![图4.3 Input_handler_list、Input_dev_list内部结构 ](https://img-blog.csdnimg.cn/2a6b53b857ee4ca9b5c66a96f6448c64.png) ```bash input_dev:是硬件驱动层,代表一个input设备。 input_handler:是事件处理层,代表一个事件处理器。 input_handle:属于核心层,代表一个配对的input设备与input事件处理器。 input_dev 通过全局的input_dev_list链接在一起,设备注册的时候完成这个操作。 ``` input_handler 通过全局的input_handler_list链接在一起。事件处理器注册的时候实现了这个操作(事件处理器一般内核自带,不需要我们来写) input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。通过input_dev 和input_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_dev和input_handler。 我们可以看到,input_device和input_handler中都有一个h_list,而input_handle拥有指向input_dev和input_handler的指针,也就是说input_handle是用来关联input_dev和input_handler的。 **那么为什么一个input_device和input_handler中拥有的是h_list而不是一个handle呢?** 因为一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。 至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个 ### 3.3.1. Input_dev 输入设备 ```c /@@* include/linux/input.h */ struct input_dev { const char *name; /@@* 设备名称 */ const char *phys; /@@* 设备在系统中的路径 */ const char *uniq; /@@* 设备唯一id */ struct input_id id; /@@* input设备id号 */ unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /@@* 设备支持的事件类型,主要有EV_SYNC,EV_KEY,EV_KEY,EV_REL,EV_ABS等*/ 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, const struct input_keymap_entry *ke, unsigned int *old_keycode); int (*getkeycode)(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 *mt; /@@* 多点触控区域 */ struct input_absinfo *absinfo; /@@* 存放绝对值坐标的相关参数数组 */ unsigned long key[BITS_TO_LONGS(KEY_CNT)]; /@@* 反应设备当前的案件状态 */ unsigned long led[BITS_TO_LONGS(LED_CNT)]; /@@* 反应设备当前的led状态 */ unsigned long snd[BITS_TO_LONGS(SND_CNT)]; /@@* 反应设备当前的声音状态 */ unsigned long sw[BITS_TO_LONGS(SW_CNT)]; /@@* 反应设备当前的开关状态 */ int (*open)(struct input_dev *dev); /@@* 第一次打开设备时调用,初始化设备用 */ void (*close)(struct input_dev *dev); /@@* 最后一个应用程序释放设备事件,关闭设备 */ int (*flush)(struct input_dev *dev, struct file *file); /@@* 用于处理传递设备的事件 */ int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); /@@* 事件处理函数,主要是接收用户下发的命令,如点亮led */ struct input_handle __rcu *grab; /@@* 当前占有设备的input_handle */ spinlock_t event_lock; /@@* 事件锁 */ struct mutex mutex; /@@* 互斥体 */ unsigned int users; /@@* 打开该设备的用户数量(input_handle) */ bool going_away; /@@* 标记正在销毁的设备 */ struct device dev; /@@* 一般设备 */ struct list_head h_list; /@@* 设备所支持的input handle */ struct list_head node; /@@* 用于将此input_dev连接到input_dev_list */ unsigned int num_vals; /@@* 当前帧中排队的值数 */ unsigned int max_vals; /@@* 队列最大的帧数*/ struct input_value *vals; /@@* 当前帧中排队的数组*/ bool devres_managed; /@@* 表示设备被devres 框架管理,不需要明确取消和释放*/ }; ``` ### 3.3.2. Input_handler 处理具体的输入事件的具体函数 ```c /@@* include/linux/input.h */ struct input_handler { void *private; /@@* 存放handle数据 */ 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; /@@* 名字 */ const struct input_device_id *id_table; /@@* input_dev匹配用的id */ struct list_head h_list; /@@* 用于链接和handler相关的handle,input_dev与input_handler配对之后就会生成一个input_handle结构 */ struct list_head node; /@@* 用于将该handler链入input_handler_list,链接所有注册到内核的所有注册到内核的事件处理器 */ }; ``` ### 3.3.3. Input_handle 连接输入设备和处理函数 ```c /@@* include/linux/input.h */ struct input_handle { void *private; /@@* 数据指针 */ int open; /@@* 打开标志,每个input_handle 打开后才能操作 */ const char *name; /@@* 设备名称 */ struct input_dev *dev; /@@* 指向所属的input_dev */ struct input_handler *handler; /@@* 指向所属的input_handler */ struct list_head d_node; /@@* 用于链入所指向的input_dev的handle链表 */ struct list_head h_node; /@@* 用于链入所指向的input_handler的handle链表 */ }; ``` ### 3.3.4. Evdev 字符设备事件 ```c /@@* drivers/input/evdev.c */ struct evdev { int open; /@@* 设备被打开的计数 */ struct input_handle handle; /@@* 关联的input_handle */ wait_queue_head_t wait; /@@* 等待队列,当前进程读取设备,没有事件产生时, 进程就会sleep */ struct evdev_client __rcu *grab; /@@* event响应 */ struct list_head client_list; /@@* evdev_client链表,说明evdev设备可以处理多个 evdev _client,可以有多个进程访问evdev设备 */ spinlock_t client_lock; struct mutex mutex; struct device dev; struct cdev cdev; bool exist; /@@* 设备存在判断 */ }; ``` ### 3.3.5. evdev_client 字符设备事件响应 ```c /@@* drivers/input/evdev.c */ struct evdev_client { unsigned int head; /@@* 动态索引,每加入一个event到buffer中,head++ */ unsigned int tail; /@@* 动态索引,每取出一个buffer中到event,tail++ */ unsigned int packet_head; /@@* 数据包头部 */ spinlock_t buffer_lock; struct fasync_struct *fasync; /@@* 异步通知函数 */ struct evdev *evdev; struct list_head node; /@@* evdev_client链表项 */ int clkid; unsigned int bufsize; struct input_event buffer[]; /@@* 用来存放input_dev事件缓冲区 */ }; ``` ### 3.3.6. Evdev_handler evdev_handler事件处理函数 ```c /@@* drivers/input/input.c */ static struct input_handler evdev_handler = { .event = evdev_event, /@@* 事件处理函数, */ .events = evdev_events, /@@* 事件处理函数, */ .connect = evdev_connect, /@@* 连接函数,将事件处理和输入设备联系起来 */ .disconnect = evdev_disconnect, /@@* 断开该链接 */ .legacy_minors = true, .minor = EVDEV_MINOR_BASE, .name = "evdev", /@@* handler名称 */ .id_table = evdev_ids, /@@* 断开该链接 */ }; ``` ### 3.3.7. input_event 标准按键编码信息 ```c /@@* drivers/input/evdev.c */ struct input_event { struct timeval time; /@@* 事件发生的时间 */ __u16 type; /@@* 事件类型 */ __u16 code; /@@* 事件码 */ __s32 value; /@@* 事件值 */ }; ``` ### 3.3.8. input_id 和input输入设备相关的id信息 ```c /@@* include/uapi/linux/input.h */ struct input_id { __u16 bustype; /@@* 总线类型 */ __u16 vendor; /@@* 生产厂商 */ __u16 product; /@@* 产品类型 */ __u16 version; /@@* 版本 */ }; ``` ### 3.3.9. input_device_id ```c /@@* include/uapi/linux/input.h */ struct input_device_id { kernel_ulong_t flags; __u16 bustype; /@@* 总线类型 */ __u16 vendor; /@@* 生产厂商 */ __u16 product; /@@* 产品类型 */ __u16 version; /@@* 版本 */ kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1]; kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1]; kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1]; kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1]; kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1]; kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1]; kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1]; kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1]; kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1]; kernel_ulong_t propbit[INPUT_DEVICE_ID_PROP_MAX / BITS_PER_LONG + 1]; kernel_ulong_t driver_info; }; ``` ### 3.3.10. input_even 输入事件的传递已input_event为基本单位 ```c struct input_event { struct timeval time; //时间戳 __u16 type; //事件总类型 __u16 code; //事件子类型 __s32 value; //事件值 }; ``` ## 3.4. Linux input 子系统关键流程 核心层,执行的时候会注册设备号,然后在handler层注册input_handler,也就是evdev_handler会注册到核心层维护的链表中。 然后进行硬件初始化获取数据,而且需要将设备注册到链表中。注册进来就就会遍历input_handler_list链表,找到对应的handler,匹配成功后会调用connect方法。connect分配evdev,evdev就记录了input_handler和input_device之间的关系,同时创建设备节点,还会注册cdev从而可以让应用调用。 当应用程序调用open,read等接口的时候就会调用input_handler层实现的xxx_open,那么open就会分配好evdev_client,最终在input_dev层上报数据的时候会自动调用input_handler,input_handler就会调用events填充上报的数据到缓冲区client,此时如果没有唤醒队列的话应用read的时候会阻塞,而唤醒队列后最终使用copy_to_user来给应用数据。 设备驱动程序上报事件的函数有: ```c input_report_key //上报按键事件 input_report_rel //上报相对坐标事件 input_report_abs //上报绝对坐标事件 input_report_ff_status input_report_switch input_sync //上报完成后需要调用这些函数来通知系统处理完整事件 input_mt_sync //上报完成后需要调用这些函数来通知系统处理完整事件 ``` 这些函数其实是input_event函数的封装,调用的都是input_event函数,在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;调用关系为:input_event->input_handle_event->input_pass_values,这一函数都在input.c实现。 ### 3.4.1. Input 设备注册流程 输入设备注册过程如图4.3所示 ![图4.3 输入设备注册流程图 ](https://img-blog.csdnimg.cn/c04daaa51f9a463cacaaaf05c7e02477.png) ### 3.4.2. 连接设备流程 连接设备流程如图4.4所示 ![图4.4 连接设备流程图](https://img-blog.csdnimg.cn/96c8e450dbf244b199f671f728542a2d.png) ### 3.4.3. 事件上报流程 事件上报流程如图4.5所示 ![图4.5 事件上报流程图 ](https://img-blog.csdnimg.cn/abc26ee5897d4aedbef13e7b4a926932.png) ### 3.4.4. 数据读取流程 数据读取流程如图4.6所示 ![图4.6数据读取流程图](https://img-blog.csdnimg.cn/a2a14397259142bdabbfd92143a8d8d1.png) ## 3.5.关键函数解析 ### 3.5.1. input_init input子系统使用subsys_initcall宏修饰input_init()函数在内核启动阶段被调用。input_init()函数在内核启动阶段被调用。input_init()函数的主要工作是:在sys文件系统下创建一个设备类(/sys/class/input),调用register_chrdev()函数注册input设备。 ```c /@@* drivers/input/input.c */ static int __init input_init(void) { int err; err = class_register(&input_class); /@@* 注册类,放在sys/class下 */ if (err) { pr_err("unable to register input_dev class\n"); return err; } err = input_proc_init(); /@@* 在proc目录下建立相关目录 */ if (err) goto fail1; err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input"); /@@* 注册字符设备编号,INPUT_MAJOR 永远是13 */ if (err) { pr_err("unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; } ``` ### 3.5.2. Input_register_device ```c /@@* drivers/input/input.c */ int input_register_device(struct input_dev *dev) { struct atomic_t input_no = ATOMIC_INIT(0); struct input_devres *devres = NULL; struct input_handler *handler; unsigned int packet_size; const char *path; int error; if (dev->devres_managed) { devres = devres_alloc(devm_input_device_unregister, sizeof(*devres), GFP_KERNEL); if (!devres) return -ENOMEM; devres->input = dev; } /@@* 每个input_device都会产生EV_SYN/SYN_REPORT时间,所以就放在一起设置 */ __set_bit(EV_SYN, dev->evbit); /@@* KEY_RESERVED is not supposed to be transmitted to userspace. */ __clear_bit(KEY_RESERVED, dev->keybit); /@@* 没有设置的位,确保被清零 */ input_cleanse_bitmasks(dev); /@@* */ packet_size = input_estimate_events_per_packet(dev); if (dev->hint_events_per_packet < packet_size) dev->hint_events_per_packet = packet_size; dev->max_vals = dev->hint_events_per_packet + 2; dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL); if (!dev->vals) { error = -ENOMEM; goto err_devres_free; } /@@* 如果延时周期是程序预先设定的,那么是由驱动自动处理,主要是为了处理重复按键 */ init_timer(&dev->timer); if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { dev->timer.data = (long) dev; dev->timer.function = input_repeat_key; dev->rep[REP_DELAY] = 250; dev->rep[REP_PERIOD] = 33; } if (!dev->getkeycode) /@@* 获取按键值 */ dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode) /@@* 设置按键值 */ dev->setkeycode = input_default_setkeycode; error = device_add(&dev->dev); /@@* 将dev注册到sys */ 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); /@@* 将新的dev放入链表中 */ /@@* 遍历input_handler_list链表中的所有input_handler,是否支持这个新input_dev ; 若两者支持,便进行连接 */ list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); if (dev->devres_managed) { dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev)); devres_add(dev->dev.parent, devres); } return 0; err_device_del: device_del(&dev->dev); err_free_vals: kfree(dev->vals); dev->vals = NULL; err_devres_free: devres_free(devres); return error; } EXPORT_SYMBOL(input_register_device); ``` input_dev_list和input_handler_list是全局的一个链表 ```c static LIST_HEAD(input_dev_list); static LIST_HEAD(input_handler_list); ``` list_for_each_entry是一个宏,展开如下 获取input_handler_list的每一项和input_dev匹配,通过for循环遍历. ```c for (handler = list_first_entry(input_handler_list, &handler, node); &handler->node != (input_handler_list); &handler = list_next_entry(&handler, node)) input_attach_handler(dev, handler); ``` list_first_entry 获得第一个列表元素 list_next_entry 获得下一个列表元素 ### 3.5.3. input_register_handle 注册一个handle,链接input_handler和input_dev的h_list ```c /@@* drivers/input/input.c */ int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; int error; error = mutex_lock_interruptible(&dev->mutex); if (error) return error; if (handler->filter) list_add_rcu(&handle->d_node, &dev->h_list); else list_add_tail_rcu(&handle->d_node, &dev->h_list); /@@* 将handle的d_node,链接到其相关的input_dev的h_list链表中 */ mutex_unlock(&dev->mutex); list_add_tail_rcu(&handle->h_node, &handler->h_list); /@@* 将handle的h_node,链接到其相关的input_handler的h_list链表中 */ if (handler->start) handler->start(handle); return 0; } EXPORT_SYMBOL(input_register_handle); ``` ### 3.5.4. input_register_handler 注册一个事件,进行匹配设备和事件的绑定 ```c /@@* drivers/input/input.c */ int input_register_handler(struct input_handler *handler) { struct input_dev *dev; int error; error = mutex_lock_interruptible(&input_mutex); if (error) return error; INIT_LIST_HEAD(&handler->h_list); list_add_tail(&handler->node, &input_handler_list); /@@* 连接到input_handler_list链表中 */ /@@* 遍历input_dev_list,配对 input_dev 和 handler */ list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); /@@* event节点加入列表 */ input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0; } EXPORT_SYMBOL(input_register_handler); ``` ### 3.5.5. evdev_connect 事件处理器evdev,生成一个新的evdev设备,连接input核心,回调函数。 ```c /@@* drivers/input/evdev.c */ 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; /@@* 获取次设备号,从evdev_table中找到一个未使用的最小的数组项,最大值32 */ 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; } /@@* 初始化client_list链表头,代表多少应用读写这个设备 */ INIT_LIST_HEAD(&evdev->client_list); spin_lock_init(&evdev->client_lock); /@@* 加锁 */ mutex_init(&evdev->mutex); /@@* */ init_waitqueue_head(&evdev->wait); /@@* 初始化等待队列,当evdev没有数据可读时,就 在 该队列上睡眠 */ evdev->exist = true; /@@* 设备存在 */ dev_no = minor; if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS) dev_no -= EVDEV_MINOR_BASE; dev_set_name(&evdev->dev, "event%d", dev_no); /@@* 设置设备名为eventX */ evdev->handle.dev = input_get_device(dev); /@@* 获取设备 */ evdev->handle.name = dev_name(&evdev->dev); /@@* 设备名称 */ evdev->handle.handler = handler; /@@* handler绑定 */ evdev->handle.private = evdev; /@@* evdev数据指向 */ evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); /@@* sysfs下的设备号 */ evdev->dev.class = &input_class; /@@* 将input_class作为设备类 */ evdev->dev.parent = &dev->dev; /@@* input_dev作为evdev的父设备 */ evdev->dev.release = evdev_free; /@@* 释放函数 */ device_initialize(&evdev->dev); /@@* 初始化设备 */ /@@* 注册一个handle处理事件 */ 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); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); err_free_minor: input_free_minor(minor); return error; } ``` * (1)是在保存区驱动设备名字,比如下图(键盘驱动)event1:因为没有设置设备号,默认从小到大排序,其中event0是表示input子系统,所以键盘驱动名字就是event1。 * (2)是保存驱动设备的主次设备号,其中主设备号INPUT_MAJOR=13,次设备号=EVSEV_MINOR_BASE+驱动程序本身设备号。 * (3)会在/sys/class/input类下创建驱动设备event%d,比如键盘驱动event1 * (4)最终进入input_register_handler()函数来注册handle。 ### 3.5.6. input_attach_handler 设备匹配具体实现 ```c /@@* drivers/input/input.c */ static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id; int error; /@@* blacklist是handler该忽略input设备类型 */ if (handler->blacklist && input_match_device(handler->blacklist, dev)) return -ENODEV; id = input_match_device(handler->id_table, dev); /@@* 这个是主要的配对函数,匹配handler和device的ID */ if (!id) return -ENODEV; error = handler->connect(handler, dev, id); /@@* 配对成功调用handler的connect函数,这个函数在事件处理器中定义,主要生成一个input_handle结构,并初始化,还生成一个事件处理器相关的设备结构 */ 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; } ``` ### 3.5.7. input_match_device 比较input_dev中的id和handler支持的id,存放在handler中的id_table。 ```c /@@* drivers/input/input.c */ static const struct input_device_id *input_match_device(struct input_handler *handler, struct input_dev *dev) { const struct input_device_id *id; /@@* 遍历id_table的id匹配 */ for (id = handler->id_table; id->flags || id->driver_info; id++) { if (input_match_device_id(dev, id) && (!handler->match || handler->match(handler, dev))) { return id; } } return NULL; } ``` ### 3.5.8. input_allocate_device 初始化input_dev设备 ```c /@@* drivers/input/evdev.c */ struct input_dev *input_allocate_device(void) { static atomic_t input_no = ATOMIC_INIT(-1); struct input_dev *dev; /@@* 遍历id_table的id匹配 */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (dev) { dev->dev.type = &input_dev_type; dev->dev.class = &input_class; device_initialize(&dev->dev); mutex_init(&dev->mutex); spin_lock_init(&dev->event_lock); timer_setup(&dev->timer, NULL, 0); INIT_LIST_HEAD(&dev->h_list); INIT_LIST_HEAD(&dev->node); dev_set_name(&dev->dev, "input%lu", (unsigned long)atomic_inc_return(&input_no)); __module_get(THIS_MODULE); } return dev; } EXPORT_SYMBOL(input_allocate_device); ``` ### 3.5.9. input_event 调用input_handle_event进行事件处理 dev是上报事件的设备,type是事件总类型,code是事件子类型,value是事件值。 ```c /@@* drivers/input/input.c */ void (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); /@@* 事件加锁 */ input_handle_event(dev, type, code, value); /@@* 发送事件调用input_pass_values */ spin_unlock_irqrestore(&dev->event_lock, flags); } }EXPORT_SYMBOL(input_event); ``` ### 3.5.10. input_handle_event 按键上报的处理函数 ```c /@@* drivers/input/input.c */ static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { /@@* 处理事件 */ int disposition = input_get_disposition(dev, type, code, &value); /@@* 处理EV_SYN事件 */ if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value); /@@* 一些特殊事件需要对dev也上报,比如led */ if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); if (!dev->vals) return; /@@* 向上层handler汇报事件 */ if (disposition & INPUT_PASS_TO_HANDLERS) { struct input_value *v; if (disposition & INPUT_SLOT) { v = &dev->vals[dev->num_vals++]; v->type = EV_ABS; v->code = ABS_MT_SLOT; v->value = dev->mt->slot; } /@@* 缓存event事件 */ v = &dev->vals[dev->num_vals++]; v->type = type; v->code = code; v->value = value; } /@@* 向上层handler汇报事件,刷新缓冲区,上报event事件 */ if (disposition & INPUT_FLUSH) { if (dev->num_vals >= 2) input_pass_values(dev, dev->vals, dev->num_vals); dev->num_vals = 0; /@@* 缓冲的时间超过上限,也进行上报处理 */ } else if (dev->num_vals >= dev->max_vals - 2) { dev->vals[dev->num_vals++] = input_value_sync; input_pass_values(dev, dev->vals, dev->num_vals);/@@* 上报event事件 */ dev->num_vals = 0; } } ``` | 描述符宏定义 | 值 | 功能| |--|--|--| | INPUT_INGORE_EVENT | 0 | 忽略该事件| | INPUT_PASS_TO_HANDLERS | 1 | 事件由handler处理| | INPUT_PASS_TO_DEVICE | 2 | 事件有设备处理| | INPUT_SLOT | 4 | 多点触摸事件| | INPUT_FLUSH | 8 | 刷新设备事件的缓冲区| | INPUT_PASS_TO_ALL | 3 | 事件有handler与设备同时处理| ### 3.5.11. input_get_disposition 获取input事件类型 ```c /@@* drivers/input/input.c */ static int input_get_disposition(struct input_dev *dev, unsigned int type, unsigned int code, int *pval) { int disposition = INPUT_IGNORE_EVENT; /@@* 定义初始变量,如果没有更新,最后忽略 */ int value = *pval; /@@* 处理各类事件 */ switch (type) { /@@* 同步事件 */ case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH; break; case SYN_MT_REPORT: disposition = INPUT_PASS_TO_HANDLERS; break; } break; case EV_KEY: /@@* 判断是否支持该键,同时判断按键状态是否改变 */ if (is_event_supported(code, dev->keybit, KEY_MAX)) { if (value == 2) { disposition = INPUT_PASS_TO_HANDLERS; break; } /@@* 判断value是否改变 */ if (!!test_bit(code, dev->key) != !!value) { __change_bit(code, dev->key); /@@* 按位取反 */ disposition = INPUT_PASS_TO_HANDLERS; } } break; case EV_SW: if (is_event_supported(code, dev->swbit, SW_MAX) && !!test_bit(code, dev->sw) != !!value) { __change_bit(code, dev->sw); disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) disposition = input_handle_abs_event(dev, code, &value); break; case EV_REL: if (is_event_supported(code, dev->relbit, REL_MAX) && value) disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC: if (is_event_supported(code, dev->mscbit, MSC_MAX)) disposition = INPUT_PASS_TO_ALL; break; case EV_LED: if (is_event_supported(code, dev->ledbit, LED_MAX) && !!test_bit(code, dev->led) != !!value) { __change_bit(code, dev->led); disposition = INPUT_PASS_TO_ALL; } break; case EV_SND: if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) __change_bit(code, dev->snd); disposition = INPUT_PASS_TO_ALL; } break; case EV_REP: if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { dev->rep[code] = value; disposition = INPUT_PASS_TO_ALL; } break; case EV_FF: if (value >= 0) disposition = INPUT_PASS_TO_ALL; break; case EV_PWR: disposition = INPUT_PASS_TO_ALL; break; } *pval = value; return disposition; } ``` ### 3.5.12. input_pass_event 调用input_pass_values ```c /@@* drivers/input/input.c */ static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_value vals[] = { { type, code, value } }; input_pass_values(dev, vals, ARRAY_SIZE(vals)); /@@* 调用input_pass_values */ } ``` ### 3.5.13. input_pass_values 上报事件的处理 ```c /@@* drivers/input/input.c */ static void input_pass_values(struct input_dev *dev, struct input_value *vals, unsigned int count) { struct input_handle *handle; struct input_value *v; if (!count) return; rcu_read_lock(); /@@* grab是强制为input device绑定的handler ,如果存在就直接调用 */ handle = rcu_dereference(dev->grab); if (handle) { count = input_to_handler(handle, vals, count); } else { /@@* 如果device绑定具体的handle,则遍历这个dev上的所有handle,向应用层open过的发送信息 */ list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) { count = input_to_handler(handle, vals, count); if (!count) break; } } rcu_read_unlock(); /@@* 按键事件自动回复 */ if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) { for (v = vals; v != vals + count; v++) { if (v->type == EV_KEY && v->value != 2) { if (v->value) input_start_autorepeat(dev, v->code); else input_stop_autorepeat(dev); } } } } ``` 函数最终就会调用到handler->event,对事件进行处理。 ### 3.5.14. input_to_handler ```c /@@* drivers/input/input.c */ static unsigned int input_to_handler(struct input_handle *handle, struct input_value *vals, unsigned int count) { struct input_handler *handler = handle->handler; struct input_value *end = vals; struct input_value *v; if (handler->filter) { for (v = vals; v != vals + count; v++) { if (handler->filter(handle, v->type, v->code, v->value)) continue; if (end != v) *end = *v; end++; } count = end - vals; } if (!count) return 0; if (handler->events) handler->events(handle, vals, count); else if (handler->event) for (v = vals; v != vals + count; v++) handler->event(handle, v->type, v->code, v->value); return count; } ``` ### 3.5.15. evdev_events 事件处理函数 ```c /@@* drivers/input/evdev.c */ static void evdev_events(struct input_handle *handle, const struct input_value *vals, unsigned int count) { struct evdev *evdev = handle->private; struct evdev_client *client; ktime_t time_mono, time_real; /@@* 获取时间信息 */ time_meno = ktime_get(); time_real = ktime_sub(time_mono, ktime_get_monotonic_offset()); rcu_read_lock(); client = rcu_dereference(evdev->grab); /@@* 如果该evdev有个专用的client,那么就将事件发给它,如果发送给它,如果该evdev不存在专用的 cliect,那就把该事件发送给evdev上client_list链表上所有的client */ if (client) evdev_pass_values(client, vals, count, ev_time); /@@* 打包数据 */ else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_values(client, vals, count, ev_time); rcu_read_unlock(); } ``` ### 3.5.16. evdev_pass_values 填充event数据。 ```c /@@* drivers/input/evdev.c */ static void evdev_pass_values(struct evdev_client *client, const struct input_value *vals, unsigned int count, ktime_t *ev_time) { struct evdev *evdev = client->evdev; const struct input_value *v; struct input_event event; struct timespec64 ts; bool wakeup = false; if (client->revoked) return; ts = ktime_to_timespec64(ev_time[client->clk_type]); /@@* 获取时间戳 */ event.input_event_sec = ts.tv_sec; event.input_event_usec = ts.tv_nsec / NSEC_PER_USEC; /@@* 中断禁止, 获取锁 */ spin_lock(&client->buffer_lock); /@@* 多个数据 */ for (v = vals; v != vals + count; v++) { if (__evdev_is_filtered(client, v->type, v->code)) continue; if (v->type == EV_SYN && v->code == SYN_REPORT) { /@@* drop empty SYN_REPORT */ if (client->packet_head == client->head) continue; wakeup = true; } /@@* 数据重新封装为event对象 */ event.type = v->type; event.code = v->code; event.value = v->value; __pass_event(client, &event);// 在里面做消息传递 } spin_unlock(&client->buffer_lock); /@@* 唤醒队列 */ if (wakeup) wake_up_interruptible(&evdev->wait); } ``` ### 3.5.17. __pass_event 设备驱动上报事件并不是直接传递给用户空间的,在通用事件处理器(evdev)中,事件被缓冲存在缓冲区中。__pass_event函数里会将input_event放到client结构结构体的环形缓冲区里,即evdev_client结构体的buffer,用户程序通过read()函数从环形缓冲区中获取input_event事件。 ```c /@@* drivers/input/evdev.c */ static void __pass_event(struct evdev_client *client, const struct input_event *event) { client->buffer[client->head++] = *event; /@@*将事件赋值给客户端的input_event缓冲区*/ client->head &= client->bufsize - 1; /@@*对头head自增指向下一个元素空间 */ /@@*当队头head与队尾tail相等时,说明缓冲区已满 */ if (unlikely(client->head == client->tail)) { /@@* * This effectively "drops" all unconsumed events, leaving * EV_SYN/SYN_DROPPED plus the newest event in the queue. */ client->tail = (client->head - 2) & (client->bufsize - 1); client->buffer[client->tail].input_event_sec = event->input_event_sec; client->buffer[client->tail].input_event_usec = event->input_event_usec; client->buffer[client->tail].type = EV_SYN; client->buffer[client->tail].code = SYN_DROPPED; client->buffer[client->tail].value = 0; client->packet_head = client->tail; } /@@* 当遇到EV_SYN/ SYN_REPORT同步事件时,packet_head移动到对头head位置*/ if (event->type == EV_SYN && event->code == SYN_REPORT) { client->packet_head = client->head; kill_fasync(&client->fasync, SIGIO, POLL_IN); 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); init_waitqueue_head(&evdev->wait); 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; /@@*这里我们能看到最终生成的设备文件,例如event0、event1等待 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; /@@*在设备驱动视图/sys/class/input/和/sys/devices/目录下产生eventx设备,最终依event机制和mdev在/dev目录生成对应的设备文件*/ 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); 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; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); err_free_minor: input_free_minor(minor); return error; } } } ``` ### 3.5.18. evdev_connect 创建一个新的evdev 设备 ### 3.5.19. input_proc_init 创建对应的目录结构 ```c /@@* drivers/input/input.c */ static int __init input_proc_init(void) { struct proc_dir_entry *entry; /@@* 在/proc/bus目录下创建input */ proc_bus_input_dir = proc_mkdir("bus/input", NULL); if (!proc_bus_input_dir) return -ENOMEM; /@@* 在/proc/bus/input目录下创建devices文件 */ entry = proc_create("devices", 0, proc_bus_input_dir, &input_devices_fileops); if (!entry) goto fail1; /@@* 在/proc/bus/input目录下创建handlers文件 */ entry = proc_create("handlers", 0, proc_bus_input_dir, &input_handlers_fileops); if (!entry) goto fail2; return 0; fail2: remove_proc_entry("devices", proc_bus_input_dir); fail1: remove_proc_entry("bus/input", NULL); return -ENOMEM; } ``` ### 3.5.20. input_set_capability 设置输入设备可以上报哪些事件,需要注意一次只能设置一个事件,如果设备上报多个事件,需要重复调用 ```c /@@* drivers/input/input.c */ void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) { switch (type) { case EV_KEY: __set_bit(code, dev->keybit); break; case EV_REL: __set_bit(code, dev->relbit); break; case EV_ABS: input_alloc_absinfo(dev); if (!dev->absinfo) return; __set_bit(code, dev->absbit); break; case EV_MSC: __set_bit(code, dev->mscbit); break; case EV_SW: __set_bit(code, dev->swbit); break; case EV_LED: __set_bit(code, dev->ledbit); break; case EV_SND: __set_bit(code, dev->sndbit); break; case EV_FF: __set_bit(code, dev->ffbit); break; case EV_PWR: /@@* do nothing */ break; default: pr_err("%s: unknown type %u (code %u)\n", __func__, type, code); dump_stack(); return; } __set_bit(type, dev->evbit); } EXPORT_SYMBOL(input_set_capability); ``` ### 3.5.21. evdev_read 读取event数据 ```c static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; size_t read = 0; int error; if (count != 0 && count < input_event_size()) return -EINVAL; for (;;) { if (!evdev->exist || client->revoked) return -ENODEV; /@@* client的环形缓冲区中没有数据并且是非阻塞的,那么返回-EAGAIN,也就是try again */ if (client->packet_head == client->tail &&(file->f_flags & O_NONBLOCK)) return -EAGAIN; /@@* * count == 0 is special - no IO is done but we check * for error conditions (see above). */ if (count == 0) break; /@@* 如果获得了数据,就取出来 */ while (read + input_event_size() <= count && evdev_fetch_next_event(client, &event)) { /@@* 传给用户 */ if (input_event_to_user(buffer + read, &event)) return -EFAULT; read += input_event_size(); } if (read) break; /@@* 如果没有数据,并且是阻塞的,则在等待队列上等待 */ if (!(file->f_flags & O_NONBLOCK)) { error = wait_event_interruptible(evdev->wait, client->packet_head != client->tail || !evdev->exist || client->revoked); if (error) return error; } } return read; } ``` 如果read进行进入休眠状态,则会被evdev_event函数唤醒 # 4. 模块测试 ## 4.1.测试概述 应用有两条路径,如下所示 /sys/class/input ```c console:/sys/class/input # ls event0 event1 event2 input0 input1 input2 mice ``` /dev/input ```bash console:/dev/input # ls event0 event1 event2 mice ``` 查看设备信息 ```bash $ cat /proc/bus/input/devices I: Bus=0000 Vendor=0000 Product=0000 Version=0000 N: Name="lombo-ir" P: Phys=lombo-ir/input0 S: Sysfs=/devices/platform/4014800.ir/rc/rc0/input0 U: Uniq= H: Handlers=event0 B: PROP=0 B: EV=100013 B: KEY=1 0 0 0 0 0 0 0 1680 0 0 ffc B: MSC=10 ``` event节点里面存放的数据都是没有经过处理的原始数据流。cat 对应的eventX节点就可以查看输入的数据。 ```bash $ cat event0 ``` ## 4.2.应用测试 ```c #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(void) { int ret; int fd; struct input-event value; fd = open("/dev/input/event1", O_RDWR); if (fd < 0) { printf("open event1 failed %d\n", fd); return 0; } while(1) { ret = read(fd, &value, sizeof(value)); if (ret < 0) printf("read failed\n"); printf("input type:%d code:%d value:%d\n", value.type, value.code, value.value); } return 0; } ```
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
一口Linux
关注
评论
(1)
登录后可评论,请
登录
或
注册
xbk_619187
176
天前...
ST 原装正品现货,有价格优势!欢迎咨询 QQ:53358029
0
回复
发布
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交