1.1 PCI 总线介绍

外围部分互连总线PCI (Peripheral Component Interconnect) 总线,是一种先进

的高性能32/64 位地址数据复用局部总线,可同时支持多组个围设备,为中央

处理器与高速外围设备提供了一座沟通的桥梁,是现在PC 领域中流行的总线,

BIOS 对于PCI 总线的支持包括三个方面:

1. 提供分配PCI设备资源的协议

2. 提供访问PCI设备的协议

3. 枚举PCI 总线上的设备以及分配设备所需要的资源。

UEFI BIOS 如何支持PCI 总线及设备

UEFI BIOS 提供了两个主要模块来支持PCI总线,一个是PCI host bridge 控制器

驱动,另一个是PCI 总线驱动。

    PCI host Bridge 控制器驱动是跟特定的平台硬件绑定的。根据系统实际I/O 空间

和memory map, 为PCI 设备指定I/O 空间和Memory 空间范围,并且产生PCI 

HOST Bridge Resource Allocation 协议, 供PCI 总线驱动使用,该驱动还对

HostBridge 控制器下所有RootBridge 设备产生句柄handle, 该句柄上安装了

PciRootBridgeProtocol 。 pci 总线驱动则利用PciRootBridgeIo Protocol 枚举

系统中所有PCI 设备,发现并获得PCI 设备的Option rom, 并且调用PCI HOST

Bridge Resource Allocation 协议对PCI HostBridge Controller 进行编程。

    针对UFEI 规范定义了PCI Root Bridge I/O 协议,该协议抽象了访问

RootBridge 设备下所有PCI设备的接口。PCI HOST Bridge controller 产生一个

或者多个PCI ROOT BRIDGE 设备,PCI Root Bridge 设备又产生了PCI LOCAL 

BUS, host bridge controller 是一个计算北桥上的硬件组件,通过它可以访问共享

PCI I/O 和MEMORY 空间的一组PCI 设备。PCI 总线驱动使用PCI root bridge

I/O 协议 对PCI 设备的IO MEMORY 空间和配置空间进行访问。它也支持对设备

进行DMA 操作。 

1.3 PCI HOST BRIDGE 控制器驱动

PCIHOSTBRIDGE 用来初始化PCI HOST BRIDGE 控制器,是PCI 总线驱动

的基础。PCIHOSTBRIDGE 驱动包括以下两个方面:

1, 提供管理和分配PCI 设备资源的协议
PciHostBridgeResourceAllocationProtocol
2. 提供访问PCI 设备的配置空间,MMIO/IO 空间的协议。
InitializePciHostBridge()
InitializePciHostBridge 函数 用来创建HostBridge 和 RootBridge 的handle, 并且产生
简单来说,就是实例化 EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOL
struct _EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL {
   ///
   /// The notification from the PCI bus enumerator that it is about to enter
   /// a certain phase during the enumeration process.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_NOTIFY_PHASE           NotifyPhase;
   
   ///
   /// Retrieves the device handle for the next PCI root bridge that is produced by the
   /// host bridge to which this instance of the EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL is attached.  
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_GET_NEXT_ROOT_BRIDGE   GetNextRootBridge;
   
   ///
   /// Retrieves the allocation-related attributes of a PCI root bridge.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_GET_ATTRIBUTES         GetAllocAttributes;
   
   ///
   /// Sets up a PCI root bridge for bus enumeration.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_START_BUS_ENUMERATION  StartBusEnumeration;
   
   ///
   /// Sets up the PCI root bridge so that it decodes a specific range of bus numbers.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_SET_BUS_NUMBERS        SetBusNumbers;
   
   ///
   /// Submits the resource requirements for the specified PCI root bridge.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_SUBMIT_RESOURCES       SubmitResources;
   
   ///
   /// Returns the proposed resource assignment for the specified PCI root bridges.
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_GET_PROPOSED_RESOURCES GetProposedResources;
   
   ///
   /// Provides hooks from the PCI bus driver to every PCI controller
   /// (device/function) at various stages of the PCI enumeration process that
   /// allow the host bridge driver to preinitialize individual PCI controllers
   /// before enumeration.  
   ///
   EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL_PREPROCESS_CONTROLLER  PreprocessController;
 };按照HostBridge 的个数(HOST_BRIDGE_NUMBER) 创建HostBridge 设备实例以及
