当前位置: 首页 > ops >正文

xdma 驱动测试与分析

目录

1. 简介

2. 基本测试

2.1 H2C 测试

2.1.1 MRRS

2.1.2 抓取 H2C 数据

2.1.3 数据位宽

2.1.4 数据对比

2.1.5 写入地址测试

2.1.6 带宽测试

2.1.6.1 x86_Gen2x4

2.1.6.2 x86_Gen3x4

2.1.6.3 x86_Gen3x8

2.1.6.4 ZCU102_Gen2x1

2.1.6.5 AGX_Gen3x4

2.1.7 带宽分析

2.1.7.1 x86_Gen2x4

2.1.7.2 x86_Gen3x4

2.1.7.3 x86_Gen3x8

2.1.7.4 ZCU102_Gen2x1

2.1.8 win10 中测试

2.2 dma_to_device 分析

2.2.1 程序功能

2.2.2 test_dma解析

2.2.3 4K对齐

2.2.4 posix_memalign

2.2.5 ioctl or write

2.2.4 test_dma

2.2.4 write_from_buffer

2.3 生成测试文件

2.3.1 dd 命令

2.3.2 BS 参数

3. 驱动解析

3.1 核心驱动模块

3.2 字符设备接口

3.3 其他文件

4. libxdma_api.h

4.1 功能概述

4.2 关键 API 函数

4.2.1 设备生命周期管理

4.2.2 中断管理


1. 简介

本文记录自己调试 Xilinx XDMA 驱动过程中的一些实用测试指南,有助于理解其工作原理和性能优化要点。主要涵盖从基本的 H2C (Host to Card) 数据传输测试到深入的带宽分析和驱动核心解析。

文章还进行了多平台(x86、ZCU102、AGX)下的 H2C 带宽测试,对比了不同 PCIe 配置(Gen2x4, Gen3x4, Gen3x8, Gen2x1)下的软件带宽(SW BW)和 ILA 抓取到的硬件带宽,并对带宽利用率进行了深入分析。

2. 基本测试

2.1 H2C 测试

2.1.1 MRRS

MRRS,Maximum Read Request Size,最大读请求数据量。

其值可以通过 lspci 查看:

>> sudo lspci -vv -s 01:00.0
---
DevCtl: CorrErr- NonFatalErr- FatalErr- UnsupReq-RlxdOrd+ ExtTag+ PhantFunc- AuxPwr- NoSnoop+MaxPayload 128 bytes, MaxReadReq 512 bytes

信息解读: 

1)MaxPayload(Maximum Payload Size)

  • 含义:MaxPayload 表示设备在单次事务(Transaction)中能够发送或接收的最大数据负载(Payload),较大的有效载荷通常可以提高数据传输的效率,因为每个 TLP 包含更多的数据,从而减少了包头开销(例如路由信息、序列号等)。
  • 作用:PCIe 链路的两端(设备和主机)会协商一个共同的 MaxPayload 值(取两者支持的最小值)。较大的值可以提高数据传输效率(减少协议开销),但需要硬件支持。
  • 取值范围:128B、256B、512B(取决于设备能力和 PCIe 版本)。
  • 配置:通常由 BIOS/固件或操作系统在枚举 PCIe 设备时自动协商。

2)MaxReadReq(Maximum Read Request Size)

  • 含义:MaxReadReq 表示设备发起读请求(Read Request)时一次可以请求的最大数据量。
  • 作用:当设备需要从主机内存读取数据时,会通过读请求分块获取。较大的 MaxReadReq 可以减少请求次数,提升读取性能(尤其对大块数据有利)。
  • 取值范围:128B、256B、512B、1024B、4096B(取决于 PCIe 版本和设备能力)。
  • 配置:同样由系统自动协商,但某些驱动或固件可能允许调整(如通过寄存器配置)。

3)两者的关系

  • MaxPayload 是数据传输的实际负载大小,而 MaxReadReq 是读操作的请求规模。两者共同影响 PCIe 总线的效率。
  • 例如:即使 MaxReadReq=512B,如果对端设备的 MaxPayload=128B,则 512B 的读请求会被拆分为 4 个 128B 的传输。

2.1.2 抓取 H2C 数据

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./datafile0_4K.bin -s 4096 -a 0 -c 1 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x1000, offset 0x0, count 1
host buffer 0x2000 = 0xaaab0b6c1000
#0: CLOCK_MONOTONIC 0.000097821 sec. write 4096 bytes
** Avg time device /dev/xdma0_h2c_0, total time 97821 nsec, avg_time = 97821.000000, size = 4096, BW = 41.872398
/dev/xdma0_h2c_0 ** Average BW = 4096, 41.872398

BW = 4096 bytes / 0.000097821 sec = 41.872398 MB/s

通过 ILA 抓取到的波形:

共计传输 4096 bytes 数据,分 8 次请求:

  • 第一次地址从 0 开始,第二次地址从 512 开始,以此类推。
  • 每次请求传输 512 bytes 数据量。

2.1.3 数据位宽

  • 数据位宽:128 bit
  • 在 XDMA IP 中设置

通过 ILA 抓取的数据进行验证: 

2.1.4 数据对比

通过原始文件进行对比:

Notepad++ 中需安装插件 HEX-Editor。

2.1.5 写入地址测试

向地址 512(dec) 写入 1024 bytes 数据:

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./datafile0_4K.bin -s 1024 -a 512 -c 1 -v

Address Editor 地址分配如下:

通过 ILA 也可抓取到首个写入的地址为 512(dec)。

2.1.6 带宽测试

  • 1)x86_Gen2x4
  • 2)x86_Gen3x4
  • 3)x86_Gen3x8
  • 4)ZCU102_Gen2x1(Downgraded from Gen2x4)
  • 5)AGX_Gen3x4
2.1.6.1 x86_Gen2x4
>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((1024*1024*1024)) -a 0 -c 5 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x40000000, offset 0x0, count 5
host buffer 0x40001000 = 0x7f20f85a3000
#0: CLOCK_MONOTONIC 0.688490023 sec. write 1073741824 bytes
#1: CLOCK_MONOTONIC 0.684646238 sec. write 1073741824 bytes
#2: CLOCK_MONOTONIC 0.685902333 sec. write 1073741824 bytes
#3: CLOCK_MONOTONIC 0.685129301 sec. write 1073741824 bytes
#4: CLOCK_MONOTONIC 0.684099317 sec. write 1073741824 bytes
** Avg time device /dev/xdma0_h2c_0, total time 3428267212 nsec, avg_time = 685653440.000000, size = 1073741824, BW = 1566.012451
/dev/xdma0_h2c_0 ** Average BW = 1073741824, 1566.012451

SW BW = 1566.012451 MB/s

通过 ILA 抓取的带宽:

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((32*1024)) -a 0 -c 1 -v

  • clks = (10256 - 512) / 125MHz = 77.952 us
  • ILA BW = 128*1024 bytes / 77.952 us = 1681.444992 MB/s

2.1.6.2 x86_Gen3x4
>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((2*1024*1024*1024)) -a 0 -c 5 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x80000000, offset 0x0, count 5
host buffer 0x80001000 = 0x7f3cbfbb9000
#0: CLOCK_MONOTONIC 0.734153732 sec. write 2147483648 bytes
#1: CLOCK_MONOTONIC 0.737794115 sec. write 2147483648 bytes
#2: CLOCK_MONOTONIC 0.735888646 sec. write 2147483648 bytes
#3: CLOCK_MONOTONIC 0.735151981 sec. write 2147483648 bytes
#4: CLOCK_MONOTONIC 0.740200678 sec. write 2147483648 bytes
** Avg time device /dev/xdma0_h2c_0, total time 3683189152 nsec, avg_time = 736637824.000000, size = 2147483648, BW = 2915.250244
/dev/xdma0_h2c_0 ** Average BW = 2147483648, 2915.250244

SW BW = 2915.250244 MB/s

通过 ILA 抓取的带宽:

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((128*1024)) -a 0 -c 1 -v

  • clks = (10039 - 512) / 250MHz = 38.108 us
  • ILA BW = 128*1024 bytes / 38.108 us = 3439.487772 MB/s

2.1.6.3 x86_Gen3x8
sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((2*1024*1024*1024)) -a 0 -c 5 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x80000000, offset 0x0, count 5
host buffer 0x80001000 = 0x7fb265a4a000
#0: CLOCK_MONOTONIC 0.441520704 sec. write 2147483648 bytes
#1: CLOCK_MONOTONIC 0.445825194 sec. write 2147483648 bytes
#2: CLOCK_MONOTONIC 0.454952837 sec. write 2147483648 bytes
#3: CLOCK_MONOTONIC 0.445229208 sec. write 2147483648 bytes
#4: CLOCK_MONOTONIC 0.455560712 sec. write 2147483648 bytes
** Avg time device /dev/xdma0_h2c_0, total time 2243088655 nsec, avg_time = 448617728.000000, size = 2147483648, BW = 4786.889648
/dev/xdma0_h2c_0 ** Average BW = 2147483648, 4786.889648

SW BW = 4786.889648 MB/s

通过 ILA 抓取的带宽:

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((256*1024)) -a 0 -c 1 -v

  • clks = (9965 - 512) / 250MHz = 37.812 us
  • ILA BW = 256*1024 bytes / 37.812 us = 6932.825558 MB/s

2.1.6.4 ZCU102_Gen2x1

