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

来源:互联网 发布:行为监控软件 编辑:程序博客网 时间:2024/06/05 02:01

你好!这里是风筝的博客,

欢迎和我一起交流。



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);elsehid_info(urb->dev, "Unknown key (scancode %#x) released.\n", 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);elsehid_info(urb->dev, "Unknown key (scancode %#x) pressed.\n", 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);elsehid_info(urb->dev, "Unknown key (scancode %#x) released.\n", 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);elsehid_info(urb->dev, "Unknown key (scancode %#x) pressed.\n", 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 <linux/kernel.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/init.h>#include <linux/usb/input.h>#include <linux/hid.h>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;i<len;i++){usb_buf_target[i] = usb_buf_source[i];}}static void keyboard_irq(struct urb *urb){struct usb_kbd *kbd = urb->context;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);elseinput_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 \n");/* 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 \n");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.17all:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-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









阅读全文
0 0
原创粉丝点击