2010-11-01 07:13:54 +01:00
|
|
|
/** @file
|
|
|
|
Boot functions implementation for UefiPxeBc Driver.
|
|
|
|
|
2018-06-27 15:12:32 +02:00
|
|
|
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
|
2016-02-12 01:38:30 +01:00
|
|
|
(C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
|
2010-11-01 07:13:54 +01:00
|
|
|
|
2019-04-04 01:06:13 +02:00
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
|
|
|
//
|
2012-08-24 10:25:42 +02:00
|
|
|
// Only DhcpPxe10 and ProxyPxe10 offer needs boot prompt.
|
2010-11-01 07:13:54 +01:00
|
|
|
//
|
2012-08-24 10:25:42 +02:00
|
|
|
if (OfferType != PxeOfferTypeProxyPxe10 && OfferType != PxeOfferTypeDhcpPxe10) {
|
2010-11-01 07:13:54 +01:00
|
|
|
return EFI_NOT_FOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// There is no specified ProxyPxe10 for IPv6 in PXE and UEFI spec.
|
|
|
|
//
|
|
|
|
ASSERT (!Mode->UsingIpv6);
|
|
|
|
|
|
|
|
VendorOpt = &Cache->Dhcp4.VendorOpt;
|
2013-05-09 07:28:27 +02:00
|
|
|
//
|
|
|
|
// 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:
|
2018-06-27 15:12:32 +02:00
|
|
|
// - the PXE_DISCOVERY_CONTROL tag(6) is present inside the Vendor Options(43), and has bit 3 set
|
2013-05-09 07:28:27 +02:00
|
|
|
// - 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;
|
|
|
|
}
|
2018-06-27 15:12:32 +02:00
|
|
|
|
2010-11-01 07:13:54 +01:00
|
|
|
if (!IS_VALID_BOOT_PROMPT (VendorOpt->BitMap)) {
|
2012-08-24 10:25:42 +02:00
|
|
|
return EFI_TIMEOUT;
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2012-08-24 10:25:42 +02:00
|
|
|
return EFI_TIMEOUT;
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
if (Timeout == 255) {
|
2012-08-24 10:25:42 +02:00
|
|
|
return EFI_SUCCESS;
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// 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,
|
2016-07-21 05:20:26 +02:00
|
|
|
MultU64x32 (Timeout, TICKS_PER_SECOND)
|
2010-11-01 07:13:54 +01:00
|
|
|
);
|
|
|
|
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--);
|
|
|
|
|
2012-08-24 10:25:42 +02:00
|
|
|
Status = EFI_TIMEOUT;
|
2010-11-01 07:13:54 +01:00
|
|
|
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.
|
2012-08-24 10:25:42 +02:00
|
|
|
// If <F8> or <Ctrl> + <M> is pressed, return success to display the boot menu.
|
2010-11-01 07:13:54 +01:00
|
|
|
//
|
|
|
|
if (InputKey.ScanCode == 0) {
|
|
|
|
|
|
|
|
switch (InputKey.UnicodeChar) {
|
|
|
|
|
|
|
|
case CTRL ('c'):
|
|
|
|
Status = EFI_ABORTED;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL ('m'):
|
|
|
|
case 'm':
|
|
|
|
case 'M':
|
2012-08-24 10:25:42 +02:00
|
|
|
Status = EFI_SUCCESS;
|
2010-11-01 07:13:54 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
switch (InputKey.ScanCode) {
|
|
|
|
|
|
|
|
case SCAN_F8:
|
2012-08-24 10:25:42 +02:00
|
|
|
Status = EFI_SUCCESS;
|
2010-11-01 07:13:54 +01:00
|
|
|
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.
|
2020-02-07 02:08:05 +01:00
|
|
|
@retval EFI_NOT_READY Read the input key from the keyboard has not finish.
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
**/
|
|
|
|
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;
|
2013-02-01 05:47:37 +01:00
|
|
|
Select = 0;
|
2010-11-01 07:13:54 +01:00
|
|
|
Index = 0;
|
|
|
|
*Type = 0;
|
|
|
|
Mode = Private->PxeBc.Mode;
|
|
|
|
Cache = Mode->ProxyOfferReceived ? &Private->ProxyOffer : &Private->DhcpAck;
|
|
|
|
OfferType = Mode->UsingIpv6 ? Cache->Dhcp6.OfferType : Cache->Dhcp4.OfferType;
|
|
|
|
|
|
|
|
//
|
2012-08-24 10:25:42 +02:00
|
|
|
// There is no specified DhcpPxe10/ProxyPxe10 for IPv6 in PXE and UEFI spec.
|
2010-11-01 07:13:54 +01:00
|
|
|
//
|
|
|
|
ASSERT (!Mode->UsingIpv6);
|
2012-08-24 10:25:42 +02:00
|
|
|
ASSERT (OfferType == PxeOfferTypeProxyPxe10 || OfferType == PxeOfferTypeDhcpPxe10);
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-08-24 10:25:42 +02:00
|
|
|
if (InputKey.ScanCode == 0) {
|
2010-11-01 07:13:54 +01:00
|
|
|
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;
|
2014-01-06 03:08:05 +01:00
|
|
|
PXEBC_VENDOR_OPTION *VendorOpt;
|
|
|
|
PXEBC_BOOT_SVR_ENTRY *Entry;
|
2018-06-27 15:12:32 +02:00
|
|
|
|
2010-11-01 07:13:54 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
NetworkPkg/UefiPxeBcDxe: handle competing DHCP servers (more) gracefully
When DHCP is misconfigured on a network segment, such that two DHCP
servers attempt to reply to requests (and therefore race with each other),
the edk2 PXE client can confuse itself.
In PxeBcDhcp4BootInfo() / PxeBcDhcp6BootInfo(), the client may refer to a
DHCP reply packet as an "earlier" packet from the "same" DHCP server, when
in reality both packets are unrelated, and arrive from different DHCP
servers.
While the edk2 PXE client can do nothing to fix this, it should at least
not ASSERT() -- ASSERT() is for catching programming errors (violations of
invariants that are under the control of the programmer). ASSERT()s should
in particular not refer to external data (such as network packets). What's
more, in RELEASE builds, we get NULL pointer references.
Check the problem conditions with actual "if"s, and return
EFI_PROTOCOL_ERROR. This will trickle out to PxeBcLoadBootFile(), and be
reported as "PXE-E99: Unexpected network error".
Cc: Jiaxin Wu <jiaxin.wu@intel.com>
Cc: Maciej Rabeda <maciej.rabeda@linux.intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Cc: Siyuan Fu <siyuan.fu@intel.com>
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200331004749.16128-1-lersek@redhat.com>
Reviewed-by: Siyuan Fu <siyuan.fu@intel.com>
Reviewed-by: Maciej Rabeda <maciej.rabeda@linux.intel.com>
2020-03-31 02:47:49 +02:00
|
|
|
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;
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
|
2014-01-06 03:08:05 +01:00
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
if (Private->ServerIp.Addr[0] == 0) {
|
2014-01-06 03:08:05 +01:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
2010-11-01 07:13:54 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
NetworkPkg/UefiPxeBcDxe: handle competing DHCP servers (more) gracefully
When DHCP is misconfigured on a network segment, such that two DHCP
servers attempt to reply to requests (and therefore race with each other),
the edk2 PXE client can confuse itself.
In PxeBcDhcp4BootInfo() / PxeBcDhcp6BootInfo(), the client may refer to a
DHCP reply packet as an "earlier" packet from the "same" DHCP server, when
in reality both packets are unrelated, and arrive from different DHCP
servers.
While the edk2 PXE client can do nothing to fix this, it should at least
not ASSERT() -- ASSERT() is for catching programming errors (violations of
invariants that are under the control of the programmer). ASSERT()s should
in particular not refer to external data (such as network packets). What's
more, in RELEASE builds, we get NULL pointer references.
Check the problem conditions with actual "if"s, and return
EFI_PROTOCOL_ERROR. This will trickle out to PxeBcLoadBootFile(), and be
reported as "PXE-E99: Unexpected network error".
Cc: Jiaxin Wu <jiaxin.wu@intel.com>
Cc: Maciej Rabeda <maciej.rabeda@linux.intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Cc: Siyuan Fu <siyuan.fu@intel.com>
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200331004749.16128-1-lersek@redhat.com>
Reviewed-by: Siyuan Fu <siyuan.fu@intel.com>
Reviewed-by: Maciej Rabeda <maciej.rabeda@linux.intel.com>
2020-03-31 02:47:49 +02:00
|
|
|
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;
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
|
2016-10-14 09:12:09 +02:00
|
|
|
//
|
|
|
|
// Set the station address to IP layer.
|
|
|
|
//
|
|
|
|
Status = PxeBcSetIp6Address (Private);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-11-01 07:13:54 +01:00
|
|
|
//
|
|
|
|
// Parse (m)tftp server ip address and bootfile name.
|
|
|
|
//
|
|
|
|
Status = PxeBcExtractBootFileUrl (
|
2016-10-14 09:12:09 +02:00
|
|
|
Private,
|
2010-11-01 07:13:54 +01:00
|
|
|
&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.
|
2012-08-24 10:25:42 +02:00
|
|
|
@param[in, out] DiscoverInfo Pointer to EFI_PXE_BASE_CODE_DISCOVER_INFO.
|
2010-11-01 07:13:54 +01:00
|
|
|
@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,
|
2012-08-24 10:25:42 +02:00
|
|
|
IN OUT EFI_PXE_BASE_CODE_DISCOVER_INFO **DiscoverInfo,
|
2010-11-01 07:13:54 +01:00
|
|
|
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;
|
2012-08-24 10:25:42 +02:00
|
|
|
EFI_PXE_BASE_CODE_DISCOVER_INFO *Info;
|
|
|
|
UINT16 Index;
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
Mode = Private->PxeBc.Mode;
|
2012-08-24 10:25:42 +02:00
|
|
|
Info = *DiscoverInfo;
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
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);
|
2012-08-24 10:25:42 +02:00
|
|
|
Info->UseUCast = (BOOLEAN) IS_VALID_BOOT_SERVERS (VendorOpt->BitMap);
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2012-08-24 10:25:42 +02:00
|
|
|
if (Info->UseUCast) {
|
2010-11-01 07:13:54 +01:00
|
|
|
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;
|
2012-08-24 10:25:42 +02:00
|
|
|
if (Info->IpCnt >= 1) {
|
|
|
|
*DiscoverInfo = AllocatePool (sizeof (*Info) + (Info->IpCnt - 1) * sizeof (**SrvList));
|
|
|
|
if (*DiscoverInfo == NULL) {
|
2018-06-27 15:12:32 +02:00
|
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
}
|
2012-08-24 10:25:42 +02:00
|
|
|
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);
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
*BootEntry = Entry;
|
2012-08-24 10:25:42 +02:00
|
|
|
*SrvList = Info->SrvList;
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
//
|
2012-08-24 10:25:42 +02:00
|
|
|
Status = PxeBcSelectBootMenu (Private, &Type, FALSE);
|
2010-11-01 07:13:54 +01:00
|
|
|
} else if (Status == EFI_TIMEOUT) {
|
|
|
|
//
|
|
|
|
// Choose by default item.
|
|
|
|
//
|
2012-08-24 10:25:42 +02:00
|
|
|
Status = PxeBcSelectBootMenu (Private, &Type, TRUE);
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2012-08-24 10:25:42 +02:00
|
|
|
|
|
|
|
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
|
2018-06-27 15:12:32 +02:00
|
|
|
);
|
2012-08-24 10:25:42 +02:00
|
|
|
}
|
|
|
|
Mode->ProxyOfferReceived = TRUE;
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// 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.
|
2020-02-07 02:08:05 +01:00
|
|
|
@retval EFI_SUCCESS PxeBaseCodeCallbackProtocol installed successfully.
|
2010-11-01 07:13:54 +01:00
|
|
|
@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 (
|
2017-12-12 08:26:49 +01:00
|
|
|
Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,
|
2010-11-01 07:13:54 +01:00
|
|
|
&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 (
|
2017-12-12 08:26:49 +01:00
|
|
|
Private->Mode.UsingIpv6 ? &Private->Ip6Nic->Controller : &Private->Ip4Nic->Controller,
|
2010-11-01 07:13:54 +01:00
|
|
|
&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 (
|
2017-12-12 08:26:49 +01:00
|
|
|
Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,
|
2010-11-01 07:13:54 +01:00
|
|
|
&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);
|
2011-06-01 05:30:05 +02:00
|
|
|
if (EFI_ERROR (Status)) {
|
|
|
|
goto ON_EXIT;
|
|
|
|
}
|
2010-11-01 07:13:54 +01:00
|
|
|
|
|
|
|
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) {
|
2016-02-12 01:38:30 +01:00
|
|
|
AsciiPrint ("\n NBP file downloaded successfully.\n");
|
2010-11-01 07:13:54 +01:00
|
|
|
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) {
|
2017-12-12 04:02:47 +01:00
|
|
|
AsciiPrint ("\n PXE-E16: No valid offer received.\n");
|
2010-11-01 07:13:54 +01:00
|
|
|
} 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");
|
2011-08-24 10:53:22 +02:00
|
|
|
} else if (Status == EFI_NOT_FOUND) {
|
|
|
|
AsciiPrint ("\n PXE-E53: No boot filename received.\n");
|
2010-11-01 07:13:54 +01:00
|
|
|
} else if (Status != EFI_BUFFER_TOO_SMALL) {
|
|
|
|
AsciiPrint ("\n PXE-E99: Unexpected network error.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|