/** @file
  This file contains code for UNDI command based on UEFI specification.

  Copyright (c) 2023, American Megatrends International LLC. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "DriverBinding.h"

// API table, defined in UEFI specification
API_FUNC  gUndiApiTable[] = {
  UndiGetState,
  UndiStart,
  UndiStop,
  UndiGetInitInfo,
  UndiGetConfigInfo,
  UndiInitialize,
  UndiReset,
  UndiShutdown,
  UndiInterruptEnable,
  UndiReceiveFilter,
  UndiStationAddress,
  UndiStatistics,
  UndiMcastIp2Mac,
  UndiNvData,
  UndiGetStatus,
  UndiFillHeader,
  UndiTransmit,
  UndiReceive
};

/**
   Callback function for enable Rate Limiter.

   @param[in] Event           Event whose notification function is being invoked
   @param[in] Context         Pointer to the notification function's context

**/
VOID
EFIAPI
UndiRateLimiterCallback (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  NIC_DATA  *Nic;

  Nic = Context;

  if (Nic->RateLimitingCreditCount < Nic->RateLimitingCredit) {
    Nic->RateLimitingCreditCount++;
  }
}

/**
  This command is used to determine the operational state of the UNDI.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiGetState (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_GET_STATE) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  Cdb->StatFlags = Cdb->StatFlags | Nic->State;

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiGetState != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiGetState (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to change the UNDI operational state from stopped to started.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiStart (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_CPB_START_31  *Cpb;
  EFI_STATUS        Status;
  BOOLEAN           EventError;

  if ((Cdb->OpCode != PXE_OPCODE_START) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_START_31)) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_STOPPED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_ALREADY_STARTED;
    return;
  }

  Cpb = (PXE_CPB_START_31 *)(UINTN)Cdb->CPBaddr;

  Nic->PxeStart.Delay     = Cpb->Delay;
  Nic->PxeStart.Virt2Phys = Cpb->Virt2Phys;
  Nic->PxeStart.Block     = Cpb->Block;
  Nic->PxeStart.Map_Mem   = 0;
  Nic->PxeStart.UnMap_Mem = 0;
  Nic->PxeStart.Sync_Mem  = Cpb->Sync_Mem;
  Nic->PxeStart.Unique_ID = Cpb->Unique_ID;
  EventError              = FALSE;
  Status                  = EFI_SUCCESS;
  if (Nic->RateLimitingEnable == TRUE) {
    Status = gBS->CreateEvent (
                    EVT_TIMER | EVT_NOTIFY_SIGNAL,
                    TPL_NOTIFY,
                    UndiRateLimiterCallback,
                    Nic,
                    &Nic->RateLimiter
                    );
    if (!EFI_ERROR (Status)) {
      Status = gBS->SetTimer (
                      Nic->RateLimiter,
                      TimerPeriodic,
                      Nic->RateLimitingPollTimer * 10000
                      );
      if (EFI_ERROR (Status)) {
        EventError = TRUE;
      }
    }
  }

  if ((Nic->UsbEth->UsbEthUndi.UsbEthUndiStart != NULL) && (EventError == FALSE)) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiStart (Cdb, Nic);
  }

  if (!EFI_ERROR (Status)) {
    // Initial the state for UNDI start.
    Nic->State     = PXE_STATFLAGS_GET_STATE_STARTED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  } else {
    if (Nic->RateLimitingEnable == TRUE) {
      if (!EventError) {
        gBS->SetTimer (&Nic->RateLimiter, TimerCancel, 0);
      }

      if (Nic->RateLimiter) {
        gBS->CloseEvent (&Nic->RateLimiter);
        Nic->RateLimiter = 0;
      }
    }

    // Initial the state when UNDI start is fail
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_DEVICE_FAILURE;
  }
}

/**
  This command is used to change the UNDI operational state from started to stopped.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiStop (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_STOP) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_STOPPED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_STARTED;
    return;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_SHUTDOWN;
    return;
  }

  Nic->PxeStart.Delay     = 0;
  Nic->PxeStart.Virt2Phys = 0;
  Nic->PxeStart.Block     = 0;
  Nic->PxeStart.Map_Mem   = 0;
  Nic->PxeStart.UnMap_Mem = 0;
  Nic->PxeStart.Sync_Mem  = 0;
  Nic->State              = PXE_STATFLAGS_GET_STATE_STOPPED;

  if (Nic->RateLimitingEnable == TRUE) {
    gBS->SetTimer (&Nic->RateLimiter, TimerCancel, 0);
    gBS->CloseEvent (&Nic->RateLimiter);
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiStop != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiStop (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to retrieve initialization information that is
  needed by drivers and applications to initialized UNDI.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiGetInitInfo (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_DB_GET_INIT_INFO  *Db;
  EFI_STATUS            Status;

  if ((Cdb->OpCode != PXE_OPCODE_GET_INIT_INFO) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != sizeof (PXE_DB_GET_INIT_INFO)) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_STOPPED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_STARTED;
    return;
  }

  Db = (PXE_DB_GET_INIT_INFO *)(UINTN)Cdb->DBaddr;

  Db->MemoryRequired         = MEMORY_REQUIRE;
  Db->FrameDataLen           = PXE_MAX_TXRX_UNIT_ETHER;
  Db->LinkSpeeds[0]          = 10;
  Db->LinkSpeeds[1]          = 100;
  Db->LinkSpeeds[2]          = 1000;
  Db->LinkSpeeds[3]          = 0;
  Db->MediaHeaderLen         = PXE_MAC_HEADER_LEN_ETHER;
  Db->HWaddrLen              = PXE_HWADDR_LEN_ETHER;
  Db->MCastFilterCnt         = MAX_MCAST_ADDRESS_CNT;
  Db->TxBufCnt               = Nic->PxeInit.TxBufCnt;
  Db->TxBufSize              = Nic->PxeInit.TxBufSize;
  Db->RxBufCnt               = Nic->PxeInit.RxBufCnt;
  Db->RxBufSize              = Nic->PxeInit.RxBufSize;
  Db->IFtype                 = PXE_IFTYPE_ETHERNET;
  Db->SupportedDuplexModes   = PXE_DUPLEX_DEFAULT;
  Db->SupportedLoopBackModes = LOOPBACK_NORMAL;

  Cdb->StatFlags |= (PXE_STATFLAGS_CABLE_DETECT_SUPPORTED |
                     PXE_STATFLAGS_GET_STATUS_NO_MEDIA_SUPPORTED);

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiGetInitInfo != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiGetInitInfo (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to retrieve configuration information about
  the NIC being controlled by the UNDI.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiGetConfigInfo (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_DB_GET_CONFIG_INFO  *Db;
  EFI_STATUS              Status;

  if ((Cdb->OpCode != PXE_OPCODE_GET_CONFIG_INFO) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != sizeof (PXE_DB_GET_CONFIG_INFO)) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_STOPPED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_STARTED;
    return;
  }

  Db = (PXE_DB_GET_CONFIG_INFO *)(UINTN)Cdb->DBaddr;

  Db->pci.BusType = PXE_BUSTYPE_USB;

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiGetConfigInfo != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiGetConfigInfo (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command resets the network adapter and initializes UNDI using
  the parameters supplied in the CPB.

  @param[in]      Cdb  A pointer to the command descriptor block.
  @param[in, out] Nic  A pointer to the Network interface controller data.

**/
VOID
UndiInitialize (
  IN      PXE_CDB   *Cdb,
  IN OUT  NIC_DATA  *Nic
  )
{
  PXE_CPB_INITIALIZE  *Cpb;
  PXE_DB_INITIALIZE   *Db;
  EFI_STATUS          Status;

  if ((Cdb->OpCode != PXE_OPCODE_INITIALIZE) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_INITIALIZE)))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_STOPPED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_STARTED;
    return;
  }

  if ((Cdb->OpFlags != PXE_OPFLAGS_INITIALIZE_DETECT_CABLE) &&
      (Cdb->OpFlags != PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  if (Nic->State == PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_ALREADY_INITIALIZED;
    return;
  }

  Cpb = (PXE_CPB_INITIALIZE *)(UINTN)Cdb->CPBaddr;
  Db  = (PXE_DB_INITIALIZE *)(UINTN)Cdb->DBaddr;

  Nic->PxeInit.LinkSpeed    = Cpb->LinkSpeed;
  Nic->PxeInit.DuplexMode   = Cpb->DuplexMode;
  Nic->PxeInit.LoopBackMode = Cpb->LoopBackMode;
  Nic->PxeInit.MemoryAddr   = Cpb->MemoryAddr;
  Nic->PxeInit.MemoryLength = Cpb->MemoryLength;
  Nic->PxeInit.TxBufCnt     = TX_BUFFER_COUNT;
  Nic->PxeInit.TxBufSize    = Nic->MaxSegmentSize;
  Nic->PxeInit.RxBufCnt     = RX_BUFFER_COUNT;
  Nic->PxeInit.RxBufSize    = Nic->MaxSegmentSize;

  Cdb->StatCode = Initialize (Cdb, Nic);

  Db->MemoryUsed = MEMORY_REQUIRE;
  Db->TxBufCnt   = Nic->PxeInit.TxBufCnt;
  Db->TxBufSize  = Nic->PxeInit.TxBufSize;
  Db->RxBufCnt   = Nic->PxeInit.RxBufCnt;
  Db->RxBufSize  = Nic->PxeInit.RxBufSize;

  Nic->RxFilter    = PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST;
  Nic->CanTransmit = FALSE;

  if (Cdb->OpFlags == PXE_OPFLAGS_INITIALIZE_DETECT_CABLE) {
    if ((Nic->Request.Request == USB_CDC_NETWORK_CONNECTION) && (Nic->Request.Value == NETWORK_DISCONNECT)) {
      Nic->CableDetect = 0;
    } else if ((Nic->Request.Request == USB_CDC_NETWORK_CONNECTION) && (Nic->Request.Value == NETWORK_CONNECTED)) {
      Nic->CableDetect = 1;
    }

    if (Nic->CableDetect == 0) {
      Cdb->StatFlags |= PXE_STATFLAGS_INITIALIZED_NO_MEDIA;
    }
  }

  if (Cdb->StatCode != PXE_STATCODE_SUCCESS) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
  } else {
    Nic->State = PXE_STATFLAGS_GET_STATE_INITIALIZED;
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiInitialize != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiInitialize (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  Initialize Network interface controller data.

  @param[in]      Cdb     A pointer to the command descriptor block.
  @param[in, out] Nic     A pointer to the Network interface controller data.

  @retval Status  A value of Pxe statcode.

**/
UINT16
Initialize (
  IN      PXE_CDB   *Cdb,
  IN OUT  NIC_DATA  *Nic
  )
{
  UINTN       Status;
  UINT32      Index;
  EFI_STATUS  EfiStatus;

  Status = MapIt (
             Nic,
             Nic->PxeInit.MemoryAddr,
             Nic->PxeInit.MemoryLength,
             TO_AND_FROM_DEVICE,
             (UINT64)(UINTN)&Nic->MappedAddr
             );

  if (Status != 0) {
    return (UINT16)Status;
  }

  for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
    Nic->PermNodeAddress[Index] = Nic->MacAddr.Addr[Index];
  }

  for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
    Nic->CurrentNodeAddress[Index] = Nic->PermNodeAddress[Index];
  }

  for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
    Nic->BroadcastNodeAddress[Index] = 0xFF;
  }

  for (Index = PXE_HWADDR_LEN_ETHER; Index < PXE_MAC_LENGTH; Index++) {
    Nic->CurrentNodeAddress[Index]   = 0;
    Nic->PermNodeAddress[Index]      = 0;
    Nic->BroadcastNodeAddress[Index] = 0;
  }

  if (Nic->UsbEth->UsbEthInitialize != NULL) {
    EfiStatus = Nic->UsbEth->UsbEthInitialize (Cdb, Nic);
    if (EFI_ERROR (EfiStatus)) {
      return PXE_STATFLAGS_COMMAND_FAILED;
    }
  }

  return (UINT16)Status;
}

