1. 程式人生 > >USB協議枚舉過程詳解

USB協議枚舉過程詳解

數字串 eset 格式 lan 事務 kth tmp start 描述

一 枚舉過程之文字描述


?主機集線器監視著每個端口的信號電壓,當有新設備接入時便可覺察。(集線器端口的兩根信號線的每一根都有15kΩ的下拉電阻,而每一個設備在D+都有一個1.5kΩ的上拉電阻。當用USB線將PC和設備接通後,設備的上拉電阻使信號線的電位升高,因此被主機集線器檢測到。)

? 連接了設備的 HUB 在 HOST 查詢其狀態改變端點 時返回對應的 bitmap,告知HOST 某個PORT狀態發生了改變。
? 主機向 HUB 查詢該PORT的狀態,得知有設備連接,並知道了該設備的基本特性。
? 主機等待(至少 100mS)設備上電穩定,然後向 HUB 發送請求,復位並使能該PORT。

? HUB執行PORT復位操作,復位完成後該PORT就使能了。現在設備進入到defalut狀態,可以從Vbus獲取不超過 100mA 的電流。主機可以通過 0地址與其通訊。
1 主機通過0地址向該設備發送get_device_descriptor標準請求,獲取設備的描述符。目的是取得卻缺省控制管道所支持的最大數據包長度,該長度包含在設備描述符的bMaxPacketSize0字段中,其地址偏移量為7,主機讀取64字節,但實際不一定能讀到,因為這時候還不知道一次能讀取的最大長度,但是肯定能讀到前8個字節,因為可能的值為(8,16,32,64).
? 主機再次向 HUB 發送請求,復位該PORT。
2 主機通過標準請求 set_address給設備分配地址。
3 主機通過新地址向設備發送 get_device_descriptor標準請求,獲取設備的描述符。
4 主機通過新地址向設備發送其他 get_configuration請求,獲取設備的配置描述符。

5 根據配置描述符的wTotalLength字段(地址偏移為2,總共兩個字節,即偏移地址3表示高8位,偏移地址2表示低8位),表示該配置描述符及其包含的接口描述符、端點描述符和供應商描述等的總長度。英文原文:Total length of data returned for thisconfiguration.Includes the combined lengthof all descriptors (configuration, interface,endpoint, and class- or vendor-specific)returned for this configuration.再次發送get_configuration請求,獲取數據長度為wTotalLength。

6根據配置信息,主機選擇合適配置,通過 set_configuration請求對設備而進行配置。這時設備方可正常使用。

枚舉過程之程序描述

這是uboot中usb_hub.c和usb.c中精簡之後的代碼,可以大致看出整個過程。文字描述中的1-6都有對應的函數,前面幾項描述由於uboot和linux內核的實現稍有差別,就不列出來了,只簡單描述,讀者自己可以查看源碼。uboot是通過在命令行usb start,調用到do_usb()->usb_init()->usb_scan_devices()->usb_new_device(dev)->usb_hub_probe()->usb_hub_configure()->usb_hub_port_connect_change()。也就是說是插入u盤之後,手動輸入命令,使用了一個查詢過程。函數實現簡單、清晰,易入手。

內核源碼也是在hub.c中,usb_hub_init()->kthread_run(hub_thread, NULL, "khubd")->hub_thread->hub_events()->hub_port_connect_change()。當有usb設備插入時,會喚醒hub_thread線程,從而調用hub_events執行,檢測端口狀態變化。

void usb_hub_port_connect_change(struct usb_device *dev, int port)
{
	/* 獲取端口狀態變化信息*/
	usb_get_port_status(dev, port + 1, portsts) ;

	/* Clear the connection change status 清除端口變化 */
	usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);

        /*等待至少100ms,等待插入設備穩定*/
	mdelay(200);

	/* Reset the port 對設備的第一次操作,復位設備;對設備的第一次操作一定是復位。先復位,後獲取 
      *註意hub端口和usb設備是不同的操作,前面usb_get_port_status是對hub 端口的操作。*/
	hub_port_reset(dev, port, &portstatus) ;
         /*等待設備復位完成*/
	mdelay(200);

           /*好戲來了*/
	usb_new_device(usb);
	
}

int usb_new_device(struct usb_device *dev) { /*包大小先初始化一個值64*/ dev->maxpacketsize = PACKET_SIZE_64; /*1 獲取設備描述符,讀取長度64*/ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, 64); /*獲取最大包長度*/ dev->descriptor.bMaxPacketSize0 = desc->bMaxPacketSize0; /* reset the port for the second time第二次復位設備 */ err = hub_port_reset(dev->parent, port, &portstatus); dev->epmaxpacketin[0] = dev->descriptor.bMaxPacketSize0; dev->epmaxpacketout[0] = dev->descriptor.bMaxPacketSize0; switch (dev->descriptor.bMaxPacketSize0) { case 8: dev->maxpacketsize = PACKET_SIZE_8; break; case 16: dev->maxpacketsize = PACKET_SIZE_16; break; case 32: dev->maxpacketsize = PACKET_SIZE_32; break; case 64: dev->maxpacketsize = PACKET_SIZE_64; break; } dev->devnum = addr; /*2 設置設備地址*/ err = usb_set_address(dev); /* set address */ mdelay(10); /* Let the SET_ADDRESS settle */ tmp = sizeof(dev->descriptor); /*3 用新設置的地址獲取設備描述符*/ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, tmpbuf, sizeof(dev->descriptor)); memcpy(&dev->descriptor, tmpbuf, sizeof(dev->descriptor)); /* only support for one config for now */ /*4 和 5封裝成了一個函數 獲取配置描述符*/ usb_get_configuration_no(dev, tmpbuf, 0); usb_parse_config(dev, tmpbuf, 0); usb_set_maxpacket(dev); /* we set the default configuration here */ /*6 配置設備*/ usb_set_configuration(dev, dev->config.desc.bConfigurationValue); return 0; }




int usb_get_configuration_no(struct usb_device *dev,
			     unsigned char *buffer, int cfgno)
{

	struct usb_configuration_descriptor *config;

	config = (struct usb_configuration_descriptor *)&buffer[0];
	/*4 獲取配置描述符,僅配置描述使長度為9*/
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, buffer, 9);
	/*當前配置下的描述符總長度*/
	tmp = le16_to_cpu(config->wTotalLength);
	/*5 再一次獲取當前配置下的全部配置、接口、端點信息*/
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, buffer, tmp);

	return result;
}

三 枚舉過程之圖形描述

枚舉的總過程,看過下面的分解過程,再回頭看這個總圖就非常容易了。

技術分享圖片

1. 獲取設備描述符GET_DESCRIPTOR

總線復位及向默認地址0發送GET_DESCRIPTOR指令包,請求設備描述。第一獲取描述符要先復位設備,然後等待至少100ms(100ms可以滿足大多數設備),這裏實際只等待了43ms。如圖一所示:

技術分享圖片

圖一

1)Index[4 - 5]:表示USB插入總線復位
2)Index[7 - 8]:表示主機向默認地址發送GET_DESCRIPTOR指令包,詳細信息也抓出來了,如圖所示:

技術分享圖片

3)Index[15 - 17]:表示設備向主機發送設備描述數據Index[16]
4)Index[18 - 19]:表示主機完成GET_DESCRIPTOR指令後,給設備發送一個空應答;

技術分享圖片

數據是由二進制數字串構成的,首先數字串構成域(有七種:同步域(SYNC)、標識域(PID)、地址域(ADDR)、端點域(ENDP)、數據域(DATA)、幀號域(FRAM)、校驗域(CRC)),域再構成包(比如握手包:格式如下 SYNC+PID,ACK屬於PID的一種),包再構成事務(IN、OUT、SETUP,每一種事務都由令牌包、數據包、握手包三個階段構成),事務最後構成傳輸(中斷傳輸、並行傳輸、批量傳輸和控制傳輸)。這裏先簡單了解一下,不影響理解這幅圖。

這裏先解析一下設置事務的值:80 06 00 01 00 00 40 00

/* device request (setup) */
struct devrequest {
unsigned char requesttype;
unsigned char request;
unsigned shortvalue;
unsigned shortindex;
unsigned shortlength;
} __attribute__ ((packed));

requesttype=0x80,request=0x06,value=0x0100,index=0x0000,length=0x0040;

根據usb2.0協議9.4.3節描述,獲取描述符時requesttype=0x80,request=0x06,這個是協議規定的。

value=0x0100,高字節表示描述符類型,01表示設備,02表示配置;低字節表示索引。比如設備有多個配置,那需要讀取不同配置的時候就通過低字節。或者一個配置下有多個接口,通過索引選擇不同的接口。

index=0x0000,這個參數如果為0,則不關心;如果為非零,則表示Langurage ID,每一位都有對應的意義。

length=0x0040,表示請求的數據包長度為64.

所以本次設置事務的目的明確了,獲取設備描述符,長度為64字節。

輸入事務,是usb設備對請求進行回應,傳輸了16個字節的數據,為什麽是傳輸了16個字節而不是64字節,看看偏移地址7bMaxPacketSize0=0x10,即該設備的最大包傳輸大小為16字節,如果超過16字節,需要多次傳輸。實際設備描述符大小為18,可以看第三步的圖,傳輸完16字節之後,主機再次發送令牌環,設備把剩下的2字節傳輸完成。這裏我們只要獲得bMaxPacketSize0值就可以了,所以接下來直接對其進行了復位操作,否則設備還在等待傳輸剩余的2個字節呢。
struct usb_device_descriptor {
u8 bLength;

u8 bDescriptorType;
u16 bcdUSB;
u8 bDeviceClass;
u8 bDeviceSubClass;
u8 bDeviceProtocol;
u8 bMaxPacketSize0;
u16 idVendor;
u16 idProduct;
u16 bcdDevice;
u8 iManufacturer;
u8 iProduct;
u8 iSerialNumber;
u8 bNumConfigurations;
} __attribute__ ((packed));

輸出事務:因為獲取描述符之前把設備包大小的初始值設為了64,所以輸入事務之後,就認為傳輸完成,主機發送了一個輸出事務,響應設備,已經收到數據。

2. 設置地址SET_ADDRESS

再次復位總線及向設備發送SET_ADDRESS指令包,設置設備地址。如圖二所示:

技術分享圖片

圖二
1)Index[22 - 23]:表示再次總線復位,該復位自動完成,不是手工插拔USB完成
2)Index[25 - 27]:表示主機向默認地址發送SET_ADDRESS指令包,詳細信息如圖所示:

技術分享圖片

設置地址為1

3)Index[29 - 31]:表示設備完成SET_ADDRESS指令後,給主機發送一個空應答;

技術分享圖片

設置地址為0x0002,由於我是從網上找的圖,所以兩幅圖地址設置的不一樣,這裏註意一下就行。

3.請求設備描述GET_DESCRIPTOR

向第二步設定的設備地址發送GET_DESCRIPTOR指令包,請求設備描述。如圖三所示:

技術分享圖片

圖三

1)Index[33 - 35]:表示主機向地址01發送GET_DESCRIPTOR指令包,詳細信息如圖

技術分享圖片

2)Index[41 - 43]:表示設備向主機發送設備描述數據Index[42]
3)Index[45 - 47]:表示設備向主機發送設備描述數據Index[46]
4)Index[48 - 50]:表示主機完成GET_DESCRIPTOR指令後,給設備發送一個空應答

技術分享圖片

4. 請求配置描述GET_DESCRIPTOR

向第二步設定的設備地址發送GET_DESCRIPTOR指令包,請求配置描述。如圖四所示:

技術分享圖片

圖四
1)Index[52 - 54]:表示主機向地址01發送GET_DESCRIPTOR指令包,詳細信息如圖所示

技術分享圖片

2)Index[57 - 59]:表示設備向主機發送配置描述數據Index[58]
3)Index[60 - 62]:表示主機完成GET_DESCRIPTOR指令後,給設備發送一個空應答;

技術分享圖片

解析輸入事件獲取的配置信息:

struct usb_configuration_descriptor {
u8 bLength; /*09,描述符長度為9*/
u8 bDescriptorType;/* 0x2, 表示配置描述符*/
u16 wTotalLength; /*0x002E,表示當前配置下的各種描述信息總長度為46*/
u8 bNumInterfaces; /*0x01,當前配置下共一個接口*/
u8 bConfigurationValue; /*0x01,當前配置索引,當設置某一配置時,給SetConfiguration(x)傳遞的參數*/
u8 iConfiguration;/*0x00,字符串描述索引*/
u8 bmAttributes;/*0x60,屬性信息*/
u8 bMaxPower;/*0x01當前配置最大消耗電流*/
} __attribute__ ((packed));

5. 讀取完整設備描述及配置描述

再次讀取配置描述符,讀取長度46字節。

技術分享圖片

09 02 2E 00 01 01 00 60 01 和第四步獲得的一樣

09 04 00 00 04 00 00 00 00 描述符長度也是09,04表示是一個接口描述符

07 05 81 03 08 00 c8 描述符長度為07,05表示端點描述符

07 05 01 03 08 00 c8

07 05 82 02 40 00 00

07 05 02 02 40 00 00

接口描述符詳解:

struct usb_interface_descriptor {
u8 bLength; /*0x09,描述符長度*/
u8 bDescriptorType;/* 0x04 ,表示接口描述符*/
u8 bInterfaceNumber; /*0,接口號為0*/
u8 bAlternateSetting; /*0,接口的可選設置*/
u8 bNumEndpoints; /*04,當前接口共4個端點*/
u8 bInterfaceClass; /*0*/
u8 bInterfaceSubClass;/*0*/
u8 bInterfaceProtocol; /*0*/
u8 iInterface; /*0*/
} ;

struct usb_endpoint_descriptor {
u8 bLength; /*07,描述符長度*/
u8 bDescriptorType;/* 0x5 ,表端點描述符*/
u8 bEndpointAddress;/*0x81,bit[7] =1,表示輸入端點,0表示輸出端點;bit[6:4],保留;bit[3:0]端點號,為1*/
u8 bmAttributes;/*0x03,bit[1:0]=11,表傳輸類型為中斷傳輸*/
u16 wMaxPacketSize;/*0x0008,當前端點最大發送和接收包大小*/
u8 bInterval;/*0xc8,查詢時發送數據的間隔時間*/
} ;

6 配置設備SET_CONFIGURATION

向第二步設定的設備地址發送SET_CONFIGURATION指令包,設置配置描述。如圖六所示:

技術分享圖片

圖六
1)Index[139 - 141]:表示主機向地址01發送SET_CONFIGURATION指令包,詳細信息如下:

技術分享圖片

2)Index[143 - 145]:表示設備完成SET_CONFIGURATION指令後,給主機發送一個空應答
至此,枚舉過程結束,設備可通過驅動與主機通信了。

/* device request (setup) */
struct devrequest {
unsigned char requesttype; /*0x00,表請求設置*/
unsigned char request; /*0x09,表示設置配置*/
unsigned shortvalue; /*0x0001,使用配置,必須匹配從配置描述符都過來的bConfigurationValue*/
unsigned shortindex; /*0x0000,協議規定設為0*/
unsigned shortlength; /*0x0000,協議規定設為0*/
} __attribute__ ((packed));


下面舉幾個例子來說明USB的通訊過程:

1:主機想要向設備傳送一串數據。 過程如下:

(1) 主機向從機發送 “令牌包”,令牌包的類型為輸出包,表示主機要向從機發送數據了。

(2) 主機向從機發送完令牌以後,USB處理器件根據發送的令牌,會將中斷狀態寄存器標誌置位,從機CPU通過查詢USB處理器件的中斷狀態寄存器,對主機的令牌包進行響應

(3)從機判別出中斷類型,於是,準備從主機接收數據。

(4) 從機準備好了,於是主機開始發送“數據包” 這時,USB處理器件會自動將從主發送過來的數據放如它的內部緩沖區內,接收完這個數據包後,從機向主機發送“應答包”

這就是一個完整的通訊過程。

由以上可以看出,USB若是想要傳送數據,那麽主機必須先發一個 IN 或OUT的令牌包,然後發送DATA0,或DATA1數據包。

簡單的用現實生活中的事件進行描述:老板想讓員工去做一件事情,老板 先會發出命令,告訴要做什麽事情,員工準備好以後呢,老板再把做這件事情的經費發放給員工,當員工把發放的經費清點以後,發現數目正確,他會給老板一個回應信息,告訴老板,錢已經收到了,而且數目正確。

老板想讓員工做的事:對應USB通訊裏的令牌包。

老板想要發放的經費:對應USB通訊裏的數據包。

員工給老板的回應: 對應USB通訊裏的握手包。

這裏尤其需要註意一個問題就是:

USB主機向設備發送令牌包的時候,接收令牌是有USB器件來完成的,而不是有從機CPU來完成的,如主機發送一個如下的令牌:

SYNC+PID+ADDR+ENDP+CRC5

USB器件回根據PID的類型來判斷是哪種類型的令牌 根據ADDR的值來判斷是否是和自己通訊,根據ENDP的值來判斷是和哪個端點進行通訊,根據校驗來判斷,數據傳送是否無誤。根據以上的令牌包信息,USB器件會將其內部的中斷狀態寄存器相應的位置位,從機CPU可以查詢這個中斷狀態寄存器來進行相應的操作。

USB協議枚舉過程詳解