1. 程式人生 > >PCI匯流排配置空間詳解

PCI匯流排配置空間詳解

其實之前是簡單學習過PCI裝置的相關知識,但是總感覺 自己的理解很函式,很多東西說不清楚,正好今天接著寫這篇文章自己重新梳理一下,文章想要分為三部分,首先介紹PCI裝置硬體相關的知識,然後介紹LINux核心中對PCI裝置的支援。本節講第一部分。

PCI匯流排在目前計算機匯流排系統中佔據舉足輕重的地位,其良好的擴充套件性,地址統一分配和匯流排競爭的處理相對於其他匯流排而言都具有絕對優勢。

擴充套件性: 先說其擴充套件性,PCI總線上存在若干PCI裝置插槽,當PCI插槽無法滿足需求,就可以通過PCI橋擴充套件PCI裝置,一個PCI橋把一個PCI匯流排連在一個PCI插槽上,作為PCI的一個裝置。例如CPU通過“宿主-PCI橋與一條PCI匯流排相連,此匯流排成為“主PCI匯流排”,當通過PCI橋擴充套件PCI匯流排時,擴充套件的匯流排成為“從匯流排”,當然還可以通過其他的橋比如“PCI-ISA”橋擴充套件ISA匯流排,所以這樣通過PCI-PCI橋可以構築起一個層次的、樹狀的PCI系統結構,對於上層的匯流排而言,連線在這條總線上的PCI橋也是一個裝置,但是這是一種特殊的裝置。
其PCI樹狀結構如圖所示:

一條PCI匯流排一般有32個介面,即可以連線32個PCI介面卡,而一個介面卡對應一個外部裝置,注意這裡的外部裝置可以有多個功能(最多八個),每一個功能稱為邏輯裝置每一個邏輯裝置對應一個PCI配置空間。對於邏輯裝置後面還會詳細解釋,這裡先說配置空間的問題。PCI配置空間可以說是記錄了關於此裝置的詳細資訊。PCI配置空間最大256個位元組,其中起先的64個位元組的格式是預定義好的。當然並非所有的項都必須填充,位置是固定了,沒有用到可以填充0。而前16個位元組的格式是一定的。包含頭部的型別、裝置的總類、裝置的性質以及製造商等。

PCi配置空間前64個位元組格式如下:

需要注意的有一下幾項:

ClassCode 用於將裝置分到具體的功能組,該欄位分為兩部分,前8個bit表示基類即大類別,後8個位元表示基類的一個子類。比如PCI_BASE_CLASS_STORAGE表示大類大容量儲存器,而PCI_CLASS_STORAGE_IDE表明這個IDE控制器。

HeaderType表明頭部型別。一般區分為0型頭部(PCI裝置)1型頭部(PCI橋),注意不同頭部的配置空間格式有差異。這裡我們主要先描述0型頭部即普通PCi裝置的配置空間。

PCI HeaderType為一個位元組的大小,最高位為0表示單功能,最高位為1表示多功能(即前面描述的邏輯裝置),單功能情況下一個PCI裝置就是一個邏輯裝置。低7位表示頭部型別。

前16個位元組都是一些基本的資訊就不在多說,重點看下接下來的6個BAR空間。每個BAR記錄了該裝置對映的一段地址空間。為了區分IO空間和IO記憶體,這裡我們分開描述:

當BAR最後一位為0表示這是對映的IO記憶體,為1是表示這是IO 埠,當是IO記憶體的時候1-2位表示記憶體的型別,bit 2為1表示採用64位地址,為0表示採用32位地址。bit1為1表示區間大小超過1M,為0表示不超過1M.bit3表示是否支援可預取。

 

而相對於IO記憶體,當最後一位為1時表示對映的IO地址空間。IO地址空間一般不支援預取,所以這裡是29位的地址。

一般情況下,6個BAR是足夠使用的,大部分情況都是3-4個BAR。而這些BAR對映的空間是連續的,即這些BAR共同描述裝置的地址空間範圍

除了6個基本的BAR空間們還有一個額外的配置ROM區間,區間最低位表示是否使用ROM區間,高21位表示地址。中間的是保留項。其餘原理和上面類似。

接下來需要注意的就是中斷,因為大部分外設和系統互動就是通過中斷的方式。。

由配置空間中的IRQ Pin決定裝置是否支援中斷,1表示支援,0表示不支援。假如支援中斷,IRQ Line表記錄下中斷號。

 PCI橋的配置空間

PCI橋同樣是連線在PCI匯流排介面卡上的一個裝置,只不過是一個橋裝置,連線一條PCI匯流排。既然同屬於裝置,那麼它同樣也就有裝置的配置空間,只是它的配置空間和普通裝置的有些差異。PCI橋的配置空間在系統軟體遍歷PCI匯流排樹的時候配置,並不需要專門的PCI驅動,故稱為透明橋。PCI橋連線兩條匯流排,和HOst 橋近的稱為上游匯流排(Primary Bus ),遠的一條稱為下游匯流排(Secondry Bus)。PCI橋的配置空間在前16個位元組的格式和普通PCI裝置並無區別,另外,橋還保留了普通裝置的前兩個BAR空間。所以從配置空間的0x18開始有了橋裝置自身的配置格式。如前所述,橋裝置記錄了上游匯流排和下游匯流排,以及橋下最大的匯流排號。這裡還有一個比較重要的概念就是視窗。在PCI橋的配置空間有三個視窗:IO地址區間視窗、儲存器區間視窗、可預取儲存器地址視窗。實際上每個視窗都是一段地址區間,就像一個門,規定了橋下裝置對映的區間。從北橋出來的地址,如果在該區間內,就可以穿過該橋到達次級匯流排,這樣依次尋找裝置。反過來,從南橋出來的地址及由裝置發出的,只有地址不在該區間範圍內才可以穿過該橋。因為同一條總線上的裝置互動不需要外部空間。就像是內網傳輸和公網傳輸一樣的道理。

核心中關於橋配置空間的定義如下:

複製程式碼
/* Header type 1 (PCI-to-PCI bridges) */
#define PCI_PRIMARY_BUS        0x18    /* Primary bus number */
#define PCI_SECONDARY_BUS    0x19    /* Secondary bus number */
#define PCI_SUBORDINATE_BUS    0x1a    /* Highest bus number behind the bridge */
#define PCI_SEC_LATENCY_TIMER    0x1b    /* Latency timer for secondary interface */

#define PCI_IO_BASE        0x1c    /* I/O range behind the bridge */
#define PCI_IO_LIMIT        0x1d

#define  PCI_IO_RANGE_TYPE_MASK    0x0fUL    /* I/O bridging type */
#define  PCI_IO_RANGE_TYPE_16    0x00
#define  PCI_IO_RANGE_TYPE_32    0x01
#define  PCI_IO_RANGE_MASK    (~0x0fUL) /* Standard 4K I/O windows */
#define  PCI_IO_1K_RANGE_MASK    (~0x03UL) /* Intel 1K I/O windows */
#define PCI_SEC_STATUS        0x1e    /* Secondary status register, only bit 14 used */

#define PCI_MEMORY_BASE        0x20    /* Memory range behind */
#define PCI_MEMORY_LIMIT    0x22

#define  PCI_MEMORY_RANGE_TYPE_MASK 0x0fUL
#define  PCI_MEMORY_RANGE_MASK    (~0x0fUL)

#define PCI_PREF_MEMORY_BASE    0x24    /* Prefetchable memory range behind */
#define PCI_PREF_MEMORY_LIMIT    0x26

#define  PCI_PREF_RANGE_TYPE_MASK 0x0fUL
#define  PCI_PREF_RANGE_TYPE_32    0x00
#define  PCI_PREF_RANGE_TYPE_64    0x01
#define  PCI_PREF_RANGE_MASK    (~0x0fUL)
#define PCI_PREF_BASE_UPPER32    0x28    /* Upper half of prefetchable memory range */
#define PCI_PREF_LIMIT_UPPER32    0x2c
#define PCI_IO_BASE_UPPER16    0x30    /* Upper half of I/O addresses */
#define PCI_IO_LIMIT_UPPER16    0x32
/* 0x34 same as for htype 0 */
/* 0x35-0x3b is reserved */
#define PCI_ROM_ADDRESS1    0x38    /* Same as PCI_ROM_ADDRESS, but for htype 1 */
/* 0x3c-0x3d are same as for htype 0 */
#define PCI_BRIDGE_CONTROL    0x3e
#define  PCI_BRIDGE_CTL_PARITY    0x01    /* Enable parity detection on secondary interface */
#define  PCI_BRIDGE_CTL_SERR    0x02    /* The same for SERR forwarding */
#define  PCI_BRIDGE_CTL_ISA    0x04    /* Enable ISA mode */
#define  PCI_BRIDGE_CTL_VGA    0x08    /* Forward VGA addresses */
#define  PCI_BRIDGE_CTL_MASTER_ABORT    0x20  /* Report master aborts */
#define  PCI_BRIDGE_CTL_BUS_RESET    0x40    /* Secondary bus reset */
#define  PCI_BRIDGE_CTL_FAST_BACK    0x80    /* Fast Back2Back enabled on secondary interface */
複製程式碼

