本节从整体上讲解了输入子系统的框架结构。有助于读者从整体上认识linux的输入子系统。在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理。
本节重点:
输入子系统的框架结构
各层对应内核中的文件位置
输入子系统的事件处理机制
输入子系统的驱动层基本操作流程
输入子系统的驱动层常用函数
本节难点:
输入子系统的事件处理机制
输入子系统的驱动工作流程
1 初识linux输入子系统
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(eventhandler)、输入子系统核心层(inputcore)和输入子系统设备驱动层。
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。
对于linux输入子系统的框架结构如下图1所示:
图1 linux输入子系统框架结构
由上图所展现的内容就是linux输入子系统的分层结构。
/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的mouse handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为mouse handler已经有了对应事件处理的方法。
输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
下图2简单描述了linux输入子系统的事件处理机制:
图2 linux输入子系统事件处理机制
由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。
作为输入设备的驱动开发者,需要做以下几步:
?在驱动加载模块中,设置你的input设备支持的事件类型,类型参见表1设置
?注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
?将输入设备注册到输入子系统中
表1 linux输入子系统支持的数据类型
ev_syn 0x00同步事件
ev_key 0x01按键事件
ev_rel 0x02相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
ev_abs 0x03绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
ev_msc 0x04其它
ev_sw 0x05开关
ev_led 0x11按键/设备灯
ev_snd 0x12声音/警报
ev_rep 0x14重复
ev_ff 0x15力反馈
ev_pwr 0x16电源
ev_ff_status 0x17力反馈状态
ev_max 0x1f事件类型最大个数和提供位掩码支持
由表1可知,设备所能表示的事件种类,一个设备可以选择一个或多个事件类型上报给输入子系统。
linux输入子系统提供了设备驱动层上报输入事件的函数,在include/linux/input.h中:
voidinput_report_key(struct input_dev *dev, unsigned int code, int value); //上报按键事件
voidinput_report_rel(struct input_dev *dev, unsigned int code, int value); //上报相对坐标事件
voidinput_report_abs(struct input_dev *dev, unsigned int code, int value); //上报绝对坐标事件
……
当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:
[cpp]view plaincopy
voidinput_sync(structinput_dev*dev);
2 输入设备驱动的简单案例
在linux内核文档的documentation/input下,有一个input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。
提供的案例代码描述了一个button设备,产生的事件通过button_port引脚获取,当有按下/释放发生时,button_irq被触发,以下是驱动的源代码:
[cpp]view plaincopy
#include
#include
#include
#include
#include
staticstructinput_dev*button_dev;
staticvoidbutton_interrupt(intirq,void*dummy,structpt_regs*fp)
{
input_report_key(button_dev,btn_1,inb(button_port)&1);
input_sync(button_dev);
}
staticint__initbutton_init(void)
{
interror;
if(request_irq(button_irq,button_interrupt,0,button,null)){
printk(kern_errbutton.c:can'tallocateirq%d\n,button_irq);
return-ebusy;
}
button_dev=input_allocate_device();
if(!button_dev){
printk(kern_errbutton.c:notenoughmemory\n);
error=-enomem;
gotoerr_free_irq;
}
button_dev->evbit[0]=bit(ev_key);
button_dev->keybit[long(btn_0)]=bit(btn_0);
error=input_register_device(button_dev);
if(error){
printk(kern_errbutton.c:failedtoregisterdevice\n);
gotoerr_free_dev;
}
return0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(button_irq,button_interrupt);
returnerror;
}
staticvoid__exitbutton_exit(void)
{
input_unregister_device(button_dev);
free_irq(button_irq,button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
编写基于输入子系统的设备驱动程序需要包含,因为它包含了输入子系统的接口和所有的宏定义,这些内容在编写输入设备驱动程序时需要用到。
button_init函数说明:
当模块加载(insmod)或内核引导过程中,button_init函数会被调用。首先做的工作是获取能够正确控制硬件设备的硬件资源(例如内存、io内存、中断和dma),在代码中button_irq作为button设备的中断资源,通过request_irq()函数被申请注册。当有按键按下/释放时,调用button_interrupt()中断处理函数获取按键值button_port(button设备的i/o资源)。
那么输入子系统怎么能够知道这个设备为输入设备呢?通过第8行为设备定义一个用于描述一个输入设备对象。
[cpp]view plaincopy
staticstructinput_dev*button_dev;
定义了button_dev之后,如何通知输入子系统有新的输入设备了呢?或者说如何把一个新的输入设备加入到输入子系统中呢?可以通过输入子系统核心层input.c中提供的函数分配一个输入设备,在代码的第25行。
[cpp]view plaincopy
button_dev=input_allocate_device();
有了输入设备的描述,当事件产生时,输入子系统怎么能够知道设备产生的事件类型呢?通过32和33行的代码。
[cpp]view plaincopy
button_dev->evbit[0]=bit(ev_key);
button_dev->keybit[long(btn_0)]=bit(btn_0);
其中evbit和keybit成员分别代表设备产生的事件类型和上报的按键值。其中输入子系统的一些位操作nbits、bit、long经常被用到:
[cpp]view plaincopy
#definenbits(x)(((x)/bits_per_long)+1)//通过位x获取数组的长度
#definebit(x)(1ul<<((x)%bits_per_long))//返回位x在数组中的位域
#definelong(x)((x)/bits_per_long)//返回位x的索引
以上的工作做完之后,即可注册为输入设备了,代码的35行。
[cpp]view plaincopy
input_register_device(button_dev);
这个函数把button_dev输入设备挂入输入设备链表中,并且通知事件处理层调用connect函数完成设备和事件处理的绑定,当用户打开设备时,便能够调用到相应的事件处理接口获得硬件上报的数据了。input_register_device()函数是会睡眠的函数,因此不能够在中断上下文和持有自旋锁的代码中调用。
当我们把上面的工作做完之后,设备驱动中唯一值得关注的就是button_interrupt()中断处理函数了。当按键动作发生,button_interrupt()函数被调用,完成事件的上报由其中的两条语句完成。
[cpp]view plaincopy
input_report_key(button_dev,btn_1,inb(button_port)&1);
input_sync(button_dev);
其中input_report_key上报了这是一个按键事件,且它的值为inb(button_port) & 1,由于案例代码只产生一个按键的值,因此input_sync()在这里不起关键作用。但如果是一个触摸屏,即有x坐标和y坐标,则需要通过input_sync()函数把x和y坐标完整地传递给输入子系统。
锂电池价格走势分析,锂电池在可再生能源行业中具有广泛的应用前景
广电计量和广州检验中心建立相应合作机制
宇凡微2.4G遥控器方案开发,灵活控制无需指向
美光发布全球第一个1TB的microSD
基于ADAM5510-E/TCP实现动力系统训练模拟器的设计
详细了解Linux设备模型中的input子系统
【USI】琻捷黑科技:通用传感器接口芯片SNU301
微调电容电路符号_微调电容电路的识读方法
无人机送菜服务落地将面临哪些阻碍?
芯盟微与飞腾深化生态合作,助力国产信创产业生态建设
5G先行者成炮灰,信号覆盖范围有限、网速缓慢引吐槽
有线电视三年内实现互联网接入不靠谱
中国IBC电池跻身国际前列,赶超美日韩还要多久
晶闸管整流器的原理和整流流程_晶闸管整流器的主要形式
中国移动蔡亚莉:打造以通话为入口的平台产品,共创新通话时代
OnRobot参展第22届工博会 展示一站式协作应用产品和解决方案
大疆无人机助力桂林警方团灭毒贩
影响工业物联网水平的关键因素:高定位/高传输/高效能
常用Linux命令介绍
《物流、仓储和快递领域应用的移动机器人技术及市场-2022版》