HostBridge 下面所有rootBridge 设备实例。
RootBridgeConstructor
  //
   // The host to pci bridge, the host memory and io addresses are
   // direct mapped to pci addresses, so no need translate, set bases to 0.
   //
   PrivateData->MemBase  = ResAppeture->MemBase;
   PrivateData->IoBase   = ResAppeture->IoBase;
  初始化RootBridge 设备下面PCI 设备的MMIO/IO 资源的起始地址和最大地址。
  //
   // Bus Appeture for this Root Bridge (Possible Range)
   //
   PrivateData->BusBase  = ResAppeture->BusBase;
   PrivateData->BusLimit = ResAppeture->BusLimit;
  RootBridge 设备产生的PCI 总线的起始编号和最大编号
PCI 总线驱动
PciBus.c                           pci总线驱动主文件以及入口
PciCommand.c                提供访问PCI 设备的函数
PciEnumerator.c             枚举PCI 设备
PciIo.c                             PCI IO 协议的实例
PciLib.c                            枚举PCI设备所需要的函数
PciResourceSupport.c    分配PCI资源所需要的函数
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;  //
   // BAR for this PCI Device
   //
   PCI_BAR                                   PciBar[PCI_MAX_BAR];  //
   // The bridge device this pci device is subject to
   //
   PCI_IO_DEVICE                             *Parent;  //
   // A linked list for children Pci Device if it is bridge device
   //
   LIST_ENTRY                                ChildList;  //
   // TURE if the PCI bus driver creates the handle for this PCI device
   //
   BOOLEAN                                   Registered;  //
   // TRUE if the PCI bus driver successfully allocates the resource required by
   // this PCI device
   //
   BOOLEAN                                   Allocated;  //
   // The attribute this PCI device currently set
   //
   UINT64                                    Attributes;  //
   // The attributes this PCI device actually supports
   //
   UINT64                                    Supports;  //
   // The resource decode the bridge supports
   //
   UINT32                                    Decodes;  //
   // TRUE if the ROM image is from the PCI Option ROM BAR
   //
   BOOLEAN                                   EmbeddedRom;  //
   // The OptionRom Size
   //
   UINT64                                    RomSize;  //
   // The OptionRom Size
   //
   UINT64                                    RomBase;  //
   // TRUE if all OpROM (in device or in platform specific position) have been processed
   //
   BOOLEAN                                   AllOpRomProcessed;  //
   // TRUE if there is any EFI driver in the OptionRom
   //
   BOOLEAN                                   BusOverride;  //
   // A list tracking reserved resource on a bridge device
   //
   LIST_ENTRY                                ReservedResourceList;  //
   // A list tracking image handle of platform specific overriding driver
   //
   LIST_ENTRY                                OptionRomDriverList;  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR         *ResourcePaddingDescriptors;
   EFI_HPC_PADDING_ATTRIBUTES                PaddingAttributes;  //
   // Bus number ranges for a PCI Root Bridge device
   //
   EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR         *BusNumberRanges;  BOOLEAN                                   IsPciExp;
   //
   // For SR-IOV
   //
   UINT8                                     PciExpressCapabilityOffset;
   UINT32                                    AriCapabilityOffset;
   UINT32                                    SrIovCapabilityOffset;
   UINT32                                    MrIovCapabilityOffset;
   PCI_BAR                                   VfPciBar[PCI_MAX_BAR];
   UINT32                                    SystemPageSize;
   UINT16                                    InitialVFs;
   UINT16                                    ReservedBusNum;
   //
   // Per PCI to PCI Bridge spec, I/O window is 4K aligned,
   // but some chipsets support non-stardard I/O window aligments less than 4K.
   // This field is used to support this case.
   //
   UINT16                                    BridgeIoAlignment;
 };当PCI 总线发现一个PCI 设备, 就创建PCI_IO_DEVICE数据结构与之对应,
