访问PCIe配置空间寄存器

Peter 于 2021-02-07 发布

我们在PCIe 体系结构简介提到在PCI 的配置空间,其中前64Bytes被称为基本配置空间,地址范围为0x00~0x3F,这64字节是所有PCI设备必须支持的。此外PCI/PCIX 和PCIe还扩展了0x40~0xFF这段配置空间用来存放MSI/MSIX以及电源管理相关的Capability结构。PCIe总线为了兼容PCI设备,在几乎完整的保留了PCI总线的前256字节的配置空间的基础上将配置空间扩展到了4KB,用于支持一些PCIe总线中的一些独有的新功能如AER, Virtual Channel,Device Serial Number, Power Budgeting等。

CFGSPACE

PCI设备使用IO空间的CF8(Configuration Address Port)/CFC(Configuration Data Port)地址来访问配置空间。

CF8

PCIe将配置空间扩展到4KB,原来CF8/CFC的访问方式仍然可以访问所有PCIe配置空间的前256Byte,比如我们想访问4/0/0 (B/D/F ) 的Vendor ID,我们则需要:

#define PCI_CONFIGURATION_ADDRESS_PORT  0xCF8
#define PCI_CONFIGURATION_DATA_PORT     0xCFC

UINT32
EFIAPI
IoRead32 (
  IN      UINTN                     Port
  )
{
  UINT32 Data;

  ASSERT ((Port & 3) == 0);

  __asm {
    mov dx, word ptr [Port]
    in  eax, dx
    mov dword ptr [Data], eax
  }
  
  return Data;
}

UINT8
EFIAPI
PciCf8Read8 (
  IN      UINTN                     Address
  )
{
  BOOLEAN  InterruptState;
  UINT32   AddressPort;
  UINT8    Result;
  
  ASSERT_INVALID_PCI_ADDRESS (Address, 0);
  InterruptState = SaveAndDisableInterrupts ();
  AddressPort = IoRead32 (PCI_CONFIGURATION_ADDRESS_PORT);
  IoWrite32 (PCI_CONFIGURATION_ADDRESS_PORT, PCI_TO_CF8_ADDRESS (Address));
  Result = IoRead8 (PCI_CONFIGURATION_DATA_PORT + (UINT16)(Address & 3));
  IoWrite32 (PCI_CONFIGURATION_ADDRESS_PORT, AddressPort);
  SetInterruptState (InterruptState);
  return Result;
}

VendorId = PciCf8Read8 (0x80040000);

为了访问完整的4KB配置空间,PCIe引入了所谓的增强配置空间访问机制Enhanced Configuration Access Mechanism,它通过将配置空间映射到MMIO空间,使得对配置空间的访问就像对内存一样,也因此可以访问完整的4KB配置空间。

ECAM

同样访问4/0/0 Vendor id, 参考代码如下:

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

UINT8
EFIAPI
MmioRead8 (
  IN      UINTN                     Address
  )
{
  UINT8                             Value;

  MemoryFence ();
  Value = *(volatile UINT8*)Address;
  MemoryFence ();

  return Value;
}

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

UINT8
EFIAPI
PciRead8 (
  IN      UINTN                     Address
  )
{
  return PciExpressRead8 (Address);
}

VendorId = PciRead8 (PCI_LIB_ADDRESS (4, 0, 0, 0x00));

PCI-X和PCIe要求设备必须支持Capability结构。在总线的基本配置空间0x40~0xFF中包含了Capability Pointer的寄存器,它存放的是Capabilities结构链表的头指针,在一个PCIe设备中可能存在多个Capability结构,这些寄存器组成一个链表。每个Capability结构都有一个唯一的ID号,和一个指针。指针指向下一个Capability结构,如果为0则表示到了链表的结尾。

CAPLST

#define EFI_PCI_CAPABILITY_ID_PCIEXP  0x10

Offset = (UINT8)PciRead32 (PCI_LIB_ADDRESS (4, 0, 0, PCI_CAPBILITY_POINTER_OFFSET));
while ( Offset ) {
  Temp32 = PciRead32 (PCI_LIB_ADDRESS (4, 0, 0, (UINTN)Offset));
  if ( ( Temp32 & 0xFF ) == EFI_PCI_CAPABILITY_ID_PCIEXP ) {
    break;
  } else {
    Offset = (UINT8)(Temp32 >> 8);
  }
}

Offset += 8;

Temp16 = PciRead16 (PCI_LIB_ADDRESS (4, 0, 0, (UINTN)Offset));
Temp16 &= ~(BIT5+BIT6+BIT7);
Temp16 |= (UINT16)(MaxPayload<<5);
PciWrite32 ( PCI_LIB_ADDRESS (4, 0, 0, (UINTN)Offset), Temp16);

PCI Express Extended Capabilities 结构存放在PCI配置空间0x100之后的位置, 该结构是PCIe独有的。跟常规的Capability结构类似,它也包含一个ID和指针, 指针指向下一个Extended Capability。其中第一个Capability结构的基地址为0x100。如果PCIe设备不含有PCI Express Extended Capabilities结构,则0x100指向的结构中,ID为0xFFFF,而Next Capability Offset字段为0x0。

EXTCAP

ExCapOffset = EFI_PCIE_CAPABILITY_BASE_OFFSET;

while (TRUE) { 
  data32 = PciRead32 (PCI_LIB_ADDRESS (Bus, Device, Function, ExCapOffset));
  if ((data32 & EXCAP_MASK) == ExtCapabilityId) {
    break;  
  }
  ExCapOffset = (UINT16)(data32 >> 20);  
}

参考:

System Address Map Initialization in x86/x64 Architecture Part 2: PCI Express-Based Systems: https://resources.infosecinstitute.com/topic/system-address-map-initialization-x86x64-architecture-part-2-pci-express-based-systems/?utm_source=tuicool&utm_medium=referral

《PCI Express Technology》

《PCI Express 体系结构导读》

BasePciCf8Lib: https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BasePciCf8Lib/

BasePciExpressLib: https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BasePciLibPciExpress/