本節目的:html
根據上節寫的USB鼠標驅動,來依葫蘆畫瓢寫出鍵盤驅動linux
1.首先咱們經過上節的代碼中修改,來打印下鍵盤驅動的數據究竟是怎樣的c++
先來回憶下,咱們以前寫的鼠標驅動的id_table是這樣:windows
因此咱們要修改id_table,使這個驅動爲鍵盤的驅動,以下圖所示:數組
而後修改中斷函數,經過printk()打印數據:緩存
咱們先按下按鍵A爲例,打印出0x04,以下圖:框架
咱們再同時按下按鍵A和S,打印出0x04,0X16, 以下圖:函數
顯然這些普通按鍵都是從buf[2]開始的,那第一個數組到底又存什麼值?post
咱們按完全部鍵盤按鍵,發現只有8個按鍵會打印在buf[0]裏,以下圖所示:學習
因此buf[0]是用來保存鍵盤的特定功能的鍵,而buf[1]多是個保留鍵,沒有用到的,buf[2]~buf[7]是普通按鍵,好比ABCD,1234,F1,F2等等,能支持最多6個按鍵同時按下。
2.那麼每一個按鍵的數據又是怎麼定義的?
2.1好比咱們按下按鍵A,爲何打印0X04?
咱們找到輸入子系統(input.h)中按鍵A定義的值,它對應的倒是30,看來不是直接調用的,以下圖:
咱們再來參考內核自帶的USB鍵盤驅動 (/drivers/hid/usbhid/usbkbd.c)
發現它的中斷函數中有個鍵盤描述碼錶(其中0表示保留的意思):
發現該數組的0X04就是0X30,看來要寫個鍵盤驅動,還須要上面的數組才行.
那麼問題又來了,若是咱們按下左alt鍵,buf[0]中會出現0x04,若是也代入到鍵盤描述碼錶中,顯然就會看成鍵盤按鍵A來使用。
2.2咱們來分析內核的鍵盤中斷函數是如何處理的:
發現有這麼一句:
for (i = 0; i < 8; i++) input_report_key(kbd->dev, usb_kbd_keycode[i+ 224], (kbd->new[0] >> i) & 1);
其中kbd->new表示的就是鍵盤數據數組,它將buf[0]的每一位統統以usb_kbd_keycode[i+ 224]的形式上傳到按鍵事件中
顯然咱們的buf[0]的0X04就是上傳的usb_kbd_keycode[4+ 224]
2.3咱們來看看usb_kbd_keycode[226]裏的數據對應的究竟是不是左ALT鍵
找到usb_kbd_keycode[226]=56:
而後再進入input.h,找到56的定義,恰好就是KEY_LEFTALT(左邊的alt鍵)
3.接下來再來仔細分析下內核自帶的USB鍵盤驅動usbkbd.c裏的中斷函數:
代碼以下:
static void usb_kbd_irq(struct urb *urb) { struct usb_kbd *kbd = urb->context; int i; switch (urb->status) { // 只有urb->status==0時,說明數據傳輸成功 case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } for (i = 0; i < 8; i++) //上傳crtl、shift、atl、windows 等按鍵 input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); for (i = 2; i < 8; i++) { //上傳普通按鍵 /*經過上個狀態的按鍵數據kbd->old[i]的非0值,來查找當前狀態的按鍵數據,若沒有找到,說明已經鬆開了該按鍵 */ if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) { if (usb_kbd_keycode[kbd->old[i]]) //再次判斷鍵盤描述碼錶的值是否非0 input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); //上傳鬆開事件 else info("Unknown key (scancode %#x) released.", kbd->old[i]); } /*經過當前狀態的按鍵數據kbd->new[i]的非0值,來查找上個狀態的按鍵數據,若沒有找到,說明已經按下了該按鍵 */ if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) { if (usb_kbd_keycode[kbd->new[i]]) //再次判斷鍵盤描述碼錶的值是否非0 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); //更新上個狀態值 resubmit: i = usb_submit_urb (urb, GFP_ATOMIC); if (i) err ("can't resubmit intr, %s-%s/input0, status %d", kbd->usbdev->bus->bus_name, kbd->usbdev->devpath, i); }
3.1上面獲取普通按鍵時,爲何不直接判斷非0,要判斷按鍵數據> 3?
以前咱們就分析了,當按鍵數據=0X0一、0X02時,表明的是特定功能的鍵(crtl、shift),是屬於buf[0]的數據
其中memscan()是用來匹配上次按鍵和當前按鍵的數據,它這麼作的緣由是怕上個buf[]和當前buf[]的數據錯位,這裏就不作詳細分析了
一切迎刃而解,咱們只須要將本身的代碼也經過這個碼錶添加全部按鍵按鍵事件,而後再在鍵盤中斷函數中根據數據來上傳事件便可
4.本節鍵盤代碼以下:
#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 *myusb_kbd_dev; //input_dev static unsigned char *myusb_kbd_buf; //虛擬地址緩存區 static dma_addr_t myusb_kbd_phyc; //DMA緩存區; static __le16 myusb_kbd_size; //數據包長度 static struct urb *myusb_kbd_urb; //urb static const unsigned char usb_kbd_keycode[252] = { 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 }; //鍵盤碼錶共有252個數據 void my_memcpy(unsigned char *dest,unsigned char *src,int len) //複製緩存 { while(len--) { *dest++= *src++; } } static void myusb_kbd_irq(struct urb *urb) //鍵盤中斷函數 { static unsigned char buf1[8]={0,0,0,0,0,0,0,0}; int i; /*上傳crtl、shift、atl、windows 等按鍵*/ for (i = 0; i < 8; i++) if(((myusb_kbd_buf[0]>>i)&1)!=((buf1[0]>>i)&1)) { input_report_key(myusb_kbd_dev, usb_kbd_keycode[i + 224], (myusb_kbd_buf[0]>> i) & 1); input_sync(myusb_kbd_dev); //上傳同步事件 } /*上傳普通按鍵*/ for(i=2;i<8;i++) if(myusb_kbd_buf[i]!=buf1[i]) { if(myusb_kbd_buf[i] ) //按下事件 input_report_key(myusb_kbd_dev,usb_kbd_keycode[myusb_kbd_buf[i]], 1); else if(buf1[i]) //鬆開事件 input_report_key(myusb_kbd_dev,usb_kbd_keycode[buf1[i]], 0); input_sync(myusb_kbd_dev); //上傳同步事件 } my_memcpy(buf1, myusb_kbd_buf, 8); //更新數據 usb_submit_urb(myusb_kbd_urb, GFP_KERNEL); } static int myusb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id) { volatile unsigned char i; struct usb_device *dev = interface_to_usbdev(intf); //設備 struct usb_endpoint_descriptor *endpoint; struct usb_host_interface *interface; //當前接口 int pipe; //端點管道 interface=intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; //當前接口下的端點描述符 printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); /* 1)分配一個input_dev結構體 */ myusb_kbd_dev=input_allocate_device(); /* 2)設置input_dev支持 按鍵事件*/ set_bit(EV_KEY, myusb_kbd_dev->evbit); set_bit(EV_REP, myusb_kbd_dev->evbit); //支持重複按功能 for (i = 0; i < 252; i++) set_bit(usb_kbd_keycode[i], myusb_kbd_dev->keybit); //添加全部鍵 clear_bit(0, myusb_kbd_dev->keybit); /* 3)註冊input_dev結構體*/ input_register_device(myusb_kbd_dev); /* 4)設置USB鍵盤數據傳輸 */ /*->4.1)經過usb_rcvintpipe()建立一個端點管道*/ pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); /*->4.2)經過usb_buffer_alloc()申請USB緩衝區*/ myusb_kbd_size=endpoint->wMaxPacketSize; myusb_kbd_buf=usb_buffer_alloc(dev,myusb_kbd_size,GFP_ATOMIC,&myusb_kbd_phyc); /*->4.3)經過usb_alloc_urb()和usb_fill_int_urb()申請並初始化urb結構體 */ myusb_kbd_urb=usb_alloc_urb(0,GFP_KERNEL); usb_fill_int_urb (myusb_kbd_urb, //urb結構體 dev, //usb設備 pipe, //端點管道 myusb_kbd_buf, //緩存區地址 myusb_kbd_size, //數據長度 myusb_kbd_irq, //中斷函數 0, endpoint->bInterval); //中斷間隔時間 /*->4.4) 由於咱們2440支持DMA,因此要告訴urb結構體,使用DMA緩衝區地址*/ myusb_kbd_urb->transfer_dma =myusb_kbd_phyc; //設置DMA地址 myusb_kbd_urb->transfer_flags =URB_NO_TRANSFER_DMA_MAP; //設置使用DMA地址 /*->4.5)使用usb_submit_urb()提交urb*/ usb_submit_urb(myusb_kbd_urb, GFP_KERNEL); return 0; } static void myusb_kbd_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); //設備 usb_kill_urb(myusb_kbd_urb); usb_free_urb(myusb_kbd_urb); usb_buffer_free(dev, myusb_kbd_size, myusb_kbd_buf,myusb_kbd_phyc); input_unregister_device(myusb_kbd_dev); //註銷內核中的input_dev input_free_device(myusb_kbd_dev); //釋放input_dev } static struct usb_device_id myusb_kbd_id_table [] = { { USB_INTERFACE_INFO( USB_INTERFACE_CLASS_HID, //接口類:hid類 USB_INTERFACE_SUBCLASS_BOOT, //子類:啓動設備類 USB_INTERFACE_PROTOCOL_KEYBOARD) }, //USB協議:鍵盤協議 }; static struct usb_driver myusb_kbd_drv = { .name = "myusb_kbd", .probe = myusb_kbd_probe, .disconnect = myusb_kbd_disconnect, .id_table = myusb_kbd_id_table, }; /*入口函數*/ static int myusb_kbd_init(void) { usb_register(&myusb_kbd_drv); return 0; } /*出口函數*/ static void myusb_kbd_exit(void) { usb_deregister(&myusb_kbd_drv); } module_init(myusb_kbd_init); module_exit(myusb_kbd_exit); MODULE_LICENSE("GPL");
5.測試運行
5.1 從新設置編譯內核(去掉默認的hid_USB驅動)
make menuconfig ,進入menu菜單從新設置內核參數:
進入-> Device Drivers -> HID Devices
<> USB Human Interface Device (full HID) support //hid:人機交互的USB驅動,好比鼠標,鍵盤等
而後make uImage 編譯內核
將新的鍵盤驅動模塊放入nfs文件系統目錄中
5.2而後燒寫內核,裝載觸摸屏驅動模塊
以下圖,當咱們插上USB鍵盤時,能夠看到該VID和PID,和電腦上的鍵盤的參數同樣
5.3使用cat tty1進程測試
5.4 使用exec 0</dev/tty1測試
(exec命令詳解入口地址: http://www.cnblogs.com/lifexy/p/7553228.html)
以下圖,就能經過板子上的鍵盤來操做了
下章學習: