1. USB OTG概念与驱动模型概览

做嵌入式Linux驱动开发的,几乎都绕不过USB OTG。我记得刚入行那会儿,领导扔给我一块板子让我把USB Host和Device功能跑起来,我心想这不就是插个U盘的事嘛。结果折腾了整整一周,发现不是驱动加载失败就是角色切换崩了。后来我才明白,要搞懂这些问题,得先弄清楚USB OTG到底是个什么东西,以及它的驱动模型长什么样。

1.1 什么是USB OTG?

USB OTG的全称是USB On-The-Go。说白了,它允许一个USB设备既能当Host(主机),也能当Device(从设备)。普通的USB设备角色是固定的——电脑永远是Host,鼠标永远是Device。但嵌入式设备不一样,它今天可能当个U盘给别人拷数据,明天又要插个游戏手柄自己玩。

我遇到过这样的项目:一款智能电视盒,用户插U盘时电视盒是Host,当通过USB线连电脑刷机时,电视盒又得变成Device。这就是典型的OTG场景。嗯,这里要注意,OTG不仅仅是切换角色那么简单,它还涉及硬件检测、电源管理、驱动绑定等一堆底层逻辑。

模式 角色 典型场景
Host模式 主机,供电方 插U盘、鼠标、键盘
Device模式 从设备,受电方 连电脑做存储/UVC摄像头
OTG模式 动态切换 ID引脚电平决定角色
小提示:别以为OTG就是简单地在Host和Device之间切来切去。实际硬件靠一个叫作ID引脚的线来识别角色。ID接地,就是Host;ID悬空,就是Device。有些芯片还支持软件强制设置模式,但这需要驱动配合。

1.2 USB OTG的驱动模型分层

Linux内核里的USB驱动不是一个大块,而是分层的。我习惯把这套模型理解为三层结构,每一层干自己那摊活,层与层之间通过回调函数和数据结构交互。你看这个图,基本就清楚了:

USB OTG 驱动模型三层架构 UDC 控制器驱动 基于Gadget API实现设备功能 USB 核心层(Core) 协议解析 / 枚举 / 角色管理 HCD / 底层硬件接口 控制器寄存器 / DMA / 中断处理 各层职责清晰,故障隔离方便 UDC驱动异常查Gadget层;角色切换失败先查Core层;硬件无响应查HCD层

1.3 各层具体是干什么的?

先说说最底层的HCD(Host Controller Driver)。这一层直接跟硬件寄存器打交道,负责处理中断、DMA传输、端点调度。我调过一款某厂的USB控制器,它的中断处理函数里有个竞态条件,导致双核CPU上偶尔丢包——查了三天才定位到。HCD层出问题,通常表现为设备插上去没反应,或者传输数据老是CRC错误。

再往上是USB核心层。这一层不直接操作硬件,而是负责解析USB协议、设备枚举、分配地址、管理配置描述符。它还负责OTG的角色协商——比如ID引脚变化了,核心层要通知上层的Gadget驱动切换模式。核心层如果出毛病,最常见的现象就是设备能识别但枚举失败,或者角色切换卡住。

最上层是UDC(USB Device Controller)驱动。这一层实现具体的设备功能,比如模拟U盘、模拟串口、模拟摄像头。我习惯叫它Gadget驱动。写Gadget驱动其实就是用Linux提供的Gadget API,实现USB描述符和请求处理。如果你写了个虚拟串口驱动,插上电脑发现电脑不识别,十有八九是描述符写错了。

关键点:OTG驱动的加载过程依赖于这三个层次依次就绪。底层HCD必须先成功注册到USB子系统,然后核心层才能收到硬件中断并启动枚举。最后UDC驱动才能被绑定并创建Gadget实例。任何一个环节卡住,整个OTG功能就挂了。

1.4 与驱动加载相关的核心器件

在实际的嵌入式板子上,USB OTG还依赖一些外围器件。我列个表格,你们看看每个器件在驱动加载中扮演什么角色:

器件/概念 作用 驱动失败的表现
USB PHY 物理层收发器,处理电气信号 驱动probe失败,dmesg报phy相关错误
ID检测引脚 GPIO中断触发的角色切换 角色无法自动切换
VBUS供电 Host模式下给外设供电 Host模式外设无法上电
DMA控制器 高速数据传输 大包传输时数据错乱
时钟/电源域 USB模块的时钟与供电 驱动超时或无响应

我曾经因为一个PHY供电没配好,折腾了整整两天。dmesg里只看到一条“usb_phy_generic_probe: failed to get phy”的错误,反复检查设备树才发现是regulator节点名字写错了。你看看,很多时候OTG驱动加载问题其实不是驱动本身有bug,而是硬件依赖没打通。

1.5 OTG驱动加载的典型流程

我现在带你过一遍OTG驱动在系统启动时的加载流程,这样后续排查问题时你心里就有谱了:

  1. PHY初始化:内核先检测USB PHY的设备树节点,调用phy驱动probe,配置时钟和供电。
  2. UDC控制器注册:底层控制器的驱动(如dwc3、musb等)注册到USB核心子系统。
  3. 核心层启动:USB核心层检测到新注册的控制器,启动它。
  4. 角色检测:读取ID引脚电平或软件配置,确定初始角色是Host还是Device。
  5. Gadget驱动绑定:如果是Device模式,UDC驱动绑定Gadget功能驱动(如g_serial、g_mass_storage)。
  6. VBUS供电(Host):如果是Host模式,打开VBUS供电,等待外设插入。
避坑指南:我曾经遇到过一个问题,内核启动顺序是乱的——PHY驱动还没初始化,UDC控制器就先跑起来了。结果当然是一堆"failed to initialize phy"的错误。解决办法是在设备树里用depends-on属性强行设置依赖顺序。这一点很多新手容易忽略。

1.6 本章小结

USB OTG的驱动模型并不复杂,难的是每一层之间依赖关系的梳理,以及硬件配置的精准。记住一个原则:排查OTG驱动加载故障时,从底层硬件往上查,而不是从上往下跳。先确认PHY有电有时钟,再确认控制器能注册,最后才去翻Gadget驱动的代码。