2. UHCI 初始化流程总览
UHCI 主机控制器的初始化是 USB 子系统正常工作的前提。该过程涉及软件(驱动程序)与硬件(UHCI 控制器)之间的严格握手协议,核心目标是为后续的帧传输建立可用的 DMA 描述符链表。本章将系统梳理初始化步骤、关键寄存器以及软硬件交互的时序。
2.1 主机控制器初始化步骤
UHCI 的初始化遵循一个固定的线性流程,任何步骤的缺失或顺序错误都会导致控制器无法进入工作状态。完整的初始化步骤分为以下 6 个阶段:
- 端口复位与全局复位:通过 USBCMD 寄存器中的 HCRESET 位触发主机控制器复位,复位后所有寄存器恢复默认值。
- 主机控制器停止确认:复位后控制器处于停止状态,软件需轮询 USBSTS 寄存器的 HCHalted 位,确认停止完成。
- 帧列表基地址设置:在内存中分配 4KB 对齐的帧列表(Frame List),并将物理地址写入 FLBASEADD 寄存器。
- 帧计数器与中断配置:设置帧号(FRNUM)、帧长度(SOF 调整)以及中断使能(USBINTR)。
- DMA 描述符链表构建:在帧列表的每个指针项中填入传输描述符(TD)或队列头(QH)的物理地址,形成完整的调度环。
- 运行位设置:将 USBCMD 寄存器的 RS 位置 1,控制器开始根据帧列表调度传输。
关键约束: 步骤 5 必须在步骤 6 之前完成。若在 RS 位为 1 时修改帧列表指针,可能导致 DMA 取指错误,引发不可预测的系统行为。
2.2 软件与硬件的握手协议
UHCI 的软硬件握手通过一组特定的寄存器位域实现。软件通过写入命令寄存器发起操作,硬件通过状态寄存器反馈执行结果。以下是关键的握手序列:
| 阶段 | 软件操作 | 硬件响应 | 超时处理 |
|---|---|---|---|
| 复位握手 | 写 USBCMD.HCRESET = 1 | 复位内部状态机,HCHalted 置 1 | 轮询 1ms 未完成则报错 |
| 停止确认 | 读 USBSTS.HCHalted | 返回 1 表示已停止 | 轮询 100μs 未就绪则复位 |
| 基地址写入 | 写 FLBASEADD = 帧列表物理地址 | 锁存地址,用于 DMA 取指 | 无超时,但地址必须 4KB 对齐 |
| 运行启动 | 写 USBCMD.RS = 1 | 开始帧周期,HCHalted 清 0 | 轮询 10μs 未清除则硬件故障 |
实战建议: 在每次写命令寄存器后,建议插入一个
Memory Barrier(如 x86 的 mfence 或 out 指令),确保写操作被 PCI 总线刷新到 UHCI 控制器,避免因写缓冲导致的握手失败。
2.3 关键寄存器列表
UHCI 初始化阶段涉及的寄存器数量有限,但每个寄存器的位域定义直接影响 DMA 描述符管理。以下是必须掌握的寄存器清单:
| 寄存器名称 | 偏移地址 | 位宽 | 初始化阶段用途 |
|---|---|---|---|
| USBCMD | 0x00 | 16-bit | 复位控制器 (HCRESET)、启动/停止 (RS)、中断阈值控制 |
| USBSTS | 0x02 | 16-bit | 查询 HCHalted 状态、检测主机控制器错误 (HSE) |
| USBINTR | 0x04 | 16-bit | 使能传输完成/错误/短包中断 |
| FRNUM | 0x06 | 16-bit | 设置当前帧号,用于帧列表索引计算 |
| FLBASEADD | 0x08 | 32-bit | 写入帧列表基地址(必须 4KB 对齐) |
| SOFMOD | 0x0C | 8-bit | 调整 SOF 帧长度,用于时钟补偿 |
常见错误: FLBASEADD 寄存器只保存物理地址的低 32 位。在 64 位系统上,如果帧列表位于 4GB 以上地址空间,必须通过 PCIe 的
DMA 重映射 或 IOMMU 将地址映射到 32 位可访问区域,否则 DMA 将访问错误地址。
2.4 初始化流程中的关键检查点
在实际的嵌入式系统开发中,初始化流程的每个步骤都应设置检查点,以确保软硬件状态一致。以下是推荐的检查点清单:
- 复位后检查:读取 USBSTS 寄存器,确认 HCHalted 位为 1,且所有其他状态位为 0。
- 基地址写入后检查:回读 FLBASEADD 寄存器,确认写入值与预期一致(PCI 写合并可能导致回读不一致,需注意)。
- 描述符链表构建后检查:通过软件遍历帧列表,验证每个指针项指向的 QH/TD 结构体签名(Signature)是否正确。
- 运行后检查:设置 RS 位后,轮询 USBSTS 的 HCHalted 位,确认在 10μs 内清零,否则触发硬件错误处理。
性能优化提示: 在初始化阶段,帧列表的填充可以采用
rep stosd 指令批量写入,减少循环开销。对于 1024 项的帧列表,单次 rep stosd 即可完成所有指针的初始化,比逐项写入快约 10 倍。