4. Operational Registers详解(上):USBCMD、USBSTS、USBINTR、FRINDEX、PERIODICLISTBASE、ASYNCLISTADDR

EHCI主机控制器的运行状态完全由一组Operational Registers(操作寄存器)控制。这些寄存器位于MMIO基地址偏移量 0x20 之后,是驱动初始化、调度传输、处理中断和调试的核心操作对象。本章深入解析前6个关键寄存器:USBCMD、USBSTS、USBINTR、FRINDEX、PERIODICLISTBASE 和 ASYNCLISTADDR。

关键提示: 操作寄存器的访问必须使用32位对齐的MMIO读写(readl/writel),且所有寄存器在Host Controller Reset(HCRESET)后恢复为默认值。驱动初始化阶段必须按顺序配置这些寄存器,否则控制器行为不可预测。

4.1 USBCMD — USB Command Register(偏移量 0x20)

USBCMD是驱动向控制器下发命令的主入口,控制运行/停止、复位、异步/周期调度使能等核心行为。其位定义如下:

位域 名称 描述 默认值
31:24 ITC 中断阈值控制,决定中断上报延迟(帧微帧数) 0x08(8微帧)
23:16 Reserved 保留,必须写0 0
15:8 Reserved 保留,必须写0 0
7 LHCR Light Host Controller Reset(轻量复位) 0
6 IAAD Interrupt on Async Advance Doorbell 0
5 ASPE Asynchronous Schedule Enable(异步调度使能) 0
4 PSPE Periodic Schedule Enable(周期调度使能) 0
3:2 Reserved 保留,必须写0 0
1 HCRESET Host Controller Reset(完整复位) 0
0 RS Run/Stop(运行/停止) 0
工程实践: 驱动初始化时,先置位HCRESET(位1)等待复位完成,再配置ITC(建议8微帧),最后置位RS(位0)启动控制器。ASPE和PSPE必须在链表基地址寄存器配置完成后才能置位。

4.1.1 启动与停止序列

启动控制器的标准代码片段:

/* 1. 复位控制器 */
ehci_writel(ehci, CMD_HCRESET, &ehci->regs->command);
while (ehci_readl(ehci, &ehci->regs->command) & CMD_HCRESET)
    cpu_relax();

/* 2. 配置中断阈值(8微帧) */
cmd = ehci_readl(ehci, &ehci->regs->command);
cmd &= ~CMD_ITC_MASK;
cmd |= 8 << 16;
ehci_writel(ehci, cmd, &ehci->regs->command);

/* 3. 启动控制器 */
cmd |= CMD_RUN;
ehci_writel(ehci, cmd, &ehci->regs->command);
避坑指南: 不要在RS置位后立即操作调度链表。必须等待USBSTS中HCHalted位清零(约1~2微帧),确认控制器已进入运行状态。否则写入链表基地址可能被忽略。
USBCMD 寄存器启动状态机 HCRESET=1 等待复位完成 配置ITC RS=1 启动 等待HCHalted=0 运行状态

4.2 USBSTS — USB Status Register(偏移量 0x24)

USBSTS反映控制器的实时状态,包括运行状态、中断挂起、错误标志等。驱动通过轮询或中断读取该寄存器判断事件。

位域 名称 描述 清除方式
31:16 Reserved 保留 -
15 AS Asynchronous Schedule Status(异步调度状态) 只读
14 PS Periodic Schedule Status(周期调度状态) 只读
13 RCL Reclamation(回收状态) 只读
12 HCH HCHalted(控制器停止) 只读
11:6 Reserved 保留 -
5 IAA Interrupt on Async Advance 写1清除
4 HSE Host System Error(主机系统错误) 写1清除
3 FLR Frame List Rollover(帧列表翻转) 写1清除
2 PCD Port Change Detect(端口变化检测) 写1清除
1 USBERRINT USB Error Interrupt(USB错误中断) 写1清除
0 USBINT USB Interrupt(USB中断) 写1清除
关键位解读: HCH(位12)是驱动判断控制器是否真正停止的唯一依据。当USBCMD.RS=0后,必须等待HCH=1才能安全修改调度链表。AS和PS位反映当前调度是否激活,与USBCMD中的ASPE/PSPE对应但存在延迟。

4.2.1 中断处理中的状态读取

/* 中断服务例程中读取状态 */
u32 status = ehci_readl(ehci, &ehci->regs->status);

/* 清除已处理的中断源(写1清除) */
ehci_writel(ehci, status, &ehci->regs->status);

if (status & STS_FATAL) {
    /* 处理主机系统错误或HSE */
    ehci_reset(ehci);
    return IRQ_HANDLED;
}

if (status & STS_IAA) {
    /* 异步调度完成通知,可安全回收QH */
    ehci_handle_iaa(ehci);
}

if (status & STS_PCD) {
    /* 端口状态变化,重新枚举 */
    ehci_port_change(ehci);
}

4.3 USBINTR — USB Interrupt Enable Register(偏移量 0x28)

USBINTR控制哪些USBSTS中的中断源可以触发硬件中断线。每一位对应USBSTS中的同名位,置位使能中断。

位域 名称 对应USBSTS位 说明
5 IAAE IAA 异步调度提前中断使能
4 HSEE HSE 主机系统错误中断使能
3 FLRE FLR 帧列表翻转中断使能
2 PCDE PCD 端口变化中断使能
1 UEIE USBERRINT USB错误中断使能
0 UIE USBINT USB中断使能(传输完成)
推荐配置: 生产驱动通常使能UIE(传输完成)、IAAE(异步调度回收)和PCDE(热插拔)。HSEE仅在调试阶段使能,因为系统错误通常意味着硬件故障,需要复位恢复。

