本文以 usbkbd.c 为例,分析 usb 键盘驱动程序。
static int __init usb_kbd_init(void) { int result = usb_register(&usb_kbd_driver); if (result == 0) printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" DRIVER_DESC "\n"); return result; }
static struct usb_driver usb_kbd_driver = { .name = "usbkbd", .probe = usb_kbd_probe, .disconnect = usb_kbd_disconnect, .id_table = usb_kbd_id_table, };
还是来看一下 id_table ,与鼠标相比,仅仅是协议不一样。
static struct usb_device_id usb_kbd_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) }, { } /* Terminating entry */ };
下面来看probe函数
static int usb_kbd_probe(struct usb_interface *iface, const struct usb_device_id *id) { /* 获得usb_device */ struct usb_device *dev = interface_to_usbdev(iface); /* 接口的设置 */ struct usb_host_interface *interface; /* 端点描述符 */ struct usb_endpoint_descriptor *endpoint; /* 键盘结构体 */ struct usb_kbd *kbd; /* 输入设备 */ struct input_dev *input_dev; int i, pipe, maxp; int error = -ENOMEM; /* 获取该接口当前的设置 */ interface = iface->cur_altsetting; /* 如果当前设置的端点数量不是1,那么错误,返回 */ if (interface->desc.bNumEndpoints != 1) return -ENODEV; /* 如果当前设置第一个端点的类型不是中断端点,错误,返回 */ endpoint = &interface->endpoint[0].desc; if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; /* 第一个端点的管道 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 最大传输包大小 */ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); /* 为键盘结构体分配空间 */ kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); /* 分配一个输入设备 */ input_dev = input_allocate_device(); /* kbd->irq = usb_alloc_urb(0, GFP_KERNEL) kbd->led = usb_alloc_urb(0, GFP_KERNEL) kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma) kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma) kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma) */ if (usb_kbd_alloc_mem(dev, kbd)) goto fail2; /* 填充键盘结构体。以及一些字符串 */ kbd->usbdev = dev; kbd->dev = input_dev; if (dev->manufacturer) strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); if (dev->product) { if (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), "USB HIDBP Keyboard %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); strlcpy(kbd->phys, "/input0", sizeof(kbd->phys)); /* 填充输入设备 */ input_dev->name = kbd->name; input_dev->phys = kbd->phys; usb_to_input_id(dev, &input_dev->id); input_dev->dev.parent = &iface->dev; input_set_drvdata(input_dev, kbd); /* 设置它支持的事件类型和具体事件 1、按键类事件 2、LED灯(大小写灯等)3、重复上报*/ 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); for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], input_dev->keybit); clear_bit(0, input_dev->keybit); /* 对于LED类型的事件,首先会调用到dev->event 然后再调用事件处理层的event */ input_dev->event = usb_kbd_event; input_dev->open = usb_kbd_open; input_dev->close = usb_kbd_close; /* 填充中断类型Urb */ usb_fill_int_urb(kbd->irq, dev, pipe, kbd->new, (maxp > 8 ? 8 : maxp), usb_kbd_irq, kbd, endpoint->bInterval); kbd->irq->transfer_dma = kbd->new_dma; kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* * bit7 控制传输 data 阶段的方向 0 主机到设备 1设备到主机, 这里 0 主机到设备 * bit5-6 表示 request 类型,是标准的还是厂家定义的, 这里为hid class定义 * bit0-4 表示这个请求针对的是设备、接口还是端点 , 这里是接口 */ kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; // 0x01 << 5 | /* #define USB_REQ_GET_STATUS 0x00 #define USB_REQ_CLEAR_FEATURE 0x01 #define USB_REQ_SET_FEATURE 0x03 #define USB_REQ_SET_ADDRESS 0x05 #define USB_REQ_GET_DESCRIPTOR 0x06 #define USB_REQ_SET_DESCRIPTOR 0x07 #define USB_REQ_GET_CONFIGURATION 0x08 #define USB_REQ_SET_CONFIGURATION 0x09 #define USB_REQ_GET_INTERFACE 0x0A #define USB_REQ_SET_INTERFACE 0x0B #define USB_REQ_SYNCH_FRAME 0x0C */ kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION; /* request 的参数 */ kbd->cr->wValue = cpu_to_le16(0x200); /* bRequestType 中,针对接口、端点时,它表示那个接口或端点 */ kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); /* data阶段数据长度 */ kbd->cr->wLength = cpu_to_le16(1); /* static inline void usb_fill_control_urb( struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) */ /* 这里使用的是默认端点0 */ usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),(void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd); kbd->led->setup_dma = kbd->cr_dma; kbd->led->transfer_dma = kbd->leds_dma; kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP); error = input_register_device(kbd->dev); usb_set_intfdata(iface, kbd); return 0; }
这里与鼠标驱动程序相比,多了一个控制传输过程,而且,这个控制传输的 bRequestType 中说明了这个传输不是标准的请求,而是Class,我们的键盘是HID类型,因此还要看USB HID协议中,关于这个请求是如何定义的,才能知道wValue 、wIndex等是什么意思(参考:http://blog.csdn.net/leo_wonty/article/details/6721214),这就是就将knd->leds
内的一字节数据发送给从设备。在鼠标驱动程序中,中断 urb 是在open函数中提交的,这里也不例外。
static int usb_kbd_open(struct input_dev *dev) { struct usb_kbd *kbd = input_get_drvdata(dev); kbd->irq->dev = kbd->usbdev; if (usb_submit_urb(kbd->irq, GFP_KERNEL)) return -EIO; return 0; }
中断传输完成之后会调用完成函数,usb_kbd_irq
static void usb_kbd_irq(struct urb *urb) { struct usb_kbd *kbd = urb->context; int i; switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } //报告usb_kbd_keycode[224..231]8按键状态 //KEY_LEFTCTRL,KEY_LEFTSHIFT,KEY_LEFTALT,KEY_LEFTMETA, //KEY_RIGHTCTRL,KEY_RIGHTSHIFT,KEY_RIGHTALT,KEY_RIGHTMETA for (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); else hid_info(urb->dev, "Unknown key (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); else hid_info(urb->dev, "Unknown key (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't resubmit intr, %s-%s/input0, status %d", kbd->usbdev->bus->bus_name, kbd->usbdev->devpath, i); }
这里,都是上报的按键类事件,我们在前边的Probe函数中,设置了输入设备支持按键类事件,还有一个LED类事件,但是搜遍代码也没有找到,LED类事件是在哪里上报的。还有,probe函数中定义了一个dev->event函数,在浏览资料时发现,有些人说在input_event时,调用事件处理层的event函数的同时会调用dev->event,我认为这种说法是不正确的,看过input_event代码的同学应该不难发现,只有上报LED类等事件的时候才会触发dev->event,我们这里单纯上报的按键类事件并不会触发dev->event
。那么,probe 函数里定义的 dev->event 函数岂不是成了摆设么?确实,我暂时没发现它有什么用。手头没有usb键盘,这个后边实验证实。
static int usb_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct usb_kbd *kbd = input_get_drvdata(dev); if (type != EV_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) return 0; if (*(kbd->leds) == kbd->newleds) return 0; *(kbd->leds) = kbd->newleds; kbd->led->dev = kbd->usbdev; if (usb_submit_urb(kbd->led, GFP_ATOMIC)) err_hid("usb_submit_urb(leds) failed"); return 0; }
这里的 dev->led 记录的是led的状态,比如,我们上报一个LED事件时,input_event 中会将对应的事件记录在 dev->led 中。这里检测LED事件是否发生,然后通过控制传输将1字节数据传送给usb键盘,Usb键盘的灯相应做出改变。但是,说到底,代码里没有上报过LED类事件,一切都是白扯。
static void usb_kbd_led(struct urb *urb) { struct usb_kbd *kbd = urb->context; if (urb->status) dev_warn(&urb->dev->dev, "led urb status %d received\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)) err_hid("usb_submit_urb(leds) failed"); }
这里的控制传输完成函数也是个累赘?每次有LED事件上报的话,那么控制传输urb就自动提交了。那么,*(kbd->leds)== kbd->newleds 必然是相等的,除非又有新的事件上报了,但是新事件上报时,在usb_kbd_event 函数里urb不就自动提交了么? 会出现不相等的情况?
此篇文章存在诸多疑问,如果有大神看到,还请解答一下。