UEFI實戰——網絡啓動

簡述

UEFI下實現了若干中網絡啓動的方式,比如HTTP啓動,PXE啓動等。

對應的默認如下:

!if $(PXE_ENABLE) == TRUE
  NetworkPkg/UefiPxeBcDxe/UefiPxeBcDxe.inf
!endif
!if $(HTTP_BOOT_ENABLE) == TRUE
  NetworkPkg/DnsDxe/DnsDxe.inf
  NetworkPkg/HttpUtilitiesDxe/HttpUtilitiesDxe.inf
  NetworkPkg/HttpDxe/HttpDxe.inf
  NetworkPkg/HttpBootDxe/HttpBootDxe.inf
!endif

從它們的宏定義就可以看出來具體HTTP啓動和PXE啓動對應哪些代碼。

下面分別介紹這兩種網絡啓動方式。

 

PXE啓動

PXE網路啓動是一種比較老的網路啓動方式,在PXE簡介及使用說明有介紹具體的使用方式。

而這裏主要介紹它的實現。

首先查看NetworkPkg\UefiPxeBcDxe\UefiPxeBcDxe.inf文件,從這裏可以看到它實際上實現了兩個Protocol用於包裝整個PXE的動作:

[Protocols]
  ## TO_START
  ## SOMETIMES_CONSUMES
  gEfiDevicePathProtocolGuid                           
  gEfiNetworkInterfaceIdentifierProtocolGuid_31        ## SOMETIMES_CONSUMES
  gEfiArpServiceBindingProtocolGuid                    ## TO_START
  gEfiArpProtocolGuid                                  ## TO_START
  gEfiIp4ServiceBindingProtocolGuid                    ## TO_START
  gEfiIp4ProtocolGuid                                  ## TO_START
  gEfiIp4Config2ProtocolGuid                           ## TO_START
  gEfiIp6ServiceBindingProtocolGuid                    ## TO_START
  gEfiIp6ProtocolGuid                                  ## TO_START
  gEfiIp6ConfigProtocolGuid                            ## TO_START
  gEfiUdp4ServiceBindingProtocolGuid                   ## TO_START
  gEfiUdp4ProtocolGuid                                 ## TO_START
  gEfiMtftp4ServiceBindingProtocolGuid                 ## TO_START
  gEfiMtftp4ProtocolGuid                               ## TO_START
  gEfiDhcp4ServiceBindingProtocolGuid                  ## TO_START
  gEfiDhcp4ProtocolGuid                                ## TO_START
  gEfiUdp6ServiceBindingProtocolGuid                   ## TO_START
  gEfiUdp6ProtocolGuid                                 ## TO_START
  gEfiMtftp6ServiceBindingProtocolGuid                 ## TO_START
  gEfiMtftp6ProtocolGuid                               ## TO_START
  gEfiDhcp6ServiceBindingProtocolGuid                  ## TO_START
  gEfiDhcp6ProtocolGuid                                ## TO_START
  gEfiDns6ServiceBindingProtocolGuid                   ## SOMETIMES_CONSUMES
  gEfiDns6ProtocolGuid                                 ## SOMETIMES_CONSUMES
  gEfiPxeBaseCodeCallbackProtocolGuid                  ## SOMETIMES_PRODUCES
  gEfiPxeBaseCodeProtocolGuid                          ## BY_START
  gEfiLoadFileProtocolGuid                             ## BY_START
  gEfiAdapterInformationProtocolGuid                   ## SOMETIMES_CONSUMES

這裏的BY_START表示的是生成Protocol。

可以直接在NetworkPkg\UefiPxeBcDxe\PxeBcDriver.c中找到它們的安裝代碼:

  //
  // Create a new handle for IPv4 virtual nic,
  // and install PxeBaseCode, LoadFile and DevicePath protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Private->Ip4Nic->Controller,
                  &gEfiDevicePathProtocolGuid,
                  Private->Ip4Nic->DevicePath,
                  &gEfiLoadFileProtocolGuid,
                  &Private->Ip4Nic->LoadFile,
                  &gEfiPxeBaseCodeProtocolGuid,
                  &Private->PxeBc,
                  NULL
                  );

實際上會安裝兩次,對應IPv4和IPv6,我們還是以IPv4爲例。

可以看到這裏除了安裝上文提到的兩個Protocol,還安裝了Device Path Protocol,它是作爲通用需求安裝的,這裏不做特別說明。