(Downgraded from Gen2x4)

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((256*1024*1024)) -a 0 -c 5 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x10000000, offset 0x0, count 5
host buffer 0x10001000 = 0xffff70fde000
#0: CLOCK_MONOTONIC 0.751145346 sec. write 268435456 bytes
#1: CLOCK_MONOTONIC 0.750490920 sec. write 268435456 bytes
#2: CLOCK_MONOTONIC 0.750135260 sec. write 268435456 bytes
#3: CLOCK_MONOTONIC 0.752382184 sec. write 268435456 bytes
#4: CLOCK_MONOTONIC 0.750452903 sec. write 268435456 bytes
** Avg time device /dev/xdma0_h2c_0, total time 3754606613 nsec, avg_time = 750921344.000000, size = 268435456, BW = 357.474792
/dev/xdma0_h2c_0 ** Average BW = 268435456, 357.474792

SW BW = 357.474792 MB/s

通过 ILA 抓取的带宽:

>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((32*1024)) -a 0 -c 1 -v

  • clks = (10377 - 512) / 125MHz = 78.92 us
  • ILA BW = 32*1024 bytes / 78.92 us = 415.2052712 MB/s

2.1.6.5 AGX_Gen3x4
>> sudo ./dma_to_device -d /dev/xdma0_h2c_0 -f ./data_2G.bin -s $((1024*1024*1024)) -a 0 -c 5 -v
---
dev /dev/xdma0_h2c_0, addr 0x0, aperture 0x0, size 0x40000000, offset 0x0, count 5
host buffer 0x40001000 = 0xffff44c8e000
#0: CLOCK_MONOTONIC 0.503695296 sec. write 1073741824 bytes
#1: CLOCK_MONOTONIC 0.493838208 sec. write 1073741824 bytes
#2: CLOCK_MONOTONIC 0.491024864 sec. write 1073741824 bytes
#3: CLOCK_MONOTONIC 0.493731072 sec. write 1073741824 bytes
#4: CLOCK_MONOTONIC 0.492071488 sec. write 1073741824 bytes
** Avg time device /dev/xdma0_h2c_0, total time 2474360928 nsec, avg_time = 494872160.000000, size = 1073741824, BW = 2169.735840
/dev/xdma0_h2c_0 ** Average BW = 1073741824, 2169.735840

SW BW = 2169.735840 MB/s

2.1.7 带宽分析

  • 1)x86_Gen2x4
  • 2)x86_Gen3x4
  • 3)x86_Gen3x8
  • 4)ZCU102_Gen2x1(Downgraded from Gen2x4)
2.1.7.1 x86_Gen2x4

该配置下:

  • Link Speed = 5 GT/s × 4 = 20 GT/s = 2500 MB/s
  • MAX BW = 128 bit × 125 MHz = 16000 Mbit/s = 2000 MB/s
  • 实测 SW BW:1566.012451 MB/s,利用率:62.6%
  • 实测 ILA BW:1681.444992 MB/s,利用率:67.3%

2.1.7.2 x86_Gen3x4

该配置下:

  • Link Speed = 8 GT/s × 4 = 32 GT/s = 4000 MB/s
  • MAX BW = 128 bit × 250 MHz = 32000 Mbit/s = 4000 MB/s
  • 实测 SW BW:2915.250244 MB/s,利用率:72.9%
  • 实测 ILA BW:3439.487772 MB/s,利用率:86.0%

2.1.7.3 x86_Gen3x8

该配置下,理论带宽:

  • Link Speed = 8 GT/s × 8 = 64 GT/s = 8000 MB/s
  • MAX BW = 256 bit × 250 MHz = 64000 Mbit/s = 8000 MB/s
  • 实测 SW BW:4786.889648 MB/s,利用率:59.8%
  • 实测 ILA BW:6932.825558 MB/s,利用率:86.7%

2.1.7.4 ZCU102_Gen2x1

(Downgraded from Gen2x4)

该配置下,理论带宽:

  • Link Speed = 5 GT/s × 1 = 625 MB/s
  • 实测 SW BW:357.474792 MB/s,利用率:57.2%
  • 实测 ILA BW:415.2052712 MB/s,利用率:66.4%

2.1.8 win10 中测试

1)查看 PCIe 详细信息

2)使用自动测试程序

>> .\xdma_test.exe
---
Detected XDMA AXI-MM design.
Found h2c_0 and c2h_0:Initiating H2C_0 transfer of 4096 bytes...Initiating C2H_0 transfer of 4096 bytes...Transfers completed. Comparing data... OK!
Found h2c_1 and c2h_1:Initiating H2C_1 transfer of 4096 bytes...Initiating C2H_1 transfer of 4096 bytes...Transfers completed. Comparing data... OK!
Found h2c_2 and c2h_2:Initiating H2C_2 transfer of 4096 bytes...Initiating C2H_2 transfer of 4096 bytes...Transfers completed. Comparing data... OK!
Found h2c_3 and c2h_3:Initiating H2C_3 transfer of 4096 bytes...Initiating C2H_3 transfer of 4096 bytes...Transfers completed. Comparing data... OK!
Success!

3)使用 AXI-Lite 控制 GPIO LED

>> .\xdma_rw.exe user write 0x4 0x0 # Config Output
>> .\xdma_rw.exe user write 0x0 0x1 # Set High
---
1 bytes written in 0.000012s

2.2 dma_to_device 分析

2.2.1 程序功能

  • 通过 Xilinx XDMA Linux 驱动提供的字符设备接口 (/dev/xdma*_h2c_*),将数据从主机内存传输到 FPGA 或连接到 FPGA 的内存区域。
  • 测量 DMA 传输的平均带宽。
  • 提供灵活的命令行选项来配置传输参数。

帮助信息:

>> ./dma_to_device -h
---
./dma_to_deviceusage: ./dma_to_device [OPTIONS]Write via SGDMA, optionally read input from a file.-d (--device) device (defaults to /dev/xdma0_h2c_0)-a (--address) the start address on the AXI bus-k (--aperture) memory address aperture-s (--size) size of a single transfer in bytes, default 32,-o (--offset) page offset of transfer-c (--count) number of transfers, default 1-f (--data infile) filename to read the data from.-w (--data outfile) filename to write the data of the transfers-h (--help) print usage help and exit-v (--verbose) verbose outputReturn code:0: all bytes were dma'ed successfully< 0: error

2.2.2 test_dma解析

static int test_dma(char    *devname    , // 指定要使用的 DMA 设备节点路径uint64_t addr       , // 指定 FPGA 端的目标内存地址uint64_t aperture   , // 定义可访问的地址范围大小,当非零时,使用 IOCTL_XDMA_APERTURE_W ioctl 进行传输,为零时使用常规 write 操作uint64_t size       , // 单次 DMA 传输的数据量(字节)uint64_t offset     , // 缓冲区内的页偏移量,用于测试非对齐访问的情况uint64_t count      , // 重复执行 DMA 传输的次数,用于性能测试,多次传输可以计算平均带宽char    *infname    , // 输入文件名char    *ofname     ) // 如果指定,每次 DMA 后会将缓冲区内容写入该文件

核心功能(test_dma 函数)功能分析:

1)打开设备:打开指定的 XDMA H2C 字符设备文件 (devname),用于执行 DMA 写入操作。

2)打开文件(可选):

  • 如果指定了输入文件 (infname),则打开该文件用于读取要传输的数据。
  • 如果指定了输出文件 (ofname),则打开/创建该文件,用于将实际传输的数据写入其中 (这在检查传输结果或处理 underflow 时可能有用)。

3)内存分配:

使用 posix_memalign 分配一个内存缓冲区。posix_memalign 用于分配对齐的内存,这对于 DMA 操作通常是必需的(DMA 控制器通常需要物理地址连续且对齐的内存块)。分配的大小为 size + 4096 并对齐到 4096 字节(一个典型的页大小),然后使用 offset (在主函数中被限制在 0-4095 范围内) 来确定缓冲区的起始地址。

4)读取输入文件数据(如果指定):如果打开了输入文件,则调用 read_to_buffer(来自 dma_utils.c)将文件内容读入分配的缓冲区。

5)DMA 传输循环: 循环 count 次执行 DMA 写入操作。

  • 计时:在每次传输前后使用 clock_gettime(CLOCK_MONOTONIC) 获取时间戳,以测量单次传输的时间。
  • 执行写入:
    • Aperture 模式:如果 aperture 参数非零,则使用 ioctl 系统调用 IOCTL_XDMA_APERTURE_W 进行写操作。这是一种通过预先配置的内存窗口(aperture)进行 DMA 访问的方式,可能用于访问 FPGA 片上内存或连接到 FPGA 的特定物理地址。它使用 struct xdma_aperture_ioctl 来指定缓冲区、长度、FPGA 端地址和 aperture 大小。
    • 标准模式:如果 aperture 参数为零,则调用 write_from_buffer (来自 dma_utils.c),这个函数很可能内部调用标准的 write() 系统调用将缓冲区内容写入设备文件。XDMA 驱动会拦截这个 write() 调用,并将其转换为一个 H2C DMA 传输到指定的 addr。
  • 检查 underflow:检查实际传输的字节数(bytes_done)是否小于请求的大小(size)。如果发生 underflow,则打印警告并设置 underflow 标志。
  • 记录时间: 计算本次传输花费的时间,并累加到 total_time。
  • 写入输出文件(如果指定):如果打开了输出文件,则调用 write_from_buffer 将实际传输的数据(bytes_done)写入输出文件,并更新文件写入偏移量。

6)计算带宽: 在循环结束后,如果所有传输都成功(没有 underflow),则计算平均传输时间,并基于此计算平均带宽(字节/时间)。

7)清理: 关闭所有打开的文件描述符(设备、输入、输出),释放分配的内存缓冲区。

8)返回状态: 根据传输是否成功(特别是是否有 underflow)返回 0 或相应的错误码。

2.2.3 4K对齐

1)为什么要 4K 对齐?

现代操作系统(如 Linux/Windows)以 4KB 为单位管理内存页。

如果 DMA 缓冲区未按 4K 对齐:

  • 额外拷贝开销:内核可能需要先拷贝数据到对齐的临时缓冲区,再传递给硬件,增加延迟。
  • TLB(页表缓存)效率下降:未对齐的缓冲区可能跨越多个内存页,导致更多 TLB 查询,降低性能。

PCIe 协议的数据包(TLP)通常对齐到 4KB 边界。如果 DMA 传输的地址或长度未对齐:

  • 分片(Payload Split):一个 4K 跨界的传输会被拆分成多个 TLP 包,增加 PCIe 总线的开销。
  • 带宽利用率下降:未对齐的传输可能无法充分利用最大有效载荷(如 256B/512B/4KB 的 PCIe 块大小)。

XDMA IP 核的 Scatter-Gather 描述符(Descriptor)要求 4K 对齐:

  • 《PG195》相邻描述符块不得跨 4K 地址边界。

2)未对齐的后果,示例

  • 假设 XDMA 从 FPGA 向主机传输 3KB 数据,但缓冲区未 4K 对齐:

    • PCIe 层:可能生成 2 个 TLP 包(一个 2KB + 一个 1KB),而非单个 3KB 包。

    • 内存子系统:若缓冲区跨 2 个物理页,需两次页表查询。

    • 性能损失:实测吞吐量可能下降 10%~30%。

3)如何查看OS是否 4k 对齐?

查看 PAGE_SIZE 变量

>> getconf PAGE_SIZE
---
4096

或者:

手动测试 mmap 对齐行为

通过 mmap 系统调用动态分配一段 4KB 大小的匿名内存区域,打印其起始地址,然后立即释放该内存。

#include <sys/mman.h>
#include <stdio.h>
int main() {void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);printf("Mapped address: %p\n", addr);munmap(addr, 4096);return 0;
}

执行编译并测试:

>> gcc 4k_tt.c
>> ./a.out
---
Mapped address: 0x7fb295e81000

原理解释:

Linux 内核默认将 mmap 返回的内存地址对齐到页大小(4KB),因此该代码可验证系统页大小是否为 4KB。

参数说明:

  • NULL:让内核自动选择分配地址。
  • 4096:分配的内存大小(4KB)。
  • PROT_READ | PROT_WRITE:内存可读可写。
  • MAP_PRIVATE | MAP_ANONYMOUS:
    • MAP_PRIVATE:私有映射,修改不会同步到文件(因未关联文件)。
    • MAP_ANONYMOUS:匿名映射,不关联任何文件,内容初始化为零。
  • -1 和 0:因是匿名映射,忽略文件描述符和偏移量。

2.2.4 posix_memalign

1)aligned_alloc 与 posix_memalign

posix_memalign((void **)&allocated, 4096 /*alignment */ , size + 4096);

2.2.5 ioctl or write

1)数据传输机制差异

  • ioctl方式:
    • 通过专用DMA控制命令(IOCTL_XDMA_APERTURE_W)触发DMA引擎
    • 由FPGA的DMA控制器直接管理数据传输
    • 支持更复杂的参数配置(如地址窗口/aperture设置)
  • write_from_buffer方式:
    • 使用标准文件写操作(write()系统调用)
    • 依赖Linux内核的通用文件I/O机制
    • 数据可能经过额外的内核缓冲区拷贝

2)适用场景

  • ioctl方式更适合:
    • 需要精确控制DMA参数(地址、突发长度等)
    • 追求最大吞吐量和最低延迟
    • 大块连续数据传输
  • write方式更适合:
    • 简单的小数据量传输
    • 需要与常规文件操作保持一致的接口
    • 开发原型阶段快速验证

3)性能测量差异

在测试代码中,两种方式都使用了相同的计时方法(clock_gettime),但:

  • ioctl 的计时反映的是 DMA 引擎的实际工作时间
  • write 的计时包含内核处理时间、可能的缓冲区拷贝时间等

2.2.4 test_dma

