EP 控制器驱动开发
Endpoint 侧的固件与驱动开发
EP控制器开发概述
EP侧开发运行在 Endpoint 设备上(如FPGA、嵌入式PCIe设备),负责配置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
内核文档
- Linux PCI Endpoint子系统文档
drivers/pci/endpoint/源码目录Documentation/PCI/endpoint/文档
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内存