首页
/ Linux 内核 PCI Endpoint Framework:用 configfs 配置与绑定 PCI 端点的完整指南

Linux 内核 PCI Endpoint Framework:用 configfs 配置与绑定 PCI 端点的完整指南

2026-09-05 11:36:27作者:凌朦慧Richard

本文基于内核文档 pci-endpoint-cfs.rst 与对应实现 pci-ep-cfs.c,讲解 Linux PCI Endpoint Framework 的 configfs 用户态控制面:如何挂载 configfs、创建 EPF(Endpoint Function)设备、逐项配置标准 PCI 配置头、把 EPF 绑定到 EPC(Endpoint Controller)、以及通过 start 属性拉起 PCIe 链路。读完本文,你可以独立完成一个 PCI 端点(例如 NTB 或测试设备)从用户态的完整配置流程,并能读懂框架底层的调用链。

1. 背景:PCI Endpoint Framework 与 configfs 的位置

Linux 的 PCI 子系统长期只支持 Root Complex(RC)模式:扫描总线、分配资源、按 vendor/device ID 加载驱动,并提供热插拔、电源管理、AER 等能力。但许多 SoC 集成的 PCIe 控制器 IP 既能当 RC 也能当 Endpoint(端点)。PCI Endpoint Framework 为此补充了端点模式支持,典型场景包括测试验证、协处理器/加速卡、非透明桥接(NTB)等(背景可参见姊妹文档 pci-endpoint.rst)。

该框架由三部分组成:

  1. EPC 库(Endpoint Controller):为"能工作在端点模式的控制器"驱动提供 API,如 write_headerset_barraise_irqstartstop 等 ops;
  2. EPF 库(Endpoint Function):为"具体功能"驱动提供 API,如 pci_epf_register_driver()pci_epf_alloc_space()
  3. configfs 层:即本文主题,负责把 EPF 设备与 EPC 设备绑定起来,并让用户通过文件接口配置端点函数的标准配置头。

