上一篇: https://blog.csdn.net/qq_40088639/article/details/110489923

四、Bulk-Only Transport协议传输模型

分析BusHound上的数据可知,PC端获取设备基本信息之后,不再使用端点0了(控制传输结束了),接下来都是通过批量传输端点传输数据。如下图:

Bulk-Only Transport协议:翻译为仅批量传输协议

一次批量事务分为三个阶段:命令阶段、数据阶段、状态阶段。需要注意的是每个阶段的数据流向和数据含义。下面以IN1和OUT2端点为例。

1. 批量输入事务(IN Transaction)的构成

由三个数据包构成,如下。

(阶段1: 命令阶段)主机先通过IN端点发出令牌包[IN-Token Packet]

同步域

IN-PID(0x96)

设备地址(7 位)

端点号(4位)

CRC5校验(5位)

EOP

数据包发出后,设备切换到数据接收状态,等待设备返回数据(这里有个等待超时的时间)。

(阶段2: 数据阶段)设备将数据包放到总线上进行传输[数据通过IN端点传输]

同步域

DATAx(x=0/1)

真正传输的数据(低位在前)

CRC16校验(16位)

EOP

注:具体的数据包,是根据数据切换位来决定的,所以协议分析仪上看到的,有可能是DATA0数据包,也有可能是DATA1数据包。

(阶段3: 状态阶段)主机应答[由主机发出]

同步域

ACK-PID(0x4B)

EOP

示例:设备返回CSW数据包,主机应答ACK。

2. 批量输出事务(OUT Transaction)的构成

(阶段1: 命令阶段)主机先通过OUT端点发出令牌包[OUT-Token Packet]

同步域

OUT-PID(0x87)

设备地址(7 位)

端点号(4位)

CRC5校验(5位)

EOP

紧接着进入阶段2,主机再发出一个DATA包。

(阶段2: 数据阶段)主机将数据包放到总线上进行传输[数据通过OUT端点传输]

同步域

DATAx(x=0/1)

真正传输的数据(低位在前)

CRC16校验(16位)

EOP

注:具体的数据包,是根据数据切换位来决定的,所以协议分析仪上看到的,有可能是DATA0数据包,也有可能是DATA1数据包。主机发出DATA数据包,地址和端点匹配的设备就收下这个数据包。主机发出DATA数据包后,切换到接收模式,等待设备的ACK握手包。

(阶段3)设备应答[由设备发出]

同步域

ACK-PID(0x4B)

EOP

示例:Host写数据到SD卡,其中的一笔数据(64 Bytes)。

批量输出/输入事务中的令牌包(Bulk的第一个数据包),数据域里面的数据结构中嵌有SCSI指令。设备端需要根据USB协议来解析该指令的具体含义,下文有解析。

例1:USB设备栈工作在Full-Speed模式下,OUT端点的最大包长为64字节。

指令含义解析:主机要连续写0x80[128]个块,每个块是512字节,总共要写:128 x 512 = 65536 Bytes。所以传输的次数:65536/64 = 1024次。

例2:USB设备栈工作在High-Speed模式下,OUT端点的最大包长为512字节。

指令含义解析:主机要连续写0x80[128]个块,每个块是512字节,总共要写:128 x 512 = 65536 Bytes。所以传输的次数:65536/512 = 128次。

3. Bulk传输数据流模型(仅针对开发device的情形)

(1) 命令阶段:一般是一个事务,由主机发出CBW数据包,CBW中指定数据传输的方向。

(2) 数据阶段:数据传输的方向由命令阶段来决定,写:Host到Device,反之。

(3) 状态阶段:总是由设备端返回命令完成的状态[CSW数据包]。

4. 小结

(1) ACK可以由设备发出,也可以由主机发出,但是CSW只能是设备发出。

(2) 命令阶段和状态阶段都由一个事务(一个传输)来完成,但是数据阶段会有很多个传输,或者是多个事务构成的传输(Full-Speed和High-Speed略有不同)。比如受端点最大包长限制,每次最大只能写64字节,要写入128字节,就分两次写入,就会有两个传输。

五、命令块封包CBW的结构

设备枚举阶段结束,接下来设备需要响应来自主机的SCSI指令,PC端在获取设备基本信息(设备枚举)之后,首先会通过SCSI指令来查询存储介质的信息(比如:容量大小),而且对存储介质的读写也是通过SCSI指令来完成。对于U盘,需要重点关注的SCSI指令有:INQUIRY指令,READ CAPACITY指令,READ(10)指令和WRITE(10)指令,REQUEST SENSE指令。可大致分类为:查询指令、读写指令和检测指令。主机发出的指令是嵌套在命令块封包CBW中的,因此首先要弄清楚CBW的结构。

1. 命令块封包CBW

CBW:Command Block Wrapper,CBW总共有31个字节,是命令过程中的DATA数据包(第一个Bulk-OUT包),如果使用协议分析仪来看,那就是DATA数据域。结构如下:

字节

7

6

5

4

3

2

1

0

0~3

dCBWSignature

4~7

dCBWTag

8~11

dCBWDataTransferLength

12

bmCBWFlags

13

保留值,设置为0

bCBWLUN

14

保留值,设置为0

bCBWCBLength

15~30

CBWCB

(1) dCBWSignature:是CBW数据包的包头,共有4字节,是一串字符串USBC。用ASCII码表示就是0x55、0x53、0x42、0x43。在USB总线上传输,按照低位在前,即先传低字节,:55 53 42 43 … … ,所以存储的时候,使用一个变量存储即可:CSW_Signature = 0x43425355。

(2) dCBWTag:由主机动态分配的4字节的标签(并不是固定的值),设备需要将这个值存储起来,在CSW阶段中的dCSWTag填入该值(dCSWTag = dCBWTag)。

(3)dCBWDataTransferLength:4字节,下一个阶段(数据阶段),将要传输的数据的总字节数量。在总线上,是低字节在前。

例子:拷贝数据到U盘,协议分析仪上的数据。

数据阶段,传输的总数据量(dCBWDataTransferLength):00 10 00 00 ,即:0x00001000 = 0x1000 = 4096 bytes = 64 x 64(次)。写入一笔数据,可分为三次传输:命令过程(CBW) + 数据过程(DATA) + 状态过程(CSW)。每个过程都由主机先发出令牌包(设备的控制器会产生令牌包中断事件)。对于Host写数据到Device来说,这个字段和write(10)指令的第七、八字节是对应的。针对上面的例子,write(10)指令中就有写数据的信息,连续写0x8个块,每个块是512,写入的总数据量也是:8 x 512 = 4096 bytes = 64 x 64(次) 。

(4)bmCBWFlags:数据传输方向的标志,只用到一字节,且只用到该字节中的最高位(D7),其余的位设置为0。

D7:为1则表示数据是输入的(相对主机来说),即值为:0x80

D7:为0则表示数据是输出的(相对主机来说),即值为:0x00

(5)bCBWLUN:这个字段只用到了一个字节中的低4位,高4位设置为0,目标逻辑单元的标号(如果存在多个逻辑单元,那么是根据该字段来区分不同的逻辑单元),对于主机的Get Max LUN请求,当设备返回的是0,则表示只有一个逻辑单元(PC上只会弹出一个盘符),那么该字段就设置为0即可。

(6)bCBWCBLength:该字段指的是CBWCB的长度。只用到了一个字节中的低5位,取值范围可以是:000 00000 ~ 000 11111,但是,在USB协议中,规定具体的SCSI命令最大长度为16个字节,因此,该字段实际上有效的取值范围是:1~16。比如,write(10)指令的长度为10,则该字段的值就是0x0A

(7)CBWCB:具体的命令数据,长度为16字节,前面的15个字节[0~14]就相当于是包头。如果命令数据长度不够16字节,则用0来凑。比如INQUIRY指令的长度只有6字节,则bCBWCBLength字段就是0x06,后面用0凑够16字节的CBWCB。

例:PC端通过OUT2端点发出的INQUIRY命令(一个完整的CBW)

55.2  OUT    55 53 42 43   60 f1 b3 0f    24 00 00 00

80 00 06     [12] 00 00 00  24 00  00 00  00 00 00 00  00 00 00 00

CBWCB: [12] 00 00 00  24 0000 00  00 00 00 00  00 00 00 00 //后面凑了10个字节的0

下面具体解析每个SCSI指令。

六、查询指令

1. 设备信息查询指令INQUIRY

这里指的是MSC磁盘设备信息,跟上文中的枚举过程所获得的信息有区别,设备枚举获取的是USB 设备的信息。主机用INQUIRY命令请求获取磁盘设备的一些基本信息,该命令长度为6个字节,格式如下:

字节

7

6

5

4

3

2

1

0

0

0x12(指令的标识)

1

Obsolete(0)

0

2

页码(设置为0)

3

返回数据的缓存区大小,一般是36字节 = 0x0024

4

5

0

对于这个指令,一般来说,设备需要返回36个字节的数据,按照USB协议,以规定的结构返回。

1.1 INQUIRY指令的返回数据格式

字节

7

6

5

4

3

2

1

0

0

保留,值为0

外设的类型

1

RMB

保留,值为0

2

ISO版本号

ECMA版本号

ANSI版本号

3

保留,值为0

对应的数据格式

4

附加数据长度(1 + 三字节的保留数据+厂商字符串+产品字符串+产品版本信息)

5~7

三字节的保留数据,都设置为0

8~15

厂商信息(字符串)

16~31

产品信息(字符串)

32~35

产品版本信息(字符串)

说明:

(1)外设类型设置为0,表示是一个可寻址设备,所以第一个字节一般设置为0。

(2)RMB表示存储介质是否可移除。0:不可移除,1:可移除;对于U盘是可移除设备,

所以,第二个字节设置为0x80。

(3)ISO、ECMA、设置为0。ANSI指令集版本号可根据实际情况设定,一般有两个版本:

ANSI指令集版本号:  0x00 ------> 老版本USB协议规定。第四字节对应的数据格式为0x01

ANSI指令集版本号:  0x02 ------> 新版本USB协议规定。第四字节对应的数据格式为0x02

(4)第四字节之后,上面表格中有解释。

注:1)翻译参考USB协议官方描述文档;2)前面的8个字节基本上是固定的,后面28字节是字符串,可用空格补全。

实例:设备响应INQUIRY命令(更正一下圈圈大神的示例)

//这是圈圈大神的示例

code uint8 DiskInf[36]=

{

0x00, //磁盘设备

0x00, //其中最高位D7为RMB。RMB=0,表示不可移除设备。如果RMB=1,则为可移除设备。

 0x00, //ANSC指令集版本号0

 0x01, //对应的数据响应格式

0x20, //附加数据长度,为32字节。/*圈圈大神原本设置为31字节,这里个人理解应该是32字节*/

0x00, //保留

 0x00, //保留

 0x00, //保留

 0xB5,0xE7,0XC4,0xD4,0xC8,0xA6,0xC8,0xA6, //厂商标识,为字符串“电脑圈圈”

                                      //产品标识,为字符串“自己做的假U盘”

 0xD7,0xD4,0xBC,0xBA,0xD7,0xF6,0xB5,0xC4,0xBC,0xD9,0x55,0xC5,0xCC,0x00,0x00,0x00,

 0x31,0x2E,0x30,0x31                    //产品版本号,为1.01

};

//这是Linux中标准的示例

static bool inquiryRequest(void)

{

/*

* inquiry[2]: ANSI SCSI level 2

* inquiry[3]: SCSI-2 INQUIRY data format

*/

u8_t inquiry[] = {

0x00, //磁盘设备

0x80, /*其中最高位D7为RMB。RMB=0,表示不可移除设备。如果RMB=1,则为可移除设备。*/

0x02, //ANSC指令集版本号2

0x02, //对应的数据响应格式

36 - 4,

0x00, 0x00, 0x00, /* 保留的三字节数据 */

'Z', 'E', 'P', 'H', 'Y', 'R', ' ', ' ',

'Z', 'E', 'P', 'H', 'Y', 'R', ' ', 'U', 'S', 'B', ' ',

'D', 'I', 'S', 'K', ' ',

'0', '.', '0', '1',

};

csw.Status = CSW_PASSED;

return write(inquiry, sizeof(inquiry));

}

注:

(1)PC端可能会多次查询(有时候,设备端没有返回足够的数据,可能导致CRC校验出错,因此Host会多次进行查询----跟USB设备枚举过程类似,只能说命令先后大体上是一致,其中因时序问题,有很多不稳定的因素,会造成指令的重复下发和响应,属于正常现象)。

(2)磁盘信息是Host使用SCSI指令来请求获取得到的,设备通过解析CBW数据包后返回,数据交互使用Bulk传输,即Bulk IN端点和Bulk OUT端点;而USB设备信息中的字符串描述符是跟控制传输有关,使用的控制传输端点,即端点0;其中有三个类似的信息字段,要注意区分[厂商+产品+产品序列号字符串]。对比如下图:

2. 读格式化容量指令READ FORMAT CAPACITIES

主机用该命令来获得设备的格式化容量,命令长度为10[0~9,第10个字节以后都用0来凑],如果不存在存储介质(比如读卡器,没插卡的时候),设备端返回最大能支持的格式化容量即可。格式如下:

字节

7

6

5

4

3

2

1

0

0

0x23

1

逻辑单元号

保留

2~6

保留

7

分配的缓冲区长度(高字节),一般设置为0

8

分配的缓冲区长度(低字节),一般设置为0xfc=252 < 255

9

保留

10

保留

11

保留

2.1 响应读最大格式化容量数据格式

设备端需要返回12个字节的数据,每个字段的含义如下表。

字节

7

6

5

4

3

2

1

0

0

保留,设置为0

1

保留,设置为0

2

保留,设置为0

3

容量列表的长度,一般设置为8字节,就是紧跟其后的8字节:0x08

4

块数(高字节在前)

5

6

7

8

保留,设置为0

描述符代码

9

每一块的字节数(高字节在前)

10

11

最大格式化容量的计算方法:格式化容量 = 块数 x 每块字节数

其中,块数是由具体的存储介质大小来决定;每一块的字节数一般设置为512字节,即0x200。容量列表长度就是8,描述符代码一般设置成0x02(也可以设置成0x03,具体含义可参考官方spec)。

例1:设备端将响应指令,需要返回的数据存在一个数组中。

//READ_FORMAT_CAPACITIES命令响应数据格式

uint8 MaximumCapacity[12]=

{

0x00, 0x00, 0x00, //保留

0x08,          //容量列表长度

0x01, 0x00, 0x00, 0x00,  //块数(最大支持8GB)

0x03,           //描述符代码为3,表示最大支持的格式化容量

0x00, 0x02, 0x00  //每块大小为512字节

};

