一、PCI配置空间简介

  • PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O地址空间和配置空间。配置空间是PCI所特有的一个物理空间。由于PCI支持设备即插即用,所以PCI设备不占用固定的内存地址空间或I/O地址空间,而是由操作系统决定其映射的基址。

  • 系统加电时,BIOS检测PCI总线,确定所有连接在PCI总线上的设备以及它们的配置要求,并进行系统配置。所以,所有的PCI设备必须实现配置空间,从而能够实现参数的自动配置,实现真正的即插即用。

  • PCI总线规范定义的配置空间总长度为256个字节,配置信息按一定的顺序和大小依次存放。前64个字节的配置空间称为配置头,对于所有的设备都一样,配置头的主要功能是用来识别设备、定义主机访问PCI卡的方式(I/O访问或者存储器访问,还有中断信息)。其余的192个字节称为本地配置空间(设备有关区),主要定义卡上局部总线的特性、本地空间基地址及范围等。

  • PCI设备有三个空间——内存地址空间、IO地址空间和配置空间。由于PCI支持即插即用,所以PCI设备不是占用固定的内存地址空间或I/O地址空间,而是可以由操作系统决定其映射的基址。怎么配置呢?这就是配置空间的作用。



配置空间中最重要的有:

Vendor ID:厂商ID。知名的设备厂商的ID。FFFFh是一个非法厂商ID,可它来判断PCI设备是否存在。

Device ID:设备ID。某厂商生产的设备的ID。操作系统就是凭着 Vendor ID和Device ID 找到对应驱动程序的。

Class Code:类代码。共三字节,分别是 类代码、子类代码、编程接口。类代码不仅用于区分设备类型,还是编程接口的规范,这就是为什么会有通用驱动程序。

IRQ Line:IRQ编号。PC机以前是靠两片8259芯片来管理16个硬件中断。现在为了支持对称多处理器,有了APIC(高级可编程中断控制器),它支持管理24个中断。

IRQ Pin:中断引脚。PCI有4个中断引脚,该寄存器表明该设备连接的是哪个引脚。



二、如何访问配置空间

如何访问配置空间呢?可通过访问0xCF8h、0xCFCh端口来实现。

  • 0xCF8h: CONFIG_ADDRESS。PCI配置空间地址端口。
  • 0xCFCh: CONFIG_DATA。PCI配置空间数据端口。

CONFIG_ADDRESS寄存器格式:

31 位: Enabled位。

23:16 位: 总线编号。

15:11 位: 设备编号。

10: 8 位:功能编号。

7: 2 位:配置空间寄存器编号。

1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。

现在有个难题——CF8h、CFCh端口是32位端口,可像Turbo C之类的16位C语言编译器都不支持32位端口访问。怎么办?我们可以使用_ _ emit _ _在程序中插入机器码。每次都 _ _ emit _ _一下肯定很麻烦,所以我们应该将它封装成函数。代码如下(注意66h是32位指令前缀)

/* 读32位端口 */
DWORD inpd(int portid)
{
DWORD dwRet;
asm mov dx, portid;
asm lea bx, dwRet;
__emit__
(0x66, 0x50, // push EAX
0x66, 0xED, // in EAX,DX
0x66, 0x89, 0x07, // mov [BX],EAX
0x66, 0x58); // pop EAX
return dwRet;
}
/* 写32位端口 */
void outpd(int portid, DWORD dwVal)
{
asm mov dx, portid;
asm lea bx, dwVal;
__emit__
(0x66, 0x50, // push EAX
0x66, 0x8B, 0x07, // mov EAX,[BX]
0x66, 0xEF, // out DX,EAX
0x66, 0x58); // pop EAX
return;
}


三、遍历PCI设备

怎么枚举PCI设备呢?我们可以尝试所有的 bus/dev/func 组合,然后判断得到的厂商ID是否为FFFFh。下面这个程序就是使用该方法枚举PCI设备的。同时为了便于分析数据,将每个设备的配置空间信息保存到文件,这样可以慢慢分析。


Windows下代码如下:

#include <stdio.h>
#include <conio.h> typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD; /* PCI设备索引。bus/dev/func 共16位,为了方便处理可放在一个WORD中 */
#define PDI_BUS_SHIFT 8
#define PDI_BUS_SIZE 8
#define PDI_BUS_MAX 0xFF
#define PDI_BUS_MASK 0xFF00
#define PDI_DEVICE_SHIFT 3
#define PDI_DEVICE_SIZE 5
#define PDI_DEVICE_MAX 0x1F
#define PDI_DEVICE_MASK 0x00F8
#define PDI_FUNCTION_SHIFT 0
#define PDI_FUNCTION_SIZE 3
#define PDI_FUNCTION_MAX 0x7
#define PDI_FUNCTION_MASK 0x0007
#define MK_PDI(bus,dev,func) (WORD)((bus&PDI_BUS_MAX)<<PDI_BUS_SHIFT | (dev&PDI_DEVICE_MAX)<<PDI_DEVICE_SHIFT | (func&PDI_FUNCTION_MAX) ) /* PCI配置空间寄存器 */
#define PCI_CONFIG_ADDRESS 0xCF8
#define PCI_CONFIG_DATA 0xCFC /* 填充PCI_CONFIG_ADDRESS */
#define MK_PCICFGADDR(bus,dev,func) (DWORD)(0x80000000L | (DWORD)MK_PDI(bus,dev,func) << 8) /* 读32位端口 */
DWORD inpd(int portid)
{
DWORD dwRet;
asm mov dx, portid;
asm lea bx, dwRet;
__emit__(
0x66,0x50, // push EAX
0x66,0xED, // in EAX,DX
0x66,0x89,0x07, // mov [BX],EAX
0x66,0x58); // pop EAX
return dwRet;
} /* 写32位端口 */
void outpd(int portid, DWORD dwVal)
{
asm mov dx, portid;
asm lea bx, dwVal;
__emit__(
0x66,0x50, // push EAX
0x66,0x8B,0x07, // mov EAX,[BX]
0x66,0xEF, // out DX,EAX
0x66,0x58); // pop EAX
return;
} int main(void)
{
int bus, dev, func;
int i;
DWORD dwAddr;
DWORD dwData;
FILE* hF;
char szFile[0x10];
printf("\n");
printf("Bus#\tDevice#\tFunc#\tVendor\tDevice\tClass\tIRQ\tIntPin\n");
/* 枚举PCI设备 */
for(bus = 0; bus <= PDI_BUS_MAX; ++bus)
{
for(dev = 0; dev <= PDI_DEVICE_MAX; ++dev)
{
for(func = 0; func <= PDI_FUNCTION_MAX; ++func)
{
/* 计算地址 */
dwAddr = MK_PCICFGADDR(bus, dev, func); /* 获取厂商ID */
outpd(PCI_CONFIG_ADDRESS, dwAddr);
dwData = inpd(PCI_CONFIG_DATA);
/* 判断设备是否存在。FFFFh是非法厂商ID */
if ((WORD)dwData != 0xFFFF)
{
/* bus/dev/func */
printf("%2.2X\t%2.2X\t%1X\t", bus, dev, func);
/* Vendor/Device */
printf("%4.4X\t%4.4X\t", (WORD)dwData, dwData>>16);
/* Class Code */
outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x8);
dwData = inpd(PCI_CONFIG_DATA);
printf("%6.6lX\t", dwData>>8);
/* IRQ/intPin */
outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x3C);
dwData = inpd(PCI_CONFIG_DATA);
printf("%d\t", (BYTE)dwData);
printf("%d", (BYTE)(dwData>>8));
printf("\n");
/* 写文件 */
sprintf(szFile, "PCI%2.2X%2.2X%X.bin", bus, dev, func);
hF = fopen(szFile, "wb");
if (hF != NULL)
{
/* 256字节的PCI配置空间 */
for (i = 0; i < 0x100; i += 4)
{
/* Read */
outpd(PCI_CONFIG_ADDRESS, dwAddr | i);
dwData = inpd(PCI_CONFIG_DATA);
/* Write */
fwrite(&dwData, sizeof(dwData), 1, hF);
}
fclose(hF);
}
}
}
}
}
return 0;
}

总线编号为0的都是主板上固有的芯片(主要是南桥),非主板设备的典型是——显卡。WindowsXP的设备管理器中也可以看到PCI信息。启动“设备管理器”,最好将查看方式设为“依连接查看设备(V)”。找到我的显卡,双击查看属性。切换到“详细信息”页,定位组合框为“硬件Id”。可看到其中一行为“PCI/VEN_10DE&DEV_0110&CC_030000”,表示厂商ID为“10DE”、设备ID为“0110”、类代码为“030000”,与程序得到的结果一致。



linux下代码如下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/io.h> #define PCI_MAX_BUS 255
#define PCI_MAX_DEV 31
#define PCI_MAX_FUN 7 #define PCI_BASE_ADDR 0x80000000L #define CONFIG_ADDR 0xcf8
#define CONFIG_DATA 0xcfc typedef unsigned long DWORD;
typedef unsigned int WORD; int main()
{
WORD bus, dev, fun;
DWORD addr, data; printf("\nbus#\tdev#\tfun#\tvendor#\t\tdevice#\n"); if ( iopl(3) < 0 )
{
printf("iopl set error\n");
return -1;
} for (bus = 0; bus <= PCI_MAX_BUS; bus++)
for (dev = 0; dev <= PCI_MAX_DEV; dev++)
for (fun = 0; fun <= PCI_MAX_FUN; fun++)
{
addr = PCI_BASE_ADDR | (bus << 16) | (dev << 11) | (fun << 8); outl(addr, CONFIG_ADDR);
data = inl(CONFIG_DATA); if (((data & 0xFFFF) != 0xFFFF) && (data != 0))
{
// bus, dev, fun
printf("%02d \t%02d \t%02d \t", bus, dev, fun); // vendorID、deviceID
printf("%04x \t\t%04x", (data & 0xFFFF), (data & 0xFFFF0000) >> 16); printf("\n--------------------------------------------\n");
}
} if (iopl(0) < 0 )
{
printf("iopl set error\n");
return -1;
} return 0; }

