当前位置: 首页 > 工具软件 > PCI工具包 > 使用案例 >

PCI Option ROM 在UEFI中加载流程

邵兴怀
2023-12-01

目录

一、UEFI Driver 是什么?

二、UEFI Driver 加载流程

1.EFI_DRIVER_BINDING_PROTOCOL

2. PCI扫描过程中是如何确定PCI设备是支持option rom的?

3. Option Rom驱动后面是如何被处理的?

3. Option Rom驱动是如何被load以及执行的

总结



一、UEFI Driver 是什么?

参考:BIOS知识枝桠——UEFI Driver_这里是为我所统率的战场-CSDN博客

UEFI中的Option ROM 驱动详解_太初有道-CSDN博客_optionrom

二、UEFI Driver 加载流程

1.EFI_DRIVER_BINDING_PROTOCOL

这个协议是由遵循UEFI驱动程序模型的每个驱动程序产生的,它是允许管理驱动程序和控制器的中心组件。它提供了一个服务来测试一个驱动程序是否支持一个特定的控制器,一个服务来启动管理一个控制器,一个服务来停止管理一个控制器。这些服务同样适用于总线控制器和设备控制器的驱动程序。(具体内容去UEFI Spec 查看)

GUID
#define EFI_DRIVER_BINDING_PROTOCOL_GUID \
{0x18A031AB,0xB443,0x4D1A,\
 {0xA5,0xC0,0x0C,0x09,0x26,0x1E,0x9F,0x71}}
Protocol Interface Structure
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
  EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED Supported;
  EFI_DRIVER_BINDING_PROTOCOL_START     Start;
  EFI_DRIVER_BINDING_PROTOCOL_STOP      Stop;
  UINT32                                Version;
  EFI_HANDLE                            ImageHandle;
  EFI_HANDLE                            DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;

2. PCI扫描过程中是如何确定PCI设备是支持option rom的?

PciBusDriverBindingStart->PciEnumerator->PciHostBridgeEnumerator->PciRootBridgeEnumerator->PciScanBus->PciSearchDevice->GetOpRomInfo

/**
  Get Pci device's oprom information.

  @param PciIoDevice    Input Pci device instance.
                        Output Pci device instance with updated OptionRom size.

  @retval EFI_NOT_FOUND Pci device has not Option Rom.
  @retval EFI_SUCCESS   Pci device has Option Rom.

**/
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 = (UINT64) ((~AllOnes) + 1);
  return EFI_SUCCESS;
}

在GetOpRomInfo中这里只是探测了这个设备是否有Rom,如果有读取RomSize的大小到PciIoDevice中。这时探测到了这个pci设备包含的rom大小。

然后在函数PciHostBridgeResourceAllocator总调用ProcessOptionRom对optionrom进行处理,函数调用关系如下:
PciBusDriverBindingStar->PciEnumerator->PciHostBridgeResourceAllocator
->ProcessOptionRom
 

/**
  This routine is used to process all PCI devices' Option Rom
  on a certain root bridge.

  @param Bridge     Given parent's root bridge.
  @param RomBase    Base address of ROM driver loaded from.
  @param MaxLength  Maximum rom size.

**/
VOID
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的代码实现:

/**
  Load Option Rom image for specified PCI device.

  @param PciDevice Pci device instance.
  @param RomBase   Base address of Option Rom.

  @retval EFI_OUT_OF_RESOURCES No enough memory to hold image.
  @retval EFI_SUCESS           Successfully loaded Option Rom.

**/
EFI_STATUS
LoadOpRomImage (
  IN PCI_IO_DEVICE   *PciDevice,
  IN UINT64          RomBase
  )
{
  UINT8                     RomBarIndex;
  UINT8                     Indicator;
  UINT16                    OffsetPcir;
  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;
  BOOLEAN                   HasEfiOpRom;

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

  RomBar = (UINT32) RomBase;

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

  RomBarOffset  = RomBar;
  RetStatus     = EFI_NOT_FOUND;
  FirstCheck    = TRUE;
  LegacyImageLength = 0;
  HasEfiOpRom   = FALSE;

  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;
    } else if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
      HasEfiOpRom = TRUE;
    }
    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->HasEfiOpRom    = HasEfiOpRom;
  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,
    (UINT64) (UINTN) PciDevice->PciIo.RomImage,
    PciDevice->PciIo.RomSize
    );

  //
  // Free allocated memory
  //
  FreePool (RomHeader);
  FreePool (RomPcir);

  return RetStatus;
}

PciRomAddImageMapping :作用是将这个设备的信息Seg号,Bus号, Dev号,Fun号,ImageHandle,RomImage(load到内存之后的地址),RomSize就是rom的大小都保存在了全局变量mRomImageTable内,这是一个全局的指针,每添加一个变量指针指针位置向后移动.这样在扫描过程中将所有的支持oprom 的pci设备都保存在了mRomImageTable内了.

根据以上代码可知,首先读取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;

EFI_PCI_EXPANSION_ROM_HEADER

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

3. Option Rom驱动后面是如何被处理的?

PciBusDriverBindingStart->PciEnumerator->PciEnumeratorLight

/**
  Process the option ROM for all the children of the specified parent PCI device.
  It can only be used after the first full Option ROM process.

  @param PciIoDevice Pci device instance.

**/
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);这个函数的代码如下:

/**
  Get Option rom driver's mapping for PCI device.

  @param PciIoDevice Device instance.

  @retval TRUE   Found Image mapping.
  @retval FALSE  Cannot found image mapping.

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

  PciRootBridgeIo = PciIoDevice->PciRootBridgeIo;
  Found           = FALSE;

  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    ) {
        Found = TRUE;

      if (mRomImageTable[Index].ImageHandle != NULL) {
        AddDriver (PciIoDevice, mRomImageTable[Index].ImageHandle);
      } else {
        PciIoDevice->PciIo.RomImage = (VOID *) (UINTN) mRomImageTable[Index].RomAddress;
        PciIoDevice->PciIo.RomSize  = (UINTN) mRomImageTable[Index].RomLength;
      }
    }
  }

  return Found;
}

根据上面的代码可知,如果传进来的这个设备的信息和之前枚举设备时添加到mRomImageTable中的设备信息匹配上之后,就会调用AddDriver,用这设备的ImageHandle,和PciIoDevice信息创建一个驱动,并添加到pci设备的OptionRomDriverList中,AddDriver
的代码如下:
 

/**
  Add an overriding driver image.

  @param PciIoDevice        Instance of PciIo device.
  @param DriverImageHandle  new added driver image.

  @retval EFI_SUCCESS          Successfully added driver.
  @retval EFI_OUT_OF_RESOURCES No memory resource for new driver instance.
  @retval other                Some error occurred when locating gEfiLoadedImageProtocolGuid.

**/
EFI_STATUS
AddDriver (
  IN PCI_IO_DEVICE     *PciIoDevice,
  IN EFI_HANDLE        DriverImageHandle
  )
{
  EFI_STATUS                    Status;
  EFI_LOADED_IMAGE_PROTOCOL     *LoadedImage;
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;
  PCI_DRIVER_OVERRIDE_LIST      *Node;

  Status = gBS->HandleProtocol (DriverImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **) &LoadedImage);
  if (EFI_ERROR (Status)) {
    return Status;
  }

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

  Node->Signature         = DRIVER_OVERRIDE_SIGNATURE;
  Node->DriverImageHandle = DriverImageHandle;

  InsertTailList (&PciIoDevice->OptionRomDriverList, &(Node->Link));

  PciIoDevice->BusOverride  = TRUE;

  ImageContext.Handle    = LoadedImage->ImageBase;
  ImageContext.ImageRead = PeCoffLoaderImageReadFromMemory;

  //
  // Get information about the image
  //
  PeCoffLoaderGetImageInfo (&ImageContext);

  return EFI_SUCCESS;
}

AddDriver的主要作用就是创建这个设备驱动的devicepatch路径,并将驱动,这个驱动的节点添加到OptionRomDriverList链表中.这里驱动节点的类型是PCI_DRIVER_OVERRIDE_LIST,并更新PciIoDevice->BusOverride = TRUE;后面会使用这个标志.
这样就将一个option rom驱动添加到了驱动列表列面.
 

3. Option Rom驱动是如何被load以及执行的

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;
  BOOLEAN                   FirstCheck;

  FirstCheck = TRUE;
  RomHeader  = RomImage;

  while ((UINT8 *) RomHeader < (UINT8 *) RomImage + RomSize) {
    if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      if (FirstCheck) {
        return FALSE;
      } else {
        RomHeader = (PCI_EXPANSION_ROM_HEADER *) ((UINT8 *) RomHeader + 512);
        continue;
      }
    }

    FirstCheck = FALSE;
    RomPcir    = (PCI_DATA_STRUCTURE *) ((UINT8 *) RomHeader + RomHeader->PcirOffset);

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

    RomHeader = (PCI_EXPANSION_ROM_HEADER *) ((UINT8 *) RomHeader + RomPcir->Length * 512);
  }

  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函数就是下面的代码:

/**
  Causes the driver to load a specified file.

  @param This        Indicates a pointer to the calling context.
  @param FilePath    The device specific path of the file to load.
  @param BootPolicy  Should always be FALSE.
  @param BufferSize  On input the size of Buffer in bytes. On output with a return
                     code of EFI_SUCCESS, the amount of data transferred to Buffer.
                     On output with a return code of EFI_BUFFER_TOO_SMALL,
                     the size of Buffer required to retrieve the requested file.
  @param Buffer      The memory buffer to transfer the file to. If Buffer is NULL,
                     then no the size of the requested file is returned in BufferSize.

  @retval EFI_SUCCESS           The file was loaded.
  @retval EFI_UNSUPPORTED       BootPolicy is TRUE.
  @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or
                                BufferSize is NULL.
  @retval EFI_NOT_FOUND         Not found PCI Option Rom on PCI device.
  @retval EFI_DEVICE_ERROR      Failed to decompress PCI Option Rom image.
  @retval EFI_BUFFER_TOO_SMALL  The BufferSize is too small to read the current directory entry.
                                BufferSize has been updated with the size needed to complete the request.

**/
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的代码如下:

/**
  Load the EFI Image from Option ROM

  @param PciIoDevice   PCI IO device instance.
  @param FilePath      The file path of the EFI Image
  @param BufferSize    On input the size of Buffer in bytes. On output with a return
                       code of EFI_SUCCESS, the amount of data transferred to Buffer.
                       On output with a return code of EFI_BUFFER_TOO_SMALL,
                       the size of Buffer required to retrieve the requested file.
  @param Buffer        The memory buffer to transfer the file to. If Buffer is NULL,
                       then no the size of the requested file is returned in BufferSize.

  @retval EFI_SUCCESS           The file was loaded.
  @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or
                                BufferSize is NULL.
  @retval EFI_NOT_FOUND         Not found PCI Option Rom on PCI device.
  @retval EFI_DEVICE_ERROR      Failed to decompress PCI Option Rom image.
  @retval EFI_BUFFER_TOO_SMALL  The BufferSize is too small to read the current directory entry.
                                BufferSize has been updated with the size needed to complete the request.
**/
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;

  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);


  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               = (UINT32) EfiRomHeader->InitializationSize * 512;
    ImageBuffer             = (UINT8 *) EfiRomHeader + EfiRomHeader->EfiImageHeaderOffset;
    ImageLength             = ImageSize - 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失败,也就会导致执行失败..
下面还是回到函数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;
  BOOLEAN                                  FirstCheck;
  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;
  EFI_LEGACY_BIOS_PROTOCOL                 *LegacyBios;
  KERNEL_CONFIGURATION                     KernelConfig;

  Status = GetKernelConfiguration (&KernelConfig);

  if (Status == EFI_SUCCESS) {
    //
    // Legacy Boot mode, skip loading Native driver.
    //
    if (KernelConfig.BootType == LEGACY_BOOT_TYPE) {
      return EFI_SUCCESS;
    }
    //
    // In Dual boot mode, the Vga device will use the legacy ROM.
    // skip the native GOP driver.
    //
    if (KernelConfig.BootType == DUAL_BOOT_TYPE) {
      if (PciDevice->Pci.Hdr.ClassCode[2] == PCI_CLASS_DISPLAY) {
        Status = gBS->LocateProtocol (&gEfiLegacyBiosProtocolGuid, NULL, (VOID **)&LegacyBios);
        if (!EFI_ERROR (Status)) {

          Indicator = 0;

          //
          // Get the Address of the Rom image
          //
          RomBar        = PciDevice->PciIo.RomImage;
          RomBarOffset  = (UINT8 *) RomBar;
          RetStatus     = EFI_NOT_FOUND;
          FirstCheck    = TRUE;

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

            FirstCheck  = FALSE;
            Pcir        = (PCI_DATA_STRUCTURE *) (RomBarOffset + EfiRomHeader->PcirOffset);
            ImageSize   = (UINT32) (Pcir->ImageLength * 512);
            Indicator   = Pcir->Indicator;
            RomBarOffset = RomBarOffset + ImageSize;
            if (Pcir->CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
              //
              // If BIOS support Legacy and the PCI device has Legacy oprom,then system will select legacy VGA rom to display.
              //
              return EFI_SUCCESS;
            }
          } while (((Indicator & 0x80) == 0x00) && ((UINTN) (RomBarOffset - (UINT8 *) RomBar) < PciDevice->RomSize));
        }
      }
    }
  }

  Indicator = 0;

  //
  // Get the Address of the Option Rom image
  //
  RomBar        = PciDevice->PciIo.RomImage;
  RomBarOffset  = (UINT8 *) RomBar;
  RetStatus     = EFI_NOT_FOUND;
  FirstCheck    = TRUE;

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

    FirstCheck  = FALSE;
    Pcir        = (PCI_DATA_STRUCTURE *) (RomBarOffset + EfiRomHeader->PcirOffset);
    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 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
                    );

    FreePool (PciOptionRomImageDevicePath);

    if (!EFI_ERROR (Status)) {
      Status = gBS->StartImage (ImageHandle, NULL, NULL);
      if (!EFI_ERROR (Status)) {
        AddDriver (PciDevice, ImageHandle);
        PciRomAddImageMapping (
          ImageHandle,
          PciDevice->PciRootBridgeIo->SegmentNumber,
          PciDevice->BusNumber,
          PciDevice->DeviceNumber,
          PciDevice->FunctionNumber,
          (UINT64) (UINTN) PciDevice->PciIo.RomImage,
          PciDevice->PciIo.RomSize
          );
        RetStatus = EFI_SUCCESS;
      }
    }

NextImage:
    RomBarOffset += ImageSize;

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

  return RetStatus;
}

由上面的代码可知,在load成功之后就会去执行start.注意这里面load函数内部就会走上面注册的函数.执行完start函数,也就是运行了驱动的entrypoint之后,会执行AddDriver.
在后面Dxe结尾的时候,在函数connectcontroller函数中,会调用GetDriver函数,GetDriver函数代码如下:
 

/**
  Uses a bus specific algorithm to retrieve a driver image handle for a controller.

  @param  This                  A pointer to the EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL instance.
  @param  DriverImageHandle     On input, a pointer to the previous driver image handle returned
                                by GetDriver(). On output, a pointer to the next driver
                                image handle. Passing in a NULL, will return the first driver
                                image handle.

  @retval EFI_SUCCESS           A bus specific override driver is returned in DriverImageHandle.
  @retval EFI_NOT_FOUND         The end of the list of override drivers was reached.
                                A bus specific override driver is not returned in DriverImageHandle.
  @retval EFI_INVALID_PARAMETER DriverImageHandle is not a handle that was returned on a
                                previous call to GetDriver().

**/
EFI_STATUS
EFIAPI
GetDriver (
  IN EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN OUT EFI_HANDLE                                         *DriverImageHandle
  )
{
  PCI_IO_DEVICE             *PciIoDevice;
  LIST_ENTRY                *CurrentLink;
  PCI_DRIVER_OVERRIDE_LIST  *Node;

  PciIoDevice = PCI_IO_DEVICE_FROM_PCI_DRIVER_OVERRIDE_THIS (This);

  CurrentLink = PciIoDevice->OptionRomDriverList.ForwardLink;

  while (CurrentLink != NULL && CurrentLink != &PciIoDevice->OptionRomDriverList) {

    Node = DRIVER_OVERRIDE_FROM_LINK (CurrentLink);

    if (*DriverImageHandle == NULL) {

      *DriverImageHandle = Node->DriverImageHandle;
      return EFI_SUCCESS;
    }

    if (*DriverImageHandle == Node->DriverImageHandle) {

      if (CurrentLink->ForwardLink == &PciIoDevice->OptionRomDriverList ||
          CurrentLink->ForwardLink == NULL) {
        return EFI_NOT_FOUND;
      }

      //
      // Get next node
      //
      Node                = DRIVER_OVERRIDE_FROM_LINK (CurrentLink->ForwardLink);
      *DriverImageHandle  = Node->DriverImageHandle;
      return EFI_SUCCESS;
    }

    CurrentLink = CurrentLink->ForwardLink;
  }

  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函数.
代码逻辑如下:

  // Then add all the remaining Driver Binding Protocols
  //
  SortIndex = NumberOfSortedDriverBindingProtocols;
  for (Index = 0; Index < DriverBindingHandleCount; Index++) {
    AddSortedDriverBindingProtocol (
      DriverBindingHandleBuffer[Index],
      &NumberOfSortedDriverBindingProtocols,
      SortedDriverBindingProtocols,
      DriverBindingHandleCount,
      DriverBindingHandleBuffer,
      FALSE
      );
  }

  //
  // Free the Driver Binding Handle Buffer
  //
  CoreFreePool (DriverBindingHandleBuffer);

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


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

 类似资料: