找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2752

积分

0

好友

387

主题
发表于 7 小时前 | 查看: 0| 回复: 0

上一期我们探讨了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-)上的转换。

Linux内核USB驱动目录结构图

值得注意的是,USB控制器与PHY通常紧密耦合。大部分控制器芯片会集成PHY模块,控制器按照USB协议封装好数据包后,交由PHY转化为电信号发送。

二、理解USB子系统的核心数据结构

当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; // 驱动私有数据
};

驱动匹配的基本流程如下:

  1. USB设备插入,触发硬件中断。
  2. HCD驱动检测中断,创建USB设备抽象。
  3. USB Core读取设备描述符。
  4. 调用 usb_match_deviceusb_match_interface 函数与已注册的驱动进行匹配。
  5. 匹配成功,触发驱动的 probe 函数进行初始化。

USB设备驱动匹配流程图

三、USB数据传输完整流程剖析

理解了静态结构,我们来看动态的数据流。它清晰地展示了从驱动到硬件的数据传递路径。

1. 设备驱动层:填充并提交URB

设备驱动负责准备URB,这是数据传输的起点。

USB设备驱动填充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进行合法性校验和统一调度,确保协议规范性。

USB Core处理URB流程代码图

关键操作:

  • URB合法性校验。
  • 获取并锁定目标端点对象。
  • 将URB加入该端点的传输队列 (urb_list)。
  • 调用HCD驱动提供的 urb_enqueue 接口,将URB交给具体的控制器驱动处理。

3. HCD层:协议封装与硬件交互

主机控制器驱动将URB转化为硬件能理解的传输请求块(如xHCI的TRB),并触发硬件执行。

HCD将URB转为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);

USB设备ID表示例代码

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; //传输的节点
};

USB设备驱动私有数据结构体代码

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;
}

USB驱动probe函数关键实现代码

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);
    }
}

URB传输完成回调函数代码

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驱动write函数实现示例代码


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




上一篇:SaaS产品上线后的增长路径:冷启动、PMF验证与系统化增长策略
下一篇:Windows与Android环境搭建:Frida 17.6.2安装与配置完整指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-28 16:53 , Processed in 0.264955 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表