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微帧),确认控制器已进入运行状态。否则写入链表基地址可能被忽略。
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。
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 → 修改地址 → 重新使能”的流程。
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输出寄存器值,对比数据手册确认位域正确。