1 简介
我当前电脑外接了USB读卡器,U盘和模拟的USB接口U盘,都会被系统识别为USB大容量存储设备。

USB接口的存储设备有很多U盘,USB接口硬盘,USB读卡器等等,这些设备统称为大容量存储设备,如何被完成识别的呢?接下来学习了解下。
1.1 相关协议规范
USB大容量存储设备类(Mass Storage Class)规范由四个相互独立的子规范构成,分别定义了不同的传输方式和命令集:
- 传输协议规范(定义数据/命令/状态在USB总线上的传输方式):
-
USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport
使用控制(Control)、批量(Bulk)和中断(Interrupt)三种端点进行数据传输
-
USB Mass Storage Class Bulk-Only Transport (BOT)
仅使用批量(Bulk)端点传送所有数据、命令和状态
- 命令集规范(定义对存储介质的操作命令):
-
USB Mass Storage Class ATA Command Block
用于硬盘设备的ATA/ATAPI命令集
-
USB Mass Storage Class UFI Command Specification
专门针对USB移动存储设备设计的命令规范
-
各规范的关系与应用:
- UFI命令规范包含19个长度为12字节的操作命令,专门为USB移动存储而制定
- 典型的U盘设计通常采用Bulk-Only传输协议结合UFI命令规范
- ATA命令块规范主要面向传统硬盘设备
- CBI传输协议作为早期方案,使用三种端点类型进行通信
-
常见设备组合示例
| 设备类型 | 典型子类 | 典型协议 | 组合描述 |
|---|---|---|---|
| 普通U盘/移动硬盘 | 06h (SCSI透明命令集) | 50h (BOT) | 使用SCSI命令,通过简单的批量传输进行通信。 |
| USB光驱 | 02h (ATAPI/MMC) | 50h (BOT) | 使用ATAPI命令(针对光驱),通过批量传输进行通信。 |
| USB软盘驱动器 | 04h (UFI) | 00h (CBI) 或 50h (BOT) | 使用UFI命令,可通过旧式CBI或现代BOT传输。 |
| 高性能移动SSD | 06h (SCSI透明命令集) | 62h (UAS) | 使用SCSI命令,但通过高效的UAS协议传输,实现接近原生SATA/NVMe的性能。 |
| 硬件加密U盘 | 07h (可锁定大容量存储) | 50h (BOT) | 主机必须先通过LSDFS协议解锁,然后才能使用SCSI命令。 |
1.2 BOT和UASP
U盘的协议分为BOT和UASP协议,下面表格简单把BOT 与 UASP 对比下:
| 特性 | BOT (Bulk-Only Transport) | UASP (USB Attached SCSI Protocol) |
|---|---|---|
| 诞生时间 | 1999年 (为USB 1.1设计) | 2008年 (与USB 3.0一同诞生) |
| 传输模式 | 半双工 (单向传输,类似对讲机) | 全双工 (双向同时传输) |
| 工作方式 | 单命令队列 (同一时间只处理一个指令,类似单车道) | 多命令并行处理 (支持NCQ,类似多车道) |
| 性能特点 | 速度较慢,CPU占用较高,延迟明显 | 速度显著提升,降低CPU占用、延迟和等待时间 |
| 兼容性 | 通用兼容 (USB 1.1至3.1均支持) | 需三方支持: 1. 设备端控制器 2. 主机端控制器 3. 操作系统驱动 |
| 系统支持 | 所有系统原生支持 | Win7:需安装驱动 Win8+ / Mac OS X 10.8.3+:已内置驱动,原生支持 |
我手头是USB2.0高速接口的USB,所以只能选择BOT协议来模拟U盘。
1.3 描述符
可能看了上面的传输协议会让有些人感到困惑;其实BOT存储设备和其它的普通设备类似,也有一些通用的描述符,如设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符。
一个标准的USB支持BOT的大容量设备描述符布局如下:

实际描述符比HID和CDC类设别还要简单,因为BOT大容量设备没有专门类描述符,实现相对简单。
但是多了传输协议,这里注册为SCSI透明指令集(主机和设备的通信协议),Bulk-Only的传输方式。后续会详细展开。
1.4 缩略词
前面以及后面都会用到一些专业词汇这里先统一总结下:
| 缩写 | 全称 | 解释与关联 |
|---|---|---|
| MSC | Mass Storage Class | USB大容量存储设备类。USB设备类的一种,定义了计算机如何通过USB接口访问存储设备(如U盘、移动硬盘)的通用框架。 |
| BOT | Bulk-Only Transport | 仅批量传输协议。MSC中最基础、最主流的传输协议。仅使用Bulk端点进行命令、数据和状态传输,结构简单,兼容性极广。 |
| UAS / UASP | USB Attached SCSI Protocol | USB附加SCSI协议。一种高性能传输协议,支持命令队列和全双工传输,速度远超BOT,通常需USB 3.0及以上接口及系统驱动支持。 |
| UFI | USB Floppy Interface | USB软盘接口命令集。一种基于SCSI命令子集的命令块规范,专为USB软盘驱动器设计。子类代码通常为 0x04。 |
| RBC | Reduced Block Commands | 简化块命令集。一种为闪存等简单块设备设计的精简SCSI命令集。子类代码通常为 0x01。 |
| SCSI | Small Computer System Interface | 小型计算机系统接口。一套功能强大的存储设备命令集和体系结构。在USB MSC中,其"透明命令集"是最重要的子类(代码 0x06),是U盘、移动硬盘及UAS协议的通用"语言"。 |
| ATAPI | ATA Packet Interface | ATA数据包接口。将SCSI命令封装在ATA通道上传输的协议。在USB MSC中,是CD/DVD等光驱设备使用的命令集,子类代码为 0x02。 |
| ATA | Advanced Technology Attachment | 高级技术附件。一种传统的并行硬盘接口标准。在USB MSC中,其命令块被引用为规范之一。 |
| CBI | Control/Bulk/Interrupt Transport | 控制/批量/中断传输。一种早期的传输协议,使用三种端点,主要用于旧式软驱,现已被BOT取代。 |
| LSDFS | Lockable Storage Device Functionality Specification | 可锁定存储设备功能规范。一种安全协议,主机必须先通过它"解锁"设备,才能使用SCSI命令。对应子类 0x07,用于硬件加密盘。 |
2 MCS BOT数据传输
2.1 命令数据状态协议
下图展示了命令传输、数据输入、数据输出及状态传输的流程。

BOT传输是通过主机向设备发送CBW请求,设备响应CBW返回CSW响应。
BOT传输开始是从设备复位(MASS_STORAGE_RESET)之后开始的,设备在接收到CBW请求和主机收到CSW回应后,是需要对CBW和CSW进行拆包解析。
2.1.1 Command Block Wrapper (CBW)
CBW即Command Block Wrapper,命令块包)是从 USB 主机发送到设备的命令包,它由 31 个字节构成,其中包含的命令遵从接口描述表中的 bInterfaceSubClass 域所指定的命令集,一般采用 SCSI 传输命令集。 USB 设备从 CBW 中取出并执行相应命令,向主机传送指定数据及发出反映当前命令执行状态的 CSW ( Command Status Wrapper ,状态包),它由 13 个字节构成,主机根据 CSW 来判断此次操作是否正确,从而决定是继续传送数据还是进行数据传输的错误校验。事实上错误校验一直伴随着整个数据的处理过程中。
CBW 应该从数据包的边界开始,在正好传输了 31 个字节后作为短包结束。所有后续数据和 CSW 都应该从新数据包的边界开始, CBW 的说明如下表:

c
struct CBW
{
uint32_t dCBWSignature; //CBW的标识,固定值:43425355h (小端模式)。
uint32_t dCBWTag; //主机发送的一个命令块标识,设备需要原样作为dCSWTag(CSW中的一部分)再发送给Host;主要用于关联CSW到对应的CBW。
uint32_t dCBWDataTransferLength; //CBW命令要求在命令与回应之间传输的字节数。如果为0,则不传输数据。
uint8_t bmCBWFlags; //反映数据传输的方向,0x00 表示来自Host,0x80 表示发至Host。
uint8_t bCBWLUN; //对于有多个LUN逻辑单元的设备,用来选择具体目标。如果没有多个LUN,则写0。
uint8_t bCBWCBLength; // 命令的长度,范围在0~16。
uint8_t CBWCB[16]; //传输的具体命令
};
其中:
- dCBWSignature :帮助指明该数据报为 CBW 的信号标记。这个字段的值为 0x43425355 (小端(USB CBW)),表示这是一个 CBW 。
- dCBWTag :主机发送的命令块标签。设备应在相关 CBW 的 dCSWTag 字段中将这个字段的内容返回给主机。 dCSWTag 将 CSW 与对应的 CBW 联系起来。
- dCBWDataTransferLength :主机要求在执行 CBW 命令期间,在批量输入或批量输出端点传输数据字节数。如果该字段为 0 ,则设备和主机不应该在 CBW 和相关的 CSW 中间传输数据,设备应该忽略 bmCBWFlags 中方向位的值。注意,这个字段指明的是跟在 CBW 之后数据包的长度。
- bmCBWFlags :本字段的位定义如下:
- 位 7 :方向。 0 = 从主机到设备的 DataOut , 1 = 从设备到主机的 DataIn ;
- 位 6 :废弃的,主机应该将该位设置为 0 ;
- 位 5-0 :保留,主机应该将该位设置为 0 ;
- bCBWLUN :命令块发送的设备逻辑单元号( LUN )。对于支持多个 LUN 的设备,主机应该将该字段设置为命令块寻址的 LUN 。否则应该设置为 0 。对于 U 盘主机系统来说,因为 U 盘都不支持多个 LUN ,因此该字段应该设置为 0 。
- bCBWCBLength: 命令的长度,范围在0~16
- CBWCB :设备将执行的命令块,对于 U 盘主机系统来说,就是将执行的 UFI 命令块。
2.1.2 Command Status Wrapper (CSW)
CSW应从数据包边界开始,并以恰好传输13(0Dh)字节的短数据包结束。字段的字节偏移量应与其字节大小的倍数对齐。所有CSW传输应按照小端序(littleendian)进行,即 LSB(字节0)优先。 CSW 的说明如下表:

c
struct CSW
{
uint32_t dCSWSignature; // CSW的标识,固定值:53425355h (小端模式)
uint32_t dCSWTag; //主机发送的一个命令块标识,设备需要原样作为dCSWTag(CSW中的一部分)再发送给Host;主要用于关联CSW到对应的CBW
uint32_t dCSWDataResidue; //还需要传送的数据,此数据根据dCBWDataTransferLength-本次已经传送的数据得到
uint8_t bCSWStatus; //指示命令的执行状态。如果命令正确执行,bCSWStatus 返回0
};
- dCSWSignature :帮助指明该数据包为 CSW 的信号标记,这个字段的值为 0x53425355 (小端),表示这是一个 CSW 。
- dCSWTag :设备应将这个字段设置为接收到的相应 CBW 的 dCBWTag 字段值。
- dCSTDataResidue :对于 DataOut ,设备应在这个字段报告 dCBWDataTransferLength 字段规定的要求数量与设备实际处理的数据量之差。对于 DataIn ,设备应在这个字段报告 dCBWDataTransferLength 字段规定的要求数量与设备实际发送的数据量之差。 dCSWResidue 的值不会超过 dCBWDataTransferLength 发送的值。
- bCSWStatus :表示命令执行是否成功。 0 = 执行成功,非 0 表示失败,如下:
- 00h 命令通过(运行良好)
- 01h 命令失败
- 02h 状态错误
- 03h - 04h 保留(废弃)
- 05h - FFh 保留
2.2 UFI(SCSI)命令
我们定义的Protocol是SCSI,实际上使用的还是UFI命令来进行U盘的操作,其实UFI是SCSI命令子集的命令块规范,下面列举下UFI命令。
命令汇总:
好的,以下是您提供的SCSI命令列表的双栏Markdown表格格式:
| 命令 | 描述 | 命令 | 描述 |
|---|---|---|---|
| Format Unit | 格式化未格式化的介质。 | Inquiry | 获取设备信息。 |
| Start / Stop Unit | 请求可移动介质设备加载或卸载其介质。 | Mode Select | 允许主机在外设中设置参数。 |
| Mode Sense | 向主机报告参数。支持软盘页面的向后兼容。 | Prevent/Allow Medium Removal | 阻止或允许从可移动介质设备中移除介质。 |
| Read (10) | 将二进制数据从介质传输到主机。 | Read (12) | 将二进制数据从介质传输到主机(支持更大容量)。 |
| Read Capacity | 报告当前介质容量。 | Read Format Capacity | 报告当前及支持的格式化容量。 |
| Request Sense | 向主机传输状态检测数据(错误报告)。 | Rezero Unit | 将驱动器的磁头定位到零磁道(传统命令)。 |
| Seek (10) | 将设备寻道到指定地址。 | Send Diagnostic | 执行硬复位并运行诊断。 |
| Test Unit Ready | 请求设备报告是否准备就绪。 | Verify | 验证介质上的数据可读性。 |
| Write (10) | 将二进制数据从主机传输到介质。 | Write (12) | 将二进制数据从主机传输到介质(支持更大容量)。 |
| Write and Verify | 将数据从主机传输到介质并验证。 |
下面介绍详细介绍几个常用的命令,其他开源参考协议。
2.2.1 READ_FORMAT_CAPACITIES
READ_FORMAT_CAPACITIES(读取格式化容量)命令允许主机请求在当前安装的介质上可以格式化的可能容量列表。如果当前未安装介质,UFI设备应返回设备可格式化的最大容量。

Allocation Length指定主机可以接收的格式数据的最大字节数。如果小于容量数据的大小,UFI设备只返回请求的字节数。但是,UFI设备不得调整格式数据中的容量列表长度以反映截断。
返回数据格式
返回数据包括两部分,分别为Capacity List Header和Current/Maximum Capacity Descriptor。
-
Capacity List Header

容量列表长度字段指定后面容量描述符的字节长度。每个容量描述符的长度为八个字节,使容量列表的长度等于描述符数量的八倍。
-
Current/Maximum Capacity Descriptor
Current/Maximum Capacity Descriptor描述了在UFI设备中装入介质且其格式已知时的当前介质容量,或者在未装入介质、装入的介质未格式化或装入的介质的格式未知时,UFI设备可以格式化的最大容量

- Number of Blocks表示描述符媒体类型的可寻址块总数。
- Descriptor Code指定返回给主机的描述符类型
| DescriptorCode | Descriptor Type |
|---|---|
| 01b | Unformatted Media - Maximum formattable capacity for this cartridge |
| 10b | Formatted Media - Current media capacity |
| 11b | No Cartridge in Drive - Maximum formattable capacity for any cartridge |
封装:
c
typedef struct _FORMAT_CAPACITIES_RESPONSE_STRUCT
{
#pragma pack(1)
//Capacity List Header
UCHAR Reserved0[3];
UCHAR CapacityListLength;
//Current/Maximum Capacity Descriptor
ULONG NumberOfBlocks;
ULONG DescriptorCode:2;
ULONG Reserved1 : 6;
ULONG BlockLength : 24;
#pragma pack()
}FORMAT_CAPACITIES_STRUCT,*PFORMAT_CAPACITIES_STRUCT;
示例:
//CWB
31 OUT 55 53 42 43 70 22 57 b4 fc 00 00 00 80 00 0a 23
00 00 00 00 00 00 00 fc 00 00 00 00 00 00 00
//Response
12 IN 00 00 00 08 00 00 07 fe 02 00 01 00 00(补齐)
//CSW
13 IN 55 53 42 53 70 22 57 b4 f0 00 00 00 00
2.2.2 INQUIRY
INQUIRY定义为0x12,用于查询USB存储即U盘的基本信息,这些信息包括厂家信息,产品信息及产品版本号等。
INQUIRY命令通过BULK传输的OUT端点下发给设备,设备需要先返回基本的INQUIRY信息,再返回CSW状态。
- 命令格式:

其中:- LUN(Logical Uint Number) :被设置为 0 。
- EVPD :被设置为 0 。
- Page Code: UFI 设备仅支持页代码 0 标准查询数据。
- Allocation Length:指定被返回的查询数据的最大字节数, 0 值将不会产生错误。
UFI 设备通常根据请求的字节数返回查询的数据。它不会使用查询命令报告介质状态,例如介质改变或者驱动器不准备。查询命令将不会影响驱动器单元条件或介质状态。
- 返回格式:
- Peripheral Device Type: 标识当前连接至请求逻辑单元的设备。
- 00h 直接访问设备(软盘)
- 1Fh 无(未连接至请求逻辑单元的 FDD)
- RMB:可移动介质位:应设置为1以指示可移动介质。
- ISO/ ECMA : UFI 设备的这些字段应为零。
- ANSI版本:必须包含零以符合本规范版本要求。
- Response Data Format:响应数据格式,UFI 设备应使用01h值。
- Additional Length:附加长度字段应指定参数的字节长度。若命令包的分配长度过小无法传输全部参数,则附加长度字段不应调整以反映截断情况。 UFI 设备应将此字段设置为1Fh。
- Vendor Identification:厂商标识字段包含8字节ASCII数据,用于标识产品厂商。数据应左对齐显示于该字段内。
- Product Identification:产品标识字段包含16字节ASCII数据,由厂商定义。数据应左对齐显示于该字段内。
- Product Revision:产品修订级别字段包含4字节ASCII数据,由厂商定义。数据应左对齐显示于该字段内。对于 UFI 设备,此字段表示固件修订版本号。
- Peripheral Device Type: 标识当前连接至请求逻辑单元的设备。
2.2.3 READ_CAPACITY
READ_CAPACITY命令用于主机从设备获取当前设备媒价的存储容量。
- 命令格式:
- RelAdr:该位应设为0。
- Logical Block Address:逻辑块地址应设为0。
- PMI:该位应设为0。
如果UFI设备识别已格式化介质,UFI设备将向批量输入端点上的主机返回READ CAPACITY数据。
如果介质未格式化、未知或未显示,UFI设备会使命令块失败,并将检测键设置为下列出的适当值。
- 返回格式:
- Last Logical Block Address:最后一个逻辑块地址字段存储媒体访问命令的最后一个有效LBA。
- Block Length In Bytes:块长度(字节)字段则定义了给定容量描述符下每个逻辑块的字节长度。
2.2.4 READ(10)
READ(10)命令请求UFI设备将数据传输到主机。应返回写入寻址逻辑块的最新数据值。

