嵌入式Hacker (es-hacker)

Embedded bsp developer enjoys thinking and hacking opensource and develop boards(NanoPi, LicheePi, RPi...)

0%

Linux-Input入门-一次快乐的代码分析

看他人如何分析代码最关键的目的是了解别人是怎么思考和探索的,其次才是获取内容相关的知识点。即吃别人的鱼不如学别人怎么钓鱼。

本文是我学生时代留下的关于输入子系统的学习笔记,对我个人而言很有纪念意义,对初学者来说也许有一丢丢参考价值。

最近工作上需要驱动一些输入设备,不由得感慨:Input 子系统设计得真好,这么多年了 Input core 的设计和 API 可以认为是仅发生了察觉不到的变化,这就是驱动界的典范子系统啊。反过来想想,Input 子系统之所以这么经典,恰恰是因为Input device driver 的数量太多了,如果设计得不够好,那将是 Maintainer 们的噩梦(整日疲于修改成吨的Input device driver)。

Linux 内核/驱动开发并不是特殊的存在,它仍然属于软件开发,或许它有许多自己独有的软件设计模式,但是上层软件开发的很多原则和方法仍然是适用的,不展开来讲了,那样就扯太远了。

下面是正文

相关参考:

  • 韦东山嵌入式 Linux 视频教程第二期: 第13课 输入子系统 (Linux-2.6)
  • Essential Linux Device Drivers

关注并且后台回复“ELDD”即可下载。

预备知识:

  • Linux 字符设备驱动

查看入口

在 drivers/input/input.c 中,查看模块入口函数 input_init():

1
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

而 input_fops 只有 open 和 llseek 函数:

1
static const struct file_operations input_fops = 
2
{
3
    .owner = THIS_MODULE,
4
    .open = input_open_file,
5
    .llseek = noop_llseek,
6
};

没有 read 函数的话该怎么读数据呢?

分析 input_open_file 函数:

1
/* 定义一个input_handler指针 */
2
struct input_handler *handler;
3
/* 根据传入文件的次设备号从 input_table中提取出一个input_handler */
4
handler = input_table[iminor(inode) >> 5];
5
/* 从input_handler中提取出new_fops */
6
new_fops = fops_get(handler->fops);
7
/* 将new_fops赋值给当前文件的file_operations */
8
file->f_op = new_fops;
9
err = new_fops->open(inode, file);

经过这些操作后,当 app 再来调用 read,write,open 等函数时,最终都会调用到file->f_op 中的 read,write,open 等函数。

input_open_file 函数中的 input_table 从何而来?

搜索代码可知,在 input.c 的input_register_handler 函数中填充了 input_table:

1
if (handler->fops != NULL) {
2
    if (input_table[handler->minor >> 5]) {
3
        retval = -EBUSY;
4
        goto out;
5
    }
6
    /* 给 input_table 中的第 handler->minor >> 5 项赋值 */
7
    input_table[handler->minor >> 5] = handler;
8
}

谁在调用 input_register_handler?

搜索内核可知:
evdev.c,tsdev.c,joydev.c,keyboard.c,mousedev.c 等文件调用了 input_register_handler,我们以 evdev.c 为例,在 drivers/input/evdev.c 中,模块入口函数 evdev_init:

1
return input_register_handler(&evdev_handler);

evdev_handler的定义如下:

1
static struct input_handler evdev_handler = {
2
.event		= evdev_event,
3
.connect	= evdev_connect,
4
.disconnect	= evdev_disconnect,
5
.fops		= &evdev_fops,//前面所用到的 new_fops 指的就是这里定义的的fops
6
.minor		= EVDEV_MINOR_BASE,
7
.name		= "evdev",
8
.id_table	= evdev_ids,
9
};

先了解一下输入子系统的架构

当通过 input_register_device 注册一个 input_dev 设备或者通过 input_register_handler 注册一个input_handler 时,input_dev 与 input_handler 会进行匹配。

如何匹配?
在 input_handler 中有一个 id_table 结构体和 match 函数,id_table 里面包含的就是这个 input_handler 能处理的 input_dev,当 input_handler 与 input_dev 匹配成功的话(即 match() 返回1),则会调用 input_handler 里的 connect 函数。

input_register_device 和 input_register_handler 分别做了什么?

input_register_device:

1
/* 将刚注册的 input_dev 放入 input_dev_list 链表 */
2
list_add_tail(&dev->node, &input_dev_list);
3
/* 对 input_handler_list 中的每一个 input_handler 都调用
4
 * input_attach_handler(dev, handler) 函数来查看是否有 input_handler
5
 * 合适该 input_dev
6
 */	
7
list_for_each_entry(handler, &input_handler_list, node)
8
    input_attach_handler(dev, handler);

input_register_handler:

