4. Android USB系统框架概览:从App到内核的完整链路

Android USB系统,说白了就是一条从App到物理USB接口的管道。我刚开始接触Android USB时,总以为插上就能用,结果被各种权限、驱动、内核配置折磨得够呛。今天就带你看看这条管道到底分几层,每层又干些什么。

4.1 总体架构:四层结构

我个人习惯把Android USB框架分成四层来理解。你想想看,一个数据请求从抖音App发出来,要经过多少关卡才能送到USB口上?

层级 所在空间 核心组件 典型文件/模块
应用层 用户空间 App / SDK API android.hardware.usb.UsbManager
框架层 用户空间 USB Service / System Server UsbService.java, UsbDeviceManager.java
原生层 用户空间 HAL / libusb / usbd android.hardware.usb@1.0
内核层 内核空间 USB Core / Gadget / HCD drivers/usb/, gadget/

关键认识:Android USB主机侧(Host)和设备侧(Gadget)在内核层共用USB Core,但分支完全不同。主机侧走HCD(主机控制器驱动),设备侧走Gadget驱动栈。这两条路千万别搞混。

一个小技巧:我在项目里调试USB时,常常先用 lsusb 确认设备是否被内核识别,再用 dumpsys usb 看框架层状态。这两步能快速定位问题到底出在哪一层。

4.2 从上到下的数据流:以adb推送文件为例

假设你用 adb push 把一个文件从电脑推到手机里。数据是怎么走的?我们跟一遍。

  1. 发起端(电脑):adb客户端通过USB bulk端点发送数据。
  2. 手机端内核层:USB Gadget驱动收到数据,交给FunctionFS。
  3. 手机端原生层:adbd 守护进程从 /dev/usb-ffs/adb/ep0 读取数据。
  4. 手机端框架层:adbd调用 vold 完成文件写入操作。

每一步都涉及权限检查、端点调度和缓冲区操作。我记得有一次在QCOM平台上调试,发现vold在处理大文件时反复超时,最后定位到是Gadget驱动的中断传输缓冲区太小。嗯,这里要注意USB批量传输的单次最大包长——高速模式下是512字节,超高速下是1024字节。

4.3 USB Gadget框架——Android设备侧的核心

既然是OTG课程,Gadget框架是重中之重。Android从3.4内核开始引入USB Gadget框架,后来迁移到了ConfigFS。我个人更喜欢ConfigFS的方式,因为用户空间可以直接配置USB功能,不用改死板的内核配置。

Gadget驱动栈有几张“功能卡”:

  • f_mass_storage.ko:U盘模式,最常用也最简单。
  • f_ecm.ko / f_rndis.ko:网络共享(USB绑定)。
  • f_mtp.ko / f_ptp.ko:媒体传输协议。
  • f_accessory.ko:Android附件模式,用于外设扩展。
  • f_uvc.ko:USB视频设备类,4.4以后才有的。

曾经踩过的坑:我在一个项目里同时启用了MTP和RNDIS,结果MTP传输时网络经常卡顿。后来发现是Gadget的UDC驱动端点在争抢带宽——批量端点优先级比中断端点低,但MTP走批量,RNDIS走中断,优先级低的MTP被打断了。最终的解决是给MTP分配独立端点,并调整调度策略。

4.4 内核驱动层深探

内核层其实是整个系统最壮实的那根柱子。从功能上可以拆成三块:

  • USB Core:通用的USB栈,包括设备枚举、控制传输、urb管理等。主机和设备侧都靠它。
  • UDC(USB Device Controller)驱动:芯片厂商提供的硬件抽象层。高通叫 dwc3,瑞芯微用 dwc2,有些MTK用 mtu3。它们负责把内核的USB请求翻译成硬件寄存器操作。
  • Gadget功能驱动:具体实现USB功能(MTP、ADB等)的内核模块。

数据流的实际路径是这样的:

应用层 (MTP App)
    ↓ 通过Binder调用
框架层 (UsbDeviceManager)
    ↓ 通过HAL
原生层 (libmtp.so → /dev/mtp_usb)
    ↓ 通过ConfigFS
内核层 (f_mtp.ko → gadget.ko → udc-core.ko)
    ↓ 通过DMA/寄存器
硬件层 (DWC3控制器 → USB PHY → 物理端口)

你可能会问,为什么Android要把USB搞得这么复杂?

说白了,为了安全和灵活。用户空间的App不能直接碰硬件;框架层负责权限管理和策略;内核层保证效率和稳定。每一层都加了一道锁,但也多了一个可能出bug的地方。

4.5 一张图看清全貌

下面的结构图把上面讲的四层框架和关键组件画出来了。重点关注每个层之间的接口——它们往往是调试的突破口。

Android USB OTG系统架构总览(以设备侧为例) 应用层(用户空间) 第三方App · MTP App · adb shell · UVC Camera App 通过android.hardware.usb API绑定服务 框架层(用户空间) UsbService · UsbDeviceManager · UsbHostManager 权限管理 · 设备策略 · 配置切换 原生层(用户空间) libusb · libmtp · adbd · vold ConfigFS配置 · HAL接口 · devfs节点读写 内核层(内核空间) Gadget功能驱动 f_mtp.ko · f_adb.ko f_mass_storage.ko f_uvc.ko · f_rndis.ko USB Core 设备枚举 · urb管理 端点调度 · 权限检查 描述符解析 · 电源管理 UDC驱动层 dwc3 · dwc2 · mtu3 DMA传输 · 中断处理 端点寄存器操作 硬件层 USB控制器 · PHY · 端口 · Hub 主机侧(Host)时,内核层走HCD(ohci-hcd · ehci-hcd · xhci-hcd) 设备侧(Gadget)时,内核层走UDC + Gadget驱动,两条路径在USB Core汇合

4.6 主机侧 vs 设备侧——关键区别

这个课既然叫OTG,主机侧和设备侧都得讲。两者的内核栈长得不一样:

对比项 主机侧(Host) 设备侧(Gadget/Peripheral)
上层接口 usb_device + 设备类驱动 usb_gadget + 功能驱动
核心模块 OHCI/EHCI/XHCI UDC(如dwc3.ko)+ Gadget层
电源管理 由Hub检测挂起/唤醒 由外接VBus变化触发
角色切换 仅从USB Core枚举 通过OTG ID引脚或软件切换
典型应用 U盘、鼠标、键盘、摄像头 MTP、ADB、RNDIS、UVC

我在产品开发中经常遇到一个问题——设备插上PC后被识别为“未知USB设备”,那时我总是先看内核日志有没有枚举成功,再看Gadget功能是否正确加载。如果都没有问题,基本就是ConfigFS的符号链接没配好。

快速回忆:host/gadget模式切换靠的是 androidboot.mode 启动参数或者 sys/kernel/config/usb_gadget/ 下的配置。我自己习惯在init.rc里提前配好,避免adb在系统启动过程中断开重连。

4.7 小结:学习这张图的实用方法

如果你刚开始啃USB驱动,我给你三个建议:

  • 先从内核层入手:打开 /sys/kernel/debug/usb/devices 看看你的设备有没有被内核认出来。这是最底层的证据。
  • 再追框架层日志:用 logcat -b all | grep -i usb 抓取UsbService的日志,看看权限管理和策略切换是否正常。
  • 最后才翻应用代码:从UsbManager API到HAL的调用链很容易看懂,但前提是底层没问题。

这张四层架构图你会反复用到。每当我遇到USB相关bug时,第一件事就是打开这张图,问自己:问题出在哪一层?然后逐层排查。掌握了这个思维,比记住一百个函数名都管用。