3. RK SDK USB驱动框架:从Host到Gadget的完整链路
做RK平台USB开发,第一件事就是搞清楚驱动框架长什么样。我见过不少新人,拿着SDK直接改dts,改完发现枚举失败,然后一脸懵——其实不是配置写错了,而是压根没搞懂数据流经过了哪些层。
这一章,咱们就把RK SDK里的USB驱动栈拆开来看。从Host端到Device端,再到中间的DWC3控制器,最后落到dts配置上。嗯,内容不少,但都是硬货。
3.1 整体框架:两条路径,一个核心
RK平台的USB驱动,说白了就是两条路径:一条走Host(主机)模式,一条走Gadget(设备)模式。但不管哪条路,都得经过DWC3这个核心控制器。
核心认知: DWC3是Synopsys的IP,RK把它集成到了SoC里。驱动框架分为三层——控制器层(DWC3)、接口层(Host/Gadget)、应用层(Class驱动)。
我画了一张框架图,你看完就明白了:
3.2 Host驱动栈:从usbcore到xhci
Host模式下,数据流是这样的:
- usbcore:USB核心层,管理总线、设备枚举、驱动匹配。
- hub驱动:处理端口状态变化,检测设备插拔。
- xhci-plat:DWC3在Host模式下注册为xHCI控制器,走标准xHCI协议。
- Class驱动:比如usb-storage、usb-net,负责具体功能。
我的经验: RK平台的Host驱动基本是主线内核的xHCI栈,改动不大。但有个坑——DWC3的xHCI实现里,有些quirks需要手动开启。比如RK3399的usb3.0端口,如果不加snps,dis-u2-freeclk-exists-quirk,高速设备可能不稳定。我踩过这个坑,排查了两天才找到原因。
3.3 Gadget驱动栈:从configfs到udc-core
Gadget模式稍微复杂一点。RK平台现在主流做法是用configfs来动态配置USB功能。流程如下:
- udc-core:Gadget核心层,管理UDC(USB Device Controller)的注册和回调。
- configfs:用户态通过/sys/kernel/config/usb_gadget/创建gadget实例。
- function驱动:比如f_mass_storage、f_ecm、f_serial,实现具体功能。
- composite层:把多个function组合成一个复合设备。
注意: 老SDK里还有legacy gadget驱动(比如g_ether.ko),但RK新内核(5.10+)已经全面转向configfs。如果你还在用旧方式,建议尽快迁移。我曾经帮客户把一个g_android的旧项目改成configfs,性能提升了15%。
3.4 DWC3控制器驱动:核心中的核心
DWC3驱动是RK USB的灵魂。它分为几个模块:
| 模块 | 文件 | 职责 |
|---|---|---|
| dwc3-core | drivers/usb/dwc3/core.c | 初始化硬件、解析dts、管理运行模式 |
| dwc3-host | drivers/usb/dwc3/host.c | 注册xHCI平台设备,处理Host相关中断 |
| dwc3-gadget | drivers/usb/dwc3/gadget.c | 实现UDC接口,处理端点传输 |
| dwc3-keystone | drivers/usb/dwc3/keystone.c | RK平台特有的glue层,处理GRF/CRU配置 |
这里有个关键点:dwc3-keystone是RK自己加的glue层。它负责配置SoC内部的GRF(通用寄存器文件)和CRU(时钟复位单元)。说白了,就是告诉硬件:PHY怎么接、时钟怎么给、复位怎么拉。
调试技巧: 如果你遇到USB不工作,先看dwc3-keystone的probe有没有成功。打印一下dwc3-keystone的dev_dbg,看看GRF寄存器写没写对。我遇到过好几次,都是因为GRF配置被其他驱动覆盖了。
3.5 RK特有dts配置节点解析
dts配置是RK USB开发的重头戏。我挑几个最关键的节点讲:
3.5.1 usb2-phy 与 usb3-phy
&usb2phy0 {
status = "okay";
};
&u2phy0_host {
status = "okay";
phy-supply = &vcc5v0_host;
};
&usbdrd3_0 {
status = "okay";
dr_mode = "otg"; // 可选: host, peripheral, otg
};
&usbdrd_dwc3_0 {
status = "okay";
dr_mode = "otg";
extcon = &usb2phy0;
};
这里要注意几个点:
- dr_mode:决定控制器角色。otg模式需要extcon来检测ID引脚。
- extcon:连接PHY的extcon驱动,用于检测USB ID和VBUS状态。
- phy-supply:给PHY供电的regulator,很多板子忘了配这个,导致PHY不工作。
3.5.2 DWC3核心参数
&usbdrd_dwc3_0 {
snps,dis_u2_susphy_quirk;
snps,dis_enblslpm_quirk;
snps,dis_u3_susphy_quirk;
snps,tx_de_emphasis_quirk = <1>;
snps,tx_de_emphasis = <6>;
};
这些quirk是干什么的?我解释一下:
| 属性 | 作用 |
|---|---|
| dis_u2_susphy_quirk | 关闭USB2.0的暂停PHY省电模式,避免某些设备唤醒失败 |
| dis_enblslpm_quirk | 禁用LPM(链路电源管理),解决部分设备兼容性问题 |
| dis_u3_susphy_quirk | 关闭USB3.0的暂停PHY省电,我遇到过Intel的USB3.0硬盘不识别,加上这个就好了 |
| tx_de_emphasis | 调整发送信号的去加重,改善信号完整性,高速PCB走线长的时候特别有用 |
避坑指南: 我曾经在一个RK3588项目上,USB3.0总是掉线。折腾了一周,最后发现是snps,tx_de_emphasis没配。RK3588的USB3.0 PHY对信号质量很敏感,默认值不够,必须根据PCB走线长度调整。嗯,这个参数不是随便抄的,得跟硬件工程师一起量一下眼图。
3.5.3 多端口配置
RK3588有3个USB控制器,dts里是这样组织的:
&usbdrd3_0 { // USB3.0 OTG
status = "okay";
};
&usbdrd3_1 { // USB3.0 Host
status = "okay";
dr_mode = "host";
};
&usbhost3_0 { // USB2.0 Host
status = "okay";
};
每个控制器都有自己的PHY和时钟域。调试时,建议一个一个来,先确保USB2.0正常,再搞USB3.0。
3.6 驱动加载顺序与调试入口
驱动加载顺序很重要,错了就起不来。RK平台典型的加载顺序:
- PHY驱动(usb2-phy / usb3-phy)
- dwc3-keystone glue层
- dwc3-core
- dwc3-host 或 dwc3-gadget(根据dr_mode)
- Class驱动
调试时,我习惯在dts里加status = "okay"后,先看dmesg | grep dwc3。如果看到dwc3 fe500000.usb: Failed to get usb3 phy,那就是PHY没配好。
我的调试三板斧:
- 第一斧:
lsusb -t看设备树拓扑,确认控制器有没有注册 - 第二斧:
cat /sys/kernel/debug/usb/devices看设备描述符 - 第三斧:
trace-cmd抓USB事件,分析枚举过程
3.7 小结:框架就是地图
RK的USB驱动框架,说白了就是一张地图。你知道了Host怎么走、Gadget怎么走、DWC3怎么配,dts怎么写,遇到问题就不会慌。我见过太多人,上来就改代码,结果越改越乱。其实先花半小时看看框架,比瞎改一天强。
嗯,这一章的内容就到这。记住:框架是骨架,dts是血肉,调试是灵魂。三样都拿住,RK USB开发你就入门了。