USB 模拟 U 盘

文章目录

    • [1 USB 大容量存储设备](#1 USB 大容量存储设备)
    • [2 设备描述符](#2 设备描述符)
    • [3 字符串描述符](#3 字符串描述符)
    • [4 配置描述符集合](#4 配置描述符集合)
      • [4.1 配置描述符](#4.1 配置描述符)
      • [4.2 接口描述符](#4.2 接口描述符)
      • [4.3 端点描述符](#4.3 端点描述符)
    • [6 类特殊请求](#6 类特殊请求)
      • [6.1 Get Max LUN 请求](#6.1 Get Max LUN 请求)
      • [6.2 Bulk-Only Mass Storage Reset 请求](#6.2 Bulk-Only Mass Storage Reset 请求)
    • [7 Bulk-Only 传输协议的数据流模型](#7 Bulk-Only 传输协议的数据流模型)
      • [7.1 CBW 的结构](#7.1 CBW 的结构)
      • [7.2 CSW 的结构](#7.2 CSW 的结构)
      • [7.3 对批量数据的处理](#7.3 对批量数据的处理)
    • [8 SCSI 命令集和 UFI 命令集](#8 SCSI 命令集和 UFI 命令集)
      • [8.1 查询命令 INQUIRY](#8.1 查询命令 INQUIRY)
      • [8.2 读格式化容量命令 READ FORMAT CAPACITIES](#8.2 读格式化容量命令 READ FORMAT CAPACITIES)
      • [8.3 读容量命令 READ CAPACITY](#8.3 读容量命令 READ CAPACITY)
      • [8.4 READ(10) 命令](#8.4 READ(10) 命令)
      • [8.5 WRITE(10) 命令](#8.5 WRITE(10) 命令)
      • [8.6 REQUEST SENSE 命令](#8.6 REQUEST SENSE 命令)
      • [8.7 TEST UNIT READY 命令](#8.7 TEST UNIT READY 命令)
    • [9 FAT 文件系统](#9 FAT 文件系统)
      • [9.1 关于 DBR](#9.1 关于 DBR)
      • [9.2 关于 FAT 表](#9.2 关于 FAT 表)
      • [9.3 关于目录项](#9.3 关于目录项)
    • 总结

《圈圈教你学USB》 第 8 章学习笔记。这篇文章我们会结合 daplink 源码来看。

1 USB 大容量存储设备

  • 1)USB 协议规定的大容量存储设备(Mass Storage Device,MSD)包括:U 盘、USB 移动硬盘、USB 移动光驱。

  • 2)MSD 设备的接口描述符中,有以下取值:

    • (1)接口类(bInterfaceClass):0x08 表示 MSD 设备
    • (2)接口子类(bInterfaceSubClass):0x06 表示 SCSI(Small Computer System Interface,小型计算机系统接口) 命令集
    • (3)接口协议(bInterfaceProtocol):0x00/0x01 需要使用中断传输;0x05 仅使用批量传输

2 设备描述符

1)设备描述符的结构如下表:

2)设备描述符在 daplink 中的实现(位于 source/usb/usb_lib.c 文件):

c 复制代码
/* USB Device Standard Descriptor */
__WEAK \
const U8 USBD_DeviceDescriptor[] = {
    USB_DEVICE_DESC_SIZE,                 /* bLength */
    USB_DEVICE_DESCRIPTOR_TYPE,           /* bDescriptorType */
#if (USBD_BOS_ENABLE)
    WBVAL(0x0210), /* 2.10 */             /* bcdUSB */
#elif ((USBD_HS_ENABLE) || (USBD_MULTI_IF))
    WBVAL(0x0200), /* 2.00 */             /* bcdUSB */
#else
    WBVAL(0x0110), /* 1.10 */             /* bcdUSB */
#endif
#if (USBD_MULTI_IF)
    USB_DEVICE_CLASS_MISCELLANEOUS,       /* bDeviceClass */
    0x02,                                 /* bDeviceSubClass */
    0x01,                                 /* bDeviceProtocol */
#elif (USBD_CDC_ACM_ENABLE)
    USB_DEVICE_CLASS_COMMUNICATIONS,      /* bDeviceClass CDC*/
    0x00,                                 /* bDeviceSubClass */
    0x00,                                 /* bDeviceProtocol */
#else
    0x00,                                 /* bDeviceClass */
    0x00,                                 /* bDeviceSubClass */
    0x00,                                 /* bDeviceProtocol */
#endif
    USBD_MAX_PACKET0,                     /* bMaxPacketSize0 */
    WBVAL(USBD_DEVDESC_IDVENDOR),         /* idVendor */
    WBVAL(USBD_DEVDESC_IDPRODUCT),        /* idProduct */
    WBVAL(USBD_DEVDESC_BCDDEVICE),        /* bcdDevice */
    0x01,                                 /* iManufacturer */
    0x02,                                 /* iProduct */
    0x03 * USBD_STRDESC_SER_ENABLE,       /* iSerialNumber */
    0x01                                  /* bNumConfigurations: one possible configuration*/
};

3 字符串描述符

在 daplink 中的实现由于篇幅原因不再展示,感兴趣的可以在 source/usb/usb_lib.c 文件中找 USBD_StringDescriptor 即可。

4 配置描述符集合

要想找到 daplink 中描述符的实现位置,可以首先找到定义在 source/usb/usb_def.h 文件中的 USB Descriptor Types(USB 描述符类型),然后根据这些类型来找到各个描述符。

4.1 配置描述符

1)配置描述符的结构:

2)配置描述符实现(见 source/usb/usb_lib.c 文件中的 start_desc_fill() 函数):

c 复制代码
const U8 start_desc[] = {
    /* Configuration 1 */
    USB_CONFIGUARTION_DESC_SIZE,                // bLength
    USB_CONFIGURATION_DESCRIPTOR_TYPE,          // bDescriptorType
    WBVAL(USBD_WTOTALLENGTH_MAX),               // wTotalLength
    USBD_IF_NUM_MAX,                            // bNumInterfaces
    0x01,                                       // bConfigurationValue: 0x01 is used to select this configuration
    0x00,                                       // iConfiguration: no string to describe this configuration
    USBD_CFGDESC_BMATTRIBUTES |                 // bmAttributes
    (USBD_POWER << 6),
    USBD_CFGDESC_BMAXPOWER                      // bMaxPower, device power consumption
};

4.2 接口描述符

1)接口描述符的结构:

2)daplink 中,在 source/usb/usb_lib.c 文件定义了许多种接口描述符:HID(人机交互设备)、WEBUSB(网页端USB)、MSC(大容量设备类)、BULK(批量设备类)、ADC(音频设备类)、CDC_ACM(通信设备类)。

4.3 端点描述符

1)端点描述符的结构:

2)daplink 中,每个接口描述符后都会跟着端点描述符,比如我们的 MSC 接口描述符后面的端点描述符如下:

c 复制代码
#define MSC_EP                          /* MSC Endpoints for Low-speed/Full-speed */                        \
/* Endpoint, EP Bulk IN */                                                                                  \
  USB_ENDPOINT_DESC_SIZE,               /* bLength */                                                       \
  USB_ENDPOINT_DESCRIPTOR_TYPE,         /* bDescriptorType */                                               \
  USB_ENDPOINT_IN(USBD_MSC_EP_BULKIN),  /* bEndpointAddress */                                              \
  USB_ENDPOINT_TYPE_BULK,               /* bmAttributes */                                                  \
  WBVAL(USBD_MSC_WMAXPACKETSIZE),       /* wMaxPacketSize */                                                \
  0x00,                                 /* bInterval: ignore for Bulk transfer */                           \
                                                                                                            \
/* Endpoint, EP Bulk OUT */                                                                                 \
  USB_ENDPOINT_DESC_SIZE,               /* bLength */                                                       \
  USB_ENDPOINT_DESCRIPTOR_TYPE,         /* bDescriptorType */                                               \
  USB_ENDPOINT_OUT(USBD_MSC_EP_BULKOUT),/* bEndpointAddress */                                              \
  USB_ENDPOINT_TYPE_BULK,               /* bmAttributes */                                                  \
  WBVAL(USBD_MSC_WMAXPACKETSIZE),       /* wMaxPacketSize */                                                \
  0x00,                                 /* bInterval: ignore for Bulk transfer */

6 类特殊请求

第 1 章我们知道:

(1)USB 以包为基本单位来传输,多个包组成事务来保证传输完整性,同时包又可以分成不同的域。

(2)包的结构为:同步域 + 包标识符 (PID,Packet Identifier) + [域] + 包结束符 EOP (End Of Packet)。

(3)根据包 PID 的不同可以将包分为:令牌包、数据包、握手包、特殊包。
第 3 章我们知道:

设备请求(结构见下图):可以通过 bmRequestType 的 bit[6:5] 来设置请求类型。如为 0 时为标准请求;为 1 时为类请求。

这一节将介绍 2 个类特殊请求(即 bmRequestType 的 bit[6:5] = 1)。

在 MSD 设备的 Bulk Only Transprot 协议中,规定了 2 个类特殊请求:Bulk-Only Mass Storage Reset(复位到命令状态) 和 Get Max LUN(获取最大逻辑单元)。
在 daplink 的 source/usb/usbd_core.c 文件的 USBD_EndPoint0() 函数中,可以看到 标准请求、类请求、厂商请求的处理逻辑,其中就包含下方的两个类特殊请求的处理,感兴趣可以看看。

6.1 Get Max LUN 请求

Get Max LUN 请求用来获取最大逻辑单元。

示例:A1 FE 00 00 00 00 01 00

txt 复制代码
A1      1010_0001B,请求类型为"类请求"
FE      请求代码为 GET MAX LUN
00 00 
00 00   请求接口号
01 00   数据长度为 1 字节

6.2 Bulk-Only Mass Storage Reset 请求

Bulk-Only Mass Storage Reset 请求是通知设备接下来的批量端点输出数据为 CBW 包(Command Block Wrapper,命令块封装包)。

示例:TODO

7 Bulk-Only 传输协议的数据流模型

Bulk-Only 传输协议中,分为 3 个阶段:命令阶段、数据阶段、状态阶段。

(1)命令阶段:由主机通过批量端点发送一个 CBW,其中定义命令、传输数据方向、传输数据数量

(2)数据阶段:传输方向由命令阶段决定

(3)状态阶段:总是由设备返回该命令完成的状态
在 daplink 的 source/usb/msc/usbd_msc.c 文件的 USBD_MSC_BulkOut() 函数中,可以看到获取 CBW 与响应 CSW 的逻辑。

7.1 CBW 的结构

CBW(Command Block Wrapper,命令块封装包)的结构如下:

属性 描述
dCBWSignature 魔数 0x43425355(小端),意为 USBC(USB Command)的 ASCII 码
dCBWTag CBW 标签,主机分配,设备响应时返回
dCBWDataTransferLength 数据阶段传输的数据长度,单位:字节,小端
bmCBWFlags CBW 标志,bit7 表示数据传输方向(0,输出;1,输入),bit[6:0] 为 0
bCBWLUN 目标逻辑单元的编号。bit[3:0] 逻辑单元编号
bCBWCBLengt CBWCB 长度。bit[4:0],有效范围 1~16
CBWCB 需要执行的命令。见第 8 节。

7.2 CSW 的结构

CSW(Command Status Wrapper,命令状态封装包)结构如下:

属性 描述
dCSWSignature 魔数 0x53425355(小端),意为 USBS(USB Satus)的 ASCII 码
dCSWTag CSW 包的标签,值为 CBW 包的 dCBWTag
dCSWDataResidue 命令完成时剩余字节数。表示实际完成传输的字节数与 dCBWDataTransferLength 的差值
bCSWStatus 命令执行状态。0x00 成功;0x01 失败;0x02 阶段错误;其它保留

7.3 对批量数据的处理

8 SCSI 命令集和 UFI 命令集

SCSI(Small Computer System Interface,小型计算机系统接口)对命令及其响应规定了完整的协议。

在 U 盘中常用的 SCSI 命令有:INQUIRY、READ CAPACITY、READ(10)、WRITE(10) 等
UFI(USB Floppy Interface,USB 软盘接口)结合 SCSI-2 和 SFF-8070i 命令集抽取的命令。

UFI 命令出现在 CBW 包的 CBWCB 字段中。最多 16 个字节,不足补0,多余忽略。命令第 1 字节为操作代码。

见 《usbmass-ufi10.pdf》第 4 节 UFI Command Descriptions: https://www.usb.org/document-library/mass-storage-ufi-command-specification-10

8.1 查询命令 INQUIRY

见 daplink 中 source/usb/msc/usbd_msc.c 文件的 USBD_MSC_Inquiry() 函数。

8.2 读格式化容量命令 READ FORMAT CAPACITIES

8.3 读容量命令 READ CAPACITY

8.4 READ(10) 命令

8.5 WRITE(10) 命令

8.6 REQUEST SENSE 命令

8.7 TEST UNIT READY 命令

9 FAT 文件系统

FAT(File Allocation Table,文件分配表)是为了方便文件存储、检索、添加、删除等操作,而提出的一种链表式的文件组织结构。
磁盘物理上以 "扇区" 为单位来组织存储,而 FAT 文件系统则以 "簇" 为单位组织。两者的映射关系为:一般一个簇由几个扇区组成。

FAT 逻辑上是一张表格,表格中的每项保存文件的文件名、文件长度、创建日期、起始簇号等信息。
磁盘格式化成 FAT16 格式后的内容如下:

  • 1)MBR(Master Boot Record,主引导记录):记录 MBR 引导代码、磁盘分区等信息。FAT16 每分区最大只有 2GB(65536x32KB) 存储空间,要映射更多的空间只能添加逻辑分区。

  • 2)EBR(Extended Boot Record,扩展引导记录):MBR 中的磁盘分区表不够用时,使用 EBR

  • 3)DBR(DOS Boot Record,磁盘操作系统引导记录):每个逻辑分区的引导记录。记录分区信息及引导代码。

    MBR、EBR、DBR 通常只占用一个扇区(512KB)

  • 4)FAT1/2:文件分配表,表格项记录文件相关信息。FAT2 为 FAT1 的副本。

9.1 关于 DBR

1)DBR 占据逻辑分区的 0 扇区,大小通常 512 字节。

示例:

来源:DAPLINK 源码的 source/daplink/drag-n-drop/virtual_fs.c 文件

c 复制代码
static const mbr_t mbr_tmpl = {
    /*uint8_t[11]*/.boot_sector = {
        0xEB, 0x3C, 0x90,
        'M', 'S', 'D', '0', 'S', '4', '.', '1' // OEM Name in text (8 chars max)
    },
    /*uint16_t*/.bytes_per_sector           = 0x0200,       // 512 bytes per sector
    /*uint8_t */.sectors_per_cluster        = 0x08,         // 4k cluser
    /*uint16_t*/.reserved_logical_sectors   = 0x0001,       // mbr is 1 sector
    /*uint8_t */.num_fats                   = 0x02,         // 2 FATs
    /*uint16_t*/.max_root_dir_entries       = 0x0020,       // 32 dir entries (max)
    /*uint16_t*/.total_logical_sectors      = 0x1f50,       // sector size * # of sectors = drive size
    /*uint8_t */.media_descriptor           = 0xf8,         // fixed disc = F8, removable = F0
    /*uint16_t*/.logical_sectors_per_fat    = 0x0001,       // FAT is 1k - ToDO:need to edit this
    /*uint16_t*/.physical_sectors_per_track = 0x0001,       // flat
    /*uint16_t*/.heads                      = 0x0001,       // flat
    /*uint32_t*/.hidden_sectors             = 0x00000000,   // before mbt, 0
    /*uint32_t*/.big_sectors_on_drive       = 0x00000000,   // 4k sector. not using large clusters
    /*uint8_t */.physical_drive_number      = 0x00,
    /*uint8_t */.not_used                   = 0x00,         // Current head. Linux tries to set this to 0x1
    /*uint8_t */.boot_record_signature      = 0x29,         // signature is present
    /*uint32_t*/.volume_id                  = 0x27021974,   // serial number
    // needs to match the root dir label
    /*char[11]*/.volume_label               = {'D', 'A', 'P', 'L', 'I', 'N', 'K', '-', 'D', 'N', 'D'},
    // unused by msft - just a label (FAT, FAT12, FAT16)
    /*char[8] */.file_system_type           = {'F', 'A', 'T', '1', '6', ' ', ' ', ' '},

    /*uint8_t[448]*/.bootstrap = {
        // 篇幅原因省略引导代码
    },
 
    // Signature MUST be 0xAA55 to maintain compatibility (i.e. with Android).
    /*uint16_t*/.signature = 0xAA55,
};

9.2 关于 FAT 表

1)FAT 表紧跟着 DBR,即从扇区 1 开始。
示例:在 daplink 的 source/daplink/drag-n-drop/virtual_fs.c 文件中的 vfs_init() 函数,结合 FAT16 文件系统的格式,可以看到 mbr 块、fat 块、根目录块的初始化。

c 复制代码
file_allocation_table_t fat;
................
void vfs_init(const vfs_filename_t drive_name, uint32_t disk_size)
{
................
    memset(&mbr, 0, sizeof(mbr));
    memset(&fat, 0, sizeof(fat));
    fat_idx = 0;
................
    // Initialize FAT
    fat_idx = 0;
    write_fat(&fat, fat_idx, 0xFFF8);    // Media type "media_descriptor"
    fat_idx++;
    write_fat(&fat, fat_idx, 0xFFFF);    // FAT12 - always 0xFFF (no meaning), FAT16 - dirty/clean (clean = 0xFFFF)
    fat_idx++;
................
    // Initialize root dir
    dir_idx = 0;
    dir_current.f[dir_idx] = root_dir_entry;
    memcpy(dir_current.f[dir_idx].filename, drive_name, sizeof(dir_current.f[0].filename));
    dir_idx++;
................    
}

9.3 关于目录项

目录项的结构:

示例:在 daplink 的 source/daplink/drag-n-drop/virtual_fs.c 文件中

c 复制代码
typedef struct FatDirectoryEntry {
    vfs_filename_t filename;
    uint8_t attributes;
    uint8_t reserved;
    uint8_t creation_time_ms;
    uint16_t creation_time;
    uint16_t creation_date;
    uint16_t accessed_date;
    uint16_t first_cluster_high_16;
    uint16_t modification_time;
    uint16_t modification_date;
    uint16_t first_cluster_low_16;
    uint32_t filesize;
} __attribute__((packed)) FatDirectoryEntry_t;

总结

结合 daplink 来看,根据鸭子定律,当我们通过 USB 总线协议告诉 USB 主机:插入的设备识别为 64MB U 盘,用起来像 64MB U 盘,那么它就是容量为 64MB 的 U 盘。实际上,运行 daplink 的 MCU 怎么可能有那么大的 FLASH。
而且别说是 64MB,就是 64GB 也可以(需要文件系统支持),因为 daplink 只需要一次性数据,并不需要把复制到 U 盘的数据再读取出来,那么这个 DAPLINK U 盘完全就是你强任你强,清风绕山岗了。
同理,找一个具有 USB 外设的 4G 模块和一台云对象存储服务器,理论上是不是可以开发一个云 U 盘出来?

相关推荐
d111111111d1 小时前
什么是内存对齐?在STM32上面如何通过编辑器指令来实现内存对齐。
笔记·stm32·单片机·嵌入式硬件·学习·编辑器
bai5459362 小时前
STM32 CuberIDE 中断
stm32·单片机·嵌入式硬件
小叶子来了啊2 小时前
5Arduino 程序结构
单片机·嵌入式硬件
小叶子来了啊2 小时前
1Arduino 简介
单片机·嵌入式硬件
雾岛听风眠3 小时前
电路板维修
单片机·嵌入式硬件
少一倍的优雅3 小时前
hi3863(WS63) 智能小车 (一) 简单介绍
单片机·嵌入式硬件·harmonyos·hi3863
小幽余生不加糖4 小时前
步进电机、有刷直流电机以及无刷直流电机对比
笔记·单片机·嵌入式硬件·学习·能源
x976664 小时前
使用 HMAC-SHA256算法对MCU UID进行加密
单片机·嵌入式硬件·算法
小叶子来了啊4 小时前
4Arduino 第一个程序
单片机·嵌入式硬件
方案开发PCBA抄板芯片解密5 小时前
什么是FIB,芯片解密为什么需要做FIB
单片机·嵌入式硬件