首页
/ Linux 内核 PCI Endpoint 测试函数绑定指南:pci_epf_test 的 configfs 配置与端到端验证

Linux 内核 PCI Endpoint 测试函数绑定指南:pci_epf_test 的 configfs 配置与端到端验证

2026-09-03 16:12:57作者:毕习沙Eudora

本篇围绕内核文档 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 默认显示为 0xffffinterrupt_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_codecache_line_sizedeviceidinterrupt_pinmsi_interruptsmsix_interruptsprogif_coderevidsubclass_codesubsys_idsubsys_vendoridvendorid

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

这里有两个硬约束值得注意,源码中都有明确实现:

  1. 只能在绑定 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. 取值必须是 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

绑定动作触发驱动的 .bindpci_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 一一对应:magiccommandstatussrc_addrdst_addrsizechecksumirq_typeirq_number 等(源码还扩展了 flagscaps、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=0x104cdeviceid=0xb500/0xb501(DRA74x/DRA72x)、baseclass_code=0xffinterrupt_pin=1..4msi_interrupts=1..32msix_interrupts=1..2048;其余字段不限。
  • 主机侧识别失败(lspcib500 设备)优先检查 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 函数的同类绑定约束,可作对照)。

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