字符设备驱动之输入子系统分析(四)

发布时间 2023-07-12 17:47:40作者: Bright-Ho~蜗牛~

作者:Bright-Ho

联系方式:836665637@qq.com


这一节主要讲解“设备硬件层”;这一层的内容就需要我们自己来实现;这里主要讲解框架;


在“核心层”里面会提供一个input_register_device()这样一个函数;


在“设备硬件层”先构造并初始化一个struct input_dev *dev结构,通过input_register_device()函数,来注册:

1dev结构放入设备链表

list_add_tail(&dev->node, &input_dev_list);

2把“事件处理层”的handler链表里面每一项handler取出来,通过idtabledev进行匹配;匹配成功,就调用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点相比,我们就能知道引入“输入子系统”后的字符设备之间差异;

差异:

1file_opreations结构不用我们自己实现了,“事件处理层”帮我们做好了;

2不用创建设备节点了;“事件处理层”帮我们做好了


最重要的是,简化了我们写设备驱动程序的难度;我们只关注设备驱动层,事件处理层和核心层,都由内核帮我们完成的;


现在以evdev.cinput_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;在环形缓冲区中准备号数据,并唤醒等待队列中的应用程序;


目前软件框架讲解的差不多了,现在应该知道,引入“输入子系统”的字符设备驱动,哪些是由自己完成的,哪些是由内核帮我们完成了;注意了,这里只是分析了软件框架,硬件设备原理相关的内容,会在后面根据具体的硬件设备进行详细的分析;