1 Linux输入子系统
前面的章节,对字符设备驱动的框架进行了学习,包括中断、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函数
-
}
标注:
-
- 文章中大量参考博文:https://www.cnblogs.com/lifexy/p/7542989.html ,非常感谢前辈总结的经验,给了我很多帮助和鼓励!