02 MSC类设备-基础篇(二)
上一篇: 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 Change-Id: I4ec5167129abc6f03d58e1651ed3ac2228a2ec4a |
MSC类的基础篇介绍结束,接下来会介绍一些开发中的实例(读卡器、空盘符、真U盘)。
02 MSC类设备-基础篇(二)相关推荐
- 01 MSC类设备-基础篇(一)
一.简介 在USB协议中,规定了一类大容量存储设备(Mass Storage Device Class)协议.常见的USB大容量设备有:U盘.USB移动硬盘.USB移动光驱.USB读卡器.USB打印机 ...
- class括号里的object_Python入门 类class 基础篇
记住一句话:类是模板,而实例则是根据类创建的对象. 我初学时对类的理解是从类的字面上,可以片面的认为它是一个种类,它是相似特征的抽像,也就是相似的东西,可以把相似特征的事务抽象成一个类.(事务可以是具 ...
- 用Kotlin撸一个图片压缩插件-插件基础篇(二)
简述: 前两天写了篇用Kotlin撸一个图片压缩插件-导学篇,现在迎来了插件基础篇,没错这篇文章就是教你如何一步一步从零开始写一个插件,包括插件项目构建,运行,调试到最后的上线发布整个流程.如果你是插 ...
- vue实战入门基础篇二:从零开始仿门户网站实例-开发框架搭建
上一篇:vue实战入门基础篇一:从零开始仿门户网站实例-前期准备工作 vue实战入门基础篇二:从零开始仿门户网-2022-2-23 21:00:27 一.目录 第一篇:前期准备工作 第二篇:开发框架搭 ...
- mysql 基础篇(二) 账号、权限管理
mysql 基础篇(二) 账号.权限管理.备份与还原 建立账号密码: Grant all on test.* to "cj"@"localhost" ident ...
- WF4.0 基础篇 (二) Activity介绍及WriteLine Activity的使用
从本篇开始,将正式讲解WF4.0,本文主要涉及如下内容:Activity介绍, WF4.0 中工作流的结构,通过WriteLine演示InArgument<T>参数的使用 目录 1 ...
- 字符变量赋值规则_Java的常量、变量、数据类型(基础篇二)
标识符 标识符:是指在程序中自己定义的内容,如类名.方法名.变量名等等. 命名规则:是有硬性要求的 关键字:是指Java已经定义好的单词,具有特殊含义,比如public.static.class.vo ...
- Python机器学习基础篇二《监督学习》
前言 前期回顾: Python机器学习基础篇一<为什么用Python进行机器学习> 前面说过,监督学习是最常用也是最成功的机器学习类型之一.本章将会详细介绍监督学 习,并解释几种常用的监督 ...
- python进阶记录之基础篇二十六_Python进阶记录之基础篇(十六)
回顾 在Python进阶记录之基础篇(十五)中,我们介绍了面向对象的基本概念以及Python中类和对象的基础知识,需要重点掌握类的创建和对象的使用.今天我们继续讲一下Python中面向对象的相关知识点 ...
最新文章
- Go 学习笔记(20)— Go 操作 json 文件(编码生成 json、解码 json 为 map、解码 json 为 struct)
- UITableView数据的添加、删除、移动
- mysql获取删除的条数_如何从mysql表中删除数百万条记录而不会减速
- rust编程之道 pdf_深挖一篇嵌入式内核论文之后,我发现 Rust 正在悄悄改变世界...
- linux swi 内核sp,Linux内核分析课程8_进程调度与进程切换过程
- 24 React.createRef()用法细节分析
- U-BOOT之一:BootLoader 的概念与功能
- 动态执行shell脚本
- redis集群断电数据怎么恢复_如何做到 10T 集群数据安全备份、1GB/s 快速恢复?...
- linux 迁移mysql目录_linux默认mysql迁移目录
- 流媒体地址文件制作方法
- python shell运行_Python 执行 Shell 命令
- navicat如何粘贴多行数据
- Unable to find image ‘yt:latest‘ locally
- 高德地图功能点使用整理
- phrases practice_Choose any passage from unit 3 and unit 4 to practice.
- 雷军:《我十年的程序员生涯》系列之三(失败的大学创业经历)
- OVS:网络环路 广播风暴解决方案
- sec2-GObject
- 计算机专业申请ps怎么写,计算机专业PS范文.