软件开发概述

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 Express System Architecture (MindShare)
  • PCI Express Technology (Mike Jackson)
  • Linux Device Drivers (LDD3)

调试工具

  • lspci - PCI设备查看
  • setpci - 配置空间访问
  • /sys/bus/pci/ - sysfs接口