PCI配置空間簡介


一、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;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM