UEFI中的Option ROM 驅動詳解

最近在調試UEFI中 option rom 的支持,板卡使用的是南京神易的網絡隔離卡,在他們的板卡上適配的。在這個過程中,收貨還是挺多的,個人感覺咋遇到bug的時候,首先要做的是冷靜仔細的分析,定位問題的方向很重要,只有找對了方向才能,後面做的纔是有用功,否則做再多的驗證也只是無用功,也只能當做排除發法的選項。下面就將以問題的方式來講解oprom驅動的執行原理和過程。話不多少,直接上乾貨。

PCI掃描過程中是如何確定PCI設備是支持option rom的?

在pci掃描設備的過程中,在函數PciSearchDevice中會調用函數GetOpRomInfo這個函數,PCI的掃描函數這裏就不做過多的介紹了。
函數調用關係如下:
PciBusDriverBindingStart->PciEnumerator->PciRootBridgeEnumerator->PciScanBus
->PciSearchDevice->GetOpRomInfo
下面詳細的介紹一下函數GetOpRomInfo,代碼如下:

EFI_STATUS
GetOpRomInfo (
  IN OUT PCI_IO_DEVICE    *PciIoDevice
  )
{
  UINT8                           RomBarIndex;
  UINT32                          AllOnes;
  UINT64                          Address;
  EFI_STATUS                      Status;
  UINT8                           Bus;
  UINT8                           Device;
  UINT8                           Function;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;

  Bus             = PciIoDevice->BusNumber;
  Device          = PciIoDevice->DeviceNumber;
  Function        = PciIoDevice->FunctionNumber;

  PciRootBridgeIo = PciIoDevice->PciRootBridgeIo;

  //  
  // 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;
  }
   //
  // 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;
  }

  //
  // Bits [1, 10] are reserved
  //
  AllOnes &= 0xFFFFF800;
  if ((AllOnes == 0) || (AllOnes == 0xFFFFF800)) {
    return EFI_NOT_FOUND;
  }

  PciIoDevice->RomSize = (~AllOnes) + 1;
    return EFI_SUCCESS;
}

首先看一下函數入參,PciIoDevice是一個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;
  //                                                                                                                                                                                          
  // 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;
  //
  // TRUE 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;
  //
  // 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-standard I/O window alignments less than 4K.
  // This field is used to support this case.
  //
  UINT16                                    BridgeIoAlignment;
};

這個結構包含了一個PCI 設備的全部信息,其中和option rom相關的有
Decodes:表示橋片是否支持解碼操作
EmbeddedRom:這個設備是否有內嵌的rom
RomSize:內嵌的rom的大小
AllOpRomProcessed:rom中的代碼是否處理過(這裏就是指load 和start)
BusOverride:是否是在pcibusoverde過程去執行
OptionRomDriverList:這個設備下面的optionrom驅動的鏈表,有一個驅動就會添加到鏈表中.
在GetOpRomInfo中這裏只是探測了這個設備是否有Rom,如果有讀取RomSize的大小到PciIoDevice中。這時探測到了這個pci設備包含的rom大小。
然後在函數PciHostBridgeResourceAllocator總調用ProcessOptionRom對optionrom進行處理,函數調用關係如下:
PciBusDriverBindingStar->PciEnumerator->PciHostBridgeResourceAllocator
->ProcessOptionRom
下面詳細的介紹一下ProcessOptionRom,其代碼如下:

ProcessOptionRom (
  IN PCI_IO_DEVICE *Bridge,
  IN UINT64        RomBase,
  IN UINT64        MaxLength
  )
{
  LIST_ENTRY      *CurrentLink;
  PCI_IO_DEVICE   *Temp;
  //
  // Go through bridges to reach all devices
  //
  CurrentLink = Bridge->ChildList.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &Bridge->ChildList) {
    Temp = PCI_IO_DEVICE_FROM_LINK (CurrentLink);
    if (!IsListEmpty (&Temp->ChildList)) {
      //   
      // Go further to process the option rom under this bridge
      //   
      ProcessOptionRom (Temp, RomBase, MaxLength);
    }    

    if (Temp->RomSize != 0 && Temp->RomSize <= MaxLength) {
      //   
      // Load and process the option rom
      //   
      LoadOpRomImage (Temp, RomBase);
    }    

    CurrentLink = CurrentLink->ForwardLink;
  }
}

由上面的代碼可知,入參傳進來了這個pci設備的結構指針和RomBase,以及Rom的大小,這裏注意ROMBase這裏傳進來的是PCI MEM空間的起始地址。是從上面分配空間過程中得到。根據代碼的邏輯,如果這個設備是一個橋設備,那麼繼續遞歸其下游的子設備,然後在調用ProcessOptionRom處理橋下面設備的optionrom,如果不是一個橋那麼就執行函數LoadOpRomImage,將rom中的數據讀取到內存中。
下面是函數LoadOpRomImage的代碼實現:

