/** @file

Copyright (c) 2004 - 2008, Intel Corporation
All rights reserved. This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

Module Name:
  pxe_loadfile.c

Abstract:
  An implementation of the load file protocol for network devices.


**/


#include "Bc.h"

#define DO_MENU     (EFI_SUCCESS)
#define NO_MENU     (DO_MENU + 1)
#define LOCAL_BOOT  (EFI_ABORTED)
#define AUTO_SELECT (NO_MENU)

#define NUMBER_ROWS   25  // we set to mode 0
#define MAX_MENULIST  23

#define Ctl(x)  (0x1F & (x))

typedef union {
  DHCPV4_OP_STRUCT          *OpPtr;
  PXE_BOOT_MENU_ENTRY       *CurrentMenuItemPtr;
  PXE_OP_DISCOVERY_CONTROL  *DiscCtlOpStr;
  PXE_OP_BOOT_MENU          *MenuPtr;
  UINT8                     *BytePtr;
} UNION_PTR;

UINTN  mPropeller;

/**
  PxeBc callback routine for status updates and aborts.

  @param  This                                        Pointer to PxeBcCallback
                                                      interface
  @param  Function                                    PxeBc function ID#
  @param  Received                                    Receive/transmit flag
  @param  PacketLength                                Length of received packet (0
                                                      == idle callback)
  @param  PacketPtr                                   Pointer to received packet
                                                      (NULL == idle callback)

  @retval EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE
                                                      EFI_PXE_BASE_CODE_CALLBACK_STATUS_ABORT
                                                      -

**/
EFI_PXE_BASE_CODE_CALLBACK_STATUS
EFIAPI
bc_callback (
  IN EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL  * This,
  IN EFI_PXE_BASE_CODE_FUNCTION           Function,
  IN BOOLEAN                              Received,
  IN UINT32                               PacketLength,
  IN EFI_PXE_BASE_CODE_PACKET             * PacketPtr OPTIONAL
  )
{
  EFI_INPUT_KEY Key;
  UINTN         Row;
  UINTN         Col;

  mPropeller = 0;
  //
  // Resolve Warning 4 unreferenced parameter problem
  //
  This = This;

  //
  // Check for user abort.
  //
  if (gST->ConIn->ReadKeyStroke (gST->ConIn, &Key) == EFI_SUCCESS) {
    if (!Key.ScanCode) {
      if (Key.UnicodeChar == Ctl ('c')) {
        return EFI_PXE_BASE_CODE_CALLBACK_STATUS_ABORT;
      }
    } else if (Key.ScanCode == SCAN_ESC) {
      return EFI_PXE_BASE_CODE_CALLBACK_STATUS_ABORT;
    }
  }
  //
  // Do nothing if this is a receive.
  //
  if (Received) {
    return EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE;
  }
  //
  // The display code is only for these functions.
  //
  switch (Function) {
  case EFI_PXE_BASE_CODE_FUNCTION_MTFTP:
    //
    // If this is a transmit and not a M/TFTP open request,
    // return now.  Do not print a dot for each M/TFTP packet
    // that is sent, only for the open packets.
    //
    if (PacketLength != 0 && PacketPtr != NULL) {
      if (PacketPtr->Raw[0x1C] != 0x00 || PacketPtr->Raw[0x1D] != 0x01) {
        return EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE;
      }
    }

    break;

  case EFI_PXE_BASE_CODE_FUNCTION_DHCP:
  case EFI_PXE_BASE_CODE_FUNCTION_DISCOVER:
    break;

  default:
    return EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE;
  }
  //
  // Display routines
  //
  if (PacketLength != 0 && PacketPtr != NULL) {
    //
    // Display a '.' when a packet is transmitted.
    //
    AsciiPrint (".");
  } else if (PacketLength == 0 && PacketPtr == NULL) {
    //
    // Display a propeller when waiting for packets if at
    // least 200 ms have passed.
    //
    Row = gST->ConOut->Mode->CursorRow;
    Col = gST->ConOut->Mode->CursorColumn;

    AsciiPrint ("%c", "/-\\|"[mPropeller]);
    gST->ConOut->SetCursorPosition (gST->ConOut, Col, Row);

    mPropeller = (mPropeller + 1) & 3;
  }

  return EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE;
}

EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL  _bc_callback = {
  EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL_REVISION,
  &bc_callback
};


/**
  Display an IPv4 address in dot notation.

  @param  Ptr                                         Pointer to IPv4 address.

  @return None

**/
VOID
PrintIpv4 (
  UINT8 *Ptr
  )
{
  if (Ptr != NULL) {
    AsciiPrint ("%d.%d.%d.%d", Ptr[0], Ptr[1], Ptr[2], Ptr[3]);
  }
}


/**
  Display client and server IP information.

  @param  Private                                     Pointer to PxeBc interface

  @return None

**/
VOID
ShowMyInfo (
  IN PXE_BASECODE_DEVICE *Private
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  UINTN                   Index;

  //
  // Do nothing if a NULL pointer is passed in.
  //
  if (Private == NULL) {
    return ;
  }
  //
  // Get pointer to PXE BaseCode mode structure
  //
  PxeBcMode = Private->EfiBc.Mode;

  //
  // Display client IP address
  //
  AsciiPrint ("\rCLIENT IP: ");
  PrintIpv4 (PxeBcMode->StationIp.v4.Addr);

  //
  // Display subnet mask
  //
  AsciiPrint ("  MASK: ");
  PrintIpv4 (PxeBcMode->SubnetMask.v4.Addr);

  //
  // Display DHCP and proxyDHCP IP addresses
  //
  if (PxeBcMode->ProxyOfferReceived) {
    AsciiPrint ("\nDHCP IP: ");
    PrintIpv4 (((DHCPV4_OP_SERVER_IP *) DHCPV4_ACK_BUFFER.OpAdds.PktOptAdds[OP_DHCP_SERVER_IP_IX - 1])->Ip.Addr);

    AsciiPrint ("  PROXY IP: ");
    PrintIpv4 (((DHCPV4_OP_SERVER_IP *) PXE_OFFER_BUFFER.OpAdds.PktOptAdds[OP_DHCP_SERVER_IP_IX - 1])->Ip.Addr);
  } else {
    AsciiPrint ("  DHCP IP: ");
    PrintIpv4 (((DHCPV4_OP_SERVER_IP *) DHCPV4_ACK_BUFFER.OpAdds.PktOptAdds[OP_DHCP_SERVER_IP_IX - 1])->Ip.Addr);
  }
  //
  // Display gateway IP addresses
  //
  for (Index = 0; Index < PxeBcMode->RouteTableEntries; ++Index) {
    if ((Index % 3) == 0) {
      AsciiPrint ("\r\nGATEWAY IP:");
    }

    AsciiPrint (" ");
    PrintIpv4 (PxeBcMode->RouteTable[Index].GwAddr.v4.Addr);
    AsciiPrint (" ");
  }

  AsciiPrint ("\n");
}


/**
  Display prompt and wait for input.

  @param  Private                                     Pointer to PxeBc interface
  @param  BootPromptPtr                               Pointer to PXE boot prompt
                                                      option

  @retval AUTO_SELECT                                 DO_MENU -
  @retval NO_MENU
  @retval LOCAL_BOOT

**/
EFI_STATUS
DoPrompt (
  PXE_BASECODE_DEVICE *Private,
  PXE_OP_BOOT_PROMPT  *BootPromptPtr
  )
{
  EFI_STATUS  Status;
  EFI_EVENT   TimeoutEvent;
  EFI_EVENT   SecondsEvent;
  INT32       SecColumn;
  INT32       SecRow;
  UINT8       SaveChar;
  UINT8       SecsLeft;

  //
  // if auto select, just get right to it
  //
  if (BootPromptPtr->Timeout == PXE_BOOT_PROMPT_AUTO_SELECT) {
    return AUTO_SELECT;
  }
  //
  // if no timeout, go directly to display of menu
  //
  if (BootPromptPtr->Timeout == PXE_BOOT_PROMPT_NO_TIMEOUT) {
    return DO_MENU;
  }
  //
  //
  //
  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &TimeoutEvent
                  );

  if (EFI_ERROR (Status)) {
    return DO_MENU;
  }

  Status = gBS->SetTimer (
                  TimeoutEvent,
                  TimerRelative,
                  BootPromptPtr->Timeout * 10000000 + 100000
                  );

  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (TimeoutEvent);
    return DO_MENU;
  }
  //
  //
  //
  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &SecondsEvent
                  );

  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (TimeoutEvent);
    return DO_MENU;
  }

  Status = gBS->SetTimer (
                  SecondsEvent,
                  TimerPeriodic,
                  10000000
                  );  /* 1 second */

  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (SecondsEvent);
    gBS->CloseEvent (TimeoutEvent);
    return DO_MENU;
  }
  //
  // display the prompt
  // IMPORTANT!  This prompt is an ASCII character string that may
  // not be terminated with a NULL byte.
  //
  SaveChar  = BootPromptPtr->Prompt[BootPromptPtr->Header.Length - 1];
  BootPromptPtr->Prompt[BootPromptPtr->Header.Length - 1] = 0;

  AsciiPrint ("%a ", BootPromptPtr->Prompt);
  BootPromptPtr->Prompt[BootPromptPtr->Header.Length - 1] = SaveChar;

  //
  // wait until time expires or selection made - menu or local
  //
  SecColumn = gST->ConOut->Mode->CursorColumn;
  SecRow    = gST->ConOut->Mode->CursorRow;
  SecsLeft  = BootPromptPtr->Timeout;

  gST->ConOut->SetCursorPosition (gST->ConOut, SecColumn, SecRow);
  AsciiPrint ("(%d) ", SecsLeft);

  //
  // set the default action to be AUTO_SELECT
  //
  Status = AUTO_SELECT;

  while (EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
    EFI_INPUT_KEY Key;

    if (!EFI_ERROR (gBS->CheckEvent (SecondsEvent))) {
      --SecsLeft;
      gST->ConOut->SetCursorPosition (gST->ConOut, SecColumn, SecRow);
      AsciiPrint ("(%d) ", SecsLeft);
    }

    if (gST->ConIn->ReadKeyStroke (gST->ConIn, &Key) == EFI_NOT_READY) {
      UINT8       Buffer[512];
      UINTN       BufferSize;

      BufferSize = sizeof Buffer;

      Status = Private->EfiBc.UdpRead (
                                &Private->EfiBc,
                                EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP |
                                EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT |
                                EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT,
                                NULL, /* dest ip */
                                NULL, /* dest port */
                                NULL, /* src ip */
                                NULL, /* src port */
                                NULL, /* hdr size */
                                NULL, /* hdr ptr */
                                &BufferSize,
                                Buffer
                                );

      continue;
    }

    if (Key.ScanCode == 0) {
      switch (Key.UnicodeChar) {
      case Ctl ('c'):
        Status = LOCAL_BOOT;
        break;

      case Ctl ('m'):
      case 'm':
      case 'M':
        Status = DO_MENU;
        break;

      default:
        continue;
      }
    } else {
      switch (Key.ScanCode) {
      case SCAN_F8:
        Status = DO_MENU;
        break;

      case SCAN_ESC:
        Status = LOCAL_BOOT;
        break;

      default:
        continue;
      }
    }

    break;
  }

  gBS->CloseEvent (SecondsEvent);
  gBS->CloseEvent (TimeoutEvent);

  gST->ConOut->SetCursorPosition (gST->ConOut, SecColumn, SecRow);
  AsciiPrint ("     ");

  return Status;
}


/**
  Display one menu item.

  @param  MenuItemPtr                                 Pointer to PXE menu item
                                                      option.

  @return None

**/
VOID
PrintMenuItem (
  PXE_BOOT_MENU_ENTRY *MenuItemPtr
  )
{
  UINT8 Length;
  UINT8 SaveChar;

  Length                    = (UINT8) MIN (70, MenuItemPtr->DataLen);
  SaveChar                  = MenuItemPtr->Data[Length];

  MenuItemPtr->Data[Length] = 0;
  AsciiPrint ("     %a\n", MenuItemPtr->Data);
  MenuItemPtr->Data[Length] = SaveChar;
}


/**
  Display and process menu.

  @param  Private                                     Pointer to PxeBc interface
  @param  RxBufferPtr                                 Pointer to receive buffer

  @retval NO_MENU
  @retval LOCAL_BOOT

**/
EFI_STATUS
DoMenu (
  PXE_BASECODE_DEVICE *Private,
  DHCP_RECEIVE_BUFFER *RxBufferPtr
  )
{
  PXE_OP_DISCOVERY_CONTROL  *DiscoveryControlPtr;
  PXE_BOOT_MENU_ENTRY       *MenuItemPtrs[MAX_MENULIST];
  EFI_STATUS                Status;
  UNION_PTR                 Ptr;
  UINTN                     SaveNumRte;
  UINTN                     TopRow;
  UINTN                     MenuLth;
  UINTN                     NumMenuItems;
  UINTN                     Index;
  UINTN                     Longest;
  UINTN                     Selected;
  UINT16                    Type;
  UINT16                    Layer;
  BOOLEAN                   Done;

  Selected  = 0;
  Layer     = 0;

  DEBUG ((DEBUG_WARN, "\nDoMenu()  Enter."));

  /* see if we have a menu/prompt */
  if (!(RxBufferPtr->OpAdds.Status & DISCOVER_TYPE)) {
    DEBUG (
      (DEBUG_WARN,
      "\nDoMenu()  No menu/prompt info.  OpAdds.Status == %xh  ",
      RxBufferPtr->OpAdds.Status)
      );

    return NO_MENU;
  }

  DiscoveryControlPtr = (PXE_OP_DISCOVERY_CONTROL *) RxBufferPtr->OpAdds.PxeOptAdds[VEND_PXE_DISCOVERY_CONTROL_IX - 1];

  //
  // if not USE_BOOTFILE or no bootfile given, must have menu stuff
  //
  if ((DiscoveryControlPtr->ControlBits & USE_BOOTFILE) && RxBufferPtr->OpAdds.PktOptAdds[OP_DHCP_BOOTFILE_IX - 1]) {
    DEBUG ((DEBUG_WARN, "\nDoMenu()  DHCP w/ bootfile.  "));
    return NO_MENU;
  }
  //
  // do prompt & menu if necessary
  //
  Status = DoPrompt (Private, (PXE_OP_BOOT_PROMPT *) RxBufferPtr->OpAdds.PxeOptAdds[VEND_PXE_BOOT_PROMPT_IX - 1]);

  if (Status == LOCAL_BOOT) {
    DEBUG ((DEBUG_WARN, "\nDoMenu()  DoPrompt() returned LOCAL_BOOT.  "));

    return Status;
  }

  Ptr.BytePtr             = (UINT8 *) RxBufferPtr->OpAdds.PxeOptAdds[VEND_PXE_BOOT_MENU_IX - 1];

  MenuLth                 = Ptr.MenuPtr->Header.Length;
  Ptr.CurrentMenuItemPtr  = Ptr.MenuPtr->MenuItem;

  //
  // build menu items array
  //
  for (Longest = NumMenuItems = Index = 0; Index < MenuLth && NumMenuItems < MAX_MENULIST;) {
    UINTN lth;

    lth = Ptr.CurrentMenuItemPtr->DataLen + sizeof (*Ptr.CurrentMenuItemPtr) - sizeof (Ptr.CurrentMenuItemPtr->Data);

    MenuItemPtrs[NumMenuItems++] = Ptr.CurrentMenuItemPtr;

    if (lth > Longest) {
      //
      // check if too long
      //
      if ((Longest = lth) > 70 + (sizeof (*Ptr.CurrentMenuItemPtr) - sizeof (Ptr.CurrentMenuItemPtr->Data))) {
        Longest = 70 + (sizeof (*Ptr.CurrentMenuItemPtr) - sizeof (Ptr.CurrentMenuItemPtr->Data));
      }
    }

    Index += lth;
    Ptr.BytePtr += lth;
  }

  if (Status != AUTO_SELECT) {
    UINT8 BlankBuf[75];

    SetMem (BlankBuf, sizeof BlankBuf, ' ');
    BlankBuf[Longest + 5 - (sizeof (*Ptr.CurrentMenuItemPtr) - sizeof (Ptr.CurrentMenuItemPtr->Data))] = 0;
    AsciiPrint ("\n");

    //
    // now put up menu
    //
    for (Index = 0; Index < NumMenuItems; ++Index) {
      PrintMenuItem (MenuItemPtrs[Index]);
    }

    TopRow = gST->ConOut->Mode->CursorRow - NumMenuItems;

    //
    // now wait for a selection
    //
    Done = FALSE;
    do {
      //
      // highlight selection
      //
      EFI_INPUT_KEY Key;
      UINTN         NewSelected;

      NewSelected = Selected;

      //
      // highlight selected row
      //
      gST->ConOut->SetAttribute (
                    gST->ConOut,
                    EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY)
                    );
      gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + Selected);

      AsciiPrint (" --->%a\r", BlankBuf);

      PrintMenuItem (MenuItemPtrs[Selected]);
      gST->ConOut->SetAttribute (
                    gST->ConOut,
                    EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)
                    );
      gST->ConOut->SetCursorPosition (gST->ConOut, 0, TopRow + NumMenuItems);

      //
      // wait for a keystroke
      //
      while (gST->ConIn->ReadKeyStroke (gST->ConIn, &Key) == EFI_NOT_READY) {
        UINT8 TmpBuf[512];
        UINTN TmpBufLen;

        TmpBufLen = sizeof TmpBuf;

        Private->EfiBc.UdpRead (
                        &Private->EfiBc,
                        EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP |
                        EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT |
                        EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT,
                        NULL, /* dest ip */
                        NULL, /* dest port */
                        NULL, /* src ip */
                        NULL, /* src port */
                        NULL, /* hdr size */
                        NULL, /* hdr ptr */
                        &TmpBufLen,
                        TmpBuf
                        );
      }

      if (!Key.ScanCode) {
        switch (Key.UnicodeChar) {
        case Ctl ('c'):
          Key.ScanCode = SCAN_ESC;
          break;

        case Ctl ('j'): /* linefeed */
        case Ctl ('m'): /* return */
          Done = TRUE;
          break;

        case Ctl ('i'): /* tab */
        case ' ':
        case 'd':
        case 'D':
          Key.ScanCode = SCAN_DOWN;
          break;

        case Ctl ('h'): /* backspace */
        case 'u':
        case 'U':
          Key.ScanCode = SCAN_UP;
          break;

        default:
          Key.ScanCode = 0;
        }
      }

      switch (Key.ScanCode) {
      case SCAN_LEFT:
      case SCAN_UP:
        if (NewSelected) {
          --NewSelected;
        }

        break;

      case SCAN_DOWN:
      case SCAN_RIGHT:
        if (++NewSelected == NumMenuItems) {
          --NewSelected;
        }

        break;

      case SCAN_PAGE_UP:
      case SCAN_HOME:
        NewSelected = 0;
        break;

      case SCAN_PAGE_DOWN:
      case SCAN_END:
        NewSelected = NumMenuItems - 1;
        break;

      case SCAN_ESC:
        return LOCAL_BOOT;
      }

      /* unhighlight last selected row */
      gST->ConOut->SetCursorPosition (gST->ConOut, 5, TopRow + Selected);

      AsciiPrint ("%a\r", BlankBuf);

      PrintMenuItem (MenuItemPtrs[Selected]);

      Selected = NewSelected;
    } while (!Done);
  }

  SaveNumRte  = Private->EfiBc.Mode->RouteTableEntries;

  Type        = NTOHS (MenuItemPtrs[Selected]->Type);

  if (Type == 0) {
    DEBUG ((DEBUG_WARN, "\nDoMenu()  Local boot selected.  "));
    return LOCAL_BOOT;
  }

  AsciiPrint ("Discover");

  Status = Private->EfiBc.Discover (
                            &Private->EfiBc,
                            Type,
                            &Layer,
                            (BOOLEAN) (Private->EfiBc.Mode->BisSupported && Private->EfiBc.Mode->BisDetected),
                            0
                            );

  if (EFI_ERROR (Status)) {
    AsciiPrint ("\r                    \r");

    DEBUG (
      (DEBUG_WARN,
      "\nDoMenu()  Return w/ %xh (%r).",
      Status,
      Status)
      );

    return Status;
  }

  AsciiPrint ("\rBOOT_SERVER_IP: ");
  PrintIpv4 ((UINT8 *) &Private->ServerIp);

  for (Index = SaveNumRte; Index < Private->EfiBc.Mode->RouteTableEntries; ++Index) {
    if ((Index % 3) == 0) {
      AsciiPrint ("\r\nGATEWAY IP:");
    }

    AsciiPrint (" ");
    PrintIpv4 ((UINT8 *) &Private->EfiBc.Mode->RouteTable[Index].GwAddr);
    AsciiPrint (" ");
  }

  AsciiPrint ("\n");

  DEBUG ((DEBUG_WARN, "\nDoMenu()  Return w/ EFI_SUCCESS.  "));

  return EFI_SUCCESS;
}


/**
  Get value 8- or 16-bit value from DHCP option.

  @param  OpPtr                                       Pointer to DHCP option

  @return Value from DHCP option

**/
UINT16
GetValue (
  DHCPV4_OP_STRUCT *OpPtr
  )
{
  if (OpPtr->Header.Length == 1) {
    return OpPtr->Data[0];
  } else {
    return NTOHS (OpPtr->Data);
  }
}


/**
  Locate opcode in buffer.

  @param  BufferPtr                                   Pointer to buffer
  @param  BufferLen                                   Length of buffer
  @param  OpCode                                      Option number

  @return Pointer to opcode, may be NULL

**/
UINT8 *
_PxeBcFindOpt (
  UINT8 *BufferPtr,
  UINTN BufferLen,
  UINT8 OpCode
  )
{
  if (BufferPtr == NULL) {
    return NULL;
  }

  while (BufferLen != 0) {
    if (*BufferPtr == OpCode) {
      return BufferPtr;
    }

    switch (*BufferPtr) {
    case OP_END:
      return NULL;

    case OP_PAD:
      ++BufferPtr;
      --BufferLen;
      continue;
    }

    if ((UINTN) BufferLen <= (UINTN) 2 + BufferPtr[1]) {
      return NULL;
    }

    BufferLen -= 2 + BufferPtr[1];
    BufferPtr += 2 + BufferPtr[1];
  }

  return NULL;
}


/**
  Find option in packet

  @param  PacketPtr                                   Pointer to packet
  @param  OpCode                                      option number

  @return Pointer to option in packet

**/
UINT8 *
PxeBcFindDhcpOpt (
  EFI_PXE_BASE_CODE_PACKET  *PacketPtr,
  UINT8                     OpCode
  )
{
  UINTN PacketLen;
  UINT8 Overload;
  UINT8 *OptionBufferPtr;

  //
  //
  //
  PacketLen = 380;
  Overload  = 0;

  //
  // Figure size of DHCP option space.
  //
  OptionBufferPtr = _PxeBcFindOpt (
                      PacketPtr->Dhcpv4.DhcpOptions,
                      380,
                      OP_DHCP_MAX_MESSAGE_SZ
                      );

  if (OptionBufferPtr != NULL) {
    if (OptionBufferPtr[1] == 2) {
      UINT16  n;

      CopyMem (&n, &OptionBufferPtr[2], 2);
      PacketLen = HTONS (n);

      if (PacketLen < sizeof (EFI_PXE_BASE_CODE_DHCPV4_PACKET)) {
        PacketLen = 380;
      } else {
        PacketLen -= (PacketPtr->Dhcpv4.DhcpOptions - &PacketPtr->Dhcpv4.BootpOpcode) + 28;
      }
    }
  }
  //
  // Look for option overloading.
  //
  OptionBufferPtr = _PxeBcFindOpt (
                      PacketPtr->Dhcpv4.DhcpOptions,
                      PacketLen,
                      OP_DHCP_OPTION_OVERLOAD
                      );

  if (OptionBufferPtr != NULL) {
    if (OptionBufferPtr[1] == 1) {
      Overload = OptionBufferPtr[2];
    }
  }
  //
  // Look for caller's option.
  //
  OptionBufferPtr = _PxeBcFindOpt (
                      PacketPtr->Dhcpv4.DhcpOptions,
                      PacketLen,
                      OpCode
                      );

  if (OptionBufferPtr != NULL) {
    return OptionBufferPtr;
  }

  if (Overload & OVLD_FILE) {
    OptionBufferPtr = _PxeBcFindOpt (PacketPtr->Dhcpv4.BootpBootFile, 128, OpCode);

    if (OptionBufferPtr != NULL) {
      return OptionBufferPtr;
    }
  }

  if (Overload & OVLD_SRVR_NAME) {
    OptionBufferPtr = _PxeBcFindOpt (PacketPtr->Dhcpv4.BootpSrvName, 64, OpCode);

    if (OptionBufferPtr != NULL) {
      return OptionBufferPtr;
    }
  }

  return NULL;
}


/**
  Download file into buffer

  @param  Private                                     Pointer to PxeBc interface
  @param  BufferSize                                  pointer to size of download
                                                      buffer
  @param  Buffer                                      Pointer to buffer

  @return EFI_BUFFER_TOO_SMALL -
  @return EFI_NOT_FOUND -
  @return EFI_PROTOCOL_ERROR -

**/
EFI_STATUS
DownloadFile (
  IN PXE_BASECODE_DEVICE  *Private,
  IN OUT UINT64           *BufferSize,
  IN VOID                 *Buffer
  )
{
  EFI_PXE_BASE_CODE_MTFTP_INFO  MtftpInfo;
  EFI_PXE_BASE_CODE_TFTP_OPCODE OpCode;
  DHCP_RECEIVE_BUFFER           *RxBuf;
  EFI_STATUS                    Status;
  UINTN                         BlockSize;

  RxBuf     = (DHCP_RECEIVE_BUFFER *) Private->BootServerReceiveBuffer;
  BlockSize = 0x8000;

  DEBUG ((EFI_D_WARN, "\nDownloadFile()  Enter."));

  if (Buffer == NULL || *BufferSize == 0 || *BufferSize < Private->FileSize) {
    if (Private->FileSize != 0) {
      *BufferSize = Private->FileSize;
      return EFI_BUFFER_TOO_SMALL;
    }

    AsciiPrint ("\nTSize");

    OpCode = EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE;
  } else if (RxBuf->OpAdds.Status & WfM11a_TYPE) {
    OpCode = EFI_PXE_BASE_CODE_MTFTP_READ_FILE;

    ZeroMem (&MtftpInfo, sizeof MtftpInfo);

    *(IPV4_ADDR *) &MtftpInfo.MCastIp = *(IPV4_ADDR *) RxBuf->OpAdds.PxeOptAdds[VEND_PXE_MTFTP_IP - 1]->Data;

    CopyMem (
      &MtftpInfo.CPort,
      RxBuf->OpAdds.PxeOptAdds[VEND_PXE_MTFTP_CPORT - 1]->Data,
      sizeof MtftpInfo.CPort
      );

    CopyMem (
      &MtftpInfo.SPort,
      RxBuf->OpAdds.PxeOptAdds[VEND_PXE_MTFTP_SPORT - 1]->Data,
      sizeof MtftpInfo.SPort
      );

    MtftpInfo.ListenTimeout   = GetValue (RxBuf->OpAdds.PxeOptAdds[VEND_PXE_MTFTP_TMOUT - 1]);

    MtftpInfo.TransmitTimeout = GetValue (RxBuf->OpAdds.PxeOptAdds[VEND_PXE_MTFTP_DELAY - 1]);

    AsciiPrint ("\nMTFTP");
  } else {
    AsciiPrint ("\nTFTP");

    OpCode = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
  }

  Private->FileSize = 0;

  RxBuf->OpAdds.PktOptAdds[OP_DHCP_BOOTFILE_IX - 1]->Data[RxBuf->OpAdds.PktOptAdds[OP_DHCP_BOOTFILE_IX - 1]->Header.Length] = 0;

  Status = Private->EfiBc.Mtftp (
                            &Private->EfiBc,
                            OpCode,
                            Buffer,
                            FALSE,
                            BufferSize,
                            &BlockSize,
                            &Private->ServerIp,
                            (UINT8 *) RxBuf->OpAdds.PktOptAdds[OP_DHCP_BOOTFILE_IX - 1]->Data,
                            &MtftpInfo,
                            FALSE
                            );

  if (Status != EFI_SUCCESS && Status != EFI_BUFFER_TOO_SMALL) {
    DEBUG ((DEBUG_WARN, "\nDownloadFile()  Exit #1 %Xh", Status));
    return Status;
  }

  if (sizeof (UINTN) < sizeof (UINT64) && *BufferSize > 0xFFFFFFFF) {
    Private->FileSize = 0xFFFFFFFF;
  } else {
    Private->FileSize = (UINTN) *BufferSize;
  }

  if (OpCode == EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE) {
    DEBUG ((DEBUG_WARN, "\nDownloadFile()  Exit #2"));
    return EFI_BUFFER_TOO_SMALL;
  }

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_WARN, "\nDownloadFile()  Exit #3 %Xh", Status));
    return Status;
  }

  if (Private->EfiBc.Mode->BisSupported && Private->EfiBc.Mode->BisDetected && Private->EfiBc.Mode->PxeBisReplyReceived) {
    UINT64  CredentialLen;
    UINT8   CredentialFilename[256];
    UINT8   *op;
    VOID    *CredentialBuffer;

    //
    // Get name of credential file.  It may be in the BOOTP
    // bootfile field or a DHCP option.
    //
    ZeroMem (CredentialFilename, sizeof CredentialFilename);

    op = PxeBcFindDhcpOpt (&Private->EfiBc.Mode->PxeBisReply, OP_DHCP_BOOTFILE);

    if (op != NULL) {
      if (op[1] == 0) {
        /* No credential filename */
        return EFI_NOT_FOUND;
      }

      CopyMem (CredentialFilename, &op[2], op[1]);
    } else {
      if (Private->EfiBc.Mode->PxeBisReply.Dhcpv4.BootpBootFile[0] == 0) {
        /* No credential filename */
        return EFI_NOT_FOUND;
      }

      CopyMem (CredentialFilename, &op[2], 128);
    }
    //
    // Get size of credential file.  It may be available as a
    // DHCP option.  If not, use the TFTP get file size.
    //
    CredentialLen = 0;

    op            = PxeBcFindDhcpOpt (&Private->EfiBc.Mode->PxeBisReply, OP_BOOT_FILE_SZ);

    if (op != NULL) {
      /*
       * This is actually the size of the credential file
       * buffer.  The actual credential file size will be
       * returned when we download the file.
       */
      if (op[1] == 2) {
        UINT16  n;

        CopyMem (&n, &op[2], 2);
        CredentialLen = HTONS (n) * 512;
      }
    }

    if (CredentialLen == 0) {
      BlockSize = 8192;

      Status = Private->EfiBc.Mtftp (
                                &Private->EfiBc,
                                EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
                                NULL,
                                FALSE,
                                &CredentialLen,
                                &BlockSize,
                                &Private->ServerIp,
                                CredentialFilename,
                                NULL,
                                FALSE
                                );

      if (EFI_ERROR (Status)) {
        return Status;
      }

      if (CredentialLen == 0) {
        //
        // %%TBD -- EFI error for invalid credential
        // file.
        //
        return EFI_PROTOCOL_ERROR;
      }
    }
    //
    // Allocate credential file buffer.
    //
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    (UINTN) CredentialLen,
                    &CredentialBuffer
                    );

    if (EFI_ERROR (Status)) {
      return Status;
    }
    //
    // Download credential file.
    //
    BlockSize = 8192;

    Status = Private->EfiBc.Mtftp (
                              &Private->EfiBc,
                              EFI_PXE_BASE_CODE_TFTP_READ_FILE,
                              CredentialBuffer,
                              FALSE,
                              &CredentialLen,
                              &BlockSize,
                              &Private->ServerIp,
                              CredentialFilename,
                              NULL,
                              FALSE
                              );

    if (EFI_ERROR (Status)) {
      gBS->FreePool (CredentialBuffer);
      return Status;
    }
    //
    // Verify credentials.
    //
    if (PxebcBisVerify (Private, Buffer, Private->FileSize, CredentialBuffer, (UINTN) CredentialLen)) {
      Status = EFI_SUCCESS;
    } else {
      //
      // %%TBD -- An EFI error code for failing credential verification.
      //
      Status = EFI_PROTOCOL_ERROR;
    }

    gBS->FreePool (CredentialBuffer);
  }

  return Status;
}


/**
  Start PXE DHCP.  Get DHCP and proxyDHCP information.
  Display remote boot menu and prompt.  Select item from menu.

  @param  Private                                     Pointer to PxeBc interface
  @param  BufferSize                                  Pointer to download buffer
                                                      size
  @param  Buffer                                      Pointer to download buffer

  @retval EFI_SUCCESS
  @retval EFI_NOT_READY

**/
EFI_STATUS
LoadfileStart (
  IN PXE_BASECODE_DEVICE  *Private,
  IN OUT UINT64           *BufferSize,
  IN VOID                 *Buffer
  )
{
  EFI_PXE_BASE_CODE_MODE      *PxeBcMode;
  EFI_SIMPLE_NETWORK_PROTOCOL *Snp;
  EFI_SIMPLE_NETWORK_MODE     *SnpMode;
  EFI_STATUS                  Status;
  VOID                        *RxBuf;

  DEBUG ((DEBUG_WARN, "\nLoadfileStart()  Enter."));

  //
  // Try to start BaseCode, for now only IPv4 is supported
  // so don't try to start using IPv6.
  //
  Status = Private->EfiBc.Start (&Private->EfiBc, FALSE);

  if (EFI_ERROR (Status)) {
    if (Status != EFI_ALREADY_STARTED) {
      DEBUG ((DEBUG_NET, "\nLoadfileStart()  Exit  BC.Start() == %xh", Status));
      return Status;
    }
  }
  //
  // Get pointers to PXE mode structure, SNP protocol structure
  // and SNP mode structure.
  //
  PxeBcMode = Private->EfiBc.Mode;
  Snp       = Private->SimpleNetwork;
  SnpMode   = Snp->Mode;

  //
  // Display client MAC address, like 16-bit PXE ROMs
  //
  AsciiPrint ("\nCLIENT MAC ADDR: ");

  {
    UINTN Index;
    UINTN hlen;

    hlen = SnpMode->HwAddressSize;

    for (Index = 0; Index < hlen; ++Index) {
      AsciiPrint ("%02x ", SnpMode->CurrentAddress.Addr[Index]);
    }
  }

  AsciiPrint ("\nDHCP");

  Status = Private->EfiBc.Dhcp (&Private->EfiBc, TRUE);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_WARN, "\nLoadfileStart()  Exit  BC.Dhcp() == %Xh", Status));
    AsciiPrint ("\r               \r");
    return Status;
  }

  ShowMyInfo (Private);

  RxBuf = PxeBcMode->ProxyOfferReceived ? &PXE_OFFER_BUFFER : &DHCPV4_ACK_BUFFER;
#define RxBufferPtr ((DHCP_RECEIVE_BUFFER *) RxBuf)

  Status = DoMenu (Private, RxBufferPtr);

  if (Status == EFI_SUCCESS) {
    //
    // did a discovery - take info from discovery packet
    //
    RxBuf = &PXE_ACK_BUFFER;
  } else if (Status == NO_MENU) {
    //
    // did not do a discovery - take info from rxbuf
    //
    Private->ServerIp.Addr[0] = RxBufferPtr->u.Dhcpv4.siaddr;

    if (!(Private->ServerIp.Addr[0])) {
      *(IPV4_ADDR *) &Private->ServerIp = *(IPV4_ADDR *) RxBufferPtr->OpAdds.PktOptAdds[OP_DHCP_SERVER_IP_IX - 1]->Data;
    }
  } else {
    DEBUG ((DEBUG_WARN, "\nLoadfileStart()  Exit  DoMenu() == %Xh", Status));
    return Status;
  }

  if (!RxBufferPtr->OpAdds.PktOptAdds[OP_DHCP_BOOTFILE_IX - 1]) {
    DEBUG ((DEBUG_WARN, "\nLoadfileStart()  Exit  Not ready?"));
    return EFI_NOT_READY;
  }
  //
  // check for file size option sent
  //
  if (RxBufferPtr->OpAdds.PktOptAdds[OP_BOOT_FILE_SZ_IX - 1]) {
    Private->FileSize = 512 * NTOHS (RxBufferPtr->OpAdds.PktOptAdds[OP_BOOT_FILE_SZ_IX - 1]->Data);
  }

  Private->BootServerReceiveBuffer  = RxBufferPtr;

  Status = DownloadFile (Private, BufferSize, Buffer);

  DEBUG (
    (DEBUG_WARN,
    "\nLoadfileStart()  Exit.  DownloadFile() = %Xh",
    Status)
    );

  return Status;
}


/**
  Loadfile interface for PxeBc interface

  @param  This                                        Pointer to Loadfile interface
  @param  FilePath                                    Not used and not checked
  @param  BootPolicy                                  Must be TRUE
  @param  BufferSize                                  Pointer to buffer size
  @param  Buffer                                      Pointer to download buffer or
                                                      NULL

  @return EFI_INVALID_PARAMETER -
  @return EFI_UNSUPPORTED -
  @return EFI_SUCCESS -
  @return EFI_BUFFER_TOO_SMALL -

**/
EFI_STATUS
EFIAPI
LoadFile (
  IN EFI_LOAD_FILE_PROTOCOL           *This,
  IN EFI_DEVICE_PATH_PROTOCOL         *FilePath,
  IN BOOLEAN                          BootPolicy,
  IN OUT UINTN                        *BufferSize,
  IN OUT VOID                         *Buffer
  )
{
  LOADFILE_DEVICE *LoadfilePtr;
  UINT64          TmpBufSz;
  INT32           OrigMode;
  INT32           OrigAttribute;
  BOOLEAN         RemoveCallback;
  BOOLEAN         NewMakeCallback;
  EFI_STATUS      Status;
  EFI_STATUS      TempStatus;

  //
  // The following line is only used for passing ICC build.
  //
  DEBUG ((EFI_D_INFO, "FilePath = %p\n", FilePath));

  //
  //
  //
  OrigMode        = gST->ConOut->Mode->Mode;
  OrigAttribute   = gST->ConOut->Mode->Attribute;
  RemoveCallback  = FALSE;

  AsciiPrint ("Running LoadFile()\n");

  //
  // Resolve Warning 4 unreferenced parameter problem
  //
  FilePath = NULL;

  //
  // If either if these parameters are NULL, we cannot continue.
  //
  if (This == NULL || BufferSize == NULL) {
    DEBUG ((DEBUG_WARN, "\nLoadFile()  This or BufferSize == NULL"));
    return EFI_INVALID_PARAMETER;
  }
  //
  // We only support BootPolicy == TRUE
  //
  if (!BootPolicy) {
    DEBUG ((DEBUG_WARN, "\nLoadFile()  BootPolicy == FALSE"));
    return EFI_UNSUPPORTED;
  }
  //
  // Get pointer to LoadFile protocol structure.
  //
  LoadfilePtr = CR (This, LOADFILE_DEVICE, LoadFile, LOADFILE_DEVICE_SIGNATURE);

  if (LoadfilePtr == NULL) {
    DEBUG (
      (DEBUG_NET,
      "\nLoadFile()  Could not get pointer to LoadFile structure")
      );
    return EFI_INVALID_PARAMETER;
  }
  //
  // Lock interface
  //
  EfiAcquireLock (&LoadfilePtr->Lock);

  //
  // Set console output mode and display attribute
  //
  if (OrigMode != 0) {
    gST->ConOut->SetMode (gST->ConOut, 0);
  }

  gST->ConOut->SetAttribute (
                gST->ConOut,
                EFI_TEXT_ATTR (EFI_LIGHTGRAY,EFI_BLACK)
                );

  //
  // See if BaseCode already has a Callback protocol attached.
  // If there is none, attach our own Callback protocol.
  //
  Status = gBS->HandleProtocol (
                  LoadfilePtr->Private->Handle,
                  &gEfiPxeBaseCodeCallbackProtocolGuid,
                  (VOID *) &LoadfilePtr->Private->CallbackProtocolPtr
                  );

  if (Status == EFI_SUCCESS) {
    //
    // There is already a callback routine.  Do nothing.
    //
    DEBUG ((DEBUG_WARN, "\nLoadFile()  BC callback exists."));

  } else if (Status == EFI_UNSUPPORTED) {
    //
    // No BaseCode Callback protocol found.  Add our own.
    //
    Status = gBS->InstallProtocolInterface (
                    &LoadfilePtr->Private->Handle,
                    &gEfiPxeBaseCodeCallbackProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    &_bc_callback
                    );

    DEBUG ((DEBUG_WARN, "\nLoadFile()  Callback install status == %xh", Status));

    RemoveCallback = (BOOLEAN) (Status == EFI_SUCCESS);

    if (LoadfilePtr->Private->EfiBc.Mode != NULL && LoadfilePtr->Private->EfiBc.Mode->Started) {
      NewMakeCallback = TRUE;
      LoadfilePtr->Private->EfiBc.SetParameters (
                                    &LoadfilePtr->Private->EfiBc,
                                    NULL,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &NewMakeCallback
                                    );
    }

  } else {
    DEBUG ((DEBUG_WARN, "\nLoadFile()  Callback check status == %xh", Status));
  }
  //
  // Check for starting or for continuing after already getting
  // the file size.
  //
  if (LoadfilePtr->Private->FileSize == 0) {
    TmpBufSz  = 0;
    Status    = LoadfileStart (LoadfilePtr->Private, &TmpBufSz, Buffer);

    if (sizeof (UINTN) < sizeof (UINT64) && TmpBufSz > 0xFFFFFFFF) {
      *BufferSize = 0xFFFFFFFF;
    } else {
      *BufferSize = (UINTN) TmpBufSz;
    }

    if (Status == EFI_BUFFER_TOO_SMALL) {
      //
      // This is done so loadfile will work even if the boot manager
      // did not make the first call with Buffer == NULL.
      //
      Buffer = NULL;
    }
  } else if (Buffer == NULL) {
    DEBUG ((DEBUG_WARN, "\nLoadfile()  Get buffer size"));

    //
    // Continuing from previous LoadFile request.  Make sure there
    // is a buffer and that it is big enough.
    //
    *BufferSize = LoadfilePtr->Private->FileSize;
    Status      = EFI_BUFFER_TOO_SMALL;
  } else {
    DEBUG ((DEBUG_WARN, "\nLoadFile()  Download file"));

    //
    // Everything looks good, try to download the file.
    //
    TmpBufSz  = *BufferSize;
    Status    = DownloadFile (LoadfilePtr->Private, &TmpBufSz, Buffer);

    //
    // Next call to loadfile will start DHCP process again.
    //
    LoadfilePtr->Private->FileSize = 0;
  }
  //
  // If we added a callback protocol, now is the time to remove it.
  //
  if (RemoveCallback) {
    NewMakeCallback = FALSE;
    TempStatus = LoadfilePtr->Private->EfiBc.SetParameters (
                                          &LoadfilePtr->Private->EfiBc,
                                          NULL,
                                          NULL,
                                          NULL,
                                          NULL,
                                          &NewMakeCallback
                                          );

    if (TempStatus == EFI_SUCCESS) {
      gBS->UninstallProtocolInterface (
            LoadfilePtr->Private->Handle,
            &gEfiPxeBaseCodeCallbackProtocolGuid,
            &_bc_callback
            );
    }
  }
  //
  // Restore display mode and attribute
  //
  if (OrigMode != 0) {
    gST->ConOut->SetMode (gST->ConOut, OrigMode);
  }

  gST->ConOut->SetAttribute (gST->ConOut, OrigAttribute);

  //
  // Unlock interface
  //
  EfiReleaseLock (&LoadfilePtr->Lock);

  DEBUG ((DEBUG_WARN, "\nBC.Loadfile()  Status == %xh\n", Status));

  if (Status == EFI_SUCCESS) {
    return EFI_SUCCESS;

  } else if (Status == EFI_BUFFER_TOO_SMALL) {
    //
    // Error is only displayed when we are actually trying to
    // download the boot image.
    //
    if (Buffer == NULL) {
      return EFI_BUFFER_TOO_SMALL;
    }

    AsciiPrint ("\nPXE-E05: Download buffer is smaller than requested file.\n");

  } else if (Status == EFI_DEVICE_ERROR) {
    AsciiPrint ("\nPXE-E07: Network device error.  Check network connection.\n");

  } else if (Status == EFI_OUT_OF_RESOURCES) {
    AsciiPrint ("\nPXE-E09: Could not allocate I/O buffers.\n");

  } else if (Status == EFI_NO_MEDIA) {
    AsciiPrint ("\nPXE-E12: Could not detect network connection.  Check cable.\n");

  } else if (Status == EFI_NO_RESPONSE) {
    AsciiPrint ("\nPXE-E16: Valid PXE offer not received.\n");

  } else if (Status == EFI_TIMEOUT) {
    AsciiPrint ("\nPXE-E18: Timeout.  Server did not respond.\n");

  } else if (Status == EFI_ABORTED) {
    AsciiPrint ("\nPXE-E21: Remote boot cancelled.\n");

  } else if (Status == EFI_ICMP_ERROR) {
    AsciiPrint ("\nPXE-E22: Client received ICMP error from server.\n");

    if (LoadfilePtr->Private->EfiBc.Mode != NULL) {
      if (LoadfilePtr->Private->EfiBc.Mode->IcmpErrorReceived) {

      AsciiPrint (
          "PXE-E98: Type: %xh  Code: %xh  ",
          LoadfilePtr->Private->EfiBc.Mode->IcmpError.Type,
          LoadfilePtr->Private->EfiBc.Mode->IcmpError.Code
          );

        switch (LoadfilePtr->Private->EfiBc.Mode->IcmpError.Type) {
        case 0x03:
          switch (LoadfilePtr->Private->EfiBc.Mode->IcmpError.Code) {
          case 0x00:              /* net unreachable */
          AsciiPrint ("Net unreachable");
            break;

          case 0x01:              /* host unreachable */
          AsciiPrint ("Host unreachable");
            break;

          case 0x02:              /* protocol unreachable */
          AsciiPrint ("Protocol unreachable");
            break;

          case 0x03:              /* port unreachable */
          AsciiPrint ("Port unreachable");
            break;

          case 0x04:              /* Fragmentation needed */
          AsciiPrint ("Fragmentation needed");
            break;

          case 0x05:              /* Source route failed */
          AsciiPrint ("Source route failed");
            break;
          }

          break;
        }

      AsciiPrint ("\n");
      }
    }

  } else if (Status == EFI_TFTP_ERROR) {
    AsciiPrint ("\nPXE-E23: Client received TFTP error from server.\n");

    if (LoadfilePtr->Private->EfiBc.Mode != NULL) {
      if (LoadfilePtr->Private->EfiBc.Mode->TftpErrorReceived) {
      AsciiPrint (
          "PXE-E98: Code: %xh  %a\n",
          LoadfilePtr->Private->EfiBc.Mode->TftpError.ErrorCode,
          LoadfilePtr->Private->EfiBc.Mode->TftpError.ErrorString
          );
      }
    }

  } else {
    AsciiPrint ("\nPXE-E99: Unexpected network error: %xh\n", Status);
  }

  LoadfilePtr->Private->EfiBc.Stop (&LoadfilePtr->Private->EfiBc);

  return Status;
}
