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. 步骤 1: 确保 UHCI 控制器处于 Halted(停止) 状态(通过检查 USBCMD 寄存器的 RS 位为 0)。
  2. 步骤 2: 将 Frame List 缓冲区清零,并填充有效的 Frame List Pointer(指向各帧的 Transfer Descriptor 链表)。
  3. 步骤 3: 将 Frame List 的物理地址(高 20 位)写入 FLBASEADD 寄存器。
  4. 步骤 4: 设置 USBCMD 寄存器的 RS 位为 1,启动 UHCI 控制器。
  5. 步骤 5: 读取 FLBASEADD 寄存器进行回读验证,确保写入成功。
常见错误: 在控制器运行(RS=1)时修改 FLBASEADD 寄存器。这会导致控制器立即使用新的基地址,但当前帧的 DMA 传输可能尚未完成,造成数据损坏。务必在 Halted 状态下修改。

4.4 FLBASEADD 与 DMA 传输的关系

FLBASEADD 寄存器是 UHCI DMA 传输的“根节点”。其与 DMA 传输的关系如下图所示:

FLBASEADD 寄存器与 DMA 传输关系图 FLBASEADD 寄存器与 DMA 传输关系 FLBASEADD 寄存器 Frame List(4 KB 对齐) 1024 个条目,每个 4 字节 帧条目 0 帧条目 1 TD 链表 0 TD 链表 1 数据缓冲区 数据缓冲区 DMA 传输路径:FLBASEADD → Frame List → 帧条目 → TD → 数据缓冲区 每帧(1 ms)由 UHCI 控制器自动遍历

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 进行比较