4. Frame List 基地址寄存器 (FLBASEADD):作用、对齐要求、初始化写入时机
FLBASEADD 寄存器是 UHCI 主机控制器初始化阶段最关键的配置寄存器之一。它定义了系统内存中 Frame List(帧列表) 的起始物理地址,是 UHCI 进行 DMA 传输的“根指针”。理解其作用、对齐约束和写入时机,是确保 DMA 传输正确启动的前提。
4.1 寄存器作用与位域定义
FLBASEADD 寄存器位于 UHCI I/O 空间偏移 0x08h,宽度为 32 位。其位域定义如下:
| 位域 | 宽度 | 名称 | 描述 |
|---|---|---|---|
| 31:12 | 20 位 | Base Address | Frame List 的基地址(物理地址高 20 位) |
| 11:4 | 8 位 | Reserved | 保留位,必须写 0 |
| 3:2 | 2 位 | Reserved | 保留位,必须写 0 |
| 1:0 | 2 位 | Reserved | 保留位,必须写 0 |
关键理解: FLBASEADD 寄存器的低 12 位被硬件强制为 0。这意味着 Frame List 必须对齐到 4 KB(4096 字节) 边界。写入时,软件只需提供物理地址的高 20 位,低 12 位由硬件自动清零。
4.2 对齐要求详解
4 KB 对齐要求并非随意设定,而是由 UHCI 的 DMA 引擎设计决定的。Frame List 包含 1024 个条目(Frame List Pointer),每个条目 4 字节,总大小为 4 KB。UHCI 控制器在每帧(1 ms)开始时,通过 FLBASEADD 寄存器加载基地址,然后使用帧计数器(Frame Counter)的低 10 位作为索引,计算当前帧的条目地址:
当前帧条目地址 = FLBASEADD[31:12] << 12 + (FrameCounter[9:0] << 2)
由于低 12 位被硬件强制为 0,Frame List 的起始地址始终是 4 KB 对齐的。如果软件违反此对齐规则,UHCI 控制器将产生 系统错误(System Error),导致 DMA 传输无法启动。
实战建议: 在初始化代码中,使用内存分配函数(如
memalign(4096, 4096) 或 aligned_alloc(4096, 4096))确保 Frame List 缓冲区满足 4 KB 对齐。对于嵌入式系统,可在链接脚本中为 Frame List 预留对齐段。
4.3 初始化写入时机
FLBASEADD 寄存器的写入时机必须严格遵循 UHCI 初始化序列。错误的写入时机可能导致控制器状态不一致或 DMA 传输异常。
推荐的写入流程如下:
- 步骤 1: 确保 UHCI 控制器处于 Halted(停止) 状态(通过检查 USBCMD 寄存器的 RS 位为 0)。
- 步骤 2: 将 Frame List 缓冲区清零,并填充有效的 Frame List Pointer(指向各帧的 Transfer Descriptor 链表)。
- 步骤 3: 将 Frame List 的物理地址(高 20 位)写入 FLBASEADD 寄存器。
- 步骤 4: 设置 USBCMD 寄存器的 RS 位为 1,启动 UHCI 控制器。
- 步骤 5: 读取 FLBASEADD 寄存器进行回读验证,确保写入成功。
常见错误: 在控制器运行(RS=1)时修改 FLBASEADD 寄存器。这会导致控制器立即使用新的基地址,但当前帧的 DMA 传输可能尚未完成,造成数据损坏。务必在 Halted 状态下修改。
4.4 FLBASEADD 与 DMA 传输的关系
FLBASEADD 寄存器是 UHCI DMA 传输的“根节点”。其与 DMA 传输的关系如下图所示:
4.5 初始化代码示例
以下代码展示了在 x86 嵌入式系统中初始化 FLBASEADD 寄存器的典型实现:
// UHCI 寄存器 I/O 基地址(由 PCI 配置空间获取)
#define UHCI_BASE 0xE000
#define UHCI_FLBASEADD (UHCI_BASE + 0x08)
// Frame List 缓冲区(4 KB 对齐)
uint32_t *frame_list __attribute__((aligned(4096)));
void uhci_init_flbaseadd(void) {
uint32_t phys_addr;
// 步骤 1:确保控制器处于 Halted 状态
uint16_t cmd = inw(UHCI_BASE + 0x00); // USBCMD 寄存器
if (cmd & 0x01) { // RS 位为 1
cmd &= ~0x01;
outw(cmd, UHCI_BASE + 0x00);
// 等待控制器停止
while (inw(UHCI_BASE + 0x02) & 0x20); // USBSTS.HCHalted
}
// 步骤 2:清零 Frame List 并初始化条目
for (int i = 0; i < 1024; i++) {
frame_list[i] = 0; // 初始化为无效指针
// 实际使用时,应填充指向 TD 链表的指针
}
// 步骤 3:获取物理地址并写入 FLBASEADD
phys_addr = (uint32_t)frame_list; // 物理地址(假设 1:1 映射)
outl(phys_addr, UHCI_FLBASEADD);
// 步骤 4:启动控制器
cmd = inw(UHCI_BASE + 0x00);
cmd |= 0x01; // 设置 RS 位
outw(cmd, UHCI_BASE + 0x00);
// 步骤 5:回读验证
uint32_t verify = inl(UHCI_FLBASEADD);
if (verify != (phys_addr & 0xFFFFF000)) {
// 错误处理:写入失败
while(1);
}
}
优化提示: 在初始化 Frame List 条目时,可以预先分配多个 TD 并形成链表,避免在运行时动态分配。对于实时性要求高的系统,建议在初始化阶段完成所有 TD 的预分配和链接。
4.6 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 写入 FLBASEADD 后控制器不启动 | Frame List 未 4 KB 对齐 | 使用对齐分配函数,检查物理地址低 12 位是否为 0 |
| DMA 传输数据错乱 | 在控制器运行时修改了 FLBASEADD | 严格遵循 Halted 状态下修改的流程 |
| 系统错误(System Error)中断 | Frame List 条目指向无效内存 | 确保所有帧条目都指向有效的 TD 或设置为终止位 |
| 回读值与写入值不一致 | 低 12 位被硬件强制清零 | 回读时使用掩码 0xFFFFF000 进行比较 |