UDC(USB Device Controller)驱动:UDC 的作用与常见实现
UDC,全称 USB Device Controller。说白了,它就是 USB 设备端最底层的硬件管家。每次你在开发板上插入 USB 线,主机发来一个控制传输,数据是怎么从物理引脚一路跑到你的 Gadget 函数里的?嗯,中间全靠 UDC 驱动在扛。
我个人习惯把 UDC 比作“硬件翻译官”——它负责把 USB 总线上的包拆开,然后按 USB 协议的标准格式,丢给上层处理。反过来,上层要回复的数据也由它打包发出去。如果没有 UDC 驱动,你的 OTG Gadget 连枚举的第一步都迈不出去。
UDC 的核心职责
- 管理端点(Endpoint)的配置与数据传输
- 处理总线事件(复位、挂起、唤醒等)
- 维护 DMA 缓冲区与 FIFO
- 向上层暴露标准的 Gadget API(usb_ep_ops、usb_gadget_ops)
UDC 在 USB Gadget 框架中的位置
先看整体层次:最下面是硬件控制器,上面是 UDC 驱动,再上面是 UDC Core(也称 Gadget Framework),最上面是各个功能驱动(如 f_mass_storage、f_serial)。UDC 驱动必须把底层寄存器操作封装成统一接口,否则 UDC Core 没法通用。
我早年调一个基于 DWC3 的板子时,系统死活枚举不到设备。后来发现是 UDC 驱动里端点映射写反了,导致控制传输的 EP0 根本没注册上来。这就是 UDC 驱动的坑——硬件手册没吃透,枚举就卡在第一关。
常见 UDC 控制器:DWC3 与 DWC2
目前市面上主流的 UDC 实现主要有两家:Synopsys 的 DWC3(DesignWare Cores USB 3.0)和 DWC2(USB 2.0)。另外还有 Chipidea、MUSB 等,但 DWC 系列占了八成以上的板子。我接触过的平台里,高通、瑞芯微、全志几乎全是 DWC3,只有一些低端 MCU 还在用 DWC2。
| 特性 | DWC3 | DWC2 |
|---|---|---|
| USB 世代 | USB 3.0 SuperSpeed + 2.0 | USB 2.0 HighSpeed / FullSpeed |
| 端点数量 | 最多 32 个(可配置) | 最多 16 个 |
| DMA 能力 | 内置 Scatter-Gather DMA | 部分模式有 DMA,常需 CPU 搬运 |
| 驱动复杂度 | 较高,需要处理 TRB 环 | 相对简单,寄存器直接操作 |
| 常见平台 | 高端 SoC(骁龙、Exynos、RK35xx) | 低功耗 MCU、老旧芯片(S3C2440、全志 F1C100s) |
DWC3 驱动要点
DWC3 的内核驱动叫 dwc3.ko,它其实是两层结构:一个核心模块处理寄存器操作和 DMA 管理,一个平台驱动模块处理设备树匹配。我记得第一次看 DWC3 的源码时,被那个 TRB(Transfer Request Block)环搞晕了。简单说,每次数据传输都要先构造一串 TRB 描述符放到环形缓冲,然后硬件自动去抓取、搬运。
调试小技巧
在 /sys/kernel/debug/dwc3/ 下,你能看到端点状态、当前 TRB 指针、FIFO 占用等。我曾经靠这个目录里的信息,定位到一个 DMA 地址不对齐导致的枚举超时问题。
DWC3 驱动里有个重要的回调函数组 dwc3_gadget_ops:
static const struct usb_gadget_ops dwc3_gadget_ops = {
.pullup = dwc3_gadget_pullup,
.udc_start = dwc3_gadget_start,
.udc_stop = dwc3_gadget_stop,
.get_frame = dwc3_gadget_get_frame,
.wakeup = dwc3_gadget_wakeup,
.set_selfpowered = dwc3_gadget_set_selfpowered,
.vbus_session = dwc3_gadget_vbus_session,
};
这些函数实现了 UDC Core 所需的接口。其中 pullup 是最关键的——它控制 D+ 上拉电阻。如果上拉时序不对,主机根本看不到设备插入。
DWC2 驱动要点
DWC2 的驱动在驱动树上叫 dwc2,支持 host、device、OTG 三种模式。它比 DWC3 简单得多,但也有不少坑。比如它的 FIFO 分配是静态的,你必须根据端点配置提前规划好。我调试一个串口 Gadget 项目时,因为 RXFIFO 分配太小,导致数据稍大就丢包,枚举成功后设备一直发送 STALL。
注意
DWC2 的驱动在 Linux 中有一个重要的配置选项:CONFIG_USB_DWC2,还需要选择 CONFIG_USB_DWC2_PERIPHERAL 或 CONFIG_USB_DWC2_DUAL_ROLE。千万别只勾了 Host 模式,否则 Gadget 功能根本编译不进去。
内核中的 UDC 驱动注册流程
无论是 DWC3 还是 DWC2,它们最终都要调用 usb_add_gadget_udc() 注册到 UDC Core。这个函数原型:
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget);
注册成功后,UDC Core 会在 /sys/devices/platform/*/udc/ 下创建设备节点。你可以用 ls /sys/class/udc/ 查看 UDC 是否被内核识别。如果这里为空,说明 UDC 驱动根本没加载成功,枚举失败就是必然的。
我遇到过一次奇葩情况:设备树里 UDC 节点没使能,但驱动模块已 insmod,结果 dwc3_probe 返回 -ENODEV,注册失败。嗯,这类问题通常从 dmesg 就能看出来,留意 "dwc3: failed to find dwc3 core" 之类的信息。
调试 UDC 的常用手段
- 检查 dmesg:看 UDC probe 是否成功,有没有错误打印。
- 查看 /sys/class/udc/:有设备文件说明 UDC 已注册。
- 使用 usb_gadget_debugfs:DWC3 下的 debug 文件能显示端点状态。
- 打开 Gadget 调试日志:在驱动里加
#define DEBUG或动态调试。 - 逻辑分析仪抓包:硬件级确认总线事务是否正常。
枚举失败时快速自查清单
- UDC 驱动加载了吗?
lsmod | grep dwc3 - UDC 设备存在吗?
ls /sys/class/udc/ - Gadget 函数绑定上了吗?
cat /sys/kernel/config/usb_gadget/g1/UDC - 端点配置对吗?
cat /sys/kernel/debug/dwc3/*.ep*/state
避坑指南
我曾经因为 UDC 电源管理问题排查了整整两天。 某款 SoC 在系统休眠后,UDC 驱动没有正确处理唤醒,导致 USB 总线复位后控制器不响应。解决方案是在设备树里加上 wakeup-source; 属性。
另一个常见陷阱是 DWC3 的 snps,dis_u3_susphy_quirk 和 snps,dis_u2_susphy_quirk 这两个 device tree 属性。如果没配好,USB PHY 可能始终处于省电模式,无法正常发起传输。你可以查一下 dmesg | grep phy 确认 PHY 状态。