4. 异步列表调度原理:Async List 链表结构与 QH/qTD 关系
EHCI 主机控制器通过两种调度列表管理 USB 传输:周期性列表(Periodic List)用于等时与中断传输,异步列表(Async List)用于批量传输与控制传输。本章聚焦异步列表的链表结构、队列头(QH)与队列传输描述符(qTD)的层次关系,以及 EHCI 硬件如何遍历该列表完成批量传输调度。
4.1 异步列表的链表结构
异步列表是一个循环双向链表,由多个队列头(QH)通过指针串联而成。EHCI 主控制器通过异步列表寄存器(AsyncListAddr)获取链表首地址,并在每个微帧(Microframe)的空闲时间遍历该列表,执行批量传输。
| 链表属性 | 说明 |
|---|---|
| 结构类型 | 循环双向链表(Circular Doubly Linked List) |
| 基本节点 | 队列头(QH) |
| 遍历方向 | 从 AsyncListAddr 指向的 QH 开始,沿 Horizontal Link Pointer 前进 |
| 终止条件 | 当 Horizontal Link Pointer 的 T 位为 1 时停止;循环链表通常将最后一个 QH 指回首节点 |
| 调度时机 | 每个微帧中,当周期性列表遍历完成后,EHCI 执行异步列表遍历 |
关键概念:异步列表的循环特性确保所有 QH 在调度上具有公平性。EHCI 硬件不会在单次遍历中处理所有 QH,而是根据带宽预算和 qTD 状态决定每次处理的深度。
4.2 队列头(QH)内部结构
每个 QH 是 32 字节对齐的数据结构,包含三个关键区域:水平链接指针(Horizontal Link Pointer)、端点特征域(Endpoint Capabilities/Characteristics)以及当前 qTD 指针(Current qTD Pointer)。
| QH 字段 | 偏移(字节) | 作用 |
|---|---|---|
| Horizontal Link Pointer | 0x00 | 指向下一个 QH 或终止符;T 位控制是否终止 |
| Endpoint Capabilities | 0x04 | 包含设备地址、端点号、最大包长、方向、速率等 |
| Current qTD Pointer | 0x08 | 指向当前正在处理的 qTD;硬件执行时更新此指针 |
| Overlay qTD(16 字节) | 0x10 | 缓存当前 qTD 的关键执行状态,用于快速上下文切换 |
实现提示:Overlay qTD 是 EHCI 硬件加速的关键设计。当 QH 被调度时,硬件将 qTD 的前 16 字节加载到 Overlay 区域,避免每次访问系统内存读取完整 qTD,显著降低延迟。
4.3 qTD 结构与传输描述
队列传输描述符(qTD)是描述一次批量传输的数据结构,每个 qTD 对应一个或多个物理缓冲区。qTD 通过Next qTD Pointer 串联成单向链表,挂接在 QH 之下。
| qTD 字段 | 偏移(字节) | 说明 |
|---|---|---|
| Next qTD Pointer | 0x00 | 指向下一个 qTD;T 位为 1 表示链表结束 |
| Alternate Next qTD Pointer | 0x04 | 用于错误重试或备用传输路径(通常置为终止) |
| Token | 0x08 | 包含 PID(IN/OUT/SETUP)、数据偏移、总字节数、中断标志等 |
| Buffer Page Pointer 0–4 | 0x0C–0x1C | 最多 5 个 4KB 页面的物理地址指针,支持跨页传输 |
4.4 QH 与 qTD 的层次关系
QH 与 qTD 构成两级链表:水平方向是 QH 之间的循环链表,垂直方向是每个 QH 下挂接的 qTD 单向链表。EHCI 硬件遍历时,先沿水平方向找到 QH,再沿垂直方向处理该 QH 下的 qTD 链。
4.5 硬件调度流程
EHCI 硬件在异步列表上的调度遵循以下步骤:
- 获取首 QH:从 AsyncListAddr 寄存器读取首 QH 物理地址。
- 检查 QH 状态:读取 QH 的 Current qTD Pointer,若为 NULL 或 qTD 已完成,则跳过该 QH。
- 加载 Overlay:将当前 qTD 的前 16 字节加载到 QH 的 Overlay 区域。
- 执行传输:根据 Token 字段的 PID 和缓冲区地址,启动 USB 事务。
- 更新状态:传输完成后,硬件更新 qTD 的 Token 状态(包括 XactErr、Babble、Halted 等标志)。
- 推进 qTD:若当前 qTD 完成且无错误,硬件将 Current qTD Pointer 更新为 Next qTD Pointer。
- 水平推进:沿 Horizontal Link Pointer 移动到下一个 QH,重复步骤 2–6。
常见陷阱:如果 QH 的 Halted 位被置位,硬件会跳过该 QH 及其所有挂接的 qTD,直到软件主动清除 Halted 位。这通常由传输错误或 STALL 握手触发,软件必须在错误恢复后重新激活 QH。
4.6 QH 与 qTD 的软件管理要点
| 管理操作 | 软件职责 | 硬件行为 |
|---|---|---|
| QH 插入 | 分配 32 字节对齐内存,初始化端点特征,设置 Horizontal Link Pointer | 遍历时自动识别新 QH |
| qTD 挂接 | 分配 qTD,设置 Token 和缓冲区指针,更新 QH 的 Current qTD Pointer | 加载 Overlay 并执行传输 |
| QH 移除 | 修改前驱 QH 的 Horizontal Link Pointer,绕过待移除 QH | 遍历时跳过已移除节点 |
| 错误恢复 | 读取 qTD 状态,分析错误原因,清除 Halted 位,重新提交 qTD | 恢复后继续执行 qTD 链 |
4.7 异步列表调度与批量传输性能
异步列表的调度效率直接影响批量传输的吞吐量。EHCI 在每个微帧中分配给异步列表的时间取决于周期性列表的负载。当周期性列表空闲时,异步列表可获得接近 90% 的 USB 总线带宽。
性能优化建议:
- 将多个 qTD 预链接到 QH 下,减少软件中断频率。
- 使用中断 qTD(Token 中的 IOC 位)仅在关键传输完成时触发中断。
- 避免在 QH 链中插入空 QH(无挂接 qTD),这会浪费遍历时间。
- 对于高速批量端点,建议 qTD 数据长度对齐 512 字节(高速批量最大包长)。