PCI总线

PCI 总线介绍

外围部件互连总线PCI (Peripheral Component Interconnect) 总线,是一种先进的高性能32/64 位地址数据复

用局部总线,可同时支持多组外围设备,为中央处理器与高速外围设备提供了一座沟通的桥梁,是现在PC

领域中流行的总线。本文重点介绍UEFI 规范如何支持PCI 总线,以及UEFI 固件中PCI 总线驱动的实现 。

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

  1.        提供分配PCI 设备资源的协议(Protocol)
  2.        提供访问PCI 设备的协议(Protocol)。
  3.        枚举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

猜你喜欢

转载自blog.csdn.net/robinsongsog/article/details/98945414
PCI