Rockchip AXI PCIe 主机控制器驱动 ------ 学习笔记
源码文件:
pcie-rockchip.c平台:Rockchip RK3399 及同系列(rk35xx)
作者:Shawn Lin, Wenrui Li (Rockchip)
基础框架参考:Synopsys Designware Host Controller + ARM PCI Host Generic Driver
目录
- 整体架构概览
- 核心数据结构
- 寄存器布局与定义
- 配置空间访问
- [设备树解析 ------ 硬件资源获取](#设备树解析 —— 硬件资源获取)
- 硬件初始化流程
- 中断处理体系
- [ATU 地址转换单元](#ATU 地址转换单元)
- [Probe 主流程](#Probe 主流程)
- 面试高频考点
1. 整体架构概览
┌─────────────────────────────────────────────────┐
│ CPU (AXI Bus) │
└────────────────────┬────────────────────────────┘
│
┌──────────▼──────────┐
│ PCIe Controller │
│ (Rockchip AXI) │
│ │
│ ┌──────────────┐ │
│ │ ATU (地址 │ │ ← Outbound: CPU→PCIe
│ │ 转换单元) │ │ ← Inbound: PCIe→CPU
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ Config Space │ │ ← ECAM 方式访问
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ Interrupt │ │ ← INTx/MSI 中断管理
│ │ Controller │ │
│ └──────────────┘ │
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ PCIe PHY │ ← 物理层 (SerDes)
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ PCIe Endpoint │ ← 下游设备 (网卡/NVMe等)
└────────────────────┘
关键设计特点:
- 基于 Designware IP 核,复用其寄存器布局
- 采用 ECAM (Enhanced Configuration Access Mechanism) 访问配置空间
- 双寄存器基址:
axi-base(数据传输/配置空间) 和apb-base(内部控制寄存器) - 支持 1/2/4 lane,Gen1(2.5GT/s) 和 Gen2(5GT/s)
2. 核心数据结构
c
struct rockchip_pcie {
// ===== 寄存器基址 =====
void __iomem *reg_base; // AXI 总线接口 (配置空间访问、数据传输)
void __iomem *apb_base; // APB 总线接口 (控制器内部寄存器)
// ===== PHY =====
struct phy *phy; // PCIe 物理层 (SerDes PHY)
// ===== 复位信号 (共7个) =====
struct reset_control *core_rst; // 核心复位
struct reset_control *mgmt_rst; // 管理接口复位
struct reset_control *mgmt_sticky_rst; // 粘性管理复位 (热复位时保持状态)
struct reset_control *pipe_rst; // PIPE 接口复位 (连接 PHY)
struct reset_control *pm_rst; // 电源管理复位
struct reset_control *aclk_rst; // ACLK 时钟域复位
struct reset_control *pclk_rst; // PCLK 时钟域复位
// ===== 时钟 (共4个) =====
struct clk *aclk_pcie; // AXI 主时钟
struct clk *aclk_perf_pcie; // AXI 性能监控时钟
struct clk *hclk_pcie; // AHB 总线时钟
struct clk *clk_pcie_pm; // 电源管理模块时钟
// ===== 电源管理 =====
struct regulator *vpcie3v3; // 3.3V (可选)
struct regulator *vpcie1v8; // 1.8V (可选)
struct regulator *vpcie0v9; // 0.9V (可选)
// ===== GPIO =====
struct gpio_desc *ep_gpio; // PERST# --- 控制 EP 设备复位
// ===== 配置信息 =====
u32 lanes; // 通道数 (1/2/4)
u8 root_bus_nr; // 根总线编号 (通常为 0)
struct device *dev; // 所属 device
struct irq_domain *irq_domain; // INTx 中断域
};
学习要点:
reg_basevsapb_base:两个基址分别对应 AXI 和 APB 总线,AXI 用于高速配置空间访问,APB 用于慢速控制寄存器的读写。- 7 个复位信号反映了硬件复杂度:核心、管理、PIPE、电源管理、两个时钟域各有独立复位。
- 3 个 regulator 都是 optional,部分硬件上这些电压域可能由 PMIC 常供电。
3. 寄存器布局与定义
3.1 Client 寄存器区 (基址 0x0)
这是面向"用户/驱动"的控制接口区。
| 偏移 | 寄存器名 | 功能 |
|---|---|---|
| 0x00 | CLIENT_CONFIG |
核心配置:使能、链路训练、ARI、通道数、RC模式、Gen2选择 |
| 0x48 | CLIENT_BASIC_STATUS1 |
链路状态(Link Up 标志位在 bit21:20) |
| 0x4c | CLIENT_INT_MASK |
中断屏蔽 |
| 0x50 | CLIENT_INT_STATUS |
中断状态 |
CLIENT_CONFIG 位域:
[0] - Enable : 控制器使能
[1] - Link Train Enable: 链路训练使能
[3] - ARI Enable : Alternative Routing-ID
[5:4]- Lane Num : 通道数编码 ((lane>>1)&3)<<4
[6] - Mode RC : 1=Root Complex, 0=Endpoint
[7] - Gen Select 2 : 1=Gen2(5GT/s), 0=Gen1(2.5GT/s)
HIWORD_UPDATE 机制(值得注意的设计):
c
#define HIWORD_UPDATE(mask, val) (((mask) << 16) | (val))
#define HIWORD_UPDATE_BIT(val) HIWORD_UPDATE(val, val)
高 16 位是写掩码,低 16 位是实际值。一次 32 位写入即可实现 "读-改-写" 的原子操作,无需额外的锁保护。
3.2 Core Control / Management 寄存器区 (基址 0x900000)
| 偏移 | 寄存器名 | 功能 |
|---|---|---|
| 0x000 | CORE_CTRL |
链路速度/宽度状态(只读/状态) |
| 0x004 | CORE_CTRL_PLC1 |
FTS (Fast Training Sequence) 计数配置 |
| 0x020 | TXCREDIT_CFG1 |
发送信用更新间隔 |
| 0x20c | CORE_INT_STATUS |
核心中断状态 |
| 0x210 | CORE_INT_MASK |
核心中断屏蔽 |
3.3 RC 配置空间 (基址 0x800000 / 0xa00000)
| 偏移 | 寄存器名 | 功能 |
|---|---|---|
| 0xa00000 | RC_CONFIG_VENDOR |
Vendor ID (写为 0x1d87 --- Rockchip) |
| 0xa00008 | RC_CONFIG_RID_CCR |
Revision ID + Class Code (PCI Bridge) |
| 0xa000d0 | RC_CONFIG_LCS |
Link Control/Status(链路重训练、带宽管理中断) |
| 0x800000 | RC_CONFIG_NORMAL_BASE |
RC 自身 Type0 配置空间基址 |
| 0xc00000 | AXI_CONF_BASE |
Outbound ATU 区域 |
| 0xc00800 | AXI_INBOUND_BASE |
Inbound ATU 区域 |
3.4 ECAM 地址计算
c
#define PCIE_ECAM_ADDR(bus, dev, func, reg) \
(PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEV(dev) | \
PCIE_ECAM_FUNC(func) | PCIE_ECAM_REG(reg))
// bus<<20 | dev<<15 | func<<12 | reg<<0
ECAM 将 PCIe 的 BDF (Bus/Device/Function) + Register 映射到 MMIO 地址空间,这是 PCIe 的标准做法。
4. 配置空间访问
4.1 读写体系
pci_ops {
.read = rockchip_pcie_rd_conf
.write = rockchip_pcie_wr_conf
}
│
├── 判断设备是否有效 (rockchip_pcie_valid_device)
│ - bus == root_bus → 只允许 dev=0(只有一个slot)
│ - bus->primary == root_bus → 只允许 dev=0(直连设备)
│
├── bus == root_bus ?
│ YES → rd_own_conf / wr_own_conf (Type0 --- RC 自身)
│ NO → rd_other_conf / wr_other_conf (Type1 --- 下游设备 ECAM)
│
└── 返回 PCIBIOS_SUCCESSFUL / PCIBIOS_DEVICE_NOT_FOUND
4.2 关键区别
| rd_own_conf | rd_other_conf | |
|---|---|---|
| 基址 | apb_base + 0x800000 |
reg_base + ECAM_ADDR |
| 对象 | RC 自身 Type0 配置头 | 下游设备 Type0/Type1 配置头 |
| 范围 | 仅 root bus | 非 root bus |
4.3 重要限制
驱动在 probe 末尾有一行警告:
only 32-bit config accesses supported; smaller writes may corrupt adjacent RW1C fields
这意味着:写配置空间时,非 4 字节对齐的小写入会先读 4 字节、修改、再写回,可能损坏相邻的 RW1C (Read-Write-1-to-Clear) 位。这是该硬件的一个已知限制。
5. 设备树解析 ------ 硬件资源获取
函数 rockchip_pcie_parse_dt() 是驱动中"时间优先级最高"的部分,在 probe 中第一个被调用。
获取的资源清单
Category | Resource Name | Method
─────────────────────────────────────────────────────
寄存器映射 | "axi-base" | devm_ioremap_resource
| "apb-base" | devm_ioremap_resource
─────────────────────────────────────────────────────
PHY | "pcie-phy" | devm_phy_get
─────────────────────────────────────────────────────
复位 (7个) | core, mgmt, | devm_reset_control_get
| mgmt-sticky, |
| pipe, pm, pclk, |
| aclk |
─────────────────────────────────────────────────────
GPIO | "ep" (PERST#) | devm_gpiod_get (默认 HIGH)
─────────────────────────────────────────────────────
时钟 (4个) | aclk, aclk-perf, | devm_clk_get
| hclk, pm |
─────────────────────────────────────────────────────
中断 (3条线) | "sys" | devm_request_irq (子系统/错误)
| "legacy" | irq_set_chained_handler (INTx)
| "client" | devm_request_irq (逻辑层/事件)
─────────────────────────────────────────────────────
电源 (3个, 可选) | vpcie3v3, | devm_regulator_get_optional
| vpcie1v8, |
| vpcie0v9 |
─────────────────────────────────────────────────────
通道数 | "num-lanes" | of_property_read_u32 (默认1)
EPROBE_DEFER 的处理
多处使用了此模式:
c
if (PTR_ERR(phy) != -EPROBE_DEFER)
dev_err(dev, "missing phy\n");
return PTR_ERR(phy);
含义: 如果依赖的资源驱动(如 PHY、复位控制器)尚未加载,返回 -EPROBE_DEFER 通知内核稍后重新尝试 probe,且不打印错误(因为这不是真正的错误,只是时序问题)。
三条中断线的职责
"sys" → 控制器内部错误/事件(FIFO溢出、TLP错误、PHY错误等)
"legacy" → 下游设备的 INTx (INTA~INTD) 传统中断 ← 级联中断处理
"client" → 高层逻辑事件(热复位、DPA、错误上报、PHY变化等)
6. 硬件初始化流程
rockchip_pcie_init_port() 是核心初始化函数,流程如下:
Step-by-Step
1. GPIO: PERST# 拉低 (复位 EP)
│
2. 复位序列 assert:
aclk_rst → pclk_rst → pm_rst
│
▼
3. udelay(10) ← 等 10μs 确保复位生效
│
4. 复位序列 deassert (按特定顺序!):
pm_rst → aclk_rst → pclk_rst
│
5. phy_init() ← 初始化 PHY
│
6. 核心复位 assert:
core_rst → mgmt_rst → mgmt_sticky_rst → pipe_rst
│
7. 写 CLIENT_CONFIG:
Enable | LinkTrain | ARI | LaneNum | ModeRC | Gen2
│
8. phy_power_on() ← PHY 上电
│
9. 核心复位 deassert (严格顺序!):
mgmt_sticky_rst → core_rst → mgmt_rst → pipe_rst
│
10. 工作区 (Controller bug workaround):
读写 L1_SUBSTATE_CTRL2 → 配置 FTS count
│
11. 使能 Gen1 链路训练
│
12. 轮询等 Link Up (每 20ms, 超时 500ms):
检查 BASIC_STATUS1[21:20] == 2'b11 → 成功
│
13. 触发 Gen2 重训练 (写 LCS.Retrain_Link)
│
14. 轮询等 Gen2 (每 20ms, 超时 500ms):
检查 CORE_CTRL[4:3] → 若超时则回退 Gen1
│
15. 读最终链路宽度 (x1/x2/x4)
│
16. 写 RC 配置头:
Vendor ID → Class Code → BAR → ATU Region0
复位顺序的重要性
/* 注释原文:Please don't reorder the deassert sequence */
复位解除 (deassert) 的顺序为:
mgmt_sticky_rst → core_rst → mgmt_rst → pipe_rst
这是由硬件设计决定的------内部状态机要求先释放粘性复位,再依次释放核心、管理、PIPE。随意调整顺序会导致控制器无法正常工作。
链路训练两阶段
Gen1 训练 (必须成功) Gen2 训练 (可降级)
┌──────────────────┐ ┌──────────────────┐
│ 2.5 GT/s 建立链接 │ ───► │ 尝试升级到 5 GT/s │
│ 超时则整体失败 │ │ 超时 → 保持 Gen1 │
│ │ │ │ │
│ 500ms timeout │ │ 500ms timeout │
└──────────────────┘ └──────────────────┘
7. 中断处理体系
三层中断结构
硬件中断线
│
├─ "sys" ──► rockchip_pcie_subsys_irq_handler()
│ ├─ INT_LOCAL: 检查 CORE_INT_STATUS
│ │ ├─ PRFPE/CRFPE/RRPE → FIFO RAM 奇偶校验错
│ │ ├─ PRFO/CRFO → FIFO 溢出
│ │ ├─ RT/RTR → 重放超时
│ │ ├─ PE → PHY 接收错误
│ │ ├─ MTR/UCR/FCE → TLP 畸形/流控错
│ │ ├─ CT → 请求完成超时
│ │ ├─ UTC → TC 映射错误
│ │ └─ MMVC → MSI 掩码变化
│ └─ INT_PHY: PHY 链路变化
│ └─ 更新 Tx Credit + 清除带宽中断
│
├─ "legacy" ─► rockchip_pcie_legacy_int_handler() [chained]
│ ├─ chained_irq_enter/exit
│ ├─ 读 INT_STATUS[8:5] (INTA~INTD)
│ ├─ 用 ffs() 逐位查找
│ └─ irq_find_mapping → generic_handle_irq
│
└─ "client" ─► rockchip_pcie_client_irq_handler()
├─ LEGACY_DONE / MSG → 传统/MSI 完成
├─ HOT_RST → 热复位
├─ DPA → 动态功率分配
├─ FATAL_ERR / NFATAL_ERR → AER 致命/非致命错
├─ CORR_ERR → 可纠正错误
└─ PHY → PHY 事件
级联中断 (Chained IRQ)
c
irq_set_chained_handler_and_data(irq, rockchip_pcie_legacy_int_handler, rockchip);
这告诉内核:当 "legacy" 中断触发时,先调用 rockchip_pcie_legacy_int_handler,它负责:
- 读取状态寄存器找到具体是哪根 INTx
- 通过
irq_find_mapping将硬件中断号映射到 Linux IRQ 号 - 调用
generic_handle_irq分发给对应的设备驱动
中断使能
c
rockchip_pcie_enable_interrupts():
1. CLIENT_INT_MASK → 只使能 CLI (Correctable/Fatal/Non-Fatal/DPA/HotRst/MSG/Legacy/PHY)
2. CORE_INT_MASK → 使能所有核心中断
3. 使能带宽变化中断 (LCS.LBMIE | LCS.LABIE)
8. ATU 地址转换单元
ATU (Address Translation Unit) 是 PCIe 控制器的关键模块,负责 CPU AXI 地址和 PCIe 总线地址之间的转换。
Outbound ATU (CPU → PCIe)
c
rockchip_pcie_prog_ob_atu(rockchip, region_no, type, num_pass_bits, lower_addr, upper_addr);
作用: CPU 发出的 AXI 读写请求 → 转换为 PCIe 总线上的 TLP 包。
参数说明:
region_no: 区域编号 (1~32),Region 0 保留给默认映射type:AXI_WRAPPER_MEM_WRITE(0x2)或AXI_WRAPPER_IO_WRITE(0x6)num_pass_bits: 地址直通位数(决定地址窗口大小),20-1表示 1MBlower_addr / upper_addr: PCI 总线侧的物理地址
硬件寄存器布局 (每个 Region 4个寄存器):
ADDR0: [5:0]=num_pass_bits, [31:8]=lower_addr[31:8]
ADDR1: upper_addr (高32位)
DESC0: [23]=enable, [2:0]=type
DESC1: 保留 (写0)
Inbound ATU (PCIe → CPU)
c
rockchip_pcie_prog_ib_atu(rockchip, region_no, num_pass_bits, lower_addr, upper_addr);
作用: 下游 EP 设备的 DMA 请求 → 转换为 AXI 地址,访问系统内存。
当前配置:
c
rockchip_pcie_prog_ib_atu(rockchip, 2, 32 - 1, 0x0, 0);
// region=2, 直通32位, CPU端地址=0x0
表示允许 EP 通过 DMA 访问整个低 4GB 的 CPU 内存空间。
地址映射图解
Outbound (CPU → PCIe)
┌─────────────────┐ ┌─────────────────┐
│ CPU AXI Address │────────►│ PCIe Mem Space │
│ 0xFA000000 │ ATU │ 0x00000000 │
│ 窗口: 1MB×N │ │ (总线侧地址) │
└─────────────────┘ └─────────────────┘
Inbound (PCIe → CPU)
┌─────────────────┐ ┌─────────────────┐
│ EP DMA Request │────────►│ System DRAM │
│ (PCIe TLP) │ ATU │ 0x00000000 │
│ │ │ (4GB 窗口) │
└─────────────────┘ └─────────────────┘
9. Probe 主流程
rockchip_pcie_probe() 是驱动入口,完整流程:
Probe 被调用 (设备树匹配 "rockchip,rk3399-pcie")
│
├─ 1. 解析 DT (rockchip_pcie_parse_dt)
│ 获取:寄存器、PHY、复位、GPIO、时钟、中断、电源、通道数
│
├─ 2. 使能时钟
│ aclk_pcie → aclk_perf_pcie → hclk_pcie → clk_pcie_pm
│
├─ 3. 使能电源 (rockchip_pcie_set_vpcie)
│ vpcie3v3 → vpcie1v8 → vpcie0v9 (链式回滚)
│
├─ 4. 初始化端口 (rockchip_pcie_init_port)
│ 复位序列 → PHY初始化 → Gen1训练 → Gen2训练 → RC配置
│
├─ 5. 使能中断 (rockchip_pcie_enable_interrupts)
│
├─ 6. 初始化 INTx IRQ Domain
│ 创建线性中断域 (irq_domain_add_linear, 大小=4)
│
├─ 7. 获取 Host Bridge 资源 (of_pci_get_host_bridge_resources)
│ 解析 DT 中 "ranges" 属性 → I/O / MEM / BUS 三种资源
│
├─ 8. 申请总线资源 (devm_request_pci_bus_resources)
│
├─ 9. 遍历资源链表
│ IORESOURCE_IO → pci_remap_iospace, 记录 io_bus_addr
│ IORESOURCE_MEM → 记录 mem_bus_addr, mem_size
│ IORESOURCE_BUS → 记录 root_bus_nr
│
├─ 10. 配置 Outbound ATU (MEM 窗口)
│ 按 1MB 步长循环配置 (非 Region 0)
│
├─ 11. 配置 Inbound ATU (DMA 窗口)
│ Region 2, 32-bit 直通, CPU地址=0
│
├─ 12. 配置 Outbound ATU (I/O 窗口)
│ Offset 偏移防止覆盖 MEM 区域
│
├─ 13. 扫描 PCI 总线 (pci_scan_root_bus)
│ 这是最核心的一步:枚举所有 PCIe 设备
│
├─ 14. 资源分配与配置
│ pci_bus_size_bridges → pci_bus_assign_resources
│ → pcie_bus_configure_settings (每个子总线)
│
├─ 15. 添加设备 (pci_bus_add_devices)
│ 触发设备驱动 probe,设备可用
│
└─ 16. 打印硬件限制警告 (32-bit only)
错误回滚路径
err_vpcie:
disable vpcie0v9 → vpcie1v8 → vpcie3v3
err_set_vpcie:
clk_disable_unprepare(pm)
err_pcie_pm:
clk_disable_unprepare(hclk)
err_hclk_pcie:
clk_disable_unprepare(aclk_perf)
err_aclk_perf_pcie:
clk_disable_unprepare(aclk)
err_aclk_pcie:
return err;
遵循 "先开后关,后开先关" 的原则。
10. 面试高频考点
Q1: ECAM 是什么?本驱动中如何实现?
ECAM (Enhanced Configuration Access Mechanism) 是 PCIe 规范定义的标准配置空间访问方式。每个 BDF (Bus/Device/Function) 占用 4KB MMIO 空间,公式为:
Address = Base + (Bus<<20 | Dev<<15 | Func<<12 | Reg<<0)
本驱动中 PCIE_ECAM_ADDR 宏实现了这个映射。
Q2: ATU 的作用是什么?Outbound 和 Inbound 有什么区别?
- Outbound ATU: CPU 访问 PCIe 总线地址空间(如设备 BAR 空间),将 CPU 物理地址翻译为 PCIe 总线地址
- Inbound ATU: PCIe 设备 DMA 访问系统内存,将 PCIe 总线地址翻译为 CPU 物理地址
Q3: 链路训练 (Link Training) 的过程是怎样的?
- 先以 Gen1 (2.5GT/s) 进行训练→成功则链路建立
- 再触发 Gen2 (5GT/s) 重训练→成功则升级,失败则保持 Gen1
- 最终协商出链路宽度 (x1/x2/x4) 和速度
Q4: 为什么需要 7 个独立的复位信号?
反映了 SoC 内部 PCIe 控制器的硬件分区设计:
- core/mgmt/mgmt-sticky: 控制器核心逻辑和管理模块需要分步复位
- pipe: PIPE 是连接 PHY 的标准接口,需要独立复位
- pm: 电源管理状态机独立复位
- aclk/pclk: 不同时钟域需要各自的复位,避免跨时钟域问题
Q5: EPROBE_DEFER 是什么?为什么在这个驱动中反复出现?
Linux 内核的设备模型机制。当驱动依赖的资源(如 PHY、复位控制器、时钟)尚未准备好时,返回 -EPROBE_DEFER 告诉设备模型"稍后重新调用我的 probe"。这个驱动依赖大量资源,因此对每个资源都需要处理这种情况。
Q6: 为什么寄存器写入使用 HIWORD_UPDATE 宏?
c
#define HIWORD_UPDATE(mask, val) (((mask) << 16) | (val))
这是一种硬件支持的原子读-改-写机制。高 16 位作为字节掩码指示要修改哪些低 16 位,一次 32 位写入即可修改特定位段,无需软件层面的 read-modify-write 序列,避免了竞态条件。
Q7: 传统中断 (INTx) 和 MSI 中断的区别?
- INTx (Legacy): 基于物理引脚的边带信号 (INTA~INTD),4 条共享线,通过级联中断处理
- MSI (Message Signaled Interrupt): 通过 PCIe 带内 TLP 报文发送中断,无需额外引脚,支持更多中断向量
本驱动通过 irq_domain + chained_irq 实现了对 INTx 的支持。
Q8: devm_* 系列函数有什么优势?
devm_ (device managed) 函数会自动管理资源的生命周期:
- probe 失败时自动释放所有已申请的资源
- driver remove 时自动释放
- 减少手动错误处理代码
本驱动大量使用 devm_kzalloc、devm_ioremap_resource、devm_request_irq 等。
附录:关键常量速查表
| 常量 | 值 | 含义 |
|---|---|---|
ROCKCHIP_VENDOR_ID |
0x1d87 | Rockchip 的 PCI-SIG Vendor ID |
AXI_REGION_SIZE |
1MB | 单个 ATU region 大小 |
AXI_REGION_0_SIZE |
32MB | Region 0 的大小 |
MAX_AXI_WRAPPER_REGION_NUM |
33 | 最大 OB ATU Region 数 |
MAX_AXI_IB_ROOTPORT_REGION_NUM |
3 | 最大 IB ATU Region 数 |
RC_REGION_0_PASS_BITS |
24 | Region 0 的地址直通位 |
| Link Training Timeout | 500ms | Gen1/Gen2 各自的超时时间 |
| Training Poll Interval | 20ms | 链路状态轮询间隔 |