UINT32                   Signature
定义数据结构的标示为PCI_DEVICE_SIGNATUE . 当用CR宏根据另一个域LINK 的地址
得到PCI_IO_DEVICE的址址时,该标示用来做进一步的检查。 确认是PCI_IO_DEVICE 结构
EFI_HANDLE                     Handle;
PCI设备的句柄(handle). 该句柄上安装了标示该PCI设备位置信息的Device path 协议, 
EfiDevicePathProtocol , 用于访问该PCI 设备的PCI IO 协议, 以及LoadFile 协议。
PciBusDriverBindingStart()
PciBusDriverBindingStart() 函数
当对PCIRootBridge 设备handle 或者devicePath 进行Connect, PCI 总线驱动就会开始
执行。 
PciBusDriverBindingStart() 开始对当前RootBridge 进行PCI 总线设备枚举和资源分配。
PciEnumerator()
PciHostBridgeEnumerator()
PciHostBridgeEnumerator 分两步进行PCI 总线枚举, 第一步调用PciRootBridgeEnumerator
对所有Bridge 和设备分配总线号(bus number), 第二步对所有bridge 和设备分配资源MMIO, IO
PciScanBus 函数
从RootBridge 产生的local PCI BUS 的BUS 0 开始枚举所有PCI 设备。调用PciDevicePresent
读取PCI 设备配置空间的VendorId, 如果该值不是0xffff, 则表明PCI 设备存在, 为该设备创建
PCI_IO_DEVICE私有数据结构, 根据深度优先原则, 如果遇到Bridge 设备, 则递归调用
PciScanBus 枚举该Bridge 设备下面的PCI 设备。
if (!EFI_ERROR (Status) && (Pci->Hdr).VendorId != 0xffff) {
位于PciDevicePresent 函数
/**
   This routine is used to check whether the pci device is present.  @param PciRootBridgeIo   Pointer to instance of EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
   @param Pci               Output buffer for PCI device configuration space.
   @param Bus               PCI bus NO.
   @param Device            PCI device NO.
   @param Func              PCI Func NO.  @retval EFI_NOT_FOUND    PCI device not present.
   @retval EFI_SUCCESS      PCI device is found.**/
 EFI_STATUS
 PciDevicePresent (
   IN  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL     *PciRootBridgeIo,
   OUT PCI_TYPE00                          *Pci,
   IN  UINT8                               Bus,
   IN  UINT8                               Device,
   IN  UINT8                               Func
   )
 {
   UINT64      Address;
   EFI_STATUS  Status;  //
   // Create PCI address map in terms of Bus, Device and Func
   //
   Address = EFI_PCI_ADDRESS (Bus, Device, Func, 0);  //
   // Read the Vendor ID register
   //
   Status = PciRootBridgeIo->Pci.Read (
                                   PciRootBridgeIo,
                                   EfiPciWidthUint32,
                                   Address,
                                   1,
                                   Pci
                                   );  if (!EFI_ERROR (Status) && (Pci->Hdr).VendorId != 0xffff) {
     //
     // Read the entire config header for the device
     //
     Status = PciRootBridgeIo->Pci.Read (
                                     PciRootBridgeIo,
                                     EfiPciWidthUint32,
                                     Address,
                                     sizeof (PCI_TYPE00) / sizeof (UINT32),
                                     Pci
                                     );    return EFI_SUCCESS;
   }  return EFI_NOT_FOUND;
 }
  PCI 总线驱动对Option ROM 的支持
GetOpROmInfo 函数
RomBarIndex 是PCI 设备配置空间中Expansion ROM base Address 寄存器, 该域在PCI
设备的配置空间偏移是0x30, Bridge 设备的配置空间中偏移是0x38. 该寄存器可以用来得到
该 PCI 设备的Option rom 的大小,并且当一个MMIO 地址写入该寄存器后, 可以从该MMIO
地址读出PCI 设备的Option ROM, 关于如何读出PCI 设备的Option ROM, 可以参考
LoadOpRomImage 函数
  //
   // The bit0 is 0 to prevent the enabling of the Rom address decoder
   //
   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;向PCI 配置空间的Expansion ROM Base Address 写入全1, The bit0 is to prevent the
enabling of the Rom address decoder
typedef union {
   struct {
     UINT16 CurrentLinkSpeed : 4;
     UINT16 NegotiatedLinkWidth : 6;
     UINT16 Undefined : 1;
     UINT16 LinkTraining : 1;
     UINT16 SlotClockConfiguration : 1;
     UINT16 DataLinkLayerLinkActive : 1;
     UINT16 LinkBandwidthManagement : 1;
     UINT16 LinkAutonomousBandwidth : 1;
   } Bits;
   UINT16   Uint16;
 } PCI_REG_PCIE_LINK_STATUS;把一个16 位的寄存器写成一个联合体。
 这样就能精确定义每一个bit. 当需要读出某一个bit 的时候,先整体读出16个bit
 然后解析出具体想要的那一位。真实场景:
   Status = GetPcieCapReg16 (IioIndex, PortIndex, R_PCIE_LSTS_OFFSET, &LnkSts.Uint16);
   if (EFI_ERROR (Status)) {
     IIO_D_PCIERR ("[%d p%d] %a: Cannot read LinkStatus register (%r)\n",
                   IioIndex, PortIndex, __FUNCTION__, Status);
     return Status;
   }
   *CurrentLinkSpeedPtr = LnkSts.Bits.CurrentLinkSpeed;  return EFI_SUCCESS;
 }

pcie training 的时候,需要知道train 出来的速度有没有达到我们期望的结果。
这一步就需要读出当前link 的速度。