2. USB 协议基础回顾:设备枚举、端点与描述符
在深入 Linux USB Gadget 虚拟网卡驱动开发之前,必须牢固掌握 USB 协议的核心基础。本章将系统回顾 USB 设备枚举的完整过程、四种端点类型的特性,以及描述符体系的层级结构。这些知识是理解 Gadget 驱动如何模拟 USB 设备、主机如何识别虚拟网卡的前提。
2.1 USB 设备枚举过程
USB 设备枚举是主机(Host)发现并配置新接入设备的一系列标准事务。整个过程由主机控制器驱动(HCD)发起,设备被动响应。枚举成功后才能加载功能驱动(如 CDC ECM / RNDIS 驱动)。
关键理解: 枚举过程完全由主机主导,Gadget 端只需按照 USB 规范正确响应标准请求(Standard Request)。虚拟网卡驱动必须实现正确的描述符和端点配置,否则主机无法识别设备类型。
枚举步骤流程
- 设备连接与复位: 主机检测到 D+/D- 线路电平变化,确认设备插入;随后主机向设备发送复位信号(SE0 状态持续至少 10ms)。
- 获取设备描述符(第 1 次): 主机发送
GET_DESCRIPTOR(DEVICE)请求,仅读取前 8 字节(获取bMaxPacketSize0)。 - 设备复位(可选): 部分主机会在获取到端点 0 最大包长后再次复位设备。
- 设置地址: 主机发送
SET_ADDRESS请求,分配唯一总线地址(1~127)。设备进入地址状态(Address State)。 - 获取设备描述符(第 2 次): 主机再次发送
GET_DESCRIPTOR(DEVICE),读取完整设备描述符(18 字节)。 - 获取配置描述符: 主机发送
GET_DESCRIPTOR(CONFIGURATION),读取配置描述符及其关联的所有接口、端点、HID/CDC 等功能描述符。 - 选择配置: 主机发送
SET_CONFIGURATION请求,激活选中的配置。设备进入配置状态(Configured State),此时功能端点可用。
开发提示: 在 Gadget 驱动中,枚举失败最常见的原因是描述符长度或格式错误。建议在
composite_bind() 回调中打印描述符的原始字节,与 USB 规范逐字节核对。
2.2 USB 端点类型
端点是 USB 通信的基本单元,每个端点有唯一的地址(端点号 + 方向)。USB 规范定义了四种传输类型,对应不同的应用场景。
| 端点类型 | 传输模式 | 特点 | 典型应用 | Gadget 网卡用途 |
|---|---|---|---|---|
| 控制 (Control) | 双向,端点 0 | 可靠,延迟有保证,数据量小 | 枚举、标准请求、类请求 | CDC 管理命令(SET_ETHERNET_PACKET_FILTER) |
| 批量 (Bulk) | 单向或双向 | 可靠,无带宽保证,数据量大 | U 盘、打印机 | 虚拟网卡数据通道(ECM / RNDIS 数据端点) |
| 中断 (Interrupt) | 单向(通常 IN) | 可靠,有轮询周期,数据量小 | 鼠标、键盘 | CDC 通知端点(连接状态、网络事件) |
| 同步 (Isochronous) | 单向 | 不可靠,带宽保证,实时性高 | 音频、视频 | 通常不使用(网卡要求可靠传输) |
常见误区: 虚拟网卡驱动(如 CDC ECM)的数据端点必须使用批量传输,而非中断或同步。中断端点仅用于 CDC 通知(Notification Element),如果混淆端点类型,主机侧驱动将无法正确匹配。
2.3 USB 描述符结构
描述符是 USB 设备的“身份证”,以层级结构描述设备的能力和配置。主机通过读取描述符来加载正确的驱动程序。对于 Gadget 虚拟网卡,描述符必须精确匹配 CDC ECM / RNDIS / NCM 等类规范。
描述符层级关系
- 设备描述符 (Device Descriptor): 顶层,描述 VID/PID、USB 版本、设备类、端点 0 最大包长。
- 配置描述符 (Configuration Descriptor): 每个设备可有一个或多个配置,描述供电模式、最大电流、接口数量。
- 接口描述符 (Interface Descriptor): 每个配置包含一个或多个接口,描述功能类(如 CDC 数据接口、CDC 控制接口)。
- 端点描述符 (Endpoint Descriptor): 每个接口包含一个或多个端点,描述端点地址、传输类型、最大包长、轮询间隔。
- 其他类/功能描述符: 如 CDC 功能描述符(Header、Union、Ethernet Networking 等),位于接口描述符之后。
Gadget 开发要点: 虚拟网卡通常需要两个接口:控制接口(CDC Control) 和 数据接口(CDC Data)。控制接口包含中断 IN 端点(通知),数据接口包含批量 IN/OUT 端点(数据)。接口关联通过 Union Functional Descriptor 实现。
关键描述符字段速查表
| 描述符类型 | 关键字段 | 含义 | 虚拟网卡典型值 |
|---|---|---|---|
| 设备描述符 | bcdUSB |
USB 规范版本号 (BCD) | 0x0200 (USB 2.0) |
bDeviceClass |
设备类代码 | 0x00 (在接口描述符中定义) | |
bMaxPacketSize0 |
端点 0 最大包长 | 64 (高速) / 8/16/32 (全速) | |
| 配置描述符 | bNumInterfaces |
该配置下的接口数量 | 2 (控制 + 数据) |
bMaxPower |
最大电流 (2mA 为单位) | 0x32 (100mA) | |
| 接口描述符 | bInterfaceClass |
接口类代码 | 0x02 (CDC Control) / 0x0A (CDC Data) |
bInterfaceSubClass |
接口子类代码 | 0x00 (ECM 控制) / 0x00 (ECM 数据) | |
| 端点描述符 | bEndpointAddress |
端点地址 (方向 + 端点号) | 0x81 (EP1 IN) / 0x02 (EP2 OUT) |
bmAttributes |
传输类型 | 0x02 (批量) / 0x03 (中断) |
2.4 描述符在 Gadget 驱动中的实现
在 Linux Gadget 框架中,描述符通过结构体数组和 usb_descriptor_header 指针链组织。以下是一个简化的 CDC ECM 描述符定义示例:
/* 设备描述符 */
static struct usb_device_descriptor device_desc = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = cpu_to_le16(0x0200),
.bDeviceClass = 0x00, /* 在接口中定义 */
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64,
.idVendor = cpu_to_le16(0x0525), /* NetChip */
.idProduct = cpu_to_le16(0xa4a1), /* ECM 示例 */
.bcdDevice = cpu_to_le16(0x0100),
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01,
};
/* 配置描述符 + 接口 + 端点 + CDC 功能描述符 */
static struct usb_configuration config = {
.label = "CDC Ethernet (ECM)",
.bConfigurationValue = 1,
.bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
.bMaxPower = 0x32, /* 100 mA */
};
调试技巧: 使用
lsusb -v 查看主机侧解析到的描述符树。如果 Gadget 描述符定义有误,主机可能显示 bInterfaceClass = 0xFF (Vendor Specific),此时需要检查 CDC 功能描述符的字节顺序和长度。
2.5 本章小结
- USB 枚举是主机驱动的标准流程,Gadget 端必须正确响应
GET_DESCRIPTOR和SET_CONFIGURATION请求。 - 虚拟网卡驱动使用批量端点传输网络数据,中断端点传输 CDC 通知,控制端点处理管理命令。
- 描述符层级为:设备 → 配置 → 接口 → 端点 + 功能描述符。CDC 类规范要求控制接口和数据接口通过 Union 功能描述符关联。
- 在 Linux Gadget 驱动中,描述符通过静态结构体定义,并在
bind()回调中注册到 composite 框架。