3. UHCI 帧列表与传输描述符(TD)

UHCI 主机控制器通过 帧列表(Frame List) 和 传输描述符(Transfer Descriptor, TD) 两级数据结构来管理 USB 总线上所有周期性(等时/中断)和非周期性(控制/批量)传输。帧列表提供时间基准,TD 则描述每一次具体传输的属性和数据位置。理解这两者的格式与链接关系,是进行嵌入式平台 UHCI 驱动开发和 DMA 验证的基础。

核心概念: 帧列表是 UHCI 的“时间轴”,每个表项指向一个等时传输描述符(ITD)或队列头(QH);TD 是“传输指令”,包含状态、控制、数据缓冲区指针和链接指针。DMA 引擎自动遍历帧列表和 TD 链,无需 CPU 逐条干预。

3.1 帧列表(Frame List)结构

帧列表是一个物理地址连续的 4 字节表项数组,位于系统内存中。UHCI 基地址寄存器(BASEADDR)指向该数组的起始地址。帧列表的大小由 帧列表大小寄存器(FRNUM & SOFMOD) 控制,通常为 1024 项(每帧 1 项),也可配置为 256 或 64 项。

每个帧列表项(Frame List Entry, FLE)的格式如下:

位域 宽度(bit) 描述
Link Pointer 31:4 指向 TD 或 QH 的物理地址(32 字节对齐,低 4 位为 0)
Reserved 3:2 保留位,必须写 0
QH/TD Select 1 0 = 指向 TD(等时传输描述符 ITD);1 = 指向队列头(QH)
Terminate 0 1 = 链表结束,UHCI 停止遍历该帧

UHCI 在每个 USB 帧(1 ms)开始时,根据当前帧号(Frame Number)索引帧列表,取出表项中的 Link Pointer,然后开始遍历该指针指向的 TD 或 QH 链。帧号寄存器(FRNUM)每帧自动递增,循环使用帧列表。

嵌入式平台注意: 帧列表必须位于 4 KB 对齐的物理内存区域,且不能被 CPU 缓存(需配置为 non-cacheable 或使用 DMA 一致性内存)。在 Linux 嵌入式系统中,通常通过 dma_alloc_coherent() 分配。
帧列表(Frame List)结构示意图 FLE[0] FLE[1] FLE[2] FLE[3] ... Link Pointer (31:4) Res Q/T T QH / TD Q/T = 0: 指向等时 TD (ITD) Q/T = 1: 指向队列头 (QH) T = 1: 链表终止

3.2 传输描述符(TD)格式详解

UHCI 使用两种 TD:等时传输描述符(Isochronous TD, ITD) 和 队列传输描述符(Queue TD, QTD)。控制传输和批量传输使用 QTD,它们通过队列头(QH)组织成单向链表。每个 TD 固定为 32 字节(4 个双字),必须 32 字节对齐。

QTD 的四个双字(DWord)格式如下:

双字 偏移 位域 宽度(bit) 描述
DWord 0
Link Pointer
0x00 Link Pointer (31:5) 27 指向下一个 QTD 或 QH 的物理地址(32 字节对齐)
Depth/Breadth (4) 1 0 = 深度优先(遍历完当前 TD 链再返回 QH);1 = 广度优先
Terminate (0) 1 1 = 链表结束
DWord 1
Status & Control
0x04 Reserved (31:24) 8 保留
Error Counter (23:21) 3 允许的最大错误重试次数(0-7),每次错误递减
Low Speed (20) 1 1 = 低速设备;0 = 全速设备
IOC (19) 1 1 = 完成此 TD 后产生中断
Active (18) 1 1 = TD 处于激活状态,UHCI 可执行;0 = 已完成或停止
DWord 2
Token
0x08 PID (31:29) 3 USB 令牌包类型:000=OUT, 001=IN, 010=SETUP
Device Address (28:21) 8 目标 USB 设备地址(0-127)
Endpoint Number (20:18) 3 端点号(0-7)
DWord 3
Buffer Pointer
0x0C Buffer Pointer (31:0) 32 数据缓冲区的物理地址(无需对齐,但建议 4 字节对齐)
Max Packet Length (10:0) 11 此 TD 允许传输的最大字节数(1-1024)
常见陷阱: 在嵌入式平台上,TD 的 Link Pointer 和 Buffer Pointer 必须使用物理地址,而非虚拟地址。如果使用 MMU(如 Linux),必须通过 virt_to_phys() 或 DMA API 转换。此外,TD 结构体在内存中必须保持 32 字节对齐,否则 UHCI 会忽略该 TD。

3.3 TD 链接与队列机制

UHCI 使用 队列头(Queue Head, QH) 作为 TD 链的入口点。QH 本身不包含传输数据,只包含一个指向第一个 QTD 的指针和一个指向下一个 QH 的指针。帧列表中的每个非等时表项指向一个 QH,QH 再链接多个 QTD 形成传输队列。

典型的控制传输(SETUP + DATA + STATUS 阶段)在 UHCI 中通过 3 个 QTD 实现:

  1. SETUP TD: PID = SETUP (010),包含 8 字节设置包,Data Toggle = 0。
  2. DATA TD: PID = IN 或 OUT,包含实际数据,Data Toggle = 1。
  3. STATUS TD: PID = IN 或 OUT(与 DATA 方向相反),数据长度为 0,Data Toggle = 1。

这三个 TD 通过 Link Pointer 串联,最后一个 TD 的 Terminate 位设为 1。UHCI 的 DMA 引擎会按顺序遍历并执行每个 TD,直到遇到 Terminate 或 Active 位被清除。

