01 MSC類裝置-基礎篇(一)

卡卡6發表於2020-12-02

一、簡介

在USB協議中,規定了一類大容量儲存裝置(Mass Storage Device Class)協議。常見的USB大容量裝置有:U盤、USB行動硬碟、USB移動光碟機、USB讀卡器、USB印表機、手機……。這些裝置上有一個硬體USB介面與主機相連線,兩者之間可以傳輸檔案。並且裝置上都有大容量儲存器,比如:Flash、硬碟、光碟,SD卡……。

 

二、關於批量傳輸(Bulk Transfer)

批量傳輸由批量事務(Bulk Transaction)構成,一次批量事務分為三個階段:令牌包階段、資料包階段和握手包階段。批量傳輸分為批量讀和批量寫,批量讀使用的是批量輸入事務,批量寫使用的是批量輸出事務,使用的是兩個不同的硬體端點。

U盤在裝置列舉的過程中,會有SETUP令牌包,但是列舉階段結束,就不會有SETUP令牌包了(列舉階段,使用的是端點0)。一個U盤至少有兩個Bulk端點,一個IN端點,一個OUT端點。批量輸入事務的令牌包是IN包,批量輸出事務的令牌包是OUT包。這兩個包的結構(低速/全速)如下:

IN令牌包

SYNC

PID

ADDR

ENDP

CRC5

EOP

00000001

0x96

7位的裝置地址

4位的端點號

5位的CRC5校驗

 

OUT令牌包

SYNC

PID

ADDR

ENDP

CRC5

EOP

00000001

0x87

7位的裝置地址

4位的端點號

5位的CRC5校驗

 

 

1. 批量輸出事務

主機先發出一個OUT令牌包,令牌包中包含了裝置的地址、端點號;接著進入資料階段,主機發出一個DATAx包,地址和端點都匹配的裝置會接收到這個資料包(裝置棧,軟體程式設計實現,對於令牌包和資料包,一般都是產生控制器中斷事件,接著進行回撥,解析資料);然後主機切換到接收模式,等待裝置返回握手包。

裝置解析令牌包,接收資料包,並且有足夠的緩衝區來儲存資料後,會使用ACK握手包來響應主機,若沒有足夠的緩衝區來接收資料,則返回NAK握手包。

 

2. 批量輸入事務

同理,對於批量輸入事務,主機先發出一個IN令牌包,令牌包中包含了裝置的地址和端點號,這是令牌包階段;然後主機切換到接收資料的狀態,等待裝置返回資料,這是資料階段;如果裝置有資料要返回,那麼它就會把一個資料包放到匯流排上(可以是0資料包);如果裝置沒有資料要返回,則裝置使用NAK握手包響應。

在程式中可細分為5個階段(狀態)。

/* MSC Bulk-only Stage */

enum Stage {

         READ_CBW,     /* wait a CBW */

         ERROR,        /* error */

         PROCESS_CBW,  /* process a CBW request */

         SEND_CSW,     /* send a CSW */

         WAIT_CSW      /* wait that a CSW has been effectively sent */

};

 

三、描述符

MSC類裝置沒有特定類描述符(class-specific descriptor),使用的都是標準描述符。

1. 裝置描述符

1.1 裝置描述符的結構

偏移量/位元組

大小/位元組

說明

0

bLength

1

描述符的長度 (18Byte=0x12)

1

bDescriptorType

1

描述符型別(裝置描述符=0x01)

2

bcdUSB

2

本裝置使用的USB協議版本

4

bDeviceClass

1

類程式碼

5

bDeviceSubClass

1

子類程式碼

6

bDeviceProtocol

1

裝置所使用的協議

7

bMaxPacketSize0

1

端點0的最大包長

8

idVendor

2

廠商ID

10

idProduct

2

產品ID

12

bcdDevice

2

裝置版本號

14

iManufacturer

1

描述廠商字串的索引

15

iProduct

1

描述產品字串的索引

16

iSerialNumber

1

產品序列號字串的索引

17

bNumConfigurations

1

可能的配置數

