点击蓝字
关注我们
linux系统输入设备繁多,例如按键、键盘、触摸屏、鼠标。这些输入设备都属于字符设备。不过这些输入设备不同类型,不同原理,不同的输入输出信息。那么是如何统一这些输入设备的呢?
答案:linux中将所有的输入设备抽象出input子系统这套软件体系,提供了统一的接口函数,实现了大统一。
input子系统分为三层:
其中:
综上所述:在linux中,输入子系统作为一个内核模块存在,向上为用户层提供接口函数,向下为驱动程序提供统一的接口函数。这样对我们的构建驱动程序非常简单灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。这样就能够将输入设备的事件通过输入子系统发送给应用层的应用程序,应用程序也可以通过输入子系统通知驱动程序完成某些任务。
第二篇: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函数。怎么回事?
图1-1
图1-2
图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所示,其中需要关乎结构体中的成员:event,connect,和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。
图2-1
图2-2
图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。
图3-1
图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)。
图4-1
图4-2
图4-3
图4-4
《五》
在上一篇文章中,有说到核心层对下提供设备驱动的编程接口,对上提供事件层的编程接口。在《三》和《四》中,我们写到事件层接口的实现,那么接下在讲解一下设备驱动的编程接口。
图5-1
图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。
图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的注册也是没有优先顺序的。
图5-4
《六》
在《四》中,我们以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。
图6-1
图6-2
图6-3
《七》
图7-1所示为evdev.c(事件设备)的connect()函数实体。dev和handler通过一个中间件hande连接起来。通过devfs_mk_cdev()函数创建设备文件。然后创建一个简单类。
图7-1
《八》
最后还有一个关键的函数接口input_event(),它用来接收应用层产生的事件。input_event()函数的实体如图8-1。红框部分可以看出,驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用input_handler的.event事件函数。
图8-1
第三篇:Linux 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子系统的驱动编写要点结合进行修改的。你会发现代码很简单。
evbit 能产生的那些事件类型:
这些事件类型对应键值:
驱动代码讲解:
入口函数:
首先使用函数:input_allocate_device()分配一个input_dev结构体。
通过函数:set_bit()设置设备支持的事件类型,和支持的事件码。
通过函数:input_register_device(),注册input设备。
注册中断。
出口函数:
注销中断。
通过函数:input_unregister_device(),注销input设备。
通过函数:input_free_device(),释放input_dev内存。
中断服务函数:
当按键按下时,进入中断服务程序,然后根据键值通过函数: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的原型。可以看出实际也是向上报告一个事件。
测试应用程序:
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; i<(int)count/sizeof(struct input_event); i++)
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;
}
测试结果:
推荐阅读
本公众号全部原创干货已整理成一个目录,点击「干货」即可获得。
后台回复「进群」,即可加入技术交流群,进群福利:免费赠送Linux学习资料。