4.4 FRINDEX — Frame Index Register(偏移量 0x2C)

FRINDEX是一个14位递增计数器,以微帧(125μs)为单位递增。每8个微帧构成一个帧(1ms)。该寄存器用于索引周期调度链表(Periodic Frame List)。

  • 位13:0 — 帧索引值,范围0~16383,循环递增。
  • 位31:14 — 保留,读返回0。
  • 每微帧递增1,每8微帧(1帧)低位3位归零,高位加1。
/* 读取当前帧索引 */
u32 frame_index = ehci_readl(ehci, &ehci->regs->frame_index);
u32 current_frame = frame_index & 0x3FFF;
u32 microframe = frame_index & 0x7;  /* 当前微帧号 0~7 */

/* 驱动调试时可用于计算调度延迟 */
u32 start_frame = current_frame;
/* ... 提交传输 ... */
u32 end_frame = ehci_readl(ehci, &ehci->regs->frame_index) & 0x3FFF;
u32 elapsed = (end_frame - start_frame) & 0x3FFF;  /* 微帧数 */
注意: FRINDEX是只读寄存器,驱动不能直接写入。但在测试模式下,部分EHCI实现允许写入以模拟特定帧号。生产驱动绝不要尝试修改FRINDEX。
FRINDEX 微帧/帧递增关系 μF0 μF1 μF2 μF3 μF4 μF5 μF6 μF7 1 Frame = 8 Microframes (1ms) FRINDEX++

4.5 PERIODICLISTBASE — Periodic Frame List Base Address Register(偏移量 0x34)

该寄存器保存周期帧列表(Periodic Frame List)在系统内存中的物理基地址。帧列表是一个包含1024个32位指针的数组,每个指针指向一个周期调度数据结构(iTD或siTD)。

  • 位31:12 — 帧列表基地址(4KB对齐),低12位硬件忽略。
  • 位11:0 — 保留,必须写0。
  • 帧列表大小固定为1024项(4KB),每项对应一个帧号(0~1023)。
/* 分配4KB对齐的帧列表内存 */
struct ehci_frame_list {
    __le32 hw_pa[1024];
} __aligned(4096);

struct ehci_frame_list *flist;
dma_addr_t flist_dma;

flist = dma_alloc_coherent(dev, sizeof(*flist), &flist_dma, GFP_KERNEL);
memset(flist, 0, sizeof(*flist));

/* 写入PERIODICLISTBASE */
ehci_writel(ehci, flist_dma, &ehci->regs->periodic_list_base);

/* 使能周期调度 */
cmd = ehci_readl(ehci, &ehci->regs->command);
cmd |= CMD_PSPE;
ehci_writel(ehci, cmd, &ehci->regs->command);
对齐要求: PERIODICLISTBASE必须指向4KB对齐的物理地址。如果使用非对齐地址,控制器将忽略低12位,导致访问错误的物理内存。驱动必须使用dma_alloc_coherent或类似API保证对齐。

4.6 ASYNCLISTADDR — Asynchronous List Address Register(偏移量 0x38)

该寄存器保存异步调度链表(Asynchronous Schedule)中第一个QH(Queue Head)的物理地址。异步调度用于处理控制传输和批量传输。

  • 位31:5 — 异步链表首QH物理地址(32字节对齐)。
  • 位4:0 — 保留,必须写0。
  • 链表为循环双向链表,最后一个QH的Next指针指向第一个QH。
/* 设置异步链表首地址 */
struct ehci_qh *async_qh;
dma_addr_t async_qh_dma;

/* 初始化首QH(类型为异步,水平链接) */
async_qh = ehci_qh_alloc(ehci, GFP_KERNEL, &async_qh_dma);
async_qh->hw_next = EHCI_LIST_END(ehci);  /* 初始化为空链表 */
async_qh->hw_info1 |= cpu_to_hc32(QH_HEAD);
async_qh->hw_info2 |= cpu_to_hc32(QH_SMASK | QH_CMASK);

/* 写入ASYNCLISTADDR */
ehci_writel(ehci, async_qh_dma, &ehci->regs->async_list_addr);

/* 使能异步调度 */
cmd = ehci_readl(ehci, &ehci->regs->command);
cmd |= CMD_ASPE;
ehci_writel(ehci, cmd, &ehci->regs->command);
关键约束: 修改ASYNCLISTADDR必须在异步调度停止时进行(USBCMD.ASPE=0且USBSTS.AS=0)。如果在调度运行中修改,控制器可能读取到不一致的链表状态,导致系统错误(HSE)。驱动应遵循“停止调度 → 等待AS=0 → 修改地址 → 重新使能”的流程。
异步调度链表与ASYNCLISTADDR关系 ASYNCLISTADDR QH 0 (首) QH 1 QH 2 循环链表(最后一个QH指向第一个) QH必须32字节对齐,Next指针位0为1表示链表结束(T=1)

4.7 寄存器配置顺序总结

驱动初始化阶段,这6个寄存器的配置必须遵循严格的时序:

步骤 寄存器 操作 等待条件
1 USBCMD 置位HCRESET HCRESET位自清零
2 USBCMD 配置ITC 无
3 USBINTR 使能所需中断源 无
4 PERIODICLISTBASE 写入帧列表基地址 PSPE=0
5 ASYNCLISTADDR 写入异步链表首地址 ASPE=0
6 USBCMD 置位RS HCHalted=0
7 USBCMD 置位ASPE/PSPE AS=0, PS=0
调试建议: 在配置过程中,每步操作后读取USBSTS验证状态。例如,复位后检查HCH=1;启动后检查HCH=0。使用printk或dev_dbg输出寄存器值,对比数据手册确认位域正确。