PCIe支援三個地址空間,與PCI中的三個地址空間完全相同:
n 配置空間(Configuration)
n 記憶體地址空間(Memory)
n IO地址空間(IO)
4.1.1 配置空間(Configuration Space)
如我們在Chapter 1中所討論的,配置空間是由PCI引入的,軟體透過配置空間就可以用一種標準化的方法來對裝置的狀態進行控制和檢查。PCIe對PCI軟體具有向後相容性,所以PCIe中仍然支援配置空間,並且支援它的原因也和PCI一樣,即用一種標準化的方法來對裝置的狀態進行控制和檢查。更多關於配置空間的資訊(目的、如何訪問、大小、內容等等)請參閱Chapter 3。
儘管配置空間出現的意義是來放置和保持一些標準化的結構(PCI-defined Header、Capability Structure能力結構等等),但是PCIe裝置也會經常的將一些裝置特定(device-specific)的暫存器對映到裝置自身的配置空間中。在這種情況下,對映到配置空間的裝置特定暫存器常用來作為控制暫存器(control)、狀態暫存器(status)或者指標暫存器(pointer),而不是用來儲存資料。
4.1.2 記憶體和IO地址空間(Memory and IO Address Spaces)
4.1.2.1 整體說明(General)
在PC的早期階段,IO裝置的內部暫存器/儲存是透過IO地址空間(IO Address,它是由intel定義的)來訪問的。然而,由於IO地址空間的一些限制和不良影響(在這裡我們暫不討論),這種地址空間很快就失去了軟體和硬體廠商的青睞。這使得IO裝置的內部暫存器/儲存被對映到了memory地址空間(Memory Address Space,記憶體地址空間),一般被稱作記憶體對映IO或者簡稱MMIO(Memory-Mapped IO)。然而,因為早期的軟體是使用IO地址空間來訪問IO裝置的內部暫存器/儲存的,所以實際中常用的做法是將一套裝置特定暫存器既對映到記憶體地址空間,也對映到IO地址空間。這使得新的軟體可以使用記憶體地址空間,也就是透過MMIO,來對裝置的內部位置進行訪問。而傳統(舊)的軟體也依然可以執行,因為它依然可以透過IO地址空間來訪問裝置的內部暫存器。
對於更加新型的裝置,如果它們不再依賴老舊的傳統軟體並且也不需要考慮對傳統操作的相容問題,那麼它們一般只要將內部暫存器/儲存對映到記憶體地址空間(MMIO)即可,而不需要請求IO地址空間來進行對映。實際上,PCIe協議中不鼓勵使用IO地址空間,支援這種操作僅僅是因為一些傳統遺留問題,並有可能在未來新版本的協議中被棄用。
如圖 4‑1所示,圖中展示了一種通用的記憶體和IO的對映。記憶體對映的大小是這個系統可使用地址範圍(通常由CPU的可定址範圍決定)的一個函式。PCIe中IO對映的大小被限制為32bits(4GB),雖然其實很多使用Intel相容(x86)處理器的計算機中僅有低16bit(64KB)被使用。PCIe可以支援的記憶體地址大小達到64bit。
圖 4‑1給出的對映示例僅展示了EP所宣告使用的MMIO和IO,但是這種能力並不是EP所獨有的。它對於Switch和RC來說也是一種很普通的能力,Switch和RC內部也存在著可以透過MMIO和IO地址來進行訪問的裝置特定暫存器。
4.1.2.2 可預取的與不可預取的記憶體空間的對比(Prefetchable vs. Non-Prefetchable Memory Space)
圖 4‑1展示了被PCIe裝置宣告的兩種不同型別的MMIO:可預取MMIO(Prefetchable MMIO,P-MMIO)和不可預取MMIO(Non-Prefetchable MMIO,NP-MMIO)。瞭解這兩種記憶體空間之間的區別是十分重要的。可預取空間有兩個意義十分明確的屬性:
n 讀操作不存在副作用。(Reads do not have side effects)
n 允許寫合併(Write merging is allowed)
將MMIO的一個區域定義為可預取的,這樣就可以推測性的提前獲取該區域中的資料,以預測Requester在不久的將來可能需要比當前實際請求更多的資料。之所以這種小型的Caching(Minor Caching)資料操作是安全的,是因為讀取這些資料並不會改變目標裝置的任何狀態資訊,也就是說讀取某個位置並不會帶來副作用。例如,如果一個Requester請求從一個地址中讀取128byte資料,那麼Completer可以在給出此次請求的128byte之後也預取出下一個128byte,而當下一個128byte被請求時資料早已經被Completer從記憶體空間中預取出來了,這樣就提高了效能。然而如果Requester再也沒有請求額外的資料,那麼Completer就需要將預取的資料清除掉,釋放自身的Buffer空間。如果讀取資料的行為改變了對應地址上的數值(或者有其他的什麼副作用),那麼就將會無法恢復被清除的資料了。然而對於可預取空間來說,讀取行為並沒有任何副作用,因此它總是可以回退並且在稍後也能獲得原始的資料,因為原始的資料一直都不變的存放在那裡。
你也許會想知道什麼樣的記憶體空間會存在讀取操作的副作用。舉一個例子,一個記憶體對映的狀態暫存器,它被設計成在讀取時自動清除自己,這樣可以減少程式設計師在讀取這個狀態後還需要額外步驟來清除這些位元的操作。
做這種可預取和不可預取的區分,對於PCI的意義要大於PCIe,因為PCI匯流排協議中的事務並沒有包含傳輸量大小的資訊。如果裝置都在同一條匯流排上交換資料,那麼沒有傳輸量大小的資訊也不是什麼問題,因為在同一匯流排上會有實時的握手訊號來指示Requester什麼時候收到了足夠的資料完成了事務。但是如果資料的傳輸需要跨過一個Bridge來到另一條匯流排,那麼情況就不像剛才一樣簡單了,因為對於跨到不同匯流排的讀操作來說,Bridge在收集另一條匯流排上的資料時需要去猜測傳輸資料總量。如果猜錯了傳輸量的大小則會增加延時而降低效能,因此在這種情況下若擁有預取的許可權則對提升效能會有不小的幫助。這就是為什麼將記憶體空間指定為可預取的概念在PCI中很有好處。由於PCIe請求中包含了傳輸量大小的資訊,所以可預取空間並不像以前在PCI中那樣引人關注了,但是為了向後相容性,PCIe還是繼承了這種概念。
圖 4‑1通用的Memory與IO的地址對映
圖 4‑1通用的Memory與IO的地址對映
4.2 基地址暫存器BARs(Base Address Registers)
4.2.1 整體說明(General)
一個系統中的每個裝置對地址空間的數量和型別可能都有不同的要求。例如,一個裝置可能有256byte大小的內部暫存器/儲存需要透過IO地址空間來訪問,而另一個裝置中可能有16KB的內部暫存器/儲存需要透過MMIO來訪問。
基於PCI的裝置不允許自己來決定哪些地址可以用來訪問它們內部的位置,做這些決定是系統軟體負責的工作(例如BIOS和作業系統核心)。因此裝置必須為系統軟體提供一個途徑用來確定裝置對地址空間的需求。一旦軟體知道了裝置對地址空間的需求是什麼樣的,並假設這個需求是可以被滿足的,軟體就會給對應的裝置分配一段可用的地址範圍和相應的地址空間型別(IO、NP-MMIO、P-MMIO)。
這些都是透過配置空間Header中的基地址暫存器BARs(Base Address Registers)來完成的。如圖 4‑2所示,一個Type 0 Header擁有6個可用的BAR(每個大小為32bit),而一個Type 1 Header只擁有2個BAR。Type 1 Header是存在於所有Bridge裝置中的,這意味著每個Switch埠和RC埠都會擁有1個Type 1 Header。Type 0 Header只存在於非Bridge裝置中,例如EP。關於這裡的一個例子可以參閱圖 4‑3。
圖 4‑2配置空間中的BARs
系統軟體必須首先確定裝置所需地址空間的大小和型別。裝置的設計者知道裝置中需要透過IO或者MMIO訪問的內部暫存器/儲存的總體大小。裝置的設計者還知道當這些暫存器被訪問時裝置將會如何工作(例如讀取操作是否有副作用)。這將決定裝置所需要的是可預取MMIO(讀取操作無副作用)還是不可預取MMIO(讀取操作有副作用)。知道了這個資訊之後,裝置設計者將BARs的低位bit固定為某個值,以此來指示需要請求的地址空間的大小和型別。
BARs的高位bit是軟體可進行寫入的。一旦系統軟體透過檢查BARs的低位bit確定了裝置所請求的地址空間的大小和型別,系統軟體就會將分配給這個裝置的地址範圍的基地址寫入BAR中。由於一個EP(使用Type 0 Header)擁有6個BARs,它最多可以請求6個不同的地址空間。然而,一般實際中請求6個不同的地址空間並不常見。絕大多數裝置會請求1到3個不同的地址範圍。
並不是所有的BARs都需要被實現。如果一個裝置並不需要使用所有的BAR來對映自己的內部暫存器,那麼多餘的BARs將會被固定為全0,以此來通知軟體這些BARs並沒有被實現。
一旦BARs被程式設計寫入(programmed),裝置內的內部暫存器或者本地記憶體(local memory)就可以透過BARs中所寫入的地址範圍進行訪問。任何時候,當裝置發現一個請求事務的地址是對映到自己的一個BAR時,它就會接收這個請求事務,因為它自己就是這個請求的目標裝置。
圖 4‑3 PCIe裝置中對Type 0、Type 1 Header的用法
4.2.2 BAR示例1——32bit記憶體地址空間請求
圖 4‑4展示了設定建立一個BAR的基礎步驟,在本例中,要請求一個4KB大小的不可預取記憶體(NP-MMIO,non-prefetchable memory)。在圖中,展示了BAR在配置過程中的三個節點:
\1. 在圖 4‑4中的(1),我們可以看到BAR處於未初始化的狀態。裝置的設計者已經將低位bit固定為一個數值,來指示需要的memory的大小和型別,但是高位bit(可寫可讀的)則仍然是用X來表示,這代表它們的值還未知。系統軟體將會首先把每個BAR都透過配置寫操作來將可寫入的bit寫為全1(當然,被固定的低位bit不會受到配置寫操作的影響)。在圖 4‑4的(2)中展示的BAR就是處於第二階段的樣子,除了被固定的低位bit以外,所有的bit都被寫為1。
寫為全1這個操作是為了確定最低位的可寫入的bit(least-significant writable bit)是哪一位,這個bit的位置指示了需要被請求的地址空間的大小。在本例中,最低位的可寫入的bit為bit 12,因此這個BAR需要請求2的12次方(或者說是4KB)的地址空間。如果最低位的可寫入的bit為bit 20,那麼這個BAR就要請求2的20次方(1MB)的地址空間。
\2. 在軟體將BARs中所有可寫bit都寫為1後,軟體將從BAR0開始,依次讀取每個BAR的數值,以此來確定各個BAR要請求的地址空間的大小和型別。表 4‑1中總結了本例中對BAR0進行配置讀的結果。
\3. 這個過程中的最後一步就是系統軟體為BAR 0分配一個地址範圍,因為對於軟體來說現在已經知道了BAR 0請求的地址空間的大小和型別。圖 4‑4的(3)中展示了BAR處於第三階段的樣子,此時系統軟體已經將一塊地址區域的起始地址寫入了BAR 0中。在本例中,這個起始地址為F900_0000h。
到這裡為止,對BAR 0的配置就完成了。一旦軟體啟用了命令暫存器(Command register,偏移地址04h)中的記憶體地址譯碼(memory address decoding),那麼這個裝置就會接受所有地址在F900_0000h-F900_0FFFh(4KB大小)範圍內的memory請求。
表 4‑1對BAR 0寫入全1後再讀取BAR 0時的結果
圖 4‑4設定建立32bit不可預取記憶體的BAR