格式化容量:0x01 00 00 00 x 0x200 = 8589934592 Bytes = 8388608 KB = 8192MB = 8 GB

注:格式化容量指的是该设备最大所支持的格式化容量,仅仅是参考的作用。实际的读写容量,由具体的存储介质大小来决定(即格式化容量可以不等于实际容量,但是一般在开发中,两者的值设置相同),在3.0的读卡器中,甚至发现设备端返回给PC的最大格式化容量都是0,所以这个值,仅仅是参考的作用。但是,开发还是要遵循USB协议,既然Host端有指令下发,就要按照协议规定的格式来回复。

例2:标准的初始化流程(伪代码)

#define BLOCK_SIZE         512

static u32_t block_count;

static bool readFormatCapacity(void)

{

u8_t capacity[] = { 0x00, 0x00, 0x00, 0x08,

(u8_t)((block_count >> 24) & 0xff),

(u8_t)((block_count >> 16) & 0xff),

(u8_t)((block_count >> 8) & 0xff),

(u8_t)((block_count >> 0) & 0xff),

0x02,

(u8_t)((BLOCK_SIZE >> 16) & 0xff),

(u8_t)((BLOCK_SIZE >> 8) & 0xff),

(u8_t)((BLOCK_SIZE >> 0) & 0xff),

};

csw.Status = CSW_PASSED;

return write(capacity, sizeof(capacity));

}

//2.响应指令

static void CBWDecode(u8_t *buf, u16_t size)

{

………………………………………………………………………………………..

case READ_FORMAT_CAPACITIES:

LOG_DBG(">> READ_FORMAT_CAPACITY");

readFormatCapacity();

break;

………………………………………………………………………………………..

}

int main(void)

{

………………………………………………………………………………………..

//1. 通过文件系统来获取存储介质的总扇区数量。比如SD卡的总扇区数量

ret = disk_ioctl(disk_no, GET_SECTOR_COUNT, &block_count);

if (ret) {

LOG_ERR("Unable to get sector count");

goto error;

}

………………………………………………………………………………………..

}

3. 读容量指令READ CAPACITY

该命令长度为10字节(0x0a),主机通过该指令来读取MSC设备中的存储介质的实际容量。指令格式如下:

字节

7

6

5

4

3

2

1

0

0

0x25

1

保留

RELADR

2

逻辑块地址

3

4

5

6

保留

7

保留

8

保留

PMI

9

保留

附图:官方文档描述

注:

(1)由于该指令只有10字节,所以,当它嵌入到CBW里面的CBWCB时,需要补上6个字节的0数据。所以,该命令除了第一个字节为固定值0x25之外,其余的都是0

例子:由主机发出的读容量指令

//读存储介质容量指令

//READ CAPACITY CMD:   Lenght--->Array[14] = 0x0a    Type Code--->Array[15] = 0x25

29.2 31 OUT 55 53 42 43  f0 6a 67 63  08 00 00 00  80 00 [0a] [25]  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00

READ CAPACITY:  [25] 00 00 00  00 00 00 00  00 00   00 00 00  00 00 00 //补上6个0

3.1 命令的响应格式

设备端要返回的8字节数据格式如下

字节

7

6

5

4

3

2

1

0

0

最大(最后)逻辑块地址,高位在前

1

2

3

4

每块的字节大小,高位在前

5

6

7

附图:官方文档描述

MSC设备中存储介质的总容量计算方法:介质总容量 = (最大逻辑地址+1) x 每块字节数。因为对存储介质的读写是从0地址开始读写的,所以,逻辑块 = 最大逻辑地址+1,读写地址可认为是扇区号。

实例:主机获取最大格式化容量和存储介质的实际容量

例1: 14G的SD卡

/*1. 读格式化容量指令 */

//(1). 主机发出(READ FORMAT CAPACITY:Lenght--->Array[14] = 0x0a Type Code--->Array[15] = 0x23)

29.2  31 OUT  55 53 42 43  10 30 9f 66  fc 00 00 00  80 00 [0a] [23]  00 00 00 00  00 00 00 fc  00  00 00 00  00 00 00 //补上6个0

//(2). 设备返回

29.1  12  IN  00 00 00 08  01 da cc 00  02 00 02

/* 2. 读存储介质容量指令 */

//(1). 主机发出(READ CAPACITY:Lenght--->Array[14] = 0x0a Type Code--->Array[15] = 0x25)

29.2 31 OUT 55 53 42 43  f0 6a 67 63  08 00 00 00  80 00 [0a] [25]  00 00 00 00  00 00 00 00  00  00 00 00  00 00 00 //补上6个0

//(2). 设备返回

29.1  8  IN  01 da cb ff  00 00 02 00

//最大地址:0x01dacbff

//根据地址计算介质容量:(0x01dacbff-1) x 0x200 = 15931538432 Bytes

//有这么多个地址/块/扇区号,每个块是512字节,总共就是15931538432 Bytes

//单位转换:15931538432/1024/1024/1024 = 14GB

4. 查询指令对比

指令

操作码

指令长度(byte)

响应长度(byte)

INQUIRY

0x12

6(0x06)

根据实际

READ FORMAT CAP

0x23

10(0x0a)

12

READ CAPACITY

0x25

10(0x0a)

8

(1)扇区号由文件系统统一管理,可以认为是连续的,并且编号是从0开始的。因此,扇区号和逻辑地址可认为是对应的[READ FORMAT CAP的计算方法和 READ CAPACITY是相同的]。

(2)READ FORMAT CAPACITIES读到的是设备所支持的最大的容量,READ CAPACITY读到的才是实际的磁盘容量。

(3)这三个查询指令的长度都不足16字节,因此,嵌入到CBW中时,需要补0[PC端自动完成,开发设备,只需要按照协议,返回指定格式的数据即可,但是当开发Host时,就需要关注每一个字段的含义]。

(4)这三个指令都属于“查询”指令[查询磁盘信息,查询磁盘容量]。指令的长度信息是CBW中的第14个字段[往后才是指令数据]。下面解析数据读写指令。

七、数据读写指令

1. READ(10)指令

主机使用READ(10)指令来读取存储介质中的数据,该指令长度为10个字节,结构如下。

字节

7

6

5

4

3

2

1

0

0

0x28

1

保留(0)

DPO

FUA

保留

RELADR

2

逻辑块地址(高字节在前)

3

4

5

6

保留(0)

7

传输的长度(高字节)

8

传输的长度(低字节)

9

0

(1)DPO、FUA、RELADR都是0,也就是第一个字节为0x00。

(2)逻辑块地址就是要读取数据的起始块的地址,块设备中读写数据是按块[块的编号就是扇区号]来进行操作的,一个逻辑块就是一个扇区,大小为512字节[对于一般的文件系统来说]。

(3)传输的长度,单位是xx个逻辑块:读多少个逻辑块,比如要读取2KB的数据,那就需要连续读取4个逻辑块,4 x 512 = 2048 bytes。

(4)设备根据指令中指定的逻辑块地址,从存储介质中读取到数据,并通过批量传输端点返回,当全部数据都返回之后,设备端最再返回CSW数据包[下文]。

(5)需要重点关注的是第七和第八个字节。

实例:PC端读取存储介质,读起始逻辑地址为0,连续读8个块。

2. WRITE(10)指令

主机使用WRITE (10)将数据写入到存储介质中,该指令的长度也是10个字节,结构如下。

字节

7

6

5

4

3

2

1

0

0

0x2A

1

保留(0)

DPO

FUA

EPB

保留(0)

RELADR

2

逻辑块地址(高字节在前)

3

4

5

6

保留(0)

7

传输的长度(高字节)

8

传输的长度(低字节)

9

0

主机发送该指令后,紧接着就会发出实际需要传输的数据,设备收到全部的数据之后,再返回CSW[下文],需要重点关注的是第七和第八个字节。

3. 数据读写指令对比

(1)指令长度都是10字节,所以嵌入到CBW中时,需要补上6字节的0数据。CBW数据包中dCBWDataTransferLength和读/写指令中的传输长度(第七第八字节)是对应的,可以计算,

例:写数据指令

将CBW的数据复制出来,解析嵌套在里面的WRITE(10)指令:

0: 55 53 42 43 B0 B1 18 33 00 00 01 00 00 00 0A 2A

16: 00 00 00 43 C0 00 00 80 00 00 00 00 00 00 00

dCBWDataTransferLength:00 00 01 00 = 0x 00 01 00 00 = 0x10000 = 65536 bytes

对应的写指令中,写数据的长度信息:

0x 00 80= 0x80块= 128 块,连续写128个块:数据量--->128 x 512 =65536 bytes

八、命令状态封包CSW

1. CSW的结构

CSW:Command Status Wrapper,共有13个字节,结构如下。

字节

7

6

5

4

3

2

1

0

0~3

dCSWSignature

4~7

dCSWTag

8~11

dCSWDataResidue

12

dCSWStatus

(1)dCSWSignature:是CSW数据包的包头,共有4字节,是一串字符串USBS。用ASCII码表示就是0x55、0x53、0x42、0x53。在USB总线上传输,先传输的是低位(LSB在前),则为:55 53 42 53 … … 。

(2)dCSWTag:命令状态封包的标签,值就是CBW中的 dCSWTag。

(3)dCSWDataResidue:命令完成时的剩余字节数,是实际完成的传输的字节数(可能是读也可能是写)和主机在CBW中的dCBWDataTransferLength的差,一般来说会是0(正常读写的情况下)。

(4)dCSWStatus:指令执行的状态,成功:0x00,失败:0x01,阶段性错误:0x02。这是需要重点关注的字段。

2. 对比CBW和CSW

(1)CBW的长度是31个字节:15+16,CSW长度是13字节。

(2)都有数据头:USBC--->55 53 42 43,USBS--->55 53 42 53

(3)都有标签:dCSWTag = dCBWTag

(4)8~11字节都代表字节数:数据过程中需要传输的字节数/数据传输结束剩余的字节数

