天天看点

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

一、简介

在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个阶段(状态)。

enum Stage {

         READ_CBW,    

         ERROR,       

         PROCESS_CBW, 

         SEND_CSW,    

         WAIT_CSW     

};

三、描述符

MSC类设备没有特定类描述符(class-specific descriptor),使用的都是标准描述符。

1. 设备描述符

1.1 设备描述符的结构

偏移量/字节 大小/字节 说明
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)。

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

其次,需要注意的是下面这三个字段,在设备描述符中都设置为0。

bDeviceClass:                      0x00 

bDeviceSubClass:                   0x00

bDeviceProtocol:                   0x00

一般是在接口描述符中去指定设备的类、子类以及协议版本。

2. 配置描述符

2.1 配置描述符的结构

一个USB设置至少有一个配置描述符。标准配置描述符结构如下:

偏移量/字节 大小/字节 说明
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 接口描述符的结构

偏移量/字节 大小/字节 说明
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 端点描述符的结构

偏移量/字节 大小/字节 说明
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控制器硬件端点资源描述如下:

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

所以设置端点地址的时候,要参照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上的数据[控制传输]

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

对于U盘设备,控制传输就6条指令,将数据提取出来,分析如下:

<----------------------------------控制传输开始--------------------------------------------------

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

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

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

44.0       CTL    00 09 01 00  00 00 00 00    

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.0和USB3.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  

设备返回:04 03                               

主机通过端点0下发:80 06 00 03  00 00 04 00 

设备返回:04 03 09 04                       

请求获得产品字符串(索引值为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 

0a 03                                

80 06 03 03  09 04 0a 00             

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。

在类请求中,处理该命令的响应:

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

BusHound抓到的总线上的数据:

01 MSC类设备-基础篇(一)一、简介二、关于批量传输(Bulk Transfer)三、描述符四、请求

目前,到此为止,处理完上述指令,控制传输算结束了(注意看,在此之后不再使用端点0来传输数据)。但是还不能弹出盘符,因为Host端仅仅是获取到了MSC设备的信息,对于存储介质的信息还无法得知。

Host端是通过块传输来获取存储介质的信息,通过Bulk OUT端点发出SCSI指令,设备收到指令,解析后又通过Bulk IN端点返回信息到主机。接下来介绍Bulk传输的数据流模型和设备端需要处理的SCSI指令。

基础篇下一篇:https://blog.csdn.net/qq_40088639/article/details/110490671