上一期我们探讨了USB的整体框架,现在让我们把焦点转向软件层面。在Linux系统中,USB子系统是如何组织,以及一个具体的USB设备驱动是如何从匹配到实现数据传输的?这正是我们今天要深入剖析的内容。
一、Linux内核中的USB目录结构
Linux内核源码中,USB相关的驱动主要分为四大模块:
- USB Core:核心层,负责总线管理、设备枚举、通用API提供。
- USB控制器驱动:
- Host控制器驱动 (HCD):实现主机控制器协议(如EHCI, xHCI),这是我们通常所说的“USB控制器驱动”。
- Gadget驱动:当设备(如手机)需要扮演USB从设备角色时使用的控制器驱动。
- USB设备驱动:为具体的USB设备类(如U盘、鼠标)或特定厂商设备提供功能驱动。
- USB PHY驱动:物理层收发器驱动,负责数字信号与模拟信号在USB数据线(D+/D-)上的转换。

值得注意的是,USB控制器与PHY通常紧密耦合。大部分控制器芯片会集成PHY模块,控制器按照USB协议封装好数据包后,交由PHY转化为电信号发送。
二、理解USB子系统的核心数据结构
当USB设备插入时,内核会触发一系列操作,创建一系列抽象数据结构来管理这个设备。

1. 与描述符对应的结构体
这些结构体定义在 include/uapi/linux/usb/ch9.h 中,直接对应USB标准描述符,层级关系如下:
usb_device_descriptor -> usb_config_descriptor -> usb_interface_descriptor -> usb_endpoint_descriptor
2. 内核管理的抽象结构体
为了方便管理,内核基于上述描述符创建了更易用的抽象对象。
struct usb_device:抽象一个物理USB设备。它存储设备的全局信息(如总线号、设备地址、速度等)。设备插入时由USB Core创建,拔出时销毁。
struct usb_device {
int busnum; // 该设备所属的【USB总线号】
int devnum; // 该设备的【USB设备号】
u16 idVendor; // 厂商ID(VID),设备匹配的核心依据
u16 idProduct; // 产品ID(PID),设备匹配的核心依据
enum usb_device_speed speed; // 设备速度
struct usb_host_config *config; // 设备的配置描述符指针
struct usb_interface **interface; // 该设备包含的【所有USB接口】
int actconfig; // 当前激活的配置编号
struct usb_bus *bus; // 指向该设备所属的USB总线结构体
struct device dev; // 内核通用设备模型的基类,必含字段!
u8 devaddr; // USB设备在总线上的地址(枚举时分配,0~127)
struct usb_device_descriptor descriptor; // 设备描述符
};
struct usb_host_config:抽象USB设备的一个完整配置。支持配置切换和电源管理。一个设备可有多个配置,但一次只能激活一个。
struct usb_host_config {
struct usb_config_descriptor desc; /* USB标准配置描述符 */
struct usb_interface **interface; /* 该配置下的所有usb_interface数组 */
u8 bConfigurationValue; /* 配置唯一标识编号 */
u16 total_length; /* 配置相关所有描述符的总长度 */
unsigned char *extra; /* 额外描述符缓冲区 */
int extralen; /* 额外描述符长度 */
int power; /* 配置所需最大供电电流(mA) */
const char *string; /* 配置的字符串描述(可选) */
...
};
struct usb_interface:抽象一个USB物理接口。提供接口能力并管理接口设置的切换。驱动匹配成功后,操作的核心对象就是它。
struct usb_interface {
struct usb_host_interface *altsetting; // 该接口的所有设置数据
struct usb_host_interface *cur_altsetting; // 当前激活的接口设置
unsigned int num_altsetting; // 接口设置的数量
int minor; // 次设备号(字符设备驱动用)
struct device dev; // 内核通用设备模型基类
struct device *usb_dev; // 指向该接口所属的USB物理设备
...
};
struct usb_host_interface:抽象USB接口的单个设置。一个接口可以有多个设置(altsetting),驱动通过切换设置来改变端点和协议。
struct usb_host_interface {
struct usb_interface_descriptor desc; //接口描述符
int extralen;
unsigned char *extra;
struct usb_host_endpoint *endpoint; //接口配置支持的端点列表
char *string;
};
struct usb_host_endpoint:抽象一个物理USB端点。它控制URB传输的生命周期,并屏蔽不同主机控制器的硬件差异。
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端口描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //超高速端点描述符
struct list_head urb_list; //urb传输队列
void *hpriv;
struct ep_device *ep_dev; //sysfs文件系统端点节点
unsigned char *extra;
int extralen;
int enabled;
int streams;
};
3. 数据传输的核心:URB
URB (USB Request Block) 是USB传输的最小单元。每次数据传输都对应一个URB实例。它由设备驱动创建,支持所有标准USB传输类型(控制、批量、中断、同步)。
struct urb {
struct usb_device *dev; // 本次传输的【目标USB设备】
unsigned int pipe; // 端点管道【方向+端点号+传输类型】
void *transfer_buffer; // 内核态数据缓冲区
int transfer_buffer_length; // 要传输的最大字节数
int actual_length; // 硬件传输完成后,【回填实际传输字节数】
int endpoint_type; // 传输类型
u8 endpoint_number; // 目标端点号(0~15)
u8 status; // 传输结果状态
usb_complete_t complete; // 传输完成后的【回调函数】
void *context; // 回调函数的上下文参数
int interval; // 中断/同步传输的【轮询间隔】
};
常用URB操作函数:
| 函数 |
作用 |
usb_alloc_urb |
申请 URB 内存 |
usb_free_urb |
释放 URB 内存 |
usb_submit_urb |
验证后加入对应 usb_host_endpoint->urb_list 传输队列 |
usb_kill_urb |
终止未完成的urb传输 |
usb_unlink_urb |
异步取消未完成的urb传输 |
4. 设备与驱动匹配的关键
struct usb_device_id 定义了驱动能匹配哪些设备。匹配可以在设备级(基于VID/PID)或接口级(基于接口类/子类/协议)进行。
struct usb_device_id {
__u32 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; // 接口协议
kernel_ulong_t driver_info; // 驱动私有数据
};
驱动匹配的基本流程如下:
- USB设备插入,触发硬件中断。
- HCD驱动检测中断,创建USB设备抽象。
- USB Core读取设备描述符。
- 调用
usb_match_device 或 usb_match_interface 函数与已注册的驱动进行匹配。
- 匹配成功,触发驱动的
probe 函数进行初始化。

