xdma 驱动测试与分析

article/2025/7/24 3:45:54

目录

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.hkcw.cn/article/OnmHoCTfRa.shtml

    相关文章

    基因编辑首次临床救人,罕见病婴儿绝处逢生 医学奇迹搬进现实

    得了绝症竟然能靠“改写基因”治好?2025年5月,患有罕见病CPS1的婴儿KJ通过基因编辑重获新生,这场医学奇迹直接把科幻片情节搬进现实。当科学家用“分子剪刀”剪断遗传病的枷锁,我们正站在改写生命规则的转折点——未来癌症、衰老甚至农作物都能被重新编码,但这把双刃剑也藏…

    郎永淳儿子哥大双硕士家里蹲:360万换月薪8000,学历还值钱吗? 精英教育的困境

    北京的五月,蝉鸣未起,但社交媒体上关于“前央视名嘴郎永淳儿子找不到工作”的话题已经沸沸扬扬。25岁的郎俣,顶着哥伦比亚大学经济统计学和哲学双硕士的光环回国一年,却以“家里蹲”的状态成了网友口中的“高配版躺平青年”。郎俣的故事像一部现实版《变形记》。14岁赴美留…

    26岁女孩骨质疏松上热搜!竟然是因为这个习惯……很多人都有,快自查 过度防晒导致维生素D缺乏

    随着天气逐渐变热,日晒也变得越来越强烈,很多人开始重视防晒。然而,最近有一则新闻引起了广泛关注:一名26岁的女孩因为长期全面防晒,竟然被确诊为骨质疏松。这让人不禁疑惑,防晒和骨质疏松之间到底有什么联系?过度防晒会导致人体缺乏维生素D。维生素D是一种脂溶性维生素…

    “芒种不过午,三伏棉衣捂”,今年芒种在何时? 芒种三大特点解析

    2025年6月5日17点57分将迎来芒种节气。这个节气在农事上具有重要意义,有“芒种芒种,连收带种”的说法,意味着北方忙着收麦子,南方忙着种稻子,全国进入“夏收、夏种、夏管”的忙碌模式。今年的芒种被认为不一般,有三个特点。首先是芒种在端午后。2025年的端午节是5月31日,…

    SSRF 接收器

    接收请求 IP.php <?php // 定义日志文件路径 $logFile hackip.txt;// 处理删除请求 if (isset($_POST[delete])) {$ipToDelete $_POST[ip];$lines file($logFile, FILE_IGNORE_NEW_LINES);$newLines array();foreach ($lines as $line) {$parts explode( | , $line);…

    UCS(Universal Control System)能成为下一代通用控制系统吗?

    UCS&#xff08;Universal Control System&#xff09;是下一代革命性通用控制系统。它以 “云 - 网 - 端” 极简架构&#xff0c;以及软件定义、全数字化、云原生等特性&#xff0c;号称颠覆了应用近 50 年的传统 DCS 技术架构。具体介绍如下&#xff1a; 架构组成&#xff1a…

    卢伟冰称竞争从来不是小米未来的关键 挑战在于自身

    近日,小米集团ceo卢伟冰发博回答投资者提问:小米未来困难或挑战是什么。他表示:“竞争从来不是,未来也不会是关键。我一直深信: 不可胜在己,可胜在敌”责任编辑:zx0176

    南通成苏超榜一大哥 自称“南哥” 三连胜领跑积分榜

    江苏省城市足球联赛第三轮比赛中,南通队以4:0客场战胜泰州队,赢得了“苏中德比”,并取得三连胜。目前,南通队在积分榜上排名第一,继续领跑“苏超”。比赛上半场双方互有攻守,但比分一直保持在0:0。下半场开始后,南通队在短短25分钟内连进3球,最终在比赛结束前再入一球,…

    券商6月金股出炉 亚钾国际青岛啤酒获最多推荐

    根据不完全统计,在各家券商的“金股”名单中,亚钾国际和青岛啤酒获得最多推荐,均被3家券商看好。东鹏饮料、万马科技、巨化股份、沪电股份等7只股票也获得了2家券商的推荐。5月A股市场整体先扬后抑,上证指数累计上涨2.9%,深证成指上涨1.42%,创业板指上涨2.32%。进入6月,…

    差分隐私的应用领域

    差分隐私作为隐私保护的“黄金标准”&#xff0c;已从理论研究渗透至多个行业的数据全生命周期管理。其核心价值在于通过数学严格性平衡数据开放与隐私保护。 一、政府与公共数据发布 1.场景扩展 宏观统计&#xff1a;人口普查、经济普查、自然灾害损失评估&#xff1b; 公共…

    【PowerShell专栏】PowerShell对象的Method方法

    PowerShell中的方法Method与属性中的Property一样&#xff0c;默认的Method方法也是从基类继承下来的方法。我们也不能为PowerShell对象创建Method方法&#xff0c;图为Process对象Method方法。 在对象添加任何的Method方法都会出错&#xff0c;提示不能为PowerShell对象添加Me…

    未来数日将成加沙停火谈判关键窗口期 谈判僵局待破

    加沙地带人道主义危机不断加剧。由于在停火方案上存在分歧,巴勒斯坦伊斯兰抵抗运动(哈马斯)和以色列之间的停火谈判再度陷入僵局。哈马斯官员指责美方提案偏袒以色列。5月31日晚间,哈马斯高级成员巴塞姆纳伊姆再次表达了对美方提出的加沙停火提案的强烈不满,指出美方立场“…

    媒体:樊振东留洋是主动走出舒适区 探索新路径

    多哈世乒赛落幕,国乒男单冠军虽然保住,但男双创下近半个世纪以来最差战绩,男线断档危机已经浮现。外界期待樊振东回归帮助国乒度过难关,没想到他却宣布加盟德国乒乓球甲级联赛的FC萨尔布吕肯乒乓球俱乐部。北京时间6月1日,FC萨尔布吕肯乒乓球俱乐部官方宣布樊振东将加盟球…

    马斯克宣布推出全新XChat 迈向超级应用

    自2022年埃隆马斯克收购推特并将其更名为X以来,该平台一直在努力拓展其功能和用户群体。曾经以公开分享观点和制造热点话题为主的X,如今正朝着更加注重隐私和日常应用的方向发展。马斯克的愿景是将X打造成一个类似微信的“超级应用”,涵盖从社交到支付等多方面的功能。X最新…

    演员吴京夺环塔拉力赛赛段冠军 安全完赛创佳绩

    2025中国环塔国际拉力赛圆满结束,演员吴京不仅安全完赛,还获得了SS9赛段的冠军。他在社交媒体上分享了参赛照片,并表示自己很幸运地赢得了这个赛段的冠军。在难度最大的麦盖提N39赛段中,吴京与领航员配合默契,以1小时53分36秒的成绩夺得T4赛组冠军。责任编辑:zx0176

    NBA总决赛6月6日开打 新王即将诞生

    北京时间6月1日,2024-2025赛季NBA季后赛继续进行。东部决赛第六战中,印第安纳步行者主场以125:108击败纽约尼克斯队,从而以大比分4:2淘汰对手,获得分区冠军。总决赛中,印第安纳步行者的对手是俄克拉荷马雷霆。印第安纳步行者与纽约尼克斯在东部半决赛中均以下克上。常规赛…

    InfluxDB 高级查询技巧:子查询与动态字段选择实战指南

    InfluxDB 的强大不仅在于数据存储&#xff0c;更在于其支持 子查询&#xff08;Subqueries&#xff09; 和 动态字段选择&#xff08;Dynamic Field Key Selection&#xff09; 等高级查询能力。本文通过真实代码示例&#xff0c;深入解析如何利用这些功能实现复杂数据分析&…

    MyBatis、MyBatis-Plus与MyBatis-Flex的区别

    MyBatis、MyBatis-Plus与MyBatis-Flex的区别 MyBatis、MyBatis-Plus和MyBatis-Flex都是Java领域流行的持久层框架&#xff0c;它们基于MyBatis的核心思想发展而来&#xff0c;但在功能、设计理念和使用体验上有显著区别 MyBatis&#xff08;原生框架&#xff09;为基础ORM框架…

    源码解析(二):nnUNet

    原文 &#x1f600; nnU-Net 是一个用于生物医学图像分割的自配置深度学习框架&#xff0c;可自动适应不同的数据集。可用于处理和训练可能规模庞大的二维和三维医学图像。该系统分析数据集属性并配置优化的基于 U-Net 的分割流程&#xff0c;无需手动参数调整或深度学习专业知…

    七、物理.

    长度单位 运动与静止 以某个物体为参照物&#xff0c;物体的位置发生了变化称为运动&#xff0c;物体的位置没变称为静止。 速度 用于描述运动快慢的物理量&#xff0c;路程与时间之比。 1m/s 3.6km/h 匀速直线运动与变速直线运动 匀速直线运动&#xff1a;物体沿着直线…