如果批量传输出现异常(比如读写出错,或者读写超时等异常),则设备需要返回错误的信息给主机(dCSWStatus = 0x01),然后主机会使用检测命令来检测出错的原因并决定是否重新发起CBW。

九、检测指令

1. REQUEST SENSE

主机使用该命令来检测上一个指令失败的原因,该命令有12个字节,格式如下:

字节

7

6

5

4

3

2

1

0

0

0x03

1

逻辑单元号

保留(0)

2

保留(0)

3

保留(0)

4

分配缓存区的长度,以字节为单位,为18字节(对应返回数据的格式)

5~11

保留(0)

CBWCB:    [03] 00 00 00  12 00 00 00  00 00 00 00  00 00 00 00 //补上4字节的0数据

1.1 命令的响应格式

设备端需要返回18字节的数据,格式如下。

字节

7

6

5

4

3

2

1

0

0

valid

0x70

1

保留(0)

2

保留(0)

Sense Key

3

MSB

信息

LSB

4

5

6

7

附加数据的长度(10字节),就是后面紧跟着的10字节

8~11

保留(0)

12

Sense Code(ASC)

13

Sense Code Qualifier(ASCQ)

14~17

保留(0)

(1)3~6字节,一般设置为0,不需要附加任何信息。如果是读写出错,则协议规定,表示的是出错的逻辑块地址信息(并不是绝对的,因为读写出错之前,就知道是对哪一个逻辑块进行读写,所以这里也没必要将出错的逻辑块地址信息返回)。

(2)第7字节是固定的0x0a

(3)所以,设备端响应主机的检测指令时,只需要按照实际的出错情况,构造这三个值:

Array[2] = Sense Key

Array[12] = Sense Code(ASC)

Array[13] = Sense Code Qualifier(ASCQ)。

其余的值,均设置为0即可。

附1:常用的出错原因代码(取自linux源码)

/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */

#define SS_NO_SENSE                                         0

#define SS_COMMUNICATION_FAILURE                  0x040800  /* 通信故障  */

#define SS_INVALID_COMMAND                          0x052000  /* 无效的命令  */

#define SS_INVALID_FIELD_IN_CDB                          0x052400  /* CDB中的字段无效 */

/* 逻辑块地址超出范围  */

#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE     0x052100

/* 不支持的逻辑单元(目标逻辑单元号不合法) */

#define SS_LOGICAL_UNIT_NOT_SUPPORTED                     0x052500

/* 存储介质不存在 */

#define SS_MEDIUM_NOT_PRESENT                                0x023a00

/* 不允许移除存储介质(存储介质是不可移除的) */

#define SS_MEDIUM_REMOVAL_PREVENTED                     0x055302

/* 还不可读 */

#define SS_NOT_READY_TO_READY_TRANSITION        0x062800

/* 有重置事件发生 */

#define SS_RESET_OCCURRED                                    0x062900

/*不支持保存参数 */

#define SS_SAVING_PARAMETERS_NOT_SUPPORTED   0x053900

/* 未恢复的读取错误 */

#define SS_UNRECOVERED_READ_ERROR              0x031100

/* 写出错 */

#define SS_WRITE_ERROR                                       0x030c02

/ * 写保护 */

#define SS_WRITE_PROTECTED                              0x072700

#define SK(x)                 ((u8_t) ((x) >> 16))    /* Sense Key byte, etc. */

#define ASC(x)              ((u8_t) ((x) >> 8))      /* AddSenseCode*/

#define ASCQ(x)                 ((u8_t) (x))            /* AddSnsCodeQlfr */

示例:空的读卡器(不插SD卡)

//1. [15] = 0x23 :读格式化容量指令,[数据过程的数据长度[8]~[11]:0xfc = 252]

55.2  OUT    55 53 42 43  10 1c ba 0d  fc 00 00 00  80 00 0a [23]  00 00 00 00  00 00 00 fc  00 00 00 00  00 00 00

55.1  RESET

55.1  IN     55 53 42 53  10 1c ba 0d  fc 00 00 00  01  // CSW = fail

//2. [15] = 0x03 :探测上一个命令执行失败的原因,[数据过程的数据长度[8]~[11]:0x12 = 18]

55.2  OUT    55 53 42 43  10 1c ba 0d  12 00 00 00  80 00 0c [03]  00 00 00 12  00 00 00 00  00 00 00 00  00 00 00

//设备端要回复SS_MEDIUM_NOT_PRESENT

55.1  IN     70 00 02 00  00 00 00 0a  00 00 00 00  3a 00 00 00  00 00

//Sense key=[2]=0x02

//Additon Sense Code(ASC)=[12]=0x3a

//Additon Sense Code Qualifier(ASCQ)=[13]=0x00

//紧接着返回REQUEST SENSE的执行状态为“成功”

55.1  IN     55 53 42 53  10 1c ba 0d  00 00 00 00  00  // CSW = success

2. TEST UNIT READY指令

