Linux 内核 PCI NTB 端点功能详解:pci_epf_ntb 的 configfs 绑定字段与源码实现
本文以 Linux 内核文档 pci-ntb.rst 为主体,完整讲解 PCI NTB(Non-Transparent Bridge,非透明桥)端点功能 pci_epf_ntb 在 configfs 中的绑定流程:标准 EPF 可配置字段(vendorid/deviceid/baseclass_code 等)与 NTB 专有字段(db_count、mw1~mw4、num_mws、spad_count)的取值约束与默认值,并结合 pci-epf-ntb.c 源码剖析这些字段在驱动内部的解析、校验与生效路径,帮助读者掌握在双主机、多 EP 控制器平台上配置 NTB 端点的完整实操方案与底层原理。
1. 背景:PCI NTB 端点功能在 Linux 中的定位
PCI NTB 允许两个主机系统把彼此暴露为"设备"进行通信:NTB 设备通常支持在远端机器上产生中断、把内存区间以 BAR 形式暴露给对端、执行 DMA,并提供双方都可访问的 scratchpad(暂存寄存器区)。在 Linux 的可配置 PCI 端点框架(PCI Endpoint framework)中,这一功能由端点功能驱动 pci-epf-ntb.c 实现:它把 SoC 上的多个 PCI Endpoint(EP)控制器实例配置成"一台主机的事务被路由到另一个 EP 控制器",从而让 HOST1 与 HOST2 以 SoC 作为桥互相通信。
从源码文件头部的注释框图(pci-epf-ntb.c#L9-L35)可以看到该拓扑:
+-------------+ +-------------+
| HOST1 | | HOST2 |
+------^------+ +------^------+
| |
+------|-------------------------------------------------|---------+
| +---v------+ +------v------+ |
| | EP | | EP | |
| |CONTROLLER1|<----------------------------------->CONTROLLER2 | |
| +-----------+ SoC With Multiple EP Instances +-------------+ |
+-------------------------------------------------------------------+
完整的 NTB 硬件建模(Config Region、Self/Peer Scratchpad、Doorbell、Memory Window 五大构件及其 BAR 布局)见配套文档 pci-ntb-function.rst;本文聚焦的 pci-ntb.rst 则回答一个更具体的问题:在 configfs 中创建 pci_epf_ntb 功能设备时,有哪些字段必须/可以配置,各自的取值要求是什么。
1.1 编译前提
根据 drivers/pci/endpoint/functions/Kconfig 与 drivers/pci/endpoint/Kconfig,需要开启:
CONFIG_PCI_ENDPOINT("PCI Endpoint Support"):平台具有可工作在端点模式的 PCI 控制器时需要;CONFIG_PCI_EPF_NTB("PCI Endpoint NTB driver"):依赖PCI_ENDPOINT,并自动select CONFIGFS_FS;CONFIG_PCI_ENDPOINT_CONFIGFS("PCI Endpoint Configfs Support"):开启 configfs 入口,用于配置端点功能设备并将其与端点控制器绑定。
对应的构建规则见 drivers/pci/endpoint/Makefile 与 drivers/pci/endpoint/functions/Makefile:
# drivers/pci/endpoint/Makefile
obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o
# drivers/pci/endpoint/functions/Makefile
obj-$(CONFIG_PCI_EPF_NTB) += pci-epf-ntb.o
2. 第一步:在 configfs 中创建 pci_epf_ntb 设备
文档给出的第一步是:在 pci_epf_ntb 目录下创建一个子目录。按 pci-ntb-howto.rst 的完整操作(前提:系统中至少有两个 EP 控制器设备,且功能驱动已注册):
# 挂载 configfs 并确认设备/驱动就绪
mount -t configfs none /sys/kernel/config
ls /sys/kernel/config/pci_ep/controllers
# 2900000.pcie-ep 2910000.pcie-ep
ls /sys/kernel/config/pci_ep/functions
# pci_epf_ntb pci_epf_ntb
# 创建功能设备(目录名可自定,如 func1)
cd /sys/kernel/config/pci_ep/
mkdir functions/pci_epf_ntb/func1
mkdir func1 创建的正是会被 pci_epf_ntb 驱动 probe 的功能设备。PCI 端点框架随后会在该目录下填充标准 EPF 可配置字段,例如:
# ls functions/pci_epf_ntb/func1
baseclass_code deviceid msi_interrupts pci-epf-ntb.0
progif_code secondary subsys_id vendorid
cache_line_size interrupt_pin msix_interrupts primary
revid subclass_code subsys_vendor_id
2.1 标准 EPF 可配置字段(文档原始表格)
pci-ntb.rst 规定了对 NTB 功能的字段取值要求:
| 字段 | 要求 |
|---|---|
| vendorid | 应为 0x104c |
| deviceid | 对 TI 的 J721E SoC 应为 0xb00d |
| revid | 无要求(don't care) |
| progif_code | 无要求 |
| subclass_code | 应为 0x00 |
| baseclass_code | 应为 0x5 |
| cache_line_size | 无要求 |
| subsys_vendor_id | 无要求 |
| subsys_id | 无要求 |
| interrupt_pin | 无要求 |
| msi_interrupts | 无要求 |
| msix_interrupts | 无要求 |
其中几个"硬要求"在源码中有对应物:baseclass_code = 0x5(PCI 基类 0x05 = 内存控制器类)与 subclass_code = 0x00 对应驱动默认 PCI 头 epf_ntb_header,见 pci-epf-ntb.c#L124-L129:
static struct pci_epf_header epf_ntb_header = {
.vendorid = PCI_ANY_ID,
.deviceid = PCI_ANY_ID,
.baseclass_code = PCI_BASE_CLASS_MEMORY,
.interrupt_pin = PCI_INTERRUPT_INTA,
};
按 pci-ntb-howto.rst 的说法,功能设备绑定驱动后框架会用默认值填充条目,pci-epf-ntb 驱动则把 vendorid 填为 0xffff、interrupt_pin 填为 0x0001。因此要让主机侧按预期识别设备,需要显式写入:
# echo 0x104c > functions/pci_epf_ntb/func1/vendorid
# echo 0xb00d > functions/pci_epf_ntb/func1/deviceid
这一步写入的值最终会体现在主机侧的 lspci 输出中(见第 6 节,Texas Instruments Device b00d 即 vendorid=0x104c、deviceid=0xb00d 的结果)。
3. 第二步:配置 NTB 专有字段
3.1 文档规定的 NTB 专有字段表
框架会自动在功能属性目录下再建一个与功能设备同名的子目录(即 pci_epf_ntb.0),其中填充 NTB 专有属性。pci-ntb.rst 给出的字段说明如下:
| 字段 | 说明 |
|---|---|
| db_count | 门铃(doorbell)数量;默认 = 4 |
| mw1 | 内存窗口 1 的大小 |
| mw2 | 内存窗口 2 的大小 |
| mw3 | 内存窗口 3 的大小 |
| mw4 | 内存窗口 4 的大小 |
| num_mws | 内存窗口数量;最大 = 4 |
| spad_count | 暂存寄存器(scratchpad)数量;默认 = 64 |
这些属性的注册与默认值可以直接在 pci-epf-ntb.c 中逐一印证:
#define SPAD_COUNT 64
#define DB_COUNT 4
#define NTB_MW_OFFSET 2
#define DB_COUNT_MASK GENMASK(15, 0)
#define MSIX_ENABLE BIT(16)
#define MAX_DB_COUNT 32
#define MAX_MW 4
- db_count(默认 4):驱动初始化时校验
ntb->db_count必须落在 1..MAX_DB_COUNT(32) 区间,否则报 "Invalid db_count" 错误(见 pci-epf-ntb.c#L1301-L1310)。该值同时决定 MSI/MSI-X 向量的数量:pci_epc_set_msi(epc, func_no, vfunc_no, ntb->db_count)或pci_epc_set_msix(...),即每个门铃对应一个中断向量。 - spad_count(默认 64):暂存区大小按
spad_size = spad_count * 4计算(每个寄存器 32 位,见 pci-epf-ntb.c#L1030-L1037),并写入控制区的SPAD OFFSET/SPAD COUNT供主机侧读取。 - num_mws(最大 4):store 函数直接拒绝大于
MAX_MW(4) 的写入,见 pci-epf-ntb.c#L1947-L1963:
static ssize_t epf_ntb_num_mws_store(struct config_item *item,
const char *page, size_t len)
{
...
if (kstrtou32(page, 0, &val) < 0)
return -EINVAL;
if (val > MAX_MW)
return -EINVAL;
ntb->num_mws = val;
return len;
}
- mw1~mw4:分别表示 4 个内存窗口的大小。注意写入
mwN前必须先保证num_mws >= N,否则 store 路径会打印 "Invalid num_nws: %d value" 并返回 -EINVAL(pci-epf-ntb.c#L1937-L1940)。窗口大小在主机下发CMD_CONFIGURE_MW时还会再校验一次:请求映射尺寸不能超过ntb->mws_size[mw],否则返回 -EINVAL(pci-epf-ntb.c#L262-L269)。
这 7 个属性通过 CONFIGFS_ATTR 宏注册进 ntb_group_type,并在 epf_ntb_add_cfs() 中挂到功能设备的 configfs 目录下(pci-epf-ntb.c#L1979-L2022),因此设备 bind 到驱动后才会出现 pci_epf_ntb.0/ 子目录。
3.2 一个可复制的示例配置
来自 pci-ntb-howto.rst 的示例(注意先设 num_mws 再设 mwN):
# echo 4 > functions/pci_epf_ntb/func1/pci_epf_ntb.0/db_count
# echo 128 > functions/pci_epf_ntb/func1/pci_epf_ntb.0/spad_count
# echo 2 > functions/pci_epf_ntb/func1/pci_epf_ntb.0/num_mws
# echo 0x100000 > functions/pci_epf_ntb/func1/pci_epf_ntb.0/mw1
# echo 0x100000 > functions/pci_epf_ntb/func1/pci_epf_ntb.0/mw2
即:4 个门铃、128 个暂存寄存器、2 个内存窗口、每个窗口 0x100000 字节。
3.3 字段如何决定 BAR 分配
这些字段不是"写了就生效"的孤立参数——它们直接决定 BAR 布局。从 struct epf_ntb(pci-epf-ntb.c#L76-L84)可见 num_mws、db_count、spad_count 与 mws_size[MAX_MW] 都保存在功能结构体中;在 BAR 分配逻辑里(pci-epf-ntb.c#L1437-L1462 附近),BAR_DB_MW1 的大小由 db_count * 对齐值 + mws_size[0] 决定,其余窗口依次占用后续 BAR。这与 pci-ntb-function.rst 给出的打包 BAR 方案一致:
| BAR | 承载构件 |
|---|---|
| BAR0 | Config Region + Self Scratchpad |
| BAR1 | Peer Scratchpad |
| BAR2 | Doorbell + Memory Window 1 |
| BAR3 | Memory Window 2 |
| BAR4 | Memory Window 3 |
| BAR5 | Memory Window 4 |
对应源码中的 BAR 枚举为:
enum epf_ntb_bar {
BAR_CONFIG,
BAR_PEER_SPAD,
BAR_DB_MW1,
BAR_MW2,
BAR_MW3,
BAR_MW4,
};
(pci-epf-ntb.c#L67-L74)。文档同时说明:对仅支持 64 位 BAR 的平台,每个构件单独占用一个 BAR 会不够用,所以 EPF NTB 采用上述打包方式,基础 NTB 功能只需 3 个 BAR。MW1 与 Doorbell 挤在同一个 BAR 内,因此控制区专门有 MEMORY WINDOW1 OFFSET 字段记录 MW1 在其中的偏移;同理 SPAD OFFSET 记录 self scratchpad 在 BAR0 中的偏移。
4. 第三步:绑定 EP 控制器并启动链路
NTB 功能设备需要同时挂到两个 EP 控制器(分别连接两台主机),使用功能设备下的 primary 和 secondary 条目(pci-ntb-howto.rst):
# ln -s controllers/2900000.pcie-ep/ functions/pci_epf_ntb/func1/primary
# ln -s controllers/2910000.pcie-ep/ functions/pci_epf_ntb/func1/secondary
随后两个控制器各自与主机建立链路:
# echo 1 > controllers/2900000.pcie-ep/start
# echo 1 > controllers/2910000.pcie-ep/start
两个控制器都建链后,功能设备即处于"就绪"状态。从源码看,epf_ntb 结构体中 epc[2] 数组(pci-epf-ntb.c#L83)正是保存 primary/secondary 两个 epf_ntb_epc 实例,命令处理逻辑会遍历 PRIMARY_INTERFACE 与 SECONDARY_INTERFACE 两类接口(如 epf_ntb_link_up(),pci-epf-ntb.c#L140-L173)。
5. 底层机制:主机与端点之间的命令协议
上述字段最终服务于主机(RC 侧)与端点(EP 侧)之间的命令交换,协议由主机侧驱动 ntb_hw_epf.c 与端点侧 pci-epf-ntb 共同实现。命令定义在两端完全一致:
#define CMD_CONFIGURE_DOORBELL 1
#define CMD_TEARDOWN_DOORBELL 2
#define CMD_CONFIGURE_MW 3
#define CMD_TEARDOWN_MW 4
#define CMD_LINK_UP 5
#define CMD_LINK_DOWN 6
(端点侧见 pci-epf-ntb.c#L47-L55,主机侧见 ntb_hw_epf.c#L16-L22)。
命令通过 BAR0 的 Config Region 传递,其内存布局由 struct epf_ntb_ctrl 描述(pci-epf-ntb.c#L107-L122):command、argument、command_status、link_status、topology、64 位 addr、64 位 size、num_mws、mw1_offset、spad_offset、spad_count、db_entry_size 以及最多 32 组 db_data/db_offset。各字段含义(COMMAND、ARGUMENT、TOPOLOGY、ADDRESS/SIZE、MW1 OFFSET、SPAD OFFSET/COUNT、DB ENTRY SIZE、DB DATA)在 pci-ntb-function.rst 中有完整说明,要点是:
- CMD_CONFIGURE_DOORBELL(0x1):主机在分配并初始化 MSI/MSI-X 后下发;
ARGUMENT低 16 位是门铃数、BIT16 指示用 MSI 还是 MSI-X。端点收到后配置出站 ATU,使写 Doorbell BAR 的事务被路由到主机编程的 MSI/MSI-X 地址。端点侧对应epf_ntb_configure_msi()/epf_ntb_configure_msix()(pci-epf-ntb.c#L383-L427),并把每个中断的 MSI data 写入对端控制区的db_data,供对端主机"写 BAR 即响铃"。 - CMD_CONFIGURE_MW(0x2):主机分配一块可被远端主机访问的缓冲区后下发,
ADDRESS/SIZE填缓冲区地址与大小,ARGUMENT填窗口索引;端点随后用pci_epc_map_addr()配置出站 ATU,把 MW BAR 上的事务路由到主机提供的地址(epf_ntb_configure_mw(),pci-epf-ntb.c#L235-L283)。这里的size > ntb->mws_size[mw]检查正是第 3 节 configfs 中mw1~mw4字段的实际约束点。 - CMD_LINK_UP(0x3):主机侧 NTB 应用绑定到 EP 设备后下发;当端点从两侧主机都收到该命令时,通过
epf_ntb_link_up()置位link_status的LINK_STATUS_UP并向两端各发一个中断,通知两端的 NTB 客户端可以开始通信。
主机侧驱动 ntb_hw_epf.c 还定义了配置区各寄存器的偏移(NTB_EPF_COMMAND = 0x0、NTB_EPF_ARGUMENT = 0x4、NTB_EPF_SPAD_OFFSET = 0x28、NTB_EPF_DB_DATA(n) = 0x34 + n*4 等,见 ntb_hw_epf.c#L16-L45),并约束主机侧门铃槽位布局:slot 0 保留给链路事件、DB 编号从 slot 2 起,故最少需要 3 个向量(NTB_EPF_MIN_DB_COUNT = 3),最多 31 个(NTB_EPF_MAX_DB_COUNT = 31,ntb_hw_epf.c#L47-L60)。这也解释了端点侧 MAX_DB_COUNT = 32 的取值:为链路事件与门铃向量预留了完整空间。
6. 主机侧验证与后续使用
端点侧完成绑定并 start 后,在主机(RootComplex 侧)执行 lspci,应看到与 configfs 中写入的 vendorid/deviceid 一致的设备(来自 pci-ntb-howto.rst):
# lspci
0000:00:00.0 PCI bridge: Texas Instruments Device b00d
0000:01:00.0 RAM memory: Texas Instruments Device b00d
主机侧软件遵循 Linux 标准 NTB 软件架构,可配合 ntb_hw_epf 主机驱动使用既有 NTB 客户端(如 NTB Transport、NTB Ping Pong Test、NTB Tool 等);更多 NTB 框架信息见 Non-Transparent Bridge 文档。
7. 小结
- pci-ntb.rst 是
pci_epf_ntb设备绑定的"字段清单":标准 EPF 字段中vendorid=0x104c、deviceid=0xb00d(TI J721E)、baseclass_code=0x5、subclass_code=0x00有硬性要求,其余 don't care;NTB 专有字段为db_count(默认 4)、mw1~mw4(各窗口大小)、num_mws(最大 4)、spad_count(默认 64)。 - 这些字段的解析与校验逻辑可在 pci-epf-ntb.c 中逐一对应:
num_mws超过 4 被拒、mwN要求num_mws >= N、主机映射请求不得大于mws_size[mw];字段值最终决定 6 个 BAR 的打包分配与出站 ATU 映射行为。 - 完整落地流程为:configfs
mkdir创建设备 → 写标准字段与 NTB 字段 →ln -s绑定 primary/secondary 两个 EP 控制器 →echo 1 > .../start建链 → 主机侧经ntb_hw_epf通过 Config Region 命令(CONFIGURE_DOORBELL / CONFIGURE_MW / LINK_UP)完成 NTB 链路。 - 相关延伸阅读:PCI 端点框架总览、EPF ConfigFS 使用、NTB 功能硬件建模、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