/**
  This command resets the network adapter and reinitializes the UNDI
  with the same parameters provided in the Initialize command.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiReset (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_RESET) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  if ((Cdb->OpFlags != PXE_OPFLAGS_NOT_USED) &&
      (Cdb->OpFlags != PXE_OPFLAGS_RESET_DISABLE_INTERRUPTS) &&
      (Cdb->OpFlags != PXE_OPFLAGS_RESET_DISABLE_FILTERS))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_RESET_DISABLE_FILTERS) == 0) {
    Nic->RxFilter = PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST;
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_RESET_DISABLE_INTERRUPTS) != 0) {
    Nic->InterrupOpFlag = 0;
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiReset != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiReset (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  The Shutdown command resets the network adapter and leaves it in a
  safe state for another driver to initialize.

  @param[in]      Cdb  A pointer to the command descriptor block.
  @param[in, out] Nic  A pointer to the Network interface controller data.

**/
VOID
UndiShutdown (
  IN      PXE_CDB   *Cdb,
  IN OUT  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_SHUTDOWN) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  Nic->CanTransmit = FALSE;

  Nic->State = PXE_STATFLAGS_GET_STATE_STARTED;

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiShutdown != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiShutdown (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  The Interrupt Enables command can be used to read and/or change
  the current external interrupt enable settings.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiInterruptEnable (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  Cdb->StatCode = PXE_STATCODE_UNSUPPORTED;
  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiInterruptEnable != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiInterruptEnable (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    } else {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
      Cdb->StatCode  = PXE_STATCODE_SUCCESS;
    }
  }
}

/**
  This command is used to read and change receive filters and,
  if supported, read and change the multicast MAC address filter list.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiReceiveFilter (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  UINT16                  NewFilter;
  PXE_DB_RECEIVE_FILTERS  *Db;
  EFI_STATUS              Status;

  if ((Cdb->OpCode != PXE_OPCODE_RECEIVE_FILTERS) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  NewFilter = (UINT16)(Cdb->OpFlags & 0x1F);

  switch (Cdb->OpFlags & PXE_OPFLAGS_RECEIVE_FILTER_OPMASK) {
    case PXE_OPFLAGS_RECEIVE_FILTER_READ:
      if (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) {
        Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
        Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
      }

      if ((NewFilter & PXE_OPFLAGS_RECEIVE_FILTER_RESET_MCAST_LIST) == 0) {
        if ((Cdb->DBsize != 0)) {
          Db = (PXE_DB_RECEIVE_FILTERS *)(UINTN)Cdb->DBaddr;
          CopyMem (Db, &Nic->McastList, Nic->McastCount);
        }
      }

      break;

    case PXE_OPFLAGS_RECEIVE_FILTER_ENABLE:
      if (NewFilter == 0) {
        Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
      }

      if (Cdb->CPBsize != 0) {
        if (((NewFilter & PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST) == 0) ||
            ((NewFilter & PXE_OPFLAGS_RECEIVE_FILTER_RESET_MCAST_LIST) != 0) ||
            ((NewFilter & PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST) != 0) ||
            ((Cdb->CPBsize % sizeof (PXE_MAC_ADDR)) != 0))
        {
          Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
        }
      }

      if ((Cdb->OpFlags & PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST) != 0) {
        if (((Cdb->OpFlags & PXE_OPFLAGS_RECEIVE_FILTER_RESET_MCAST_LIST) != 0) ||
            ((Cdb->OpFlags & PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST) != 0))
        {
          Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
        }

        if (Cdb->CPBsize == 0) {
          Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
        }
      }

      Cdb->StatCode = SetFilter (Nic, NewFilter, Cdb->CPBaddr, Cdb->CPBsize);
      break;

    case PXE_OPFLAGS_RECEIVE_FILTER_DISABLE:
      if (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) {
        Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
        Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
      }

      break;

    default:
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
      Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
  }

  Cdb->StatFlags = (PXE_STATFLAGS)(Cdb->StatFlags | Nic->RxFilter);

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiReceiveFilter != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiReceiveFilter (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  Set PXE receive filter.

  @param[in]  Nic         A pointer to the Network interface controller data.
  @param[in]  SetFilter   PXE receive filter
  @param[in]  CpbAddr     Command Parameter Block Address
  @param[in]  CpbSize     Command Parameter Block Size

**/
UINT16
SetFilter (
  IN  NIC_DATA  *Nic,
  IN  UINT16    SetFilter,
  IN  UINT64    CpbAddr,
  IN  UINT32    CpbSize
  )
{
  EFI_STATUS                   Status;
  UINT8                        *McastList;
  UINT8                        Count;
  UINT8                        Index1;
  UINT8                        Index2;
  PXE_CPB_RECEIVE_FILTERS      *Cpb;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthFunDescriptor;

  Count = 0;
  Cpb   = (PXE_CPB_RECEIVE_FILTERS *)(UINTN)CpbAddr;

  // The Cpb could be NULL.(ref:PXE_CPBADDR_NOT_USED)
  Nic->RxFilter = (UINT8)SetFilter;

  if (((SetFilter & PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST) != 0) || (Cpb != NULL)) {
    if (Cpb != NULL) {
      Nic->McastCount = (UINT8)(CpbSize / PXE_MAC_LENGTH);
      CopyMem (&Nic->McastList, Cpb, Nic->McastCount);
    }

    Nic->UsbEth->UsbEthFunDescriptor (Nic->UsbEth, &UsbEthFunDescriptor);
    if ((UsbEthFunDescriptor.NumberMcFilters & MAC_FILTERS_MASK) == 0) {
      Nic->RxFilter |= PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST;
      Nic->UsbEth->SetUsbEthPacketFilter (Nic->UsbEth, Nic->RxFilter);
    } else {
      Status = gBS->AllocatePool (EfiBootServicesData, Nic->McastCount * 6, (VOID **)&McastList);
      if (EFI_ERROR (Status)) {
        return PXE_STATCODE_INVALID_PARAMETER;
      }

      if (Cpb != NULL) {
        for (Index1 = 0; Index1 < Nic->McastCount; Index1++) {
          for (Index2 = 0; Index2 < 6; Index2++) {
            McastList[Count++] = Cpb->MCastList[Index1][Index2];
          }
        }
      }

      Nic->RxFilter |= PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST;
      if (Cpb != NULL) {
        Nic->UsbEth->SetUsbEthMcastFilter (Nic->UsbEth, Nic->McastCount, McastList);
      }

      Nic->UsbEth->SetUsbEthPacketFilter (Nic->UsbEth, Nic->RxFilter);
      FreePool (McastList);
    }
  }

  return PXE_STATCODE_SUCCESS;
}

/**
  This command is used to get current station and broadcast MAC addresses
  and, if supported, to change the current station MAC address.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiStationAddress (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_CPB_STATION_ADDRESS  *Cpb;
  PXE_DB_STATION_ADDRESS   *Db;
  UINT16                   Index;
  EFI_STATUS               Status;

  if ((Cdb->OpCode != PXE_OPCODE_STATION_ADDRESS) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->DBsize != sizeof (PXE_DB_STATION_ADDRESS)))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  if (Cdb->OpFlags == PXE_OPFLAGS_STATION_ADDRESS_RESET) {
    if (CompareMem (&Nic->CurrentNodeAddress[0], &Nic->PermNodeAddress[0], PXE_MAC_LENGTH) != 0) {
      for (Index = 0; Index < PXE_MAC_LENGTH; Index++) {
        Nic->CurrentNodeAddress[Index] = Nic->PermNodeAddress[Index];
      }
    }
  }

  if (Cdb->CPBaddr != 0) {
    Cpb = (PXE_CPB_STATION_ADDRESS *)(UINTN)Cdb->CPBaddr;
    for (Index = 0; Index < PXE_MAC_LENGTH; Index++) {
      Nic->CurrentNodeAddress[Index] = Cpb->StationAddr[Index];
    }
  }

  if (Cdb->DBaddr != 0) {
    Db = (PXE_DB_STATION_ADDRESS *)(UINTN)Cdb->DBaddr;
    for (Index = 0; Index < PXE_MAC_LENGTH; Index++) {
      Db->StationAddr[Index]   = Nic->CurrentNodeAddress[Index];
      Db->BroadcastAddr[Index] = Nic->BroadcastNodeAddress[Index];
      Db->PermanentAddr[Index] = Nic->PermNodeAddress[Index];
    }
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiStationAddress != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiStationAddress (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to read and clear the NIC traffic statistics.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiStatistics (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_STATISTICS) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  if ((Cdb->OpFlags != PXE_OPFLAGS_STATISTICS_RESET) &&
      (Cdb->OpFlags != PXE_OPFLAGS_STATISTICS_READ))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  Cdb->StatCode = Statistics (Nic, Cdb->DBaddr, Cdb->DBsize);

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiStatistics != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiStatistics (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  Return data for DB data.

  @param[in]  Nic      A pointer to the Network interface controller data.
  @param[in]  DbAddr   Data Block Address.
  @param[in]  DbSize   Data Block Size.

**/
UINT16
Statistics (
  IN NIC_DATA  *Nic,
  IN UINT64    DbAddr,
  IN UINT16    DbSize
  )
{
  PXE_DB_STATISTICS  *DbStatistic;
  EFI_STATUS         Status;

  DbStatistic = (PXE_DB_STATISTICS *)(UINTN)DbAddr;

  if (DbSize == 0) {
    return PXE_STATCODE_SUCCESS;
  }

  DbStatistic->Supported  = 0x802;
  DbStatistic->Data[0x01] = Nic->RxFrame;
  DbStatistic->Data[0x0B] = Nic->TxFrame;

  if (Nic->UsbEth->UsbEthStatistics != NULL) {
    Status = Nic->UsbEth->UsbEthStatistics (Nic, DbAddr, DbSize);
    if (EFI_ERROR (Status)) {
      return PXE_STATFLAGS_COMMAND_FAILED;
    }
  }

  return PXE_STATCODE_SUCCESS;
}

