PCI配置空間簡介

PCI配置空間簡介 作者:敏行
PCI有三個相互獨立的物理地址空間:設備存儲器地址空間I/O地址空間配置空間配置空間是PCI所特有的一個物理空間。由於PCI支持設備即插即用,所以PCI設備不佔用固定的內存地址空間或I/O地址空間,而是由操作系統決定其映射的基址。
系統加電時,BIOS檢測PCI總線,確定所有連接在PCI總線上的設備以及它們的配置要求,並進行系統配置。所以,所有的PCI設備必須實現配置空間,從而能夠實現參數的自動配置,實現真正的即插即用。
PCI總線規範定義的配置空間總長度爲256個字節,配置信息按一定的順序和大小依次存放。前64個字節的配置空間稱爲配置頭,對於所有的設備都一樣,配置頭的主要功能是用來識別設備定義主機訪問PCI卡的方式(I/O訪問或者存儲器訪問,還有中斷信息)。其餘的192個字節稱爲本地配置空間,主要定義卡上局部總線的特性、本地空間基地址及範圍等。
訪問PCI配置空間方法一
訪問PCI配置空間可通過兩個訪問寄存器,CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器。這兩個寄存器在PC中分別對應着CF8h和CFCh端口,並且是32位端口,即讀寫要用的32爲IN和OUT彙編指令。
每個PCI設備可應用三個信息進行定位,即Bus Number、Device Number和Function Number。另外,PCI配置空間一共是256個字節,被分割成64個4字節的寄存器,從0-63編號。
每次要訪問PCI配置空間時,先設置CONFIG_ADDRESS寄存器,這時CONFIG_DATA存儲器的內容就對應着該PCI配置空間中的相應的寄存器
訪問PCI配置空間方法二
第二種訪問配置空間的方法是通過HalGetBusData和HalSetBusData兩個內核函數。這兩個函數將方法進行了封裝,不需要程序員對PCI空間進行直接讀取。
DDK提供了兩個內核函數HalGetBusData和HalSetBusData,分別用於讀取PCI設備的配置空間和設置PCI配置空間。
ULONG HalGetBusData(
IN BUS_DATA_TYPE BusDataType,
IN ULONG BusNumber,
IN ULONG SlotNumber,
IN PVOID Buffer,
IN ULONG Length
);
註解:
BusDataType:該參數指定總線類型。HalGetBusData函數可以查詢各個總線的情況,對於PCI總線,這裏設置爲PCIConfiguration。
BusNumber:該參數指定Bus的總線號.
SlotNumber:該參數由Device號和功能號共同組成。
Buffer:該參數是得到的PCI配置空間的首地址。
Length:該參數是PCI配置空間的大小。
訪問PCI配置空間方法三(本例僅限於WDM驅動,比較廣泛使用)
方法二適用於NT式驅動,但並不適用於WDM驅動。WDM中使用IRP獲得PCI配置空間。此方法不需要了解PCI具體的配置空間結構,WDM驅動程序已經幫助程序員從PCI配置空間中分析出有用信息,並通知給程序員。
WDM會爲不同總線上的設備提供一個PDO設備,當程序員所寫的功能驅動掛接在PDO之上的時候,就可以將IRP_MN_START_DEVICE傳遞給底層的PDO去處理。PCI總線的PDO就會得到PCI配置空間,並從中得到有用信息,如中斷號、設備物理內存及I/O端口等信息。
在處理完IRP_MN_START_DEVICE後,驅動程序會將處理結果存儲在IRP的設備堆棧中,從I/O堆棧可以取出CM_FULL_RESOURCE_DESCRIPTOR數據結構,從CM_FULL_RESOURCE_DESCRIPTOR中可以取出CM_PARTIAL_RESOURCE_LIST數據結構,而在CM_PARTIAL_RESOURCE_LIST中又可以取出CM_PARTIAL_RESOURCE_DESCRIPTOR數據結構。
CM_FULL_RESOURCE_DESCRIPTOR數據結構就是PDO幫助程序員從256字節的PCI配置空間中獲取的有用信息,這其中包括終端號、設備物理內存、I/O端口等信息。
訪問PCI配置空間方法四(本例僅限於WDM驅動)
方法三沒有完整的的獲取到256字節的PCI配置空間,需要自己創建IRP_MN_READ_CONFIG或者IRP_MN_WRITE_CONFIG,然後將創建好的即插即用IRP發送到底層的PDO,並等待PDO的處理。該方法獲取完整的PCI配置空間。
 
 配置空間 操作系統
     PCI總線推出以來,以其獨有的特性受到衆多廠商的青睞,已經成爲計算機擴展總線的主流。目前,國內的許多技術人員已經具備開發PCI總線接口設備的能力。但是PCI總線的編程技術,也就是對PCI總線設備的操作技術,一直是一件讓技術人員感到頭疼的事情。PCI總線編程的核心技術是對相應板卡配置空間的理解和訪問。一般軟件編程人員基於對硬件設備原理的生疏,很難理解並操作配置空間,希望硬件開發人員直接告訴他們怎樣操作;而PCI總線硬件開發人員雖深刻地理解了其意義,在沒有太多編程經驗地前提下,也難於輕易地操作PCI板卡。結果大多是硬件技術人員花費大量時間和精力去學習DDK、WINDRVER等驅動程序開發軟件。
     作者在開發PCI總線接口設備時,經過對PCI總線協議的深入研究,從協議本身的角度出發,找到一種方面而快捷的PCI配置空間操作方法,只使用簡單的I/O命令即可找到特定的PCI總線設備並對其所有的配置空間進行讀寫操作。一旦讀得其配置空間的內容,即可中得到擔任系統對該PCI總線設備的資源分配。
     1 PCI總線配置空間及配置機制
     爲避免各PCI設備在資源的佔用上發生衝突,PCI總線採用即插即用協議。即在系統建立時由操作系統按照各設備的要求統一分配資源,資源分配的信息由系統寫入各PCI設備的配置空間寄存器,並在操作系統內部備份。各PCI設備有其獨自的配置空間,設計者通過對積壓設備(或插槽)的ISDEL引腳的驅動區分不同設備的配置空間。配置空間的前64個字節稱爲配置空間的預定自區,它對每個設備都具有相同的定義且必須被支持;共後的空間稱爲設備關聯區,由設備製造商根據需要定義。與編程有關的配置空間信息主要有:
     (1)設備號(Device ID)及銷售商號(Vendor
     ID),配置空間偏移量爲00h,用於對各PCI設備的區分和查找。爲了保證其唯一性,Vendor
     ID應當向PCI特別興趣小組(PCI SIG)申請而得到。
     (2)PCI基地址(PCI Base Address),配置空間偏移量爲10~24h,設備通過設定可讀寫的高位數值來向操作系統指示所需資源空間的大小。比如,某設備需要64K字節的內存空間,可以將配置空間的某基地址寄存器的高16位設成可讀寫的,而將低16位置爲0(只可讀)。操作系統在建立時,先向所有位寫1,實際上只有高16位被接收而被置成了1,低16位仍爲0.這樣操作系統讀取該寄存器時,返回值爲FFFF0000h,據此操作系統可以斷定其需要的空間大小是64K字節,然後分配一段空閒的內存空間並向該寄存器的高16位填寫其地址。
     其它可能與編程有關的配置空間的定義及地址請參閱參考文獻[1]。
     由於PC-AT兼容系統CPU只有內存和I/O兩種空間,沒有專用的配置空間,PCI協議規定利用特定的I/O空間操作驅動PCI橋路轉換成配置空間的操作。目前存在兩種轉換機制,即配置機制1#和配置機制2#。配置機制2#在新的設計中將不再被採用,新的設計應使用配置機制1#來產生配置空間的物理操作。這種機制使用了兩個特定的32位I/O空間,即CF8h和CFCh。這兩個空間對應於PCI橋路的兩個寄存器,當橋路看到CPU在局部總線對這兩個I/O空間進行雙字操作時,就將該I/O操作轉變爲PCI總線的配置操作。寄存器CF8h用於產生配置空間的地址(CONFIG-ADDRESS),寄存器CFCh用於保存配置空間的讀寫數據(CONFIG-DATA)。
     配置空間地址寄存器的格式如圖1。
     CF8H(局部總線):
     當CPU發出對I/O空間CFCh的操作時,PCI橋路將檢查配置空間地址寄存器CF8h的31位。如果爲1,就在PCI總線上產生一個相應的配置空間讀或寫操作,其地址由PCI橋路根據配置空間地址寄存器的內容作如圖2所示的轉換。
     CFCh (局部總線):
     設備號被PCI橋路譯碼產生PCI總線地址的高位地址,它們被設計者用作IDSEL信號來區分相應的PCI設備。6位寄存器號用於尋址該PCI設備配置空間62個雙字的配置寄存器(256字節)。功能號用於區分多功能設備的某特定功能的配置空間,對常用的單功能設備爲000。某中PCI插槽的總線號隨系統(主板)的不同稍有區別,大多數PC機爲1,工控機可能爲2或3。爲了找到某設備,應在系統的各個總線號上查找,直到定位。如果在0~5號總線上不能發現該設備,即可認爲該設備不存在。
     理解了上述PCI協議裏的配置機制後,就可以直接對CF8h和CFCh兩個雙字的I/O空間進行操作,查找某個PCI設備並訪問其配置空間,從而得到操作系統對該PCI設備的資源分配。
     2 用I/O命令訪問PCI總線配置空間
     要訪問PCI總線設備的配置空間,必須先查找該設備。查找的基本根據是各PCI設備的配置空間裏都存有特定的設備號(Device
     ID)及銷售商號(Vendor ID),它們佔用配置空間的00h地址。而查找的目的是獲得該設備的總線號和設備號。查找的基本過程如下:用I/O命令寫配置空間的地址寄存器CF8h,使其最高位爲1,總線號及設備爲0,功能號及寄存器號爲0,即往I/O端口CF8h80000000h;然後用I/O命令讀取配置空間的數據寄存器CFCh。如果該寄存器值與該PCI設備的Device
     ID及Vendor ID不相符,則依次遞增設備號/總線號,重複上述操作直到找到該設備爲止。如果查完所有的設備號/總線號(1~5)仍不能找到該設備,則應當考慮硬件上的問題。對於多功能設備,只要設備配置寄存器相應的功能號值,其餘步驟與單功能設備一樣。
     如查找設備號爲9054h,銷售商號爲10b5的單功能PCI設備,用VC++6.0編寫的程序如下:
     char bus;char device;
     unsigned int ioa0,iod;
     int scan( )
     {
     bus=0;device=0;
     for(char i=0;i<5;i++) {
     for(char j=0;j<32;j++) {
     bus=i; device=j;
     ioa0=0x80000000+bus*0x10000
     +(device*8)*0x100;
     _outpd(0xcf8,ioa0);
     iod=_inpd(0xcfc);
     if (iod0= =0x905410b5) return 0;
     }
     }
     retrn -1
     }
     調用子程序scan( ),如果返回值爲-1,則沒有找到該PCI設備。如果返回值爲0,則找到了該PCI設備。該設備的總線號和設備號分別在全局變量bus和device中,利用這兩個變量即可輕易對該設備的配置空間進行訪問,從而得到分配的資源信息。假設該PCI設備佔用了4個資源空間,分別對應於配置空間10h~1ch,其中前兩個爲I/O空間,後兩個爲內存空間,若定義其基地址分別爲ioaddr1,ioaddr2,memaddr1,memaddr2,相應的程序如下:
     unsigned short ioaddr1,ioaddr2;
     unsigned int memaddr1,memaddr2;
     unsigned int iobase,ioa;
     void getbaseaddr(char bus,char device);
     {
     iobase=0x80000000+bus*0x10000+(device*8)*0x100;
     ioa=iobase+0x10;/*尋址基地址寄存器0*/
     _outpd(0xcf8,ioa);
     ioaddr1=(unsigned
     short)_inpd(0xcfc)&0xfffc;
     /*屏蔽低兩位和高16位*/
     ioa=iobase+0x14; /*尋址基地址寄存器1*/
     _outpd(0xcf8,ioa);
     ioaddr2=(unsigned
     short)_inpd(0xcfc)&0xfffc;
     ioa=iobase+0x18;/*尋址基地寄存器2*/
     _outpd(0xcf8,ioa);
     memaddr1=_inpd(0xcfc) & 0xfffffff0;
     /*屏蔽低4位*/
     ioa=iobase+0x1c; /*尋址基地址寄存器3*/
     _outpd(0xcf8,ioa);
     memaddr2=_inpd(0xcfc) & 0xfffffff0;
     }
     對於I/O基地址,最低兩位D0、D1固定爲01,對地址本身無效,應當被屏蔽。對PC-AT兼容機,I/O有效地址爲16位,因此高位也應被屏蔽。對於內存地址,最低位D0固定爲0,而D1~D3用於指示該地址的一些物理特性[1],因此其低4位地址應當被屏蔽。需要指出的是該內存地址是系統的物理地址,在WINDOWS運行於保護模式時,需要經過轉換得到相應的線性地址才能對該內存空間進行直接讀寫。介紹該轉換方法的相關文章較爲常見,此處不再贅述。
     上述程序給出了讀取配置空間裏的基地址的方法。另有相當多PCI設備通過配置空間的設備關聯區來設置該設備的工作狀態,可輕易地用I/O命令進行相應的設置,無須編寫繁雜的驅動程序。在開發PCI視頻圖像採集卡的過程中,該方法得到了實際應用。
 
如何訪問PCI配置空間
1. 配置事務的產生
   PCI協議指出,一般情況下,系統都是通過主橋來實現軟件產生配置事務的,CF8和CFC端口對應主橋的兩個32位寄存器,
對CF8 和 CFC進行32位讀寫操作,即可產生PCI配置事務。
2. PCI兩類配置命令:類型0和類型1
   類型0:用於選擇總線上的一個設備
   命令格式:
 31      11 10              8 7               2  1   0
| reserve  | function number | register number | 0 | 0 | 
 
   類型1:用於向下級總線傳遞配置請求
   命令格式:
 31      24 23         16 15            11 10              8 7               2  1   0
| reserve  | bus number  | device number  | function number | register number | 0 | 1 |
3. PCI配置空間訪問流程
   所有PCI主橋和PCI-PCI橋都必須能夠產生這兩類配置命令,當發生cf8寄存器32位寫操作時,根據Bus Number,若爲0
,主橋則產生類型0配置命令,否則產生類型1配置命令。假設產生類型0配置命令,主橋將其(產生的32位命令數據)放在其
局部總線上,在其局部總線上尋找選擇目標設備,找到目標設備將其IDSEL#有效,所有的PCI設備都要相應類型0配置命令,
在其IDSEL#有效情況下,使其DEVSEL#有效,告訴橋設備自己就是目標設備,再根據寄存器索引來訪問具體的配置寄存器;若
其局部總線上沒有目標設備,則產生類型1配置命令並將其(產生的32位命令數)放在其局部總線上,所有的PCI設備(除
PCI-PCI橋)都要忽略類型1配置命令,只有橋設備響應類型1命令,橋設備譯碼總線號判斷配置事務的目標總線是否在其後,
若目標總線不在其後,忽略該配置事務,否則,它將聲明目標總線就在其後,若總線號不是該橋所掛接的總線,則原封不變
的向下傳遞,否則,將類型1配置命令轉換成類型0配置命令放在其掛接的總線上選擇設備。
如何在DOS中枚舉PCI設備(作者zyl910)
07-01-07 12:55  發表於:《VC的夢,VC的結》 分類:B)唧唧復唧唧
http://blog.csdn.net/xqd2006/articles/914267.aspx
File:      zEnumPCI
Name:      如何在DOS中枚舉PCI設備
Author:    zyl910
Blog:      http://blog.csdn.net/zyl910/
Version:   V1.0
Updata:    2006-6-30
下載(注意修改下載後的擴展名)

