4. UHCI 队列头(QH)与 TD 链表
UHCI 主机控制器通过内存中的链表结构来管理 USB 总线上所有的传输任务。这套链表体系由队列头(QH, Queue Head)和传输描述符(TD, Transfer Descriptor)两种核心数据结构构成。QH 作为调度入口,TD 承载具体的传输参数与数据缓冲区指针。理解 QH 与 TD 的链接方式、水平链表与垂直链表的组织逻辑,是掌握 UHCI 帧调度与 DMA 传输机制的基础。
4.1 QH(Queue Head)结构
QH 是 UHCI 调度链表中的基本调度单元,每个 QH 对应一个 USB 端点(Endpoint)或一个控制传输阶段。QH 在内存中固定为 16 字节对齐,其结构定义如下:
| 偏移(字节) | 位域 | 描述 |
|---|---|---|
| 0-3 | Horizontal Link Pointer (HLP) | 水平链接指针,指向下一个 QH 或 TD,最低位(bit 0)为 QH/TD 选择位(0=TD, 1=QH) |
| 4-7 | Endpoint Number / Device Address / Max Packet Size | bit 0-6: Device Address;bit 7-10: Endpoint Number;bit 11-14: Max Packet Size(以字节为单位,实际值为 (N+1)*8);bit 15: 低速设备标志(Low Speed Device);bit 16-31: 保留 |
| 8-11 | TD Queue Head Pointer (TQHP) | 垂直链接指针,指向该 QH 挂载的第一个 TD(TD 链表的头节点) |
| 12-15 | TD Queue Tail Pointer (TQTP) | 垂直链接尾指针,指向该 QH 挂载的最后一个 TD 的下一个位置(用于硬件回写,软件初始化时指向链表末尾的保留 TD) |
4.2 QH 与 TD 的链接方式
QH 与 TD 通过水平链接和垂直链接两种方式构成二维链表结构。水平链接用于遍历调度队列,垂直链接用于挂载具体传输任务。
- 水平链接(Horizontal Link):QH 的 Horizontal Link Pointer(HLP)指向下一个调度节点(可以是另一个 QH 或一个 TD)。所有 QH 通过 HLP 串联成一个水平链表,UHCI 主机控制器按帧遍历该链表。
- 垂直链接(Vertical Link):QH 的 TD Queue Head Pointer(TQHP)指向该 QH 挂载的第一个 TD。TD 之间通过 Next Link Pointer(每个 TD 的 0-3 字节)串联成垂直链表。QH 的 TD Queue Tail Pointer(TQTP)用于硬件回写,标记链表末尾。
- 水平链表:QH → QH → QH(或 TD),用于调度顺序遍历。
- 垂直链表:QH → TD → TD → TD,用于挂载同一端点的多个传输描述符。
4.3 水平链表与垂直链表
UHCI 的链表体系通过水平链表和垂直链表的二维结构,实现了调度与传输的分离管理。
4.3.1 水平链表(Horizontal List)
水平链表由 QH 通过 HLP 指针串联而成,UHCI 主机控制器在每个帧(Frame)开始时,从帧列表(Frame List)中取出当前帧的指针,然后遍历水平链表。水平链表上的每个 QH 代表一个需要被服务的 USB 端点。水平链表的遍历顺序决定了端点的服务优先级。
- 遍历规则:UHCI 从帧列表指针开始,依次访问每个 QH。如果 QH 的垂直链表非空(即挂载了 TD),则处理该 QH 的第一个 TD;如果垂直链表为空,则跳过该 QH。
- 链表终止:水平链表以 HLP 为 NULL(0x00000001,最低位为 1 表示 QH 类型,但地址为 0)结束。
- 动态插入:软件可以在运行时动态插入或移除 QH,只需修改前一个 QH 的 HLP 指针即可。
4.3.2 垂直链表(Vertical List)
垂直链表由 TD 通过 Next Link Pointer 串联而成,挂载在 QH 的 TQHP 指针下。每个 QH 管理一个垂直链表,链表中包含该端点待处理的所有传输描述符。
- 链表结构:QH 的 TQHP 指向第一个 TD,每个 TD 的 Next Link Pointer 指向下一个 TD,最后一个 TD 的 Next Link Pointer 为 NULL(0x00000001,最低位为 0 表示 TD 类型,但地址为 0)。
- 硬件回写:UHCI 硬件在处理完一个 TD 后,会更新 QH 的 TQTP 指针,用于软件判断链表是否处理完毕。
- 深度优先:UHCI 在遍历水平链表时,遇到挂载了 TD 的 QH,会先处理该 QH 垂直链表上的所有 TD(深度优先),然后再继续遍历下一个 QH。
4.4 调度策略
UHCI 的调度策略基于帧(Frame)和链表遍历机制,确保 USB 总线的带宽分配满足等时(Isochronous)、中断(Interrupt)、控制(Control)和批量(Bulk)传输的不同需求。
| 传输类型 | 调度优先级 | 帧列表位置 | QH 挂载方式 |
|---|---|---|---|
| 等时传输 | 最高 | 帧列表直接指向 TD(不经过 QH) | 无 QH,直接挂载 TD 到帧列表 |
| 中断传输 | 高 | 帧列表指向中断 QH 链表 | 每个中断端点对应一个 QH,挂载 TD 链 |
| 控制传输 | 中 | 帧列表指向控制 QH 链表 | 每个控制端点对应一个 QH,挂载 TD 链 |
| 批量传输 | 低 | 帧列表指向批量 QH 链表 | 每个批量端点对应一个 QH,挂载 TD 链 |
UHCI 主机控制器在每个帧开始时,按以下顺序遍历链表:
- 等时传输:直接从帧列表指针读取等时 TD,立即处理。
- 中断传输:遍历中断 QH 链表,处理所有挂载的 TD。
- 控制传输:遍历控制 QH 链表,处理所有挂载的 TD。
- 批量传输:遍历批量 QH 链表,处理所有挂载的 TD。
- 等时传输具有最高优先级,保证带宽和延迟。
- 中断传输在等时之后处理,保证周期性数据的及时性。
- 控制传输在中断之后处理,用于设备枚举和配置。
- 批量传输优先级最低,仅在总线空闲时处理。
- 每个帧的时间为 1ms(全速)或 125µs(高速,但 UHCI 仅支持全速/低速)。
4.5 链表初始化与维护
在嵌入式平台上初始化 UHCI 链表时,需要遵循以下步骤:
- 分配内存:为帧列表(Frame List)分配 1024 个 32 位指针(4KB),必须 4KB 对齐。为每个 QH 和 TD 分配 16 字节对齐的内存。
- 初始化帧列表:将所有帧列表指针初始化为 NULL(0x00000001,表示 QH 类型但地址为 0),或指向默认的空闲 QH。
- 创建 QH:为每个需要调度的端点创建一个 QH,设置 Device Address、Endpoint Number、Max Packet Size 等字段。将 TQHP 和 TQTP 初始化为指向一个保留的“空 TD”(Dummy TD)。
- 链接 QH:将 QH 按优先级顺序通过 HLP 串联成水平链表,并将第一个 QH 的地址写入帧列表的对应位置。
- 挂载 TD:当有传输请求时,创建 TD 并挂载到对应 QH 的垂直链表上。更新 TQHP 指向第一个 TD,TQTP 指向最后一个 TD 的下一个位置。
- 启动主机控制器:设置 UHCI 基地址寄存器(BASEADDR)指向帧列表,设置控制寄存器(USBCMD)中的 Run/Stop 位为 1,启动调度。
4.6 链表遍历示例
以下是一个简化的链表遍历伪代码,展示了 UHCI 硬件在每个帧中的处理逻辑:
// UHCI 帧处理伪代码
void uhci_frame_process(uint32_t frame_list_ptr, int frame_index) {
uint32_t current_ptr = frame_list_ptr[frame_index];
while (current_ptr != NULL) {
if (current_ptr & 0x1) {
// 当前节点是 QH(最低位为 1)
QH *qh = (QH *)(current_ptr & 0xFFFFFFF0);
// 检查垂直链表是否非空
if (qh->td_queue_head_ptr != NULL) {
// 处理该 QH 下的所有 TD
TD *td = qh->td_queue_head_ptr;
while (td != NULL) {
uhci_process_td(td);
td = td->next_link_ptr;
}
}
// 继续遍历水平链表
current_ptr = qh->horizontal_link_ptr;
} else {
// 当前节点是 TD(最低位为 0)
TD *td = (TD *)(current_ptr & 0xFFFFFFF0);
uhci_process_td(td);
current_ptr = td->next_link_ptr;
}
}
}
通过理解 QH 与 TD 的链表结构、水平与垂直链接方式以及调度策略,嵌入式开发者可以正确配置 UHCI 主机控制器,实现高效的控制传输与 DMA 传输。在实际测试验证中,建议使用逻辑分析仪或 USB 协议分析仪观察链表遍历顺序,确保调度行为符合预期。