整个输入子系统的框架主要分为三个部分,如下图所示:
下面是具体的阐述:
首先从核心文件drivers/input/input.c中的入口函数分析:
input_init:
// 向系统中注册设备,fops使用input_fops
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
下面我们着重看一下该文件中定义的file_operations结构体:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
可见这个结构体定义非常简单,只有一个open函数,要想实现对输入
设备的一系列操作,该fops显然不可能完成,那么系统又是怎样处理
的呢?
猜测:我们猜测系统可能是在open函数中做了进一步的处理,比如说
重新绑定了一个file_operations结构体。
下面我们来看open函数:
input_open_file:
// 由设备的次设备号在input_table中获取一个处理句柄handler
handler = input_table[iminor(inode) >> 5];
// 获取新的fops
if (handler)
new_fops = fops_get(handler->fops);
// 重新绑定新的fops
file->f_op = new_fops;
上面这段代码验证了我们的猜想,旧的fops只是一个引子,具体做事的
是handler中定义的fops。那么这个handler的作用是什么呢?由名字可
以猜测是一个句柄,帮着处理事情的,可以看成是一个中间工具。
handler结构体的详细描述:
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
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);
const struct file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
这个结构体里面成员的具体作用我们先放着,后面用到了再说。
思考:问题来了,这个input_table中的handler是在哪里定义并填充到
这个数组中来的呢?
下面我们就要看input_register_handler函数了:
input_register_handler:
// 将handler加入到input_table数组中
input_table[handler->minor >> 5] = handler
// 将handler加入到input_handler_list链表中
list_add_tail(&handler->node, &input_handler_list);
// 遍历input_dev_list链表,找到里面的每一个设备
list_for_each_entry(dev, &input_dev_list, node)
// 将该handler与其中的某个设备进行配对
input_attach_handler(dev, handler);
思考:上面我们讲到了handler与device的配对,handler结构体已经讲解
device设备又是哪里来的呢?
device设备主要是通过input_register_device函数注册进来的:
input_register_device:
// 将设备加入到input_dev_list链表中
list_add_tail(&dev->node, &input_dev_list);
// 遍历input_handler_list链表得到每一个handler
list_for_each_entry(handler, &input_handler_list, node)
// 将handler与device进行配对
input_attach_handler(dev, handler);
思考:上面两个注册函数非常对称,都用到了匹配,这个配对是怎样
实现的呢?答案在input_attach_handler函数中
input_attach_handler:
id = input_match_device(handler, dev);
for (id = handler->id_table; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
if (!handler->match || handler->match(handler, dev))
return id;
}
// 将handler与device进行连接,印证了我们最开始讲解的那副三角关系图
error = handler->connect(handler, dev, id);
上面的匹配过程也很简单,主要用到了handler结构体中的id_table成员,
id_table中有很多id选项,将其中的id与device结构体的id一一对比,
只要有一个id对的上,就表明该handler可以配对该设备,并返回id。
上面两个地方均进行匹配的好处:
不管是先注册的设备,还是先注册的handler,都会找到一个handler与
一个device进行配对,这个实现很巧妙。
上面将handler与device进行了连接connect,调用的是操作句柄handler
的connect函数,下面以一个具体的handler分析一下这个实现过程:
以drivers/input/evdev.c中的evdev_handler为例:
evdev_connect:
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
evdev->handle.dev = input_get_device(dev);
evdev->handle.handler = handler;
error = input_register_handle(&evdev->handle);
上面代码主要是构造了一个evdev结构体,这个结构体有一个关键的
成员handle,这个是将handler与device联系起来的关键,这个handle
对象同时指向了device与handler,也就是说通过handle对象既可以
找到handler也可以找到device
下面我们来看看input_register_handle函数做了什么:
input_register_handle:
// 将handle对象加入到dev->h_list链表中
list_add_rcu(&handle->d_node, &dev->h_list)
// 将handle对象加入到handler->h_list链表中
list_add_tail_rcu(&handle->h_node, &handler->h_list)
上面代码的目的很明确:这样做了之后,我们既可以通过handler找到
对应的device,也可以通过device找到对应的handler,这样handler
与device之间就建立了双向连接。
有了上面的三角关系,下面我们具体看驱动框架怎样读按键:
read:
evdev_read
// 判断读取的字节数是否正确
if (count < input_event_size())
return -EINVAL;
// 如果数据为空,且为非阻塞读取,则直接返回
if (client->head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
// 如果为阻塞读取,而此时无数据,则进入休眠
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
上面代码我们并不陌生,这是驱动中读函数惯用的处理方法,这里既然有个
休眠,那么在哪里进行唤醒呢?
唤醒是在evdev_event函数中:
evdev_event:
if (client)
evdev_pass_event(client, &event);
-->client->buffer[client->head++] = *event;
client->head &= EVDEV_BUFFER_SIZE - 1;
else
list_f
or_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);
wake_up_interruptible(&evdev->wait)
由上面的代码我们不难猜测出这是一个事件的处理函数,当有事件发生时
我们填充数据,并唤醒进程。
上面的定义的事件处理函数是纯软件层面的东西,那么由谁来调用这个
事件处理函数呢?在什么时候调用呢?其实这个不难猜测,我们在与
硬件有关的device代码中一旦获取到输入数据,就表示有事件发生,
我们就调用这个事件处理函数。
以drivers/input/keyboard/gpio_keys.c为例验证一下我们的想法:
gpio_keys_report_event:
input_event(input, type, button->code, !!state)
input_handle_event(dev, type, code, value)
// 如果dev->event设备中定义了event处理函数,就调用此事件函数
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
// 由设备找到handle
handle = rcu_dereference(dev->grab);
// 如果存在对应的handle,通过handle就能找到handler,调用其事件处理函数
if (handle)
handle->handler->event(handle, type, code, value);
上面代码涉及到的由device找到handle,由handle找到handler,这就是
为什么前面要将device与handler建立connect的原因,怎样建立connect
前面已经解释过了。
总结:
怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件