4、USB协议基础:USB 2.0协议概述、设备描述符与配置描述符、端点(Endpoint)概念、控制传输与批量传输

做加密狗固件开发,USB协议是绕不开的核心。说实话,我刚开始接触USB时也被那堆描述符搞得头大。但后来发现,搞懂几个关键概念,剩下的就是查手册的事了。

这一章,咱们就聊聊USB 2.0协议里最基础、也最常用的那部分。你想想看,加密狗本质上就是个USB设备,主机怎么识别它?怎么跟它通信?全靠这套协议。

4.1 USB 2.0协议概述

USB 2.0,全称Universal Serial Bus 2.0。它支持三种速率:低速(1.5 Mbps)、全速(12 Mbps)和高速(480 Mbps)。做加密狗,我们通常用全速或高速模式。

USB的通信模型是主从结构。主机(PC)是主,设备(加密狗)是从。所有通信都由主机发起,设备只能被动响应。嗯,这里要注意:设备不能主动给主机发数据,只能等主机来问。

USB的物理层用了差分信号(D+和D-)。我曾在项目中遇到过信号线接反的情况,结果设备死活枚举不上。排查了半天,最后发现是PCB走线时把D+和D-搞反了。所以画板子时一定仔细核对。

核心要点:USB通信永远是主机先开口。设备想说话?得先等主机来问。

4.2 设备描述符与配置描述符

描述符是什么?说白了就是设备的「身份证」和「说明书」。主机通过读取描述符,才知道你插上的是个什么东西,该怎么跟它打交道。

USB描述符是层级结构的。最顶层是设备描述符,下面挂着配置描述符,配置下面又挂着接口描述符和端点描述符。我习惯把这个结构想象成一棵树:根是设备,主干是配置,树枝是接口,树叶是端点。

4.2.1 设备描述符

设备描述符是主机读到的第一个描述符。它只有一份,描述了设备的基本信息。比如:

  • bLength:描述符长度,固定18字节
  • bDescriptorType:描述符类型,设备描述符是0x01
  • bcdUSB:USB规范版本号,比如0x0200表示USB 2.0
  • idVendor:厂商ID(VID),需要向USB-IF申请
  • idProduct:产品ID(PID),自己定义
  • bNumConfigurations:配置描述符的数量,通常为1

我记得第一次做加密狗时,VID/PID随便填了个测试值。结果插到客户电脑上,驱动死活装不上。后来才知道,VID必须合法,否则系统不认。所以量产前一定申请正式的VID。

小技巧:开发阶段可以用Linux的lsusb命令查看设备描述符。命令:lsusb -v -d 1234:5678(替换成你的VID:PID)

4.2.2 配置描述符

配置描述符描述了设备的一种工作模式。一个设备可以有多个配置,但同一时间只能激活一个。加密狗通常只有一个配置。

配置描述符里包含:

  • bLength:9字节
  • bDescriptorType:0x02
  • wTotalLength:整个配置的总长度(包括接口和端点描述符)
  • bNumInterfaces:该配置下的接口数量
  • bConfigurationValue:配置编号,用于Set Configuration请求
  • bmAttributes:电源属性,比如总线供电还是自供电
  • bMaxPower:最大功耗,单位2mA

这里有个坑:bMaxPower的值是2mA的倍数。比如你想声明100mA,就填50。我曾经填错过,结果设备枚举时主机报告「超过总线供电能力」,折腾了好久才发现是这里算错了。

4.3 端点(Endpoint)概念

端点,是USB通信的最小单元。你可以把它想象成一个「数据管道」。每个端点都有一个编号(0~15)和一个方向(IN或OUT)。

  • IN端点:设备→主机。比如加密狗把计算结果发给PC。
  • OUT端点:主机→设备。比如PC把要加密的数据发给加密狗。

端点0是特殊的控制端点,每个USB设备都必须有。它用于枚举和基本控制。其他端点(1~15)才是真正干活的。

端点描述符里最重要的几个字段:

字段 含义 常见值
bEndpointAddress 端点地址(含方向) 0x81表示端点1 IN,0x01表示端点1 OUT
bmAttributes 传输类型 0x02表示批量传输
wMaxPacketSize 最大包大小 全速下批量传输通常是64字节
bInterval 轮询间隔(毫秒) 批量传输通常为0

注意:端点地址的bit7表示方向。1是IN,0是OUT。所以0x81其实是端点1的IN方向。别搞混了。

4.4 控制传输与批量传输

USB定义了四种传输类型:控制传输、批量传输、中断传输和同步传输。加密狗开发中,最常用的是控制传输和批量传输。

4.4.1 控制传输

控制传输用于可靠但数据量小的通信。它主要用于设备枚举和命令控制。控制传输有四个阶段:

  1. Setup阶段:主机发送请求(8字节)
  2. Data阶段(可选):传输数据
  3. Status阶段:确认传输结果

控制传输的请求格式是固定的:

typedef struct {
    uint8_t  bmRequestType;  // 请求类型(方向、类型、接收者)
    uint8_t  bRequest;       // 请求码
    uint16_t wValue;         // 值
    uint16_t wIndex;         // 索引
    uint16_t wLength;        // 数据长度
} USB_SetupPacket;

常见的标准请求有:Get Descriptor(获取描述符)、Set Address(设置地址)、Set Configuration(激活配置)。

我个人习惯把控制传输用在「命令通道」上。比如加密狗的「获取状态」、「设置密钥」这类小数据量的操作,用控制传输最合适。它可靠,而且不需要额外配置端点。

4.4.2 批量传输

批量传输用于大数据量、非实时的传输。它保证数据正确到达,但不保证延迟。加密狗处理加密数据时,通常用批量传输。

批量传输的特点:

  • 可靠:有CRC校验和重传机制
  • 非实时:带宽不够时会被延迟
  • 全速下最大包64字节,高速下512字节

批量传输没有固定的帧结构。主机和设备通过令牌包、数据包和握手包来完成一次传输。如果数据超过最大包大小,会自动分包。

我在项目中遇到过一个问题:加密狗批量传输时,偶尔会丢包。排查后发现是固件里的端点缓冲区没清空。每次传输完成后,一定要把FIFO复位一下。这个坑我踩过两次,后来就养成了习惯:批量传输完成后,强制清空端点缓冲区。

实战建议:加密狗固件里,控制端点0用于枚举和命令,批量端点1用于数据传输。这样分工清晰,调试也方便。

4.5 小结

这一章我们聊了USB 2.0协议的核心概念。说白了,就是三件事:

  • 描述符告诉主机「你是谁、你能干什么」
  • 端点是通信的管道,端点0是控制通道
  • 控制传输发命令,批量传输传数据

下一章,我们会把这些知识串起来,真正动手写一个USB加密狗的枚举代码。到时候你会发现,搞懂了这些基础,写代码其实没那么难。

推荐工具:USBlyzer(Windows)或Wireshark(带USB捕获功能)。抓包分析是理解USB协议最快的方式。我当年就是靠抓包学会的。