嵌入式Linux驱动笔记(四)------USB键盘驱动程序

2019-07-12 16:46发布

你好!这里是风筝的博客, 欢迎和我一起交流。

Kernel版本为4.4.17.
编写USB键盘的驱动,可以参考Kernel里的usbkbd.c这个文件. 我越发觉得驱动都是按套路来的.......流程都差不多一样.
在这个文件里,最主要就是看usb_kbd_probe函数和usb_kbd_irq函数了。 在文件最开头有个数组: static const unsigned char usb_kbd_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 150,158,159,128,136,177,178,176,142,152,173,140 };这个是USB键盘的码表,上报按键事件的时候需要用到。
接下来就是usb_kbd_probe函数了,就一点点内容,我都用注释写好了,还是比较简单的(其实注释也是我抄来的,///////捂脸)
static int usb_kbd_probe(struct usb_interface *iface, const struct usb_device_id *id) { 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; 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(); if (!kbd || !input_dev) goto fail1; if (usb_kbd_alloc_mem(dev, kbd)) goto fail2; /* 填充 usb 设备结构体和输入设备结构体 */ kbd->usbdev = dev; kbd->dev = input_dev; spin_lock_init(&kbd->leds_lock); /*以"厂商名字 产品名字"的格式将其写入kbd->name*/ 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)); strlcat(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); 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); 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; 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); /*初始化控制URB*/ usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0), (void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd); kbd->led->transfer_dma = kbd->leds_dma; kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; error = input_register_device(kbd->dev); if (error) goto fail2; usb_set_intfdata(iface, kbd); device_set_wakeup_enable(&dev->dev, 1); return 0; fail2: usb_kbd_free_mem(dev, kbd); fail1: input_free_device(input_dev); kfree(kbd); return error; }
感觉主要的也没什么,就是:
选择interface. 获取描述符. 将endpoint设置为中断IN端点.
分配kbd、input_dev空间. 填充 usb 设备结构体和输入设备结构体 .
以"厂商名字 产品名字"的格式将其写入kbd->name.
设置上报输入子系统的事件类型. 设置上报输入子系统的事件有什么. 设置注册事件的处理、打开、关闭函数入口. 初始化中断URB.
初始化控制URB.
感觉就差不多了。 主要的还是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; } for (i = 0; i < 8; i++) input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); for (i = 2; i < 8; i++) { 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. ", kbd->old[i]); } 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) pressed. ", kbd->new[i]); } } input_sync(kbd->dev); memcpy(kbd->old, kbd->new, 8); resubmit: i = usb_submit_urb (urb, GFP_ATOMIC);/*发送USB请求块*/ if (i) hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d", kbd->usbdev->bus->bus_name, kbd->usbdev->devpath, i); }
其中,每次中断接收的值都是8个字节,是这样的: data0 data1 data2 data3 data4 data5 data6 data7 
一般,只按下一个键时,键值会出现在data2上。 如按下键盘a时,数据为:00 00 04 00 00 00 00 00. a键松开时,数据为:00 00 00 00 00 00 00 00. 同时按下两个键时,第二个键的键值会出现在data3上。 如按下a时再按下b键,数据为:00 00 04 05 00 00 00 00. 所以能同时按下6个按键。 接下来就很好分析中断函数了: for (i = 0; i < 8; i++) input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); 这一句是用来检测修饰按键的,虽然我也不造修饰按键是什么......
其中,kbd->new为urb的缓冲区,在probe函数里有一句:usb_kbd_alloc_mem(dev, kbd),函数中为kbd->new分配8字节内存:kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma), 
并且在中断urb初始化函数中usb_fill_int_urb(kbd->irq, dev, pipe, kbd->new, (maxp > 8 ? 8 : maxp), usb_kbd_irq, kbd, endpoint->bInterval); 
即从端点里取得的数据会放在new指向的内存里面,最大为8字节.
接下来的才是真正的常用按键检测:
for (i = 2; i < 8; i++) { 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. ", kbd->old[i]); } 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) pressed. ", kbd->new[i]); } } 因为键值是在data2及其之后存放的,所以for循环从i=2开始查询。for里面有两个if,第一个if是判断按键是否是松开的,第二个if是判断按键是否被按下的。其中memscan函数是判断键值是否相等的,kbd->old存放的是上一次按键的数据,kbd->new是这一次按键的数据,memscan函数在string.c里,如下:void *memscan(void *addr, int c, size_t size) { unsigned char *p = addr; while (size) { if (*p == c) return (void *)p; p++; size--; } return (void *)p; }很简单的一个c语言函数,主要就是判断是否相等而已。通过上一次数据和这一次数据的比较就知道按键是否被按下了。然后上报按键事件即可。
最后,memcpy(kbd->old, kbd->new, 8);就是把kbd->new的成员赋值给kbd->old,实现新旧数据存储。

