USB
USB2.0
枚举与配置
- 设备被连接到 USB 端口上,并得到检测。
- 集线器通过监控端口的电压来检测设备。集线器的 D+线和 D-线上带有下拉电阻,根据设备的速度,D+或 D-线上会带有上拉电阻。通过监控这些线上的电压变换,集线器检测设备是否得到连接。
- 这部分具体IAD描述符的参考<<iadclasscode_r10.pdf>> 2节 IAD Use Model Example
- 具体枚举配置过程,wireshark抓包不够准确,可以通过打印日志查看
- 方向分为设备到主机和主机到设备;意思是收到请求后执行的方向
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :1 (设备描述符)
- 描述符索引: 0 (第一个描述符)
- wIndex字段为字符串描述符指定语言ID
- wValue
1 | [80 06 00 01 00 00 12 00] |
- 回复设备描述符18字节,返回的是IAD描述符
- bLength: 0x12
- bDescriptorType: 0x01 (设备描述符)
- bcdUSB: 0x0200 (USB2.0)
- bDeviceClass: 0xef (Miscellaneous Device Class)
- bDeviceSubClass: 0x02 (Common Class Subclass)
- bDeviceProtocol: 0x01 (Interface Association Descriptor)
- bMaxPacketSize0: 0x40 (64 bytes)
- idVendor: 0xffff (供应商 ID)
- idProduct: 0xffff (产品 ID)
- bcdDevice 0x0100 (设备版本号)
- iManufacturer: 0x01 (制造商字符串描述符索引)
- iProduct: 0x02 (产品字符串描述符索引)
- iSerialNumber: 0x03 (序列号字符串描述符索引)
- bNumConfigurations: 0x01 (配置数量)
1 | [12 01 00 02 ef 02 01 40 ff ff ff ff 00 01 01 02 03 01] |
- 请求类型:Standard Device Request 方向:主机到设备 请求:SET_ADDRESS
- wValue: 0x001d (地址为29)
1 | [23:29:12 631]Setup: bmRequestType 0x00, bRequest 0x05, wValue 0x001d, wIndex 0x0000, wLength 0x0000 |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :1 (设备描述符)
- 描述符索引: 0 (第一个描述符)
- wIndex字段为字符串描述符指定语言ID
- wValue
1 | [23:29:12 650]Setup: bmRequestType 0x80, bRequest 0x06, wValue 0x0100, wIndex 0x0000, wLength 0x0012 |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :2 (配置描述符)
- 描述符索引: 0 (第一个描述符)
- wIndex字段为字符串描述符指定语言ID
- wLength: 0x004b (75 bytes)
- wValue
1 | [80 06 00 02 00 00 4b 00] |
- 回复描述符75字节
- 配置描述符
- bLength: 0x09
- bDescriptorType: 0x02 (配置描述符)
- wTotalLength: 0x004b (配置描述符及其子描述符的总长度)
- bNumInterfaces: 0x01 (接口数量)
- bConfigurationValue: 0x01 (配置值)
- bmAttributes: 0x80 (配置属性)
- Bit 6: Self-powered 0:总线供电
- Bit 5: Remote Wakeup 0:不支持远程唤醒
- bMaxPower: 0x32 (最大功率 单位2mA) 100mA
- 接口描述符
- bLength: 0x08
- bDescriptorType: 0x0b (接口描述符)
- bInterfaceNumber: 0x00 (接口号)
- bInterfaceCount: 0x02 (接口数量)
- bFunctionClass: 0x02 (Communication Device Class)
- bFunctionSubClass: 0x02 (Abstract Control Model)
- bFunctionProtocol: 0x01 (ITU-T V.250)
- iFunction: 0x00 (接口描述符索引)
- 接口描述符
- bLength: 0x09
- bDescriptorType: 0x04 (接口描述符)
- bInterfaceNumber: 0x00 (接口号)
- bAlternateSetting: 0x00 (备用设置)
- bNumEndpoints: 0x01 (端点数量)
- bInterfaceClass: 0x02 (Communication Device Class)
- bInterfaceSubClass: 0x02 (Abstract Control Model)
- bInterfaceProtocol: 0x01 (ITU-T V.250)
- iInterface: 0x02 (接口描述符索引)
- COMMUNICATION DESCRIPTOR
- bLength: 0x05
- bDescriptorType: 0x24 (CS_INTERFACE)
- bDescriptorSubtype: 0x00 (Header Functional Descriptor)
- bcdCDC: 0x0110 (CDC version 1.10)
- COMMUNICATION DESCRIPTOR
- bLength: 0x05
- bDescriptorType: 0x24 (CS_INTERFACE)
- bDescriptorSubtype: 0x01 (Call Management Functional Descriptor)
- bmCapabilities: 0x00
- Call Management over Data Class Interface: Not supported
- Call Management over Communication Class Interface: Not supported
- bDataInterface: 0x01 (Data Class Interface)
- COMMUNICATION DESCRIPTOR
- bLength: 0x04
- bDescriptorType: 0x24 (CS_INTERFACE)
- bDescriptorSubtype: 0x02 (Abstract Control Management Functional Descriptor)
- bmCapabilities: 0x02
- Comm Features Combinations: Not supported
- Line Coding and Serial State: Supported
- Send Break: Not supported
- Network Connection: Not supported
- COMMUNICATION DESCRIPTOR
- bLength: 0x05
- bDescriptorType: 0x24 (CS_INTERFACE)
- bDescriptorSubtype: 0x06 (Union Functional Descriptor)
- bControlInterface: 0x00 (Control Class Interface)
- bSubordinateInterface0: 0x01 (Data Class Interface)
- ENDPOINT DESCRIPTOR
- bLength: 0x07
- bDescriptorType: 0x05 (ENDPOINT)
- bEndpointAddress: 0x83 (Endpoint 3 IN)
- Direction: IN
- Number: 3
- bmAttributes: 0x03 (Transfer type: Interrupt)
- Transactions per microframe: 1 (0)
- wMaxPacketSize: 0x0008 (8 bytes)
- bInterval: 0x00 (Polling interval in (micro) frames)
- INTERFACE DESCRIPTOR
- bLength: 0x09
- bDescriptorType: 0x04 (INTERFACE)
- bInterfaceNumber: 0x01 (Interface 1)
- bAlternateSetting: 0x00 (Alternate setting 0)
- bNumEndpoints: 0x02 (2 endpoints)
- bInterfaceClass: 0x0a (Data Interface Class)
- bInterfaceSubClass: 0x00 (No specific subclass)
- bInterfaceProtocol: 0x00 (No specific protocol)
- iInterface: 0x00 (No string descriptor)
- ENDPOINT DESCRIPTOR
- bLength: 0x07
- bDescriptorType: 0x05 (ENDPOINT)
- bEndpointAddress: 0x02 (Endpoint 2 OUT)
- Direction: OUT
- Number: 2
- bmAttributes: 0x02 (Transfer type: Bulk)
- Transactions per microframe: 1 (0)
- wMaxPacketSize: 0x0040 (64 bytes)
- bInterval: 0x00 (Polling interval in (micro) frames)
- ENDPOINT DESCRIPTOR
- bLength: 0x07
- bDescriptorType: 0x05 (ENDPOINT)
- bEndpointAddress: 0x81 (Endpoint 1 IN)
- Direction: IN
- Number: 1
- bmAttributes: 0x02 (Transfer type: Bulk)
- Transactions per microframe: 1 (0)
- wMaxPacketSize: 0x0040 (64 bytes)
1 | [09 02 4b 00 02 01 00 80 32] |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :3 (字符串描述符)
- 描述符索引: 3 (第3个描述符)
- wIndex字段为字符串描述符指定语言ID 0x0409 (英文)
- wValue
1 | [80 06 02 03 09 04 26 00] |
- 回复字符串描述符22字节
- bLength: 0x16
- bDescriptorType: 0x03 (字符串描述符)
- bString: “2022123456”
1 | [16 03 32 00 32 00 32 00 31 00 32 00 33 00 34 00 35 00 36 00] |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :3 (字符串描述符)
- 描述符索引: 0 (第0个描述符)
- wIndex字段为字符串描述符指定语言ID 0x0000
- wValue
1 | [23:29:12 695]Setup: bmRequestType 0x80, bRequest 0x06, wValue 0x0300, wIndex 0x0000, wLength 0x00ff |
- 回复字符串描述符4字节
- bLength: 0x04
- bDescriptorType: 0x03 (字符串描述符)
- bString: 0x0409 (英文)
1 | [04 03 09 04] |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :3 (字符串描述符)
- 描述符索引: 2 (第2个描述符)
- wIndex字段为字符串描述符指定语言ID 0x0409 (英文)
- wValue
1 | [23:29:12 706]Setup: bmRequestType 0x80, bRequest 0x06, wValue 0x0302, wIndex 0x0409, wLength 0x00ff |
- 回复字符串描述符38字节
- bLength: 0x26
- bDescriptorType: 0x03 (字符串描述符)
- bString: “CherryUSB CDC DEMO”
1 | [26 03 43 00 68 00 65 00 72 00 72 00 79 00 55 00 53 00 42 00 20 00 43 00 44 00 43 00 20 00 44 00 45 00 4d 00 4f 00] |
- 请求类型:Standard Device Request 方向:设备到主机 请求:GET_DESCRIPTOR
- wValue
- 描述符类型 :06 (设备限定描述符)
- 描述符索引: 0 (第0个描述符)
- wValue
- 没有该描述符,回复STALL
1 | [23:29:12 719]Setup: bmRequestType 0x80, bRequest 0x06, wValue 0x0600, wIndex 0x0000, wLength 0x000a |
- 请求类型:Standard Device Request 方向:设备到主机 请求:SET_CONFIGURATION
- wValue: 0x0001 (配置为1)
- 配置对应端点,回复配置状态
- 配置0x03端点方向为OUT,类型为中断,最大包大小为8
- 配置0x02端点方向为OUT,类型为批量,最大包大小为64
- 配置0x01端点方向为IN,类型为批量,最大包大小为64
1 | [23:29:12 773]Setup: bmRequestType 0x00, bRequest 0x09, wValue 0x0001, wIndex 0x0000, wLength 0x0000 |
- 请求类型: Class Interface Request 方向:设备到主机 请求:GET_LINE_CODING
- wValue: 0x0000
- wIndex: 0x0000
- wLength: 0x0007
- bRequest 0x21
- 回复波特率:2000000,停止位:1,校验位:0,数据位:8
1 | [a1 21 00 00 00 00 07 00] |
- 请求类型: Class Interface Request 方向:设备到主机 请求:SET_CONTROL_LINE_STATE
- wValue: 0x0000
- wIndex: 0x0000
- wLength: 0x0007
- bRequest 0x20
- 设置DTR和RTS
1 | [21 22 00 00 00 00 00 00] |
- 请求类型: Class Interface Request 方向:主机到设备 请求:SET_LINE_CODING
- wValue: 0x0000
- wIndex: 0x0000
- wLength: 0x0007
- bRequest 0x20
- 执行接收,配置波特率:2000000,停止位:1,校验位:0,数据位:8
1 | [23:29:12 897]Setup: bmRequestType 0x21, bRequest 0x20, wValue 0x0000, wIndex 0x0000, wLength 0x0007 |
- 请求类型: Class Interface Request 方向:主机到设备 请求:SET_CONTROL_LINE_STATE
- wValue: 0x0002
- wIndex: 0x0000
- wLength: 0x0000
- bRequest 0x22
- 设置DTR和RTS(串口助手开启关闭串口发送此命令,MCU可通过该命令实现同步开启输出或关闭)
- DTR:0
- RTS:1
1 | [23:29:18 841] [I/USB] Setup: bmRequestType 0x21, bRequest 0x22, wValue 0x0002, wIndex 0x0000, wLength 0x0000 |
Endpoint
1 | /** Standard Endpoint Descriptor */ |
- bLength: 描述符长度 7 字节
- bDescriptorType: 描述符类型 5(端点描述符)
- bEndpointAddress: 8位,低7位为端点号,最高位为方向,0表示OUT,1表示IN
- bmAttributes: 属性
位1 . .0:传输类型:00 =控制;01 =同步;10 =批量;11 =中断
如果不是等时端点,第5位…2为预留值,必须设置为0。如果是等时的,则定义如下:
位3 . .2:同步类型:00 =不同步;01 =异步;10 =自适应;11 =同步
Bits 5..4:使用类型:00 =数据端点;01 =反馈端点;10 =隐式反馈数据端点;11 =保留
所有其他的位被保留,必须重置为零。保留位必须被主机忽略。
wMaxPacketSize: 最大包大小
对于所有端点,第10位…0指定最大数据包大小(以字节为单位)。bInterval: 传输间隔
数据传输轮询端点的时间间隔。
对于全速/低速中断端点,该字段的取值范围是1 ~ 255。
Device Requests
- 所有USB设备在设备的默认控制管道上响应来自主机的请求。这些请求是使用控制传输发出的。请求和请求的参数在安装包中发送到设备。主机负责建立表9-2所示字段中传递的值。每个安装包有8个字节
偏移 | 字段 | 大小 | 值 | 说明 |
---|---|---|---|---|
0 | bmRequestType | 1 | Bitmap | D7:数据传输方向 0 =主机到设备1 =设备到主机 D6……5:类型 0 =标准 1 =类别 2 =供应商 3 =保留 D4……0:接收方 0 =设备 1 =接口 2 =终端 3 =其他 4…31 =保留 |
1 | bRequest | 1 | Value | 具体要求(参见表9-3) |
2 | wValue | 2 | Value | |
4 | wIndex | 2 | Index or Offset | |
6 | wLength | 2 | Count | 如果有数据阶段,要传输的字节数 |
- 标准设备请求
描述符
标准设备描述符
- 参考<<USB2.0.pdf>> 9.6.1节
1 | /** Standard Device Descriptor |
标准配置描述符
bConfigurationValue
字段指示设备固件中定义的配置编号。客户端驱动程序使用该数字值来选择活动配置。
1 | /* |
IAD描述符
1 | /* |
标准接口描述符
1 | /* |
标准端点描述符
1 |
字符串描述符
- 字符串描述符是可选的。如前所述,如果设备不支持字符串描述符,则必须将设备、配置和接口描述符中对字符串描述符的所有引用重置为零。
- 字符串描述符使用由统一码标准定义的统一码编码,所有语言的字符串索引为零返回一个字符串描述符,该描述符包含设备支持的双字节LANGID代码数组。USB设备可以省略所有的字符串描述符。省略所有字符串描述符的USB设备必须不返回LANGID代码数组。
1 |
cherryUSB
杂项
- USB_MEM_ALIGNX: 用于指定内存对齐方式,用
CONFIG_USB_ALIGN_SIZE
配置,默认4字节对齐
1 |
- USB_NOCACHE_RAM_SECTION: 用于指定数据存储在无缓存RAM中,用于DMA传输
1 | /* attribute data into no cache ram */ |
CONFIG_USBDEV_ADVANCE_DESC
:usbd_desc_register
当前 API 仅支持一种速度,如果需要更高级的速度切换功能,请开启 CONFIG_USBDEV_ADVANCE_DESC,并且包含了下面所有描述符注册功能
dwc2
usb_dc_init
- 初始化 usb device controller 寄存器,设置 usb 引脚、时钟、中断等等
- 软断开
- 供电状态可借助软断开功能通过软件退出。将设备控制寄存器中的软断开位(OTG_DCTL中的 SDIS 位)置 1 即可移除 DP 上拉电阻,此时尽管没有从主机端口实际拔出 USB 电缆, 但主机端仍会发生设备断开检测中断.
- 关闭全局中断
- B会话有效覆盖使能
- 由 OTG_HS 模块控制的 DP/DM 集成上拉电阻和下拉电阻,具体使能哪种电阻取决于设备的当前角色。作为外设使用时,只要检测到 VBUS 为有效电平(B 会话有效),立即使能 DP 上拉电阻,以告知全速设备的连接。
- VBUS 输入检测到 B 会话有效电压,就会使 USB 设备进入供电状态(请参见 USB2.0 第 9.1 部分)。
- 如果检测到 VBUS 降至 B 会话有效电压以下(例如,因电源干扰或主机端口关闭引发),OTG_HS 将自动断 开连接并生成检测到会话结束中断(OTG_GOTGINT 中的 SEDET 位),指示 OTG_HS 已 退出供电状态。
- 全速串行收发器选择
0:USB 2.0 外部 ULPI 高速 PHY。
1:USB 1.1 全速串行收发器。 - 复位
- 强制设备模式
- 向该位写入 1 时,可将模块强制为设备模式,而无需考虑 OTG_ID 输入引脚。
- 0:正常模式1:强制设备模式
- 将强制位置 1 后,应用程序必须等待至少 25 ms 后更改方可生效。
- 重启 PHY 时钟
- 设备速度配置
- 指示应用程序要求模块进行枚举所采用的速度,或应用程序支持的最大速度。但是,实际总线速度只有在完成 chirp 序列后才能确定,同时此速度基于与模块连接的 USB 主机的速度。
- 00:高速01:使用 HS 进行全速通信;10:保留;11:使用内置 FS PHY 进行全速通信
- 清除中断标志
- 配置fifo
- 0X10刷新所有发送FIFO
- 通常建议在重新配置 FIFO 时进行刷新。还建议在设备端点禁止期间进行 FIFO 刷新。应用程序必须等待模块将此位清零,才能执行任意操作。该位需要八个时钟来清零(使用较慢的phy_clk 或 hclk 时钟)。
- 应用程序可使用此位刷新整个 Rx FIFO,但必须首先确保模块当前未在处理事务。只有在确认模块当前未对 Rx FIFO 执行读写操作后,应用程序方可对此位执行写操作。应用程序必须等到此位清零后,方可执行其它操作。通常需要等待 8 个时钟周期(以 PHY 或
AHB 时钟中最慢的为准)。
- 使能中断,关闭软断开
1 | int usb_dc_init(uint8_t busid) |
usb_dc_low_level_init
- stm32: cubemx 生成的代码copy msp_init既可
- 初始化USB时钟
- 初始化USB引脚
- 初始化USB中断
USBD_IRQHandler
- 要将 rc_w1 类型中断状态位清零,应用程序必须向该位写入 1。
USB复位 USB_OTG_GINTSTS_USBRST
- 清除USB复位中断标志
- 刷新所有FIFO
- 使能0端点,发送NACK;禁用非0端点,发送NACK
- 解除0端点中断屏蔽
1 | USB_OTG_GLB->GINTSTS |= USB_OTG_GINTSTS_USBRST; //清除USB复位中断标志 |
USB OUT端点中断 USB_OTG_GINTSTS_OEPINT
- 接收到OUT0端点中断
- xfer_len = 0,没接收到,再次接收;
dwc2_ep0_start_read_setup
usbd_event_ep_out_complete_handler
- 接收其他OUT端点中断
- 执行
usbd_event_ep_out_complete_handler
处理接收完成事件
- 接收到setup完成中断
USB_OTG_DOEPINT_STUP
usbd_event_ep0_setup_complete_handler
- STUP:SETUP 阶段完成 (SETUP phase done):仅适用于控制 OUT 端点。指示控制端点的 SETUP 阶段已完成,当前控制传输中不再接收到连续的 SETUP 数据包。在此中断上,应用程序可以对接收到的 SETUP 数据包进行解码。
- setup流程
- 初始化完成触发RESET中断执行
dwc2_ep0_start_read_setup
- DMA接收或
USB_OTG_GINTSTS_RXFLVL
中断触发接收完成,执行dwc2_ep_read((uint8_t *)&g_dwc2_udc.setup, read_count);
完成SETUP包接收 - 主机发送SETUP包,触发SETUP中断,执行
usbd_event_ep0_setup_complete_handler
- 接收完成触发XFRC中断,执行
usbd_event_ep_out_complete_handler
1 | ep_idx = 0; |
USB RxFIFO nonempty 中断 USB_OTG_GINTSTS_RXFLVL
- 屏蔽接收中断
- 是数据包,传入
xfer_buf
- 是SETUP包,传入
g_dwc2_udc.setup
- 解除接收中断屏蔽
1 | USB_MASK_INTERRUPT(USB_OTG_GLB, USB_OTG_GINTSTS_RXFLVL); |
USB IN端点中断 USB_OTG_GINTSTS_IEPINT
- USB_OTG_DIEPINT_TXFE 触发,执行
dwc2_tx_fifo_empty_procecss
- USB_OTG_DIEPINT_XFRC 触发,执行
usbd_event_ep_in_complete_handler
1 | while (ep_intr != 0U) { |
usbd_ep_open
- USBAEP:USB 活动端点 (USB active endpoint)
指示此端点在当前配置和接口中是否激活。检测到 USB 复位后,模块会为所有端点(端点0 除外)将此位清零。接收到 SetConfiguration 和 SetInterface 命令后,应用程序必须相应地对端点寄存器进行编程并将此位置 1。 - SNPM:监听模式 (Snoop mode)
此位用于将端点配置为监听模式。在监听模式下,模块不会在将 OUT 数据包传输到应用存储区前检查其是否正确。
1 | int usbd_ep_open(uint8_t busid, const struct usb_endpoint_descriptor *ep) |
dwc2_ep0_start_read_setup
1 | static void dwc2_ep0_start_read_setup(uint8_t *psetup) |
usbd_event_ep0_setup_complete_handler
- 复制SETUP包
- 判断缓存长度是否满足接收要求
- 设置数据缓存
- 通过安装的处理程序处理请求,具有数据阶段的请求,继续读取数据
- 执行请求处理程序
- 检查发送长度并设置
- 复制数据到ep0_data_buf
- 发送数据或状态到主机
- 设置 zlp_flag 的目的是为了在这种情况下发送一个零长度包,让主机需要知道传输已经完成。
例外:情况
SET_CONFIGURATION
请求执行后,返回长度为0,则发送0长度包,将req_data
设置为ep0_data_buf
发送
例如
收到80 06 00 01 00 00 12 00
,执行usbd_get_descriptor
函数,发送设备描述符
1 | void usbd_event_ep0_setup_complete_handler(uint8_t busid, uint8_t *psetup) |
usbd_ep_start_read
- 确保数据地址是4字节对齐
- 数据长度为0
- 数据长度为0,设置数据包数量为1,传输大小为端点最大包大小,使能端点
- 端点0
- 端点0,数据长度大于端点最大包大小,设置数据长度为端点最大包大小,数据长度小于端点最大包大小,设置数据长度为数据长度
- 其他端点
- 允许多次传输,计算数据包数量,设置数据包数量,传输大小为数据长度
- 中断中执行数据接收,或者DMA自动接收
1 | int usbd_ep_start_read(uint8_t busid, const uint8_t ep, uint8_t *data, uint32_t data_len) |
usbd_ep_set_stall
- 停止端点传输,设置端点STALL
usbd_ep_start_write
同read类似
数据为
USB_ENDPOINT_TYPE_ISOCHRONOUS
,执行额外操作数据长度为0,设置数据包数量为1,传输大小为端点最大包大小,使能端点
dwc2_tx_fifo_empty_procecss
- 发送数据到端点FIFO
usbd_event_ep0_in_complete_handler
usbd_event_ep0_setup_complete_handler
会对ep0_data_buf
进行处理,处理完成后,执行usbd_ep_start_write
,进而在中断中执行usbd_event_ep0_in_complete_handler
1 | void usbd_event_ep0_in_complete_handler(uint8_t busid, uint8_t ep, uint32_t nbytes) |
usbd_event_ep_out_complete_handler
- 同
usbd_event_ep0_in_complete_handler
流程
usbd_set_address
1 | int usbd_set_address(uint8_t busid, const uint8_t addr) |
device
core
usbd_desc_register
- 注册描述符
1 | void usbd_desc_register(uint8_t busid, const uint8_t *desc) |
usbd_add_interface
- 添加一个接口驱动。 添加顺序必须按照描述符顺序。
- intf 接口驱动句柄,通常从不同 class 的 xxx_init_intf 函数获取
1 | void usbd_add_interface(uint8_t busid, struct usbd_interface *intf) |
usbd_add_endpoint
- 注册端点地址和回调函数
1 | void usbd_add_endpoint(uint8_t busid, struct usbd_endpoint *ep) |
usbd_initialize
- 用来初始化 usb device 寄存器配置、usb 时钟、中断等,需要注意,此函数必须在所有列出的 API 最后。 如果使用 os,必须放在线程中执行。
- 填入 busid 和 USB IP 的 reg base, busid 从 0 开始,不能超过 CONFIG_USBDEV_MAX_BUS
1 | int usbd_initialize(uint8_t busid, uint32_t reg_base, void (*event_handler)(uint8_t busid, uint8_t event)) |
usbd_class_event_notify_handler
- 执行接口通知函数:
intf->notify_handler(busid, event, arg);
usbd_event_reset_handler
- 设置USB设备地址为0
- 清空配置
- 配置IN端点
- 配置OUT端点
- 执行接口通知函数
- 执行事件处理函数
1 | void usbd_event_reset_handler(uint8_t busid) |
usbd_setup_request_handler
- 处理setup请求
USB_REQUEST_STANDARD 0x00 标准请求
usbd_standard_request_handler
USB_REQUEST_RECIPIENT_DEVICE 0x00 设备
usbd_std_device_req_handler
USB_REQUEST_GET_STATUS 0x00 获取设备状态
1 | /* bit 0: self-powered */ |
USB_REQUEST_CLEAR_FEATURE 0x01 清除设备特性
USB_REQUEST_SET_FEATURE 0x03 设置设备特性
USB_REQUEST_SET_ADDRESS 0x05 设置设备地址
- 调用IP层设置设备地址
1 | usbd_set_address(busid, value); |
USB_REQUEST_GET_DESCRIPTOR 0x06 获取描述符
执行
ret = usbd_get_descriptor(busid, value, data, len);
例如接收到:
80 06 00 01 00 00 12 00
,执行usbd_get_descriptor
函数,返回设备描述符以及长度调用
usbd_desc_register
用于注册描述符
例如usbd_desc_register(busid, cdc_descriptor);
1 | static bool usbd_get_descriptor(uint8_t busid, uint16_t type_index, uint8_t **data, uint32_t *len) |
USB_REQUEST_SET_DESCRIPTOR 0x07 设置描述符
USB_REQUEST_GET_CONFIGURATION 0x08 获取配置
USB_REQUEST_SET_CONFIGURATION 0x09 设置配置
wValue
字段的下一个字节指定所需的配置。此配置值必须为零或与配置描述符中的配置值匹配。如果配置值为零,则设备处于地址状态。保留wValue字段的上一个字节。usbd_set_configuration
执行流程- 遍历全局描述符,找到配置描述符,标记为
found
- 找到接口描述符,记录
cur_alt_setting
- 找打端点描述符,匹配配置和接口,执行
usbd_set_endpoint
- 遍历全局描述符,找到配置描述符,标记为
1 | value &= 0xFF; |
1 | static bool usbd_set_configuration(uint8_t busid, uint8_t config_index, uint8_t alt_setting) |
USB_REQUEST_GET_INTERFACE 0x0A 获取接口
USB_REQUEST_SET_INTERFACE 0x0B 设置接口
- 返回false
USB_REQUEST_RECIPIENT_INTERFACE 0x01 接口
USB_REQUEST_RECIPIENT_ENDPOINT 0x02 端点
USB_REQUEST_CLASS 0x1 类
1 | if (usbd_class_request_handler(busid, setup, data, len) < 0) { |
USB_REQUEST_RECIPIENT_INTERFACE 0x01 接口
- 匹配对应的接口函数进行执行
1 | for (uint8_t i = 0; i < g_usbd_core[busid].intf_offset; i++) { |
USB_REQUEST_RECIPIENT_ENDPOINT 0x02 端点
USB_REQUEST_VENDOR 0x2 厂商
usbd_set_endpoint
1 | static bool usbd_set_endpoint(uint8_t busid, const struct usb_endpoint_descriptor *ep) |
CDC (Communication Device Class)
cdc function
usbd_cdc_acm_init_intf
- 用来初始化 USB CDC ACM 类接口,并实现该接口相关的函数
1 | struct usbd_interface *usbd_cdc_acm_init_intf(uint8_t busid, struct usbd_interface *intf) |
CDC-ACM (Abstract Control Model)
- CDC-ACM用于实现虚拟串口,无需安装驱动,系统会自动识别为串口设备;但是CDC-ACM的速度较慢,不适合高速传输,且不支持流控制;即与硬件串口无关
- CDC-ACM的虚拟串口,如果是充当Bridge的角色,即上位机发送的数据是需要CDC设备继续下传到第三方设备,此时波特率有意义, 上位机和你的第三方设备的串口波特率必须设置为相同才能正常数据传输
- USB CDC虚拟串口实质是USB通信,在电脑端映射成串口,波特率没有实际意义。
- 另外的实现虚拟串口的方式是VCP(Virtual COM Port),需要安装驱动,但是速度较快,支持流控制;
- 主机发送
GET LINE CODING Request
获取波特率,停止位,校验位等信息 - 设备返回波特率,停止位,校验位等信息
- 主机发送
SET CONTROL LINE STATE Request
设置DTR和RTS - 设备返回ACK
- 主机发送
SET LINE CODING Request
设置波特率,停止位,校验位等信息 - 设备返回ACK
- 主机发送
GET LINE CODING Request
获取波特率,停止位,校验位等信息 - 设备返回波特率,停止位,校验位等信息
- URB_BULK in -> 45 回应主机请求
- URB_INTERRUPT in -> 64 回应主机请求
- 主机发送
SET LINE CODING Request
设置波特率115200
初始化
cdc_acm_init
There are three classes that make up the definition for communications devices:
- Communications Device Class
- Communications Interface Class
- Data Interface Class.
1 | /*!< endpoint address */ |
全局描述符
- 设备描述符:USB2.0,基类为多功能设备类,IAD.(接口关联描述符)
- 配置描述符:接口数为2,配置值为1,属性为总线供电,最大功率为100mA
- CDC ACM 描述符:接口数为2,类为CDC,子类为ACM,协议为V.25ter,最大包大小为64
- 第一个字符串描述符为厂商描述符, 第二个字符串描述符为产品描述符, 第三个字符串描述符为序列号描述符
1 | /*!< global descriptor */ |
CDC_ACM 描述符
描述符举例
- IAD描述符
- bFunctionClass:
Communications Device Class
;参考<<CDC120-20101103-track.pdf>> 4.1 - bFunctionSubClass:
Abstract Control Model
0X02;参考<<CDC120-20101103-track.pdf>> 4.3 - bFunctionProtocol:
AT Commands: V.250 etc
0X01;参考<<CDC120-20101103-track.pdf>> 4.4 - iFunction: 0x00; 可选的函数名字符串描述符索引。
- 接口描述符
- bInterfaceNumber: CDC ACM接口编号 0x00
- bAlternateSetting: 0x00; 可选的备用设置号
- bNumEndpoints: 0x01; 端点数量
- bInterfaceClass: CDC类 0x02
- bInterfaceSubClass: ACM接口子类 0x02
- bInterfaceProtocol: AT Commands: V.250 etc协议 0x01
- iInterface: 接口字符串描述符索引
- CDC功能描述符
- bcdCDC: CDC规范版本号
- Call Management功能描述符
- bmCapabilities:设备仅通过通信类接口发送,设备本身不处理呼叫管理
- bDataInterface: (bFirstInterface + 1) 可选用于呼叫管理的数据类接口的接口号。
- 抽象控制管理功能描述符
- bmCapabilities: 0x02
- 通信组合功能 不支持
- 链路请求和状态通知 支持
- Send_Break 不支持
- 网络链接 不支持
- Union功能描述符
- bMasterInterface: 通信或数据类接口的接口号,指定为联合的控制接口;
- bSlaveInterface0: 联合中第一个从属接口的接口号
- 这里来说,
CALL_MANAGEMENT
为从属接口
- 端点描述符
- bEndpointAddress: 端点地址 0x81 (IN端点)
- bmAttributes: 0x03 (中断传输)
- wMaxPacketSize: 0x08 0x00 (8 bytes)
- bInterval: 0x0a (10ms)
- 接口描述符
- bInterfaceNumber: CDC ACM接口编号 0x01
- bAlternateSetting: 0x00; 可选的备用设置号
- bNumEndpoints: 0x02; 端点数量
- bInterfaceClass: 数据接口类代码 0X0A
- bInterfaceSubClass: 0x00 无 该字段未用于Data Class接口,其值应为00h
- bInterfaceProtocol: 0x00 无 不需要特定于类的协议
- iInterface: ACM接口字符串描述符索引
- 端点描述符
- bEndpointAddress: 端点地址 0x02 (OUT端点)
- bmAttributes: 0x02 (块传输)
- wMaxPacketSize: 0x40 0x00 (64 bytes)
- bInterval: 0x00 (不使用)
- 端点描述符
- bEndpointAddress: 端点地址 0x82 (IN端点)
- bmAttributes: 0x02 (块传输)
- wMaxPacketSize: 0x40 0x00 (64 bytes)
- bInterval: 0x00 (不使用)
- 字符串描述符
- 语言ID描述符
- 字符串描述符1
- 字符串描述符2
- 字符串描述符3
1 | /* |
usbd_event_handler
USB_REQUEST_SET_CONFIGURATION
执行后,执行g_usbd_core[busid].event_handler(busid, USBD_EVENT_CONFIGURED);
1 | static void usbd_event_handler(uint8_t busid, uint8_t event) |
cdc_acm_class_interface_request_handler
- 主机发送
GET LINE CODING Request
,即CLASS INTERFACE REQUEST,执行 - 进而执行此函数
CDC_REQUEST_GET_LINE_CODING 0x21 获取线编码
1 | __WEAK void usbd_cdc_acm_get_line_coding(uint8_t busid, uint8_t intf, struct cdc_line_coding *line_coding) |
CDC_REQUEST_SET_CONTROL_LINE_STATE 0x22 设置控制线状态
- dtr: 数据终端准备好
- rts: 请求发送
1 | dtr = (setup->wValue & 0x0001); |
- 例如,通过
usbd_cdc_acm_set_dtr
来实现串口打开才输出数据功能
1 | void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr) |
CDC_REQUEST_SET_LINE_CODING 0x20 设置线编码
- 同上
1 | memcpy(&line_coding, *data, setup->wLength); |
Endpoint描述符
Functional Descriptors
- 参考<<CDC120-20101103-track.pdf>> 5.2.3 Table 11: Functional Descriptor General Format
Header Functional Descriptor
- 类特定的描述符应该从表11中定义的头开始。bcdCDC字段标识通信设备规范的USB类定义(本规范)的发布,该接口及其描述符符合该规范。
Union Functional Descriptor
- 联合功能描述符描述了一组接口之间的关系,这些接口可以被认为是一个功能单元。它只能发生在类特定部分描述符。
- 组中的一个接口被指定为组的主接口或控制接口,并且某些特定于类的消息可以发送到该接口以对整个组起作用。类似地,整个组的通知可以从该接口发送,但适用于整个组的接口。该组中的接口可以包括通信、数据或任何其他有效的USB接口类(包括但不限于音频、HID和监视器)。
MSC (Mass Storage Class)
抓包
枚举过程
- 忽略设备描述符及字符串描述符请求和回应过程
- 主机发送
GET CONFIGURATION Request
获取配置描述符- 配置端点
- 执行
usbd_class_event_notify_handler(busid, USBD_EVENT_CONFIGURED, NULL);
,执行msc_storage_notify_handler
- 开始读取CBW,MSC Bulk-Only Command Block Wrapper (CBW),存储至
g_usbd_msc[busid].cbw
stage
状态 = MSC_READ_CBW;
1 | [15:58:27] [412][I/USB] Setup: bmRequestType 0x00, bRequest 0x09, wValue 0x0001, wIndex 0x0000, wLength 0x0000 |
- 请求类型: Class Interface Request, 方向主机到设备, 请求:Get Max LUN (GML)
- 回复
0
lun
- 回复
1 | [a1 fe 00 00 00 00 01 00] |
- 主机通过端点2发送CBW, SCSI: Inquiry LUN: 0x00
- Signature: 0x43425355 签名
- Tag: 0x16db9010
- Data Transfer Length: 36
- flag: 0x80
- target: 0x00
- lun: 0x00
- CDB Length: 6
- CDB: 0x12
1 | [55 53 42 43 10 90 db 16 24 00 00 00 80 00 06 12 00 00 24 00 00 00 00 00 00 00 00 00 00 00] |
- 解码SCSI命令,执行
SCSI_inquiry
,回复设备信息- 外设限定符: 0x00 , 设备类型连接到逻辑单元
- 设备类型: 0x00 , 直接访问设备(磁盘)
- RMB: 0x01 , 可移动介质
- Device-type modifier: 0x00 不支持
- ISO/IEC 646: 0x02 , ASCII
- Response data format: 0x01 , ACSI-2
- Additional Length: 0x1f , 31 bytes
- Vendor ID: “ “
- Product ID: “ “
- Product Revision: “0.01”
- 回复长度: 36
1 | [00 80 02 02 1f 00 00 00 20 20 20 20 20 20 20 20 0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 30 2e 30 31] |
- 设备通过端点1发送CSW状态(good),转入
MSC_READ_CBW
状态- Signature: 0x53425355
- Tag: 0x16db9010
- dDataResidue: 0, 预期与实际数据长度差异
- Status: 0x00 (Passed[good])
1 | [55 53 42 53 10 90 db 16 00 00 00 00 00] |
主机通过端点2发送CBW, SCSI Command: 0x23(READ FORMAT CAPACITIES) LUN:0x00
Signature: 0x43425355 签名
Tag: 0x0d89a010
Data Transfer Length: 252
flag: 0x80
- target: 0x00
- lun: 0x00
- CDB Length: 10
CDB: 0x23 READ FORMAT CAPACITIES
- LUN: 0x00
- Allocation Length: 0xfc = 252
设备回复容量信息(512字节块,1000块)= 512KB
- Capacity List Length: 8
- Number of blocks: 0x000003e8 = 1000
- Desc Type: 0x02 = 可格式化设备
- Block Length: 0x200 = 512
SW状态(GOOD),转入
MSC_READ_CBW
状态
1 | [55 53 42 43 10 a0 89 0d fc 00 00 00 80 00 0a 23 00 00 00 00 00 00 00 fc 00 00 00 00 00 00 00] |
- 主机通过端点2发送CBW, SCSI Command: 0x25(READ CAPACITY) LUN:0x00
Signature: 0x43425355 签名
Tag: 0x17db9010
Data Transfer Length: 8
flag: 0x80
- target: 0x00
- lun: 0x00
- CDB Length: 10
CDB: 0x25 READ CAPACITY
- LUN: 0x00
- RelAdr: 0x00
- Logical Block Address: 0x00000000
- PMI: 0x00
设备回复容量信息(512字节块,1000块)= 512KB
- Last Logical Block Address: 0x000003e7 = 999
- Block Length: 0x200 = 512
1 | [55 53 42 43 10 d0 0e 17 08 00 00 00 80 00 0a 25 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] |
- 主机通过端点2发送CBW, SCSI Command: 0x1A(MODE SENSE(6)) LUN:0x00
- Signature: 0x43425355 签名
- Tag: 0x16db9010
- Data Transfer Length: 192
- flag: 0x80
- target: 0x00
- lun: 0x00
- CDB Length: 6
- CDB: 0x1A MODE SENSE(6)
- LUN: 0x00
- DBD: 0x00
- ALLOCATE LENGTH: 192
- Control: 0x00
- 回复设备信息
- Mode Data Length: 0x00
- Medium Type: 0x00
- Device-Specific Parameter: 0x00
- Block Descriptor Length: 0x00
1 | [55 53 42 43 10 40 ff 16 c0 00 00 00 80 00 06 1a 00 1c 00 c0 00 00 00 00 00 00 00 00 00 00 00] |
- 主机通过端点2发送CBW, SCSI Command: 0X28(READ(10)) LUN:0x00
Signature: 0x43425355 签名
Tag: 0X1405b290
Data Transfer Length: 512
flag: 0x80
- target: 0x00
- lun: 0x00
- CDB Length: 10
CDB: 0x28 READ(10)
- LUN: 0x00
- rdprotect: 0x00
- dpo: 0x00
- fua: 0x00
- rarc: 0x00
- Logical Block Address: 0x00000000
- Group Number: 0x00
- Transfer Length: 1
- Control: 0x00
回复返回所需LBA地址数据
1 | [55 53 42 43 90 b2 05 14 00 02 00 00 80 00 0a 28 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00] |
- 读取lba:2的数据,读取0~15块数据
- 应该是获取MBR分区信息和FAT表信息
1 | 144 [2024/8/17 15:58:27] [746][D/USB] Decode CB:0x28 |
- 主机通过端点2发送CBW, SCSI Command: 0x00(TEST UNIT READY) LUN:0x00
- 回复(Test Unit Ready) (Good)
1 | [55 53 42 43 10 30 50 1d 00 00 00 00 00 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] |
- 发送CBW, SCSI Command: 0x1e(PREVENT-ALLOW MEDIUM REMOVAL) LUN:0x00
- Prevent Allow Flags: 0x00 不允许移除
- 回复(Prevent/Allow Medium Removal) (Good)
发送CBW, SCSI Command: 0x1e(PREVENT-ALLOW MEDIUM REMOVAL) LUN:0x00 - Prevent Allow Flags: 0x01 允许移除
- 回复(Prevent/Allow Medium Removal) (Good)
1 | [55 53 42 43 60 95 ca 16 00 00 00 00 00 00 06 1e 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00] |
数据交互过程
不断读取LBA分区数据(wireshark抓包导致)
- event:2 MSC_DATA_IN事件
- ?可能是未格式化缘故
1 | 1647 [2024/8/18 15:17:42] [766][I/USB] read lba:64 |
执行格式化操作
- 发送CBW, SCSI Command: 0x2a(WRITE(10)) LUN:0X00
- Signature: 0x43425355 签名
- Tag: 0X12B1C010
- Data Transfer Length: 512
- flag: 0x00
- target: 0x00
- lun: 0x00
- CDB Length: 10
- CDB: 0x2a WRITE(10)
- LUN: 0x00
- wrprotect: 0x00
- dpo: 0x00
- fua: 0x00
- rarc: 0x00
- Logical Block Address: 0x00000000
- Group Number: 0x00
- Transfer Length: 1
- Control: 0x00
- 对扇区写入数据
- 设备回复good
- 主机使用read(10)读取写入扇区数据,确保写入成功
1 | [55 53 42 43 10 c0 b1 12 00 02 00 00 00 00 0a 2a 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00] |
- 写入末尾扇区数据,回复good,写入全0
1 | [16:16:11] [903][I/USB] Decode CB:0x2a |
- 写入LBA2,3个扇区数据;写入LBA5,三个扇区数据
1 | [2024/8/18 16:16:11] [915][I/USB] Decode CB:0x2a |
- 写入LBA8,32个扇区数据;写入全零数据
1 | [16:16:11] [941][I/USB] Decode CB:0x2a |
数据写入过程(创建文本)
- 创建FAT12文件系统,写入MBR数据,对LBA0~1扇区写入数据
- LBA0扇区末尾: 55 aa
- 引导代码: 位于数据的前446字节,用于启动操作系统。
- 分区表: 包含四个分区条目,每个条目16字节,总共64字节。每个条目描述一个分区的起始和结束位置、类型等信息。
- 引导标志: 位于数据的最后两个字节(0x55AA),表示这是一个有效的MBR。
1 | [16:16:12] [033][I/USB] Decode CB:0x2a |
- 写入根目录数据,对LBA8~16扇区写入数据
1 | [16:16:12] [068][I/USB] Decode CB:0x2a |
- 写入数据内容,对LBA28~29扇区写入数据
1 | [16:16:12] [120][I/USB] Decode CB:0x2a |
写入文件数据,对LBA42扇区写入数据
- {7A10627C-EB63-4B65-B8F5-3C36782DF3DF}
- 写入GUID (全局唯一标识符)
1 | 0000 7b 00 37 00 41 00 31 00 30 00 36 00 32 00 37 00 {.7.A.1.0.6.2.7. |
- 在目录中添加文件索引,对LBA8~16写入数据
- 写入文件索引,
123.txt
文件索引
- 写入文件索引,
1 | 127 [2024/8/18 16:16:17] [106][I/USB] Decode CB:0x2a |
- 在文件扇区写入数据,对LBA43写入数据
1 | [16:16:21] [708][I/USB] Decode CB:0x2a |
U盘弹出操作
- 使用
SCSI_CMD_PREVENTMEDIAREMOVAL
命令进行交互完成,任务栏弹出使用的命令 - 使用
SCSI_CMD_STARTSTOPUNIT
,盘符执行弹出使用
msc function
init
1 | void msc_ram_init(uint8_t busid, uint32_t reg_base) |
usbd_msc_init_intf
1 | struct usbd_interface *usbd_msc_init_intf(uint8_t busid, struct usbd_interface *intf, const uint8_t out_ep, const uint8_t in_ep) |
usbd_msc_get_cap
- 需要自行实现,以下为示例
1 | void usbd_msc_get_cap(uint8_t busid, uint8_t lun, uint32_t *block_num, uint32_t *block_size) |
msc_storage_notify_handler
1 | void msc_storage_notify_handler(uint8_t busid, uint8_t event, void *arg) |
mass_storage_bulk_out
- out端点回调函数,根据
stage
状态执行不同操作
1 | void mass_storage_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) |
SCSI_CBWDecode
- 解码CBW,并根据命令执行不同操作
1 | static bool SCSI_CBWDecode(uint8_t busid, uint32_t nbytes) |
SCSI_CMD_INQUIRY 0x12
- copy默认的inquiry信息,并回复
1 | if (g_usbd_msc[busid].cbw.CB[4] < SCSIRESP_INQUIRY_SIZEOF) { |
usbd_msc_send_info
- 发送信息
1 | static void usbd_msc_send_info(uint8_t busid, uint8_t *buffer, uint8_t size) |
mass_storage_bulk_in
- in端点回调函数,根据
stage
状态执行不同操作
1 | void mass_storage_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) |
MSC_SEND_CSW
1 | usbd_msc_send_csw(busid, CSW_STATUS_CMD_PASSED); |
usbd_msc_send_csw
- 发送CSW,并设置状态为
MSC_WAIT_CSW
,等待主机发送批量传输
1 | static void usbd_msc_send_csw(uint8_t busid, uint8_t CSW_Status) |
SCSI_processRead
- 判断最小传输长度
- 通过
usbd_msc_sector_read
接口读取数据 - 更新开始扇区,剩余扇区,数据长度
- CBW需要获取扇区数为0,设置状态为
MSC_SEND_CSW
- 通过
usbd_ep_start_write
发送数据
1 | static bool SCSI_processRead(uint8_t busid) |
usbd_msc_sector_read
- 需要自行实现,以下为示例
1 | int usbd_msc_sector_read(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length) |
usbd_msc_sector_write
- 需要自行实现,以下为示例
1 | int usbd_msc_sector_write(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length) |
msc_ram_descriptor
bInterfaceClass : USB_DEVICE_CLASS_MASS_STORAGE 0x08
bInterfaceSubClass : MSC_SUBCLASS_SCSI 0x06
bInterfaceProtocol : MSC_PROTOCOL_BULK_ONLY 0x50
- USB大容量存储类仅大容量(BBB)传输
设置了两个端点,一个接收,一个发送
1 |
1 | const uint8_t msc_ram_descriptor[] = { |
Mass Storage Request Codes
Get Max LUN (GML) 0xFE
- 由USB大容量存储类仅限大容量(BBB)传输分配
Bulk-Only Transport Protocol
- 该规范处理仅批量传输,或者换句话说,仅通过批量端点(不通过中断或控制端点)传输命令、数据和状态。此规范仅使用默认管道清除Bulk端点上的STALL条件,并发出如下所定义的特定于类的请求。本规范不要求使用中断端点。
- 该规范定义了对共享公共设备特性的逻辑单元的支持。虽然这个特性提供了必要的支持,允许类似的大容量存储设备共享一个通用的USB接口描述符,但它并不打算用于实现接口桥接设备。
CBW (Command Block Wrapper)
- 表 5-1 命令块包装器 (Command Block Wrapper)
字节 | 字段名称 | 描述 |
---|---|---|
0-3 | dCBWSignature | 用于识别此数据包为 CBW 的签名。 |
4-7 | dCBWTag | 主机发送的标签,用于将此 CBW 与相应的 CSW 关联。 |
8-11 | dCBWDataTransferLength | 主机期望在此命令中传输的数据字节数。 |
12 | bmCBWFlags | 指示数据传输方向的标志。 |
13 | bCBWLUN | 与此命令关联的逻辑单元号。 |
14 | 保留 | 保留 (0) |
15-30 | 保留 | 保留 (0) |
- dCBWSignature: 此字段包含
43425355h
,用于识别传入的 CBW。 - dCBWTag: 用于将 CBW 与相应的 CSW 关联的唯一标识符。
- 位 7: 方向 - 如果 dCBWDataTransferLength 字段为零,设备应忽略此位,否则:
- 0 = 数据从主机传输到设备(Data-Out)
- 1 = 数据从设备传输到主机(Data-In)
- 位 6: 废弃。主机应将此位设置为零。
- 位 5-0: 保留 - 主机应将这些位设置为零。
- bmCBWFlags: 指示设备是否应忽略或遵守
bCBWCBLength
中的值。
- 位 7: 方向 - 如果 dCBWDataTransferLength 字段为零,设备应忽略此位,否则:
- dCBWDataTransferLength: 主机期望在 Bulk-In 或 Bulk-Out 端点(由方向位指示)上传输的数据字节数。如果此字段为零,则设备和主机在 CBW 和相应的 CSW 之间不传输数据,设备应忽略 bmCBWFlags 中的方向位的值。
- bCBWLUN: 对于支持多个 LUN 的设备,主机应在此字段中放置发送命令块的 LUN。否则,主机应将此字段设置为零。
- bCBWCBLength: 此值定义命令块的有效长度。唯一合法的值是 1 到 16(01h 到 10h)。所有其他值均为保留值。
- CBWCB: 设备要执行的命令块。设备应将此字段中的前
bCBWCBLength
字节解释为由bInterfaceSubClass
标识的命令集定义的命令块。如果设备支持的命令集使用少于 16(10h)字节的命令块,则应首先传输有效字节,从偏移量 15(Fh)开始。设备应忽略CBWCB
字段中偏移量(15 +bCBWCBLength
- 1)之后的内容。
SCSI Command
SCSI_CMD_INQUIRY 0x12
参考<<usbmassbulk_10.pdf>> 6.4.2 INQUIRY命令
INQUIRY命令(参见表58)请求将有关逻辑单元和SCSI目标设备的信息发送到应用程序客户端。
表 58 INQUIRY 命令
Byte | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|
0 | OPERATION CODE (12h) | |||||||
1 | Reserved | Formerly | EVPD | |||||
2 | PAGE CODE | |||||||
3 - 4 | ALLOCATION LENGTH | |||||||
5 | CONTROL |
EVPD(启用重要产品数据)位:设置为 1 时,指定设备服务器应返回由页面代码字段指定的重要产品数据。如果 EVPD 位设置为 0,设备服务器应返回标准 INQUIRY 数据(见 3.6.2)。如果在 EVPD 位设置为 0 时页面代码字段不为零,则命令应以 CHECK CONDITION 状态终止,感知键设置为 ILLEGAL REQUEST,附加感知代码设置为 INVALID FIELD IN CDB。
CMDDT(命令支持数据)位:此位已被 T10 委员会声明为废弃。然而,它仍然包含在内,因为某些产品可能会实现此功能。有关此位的描述,请参见 SPC-2。如果 EVPD 和 CMDDT 位都为 1,目标应返回 CHECK CONDITION 状态,感知键设置为 ILLEGAL REQUEST,附加感知代码为 Invalid Field in CDB。当 EVPD 位为 1 时,页面或操作码字段指定目标应返回的重要产品数据页面。
ALLOCATION LENGTH: 如果EVPD设置为0,则分配长度至少为5,这样将返回参数数据(参见3.6.2)中的ADDITIONAL length字段。如果EVPD设置为1,则分配长度应该至少为4,这样就会返回参数数据(参见5.4)中的PAGE length字段。
回复的为:
Standard INQUIRY data format
标准INQUIRY数据格式
1 | uint8_t inquiry[SCSIRESP_INQUIRY_SIZEOF] = { |
SCSI_CMD_READFORMATCAPACITIES 0x23
- 参见<<usbmass-ufi10.pdf>> 第4节
- 发送
[23 00 00 00 00 00 00 00 fc 00 00 00 00 00 00 00]
- CB: 0x23
- LUN: 0x00
- Allocation Length: 0xfc = 252
- 在收到这个命令块后,UFI设备将容量列表返回给Bulk In端点上的主机。
scsi_blk_size
和scsi_blk_nbr
通过usbd_msc_get_cap
获取
1 | static bool SCSI_readFormatCapacity(uint8_t busid, uint8_t **data, uint32_t *len) |
SCSI_CMD_READCAPACITY10 0x25
- READ capacity命令允许主机请求当前安装的介质的容量
1 | static bool SCSI_readCapacity10(uint8_t busid, uint8_t **data, uint32_t *len) |
SCSI_CMD_MODESENSE6 0x1A
- MODE SENSE(6)命令为设备服务器向应用程序客户机报告参数提供了一种方法。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
0 | OPERATION CODE (1Ah) | ||||||
1 | Reserved | DBD | Reserved | ||||
2 | PC PAGE CODE | ||||||
3 | SUBPAGE CODE | ||||||
4 | ALLOCATION LENGTH | ||||||
5 | CONTROL |
- DBD(禁用块描述符)位:如果 DBD 位设置为 1,则设备服务器应返回参数数据,其中不包含块描述符。如果 DBD 位设置为 0,则设备服务器应返回参数数据,其中包含块描述符。
- PC(页面控制)字段:此字段指定设备服务器应返回的参数数据页面。如果 PC 字段为零,则设备服务器应返回当前值。如果 PC 字段为 1,则设备服务器应返回默认值。如果 PC 字段为 2,则设备服务器应返回保存值。如果 PC 字段为 3,则设备服务器应返回更改值。
- PAGE CODE 字段:指定要返回的模式页
- SUBPAGE CODE 字段:指定要返回的模式页的子页
- ALLOCATION LENGTH 字段:此字段指定设备服务器应返回的参数数据的长度(以字节为单位)。如果 ALLOCATION LENGTH 字段为零,则设备服务器应返回参数数据的长度,以便填充主机分配的数据缓冲区。如果 ALLOCATION LENGTH 字段为非零值,则设备服务器应返回参数数据的长度,以便填充主机分配的数据缓冲区,但不得超过 ALLOCATION LENGTH 字段中指定的长度。
- CONTROL 字段:此字段包含用于错误检测和纠正的 CRC 或校验和。
1 | static bool SCSI_modeSense6(uint8_t busid, uint8_t **data, uint32_t *len) |
SCSI_CMD_READ10 0x28
READ(10)命令(参见表97)请求设备服务器读取指定的逻辑块并将它们传输到data-in缓冲区。读取的每个逻辑块包括用户数据,如果介质已启用保护信息进行格式化,还包括保护信息。传输的每个逻辑块包括用户数据,也可能包括基于RDPROTECT字段和介质格式的保护信息。应该返回最近写入地址逻辑块的数据值
获取传输过来需要读取的LBA和读取的块数,并判断是否超出范围
判断
dDataLength
是否和nsectors * scsi_blk_size
相等设置
stage
为MSC_DATA_IN
,并发送MSC_DATA_IN
消息
1 | static bool SCSI_read10(uint8_t busid, uint8_t **data, uint32_t *len) |
SCSI_CMD_TESTUNITREADY 0x00
- TEST UNIT READY命令(参见表202)提供了一种检查逻辑单元是否就绪的方法。这不是要求自测。如果逻辑单元能够接受适当的介质访问命令而不返回CHECK CONDITION状态,则该命令应返回GOOD状态。如果逻辑单元无法运行或处于需要应用程序客户端操作(例如,START unit命令)使逻辑单元准备就绪的状态,则该命令应以CHECK CONDITION状态终止,并将感测键设置为NOT ready。
1 | static bool SCSI_testUnitReady(uint8_t busid, uint8_t **data, uint32_t *len) |
SCSI_CMD_PREVENTMEDIAREMOVAL 0x1E
- 这个命令告诉UFI设备启用或禁用删除逻辑单元中的介质
字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
0 | 操作码 (4Eh) | |||||||
1 | 逻辑单元号 | |||||||
2 | 保留 | |||||||
3 | 保留 | |||||||
4 | 保留 | Prevent | ||||||
5 | 保留 | |||||||
6 | 保留 | |||||||
7 | 保留 | |||||||
8 | 保留 | |||||||
9 | 保留 | |||||||
10 | 保留 | |||||||
11 | 保留 |
- Prevent: 0x01 防止介质删除 0x00 允许介质删除
1 | static bool SCSI_preventAllowMediaRemoval(uint8_t busid, uint8_t **data, uint32_t *len) |
WRITE(10) 0x2a
- WRITE(10)命令请求UFI设备将主机传输的数据写入介质
字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
0 | 操作码 (2Ah) | |||||||
1 | 逻辑单元号 | DPO | FUA | 保留 | RelAdr | |||
2 | (MSB) | 逻辑块地址 | ||||||
3 | 逻辑块地址 | |||||||
4 | 逻辑块地址 | |||||||
5 | 逻辑块地址 | (LSB) | ||||||
6 | 保留 | |||||||
7 | 传输长度 (MSB) | |||||||
8 | 传输长度 (LSB) | |||||||
9 | 保留 | |||||||
10 | 保留 | |||||||
11 | 保留 |
逻辑块地址:该字段指定写操作开始的逻辑块。
传输长度:传输长度字段指定要传输的连续数据逻辑块的数量。传输长度为0表示不传输逻辑块。此情况不应视为错误,也不应写入任何数据。任何其他值表示需要传输的逻辑块的数量
主机将要写入的数据发送到Bulk Output端点上的UFI设备。传输的字节数应该是传输长度乘以逻辑块大小。如果WRITE命令成功完成,则UFI设备将感测数据设置为NO sense。否则,设备应将感测数据设置为第5节所列的适当值。如果WRITE命令因为USB位填充错误或CRC错误而被终止,UFI设备应该将感测数据设置为USB to HOST SYSTEM INTERFACE FAILURE。注意:即使发生了写错误,介质也可能被改变了。对于跨越磁盘的多个物理磁道的命令块尤其如此。
1 | /* |
Command Status Wrapper (CSW)
- 表 5.3 - 命令块状态值
值 | 描述 |
---|---|
00h | 命令通过(“good status”) |
01h | 命令失败 |
02h | 阶段错误 |
03和04号 | 预留(Obsolete) |
05到FF号 | 预留 |
1 | /** MSC Bulk-Only Command Status Wrapper (CSW) */ |
HID (Human Interface Device)
抓包
描述符
1 | static const uint8_t hid_descriptor[] = { |