/**
  Translate a multicast IPv4 or IPv6 address to a multicast MAC address.

  @param[in, out] Cdb  A pointer to the command descriptor block.
  @param[in]      Nic  A pointer to the Network interface controller data.

**/
VOID
UndiMcastIp2Mac (
  IN OUT  PXE_CDB   *Cdb,
  IN      NIC_DATA  *Nic
  )
{
  PXE_CPB_MCAST_IP_TO_MAC  *Cpb;
  PXE_DB_MCAST_IP_TO_MAC   *Db;
  UINT8                    *Tmp;
  EFI_STATUS               Status;

  if ((Cdb->OpCode != PXE_OPCODE_MCAST_IP_TO_MAC) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_MCAST_IP_TO_MAC)) ||
      (Cdb->DBsize != sizeof (PXE_DB_MCAST_IP_TO_MAC)))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  Cpb = (PXE_CPB_MCAST_IP_TO_MAC *)(UINTN)Cdb->CPBaddr;
  Db  = (PXE_DB_MCAST_IP_TO_MAC *)(UINTN)Cdb->DBaddr;

  if ((Cdb->OpFlags & PXE_OPFLAGS_MCAST_IPV6_TO_MAC) != 0) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_UNSUPPORTED;
    return;
  }

  Tmp = (UINT8 *)(&Cpb->IP.IPv4);

  if ((Tmp[0] & 0xF0) != 0xE0) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CPB;
  }

  Db->MAC[0] = 0x01;
  Db->MAC[1] = 0x00;
  Db->MAC[2] = 0x5E;
  Db->MAC[3] = Tmp[1] & 0x7F;
  Db->MAC[4] = Tmp[2];
  Db->MAC[5] = Tmp[3];

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiMcastIp2Mac != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiMcastIp2Mac (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to read and write (if supported by NIC H/W)
  nonvolatile storage on the NIC.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiNvData (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  Cdb->StatCode = PXE_STATCODE_UNSUPPORTED;
  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiNvData != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiNvData (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    } else {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
      Cdb->StatCode  = PXE_STATCODE_SUCCESS;
    }
  }
}

