字符设备驱动之输入子系统分析(四)
作者:Bright-Ho
联系方式:836665637@qq.com
这一节主要讲解“设备硬件层”;这一层的内容就需要我们自己来实现;这里主要讲解框架;
在“核心层”里面会提供一个input_register_device()这样一个函数;
在“设备硬件层”先构造并初始化一个struct input_dev *dev结构,通过input_register_device()函数,来注册:
(1)把dev结构放入设备链表
list_add_tail(&dev->node, &input_dev_list);
(2)把“事件处理层”的handler链表里面每一项handler取出来,通过idtable和dev进行匹配;匹配成功,就调用input_handler里面的connect函数,建立“连接”;
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
从框架来讲,主要就是做这两件事情;
这里小小的总结一下,设备硬件层,核心层,事件处理层,各个层次,主要做了哪些工作?
设备硬件层:和具体硬件设备相关的,需要自己构造一个符合硬件设备特性的结构dev;并注册进设备链表;
设备硬件层:该层构造一个符合硬件设备的dev结构,并注册进dev链表;
核心层:该层会给设备驱动层,事件处理层,提供注册接口,以及匹配函数,等等;类似于一个工具箱,设备驱动层和事件处理层要使用什么接口,直接调用核心层提供的接口;
事件处理层:这一层也是构建一个符合事件处理的结构,并注册进事件处理的链表;并且提供设备接口给应用层;(该结构实现了file_operations里面的驱动接口,以便给应用层调用)
设备和事件通过id号,匹配成功后就会建立连接;一旦“连接”成功,应用层open,read,write一个设备节点,就能调用到“事件处理层”里面的驱动接口;
和之前我们写的字符设备的那5点相比,我们就能知道引入“输入子系统”后的字符设备之间差异;
差异:
(1)file_opreations结构不用我们自己实现了,“事件处理层”帮我们做好了;
(2)不用创建设备节点了;“事件处理层”帮我们做好了
最重要的是,简化了我们写设备驱动程序的难度;我们只关注设备驱动层,事件处理层和核心层,都由内核帮我们完成的;
现在以evdev.c中input_handler实现的connect函数为例,看看它是怎么建立“连接”的?
evdev_connect函数:
(1)内核空间分配一个evdev结构
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
(2)设置evdev结构
646 evdev->exist = 1;
647 evdev->minor = minor;
648 evdev->handle.dev = dev;
649 evdev->handle.name = evdev->name;
650 evdev->handle.handler = handler;
651 evdev->handle.private = evdev;
652 sprintf(evdev->name, "event%d", minor);
注意:这里handle.dev->dev(左边); handle.handler->handler(右边)
(3)创建设备节点
cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name);
注意:为什么会在这里创建设备?
之前写字符设备创建类和设备的时候都是一气呵成;其原因是设备驱动和事件处理驱动,都是自己一次性完成的,不存在设备匹配事件的过程;那么引入输入子系统后,在核心层input.c中,一开始就通过class_register(&input_class)来创建了设备;至于什么时候在设备下面创建节点?那就是当执行connnect函数的时候,肯定设备和事件已经匹配成功了;所以在连接函数中创建设备节点,才会使节点拥有设备信息!!!
(4)注册evdev中的handle
input_register_handle(&evdev->handle);
1223 int input_register_handle(struct input_handle *handle)
1224 {
1225 struct input_handler *handler = handle->handler;
1226
1227 list_add_tail(&handle->d_node, &handle->dev->h_list);
1228 list_add_tail(&handle->h_node, &handler->h_list);
1229
1230 if (handler->start)
1231 handler->start(handle);
1232
1233 return 0;
1234 }
重点:
这里dev链表和handler链表同时都指向handle结构,handle在设置的时候,里面的dev就指向connect函数传进来的dev设备(这个是自己实现的),里面的handler就指向connect函数传进来的handler处理事件(这个是事件处理层提供的,并且是与dev匹配成功的);这样dev设备和handler处理事件,就建立了联系!!!
以后dev设备就可以通过dev链表找到handle,再通过handle中的handler就可以直接找到具体的处理事件;反过来,handler处理事件就可以通过handler链表找到handle,再通过handle中的dev就可以直接找到具体的硬件设备;
这里再简要说明一下“事件处理层”input_handler.evdev_event事件上报函数;
evdev_event:该函数什么时候调用?
46 static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
47 {
...
54 do_gettimeofday(&client->buffer[client->head].time);
55 client->buffer[client->head].type = type;
56 client->buffer[client->head].code = code;
57 client->buffer[client->head].value = value;
58 client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1); ...
69
70 kill_fasync(&client->fasync, SIGIO, POLL_IN);
73 wake_up_interruptible(&evdev->wait);
74 }
那么根据该函数实现的内容来看,数据在环形缓冲区中准备好了,就来唤醒等待队列中的进程;有唤醒操作,前提是有进程会休眠,那么是哪个进程进入休眠了呢?
我们可以看“事件处理层”中input_handlr.evdev_fopse.vdev_read,也就是实现驱动接口的读函数;
281 static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
282 {
…
287 if (count < evdev_event_size())
288 return -EINVAL;
289
290 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
291 return -EAGAIN;
292
293 retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist); ...
312 return retval;
313 }
当应用程序调用read函数去去读取硬件设备的数据的时候,会调用到“事件处理层”里面的提供的read驱动接口函数,该函数发现环形缓冲区没有数据,并且以阻塞方式打开该设备接口时(注意看上面红色代码),就会陷入休眠,等待数据的到来;我们分析了evdev_event事件上报函数是数据准备好了用来换唤醒等待队列的进程,那么关键问题来了,是谁来调用这个事件上报函数呢?我们写按键驱动的时候就知道,当按键按下的时候,会产生按键数据,同时会产生按键中断,并进入按键中断服务程序,在中断程序中,采集数据,并唤醒休眠的应用程序,告诉应用程序,数据已经准备好了;所以说这里evdev_event事件处理函数会被中断处理程序调用;
例如:内核自带的按键驱动例子
30 static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
31 {
32 int i;
33 struct platform_device *pdev = dev_id;
34 struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
35 struct input_dev *input = platform_get_drvdata(pdev);
36
37 for (i = 0; i < pdata->nbuttons; i++) {
38 struct gpio_keys_button *button = &pdata->buttons[i];
39 int gpio = button->gpio;
40
41 if (irq == gpio_to_irq(gpio)) {
42 unsigned int type = button->type ?: EV_KEY;
43 int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;
44
45 input_event(input, type, button->code, !!state);
46 input_sync(input);
47 }
48 }
49
50 return IRQ_HANDLED;
51 }
input_event就是上报事件函数;原型如下:
46 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) {
...
192 list_for_each_entry(handle, &dev->h_list, d_node)
193 if (handle->open)
194 handle->handler->event(handle, type, code, value);
}
该中断函数,最终会调用到“事件处理层”提供的事件上报函数event;在环形缓冲区中准备号数据,并唤醒等待队列中的应用程序;
目前软件框架讲解的差不多了,现在应该知道,引入“输入子系统”的字符设备驱动,哪些是由自己完成的,哪些是由内核帮我们完成了;注意了,这里只是分析了软件框架,硬件设备原理相关的内容,会在后面根据具体的硬件设备进行详细的分析;