linux3.4.2 之usb鼠標驅動,鍵盤驅動

目錄

1  USB相關基本知識

2  USB鼠標編程指導

3 USB鼠標驅動程序完整源碼

4  USB鼠標驅動測試

5  USB鍵盤基本知識

6 USB鍵盤驅動程序 

4  USB鍵盤驅動測試


1  USB相關基本知識

摘自博客:https://www.cnblogs.com/lifexy/p/7634511.html

  • 在USB描述符中,從上到下分爲四個層次

USB設備描述符(usb_device_descriptor)、
USB配置描述符(usb_config_descriptor)、
USB接口描述符(usb_interface_descriptor)、
USB端點描述符(usb_endpoint_descriptor)、

一個設置描述符可以有多個配置描述符
一個配置描述符可以有多個接口描述符(比如聲卡驅動,就有兩個接口:錄音接口和播放接口)
一個接口描述符可以有多個端點描述符

  • USB設備描述符結構體如下所示:
struct usb_device_descriptor {
 __u8  bLength;                          //本描述符的size
 __u8  bDescriptorType;              //描述符的類型,這裏是設備描述符DEVICE
 __u16 bcdUSB;                           //指明usb的版本,比如usb2.0
 __u8  bDeviceClass;                 //類
 __u8  bDeviceSubClass;             //子類
 __u8  bDeviceProtocol;              //指定協議
 __u8  bMaxPacketSize0;            //端點0對應的最大包大小
 __u16 idVendor;                         //廠家ID
 __u16 idProduct;                        //產品ID
 __u16 bcdDevice;                       //設備的發佈號
 __u8  iManufacturer;                 //字符串描述符中廠家ID的索引
 __u8  iProduct;                         //字符串描述符中產品ID的索引
 __u8  iSerialNumber;                 //字符串描述符中設備序列號的索引
 __u8  bNumConfigurations;              //配置描述符的個數,表示有多少個配置描述符
} __attribute__ ((packed));

USB設備描述符位於USB設備結構體usb_device中的成員descriptor中

同樣地,配置、接口、端點描述符也是位於USB配置、接口、端點結構體中,不過這3個對於我們寫驅動的不是很常用

  • usb_device結構體如下所示:
struct usb_device {
   int devnum;           //設備號,是在USB總線的地址
   char devpath [16];       //用於消息的設備ID字符串
   enum usb_device_state state; //設備狀態:已配置、未連接等等
   enum usb_device_speed speed; //設備速度:高速、全速、低速或錯誤
  
   struct usb_tt *tt;       //處理傳輸者信息;用於低速、全速設備和高速HUB
   int ttport;           //位於tt HUB的設備口
  
   unsigned int toggle[2];    //每個端點的佔一位,表明端點的方向([0] = IN, [1] = OUT)  
   struct usb_device *parent;  //上一級HUB指針
   struct usb_bus *bus;       //總線指針
   struct usb_host_endpoint ep0; //端點0數據
   struct device dev;         //一般的設備接口數據結構
 
   struct usb_device_descriptor descriptor; //USB設備描述符,
   struct usb_host_config *config;       //設備的所有配置結構體,配置結構體裏包含了配置描述符
   struct usb_host_config *actconfig;     //被激活的設備配置
   struct usb_host_endpoint *ep_in[16];     //輸入端點數組
   struct usb_host_endpoint *ep_out[16];     //輸出端點數組
  
   char **rawdescriptors;             //每個配置的raw描述符
  
   unsigned short bus_mA;         //可使用的總線電流

   u8 portnum;               //父端口號
   u8 level;                //USB HUB的層數
  
   unsigned can_submit:1;         //URB可被提交標誌
   unsigned discon_suspended:1;      //暫停時斷開標誌
   unsigned persist_enabled:1;       //USB_PERSIST使能標誌
   unsigned have_langid:1;         //string_langid存在標誌
   unsigned authorized:1; 
   unsigned authenticated:1;
   unsigned wusb:1;             //無線USB標誌
   int string_langid;             //字符串語言ID
  
   /* static strings from the device */ //設備的靜態字符串
   char *product;               //產品名
   char *manufacturer;             //廠商名
   char *serial;                 //產品串號
  
   struct list_head filelist;         //此設備打開的usbfs文件
  #ifdef CONFIG_USB_DEVICE_CLASS
   struct device *usb_classdev;       //用戶空間訪問的爲usbfs設備創建的USB類設備
  #endif
  #ifdef CONFIG_USB_DEVICEFS
   struct dentry *usbfs_dentry;        //設備的usbfs入口
  #endif
  
