天天看點

USB原理及驅動架構介紹、編寫

  • 本文環境:
  • JZ2440V3開發闆
  • Linux3.4.2核心
  • arm-linux-gcc4.3.2編譯器
  • 參考資料:
  • ​​2_USB驅動程式之USB總線驅動程式 (100ask.net)​​
  • ​​19.Linux-USB總線驅動分析 - 諾謙​​​

1、幾個常見疑惑?

  • 為什麼一插上就會有提示資訊?

    是因為windows自帶了USB總線驅動程式;

  • 那USB總線驅動程式是幹嘛用的?
  • 識别USB裝置;
  • 給USB裝置找到并安裝對應的驅動程式;
  • 提供USB的讀寫函數。
首先,新接入的USB裝置的預設位址(編号)為0,再未配置設定新編号前,PC主機使用0位址和它通信。然後USB總線驅動程式都會給它配置設定一個位址(編号)。PC機想通路USB總線上某個USB裝置時,發出的指令都含有對應的位址(編号)。
  • USB的結構是怎樣的?
  • USB是一種主從結構。主機叫做Host,從機叫做Device,所有的USB傳輸,都是從USB主機這方發起;USB裝置沒有“主動”通知USB主機的能力。
  • 例如:USB滑鼠滑動一下立刻産生資料,但是它還沒有能力通過OC機來讀資料,隻能被動地等待PC機來讀。
  • USB可以熱拔插的硬體原理
  • 在USB集線器(hub)的每個下遊端口的D+和D-上,分别接了一個15K歐姆的下拉電阻到地。這樣,在集線器的端口懸空時,就被這兩個下拉電阻拉到了低電平。
  • 而在USB裝置端,在D+或者D-上接了1.5k歐姆上拉電阻,對于全速和高速裝置,上拉電阻是接在D+上;而低速裝置則是上拉電阻接在D-上。
  • 這樣,當裝置插入到集線器時,由1.5k的上拉電阻和15k的下拉電阻分呀,結果就将差分資料線中的一條拉高了。集線器檢測到這個狀态後,它就報告給USB主要制器(或者通過它上一層的集線器報告給USB主要制器),這樣就檢測到裝置的插入了。
  • USB高速裝置先是被識别為全速裝置,然後通過HOST和DEVICE兩者之間的确認,再切換到告訴模式的。在高速模式下,是電流傳輸模式,這時将D+上的上拉電阻斷開。
  • USB的4大傳輸類型:
  • 控制傳輸
  • 是每一個USB裝置必須支援的,通常用來擷取裝置描述符、設定裝置的狀态等等。一個USB裝置從插入到最後的拔出這個過程一定會産生控制傳輸(即便這個USB裝置不能被這個系統支援)。
  • 中斷傳輸
  • 支援中斷傳輸的典型裝置有USB滑鼠、USB鍵盤等等。中斷傳輸不是說我的裝置真正發出一個中斷,然後主機會來讀取資料。它其實是一種輪詢的方式來完成資料的通信。USB裝置會在裝置驅動程式中設定一個參數叫做interval,它是endpoint的一個成員。interval是間隔時間的意思,表示我這個裝置希望主機多長時間來輪詢自己,隻要這個值确定了之後,我主機就會周期性來檢視有沒有資料需要處理。
  • 批量處理
  • 支援批量傳輸最典型的裝置就是U盤,它進行大數量的資料傳輸,能夠保證資料的準确性,但是時間不是固定的。
  • 實時傳輸
  • USB攝像頭就是實時傳輸裝置的典型代表,它同樣進行大量資料的傳輸,資料的準确性無法保證,但是對傳輸延遲非常敏感,也就是說對實時性要求比較高
  • USB端點:
  • 每個USB裝置與主機會有若幹個通信的“端點”。每個端點都有個端點号,除了端點0外,每一個端點隻能工作在一種傳輸類型(控制傳輸、中斷傳輸、批量傳輸、實時傳輸)下,一個傳輸方向。
  • 傳輸方向都是基于USB主機的立場說的,比如:滑鼠的資料是從滑鼠傳到PC機,對應的端點稱為“中斷輸入端點”。
  • 端點0是裝置的預設控制端點,既能輸出也能輸入,主要用于USB裝置的識别過程。
  • USB驅動整體架構:
  • USB原理及驅動架構介紹、編寫
  • USB主機控制器類型

    要想成為一個USB主機,硬體上就必須要有USB主機控制器,USB主機控制器又分為4種接口:

  • OHCI(Open Host Controller Inerface):微軟主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的硬體簡單,軟體複雜。
  • UHCI(Universal Host Controller Interface):Intel主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的軟體簡單,硬體複雜。
  • EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)。
  • xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9針腳設針,同時也支援USB2.0、1.1等。

2、USB總線驅動分析

2.1 USB描述符的層次及定義

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-OvFI8va0-1627907220913)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210731131536533.png)]

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

一個裝置描述符可以有多個配置描述符;

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

一個接口描述符可以有多個端點描述符;

2.1.1 USB裝置描述符(usb_device_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特定的資料結構
};      

2.2.2 USB配置描述符

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

  __le16 wTotalLength;               //配置所傳回的所有資料的大小
  __u8  bNumInterfaces;              //配置所支援的接口個數, 表示有多少個接口描述符
  __u8  bConfigurationValue;         //Set_Configuration指令需要的參數值
  __u8  iConfiguration;              //描述該配置的字元串的索引值
  __u8  bmAttributes;                //供電模式的選擇
  __u8  bMaxPower;                   //裝置從總線提取的最大電流
} __attribute__ ((packed));      

2.2.3 接口描述符(邏輯裝置)

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 包含一套端點配置(即struct usb_host_endpoint結構所定義的端點配置。這些接口結構沒有特别的順序。*/
    struct usb_host_interface *altsetting;
    
    /* 指向altsetting内部的指針,表示目前激活的接口配置*/
    struct usb_host_interface *cur_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;
    
    /* 如果綁定到這個接口的 USB 驅動使用 USB 主裝置号, 這個變量包含由 USB 核心配置設定給接口的次裝置号. 這隻在一個成功的調用 usb_register_dev後才有效。*/ 
    int minor; 
    ... ...
}      
  • 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;
};      

2.2.4 端點描述符

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.2 USB總線驅動如何識别裝置

由于核心自帶了USB驅動,是以我們先插入一個USB鍵盤到開發闆上看列印資訊發現以下字段:

USB原理及驅動架構介紹、編寫

如下圖,找到第一段話是位于drivers/usb/core/hub.c的第2186行:

USB原理及驅動架構介紹、編寫

這個hub其實就是我們的USB主機控制器的集線器,用來管理多個USB接口

2.2.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函數裡

它又是被誰調用的,如下圖所示,我們搜尋到它是通過hub_thread()函數調用的

USB原理及驅動架構介紹、編寫

hub_thread()函數如下:

static int hub_thread(void *__unused)
{

do {
       hub_events();       //執行一次hub事件函數
       wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());                              //(1).每次執行一次hub事件,都會進入一次等待事件中斷函數
       try_to_freeze();            
} while (!kthread_should_stop() || !list_empty(&hub_event_list));

pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}      

從上面函數中得到, 要想執行hub_events(),都要等待khubd_wait這個中斷喚醒才行。

2.2.2 搜尋”khubd_wait”,看看是被誰喚醒

找到該中斷在kick_khubd()函數中喚醒,代碼如下:

static void kick_khubd(struct usb_hub *hub)
{
       unsigned long       flags;
       to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
 
       spin_lock_irqsave(&hub_event_lock, flags);
       if (list_empty(&hub->event_list)) {
              list_add_tail(&hub->event_list, &hub_event_list);
              wake_up(&khubd_wait);                     //喚醒khubd_wait這個中斷
       }

       spin_unlock_irqrestore(&hub_event_lock, flags);
}      

2.2.3 繼續搜尋kick_khubd,發現被hub_irq()函數中調用

顯然,就是當USB裝置插入後,D+或D-就會被拉高,然後USB主機控制器就會産生一個hub_irq中斷.

2.2.4 分析hub_port_connect_change()函數如何連接配接端口

static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{ 
  ... ...
  udev = usb_alloc_dev(hdev, hdev->bus, port1);     //(1)注冊一個usb_device,然後會放在usb總線上

  usb_set_device_state(udev, USB_STATE_POWERED); //設定注冊的USB裝置的狀态标志
  ... ...

  choose_address(udev);                              //(2)給新的裝置配置設定一個位址編号
   status = hub_port_init(hub, udev, port1, i);      //(3)初始化端口,與USB裝置建立連接配接
  ... ...

  status = usb_new_device(udev);                 //(4)建立USB裝置,與USB裝置驅動連接配接
  ... ...
}      

是以最終流程圖如下:

USB原理及驅動架構介紹、編寫

2.2.5 進入hub_port_connect_change()->usb_alloc_dev(),看它是怎麼設定usb_device

usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
    struct usb_device *dev;
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);   //配置設定一個usb_device裝置結構體

       ... ...
    device_initialize(&dev->dev);   //初始化usb_device
    dev->dev.bus = &usb_bus_type;   //(1)設定usb_device的成員device->bus等于usb_bus總線

    dev->dev.type = &usb_device_type;    //設定usb_device的成員device->type等于usb_device_type
                                   
        ... ...
 
    return dev;                         //傳回一個usb_device結構體
}      
  • 在第17行上,設定device成員,主要是用來後面8.2小節,注冊usb總線的device表上.
  • 其中usb_bus_type是一個全局變量, 它和我們之前學的platform平台總線相似,屬于USB總線, 是Linux中bus的一種.
  • 如下圖所示,每當建立一個USB裝置,或者USB裝置驅動時,USB總線都會調用match成員來比對一次,使USB裝置和USB裝置驅動聯系起來.
USB原理及驅動架構介紹、編寫

usb_bus_type結構體如下:

struct bus_type usb_bus_type = {
    .name =        "usb",            //總線名稱,存在/sys/bus下
    .match =    usb_device_match, //比對函數,比對成功就會調用usb_driver驅動的probe函數成員
    .uevent =   usb_uevent,     //事件函數
    .suspend =  usb_suspend,   //休眠函數
    .resume =   usb_resume,    //喚醒函數
};      

2.2.6 進入hub_port_connect_change()->choose_address(),看它是怎麼配置設定位址編号的

static void choose_address(struct usb_device *udev)
{
    int devnum;
    struct usb_bus    *bus = udev->bus;
    devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
    //在bus->devnum_next~128區間中,循環查找下一個非0(沒有裝置)的編号

    if (devnum >= 128)                 //若編号大于等于128,說明沒有找到空餘的位址編号,從頭開始找
        devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
    bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);  //設定下次尋址的區間+1
    if (devnum < 128) {
        set_bit(devnum, bus->devmap.devicemap);      //設定位
        udev->devnum = devnum;                 
    }
}      

從上面代碼中分析到每次的位址編号是連續加的,USB接口最大能接127個裝置,我們連續插拔兩次USB鍵盤,也可以看出,如下圖所示:

USB原理及驅動架構介紹、編寫

2.2.7 再看hub_port_connect_change()->hub_port_init()函數是如何來實作連接配接USB裝置的

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{
    ... ...
    for (j = 0; j < SET_ADDRESS_TRIES; ++j){
        retval = hub_set_address(udev);     //(1)設定位址,告訴USB裝置新的位址編号
        if (retval >= 0)
            break;
        msleep(200);
    }
    retval = usb_get_device_descriptor(udev, 8);   //(2)獲得USB裝置描述符前8個位元組
    ... ...
    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);  //重新擷取裝置描述符資訊
    ... ...
}      
  • 上面第6行中,hub_set_address()函數主要是用來告訴USB裝置新的位址編号,hub_set_address()函數如下:
static int hub_set_address(struct usb_device *udev)
{
       int retval;
       ... ...
       retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);                                                //(1.1)等待傳輸完成
    if (retval == 0) {              //設定新的位址,傳輸完成,傳回0
              usb_set_device_state(udev, USB_STATE_ADDRESS);  //設定狀态标志
              ep0_reinit(udev);
       }
return retval;
}      
  • usb_control_msg()函數就是用來讓USB主機控制器把一個控制封包發給USB裝置,如果傳輸完成就傳回0.其中參數udev表示目标裝置;使用的管道為usb_sndaddr0pipe(),也就是預設的位址0加上控制端點号0; USB_REQ_SET_ADDRESS表示指令碼,既設定位址; udev->devnum表示要設定目标裝置的裝置号;允許等待傳輸完成的時間為5秒,因為USB_CTRL_SET_TIMEOUT定義為5000。
  • 上面第12行中,usb_get_device_descriptor()函數主要是擷取目标裝置描述符前8個位元組,為什麼先隻開始讀取8個位元組?是因為開始時還不知道對方所支援的信包容量,這8個位元組是每個裝置都有的,後面再根據裝置的資料,通過usb_get_device_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));      

2.2.8 再看hub_port_connect_change()->usb_new_device()函數是如何來建立USB裝置的

int usb_new_device(struct usb_device *udev)
{
   ... ...
   err = usb_get_configuration(udev);           //(1)擷取配置描述塊
  ... ...
  err = device_add(&udev->dev);     // (2)把device放入bus的dev連結清單中,并尋找對應的裝置驅動
}      
  1. 其中usb_get_configuration()函數如下,就是擷取各個配置
int   usb_get_configuration(struct usb_device *dev)
{
  ... ...
      /* USB_MAXCONFIG 定義為8,表示裝置描述塊下有最多不能超過8個配置描述塊 */
      /*ncfg表示 裝置描述塊下 有多少個配置描述塊 */
    if (ncfg > USB_MAXCONFIG) {
        dev_warn(ddev, "too many configurations: %d, "
                      "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
        dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
    }
    ... ...
    
    for (cfgno = 0; cfgno < ncfg; cfgno++){   //for循環,從USB裝置裡依次讀入所有配置描述塊
          //每次先讀取USB_DT_CONFIG_SIZE個位元組,也就是9個位元組,暫放到buffer中
          result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
                                  
          ... ...

          //通過wTotalLength,知道實際資料大小
          length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);         

          bigbuffer = kmalloc(length, GFP_KERNEL);  //然後再來配置設定足夠大的空間
          ... ...

          //在調用一次usb_get_descriptor,把整個配置描述塊讀出來,放到bigbuffer中
          result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
                                 
          ... ...

          //再将bigbuffer位址放在rawdescriptors所指的指針數組中
          dev->rawdescriptors[cfgno] = bigbuffer;   

          result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],

        bigbuffer, length);         //最後在解析每個配置塊
    }
  ... ...
}      
  1. 其中device_add ()函數如下
int   usb_get_configuration(struct usb_device *dev)
{
    dev = get_device(dev);         //使dev等于usb_device下的device成員
    ... ...
    if ((error = bus_add_device(dev))) // 把這個裝置添加到dev->bus的device表中
        goto BusError;
    ... ...
    bus_attach_device(dev);           //來比對對應的驅動程式
    ... ...
}      

當bus_attach_device()函數比對成功,就會調用驅動的probe函數

2.2.9 再看usb_bus_type這個的成員usb_device_match函數是如何比對的

USB原理及驅動架構介紹、編寫

usb_device_match函數如下所示:

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
    if (is_usb_device(dev)) {                       //判斷是不是USB裝置
      if (!is_usb_device_driver(drv))
         return 0;
      return 1;
    }
    else
    {       //否則就是USB驅動或者USB裝置的接口
        struct usb_interface *intf;
        struct usb_driver *usb_drv;
        const struct usb_device_id *id;           

        if (is_usb_device_driver(drv))   //如果是USB驅動,就不需要比對,直接return
         return 0; 

        intf = to_usb_interface(dev);               //擷取USB裝置的接口
        usb_drv = to_usb_driver(drv);                    //擷取USB驅動
        id = usb_match_id(intf, usb_drv->id_table);  //比對USB驅動的成員id_table
        if (id)
           return 1;

        id = usb_match_dynamic_id(intf, usb_drv);
        if (id)
          return 1;
   }
   return 0;
}      

顯然就是比對USB驅動的id_table

2.2.10 USB驅動的id_table該如何定義

id_table的結構體為usb_device_id,如下所示:

struct usb_device_id {
        
       __u16             match_flags;   //與usb裝置比對那種類型?比較類型的宏如下:
         //USB_DEVICE_ID_MATCH_INT_INFO : 用于比對裝置的接口描述符的3個成員
         //USB_DEVICE_ID_MATCH_DEV_INFO: 用于比對裝置描述符的3個成員
         //USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于比對特定的USB裝置的4個成員
         //USB_DEVICE_ID_MATCH_DEVICE:用于比對特定的USB裝置的2個成員(idVendor和idProduct)

 
       /* 以下4個用比對描述特定的USB裝置 */
       __u16             idVendor;              //廠家ID
       __u16             idProduct;             //産品ID
       __u16             bcdDevice_lo;        //裝置的低版本号
       __u16             bcdDevice_hi;        //裝置的高版本号

       /*以下3個就是用于比較裝置描述符的*/
       __u8        bDeviceClass;                    //裝置類
       __u8        bDeviceSubClass;                 //裝置子類
       __u8        bDeviceProtocol;                 //裝置協定

       /* 以下3個就是用于比較裝置的接口描述符的 */
       __u8        bInterfaceClass;                   //接口類型
       __u8        bInterfaceSubClass;             //接口子類型
       __u8        bInterfaceProtocol;           //接口所遵循的協定

       /* not matched against */
       kernel_ulong_t       driver_info;
};      

參考/drivers/hid/usbhid/usbmouse.c(核心自帶的USB滑鼠驅動),看它是如何使用的:

USB原理及驅動架構介紹、編寫

發現,它是通過**USB_INTERFACE_INFO()**這個宏定義的.該宏如下所示:

#define USB_INTERFACE_INFO(cl,sc,pr)\
      .match_flags = USB_DEVICE_ID_MATCH_INT_INFO,//設定id_table的.match_flags成員
      .bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
                                                         //設定id_table的3個成員,用于與比對USB裝置的3個成員      

然後将上圖裡的usb_mouse_id_table []裡的3個值代入宏**USB_INTERFACE_INFO(cl,sc,pr)**中:

.bInterfaceClass =USB_INTERFACE_CLASS_HID;  
   //設定比對USB的接口類型為HID類, 因為USB_INTERFACE_CLASS_HID=0x03
   //HID類是屬于人機互動的裝置,比如:USB鍵盤,USB滑鼠,USB觸摸闆,USB遊戲操作杆都要填入0X03

.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;  
   //設定比對USB的接口子類型為啟動裝置

.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
  //設定比對USB的接口協定為USB滑鼠的協定,等于2
  //當.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD時,表示USB鍵盤的協定      

如下圖,我們也可以通過windows上也可以找到滑鼠的協定号,也是2:

USB原理及驅動架構介紹、編寫

其中VID:表示廠家(vendor)ID

PID:表示産品(Product) ID

總結:當我們插上USB裝置時,系統就會擷取USB裝置的裝置、配置、接口、端點的資料,并建立新裝置,是以我們的驅動就需要寫id_table來比對該USB裝置。

3、USB裝置(滑鼠)驅動編寫

  • 達到的目的:滑鼠左鍵=按鍵L,滑鼠右鍵=按鍵S,滑鼠中鍵=Enter鍵
  • .probe函數的結構:
  • 1 配置設定input_dev
  • 2 設定
  • 3 注冊
  • 硬體操作
  • 使用USB總線驅動函數的讀寫函數來收發資料
  • 怎麼寫USB裝置驅動程式:
  • 1 配置設定/設定usb_driver結構體
  • .name
  • .id_table
  • .probe
  • .disconnect
  • 2 注冊
  • 代碼編寫步驟如下:

0 定義全局變量 :usb_driver結構體、input_dev指針結構體 、虛拟位址緩存區、DMA位址緩存區

1 在入口函數中

  1. 通過usb_register()函數注冊usb_driver結構體

2 在usb_driver的probe函數中

  1. 配置設定一個input_dev結構體
  2. 設定input_dev支援L、S、回車、3個按鍵事件
  3. 注冊input_dev結構體
  4. 設定USB資料傳輸:

->4.1)通過usb_rcvintpipe()建立一個接收中斷類型的端點管道pipe,用于端點和資料緩沖區之間的連接配接

->4.2)通過usb_buffer_alloc()申請USB緩沖區

->4.3)申請并初始化urb結構體(urb:用來傳輸資料)

->4.4) 因為我們2440支援DMA,是以要告訴urb結構體,使用DMA緩沖區位址

->4.5)使用usb_submit_urb()送出urb

3 在滑鼠中斷函數中

1)判斷緩存區資料是否改變,若改變則上傳滑鼠事件

2)使用usb_submit_urb()送出urb

4 在usb_driver的disconnect函數中

1)通過usb_kill_urb()殺掉送出到核心中的urb

2)釋放urb

3)釋放USB緩存區

4)登出input_device,釋放input_device

5 在出口函數中

3.1 先寫驅動主架構

/*
 * 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>

/* 1. 配置設定/設定usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
  .name    = "usbmouse_as_key_",
  .probe    = usbmouse_as_key_probe,
  .disconnect  = usbmouse_as_key_disconnect,
  .id_table  = usbmouse_as_key_id_table,
};

static int usbmouse_as_key_init(void)
{
  /* 2. 注冊 */
  usb_register(&usbmouse_as_key_driver);
  return 0;
}

static void usbmouse_as_key_exit(void)
{
  usb_deregister(&usbmouse_as_key_driver);  
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");      

3.2 編寫.id_table

  • 該USB裝置驅動比對的是接口描述符中的HID類 & BOOT子類 & MOUSE協定的裝置。
static struct usb_device_id usbmouse_as_key_id_table[] = {
    {USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,\
        USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE)
    },
    //如果需要比對USB裝置的廠家和産品ID,可以添加{USB_DEVICE(0x1234,0x5678)},
    { }    /* Terminating entry */
};      

3.3 編寫.probe函數

  • 開始的時候可以僅在該函數中列印一條語句,待整個架構測試通過後再逐漸添加inputceng 。
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(intf);    //獲得usb裝置資訊
    
    /* 以下語句可以用來列印USB裝置的廠家及産品id資訊
     *printk("Found usbmouse!\n");
     *printk("bcdUSB = %x\n",dev->descriptor.bcdUSB);    
     *printk("VID     = 0X%x\n",dev->descriptor.idVendor);   
     *printk("PID     = 0X%x\n",dev->descriptor.idProduct);   
    */
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe;    
  
    interface = intf->cur_altsetting;
    if (interface->desc.nNumEndpoints != 1)        //除了端點0之外,滑鼠的端點數如果不是1的話,則傳回錯誤
        return -ENODEV;
    
    endpoint = &interface->endpoint[0].desc;    //得到第一個非零endpoint的描述符
    
    if (!usb_endpoint_is_int_in(endpoint))        //如果不是中斷輸入型端點的話,傳回錯誤
        return -ENODEV;

    /* a. 配置設定一個input_dev */
    uk_dev = input_allocate_device();    //在檔案開頭先定義輸入裝置(input_dev)指針uk_dev
    
    /* b. 設定 */
    /* b.1 能産生哪類事件 */
    set_bit(EV_KEY, uk_dev->evbit);    //能産生按鍵類事件
    set_bit(EV_REP, uk_dev->evbit);    //能産生按鍵的重複類事件
    
    /* b.2 能産生哪些事件 */
    set_bit(KEY_L, uk_dev->keybit);
    set_bit(KEY_S, uk_dev->keybit);
    set_bit(KEY_ENTER, uk_dev->keybit);
    
    /* c. 注冊 */
    input_register_device(uk_dev);
    
    /* d. 硬體相關操作 */
    /* 資料傳輸“三要素”: 源,目的,長度 */
    
    /* 1源: USB裝置的某個端點 */
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

    /* 2長度: 要在檔案開頭定義一個長度變量,int len*/
    len = endpoint->wMaxPacketSize;

    /* 3目的: (1.在檔案開頭定義一個字元串指針,char * usb_buf) 
     *       (2.在檔案開頭定義一個usb_buf的實體位址,dma_addr_t * usb_buf_phys)
    */
    usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

    /* 使用"3要素" */
    
    /* 1配置設定usb request block */
    uk_urb = usb_alloc_urb(0, GFP_KERNEL);    //先要在檔案開頭定義一個urb指針,即urb * uk_urb
    /* 2使用之前的三要素設定urb" */
    usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
    uk_urb->transfer_dma = usb_buf_phys;    
    uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

    /* 3使用URB */
    usb_submit_urb(uk_urb, GFP_KERNEL);
    
    return 0;
}      

3.4 編寫USB中斷函數

static void usbmouse_as_key_irq(struct urb *urb)
{   
//為了調試,先列印usb_buf中的資料
#if 0
    int i;
    static int cnt = 0;
    printk("data cnt %d: ", ++cnt);
    //usb_buf[]裡存放的是滑鼠按鍵值、x坐标、y坐标和滾輪的值
    for (i = 0; i < len; i++)
    {
        printk("%02x ", usb_buf[i]);
    }
    printk("\n");
#endif

//上報事件(USB總線驅動是不知道usb資料含義的,隻有在USB裝置驅動中加以解析)
#if 1
    /* USB滑鼠資料含義
     * data[0]: bit0-左鍵(1-按下, 0-松開)
     *          bit1-右鍵(1-按下, 0-松開)
     *          bit2-中鍵(1-按下, 0-松開)
     */
    static unsigned char pre_val;
    if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))    //如果上次資料的Bit0不等于現在的值
    {
        /* 左鍵發生了變化 */
        input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
        input_sync(uk_dev);
    }

    if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
    {
        /* 右鍵發生了變化 */
        input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
        input_sync(uk_dev);
    }

    if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
    {
        /* 中鍵發生了變化 */
        input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
        input_sync(uk_dev);
    }
    
    pre_val = usb_buf[0];
#endif
    
    /* 重新送出urb */
    usb_submit_urb(uk_urb, GFP_KERNEL);
}      

3.5 編寫.disconnect函數

  • 開始的時候可以僅在該函數中列印一條語句,待整個架構測試通過後再逐漸添加inputceng 。
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
  struct usb_device *dev = interface_to_usbdev(intf);

  //printk("disconnect usbmouse!\n");
  usb_kill_urb(uk_urb);  //殺死urb
  usb_free_urb(uk_urb);  //釋放urb

  usb_free_coherent(dev, len, usb_buf, usb_buf_phys);  //釋放usb buffer
  input_unregister_device(uk_dev);
  input_free_device(uk_dev);
}      

4、測試

4.1 去掉核心滑鼠驅動支援

cd linux3.4.2
make menuconfig

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

4.2 使用新核心啟動開發闆

tftp 30000000 uImage  //将新的核心複制到tftp共享目錄後,下載下傳到開發闆核心
//nfs 30000000 主機IP:/nfs_root/uImage  //或者使用nfs指令下載下傳新核心

bootm 30000000  //從核心的30000000位址處啟動核心      

4.3 安裝自己編譯的USB裝置驅動

mount -t nfs -o nolock  主機IP:/nfs_root /mnt    //挂接網絡檔案系統
insmod usbmouse_as_key.ko  //核心啟動後,安裝自己編譯的USB裝置驅動      

4.4 測試

  • 在開發闆上接入、拔出USB滑鼠,觀察序列槽輸出
  • 将滑鼠當作鍵盤,輸入ls+回車指令
# ls /dev/event*      //觀察目前系統有沒有其它event裝置
ls:/dev/event*:No such file or directory

接上USB滑鼠
usb 1-1:configuration #1 chosen from 1 choice
input:Unspecified device as /class/input/input0

# ls /dev/event*
/dev/event0     //對應我們剛接上去的滑鼠

# cat /dev/tty1   //将滑鼠作為鍵盤,觀察tty1口輸出
l(按下左鍵)s(按下右鍵)
(按下回車)

llllll(按住左鍵)      

5、整體代碼

  • usbmouse_as_key.c
/*
 * 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>

static struct input_dev *uk_dev;
static char *usb_buf;      //使用虛拟位址的usb buffer
static dma_addr_t usb_buf_phys;  //使用實體位址的DMA緩沖區
static int len;          //usb buffer 的大小
static struct urb *uk_urb;    //usb請求塊

static struct usb_device_id usbmouse_as_key_id_table [] = {
  { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
    USB_INTERFACE_PROTOCOL_MOUSE) },
  //{USB_DEVICE(0x1234,0x5678)},
  { }  /* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
  static unsigned char pre_val;
#if 0
  int i;
  static int cnt = 0;
  printk("data cnt %d: ", ++cnt);
  for (i = 0; i < len; i++)
  {
    printk("%02x ", usb_buf[i]);
  }
  printk("\n");
#endif
  /* USB滑鼠資料含義
   * data[0]: bit0-左鍵, 1-按下, 0-松開
   *          bit1-右鍵, 1-按下, 0-松開
   *          bit2-中鍵, 1-按下, 0-松開 
   *
     */
  if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
  {
    /* 左鍵發生了變化 */
    input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
    input_sync(uk_dev);
  }

  if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
  {
    /* 右鍵發生了變化 */
    input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
    input_sync(uk_dev);
  }

  if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
  {
    /* 中鍵發生了變化 */
    input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
    input_sync(uk_dev);
  }
  
  pre_val = usb_buf[0];

  /* 重新送出urb */
  usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_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;
  
  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 能産生哪些事件 */
  set_bit(KEY_L, uk_dev->keybit);
  set_bit(KEY_S, uk_dev->keybit);
  set_bit(KEY_ENTER, uk_dev->keybit);
  
  /* c. 注冊 */
  input_register_device(uk_dev);
  
  /* d. 硬體相關操作 */
  /* 資料傳輸3要素: 源,目的,長度 */
  /* 源: USB裝置的某個端點 */
  pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

  /* 長度: */
  len = endpoint->wMaxPacketSize;

  /* 目的: */
  usb_buf = usb_alloc_coherent(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, usbmouse_as_key_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);
  
  return 0;
}

static void usbmouse_as_key_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_free_coherent(dev, len, usb_buf, usb_buf_phys);
  input_unregister_device(uk_dev);
  input_free_device(uk_dev);
}

/* 1. 配置設定/設定usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
  .name    = "usbmouse_as_key_",
  .probe    = usbmouse_as_key_probe,
  .disconnect  = usbmouse_as_key_disconnect,
  .id_table  = usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
  /* 2. 注冊 */
  usb_register(&usbmouse_as_key_driver);
  return 0;
}

static void usbmouse_as_key_exit(void)
{
  usb_deregister(&usbmouse_as_key_driver);  
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");      
  • Makefile檔案
KERN_DIR = /home/leon/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  += usbmouse_as_key.o