
Linux 内核 PCI 端点 vNTB 功能详解单端点搭建 NTB 通道的架构、BAR 建模与源码剖析【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文以内核文档 pci-vntb-function.rst 为核心讲解 Linux PCI Endpoint 框架中虚拟 NTBvNTB功能的工作原理它如何让一个PCIe 端点EP与主机之间建立 NTBNon-Transparent Bridge非透明桥通道而不必像传统 NTB 那样占用两个端点实例。读完后你能理解 vNTB 的五大构造Config Region、Scratchpad、Doorbell、Memory Window、BAR 建模规则、主机与 EP 之间的命令协议并知道如何在内核中配置CONFIG_PCI_EPF_VNTB和通过 configfs 完成部署验证。vNTB 与传统 NTB 的本质区别传统 PCI NTB 功能需要两个端点实例分别连接 HOST1 和 HOST2由两块端点控制器共同构成一条 NTB 通道而 PCI vNTB 功能只使用一个主机Host/RC和一个端点EP用 NTB 连接 EP 与 PCI 主机从而节省一个端点实例。原文档给出的拓扑关系如下pci-vntb-function.rst 第 1742 行------------ --------------------------------------- | | | | ------------ | -------------- | NTB | | | NTB | | NetDev | | | NetDev | ------------ | -------------- | NTB | | | NTB | | Transfer | | | Transfer | ------------ | -------------- | | | | | | PCI NTB | | | PCI Virtual | | EPF | | | NTB Driver | | Driver | | | | | | --------------- | | | | | PCI EP NTB |------| | | | | FN Driver | | | ------------ --------------- -------------- | | | | | | | PCI BUS | ----- | PCI EP BUS | | Virtual PCI | | | PCI | | | BUS | ------------ ------------------------------------- PCI RC PCI EP从这张图可以读出 vNTB 的核心思想EP 侧的 PCI EP Bus 上挂着一个虚拟 PCI BusVirtual PCI BUS其上存在一个PCI Virtual NTB Driver设备。也就是说Host 侧PCI RC看到的是标准 NTB 架构——ntb_hw_epf.c 这个 NTB 硬件驱动把 EP 呈现的 BAR 当作 NTB 控制寄存器、Doorbell 与 scratchpad 来操作Host 上所有标准 NTB 客户端NTB Transport、NTB NetDev、Ping Pong Test、Tool Test都可以直接使用EP 侧pci-epf-vntb.c 既是 EPF 功能驱动对 Host 扮演NTB 设备又在内核中伪造一条虚拟 PCI 总线、扫描出一个虚拟 PCI 设备并给这个虚拟设备注册一个pci-vntbPCI 驱动把ntb_dev注册进 NTB 核心层见 ntb_hw_epf.c 与 ntb 核心。因此 Host 的 NTB 驱动ntb_hw_epf与 EP 虚拟总线上的 NTB 驱动vntb_epf_ops互为 peer共享同一组 Config/Scratchpad/Doorbell/MW 资源。这一设计也体现在源码文件头部注释中pci-epf-vntb.c 开头即绘制了与文档一致的架构图。实现 vNTB 的五大构造原文档明确列出了实现 vNTB 所用的构造ConstructsConfig Region配置区Self Scratchpad Registers本地暂存寄存器Peer Scratchpad Registers对端暂存寄存器DoorbellDB门铃寄存器Memory WindowMW内存窗口Config Region命令与状态共享区文档指出 Config Region 与传统 PCI NTB 功能驱动相同可参考 pci-ntb-function.rst。结合源码它在 EP 侧对应的就是 struct epf_ntb_ctrlstruct epf_ntb_ctrl { u32 command; /* 0x00 命令字 */ u32 argument; /* 0x04 命令参数如 MW 索引 */ u16 command_status; /* 0x08 命令状态 */ u16 link_status; /* 0x0A 链路状态 */ u32 topology; /* 0x0C 拓扑 */ u64 addr; /* 0x10 对端 OB 地址 */ u64 size; /* 0x18 对端 OB 大小 */ u32 num_mws; /* 0x20 MW 数量 */ u32 reserved; u32 spad_offset; /* 0x28 scratchpad 区偏移 */ u32 spad_count; /* 0x2C scratchpad 数量 */ u32 db_entry_size; /* 0x30 Doorbell 条目大小 */ u32 db_data[MAX_DB_COUNT]; /* 0x34n*4 Doorbell MSI 数据 */ u32 db_offset[MAX_DB_COUNT]; /* 0xB4n*4 Doorbell 偏移 */ } __packed;上述偏移量与 Host 侧驱动 ntb_hw_epf.c 中定义的寄存器常量一一对应NTB_EPF_COMMAND 0x0、NTB_EPF_ARGUMENT 0x4、NTB_EPF_CMD_STATUS 0x8、NTB_EPF_SPAD_OFFSET 0x28、NTB_EPF_DB_DATA(n) 0x34n*4等构成 Host 与 EP 之间的控制平面协议。Scratchpad Registersself 与 peer 的内存布局文档说明 Scratchpad 区紧跟在 Config Region 之后其布局为pci-vntb-function.rst 第 6387 行-------------------------------------------------- Base | | | Common Config Register | | | ------------------------------------------------- Base span_offset | | | | Peer Span Space | Span Space | | | | ------------------------------------------------- Base span_offset | | | span_count * 4 | Span Space | Peer Span Space | | | | ------------------------------------------------- Virtual PCI Pcie Endpoint NTB Driver NTB Driver这个布局有一个精巧之处对 Virtual PCI Bus 上的 vNTB 驱动而言Peer Span Space正是 PCIe Endpoint 上自己的 Span Space——两侧互为对方的 scratchpad。pci-epf-vntb.c 中的注释明确说明Please note the self scratchpad region and config region is combined to a single region and mapped using the same BAR. Also note VHOSTs peer scratchpad is HOSTs self scratchpad.config 区与 self scratchpad 区合并映射到同一个 BARVHOST 的 peer scratchpad 就是 HOST 的 self scratchpad。源码中 scratchpad 的读写实现也印证了这一点。EP 侧的self spad读取访问后半段peer spad读取访问前半段vntb_epf_spad_read 等static u32 vntb_epf_spad_read(struct ntb_dev *ndev, int idx) { int off ntb-reg-spad_offset, ct ntb-reg-spad_count * sizeof(u32); val readl(base off ct idx * sizeof(u32)); /* self偏移 spad_offset spad_count*4 */ return val; } static u32 vntb_epf_peer_spad_read(struct ntb_dev *ndev, int pidx, int idx) { val readl(base off idx * sizeof(u32)); /* peer偏移 spad_offset */ return val; }scratchpad 区在绑定时与 config 区一起通过pci_epf_alloc_space()分配ctrl_size ALIGN(sizeof(struct epf_ntb_ctrl), 4)spad_size 2 * spad_count * 4两块、每块spad_count个 u32见 epf_ntb_config_spad_bar_alloc并在ctrl-spad_offset中记录偏移供 Host 侧驱动通过NTB_EPF_SPAD_OFFSET读取。Doorbell Registers文档定义Doorbell Registers are used by the hosts to interrupt each other.Doorbell 寄存器用于两个 Host 相互中断。在 vNTB 中两个 Host即物理 Host 与 EP 虚拟总线上的 vNTB 驱动。源码中的 Doorbell 槽位遵循一个为了向后兼容而保留的历史布局epf_irq_slot 与注释槽位含义slot 0EPF_IRQ_LINK保留给 link 事件slot 1EPF_IRQ_RESERVED_DB历史遗留的空闲槽位slot 2 起EPF_IRQ_DB_START真正的 Doorbell DB#0、DB#1…因此MIN_DB_COUNT EPF_IRQ_DB_START 1 3至少要有 DB#0 和 DB#1MAX_DB_COUNT 32configfs 写入db_count时超范围会返回-EINVAL见 epf_ntb_db_count_store。MSI 号与 Doorbell 位的对应关系在 vntb_epf_peer_db_work 的注释中写得很清楚Host 侧 ntb_hw_epf.c 也有完全一致的说明MSI #1 : link event (reserved) MSI #2 : unused (historical offset) MSI #3 : doorbell bit 0 (DB#0) MSI #4 : doorbell bit 1 (DB#1) ...源码特别注明Do not change this mapping to avoid breaking interoperability with older peers——不要更改该映射以免破坏与旧对端的互操作性。Memory Window文档定义Actual transfer of data between the two hosts will happen using the memory window.两个 Host 之间实际的数据传输通过内存窗口完成。在 vNTB 中每个 MW 对应 EP 的一个 BAR。EP 初始化时通过pci_epc_mem_alloc_addr()为虚拟总线分配 DMA 内存vpci_mw_phy[]/vpci_mw_addr[]见 epf_ntb_mw_bar_init当 Host 侧驱动通过命令下发配置 MW 目标地址时EP 侧执行 epf_ntb_configure_mwphys_addr ntb-vpci_mw_phy[mw]; /* EP 本地窗口源地址 */ addr ntb-reg-addr; /* Host 写入的 OB 目标地址 */ size ntb-reg-size; ret pci_epc_map_addr(ntb-epf-epc, func_no, vfunc_no, phys_addr, addr, size);即用 EPC 的 Outbound ATU 把 EP 的虚拟窗口映射到 Host 通知的地址上虚拟 vNTB 驱动随后便可直写Host 共享内存。窗口对齐约束由 vntb_epf_mw_get_align 给出地址对齐SZ_4K大小对齐 1最大值为 configfs 配置的mws_size[idx]。BAR 建模32-bit 与 64-bit 的构造分配文档给出了 vNTB 各构造到 BAR 的两种建模方式。32-bit BARs每个构造占一个 32 位 BARBAR NOCONSTRUCTS USEDBAR0Config RegionBAR1DoorbellBAR2Memory Window 1BAR3Memory Window 2BAR4Memory Window 3BAR5Memory Window 464-bit BARs64 位 BAR 成对占用 BAR 槽因此只用到偶数编号最多 2 个 MWBAR NOCONSTRUCTS USEDBAR0Config Region ScratchpadBAR1BAR0 的高 32 位BAR2DoorbellBAR3BAR2 的高 32 位BAR4Memory Window 1BAR5BAR4 的高 32 位源码与该建模完全吻合BAR 逻辑角色用 enum epf_ntb_barBAR_CONFIG, BAR_DB, BAR_MW1..BAR_MW4表示实际 BAR 编号由epf_ntb_bar[]数组承载。当用户未通过 configfs 指定*_bar属性时epf_ntb_init_epc_bar 会按文档描述的按顺序逐个取空闲 BAR的默认策略分配/* These are required BARs which are mandatory for NTB functionality */ for (bar BAR_CONFIG; bar BAR_MW1; bar) barno epf_ntb_find_bar(ntb, epc_features, bar, barno); /* These are optional BARs which dont impact NTB functionality */ for (bar BAR_MW1, i 1; i num_mws; bar, i) barno epf_ntb_find_bar(ntb, epc_features, bar, barno);即 Config、Doorbell、MW1 为必需 BAR分配失败直接报错返回-ENOENT其余 MW 为可选 BAR——BAR 不够时只是减少可用 MW 数量ntb-num_mws i并打印 debug 日志不影响 NTB 基本功能。用户也可通过 configfs 的ctrl_bar、db_bar、mw1_bar~mw4_bar属性取值范围NO_BAR~BAR_5见 EPF_NTB_BAR_W按平台需要固定分配这与文档By default, each construct is assigned a BAR, as needed and in order的说明一致。控制平面命令协议与事件通道Host → EP命令握手Host 侧驱动 ntb_epf_send_command 在cmd_lock互斥保护下依次写入argument和command然后轮询command_status超时时间NTB_EPF_COMMAND_TIMEOUT为 1 秒writel(argument, ndev-ctrl_reg NTB_EPF_ARGUMENT); writel(command, ndev-ctrl_reg NTB_EPF_COMMAND); /* 轮询 statusOK 则返回ERROR 返回 -EINVAL超时 WARN 并返回 -ETIMEDOUT */命令码在 EPF 侧定义为COMMAND_* 宏COMMAND_CONFIGURE_DOORBELL1、COMMAND_TEARDOWN_DOORBELL2、COMMAND_CONFIGURE_MW3、COMMAND_TEARDOWN_MW4、COMMAND_LINK_UP5、COMMAND_LINK_DOWN6状态码COMMAND_STATUS_OK1/COMMAND_STATUS_ERROR2。EP 侧cmd_handler 工作队列EP 侧由 delayed workepf_ntb_cmd_handler周期性处理命令epf_ntb_cmd_handler。其工作循环还承担了两个额外职责轮询 Doorbell仅当 Doorbell 非中断模式时每次迭代检查epf_db[]发现置位则更新原子位图ntb-db并调用ntb_db_event()通知 NTB 客户端重排下一次延迟queue_delayed_work(..., msi_doorbell ? 500ms : 5ms)——MSI 中断驱动 Doorbell 时命令轮询放宽到 500ms纯轮询模式保持 5ms 高频率。对COMMAND_CONFIGURE_MWhandler 调用epf_ntb_configure_mw()建立 OB ATU 映射成功/失败分别写回command_statusCOMMAND_LINK_UP/LINK_DOWN则置位/清除link_status的LINK_STATUS_UP位并经ntb_link_event()触发虚拟侧的 link 事件。EP → HostDoorbell 中断的合并与延迟触发vNTB 驱动向 Host 打对端门铃的路径是 vntb_epf_peer_db_set由于peer_db_set()可能在原子上下文被调用而pci_epc_raise_irq()会睡眠取epc-lock所以这里先把请求位图合并进peer_db_pending再投递到kpcintb工作队列由 vntb_epf_peer_db_work 在进程上下文逐个pci_epc_raise_irq(PCI_IRQ_MSI, interrupt_num)。每次运行有VNTB_PEER_DB_WORK_BUDGET 5个位图快照的处理预算防止门铃风暴独占 kworker处理不完则重新排队——这是典型的合并 限速设计。虚拟 PCI 总线EP 侧如何造出一个 NTB 设备vNTB 最巧妙的部分是 EP 侧完全用软件模拟出一条 PCI 总线。pci-epf-vntb.c 中配置空间模板静态数组pci_space[16]是 64 字节 PCI 配置空间的初始值——设备/厂商 ID 初值为0xffffffff第 0 dword、Header Type 字段置0x40正常设备、单功能等pci_ops 实现vpci_ops.read仅当devfn 0bus 0 设备 0时从pci_space拷贝其余返回PCIBIOS_DEVICE_NOT_FOUNDwrite直接返回 0总线扫描vpci_scan_bus()调用pci_scan_bus(ndev-vbus_number, vpci_ops, sysdata)建立虚拟总线再pci_bus_add_devices()让 PCI 核心枚举出设备 0动态 VID/PIDbind 时把用户配置的虚拟设备 ID 写入配置模板和匹配表epf_ntb_bindpci_space[0] (ntb-vntb_pid 16) | ntb-vntb_vid; pci_vntb_table[0].vendor ntb-vntb_vid; pci_vntb_table[0].device ntb-vntb_pid; ret pci_register_driver(vntb_pci_driver); ret vpci_scan_bus(ntb);随后vntb_pci_driverPCI_DEVICE(vntb_vid, vntb_pid)匹配到该虚拟设备pci_vntb_probe 中设置ntb.pdev、ntb.topo NTB_TOPO_NONE、ntb.ops vntb_epf_ops限制DMA_BIT_MASK(32)并调用ntb_register_device()完成 NTB 核心层注册。至此虚拟总线上的 vNTB 驱动与 Host 侧的ntb_hw_epf驱动正式成为一对 NTB peer二者通过共享的 Config/SPAD/DB/MW 区域通信。ntb_dev_opsEP 侧 NTB API 的实现vntb_epf_ops 实现了 NTB 核心层include/linux/ntb.h 中struct ntb_dev_ops要求的回调关键几项回调实现要点mw_count/peer_mw_count均返回 configfs 的num_mwsspad_count返回 configfs 的spad_countspad_read/spad_write/peer_spad_read/peer_spad_write直接readl/writel到共享控制块偏移见上文布局db_valid_mask/db_vector_count只报告可用的 Doorbell 中断数db_count - EPF_IRQ_DB_START屏蔽两个保留槽db_read/db_clear操作原子位图ntb-dbpeer_db_set合并到peer_db_pending并投递工作队列见上节mw_set_trans更新对应 MW 的epf_bar并pci_epc_set_bar()重建 INB 映射peer_mw_get_addr返回vpci_mw_phy[idx]/mws_size[idx]link_is_up返回reg-link_statusHost 通过COMMAND_LINK_UP/DOWN更新link_enable/link_disable返回 0链路状态由 Host 命令驱动get_dma_dev返回epc-dev.parent宿主机侧配套ntb_hw_epf 硬件驱动与 EPF 驱动配对的是 drivers/ntb/hw/epf/ntb_hw_epf.c它运行在 Host 内核中把 EP 呈现的RAM memory设备基类为PCI_BASE_CLASS_MEMORY见 epf_ntb_header识别为 NTB 设备从NTB_EPF_MW_COUNT0x20、NTB_EPF_SPAD_COUNT0x2C等寄存器读取对端能力通过barno_map把逻辑 BAR 角色BAR_CONFIG/BAR_PEER_SPAD/BAR_DB/BAR_MW1..4映射到实际 BAR 编号用ntb_epf_send_command()下发CMD_CONFIGURE_MW等命令1 秒超时Doorbell 槽位约束NTB_EPF_MIN_DB_COUNT3、NTB_EPF_MAX_DB_COUNT31与 EPF 侧的 3~32 范围相互兼容EPF 侧注释说明 slot 0 留给 link 事件、slot 1 历史未用因此至少 3 个向量才能容纳 DB#0/DB#1见 ntb_hw_epf.c。内核配置与部署操作内核配置vNTB 功能由 Kconfig 中的 PCI_EPF_VNTB 控制config PCI_EPF_VNTB tristate PCI Endpoint Virtual NTB driver depends on PCI_ENDPOINT depends on NTB select CONFIGFS_FS help Select this configuration option to enable the Non-Transparent Bridge (NTB) driver for PCIe Endpoint. NTB driver implements NTB between PCI Root Port and PCIe Endpoint.即必须同时开启PCI_ENDPOINTPCI 端点框架和NTBNTB 核心层并自动选择CONFIGFS_FS开启后按 Makefile 编译出pci-epf-vntb模块。Host 侧还需启用 NTB 及其 epf 硬件驱动drivers/ntb/hw/epf/下两者缺一不可。端点侧configfs 创建与配置以下操作步骤继承自配套用户指南 pci-vntb-howto.rst是原文档硬件配置与内部原理之外的落地操作。1查看可用资源——端点控制器与功能驱动# ls /sys/class/pci_epc/ 5f010000.pcie_ep # ls /sys/kernel/config/pci_ep/controllers # PCI_ENDPOINT_CONFIGFS 使能时 5f010000.pcie_ep # ls /sys/bus/pci-epf/drivers pci_epf_ntb pci_epf_test pci_epf_vntb2创建 vNTB 功能设备# mount -t configfs none /sys/kernel/config # cd /sys/kernel/config/pci_ep/ # mkdir functions/pci_epf_vntb/func1mkdir func1创建的功能设备会被pci_epf_vntb驱动 probe框架填充标准字段驱动默认值与源码 epf_ntb_header 一致vendorid默认0xffff、interrupt_pin默认0x0001即 INT A# cat functions/pci_epf_vntb/func1/vendorid 0xffff # cat functions/pci_epf_vntb/func1/interrupt_pin 0x00013配置功能参数对应 epf_ntb_attrs 的 17 个属性# echo 0x1957 functions/pci_epf_vntb/func1/vendorid # echo 0x0809 functions/pci_epf_vntb/func1/deviceid # ls functions/pci_epf_vntb/func1/pci_epf_vntb.0/ ctrl_bar db_count mw1_bar mw2_bar mw3_bar mw4_bar spad_count db_bar mw1 mw2 mw3 mw4 num_mws vbus_number vntb_vid vntb_pid # echo 4 functions/pci_epf_vntb/func1/pci_epf_vntb.0/db_count # echo 128 functions/pci_epf_vntb/func1/pci_epf_vntb.0/spad_count # echo 1 functions/pci_epf_vntb/func1/pci_epf_vntb.0/num_mws # echo 0x100000 functions/pci_epf_vntb/func1/pci_epf_vntb.0/mw1各属性的取值约束源码核对结果属性含义约束db_countDoorbell 槽位总数3 ~ 32超出返回-EINVALspad_countscratchpad 条目数u32决定2 * spad_count * 4字节的 SPAD 区大小num_mws内存窗口数最大 4MAX_MWmw1~mw4各窗口大小字节仅索引 num_mws的窗口有效否则-ERANGEctrl_bar/db_bar/mwN_bar固定构造到 BAR 编号NO_BAR~BAR_5不写则按默认顺序分配vntb_vid/vntb_pidEP 虚拟总线上 vNTB 设备的 VID/PID决定虚拟总线 lspci 结果vbus_number虚拟 PCI 总线号驱动初始为0xff示例配为0x10为虚拟 PCI 总线上的 vNTB 设备设置标识# echo 0x1957 functions/pci_epf_vntb/func1/pci_epf_vntb.0/vntb_vid # echo 0x080A functions/pci_epf_vntb/func1/pci_epf_vntb.0/vntb_pid # echo 0x10 functions/pci_epf_vntb/func1/pci_epf_vntb.0/vbus_number4绑定到端点控制器并启动链路vNTB 只有一个端点控制器# ln -s controllers/5f010000.pcie_ep functions/pci_epf_vntb/func1/primary # echo 1 controllers/5f010000.pcie_ep/startbind 触发后驱动依次执行BAR 规划 → 分配 configSPAD 区 → 设置 config/SPAD BAR → 配置 MSI/MSI-X要求 EPC 具备 MSI 或 MSI-X 能力否则报错 MSI or MSI-X is required for doorbell见 epf_ntb_configure_interrupt→ Doorbell BAR 初始化MSI 失败时回退为共享 BAR 内轮询→ MW BAR 初始化 → 写配置头 → 注册虚拟 PCI 驱动并扫描总线完整顺序见 epf_ntb_bind。主机侧与 EP 侧验证按 howto 文档配置生效后 Host 侧lspci应看到 EP 呈现的设备与上面vendorid0x1957、deviceid0x0809对应# lspci 00:00.0 PCI bridge: Freescale Semiconductor Inc Device 0000 (rev 01) 01:00.0 RAM memory: Freescale Semiconductor Inc Device 0809EP 侧内核中虚拟 PCI 总线编号 0x10上应出现由vntb_vid/vntb_pid决定的虚拟 NTB 设备# lspci 10:00.0 Unassigned class [ffff]: Dawicontrol Computersysteme GmbH Device 1234 (rev ff)客户端复用由于 Host 侧遵循标准 NTB 软件架构文档 pci-vntb-howto.rst 明确指出全部现成 NTB 客户端均可直接使用NTB Transportntb_transport 客户端、NTB NetDevdrivers/net/ntb_netdev.c、Ping Pong 测试drivers/ntb/test/ntb_pingpong.c、Tool 测试drivers/ntb/test/ntb_tool.c以及性能测试drivers/ntb/test/ntb_perf.c。小结PCI vNTB 功能用一个 Host 一个 EP替代传统 NTB 的双端点方案EP 侧的 pci-epf-vntb.c 在 configfs 驱动下完成 BAR 建模32 位 BAR 下 Config/DB/MW1-4 各占一 BAR64 位 BAR 下 ConfigSPAD、DB、MW1 各占一对与内存布局config 区 前后对称的 self/peer scratchpad通过struct epf_ntb_ctrl命令块与 Host 侧 ntb_hw_epf.c 建立 1 秒超时的命令握手用 MSI 中断或 5ms 轮询传递 Doorbell并借pci_scan_bus()伪造虚拟 PCI 总线让标准 NTB 客户端无感接入。理解这套构造后你可以在带 PCI Endpoint 控制器的 SoC 上按 configfs 流程快速搭出 NTB 通道并复用内核全部 NTB 客户端生态。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考