详细了解Linux设备模型中的input子系统

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解详细了解Linux设备模型中的input子系统问题。 本节从整体上解释输入子系统的框架结构。有助

大家好,我是本期栏目编辑小友,现在为大家讲解详细了解Linux设备模型中的input子系统问题。

本节从整体上解释输入子系统的框架结构。有助于读者从整体上了解linux的输入子系统。在代码分析的过程中,我们可以通过这一节的知识找到正确的方向,理解原理。

本节的重点是:

输入子系统的框架结构。

每一层都对应内核中的文件位置。

输入子系统的事件处理机制。

进入子系统驱动层的基本操作流程。

输入子系统驱动程序层的常用功能。

本节中的困难:

输入子系统的事件处理机制。

进入子系统的驱动工作流程。

1了解linux输入子系统。

linux输入子系统自上而下由三层实现,即输入子系统EventHandler、输入子系统核心和输入子系统设备驱动层。

对于输入子系统的设备驱动层,主要实现对硬件设备的读写访问、中断设置,将硬件产生的事件转化为核心层定义的规范,提交给事件处理层。

对于核心层,它为设备驱动程序层提供规范和接口。只要设备驱动层关心如何驱动硬件并获取硬件数据(如按键数据),然后调用核心层提供的接口,核心层就会自动将数据提交给事件处理层。

就事件处理层而言,是用户编程的接口(设备节点),处理驱动层提交的数据处理。

linux输入子系统的框架结构如下图1所示。

图1 Linux输入子系统的框架。

上图展示了linux输入子系统的层次结构。

/dev/input目录显示了在内核中注册的设备编程接口。用户可以打开这些设备文件,打开不同的输入设备进行硬件操作。

事件处理层为不同的硬件类型提供用户访问和处理接口。例如,当我们打开device/dev/input/mices时,我们会在事件处理层调用Mouse Handler来处理输入事件,这也使得设备驱动层不必关心设备文件的操作,因为Mouse Handler已经有了相应的事件处理方法。

输入子系统由内核代码drivers/input/input.c组成,屏蔽了用户和设备驱动交互的细节,为设备驱动层和事件处理层之间的通信提供了统一的接口。

下图简要描述了linux输入子系统的事件处理机制:

图2 Linux输入子系统的事件处理机制。

上图显示了输入子系统核心层提供的支持,以及如何向输入事件驱动程序报告事件。

作为输入设备的驱动程序开发人员,您需要执行以下步骤:

?在驱动程序加载模块中,设置输入设备支持的事件类型。类型请参考表1。

?例如,要注册中断处理功能,键盘设备需要写上下键,触摸屏设备需要写向下、向上和绝对移动,鼠标设备需要写点击、向上和相对移动,以及硬件数据(键值/坐标/状态等)。)需要在必要时提交。

?将输入设备注册到输入子系统中。

表1 Linux输入子系统支持的数据类型。

EV_SYN0x00

 同步事件

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

void input_sync(struct input_dev *dev);  

2    输入设备驱动的简单案例

在Linux内核文档的documentation/input下,有一个input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。

提供的案例代码描述了一个button设备,产生的事件通过BUTTON_PORT引脚获取,当有按下/释放发生时,BUTTON_IRQ被触发,以下是驱动的源代码:

[cpp] view plaincopy

#include                                                                                                           

#include   

#include   

#include   

#include   

staTIc struct input_dev *button_dev;  

staTIc void button_interrupt(int irq, void*dummy, struct pt_regs *fp)  

{  

input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);  

input_sync(button_dev);  

}        

staTIc int __init button_init(void)  

{  

int error;  

if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) {  

printk(KERN_ERR"button.c: Can't allocate irq %d\n", button_irq);  

return -EBUSY;  

}        

button_dev = input_allocate_device();  

if (!button_dev) {  

printk(KERN_ERR"button.c: Not enough memory\n");  

error = -ENOMEM;  

goto err_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_ERR"button.c: Failed to register device\n");  

goto err_free_dev;  

}  

return 0;  

err_free_dev:  

input_free_device(button_dev);  

err_free_irq:  

free_irq(BUTTON_IRQ, button_interrupt);  

return error;  

}  

staTIc void __exit button_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

static struct input_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坐标完整地传递给输入子系统。

 

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。