1
/* 将刚注册的 input_handler 放入 input_table 中 */
2
input_table[handler->minor >> 5] = handler;
3
/* 将刚注册的 input_handler 放入 input_handler_list 链表中 */
4
5
list_add_tail(&handler->node, &input_handler_list);
6
/* 对 input_dev_list 中的每一个 input_dev 都调用
7
 * input_attach_handler(dev, handler) 函数,
8
 * 来查看是否有 input_dev 合适该 input_handler
9
 */
10
list_for_each_entry(dev, &input_dev_list, node)
11
    input_attach_handler(dev, handler);

由此可以看出,无论是先注册 input_handler 还是先注册 input_dev ,最终都会调用 input_attach_handler(dev, handler) 来进行两两匹配。

input_attach_handler 是如何将 input_handler 和 input_dev 进行匹配的?

在input_attach_handler函数中:

1
/* 先进行匹配,匹配的依据就是 input_handler 中的 id_table 与
2
 * input_dev 中的 id 里的信息是否相同
3
 */
4
id = input_match_device(handler, dev);
5
/* 再调用 input_handler 中的 connect 函数完成连接,具体如何连接,
6
 * 需要分析connect函数
7
 */
8
error = handler->connect(handler, dev, id);

我们以 evdev.c 中的 input_handler 结构中的 connect 函数为例,分析 connect 函数做了些什么。

在 evdev_connect 函数中:

1
/*分配一个 evdev 结构体,该结构体中含一个 input_handle 结构
2
 *注意:不同于 input_handler 结构
3
 */
4
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
5
/*设置 evdev 结构中的 input_handle
6
 * @input_handle 的 dev变量 指向input_dev结构
7
 * @input_handle 的 handler 变量指向input_handler结构
8
*/
9
evdev->handle.dev = input_get_device(dev);
10
evdev->handle.name = dev_name(&evdev->dev);
11
evdev->handle.handler = handler;
12
evdev->handle.private = evdev;
13
/* 向内核注册input_handle */
14
error = input_register_handle(&evdev->handle);

input_register_handle 做了些什么?

1
/* 将 input_handle 添加到 input_dev 中的 h_list 链表中
2
 * 以后当 input_dev 需要使用对应的 input_handler 时就可以通过自身的
3
 * h_list 链表找到 input_handle,从而找到匹配的 input_handler。
4
 */
5
list_add_tail_rcu(&handle->d_node, &dev->h_list);
6
7
/* 将input_handle添加到input_handler中的h_list链表中,
8
 * 以后当input_handler需要使用对应的input_dev时就可以通过自身的
9
 * h_list链表找到input_handle,从而找到匹配的input_dev
10
 */
11
list_add_tail_rcu(&handle->h_node, &handler->h_list);

如何读取到数据?

对于输入设备,我们最终的目的就是读取其数据,那么经过上述的一些列动作后又是如何读取到数据的呢?

分析 evdev.c 中的 input_handler 中的 fops 中的 read 函数就可以确定了。

在 evdev_read 函数中:

1
// 如果没有数据且 nonblock 的话,则 EAGAIN
2
if (client->head == client->tail && evdev->exist &&
3
    (file->f_flags & O_NONBLOCK))
4
    return -EAGAIN;
5
// 否则,睡眠
6
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !		evdev->exist);

既然有睡眠,那么何时被唤醒,搜索代码。

在 evdev_event 函数中:

1
//唤醒
2
wake_up_interruptible(&evdev->wait);

evdev_event 就是 input_handler 中的 event 成员,当有数据可读(如触摸屏被按下,按键被按下)时,event 函数会被调用。而 event 函数是怎么被调用到的?这就得看设备层了。

设备层的驱动做了如下工作:

  1. 在中断函数中确定判断发生了什么事件,并且相应的值是多少;
  2. 通过input_event()函数上报事件;
  3. 通过input_sync()函数表明上报结束;

分析 input_event 函数我们就可以知道 input_handler 中的 event 函数是如何被调用到的了。

在 input_event 中,调用了 input_handle_event 函数,在 input_handle_event 函数中调用了input_pass_event 函数;

在 input_pass_event 函数中:

struct input_handler *handler;

/* 从注册的 input_dev 的 h_list 中将 input_handle 一个个拿出来,
 * 如果该 input_handle 被打开过,
 * 则该input_handle->input_handler 即为可处理该 input_dev的 handler
 */
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
    if (!handle->open)
        continue;
    handler = handle->handler;
    if (!handler->filter) {
        if (filtered)
            break;

        /*最终调用到event函数*/
        handler->event(handle, type, code, value);
    } else if (handler->filter(handle, type, code, value))
        filtered = true;
}    

如何写符合输入子系统框架的驱动程序 ?

水到渠成:

  1. 分配一个 input_dev 结构体;
  2. 设置 input_dev;
  3. 注册 input_dev:input_register_device(input_dev);
  4. 在中断函数中上报事件:input_event()

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对 嵌入式系统和开源软件 感兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:嵌入式系统砖家,一起来学习吧,关注或转发都是是对作者最大的支持,谢谢大家,祝工作顺利~

这是一篇有趣的文章吗?

欢迎关注我的其它发布渠道