4. ACM Gadget驱动原理:CDC ACM协议、串口仿真模型、数据流与控制流分离
本章深入剖析Linux USB Gadget子系统中ACM(Abstract Control Model)功能的实现原理。理解CDC ACM协议规范、串口仿真模型以及数据流与控制流分离的设计思想,是开发稳定、高性能USB虚拟串口驱动的基础。我们将从协议标准出发,逐步拆解Gadget侧的软件架构与数据路径。
4.1 CDC ACM协议规范与Gadget实现
CDC(Communication Device Class)是USB-IF定义的用于通信设备的类规范。ACM(Abstract Control Model)是CDC下的一个子类,专门用于模拟传统串口(如RS-232)的控制信号与数据传输。在Gadget侧,驱动需要实现CDC ACM描述符集合,以向Host侧正确枚举为一个“通信设备”。
4.1.1 描述符结构
一个标准的CDC ACM Gadget需要实现以下描述符层次:
| 描述符类型 | 数量 | 说明 |
|---|---|---|
| 设备描述符 | 1 | bDeviceClass=0x02(通信设备),bDeviceSubClass=0x00 |
| 配置描述符 | 1 | 包含接口关联描述符(IAD) |
| 接口关联描述符(IAD) | 1 | 将通信接口与数据接口绑定为一个功能 |
| 通信接口描述符 | 1 | bInterfaceClass=0x02,bInterfaceSubClass=0x02(ACM) |
| 端点描述符(中断IN) | 1 | 用于通知Host串口状态变化(如CTS、DSR) |
| 数据接口描述符 | 1 | bInterfaceClass=0x0A(数据接口) |
| 端点描述符(批量IN) | 1 | Host→Device的数据接收端点 |
| 端点描述符(批量OUT) | 1 | Device→Host的数据发送端点 |
drivers/usb/gadget/function/u_serial.c 和 f_acm.c 提供了CDC ACM的通用实现。开发者通常通过配置 g_serial 或 g_acm_ms 模块来启用。
4.1.2 控制请求与通知
CDC ACM定义了一系列类特定请求(Class-Specific Requests)和通知(Notifications),用于管理串口参数与信号:
- SET_LINE_CODING (0x20): Host设置波特率、数据位、停止位、校验方式。
- GET_LINE_CODING (0x21): Host查询当前串口参数。
- SET_CONTROL_LINE_STATE (0x22): Host设置DTR和RTS信号状态。
- SERIAL_STATE (0x20): Device通过中断端点主动上报CTS、DSR、RI、DCD等信号变化。
/* 内核中定义的CDC ACM请求宏 */
#define USB_CDC_REQ_SET_LINE_CODING 0x20
#define USB_CDC_REQ_GET_LINE_CODING 0x21
#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22
/* 串口状态位图定义 */
#define USB_CDC_SERIAL_STATE_DCD (1 << 0)
#define USB_CDC_SERIAL_STATE_DSR (1 << 1)
#define USB_CDC_SERIAL_STATE_BREAK (1 << 2)
#define USB_CDC_SERIAL_STATE_RING (1 << 3)
#define USB_CDC_SERIAL_STATE_FRAMING (1 << 4)
#define USB_CDC_SERIAL_STATE_PARITY (1 << 5)
#define USB_CDC_SERIAL_STATE_OVERRUN (1 << 6)
4.2 串口仿真模型
ACM Gadget的核心任务是在Gadget侧模拟一个完整的UART(通用异步收发器)行为。这包括数据收发、波特率管理、流控信号模拟以及线路编码处理。串口仿真模型将物理UART的硬件寄存器操作,映射为USB端点上的数据包与控制请求。
4.2.1 数据路径仿真
数据路径的仿真通过批量端点实现,与物理UART的TX/RX引脚对应:
- 发送路径(Gadget→Host): Gadget将数据写入批量IN端点,Host通过批量OUT端点读取。对应物理UART的TXD引脚。
- 接收路径(Host→Gadget): Host将数据写入批量OUT端点,Gadget通过批量IN端点读取。对应物理UART的RXD引脚。
4.2.2 控制信号仿真
控制信号通过中断端点和类特定请求实现仿真:
| 物理信号 | 仿真方式 | 方向 |
|---|---|---|
| DTR (Data Terminal Ready) | SET_CONTROL_LINE_STATE请求 | Host→Device |
| RTS (Request To Send) | SET_CONTROL_LINE_STATE请求 | Host→Device |
| CTS (Clear To Send) | SERIAL_STATE通知 | Device→Host |
| DSR (Data Set Ready) | SERIAL_STATE通知 | Device→Host |
| DCD (Data Carrier Detect) | SERIAL_STATE通知 | Device→Host |
| RI (Ring Indicator) | SERIAL_STATE通知 | Device→Host |
4.3 数据流与控制流分离
CDC ACM协议最核心的设计思想是数据流与控制流分离。这一分离体现在USB端点层面、驱动软件架构层面以及Host侧设备节点层面。分离设计使得大数据量传输不受控制信号交互的阻塞,提高了虚拟串口的吞吐效率。
4.3.1 端点层面的分离
在USB描述符中,数据流与控制流使用完全不同的端点类型:
| 流类型 | 端点类型 | 传输类型 | 典型最大包长 | 特点 |
|---|---|---|---|---|
| 数据流 | 批量端点 | 批量传输 | 512字节(HS)/ 64字节(FS) | 保证数据完整性,无固定带宽 |
| 控制流 | 中断端点 | 中断传输 | 16字节 | 保证低延迟,定期轮询 |
4.3.2 软件架构中的分离
在Linux Gadget驱动中,数据流与控制流的分离通过不同的URB(USB Request Block)处理路径实现:
- 数据流路径: 通过
usb_ep_queue()提交批量URB,由gserial_alloc_request()管理缓冲区。数据在Gadget的TTY层与USB批量端点之间直接传递。 - 控制流路径: 通过
usb_ep_queue()提交中断URB,用于发送SERIAL_STATE通知。控制请求(如SET_LINE_CODING)通过setup()回调处理,不经过数据路径。
4.3.3 分离设计的优势
usbmon 抓取USB总线数据,观察批量端点是否有数据交换,以及中断端点是否按时发送SERIAL_STATE通知。
4.4 本章小结
本章详细阐述了ACM Gadget驱动的三大核心原理:
- CDC ACM协议: 定义了描述符结构、类特定请求和通知机制,是Gadget与Host通信的规范基础。
- 串口仿真模型: 将物理UART的硬件信号映射为USB端点操作和参数结构体,实现透明串口仿真。
- 数据流与控制流分离: 通过批量端点与中断端点/控制请求的分离设计,保证了虚拟串口的高吞吐与实时响应。