/**
  This command returns the current interrupt status and/or the
  transmitted buffer addresses and the current media status.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiGetStatus (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_DB_GET_STATUS  *Db;
  PXE_DB_GET_STATUS  TmpGetStatus;
  UINT16             NumEntries;
  UINTN              Index;
  EFI_STATUS         Status;

  if ((Cdb->OpCode != PXE_OPCODE_GET_STATUS) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != PXE_CPBSIZE_NOT_USED) ||
      (Cdb->CPBaddr != PXE_CPBADDR_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  TmpGetStatus.RxFrameLen = 0;
  TmpGetStatus.reserved   = 0;
  Db                      = (PXE_DB_GET_STATUS *)(UINTN)Cdb->DBaddr;

  if ((Cdb->DBsize > 0) && (Cdb->DBsize < sizeof (UINT32) * 2)) {
    CopyMem (Db, &TmpGetStatus, Cdb->DBsize);
  } else {
    CopyMem (Db, &TmpGetStatus, sizeof (UINT32) * 2);
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_GET_TRANSMITTED_BUFFERS) != 0) {
    if (Cdb->DBsize == 0) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
      Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
      return;
    }

    NumEntries  = Cdb->DBsize - sizeof (UINT64);
    Cdb->DBsize = sizeof (UINT32) * 2;

    for (Index = 0; NumEntries >= sizeof (UINT64); Index++, NumEntries -= sizeof (UINT64)) {
      if (Nic->TxBufferCount > 0) {
        Nic->TxBufferCount--;
        Db->TxBuffer[Index] = Nic->MediaHeader[Nic->TxBufferCount];
      }
    }
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_GET_INTERRUPT_STATUS) != 0) {
    if (Nic->ReceiveStatus != 0) {
      Cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_RECEIVE;
    }
  }

  if ((Nic->Request.Request == USB_CDC_NETWORK_CONNECTION) && (Nic->Request.Value == NETWORK_DISCONNECT)) {
    Nic->CableDetect = 0;
  } else if ((Nic->Request.Request == USB_CDC_NETWORK_CONNECTION) && (Nic->Request.Value == NETWORK_CONNECTED)) {
    Nic->CableDetect = 1;
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_GET_MEDIA_STATUS) != 0) {
    if (Nic->CableDetect == 0) {
      Cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_NO_MEDIA;
    }
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiGetStatus != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiGetStatus (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  This command is used to fill the media header(s) in transmit packet(s).

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiFillHeader (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  PXE_CPB_FILL_HEADER             *CpbFillHeader;
  PXE_CPB_FILL_HEADER_FRAGMENTED  *CpbFill;
  ETHERNET_HEADER                 *MacHeader;
  UINTN                           Index;
  EFI_STATUS                      Status;

  if ((Cdb->OpCode != PXE_OPCODE_FILL_HEADER) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_FILL_HEADER_FRAGMENTED)) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    return;
  }

  if (Cdb->CPBsize == PXE_CPBSIZE_NOT_USED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  if ((Cdb->OpFlags & PXE_OPFLAGS_FILL_HEADER_FRAGMENTED) != 0) {
    CpbFill = (PXE_CPB_FILL_HEADER_FRAGMENTED *)(UINTN)Cdb->CPBaddr;

    if ((CpbFill->FragCnt == 0) || (CpbFill->FragDesc[0].FragLen < PXE_MAC_HEADER_LEN_ETHER)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
      Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
      return;
    }

    MacHeader           = (ETHERNET_HEADER *)(UINTN)CpbFill->FragDesc[0].FragAddr;
    MacHeader->Protocol = CpbFill->Protocol;

    for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
      MacHeader->DestAddr[Index] = CpbFill->DestAddr[Index];
      MacHeader->SrcAddr[Index]  = CpbFill->SrcAddr[Index];
    }
  } else {
    CpbFillHeader = (PXE_CPB_FILL_HEADER *)(UINTN)Cdb->CPBaddr;

    MacHeader           = (ETHERNET_HEADER *)(UINTN)CpbFillHeader->MediaHeader;
    MacHeader->Protocol = CpbFillHeader->Protocol;

    for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
      MacHeader->DestAddr[Index] = CpbFillHeader->DestAddr[Index];
      MacHeader->SrcAddr[Index]  = CpbFillHeader->SrcAddr[Index];
    }
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiFillHeader != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiFillHeader (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }
  }
}

/**
  The Transmit command is used to place a packet into the transmit queue.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiTransmit (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_TRANSMIT) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_TRANSMIT)) ||
      (Cdb->DBsize != PXE_DBSIZE_NOT_USED) ||
      (Cdb->DBaddr != PXE_DBADDR_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    return;
  }

  if (Cdb->CPBsize == PXE_CPBSIZE_NOT_USED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiTransmit != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiTransmit (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }

    return;
  }

  Cdb->StatCode = Transmit (Cdb, Nic, Cdb->CPBaddr, Cdb->OpFlags);

  if (Cdb->StatCode != PXE_STATCODE_SUCCESS) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
  }
}

/**
  Use USB Ethernet Protocol Bulk out command to transmit data.

  @param[in]      Cdb      A pointer to the command descriptor block.
  @param[in, out] Nic      A pointer to the Network interface controller data.
  @param[in]      CpbAddr  Command Parameter Block Address.
  @param[in]      OpFlags  Operation Flags.

**/
UINT16
Transmit (
  IN      PXE_CDB   *Cdb,
  IN OUT  NIC_DATA  *Nic,
  IN      UINT64    CpbAddr,
  IN      UINT16    OpFlags
  )
{
  EFI_STATUS        Status;
  PXE_CPB_TRANSMIT  *Cpb;
  UINT64            BulkOutData;
  UINTN             DataLength;
  UINTN             TransmitLength;
  UINTN             Map;
  UINT32            Counter;
  UINT16            StatCode;

  BulkOutData = 0;
  Counter     = 0;
  Cpb         = (PXE_CPB_TRANSMIT *)(UINTN)CpbAddr;

  if (Nic->CanTransmit) {
    return PXE_STATCODE_BUSY;
  }

  Nic->CanTransmit = TRUE;

  if ((OpFlags & PXE_OPFLAGS_TRANSMIT_FRAGMENTED) != 0) {
    return PXE_STATCODE_INVALID_PARAMETER;
  }

  Map = MapIt (
          Nic,
          Cpb->FrameAddr,
          Cpb->DataLen + (UINT32)Cpb->MediaheaderLen,
          TO_DEVICE,
          (UINT64)(UINTN)&BulkOutData
          );

  if (Map != 0) {
    Nic->CanTransmit = FALSE;
    return PXE_STATCODE_INVALID_PARAMETER;
  }

  if (Nic->TxBufferCount < MAX_XMIT_BUFFERS) {
    Nic->MediaHeader[Nic->TxBufferCount] = Cpb->FrameAddr;
    Nic->TxBufferCount++;
  }

  DataLength = (UINTN)(Cpb->DataLen + (UINT32)Cpb->MediaheaderLen);

  while (1) {
    if (Counter >= 3) {
      StatCode = PXE_STATCODE_BUSY;
      break;
    }

    TransmitLength = DataLength;

    Status = Nic->UsbEth->UsbEthTransmit (Cdb, Nic->UsbEth, (VOID *)(UINTN)BulkOutData, &TransmitLength);
    if (EFI_ERROR (Status)) {
      StatCode =  PXE_STATFLAGS_COMMAND_FAILED;
    }

    if (Status == EFI_INVALID_PARAMETER) {
      StatCode = PXE_STATCODE_INVALID_PARAMETER;
      break;
    }

    if (Status == EFI_DEVICE_ERROR) {
      StatCode = PXE_STATCODE_DEVICE_FAILURE;
      break;
    }

    if (!EFI_ERROR (Status)) {
      Nic->TxFrame++;
      StatCode = PXE_STATCODE_SUCCESS;
      break;
    }

    Counter++;
  }

  UnMapIt (
    Nic,
    Cpb->FrameAddr,
    Cpb->DataLen + (UINT32)Cpb->MediaheaderLen,
    TO_DEVICE,
    BulkOutData
    );

  Nic->CanTransmit = FALSE;

  return StatCode;
}

