轉載: [http://www.ssdfans.com]
轉:http://www.ssdfans.com/blog/2017/08/03/%e8%80%81%e7%94%b7%e5%ad%a9%e8%af%bbpcie%e4%b9%8b%e5%85%ad%ef%bc%9a%e9%85%8d%e7%bd%ae%e5%92%8c%e5%9c%b0%e5%9d%80%e7%a9%ba%e9
每個PCIe裝置,有這麼一段空間,Host軟體可以讀取它獲得該裝置的一些資訊,也可以通過它來配置該裝置,這段空間就叫做PCIe的配置空間。不同於每個裝置的其它空間,PCIe裝置的配置空間是協議規定好的,哪個地方放什麼內容,都是有定義的。PCI或者PCI-X時代就有配置空間的概念,那時的配置空間如下:
整個配置空間就是一系列暫存器的集合,其中Type 0是Endpoint的配置,Type 1是Bridge(PCIe時代就是Switch)的配置,都由兩部分組成:64 Bytes的Header+192Bytes的Capability結構,後者是裝置告訴Host它有多牛逼,都會什麼絕活。
進入PCIe時代,PCIe能耐更大,192 Bytes不足以羅列它的絕活。為了保持後向相容,又要不把絕活落下,怎麼辦?很簡單,我擴充套件後者的空間,整個配置空間由256 Bytes擴充套件成4KB,前面256 Bytes保持不變:
PCIe有什麼能耐(Capability)我們不看,我們先挑軟柿子捏,先看看只佔64 Bytes的Configuration Header。
像Device ID,Vendor ID,Class Code和Revision ID,是隻讀暫存器,PCIe裝置通過這些暫存器告訴Host軟體,這是哪個廠家的裝置、裝置ID是多少、以及是什麼型別的(網絡卡?顯示卡?橋?)裝置。
其它的我們暫時不看,我們看看重要的BAR(Base Address Register)。
對Endpoint Configuration(Type 0),提供了最多6個BAR,而對Switch(Type 1)來說,只有2個。BAR是幹什麼用的?
每個PCIe裝置,都有自己的內部空間,這部分空間如果開放給Host(軟體或者CPU)訪問,那麼Host怎樣才能往這部分空間寫入資料,或者讀資料呢?
我們知道,CPU只能直接訪問Host記憶體(Memory)空間(或者IO空間,我們不考慮),不對PCIe等外設直接操作。怎麼辦?記得皇帝身邊那個有根的太監嗎?Root Complex,RC。RC可以為CPU分憂。
解決辦法是:CPU如果想訪問某個裝置的空間,由於它不能(或者不屑)親自跟那些PCIe外設打交道,因此叫太監RC去辦。比如,如果CPU想讀PCIe外設的資料,先叫RC通過TLP把資料從PCIe外設讀到Host記憶體,然後CPU從Host記憶體讀資料;如果CPU要往外設寫資料,則先把資料在記憶體中準備好,然後叫RC通過TLP寫入到PCIe裝置。完美!
上圖例子中,最左邊虛線的表示CPU要讀Endpoint A的資料,RC則通過TLP(經歷Switch)資料互動獲得資料,並把它寫入到系統記憶體中,然後CPU從記憶體中讀取資料(紫色箭頭所示),從而CPU間接完成對PCIe裝置資料的讀取。
具體實現就是上電的時候,系統把PCIe裝置開放的空間(系統軟體可見)對映到記憶體空間,CPU要訪問該PCIe裝置空間,只需訪問對應的記憶體空間。RC檢查該記憶體地址,如果發現該記憶體空間地址是某個PCIe裝置空間的對映,就會觸發其產生TLP,去訪問對應的PCIe裝置,讀取或者寫入PCIe裝置。
一個PCIe裝置,可能有若干個內部空間(屬性可能不一樣,比如有些可預讀,有些不可預讀)需要對映到記憶體空間,裝置出廠時,這些空間的大小和屬性都寫在Configuration BAR暫存器裡面,然後上電後,系統軟體讀取這些BAR,分別為其分配對應的系統記憶體空間,並把相應的記憶體基地址寫回到BAR。(BAR的地址其實是PCI匯流排域的地址,CPU訪問的是儲存器域的地址,CPU訪問PCIe裝置時,需要把匯流排域地址轉換成儲存器域的地址。)
如上圖例子,一個Native PCIe Endpoint,只支援Memory Map,它有兩個不同屬性的內部空間要開放給系統軟體,因此,它可以分別對映到系統記憶體空間的兩個地方;還有一個Legacy Endpoint,它既支援Memory Map,還支援IO Map,它也有兩個不同屬性的內部空間,分別對映到系統記憶體空間和IO空間。
來個例子,看一下一個PCIe裝置,系統軟體是如何為其分配對映空間的。
上電時,系統軟體首先會讀取PCIe裝置的BAR0,得到資料:
然後系統軟體往該BAR0寫入全1,得到:
BAR暫存器有些bit是隻讀的,是PCIe裝置在出廠前就固定好的bit,寫全1進去,如果值保持不變,就說明這些bit是廠家固化好的,這些固化好的bit提供了這塊內部空間的一些資訊:
怎麼解讀?低12沒變,表明該裝置空間大小是4KB(2的12次方),然後低4位表明了該儲存空間的一些屬性(IO對映還是記憶體對映,32bit地址還是64bit地址,能否預取?做過微控制器的人可能知道,有些暫存器只要一讀,資料就會清掉,因此,對這樣的空間,是不能預讀的,因為預讀會改變原來的值),這些都是PCIe裝置在出廠前都設定好的,提供給系統軟體的資訊。
然後系統軟體根據這些資訊,在系統記憶體空間找到這樣一塊地方來對映這4KB的空間,把分配的基地址寫入到BAR0:
從而最終完成了該PCIe空間的對映。一個PCIe裝置可能有若干個內部空間需要開放出來,系統軟體依次讀取BAR1,BAR2。。。,直到BAR5,完成所有內部空間的對映。
上面主要講了Endpoint的BAR,Switch也有兩個BAR,今天不打算講,下節講TLP路由,再回過頭來講。繼續說配置空間。
前面說每個PCIe裝置都有一個配置空間,其實這樣說是不準確的,而是每個PCIe裝置至少有一個配置空間。一個PCIe裝置,它可能具有多個功能(function),比如既能當硬碟,還能當網絡卡。每個功能對應一個配置空間。
在一個PCIe拓撲結構裡,一條匯流排下面可以掛幾個裝置,而每個裝置可以具有幾個功能,如下所示:
因此,在整個PCIe系統中,只要知道了Bus+Device+Function,就能找到對應的Function。定址基本單元是功能(function),它的ID就由Bus+Device+Function組成 (BDF)。一個PCIe系統,可以最多有256條Bus,每條Bus上可以掛最多32個Device,而每個Device最多又能實現8個Function,而每個Function對應著4KB的配置空間。上電的時候,這些配置空間都是需要對映到Host的記憶體空間,因此,需要佔用記憶體空間是:256*32*8*4KB =256MB。在這個動輒4GB、8GB記憶體的時代,256MB算不了什麼。
系統軟體是如何讀取Configuration空間呢?不能通過BAR中的地址,為什麼?別忘了BAR是在Configuration中的,你首先要讀取Configuration,才能得到BAR。前面不是系統為所有可能的Configuration預留了256MB記憶體空間嗎?系統軟體想訪問哪個Configuration,只需指定相應Function對應的記憶體空間地址,RC發現這個地址是Configuration對映空間,就會產生相應的Configuration Read TLP去獲得相應Function的Configuration。
再回想一下前面介紹的Configuration Read TLP的Header格式:
Bus Number + Device + Function就唯一決定了目標裝置; Ext Reg Number + Register Number相當於配置空間的偏移。找到了裝置,然後指定了配置空間的偏移,就能找到具體想訪問的配置空間的某個位置。
結束前,強調一下,只有RC才能發起Configuration的訪問請求,其他裝置是不允許對別的裝置進行Configuration讀寫的。