- DPO :该位应设为0。
- FUA :该位应设为0。
- RelAdr:该位应设为0。
- Logical Block Address:一个逻辑块地址字段。
返回即为Logical Block Address的数据,以及CSB。
2.2.5 WRITE(10)
WRITE(10)命令要求UFI设备将主机传输的数据写入介质。

- DPO :该位应设为0。
- FUA :该位应设为0。
- RelAdr:该位应设为0。
- Logical Block Address:一个逻辑块地址字段。
2.3 特殊类请求
2.3.1 MASS_STORAGE_RESET
Bulk-Only Mass Storage Reset类特定请求是USB大容量存储设备独有的。
该特定类请求的功能
- 用于复位大容量存储设备和与之关联的接口。
- 通知设备接下来的批量端点输出数据为命令块包(CBW)。
说明:
由于该请求是控制请求,所以是通过端点0发送的。
在设备完成该请求即复位之前,设备应保持NAK设备请求的状态阶段。
尽管批量大容量存储复位,设备仍应保留其批量数据切换位和端点 STALL 条件的值。
特定类请求是USB标准请求中的一种,只是相对于USB设备的标准请求,只是bmRequestType字段的D6-D5为01,表示类请求命令。
| bmRequestType(1) | bRequest(1) | wValue (2) | wIndex(2) | wLength(2) | Data(N) |
|---|---|---|---|---|---|
| 00100001b 0x21 | 11111111b 0xFF | 0000h 0x0000 | Interface 接口号 | 0000h 0x0000 | none 无数据 |
- 0x21:表示主机到设备(D7=0),类请求(D6,D5为01),接受者为设备(D4-D0为00000)
- bRequest:固定为0xff
- wValue:固定为0x0000
- wIndex:接口号ID
- wLength:不携带数据,所以数据长度为0x0000
2.3.2 BOT GET_MAX_LUN
GET_MAX_LUN特定类请求用于获取最大逻辑单元。
GET_MAX_LUN是:
- 控制请求,通过端点0来发送。
- 主机发送组设备,设备返回1字节数据。
- 发送的目标对象是接口。
| bmRequestType | bRequest | wValue | wIndex | wLength | Data |
|---|---|---|---|---|---|
| 10100001b 0xA1 | 11111110b 0xFE | 0000h 0x0000 | Interface 低字节为接口ID,高字节为0x00 | 0001h 0x0001 | 1 byte 解释见下面 |
该请求返回一个字段的数值:
- 值为0时表示有1个逻辑存储单元
- 值为1时表示有2个逻辑存储单元
- ....
- 值为15时表示有16个逻辑存储单元(最大的值为15)
2.4 主机/设备数据传输
其实这里要说的是主从机交互CBW和CSW的处理编程模型;实际在很多开源协议中比如stm32的usbd、tinyusb等都有具体实现,协议中也有具体描述。
故这里不再具体描述。
3 示例
这里也不再具体展示。
我是在stm32F103C8T6上移植成功后,又把usbd移植到我们公司开发的USB IP上,后面会移植tinyusb来适配下。
这里把在stm上实现的代码贴出来,可以根据具体代码学习:
https://github.com/Hezz-tulaf/stm32f103c8t6_demo_test/tree/main/USB