2. PCI总线基础:配置空间、BAR与中断路由

PCI(Peripheral Component Interconnect)总线是嵌入式系统中最核心的外设互联标准。在QNX环境下开发PCI驱动,必须透彻理解三个基础概念:配置空间(用于枚举和初始化设备)、基地址寄存器(BAR)(用于映射设备内存/I/O)、中断路由机制(INTx/MSI/MSI-X)。本章将逐一拆解,并给出QNX下的操作要点。

学习目标:
  • 掌握PCI配置空间的布局与访问方法(Type 0/Type 1)。
  • 理解BAR的编码规则与地址映射流程。
  • 区分INTx、MSI、MSI-X三种中断机制,并明确QNX驱动中的选择策略。

2.1 PCI配置空间

每个PCI设备(或功能)都拥有一个独立的256字节配置空间(Type 0),前64字节为预定义头部,后192字节为设备特定区域。配置空间由Host Bridge通过配置读写总线周期访问,QNX下通过pci_attach_device()和pci_read_config32()等API读取。

2.1.1 Type 0配置空间头部结构

偏移(字节)寄存器名称位宽说明
0x00Vendor ID16厂商编号(如Intel=0x8086)
0x02Device ID16设备编号
0x04Command16控制I/O、内存、总线主控等使能位
0x06Status16中断状态、能力列表等
0x08Revision ID8修订版本
0x09Class Code24设备分类(基类/子类/编程接口)
0x0CCache Line Size8缓存行大小(单位DWORD)
0x0DLatency Timer8总线延迟定时器
0x0EHeader Type80x00=Type 0(普通设备),0x01=Type 1(PCI桥)
0x0FBIST8内建自测试
0x10-0x24BAR0-BAR532×6基地址寄存器(共6个)
0x3CInterrupt Line8中断线号(由BIOS/固件分配)
0x3DInterrupt Pin8中断引脚(INTA#=1, INTB#=2, INTC#=3, INTD#=4)
QNX实践提示: 在QNX驱动中,通常使用pci_attach_device()返回的句柄,配合pci_read_config32(handle, offset, &value)读取配置空间。注意Interrupt Line字段在QNX Neutrino下可能被Hypervisor或pci-server重写,驱动应优先使用pci_get_irq()获取中断号。

2.1.2 配置空间访问流程

下图展示了QNX驱动枚举PCI设备时,从配置空间读取关键信息的典型流程:

PCI配置空间读取流程 PCI配置空间读取流程(QNX驱动视角) pci_attach_device() pci_read_config32(VID/DID) 匹配驱动ID表 pci_read_config32(BAR0~5) pci_get_irq() / INTx/MSI 说明:每个步骤对应一个QNX PCI API调用,驱动需按此顺序完成设备初始化

2.2 PCI BAR(基地址寄存器)

BAR是PCI设备向系统报告其所需内存空间或I/O空间大小的机制。每个BAR为32位寄存器(64位BAR占用连续两个BAR槽位),通过向BAR写入全1再回读的方式确定所需地址范围。

2.2.1 BAR类型与编码

Bit 0Bit 1-2Bit 3-31类型说明
00032位地址32位内存BAR映射到32位内存空间
00132位地址32位内存BAR(1MB以下)保留,现代设备很少使用
01064位地址64位内存BAR占用连续两个BAR,支持64位地址
1—31位I/O地址I/O BAR映射到I/O空间(x86特有)
QNX开发注意: 在ARM/ARM64平台上,PCI I/O空间通常通过内存映射I/O(MMIO)模拟,驱动应优先使用内存BAR。QNX的pci_map_bars()函数会自动处理BAR的物理地址到虚拟地址的映射,返回pci_bar_info_t结构体,包含address、size、type字段。

2.2.2 BAR地址映射流程

QNX驱动中,BAR映射的典型代码片段如下:

#include <hw/pci.h>

pci_handle_t handle;
pci_bar_info_t bar_info;
uintptr_t bar_vaddr;

// 1. 连接设备
if (pci_attach_device(&handle, 0, PCI_DEVINFO(0, 0, 0)) != PCI_SUCCESS) {
    return -1;
}

// 2. 读取并映射BAR0
if (pci_map_bars(handle, 0, &bar_info, 1) != PCI_SUCCESS) {
    return -1;
}

// 3. 获取虚拟地址
bar_vaddr = bar_info.address;

// 4. 通过bar_vaddr访问设备寄存器
*(volatile uint32_t *)(bar_vaddr + REG_OFFSET) = value;

2.3 PCI中断路由:INTx / MSI / MSI-X

PCI设备通过中断通知CPU数据就绪或事件发生。QNX驱动必须根据设备能力和平台支持选择合适的中断机制。三种机制的对比如下:

特性INTx(传统中断)MSI(消息信号中断)MSI-X(增强型MSI)
信号方式专用中断引脚(INTA#~INTD#)通过内存写事务发送中断通过内存写事务发送中断
中断向量数每功能最多1个(共享)最多32个(2^n,n≤5)最多2048个(独立)
中断共享支持(线或)不支持(每个向量独立)不支持(每个向量独立)
配置空间支持必须(Interrupt Pin/Line)可选(Capability ID=0x05)可选(Capability ID=0x11)
QNX支持完全支持支持(需pci-server支持)支持(QNX 6.5+)
适用场景低吞吐、兼容性优先中高吞吐、减少中断延迟高吞吐、多队列、虚拟化

2.3.1 INTx中断路由

INTx使用物理引脚(INTA#~INTD#)连接到中断控制器。在x86平台上,通过中断路由表将PCI引脚映射到IRQ号。QNX驱动通过pci_get_irq()获取中断号,然后使用InterruptAttach()或InterruptAttachEvent()注册ISR。

INTx注册示例(QNX):
int irq = pci_get_irq(handle);
if (irq < 0) {
    // 回退到Interrupt Line字段
    pci_read_config32(handle, 0x3C, &irq);
}
InterruptAttach(irq, isr_handler, NULL, 0, 0);

2.3.2 MSI/MSI-X中断

MSI通过向特定地址写入数据来触发中断,避免了INTx的引脚共享和路由延迟。MSI-X在此基础上支持更多独立向量,每个向量可绑定不同的CPU核心,是实现中断负载均衡的关键。

下图展示了MSI-X在QNX驱动中的初始化流程:

MSI-X初始化流程 QNX MSI-X中断初始化流程 检查MSI-X Capability 读取MSI-X Table/BIR pci_msix_alloc_vectors() InterruptAttach(向量, ISR) 说明:MSI-X表通常映射在BAR空间中,QNX的pci_msix_alloc_vectors()自动处理地址映射

2.3.3 中断选择策略

在QNX驱动中,中断机制的选择应遵循以下优先级:

  1. 优先尝试MSI-X:如果设备支持MSI-X且QNX版本支持(6.5+),优先使用MSI-X,以获得最多向量和最佳性能。
  2. 回退到MSI:如果MSI-X不可用,尝试MSI(最多32向量)。
  3. 最后使用INTx:传统INTx作为兼容性保底,但注意中断共享可能导致ISR延迟增加。
关键避坑: 在QNX虚拟化环境中(如QNX Hypervisor),MSI/MSI-X可能被Guest OS直接捕获,导致中断丢失。此时应使用INTx+中断重映射或通过pci-server代理中断。务必在目标平台上进行完整的中断压力测试。

2.4 本章小结

知识点核心要点QNX API/操作
配置空间256字节,Type 0/Type 1,包含VID/DID/Class Code/BAR/中断信息pci_attach_device(), pci_read_config32()
BAR内存BAR/I/O BAR,通过写全1回读确定大小,64位BAR占用两个槽位pci_map_bars(), pci_bar_info_t
INTx引脚中断,共享,通过Interrupt Line/Pin配置pci_get_irq(), InterruptAttach()
MSI内存写中断,最多32向量,不支持共享pci_msi_alloc_vectors()
MSI-X增强型MSI,最多2048向量,支持每个向量独立配置pci_msix_alloc_vectors()