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 控制传输
控制传输用于可靠但数据量小的通信。它主要用于设备枚举和命令控制。控制传输有四个阶段:
- Setup阶段:主机发送请求(8字节)
- Data阶段(可选):传输数据
- 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协议最快的方式。我当年就是靠抓包学会的。