19.Linux-USB總線驅動分析

以下圖所示,以windows爲例,咱們插上一個沒有USB設備驅動的USB,就會提示你安裝驅動程序html

 

爲何一插上就有會提示信息? linux

是由於windows自帶了USB總線驅動程序,windows

USB總線驅動程序負責:數組

識別USB設備,給USB設備找到對應的驅動程序框架

新接入的USB設備的默認地址(編號)是0,在未分配新編號前,PC主機使用0地址和它通訊。函數

而後USB總線驅動程序都會給它分配一個地址(編號)spa

PC機想訪問USB總線上某個USB設備時,發出的命令都含有對應的地址(編號)debug

USB是一種主從結構。主機叫作Host,從機叫作Device,全部的USB傳輸,都是從USB主機這方發起;USB設備沒有"主動"通知USB主機的能力。設計

例子:USB鼠標滑動一下馬上產生數據,可是它沒有能力通知PC機來讀數據,只能被動地等得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大傳輸類型:

控制傳輸(control)  

是每個USB設備必須支持的,一般用來獲取設備描述符、設置設備的狀態等等。一個USB設備從插入到最後的拔出這個過程必定會產生控制傳輸(即使這個USB設備不能被這個系統支持)。
中斷傳輸(interrupt)

支持中斷傳輸的典型設備有USB鼠標、 USB鍵盤等等。中斷傳輸不是說個人設備真正發出一箇中斷,而後主機會來讀取數據。它實際上是一種輪詢的方式來完成數據的通訊。USB設備會在設備驅動程序中設置一個參數叫作interval,它是endpoint的一個成員。 interval是間隔時間的意思,表示我這個設備但願主機多長時間來輪詢本身,只要這個值肯定了以後,我主機就會週期性的來查看有沒有數據須要處理

批量傳輸(bulk)

支持批量傳輸最典型的設備就是U盤,它進行大數量的數據傳輸,可以保證數據的準確性,可是時間不是固定的。

實時傳輸(isochronous) 
USB攝像頭就是實時傳輸設備的典型表明,它一樣進行大數量的數據傳輸,數據的準確性沒法保證,可是對傳輸延遲很是敏感,也就是說對實時性要求比較高

 

USB端點:

USB設備與主機會有若干個通訊的」端點」,每一個端點都有個端點號,除了端點0外,每個端點只能工做在一種傳輸類型(控制傳輸、中斷傳輸、批量傳輸、實時傳輸)下,一個傳輸方向下

傳輸方向都是基於USB主機的立場說的,

好比:鼠標的數據是從鼠標傳到PC機, 對應的端點稱爲"中斷輸入端點"

其中端點0是設備的默認控制端點, 既能輸出也能輸入,用於USB設備的識別過程

 

一樣linux內核也自帶了USB總線驅動程序,框架以下:

 

 

要想成爲一個USB主機,硬件上就必需要有USB主機控制器才行,USB主機控制器又分爲4種接口:

OHCI(Open Host Controller Interface): 微軟主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的軟件簡單,硬件複雜  

UHCI(Universal Host Controller Interface): Intel主導的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps), 而UHCI接口的軟件複雜,硬件簡單  

EHCI(Enhanced Host Controller Interface):高速USB2.0(480Mbps),

xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),採用了9針腳設計,同時也支持USB2.0、1.1等

 

接下來進入正題,開始分析USB總線驅動,如何識別USB設備

因爲內核自帶了USB驅動,因此咱們先插入一個USB鍵盤到開發板上看打印信息

發現如下字段:

 

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

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

1. drivers/usb/core/hub.c的第2186行位於hub_port_init()函數裏

它又是被誰調用的,以下圖所示,咱們搜索到它是經過hub_thread()函數調用的

 

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.咱們搜索」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);
}

 

3.繼續搜索kick_khubd,發現被hub_irq()函數中調用

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

 

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_device設備結構體參考:http://www.cnblogs.com/lifexy/p/7634511.html

因此最終流程圖以下:

 

5.咱們進入hub_port_connect_change()->usb_alloc_dev(),來看看它是怎麼設置usb_device的

 1 usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
 2 
 3 {
 4 
 5        struct usb_device *dev;
 6 
 7  
 8 
 9        dev = kzalloc(sizeof(*dev), GFP_KERNEL);   //分配一個usb_device設備結構體
10 
11        ... ...
12 
13  
14 
15        device_initialize(&dev->dev);           //初始化usb_device
16                                     
17        dev->dev.bus = &usb_bus_type;         //(1)設置usb_device的成員device->bus等於usb_bus總線
18 
19        dev->dev.type = &usb_device_type;    //設置usb_device的成員device->type等於usb_device_type
20                                   
21        ... ...
22 
23        return dev;                                        //返回一個usb_device結構體
24 
25 }

(1)在第17行上,設置device成員,主要是用來後面8.2小節,註冊usb總線的device表上.

其中usb_bus_type是一個全局變量, 它和咱們以前學的platform平臺總線類似,屬於USB總線, 是Linux中bus的一種.

以下圖所示,每當建立一個USB設備,或者USB設備驅動時,USB總線都會調用match成員來匹配一次,使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,       //喚醒函數
};  

 

 

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鍵盤,也能夠看出,以下圖所示:

 

 

7.咱們再來看看hub_port_connect_change()->hub_port_init()函數是如何來實現鏈接USB設備的

 1 static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
 2 {
 3    ... ...
 4    for (j = 0; j < SET_ADDRESS_TRIES; ++j) 
 5   {
 6        retval = hub_set_address(udev);     //(1)設置地址,告訴USB設備新的地址編號
 7 
 8        if (retval >= 0)
 9                 break;
10        msleep(200);
11    }
12  retval = usb_get_device_descriptor(udev, 8);   //(2)得到USB設備描述符前8個字節
13  ... ...
14 
15  retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);  //從新獲取設備描述符信息
16  ... ...
17 }

(1)上面第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。

2)上面第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));

 

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設備裏依次讀入全部配置描述塊
  {
      result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
                              //每次先讀取USB_DT_CONFIG_SIZE個字節,也就是9個字節,暫放到buffer中
      ... ...

      length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);
                  //經過wTotalLength,知道實際數據大小

bigbuffer = kmalloc(length, GFP_KERNEL); //而後再來分配足夠大的空間 ... ... result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length); //在調用一次usb_get_descriptor,把整個配置描述塊讀出來,放到bigbuffer中 ... ... dev->rawdescriptors[cfgno] = bigbuffer; //再將bigbuffer地址放在rawdescriptors所指的指針數組中 result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],     bigbuffer, length); //最後在解析每一個配置塊 } ... ... }

(2)其中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函數

 

 

9.咱們再來看看usb_bus_type這個的成員usb_device_match函數,看看是如何匹配的

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

 

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;
};

 (設備描述符合接口描述符結構體參考:http://www.cnblogs.com/lifexy/p/7634511.html

咱們參考/drivers/hid/usbhid/usbmouse.c(內核自帶的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:

 

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

PID:表示產品(Product) ID

 

總結:當咱們插上USB設備時,系統就會獲取USB設備的設備、配置、接口、端點的數據,並建立新設備,因此咱們的驅動就須要寫id_table來匹配該USB設備

 

USB總線驅動程序大概流程就此結束,未完待續——分析完後下節開始寫USB驅動

相關文章
相關標籤/搜索