ECAM(增强型配置访问机制)在 PCIe 控制器驱动程序中的映射和寻址

本文作者:AMD 工程师 Boreddy Padmini,文章来源:Xilinx开发者社区

简介

PCIe 控制器驱动程序 (pcie-xilinx-cpm.c) 遵循 ECAM(增强型配置访问机制)标准来访问器件的 PCIe 配置空间。ECAM 提供了一种标准化的方式,使用存储器映射 I/O 来访问 PCIe 器件的配置寄存器。

ECAM 将 PCIe 配置空间映射到存储器映射地址空间,允许软件访问器件配置寄存器。物理地址转换为存储器映射地址的一般结构的方式如下所述:

通常,在设备树(.dts 文件)的 PCIe 节点下的“reg” 属性中指定 ECAM 区域。在以下代码片段中,ECAM 基地址为 0x06_0000_0000, 其大小为 0x01000000 (16 MB)。

xilinx_cpm_pcie_probe

pcie-xilinx-cpm.c 中实现的探测函数负责通过执行地址转换和映射操作来初始化基于 ECAM 的配置空间。

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

整个过程分为六个不同的步骤,如下:

注释:专门针对此博客为了捕获 ECAM 功能,在现有驱动程序代码中添加了一些额外的 dev_info 语句。并在 VPK120 评估板上运行“Versal CPM5 PCIe Bridge Root Port design”CED 来捕获启动日志。

请参阅以下页面,了解有关“Versal CPM5 PCIe Bridge Root Port design”的更多详细信息:

https://xilinx.github.io/pcie-debug-kmap/pciedebug/build/html/docs/PS_PC...

步骤 1:解析设备树:xilinx_cpm_pcie_parse_dt

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

此函数是从“xilinx_cpm_pcie_probe”函数调用的。

此函数负责从设备树 (DT) 解析和提取硬件配置详细信息。它会执行一系列步骤来初始化 AMD CPM PCIe 驱动程序所需的资源和寄存器映射。该函数使用来自 DT 的数据来初始化 xilinx_cpm_pcie 结构体 (port) 的若干关键组成部分。这些组成部分包括操作 PCIe 控制器所需的存储器区域和配置窗口。

从设备树中获取 ECAM 资源:

从设备树中获取名为“cfg”的存储器资源,该资源对应于 PCIe 的 ECAM。在以下代码片段中以绿色高亮显示了此资源。


. 函数调用:platform_get_resource_byname() 从设备树中获取由名称“cfg”指定的存储器资源。

. 错误处理:如果未找到资源 (res),那么该函数会返回错误 (-ENXIO),表示资源缺失。

以下代码片段展示了相应的日志输出:

步骤 2:初始化 ECAM 区域

使用 pci_ecam_create 并将其分配给 port->cfg。如果此步骤失败,会返回错误。

. 函数调用:pci_ecam_create 会初始化 ECAM 区域。它接受以下几个参数:

dev:表示 PCIe 控制器的器件结构。

res:先前检索得到的资源,该资源定义了 ECAM 的存储器映射区域。

bus_range:此 ECAM 涵盖的 PCIe 总线号范围。

pci_generic_ecam_ops:包含用于访问 PCIe 器件的通用 ECAM 操作的结构。

. 错误处理:如果创建失败(例如,存储器不足或参数无效),那么该函数会通过 PTR_ERR(port -> cfg) 返回错误代码。

. 链接到驱动程序数据:如果成功,pci_ecam_create 返回的 pci_config_window 结构会被存储在 port->cfg 中。此结构充当访问配置空间的句柄。

** pci_ecam_create

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc... pci_ecam_create

返回指向 struct pci_config_window 的指针,表示所创建的配置窗口。如果出现错误,它会使用 ERR_PTR 返回错误指针。

此函数中的关键步骤解释如下:


1. 基本确认:函数首先检查“busr”范围是否有效 (busr->start > busr->end),如果范围无效,则退出并返回错误 (-EINVAL)。

2. 存储器分配:使用 kzalloc 为 struct pci_config_window 分配存储器。如果存储器分配失败,它会返回错误指针 (-ENOMEM)。

3. 总线移位计算:从 ops 设置 bus_shift 值。如未提供该值,则使用默认值 (PCIE_ECAM_BUS_SHIFT)。

4. cfg 结构的配置:为父器件、操作处理程序 (ops) 和总线资源 (busr) 初始化 cfg 的字段。

5. 总线范围调整:计算总线范围,确保不超过 ECAM 资源 (cfgres) 的最大大小。它会按需调整总线范围并使用 dev_warn 记录警告。

a. 所需的总线范围为 [bus 00-ff],即总线 0 到 255。

b. 调整后的总线范围变为 [bus 00-0f],即 ECAM 区域仅容纳总线 0 到 15。

c. ECAM 区域指定为 [mem 0x600000000-0x600ffffff]。即,ECAM 总大小 = 16 MB。

d. 每条总线所需的存储器:每条总线需要 ( 2^{bus_shift} ) 字节的存储器。

对于 bus_shift=20,每条总线所需的存储器为 1 MB。

总线最大数量 = 16 MB/1 MB = 16(0 到 15)

6. 存储器资源设置:配置 ECAM 区域的存储器资源 (cfg->res),将其标记为忙碌并命名为 (PCI ECAM)。

7. 资源冲突检查:使用 request_resource_conflict 检查与现有资源是否存在地址冲突。如果存在冲突,它会使用 dev_err 记录错误并退出。

8. PCI 配置空间映射:根据映射模式 (per_bus_mapping),它要么根据每条总线来分配映射 (cfg->winp),要么使用 pci_remap_cfgspace(即在 64 位系统上)映射整个 ECAM 区域。

9. 初始化:如果操作 (ops) 包含 init 函数,它会调用此函数进行进一步初始化。此步骤中的任何错误都会导致清理资源并退出。

10. 成功日志记录:使用 dev_info 记录 ECAM 的成功设置。

以下代码片段展示了相应的日志输出:

步骤 3:分配 ECAM 操作

bridge->ops = (struct pci_ops *)&pci_generic_ecam_ops.pci_ops;

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

1. 标准 PCI 操作“pci_generic_ecam_ops.pci_ops”:

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

这是 Linux PCI 子系统中实现 ECAM 访问方法的预定义结构。此结构包含用于标准 PCIe 配置访问操作的函数指针,例如:

. read:从 PCIe 器件的配置空间读取值。

. write:将值写入配置空间。

. add_bus:负责重映射特定总线的配置空间。

. remove_bus:从 ECAM 映射中移除 PCI 总线。

. map_bus:映射给定总线上的 PCI 器件的配置空间。

2. 链接主桥 (bridge->ops)

将 pci_host_bridge 结构的“bridge->ops”成员分配给 pci_generic_ecam_ops.pci_ops

此分配会将主桥绑定到 ECAM 方法,允许 Linux PCI 子系统执行下列操作:

. 发现 PCIe 器件。

. 枚举总线层级。

. 通过 ECAM 配置器件。

步骤 4:建立桥接系统数据

bridge->sysdata = port->cfg;

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

此步骤会将 ECAM 映射链接到桥接器,使内核的更高层能够通过存储器映射配置空间与 PCIe 器件进行交互。

1. 它会访问 (bridge->sysdata) 以检索存储在 port->cfg 中的平台特定的配置详细信息。

2. 对于 ECAM 相关操作(例如,pci_generic_ecam_ops 中的 read 和 write 函数):

. 从 sysdata 中提取 ECAM 区域的基地址和大小。

. 内核使用以下公式计算特定器件配置空间的物理地址:

Physical Address = Base Address + (Bus Number << 20) + (Device Number << 15) + (Function Number << 12) + Offset

其中“offset”表示正在访问的器件配置空间中的特定寄存器或字段。例如,供应商 ID 位于偏移地址 0x00,器件 ID 位于 0x02。

pci_ops ->map_bus 方法是在“pci_ecam_map_bus”函数中实现的。

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

步骤 5:探测 PCI 主桥

PCI 主桥的探测包括最终确定初始化过程,以便系统可以通过存储器映射配置空间 (ECAM) 与 PCIe 器件进行交互。

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

1. 调用 pci_host_probe():

此函数负责将 PCI 主桥寄存到内核。它会确保将桥接器正确识别为器件,并且内核可以开始扫描连接到桥接器的子器件(端点)。

2. 配置与初始化:

作为探测过程的一部分,该函数会使用 ECAM 访问该系统的存储器映射配置空间。它会检查提供的 ECAM 相关配置数据,例如:基地址、存储器区域大小和总线号范围。

3. 器件枚举:

一旦成功寄存主桥,该内核就会触发 PCI 总线枚举过程。在此步骤中,内核会扫描桥接器下游的所有潜在总线号和器件号。它会识别已连接的器件、分配地址,并对其进行初始化以供操作系统使用。

4. 成功案例:

如果一切按预期进行:

. ECAM 映射完成,使系统能够使用存储器映射配置空间来访问器件。

. 连接到桥接器的 PCIe 器件现已准备好进行通信。

5. 失败案例:

. 如果探测失败,该函数会记录错误消息以帮助诊断问题(例如,ECAM 设置问题或存储器区域不可访问)。

. 然后系统会执行清理任务,例如,释放为 ECAM 操作分配的存储器。

. 最后,函数退出并返回错误代码以指示失败。

ECAM 操作的使用方式示例:

假设内核需要读取 PCIe 器件的供应商 ID:

. ECAM 读操作会访问与器件配置空间对应的存储器映射区域。

. 该内核会使用总线号、器件号、功能号和偏移来计算配置空间的物理地址。

. pci_generic_ecam_ops.pci_ops->read() 函数会从该地址读取并返回值。

同样,在枚举过程中,内核会使用 ECAM 操作来读取所有器件的配置寄存器、对其进行初始化并分配资源。

从启动日志中捕获的以下代码片段展示了枚举过程中涉及的若干步骤:

在以上代码片段中,红色框中高亮显示的消息呈现了以下信息:

a. ECAM 基地址 = 0xffff800082000000(如步骤 2 中推导所得)。

b. 若 Bus=0、Device=0、Function=0 和 Register Offset = 0,相应的物理地址可按如下方式计算:

Mapped address/Physical Address = Base Address + (Bus Number << 20) + (Device Number << 15) + (Function Number << 12) + Offset

= 0xffff800082000000 + (0 << 20) + (0 << 15) + (0 << 12) + 0

寄存器 0x0 的映射地址/物理地址 = 0xffff800082000000

c. pci 0000:00:00.0: [10ee:b348] type 01 class 0x060400 ===> 读取某些寄存器的序列(如代码片段所示,访问的寄存器为:0x0, 0xE, 0x6, 0x34, 0x40, 0x60, 0x70, 0x72, 0x74, 0x7C, 0x8, 0x100, 0x0, 0x100)后,会识别出具有以下详细信息的根端口器件:

. 总线号:00h

. 器件号:00h

. 功能号:0h

. 供应商 ID:0x10EE

. 器件 ID:0xB348

. 类型:0x01

. 类别:0x060400

如以上代码片段所示,红色框中高亮显示的消息表明内核会扫描桥接器的所有下游器件号,通过访问偏移 0 处的器件/供应商 ID 寄存器来检测已连接的器件。

以上代码片段显示了一条消息,其中内核会扫描位于寄存器偏移 0 处的总线 1、器件 0 和功能 0,并检测到器件/供应商 ID = 144d:a808 的器件(我们的端点 SSD 器件)。

步骤 6:清理 ECAM 资源

https://gitenterprise.xilinx.com/Linux/linux-xlnx/blob/master/drivers/pc...

此代码定义了 pci_ecam_free 函数,该函数负责清理为 PCI ECAM 配置窗口分配的资源和存储器。

struct pci_config_window *cfg 表示与要释放的 PCI ECAM 区域关联的配置窗口。此结构包含有关 PCI 总线的资源、映射和配置的详细信息。

1. 检查 per_bus_mapping

该函数首先检查是否启用了 per_bus_mapping。这决定了是为总线创建了单独的配置空间映射 (winp),还是为整个 ECAM 区域使用了单一映射 (win)。

2. 释放每条总线的映射 (winp):如果 per_bus_mapping 已启用:

. 它会检查 cfg->winp(每条总线映射的阵列)是否存在。

. 使用 for 循环迭代整个阵列,覆盖 cfg->busr 定义的范围内的所有总线号。

. 对于每条已映射的总线,它会使用 iounmap(cfg->winp[i]) 取消映射虚拟地址。

. 所有总线都取消映射后,它会使用 kfree(cfg->winp) 释放为阵列分配的存储器。

3. 释放单一映射 (win):如果 per_bus_mapping 未启用:

. 检查 ECAM 区域的单一映射 (cfg->win) 是否存在。

. 使用 iounmap(cfg->win) 取消映射虚拟存储器。

4. 释放存储器资源 (cfg->res):

如果存储器资源 (cfg->res) 具有父资源(表示它是资源层级的一部分),则使用 release_resource(&cfg->res) 释放该资源。

5. 释放配置结构 (cfg):

最后,使用 kfree(cfg) 释放为 pci_config_window 结构本身分配的存储器。