RK3588与FPGA的PCIe XDMA驱动开发实战:从设备树到DMA带宽调优
2026/9/19 4:42:29 网站建设 项目流程

从编译到调通,RK3588与FPGA的PCIe XDMA驱动这条路,我前前后后走了大概两周。中间踩的坑不算少,但真正跑通DMA搬运那一刻,前面所有的折腾都值了。这篇文章把整个流程拆开揉碎了讲,从硬件链路、设备树配置、内核编译、驱动模块分析,到枚举调试、DMA性能实测和常见故障定位,尽量做到你照着走也能通。

适合的读者比较明确:手里正好有一块RK3588主板和带PCIe接口的FPGA开发板,想自己把XDMA这套东西调通的人。或者虽然硬件不同,但做Linux驱动、嵌入式异构开发,想了解PCIe枚举和DMA驱动的完整链路,也可以拿这篇当参考。

先说下我手上的环境:主控是RK3588,内核用的官方Linux 5.10内核源码,FPGA是Xilinx UltraScale+系列,PCIe IP用的XDMA(BMD模式),FPGA侧逻辑基于Xilinx官方XDMA驱动源码,也就是xdma字符设备驱动。RK3588这边跑的是Ubuntu 22.04文件系统,内核自己编。

1. RK3588侧PCIe控制器:三个接口、两种速率、一套驱动框架

RK3588的PCIe资源,大部分人只知道“支持PCIe”,但具体到项目选型和板卡设计,里面的门道值得先说清楚。3588一共有3个PCIe控制器,命名分别是:PCIe 3.0 x4(PCIE30)、PCIe 3.0 x2(PCIE30)、PCIe 2.0 x1(PCIE20)。

这三个控制器不是简单的速率差异,它们挂在不同的总线上,使用不同的DTS节点,而且compatible字符串也有区别。PCIe 3.0的控制器compatible是"rockchip,rk3588-pcie",而PCIe 2.0的是"rockchip,rk3568-pcie"这种复用老IP的描述。换言之,3588的PCIe2.0控制器和RK3568是同一个IP,驱动可以直接复用。

控制器通道数最高速率典型用途
PCIE30 x44 lanes8GT/s/laneNVMe SSD、FPGA大带宽互联
PCIE30 x22 lanes8GT/s/lane网卡、视频采集卡
PCIE20 x11 lane5GT/s/lane低速设备、WiFi模块

你用的FPGA接哪一个,直接决定了理论带宽上限。我这边FPGA接的是PCIE30的x4控制器,跑x4模式,链路速率8GT/s,单lane理论带宽约985MB/s(考虑到8b/10b编码,实际有效载荷大约这个值)。4条lane合在一起理论极限在3.9GB/s左右,但这是理想的物理层上限,软件和DMA会有额外开销,实测后面会讲。

在RK3588的硬件层面,PCIE30控制器的REFCLK通常是独立的100MHz差分时钟,FPGA侧的PCIe PHY也需要外部输入100MHz参考时钟,或者是用RK3588输出的REFCLK。这里有个很容易忽略的点:如果用同一个时钟源,要注意时钟缓冲器的扇出能力和走线长度匹配,否则链路训练容易出现间歇性失败。

另外,RK3588的PCIE30控制器支持RC和EP两种模式,但大多数主板默认工作在RC模式。如果FPGA侧想要做EP,RK3588这边的驱动完全不用动,它作为RC天然负责枚举EP设备。我项目里的FPGA就是EP角色,RK3588是RC,顺着这个思路整个软件架构就清晰了:

  • RK3588:RC端,运行Linux,加载PCIe驱动,负责枚举FPGA设备、分配资源、配置BAR空间,并且运行XDMA字符设备驱动,提供/dev/xdma0_*接口给应用层。
  • FPGA:EP端,XDMA IP核作为Endpoint,通过AXI接口连接FPGA内部的DMA逻辑和用户逻辑,负责把数据搬运到DDR或者从DDR取数。

了解这个拓扑结构后,接下来的设备树配置就有了明确的方向。

2. RK3588 DT配置:单控制器双端口的隐藏坑位

设备树配置是整个链路能否枚举成功的第一道关卡。RK3588的PCIE30控制器物理上有两个端口(Port0/Port1),对应dts里的pcie3x4pcie3x2两个节点。更准确地说,3588的PCIE30是一个支持 bifurcation 的控制器,可以做x4、x2x2、x2、x1这些拆分组合。具体怎么拆,由rockchip,bifurcation属性和主板硬件走线决定。

我手上这块核心板把PCIE30 x4引出来给了FPGA,所以设备树里主要关注pcie3x4节点。

一个典型的配置片段类似这样:

&pcie3x4 { status = "okay"; reset-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_HIGH>; vpcie3v3-supply = <&vcc3v3_pcie>; pinctrl-names = "default"; pinctrl-0 = <&pcie30x4_perstn>; max-link-speed = <3>; num-lanes = <4>; };

几个容易出问题的地方:

  • reset-gpios:PERST#信号,RC端拉高释放EP的复位,FPGA侧必须正确响应这个复位时序。很多FPGA工程只做了PCIe硬核的初始化,却没有把PERST#引入FPGA的逻辑里做复位控制,结果就是RC一直等不到EP就绪,链路训练失败。
  • max-link-speed:3代表Gen3,如果FPGA侧XDMA IP生成时选的是Gen2,这里设为3也能协商到2,不冲突。但反过来,如果FPGA只支持Gen1,你设Gen3还可能偶尔被训练失败,降低到2或1更稳妥。
  • num-lanes:4表示4条lane,但这个必须和FPGA侧IP配置保持一致,如果FPGA工程只例化了2条lane,RK3588这边设置x4也能协商成x2,但带宽只有一半。

在3588的dts里还要留意pcie30_phy_grfpcie30_pipe这些PHY相关节点,时钟和PHY配置大多在pcie30-phy里。如果你用的是主线内核,可能还需要确认rockchip,pcie30-phy驱动已编译进内核,否则PHY不工作,设备树配得再对也枚举不了。

我踩过的具体坑:PCIe节点配成status = "okay"了,但pinctrl-0里没有加上pcie30x4_perstn,导致PERST引脚没有被正确配置为GPIO功能,复位信号一直处于无效电平,FPGA的EP始终无法起来。lspci里什么都看不到,排查了老半天。

3. XDMA驱动核心机制:BMD模式寄存器、描述符和中断通道

XDMA(Xilinx DMA/Bridge for PCIe)IP核有两种工作模式:BMD(Bus Master DMA)模式和AXI Bridge模式。BMD模式下,FPGA作为PCIe EP可以通过DMA读写主机内存,不需要CPU逐字拷贝,这是高性能数据传输的核心。

XDMA IP在FPGA侧暴露了几块重要的寄存器空间和中断资源。首先,BAR0通常映射的是用户逻辑寄存器,BAR1映射的是XDMA控制寄存器(如果你例化时有勾选),而MSI中断则用于通知主机DMA完成事件。

驱动侧核心的数据结构是描述符(Descriptor),也叫BD(Buffer Descriptor)。XDMA的BMD模式支持环形描述符(Ring)和聚合描述符(Aggregation),我用的驱动是Xilinx官方开源的xdma驱动,它用的是环形描述符机制。

描述符结构体(dma_desc)主要包含以下几个字段:

  • next:指向下一个描述符的地址,形成环形链。
  • addr_lo/addr_hi:DMA搬运的源或目的地址(对于主机内存而言就是DMA缓冲区地址)。
  • len:本次搬运的字节数。
  • ctl:控制和状态位,包括DMA方向、完成中断使能等等。

这里描述符的环形队列放在主机内存(DMA Buffer),FPGA侧通过AXI MM2S/S2MM通道读取描述符。每次用户发起readwrite,驱动就往环形队列塞一个描述符,然后写XDMA的寄存器来通知硬件处理。传输完成后,FPGA通过MSI给CPU发中断,驱动在中断处理函数里将对应的描述符标记为完成,然后唤醒等待队列中的进程。

这个逻辑看起来很线性,但真正跑代码时,你会发现:描述符循环是用无锁方式改写的,tail指针由驱动写、head指针由硬件更新。所以理解这两个指针的含义就很重要。还有,XDMA的寄存器里有个H2C Channel ControlC2H Channel Control,分别对应主机到FPGA(Host-to-Card,MM2S,其实就是主机写)和FPGA到主机(Card-to-Host,S2MM,主机读)。

这张表建议收藏,调试的时候会频繁用到:

方向XDMA术语等价AXI通道用户操作
主机写FPGAH2C / MM2SAXI Writewrite(fd, buf, len)
FPGA读主机C2H / S2MMAXI Readread(fd, buf, len)
中断MSI-X / MSI——驱动中断处理

XDMA支持最多4个H2C通道和4个C2H通道(在IP配置里可选),每个通道有独立的中断。驱动默认会注册/dev/xdma0_h2c_0/dev/xdma0_c2h_0,还有/dev/xdma0_control/dev/xdma0_user

4. 编译内核和驱动模块:版本一致性比想象中更重要

RK3588的板级支持包(BSP)内核是最稳妥的起点。可以从Rockchip官方仓库拉取5.10内核,也可以用你主板厂商提供的内核源码。这里有个核心前提:内核源码版本必须和你板子实际运行的内核完全一致,否则编译出来的驱动模块要么死活加载不进去,要么加载了之后行为诡异。

我自己第一次就是用了board vendor提供的内核源码,但板子跑的是他们预编译的内核镜像,版本号对不上,编译模块时头文件指向了源码目录,硬加载进去直接报version magic不匹配。后来干脆用源码自己编译整个内核镜像,一了百了。

编译内核前,需要确保开启以下配置:

CONFIG_PCI=y CONFIG_PCIE_DW=y CONFIG_PCIE_DW_PLAT=y CONFIG_PCI_MSI=y CONFIG_PCI_MSI_IRQ_DOMAIN=y CONFIG_DMA_ENGINE=y CONFIG_DMA_OF=y CONFIG_UIO=y CONFIG_UIO_PCI_GENERIC=y

其中CONFIG_UIO_PCI_GENERIC是给不带驱动的PCIe设备用的,XDMA驱动用不上,但有时排查BAR映射问题时会用到。XDMA官方驱动不依赖UIO,它自己直接操作BAR0和BAR1。

内核编译我用的命令:

make ARCH=arm64 rockchip_linux_defconfig make ARCH=arm64 menuconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j16

如果是交叉编译,交叉工具链必须与目标架构匹配。然后单独编译XDMA驱动模块时,需要用到编译内核时生成的Module.symvers,否则modpost阶段会出现unknown symbol之类的错误。这里建议把XDMA驱动源码放到内核源码树的drivers/目录下,或者至少确保KERNELDIR指向已编译好的内核源码树。

XDMA驱动的Makefile大概长这样:

KERNELDIR ?= /path/to/kernel-source PWD := $(shell pwd) obj-m := xdma.o xdma-objs := xdma_mod.o xdma_core.o xdma_linux.o c2h.o h2c.o libxdma.o xdma_proc.o all: $(MAKE) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KERNELDIR) M=$(PWD) modules install: $(MAKE) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KERNELDIR) M=$(PWD) modules_install

实际编译时,如果内核源码开启了CONFIG_MODVERSIONS,强烈建议保留Module.symvers。遇到Unknown symbol的报错,优先检查Module.symvers是否存在以及对应符号是否包含在内。另一种省事方法是直接把xdma编译进内核而不是作为模块,把xdma相关文件加入drivers/dma/KconfigMakefile,这也能绕开模块版本问题,缺点就是每次改驱动都得重新烧内核,调试效率低。我建议开发阶段用模块,稳定后再考虑内建。

5. 链路枚举与板级调试:lspci看不到设备时按顺序查这几项

驱动模块编译好,dts也配了,接下来就是激动人心的上电和枚举。把内核镜像烧进去,系统起来后,先执行:

lspci -vvv

如果一切顺利,你应该能看到类似输出:

01:00.0 Unassigned class [ff00]: Xilinx Corporation Device 9038