控制传输 TD 链(SETUP → DATA → STATUS) 队列头 (QH) Next QH → SETUP TD PID=010, Toggle=0 8 字节设置包 DATA TD PID=IN/OUT, Toggle=1 数据缓冲区 STATUS TD PID=IN/OUT 长度=0 DMA 遍历顺序: QH → SETUP TD → DATA TD → STATUS TD 每个 TD 执行完后,UHCI 自动读取下一个 TD 的 Link Pointer STATUS TD 的 Terminate = 1,遍历结束,返回帧列表处理下一帧 数据缓冲区(系统内存) Buffer Pointer 字段指向此处,DMA 引擎直接读写 无需 CPU 逐字节搬运,实现真正的 DMA 传输

3.4 关键字段详解与嵌入式测试要点

在嵌入式平台上验证 UHCI 控制传输时,以下 TD 字段需要特别关注:

字段 测试验证要点 常见错误
Active (DWord1 bit18) TD 初始化时必须置 1;UHCI 执行完成后自动清 0。轮询该位可判断传输是否完成。 未置 Active 位导致 UHCI 跳过 TD;或 Active 位被意外修改。
Error Counter (DWord1 bit23:21) 初始值通常设为 3。每次 USB 错误(如 CRC 校验失败)递减,减到 0 时 UHCI 停止该 TD 并置位 Stalled。 设为 0 导致首次错误即停止;或未检查错误计数递减情况。
IOC (DWord1 bit19) 置 1 可使 UHCI 在完成该 TD 后产生中断。控制传输通常在 STATUS TD 置 IOC。 每个 TD 都置 IOC 导致中断风暴;或未置 IOC 导致无法触发完成回调。
Buffer Pointer (DWord3 bit31:0) 必须指向有效的物理内存,且该内存区域不能被缓存或重映射。 使用虚拟地址导致 DMA 访问错误;或缓冲区跨页边界未处理。
Max Packet Length (DWord3 bit10:0) 对于全速设备,控制传输最大包长为 64 字节;低速设备为 8 字节。 设置超过端点能力导致 Babble 错误;或设置 0 导致传输被忽略。
嵌入式测试建议: 在调试 UHCI 控制传输时,建议在 TD 完成后通过 DMA 回读 TD 的 Status 字段(DWord1),检查 Active、Error Counter、Stalled 和 Data Buffer Error 等状态位。使用逻辑分析仪或 USB 协议分析仪对比 TD 配置与实际总线信号,是定位 DMA 问题的有效手段。

3.5 帧列表与 TD 的协同工作流程

UHCI 在每个 USB 帧开始时执行以下步骤:

  1. 读取帧号: 从 FRNUM 寄存器获取当前帧号,对帧列表大小取模得到索引。
  2. 获取表项: 从帧列表数组中读取对应 FLE,解析 Link Pointer 和 Q/T 位。
  3. 遍历队列: 如果 Q/T=1,则进入 QH 链;否则处理 ITD 链。
  4. 执行 TD: 从 QH 指向的第一个 QTD 开始,检查 Active 位,若为 1 则执行 USB 传输。
  5. 更新指针: 执行完当前 TD 后,通过 Link Pointer 找到下一个 TD,直到 Terminate=1。
  6. 帧结束: 当前帧时间用完或链表遍历完毕,UHCI 等待下一帧 SOF 信号。

整个过程中,CPU 只需在初始化时设置帧列表和 TD 链,之后 UHCI 的 DMA 引擎自动完成所有遍历和传输操作,极大降低了 CPU 占用率。

UHCI 帧列表与 TD 协同工作流程 1. 读取帧号 FRNUM 寄存器 2. 索引帧列表 FLE[frame_index] 3. 解析表项 Link Pointer + Q/T 4. 遍历 QH 链 找到第一个 QTD 5. 执行 TD Active=1 则传输 6. 链接下一 TD Terminate=0 继续 循环直到 Terminate=1 或帧结束 关键点: CPU 只需设置帧列表和 TD 链,UHCI DMA 引擎自动完成所有遍历与传输 每帧(1 ms)重复上述流程,帧号自动递增,帧列表循环使用 控制传输的 SETUP/DATA/STATUS 通过 TD 链的 Link Pointer 顺序执行

3.6 嵌入式平台验证清单

在嵌入式平台上对 UHCI 帧列表和 TD 进行测试验证时,建议按以下清单逐项确认:

  • 内存对齐: 帧列表 4 KB 对齐,TD 32 字节对齐,QH 16 字节对齐。
  • 缓存一致性: 帧列表和 TD 所在内存区域必须配置为 non-cacheable,或在每次 CPU 修改后执行缓存刷新(如 ARM 的 clean_dcache_range())。
  • 物理地址正确性: Link Pointer 和 Buffer Pointer 必须使用物理地址,且指向的内存区域可被 DMA 访问。
  • Active 位初始化: 所有待执行的 TD 的 Active 位必须置 1,否则 UHCI 会跳过。
  • Terminate 位设置: 链尾 TD 的 Terminate 位必须置 1,否则 UHCI 会继续读取无效内存地址。
  • 中断处理: 仅在必要的 TD(如 STATUS TD)置 IOC 位,避免不必要的中断开销。
  • 错误处理: 检查 TD 完成后的 Error Counter 和 Stalled 位,实现超时重试或错误恢复逻辑。
快速验证方法: 在嵌入式平台启动后,先构造一个简单的控制传输(如 Get Descriptor 请求),通过调试串口打印 TD 的 Status 字段。如果 Active 位被清除且 Error Counter 未递减,说明 TD 被 UHCI 正确执行;如果 Active 位仍为 1,则 UHCI 可能未访问该 TD,需检查帧列表链接和内存一致性。