PCI设备操作--枚举

来源:互联网 发布:淘宝店铺信誉怎么看 编辑:程序博客网 时间:2024/04/28 08:26

一、配置空间介绍:

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个中断引脚,该寄存器表明该设备连接的是哪个引脚。

 二、访问配置空间

可通过访问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 
#include

typedef unsigned charBYTE;
typedef unsigned intWORD;
typedef unsigned longDWORD;

/* 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_DEVICE_SHIFT | (func&PDI_FUNCTION_MAX) )< pre>            

/* 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位端口 */
DWORDinpd(intportid)
{
DWORDdwRet;
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位端口 */
voidoutpd(intportid, DWORDdwVal)
{
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;
}
            

intmain(void)
{
intbus, dev, func;
inti;
DWORDdwAddr;
DWORDdwData;
FILE*hF;
charszFile[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);
}
}
}
}
}
            
return0;
}
            


  对于我的电脑的枚举结果是:

Bus# Device# Func# Vendor Device Class IRQ IntPin 类代码的说明 0 0 0 1106 3189 60000 0 0 Host bridge 0 1 0 1106 B168 60400 0 0 PCI-to-PCI bridge(实际上是PCI/AGP桥,AGP可看成一种特殊的PCI设备) 0 9 0 14F1 2013 78000 11 1 Simple communication controllers 0 9 1 14F1 2013 78000 11 1 Simple communication controllers 0 9 2 14F1 2013 78000 11 1 Simple communication controllers 0 9 3 14F1 2013 78000 11 1 Simple communication controllers 0 9 4 14F1 2013 78000 11 1 Simple communication controllers 0 9 5 14F1 2013 78000 11 1 Simple communication controllers 0 9 6 14F1 2013 78000 11 1 Simple communication controllers 0 9 7 14F1 2013 78000 11 1 Simple communication controllers 0 10 0 1106 3038 0C0300 11 1 USB controller: Universal Host Controller Specification 0 10 1 1106 3038 0C0300 5 2 USB controller: Universal Host Controller Specification 0 10 2 1106 3038 0C0300 5 3 USB controller: Universal Host Controller Specification 0 10 3 1106 3104 0C0320 11 4 USB2 controller: Intel Enhanced Host Controller Interface 0 11 0 1106 3177 60100 0 0 ISA bridge 0 11 1 1106 571 01018A 255 1 IDE controller 0 11 5 1106 3059 40100 5 3 Audio device 0 12 0 1106 3065 20000 11 1 Ethernet controller 1 0 0 10DE 110 30000 11 1 VGA-compatible controller
  总线编号为0的都是主板上固有的芯片(主要是南桥),非主板设备的典型是——显卡。
  WindowsXP的设备管理器中也可以看到PCI信息。启动“设备管理器”,最好将查看方式设为“依连接查看设备(V)”。找到我的显卡,双击查看属性。切换到“详细信息”页,定位组合框为“硬件 Id”。可看到其中一行为“PCI/VEN_10DE&DEV_0110&CC_030000”,表示厂商ID为“10DE”、设备ID为“0110”、类代码为“030000”,与程序得到的结果一致。

原创粉丝点击