Skip to main content

PCIe

介绍 PCIe 的功能和使用方法。

模块介绍

PCIe(Peripheral Component Interconnect Express) 是一种高速串行计算机扩展总线标准。采用高速串行点对点双通道高带宽传输,所连接设备独享通道带宽。

K3 平台提供 5 个 PCIe 控制器(Port A ~ Port E),配备 6 个独立 PHY,支持连接多种 PCIe 外设,包括 NVMe SSD、SATA 控制器、Wi-Fi 模块等。

注意: Port D 与 USB3 控制器共享同一个 PHY 硬件资源,无法同时启用。使用时需在设备树中根据实际需求选择启用 PCIe 或 USB3。

功能介绍

Linux PCIe 子系统框架由三部分组成:

  1. PCIe 核心

    • PCIe 总线枚举、资源分配、中断处理
    • PCIe 设备添加和删除
    • PCIe 设备驱动注册与注销
  2. PCIe 控制器驱动

    • 对 PCIe 主机控制器进行操作
  3. PCIe 设备驱动

    • PCIe 具体设备的驱动,如 GPU、NIC 和 NVMe 等

源码结构介绍

控制器驱动代码位于 drivers/pci/controller/dwc 目录下:

|-- pcie-designware.c #dwc pcie驱动公共代码
|-- pcie-designware-host.c #dwc pcie主机驱动代码
|-- pcie-spacemit-k1.c #k3 pcie控制器驱动
|-- spacemit_pcie_phy.c #k3 pcie phy驱动

关键特性

特性

特性特性说明
支持模式支持 RC 模式
支持协议和lane数支持 Gen3x1, Gen3x2, Gen3x4, Gen3x8
支持设备支持NVMe SSD、PCIe转SATA、PCIe 网卡和 PCIe 显卡

控制器与 PHY 对应关系

K3 平台 5 个 PCIe 控制器与 6 个 PHY 的对应关系如下:

控制器端口最大lane数可用 PHY说明
pcie0_rcPort Ax8phy0~phy5支持 x8/x4/x2 模式,可聚合多个 PHY
pcie1_rcPort Bx2phy1仅在 Port A 使用 x2 模式时可用
pcie2_rcPort Cx2phy2, phy3支持 x2(双PHY)或 x1 模式
pcie3_rcPort Dx1phy4与 USB3 共享 PHY,二选一
pcie4_rcPort Ex1phy5独立使用

性能参数

SSD 型号(容量)顺序读(MB/s)顺序写(MB/s)随机读(4K MB/s)随机写(4K MB/s)
Colorful CN600 128GB12801156358352
HS-SSD-C2000Pro 256G2150258321209
MAXIO MAP1202 512G34703138282325

数据基于 deb1 (K3) + PCIe Gen3x4 插槽,在 Buildroot 2024.12 + linux-6.18 SDK 版本上,使用下面的 fio 测试方法,随机场景均为 4K block。

测试方法

# 顺序读
fio --name=nvme_seq_read --filename=/mnt/nvme_vfat/fiotest.bin --size=1G --bs=1M --rw=read --ioengine=libaio --direct=1 --iodepth=32 --numjobs=1 --time_based --runtime=30 --group_reporting

# 顺序写
fio --name=nvme_seq_write --filename=/mnt/nvme_vfat/fiotest.bin --size=1G --bs=1M --rw=write --ioengine=libaio --direct=1 --iodepth=32 --numjobs=1 --time_based --runtime=30 --group_reporting

# 随机读(4K)
fio --name=nvme_rand_read --filename=/mnt/nvme_vfat/fiotest.bin --size=1G --bs=4k --rw=randread --ioengine=libaio --direct=1 --iodepth=32 --numjobs=1 --time_based --runtime=30 --group_reporting

# 随机写(4K)
fio --name=nvme_rand_write --filename=/mnt/nvme_vfat/fiotest.bin --size=1G --bs=4k --rw=randwrite --ioengine=libaio --direct=1 --iodepth=32 --numjobs=1 --time_based --runtime=30 --group_reporting

配置介绍

主要包括 驱动使能配置DTS 配置

CONFIG配置

CONFIG_PCI:为 PCI 和 PCIe 总线协议提供支持,默认情况,此选项为 Y

Device Drivers
PCI support (PCI [=y])

PCIE_SPACEMIT_K1:为 K3 PCIe 控制器驱动提供支持,默认情况下,此选项为 Y

Device Drivers
PCI support (PCI [=y])
PCI controller drivers
DesignWare-based PCIe controllers
SpacemiT K1 PCIe controller - Host Mode (PCIE_SPACEMIT_K1 [=y])

DTS 配置

配置空间分配

K3 的每个 PCIe Root Complex 都在 DTS 中声明了 4 类地址窗口:config 配置空间、I/O 空间、MEM non-prefetchable 空间和 MEM prefetchable 空间。各控制器在 linux-6.18/arch/riscv/boot/dts/spacemit/k3.dtsi 中的分配如下:

各控制器地址空间基地址:

控制器configI/O非预取 MEM可预取 MEM
Port A (pcie0_rc)0x11_0000_00000x11_0001_00000x11_0011_00000x18_0000_0000
Port B (pcie1_rc)0x11_8000_00000x11_8001_00000x11_8011_00000x16_0000_0000
Port C (pcie2_rc)0x12_0000_00000x12_0001_00000x12_0011_00000x15_0000_0000
Port D (pcie3_rc)0x12_8000_00000x12_8001_00000x12_8011_00000x14_0000_0000
Port E (pcie4_rc)0x12_c000_00000x12_c001_00000x12_c011_00000x13_0000_0000

各窗口大小:

窗口类型Port A / B / CPort D / E
config64 KiB64 KiB
I/O1 MiB1 MiB
非预取 MEM~2046.94 MiB (0x7fef0000)~1022.94 MiB (0x3fef0000)
可预取 MEM4 GiB4 GiB

Port D / E 的非预取 MEM 窗口较小,是因为其整体地址空间窗口比 Port A / B / C 小,扣除 config + I/O 区域后剩余空间更少。

配置空间详细说明

以 Port A (pcie0_rc) 为例,说明 DTS 中地址空间的配置方式:

pcie0_rc: pcie@80000000 {
...
reg = <0x0 0x80000000 0x0 0x00001000>, /* dbi */
<0x0 0x80100000 0x0 0x00001000>, /* dbi2 */
<0x0 0x80300000 0x0 0x00003f20>, /* atu registers */
<0x11 0x00000000 0x0 0x00010000>, /* config space */
<0x0 0x82900000 0x0 0x00001000>; /* phy ahb (link) */
reg-names = "dbi", "dbi2", "atu", "config", "link";
ranges = <0x01000000 0x00 0x00010000 0x11 0x00010000 0x0 0x00100000>,
<0x02000000 0x0 0x00110000 0x11 0x00110000 0x0 0x7fef0000>,
<0x43000000 0x18 0x00000000 0x18 0x00000000 0x1 0x00000000>;
...
};

ranges 每一项的格式为 <flags child-bus-address cpu-physical-address size>,其中 flags 含义为:

flags类型
0x01000000I/O 空间
0x0200000032 位不可预取内存空间
0x4300000064 位可预取内存空间

对应到 Port A 的三条 ranges 条目:

窗口CPU 物理地址大小
I/O0x11_0001_00000x0010_0000(1 MiB)
非预取 MEM0x11_0011_00000x7fef_0000(~2046.94 MiB)
可预取 MEM0x18_0000_00000x1_0000_0000(4 GiB)

config 空间通过 reg 属性分配,基地址 0x11_0000_0000,大小 64 KiB。

PHY 配置

K3 PCIe 使用独立的 PHY 节点,需在方案 DTS 中同时启用 PHY 和控制器。

PHY 节点定义在 k3.dtsi 中:

phy0: phy-pcie@81d00000 {
compatible = "spacemit,k3-pcie-phy";
reg = <0x0 0x81d00000 0x0 0x1000>;
spacemit,syscon-apb-spare = <&pll>;
num-lanes = <2>;
spacemit,phy-id = <0>;
#phy-cells = <0>;
status = "disabled";
};

pinctrl 配置

PCIe 的 pinctrl 只负责边带信号(sideband signals),不负责高速差分 Lane。真正的数据链路由 phys = <&phyX ...> 绑定到 PCIe PHY 节点,pinctrl 主要处理以下几类信号:

  • PERST#:设备复位信号
  • WAKE#:设备唤醒信号
  • CLKREQ#:参考时钟请求信号

配置步骤建议按下面顺序做:

  1. 先根据原理图确认板子实际接出了哪些边带信号,以及这些信号落在哪组 PAD 上。
  2. 再到 k3-pinctrl.dtsi 中查找对应的 pcie*_cfg 组。
  3. 如果公共 pinctrl 组和你的板级连线不完全一致,就在板级 DTS 里重新定义该组,只保留实际接出的 PAD。

K3_PADCONF(pin, func) 中的 pin 是 PAD 编号,func 是该 PAD 选择的复用功能。bias-disablebias-pull-updrive-strengthpower-source 则要和板级电压域、上拉方式保持一致。

deb1 为例,板级 DTS 对公共 pinctrl 组做了裁剪。比如 pcie0-1-cfg 只保留了 PERST#CLKREQ#,没有配置 WAKE#

pcie0-1-cfg {
pcie0-0-pins {
pinmux = <K3_PADCONF(79, 5)>,
<K3_PADCONF(81, 5)>;
bias-pull-up;
drive-strength = <33>;
power-source = <1800>;
};
};

power-source 字段的单位为 mV,<1800> 对应 1.8 V;若板级将边带信号接到 3.3 V(,需要根据原理图把该值改成 <3300> ,以免 PAD 供电域与外设不匹配。

如果你的板子没有把 WAKE# 接出来,可以参照这种方式在板级 DTS 中覆盖默认 pin 组,而不是硬套共享 k3-pinctrl.dtsi 里的三线配置。

PERST# 控制方式

K3 PCIe 驱动支持两种 PERST#(复位信号)控制方式:

  1. GPIO 控制
  2. PMU 寄存器控制(默认)

优先级规则:驱动会优先检查设备树中是否配置了 reset-gpios 属性。如果配置了,则使用 GPIO 方式控制 PERST#;否则回退到 PMU 寄存器控制方式。

GPIO 控制方式

通过 GPIO 子系统直接控制 PERST# 引脚的电平。这种方式常见于硬件 PERST pin用于其他功能,选用了 GPIO 作为 PERST#。

配置示例

&pcie0_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie0_1_cfg>;
phys = <&phy0>, <&phy1>;
phy-names = "phy0", "phy1";
num-lanes = <4>;
reset-gpios = <&gpio 3 28 GPIO_ACTIVE_LOW>; /* GPIO_120, 低电平有效 */
status = "okay";
};

参数说明

  • reset-gpios = <&gpio bank pin flags>
    • &gpio:GPIO 控制器节点
    • bank pin:GPIO 编号(例如 3 28 表示 GPIO bank 3 的 pin 28,即全局 GPIO_120)
    • flagsGPIO_ACTIVE_LOW 表示低电平有效(assert 时输出低电平)
PMU 寄存器控制方式

通过 PMU(Power Management Unit)寄存器间接控制 PERST#。这是K3默认的方式,当设备树中未配置 reset-gpios 时使用。

配置示例

&pcie0_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie0_1_cfg>;
phys = <&phy0>, <&phy1>;
phy-names = "phy0", "phy1";
num-lanes = <4>;
/* 不配置 reset-gpios,驱动将使用 PMU 寄存器控制 */
status = "okay";
};

方案 DTS 配置示例(以 deb1 为例)

deb1 板级文件使用的是 k3_deb1.dts,实际启用了 phy0/1/2/3/5pcie0/1/2/4,没有启用 phy4 / pcie3_rc

这里需要注意 Port A / Port B 的关系: k3_deb1.dts 示例里虽然写了 pcie0_rc { num-lanes = <4>; },但驱动在检测到 bifurcation GPIO 置位后,会把 Port A 运行时降到 x2,并把另外的 x2 lane 让给 Port B。也就是说,pcie1_rc 能否真正带起,取决于分叉 GPIO 的实际电平,而不只是 DTS 里的 num-lanes 静态值。

首先启用相关 PHY:

&phy0{
status = "okay";
};

&phy1{
status = "okay";
};

&phy2{
status = "okay";
};

&phy3{
status = "okay";
};

&phy5{
status = "okay";
};

然后配置各 PCIe 控制器:

&pcie0_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie0_1_cfg>;
phys = <&phy0>, <&phy1>;
phy-names = "phy0", "phy1";
num-lanes = <4>;
spacemit,bifurcation-gpios = <&gpio 2 25 GPIO_ACTIVE_HIGH>;
status = "okay";
};

&pcie1_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie1_1_cfg>;
phys = <&phy1>;
phy-names = "phy1";
num-lanes = <2>;
spacemit,device-detect-gpios = <&gpio 2 25 GPIO_ACTIVE_HIGH>;
status = "okay";
};

&pcie2_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie2_1_cfg>;
phys = <&phy2>, <&phy3>;
phy-names = "phy2", "phy3";
num-lanes = <2>;
status = "okay";
};

