1. 程式人生 > >PCI/PCIe基礎——配置空間

PCI/PCIe基礎——配置空間

簡介

PCI/PCIe裝置有自己的獨立地址空間,這部分空間會對映到整個系統的地址空間。

對映地址在BIOS/UEFI下指定(如果有的話,對於使用非BIOS啟動的OS,不清楚),它有兩種型別,一種是MMIO,一種是IO。對於MMIO的訪問,跟訪問記憶體的方式一樣,它從稱為PCIEXBAR的基地址開始,有很大的一段空間,這個PCIEXBAR的值根據不同的平臺可能不同,大致可能值有0xC0000000、0xE0000000等,關於這個值是怎麼使用的後面的章節會講到;對於IO,它是一種比較老的訪問PCI/PCIe裝置的方式,而且佔有的空間相比MMIO非常小,好像只有64K的空間。

PCI/PCIe裝置使用的空間也有兩個部分,一部分稱為配置空間(通過MMIO);另一部分通過配置空間的BAR暫存器指定,是裝置實現功能所需要用到的地址空間(有MMIO也有IO, 不過IO用的比較少了)。

PCI/PCIe配置空間的訪問方式

PCI/PCIe裝置的配置空間通過PCIEXBAR加上裝置的Bus、Device、Fun號的轉換來得到,BDF到地址的轉換關係如下:

/**
  Macro that converts PCI Bus, PCI Device, PCI Function and PCI Register to an
  address that can be passed to the PCI Library functions.

  @param  Bus       PCI Bus number. Range 0..255.
  @param  Device    PCI Device number. Range 0..31.
  @param  Function  PCI Function number. Range 0..7.
  @param  Register  PCI Register number. Range 0..255 for PCI. Range 0..4095
                    for PCI Express.

  @return The encoded PCI address.

**/
#define PCI_LIB_ADDRESS(Bus,Device,Function,Register)   \
  (((Register) & 0xfff) | (((Function) & 0x07) << 12) | (((Device) & 0x1f) << 15) | (((Bus) & 0xff) << 20))

其中的Register是具體要訪問的暫存器。

這是最常用的一種方式,通過將B/D/F轉換成MMIO的地址,之後就可以通過MMIO的方式來訪問,下面是一個例子:

UINT8
EFIAPI
PciExpressRead8 (
  IN      UINTN                     Address
  )
{
  ASSERT_INVALID_PCI_ADDRESS (Address);
  return MmioRead8 ((UINTN) GetPciExpressBaseAddress () + Address);
}

不過這裡有個問題,通過PCI_LIB_ADDRESS得到的並不是實際的系統地址空間,它算是一個偏移,還需要加上一個基地址(就是這個通過函式GetPciExpressBaseAddress()得到的)。在UEFI中這個基地址被設定成一個PCD變數:PcdPciExpressBaseAddress。它的值根據不同平臺可能會不同。

不過重點不是它的值,重點是如何設定這個值,因為只有設定了這個值才能使用上面說的方式來讀寫PCI/PCIe配置空間。

而PCIEXBAR也是要寫到PCI裝置的配置空間中的,它會被寫到B0/D0/F0/R060h這個暫存器(不同平臺可能不同)。

這裡就遇到了一個問題,該通過什麼方式來寫這個值呢?

實際上,需要注意幾點:

1. 上述提供的訪問PCI/PCIe配置空間的方式是PCIe的方式;

2. 早期在沒有PCIe的時候,要訪問配置空間時,使用的是兩個IO埠,CFCh和CF8h,通過往一個埠指定暫存器,另一個埠寫值的方式為指定暫存器賦值。

所以我們要注意,有兩個配置空間的方式:

1. 傳統方式,寫IO埠CFCh和CF8h。只能訪問PCI/PCIe裝置的開始256個位元組(因為PCI裝置的配置空間本來就只有256個位元組);

2. PCIe的方式,就是上面提到的方式,它可以方位4K個位元組的配置空間。

PCI配置空間

由於PCI/PCIe裝置分為Bridge和Agent兩種,所以配置空間也有兩種型別:

其中Agent的配置空間型別稱為Type 00h:

簡單介紹其中的幾個暫存器的意義:

Vendor ID,Device ID:標記了一個裝置的生產廠商和具體的裝置,比如Intel的裝置Vendor ID通常是0x8086,Device ID就需要廠家自定義了,總之能夠識別到具體是哪個裝置就可以了。

Status:裝置狀態字,具體每個BIT的意義見下圖:

Command:裝置狀態字:

Base Address Registers:決定PCI/PCIe裝置空間對映到系統空間具體位置的暫存器,對映方式有兩種,分別是IO和Memory對映:

處理器系統資源分為IO資源和MMIO資源兩種,因此PCI/PCIe空間地址對應也有兩種。

下面是Bridge的配置空間,它的型別被稱為Type 01h:

Type 01h中也有Vendor ID,Device ID,Status,Command等暫存器。

另外需要注意的是這裡的BAR計算得到的系統空間是該橋下掛的所有裝置的系統空間的總和。

另外Subordinate Bus Number、Secondary Bus Number和Primary Bus Number,這些暫存器共同確定了該橋上行和下行的所有Bus號。

PCIe配置空間

PCIe是在PCI基礎上發展的協議,PCIe也有上述的PCI配置空間,並且在此基礎之上進行了擴充套件,其擴充套件形式是通過一種稱為Capability的暫存器塊來完成的。

PCI配置空間的大小是256個位元組,即0x00~0xFF,而PCIe的配置空間擴大到了0x00~0xFFF,下圖是具體的佈局。

在原來的配置空間中,有一個暫存器指定了第一個Capability的位置,而第一個Capability又指定下一個Capability,構成了一串Capability,具體如下圖所示:

各個不同的Capability的作用不同,且不同的裝置有不同的Capability,在這裡不一一介紹了。

其它說明

處理器系統中會為所有的PCI/PCIe裝置留足足夠的空間,但是幾乎沒有系統會滿配,所以很多的配置空間實際上是空的,此時如果去訪問,就會得到全FF,表示裝置不存在。

下面是linux下訪問PCI/PCIe配置空間的一個例子:

注:以上是一個Intel網絡卡的PCI/PCIe配置空間,這是虛擬機器下的結果,真實機器上可能有所不同。