X86硬件之dos下的PCI device遍歷及實例(BorlandC++)

一、PCI簡介

PCI設備都有獨立的配置空間,HOST主橋通過配置讀寫總線事務訪問這段空間。PCI總線規定了三種類型的PCI配置空間,分別是PCI Agent設備使用的配置空間,PCI橋使用的配置空間和Cardbus橋片使用的配置空間。

1、 PCI橋

PCI橋的引入使PCI總線極具擴展性,也極大地增加了PCI總線的複雜度。PCI總線的電氣特性決定了在一條PCI總線上掛接的負載有限,當PCI總線需要連接多個PCI設備時,需要使用PCI橋進行總線擴展,擴展出的PCI總線可以連接其他PCI設備,包括PCI橋。在一顆PCI總線樹上,最多可以掛接256個PCI設備,包括PCI橋。
PCI橋在PCI總線樹中的位置如圖所示。
在這裏插入圖片描述

PCI橋作爲一個特殊的PCI設備,具有獨立的配置空間。但是PCI橋配置空間的定義與PCI Agent設備有所不同。PCI橋的配置空間可以管理其下PCI總線子樹的PCI設備,並可以優化這些PCI設備通過PCI橋的數據訪問。PCI橋的配置空間在系統軟件遍歷PCI總線樹時配置,系統軟件不需要專門的驅動程序設置PCI橋的使用方法,這也是PCI橋被稱爲透明橋的主要原因。
在某些處理器系統中,還有一類PCI橋,叫做非透明橋。非透明橋不是PCI總線定義的標準橋片,但是在使用PCI總線掛接另外一個處理器系統時非常有用,非透明橋片的主要作用是連接兩個不同的PCI總線域,進而連接兩個處理器系統。
使用PCI橋可以擴展出新的PCI總線,在這條PCI總線上還可以繼續掛接多個PCI設備。PCI橋跨接在兩個PCI總線之間,其中距離HOST主橋較近的PCI總線被稱爲該橋片上游總線(Primary Bus),距離HOST主橋較遠的PCI總線被稱爲該橋片的下游總線(Secondary Bus)。如圖2 8所示,PCI橋1的上游總線爲PCI總線x0,而PCI橋1的下游總線爲PCI總線x1。這兩條總線間的數據通信需要通過PCI橋1。
通過PCI橋連接的PCI總線屬於同一個PCI總線域,在圖2 8中,PCI橋1、2和3連接的PCI總線都屬於PCI總線x域。在這些PCI總線域上的設備可以通過PCI橋直接進行數據交換而不需要進行地址轉換;而分屬不同PCI總線域的設備間的通信需要進行地址轉換,如與PCI非透明橋兩端連接的設備之間的通信。
如圖所示,每一個PCI總線的下方都可以掛接一個到多個PCI橋,每一個PCI橋都可以推出一條新的PCI總線。在同一條PCI總線上的設備之間的數據交換不會影響其他PCI總線。如PCI設備21與PCI設備22之間的數據通信僅佔用PCI總線x2的帶寬,而不會影響PCI總線x0、x1與x3,這也是引入PCI橋的另一個重要原因。
由圖2 8我們還可以發現PCI總線可以通過PCI橋組成一個胖樹結構,其中每一個橋片都是父節點,而PCI Agent設備只能是子節點。當PCI橋出現故障時,其下的設備不能將數據傳遞給上游總線,但是並不影響PCI橋下游設備間的通信。當PCI橋1出現故障時,PCI設備11、PCI設備21和PCI設備22將不能與PCI設備01和存儲器進行通信,但是PCI設備21和PCI設備22之間的通信可以正常進行。
使用PCI橋可以擴展一條新的PCI總線,但是不能擴展新的PCI總線域。如果當前系統使用32位的PCI總線地址,那麼這個系統的PCI總線域的地址空間爲4GB大小,在這個總線域上的所有設備將共享這個4GB大小的空間。如在PCI總線x域上的PCI橋1、PCI設備01、PCI設備11、PCI橋2、PCI設備21和PCI設備22等都將共享一個4GB大小的空間。再次強調這個4GB空間是PCI總線x域的“PCI總線地址空間”,和存儲器域地址空間和PCI總線y域沒有直接聯繫。
處理器系統可以通過HOST主橋擴展出新的PCI總線域,如MPC8548處理器的HOST主橋x和y可以擴展出兩個PCI總線域x和y。這兩個PCI總線域x和y之間的PCI空間在正常情況下不能直接進行數據交換,但是PowerPC處理器可以通過設置PIWARn寄存器的TGI字段使得不同PCI總線域的設備直接通信。
許多處理器系統使用的PCI設備較少,因而並不需要使用PCI橋。因此在這些處理器系統中,PCI設備都是直接掛接在HOST主橋上,而不需要使用PCI橋擴展新的PCI總線。即便如此讀者也需要深入理解PCI橋的知識。
PCI橋對於理解PCI和PCIe總線都非常重要。在PCIe總線中,雖然在物理結構上並不含有PCI橋,但是與PCI橋相關的知識在PCIe總線中無處不在,比如在PCIe總線的Switch中,每一個端口都與一個虛擬PCI橋對應,Switch使用這個虛擬PCI橋管理其下PCI總線子樹的地址空間。
2、PCI Agent設備的配置空間
在一個具體的處理器應用中,PCI設備通常將PCI配置信息存放在E2PROM中。PCI設備進行上電初始化時,將E2PROM中的信息讀到PCI設備的配置空間中作爲初始值。這個過程由硬件邏輯完成,絕大多數PCI設備使用這種方式初始化其配置空間。
在x86處理器中,系統軟件使用CONFIG_ADDR和CONFIG_DATA寄存器,讀取PCI設備配置空間的這些初始化信息,然後根據處理器系統的實際情況使用DFS算法,初始化處理器系統中所有PCI設備的配置空間。
在PCI Agent設備的配置空間中包含了許多寄存器,這些寄存器決定了該設備在PCI總線中的使用方法。
PCI Agent設備使用的配置空間如圖2 9所示。
在這裏插入圖片描述
在PCI Agent設備配置空間中包含的寄存器如下所示。
(1) Device ID和Vendor ID寄存器
這兩個寄存器的值由PCISIG分配,只讀。其中Vendor ID代表PCI設備的生產廠商,而Device ID代表這個廠商所生產的具體設備。如Intel公司的基於82571EB芯片的系列網卡,其Vendor ID爲0x8086 [1],而Device ID爲0x105E [2]。
(2) Revision ID和Class Code寄存器
這兩個寄存器只讀。其中Revision ID寄存器記載PCI設備的版本號。該寄存器可以被認爲是Device ID寄存器的擴展。
(3) Header Type寄存器
該寄存器只讀,由8位組成。
第7位爲1表示當前PCI設備是多功能設備,爲0表示爲單功能設備。
第6~0位表示當前配置空間的類型,爲0表示該設備使用PCI Agent設備的配置空間,普通PCI設備都使用這種配置頭;爲1表示使用PCI橋的配置空間,PCI橋使用這種配置頭;爲2表示使用Cardbus橋片的配置空間,Card Bus橋片使用這種配置頭,本篇對這類配置頭不感興趣。
系統軟件需要使用該寄存器區分不同類型的PCI配置空間,該寄存器的初始化必須與PCI設備的實際情況對應,而且必須爲一個合法值。
(4) Cache Line Size寄存器
該寄存器記錄HOST處理器使用的Cache行長度。在PCI總線中和Cache相關的總線事務,如存儲器寫並無效和Cache多行讀等總線事務需要使用這個寄存器。值得注意的是,該寄存器由系統軟件設置,但是在PCI設備的運行過程中,只有其硬件邏輯纔會使用該寄存器,比如PCI設備的硬件邏輯需要得知處理器系統Cache行的大小,才能進行存儲器寫並無效總線事務,單行讀和多行讀總線事務。
如果PCI設備不支持與Cache相關的總線事務,系統軟件可以不設置該寄存器,此時該寄存器爲初始值0x00。對於PCIe設備,該寄存器的值無意義,因爲PCIe設備在進行數據傳送時,在其報文中含有一次數據傳送的大小,PCIe總線控制器可以使用這個“大小”,判斷數據區域與Cache行的對應關係。
(5) Subsystem ID和Subsystem Vendor ID寄存器
這兩個寄存器和Device ID和Vendor ID類似,也是記錄PCI設備的生產廠商和設備名稱。但是這兩個寄存器和Device ID與Vendor ID寄存器略有不同。下文以一個實例說明Subsystem ID和Subsystem Vendor ID的用途。
Xilinx公司在FGPA中集成了一個PCIe總線接口的IP核,即LogiCORE。用戶可以使用LogiCORE設計各種各樣基於PCIe總線的設備,但是這些設備的Device ID都是0x10EE,而Vendor ID爲0x0007 [3]。
(6) Expansion ROM base address寄存器
有些PCI設備在處理器還沒有運行操作系統之前,就需要完成基本的初始化設置,比如顯卡、鍵盤和硬盤等設備。爲了實現這個“預先執行”功能,PCI設備需要提供一段ROM程序,而處理器在初始化過程中將運行這段ROM程序,初始化這些PCI設備。Expansion ROM base address記載這段ROM程序的基地址。
(7) Capabilities Pointer寄存器
在PCI設備中,該寄存器是可選的,但是在PCI-X和PCIe設備中必須支持這個寄存器,Capabilities Pointer寄存器存放Capabilities寄存器組的基地址,PCI設備使用Capabilities寄存器組存放一些與PCI設備相關的擴展配置信息。該組寄存器的詳細說明見第4.3節。
(8) Interrupt Line寄存器
這個寄存器是系統軟件對PCI設備進行配置時寫入的,該寄存器記錄當前PCI設備使用的中斷向量號,設備驅動程序可以通過這個寄存器,判斷當前PCI設備使用處理器系統中的哪個中斷向量號,並將驅動程序的中斷服務例程註冊到操作系統中 [4]。
該寄存器由系統軟件初始化,其保存的值與8259A中斷控制器相關,該寄存器的值也是由PCI設備與8259A中斷控制器的連接關係決定的。如果在一個處理器系統中,沒有使用8259A中斷控制器管理PCI設備的中斷,則該寄存器中的數據並沒有意義。
在多數PowerPC處理器系統中,並不使用8259A中斷控制器管理PCI設備的中斷請求,因此該寄存器沒有意義。即使在x86處理器系統中,如果使用I/O APIC中斷控制器,該寄存器保存的內容仍然無效。目前在絕大多數處理器系統中,並沒有使用該寄存器存放PCI設備使用的中斷向量號。
(9) Interrupt Pin寄存器
這個寄存器保存PCI設備使用的中斷引腳,PCI總線提供了四個中斷引腳INTA#、INTB#、INTC#和INTD#。Interrupt Pin寄存器爲1時表示使用INTA#引腳向中斷控制器提交中斷請求,爲2表示使用INTB#,爲3表示使用INTC#,爲4表示使用INTD#。
如果PCI設備只有一個子設備時,該設備只能使用INTA#;如果有多個子設備時,可以使用INTBD#信號。如果PCI設備不使用這些中斷引腳,向處理器提交中斷請求時,該寄存器的值必須爲0。值得注意的是,雖然在PCIe設備中並不含有INTAD#信號,但是依然可以使用該寄存器,因爲PCIe設備可以使用INTx中斷消息,模擬PCI設備的INTA~D#信號,詳見第6.3.4節。
(10) Base Address Register 0~5寄存器
該組寄存器簡稱爲BAR寄存器,BAR寄存器保存PCI設備使用的地址空間的基地址,該基地址保存的是該設備在PCI總線域中的地址。其中每一個設備最多可以有6個基址空間,但多數設備不會使用這麼多組地址空間。
在PCI設備復位之後,該寄存器將存放PCI設備需要使用的基址空間大小,這段空間是I/O空間還是存儲器空間 [5],如果是存儲器空間該空間是否可預取,有關PCI總線預讀機制的詳細說明見第3.4.5節。
系統軟件對PCI總線進行配置時,首先獲得BAR寄存器中的初始化信息,之後根據處理器系統的配置,將合理的基地址寫入相應的BAR寄存器中。系統軟件還可以使用該寄存器,獲得PCI設備使用的BAR空間的長度,其方法是向BAR寄存器寫入0xFFFF-FFFF,之後再讀取該寄存器。
處理器訪問PCI設備的BAR空間時,需要使用BAR寄存器提供的基地址。值得注意的是,處理器使用存儲器域的地址,而BAR寄存器存放PCI總線域的地址。因此處理器系統並不能直接使用“BAR寄存器+偏移”的方式訪問PCI設備的寄存器空間,而需要將PCI總線域的地址轉換爲存儲器域的地址。
如果x86處理器系統使能了IOMMU後,這兩個地址也並不一定相等,因此處理器系統直接使用這個PCI總線域的物理地址,並不能確保訪問PCI設備的BAR空間的正確性。除此之外在Linux系統中,ioremap函數的輸入參數爲存儲器域的物理地址,而不能使用PCI總線域的物理地址。
而在pci_devresource[bar].start參數中保存的地址已經經過PCI總線域到存儲器域的地址轉換,因此在編寫Linux系統的設備驅動程序時,需要使用pci_devresource[bar].start參數中的物理地址,然後再經過ioremap函數將物理地址轉換爲“存儲器域”的虛擬地址。
(11) Command寄存器
該寄存器爲PCI設備的命令寄存器,該寄存器在初始化時,其值爲0,此時這個PCI設備除了能夠接收配置請求總線事務之外,不能接收任何存儲器或者I/O請求。系統軟件需要合理設置該寄存器之後,才能訪問該設備的存儲器或者I/O空間。在Linux系統中,設備驅動程序調用pci_enable_device函數,使能該寄存器的I/O和Memory Space位之後,才能訪問該設備的存儲器或者I/O地址空間。
(12) Status寄存器
該寄存器的絕大多數位都是隻讀位,保存PCI設備的狀態。
(13) Latency Timer寄存器
在PCI總線中,多個設備共享同一條總線帶寬。該寄存器用來控制PCI設備佔用PCI總線的時間,當PCI設備獲得總線使用權,並使能Frame#信號後,Latency Timer寄存器將遞減,當該寄存器歸零後,該設備將使用超時機制停止 [6]對當前總線的使用。
如果當前總線事務爲Memeory Write and Invalidate時,需要保證對一個完整Cache行的操作結束後才能停止當前總線事務。對於多數PCI設備而言,該寄存器的值爲32或者64,以保證一次突發傳送的基本單位爲一個Cache行。
PCIe設備不需要使用該寄存器,該寄存器的值必須爲0。因爲PCIe總線的仲裁方法與PCI總線不同,使用的連接方法也與PCI總線不同。
3、 PCI橋的配置空間
PCI橋使用的配置空間的寄存器如錯誤!未找到引用源。PCI橋作爲一個PCI設備,使用的許多配置寄存器與PCI Agent的寄存器是類似的,如Device ID、Vendor ID、Status、Command、Interrupt Pin、Interrupt Line寄存器等,本節不再重複介紹這些寄存器。下文將重點介紹在PCI橋中與PCI Agent的配置空間不相同的寄存器。
在這裏插入圖片描述

