Linux 内核 PCI Endpoint 测试实战指南:基于 pci-epf-test 与 Kselftest 的完整验证流程
本文围绕 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_test 的 pci_epf_driver(probe 与驱动注册),并为其分配名为 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_size~bar5_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 写入实现)可知三条硬性约束:
- 只能在绑定 EPC 之前修改:
pci_epf_test_alloc_space()在.bind()阶段被调用,此时若设备已关联控制器(epf->epc非空),写入直接返回-EOPNOTSUPP——即"Overriding the default BAR sizes can only be done before binding"的源码依据; - 必须是 2 的幂:非幂次值返回
-EINVAL; - 部分控制器的 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(测试寄存器结构)中的 command、status、caps 等字段承载 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 控制器上排查兼容性问题提供了直接的对照基线。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00