4. VxWorks PCI子系统架构

VxWorks 的 PCI 子系统为嵌入式开发者提供了一套标准化的软件抽象层,用于访问 PCI/PCIe 设备的配置空间、内存空间和 I/O 空间。本章深入剖析 pciConfigLib 库的核心接口,包括设备扫描函数 pciFindDevice() 以及配置读写函数 pciReadConfig() / pciWriteConfig(),帮助您建立从硬件寄存器到软件 API 的完整映射。

核心要点:VxWorks PCI 子系统通过 BSP 初始化阶段建立的 PCI 主机桥窗口,将设备 BAR 空间映射到 CPU 物理地址空间。开发者只需调用标准 API 即可完成设备枚举、配置空间访问和资源分配,无需直接操作底层总线事务。

4.1 PCI 子系统软件分层

VxWorks PCI 软件栈分为三层,每一层都承担特定的职责,并通过清晰的接口向上层提供服务。

层次组件/文件职责描述
应用层用户驱动 / 应用程序调用 pciFindDevice() 枚举设备,通过 pciReadConfig() / pciWriteConfig() 读写配置空间,调用 sysBusToLocalAdrs() 转换 BAR 地址。
API 层pciConfigLib提供与架构无关的 PCI 配置访问接口,管理配置空间读写锁,维护设备链表。
硬件抽象层BSP / sysPci.c实现平台相关的 PCI 配置空间访问机制(Type 0 / Type 1 配置周期),初始化 PCI 主机桥窗口,定义 PCI_MEM_BASE、PCI_IO_BASE 等宏。
VxWorks PCI 子系统软件分层架构 应用层:用户驱动 / 应用程序 pciFindDevice() | pciReadConfig() / pciWriteConfig() | sysBusToLocalAdrs() API 层:pciConfigLib 配置空间读写锁 | 设备链表管理 | 与架构无关的配置访问接口 硬件抽象层:BSP / sysPci.c Type 0 / Type 1 配置周期 | PCI 主机桥窗口初始化 | PCI_MEM_BASE / PCI_IO_BASE

4.2 pciFindDevice():设备枚举与定位

pciFindDevice() 是 VxWorks 中最常用的 PCI 设备查找函数。它通过遍历 PCI 总线树,匹配 Vendor ID 和 Device ID,返回目标设备的总线号、设备号和功能号。

函数原型

STATUS pciFindDevice
    (
    UINT16  vendorId,       /* 厂商 ID */
    UINT16  deviceId,       /* 设备 ID */
    UINT16  index,          /* 匹配索引(从 0 开始) */
    UINT32  *pBusNo,        /* 返回:总线号 */
    UINT32  *pDeviceNo,     /* 返回:设备号 */
    UINT32  *pFuncNo        /* 返回:功能号 */
    )

参数说明

参数说明
vendorIdPCI 厂商 ID,例如 Intel 为 0x8086,Xilinx 为 0x10EE。
deviceIdPCI 设备 ID,由厂商定义,例如某款网卡的 Device ID 为 0x153A。
index当系统中存在多个相同 Vendor ID + Device ID 的设备时,指定返回第几个匹配项(从 0 开始)。
pBusNo输出参数,指向存储总线号的变量。
pDeviceNo输出参数,指向存储设备号的变量。
pFuncNo输出参数,指向存储功能号的变量。

典型用法示例

UINT32  busNo, devNo, funcNo;
STATUS  status;

/* 查找系统中第一个 Vendor ID=0x10EE, Device ID=0x7021 的 PCIe 设备 */
status = pciFindDevice(0x10EE, 0x7021, 0, &busNo, &devNo, &funcNo);

if (status == OK)
    {
    printf("设备位于 BDF: %02x:%02x.%x\n", busNo, devNo, funcNo);
    }
else
    {
    printf("未找到匹配设备\n");
    }
实战提示:在调用 pciFindDevice() 之前,确保 BSP 已经完成了 PCI 总线枚举(通常在 sysHwInit() 中调用 pciConfigInit())。如果返回 ERROR,请检查 Vendor ID / Device ID 是否正确,或者设备是否被其他驱动占用。
pciFindDevice() 设备查找流程 调用 pciFindDevice() 遍历 PCI 总线树(Bus 0 → 下级总线) Vendor ID & Device ID 匹配? 返回 OK + BDF 继续遍历下一设备 返回 ERROR

4.3 pciReadConfig() / pciWriteConfig():配置空间访问

一旦通过 pciFindDevice() 获取了设备的 BDF(Bus:Device.Function),就可以使用 pciReadConfig() 和 pciWriteConfig() 直接访问该设备的 PCI 配置空间寄存器。

函数原型

STATUS pciReadConfig
    (
    UINT32  busNo,          /* 总线号 */
    UINT32  deviceNo,       /* 设备号 */
    UINT32  funcNo,         /* 功能号 */
    UINT32  offset,         /* 配置空间偏移地址(0x00 ~ 0xFF) */
    UINT32  *pData          /* 输出:读取到的 32 位数据 */
    );

STATUS pciWriteConfig
    (
    UINT32  busNo,          /* 总线号 */
    UINT32  deviceNo,       /* 设备号 */
    UINT32  funcNo,         /* 功能号 */
    UINT32  offset,         /* 配置空间偏移地址(0x00 ~ 0xFF) */
    UINT32  data            /* 待写入的 32 位数据 */
    );

常用配置空间偏移地址

偏移 (Hex)寄存器名称说明
0x00Vendor ID / Device ID只读,标识设备厂商和型号
0x04Command / Status控制总线命令(I/O、内存、总线主控使能)
0x08Revision ID / Class Code设备分类和修订版本
0x10 ~ 0x24BAR0 ~ BAR5基地址寄存器,定义设备地址空间
0x34Capabilities Pointer指向 Capabilities 链表的指针
0x3CInterrupt Line / Interrupt Pin中断路由信息

