天天看點

大話USB驅動之USB鍵盤驅動

轉載請注明出處:http://blog.csdn.net/ruoyunliufeng/article/details/25040049

一.總體框圖

大話USB驅動之USB鍵盤驅動

二.驅動代碼

/***************************************************************

*版權所有 (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,一共四根線,還一個電源一個地

大話USB驅動之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);
}
           
大話USB驅動之USB鍵盤驅動

       看了上圖你或許能夠清楚的明白程式了,但是這裡你會發現我引入了一個pre_val_change變量,因為我怕繼續循環會造成BUG,因為當你上報按下還是松開的時候循環并沒有結束(調這個BUG調了好久,才發現的),這将帶來備援的循環。(但對效率影響不大)

繼續閱讀