From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c | 1282 ++++++++++++++++++++ 1 file changed, 1282 insertions(+) create mode 100644 src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c (limited to 'src/VBox/Devices/EFI/Firmware/NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c') 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.
+ (C) Copyright 2016 Hewlett Packard Enterprise Development LP
+ + 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 or + 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; +} + -- cgit v1.2.3