構造裝置描述符,需要注意USB協議版本。對於批量傳輸,不同的速度模式下,端點支援的最大包長是不一樣的。當前zephyr協議棧實現的是USB1.1版本,裝置工作在全速模式下(Full-Speed Mode)。

 

其次,需要注意的是下面這三個欄位,在裝置描述符中都設定為0。

bDeviceClass:                      0x00 

bDeviceSubClass:                   0x00

bDeviceProtocol:                   0x00

一般是在介面描述符中去指定裝置的類、子類以及協議版本。

 

2. 配置描述符

2.1 配置描述符的結構

一個USB設定至少有一個配置描述符。標準配置描述符結構如下:

偏移量/位元組

大小/位元組

說明

0

bLength

1

該描述符的長度(9 Byte=0x09)

1

bDescriptorType

1

該描述符的型別(配置描述符是0x02)

2

wTotalLength

2

配置描述符集合 的總長度

4

bNumInterfaces

1

該配置下的介面數

5

bConfigurationValue

1

該配置的值

6

iConfiguration

1

描述該配置的字串的索引值

7

bmAttributes

1

該裝置的屬性

8

bMaxPower

1

該裝置所需的電流(單位 2mA)

構造配置描述符需要注意兩個欄位:(1)配置描述符集合總長度wTotalLength (2)該配置下共有多少個介面bNumInterfaces。

對於大容量裝置來說,一般是單一的類裝置,描述符層次結構也不是很複雜。比如,實現一個U盤,只需要一個裝置描述符,一個配置描述符,一個介面描述符,兩個端點描述符。那麼,配置描述符集合總長度wTotalLength = 配置描述符(9 Byte) + 介面描述符(9 Byte) + 輸入端點描述符(7 Byte) + 輸出端點描述符(7 Byte),介面下有兩個端點(一個IN,一個OUT)即可。

 

3. 介面描述符

3.1 介面描述符的結構

偏移量/位元組

大小/位元組

說明

0

bLength

1

描述符的長度(9 Byte)

1

bDescriptorType

1

該描述符的型別(0x04)

2

bInterfaceNumber

1

該介面的編號(從0開始)

3

bAlternateSetting

1

該介面的備用編號

4

bNumEndpoints

1

該介面所使用的端點數

5

bInterfaceClass

1

該介面所使用的的類

6

bInterfaceSubClass

1

該介面所使用的的子類

7

bInterfaceProtocol

1

該介面所使用的的協議

8

iInterface

1

描述該介面的字串的索引值

類的定義在介面中實現,所以對於MSC裝置,介面描述符中有三個欄位的取值如下:

介面類程式碼bInterfaceClass:大容量儲存類裝置,程式碼為0x08

介面子類程式碼bInterfaceSubClass:大部分的U盤都是用0x06,即SCSI透明指令集。

介面協議程式碼bInterfaceProtocol:0x50,批量傳輸

 

4. 端點描述符

MSC裝置使用兩個物理端點,都需要配置為批量端點。

(1) 一個批量IN端點,用於Device向Host傳輸資料。

(2) 一個批量OUT端點,用於Host向Device傳輸資料。

4.1 端點描述符的結構

偏移量/位元組

大小/位元組

說明

0

bLength

1

該描述符的長度(7 Byte)

1

bDescriptorType

1

該描述符的型別(0x05)

2

bEndpointAddress

1

該端點的地址及輸入輸出屬性

3

bmAttributes

1

該端點的傳輸型別屬性

4

wMaxPacketSize

2

該端點支援的最大包長

6

bInterval

1

端點的查詢時間

構造端點描述符的時候,需要注意:(1)端點號與端點地址的區別  (2)端點屬性 (3)端點最大包長

邏輯上的端點地址是有對應的物理端點實體的。所以,首先需要弄清楚的是當前IC的USB控制器的硬體端點資源。例如,某顆IC的晶片描述文件(IC Spec)對USB控制器硬體端點資源描述如下:

所以設定端點地址的時候,要參照spec對硬體端點資源的描述符!

端點屬性bmAttributes:批量傳輸端點,程式碼為0x02

