PCIe 软件开发
从驱动框架到实战编程
软件开发概述
PCIe软件开发涉及多个层次,从固件层的基本输入输出系统(BIOS/UEFI)初始化,到操作系统内核的设备驱动,再到用户空间的应用程序。在开始学习之前,请务必明确你的开发方向(参见架构概览)。
重要提示
本页面主要介绍PCIe设备驱动开发(运行在RC侧,驱动EP设备)。如果你需要开发RC控制器驱动或EP固件,请参考下方的专门章节。
用户应用程序
设备控制、数据处理
内核驱动
设备枚举、资源管理、中断处理
PCI子系统
总线枚举、资源分配、热插拔
固件 (BIOS/UEFI)
平台初始化、ACPI配置
Linux PCI子系统
Linux内核提供了完善的PCI/PCIe子系统框架,驱动开发者可以通过标准API访问设备资源。
PCI Core
总线核心、设备模型
PCI Host Bridge Driver
RC驱动
Device Driver
终端设备驱动
Hardware
Root Complex / Endpoint
关键数据结构
/*
* pci_dev - 表示一个PCI设备
* 包含设备的所有信息:配置空间、资源、驱动等
*/
struct pci_dev {
struct list_head bus_list; /* 总线链表 */
struct pci_bus *bus; /* 所属总线 */
struct pci_bus *subordinate; /* 桥接设备的下游总线 */
u16 vendor; /* 厂商ID */
u16 device; /* 设备ID */
u16 subsystem_vendor; /* 子系统厂商ID */
u16 subsystem_device; /* 子系统设备ID */
u8 revision; /* 修订版本 */
u8 hdr_type; /* 头部类型 */
struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR资源 */
u8 irq; /* 中断号 */
struct msix_entry *msix_entries; /* MSI-X条目 */
struct pci_driver *driver; /* 绑定的驱动 */
void *driver_data; /* 驱动私有数据 */
};
/*
* pci_driver - PCI驱动结构
* 定义驱动标识、探测/移除回调等
*/
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id *id_table;
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
void (*remove)(struct pci_dev *dev);
int (*suspend)(struct pci_dev *dev, pm_message_t state);
int (*resume)(struct pci_dev *dev);
void (*shutdown)(struct pci_dev *dev);
struct device_driver driver;
const struct pci_error_handlers *err_handler;
};
驱动开发基础
设备ID表与模块注册
/*
* 定义设备ID表 - 驱动匹配规则
*/
static const struct pci_device_id my_pci_ids[] = {
/* vendor, device, subvendor, subdevice, class, class_mask, driver_data */
{ PCI_DEVICE(0x1234, 0x5678) }, /* 匹配厂商1234, 设备5678 */
{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_NVME, 0xffffff) }, /* 匹配NVMe类 */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
/*
* 驱动结构定义
*/
static struct pci_driver my_pci_driver = {
.name = "my_pci_driver",
.id_table = my_pci_ids,
.probe = my_probe,
.remove = my_remove,
};
/*
* 模块注册
*/
module_pci_driver(my_pci_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PCIe Device Driver Example");
Probe函数实现
static int my_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int ret;
void __iomem *mmio_base;
struct my_device *dev;
/* 1. 启用PCI设备 */
ret = pci_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "无法启用设备\n");
return ret;
}
/* 2. 请求MMIO区域 (BAR0) */
ret = pci_request_region(pdev, 0, "my_driver");
if (ret) {
dev_err(&pdev->dev, "无法请求BAR0区域\n");
goto err_disable;
}
/* 3. 映射MMIO到内核虚拟地址空间 */
mmio_base = pci_iomap(pdev, 0, 0);
if (!mmio_base) {
dev_err(&pdev->dev, "无法映射MMIO\n");
ret = -ENOMEM;
goto err_region;
}
/* 4. 设置DMA掩码 */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "不支持DMA\n");
goto err_iomap;
}
}
/* 5. 启用总线主控 (DMA) */
pci_set_master(pdev);
/* 6. 分配设备私有结构 */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
ret = -ENOMEM;
goto err_iomap;
}
dev->pdev = pdev;
dev->mmio_base = mmio_base;
pci_set_drvdata(pdev, dev);
/* 7. 初始化设备硬件 */
my_hw_init(dev);
/* 8. 注册中断 */
ret = request_irq(pdev->irq, my_interrupt, IRQF_SHARED,
"my_device", dev);
if (ret) {
dev_err(&pdev->dev, "无法注册中断\n");
goto err_free;
}
dev_info(&pdev->dev, "设备初始化成功\n");
return 0;
err_free:
kfree(dev);
err_iomap:
pci_iounmap(pdev, mmio_base);
err_region:
pci_release_region(pdev, 0);
err_disable:
pci_disable_device(pdev);
return ret;
}
Remove函数实现
static void my_remove(struct pci_dev *pdev)
{
struct my_device *dev = pci_get_drvdata(pdev);
if (!dev)
return;
/* 禁用中断 */
free_irq(pdev->irq, dev);
/* 停止设备 */
my_hw_stop(dev);
/* 清理资源 */
pci_iounmap(pdev, dev->mmio_base);
pci_release_region(pdev, 0);
pci_disable_device(pdev);
kfree(dev);
pci_set_drvdata(pdev, NULL);
}
配置空间访问
Linux内核提供了多种访问配置空间的方法。
标准配置空间访问 (0x00-0xFF)
/* 读取配置空间 */
u8 val8;
u16 val16;
u32 val32;
pci_read_config_byte(pdev, PCI_VENDOR_ID, &val16);
pci_read_config_word(pdev, PCI_DEVICE_ID, &val16);
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &val32);
/* 写入配置空间 */
pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, 10);
pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, 0xf0000000);
扩展配置空间访问 (0x100-0xFFF)
/*
* PCIe扩展配置空间 (4KB)
* 需要使用ECAM或PCIe增强访问机制
*/
/* 读取扩展配置寄存器 */
int pcie_capability_read_word(struct pci_dev *dev, int pos, u16 *val);
int pcie_capability_read_dword(struct pci_dev *dev, int pos, u32 *val);
/* 示例:读取链路状态 */
u16 link_status;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &link_status);
dev_info(&pdev->dev, "链路速度: %d, 宽度: x%d\n",
link_status & PCI_EXP_LNKSTA_CLS,
(link_status & PCI_EXP_LNKSTA_NLW) >> 4);
DMA 编程
PCIe设备通常使用DMA(直接内存访问)进行高速数据传输,避免CPU参与数据搬运。
流式DMA (Streaming DMA)
用于一次性数据传输,需要显式同步。
/*
* 流式DMA映射 - 用于单向传输
*/
/* 映射单个缓冲区 */
dma_addr_t dma_handle;
void *virt_addr = kmalloc(size, GFP_KERNEL);
dma_handle = dma_map_single(&pdev->dev, virt_addr, size, DMA_TO_DEVICE);
/* 检查映射结果 */
if (dma_mapping_error(&pdev->dev, dma_handle)) {
dev_err(&pdev->dev, "DMA映射失败\n");
kfree(virt_addr);
return -ENOMEM;
}
/* 告知设备DMA地址和大小 */
writel(dma_handle, dev->mmio_base + DMA_ADDR_REG);
writel(size, dev->mmio_base + DMA_SIZE_REG);
writel(START_TRANSFER, dev->mmio_base + DMA_CTRL_REG);
/* 等待传输完成... */
/* 传输完成后取消映射 */
dma_unmap_single(&pdev->dev, dma_handle, size, DMA_TO_DEVICE);
kfree(virt_addr);
一致性DMA (Coherent DMA)
用于设备频繁访问的共享内存,自动维护缓存一致性。
/*
* 一致性DMA映射 - 用于设备持续访问
*/
void *virt_addr;
dma_addr_t dma_handle;
/* 分配一致性DMA缓冲区 */
virt_addr = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL);
if (!virt_addr) {
dev_err(&pdev->dev, "DMA分配失败\n");
return -ENOMEM;
}
/* CPU写入数据 */
memset(virt_addr, 0xaa, size);
/* 确保数据对设备可见 */
wmb();
/* 告知设备DMA地址 */
writel(dma_handle, dev->mmio_base + DMA_ADDR_REG);
writel(size, dev->mmio_base + DMA_SIZE_REG);
/* 设备可以直接访问此缓冲区 */
/* 不需要手动同步 */
/* 清理时释放 */
dma_free_coherent(&pdev->dev, size, virt_addr, dma_handle);
分散/聚集DMA (Scatter/Gather)
用于处理不连续内存区域的传输。
/*
* Scatter/Gather DMA - 处理不连续内存
*/
struct scatterlist sg[16];
int nents;
/* 初始化scatterlist */
sg_init_table(sg, 16);
/* 添加页面到scatterlist */
nents = 0;
for (i = 0; i < nr_pages; i++) {
struct page *page = alloc_page(GFP_KERNEL);
sg_set_page(&sg[nents], page, PAGE_SIZE, 0);
nents++;
}
/* 映射scatterlist */
nents = dma_map_sg(&pdev->dev, sg, nents, DMA_TO_DEVICE);
if (!nents) {
dev_err(&pdev->dev, "scatterlist映射失败\n");
return -ENOMEM;
}
/* 将sg列表地址告知设备 */
/* 设备驱动通常会将sg列表转换为设备特定的描述符链表 */
/* 传输完成后取消映射 */
dma_unmap_sg(&pdev->dev, sg, nents, DMA_TO_DEVICE);
中断处理
PCIe支持多种中断机制:传统INTx、MSI和MSI-X。
| 类型 | 向量数 | 特点 | 适用场景 |
|---|---|---|---|
| INTx | 1 | 共享中断,性能较低 | 传统设备 |
| MSI | 1-32 | 向量独立,中等性能 | 一般设备 |
| MSI-X | 可达2048 | 向量独立,高性能 | 高速设备(NVMe, NIC) |
MSI-X 实现
/*
* MSI-X中断实现
*/
#define NUM_MSIX_VECTORS 16
struct msix_entry msix_entries[NUM_MSIX_VECTORS];
static irqreturn_t my_msix_handler(int irq, void *data)
{
struct my_queue *q = data;
u32 status;
/* 读取中断状态 */
status = readl(q->mmio + QUEUE_INT_STATUS);
writel(status, q->mmio + QUEUE_INT_STATUS); /* 清除中断 */
/* 处理完成队列 */
my_process_completions(q);
return IRQ_HANDLED;
}
static int setup_msix(struct pci_dev *pdev, struct my_device *dev)
{
int ret, i;
/* 启用MSI-X */
ret = pci_alloc_irq_vectors(pdev, NUM_MSIX_VECTORS, NUM_MSIX_VECTORS,
PCI_IRQ_MSIX);
if (ret < 0) {
dev_err(&pdev->dev, "无法分配MSI-X向量: %d\n", ret);
return ret;
}
/* 为每个向量注册中断处理函数 */
for (i = 0; i < NUM_MSIX_VECTORS; i++) {
int vector = pci_irq_vector(pdev, i);
ret = request_irq(vector, my_msix_handler, 0,
"my_device", &dev->queues[i]);
if (ret) {
dev_err(&pdev->dev, "无法请求IRQ %d: %d\n", vector, ret);
goto err_free_irqs;
}
dev->queues[i].vector = vector;
}
return 0;
err_free_irqs:
while (--i >= 0) {
free_irq(dev->queues[i].vector, &dev->queues[i]);
}
pci_free_irq_vectors(pdev);
return ret;
}
用户空间交互
字符设备接口
/*
* 字符设备 - 提供标准文件操作
*/
static int my_open(struct inode *inode, struct file *filp)
{
struct my_device *dev = container_of(inode->i_cdev,
struct my_device, cdev);
filp->private_data = dev;
return 0;
}
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct my_device *dev = filp->private_data;
switch (cmd) {
case MY_IOCTL_GET_INFO:
/* 返回设备信息 */
return copy_to_user((void __user *)arg, &dev->info,
sizeof(dev->info));
case MY_IOCTL_SUBMIT_CMD:
/* 提交命令到设备 */
return my_submit_command(dev, arg);
default:
return -ENOTTY;
}
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.mmap = my_mmap,
.unlocked_ioctl = my_ioctl,
};
mmap实现
/*
* mmap - 将设备内存映射到用户空间
*/
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct my_device *dev = filp->private_data;
unsigned long vsize = vma->vm_end - vma->vm_start;
phys_addr_t paddr = dev->mmio_phys;
unsigned long psize;
psize = pci_resource_len(dev->pdev, 0);
if (vsize > psize)
return -EINVAL;
/* 禁用缓存(MMIO区域) */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* 映射物理地址到用户虚拟地址 */
if (io_remap_pfn_range(vma, vma->vm_start,
paddr >> PAGE_SHIFT, vsize,
vma->vm_page_prot))
return -EAGAIN;
return 0;
}
/*
* 映射DMA缓冲区到用户空间
*/
static int my_dma_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct my_device *dev = filp->private_data;
return dma_mmap_coherent(&dev->pdev->dev, vma,
dev->dma_virt, dev->dma_handle,
vma->vm_end - vma->vm_start);
}
Sysfs接口
/*
* Sysfs属性 - 提供设备参数访问
*/
static ssize_t speed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
u16 link_status;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &link_status);
return sprintf(buf, "Gen%d x%d\n",
link_status & PCI_EXP_LNKSTA_CLS,
(link_status & PCI_EXP_LNKSTA_NLW) >> 4);
}
static ssize_t speed_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
/* 可以实现配置修改 */
return count;
}
static DEVICE_ATTR_RW(speed);
static struct attribute *my_attrs[] = {
&dev_attr_speed.attr,
NULL,
};
static const struct attribute_group my_attr_group = {
.attrs = my_attrs,
};
/* 在probe中创建 */
sysfs_create_group(&pdev->dev.kobj, &my_attr_group);
调试方法
内核命令
# 列出所有PCI设备
lspci -vvv
# 查看特定设备
lspci -s 01:00.0 -vvv
# 查看设备资源
cat /sys/bus/pci/devices/0000:01:00.0/resource
# 查看配置空间
hexdump -C /sys/bus/pci/devices/0000:01:00.0/config
# 启用设备
echo 1 > /sys/bus/pci/devices/0000:01:00.0/enable
# 绑定/解绑驱动
echo '0000:01:00.0' > /sys/bus/pci/drivers/my_driver/bind
echo '0000:01:00.0' > /sys/bus/pci/drivers/my_driver/unbind
# 查看内核日志
dmesg | tail -50
# 动态调试
echo 'module my_driver +p' > /sys/kernel/debug/dynamic_debug/control
内核调试选项
# 内核配置选项
CONFIG_PCI_DEBUG=y # PCI调试信息
CONFIG_DYNAMIC_DEBUG=y # 动态调试
# 启用所有PCI调试
echo 1 > /proc/sys/kernel/printk
# 使用ftrace跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo pci_* > /sys/kernel/debug/tracing/set_ftrace_filter
cat /sys/kernel/debug/tracing/trace
最佳实践
资源管理
- 使用devm_*函数族进行资源管理,简化清理代码
- 在probe失败时正确清理已分配的资源
- 使用引用计数管理设备生命周期
错误处理
- 检查所有可能失败的函数返回值
- 使用goto进行错误路径的集中处理
- 实现AER错误恢复回调
性能优化
- 合理使用一致性DMA和流式DMA
- 利用MSI-X多向量实现中断亲和性
- 考虑使用DPDK或RDMA等高性能框架
电源管理
- 实现runtime PM回调
- 正确处理suspend/resume
- 使用ASPM减少功耗
RC控制器驱动开发
RC控制器驱动运行在Root Complex所在的系统中,负责初始化PCIe控制器硬件、枚举设备、提供配置空间访问接口。这是PCIe子系统的"基础设施"。
典型场景
- SoC厂商为新芯片开发PCIe RC支持
- 移植Linux内核到新硬件平台
- 调试PCIe链路训练失败问题
关键数据结构
/*
* pci_host_bridge - 表示一个PCI主机桥
* 是RC控制器在内核中的抽象
*/
struct pci_host_bridge {
struct device dev;
struct pci_bus *bus; /* 根总线 */
struct list_head windows; /* 资源窗口 */
void *sysdata; /* 平台私有数据 */
/* 回调函数 */
int (*map_irq)(const struct pci_dev *, u8, u8);
void (*swizzle_irq)(struct pci_dev *, u8 *, u8 *);
};
/*
* pci_ops - 配置空间访问操作
* RC驱动必须实现这些操作
*/
struct pci_ops {
void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where);
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
};
ECAM配置访问实现
ECAM(Enhanced Configuration Access Mechanism)是PCIe规范定义的标准配置空间访问方法,通过MMIO访问完整的4KB配置空间。
/*
* ECAM配置空间映射
* 地址计算:Base + (Bus << 20) + (Device << 15) + (Function << 12) + Offset
*/
static void __iomem *ecam_map_bus(struct pci_bus *bus, unsigned int devfn, int where)
{
struct my_rc_data *data = bus->sysdata;
void __iomem *base = data->ecam_base;
unsigned int busn = bus->number;
unsigned int dev = PCI_SLOT(devfn);
unsigned int fn = PCI_FUNC(devfn);
u32 offset = (busn << 20) | (dev << 15) | (fn << 12) | (where & ~3);
return base + offset;
}
static int ecam_read(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)
{
void __iomem *addr = ecam_map_bus(bus, devfn, where);
if (!addr) {
*val = ~0;
return PCIBIOS_DEVICE_NOT_FOUND;
}
*val = readl(addr);
/* 根据size调整返回值 */
if (size == 1)
*val = (*val >> ((where & 3) * 8)) & 0xff;
else if (size == 2)
*val = (*val >> ((where & 3) * 8)) & 0xffff;
return PCIBIOS_SUCCESSFUL;
}
static struct pci_ops ecam_ops = {
.map_bus = ecam_map_bus,
.read = ecam_read,
.write = ecam_write, /* 类似实现 */
};
RC控制器初始化流程
/*
* RC驱动probe函数示例
*/
static int my_rc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pci_host_bridge *bridge;
struct resource *cfg_res, *io_res, *mem_res;
struct my_rc_data *data;
int ret;
/* 1. 分配host bridge结构 */
bridge = devm_pci_alloc_host_bridge(dev, sizeof(*data));
if (!bridge)
return -ENOMEM;
data = pci_host_bridge_priv(bridge);
/* 2. 获取资源(从设备树或ACPI) */
cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config");
data->ecam_base = devm_ioremap_resource(dev, cfg_res);
if (IS_ERR(data->ecam_base))
return PTR_ERR(data->ecam_base);
/* 3. 初始化硬件 */
ret = my_rc_hw_init(pdev, data);
if (ret)
return ret;
/* 4. 设置配置空间操作 */
bridge->ops = &ecam_ops;
bridge->sysdata = data;
/* 5. 设置资源窗口 */
list_splice_init(&bridge->windows, &bridge->dma_ranges);
/* 6. 注册host bridge */
ret = devm_pci_scan_host_bridge(dev, bridge);
if (ret)
return ret;
/* 7. 为每个根总线创建sysfs */
pci_bus_assign_resources(bridge->bus);
pci_bus_add_devices(bridge->bus);
return 0;
}
static const struct of_device_id my_rc_of_match[] = {
{ .compatible = "vendor,pcie-rc", },
{ }
};
static struct platform_driver my_rc_driver = {
.probe = my_rc_probe,
.driver = {
.name = "my-pcie-rc",
.of_match_table = my_rc_of_match,
},
};
builtin_platform_driver(my_rc_driver);
设备树示例
/* ARM平台设备树示例 */
pcie@40000000 {
compatible = "vendor,pcie-rc";
reg = <0x40000000 0x01000000>, /* 配置空间 */
<0x41000000 0x00100000>; /* RC寄存器 */
reg-names = "config", "ctrl";
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x02000000 0x0 0x80000000 0x80000000 0x0 0x20000000>;
bus-range = <0x00 0xff>;
interrupts = ,
;
interrupt-names = "intx", "msi";
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &gic GIC_SPI 120 ...>;
status = "okay";
};
相关内核源码
| 文件路径 | 内容 |
|---|---|
| drivers/pci/controller/ | 各种RC控制器驱动 |
| drivers/pci/controller/pcie-xilinx.c | Xilinx PCIe RC驱动示例 |
| drivers/pci/controller/dwc/ | Synopsys DesignWare PCIe控制器 |
| drivers/pci/probe.c | 设备枚举逻辑 |
| drivers/pci/setup-bus.c | 总线资源分配 |
EP固件/驱动开发简介
EP侧开发运行在Endpoint设备上(如FPGA、嵌入式PCIe设备),负责配置EP控制器、响应RC的请求、实现设备特定功能。这是一个相对小众但专业的方向。
典型场景
- FPGA PCIe IP核开发(数据采集卡、加速卡)
- 嵌入式PCIe设备固件开发
- PCIe测试设备/工具开发
EP控制器配置要点
/*
* 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, 0x00004004); /* 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请求
/*
* 处理来自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中断到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核
| 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.14开始支持PCIe EP子系统,允许在Linux系统中实现PCIe Endpoint功能。
/*
* 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];
int ret;
/* 设置配置空间 */
ret = pci_epc_write_header(epc, epf->func_no, &epf->header);
/* 配置BAR */
ret = pci_epc_set_bar(epc, epf->func_no, epf_bar);
/* 映射地址 */
epf->epc_mem = pci_epc_mem_alloc_addr(epc, &epf->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,
};
推荐学习资源
规范文档
- PCI-SIG 官方规范
- PCI Express Base Specification
- PCI Express Card Electromechanical Spec
内核文档
- Linux PCI子系统文档
- Documentation/PCI/pci.txt
- drivers/pci/README
推荐书籍
- PCI Express System Architecture (MindShare)
- PCI Express Technology (Mike Jackson)
- Linux Device Drivers (LDD3)
调试工具
- lspci - PCI设备查看
- setpci - 配置空间访问
- /sys/bus/pci/ - sysfs接口