主机使用该命令来检测逻辑单元(磁盘设备)是否已经准备好,该指令只有6字节,格式如下:

字节

7

6

5

4

3

2

1

0

0

0x00

1

保留(0)

2

3

4

5

保留(0)

这个指令其实就是6字节的0数据:

CMD    00 00 00 00  00 00  TEST UNIT READY

当它嵌入到CBW中时,要补上10个0数据,所以CBWCB就是16个0。

2.1 指令的响应格式

该指令没有数据过程,即CBW中的array[8]~array[11]都是0。设备直接在Bulk IN端点中返回CSW包即可。如果设备已经准备好(枚举成功、正确响应主机的SCSI指令),没有出错,则返回指令执行成功即可(dCSWStatus = 0x00),反之,则返回指令执行失败(dCSWStatus = 0x01)。

如果返回TEST UNIT READY指令执行失败,则主机会紧接着询问指令执行失败的原因(REQUEST SENSE),这时候又回到REQUEST SENSE指令的处理。

例:读卡器(不插SD卡)

-------------------------------------------------------------------------------------------------

//1. 逻辑单元是否已经准备好

61    CMD    00 00 00 00  00 00

//[15]= 0x00 [数据过程的数据长度[8]~[11]:0x00 = 0] 没有数据

55.2  OUT    55 53 42 43  00 fa ad 13  00 00 00 00  00 00 06 [00]  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00

//csw = fail

55.1  IN     55 53 42 53  00 fa ad 13  00 00 00 00  01

//2.  [15] = 0x03 :探测上一个命令执行失败的原因 [数据过程[8]~[11]:0x12 = 18]

55.2  OUT    55 53 42 43  00 fa ad 13  12 00 00 00  80 00 0c [03] 00 00 00 12  00 00 00 00  00 00 00 00  00 00 00

//Device 返回失败原因

55.1  IN     70 00 02 00  00 00 00 0a  00 00 00 00  3a 00 00 00  00 00

//csw = success

55.1  IN     55 53 42 53  00 fa ad 13  00 00 00 00  00

//主机解析返回的探测数据[ 18 Byte ],得知失败的原因是:没有存储媒介

61    SENSE  70 00 02 00  00 00 00 0a  00 00 00 00  3a 00 00 00  00 00  //no media

-------------------------------------------------------------------------------------------------

3. 对比两个检测指令

(1)REQUEST SENSE长度为12字节,TEST UNIT READY长度为6字节。

(2)REQUEST SENSE有数据过程,TEST UNIT READY是直接返回CSW数据包。

场景1:主机发出的某条SCSI指令[非TEST UNIT READY ]执行失败,紧接着主机询问原因。

---------------------------------------------------------------------------------------------------------------------

//某条SCSI指令执行失败[dCSWStatus = 0x01],比如:

/* 读格式化容量/容量失败 */

/* 读写SD卡失败 */

命令阶段(主机发出):REQUEST SENSE

数据阶段(设备返回):18字节的信息(上一条指令失败的原因)。

状态阶段(设备返回):CSW数据包[当前SCSI指令执行状态:成功]

----------------------------------------------------------------------------------------------------------------------

场景2:主机发出TEST UNIT READY,设备返回失败,紧接着主机询问原因。

---------------------------------------------------------------------------------------------------------------------

命令阶段(主机发出):TEST UNIT READY

状态阶段(设备返回):CSW数据包(dCSWStatus = 0x01)

命令阶段(主机发出):REQUEST SENSE

数据阶段(设备返回):18字节的信息(上一条指令失败的原因)。/*写出错/介质不存在 */

状态阶段(设备返回):CSW数据包[当前SCSI指令执行状态:成功]

----------------------------------------------------------------------------------------------------------------------

十、设备端状态的变化

(1)设备的初始化处于命令阶段,等待主机发出CBW,收到CBW后进入数据阶段。这个阶段有一个批量输出事务构成一个批量传输。设备收到CBW后,解析CBW,进入数据阶段。

(2)整个数据阶段就是一个批量传输,这个批量传输由很多个批量输入/输出事务构成。

(3)数据发送或者接收完成,设备端进入状态阶段。在状态阶段返回指令执行的情况。

(4)状态阶段结束,设备又进入命令阶段,等待主机发出下一个CBW。

十一、比较特殊的SCSI指令

modeSense6 和 modeSense10,指令格式以及设备响应的数据格式(后续补上)。

十二、苹果系统的兼容性问题

回复INQUIRY指令时,如果指定ANSI指令集版本号为0,则苹果系统会下发modeSense10;要对这个SCSI指令做回复,否则苹果系统上没法弹出盘符。对于modeSense10,回复的数据格式:

static bool modeSense10(void)

{

u8_t sense10[] = { 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

return write(sense10, sizeof(sense10));

}

如果指定指令集版本号为2,则不会下发。

附:修复兼容性的提交标签

usb: mass_storage: process command to support Mac os
1. modify ANSI-SCSI level for inquiry command.
2. support MODE_SENSE10 command.

Change-Id: I4ec5167129abc6f03d58e1651ed3ac2228a2ec4a

MSC类的基础篇介绍结束,接下来会介绍一些开发中的实例(读卡器、空盘符、真U盘)。

02 MSC类设备-基础篇(二)相关推荐

  1. 01 MSC类设备-基础篇(一)

    一.简介 在USB协议中,规定了一类大容量存储设备(Mass Storage Device Class)协议.常见的USB大容量设备有:U盘.USB移动硬盘.USB移动光驱.USB读卡器.USB打印机 ...

  2. class括号里的object_Python入门 类class 基础篇

    记住一句话:类是模板,而实例则是根据类创建的对象. 我初学时对类的理解是从类的字面上,可以片面的认为它是一个种类,它是相似特征的抽像,也就是相似的东西,可以把相似特征的事务抽象成一个类.(事务可以是具 ...

  3. 用Kotlin撸一个图片压缩插件-插件基础篇(二)

    简述: 前两天写了篇用Kotlin撸一个图片压缩插件-导学篇,现在迎来了插件基础篇,没错这篇文章就是教你如何一步一步从零开始写一个插件,包括插件项目构建,运行,调试到最后的上线发布整个流程.如果你是插 ...

  4. vue实战入门基础篇二:从零开始仿门户网站实例-开发框架搭建

    上一篇:vue实战入门基础篇一:从零开始仿门户网站实例-前期准备工作 vue实战入门基础篇二:从零开始仿门户网-2022-2-23 21:00:27 一.目录 第一篇:前期准备工作 第二篇:开发框架搭 ...

  5. mysql 基础篇(二) 账号、权限管理

    mysql 基础篇(二) 账号.权限管理.备份与还原 建立账号密码: Grant all on test.* to "cj"@"localhost" ident ...

  6. WF4.0 基础篇 (二) Activity介绍及WriteLine Activity的使用

    从本篇开始,将正式讲解WF4.0,本文主要涉及如下内容:Activity介绍, WF4.0 中工作流的结构,通过WriteLine演示InArgument<T>参数的使用 目录 1     ...

  7. 字符变量赋值规则_Java的常量、变量、数据类型(基础篇二)

    标识符 标识符:是指在程序中自己定义的内容,如类名.方法名.变量名等等. 命名规则:是有硬性要求的 关键字:是指Java已经定义好的单词,具有特殊含义,比如public.static.class.vo ...

  8. Python机器学习基础篇二《监督学习》

    前言 前期回顾: Python机器学习基础篇一<为什么用Python进行机器学习> 前面说过,监督学习是最常用也是最成功的机器学习类型之一.本章将会详细介绍监督学 习,并解释几种常用的监督 ...

  9. python进阶记录之基础篇二十六_Python进阶记录之基础篇(十六)

    回顾 在Python进阶记录之基础篇(十五)中,我们介绍了面向对象的基本概念以及Python中类和对象的基础知识,需要重点掌握类的创建和对象的使用.今天我们继续讲一下Python中面向对象的相关知识点 ...

最新文章

  1. Go 学习笔记(20)— Go 操作 json 文件(编码生成 json、解码 json 为 map、解码 json 为 struct)
  2. UITableView数据的添加、删除、移动
  3. mysql获取删除的条数_如何从mysql表中删除数百万条记录而不会减速
  4. rust编程之道 pdf_深挖一篇嵌入式内核论文之后,我发现 Rust 正在悄悄改变世界...
  5. linux swi 内核sp,Linux内核分析课程8_进程调度与进程切换过程
  6. 24 React.createRef()用法细节分析
  7. U-BOOT之一:BootLoader 的概念与功能
  8. 动态执行shell脚本
  9. redis集群断电数据怎么恢复_如何做到 10T 集群数据安全备份、1GB/s 快速恢复?...
  10. linux 迁移mysql目录_linux默认mysql迁移目录
  11. 流媒体地址文件制作方法
  12. python shell运行_Python 执行 Shell 命令
  13. navicat如何粘贴多行数据
  14. Unable to find image ‘yt:latest‘ locally
  15. 高德地图功能点使用整理
  16. phrases practice_Choose any passage from unit 3 and unit 4 to practice.
  17. 雷军:《我十年的程序员生涯》系列之三(失败的大学创业经历)
  18. OVS:网络环路 广播风暴解决方案
  19. sec2-GObject
  20. 计算机专业申请ps怎么写,计算机专业PS范文.

热门文章

  1. kvm线程-005-线程状态-THREAD_JUST_BORN
  2. html通过css来设置半透明背景
  3. 在PPT中看不到边框
  4. adaboost训练 之 弱分类器训练原理
  5. JAVA线程 -- 线程状态
  6. 服务器事件查看器根据登录id如何查找信息,Windows中如何查看日志(如查看远程登陆的IP地址)以及常用日志ID...
  7. 前端代理解决跨域问题
  8. 网络命令一览表(绝对实用)
  9. java遍历json数据_Java 如何遍历JsonObject对象
  10. php中的时间戳_php时间戳是什么