大话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

),data1保留,data2--data7 是 普通按键,既然我们上面已经判断的data0了,那我们现在理所当然要从i=2开始判断 data2--data7了。这样也能解决Control+c的问题,先判断control在判断c。

问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调了好久,才发现的),这将带来冗余的循环。(但对效率影响不大)




4 0