Linux USB驱动阅读笔记(1)

本文是对相关 USB 驱动技术文章的补充与拓展。内容围绕 Linux USB 体系结构展开,深入补充了四个核心板块:USB 总线 的设备查询与 Hub 传输机制;usb-skeleton 框架 中的 DMA 机制与读写逻辑;USB 子系统核心 的事件队列(usb_hub_wq)与主控初始化流程;以及 USB 鼠标驱动usb_mouseinput_dev 的结构体绑定和 URB 中断处理细节,旨在补全原文章的底层技术实现,让读者更好地掌握 USB 驱动框架。


1. USB总线

补充知识点:


1.1 设备查询链路

将 USB 的物理传输机制与 Linux 内核的软件响应流程结合起来,可以完整还原一个 USB 设备从"插入端口"到"完成枚举"的全链路工作机制。

系统的核心设计哲学可以概括为:物理总线层靠"硬件自动轮询",系统内核层靠"中断异步驱动"

全链路协同工作流程

text 复制代码
======================= 1. 物理总线层 (纯硬件自动维护) =======================
  [Host 控制器硬件 (xHCI/EHCI)] ───(每 8ms 自动发 IN 包)───> [Hub 芯片]
                                                               │
  ┌────────────────────────────────────────────────────────────┘
  ├─►【无插拔事件】:Hub 回复 NAK ───> 物理层握手结束,无 DMA 写入,CPU 0% 占用 (休眠/做其他事)
  │
  └─►【有插拔事件】:物理引脚检测到电平变化 ───> Hub 寄存器置位
                                                       │
======================= 2. 硬件与内核交接 (DMA + IRQ) =======================
  [Host 控制器再次发 IN 包] ───> [Hub 回复 DATA 包]
                                       │
                         (Host 控制器通过 DMA 将数据写入内存)
                                       │
                         (Host 控制器拉高中断线,触发 CPU 物理中断 IRQ)
                                       │
======================= 3. 内核软件层 (事件驱动异步处理) =====================
  [CPU 响应 IRQ] ───> [进入 HCD ISR 中断服务程序]
                               │
                (解析为 Hub 端口状态变更,完成 URB)
                               │
                (唤醒内核工作队列 Workqueue 中的 hub_event 线程)
                               │
  [hub_event 线程] ───> 执行复位端口、分配地址、读取描述符、匹配设备驱动 (枚举流程)
                               │
                       [处理完毕,线程重新进入休眠]

关键协同节点深度剖析

1. 硬件轮询与 CPU 解耦

USB 规范规定只能由 Host 发起通信,因此在物理层必须存在"周期性查询"。

  • 硬件分工 :这一查询完全由 Host 控制器的物理 PHY 状态机与 DMA 自动执行(如以 8ms 为周期向 Hub 的中断端点发送 IN 包)。
  • 对 CPU 的保护 :在没有插拔事件发生时,Hub 始终回复 NAK 包。此过程在芯片硬件电路内部消化,既不产生数据写入,也不向 CPU 发送中断信号,彻底解耦了总线物理轮询与 CPU 算力

2. 从物理事件到内核中断的转接

当物理端口发生改变(如插入 U 盘)时,Hub 芯片内部寄存器置位:

  • 当 Host 控制器硬件下一次发出 IN 包时,Hub 回复 DATA 包。
  • Host 控制器将返回的状态数据通过 DMA 自动写入 预先分配好的系统内存。
  • 数据搬运完成后,Host 控制器芯片硬件拉高中断线,向 CPU 发送 物理 CPU 中断(IRQ)。此时,内核才正式被介入。

3. 软件层的异步延迟处理

为了避免在中断上下文(ISR)中占用 CPU 时间过长,Linux 内核采用了典型的"上下半部"异步设计:

  • 上半部(HCD ISR):快速识别并接收该 URB,通知内核"Hub 状态改变了",随后立即退出中断。
  • 下半部(Workqueue 线程) :内核唤醒处于阻塞休眠状态的 hub_event() 异步线程。由该线程在后台完成复位端口、分配地址、读取描述符及绑定驱动等相对耗时的枚举操作。
  • 复位休眠 :枚举完成后,hub_event() 重新进入阻塞休眠状态,等待下一次硬件中断的唤醒。

总结:

这种"硬件轮询 + 内核中断"结合的设计,巧妙兼顾了 USB 规范的严苛要求与操作系统的高效运行:

  1. 总线合规性 :依靠 Host 控制器硬件逻辑 定期轮询,满足了 USB 严格的单主机主从通信规范。
  2. 系统高性能 :依靠 物理中断 + 内核工作队列 的事件驱动机制,保证了 CPU 在平时零开销、有事件时按需唤醒,绝不会因为死循环轮询而浪费算力与功耗。

1.2 集线器与根集线器的传输差异

在 Linux USB 主机子系统(USB Core & HCD)的设计中,将 URB(USB Request Block)的传输和处理明确划分为根集线器(Root Hub) 普通外部集线器/设备(Non-Root Hub)两种情况,主要是由硬件架构的物理差异内核软件抽象的统一性需求决定的。

1. 硬件本质的差异:物理总线 vs 芯片内部寄存器

  • 普通设备 / 外部 Hub(Non-Root Hub)

    • 硬件实体 :通过物理 USB 线缆( D + / D − D+/D- D+/D− 或 Type-C 差分线)连接在板外的实体硬件。
    • 通信方式:必须遵循严格的 USB 物理层与链路层协议。Host 控制器(如 xHCI/EHCI)需要通过 DMA 将 URB 中的数据打包,在物理总线上产生真实的电平信号(发包、收包、Handshake 应答)。
  • 根集线器(Root Hub)

    • 硬件实体 :它是集成在 Host 控制器芯片内部(SoC 内部)的逻辑电路,并不是一个物理上接在总线上的外设。
    • 通信方式 :Root Hub 根本没有物理 USB 线缆,它的端口状态、控制寄存器(如端口复位、供电使能、插拔检测状态)直接映射在 Host 控制器的 MMIO(内存映射 I/O)地址空间或寄存器 中。

2. 内核调度的差异:软件仿真 vs 硬件 DMA 调度

当上层驱动(如 hub 驱动)发起一个 URB 请求时,USB Core 会根据目标设备做出不同的路由分发:

① 对非 Root Hub 的 URB 处理

URB 提交后,HCD 驱动会将其翻译为硬件 Controller 能理解的传输描述符(TRB/TD),写进系统的内存环形缓冲区,然后启动 DMA 引擎 往物理总线上发包。传输完成后通过硬件中断返回状态。

② 对 Root Hub 的 URB 处理(软件仿真 / Emulation)

既然 Root Hub 只是 Host 控制器内部的一堆寄存器,如果像普通设备那样去发物理包,在硬件上根本行不通。

因此,Linux 内核中的 HCD 驱动为 Root Hub 做了软件仿真(Virtual Hub / Emulation)

  • hub 驱动给 Root Hub 提交控制 URB(如读取描述符、复位端口)时,HCD 驱动完全不触发物理 DMA 传输
  • HCD 会直接拦截这个 URB,用 CPU 直接读取 Host 控制器的内部 MMIO 寄存器,将寄存器里的状态信息(如端口是否有设备插入)手动拼装成标准 USB 描述符格式,填入 URB 的缓冲区中。
  • 接着,HCD 手动将 urb->status 设为 0,并直接调用/模拟触发 urb->complete() 回调函数

3. 设计哲学:用统一的接口屏蔽底层硬件差异

