PXE
PXE代码综述
PXE的全称是Preboot eXecute Environment,它可以认为是BIOS特有的协议,因为它的作用就是通过网络启动操作系统。其实现在NetworkPkg\UefiPxeBcDxe\UefiPxeBcDxe.inf,入口如下:
EFI_STATUS
EFIAPI
PxeBcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gPxeBcIp4DriverBinding,
ImageHandle,
&gPxeBcComponentName,
&gPxeBcComponentName2
);
}
这里有IPv4和IPv6两个版本,这里以IPv4为例,它也是一个UEFI Driver Model,安装的EFI_DRIVER_BINDING_PROTOCOL
如下:
EFI_DRIVER_BINDING_PROTOCOL gPxeBcIp4DriverBinding = {
PxeBcIp4DriverBindingSupported,
PxeBcIp4DriverBindingStart,
PxeBcIp4DriverBindingStop,
0xa,
NULL,
NULL
};
PxeBcIp4DriverBindingSupported
Supported函数如下:
EFI_STATUS
EFIAPI
PxeBcIp4DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
return PxeBcSupported (
This,
ControllerHandle,
RemainingDevicePath,
IP_VERSION_4
);
}
这里用PxeBcSupported()
包装一层,是为了统一处理IPv4和IPv6,对于IPv4来说,其依赖的Protocol:
EFI_STATUS
EFIAPI
PxeBcSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL,
IN UINT8 IpVersion
)
{
EFI_STATUS Status;
EFI_GUID *DhcpServiceBindingGuid;
EFI_GUID *MtftpServiceBindingGuid;
if (IpVersion == IP_VERSION_4) {
if (PcdGet8 (PcdIPv4PXESupport) == PXE_DISABLED) {
return EFI_UNSUPPORTED;
}
DhcpServiceBindingGuid = &gEfiDhcp4ServiceBindingProtocolGuid;
MtftpServiceBindingGuid = &gEfiMtftp4ServiceBindingProtocolGuid;
可以看到其依赖的是gEfiDhcp4ServiceBindingProtocolGuid
和gEfiMtftp4ServiceBindingProtocolGuid
。
PxeBcIp4DriverBindingStart
Start函数如下:
EFI_STATUS
EFIAPI
PxeBcIp4DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
return PxeBcStart (
This,
ControllerHandle,
RemainingDevicePath,
IP_VERSION_4
);
}
同样PxeBcStart()
也包装了IPv4和IPv6的实现,这里只关注IPv4的版本。其流程大致如下:
- 创建和初始化
PXEBC_PRIVATE_DATA
结构体。 - 执行
PxeBcCreateIp4Children()
,该函数的大部分代码也是初始化PXEBC_PRIVATE_DATA
结构体中的内容,其中最重要的是其成员Ip4Nic
,它表示的是一个PXE子项。最终还会安装gEfiLoadFileProtocolGuid
和gEfiPxeBaseCodeProtocolGuid
对应的两个Protocol,分别对应:
struct _EFI_LOAD_FILE_PROTOCOL {
EFI_LOAD_FILE LoadFile;
};
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;
};
后面会进一步介绍这些Protocol。不过此前先要说明PXEBC_PRIVATE_DATA
这个结构体。
PXEBC_PRIVATE_DATA
PXEBC_PRIVATE_DATA
在PxeBcStart()
函数中创建:
EFI_STATUS
EFIAPI
PxeBcStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL,
IN UINT8 IpVersion
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
PXEBC_PRIVATE_PROTOCOL *Id;
BOOLEAN FirstStart;
FirstStart = FALSE;
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiCallerIdGuid,
(VOID **)&Id,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (!EFI_ERROR (Status)) {
//
// Skip the initialization if the driver has been started already.
//
Private = PXEBC_PRIVATE_DATA_FROM_ID (Id);
} else {
FirstStart = TRUE;
//
// If the driver has not been started yet, it should do initialization.
//
Private = AllocateZeroPool (sizeof (PXEBC_PRIVATE_DATA));
而且不同于之前的网路协议中的数据结构会根据服务结构体来配置,PXE只有一个PXEBC_PRIVATE_DATA
就足够了。该结构体位于NetworkPkg\UefiPxeBcDxe\PxeBcImpl.h:
struct _PXEBC_PRIVATE_DATA {
UINT32 Signature;
EFI_HANDLE Controller;
EFI_HANDLE Image;
PXEBC_PRIVATE_PROTOCOL Id;
EFI_SIMPLE_NETWORK_PROTOCOL *Snp;
PXEBC_VIRTUAL_NIC *Ip4Nic;
PXEBC_VIRTUAL_NIC *Ip6Nic;
EFI_HANDLE ArpChild;
EFI_HANDLE Ip4Child;
EFI_HANDLE Dhcp4Child;
EFI_HANDLE Mtftp4Child;
EFI_HANDLE Udp4ReadChild;
EFI_HANDLE Udp4WriteChild;
EFI_ARP_PROTOCOL *Arp;
EFI_IP4_PROTOCOL *Ip4;
EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2;
EFI_DHCP4_PROTOCOL *Dhcp4;
EFI_MTFTP4_PROTOCOL *Mtftp4;
EFI_UDP4_PROTOCOL *Udp4Read;
EFI_UDP4_PROTOCOL *Udp4Write;
EFI_HANDLE Ip6Child;
EFI_HANDLE Dhcp6Child;
EFI_HANDLE Mtftp6Child;
EFI_HANDLE Udp6ReadChild;
EFI_HANDLE Udp6WriteChild;
EFI_IP6_PROTOCOL *Ip6;
EFI_IP6_CONFIG_PROTOCOL *Ip6Cfg;
EFI_DHCP6_PROTOCOL *Dhcp6;
EFI_MTFTP6_PROTOCOL *Mtftp6;
EFI_UDP6_PROTOCOL *Udp6Read;
EFI_UDP6_PROTOCOL *Udp6Write;
EFI_DNS6_PROTOCOL *Dns6;
EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *Nii;
EFI_PXE_BASE_CODE_PROTOCOL PxeBc;
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL LoadFileCallback;
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL *PxeBcCallback;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
EFI_PXE_BASE_CODE_MODE Mode;
EFI_PXE_BASE_CODE_FUNCTION Function;
UINT32 Ip6Policy;
UINT32 SolicitTimes;
UINT64 ElapsedTime;
EFI_UDP4_CONFIG_DATA Udp4CfgData;
EFI_UDP6_CONFIG_DATA Udp6CfgData;
EFI_IP4_CONFIG_DATA Ip4CfgData;
EFI_IP6_CONFIG_DATA Ip6CfgData;
EFI_EVENT UdpTimeOutEvent;
EFI_EVENT ArpUpdateEvent;
EFI_IP4_COMPLETION_TOKEN IcmpToken;
EFI_IP6_COMPLETION_TOKEN Icmp6Token;
BOOLEAN IsAddressOk;
BOOLEAN IsOfferSorted;
BOOLEAN IsProxyRecved;
BOOLEAN IsDoDiscover;
EFI_IP_ADDRESS TmpStationIp;
EFI_IP_ADDRESS StationIp;
EFI_IP_ADDRESS SubnetMask;
EFI_IP_ADDRESS GatewayIp;
EFI_IP_ADDRESS ServerIp;
EFI_IPv6_ADDRESS *DnsServer;
UINT16 CurSrcPort;
UINT32 IaId;
UINT32 Ip4MaxPacketSize;
UINT32 Ip6MaxPacketSize;
UINT8 *BootFileName;
UINTN BootFileSize;
UINTN BlockSize;
PXEBC_DHCP_PACKET_CACHE ProxyOffer;
PXEBC_DHCP_PACKET_CACHE DhcpAck;
PXEBC_DHCP_PACKET_CACHE PxeReply;
EFI_DHCP6_PACKET *Dhcp6Request;
EFI_DHCP4_PACKET SeedPacket;
//
// OfferIndex records the index of DhcpOffer[] buffer, and OfferCount records the num of each type of offer.
//
// It supposed that
//
// OfferNum: 8
// OfferBuffer: [ProxyBinl, ProxyBinl, DhcpOnly, ProxyPxe10, DhcpOnly, DhcpPxe10, DhcpBinl, ProxyBinl]
// (OfferBuffer is 0-based.)
//
// And assume that (DhcpPxe10 is the first priority actually.)
//
// SelectIndex: 2
// SelectProxyType: PXEBC_OFFER_TYPE_PROXY_BINL
// (SelectIndex is 1-based, and 0 means no one is selected.)
//
// So it should be
//
// DhcpOnly DhcpPxe10 DhcpWfm11a DhcpBinl ProxyPxe10 ProxyWfm11a ProxyBinl Bootp
// OfferCount: [ 2(n), 1(n), 0(n), 1(n), 1(1), 0(1), 3(n), 1(1)]
//
// OfferIndex: {[ 2, 5, 0, 6, 3, 0, *0, 0]
// [ 4, 0, 0, 0, 0, 0, 1, 0]
// [ 0, 0, 0, 0, 0, 0, 7, 0]
// ... ]}
// (OfferIndex is 0-based.)
//
//
UINT32 SelectIndex;
UINT32 SelectProxyType;
PXEBC_DHCP_PACKET_CACHE OfferBuffer[PXEBC_OFFER_MAX_NUM];
UINT32 OfferNum;
UINT32 OfferCount[PxeOfferTypeMax];
UINT32 OfferIndex[PxeOfferTypeMax][PXEBC_OFFER_MAX_NUM];
}
成员比较多,这里介绍其中比较重要的:
Id
:它是一个Protocol,而且是以非指针的形式存在的,但是它并没有特别的实现,只是作为一个标记存在。Snp
:SNP实例。Ip4Nic
:这是PXE的子项,其对应结构体:
struct _PXEBC_VIRTUAL_NIC {
UINT32 Signature;
EFI_HANDLE Controller;
EFI_LOAD_FILE_PROTOCOL LoadFile;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
PXEBC_PRIVATE_DATA *Private;
};
它由PXE创建子项时初始化,其内容来自如下的函数PxeBcCreateIp4Children()
:
Private->Ip4Nic->Private = Private;
Private->Ip4Nic->Signature = PXEBC_VIRTUAL_NIC_SIGNATURE;
ZeroMem (&Ip4Node, sizeof (IPv4_DEVICE_PATH));
Ip4Node.Header.Type = MESSAGING_DEVICE_PATH;
Ip4Node.Header.SubType = MSG_IPv4_DP;
Ip4Node.StaticIpAddress = FALSE;
SetDevicePathNodeLength (&Ip4Node.Header, sizeof (Ip4Node));
Private->Ip4Nic->DevicePath = AppendDevicePathNode (Private->DevicePath, &Ip4Node.Header);
CopyMem (
&Private->Ip4Nic->LoadFile,
&gLoadFileProtocolTemplate,
sizeof (EFI_LOAD_FILE_PROTOCOL)
);
Controller
上还安装着一些后续需要使用到的Protocol:
Status = gBS->InstallMultipleProtocolInterfaces (
&Private->Ip4Nic->Controller,
&gEfiDevicePathProtocolGuid,
Private->Ip4Nic->DevicePath,
&gEfiLoadFileProtocolGuid,
&Private->Ip4Nic->LoadFile,
&gEfiPxeBaseCodeProtocolGuid,
&Private->PxeBc,
NULL
);
if (Private->Snp != NULL) {
//
// Install SNP protocol on purpose is for some OS loader backward
// compatibility consideration.
//
Status = gBS->InstallProtocolInterface (
&Private->Ip4Nic->Controller,
&gEfiSimpleNetworkProtocolGuid,
EFI_NATIVE_INTERFACE,
Private->Snp
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
XXXChild
以及安装其上的Protocol:描述PXE需要使用到的底层网路协议的子项,从这里也可以看到PXE使用到的协议(注意这比Supported中的要多):
PxeBc
:对应EFI_PXE_BASE_CODE_PROTOCOL
,后面会进一步介绍。LoadFileCallback
、PxeBcCallback
:对应EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL
:
struct _EFI_PXE_BASE_CODE_CALLBACK_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_CALLBACK Callback;
};
这里的两个Callback是互斥的,只会使用到其中的一个,这可以从下面的代码看出来:
EFI_STATUS
PxeBcInstallCallback (
IN OUT PXEBC_PRIVATE_DATA *Private,
OUT BOOLEAN *NewMakeCallback
)
{
Status = gBS->HandleProtocol (
Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,
&gEfiPxeBaseCodeCallbackProtocolGuid,
(VOID **)&Private->PxeBcCallback
);
if (Status == EFI_UNSUPPORTED) {
CopyMem (
&Private->LoadFileCallback,
&gPxeBcCallBackTemplate,
sizeof (EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL)
);
//
// Install a default callback if user didn't offer one.
//
Status = gBS->InstallProtocolInterface (
Private->Mode.UsingIpv6 ? &Private->Ip6Nic->Controller : &Private->Ip4Nic->Controller,
&gEfiPxeBaseCodeCallbackProtocolGuid,
EFI_NATIVE_INTERFACE,
&Private->LoadFileCallback
);
(*NewMakeCallback) = (BOOLEAN)(Status == EFI_SUCCESS);
Status = PxeBc->SetParameters (PxeBc, NULL, NULL, NULL, NULL, NewMakeCallback);
if (EFI_ERROR (Status)) {
PxeBc->Stop (PxeBc);
return Status;
}
}
return EFI_SUCCESS;
}
这里首先是查看gEfiPxeBaseCodeCallbackProtocolGuid
是否已经安装,如果安装了则赋值给PxeBcCallback
,如果没有安装则使用默认的版本,它会放到LoadFileCallback
这个成员中,然后执行SetParameters()
函数,在该函数中:
EFI_STATUS
EFIAPI
EfiPxeBcSetParameters (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN BOOLEAN *NewAutoArp OPTIONAL,
IN BOOLEAN *NewSendGUID OPTIONAL,
IN UINT8 *NewTTL OPTIONAL,
IN UINT8 *NewToS OPTIONAL,
IN BOOLEAN *NewMakeCallback OPTIONAL
)
{
if (NewMakeCallback != NULL) {
if (*NewMakeCallback) {
//
// Update the previous PxeBcCallback protocol.
//
Status = gBS->HandleProtocol (
Mode->UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,
&gEfiPxeBaseCodeCallbackProtocolGuid,
(VOID **)&Private->PxeBcCallback
);
}
到这里最终又赋值给了PxeBcCallback
。这个Callback在如下的两个位置调用:
它们分别在DHCP的配置和EFI_MTFTP4_TOKEN
中使用:
///
/// The callback function to intercept various events that occurred in
/// the DHCP configuration process. Set to NULL to ignore all those events.
///
EFI_DHCP4_CALLBACK Dhcp4Callback;
///
/// The pointer to the callback function to check the contents of the received packet.
///
EFI_MTFTP4_CHECK_PACKET CheckPacket;
而默认的回调函数是gPxeBcCallBackTemplate
:
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL gPxeBcCallBackTemplate = {
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL_REVISION,
EfiPxeLoadFileCallback
};
EfiPxeLoadFileCallback()
主要是一些显示输出和按键处理。
Mode
:其结构体如下:
///
/// EFI_PXE_BASE_CODE_MODE.
/// The data values in this structure are read-only and
/// are updated by the code that produces the
/// EFI_PXE_BASE_CODE_PROTOCOL functions.
///
typedef struct {
BOOLEAN Started;
BOOLEAN Ipv6Available;
BOOLEAN Ipv6Supported;
BOOLEAN UsingIpv6;
BOOLEAN BisSupported;
BOOLEAN BisDetected;
BOOLEAN AutoArp;
BOOLEAN SendGUID;
BOOLEAN DhcpDiscoverValid;
BOOLEAN DhcpAckReceived;
BOOLEAN ProxyOfferReceived;
BOOLEAN PxeDiscoverValid;
BOOLEAN PxeReplyReceived;
BOOLEAN PxeBisReplyReceived;
BOOLEAN IcmpErrorReceived;
BOOLEAN TftpErrorReceived;
BOOLEAN MakeCallbacks;
UINT8 TTL;
UINT8 ToS;
EFI_IP_ADDRESS StationIp;
EFI_IP_ADDRESS SubnetMask;
EFI_PXE_BASE_CODE_PACKET DhcpDiscover;
EFI_PXE_BASE_CODE_PACKET DhcpAck;
EFI_PXE_BASE_CODE_PACKET ProxyOffer;
EFI_PXE_BASE_CODE_PACKET PxeDiscover;
EFI_PXE_BASE_CODE_PACKET PxeReply;
EFI_PXE_BASE_CODE_PACKET PxeBisReply;
EFI_PXE_BASE_CODE_IP_FILTER IpFilter;
UINT32 ArpCacheEntries;
EFI_PXE_BASE_CODE_ARP_ENTRY ArpCache[EFI_PXE_BASE_CODE_MAX_ARP_ENTRIES];
UINT32 RouteTableEntries;
EFI_PXE_BASE_CODE_ROUTE_ENTRY RouteTable[EFI_PXE_BASE_CODE_MAX_ROUTE_ENTRIES];
EFI_PXE_BASE_CODE_ICMP_ERROR IcmpError;
EFI_PXE_BASE_CODE_TFTP_ERROR TftpError;
} EFI_PXE_BASE_CODE_MODE;
包含了PXE的一些属性和数据。
Function
:对应的值:
///
/// Event type list for PXE Base Code Protocol function.
///
typedef enum {
EFI_PXE_BASE_CODE_FUNCTION_FIRST,
EFI_PXE_BASE_CODE_FUNCTION_DHCP,
EFI_PXE_BASE_CODE_FUNCTION_DISCOVER,
EFI_PXE_BASE_CODE_FUNCTION_MTFTP,
EFI_PXE_BASE_CODE_FUNCTION_UDP_WRITE,
EFI_PXE_BASE_CODE_FUNCTION_UDP_READ,
EFI_PXE_BASE_CODE_FUNCTION_ARP,
EFI_PXE_BASE_CODE_FUNCTION_IGMP,
EFI_PXE_BASE_CODE_PXE_FUNCTION_LAST
} EFI_PXE_BASE_CODE_FUNCTION;
目前主要在Callback函数中用,即前面提到的EfiPxeLoadFileCallback()
。
XXXCfgData
:一些配置参数。UdpTimeOutEvent
:关于该成员可以直接看代码中的说明:
//
// Create event for UdpRead/UdpWrite timeout since they are both blocking API.
//
Status = gBS->CreateEvent (
EVT_TIMER,
TPL_CALLBACK,
NULL,
NULL,
&Private->UdpTimeOutEvent
);
ArpUpdateEvent
:用于更新ARP缓存的事件。
EFI_LOAD_FILE_PROTOCOL
该Protocol只有一个接口:
struct _EFI_LOAD_FILE_PROTOCOL {
EFI_LOAD_FILE LoadFile;
};
其实现是EfiPxeLoadFile()
,流程如下:
从这里可以看出来该实现主要还是依赖于EFI_PXE_BASE_CODE_PROTOCOL
。
另外需要说明,EFI_LOAD_FILE_PROTOCOL
并不是PXE特有的,所有支持下载Bootloader来启动操作系统的UEFI应用或驱动都可以实现该接口,这样EDK的基础代码就会为其创建启动项,从而从该设备启动操作系统。