為了發起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)
}