实战:读取并打印 BAR0 地址

UINT32  bar0Value;
STATUS  status;

/* 读取 BDF=0:0:0 设备的 BAR0(偏移 0x10) */
status = pciReadConfig(0, 0, 0, 0x10, &bar0Value);

if (status == OK)
    {
    printf("BAR0 = 0x%08X\n", bar0Value);

    /* 判断 BAR 类型:bit0 == 0 表示 Memory Space,bit0 == 1 表示 I/O Space */
    if (bar0Value & 0x01)
        printf("  → I/O Space\n");
    else
        printf("  → Memory Space\n");
    }
else
    {
    printf("配置空间读取失败\n");
    }
注意事项:
  • pciReadConfig() / pciWriteConfig() 只能访问标准配置空间(前 256 字节)。对于 PCIe 扩展配置空间(256 字节 ~ 4095 字节),需要使用 pciExtendedReadConfig() / pciExtendedWriteConfig()。
  • 写入配置空间时,某些寄存器是只读的(如 Vendor ID、Device ID),写入操作会被硬件忽略。
  • 在修改 Command 寄存器(偏移 0x04)使能 Bus Master 或 Memory Space 之前,确保 BAR 已经正确配置。
PCI 配置空间布局与 BAR 映射 PCI 配置空间(前 64 字节) 0x00: Vendor ID / Device ID 0x04: Command / Status 0x08: Revision ID / Class Code 0x10: BAR0(基地址寄存器 0) 0x14: BAR1(基地址寄存器 1) 0x18: BAR2(基地址寄存器 2) 0x34: Capabilities Pointer 0x3C: Interrupt Line / Interrupt Pin BAR 映射到 CPU 物理地址空间 通过 sysBusToLocalAdrs() 转换 pciReadConfig(0x10) 读取 BAR0 值 pciWriteConfig(0x10, addr) 写入 BAR0

4.4 综合实战:完整设备初始化流程

以下代码演示了从设备查找到配置空间读写,再到 BAR 地址转换的完整流程。这是 VxWorks PCIe 驱动开发的标准模板。

#include <vxWorks.h>
#include <pci.h>
#include <sysLib.h>

STATUS pcieDeviceInit
    (
    UINT16 vendorId,
    UINT16 deviceId
    )
    {
    UINT32  busNo, devNo, funcNo;
    UINT32  bar0Value, bar0Addr;
    UINT32  cmdReg;
    STATUS  status;

    /* 步骤 1:查找设备 */
    status = pciFindDevice(vendorId, deviceId, 0, &busNo, &devNo, &funcNo);
    if (status != OK)
        {
        printf("设备 0x%04X:0x%04X 未找到\n", vendorId, deviceId);
        return ERROR;
        }

    /* 步骤 2:读取 BAR0 */
    status = pciReadConfig(busNo, devNo, funcNo, 0x10, &bar0Value);
    if (status != OK)
        {
        printf("BAR0 读取失败\n");
        return ERROR;
        }

    /* 步骤 3:将 BAR0 中的总线地址转换为 CPU 本地地址 */
    bar0Addr = sysBusToLocalAdrs(bar0Value & 0xFFFFFFF0);

    printf("设备 BDF: %02x:%02x.%x\n", busNo, devNo, funcNo);
    printf("BAR0 原始值: 0x%08X\n", bar0Value);
    printf("BAR0 本地地址: 0x%08X\n", bar0Addr);

    /* 步骤 4:使能 Memory Space 和 Bus Master */
    status = pciReadConfig(busNo, devNo, funcNo, 0x04, &cmdReg);
    if (status != OK)
        {
        printf("Command 寄存器读取失败\n");
        return ERROR;
        }

    cmdReg |= 0x06;   /* 设置 bit1 (Memory Space) 和 bit2 (Bus Master) */
    status = pciWriteConfig(busNo, devNo, funcNo, 0x04, cmdReg);
    if (status != OK)
        {
        printf("Command 寄存器写入失败\n");
        return ERROR;
        }

    printf("设备初始化完成\n");
    return OK;
    }
关键步骤总结:
  1. 查找设备:使用 pciFindDevice() 获取 BDF。
  2. 读取 BAR:使用 pciReadConfig() 读取 BAR 寄存器,获取总线地址。
  3. 地址转换:调用 sysBusToLocalAdrs() 将总线地址转换为 CPU 可访问的本地地址。
  4. 使能命令:设置 Command 寄存器的 Memory Space 和 Bus Master 位,使设备能够响应内存访问和发起 DMA。

4.5 常见错误与调试技巧

错误现象可能原因排查方法
pciFindDevice() 返回 ERRORVendor ID / Device ID 错误;设备未上电;PCI 枚举未完成使用 pciShow() 命令打印所有 PCI 设备列表
pciReadConfig() 返回 ERRORBDF 参数无效;配置空间偏移超出 0xFF检查 BDF 范围;确认使用 pciExtendedReadConfig() 访问扩展空间
BAR 地址为 0xFFFFFFFF设备未响应;BAR 未初始化检查设备是否处于复位状态;确认电源和时钟正常
访问 BAR 地址时触发异常地址转换错误;MMU 未映射;BAR 空间未使能确认 sysBusToLocalAdrs() 返回值;检查 Command 寄存器
调试利器:在 VxWorks shell 中直接输入 pciShow() 可以打印当前系统中所有 PCI 设备的完整配置空间信息,包括 Vendor ID、Device ID、BAR 地址、Class Code 等。这是验证设备枚举是否正确的第一道防线。