如何訪問pcie整個4k的配置空間
目前用於訪問PCIe配置空間寄存器的方法需要追溯到原始的PCI規範。為了發起PCI總線配置周期,Intel實現的PCI規範使用IO空間的CF8h和CFCh來分別作為索引和數據寄存器,這種方法可以訪問所有PCI設備的255 bytes配置寄存器。Intel Chipsets目前仍然支持這種訪問PCI配置空間的方法。PCIe規範在PCI規範的基礎上,將配置空間擴展到4K bytes,至於為什麼擴展到4K,具體可以參考PCIe規範,這些功能都需要配置空間。原來的CF8/CFC方法仍然可以訪問所有PCIe設備配置空間的頭255 bytes,但是該方法訪問不了剩下的(4K-255)配置空間。怎麼辦呢?Intel提供了另外一種PCIe配置空間訪問方法。Intel Chipset通過將配置空間映射到內存地址空間,PCIe配置空間可以像對映射範圍內的內存進行read/write一樣來訪問了。這種映射是由北橋芯片來完成的,但是不同芯片的映射方式也是不同的。
1、CF8h/CFCH Method
Intel Chipsets使用IO空間的CF8h/CFCh地址來訪問PCI設備的配置寄存器,該方法同樣可以訪問PCIe設備的頭255配置寄存器。
為了對已知PCI設備發起一個PCI總線配置周期,軟件必須執行以下步驟:
-
PCI設備的總線號必須被填寫到IO地址CF8h的[23:16] bits
-
PCI設備的設備號必須被填寫到IO地址CF8h的[15:11] bits
-
PCI設備的功能號必須被填寫到IO地址CF8h的[10:8] bits
-
需要訪問的寄存器雙字地址必須被填寫到IO地址CF8h的[7:2] bits
-
CF8h的最高位為配置位,該位必須設置為1
-
對於寫操作,將設備的特定信息組合成一個雙字(4bytes)後,寫到CFCh地址
-
對於讀操作,將設備的特定信息組合成一個雙字後,把數據從CFCh讀回來
當執行6或者7步驟時,相應的PCI配置read/write cycle被Created by Intel Chipset,並在需要時傳遞到整個係統。在步驟4配置需要讀寫的寄存器地址時,該空間隻有6位,也就說隻有64個地址可寫,但是PCI配置空間不是256嗎?別急,記得是雙字地址,一個Dword=4 bytes,也就是說4 * 64 = 256,剛好,不是嗎?
2、Memory Mapped Method
PCIe規範為每個PCIe設備添加了更多的配置寄存器,空間為4K,盡管CF8h/CFCh方法仍然能夠訪問lower 255 bytes,但是必須提供另外一種方法來訪問剩下的4K range寄存器。Intel的解決方案是使用了預留256MB內存地址空間,對這段內存的任何訪問都會發起PCI 配置cycle。但是為什麼是256MB???聽我慢慢解釋給大家聽:猶豫4K的配置空間是directly mapped to memory的,那麼PCIe規範必須保證所有的PCIe設備的配置空間占用不同的內存地址,按照PCIe規範,支持最多256個buses,每個Bus支持最多32個PCI devices,每個device支持最多8個function,也就是說:占用內存的最大值為:256 * 32 * 8 * 4K = 256MB。
這段256MB的內存區將根據intel chipset的不同,可以映射到係統內存映射範圍內的任何位置,一般北橋芯片都會有一個寄存器來指明PCI配置空間的內存映射地址,它叫PCIe Configuration Register Base Address Register (BAR),如下圖:
當軟件訪問指定PCIe設備的配置寄存器時,必須正確計算該寄存器映射到內存的具體地址,那麼怎麼計算呢,參考上圖我們可以知道,busNo=0,deviceNo=0,funcNo=0的地址剛好是BAR,一條總線占用的最大空間計算如下:
SIZE_PER_BUS = 4K * 32 * 8 = 256K = 1M = 100000h
SIZE_PER_DEVICE = 4K * 8 = 8000h
SIZE_PER_FUNC = 4K = 1000h
訪問總線號為busNo,設備號為DevNo,功能號為funcNo的offset寄存器的計算公式是:
Memory Address = PCIe Configuration Register Base Address Register (BAR)
+ busNo * SIZE_PER_BUS
+ devNo * SIZE_PER_DEVICE
+ funcNo * SIZE_PER_FUNC
+ offset
For example, to access the following configuration register:
• PCI Express Configuration Register F0000000h
• Bus Number 15h
• Device Number 00h
• Function Number 05h
• Register Offset 84h
Memory Address = F0000000h + 15h * 100000h + 00h * 8000h + 05h * 1000h + 84h
= F1505084h
現在我們可以從已知的busNo,devNo,funcNo和offset來計算映射後的內存地址,那麼反過來,給定的內存地址,我們想知道這個地址的busNo, devNo, funcNo和offset信息,可以嗎?當然可以,計算公式如下:
busNo = (Memory Address - BAR) / SIZE_PER_BUS;
devNo = (Memory Address - BAR - busNo * SIZE_PER_BUS) / SIZE_PER_DEVICE;
funcNo = (Memory Address - BAR - busNo * SIZE_PER_BUS
- devNo * SIZE_PER_DEVICE) / SIZE _PER_FUNC;
offset = Memory Address - BAR - busNo * SIZE_PER_BUS - devNo * SIZE_PER_DEVICE
- funcNo * SIZE_PER_FUNC;
又或offset = Memory Address & 0x0FFFh;(為什麼是0x0FFFh?自己想想啦)
想起來了麼?因此PCIe的配置空間大小就是4K啊。
3、芯片組的異同
上麵說的BAR,也就是PCI配置空間寄存器映射到內存的基地址寄存器,在intel chipset中的實現方式也千差萬別。在前期的intel chipset中,該寄存器被包含在芯片組(MCH ,GMCH)的內存控製器部分。
另外,由於被PCIe配置空間占用的256M內存空間會屏蔽掉DRAM使用該段內存區,大部分的Intel Chipset允許BIOS來配置該空間大小,因此在實際應用中,一般就應用前麵幾個總線號,BIOS通過檢測PCIe總線的擴展深度來動態設置該映射內存區的大小,比如PM965芯片組,如果配置軟件檢測係統使用不大於64的總線號,那麼該軟件將編程內存映射大小為64M,剩下的(256M-64M = 192M)留給DRAM。
4、PCIe配置空間的內存映射對32bit係統的影響
由於PCIe配置空間占用了256M內存空間,而且該被占用空間對DRAM來說是不可用的,這意味著256M空間消失於係統內存,這在32bit係統中更為明顯。
比如,在32 bit WINxp中,理論上可以訪問到的內存是4G,如果4G空間都被DRAM給占用,由於PCIe的存在,被PCIe占用的那部分內存空間對OS來說是不可用的,莫名的消失了最多256M內存,這也是大部分Intel Chipset允許BIOS來配置該空間大小的原因。
在64 bit 係統中,不存在這個問題,因為係統可以訪問超過4G的內存空間,Intel Chipset會包含控製邏輯把該PCIe的內存映射到above 4G,這樣跟DRAM就沒有衝突。在64bit係統中,不可能使用2的64次方的內存吧。哈哈,總會沒有使用到的內存空間。
5、訪問PCIe配置空間的C轉換代碼
//**********************************************************************
unsigned long PCIeBase = 0xF0000000UL;
unsigned long FinalAddress;
unsigned long Bus = 0;
unsigned long Device = 0;
unsigned long Function = 0;
unsigned long Register = 0;
//**********************************************************************
void Convert_to_Memory()
{
FinalAddress = PCIeBase +
(Bus*0x100000UL) +
(Device*0x8000) +
(Function*0x1000) +
Register;
}
//**********************************************************************
void Convert_to_Register()
{
Bus = (FinalAddress-PCIeBase) / (0x100000UL);
Device = (FinalAddress-PCIeBase - (Bus*0x100000UL)) / (0x8000);
Function = (FinalAddress-PCIeBase - (Bus*0x100000UL) -
(Device*0x8000)) / (0x1000);
Register = (FinalAddress) & (0x00000FFF)
}
最後更新:2017-04-03 12:56:43