【UEFI实战】网络启动
【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
};
而PxeBc和LoadFile之间的关系是通过如下的结构体连接在一起的(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没有大的差别。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)