首页 > 代码库 > 大话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

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