Linux 内核 PCI Endpoint Framework:用 configfs 配置与绑定 PCI 端点的完整指南
本文基于内核文档 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)。
该框架由三部分组成:
- EPC 库(Endpoint Controller):为"能工作在端点模式的控制器"驱动提供 API,如
write_header、set_bar、raise_irq、start、stop等 ops; - EPF 库(Endpoint Function):为"具体功能"驱动提供 API,如
pci_epf_register_driver()、pci_epf_alloc_space(); - configfs 层:即本文主题,负责把 EPF 设备与 EPC 设备绑定起来,并让用户通过文件接口配置端点函数的标准配置头。
configfs 层由 drivers/pci/endpoint/pci-ep-cfs.c 实现,其模块初始化函数 pci_ep_cfs_init()(见 pci-ep-cfs.c#L722)向 configfs 注册了子系统根 pci_ep,并在其下注册两个默认组 functions 与 controllers。
启用前提(见 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-mhi、pci-epf-ntb(非透明桥)、pci-epf-vntb。
4.2 EPF 设备下的标准配置头属性
每创建一个 EPF 设备目录,框架会自动生成下列属性文件,用于配置端点函数的标准 PCI 配置头。它们与 include/linux/pci-epf.h 中 struct 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_type 的 allow_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>/
底层差异清晰可见:primary 的 allow_link 调 pci_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/0(true/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),其顺序是:
pci_epc_add_epf(epc, epf, PRIMARY_INTERFACE)—— 在 EPC 上登记该函数(PCIe 规范允许一个设备最多 8 个函数);pci_epf_bind(epf)—— 通知 EPF 驱动(执行其bindops,通常是写配置头、配 BAR);失败则回滚执行pci_epc_remove_epf();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=y、CONFIG_PCI_ENDPOINT_CONFIGFS=y、CONFIG_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 配置流程的理想抓手。
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 StartedRust0622
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