configfs 层由 drivers/pci/endpoint/pci-ep-cfs.c 实现,其模块初始化函数 pci_ep_cfs_init()(见 pci-ep-cfs.c#L722)向 configfs 注册了子系统根 pci_ep,并在其下注册两个默认组 functionscontrollers

启用前提(见 drivers/pci/endpoint/Kconfig):

  • CONFIG_PCI_ENDPOINT(PCI Endpoint Support,依赖 HAVE_PCI):构建 EPC/EPF 两个库;
  • CONFIG_PCI_ENDPOINT_CONFIGFS:构建本文讲的 configfs 入口,该选项会 select CONFIGFS_FS
  • 可选的 CONFIG_PCI_ENDPOINT_MSI_DOORBELL:让 EP 的 MSI 控制器充当门铃,RC 写专用 BAR 即可触发 EP 中断。

2. 挂载 configfs

PCI Endpoint 核心层在 configfs 挂载点下创建 pci_ep 目录。挂载命令如下(与原文档一致):

mount -t configfs none /sys/kernel/config

挂载后框架提供的入口为:

/sys/kernel/config/pci_ep/

3. 目录结构:controllers 与 functions

pci_ep 下有两个根目录(见 pci_ep_cfs_init()pci-ep-cfs.c#L736):

  • controllers/:系统中每一个已注册的 EPC 设备都有一个条目,由 EPC 核心创建(EPC 驱动调用 pci_epc_create() 时,经由 pci_ep_cfs_add_epc_group() 注册,见 pci-ep-cfs.c#L270);
  • functions/:系统中每一个已注册的 EPF 驱动都有一个条目,由 EPF 核心创建(EPF 驱动调用 pci_epf_register_driver() 时,经由 pci_ep_cfs_add_epf_group() 注册,见 pci-ep-cfs.c#L676)。

原文档给出的完整目录结构如下,务必记住这是后续所有操作的对象:

/sys/kernel/config/pci_ep/
	.. controllers/
	.. functions/

4. 创建 EPF 设备

4.1 在 functions/<EPF 驱动>/ 下建目录

每个已注册的 EPF 驱动会出现在 functions/ 目录下(原文档此处写作 "controllers directory" 属于笔误,实际为 functions/,与其给出的目录树一致)。要创建由 <EPF Driver> 驱动的类型为 <EPF device> 的端点函数,用户只需在对应驱动目录下创建目录:

/sys/kernel/config/pci_ep/functions/
	.. <EPF Driver1>/
		... <EPF Device 11>/
		... <EPF Device 21>/
		... <EPF Device 31>/
	.. <EPF Driver2>/
		... <EPF Device 12>/
		... <EPF Device 22>/

从源码看,functions/<驱动>/ 是一个 "default group",用户 mkdir 时触发 make_group 回调 pci_epf_make()pci-ep-cfs.c#L599)。它内部依次:分配 pci_epf_group 并从全局 IDR 取编号;把 EPF 设备命名成 <驱动名>.<编号>(如 pci-epf-test.0);调用 pci_epf_create() 创建 EPF 设备;最后通过 pci_epf_cfs_add_sub_groups() 创建 primary/secondary/ 子组以及功能驱动自定义的属性组。

当前内核树内置的 EPF 函数驱动位于 drivers/pci/endpoint/functions/pci-epf-test(功能验证)、pci-epf-mhipci-epf-ntb(非透明桥)、pci-epf-vntb

4.2 EPF 设备下的标准配置头属性

每创建一个 EPF 设备目录,框架会自动生成下列属性文件,用于配置端点函数的标准 PCI 配置头。它们与 include/linux/pci-epf.hstruct pci_epf_header 的字段一一对应:

.. <EPF Driver1>/
	... <EPF Device 11>/
		... vendorid
		... deviceid
		... revid
		... progif_code
		... subclass_code
		... baseclass_code
		... cache_line_size
		... subsys_vendor_id
		... subsys_id
		... interrupt_pin

结合源码可以给出每个属性的完整说明。属性的 show/store 由 PCI_EPF_HEADER_R/W_u8/u16 宏批量生成(pci-ep-cfs.c#L327),写入时统一用 kstrtou16/u8/page, 0 解析,即同时接受十六进制(0x 前缀)与十进制数值,读回时按 0x%04x/0x%02x 十六进制打印:

属性 宽度 含义(来自 struct pci_epf_header 注释) 取值约束
vendorid u16 设备厂商 ID 0x0000–0xFFFF
deviceid u16 设备 ID 0x0000–0xFFFF
revid u8 设备修订号 0x00–0xFF
progif_code u8 编程接口(Programming Interface)代码 0x00–0xFF
subclass_code u8 更具体的功能子类代码 0x00–0xFF
baseclass_code u8 基本类代码(PCI Base Class) 0x00–0xFF
cache_line_size u8 系统缓存行大小,以 DWORD 为单位 0x00–0xFF
subsys_vendor_id u16 子系统(板卡)厂商 ID 0x0000–0xFFFF
subsys_id u16 子系统 ID 0x0000–0xFFFF
interrupt_pin u8 使用的中断引脚(INTA–INTD 或无) enum pci_interrupt_pin

注意两点:

  • 这些值先写入内存中的 epf->header;真正下发到硬件是在 EPF 驱动绑定 EPC 之后,由功能驱动通过 EPC 库的 pci_epc_write_header() 完成(参考 pci-endpoint.rst 的 API 列表)。
  • 源码中还定义了原文档未列出的 msi_interrupts(u8)与 msix_interrupts(u16)两个属性(pci-ep-cfs.c#L378),用于指定该函数要暴露的 MSI / MSI-X 中断数量,与 vendorid 等属性同处 pci_epf_attrs[] 属性表中。

此外,EPF 驱动可通过 add_cfs() ops 追加功能专属的 configfs 属性。框架在 pci_epf_type_add_cfs() 中调用它(pci-ep-cfs.c#L538)。例如测试驱动 pci-epf-test.c 就暴露了 BAR 大小等可调参数(其默认 BAR 大小为 {131072, 131072, 131072, 131072, 131072, 1048576},见该文件 L126 附近)。原文档末尾目录树中的 function 一行,对应的就是这类功能驱动自带的属性/子项。

4.3 VF 与 PF 的符号链接

EPF 设备目录下可以存在指向其他 EPF 设备的符号链接(<Symlink EPF Device 31>),这些链接必须由用户创建,用于表示绑定在某个物理函数(PF)上的虚拟函数(VF)。在原文档的例子中,<EPF Device 11> 是 PF,<EPF Device 31> 是 VF。一个 EPF 设备一旦以符号链接方式挂到另一个 EPF 设备(成为 VF),就不能再与 EPC 设备链接。

源码印证:EPF 组类型 pci_epf_typeallow_link/drop_link 回调为 pci_epf_vepf_link()/pci_epf_vepf_unlink(),直接调用 pci_epf_add_vepf() / pci_epf_remove_vepf()pci-ep-cfs.c#L477),即在两个 EPF 之间建立 PF/VF 关联。

4.4 primary/secondary:双 EPC 接口(NTB 场景)

如果 EPF 设备需要关联两个 EPC(典型如非透明桥 NTB:桥的两端各接一个控制器),则把连到主接口的 EPC 的符号链接放进 primary/ 子目录,把连到次接口的 EPC 的符号链接放进 secondary/ 子目录。每个 EPF 设备目录因此默认带有这两个子组(pci_ep_cfs_add_sub_groups() 创建,pci-ep-cfs.c#L576):

... <EPF Device 11>/
    ... primary/
    ...     ... <Symlink EPC Device1>/
    ... secondary/
    ...     ... <Symlink EPC Device2>/

底层差异清晰可见:primaryallow_linkpci_epc_add_epf(epc, epf, PRIMARY_INTERFACE)pci-ep-cfs.c#L110),secondary 的则调 pci_epc_add_epf(epc, epf, SECONDARY_INTERFACE)pci-ep-cfs.c#L46)。pci-epf-ntb.c 就是这种双控制器绑定的典型用户。

5. EPC 设备:绑定 EPF 并拉起链路

5.1 controllers/ 目录与符号链接

每个已注册的 EPC 设备在 controllers/ 下有一个目录(由 EPC 核心通过 pci_ep_cfs_add_epc_group() 创建),其中可包含:

  • 指向 <EPF Device> 的符号链接,由用户创建,表示该端点设备上存在的函数。只有代表物理函数(PF)的 EPF 设备才能链接到 EPC(VF 只能通过 4.3 节的 PF/VF 链接间接存在);
  • 一个 start 文件。

原文档给出的结构:

| controllers/
	| <Directory: EPC name>/
		| <Symbolic Link: Function>
		| start
| functions/
	| <Directory: EPF driver>/
		| <Directory: EPF device>/
			| vendorid
			| deviceid
			| revid
			| progif_code
			| subclass_code
			| baseclass_code
			| cache_line_size
			| subsys_vendor_id
			| subsys_id
			| interrupt_pin
			| function

5.2 start 属性的实现细节

EPC 目录下的 start 属性由 pci_epc_start_store() 处理(pci-ep-cfs.c#L173),行为要点:

  • 输入用 kstrtobool() 解析,即写 1/0true/false 亦可);
  • 1 时调用 pci_epc_start(epc) 启动 PCIe 链路,成功后置 start=1;失败则打印 failed to start endpoint controller 并返回 -EINVAL
  • 0 时调用 pci_epc_stop(epc) 停止链路;
  • 若当前状态与所写值相同,返回 -EALREADY

原文档强调:start 一般在所有 EPF 设备都已创建并链接到 EPC 之后才写,此时端点设备才准备好与 host 建立链路。

5.3 绑定与解绑的调用链

在 EPC 目录里对 EPF 建符号链接时,触发 pci_epc_epf_link()pci-ep-cfs.c#L218),其顺序是:

  1. pci_epc_add_epf(epc, epf, PRIMARY_INTERFACE) —— 在 EPC 上登记该函数(PCIe 规范允许一个设备最多 8 个函数);
  2. pci_epf_bind(epf) —— 通知 EPF 驱动(执行其 bind ops,通常是写配置头、配 BAR);失败则回滚执行 pci_epc_remove_epf()
  3. pci_epc_notify_pending_init(epc, epf) —— 若 EPC 此前已完成初始化,补发一次"初始化完成"通知给 EPF 驱动。

删除符号链接(解绑)时触发 pci_epc_epf_unlink():先 pci_epf_unbind(),再 pci_epc_remove_epf()。注意其中有一句 WARN_ON_ONCE(epc_group->start)——链路处于启动状态时解绑会触发内核警告,因此正确顺序是先写 echo 0 > start,再删除符号链接和 EPF 目录。

6. 完整操作流程示例(以测试函数驱动为例)

假设已启用 CONFIG_PCI_ENDPOINT=yCONFIG_PCI_ENDPOINT_CONFIGFS=yCONFIG_PCI_EPF_TEST=y,且系统的端点控制器驱动已在 controllers/ 下注册了名为 <EPC-name> 的设备。完整流程:

# 1. 挂载 configfs
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/pci_ep

# 2. 查看可用的 EPF 驱动与 EPC 设备
ls functions/ controllers/

# 3. 创建一个端点函数(EPF 驱动名为 pci-epf-test)
cd functions
mkdir pci-epf-test/epf0

# 4. 配置标准配置头(十六进制或十进制均可)
cd pci-epf-test/epf0
echo 0x1234 > vendorid          # 厂商 ID
echo 0x0001 > deviceid           # 设备 ID
echo 0x01     > revid
echo 0x00     > progif_code
echo 0x00     > subclass_code
echo 0x0d     > baseclass_code  # 按 PCI 类代码填写
echo 0x10     > cache_line_size
echo 0x1234   > subsys_vendor_id
echo 0x0001   > subsys_id
echo 0x01     > interrupt_pin   # INTA
cat vendorid                  # 回读校验

# 5. 将 EPF(PF)链接到 EPC
cd /sys/kernel/config/pci_ep
ln -s ../../../functions/pci-epf-test/epf0 controllers/<EPC-name>/

# 6. 拉起链路
echo 1 > controllers/<EPC-name>/start

若涉及 VF,则在 EPF 目录内建符号链接(例如把 VF epf3 挂到 PF epf0 下:ln -s ../../epf3 epf0/),此后 epf3 不能再链接到任何 EPC。

拆除与反向操作同理:

# 先停链路,再删符号链接,最后删 EPF 目录
echo 0 > controllers/<EPC-name>/start
rm controllers/<EPC-name>/epf0
rmdir functions/pci-epf-test/epf0

删除 EPF 目录会触发 pci_epf_release()pci-ep-cfs.c#L499),释放 IDR 编号、销毁 EPF 设备并回收内存。

7. 小结与延伸阅读

  • 本文的核心对象是 Documentation/PCI/endpoint/pci-endpoint-cfs.rst,其全部要点——configfs 挂载、controllers/functions/ 双根目录、EPF 设备创建与 10 个标准配置头属性、PF/VF 符号链接规则、primary/secondary 双 EPC 接口、start 启动语义——均可在 pci-ep-cfs.c 中找到一一对应的实现,且源码额外提供了 msi_interrupts/msix_interrupts 属性与功能驱动 add_cfs() 扩展机制。
  • 理解 EPF 驱动该实现哪些 ops、EPC 驱动该实现哪些 ops、以及 pci_epc_write_header()/pci_epc_set_bar()/pci_epc_raise_irq() 等完整 API 语义,请阅读 pci-endpoint.rst
  • 编写验证实验时可直接参考 pci-epf-test.c:它暴露了 command/status/magic 寄存器结构、支持 READ/WRITE/COPY、INTx/MSI/MSI-X 中断触发、DMA 传输与 BAR 子区映射等命令,是端到端验证 configfs 配置流程的理想抓手。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384