第7章输入设备驱动内核的输入子系统是为了对分散的、多种不同类别的输入设备(如键盘、鼠标、跟踪球、操纵杆、辊轮、触摸屏、加速计和手写板)进行统一处理的驱动。输入子系统带来了如下好处:•统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB,还是蓝牙,都被同样处理。•提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。XWindows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。•抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入设备的访问。图7.1展示了输入子系统的操作。此子系统包括一前一后运行的两类驱动:事件驱动和设备驱动。事件驱动负责和应用程序的接口,而设备驱动负责和底层输入设备的通信。鼠标事件产生者mousedev,是前者的实例;而PS/2鼠标驱动是后者的实例。事件驱动和设备驱动都可以利用输入子系统的高效、可重用的核心提供的服务。图7.1.输入子系统事件驱动是标准的,对所有的输入类都是可用的,所以你更可能的是实现设备驱动而不是事件驱动。你的设备驱动可以利用一个已经存在的、合适的事件驱动通过输入核心和用户应用程序接口。需要注意的是本章使用的名辞“设备驱动”指的是输入设备驱动,而不是输入事件驱动。输入事件驱动输入子系统提供的事件接口已经发展成为很多图形窗口系统理解的标准。事件驱动提供一个硬件无关的抽象,以和输入设备交互;如同帧缓冲接口(在第12章《视频设备驱动》中讨论)提供一个通用的机制以和显示设备通信一样。事件驱动和帧缓冲驱动一起,将图形用户接口(GUI)和各种各样的底层硬件隔离开来。Evdev接口Evdev是一个通用的输入事件驱动。Evdev产生的每个事件包都有如下格式,定义于include/linux/input.h:structinput_event{structtimevaltime;/*Timestamp*/__u16type;/*EventType*/__u16code;/*EventCode*/__s32value;/*EventValue*/};为了学习如何使用evdev,让我们来实现一个虚拟鼠标的输入设备驱动。设备例子:虚拟鼠标我们的虚拟鼠标工作过程如下:一个应用程序(coord.c)模拟鼠标移动,并通过一个sysfs节点/sys/devices/platform/vms/coordinates分发坐标信息给虚拟鼠标驱动(vms.c)。虚拟鼠标驱动(vms驱动)通过evdev向上层传送这些移动信息。图7.2展示了详细过程:图7.2.虚拟鼠标的输入驱动!--[if!vml]--!--[endif]--通用目的鼠标(gpm)是服务器,让你在文本模式下使用鼠标,而无需X服务器。Gpm能够理解evdev消息,因此vms驱动能够直接和其通信。一切就绪后,你将会看到随着由coord.c产生虚拟鼠标移动,光标在屏幕上跳动。清单7.1包含coord.c,它连续产生随机的X和Y坐标。鼠标与操纵杆或触摸屏不同,它产生的是相对坐标!--[if!supportAnnotations]--[MS1]!--[endif]--而非绝对坐标,这就是coord.c所做的工作。vms驱动在清单7.2中。清单7.1.模拟虚拟移动的应用程序(coord.c)CodeView:#includefcntl.hintmain(intargc,char*argv[]){intsim_fd;intx,y;charbuffer[10];/*Openthesysfscoordinatenode*/sim_fd=open(/sys/devices/platform/vms/coordinates,O_RDWR);if(sim_fd0){perror(Couldn'topenvmscoordinatefile\n);exit(-1);}while(1){/*Generaterandomrelativecoordinates*/x=random()%20;y=random()%20;if(x%2)x=-x;if(y%2)y=-y;/*Conveysimulatedcoordinatestothevirtualmousedriver*/sprintf(buffer,%d%d%d,x,y,0);write(sim_fd,buffer,strlen(buffer));fsync(sim_fd);sleep(1);}close(sim_fd);}清单7.2.InputDriverfortheVirtualMouse(vms.c)CodeView:#includelinux/fs.h#includeasm/uaccess.h#includelinux/pci.h#includelinux/input.h#includelinux/platform_device.hstructinput_dev*vms_input_dev;/*Representationofaninputdevice*/staticstructplatform_device*vms_dev;/*Devicestructure*//*Sysfsmethodtoinputsimulatedcoordinatestothevirtualmousedriver*/staticssize_twrite_vms(structdevice*dev,structdevice_attribute*attr,constchar*buffer,size_tcount){intx,y;sscanf(buffer,%d%d,&x,&y);/*Reportrelativecoordinatesviatheeventinterface*/input_report_rel(vms_input_dev,REL_X,x);input_report_rel(vms_input_dev,REL_Y,y);input_sync(vms_input_dev);returncount;}/*Attachthesysfswritemethod*/DEVICE_ATTR(coordinates,0644,NULL,write_vms);/*AttributeDescriptor*/staticstructattribute*vms_attrs[]={&dev_attr_coordinates.attr,NULL};/*Attributegroup*/staticstructattribute_groupvms_attr_group={.attrs=vms_attrs,};/*DriverInitialization*/int__initvms_init(void){/*Registeraplatformdevice*/vms_dev=platform_device_register_simple(vms,-1,NULL,0);if(IS_ERR(vms_dev)){PTR_ERR(vms_dev);printk(vms_init:error\n);}/*Createasysfsnodetoreadsimulatedcoordinates*/sysfs_create_group(&vms_dev-dev.kobj,&vms_attr_group);/*Allocateaninputdevicedatastructure*/vms_input_dev=input_allocate_device();if(!vms_input_dev){printk(Badinput_alloc_device()\n);}/*Announcethatthevirtualmousewillgeneraterelativecoordinates*/set_bit(EV_REL,vms_input_dev-evbit);set_bit(REL_X,vms_input_dev-relbit);set_bit(REL_Y,vms_input_dev-relbit);/*Registerwiththeinputsubsystem*/input_register_device(vms_input_dev);printk(VirtualMouseDriverInitialized.\n);return0;}/*DriverExit*/voidvms_cleanup(void){/*Unregisterfromtheinputsubsystem*/input_unregister_device(vms_input_dev);/*Cleanupsysfsnode*/sysfs_remove_group(&vms_dev-dev.kobj,&vms_attr_group);/*Unregisterdriver*/platform_device_unregister(vms_dev);return;}module_init(vms_init);module_exit(vms_cleanup);让我们仔细阅读清单7.2中的代码。初始化期间,vms驱动注册自身为输入设备驱动。为此,它首先使用内核APIinput_allocate_device()分配input_dev结构:vms_input_dev=input_allocate_device();然后,声明虚拟鼠标产生相对性事件:set_bit(EV_REL,vms_input_dev-evbit);/*事件类型为EV_REL*/下一步,声明虚拟鼠标产生的事件的编码:set_bit(REL_X,vms_input_dev-relbit);/*Relative'X'movement*/set_bit(REL_Y,vms_input_dev-relbit);/*Relative'Y'movement*/如果你的虚拟鼠标也能产生按钮点击事件,还需要将其加入vms_init():set_bit(EV_KEY,vms_input_dev-evbit);/*EventTypeisEV_KEY*/set_bit(BTN_0,vms_input_dev-keybit);/*EventCodeisBTN_0*/昀后,进行注册:input_register_device(vms_input_dev);write_vms()是sysfs中的store()方法,和/sys/devices/platform/vms/coordinates相关联。当coord.c写入X/Y坐标对进此文件时,write_vms()做如下操作:input_report_rel(vms_input_dev,REL_X,x);input_report_rel(vms_input_dev,REL_Y,y);input_sync(vms_input_dev);第一条语句会产生REL_X事件,或设备在X方向的相对移动。第二条语句产生REL_Y事件,或设备在Y方向的相对移动。input_sync()表明此事件已经完成,因此输入子系统将这两个事件组成一个evdev包,并通过/dev/input/eventX发送出去,eventX中的X是分配给vms驱动的接口序号。读该文件的应用程序将以前面描述的input_event格式接收事件包。为了将gpm关联至此事件接口,从而追逐光标的跳动,需要做如下操作:bashgpm-m/dev/input/eventX-tevdev在“触摸控制器”和后面的“加速度传感器”章节中讨论的ADS7846触摸控制器驱动以及加速度传感器驱动,也都使用了evdev。更多的事件接口vms驱动利用通用的evdev事件接口,但像键盘、鼠