原文链接地址:/Linux/-12/76197p9.htm
跟USB鼠标类型一样,USB键盘也属于HID类型,代码在/dirver/hid/usbhid/usbkbd.c下。USB键盘除了提交中断URB外,还需要提交控制URB。不多话,我们看代码
[cpp]view plaincopy staticint__initusb_kbd_init(void){intresult=usb_register(&usb_kbd_driver);if(result==0)printk(KERN_INFOKBUILD_MODNAME":"DRIVER_VERSION":"DRIVER_DESC"\n");returnresult;}[cpp]view plaincopy staticstructusb_driverusb_kbd_driver={.name="usbkbd",.probe=usb_kbd_probe,.disconnect=usb_kbd_disconnect,.id_table=usb_kbd_id_table,//驱动设备ID表,用来指定设备或接口};
下面跟踪usb_driver中的probe
[cpp]view plaincopy staticintusb_kbd_probe(structusb_interface*iface,conststructusb_device_id*id){structusb_device*dev=interface_to_usbdev(iface);//通过接口获取USB设备指针structusb_host_interface*interface;//设置structusb_endpoint_descriptor*endpoint;//端点描述符structusb_kbd*kbd;//usb_kbd私有数据structinput_dev*input_dev;//input设备inti,pipe,maxp;interror=-ENOMEM;interface=iface->cur_altsetting;//获取设置if(interface->desc.bNumEndpoints!=1)//与mouse一样只有一个端点return-ENODEV;endpoint=&interface->endpoint[0].desc;//获取端点描述符if(!usb_endpoint_is_int_in(endpoint))//检查端点是否为中断输入端点return-ENODEV;pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress);//将endpoint设置为中断IN端点maxp=usb_maxpacket(dev,pipe,usb_pipeout(pipe));//端点传输的最大数据包kbd=kzalloc(sizeof(structusb_kbd),GFP_KERNEL);//分配urbinput_dev=input_allocate_device();//分配input设备空间if(!kbd||!input_dev)gotofail1;if(usb_kbd_alloc_mem(dev,kbd))//分配urb空间和其他缓冲区gotofail2;kbd->usbdev=dev;//给内嵌结构体赋值kbd->dev=input_dev;if(dev->manufacturer)//拷贝厂商IDstrlcpy(kbd->name,dev->manufacturer,sizeof(kbd->name));if(dev->product){//拷贝产品IDif(dev->manufacturer)strlcat(kbd->name,"",sizeof(kbd->name));strlcat(kbd->name,dev->product,sizeof(kbd->name));}if(!strlen(kbd->name))//检测不到厂商名字snprintf(kbd->name,sizeof(kbd->name),"USBHIDBPKeyboard%04x:%04x",le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));//设备链接地址usb_make_path(dev,kbd->phys,sizeof(kbd->phys));strlcat(kbd->phys,"/input0",sizeof(kbd->phys));input_dev->name=kbd->name;//给input_dev结构体赋值input_dev->phys=kbd->phys;usb_to_input_id(dev,&input_dev->id);//拷贝usb_driver的支持给input,设置bustype,vendo,product等input_dev->dev.parent=&iface->dev;input_set_drvdata(input_dev,kbd);//将kbd设置为input的私有数据input_dev->evbit[0]=BIT_MASK(EV_KEY)|BIT_MASK(EV_LED)|BIT_MASK(EV_REP);//支持的按键事件类型input_dev->ledbit[0]=BIT_MASK(LED_NUML)|BIT_MASK(LED_CAPSL)|BIT_MASK(LED_SCROLLL)|BIT_MASK(LED_COMPOSE)|BIT_MASK(LED_KANA);//EV_LED事件支持的事件码for(i=0;i<255;i++)set_bit(usb_kbd_keycode[i],input_dev->keybit);//EV_KEY事件支持的事件码(即设置支持的键盘码)clear_bit(0,input_dev->keybit);input_dev->event=usb_kbd_event;//定义event函数input_dev->open=usb_kbd_open;input_dev->close=usb_kbd_close;usb_fill_int_urb(kbd->irq,dev,pipe,kbd->new,(maxp>8?8:maxp),usb_kbd_irq,kbd,endpoint->bInterval);//填充中断urbkbd->irq->transfer_dma=kbd->new_dma;kbd->irq->transfer_flags|=URB_NO_TRANSFER_DMA_MAP;kbd->cr->bRequestType=USB_TYPE_CLASS|USB_RECIP_INTERFACE;kbd->cr->bRequest=0x09;//设置控制请求的格式kbd->cr->wValue=cpu_to_le16(0x200);kbd->cr->wIndex=cpu_to_le16(interface->desc.bInterfaceNumber);kbd->cr->wLength=cpu_to_le16(1);usb_fill_control_urb(kbd->led,dev,usb_sndctrlpipe(dev,0),(void*)kbd->cr,kbd->leds,1,usb_kbd_led,kbd);//填充控制urbkbd->led->transfer_dma=kbd->leds_dma;kbd->led->transfer_flags|=URB_NO_TRANSFER_DMA_MAP;error=input_register_device(kbd->dev);if(error)gotofail2;usb_set_intfdata(iface,kbd);device_set_wakeup_enable(&dev->dev,1);return0;fail2:usb_kbd_free_mem(dev,kbd);fail1:input_free_device(input_dev);kfree(kbd);returnerror;}
在上面的probe中,我们主要是初始化一些结构体,然后提交中断urb和控制urb,并注册input设备。其中有几个地方需要细看下,其一,usb_kbd_alloc_mem的实现。其二,设置控制请求的格式。
先来看看usb_kbd_alloc_mem的实现
[cpp]view plaincopy staticintusb_kbd_alloc_mem(structusb_device*dev,structusb_kbd*kbd){if(!(kbd->irq=usb_alloc_urb(0,GFP_KERNEL)))//分配中断urbreturn-1;if(!(kbd->led=usb_alloc_urb(0,GFP_KERNEL)))//分配控制urbreturn-1;if(!(kbd->new=usb_alloc_coherent(dev,8,GFP_ATOMIC,&kbd->new_dma)))return-1;//分配中断urb使用的缓冲区if(!(kbd->cr=kmalloc(sizeof(structusb_ctrlrequest),GFP_KERNEL)))return-1;//分配控制urb使用的控制请求描述符if(!(kbd->leds=usb_alloc_coherent(dev,1,GFP_ATOMIC,&kbd->leds_dma)))return-1;//分配控制urb使用的缓冲区return0;}
这里我们需要明白中断urb和控制urb需要分配不同的urb结构体,同时在提交urb之前,需要填充的内容也不同,中断urb填充的是缓冲区和中断处理函数,控制urb填充的是控制请求描述符与回调函数。
设置控制请求的格式。cr是struct usb_ctrlrequest结构的指针,USB协议中规定一个控制请求的格式为一个8个字节的数据包,其定义如下
[cpp]view plaincopy /***structusb_ctrlrequest-SETUPdataforaUSBdevicecontrolrequest*@bRequestType:matchestheUSBbmRequestTypefield*@bRequest:matchestheUSBbRequestfield*@wValue:matchestheUSBwValuefield(le16byteorder)*@wIndex:matchestheUSBwIndexfield(le16byteorder)*@wLength:matchestheUSBwLengthfield(le16byteorder)**ThisstructureisusedtosendcontrolrequeststoaUSBdevice.Itmatches*thedifferentfieldsoftheUSB2.0Specsection9.3,table9-2.Seethe*USBspecforafullerdescriptionofthedifferentfields,andwhattheyare*usedfor.**Notethatthedriverforanyinterfacecanissuecontrolrequests.*Formostdevices,interfacesdon'tcoordinatewitheachother,so*suchrequestsmaybemadeatanytime.*/structusb_ctrlrequest{__u8bRequestType;//设定传输方向、请求类型等__u8bRequest;//指定哪个请求,可以是规定的标准值也可以是厂家定义的值__le16wValue;//即将写到寄存器的数据__le16wIndex;//接口数量,也就是寄存器的偏移地址__le16wLength;//数据传输阶段传输多少个字节}__attribute__((packed));
USB协议中规定,所有的USB设备都会响应主机的一些请求,这些请求来自USB主机控制器,主机控制器通过设备的默认控制管道发出这些请求。默认的管道为0号端口对应的那个管道。
同样这个input设备首先由用户层调用open函数,所以先看看input中定义的open
[cpp]view plaincopy staticintusb_kbd_open(structinput_dev*dev){structusb_kbd*kbd=input_get_drvdata(dev);kbd->irq->dev=kbd->usbdev;if(usb_submit_urb(kbd->irq,GFP_KERNEL))return-EIO;return0;}
因为这个驱动里面有一个中断urb一个控制urb,我们先看中断urb的处理流程。中断urb在input的open中被提交后,当USB core处理完毕,会通知这个USB设备驱动,然后执行回调函数,也就是中断处理函数usb_kbd_irq
[cpp]view plaincopy staticvoidusb_kbd_irq(structurb*urb){structusb_kbd*kbd=urb->context;inti;switch(urb->status){case0:/*success*/break;case-ECONNRESET:/*unlink*/case-ENOENT:case-ESHUTDOWN:return;/*-EPIPE:shouldclearthehalt*/default:/*error*/gotoresubmit;}//报告usb_kbd_keycode[224..231]8按键状态//KEY_LEFTCTRL,KEY_LEFTSHIFT,KEY_LEFTALT,KEY_LEFTMETA,//KEY_RIGHTCTRL,KEY_RIGHTSHIFT,KEY_RIGHTALT,KEY_RIGHTMETAfor(i=0;i<8;i++)input_report_key(kbd->dev,usb_kbd_keycode[i+224],(kbd->new[0]>>i)&1);//若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下for(i=2;i<8;i++){//获取键盘离开的中断//同时没有该KEY的按下状态if(kbd->old[i]>3&&memscan(kbd->new+2,kbd->old[i],6)==kbd->new+8){if(usb_kbd_keycode[kbd->old[i]])input_report_key(kbd->dev,usb_kbd_keycode[kbd->old[i]],0);elsehid_info(urb->dev,"Unknownkey(scancode%#x)released.\n",kbd->old[i]);}//获取键盘按下的中断//同时没有该KEY的离开状态if(kbd->new[i]>3&&memscan(kbd->old+2,kbd->new[i],6)==kbd->old+8){if(usb_kbd_keycode[kbd->new[i]])input_report_key(kbd->dev,usb_kbd_keycode[kbd->new[i]],1);elsehid_info(urb->dev,"Unknownkey(scancode%#x)released.\n",kbd->new[i]);}}input_sync(kbd->dev);//同步设备,告知事件的接收者驱动已经发出了一个完整的报告memcpy(kbd->old,kbd->new,8);//防止未松开时被当成新的按键处理resubmit:i=usb_submit_urb(urb,GFP_ATOMIC);if(i)hid_err(urb->dev,"can'tresubmitintr,%s-%s/input0,status%d",kbd->usbdev->bus->bus_name,kbd->usbdev->devpath,i);}
这个就是中断urb的处理流程,跟前面讲的的USB鼠标中断处理流程类似。好了,我们再来看看剩下的控制urb处理流程吧。
我们有个疑问,我们知道在probe中,我们填充了中断urb和控制urb,但是在input的open中,我们只提交了中断urb,那么控制urb什么时候提交呢?
我们知道对于input子系统,如果有事件被响应,我们会调用事件处理层的event函数,而该函数最终调用的是input下的event。所以,对于input设备,我们在USB键盘驱动中只设置了支持LED选项,也就是ledbit项,这是怎么回事呢?刚才我们分析的那个中断urb其实跟这个 input基本没啥关系,中断urb并不是像讲键盘input实现的那样属于input下的中断。我们在USB键盘驱动中的input子系统中只设计了 LED选项,那么当input子系统有按键选项的时候必然会使得内核调用调用事件处理层的event函数,最终调用input下的event。好了,那我们来看看input下的event干了些什么。
[cpp]view plaincopy staticintusb_kbd_event(structinput_dev*dev,unsignedinttype,unsignedintcode,intvalue){structusb_kbd*kbd=input_get_drvdata(dev);if(type!=EV_LED)//不支持LED事件return-1;//获取指示灯的目标状态kbd->newleds=(!!test_bit(LED_KANA,dev->led)<<3)|(!!test_bit(LED_COMPOSE,dev->led)<<3)|(!!test_bit(LED_SCROLLL,dev->led)<<2)|(!!test_bit(LED_CAPSL,dev->led)<<1)|(!!test_bit(LED_NUML,dev->led));if(kbd->led->status==-EINPROGRESS)return0;//指示灯状态已经是目标状态则不需要再做任何操作if(*(kbd->leds)==kbd->newleds)return0;*(kbd->leds)=kbd->newleds;kbd->led->dev=kbd->usbdev;if(usb_submit_urb(kbd->led,GFP_ATOMIC))pr_err("usb_submit_urb(leds)failed\n");//提交控制urbreturn0;}
当在input的event里提交了控制urb后,经过URB处理流程,最后返回给USB设备驱动的回调函数,也就是在probe中定义的usb_kbd_led
[cpp]view plaincopy staticvoidusb_kbd_led(structurb*urb){structusb_kbd*kbd=urb->context;if(urb->status)hid_warn(urb->dev,"ledurbstatus%dreceived\n",urb->status);if(*(kbd->leds)==kbd->newleds)return;*(kbd->leds)=kbd->newleds;kbd->led->dev=kbd->usbdev;if(usb_submit_urb(kbd->led,GFP_ATOMIC))hid_err(urb->dev,"usb_submit_urb(leds)failed\n");}
总结下,我们的控制urb走的是先由input的event提交,触发后由控制urb的回调函数再次提交。好了,通过USB鼠标,我们已经知道了控制urb和中断urb的设计和处理流程。