基于NXP S32G & MicroChip MPF300T 的PCIE调试
最近调了下NXP的S32G PCIE的功能,其实原本目的是验证FPAG的PCIE逻辑是否正常,是个FPGA的活,但是主要时间花在了CPU那边,原本软件那边给我烧的uboot压根不能用,都枚举不到pcie设备,没办法只能自己去调S32G了,简单记录下,也回顾下PCIE的基础知识,这里不涉及PCIE具体协议层面。
首先PCIE设备有两种模式,RC和EP,RC字面意思就是根节点,具体来说可以理解为CPU和PCIE终端设备的一个中间方,它可以接收CPU发出的PCIE相关的请求,转发给下游的终端PCIE设备,而下游的终端设备就是EP了,一张FPGA的板卡插在CPU板子的PCIE插槽上,通常FPGA扮演的就是EP,CPU扮演的就是RC,当然这是很粗的说法,我们这里只关注EP。
系统启动时候具体是如何枚举到设备的?这里就会引入pcie的配置空间了,就是所有的pcie设备,都会有固定的一个空间,存放设备信息,这个是协议定死的。这个配置空间是64Byte的header和192B的Capablitty组成,共256Byte,PCIE则更大有4KB。我们很多时候只关注前64Byte就可以了,EP的前64Byte如下:

所以系统启动时候,RC会顺序扫描总线上所有位置的pcie设备,本质上就是在读pcie配置空间,比如在bus0 device0处读配置,读到了设备ID 厂商信息等,系统就发现这里存在1个pcie设备,这就是枚举的大致过程,这个中间系统还会干一个很重要的事,就是分配地址空间。上面那个图里有6个基址,就是我们pcie设备内部具备的地址空间,可以给cpu去访问的,也就是大名鼎鼎的bar空间,也是我们在fpga的pcie相关的ip里常见到的配置项,pcie_bar指的是pcie地址域到axi地址域的方向,而axi_bar则是axi到pcie的方向,一般来说pcie_bar我们更常见,就是CPU做读写的发起方,对应的就是FPGA PCIE IP那边有个master的axi总线出去,而axi_bar则就是FPGA内部的逻辑做读写的主控方,通过IP的 AXI slave向CPU主动读写。





首先,CPU会向BAR0写入全1,因为bar低多少位是只读的,具体多少位取决于这个bar的空间大小,比如4KB的就是低12位只读,注意bar的低4位始终都是只读的,并且有特殊的含义:
- bit0:表示bar空间是映射到memory(0)还是io(1)
- bit1:reserved
- bit2:表示bar地址空间是32位(0)还是64位(1)
- bit3:表示允许prefetch预取,1可以,0不可以
低4位虽然有自己的功能定义但是也参与到空间大小的表示,所以有两重作用,然后bit4-bit11就是全0。
我们继续说回地址分配的事,CPU向BAR0写入全1后,因为低12位为只读,所以cpu发现bit4-bit11没变,还是0,就知道这个bar有4KB大小,然后在自己的系统内存空间中找4KB的地址空间分配给它,把这个地址写到bar0的bit12-bit31位上,图里这个例子还考虑了4K对齐:


这样,当CPU要访问PCIE设备空间时,其实CPU不知道这个空间是PCIE的空间,它只知道它要访问的具体地址,它把这个请求发给RC,RC发现这个地址是某个PCIE设备的地址空间映射,就会触发PCIE相关的消息给到对应的设备。从而最终实现pcie设备的读写,大致的流程就是这样。下面以S32G验证下,pcie端是配置了一个1MB大小的bar0的FPGA板卡:
-
FPGA配置:
![pcie1]()
![pcie2]()
-
首先在uboot里,查看能否枚举到设备:
![屏幕截图 2026-05-13 164917]()
-
查看bar空间,bar0的是300008,也就是大小1MB,32bit,prefetch,属性和fpga端配置相符。但是这里的300000应该不是映射之后的地址,首先地址太小,而且后面尝试对这个地址读写也是不成功的:
![屏幕截图 2026-05-13 165143]()
-
我们模拟下bar配置时写全F的操作,可以看到低20bit是不变的:
![屏幕截图 2026-05-13 165756]()
-
我们进到kernel里进行bar读写(uboot里读写有点问题),首先kernel里也检测到卡:
![屏幕截图 2026-05-13 170437]()
-
但是卡的属性不对,不支持memory读写,因为是Mem-:
![屏幕截图 2026-05-13 170509]()
-
在没有驱动的情况下通过setpci -s 01:00.0 COMMAND=0x06来配置属性强制开启mem读写:
![屏幕截图 2026-05-13 170707]()
-
借助pcimem读写:
![屏幕截图 2026-05-13 171034]()
-
fpga端也捕获到写的信号,地址和数据都和cpu端一致:
![wav2]()
![wav1]()
这里特别说下容易坑的地方
-
S32G那块开发板默认第二个口是不做RC,所以要修改,我是直接在做uboot时候改了配置:
![image-112]()
![image-122]()
-
S32G的BSP 46.0的版本做出来的uboot是有问题的,启动时候会报DDR的PANIC错误,直接起不来,我是降到42才解决了这个问题
-
microchip 2024之前的pcie驱动是有内存泄漏的问题的,在我的连续DMA数据搬运测试中,程序运行一段时间会挂掉,挂掉后重启程序会更快的崩溃,不符合用户程序内存泄漏的现象,dmesg显示pci alloc pages failed,经查,驱动侧在申请SG DMA 描述符连续物理地址时候,没有释放该物理地址,由于描述符每个只有8字节,所以终端查看内存泄漏的不明显,但是kernel连续物理地址比虚拟地址申请资源更有限,所以重新运行后用户程序会更快的挂掉,修改驱动添加pci_free_consistent,程序运行正常:
![image-4-1]()
![image-5-1]()
最后记下过程中使用的yocto指令,方便记忆:
- bitbake -e u-boot | grep "^S=" 查找u-boot的源码位置
- bitbake virtual/kernel -c menuconfig 配置
- bitbake u-boot -c configure
- bitbake u-boot -c devshell
- cd ../build/<defconfig>
- make menuconfig
- exit
- bitbake -c compile -f u-boot
- bitbake u-boot
- bitbake -e linux-xlnx | grep ^S= 查看yocto内核编译部分
- bitbake -e hello | grep ^SRC_URI 查找包的路径
- bitbake -g petalinux-image-minimal && cat pn-buildlist | grep -ve "native" | sort | uniq 等同于./build/tmp/deploy/images/<your_image>/<image_name>.manifest
- bitbake -s | grep XXX 查找包
- bitbake -c cleanall hw-hs -f && bitbake -c compile hw-hs -f
- bitbake-layers show-recipes
- bitbake-layers show-layers

添加自定义recipes并编进rootfs:
-
在sources下添加meta-custom目录,结构如下:
![image-116]()
-
layer.conf内容如下:
![image-117]()
-
pcimem.bb内容如下:
![image-118]()
先记到这里,整个过程其实大部分都是在折腾S32G,FPGA这边没怎么动。PCIE的整体的体系结构其实很复杂,这里没有涉及具体的协议层面,毕竟我们都用的现成的IP,等后面有机会深入的时候再补充,有错的地方请指正。这里推荐看下<<深入浅出SSD>>里的协议篇PCIE部分,讲的比较简单但通俗易懂。
参:
- https://iriscores.com/2021/07/14/understanding-pcie-to-axi-bridge/
- https://ww1.microchip.com/downloads/aemDocuments/documents/FPGA/ApplicationNotes/ApplicationNotes/microsemi_polarfire_fpga_pcie_endpoint_ddr3_ddr4_memory_controller_data_plane_demo_guide_dg0756_v10.pdf
- pg055-axi-bridge-pcie
- https://zhuanlan.zhihu.com/p/445877158
- 深入浅出SSD 协议篇



















浙公网安备 33010602011771号