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驱动)。

我画了一张框架图,你看完就明白了:

RK平台USB驱动框架总览 Host 端 Class 驱动 usb-storage / usb-net / usb-hid / xhci-plat Gadget 端 Class 驱动 g_ether / g_mass_storage / g_serial / g_android Host 核心栈 usbcore / hub / usb-stack Gadget 核心栈 udc-core / configfs / gadget-stack DWC3 控制器驱动 dwc3-core / dwc3-host / dwc3-gadget / dwc3-keystone RK SoC USB 硬件(PHY / GRF / CRU) Host 和 Gadget 共享同一套 DWC3 硬件,通过 dr_mode 切换角色

3.2 Host驱动栈:从usbcore到xhci

Host模式下,数据流是这样的:

  1. usbcore:USB核心层,管理总线、设备枚举、驱动匹配。
  2. hub驱动:处理端口状态变化,检测设备插拔。
  3. xhci-plat:DWC3在Host模式下注册为xHCI控制器,走标准xHCI协议。
  4. 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平台典型的加载顺序:

  1. PHY驱动(usb2-phy / usb3-phy)
  2. dwc3-keystone glue层
  3. dwc3-core
  4. dwc3-host 或 dwc3-gadget(根据dr_mode)
  5. 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开发你就入门了。