i.MX6ULL USB 驱动分析——以 RTL8732 USB 设备为例

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

目录

一、USB硬件设计

 二、USB 设备接入

三、通信原理

四、描述符解析

① 设备描述符层次结构

② 枚举设备

五、RTL8723BU USB 设备运行流程

1. USB PHY 层

2. OF Platform 层

3. i.MX Glue 层

4. ChipIdea Core 层

5. USB Core / HCD 层

6. USB Interface 驱动层

7. 网络设备层


一、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_devicestruct 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_datapdata 的区别

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_hcdhc_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_driverhc_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_DESCRIPTORSET_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 驱动注册到网络子系统后生成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值