Linux USB 设备驱动与子系统之驱动模型
第一部分:USB 物理层枚举协议与内核状态机
1.1 USB 设备检测与速度协商
USB 设备与主机之间 “物理层信号” 与 “协议握手” 的过程。
1.1.1 设备连接与速度识别
USB 使用 D+ 和 D- 两根差分信号线来检测设备并识别其速度。
[ 主机端 (Host) ] [ 设备端 (Device) ] +--------+ +--------+ | D+ |---[ 15kΩ 下拉 (GND) ] | D+ |---[ 1.5kΩ 上拉 (3.3V) ]—— (全速/高速) | D- |---[ 15kΩ 下拉 (GND) ] | D- |---[ 1.5kΩ 上拉 (3.3V) ]—— (低速) +--------+ +--------+ ↓ 上拉电阻决定了速度 ↓
-
低速设备:在 D- 线上有 1.5kΩ 上拉电阻。
-
全速/高速设备:在 D+ 线上有 1.5kΩ 上拉电阻。
1.1.2 高速握手(Chirp 序列)
当主机检测到 D+ 被拉高时,它会进行 高速握手(High-Speed Handshake) 来确认设备是否支持高速模式。
-
设备发送 Chirp K:设备先断开 D+ 上拉,并在 D- 上发送一个 Chirp K 序列(电平跳变)。
-
主机响应 Chirp KJ:主机检测到 Chirp K 后,如果它支持高速模式,就会在总线上回复一组连续的 Chirp KJ 序列。
-
速度确认:设备识别到 Chirp KJ 序列后,确认双方都支持高速模式。然后设备断开 D+ 上拉,连接 高速终端电阻。双方进入 高速(High-Speed) 模式。
示波器/逻辑分析仪分析:
-
chirp K:设备发起的高速请求信号。 -
3对 chirp KJ 序列:主机的高速握手响应。 -
阈值降低一半:高速模式下的信号电压摆幅降低,以适应 480Mbps 的高速通信。
1.2 USB 枚举状态机
USB 设备从插入到可用的过程,是一个严谨的 状态机。
1.2.1 枚举流程图
[插入] -> [主机检测到电平变化] -> [主机复位设备(至少10ms)] -> [主机等待至少100ms(稳定期)] ↓ [主机分配地址] -> [主机读取Device Descriptor(前8字节)] -> [主机读取所有Device Descriptor] ↓ [主机读取Configuration Descriptor] -> [主机分配配置] -> [主机读取String Descriptor(可选)] ↓ [设备配置完成,进入可用状态]
1.2.2 内核中枚举过程的状态映射
Linux 内核中,struct usb_device 通过一系列状态来跟踪枚举进度。
/**
* @enum usb_device_state
* @brief USB 设备在 Linux 内核中的状态,对应枚举流程图。
*/
enum usb_device_state {
USB_STATE_NOTATTACHED = 0, /**< 设备未插入 */
USB_STATE_ATTACHED, /**< 设备已插入(对应图片中的“插入”为YES) */
USB_STATE_POWERED, /**< 设备已上电(对应“供电”为YES) */
USB_STATE_DEFAULT, /**< 设备已复位,但未分配地址(对应“初始”为YES) */
USB_STATE_ADDRESS, /**< 设备已分配唯一地址(对应“地址”为YES) */
USB_STATE_CONFIGURED, /**< 设备已完成配置(对应“配置”为YES) */
USB_STATE_SUSPENDED /**< 设备处于挂起状态(对应“挂起”为YES) */
};
/**
* @struct usb_device
* @brief 代表一个 USB 设备,是整个 USB 子系统的核心数据结构。
*/
struct usb_device {
int devnum; /**< 设备地址 (1-127) */
char devpath[16]; /**< 设备路径 (如 "1-1.2") */
enum usb_device_state state;/**< 当前设备状态 */
u32 quirks; /**< 设备特殊标志 */
struct usb_device *parent; /**< 父设备(根集线器或上级集线器) */
struct usb_bus *bus; /**< 所属总线 */
struct usb_device_descriptor descriptor; /**< 设备描述符 */
struct usb_config_descriptor *config; /**< 配置描述符 */
struct usb_host_endpoint *ep0; /**< 端点0 (默认控制端点) */
// ...
};
1.3 枚举过程中的关键操作
在 drivers/usb/core/hub.c 中,hub_thread 和 hub_port_connect_change 是处理枚举的核心代码。
/**
* @brief 处理端口连接变化(包含枚举的核心逻辑)。
* @param hub 指向集线器的指针。
* @param port1 端口号。
*/
static void hub_port_connect_change(struct usb_hub *hub, int port1)
{
struct usb_device *hdev = hub->hdev;
struct usb_device *udev;
int status, portstatus;
// 1. 读取端口状态,检测连接
status = hub_port_status(hub, port1, &portstatus);
if (status < 0) return;
// 2. 如果连接状态变更,开始枚举
// ... 省略部分细节
if (portstatus & USB_PORT_STAT_CONNECTION) {
// 3. 分配并初始化设备结构体 (对应图片中的“初始”)
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) return;
// 4. 复位设备 (对应图片中的“复位”)
usb_set_device_state(udev, USB_STATE_DEFAULT);
status = hub_port_reset(hub, port1, udev);
if (status < 0) goto error;
// 5. 分配地址 (对应图片中的“地址”)
status = usb_get_device_descriptor(udev, 8);
if (status < 8) goto error;
status = usb_get_device_descriptor(udev, 18);
if (status < 18) goto error;
// 6. 设置地址
udev->devnum = hub->hdev->bus->devnum_next++;
usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_ADDRESS, 0, udev->devnum, 0,
NULL, 0, 1000);
usb_set_device_state(udev, USB_STATE_ADDRESS);
// ... 后续获取配置描述符、配置设备等
}
}
1.4 软件设计模式树形分析(基于枚举与状态机)
USB 枚举协议与状态机设计模式 ├── 状态模式 (State Pattern) │ └── usb_device_state 状态机:定义了设备在枚举过程中的不同阶段。 ├── 模板方法模式 (Template Method Pattern) │ └── hub_port_connect_change 流程:定义了处理连接变化的标准步骤(复位 -> 获取描述符 -> 分配地址 -> 配置)。 ├── 策略模式 (Strategy Pattern) │ └── 速度协商逻辑:根据握手结果选择高速/全速/低速的处理策略。 ├── 工厂模式 (Factory Pattern) │ └── usb_alloc_dev():在探测到新设备时,创建并初始化 `usb_device` 对象。 └── 观察者模式 (Observer Pattern) └── hub_thread:持续轮询端口状态,当设备插入/拔出时,触发 `hub_port_connect_change` 事件。
1.5 资深视角:枚举调试与常见问题
1.5.1 设备无法被识别(无效状态)
现象:lsusb 看不到设备,或 dmesg 显示 Device not accepting address。
原因:
-
信号噪声:上拉/下拉电阻虚焊或布线错误(常见于自制板)。
-
电压不足:USB 接口提供的 5V 电流不足。
-
握手失败:高速设备与不支持高速的主机握手失败,退回到全速模式,但驱动未正确处理。
调试方法:
-
使用逻辑分析仪:抓取 D+/D- 信号,确认复位信号是否正常、Chirp 序列是否完成。
-
检查供电:使用
lsusb -t查看总线供电情况。 -
启用 usbmon:
sudo modprobe usbmon,然后用sudo cat /sys/kernel/debug/usb/usbmon/0u实时查看 USB 协议包。
1.5.2 枚举过程中导致系统卡死(死锁)
现象:插入特定 USB 设备后,系统响应缓慢或完全冻结。
原因:
-
设备固件缺陷:设备固件在处理特定控制请求时,导致 USB 核心层卡死。
-
内核锁竞争:
usb_alloc_dev与hub_thread之间可能存在资源竞争。
调试方法:
-
使用
wireshark+usbmon:抓取完整的枚举 URB 包,分析是哪个请求导致了问题。 -
使用
lockdep:echo 1 > /proc/sys/kernel/lockdep/on,检查内核是否有潜在的死锁。
第二部分 Linux USB 核心架构:主机控制器驱动 HCD 与核心层
2.1 USB 核心架构分层
Linux USB 子系统采用典型的分层架构,从物理硬件到应用层逐级抽象。
2.1.1 分层结构图
[用户空间] ↑ [USB 设备驱动 (usb_driver)] ← 鼠标、键盘、摄像头、串口等 ↑ [USB 核心层 (USB Core)] ↑ [主机控制器驱动 (HCD)] ← 对应具体硬件(EHCI/OHCI/UHCI/xHCI) ↑ [硬件 (USB 控制器)] ← PCI/Platform 设备
2.1.2 核心组件交互流程
[设备驱动] [USB Core] [HCD] ↓ ↓ ↓ 1. usb_submit_urb() → 2. 检查URB合法性 → 3. hcd->urb_enqueue() ↑ ↑ ↑ 6. 完成回调 (complete) ← 5. 调用完成回调 ← 4. 中断处理完成
2.2 核心数据结构
2.2.1 主机控制器驱动(HCD)核心结构
/**
* @struct usb_hcd
* @brief USB 主机控制器驱动核心数据结构。
* 每个 USB 控制器实例对应一个 struct usb_hcd。
*/
struct usb_hcd {
struct usb_bus self; /**< 关联的 USB 总线 */
struct usb_device *root_hub; /**< 根集线器设备 */
struct hcd_driver *driver; /**< HCD 驱动操作 */
struct device *dev; /**< 关联的设备 */
unsigned int irq; /**< 中断号 */
spinlock_t lock; /**< 保护 HCD 状态的自旋锁 */
/**
* @brief HCD 核心操作函数集,由具体硬件驱动实现。
*/
struct hc_driver *hc_driver; /**< 硬件驱动操作 */
dma_addr_t *reserved; /**< 保留 DMA 地址 */
unsigned long flags; /**< 标志位 */
/* 状态与统计 */
unsigned int rh_timer; /**< 根集线器轮询定时器 */
unsigned int rh_timer_delta; /**< 定时器增量 */
};
2.2.2 USB 总线结构
/**
* @struct usb_bus
* @brief 代表一条 USB 总线。
* 对应于物理 USB 控制器管理的所有设备。
*/
struct usb_bus {
struct usb_host_bus *bus; /**< 总线抽象 */
struct device *controller; /**< 控制器设备 */
int busnum; /**< 总线编号 */
const struct usb_bus_ops *ops; /**< 总线操作 */
struct usb_hcd *hcd; /**< 关联的 HCD */
int bandwidth_allocated; /**< 带宽分配统计 */
void *hcpriv; /**< HCD 私有数据 */
};
2.2.3 URB (USB Request Block) 结构
/**
* @struct urb
* @brief USB 请求块,所有 USB 数据传输的核心结构。
*/
struct urb {
struct list_head urb_list; /**< URB 链表节点 */
struct usb_device *dev; /**< 目标设备 */
unsigned int pipe; /**< 管道类型 (端点+方向) */
unsigned int status; /**< 传输状态 */
unsigned int actual_length; /**< 实际传输长度 */
int error_count; /**< 错误计数 */
/* 缓冲区信息 */
void *transfer_buffer; /**< 传输缓冲区 */
dma_addr_t transfer_dma; /**< 缓冲区 DMA 地址 */
unsigned int transfer_buffer_length; /**< 缓冲区长度 */
/* 中断/异步回调 */
usb_complete_t complete; /**< 完成回调函数 */
void *context; /**< 回调上下文 */
};
2.3 核心代码实现
2.3.1 HCD 注册与初始化
/**
* @brief HCD 注册函数(由具体硬件驱动调用)。
* @param hcd 指向 usb_hcd 结构
* @return 0 成功,负数错误
*/
int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
{
struct usb_bus *bus = &hcd->self;
struct device *dev = hcd->dev;
int ret;
// 1. 初始化总线
bus->busnum = hcd->bus_num ? : usb_busnum_next();
bus->ops = &usb_bus_ops_common;
bus->hcd = hcd;
// 2. 注册到 USB 核心
ret = usb_register_bus(bus);
if (ret) goto err_bus;
// 3. 创建根集线器
hcd->root_hub = usb_alloc_dev(NULL, bus, 0);
if (!hcd->root_hub) {
ret = -ENOMEM;
goto err_root;
}
// 4. 注册中断
if (hcd->driver->flags & HCD_FLAG_SHARED_IRQ) {
ret = request_irq(irqnum, usb_hcd_irq, IRQF_SHARED, hcd->driver->name, hcd);
} else {
ret = request_irq(irqnum, usb_hcd_irq, 0, hcd->driver->name, hcd);
}
if (ret) goto err_irq;
// 5. 启动 HCD
if (hcd->driver->start) {
ret = hcd->driver->start(hcd);
if (ret) goto err_start;
}
// 6. 启动根集线器轮询
mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(100));
return 0;
err_start:
free_irq(irqnum, hcd);
err_irq:
usb_put_dev(hcd->root_hub);
err_root:
usb_unregister_bus(bus);
err_bus:
return ret;
}
EXPORT_SYMBOL(usb_add_hcd);
2.3.2 URB 提交与处理
/**
* @brief 提交 URB 到 HCD。
* @param urb 指向 urb 结构
* @return 0 成功,负数错误
*/
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
struct usb_device *dev = urb->dev;
struct usb_hcd *hcd = bus_to_hcd(dev->bus);
int ret;
// 1. 检查 URB 合法性
if (!urb || !dev) return -EINVAL;
if (!hcd->hc_driver->urb_enqueue) return -ENXIO;
// 2. 锁定 HCD,提交请求
spin_lock_irqsave(&hcd->lock, flags);
ret = hcd->hc_driver->urb_enqueue(hcd, urb, mem_flags);
spin_unlock_irqrestore(&hcd->lock, flags);
return ret;
}
EXPORT_SYMBOL(usb_submit_urb);
/**
* @brief URB 完成回调(由 HCD 中断处理调用)。
* @param urb 指向 urb 结构
*/
void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb)
{
struct usb_device *dev = urb->dev;
// 1. 更新状态
urb->status = 0;
// 2. 调用驱动注册的完成回调
if (urb->complete) {
urb->complete(urb);
}
// 3. 释放 URB(如果是异步的)
usb_free_urb(urb);
}
EXPORT_SYMBOL(usb_hcd_giveback_urb);
2.3.3 根集线器轮询
/**
* @brief 根集线器状态轮询定时器回调。
*/
static void rh_timer_callback(struct timer_list *t)
{
struct usb_hcd *hcd = from_timer(hcd, t, rh_timer);
struct usb_device *rh = hcd->root_hub;
unsigned long flags;
// 1. 读取端口状态
if (hcd->hc_driver->hub_status_data) {
hcd->hc_driver->hub_status_data(hcd, NULL);
}
// 2. 如果端口状态变化,触发中断
if (hcd->port_change) {
usb_hcd_irq(hcd->irq, hcd);
}
// 3. 重置定时器
mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(100));
}
2.4 软件设计模式树形分析
USB 核心架构设计模式 ├── 工厂模式 (Factory Pattern) │ └── usb_alloc_dev():创建并初始化 usb_device 对象。 ├── 抽象工厂模式 (Abstract Factory Pattern) │ └── usb_add_hcd():创建 HCD 实例并绑定到具体硬件驱动。 ├── 策略模式 (Strategy Pattern) │ ├── hcd->hc_driver:不同 HCD(EHCI/xHCI)实现不同的硬件操作策略。 │ └── usb_hcd_irq:HCD 中断处理策略。 ├── 观察者模式 (Observer Pattern) │ └── urb->complete:URB 完成时通知设备驱动。 ├── 代理模式 (Proxy Pattern) │ └── usb_submit_urb():代理对 HCD 的请求,提供安全性检查。 └── 模板方法模式 (Template Method Pattern) └── usb_add_hcd():定义了 HCD 注册的标准模板(中断注册、设备创建、定时器启动)。
2.5 资深视角:HCD 调试核心难点
2.5.1 HCD 中断风暴
现象:cat /proc/interrupts 显示 USB 中断触发频率极高,CPU 占用率高。
原因:
-
中断标志未正确清除,导致重复触发。
-
根集线器轮询定时器间隔过短。
-
硬件驱动在处理短包时卡死。
调试方法:
-
在 ISR 中检查中断状态并确认清除。
-
调整轮询定时器间隔。
-
使用
perf跟踪 ISR 执行时间。
2.5.2 URB 提交失败
现象:usb_submit_urb() 返回 -EINVAL 或 -ENODEV。
原因:
-
端点地址或方向错误。
-
设备处于
SUSPENDED状态。 -
缓冲区未对齐或大小错误。
调试方法:
-
打印 URB 字段,检查管道设置。
-
检查设备状态。
-
使用
usbmon观察实际请求。
2.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| PCI/Platform 驱动 | USB 控制器通常作为 PCI/Platform 设备发现,HCD 初始化时注册 | 设备树解析、中断分配 |
| DMA 控制器 | HCD 使用 DMA 进行批量数据传输 | 地址对齐、传输大小 |
| 中断控制器 (GIC) | HCD 注册中断处理函数 | 中断亲和性、优先级 |
| 电源管理 | HCD 在系统挂起/唤醒时进入低功耗模式 | 唤醒源、USB 设备唤醒 |
| Input 子系统 | USB HID 驱动使用 Input 子系统上报键鼠事件 | 事件类型、报告描述符解析 |
| V4L2 子系统 | USB 摄像头驱动使用 V4L2 传输视频数据 | 视频格式、帧率控制 |
| ALSA 子系统 | USB 音频驱动使用 ALSA 播放/录音 | 采样率、缓冲区大小 |
| Network 子系统 | USB 网卡驱动使用 net_device 接口 | 吞吐量、丢包率 |
第三部分 USB 设备驱动模型:usb_driver、probe 与 disconnect
3.1 USB 设备驱动模型概述
USB 设备驱动是运行在 USB 核心层之上的软件模块,负责与特定类型的 USB 设备进行通信。每个 USB 设备驱动通过 usb_driver 结构注册到 USB 核心,核心层在检测到匹配的设备时调用 probe 函数,在设备移除时调用 disconnect 函数。
3.1.1 设备驱动与 USB 核心的交互流程
[USB 核心层] [USB 设备驱动] ↓ ↓ 1. 检测到新设备 → 读取设备/配置描述符 ↓ ↓ 2. 遍历已注册的 usb_driver 列表 ↓ ↓ 3. 匹配成功 (id_table 或 动态匹配) ↓ ↓ 4. 调用 usb_driver->probe() ← 驱动执行初始化 ↓ ↓ 5. 驱动创建设备节点 (如 /dev/input/eventX) ↓ ↓ 6. 正常通信 (URB 提交/完成) ↔ 处理数据 ↓ ↓ 7. 设备拔出 → 调用 usb_driver->disconnect() ← 驱动清理资源
3.1.2 设备驱动生命周期
[设备插入] → [匹配] → [probe()] → [正常通信] → [设备拔出] → [disconnect()] → [驱动卸载]
3.2 核心数据结构
3.2.1 usb_driver 结构
/**
* @struct usb_driver
* @brief USB 设备驱动的核心结构。
* 每个 USB 设备驱动必须填充此结构并注册到 USB 核心。
*/
struct usb_driver {
const char *name; /**< 驱动名称 */
int (*probe)(struct usb_interface *intf, const struct usb_device_id *id);
void (*disconnect)(struct usb_interface *intf);
int (*unlocked_ioctl)(struct usb_interface *intf, unsigned int code, void *buf);
int (*suspend)(struct usb_interface *intf, pm_message_t message);
int (*resume)(struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
const struct usb_device_id *id_table; /**< 支持的设备 ID 列表 */
struct device_driver driver; /**< 通用设备驱动结构 */
unsigned int no_dynamic_id:1; /**< 是否禁止动态 ID */
unsigned int supports_autosuspend:1; /**< 是否支持自动挂起 */
unsigned int disable_hub_initiated_lpm:1; /**< 是否禁用 LPM */
unsigned int soft_unbind:1; /**< 是否软解绑 */
};
3.2.2 usb_device_id 结构
/**
* @struct usb_device_id
* @brief USB 设备 ID 匹配结构。
* 用于在 USB 核心中匹配驱动与设备。
*/
struct usb_device_id {
__u16 match_flags; /**< 匹配标志 */
__u16 idVendor; /**< 厂商 ID */
__u16 idProduct; /**< 产品 ID */
__u16 bcdDevice_lo; /**< 设备版本下限 */
__u16 bcdDevice_hi; /**< 设备版本上限 */
__u8 bDeviceClass; /**< 设备类 */
__u8 bDeviceSubClass; /**< 设备子类 */
__u8 bDeviceProtocol; /**< 设备协议 */
__u8 bInterfaceClass; /**< 接口类 */
__u8 bInterfaceSubClass; /**< 接口子类 */
__u8 bInterfaceProtocol; /**< 接口协议 */
__u8 bInterfaceNumber; /**< 接口编号 */
kernel_ulong_t driver_info; /**< 驱动私有数据 */
};
3.2.3 usb_interface 结构
/**
* @struct usb_interface
* @brief 代表 USB 设备的一个接口。
* 每个接口可能包含多个端点,设备驱动通常绑定到一个接口。
*/
struct usb_interface {
struct usb_host_interface *altsetting; /**< 接口设置列表 */
struct usb_host_interface *cur_altsetting; /**< 当前使用的接口设置 */
unsigned int num_altsetting; /**< 接口设置数量 */
int minor; /**< 次设备号 */
enum usb_interface_condition condition; /**< 接口状态 */
struct usb_device *dev; /**< 所属设备 */
struct device dev; /**< 通用设备结构 */
struct usb_driver *driver; /**< 绑定的驱动 */
void *drvdata; /**< 驱动私有数据 */
};
3.3 核心代码实现
3.3.1 USB 设备驱动注册
/**
* @brief USB 设备驱动注册函数。
* @param driver 指向 usb_driver 结构
* @param module 所属模块
* @return 0 成功,负数错误
*/
int usb_register_driver(struct usb_driver *driver, struct module *module)
{
int ret;
// 1. 设置驱动名称
driver->driver.name = driver->name;
driver->driver.bus = &usb_bus_type;
driver->driver.owner = module;
// 2. 注册到设备驱动核心
ret = driver_register(&driver->driver);
if (ret) return ret;
// 3. 添加到 USB 驱动列表
usb_driver_list_add(driver);
return 0;
}
EXPORT_SYMBOL(usb_register_driver);
/**
* @brief USB 设备驱动注销函数。
* @param driver 指向 usb_driver 结构
*/
void usb_deregister_driver(struct usb_driver *driver)
{
// 1. 从列表移除
usb_driver_list_remove(driver);
// 2. 注销设备驱动
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(usb_deregister_driver);
3.3.2 Probe 函数实现示例(USB HID 驱动)
/**
* @brief USB HID 驱动 probe 函数。
* @param intf 指向 usb_interface
* @param id 指向匹配的 usb_device_id
* @return 0 成功,负数错误
*/
static int usb_hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_hid *hid;
int ret;
// 1. 分配驱动私有数据
hid = kzalloc(sizeof(struct usb_hid), GFP_KERNEL);
if (!hid) return -ENOMEM;
// 2. 关联接口与私有数据
usb_set_intfdata(intf, hid);
hid->dev = dev;
hid->intf = intf;
// 3. 获取接口端点信息
ret = usb_find_common_endpoints(intf->cur_altsetting,
&hid->ep_in, &hid->ep_out);
if (ret) goto error;
// 4. 分配 URB 和缓冲区
hid->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!hid->urb) goto error;
hid->buf = kmalloc(8, GFP_KERNEL);
if (!hid->buf) goto error;
// 5. 初始化 URB(用于读取 HID 报告)
usb_fill_int_urb(hid->urb, dev,
usb_rcvintpipe(dev, hid->ep_in->bEndpointAddress),
hid->buf, 8,
usb_hid_irq, hid,
hid->ep_in->bInterval);
// 6. 提交 URB 开始接收数据
ret = usb_submit_urb(hid->urb, GFP_KERNEL);
if (ret) goto error;
// 7. 注册 HID 设备到 HID 子系统
ret = hid_add_device(hid);
if (ret) goto error;
dev_info(&intf->dev, "USB HID device initialized\n");
return 0;
error:
if (hid->urb) usb_free_urb(hid->urb);
kfree(hid->buf);
kfree(hid);
return ret;
}
3.3.3 Disconnect 函数实现
/**
* @brief USB HID 驱动 disconnect 函数。
* @param intf 指向 usb_interface
*/
static void usb_hid_disconnect(struct usb_interface *intf)
{
struct usb_hid *hid = usb_get_intfdata(intf);
if (!hid) return;
// 1. 注销 HID 设备
hid_remove_device(hid);
// 2. 取消 URB 并释放
usb_kill_urb(hid->urb);
usb_free_urb(hid->urb);
// 3. 释放缓冲区
kfree(hid->buf);
// 4. 释放私有数据
usb_set_intfdata(intf, NULL);
kfree(hid);
dev_info(&intf->dev, "USB HID device disconnected\n");
}
3.3.4 USB 设备 ID 表示例
/**
* @brief USB 设备 ID 表(用于匹配 HID 键盘/鼠标)。
*/
static const struct usb_device_id usb_hid_id_table[] = {
{ USB_INTERFACE_CLASS(USB_INTERFACE_CLASS_HID,
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_KEYBOARD) },
{ USB_INTERFACE_CLASS(USB_INTERFACE_CLASS_HID,
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ USB_INTERFACE_CLASS(USB_INTERFACE_CLASS_HID, 0, 0) },
{ }
};
MODULE_DEVICE_TABLE(usb, usb_hid_id_table);
/**
* @brief USB HID 驱动结构。
*/
static struct usb_driver usb_hid_driver = {
.name = "usbhid",
.id_table = usb_hid_id_table,
.probe = usb_hid_probe,
.disconnect = usb_hid_disconnect,
.suspend = usb_hid_suspend,
.resume = usb_hid_resume,
.supports_autosuspend = 1,
};
module_usb_driver(usb_hid_driver);
3.3.5 动态 ID 添加(热插拔支持)
/**
* @brief 用户空间通过 sysfs 动态添加 USB 设备 ID。
*
* echo "vendor_id product_id" > /sys/bus/usb/drivers/驱动名/new_id
*/
static ssize_t new_id_store(struct device_driver *drv, const char *buf, size_t count)
{
struct usb_driver *usb_drv = to_usb_driver(drv);
struct usb_device_id *id;
int vendor, product;
int ret;
if (sscanf(buf, "%x %x", &vendor, &product) != 2)
return -EINVAL;
// 1. 分配新的 ID
id = kzalloc(sizeof(struct usb_device_id), GFP_KERNEL);
if (!id) return -ENOMEM;
// 2. 设置 ID 匹配字段
id->match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;
id->idVendor = vendor;
id->idProduct = product;
// 3. 添加到驱动 ID 列表
ret = usb_store_new_id(usb_drv, id);
if (ret) {
kfree(id);
return ret;
}
return count;
}
3.4 软件设计模式树形分析
USB 设备驱动模型设计模式 ├── 工厂模式 (Factory Pattern) │ └── usb_alloc_urb():创建并初始化 URB 对象。 ├── 策略模式 (Strategy Pattern) │ ├── usb_driver->probe:不同驱动有不同的 probe 初始化策略。 │ └── usb_driver->suspend/resume:不同驱动的电源管理策略。 ├── 观察者模式 (Observer Pattern) │ ├── usb_register_driver:注册驱动后,USB 核心检测到设备时自动通知。 │ └── usb_hid_irq:中断回调函数观察 URB 完成事件。 ├── 单例模式 (Singleton Pattern) │ └── USB 核心层的驱动列表由全局变量管理,每个驱动只注册一次。 ├── 适配器模式 (Adapter Pattern) │ └── usb_hid_probe:将 USB 接口适配为 HID 设备。 └── 模板方法模式 (Template Method Pattern) └── usb_hid_probe/disconnect:定义了 HID 设备初始化和清理的标准流程(分配 → 初始化 → 提交 → 注销 → 释放)。
3.5 USB 设备驱动调试核心难点
3.5.1 Probe 返回负值但设备仍然绑定
现象:probe 返回 -ENOMEM 或 -EIO,但 lsusb 显示驱动已绑定。
原因:
-
probe中部分资源分配成功,但返回前未清理。 -
USB 核心未正确处理
probe返回值。
解决方法:
-
在
probe中确保错误路径释放所有已分配资源。 -
使用
goto标签统一错误处理。
3.5.2 URB 提交后无响应
现象:usb_submit_urb 成功,但 complete 回调未被调用。
原因:
-
端点管道设置错误。
-
设备未配置为中断传输模式。
-
设备停止工作。
解决方法:
-
使用
usbmon查看 USB 总线上的实际请求。 -
检查端点描述符的传输类型和间隔设置。
-
在
probe中确认设备是否正确配置。
3.5.3 设备拔除后驱动未卸载
现象:设备拔除后,lsmod 仍显示驱动加载。
原因:
-
disconnect未释放所有资源。 -
设备引用计数未归零。
-
有进程占用设备节点。
解决方法:
-
在
disconnect中使用usb_kill_urb确保所有 URB 停止。 -
使用
usb_put_dev减少引用计数。 -
检查是否有进程打开
/dev/input/eventX。
3.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 通过 usb_register_driver 注册,核心层在设备插入/拔出时调用 probe/disconnect |
匹配流程、设备枚举状态 |
| Input 子系统 | USB HID 驱动将按键/鼠标事件通过 Input 子系统上报 | 报告描述符解析、事件类型 |
| V4L2 子系统 | USB 摄像头驱动通过 V4L2 接口提供视频流 | 视频格式协商、缓冲区管理 |
| ALSA 子系统 | USB 音频驱动通过 ALSA 接口播放/录音 | 采样率匹配、音频同步 |
| 网络子系统 | USB 网卡驱动通过 net_device 接口接入网络栈 |
吞吐量、MTU、丢包处理 |
| PCI/Platform 驱动 | USB 控制器驱动作为 PCI/Platform 设备注册 | 设备树解析、中断分配 |
| 电源管理 | 驱动实现 suspend/resume 支持系统睡眠 |
唤醒源、设备状态恢复 |
| sysfs | 通过 /sys/bus/usb/drivers/ 动态添加 ID |
用户空间配置、权限控制 |
第四部分 USB 请求块 URB:异步 I/O 与 DMA 数据传输
4.1 URB 的核心地位
URB(USB Request Block)是 Linux USB 子系统中数据传输的核心单元。它封装了 USB 传输所需的所有信息,包括目标设备、端点、数据缓冲区、传输方向、完成回调等。理解 URB 是编写 USB 设备驱动的基础。
4.1.1 URB 生命周期
[分配 URB] → [初始化 URB] → [提交 URB] → [硬件传输] → [完成回调] → [释放 URB] ↓ ↓ ↓ ↓ (usb_alloc_urb) (usb_fill_*_urb) (usb_submit_urb) (usb_free_urb)
4.1.2 URB 与 USB 传输类型对应关系
| 传输类型 | 对应的 URB 函数 | 使用场景 |
|---|---|---|
| 控制传输 | usb_control_msg |
设备配置、命令发送 |
| 批量传输 | usb_bulk_msg |
大数据传输(存储、网络) |
| 中断传输 | usb_fill_int_urb |
周期性数据(HID 设备) |
| 等时传输 | usb_fill_isoc_urb |
实时数据(音频、视频) |
4.2 核心数据结构
4.2.1 URB 结构详解
/**
* @struct urb
* @brief USB 请求块,所有 USB 数据传输的核心结构。
* 驱动开发者必须正确填充此结构并提交到 USB 核心。
*/
struct urb {
/* 链表管理和引用计数 */
struct list_head urb_list; /**< 链表节点 */
struct kref kref; /**< 引用计数 */
struct usb_device *dev; /**< 目标设备 */
struct usb_host_endpoint *ep; /**< 目标端点 */
unsigned int pipe; /**< 管道 (端点+方向+类型) */
/* 状态和长度信息 */
unsigned int status; /**< 传输状态 */
unsigned int actual_length; /**< 实际传输长度 */
int error_count; /**< 错误计数 */
int start_frame; /**< 起始帧 (等时传输) */
int number_of_packets; /**< 包数量 (等时传输) */
/* 缓冲区信息 */
void *transfer_buffer; /**< 传输缓冲区 */
dma_addr_t transfer_dma; /**< 缓冲区 DMA 地址 */
unsigned int transfer_buffer_length; /**< 缓冲区长度 */
/* 列表管理 */
int interval; /**< 中断传输间隔 */
int num_sgs; /**< SG 列表数量 */
struct scatterlist *sg; /**< SG 列表指针 */
/* 回调上下文 */
usb_complete_t complete; /**< 完成回调函数 */
void *context; /**< 回调上下文 */
struct usb_iso_packet_descriptor *iso_frame_desc; /**< 等时帧描述符 */
struct urb *next; /**< 链表下一个 URB */
};
4.2.2 管道宏定义
/* 管道宏定义 */ #define usb_rcvctrlpipe(dev, ep) ((dev)->ep0->pipe) + 0x80 #define usb_sndctrlpipe(dev, ep) ((dev)->ep0->pipe) #define usb_rcvbulkpipe(dev, ep) ((dev)->ep0->pipe + 0x80) + (ep) #define usb_sndbulkpipe(dev, ep) ((dev)->ep0->pipe) + (ep) #define usb_rcvintpipe(dev, ep) ((dev)->ep0->pipe + 0x80) + (ep) + 0x100 #define usb_sndintpipe(dev, ep) ((dev)->ep0->pipe) + (ep) + 0x100 #define usb_rcvisocpipe(dev, ep) ((dev)->ep0->pipe + 0x80) + (ep) + 0x200 #define usb_sndisocpipe(dev, ep) ((dev)->ep0->pipe) + (ep) + 0x200
4.3 核心代码实现
4.3.1 URB 分配与初始化
/**
* @brief 分配 URB 并初始化(批量传输示例)。
* @param dev 指向 USB 设备
* @param endpoint 端点地址
* @param buffer 数据缓冲区
* @param len 缓冲区长度
* @param callback 完成回调函数
* @param context 回调上下文
* @return 指向 URB 的指针,失败返回 NULL
*/
static struct urb *usb_alloc_bulk_urb(struct usb_device *dev,
int endpoint,
void *buffer,
int len,
usb_complete_t callback,
void *context)
{
struct urb *urb;
int pipe;
// 1. 确定管道方向和类型
if (endpoint & USB_DIR_IN) {
pipe = usb_rcvbulkpipe(dev, endpoint & 0x7F);
} else {
pipe = usb_sndbulkpipe(dev, endpoint & 0x7F);
}
// 2. 分配 URB
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) return NULL;
// 3. 填充 URB 字段
urb->dev = dev;
urb->pipe = pipe;
urb->transfer_buffer = buffer;
urb->transfer_buffer_length = len;
urb->complete = callback;
urb->context = context;
// 4. 如果是 DMA 传输,设置 DMA 地址
urb->transfer_dma = dma_map_single(&dev->dev, buffer, len, DMA_FROM_DEVICE);
if (dma_mapping_error(&dev->dev, urb->transfer_dma)) {
usb_free_urb(urb);
return NULL;
}
return urb;
}
/**
* @brief 初始化中断传输 URB(用于 HID 设备)。
*/
static void usb_fill_hid_urb(struct urb *urb,
struct usb_device *dev,
int endpoint,
void *buffer,
int len,
usb_complete_t callback,
void *context,
int interval)
{
urb->dev = dev;
urb->pipe = usb_rcvintpipe(dev, endpoint);
urb->transfer_buffer = buffer;
urb->transfer_buffer_length = len;
urb->complete = callback;
urb->context = context;
urb->interval = interval;
}
4.3.2 URB 提交与取消
/**
* @brief 提交 URB 进行异步传输。
* @param urb 指向 URB
* @param mem_flags 内存分配标志
* @return 0 成功,负数错误
*/
static int usb_submit_async_urb(struct urb *urb, gfp_t mem_flags)
{
int ret;
// 1. 检查 URB 状态
if (!urb || !urb->dev || urb->status == -EINPROGRESS) {
return -EINVAL;
}
// 2. 锁定并提交
ret = usb_submit_urb(urb, mem_flags);
if (ret) {
dev_err(&urb->dev->dev, "URB submit failed: %d\n", ret);
return ret;
}
return 0;
}
/**
* @brief 取消正在进行的 URB 传输。
* @param urb 指向 URB
* @return 0 成功
*/
static void usb_cancel_async_urb(struct urb *urb)
{
if (!urb) return;
// 1. 检查 URB 是否正在进行
if (urb->status == -EINPROGRESS) {
// 2. 取消 URB
usb_unlink_urb(urb);
}
}
/**
* @brief 批量传输示例(同步方式)。
* @param dev 指向 USB 设备
* @param endpoint 端点地址
* @param buffer 数据缓冲区
* @param len 缓冲区长度
* @param timeout 超时时间 (ms)
* @return 0 成功,负数错误
*/
static int usb_bulk_transfer_sync(struct usb_device *dev,
int endpoint,
void *buffer,
int len,
int timeout)
{
int pipe;
int actual_length;
int ret;
// 1. 确定管道
if (endpoint & USB_DIR_IN) {
pipe = usb_rcvbulkpipe(dev, endpoint & 0x7F);
} else {
pipe = usb_sndbulkpipe(dev, endpoint & 0x7F);
}
// 2. 执行同步传输
ret = usb_bulk_msg(dev, pipe, buffer, len, &actual_length, msecs_to_jiffies(timeout));
if (ret) {
dev_err(&dev->dev, "Bulk transfer failed: %d\n", ret);
return ret;
}
return 0;
}
4.3.3 URB 完成回调处理
/**
* @brief URB 完成回调函数(批量传输示例)。
* @param urb 指向完成的 URB
*/
static void usb_bulk_irq_callback(struct urb *urb)
{
struct usb_device *dev = urb->dev;
void *context = urb->context;
// 1. 检查传输状态
if (urb->status == 0) {
// 传输成功
dev_info(&dev->dev, "Bulk transfer completed: %d bytes\n", urb->actual_length);
// 处理接收到的数据
handle_urb_data(urb->transfer_buffer, urb->actual_length, context);
} else if (urb->status == -ECONNRESET) {
// 传输被取消
dev_dbg(&dev->dev, "Bulk transfer cancelled\n");
} else if (urb->status == -ESHUTDOWN) {
// 设备被移除
dev_warn(&dev->dev, "Device removed while transfer in progress\n");
} else {
// 其他错误
dev_err(&dev->dev, "Bulk transfer error: %d\n", urb->status);
}
// 2. 释放 URB(如果是单次传输)
usb_free_urb(urb);
}
/**
* @brief 处理 URB 数据(示例)。
*/
static void handle_urb_data(void *buffer, int len, void *context)
{
// 处理数据...
uint8_t *data = (uint8_t *)buffer;
for (int i = 0; i < len; i++) {
// 处理每个字节
}
}
4.3.4 等时传输 URB 处理
/**
* @brief 等时传输 URB 创建和初始化。
* @param dev 指向 USB 设备
* @param endpoint 端点地址
* @param buffer 数据缓冲区
* @param packets 包数量
* @param packets_size 每包大小
* @return 指向 URB 的指针
*/
static struct urb *usb_alloc_isoc_urb(struct usb_device *dev,
int endpoint,
void *buffer,
int packets,
int packets_size)
{
struct urb *urb;
int pipe;
int i;
// 1. 确定管道
if (endpoint & USB_DIR_IN) {
pipe = usb_rcvisocpipe(dev, endpoint & 0x7F);
} else {
pipe = usb_sndisocpipe(dev, endpoint & 0x7F);
}
// 2. 分配 URB(包含等时帧描述符)
urb = usb_alloc_urb(packets, GFP_KERNEL);
if (!urb) return NULL;
// 3. 填充 URB 字段
urb->dev = dev;
urb->pipe = pipe;
urb->transfer_buffer = buffer;
urb->transfer_buffer_length = packets * packets_size;
urb->number_of_packets = packets;
// 4. 填充等时帧描述符
for (i = 0; i < packets; i++) {
urb->iso_frame_desc[i].offset = i * packets_size;
urb->iso_frame_desc[i].length = packets_size;
}
return urb;
}
/**
* @brief 等时传输完成回调。
*/
static void usb_isoc_irq_callback(struct urb *urb)
{
int i;
// 检查每个帧的传输状态
for (i = 0; i < urb->number_of_packets; i++) {
struct usb_iso_packet_descriptor *desc = &urb->iso_frame_desc[i];
if (desc->status == 0) {
// 该帧传输成功
process_isoc_frame(urb->transfer_buffer + desc->offset, desc->actual_length);
} else {
dev_err(&urb->dev->dev, "ISO frame %d failed: %d\n", i, desc->status);
}
}
// 重新提交 URB(等时传输通常循环提交)
usb_submit_urb(urb, GFP_ATOMIC);
}
4.4 软件设计模式树形分析
URB 异步 I/O 设计模式 ├── 工厂模式 (Factory Pattern) │ ├── usb_alloc_urb():创建并初始化 URB 对象。 │ └── usb_alloc_isoc_urb():为等时传输创建特殊的 URB。 ├── 命令模式 (Command Pattern) │ └── urb 封装了完整的 USB 传输命令,包括数据、地址、回调。 ├── 观察者模式 (Observer Pattern) │ └── urb->complete:完成回调观察 URB 传输完成事件。 ├── 策略模式 (Strategy Pattern) │ ├── usb_fill_int_urb:中断传输策略。 │ ├── usb_fill_bulk_urb:批量传输策略。 │ └── usb_fill_isoc_urb:等时传输策略。 ├── 状态模式 (State Pattern) │ └── urb->status:URB 状态(SUBMITTED、COMPLETED、ERROR)。 ├── 装饰器模式 (Decorator Pattern) │ └── 为 URB 添加 sg 列表(分散/聚合传输)。 └── 代理模式 (Proxy Pattern) └── usb_submit_urb():代理对 URB 的提交操作,提供安全性检查。
4.5 URB 调试核心难点
4.5.1 URB 提交后回调未被调用
现象:usb_submit_urb() 返回成功,但 complete 回调始终未被调用。
原因:
-
URB 未正确绑定中断/批量/等时端点。
-
设备未正确配置,端点未激活。
-
缓冲区大小与端点描述符不匹配。
解决方法:
-
使用
usbmon检查 URB 是否实际到达硬件。 -
确认端点在
probe中已正确配置。 -
检查 USB 速度与端点带宽限制。
4.5.2 URB 缓冲区 DMA 映射失败
现象:usb_alloc_urb 后,usb_submit_urb 返回 -ENOMEM。
原因:
-
缓冲区未对齐(要求 4 字节对齐)。
-
缓冲区大小超出 DMA 能力。
-
设备处于禁用 DMA 状态。
解决方法:
-
使用
kmalloc并确保地址对齐(64 字节)。 -
使用
usb_buffer_alloc统一分配 DMA 缓冲。 -
检查 HCD 是否支持 DMA。
4.5.3 URB 内存泄漏
现象:长时间运行后内存占用上升。
原因:
-
complete回调中未调用usb_free_urb。 -
URB 被重复提交但未正确释放。
-
队列中未取消的 URB 累积。
解决方法:
-
在
disconnect中使用usb_kill_urb取消所有未完成的 URB。 -
每次 URB 完成后调用
usb_free_urb。 -
使用
usb_poison_urb停止队列中的 URB。
4.5.4 URB 超时处理
现象:URB 提交后长时间无响应。
原因:
-
设备硬件故障,不响应请求。
-
总线上设备过多,带宽不足。
-
端点配置错误,传输类型错误。
解决方法:
-
使用定时器检查 URB 超时。
-
重新提交 URB 或触发错误恢复。
-
使用
usb_unlink_urb手动取消。
4.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 通过 URB 提交数据到 HCD,完成回调通知核心层 | URB 状态、传输类型 |
| HCD 驱动 | 将 URB 转换为硬件事务,并调用完成回调 | DMA 映射、中断处理 |
| DMA 控制器 | 批量 URB 使用 DMA 进行数据传输 | 地址对齐、传输长度 |
| 中断控制器 | 中断 URB 触发硬件中断,调用完成回调 | 中断频率、延迟 |
| Input 子系统 | 中断 URB 将 HID 报告转换为 Input 事件 | 报告解析、事件类型 |
| V4L2 子系统 | 批量/等时 URB 传输视频帧数据 | 帧率、缓冲区管理 |
| ALSA 子系统 | 等时 URB 传输音频数据 | 采样率、同步 |
| 网络子系统 | 批量 URB 传输网络数据包 | 吞吐量、丢包率 |
| sysfs | 通过 sysfs 控制 URB 调试参数 | 调试开关、统计信息 |
第五部分 USB 枚举流程:从插入到配置完成
5.1 枚举流程概述
USB 设备的枚举过程是从物理连接开始,到设备可以被应用程序使用为止的完整初始化序列。整个流程包括:设备插入检测、供电、复位、获取描述符、分配地址、配置设备等步骤。
5.1.1 枚举流程文字流程图
[设备插入] -> [USB端口状态变化] -> [hub_thread 检测到连接] ↓ [hub_port_connect_change()] -> [usb_alloc_dev() 创建设备结构] ↓ [hub_port_reset()] -> [设备复位 (10ms)] -> [读取设备状态] ↓ [usb_get_device_descriptor()] -> [获取设备描述符 (前8字节)] ↓ [usb_control_msg()] -> [USB_REQ_SET_ADDRESS] -> [分配地址] ↓ [usb_get_device_descriptor()] -> [获取完整设备描述符 (18字节)] ↓ [usb_get_configuration()] -> [获取配置描述符] ↓ [usb_choose_configuration()] -> [选择配置] ↓ [usb_set_configuration()] -> [配置设备] ↓ [usb_register_device()] -> [驱动匹配并调用 probe]
5.1.2 枚举状态机
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 未插入 │─────>│ 已插入 │─────>│ 已供电 │ │ (State 0) │ │ (State 1) │ │ (State 2) │ └──────────────┘ └──────────────┘ └──────────────┘ ↑ ↓ ↓ │ │ │ │ │ │ └──────────────────────┴──────────────────────┘ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 已配置 │<─────│ 已分配地址 │<─────│ 已复位 │ │ (State 5) │ │ (State 4) │ │ (State 3) │ └──────────────┘ └──────────────┘ └──────────────┘
5.2 核心数据结构
5.2.1 枚举过程中的关键结构
/**
* @struct usb_device_descriptor
* @brief USB 设备描述符(由主机读取,了解设备能力)。
*/
struct usb_device_descriptor {
__u8 bLength; /**< 描述符长度 (18字节) */
__u8 bDescriptorType; /**< 描述符类型 (DEVICE = 0x01) */
__le16 bcdUSB; /**< USB 协议版本号 (BCD) */
__u8 bDeviceClass; /**< 设备类 */
__u8 bDeviceSubClass; /**< 设备子类 */
__u8 bDeviceProtocol; /**< 设备协议 */
__u8 bMaxPacketSize0; /**< 端点0最大包大小 */
__le16 idVendor; /**< 厂商 ID */
__le16 idProduct; /**< 产品 ID */
__le16 bcdDevice; /**< 设备版本号 (BCD) */
__u8 iManufacturer; /**< 厂商字符串索引 */
__u8 iProduct; /**< 产品字符串索引 */
__u8 iSerialNumber; /**< 序列号字符串索引 */
__u8 bNumConfigurations; /**< 配置数量 */
} __attribute__((packed));
/**
* @struct usb_config_descriptor
* @brief USB 配置描述符。
*/
struct usb_config_descriptor {
__u8 bLength; /**< 描述符长度 (9字节) */
__u8 bDescriptorType; /**< 描述符类型 (CONFIGURATION = 0x02) */
__le16 wTotalLength; /**< 配置描述符总长度 */
__u8 bNumInterfaces; /**< 接口数量 */
__u8 bConfigurationValue; /**< 配置值 */
__u8 iConfiguration; /**< 配置字符串索引 */
__u8 bmAttributes; /**< 配置属性 (总线供电/自供电) */
__u8 bMaxPower; /**< 最大功耗 (2mA单位) */
} __attribute__((packed));
5.3 核心代码实现
5.3.1 设备插入检测与端口状态轮询
/**
* @brief 集线器线程,定期轮询端口状态。
*/
static void hub_thread(struct work_struct *work)
{
struct usb_hub *hub = container_of(work, struct usb_hub, events);
struct usb_device *hdev = hub->hdev;
int i;
// 1. 遍历所有端口
for (i = 1; i <= hub->maxchild; i++) {
u16 portstatus, portchange;
int ret;
// 2. 读取端口状态
ret = hub_port_status(hub, i, &portstatus, &portchange);
if (ret < 0) continue;
// 3. 检查连接状态变化
if (portchange & USB_PORT_STAT_C_CONNECTION) {
// 触发连接变化处理
hub_port_connect_change(hub, i);
}
}
// 4. 继续轮询
schedule_work(&hub->events);
}
5.3.2 设备复位与地址分配
/**
* @brief 连接变化处理函数(核心枚举入口)。
*/
static void hub_port_connect_change(struct usb_hub *hub, int port1)
{
struct usb_device *hdev = hub->hdev;
struct usb_device *udev;
u16 portstatus, portchange;
int ret;
// 1. 读取端口状态
ret = hub_port_status(hub, port1, &portstatus, &portchange);
if (ret < 0) return;
// 2. 检查是否连接
if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
// 设备已拔出,清理
usb_remove_device(hub->children[port1]);
return;
}
// 3. 分配并初始化设备结构(对应状态:未插入 → 已插入)
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) return;
// 4. 设备复位(对应状态:已插入 → 已复位)
ret = hub_port_reset(hub, port1, udev);
if (ret < 0) {
dev_err(&hdev->dev, "reset failed, port %d\n", port1);
usb_put_dev(udev);
return;
}
// 5. 设置默认状态
usb_set_device_state(udev, USB_STATE_DEFAULT);
// 6. 获取设备描述符(前8字节,获取最大包大小)
ret = usb_get_device_descriptor(udev, 8);
if (ret < 8) {
dev_err(&hdev->dev, "get descriptor failed\n");
usb_put_dev(udev);
return;
}
// 7. 分配唯一地址(对应状态:已复位 → 已分配地址)
udev->devnum = hdev->bus->devnum_next++;
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_ADDRESS, 0,
udev->devnum, 0, NULL, 0, 1000);
if (ret < 0) {
dev_err(&hdev->dev, "set address failed\n");
usb_put_dev(udev);
return;
}
usb_set_device_state(udev, USB_STATE_ADDRESS);
}
5.3.3 获取描述符与配置设备
/**
* @brief 获取完整设备描述符并配置设备。
*/
static int hub_configure_device(struct usb_device *udev)
{
int ret;
// 1. 获取完整设备描述符(18字节)
ret = usb_get_device_descriptor(udev, sizeof(struct usb_device_descriptor));
if (ret < 0) {
dev_err(&udev->dev, "get full descriptor failed\n");
return ret;
}
// 2. 获取配置描述符(对应状态:已分配地址 → 已配置)
ret = usb_get_configuration(udev);
if (ret < 0) {
dev_err(&udev->dev, "get configuration failed\n");
return ret;
}
// 3. 选择并设置配置
ret = usb_choose_configuration(udev);
if (ret < 0) {
dev_err(&udev->dev, "choose configuration failed\n");
return ret;
}
// 4. 设置配置(对应状态:已配置)
ret = usb_set_configuration(udev, ret);
if (ret < 0) {
dev_err(&udev->dev, "set configuration failed\n");
return ret;
}
// 5. 更新设备状态
usb_set_device_state(udev, USB_STATE_CONFIGURED);
// 6. 注册设备到系统(触发驱动匹配)
ret = usb_register_device(udev);
if (ret < 0) {
dev_err(&udev->dev, "device registration failed\n");
return ret;
}
dev_info(&udev->dev, "USB device configured\n");
return 0;
}
5.3.4 端点配置与接口解析
/**
* @brief 解析配置描述符,配置端点。
*/
static int usb_parse_configuration(struct usb_device *dev,
struct usb_config_descriptor *config)
{
struct usb_interface *interface;
struct usb_host_interface *altsetting;
struct usb_endpoint_descriptor *endpoint;
unsigned char *buffer;
int interface_count = 0;
int endpoint_count = 0;
int i, j;
// 1. 分配接口数组
dev->actconfig = kzalloc(sizeof(struct usb_host_config), GFP_KERNEL);
if (!dev->actconfig) return -ENOMEM;
// 2. 遍历配置描述符中的接口
buffer = (unsigned char *)config + config->bLength;
for (i = 0; i < config->bNumInterfaces; i++) {
// 3. 读取接口描述符
interface = usb_alloc_interface(dev, i);
if (!interface) return -ENOMEM;
// 4. 解析接口及其替代设置
altsetting = &interface->altsetting[0];
memcpy(altsetting, buffer, config->bLength);
// 5. 解析端点
buffer += altsetting->desc.bLength;
for (j = 0; j < altsetting->desc.bNumEndpoints; j++) {
endpoint = (struct usb_endpoint_descriptor *)buffer;
// 保存端点信息
altsetting->endpoint[j].desc = *endpoint;
buffer += endpoint->bLength;
endpoint_count++;
}
// 6. 添加接口到设备
usb_add_interface(dev, interface);
interface_count++;
}
dev_info(&dev->dev, "Configured %d interfaces, %d endpoints\n",
interface_count, endpoint_count);
return 0;
}
5.4 软件设计模式树形分析
USB 枚举流程设计模式 ├── 状态模式 (State Pattern) │ └── usb_device_state:枚举过程中的状态机(未插入 → 已插入 → 已供电 → 已复位 → 已分配地址 → 已配置) ├── 工厂模式 (Factory Pattern) │ ├── usb_alloc_dev():创建并初始化设备结构 │ └── usb_alloc_interface():创建并初始化接口结构 ├── 模板方法模式 (Template Method Pattern) │ └── hub_port_connect_change():定义了枚举的标准流程模板(复位 → 分配地址 → 获取描述符 → 配置) ├── 策略模式 (Strategy Pattern) │ ├── usb_choose_configuration():选择配置策略 │ └── usb_set_configuration():配置设备策略 ├── 观察者模式 (Observer Pattern) │ └── hub_thread:观察端口状态变化,触发枚举 ├── 命令模式 (Command Pattern) │ └── usb_control_msg():发送控制命令请求 └── 适配器模式 (Adapter Pattern) └── usb_parse_configuration():将原始描述符数据适配为内核数据结构
5.5 枚举调试核心难点
5.5.1 枚举过程中复位失败
现象:hub_port_reset 返回 -ETIMEDOUT,设备无法被识别。
原因:
-
设备供电不足,无法完成复位。
-
设备固件损坏,不响应复位信号。
-
总线上存在短接。
解决方法:
-
检查 USB 供电电压和电流。
-
使用专用工具重新烧录设备固件。
-
检查 D+/D- 信号质量。
5.5.2 地址分配失败
现象:usb_control_msg 返回 -EPIPE,设备无法获取地址。
原因:
-
设备端点0不响应控制请求。
-
设备 ID 冲突(多个设备同地址)。
-
总线故障。
解决方法:
-
使用
usbmon抓取地址分配请求。 -
检查总线上的设备数量是否过多。
-
使用独立的 USB 控制器进行测试。
5.5.3 配置描述符解析失败
现象:usb_get_configuration 返回 -EPROTO,无法获取配置信息。
原因:
-
设备返回的配置描述符格式错误。
-
设备未能正确响应配置请求。
-
描述符长度超限。
解决方法:
-
使用
usb_control_msg直接读取配置描述符。 -
检查设备配置描述符是否符合规范。
-
强制设置最大包大小为 64。
5.5.4 枚举缓慢
现象:设备枚举时间超过 3 秒,系统启动延迟。
原因:
-
设备响应慢。
-
复位时间过短。
-
控制传输超时设置过长。
解决方法:
-
缩短复位等待时间。
-
优化描述符读取顺序(先读关键信息)。
-
调整控制传输超时值。
5.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 提供枚举 API(usb_get_device_descriptor、usb_set_configuration) |
传输成功率、状态机正确性 |
| HCD 驱动 | 执行底层控制传输 | 端点0数据包大小、传输超时 |
| PCI/Platform 驱动 | 提供物理 USB 控制器支持 | 寄存器访问、中断分配 |
| Input 子系统 | 设备配置后触发驱动匹配,加载 HID 驱动 | 报告描述符解析、设备注册 |
| sysfs | 通过 /sys/bus/usb/devices/ 查看枚举结果 |
设备地址、接口信息 |
| 电源管理 | 枚举完成后设备进入正常工作状态 | 供电状态、唤醒能力 |
| 内核线程 | hub_thread 定期轮询端口状态 |
轮询间隔、资源占用 |
| 设备驱动模型 | usb_register_device 触发驱动匹配 |
匹配算法、优先级 |
第六部分 USB 类驱动:HID、音频、视频、网络等
6.1 USB 类驱动概述
USB 设备类驱动是建立在 USB 核心驱动之上的通用驱动。它们根据设备类型(如 HID、音频、视频、网络、存储)提供标准化的接口,避免开发者重复实现相同功能。Linux 内核中的 USB 类驱动实现了从底层 URB 处理到上层子系统(Input、ALSA、V4L2、netdev)的完整转换。
6.1.1 USB 设备类分类表
| 设备类 | 类代码 | 典型设备 | 对应的内核子系统 |
|---|---|---|---|
| HID | 0x03 | 键盘、鼠标、游戏手柄 | Input 子系统 |
| 音频 | 0x01 | 耳机、麦克风、音箱 | ALSA 子系统 |
| 视频 | 0x0E | 摄像头、视频采集卡 | V4L2 子系统 |
| 网络 | 0xFF | 以太网适配器、4G 模块 | Netdev 子系统 |
| 存储 | 0x08 | U 盘、移动硬盘 | SCSI/Block 子系统 |
| CDC | 0x02 | 调制解调器、串口 | TTY/CDC 子系统 |
6.1.2 USB 类驱动在 Linux 内核中的位置
[用户空间] ↑ [应用程序] (如 alsa-utils, v4l2-ctl, evtest) ↑ [内核子系统] (Input, ALSA, V4L2, Netdev, SCSI) ↑ [USB 类驱动] (usbhid, snd-usb-audio, uvcvideo, rtl8152) ↑ [USB 核心层] (USB Core) ↑ [HCD 驱动] (EHCI/xHCI) ↑ [硬件]
6.2 核心数据结构
6.2.1 HID 类驱动核心结构
/**
* @struct hid_driver
* @brief HID 设备驱动核心结构。
* 每个 HID 设备驱动必须填充此结构并注册到 HID 核心。
*/
struct hid_driver {
const char *name; /**< 驱动名称 */
const struct hid_device_id *id_table; /**< 支持的设备 ID 列表 */
int (*probe)(struct hid_device *hdev, const struct hid_device_id *id);
void (*remove)(struct hid_device *hdev);
int (*input_mapping)(struct hid_device *hdev,
struct hid_input *hidinput,
struct hid_field *field,
struct hid_usage *usage,
unsigned long **bit,
int *max);
int (*event)(struct hid_device *hdev,
struct hid_field *field,
struct hid_usage *usage,
__s32 value);
void (*report)(struct hid_device *hdev, struct hid_report *report);
int (*raw_event)(struct hid_device *hdev,
struct hid_report *report,
u8 *data, int size);
};
/**
* @struct hid_device
* @brief HID 设备结构,代表一个物理 HID 设备。
*/
struct hid_device {
struct device dev; /**< 通用设备结构 */
struct usb_interface *intf; /**< 关联的 USB 接口 */
unsigned int hiddev; /**< HID 设备 ID */
struct hid_report_enum report_enum[HID_REPORT_TYPES];
struct list_head inputs; /**< 输入设备列表 */
struct list_head reports; /**< 报告列表 */
struct list_head debug_list; /**< 调试列表 */
void *driver_data; /**< 驱动私有数据 */
};
6.2.2 音频类驱动核心结构
/**
* @struct snd_usb_audio
* @brief USB 音频设备核心结构。
*/
struct snd_usb_audio {
struct snd_card *card; /**< ALSA 声卡核心 */
struct usb_device *dev; /**< USB 设备 */
struct snd_usb_endpoint *ep; /**< 端点列表 */
int samplerate; /**< 当前采样率 */
int channels; /**< 当前通道数 */
int format; /**< 音频格式 (PCM/DSD) */
struct list_head stream_list; /**< 流列表 */
struct list_head midi_list; /**< MIDI 列表 */
unsigned int autopm:1; /**< 自动电源管理 */
};
6.3 核心代码实现
6.3.1 USB HID 类驱动实现
/**
* @brief USB HID 设备 probe 函数。
* @param intf 指向 USB 接口
* @param id 指向匹配的 USB 设备 ID
* @return 0 成功,负数错误
*/
static int usb_hid_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct hid_device *hdev;
int ret;
// 1. 分配 HID 设备结构
hdev = hid_allocate_device();
if (IS_ERR(hdev)) {
return PTR_ERR(hdev);
}
// 2. 设置 HID 设备属性
hdev->dev.parent = &intf->dev;
hdev->bus = BUS_USB;
hdev->vendor = le16_to_cpu(dev->descriptor.idVendor);
hdev->product = le16_to_cpu(dev->descriptor.idProduct);
hdev->country = 0;
hdev->hiddev = 0;
// 3. 设置驱动数据
hid_set_drvdata(hdev, intf);
usb_set_intfdata(intf, hdev);
// 4. 读取 HID 报告描述符
ret = hid_parse_report(hdev, hid_get_descriptor(dev, HID_DT_REPORT, 0),
hid_get_descriptor_len(dev, HID_DT_REPORT, 0));
if (ret) {
dev_err(&intf->dev, "report descriptor parse failed\n");
goto error;
}
// 5. 注册 HID 设备到 HID 核心
ret = hid_add_device(hdev);
if (ret) {
dev_err(&intf->dev, "device add failed\n");
goto error;
}
// 6. 注册 Input 设备(如果支持)
if (hdev->group == HID_GROUP_INPUT) {
ret = hid_input_register(hdev);
if (ret) {
dev_err(&intf->dev, "input register failed\n");
goto error;
}
}
dev_info(&intf->dev, "USB HID device registered\n");
return 0;
error:
hid_destroy_device(hdev);
return ret;
}
/**
* @brief 处理 HID 报告(键盘按键处理示例)。
*/
static int usb_hid_event(struct hid_device *hdev,
struct hid_field *field,
struct hid_usage *usage,
__s32 value)
{
struct input_dev *input = NULL;
int key = 0;
// 1. 查找对应的 input 设备
if (field->hidinput) {
input = field->hidinput->input;
}
// 2. 处理 HID 用法(Usage)
switch (usage->hid) {
case HID_GD_UP:
key = KEY_UP;
break;
case HID_GD_DOWN:
key = KEY_DOWN;
break;
case HID_GD_LEFT:
key = KEY_LEFT;
break;
case HID_GD_RIGHT:
key = KEY_RIGHT;
break;
default:
// 其他用法处理
break;
}
// 3. 上报按键事件
if (input && key) {
input_event(input, EV_KEY, key, value);
input_sync(input);
}
return 0;
}
6.3.2 USB 音频类驱动实现
/**
* @brief USB 音频设备驱动 probe 函数。
* @param intf 指向 USB 接口
* @param id 指向匹配的 USB 设备 ID
* @return 0 成功,负数错误
*/
static int snd_usb_audio_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct snd_usb_audio *chip;
int ret;
// 1. 分配声卡结构
chip = snd_usb_create_card(intf, 0);
if (IS_ERR(chip)) {
return PTR_ERR(chip);
}
// 2. 初始化声卡
chip->dev = dev;
ret = snd_card_new(&intf->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, 0, &chip->card);
if (ret < 0) {
dev_err(&intf->dev, "card creation failed\n");
return ret;
}
// 3. 解析音频流接口
ret = snd_usb_parse_audio_interface(chip->card, intf);
if (ret < 0) {
dev_err(&intf->dev, "audio interface parse failed\n");
return ret;
}
// 4. 注册 ALSA 设备
ret = snd_card_register(chip->card);
if (ret < 0) {
dev_err(&intf->dev, "card register failed\n");
return ret;
}
// 5. 设置接口数据
usb_set_intfdata(intf, chip);
dev_info(&intf->dev, "USB audio device registered\n");
return 0;
}
/**
* @brief USB 音频播放处理函数。
*/
static int snd_usb_audio_playback(struct snd_usb_audio *chip,
void *buffer,
int length,
int stream_id)
{
struct urb *urb;
int ret;
// 1. 创建等时传输 URB
urb = usb_alloc_urb(8, GFP_ATOMIC);
if (!urb) return -ENOMEM;
// 2. 填充 URB
usb_fill_isoc_urb(urb, chip->dev,
usb_sndisocpipe(chip->dev, stream_id),
buffer, length,
snd_usb_audio_irq, chip, 1);
// 3. 提交等时传输
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(&chip->dev->dev, "isoc urb submit failed\n");
usb_free_urb(urb);
return ret;
}
return 0;
}
6.3.3 USB 视频类驱动实现
/**
* @brief USB 视频设备驱动 probe 函数。
* @param intf 指向 USB 接口
* @param id 指向匹配的 USB 设备 ID
* @return 0 成功,负数错误
*/
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct uvc_device *uvc;
int ret;
// 1. 创建 UVC 设备结构
uvc = kzalloc(sizeof(struct uvc_device), GFP_KERNEL);
if (!uvc) return -ENOMEM;
// 2. 初始化 V4L2 设备
uvc->vdev = video_device_alloc();
if (!uvc->vdev) {
kfree(uvc);
return -ENOMEM;
}
// 3. 解析 UVC 控制接口
ret = uvc_parse_control(uvc, intf);
if (ret < 0) {
dev_err(&intf->dev, "control interface parse failed\n");
goto error;
}
// 4. 创建 V4L2 设备节点
uvc->vdev->v4l2_dev = &uvc->v4l2_dev;
uvc->vdev->fops = &uvc_fops;
uvc->vdev->ioctl_ops = &uvc_ioctl_ops;
uvc->vdev->release = video_device_release;
ret = video_register_device(uvc->vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
dev_err(&intf->dev, "video device register failed\n");
goto error;
}
// 5. 设置接口数据
usb_set_intfdata(intf, uvc);
dev_info(&intf->dev, "UVC camera device registered\n");
return 0;
error:
kfree(uvc);
return ret;
}
/**
* @brief UVC 视频帧处理函数。
*/
static int uvc_process_frame(struct uvc_device *uvc,
void *buffer,
int length)
{
struct vb2_buffer *vb = uvc->vb2_buf;
void *dst;
int ret;
// 1. 获取 VB2 缓冲区
if (!vb || vb->state != VB2_BUF_STATE_ACTIVE) {
return -EINVAL;
}
// 2. 将数据复制到 V4L2 缓冲区
dst = vb2_plane_vaddr(vb, 0);
if (!dst) {
return -EFAULT;
}
ret = uvc_video_copy_frame(buffer, dst, length);
if (ret < 0) {
return ret;
}
// 3. 标记缓冲区完成
vb->timestamp = ktime_get_ns();
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
return 0;
}
6.4 软件设计模式树形分析
USB 类驱动设计模式 ├── 工厂模式 (Factory Pattern) │ ├── hid_allocate_device():创建 HID 设备结构 │ ├── snd_card_new():创建 ALSA 声卡结构 │ └── video_device_alloc():创建 V4L2 视频设备结构 ├── 适配器模式 (Adapter Pattern) │ ├── usbhid:将 USB 中断传输适配为 HID 报告 │ ├── snd-usb-audio:将 USB 等时传输适配为 ALSA PCM 流 │ └── uvcvideo:将 USB 批量传输适配为 V4L2 视频帧 ├── 策略模式 (Strategy Pattern) │ ├── hid_input_register:注册 Input 设备策略 │ └── uvc_parse_control:解析 UVC 控制策略 ├── 观察者模式 (Observer Pattern) │ ├── hid_report:观察 HID 报告事件 │ └── snd_usb_audio_irq:观察音频完成事件 ├── 装饰器模式 (Decorator Pattern) │ ├── 为 HID 报告添加时间戳 │ └── 为 UVC 帧添加序列号 └── 模板方法模式 (Template Method Pattern) ├── usb_hid_probe:定义 HID 设备初始化的标准流程 ├── snd_usb_audio_probe:定义 USB 音频设备初始化的标准流程 └── uvc_probe:定义 UVC 设备初始化的标准流程
6.5 类驱动调试核心难点
6.5.1 HID 报告描述符解析失败
现象:hid_parse_report 返回 -EINVAL,键盘/鼠标无法工作。
原因:
-
设备返回的报告描述符格式错误。
-
描述符中存在未定义的用法(Usage)。
-
描述符长度与设备报告不符。
解决方法:
-
使用
hidraw直接读取报告描述符。 -
使用
usbmon抓取描述符传输数据。 -
添加调试打印,输出描述符内容。
6.5.2 USB 音频等时传输中断
现象:音频播放过程中出现爆音或卡顿,dmesg 显示 URB_ISO_BUF
原因:
-
USB 带宽不足,无法满足等时传输需求。
-
设备驱动中的等时传输参数配置不当。
-
系统负载过高,无法及时处理中断。
解决方法:
-
降低采样率或通道数,减少带宽需求。
-
调整等时传输间隔(
bInterval)参数。 -
使用实时优先级提升音频线程。
6.5.3 UVC 视频帧丢失
现象:视频预览卡顿,dmesg 显示 vb2_buffer 相关错误。
原因:
-
USB 批量传输数据包丢失。
-
缓冲区队列设置不当,没有足够缓冲区。
-
视频格式协商失败。
解决方法:
-
增加缓冲区队列深度(
VIDIOC_REQBUFS)。 -
使用
usb_bulk_msg替代异步 URB。 -
检查视频格式协商是否正确。
6.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| HID 核心 | 将 USB HID 报告转换为标准 Input 事件 | 报告解析、事件映射 |
| Input 子系统 | 接收 HID 驱动上报的按键、鼠标事件 | 事件类型、重复触发 |
| ALSA 核心 | 通过 PCM 接口播放/录制音频数据 | 采样率匹配、缓冲区管理 |
| V4L2 核心 | 通过视频设备节点传输视频帧 | 帧格式协商、缓冲区队列 |
| NET 核心 | 通过网卡设备节点传输网络数据包 | 吞吐量、丢包率 |
| USB 核心层 | 通过 URB 与 USB 设备进行数据交换 | 传输类型、端点配置 |
| HCD 驱动 | 执行底层 USB 数据传输 | 传输状态、错误处理 |
| sysfs | 通过 sysfs 查看和配置类设备参数 | 设备信息、调试开关 |
| 用户空间 | 通过类设备节点访问设备功能 | 权限控制、设备命名 |
第七部分 Linux Platform 驱动与 USB 设备的交互
7.1 Platform 驱动与 USB 的关联
在嵌入式系统中,USB 控制器通常作为 Platform 设备集成在 SoC 内部,而不是通过 PCI 总线连接。Linux USB 子系统需要与 Platform 驱动框架紧密协作,完成 USB 控制器的初始化、电源管理和中断处理。
7.1.1 Platform USB 控制器架构
[设备树 (DTS)] ↓ [Platform 设备注册] ↓ [USB 控制器 Platform 驱动] ├── 硬件初始化 (时钟、复位、PHY) ├── HCD 注册 (usb_add_hcd) └── 电源管理 (suspend/resume) ↓ [USB 核心层] ↔ [HCD 驱动] ↓ [USB 设备驱动]
7.1.2 Platform USB 控制器典型硬件架构
+------------------+ | SoC | | +------------+ | | | USB 控制器 | | | | (DWC3/ EHCI)| | | +------------+ | | | | | +------------+ | | | USB PHY | | | +------------+ | +------------------+ | [USB 接口]
7.2 核心数据结构
7.2.1 Platform USB 控制器私有数据结构
/**
* @struct platform_usb_priv
* @brief Platform USB 控制器私有数据结构。
* 管理 Platform 设备特定的硬件资源。
*/
struct platform_usb_priv {
struct platform_device *pdev; /**< Platform 设备指针 */
struct usb_hcd *hcd; /**< 关联的 HCD */
struct clk *clk; /**< 控制器时钟 */
struct clk *pclk; /**< 总线时钟 */
struct reset_control *rst; /**< 复位控制 */
struct phy *phy; /**< USB PHY */
int irq; /**< 中断号 */
void __iomem *base; /**< 寄存器基址 */
dma_addr_t dma_base; /**< DMA 基址 */
struct device *dev; /**< 设备指针 */
struct regulator *vbus; /**< VBUS 电源 */
bool usb2_phy_enabled; /**< USB2 PHY 使能 */
bool usb3_phy_enabled; /**< USB3 PHY 使能 */
spinlock_t lock; /**< 自旋锁 */
struct work_struct irq_work; /**< 中断工作队列 */
};
/**
* @struct usb_platform_data
* @brief USB Platform 数据,从设备树解析。
*/
struct usb_platform_data {
enum usb_dr_mode dr_mode; /**< 双角色模式 (host/device/otg) */
bool host_mode; /**< 是否 Host 模式 */
bool device_mode; /**< 是否 Device 模式 */
int phy_type; /**< PHY 类型 (utmi/ulpi) */
u32 maximum_speed; /**< 最大速度 (high/super) */
int vbus_gpio; /**< VBUS 控制 GPIO */
bool vbus_active_high; /**< VBUS 高电平有效 */
int power_off_delay; /**< 断电延迟 (ms) */
struct regulator *vbus_reg; /**< VBUS 调节器 */
};
7.3 核心代码实现
7.3.1 Platform USB 控制器驱动初始化
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/phy/phy.h>
#include <linux/usb/hcd.h>
#include <linux/usb/otg.h>
/**
* @brief Platform USB 控制器 probe 函数。
* @param pdev Platform 设备指针
* @return 0 成功,负数错误
*/
static int platform_usb_probe(struct platform_device *pdev)
{
struct platform_usb_priv *priv;
struct usb_hcd *hcd;
struct resource *res;
int ret;
// 1. 分配私有数据结构
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
priv->pdev = pdev;
spin_lock_init(&priv->lock);
INIT_WORK(&priv->irq_work, platform_usb_irq_work);
// 2. 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENXIO;
}
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base)) return PTR_ERR(priv->base);
// 3. 获取中断
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) return priv->irq;
// 4. 获取时钟
priv->clk = devm_clk_get(&pdev->dev, "usb_clk");
if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "Failed to get usb_clk\n");
return PTR_ERR(priv->clk);
}
priv->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(priv->pclk)) {
dev_err(&pdev->dev, "Failed to get pclk\n");
return PTR_ERR(priv->pclk);
}
clk_prepare_enable(priv->clk);
clk_prepare_enable(priv->pclk);
// 5. 获取复位控制
priv->rst = devm_reset_control_get(&pdev->dev, "usb");
if (IS_ERR(priv->rst)) {
dev_err(&pdev->dev, "Failed to get reset\n");
ret = PTR_ERR(priv->rst);
goto err_clk;
}
// 6. 获取 USB PHY
priv->phy = devm_phy_get(&pdev->dev, "usb_phy");
if (IS_ERR(priv->phy)) {
dev_err(&pdev->dev, "Failed to get phy\n");
ret = PTR_ERR(priv->phy);
goto err_clk;
}
phy_init(priv->phy);
phy_power_on(priv->phy);
// 7. 获取 VBUS 电源
priv->vbus = devm_regulator_get(&pdev->dev, "vbus");
if (IS_ERR(priv->vbus)) {
dev_err(&pdev->dev, "Failed to get vbus\n");
ret = PTR_ERR(priv->vbus);
goto err_phy;
}
regulator_enable(priv->vbus);
// 8. 创建 HCD
hcd = usb_create_hcd(&platform_usb_hc_driver, &pdev->dev,
dev_name(&pdev->dev));
if (!hcd) {
dev_err(&pdev->dev, "Failed to create HCD\n");
ret = -ENOMEM;
goto err_phy;
}
// 9. 关联私有数据到 HCD
hcd->driver_data = priv;
hcd->has_tt = 1; // 事务翻译器支持
hcd->speed = USB_SPEED_HIGH;
priv->hcd = hcd;
// 10. 注册 HCD
ret = usb_add_hcd(hcd, priv->irq, IRQF_SHARED);
if (ret) {
dev_err(&pdev->dev, "Failed to add HCD\n");
usb_put_hcd(hcd);
goto err_phy;
}
// 11. 设置平台驱动数据
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "Platform USB controller initialized\n");
return 0;
err_phy:
phy_power_off(priv->phy);
phy_exit(priv->phy);
err_clk:
clk_disable_unprepare(priv->pclk);
clk_disable_unprepare(priv->clk);
return ret;
}
/**
* @brief Platform USB 控制器 remove 函数。
* @param pdev Platform 设备指针
* @return 0 成功
*/
static int platform_usb_remove(struct platform_device *pdev)
{
struct platform_usb_priv *priv = platform_get_drvdata(pdev);
// 1. 移除 HCD
if (priv->hcd) {
usb_remove_hcd(priv->hcd);
usb_put_hcd(priv->hcd);
}
// 2. 关闭 PHY
if (priv->phy) {
phy_power_off(priv->phy);
phy_exit(priv->phy);
}
// 3. 关闭 VBUS
if (priv->vbus) {
regulator_disable(priv->vbus);
}
// 4. 关闭时钟
clk_disable_unprepare(priv->pclk);
clk_disable_unprepare(priv->clk);
// 5. 释放复位
reset_control_assert(priv->rst);
return 0;
}
7.3.2 设备树绑定解析
/**
* @brief 从设备树解析 USB 控制器配置。
* @param pdev Platform 设备指针
* @param pdata 输出 Platform 数据
* @return 0 成功,负数错误
*/
static int platform_usb_parse_dt(struct platform_device *pdev,
struct usb_platform_data *pdata)
{
struct device_node *np = pdev->dev.of_node;
u32 dr_mode;
int ret;
// 1. 解析 dr_mode (双角色模式)
ret = of_property_read_u32(np, "dr_mode", &dr_mode);
if (ret) {
pdata->dr_mode = USB_DR_MODE_HOST;
} else {
pdata->dr_mode = (enum usb_dr_mode)dr_mode;
}
// 2. 解析最大速度
ret = of_property_read_u32(np, "maximum-speed", &pdata->maximum_speed);
if (ret) {
pdata->maximum_speed = USB_SPEED_HIGH;
}
// 3. 解析 PHY 类型
ret = of_property_read_u32(np, "phy_type", &pdata->phy_type);
if (ret) {
pdata->phy_type = 0; // 默认 UTMI
}
// 4. 解析 VBUS GPIO
pdata->vbus_gpio = of_get_named_gpio(np, "vbus-gpios", 0);
if (gpio_is_valid(pdata->vbus_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, pdata->vbus_gpio,
GPIOF_OUT_INIT_LOW, "vbus");
if (ret) {
dev_err(&pdev->dev, "Failed to request vbus GPIO\n");
return ret;
}
}
// 5. 解析电源管理参数
of_property_read_u32(np, "power-off-delay", &pdata->power_off_delay);
return 0;
}
7.3.3 Platform USB 控制器中断处理
/**
* @brief Platform USB 控制器中断处理函数。
* @param irq 中断号
* @param dev_id 设备私有数据
* @return 中断处理结果
*/
static irqreturn_t platform_usb_irq_handler(int irq, void *dev_id)
{
struct platform_usb_priv *priv = dev_id;
u32 int_status;
// 1. 读取中断状态寄存器
int_status = readl(priv->base + 0x20);
if (!int_status) {
return IRQ_NONE;
}
// 2. 清除中断标志
writel(int_status, priv->base + 0x24);
// 3. 处理特定中断
if (int_status & 0x01) {
// 主机模式中断
schedule_work(&priv->irq_work);
}
if (int_status & 0x02) {
// 设备模式中断
dev_dbg(priv->dev, "Device mode interrupt\n");
}
// 4. 调用 HCD 中断处理
if (priv->hcd) {
usb_hcd_irq(irq, priv->hcd);
}
return IRQ_HANDLED;
}
/**
* @brief 中断工作队列处理函数。
*/
static void platform_usb_irq_work(struct work_struct *work)
{
struct platform_usb_priv *priv = container_of(work, struct platform_usb_priv, irq_work);
// 处理需要非中断上下文的工作
// 例如:电源状态切换、PHY 重新配置等
if (priv->hcd && priv->hcd->state == HC_STATE_RUNNING) {
// 保持正常运行
} else {
// 可能需要恢复 HCD
usb_hcd_resume(priv->hcd);
}
}
7.3.4 设备树节点示例
/* DTS 节点示例: USB 控制器 */
usb@ff500000 {
compatible = "vendor,platform-usb";
reg = <0xff500000 0x2000>;
interrupts = <0 10 4>;
clocks = <&clk_usb>, <&clk_bus>;
clock-names = "usb_clk", "pclk";
resets = <&reset_usb>;
reset-names = "usb";
phys = <&usb_phy>;
phy-names = "usb_phy";
vbus-supply = <&vbus_reg>;
dr_mode = "host";
maximum-speed = "high-speed";
power-off-delay = <100>;
status = "okay";
};
7.4 软件设计模式树形分析
Platform USB 驱动设计模式 ├── 工厂模式 (Factory Pattern) │ ├── usb_create_hcd():创建 HCD 实例 │ └── phy_get():获取 PHY 实例 ├── 适配器模式 (Adapter Pattern) │ └── platform_usb_probe():将 Platform 设备适配为 USB HCD ├── 策略模式 (Strategy Pattern) │ └── usb_dr_mode:Host/Device/OTG 模式切换策略 ├── 观察者模式 (Observer Pattern) │ └── platform_usb_irq_handler:观察硬件中断事件 ├── 代理模式 (Proxy Pattern) │ └── usb_add_hcd():代理 HCD 注册到 USB 核心 └── 模板方法模式 (Template Method Pattern) └── platform_usb_probe():定义了 Platform USB 控制器的标准初始化流程
7.5 Platform USB 调试核心难点
7.5.1 设备树解析失败
现象:of_property_read_u32 返回 -EINVAL,驱动无法加载。
原因:
-
设备树节点中缺少必需属性。
-
属性名称拼写错误。
-
设备树未正确编译。
解决方法:
-
使用
dtc -I fs /sys/firmware/devicetree/base/检查设备树。 -
添加调试打印,输出解析参数。
-
检查设备树文件是否正确。
7.5.2 PHY 初始化失败
现象:phy_init() 返回 -ENODEV,USB 控制器无法工作。
原因:
-
PHY 驱动未加载。
-
PHY 设备树节点不存在。
-
PHY 硬件未上电。
解决方法:
-
检查
ls /sys/class/phy/确认 PHY 驱动加载。 -
检查设备树中的 PHY 节点。
-
检查 PHY 供电和复位电路。
7.5.3 时钟频率错误
现象:USB 控制器无法识别设备,或传输频繁出错。
原因:
-
时钟频率与 USB 要求不匹配。
-
时钟分频设置错误。
-
时钟未使能。
解决方法:
-
使用
clk_summary检查时钟状态。 -
根据 USB 规范配置时钟分频。
-
检查时钟驱动实现。
7.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| 设备树 (DTS) | 提供 USB 控制器硬件描述 | 节点解析、属性检查 |
| 时钟控制器 | 提供 USB 控制器时钟 | 时钟频率、使能状态 |
| 复位控制器 | 提供 USB 控制器复位 | 复位序列、延时 |
| PHY 驱动 | 提供 USB PHY 硬件支持 | PHY 状态、电源管理 |
| 电源管理 | 管理 VBUS 电源和控制器电源 | 上下电序列、唤醒 |
| GPIO 驱动 | 控制 VBUS 电源 GPIO | GPIO 状态、电流能力 |
| 中断控制器 | 路由 USB 控制器中断 | 中断号、亲和性 |
| DMA 控制器 | 提供 DMA 传输支持 | 传输完成、DMA 错误 |
第八部分 USB 控制器设备树绑定与硬件描述
8.1 设备树在 USB 控制器中的核心作用
在嵌入式系统中,设备树(Device Tree)是描述硬件配置的标准方式。对于 USB 控制器,设备树节点提供了寄存器地址、中断号、时钟、PHY、电源管理、DMA 配置等所有硬件信息。正确的设备树绑定是 USB 控制器驱动初始化的前提。
8.1.1 设备树节点与驱动映射关系
[设备树节点 (DTS)] ↓ [内核设备树解析] ↓ [Platform 设备注册] ↓ [USB 控制器驱动 Probe] ↓ [硬件初始化] ↓ [USB 核心层注册]
8.1.2 典型 USB 控制器设备树节点结构
usb@ff500000 {
compatible = "vendor,usb-controller";
reg = <0xff500000 0x2000>;
interrupts = <0 10 4>;
clocks = <&clk_usb>, <&clk_bus>;
clock-names = "usb_clk", "pclk";
resets = <&reset_usb>;
reset-names = "usb";
phys = <&usb_phy>;
phy-names = "usb_phy";
vbus-supply = <&vbus_reg>;
dr_mode = "host";
maximum-speed = "high-speed";
power-off-delay = <100>;
status = "okay";
};
8.2 核心数据结构
8.2.1 设备树解析相关结构
/**
* @struct of_usb_config
* @brief 从设备树解析的 USB 配置结构。
*/
struct of_usb_config {
struct resource *res; /**< 寄存器资源 */
int irq; /**< 中断号 */
struct clk *clk; /**< 控制器时钟 */
struct clk *pclk; /**< 总线时钟 */
struct reset_control *rst; /**< 复位控制 */
struct phy *phy; /**< USB PHY */
struct regulator *vbus; /**< VBUS 电源 */
enum usb_dr_mode dr_mode; /**< 双角色模式 */
enum usb_device_speed max_speed; /**< 最大速度 */
int vbus_gpio; /**< VBUS 控制 GPIO */
int power_off_delay; /**< 断电延迟 (ms) */
const char *phy_type; /**< PHY 类型 */
u32 dma_capable; /**< DMA 能力 */
u32 quirks; /**< 设备特殊标志 */
};
/**
* @struct usb_phy_bind
* @brief USB PHY 绑定结构。
*/
struct usb_phy_bind {
const char *compatible; /**< PHY 兼容字符串 */
const char *phy_name; /**< PHY 名称 */
const char *device_node; /**< 设备节点名称 */
const char *phy_type; /**< PHY 类型 (utmi/ulpi) */
u32 phy_interface; /**< PHY 接口类型 */
u32 phy_id; /**< PHY ID */
u32 phy_version; /**< PHY 版本 */
};
8.3 核心代码实现
8.3.1 设备树解析核心函数
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/clk-provider.h>
#include <linux/reset-controller.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
/**
* @brief 从设备树解析 USB 控制器资源。
* @param pdev Platform 设备指针
* @param config 输出解析后的配置
* @return 0 成功,负数错误
*/
static int usb_of_parse_config(struct platform_device *pdev,
struct of_usb_config *config)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
u32 val;
int ret;
// 1. 解析寄存器资源
config->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!config->res) {
dev_err(dev, "No memory resource in device tree\n");
return -ENXIO;
}
// 2. 解析中断
config->irq = platform_get_irq(pdev, 0);
if (config->irq < 0) {
dev_err(dev, "No interrupt in device tree\n");
return config->irq;
}
// 3. 解析时钟
config->clk = of_clk_get_by_name(np, "usb_clk");
if (IS_ERR(config->clk)) {
dev_err(dev, "No usb_clk in device tree\n");
return PTR_ERR(config->clk);
}
config->pclk = of_clk_get_by_name(np, "pclk");
if (IS_ERR(config->pclk)) {
dev_err(dev, "No pclk in device tree\n");
return PTR_ERR(config->pclk);
}
// 4. 解析复位
config->rst = of_reset_control_get(np, "usb");
if (IS_ERR(config->rst)) {
dev_err(dev, "No reset in device tree\n");
return PTR_ERR(config->rst);
}
// 5. 解析 PHY
config->phy = of_phy_get(np, "usb_phy");
if (IS_ERR(config->phy)) {
dev_err(dev, "No PHY in device tree\n");
return PTR_ERR(config->phy);
}
// 6. 解析 VBUS 电源
config->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(config->vbus)) {
dev_err(dev, "No vbus regulator in device tree\n");
return PTR_ERR(config->vbus);
}
// 7. 解析 dr_mode
ret = of_property_read_u32(np, "dr_mode", &val);
if (ret) {
config->dr_mode = USB_DR_MODE_HOST;
} else {
config->dr_mode = (enum usb_dr_mode)val;
}
// 8. 解析最大速度
ret = of_property_read_u32(np, "maximum-speed", &val);
if (ret) {
config->max_speed = USB_SPEED_HIGH;
} else {
config->max_speed = (enum usb_device_speed)val;
}
// 9. 解析 VBUS GPIO
config->vbus_gpio = of_get_named_gpio(np, "vbus-gpios", 0);
if (gpio_is_valid(config->vbus_gpio)) {
ret = devm_gpio_request_one(dev, config->vbus_gpio,
GPIOF_OUT_INIT_LOW, "vbus");
if (ret) {
dev_err(dev, "Failed to request vbus GPIO\n");
return ret;
}
}
// 10. 解析其他参数
of_property_read_u32(np, "power-off-delay", &config->power_off_delay);
of_property_read_u32(np, "dma-capable", &config->dma_capable);
of_property_read_u32(np, "quirks", &config->quirks);
return 0;
}
8.3.2 设备树节点匹配与绑定
/**
* @brief USB 控制器设备树匹配表。
*/
static const struct of_device_id usb_of_match[] = {
{ .compatible = "vendor,usb-controller" },
{ .compatible = "snps,dwc3" },
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "allwinner,sun8i-h3-dwc3" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, usb_of_match);
/**
* @brief USB 控制器设备树绑定函数。
* @param pdev Platform 设备指针
* @return 0 成功,负数错误
*/
static int usb_of_bind(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *phy_node;
u32 phy_mode;
int ret;
// 1. 检查兼容性
if (!of_match_node(usb_of_match, np)) {
dev_err(&pdev->dev, "No compatible match\n");
return -ENODEV;
}
// 2. 获取 PHY 节点
phy_node = of_parse_phandle(np, "phys", 0);
if (!phy_node) {
dev_err(&pdev->dev, "No PHY phandle\n");
return -ENODEV;
}
// 3. 解析 PHY 模式
ret = of_property_read_u32(phy_node, "phy-mode", &phy_mode);
if (ret) {
dev_err(&pdev->dev, "No phy-mode in PHY node\n");
return ret;
}
// 4. 绑定 PHY
ret = of_phy_bind(pdev->dev.parent, phy_node, "usb_phy");
if (ret) {
dev_err(&pdev->dev, "PHY bind failed\n");
return ret;
}
// 5. 设置 DMA 配置
if (of_dma_is_coherent(np)) {
dev_dbg(&pdev->dev, "Device is DMA coherent\n");
}
// 6. 保存绑定信息
platform_set_drvdata(pdev, (void *)np);
return 0;
}
8.3.3 设备树资源映射
/**
* @brief 从设备树映射 USB 控制器寄存器。
* @param pdev Platform 设备指针
* @param base 输出映射后的寄存器基址
* @return 0 成功,负数错误
*/
static int usb_of_map_registers(struct platform_device *pdev,
void __iomem **base)
{
struct resource *res;
void __iomem *reg_base;
// 1. 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENXIO;
}
// 2. 映射寄存器
reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(reg_base)) {
dev_err(&pdev->dev, "Failed to ioremap\n");
return PTR_ERR(reg_base);
}
*base = reg_base;
return 0;
}
/**
* @brief 从设备树获取中断信息。
* @param pdev Platform 设备指针
* @param irq 输出中断号
* @return 0 成功,负数错误
*/
static int usb_of_get_irq(struct platform_device *pdev, int *irq)
{
int ret;
ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(&pdev->dev, "No interrupt in device tree\n");
return ret;
}
*irq = ret;
return 0;
}
8.3.4 设备树时钟解析
/**
* @brief 从设备树解析 USB 控制器时钟。
* @param pdev Platform 设备指针
* @param clocks 输出时钟结构
* @return 0 成功,负数错误
*/
static int usb_of_get_clocks(struct platform_device *pdev,
struct usb_clocks *clocks)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
int ret;
// 1. 获取主时钟
clocks->clk = of_clk_get_by_name(np, "usb_clk");
if (IS_ERR(clocks->clk)) {
dev_err(dev, "No usb_clk in device tree\n");
return PTR_ERR(clocks->clk);
}
// 2. 获取总线时钟
clocks->pclk = of_clk_get_by_name(np, "pclk");
if (IS_ERR(clocks->pclk)) {
dev_err(dev, "No pclk in device tree\n");
return PTR_ERR(clocks->pclk);
}
// 3. 获取 PHY 时钟
clocks->phy_clk = of_clk_get_by_name(np, "phy_clk");
if (IS_ERR(clocks->phy_clk)) {
dev_dbg(dev, "No phy_clk in device tree\n");
}
// 4. 获取参考时钟
clocks->ref_clk = of_clk_get_by_name(np, "ref_clk");
if (IS_ERR(clocks->ref_clk)) {
dev_dbg(dev, "No ref_clk in device tree\n");
}
return 0;
}
8.3.5 设备树 PHY 配置解析
/**
* @brief 从设备树解析 USB PHY 配置。
* @param pdev Platform 设备指针
* @param phy_config 输出 PHY 配置
* @return 0 成功,负数错误
*/
static int usb_of_parse_phy(struct platform_device *pdev,
struct usb_phy_config *phy_config)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *phy_np;
u32 phy_type;
int ret;
// 1. 解析 PHY 节点
phy_np = of_parse_phandle(np, "phys", 0);
if (!phy_np) {
dev_err(&pdev->dev, "No PHY phandle\n");
return -ENODEV;
}
// 2. 解析 PHY 类型
ret = of_property_read_u32(phy_np, "phy-type", &phy_type);
if (ret) {
phy_config->phy_type = PHY_TYPE_UTMI;
} else {
phy_config->phy_type = (enum phy_type)phy_type;
}
// 3. 解析 PHY 接口
ret = of_property_read_u32(phy_np, "phy-interface", &phy_config->phy_interface);
if (ret) {
phy_config->phy_interface = PHY_INTERFACE_MODE_UTMI;
}
// 4. 解析 PHY 速度
ret = of_property_read_u32(phy_np, "maximum-speed", &phy_config->max_speed);
if (ret) {
phy_config->max_speed = USB_SPEED_HIGH;
}
// 5. 解析 PHY 电源
of_property_read_bool(phy_np, "power-off-in-suspend", &phy_config->power_off_in_suspend);
return 0;
}
8.3.6 设备树电源管理解析
/**
* @brief 从设备树解析 USB 电源管理配置。
* @param pdev Platform 设备指针
* @param pm_config 输出电源管理配置
* @return 0 成功,负数错误
*/
static int usb_of_parse_pm(struct platform_device *pdev,
struct usb_pm_config *pm_config)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
u32 val;
int ret;
// 1. 解析 VBUS 电源
pm_config->vbus_supply = devm_regulator_get(dev, "vbus");
if (IS_ERR(pm_config->vbus_supply)) {
dev_err(dev, "No vbus supply\n");
return PTR_ERR(pm_config->vbus_supply);
}
// 2. 解析 VBUS GPIO
pm_config->vbus_gpio = of_get_named_gpio(np, "vbus-gpios", 0);
if (gpio_is_valid(pm_config->vbus_gpio)) {
ret = devm_gpio_request_one(dev, pm_config->vbus_gpio,
GPIOF_OUT_INIT_LOW, "vbus");
if (ret) {
dev_err(dev, "Failed to request vbus GPIO\n");
return ret;
}
}
// 3. 解析电源延迟
ret = of_property_read_u32(np, "power-off-delay", &pm_config->power_off_delay);
if (ret) {
pm_config->power_off_delay = 100;
}
// 4. 解析唤醒能力
pm_config->wakeup_capable = of_property_read_bool(np, "wakeup-capable");
// 5. 解析暂停策略
ret = of_property_read_u32(np, "suspend-mode", &pm_config->suspend_mode);
if (ret) {
pm_config->suspend_mode = 0;
}
return 0;
}
8.4 软件设计模式树形分析
USB 控制器设备树绑定设计模式 ├── 工厂模式 (Factory Pattern) │ ├── of_clk_get_by_name():获取时钟实例 │ └── devm_regulator_get():获取电源调节器实例 ├── 适配器模式 (Adapter Pattern) │ └── usb_of_parse_config():将设备树节点适配为驱动配置 ├── 策略模式 (Strategy Pattern) │ ├── usb_dr_mode:Host/Device/OTG 模式策略 │ └── phy_type:不同类型的 PHY 配置策略 ├── 观察者模式 (Observer Pattern) │ └── 设备树节点与驱动的绑定关系通过匹配表实现 ├── 单例模式 (Singleton Pattern) │ └── 每个 USB 控制器只有一个设备树节点 └── 模板方法模式 (Template Method Pattern) └── usb_of_parse_config():定义了从设备树解析配置的标准流程
8.5 设备树调试核心难点
8.5.1 设备树节点不匹配
现象:of_match_node 返回 NULL,驱动无法加载。
原因:
-
设备树节点兼容字符串与驱动不匹配。
-
设备树文件未编译或未加载。
-
节点状态为
disabled。
解决方法:
-
使用
dtc -I fs /sys/firmware/devicetree/base/检查设备树节点。 -
检查兼容字符串是否正确。
-
确认节点状态为
okay或enabled。
8.5.2 PHY 节点解析失败
现象:of_parse_phandle 返回 NULL,PHY 无法初始化。
原因:
-
PHY 节点未在设备树中定义。
-
PHY 引用 phandle 错误。
-
PHY 节点状态为
disabled。
解决方法:
-
检查设备树中 PHY 节点是否存在。
-
确认 phandle 值正确。
-
确认 PHY 节点状态为
okay。
8.5.3 时钟获取失败
现象:of_clk_get_by_name 返回 ERR_PTR(-ENOENT)。
原因:
-
时钟名称与设备树中的
clock-names不匹配。 -
时钟节点未定义。
-
时钟提供方驱动未加载。
解决方法:
-
检查
clock-names属性是否正确。 -
使用
clk_summary检查时钟是否存在。 -
确认时钟控制器驱动已加载。
8.5.4 设备树资源冲突
现象:多个设备使用相同的寄存器地址或中断号。
原因:
-
设备树中地址或中断冲突。
-
资源复用配置错误。
-
硬件设计问题。
解决方法:
-
检查
reg和interrupts属性是否有重叠。 -
使用
dmesg查看资源冲突信息。 -
重新设计硬件或设备树。
8.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| 设备树编译器 (DTC) | 编译设备树文件为二进制格式 | 语法检查、节点验证 |
| 内核设备树解析 | 解析设备树二进制文件,注册 Platform 设备 | 节点匹配、属性解析 |
| 时钟控制器 | 提供 USB 控制器所需时钟 | 时钟频率、使能状态 |
| 复位控制器 | 提供 USB 控制器复位信号 | 复位序列、延迟 |
| PHY 驱动 | 提供 USB PHY 配置和初始化 | PHY 类型、接口模式 |
| 电源管理 | 管理 VBUS 电源和控制器电源 | 供电稳定、唤醒能力 |
| GPIO 驱动 | 控制 VBUS 电源 GPIO | GPIO 状态、电流能力 |
| 中断控制器 | 路由 USB 控制器中断 | 中断号、亲和性 |
第九部分 USB Gadget 设备端驱动框架
9.1 USB Gadget 框架概述
USB Gadget 是 Linux 内核中用于实现 USB 设备端(Device-side)功能的框架。与 USB Host 驱动不同,Gadget 框架允许 SoC 或专用控制器作为 USB 设备连接到主机,模拟各种 USB 设备类型,如串口、以太网、存储、音频等。
9.1.1 Gadget 框架架构
[用户空间] ↑ [Gadget 功能驱动 (Function Drivers)] ├── 串口 (g_serial) ├── 网卡 (g_ether) ├── 存储 (g_mass_storage) ├── 音频 (g_audio) └── HID (g_hid) ↑ [Gadget 核心层 (Gadget Core)] ↑ [UDC 驱动 (UDC Driver)] ← 具体硬件驱动 ↑ [USB 设备控制器硬件 (UDC)] ↑ [USB 接口]
9.1.2 Gadget 框架数据流
[功能驱动] → [Gadget 核心] → [UDC 驱动] → [硬件] ↓ ↓ ↓ ↓ [用户数据] [协议处理] [寄存器操作] [USB 包]
9.2 核心数据结构
9.2.1 Gadget 核心结构
/**
* @struct usb_gadget
* @brief USB Gadget 核心结构,代表一个 USB 设备控制器实例。
*/
struct usb_gadget {
struct usb_udc *udc; /**< 关联的 UDC */
struct usb_gadget_driver *driver; /**< 绑定的 Gadget 驱动 */
struct usb_device *dev; /**< USB 设备指针 */
struct usb_gadget_ops *ops; /**< 硬件操作函数 */
struct usb_ep *ep0; /**< 端点0 (控制端点) */
struct list_head ep_list; /**< 端点列表 */
unsigned int sg_supported:1; /**< 是否支持 SG 传输 */
unsigned int is_otg:1; /**< 是否支持 OTG */
unsigned int is_a_peripheral:1; /**< 是否作为外设 */
unsigned int is_self_powered:1; /**< 是否自供电 */
unsigned int is_fullspeed:1; /**< 是否全速 */
unsigned int is_hight_speed:1; /**< 是否高速 */
unsigned int is_super_speed:1; /**< 是否超速 */
enum usb_device_speed speed; /**< 当前速度 */
enum usb_device_state state; /**< 设备状态 */
struct work_struct work; /**< 工作队列 */
void *private_data; /**< 私有数据 */
};
/**
* @struct usb_gadget_driver
* @brief USB Gadget 驱动结构。
*/
struct usb_gadget_driver {
const char *name; /**< 驱动名称 */
int (*bind)(struct usb_gadget *gadget, struct usb_gadget_driver *driver);
void (*unbind)(struct usb_gadget *gadget);
int (*setup)(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl);
void (*disconnect)(struct usb_gadget *gadget);
void (*suspend)(struct usb_gadget *gadget);
void (*resume)(struct usb_gadget *gadget);
void (*reset)(struct usb_gadget *gadget);
struct device *dev; /**< 关联的设备 */
};
9.2.2 UDC 驱动结构
/**
* @struct usb_udc
* @brief USB 设备控制器驱动结构。
*/
struct usb_udc {
struct list_head list; /**< UDC 列表 */
struct usb_gadget *gadget; /**< 关联的 Gadget */
struct usb_gadget_driver *driver; /**< 绑定的 Gadget 驱动 */
struct device dev; /**< 设备 */
const char *name; /**< 名称 */
int (*udc_start)(struct usb_udc *udc);
int (*udc_stop)(struct usb_udc *udc);
void (*udc_set_selfpowered)(struct usb_udc *udc, int value);
void *private_data; /**< 私有数据 */
};
/**
* @struct usb_ep
* @brief USB 端点结构,代表一个物理端点。
*/
struct usb_ep {
struct list_head ep_list; /**< 端点列表 */
struct usb_endpoint_descriptor *desc; /**< 端点描述符 */
struct usb_gadget *gadget; /**< 所属 Gadget */
int maxpacket; /**< 最大包大小 */
int maxburst; /**< 最大突发长度 */
int mult; /**< 多重传输 */
int address; /**< 端点地址 */
int bEndpointAddress; /**< 端点地址 */
int bmAttributes; /**< 端点属性 */
int interval; /**< 轮询间隔 */
const struct usb_ep_ops *ops; /**< 端点操作函数 */
void *driver_data; /**< 驱动私有数据 */
};
9.3 核心代码实现
9.3.1 UDC 驱动初始化
#include <linux/module.h>
#include <linux/usb/gadget.h>
#include <linux/dma-mapping.h>
/**
* @brief UDC 驱动 probe 函数。
* @param pdev Platform 设备指针
* @return 0 成功,负数错误
*/
static int udc_probe(struct platform_device *pdev)
{
struct usb_udc *udc;
struct usb_gadget *gadget;
int ret;
// 1. 分配 UDC 和 Gadget 结构
udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL);
if (!udc) return -ENOMEM;
gadget = devm_kzalloc(&pdev->dev, sizeof(*gadget), GFP_KERNEL);
if (!gadget) return -ENOMEM;
// 2. 初始化 Gadget
gadget->ops = &udc_ops;
gadget->ep0 = devm_kzalloc(&pdev->dev, sizeof(*gadget->ep0), GFP_KERNEL);
if (!gadget->ep0) return -ENOMEM;
// 3. 初始化端点0
gadget->ep0->gadget = gadget;
gadget->ep0->maxpacket = 64;
gadget->ep0->bEndpointAddress = 0;
gadget->ep0->bmAttributes = USB_ENDPOINT_XFER_CONTROL;
gadget->ep0->ops = &udc_ep0_ops;
// 4. 初始化 UDC
udc->gadget = gadget;
udc->name = dev_name(&pdev->dev);
udc->udc_start = udc_start;
udc->udc_stop = udc_stop;
// 5. 注册 UDC
ret = usb_add_gadget_udc(&pdev->dev, gadget);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to add gadget UDC\n");
return ret;
}
// 6. 设置平台驱动数据
platform_set_drvdata(pdev, udc);
dev_info(&pdev->dev, "UDC driver initialized\n");
return 0;
}
/**
* @brief UDC 驱动 remove 函数。
*/
static int udc_remove(struct platform_device *pdev)
{
struct usb_udc *udc = platform_get_drvdata(pdev);
// 1. 移除 UDC
usb_del_gadget_udc(udc->gadget);
return 0;
}
9.3.2 Gadget 功能驱动注册
/**
* @brief Gadget 功能驱动注册(以串口为例)。
* @param gadget 指向 Gadget
* @param driver 指向 Gadget 驱动
* @return 0 成功,负数错误
*/
static int g_serial_bind(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
struct usb_composite_dev *cdev;
int ret;
// 1. 创建复合设备
cdev = usb_composite_dev_create(gadget);
if (IS_ERR(cdev)) {
return PTR_ERR(cdev);
}
// 2. 注册串口功能
ret = usb_string_ids_tab(cdev, g_serial_strings);
if (ret < 0) {
dev_err(&gadget->dev, "Failed to register strings\n");
goto error;
}
// 3. 创建配置
ret = usb_composite_add_config(cdev, CONFIG_ID,
&g_serial_config, g_serial_config_ops);
if (ret < 0) {
dev_err(&gadget->dev, "Failed to add config\n");
goto error;
}
// 4. 注册复合设备
ret = usb_composite_register(cdev);
if (ret < 0) {
dev_err(&gadget->dev, "Failed to register composite\n");
goto error;
}
return 0;
error:
usb_composite_dev_destroy(cdev);
return ret;
}
/**
* @brief Gadget 串口功能驱动结构。
*/
static struct usb_gadget_driver g_serial_driver = {
.name = "g_serial",
.bind = g_serial_bind,
.unbind = g_serial_unbind,
.setup = g_serial_setup,
.disconnect = g_serial_disconnect,
.suspend = g_serial_suspend,
.resume = g_serial_resume,
.reset = g_serial_reset,
};
/**
* @brief 注册串口 Gadget 驱动。
*/
static int __init g_serial_init(void)
{
return usb_gadget_register_driver(&g_serial_driver);
}
/**
* @brief 注销串口 Gadget 驱动。
*/
static void __exit g_serial_exit(void)
{
usb_gadget_unregister_driver(&g_serial_driver);
}
9.3.3 端点操作实现
/**
* @brief 端点控制传输处理。
* @param ep 指向端点
* @param req 指向请求
* @return 0 成功,负数错误
*/
static int udc_ep0_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp)
{
struct usb_gadget *gadget = ep->gadget;
struct udc_driver *udc = gadget->private_data;
int ret;
// 1. 检查请求合法性
if (!req || !req->buf || req->length == 0) {
return -EINVAL;
}
// 2. 填充请求到硬件队列
ret = udc_hw_queue(udc, ep, req);
if (ret < 0) {
dev_err(&gadget->dev, "HW queue failed\n");
return ret;
}
// 3. 触发硬件传输
udc_hw_start_transfer(udc, ep);
return 0;
}
/**
* @brief 端点完成回调。
*/
static void udc_ep0_complete(struct usb_ep *ep, struct usb_request *req)
{
struct usb_gadget *gadget = ep->gadget;
struct udc_driver *udc = gadget->private_data;
// 1. 处理完成状态
if (req->status == 0) {
// 传输成功
udc->ep0_state = EP0_STATE_COMPLETED;
} else {
// 传输失败
udc->ep0_state = EP0_STATE_ERROR;
}
// 2. 触发后续处理
schedule_work(&udc->ep0_work);
}
9.3.4 复合设备配置
/**
* @brief 复合设备配置结构。
*/
static struct usb_configuration g_serial_config = {
.label = "Serial Configuration",
.config.bConfigurationValue = CONFIG_ID,
.config.bmAttributes = USB_CONFIG_ATT_POWER,
.config.bMaxPower = 100,
};
/**
* @brief 创建串口功能实例。
*/
static int g_serial_create_interface(struct usb_composite_dev *cdev,
struct usb_gadget *gadget)
{
struct usb_function *func;
int ret;
// 1. 创建串口功能
func = usb_alloc_function("serial", &g_serial_function_ops);
if (IS_ERR(func)) {
dev_err(&gadget->dev, "Failed to allocate function\n");
return PTR_ERR(func);
}
// 2. 绑定功能到配置
ret = usb_add_function(&g_serial_config, func);
if (ret < 0) {
dev_err(&gadget->dev, "Failed to add function to config\n");
usb_free_function(func);
return ret;
}
return 0;
}
9.3.5 用户空间 Gadget 配置(ConfigFS)
#!/bin/bash # 配置 USB Gadget 为串口设备 # 1. 加载 Gadget 模块 modprobe libcomposite modprobe usb_f_serial # 2. 创建 Gadget 实例 mkdir -p /sys/kernel/config/usb_gadget/g_serial cd /sys/kernel/config/usb_gadget/g_serial # 3. 设置 USB ID echo 0x1d6b > idVendor echo 0x0104 > idProduct # 4. 设置设备描述符 mkdir -p strings/0x409 echo "Linux Gadget Serial" > strings/0x409/manufacturer echo "Gadget Serial Device" > strings/0x409/product echo "123456789" > strings/0x409/serialnumber # 5. 创建配置 mkdir -p configs/c.1 echo 100 > configs/c.1/MaxPower # 6. 创建功能 mkdir -p functions/acm.usb0 ln -s functions/acm.usb0 configs/c.1 # 7. 绑定到 UDC echo "udc0" > UDC # 8. 创建设备节点 mknod /dev/ttyGS0 c 255 0
9.4 软件设计模式树形分析
USB Gadget 框架设计模式 ├── 工厂模式 (Factory Pattern) │ ├── usb_alloc_function():创建功能实例 │ └── usb_composite_dev_create():创建复合设备 ├── 适配器模式 (Adapter Pattern) │ └── usb_gadget_register_driver():适配 Gadget 驱动到 UDC ├── 策略模式 (Strategy Pattern) │ └── 不同 Gadget 功能(串口/网卡/存储)实现不同的策略 ├── 观察者模式 (Observer Pattern) │ └── 端点完成回调观察硬件传输事件 ├── 装饰器模式 (Decorator Pattern) │ └── 复合设备配置装饰基本功能 ├── 单例模式 (Singleton Pattern) │ └── 每个 UDC 只有一个 Gadget 实例 └── 模板方法模式 (Template Method Pattern) └── usb_gadget_register_driver():定义了注册的标准流程
9.5 Gadget 调试核心难点
9.5.1 UDC 绑定失败
现象:usb_add_gadget_udc 返回 -ENODEV。
原因:
-
UDC 驱动未正确注册。
-
设备树中 UDC 节点不存在。
-
UDC 硬件未使能。
解决方法:
-
检查
ls /sys/class/udc/确认 UDC 设备。 -
检查设备树节点。
-
确认 UDC 时钟和电源正常。
9.5.2 主机识别失败
现象:插入 USB 线后主机无响应。
原因:
-
Gadget 驱动未正确绑定。
-
端点配置错误。
-
硬件连接问题。
解决方法:
-
检查
dmesg | grep gadget。 -
检查
configfs配置是否正确。 -
检查硬件连接。
9.5.3 传输超时
现象:Gadget 传输超时,功能无法正常工作。
原因:
-
端点缓冲区大小设置不当。
-
传输速度不匹配。
-
UDC 硬件问题。
解决方法:
-
增加缓冲区大小。
-
调整传输速度。
-
检查 UDC 硬件。
9.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| Platform 驱动 | 提供 UDC 硬件访问 | 资源映射、中断分配 |
| DMA 控制器 | 提供 Gadget 数据传输 | DMA 配置、缓冲区管理 |
| 中断控制器 | 处理 USB 设备端中断 | 中断号、亲和性 |
| 电源管理 | 管理 Gadget 唤醒和低功耗 | 唤醒能力、电源状态 |
| ConfigFS | 用户空间配置 Gadget 功能 | 配置语法、功能绑定 |
| 用户空间 | 通过设备节点访问 Gadget | 权限、设备命名 |
| USB 核心层 | 提供 Gadget 框架基础设施 | 驱动注册、事件处理 |
第十部分 USB 电源管理与远程唤醒
10.1 USB 电源管理概述
USB 电源管理是确保系统在连接 USB 设备时能够高效使用电能的关键机制。USB 规范定义了多种电源状态,包括 挂起 (Suspend)、恢复 (Resume)、远程唤醒 (Remote Wakeup) 和 选择性挂起 (Selective Suspend)。
10.1.1 USB 电源状态转换图
[正常状态 (D0)] ↓ [挂起请求 → D3 (挂起)] ↓ [主机唤醒 (Resume)] ← [设备远程唤醒 (Remote Wakeup)] ↓ [恢复到 D0]
10.1.2 USB 设备电源状态
| 状态 | 描述 | 功耗 | 唤醒能力 |
|---|---|---|---|
| D0 | 正常工作状态 | 高 | 立即响应 |
| D1 | 深度空闲 | 中 | 支持 |
| D2 | 轻量挂起 | 低 | 支持 |
| D3 | 完全挂起 | 极低 | 支持远程唤醒 |
10.2 核心数据结构
10.2.1 USB 电源管理配置结构
/**
* @struct usb_power_config
* @brief USB 电源管理配置结构。
*/
struct usb_power_config {
bool autosuspend_enabled; /**< 是否启用自动挂起 */
bool remote_wakeup_enabled; /**< 是否启用远程唤醒 */
int autosuspend_delay_ms; /**< 自动挂起延迟 (毫秒) */
int wakeup_irq; /**< 唤醒中断号 */
struct device *dev; /**< 设备指针 */
struct usb_device *udev; /**< USB 设备 */
struct timer_list suspend_timer; /**< 挂起定时器 */
struct work_struct wakeup_work; /**< 唤醒工作队列 */
spinlock_t lock; /**< 自旋锁 */
bool suspended; /**< 是否已挂起 */
struct usb_power_data *data; /**< 电源数据 */
};
/**
* @struct usb_power_data
* @brief USB 电源管理数据。
*/
struct usb_power_data {
unsigned int hub_good_delay; /**< 集线器就绪延时 */
unsigned int remote_wakeup_delay; /**< 远程唤醒延时 */
unsigned int reset_resume_delay; /**< 复位唤醒延时 */
unsigned int resume_delay; /**< 唤醒延时 */
struct usb_device *udev; /**< USB 设备 */
struct work_struct reset_work; /**< 复位工作队列 */
};
10.3 核心代码实现
10.3.1 USB 设备挂起
/**
* @brief USB 设备挂起函数。
* @param udev USB 设备指针
* @param message 电源消息
* @return 0 成功,负数错误
*/
static int usb_device_suspend(struct usb_device *udev, pm_message_t message)
{
struct usb_power_config *config = udev->power_config;
unsigned long flags;
int ret;
// 1. 检查是否允许挂起
if (!config->autosuspend_enabled) {
dev_dbg(&udev->dev, "Auto-suspend disabled\n");
return -EBUSY;
}
// 2. 锁定设备状态
spin_lock_irqsave(&config->lock, flags);
if (config->suspended) {
spin_unlock_irqrestore(&config->lock, flags);
return 0;
}
// 3. 通知设备驱动准备挂起
ret = usb_suspend_both(udev, message);
if (ret < 0) {
dev_err(&udev->dev, "Device suspend failed: %d\n", ret);
spin_unlock_irqrestore(&config->lock, flags);
return ret;
}
// 4. 设置挂起状态
config->suspended = true;
udev->state = USB_STATE_SUSPENDED;
spin_unlock_irqrestore(&config->lock, flags);
// 5. 停止定时器
del_timer(&config->suspend_timer);
dev_info(&udev->dev, "Device suspended\n");
return 0;
}
10.3.2 USB 设备唤醒
/**
* @brief USB 设备唤醒函数。
* @param udev USB 设备指针
* @return 0 成功,负数错误
*/
static int usb_device_resume(struct usb_device *udev)
{
struct usb_power_config *config = udev->power_config;
unsigned long flags;
int ret;
// 1. 检查状态
spin_lock_irqsave(&config->lock, flags);
if (!config->suspended) {
spin_unlock_irqrestore(&config->lock, flags);
return 0;
}
// 2. 通知设备驱动恢复
ret = usb_resume_both(udev, PM_EVENT_RESUME);
if (ret < 0) {
dev_err(&udev->dev, "Device resume failed: %d\n", ret);
spin_unlock_irqrestore(&config->lock, flags);
return ret;
}
// 3. 重置挂起状态
config->suspended = false;
udev->state = USB_STATE_CONFIGURED;
spin_unlock_irqrestore(&config->lock, flags);
// 4. 重新启动挂起定时器
if (config->autosuspend_enabled) {
mod_timer(&config->suspend_timer,
jiffies + msecs_to_jiffies(config->autosuspend_delay_ms));
}
dev_info(&udev->dev, "Device resumed\n");
return 0;
}
10.3.3 远程唤醒实现
/**
* @brief USB 远程唤醒请求处理。
* @param udev USB 设备指针
* @return 0 成功,负数错误
*/
static int usb_remote_wakeup(struct usb_device *udev)
{
struct usb_power_config *config = udev->power_config;
int ret;
// 1. 检查唤醒能力
if (!config->remote_wakeup_enabled) {
dev_dbg(&udev->dev, "Remote wakeup disabled\n");
return -EACCES;
}
// 2. 执行远程唤醒
ret = usb_device_resume(udev);
if (ret < 0) {
dev_err(&udev->dev, "Remote wakeup failed: %d\n", ret);
return ret;
}
// 3. 发送唤醒信号到主机
ret = usb_hcd_resume(udev->bus->hcd);
if (ret < 0) {
dev_err(&udev->dev, "HCD resume failed: %d\n", ret);
return ret;
}
dev_info(&udev->dev, "Remote wakeup successful\n");
return 0;
}
/**
* @brief 远程唤醒中断处理函数。
*/
static irqreturn_t usb_wakeup_irq_handler(int irq, void *dev_id)
{
struct usb_device *udev = dev_id;
struct usb_power_config *config = udev->power_config;
// 1. 检查设备状态
if (!config->suspended) {
return IRQ_NONE;
}
// 2. 调度唤醒工作队列
schedule_work(&config->wakeup_work);
return IRQ_HANDLED;
}
/**
* @brief 唤醒工作队列处理函数。
*/
static void usb_wakeup_work_handler(struct work_struct *work)
{
struct usb_power_config *config = container_of(work, struct usb_power_config, wakeup_work);
struct usb_device *udev = config->udev;
// 执行远程唤醒
usb_remote_wakeup(udev);
}
10.3.4 自动挂起定时器
/**
* @brief 自动挂起定时器回调。
*/
static void usb_autosuspend_timer_callback(struct timer_list *t)
{
struct usb_power_config *config = from_timer(config, t, suspend_timer);
struct usb_device *udev = config->udev;
// 1. 检查设备是否繁忙
if (!config->suspended && !usb_device_is_busy(udev)) {
// 2. 执行自动挂起
pm_message_t message;
message.event = PM_EVENT_SUSPEND;
usb_device_suspend(udev, message);
} else {
// 3. 重置定时器
mod_timer(&config->suspend_timer,
jiffies + msecs_to_jiffies(config->autosuspend_delay_ms));
}
}
/**
* @brief 启动自动挂起定时器。
*/
static void usb_autosuspend_start(struct usb_device *udev)
{
struct usb_power_config *config = udev->power_config;
if (!config->autosuspend_enabled) {
return;
}
// 1. 初始化定时器
timer_setup(&config->suspend_timer, usb_autosuspend_timer_callback, 0);
config->suspend_timer.expires = jiffies + msecs_to_jiffies(config->autosuspend_delay_ms);
add_timer(&config->suspend_timer);
dev_dbg(&udev->dev, "Auto-suspend timer started\n");
}
10.3.5 电源管理接口
/**
* @brief USB 电源管理操作结构。
*/
static const struct dev_pm_ops usb_pm_ops = {
.suspend = usb_pm_suspend,
.resume = usb_pm_resume,
.freeze = usb_pm_freeze,
.thaw = usb_pm_thaw,
.poweroff = usb_pm_poweroff,
.restore = usb_pm_restore,
.runtime_suspend = usb_pm_runtime_suspend,
.runtime_resume = usb_pm_runtime_resume,
.runtime_idle = usb_pm_runtime_idle,
};
/**
* @brief 运行时挂起处理。
*/
static int usb_pm_runtime_suspend(struct device *dev)
{
struct usb_device *udev = to_usb_device(dev);
pm_message_t message;
message.event = PM_EVENT_SUSPEND;
return usb_device_suspend(udev, message);
}
/**
* @brief 运行时唤醒处理。
*/
static int usb_pm_runtime_resume(struct device *dev)
{
struct usb_device *udev = to_usb_device(dev);
return usb_device_resume(udev);
}
10.4 软件设计模式树形分析
USB 电源管理设计模式 ├── 状态模式 (State Pattern) │ └── USB 设备电源状态 (D0 → D1 → D2 → D3) ├── 观察者模式 (Observer Pattern) │ ├── 中断处理观察硬件唤醒事件 │ └── 定时器观察设备空闲状态 ├── 策略模式 (Strategy Pattern) │ ├── 自动挂起策略 (延迟时间、条件检查) │ └── 唤醒策略 (远程唤醒 vs 主机唤醒) ├── 命令模式 (Command Pattern) │ └── usb_device_suspend/resume 封装为命令 ├── 装饰器模式 (Decorator Pattern) │ └── 为电源管理添加延时和重试机制 └── 模板方法模式 (Template Method Pattern) └── usb_pm_runtime_suspend:定义了运行时挂起的标准流程
10.5 电源管理调试核心难点
10.5.1 设备无法挂起
现象:usb_device_suspend 返回 -EBUSY,设备无法进入挂起状态。
原因:
-
设备驱动禁止自动挂起。
-
设备上有未完成的数据传输。
-
设备引用计数不为零。
解决方法:
-
检查设备驱动的
autosuspend设置。 -
取消所有未完成的 URB。
-
检查引用计数。
10.5.2 远程唤醒无响应
现象:设备发送远程唤醒信号后,主机无响应。
原因:
-
远程唤醒功能未启用。
-
主机不支持远程唤醒。
-
唤醒信号被过滤。
解决方法:
-
检查
sysfs中power/wakeup设置。 -
检查主机控制器是否支持远程唤醒。
-
使用
usbmon监测唤醒信号。
10.5.3 自动挂起触发频繁
现象:设备频繁进入和退出挂起状态,影响性能。
原因:
-
自动挂起延迟设置过短。
-
设备间歇性活动。
-
驱动未正确处理 URB 活动。
解决方法:
-
增加自动挂起延迟时间。
-
优化设备驱动,减少不必要的活动。
-
使用
usb_kill_urb清理 URB。
10.5.4 唤醒后设备无法正常工作
现象:设备唤醒后,传输错误或功能异常。
原因:
-
设备驱动未正确处理唤醒事件。
-
硬件状态未完全恢复。
-
唤醒过程中数据丢失。
解决方法:
-
在驱动
resume函数中恢复硬件状态。 -
重新初始化设备。
-
重新提交 URB。
10.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 提供电源管理接口 | 设备状态、引用计数 |
| HCD 驱动 | 执行挂起/唤醒硬件操作 | 寄存器配置、唤醒信号 |
| PCI/Platform 驱动 | 管理 USB 控制器电源 | 时钟、电源域 |
| 设备驱动 | 响应电源管理事件 | 状态保存、恢复 |
| 中断控制器 | 处理远程唤醒中断 | 中断号、优先级 |
| 电源管理框架 | 集成系统级电源管理 | 睡眠、唤醒 |
| sysfs | 用户空间控制电源参数 | 自动挂起、唤醒 |
| 用户空间 | 通过 pm-autosuspend 工具调试 |
延迟设置、状态查询 |
第十一部分 USB 与 Input 子系统的集成
11.1 USB HID 与 Input 子系统的关系
USB HID(Human Interface Device)类是 USB 设备中最常见的类型之一,包括键盘、鼠标、触摸屏、游戏手柄等。HID 驱动将 USB 中断传输的数据转换为标准化的输入事件,通过 Input 子系统上报给用户空间。
11.1.1 系统架构
[USB HID 设备] ↔ [USB 核心层] ↔ [usbhid 驱动] ↔ [Input 核心层] ↔ [用户空间] ↓ [evdev 接口] ↓ [应用程序]
11.1.2 数据流
[硬件中断] → [URB 完成] → [HID 报告解析] → [Input 事件映射] → [事件上报] ↓ ↓ [原始 HID 报告] [input_event 结构]
11.2 核心数据结构
11.2.1 HID 到 Input 的映射结构
/**
* @struct hid_input
* @brief HID 输入设备结构,连接 HID 和 Input 子系统。
*/
struct hid_input {
struct list_head list; /**< 链表节点 */
struct hid_device *hid; /**< 关联的 HID 设备 */
struct input_dev *input; /**< Input 设备 */
struct hid_report *report; /**< 关联的 HID 报告 */
unsigned int registered:1; /**< 是否已注册 */
unsigned int enabled:1; /**< 是否已启用 */
unsigned int quirks; /**< 特殊标志 */
void *private_data; /**< 私有数据 */
};
/**
* @struct hid_usage
* @brief HID 用法结构,表示 HID 报告的某个字段的含义。
*/
struct hid_usage {
unsigned int hid; /**< 用法 HID 代码 */
unsigned int usage_index; /**< 用法索引 */
unsigned int collection_index; /**< 集合索引 */
struct hid_field *field; /**< 关联的字段 */
struct hid_report *report; /**< 关联的报告 */
unsigned int size; /**< 大小 (位) */
unsigned int logical_minimum; /**< 逻辑最小值 */
unsigned int logical_maximum; /**< 逻辑最大值 */
unsigned int physical_minimum; /**< 物理最小值 */
unsigned int physical_maximum; /**< 物理最大值 */
void *private_data; /**< 私有数据 */
};
/**
* @struct hid_field
* @brief HID 字段结构,代表 HID 报告中的一个数据字段。
*/
struct hid_field {
struct hid_report *report; /**< 关联的报告 */
unsigned int index; /**< 字段索引 */
unsigned int type; /**< 字段类型 (输入/输出/特征) */
unsigned int flags; /**< 字段标志 */
unsigned int report_count; /**< 报告数量 */
unsigned int report_size; /**< 报告大小 (位) */
unsigned int logical_minimum; /**< 逻辑最小值 */
unsigned int logical_maximum; /**< 逻辑最大值 */
unsigned int physical_minimum; /**< 物理最小值 */
unsigned int physical_maximum; /**< 物理最大值 */
struct hid_usage *usage; /**< 用法列表 */
unsigned int usage_count; /**< 用法数量 */
};
11.3 核心代码实现
11.3.1 报告解析
/**
* @brief 解析 HID 报告描述符。
* @param hdev HID 设备指针
* @param rdesc 报告描述符数据
* @param rsize 描述符大小
* @return 0 成功,负数错误
*/
static int hid_parse_report(struct hid_device *hdev,
const u8 *rdesc, int rsize)
{
struct hid_parser *parser;
int ret;
// 1. 创建解析器
parser = hid_parser_alloc(hdev);
if (!parser) {
return -ENOMEM;
}
// 2. 解析报告描述符
ret = hid_parse_report_parser(parser, rdesc, rsize);
if (ret < 0) {
dev_err(&hdev->dev, "Report descriptor parsing failed\n");
goto error;
}
// 3. 生成报告映射
ret = hid_report_parse_to_map(parser);
if (ret < 0) {
dev_err(&hdev->dev, "Report mapping failed\n");
goto error;
}
// 4. 保存解析结果
hdev->report_enum = parser->report_enum;
hid_parser_free(parser);
return 0;
error:
hid_parser_free(parser);
return ret;
}
11.3.2 Input 设备注册
/**
* @brief 从 HID 设备注册 Input 设备。
* @param hdev HID 设备指针
* @return 0 成功,负数错误
*/
static int hid_register_input_device(struct hid_device *hdev)
{
struct input_dev *input;
struct hid_input *hidinput;
int ret;
// 1. 分配 Input 设备
input = input_allocate_device();
if (!input) {
dev_err(&hdev->dev, "Failed to allocate input device\n");
return -ENOMEM;
}
// 2. 设置 Input 设备属性
input->name = "USB HID Device";
input->phys = dev_name(&hdev->dev);
input->dev.parent = &hdev->dev;
input->id.bustype = BUS_USB;
input->id.vendor = hdev->vendor;
input->id.product = hdev->product;
input->id.version = hdev->version;
// 3. 设置能力位
__set_bit(EV_KEY, input->evbit);
__set_bit(EV_REL, input->evbit);
__set_bit(EV_ABS, input->evbit);
// 4. 注册 Input 设备
ret = input_register_device(input);
if (ret < 0) {
dev_err(&hdev->dev, "Failed to register input device\n");
input_free_device(input);
return ret;
}
// 5. 创建 HID 输入结构
hidinput = kzalloc(sizeof(struct hid_input), GFP_KERNEL);
if (!hidinput) {
input_unregister_device(input);
return -ENOMEM;
}
hidinput->hid = hdev;
hidinput->input = input;
hidinput->registered = 1;
list_add_tail(&hidinput->list, &hdev->inputs);
dev_info(&hdev->dev, "Input device registered\n");
return 0;
}
11.3.3 HID 报告到 Input 事件映射
/**
* @brief 映射 HID 报告到 Input 事件。
* @param hdev HID 设备指针
* @param field HID 字段指针
* @param usage HID 用法指针
* @param value 当前值
* @return 0 成功,负数错误
*/
static int hid_map_usage_to_input(struct hid_device *hdev,
struct hid_field *field,
struct hid_usage *usage,
int value)
{
struct input_dev *input = hdev->inputs->input;
int event_code;
int event_type;
// 1. 确定 HID 用法类型
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_KEYBOARD:
// 键盘按键
event_type = EV_KEY;
event_code = hid_usage_to_key(usage->hid);
break;
case HID_UP_BUTTON:
// 鼠标按键
event_type = EV_KEY;
event_code = hid_usage_to_button(usage->hid);
break;
case HID_UP_GENERIC_DESKTOP:
switch (usage->hid & 0xFFFF) {
case HID_GD_X:
event_type = EV_REL;
event_code = REL_X;
break;
case HID_GD_Y:
event_type = EV_REL;
event_code = REL_Y;
break;
case HID_GD_Z:
event_type = EV_REL;
event_code = REL_Z;
break;
case HID_GD_WHEEL:
event_type = EV_REL;
event_code = REL_WHEEL;
break;
case HID_GD_HWHEEL:
event_type = EV_REL;
event_code = REL_HWHEEL;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
// 2. 映射特殊标志
if (usage->flags & HID_USAGE_FLAG_SPECIAL) {
// 处理特殊标志
usage->flags &= ~HID_USAGE_FLAG_SPECIAL;
}
// 3. 保存映射
input_map_usage(usage, field, input, event_type, event_code);
return 0;
}
11.3.4 HID 报告处理
/**
* @brief 处理接收到的 HID 报告。
* @param hdev HID 设备指针
* @param report HID 报告指针
* @param data 报告数据
* @param size 报告大小
* @return 0 成功,负数错误
*/
static int hid_process_report(struct hid_device *hdev,
struct hid_report *report,
const u8 *data, int size)
{
struct hid_field *field;
struct hid_usage *usage;
struct input_dev *input = hdev->inputs->input;
int i, j, k;
// 1. 遍历所有字段
for (i = 0; i < report->maxfield; i++) {
field = report->field[i];
if (!field) continue;
// 2. 遍历字段中的所有用法
for (j = 0; j < field->usage_count; j++) {
usage = &field->usage[j];
if (!usage) continue;
// 3. 计算字段值
int value = hid_field_extract(data, field->report_count, field->report_size, j);
if (value < field->logical_minimum ||
value > field->logical_maximum) {
continue;
}
// 4. 处理输入字段
if (field->type == HID_INPUT) {
// 映射到 Input 事件
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_KEYBOARD:
case HID_UP_BUTTON:
// 按键事件
for (k = 0; k < field->report_count; k++) {
int key = hid_usage_to_key(usage->hid);
input_report_key(input, key, value);
}
break;
case HID_UP_GENERIC_DESKTOP:
// 鼠标移动
switch (usage->hid & 0xFFFF) {
case HID_GD_X:
input_report_rel(input, REL_X, value);
break;
case HID_GD_Y:
input_report_rel(input, REL_Y, value);
break;
}
break;
}
}
}
}
// 5. 同步 Input 事件
input_sync(input);
return 0;
}
11.3.5 键盘按键映射示例
/**
* @brief HID 用法到键盘按键的映射表。
*/
static const int hid_usage_to_key_map[] = {
[0x04] = KEY_A,
[0x05] = KEY_B,
[0x06] = KEY_C,
[0x07] = KEY_D,
[0x08] = KEY_E,
[0x09] = KEY_F,
[0x0A] = KEY_G,
[0x0B] = KEY_H,
[0x0C] = KEY_I,
[0x0D] = KEY_J,
[0x0E] = KEY_K,
[0x0F] = KEY_L,
[0x10] = KEY_M,
[0x11] = KEY_N,
[0x12] = KEY_O,
[0x13] = KEY_P,
[0x14] = KEY_Q,
[0x15] = KEY_R,
[0x16] = KEY_S,
[0x17] = KEY_T,
[0x18] = KEY_U,
[0x19] = KEY_V,
[0x1A] = KEY_W,
[0x1B] = KEY_X,
[0x1C] = KEY_Y,
[0x1D] = KEY_Z,
};
/**
* @brief 从 HID 用法获取键盘按键。
* @param usage HID 用法代码
* @return 按键码
*/
static int hid_usage_to_key(int usage)
{
int idx = usage & 0xFF;
if (idx < ARRAY_SIZE(hid_usage_to_key_map)) {
return hid_usage_to_key_map[idx];
}
return KEY_UNKNOWN;
}
11.3.6 触摸屏处理
/**
* @brief 触摸屏 HID 报告处理。
* @param hdev HID 设备指针
* @param report HID 报告指针
* @param data 报告数据
* @param size 报告大小
* @return 0 成功,负数错误
*/
static int hid_process_touch_report(struct hid_device *hdev,
struct hid_report *report,
const u8 *data, int size)
{
struct input_dev *input = hdev->inputs->input;
struct hid_field *field;
int i, j;
int x = 0, y = 0, pressure = 0, contact_id = 0;
// 1. 提取触摸坐标
for (i = 0; i < report->maxfield; i++) {
field = report->field[i];
if (!field) continue;
for (j = 0; j < field->usage_count; j++) {
struct hid_usage *usage = &field->usage[j];
int value = hid_field_extract(data, field->report_count, field->report_size, j);
switch (usage->hid & 0xFFFF) {
case HID_GD_X:
x = value;
break;
case HID_GD_Y:
y = value;
break;
case HID_DG_CONTACTID:
contact_id = value;
break;
case HID_DG_TIPSWITCH:
input_report_key(input, BTN_TOUCH, value);
break;
case HID_DG_PRESSURE:
pressure = value;
break;
}
}
}
// 2. 上报触摸事件
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
input_report_abs(input, ABS_MT_PRESSURE, pressure);
input_report_abs(input, ABS_MT_SLOT_ID, contact_id);
// 3. 同步事件
input_sync(input);
return 0;
}
11.4 软件设计模式树形分析
USB HID 与 Input 集成设计模式 ├── 适配器模式 (Adapter Pattern) │ ├── HID 报告 → Input 事件转换适配器 │ └── HID 用法 → Input 按键映射适配器 ├── 工厂模式 (Factory Pattern) │ ├── hid_register_input_device():创建 Input 设备实例 │ └── hid_parser_alloc():创建 HID 解析器实例 ├── 策略模式 (Strategy Pattern) │ ├── hid_map_usage_to_input():不同 HID 用法的映射策略 │ └── hid_process_report():不同报告类型的处理策略 ├── 观察者模式 (Observer Pattern) │ ├── 报告处理观察 HID 事件 │ └── 中断处理观察硬件中断 ├── 装饰器模式 (Decorator Pattern) │ └── 为 Input 事件添加时间戳和序列号 └── 模板方法模式 (Template Method Pattern) └── hid_process_report():定义了报告处理的标准流程
11.5 资深视角:HID 与 Input 调试核心难点
11.5.1 HID 报告解析错误
现象:hid_parse_report 返回 -EINVAL,设备无法识别。
原因:
-
报告描述符格式错误。
-
描述符长度与设备返回不符。
-
设备返回的报告描述符不完整。
解决方法:
-
使用
hidraw直接读取报告描述符。 -
使用
usbmon抓取描述符传输数据。 -
添加调试打印,输出描述符内容。
11.5.2 按键映射错误
现象:按键按下后,系统收到的按键码与物理按键不符。
原因:
-
HID 用法到按键码的映射错误。
-
HID 报告数据提取错误。
-
键盘布局配置错误。
解决方法:
-
检查 HID 用法到按键码的映射表。
-
使用
evtest查看实际按键码。 -
检查键盘布局设置。
11.5.3 触摸屏多点触控无效
现象:触摸屏支持多点触控,但只能识别单点。
原因:
-
触摸屏 HID 报告不支持多点触控。
-
未正确解析 MT 协议。
-
Input 设备未配置 MT 能力。
解决方法:
-
检查触摸屏 HID 报告描述符。
-
配置 Input 设备的 MT 能力。
-
正确上报 MT 事件。
11.5.4 事件上报延迟
现象:输入事件上报有延迟,影响用户体验。
原因:
-
URB 传输间隔设置不当。
-
报告处理路径效率低下。
-
系统调度延迟。
解决方法:
-
调整 URB 传输间隔。
-
优化报告处理代码。
-
使用实时优先级。
11.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 提供 URB 传输和端点配置 | 传输类型、间隔设置 |
| Input 核心层 | 提供 Input 设备注册和事件上报 | 事件类型、设备能力 |
| HID 核心层 | 提供 HID 报告解析和管理 | 报告描述符、字段解析 |
| 用户空间 | 通过 evdev 接口获取事件 | 设备节点、权限控制 |
| evdev | 提供用户空间访问接口 | 事件队列、缓冲区管理 |
| sysfs | 查看和配置 HID 设备参数 | 设备信息、调试开关 |
| 电源管理 | 设备空闲时自动挂起 | 唤醒能力、自动挂起 |
第十二部分 USB 与 V4L2 子系统的集成
12.1 USB 视频设备与 V4L2 的关系
USB 视频设备类(UVC)是 USB 设备类中最常见的类型之一,包括网络摄像头、视频采集卡、USB 显微镜等。UVC 驱动将 USB 批量/等时传输的数据转换为标准的 V4L2 视频帧,通过 V4L2 子系统上报给用户空间。
12.1.1 系统架构
[USB 摄像头] ↔ [USB 核心层] ↔ [uvcvideo 驱动] ↔ [V4L2 核心层] ↔ [用户空间] ↓ [V4L2 设备节点] ↓ [应用程序]
12.1.2 数据流
[硬件中断] → [URB 完成] → [帧数据解析] → [V4L2 缓冲区] → [用户空间] ↓ ↓ [原始视频数据] [v4l2_buffer 结构]
12.2 核心数据结构
12.2.1 UVC 设备结构
/**
* @struct uvc_device
* @brief UVC 设备结构,管理 UVC 摄像头实例。
*/
struct uvc_device {
struct usb_device *udev; /**< USB 设备指针 */
struct usb_interface *intf; /**< USB 接口 */
struct video_device *vdev; /**< V4L2 视频设备 */
struct v4l2_device v4l2_dev; /**< V4L2 设备 */
struct uvc_streaming *streaming; /**< 流接口 */
struct uvc_control *control; /**< 控制接口 */
struct list_head entities; /**< 实体列表 */
int uvc_version; /**< UVC 版本 */
int video_format; /**< 视频格式 */
int width; /**< 图像宽度 */
int height; /**< 图像高度 */
int frame_rate; /**< 帧率 */
int frame_interval; /**< 帧间隔 */
int payload_size; /**< 负载大小 */
struct uvc_buffer *buffer; /**< 当前缓冲区 */
struct list_head free_buffers; /**< 空闲缓冲区列表 */
struct list_head done_buffers; /**< 完成缓冲区列表 */
spinlock_t lock; /**< 自旋锁 */
wait_queue_head_t wait; /**< 等待队列 */
int streaming_state; /**< 流状态 */
};
/**
* @struct uvc_streaming
* @brief UVC 流接口结构。
*/
struct uvc_streaming {
struct uvc_device *dev; /**< 所属设备 */
struct usb_endpoint_descriptor *ep; /**< 端点描述符 */
int endpoint; /**< 端点地址 */
int bInterval; /**< 中断间隔 */
int max_packet_size; /**< 最大包大小 */
int pkt_size; /**< 包大小 */
int nb_isoc_packets; /**< 等时包数量 */
struct urb **urb; /**< URB 数组 */
struct uvc_video_chain *chain; /**< 视频链 */
struct uvc_format *format; /**< 格式 */
struct uvc_frame *frame; /**< 帧 */
struct list_head urbs; /**< URB 列表 */
int packets_dropped; /**< 丢包计数 */
int packets_processed; /**< 已处理包计数 */
int packets_error; /**< 错误包计数 */
};
12.3 核心代码实现
12.3.1 UVC 驱动初始化
#include <linux/module.h>
#include <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-contig.h>
/**
* @brief UVC 设备 probe 函数。
* @param intf USB 接口指针
* @param id 匹配的 USB 设备 ID
* @return 0 成功,负数错误
*/
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct uvc_device *uvc;
int ret;
// 1. 分配 UVC 设备结构
uvc = kzalloc(sizeof(struct uvc_device), GFP_KERNEL);
if (!uvc) return -ENOMEM;
uvc->udev = udev;
uvc->intf = intf;
spin_lock_init(&uvc->lock);
init_waitqueue_head(&uvc->wait);
INIT_LIST_HEAD(&uvc->free_buffers);
INIT_LIST_HEAD(&uvc->done_buffers);
// 2. 注册 V4L2 设备
ret = v4l2_device_register(&intf->dev, &uvc->v4l2_dev);
if (ret < 0) {
dev_err(&intf->dev, "Failed to register V4L2 device\n");
goto error;
}
// 3. 解析 UVC 控制接口
ret = uvc_parse_control(uvc);
if (ret < 0) {
dev_err(&intf->dev, "Failed to parse control interface\n");
goto error_v4l2;
}
// 4. 创建视频设备
uvc->vdev = video_device_alloc();
if (!uvc->vdev) {
ret = -ENOMEM;
goto error_v4l2;
}
// 5. 设置视频设备操作
uvc->vdev->v4l2_dev = &uvc->v4l2_dev;
uvc->vdev->fops = &uvc_fops;
uvc->vdev->ioctl_ops = &uvc_ioctl_ops;
uvc->vdev->release = video_device_release;
uvc->vdev->queue = &uvc->queue;
uvc->vdev->name = "USB Camera";
// 6. 注册视频设备
ret = video_register_device(uvc->vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
dev_err(&intf->dev, "Failed to register video device\n");
goto error_vdev;
}
// 7. 保存设备数据
usb_set_intfdata(intf, uvc);
dev_info(&intf->dev, "UVC camera device registered\n");
return 0;
error_vdev:
video_device_release(uvc->vdev);
error_v4l2:
v4l2_device_unregister(&uvc->v4l2_dev);
error:
kfree(uvc);
return ret;
}
12.3.2 流接口配置
/**
* @brief 配置 UVC 流接口。
* @param uvc UVC 设备指针
* @param format 视频格式
* @param width 宽度
* @param height 高度
* @param frame_rate 帧率
* @return 0 成功,负数错误
*/
static int uvc_set_streaming_config(struct uvc_device *uvc,
int format, int width, int height,
int frame_rate)
{
struct uvc_streaming *streaming;
struct usb_interface *intf = uvc->intf;
int ret;
// 1. 检查流接口是否存在
if (uvc->streaming == NULL) {
dev_err(&intf->dev, "No streaming interface\n");
return -ENODEV;
}
streaming = uvc->streaming;
// 2. 选择格式
ret = uvc_select_format(streaming, format, width, height);
if (ret < 0) {
dev_err(&intf->dev, "Format not supported\n");
return ret;
}
// 3. 选择帧率和帧大小
ret = uvc_select_frame(streaming, frame_rate);
if (ret < 0) {
dev_err(&intf->dev, "Frame rate not supported\n");
return ret;
}
// 4. 保存配置
uvc->video_format = format;
uvc->width = width;
uvc->height = height;
uvc->frame_rate = frame_rate;
uvc->payload_size = streaming->pkt_size * streaming->nb_isoc_packets;
// 5. 配置端点
ret = usb_set_interface(uvc->udev,
streaming->ep->bInterfaceNumber,
streaming->ep->bAlternateSetting);
if (ret < 0) {
dev_err(&intf->dev, "Failed to set interface\n");
return ret;
}
dev_info(&intf->dev, "Streaming configured: %dx%d @ %dfps\n",
width, height, frame_rate);
return 0;
}
12.3.3 视频流启动
/**
* @brief 启动视频流。
* @param uvc UVC 设备指针
* @return 0 成功,负数错误
*/
static int uvc_start_streaming(struct uvc_device *uvc)
{
struct uvc_streaming *streaming = uvc->streaming;
struct urb *urb;
int i, ret;
// 1. 检查是否已启动
if (uvc->streaming_state) {
dev_dbg(&uvc->udev->dev, "Streaming already started\n");
return 0;
}
// 2. 分配 URB 数组
streaming->urb = kcalloc(streaming->nb_isoc_packets,
sizeof(struct urb *), GFP_KERNEL);
if (!streaming->urb) return -ENOMEM;
// 3. 分配并初始化 URB
for (i = 0; i < streaming->nb_isoc_packets; i++) {
urb = usb_alloc_urb(streaming->nb_isoc_packets, GFP_KERNEL);
if (!urb) {
ret = -ENOMEM;
goto error_urb;
}
streaming->urb[i] = urb;
usb_fill_isoc_urb(urb, uvc->udev,
usb_rcvisocpipe(uvc->udev, streaming->endpoint),
NULL, streaming->max_packet_size,
uvc_iso_irq, uvc, streaming->bInterval);
}
// 4. 提交 URB
for (i = 0; i < streaming->nb_isoc_packets; i++) {
ret = usb_submit_urb(streaming->urb[i], GFP_KERNEL);
if (ret < 0) {
dev_err(&uvc->udev->dev, "Failed to submit URB %d\n", i);
goto error_submit;
}
}
uvc->streaming_state = 1;
dev_info(&uvc->udev->dev, "Video streaming started\n");
return 0;
error_submit:
for (i = 0; i < streaming->nb_isoc_packets; i++) {
if (streaming->urb[i]) {
usb_kill_urb(streaming->urb[i]);
usb_free_urb(streaming->urb[i]);
}
}
error_urb:
kfree(streaming->urb);
return ret;
}
12.3.4 视频帧处理
/**
* @brief 等时传输 URB 完成回调。
* @param urb 完成 的 URB 指针
*/
static void uvc_iso_irq(struct urb *urb)
{
struct uvc_device *uvc = urb->context;
struct uvc_streaming *streaming = uvc->streaming;
u8 *data = urb->transfer_buffer;
int i;
// 1. 检查传输状态
if (urb->status) {
if (urb->status == -ENODEV || urb->status == -ESHUTDOWN) {
return;
}
dev_err(&uvc->udev->dev, "URB error: %d\n", urb->status);
return;
}
// 2. 处理每个包
for (i = 0; i < urb->number_of_packets; i++) {
struct usb_iso_packet_descriptor *desc = &urb->iso_frame_desc[i];
u8 *frame_data = data + desc->offset;
int length = desc->actual_length;
int status = desc->status;
// 3. 检查包状态
if (status) {
streaming->packets_error++;
continue;
}
if (length < 0) {
streaming->packets_error++;
continue;
}
// 4. 处理视频数据
uvc_process_video_data(uvc, frame_data, length);
}
// 5. 重新提交 URB
usb_submit_urb(urb, GFP_ATOMIC);
}
/**
* @brief 处理视频数据。
* @param uvc UVC 设备指针
* @param data 视频数据
* @param length 数据长度
*/
static void uvc_process_video_data(struct uvc_device *uvc,
u8 *data, int length)
{
struct uvc_streaming *streaming = uvc->streaming;
struct uvc_buffer *buf;
int header_len = 0;
// 1. 解析数据包头
if (length < 2) {
streaming->packets_dropped++;
return;
}
// 2. 确定头部长度和帧信息
if (data[0] & 0x80) {
header_len = data[1] & 0x7F;
if (length < header_len) {
streaming->packets_dropped++;
return;
}
}
// 3. 检查是否为新帧的开始
if (data[0] & 0x40) {
// 新帧开始,提交当前缓冲区
if (uvc->buffer) {
uvc_buffer_done(uvc->buffer);
uvc->buffer = NULL;
}
}
// 4. 获取空闲缓冲区
if (!uvc->buffer) {
spin_lock(&uvc->lock);
if (!list_empty(&uvc->free_buffers)) {
uvc->buffer = list_first_entry(&uvc->free_buffers,
struct uvc_buffer, list);
list_del(&uvc->buffer->list);
}
spin_unlock(&uvc->lock);
}
// 5. 将数据复制到缓冲区
if (uvc->buffer) {
u8 *dst = uvc->buffer->data + uvc->buffer->filled;
int copy_len = min(length - header_len,
uvc->buffer->length - uvc->buffer->filled);
memcpy(dst, data + header_len, copy_len);
uvc->buffer->filled += copy_len;
// 6. 检查是否为一帧的结束
if (data[0] & 0x20) {
uvc_buffer_done(uvc->buffer);
uvc->buffer = NULL;
}
}
streaming->packets_processed++;
}
12.3.5 VB2 缓冲区管理
/**
* @brief VB2 缓冲区操作结构。
*/
static const struct vb2_ops uvc_vb2_ops = {
.queue_setup = uvc_queue_setup,
.buf_prepare = uvc_buf_prepare,
.buf_queue = uvc_buf_queue,
.start_streaming = uvc_start_streaming,
.stop_streaming = uvc_stop_streaming,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
};
/**
* @brief 处理缓冲区完成。
* @param buf VB2 缓冲区指针
*/
static void uvc_buffer_done(struct uvc_buffer *buf)
{
struct uvc_device *uvc = buf->uvc;
// 1. 设置缓冲区状态
buf->vbuf.sequence = uvc->sequence++;
buf->vbuf.timestamp = ktime_get_ns();
buf->vbuf.field = V4L2_FIELD_NONE;
// 2. 标记缓冲区完成
vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_DONE);
// 3. 唤醒等待进程
wake_up(&uvc->wait);
}
12.4 软件设计模式树形分析
UVC 与 V4L2 集成设计模式 ├── 工厂模式 (Factory Pattern) │ ├── video_device_alloc():创建 V4L2 视频设备 │ └── kcalloc():创建 URB 数组 ├── 适配器模式 (Adapter Pattern) │ ├── uvc_process_video_data():将 USB 数据适配为 V4L2 帧 │ └── uvc_vb2_ops:适配 VB2 接口 ├── 策略模式 (Strategy Pattern) │ ├── uvc_select_format():选择视频格式策略 │ └── uvc_select_frame():选择帧率策略 ├── 观察者模式 (Observer Pattern) │ ├── uvc_iso_irq():观察 URB 完成事件 │ └── vb2_buffer_done():观察缓冲区完成事件 ├── 装饰器模式 (Decorator Pattern) │ └── 为视频帧添加时间戳和序列号 └── 模板方法模式 (Template Method Pattern) └── uvc_process_video_data():定义了视频数据处理的标淮流程
12.5 UVC 与 V4L2 调试核心难点
12.5.1 视频格式协商失败
现象:摄像头无法设置指定分辨率或帧率。
原因:
-
设备不支持请求的格式。
-
格式协商接口未正确实现。
-
带宽不足。
解决方法:
-
使用
v4l2-ctl --list-formats-ext查看支持格式。 -
检查格式协商代码。
-
降低分辨率或帧率。
12.5.2 视频帧丢失
现象:视频预览卡顿,dmesg 显示丢包。
原因:
-
USB 带宽不足。
-
缓冲区队列设置不当。
-
等时传输包损坏。
解决方法:
-
增加缓冲区数量。
-
调整等时传输参数。
-
使用批量传输替代等时传输。
12.5.3 视频流无法启动
现象:VIDIOC_STREAMON 返回 -EIO。
原因:
-
设备未正确配置。
-
URB 提交失败。
-
带宽不足。
解决方法:
-
检查设备配置。
-
检查 URB 提交状态。
-
降低分辨率或帧率。
12.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 提供 URB 传输和端点配置 | 等时传输、批量传输 |
| V4L2 核心层 | 提供视频设备注册和缓冲区管理 | 格式协商、缓冲区队列 |
| VB2 框架 | 提供视频缓冲区管理 | 缓冲区分配、准备 |
| 用户空间 | 通过 V4L2 接口获取视频帧 | 格式协商、缓冲区状态 |
| sysfs | 查看和配置视频参数 | 设备信息、调试开关 |
| DMA 控制器 | 提供视频数据传输 | 缓冲区地址、传输完成 |
| 电源管理 | 设备空闲时自动挂起 | 唤醒能力、自动挂起 |
第十三部分 USB 与 ALSA 子系统的集成
13.1 USB 音频设备与 ALSA 的关系
USB 音频设备类(UAC)是 USB 设备类中最常见的类型之一,包括 USB 耳机、麦克风、音箱、音频接口等。USB 音频驱动将 USB 等时传输的音频数据转换为标准的 ALSA PCM 流,通过 ALSA 子系统上报给用户空间。
13.1.1 系统架构
[USB 音频设备] ↔ [USB 核心层] ↔ [snd-usb-audio 驱动] ↔ [ALSA 核心层] ↔ [用户空间] ↓ [PCM 设备节点] ↓ [应用程序]
13.1.2 数据流
[硬件中断] → [URB 完成] → [音频数据解析] → [ALSA PCM 缓冲区] → [用户空间] ↓ ↓ [原始音频数据] [snd_pcm 结构]
13.2 核心数据结构
13.2.1 USB 音频设备结构
/**
* @struct snd_usb_audio
* @brief USB 音频设备核心结构。
*/
struct snd_usb_audio {
struct snd_card *card; /**< ALSA 声卡核心 */
struct usb_device *dev; /**< USB 设备 */
struct usb_interface *intf; /**< USB 接口 */
struct list_head stream_list; /**< 音频流列表 */
struct list_head midi_list; /**< MIDI 列表 */
struct list_head pcm_list; /**< PCM 列表 */
int samplerate; /**< 当前采样率 */
int channels; /**< 当前通道数 */
int format; /**< 音频格式 (PCM/DSD) */
int bits_per_sample; /**< 采样位深 */
int buffer_size; /**< 缓冲区大小 */
int period_size; /**< 周期大小 */
int periods; /**< 周期数量 */
int rate; /**< 采样率 */
int frame_size; /**< 帧大小 */
struct snd_usb_endpoint *ep; /**< 端点列表 */
struct snd_usb_substream *substream; /**< 子流 */
struct snd_usb_pcm *pcm; /**< PCM 设备 */
struct snd_usb_midi *midi; /**< MIDI 设备 */
spinlock_t lock; /**< 自旋锁 */
struct work_struct work; /**< 工作队列 */
int autopm:1; /**< 自动电源管理 */
int auto_suspend:1; /**< 自动挂起 */
int is_audio:1; /**< 是否音频设备 */
int is_midi:1; /**< 是否 MIDI 设备 */
int is_control:1; /**< 是否控制设备 */
};
/**
* @struct snd_usb_substream
* @brief USB 音频子流结构。
*/
struct snd_usb_substream {
struct snd_usb_audio *chip; /**< 所属芯片 */
struct snd_pcm_substream *substream; /**< ALSA 子流 */
struct snd_usb_endpoint *ep; /**< 关联端点 */
struct urb **urb; /**< URB 数组 */
int stream_id; /**< 流 ID */
int direction; /**< 方向 (播放/录音) */
int endpoint; /**< 端点地址 */
int bInterval; /**< 中断间隔 */
int max_packet_size; /**< 最大包大小 */
int packet_size; /**< 包大小 */
int period_frames; /**< 周期帧数 */
int period_bytes; /**< 周期字节数 */
int buffer_frames; /**< 缓冲区帧数 */
int buffer_bytes; /**< 缓冲区字节数 */
int sample_rate; /**< 采样率 */
int channels; /**< 通道数 */
int format; /**< 格式 */
int bits_per_sample; /**< 采样位深 */
int frame_size; /**< 帧大小 */
int packets_dropped; /**< 丢包计数 */
int packets_processed; /**< 已处理包计数 */
int packets_error; /**< 错误包计数 */
dma_addr_t dma_addr; /**< DMA 地址 */
void *dma_area; /**< DMA 区域 */
size_t dma_bytes; /**< DMA 字节数 */
struct list_head list; /**< 链表节点 */
spinlock_t lock; /**< 自旋锁 */
};
13.3 核心代码实现
13.3.1 USB 音频设备初始化
#include <linux/module.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
/**
* @brief USB 音频设备 probe 函数。
* @param intf USB 接口指针
* @param id 匹配的 USB 设备 ID
* @return 0 成功,负数错误
*/
static int snd_usb_audio_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct snd_usb_audio *chip;
struct snd_card *card;
int ret;
// 1. 分配声卡结构
ret = snd_card_new(&intf->dev, -1, "USB Audio", THIS_MODULE,
sizeof(struct snd_usb_audio), &card);
if (ret < 0) {
dev_err(&intf->dev, "Failed to create sound card\n");
return ret;
}
// 2. 初始化芯片数据
chip = card->private_data;
chip->card = card;
chip->dev = dev;
chip->intf = intf;
spin_lock_init(&chip->lock);
INIT_LIST_HEAD(&chip->stream_list);
INIT_LIST_HEAD(&chip->midi_list);
INIT_LIST_HEAD(&chip->pcm_list);
// 3. 解析音频控制接口
ret = snd_usb_parse_audio_control(chip);
if (ret < 0) {
dev_err(&intf->dev, "Failed to parse audio control\n");
goto error;
}
// 4. 解析音频流接口
ret = snd_usb_parse_audio_streaming(chip);
if (ret < 0) {
dev_err(&intf->dev, "Failed to parse audio streaming\n");
goto error;
}
// 5. 解析 MIDI 接口
ret = snd_usb_parse_midi(chip);
if (ret < 0) {
dev_err(&intf->dev, "Failed to parse MIDI\n");
goto error;
}
// 6. 注册声卡
ret = snd_card_register(card);
if (ret < 0) {
dev_err(&intf->dev, "Failed to register sound card\n");
goto error;
}
// 7. 保存设备数据
usb_set_intfdata(intf, chip);
dev_info(&intf->dev, "USB audio device registered\n");
return 0;
error:
snd_card_free(card);
return ret;
}
13.3.2 PCM 设备注册
/**
* @brief 注册 PCM 设备。
* @param chip USB 音频芯片指针
* @return 0 成功,负数错误
*/
static int snd_usb_register_pcm(struct snd_usb_audio *chip)
{
struct snd_pcm *pcm;
int ret;
// 1. 分配 PCM 设备
ret = snd_pcm_new(chip->card, "USB Audio", 0, 1, 1, &pcm);
if (ret < 0) {
dev_err(&chip->dev->dev, "Failed to create PCM\n");
return ret;
}
// 2. 设置 PCM 操作
pcm->private_data = chip;
pcm->ops = &snd_usb_pcm_ops;
pcm->no_device = 0;
pcm->dev = &chip->dev->dev;
pcm->info_flags = 0;
// 3. 设置播放和录音功能
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usb_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_pcm_ops);
// 4. 分配 DMA 缓冲区
ret = snd_pcm_lib_preallocate_pages(pcm, SNDRV_PCM_STREAM_PLAYBACK,
SNDRV_DMA_TYPE_DEV, &chip->dev->dev,
64 * 1024, 64 * 1024);
if (ret < 0) {
dev_err(&chip->dev->dev, "Failed to preallocate playback buffer\n");
return ret;
}
ret = snd_pcm_lib_preallocate_pages(pcm, SNDRV_PCM_STREAM_CAPTURE,
SNDRV_DMA_TYPE_DEV, &chip->dev->dev,
64 * 1024, 64 * 1024);
if (ret < 0) {
dev_err(&chip->dev->dev, "Failed to preallocate capture buffer\n");
return ret;
}
// 5. 保存 PCM 设备
chip->pcm = pcm;
dev_info(&chip->dev->dev, "PCM device registered\n");
return 0;
}
13.3.3 音频流启动
/**
* @brief 启动音频流。
* @param subs USB 音频子流指针
* @return 0 成功,负数错误
*/
static int snd_usb_start_stream(struct snd_usb_substream *subs)
{
struct snd_usb_audio *chip = subs->chip;
struct urb *urb;
int i, ret;
// 1. 检查是否已启动
if (subs->ep->state == EP_STATE_RUNNING) {
return 0;
}
// 2. 分配 URB 数组
subs->urb = kcalloc(subs->ep->packets_per_period, sizeof(struct urb *), GFP_KERNEL);
if (!subs->urb) return -ENOMEM;
// 3. 分配并初始化 URB
for (i = 0; i < subs->ep->packets_per_period; i++) {
urb = usb_alloc_urb(subs->ep->packets_per_period, GFP_KERNEL);
if (!urb) {
ret = -ENOMEM;
goto error_urb;
}
subs->urb[i] = urb;
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
usb_fill_isoc_urb(urb, chip->dev,
usb_sndisocpipe(chip->dev, subs->endpoint),
subs->dma_area + i * subs->packet_size,
subs->packet_size,
snd_usb_playback_irq, subs,
subs->bInterval);
} else {
usb_fill_isoc_urb(urb, chip->dev,
usb_rcvisocpipe(chip->dev, subs->endpoint),
subs->dma_area + i * subs->packet_size,
subs->packet_size,
snd_usb_capture_irq, subs,
subs->bInterval);
}
}
// 4. 提交 URB
for (i = 0; i < subs->ep->packets_per_period; i++) {
ret = usb_submit_urb(subs->urb[i], GFP_KERNEL);
if (ret < 0) {
dev_err(&chip->dev->dev, "Failed to submit URB %d\n", i);
goto error_submit;
}
}
subs->ep->state = EP_STATE_RUNNING;
return 0;
error_submit:
for (i = 0; i < subs->ep->packets_per_period; i++) {
if (subs->urb[i]) {
usb_kill_urb(subs->urb[i]);
usb_free_urb(subs->urb[i]);
}
}
error_urb:
kfree(subs->urb);
return ret;
}
13.3.4 音频播放完成回调
/**
* @brief 音频播放 URB 完成回调。
* @param urb 完成的 URB 指针
*/
static void snd_usb_playback_irq(struct urb *urb)
{
struct snd_usb_substream *subs = urb->context;
struct snd_usb_audio *chip = subs->chip;
struct snd_pcm_substream *substream = subs->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
int i;
// 1. 检查传输状态
if (urb->status) {
if (urb->status == -ENODEV || urb->status == -ESHUTDOWN) {
return;
}
dev_err(&chip->dev->dev, "URB error: %d\n", urb->status);
return;
}
// 2. 更新 DMA 缓冲区索引
subs->current_index = (subs->current_index + 1) % subs->ep->packets_per_period;
// 3. 填充音频数据
for (i = 0; i < urb->number_of_packets; i++) {
struct usb_iso_packet_descriptor *desc = &urb->iso_frame_desc[i];
int length = desc->actual_length;
int offset = desc->offset;
if (length > 0) {
// 将数据从 DMA 缓冲区复制到 ALSA 缓冲区
snd_pcm_sync(substream, runtime, subs->dma_area + offset, length);
}
}
// 4. 通知 ALSA 周期完成
if (subs->period_frames > 0) {
snd_pcm_period_elapsed(substream);
}
// 5. 重新提交 URB
usb_submit_urb(urb, GFP_ATOMIC);
}
13.3.5 音频录完成回调
/**
* @brief 音频录音 URB 完成回调。
* @param urb 完成的 URB 指针
*/
static void snd_usb_capture_irq(struct urb *urb)
{
struct snd_usb_substream *subs = urb->context;
struct snd_usb_audio *chip = subs->chip;
struct snd_pcm_substream *substream = subs->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
int i;
// 1. 检查传输状态
if (urb->status) {
if (urb->status == -ENODEV || urb->status == -ESHUTDOWN) {
return;
}
dev_err(&chip->dev->dev, "URB error: %d\n", urb->status);
return;
}
// 2. 处理每个包
for (i = 0; i < urb->number_of_packets; i++) {
struct usb_iso_packet_descriptor *desc = &urb->iso_frame_desc[i];
int length = desc->actual_length;
int offset = desc->offset;
if (length > 0) {
// 将数据从 DMA 缓冲区复制到 ALSA 缓冲区
snd_pcm_sync(substream, runtime, subs->dma_area + offset, length);
}
}
// 3. 更新 DMA 缓冲区索引
subs->current_index = (subs->current_index + 1) % subs->ep->packets_per_period;
// 4. 通知 ALSA 周期完成
if (subs->period_frames > 0) {
snd_pcm_period_elapsed(substream);
}
// 5. 重新提交 URB
usb_submit_urb(urb, GFP_ATOMIC);
}
13.3.6 PCM 硬件参数配置
/**
* @brief PCM 硬件参数配置。
* @param substream ALSA 子流
* @param params 硬件参数
* @return 0 成功,负数错误
*/
static int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_usb_substream *subs = substream->private_data;
int ret;
// 1. 获取参数
subs->sample_rate = params_rate(params);
subs->channels = params_channels(params);
subs->format = params_format(params);
subs->bits_per_sample = snd_pcm_format_width(subs->format);
// 2. 计算帧大小
subs->frame_size = (subs->bits_per_sample * subs->channels) / 8;
subs->period_frames = params_period_size(params);
subs->buffer_frames = params_buffer_size(params);
subs->period_bytes = subs->period_frames * subs->frame_size;
subs->buffer_bytes = subs->buffer_frames * subs->frame_size;
// 3. 分配 DMA 缓冲区
ret = snd_pcm_lib_malloc_pages(substream, subs->buffer_bytes);
if (ret < 0) {
dev_err(&subs->chip->dev->dev, "Failed to allocate DMA buffer\n");
return ret;
}
subs->dma_area = substream->runtime->dma_area;
subs->dma_addr = substream->runtime->dma_addr;
subs->dma_bytes = subs->buffer_bytes;
// 4. 配置端点
ret = snd_usb_set_interface(subs, subs->ep);
if (ret < 0) {
dev_err(&subs->chip->dev->dev, "Failed to set interface\n");
return ret;
}
return 0;
}
13.4 软件设计模式树形分析
USB 音频与 ALSA 集成设计模式 ├── 工厂模式 (Factory Pattern) │ ├── snd_card_new():创建 ALSA 声卡 │ └── snd_pcm_new():创建 PCM 设备 ├── 适配器模式 (Adapter Pattern) │ ├── snd_usb_playback_irq():将 USB 数据适配为 ALSA PCM │ └── snd_usb_pcm_hw_params():适配 ALSA 参数到 USB ├── 策略模式 (Strategy Pattern) │ ├── snd_usb_pcm_ops:ALSA PCM 操作策略 │ └── snd_usb_set_interface():设置接口策略 ├── 观察者模式 (Observer Pattern) │ ├── snd_usb_playback_irq():观察 URB 完成事件 │ └── snd_pcm_period_elapsed():观察周期完成事件 ├── 装饰器模式 (Decorator Pattern) │ └── 为音频数据添加时间戳和序列号 └── 模板方法模式 (Template Method Pattern) └── snd_usb_audio_probe():定义了 USB 音频设备初始化的标准流程
13.5 资深视角:USB 音频调试核心难点
13.5.1 音频采样率不支持
现象:aplay 或 arecord 报错 -EINVAL,无法设置采样率。
原因:
-
设备不支持指定的采样率。
-
采样率转换失败。
-
带宽不足。
解决方法:
-
使用
cat /proc/asound/card0/stream0查看支持采样率。 -
尝试使用
plughw代替hw进行重采样。 -
降低采样率。
13.5.2 音频播放爆音
现象:播放音频时出现爆音或卡顿。
原因:
-
USB 带宽不足。
-
缓冲区设置不当。
-
等时传输不稳定。
解决方法:
-
增加缓冲区大小。
-
调整等时传输参数。
-
降低采样率。
13.5.3 录音无声音
现象:录制音频,但得到的文件无声。
原因:
-
麦克风未启用。
-
增益设置过低。
-
URB 传输错误。
解决方法:
-
使用
alsamixer检查麦克风设置。 -
增加麦克风增益。
-
检查 URB 传输状态。
13.5.4 音频流无法启动
现象:snd_pcm_hw_params 成功后,snd_pcm_prepare 返回 -EIO。
原因:
-
设备未正确配置。
-
URB 提交失败。
-
接口设置错误。
解决方法:
-
检查设备配置。
-
检查 URB 提交状态。
-
检查接口设置。
13.6 与其他模块的协同
| 模块 | 协同方式 | 调试关键点 |
|---|---|---|
| USB 核心层 | 提供 URB 传输和端点配置 | 等时传输、批量传输 |
| ALSA 核心层 | 提供音频设备注册和 PCM 管理 | 采样率、缓冲区管理 |
| 用户空间 | 通过 ALSA 接口播放/录制音频 | 采样率、通道数 |
| sysfs | 查看和配置音频参数 | 设备信息、调试开关 |
| DMA 控制器 | 提供音频数据传输 | 缓冲区地址、传输完成 |
| 电源管理 | 设备空闲时自动挂起 | 唤醒能力、自动挂起 |
| 中断控制器 | 处理音频中断 | 中断号、优先级 |
| 用户空间工具 | aplay/arecord 测试音频功能 |
设备节点、参数配置 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)