3. QNX PCI子系统架构

QNX的PCI子系统采用经典的客户端-服务器架构,将硬件枚举、资源分配与中断路由集中管理,为驱动开发者提供统一的访问接口。理解这一架构是编写正确PCI驱动的前提。

3.1 架构总览:三层分离模型

整个PCI子系统分为三个逻辑层,每一层职责清晰,通过标准IPC机制通信:

核心设计思想: 驱动开发者无需直接操作PCI配置空间,所有对PCI配置空间的读写请求都通过消息传递委托给 pci-server 处理,从而保证系统级的资源冲突检测与原子性。
QNX PCI 子系统三层架构 用户层 (User Space) PCI 驱动进程 (io-pci-xxx / devp-xxx) 调用 libpci API 发送 PCI 配置空间请求 服务层 (System Service) pci-server 进程 (io-pci) 管理 PCI 配置空间、BAR 分配、中断路由、总线枚举 内核层 (Kernel Space) PCI Host Bridge 驱动 + 硬件寄存器访问 pci-server 通过 /dev/pci 或 syspage 与内核交互 IPC 通信:驱动进程 → pci-server → 内核 → 硬件

3.2 核心组件详解

3.2.1 pci-server (io-pci)

pci-server 是PCI子系统的唯一仲裁者,在系统启动时由进程管理器自动启动。它负责以下关键任务:

  • 总线枚举: 扫描PCI总线树,发现所有设备与桥接器。
  • 资源分配: 为每个设备分配内存基址(BAR)、I/O端口、中断向量。
  • 配置空间代理: 接收客户端请求,读写PCI配置空间寄存器。
  • 中断路由: 管理MSI/MSI-X与传统INTx中断的映射。
  • 热插拔事件: 监控PCIe热插拔状态变化并通知注册的驱动。
启动确认: 在目标系统上执行 pidin | grep pci 可确认 pci-server 是否运行。若未运行,驱动将无法通过 libpci 获取设备信息。

3.2.2 pci-tool (命令行工具)

pci-tool 是调试PCI子系统的首选工具,直接与 pci-server 通信,无需编写代码即可查看设备拓扑与配置空间。

命令 功能 典型输出
pci -v 列出所有PCI设备及详细信息 Bus/Dev/Func、Vendor ID、Device ID、Class Code
pci -b 0 -d 0 -f 0 查看指定BDF设备的配置空间 256字节配置空间十六进制转储
pci -r 显示BAR地址与大小 BAR0=0xFE000000 (size=1MB)
pci -i 显示中断路由信息 INTx#、MSI向量、IRQ编号

3.2.3 libpci 接口

libpci 是驱动开发者直接使用的客户端库,封装了与 pci-server 的IPC通信细节。核心API如下:

#include <hw/pci.h>

/* 1. 建立与 pci-server 的连接 */
pci_handle_t pci_attach(int flags);

/* 2. 枚举设备:遍历PCI总线树 */
int pci_find_device(pci_handle_t handle, uint16_t vid, uint16_t did, int index, struct pci_dev_info *info);

/* 3. 读取配置空间寄存器 */
int pci_read_config32(pci_handle_t handle, struct pci_dev_info *info, int offset, uint32_t *value);

/* 4. 写入配置空间寄存器 */
int pci_write_config32(pci_handle_t handle, struct pci_dev_info *info, int offset, uint32_t value);

/* 5. 获取BAR物理地址 */
int pci_get_bar_addr(pci_handle_t handle, struct pci_dev_info *info, int barnum, paddr_t *addr, size_t *size);

/* 6. 中断相关:绑定中断处理 */
int pci_attach_interrupt(pci_handle_t handle, struct pci_dev_info *info, int vector, struct sigevent *event, int flags);

/* 7. 断开连接 */
void pci_detach(pci_handle_t handle);
注意: 所有 libpci 调用都是阻塞的,因为底层通过 MsgSend() 向 pci-server 发送同步消息。在中断服务例程(ISR)中禁止调用 libpci 函数,否则会导致死锁。

3.3 PCI设备枚举流程

设备枚举是驱动初始化的第一步,典型流程如下:

  1. 调用 pci_attach():建立与 pci-server 的IPC通道,返回句柄。
  2. 循环调用 pci_find_device():按 Vendor ID + Device ID 匹配目标设备,每次调用返回一个匹配项。
  3. 读取配置空间:通过 pci_read_config32() 获取 Class Code、Revision ID、Subsystem ID 等。
  4. 获取BAR资源:调用 pci_get_bar_addr() 获取每个BAR的物理地址与大小。
  5. 注册中断:调用 pci_attach_interrupt() 绑定中断事件。
  6. 调用 pci_detach():驱动卸载时释放连接。
PCI 设备枚举流程 (驱动视角) 1. pci_attach() 2. pci_find_device() 循环 3. 读取配置空间 (Class/Rev/SSID) 4. pci_get_bar_addr() 获取BAR 5. pci_attach_interrupt() 6. pci_detach() (卸载时) 注意:步骤2-5在驱动初始化函数中顺序执行,步骤6在驱动卸载函数中执行

3.4 关键数据结构:pci_dev_info

pci_find_device() 填充的 struct pci_dev_info 是驱动获取设备信息的核心结构体:

struct pci_dev_info {
    uint16_t    VendorId;       /* 厂商ID */
    uint16_t    DeviceId;       /* 设备ID */
    uint16_t    SubVendorId;    /* 子系统厂商ID */
    uint16_t    SubDeviceId;    /* 子系统设备ID */
    uint8_t     RevisionId;     /* 修订版本 */
    uint8_t     ClassCode;      /* 基类代码 (如 0x02 表示网络控制器) */
    uint8_t     SubClass;       /* 子类代码 */
    uint8_t     ProgIf;         /* 编程接口 */
    uint8_t     Bus;            /* 总线号 */
    uint8_t     DevFunc;        /* 设备号(高5位) + 功能号(低3位) */
    uint16_t    Command;        /* 命令寄存器当前值 */
    uint16_t    Status;         /* 状态寄存器当前值 */
    paddr_t     BaseAddress[6]; /* BAR0-BAR5 物理地址 */
    size_t      BaseAddressSize[6]; /* 每个BAR的大小 */
    uint8_t     Irq;            /* 中断线 (INTx) */
    uint8_t     MinGnt;         /* 突发传输周期 */
    uint8_t     MaxLat;         /* 最大延迟 */
};

3.5 典型驱动初始化代码片段

以下代码展示了如何在驱动中完成PCI设备枚举与资源获取:

#include <hw/pci.h>
#include <stdio.h>

int pci_driver_init(pci_handle_t *handle, struct pci_dev_info *info) {
    int ret;
    int index = 0;

    /* 1. 连接 pci-server */
    *handle = pci_attach(0);
    if (*handle == NULL) {
        fprintf(stderr, "pci_attach failed\n");
        return -1;
    }

    /* 2. 枚举设备:匹配 Vendor=0x10EE, Device=0x9034 */
    while ((ret = pci_find_device(*handle, 0x10EE, 0x9034, index, info)) == PCI_SUCCESS) {
        printf("Found device at BDF %02x:%02x.%x\n",
               info->Bus, info->DevFunc >> 3, info->DevFunc & 0x07);

        /* 3. 打印BAR0信息 */
        printf("  BAR0 addr=0x%llx size=%zu\n",
               info->BaseAddress[0], info->BaseAddressSize[0]);

        /* 4. 检查中断线 */
        if (info->Irq != 0) {
            printf("  INTx IRQ=%d\n", info->Irq);
        }
        index++;
    }

    if (index == 0) {
        fprintf(stderr, "No matching device found\n");
        pci_detach(*handle);
        return -1;
    }

    return 0;
}
调试技巧: 在调用 pci_find_device() 之前,先用 pci -v | grep 10EE 确认目标设备是否已被 pci-server 正确枚举。如果设备未出现,检查硬件连接或 PCIe 链路状态。

3.6 架构设计要点总结

组件 角色 驱动开发者关注点
pci-server 系统服务,管理全局PCI资源 确保其运行;不直接交互
pci-tool 调试工具,查询设备信息 开发阶段频繁使用
libpci 客户端API库 驱动初始化/卸载时调用
pci_dev_info 设备信息结构体 获取BAR、IRQ、BDF等关键参数