EFI_STATUS
LoadOpRomImage (
  IN PCI_IO_DEVICE   *PciDevice,
  IN UINT64          RomBase
  )
{
  UINT8                     RomBarIndex;
  UINT8                     Indicator;
  UINT16                    OffsetPcir = 0;
  UINT32                    RomBarOffset;
  UINT32                    RomBar;
  EFI_STATUS                RetStatus;
  BOOLEAN                   FirstCheck;
  UINT8                     *Image;
  PCI_EXPANSION_ROM_HEADER  *RomHeader;
  PCI_DATA_STRUCTURE        *RomPcir;
  UINT64                    RomSize;
  UINT64                    RomImageSize;
  UINT32                    LegacyImageLength;
  UINT8                     *RomInMemory;
  UINT8                     CodeType;

  RomSize       = PciDevice->RomSize;

  Indicator     = 0;
  RomImageSize  = 0;
  RomInMemory   = NULL;
  CodeType      = 0xFF;

  //
  // Get the RomBarIndex  0x30
  //

  RomBarIndex = PCI_EXPANSION_ROM_BASE;
  if (IS_PCI_BRIDGE (&(PciDevice->Pci))) {
    //
    // if is ppb  0x38
    //
    RomBarIndex = PCI_BRIDGE_ROMBAR;
  }
  //
  // Allocate memory for Rom header and PCIR
  //
  RomHeader = AllocatePool (sizeof (PCI_EXPANSION_ROM_HEADER));
  if (RomHeader == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  RomPcir = AllocatePool (sizeof (PCI_DATA_STRUCTURE));
  if (RomPcir == NULL) {
    FreePool (RomHeader);
    return EFI_OUT_OF_RESOURCES;                                                                                                                                                              
  }
  RomPcir = AllocatePool (sizeof (PCI_DATA_STRUCTURE));
  if (RomPcir == NULL) {
    FreePool (RomHeader);
    return EFI_OUT_OF_RESOURCES;
  }
  RomBar = (UINT32) RomBase;

  //
  // Enable RomBar
  //
  RomDecode (PciDevice, RomBarIndex, RomBar, TRUE);

  RomBarOffset  = RomBar;
  RetStatus     = EFI_NOT_FOUND;
  FirstCheck    = TRUE;
  LegacyImageLength = 0;
  do {
    PciDevice->PciRootBridgeIo->Mem.Read (
                                      PciDevice->PciRootBridgeIo,
                                      EfiPciWidthUint8,
                                      RomBarOffset,
                                      sizeof (PCI_EXPANSION_ROM_HEADER),
                                      (UINT8 *) RomHeader
                                      );

    if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      RomBarOffset = RomBarOffset + 512;
      if (FirstCheck) {
        break;
      } else {
        RomImageSize = RomImageSize + 512;
        continue;
      }
    }

    FirstCheck  = FALSE;
    OffsetPcir  = RomHeader->PcirOffset;
    //
    // If the pointer to the PCI Data Structure is invalid, no further images can be located.
    // The PCI Data Structure must be DWORD aligned.
    //
    if (OffsetPcir == 0 ||
        (OffsetPcir & 3) != 0 ||
        RomImageSize + OffsetPcir + sizeof (PCI_DATA_STRUCTURE) > RomSize) {
      break;
    }
    PciDevice->PciRootBridgeIo->Mem.Read (
                                      PciDevice->PciRootBridgeIo,
                                      EfiPciWidthUint8,
                                      RomBarOffset + OffsetPcir,
                                      sizeof (PCI_DATA_STRUCTURE),
                                      (UINT8 *) RomPcir
                                      );
   //
    // If a valid signature is not present in the PCI Data Structure, no further images can be located.
    //
    if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
      break;
    }
    if (RomImageSize + RomPcir->ImageLength * 512 > RomSize) {
      break;
    }
    if (RomPcir->CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
      CodeType = PCI_CODE_TYPE_PCAT_IMAGE;
      LegacyImageLength = ((UINT32)((EFI_LEGACY_EXPANSION_ROM_HEADER *)RomHeader)->Size512) * 512;
    }
    Indicator     = RomPcir->Indicator;
    RomImageSize  = RomImageSize + RomPcir->ImageLength * 512;
    RomBarOffset  = RomBarOffset + RomPcir->ImageLength * 512;
  } while (((Indicator & 0x80) == 0x00) && ((RomBarOffset - RomBar) < RomSize));
  //
  // Some Legacy Cards do not report the correct ImageLength so used the maximum
  // of the legacy length and the PCIR Image Length
  //
  if (CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
    RomImageSize = MAX (RomImageSize, LegacyImageLength);
  }

  if (RomImageSize > 0) {
    RetStatus = EFI_SUCCESS;
    Image     = AllocatePool ((UINT32) RomImageSize);
    if (Image == NULL) {
      RomDecode (PciDevice, RomBarIndex, RomBar, FALSE);
      FreePool (RomHeader);
      FreePool (RomPcir);

      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Copy Rom image into memory                                                                                                                                                             
    //
    PciDevice->PciRootBridgeIo->Mem.Read (
                                      PciDevice->PciRootBridgeIo,
                                      EfiPciWidthUint8,
                                      RomBar,
                                      (UINT32) RomImageSize,
                                      Image
                                      );
      RomInMemory = Image;
      }
      RomDecode (PciDevice, RomBarIndex, RomBar, FALSE);

  PciDevice->EmbeddedRom    = TRUE;
  PciDevice->PciIo.RomSize  = RomImageSize;
  PciDevice->PciIo.RomImage = RomInMemory;

  //
  // For OpROM read from PCI device:
  //   Add the Rom Image to internal database for later PCI light enumeration
  //
  PciRomAddImageMapping (
    NULL,
    PciDevice->PciRootBridgeIo->SegmentNumber,
    PciDevice->BusNumber,
    PciDevice->DeviceNumber,
    PciDevice->FunctionNumber,
    PciDevice->PciIo.RomImage,
    PciDevice->PciIo.RomSize
    );
    //
  // Free allocated memory
  //
  FreePool (RomHeader);
  FreePool (RomPcir);

  return RetStatus;
}

根據以上代碼可知,首先讀取PCI_EXPANSION_ROM_HEADER的數據,PCI_EXPANSION_ROM_HEADER結構如下:

typedef struct {
  UINT16  Signature;    // 0xaa55
  UINT8   Reserved[0x16];
  UINT16  PcirOffset;
} PCI_EXPANSION_ROM_HEADER;

讀取這個結構數據主要是爲了得到結構中的PcirOffset的值,通過這個值的偏移,可以得到PCI的數據PCI_DATA_STRUCTURE的內容,(結構定義如下:

typedef struct {
  UINT32  Signature;    // "PCIR"         
  UINT16  VendorId;                       
  UINT16  DeviceId;
  UINT16  Reserved0;
  UINT16  Length;
  UINT8   Revision;
  UINT8   ClassCode[3];
  UINT16  ImageLength;
  UINT16  CodeRevision;
  UINT8   CodeType;
  UINT8   Indicator;
  UINT16  Reserved1;
} PCI_DATA_STRUCTURE;

)根據裏面的內容判斷是否是一個有效的Option Rom 驅動.讀取數據之後,相應的校驗都正確之後,會重PCI_DATA_STRUCTURE結構中計算出RomImageSize的大小,這裏需要注意RomImageSize的大小是ImageLength*512,這裏是uefi spec規定的.同時RomImageSize的大小應該和InitializationSize(下面結構體的第二個成員)大小一致,否則認爲是無效的option rom.這些實在後面的LocalLoadFile2函數中體現的.

typedef struct {
  UINT16  Signature;    // 0xaa55
  UINT16  InitializationSize;
  UINT32  EfiSignature; // 0x0EF1
  UINT16  EfiSubsystem;
  UINT16  EfiMachineType;
  UINT16  CompressionType;
  UINT8   Reserved[8];
  UINT16  EfiImageHeaderOffset;  
  UINT16  PcirOffset;
} EFI_PCI_EXPANSION_ROM_HEADER; 

這裏校驗沒有失敗,那麼就會調用函數PciRomAddImageMapping添加到數據庫中.待後面處理.這裏函數PciRomAddImageMapping代碼如下:

VOID
PciRomAddImageMapping (
  IN  EFI_HANDLE  ImageHandle,                                                                                                                                                                                                                
  IN  UINTN       Seg,
  IN  UINT8       Bus,
  IN  UINT8       Dev,
  IN  UINT8       Func,
  IN  VOID        *RomImage,
  IN  UINT64      RomSize
  )
{
  UINTN           Index;
  PCI_ROM_IMAGE   *NewTable;

  for (Index = 0; Index < mNumberOfPciRomImages; Index++) {
    if (mRomImageTable[Index].Seg  == Seg &&
        mRomImageTable[Index].Bus  == Bus &&
        mRomImageTable[Index].Dev  == Dev &&
        mRomImageTable[Index].Func == Func) {
      //
      // Expect once RomImage and RomSize are recorded, they will be passed in
      // later when updating ImageHandle
      //
      ASSERT ((mRomImageTable[Index].RomImage == NULL) || (RomImage == mRomImageTable[Index].RomImage));
      ASSERT ((mRomImageTable[Index].RomSize  == 0   ) || (RomSize  == mRomImageTable[Index].RomSize ));
      break;
    }
  }

  if (Index == mNumberOfPciRomImages) {
    //
    // Rom Image Table buffer needs to grow.
    //
    if (mNumberOfPciRomImages == mMaxNumberOfPciRomImages) {
      NewTable = ReallocatePool (
                   mMaxNumberOfPciRomImages * sizeof (PCI_ROM_IMAGE),
                   (mMaxNumberOfPciRomImages + 0x20) * sizeof (PCI_ROM_IMAGE),
                   mRomImageTable
                   );
      if (NewTable == NULL) {
        return ;
      }
      mRomImageTable            = NewTable;
      mMaxNumberOfPciRomImages += 0x20;
    }
    //
    // Record the new PCI device
    //
    mRomImageTable[Index].Seg  = Seg;
    mRomImageTable[Index].Bus  = Bus;
    mRomImageTable[Index].Dev  = Dev;
    mRomImageTable[Index].Func = Func;
    mNumberOfPciRomImages++;
  }

  mRomImageTable[Index].ImageHandle = ImageHandle;
  mRomImageTable[Index].RomImage    = RomImage;
  mRomImageTable[Index].RomSize     = RomSize;
}