前言
~~~~
  學計算機這麼多年了,PCI這個名詞不知道叫了幾百遍了。可是,我一直不知道PC機是如何使用PCI總線的、PCI總線設備到底是如何工作的。可是以前我從來沒意識到這個問題,只是麻木的、帶着虛僞的自信活着。
  直到前段時間在書店看到《PCI Express 系統體系結構標準教材》,才突然感受到——我對PCI還一無所知,可現在 PCI Express 的時代都快到來了。我很受震撼,所以毫不猶豫地買下了那本書。
  回家打開書一看,發現絕大東西看不太懂。當年學接口技術時,那時只有ISA。ISA很簡單,是直接採用電路連線方式將設備與地址總線、各個IRQ及其他控制線路連接起來。而現在PCI-E就很複雜了,而我跳過了PCI,存在着知識斷層。
  於是我又跑到書店,去查PCI或接口技術上面的書。後來發現,這十年來,接口技術的書的確更新了,但只爲PCI寫個寥寥幾頁簡介,有的書甚至連引腳定義、配置空間等PCI最核心內容都沒有。那樣子還不如不說,浪費了紙張。典型只爲了教學大綱,而不考慮學生是否能學會、理解。
  我費了好大功夫,才找了幾本關於PCI的書,但主要是英文的。特別是找到了《PCI Express 系統體系結構標準教材》的英文版,這樣就可以對照參考,理解那些英文資料。
  看了一段時間之後,發現那些書主要是講硬件電氣特性、通信協議細節,面向的讀者是設計PCI設備的工程師。而我的學習目的是如果在PC機上編程控制PCI設備。還好在網絡上找了一些關於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# 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”,與程序得到的結果一致。
