首页 > 代码库 > 大话USB驱动之USB键盘
大话USB驱动之USB键盘
转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/25040049
一.总体框图
二.驱动代码
/*************************************************************** *版权所有 (C)2014, *文件名称:linux键盘驱动 *内容摘要:用另一种方式改写linux键盘驱动 *其它说明: *当前版本:V1.2 *作 者: 若云流风 *完成日期:2014.5.6 *修改记录1: * 修改日期:2014.5.7 * 版本号: V1.1 * 修改人: 若云流风 * 修改内容:消除程序硬释放不能实现重复事件问题 ***************************************************************/ /* * 参考:drivers\hid\usbhid\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 struct input_dev *uk_dev; static unsigned char *usb_buf; static dma_addr_t usb_buf_phys; static int len, pre_val,pre_val_change; static struct urb *uk_urb; /*键码表*/ static 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 }; /* *接口描述符:类(HID),子类(boot),协议(KEYBOARD) */ static struct usb_device_id usb_keyboard_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD)}, //{USB_DEVICE(0x1234,0x5678)}, { } /* Terminating entry */ }; static void usb_keyboard_irq(struct urb *urb) { /* //读者可自行调试,能够看到usb_buf[i]的具体数值 int i; static int cnt = 0; printk("data cnt %d: ", ++cnt); for (i = 0; i < len; i++) { printk("%02x ", usb_buf[i]); } printk("\n"); */ int i; /*主要是上报是否按下了29, 42, 56,125, 97, 54,100,126这几个键码对应的键 *也就是 Left Control、 Left Shift 、Left Alt 、 Left GUI 、 Right Control、 Right Shift 、 Right Alt 、 Right GUI *这几个建。因为要处理大小写等问题。要先判断是否按下他们,然后再判断是否按下其余按键 */ for (i = 0; i < 8; i++) input_report_key(uk_dev, usb_kbd_keycode[i + 224], (usb_buf[0] >> i) & 1); /*因为bit0上面已经写出,bit1保留,其余的按键在bit2到7*/ for (i = 2; i < len; i++) { if (pre_val!= usb_buf[i]) //判断两次值是否相等(松开和按下肯等都不会相等) { /* 按下时候数组中不等于0的那个才能元素进来,进来后直接进入if *在if中上报键值,并把键值给pre_val_change用来松手判断(如果直接赋值给pre_val会有BUG) *在没有松按键时会一直打印(实现重复事件) */ if(usb_buf[i]!=0) { input_report_key(uk_dev, usb_kbd_keycode[usb_buf[i]], 1); input_sync(uk_dev); pre_val_change=usb_buf[i]; } /*当松手时,usb_buf[i]值全为0,所以必然进入大if中, *由于每个值都等于0,所以每次都会进入这个else(进入8次) *进的最后要清零pre_val_change,要不然下次你再按此键值的时候 *pre_val!= usb_buf[i] 每次都会上报一次松开此键的BUG */ else { input_report_key(uk_dev, usb_kbd_keycode[pre_val], 0); input_sync(uk_dev); pre_val_change=0; //此处效果相同(pre_val_change=usb_buf[i];)要清零,否则会出现下次再按此键按不了的情况。 } } } pre_val=pre_val_change; //将change值赋给per_val用来判断是否按下 /* 重新提交urb */ usb_submit_urb(uk_urb, GFP_ATOMIC); } static int usb_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,i; pre_val=0; //清零pre_val,这个是按键的old值 pre_val_change=0; //清零pre_val_change,引入第三变量 interface = intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; /* a. 分配一个input_dev */ uk_dev = input_allocate_device(); /* b. 设置 */ /* b.1 能产生哪类事件 */ set_bit(EV_KEY, uk_dev->evbit); set_bit(EV_REP, uk_dev->evbit); /* b.2 能产生哪些事件 */ for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], uk_dev->keybit); /* c. 注册 */ input_register_device(uk_dev); /* d. 硬件相关操作 */ /* 数据传输3要素: 源,目的,长度 */ /* 源: USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //urb索要发送的特定目标struct dev端点信息。 /* 长度: */ len = endpoint->wMaxPacketSize; //最大包的长度 /* 目的: */ usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); //分配一个usb_buffer /* 使用"3要素" */ /* 分配usb request block */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); //给urb分配内存空间 /* 使用"3要素设置urb" */ usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usb_keyboard_irq, NULL, endpoint->bInterval);//用来正确地初始化即将被发送到USB设备的中断端点urb uk_urb->transfer_dma = usb_buf_phys; //用dma方式传输数据到USB缓冲区 uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //用于控制带有已设置好的DMA缓冲区的URB /* 使用URB */ usb_submit_urb(uk_urb, GFP_KERNEL); //提交到USB核心以发送到USB设备 return 0; } static void usb_keyboard_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); //printk("disconnect usbmouse!\n"); usb_kill_urb(uk_urb); usb_free_urb(uk_urb); usb_buffer_free(dev, len, usb_buf, usb_buf_phys); input_unregister_device(uk_dev); input_free_device(uk_dev); } /* 1. 分配/设置usb_driver */ static struct usb_driver usb_keyboadr_driver = { .name = "usb_keyboard", .probe = usb_keyboard_probe, .disconnect = usb_keyboard_disconnect, .id_table = usb_keyboard_table, }; static int usb_keyboard_init(void) { /* 2. 注册 */ usb_register(&usb_keyboadr_driver); return 0; } static void usb_keyboard_exit(void) { usb_deregister(&usb_keyboadr_driver); } module_init(usb_keyboard_init); module_exit(usb_keyboard_exit); MODULE_LICENSE("GPL");
三.程序分析
上面的代码是根据原USB键盘驱动代码改写而成,写的比较简单,但是有一个缺陷,不能实现重复事件(按下一个键不动,屏幕不会连续打印出这个键)。欢迎大家提供解决方法,后续有时间我还会改进,然后给大家分享。
1.框架分析
a. 分配结构体
static struct usb_driver usb_keyboadr_driver = { .name = "usb_keyboard", //只是个名字而已,随便起 .probe = usb_keyboard_probe, //探测函数(后面细讲) .disconnect = usb_keyboard_disconnect, //设备卸载(断开)时调用(做清理工作) .id_table = usb_keyboard_table, //用来比较接口描述符,有则调用probe };
b.注册
usb_register(&usb_keyboadr_driver);2.具体分析
a.usb_keyboard_table
/*类(HID),子类(boot),协议(MOUSE)*/ static struct usb_device_id usb_keyboard_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD)}, //{USB_DEVICE(0x1234,0x5678)}, { } /* Terminating entry */ };
创建一个struct usb_device_id 的结构体,仅和USB接口的制定类型匹配,例如你插入键盘能识别并调用probe,但是你插入个鼠标就不能识别了(你按左键、右键什么的基本就什么反应也没有)。
b.usb_keboard_probe(重要)
看代码你会有似曾相识的感觉,没错相信你的感觉,USB驱动也在输入子系统里面(不清楚输入子系统可以看我之前写的触摸屏驱动三部曲,那对输入子系统讲的比较详细),但USB驱动也有自己与众不同之处。
1. 分配一个input_dev
uk_dev = input_allocate_device();
2. 设置
2.1 能产生哪类事件
set_bit(EV_KEY, uk_dev->evbit);
2.2 能产生哪些事件
for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], uk_dev->keybit);
在这里插将下简码表:
君可见usb_kbd_keycode[256] 这个大数组,没错他就是简码表,他是干啥用的?好了,让我们想一下我们按键为什么系统能够知道,我们按下一个A,系统可不知道你按下的是啥,于是我们给键盘编码,叫每一个按键拥有一个确定的的值,例如A就对应30这个数字。好好理解下简码表对后面我们分析中断函数十分重要。具体键值参考(linux-2.6.22.6\linux-2.6.22.6\include\linux\input.h)
3. 注册
input_register_device(uk_dev);
程序到这里和我们以前写的输入子系统的框架没有本质差别,以前硬件相关操作我们都是去操作寄存器,现在我们只要操作urb就行了(想操作寄存器也没有啊,一个USB,一共四根线,还一个电源一个地)
4. 硬件相关操作
/* a. 分配一个input_dev */ uk_dev = input_allocate_device(); /* b. 设置 */ /* b.1 能产生哪类事件 */ set_bit(EV_KEY, uk_dev->evbit); set_bit(EV_REP, uk_dev->evbit); /* b.2 能产生哪些事件 */ for (i = 0; i < 255; i++) set_bit(usb_kbd_keycode[i], uk_dev->keybit); /* c. 注册 */ input_register_device(uk_dev); /* d. 硬件相关操作 */ /* 数据传输3要素: 源,目的,长度 */ /* 源: USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 长度: */ len = endpoint->wMaxPacketSize; /* 目的: */ usb_buf = usb_buffer_alloc(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, usb_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);
c.usb_keyboard_irq(重要)
这个函数可以说是整个驱动的精华所在,由于编程能力有限,我改了很长时间才弄出来。
首先我们来看下内核自带的源代码是如何实现的.(我只是拷一个片段)
/*主要是上报是否按下了29, 42, 56,125, 97, 54,100,126这几个键码对应的键 *也就是 Left Control、 Left Shift 、Left Alt 、 Left GUI 、 Right Control、 Right Shift 、 Right Alt 、 Right GUI *这几个建。因为要处理大小写等问题。要先判断是否按下他们,然后再判断是否按下其余按键 */ 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 info("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 info("Unknown key (scancode %#x) pressed.", kbd->new[i]); } } input_sync(kbd->dev); memcpy(kbd->old, kbd->new, 8); //将new的数据拷到old里面去
下面我将用几个问题揭开驱动这层神秘的面纱。三问驱动:
问1:第一个循环什么意思?没有行不行?
答:主要是上报是否按下了29, 42, 56,125, 97, 54,100,126这几个键码对应的键
也就是 Left Control、 Left Shift 、Left Alt 、 Left GUI 、 Right Control、 Right Shift 、 Right Alt 、 Right GUI
这几个键。因为要处理大小写等问题。要先判断是否按下他们,然后再判断是否按下其余按键。右移i位,再与 1就能判断是否按下。这个和他们数据格式有关,看下个问题。没有这个循环肯定不行,例如你想中断某个程序要按Control+c,亲怎么办?所以这个循环是必须的。
问2:为什么从第二个循环要从i=2开始?
答:这个和数据的格式有关系,键盘发送给PC的数据每次8个字节 data0 data1 data2 data3 data4 data5 data6 data7 ,其中data0就是我上面第一个问题将的那8个按键( |--bit0: Left Control是否按下,按下为1
|--bit1: Left Shift 是否按下,按下为1
|--bit2: Left Alt 是否按下,按下为1
|--bit3: Left GUI 是否按下,按下为1
|--bit4: Right Control是否按下,按下为1
|--bit5: Right Shift 是否按下,按下为1
|--bit6: Right Alt 是否按下,按下为1
|--bit7: Right GUI 是否按下,按下为1
问3:怎么判断是否按下还是松开?
答:判断按下:第二个for循环里的第二个if里面
首先判断new[i]是否大于3,因为由于简码表可知usb_kbd_keycode[0]到usb_kbd_keycode[3]都是0(KEY_RESERVED),没有意义。并且在old[2]到old[8]中没有出现过,也就是和上次的键值不一样(memcpy函数把每次的键值进行拷贝,用来判断是否按下),如果按下了,肯定值会不一样的嘛(就是memescan没找到)。然后再判断一下键值是否为0,如何非0,则上报按键。如果是0,就是不知道的键按下(你会发现键码表中除了前4个还是有许多0的)。
判断松开:第二个for循环里的第一个if里面
和判断按下类似,如果你松开,你的new[]数组里面全是0,肯定和上次的old[]不一样,所以会进入if();然后继续判断是否是0,如果非0,上报按键已经松开。注意第二次判断用的都是old,因为你要上报的是上次是否松开,这次的new里面全是0.
四.程序改写
版本V1.2:
static void usb_keyboard_irq(struct urb *urb) { int i; /*主要是上报是否按下了29, 42, 56,125, 97, 54,100,126这几个键码对应的键 *也就是 Left Control、 Left Shift 、Left Alt 、 Left GUI 、 Right Control、 Right Shift 、 Right Alt 、 Right GUI *这几个建。因为要处理大小写等问题。要先判断是否按下他们,然后再判断是否按下其余按键 */ for (i = 0; i < 8; i++) input_report_key(uk_dev, usb_kbd_keycode[i + 224], (usb_buf[0] >> i) & 1); /*因为bit0上面已经写出,bit1保留,其余的按键在bit2到7*/ for (i = 2; i < len; i++) { if (pre_val!= usb_buf[i]) //判断两次值是否相等(松开和按下肯等都不会相等) { /* 按下时候数组中不等于0的那个才能元素进来,进来后直接进入if *在if中上报键值,并把键值给pre_val_change用来松手判断(如果直接赋值给pre_val会有BUG) *在没有松按键时会一直打印(实现重复事件) */ if(usb_buf[i]!=0) { input_report_key(uk_dev, usb_kbd_keycode[usb_buf[i]], 1); input_sync(uk_dev); pre_val_change=usb_buf[i]; } /*当松手时,usb_buf[i]值全为0,所以必然进入大if中, *由于每个值都等于0,所以每次都会进入这个else(进入8次) *进的最后要清零pre_val_change,要不然下次你再按此键值的时候 *pre_val!= usb_buf[i] 每次都会上报一次松开此键的BUG */ else { input_report_key(uk_dev, usb_kbd_keycode[pre_val], 0); input_sync(uk_dev); pre_val_change=0; //此处效果相同(pre_val_change=usb_buf[i];)要清零,否则会出现下次再按此键按不了的情况。 } } } pre_val=pre_val_change; //将change值赋给per_val用来判断是否按下 /* 重新提交urb */ usb_submit_urb(uk_urb, GFP_ATOMIC); }
看了上图你或许能够清楚的明白程序了,但是这里你会发现我引入了一个pre_val_change变量,因为我怕继续循环会造成BUG,因为当你上报按下还是松开的时候循环并没有结束(调这个BUG调了好久,才发现的),这将带来冗余的循环。(但对效率影响不大)