我们在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等。
PCI设备使用IO空间的CF8(Configuration Address Port)/CFC(Configuration Data Port)地址来访问配置空间。
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配置空间。
同样访问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则表示到了链表的结尾。
#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。
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/