下面說說PCI裝置的地址空間:

前面也簡單介紹了下PCI裝置的地址空間支援PIO和MMIO,即IO埠和IO記憶體。下面詳細分析下這兩種方式:

PIO 

 IO埠的編址是獨立於系統的地址空間,其實就是一段地址區域,所有外設的地址都對映到這段區域中。就像是一個程序內部的各個變數,公用程序地址空間一樣。不同外設的IO埠不同。訪問IO埠需要特殊的IO指令,OUT/IN,OUT用於write操作,in用於read操作。在此基礎上,作業系統實現了讀寫不同大小埠的函式。為什麼說是不同大小呢??因為前面也說到,IO埠實際上是一段連續的區域,每個埠理論上是位元組為單位即8bit,那麼要想讀寫16位的埠只能把相鄰的埠進行合併,32位的埠也是如此。

例如下面的彙編指令: OUT 21h,al 0x21是8259A中斷控制器的中斷遮蔽暫存器,該指令將Intel X86處理器的al暫存器中的值寫到中斷暫存器的控制暫存器中,從而達到遮蔽某些中斷訊號的目的。類似的,在下面的彙編指令中: IN al,20h 0x20是8259A中斷控制器的正在服務暫存器,該指令將此暫存器中的狀態值傳遞到處理器的al暫存器中。 無論是windows還是Linux都會上述指令做了封裝以滿足讀寫不同長度埠的需要。不過這種方式缺點也比較明顯,一般情況CPU分配給IO埠的空間都比較小,在當前外設儲存日益增大的情況下很難滿足需要。另一方面,

MMIO

IO記憶體是直接把暫存器的地址空間直接對映到系統地址空間,系統地址空間往往會保留一段記憶體區用於這種MMIO的對映(當然肯定是位於系統記憶體區),這樣系統可以直接使用普通的訪存指令直接訪問裝置的暫存器,隨著計算機記憶體容量的日益增大,這種方式更是顯出獨特的優勢,在效能至上的理念下,使用MMIO可以最大限度滿足日益增長的系統和外設儲存的需要。所以當前其實大多數外設都是採用MMIO的方式。

還有一種方式是把IO埠空間對映到記憶體空間,這樣依然可以通過正常的訪存指令訪問IO埠,但是這種方式下依然受到IO空間大小的制約,可以說並沒有解決實際問題。

但是上述方案只適用於在外設和記憶體進行小資料量的傳輸時,假如進行大資料量的傳輸,那麼IO埠這種以位元組為單位的傳輸就不用說了,IO記憶體雖然進行了記憶體對映,但是其對映的範圍大小相對於大量的資料,仍然不值一提,所以即使採用IO記憶體仍然是滿足不了需要,會讓CPU大部分時間處理繁瑣的對映,極大的浪費了CPU資源。那麼這種情況就引入了DMA,直接記憶體訪問。這種方式的傳輸由DMA控制器控制,CPU給DMA控制器下達傳輸指令後就轉而處理其他的事務,然後DMA控制器就開始進行資料的傳輸,在完成資料的傳輸後通過中斷的方式通知CPU,這樣就可以極大的解放CPU。當然本次討論的重點不在DMA,所以對於DMA的討論僅限於此。

那麼CPU是怎麼訪問這些配置暫存器的呢?要知道配置暫存器指定了裝置儲存暫存器的對映方式以及地址區間,但是配置暫存器本身的訪問就是一個問題。為每個裝置預留IO埠或者IO記憶體都是不現實的。暫且不說x86架構下IO埠地址空間只有區區64K,就是記憶體,雖然現在隨著科技的發展,記憶體空間越來越大,但是也不可能為每個裝置預留空間。那麼折中的方式就是為所有裝置的配置暫存器使用同一個IO埠,(雖然通過同一個埠訪問配置空間,但可以通過配置暫存器中的匯流排號、裝置號、功能號來區別是哪一個邏輯裝置)系統在IO地址空間預留了一段地址就是0xCF8~0xCFF一共八個位元組,前四個位元組做地址埠,後四個位元組做資料埠。CPU訪問某個裝置的配置暫存器時,先向地址埠寫入地址,然後從資料埠讀寫資料。這裡的地址是一個綜合地址,結構如下:

這裡就可以解釋我們匯流排數量和邏輯裝置數量的限制了。在尋找某一個邏輯裝置時,先根據匯流排號找到匯流排,然後根據裝置號找到總線上的某個介面,最後在根據功能號定位某一個邏輯裝置

http://www.cnblogs.com/ck1020/p/5942703.html