   int maxchild;                     //(若爲HUB)接口數
   struct usb_device *children[USB_MAXCHILDREN];//連接在這個HUB上的子設備
   int pm_usage_cnt;                 //自動掛起的使用計數
   u32 quirks; 
   atomic_t urbnum;                   //這個設備所提交的URB計數
  
   unsigned long active_duration;         //激活後使用計時

  #ifdef CONFIG_PM                 //電源管理相關
   struct delayed_work autosuspend;       //自動掛起的延時
   struct work_struct autoresume;       //(中斷的)自動喚醒需求
   struct mutex pm_mutex;           //PM的互斥鎖 
  
   unsigned long last_busy;         //最後使用的時間
   int autosuspend_delay; 
   unsigned long connect_time;       //第一次連接的時間
  
   unsigned auto_pm:1;           //自動掛起/喚醒
   unsigned do_remote_wakeup:1;     //遠程喚醒
   unsigned reset_resume:1;       //使用復位替代喚醒
   unsigned autosuspend_disabled:1;   //掛起關閉
   unsigned autoresume_disabled:1;   //喚醒關閉
   unsigned skip_sys_resume:1;     //跳過下個系統喚醒
  #endif
   struct wusb_dev *wusb_dev;     //(如果爲無線USB)連接到WUSB特定的數據結構
  };
  • 配置描述符結構如下所示:
struct usb_config_descriptor {   
  __u8  bLength;                          //描述符的長度
  __u8  bDescriptorType;              //描述符類型的編號

  __le16 wTotalLength;                        //配置 所返回的所有數據的大小
  __u8  bNumInterfaces;              //配置 所支持的接口個數, 表示有多少個接口描述符
  __u8  bConfigurationValue;        //Set_Configuration命令需要的參數值
  __u8  iConfiguration;                        //描述該配置的字符串的索引值
  __u8  bmAttributes;                         //供電模式的選擇
  __u8  bMaxPower;                    //設備從總線提取的最大電流
 } __attribute__ ((packed));
  • 接口描述符結構如下所示: 

USB接口只處理一種USB邏輯連接。一個USB接口代表一個邏輯上的設備,比如聲卡驅動,就有兩個接口:錄音接口和播放接口

這可以在windows系統中看出,有時插入一個USB設備後,系統會識別出多個設備,並安裝相應多個的驅動。

struct usb_interface_descriptor {  
  __u8  bLength;                          //描述符的長度
  __u8  bDescriptorType;              //描述符類型的編號