static int test_dma(char    *devname    ,uint64_t addr       ,uint64_t aperture   ,uint64_t size       ,uint64_t offset     ,uint64_t count      ,char    *infname    ,char    *ofname     )
{uint64_t i;ssize_t rc;size_t bytes_done = 0;size_t out_offset = 0;char *buffer = NULL;char *allocated = NULL;struct timespec ts_start, ts_end;int infile_fd = -1;int outfile_fd = -1;int fpga_fd = open(devname, O_RDWR);long total_time = 0;float result;float avg_time = 0;int underflow = 0;if (fpga_fd < 0) {fprintf(stderr, "unable to open device %s, %d.\n",devname, fpga_fd);perror("open device");return -EINVAL;}if (infname) {infile_fd = open(infname, O_RDONLY);if (infile_fd < 0) {fprintf(stderr, "unable to open input file %s, %d.\n",infname, infile_fd);perror("open input file");rc = -EINVAL;goto out;}}if (ofname) {outfile_fd =open(ofname, O_RDWR | O_CREAT | O_TRUNC | O_SYNC,0666);if (outfile_fd < 0) {fprintf(stderr, "unable to open output file %s, %d.\n",ofname, outfile_fd);perror("open output file");rc = -EINVAL;goto out;}}posix_memalign((void **)&allocated, 4096 /*alignment */ , size + 4096);if (!allocated) {fprintf(stderr, "OOM %lu.\n", size + 4096);rc = -ENOMEM;goto out;}buffer = allocated + offset;if (verbose)fprintf(stdout, "host buffer 0x%lx = %p\n",size + 4096, buffer); if (infile_fd >= 0) {rc = read_to_buffer(infname, infile_fd, buffer, size, 0);if (rc < 0 || rc < size)goto out;}for (i = 0; i < count; i++) {/* write buffer to AXI MM address using SGDMA */rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);if (aperture) {struct xdma_aperture_ioctl io;io.buffer = (unsigned long)buffer;io.len = size;io.ep_addr = addr;io.aperture = aperture;io.done = 0UL;rc = ioctl(fpga_fd, IOCTL_XDMA_APERTURE_W, &io);if (rc < 0 || io.error) {fprintf(stdout,"#%d: aperture W ioctl failed %d,%d.\n",i, rc, io.error);goto out;}bytes_done = io.done;} else {rc = write_from_buffer(devname, fpga_fd, buffer, size,addr);if (rc < 0)goto out;bytes_done = rc;}rc = clock_gettime(CLOCK_MONOTONIC, &ts_end);if (bytes_done < size) {printf("#%d: underflow %ld/%ld.\n",i, bytes_done, size);underflow = 1;}/* subtract the start time from the end time */timespec_sub(&ts_end, &ts_start);total_time += ts_end.tv_nsec;/* a bit less accurate but side-effects are accounted for */if (verbose)fprintf(stdout,"#%lu: CLOCK_MONOTONIC %ld.%09ld sec. write %ld bytes\n",i, ts_end.tv_sec, ts_end.tv_nsec, size); if (outfile_fd >= 0) {rc = write_from_buffer(ofname, outfile_fd, buffer,bytes_done, out_offset);if (rc < 0 || rc < bytes_done)goto out;out_offset += bytes_done;}}if (!underflow) {avg_time = (float)total_time/(float)count;result = ((float)size)*1000/avg_time;if (verbose)printf("** Avg time device %s, total time %ld nsec, avg_time = %f, size = %lu, BW = %f \n",devname, total_time, avg_time, size, result);printf("%s ** Average BW = %lu, %f\n", devname, size, result);}out:close(fpga_fd);if (infile_fd >= 0)close(infile_fd);if (outfile_fd >= 0)close(outfile_fd);free(allocated);if (rc < 0)return rc;/* treat underflow as error */return underflow ? -EIO : 0;
}

2.2.4 write_from_buffer

Linux 系统中,write()(及类似的系统调用)最多传输 0x7ffff000(2,147,479,552)字节的数据,并返回实际传输的字节数。

ssize_t write_from_buffer(char *fname, int fd, char *buffer, uint64_t size,uint64_t base)
{ssize_t rc;uint64_t count = 0;char *buf = buffer;off_t offset = base;int loop = 0;while (count < size) {uint64_t bytes = size - count;if (bytes > RW_MAX_SIZE)bytes = RW_MAX_SIZE;if (offset) {rc = lseek(fd, offset, SEEK_SET);if (rc != offset) {fprintf(stderr, "%s, seek off 0x%lx != 0x%lx.\n",fname, rc, offset);perror("seek file");return -EIO;}}/* write data to file from memory buffer */rc = write(fd, buf, bytes);if (rc < 0) {fprintf(stderr, "%s, write 0x%lx @ 0x%lx failed %ld.\n",fname, bytes, offset, rc);perror("write file");return -EIO;}count += rc;if (rc != bytes) {fprintf(stderr, "%s, write underflow 0x%lx/0x%lx @ 0x%lx.\n",fname, rc, bytes, offset);break;}buf += bytes;offset += bytes;loop++;}	if (count != size && loop)fprintf(stderr, "%s, write underflow 0x%lx/0x%lx.\n",fname, count, size);return count;
}

2.3 生成测试文件

2.3.1 dd 命令

dd --help
---
Usage: dd [OPERAND]...or:  dd OPTION
Copy a file, converting and formatting according to the operands.bs=BYTES        read and write up to BYTES bytes at a time (default: 512);overrides ibs and obscbs=BYTES       convert BYTES bytes at a timeconv=CONVS      convert the file as per the comma separated symbol listcount=N         copy only N input blocksibs=BYTES       read up to BYTES bytes at a time (default: 512)if=FILE         read from FILE instead of stdiniflag=FLAGS     read as per the comma separated symbol listobs=BYTES       write BYTES bytes at a time (default: 512)of=FILE         write to FILE instead of stdoutoflag=FLAGS     write as per the comma separated symbol listseek=N          skip N obs-sized blocks at start of outputskip=N          skip N ibs-sized blocks at start of inputstatus=LEVEL    The LEVEL of information to print to stderr;'none' suppresses everything but error messages,'noxfer' suppresses the final transfer statistics,'progress' shows periodic transfer statisticsN and BYTES may be followed by the following multiplicative suffixes:
c =1, w =2, b =512, kB =1000, K =1024, MB =1000*1000, M =1024*1024, xM =M,
GB =1000*1000*1000, G =1024*1024*1024, and so on for T, P, E, Z, Y.

    示例:

    1)生成随机文件

    dd if=/dev/urandom of=data_2G.bin bs=2M count=1024

    相比 /dev/random,/dev/urandom 速度更快,但随机性稍弱(仍足够一般用途)。

    2)生成全 0 文件

    dd if=/dev/zero of=data_2G.bin bs=2M count=1024

    3)进度查看

    默认无进度显示,可添加 status=progress 选项(如 dd if=... of=... status=progress)

    dd if=/dev/zero of=data_2G.bin bs=2M count=1024 status=progress

    2.3.2 BS 参数

    重点解释 bs 这个参数,用于控制单次读写的数据量:

    • bs=1024 表示每次读取或写入 1024 字节(即 1KB)的数据块。
    • dd 会按这个大小逐块处理数据,直到完成 count 指定的总块数。
    • 最终文件大小 = bs × count。

    dd if=/dev/zero of=large_file bs=1024M count=1dd if=/dev/zero of=large_file bs=1M count=1024

    关键区别:

    1)I/O 操作次数

    • 命令 1(bs=1G):
      • 仅需 1 次 I/O 操作(单次分配和写入 1GB 数据)。
      • 优点:减少系统调用次数,理论速度更快。
      • 缺点:可能因一次性分配大内存导致短暂卡顿(依赖系统内存管理)。
    • 命令 2(bs=1M):
      • 需 1024 次 I/O 操作(每次写入 1MB)。
      • 优点:内存占用更平稳,适合资源受限环境。
      • 缺点:频繁系统调用可能降低效率(尤其机械硬盘或高延迟存储)。

    2)实际写入速度

    • 命令 1 通常更快:
      • 现代操作系统和存储设备(如 SSD)对大块连续写入优化较好。
      • 减少 I/O 调度和上下文切换开销。
    • 命令 2 可能稍慢:频繁的小块写入可能触发更多磁盘寻址或缓存刷新(尤其机械硬盘)。

    3)系统资源占用

    • 命令 1:可能短暂占用较高内存(需缓存 1GB 数据块),对低内存机器不友好。
    • 命令 2:内存占用更稳定(每次仅处理 1MB),适合嵌入式设备或低配置环境。

    3. 驱动解析

    3.1 核心驱动模块

    1)xdma_mod.c / xdma_mod.h

    • 驱动的主模块,处理 PCIe 设备的初始化、探测(probe)、卸载等。
    • 定义 PCIe 设备 ID、驱动注册、设备资源分配等。

    2)libxdma.c / libxdma.h

    • 提供 XDMA 的核心功能库,如 DMA 缓冲区管理、中断处理、寄存器操作等。
    • 可能包含底层硬件访问的通用函数。

    3)xdma_thread.c / xdma_thread.h

    • 处理 DMA 传输的线程或工作队列,用于异步数据传输或事件处理。

    3.2 字符设备接口

    XDMA 通过多个字符设备文件(/dev/xdma*)向用户空间暴露功能,每个文件对应一个子模块:

    1)xdma_cdev.c / xdma_cdev.h

    • 字符设备的通用框架,实现 file_operations 结构体(如 open、read、write、ioctl 等)。

    2)cdev_bypass.c

    • 可能实现“旁路模式”设备,允许用户空间直接访问 PCIe 设备的 BAR 空间(寄存器或内存)。

    3)cdev_ctrl.c / cdev_ctrl.h

    • 控制设备,用于配置 DMA 引擎、查询设备状态或发送控制命令(如通过 ioctl)。

    4)cdev_sgdma.c / cdev_sgdma.h

    • 实现 Scatter-Gather DMA(SGDMA)功能,支持分散/聚集数据传输(高效处理非连续内存)。

    5)cdev_xvc.c / cdev_xvc.h

    • 可能用于 Xilinx Virtual Cable(XVC)功能,通过 PCIe 实现 JTAG 调试接口。

    6)cdev_events.c

    • 处理 DMA 事件或中断,例如完成通知或错误上报。

    3.3 其他文件

    1)Makefile

    • 驱动编译的构建规则,定义如何编译内核模块。

    2)version.h

    • 驱动版本号或宏定义,可能用于兼容性检查或日志输出。

    4. libxdma_api.h

    4.1 功能概述

    这个文件是 XDMA 驱动的核心 API 头文件,定义了 XDMA 驱动提供给其他模块(如字符设备驱动 cdev_*)或用户空间的关键函数接口和数据结构。它的主要作用是 抽象硬件操作,提供统一的 DMA 控制接口

    关键作用:

    • 定义 XDMA 驱动的核心 API:其他模块(如 cdev_ctrl.c、cdev_sgdma.c)通过调用这些接口与硬件交互,无需直接操作寄存器或 PCIe 配置。
    • 统计与状态管理:记录 DMA 传输的统计信息(如提交/完成的读写次数)。
    • 中断与 DMA 传输控制:提供用户中断注册、DMA 数据传输提交等关键功能。

    设计意图:

    • 硬件抽象层(HAL):将 PCIe 配置、DMA 引擎操作、中断处理等底层细节封装成统一接口,简化上层模块开发。
    • 灵活性:
      • 支持多通道 DMA 和用户自定义中断。
      • 允许同步/异步数据传输模式。
    • 可扩展性:注释中提到的 xdma_get_channel_state 和 xdma_channel_restart 等未实现 API,预留了未来功能扩展的可能。

    4.2 关键 API 函数

    4.2.1 设备生命周期管理

    1)xdma_device_open()

    • 作用:初始化 PCIe 设备,映射 BAR 空间,配置 DMA 通道和用户中断。
    • 调用时机:PCIe 驱动的 probe() 函数中。
    • 参数:
      • mod_name:驱动模块名(用于中断注册)。
      • pdev:PCIe 设备指针。
      • user_max/h2c_channel_max/c2h_channel_max:用户中断和 DMA 通道的最大数量(实际值可能被驱动调整)。

    2)xdma_device_close()

    • 作用:释放资源,禁用中断,准备设备移除。
    • 调用时机:PCIe 驱动的 remove() 函数中。
    • xdma_device_restart()
    • 作用:重启 FPGA 设备(用于错误恢复或重新配置)。

    4.2.2 中断管理

    1)xdma_user_isr_register()

    • 作用:注册用户自定义中断处理函数(如 FPGA 触发的中断)。
    • 参数:
      • mask:中断号位掩码(支持 0~15)。
      • handler:中断处理函数,传入 NULL 表示注销。

    2)xdma_user_isr_enable/disable()

    • 作用:启用/禁用指定用户中断。

    http://www.xdnf.cn/news/10399.html

    相关文章:

  • Launcher3体系化之路
  • Spring Boot对一些技术框架进行了统一版本号管理
  • vue3常用组件有哪些
  • 【STM32F1标准库】理论——外部中断
  • YOLOv5 环境配置指南
  • 高速串行通信解惑说明
  • 数据结构-排序-排序的七种算法(2)
  • Java流【全】
  • vscode + cmake + ninja+ gcc 搭建MCU开发环境
  • 6v6-导航收录:2025年网站自动引流终极方案 - 提升SEO排名新策略
  • PCIe—TS1/TS2 之Polling.Active(一)
  • Java异步编程:CompletionStage接口详解
  • rv1126b sdk移植
  • QT中更新或添加组件时出现“”qt操作至少需要一个处于启用状态的有效资料档案库“解决方法”
  • 深入理解设计模式之观察者模式
  • 59、干系人概述
  • Windows系统时间怎么设置
  • Centos7 中Gunicorn的安装配置
  • Docker 在云环境中的部署:AWS/ECS 与 Azure/AKS 的实践对比
  • 自动驾驶系统研发系列—激光雷达感知延迟:自动驾驶安全的隐形隐患?
  • opencv使用经典bug
  • OD 算法题 B卷【文件目录大小】
  • 基于ssm+mysql的大创项目申报管理系统(含LW+PPT+源码+系统演示视频+安装说明)
  • 历年中山大学计算机保研上机真题
  • java swing 晃动鼠标改变背景颜色
  • PySide6 GUI 学习笔记——常用类及控件使用方法(标签控件QLabel)
  • Git初识Git安装
  • Spring Boot,两种配置文件
  • LeetCode 39.组合总和:回溯法与剪枝优化的完美结合
  • CCPC dongbei 2025 F