好了。接下来写驱动程序就照葫芦画瓢就可以了: /*参考usbkbd.c*/ #include #include #include #include #include #include static const unsigned char usb_kbd_keycode[256] = {/*键值码表*/ 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 150,158,159,128,136,177,178,176,142,152,173,140 }; static int len; static struct input_dev *uk_dev; static char *usb_buf; static dma_addr_t usb_buf_phys; static struct urb *uk_urb; static struct usb_device_id keyboard_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/ { } /* Terminating entry */ }; void buffer_copy (char *usb_buf_source , char *usb_buf_target , int len) { int i; for(i=0;icontext; int i; static char usb_buf_pre_val[8]; for (i = 2; i < 8; i++) {/*若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下*/ if(usb_buf[i]!=usb_buf_pre_val[i]) { if((usb_buf[i] ) ? 1 : 0) input_report_key(uk_dev, usb_kbd_keycode[usb_buf[i]], 1); else input_report_key(uk_dev, usb_kbd_keycode[usb_buf_pre_val[i]], 0); } } /*同步设备,告知事件的接收者驱动已经发出了一个完整的报告*/ input_sync(uk_dev); buffer_copy(usb_buf , usb_buf_pre_val , 8);/*防止未松开时被当成新的按键处理*/ /* 重新提交urb */ usb_submit_urb(uk_urb, GFP_KERNEL); } static int keyboard_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; int pipe; /*当前选择的interface*/ interface = intf->cur_altsetting; /*获取端点描述符*/ endpoint = &interface->endpoint[0].desc; /* a. 分配一个input_dev */ uk_dev = input_allocate_device(); if ( !uk_dev) printk("allocate false "); /* b. 设置 */ /* b.1 能产生哪类事件 */ set_bit(EV_KEY, uk_dev->evbit);/*键码事件*/ set_bit(EV_REP, uk_dev->evbit);/*自动重覆数值*/; uk_dev->ledbit[0] = BIT(LED_NUML)/*数字灯*/ | BIT(LED_CAPSL)/*大小写灯*/ | BIT(LED_SCROLLL)/*滚动灯*/ | BIT(LED_COMPOSE) | BIT(LED_KANA); /* b.2 能产生哪些事件 */ for (len = 0; len < 255; len++) set_bit(usb_kbd_keycode[len], uk_dev->keybit); clear_bit(0, uk_dev->keybit); /* c. 注册 */ input_register_device(uk_dev); /* d. 硬件相关操作 */ /* 数据传输3要素: 源,目的,长度 */ /* 源: USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);/*将endpoint设置为中断IN端点*/ /* 长度: */ len = endpoint->wMaxPacketSize; /* 目的: */ //usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"3要素" */ /* 分配usb request block */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); /* 使用"3要素设置urb" */ usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, keyboard_irq, NULL, endpoint->bInterval); uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 使用URB */ usb_submit_urb(uk_urb, GFP_KERNEL);/*发送USB请求块*/ return 0; } static void keyboard_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); usb_kill_urb(uk_urb); usb_free_urb(uk_urb); usb_free_coherent(dev, len, usb_buf, usb_buf_phys); input_unregister_device(uk_dev); input_free_device(uk_dev); } /* 1. 分配/设置usb_driver */ static struct usb_driver keyboard_driver = {/*任何一个LINUX下的驱动都有个类似的驱动结构*/ .name = "my_keyboard", .probe = keyboard_probe,/*驱动探测函数,加载时用到*/ .disconnect = keyboard_disconnect, .id_table = keyboard_id_table,/*驱动设备ID表,用来指定设备或接口*/ }; static int keyboard_init(void) { /* 2. 注册 */ int result = usb_register(&keyboard_driver); if (result != 0) /*注册失败*/ printk("register false "); return 0; } static void keyboard_exit(void) { usb_deregister(&keyboard_driver); } module_init(keyboard_init); module_exit(keyboard_exit); MODULE_LICENSE("GPL");
其中kbd我没有用到,就不写了,实现了按键即可。
Makefile: KERN_DIR = /work/system/linux-4.4.17 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += my_keyboard.o

最后make编译好,insmod my_keyboard.ko 插上键盘即可,usb键盘是热拔插的,随便插都没事。 cat /dev/tty1 即可看到在键盘按下的数据。
哦,忘记了,我是4.4.17的Kernel,usb键盘的驱动是没被编译进内核的,所以insmod自己写的驱动时不会出问题,其他的Kernel需要自己注意下,避免加载冲突了。 要是想用Kernel自带的驱动,在 Device Drivers  ---> HID support  ---> USB HID support  ---> < > USB HID transport layer
选中<*> USB HID transport layer即可。 估计以前的Kernel是 Device Drivers  ---> HID support  ---><*>  USB Human Interface Device (full HID) suppoort
默认是选中的,反正我现在的Kernel是 USB Human Interface Device (full HID) suppoort没有了的,估计就是变成了USB HID transport layer