summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c')
-rw-r--r--src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c1282
1 files changed, 1282 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c b/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c
new file mode 100644
index 00000000..0c0c2ec5
--- /dev/null
+++ b/src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c
@@ -0,0 +1,1282 @@
+/** @file
+ Boot functions implementation for UefiPxeBc Driver.
+
+ Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
+ (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "PxeBcImpl.h"
+
+
+/**
+ Display the string of the boot item.
+
+ If the length of the boot item string beyond 70 Char, just display 70 Char.
+
+ @param[in] Str The pointer to the string.
+ @param[in] Len The length of the string.
+
+**/
+VOID
+PxeBcDisplayBootItem (
+ IN UINT8 *Str,
+ IN UINT8 Len
+ )
+{
+ UINT8 Tmp;
+
+ //
+ // Cut off the chars behind 70th.
+ //
+ Len = (UINT8) MIN (PXEBC_DISPLAY_MAX_LINE, Len);
+ Tmp = Str[Len];
+ Str[Len] = 0;
+ AsciiPrint ("%a \n", Str);
+
+ //
+ // Restore the original 70th char.
+ //
+ Str[Len] = Tmp;
+}
+
+
+/**
+ Select and maintain the boot prompt if needed.
+
+ @param[in] Private Pointer to PxeBc private data.
+
+ @retval EFI_SUCCESS Selected boot prompt done.
+ @retval EFI_TIMEOUT Selected boot prompt timed out.
+ @retval EFI_NOT_FOUND The proxy offer is not Pxe10.
+ @retval EFI_ABORTED User cancelled the operation.
+ @retval EFI_NOT_READY Reading the input key from the keyboard has not finish.
+
+**/
+EFI_STATUS
+PxeBcSelectBootPrompt (
+ IN PXEBC_PRIVATE_DATA *Private
+ )
+{
+ PXEBC_DHCP_PACKET_CACHE *Cache;
+ PXEBC_VENDOR_OPTION *VendorOpt;
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ EFI_EVENT TimeoutEvent;
+ EFI_EVENT DescendEvent;
+ EFI_INPUT_KEY InputKey;
+ EFI_STATUS Status;
+ UINT32 OfferType;
+ UINT8 Timeout;
+ UINT8 *Prompt;
+ UINT8 PromptLen;
+ INT32 SecCol;
+ INT32 SecRow;
+
+ TimeoutEvent = NULL;
+ DescendEvent = NULL;
+ Mode = Private->PxeBc.Mode;
+ Cache = Mode->ProxyOfferReceived ? &Private->ProxyOffer : &Private->DhcpAck;
+ OfferType = Mode->UsingIpv6 ? Cache->Dhcp6.OfferType : Cache->Dhcp4.OfferType;
+
+ //
+ // Only DhcpPxe10 and ProxyPxe10 offer needs boot prompt.
+ //
+ if (OfferType != PxeOfferTypeProxyPxe10 && OfferType != PxeOfferTypeDhcpPxe10) {
+ return EFI_NOT_FOUND;
+ }
+
+ //
+ // There is no specified ProxyPxe10 for IPv6 in PXE and UEFI spec.
+ //
+ ASSERT (!Mode->UsingIpv6);
+
+ VendorOpt = &Cache->Dhcp4.VendorOpt;
+ //
+ // According to the PXE specification 2.1, Table 2-1 PXE DHCP Options,
+ // we must not consider a boot prompt or boot menu if all of the following hold:
+ // - the PXE_DISCOVERY_CONTROL tag(6) is present inside the Vendor Options(43), and has bit 3 set
+ // - a boot file name has been presented in the initial DHCP or ProxyDHCP offer packet.
+ //
+ if (IS_DISABLE_PROMPT_MENU (VendorOpt->DiscoverCtrl) &&
+ Cache->Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL) {
+ return EFI_ABORTED;
+ }
+
+ if (!IS_VALID_BOOT_PROMPT (VendorOpt->BitMap)) {
+ return EFI_TIMEOUT;
+ }
+
+ Timeout = VendorOpt->MenuPrompt->Timeout;
+ Prompt = VendorOpt->MenuPrompt->Prompt;
+ PromptLen = (UINT8) (VendorOpt->MenuPromptLen - 1);
+
+ //
+ // The valid scope of Timeout refers to PXE2.1 spec.
+ //
+ if (Timeout == 0) {
+ return EFI_TIMEOUT;
+ }
+ if (Timeout == 255) {
+ return EFI_SUCCESS;
+ }
+
+ //
+ // Create and start a timer as timeout event.
+ //
+ Status = gBS->CreateEvent (
+ EVT_TIMER,
+ TPL_CALLBACK,
+ NULL,
+ NULL,
+ &TimeoutEvent
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = gBS->SetTimer (
+ TimeoutEvent,
+ TimerRelative,
+ MultU64x32 (Timeout, TICKS_PER_SECOND)
+ );
+ if (EFI_ERROR (Status)) {
+ goto ON_EXIT;
+ }
+
+ //
+ // Create and start a periodic timer as descend event by second.
+ //
+ Status = gBS->CreateEvent (
+ EVT_TIMER,
+ TPL_CALLBACK,
+ NULL,
+ NULL,
+ &DescendEvent
+ );
+ if (EFI_ERROR (Status)) {
+ goto ON_EXIT;
+ }
+
+ Status = gBS->SetTimer (
+ DescendEvent,
+ TimerPeriodic,
+ TICKS_PER_SECOND
+ );
+ if (EFI_ERROR (Status)) {
+ goto ON_EXIT;
+ }
+
+ //
+ // Display the boot item and cursor on the screen.
+ //
+ SecCol = gST->ConOut->Mode->CursorColumn;
+ SecRow = gST->ConOut->Mode->CursorRow;
+
+ PxeBcDisplayBootItem (Prompt, PromptLen);
+
+ gST->ConOut->SetCursorPosition (gST->ConOut, SecCol + PromptLen, SecRow);
+ AsciiPrint ("(%d) ", Timeout--);
+
+ Status = EFI_TIMEOUT;
+ while (EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
+ if (!EFI_ERROR (gBS->CheckEvent (DescendEvent))) {
+ gST->ConOut->SetCursorPosition (gST->ConOut, SecCol + PromptLen, SecRow);
+ AsciiPrint ("(%d) ", Timeout--);
+ }
+ if (gST->ConIn->ReadKeyStroke (gST->ConIn, &InputKey) == EFI_NOT_READY) {
+ gBS->Stall (10 * TICKS_PER_MS);
+ continue;
+ }
+ //
+ // Parse the input key by user.
+ // If <F8> or <Ctrl> + <M> is pressed, return success to display the boot menu.
+ //
+ if (InputKey.ScanCode == 0) {
+
+ switch (InputKey.UnicodeChar) {
+
+ case CTRL ('c'):
+ Status = EFI_ABORTED;
+ break;
+
+ case CTRL ('m'):
+ case 'm':
+ case 'M':
+ Status = EFI_SUCCESS;
+ break;
+
+ default:
+ continue;
+ }
+
+ } else {
+
+ switch (InputKey.ScanCode) {
+
+ case SCAN_F8:
+ Status = EFI_SUCCESS;
+ break;
+
+ case SCAN_ESC:
+ Status = EFI_ABORTED;
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ //
+ // Reset the cursor on the screen.
+ //
+ gST->ConOut->SetCursorPosition (gST->ConOut, 0 , SecRow + 1);
+
+ON_EXIT:
+ if (DescendEvent != NULL) {
+ gBS->CloseEvent (DescendEvent);
+ }
+ if (TimeoutEvent != NULL) {
+ gBS->CloseEvent (TimeoutEvent);
+ }
+
+ return Status;
+}
+
+
+/**
+ Select the boot menu by user's input.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[out] Type The type of the menu.
+ @param[in] UseDefaultItem Use default item or not.
+
+ @retval EFI_ABORTED User cancel operation.
+ @retval EFI_SUCCESS Select the boot menu success.
+ @retval EFI_NOT_READY Read the input key from the keyboard has not finish.
+
+**/
+EFI_STATUS
+PxeBcSelectBootMenu (
+ IN PXEBC_PRIVATE_DATA *Private,
+ OUT UINT16 *Type,
+ IN BOOLEAN UseDefaultItem
+ )
+{
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ PXEBC_DHCP_PACKET_CACHE *Cache;
+ PXEBC_VENDOR_OPTION *VendorOpt;
+ EFI_INPUT_KEY InputKey;
+ UINT32 OfferType;
+ UINT8 MenuSize;
+ UINT8 MenuNum;
+ INT32 TopRow;
+ UINT16 Select;
+ UINT16 LastSelect;
+ UINT8 Index;
+ BOOLEAN Finish;
+ CHAR8 Blank[PXEBC_DISPLAY_MAX_LINE];
+ PXEBC_BOOT_MENU_ENTRY *MenuItem;
+ PXEBC_BOOT_MENU_ENTRY *MenuArray[PXEBC_MENU_MAX_NUM];
+
+ Finish = FALSE;
+ Select = 0;
+ Index = 0;
+ *Type = 0;
+ Mode = Private->PxeBc.Mode;
+ Cache = Mode->ProxyOfferReceived ? &Private->ProxyOffer : &Private->DhcpAck;
+ OfferType = Mode->UsingIpv6 ? Cache->Dhcp6.OfferType : Cache->Dhcp4.OfferType;
+
+ //
+ // There is no specified DhcpPxe10/ProxyPxe10 for IPv6 in PXE and UEFI spec.
+ //
+ ASSERT (!Mode->UsingIpv6);
+ ASSERT (OfferType == PxeOfferTypeProxyPxe10 || OfferType == PxeOfferTypeDhcpPxe10);
+
+ VendorOpt = &Cache->Dhcp4.VendorOpt;
+ if (!IS_VALID_BOOT_MENU (VendorOpt->BitMap)) {
+ return EFI_SUCCESS;
+ }
+
+ //
+ // Display the boot menu on the screen.
+ //
+ SetMem (Blank, sizeof(Blank), ' ');
+
+ MenuSize = VendorOpt->BootMenuLen;
+ MenuItem = VendorOpt->BootMenu;
+
+ if (MenuSize == 0) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ while (MenuSize > 0 && Index < PXEBC_MENU_MAX_NUM) {
+ ASSERT (MenuItem != NULL);
+ MenuArray[Index] = MenuItem;
+ MenuSize = (UINT8) (MenuSize - (MenuItem->DescLen + 3));
+ MenuItem = (PXEBC_BOOT_MENU_ENTRY *) ((UINT8 *) MenuItem + MenuItem->DescLen + 3);
+ Index++;
+ }
+
+ if (UseDefaultItem) {
+ ASSERT (MenuArray[0] != NULL);
+ CopyMem (Type, &MenuArray[0]->Type, sizeof (UINT16));
+ *Type = NTOHS (*Type);
+ return EFI_SUCCESS;
+ }
+
+ MenuNum = Index;
+
+ for (Index = 0; Index < MenuNum; Index++) {
+ ASSERT (MenuArray[Index] != NULL);
+ PxeBcDisplayBootItem (MenuArray[Index]->DescStr, MenuArray[Index]->DescLen);
+ }
+
+ TopRow = gST->ConOut->Mode->CursorRow - MenuNum;
+
+ //
+ // Select the boot item by user in the boot menu.
+ //
+ do {
+ //
+ // Highlight selected row.
+ //
+ gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY));
+ gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + Select);
+ ASSERT (Select < PXEBC_MENU_MAX_NUM);
+ ASSERT (MenuArray[Select] != NULL);
+ Blank[MenuArray[Select]->DescLen] = 0;
+ AsciiPrint ("%a\r", Blank);
+ PxeBcDisplayBootItem (MenuArray[Select]->DescStr, MenuArray[Select]->DescLen);
+ gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + MenuNum);
+ LastSelect = Select;
+
+ while (gST->ConIn->ReadKeyStroke (gST->ConIn, &InputKey) == EFI_NOT_READY) {
+ gBS->Stall (10 * TICKS_PER_MS);
+ }
+
+ if (InputKey.ScanCode == 0) {
+ switch (InputKey.UnicodeChar) {
+ case CTRL ('c'):
+ InputKey.ScanCode = SCAN_ESC;
+ break;
+
+ case CTRL ('j'): /* linefeed */
+ case CTRL ('m'): /* return */
+ Finish = TRUE;
+ break;
+
+ case CTRL ('i'): /* tab */
+ case ' ':
+ case 'd':
+ case 'D':
+ InputKey.ScanCode = SCAN_DOWN;
+ break;
+
+ case CTRL ('h'): /* backspace */
+ case 'u':
+ case 'U':
+ InputKey.ScanCode = SCAN_UP;
+ break;
+
+ default:
+ InputKey.ScanCode = 0;
+ }
+ }
+
+ switch (InputKey.ScanCode) {
+ case SCAN_LEFT:
+ case SCAN_UP:
+ if (Select != 0) {
+ Select--;
+ }
+ break;
+
+ case SCAN_DOWN:
+ case SCAN_RIGHT:
+ if (++Select == MenuNum) {
+ Select--;
+ }
+ break;
+
+ case SCAN_PAGE_UP:
+ case SCAN_HOME:
+ Select = 0;
+ break;
+
+ case SCAN_PAGE_DOWN:
+ case SCAN_END:
+ Select = (UINT16) (MenuNum - 1);
+ break;
+
+ case SCAN_ESC:
+ return EFI_ABORTED;
+ }
+
+ //
+ // Unhighlight the last selected row.
+ //
+ gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
+ gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + LastSelect);
+ ASSERT (LastSelect < PXEBC_MENU_MAX_NUM);
+ ASSERT (MenuArray[LastSelect] != NULL);
+ Blank[MenuArray[LastSelect]->DescLen] = 0;
+ AsciiPrint ("%a\r", Blank);
+ PxeBcDisplayBootItem (MenuArray[LastSelect]->DescStr, MenuArray[LastSelect]->DescLen);
+ gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + MenuNum);
+ } while (!Finish);
+
+ //
+ // Swap the byte order.
+ //
+ ASSERT (Select < PXEBC_MENU_MAX_NUM);
+ ASSERT (MenuArray[Select] != NULL);
+ CopyMem (Type, &MenuArray[Select]->Type, sizeof (UINT16));
+ *Type = NTOHS (*Type);
+
+ return EFI_SUCCESS;
+}
+
+
+/**
+ Parse out the boot information from the last Dhcp4 reply packet.
+
+ @param[in, out] Private Pointer to PxeBc private data.
+ @param[out] BufferSize Size of the boot file to be downloaded.
+
+ @retval EFI_SUCCESS Successfully parsed out all the boot information.
+ @retval Others Failed to parse out the boot information.
+
+**/
+EFI_STATUS
+PxeBcDhcp4BootInfo (
+ IN OUT PXEBC_PRIVATE_DATA *Private,
+ OUT UINT64 *BufferSize
+ )
+{
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ EFI_STATUS Status;
+ PXEBC_DHCP4_PACKET_CACHE *Cache4;
+ UINT16 Value;
+ PXEBC_VENDOR_OPTION *VendorOpt;
+ PXEBC_BOOT_SVR_ENTRY *Entry;
+
+ PxeBc = &Private->PxeBc;
+ Mode = PxeBc->Mode;
+ Status = EFI_SUCCESS;
+ *BufferSize = 0;
+
+ //
+ // Get the last received Dhcp4 reply packet.
+ //
+ if (Mode->PxeReplyReceived) {
+ Cache4 = &Private->PxeReply.Dhcp4;
+ } else if (Mode->ProxyOfferReceived) {
+ Cache4 = &Private->ProxyOffer.Dhcp4;
+ } else {
+ Cache4 = &Private->DhcpAck.Dhcp4;
+ }
+
+ if (Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] == NULL) {
+ //
+ // This should never happen in a correctly configured DHCP / PXE
+ // environment. One misconfiguration that can cause it is two DHCP servers
+ // mistakenly running on the same network segment at the same time, and
+ // racing each other in answering DHCP requests. Thus, the DHCP packets
+ // that the edk2 PXE client considers "belonging together" may actually be
+ // entirely independent, coming from two (competing) DHCP servers.
+ //
+ // Try to deal with this gracefully. Note that this check is not
+ // comprehensive, as we don't try to identify all such errors.
+ //
+ return EFI_PROTOCOL_ERROR;
+ }
+
+ //
+ // Parse the boot server address.
+ // If prompt/discover is disabled, get the first boot server from the boot servers list.
+ // Otherwise, parse the boot server Ipv4 address from next server address field in DHCP header.
+ // If all these fields are not available, use option 54 instead.
+ //
+ VendorOpt = &Cache4->VendorOpt;
+ if (IS_DISABLE_PROMPT_MENU (VendorOpt->DiscoverCtrl) && IS_VALID_BOOT_SERVERS (VendorOpt->BitMap)) {
+ Entry = VendorOpt->BootSvr;
+ if (VendorOpt->BootSvrLen >= sizeof (PXEBC_BOOT_SVR_ENTRY) && Entry->IpCnt > 0) {
+ CopyMem (
+ &Private->ServerIp,
+ &Entry->IpAddr[0],
+ sizeof (EFI_IPv4_ADDRESS)
+ );
+ }
+ }
+ if (Private->ServerIp.Addr[0] == 0) {
+ //
+ // ServerIp.Addr[0] equals zero means we failed to get IP address from boot server list.
+ // Try to use next server address field.
+ //
+ CopyMem (
+ &Private->ServerIp,
+ &Cache4->Packet.Offer.Dhcp4.Header.ServerAddr,
+ sizeof (EFI_IPv4_ADDRESS)
+ );
+ }
+ if (Private->ServerIp.Addr[0] == 0) {
+ //
+ // Still failed , use the IP address from option 54.
+ //
+ CopyMem (
+ &Private->ServerIp,
+ Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_SERVER_ID]->Data,
+ sizeof (EFI_IPv4_ADDRESS)
+ );
+ }
+
+ //
+ // Parse the boot file name by option.
+ //
+ Private->BootFileName = Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE]->Data;
+
+ if (Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE_LEN] != NULL) {
+ //
+ // Parse the boot file size by option.
+ //
+ CopyMem (&Value, Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE_LEN]->Data, sizeof (Value));
+ Value = NTOHS (Value);
+ //
+ // The field of boot file size is 512 bytes in unit.
+ //
+ *BufferSize = 512 * Value;
+ } else {
+ //
+ // Get the bootfile size by tftp command if no option available.
+ //
+ Status = PxeBc->Mtftp (
+ PxeBc,
+ EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
+ NULL,
+ FALSE,
+ BufferSize,
+ &Private->BlockSize,
+ &Private->ServerIp,
+ Private->BootFileName,
+ NULL,
+ FALSE
+ );
+ }
+
+ //
+ // Save the value of boot file size.
+ //
+ Private->BootFileSize = (UINTN) *BufferSize;
+
+ //
+ // Display all the information: boot server address, boot file name and boot file size.
+ //
+ AsciiPrint ("\n Server IP address is ");
+ PxeBcShowIp4Addr (&Private->ServerIp.v4);
+ AsciiPrint ("\n NBP filename is %a", Private->BootFileName);
+ AsciiPrint ("\n NBP filesize is %d Bytes", Private->BootFileSize);
+
+ return Status;
+}
+
+
+/**
+ Parse out the boot information from the last Dhcp6 reply packet.
+
+ @param[in, out] Private Pointer to PxeBc private data.
+ @param[out] BufferSize Size of the boot file to be downloaded.
+
+ @retval EFI_SUCCESS Successfully parsed out all the boot information.
+ @retval EFI_BUFFER_TOO_SMALL
+ @retval Others Failed to parse out the boot information.
+
+**/
+EFI_STATUS
+PxeBcDhcp6BootInfo (
+ IN OUT PXEBC_PRIVATE_DATA *Private,
+ OUT UINT64 *BufferSize
+ )
+{
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ EFI_STATUS Status;
+ PXEBC_DHCP6_PACKET_CACHE *Cache6;
+ UINT16 Value;
+
+ PxeBc = &Private->PxeBc;
+ Mode = PxeBc->Mode;
+ Status = EFI_SUCCESS;
+ *BufferSize = 0;
+
+ //
+ // Get the last received Dhcp6 reply packet.
+ //
+ if (Mode->PxeReplyReceived) {
+ Cache6 = &Private->PxeReply.Dhcp6;
+ } else if (Mode->ProxyOfferReceived) {
+ Cache6 = &Private->ProxyOffer.Dhcp6;
+ } else {
+ Cache6 = &Private->DhcpAck.Dhcp6;
+ }
+
+ if (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) {
+ //
+ // This should never happen in a correctly configured DHCP / PXE
+ // environment. One misconfiguration that can cause it is two DHCP servers
+ // mistakenly running on the same network segment at the same time, and
+ // racing each other in answering DHCP requests. Thus, the DHCP packets
+ // that the edk2 PXE client considers "belonging together" may actually be
+ // entirely independent, coming from two (competing) DHCP servers.
+ //
+ // Try to deal with this gracefully. Note that this check is not
+ // comprehensive, as we don't try to identify all such errors.
+ //
+ return EFI_PROTOCOL_ERROR;
+ }
+
+ //
+ // Set the station address to IP layer.
+ //
+ Status = PxeBcSetIp6Address (Private);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+
+ //
+ // Parse (m)tftp server ip address and bootfile name.
+ //
+ Status = PxeBcExtractBootFileUrl (
+ Private,
+ &Private->BootFileName,
+ &Private->ServerIp.v6,
+ (CHAR8 *) (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->Data),
+ NTOHS (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->OpLen)
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Parse the value of boot file size.
+ //
+ if (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_PARAM] != NULL) {
+ //
+ // Parse it out if have the boot file parameter option.
+ //
+ Status = PxeBcExtractBootFileParam ((CHAR8 *) Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_PARAM]->Data, &Value);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ //
+ // The field of boot file size is 512 bytes in unit.
+ //
+ *BufferSize = 512 * Value;
+ } else {
+ //
+ // Send get file size command by tftp if option unavailable.
+ //
+ Status = PxeBc->Mtftp (
+ PxeBc,
+ EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
+ NULL,
+ FALSE,
+ BufferSize,
+ &Private->BlockSize,
+ &Private->ServerIp,
+ Private->BootFileName,
+ NULL,
+ FALSE
+ );
+ }
+
+ //
+ // Save the value of boot file size.
+ //
+ Private->BootFileSize = (UINTN) *BufferSize;
+
+ //
+ // Display all the information: boot server address, boot file name and boot file size.
+ //
+ AsciiPrint ("\n Server IP address is ");
+ PxeBcShowIp6Addr (&Private->ServerIp.v6);
+ AsciiPrint ("\n NBP filename is %a", Private->BootFileName);
+ AsciiPrint ("\n NBP filesize is %d Bytes", Private->BootFileSize);
+
+ return Status;
+}
+
+
+/**
+ Extract the discover information and boot server entry from the
+ cached packets if unspecified.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[in] Type The type of bootstrap to perform.
+ @param[in, out] DiscoverInfo Pointer to EFI_PXE_BASE_CODE_DISCOVER_INFO.
+ @param[out] BootEntry Pointer to PXEBC_BOOT_SVR_ENTRY.
+ @param[out] SrvList Pointer to EFI_PXE_BASE_CODE_SRVLIST.
+
+ @retval EFI_SUCCESS Successfully extracted the information.
+ @retval EFI_DEVICE_ERROR Failed to extract the information.
+
+**/
+EFI_STATUS
+PxeBcExtractDiscoverInfo (
+ IN PXEBC_PRIVATE_DATA *Private,
+ IN UINT16 Type,
+ IN OUT EFI_PXE_BASE_CODE_DISCOVER_INFO **DiscoverInfo,
+ OUT PXEBC_BOOT_SVR_ENTRY **BootEntry,
+ OUT EFI_PXE_BASE_CODE_SRVLIST **SrvList
+ )
+{
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ PXEBC_DHCP4_PACKET_CACHE *Cache4;
+ PXEBC_VENDOR_OPTION *VendorOpt;
+ PXEBC_BOOT_SVR_ENTRY *Entry;
+ BOOLEAN IsFound;
+ EFI_PXE_BASE_CODE_DISCOVER_INFO *Info;
+ UINT16 Index;
+
+ Mode = Private->PxeBc.Mode;
+ Info = *DiscoverInfo;
+
+ if (Mode->UsingIpv6) {
+ Info->IpCnt = 1;
+ Info->UseUCast = TRUE;
+
+ Info->SrvList[0].Type = Type;
+ Info->SrvList[0].AcceptAnyResponse = FALSE;
+
+ //
+ // There is no vendor options specified in DHCPv6, so take BootFileUrl in the last cached packet.
+ //
+ CopyMem (&Info->SrvList[0].IpAddr, &Private->ServerIp, sizeof (EFI_IP_ADDRESS));
+
+ *SrvList = Info->SrvList;
+ } else {
+ Entry = NULL;
+ IsFound = FALSE;
+ Cache4 = (Mode->ProxyOfferReceived) ? &Private->ProxyOffer.Dhcp4 : &Private->DhcpAck.Dhcp4;
+ VendorOpt = &Cache4->VendorOpt;
+
+ if (!Mode->DhcpAckReceived || !IS_VALID_DISCOVER_VENDOR_OPTION (VendorOpt->BitMap)) {
+ //
+ // Address is not acquired or no discovery options.
+ //
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // Parse the boot server entry from the vendor option in the last cached packet.
+ //
+ Info->UseMCast = (BOOLEAN) !IS_DISABLE_MCAST_DISCOVER (VendorOpt->DiscoverCtrl);
+ Info->UseBCast = (BOOLEAN) !IS_DISABLE_BCAST_DISCOVER (VendorOpt->DiscoverCtrl);
+ Info->MustUseList = (BOOLEAN) IS_ENABLE_USE_SERVER_LIST (VendorOpt->DiscoverCtrl);
+ Info->UseUCast = (BOOLEAN) IS_VALID_BOOT_SERVERS (VendorOpt->BitMap);
+
+ if (Info->UseMCast) {
+ //
+ // Get the multicast discover ip address from vendor option if has.
+ //
+ CopyMem (&Info->ServerMCastIp.v4, &VendorOpt->DiscoverMcastIp, sizeof (EFI_IPv4_ADDRESS));
+ }
+
+ Info->IpCnt = 0;
+
+ if (Info->UseUCast) {
+ Entry = VendorOpt->BootSvr;
+
+ while (((UINT8) (Entry - VendorOpt->BootSvr)) < VendorOpt->BootSvrLen) {
+ if (Entry->Type == HTONS (Type)) {
+ IsFound = TRUE;
+ break;
+ }
+ Entry = GET_NEXT_BOOT_SVR_ENTRY (Entry);
+ }
+
+ if (!IsFound) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ Info->IpCnt = Entry->IpCnt;
+ if (Info->IpCnt >= 1) {
+ *DiscoverInfo = AllocatePool (sizeof (*Info) + (Info->IpCnt - 1) * sizeof (**SrvList));
+ if (*DiscoverInfo == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+ CopyMem (*DiscoverInfo, Info, sizeof (*Info));
+ Info = *DiscoverInfo;
+ }
+
+ for (Index = 0; Index < Info->IpCnt; Index++) {
+ CopyMem (&Info->SrvList[Index].IpAddr, &Entry->IpAddr[Index], sizeof (EFI_IPv4_ADDRESS));
+ Info->SrvList[Index].AcceptAnyResponse = !Info->MustUseList;
+ Info->SrvList[Index].Type = NTOHS (Entry->Type);
+ }
+ }
+
+ *BootEntry = Entry;
+ *SrvList = Info->SrvList;
+ }
+
+ return EFI_SUCCESS;
+}
+
+
+/**
+ Build the discover packet and send out for boot server.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[in] Type PxeBc option boot item type.
+ @param[in] Layer Pointer to option boot item layer.
+ @param[in] UseBis Use BIS or not.
+ @param[in] DestIp Pointer to the destination address.
+ @param[in] IpCount The count of the server address.
+ @param[in] SrvList Pointer to the server address list.
+
+ @retval EFI_SUCCESS Successfully discovered boot file.
+ @retval EFI_OUT_OF_RESOURCES Failed to allocate resource.
+ @retval EFI_NOT_FOUND Can't get the PXE reply packet.
+ @retval Others Failed to discover boot file.
+
+**/
+EFI_STATUS
+PxeBcDiscoverBootServer (
+ IN PXEBC_PRIVATE_DATA *Private,
+ IN UINT16 Type,
+ IN UINT16 *Layer,
+ IN BOOLEAN UseBis,
+ IN EFI_IP_ADDRESS *DestIp,
+ IN UINT16 IpCount,
+ IN EFI_PXE_BASE_CODE_SRVLIST *SrvList
+ )
+{
+ if (Private->PxeBc.Mode->UsingIpv6) {
+ return PxeBcDhcp6Discover (
+ Private,
+ Type,
+ Layer,
+ UseBis,
+ DestIp
+ );
+ } else {
+ return PxeBcDhcp4Discover (
+ Private,
+ Type,
+ Layer,
+ UseBis,
+ DestIp,
+ IpCount,
+ SrvList
+ );
+ }
+}
+
+
+/**
+ Discover all the boot information for boot file.
+
+ @param[in, out] Private Pointer to PxeBc private data.
+ @param[out] BufferSize Size of the boot file to be downloaded.
+
+ @retval EFI_SUCCESS Successfully obtained all the boot information .
+ @retval EFI_BUFFER_TOO_SMALL The buffer size is not enough for boot file.
+ @retval EFI_ABORTED User cancel current operation.
+ @retval Others Failed to parse out the boot information.
+
+**/
+EFI_STATUS
+PxeBcDiscoverBootFile (
+ IN OUT PXEBC_PRIVATE_DATA *Private,
+ OUT UINT64 *BufferSize
+ )
+{
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+ EFI_PXE_BASE_CODE_MODE *Mode;
+ EFI_STATUS Status;
+ UINT16 Type;
+ UINT16 Layer;
+ BOOLEAN UseBis;
+
+ PxeBc = &Private->PxeBc;
+ Mode = PxeBc->Mode;
+ Type = EFI_PXE_BASE_CODE_BOOT_TYPE_BOOTSTRAP;
+ Layer = EFI_PXE_BASE_CODE_BOOT_LAYER_INITIAL;
+
+ //
+ // Start D.O.R.A/S.A.R.R exchange to acquire station ip address and
+ // other pxe boot information.
+ //
+ Status = PxeBc->Dhcp (PxeBc, TRUE);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Select a boot server from boot server list.
+ //
+ Status = PxeBcSelectBootPrompt (Private);
+
+ if (Status == EFI_SUCCESS) {
+ //
+ // Choose by user's input.
+ //
+ Status = PxeBcSelectBootMenu (Private, &Type, FALSE);
+ } else if (Status == EFI_TIMEOUT) {
+ //
+ // Choose by default item.
+ //
+ Status = PxeBcSelectBootMenu (Private, &Type, TRUE);
+ }
+
+ if (!EFI_ERROR (Status)) {
+
+ if (Type == EFI_PXE_BASE_CODE_BOOT_TYPE_BOOTSTRAP) {
+ //
+ // Local boot(PXE bootstrap server) need abort
+ //
+ return EFI_ABORTED;
+ }
+
+ //
+ // Start to discover the boot server to get (m)tftp server ip address, bootfile
+ // name and bootfile size.
+ //
+ UseBis = (BOOLEAN) (Mode->BisSupported && Mode->BisDetected);
+ Status = PxeBc->Discover (PxeBc, Type, &Layer, UseBis, NULL);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if (Mode->PxeReplyReceived && !Mode->ProxyOfferReceived) {
+ //
+ // Some network boot loader only search the packet in Mode.ProxyOffer to get its server
+ // IP address, so we need to store a copy of Mode.PxeReply packet into Mode.ProxyOffer.
+ //
+ if (Mode->UsingIpv6) {
+ CopyMem (
+ &Mode->ProxyOffer.Dhcpv6,
+ &Mode->PxeReply.Dhcpv6,
+ Private->PxeReply.Dhcp6.Packet.Ack.Length
+ );
+ } else {
+ CopyMem (
+ &Mode->ProxyOffer.Dhcpv4,
+ &Mode->PxeReply.Dhcpv4,
+ Private->PxeReply.Dhcp4.Packet.Ack.Length
+ );
+ }
+ Mode->ProxyOfferReceived = TRUE;
+ }
+ }
+
+ //
+ // Parse the boot information.
+ //
+ if (Mode->UsingIpv6) {
+ Status = PxeBcDhcp6BootInfo (Private, BufferSize);
+ } else {
+ Status = PxeBcDhcp4BootInfo (Private, BufferSize);
+ }
+
+ return Status;
+}
+
+
+/**
+ Install PxeBaseCodeCallbackProtocol if not installed before.
+
+ @param[in, out] Private Pointer to PxeBc private data.
+ @param[out] NewMakeCallback If TRUE, it is a new callback.
+ Otherwise, it is not new callback.
+ @retval EFI_SUCCESS PxeBaseCodeCallbackProtocol installed successfully.
+ @retval Others Failed to install PxeBaseCodeCallbackProtocol.
+
+**/
+EFI_STATUS
+PxeBcInstallCallback (
+ IN OUT PXEBC_PRIVATE_DATA *Private,
+ OUT BOOLEAN *NewMakeCallback
+ )
+{
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+ EFI_STATUS Status;
+
+ //
+ // Check whether PxeBaseCodeCallbackProtocol already installed.
+ //
+ PxeBc = &Private->PxeBc;
+ 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;
+}
+
+
+/**
+ Uninstall PxeBaseCodeCallbackProtocol.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[in] NewMakeCallback If TRUE, it is a new callback.
+ Otherwise, it is not new callback.
+
+**/
+VOID
+PxeBcUninstallCallback (
+ IN PXEBC_PRIVATE_DATA *Private,
+ IN BOOLEAN NewMakeCallback
+ )
+{
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+
+ PxeBc = &Private->PxeBc;
+
+ if (NewMakeCallback) {
+
+ NewMakeCallback = FALSE;
+
+ PxeBc->SetParameters (PxeBc, NULL, NULL, NULL, NULL, &NewMakeCallback);
+
+ gBS->UninstallProtocolInterface (
+ Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,
+ &gEfiPxeBaseCodeCallbackProtocolGuid,
+ &Private->LoadFileCallback
+ );
+ }
+}
+
+
+/**
+ Download one of boot file in the list, and it's special for IPv6.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[in, out] BufferSize Size of user buffer for input;
+ required buffer size for output.
+ @param[in] Buffer Pointer to user buffer.
+
+ @retval EFI_SUCCESS Read one of boot file in the list successfully.
+ @retval EFI_BUFFER_TOO_SMALL The buffer size is not enough for boot file.
+ @retval EFI_NOT_FOUND There is no proper boot file available.
+ @retval Others Failed to download boot file in the list.
+
+**/
+EFI_STATUS
+PxeBcReadBootFileList (
+ IN PXEBC_PRIVATE_DATA *Private,
+ IN OUT UINT64 *BufferSize,
+ IN VOID *Buffer OPTIONAL
+ )
+{
+ EFI_STATUS Status;
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+
+ PxeBc = &Private->PxeBc;
+
+ //
+ // Try to download the boot file if everything is ready.
+ //
+ if (Buffer != NULL) {
+ Status = PxeBc->Mtftp (
+ PxeBc,
+ EFI_PXE_BASE_CODE_TFTP_READ_FILE,
+ Buffer,
+ FALSE,
+ BufferSize,
+ &Private->BlockSize,
+ &Private->ServerIp,
+ Private->BootFileName,
+ NULL,
+ FALSE
+ );
+
+
+ } else {
+ Status = EFI_BUFFER_TOO_SMALL;
+ }
+
+ return Status;
+}
+
+
+/**
+ Load boot file into user buffer.
+
+ @param[in] Private Pointer to PxeBc private data.
+ @param[in, out] BufferSize Size of user buffer for input;
+ required buffer size for output.
+ @param[in] Buffer Pointer to user buffer.
+
+ @retval EFI_SUCCESS Get all the boot information successfully.
+ @retval EFI_BUFFER_TOO_SMALL The buffer size is not enough for boot file.
+ @retval EFI_ABORTED User cancelled the current operation.
+ @retval Others Failed to parse out the boot information.
+
+**/
+EFI_STATUS
+PxeBcLoadBootFile (
+ IN PXEBC_PRIVATE_DATA *Private,
+ IN OUT UINTN *BufferSize,
+ IN VOID *Buffer OPTIONAL
+ )
+{
+ BOOLEAN NewMakeCallback;
+ UINT64 RequiredSize;
+ UINT64 CurrentSize;
+ EFI_STATUS Status;
+ EFI_PXE_BASE_CODE_PROTOCOL *PxeBc;
+ EFI_PXE_BASE_CODE_MODE *PxeBcMode;
+
+ NewMakeCallback = FALSE;
+ PxeBc = &Private->PxeBc;
+ PxeBcMode = &Private->Mode;
+ CurrentSize = *BufferSize;
+ RequiredSize = 0;
+
+ //
+ // Install pxebc callback protocol if hasn't been installed yet.
+ //
+ Status = PxeBcInstallCallback (Private, &NewMakeCallback);
+ if (EFI_ERROR(Status)) {
+ return Status;
+ }
+
+ if (Private->BootFileSize == 0) {
+ //
+ // Discover the boot information about the bootfile if hasn't.
+ //
+ Status = PxeBcDiscoverBootFile (Private, &RequiredSize);
+ if (EFI_ERROR (Status)) {
+ goto ON_EXIT;
+ }
+
+ if (PXEBC_IS_SIZE_OVERFLOWED (RequiredSize)) {
+ //
+ // It's error if the required buffer size is beyond the system scope.
+ //
+ Status = EFI_DEVICE_ERROR;
+ goto ON_EXIT;
+ } else if (RequiredSize > 0) {
+ //
+ // Get the right buffer size of the bootfile required.
+ //
+ if (CurrentSize < RequiredSize || Buffer == NULL) {
+ //
+ // It's buffer too small if the size of user buffer is smaller than the required.
+ //
+ CurrentSize = RequiredSize;
+ Status = EFI_BUFFER_TOO_SMALL;
+ goto ON_EXIT;
+ }
+ CurrentSize = RequiredSize;
+ } else if (RequiredSize == 0 && PxeBcMode->UsingIpv6) {
+ //
+ // Try to download another bootfile in list if failed to get the filesize of the last one.
+ // It's special for the case of IPv6.
+ //
+ Status = PxeBcReadBootFileList (Private, &CurrentSize, Buffer);
+ goto ON_EXIT;
+ }
+ } else if (CurrentSize < Private->BootFileSize || Buffer == NULL ) {
+ //
+ // It's buffer too small if the size of user buffer is smaller than the required.
+ //
+ CurrentSize = Private->BootFileSize;
+ Status = EFI_BUFFER_TOO_SMALL;
+ goto ON_EXIT;
+ }
+
+ //
+ // Begin to download the bootfile if everything is ready.
+ //
+ AsciiPrint ("\n Downloading NBP file...\n");
+ if (PxeBcMode->UsingIpv6) {
+ Status = PxeBcReadBootFileList (
+ Private,
+ &CurrentSize,
+ Buffer
+ );
+ } else {
+ Status = PxeBc->Mtftp (
+ PxeBc,
+ EFI_PXE_BASE_CODE_TFTP_READ_FILE,
+ Buffer,
+ FALSE,
+ &CurrentSize,
+ &Private->BlockSize,
+ &Private->ServerIp,
+ Private->BootFileName,
+ NULL,
+ FALSE
+ );
+ }
+
+ON_EXIT:
+ *BufferSize = (UINTN) CurrentSize;
+ PxeBcUninstallCallback(Private, NewMakeCallback);
+
+ if (Status == EFI_SUCCESS) {
+ AsciiPrint ("\n NBP file downloaded successfully.\n");
+ return EFI_SUCCESS;
+ } else if (Status == EFI_BUFFER_TOO_SMALL && Buffer != NULL) {
+ AsciiPrint ("\n PXE-E05: Buffer size is smaller than the requested file.\n");
+ } else if (Status == EFI_DEVICE_ERROR) {
+ AsciiPrint ("\n PXE-E07: Network device error.\n");
+ } else if (Status == EFI_OUT_OF_RESOURCES) {
+ AsciiPrint ("\n PXE-E09: Could not allocate I/O buffers.\n");
+ } else if (Status == EFI_NO_MEDIA) {
+ AsciiPrint ("\n PXE-E12: Could not detect network connection.\n");
+ } else if (Status == EFI_NO_RESPONSE) {
+ AsciiPrint ("\n PXE-E16: No valid offer received.\n");
+ } else if (Status == EFI_TIMEOUT) {
+ AsciiPrint ("\n PXE-E18: Server response timeout.\n");
+ } else if (Status == EFI_ABORTED) {
+ AsciiPrint ("\n PXE-E21: Remote boot cancelled.\n");
+ } else if (Status == EFI_ICMP_ERROR) {
+ AsciiPrint ("\n PXE-E22: Client received ICMP error from server.\n");
+ } else if (Status == EFI_TFTP_ERROR) {
+ AsciiPrint ("\n PXE-E23: Client received TFTP error from server.\n");
+ } else if (Status == EFI_NOT_FOUND) {
+ AsciiPrint ("\n PXE-E53: No boot filename received.\n");
+ } else if (Status != EFI_BUFFER_TOO_SMALL) {
+ AsciiPrint ("\n PXE-E99: Unexpected network error.\n");
+ }
+
+ return Status;
+}
+