[轉] PCI I/O Protocols

出處:http://iorlvskyo.blogspot.tw/2010/10/pci-io-protocols.html

若要對PCI Device進行讀寫的動作時,可以使用EFI_PCI_IO_PROTOCOL所提供的Function來簡化以前所要做的步驟,以下是PCI IO Protocol的結構:

typedef struct _EFI_PCI_IO_PROTOCOL {
 EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem;
 EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;
 EFI_PCI_IO_PROTOCOL_ACCESS Mem;
 EFI_PCI_IO_PROTOCOL_ACCESS Io;
 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;
 EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem;
 EFI_PCI_IO_PROTOCOL_MAP Map;
 EFI_PCI_IO_PROTOCOL_UNMAP Unmap;
 EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
 EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
 EFI_PCI_IO_PROTOCOL_FLUSH Flush;
 EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation;
 EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes;
 EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
 EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
 UINT64 RomSize;
 VOID *RomImage;
} EFI_PCI_IO_PROTOCOL;

// ++++++++++PCI Configuration Space++++++++++
首先來講如何取得PCI Configuration Space:

以上為PCI Configuration Space配置表Type 0。

先使用LocateHandleBuffer,將所有PCI Device的Handle全都找出來:
gBS->LocateHandleBuffer (ByProtocol, &gEfiPciIoProtocolGuid, NULL, &PciHandleCount, &PciHandleBuffer);

隨後在指定的Handle中將PCI IO Protocol找出來:
gBS->OpenProtocol (PciHandle, &gEfiPciIoProtocolGuid, &PciIo, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

以下可取得PCI Device的四個參數:
UINTN Seg;
UINTN Bus;
UINTN Device;
UINTN Function;
PciIo->GetLocation (PciIo, &Seg, &Bus, &Device, &Function);

若要取得整張配置表可以宣告此union型別變數:
typedef union {
 PCI_TYPE00 Device;
 PCI_TYPE01 Bridge;
} PCI_TYPE_GENERIC;
可用其Header Type來判斷它是Device或Bridge,00h與01h…等。

typedef struct {
 PCI_DEVICE_INDEPENDENT_REGION Hdr;
 PCI_DEVICE_HEADER_TYPE_REGION Device;
} PCI_TYPE00;

typedef struct {
 PCI_DEVICE_INDEPENDENT_REGION Hdr;
 PCI_BRIDGE_CONTROL_REGISTER Bridge;
} PCI_TYPE01;

PCI_DEVICE_INDEPENDENT_REGION、PCI_DEVICE_HEADER_TYPE_REGION、PCI_BRIDGE_CONTROL_REGISTER
這三個結構定義可看Spec。

宣告一個PCI_TYPE_GENERIC變數
PCI_TYPE_GENERIC Pci;
PciIo->Pci.Read (PciIo, EfiPciWidthUint32, 0, sizeof (PCI_TYPE_GENERIC) / sizeof (UINT32), &Pci);
第二個參數可以設定每次所要取得的大小,詳情可看其定義。
第三個參數為起始位址,由於要抓整張表所以便填入0。
第四個參數為總共要抓取的次數。
成功後可一一取值,如:
Pci.Device.Hdr.VendorId;
Pci.Device.Device.SubsystemVendorID;

若要取得配置表的其中一個值,可先算出所在位址,如Status位址在06h而大小是16bits,先宣告一個UINT16的變數Status,代入Pci.Read()。
PciIo->Pci.Read (PciIo, EfiPciWidthUint16, 0, 1, &Status);

如果要取得ClassCode的話,而它是3個8bits整數,所以宣告一個UINT8 ClassCode[3]陣列,再呼叫Pci.Read()。
PciIo->Pci.Read (PciIo, EfiPciWidthUint8, 0, 3, ClassCode);

以下可印出整張表的內容:
UINT8 Content;
for (i = 0x00; i <= 0x0F; ++i) {  for (j = 0x00; j <= 0x0F; ++j) {   Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, ((i * 0x10) + j), sizeof (Content), &Content);
  PrintAt (j * 3, i, L”%02x”, Content);
 }
}

// ++++++++++PCI Base Address Register++++++++++
接下來是Base Address Register的部分,一般PCI Device共有六個BAR,而PCI/PCI Bridge只有兩個BAR,可看Spec。
而BAR又分了IO Space與Memory Space兩種方式,可看其第一個bit決定,0是Memory Space而1是IO Space,也就是基偶之分,EFI也提供了一個結構儲存該BAR的屬性。
typedef struct {
 UINT8 Desc;
 UINT16 Len;
 UINT8 ResType;
 UINT8 GenFlag;
 UINT8 SpecificFlag;
 UINT64 AddrSpaceGranularity;
 UINT64 AddrRangeMin;
 UINT64 AddrRangeMax;
 UINT64 AddrTranslationOffset;
 UINT64 AddrLen;
} EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR;

此時宣告其指標:
EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Resources;
PciIo->GetBarAttributes (PciIo, PciBarIndex, NULL, &Resources);
注意用完Resources後要釋放掉。

Resources->ResType也可判斷BAR的屬性(IO/Memory)。
if (Resources->ResType == 0) {
 PciIo->Mem.Read(PciIo, EfiPciIoWidthUint8, PciBarIndex,0, sizeof (Content), &Content);
} else {
 PciIo->Io.Read(PciIo, EfiPciIoWidthUint8, PciBarIndex, 0, sizeof (Content), &Content);
}
BarIndex按自己所需的BAR代入0~5(P2P Bridge: 0~1)的整數。
至於寫入也是相同方式。

未經允許不得轉載:GoMCU » [轉] PCI I/O Protocols