首页
/ Linux 内核 PCI Endpoint 测试实战指南:基于 pci-epf-test 与 Kselftest 的完整验证流程

Linux 内核 PCI Endpoint 测试实战指南:基于 pci-epf-test 与 Kselftest 的完整验证流程

2026-09-05 17:52:46作者:范靓好Udolf

本文围绕 Linux 内核的 Documentation/PCI/endpoint/pci-test-howto.rst 用户指南展开,系统讲解如何使用 pci_epf_test 端点功能驱动、configfs 框架与 pci_endpoint_test Kselftest 在 Endpoint(EP)侧与 RootComplex(RC)侧完成一次完整的 PCI Endpoint 功能验证。读完本文,你将能够独立搭建 PCI Endpoint 测试环境(创建功能设备、配置 BAR/中断、绑定控制器、启动链路),并运行、解读内核自带的 16 项默认测试用例,同时理解这些操作在源码中的实现依据。

整体架构:EP 侧、RC 侧与 configfs 框架

内核的 PCI Endpoint 框架分为两层:

  • EP(Endpoint)侧:SoC 内置的 PCI Endpoint 控制器驱动(EPC,Endpoint Controller,如 51000000.pcie_ep)负责管理物理链路;功能驱动(EPF,Endpoint Function,如 pci_epf_test)负责定义设备暴露给 RC 的 PCI 功能语义(Vendor/Device ID、BAR、中断等)。
  • RC(RootComplex)侧:主机(如 x86)作为根复合体枚举 EP 设备,pci_endpoint_test 主机驱动为其创建字符设备 /dev/pci-endpoint-test.0,Kselftest 通过 ioctl 驱动 EP 执行各类测试。

EP 侧功能设备的创建、配置与绑定全部通过 configfs 完成,其实现位于 configfs 支持文件(仅在 CONFIG_PCI_ENDPOINT_CONFIGFS 启用时编译),核心设备模型在 EPF 核心代码。测试用的功能驱动实现在 pci-epf-test.c,RC 侧测试程序在 pci_endpoint_test.c

前置内核配置

EP 侧内核至少需要开启(可参考 Kselftest 自带的 config 文件):

CONFIG_PCI_ENDPOINT=y
CONFIG_PCI_ENDPOINT_CONFIGFS=y
CONFIG_PCI_EPF_TEST=m

RC 侧内核需要 CONFIG_PCI_ENDPOINT_TEST=m(即 pci_endpoint_test 主机驱动),编译选项定义见 EP Kconfig功能驱动 Kconfig

Endpoint 侧:发现控制器与功能驱动

查找 Endpoint 控制器设备

列出系统中所有已注册的 Endpoint 控制器:

# ls /sys/class/pci_epc/
  51000000.pcie_ep

如果启用了 PCI_ENDPOINT_CONFIGFS,configfs 中也有对应条目:

# ls /sys/kernel/config/pci_ep/controllers
  51000000.pcie_ep

控制器名(如 51000000.pcie_ep)是该 EP 控制器平台设备的 dev_name,后续绑定与启动命令都会用到它。

查找 Endpoint 功能驱动

列出已注册的 EP 功能驱动:

# ls /sys/bus/pci-epf/drivers
  pci_epf_test

configfs 下同样可见:

# ls /sys/kernel/config/pci_ep/functions
  pci_epf_test

从源码看,pci_epf_test 驱动在模块初始化时注册名为 pci_epf_testpci_epf_driverprobe 与驱动注册),并为其分配名为 kpcitest 的高优先级 percpu 工作队列,测试命令均在该工作队列中异步执行。

Endpoint 侧:创建 pci-epf-test 功能设备

先挂载 configfs,然后在 pci_ep 子目录下创建功能实例:

# mount -t configfs none /sys/kernel/config
# cd /sys/kernel/config/pci_ep/
# mkdir functions/pci_epf_test/func1

mkdir func1 即创建一个 PCI Endpoint 功能设备,随后被 pci_epf_test 驱动 probe(probe 时驱动会填充默认 header 与默认 BAR 大小)。框架随后在该目录下生成一组可配置字段:

# ls functions/pci_epf_test/func1
  baseclass_code  interrupt_pin  progif_code  subsys_id
  cache_line_size msi_interrupts revid       subsys_vendorid
  deviceid        msix_interrupts subclass_code vendorid

前 12 个字段由 PCI Endpoint 框架统一提供;此外 pci_epf_test 驱动还通过 add_cfs 回调追加了 bar0_sizebar5_size 六个属性(BAR size configfs 属性定义)。

设备绑定驱动后,pci_epf_test 会写入默认值。对照源码中的 test_header默认 PCI 头),可以看到:

static struct pci_epf_header test_header = {
	.vendorid       = PCI_ANY_ID,      /* 0xffff */
	.deviceid       = PCI_ANY_ID,
	.baseclass_code = PCI_CLASS_OTHERS,
	.interrupt_pin  = PCI_INTERRUPT_INTA, /* 0x0001 */
};

这与指南中观察到的现象一致:

# cat functions/pci_epf_test/func1/vendorid
  0xffff
# cat functions/pci_epf_test/func1/interrupt_pin
  0x0001

Endpoint 侧:配置 vendorid/deviceid 与中断

通过 configfs 条目即可修改设备暴露给 RC 的 PCI 配置空间:

# echo 0x104c > functions/pci_epf_test/func1/vendorid
# echo 0xb500 > functions/pci_epf_test/func1/deviceid
# echo 32 > functions/pci_epf_test/func1/msi_interrupts
# echo 2048 > functions/pci_epf_test/func1/msix_interrupts
  • vendorid / deviceid:EP 在 RC 侧 lspci 中显示为 "Texas Instruments Device b500" 即来源于此(0x104c 是 TI 的 vendor id)。
  • msi_interrupts / msix_interrupts:声明该功能使用的 MSI / MSI-X 中断数量,决定 RC 侧可用的中断向量上限。

覆盖默认 BAR 大小

pci_epf_test 的默认 BAR 大小在源码中硬编码(default_bar_size):

static size_t default_bar_size[] = { 131072, 131072, 131072, 131072, 131072, 1048576 };

与 configfs 实际输出一致:

# grep . functions/pci_epf_test/func1/pci_epf_test.0/bar?_size
  functions/pci_epf_test/func1/pci_epf_test.0/bar0_size:131072
  functions/pci_epf_test/func1/pci_epf_test.0/bar1_size:131072
  functions/pci_epf_test/func1/pci_epf_test.0/bar2_size:131072
  functions/pci_epf_test/func1/pci_epf_test.0/bar3_size:131072
  functions/pci_epf_test/func1/pci_epf_test.0/bar4_size:131072
  functions/pci_epf_test/func1/pci_epf_test.0/bar5_size:1048576

用户可覆盖默认值,例如:

# echo 1048576 > functions/pci_epf_test/func1/pci_epf_test.0/bar1_size

从源码的写回处理函数(bar_size 写入实现)可知三条硬性约束:

  1. 只能在绑定 EPC 之前修改pci_epf_test_alloc_space().bind() 阶段被调用,此时若设备已关联控制器(epf->epc 非空),写入直接返回 -EOPNOTSUPP——即"Overriding the default BAR sizes can only be done before binding"的源码依据;
  2. 必须是 2 的幂:非幂次值返回 -EINVAL
  3. 部分控制器的 BAR 是固定或预留的:这类控制器的 configfs BAR size 会被忽略(对应指南中的 Note)。

Endpoint 侧:绑定控制器并启动链路

功能设备必须绑定到某个 Endpoint 控制器驱动才能发挥作用:

# ln -s functions/pci_epf_test/func1 controllers/51000000.pcie_ep/

绑定触发 EPF 驱动的 .bind() 回调:分配 inbound 窗口、初始化 DMA 通道(若控制器支持)等。完成后写入 start 字段即可建立与主机的链路:

# echo 1 > controllers/51000000.pcie_ep/start

此时 EP 侧准备完毕。

RootComplex 侧:确认枚举结果

主机上 lspci 应能看到与 EP 侧 configfs 配置对应的设备(vendorid 0x104c、deviceid 0xb500、baseclass PCI_CLASS_OTHERS 显示为 ff00):

00:00.0 PCI bridge: Texas Instruments Device 8888 (rev 01)
01:00.0 Unassigned class [ff00]: Texas Instruments Device b500

RC 侧 pci_endpoint_test 主机驱动会为 EP 功能创建字符设备 /dev/pci-endpoint-test.0,Kselftest 正是打开该节点后通过 ioctl 下发测试命令。所有 ioctl 命令码定义在 UAPI 头文件 pcitest.h

ioctl 命令 用途
PCITEST_BAR _IO('P', 0x1) 对指定 BAR 做读写自检
PCITEST_INTX_IRQ(别名 PCITEST_LEGACY_IRQ _IO('P', 0x2) 触发传统 INTx 中断
PCITEST_MSI _IOW('P', 0x3, int) 触发指定编号的 MSI 中断
PCITEST_WRITE / PCITEST_READ / PCITEST_COPY _IOW('P', 0x4~0x6, ...) BAR 空间写/读/拷贝
PCITEST_MSIX _IOW('P', 0x7, int) 触发指定编号的 MSI-X 中断
PCITEST_SET_IRQTYPE / PCITEST_GET_IRQTYPE _IOW('P', 0x8~0x9, int) 设置/查询中断类型(INTX=0、MSI=1、MSIX=2、AUTO=3)
PCITEST_BARS _IO('P', 0xa) 校验 BAR 地址是否连续
PCITEST_DOORBELL _IO('P', 0xb) 通过 EP 的 MSI 控制器发门铃
PCITEST_BAR_SUBRANGE _IO('P', 0xc) BAR 子区映射测试
PCITEST_FLAGS_USE_DMA 0x1 READ/WRITE/COPY 时走 DMA 而非 memcpy

EP 侧驱动内部以命令/状态位图协议与 RC 通信,struct pci_epf_test_reg测试寄存器结构)中的 commandstatuscaps 等字段承载 READ/WRITE/COPY、各类中断触发、DOORBELL、BAR 子区设置等全部测试语义;数据搬运支持两种路径——直接 memcpy 与 DMA,DMA 路径通过 dmaengine API 提交 memcpy 传输(DMA 传输实现),这也是后文"DMA COPY 测试"存在能力差异的原因。

运行 PCI Endpoint Kselftest

编译与安装

Kselftest 位于 tools/testing/selftests/pci_endpoint,只需编译即可:

# cd <kernel-dir>
# make -C tools/testing/selftests/pci_endpoint

或编译并安装到系统:

# cd <kernel-dir>
# make -C tools/testing/selftests/pci_endpoint INSTALL_PATH=/usr/bin install

Makefile-O2 编译 pci_endpoint_test 并链接 -lrt -lpthread -lm,安装后测试二进制位于 <rootfs>/usr/bin/。注意 Kselftest 运行在 RC(主机)侧,而 EP 侧设备须已按前述步骤绑定并 start。

默认测试集与 TAP 输出

直接在 RC 侧运行:

# pci_endpoint_test
	TAP version 13
	1..16
	# Starting 16 tests from 9 test cases.
	#  RUN           pci_ep_bar.BAR0.BAR_TEST ...
	#            OK  pci_ep_bar.BAR0.BAR_TEST
	ok 1 pci_ep_bar.BAR0.BAR_TEST
	...(BAR1~BAR5 同理)...
	#  RUN           pci_ep_basic.CONSECUTIVE_BAR_TEST ...
	ok 7 pci_ep_basic.CONSECUTIVE_BAR_TEST
	#  RUN           pci_ep_basic.LEGACY_IRQ_TEST ...
	ok 8 pci_ep_basic.LEGACY_IRQ_TEST
	#  RUN           pci_ep_basic.MSI_TEST ...
	ok 9 pci_ep_basic.MSI_TEST
	#  RUN           pci_ep_basic.MSIX_TEST ...
	ok 10 pci_ep_basic.MSIX_TEST
	#  RUN           pci_ep_data_transfer.memcpy.READ_TEST ...
	ok 11 pci_ep_data_transfer.memcpy.READ_TEST
	#  RUN           pci_ep_data_transfer.memcpy.WRITE_TEST ...
	ok 12 pci_ep_data_transfer.memcpy.WRITE_TEST
	#  RUN           pci_ep_data_transfer.memcpy.COPY_TEST ...
	ok 13 pci_ep_data_transfer.memcpy.COPY_TEST
	#  RUN           pci_ep_data_transfer.dma.READ_TEST ...
	ok 14 pci_ep_data_transfer.dma.READ_TEST
	#  RUN           pci_ep_data_transfer.dma.WRITE_TEST ...
	ok 15 pci_ep_data_transfer.dma.WRITE_TEST
	#  RUN           pci_ep_data_transfer.dma.COPY_TEST ...
	ok 16 pci_ep_data_transfer.dma.COPY_TEST
	# PASSED: 16 / 16 tests passed.
	# Totals: pass:16 fail:0 xfail:0 xpass:0 skip:0 error:0

