Skip to content

Gadget ACM Function 路径 ​

层:Function(f_acm.c + u_serial.c)
深度:T2 专深(SET_CONFIGURATION 之后数据面)
内核:Linux 5.4 源码(dwc2 dual-role 平台对照);路径与 Linux 6.8 同源,差异处另行注明
关联:Composite EP0 枚举 · Configfs 组装分析 · DWC2 接口总览 · UDC bind 分析 · Gadget CDC ACM 串口实践
说明:bulk usb_ep_queue 见 DWC2 接口总览 §3.4;实践脚本见 Gadget CDC ACM 串口实践


目录 ​


1. 本文要回答 ​

以 acm.0 → /dev/ttyGS0 为例,从 configfs 创建 function 到 Host 能读写串口,L4 层经历哪些阶段:

阶段何时关键函数
实例化mkdir acm.0acm_alloc_inst → gserial_alloc_line
bindT1 configfs_composite_bindacm_bind:interface ID、EP autoconfig
激活T2 SET_CONFIGURATIONacm_set_alt → gserial_connect
I/OHost open + 数据gs_start_io、bulk 完成回调
关闭reset config / disconnectacm_disable → gserial_disconnect

前置:set_alt 触发点见 Composite EP0 枚举 §4;configfs mkdir acm.0 见 Configfs 组装分析 §4。


2. 文件分工 ​

文件职责
f_acm.cUSB 描述符、CDC class、acm_bind/set_alt/setup、notify EP
u_serial.cgserial_* glue:TTY 驱动、ttyGS*、gs_port、bulk 请求池
configfs.cusb_function_instance 注册、acm.0 属性

struct gserial 是 USB 端点 ↔ TTY 端口 的桥梁;struct f_acm 内嵌 struct gserial port。


3. 阶段 A:T0 — mkdir acm.0 ​

configfs 创建 acm.0 → acm_alloc() → gserial_alloc_line(&acm->port_num)(u_serial.c):

  1. 找空闲 port_num(0 … MAX_U_SERIAL_PORTS-1)
  2. gs_port_alloc() — 分配 gs_port、默认 line coding 9600 8N1
  3. tty_port_register_device(..., gs_tty_driver, port_num, ...) → 预创 /dev/ttyGS0
  4. gserial_console_init()(若启用 console)

此时尚 未 bind 到 gadget,无 USB EP;TTY 节点存在但无 carrier。


4. 阶段 B:T1 bind — acm_bind() ​

在 configfs_composite_bind 里对每个 function 调用 usb_add_function(c, f) → acm_bind(c, f):

步骤代码要点
字符串usb_gstrings_attach(acm_strings, ...) → interface 字符串 ID
Interface两次 usb_interface_id → acm->ctrl_id、acm->data_id;写 IAD、union、call-mgmt 描述符
端点三次 usb_ep_autoconfig:bulk IN、bulk OUT、interrupt notify
描述符表usb_assign_descriptors(f, acm_fs_function, acm_hs_function, acm_ss_function, NULL)
notifygs_alloc_req(notify_ep, ...) + acm_cdc_notify_complete

bind 不 enable EP,也不 gserial_connect。EP 地址在 autoconfig 时分配,描述符挂到 f->fs_descriptors 等供 EP0 GET_DESCRIPTOR。


5. 阶段 C:T2 — acm_set_alt() 与 gserial_connect() ​

Host SET_CONFIGURATION → composite set_config → 对每个 function 调 acm_set_alt(f, intf, 0)。

ACM 有两个 interface(control + data),set_alt 按 intf 分支:

5.1 Control interface(intf == acm->ctrl_id) ​

c
usb_ep_disable(acm->notify);
config_ep_by_speed(..., acm->notify);
usb_ep_enable(acm->notify);

启用 CDC notification(SerialState 等),不走 gserial_connect。

5.2 Data interface(intf == acm->data_id) ​

c
if (acm->notify->enabled) {
    gserial_disconnect(&acm->port);   /* 复配时先断 */
}
config_ep_by_speed(..., acm->port.in/out);
gserial_connect(&acm->port, acm->port_num);

gserial_connect(u_serial.c):

步骤动作
1usb_ep_enable(gser->in/out),driver_data = port
2port->port_usb = gser,gser->ioport = port
3若 TTY 已 open(port->port.count > 0)→ gs_start_io(port)
4可选 gser->connect() 回调(ACM 用于 DTR 等)

T2 完成标志:bulk IN/OUT 已 enable,TTY 层认为 USB link active(类似 carrier detect)。


6. 阶段 D:数据 I/O ​

Host 侧打开 /dev/ttyACM0 并配置 line coding;Device 侧若已 gserial_connect:

6.1 TTY open → 启动 USB I/O ​

用户 open("/dev/ttyGS0") → TTY core → 若 port->port_usb 非空 → gs_start_io(port):

  • 在 bulk OUT 上挂 gs_read_complete 请求池(收 Host 数据 → 放入 TTY flip buffer)
  • 在 bulk IN 上准备 gs_write_complete 池(TTY write → USB IN)

6.2 方向对照 ​

方向USBTTY 用户态
Host → Devicebulk OUTread ttyGS0
Device → Hostbulk INwrite ttyGS0

底层 usb_ep_queue 由 UDC(dwc2)完成,经 ep_ops.queue(DWC2 接口总览 §3.4);DxEPDMA 详读见 dwc2 Buffer DMA(待迁入)。

6.3 CDC class EP0(并行) ​

Host SET_LINE_CODING / SET_CONTROL_LINE_STATE → acm_setup → 更新 port_line_coding、DTR/RTS 通知。与 bulk 数据面正交,仍走 EP0(Composite EP0 枚举 §6)。


7. 阶段 E:去激活 — acm_disable() / disconnect ​

触发路径
SET_CONFIGURATION 0 / 换配置reset_config → acm_disable
USB disconnectcomposite_disconnect → disable functions

acm_disable:

c
gserial_disconnect(&acm->port);
usb_ep_disable(acm->notify);

gserial_disconnect:清 port_usb、TTY hangup、usb_ep_disable in/out、释放 request 池。


8. 时间线总览(ACM 专用) ​

T0  mkdir acm.0
      └─ gserial_alloc_line → /dev/ttyGS0 出现(无 USB)

T1  echo UDC → configfs_composite_bind
      └─ acm_bind:EP 号、描述符进 composite
      └─ pullup → Host 枚举(Composite EP0)

T2  Host SET_CONFIGURATION
      └─ acm_set_alt(notify) + acm_set_alt(data)
      └─ gserial_connect → bulk enable

T2+ Host open ttyACM + 用户 open/write ttyGS0
      └─ gs_start_io ↔ bulk DMA

Teardown  echo "" > UDC / disconnect
      └─ acm_disable / gserial_disconnect

9. 调试提示 ​

现象可能 L4 原因
有 ttyGS0 但无法读写T2 未到:gserial_connect 未执行(未 SET_CONFIGURATION)
lsusb 无 ACMT0/T1:function 未挂进 config 或 bind 失败
一端 open 无数据仅 Host 或仅 Device open;需 gs_start_io 两侧至少 Device open
拔线 hangup正常:gserial_disconnect → tty_hangup

dmesg 关键字:activate acm ttyGS0、gserial_connect: start ttyGS0、acm ttyGS0 deactivated。


10. 源码索引 ​

函数文件行号约
acm_alloc / acm_free_instf_acm.c719+
acm_bind / acm_unbindf_acm.c606 / 709
acm_set_alt / acm_disablef_acm.c420 / 464
acm_setupf_acm.c~330
gserial_alloc_lineu_serial.c1220
gserial_connect / gserial_disconnectu_serial.c1288 / 1364
gs_start_iou_serial.c536
gs_read_complete / gs_write_completeu_serial.c450 / 461

11. 关联文档 ​

文档内容
Composite EP0 枚举EP0、SET_CONFIGURATION、set_alt 触发
UDC bind 分析echo UDC、pullup 与 T1
Configfs 组装分析mkdir acm.0、gadget_info 拼装
Gadget CDC ACM 串口实践脚本实操、lsusb 与 Host cdc_acm
DWC2 接口总览ep_ops.queue 与 bulk 硬件路径
dwc2 Buffer DMA(待迁入)DxEPDMA、start_req 详读

基于 VitePress 构建