大话USB驱动之USB键盘驱动
来源:互联网 发布:linux杀掉进程的命令 编辑:程序博客网 时间:2024/04/26 08:27
转载请注明出处: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设备的中断端点urbuk_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);elseinfo("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调了好久,才发现的),这将带来冗余的循环。(但对效率影响不大)
- 大话USB驱动之USB键盘驱动
- 大话USB驱动之基础概念
- 大话USB驱动之总线驱动程序
- s5pv210-Linux驱动之USB键盘
- USB驱动之USB简介
- 25.3.2 USB键盘驱动
- linux usb键盘驱动详解
- usb驱动之设备驱动
- USB驱动之xhci
- 【驱动】USB驱动实例·串口驱动·键盘驱动
- USB驱动
- USB驱动
- usb 驱动
- usb驱动
- usb驱动
- usb 驱动
- usb驱动
- USB驱动--USB鼠标
- Gitblit进行Git管理
- 大话USB驱动之基础概念
- Android工程下的子目录解析
- 大话USB驱动之总线驱动程序
- 安卓2048源码解析
- 大话USB驱动之USB键盘驱动
- 第九周项目五设计一元一次方程类
- SSH+Oracle整合问题7,description The requested resource (/PetDogIMS/open) is not available.
- Struts2教程10:国际化
- 在Android使用WebService
- 一步步搭建corseek服务
- cocos2d中sprite动画接口及动画实现思路总结
- pure-ftpd - 启动参数
- SequenceFile