三、USB数据传输完整流程剖析
理解了静态结构,我们来看动态的数据流。它清晰地展示了从驱动到硬件的数据传递路径。
1. 设备驱动层:填充并提交URB
设备驱动负责准备URB,这是数据传输的起点。

主要步骤:
- 分配URB (
usb_alloc_urb)
- 申请数据缓冲区 (
kmalloc)
- 创建传输管道 (
usb_sndbulkpipe 等,根据传输类型和方向)
- 填充URB (
usb_fill_bulk_urb 等)
- 提交URB给USB Core (
usb_submit_urb)
2. USB Core层:统一管理与调度
USB Core作为中间层,对URB进行合法性校验和统一调度,确保协议规范性。

关键操作:
- URB合法性校验。
- 获取并锁定目标端点对象。
- 将URB加入该端点的传输队列 (
urb_list)。
- 调用HCD驱动提供的
urb_enqueue 接口,将URB交给具体的控制器驱动处理。
3. HCD层:协议封装与硬件交互
主机控制器驱动将URB转化为硬件能理解的传输请求块(如xHCI的TRB),并触发硬件执行。

核心过程:
- 解析URB,获取端点硬件上下文。
- 将URB及数据缓冲区映射为DMA可访问的物理地址。
- 构建TRB(传输请求块)并添加到对应端点的硬件传输环。
- 更新传输环状态,写“门铃”寄存器通知硬件开始传输。
- 传输完成后,硬件产生中断,HCD在中断处理程序中回调驱动提供的完成函数。
四、手把手实现一个USB设备驱动
理论最终要落地。实现一个简单的USB设备驱动,需要抓住以下几个关键点。
1. 定义设备ID表
这是驱动声明自己支持哪些设备的依据。
static const struct usb_device_id usb_demo_id_table[] = {
// 精准匹配: 指定厂商(0x1234)和产品(0x5678)
{ USB_DEVICE(0x1234, 0x5678) },
// 通用匹配: 匹配所有HID类设备(接口类0x03)
{ USB_INTERFACE_INFO(USB_CLASS_HID, 0x00, 0x00) },
// 结束标记
{ }
};
MODULE_DEVICE_TABLE(usb, usb_demo_id_table);