PCI配置空间简介的更多相关文章

  1. PCI、PCIE配置空间的訪问(MCFG,Bus,Device,Funtion)

    一般来说,在x86平台上,有两大类方式能够訪问这一区间的寄存器,   1,配置机制1#或者配置机制2#   訪问时借助in/out指令.请注意,这样的方式有别于一般的in/out指令訪问PCI的IO空 ...

  2. 2.3 PCI桥与PCI设备的配置空间

    PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI桥使用的配置空间和Cardbus ...

  3. [转载]PCI/PCIe基础——配置空间

    转载地址:http://blog.csdn.net/jiangwei0512/article/details/51603525 PCI/PCIe设备有自己的独立地址空间,这部分空间会映射到整个系统的地 ...

  4. 如何访问pcie整个4k的配置空间

    目前用于访问PCIe配置空间寄存器的方法需要追溯到原始的PCI规范.为了发起PCI总线配置周期,Intel实现的PCI规范使用IO空间的CF8h和CFCh来分别作为索引和数据寄存器,这种方法可以访问所 ...

  5. DOS下读取PCI配置空间信息的汇编程序(通过IOCF8/IOCFC)

    汇编程序编写的读取PCI配置空间信息的代码(通过IOCF8/IOCFC): ;------------------------------------------------ ;功能: 读取PCI 配 ...

  6. 怎样訪问pcie整个4k的配置空间

    眼下用于訪问PCIe配置空间寄存器的方法须要追溯到原始的PCI规范. 为了发起PCI总线配置周期,Intel实现的PCI规范使用IO空间的CF8h和CFCh来分别作为索引和数据寄存器,这样的方法能够訪 ...

  7. 【PCIE-2】---PCIE配置空间及访问方式简介

    对新手来说,第一步了解PCIE的相关基本概念,第二步了解PCIE配置空间,第三步深入研究PCIE设备枚举方式.本章主要总结第二步的PCIE配置空间 按照国际惯例,先提问题: 1. 什么是PCIE的配置 ...

  8. PCIe设备的配置空间

    关于PCI设备的配置空间网上已经有很多资料了,如下图就是PCI设备必须支持的64个字节的配置空间,范围为0x00-0x3f. 很多PCI设备仅仅支持者64字节的配置空间.PCI和PCIe配置空间的区别 ...

  9. Linux 内核存取配置空间

    在驱动已探测到设备后, 它常常需要读或写 3 个地址空间: 内存, 端口, 和配置. 特别 地, 存取配置空间对驱动是至关重要的, 因为这是唯一的找到设备被映射到内存和 I/O 空间的位置的方法. 因 ...

随机推荐

  1. ORBSlam with ROS

    ...相机标定 calibration 基本就是做CV 的常识 ORBSlam源码:

  2. (最小生成树)Truck History --POJ -- 1789

    链接: http://poj.org/problem?id=1789 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 2213 ...

  3. Java内存模型解惑--观深入理解Java内存模型系列文章有感(二)

    1.volatile关键字修饰的域的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用 ...

  4. 创建TFS备份计划失败,错误提示:TF400997

    问题描述 在一个TFS 2018 + SQL Server 2017的环境中,从TFS控制台中配置备份计划时,系统提示错误TF400997,需要授予数据库服务账户sqlservice@domain.c ...

  5. c++ 日志输出库 spdlog 简介(4)- 多线程txt输出日志

    在上一节的代码中加入了向文本文件中写入日志的代码: UINT CMFCApplication1Dlg::Thread1(LPVOID pParam) { try{ size_t q_size = ; ...

  6. JWT+ASP.NET MVC 时间戳防止重放攻击

     时间戳作用 客户端在向服务端接口进行请求,如果请求信息进行了加密处理,被第三方截取到请求包,可以使用该请求包进行重复请求操作.如果服务端不进行防重放攻击,就会服务器压力增大,而使用时间戳的方式可以解 ...

  7. .Net Core 跨平台应用使用串口、串口通信 ,可能出现的问题、更简洁的实现方法

    前些天在学习在 .NET Core下,跨平台使用串口通讯,有一篇文章说到在Linux/物联网下,实现通讯. 主要问题出现在以下两个类库 SerialPortStream flyfire.CustomS ...

  8. NetCore入门篇:(四)Net Core项目启动文件Startup

    一.Startup介绍 1.Startup文件是Net Core应用程的启动程序,实现全局配置. 2.Net Core默认情况下,静态文件及Session都未启动,需要在Startup文件配置启动,否 ...

  9. sqlServer数据库常用连接字符串

    sqlServer   数据库常用连接字符串 用户名和密码验证的方式去连接到数据库服务器 <add name="conStr" connectionString=" ...

  10. RxJS入门之函数响应式编程

    一.函数式编程 1.声明式(Declarativ) 和声明式相对应的编程⽅式叫做命令式编程(ImperativeProgramming),命令式编程也是最常见的⼀种编程⽅式. //命令式编程: fun ...