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 等宏。 |
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 /* 返回:功能号 */
)
参数说明
| 参数 | 说明 |
|---|---|
vendorId | PCI 厂商 ID,例如 Intel 为 0x8086,Xilinx 为 0x10EE。 |
deviceId | PCI 设备 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 是否正确,或者设备是否被其他驱动占用。
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) | 寄存器名称 | 说明 |
|---|---|---|
| 0x00 | Vendor ID / Device ID | 只读,标识设备厂商和型号 |
| 0x04 | Command / Status | 控制总线命令(I/O、内存、总线主控使能) |
| 0x08 | Revision ID / Class Code | 设备分类和修订版本 |
| 0x10 ~ 0x24 | BAR0 ~ BAR5 | 基地址寄存器,定义设备地址空间 |
| 0x34 | Capabilities Pointer | 指向 Capabilities 链表的指针 |
| 0x3C | Interrupt 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 已经正确配置。
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;
}
关键步骤总结:
- 查找设备:使用
pciFindDevice()获取 BDF。 - 读取 BAR:使用
pciReadConfig()读取 BAR 寄存器,获取总线地址。 - 地址转换:调用
sysBusToLocalAdrs()将总线地址转换为 CPU 可访问的本地地址。 - 使能命令:设置 Command 寄存器的 Memory Space 和 Bus Master 位,使设备能够响应内存访问和发起 DMA。
4.5 常见错误与调试技巧
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
pciFindDevice() 返回 ERROR | Vendor ID / Device ID 错误;设备未上电;PCI 枚举未完成 | 使用 pciShow() 命令打印所有 PCI 设备列表 |
pciReadConfig() 返回 ERROR | BDF 参数无效;配置空间偏移超出 0xFF | 检查 BDF 范围;确认使用 pciExtendedReadConfig() 访问扩展空间 |
| BAR 地址为 0xFFFFFFFF | 设备未响应;BAR 未初始化 | 检查设备是否处于复位状态;确认电源和时钟正常 |
| 访问 BAR 地址时触发异常 | 地址转换错误;MMU 未映射;BAR 空间未使能 | 确认 sysBusToLocalAdrs() 返回值;检查 Command 寄存器 |
调试利器:在 VxWorks shell 中直接输入
pciShow() 可以打印当前系统中所有 PCI 设备的完整配置空间信息,包括 Vendor ID、Device ID、BAR 地址、Class Code 等。这是验证设备枚举是否正确的第一道防线。