PCI驱动架构与DMA传输 设备驱动工程实践进阶 - 第1节
1. PCI总线基础
PCI(Peripheral Component Interconnect)总线自1992年由Intel提出以来,经历了从并行共享总线到串行点对点互连的深刻变革。理解这一演进过程,是掌握PCI驱动架构的基础。
| 总线标准 | 推出年份 | 总线宽度 | 工作频率 | 峰值带宽 | 拓扑结构 |
|---|---|---|---|---|---|
| ISA | 1981 | 8/16 bit | 8.33 MHz | ~8 MB/s | 共享并行 |
| PCI | 1992 | 32/64 bit | 33/66 MHz | 133/533 MB/s | 共享并行 |
| PCI-X | 1998 | 64 bit | 66~533 MHz | ~4.3 GB/s | 共享并行 |
| PCIe 1.0 | 2003 | 1~32 Lane | 2.5 GT/s | 250 MB/s per Lane | 点对点串行 |
| PCIe 4.0 | 2017 | 1~16 Lane | 16 GT/s | ~2 GB/s per Lane | 点对点串行 |
关键转折点:PCIe采用差分串行信号和交换架构,彻底解决了并行总线在高速下的信号同步与反射问题,使带宽可随Lane数线性扩展。
1.2 PCI配置空间:设备发现的基石
PCI配置空间是驱动与硬件通信的“身份证”。每个PCI功能(Function)都拥有独立的256字节配置空间(PCIe扩展至4KB),由Host Bridge在系统启动时通过配置读写周期访问。
1.2.1 配置空间布局(前64字节)
| 偏移 | 寄存器名称 | 宽度 | 描述 |
|---|---|---|---|
| 0x00 | Vendor ID | 16 bit | 厂商编号(如Intel 0x8086) |
| 0x02 | Device ID | 16 bit | 设备型号编号 |
| 0x04 | Command | 16 bit | 控制I/O、内存、DMA、中断使能 |
| 0x06 | Status | 16 bit | 中断状态、Capabilities链表等 |
| 0x08 | Revision ID | 8 bit | 硅片修订版本 |
| 0x09 | Class Code | 24 bit | 设备分类(Base/Sub/Prog IF) |
| 0x10~0x24 | Base Address Registers (BAR0~5) | 32 bit each | 映射I/O或内存地址空间 |
| 0x3C | Interrupt Line | 8 bit | 中断向量号(由BIOS/OS填写) |
| 0x3D | Interrupt Pin | 8 bit | INTA#~INTD#(1~4) |
驱动开发要点:Linux内核通过
pci_read_config_*() / pci_write_config_*() 系列函数访问配置空间。BAR寄存器在驱动探测阶段由内核自动分配资源,驱动只需调用 pci_ioremap_bar() 映射到虚拟地址空间。
1.3 PCI地址空间:三种地址域与DMA基础
PCI总线定义了三种独立的地址空间:内存空间、I/O空间和配置空间。驱动开发者必须理解这三种空间的映射关系,才能正确操作设备寄存器并配置DMA传输。
1.3.1 三种地址空间对比
| 特性 | 内存空间 | I/O空间 | 配置空间 |
|---|---|---|---|
| 访问指令 | MOV / LD / ST | IN / OUT (x86) | 配置读写周期 |
| 地址宽度 | 32/64 bit | 16/32 bit | 8 bit (Bus:Dev:Func) |
| BAR类型 | Memory BAR (bit0=0) | I/O BAR (bit0=1) | 不通过BAR |
| Prefetchable | 支持(可合并写) | 不支持 | N/A |
| DMA支持 | 主控DMA必须使用 | 不支持DMA | 仅用于配置 |
| Linux API | ioremap() + readl/writel |
ioport_map() + inb/outb |
pci_read_config_*() |
常见陷阱:许多现代PCIe设备只实现Memory BAR,不再支持I/O空间。驱动开发时应优先使用内存映射I/O(MMIO),避免依赖I/O空间以保证跨平台兼容性(如ARM/ARM64不支持x86的IN/OUT指令)。
1.3.2 地址转换与DMA
DMA传输涉及三个地址域:CPU虚拟地址 → CPU物理地址 → PCI总线地址。IOMMU(如Intel VT-d)在中间层提供映射服务,使设备可以访问非连续的物理内存。
1.4 PCI中断机制:从INTx到MSI/MSI-X
PCI中断机制经历了从传统的边带中断(INTx)到消息信号中断(MSI/MSI-X)的演进。MSI/MSI-X通过内存写事务传递中断,避免了INTx的共享中断线问题,是高性能设备驱动的首选。
1.4.1 中断类型对比
| 特性 | INTx (传统) | MSI | MSI-X |
|---|---|---|---|
| 信号方式 | 专用中断线(INTA~INTD) | 内存写TLP | 内存写TLP |
| 中断向量数 | 最多4个/设备 | 最多32个/功能 | 最多2048个/功能 |
| 共享中断 | 支持(需驱动处理共享) | 不支持共享 | 不支持共享 |
| 每个向量独立配置 | 否 | 否(连续分配) | 是(独立地址+数据) |
| Linux使能API | request_irq() |
pci_enable_msi() |
pci_enable_msix() |
| 适用场景 | 兼容旧设备 | 通用高性能 | 多队列/多核负载均衡 |
性能建议:NVMe SSD、万兆网卡等高性能设备应优先使用MSI-X,为每个硬件队列分配独立中断向量,配合CPU亲和性设置实现中断负载均衡。
1.4.2 MSI/MSI-X中断传递流程
1.4.3 中断处理编程模板
/* MSI-X中断使能示例 */
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
struct my_device *dev;
struct msix_entry entries[NR_IRQS];
int i;
/* 1. 分配设备私有结构 */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
pci_set_drvdata(pdev, dev);
/* 2. 使能PCI设备 */
ret = pci_enable_device(pdev);
if (ret) goto err_free;
/* 3. 设置DMA掩码(64-bit优先) */
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) goto err_disable;
/* 4. 请求MMIO资源 */
ret = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME);
if (ret) goto err_disable;
dev->regs = pcim_iomap_table(pdev)[0];
/* 5. 分配MSI-X向量 */
for (i = 0; i < NR_IRQS; i++)
entries[i].entry = i; /* 硬件队列索引 */
ret = pci_enable_msix_exact(pdev, entries, NR_IRQS);
if (ret) goto err_disable;
/* 6. 注册中断处理 */
for (i = 0; i < NR_IRQS; i++) {
ret = devm_request_irq(&pdev->dev, entries[i].vector,
my_interrupt_handler, 0,
dev_name(&pdev->dev), &dev->queue[i]);
if (ret) goto err_free_msix;
}
return 0;
err_free_msix:
pci_disable_msix(pdev);
err_disable:
pci_disable_device(pdev);
err_free:
kfree(dev);
return ret;
}
最佳实践:使用
devm_* 系列API(Managed Device Resource)可以自动管理资源释放,减少错误处理路径的复杂度。上述示例中 pcim_iomap_regions 和 devm_request_irq 在设备移除时自动释放。
本章小结
PCI总线基础是设备驱动开发的必修课。本章覆盖了四个核心知识点:
- 总线历史:从ISA到PCIe的演进,理解并行到串行、共享到交换的架构变革。
- 配置空间:256字节标准配置空间是设备枚举和资源分配的基础,BAR寄存器决定了设备与系统的地址映射关系。
- 地址空间:三种地址域(内存/I/O/配置)各有用途,DMA传输必须通过IOMMU完成地址转换。
- 中断机制:MSI/MSI-X取代传统INTx成为主流,多队列设备应充分利用MSI-X实现中断负载均衡。