  __u8  bInterfaceNumber;           //接口的編號
  __u8  bAlternateSetting;            //備用的接口描述符編號,提供不同質量的服務參數.
  __u8  bNumEndpoints;              //要使用的端點個數(不包括端點0), 表示有多少個端點描述符,比如鼠標就只有一個端點
  __u8  bInterfaceClass;              //接口類型,與驅動的id_table 
  __u8  bInterfaceSubClass;                 //接口子類型
  __u8  bInterfaceProtocol;                 //接口所遵循的協議
  __u8  iInterface;                        //描述該接口的字符串索引值
 } __attribute__ ((packed)

 它位於usb_interface->cur_altsetting->desc 這個成員結構體裏,

  •  usb_interface結構體如下所示:
struct usb_interface { 
 struct usb_host_interface *altsetting; /* 包含所有可用於該接口的可選設置的接口結構數組。每個 struct usb_host_interface 包含一套端點配置(即struct usb_host_endpoint結構所定義的端點配置。這些接口結構沒有特別的順序。*/
   
 struct usb_host_interface *cur_altsetting; /* 指向altsetting內部的指針,表示當前激活的接口配置*/
    
 unsigned num_altsetting; /* 可選設置的數量*/
   
 /* If there is an interface association descriptor then it will list the associated interfaces */ 
 struct usb_interface_assoc_descriptor *intf_assoc;
    

 int minor; /* 如果綁定到這個接口的 USB 驅動使用 USB 主設備號, 這個變量包含由 USB 核心分配給接口的次設備號. 這隻在一個成功的調用 usb_register_dev後纔有效。*/ 
  ... ...
}
  • cur_altsetting成員的結構體是usb_host_interface,如下: 
struct usb_host_interface {
    struct usb_interface_descriptor desc;   //當前被激活的接口描述符
    struct usb_host_endpoint *endpoint;   /* 這個接口的所有端點結構體的聯合數組*/
    char *string;                 /* 接口描述字符串 */
    unsigned char *extra;           /* 額外的描述符 */
    int extralen;
};
  • 端點描述符結構如下所示:
struct usb_endpoint_descriptor {
__u8  bLength;                          //描述符的長度
__u8  bDescriptorType;              //描述符類型的編號

__u8  bEndpointAddress;              //端點編號,比如端點1,就是1
__u8  bmAttributes;                  //端點的屬性, 比如中斷傳輸類型,輸入類型
__le16 wMaxPacketSize;               //一個端點的最大包大小,
__u8  bInterval;                     //間隔時間,用在中斷傳輸上,比如間隔時間查詢鼠標的數據

 
/* NOTE:  these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8  bRefresh;
__u8  bSynchAddress;

} __attribute__ ((packed));

 比如端點0,就位於usb_interface->cur_altsetting->desc->endpoint[0].desc

  • endpoint的結構體爲usb_host_endpoint,如下所示:
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端點描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp;//超快速端點描述符
struct list_head urb_list; //本端口對應的urb鏈表
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */

unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;//使能的話urb才能被提交到此端口
};

2  USB鼠標編程指導

  • 編寫usb鼠標驅動最核心的結構體就是定義一個usb_mouse與usb_driver結構體,該結構體成員如下
struct usb_mouse {
	struct usb_device *usbdev;/* USB鼠標是一種USB設備,需要內嵌一個USB設備結構體來描述其USB屬性 */  
	struct input_dev *inputdev;/* USB鼠標同時又是一種輸入設備,需要內嵌一個輸入設備結構體來描述其輸入設備的屬性 */  
	struct urb *urb;/* URB 請求包結構體,用於傳送數據 */  

	signed char *data;/* 普通傳輸用的地址 */  
	dma_addr_t data_dma;/* dma 傳輸用的地址 */  
};

static struct usb_driver usb_mouse_driver = {
	.name = "usb_mouse",
	.probe = usb_mouse_probe,
	.disconnect = usb_mouse_disconnect,
	.id_table = usb_mouse_id_table,
};
  • USB鼠標是一種USB設備,所需要爲其分配一個usb_device結構體,該結構體直接通過一下函數就可以獲取
mouse->usbdev = interface_to_usbdev(intf);//獲取usb_device結構體
  • USB數據控制的一個重要的結構體就是urb,該結構體初始化方式如下:
//分配,設置urb
mouse->urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(mouse->urb,        //urb結構體
                     mouse->usbdev,     //usb設備
                     pipe,              //端點管道
                     mouse->data,       //緩存區地址
                     endpoint->wMaxPacketSize,//數據長度
                     usb_mouse_irq,     //usb中斷處理函數
                     mouse,
                     endpoint->bInterval);//中斷間隔時間
mouse->data = usb_alloc_coherent(mouse->usbdev, 8, GFP_ATOMIC, &mouse->data_dma);//分配dma傳輸地址
mouse->urb->transfer_dma = mouse->data_dma;//設置DMA地址
mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//設置使用DMA地址

usb_submit_urb(mouse->urb, GFP_KERNEL);//提交urb
  • USB鼠標同時又是一種輸入設備,所以需要爲鼠標註冊一個input_device結構體
//1.分配input_device結構體
mouse->inputdev = input_allocate_device();
//2.1 設置按鍵事件與相對位移事件
mouse->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
//2.2 設置具體的按鍵事件
mouse->inputdev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | 
                      BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);
//2.3 設置鼠具體的相對位移事件
mouse->inputdev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) | BIT_MASK(REL_WHEEL);
//3.註冊
input_register_device(mouse->inputdev);
  • 當產生鼠標相關事件時,在中斷處理函數中上報事件
input_report_key(mouse->inputdev, BTN_LEFT,   mouse->data[0] & 0x01);//鼠標左鍵
input_report_key(mouse->inputdev, BTN_RIGHT,  mouse->data[0] & 0x02);//鼠標右鍵
input_report_key(mouse->inputdev, BTN_MIDDLE, mouse->data[0] & 0x04);//鼠標中輪
input_report_key(mouse->inputdev, BTN_SIDE,   mouse->data[0] & 0x08);//鼠標側邊按鈕
input_report_key(mouse->inputdev, BTN_EXTRA,  mouse->data[0] & 0x10);//鼠標擴展按鈕
input_report_rel(mouse->inputdev, REL_X,     mouse->data[1]);//鼠標水平位移
input_report_rel(mouse->inputdev, REL_Y,     mouse->data[2]);//鼠標垂直位移
input_report_rel(mouse->inputdev, REL_WHEEL, mouse->data[3]);//鼠標滾輪位移
input_sync(mouse->inputdev);
  • usb鼠標傳輸給底層驅動的數據協議,usb鼠標一次傳輸4字節數據D0-D3,每個字節含義如下:
D0 bit7:   1   表示 Y 座標的變化量超出-256~255的範圍,0表示沒有溢出
bit6:   1   表示 X 座標的變化量超出-256~255的範圍,0表示沒有溢出
bit5:   Y   座標變化的符號位,1表示負數,即鼠標向下移動 
bit4:   X   座標變化的符號位,1表示負數,即鼠標向左移動 
bit3:   恆爲1 
bit2:   1 表示中鍵按下 
bit1:   1 表示右鍵按下 
bit0:   1 表示左鍵按下 
D1 X座標變化量,與D0的bit4組成9位符號數,負數表示向左移,正數表右移。用補碼錶示變化量     0x01  向右移動    0xfe 向左移動
D2 Y座標變化量,與D0的bit5組成9位符號數,負數表示向下移,正數表上移。用補碼錶示變化量   0x01 向下移動   0xfe 向上移動
D3 滾輪變化

 

3 USB鼠標驅動程序完整源碼

//系統自帶的usb鼠標驅動位於:drivers/hid/usbhid/usbmouse.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>

struct usb_mouse {
	struct usb_device *usbdev;/* USB鼠標是一種USB設備,需要內嵌一個USB設備結構體來描述其USB屬性 */  
	struct input_dev *inputdev;/* USB鼠標同時又是一種輸入設備,需要內嵌一個輸入設備結構體來描述其輸入設備的屬性 */  
	struct urb *urb;/* URB 請求包結構體,用於傳送數據 */  

	signed char *data;/* 普通傳輸用的地址 */  
	dma_addr_t data_dma;/* dma 傳輸用的地址 */  
};

static struct usb_device_id usb_mouse_id_table[]={
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,  //USB類
                        USB_INTERFACE_SUBCLASS_BOOT, //USB子類
                        USB_INTERFACE_PROTOCOL_MOUSE)},//USB協議類型
};

static void usb_mouse_irq(struct urb * urb)
{
	struct usb_mouse *mouse = urb->context;
//	printk("mouse->data= %x,%x,%x,%x\n",mouse->data[0],mouse->data[1],mouse->data[2],mouse->data[3]);
    input_report_key(mouse->inputdev, BTN_LEFT,   mouse->data[0] & 0x01);//鼠標左鍵
	input_report_key(mouse->inputdev, BTN_RIGHT,  mouse->data[0] & 0x02);//鼠標右鍵
	input_report_key(mouse->inputdev, BTN_MIDDLE, mouse->data[0] & 0x04);//鼠標中輪
	input_report_key(mouse->inputdev, BTN_SIDE,   mouse->data[0] & 0x08);//鼠標側邊按鈕
	input_report_key(mouse->inputdev, BTN_EXTRA,  mouse->data[0] & 0x10);//鼠標擴展按鈕

	input_report_rel(mouse->inputdev, REL_X,     mouse->data[1]);//鼠標水平位移
	input_report_rel(mouse->inputdev, REL_Y,     mouse->data[2]);//鼠標垂直位移
	input_report_rel(mouse->inputdev, REL_WHEEL, mouse->data[3]);//鼠標滾輪位移

	input_sync(mouse->inputdev);
	//重新提交urb,以能夠響應下次鼠標事件
	usb_submit_urb(mouse->urb, GFP_KERNEL);
}


static int usb_mouse_probe (struct usb_interface *intf,const struct usb_device_id *id)
{
    struct usb_mouse *mouse;
    struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
    int pipe;

	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

    mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
    mouse->usbdev = interface_to_usbdev(intf);//獲取usb_device結構體

    //1.分配input_device結構體
    mouse->inputdev = input_allocate_device();
	//2.1 設置按鍵事件與相對位移事件
    mouse->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
	//2.2 設置具體的按鍵事件
	mouse->inputdev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | 
                      BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);
    //2.3 設置鼠具體的相對位移事件
    mouse->inputdev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) | BIT_MASK(REL_WHEEL);
	//3.註冊
	input_register_device(mouse->inputdev);

	//4.硬件相關操作
	//源:USB設備某個端點
	pipe = usb_rcvintpipe(mouse->usbdev, endpoint->bEndpointAddress);

	//分配,設置urb
    mouse->urb = usb_alloc_urb(0, GFP_KERNEL);
	usb_fill_int_urb(mouse->urb,        //urb結構體
                     mouse->usbdev,     //usb設備
                     pipe,              //端點管道
                     mouse->data,       //緩存區地址
                     endpoint->wMaxPacketSize,//數據長度
                     usb_mouse_irq,     //usb中斷處理函數
                     mouse,
                     endpoint->bInterval);//中斷間隔時間
    mouse->data = usb_alloc_coherent(mouse->usbdev, 8, GFP_ATOMIC, &mouse->data_dma);//分配dma傳輸地址
	mouse->urb->transfer_dma = mouse->data_dma;//設置DMA地址
	mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//設置使用DMA地址

	usb_submit_urb(mouse->urb, GFP_KERNEL);//提交urb

    usb_set_intfdata(intf, mouse);
	return 0;
}