根據上面的代碼可知,此時將這個設備的信息Seg號,Bus號, Dev號,Func號,ImageHandle,RomImage(load到內存之後的地址),RomSize就是rom的大小都保存在了全局變量mRomImageTable內,這是一個全局的指針,沒添加一個變量指針指針位置向後移動.這樣在掃描過程中將所有的支持oprom 的pci設備都保存在了mRomImageTable內了.

Option Rom 驅動後面是如何被處理成的 ?

在函數PciBusDriverBindingStart中,當pci設備都枚舉之後,再此調用這個函數的時候,就會調用函數PciEnumeratorLight
在這個函數內去調用函數ProcessOptionRomLight,其代碼如下:

VOID
ProcessOptionRomLight (
  IN PCI_IO_DEVICE                      *PciIoDevice
  )
{
  PCI_IO_DEVICE   *Temp;
  LIST_ENTRY      *CurrentLink;
  //
  // For RootBridge, PPB , P2C, go recursively to traverse all its children
  //
  CurrentLink = PciIoDevice->ChildList.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &PciIoDevice->ChildList) {

    Temp = PCI_IO_DEVICE_FROM_LINK (CurrentLink);

    if (!IsListEmpty (&Temp->ChildList)) {
      ProcessOptionRomLight (Temp);
    }    
    PciRomGetImageMapping (Temp);
    //   
    // The OpRom has already been processed in the first round
    //   
    Temp->AllOpRomProcessed = TRUE;

    CurrentLink = CurrentLink->ForwardLink;
  }
}

根據上面的代碼可知,枚舉每一個pcie設備,如果是橋設備繼續調用函數ProcessOptionRomLight自身,這裏是遞歸函數.如果不是橋設備,那麼就調用函數 PciRomGetImageMapping (Temp);這個函數的代碼如下:

BOOLEAN
PciRomGetImageMapping (                                                                                                                                                                                                                       
  IN  PCI_IO_DEVICE                       *PciIoDevice
  )
{ 
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;
  UINTN                           Index;

  PciRootBridgeIo = PciIoDevice->PciRootBridgeIo;
  for (Index = 0; Index < mNumberOfPciRomImages; Index++) {
    if (mRomImageTable[Index].Seg  == PciRootBridgeIo->SegmentNumber &&
        mRomImageTable[Index].Bus  == PciIoDevice->BusNumber         &&
        mRomImageTable[Index].Dev  == PciIoDevice->DeviceNumber      &&
        mRomImageTable[Index].Func == PciIoDevice->FunctionNumber    ) {
      if (mRomImageTable[Index].ImageHandle != NULL) {
        AddDriver (PciIoDevice, mRomImageTable[Index].ImageHandle, NULL);
      }
      PciIoDevice->PciIo.RomImage = mRomImageTable[Index].RomImage;
      PciIoDevice->PciIo.RomSize  = mRomImageTable[Index].RomSize;
      return TRUE;
    }
  }
  return FALSE;
}

根據上面的代碼可知,如果傳進來的這個設備的信息和之前枚舉設備時添加到mRomImageTable中的設備信息匹配上之後,就會調用AddDriver,用這設備的ImageHandle,和PciIoDevice信息創建一個驅動,並添加到pci設備的OptionRomDriverList中,AddDriver
的代碼如下:

EFI_STATUS
AddDriver (
  IN PCI_IO_DEVICE            *PciIoDevice,
  IN EFI_HANDLE               DriverImageHandle,
  IN EFI_DEVICE_PATH_PROTOCOL *DriverImagePath
  )
{
  PCI_DRIVER_OVERRIDE_LIST      *Node;

  //
  // Caller should pass in either Image Handle or Image Path, but not both.
  //
  ASSERT ((DriverImageHandle == NULL) || (DriverImagePath == NULL));

  Node = AllocateZeroPool (sizeof (PCI_DRIVER_OVERRIDE_LIST));
  if (Node == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Node->Signature         = DRIVER_OVERRIDE_SIGNATURE;
  Node->DriverImageHandle = DriverImageHandle;
  Node->DriverImagePath   = DuplicateDevicePath (DriverImagePath);
  InsertTailList (&PciIoDevice->OptionRomDriverList, &Node->Link);

  PciIoDevice->BusOverride  = TRUE;
  return EFI_SUCCESS;
} 

AddDriver的主要作用就是創建這個設備驅動的devicepatch路徑,並將驅動,這個驅動的節點添加到OptionRomDriverList鏈表中.這裏驅動節點的類型是PCI_DRIVER_OVERRIDE_LIST,並更新PciIoDevice->BusOverride = TRUE;後面會使用這個標誌.
這樣就將一個option rom驅動添加到了驅動列表列面.

Option Rom 驅動是如何被load 以及執行的?

在函數PciBusDriverBindingStart後面會調用函數StartPciDevices,在這個函數內會調用函數StartPciDevicesOnBridge,然後再調用RegisterPciDevice函數,整個函數的調用關係如下:
PciBusDriverBindingStart->StartPciDevices->StartPciDevicesOnBridge->RegisterPciDevice->ContainEfiImage
下面主要介紹一下函數ContainEfiImage,這個函數是判讀這個option rom是否含有uefi image的函數,代碼如下:

/**
  Check if the RomImage contains EFI Images.

  @param  RomImage  The ROM address of Image for check.
  @param  RomSize   Size of ROM for check.

  @retval TRUE     ROM contain EFI Image.
  @retval FALSE    ROM not contain EFI Image.

**/
BOOLEAN
ContainEfiImage (
  IN VOID            *RomImage,
  IN UINT64          RomSize
  )
{
  PCI_EXPANSION_ROM_HEADER  *RomHeader;
  PCI_DATA_STRUCTURE        *RomPcir;
  UINT8                     Indicator;

  Indicator = 0;
  RomHeader = RomImage;
  if (RomHeader == NULL) {
    return FALSE;
  }

  do {
    if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      RomHeader = (PCI_EXPANSION_ROM_HEADER *) ((UINT8 *) RomHeader + 512);
      continue;
    }

    //
    // The PCI Data Structure must be DWORD aligned.
    //
    if (RomHeader->PcirOffset == 0 ||
        (RomHeader->PcirOffset & 3) != 0 ||
        (UINT8 *) RomHeader + RomHeader->PcirOffset + sizeof (PCI_DATA_STRUCTURE) > (UINT8 *) RomImage + RomSize) {
      break;
    } 
    RomPcir = (PCI_DATA_STRUCTURE *) ((UINT8 *) RomHeader + RomHeader->PcirOffset);
    if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
      break;
    }

    if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
      return TRUE;
    }

    Indicator = RomPcir->Indicator;
    RomHeader = (PCI_EXPANSION_ROM_HEADER *) ((UINT8 *) RomHeader + RomPcir->ImageLength * 512);
  } while (((UINT8 *) RomHeader < (UINT8 *) RomImage + RomSize) && ((Indicator & 0x80) == 0x00));

  return FALSE;
}

由上面的代碼可知,如果函數返回TRUE 就是表示這個OPtion Rom裏面包含了一個正規的Uefi Image,是可以被load 和執行的.
這個函數返回後,如果返回了TRUE 那麼就會在函數RegisterPciDevice內安裝&gEfiLoadFile2ProtocolGuid這個protocol,等待後面真正去load image的時候需要這裏註冊的接口&PciIoDevice->LoadFile2.代碼如下:

 if (HasEfiImage) {                                                                                                                                                                                                                          
    Status = gBS->InstallMultipleProtocolInterfaces (
                    &PciIoDevice->Handle,
                    &gEfiLoadFile2ProtocolGuid,
                    &PciIoDevice->LoadFile2,
                    NULL 
                    );
    if (EFI_ERROR (Status)) {
      gBS->UninstallMultipleProtocolInterfaces (
             &PciIoDevice->Handle,
             &gEfiDevicePathProtocolGuid,
             PciIoDevice->DevicePath,
             &gEfiPciIoProtocolGuid,
             &PciIoDevice->PciIo,
             NULL 
             );
      return Status;
    }    
  }

這裏PciIoDevice->LoadFile2函數就是下面的代碼:

EFI_STATUS
EFIAPI
LoadFile2 (                                                                                                                                                                                                                                   
  IN EFI_LOAD_FILE2_PROTOCOL  *This,
  IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
  IN BOOLEAN                  BootPolicy,
  IN OUT UINTN                *BufferSize,
  IN VOID                     *Buffer      OPTIONAL
  )
{
  PCI_IO_DEVICE                             *PciIoDevice;

  if (BootPolicy) {
    return EFI_UNSUPPORTED;
  }
  PciIoDevice = PCI_IO_DEVICE_FROM_LOAD_FILE2_THIS (This);

  return LocalLoadFile2 (
           PciIoDevice,
           FilePath,
           BufferSize,
           Buffer
           );
}

LocalLoadFile2的代碼如下:

EFI_STATUS
LocalLoadFile2 (
  IN PCI_IO_DEVICE            *PciIoDevice,
  IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
  IN OUT UINTN                *BufferSize,
  IN VOID                     *Buffer      OPTIONAL
  )
{
  EFI_STATUS                                Status;
  MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH   *EfiOpRomImageNode;
  EFI_PCI_EXPANSION_ROM_HEADER              *EfiRomHeader;
  PCI_DATA_STRUCTURE                        *Pcir;
  UINT32                                    ImageSize;
  UINT8                                     *ImageBuffer;
  UINT32                                    ImageLength;
  UINT32                                    DestinationSize;
  UINT32                                    ScratchSize;
  VOID                                      *Scratch;
  EFI_DECOMPRESS_PROTOCOL                   *Decompress;
  UINT32                                    InitializationSize;

  EfiOpRomImageNode = (MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH *) FilePath;
  if ((EfiOpRomImageNode == NULL) ||
      (DevicePathType (FilePath) != MEDIA_DEVICE_PATH) ||
      (DevicePathSubType (FilePath) != MEDIA_RELATIVE_OFFSET_RANGE_DP) ||
      (DevicePathNodeLength (FilePath) != sizeof (MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH)) ||
      (!IsDevicePathEnd (NextDevicePathNode (FilePath))) ||
      (EfiOpRomImageNode->StartingOffset > EfiOpRomImageNode->EndingOffset) ||
      (EfiOpRomImageNode->EndingOffset >= PciIoDevice->RomSize) ||
      (BufferSize == NULL)
      ) {
    return EFI_INVALID_PARAMETER;
  }
  EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *) (
      (UINT8 *) PciIoDevice->PciIo.RomImage + EfiOpRomImageNode->StartingOffset
      );

  if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {                                                                                                                                                        
    return EFI_NOT_FOUND;
  }
  Pcir = (PCI_DATA_STRUCTURE *) ((UINT8 *) EfiRomHeader + EfiRomHeader->PcirOffset);
  ASSERT (Pcir->Signature == PCI_DATA_STRUCTURE_SIGNATURE);

  if ((Pcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) &&
      (EfiRomHeader->EfiSignature == EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE) &&
      ((EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER) ||
       (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER)) &&
      (EfiRomHeader->CompressionType <= EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED)
       ) {
    ImageSize = Pcir->ImageLength * 512;
    InitializationSize = (UINT32) EfiRomHeader->InitializationSize * 512;
    if (InitializationSize > ImageSize || EfiRomHeader->EfiImageHeaderOffset >=  InitializationSize) {
      return EFI_NOT_FOUND;
    }
    ImageBuffer             = (UINT8 *) EfiRomHeader + EfiRomHeader->EfiImageHeaderOffset;
    ImageLength             = InitializationSize - EfiRomHeader->EfiImageHeaderOffset;

    if (EfiRomHeader->CompressionType != EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) {
      //
      // Uncompressed: Copy the EFI Image directly to user's buffer
      //
      if (Buffer == NULL || *BufferSize < ImageLength) {
        *BufferSize = ImageLength;
        return EFI_BUFFER_TOO_SMALL;
      }
      *BufferSize = ImageLength;                                                                                                                                                                                                              
      CopyMem (Buffer, ImageBuffer, ImageLength);
      return EFI_SUCCESS;
} else {
      //
      // Compressed: Uncompress before copying
      //
      Status = gBS->LocateProtocol (&gEfiDecompressProtocolGuid, NULL, (VOID **) &Decompress);
      if (EFI_ERROR (Status)) {
        return EFI_DEVICE_ERROR;
      }
      Status = Decompress->GetInfo (
                             Decompress,
                             ImageBuffer,
                             ImageLength,
                             &DestinationSize,
                             &ScratchSize
                             );
      if (EFI_ERROR (Status)) {
        return EFI_DEVICE_ERROR;
      }
      if (Buffer == NULL || *BufferSize < DestinationSize) {
        *BufferSize = DestinationSize;
        return EFI_BUFFER_TOO_SMALL;
      }
      *BufferSize = DestinationSize;
      Scratch = AllocatePool (ScratchSize);
      if (Scratch == NULL) {
        return EFI_DEVICE_ERROR;
      }
      Status = Decompress->Decompress (
                             Decompress,
                             ImageBuffer,
                             ImageLength,
                             Buffer,
                             DestinationSize,
                             Scratch,
                             ScratchSize                                                                                                                                                                                                      
                             );
      FreePool (Scratch);
      if (EFI_ERROR (Status)) {
        return EFI_DEVICE_ERROR;
      }
      return EFI_SUCCESS;
    }
  }
  return EFI_NOT_FOUND;
}

LocalLoadFile2函數是load 的回調函數,代碼細節這裏不做詳細的介紹了.很多Image 格式的要求都在代碼裏面有體現.有一點不滿足那麼就會load失敗,也就會導致執行失敗.我們平臺一開始不支持也就是這個原因.這個函數要求的條件沒有滿足,所以導致laod
失敗.
下面還是回到函數RegisterPciDevice,上面介紹萬安裝&gEfiLoadFile2ProtocolGuid之後,接下來就會調用函數ProcessOpRomImage,這個函數是真正執行load操作的,load成功之後就會去執行驅動的entrypoint函數.
下面是詳細的代碼實現:

/**
  Load and start the Option Rom image.

  @param PciDevice       Pci device instance.

  @retval EFI_SUCCESS    Successfully loaded and started PCI Option Rom image.
  @retval EFI_NOT_FOUND  Failed to process PCI Option Rom image.

**/
EFI_STATUS                                                                                                                                                                                                                                    
ProcessOpRomImage (
  IN  PCI_IO_DEVICE   *PciDevice
  )
{
  UINT8                                    Indicator;
  UINT32                                   ImageSize;
  VOID                                     *RomBar;
  UINT8                                    *RomBarOffset;
  EFI_HANDLE                               ImageHandle;
  EFI_STATUS                               Status;
  EFI_STATUS                               RetStatus;
  EFI_PCI_EXPANSION_ROM_HEADER             *EfiRomHeader;
  PCI_DATA_STRUCTURE                       *Pcir;
  EFI_DEVICE_PATH_PROTOCOL                 *PciOptionRomImageDevicePath;
  MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH  EfiOpRomImageNode;
  VOID                                     *Buffer;
  UINTN                                    BufferSize;

  Indicator = 0;
  //
  // Get the Address of the Option Rom image
  //
  RomBar        = PciDevice->PciIo.RomImage;
  RomBarOffset  = (UINT8 *) RomBar;
  RetStatus     = EFI_NOT_FOUND;
  if (RomBar == NULL) {
    return RetStatus;
  }
  ASSERT (((EFI_PCI_EXPANSION_ROM_HEADER *) RomBarOffset)->Signature == PCI_EXPANSION_ROM_HEADER_SIGNATURE);

  do {
    EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *) RomBarOffset;
    if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      RomBarOffset += 512;
      continue;
    }   

    Pcir        = (PCI_DATA_STRUCTURE *) (RomBarOffset + EfiRomHeader->PcirOffset);
    ASSERT (Pcir->Signature == PCI_DATA_STRUCTURE_SIGNATURE);
    ImageSize   = (UINT32) (Pcir->ImageLength * 512);
    Indicator   = Pcir->Indicator;

    //  
    // Skip the image if it is not an EFI PCI Option ROM image
    //  
    if (Pcir->CodeType != PCI_CODE_TYPE_EFI_IMAGE) {
      goto NextImage;
    }
    /
    // Skip the image if it is not an EFI PCI Option ROM image
    //
    if (Pcir->CodeType != PCI_CODE_TYPE_EFI_IMAGE) {
      goto NextImage;
    }

    //
    // Skip the EFI PCI Option ROM image if its machine type is not supported
    //
    if (!EFI_IMAGE_MACHINE_TYPE_SUPPORTED (EfiRomHeader->EfiMachineType)) {
      goto NextImage;
    }

    //
    // Ignore the EFI PCI Option ROM image if it is an EFI application
    //
    if (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
      goto NextImage;
    }

    //
    // Create Pci Option Rom Image device path header
    //
    EfiOpRomImageNode.Header.Type     = MEDIA_DEVICE_PATH;
    EfiOpRomImageNode.Header.SubType  = MEDIA_RELATIVE_OFFSET_RANGE_DP;
    SetDevicePathNodeLength (&EfiOpRomImageNode.Header, sizeof (EfiOpRomImageNode));
    EfiOpRomImageNode.StartingOffset  = (UINTN) RomBarOffset - (UINTN) RomBar;
    EfiOpRomImageNode.EndingOffset    = (UINTN) RomBarOffset + ImageSize - 1 - (UINTN) RomBar;
    PciOptionRomImageDevicePath = AppendDevicePathNode (PciDevice->DevicePath, &EfiOpRomImageNode.Header);
    ASSERT (PciOptionRomImageDevicePath != NULL);

    //                                                                                                                                                                                                                                        
    // load image and start image
    //
    BufferSize  = 0;
    Buffer      = NULL;
    ImageHandle = NULL;


    Status = gBS->LoadImage (
                    FALSE,
                    gPciBusDriverBinding.DriverBindingHandle,
                    PciOptionRomImageDevicePath,
                    Buffer,
                    BufferSize,
                    &ImageHandle
                    );
    if (EFI_ERROR (Status)) {
      //
      // Record the Option ROM Image device path when LoadImage fails.
      // PciOverride.GetDriver() will try to look for the Image Handle using the device path later.
      //
      AddDriver (PciDevice, NULL, PciOptionRomImageDevicePath);
} else {
      Status = gBS->StartImage (ImageHandle, NULL, NULL);
      if (!EFI_ERROR (Status)) {
        //
        // Record the Option ROM Image Handle
        //
        AddDriver (PciDevice, ImageHandle, NULL);
        PciRomAddImageMapping (
          ImageHandle,
          PciDevice->PciRootBridgeIo->SegmentNumber,
          PciDevice->BusNumber,
          PciDevice->DeviceNumber,
          PciDevice->FunctionNumber,
          PciDevice->PciIo.RomImage,
          PciDevice->PciIo.RomSize
          );
        RetStatus = EFI_SUCCESS;
      }
    }
    FreePool (PciOptionRomImageDevicePath);
NextImage:
    RomBarOffset += ImageSize;

  } while (((Indicator & 0x80) == 0x00) && (((UINTN) RomBarOffset - (UINTN) RomBar) < PciDevice->RomSize));
  return RetStatus;
}
  

由上面的代碼可知,在load成功之後就會去執行start.注意這裏面load函數內部就會走上面註冊的函數.執行完start函數,也就是運行了驅動的entrypoint之後,會執行AddDriver.
在後面Dxe結尾的時候,在函數connectcontroller函數中,會調用GetDriver函數,GetDriver函數代碼如下:

EFI_STATUS
EFIAPI
GetDriver (                                                                                                                                                                                                                                   
  IN EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN OUT EFI_HANDLE                                         *DriverImageHandle
  )
{
  PCI_IO_DEVICE             *PciIoDevice;
  LIST_ENTRY                *Link;
  PCI_DRIVER_OVERRIDE_LIST  *Override;
  BOOLEAN                   ReturnNext;

  Override    = NULL;
  PciIoDevice = PCI_IO_DEVICE_FROM_PCI_DRIVER_OVERRIDE_THIS (This);
  ReturnNext  = (BOOLEAN) (*DriverImageHandle == NULL);
  for ( Link = GetFirstNode (&PciIoDevice->OptionRomDriverList)
      ; !IsNull (&PciIoDevice->OptionRomDriverList, Link)
      ; Link = GetNextNode (&PciIoDevice->OptionRomDriverList, Link)
      ) {

    Override = DRIVER_OVERRIDE_FROM_LINK (Link);

    if (ReturnNext) {
      if (Override->DriverImageHandle == NULL) {
        Override->DriverImageHandle = LocateImageHandle (Override->DriverImagePath);
      }

      if (Override->DriverImageHandle == NULL) {
        //
        // The Option ROM identified by Override->DriverImagePath is not loaded.
        //
        continue;
      } else {
        *DriverImageHandle = Override->DriverImageHandle;
        return EFI_SUCCESS;
      }
    }
    if (*DriverImageHandle == Override->DriverImageHandle) {
      ReturnNext = TRUE;
    }
  }

  ASSERT (IsNull (&PciIoDevice->OptionRomDriverList, Link));
  //
  // ReturnNext indicates a handle match happens.
  // If all nodes are checked without handle match happening,
  // the DriverImageHandle should be a invalid handle.
  //
  if (ReturnNext) {
    return EFI_NOT_FOUND;
  } else {
    return EFI_INVALID_PARAMETER;
  }
}