9038是XDMA常见的Device ID。如果这里什么都看不到,不用慌,链路协商失败的原因就那么几类,按顺序排查:

  • PERST#信号:用示波器查RK3588输出的PERST#是否拉高,FPGA侧是否收到了有效的复位释放。时序上,PERST#释放必须在REFCLK稳定之后,且有一定的延时要求。最简单的方法是拿万用表测电平,拉高正常,但释放时机需要示波器才看得准。
  • REFCLK信号:100MHz差分时钟,PCIe对REFCLK的抖动要求很严格,但至少先确认频率对、幅度在合理范围(约0.6Vpp-1.0Vpp)。如果FPGA和RK3588共用时钟源,确认时钟缓冲器供电正常。
  • 链路训练状态机:FPGA例化XDMA IP后,如果链路训练卡在某个状态,可以在硬件里加入ILA(Integrated Logic Analyzer)抓取PCIe PHY的LTSSM状态,看看是卡在Detect、Polling还是Configuration。RK3588侧没有类似工具,但FPGA侧用Xilinx的debug核很方便。
  • 设备树上电顺序:RK3588的PCIe供电时序,要求3.3V和1.8V电源、REFCLK、PERST#释放顺序符合PCIe规范。不少板子的电源轨由GPIO控制,如果vpcie3v3-supply没配好,导致供电晚于PERST释放,就会枚举失败。

我调试过程中最典型的一次:FPGA侧XDMA IP的PCIe硬核没有正确引入PERST#,导致EP永远处于复位状态。这个排查过程比较痛苦,因为FPGA内部逻辑是正常的、JTAG能看到,但PCIe的PHY状态一直不对。最后在FPGA里把PERST#作为一个普通信号引入逻辑,接到XDMA IP的axi_resetn,重新综合后链路直接训练成功。

链路起来后,还要确认BAR空间分配。用lspci -v看:

Region 0: Memory at 10000000 (32-bit, non-prefetchable) [size=1M] Region 2: Memory at 10100000 (32-bit, non-prefetchable) [size=64K]

BAR0的地址就是驱动需要映射的用户寄存器空间,XDMA控制寄存器也会映射在这附近。驱动加载后,dmesg里会打印类似:

xdma 0000:01:00.0: enabling device (0000 -> 0002) xdma: xdma_probe: xdma_dev 00000000abcdef12

如果看到probe成功,/dev/xdma0_*节点也就创建出来了。

6. 驱动加载后不能立刻愉快DMA:先解决dmesg里的这堆异常

驱动加载成功只是第一步。/dev/xdma0节点出现了,但调用readwrite能不能真正完成DMA传输,还取决于两件事:DMA缓冲区映射是否成功、MSI中断是否注册成功。

第一次调通时,我执行了个最简单的测试——往FPGA写1KB数据:

echo "test" > /dev/xdma0_h2c_0

结果dmesg直接报错:

xdma 0000:01:00.0: DMA mapping failed: out of memory

这个错误看着像内存不足,实际上是DMA缓冲区地址超过了设备能访问的地址范围。XDMA这类PCIe设备默认可能只支持32位地址(取决于IP配置和驱动代码里的dma_set_mask_and_coherent设置),如果驱动没正确调用dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)),内核就尝试分配高于4GB的缓冲区,而设备寻址不了。

这个问题的解决方法是修改驱动代码,在probe函数里加上类似这样的调用:

if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)) == 0) { dev_info(dev, "Using 64-bit DMA\n"); } else { dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); dev_info(dev, "Using 32-bit DMA\n"); }

但注意:FPGA侧的XDMA IP如果地址宽度只配了32位,主机端设64位掩码也没用,传输时地址会截断。所以IP配置和驱动需要双向对齐。

还有一个高频报错是中断注册失败或MSI不可用:

xdma 0000:01:00.0: failed to enable MSI-X

这多半是内核没开启CONFIG_PCI_MSI,或者BIOS/U-Boot没有为PCIe正确初始化MSI控制器。RK3588这边U-Boot的PCIe初始化很关键,如果U-Boot里PCIe node就是disabled,内核即便能枚举,中断资源也可能分配异常。检查一下U-Boot dts里PCIe节点的状态,确保U-Boot阶段也做了PCIe初始化。

还有一个很多人容易忽略的问题:XDMA驱动默认的DMA缓冲区大小。在官方驱动里,xdma分配DMA缓冲区用的参数在xdma_mod.c里,默认可能只有几MB。如果你通过mmap映射用户缓冲区做非对齐传输,性能会惨不忍睹。我后来把缓冲区调到64MB,配合read/write一次性搬运大块数据,吞吐量才真正上来了。

xdma驱动支持mmap操作,你在应用层把设备文件映射到用户空间,然后直接读写映射区域,对驱动而言就是DMA缓冲区的直接访问,绕过了copy_to_user这一层拷贝。性能提升非常明显,尤其是大数据量传输。

7. 从BSP内核对接到自编译模块:处理Mismatch和依赖问题

前面提过内核版本一致性,这节展开讲讲几种典型的模块加载失败场景和应对方法。

场景一:version magic不匹配。明明源码是同一个tag,但编译出来的模块就是加载不了。原因通常是内核源码里的.config和板子实际运行内核的config不一样,尤其CONFIG_LOCALVERSION字段会导致version magic变化。解决办法是用板子/proc/config.gz(如果内核开启了CONFIG_IKCONFIG_PROC)导出现有config,再做编译。

zcat /proc/config.gz > .config make ARCH=arm64 olddefconfig

场景二:符号依赖不满足。报错通常长这样:

xdma: Unknown symbol dma_set_mask_and_coherent (err -22)

这种情况多半是Module.symvers缺失或路径不对。你可以把编译内核时生成的Module.symvers手动拷贝到XDMA驱动源码目录,或者在Makefile里显式指定KBUILD_EXTRA_SYMBOLS

场景三:设备树里中断配置和驱动不符。XDMA IP在生成时会默认上报MSI中断,但有些旧版本驱动写死了INTx中断方式,拉着irq号初始化,结果中断处理函数一直没触发。排查方法是看/proc/interrupts里有没有注册对应的中断号。

cat /proc/interrupts

里面应该有类似xdma或者pcie相关的条目。没有的话,多半是驱动在request_irq时拿到的irq号不对。新版xdma驱动用的是pci_alloc_irq_vectorsrequest_threaded_irq,对MSI-X支持比较完善。如果你用的是老版本,建议从Xilinx官方GitHub更新到最新版的xdma驱动源码。

这些坑排查完之后,驱动模块就可以稳定加载了。接下来进入最关键的环节——性能实测。

8. 带宽实测和性能调优:从理论3.9GB/s到实际性能只有六成

链路跑通后,大家最关心的肯定是带宽到底能到多少。我第一次测试时,直接用dd/dev/xdma0_c2h_0读数据,结果惨不忍睹:

dd if=/dev/xdma0_c2h_0 of=/dev/null bs=4k count=1024

速度只有150MB/s左右。这个数字离x4 Gen3的理论上限差太远了。问题出在几个环节,逐个优化后有了明显改善。

优化1:DMA缓冲区对齐和大小

XDMA对DMA地址的alignment要求比较严格。官方驱动内部用的缓冲区是kzalloc分配的,但这种方式分配的内存不保证物理连续或满足设备对齐需求。应改用dma_alloc_coherent,或者gen_pool分配大块连续内存。在驱动初始化时,把每个通道的缓冲区按1MB对齐分配,且支持mmap,这样用户态可以用read/write直接批量DMA。

优化2:中断合并和DESC数量

XDMA每个描述符可以携带一次中断请求,但如果每个小包都中断,CPU压力很大。解决方式是只在描述符链的最后一个描述符开启中断,也就是把ctl里的stop_on_errirq_enable合理设置。同时增加描述符数量,让一次DMA操作尽量长。我后来把每个环的desc数量设为4096,单次DMA长度可以做到几十MB,中断频率大幅下降,吞吐率提升明显。

优化3:使用io_uring或者异步IO

XDMA驱动本质上支持阻塞IO和异步IO。但用户态最简单的read/write其实每次都会产生一次系统调用和上下文切换,传输小包时开销相对大。如果应用允许,尽量使用mmap映射DMA缓冲区,然后批量填充数据,不断触发DMA。或者用libaio提交多个请求,让驱动和DMA引擎尽可能满负荷。

优化后我的实测数据如下(PIO读和DMA读分开测):

模式数据块大小实测带宽
驱动读写(read/write)4KB260MB/s
驱动读写(read/write)1MB1.8GB/s
mmap映射+DMA1MB2.2GB/s
mmap映射+DMA16MB2.5GB/s

最终稳定在2.5GB/s左右,也就是理论带宽的六成多。对于XDMA这种面向数据搬运的IP,这个成绩已经算正常偏上。如果换算成PCIe Gen3 x4链路,2.5GB/s意味着链路利用率超过60%,说明瓶颈主要不在PCIe协议上,而在DMA描述符处理、中断延迟和应用层拷贝。

如果想要逼近3GB/s以上,建议在FPGA侧把XDMA的Data Width做成512bit,并在AXI互联上避免经过低速总线桥。另外,用c2h连续读大块数据,比h2c写大块数据更容易跑满,因为读操作在PCIe协议上可以有更好的延迟隐藏。

9. 常见故障排查与调试工具:问题定位思路比工具本身更重要

这篇文章到这里,核心链路已经完整走了一遍。再补充一些调试中高频出现的故障类型和定位方法,遇到问题的时候能少走弯路。

9.1 设备枚举出来了但无法访问BAR空间

lspci -v能看到设备,但用户空间程序访问BAR地址时段错误(Segmentation Fault)。这个问题的根源通常不是内核驱动,而是你没有将BAR映射到用户态。XDMA设备节点/dev/xdma0_user是专门用来访问BAR0用户空间的。如果你直接对/dev/xdma0_control做mmap,需要确认偏移地址和BAR映射范围。最好的办法是先用pcim_iomap映射BAR0,然后通过字符设备的mmap回调把这段iomem映射给应用层。

9.2 DMA传输超时

read调用一直阻塞,最终返回Resource temporarily unavailable或者ETIMEDOUT。XDMA描述符环的tail指针没有成功写入硬件寄存器,导致FPGA侧无法拾取描述符。排查时可以先用xdma驱动自带的proc接口查看:

cat /proc/xdma0/h2c0_desc

正常情况可以看到可用的desc数和已处理的desc数。如果显示head=0, tail=0,说明驱动压根没有提交描述符。

9.3 中断风暴

启动系统后CPU占用飙升到100%,/proc/interrupts里某个中断计数狂涨。这个通常是因为FPGA侧在链路训练完成后,持续产生了未处理的中断,比如错误中断或MSI-X向量表被错误配置。解决办法是检查FPGA侧XDMA IP的irq_ack逻辑,确保中断被正确清掉。如果只是调试驱动,临时在中断处理函数里把中断屏蔽掉也能定位问题。

9.4 用perfftrace辅助定位

有时候问题出在内核路径上,最好直接看函数耗时和调用栈:

perf record -g -a -- sleep 10 perf report

xdma驱动的关键函数会在输出里清晰展现,比如xdma_irq_handlerxdma_h2c_write这些函数如果占用了太多CPU时间,说明中断处理或者描述符操作优化空间还没挖尽。

另外,trace-cmd可以跟踪中断的进入和退出,以及DMA映射是否一直在繁忙等待:

trace-cmd record -e irq:irq_handler_entry -e irq:irq_handler_exit trace-cmd report

10. 对整体方案的几点个人体会

串完整个项目,有几个感受想最后提一下。

第一,fpga和RK3588的PCIe互联,硬件设计的余量直接决定了软件调试的难度。如果板子走线不规范、时钟源设计不合理,后面浮现的问题会让你怀疑驱动写错了,实际上问题在物理层。有条件的话,第一次打板就预留PCIe的调试探测点,LTSSM的状态观测点也很重要。

第二,XDMA驱动代码虽然看起来复杂,但核心就是描述符和中断两件事。建议拿官方驱动源码,结合XDMA IP的寄存器手册(PG195文档)对照着看,比盲目百度高效得多。尤其是H2C/C2H的通道控制寄存器,Channel IDTarget Address这些字段的布局,手册里写得明明白白,驱动代码里的偏移量也都能对上。

第三,RK3588这种多控制器SoC做PCIe主控,如果遇到枚举不稳定的情况,优先怀疑电源和时钟时序,不要一上来就改驱动。我实际调的过程中,第一次上板100%失败,后来发现是PERST#释放比供电晚,根本原因是FPGA侧的电源监控芯片delay配置不对。

最后分享一个小技巧:驱动、硬件、FPGA逻辑三方联调时,先在FPGA里写一个简单的寄存器回环模块,用/dev/xdma0_user读写寄存器验证PCIe通路,然后再跑DMA。把小问题消化在最小系统里,能让你少熬好几个通宵。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询