以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、前言
由input子系统简介可知,input子系统分为三层:
1、上层输入事件驱动层
涉及的文件有x210_kernel\drivers\input\evdev.c、mousedev.c 和 joydev.c文件,分别对应上层的各个不同的handler的源代码。但如图所示,一般集中采用 event handlers的方式,但这种方式不是排他性的,可以同时存在。该层负责将 struct input_event 呈送给应用层。
2、中层框架核心层
涉及的文件有x210_kernel\drivers\input\input.c文件。
3、下层具体硬件驱动层
涉及的文件包括x210_kernel\drivers\input目录中的各个文件夹,比如joystick文件、mouse文件夹、keyboard文件夹、touchscreen文件夹等。
下面将对上层输入事件驱动层进行分析。
二、输入事件驱动层分析
input子系统的输入事件驱动层其实是由各个handler构成的,各个handler之间是属于平行关系,不存在相互调用的现象。目前用的最多是event,今天就以这个handler为例分析他的源代码,以便对handler的实现有一定的了解。其中 drivers\input\evdev.c 就是event的源代码文件。
从evdev.c文件的末尾可以看到module_init、module_exit这些宏,这说明内核中将这部分实现为模块的方式,这很好理解,因为框架核心层都实现为模块的方式,而上层依赖于框架核心层才能够注册与工作的,所以上层也需要实现为模块的方式。
1、相关数据结构
(1)struct evdev结构体
struct evdev {int exist;int open; // 这个是用来作为设备被打开的计数int minor; // handler 与 input设备匹配成功之后创建的设备对应的device的次设备号相对于基准次设备号的偏移量struct input_handle handle; // 内置的一个 handle ,里面记录了匹配成功的input_dev 和 handlerwait_queue_head_t wait;struct evdev_client *grab;struct list_head client_list; // 用来挂接与 evdev 匹配成功的evdev_client 的一个链表头spinlock_t client_lock; /* protects client_list */struct mutex mutex; // 互斥锁struct device dev; // 这个是handler 与 input设备匹配成功之后创建的设备对应的device };
(2)struct evdev_client结构体
struct evdev_client {struct input_event buffer[EVDEV_BUFFER_SIZE]; // 用来存放input_dev 事件的缓冲区int head;int tail;spinlock_t buffer_lock; /* protects access to buffer, head and tail */struct fasync_struct *fasync;struct evdev *evdev; // evdev 指针struct list_head node; // 作为一个链表节点挂接到相应的 evdev->client_list 链表上struct wake_lock wake_lock;char name[28]; // 名字 };
(3)struct input_event结构体
struct input_event {struct timeval time;//事件发生的时间__u16 type;//事件的类型(键盘?触摸屏?)__u16 code;//事件的码值(按键a对应的编码)__s32 value;//事件的状态、操作值(比如是按下了还是弹起了;触摸点坐标等) };
2、模块注册函数: evdev_init()
static struct input_handler evdev_handler = {.event = evdev_event,.connect = evdev_connect,.disconnect = evdev_disconnect,.fops = &evdev_fops,.minor = EVDEV_MINOR_BASE, //次设备号起始编号是64.name = "evdev",.id_table = evdev_ids, };static int __init evdev_init(void) {return input_register_handler(&evdev_handler); }
其中evdev_handler这个结构体变量,就是本次分析的handler对应的结构体变量。对这个结构体变量的填充操作里,最重要的有3个:evdev_event函数、evdev_connect函数和evdev_fops变量。
3、其他函数分析
(1)evdev_connect函数分析
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id) {struct evdev *evdev; // 定义一个 evdev 指针int minor;int error;for (minor = 0; minor < EVDEV_MINORS; minor++) // 从evdev_table 数组中找到一个没有被使用的最小的数组项 最大值32if (!evdev_table[minor])break;if (minor == EVDEV_MINORS) {printk(KERN_ERR "evdev: no more free evdev devices\n");return -ENFILE;}evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 给evdev 申请分配内存if (!evdev)return -ENOMEM;INIT_LIST_HEAD(&evdev->client_list); // 初始化 evdev->client_list 链表spin_lock_init(&evdev->client_lock); // 初始化自旋锁 evdev->client_lockmutex_init(&evdev->mutex); // 初始化互斥锁 evdev->mutexinit_waitqueue_head(&evdev->wait);dev_set_name(&evdev->dev, "event%d", minor); // 设置input设备的名字evdev->exist = 1;evdev->minor = minor; // input设备的次设备号的偏移量evdev->handle.dev = input_get_device(dev); // 将我们传进来的 input_dev 指针存放在 evdev->handle.dev 中evdev->handle.name = dev_name(&evdev->dev); // 设置 evdev -> dev 对象的名字,并且把名字赋值给 evdev->handle.nameevdev->handle.handler = handler; // 将我们传进来的 handler 指针存放在 handle.handler 中evdev->handle.private = evdev; // 把evdev 作为handle 的私有数据evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); // 设置 evdev->device 设备的设备号evdev->dev.class = &input_class; // 将 input_class 作为 evdev->device 的设备类evdev->dev.parent = &dev->dev; // 将input_dev -> device 作为evdev->device 的父设备evdev->dev.release = evdev_free; // evdev -> device 设备的卸载函数device_initialize(&evdev->dev); // 设备初始化error = input_register_handle(&evdev->handle); // 注册handleif (error)goto err_free_evdev;error = evdev_install_chrdev(evdev); // 安装evdev 其实就是将evdev 结构体指针存放在evdev_table数组当中 下标就是evdev->minorif (error)goto err_unregister_handle;error = device_add(&evdev->dev); // 添加设备到系统 /sys/devices/virtual/input/input0/event0 event0就是表示建立的设备文件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);return error; }
注意,/sys/devices/virtual/input/input0 这个设备是在注册input_dev时创建的,而input0/event0就是在handler和input_dev匹配成功之后创建的,也会在/dev/目录下创建设备节点。
(2)evdev_open函数分析
static int evdev_open(struct inode *inode, struct file *file) {struct evdev *evdev; // 定义一个 evdev 结构体指针struct evdev_client *client; // 定义一个evdev_client 指针int i = iminor(inode) - EVDEV_MINOR_BASE; // 通过inode 获取 需要打开的设备对应的evdev_table 数组中的下标变量int error;if (i >= EVDEV_MINORS)return -ENODEV;error = mutex_lock_interruptible(&evdev_table_mutex);if (error)return error;evdev = evdev_table[i]; // 从evdev_table 数组中找到evdevif (evdev)get_device(&evdev->dev);mutex_unlock(&evdev_table_mutex);if (!evdev)return -ENODEV;client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); // 给 client 申请分配内存if (!client) {error = -ENOMEM;goto err_put_evdev;}spin_lock_init(&client->buffer_lock);snprintf(client->name, sizeof(client->name), "%s-%d",dev_name(&evdev->dev), task_tgid_vnr(current));wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);client->evdev = evdev; // 通过client->evdev 指针指向 evdevevdev_attach_client(evdev, client); // 其实这个函数就是做了一个链表挂接: client->node 挂接到 evdev->client_listerror = evdev_open_device(evdev); // 打开 evdev 设备 最终就会打开 input_dev -> open 函数if (error)goto err_free_client;file->private_data = client; // 将evdev_client 作为file 的私有数据存在nonseekable_open(inode, file);return 0;err_free_client:evdev_detach_client(evdev, client);kfree(client);err_put_evdev:put_device(&evdev->dev);return error; }
4、总结
(1)handler为何只能够处理对应的事件?
下层可以上报的事件在我们的内核中是定义好的,我们可以上报这些事。但是input子系统的上层输入事件驱动层的各个handler只能够处理某些特定的事件(event除外)。例如,joy handler只能处理摇杆类型的事件,key handler只能处理键盘。这是因为框架核心层会进行handler和device的匹配操作。如果上报的事件与多个handler都能够匹配成功,则绑定之后框架核心层会向输入事件驱动层中的多个handler都上报事件,再由handler上报给应用层。
(2)input设备注册的流程
具体硬件驱动层利用框架核心层提供的函数,向子系统注册输入设备
/******************************************************************************/
input_register_device
device_add: /sys/devices/virtual/input/input0
链表挂接: input_dev->node -------> input_dev_list
input_attach_handler // 进行input_dev和handler之间的匹配
调用handler->connect进行连接
构建evdev结构体,加入evdev_table数组
input_register_handle
device_add: /sys/devices/virtual/input/input0/event0
/*******************************************************************************/
(3)handler注册流程
/****************************************************************/
input_register_handler
input_table[handler->minor >> 5] = handler
链表挂接: handler->node -----> input_handler_list
input_attach_handler
handler->connect // 调用handler的connect函数进行连接
/****************************************************************/
(4)事件如何传递到应用层
input子系统下层通过调用input_event函数向核心层上报数据。
input_event
input_handle_event
input_pass_event
handler->event() // 最终会调用到handler 中的event函数
evdev_pass_event
client->buffer[client->head++] = *event; // 会将input输入事件数据存放在evdev_client结构体中的缓冲去中
当应用层通过open打开event0这个设备节点时,最终会调用到input_init函数中注册的字符设备input时注册的file_operations->open() 函数
input_open_file
handler = input_table[iminor(inode) >> 5]
handler->fops->open()
evdev = evdev_table[i];
evdev_open_device
input_open_device
input_dev->open() // 最终就是执行input设备中的open函数
file->private_data = evdev_client;
所以当我们在应用层调用read函数时,最终会调用到handler->fops->read函数
evdev_read
evdev_fetch_next_event
*event = client->buffer[client->tail++] // 将evdev_client->buffer中的数据取走
input_event_to_user
copy_to_user // 拷贝到用户空间