各测试组对应 测试源码 中的 fixture:

  • pci_ep_bar(6 项):逐个对 BAR0~BAR5 执行读写自检;若返回 -ENODATA 表示该 BAR 被禁用、-ENOBUFS 表示 BAR 被控制器预留,对应测试会自动 SKIP。
  • pci_ep_basic(4 项)CONSECUTIVE_BAR_TEST 校验各 BAR 地址空间连续(PCITEST_BARS);LEGACY_IRQ_TEST / MSI_TEST / MSIX_TEST 分别验证三类中断从 EP 触发、RC 收到的完整链路。
  • pci_ep_data_transfer(6 项):以 memcpy(EP 侧 CPU 搬运)与 dma(EP 侧 DMA 引擎搬运)两种方式,各做 READ / WRITE / COPY。测试数据大小取自源码中的 test_size 数组(1、1024、1025、1024000、1024001 字节),覆盖 1 字节边界与跨页场景。
  • pcie_ep_doorbell(按需):见下节。

跳过 DMA COPY 测试

指南特别指出:第 16 项 pci_ep_data_transfer.dma.COPY_TEST 在大多数支持 DMA 的 Endpoint 控制器上会失败,因为缺少"基于 DMA 的 MEMCPY"通道(DMA 引擎只支持设备↔内存的 READ/WRITE,不支持本地 DMA memcpy 通道时无法完成 BAR 间拷贝)。对这类控制器,建议用 -f(按 fixture 过滤)与 -v(按变体跳过)排除该用例:

# pci_endpoint_test -f pci_ep_bar -f pci_ep_basic -v memcpy -T COPY_TEST -v dma

Endpoint 门铃(Doorbell)测试

如果 EP 侧配置了 Endpoint MSI 控制器(即 EP 的 MSI 控制器本身被当作"门铃"使用),可单独运行门铃测试:

# pci_endpoint_test -f pcie_ep_doorbell

	# Starting 1 tests from 1 test cases.
	#  RUN           pcie_ep_doorbell.DOORBELL_TEST ...
	#            OK  pcie_ep_doorbell.DOORBELL_TEST
	ok 1 pcie_ep_doorbell.DOORBELL_TEST
	# PASSED: 1 / 1 tests passed.
	# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0

该用例对应 EP 驱动中的 COMMAND_ENABLE_DOORBELL / COMMAND_DOORBELL 命令位与 PCITEST_DOORBELL ioctl,用于验证 RC 向 EP 发送 MSI 门铃、EP 收到并确认的完整路径。

关键参考路径汇总

内容 路径
本文档原始指南 Documentation/PCI/endpoint/pci-test-howto.rst
EP 功能测试驱动 drivers/pci/endpoint/functions/pci-epf-test.c
configfs 支持 drivers/pci/endpoint/pci-ep-cfs.c
EPF 设备核心 drivers/pci/endpoint/pci-epf-core.c
Kselftest 源码 tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
Kselftest 编译规则 tools/testing/selftests/pci_endpoint/Makefile
Kselftest 依赖的内核配置 tools/testing/selftests/pci_endpoint/config
UAPI ioctl 定义 include/uapi/linux/pcitest.h

小结

pci-test-howto.rst 描述的工作流是内核 PCI Endpoint 子系统官方验证路径:EP 侧经 configfs 完成"创建功能 → 配置 header/BAR/中断 → 绑定控制器 → start"四步;RC 侧通过 lspci 确认枚举、打开 /dev/pci-endpoint-test.0 并由 Kselftest 经 pcitest ioctl 批量驱动 BAR 读写、三类中断、memcpy/DMA 数据搬运与门铃等 16 项测试。结合源码可以看到每一条约束(BAR size 只能绑定前修改且须为 2 的幂、DMA COPY 依赖 DMA memcpy 通道能力、BAR 预留导致 SKIP 等)都有明确的实现依据,这为在新型 Endpoint 控制器上排查兼容性问题提供了直接的对照基线。

登录后查看全文
热门项目推荐
相关项目推荐