2. EHCI寄存器空间布局:MMIO基地址获取(PCI BAR)、寄存器偏移映射、Capability Registers与Operational Registers分区
EHCI主机控制器的寄存器空间通过PCIe配置空间中的BAR(Base Address Register)暴露给软件。驱动开发的第一步是正确获取MMIO基地址,并理解寄存器空间的分区结构。EHCI规范将寄存器划分为Capability Registers(能力寄存器)和Operational Registers(操作寄存器)两大区域,两者通过固定的偏移量进行访问。
2.1 MMIO基地址获取:PCI BAR解析
在x86/ARM64平台上,EHCI控制器作为PCIe设备存在。其MMIO基地址存储在PCI配置空间的BAR0中(通常为32位非预取内存空间)。驱动通过读取PCIe配置空间获取该地址,并将其映射到内核虚拟地址空间。
- 遍历PCI总线,匹配EHCI设备的Vendor ID(0x8086 for Intel)和Class Code(0x0C0320)。
- 读取PCI配置空间偏移0x10处的BAR0寄存器。
- 检查BAR的低4位:bit0为0表示MMIO空间,bit1-bit2表示类型(00=32位,10=64位)。
- 对BAR值进行掩码(清除低4位),得到MMIO物理基地址。
- 调用
ioremap()或ioremap_nocache()将物理地址映射到内核虚拟地址空间。
| 位域 | 宽度 | 描述 |
|---|---|---|
| 31:12 | 20 bits | MMIO基地址(4KB对齐) |
| 11:4 | 8 bits | 保留(读回0) |
| 3 | 1 bit | Prefetchable(0=非预取) |
| 2:1 | 2 bits | 地址空间类型(00=32位MMIO) |
| 0 | 1 bit | 空间指示(0=MMIO,1=IO) |
// Linux内核中获取EHCI MMIO基地址的典型代码片段
#include <linux/pci.h>
#include <linux/io.h>
struct pci_dev *ehci_pdev;
void __iomem *ehci_regs;
unsigned long mmio_phys_base;
// 1. 查找EHCI设备
ehci_pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x1D26, NULL);
if (!ehci_pdev) {
pr_err("EHCI device not found\n");
return -ENODEV;
}
// 2. 启用设备并读取BAR0
pci_enable_device(ehci_pdev);
mmio_phys_base = pci_resource_start(ehci_pdev, 0);
// 3. 映射MMIO空间(4KB对齐)
ehci_regs = ioremap_nocache(mmio_phys_base, 4096);
if (!ehci_regs) {
pr_err("ioremap failed\n");
return -ENOMEM;
}
pr_info("EHCI MMIO base: 0x%lx, mapped at %p\n", mmio_phys_base, ehci_regs);
reg属性中获取。驱动应同时支持PCI和平台设备两种方式,通过of_match_table或acpi_match_table进行兼容。
2.2 寄存器偏移映射:从基地址到具体寄存器
EHCI寄存器空间以4KB为总大小,但实际使用的偏移范围在0x00~0x3FF之间。驱动通过基地址 + 偏移量的方式访问每个寄存器。偏移量分为两类:绝对偏移(从MMIO基地址开始计算)和相对偏移(从Capability或Operational区域起始处计算)。
2.3 Capability Registers:能力寄存器详解
Capability Registers位于MMIO空间的起始64字节(偏移0x00~0x3F),用于描述EHCI控制器的版本、结构参数、能力标志。驱动在初始化阶段必须读取这些寄存器以确定后续操作寄存器的布局和功能支持。
| 偏移 | 寄存器名称 | 宽度 | 描述 |
|---|---|---|---|
| 0x00 | CAPLENGTH | 8 bits | Capability寄存器长度(通常为0x40),指示Operational Registers起始偏移 |
| 0x02 | HCIVERSION | 16 bits | 主机控制器接口版本号(如0x0100表示EHCI 1.0) |
| 0x04 | HCCPARAMS | 32 bits | 结构参数:64位寻址支持、端口数、中断路由等 |
| 0x08 | HCCSPARAMS | 32 bits | 能力参数:端口功率控制、帧列表大小、异步/周期性调度支持 |
// 读取Capability Registers示例
u8 cap_length;
u16 hci_version;
u32 hcc_params, hcc_sparams;
cap_length = readb(ehci_regs + 0x00); // CAPLENGTH
hci_version = readw(ehci_regs + 0x02); // HCIVERSION
hcc_params = readl(ehci_regs + 0x04); // HCCPARAMS
hcc_sparams = readl(ehci_regs + 0x08); // HCCSPARAMS
pr_info("EHCI CAPLENGTH: %d bytes\n", cap_length);
pr_info("EHCI HCIVERSION: 0x%04x\n", hci_version);
pr_info("EHCI HCCPARAMS: 0x%08x\n", hcc_params);
pr_info("EHCI HCCSPARAMS: 0x%08x\n", hcc_sparams);
// 从HCCPARAMS提取关键字段
int n_ports = (hcc_params >> 24) & 0x0F; // 端口数量
int has_64bit = (hcc_params >> 0) & 0x01; // 64位寻址支持
int frame_list_size = (hcc_params >> 15) & 0x01; // 帧列表大小(0=1024, 1=512)
2.4 Operational Registers:操作寄存器分区
Operational Registers从CAPLENGTH指定的偏移开始(通常为0x40),包含控制、状态、中断、端口状态等运行时寄存器。这些寄存器是驱动进行控制传输、调度管理和端口操作的核心。
| 相对偏移 | 寄存器 | 宽度 | 功能描述 |
|---|---|---|---|
| 0x00 | USBCMD | 32 bits | 主机控制器运行/停止、复位、帧列表大小、中断阈值控制 |
| 0x04 | USBSTS | 32 bits | 运行状态、挂起、端口变更、中断标志、系统错误 |
| 0x08 | USBINTR | 32 bits | 中断使能:USB中断、错误中断、端口变更中断、帧列表滚动中断 |
| 0x0C | FRINDEX | 32 bits | 当前帧索引(微帧精度),用于调度周期性传输 |
| 0x10 | CTRLDSSEGMENT | 32 bits | 控制数据结构段选择(64位模式下高32位地址) |
| 0x14 | PERIODICLISTBASE | 32 bits | 周期性帧列表基地址(低32位) |
| 0x18 | ASYNCLISTADDR | 32 bits | 异步队列头列表基地址(低32位) |
| 0x40 | PORTSC(1) | 32 bits | 端口1状态与控制:连接检测、使能、复位、速度指示、过流 |
2.5 寄存器访问封装与字节序处理
EHCI寄存器采用小端字节序(Little-Endian)。在x86平台上无需额外转换,但在ARM大端模式下,驱动必须使用readl_relaxed()/writel_relaxed()配合le32_to_cpu()宏进行字节序转换。推荐使用封装函数统一管理寄存器访问。
// 寄存器访问封装示例(支持大/小端)
static inline u32 ehci_readl(const struct ehci_hcd *ehci, int offset)
{
u32 val = readl(ehci->regs + offset);
return le32_to_cpu(val); // 小端转CPU字节序
}
static inline void ehci_writel(const struct ehci_hcd *ehci, u32 val, int offset)
{
writel(cpu_to_le32(val), ehci->regs + offset);
}
// 位域操作宏
#define EHCI_REG_SET_BITS(reg, mask) \
ehci_writel(ehci, ehci_readl(ehci, reg) | (mask), reg)
#define EHCI_REG_CLEAR_BITS(reg, mask) \
ehci_writel(ehci, ehci_readl(ehci, reg) & ~(mask), reg)
// 使用示例:设置USBCMD的Run/Stop位
EHCI_REG_SET_BITS(0x40, 1 << 0); // USBCMD[0] = 1 启动控制器
readl_relaxed()/writel_relaxed()代替普通版本,可避免不必要的内存屏障(DMB/DSB),提升吞吐量。但需确保在跨核同步点(如DMA描述符更新后)显式插入mb()屏障。
2.6 寄存器空间验证与调试技巧
在驱动开发初期,验证寄存器空间映射是否正确至关重要。以下方法可快速诊断MMIO基地址和寄存器偏移问题:
- 读取CAPLENGTH验证:读取偏移0x00,期望值为0x40。若为0xFF或0x00,说明基地址错误或设备未使能。
- 读取HCIVERSION验证:读取偏移0x02,期望值为0x0100(EHCI 1.0)或0x0110(EHCI 1.1)。
- 端口状态回读:读取PORTSC寄存器,检查连接状态位(bit0)是否随设备插拔变化。
- 使用devmem2工具:在用户态通过
devmem2 0xMMIO_BASE 32直接读取物理地址,与驱动内读取值对比。
// 调试输出:打印所有Capability和关键Operational寄存器
void ehci_dump_regs(struct ehci_hcd *ehci)
{
int i;
pr_info("=== EHCI Capability Registers ===\n");
pr_info("CAPLENGTH: 0x%02x\n", readb(ehci->regs + 0x00));
pr_info("HCIVERSION: 0x%04x\n", readw(ehci->regs + 0x02));
pr_info("HCCPARAMS: 0x%08x\n", readl(ehci->regs + 0x04));
pr_info("HCCSPARAMS: 0x%08x\n", readl(ehci->regs + 0x08));
pr_info("=== EHCI Operational Registers ===\n");
pr_info("USBCMD: 0x%08x\n", ehci_readl(ehci, 0x40));
pr_info("USBSTS: 0x%08x\n", ehci_readl(ehci, 0x44));
pr_info("USBINTR: 0x%08x\n", ehci_readl(ehci, 0x48));
pr_info("FRINDEX: 0x%08x\n", ehci_readl(ehci, 0x4C));
// 打印前4个端口状态
for (i = 0; i < 4; i++) {
u32 portsc = ehci_readl(ehci, 0x80 + i * 8);
pr_info("PORTSC[%d]: 0x%08x\n", i+1, portsc);
}
}
- BAR0可能为64位地址(当HCCPARAMS的bit0=1时),此时需要读取BAR0和BAR1组合成64位物理地址。
- 某些BIOS/UEFI在启动时未对EHCI控制器分配MMIO资源,驱动需调用
pci_assign_resource()手动分配。 - 在虚拟化环境中(如QEMU/KVM),MMIO基地址可能被VMM重映射,驱动应始终使用PCI子系统提供的资源值,而非硬编码地址。