與PCI Agent設備不同,在PCI橋中只含有兩組BAR寄存器,Base Address Register 0~1寄存器。這兩組寄存器與PCI Agent設備配置空間的對應寄存器的含義一致。但是在PCI橋中,這兩個寄存器是可選的。如果在PCI橋中不存在私有寄存器,那麼可以不使用這組寄存器設置BAR空間。

在大多數PCI橋中都不存在私有寄存器,操作系統也不需要爲PCI橋提供專門的驅動程序,這也是這類橋被稱爲透明橋的原因。如果在PCI橋中不存在私有空間時,PCI橋將這兩個BAR寄存器初始化爲0。在PCI橋的配置空間中使用兩個BAR寄存器的目的是這兩個32位的寄存器可以組成一個64位地址空間。
在PCI橋的配置空間中,有許多寄存器是PCI橋所特有的。PCI橋除了作爲PCI設備之外,還需要管理其下連接的PCI總線子樹使用的各類資源,即Secondary Bus所連接PCI總線子樹使用的資源。這些資源包括存儲器、I/O地址空間和總線號。
在PCI橋中,與Secondary bus相關的寄存器包括兩大類。一類寄存器管理Secondary Bus之下PCI子樹的總線號,如Secondary和Subordinate Bus Number寄存器;另一類寄存器管理下游PCI總線的I/O和存儲器地址空間,如I/O和Memory Limit、I/O和Memory Base寄存器。在PCI橋中還使用Primary Bus寄存器保存上游的PCI總線號。
其中存儲器地址空間還分爲可預讀空間和不可預讀空間,Prefetchable Memory Limit和Prefetchable Memory Base寄存器管理可預讀空間,而Memory Limit、Memory Base管理不可預讀空間。在PCI體系結構中,除了了ROM地址空間之外,PCI設備使用的地址空間大多都是不可預讀的。
(1) Subordinate Bus Number、Secondary Bus Number和Primary Bus Number寄存器
PCI橋可以管理其下的PCI總線子樹。其中Subordinate Bus Number寄存器存放當前PCI子樹中,編號最大的PCI總線號。而Secondary Bus Number寄存器存放當前PCI橋Secondary Bus使用的總線號,這個PCI總線號也是該PCI橋管理的PCI子樹中編號最小的PCI總線號。因此一個PCI橋能夠管理的PCI總線號在Secondary Bus Number~Subordinate Bus Number之間。這兩個寄存器的值由系統軟件遍歷PCI總線樹時設置。
Primary Bus Number寄存器存放該PCI橋上游的PCI總線號,該寄存器可讀寫。Primary Bus Number、Subordinate Bus Number和Secondary Bus Number寄存器在初始化時必須爲0,系統軟件將根據這幾個寄存器是否爲0,判斷PCI橋是否被配置過。
不同的操作系統使用不同的Bootloader引導,有的Bootloader可能會對PCI總線樹進行遍歷,此時操作系統可以不再重新遍歷PCI總線樹。在x86處理器系統中,BIOS會遍歷處理器系統中的所有PCI總線樹,操作系統可以直接使用BIOS的結果,也可以重新遍歷PCI總線樹。而PowerPC處理器系統中的Bootloader,如U-Boot並沒有完全遍歷PCI總線樹,此時操作系統必須重新遍歷PCI總線樹。
(2) Secondary Status寄存器
該寄存器的含義與PCI Agent配置空間的Status寄存器的含義相近,PCI橋的Secondary Status寄存器記錄Secondary Bus的狀態,而不是PCI橋作爲PCI設備時使用的狀態。在PCI橋配置空間中還存在一個Status寄存器,該寄存器保存PCI橋作爲PCI設備時的狀態。
(3) Secondary Latency Timer寄存器
該寄存器的含義與PCI Agent配置空間的Latency Timer寄存器的含義相近,PCI橋的Secondary Latency Timer寄存器管理Secondary Bus的超時機制,即PCI橋發向下游的總線事務;在PCI橋配置空間中還存在一個Latency Timer寄存器,該寄存器管理PCI橋發向上游的總線事務。
(4) I/O Limit和I/O Base寄存器
在PCI橋管理的PCI子樹中包含許多PCI設備,而這些PCI設備可能會使用I/O地址空間。PCI橋使用這兩個寄存器,存放PCI子樹中所有設備使用的I/O地址空間集合的基地址和大小。
(5) Memory Limit和Memory Base寄存器
在PCI橋管理的PCI子樹中有許多PCI設備,這些PCI設備可能會使用存儲器地址空間。這兩個寄存器存放所有這些PCI設備使用的,存儲器地址空間集合的基地址和大小,PCI橋規定這個空間的大小至少爲1MB。
(6) Prefetchable Memory Limit和Prefetchable Memory Base寄存器
在PCI橋管理的PCI子樹中有許多PCI設備,如果這些PCI設備支持預讀,則需要從PCI橋的可預讀空間中獲取地址空間。PCI橋的這兩個寄存器存放這些PCI設備使用的,可預取存儲器空間的基地址和大小。
如果PCI橋不支持預讀,則其下支持預讀的PCI設備需要從Memory Base寄存器爲基地址的存儲器空間中獲取地址空間。如果PCI橋支持預讀,其下的PCI設備需要根據情況,決定使用可預讀空間,還是不可預讀空間。PCI總線建議PCI設備支持預讀,但是支持預讀的PCI設備並不多見。
(7) I/O Base Upper 16 Bits and I/O Limit Upper 16寄存器
如果PCI橋僅支持16位的I/O端口,這組寄存器只讀,且其值爲0。如果PCI橋支持32位I/O端口,這組寄存器可以提供I/O端口的高16位地址。
(8) Bridge Control Register。
該寄存器用來管理PCI橋的Secondary Bus,其主要位的描述如下。
Secondary Bus Reset位,第6位,可讀寫。當該位爲1時,將使用下游總線提供的RST#信號復位與PCI橋的下游總線連接的PCI設備。通常情況下與PCI橋下游總線連接的PCI設備,其復位信號需要與PCI橋提供的RST#信號連接,而不能與HOST主橋提供的RST#信號連接。
Primary Discard Timer位,第8位,可讀寫。PCI橋支持Delayed傳送方式,當PCI橋的Primary總線上的主設備使用Delayed方式進行數據傳遞時,PCI橋使用Retry週期結束Primary總線的Non-Posted數據請求,並將這個Non-Posted數據請求轉換爲Delayed數據請求,之後主設備需要擇時重試相同的Non-Posted數據請求。當該位爲1時,表示在Primary Bus上的主設備需要在210個時鐘週期之內重試這個數據請求,爲0時,表示主設備需要在215個時鐘週期之內重試這個數據請求,否則PCI橋將丟棄Delayed數據請求。
Secondary Discard Timer位,第9位,可讀寫。當該位爲1時,表示在Secondary Bus上的主設備需要在210個時鐘週期之內重試這個數據請求,爲0時,表示主設備需要在215個時鐘週期之內重試這個數據請求,如果主設備在規定的時間內沒有進行重試時,PCI橋將丟棄Delayed數據請求。

[1] PCI SIG分配給Intel的Vendor ID號是0x8086,8086處理器也是Intel設計的第一個PC處理器。
[2] 這僅是Intel爲82571的Copper口分配的Vendor ID.
[3] Xilinx使用的Device ID號爲0x10EE,而LogiCORE的Vendor ID號爲0x0007。
[4] Linux系統使用request_irq函數註冊一個設備的中斷服務例程。
[5] 一般來說PCI設備使用E2PROM保存BAR寄存器的初始值。
[6] 此時GNT#信號爲無效。爲提高仲裁效率,PCI設備在進行數據傳送時,GNT#信號可能已經無效。

4、example 遍歷pci設備(borland C)
4.1 獲取PCI列表
方法一:
直接訪問CF8、CFCH端口的方法:
1.定義PCI設備索引和配置空間寄存器
2.填充bus,dev,func的16位空間
3.讀寫32位端口CF8,CFC,16位編譯器加入機器碼
4.枚舉pci設備(獲取廠商,廠商ID,class code,IRQ等)
方法二:
使用pci BIOS 中斷(中斷號:1AH位B1,AL位功能號)
1.定義pci設備索引和使用WORD方式讀取配置字來訪問PCI配置空間
2.定義union來緩存pci信息
3.枚舉PCI設備(獲取廠商,廠商ID,class code,IRQ等)

注:
在這裏插入圖片描述

使用端口遍歷PCI列表:

#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) )

/* 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;
}

使用BIOS 中斷遍歷PCI列表

#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* fp;
  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;
   regs.x.bx=wAddr;
   regs.x.di=0;
   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);
   /*
   switch(regs.x.cx)
   {
	case 1106: printf("%3d VIM %4.4X\t",regs.x.cx);
	break;
   //	case "10EC": printf("%3d Realtek" );
   //	break;
	default:printf("%3d nodefnal" );
   }
   */
   //Device 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=2;//Debice ID
   int86(0x1A,&regs,&regs);
   printf("%4.4X\t",regs.x.cx);

   // Class Code 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=0xA;//Debice ID
   int86(0x1A,&regs,&regs);
   printf("%4.4X\t",regs.x.cx);

   // IRQ/intPin 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=0x3C;//Debice ID
   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);
   fp = fopen(szFile, "wb");
   if (fp != NULL) {
    // 256字節的PCI配置空間
    for (i = 0; i < 0x100; i += 2) {
    // Read 
      regs.x.ax=0xB109;
      regs.x.bx=wAddr;
      regs.x.di=i;
      int86(0x1A,&regs,&regs);
      fwrite(&regs.x.cx,2,1,fp);
    }
	 fclose(fp);
   }
  }
   }
  }
 }
 return 0;
}

二、獲取PCI speed 和version

1、在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

#include<string.h>
#include<stdio.h>
#include<dos.h>
#include<conio.h>

typedef unsigned char BYTE;//1byte--4bit
typedef unsigned int WORD;//2byte--8bit
typedef unsigned long DWORD;//4byte--16bit

#define PDI_BUS_MAX 0xFF
#define PDI_DEVICE_MAX 0x1F
#define PDI_FUNCTION_MAX 0x7

#define PCI_CONFIG_ADDRESS 0xCF8
#define PCI_CONFIG_DATA 0xCFC

/*filled with one WORD*/
#define MK_PDI(bus,dev,func) (WORD)((bus<<8)|(dev<<3)|func)
/*filled with PCI_CONFIG_ADDRESS*/
#define MK_PCIaddr(bus,dev,func) ((DWORD)0x80000000L|(DWORD)MK_PDI(bus,dev,func)<<8)

/*read 32bit port*/
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;
}
/*write 32bit port*/
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;
}
DWORD GetData(DWORD address)
{
  DWORD data;
  outpd(0xCF8,address);
  data=inpd(0xCFC);
  return data;
}
void main()
{
  int bus,dev,func;
  int i;
  DWORD address;
  DWORD data;

  printf("Bus#\tDevice#\tFunc#\tVersion#\tWidth\tWorkSpeed\tConfigSpeed\n");

  for (bus = 0; bus <= 0xFF; ++bus)
  {
	for (dev = 0; dev <= 0x1F; ++dev)
	{
	  for (func = 0; func <= 0x7; ++func)
	  {
			address =MK_PCIaddr(bus,dev,func);
		  data = GetData(address);
		if((WORD)data!=0xFFFF)
		{
		  /* bus/dev/func */
		  printf("%1X\t%1X\t%1X\t", bus, dev, func);

		  DWORD address1=address|0x34;
		  data=GetData(address1);
		if((BYTE)data==0x00)
		  {
			printf("-\t-\t-\t-\n");

		  }else //data!=0
	   {
		address1=address|(BYTE)data;
		data=GetData(address1);

		if ((BYTE)data==0x10)
		{
		  data=GetData(address1|0x0C);

		  switch(((WORD)data)&0x03ff)
				{
					case 0x0011:printf("1.0\t250MB/s\t");
			break;
			case 0x0021:printf("1.0\t1GB/s\t");
			break;
			case 0x0041:printf("1.0\t2GB/s\t");
			break;
			case 0x0101:printf("1.0\t4GB/s\t");
			break;
			case 0x0012:printf("2.0\t500MB/s\t");
			break;
			case 0x0022:printf("2.0\t2GB/s\t");
			break;
			case 0x0042:printf("2.0\t4GB/s\t");
			break;
			case 0x0102:printf("2.0\t8GB/s\t");
			break;
			case 0x0013:printf("3.0\t984.6MB/s\t");
			break;
			case 0x0023:printf("3.0\t3.938GB/s\t");
			break;
			case 0x0043:printf("3.0\t7.877GB/s\t");
			break;
			case 0x0103:printf("3.0\t15.754GB/s\t");
			break;
			default:    printf("-\t-\t");
			break;
		}
		data=GetData(address1|0x10);
		switch((BYTE)(data>>16))
		{
		  case 0x01:printf("2.5GT/s\t");
		  break;
		  case 0x02:printf("5GT/s\t");
		  break;
		  case 0x03:printf("8GT/s\t");
		  break;
		  case 0x11:printf("2.5GT/s\t");
		  break;
		  case 0x12:printf("5GT/s\t");
		  break;
		  case 0x13:printf("8GT/s\t");
		  break;
		  default:  printf("-\t");
		  break;
		}
		data=GetData(address1|0x30);
		switch((BYTE)data)
		{
		  case 0x01:printf("2.5GT/s\n");
		  break;
		  case 0x02:printf("5GT/s\n");
		  break;
		  case 0x03:printf("8GT/s\n");
		  break;
		  case 0x04:printf("16GT/s\n");
		  break;
		  case 0x05:printf("32GT/s\n");
		  break;
		  default:  printf("-\n");
		  break;
		}

		  }
	   else
		{
			address1=address|(BYTE)(data>>8);
				data=GetData(address1);
		}
	   printf("-\t-\t-\t-\n");

	   }
	   //printf("-\t-\t-\t-\n");
	   break;

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