目前用于通路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)