既然 Root Hub 是芯片内部寄存器,为什么不单独为它写一套 API,而要强行让它也走 URB 流程呢?

  • 软件架构的统一性(Software Abstraction)
    在 Linux 内核上层看来,无论是芯片内部的第一个端口(Root Hub Port),还是用户通过外接 Hub 扩展出来的第 10 个端口,在业务逻辑上都必须是同等对待的 USB Hub
  • 复用集线器驱动代码(drivers/usb/core/hub.c
    如果将 Root Hub 和外部 Hub 区分对待,Linux 内核就必须写两套 Hub 设备枚举与状态监听逻辑。而通过将 Root Hub 的寄存器操作包装成 URB 响应 ,上层的 hub 驱动就可以用完全相同的一套状态机、完全相同的 hub_event() 工作队列来同时管理 Root Hub 和外部 Hub。

总结:

Linux 将 URB 传输划分为根集线器和非根集线器,本质上是一种"用软件仿真(Emulation)统一硬件差异"的设计:

维度 非根集线器 (External Device / Hub) 根集线器 (Root Hub)
物理本质 芯片外部的物理外设 Host 控制器芯片内部的逻辑寄存器
数据传输 走真实物理总线 + DMA 硬件发包 CPU 直接读写 MMIO 寄存器 模拟传输
URB 完成方式 物理总线传输完成 → \rightarrow → 硬件中断 → \rightarrow → complete 回调 HCD 直接读取寄存器填充缓冲区 → \rightarrow → 手动触发 complete 回调
划分目的 按照 USB 物理协议规范收发数据 欺骗上层内核,实现 Root Hub 与外部 Hub 驱动逻辑的统一

2. usb-skeleton.c usb驱动源码分析

补充知识点:


2.1 skel_driver

这段代码是 Linux USB 接口驱动(usb_driver)的注册结构体定义与模块入口宏,是所有 USB 接口驱动(Interface Driver)接入 Linux USB Core 框架的核心基础。

c 复制代码
/* 
 * 定义并初始化一个 usb_driver 结构体变量 skel_driver。
 * 该结构体包含了 USB 核心层(USB Core)管理该驱动所需的基本信息与回调函数指针。
 */
static struct usb_driver skel_driver = {
        /* 驱动程序的名称,注册后会在 /sys/bus/usb/drivers/ 下生成对应的目录 */
        .name =         "skeleton",

        /* 
         * 设备探测回调函数(关键)
         * 当 USB 总线上插入一个新设备,且该设备的 VID/PID 与 id_table 匹配成功时,
         * USB Core 会自动调用此函数,用于初始化该设备、分配内存并创建字符设备节点。
         */
        .probe =        skel_probe,

        /* 
         * 设备拔出/断开回调函数(关键)
         * 当 USB 设备从主机物理拔出,或驱动被卸载时调用。
         * 用于释放 probe 中分配的内存、注销接口节点并终止所有未完成的 URB。
         */
        .disconnect =   skel_disconnect,

        /* 
         * 电源管理:挂起回调函数
         * 当系统进入休眠状态,或该 USB 设备触发自动挂起(Autosuspend)时调用,
         * 用于暂停数据传输、释放/取消正在进行中的 URB,降低设备功耗。
         */
        .suspend =      skel_suspend,

        /* 
         * 电源管理:恢复回调函数
         * 当系统从休眠唤醒,或设备从挂起状态恢复时调用,
         * 用于重新初始化设备并恢复正常的 I/O 传输。
         */
        .resume =       skel_resume,

        /* 
         * 复位前回调函数
         * 当 USB Core 或其他驱动准备对该设备发起总线复位(Reset)之前调用,
         * 驱动需要在此处停止提交新的 URB,并等待当前传输完成,防止复位过程中发生数据错乱。
         */
        .pre_reset =    skel_pre_reset,

        /* 
         * 复位后回调函数
         * 当总线复位操作完成后调用,
         * 驱动需要在此处重新配置接口、重新分配端点资源并恢复 I/O 传输。
         */
        .post_reset =   skel_post_reset,

        /* 
         * 设备 ID 匹配表(关键)
         * 指向一个 struct usb_device_id 数组,列出了该驱动能够支持的所有 USB 设备的 VID(厂商 ID)和 PID(产品 ID)。
         * USB Core 依靠这张表来进行总线上的"驱动与设备匹配(Match)"。
         */
        .id_table =     skel_table,

        /* 
         * 标记该驱动是否支持运行时的自动挂起(Autosuspend)功能。
         * 设置为 1 表示该驱动具备 PM(电源管理)能力,当总线空闲一段时间后,允许内核将设备置入低功耗模式。
         */
        .supports_autosuspend = 1,
};

/* 
 * 模块驱动注册便捷宏(内核辅助宏)
 * 这是一个极简宏,用以替换传统的 module_init() 与 module_exit() 模版代码。
 * 它会在模块加载时自动调用 usb_register(&skel_driver),
 * 在模块卸载时自动调用 usb_deregister(&skel_driver)。
 */
module_usb_driver(skel_driver);

关键机制补充说明:

  1. id_tableprobe 的联动

    当用户插入 USB 设备时,内核的 USB 总线扫描程序读取设备的硬件描述符,获取其 VID/PID。随后遍历所有已注册的 usb_driver 中的 id_table。一旦匹配成功,内核就会自动触发 .probe 指针指向的 skel_probe 函数。

  2. module_usb_driver() 宏展开后的实质

    如果不使用 module_usb_driver(skel_driver),开发者需要手动编写如下代码:

    c 复制代码
    static int __init skel_init(void)
    {
        return usb_register(&skel_driver);
    }
    
    static void __exit skel_exit(void)
    {
        usb_deregister(&skel_driver);
    }
    
    module_init(skel_init);
    module_exit(skel_exit);

    module_usb_driver() 的作用就是自动生成上述模板代码,简化内核模块的入口与出口声明。

总结:

skel_driver 是 Linux 官方提供的一个 USB 骨架驱动实例(Skeleton Driver)

它的核心作用是:作为编写 Linux USB 字符设备驱动的"官方标准模板"与"框架入口"

具体作用体现在以下三个方面:

  1. 向内核注册骨架驱动 :通过 module_usb_driver(skel_driver) 统一向 Linux USB Core 声明该骨架驱动的存在,使其能够接入内核的总线管理体系。
  2. 定义骨架驱动的行为规范 :绑定了该模板处理硬件插拔(skel_probe / skel_disconnect)、电源管理(skel_suspend / skel_resume)以及总线复位时的具体函数实现。
  3. 建立设备识别规则 :通过绑定 skel_table,指定该模板驱动用来匹配和测试哪些具体的 USB 硬件(VID/PID)。

2.2 usb_skel

struct usb_skel 是 USB 骨架驱动的核心自定义结构体。在 Linux 驱动开发中,它被称为驱动的私有数据结构(Private Data Structure)

它的核心作用是:把"一个具体的物理 USB 设备"在软件层面上所需要的所有状态、数据缓冲区、端点地址、并发锁以及同步等待队列装入同一个结构体中,实现设备资源的统一集中管理。

c 复制代码
struct usb_skel {
        /* 指向该设备对应的 Linux USB 核心设备结构体 (代表整个物理 USB 设备) */
        struct usb_device       *udev;                  

        /* 指向当前驱动所绑定的 USB 接口结构体 (代表该设备上的某个具体接口) */
        struct usb_interface    *interface;             

        /* 信号量:用于限制当前正在处理中的写 URB 数量,防止并发写入过多导致内存耗尽或硬件崩溃 */
        struct semaphore        limit_sem;              

        /* USB 锚点:用于集中管理所有已提交(In-flight)的 URB。当设备拔出时,可以通过锚点一键取消所有挂起的 URB */
        struct usb_anchor       submitted;              

        /* 用于执行 Bulk IN(批量读取)传输的 URB 结构体指针 */
        struct urb              *bulk_in_urb;           

        /* 驱动在内核空间分配的读缓冲区指针,用于接收从 USB 设备端点传来的原始数据 */
        unsigned char           *bulk_in_buffer;        

        /* 读缓冲区(bulk_in_buffer)的总内存容量大小 (字节数) */
        size_t                  bulk_in_size;           

        /* 当前读缓冲区中已被硬件填充的有效数据总字节数 */
        size_t                  bulk_in_filled;         

        /* 当前读缓冲区中的数据已被 copy_to_user() 拷贝给用户空间的字节数 (用于偏移指针计算) */
        size_t                  bulk_in_copied;         

        /* 设备的批量输入端点地址(Bulk IN Endpoint Address,含方向标志位) */
        __u8                    bulk_in_endpointAddr;   

        /* 设备的批量输出端点地址(Bulk OUT Endpoint Address,含方向标志位) */
        __u8                    bulk_out_endpointAddr;  

        /* 记录上一次 I/O 传输过程中发生的错误码(如超时、硬件断开等) */
        int                     errors;                 

        /* 标记位:表示当前是否有一个 Bulk IN 读取传输正在物理总线上执行 */
        bool                    ongoing_read;           

        /* 自旋锁:用于在中断上下文与线程上下文之间互斥保护 errors 字段的读写 */
        spinlock_t              err_lock;               

        /* 内核引用计数器(Kernel Reference):用于管理该结构体自身的生命周期,防止"设备已拔出但用户程序仍打开文件"时引发野指针崩溃 */
        struct kref             kref;

        /* 互斥锁:用于同步用户空间的读写 I/O 操作与内核的 disconnect(设备拔出)流程,防止并发竞态 */
        struct mutex            io_mutex;               

        /* 1-bit 位域标记:标记设备是否已被物理拔出(1 表示已断开,0 表示正常连接) */
        unsigned long           disconnected:1;

        /* 等待队列头:当应用层发起 read() 但当前正有一个物理读取操作在进行时,用于将当前进程挂起休眠 */
        wait_queue_head_t       bulk_in_wait;           
};

结构体设计核心功能分类:

为了更清晰地理解,该结构体内部的成员可以划分为 5 大功能模块:

  1. 硬件基础信息 (udev, interface, bulk_in_endpointAddr, bulk_out_endpointAddr)

    • 记录设备在 USB Core 中的句柄以及通讯所必需的端点地址。
  2. 读数据缓冲区管理 (bulk_in_urb, bulk_in_buffer, bulk_in_size, bulk_in_filled, bulk_in_copied)

    • 管理从物理设备读到的数据流。配合 ongoing_readbulk_in_wait 实现当内存无数据时发起物理 I/O 并阻塞等待的逻辑。
  3. 并发流量控制与 URB 追回 (limit_sem, submitted)

    • limit_sem 避免应用层高频 write() 导致内核分配过多 URB 耗尽内存。
    • submittedstruct usb_anchor)将驱动发出的所有 URB 锚定在一起,拔出设备时只需调用 usb_kill_anchored_urbs() 即可一次性安全取消所有在途传输。
  4. 安全退场与生命周期保护 (kref, io_mutex, disconnected)

    • 解决 Linux 驱动中最棘手的"热插拔竞态问题":如果用户打开了 /dev/skel0 接口,然后突发拔掉 USB 设备,kref 会确保 usb_skel 结构体不会被立即 kfree() 释放,直到应用层关闭文件句柄;io_mutex 则确保拔出事件不会破坏正在进行的读写流程。
  5. 异常与同步 (errors, err_lock, bulk_in_wait)

    • 收集底层 ISR(中断服务程序)异步返回的传输错误,并通过等待队列让应用层线程按需休眠/唤醒。

2.3 URB_NO_TRANSFER_DMA_MAP

这里的标志名 URB_NO_TRANSFER_DMA_MAP 中的 NO 并不是针对"要不要进行 DMA 传输"的限制,而是针对"USB Core(USB 核心层)要不要去帮你做 DMA 映射(Mapping)"的控制

