第 4 章:Linux USB 驱动框架简介
很多人在做 USB OTG 调试时,一遇到加载失败就慌了。其实,当你理解了 Linux USB 驱动框架,很多问题自己就能找到头绪。我当年刚接触 USB 驱动时,也是被一堆术语搞得晕头转向——URB、端点、接口、配置……后来亲自调了一次驱动,才明白这框架设计的妙处。
说白了,Linux USB 驱动分三层:上层的 设备驱动(比如 U 盘、鼠标、网卡)、中间的 USB 核心(负责管理总线、设备和驱动的匹配)、以及底层的 主机控制器驱动(HCD,直接跟硬件打交道)。你想想看,OTG 驱动加载失败,绝大多数问题都出在这三层的接口上——要么设备驱动注册不对,要么核心没找到匹配的驱动,要么 HCD 没识别到设备。
4.1 整体分层架构
我们先看一张图,这图我每次讲课时都会画一遍,基本能把框架说清楚。
这张图里,最上面是用户空间,比如你用 lsusb 看到的设备信息。中间的内核空间才是我们开发的重点。我个人习惯把 USB 驱动分成三个关注点:
- 设备驱动层:关注 USB 设备的特定功能(比如 U 盘做存储、鼠标做 HID)。
- USB 核心层:关心总线上的设备枚举、驱动匹配、URB 管理。
- HCD 层:处理主机控制器寄存器,产生 USB 帧。
避坑提示:OTG 设备驱动加载失败,我遇到过好几次是因为 HCD 层没正确初始化。先检查 dmesg 里有没有 "hub 1-0:1.0: USB hub found" 这行——如果没有,说明 HCD 都没跑起来,后面的事情就不用看了。
4.2 核心数据结构
框架里最绕不开的是几个数据结构:usb_device、usb_interface、usb_host、usb_driver。我列个表格,方便你对比记忆。
| 结构体 | 作用 | 对应概念 | 加载失败常见原因 |
|---|---|---|---|
usb_device |
代表一个 USB 设备(物理或逻辑) | 设备对象 | 设备描述符解析失败,厂商ID/产品ID不匹配 |
usb_interface |
设备的一个接口(functional unit) | 接口对象 | 接口描述符读取异常,备选设置错误 |
usb_driver |
驱动结构体,包含 probe、disconnect、id_table | 驱动对象 | id_table 未正确注册,probe 函数返回负数 |
urb |
USB 请求块,所有数据传输的载体 | 传输单元 | URB 提交失败,端点类型不匹配,DMA 缓冲区问题 |
为什么强调这些?我记得有一次调一个 OTG 网卡驱动,probe 函数明明被调用了,但设备就是报错。最后发现是 usb_interface 的 cur_altsetting 指向了错误的备选设置,导致端点描述符和实际硬件不匹配。嗯,这种问题光靠看 dmesg 根本看不出来,得自己加打印。
4.3 驱动匹配流程
当设备插入时,USB 核心会执行以下流程:
- HCD 上报设备接入:主机控制器检测到端口状态变化,调用
hub_events。 - 设备枚举:复位设备,获取设备描述符、配置描述符、接口描述符、端点描述符。
- 分配
usb_device:将枚举结果填入结构体,注册到 USB 总线。 - 驱动匹配:遍历已注册的
usb_driver,比对id_table中的 VID/PID 或 class/subclass/protocol。 - 调用
probe:匹配成功则调用驱动的 probe 函数,进行初始化。 - 接口绑定:如果设备有多个接口,每个接口会独立匹配驱动(比如 OTG 设备可能有 CDC 控制接口和数据接口)。
关键点:驱动加载失败,90% 发生在第 4 步(匹配不到)或第 5 步(probe 失败)。你只需要在 dmesg 里搜 "new USB device found" 和 "driver probe" 就能定位问题。
4.4 URB 与端点通信
有了设备和驱动,数据怎么传?靠 URB。URB 是 USB 驱动里的核心传输载体。我通常会关注它的这几个字段:
struct urb {
struct usb_device *dev; /* 目标设备 */
unsigned int pipe; /* 管道(端点方向+类型) */
int status; /* 传输状态 */
unsigned int transfer_flags; /* 比如 URB_NO_TRANSFER_DMA_MAP */
void *transfer_buffer; /* 数据缓冲区 */
int transfer_buffer_length; /* 缓冲区长度 */
int actual_length; /* 实际传输长度 */
int interval; /* 中断/等时传输的间隔 */
/* ... 还有很多 */
};
URB 的 status 字段是调试故障的关键。我遇到过几次 status = -EPIPE(端点停止),原因是端点没有正确设置 halt 清除。这问题在 USB 存储设备上尤其常见。
注意:提交 URB 时必须保证 transfer_buffer 的 DMA 一致性。如果你用栈变量或普通 kmalloc 内存做 DMA 缓冲区,在某些架构(比如 ARM)上会直接崩溃。我踩过这个坑,后来养成了用 usb_alloc_coherent 的习惯。
4.5 USB 驱动模块组织
实际开发中,驱动通常编译成一个 .ko 文件。模块加载时会调用 usb_register 把 usb_driver 注册到核心。我建议你在模块入口加一个打印,确认加载顺序:
static int __init my_driver_init(void)
{
int ret;
pr_info("My USB OTG driver initializing...\n");
ret = usb_register(&my_usb_driver);
if (ret)
pr_err("usb_register failed: %d\n", ret);
return ret;
}
module_init(my_driver_init);
加载失败时,首先确认 modprobe/sys/bus/usb/drivers 目录下有没有你的驱动名字。如果没有,说明注册就没成功。我曾经帮一个同事排查问题,他的驱动一直不加载,最后发现是 module_init 和 module_exit 宏写错了——少了一个分号,编译过去但运行时符号没导出,真是让人哭笑不得。
4.6 常见加载失败模式
基于上面的框架,我把常见失败原因归纳成一张清单,你可以对照排查:
| 现象 | 可能出错层 | 排查点 |
|---|---|---|
| dmesg 无任何 USB 设备信息 | HCD / 硬件 | 检查 USB 控制器是否使能,时钟、电源、中断 |
| 设备枚举失败(重连或超时) | USB 核心 | 描述符解析出错,复位时序问题,电压不足 |
| 驱动不 probe | USB 核心 → 驱动层 | id_table 匹配规则,驱动是否注册到正确总线 |
| probe 返回负数 | 设备驱动层 | 资源申请失败(内存、GPIO、中断),端点检查失败 |
| 传输时 status 为 -EPROTO 或 -EILSEQ | HCD / 设备驱动层 | 端点管道配置错误,USB 信号完整性问题 |
速查小技巧:当你不确定是驱动还是核心的问题时,可以用 cat /sys/kernel/debug/usb/devices 查看设备树。如果设备在 debugfs 里出现了,说明枚举和核心没问题,问题肯定在驱动 probe 或 URB 提交上。