U 盘

《圈圈教你学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 文件):

/* 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() 函数):

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 接口描述符后面的端点描述符如下:

#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

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 文件

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 块、根目录块的初始化。

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 文件中

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 盘出来?

posted @ 2024-10-23 10:12  送南阳马生序  阅读(3)  评论(0编辑  收藏  举报