轉載請注明出處: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調了好久,才發現的),這将帶來備援的循環。(但對效率影響不大)