前面的章节,对字符设备驱动的框架进行了学习,包括中断、poll机制、异步信号通知、原子操作、阻塞、定时器按键消抖等机制。其驱动框架结构如下:
- 编写file_operation结构体的成员函数:open、read、write等。
- 在驱动入口函数xx_init()中,调用register_chrdev()注册驱动,生成主设备号major,写入file_operation结构体。
- 出口函数中调用unregister_chrdev()卸载驱动。
这种方式编写的驱动,必须指定具体的设备文件"/dev/buttons"才能打开,而一般的应用程序不会直接去打开"/dev/buttons"设备。这些设备文件只有编写驱动的人员比较清楚,并不具有通用性。因此,为了提高驱动的通用性,以便应用程序可以无缝使用,就需要借助内核现成的驱动框架,将驱动添加到内核的驱动框架中。输入子系统-现成的驱动。
1 Linux输入子系统的简介
输入子系统的诞生主要是为了统一分散的、不同类别的输入设备的驱动。具有如下优点:
- 统一了物理形态各异的相似的输入设备的处理功能。例如:各种类型的鼠标(PS/2、USB、蓝牙)做相同的处理。
- 提供用于分发输入报告给用户应用程的简单的事件(event)接口。不在使用创建、管理/dev节点的访问方式,而采用调用输入API发送事件到应用层的形式。Windows这样的应用程序能够无缝地运行于输入子系统提供的事件接口之上。
- 抽取出了输入程序的通用部分,简化了驱动程序,并引入了一致性。例如:输入子系统提供可一个底层驱动程序(serio)的集合,支持对串行端口和键盘控制器等硬件输入设备的访问。
Linux输入子系统(linux input subsystem)由上到下分为三层:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层(Input driver)。
Input driver:实现对硬件设备的读写访问,中断设置,并将硬件产生的事件转换为InputCore层定义的规范提交给EventHandler。
InputCore:承上启下。为Input driver层提供设备注册和操作的接口。通知EventHandler对事件进行处理。
EventHandler:用户编程的接口。对驱动层提交的数据进行处理。
其结构如图所示。
2输入子系统代码分析
上面从功能方面对输入子系统的框架进行了介绍,接下来对代码进行分析。
分析驱动首先从入口函数开始,找到drivers/input/input.c。
- subsys_initcall(input_init);
- module_exit(input_exit);
入口函数为input_init,
- static int __init input_init(void)
- {
- int err;
-
- err = class_register(&input_class);//在/sys/class下创建逻辑类input_class
- if (err) {
- printk(KERN_ERR "input: unable to register input_dev class\n");
- return err;
- }
-
- err = input_proc_init();//在/proc下面创建
- if (err)
- goto fail1;
-
- //申请一个字符设备,主设备号13
- err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
- if (err) {
- printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
- goto fail2;
- }
-
- return 0;
-
- fail2: input_proc_exit();
- fail1: class_unregister(&input_class);
- return err;
- }
在入口函数中"err = class_register(&input_class);"创建一个input_class类,即在/sys/class下创建目录input。
启动系统后,可以查看到"input"类:
这里为什么只创建了类,没有使用class_device_create()函数在类下面创建驱动设备?后面在分析。。。
通过register_chrdev()函数创建驱动设备,其中INPUT_MAJOR宏为13,即创建一个主设备号为13的"input"设备。传入的input_fops结构体定义如下:
该结构体中只有open成员函数,在挂载该驱动后,就会调用open函数,下面就分析.open函数。
进入input_open_file函数
- static int input_open_file(struct inode *inode, struct file *file)
- {
- struct input_handler *handler = input_table[iminor(inode) >> 5];//(1)
- const struct file_operations *old_fops, *new_fops = NULL;
- int err;
-
- /* No load-on-demand here? */
- if (!handler || !(new_fops = fops_get(handler->fops)))//(2)
- return -ENODEV;
-
- /*
- * That's _really_ odd. Usually NULL ->open means "nothing special",
- * not "no device". Oh, well...
- */
- if (!new_fops->open) {
- fops_put(new_fops);
- return -ENODEV;
- }
- old_fops = file->f_op;
- file->f_op = new_fops;//(3)
-
- err = new_fops->open(inode, file);//(4)
-
- if (err) {
- fops_put(file->f_op);
- file->f_op = fops_get(old_fops);
- }
- fops_put(old_fops);
- return err;
- }
-
- 第三行中,iminor(inode)取出设备的次设备号,再除以32,找到input_table数组中选项的索引,并将输入处理函数指针handler指向该选项。
- 若handler 有值,说明已经挂载了该驱动,将handler结构体中的file_operations *fops成员赋值给新的file_operations *new_fops。
- 在将新的file_operations *new_fops赋值给file->f_op ,此时输入子系统的file_operations就是新挂接的input驱动的file_operations结构体了。
- 调用新挂接的input驱动的new_fops->open函数。
上面这段代码中input_table数组在初始化时并未赋值,这里在drivers/input/input.c文件中查看一下该数组在哪里赋值。
找到在input_register_handler函数中对数组进行赋值,代码如下:
- int input_register_handler(struct input_handler *handler)
- {
- struct input_dev *dev;
-
- INIT_LIST_HEAD(&handler->h_list);
-
- if (handler->fops != NULL) {
- if (input_table[handler->minor >> 5])
- return -EBUSY;
-
- input_table[handler->minor >> 5] = handler;
- }
-
- list_add_tail(&handler->node, &input_handler_list);
-
- list_for_each_entry(dev, &input_dev_list, node)
- input_attach_handler(dev, handler);
-
- input_wakeup_procfs_readers();
- return 0;
- }
代码中将handler写入到数组input_table,并将其加入到input_handler_list链表中。
继续搜索函数input_register_handler被谁调用?
发现很多文件中都调用了该函数
这里以evdev.c为例,在evdev_init()函数中调用了input_register_handler()函数。
- static int __init evdev_init(void)
- {
- return input_register_handler(&evdev_handler);
- }
这里evdev_handler结构体的定义如下:
- static struct input_handler evdev_handler = {
- .event = evdev_event,
- .connect = evdev_connect,
- .disconnect = evdev_disconnect,
- .fops = &evdev_fops,
- .minor = EVDEV_MINOR_BASE,
- .name = "evdev",
- .id_table = evdev_ids,
- };
这是一个input_handler类型的结构体,
(1)成员evdev_fops就是我们需要自己实现的驱动的操作函数。
(2)次设备号EVDEV_MINOR_BASE为64,调用input_register_handler函数,64/32=2,即将evdev_handler结构体存放到input_table[2]中。在打开这个evdev input设备时,调用.open实际上就是evdev_handler->evdev_fops.evdev_open函数。
(3)其中第8行的.id_table表示支持哪些输入设备。当驱动设备的id input_dev->id和input_handler的id_table相匹配时,就会调用.connnect连接函数。
(4)第3行的.connect函数是将设备input_dev和input_handler建立如下图的关联
接下来分析input_register_device函数。
- int input_register_device(struct input_dev *dev)
- {
- static atomic_t input_no = ATOMIC_INIT(0);
- struct input_handler *handler;
- const char *path;
- int error;
-
- set_bit(EV_SYN, dev->evbit);
-
- /*
- * If delay and period are pre-set by the driver, then autorepeating
- * is handled by the driver itself and we don't do it in input.c.
- */
-
- 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;
-
- list_add_tail(&dev->node, &input_dev_list);//(1)
-
- snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
- "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
-
- if (!dev->cdev.dev)
- dev->cdev.dev = dev->dev.parent;
-
- error = class_device_add(&dev->cdev);
- if (error)
- return error;
-
- path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
- printk(KERN_INFO "input: %s as %s\n",
- dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
- kfree(path);
-
- list_for_each_entry(handler, &input_handler_list, node)//(2)
- input_attach_handler(dev, handler);
-
- input_wakeup_procfs_readers();
-
- return 0;
- }
-
- 第4行中,将要注册的 input_dev驱动设备添加到链表input_dev_list中;
- 第46行中,input_handler_list是驱动处理函数结构体链表,list_for_each_entry是将input_handler_list链表中对应的节点取出来,存放到handler中。
- 最后调用input_attach_handler,将dev->id依次与handler-id_table进行判断,如果相同就进行连接。
接着看input_handler的注册函数input_register_handler()。
- int input_register_handler(struct input_handler *handler)
- {
- struct input_dev *dev;
-
- INIT_LIST_HEAD(&handler->h_list);
-
- if (handler->fops != NULL) {
- if (input_table[handler->minor >> 5])
- return -EBUSY;
-
- input_table[handler->minor >> 5] = handler; //(1)
- }
-
- list_add_tail(&handler->node, &input_handler_list); //(2)
-
- list_for_each_entry(dev, &input_dev_list, node) //(3)
- input_attach_handler(dev, handler); //(4)
-
- input_wakeup_procfs_readers();
- return 0;
- }
-
- 根据次设备号/32,定位到input_table数组中,将handler放到数组对应的选项中。
- 第14行,将handler存放到input_handler_list中。
- 将input_dev_list中的对应设备节点取出来存到input_dev中。
- 最后调用input_attach_handler,将input_dev->id依次与handler->id_table进行判断,如果相同就进行连接。
下面来看看input_attach_handler函数。
- static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
- {
- const struct input_device_id *id;
- int error;
-
- if (handler->blacklist && input_match_device(handler->blacklist, dev))
- return -ENODEV;
-
- id = input_match_device(handler->id_table, dev); //匹配
- if (!id) //不相同,退出
- return -ENODEV;
-
- error = handler->connect(handler, dev, id); //相同,连接
- if (error && error != -ENODEV)
- printk(KERN_ERR
- "input: failed to attach handler %s to device %s, "
- "error: %d\n",
- handler->name, kobject_name(&dev->cdev.kobj), error);
-
- return error;
- }
以evdev_connect为例。
- static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
- {
- struct evdev *evdev;
- struct class_device *cdev;
- dev_t devt;
- int minor;
- int error;
-
- for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
- 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);
- init_waitqueue_head(&evdev->wait);
- //配置
- evdev->exist = 1;
- evdev->minor = minor;
- evdev->handle.dev = dev;
- evdev->handle.name = evdev->name;
- evdev->handle.handler = handler;
- evdev->handle.private = evdev;
- sprintf(evdev->name, "event%d", minor);
-
- evdev_table[minor] = evdev;
-
- devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //将主设备号和次设备号转换为dev_t类型
-
- cdev = class_device_create(&input_class, &dev->cdev, devt,
- dev->cdev.dev, evdev->name); //在input_class类下面创建设备dev
- if (IS_ERR(cdev)) {
- error = PTR_ERR(cdev);
- goto err_free_evdev;
- }
-
- /* temporary symlink to keep userspace happy */
- error = sysfs_create_link(&input_class.subsys.kobj,
- &cdev->kobj, evdev->name);
- if (error)
- goto err_cdev_destroy;
-
- error = input_register_handle(&evdev->handle); //注册input_handle结构体
- if (error)
- goto err_remove_link;
-
- return 0;
-
- err_remove_link:
- sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
- err_cdev_destroy:
- class_device_destroy(&input_class, devt);
- err_free_evdev:
- kfree(evdev);
- evdev_table[minor] = NULL;
- return error;
- }
第47行调用input_register_handle注册input_handle。
下面来看看input_register_handle函数。
- int input_register_handle(struct input_handle *handle)
- {
- struct input_handler *handler = handle->handler;
-
- list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
- list_add_tail(&handle->h_node, &handler->h_list); //(2)
-
- if (handler->start)
- handler->start(handle);
-
- return 0;
- }
-
- 第5行中,handle->dev指向的是input_dev,将handle->d_node存放到input_dev的h_list链表中,即input_dev的h_list链表指向handle->d_node。
- 第6行中,将handle->h_node存放到input_handler结构体的h_list中,即input_handler结构体的h_list链表指向handle->h_node。
其关系如图所示。
input_dev和input_handler的.h_list都指向了handle结构,通过handle的成员.dev和.handler就可以分别找到对应的input_dev和input_handler,即建立了驱动层和handler层之间的联系。
回到最开始的问题,应用层如何读取底层驱动的事件?
来看看底层驱动的.read函数,继续以evdev.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;
- int retval;
-
- if (count < evdev_event_size()) //判断应用层传进来的参数是否正确
- return -EINVAL;
-
- if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) //在非阻塞的情况下,若 client->head == client->tail || evdev->exist 没有数据,则返回
- return -EAGAIN;
-
- retval = wait_event_interruptible(evdev->wait,
- client->head != client->tail || !evdev->exist); //等待数据,进入休眠
- if (retval)
- return retval;
-
- if (!evdev->exist)
- return -ENODEV;
-
- while (client->head != client->tail && retval + evdev_event_size() <= count) {
-
- struct input_event *event = (struct input_event *) client->buffer + client->tail;
-
- if (evdev_event_to_user(buffer + retval, event)) //向应用层发送事件
- return -EFAULT;
-
- client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
- retval += evdev_event_size();
- }
-
- return retval;
- }
上述代码中,第13行进入休眠后,在哪里唤醒呢?
在evdev_event函数中,会调用wake_up_interruptible唤醒函数。
- static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
- {
- struct evdev *evdev = handle->private;
- struct evdev_client *client;
-
- if (evdev->grab) {
- client = evdev->grab;
-
- do_gettimeofday(&client->buffer[client->head].time);
- client->buffer[client->head].type = type;
- client->buffer[client->head].code = code;
- client->buffer[client->head].value = value;
- client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
-
- kill_fasync(&client->fasync, SIGIO, POLL_IN);
- } else
- list_for_each_entry(client, &evdev->client_list, node) {
-
- do_gettimeofday(&client->buffer[client->head].time);
- client->buffer[client->head].type = type;
- client->buffer[client->head].code = code;
- client->buffer[client->head].value = value;
- client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
-
- kill_fasync(&client->fasync, SIGIO, POLL_IN);
- }
-
- wake_up_interruptible(&evdev->wait);
- }
evdev_event函数是evdev_handler结构体的成员。
那么谁会调用evdev_handler.event函数?
这里input_event()函数会调用.event函数,代码如下:
- void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
- {
- struct input_handle *handle;
- ......
-
- if (dev->grab)
- dev->grab->handler->event(dev->grab, type, code, value);
- else
- list_for_each_entry(handle, &dev->h_list, d_node) //通过dev->h_list结构找到handle结构
- if (handle->open)
- handle->handler->event(handle, type, code, value); //在通过handle结构结构调用handler的event函数
- }
标注:
-
- 文章中大量参考博文: ,非常感谢前辈总结的经验,给了我很多帮助和鼓励!