PCI配置空间


一、PCI配置空间
  PCI设备有三个空间——内存地址空间、IO地址空间和配置空间。由于PCI支持即插即用,所以PCI设备不是占用固定的内存地址空间或I/O地址空间,而是可以由操作系统决定其映射的基址。怎么配置呢?这就是配置空间的作用。
DW |    Byte3    |    Byte2    |    Byte1    |     Byte0     | Addr
---+---------------------------------------------------------+-----
 0 |     Device ID     |     Vendor ID      | 00
---+---------------------------------------------------------+-----
 1 |      Status     |      Command      | 04
---+---------------------------------------------------------+-----
 2 |        Class Code        | Revision ID | 08
---+---------------------------------------------------------+-----
 3 |   BIST  | Header Type | Latency Timer | Cache Line  | 0C
---+---------------------------------------------------------+-----
 4 |           Base Address 0           | 10
---+---------------------------------------------------------+-----
 5 |           Base Address 1           | 14
---+---------------------------------------------------------+-----
 6 |           Base Address 2           | 18
---+---------------------------------------------------------+-----
 7 |           Base Address 3           | 1C
---+---------------------------------------------------------+-----
 8 |           Base Address 4           | 20
---+---------------------------------------------------------+-----
 9 |           Base Address 5           | 24
---+---------------------------------------------------------+-----
10 |          CardBus CIS pointer          | 28
---+---------------------------------------------------------+-----
11 |  Subsystem Device ID  |   Subsystem Vendor ID   | 2C
---+---------------------------------------------------------+-----
12 |        Expansion ROM Base Address        | 30
---+---------------------------------------------------------+-----
13 |        Reserved(Capability List)         | 34
---+---------------------------------------------------------+-----
14 |            Reserved             | 38
---+---------------------------------------------------------+-----
15 |  Max_Lat  |  Min_Gnt  |  IRQ Pin  |  IRQ Line  | 3C
-------------------------------------------------------------------
  配置空间中最重要的有:
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个中断引脚,该寄存器表明该设备连接的是哪个引脚。
  关于配置空间的详细说明请参考《PCI Local Bus Specification》的第六章。
二、如何访问配置空间
  如何访问配置空间呢?可通过访问CF8h、CFCh端口来实现(《PCI Local Bus Specification》的3.2.2.3.2)。
CF8h: CONFIG_ADDRESS。PCI配置空间地址端口。
CFCh: CONFIG_DATA。PCI配置空间数据端口。
  CONFIG_ADDRESS寄存器格式:
 31 位:Enabled位。
23:16 位:总线编号。
15:11 位:设备编号。
10: 8 位:功能编号。
 7: 2 位:配置空间寄存器编号。
 1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。

  现在有个难题——CF8h、CFCh端口是32位端口,可像TurboC之类的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设备的。同时为了便于分析数据,将每个设备的配置空间信息保存到文件,这样可以慢慢分析。
 
/*
File:      epcip.c
Name:      访问CF8h、CFCh端口来枚举PCI设备
Author:    zyl910
Blog:      http://blog.csdn.net/zyl910/
Version:   V1.0
Updata:    2006-6-30
*/
#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;
}

  对于我的电脑的枚举结果是:
 
Bus#Device#Func#VendorDeviceClassIRQIntPin类代码的说明
000110631896000000Host bridge
0101106B1686040000PCI-to-PCI bridge(实际上是PCI/AGP桥,AGP可看成一种特殊的PCI设备)
09014F1201378000111Simple communication controllers
09114F1201378000111Simple communication controllers
09214F1201378000111Simple communication controllers
09314F1201378000111Simple communication controllers
09414F1201378000111Simple communication controllers
09514F1201378000111Simple communication controllers
09614F1201378000111Simple communication controllers
09714F1201378000111Simple communication controllers
0100110630380C0300111USB controller: Universal Host Controller Specification
0101110630380C030052USB controller: Universal Host Controller Specification
0102110630380C030053USB controller: Universal Host Controller Specification
0103110631040C0320114USB2 controller: Intel Enhanced Host Controller Interface
0110110631776010000ISA bridge
0111110657101018A2551IDE controller
0115110630594010053Audio device
01201106306520000111Ethernet controller
10010DE11030000111VGA-compatible controller

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值