[Display.gif]

四、PCI BIOS
  直接訪問CF8h、CFCh端口的方法太底層。爲了提高兼容性,我們可以使用PCI BIOS。PCI BIOS的中斷號是1Ah,AH爲B1,AL爲功能號。其功能列表爲:
01h: INSTALLATION CHECK
02h: FIND PCI DEVICE
03h: FIND PCI CLASS CODE
06h: PCI BUS-SPECIFIC OPERATIONS
08h: READ CONFIGURATION BYTE
09h: READ CONFIGURATION WORD
0Ah: READ CONFIGURATION DWORD
0Bh: WRITE CONFIGURATION BYTE
0Ch: WRITE CONFIGURATION WORD
0Dh: WRITE CONFIGURATION DWORD
0Eh: GET IRQ ROUTING INFORMATION
0Fh: SET PCI IRQ
81h: INSTALLATION CHECK (32-bit)
82h: FIND PCI DEVICE (32-bit)
83h: FIND PCI CLASS CODE (32-bit)
86h: PCI BUS-SPECIFIC OPERATIONS (32-bit)
88h: READ CONFIGURATION BYTE (32-bit)
89h: READ CONFIGURATION WORD (32-bit)
8Ah: READ CONFIGURATION DWORD (32-bit)
8Bh: WRITE CONFIGURATION BYTE (32-bit)
8Ch: WRITE CONFIGURATION WORD (32-bit)
8Dh: WRITE CONFIGURATION DWORD (32-bit)
8Eh: GET IRQ ROUTING INFORMATION (32-bit)
8Fh: SET PCI IRQ (32-bit)

  由於像Turbo C這樣的16位編譯器訪問32位寄存器很麻煩,所以建議使用WORD方式來訪問PCI配置空間。以下是09h號功能的詳細說明(摘自Ralf Brown's Interrupt List):
--------X-1AB109-----------------------------
INT 1A - PCI BIOS v2.0c+ - READ CONFIGURATION WORD
 AX = B109h
 BH = bus number
 BL = device/function number (bits 7-3 device, bits 2-0 function)
 DI = register number (0000h-00FFh, must be multiple of 2) (see #00878)
Return: CF clear if successful
     CX = word read
 CF set on error
 AH = status (00h,87h) (see #00729)
 EAX, EBX, ECX, and EDX may be modified
 all other flags (except IF) may be modified
Notes: this function may require up to 1024 byte of stack; it will not enable
   interrupts if they were disabled before making the call
 the meanings of BL and BH on entry were exchanged between the initial
   drafts of the specification and final implementation
BUG: the Award BIOS 4.51PG (dated 05/24/96) incorrectly returns FFFFh for
   register 00h if the PCI function number is nonzero
SeeAlso: AX=B108h,AX=B10Ah,AX=B189h,INT 2F/AX=1684h/BX=304Ch
  代碼如下:
 
/*
File:      epcib.c
Name:      使用PCI BIOS來枚舉PCI設備
Author:    zyl910
Blog:      http://blog.csdn.net/zyl910/
Version:   V1.0
Updata:    2006-6-30
*/
#include <stdio.h>
#include <conio.h>
#include <dos.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) )
int main(void)
{
 int bus, dev, func;
 int i;
 union REGS regs;
 WORD wAddr;
 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) {
    /* 計算地址 */
    wAddr = MK_PDI(bus, dev, func);
    
    /* 獲取廠商ID */
    regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
    regs.x.bx = wAddr;
    regs.x.di = 0; // Vendor ID
    regs.x.cx = 0xFFFF; // 非法的Vendor ID
    int86(0x1A, &regs, &regs);
    /* 判斷設備是否存在。FFFFh是非法廠商ID */
    if (regs.x.cx != 0xFFFF) {
     /* bus/dev/func */
     printf("%2.2X\t%2.2X\t%1X\t", bus, dev, func);
     /* Vendor */
     printf("%4.4X\t", regs.x.cx);
     /* Device */
     regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
     regs.x.bx = wAddr;
     regs.x.di = 2; // Device ID
     int86(0x1A, &regs, &regs);
     printf("%4.4X\t", regs.x.cx);
     /* Class Code */
     regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
     regs.x.bx = wAddr;
     regs.x.di = 0xA; // Class/SubClass
     int86(0x1A, &regs, &regs);
     printf("%4.4X\t", regs.x.cx);
     /* IRQ/intPin */
     regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
     regs.x.bx = wAddr;
     regs.x.di = 0x3C; // IRQ/IntPin
     int86(0x1A, &regs, &regs);
     printf("%d\t", (BYTE)regs.x.cx);
     printf("%d", (BYTE)(regs.x.cx>>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 += 2) {
       /* Read */
       regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
       regs.x.bx = wAddr;
       regs.x.di = i;
       int86(0x1A, &regs, &regs);
       /* Write */
       fwrite(&regs.x.cx, 2, 1, hF);
      }
      fclose(hF);
     }
    }
   }
  }
 }
 return 0;
}