端點傳輸最大包長wMaxPacketSize: 高速模式固定為 512 Byte  ,全速模式可以是16 Byte、32 Byte、64 Byte。

主機對端點的輪詢bInterval:對於批量傳輸端點來說,不需要輪詢,所以設定為0即可。即對於批量傳輸端點,欄位bInterval沒有意義,設定為0x00即可。

示例:使用端點1作為IN端點,使用端點2作為OUT端點。則端點地址分別是0x81和0x02。

端點描述符各個欄位的含義和端點地址的設定規則詳見之前這篇部落格:

https://blog.csdn.net/qq_40088639/article/details/109464054

 

四、請求

1. 獲取裝置描述符的標準請求

在裝置列舉階段,PC端獲取device的裝置資訊,使用的是控制傳輸。列舉過程比較簡單,就是獲取裝置相關的描述符,最後下發設定介面指令,使得配置生效(可以使用BusHound工具捕獲USB2.0的讀卡器,分析資料)。

如下圖:端點0上的資料[控制傳輸]

對於U盤裝置,控制傳輸就6條指令,將資料提取出來,分析如下:

<----------------------------------控制傳輸開始--------------------------------------------------

/* 01. 主機請求獲取裝置描述符(長度:18 = 0x12) */

44.0       CTL    80 06 00 01  00 00 12 00 

//裝置通過端點0返回裝置描述符資料                                                                                       

44.0   18  IN     12 01 00 02  00 00 00 40  08 19 26 02  11 01 00 00  00 01

 

/* 02. 主機請求獲取裝置配置描述符(長度:9 = 0x09) */                                                  

44.0       CTL    80 06 00 02  00 00 09 00

//裝置通過端點0返回裝置配置描述符資料(裡面包含有配置描述符集合的長度資訊)                                                                                        

44.0    9  IN     09 02 20 00  01 01 00 80  4b

 

/* 03. 主機通過解析配置描述符資料,請求配置描述符集合資料(U盤配置描述符集合資料,通常是32個位元組 = 0x20)*/

44.0       CTL    80 06 00 02  00 00 20 00 

//裝置通過端點0返回配置描述符集合資料  

44.0   32  IN     09 02 20 00  01 01 00 80  4b 09 04 00  00 02 08 06  50 00 07 05  01 02 00 02  00 07 05 81  02 00 02 00

 

/* 04. 主機下發設定配置指令 */

44.0       CTL    00 09 01 00  00 00 00 00    

                                                                                   

/* 05. 主機下發設定介面指令 */ 

44.0       CTL    01 0b 00 00  00 00 00 00 

                                                                                   

//06. 主機下發獲取邏輯單元數量指令  /* 這是類特有的請求,下文有解析 */

44.0       CTL    a1 fe 00 00  00 00 01 00                                                                                       

//裝置通過端點0返回邏輯單元數量(0+1)

44.0   1   IN     00    

----------------------------------控制傳輸結束-------------------------------------------------->

有的讀卡器會多出幾次對字串描述符的請求(如果裝置描述符中三個字串的索引不為0的話),USB2.0USB3.0協議對於字串描述符的請求,略有不同

例項:Host(作業系統為windows10)下發的資料包(標準請求)對於USB2.0,

請求獲得語言ID字串(索引值為0):

bmRequestType=0x80  bRequest=0x06  wValue=0x0300  wIndex=0x0000  wLength=0x0004

例項1:

主機通過端點0下發:80 06 00 03  00 00 02 00  /* 請求長度為:2位元組 */

裝置返回:04 03                                /* 返回2位元組(字串的長度資訊) */

 

主機通過端點0下發:80 06 00 03  00 00 04 00  /* 請求長度為:4位元組 */

裝置返回:04 03 09 04                        /* 返回4位元組 */

請求獲得產品字串(索引值為1):

bmRequestType=0x80  bRequest=0x06  wValue=0x0301  wIndex=0x0409 

 

請求獲得產品字串(索引值為2):

bmRequestType=0x80  bRequest=0x06  wValue=0x0302  wIndex=0x0409 

 

請求獲得產品序列號字串(索引值為3):

bmRequestType=0x80  bRequest=0x06  wValue=0x0303  wIndex=0x0409 

 

根據bRequest可知這是一個獲得描述符的請求,對wValue高位元組的解析可知,這是一個獲得字串描述符的請求,對wValue低位元組的解析可知,這是一個獲得XXX字串的請求。

注意低位元組表示字串索引值。高位元組表示描述符型別,都是屬於字串描述符,所以都是03

對於長度,有可能是先獲取指定的字串的兩位元組wLength=0x02,裝置返回兩位元組的裝置描述符長度,再根據長度獲取指定的字串;也有可能直接是以最大長度來獲取,即wLength=0xff。比如:

 

80 06 03 03  09 04 02 00  /* PC先獲取兩位元組長度的產品序列號字串 */

0a 03                                 /* 裝置返回產品序列號字串長度[前面兩位元組] */

 

80 06 03 03  09 04 0a 00              /* PC根據返回的資料長度,再次獲取指定長度的字串描述符*/

0a 03 30 00  2e 00 30 00  31 00   /* 裝置返回實際的字串描述符資料 */

 

注:USB3.0協議中,對三個字串描述符的索引號規定不再是1/2/3,而是3/4/5。目前開發裝置端使用的都是1.1或者2.0版本的協議。

 

2. 裝置列舉階段的類特有請求

在MSC類裝置的列舉過程中,會涉及幾個類請求,這幾個類請求是Bulk-Only Transport協議中規定的類請求(介面描述符中,介面協議【bInterfaceProtocol欄位】指定為0x50)。

2.1 Bulk-Only Mass Storage Reset請求

結構如下:

請求型別

請求程式碼

數值

索引

資料長度

資料

bRequestType

bRequest

wValue

wIndex

wLength

Data

0010 0001

1111 1111

0000

介面號

0000

None

請求解析:

(1) bRequestType = 0x21 : 這是一個類請求,資料方向是裝置到主機,請求的接收者是介面。

(2) wLength = 0:沒有資料域,即裝置收到這個請求之後,按照控制傳輸的標準格式,返回資料為0的狀態資料包給主機即可。

(3) bRequest = 0xff

Debug:目前裝置中沒見到有這個命令的請求,協議中是不是已經廢棄掉了?

 

2.2 Get Max LUN請求

該請求要求裝置返回所能支援的最大的LUN(Logic Unit Nunber 最大邏輯單元數量)。邏輯單元指的是儲存器的邏輯劃分,一般一個U盤就是一個邏輯單元,而行動硬碟就有兩個甚至更多的邏輯單元。每個邏輯單元都會被作業系統賦予一個碟符(C、D、E、F等),並且以圖形化的方式顯示出來。結構如下:

請求型別

請求程式碼

數值

索引

資料長度

資料

bRequestType

bRequest

wValue

wIndex

wLength

Data

1010 0001

1111 1110

0000

介面號

0001

1 Byte

請求解析:

(1) bRequestType = 0xa1 :這是一個類請求,資料方向是裝置到主機。

(2) bRequest = 0xfe :

收到請求後,裝置在資料階段要返回1位元組的資料,該位元組表示有多少個邏輯單元。注意:數值為0表示有一個邏輯單元;數值為1表示有兩個邏輯單元;以此類推。最大支援16個邏輯單元(y = n+1 , n[ 0 , 15 ])。所以,在程式中可以靈活地定義成一個可配置的配置項。我們只需要顯示一個碟符,所以裝置端對於這個請求的響應是返回一個0。

在類請求中,處理該命令的響應:

BusHound抓到的匯流排上的資料:

目前,到此為止,處理完上述指令,控制傳輸算結束了(注意看,在此之後不再使用端點0來傳輸資料)。但是還不能彈出碟符,因為Host端僅僅是獲取到了MSC裝置的資訊,對於儲存介質的資訊還無法得知。

Host端是通過塊傳輸來獲取儲存介質的資訊,通過Bulk OUT端點發出SCSI指令,裝置收到指令,解析後又通過Bulk IN端點返回資訊到主機。接下來介紹Bulk傳輸的資料流模型和裝置端需要處理的SCSI指令。

相關文章