1. 核心概念辨析:传输 vs 映射(Map)

  • 做 DMA 传输(Transfer):指的是 USB Host 控制器硬件通过 DMA 引擎直接从 RAM 中读取或写入数据。
  • 做 DMA 映射(DMA Mapping) :指的是将内核中的虚拟地址(transfer_buffer 转换为物理/设备能直接访问的 DMA 物理地址(transfer_dma,即 dma_addr_t 的过程。这个转换需要调用系统底层接口(如 dma_map_single()),在某些平台上甚至要操作 IOMMU 和刷 CPU Cache,是有一定性能开销的。

2. 标志位的真正含义

默认情况(标志位未设置,NO 为假):

  • 驱动层的行为 :驱动开发者只申请并填充了普通的内存缓冲区(transfer_buffer,比如用 kmalloc 出来的虚拟内存),并没有自己去映射 DMA 地址。
  • USB Core 的行为 :USB Core 检查到标志位未置位,就会意识到:"设备驱动还没做 DMA 映射,我必须在帮它把 URB 发给 HCD 之前,调用 dma_map_single() 自动建立 DMA 映射 ,并将得到的物理地址填入 transfer_dma。"

设置了 URB_NO_TRANSFER_DMA_MAP(NO 为真):

  • 含义"USB 核心层,请【不要】帮我做 DMA 映射了!"
  • 原因 :因为驱动程序(如高性能驱动或设备控制器驱动)自己已经提前用 dma_alloc_coherent() 申请好了 DMA 缓冲区 ,并且把 DMA 物理地址手动写进了 transfer_dma 中。
  • USB Core 的行为 :USB Core 看到这个标志后,就会跳过 dma_map_single() 的映射步骤,直接将 transfer_dma 中的物理地址传给 Host 控制器去执行 DMA 传输。

3. 同理:URB_NO_SETUP_DMA_MAP

对于 USB 控制传输(Control Transfer)特有的 Setup 包,逻辑完全一致:

  • 设置了标志 :通知 USB Core 不要去映射 setup_packet(虚拟地址),因为驱动已经提前映射好并把 DMA 物理地址放在了 setup_dma 中,请直接使用 setup_dma 进行传输

4. 为什么要设计这个标志位?(性能优化)

如果一个驱动需要高频、大批量地复用同一块缓冲区发 URB(例如连续传输 4K 视频流):

  1. 如果每次提交 URB 都让 USB Core 去做 dma_map_single()dma_unmap_single(),频繁刷 Cache 会严重拖慢性能。
  2. 驱动开发者可以事先一次性申请好一块 DMA 物理连续内存(或者只映射一次),后续每次发 URB 时只填 transfer_dma 并挂上 URB_NO_TRANSFER_DMA_MAP 标志,从而省去了每次传输时 USB Core 进行 DMA 映射的软件开销

总结:

  • URB_NO_TRANSFER_DMA_MAP 的全称理解应该是:URB_NO_[USB_CORE_DOING_]_TRANSFER_DMA_MAP(USB 核心层无须进行 DMA 映射)。
  • 不是禁用 DMA ,而是告诉系统:"DMA 物理地址我已经自己映射并准备好了(在 transfer_dma 里),你直接拿去用,别再多此一举帮我做映射了。"

2.4 skel_read 与 skel_do_read_io

以下通过一个具体的数值场景,说明应用层、skel_read()skel_do_read_io() 与 USB 硬件设备之间的协同过程。

初始状态:

  • 用户空间 :准备调用 read()
  • 驱动缓冲区(dev->bulk_in_buffer :容量为 4096 字节,当前有效数据量为 0 字节。
  • USB 硬件端点 FIFO :存有设备采集到的 2048 字节数据。

场景一:缓冲区为空时发起读取(触发物理 I/O)

1. 应用层发起请求

用户程序执行:

c 复制代码
char buf[1024];
read(fd, buf, 1024); // 请求读取 1024 字节

2. 进入 skel_read() 判定

  • skel_read() 读取内核结构体,发现 dev->bulk_in_buffer 中有效数据量为 0(小于请求的 1024 字节)。
  • 内存中没有可用数据,skel_read() 调用 skel_do_read_io(dev, 1024)

3. 执行 skel_do_read_io() 向硬件要数据

  • 配置与提交 URB :分配 Bulk IN URB,将接收缓冲区指向 dev->bulk_in_buffer,调用 usb_submit_urb() 发送到总线。
  • 休眠等待 :调用 wait_for_completion(),当前进程进入休眠状态。
  • 总线传输 :Host 控制器向设备发送 IN 包,USB 硬件将 FIFO 中的数据发送出来。假设硬件本次实际通过 DMA 向 dev->bulk_in_buffer 写入了 2048 字节,并触发中断。
  • 唤醒与返回 :中断服务函数唤醒进程,skel_do_read_io() 更新缓冲区记录(当前有效数据量 = 2048 字节),随后返回。

4. skel_read() 完成拷贝

  • 此时 dev->bulk_in_buffer 内已有 2048 字节新数据。
  • skel_read() 调用 copy_to_user() 将前 1024 字节拷贝给用户程序的 buf
  • 更新缓冲区状态:偏移指针后移 1024 字节,剩余有效数据量变为 1024 字节(2048 - 1024)。
  • skel_read() 返回 1024

场景二:缓冲区有剩余数据时发起读取(不触发物理 I/O)

1. 应用层再次发起请求

用户程序紧接着执行:

c 复制代码
char buf2[512];
read(fd, buf2, 512); // 请求读取 512 字节

2. 进入 skel_read() 判定

  • skel_read() 检查发现 dev->bulk_in_buffer 中仍有 1024 字节上次留下的数据,大于本次请求的 512 字节。
  • **不调用 skel_do_read_io()**,完全不向 USB 总线和硬件发送任何请求。

3. 直接拷贝内存数据

  • skel_read() 直接调用 copy_to_user(),从 dev->bulk_in_buffer 当前偏移位置取出 512 字节拷贝给用户程序的 buf2
  • 更新缓冲区状态:剩余有效数据量变为 512 字节(1024 - 512)。
  • skel_read() 返回 512

3. USB总线核心和主控驱动

3.1 khubd → usb_hub_wq

从早期的专用内核线程(khubd)演进到高版本的共享/专用工作队列(usb_hub_wq),这一架构重构主要是为了解决资源开销、系统并发性能以及内核线程滥用的问题。

具体原因可以归纳为以下四个核心维度:

1. 降低系统内存与调度开销(避免内核线程膨胀)

在低版本内核中,khubd 是一个独立的内核线程(struct task_struct)。

  • 独立线程的代价:每一个内核线程都需要占用独立的内核栈空间(通常为 8KB 或 16KB)、线程控制块(TCB)以及调度器资源。
  • 实际利用率极低 :在大部分平静状态下,系统没有 USB 设备插拔,khubd 线程 99.9% 的时间都在阻塞休眠。为这样一个低频触发的任务长期维持一个独立的内核线程,是对内存和 CPU 调度资源的浪费。
  • 工作队列的优势 :工作队列(Workqueue)将"任务逻辑"与"执行线程"解耦。工作队列由内核统一管理(Workqueue 子系统),只有在真正产生插拔事件时,才会将工作项(work_struct)调度到内核工作线程上执行,平峰期零额外线程开销

2. 拥抱 Linux 内核 CMWQ(并发管理工作队列)架构

Linux 3.x 及更高版本内核重构了工作队列子系统,引入了 CMWQ(Concurrency Managed Workqueue)

  • 旧机制的痛点 :在引入 CMWQ 之前,驱动开发人员倾向于为各种子系统创建独立的内核线程(如 khubd),导致系统中充斥着大量 kworker 和专用守护线程,极易引发上下文切换开销过大和 CPU 缓存失效。
  • CMWQ 的优化 :CMWQ 能够根据系统负载动态创建和销毁内核线程,并在多核 CPU 之间智能分配工作项。USB 子系统使用 usb_hub_wq 后,可以直接利用 CMWQ 的动态并发能力:当有多个 USB 端口同时发生插拔时,CMWQ 会自动并行处理;而在空闲时则不占用额外的线程资源。

3. 简化内核代码逻辑与生命周期管理

  • 旧线程模式(khubd :必须由 USB Core 显式维护线程的创建(kthread_run)、循环体中的条件等待(wait_event_freezable)、唤醒逻辑(wake_up_process)以及卸载时的停止(kthread_stop)。
  • 工作队列模式(usb_hub_wq:把复杂的线程休眠、唤醒和状态同步完全委托给内核成熟的 Workqueue 框架。USB Core 只需要执行:
  • 初始化usb_hub_wq = alloc_ordered_workqueue("usb_hub_wq", WQ_FREEZABLE);
  • 触发事件queue_work(usb_hub_wq, &hub->events);
  • 销毁destroy_workqueue(usb_hub_wq);
    代码简洁度与健壮性显著提升。

4. 更好的系统电源管理(PM & Freeze Support)

USB 集线器必须良好支持系统的休眠(Suspend)与唤醒(Resume):

  • 在配置 usb_hub_wq 时,内核传入了 WQ_FREEZABLE 标志。
  • 这使得系统在进入低功耗休眠(System Suspend)时,电源管理子系统可以自动冻结 usb_hub_wq 中挂起的工作项,防止休眠过程中突然插入 USB 设备导致系统唤醒逻辑被打乱。如果使用自定义内核线程,则需要编写大量的冻结检测代码(如 try_to_freeze())。

总结对比:

维度 低版本内核:khubd 内核线程 高版本内核:usb_hub_wq 工作队列
资源占用 长期占用独立的 task_struct 与内核栈 仅在有事件时借用/分配工作线程,无事件时零开销
并发能力 单线程顺序处理,多端口同时插拔时效率较低 基于 CMWQ,可根据需要自动实现多核并发处理
代码维护 需要手动维护线程循环、唤醒与停止逻辑 规范化的 queue_work() 接口,逻辑封装更完善
电源管理 需手动处理线程冻结(Freezing) 设置 WQ_FREEZABLE 标志后由 PM 子系统自动托管

3.2 hub_event

c 复制代码
static void hub_event(struct work_struct *work)
{
        struct usb_device *hdev;
        struct usb_interface *intf;
        struct usb_hub *hub;
        struct device *hub_dev;
        u16 hubstatus;
        u16 hubchange;
        int i, ret;

        /* 1. 上下文获取:通过 work 指针反查出包含它的 struct usb_hub 结构体 */
        hub = container_of(work, struct usb_hub, events);
        hdev = hub->hdev;                     /* 获取 Hub 对应的 USB 设备指针 */
        hub_dev = hub->intfdev;               /* 获取 Hub 接口的 struct device 指针 */
        intf = to_usb_interface(hub_dev);     /* 获取 Hub 的 USB 接口结构体 */

        /* 开启内核动态覆盖率跟踪 (KCOV) 工具,用于内核安全测试与 Fuzzing */
        kcov_remote_start_usb((u64)hdev->bus->busnum);

        dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
                hdev->state, hdev->maxchild,
                (u16) hub->change_bits[0],
                (u16) hub->event_bits[0]);

        /* 2. 安全检查:上锁保护,防止并发处理竞态 */
        usb_lock_device(hdev);
        
        /* 如果在等待锁的过程中该 Hub 设备已经被物理拔出,直接退出 */
        if (unlikely(hub->disconnected))
                goto out_hdev_lock;

        /* 如果 Hub 设备本身掉线/失效,清理 Hub 驱动资源并退出 */
        if (hdev->state == USB_STATE_NOTATTACHED) {
                hub->error = -ENODEV;
                hub_quiesce(hub, HUB_DISCONNECT); /* 停止该 Hub 所有的 URB 传输 */
                goto out_hdev_lock;
        }

        /* 3. 运行休眠管理:唤醒 Hub (Autoresume) */
        /* 在访问 Hub 寄存器前,先通过 Runtime PM 机制确保 Hub 处于供电正常工作状态 */
        ret = usb_autopm_get_interface(intf);
        if (ret) {
                dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
                goto out_hdev_lock;
        }

        /* 如果 Hub 当前正在停止/静默过程中,不做任何处理 */
        if (hub->quiescing)
                goto out_autopm;

        /* 4. 异常恢复:如果 Hub 之前记录了严重传输错误,尝试做重置复位 */
        if (hub->error) {
                dev_dbg(hub_dev, "resetting for error %d\n", hub->error);

                ret = usb_reset_device(hdev); /* 执行硬件重置 */
                if (ret) {
                        dev_dbg(hub_dev, "error resetting hub: %d\n", ret);
                        goto out_autopm;
                }

                hub->nerrors = 0;
                hub->error = 0;
        }

        /* ================= 5. 下属端口状态变更处理 (核心逻辑 1) ================= */
        /* 遍历该 Hub 上的每一个物理下行端口 (Port 1 ~ maxchild) */
        for (i = 1; i <= hdev->maxchild; i++) {
                struct usb_port *port_dev = hub->ports[i - 1];

                /* 检查该端口是否有事件发生 (事件位、变更位或远程唤醒位) */
                if (test_bit(i, hub->event_bits)
                                || test_bit(i, hub->change_bits)
                                || test_bit(i, hub->wakeup_bits)) {
                        
                        /* 保护该端口在处理过程中不会被系统后台 Runtime PM 强制挂起休眠 */
                        pm_runtime_get_noresume(&port_dev->dev);
                        pm_runtime_barrier(&port_dev->dev);
                        
                        /* 上锁保护该端口,防止并发处理 */
                        usb_lock_port(port_dev);
                        
                        /* 核心子函数:处理该端口的具体事件 (如插拔枚举、复位、唤醒等) */
                        port_event(hub, i);
                        
                        usb_unlock_port(port_dev);
                        pm_runtime_put_sync(&port_dev->dev);
                }
        }

        /* ================= 6. Hub 自身状态变更处理 (核心逻辑 2) ================= */
        /* event_bits 位 0 代表 Hub 自身的状态改变 */
        if (test_and_clear_bit(0, hub->event_bits) == 0)
                ;       /* 没有 Hub 自身状态事件,什么都不做 */
        /* 向 Hub 发送标准 USB 控制命令获取最新的 Hub Status */
        else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
                dev_err(hub_dev, "get_hub_status failed\n");
        else {
                /* 处理 Hub 电源模式改变 (如从总线供电切换到外接电源供电) */
                if (hubchange & HUB_CHANGE_LOCAL_POWER) {
                        dev_dbg(hub_dev, "power change\n");
                        clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
                        if (hubstatus & HUB_STATUS_LOCAL_POWER)
                                hub->limited_power = 1;
                        else
                                hub->limited_power = 0;
                }
                
                /* 处理 Hub 过流警告 (Over-Current) */
                if (hubchange & HUB_CHANGE_OVERCURRENT) {
                        u16 status = 0;
                        u16 unused;

                        dev_dbg(hub_dev, "over-current change\n");
                        clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
                        msleep(500);    /* 冷却等待 500ms,防止硬件烧毁 */
                        hub_power_on(hub, true); /* 尝试重新供电 */
                        hub_hub_status(hub, &status, &unused);
                        if (status & HUB_STATUS_OVERCURRENT)
                                dev_err(hub_dev, "over-current condition\n");
                }
        }

/* ================= 7. 清理与释放资源退出 ================= */
out_autopm:
        /* 平衡上面调用过的 usb_autopm_get_interface() 计数 */
        usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
        usb_unlock_device(hdev);

        /* 释放引用计数,允许 Hub 在空闲时再次进入自动休眠 (Autosuspend) */
        usb_autopm_put_interface(intf);
        kref_put(&hub->kref, hub_release);

        kcov_remote_stop();
}

hub_event 函数是 Linux 内核 USB Hub(集线器)子系统的核心事件处理函数

当物理层检测到 USB 端口有设备插入、拔出、过流或电源状态改变时,Hub 中断端点会接收到数据并向系统提交 Work 项。usb_hub_wq 工作队列正是通过调度并执行 hub_event 函数,来统一处理该 Hub 及其下属所有下行端口的改变。

函数的核心执行主线总结:

hub_event 函数的工作可以总结为以下五个关键步骤:

  1. 设备状态校验与安全互斥

    • 优先获取 usb_lock_device,防止在处理端口事件时发生热插拔竞态;
    • 确认 Hub 是否在线,若已断开则调用 hub_quiesce 停止所有 URB 传输。
  2. 电源唤醒与自愈(Auto-Resume & Self-Healing)

    • 调用 usb_autopm_get_interface 将可能处于低功耗休眠(Suspend)的 Hub 重新唤醒;
    • 如果该 Hub 之前出现过严重的总线错误,调用 usb_reset_device 对 Hub 进行总线复位。
  3. 处理端口级变化(Port Event Handles)

    • 遍历所有的物理下行端口,查找 event_bits / change_bits 标志。
    • 一旦发现端口有状态改变(例如:插入了新设备、拔出了旧设备、端口过流等),调用**最核心的子函数 port_event(hub, i)** 来执行设备的复位、地址分配以及驱动绑定(枚举流程)。
  4. 处理 Hub 自身事件(Hub Status Handles)

    • 检查标志位 0,读取 Hub 整体状态。处理 Hub 本身的本地供电状态变化 以及整机过流(Over-current)保护(切断电源 → \rightarrow → 冷却 500ms → \rightarrow → 尝试重新上电)。
  5. 电源平衡与资源释放

    • 减少电源管理计数,重新允许 Hub 在无事可做时进入 Runtime Autosuspend 低功耗状态,释放引用计数退出。

3.3 usb 主机控制器设备

c 复制代码
// arch/arm/mach-s3c/mach-smdk2440.c
/* 声明该文件属于 S3C2440 开发板 (SMDK2440) 的板级描述文件 */

/* MACHINE_START 是一个宏,用来定义 struct machine_desc 结构体,描述特定开发板的信息 */
/* 参数 S3C2440 是开发板的 Machine ID,"SMDK2440" 是内核启动时打印的开发板名称 */
MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ /* 维护者注释信息 */
        
        .atag_offset    = 0x100,               /* Bootloader (如 U-Boot) 传参 (ATAGS) 存放在 SDRAM 内存起始地址后的偏移量 (0x30000100) */

        .init_irq       = s3c2440_init_irq,    /* 函数指针:用于初始化 S3C2440 的中断控制器 (VIC/IRQ) */
        .map_io         = smdk2440_map_io,     /* 函数指针:建立外设物理寄存器到内核虚拟地址空间的静态映射 */
        .init_machine   = smdk2440_machine_init, /* 函数指针:板级设备初始化入口函数 (注册 platform_device 的核心) */
        .init_time      = smdk2440_init_time,  /* 函数指针:初始化系统定时器/时钟源 (System Timer/Clock Source) */
MACHINE_END                                    /* 结合 MACHINE_START 闭合结构体定义,把该结构体编译进 .arch.info.init 段 */


/* 板级设备初始化函数,__init 标记表示该函数在系统初始化完成后,占用的内存会被内核释放 */
static void __init smdk2440_machine_init(void)
{
        /* 设置 LCD 控制器的平台私有数据 (Platform Data),包含屏幕分辨率、时序等硬件参数 */
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);
        
        /* 设置 I2C0 控制器的平台私有数据,传递 NULL 表示使用默认时钟频率与配置 */
        s3c_i2c0_set_platdata(NULL);
        
        /* 配置 I2S 音频接口需要的 GPIO 引脚复用模式 */
        /* 参数含义:从 GPE0 开始,连续配置 5 个引脚,设为功能 2 (S3C_GPIO_SFN(2),即 I2S 模式),禁用内部上拉/下拉电阻 */
        s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
                              S3C_GPIO_PULL_NONE);
                              
        /* 核心逻辑:向内核平台总线 (platform_bus) 批量注册 smdk2440_devices 数组中定义的硬件设备 */
        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
        
        /* 执行 SMDK 系列开发板通用的板级后处理初始化 */
        smdk_machine_init();
}

/* 定义该开发板挂载的所有 platform_device 指针数组 */
/* __initdata 标记表示该数组在内核完成初始化后会被内核自动释放 */
static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_ohci, /* USB 1.1 Host 控制器 (OHCI 规范) 设备 */
        &s3c_device_lcd,  /* Framebuffer LCD 控制器设备 */
        &s3c_device_wdt,  /* 看门狗定时器 (Watchdog Timer) 设备 */
        &s3c_device_i2c0, /* I2C 总线控制器 0 设备 */
        &s3c_device_iis,  /* IIS (I2S) 音频总线控制器设备 */
};


// arch/arm/mach-s3c/devs.c
/* 位于 SoC 通用外设定义文件中:描述 S3C2440 芯片内部整合的 USB OHCI 主控平台设备 */
struct platform_device s3c_device_ohci = {
        .name           = "s3c2410-ohci", /* 设备名称:内核驱动模型通过该名字与对应的 platform_driver 进行匹配 */
        .id             = -1,             /* 设备 ID:-1 表示系统中只有一个该类型的设备,无需在名字后追加 .0, .1 等编号 */
        .num_resources  = ARRAY_SIZE(s3c_usb_resource), /* 该设备占用的资源 (Resource) 数量 */
        .resource       = s3c_usb_resource,             /* 包含寄存器物理内存基地址 (MMIO) 和中断号 (IRQ) 的资源数组 */
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask, /* 该设备能够寻址的 DMA 地址范围掩码指针 */
                .coherent_dma_mask      = DMA_BIT_MASK(32),        /* 一致性 DMA (Coherent DMA) 的 32 位寻址掩码 */
        }
};

}; /* 提示:末尾多余的闭合花括号为源码复制时的语法残留 */

核心机制与逻辑说明

1. Linux 内核驱动模型:设备与驱动分离

这段代码反映了在设备树 (Device Tree) 引入之前,Linux 内核对 SoC 板级硬件的组织方式:

复制代码
       +-------------------------------------------------------+
       |               Platform Bus (平台总线)                 |
       +-------------------------------------------------------+
                                   |
           Match 匹配机制:比对 platform_device.name 
                          与 platform_driver.driver.name
                                   |
             +---------------------+---------------------+
             |                                           |
             v                                           v
+--------------------------+               +--------------------------+
|     platform_device      |               |     platform_driver      |
|  (如: s3c_device_ohci)   |               |   (如: ohci-hcd-s3c2410)  |
+--------------------------+               +--------------------------+
| .name = "s3c2410-ohci"   | <=== 匹配 ===> | .driver.name =           |
| .resource (寄存器/IRQ)    |               |         "s3c2410-ohci"   |
+--------------------------+               +--------------------------+
                                                         |
                                                 匹配成功,调用
                                                 .probe() 初始化硬件
  • platform_device(描述硬件) :负责告知内核"系统里有什么硬件、物理基地址在哪、使用什么中断号"。例如 s3c_device_ohci
  • platform_driver(编写软件逻辑) :驱动程序(如 drivers/usb/host/ohci-s3c2410.c)注册时提供操作硬件的代码逻辑。
  • Platform Bus(解耦桥梁) :当 platform_add_devices() 被调用时,总线会将注册的设备与已经挂载的驱动进行字符串比对,名字一致即触发驱动的 .probe() 入口。

2. 系统启动链路 (Boot Flow)

  1. setup_arch() 阶段 :Bootloader (U-Boot) 传入 Machine ID,内核在 .arch.info.init 段中找到匹配的 MACHINE_START(S3C2440, ...) 结构体。

  2. 初始化钩子执行

    • 调用 .map_io 建立 IO 内存映射。
    • 调用 .init_irq 挂载中断向量表。
  3. smdk2440_machine_init() 被调用

    • 配置特定 GPIO 复用引脚(如 I2S)。
    • 通过 platform_add_devices 批量将 smdk2440_devices 挂载到平台总线上。
  4. 触发驱动 probe :注册 s3c_device_ohci 后,USB OHCI 控制器驱动匹配成功并启动,进一步向 USB 子系统注册 Root Hub,从而使能芯片的 USB Host 功能。

3.4 usb 主机控制器驱动

c 复制代码
//  .config
CONFIG_ARCH_S3C2410=y  /* 内核配置选项:开启三星 S3C24XX 平台架构支持 */
c 复制代码
//  drivers/usb/host/ohci-s3c2410.c

/* 模块初始化函数,当驱动被内核加载或静态编译打包启动时执行 */
static int __init ohci_s3c2410_init(void)
{
        /* 检查系统命令行是否禁用了 USB(如 bootargs 中传入了 usb-disabled) */
        if (usb_disabled())
                return -ENODEV;

        /* 打印驱动加载日志信息 */
        pr_info("%s: " DRIVER_DESC "\n", hcd_name);
        
        /* ------------ (1) 初始化通用 OHCI 驱动结构体 ------------ */
        /* 使用内核标准的 OHCI 框架初始化结构体 ohci_s3c2410_hc_driver (struct hc_driver) */
        ohci_init_driver(&ohci_s3c2410_hc_driver, NULL); 

        /*
         * 注释说明:
         * 三星 S3C2410/2440 的硬件 OHCI 控制器存在一些特殊 Quirks(硬件缺陷/特性),
         * 需要三星特有的 Workarounds(变通修复方案)。
         * 这里显式重写(Override)通用 hc_driver 中的 Hub 状态轮询和 Hub 控制函数。
         */

        /* 重写 Hub 状态变化检测函数,用于适配三星芯片的端口状态寄存器行为 */
        ohci_s3c2410_hc_driver.hub_status_data  = ohci_s3c2410_hub_status_data;
        
        /* 重写 Hub 控制请求处理函数,用于适配端口电源使能、复位等引脚控制 */
        ohci_s3c2410_hc_driver.hub_control      = ohci_s3c2410_hub_control;

        /* ------------ (2) 向平台总线注册平台驱动 ------------ */
        /* 将 ohci_hcd_s3c2410_driver 注册到 Linux 平台总线 (platform_bus) 上 */
        /* 注册过程会触发总线匹配,寻找名为 "s3c2410-ohci" 的 platform_device */
        return platform_driver_register(&ohci_hcd_s3c2410_driver);
}
/* 指定 ohci_s3c2410_init 为该模块的入口函数 */
module_init(ohci_s3c2410_init);


/* 定义平台驱动结构体 (struct platform_driver) */
static struct platform_driver ohci_hcd_s3c2410_driver = {
        /* 当平台总线匹配设备成功后调用的探测函数 (完成硬件初始化、申请中断、创建 Root Hub) */
        .probe          = ohci_hcd_s3c2410_probe,
        
        /* 当设备被卸载或卸载驱动时调用的清理函数 */
        .remove         = ohci_hcd_s3c2410_remove,
        
        /* 系统关机时的关机回调函数 */
        .shutdown       = usb_hcd_platform_shutdown,
        
        /* 内核标准设备驱动信息块 */
        .driver         = {
                /* 关键匹配标志:名字必须与 platform_device 的 .name (即 "s3c2410-ohci") 完全一致 */
                .name   = "s3c2410-ohci",
                
                /* 电源管理回调函数 (休眠/唤醒) */
                .pm     = &ohci_hcd_s3c2410_pm_ops,
                
                /* 设备树 (Device Tree) 匹配表,用于兼容现代内核 DTS 模式 */
                .of_match_table = ohci_hcd_s3c2410_dt_ids,
        },
};

这段代码是 S3C2410/S3C2440 平台 USB OHCI 主控驱动(Host Controller Driver, HCD) 的模块初始化与平台驱动注册入口。它与你之前提到的板级文件 mach-smdk2440.c(注册 platform_device)构成了完整的 总线-设备-驱动 闭环。

核心机制与全流程串联:

这段代码在整个 Linux USB 驱动架构中起到了连接 Linux 通用 OHCI 框架与三星特有硬件的承上启下作用:

复制代码
+-----------------------------------------------------------------------+
|                    板级代码 (mach-smdk2440.c / devs.c)                |
|                    注册 platform_device (.name = "s3c2410-ohci")      |
+-----------------------------------------------------------------------+
                                   |
                                   | (挂载到 platform_bus)
                                   v
+-----------------------------------------------------------------------+
|                   平台总线 (Platform Bus) 匹配机制                      |
|            比较 device.name与 driver.driver.name ("s3c2410-ohci")      |
+-----------------------------------------------------------------------+
                                   |
                                   | 匹配成功,触发调用
                                   v
+-----------------------------------------------------------------------+
|                  驱动代码 (ohci-s3c2410.c)                             |
|  1. ohci_s3c2410_init() 覆写芯片特定 hook (hub_status_data / control) |
|  2. 执行 .probe() -> ohci_hcd_s3c2410_probe()                          |
|  3. 调用 usb_add_hcd() 注册根集线器 (Root Hub)                        |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
|                   USB Core / usb_hub_wq 框架                          |
|     唤醒 hub_thread,开始检测 USB 端口物理插拔事件 (hub_event)         |
+-----------------------------------------------------------------------+

关键点解析:

  1. 硬件缺陷兼容(Quirks Handling)
    标准的 OHCI 规范定义了一套通用的 Root Hub 寄存器,但三星 S3C2410/2440 的 USB 控制器在处理端口电源使能、过流检测以及状态查询时与通用规范有细微差异。因此驱动在 (1) 处先用 ohci_init_driver 填入标准的 OHCI 函数,随后立即将 .hub_status_data.hub_control 替换为三星特有的实现。
  2. 设备树无缝过渡
    ohci_hcd_s3c2410_driver 中,既保留了用于传统板级代码匹配的 .name = "s3c2410-ohci",也加入了 .of_match_table。这使得同一个驱动既能兼容旧版的板级 C 代码时代,也能直接在新的设备树(DTS)内核中工作。

3.5 USB驱动 系统级联初始化

核心主线是:"平台总线(Platform Bus)创建控制通道 → \rightarrow → 驱动控制器创建 Root Hub → \rightarrow → USB 总线(USB Bus)创建设备与接口通道"

整体级联初始化架构图

整个过程由 Platform 总线 触发,中途交接给 USB 总线,形成双层总线模型的联动:

复制代码
========================= 【第一阶段:Platform 总线层】 =========================
1. 板级代码/设备树注册 platform_device ("s3c2410-ohci")
2. 主控驱动注册 platform_driver ("s3c2410-ohci")
3. platform_bus 匹配成功 (strcmp 名字相同)
4. 触发调用 ohci_hcd_s3c2410_probe()
                               |
                               v
