当前位置: 首页 > 科技观察

Linux输入子系统编程、分析、模板

时间:2023-03-13 06:52:13 科技观察

输入设备有共性:中断驱动+字符IO,基于分层的思想,Linux内核抽取了这些设备的公共部分,提供基于cdev的接口,以及设计inputSubsystem,所有使用input子系统构建的设备都使用主设备号13,input子系统还支持自动创建设备文件。这些文件是在“/dev/input/”下使用阻塞IO读写方式创建的。如下所示。内核中的输入子系统从下到上分为设备驱动层、输入核心层、事件处理层。由于每个输入设备上报的事件不同,为了应用层能够很好地识别上报的事件,内核还封装了一个标准接口供应用层描述一个事件。这些接口在“/include/upai/linux/input”中。设备驱动层是与具体硬件相关的实现,也是驱动开发的主体部分。输入核心层主要提供一些API供设备驱动层调用,设备驱动层通过这些API上报的数据可以传递给事件处理层。处理层负责创建设备文件并将报告的事件传递给用户空间。输入对象描述了一个输入设备,包括它可能报告的事件。这些事件使用位图进行描述。内核提供的相应工具帮助我们构建一个输入对象,可以参考内核文档“Documentation/input/input-programming.txt”,里面对输入子系统的使用有详细的说明。//输入设备对象structinput_dev{constchar*name;unsignedlongevbit[BITS_TO_LONGS(EV_CNT)];unsignedlongkeybit[BITS_TO_LONGS(KEY_CNT)];unsignedlongrelbit[BITS_TO_LONGS(REL_CNT)];unsignedlongabsbit[BITS_TO_LONGS(ABS_CNT_CNT)]Cbit[BITS_TO_LONGS(ABS_CNTm)]Cbit[BITS_CNTm)];unsignedlongledbit[BITS_TO_LONGS(LED_CNT)];unsignedlongsndbit[BITS_TO_LONGS(SND_CNT)];unsignedlongffbit[BITS_TO_LONGS(FF_CNT)];unsignedlongswbit[BITS_TO_LONGS(SW_CNT)];unsignedlongkey[BITS_TO_LONGS(KEY_CNT)];unsignedLongsLEDS(KEY_CNT)ndS_LED_BIunledClongled[BITS_TO_LONGS(SW_CNT)][BITS_TO_LONGS(SND_CNT)];unsignedlongsw[BITS_TO_LONGS(SW_CNT)];structinput_handle__rcu*grab;structdevicedev;structlist_headh_list;structlist_headnode;};structinput_dev--122-->这个名字不是设备名,输入子系统的设备名是在源码中指定的子系统,不是这个。--129-->设备支持的输入事件位图,EV_KEY,EV_REL等--130-->对于按键事件,设备支持的输入子事件位图--132-->对于相对坐标事件,设备支持的相对坐标子事件位图--133-->对于绝对坐标事件,设备支持的绝对坐标子事件位图--134-->设备支持的子事件位图mixeddevice--180-->表示这是一个设备。--182-->h_list是用来链接相关句柄的链表--183-->node是用来链接其他input_devallocation/release//drivers/input/input.c//createaninputobject的链表structinput_dev*input_allocate_device(void);//释放一个输入对象voidinput_free_device(structinput_dev*dev);初始化初始化一个输入对象是使用输入子系统编写驱动程序的主要工作,内核在头文件“include/uapi/linux/input.h”中指定了一些常用输入设备的常用输入事件,这些宏和数组是我们初始化输入对象的工具。这些宏在用户空间事件解析和驱动程序事件注册中都用到了,可以看作是驱动程序和用户空间之间的一种通信协议,所以理解其含义非常重要。在输入子系统中,每一个事件的发生都用事件(type)->子事件(code)->value(值)三个层次来描述,例如keyevent->keyF1subevent->keyF1sub事件触发器的值为高电平1。注意事件和子事件与值是相辅相成的。只有注册了事件EV_KEY,才能注册子事件BTN_0,只有注册才有意义。下面是内核约定的事件类型,对应应用层事件对象的type字段。这些是关键子事件的类型。可以看到PC键值的定义。除了描述普通事件外,内核还提供工具将这些事件正确填充到描述输入对象中事件的位图中。//***种类//这个方法很适合同时注册多个事件button_dev->evbit[0]=BIT_MASK(EV_KEY);button_dev->keybit[BIT_WORD(BTN_0|BTN_1)]=BIT_MASK(BTN_0|BTN_1);注册/注销输入对象初始化后,接下来需要注册到内核。Layerreportevent在合适的时候(由于输入最终是由中断来表示的,通常是在驱动程序的中断处理程序中),驱动程序可以上报注册的事件,也可以同时上报多个事件。下面是内核提供的API//上报指定事件+子事件+值voidinput_event(structinput_dev*dev,unsignedinttype,unsignedintcode,intvalue);//上报key值voidinput_report_key(structinput_dev*dev,unsignedintcode,intvalue);//上报绝对坐标voidinput_report_abs(structinput_dev*dev,unsignedintcode,intvalue);//上报同步事件voidinput_report_rel(structinput_dev*dev,unsignedintcode,intvalue);//同步所有上报voidinput_sync(structinput_dev*dev);上报事件有两点需要注意:上报函数不是真正的上报,只是准备上报,sync会真正上报刚刚上报给inputcore的事件inputcore会做出裁决然后上报事件处理层,所以对于button事件,必须先报1再报0(或者反过来),不能只报1或者0,这样core会认为一个事件误触发了多次而只报一次,虽然我们真的按了很多次。应用层解析事件处理层最终会将驱动synconce时的所有上报事件组织成structinput_value[]的形式上报给应用层。应用层从对应的设备文件中获取上报的事件时,需要注意:接收到的数组元素个数会比底层多一个空元素,类似于写of_device_id[]时的空元素,应用层在解析时需要注意。事件处理层不缓存接收到的事件。如果有新事件到来,即使旧事件没有被读取,也会被覆盖,所以应用需要及时读取。前面提到,“include/uapi/linux/input.h”中的宏是应用层和驱动层共享的通信协议,所以应用层在解析接收到的structinput_value对象时,只需要“include"使用其中的宏。/**事件结构本身*/structinput_event{structtimevaltime;__u16type;__u16code;__s32value;};输入分析上文提到,输入子系统采用三层结构实现将驾驶事件传递到应用层。具体来说,这三层中的每一层都由一个结构链表组成。在设备驱动层,核心结构是input_dev;在输入核心层,是input_handle;在事件处理层,就是input_handler。内核通过链表和指针将三者结合在一起,最终实现了input_dev和input_handler之间的多对多映射关系。下图可以简要描述这种关系。模板下面的模板首先使用输入子系统上报按键事件,然后在应用层读取。输入按键设备驱动{key@26{compatible="xj4412,key";interrupt-parent=<&gpx1>;interrupts=<22>;};};staticstructinput_dev*button_dev;staticintbutton_irq;staticintirqflags;staticirqreturn_tbutton_interrupt(intirq,void*dummy){input_report_key(button_dev,BTN_0,0);input_report_key(button_dev,BTN_0,1);input_sync(button_dev);returnIRQ_HANDLED;}staticintbutton_init(void){request_irq(button_irq,button_interrupt,irqflags,"button",NULL));button_dev=input_allocate_device();button_dev->name="button";button_dev->evbit[0]=BIT_MASK(EV_KEY);button_dev->keybit[BIT_WORD(BTN_0)]=BIT_MASK(BTN_0);input_register_device(button_dev);return0;}staticintbutton_exit(void){input_free_device(button_dev);free_irq(button_irq,button_interrupt);return0;}staticintkey_probe(structplatform_device*pdev){structresource*irq_res;irq_res=platform_get_resource(pdev,IORESOURCE_IRQ,0);if(irq_res){button_irq=irq_res->开始;irqflags=irq_res->flags&IRQF_TRIGGER_MASK;}else{return-EINVAL;}returnbutton_init();}staticintkey_remove(structplatform_device*dev){returnbutton_exit();}structof_device_idof_tbl[]={{.compatible="xj4412,key",},{LE},DE};MODUTA(of,of_tbl);structplatform_driverkey_drv={.probe=key_probe,.remove=key_remove,.driver.name="keydrv",.driver.of_match_table=of_tbl,};module_platform_driver_register(key_drv);MODULE_LICENSE("GPL");applyLayergetkeyvalue#includestructinput_event{structtimevaltime;unsignedshorttype;unsignedshortcode;intvalue;};intmain(intargc,char*constargv[]){intfd=0;structinput_eventevent[3]={0};//3!!!,驱动上传了2个事件,第三个用来存放空元素intret=0;fd=open(argv[1],O_RDONLY);while(1){ret=read(fd,&event,sizeof(event));printf("ret:%d,val0:%d,val1:%d,val12:%d\n",ret,event[0].value,event[1].value,event[2].value);//2!!!,最后一个是空sleep(1);}return0;}