2. 定义私有设备结构体
用于在驱动内部管理设备的状态和信息。
struct usb_demo_dev {
struct usb_device *udev; //USB设备
struct usb_interface *intf; //USB接口
struct urb *out_urb; //URB数据
unsigned char *tx_buffer; //URB缓存区
int bulk_out_ep; //传输的节点
};

3. 实现 probe 函数
这是驱动初始化的核心,在设备匹配成功后自动调用。
static int usb_demo_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_demo_dev *dev_data;
struct usb_host_interface *host_intf = intf->cur_altsetting;
int i;
unsigned int pipe;
// 1. 申请私有数据结构
dev_data = kzalloc(sizeof(struct usb_demo_dev), GFP_KERNEL);
// 2. 填充基本信息并绑定到接口
dev_data->udev = udev;
dev_data->intf = intf;
usb_set_intfdata(intf, dev_data);
// 3. 申请发送缓冲区
dev_data->tx_buffer = kmalloc(1024, GFP_KERNEL);
// 4. 分配URB
dev_data->out_urb = usb_alloc_urb(0, GFP_KERNEL);
// 5. 查找批量OUT端点
for (i = 0; i < host_intf->desc.bNumEndpoints; i++) {
struct usb_endpoint_descriptor *ep_desc = &host_intf->endpoint[i].desc;
if (usb_endpoint_is_bulk_out(ep_desc)) {
dev_data->bulk_out_ep = usb_endpoint_num(ep_desc);
break;
}
}
pipe = usb_sndbulkpipe(udev, dev_data->bulk_out_ep);
// 6. 初始化URB
usb_fill_bulk_urb(
dev_data->out_urb, // 待填充的URB
udev, // 目标USB设备
pipe, // 批量OUT传输管道
dev_data->tx_buffer, // 发送缓冲区
1024, // 缓冲区长度
usb_demo_out_complete, // 传输完成回调函数
dev_data // 私有上下文
);
// 7. 注册字符设备等后续操作...
// ...
return 0;
}

4. 实现传输完成回调函数
当URB传输完成(成功或出错)后,内核会调用此函数进行后续处理。
static void usb_demo_out_complete(struct urb *urb) {
// 解析传输完成的响应
struct usb_demo_dev *dev = urb->context;
if (urb->status) {
// 处理传输错误
pr_err("URB传输失败,状态码: %d\n", urb->status);
} else {
// 传输成功,可进行下一步操作
pr_info("成功发送 %d 字节数据\n", urb->actual_length);
}
}

5. 实现设备操作(如write)
通常USB设备驱动会注册为字符设备,在 write 函数中触发数据传输。
//USB write函数
static int submit_bluk_out_urb(struct usb_demo_dev *dev, const char *data,size_t len){
//1、复制数据到URB缓存区
memcpy(dev->tx_buffer,data,len);
//2、准备URB
usb_fill_bulk_urb(
dev->out_urb, // 待填充的URB
dev->udev, // 目标USB设备
usb_sndbulkpipe(dev->udev,dev->bulk_out_ep),// 批量OUT传输管道
dev->tx_buffer, // 发送缓冲区
len, // 缓冲区长度
usb_demo_out_complete, // 传输完成回调函数
dev // 私有上下文(传递给回调函数)
);
//提交URB
usb_submit_urb(dev->out_urb,GFP_KERNEL);
...
}

至此,一个简易USB设备驱动的骨架和关键实现点已经清晰。掌握从内核数据结构、数据传输流程到具体驱动模块的实现,是深入Linux设备驱动开发的重要一步。关于USB sysfs文件系统查看设备信息,以及USB设备低功耗管理等更多高级话题,我们将在后续的分享中继续探讨。如果你对底层技术实现有更多兴趣,欢迎在云栈社区交流讨论。