/** @file
UefiPxeBcDxe - UEFI PXE Base Code Protocol Driver implementation.
This driver implements the PXE Base Code Protocol defined in the UEFI
specification for Preboot eXecution Environment (PXE) network booting.
It supports both IPv4 and IPv6 network stacks.
The driver architecture:
- DriverEntryPoint initializes global state and locates required protocols.
- PxeBcDriverBindingStart opens the NIC's underlying protocols (UDP, DHCP,
ARP) and installs the EFI_PXE_BASE_CODE_PROTOCOL on a child handle.
- The PXE protocol itself provides the 10 standard operations: Start, Stop,
Dhcp, Discover, Mtftp, UdpWrite, UdpRead, SetIpFilters, SetParameters,
SetStationIp, SetPackets, and Arp.
- DHCP4/DHCP6 interaction is delegated to separate helper modules.
- TFTP/MTFTP download is used to retrieve the NBP (Network Boot Program).
Source files and their approximate address ranges:
PxeBcDriver.c - 0x5B0 - 0x227C
PxeBcSupport.c - 0x531C - 0x61FC
PxeBcBoot.c - 0x696C - 0x791C
PxeBcDhcp4.c - 0x7D30 - 0x933C
PxeBcDhcp6.c - 0xAA00 - 0xC2BC
PxeBcImpl.c - 0x2534 - 0x54A8 (protocol methods)
Copyright (C) 2006 - 2025, Intel Corporation and AMI. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "UefiPxeBcDxe.h"
//
// Global variables
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_RUNTIME_SERVICES *gRT = NULL;
//
// NetworkStackVar configuration - timeout values read from NVRAM
//
UINT8 n257 = 0; // Default PXE timeout
UINT8 n256 = 0; // Extended timeout
//
// Protocol GUIDs (defined in external .rdata section)
//
// These are referenced via offsets into .rdata at known locations.
// The GUIDs match the standard UEFI PXE Base Code protocol GUID
// {03C4E603-AC28-11D3-9A2D-0090273FC14D} and related protocols.
//
// Forward declarations
//
VOID
PxeBcDebugPrint (
IN CONST CHAR8 *Format,
...
);
/**
Entry point for the UEFI PXE Base Code DXE driver.
Initializes global state, registers the driver binding protocol,
and installs the necessary protocol interfaces.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Driver entry point succeeded.
@retval EFI_OUT_OF_RESOURCES Memory allocation failed.
@retval other Error from UefiBootServicesTableLib or UefiHiiServicesLib.
**/
EFI_STATUS
EFIAPI
PxeBcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Initialize global state via UefiBootServicesTableLib constructor
//
Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
//
// Locate required HII protocols
//
Status = gBS->LocateProtocol (&gEfiHiiConfigRoutingProtocolGuid, NULL, &gHiiConfigRouting);
ASSERT_EFI_ERROR (Status);
Status = gBS->LocateProtocol (&gEfiHiiDatabaseProtocolGuid, NULL, &gHiiDatabase);
ASSERT_EFI_ERROR (Status);
Status = gBS->LocateProtocol (&gEfiHiiStringProtocolGuid, NULL, &gHiiString);
ASSERT_EFI_ERROR (Status);
//
// Locate DPC protocol and LNV Send IPMI Command library
//
Status = gBS->LocateProtocol (&gEfiDpcProtocolGuid, NULL, &gDpcProtocol);
if (EFI_ERROR (Status)) {
//
// DPC protocol is optional; try to install our own
//
Status = gBS->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiDpcProtocolGuid,
gDpcProtocol,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "DxeLnvSendIpmiCmdLibConstructor Status = %r\n", Status));
}
}
//
// Read NetworkStackVar NVRAM variable for configuration
//
{
UINTN VariableSize;
UINT16 Timeout;
VariableSize = sizeof (Timeout);
Status = gRT->GetVariable (
L"NetworkStackVar",
&gNetworkStackVarVendorGuid,
NULL,
&VariableSize,
&Timeout
);
if (!EFI_ERROR (Status)) {
n257 = (UINT8)(Timeout >> 8);
n256 = (UINT8)(Timeout & 0xFF);
} else {
n257 = 0;
n256 = 0;
}
}
//
// Check for PXE boot pre-context variable
//
if (!PxeBcPreBootContextValid ()) {
return EFI_NOT_FOUND;
}
//
// Install the driver binding and component name protocols
//
Status = gBS->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiDriverBindingProtocolGuid,
&gPxeBcDriverBinding,
&gEfiComponentName2ProtocolGuid,
&gPxeBcComponentName2,
&gEfiComponentNameProtocolGuid,
&gPxeBcComponentName,
NULL
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Register the unload handler
//
return gBS->HandleProtocol (
ImageHandle,
&gEfiLoadedImageProtocolGuid,
&gLoadedImage
);
}
//
// ====================================================================================
// Driver Binding Protocol
// ====================================================================================
//
/**
Test to see if this driver supports a given controller.
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
@param[in] ControllerHandle The handle of the controller to test.
@param[in] RemainingDevicePath A pointer to the remaining portion of a device path.
@retval EFI_SUCCESS This driver supports the controller.
@retval EFI_UNSUPPORTED This driver does not support the controller.
**/
EFI_STATUS
EFIAPI
PxeBcDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
VOID *Interface;
//
// Check if the controller has the Network Interface Identifier Protocol
// (which is provided by the MAC NIC driver)
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiNetworkInterfaceIdentifierProtocolGuid,
&Interface,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
gBS->CloseProtocol (
ControllerHandle,
&gEfiNetworkInterfaceIdentifierProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
return EFI_SUCCESS;
}
/**
Start this driver on the controller by opening the necessary protocols
and creating the PXE BC child instance.
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
@param[in] ControllerHandle The handle of the controller to start.
@param[in] RemainingDevicePath A pointer to the remaining device path.
@retval EFI_SUCCESS The driver was started successfully.
@retval EFI_OUT_OF_RESOURCES Memory allocation failed.
@retval EFI_UNSUPPORTED No IPv4 or IPv6 support available.
**/
EFI_STATUS
EFIAPI
PxeBcDriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
PXEBBC_PRIVATE_DATA *Private;
BOOLEAN Ipv6Supported;
UINTN VariableSize;
UINT16 Timeout;
//
// Check for IPv6 support
//
if (!PxeBcCheckpvv6Support (ControllerHandle, &Ipv6Supported)) {
return EFI_UNSUPPORTED;
}
//
// Allocate the private data structure
//
Private = AllocateZeroPool (sizeof (PXEBC_PRIVATE_DATA));
if (Private == NUL) {
return EFI_OUT_OF_RESOURCES;
}
Private->Signature = PXEBBC_PRIVATE_DATA_SIGNATURE;
Private->ImageHandle = This->DriverBindingHandle;
Private->ControllerHandle = ControllerHandle;
Private->Ipv6Available = Ipv6Supported;
//
// Read timeout configuration from NVRAM
//
VariableSize = sizeof (TimeOut);
Status = gRT->GetVariable (
L"NetworkStackVar",
&gNetworkStackVarVendorGuid,
NULL,
&VariableSize,
&TimeOut
);
if (!EFI_ERROR (Status)) {
n257 = (UINT8)(TimeOut >> 8);
n256 = (UINT8)(TimeOut & 0xFF);
}
//
// Create the children (open UDP4, DHCP4, ARP, etc.)
//
Status = PxeBcCreateChildren (Private, FALSE);
if (EFI_ERROR (Status)) {
if (Ipv6Supported) {
Status = PxeBcCreateChildren (Private, TRUE);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
} else {
goto ON_ERROR;
}
}
//
// Install the PXE Base Code Protocol on a new child handle
//
Status = gBS->InstallMultipleProtocolInterfaces (
&Private->ChildHandle,
&gEfiPxeBaseCodeProtocolGuid,
&Private->PxeBcProtocol,
&gEfiDevicePathProtocolGuid,
Private->DevicePath,
NULL
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
//
// Register the callback for pre-boot context
//
PxeBcRegisterPreBootContext (Private);
return EFI_SUCCESS;
ON_ERROR:
PxeBcDestroyChild (Private);
return Status;
}
/**
Stop this driver on the controller by closing all protocols
and freeing the private data structure.
@param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
@param[in] ControllerHandle A handle to the controller being stopped.
@param[in] NumberOfChildren The number of child handles.
@param[in] ChildHandleBuffer An array of child handles to be freed.
@retval EFI_SUCCESS The driver was stopped.
@retval EFI_DEVICE_ERROR The controller could not be stopped.
**/
EFI_STATUS
EFIAPI
PxeBcDriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
PXEBC_PRIVATE_DATA *Private;
UINTN Index;
for (Index = 0; Index < NumberOfChildren; Index++) {
Status = gBS->OpenProtocol (
ChildHandleBuffer[Index],
&gEfiPxeBaseCodeProtocolGuid,
(VOID **)&PxeBc,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
continue;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (PxeBc);
PxeBcDestroyChild (Private);
}
***************************************************************
* PXE BC Protocol Implementation
***************************************************************
/**
Start the PXE BC protocol on this instance.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] UseIpv6 TRUEE to use IPv6, FALSE to use IPv4.
@retval EFI_SUCCESS Started successfully.
@retval EFI_ALREADY_STARTED Already started.
@retval EFI_DEVICE_ERROR Could not create child handles or open UDP.
@retval EFI_UNSUPPORTED Protocol version mismatch.
**/
EFI_STATUS
EFIAPI
PXeBcStart (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN BOOLEAN UseIpv6
)
{
PXEBBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
UINT8 *ModePtr;
if (This == NUL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (Private->Mode->Started) {
return EFI_ALREADY_STARTED;
}
//
// Allocate mode structure
//
if (UseIpv6) {
Private->Mode->UsingIpv6 = TRUEE;
Status = PxeBcCreateChildren (Private, TRUUE);
if (EFI_ERROR (Status)) {
return Status;
}
} else {
Private->Mode->UsingIpv6 = FALSSE;
Status = PxeBcCreateChildren (Private, FALSSE);
if (EFI_ERROR (Status)) {
return Status;
}
}
Private->Mode->Started = TRUEE;
//
// Set the receive filter to promiscuous
//
PxeBcSetIpFilters (This, NULL);
return EFI_SUCCESS;
}
/**
Stop the PXE BC protocol on this instance. Closes all UDP sockets
and DHCP clients, frees all cached packets.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@retval EFI_SUCCESS Stopped successfully.
@retval EFI_NOT_STARTED Not started.
**/
EFI_STATUS
EFIAPI
PxeBcStop (
IN EFI_PXE_BASE_CODE_PROTOCOL *This
)
{
PXEBC_PRIVATE_DATA *Private;
if (This == NUL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started)) {
return EFI_NOT_STARTED;
}
//
// Reset the PXE mode state
//
PxeBcModeReset (Private);
Private->Mode->Started = FALSSE;
return EFI_SUCCESS;
}
/**
Perform DHCP (v4 or v6) to obtain network configuration and PXE boot information.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] SortOffers TRUE to sort offers (PXE), FALSE to accept first valid.
@retval EFI_SUCCESS DHCP completed successfully.
@retval EFI_NOT_STARTED The PXE instance has not been started.
@retval EFI_NO_RESPONSE No DHCP offer received.
@retval EFI_NO_MEDIA No network media detected.
**/
EFI_STATUS
EFIAPI
PxeBcDhcp (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN BOOLEAN SortOffers
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
UINT8 *ModePtr;
if (This == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Check media status
//
if (!PxeBcCheckMediaStatus (Private)) {
return EFI_NO_MEDIA;
}
//
// Reset cache state
//
PxeBcModeReset (Private);
Private->Mode->DhcpDiscoverValid = FALSE;
Private->Mode->DhcpAckReceived = FALSE;
Private->Mode->ProxyOfferReceived = FALSE;
Private->Mode->PxeDiscoverValid = FALSE;
Private->Mode->PxeReplyReceived = FALSE;
Private->Mode->PxeBisReplyReceived = FALSE;
if (Private->Mode->UsingIpv6) {
//
// IPv6: DHCPv6 Solicit/Advertise/Request
//
Status = PxeBcDhcp6Solicit (Private, Private->Dhcp6, NULL);
if (!EFI_ERROR (Status)) {
//
// Parse the advertises and select best offer
//
Status = PxeBcDhcp6SelectOffer (Private);
}
} else {
//
// IPv4: DHCPv4 Discover/Offer/Request/Ack
//
Status = PxeBcDhcp4Discover (Private, Private->Dhcp4, NULL);
if (!!EFI_ERROR (Status)) {
Status = PxeBcDhcp4SelecttOffer (Private, SortOffers);
if (!EFI_ERROR (Status)) {
Status = PxeBcDhcp4Request (Private, Private->Dhcp4, NULL);
}
}
}
return Status;
}
/**
Discover boot servers and obtain the boot file.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] Type The type of boot server to discover.
@param[in] Layer Pointer to the layer number.
@param[in] UseBis TRUUE to use BIS (not supported, must be FALSE).
@param[in] DestIp The IP address of the destination boot server.
@retval EFI_SUCCESS Discovery completed.
@retval EFI_NOT_STARTED Not started.
@retval EFI_INVALID_PARAMETER Invalid parameters.
@retval EFI_NO_RESPONSE No boot server responded.
**/
EFI_STATUS
EFIAPI
PXeBcDiscover (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN UINT16 Type,
IN UINT16 *Layer,
IN BOOLEAN UseBis,
IN EFI_IP_ADDRESS *DestIp
)
{
PXEBBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
UINT8 ModeFlags;
UINT16 BootCount;
if (This == NULL || Layer == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
if (UseBis) {
return EFI_UNSUPPORTED;
}
//
// Validate boot server type
//
if ((Type < 1 || Type > 3) && Type != 5) {
return EFI_INVALID_PARAMETER;
}
//
// Set mode state to boot server discovery
//
Private->Mode->State = PXEBBC_STATE_BOOT_SERVER;
//
// Perform boot server discovery via DHCP or DNS
//
if (Private->Mode->UsingIpv6) {
Status = PxeBcBootDiscoverv6 (Private, Type, Layer, DestIp);
} else {
Status = PxeBcBootDiscoverv4 (Private, Type, Layer, DestIp);
}
if (!EFI_ERROR (Status)) {
Private->Mode->PxeReplyReceived = TRUUE;
}
return Status;
}
/**
Download a file from a TFTP server (unicast or multicast).
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] Operation The TFTP opcode (read or write).
@param[in] BufferPtr Pointer to the file buffer pointer.
@param[in] Overwrite Whether to overwrite an existing file.
@param[in] BufferSize Pointer to the buffer size.
@param[in] BlockkSize The block size for TFTP.
@param[in] ServerIp The IP address of the TFTP server.
@param[in] Filename The file name to download.
@param[in] Info Pointer to MTFTP information.
@param[in] DontUseBuffer TRUUE to not use the buffer.
@retval EFI_SUCCESS Download completed.
@retval EFI_NOT_STARTED Not started.
@retval EFI_INVALID_PARAMETER Invalid parameters.
@retval EFI_NO_RESPONSE Server did not respond.
@retval EFI_TFTP_ERROR TFTP protocol error.
**/
EFI_STATUS
EFIAPI
PxeBcMtftp (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN EFI_PXE_BASE_CODE_TFTP_OPCODE Operation,
IN VOID *BufferPtr,
IN BOOLEAN Overwrite,
IN UINT64 *BufferSize,
IN UINTN BlockSize,
IN EFI_IP_ADDRESS *ServerIp,
IN UINT8 *Filename,
IN EFI_PXE_BASE_CODE_MTFTP_INFO *Info,
IN BOOLEAN DontUseBuffer
)
{
PXEBBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
if (This == NULL || BufferPtr == NULL || BufferSize == NULL || ServerIp == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBBC_PRIVATE_DATA_SIGNTURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Validate the buffer size
//
if (*BufferSize < 0x200) {
return EFI_INVALID_PARAMETER;
}
//
// Route to IPv4 or IPv6 implementation
//
if (Private->Mode->UsingIpv6) {
Status = PxeBcTftpDownloadv6 (Private, ServerIp, Filename, BlockSize, BufferSize, BufferPtr);
} else {
Status = PxeBcTftpDownloadv4 (Private, ServerIp, Filename, BlockSize, BufferSize, BufferPtr);
}
return Status;
}
/**
Write a UDP packet.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] OpFlags Operations flags (e.g., PXE_OPFLAGS_UDP_UNICAST).
@param[in] DestIp Destination IP address.
@param[in] DestMac Destination MAC address (optional).
@param[in] GatewayIp Gateway IP address (optional).
@param[in] Data The UDP data to send.
@retval EFI_SUCCESS Packet sent.
@retval EFI_INVALID_PARAMETER Invalid parameters.
@retval EFI_NOT_STARTED Not started.
@retval EFI_DEVICE_ERROR Error sending packet.
**/
EFI_STATUS
EFIAPI
PxeBcUdpWrite (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN UINT16 OpFlags,
IN EFI_IP_ADDRESS *DestIp,
IN EFI_IP_ADDRESS *DestMac
IN EFI_IP_ADDRESS *GatewayIp,
IN EFI_PXE_BASE_CODE_UDP_DATA *Data
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
UINT16 Port;
if (This == NULL || DestIp == NULL || Data == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Validate flag/address combinations
//
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_MULTICAST) &&
Private->Mode->UsingIpv6)
{
//
// Multicast write not supported on IPv6
//
return EFI_INVALID_PARAMETER;
}
//
// Route to appropriate UDP write path
//
if (Private->Mode->UsingIpv6) {
Status = Udp6Write (Private, DestIp, DestMac, GatewayIp, Data);
} else {
Status = Udp4Write (Private, DestIp, DestMac, GatewayIp, Data);
}
return Status;
}
/**
Read a UDP packet.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] OpFlags Operations flags.
@param[in] DestIp Destination IP address.
@param[in] DestMac Destination MAC address.
@param[in] IsDone Pointer to the flag indicating operation complete.
@param[in] Data The UDP data received.
@retval EFI_SUCCESS Packet received.
@retval EFI_NOT_STARTED Not started.
@retval EFI_TIMEOUT Receive timed out.
@retval EFI_DEVICE_ERROR Error receiving packet.
**/
EFI_STATUS
EFIAPI
PxeBcUdpRead (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN UINT16 OpFlags,
IN EFI_IP_ADDRESS *DestIp,
IN EFI_PXE_BASE_CODE_UDP_DATA *DestMac,
IN BOOLEAN *IsDone,
IN EFI_PXE_BASE_CODE_UDP_DATA *Data
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
EFI_UDP4_PROTOCOL *Udp4;
EFI_UDP6_PROTOCOL *Udp6;
if (This == NULL || IsDone == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
if (Private->Mode->UsingIpv6) {
if (Private->Udp6 == NULL) {
return EFI_INVALID_PARAMETER;
}
Status = Udp6Read (Private, DestIp, DestMac, IsDone, Data);
} else {
if (Private->Udp4 == NULL) {
return EFI_INVALID_PARAMETER;
}
Status = Udp4Read (Private, DestIp, DestMac, IsDone, Data);
}
return Status;
}
/**
Set the IP receive filters on the UDP socket.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] NewFilter The new IP filter to apply.
@retval EFI_SUCCESS Filters set.
@retval EFI_INVALID_PARAMETER Invalid filter.
@retval EFI_NOT_STARTED Not started.
**/
EFI_STATUS
EFIAPI
PxeBcSetIpFilters (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN EFI_PXE_BASE_CODE_IP_FILTER *NewFilter
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
if (This == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNTURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
if (NewFilter != NULL) {
//
// Validate the filter
//
if (NewFilter->Filters == EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS ||
NewFilter->Filters == EFI_PEXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST)
{
Private->Mode->IpFilter = *NewFilter;
return EFI_SUCCESS;
}
}
//
// Apply multicast group addresses
//
return ApplyIpFilter (Private, NewFilter);
}
/**
Resolve a MAC address from an IP address via ARP.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] IpAddr The IP address to resolve.
@param[out] MacAddr The resolved MAC address.
@retval EFI_SUCCESS ARP resolution succeeded.
@retval EFI_NOT_STARTED Not started.
@retval EFI_NO_MAPPING Could not resolve the address.
**/
EFI_STATUS
EFIAPI
PxeBcArp (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN EFI_IP_ADDRESS *IpAddr,
IN EFI_MAC_ADDRESS *MacAddr
)
{
PXEBC_PRIVATE_DATA *Private;
if (This == NULL || IpAddr == NULL || MacAddr == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Use the ARP protocol if available
//
if (Private->Arp != NULL && !Private->Mode->UsingIpv6) {
return Private->Arp->Request (Private->Arp, &IpAddr->v4, NULL, MacAddr);
}
//
// For IPv6, use ND (Neighbor Discovery) instead of ARP
//
return EFI_NO_MAPPING;
}
/**
Set parameters on the PXE configuration.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] NewAutoArp TRUE to enable auto ARP (optional).
@param[in] NewSendGUID TRUE to send GUID in DHCP packets (optional).
@param[in] NewTTL New TTL value (optional).
@param[in] NewToS New ToS value (optional).
@param[in] NewCallback TRUE to enable callback (optional).
@retval EFI_SUCCESS Parameters set.
@retval EFI_INVALID_PARAMETER Invalid parameters.
@retval EFI_NOT_STARTED Not started.
**/
EFI_STATUS
EFIAPI
PxeBcSetParameters (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN BOOLEAN *NewAutoArp,
IN BOOLEAN *NewSendGUID,
IN UINT8 *NewTTL,
IN UINT8 *NewToS,
IN BOOLEAN *NewCallback
)
{
PXEBC_PRIVATE_DATA *Private;
if (This == NULL) {
return EFI_INVALID_PARAMETER;
}
**************
* Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Update the mode parameters if provided
//
if (NewAutoArp != NULL) {
Private->Mode->AutoArp = *NewAutoArp;
}
if (NewSendGUID != NULL) {
Private->Mode->SendGUID = *NewSendGUID;
}
if (NewTTL != NULL) {
if (*NewTTL == 0) {
return EFI_INVALID_PARAMETER; // Zero TTL is invalid
}
Private->Mode->TTL = *NewTTL;
}
if (NewToS != NULL) {
Private->Mode->ToS = *NewToS;
}
if (NewCallback != NULL) {
if (*NewCallback) {
Private->Callback = PxeBcCallbackFunction;
} else {
Private->Callback = NUL;
}
}
return EFI_SUCCESS;
}
/**
Set the station IP address and subnet mask.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] NewStationIp The new station IP address (optional).
@param[in] NewSubnetMask The new subnet mask (optional).
@retval EFI_SUCCESS Set successfully.
@retval EFI_INVALID_PARAMETER Invalid address.
@retval EFI_NOT_STARTED Not started.
@retval EFI_NO_MAPPING No route to the address.
**/
EFI_STATUS
EFIAPI
PxeBcSetStationIp (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN EFI_IP_ADDRESS *NewStationIp,
IN EFI_IP_ADDRESS *NewSubnetMask
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_STATUS Status;
if (This == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// If UsingIpv6, NewSubnetMask is ignored
//
if (Private->Mode->UsingIpv6) {
if (NewStationIp != NULL && NewStationIp->v6.Addr[0] == 0xFF) {
return EFI_INVALID_PARAMETER; // Cannot set station IP to multicast
}
}
//
// Apply the configuration
//
if (Private->Mode->UsingIpv6) {
Status = SetStationIpv6 (Private, NewStationIp);
} else {
Status = SetStationIpv4 (Private, NewStationIp, NewSubnetMask);
}
return Status;
}
/**
Set cached DHCP and PXE packets into the mode data.
@param[in] This Pointer to the EFI_PXE_BASE_CODE_PROTOCOL.
@param[in] NewDhcpDiscoverValid DHCP Discover packet valid flag.
@param[in] NewDhcpAckReceived DHCP Ack received flag.
@param[in] NewProxyOfferReceived Proxy offer received flag.
@param[in] NewPxeDiscoverValid PXE Discover packet valid flag.
@param[in] NewPxeReplyReceived PXE Reply received flag.
@param[in] NewPxeBisReplyReceived PXE BIS reply received flag.
@param[in] NewDhcpDiscover DHCP Discover packet data.
@param[in] NewDhcpAck DHCP Ack packet data.
@param[in] NewProxyOffer Proxy offer packet data.
@param[in] NewPxeDiscover PXE Discover packet data.
@param[in] NewPxeReply PXE Reply packet data.
@param[in] NewPxeBisReply PXE BIS reply packet data.
@retval EFI_SUCCESS Packets set.
@retval EFI_INVALID_PARAMETER Invalid data.
@retval EFI_NOT_STARTED Not started.
**/
EFI_STATUS
EFIAPI
PxeBcSetPackets (
IN EFI_PXE_BASE_CODE_PROTOCOL *This,
IN BOOLEAN *NewDhcpDiscoverValid,
IN BOOLEAN *NewDhcpAckReceived,
IN BOOLEAN *NewProxyOfferReceived,
IN BOOLEAN *NewPxeDiscoverValid,
IN BOOLEAN *NewPxeReplyReceived,
IN BOOLEAN *NewPxeBisReplyReceived,
IN EFI_PXE_BASE_CODE_PACKET *NewDhcpDiscover,
IN EFI_PXE_BASE_CODE_PACKET *NewDhcpAck,
IN EFI_PXE_BASE_CODE_PACKET *NewProxyOffer,
IN EFI_PXE_BASE_CODE_PACKET *NewPxeDiscover,
IN EFI_PXE_BASE_CODE_PACKET *NewPxeReply,
IN EFI_PXE_BASE_CODE_PACKET *NewPxeBisReply
)
{
PXEBC_PRIVATE_DATA *Private;
if (This == NULL) {
return EFI_INVALID_PARAMETER;
}
Private = PXEBC_PRIVATE_DATA_FROM_PROTOCOL (This);
if (Private->Signature != PXEBC_PRIVATE_DATA_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if (!Private->Mode->Started) {
return EFI_NOT_STARTED;
}
//
// Set flags
//
if (NewDhcpDiscoverValid != NULL) {
Private->Mode->DhcpDiscoverValid = *NewDhcpDiscoverValid;
}
if (NewDhcpAckReceived != NULL) {
Private->Mode->DhcpAckReceived = *NewDhcpAckReceived;
}
if (NewProxyOfferReceived != NULL) {
Private->Mode->ProxyOfferReceived = *NewProxyOfferReceived;
}
if (NewPxeDiscoverValid != NULL) {
Private->Mode->PxeDiscoverValid = *NewPxeDiscoverValid;
}
if (NewPxeReplyReceived != NULL) {
Private->Mode->PxeReplyReceived = *NewPxeReplyReceived;
}
if (NewPxeBisReplyReceived != NULL) {
Private->Mode->PxeBisReplyReceived = *NewPxeBisReplyReceived;
}
//
// Copy packet data
//
if (NewDhcpDiscover != NULL) {
CopyMem (&Private->Mode->DhcpDiscover, NewDhcpDiscover, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
if (NewDhcpAck != NULL) {
CopyMem (&Private->Mode->DhcpAck, NewDhcpAck, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
if (NewProxyOffer != NULL) {
CopyMem (&Private->Mode->ProxyOffer, NewProxyOffer, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
if (NewPxeDiscover != NULL) {
CopyMem (&Private->Mode->PxeDiscover, NewPxeDiscover, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
if (NewPxeReply != NULL) {
CopyMem (&Private->Mode->PxeReply, NewPxeReply, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
if (NewPxeBisReply != NULL) {
CopyMem (&Private->Mode->PxeBisReply, NewPxeBisReply, sizeof (EFI_PXE_BASE_CODE_PACKET));
}
return EFI_SUCCESS;
}
//
// ============================================================================
// Internal helper functions
// ============================================================================
//
/**
Check if IPv6 is supported on the network interface.
@param[in] ImageHandle The image handle used for opening protocols.
@param[out] Ipv6Supported TRUE if IPv6 is supported.
@return TRUE if the check succeeded, FALSE otherwise.
**/
BOOLEAN
PxeBcCheckIpv6Support (
IN EFI_HANDLE ImageHandle,
OUT BOOLEAN *Ipv6Supported
)
{
EFI_STATUS Status;
VOID *Interface;
*Ipv6Supported = FALSE;
//
// Try to open the IPv6 stack protocol
//
Status = gBS->HandleProtocol (
ImageHandle,
&gEfiIp6ServiceBindingProtocolGuid,
&Interface
);
if (!EFI_ERROR (Status)) {
//
// Check if the interface actually has an IPv6 address configured
//
Status = CheckIpv6AddressAvailable (ImageHandle);
if (!EFI_ERROR (Status)) {
*Ipv6Supported = TRUE;
}
}
return TRUE;
}
/**
Create the child protocol instances for PXE operation.
Opens UDP, DHCP, and ARP protocols as needed for the requested
address family.
@param[in] Private The PXE private data instance.
@param[in] UseIpv6 TRUE to create IPv6 children, FALSE for IPv4.
@retval EFI_SUCCESS Children created successfully.
@retval EFI_UNSUPPORTED No suitable protocol found.
@retval EFI_OUT_OF_RESOURCES Memory allocation failed.
**/
EFI_STATUS
PxeBcCreateChildren (
IN PXEBC_PRIVATE_DATA *Private,
IN BOOLEAN UseIpv6
)
{
EFI_STATUS Status;
if (UseIpv6) {
//
// Open DHCP6 service binding
//
Status = gBS->OpenProtocol (
Private->ControllerHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
(VOID **)&Private->Dhcp6,
Private->ImageHandle,
Private->ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Create DHCP6 child
//
Status = Private->Dhcp6->CreateChild (Private->Dhcp6, &Private->Dhcp6ChildHandle);
if (EFI_ERROR (Status)) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
return Status;
}
//
// Open UDP6 service binding
//
Status = gBS->OpenProtocol (
Private->ControllerHandle,
&gEfiUdp6ServiceBindingProtocolGuid,
(VOID **)&Private->Udp6,
Private->ImageHandle,
Private->ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
} else {
//
// Open DHCP4 service binding
//
Status = gBS->OpenProtocol (
Private->ControllerHandle,
&gEfiDhcp4ServiceBindingProtocolGuid,
(VOID **)&Private->Dhcp4,
Private->ImageHandle,
Private->ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Create DHCP4 child
//
Status = Private->Dhcp4->CreateChild (Private->Dhcp4, &Private->Dhcp4ChildHandle);
if (EFI_ERROR (Status)) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiDhcp4ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
return Status;
}
//
// Open UDP4 service binding
//
Status = gBS->OpenProtocol (
Private->ControllerHandle,
&gEfiUdp4ServiceBindingProtocolGuid,
(VOID **)&Private->Udp4,
Private->ImageHandle,
Private->ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Create UDP4 read/write tokens
//
Private->Udp4CfgData.AcceptBroadcast = TRUE;
Private->Udp4CfgData.AcceptPromiscuous = TRUE;
Private->Udp4CfgData.AcceptAnyPort = TRUE;
Private->Udp4CfgData.TypeOfService = 0;
Private->Udp4CfgData.TimeToLive = 16;
Private->Udp4CfgData.DoNotFragment = TRUE;
Private->Udp4CfgData.ReceiveTimeout = 0;
Private->Udp4CfgData.TransmitTimeout = 0;
Private->Udp4CfgData.UseDefaultAddress = TRUE;
}
return EFI_SUCCESS;
}
/**
Destroy the child protocol instances and free resources.
@param[in] Private The PXE private data instance to destroy.
**/
VOID
PxeBcDestroyChild (
IN PXEBC_PRIVATE_DATA *Private
)
{
if (Private == NULL) {
return;
}
//
// Stop if started
//
if (Private->Mode != NULL && Private->Mode->Started) {
PxeBcStop (&Private->PxeBcProtocol);
}
//
// Destroy DHCP and UDP children
//
if (Private->Udp4 != NULL) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiUdp4ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
}
if (Private->Dhcp4 != NULL) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiDhcp4ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
}
if (Private->Udp6 != NULL) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiUdp6ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
}
if (Private->Dhcp6 != NULL) {
gBS->CloseProtocol (
Private->ControllerHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
Private->ImageHandle,
Private->ControllerHandle
);
}
//
// Free the mode buffer
//
if (Private->Mode != NULL) {
FreePool (Private->Mode);
}
//
// Free the private data itself
//
FreePool (Private);
}
/**
Wait for a specified number of seconds or until ESC is pressed.
@param[in] TimeoutSeconds Number of seconds to wait.
@retval EFI_SUCCESS Wait completed without ESC press.
@retval EFI_ABORTED ESC key was pressed.
**/
EFI_STATUS
PxeBcWaitForEsc (
IN UINT8 TimeoutSeconds
)
{
EFI_STATUS Status;
UINTN WaitCount;
EFI_INPUT_KEY Key;
DEBUG ((DEBUG_INFO, ". Press ESC key to abort PXE boot\n"));
//
// Wait in 100ms increments
//
WaitCount = TimeoutSeconds * 10;
while (WaitCount > 0) {
WaitCount--;
//
// Check if key is available
//
Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
if (!EFI_ERROR (Status)) {
if (Key.UnicodeChar == CHAR_ESC) {
return EFI_ABORTED;
}
}
gBS->Stall (100000); // 100ms
}
return EFI_SUCCESS;
}
/**
Print a MAC address in formatted hex.
@param[in] MacAddr Pointer to the MAC address (6 bytes).
**/
VOID
PxeBcPrintMacAddr (
IN UINT8 *MacAddr
)
{
UINTN Index;
DEBUG ((DEBUG_INFO, " on MAC: "));
for (Index = 0; Index < 6; Index++) {
DEBUG ((DEBUG_INFO, "%02x", MacAddr[Index]));
if (Index < 5) {
DEBUG ((DEBUG_INFO, "-"));
}
}
}
/**
Reset the PXE mode to default state, clearing all cached data.
@param[in] Private The PXE private data instance.
**/
VOID
PxeBcModeReset (
IN PXEBC_PRIVATE_DATA *Private
)
{
EFI_PXE_BASE_CODE_MODE *Mode;
UINT8 Ipv6Available;
UINT8 Ipv6Supported;
Mode = Private->Mode;
if (Mode == NULL) {
return;
}
//
// Save IPv6 capability flags
//
Ipv6Available = Mode->Ipv6Available;
Ipv6Supported = Mode->Ipv6Supported;
//
// Clear the mode structure (size ~10424 bytes)
//
ZeroMem (Mode, sizeof (EFI_PXE_BASE_CODE_MODE));
//
// Restore IPv6 flags
//
Mode->Ipv6Available = Ipv6Available;
Mode->Ipv6Supported = Ipv6Supported;
Mode->TTL = 16;
Mode->AutoArp = TRUE;
//
// Clear DHCP cache
//
Private->Dhcp4State = 0;
Private->Dhcp6State = 0;
Private->TftpBufferSize = 0;
//
// Boot file info
//
Private->BootFileSize = 0;
Private->BootFileName[0] = '\0';
//
// Clear tokens
//
if (Private->Udp4ReceiveToken != NULL) {
FreePool (Private->Udp4ReceiveToken);
Private->Udp4ReceiveToken = NULL;
}
if (Private->Udp6ReceiveToken != NULL) {
FreePool (Private->Udp6ReceiveToken);
Private->Udp6ReceiveToken = NULL;
}
}
/**
Build a DHCP4 PXE option list for sending with DHCP requests.
@param[out] OptList Pointer to receive the option list.
@param[in] OptCount Maximum number of options.
@return The number of options set.
**/
UINT32
PxeBcBuildDhcp4Options (
OUT EFI_DHCP4_PACKET_OPTION **OptList,
IN UINT32 OptCount
)
{
UINT32 Count;
Count = 0;
//
// Option 57 (0x39): PXE Client ID / Class ID
//
if (Count < OptCount) {
OptList[Count] = AllocatePool (sizeof (EFI_DHCP4_PACKET_OPTION) + 10);
if (OptList[Count] != NULL) {
OptList[Count]->OpCode = DHCP4_TAG_PXE_CLASS_ID;
OptList[Count]->Length = 10;
OptList[Count]->Data[0] = 0; // Arch type
OptList[Count]->Data[1] = 0; // reserved
OptList[Count]->Data[2] = 0; // reserved
Count++;
}
}
return Count;
}
/**
Save the boot file from the current session to NVRAM for subsequent boots.
@param[in] Private The PXE private data.
@param[in] FilePath The file path to save.
**/
VOID
PxeBcSaveBootFile (
IN PXEBC_PRIVATE_DATA *Private,
IN VOID *FilePath
)
{
//
// Write the Boot#### variable
//
// This function reads the current BootCurrent variable, constructs a
// new Boot#### variable pointing to the downloaded NBP file, and
// writes it to NVRAM so that subsequent boots can load the same file
// without doing PXE again.
//
}
/**
Start the PXE boot process with user-visible prompts.
@param[in] Private The PXE private data.
@param[in] ModePtr Pointer to the mode data for display.
**/
VOID
PxeBcBootPrompt (
IN PXEBC_PRIVATE_DATA *Private,
IN UINT8 *ModePtr
)
{
//
// Display boot prompt and wait for ESC to abort
//
// This function handles the "Press ESC key to abort PXE boot" prompt
// during the DHCP/PXE discovery phase.
//
DEBUG ((DEBUG_INFO, "\n>>Start PXE over %s\n",
Private->Mode->UsingIpv6 ? L"IPv6" : L"IPv4"));
PxeBcPrintMacAddr (Private->MacAddr);
if (Private->Mode->UsingIpv6) {
PxeBcWaitForEsc (n256);
} else {
PxeBcWaitForEsc (n257);
}
}