/**
  When the network adapter has received a frame, this command is used
  to copy the frame into driver/application storage.

  @param[in]  Cdb  A pointer to the command descriptor block.
  @param[in]  Nic  A pointer to the Network interface controller data.

**/
VOID
UndiReceive (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  if ((Cdb->OpCode != PXE_OPCODE_RECEIVE) ||
      (Cdb->StatCode != PXE_STATCODE_INITIALIZE) ||
      (Cdb->StatFlags != PXE_STATFLAGS_INITIALIZE) ||
      (Cdb->IFnum >= (gPxe->IFcnt | gPxe->IFcntExt << 8)) ||
      (Cdb->CPBsize != sizeof (PXE_CPB_RECEIVE)) ||
      (Cdb->DBsize != sizeof (PXE_DB_RECEIVE)) ||
      (Cdb->OpFlags != PXE_OPFLAGS_NOT_USED))
  {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_INVALID_CDB;
    return;
  } else {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE;
    Cdb->StatCode  = PXE_STATCODE_SUCCESS;
  }

  if (Nic->State != PXE_STATFLAGS_GET_STATE_INITIALIZED) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    Cdb->StatCode  = PXE_STATCODE_NOT_INITIALIZED;
    return;
  }

  if (Nic->UsbEth->UsbEthUndi.UsbEthUndiReceive != NULL) {
    Status = Nic->UsbEth->UsbEthUndi.UsbEthUndiReceive (Cdb, Nic);
    if (EFI_ERROR (Status)) {
      Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
    }

    return;
  }

  Cdb->StatCode = Receive (Cdb, Nic, Cdb->CPBaddr, Cdb->DBaddr);

  if (Cdb->StatCode != PXE_STATCODE_SUCCESS) {
    Cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED;
  }
}

/**
  Use USB Ethernet Protocol Bulk in command to receive data.

  @param[in]      Cdb      A pointer to the command descriptor block.
  @param[in, out] Nic      A pointer to the Network interface controller data.
  @param[in]      CpbAddr  Command Parameter Block Address.
  @param[in, out] DbAddr   Data Block Address.

**/
UINT16
Receive (
  IN PXE_CDB       *Cdb,
  IN OUT NIC_DATA  *Nic,
  IN UINT64        CpbAddr,
  IN OUT UINT64    DbAddr
  )
{
  EFI_STATUS       Status;
  UINTN            Index;
  PXE_FRAME_TYPE   FrameType;
  PXE_CPB_RECEIVE  *Cpb;
  PXE_DB_RECEIVE   *Db;
  NIC_DEVICE       *NicDevice;
  UINT8            *BulkInData;
  UINTN            DataLength;
  ETHERNET_HEADER  *Header;
  EFI_TPL          OriginalTpl;

  FrameType  = PXE_FRAME_TYPE_NONE;
  NicDevice  = UNDI_DEV_FROM_NIC (Nic);
  BulkInData = NicDevice->ReceiveBuffer;
  DataLength = (UINTN)Nic->MaxSegmentSize;
  Cpb        = (PXE_CPB_RECEIVE *)(UINTN)CpbAddr;
  Db         = (PXE_DB_RECEIVE *)(UINTN)DbAddr;

  if (!BulkInData) {
    return PXE_STATCODE_INVALID_PARAMETER;
  }

  if ((Nic->RateLimitingCreditCount == 0) && (Nic->RateLimitingEnable == TRUE)) {
    return PXE_STATCODE_NO_DATA;
  }

  Status = Nic->UsbEth->UsbEthReceive (Cdb, Nic->UsbEth, (VOID *)BulkInData, &DataLength);
  if (EFI_ERROR (Status)) {
    Nic->ReceiveStatus = 0;
    if (Nic->RateLimitingEnable == TRUE) {
      OriginalTpl = gBS->RaiseTPL (TPL_NOTIFY);
      if (Nic->RateLimitingCreditCount != 0) {
        Nic->RateLimitingCreditCount--;
      }

      gBS->RestoreTPL (OriginalTpl);
    }

    return PXE_STATCODE_NO_DATA;
  }

  Nic->RxFrame++;

  if (DataLength != 0) {
    if (DataLength > Cpb->BufferLen) {
      DataLength = (UINTN)Cpb->BufferLen;
    }

    CopyMem ((UINT8 *)(UINTN)Cpb->BufferAddr, (UINT8 *)BulkInData, DataLength);

    Header = (ETHERNET_HEADER *)BulkInData;

    Db->FrameLen       = (UINT32)DataLength;
    Db->MediaHeaderLen = PXE_MAC_HEADER_LEN_ETHER;

    for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
      if (Header->DestAddr[Index] != Nic->CurrentNodeAddress[Index]) {
        break;
      }
    }

    if (Index >= PXE_HWADDR_LEN_ETHER) {
      FrameType = PXE_FRAME_TYPE_UNICAST;
    } else {
      for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
        if (Header->DestAddr[Index] != Nic->BroadcastNodeAddress[Index]) {
          break;
        }
      }

      if (Index >= PXE_HWADDR_LEN_ETHER) {
        FrameType = PXE_FRAME_TYPE_BROADCAST;
      } else {
        if ((Header->DestAddr[0] & 1) == 1) {
          FrameType = PXE_FRAME_TYPE_FILTERED_MULTICAST;
        } else {
          FrameType = PXE_FRAME_TYPE_PROMISCUOUS;
        }
      }
    }

    Db->Type     = FrameType;
    Db->Protocol = Header->Protocol;

    for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
      Db->SrcAddr[Index]  = Header->SrcAddr[Index];
      Db->DestAddr[Index] = Header->DestAddr[Index];
    }
  }

  if (FrameType == PXE_FRAME_TYPE_NONE) {
    Nic->ReceiveStatus = 0;
  } else {
    Nic->ReceiveStatus = 1;
  }

  return PXE_STATCODE_SUCCESS;
}

