USB 驱动的核心作用,是让 Linux 内核能够识别、管理并与 USB 设备通信。本文以 i.MX6ULL 平台上的 RTL8732 USB 设备为例,从USB硬件设计、 USB 设备接入、通信过程、描述符解析以及枚举流程、RTL8732 USB 设备 运行流程几个方面,逐步分析 Linux USB 驱动的基本工作原理。
目录
一、USB硬件设计

USB 接口通常包含 4 个基本引脚,分别用于供电、数据传输和信号参考:
Vcc :供电,一般是 USB VBUS 5V,板载模块也可能是 3.3V
D+ :USB 差分数据正线
D- :USB 差分数据负线
GND :地线,供电回路和信号参考
二、USB 设备接入

1、当没有 USB 设备接入时,主机端的 D+ 和 D- 都被下拉到 GND,因此两根线都是低电平,主机判断为“无设备接入”。
2、当接入低速设备时,从机会将 D- 通过 1.5 kΩ 电阻上拉到 3.3V,因此主机检测到 D+ 低、D- 高,判断为低速设备接入。
3、当接入高速设备时,设备初始阶段通常先通过 D+ 上拉到 3.3V,让主机检测到 D+ 高、D- 低,先按全速设备接入,随后再通过高速握手切换到 High-Speed 模式。
因此,D+ 或 D- 的上拉主要用于设备接入检测和速度判断。真正传输数据时,由 USB PHY 主动驱动 D+ 和 D-,完成数字信号与 USB 差分电气信号之间的转换。
三、通信原理
USB 主机在访问 USB 设备时,并不是直接通过物理连接线来区分设备,而是通过枚举过程中(使用address =0 ep0进行枚举)分配的设备地址来区分不同的 USB 从机。每个 USB 设备接入主机后,主机会为它分配一个唯一的 Address。后续主机要访问某个设备时,就会在 USB 通信包中带上对应的设备地址,只有地址匹配的从机才会响应。

但是,一个 USB 设备内部可能包含多个功能,例如键盘、鼠标、串口、网卡等。因此,仅靠 Address 只能找到具体的 USB 设备,还不能区分设备内部的具体功能。USB 使用 Endpoint 来表示设备内部的通信通道,主机通过 “Address + Endpoint” 的方式访问指定设备的指定功能。例如,Address 用来找到 USB WiFi 设备,Endpoint IN 用来接收 WiFi 数据,Endpoint OUT 用来发送 WiFi 数据
一次 USB 通信通常由 Token、Data 和 Handshake 三个过程组成。Token 包由主机发送,用来说明本次通信要访问哪个设备地址、哪个端点,以及是读取数据还是发送数据。Data 包是真正传输的数据内容,例如设备描述符、鼠标移动数据、U 盘文件数据或 WiFi 网络数据。

最后是 Handshake 包,用来表示本次通信的结果。如果数据接收成功,接收方会返回 ACK;如果设备暂时没有数据或没有准备好,会返回 NAK;如果请求不支持或端点异常,则可能返回 STALL。简单来说,Address + Endpoint 决定“访问谁、访问哪个通道”,Token + Data + Handshake 决定“一次 USB 数据通信如何完成”。
四、描述符解析
① 设备描述符层次结构
USB 主机在识别 USB 设备时,并不是一开始就知道它是鼠标、键盘、U 盘还是 WiFi 设备,而是通过读取设备提供的描述符集合来逐步认识从机。描述符集合可以理解为 USB 设备的“自我介绍”,里面记录了设备类型、厂商信息、产品信息、配置数量、接口功能以及端点通信方式等内容。

USB 描述符具有一定的层次结构。一个 USB 设备有且只有一个设备描述符,设备描述符下面可以包含一个或多个配置描述符;每个配置描述符下面又可以包含一个或多个接口描述符;每个接口描述符下面再包含一个或多个端点描述符。简单来说,设备描述符说明“我是谁”,配置描述符说明“我有哪些工作配置”,接口描述符说明“我有哪些功能”,端点描述符说明“这些功能通过哪些通道收发数据”。