static void usb_mouse_disconnect (struct usb_interface *intf)
{
    struct usb_mouse *mouse = usb_get_intfdata (intf);
	usb_set_intfdata(intf, NULL);
    if (mouse) {
        usb_kill_urb(mouse->urb);
		input_unregister_device(mouse->inputdev);
        input_free_device(mouse->inputdev);
		usb_free_urb(mouse->urb);
		usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
		kfree(mouse);
    }
}

static struct usb_driver usb_mouse_driver = {
	.name = "usb_mouse",
	.probe = usb_mouse_probe,
	.disconnect = usb_mouse_disconnect,
	.id_table = usb_mouse_id_table,
};

module_usb_driver(usb_mouse_driver);
MODULE_LICENSE("GPL");

4  USB鼠標驅動測試

  • 去掉系統自帶的鼠標驅動

-> Device Drivers
     -> HID Devices
           <>USB Human Interface Device (full HID) support

  • 重新make uImage,並燒錄到開發板
  • 啓動開發版後安裝usb驅動insmod usb_mouse_drv.ko ,此時會有如下輸出
usbcore: registered new interface driver usb_mouse  
  • 插入USB鼠標會有如下輸出
usb 1-1: new low-speed USB device number 2 using s3c2410-ohci           
usb 1-1: New USB device found, idVendor=28a0, idProduct=1185                      
usb 1-1: New USB device strings: Mfr=0, Product=1, SerialNumber=0                 
usb 1-1: Product: USB OPTICAL MOUSE                                               
input: Unspecified device as /devices/virtual/input/input1      
  • 此時執行hexdump /dev/iniput/event1 命令會有如下輸出
0000000 06aa 0000 4302 0008 0001 0110 0001 0000                                 
0000010 06aa 0000 4320 0008 0000 0000 0000 0000                                 
0000020 06aa 0000 94b4 000a 0001 0110 0000 0000                                 
0000030 06aa 0000 94ce 000a 0000 0000 0000 0000    

5  USB鍵盤基本知識

  • usb鍵盤按鍵數據協議

鍵盤採用中斷傳輸的方式傳輸鍵盤數據,每次傳輸8個字節數據,該8字節定義見下表,其中D0位是按鍵修飾符用於判斷是否有特殊按鍵按下(Shift,ctrl,win等),普通按鍵從D2開始,支持6個普通按鍵同時按下。

D0 D1 D2 D3 D4 D5 D6 D7
Modefier keys 保留 keycode1 keycode2 keycode3 keycode4 keycode5 keycode6
                                                   D0(Modefier keys)各bit含義(按下爲1)
BIT0 BIT1 BIT2 BIT3 BIT4 BIT5 BIT6 BIT7
Left Control Left Shift Left Alt Left meta Right Control Right Shift Right Alt Right meta
  • 按鍵按下情景分析

1.  按下a鍵時,USB鍵盤傳輸的數據爲:00 00 04 00 00 00 00 00.
(爲什麼按下a鍵盤爲04,可以參考USB HID to PS/2 Scan Code Translation Table)

2. 當檢測到 D2=04 時,調用input_report_key(kbd->inputdev,KEY_A, 1) 上報按鍵a按下事件,在include/linux/input.h中可以查到KEY_A = 30  (#define KEY_A 30)

3. 以上可知,當USB鍵盤傳輸的給內核的數據中D2 = 04時,驅動需要上報30(KEY_A)的事件
                      當USB鍵盤傳輸的給內核的數據中D2 = 05時,驅動需要上報48(KEY_B)的事件
                      ......
    由於鍵盤的按鍵特別多,而usb鍵盤傳內核的按鍵編碼與內核空間傳給用戶空間的按鍵編碼又不一樣,所以我們需要建立一個映射數組,以鍵盤傳輸的給內核的數據作爲該數組的下標,該下標對應的數值爲內核空間傳給用戶空間的按鍵編碼,如下所示:

unsigned char usb_kbd_keycode[256] = {
	  0,  0,  0,  0, KEY_A, KEY_B,............
};

此時當usb檢測到數據D2 = 4 時,上傳usb_kbd_keycode[4]按下事件就行了,到了這裏還有一個問題,這麼多的編碼一個個去對應太麻煩了,這時候就可以查看linux源碼中自帶的按鍵驅動drivers/hid/usbhid/usbkbd.c,裏面可以查看到映射數組:

static const 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
};
  • led控制狀態燈情景分析

