天天看點

如何通路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配置寄存器。

如何通路pcie整個4k的配置空間

    為了對已知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整個4k的配置空間

    當軟體通路指定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)

繼續閱讀