2. PCI总线基础
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配置空间头部结构
| 偏移(字节) | 寄存器名称 | 位宽 | 说明 |
|---|---|---|---|
| 0x00 | Vendor ID | 16 | 厂商编号(如Intel=0x8086) |
| 0x02 | Device ID | 16 | 设备编号 |
| 0x04 | Command | 16 | 控制I/O、内存、总线主控等使能位 |
| 0x06 | Status | 16 | 中断状态、能力列表等 |
| 0x08 | Revision ID | 8 | 修订版本 |
| 0x09 | Class Code | 24 | 设备分类(基类/子类/编程接口) |
| 0x0C | Cache Line Size | 8 | 缓存行大小(单位DWORD) |
| 0x0D | Latency Timer | 8 | 总线延迟定时器 |
| 0x0E | Header Type | 8 | 0x00=Type 0(普通设备),0x01=Type 1(PCI桥) |
| 0x0F | BIST | 8 | 内建自测试 |
| 0x10-0x24 | BAR0-BAR5 | 32×6 | 基地址寄存器(共6个) |
| 0x3C | Interrupt Line | 8 | 中断线号(由BIOS/固件分配) |
| 0x3D | Interrupt Pin | 8 | 中断引脚(INTA#=1, INTB#=2, INTC#=3, INTD#=4) |
pci_attach_device()返回的句柄,配合pci_read_config32(handle, offset, &value)读取配置空间。注意Interrupt Line字段在QNX Neutrino下可能被Hypervisor或pci-server重写,驱动应优先使用pci_get_irq()获取中断号。
2.1.2 配置空间访问流程
下图展示了QNX驱动枚举PCI设备时,从配置空间读取关键信息的典型流程:
2.2 PCI BAR(基地址寄存器)
BAR是PCI设备向系统报告其所需内存空间或I/O空间大小的机制。每个BAR为32位寄存器(64位BAR占用连续两个BAR槽位),通过向BAR写入全1再回读的方式确定所需地址范围。
2.2.1 BAR类型与编码
| Bit 0 | Bit 1-2 | Bit 3-31 | 类型 | 说明 |
|---|---|---|---|---|
| 0 | 00 | 32位地址 | 32位内存BAR | 映射到32位内存空间 |
| 0 | 01 | 32位地址 | 32位内存BAR(1MB以下) | 保留,现代设备很少使用 |
| 0 | 10 | 64位地址 | 64位内存BAR | 占用连续两个BAR,支持64位地址 |
| 1 | — | 31位I/O地址 | I/O BAR | 映射到I/O空间(x86特有) |
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。
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驱动中的初始化流程:
2.3.3 中断选择策略
在QNX驱动中,中断机制的选择应遵循以下优先级:
- 优先尝试MSI-X:如果设备支持MSI-X且QNX版本支持(6.5+),优先使用MSI-X,以获得最多向量和最佳性能。
- 回退到MSI:如果MSI-X不可用,尝试MSI(最多32向量)。
- 最后使用INTx:传统INTx作为兼容性保底,但注意中断共享可能导致ISR延迟增加。
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() |