其中,设备描述符主要包含 VID、PID、USB 协议版本、EP0 最大包长度以及配置数量等信息,主机可以通过这些信息初步判断设备身份。配置描述符用于描述某个配置集合的整体属性,例如接口数量、最大供电电流、是否支持远程唤醒等。接口描述符用于描述具体功能,例如 HID 键盘、鼠标、CDC 串口或 USB 网卡等。端点描述符则进一步说明该接口使用哪些端点、传输方向是 IN 还是 OUT、传输类型是 Bulk、Interrupt、Isochronous 还是 Control。
② 枚举设备
USB 枚举就是主机识别 USB 设备并让它进入可用状态的过程。设备刚插入时还没有正式地址,只能临时使用 地址 0 + EP0 控制端点 和主机通信。主机会先复位设备,然后读取设备描述符,获取设备的基本信息,比如 VID、PID、USB 版本、EP0 最大包长度等,接着通过 Set Address 给设备分配一个唯一地址。
分配地址后,主机会继续读取完整的设备描述符、配置描述符、接口描述符、端点描述符以及字符串描述符。通过这些描述符,主机就能知道这个 USB 设备是什么类型、有几个功能接口、每个接口通过哪些端点通信,以及使用控制传输、中断传输、批量传输还是等时传输等通信方式。例如 U 盘会暴露 Mass Storage 接口,鼠标键盘会暴露 HID 接口。
在 Linux 内核中,主机读取到这些描述符后,并不是只看一遍就结束,而是会把这些信息解析并保存到 USB Core 维护的数据结构中。大致可以理解为:
struct usb_device
struct usb_host_config *config;
struct usb_interface *interface;
struct usb_host_endpoint *endpoint;
其中,struct usb_device 表示一个 USB 设备;struct usb_host_config 表示这个设备支持的配置;struct usb_interface 表示配置下面的接口;struct usb_host_endpoint 表示接口下面的端点。也就是说,USB 设备描述符、配置描述符、接口描述符、端点描述符最终会被 Linux 内核解析成这些结构体,方便后续选择配置、匹配驱动和进行数据传输。
最后,主机会通过 EP0 发送 Set Configuration 标准请求给 USB 设备。这个请求不是把完整的配置内容重新发送给设备,而是告诉设备:“我选择你已经声明的某一个配置,现在请启用它。”设备收到 Set Configuration 后,会在内部启动对应配置下的接口和端点,使这些端点真正进入可通信状态。比如 U 盘在被设置配置后,Bulk IN 和 Bulk OUT 端点才会正式启用,随后系统才能通过 usb-storage 驱动进行读写操作。

