2198806_zh-CN

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

2198806_zh-CN

2198806_zh-CN

揭开Linux中PCIe和CPU地址空间转换的神秘面纱

lspci output on iMX95EVK as PCIe RClspci output on iMX95EVK as PCIe RCiMX95EVK作为PCIe RC时的lspci输出

请仔细观察上述片段。它取自iMX95的控制台,在对特定PCIe设备[iMX8MM即PCIe EP]执行“lspci”后,此设备已枚举为BDF[总线设备功能]01:00.0。
本博客试图揭开围绕
lspci输出内存地址信息的神秘面纱。
我们将探讨这一地址的定义、用途及其在PCIe领域的重要性。本博客将重点讨论以下议题:-

1. Linux设备树中的PCIe对应关系
2. 什么是CPU和PCIe地址空间,以及地址空间转换的必要性?
3. 
在Linux中为PCIe设备分配资源
4. 如何在Linux PCI子系统中进行地址空间转换?


 

Linux设备树中的PCIe对应关系


在Linux设备树中,PCIe对应关系可定义PCIe根复合体和端点在系统中的位置。设备树中的PCIe父节点代表PCIe控制器(根复合体/主机桥)。


参考imx95设备树源中的PCIe节点:-

pcie@4c300000 {

                        compatible = "fsl,imx95-pcie";

reg = <0x00 0x4c300000 0x00 0x10000 0x00 0x4c360000 0x00 0x20000 0x00 0x60100000 0x00 0xfe00000>;

                        reg-names = "dbi\0atu\0config";

                        #address-cells = <0x03>

  …


 }


pcie@4c300000代表Designware PCIe控制器根复合体,它将作为与其连接的设备/桥的父节点。

-- “compatible”属性则用于标识特定的PCIe控制器。其对应的驱动程序位于
drivers/pci/controller/dwc/pci-imx6.c

-- “reg”属性可指定PCIe控制器的内存映射寄存器。



PCIe RC下的子节点代表PCIe总线上的设备。它们可以是固定功能设备,例如Wi-fi、以太网、NVMe,也可以是PCIe桥,后者可进一步连接设备。

参考“
arch/arm64/boot/dts/freescale/imx95.dtsi”

pcie_4ca00000: pcie@4ca00000 {

compatible = "pci-host-ecam-generic";

reg = <0x0 0x4ca00000 0x0 0x100000>;

                        /* 必须为3。*/

             …
             …

             enetc_port0: ethernet@0,0 {

兼容 = "fsl,imx95-enetc";

reg = <0x000000 0 0 0 0>;

clocks = <&scmi_clk IMX95_CLK_ENET>,

                                         <&scmi_clk IMX95_CLK_ENETREF>;

                                clock-names = "ipg_clk", "enet_ref_clk";

nvmem-cells = <&eth_mac0>;

                                nvmem-cell-names = "mac-address";

状态 = "disabled";

                        };

}
 
ethernet@0,0是位于
总线0、设备0、功能0的PCIe设备。它是PCIe RC的子设备,内存映射位置为0x4ca00000

这些子设备/桥既可以借助PCI枚举动态发现,也可以在设备树中静态描述,如上文中设备树片段所示,其中“ethernet@0,0”条目以静态方式向RC声明与其连接的以太网子设备。如上例所示,这些子节点嵌套在设备树的PCI父节点中。


 
什么是CPU和PCIe地址空间,以及地址空间转换的必要性?


CPU地址空间是处理器所能查看的系统物理内存映射。
Cortex-A55在iMX95上查看的CPU物理地址空间示例:

 

起始地址      结束地址    模块

0x48000000       0x4812FFFF    GIC编程寄存器

0x4AA00000      0x4AAFFFFF    Neutron SRAM

0x4AC10000      0x4AC1FFFF    摄像头域块控制
0x4E080000       0x4E08FFFF    DDR控制器

此地址空间是由系统固件/操作系统管理的全局系统视图。这些地址均由硬件设计固定。


另一方面,PCIe地址空间是PCI总线的本地空间,由PCIe子系统管理。该空间中的地址采用动态分配模式。

PCIe地址空间的示例如下:-

0x00000000   -    0x0FFFFFFF

0x10000000-0x1FFFFFFF

0x20000000   -    0x2FFFFFFF

由上文说明可知,CPU和PCIe地址空间运行在各自独立的地址域中。因此,除非部署转换机制,否则CPU无法访问PCIe设备的地址空间。我们将在后续章节中讨论这一内容,但请花几分钟时间来思考以下问题:

问:为什么CPU和PCIe需要独立的地址空间?

答:主要考量因素之一是模块化。我们采用独立的空间设计,确保PCIe设备独立于CPU架构进行设计。同一张卡可以在不同的系统中运行。CPU可以根据需要随时重新映射PCIe空间,从而始终保持灵活性。
此外,不同的地址空间会阻止设备访问任意系统内存。


根据本节的讨论内容,PCIe地址空间显然与CPU地址空间存在本质差异,而事实上——这种差异恰恰赋予其独特优势。因此,我们需要一个能在地址空间之间实现双向转换的实体。

接下来,我们将介绍“
iATU”,即内部地址转换单元。在iMX SoC中,这些硬件单元负责执行地址转换。它们是Synopsys DesignWare PCIe控制器的组成部分,可为入站和出站事务提供可编程的地址转换窗口。

对于不熟悉PCIe入站和出站事务的读者,请花些时间阅读这篇技术博客 -

通过用例了解PCIe出站/入站窗口 - 恩智浦网络社区

注意:地址转换仅确保CPU可访问PCIe设备的内存,反之亦然。


到目前为止,读者应对PCIe地址转换形成基本认知。在深入研究Linux PCI子系统中的转换操作之前,让我们先讨论一下如何为PCIe设备分配资源。
 

在Linux中为PCIe设备分配资源

PCIe设备并未配置直接的CPU指令接口,因此其通过内存映射区域进行通信。设备需要内存来执行DMA操作或MSI/MSIX中断。不同的设备有不同的需求,因此PCIe中的资源可以是映射设备寄存器的MMIO,也可以是DMA传输所需的内存区域。

在Linux中,PCI子系统的pci_assign_resource函数负责在系统初始化期间为PCIe设备分配IO和内存资源,此操作将在枚举PCIe设备后执行。PCI总线上的所有设备均可调用此函数,并根据PCI设备的资源需求进行分配。

但是,Linux中的PCI子系统如何确定PCIe设备需要什么资源?

- 每台PCIe设备都设有由PCIe规范定义的配置空间。这包括
 

BAR[基地址寄存器] - 用于指示设备所需资源的类型[IO/Mem]和规模。

功能 - 用于广播设备功能,例如MSI中断、ASPM低功耗状态等。

通过从PCIe设备读取BAR,我们可以了解设备所需资源的类型和规模。

// 从PCIe设备的BAR中确定资源规模:-

步骤-1:将所有1写入目标BAR寄存器。

步骤-2:读回该值并清除低4位(对于内存BAR)或2位(对于I/O BAR),因为这些状态位并不参与大小的计算


步骤-3:对该值执行按位取反后加1。

步骤-4:返回值表示大小。

举例说明:-

假设在读回上述步骤-2中的值后,BAR返回0xFFFFF000。低4位已清零。
步骤-3:对该值执行按位取反 -> ~(0xFFFFF000) = 0x00000FFF
加1:0x00000FFF + 1 = 0x00001000

得到的值0x1000 = 4096字节表示大小,即BAR需要4KB的内存区域。

// 从PCIe设备的BAR中确定资源类型:-
 

PCI 配置空间中的基地址寄存器(BAR):

  • 位 0 → 资源类型:
    • 1 = I/O 空间
    • 0 = 内存空间
  • 对于内存BAR:
    • 00 = 32位
    • 10 = 64 位
    • 位1–2 → 寻址类型:
    • 位 3 → 可预取标志

解释值0xFFFFF000,我们得到:

 

  • 位 0 = 0 → 内存空间
  • 位 1–2 = 00 → 32位地址
  • 位 3 = 0 → 可预取
  • 高位 → 基址(屏蔽后)

 

pci_read_bases [drivers/pci/probe.c]在Linux PCI子系统中负责确定设备枚举期间的BAR内存大小和类型需求。



毋庸置疑,上述向端点的BAR写入数据并确定资源类型与规模的操作流程,均在PCIe RC上执行。我们的设置如下:-

iMX95<------>
iMX8MM[RC]                     [EP]


在PCIe RC确定所需的BAR大小后,
pci_assign_resource函数会分配一个内存范围,然后设置从该内存范围向PCIe地址空间的转换。


我们以一个lspci日志片段为切入点来撰写这篇博客文章,具体片段如下:-

2.png

综上所述,请注意RC驱动程序已分配以下资源:

0x910100000 - 0x910110000作为不可预取的内存地址范围,大小=64KB

上述内存地址范围位于iMX95 SoC上映射的PCIe 1出站空间内存中:-
 
3.png

范围0x910100000 - 0x910110000将映射到端点的PCIe地址空间。这实际上意味着,如果CPU生成此范围内的任何地址[包括起始和结束地址],PCIe控制器则会在RC上向总线中的端点发送PCIe TLP。它可能是对端点内存的读取/写入操作。写入/读取地址将根据地址空间转换来决定。

我们将在下一节详细探讨如何在Linux内核中实现这一转换。
 

在 Linux PCI 子系统中如何进行地址空间转换?
 


我们先从部分关键问题入手:-
如何指定0x910100000-0x910110000的范围?内核如何确认其必须映射PCIe 1出站空间,而非PCIe2出站空间或其他地址空间?

-- 正如Linux 中的所有优秀设计,这同样始于“设备树二进制”。Uboot会将dtb传递给内核,以便其获取我们的板硬件描述。由于我们使用Torradex的Verdin iMX95 EVK板作为根复合体,因此我们使用的dtb为

imx95-19x19-verdin-adv7535.dtb

我会附上可用的dtb,供读者按需使用。

该dtb包括 -arch/arm64/boot/dts/freescale/imx95.dtsi

让我们重点关注特定的pcie节点:-
 

4.png

“ranges”属性正是本节先前所提问题的答案。

 - 此属性可定义父地址空间和子PCI地址空间之间的地址转换规则。

 

注意:此博客仅关注“ranges”属性,因为它与我们的讨论内容相关。因此,如果读者想了解PCIe节点的其他设备树属性,建议查阅其他资料。

 让我们解码
“ranges”属性:

具体格式如下:-



     3个单元               2个单元             2个单元
            


因此,一个条目将配置7个单元。我们的dtsi设有2个条目。第一个条目用于IO空间转换,第二个条目则用于内存空间转换。请参考第二个条目:-

 

0x82000000 0x0 0x10000000 0x9 0x10000000 0 0x10000000

|------PCI地址---------------| |-CPU地址-| |---PCI大小---|

 

根据上文,我们获得以下信息:-

内存空间可预取

<

  0x82000000 0x00 0x10000000 // PCIe 地址:0x10000000

  0x09 0x10000000              // CPU/系统地址:0x910000000

0x00 0x10000000 // 大小:256MB

>;


0x82000000 = 1000 0010 0000 0000 0000 0000 0000 0000

 

  • 位 31-30 (10) → 配置空间类型:这表示内存空间。
  • 位29(0) → 不可重定位
  • 位 28 (1) → 可预取 = 否(0 表示不可预取)
  • 位 27–24 (0010) → 地址空间类型 = 内存

因此,0x82000000表示:

  • PCI 内存空间
  • 不可预取
  • 32 位地址空间

注意:- 对于想了解lspci输出为何显示[size=64K]而dts256 MB的人,这是因为256MB是PCIe设备可用的最大地址空间。端点设备根据自身所需地址空间的大小,向系统申请分配。



同样,IO空间转换也是根据“ranges”中的第一个条目创建的:-

<

0x81000000 0x00 0x00 → PCI I/O 地址:0x00000000

0x00 0x6ff00000 → CPU/系统地址:0x6ff00000

0x00 0x100000 → 大小:1MB

>;
 
我们在iMX95 Verdin EVK Linux控制台的dmesg输出中也观察到类似情况:-
 
5.png

因此,内存空间映射已从

CPU地址0x910000000 - 0x091fffffff转换为PCIe地址0x10000000 - 0x1fffffff

我们有必要说明使用“ranges”属性的驱动程序。“ranges”属性在“pci_parse_request_of_pci_ranges -> devm_of_pci_get_host_bridge_resources”中进行解析,具体路径是“drivers/pci/of.c”



每个范围中的devm_of_pci_get_host_bridge_resources会自动管理这些资源的内存分配。它能确保在设备断开连接或驱动程序移除时释放这些资源。


我们已确定CPU和PCI地址范围的定义和原因。但是在lspci中,您观察到的是
0x910100000,而非0x910000000,后者才是根据dtb指定的起始范围。这是为什么呢?

要回答这个问题,我们需要回溯PCIe设备枚举过程。
如上文所述,在PCIe枚举过程中,Linux PCI驱动程序会确定BAR资源,随后PCI核心驱动程序可能会遵循对齐要求分配地址。正因如此,系统会为EP的BAR0分配PCI总线地址
0x10100000,且与0x10000000存在1MB[0x100000]的偏移量。

同时,您还需关注设备树PCI转换窗口:-

0x10100000转换为0x910100000

这一转换并非自动完成。设备树二进制仅指定转换窗口的具体信息,例如目标CPU地址空间和源PCI 地址空间之间的转换关系。实际转换是由iATU完成的。

此操作可在drivers/pci/controller/dwc/pcie-designware-host.c的
dw_pcie_iatu_setup函数中完成,具体方式是使用dw_pcie_prog_outbound_atu函数创建出站窗口。

RC上的转换已成功配置,但部分环节仍需完善。
..
..

入站窗口!!

如果端点(如iMX8MM)上并未配置入站窗口,对0x910100000的读取/写入操作将毫无意义。我们在iMX8MM上使用的是PCI端点测试驱动程序,该驱动程序是Linux社区中的热门选择。如果读者需要了解更多信息,建议访问此页面:-
9. PCI端点框架 — Linux内核文档

pci_epc_map_addr函数(位于drivers/pci/endpoint/pci-epc-core.c中)通过将PCI地址[0x10100000]映射到EP内存中的物理地址来创建入站窗口。读取和写入操作就是通过这种方式进行的。

如果未配置入站窗口,进行读取操作时将出现以下情况:-
 

6.png

现在,一切准备就绪。转换窗口已在PCI驱动程序中配置,且处于Linux控制台环境中。当CPU发起内存读取请求时,将按以下顺序执行:-
 
7.png

对于内存写入操作:-

8.png

端点上会出现以下情况:-
9.png

最妙的是,整个转换过程由硬件以透明的方式完成 - 您的驱动程序只需读取/写入CPU地址,PCI主机控制器就会自动处理所有转换!



—— 我们如何测试地址映射?
 

要测试读取和写入操作,我们可以针对驱动程序本身作出一些改动,或者使用devmem5用户空间二进制。我们将对iMX8MM进行驱动程序方面的细微修改,并在RC上使用devmem5。

iMX8MM是PCIe端点,我们使用端点测试驱动程序对其进行相应配置。如果您需要实现相同配置,请参照此博客的指引操作 -

在iMX95 torradex板和iMX8MM EVK上启用PCIe端点框架 - 恩智浦网络社区

相反,如果您想将iMX95设为RC并将iMX8MM设为端点,请随时关注此博客 - 如何将iMX95EVK配置为PCIe端点并使用PCIe端点测试框架进行测试 - 恩智浦网络社区


接下来,我们需要完成两个任务:-

1. 在iMX8MM EP中,我们将写入部分随机值

在drivers/pci/endpoint/pci-epf-core.c中,对pci_epf_alloc_space函数进行以下修改:-
 

 10.png

“space”是虚拟地址,“phys_addr”是连续的物理地址。

请注意,这是测试转换的初步方式,还有更合适的方案。

在修改后编译内核并用其启动开发板。使用PCI端点测试框架将iMX8MM设为端点。



2. 在iMX95 Verdin EVK [PCIe RC]上,我们将使用devmem5读取地址0x910100000,以验证能否在RC上观测到相同的数据。
 
11.png

以上就是本期的全部内容。此博客篇幅较长,如果您对其中的细节感到困惑,请随时通过私信或评论留言,我将尽力为您解答。

下次再见!
Gaurav Sharma



IMX95EVK
Tags (1)
No ratings
Version history
Last update:
‎11-22-2025 08:07 AM
Updated by: