PCI总线
PCI 总线介绍
外围部件互连总线PCI (Peripheral Component Interconnect) 总线,是一种先进的高性能32/64 位地址数据复
用局部总线,可同时支持多组外围设备,为中央处理器与高速外围设备提供了一座沟通的桥梁,是现在PC
领域中流行的总线。本文重点介绍UEFI 规范如何支持PCI 总线,以及UEFI 固件中PCI 总线驱动的实现 。
BIOS 对于PCI 总线的支持包括以下三个方面:
- 提供分配PCI 设备资源的协议(Protocol)
- 提供访问PCI 设备的协议(Protocol)。
- 枚举PCI 总线上的设备以及分配设备所需的资源 。
UEFI BIOS 如何支持PCI 总线及设备
UEFI BIOS 提供了两个主要的模块来支持PCI 总线,一个是PCI Host Bridge 控制器驱动,另一个是
PCI 总线驱动。
PCI Host Bridge 控制器驱动是跟特定的平台硬件绑定的,根据系统实际IO 空间和memory map, 为PCI
设备指定I/O 空间和Memory 空间的范围,并且产生PCI Host Bridge Resource Allocation 协议(protocol)
供PCI 总线驱动使用。该驱动还对HostBridge控制器下所有RootBridge 设备产生句柄(Handle), 该句柄上
安装了PciRootBridgeProtocol . PCI 总线驱动则利用PciRootBridgeIo Protocol 枚举系统中所有PCI 设备,发
现并获得PCI 设备的Option Rom, 并且调用PCI Host Bridge Resource Allocation 协议对PCI Host
Bridge Controller 进行编程。
针对 UEFI 规范定义了PCI Root Bridge I/O 协议(Protocol),该协议抽象了访问RootBridge设备
下所有PCI 设备的接口。PCI HostBridge Controller 产生一个或者多个PCI RootBridge设备,
PCI RootBridge设备又产生了PCI Local Bus. Host Bridge controller 是一个计算机北桥上的硬件
组件,通过它可以访问共享PCI I/O 和Memory 空间和配置空间进行访问。它支持对设备进行DMA
操作。PCI 设备驱动不会使用PCI Root Bridge I/O 协议访问PCI 设备,而是会使用PCI总线驱动
为PCI 设备产生的PCI IO Protocol 来访问PCI IO/MEMORY 空间和配置空间,PCI Root Bridge
I/O 协议是安装在RootBridge 设备的句柄上(handle), 同时该handle 上也会有表明RootBridge设备
的DevicePath 协议(Protocol) , 如下图所示:
下图是一个简单的PCI 系统,该系统有一个PCI HostBridge 设备和一个Root Bridge 设备,
RootBridge 设备又产生了PCI 总线。UEFI BIOS 对该系统会产生一个PCI Root Bridge I/O Protocol
根据实际中不同的PCI 系统设计,一个HostBridge 设备下面可以有多个RootBridge 设备。如果
系统中有n个Root Bridge 设备,则有n个PCI Root Bridge I/O Protocol 实例。
对于一个RootBridge设备下的PCI 总线上的所有的PCI 设备,它们必然会共享PCI I/O 和Memory 空间。
HostBridgeController 驱动产生了两个Handle
一个是RootBridge Handle,该RootBridge handle 表明当前HostBridge 下面某一个Root Bridge 设备。
在它上面安装了PciRootBridgeIoProtocol 和DevicePathProtocol.
另一个是HostBridge handle , 在它上面安装了PciHostBridgeResourceAllocationProtocol,统一管理
和分配 HostBridge 下所有PCI 设备的资源。
PCI Host Bridge 控制器驱动
PciHostBride 用来初始化PCI HostBridge 控制器,是PCI 总线驱动的基础。PciHostBridge 驱动
包括以下两个方面:
提供管理和分配PCI 设备资源的协议(PciHostBridgeResourceAllocationProtocol)
提供访问PCI 设备的配置空间,MMIO/IO 空间的协议(PciRootBridgeIoProtocol).
NotifyPhase 函数
提供了PCI总线驱动枚举和资源分配中的钩子函数,可以插入任何平台实现相关的代码。例如当PCI总线
如当PCI 总线驱动将要为某个RootBridge 设备下所有PCI 设备分配资源时,PCI 总线驱动调用
EfiPciHostBridgeAllocateResources 通知PciHostBridge 驱动分配总的内存空间,供PCI 总线驱动使用。
typedef struct {
UINTN Signature;
EFI_HANDLE Handle;
LIST_ENTRY RootBridges;
BOOLEAN CanRestarted;
EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL ResAlloc;
} PCI_HOST_BRIDGE_INSTANCE;
从这个结构体可以看出,host bridge 下面挂着root bridge.
已知
EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL ResAlloc; 的地址, 就可以算出
HostBridge 的地址。
StartBusEnumeration 函数
for (Link = GetFirstNode (&HostBridge->RootBridges)
; !IsNull (&HostBridge->RootBridges, Link)
; Link = GetNextNode (&HostBridge->RootBridges, Link)
) {
RootBridge = ROOT_BRIDGE_FROM_LINK (Link);
if (RootBridgeHandle == RootBridge->Handle) {
*Configuration = AllocatePool (sizeof (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR) + sizeof (EFI_ACPI_END_TAG_DESCRIPTOR));
if (*Configuration == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) *Configuration;
Descriptor->Desc = ACPI_ADDRESS_SPACE_DESCRIPTOR;
Descriptor->Len = sizeof (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR) - 3;
Descriptor->ResType = ACPI_ADDRESS_SPACE_TYPE_BUS;
Descriptor->GenFlag = 0;
Descriptor->SpecificFlag = 0;
Descriptor->AddrSpaceGranularity = 0;
Descriptor->AddrRangeMin = RootBridge->Bus.Base;
Descriptor->AddrRangeMax = 0;
Descriptor->AddrTranslationOffset = 0;
Descriptor->AddrLen = RootBridge->Bus.Limit - RootBridge->Bus.Base + 1;
End = (EFI_ACPI_END_TAG_DESCRIPTOR *) (Descriptor + 1);
End->Desc = ACPI_END_TAG_DESCRIPTOR;
End->Checksum = 0x0;
return EFI_SUCCESS;
}
}
return EFI_INVALID_PARAMETER;
}
根据总线起始编号和最大编号,构建EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR
供PCI 总线驱动使用。
SetBusNumbers 函数
更新当前RootBridge 实际总线编号的范围。
//
// Update the Bus Range
//
RootBridge->ResAllocNode[TypeBus].Base = Descriptor->AddrRangeMin;
RootBridge->ResAllocNode[TypeBus].Length = Descriptor->AddrLen;
RootBridge->ResAllocNode[TypeBus].Status = ResAllocated;
return EFI_SUCCESS;
}
更新当前RootBridge 设备的总线资源类型的实际大小和范围。
PCI 总线驱动
PCI_IO_DEVICE 数据结构
当PCI 总线发现一个PCI设备,就创建PCI_IO_DEVICE 数据结构与之对应。
PCI_IO_DEVICE 每个域含义如下,
struct _PCI_IO_DEVICE {
UINT32 Signature;
EFI_HANDLE Handle;
EFI_PCI_IO_PROTOCOL PciIo;
LIST_ENTRY Link;
EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL PciDriverOverride;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;
EFI_LOAD_FILE2_PROTOCOL LoadFile2;
//
// PCI configuration space header type
//
PCI_TYPE00 Pci;
//
// Bus number, Device number, Function number
//
UINT8 BusNumber;
UINT8 DeviceNumber;
UINT8 FunctionNumber;
定义 数据结构的标示为PCI_IO_DEVICE_SIGNATURE,当用CR 宏根据另一个域Link
的地址得到PCI_IO_DEVICE 的地址时,该标示用来做进一步的检查,确认是PCI_IO_DEVICE 结构。
PCI 设备的句柄(Handle). 该句柄上安装了标示该PCI 设备位置信息的Device Path 协议(
EfiDevicePathProtocol) , 用于访问该PCI 设备的PCI IO 协议以及LoadFile 协议。
LIST_ENTRY Link;
Link 用于该PCI 设备挂在桥设备的ChildList 上。可以用桥设备的
PciBusDriver 关键函数解析
PciBusDriverBindingStart 函数
当对PCIRootBridge 设备Handle 或者DevicePath进行Connect, PCI 总线驱动就会开始执行。
PcibusDriverBindingStart()开始对当前RootBridge 进行PCI 总线设备枚举和资源分配。
Controller handle 表明Host Bridge Controller 下面某一个RootBridge 设备。
if (!EFI_ERROR (Status)) {
Status = PciEnumerator (Controller, PciRootBridgeIo->ParentHandle);
}
} else {
//
// If PCI bus has already done the full enumeration, never do it again
//
Status = PciEnumeratorLight (Controller);
}
if (EFI_ERROR (Status)) {
return Status;
}
//
// Start all the devices under the entire host bridge.
//
StartPciDevices (Controller);
PciEnumerator 函数
注意: 以下提到的HostBridge Controller 均指当前PCI 总线驱动管理的RootBridge 所属的HostBridge controller.
//
// Get the pci host bridge resource allocation protocol
//
Status = gBS->OpenProtocol (
HostBridgeHandle,
&gEfiPciHostBridgeResourceAllocationProtocolGuid,
(VOID **) &PciResAlloc,
gPciBusDriverBinding.DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
获得当前PCI 总线驱动管理的RootBridge 的PciRootBridgeIoProtocol.
//
// Get the pci host bridge resource allocation protocol
//
Status = gBS->OpenProtocol (
HostBridgeHandle,
&gEfiPciHostBridgeResourceAllocationProtocolGuid,
(VOID **) &PciResAlloc,
gPciBusDriverBinding.DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return Status;
}
获得该HostBridge Controller 的PciHostBridgeResourceAllocationProtocol, 用来分配该HostBridge controller
下所有RootBridge 产生的PCI 总线上的PCI 设备资源 。
//
// Notify the pci bus enumeration is about to begin
//
Status = NotifyPhase (PciResAlloc, EfiPciHostBridgeBeginEnumeration);
通知platform PCI 总线枚举即将开始,调用platform 提供的钩子函数。
PCI 总线驱动对Option ROM 的支持
GetOpRomInfo函数
GetOpRomInfo 获得PCI 设备OpRom的大小。
//
// Offset is 0x30 if is not ppb
//
//
// 0x30
//
RomBarIndex = PCI_EXPANSION_ROM_BASE;
if (IS_PCI_BRIDGE (&PciIoDevice->Pci)) {
//
// If is ppb, 0x38
//
RomBarIndex = PCI_BRIDGE_ROMBAR;
}
RomBarIndex 是PCI 设备配置空间中Expansion ROM Base Address 寄存器,该域在
PCI 设备的配置空间中偏移是0x30, Bridge 设备的配置空间中偏移是0x38. 该寄存器可以
用来得到该PCI 设备的Option Rom的大小,并且当一个MMIO 地址写入该寄存器后,
可以从该MMIO 地址读出PCI 设备的Option Rom .
AllOnes = 0xfffffffe;
Address = EFI_PCI_ADDRESS (Bus, Device, Function, RomBarIndex);
Status = PciRootBridgeIo->Pci.Write (
PciRootBridgeIo,
EfiPciWidthUint32,
Address,
1,
&AllOnes
);
if (EFI_ERROR (Status)) {
return EFI_NOT_FOUND;
}
//
// Read back
//
Status = PciRootBridgeIo->Pci.Read(
PciRootBridgeIo,
EfiPciWidthUint32,
Address,
1,
&AllOnes
);
if (EFI_ERROR (Status)) {
return EFI_NOT_FOUND;
}
//
// Bits [1, 10] are reserved
//
AllOnes &= 0xFFFFF800;
if ((AllOnes == 0) || (AllOnes == 0xFFFFF800)) {
return EFI_NOT_FOUND;
}
PciIoDevice->RomSize = (~AllOnes) + 1;
return EFI_SUCCESS;
}
向PCI 配置空间的Expansion ROM Base Address 写入全1。
构建PCI 配置空间的Expansion ROM Base Address 地址。
向Expansion ROM Base Address 写入全1
去除低10位的保留位,然后取反加1,得到该PCI 设备的Option ROM 的大小。
505