1.在probe函數設置好中斷urb後,當按鍵事件發生時,會調用usb_kbd_event函數. (kbd->inputdev->event = usb_kbd_event;)
2.在usb_kbd_event函數中完成對led燈的控制,判斷流程如下:

  • usb鍵盤狀態燈顯示數據協議

狀態燈控制只需要1Byte數據進行控制,該數據各bit含義如下,我們需要做的就是控制BIT0-BIT2(3個狀態燈)

BIT0 BIT1 BIT2 BIT3 BIT4 BIT5-BIT7

NUM LOCK

小鍵盤數字鍵

CAPS LOCK

大小寫鎖定

SCROLL LOCK

滾動鎖定鍵

COMPOSE KANA CONSTANT
  • usb_ctrlrequest結構體分析
    struct usb_ctrlrequest {
    	__u8 bRequestType;
    	__u8 bRequest;
    	__le16 wValue;
    	__le16 wIndex;
    	__le16 wLength;
    } __attribute__ ((packed));
    bRequestType:
    
    BIT7 BIT6-BIT5 BIT4-BIT0
    BIT7=0時,表示後面的數據是從主控器發送到USB設備。在PC裏,就是從PC機發送到USB的設備。
    當D7等於1時,表示後面的數據是從USB設備發送到主控器。在PC裏,就是從USB設備發送到USB設備。
    0 是表示標準的請求。
    1 是表示類別的請求。
    2 是表示廠商的請求。
    3 是保留。
    0 是表示USB設備接收。
    1 是表示接口接收。
    2 是表示端點接收。
    3 是表示其它接收,不知道的。
    4-31是保留。

    bRequest  描述符的請求類型,其取值可以在include/linux/usb/ch9.h文件中查看,取值如下:

    #define USB_REQ_GET_STATUS		0x00
    #define USB_REQ_CLEAR_FEATURE		0x01
    #define USB_REQ_SET_FEATURE		0x03
    #define USB_REQ_SET_ADDRESS		0x05
    #define USB_REQ_GET_DESCRIPTOR		0x06
    #define USB_REQ_SET_DESCRIPTOR		0x07
    #define USB_REQ_GET_CONFIGURATION	0x08
    #define USB_REQ_SET_CONFIGURATION	0x09
    #define USB_REQ_GET_INTERFACE		0x0A
    #define USB_REQ_SET_INTERFACE		0x0B
    #define USB_REQ_SYNCH_FRAME		0x0C
    wValue:高字節表示描述符的類型,其取值可以在include/linux/usb/ch9.h文件中查看,取值如下。低字節表示描述符的索引。
#define USB_DT_DEVICE			0x01
#define USB_DT_CONFIG			0x02
#define USB_DT_STRING			0x03
#define USB_DT_INTERFACE		0x04
#define USB_DT_ENDPOINT			0x05
#define USB_DT_DEVICE_QUALIFIER		0x06
#define USB_DT_OTHER_SPEED_CONFIG	0x07
#define USB_DT_INTERFACE_POWER		0x08
wIndex:是根據不同的請求而設置不同的值。一般用來說明端點號或者說明接口標識
wLength是根據請求來決定下一階段發送數據的長度。
  • usb_ctrlrequest結構體初始化
kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;//設定傳輸方向、請求類型等
kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION; //指定請求類型
kbd->cr->wValue = cpu_to_le16(0x200);//即將寫到寄存器的數據
kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);//接口數量,也就是寄存器的偏移地址
kbd->cr->wLength = cpu_to_le16(1);//數據傳輸階段傳輸多少個字節
  • led相關事件設置
kbd->inputdev->evbit[0] = BIT_MASK(EV_LED);//設置led事件
kbd->inputdev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |BIT_MASK(LED_KANA);

6 USB鍵盤驅動程序 

//系統自帶驅動位於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 const 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
};

struct usb_kbd {
	struct usb_device *usbdev;/* USB鼠標是一種USB設備,需要內嵌一個USB設備結構體來描述其USB屬性 */  
	struct input_dev *inputdev;/* USB鼠標同時又是一種輸入設備,需要內嵌一個輸入設備結構體來描述其輸入設備的屬性 */  
	
    struct urb *irq_urb;/* 用於中斷傳輸的urb */  
    unsigned char old_data[8];
	signed char *keys_data;/* 普通傳輸用的地址 */  
	dma_addr_t keys_dma;/* dma 傳輸用的地址 */  

    struct urb *ctl_urb;/* 用於控制傳輸的urb */
    struct usb_ctrlrequest *cr;/*用於控制傳輸*/
    unsigned char *leds_data;
    unsigned char new_leds_data;
    dma_addr_t cr_dma; /*控制請求DMA緩衝地址*/ 
    dma_addr_t leds_dma;
    spinlock_t leds_lock;
};

static struct usb_device_id usb_kbd_id_table[]={
	{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,  //USB類
                        USB_INTERFACE_SUBCLASS_BOOT, //USB子類
                        USB_INTERFACE_PROTOCOL_KEYBOARD)},//USB協議類型
    { }						/* Terminating entry */
};

static void usb_kbd_irq(struct urb * urb)
{
	struct usb_kbd *kbd = urb->context;
    int i;
    // printk("data = %d,%d,%d,%d,%d,%d,%d,%d\n",
    //        kbd->keys_data[0],kbd->keys_data[1],kbd->keys_data[2],kbd->keys_data[3],
    //        kbd->keys_data[4],kbd->keys_data[5],kbd->keys_data[6],kbd->keys_data[7]);

    for (i = 0; i < 8; i++)//上傳crtl、shift、atl 等修飾按鍵
        input_report_key(kbd->inputdev, usb_kbd_keycode[i + 224], (kbd->keys_data[0] >> i) & 1);

    for(i=2;i<8;i++){
        if(kbd->keys_data[i] != kbd->old_data[i]){
            if(kbd->keys_data[i] )      //按下事件
                input_report_key(kbd->inputdev,usb_kbd_keycode[kbd->keys_data[i]], 1);   
            else  if(kbd->old_data[i]) //鬆開事件
                input_report_key(kbd->inputdev,usb_kbd_keycode[kbd->old_data[i]], 0);
        }
    }
    memcpy(kbd->old_data, kbd->keys_data, 8);
	input_sync(kbd->inputdev);
	//重新提交urb,以能夠響應下次鼠標事件
	usb_submit_urb(kbd->irq_urb, GFP_KERNEL);
}

static void usb_kbd_led(struct urb *urb)
{
 
}

/*如果有事件被響應,我們會調用事件處理層的event函數*/
static int usb_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    unsigned long flags;
    struct usb_kbd *kbd = input_get_drvdata(dev);
    // printk("usb_kbd_event\n");
    if (type != EV_LED)    //不是LED事件就返回
        return -1;
    spin_lock_irqsave(&kbd->leds_lock, flags);//上鎖
    //將當前的LED值保存在kbd->newleds中
	kbd->new_leds_data = (test_bit(LED_KANA,    dev->led) << 3) | 
                         (test_bit(LED_COMPOSE, dev->led) << 3) |
		                 (test_bit(LED_SCROLLL, dev->led) << 2) | 
                         (test_bit(LED_CAPSL,   dev->led) << 1) |
		                 (test_bit(LED_NUML,    dev->led));

    if (*(kbd->leds_data) == kbd->new_leds_data){
		return 0;
	}
    *(kbd->leds_data) = kbd->new_leds_data;//更新數據
    
    usb_submit_urb(kbd->ctl_urb, GFP_ATOMIC);//提交urb
    spin_unlock_irqrestore(&kbd->leds_lock, flags);//解鎖
    return 0;
}


