Linux 内核 PCI Endpoint 测试函数绑定指南:pci_epf_test 的 configfs 配置与端到端验证
本篇围绕内核文档 Documentation/PCI/endpoint/function/binding/pci-test.rst 展开,系统讲解 PCI endpoint test 函数驱动 pci_epf_test 的绑定要求:名字字段、vendorid/deviceid、baseclass、中断引脚以及 MSI/MSI-X 中断数量的取值约束,并结合 drivers/pci/endpoint/functions/pci-epf-test.c 的源码逐项印证这些配置项在驱动内的真实消费路径。读完后你能够独立完成 configfs 下 test 函数设备的创建、配置、绑定与链路启动,并使用内核自带的 kselftest 对 EP 端的 BAR 读写、中断触发与数据搬运能力做端到端验证。
1. 这个绑定文档在整个 PCI Endpoint 框架中的位置
Linux 内核的 PCI endpoint 框架分为两层:
- EP 控制器(EPC,Endpoint Controller):硬件上能工作在 endpoint 模式的 PCIe 控制器,其驱动注册后出现在
/sys/class/pci_epc/(如51000000.pcie_ep); - EP 函数驱动(EPF,Endpoint Function Driver):定义 endpoint 设备的"功能"——即对 Root Complex(主机)呈现什么 PCI 头、BAR 布局和中断能力。
pci_epf_test就是其中的一个函数驱动,它是一个纯软件定义的虚拟设备,专门用来测试 endpoint 功能,同时充当其他 EP 设备驱动的开发样例(见 Documentation/PCI/endpoint/pci-test-function.rst)。
框架通过 configfs 让"函数设备"与"控制器驱动"解耦:先用 mkdir 创建函数设备并填入 PCI 头字段,再用 ln -s 把它绑定到某个 EPC,最后写 start 建立链路。本文的绑定文档 Documentation/PCI/endpoint/function/binding/pci-test.rst 正是规定了 test 函数设备的这一份 configfs 目录在绑定时各字段的合法取值。框架整体机制可参考 Documentation/PCI/endpoint/pci-endpoint.rst。
2. 绑定文档规定的配置字段(完整继承)
2.1 名字字段
name: 应为
pci_epf_test,用于绑定到 pci_epf_test 驱动。
configfs 目录名即函数类型名,框架用它在 pci-epf 总线上匹配驱动。这一点在源码中可以直接确认:驱动在 pci-epf-test.c 中声明了匹配表:
static const struct pci_epf_device_id pci_epf_test_ids[] = {
{
.name = "pci_epf_test",
},
{},
};
以及驱动注册名 test_driver 中 .driver.name = "pci_epf_test"。因此创建函数设备时必须写成:
# mkdir /sys/kernel/config/pci_ep/functions/pci_epf_test/func1
2.2 可配置字段取值约束
原文档给出的完整约束表如下(原样继承):
| 字段 | 取值要求 |
|---|---|
vendorid |
应为 0x104c |
deviceid |
DRA74x 上应为 0xb500,DRA72x 上应为 0xb501 |
revid |
不限 |
progif_code |
不限 |
subclass_code |
不限 |
baseclass_code |
应为 0xff |
cache_line_size |
不限 |
subsys_vendor_id |
不限 |
subsys_id |
不限 |
interrupt_pin |
应为 1 - INTA、2 - INTB、3 - INTC、4 - INTD |
msi_interrupts |
应为 1 到 32,取决于要测试的 MSI 中断数 |
msix_interrupts |
应为 1 到 2048,取决于要测试的 MSI-X 中断数 |
逐项解释其约束原因:
-
vendorid / deviceid:这组 ID 决定主机侧
lspci看到的设备身份,同时也是主机侧 host 驱动(pci_endpoint_test)的设备匹配 ID。文档指定0x104c(Texas Instruments 的 PCI vendor ID)加0xb500/0xb501,正是为了让主机驱动能识别该虚拟设备。howto 文档 Documentation/PCI/endpoint/pci-test-howto.rst 中给出的预期lspci输出与之严格对应:00:00.0 PCI bridge: Texas Instruments Device 8888 (rev 01) 01:00.0 Unassigned class [ff00]: Texas Instruments Device b500 -
baseclass_code = 0xff:对应 PCI 规范中的 "Unassigned class"(其他/未分类类),
lspci才会显示Unassigned class [ff00]。注意驱动头模板的默认值是PCI_CLASS_OTHERS(见 test_header):static struct pci_epf_header test_header = { .vendorid = PCI_ANY_ID, .deviceid = PCI_ANY_ID, .baseclass_code = PCI_CLASS_OTHERS, .interrupt_pin = PCI_INTERRUPT_INTA, };其中
PCI_ANY_ID(0xffff)只是模板占位默认值,实际值以用户写入 configfs 的字段为准。howto 文档也说明了这一点:绑定后不写的话vendorid默认显示为0xffff,interrupt_pin默认为0x0001(INTA)。 -
interrupt_pin 取 1~4:PCI 配置空间中 Interrupt Pin 字段的非零值定义为 1=INTA# 直至 4=INTD#,写 0 表示无 legacy INTx 中断能力。EP 侧在收到"触发 legacy 中断"命令时通过
pci_epc_raise_irq(epc, ..., PCI_IRQ_INTX, 0)上报,因此要保证该字段与测试需求一致。 -
msi_interrupts 取 1~32、msix_interrupts 取 1~2048:这两个上限来自 PCI 规范——MSI 能力最多 32 个中断向量(
log2(32)=5),MSI-X 表最多 2048 个条目。EP 驱动在初始化链路时会把用户配置的这两个数原样下发给控制器:if (epc_features->msi_capable) { ret = pci_epc_set_msi(epc, epf->func_no, epf->vfunc_no, epf->msi_interrupts); ... if (epc_features->msix_capable) { ret = pci_epc_set_msix(epc, epf->func_no, epf->vfunc_no, epf->msix_interrupts, epf_test->test_reg_bar, � epf_test->msix_table_offset);(见 pci_epf_test_epc_init)。写超出范围的值会导致 EPC 驱动配置失败,链路初始化直接报错返回 "MSI/MSI-X configuration failed"。
MSI-X 条目的数量还会直接影响 BAR 空间布局:驱动为 MSI-X 表按
PCI_MSIX_ENTRY_SIZE * msix_interrupts分配空间,并附带 PBA(Pending Bit Array)区域(见 alloc_space)。因此msix_interrupts越大,测试寄存器所在 BAR 需要的空间越大。 -
revid / progif_code / subclass_code / cache_line_size / subsys_vendor_id / subsys_id "don't care":这些字段对测试功能无功能意义,主机侧 host 驱动只匹配 vendor/device ID,不检查其余配置空间字段,可以保留默认值。
3. 端到端实操:从创建函数设备到跑通 kselftest
以下操作完整来自 Documentation/PCI/endpoint/pci-test-howto.rst,是绑定文档各字段约束的实际使用场景。
3.1 确认 EPC 与 EPF 驱动就绪
# ls /sys/class/pci_epc/
51000000.pcie_ep
# ls /sys/bus/pci-epf/drivers
pci_epf_test
若启用了 PCI_ENDPOINT_CONFIGFS,也可通过 /sys/kernel/config/pci_ep/controllers 与 /sys/kernel/config/pci_ep/functions 查看。
3.2 创建函数设备
# mount -t configfs none /sys/kernel/config
# cd /sys/kernel/config/pci_ep/
# mkdir functions/pci_epf_test/func1
框架随即在 functions/pci_epf_test/func1/ 下填充 13 个可配置字段:baseclass_code、cache_line_size、deviceid、interrupt_pin、msi_interrupts、msix_interrupts、progif_code、revid、subclass_code、subsys_id、subsys_vendorid、vendorid。
3.3 按绑定文档要求写字段
# 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
(DRA72x 平台应把 deviceid 改为 0xb501。)
3.4 查看并覆盖 BAR 大小
驱动为 6 个 BAR 内置了默认大小,见 default_bar_size:
/* default BAR sizes, can be overridden by the user using configfs */
static size_t default_bar_size[] = { 131072, 131072, 131072, 131072, 131072, 1048576 };
与 howto 中 bar?_size 的实际输出一致:BAR0~BAR4 各 128 KiB,BAR5 为 1 MiB。BAR 大小可通过 configfs 覆盖:
# echo 1048576 > functions/pci_epf_test/func1/pci_epf_test.0/bar1_size
这里有两个硬约束值得注意,源码中都有明确实现:
-
只能在绑定 EPC 之前修改。configfs 的写入函数直接检查:
/* * BAR sizes can only be modified before binding to an EPC, * because pci_epf_test_alloc_space() is called in .bind(). */ if (epf_test->epf->epc) return -EOPNOTSUPP;(见 PCI_EPF_TEST_BAR_SIZE_W,对应的六个属性为
bar0_size~bar5_size。) -
取值必须是 2 的幂(
is_power_of_2(val)校验,否则返回-EINVAL),这是 PCI BAR 尺寸的基本规范。
另外注意:部分 endpoint 控制器的 BAR 是固定大小或保留用途的(BAR_FIXED/BAR_RESERVED),对这类控制器 configfs 中的对应 BAR 大小会被忽略——pci_epf_test_alloc_space 中遇到 BAR_FIXED 时直接采用 epc_features->bar[bar].fixed_size。
3.5 绑定到控制器并启动链路
# ln -s functions/pci_epf_test/func1 controllers/51000000.pcie_ep/
# echo 1 > controllers/51000000.pcie_ep/start
绑定动作触发驱动的 .bind → pci_epf_test_bind:先向 EPC 查询特性(pci_epc_get_features),用 pci_epc_get_first_free_bar() 选一个空闲 BAR 存放测试寄存器组,再分配 BAR 空间;start 写 1 之后走 epc_init 回调,完成配置头写入、能力位发布、MSI/MSI-X 配置。链路建立后,主机侧 lspci 应显示 3.3 节设置的 TI b500 设备。
4. 主机侧验证:kselftest 与测试寄存器协议
4.1 运行 kselftest
EP 侧链路就绪后,在主机(RC)侧编译并运行内核自带测试:
# cd <kernel-dir>
# make -C tools/testing/selftests/pci_endpoint
# make -C tools/testing/selftests/pci_endpoint INSTALL_PATH=/usr/bin install
# pci_endpoint_test
成功时的 TAP 输出形如(节选自 pci-test-howto.rst):
1..16
# Starting 16 tests from 9 test cases.
ok 1 pci_ep_bar.BAR0.BAR_TEST
...
ok 6 pci_ep_bar.BAR5.BAR_TEST
ok 7 pci_ep_basic.CONSECUTIVE_BAR_TEST
ok 8 pci_ep_basic.LEGACY_IRQ_TEST
ok 9 pci_ep_basic.MSI_TEST
ok 10 pci_ep_basic.MSIX_TEST
ok 11 pci_ep_data_transfer.memcpy.READ_TEST
ok 12 pci_ep_data_transfer.memcpy.WRITE_TEST
ok 13 pci_ep_data_transfer.memcpy.COPY_TEST
ok 14 pci_ep_data_transfer.dma.READ_TEST
ok 15 pci_ep_data_transfer.dma.WRITE_TEST
ok 16 pci_ep_data_transfer.dma.COPY_TEST
# PASSED: 16 / 16 tests passed.
两个已知注意事项(原文档明确说明):
-
第 16 项
pci_ep_data_transfer.dma.COPY_TEST在大多数 DMA 控制器上会失败,因为其 DMA 引擎不支持 MEMCPY 能力(EP 驱动在 pci_epf_test_copy 中显式检查dma_has_cap(DMA_MEMCPY, ...))。此时应跳过:# pci_endpoint_test -f pci_ep_bar -f pci_ep_basic -v memcpy -T COPY_TEST -v dma -
若 endpoint 的 MSI 控制器被用于 doorbell 用例,可单独运行:
pci_endpoint_test -f pcie_ep_doorbell。
4.2 测试寄存器组与命令/状态位(支撑绑定文档字段的底层协议)
host 驱动之所以要固定 vendorid/deviceid,是因为它按 pci_endpoint_test 设备驱动的方式,通过 BAR 中的寄存器组与 EP 通信。寄存器定义见 pci-test-function.rst,与源码中的 struct pci_epf_test_reg 一一对应:magic、command、status、src_addr、dst_addr、size、checksum、irq_type、irq_number 等(源码还扩展了 flags、caps、doorbell 相关寄存器)。
关键位定义(源自 pci-epf-test.c 顶部宏定义):
COMMAND 寄存器(主机写入,EP 的命令工作队列 pci_epf_test_cmd_handler 每 1ms 轮询一次):
| 位 | 含义 |
|---|---|
| Bit 0 | raise legacy IRQ |
| Bit 1 | raise MSI IRQ |
| Bit 2 | raise MSI-X IRQ |
| Bit 3 | read(从 RC 缓冲区读数据) |
| Bit 4 | write(向 RC 缓冲区写数据) |
| Bit 5 | copy(RC 缓冲区之间拷贝) |
STATUS 寄存器(EP 写回,主机轮询):Bit 0/1 读成功/失败、Bit 2/3 写成功/失败、Bit 4/5 拷贝成功/失败、Bit 6 已触发中断、Bit 7 源地址非法、Bit 8 目的地址非法。
irq_type 取值 Legacy=0、MSI=1、MSI-X=2;irq_number 的合法范围 Legacy=0、MSI=1..32、MSI-X=1..2048——这正是第 2 节中 msi_interrupts/msix_interrupts 取值上限的来源。EP 侧触发中断前会先置 STATUS_IRQ_RAISED 再调用 pci_epc_raise_irq(),并校验中断号不超出 EPC 实际配置的向量数(见 pci_epf_test_raise_irq):
case IRQ_TYPE_MSI:
count = pci_epc_get_msi(epc, epf->func_no, epf->vfunc_no);
if (irq_number > count || count <= 0) {
dev_err(dev, "Invalid MSI IRQ number %d / %d\n",
irq_number, count);
return;
}
pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no,
PCI_IRQ_MSI, irq_number);
break;
5. 小结与排错要点
pci-test.rst绑定文档规定的是pci_epf_test函数设备 configfs 目录的字段契约:vendorid=0x104c、deviceid=0xb500/0xb501(DRA74x/DRA72x)、baseclass_code=0xff、interrupt_pin=1..4、msi_interrupts=1..32、msix_interrupts=1..2048;其余字段不限。- 主机侧识别失败(
lspci无b500设备)优先检查 vendorid/deviceid 是否写错、是否写在了start之前。 set msi/msix failed报错优先检查中断数是否超范围,以及该 EPC 是否声明了msi_capable/msix_capable特性。- 修改 BAR 大小失败返回
EOPNOTSUPP说明已绑定 EPC;返回-EINVAL说明不是 2 的幂。 - 数据面命令失败时,主机读 EP 的 STATUS 寄存器定位是地址非法(Bit 7/8)还是传输失败(Bit 1/3/5),并可结合 EP 侧 dmesg 中
pci_epf_test_print_rate打印的速率/时长信息判断 DMA 路径是否正常。
相关文档入口:Documentation/PCI/endpoint/function/binding/pci-test.rst(本篇核心)、Documentation/PCI/endpoint/pci-test-function.rst(寄存器协议)、Documentation/PCI/endpoint/pci-test-howto.rst(用户操作指南)、Documentation/PCI/endpoint/function/binding/pci-ntb.rst(NTB 函数的同类绑定约束,可作对照)。
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