将添加的系统调用写成一个内核模块_Linux input子系统详解

点击蓝字

关注我们

第一篇:Linux input子系统的概念

linux系统输入设备繁多,例如按键、键盘、触摸屏、鼠标。这些输入设备都属于字符设备。不过这些输入设备不同类型,不同原理,不同的输入输出信息。那么是如何统一这些输入设备的呢?

答案:linux中将所有的输入设备抽象出input子系统这套软件体系,提供了统一的接口函数,实现了大统一。

input子系统分为三层:

1. 输入子系统设备驱动层 。2. 输入子系统核心层3. 输入子系统事件处理层

其中:

1. 设备驱动层提供对硬件寄存器的读写访问和将底层硬件对用户输入访问的相应转换为标准的输入事件,在通过核心层提交给事件处理层。2. 核心层对下提供了设备驱动层的编程接口,对上提供了事件层的编程接口。3. 事件层为用户的应用程序提供了统一的设备访问接口,和驱动层提交的事件处理。

86bfd47548e4b85f0ed5ba2851a3a9d1.png

输入子系统使得我们输入设备的驱动部分 不再关心对设备文件的操作,只需要关心对各个硬件寄存器的操作和提交相应的输入事件 。

综上所述:在linux中,输入子系统作为一个内核模块存在,向上为用户层提供接口函数,向下为驱动程序提供统一的接口函数。这样对我们的构建驱动程序非常简单灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。这样就能够将输入设备的事件通过输入子系统发送给应用层的应用程序,应用程序也可以通过输入子系统通知驱动程序完成某些任务。

590b8101c5dcc6a5217a063efe787021.png

第二篇:Linux input子系统的代码分析(input core)

input子系统的核心代码:drivers\input\input.c。

《一》

input模块的入口函数:如图1-1,入口函数通过调用register_chrdev()注册一个字符设备。其中

①  INPUT_MAJOR:主设备号:13(如图1-2,定义在include\linux\major.h下)。

②  &input_fops:file_operations结构体(如图1-3)。会发现结构体只有open函数被注册(input_open_file()),跟我之前文章写的字符设备不一样,没有了read,write函数。怎么回事?

313953e176fdf1a247851b3e2f59d900.png

图1-1

0894fb3267236a4ed25bf4ade62ef33f.png

图1-2

ab502602ee7830fe625fa91bcf080147.png

图1-3

《二》

file_operations结构体只有open函数,那么就看看open函数究竟做了什么?input_open_file()函数的实体如图2-1。其中

①  iminor(inode):函数调用了函数MINOR(inode->i_rdev)(其中iminor函数原型如图2-2,通过获取子设备号左移5位后,获取挂载的input设备驱动的数组号,从而获得input设备驱动的input_handler。input_handler结构体,如图2-3所示,其中需要关乎结构体中的成员:eventconnect,和file_operations

②  如果handler存在,说明挂载了这个设备驱动。然后获取handler的成员fops赋值给new_fops(其中,在第①点中提到要关乎input_handler结构体成员file_operations)。

③  将新的new_fops赋值给file->f_op,此时的input子系统的file_operations为新挂载的input设备的file_operations。

④  调用新挂载的input设备的open函数。

⑤如果打开失败,input子系统的file_operations将使用回旧的file_operations,所以第③点,做了保存机制,保存了旧的file_operations。

bb76c57f4cef7f4149eb847042b0b1e4.png

图2-1

490e942cc761a2de9d69a8386cd0ab48.png

图2-2

23c1b00025a6b2cc681d7db1e1657470.png

图2-3

《三》

input_table[]数组从以上的代码中都没有赋值,那么他在哪里赋值的呢?在drivers\input\input.c中,input_table[]是静态全局变量,所以只需要在input.c中查找,可以发现在input_register_handler()函数中可以看到input_table[]有赋值,如图3-1。

①  判断input_handler驱动处理程序是否存在,不存在则将handler赋值给input_table[]中。

②  并将其添加到input_handler_list链表中。

③  对每一个的input_dev,调用input_match_handler(),判断input_handler是否有支持input_dev。

9a3169889c54a1a25a58126720c75171.png

图3-1

通过查找可以发现,如图3-2 所示,有键盘设备,鼠标设备等通过 input_register_handler() 注册了设备处理函数。

4217400bd453bd07e9fbef41ce4e86cd.png

图3-2

《四》

这里我们以evdev.c(事件设备)来讲解如何注册handler??

在evdev.c中入口函数中(图4-1)通过input_register_handler()函数,注册了一个结构体evdev_handler(图4-2).

①  fops:注册了file_operations结构体(图4-3),似曾相识,跟我们之前的注册的字符设备的结构很像,文件的操作。

②  minor:子设备号(evdev:64),用于上面说到input_table[]数组中。

③  id_table:用来和input_dev匹配(图4-4),从注释上可以获知,支持所有的输入设备。

④  event:从字面意思理解就是事件处理函数,下面将进一步讲解这个函数。

⑤connect:在通过input_register_handler()注册handler时,会调用input_match_handler()进行匹配(图3-1),如果匹配成功会调用,handler->connect(handler,dev, id)。

ad79129b44f85508c8108641d5df692d.png

图4-1

fdf1a180b2f379ee2a0ef1dcdba4e177.png

图4-2

768c033bd885d63ba0f56f1cbca1733a.png

图4-3

4ee3faa870b640e2470ae3e7433509a4.png

图4-4

《五》

在上一篇文章中,有说到核心层对下提供设备驱动的编程接口,对上提供事件层的编程接口。在《三》和《四》中,我们写到事件层接口的实现,那么接下在讲解一下设备驱动的编程接口。

2d4c07ed6bfa8653d7758af3e9318e42.png

图5-1

图 5-1 是上一篇文章写到的内容,我们将红框的文字转为图5-2 所示。这样我们进一步了解 input_dev 和 input_handler 的关系。

29ac67e5e61969bc8841f381c1bce818.png

图5-2

在drivers\input\input.c中,我们看到提供给input_dev的接口为input_register_device(),函数实体(图5-3)。通过input_register_device()函数注册一个驱动设备,然后加到input_handler_list链表中,对每一个的input_handler,调用input_match_device (),判断input_dev是否有支持input_handler。

128e319580d3a4c757d5442011e21c17.png

图5-3

在图3-1中,注册handler的时候,对每一个的input_dev,调用input_match_device(),判断input_handler是否有支持input_dev。在图5-3,对每一个的input_handler,调用input_match_device (),判断input_dev是否有支持input_handler。

显然,你会发现跟平台总线很像,字符设备通过platform_match()函数设备和驱动进行匹配。而input子系统通过调用input_match_device ()函数将input_dev和input_handler进行匹配。

         在平台总线上不管是注册设备先还是注册驱动,都可以。其实input子系统也一样,驱动跟handle的注册也是没有优先顺序的。

129734e1d10c5e092929d76c51cfa536.png

图5-4

《六》

Input 子系统中注册 input_dev 和注册 input_handler 都调用了 input_match_device() 函数。那么我们来看看这个函数实现了什么?图6-1 为 input_match_device () 函数的实体。

在《四》中,我们以evdev.c(事件设备)。在图4-4中,我们可以看到input_device_id只注册了driver_info,所以我们前面四个if可以不解读。直接看看MATCH(),MATCH是一个宏,结构如图6-2,我们以图6-1中红框的evbit为例,可以将图6-2改写为图6-3,如果dev支持某一种事件类型,则会将dev->bit[0]中置1。

在图3-1 中的 input_match_device(handler->id_table,dev) ,传入的参数 handler->id_table ,我们看看evdev.c (事件设备)的id_table 赋值了什么?可以看到图4-2 和图4-4 。 handler->id_table->evbit[0] 等成员全部都为0 ,所以0& 任何数都为0 ,0 != 0 不成立,所以不会跳出循环,返回id ,匹配成功。

0f0d6a90711e6d5a222bfc4fa58e0fd7.png

图6-1

73cd5a8c3c645ab38179b97e1c418ffb.png

图6-2

5e45b494d75f95e24a91f1dd83f29121.png

图6-3

我们看图3-1和图5-3,当匹配成功,则会调用 handler的connect 函数。

《七》

图7-1所示为evdev.c(事件设备)的connect()函数实体。dev和handler通过一个中间件hande连接起来。通过devfs_mk_cdev()函数创建设备文件。然后创建一个简单类。

291684ee36dbd272f2b9c8f743b6ebe3.png

图7-1

《八》

         最后还有一个关键的函数接口input_event(),它用来接收应用层产生的事件。input_event()函数的实体如图8-1。红框部分可以看出,驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用input_handler的.event事件函数。

919a934597e226562ac69dd8a797238c.png

图8-1

第三篇:Linux input子系统的驱动程序编写

input 子系统的驱动编写要点:

1.分配input_dev结构体(函数:struct input_dev *input_allocate_device(void))

2.注册input设备(函数:int input_register_device(struct input_dev *dev))

3.注销input设备(函数:void input_unregister_device(struct input_dev *dev))

4.设置input设备支持的事件类型、事件码、事件值、input_id等信息。

5.在发生输入事件时,先上报告事件。

input设备是使用input_dev结构体描述,使用input子系统实现输入设备驱动,驱动的核心是向系统报告输入事件,不在关心文件操作接口,驱动报告的事件经过input核心层,input handler最终到达用户空间。从这句话中,可以看出input子系统的驱动部分会变得简单。

input子系统的驱动还是比较简单的,因为大部分工作,都在input核心层,input handler做完了。input驱动代码,我是在之前文章《linux 中断机制》和input子系统的驱动编写要点结合进行修改的。你会发现代码很简单。

上面说到,input 设备是使用 input_dev结构体 来描述。下图为input_dev 中我们比较关乎的主要内容。

16263a586f3a439be2335d85429a1946.png

evbit 能产生的那些事件类型:

09dd283484c1b81ca8ee450b5432a2d8.png

这些事件类型对应键值:

f3bd3bbf097982d17f46cbcccdadeed3.png

驱动代码讲解:

入口函数:

a7c287014042ee1a7bf5e4139850112d.png

  1. 首先使用函数:input_allocate_device()分配一个input_dev结构体。

  2. 通过函数:set_bit()设置设备支持的事件类型,和支持的事件码。

  3. 通过函数:input_register_device(),注册input设备。

  4. 注册中断。

出口函数:

bd65f483a86d3643157625ac9278398c.png

  1. 注销中断。

  2. 通过函数:input_unregister_device(),注销input设备。

  3. 通过函数:input_free_device(),释放input_dev内存。

中断服务函数:

1ccb3f50f1c778724d928a4b735c3b2c.png

当按键按下时,进入中断服务程序,然后根据键值通过函数:input_event()上报事件类型,事件码,事件值。通过函数:input_sync()发出同步信号。

其中:

事件码(code):时间的代码,如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件。

事件值(value):事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。

input_sync():用于事件同步,它告知事件的接收者:驱动已经发出了一个完整的报告。下图为函数:input_sync的原型。可以看出实际也是向上报告一个事件。

698256f0372c2092f7ee3a73564ca41f.png

测试应用程序:

#include #include #include #include #include #include #include #include #include #include  int main(void) {   int buttons_fd;   int key_value,i=0,count;   struct input_event ev_key;    buttons_fd = open("/dev/event1", O_RDWR);    if (buttons_fd < 0) {     perror("open device buttons");     exit(1);   }   while(1) {     count = read(buttons_fd,&ev_key,sizeof(struct input_event));     for(i=0; iint)count/    if(EV_KEY==ev_key.type)       printf("type:%d,code:%d,value:%d\n", ev_key.type,ev_key.code,ev_key.value);     if(EV_SYN==ev_key.type)       printf("syn event\n\n");   }   close(buttons_fd);   return 0; }

测试结果:

2466cb12c3573eb640ac1e311e1251fc.png

f055afe71228a7baba95a37e2b35e377.png

推荐阅读

【1】 100ASK_IMX6ULL arm板子如何显示图片、汉字、划线、背景色 【2】 到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?一文帮你梳理基础概念【科普】 必读 【3】搞懂进程组、会话、控制终端关系,才能明白守护进程干嘛的? 【4】鸿蒙系统HarmonyOS实现点亮LED 【5】Linux信号处理机制详解

本公众号全部原创干货已整理成一个目录,点击「干货即可获得。

后台回复「进群」,即可加入技术交流群,进群福利:免费赠送Linux学习资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值