static int usb_kbd_probe (struct usb_interface *intf,const struct usb_device_id *id)
{
    struct usb_kbd *kbd;
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe,i;

    spin_lock_init(&kbd->leds_lock);//初始化自旋鎖

    kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
    kbd->usbdev = interface_to_usbdev(intf);//獲取usb_device結構體
    kbd->inputdev = input_allocate_device();//分配input_device結構體
    kbd->irq_urb = usb_alloc_urb(0, GFP_KERNEL);//分配中斷urb
    kbd->ctl_urb = usb_alloc_urb(0, GFP_KERNEL);//分配控制urb
    kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);//分配控制請求描述符
    kbd->keys_data = usb_alloc_coherent(kbd->usbdev, 8, GFP_ATOMIC, &kbd->keys_dma);//分配中斷傳輸dma傳輸地址
    kbd->leds_data = usb_alloc_coherent(kbd->usbdev, 1, GFP_ATOMIC, &kbd->leds_dma);//分配控制傳輸dma傳輸地址

    //設置 input_device結構體
    kbd->inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED);//設置按鍵事件,重複按鍵事件與led事件
    kbd->inputdev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |BIT_MASK(LED_KANA);//設置LED事件
    for (i = 0; i < 255; i++)//設置具體的按鍵事件
		set_bit(usb_kbd_keycode[i], kbd->inputdev->keybit);
    clear_bit(0, kbd->inputdev->keybit);
    kbd->inputdev->event = usb_kbd_event;//當有事件產生時,調用usb_kbd_event函數
    input_register_device(kbd->inputdev);//註冊input_device結構體

    //獲取端點屬性
    interface = intf->cur_altsetting;
    endpoint = &interface->endpoint[0].desc;
    pipe = usb_rcvintpipe(kbd->usbdev, endpoint->bEndpointAddress);

	//設置 中斷urb
    usb_fill_int_urb(kbd->irq_urb,        //urb結構體
                     kbd->usbdev,     //usb設備
                     pipe,              //端點管道
                     kbd->keys_data,       //緩存區地址
                     endpoint->wMaxPacketSize,//數據長度
                     usb_kbd_irq,     //usb中斷處理函數
                     kbd,
                     endpoint->bInterval);//中斷間隔時間
    kbd->irq_urb->transfer_dma = kbd->keys_dma;//設置DMA地址
    kbd->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//設置使用DMA地址
    usb_submit_urb(kbd->irq_urb, GFP_KERNEL);//提交urb

    //設置 控制請求描述符usb_ctrlrequest
    kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;//設定傳輸方向、請求類型等
	kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION; //指定請求類型
	kbd->cr->wValue = cpu_to_le16(0x200);//即將寫到寄存器的數據
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);//接口數量,也就是寄存器的偏移地址
	kbd->cr->wLength = cpu_to_le16(1);//數據傳輸階段傳輸多少個字節

    //設置 控制urb
    usb_fill_control_urb(kbd->ctl_urb, 
                         kbd->usbdev, 
                         usb_sndctrlpipe(kbd->usbdev, 0),//the endpoint pipe
			             (void *) kbd->cr,//setup_packet buffer
                         kbd->leds_data, //transfer buffer
                         1,              //length of the transfer buffer
			             usb_kbd_led,    //pointer to the usb_complete_t function
                         kbd);
	kbd->ctl_urb->transfer_dma = kbd->leds_dma;
	kbd->ctl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    
    input_set_drvdata(kbd->inputdev, kbd);//設置私有數據
    usb_set_intfdata(intf, kbd);
    return 0;
}

static void usb_kbd_disconnect (struct usb_interface *intf)
{
    struct usb_kbd *kbd = usb_get_intfdata (intf);
	usb_set_intfdata(intf, NULL);
    if (kbd) {
        usb_free_urb(kbd->irq_urb);
        usb_free_urb(kbd->ctl_urb);
        kfree(kbd->cr);
        usb_free_coherent(kbd->usbdev, 8, kbd->keys_data, kbd->keys_dma);
        usb_free_coherent(kbd->usbdev, 1, kbd->leds_data, kbd->leds_dma);

        input_unregister_device(kbd->inputdev);
        usb_kill_urb(kbd->irq_urb);
        usb_kill_urb(kbd->ctl_urb);
		kfree(kbd);
    }
}

static struct usb_driver usb_kbd_driver = {
	.name = "usb_kbd",
	.probe = usb_kbd_probe,
	.disconnect = usb_kbd_disconnect,
	.id_table = usb_kbd_id_table,
};

module_usb_driver(usb_kbd_driver);
MODULE_LICENSE("GPL");

 

7  USB鍵盤驅動測試

  • 去掉系統自帶的鼠標驅動

-> Device Drivers
     -> HID Devices
           <>USB Human Interface Device (full HID) support

  • 重新make uImage,並燒錄到開發板。
  • 啓動開發版後安裝usb驅動insmod usb_kbd_drv.ko。makefile文件如下:
KERN_DIR = /home/ningjw/linux-3.4.2

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= usb_kbd_drv.o
  • 驅動測試
cat /dev/tty1        //可在LCD屏幕上看到按鍵值
hexdump event0       //可在終端看到事件上傳
  • 使用上節的鍵盤驅動在LCD終端打印命令行
vi  /etc/inittab                //修改inittab, inittab:配置文件,用於啓動init進程時,讀取inittab
添加->tty1::askfirst:-/bin/sh   //將sh進程(命令行)輸出到tty1裏,也就是使LCD輸出信息

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章