USB(通用串行总线)是一种高度结构化与复杂的总线系统,相较于I2C、SPI等简单的串行协议,它具备独立的事务处理协议、电源管理、复杂的设备枚举机制以及四种不同的数据传输模式。在Linux系统中,USB驱动的实现基于一个庞大且高度抽象的子系统,严格遵循“主机-设备-驱动”的模型架构。
Linux内核中的USB驱动体系被清晰地划分为以下几个层次:
- USB主机控制器驱动:位于最底层,直接操作SoC上的USB主机控制器硬件(如EHCI、OHCI、XHCI),负责最底层的信号控制和物理数据传输。这一层通常由内核官方提供。
- USB核心:由内核实现,作为中间层,它负责处理USB设备的插拔事件、枚举流程、配置管理和电源管理等核心服务。
- USB设备驱动:开发者针对特定USB设备(如自定义硬件、打印机、特定键盘)编写的驱动程序。该层无需关心如何发送SOF(帧起始)包等底层细节,只需向核心层声明“需要发送或接收这些数据”。
- USB Class驱动:这是可选的通用驱动框架,针对某一类标准USB设备(如HID人机接口设备、Mass Storage大容量存储、CDC通信设备类)提供现成的接口。如果你的设备属于这些通用类别,可以直接使用这些框架,而无需从零开始开发。
深入理解USB驱动,必须掌握其面向对象的描述符结构,这是设备自我描述的“身份证”:
- 设备描述符:描述整个USB设备的基本信息,如USB协议版本、厂商ID(VID)和产品ID(PID)。
- 配置描述符:描述设备对电源的需求、支持的接口数量等信息。
- 接口描述符:最重要的描述符!它定义设备提供的具体功能,例如声明“这是一个键盘接口”或“这是一个用于数据传输的接口”。
- 端点描述符:描述数据传输的通道和方式。端点是USB设备上的数据缓冲区,决定了数据流向(IN或OUT)和传输类型(控制、中断、批量、等时)。
与I2C驱动类似,USB驱动同样通过VID/PID或设备类ID(Class ID)进行匹配,匹配成功后执行probe函数进行初始化。
驱动必须填充struct usb_driver结构体,这是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);
const struct usb_device_id *id_table; // 设备匹配表
struct device_driver driver;
};
内核通过驱动提供的匹配表(id_table)来查找对应的驱动:
static const struct usb_device_id my_usb_id_table[] = {
// 匹配特定设备的 VID/PID
{ USB_DEVICE(0x1234, 0x5678) },
// 或者匹配某一类设备,例如所有HID设备
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_CLASS_HID },
{ /* 哨兵,结束标志 */ }
};
MODULE_DEVICE_TABLE(usb, my_usb_id_table);
probe函数是驱动开始工作的核心入口,其主要任务包括:
- 获取设备指针:从传入的
struct usb_interface中提取struct usb_device指针。
- 查找端点:遍历接口的描述符,找到用于数据传输的端点(例如Bulk IN或OUT端点)。
- 创建URB:URB(USB请求块)是USB数据传输的核心结构。它封装了数据缓冲区以及传输描述信息(如目标端点、传输类型、回调函数)。你需要为每个传输任务分配并初始化一个URB。
- 提交URB:调用
usb_submit_urb()将URB提交给USB核心层。当传输完成(无论成功或失败)时,URB中预设的回调函数将被调用,进行数据处理或错误处理。
以下是一个简化的驱动probe函数示例,展示了如何分配和提交一个用于批量接收(Bulk IN)数据的URB:
#include <linux/usb.h>
/* URB传输完成回调函数 */
static void my_usb_complete(struct urb *urb)
{
// 检查传输状态:urb->status == 0 表示成功
if (urb->status) {
// 处理传输错误
return;
}
// 数据接收成功,处理 urb->transfer_buffer 中的数据
// 处理完毕后,通常需要重新提交此URB以接收后续数据包
}
static int my_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct urb *my_urb;
// 1. 假设我们已通过描述符解析得知批量输入端点地址为0x81
__u8 bulk_in_endpoint = 0x81;
// 2. 分配URB和数据缓冲区
my_urb = usb_alloc_urb(0, GFP_KERNEL);
char *transfer_buffer = kmalloc(512, GFP_KERNEL);
// 3. 初始化URB(用于批量传输)
usb_fill_bulk_urb(my_urb, // URB指针
dev, // USB设备
usb_rcvbulkpipe(dev, bulk_in_endpoint), // 构建接收管道
transfer_buffer, // 传输缓冲区
512, // 缓冲区长度
my_usb_complete, // 完成回调函数
NULL); // 传递给回调函数的上下文数据
// 4. 提交URB,发起异步数据接收
if (usb_submit_urb(my_urb, GFP_KERNEL)) {
// 提交失败,进行错误处理
kfree(transfer_buffer);
usb_free_urb(my_urb);
return -EIO;
}
// 可以将驱动私有数据存储到接口结构中,便于在disconnect等函数中获取
// usb_set_intfdata(intf, my_private_data);
return 0;
}
通过上述流程,USB设备驱动便与内核的USB子系统及网络协议栈协同工作,完成了复杂设备通信的抽象与实现。