五、保護模式下的PCI BIOS
  剛纔所說的1Ah中斷是實模式下的BIOS,保護模式下怎麼辦?
  在BIOS內存區(E0000h ~ FFFFFh)可以找到PCI BIOS保護模式入口。其格式爲:
0h(dw): “_32_”標誌
4h(dw): PCI BIOS保護模式入口
8h(by): Rev Level
9h(by): 長度
Ah(by): 校檢和
Bh ~ Fh: 保留

  關於保護模式下PCI BIOS的具體用法可參考《PCI Bus Demystified》的第七章。
  由於切換到保護模式的代碼比較複雜,所以就沒有編程測試了。
六、總結
  本文章中的代碼能在純DOS下或Windows9X中正常運行,但是不能在WindowsXP等NT平臺下運行:對於訪問端口方式,廠商ID均返回FFFFh;對於調用 PCI BIOS 方式,貌似根本沒有實現該功能。這可能與WindowsXP設計有關——不再使用V86方式執行DOS程序,而是專門做了個DOS虛擬機。而且還有許多這樣的兼容性問題,比如VBE。我都有點懷疑這不是微軟故意這麼做,讓我們不能接觸底層,只能使用高層的.Net,這樣不可能對像微軟這樣掌握核心技術的公司造成威脅。

參考文獻
~~~~~~~~
[1] PCI-SIG. PCI Local Bus Specification Revision 3.0. 2002.8.12
[2] Doug Abbott. PCI Bus Demystified. LLH Technology Publishing, 2000
[3] 李貴山、戚德虎. PCI 局部總線開發者指南. 西安電子科技大學出版社, 1997.1
[4] MindShare, Inc , Ravi Budruk, Don Anderson, Tom Shanley. PCI Express System Architecture. Addison Wesley, 2003.9.4
[5] MindShare公司. 田玉敏,王崧,張波 譯. PCI Express 系統體系結構標準教材. 電子工業出版社, 2005.11
[6] Ralf Brown's Interrupt List. http://www.cs.cmu.edu/~ralf/

更新
~~~~

0

收藏

zhuyi108

16篇文章,9W+人氣,0粉絲

Ctrl+Enter 發佈

發佈

取消

掃一掃,領取大禮包

0

分享
zhuyi108
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章