在CoreconnectController中會調用函數CoreConnectSingleController,然後在這個函數內會調用GetDriver函數,代碼如下:

//  
  // Get the Bus Specific Driver Override Protocol instance on the Controller Handle
  //  
  Status = CoreHandleProtocol (
             ControllerHandle,
             &gEfiBusSpecificDriverOverrideProtocolGuid,
             (VOID **) &BusSpecificDriverOverride
             );  
  if (!EFI_ERROR (Status) && (BusSpecificDriverOverride != NULL)) {
    DriverImageHandle = NULL;
    do {
      Status = BusSpecificDriverOverride->GetDriver (
                                            BusSpecificDriverOverride,
                                            &DriverImageHandle
                                            );  
      if (!EFI_ERROR (Status)) {
        AddSortedDriverBindingProtocol (
          DriverImageHandle,
          &NumberOfSortedDriverBindingProtocols,
          SortedDriverBindingProtocols,
          DriverBindingHandleCount,
          DriverBindingHandleBuffer,
          TRUE
          );  
      }   
    } while (!EFI_ERROR (Status));
  }

執行完之後,後面在執行驅動中的support函數的時候就會調用這個驅動的support函數,然後進而執行驅動的start函數.
代碼邏輯如下:

 //
  // If the number of Driver Binding Protocols has increased since this function started, then return
  // EFI_NOT_READY, so it will be restarted
  //
  Status = CoreLocateHandleBuffer (
             ByProtocol,
             &gEfiDriverBindingProtocolGuid,
             NULL,
             &NewDriverBindingHandleCount,
             &NewDriverBindingHandleBuffer
             );
  CoreFreePool (NewDriverBindingHandleBuffer);
  if (NewDriverBindingHandleCount > DriverBindingHandleCount) {
    //
    // Free any buffers that were allocated with AllocatePool()
    //
    CoreFreePool (SortedDriverBindingProtocols);

    return EFI_NOT_READY;
  }

  //
  // Sort the remaining DriverBinding Protocol based on their Version field from
  // highest to lowest.
  //
  for ( ; SortIndex < NumberOfSortedDriverBindingProtocols; SortIndex++) {
    HighestVersion = SortedDriverBindingProtocols[SortIndex]->Version;
    HighestIndex   = SortIndex;
    for (Index = SortIndex + 1; Index < NumberOfSortedDriverBindingProtocols; Index++) {
      if (SortedDriverBindingProtocols[Index]->Version > HighestVersion) {
        HighestVersion = SortedDriverBindingProtocols[Index]->Version;
        HighestIndex   = Index;
      }
    }
    if (SortIndex != HighestIndex) {
      DriverBinding = SortedDriverBindingProtocols[SortIndex];
      SortedDriverBindingProtocols[SortIndex] = SortedDriverBindingProtocols[HighestIndex];
      SortedDriverBindingProtocols[HighestIndex] = DriverBinding;
    }
  }
  //                                                                                                                                                                                                                                          
  // Loop until no more drivers can be started on ControllerHandle
  //
    OneStarted = FALSE;
  do {

    //
    // Loop through the sorted Driver Binding Protocol Instances in order, and see if
    // any of the Driver Binding Protocols support the controller specified by
    // ControllerHandle.
    //
    DriverBinding = NULL;
    DriverFound = FALSE;
    for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
      if (SortedDriverBindingProtocols[Index] != NULL) {
        DriverBinding = SortedDriverBindingProtocols[Index];
        PERF_START (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
        Status = DriverBinding->Supported(
                                  DriverBinding,
                                  ControllerHandle,
                                  RemainingDevicePath
                                  );
        PERF_END (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
        if (!EFI_ERROR (Status)) {
          SortedDriverBindingProtocols[Index] = NULL;
          DriverFound = TRUE;

          //
          // A driver was found that supports ControllerHandle, so attempt to start the driver
          // on ControllerHandle.
          //
          PERF_START (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
          Status = DriverBinding->Start (
                                    DriverBinding,
                                    ControllerHandle,
                                    RemainingDevicePath
                                    );
          PERF_END (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);

          if (!EFI_ERROR (Status)) {                                                                                                                                                                                                          
            //
            // The driver was successfully started on ControllerHandle, so set a flag
            //
            OneStarted = TRUE;
          }
        }
      }
    }
  } while (DriverFound);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章