///
/// The EFI_LOAD_FILE_PROTOCOL is a simple protocol used to obtain files from arbitrary devices.
///
struct _EFI_LOAD_FILE_PROTOCOL {
  EFI_LOAD_FILE LoadFile;
};

它就是一個獲取文件的接口。

事實上PXE說到底也只是一個通過網絡獲取BootLoader的過程,所以這裏的接口也很合適。

還需要說明,事實上HTTP啓動也是實現了這個接口,因爲它也只是通過網絡獲取BootLoader而已。

LoadFile的實現如下(NetworkPkg\UefiPxeBcDxe\PxeBcImpl.c):

EFI_LOAD_FILE_PROTOCOL  gLoadFileProtocolTemplate = { EfiPxeLoadFile };

它的具體實現是以來於的PxeBc

gEfiPxeBaseCodeProtocolGuid對應的Protocol稍微複雜些(MdePkg\Include\Protocol\PxeBaseCode.h):

///
/// The EFI_PXE_BASE_CODE_PROTOCOL is used to control PXE-compatible devices.
/// An EFI_PXE_BASE_CODE_PROTOCOL will be layered on top of an
/// EFI_MANAGED_NETWORK_PROTOCOL protocol in order to perform packet level transactions.
/// The EFI_PXE_BASE_CODE_PROTOCOL handle also supports the
/// EFI_LOAD_FILE_PROTOCOL protocol. This provides a clean way to obtain control from the
/// boot manager if the boot path is from the remote device.
///
struct _EFI_PXE_BASE_CODE_PROTOCOL {
  ///
  ///  The revision of the EFI_PXE_BASE_CODE_PROTOCOL. All future revisions must 
  ///  be backwards compatible. If a future version is not backwards compatible 
  ///  it is not the same GUID.
  ///
  UINT64                            Revision;
  EFI_PXE_BASE_CODE_START           Start;
  EFI_PXE_BASE_CODE_STOP            Stop;
  EFI_PXE_BASE_CODE_DHCP            Dhcp;
  EFI_PXE_BASE_CODE_DISCOVER        Discover;
  EFI_PXE_BASE_CODE_MTFTP           Mtftp;
  EFI_PXE_BASE_CODE_UDP_WRITE       UdpWrite;
  EFI_PXE_BASE_CODE_UDP_READ        UdpRead;
  EFI_PXE_BASE_CODE_SET_IP_FILTER   SetIpFilter;
  EFI_PXE_BASE_CODE_ARP             Arp;
  EFI_PXE_BASE_CODE_SET_PARAMETERS  SetParameters;
  EFI_PXE_BASE_CODE_SET_STATION_IP  SetStationIp;
  EFI_PXE_BASE_CODE_SET_PACKETS     SetPackets;
  ///
  /// The pointer to the EFI_PXE_BASE_CODE_MODE data for this device.
  ///
  EFI_PXE_BASE_CODE_MODE            *Mode;
};

它實際上需要完成網絡通信相關的動作,比如DHCP/TFPT等。

PxeBc的實現如下(NetworkPkg\UefiPxeBcDxe\PxeBcImpl.c):

EFI_PXE_BASE_CODE_PROTOCOL  gPxeBcProtocolTemplate = {
  EFI_PXE_BASE_CODE_PROTOCOL_REVISION,
  EfiPxeBcStart,
  EfiPxeBcStop,
  EfiPxeBcDhcp,
  EfiPxeBcDiscover,
  EfiPxeBcMtftp,
  EfiPxeBcUdpWrite,
  EfiPxeBcUdpRead,
  EfiPxeBcSetIpFilter,
  EfiPxeBcArp,
  EfiPxeBcSetParameters,
  EfiPxeBcSetStationIP,
  EfiPxeBcSetPackets,
  NULL
};

PxeBcLoadFile之間的關係是通過如下的結構體連接在一起的(NetworkPkg\UefiPxeBcDxe\PxeBcImpl.h):

struct _PXEBC_VIRTUAL_NIC {
  UINT32                                    Signature;
  EFI_HANDLE                                Controller;
  EFI_LOAD_FILE_PROTOCOL                    LoadFile;
  EFI_DEVICE_PATH_PROTOCOL                  *DevicePath;
  PXEBC_PRIVATE_DATA                        *Private;
};

 

HTTP啓動

HTTP雖然本身也不是什麼新東西,但是用作網絡啓動還是比PXE要新。

相比PXE使用TFTP下載BootLoader,HTTP啓動當然就是使用HTTP來下載Boot Loader。

兩者雖沒有本質的區別,但是HTTP具有更廣泛的適用性。

下面是HTTP啓動的環境框圖:

BootLoader位於HTTP服務器上,而不是PXE啓動的TFTP服務器上。且HTTP使用URI來訪問服務器設備,而PXE使用IP來訪問服務器設備。

HTTP啓動的實現相比PXE啓動要稍微複雜些,涉及到DNS/DHCP/URL的等內容。

不過關鍵還是NetworkPkg\HttpBootDxe\HttpBootDxe.inf模塊,它實現了EFI_LOAD_FILE_PROTOCOL(NetworkPkg\HttpBootDxe\HttpBootDxe.c):

  //
  // Create a child handle for the HTTP boot and install DevPath and Load file protocol on it.
  //
  CopyMem (&Private->Ip4Nic->LoadFile, &gHttpBootDxeLoadFile, sizeof (EFI_LOAD_FILE_PROTOCOL));
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Private->Ip4Nic->Controller,
                  &gEfiLoadFileProtocolGuid,
                  &Private->Ip4Nic->LoadFile,
                  &gEfiDevicePathProtocolGuid,
                  Private->Ip4Nic->DevicePath,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

它也分爲IPv4和IPv6兩種類型。

對應的LoadFile實現(NetworkPkg\HttpBootDxe\HttpBootImpl.c):

///
/// Load File Protocol instance
///
GLOBAL_REMOVE_IF_UNREFERENCED 
EFI_LOAD_FILE_PROTOCOL  gHttpBootDxeLoadFile = {
  HttpBootDxeLoadFile
};

另外還有一些Protocol用來處理HTTP通信的內容,比如:

///
/// EFI_HTTP_UTILITIES_PROTOCOL
/// designed to be used by EFI drivers and applications to parse HTTP
/// headers from a byte stream. This driver is neither dependent on
/// network connectivity, nor the existence of an underlying network
/// infrastructure.
///
struct _EFI_HTTP_UTILITIES_PROTOCOL {
  EFI_HTTP_UTILS_BUILD          Build;
  EFI_HTTP_UTILS_PARSE          Parse;
};
///
/// The EFI_DNS4_Protocol provides the function to get the host name and address
/// mapping, also provides pass through interface to retrieve arbitrary information
/// from DNS.
///
struct _EFI_DNS4_PROTOCOL {
  EFI_DNS4_GET_MODE_DATA        GetModeData;
  EFI_DNS4_CONFIGURE            Configure;
  EFI_DNS4_HOST_NAME_TO_IP      HostNameToIp;
  EFI_DNS4_IP_TO_HOST_NAME      IpToHostName;
  EFI_DNS4_GENERAL_LOOKUP       GeneralLookUp;
  EFI_DNS4_UPDATE_DNS_CACHE     UpdateDnsCache;
  EFI_DNS4_POLL                 Poll;
  EFI_DNS4_CANCEL               Cancel;
};

等。

關於HTTP啓動,還可以參考:https://github.com/tianocore/tianocore.github.io/wiki/HTTP-Boot

 

網路啓動項

簡單介紹完HTTP啓動和PXE啓動,後續需要關注的是如何使用這些啓動實現。

其實前面已經介紹過,這主要通過EFI_LOAD_FILE_PROTOCOL 來完成。