设备配置启用完成后,Linux 内核会根据 USB 枚举阶段解析出来的信息进行驱动匹配。这些信息会保存在 USB Core 维护的 struct usb_device、struct usb_interface 等结构体中,例如 VID/PID、设备类、接口类、协议类型等。内核会根据这些信息选择合适的驱动,驱动加载完成后,主机就可以按照端点描述符规定的通信方式,与 USB 设备进行正常的数据传输。
五、usb框架
一、先记住一条总主线
设备树 usbotg 节点
↓
i.MX 平台适配层:ci_hdrc_imx_data
↓
平台配置包:ci_hdrc_platform_data
↓
通用 ChipIdea 核心:ci_hdrc
↓
角色层:ci_role_driver
↓
Host 角色启动:host_start()
↓
主机控制器实例:usb_hcd
↓
主机硬件 ops:hc_driver
↓
逻辑总线:usb_bus
↓
USB 设备:usb_device
↓
EP0 枚举:GET_DESCRIPTOR / SET_ADDRESS / SET_CONFIGURATION
↓
描述符对象:config / interface / endpoint
↓
usb_interface 匹配 usb_driver
↓
功能驱动通过 URB 收发数据
一句话总结:
i.MX 平台层准备硬件资源;
ChipIdea core 管理控制器和角色;
Host 角色创建 HCD 并接入 USB Core;
USB Core 管理总线、设备、枚举和驱动匹配;
功能驱动通过 URB 让 EHCI 硬件传输数据。
二、第一层:i.MX 平台硬件适配层
主要文件:
drivers/usb/chipidea/ci_hdrc_imx.c
drivers/usb/chipidea/usbmisc_imx.c
arch/arm/boot/dts/imx6ull.dtsi
arch/arm/boot/dts/100ask_imx6ull-14x14.dts
核心结构体:
struct ci_hdrc_imx_data {
struct usb_phy *phy; // USB PHY
struct platform_device *ci_pdev; // 通用 ci_hdrc 子设备
struct clk *clk; // USB 控制器时钟
struct imx_usbmisc_data *usbmisc_data; // USBMISC 端口数据
bool supports_runtime_pm; // Runtime PM 能力
bool in_lpm; // 当前低功耗状态
struct regmap *anatop; // ANATOP 寄存器接口
struct pinctrl *pinctrl; // 平台引脚状态
const struct ci_hdrc_imx_platform_flag *data;
/* 其他时钟、HSIC 和 PM QoS 成员 */
};
ci_hdrc_imx_data 的作用:
1. 保存一套 i.MX USB 控制器的平台资源。
2. 初始化时钟、PHY、USBMISC、ANATOP。
3. 在 suspend/resume/remove 中继续使用这些资源。
4. 保存通用 ci_hdrc 子设备指针 data->ci_pdev。
三、ci_hdrc_imx_probe() 的作用
ci_hdrc_imx_probe(pdev) // i.MX 平台 USB probe 入口
│
├─ of_match_device() // 匹配 fsl,imx6ul-usb
│ └─ 得到 imx6ul_usb_data 平台 flags
│
├─ devm_kzalloc(ci_hdrc_imx_data) // 申请 i.MX 平台私有状态
├─ platform_set_drvdata(pdev, data) // 将 data 挂到父 platform_device
│
├─ usbmisc_get_init_data() // 解析 fsl,usbmisc 和端口 index
├─ imx_get_clks() // 获取 USB 时钟
├─ imx_prepare_enable_clks() // 打开 USB 时钟
├─ devm_usb_get_phy_by_phandle() // 获取 usbphy1 或 usbphy2
├─ syscon_regmap_lookup_by_phandle() // 获取 ANATOP regmap
├─ imx_usbmisc_init() // 初始化 i.MX USBMISC 寄存器
│
├─ 填充 ci_hdrc_platform_data pdata // 整理通用 core 需要的配置
│
└─ ci_hdrc_add_device(&pdata) // 创建通用 ci_hdrc 子设备
├─ platform_device_alloc("ci_hdrc") // 创建软件子设备
├─ platform_device_add_resources() // 复制 HDRC MEM 和 IRQ 资源
├─ platform_device_add_data() // 复制 pdata
└─ platform_device_add() // 注册并触发 ci_hdrc_probe
四、ci_hdrc_imx_data 和 pdata 的区别
ci_hdrc_imx_data:
- 是父 i.MX 平台驱动的运行状态;
- 保存在父 platform_device 的 drvdata;
- 父驱动用它管理时钟、PHY、USBMISC 和 PM。
ci_hdrc_platform_data pdata:
- 是传给通用 ChipIdea 驱动的配置包;
- 包含设备树解析结果、SoC flags、PHY 指针和回调;
- 经 platform_device_add_data() 复制给 ci_pdev;
- 最后由 ci->platdata 指向。
pdata 不只是设备树信息,它还包含:
- pdata.flags:SoC 兼容性和 quirk;
- pdata.usb_phy = data->phy:共享 PHY 指针;
- pdata.notify_event = ci_hdrc_imx_notify_event:反向平台回调;
- pdata.dr_mode:Host/Peripheral/OTG 模式;
- pdata.reg_vbus:VBUS regulator;
- AHB/TX/RX burst 配置。
正确关系:
父 i.MX platform_device
└─ drvdata = ci_hdrc_imx_data
└─ data->ci_pdev
└─ 子 platform_device("ci_hdrc")
├─ platform_data = ci_hdrc_platform_data 副本
└─ drvdata = ci_hdrc
注意:ci_pdev 不是一块新硬件,而是同一套 USB 控制器的通用软件子设备。
五、第二层:通用 ChipIdea 核心
主要文件:
drivers/usb/chipidea/core.c
drivers/usb/chipidea/ci.h
核心结构体:
struct ci_hdrc {
struct device *dev; // ci_hdrc 子设备
spinlock_t lock;
struct hw_bank hw_bank; // HDRC 寄存器映射
int irq; // HDRC 中断号
struct ci_role_driver *roles[CI_ROLE_END]; // Host/Gadget 角色 ops
enum ci_role role; // 当前角色
bool is_otg;
struct ci_hdrc_platform_data *platdata; // 平台配置
struct phy *phy;
struct usb_phy *usb_phy;
struct usb_hcd *hcd; // Host 模式 HCD
struct usb_gadget gadget; // Gadget 模式对象
/* OTG、端点、PM 和工作队列成员 */
};
ci_hdrc 的作用:
- 表示一套通用 ChipIdea HDRC 控制器实例;
- 保存控制器寄存器布局、IRQ 和 PHY;
- 保存 Host/Gadget 角色函数表;
- 保存当前运行角色;
- Host 时保存 ci->hcd;
- Gadget 时保存 gadget/UDC 状态。
它是“ChipIdea 控制器的软件总控制块”。
六、ci_hdrc_probe() 的核心流程
ci_hdrc_probe(pdev) // 通用 ChipIdea probe
│
├─ dev_get_platdata() // 获取 pdata 副本
├─ platform_get_resource(MEM) // 获取 HDRC 寄存器资源
├─ devm_ioremap_resource() // 映射寄存器
├─ devm_kzalloc(ci_hdrc) // 创建控制器中心对象
│
├─ ci->platdata = dev_get_platdata(dev) // 指向 pdata,不是把 pdata 嵌入 ci
│
├─ hw_device_init(ci, base) // 初始化 HDRC 寄存器布局
│ ├─ 定位 Capability 寄存器
│ ├─ 定位 Operational 寄存器
│ ├─ 读取端点数量和硬件版本
│ ├─ 退出低功耗
│ ├─ 关闭中断
│ └─ 清除遗留中断状态
│
├─ ci_usb_phy_init(ci) // 初始化 USB PHY
├─ platform_get_irq() // 获取 IRQ 43 或 42
├─ ci_get_otg_capable() // 检查 HC/DC 能力
│
├─ dr_mode = ci->platdata->dr_mode // 本板为 HOST
├─ ci_hdrc_host_init(ci) // 登记 Host 角色 ops
├─ [OTG/Peripheral] ci_hdrc_gadget_init(ci) // 登记 Gadget 角色 ops
│
├─ ci_get_role(ci) // 选择当前角色
├─ ci_role_start(ci, ci->role) // 启动当前角色
│
├─ platform_set_drvdata(pdev, ci) // ci 作为子设备 drvdata
├─ devm_request_irq(..., ci_irq, ..., ci) // 注册统一中断入口
├─ ci_extcon_register() // 注册 ID/VBUS 通知
├─ 配置 Runtime PM 和 autosuspend
├─ 初始化掉电恢复 work 和 mutex
└─ 创建 debugfs 节点
七、第三层:ci_role_driver 角色层
struct ci_role_driver {
int (*start)(struct ci_hdrc *);
void (*stop)(struct ci_hdrc *);
irqreturn_t (*irq)(struct ci_hdrc *);
void (*suspend)(struct ci_hdrc *);
void (*resume)(struct ci_hdrc *, bool power_lost);
const char *name;
};
Host 角色赋值:
rdrv->start = host_start;
rdrv->stop = host_stop;
rdrv->irq = host_irq;
rdrv->suspend = ci_hdrc_host_suspend;
rdrv->resume = ci_hdrc_host_resume;
rdrv->name = "host";
ci->roles[CI_ROLE_HOST] = rdrv;
ci_role_driver 的作用:
- 管理 ChipIdea 的 Host/Gadget 角色生命周期;
- 不是 USB 传输层 ops;
- 不直接等于 HCD;
- dr_mode 决定 probe 时允许登记哪些 role;
- OTG 运行时切换的是 ci->role,不是修改 dr_mode。
调用:
ci_role_start(ci, HOST)
└─ ci->roles[HOST]->start(ci)
└─ host_start(ci)
八、第四层:usb_hcd 和 hc_driver
host_start() 创建 usb_hcd:
hcd = __usb_create_hcd(&ci_ehci_hc_driver, ...);
usb_add_hcd(hcd, 0, 0);
ci->hcd = hcd;
usb_hcd 的作用:
- 表示 USB Core 看到的一台 Host Controller 实例;
- 保存控制器寄存器、PHY、状态和 Root Hub 状态;
- 内嵌一条 usb_bus;
- 通过 hcd->driver 调用具体 EHCI 实现;
- 它不是 Linux USB Core 本身;
- 它也不是控制命令的封装对象。
简化结构:
struct usb_hcd {
struct usb_bus self; // 此 Host 拥有的逻辑 USB 总线
const struct hc_driver *driver; // EHCI 硬件 ops
struct usb_phy *usb_phy;
struct phy *phy;
unsigned int irq;
void __iomem *regs;
struct urb *status_urb;
int state;
unsigned long hcd_priv[0]; // EHCI 私有数据
};
hc_driver 的作用:
- 是 USB Core 操作 Host 硬件的 ops;
- 管理 EHCI 初始化、运行、中断、URB、Root Hub 和总线 PM。
典型回调:
hc_driver
├─ reset = ehci_ci_reset / ehci_setup
├─ start = ehci_run
├─ stop = ehci_stop
├─ irq = ehci_irq
├─ urb_enqueue = ehci_urb_enqueue
├─ urb_dequeue = ehci_urb_dequeue
├─ hub_status_data = ehci_hub_status_data
├─ hub_control = ehci_hub_control 或 ChipIdea 覆盖函数
├─ bus_suspend = ehci_bus_suspend
└─ bus_resume = ehci_bus_resume
九、ci_role_driver 和 hc_driver 的区别
ci_role_driver:
- 所属 ChipIdea 角色层;
- 参数是 struct ci_hdrc *;
- 负责进入/退出 Host 或 Gadget 角色;
- Host start 函数是 host_start;
- Host irq 函数是 host_irq。
hc_driver:
- 所属 USB HCD 硬件层;
- 参数通常是 struct usb_hcd *;
- 负责真正控制 EHCI 硬件;
- start 函数是 ehci_run;
- irq 函数是 ehci_irq;
- urb_enqueue 函数是 ehci_urb_enqueue。
两组 start/stop 不是同一函数,而是上下层嵌套:
ci_role_driver->start
└─ host_start(ci) // 建立完整 Host 角色
├─ __usb_create_hcd() // 创建 HCD
└─ usb_add_hcd()
├─ hcd->driver->reset(hcd) // 初始化 EHCI
└─ hcd->driver->start(hcd) // ehci_run 启动硬件
ci_role_driver->stop
└─ host_stop(ci) // 删除完整 Host 角色
└─ usb_remove_hcd()
└─ hcd->driver->stop(hcd) // ehci_stop 停止硬件
中断嵌套:
ci_irq()
└─ ci_role_driver->irq(ci)
└─ host_irq(ci)
└─ usb_hcd_irq(ci->irq, ci->hcd)
└─ hcd->driver->irq(hcd)
└─ ehci_irq(hcd)
十、第五层:usb_bus
usb_bus 内嵌在 usb_hcd 中:
struct usb_hcd {
struct usb_bus self;
};
它不单独申请,创建 usb_hcd 时就已经存在;注册 HCD 时才注册总线:
usb_add_hcd(hcd)
└─ usb_register_bus(&hcd->self)
└─ 为 bus 分配 busnum 并加入 USB Core 总线表
usb_bus 的作用:
- 管理一台 Host 控制器下面的逻辑 USB 总线;
- 分配总线编号 busnum;
- 管理 USB 地址 1~127;
- 保存虚拟 Root Hub;
- 组织下游设备拓扑;
- 管理 Interrupt/ISO 周期带宽。
简化结构:
struct usb_bus {
struct device *controller;
struct device *sysdev;
int busnum;
int devnum_next;
struct usb_devmap devmap;
struct usb_device *root_hub;
int bandwidth_allocated;
int bandwidth_int_reqs;
int bandwidth_isoc_reqs;
};
转换关系:
hcd_to_bus(hcd)
└─ return &hcd->self
bus_to_hcd(bus)
└─ container_of(bus, struct usb_hcd, self)
十一、第六层:usb_device
usb_device 表示总线上的一个 USB 设备:
- 虚拟 Root Hub 也用 usb_device 表示;
- 外接 Hub 用 usb_device 表示;
- U 盘、鼠标、键盘、4G 模组也用 usb_device 表示。
简化结构:
struct usb_device {
int devnum; // USB 地址
char devpath[16]; // 拓扑路径
enum usb_device_state state;
enum usb_device_speed speed;
struct usb_device *parent; // 上级 Hub
struct usb_bus *bus; // 所属 USB 总线
struct usb_host_endpoint ep0; // 默认控制端点
struct device dev; // Linux 设备模型
struct usb_device_descriptor descriptor;
struct usb_host_config *config;
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
u8 portnum;
u8 level;
char *product;
char *manufacturer;
char *serial;
};
Root Hub 创建:
usb_add_hcd()
├─ usb_register_bus(&hcd->self)
├─ usb_alloc_dev(NULL, &hcd->self, 0) // 创建虚拟 Root Hub
└─ hcd->self.root_hub = rhdev
普通设备插入时创建:
hub_port_connect()
└─ usb_alloc_dev(hdev, hdev->bus, port1) // 创建下游 usb_device
├─ dev->bus = bus
├─ dev->parent = parent Hub
└─ dev->portnum = port1
拓扑:
usb_hcd
└─ usb_bus
└─ root_hub: usb_device
├─ port1 → usb_device:鼠标
├─ port2 → usb_device:U 盘
└─ port3 → usb_device:外接 Hub
└─ port1 → usb_device:4G 模组
十二、第七层:USB 枚举和描述符解析
重要结论:
usb_host_config、usb_interface、usb_host_interface、usb_host_endpoint
只保存描述符解析结果,不负责发送枚举命令。
真正的枚举命令通过 EP0 Control Transfer 发送:
- GET_DESCRIPTOR;
- SET_ADDRESS;
- SET_CONFIGURATION。
设备连接和枚举主线:
EHCI 端口变化中断
└─ hub_event() // Hub 工作线程处理端口事件
└─ hub_port_connect() // 确认设备连接并消抖
├─ usb_alloc_dev() // 创建 usb_device
├─ choose_devnum() // 选择 USB 地址
└─ hub_port_init() // 复位、读描述符、设置地址
├─ hub_port_reset() // 复位 USB 端口
├─ usb_get_device_descriptor() // 读取 Device Descriptor
│ └─ usb_get_descriptor()
│ └─ usb_control_msg(GET_DESCRIPTOR)
├─ hub_set_address()
│ └─ usb_control_msg(SET_ADDRESS)
└─ 再读取完整 Device Descriptor
随后:
usb_new_device(udev)
└─ usb_enumerate_device(udev)
└─ usb_get_configuration(udev)
├─ usb_get_descriptor(CONFIG, 9 字节) // 先读配置头
├─ 根据 wTotalLength 申请缓冲区
├─ usb_get_descriptor(CONFIG, 全长) // 再读完整配置集合
└─ usb_parse_configuration()
├─ 解析 Configuration
├─ 解析 Interface 和 alternate setting
└─ 解析 Endpoint
Linux 为兼容异常 USB 设备,GET_DESCRIPTOR 和 SET_ADDRESS 的尝试顺序可能交错,但本质都是 EP0 控制传输。
十三、控制命令是怎样发送到 EHCI 的
命令本体:struct usb_ctrlrequest
struct usb_ctrlrequest {
u8 bRequestType;
u8 bRequest;
le16 wValue;
le16 wIndex;
le16 wLength;
};
usb_control_msg() 先填写 Setup Packet,再调用 usb_internal_control_msg()。
发送主线:
usb_control_msg() // 构造 usb_ctrlrequest
└─ usb_internal_control_msg() // 创建同步 Control 传输
├─ usb_alloc_urb() // 申请 URB
├─ usb_fill_control_urb() // 将命令、数据、EP0 装入 URB
└─ usb_start_wait_urb() // 提交并等待完成
└─ usb_submit_urb() // USB Core 通用 URB 入口
└─ usb_hcd_submit_urb() // 找到负责该设备的 HCD
├─ hcd = bus_to_hcd(urb->dev->bus)
├─ map_urb_for_dma() // DMA 映射
└─ hcd->driver->urb_enqueue() // 调用 HCD 硬件 ops
└─ ehci_urb_enqueue() // EHCI 入队
├─ qh_urb_transaction() // 拆成 QH/qTD
└─ submit_async() // 挂入 EHCI 异步调度链
Control URB 会被拆成:
Setup qTD
└─ 8 字节 usb_ctrlrequest
Data qTD(可选)
└─ IN 或 OUT 数据
Status qTD
└─ 零长度、与 Data 方向相反
注意分工:
- usb_ctrlrequest 封装控制命令;
- URB 封装一次传输;
- usb_hcd 表示负责该设备的 Host 控制器;
- hc_driver 负责把 URB 交给 EHCI 硬件。
Root Hub 是特殊情况:
if (is_root_hub(urb->dev))
rh_urb_enqueue(hcd, urb); // 软件模拟 Root Hub 请求
else
hcd->driver->urb_enqueue(hcd, urb, ...); // 普通外设走 EHCI 硬件
十四、描述符对象如何形成
设备在线上返回连续的原始描述符字节:
Configuration Descriptor
Interface Descriptor
Endpoint Descriptor
Endpoint Descriptor
Interface Descriptor
Endpoint Descriptor
...
USB Core 解析成:
usb_device
└─ usb_host_config[]
└─ usb_interface_cache[]
└─ usb_host_interface[] // alternate setting
└─ usb_host_endpoint[]
主要结构体作用:
usb_host_config
└─ 保存一套 Configuration 及其中的 Interface 缓存
usb_host_interface
└─ 保存一个 Interface 的某个 alternate setting 描述
usb_host_endpoint
└─ 保存 Endpoint 描述符、URB 队列和 HCD 私有队列头
usb_interface
└─ 当前 Configuration 激活后创建的 Linux 设备对象,供功能驱动绑定
十五、SET_CONFIGURATION 和 Interface 创建
generic_probe(udev)
├─ usb_choose_configuration() // 选择配置
└─ usb_set_configuration() // 激活配置
├─ 为每个 Interface 申请 usb_interface
├─ 设置 altsetting 和 endpoint
├─ usb_control_msg(SET_CONFIGURATION) // 在 EP0 发送配置命令
├─ dev->actconfig = cp // 保存当前配置
├─ 设备状态改为 CONFIGURED
└─ device_add(&intf->dev) // 注册每个 Interface
Interface 注册后触发驱动匹配:
device_add(&intf->dev)
└─ usb_bus_type.match = usb_device_match()
└─ 匹配 usb_driver->id_table
└─ usb_probe_interface()
└─ usb_driver->probe(intf, id)
例子:
- U 盘 Interface → usb-storage;
- 鼠标 Interface → usbhid;
- 4G 串口 Interface → option/usb_wwan;
- UVC 摄像头 Interface → uvcvideo。
一个 usb_device 可以有多个 usb_interface,并分别绑定不同驱动。
十六、普通数据传输路径
功能驱动创建 URB:
struct urb
├─ dev // 目标 usb_device
├─ pipe/ep // 目标端点、方向和类型
├─ transfer_buffer // 数据缓冲区
├─ transfer_buffer_length
├─ setup_packet // 仅 Control 使用
├─ status/actual_length // 完成结果
├─ context // 驱动上下文
└─ complete // 完成回调
发送路径:
USB 功能驱动
└─ usb_submit_urb(urb)
└─ usb_hcd_submit_urb(urb)
├─ hcd = bus_to_hcd(urb->dev->bus)
├─ map_urb_for_dma()
└─ hcd->driver->urb_enqueue()
└─ ehci_urb_enqueue()
├─ Control/Bulk → QH/qTD 异步链
├─ Interrupt → 周期调度
└─ Isochronous → iTD/siTD 周期调度
完成路径:
EHCI 硬件完成传输
└─ ehci_irq() // 硬件完成中断
└─ ehci_work()/scan_async() // 扫描完成队列
└─ qh_completions() // 处理已完成 qTD
└─ ehci_urb_done() // 结束 URB
└─ usb_hcd_giveback_urb() // 把 URB 还给 USB Core/驱动
├─ unmap_urb_for_dma()
└─ urb->complete(urb) // 调用功能驱动完成回调
同步 usb_control_msg 的完成回调为:
usb_api_blocking_completion()
└─ complete(&ctx.done)
└─ 唤醒 usb_start_wait_urb()
└─ usb_internal_control_msg() 返回传输长度或错误
十七、最终七层模型
第 1 层:i.MX 平台硬件层
└─ ci_hdrc_imx_data
└─ 时钟、PHY、USBMISC、ANATOP、PM
第 2 层:ChipIdea 控制器核心层
└─ ci_hdrc_platform_data + ci_hdrc
└─ 寄存器映射、平台配置、控制器总状态
第 3 层:角色层
└─ ci_role_driver
└─ Host/Gadget 角色启动、停止、中断和 PM
第 4 层:Host Controller 层
└─ usb_hcd + hc_driver
└─ HCD 实例、EHCI 硬件 ops、Root Hub 入口
第 5 层:USB 总线和设备层
└─ usb_bus + usb_device
└─ 地址、拓扑、Root Hub、设备对象
第 6 层:枚举和描述符层
└─ EP0 控制命令 + config/interface/endpoint
└─ 读取描述符、设置地址、选择配置
第 7 层:功能驱动和传输层
└─ usb_interface + usb_driver + urb
└─ 驱动匹配以及 Control/Bulk/Interrupt/ISO 传输
六、RTL8723BU USB 设备运行流程
前面主要介绍了 USB 设备如何通过描述符向主机声明自身信息,以及主机如何根据描述符完成枚举、选择配置、匹配驱动并进行通信。但是需要注意的是,USB 设备能够正常枚举和通信的前提,是底层 USB 控制器已经完成初始化。也就是说,需要先完成 USB PHY、USB 控制器以及 Host 控制器驱动的初始化,随后 USB Core 才能继续扫描端口、枚举外部 USB 设备。
下面以 i.MX6ULL 的 OTG2 接口连接 RTL8723BU USB WiFi 模块为例,整体运行流程可以分为以下几层:

1、usb phy层:首先,设备树中的 usbphy2 节点会被转换成 platform_device,并匹配到 mxs_phy_probe(),用于初始化 USB PHY 相关的硬件功能,例如 D+ / D- 信号、电源、时钟和线状态检测等。
2、of platform层 : usbotg2 节点会引用 usbphy2,同时自身也会被 OF platform 机制转换成 platform 设备,因此可以在 /sys/bus/platform/devices/2184200.usb 下看到对应设备。
3、iMx Glue层:2184200.usb 会根据设备树中的 compatible 自动匹配到 i.MX 平台相关的 glue 驱动,也就是 ci_hdrc_imx_probe()。这一层主要负责提取 i.MX SoC 相关的 USB 资源信息,例如时钟、PHY、usbmisc 等,并进一步注册 USB 控制器设备,生成 /sys/bus/platform/devices/ci_hdrc.0
4、ChipIdea Core层:ci_hdrc.0 会继续匹配 ChipIdea Core 驱动 ci_hdrc_probe(),完成 USB 控制器核心功能初始化,并根据设备树中的 dr_mode = "host" 将控制器设置为 Host 模式。
5、USB Core / HCD 层;内核会注册 root hub,并开始扫描 USB 端口、枚举外部 USB 设备
6、USB Interface 驱动层:枚举完成后,USB core 会读取设备的 VID、PID 以及 interface 信息,并根据这些信息匹配对应的 USB 设备驱动,例如 RTL8723BU 对应的 WiFi 驱动 .ko 文件
7、网络设备层:当驱动匹配并初始化成功后,会向 Linux 网络子系统注册网络设备,最终生成 /sys/class/net/wlan0。
1. USB PHY 层
设备树中的 usbphy2 节点会被转换成 platform_device,并匹配到:
mxs_phy_probe()
USB PHY 主要负责 USB 的物理信号,例如:
-
D+ / D- 信号收发
-
USB 线状态检测
-
时钟、电源管理
-
连接 USB 控制器和外部 USB 接口
mxs_phy_probe() 主要是先注册 USB PHY。真正的 PHY 初始化一般在 USB 控制器启动时完成
2. OF Platform 层
设备树中的 usbotg2 节点会被内核转换成一个 platform 设备,对应的 sysfs 路径一般是:
/sys/bus/platform/devices/2184200.usb
这一层的作用就是:把设备树里的 USB 控制器节点变成 Linux 设备模型中的设备。
3. i.MX Glue 层
2184200.usb 会根据设备树中的:
compatible = "fsl,imx6ul-usb";
匹配到 i.MX 平台驱动:
ci_hdrc_imx_probe()
这一层主要处理 i.MX SoC 特有的 USB 初始化,例如:
-
获取时钟
-
查找
usbphy2 -
获取
usbmisc -
初始化 SoC 相关寄存器
-
调用
ci_hdrc_add_device()
然后会创建一个新的 platform 设备:
/sys/bus/platform/devices/ci_hdrc.0
也就是说:
2184200.usb 是设备树生成的设备
ci_hdrc.0 是 glue 层创建出来的 USB 控制器 core 设备
4. ChipIdea Core 层
ci_hdrc.0 会继续匹配通用 ChipIdea USB 控制器驱动:
ci_hdrc.0
-> ci_hdrc_probe()
ci_hdrc_probe() 会初始化 USB 控制器核心,包括寄存器、PHY、中断和 USB 角色。
因为设备树中配置了:
dr_mode = "host";
所以 OTG2 会工作在 Host 模式。
5. USB Core / HCD 层
进入 Host 模式后,会创建 HCD,并注册 USB bus 和虚拟 root hub,之后 USB core 和 hub 驱动会开始扫描端口,枚举外部 USB 设备。sysfs 中可以这样理解:
usb1 虚拟 root hub
1-0:1.0 虚拟 root hub 的 interface
1-1 外部 USB HUB
1-1:1.0 外部 USB HUB 的 interface
1-1.1 RTL8723BU USB WiFi 设备
1-1.1:1.0 RTL8723BU 的 interface 0
1-1.1:1.1 RTL8723BU 的 interface 1
1-1.1:1.2 RTL8723BU 的 interface 2
6. USB Interface 驱动层
USB 设备枚举完成后,USB core 会为每个 interface 创建:
struct usb_interface
然后 USB 功能驱动会根据这些信息进行匹配:
-
VID
-
PID
-
interface class
-
interface subclass
-
interface protocol
RTL8723BU 驱动中包含对应的设备 ID,例如:
VID = 0x0bda
PID = 0xb720
当 USB core 发现设备的 VID/PID 与驱动匹配时,就会调用 RTL8723BU 驱动的 probe 函数。
7. 网络设备层
RTL8723BU 驱动匹配成功后,会初始化 WiFi 芯片,并注册网络设备。最终生成:
/sys/class/net/wlan0
也就是说,wlan0 不是 USB core 直接创建的,而是 RTL8723BU 驱动注册到网络子系统后生成的。
532

被折叠的 条评论
为什么被折叠?