========================= 【第二阶段:USB 控制器与 Root Hub 创建】 =========================
5. usb_add_hcd() 申请 Root Hub 设备结构体 (struct usb_device)
6. register_root_hub() -> usb_new_device() -> device_add()
7. Root Hub 作为 "USB 设备" 被挂载到 usb_bus_type (USB 总线) 上
                               |
                               v
========================= 【第三阶段:USB 总线层 - 设备级匹配】 =========================
8. 触发 usb_bus_type 的 device_attach() 遍历 USB 驱动链表
9. 调用 usb_device_match():
   - 检测到当前是 Root Hub (is_usb_device == true)
   - 筛选出专管设备的 usb_device_driver (for_devices == 1)
10. 成功匹配到内核通用的设备驱动:usb_generic_driver
11. 执行 usb_generic_driver_probe():
    - 读取配置描述符,选择 Configuration
    - 调用 usb_set_configuration() 创建并注册 "USB 接口设备" (usb_interface)
                               |
                               v
========================= 【第四阶段:USB 总线层 - 接口级匹配】 =========================
12. 接口设备 (usb_interface) 再次执行 device_add(),重新触发 usb_bus_type 遍历
13. 调用 usb_device_match():
    - 检测到当前是接口设备 (is_usb_device == false)
    - 过滤掉设备驱动,只在 usb_driver (接口驱动) 中匹配 id_table
14. 成功匹配到系统中的 Hub 接口驱动:hub_driver (.id_table 匹配 USB_CLASS_HUB)
15. 执行 hub_probe():
    - 申请并提交 status URQ (中断传输/轮询)
    - 绑定中断回调函数 hub_irq
    - 创建/唤醒 hub_wq (内核工作队列) 监听端口插拔事件

这三个阶段的递进关系和回调链条(__driver_attach__device_attach 的对偶调用)全部串联闭环了。这种分层与解耦的思想,正是 Linux 内核设备驱动模型最精妙的地方。

4. USB鼠标驱动源码分析

4.1 usb_mouse_probe

先介绍 usb_mouse input_dev

4.1.1 usb_mouse

c 复制代码
struct usb_mouse {
        char name[128];             /* 设备的完整产品名称字符串(如 "Logitech USB Optical Mouse"),用于注册给 Input 子系统 */
        char phys[64];              /* 设备在系统中的物理拓扑路径字符串(如 "usb-s3c2410-1.2/input0"),用于区分多设备 */
        
        struct usb_device *usbdev;  /* 指向该鼠标所挂载的通用 USB 设备结构体 (struct usb_device),方便在驱动中获取设备信息 */
        struct input_dev *dev;      /* 指向 Linux Input 子系统的输入设备结构体,内核用它向用户态 (/dev/input/eventX) 上报鼠标事件 */
        struct urb *irq;            /* 专门用于接收鼠标按键和移动坐标数据的"中断 URB" (Interrupt URB) 指针 */

        signed char *data;          /* 存放从鼠标接收到的原始硬件数据包(Raw Data)的虚拟内存地址缓冲区(通常为 4 字节,含按键状态、X/Y 偏移值) */
        dma_addr_t data_dma;        /* 上述 data 缓冲区对应的 DMA 总线物理地址,供 USB 主控芯片通过 DMA 直接传输数据,避免 CPU 搬运 */
};

这是内核官方经典示例驱动 drivers/usb/input/usbmouse.c 中定义的USB 鼠标私有结构体 (Private Data Structure)。它是一个典型的"桥梁"结构体,将 USB 子系统Linux Input(输入)子系统 紧密连接在一起。

关键设计点拆解

  • 为何使用 dma_addr_t data_dma

    USB 鼠标使用中断传输(Interrupt Transfer)来上报数据。由于鼠标传输频率高(如每秒轮询 125Hz~1000Hz),为了降低 CPU 占用率,驱动在 usb_alloc_coherent() 申请内存时,会同时返回 CPU 访问的虚拟地址 data 和主控硬件访问的 DMA 物理地址 data_dma。USB 主控收到硬件数据后,直接通过 DMA 搬运到 data_dma,搬完后才发中断通知内核。

  • 数据缓冲区 signed char *data 为何是有符号类型?

    鼠标上报的相对位移(Rel Position)是有方向的:

    • X 轴:正数代表向右移动,负数代表向左移动。
    • Y 轴 :正数代表向下移动,负数代表向上移动。
      因此使用 signed char(有符号 8 位整数,范围 -128 到 127)可以天然表示这个相对位移量。
  • 两个子系统的连接纽带

    在驱动的 .probe 函数被触发时,内核会分配并初始化这个 struct usb_mouse

    1. 通过 USB 子系统 拿到 usbdev 并申请 irq URB。
    2. 通过 Input 子系统 申请 input_dev,配置鼠标支持的事件类型(如 EV_KEY 按键事件、EV_REL 相对坐标事件)。
    3. usb_mouse 结构体挂载到 input_devdev->dev.platform_data 或通过 usb_set_intfdata() 挂载到 USB 接口私有数据中,确保任何回调函数(如 URB 完成回调 usb_mouse_irq)都能随时取回完整的上下文数据

4.1.2 input_dev

c 复制代码
struct input_dev {
        /* ================= 1. 设备基本身份与属性标志 ================= */
        const char *name;         /* 设备名称(如 "Logitech USB Optical Mouse"),出现在 /proc/bus/input/devices 中 */
        const char *phys;         /* 系统拓扑物理路径(如 "usb-s3c2410-1/input0") */
        const char *uniq;         /* 设备唯一识别码(如 MAC 地址或序列号) */
        struct input_id id;       /* 硬件 ID 结构体,包含总线类型 (bus)、厂商 ID (vendor)、产品 ID (product)、版本号 (version) */

        unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; /* 设备属性位图(如 INPUT_PROP_DIRECT 标识直接触摸屏/指针) */

        /* ================= 2. 能力位图 (Capabilities Bitmaps) - 关键 ================= */
        /* 位图机制用于告知内核:该设备"能够支持哪些事件类型"以及"支持哪些具体的按键/坐标轴" */
        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];   /* 支持的事件类型位图(如 EV_KEY按键, EV_REL相对坐标, EV_ABS绝对坐标) */
        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 支持的按键/键值位图(如 KEY_A, BTN_LEFT, BTN_RIGHT) */
        unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 支持的相对坐标位图(如 REL_X, REL_Y, REL_WHEEL 滚轮) */
        unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 支持的绝对坐标位图(如 ABS_X, ABS_Y 触摸屏坐标) */
        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 支持的杂项事件位图(如 MSC_SCAN 扫描码) */
        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /* 支持的指示灯位图(如 LED_NUML, LED_CAPSL 键盘灯) */
        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* 支持的声音事件位图(如 SND_BELL, SND_TONE 蜂鸣器) */
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   /* 支持的力反馈事件位图(如游戏手柄震动) */
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   /* 支持的开关事件位图(如 SW_LID 滑盖/盖子开关) */

        unsigned int hint_events_per_packet; /* 性能优化提示:单次事件数据包预计产生的事件平均数量 */

        /* ================= 3. 键码映射 (Keymap Management) ================= */
        unsigned int keycodemax;  /* 键码表最大容量 */
        unsigned int keycodesize; /* 键码表中单项的大小(字节) */
        void *keycode;            /* 扫描码 (Scan Code) 到 Linux 键码 (Key Code) 的映射表指针 */

        /* 函数指针:修改和获取映射表的钩子 */
        int (*setkeycode)(struct input_dev *dev,
                          const struct input_keymap_entry *ke,
                          unsigned int *old_keycode);
        int (*getkeycode)(struct input_dev *dev,
                          struct input_keymap_entry *ke);

        /* ================= 4. 特殊硬件扩展功能 ================= */
        struct ff_device *ff;     /* 指向力反馈 (Force Feedback) 设备结构体 */

        /* 键盘连按自动重复 (Auto-repeat / Key Repeat) 机制 */
        unsigned int repeat_key;   /* 当前正在按下的按键键码 */
        struct timer_list timer;   /* 负责触发按键长按连发的内核定时器 */
        int rep[REP_CNT];          /* 连发参数数组:rep[REP_DELAY] 首次延迟,rep[REP_PERIOD] 连发间隔 */

        struct input_mt *mt;       /* 指向多点触摸 (Multi-Touch) 协议上下文结构体 */

        struct input_absinfo *absinfo; /* 绝对坐标轴参数数组(包含 ABS_X/Y 的最小值、最大值、消抖范围等) */

        /* ================= 5. 设备当前状态缓存 (Current State) ================= */
        /* 记录设备"当前时刻"的状态,防止上报重复无用数据 */
        unsigned long key[BITS_TO_LONGS(KEY_CNT)]; /* 记录当前有哪些按键正处于按下状态 */
        unsigned long led[BITS_TO_LONGS(LED_CNT)]; /* 记录当前有哪些 LED 指示灯点亮 */
        unsigned long snd[BITS_TO_LONGS(SND_CNT)]; /* 记录当前发声状态 */
        unsigned long sw[BITS_TO_LONGS(SW_CNT)];   /* 记录当前开关状态(开/关) */

        /* ================= 6. 控制回调函数 (Control Callbacks) ================= */
        int (*open)(struct input_dev *dev);   /* 当用户态程序第一次 open("/dev/input/eventX") 时被调用(可在此使能硬件中断) */
        void (*close)(struct input_dev *dev); /* 当所有应用关闭该设备节点时调用(可在此禁用硬件以节省功耗) */
        int (*flush)(struct input_dev *dev, struct file *file); /* 刷盘/刷新缓冲区回调 */
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); /* 核心下发回调:当内核要向设备下发命令(如亮键盘灯、触发震动)时调用 */

        /* ================= 7. Input 子系统匹配与并发控制 ================= */
        struct input_handle __rcu *grab; /* 独占句柄(当某个进程独占该输入设备时指向对应的 handle) */

        spinlock_t event_lock;           /* 保护上报事件队列与内部状态的自旋锁 */
        struct mutex mutex;              /* 保护 open/close 与设备配置变化的互斥锁 */

        unsigned int users;              /* 当前打开该设备的应用程序数量 (引用计数) */
        bool going_away;                 /* 设备正在注销/注销标志 */

        struct device dev;               /* 嵌入 Linux 标准设备模型结构体,在 /sys/class/input/ 下创建设备节点 */

        struct list_head h_list;         /* 链表头:挂载与该 input_dev 绑定的所有 input_handle(如 evdev, mousedev) */
        struct list_head node;           /* 链表节点:用于将本 input_dev 挂载到全系统的 input_dev_list 主链表上 */

        /* 批量事件上报优化相关缓冲区 */
        unsigned int num_vals;
        unsigned int max_vals;
        struct input_value *vals;

        bool devres_managed;             /* 标识该结构体是否由 devm_input_allocate_device 托管释放内存 */

        ktime_t timestamp[INPUT_CLK_MAX]; /* 时间戳缓存,支持单调时钟、实时时钟等不同基准 */
};

位图机制:

input_dev 采用了非常高效的 unsigned long array 作为位图(Bitmap)

  • 为什么需要?
    Linux 输入设备千奇百怪。内核必须通过这些位图明确设备能做什么。

  • 驱动开发范例(以鼠标为例)

    c 复制代码
    /* 1. 声明支持的事件类型 */
    set_bit(EV_KEY, dev->evbit); /* 支持按键事件 */
    set_bit(EV_REL, dev->evbit); /* 支持相对坐标事件 */
    
    /* 2. 声明具体的按键和坐标 */
    set_bit(BTN_LEFT, dev->keybit);  /* 支持左键 */
    set_bit(BTN_RIGHT, dev->keybit); /* 支持右键 */
    set_bit(REL_X, dev->relbit);     /* 支持 X 轴移动 */
    set_bit(REL_Y, dev->relbit);     /* 支持 Y 轴移动 */

4.1.3 代码解释

c 复制代码
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
        struct usb_device *dev = interface_to_usbdev(intf); // 通过接口结构体获取对应的 USB 设备指针 usb_device
        struct usb_host_interface *interface;
        struct usb_endpoint_descriptor *endpoint;           // 端点描述符指针
        struct usb_mouse *mouse;                             // 本驱动自定义的私有数据结构体指针
        struct input_dev *input_dev;                         // 内核 Input 子系统的设备结构体指针
        int pipe, maxp;
        int error = -ENOMEM;

        interface = intf->cur_altsetting;                    // 获取当前接口的活跃设置 (Altsetting)

        /* ------------------ 1. 硬件合规性校验 ------------------ */
        if (interface->desc.bNumEndpoints != 1)              // 启动校验:Boot Protocol 标准 USB 鼠标的端点数量必须有且仅有 1 个
                return -ENODEV;

        endpoint = &interface->endpoint[0].desc;             // 获取这唯一的端点描述符
        if (!usb_endpoint_is_int_in(endpoint))               // 校验端点传输类型:必须是"中断输入"端点 (Interrupt IN)
                return -ENODEV;

        /* ------------------ 2. USB 端点与管道配置 ------------------ */
        pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); // 创建中断输入管道 (Pipe),结合了设备号和端点地址
        maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));     // 获取端点支持的最大包长 (Max Packet Size)

        /* ------------------ 3. 驱动内存与资源申请 ------------------ */
        mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); // 申请本驱动私有结构体 usb_mouse
        input_dev = input_allocate_device();                   // 向内核 Input 核心申请分配 input_dev 结构体
        if (!mouse || !input_dev)
                goto fail1;

        /* 分配一致性 DMA 缓冲区 (8字节),避免 Cache 一致性问题,DMA 直接将数据写入 mouse->data */
        mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
        if (!mouse->data)
                goto fail1;

        mouse->irq = usb_alloc_urb(0, GFP_KERNEL);             // 为中断传输申请一个 URB (USB Request Block)
        if (!mouse->irq)
                goto fail2;

        /* ------------------ 4. 交叉指针绑定与设备信息构建 ------------------ */
        mouse->usbdev = dev;    // mouse 私有结构体记录对应的 usb_device 指针
        mouse->dev = input_dev; // mouse 私有结构体记录对应的 input_dev 指针

        /* 拼接设备名称字符串(厂商名 + 产品名),存入 mouse->name */
        if (dev->manufacturer)
                strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

        if (dev->product) {
                if (dev->manufacturer)
                        strlcat(mouse->name, " ", sizeof(mouse->name));
                strlcat(mouse->name, dev->product, sizeof(mouse->name));
        }

        if (!strlen(mouse->name))
                snprintf(mouse->name, sizeof(mouse->name),
                         "USB HIDBP Mouse %04x:%04x",
                         le16_to_cpu(dev->descriptor.idVendor),
                         le16_to_cpu(dev->descriptor.idProduct));

        /* 构建物理拓扑路径字符串 (如 "usb-0000:00:14.0-1/input0"),存入 mouse->phys */
        usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
        strlcat(mouse->phys, "/input0", sizeof(mouse->phys));

        /* 将构建好的字符串和属性同步给 input_dev 结构体 */
        input_dev->name = mouse->name;         // 设置 input 设备的名称
        input_dev->phys = mouse->phys;         // 设置 input 设备的物理路径
        usb_to_input_id(dev, &input_dev->id);  // 提取 USB 设备的 Vendor/Product/Version ID 填入 input_dev
        input_dev->dev.parent = &intf->dev;    // 设置 Linux 设备模型中的父设备节点为当前 USB 接口

        /* ------------------ 5. 配置 input_dev 的能力位图 (Capabilities) ------------------ */
        // evbit:表明该设备能够产生"按键事件 (EV_KEY)"和"相对位移事件 (EV_REL)"
        input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

        // keybit:声明支持的按键类型(鼠标左键、右键、中键)
        input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
                BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);

        // relbit:声明支持的相对坐标轴(X 轴、Y 轴)
        input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);

        // 进一步扩展 keybit:追加支持侧键 (BTN_SIDE) 和额外扩展键 (BTN_EXTRA)
        input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
                BIT_MASK(BTN_EXTRA);

        // 进一步扩展 relbit:追加支持滚轮 (REL_WHEEL)
        input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);

        /* 将 mouse 设置为 input_dev 的私有驱动数据 (存入 input_dev->dev.driver_data) */
        input_set_drvdata(input_dev, mouse);

        /* 绑定 input 设备的打开和关闭回调函数(控制硬件供电/URB 的提交与取消) */
        input_dev->open = usb_mouse_open;
        input_dev->close = usb_mouse_close;

        /* ------------------ 6. 初始化 URB 与 DMA 关联 ------------------ */
        /* 填充中断 URB:指定接收端点、缓冲区地址、回调函数 (usb_mouse_irq)、上下文参数 (mouse) 及轮询间隔 */
        usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
                         (maxp > 8 ? 8 : maxp),
                         usb_mouse_irq, mouse, endpoint->bInterval);

        mouse->irq->transfer_dma = mouse->data_dma;               // 关联之前申请的 DMA 物理地址
        mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;  // 标记 URB 使用现成的 DMA 映射,无需 USB 核心重复映射

        /* ------------------ 7. 注册到内核框架 ------------------ */
        error = input_register_device(mouse->dev);               // 向内核注册 input 设备,此时系统会在 /dev/input/ 创建 eventX 节点
        if (error)
                goto fail3;

        usb_set_intfdata(intf, mouse);                           // 将 mouse 设置为 USB 接口 (usb_interface) 的私有数据
        return 0;                                                // 探测成功,返回 0

/* ------------------ 错误处理与清理逻辑 ------------------ */
fail3:
        usb_free_urb(mouse->irq);
fail2:
        usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
        input_free_device(input_dev);
        kfree(mouse);
        return error;
}

这段代码是典型的 Linux USB 鼠标驱动的探测函数 (probe) ,运行于 USB 接口与该驱动匹配成功时。它的核心使命是:完成硬件校验、内存资源申请、底层 USB URB 配置,并将 USB 硬件数据转译连接到内核 Input 输入子系统中。

usb_mouseinput_dev 的关系解析

在 Linux 驱动开发中,usb_mouseinput_dev 代表了架构分层思想 中的两个核心维度:硬件物理层逻辑事件层 。它们通过交叉绑定实现了数据和控制流的解耦。

1. 职责分工

结构体 扮演角色 核心职责
struct usb_mouse 硬件/私有层 代表真实的 USB 物理设备。负责管理 USB URB 传输、DMA 内存缓冲区、USB 描述符解析以及控制底层 USB 中断通信。
struct input_dev 系统/逻辑层 代表抽象的 Linux 输入设备 。向内核及用户态注册 /dev/input/eventX,声明设备具备的能力(按键、坐标轴),并提供标准事件上报接口。

2. 指针绑定关系图解

代码通过双向指针建立了相互调用的通道:

复制代码
 +----------------------------------+                      +----------------------------------+
 |         struct usb_mouse         |                      |         struct input_dev         |
 |                                  |      mouse->dev      |                                  |
 |  (底层 USB 通信私有结构体)          | -------------------> |    (Linux Input 子系统抽象设备)    |
 |                                  |                      |                                  |
 |  - struct usb_device *usbdev     | input_get_drvdata()  |  - evbit / keybit / relbit       |
 |  - struct urb *irq               | <------------------- |  - dev.driver_data               |
 |  - signed char *data (DMA 缓存)  |  (input_set_drvdata) |  - int (*open) / int (*close)    |
 +----------------------------------+                      +----------------------------------+

3. 关键的"双向协作"机制

① 正向数据上报:usb_mouse → \rightarrow → input_dev

当硬件产生位移或点击时,USB 硬件响应中断:

  1. USB 控制器收到数据包,触发 URB 回调函数 usb_mouse_irq(struct urb *urb)

  2. 回调函数的参数 urb->context 取出的就是 mouse 指针。

  3. 驱动解析 mouse->data 中的原始 DMA 数据包。

  4. 驱动通过 mouse->dev 找到绑定的 input_dev 指针,调用内核标准接口进行上报:

    c 复制代码
    input_report_key(mouse->dev, BTN_LEFT,   data[0] & 0x01); // 上报左键按下/抬起
    input_report_rel(mouse->dev, REL_X,      data[1]);        // 上报 X 轴偏移量
    input_sync(mouse->dev);                                   // 发送同步信号 (EV_SYN)

② 反向控制控制:input_dev → \rightarrow → usb_mouse

当用户态程序(如 X11 或嵌入式 GUI)打开或关闭设备节点(如 /dev/input/event0)时:

  1. Input Core 触发 input_dev->open(即 usb_mouse_open)或 close 回调。
  2. 回调函数传入的是 struct input_dev *dev 指针。
  3. 通过 input_get_drvdata(dev) 反向检索出关联的 struct usb_mouse *mouse 指针。
  4. 拿到 mouse 后,执行底层的 USB 硬件操作:
    • open :调用 usb_submit_urb(mouse->irq, GFP_KERNEL) 开启中断 URB,开始轮询 USB 硬件。
    • close :调用 usb_kill_urb(mouse->irq) 停止 URB,让 USB 设备进入休眠以节约功耗。

4.2 usb_mouse_irq

c 复制代码
static void usb_mouse_irq(struct urb *urb)
{
        /* 1. 上下文与变量提取 */
        /* 获取填充 URB 时传入的 context 参数(即 probe 中绑定的 struct usb_mouse 指针) */
        struct usb_mouse *mouse = urb->context;
        
        /* 指向 DMA 接收缓冲区,存放的是 USB 鼠标硬件返回的原始 HID 字节数据 */
        signed char *data = mouse->data;
        
        /* 获取绑定的 Linux Input 设备指针,用于向子系统上报事件 */
        struct input_dev *dev = mouse->dev;
        int status;

        /* 2. URB 传输状态检查 (Status Checking) */
        switch (urb->status) {
        case 0:                 /* 0 表示传输成功,接收到了鼠标发来的有效数据 */
                break;
        case -ECONNRESET:       /* 异步解除链接 (usb_unlink_urb) */
        case -ENOENT:           /* URB 被同步强行取消 (usb_kill_urb) */
        case -ESHUTDOWN:        /* USB 主控制器或 Hub 被拔出/禁用/关闭 */
                /* 以上三种情况属于设备正在被卸载或停用,直接返回,绝不能重新提交 URB */
                return;
        /* -EPIPE: 端点被挂起 (halted),常规驱动开发中通常需要调用 usb_clear_halt 清除 */
        default:                /* 发生了其他传输错误(如 CRC 校验错误、Noise 干扰等) */
                goto resubmit;  /* 忽略本次异常数据,直接跳过解析,尝试重新提交 URB */
        }

        /* 3. 解析 HID 数据并向 Input 子系统上报 (Data Parsing & Reporting) */
        
        /* ------------------ 按键状态解析 (data[0]) ------------------ */
        /* data[0] 的每一位 (Bit) 对应一个按键状态:1 为按下,0 为抬起 */
        input_report_key(dev, BTN_LEFT,   data[0] & 0x01); // Bit 0: 鼠标左键
        input_report_key(dev, BTN_RIGHT,  data[0] & 0x02); // Bit 1: 鼠标右键
        input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); // Bit 2: 鼠标中键(滚轮按下)
        input_report_key(dev, BTN_SIDE,   data[0] & 0x08); // Bit 3: 侧键(如前进键)
        input_report_key(dev, BTN_EXTRA,  data[0] & 0x10); // Bit 4: 扩展键(如后退键)

        /* ------------------ 相对坐标偏移量解析 (data[1]~data[3]) ------------------ */
        /* 注意:data 是 signed char 类型,代表补码表示的有符号相对位移量 */
        input_report_rel(dev, REL_X,     data[1]); // data[1]: X 轴物理位移(正数向右,负数向左)
        input_report_rel(dev, REL_Y,     data[2]); // data[2]: Y 轴物理位移(正数向下,负数向上)
        input_report_rel(dev, REL_WHEEL, data[3]); // data[3]: 滚轮滚动值(正数向前,负数向后)

        /* ------------------ 事件同步 ------------------ */
        /* 上报 EV_SYN 标识(SYN_REPORT),通知 Input Core 将上述所有改变打包一次性推送到用户态 */
        input_sync(dev);

/* 4. 重新提交 URB (Resubmit URB) */
resubmit:
        /* 
         * USB 中断传输本质上仍是主控器发起的轮询(One-shot)。
         * 每次 URB 完成后必须手动重新提交,驱动才能在下一个轮询周期(bInterval)继续收到数据。
         * 注意:此回调处于中断上下文,内存分配标志必须使用 GFP_ATOMIC!
         */
        status = usb_submit_urb(urb, GFP_ATOMIC);
        if (status)
                dev_err(&mouse->usbdev->dev,
                        "can't resubmit intr, %s-%s/input0, status %d\n",
                        mouse->usbdev->bus->bus_name,
                        mouse->usbdev->devpath, status);
}

这段代码是 USB 鼠标驱动的核心------URB 中断传输完成回调函数(usb_mouse_irq

当 USB 主控芯片通过中断端点成功轮询到鼠标的数据(或者发生传输错误)时,系统底层会在 中断上下文 中自动调用该函数。它的使命是:解析物理数据包、上报 Input 事件,并重新提交 URB 以维持连续的硬件轮询

核心机制深度说明:

1. 硬件数据包协议

标准的 USB HID Boot 协议鼠标返回的数据长度通常为 4 字节,mouse->data 数组各字节含义如下:

字节偏移 数据类型 对应变量 物理含义与位定义
data[0] unsigned char 按键掩码 Bit 0: 左键
data[1] signed char X 轴相对坐标 -128 ~ +127,负数代表向左移动,正数代表向右移动
data[2] signed char Y 轴相对坐标 -128 ~ +127,负数代表向上移动,正数代表向下移动
data[3] signed char 滚轮相对位移 -128 ~ +127,负数代表向下/后滚,正数代表向上/前滚

2. 中断传输的"乒乓轮询"机制

USB 规范中不存在真正的"硬件主动中断"。所谓的"USB 中断传输",是 USB Host 控制器按照 endpoint->bInterval 指定的时间间隔,周期性地向鼠标发包询问是否有数据。

  • 当鼠标没有移动时,硬件不会返回数据完成包;

  • 一旦鼠标发生位移或按键点击,Host 控制器读取到数据,触发 usb_mouse_irq

  • 函数末尾的 usb_submit_urb(urb, GFP_ATOMIC) 是维持这个周期的关键。如果没有重新提交,USB 轮询就会终止,鼠标也会"冻结"不再响应。

    c 复制代码
     switch (urb->status) {
            case 0:                 /* 0 表示传输成功,接收到了鼠标发来的有效数据 */
                    break;
            case -ECONNRESET:       /* 异步解除链接 (usb_unlink_urb) */
            case -ENOENT:           /* URB 被同步强行取消 (usb_kill_urb) */
            case -ESHUTDOWN:        /* USB 主控制器或 Hub 被拔出/禁用/关闭 */
                    /* 以上三种情况属于设备正在被卸载或停用,直接返回,绝不能重新提交 URB */
                    return;
            /* -EPIPE: 端点被挂起 (halted),常规驱动开发中通常需要调用 usb_clear_halt 清除 */
            default:                /* 发生了其他传输错误(如 CRC 校验错误、Noise 干扰等) */
                    goto resubmit;  /* 忽略本次异常数据,直接跳过解析,尝试重新提交 URB */
            }
    URB 传输状态 执行路线 结果与目的
    成功 (case 0) case 0 → \rightarrow → break 跳出 switch → \rightarrow → 顺序执行数据解析与 input_sync → \rightarrow → 顺路走到 resubmit: → \rightarrow → 执行 usb_submit_urb 数据有效,先上报给应用层,再重新提交 URB 以等待下一次数据。
    硬件临时错误 (default) default → \rightarrow → goto resubmit; 跨跃 → \rightarrow → 跳过中间的数据解析 → \rightarrow → 降落到 resubmit: → \rightarrow → 执行 usb_submit_urb 数据损坏不可用(丢弃),防止误报导致光标乱飞;但设备还在,直接重新提交 URB 给下一次尝试的机会。
    设备拔出/停用 (-ESHUTDOWN等) 匹配 case → \rightarrow → return 彻底退出函数 设备已断开,必须强制停止 URB 的循环提交,防止死循环和内存崩溃。
相关推荐
呼噜想睡觉1 小时前
Bootstrap3完成从导航到页脚完整页面开发
学习
JacksonMx1 小时前
Java CompletableFuture 异步编程实战:从入门到架构师避坑指南
linux·数据库·python
半生过往2 小时前
前端学 Java 课程笔记
java·前端·笔记
九硕智慧建筑一体化厂家2 小时前
楼宇自控|智慧建筑核心引擎!楼宇自控系统,赋能建筑高效低碳运维
运维·人工智能·笔记·智慧城市
byte轻骑兵2 小时前
【BlueZ 】蓝牙 SDAP 协议入门:BlueZ 中设备服务发现的基础逻辑
linux·人工智能·bluez·电脑蓝牙·嵌入式蓝牙
mounter6252 小时前
GCCRS 突破与 Rust 入核:Linux 工具链的重大变革
linux·开发语言·rust·linux kernel·kernel
GGMM7893 小时前
施耐德 VX5A1HC90110(VX5A1HD9011)ATV61/ATV71 90‑110KW 电源驱动板详解
笔记·变频器·变频器维修
Wang's Blog3 小时前
PostgreSQL笔记48: 全文检索与向量检索的对比与混合检索实践
笔记·postgresql·全文检索
liuyicenysabel4 小时前
Flask 个人站 iProfile 上线笔记(k3s + Nginx + Let‘s Encrypt)
笔记·nginx·flask