對於網路啓動,導致的代碼如下:

  Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiLoadFileProtocolGuid, NULL, &HandleCount, &Handles);
  if (EFI_ERROR (Status)) {
    HandleCount = 0;
    Handles = NULL;
  }
  NextFullPath = NULL;
  GetNext = (BOOLEAN)(FullPath == NULL);
  for (Index = 0; Index < HandleCount; Index++) {
    NextFullPath = BmExpandLoadFile (Handles[Index], FilePath);

這裏獲取到所有實現了的EFI_LOAD_FILE_PROTOCOL ,針對每一個Protocol,調用其LoadFile來獲取文件。

但是需要注意,因爲HTTP啓動和PXE啓動使用的地址是不同的,所以對應到Device Path也是不同的:

/**
  Causes the driver to load a specified file.
  @param  This       Protocol instance pointer.
  @param  FilePath   The device specific path of the file to load.
  @param  BootPolicy If TRUE, indicates that the request originates from the
                     boot manager is attempting to load FilePath as a boot
                     selection. If FALSE, then FilePath must match as exact file
                     to be loaded.
  @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 the size of the requested file is returned in
                     BufferSize.

  @retval EFI_SUCCESS           The file was loaded.
  @retval EFI_UNSUPPORTED       The device does not support the provided BootPolicy
  @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or
                                BufferSize is NULL.
  @retval EFI_NO_MEDIA          No medium was present to load the file.
  @retval EFI_DEVICE_ERROR      The file was not loaded due to a device error.
  @retval EFI_NO_RESPONSE       The remote system did not respond.
  @retval EFI_NOT_FOUND         The file was not found.
  @retval EFI_ABORTED           The file load process was manually cancelled.
  @retval EFI_WARN_FILE_SYSTEM  The resulting Buffer contains UEFI-compliant file system.
**/

typedef
EFI_STATUS
(EFIAPI *EFI_LOAD_FILE)(
  IN EFI_LOAD_FILE_PROTOCOL           *This,
  IN EFI_DEVICE_PATH_PROTOCOL         *FilePath,
  IN BOOLEAN                          BootPolicy,
  IN OUT UINTN                        *BufferSize,
  IN VOID                             *Buffer OPTIONAL
  );

即這裏的FilePath有不同的取值。

HTTP對應的Device Path結構體:

///
/// Uniform Resource Identifiers (URI) Device Path SubType
///
#define MSG_URI_DP                0x18
typedef struct {
  EFI_DEVICE_PATH_PROTOCOL        Header;
  ///
  /// Instance of the URI pursuant to RFC 3986.
  ///
  CHAR8                           Uri[];
} URI_DEVICE_PATH;

HttpBootDxeLoadFile實現中會需要使用到Device Path:

EFI_STATUS
EFIAPI
HttpBootDxeLoadFile (
  IN EFI_LOAD_FILE_PROTOCOL           *This,
  IN EFI_DEVICE_PATH_PROTOCOL         *FilePath,
  IN BOOLEAN                          BootPolicy,
  IN OUT UINTN                        *BufferSize,
  IN VOID                             *Buffer OPTIONAL
  )
{

  HTTP_BOOT_PRIVATE_DATA        *Private;
  HTTP_BOOT_VIRTUAL_NIC         *VirtualNic;
  BOOLEAN                       MediaPresent;
  BOOLEAN                       UsingIpv6;
  EFI_STATUS                    Status;
  HTTP_BOOT_IMAGE_TYPE          ImageType;

  if (This == NULL || BufferSize == NULL || FilePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Only support BootPolicy
  //
  if (!BootPolicy) {
    return EFI_UNSUPPORTED;
  }

  VirtualNic = HTTP_BOOT_VIRTUAL_NIC_FROM_LOADFILE (This);
  Private = VirtualNic->Private;
 
  //
  // Check media status before HTTP boot start
  //
  MediaPresent = TRUE;
  NetLibDetectMedia (Private->Controller, &MediaPresent);
  if (!MediaPresent) {
    return EFI_NO_MEDIA;
  }
  

  //
  // Check whether the virtual nic is using IPv6 or not.
  //
  UsingIpv6 = FALSE;
  if (VirtualNic == Private->Ip6Nic) {
    UsingIpv6 = TRUE;
  }

  //
  // Initialize HTTP boot.
  //
  Status = HttpBootStart (Private, UsingIpv6, FilePath);
  if (Status != EFI_SUCCESS && Status != EFI_ALREADY_STARTED) {
    return Status;
  }

而PXE啓動並不需要特別的Device Path:

EFI_STATUS
EFIAPI
EfiPxeLoadFile (
  IN     EFI_LOAD_FILE_PROTOCOL           *This,
  IN     EFI_DEVICE_PATH_PROTOCOL         *FilePath,
  IN     BOOLEAN                          BootPolicy,
  IN OUT UINTN                            *BufferSize,
  IN     VOID                             *Buffer       OPTIONAL
  )
{
  PXEBC_PRIVATE_DATA          *Private;
  PXEBC_VIRTUAL_NIC           *VirtualNic;
  EFI_PXE_BASE_CODE_PROTOCOL  *PxeBc;
  BOOLEAN                     UsingIpv6;
  EFI_STATUS                  Status;
  BOOLEAN                     MediaPresent;

  if (FilePath == NULL || !IsDevicePathEnd (FilePath)) {
    return EFI_INVALID_PARAMETER;
  }

這裏只做了判斷,但是沒有真正使用。

除了Device Path,兩者調用LoadFile沒有大的差別。

 

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