rk3588/kernel-6.1 USB 总线枚举、驱动匹配与数据传输分析
1. USB 子系统整体结构
rk3588/kernel-6.1 中 USB 主机侧代码主要位于:
drivers/usb/core/:USB core,总线、枚举、匹配、URB、hub、配置描述符解析;drivers/usb/host/:Host Controller Driver,例如 xHCI/EHCI/OHCI;drivers/usb/dwc3/:Synopsys DWC3 双角色控制器,RK3588 常见 USB3/DRD 控制器基础;drivers/usb/gadget/:USB device/gadget 侧框架;drivers/usb/class/:USB class 驱动;drivers/usb/storage/:U 盘/USB mass storage;drivers/usb/serial/:USB 串口;drivers/usb/typec/、drivers/usb/roles/:Type-C 与 dual-role 切换;drivers/phy/rockchip/:Rockchip USB2/USB3/Type-C PHY。
USB core 的 Makefile:
1 | usbcore-y := usb.o hub.o hcd.o urb.o message.o driver.o |
核心职责:
hub.c:端口变化检测、设备复位、地址分配、读取描述符、枚举;config.c:解析配置、接口、端点描述符;driver.c:USB bus 注册、驱动注册、id_table 匹配、probe/disconnect;urb.c:URB 提交前检查和通用入口;hcd.c:URB 到 Host Controller Driver 的桥接;message.c:同步 control/bulk 消息封装;usb.c:USB core 初始化、设备模型、接口辅助函数;endpoint.c/sysfs.c:端点和设备信息导出。
2. USB 主机侧分层模型
USB 主机侧从上到下可分为:
1 | USB 设备功能驱动 |
对 RK3588 来说,常见主机路径是:
1 | Rockchip USB PHY / Type-C / role switch |
drivers/usb/dwc3/core.c 中 dwc3_get_dr_mode()、dwc3_set_prtcap()、__dwc3_set_mode() 负责 DWC3 host/device/OTG 角色处理。drivers/usb/host/xhci-plat.c 负责 platform xHCI HCD 创建、DMA mask 设置、寄存器映射和 usb_add_hcd()。
3. USB 设备枚举流程
USB 枚举由 hub 负责。无论是 root hub 还是外接 hub,端口状态变化最终都会进入 drivers/usb/core/hub.c。
整体流程:
1 | 端口 connect/change 中断 |
3.1 端口连接处理:hub_port_connect()
hub_port_connect() 是新设备枚举的主入口之一。
它做的事情:
- 如果端口已有旧设备,先
usb_disconnect(); - 对端口信号做 debounce;
- 分配
struct usb_device; - 设置初始状态为
USB_STATE_POWERED; - 调用
choose_devnum()分配 USB 地址编号; - 调用
hub_port_init()复位设备并读取设备描述符; - 把设备挂到父 hub 的
port_dev->child; - 调用
usb_new_device()继续枚举和注册。
关键路径:
1 | hub_port_connect() |
3.2 地址分配:choose_devnum()
USB 总线上设备地址范围是 1-127。choose_devnum() 在 usb_bus.devmap 中找空闲地址:
1 | find_next_zero_bit(bus->devmap.devicemap, 128, bus->devnum_next) |
地址释放在 release_devnum() 中完成。
3.3 端口复位和设备描述符读取:hub_port_init()
hub_port_init() 是 USB 枚举中最复杂的阶段。
主要动作:
hub_port_reset()复位端口,识别设备速度;- 根据速度设置 ep0 max packet 初始值;
- 读取设备描述符前 8/64 字节,确认
bMaxPacketSize0; hub_set_address()发送标准SET_ADDRESS请求;- 再读取完整
usb_device_descriptor; - 处理 USB2/USB3 速度、quirk、延迟、链路状态等。
这个阶段设备从默认地址 0 切换到内核分配的地址,并进入 USB_STATE_ADDRESS。
3.4 读取配置与字符串:usb_enumerate_device()
usb_new_device() 会调用:
1 | usb_enumerate_device() |
usb_get_configuration() 会读取并解析:
- configuration descriptor;
- interface descriptor;
- endpoint descriptor;
- class/vendor-specific descriptor;
- SuperSpeed companion descriptor。
解析完成后形成内核对象:
1 | struct usb_device |
3.5 注册到设备模型:device_add()
usb_new_device() 最后调用:
1 | device_add(&udev->dev) |
此时 USB 设备对 sysfs、udev、driver core 可见。
后续 generic USB 设备驱动通常会选择配置,创建 interface device,再触发接口驱动匹配。
4. 配置、接口和端点
USB 设备结构不是“一个设备只对应一个驱动”,而是:
1 | Device |
Linux USB 驱动多数绑定在 struct usb_interface 上,而不是整个 struct usb_device 上。
例如复合设备:
- 一个 USB 声卡可能有 audio control interface 和 audio streaming interface;
- 一个 4G 模块可能同时暴露 serial、network、diagnostic interface;
- 一个 USB 摄像头可能有 video control 和 video streaming interface。
每个 interface 可以匹配不同驱动。
关键结构在 include/linux/usb.h:
struct usb_devicestruct usb_interfacestruct usb_host_interfacestruct usb_host_endpointstruct usb_driverstruct urb
5. USB 驱动注册
USB 接口驱动通过 struct usb_driver 描述:
1 | static struct usb_driver my_usb_driver = { |
module_usb_driver() 最终调用:
1 | usb_register_driver() |
usb_register_driver() 会设置:
1 | new_driver->drvwrap.driver.bus = &usb_bus_type |
也就是说,驱动真正注册到 Linux driver core,挂在 usb_bus_type 总线上。
6. USB 驱动匹配机制
6.1 id_table
USB 驱动通过 struct usb_device_id 表声明支持的设备或接口。
常见宏:
1 | USB_DEVICE(vendor, product) |
示例:drivers/usb/usb-skeleton.c
1 | static const struct usb_device_id skel_table[] = { |
MODULE_DEVICE_TABLE(usb, ...) 会导出模块别名,支持热插拔自动加载模块。
6.2 匹配入口:usb_device_match()
drivers/usb/core/driver.c 中 usb_device_match() 是 USB bus 的 .match 回调。
它区分两类对象:
struct usb_device:匹配usb_device_driver;struct usb_interface:匹配普通usb_driver。
接口驱动匹配路径:
1 | usb_device_match() |
usb_match_id() 会遍历驱动的 id_table,调用 usb_match_one_id() 对比:
- idVendor/idProduct;
- bDeviceClass/bDeviceSubClass/bDeviceProtocol;
- bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol;
- product revision 范围;
- match_flags。
6.3 probe 调用:usb_probe_interface()
匹配成功后,driver core 调用 usb_probe_interface()。
它做的事情:
- 检查设备和 interface 是否 authorized;
- 再次用动态 ID 或
id_table找匹配项; - runtime PM 唤醒设备;
- 设置 interface 状态为
USB_INTERFACE_BINDING; - 处理 autosuspend、LPM、altsetting;
- 调用具体驱动的
driver->probe(intf, id); - 成功后设置为
USB_INTERFACE_BOUND。
核心流程:
1 | usb_probe_interface() |
驱动自己的 probe() 通常会:
- 分配私有结构;
- 获取
usb_device和usb_interface; - 解析 endpoint;
- 分配 URB 和 buffer;
- 注册字符设备、input 设备、netdev、scsi host 等上层对象;
usb_set_intfdata()保存私有数据。
7. usb-skeleton.c 驱动示例
drivers/usb/usb-skeleton.c 是 USB 字符设备驱动模板。
它展示了完整接口驱动写法:
- 声明
usb_device_id:
1 | static const struct usb_device_id skel_table[] = { |
- 在
probe()中查找 bulk endpoint; - 分配 bulk in URB 和 buffer;
- 注册 USB 字符设备;
- 在
read()中提交 bulk in URB; - 在
write()中提交 bulk out URB; - 在
disconnect()中 kill URB、注销设备、处理并发关闭。
读路径:
1 | userspace read() |
写路径:
1 | userspace write() |
8. USB 数据传输类型
USB 有四种传输类型:
| 类型 | 用途 | Linux 常用接口 |
|---|---|---|
| Control | 枚举、标准请求、配置、少量控制命令 | usb_control_msg() / control URB |
| Bulk | 大量可靠数据,吞吐优先 | usb_bulk_msg() / bulk URB |
| Interrupt | 小数据、周期轮询、低延迟 | interrupt URB |
| Isochronous | 音视频实时流,带宽保证,不重传 | isoc URB |
端点 0 永远是 control endpoint。
其他 endpoint 来自配置描述符和接口 altsetting。
9. URB 数据传输模型
Linux USB 数据传输核心对象是 struct urb(USB Request Block)。
异步传输基本流程:
1 | usb_alloc_urb() |
URB 关键字段:
dev:目标usb_device;pipe:端点号、方向、传输类型;transfer_buffer:数据 buffer;transfer_buffer_length:请求长度;actual_length:实际传输长度;setup_packet:control 传输 setup 包;complete:完成回调;context:驱动私有上下文;status:完成状态;interval:interrupt/isoc 周期;iso_frame_desc[]:isoc 分包描述。
10. usb_submit_urb() 到 HCD 的路径
drivers/usb/core/urb.c 中 usb_submit_urb() 做通用检查:
- URB 和 complete 回调不能为空;
- 设备状态必须有效;
- 通过 pipe 找 endpoint;
- 检查 control setup packet;
- 检查 endpoint max packet;
- 检查 isoc packet 长度;
- 检查 SG 约束;
- 修正 interrupt/isoc interval;
- 调用
usb_hcd_submit_urb()。
路径:
1 | usb_submit_urb() |
drivers/usb/core/hcd.c 中 usb_hcd_submit_urb():
1 | usb_hcd_submit_urb() |
如果是 root hub,则走 rh_urb_enqueue() 模拟 root hub 响应。
普通设备则映射 DMA,并调用具体 HCD 的 urb_enqueue()。
11. xHCI 如何传输 URB
RK3588 主机侧常见是 xHCI。include/linux/usb/hcd.h 中 struct hc_driver 定义 HCD 操作:
1 | int (*urb_enqueue)(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); |
drivers/usb/host/xhci.c 的 xhci_hc_driver:
1 | .map_urb_for_dma = xhci_map_urb_for_dma |
xhci_urb_enqueue() 会根据 endpoint 类型进入不同队列函数:
1 | control -> xhci_queue_ctrl_tx() |
xHCI 硬件用 TRB(Transfer Request Block)描述传输:
1 | URB |
xhci-ring.c 文件开头描述了 xHCI ring 的生产者/消费者模型:
- software 往 command/endpoint ring 里放 TRB;
- hardware 消费 endpoint ring;
- hardware 往 event ring 里写 completion event;
- software 消费 event ring;
- doorbell 用来通知 hardware 有新 TRB。
12. 同步传输封装
USB core 提供同步接口,底层仍然使用 URB。
12.1 Control 传输
usb_control_msg() 位于 drivers/usb/core/message.c。
流程:
1 | usb_control_msg() |
control 传输常用于:
GET_DESCRIPTORSET_ADDRESSSET_CONFIGURATIONGET_STATUSCLEAR_FEATURE- class/vendor command。
USB 枚举本身大量使用 control transfer。
12.2 Bulk 同步传输
usb_bulk_msg() 同样位于 message.c。
流程:
1 | usb_bulk_msg() |
适合简单、低并发场景。
高性能驱动更常使用异步 URB 队列保持吞吐。
13. USB DMA 与 buffer
USB 主机控制器一般通过 DMA 访问 URB buffer。
驱动可以用普通 buffer,让 USB core/HCD 自动 DMA map:
1 | buf = kmalloc(size, GFP_KERNEL); |
也可以使用 coherent buffer 并设置 URB_NO_TRANSFER_DMA_MAP:
1 | buf = usb_alloc_coherent(udev, size, GFP_KERNEL, &dma); |
usb-skeleton.c 的写路径使用了 usb_alloc_coherent(),减少反复 map/unmap 成本。
注意:
- completion 回调可能在软中断上下文执行;
- callback 中不能做长时间睡眠操作;
- disconnect 中必须
usb_kill_urb()或usb_kill_anchored_urbs(); - 已提交 URB 的 buffer 在 complete 前不能释放;
usb_anchor常用于批量管理多个未完成 URB。
14. 断开、解绑与错误处理
设备拔出时路径:
1 | hub 发现 disconnect |
usb_unbind_interface() 会:
- 设置 interface 为
USB_INTERFACE_UNBINDING; - 终止接口上的 URB;
- 调用驱动
disconnect(); - 禁用 endpoint;
- 清理 runtime PM 状态。
驱动 disconnect() 要特别注意并发:
- 用户进程可能还持有 file descriptor;
- read/write 可能正在等待 completion;
- URB callback 可能正在或即将执行;
- 需要用 mutex、kref、anchor、waitqueue 保护生命周期。
usb-skeleton.c 使用:
kref管理对象生命周期;io_mutex防止 disconnect 与 read/write 并发;usb_anchor管理提交中的 URB;usb_kill_anchored_urbs()停止所有写请求。
15. RK3588 平台相关点
RK3588 USB 主机侧通常涉及:
DWC3 控制器
drivers/usb/dwc3/core.c- 支持 host/device/OTG/DRD;
- 通过
dr_mode、role switch、Type-C 状态决定角色; - host 模式下创建 xHCI 子设备。
xHCI platform HCD
drivers/usb/host/xhci-plat.c- 设置 DMA mask;
- 创建
usb_hcd; - 映射寄存器;
- 注册 USB2 roothub 和 USB3 roothub;
- 调用
usb_add_hcd()后 USB core 开始管理 root hub。
Rockchip PHY
drivers/phy/rockchip/phy-rockchip-inno-usb2.cdrivers/phy/rockchip/phy-rockchip-inno-usb3.cdrivers/phy/rockchip/phy-rockchip-typec.cdrivers/phy/rockchip/phy-rockchip-usbdp.c- 负责 USB2/USB3 物理层、Type-C/DP combo PHY 等。
Type-C / role switch
drivers/usb/typec/drivers/usb/roles/- 决定 USB-C 口是 host 还是 device。
因此,RK3588 上“插入 U 盘并枚举”的大致链路是:
1 | Type-C/USB PHY 检测连接 |
16. USB 数据传输示例:Bulk 读写
16.1 Bulk IN
设备到主机:
1 | usb_fill_bulk_urb(urb, udev, |
完成后:
1 | complete callback |
16.2 Bulk OUT
主机到设备:
1 | usb_fill_bulk_urb(urb, udev, |
高吞吐场景通常同时提交多个 URB,避免 endpoint 队列空闲。
17. USB 调试方法
常用日志:
1 | dmesg | grep -i usb |
常用 sysfs:
1 | /sys/bus/usb/devices/ |
常用工具:
lsusblsusb -tlsusb -vusbmontrace-cmd/ ftrace
重要 trace/debug 方向:
- 枚举失败:看
hub.c日志、device descriptor read、SET_ADDRESS、供电、PHY; - 匹配失败:看
idVendor/idProduct、interface class、MODULE_DEVICE_TABLE; - 数据失败:看 URB
status、endpoint halt、短包、timeout; - 高速不稳定:看 PHY、线缆、Type-C、供电、LPM、autosuspend;
- 吞吐不足:看 URB 队列深度、bulk max packet、xHCI event、DMA 映射。
18. 常见问题
设备能枚举但驱动不 probe
id_table不匹配;- 驱动匹配的是 interface class,但设备是 vendor-specific;
- 模块没有导出
MODULE_DEVICE_TABLE(usb, ...); - interface 已被其他驱动绑定。
读取 device descriptor 失败
- PHY/供电/线缆问题;
- 端口 reset 失败;
- 设备 firmware 异常;
- low/full/high/super speed 协商异常。
bulk 传输超时
- endpoint 地址或方向错误;
- 设备没有准备好;
- 没有处理 stall,需要
usb_clear_halt(); - disconnect 与 I/O 并发。
callback 后数据长度不对
- 检查
urb->actual_length; - IN 方向可能短包结束;
- OUT 方向可能需要
URB_ZERO_PACKET; - isoc 要逐个检查
iso_frame_desc[]。
- 检查
拔出设备后崩溃
- URB buffer 提前释放;
- callback 访问已释放私有结构;
- disconnect 没有 kill URB;
- file_operations 没有用 kref/mutex 保护。
19. 总结
USB 枚举、匹配和传输可以概括为三条主线:
- 枚举主线
1 | hub 端口变化 |
- 驱动匹配主线
1 | usb_register_driver() |
- 数据传输主线
1 | driver 构造 URB |
一句话概括:
USB core 负责发现设备、解析描述符并按 usb_device_id 绑定 interface 驱动;真正的数据收发由驱动提交 URB,USB core/HCD/xHCI 把 URB 转成硬件传输描述符并在完成后回调驱动。
正在加载留言…