简述

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没有大的差别。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