2. USB 协议基础回顾:设备枚举、端点与描述符

在深入 Linux USB Gadget 虚拟网卡驱动开发之前,必须牢固掌握 USB 协议的核心基础。本章将系统回顾 USB 设备枚举的完整过程、四种端点类型的特性,以及描述符体系的层级结构。这些知识是理解 Gadget 驱动如何模拟 USB 设备、主机如何识别虚拟网卡的前提。

2.1 USB 设备枚举过程

USB 设备枚举是主机(Host)发现并配置新接入设备的一系列标准事务。整个过程由主机控制器驱动(HCD)发起,设备被动响应。枚举成功后才能加载功能驱动(如 CDC ECM / RNDIS 驱动)。

关键理解: 枚举过程完全由主机主导,Gadget 端只需按照 USB 规范正确响应标准请求(Standard Request)。虚拟网卡驱动必须实现正确的描述符和端点配置,否则主机无法识别设备类型。

枚举步骤流程

  1. 设备连接与复位: 主机检测到 D+/D- 线路电平变化,确认设备插入;随后主机向设备发送复位信号(SE0 状态持续至少 10ms)。
  2. 获取设备描述符(第 1 次): 主机发送 GET_DESCRIPTOR(DEVICE) 请求,仅读取前 8 字节(获取 bMaxPacketSize0)。
  3. 设备复位(可选): 部分主机会在获取到端点 0 最大包长后再次复位设备。
  4. 设置地址: 主机发送 SET_ADDRESS 请求,分配唯一总线地址(1~127)。设备进入地址状态(Address State)。
  5. 获取设备描述符(第 2 次): 主机再次发送 GET_DESCRIPTOR(DEVICE),读取完整设备描述符(18 字节)。
  6. 获取配置描述符: 主机发送 GET_DESCRIPTOR(CONFIGURATION),读取配置描述符及其关联的所有接口、端点、HID/CDC 等功能描述符。
  7. 选择配置: 主机发送 SET_CONFIGURATION 请求,激活选中的配置。设备进入配置状态(Configured State),此时功能端点可用。
开发提示: 在 Gadget 驱动中,枚举失败最常见的原因是描述符长度或格式错误。建议在 composite_bind() 回调中打印描述符的原始字节,与 USB 规范逐字节核对。
USB 设备枚举状态机 连接 / 复位 获取设备描述符(8B) 设置地址 获取完整设备描述符 获取配置描述符 选择配置 配置状态 (可用) 说明:枚举由主机主导,设备被动响应。Gadget 驱动需在描述符中声明正确的 bDeviceClass / bInterfaceClass 以匹配网卡类驱动。

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),如果混淆端点类型,主机侧驱动将无法正确匹配。
USB 四种端点类型对比 控制 (Control) 端点 0,双向 枚举 / 标准请求 批量 (Bulk) 单向 / 双向 数据通道 (网卡) 中断 (Interrupt) 单向 (IN) 通知端点 (CDC) 同步 (Isoch) 单向 音频 / 视频 Gadget 网卡驱动端点分配建议: • 端点 0 (控制):CDC 管理命令(SET_ETHERNET_PACKET_FILTER、GET_NTB_PARAMETERS 等) • 批量 OUT 端点:主机发送网络数据包到设备 • 批量 IN 端点:设备发送网络数据包到主机 • 中断 IN 端点:CDC 通知(连接状态变化、网络事件)

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 (中断)
USB 描述符层级结构 设备描述符 (Device Descriptor) 配置描述符 (Configuration Descriptor) 接口描述符 0 (CDC 控制) 接口描述符 1 (CDC 数据) 端点描述符 (中断 IN) 通知端点 批量 OUT 批量 IN CDC 功能描述符 (Header/Union/Ethernet) 说明:虚拟网卡描述符必须包含 CDC Union 功能描述符,将控制接口与数据接口关联,主机才能正确识别为网卡设备。

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 框架。