/**
  Fill out PXE SW UNDI structure.

  @param[out]  PxeSw      A pointer to the PXE SW UNDI structure.

**/
VOID
PxeStructInit (
  OUT PXE_SW_UNDI  *PxeSw
  )
{
  PxeSw->Signature = PXE_ROMID_SIGNATURE;
  PxeSw->Len       = (UINT8)sizeof (PXE_SW_UNDI);
  PxeSw->Fudge     = 0;
  PxeSw->IFcnt     = 0;
  PxeSw->IFcntExt  = 0;
  PxeSw->Rev       = PXE_ROMID_REV;
  PxeSw->MajorVer  = PXE_ROMID_MAJORVER;
  PxeSw->MinorVer  = PXE_ROMID_MINORVER;
  PxeSw->reserved1 = 0;

  PxeSw->Implementation = PXE_ROMID_IMP_SW_VIRT_ADDR |
                          PXE_ROMID_IMP_FRAG_SUPPORTED |
                          PXE_ROMID_IMP_CMD_LINK_SUPPORTED |
                          PXE_ROMID_IMP_STATION_ADDR_SETTABLE |
                          PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED |
                          PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED |
                          PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED |
                          PXE_ROMID_IMP_FILTERED_MULTICAST_RX_SUPPORTED;

  PxeSw->EntryPoint   = (UINT64)(UINTN)UndiApiEntry;
  PxeSw->reserved2[0] = 0;
  PxeSw->reserved2[1] = 0;
  PxeSw->reserved2[2] = 0;
  PxeSw->BusCnt       = 1;
  PxeSw->BusType[0]   = PXE_BUSTYPE_USB;
  PxeSw->Fudge        = PxeSw->Fudge - CalculateSum8 ((VOID *)PxeSw, PxeSw->Len);
}

/**
  Update NIC number.

  @param[in]      Nic       A pointer to the Network interface controller data.
  @param[in, out] PxeSw     A pointer to the PXE SW UNDI structure.

**/
VOID
UpdateNicNum (
  IN      NIC_DATA     *Nic,
  IN OUT  PXE_SW_UNDI  *PxeSw
  )
{
  UINT16  NicNum;

  NicNum = (PxeSw->IFcnt | PxeSw->IFcntExt << 8);

  if (Nic == NULL) {
    if (NicNum > 0) {
      NicNum--;
    }

    PxeSw->IFcnt    = (UINT8)(NicNum & 0xFF);          // Get lower byte
    PxeSw->IFcntExt = (UINT8)((NicNum & 0xFF00) >> 8); // Get upper byte
    PxeSw->Fudge    = (UINT8)(PxeSw->Fudge - CalculateSum8 ((VOID *)PxeSw, PxeSw->Len));
    return;
  }

  NicNum++;

  PxeSw->IFcnt    = (UINT8)(NicNum & 0xFF);          // Get lower byte
  PxeSw->IFcntExt = (UINT8)((NicNum & 0xFF00) >> 8); // Get upper byte
  PxeSw->Fudge    = (UINT8)(PxeSw->Fudge - CalculateSum8 ((VOID *)PxeSw, PxeSw->Len));
}

/**
  UNDI API table entry.

  @param[in]  Cdb  A pointer to the command descriptor block.

**/
EFI_STATUS
EFIAPI
UndiApiEntry (
  IN  UINT64  Cdb
  )
{
  PXE_CDB   *CdbPtr;
  NIC_DATA  *Nic;

  if (Cdb == 0) {
    return EFI_INVALID_PARAMETER;
  }

  CdbPtr = (PXE_CDB *)(UINTN)Cdb;
  Nic    = &(gLanDeviceList[CdbPtr->IFnum]->NicInfo);
  gUndiApiTable[CdbPtr->OpCode](CdbPtr, Nic);
  return EFI_SUCCESS;
}

/**
  Map virtual memory address for DMA. This field can be set to
  zero if there is no mapping service.

  @param[in]  Nic           A pointer to the Network interface controller data.
  @param[in]  MemAddr       Virtual address to be mapped.
  @param[in]  Size          Size of memory to be mapped.
  @param[in]  Direction     Direction of data flow for this memory's usage:
                            cpu->device, device->cpu or both ways.
  @param[out] MappedAddr    Pointer to return the mapped device address.

**/
UINTN
MapIt (
  IN NIC_DATA  *Nic,
  IN UINT64    MemAddr,
  IN UINT32    Size,
  IN UINT32    Direction,
  OUT UINT64   MappedAddr
  )
{
  UINT64  *PhyAddr;

  PhyAddr = (UINT64 *)(UINTN)MappedAddr;

  if (Nic->PxeStart.Map_Mem == 0) {
    *PhyAddr = MemAddr;
  } else {
    ((void (*)(UINT64, UINT64, UINT32, UINT32, UINT64))(UINTN) Nic->PxeStart.Map_Mem)(
  Nic->PxeStart.Unique_ID,
  MemAddr,
  Size,
  Direction,
  MappedAddr
  );
  }

  return PXE_STATCODE_SUCCESS;
}

/**
  Un-map previously mapped virtual memory address. This field can be set
  to zero only if the Map_Mem() service is also set to zero.

  @param[in]  Nic           A pointer to the Network interface controller data.
  @param[in]  MemAddr       Virtual address to be mapped.
  @param[in]  Size          Size of memory to be mapped.
  @param[in]  Direction     Direction of data flow for this memory's usage:
                            cpu->device, device->cpu or both ways.
  @param[in]  MappedAddr    Pointer to return the mapped device address.

**/
VOID
UnMapIt (
  IN NIC_DATA  *Nic,
  IN UINT64    MemAddr,
  IN UINT32    Size,
  IN UINT32    Direction,
  IN UINT64    MappedAddr
  )
{
  if (Nic->PxeStart.UnMap_Mem != 0) {
    ((void (*)(UINT64, UINT64, UINT32, UINT32, UINT64))(UINTN) Nic->PxeStart.UnMap_Mem)(
  Nic->PxeStart.Unique_ID,
  MemAddr,
  Size,
  Direction,
  MappedAddr
  );
  }

  return;
}
