EP控制器开发概述

EP侧开发运行在 Endpoint 设备上(如FPGA、嵌入式PCIe设备),负责配置EP控制器、响应RC的请求、实现设备特定功能。这是一个相对小众但专业的方向。

本页定位

本页介绍 EP控制器驱动/固件开发——运行在Endpoint设备上,配置EP控制器、响应RC请求。如果你在开发 PCIe设备驱动(RC侧驱动EP设备)或 RC控制器驱动,请访问对应页面。

典型场景

  • FPGA PCIe IP核开发(数据采集卡、加速卡)
  • 嵌入式PCIe设备固件开发
  • PCIe测试设备/工具开发

EP开发与RC侧开发的本质区别:EP设备是"被驱动"的一方,它需要配置自己的BAR、响应来自RC的配置访问和Memory/IO请求、主动发送MSI中断通知RC。EP开发者需要理解PCIe协议的事务层和设备控制器硬件。

EP控制器配置要点

EP控制器初始化的核心步骤包括:设置设备ID、配置BAR、配置能力结构(如MSI)、启用控制器、等待链路训练完成。

/*
 * EP控制器初始化示例(伪代码)
 * 以Xilinx PCIe IP核为例
 */

/* 1. 设置设备ID和厂商ID */
Xil_Out32(PCIE_EP_BASE + PCIE_CFG_VENDOR_ID, 0x1234);
Xil_Out32(PCIE_EP_BASE + PCIE_CFG_DEVICE_ID, 0x5678);

/* 2. 配置BAR */
/* BAR0: 16KB Memory空间,可预取 */
Xil_Out32(PCIE_EP_BASE + PCIE_CFG_BAR0, 0x0000400C); /* 16KB, Prefetchable, 64-bit */
Xil_Out32(PCIE_EP_BASE + PCIE_CFG_BAR0_HIGH, 0x00000000);

/* BAR2: I/O空间 */
Xil_Out32(PCIE_EP_BASE + PCIE_CFG_BAR2, 0x00000101); /* 256B I/O */

/* 3. 配置能力结构 */
/* 设置MSI能力 */
Xil_Out32(PCIE_EP_BASE + PCIE_CAP_MSI_ADDR, msi_address);
Xil_Out32(PCIE_EP_BASE + PCIE_CAP_MSI_DATA, msi_data);

/* 4. 启用EP控制器 */
Xil_Out32(PCIE_EP_BASE + PCIE_CTRL, PCIE_CTRL_ENABLE);

/* 5. 等待链路训练完成 */
while (!(Xil_In32(PCIE_EP_BASE + PCIE_STATUS) & PCIE_STATUS_LINK_UP))
    ;

处理RC请求

EP设备需要响应来自RC的Memory Read/Write请求,并在需要时主动发送MSI中断通知RC。

处理Memory Write请求

/*
 * 处理来自RC的Memory Write请求
 */
void handle_rc_write(u32 bar_index, u32 offset, u32 data)
{
    switch (bar_index) {
    case 0: /* BAR0 - 控制寄存器区域 */
        if (offset == REG_CTRL_START) {
            /* 处理启动命令 */
            start_device();
        } else if (offset == REG_CTRL_RESET) {
            /* 处理复位命令 */
            reset_device();
        }
        break;
        
    case 2: /* BAR2 - 数据缓冲区 */
        /* 处理数据写入 */
        data_buffer[offset/4] = data;
        break;
    }
}

发送MSI中断

/*
 * 发送MSI中断到RC
 */
void send_msi_interrupt(u32 vector)
{
    /* 设置MSI数据 */
    Xil_Out32(PCIE_EP_BASE + PCIE_MSI_DATA, vector);
    
    /* 触发MSI */
    Xil_Out32(PCIE_EP_BASE + PCIE_MSI_TRIGGER, 1);
}

常用EP控制器IP核

FPGA和SoC厂商提供了多种PCIe控制器IP核,支持EP模式。以下是主流选择:

IP核 厂商 特点
Xilinx PCIe IP Xilinx/AMD 支持7系列、UltraScale、Versal,文档完善
PCIe Hard IP Intel 集成在Intel FPGA中,支持Gen1-Gen4
DesignWare PCIe Synopsys 广泛授权,支持EP/RC模式
PCIe Controller ARM CoreLink系列,常用于SoC设计

Linux PCIe EP子系统

Linux内核从4.13开始提供PCIe EP子系统,允许在Linux系统中实现PCIe Endpoint功能。这为EP开发提供了标准化的框架,无需裸机编程。

启用方式:内核配置 CONFIG_PCI_ENDPOINT=y,并选择对应的EP控制器驱动(如 DesignWare 平台的 CONFIG_PCIE_DW_PLAT_EP)与功能测试驱动(CONFIG_PCI_EPF_TEST);通过 configfs 组态需要 CONFIG_PCI_ENDPOINT_CONFIGFS=y

EP驱动核心API

/*
 * Linux PCIe EP驱动示例
 * 需要启用CONFIG_PCI_ENDPOINT
 */
#include 
#include 