&pcie4_rc {
pinctrl-names = "default";
pinctrl-0 = <&pcie4_0_cfg>;
phys = <&phy5>;
phy-names = "phy5";
num-lanes = <1>;
status = "okay";
};

其中 pcie0_rcspacemit,bifurcation-gpios 用于控制 Port A 的分叉模式;pcie1_rcspacemit,device-detect-gpios 则是板级设备检测 GPIO。板级方案移植时,除了 statusphys 之外,这两个 GPIO 也要按原理图一起检查。

各配置项说明:

  • pinctrl-0:选择边带信号实际落在的 PAD。确认 PERST#/WAKE#/CLKREQ# 是否全部焊接,如果某条线未引出,就像上文示例一样裁剪 pin 组,避免错误开启悬空 PAD。
  • phys / phy-names:与控制器绑定的 PHY 列表,顺序要和硬件 lane wiring 一致。例如 deb1 的 Port A 需要占用 phy0 + phy1,否则无法拉到 x4。
  • num-lanes:描述期望的 lane 数。必须同时满足(1)板级是否真的把这么多 lane 接到插槽;(2)是否与 bifurcation/retimer 的拨码一致。建议把硬件连线表直接写进文档,调试时核对。
  • spacemit,bifurcation-gpios:根据板级设计决定是否需要配置,该 gpio 检测到高电平,则表示有设备接入到与该 pcie 共用 phy 的 另一个 pcie 接口。deb1 这里是决定是否把 lane 分给 Port B。
  • spacemit,device-detect-gpios:根据板级设计决定是否需要配置,该 gpio 检测到低电平,则表示没有设备接入该 pcie 接口,用来在驱动里屏蔽未插卡的控制器。
  • status:是否启用该控制器。

完整的 PCIe DTS

pcie0_rc(Port A)控制器 DTS 为例:

pcie0_rc: pcie@80000000 {
compatible = "spacemit,k1-pcie";
reg = <0x0 0x80000000 0x0 0x00001000>, /* dbi */
<0x0 0x80100000 0x0 0x00001000>, /* dbi2 */
<0x0 0x80300000 0x0 0x00003f20>, /* atu registers */
<0x11 0x00000000 0x0 0x00010000>, /* config space */
<0x0 0x82900000 0x0 0x00001000>; /* phy ahb (link) */
reg-names = "dbi", "dbi2", "atu", "config", "link";

bus-range = <0x00 0xff>;
max-link-speed = <3>;
num-lanes = <8>;
device_type = "pci";
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x01000000 0x00 0x00010000 0x11 0x00010000 0x0 0x00100000>,
<0x02000000 0x0 0x00110000 0x11 0x00110000 0x0 0x7fef0000>,
<0x43000000 0x18 0x00000000 0x18 0x00000000 0x1 0x00000000>;

interrupt-parent = <&saplic>;
interrupts = <141 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "pcie_irq";

clocks = <&syscon_apmu CLK_APMU_PCIE_PORTA_BUS>,
<&syscon_apmu CLK_APMU_PCIE_PORTA_MSTR>,
<&syscon_apmu CLK_APMU_PCIE_PORTA_SLV>;
clock-names = "dbi", "mstr", "slv";
resets = <&syscon_apmu RESET_APMU_PCIE_PORTA_DBI>,
<&syscon_apmu RESET_APMU_PCIE_PORTA_MSTR>,
<&syscon_apmu RESET_APMU_PCIE_PORTA_SLV>;
reset-names = "dbi", "mstr", "slv";

linux,pci-domain = <0>;
msi-parent = <&simsic>;
spacemit,apmu = <&syscon_apmu 0x1f0>;
iommu-map = <0x0 &iommu 0x00000 0x10000>;

status = "disabled";
};

接口介绍

API介绍

  • 注册 PCI 设备驱动
/* Proper probing supporting hot-pluggable devices */
int __must_check __pci_register_driver(struct pci_driver *, struct module *,
const char *mod_name);

/* pci_register_driver() must be a macro so KBUILD_MODNAME can be expanded */
#define pci_register_driver(driver) \
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
  • 注销 PCI 设备驱动
void pci_unregister_driver(struct pci_driver *dev);

Debug 介绍

sysfs

/sys/bus/pci:查看系统 PCI 总线设备和驱动信息

|-- devices // PCI 总线上的设备
|-- drivers // PCI 总线上注册的设备驱动
|-- drivers_autoprobe
|-- drivers_probe
|-- rescan
|-- resource_alignment
|-- slots
`-- uevent

测试介绍

  1. 查看 PCI 总线拓扑信息
#lspci
  1. 查看 PCI 设备详细信息
lspci -vvvs <BDF>
  1. NVMe SSD 读测试
fio --name read --eta-newline=5s --filename=/dev/nvme0n1 --rw=read --size=2g --io_size=10g --blocksize=1024k --ioengine=libaio --fsync=10000 --iodepth=32 --direct=1 --numjobs=1 --runtime=60 --group_reporting
  1. NVMe SSD 写测试
fio --name write --eta-newline=5s --filename=/dev/nvme0n1 --rw=write --size=2g --io_size=60g --blocksize=1024k --ioengine=libaio --fsync=10000 --iodepth=32 --direct=1 --numjobs=1 --runtime=60 --group_reporting

兼容 32-bit PCIe EP 设备

问题背景

K3 平台的 DDR 物理地址全部位于 4 GiB 以上(起始于 0x1_0000_0000)。部分 PCIe EP 设备(如 MT7921E Wi-Fi 网卡)仅支持 32-bit DMA 寻址,无法直接访问 4 GiB 以上的系统内存,导致 DMA 传输失败。

解决方案

通过 restricted DMA pool + dma-ranges 地址映射 解决:

  1. 在内存中预留一块 DMA 专用区域
  2. 通过 PCIe ATU(Address Translation Unit)将 EP 设备可见的 32-bit 总线地址映射到该物理内存区域
  3. EP 设备的 DMA 操作使用 32-bit 总线地址,由 PCIe 控制器自动完成地址转换

CONFIG 配置

需要开启以下内核配置:

CONFIG_SWIOTLB=y # Software I/O TLB,提供 bounce buffer 支持
CONFIG_DMA_CMA=y # DMA 连续内存分配
CONFIG_DMA_RESTRICTED_POOL=y # 支持 restricted DMA pool

DTS 配置

1. 预留 DMA 内存池

reserved-memory 节点中添加一块 restricted DMA pool。以下示例在 0x1_f000_0000 处预留 32 MiB:

reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;

pcie_dma_pool: pcie-dma-pool@1f0000000 {
compatible = "restricted-dma-pool";
reg = <0x1 0xf0000000 0x0 0x02000000>; /* 32 MiB */
};
};

2. 配置 PCIe 控制器节点

在对应的 PCIe 控制器节点中添加 dma-rangesmemory-region

&pcie4_rc {
/* ... 其他配置 ... */
dma-ranges = <0x02000000 0x0 0x80000000 0x1 0xf0000000 0x0 0x02000000>;
memory-region = <&pcie_dma_pool>;
status = "okay";
};

dma-ranges 条目含义:

字段说明
flags0x0200000032-bit 非预取内存空间
PCIe 总线地址0x0_8000_0000EP 设备看到的 DMA 地址(32-bit 可达)
CPU 物理地址0x1_f000_0000实际物理内存地址
大小0x0_0200_000032 MiB

工作流程:EP 设备向总线地址 0x8000_0000 发起 DMA 读写 → PCIe ATU 将其转换为 CPU 物理地址 0x1_f000_0000 → 访问预留的 DMA 内存池。

FAQ

1. SSD 没有被系统识别,如何判断问题点?

  1. 先确认是否在 PCIe 总线上被枚举。 执行 lspci | grep -i -E "nvme|non-volatile",确认若无任何输出,说明 Host 在 PCIe 总线层面完全没有抓到 NVMe 设备。
  2. 核对 pin 配置与电压域。 DTS 里的 pinctrlpower-sourcespacemit,*-gpios 必须与板级原理图一致,确保 pin 编号、复用功能、PAD 供电域、电压等级以及实际焊接的边带信号完全配对。
  3. 排除 SSD 器件故障。 更换一块确认良好的 NVMe SSD,或将疑似故障 SSD 接到其他已知可用的平台验证,确认是否为器件损坏。
  4. 检查初始化时序与信号质量。 若链路偶发掉卡或在 Training 阶段失败,需配合示波器/协议分析仪查看 PERST# / CLKREQ# / REFCLK 等时序、电压摆幅与 SI,必要时与原厂工程支持联调。

2. 什么时候需要使用 GPIO 控制 PERST#?

推荐场景

  • 原理图明确将 PERST# 引出到独立 GPIO 引脚

配置方法

在 PCIe 控制器节点中添加 reset-gpios 属性:

&pcie0_rc {
reset-gpios = <&gpio 3 28 GPIO_ACTIVE_LOW>; /* 使用 GPIO_120 */
/* ... 其他配置 ... */
};

排查问题

如果配置 GPIO 后 PCIe 仍无法正常工作:

  1. 确认 GPIO 编号正确:检查原理图中 PERST# 连接的实际 GPIO 引脚号
  2. 检查 GPIO 极性GPIO_ACTIVE_LOW 表示低电平有效(assert 时输出低),GPIO_ACTIVE_HIGH 表示高电平有效
  3. 验证 GPIO 功能:在启动时通过 cat /sys/kernel/debug/gpio 确认该 GPIO 已被 PCIe 驱动申请并正确控制
  4. 查看内核日志dmesg | grep -i pcie 查找 GPIO 申请失败或方向设置错误的信息

3. k3 PCIe不支持16位msi消息中断

原理说明:

Spacemit K3遵循RISC-V AIA协议规范,其内部MSI控制器(IMSIC)仅接收32位的配置写入。

原因分析:

部分老旧PCIe外设默认发起的是16位传统MSI消息,会导致K3 IMSIC直接丢弃该数据,表现为系统能正常识别PCI设备并为其分配中断号,但运行期间完全接收不到硬件中断响应。

设备msi中断位数确定:

将pcie ep msi中断消息写入地址(即msi中断控制器寄存器地址)修改为ddr某个地址mem1,通过读取ddr mem1的值检查pcie ep发送的msi中断消息值。

diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c
index 2f647cac4cae..0ba07cbd67b9 100644
--- a/drivers/pci/msi/msi.c
+++ b/drivers/pci/msi/msi.c
@@ -184,17 +184,34 @@ void __pci_read_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
}
}

+static u64 *msi_vaddr = NULL;
+static dma_addr_t msi_dma_handle;
+
static inline void pci_write_msg_msi(struct pci_dev *dev, struct msi_desc *desc,
struct msi_msg *msg)
{
int pos = dev->msi_cap;
u16 msgctl;

+ if (!msi_vaddr) {
+ msi_vaddr = dmam_alloc_coherent(&dev->dev, sizeof(u64),
+ &msi_dma_handle, GFP_KERNEL);
+ if (!msi_vaddr) {
+ pr_err("Failed to allocate MSI address\n");
+ return;
+ }
+ memset(msi_vaddr, 0, 64);
+ dev_info(&dev->dev, "MSI address allocated at 0x%llx, msi vaddr 0x%llx\n",
+ (unsigned long long)msi_dma_handle,
+ (unsigned long long)(uintptr_t)msi_vaddr);
+ }
+
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl);
msgctl &= ~PCI_MSI_FLAGS_QSIZE;
msgctl |= FIELD_PREP(PCI_MSI_FLAGS_QSIZE, desc->pci.msi_attrib.multiple);
pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl);

+#if 0
pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msg->address_lo);
if (desc->pci.msi_attrib.is_64) {
pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, msg->address_hi);
@@ -202,6 +219,20 @@ static inline void pci_write_msg_msi(struct pci_dev *dev, struct msi_desc *desc,
} else {
pci_write_config_word(dev, pos + PCI_MSI_DATA_32, msg->data);
}
+#else
+
+ dev_info(&dev->dev, "MSI msg orgin address 0x%08x%08x, data 0x%x\n", msg->address_hi, msg->address_lo, msg->data);
+ /* 对pcie ep msi中断消息地址,写入原始值0x12345678;检查pcie ep设备是否有写入msi消息,包括写入的消息的位数。
+ * 假设msg->data为0x0008,该值也是pcie ep写入rc端的msi消息值
+ * 如果pcie ep写入消息为32位,则msi_vaddr地址的内容会为0x00000008;
+ * 如果pcie ep写入消息为16位,则msi_vaddr地址的内容会位0x12340008
+ */
+ *msi_vaddr = 0x12345678;
+
+ pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msi_dma_handle & 0xFFFFFFFF);
+ if (desc->pci.msi_attrib.is_64) {
+ pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, (msi_dma_handle >> 32) & 0xFFFFFFFF);
+ pci_write_config_word(dev, pos + PCI_MSI_DATA_64, msg->data);
+ } else {
+ pci_write_config_word(dev, pos + PCI_MSI_DATA_32, msg->data);
+ }
+
+#endif
/* Ensure that the writes are visible in the device */
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl);
}