PCI 配置空间简介

来源:互联网 发布:深信服网络行为管理 编辑:程序博客网 时间:2024/05/29 19:38

一、PCI配置空间简介

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

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

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

PPCI设备有三个空间——内存地址空间、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端口来实现。    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位指令前缀):

[cpp] view plain copy
  1. /* 读32位端口 */  
  2. DWORD inpd(int portid)  
  3. {  
  4.     DWORD dwRet;  
  5.     asm mov dx, portid;  
  6.     asm lea bx, dwRet;  
  7.     __emit__  
  8.     (0x66, 0x50,       // push EAX  
  9.     0x66, 0xED,        // in EAX,DX  
  10.     0x66, 0x89, 0x07,  // mov [BX],EAX  
  11.     0x66, 0x58);       // pop EAX  
  12.     return dwRet;  
  13. }  
  14. /* 写32位端口 */  
  15. void outpd(int portid, DWORD dwVal)  
  16. {  
  17.     asm mov dx, portid;  
  18.     asm lea bx, dwVal;  
  19.     __emit__  
  20.     (0x66, 0x50,       // push EAX  
  21.     0x66, 0x8B, 0x07,  // mov EAX,[BX]  
  22.     0x66, 0xEF,        // out DX,EAX  
  23.     0x66, 0x58);       // pop EAX  
  24.     return;  
  25. }  

三、枚举PCI设备

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

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <conio.h>  
  3.   
  4. typedef unsigned char BYTE;  
  5. typedef unsigned int WORD;  
  6. typedef unsigned long DWORD;  
  7.   
  8. /* PCI设备索引。bus/dev/func 共16位,为了方便处理可放在一个WORD中 */  
  9. #define PDI_BUS_SHIFT        8  
  10. #define PDI_BUS_SIZE         8  
  11. #define PDI_BUS_MAX          0xFF  
  12. #define PDI_BUS_MASK         0xFF00  
  13. #define PDI_DEVICE_SHIFT     3  
  14. #define PDI_DEVICE_SIZE      5  
  15. #define PDI_DEVICE_MAX       0x1F  
  16. #define PDI_DEVICE_MASK      0x00F8  
  17. #define PDI_FUNCTION_SHIFT   0  
  18. #define PDI_FUNCTION_SIZE    3  
  19. #define PDI_FUNCTION_MAX     0x7  
  20. #define PDI_FUNCTION_MASK    0x0007  
  21. #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) )  
  22.   
  23. /* PCI配置空间寄存器 */  
  24. #define PCI_CONFIG_ADDRESS   0xCF8  
  25. #define PCI_CONFIG_DATA      0xCFC  
  26.   
  27. /* 填充PCI_CONFIG_ADDRESS */  
  28. #define MK_PCICFGADDR(bus,dev,func) (DWORD)(0x80000000L | (DWORD)MK_PDI(bus,dev,func) << 8)  
  29.   
  30. /* 读32位端口 */  
  31. DWORD inpd(int portid)   
  32. {  
  33.     DWORD dwRet;  
  34.     asm mov dx, portid;  
  35.     asm lea bx, dwRet;  
  36.     __emit__(  
  37.     0x66,0x50,      // push EAX  
  38.     0x66,0xED,      // in EAX,DX  
  39.     0x66,0x89,0x07, // mov [BX],EAX  
  40.     0x66,0x58);     // pop EAX  
  41.     return dwRet;  
  42. }  
  43.   
  44. /* 写32位端口 */  
  45. void outpd(int portid, DWORD dwVal)  
  46. {  
  47.     asm mov dx, portid;  
  48.     asm lea bx, dwVal;  
  49.     __emit__(  
  50.     0x66,0x50,      // push EAX  
  51.     0x66,0x8B,0x07, // mov EAX,[BX]  
  52.     0x66,0xEF,      // out DX,EAX  
  53.     0x66,0x58);     // pop EAX  
  54.     return;  
  55. }  
  56.   
  57. int main(void)  
  58. {  
  59.     int bus, dev, func;  
  60.     int i;  
  61.     DWORD dwAddr;  
  62.     DWORD dwData;  
  63.     FILE* hF;  
  64.     char szFile[0x10];  
  65.     printf("\n");  
  66.     printf("Bus#\tDevice#\tFunc#\tVendor\tDevice\tClass\tIRQ\tIntPin\n");  
  67.     /* 枚举PCI设备 */  
  68.     for(bus = 0; bus <= PDI_BUS_MAX; ++bus)  
  69.     {  
  70.         for(dev = 0; dev <= PDI_DEVICE_MAX; ++dev)  
  71.         {  
  72.             for(func = 0; func <= PDI_FUNCTION_MAX; ++func)  
  73.             {  
  74.                 /* 计算地址 */  
  75.                 dwAddr = MK_PCICFGADDR(bus, dev, func);  
  76.   
  77.                 /* 获取厂商ID */  
  78.                 outpd(PCI_CONFIG_ADDRESS, dwAddr);  
  79.                 dwData = inpd(PCI_CONFIG_DATA);  
  80.                 /* 判断设备是否存在。FFFFh是非法厂商ID */  
  81.                 if ((WORD)dwData != 0xFFFF)  
  82.                 {  
  83.                     /* bus/dev/func */  
  84.                     printf("%2.2X\t%2.2X\t%1X\t", bus, dev, func);  
  85.                     /* Vendor/Device */  
  86.                     printf("%4.4X\t%4.4X\t", (WORD)dwData, dwData>>16);  
  87.                     /* Class Code */  
  88.                     outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x8);  
  89.                     dwData = inpd(PCI_CONFIG_DATA);  
  90.                     printf("%6.6lX\t", dwData>>8);  
  91.                     /* IRQ/intPin */  
  92.                     outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x3C);  
  93.                     dwData = inpd(PCI_CONFIG_DATA);  
  94.                     printf("%d\t", (BYTE)dwData);  
  95.                     printf("%d", (BYTE)(dwData>>8));  
  96.                     printf("\n");  
  97.                     /* 写文件 */  
  98.                     sprintf(szFile, "PCI%2.2X%2.2X%X.bin", bus, dev, func);  
  99.                     hF = fopen(szFile, "wb");  
  100.                     if (hF != NULL)  
  101.                     {  
  102.                         /* 256字节的PCI配置空间 */  
  103.                         for (i = 0; i < 0x100; i += 4)  
  104.                         {  
  105.                             /* Read */  
  106.                             outpd(PCI_CONFIG_ADDRESS, dwAddr | i);  
  107.                             dwData = inpd(PCI_CONFIG_DATA);  
  108.                             /* Write */  
  109.                             fwrite(&dwData, sizeof(dwData), 1, hF);  
  110.                         }  
  111.                         fclose(hF);  
  112.                     }  
  113.                 }  
  114.             }  
  115.         }  
  116.     }  
  117.     return 0;  
  118. }  

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

0 0
原创粉丝点击