static int my_epf_bind(struct pci_epf *epf)
{
    struct pci_epc *epc = epf->epc;
    struct pci_epf_bar *epf_bar = &epf->bar[0];
    phys_addr_t phys_addr;
    int ret;
    
    /* 设置配置空间(vfunc_no 传 0 表示物理功能) */
    ret = pci_epc_write_header(epc, epf->func_no, epf->vfunc_no, &epf->header);
    
    /* 配置BAR */
    ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, epf_bar);
    
    /* 分配局部可映射地址:返回值与物理地址挂在驱动私有结构中管理,
     * pci_epc_mem_alloc_addr 的结果不属于 struct pci_epf 的字段 */
    epf_test->reg_base = pci_epc_mem_alloc_addr(epc, &phys_addr, epf_bar->size);
    
    return 0;
}

static struct pci_epf_ops my_epf_ops = {
    .bind = my_epf_bind,
    .unbind = my_epf_unbind,
    .linkup = my_epf_linkup,
};

static struct pci_epf_driver my_epf_driver = {
    .driver.name = "my-pcie-epf",
    .ops = &my_epf_ops,
};

EP子系统架构

层次 结构 职责
EP Controller (EPC) struct pci_epc 底层硬件抽象,由控制器驱动实现
EP Function (EPF) struct pci_epf 设备功能逻辑,由功能驱动实现
EPF Driver struct pci_epf_driver 注册EPF ops,定义bind/unbind回调

关键API说明

  • pci_epc_write_header() — 写入配置空间头部(Vendor/Device ID等)
  • pci_epc_set_bar() — 配置BAR空间
  • pci_epc_mem_alloc_addr() — 分配EP侧内存映射
  • pci_epc_raise_irq() — 触发中断(MSI/MSI-X/INTx)
  • pci_epf_linkup() — 链路训练完成回调

通过 configfs 组态 EPC + EPF(实际使用方式)

EP 子系统通过 configfs 在运行时把 EPF 功能绑定到 EPC 控制器并启动链路,参考 Documentation/PCI/endpoint/pci-endpoint-cfs.rst

mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/pci_ep

# 1. 创建 EPF 功能实例并设置配置空间参数
mkdir functions/pci_epf_test/func1
echo 0x104c > functions/pci_epf_test/func1/vendorid
echo 0xb500 > functions/pci_epf_test/func1/deviceid

# 2. 将 EPF 关联到目标 EPC 控制器节点
mkdir controllers/51000000.pcie-ep
ln -s /sys/kernel/config/pci_ep/functions/pci_epf_test/func1 \
      controllers/51000000.pcie-ep/epf

# 3. 启动链路(LTSSM 开始训练,对端 RC 即可枚举到该 EP)
echo 1 > controllers/51000000.pcie-ep/start

EP开发调试

从RC侧验证EP设备

# 从RC侧查看EP设备是否被枚举
lspci -vvv

# 确认Vendor/Device ID
lspci -nn

# 查看BAR空间分配
lspci -vvv -s 01:00.0 | grep -A 5 Region

# 读取配置空间验证
lspci -xxx -s 01:00.0   # 000-0FFh
lspci -xxxx -s 01:00.0  # 000-FFFh (扩展配置空间)

# 使用setpci写入BAR测试
setpci -s 01:00.0 04.W=0006  # 启用Memory Space + Bus Master

# 使用devmem直接访问BAR映射的内存
devmem 0x80000000 32 0xDEADBEEF

常见问题排查

# 1. 设备未被RC枚举 → 检查链路训练
#    确认PCIE_STATUS_LINK_UP已置位
#    检查PHY复位和时钟
#    确认EP控制器已启用 (PCIE_CTRL_ENABLE)

# 2. 配置空间全0 → 检查Vendor/Device ID设置
#    确认PCIE_CFG_VENDOR_ID和PCIE_CFG_DEVICE_ID已写入
#    确认配置空间访问权限已开启

# 3. BAR无法访问 → 检查BAR配置
#    确认BAR大小和类型(Memory/I/O)正确
#    确认Prefetchable位设置正确
#    从RC侧确认Command寄存器Memory Space位已置位

# 4. MSI中断未收到 → 检查MSI配置
#    确认MSI Capability已正确配置
#    确认MSI Address/Data寄存器已写入RC地址
#    从RC侧检查dmesg是否有MSI分配日志

# 5. Linux EP子系统不加载 → 检查内核配置
#    确认CONFIG_PCI_ENDPOINT=y
#    确认EP控制器驱动已编译进内核或模块
#    检查dmesg | grep -i endpoint

推荐学习资源

规范文档

  • PCI Express Base Specification(EP相关章节)
  • Xilinx PG054 — AXI Bridge for PCIe Gen3
  • Synopsys DesignWare PCIe Controller Databook

内核文档

FPGA开发

  • Xilinx Vivado PCIe IP核向导
  • Intel Quartus PCIe IP编译工具
  • PCIe协议分析仪(如LeCroy)

测试工具

  • pci_epf_test — 内核自带EP功能测试驱动(CONFIG_PCI_EPF_TEST)
  • pcitest / selftests/pci_endpoint — 与 pci_epf_test 配套的用户态测试
  • Devmem — 直接访问BAR内存