/** @file
  This file contains code for USB Ethernet descriptor
  and specific requests implement.

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

#include "UsbRndis.h"

UINT16  gStopBulkInCnt  = 0;
UINT16  gBlockBulkInCnt = 0;

/**
  Load All of device descriptor.

  @param[in]  UsbIo                 A pointer to the EFI_USB_IO_PROTOCOL instance.
  @param[out] ConfigDesc            A pointer to the configuration descriptor.

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_OUT_OF_RESOURCES  The request could not be completed because the
                                buffer specified by DescriptorLength and Descriptor
                                is not large enough to hold the result of the request.
  @retval EFI_TIMEOUT           A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR      The request failed due to a device error. The transfer
                                status is returned in Status.
**/
EFI_STATUS
LoadAllDescriptor (
  IN  EFI_USB_IO_PROTOCOL        *UsbIo,
  OUT EFI_USB_CONFIG_DESCRIPTOR  **ConfigDesc
  )
{
  EFI_STATUS                 Status;
  UINT32                     TransStatus;
  EFI_USB_CONFIG_DESCRIPTOR  Tmp;

  Status = UsbIo->UsbGetConfigDescriptor (UsbIo, &Tmp);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:UsbGetConfigDescriptor status = %r\n", __func__, Status));
    return Status;
  }

  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  Tmp.TotalLength,
                  (VOID **)ConfigDesc
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: AllocatePool status = %r\n", __func__, Status));
    return Status;
  }

  Status = UsbGetDescriptor (
             UsbIo,
             USB_DESC_TYPE_CONFIG << 8 | (Tmp.ConfigurationValue - 1),                 // zero based
             0,
             Tmp.TotalLength,
             *ConfigDesc,
             &TransStatus
             );
  return Status;
}

/**
  Returns pointer to the next descriptor for the pack of USB descriptors
  located in continues memory segment

  @param[in]      Desc   A pointer to the CONFIG_DESCRIPTOR instance.
  @param[in, out] Offset A pointer to the sum of descriptor length.

  @retval TRUE   The request executed successfully.
  @retval FALSE  No next descriptor.

**/
BOOLEAN
NextDescriptor (
  IN      EFI_USB_CONFIG_DESCRIPTOR  *Desc,
  IN OUT  UINTN                      *Offset
  )
{
  if ((Desc == NULL) || (*Offset >= Desc->TotalLength)) {
    return FALSE;
  }

  if (((EFI_USB_CONFIG_DESCRIPTOR *)((char *)Desc+*Offset))->Length == 0) {
    return FALSE;
  }

  *Offset += ((EFI_USB_CONFIG_DESCRIPTOR *)((char *)Desc+*Offset))->Length;
  if ( *Offset >= Desc->TotalLength ) {
    return FALSE;
  }

  return TRUE;
}

/**
  Read Function descriptor

  @param[in]  Config             A pointer to all of configuration.
  @param[in]  FunDescriptorType  USB CDC class descriptor SubType.
  @param[out] DataBuffer         A pointer to the Data of corresponding to device capability.

  @retval EFI_SUCCESS        The device capability descriptor was retrieved
                             successfully.
  @retval EFI_UNSUPPORTED    No supported.
  @retval EFI_NOT_FOUND      The device capability descriptor was not found.

**/
EFI_STATUS
GetFunctionalDescriptor (
  IN  EFI_USB_CONFIG_DESCRIPTOR  *Config,
  IN  UINT8                      FunDescriptorType,
  OUT VOID                       *DataBuffer
  )
{
  EFI_STATUS                    Status;
  UINTN                         Offset;
  EFI_USB_INTERFACE_DESCRIPTOR  *Interface;

  Status = EFI_NOT_FOUND;

  for (Offset = 0; NextDescriptor (Config, &Offset);) {
    Interface = (EFI_USB_INTERFACE_DESCRIPTOR *)((UINT8 *)Config + Offset);
    if (Interface->DescriptorType == CS_INTERFACE) {
      if (((USB_HEADER_FUN_DESCRIPTOR *)Interface)->DescriptorSubtype == FunDescriptorType) {
        switch (FunDescriptorType) {
          case HEADER_FUN_DESCRIPTOR:
            CopyMem (
              DataBuffer,
              (USB_HEADER_FUN_DESCRIPTOR *)Interface,
              sizeof (USB_HEADER_FUN_DESCRIPTOR)
              );
            return EFI_SUCCESS;
          case UNION_FUN_DESCRIPTOR:
            CopyMem (
              DataBuffer,
              (USB_UNION_FUN_DESCRIPTOR *)Interface,
              ((USB_UNION_FUN_DESCRIPTOR *)Interface)->FunctionLength
              );
            return EFI_SUCCESS;
          case ETHERNET_FUN_DESCRIPTOR:
            CopyMem (
              DataBuffer,
              (USB_ETHERNET_FUN_DESCRIPTOR *)Interface,
              sizeof (USB_ETHERNET_FUN_DESCRIPTOR)
              );
            return EFI_SUCCESS;
          default:
            Status = EFI_UNSUPPORTED;
            break;
        }
      }
    }
  }

  return Status;
}

/**
  Get USB Ethernet IO endpoint and USB CDC data IO endpoint.

  @param[in]      UsbIo           A pointer to the EFI_USB_IO_PROTOCOL instance.
  @param[in, out] UsbRndisDevice  A pointer to the USB_RNDIS_DEVICE instance.

**/
VOID
GetEndpoint (
  IN      EFI_USB_IO_PROTOCOL  *UsbIo,
  IN OUT  USB_RNDIS_DEVICE     *UsbRndisDevice
  )
{
  EFI_STATUS                    Status;
  UINT8                         Index;
  UINT32                        Result;
  EFI_USB_INTERFACE_DESCRIPTOR  Interface;
  EFI_USB_ENDPOINT_DESCRIPTOR   Endpoint;

  Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &Interface);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:UsbGetInterfaceDescriptor status = %r\n", __func__, Status));
    return;
  }

  if (Interface.NumEndpoints == 0 ) {
    Status = UsbSetInterface (UsbIo, 1, 0, &Result);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a:UsbSetInterface status = %r\n", __func__, Status));
      return;
    }

    Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &Interface);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a:UsbGetInterfaceDescriptor status = %r\n", __func__, Status));
      return;
    }
  }

  for (Index = 0; Index < Interface.NumEndpoints; Index++) {
    Status = UsbIo->UsbGetEndpointDescriptor (UsbIo, Index, &Endpoint);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a:UsbGetEndpointDescriptor status = %r\n", __func__, Status));
      return;
    }

    switch ((Endpoint.Attributes & (BIT0 | BIT1))) {
      case USB_ENDPOINT_BULK:
        if (Endpoint.EndpointAddress & BIT7) {
          UsbRndisDevice->BulkInEndpoint = Endpoint.EndpointAddress;
        } else {
          UsbRndisDevice->BulkOutEndpoint = Endpoint.EndpointAddress;
        }

        break;
      case USB_ENDPOINT_INTERRUPT:
        UsbRndisDevice->InterrupEndpoint = Endpoint.EndpointAddress;
        break;
    }
  }
}

/**
  Async USB transfer callback routine.

  @param[in]  Data            Data received or sent via the USB Asynchronous Transfer, if the
                              transfer completed successfully.
  @param[in]  DataLength      The length of Data received or sent via the Asynchronous
                              Transfer, if transfer successfully completes.
  @param[in]  Context         Data passed from UsbAsyncInterruptTransfer() request.
  @param[in]  Status          Indicates the result of the asynchronous transfer.

  @retval EFI_SUCCESS           The asynchronous USB transfer request has been successfully executed.
  @retval EFI_DEVICE_ERROR      The asynchronous USB transfer request failed.

**/
EFI_STATUS
EFIAPI
InterruptCallback (
  IN  VOID    *Data,
  IN  UINTN   DataLength,
  IN  VOID    *Context,
  IN  UINT32  Status
  )
{
  if ((Data == NULL) || (Context == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (((EFI_USB_DEVICE_REQUEST *)Data)->Request == 0) {
    CopyMem (
      (EFI_USB_DEVICE_REQUEST *)Context,
      (EFI_USB_DEVICE_REQUEST *)Data,
      sizeof (EFI_USB_DEVICE_REQUEST)
      );
  }

  return EFI_SUCCESS;
}

/**
  This function is used to manage a USB device with an interrupt transfer pipe.

  @param[in]  This              A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  IsNewTransfer     If TRUE, a new transfer will be submitted to USB controller. If
                                FALSE, the interrupt transfer is deleted from the device's interrupt
                                transfer queue.
  @param[in]  PollingInterval   Indicates the periodic rate, in milliseconds, that the transfer is to be
                                executed.This parameter is required when IsNewTransfer is TRUE. The
                                value must be between 1 to 255, otherwise EFI_INVALID_PARAMETER is returned.
                                The units are in milliseconds.
  @param[in]  Requst            A pointer to the EFI_USB_DEVICE_REQUEST data.

  @retval EFI_SUCCESS           The asynchronous USB transfer request transfer has been successfully executed.
  @retval EFI_DEVICE_ERROR      The asynchronous USB transfer request failed.

**/
EFI_STATUS
EFIAPI
UsbRndisInterrupt (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN BOOLEAN                      IsNewTransfer,
  IN UINTN                        PollingInterval,
  IN EFI_USB_DEVICE_REQUEST       *Requst
  )
{
  EFI_STATUS        Status;
  USB_RNDIS_DEVICE  *UsbRndisDevice;
  UINTN             DataLength;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);
  DataLength     = 0;

  if (IsNewTransfer) {
    DataLength = sizeof (EFI_USB_DEVICE_REQUEST) + sizeof (USB_CONNECT_SPEED_CHANGE);
    Status     = UsbRndisDevice->UsbIo->UsbAsyncInterruptTransfer (
                                          UsbRndisDevice->UsbIo,
                                          UsbRndisDevice->InterrupEndpoint,
                                          IsNewTransfer,
                                          PollingInterval,
                                          DataLength,
                                          InterruptCallback,
                                          Requst
                                          );

    if (Status == EFI_INVALID_PARAMETER) {
      // Because of Stacked AsyncInterrupt request are not supported
      Status = UsbRndisDevice->UsbIo->UsbAsyncInterruptTransfer (
                                        UsbRndisDevice->UsbIo,
                                        UsbRndisDevice->InterrupEndpoint,
                                        0,
                                        0,
                                        0,
                                        NULL,
                                        NULL
                                        );
    }
  } else {
    Status = UsbRndisDevice->UsbIo->UsbAsyncInterruptTransfer (
                                      UsbRndisDevice->UsbIo,
                                      UsbRndisDevice->InterrupEndpoint,
                                      IsNewTransfer,
                                      0,
                                      0,
                                      NULL,
                                      NULL
                                      );
  }

  return Status;
}

/**
  This function is used to read USB interrupt transfer before the response RNDIS message.

  @param[in]  UsbRndisDevice    A pointer to the USB_RNDIS_DEVICE instance.

  @retval EFI_SUCCESS           The USB interrupt transfer has been successfully executed.
  @retval EFI_DEVICE_ERROR      The USB interrupt transfer failed.

**/
EFI_STATUS
EFIAPI
ReadRndisResponseInterrupt (
  IN USB_RNDIS_DEVICE  *UsbRndisDevice
  )
{
  EFI_STATUS  Status;
  UINT32      Data[2];
  UINT32      UsbStatus;
  UINTN       DataLength;

  DataLength = 8;

  ZeroMem (Data, sizeof (Data));

  Status = UsbRndisDevice->UsbIo->UsbSyncInterruptTransfer (
                                    UsbRndisDevice->UsbIo,
                                    UsbRndisDevice->InterrupEndpoint,
                                    &Data,
                                    &DataLength,
                                    0x20,
                                    &UsbStatus
                                    );

  return Status;
}

/**
  Retrieves the USB Ethernet Mac Address.

  @param[in]  This          A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[out] MacAddress    A pointer to the caller allocated USB Ethernet Mac Address.

  @retval EFI_SUCCESS           The USB Header Functional descriptor was retrieved successfully.
  @retval EFI_INVALID_PARAMETER UsbHeaderFunDescriptor is NULL.
  @retval EFI_NOT_FOUND         The USB Header Functional descriptor was not found.

**/
EFI_STATUS
EFIAPI
GetUsbEthMacAddress (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT EFI_MAC_ADDRESS              *MacAddress
  )
{
  EFI_STATUS                   Status;
  USB_RNDIS_DEVICE             *UsbRndisDevice;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthDescriptor;
  CHAR16                       *Data;
  CHAR16                       *DataPtr;
  CHAR16                       TmpStr[1];
  UINT8                        Index;
  UINT8                        Hi;
  UINT8                        Low;

  REMOTE_NDIS_QUERY_MAC_MSG    RndisQueryMsg;
  REMOTE_NDIS_QUERY_MAC_CMPLT  RndisQueryMsgCmplt;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  ZeroMem (&RndisQueryMsg, sizeof (REMOTE_NDIS_QUERY_MAC_MSG));
  ZeroMem (&RndisQueryMsgCmplt, sizeof (REMOTE_NDIS_QUERY_MAC_CMPLT));

  RndisQueryMsg.QueryMsg.MessageType   = RNDIS_QUERY_MSG;
  RndisQueryMsg.QueryMsg.MessageLength = sizeof (REMOTE_NDIS_QUERY_MAC_MSG);
  RndisQueryMsg.QueryMsg.RequestID     = UsbRndisDevice->RequestId;
  RndisQueryMsg.QueryMsg.Oid           = OID_802_3_CURRENT_ADDRESS;

  RndisQueryMsgCmplt.QueryCmplt.MessageType   = RNDIS_QUERY_CMPLT;
  RndisQueryMsgCmplt.QueryCmplt.MessageLength = sizeof (REMOTE_NDIS_QUERY_MAC_CMPLT);

  Status = RndisControlMsg (
             UsbRndisDevice,
             (REMOTE_NDIS_MSG_HEADER *)&RndisQueryMsg,
             (REMOTE_NDIS_MSG_HEADER *)&RndisQueryMsgCmplt
             );
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "Success to get Mac address from RNDIS message.\n"));
    for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
      MacAddress->Addr[Index] = RndisQueryMsgCmplt.Addr[Index];
    }

    UsbRndisDevice->RequestId++;
    return Status;
  }

  // If it is not support the OID_802_3_CURRENT_ADDRESS.
  // To check USB Ethernet functional Descriptor
  Status = This->UsbEthFunDescriptor (This, &UsbEthDescriptor);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:UsbEthFunDescriptor status = %r\n", __func__, Status));
    return Status;
  }

  Status = UsbRndisDevice->UsbIo->UsbGetStringDescriptor (
                                    UsbRndisDevice->UsbIo,
                                    0x409,                       // English-US Language ID
                                    UsbEthDescriptor.MacAddress,
                                    &Data
                                    );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:UsbGetStringDescriptor status = %r\n", __func__, Status));
    return Status;
  }

  DataPtr = Data;
  for (Index = 0; Index < PXE_HWADDR_LEN_ETHER; Index++) {
    CopyMem (TmpStr, DataPtr, sizeof (CHAR16));
    DataPtr++;
    Hi = (UINT8)StrHexToUintn (TmpStr);
    CopyMem (TmpStr, DataPtr, sizeof (CHAR16));
    DataPtr++;
    Low                     = (UINT8)StrHexToUintn (TmpStr);
    MacAddress->Addr[Index] = (Hi << 4) | Low;
  }

  return Status;
}

/**
  Retrieves the USB Ethernet Bulk transfer data size.

  @param[in]  This          A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[out] BulkSize      A pointer to the Bulk transfer data size.

  @retval EFI_SUCCESS       The bulk transfer data size was retrieved successfully.
  @retval other             Failed to retrieve the bulk transfer data size.

**/
EFI_STATUS
EFIAPI
UsbEthBulkSize (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT UINTN                        *BulkSize
  )
{
  EFI_STATUS                   Status;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthFunDescriptor;
  USB_RNDIS_DEVICE             *UsbRndisDevice;

  REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_MSG    RndisQueryMsg;
  REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_CMPLT  RndisQueryMsgCmplt;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  ZeroMem (&RndisQueryMsg, sizeof (REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_MSG));
  ZeroMem (&RndisQueryMsgCmplt, sizeof (REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_CMPLT));

  RndisQueryMsg.QueryMsg.MessageType   = RNDIS_QUERY_MSG;
  RndisQueryMsg.QueryMsg.MessageLength = sizeof (REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_MSG);
  RndisQueryMsg.QueryMsg.RequestID     = UsbRndisDevice->RequestId;
  RndisQueryMsg.QueryMsg.Oid           = OID_GEN_MAXIMUM_TOTAL_SIZE;

  RndisQueryMsgCmplt.QueryCmplt.MessageType   = RNDIS_QUERY_CMPLT;
  RndisQueryMsgCmplt.QueryCmplt.MessageLength = sizeof (REMOTE_NDIS_QUERY_MAX_TOTAL_SIZE_CMPLT);

  Status = RndisControlMsg (
             UsbRndisDevice,
             (REMOTE_NDIS_MSG_HEADER *)&RndisQueryMsg,
             (REMOTE_NDIS_MSG_HEADER *)&RndisQueryMsgCmplt
             );
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "Success to get Max Total size : %X  \n", RndisQueryMsgCmplt.MaxTotalSize));
    *BulkSize = RndisQueryMsgCmplt.MaxTotalSize;
    UsbRndisDevice->RequestId++;
    return Status;
  }

  Status = This->UsbEthFunDescriptor (This, &UsbEthFunDescriptor);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *BulkSize = (UINTN)UsbEthFunDescriptor.MaxSegmentSize;
  return Status;
}

/**
  Retrieves the USB Header functional Descriptor.

  @param[in]  This                   A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[out] UsbHeaderFunDescriptor A pointer to the caller allocated USB Header Functional Descriptor.

  @retval EFI_SUCCESS           The USB Header Functional descriptor was retrieved successfully.
  @retval EFI_INVALID_PARAMETER UsbHeaderFunDescriptor is NULL.
  @retval EFI_NOT_FOUND         The USB Header Functional descriptor was not found.

**/
EFI_STATUS
EFIAPI
GetUsbHeaderFunDescriptor (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT USB_HEADER_FUN_DESCRIPTOR    *UsbHeaderFunDescriptor
  )
{
  EFI_STATUS        Status;
  USB_RNDIS_DEVICE  *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  if (UsbHeaderFunDescriptor == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = GetFunctionalDescriptor (
             UsbRndisDevice->Config,
             HEADER_FUN_DESCRIPTOR,
             UsbHeaderFunDescriptor
             );
  return Status;
}

/**
  Retrieves the USB Union functional Descriptor.

  @param[in]  This                   A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[out] UsbUnionFunDescriptor  A pointer to the caller allocated USB Union Functional Descriptor.

  @retval EFI_SUCCESS            The USB Union Functional descriptor was retrieved successfully.
  @retval EFI_INVALID_PARAMETER  UsbUnionFunDescriptor is NULL.
  @retval EFI_NOT_FOUND          The USB Union Functional descriptor was not found.

**/
EFI_STATUS
EFIAPI
GetUsbUnionFunDescriptor (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT USB_UNION_FUN_DESCRIPTOR     *UsbUnionFunDescriptor
  )
{
  EFI_STATUS        Status;
  USB_RNDIS_DEVICE  *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  if (UsbUnionFunDescriptor == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = GetFunctionalDescriptor (
             UsbRndisDevice->Config,
             UNION_FUN_DESCRIPTOR,
             UsbUnionFunDescriptor
             );
  return Status;
}

/**
  Retrieves the USB Ethernet functional Descriptor.

  This function get the Mac Address, Ethernet statistics, maximum segment size,
  number of multicast filters, and number of pattern filters from Ethernet
  functional Descriptor.

  @param[in]  This                   A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[out] UsbEthFunDescriptor    A pointer to the caller allocated USB Ethernet Functional Descriptor.

  @retval EFI_SUCCESS            The USB Ethernet Functional descriptor was retrieved successfully.
  @retval EFI_INVALID_PARAMETER  UsbEthFunDescriptor is NULL.
  @retval EFI_NOT_FOUND          The USB Ethernet Functional descriptor was not found.

**/
EFI_STATUS
EFIAPI
GetUsbRndisFunDescriptor (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT USB_ETHERNET_FUN_DESCRIPTOR  *UsbEthFunDescriptor
  )
{
  EFI_STATUS        Status;
  USB_RNDIS_DEVICE  *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  if (UsbEthFunDescriptor == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = GetFunctionalDescriptor (
             UsbRndisDevice->Config,
             ETHERNET_FUN_DESCRIPTOR,
             UsbEthFunDescriptor
             );
  return Status;
}

/**
  This request sets the Ethernet device multicast filters as specified in the
  sequential list of 48 bit Ethernet multicast addresses.

  @param[in]  This                   A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  Value                  Number of filters.
  @param[in]  McastAddr              A pointer to the value of the multicast addresses.

  @retval EFI_SUCCESS            The request executed successfully.
  @retval EFI_TIMEOUT            A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR       The request failed due to a device error.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED        Not supported.

**/
EFI_STATUS
EFIAPI
SetUsbRndisMcastFilter (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN UINT16                       Value,
  IN VOID                         *McastAddr
  )
{
  EFI_STATUS                   Status;
  EFI_USB_DEVICE_REQUEST       Request;
  UINT32                       TransStatus;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthFunDescriptor;
  USB_RNDIS_DEVICE             *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  Status = This->UsbEthFunDescriptor (This, &UsbEthFunDescriptor);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if ((UsbEthFunDescriptor.NumberMcFilters & MAC_FILTERS_MASK) == 0) {
    return EFI_UNSUPPORTED;
  }

  Request.RequestType = USB_ETHERNET_SET_REQ_TYPE;
  Request.Request     = SET_ETH_MULTICAST_FILTERS_REQ;
  Request.Value       = Value;
  Request.Index       = UsbRndisDevice->NumOfInterface;
  Request.Length      = Value * 6;

  return UsbRndisDevice->UsbIo->UsbControlTransfer (
                                  UsbRndisDevice->UsbIo,
                                  &Request,
                                  EfiUsbDataOut,
                                  USB_ETHERNET_TRANSFER_TIMEOUT,
                                  McastAddr,
                                  Request.Length,
                                  &TransStatus
                                  );
}

/**
  This request sets up the specified Ethernet power management pattern filter as
  described in the data structure.

  @param[in]  This                  A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  Value                 Number of filters.
  @param[in]  Length                Size of the power management pattern filter data.
  @param[in]  PatternFilter         A pointer to the power management pattern filter structure.

  @retval EFI_SUCCESS            The request executed successfully.
  @retval EFI_TIMEOUT            A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR       The request failed due to a device error.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED        Not supported.

**/
EFI_STATUS
EFIAPI
SetUsbRndisPowerFilter (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN UINT16                       Value,
  IN UINT16                       Length,
  IN VOID                         *PatternFilter
  )
{
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TransStatus;
  USB_RNDIS_DEVICE        *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  Request.RequestType = USB_ETHERNET_SET_REQ_TYPE;
  Request.Request     = SET_ETH_POWER_MANAGEMENT_PATTERN_FILTER_REQ;
  Request.Value       = Value;
  Request.Index       = UsbRndisDevice->NumOfInterface;
  Request.Length      = Length;

  return UsbRndisDevice->UsbIo->UsbControlTransfer (
                                  UsbRndisDevice->UsbIo,
                                  &Request,
                                  EfiUsbDataOut,
                                  USB_ETHERNET_TRANSFER_TIMEOUT,
                                  PatternFilter,
                                  Length,
                                  &TransStatus
                                  );
}

/**
  This request retrieves the status of the specified Ethernet power management
  pattern filter from the device.

  @param[in]  This                   A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  Value                  The filter number.
  @param[out] PatternActive          A pointer to the pattern active boolean.

  @retval EFI_SUCCESS            The request executed successfully.
  @retval EFI_TIMEOUT            A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR       The request failed due to a device error.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED        Not supported.

**/
EFI_STATUS
EFIAPI
GetUsbRndisPowerFilter (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN  UINT16                       Value,
  OUT BOOLEAN                      *PatternActive
  )
{
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TransStatus;
  USB_RNDIS_DEVICE        *UsbRndisDevice;

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  Request.RequestType = USB_ETHERNET_GET_REQ_TYPE;
  Request.Request     = GET_ETH_POWER_MANAGEMENT_PATTERN_FILTER_REQ;
  Request.Value       = Value;
  Request.Index       = UsbRndisDevice->NumOfInterface;
  Request.Length      = USB_ETH_POWER_FILTER_LENGTH;

  return UsbRndisDevice->UsbIo->UsbControlTransfer (
                                  UsbRndisDevice->UsbIo,
                                  &Request,
                                  EfiUsbDataIn,
                                  USB_ETHERNET_TRANSFER_TIMEOUT,
                                  PatternActive,
                                  USB_ETH_POWER_FILTER_LENGTH,
                                  &TransStatus
                                  );
}

BIT_MAP  gTable[] = {
  { PXE_OPFLAGS_RECEIVE_FILTER_UNICAST,            NDIS_PACKET_TYPE_DIRECTED      },
  { PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST,          NDIS_PACKET_TYPE_BROADCAST     },
  { PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST, NDIS_PACKET_TYPE_MULTICAST     },
  { PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS,        NDIS_PACKET_TYPE_PROMISCUOUS   },
  { PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST,      NDIS_PACKET_TYPE_ALL_MULTICAST },
};

/**

  Converts PXE filter settings to RNDIS values

  @param[in]  Value      PXE filter data.
  @param[out] CdcFilter  A pointer to the Ethernet Packet Filter Bitmap value converted by PXE_OPFLAGS.

**/
VOID
ConvertFilter (
  IN  UINT16  Value,
  OUT UINT16  *CdcFilter
  )
{
  UINT32  Index;
  UINT32  Count;

  Count = sizeof (gTable)/sizeof (gTable[0]);

  for (Index = 0; (gTable[Index].Src != 0) && (Index < Count); Index++) {
    if (gTable[Index].Src & Value) {
      *CdcFilter |= gTable[Index].Dst;
    }
  }
}

/**

  Updates Filter settings on the device.

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

  @retval EFI_STATUS

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

  Count     = 0;
  CpbAddr   = Cdb->CPBaddr;
  CpbSize   = Cdb->CPBsize;
  SetFilter = (UINT16)(Cdb->OpFlags & 0x1F);
  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);
    } else {
      Nic->McastCount = 0;
    }

    Nic->UsbEth->UsbEthFunDescriptor (Nic->UsbEth, &UsbEthFunDescriptor);
    if ((UsbEthFunDescriptor.NumberMcFilters & MAC_FILTERS_MASK) == 0) {
      Nic->RxFilter |= PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST;
      DEBUG ((DEBUG_INFO, "SetUsbEthPacketFilter Nic %lx Nic->UsbEth %lx ", Nic, Nic->UsbEth));
      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 EFI_SUCCESS;
}

/**
  This request is used to configure device Ethernet packet filter settings.

  @param[in]  This              A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  Value             Packet Filter Bitmap.

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_TIMEOUT           A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR      The request failed due to a device error.
  @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED       Not supported.

**/
EFI_STATUS
EFIAPI
SetUsbRndisPacketFilter (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN UINT16                       Value
  )
{
  return EFI_SUCCESS;
}

/**
  This request is used to retrieve a statistic based on the feature selector.

  @param[in]  This                  A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]  FeatureSelector       Value of the feature selector.
  @param[out] Statistic             A pointer to the 32 bit unsigned integer.

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_TIMEOUT           A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR      The request failed due to a device error.
  @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value.
  @retval EFI_UNSUPPORTED       Not supported.

**/
EFI_STATUS
EFIAPI
GetRndisStatistic (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN  UINT16                       FeatureSelector,
  OUT VOID                         *Statistic
  )
{
  return EFI_SUCCESS;
}

/**
  This function is called when UndiStart is invoked.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiStart (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_INFO, "RndisUndiStart Nic %lx Cdb %lx Nic State %x\n", Nic, Cdb, Nic->State));

  // Issue Rndis Reset and bring the device to RNDIS_BUS_INITIALIZED state
  Status = RndisUndiReset (Cdb, Nic);
  if (EFI_ERROR (Status)) {
    RndisUndiReset (Cdb, Nic);
  }

  Status = RndisUndiInitialize (Cdb, Nic);
  if (EFI_ERROR (Status)) {
    RndisUndiInitialize (Cdb, Nic);
  }

  RndisUndiShutdown (Cdb, Nic);

  return EFI_SUCCESS;
}

/**
  This function is called when Undistop is invoked.

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

  @retval EFI_SUCCESS     The request executed successfully.
**/
EFI_STATUS
EFIAPI
RndisUndiStop (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  DEBUG ((DEBUG_INFO, "RndisUndiStop State %x\n", Nic->State));
  return EFI_SUCCESS;
}

/**
  This function is called when UndiGetInitInfo is invoked.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiGetInitInfo (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EDKII_USB_ETHERNET_PROTOCOL  *UsbEthDevice;
  USB_RNDIS_DEVICE             *UsbRndisDevice;
  PXE_DB_GET_INIT_INFO         *Db;

  DEBUG ((DEBUG_INFO, "RndisUndiGetInitInfo\n"));

  UsbEthDevice   = Nic->UsbEth;
  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (UsbEthDevice);

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

  Db->FrameDataLen = UsbRndisDevice->MaxTransferSize - sizeof (REMOTE_NDIS_PACKET_MSG) - PXE_MAC_HEADER_LEN_ETHER;
  // Limit Max MTU size to 1500 bytes as RNDIS spec.
  if (Db->FrameDataLen > PXE_MAX_TXRX_UNIT_ETHER) {
    Db->FrameDataLen = PXE_MAX_TXRX_UNIT_ETHER;
  }

  DEBUG ((DEBUG_INFO, "Db->FrameDataLen %x\n", Db->FrameDataLen));

  return EFI_SUCCESS;
}

/**
  This function is called when RndisUndiGetConfigInfo is invoked.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiGetConfigInfo (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  DEBUG ((DEBUG_INFO, "RndisUndiGetConfigInfo\n"));
  return EFI_SUCCESS;
}

/**
  This function is called when UndiInitialize is invoked.

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

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_UNSUPPORTED       Not supported.

**/
EFI_STATUS
EFIAPI
RndisUndiInitialize (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EDKII_USB_ETHERNET_PROTOCOL   *UsbEthDriver;
  USB_RNDIS_DEVICE              *UsbRndisDevice;
  REMOTE_NDIS_INITIALIZE_MSG    RndisInitMsg;
  REMOTE_NDIS_INITIALIZE_CMPLT  RndisInitMsgCmplt;
  EFI_STATUS                    Status;

  DEBUG ((DEBUG_INFO, "RndisUndiInitialize\n"));

  UsbEthDriver   = Nic->UsbEth;
  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (UsbEthDriver);

  ZeroMem (&RndisInitMsg, sizeof (REMOTE_NDIS_INITIALIZE_MSG));
  ZeroMem (&RndisInitMsgCmplt, sizeof (REMOTE_NDIS_INITIALIZE_CMPLT));

  RndisInitMsg.MessageType     = RNDIS_INITIALIZE_MSG;
  RndisInitMsg.MessageLength   = sizeof (REMOTE_NDIS_INITIALIZE_MSG);
  RndisInitMsg.RequestID       = UsbRndisDevice->RequestId;
  RndisInitMsg.MajorVersion    = RNDIS_MAJOR_VERSION;
  RndisInitMsg.MinorVersion    = RNDIS_MINOR_VERSION;
  RndisInitMsg.MaxTransferSize = RNDIS_MAX_TRANSFER_SIZE;

  RndisInitMsgCmplt.MessageType   = RNDIS_INITIALIZE_CMPLT;
  RndisInitMsgCmplt.MessageLength = sizeof (REMOTE_NDIS_INITIALIZE_CMPLT);

  Status = RndisControlMsg (UsbRndisDevice, (REMOTE_NDIS_MSG_HEADER *)&RndisInitMsg, (REMOTE_NDIS_MSG_HEADER *)&RndisInitMsgCmplt);

  UsbRndisDevice->RequestId++;

  if (EFI_ERROR (Status) || (RndisInitMsgCmplt.Status & 0x80000000)) {
    return Status;
  }

  // Only Wired Medium is supported
  if (RndisInitMsgCmplt.Medium) {
    return EFI_UNSUPPORTED;
  }

  UsbRndisDevice->Medium                = RndisInitMsgCmplt.Medium;
  UsbRndisDevice->MaxPacketsPerTransfer = RndisInitMsgCmplt.MaxPacketsPerTransfer;
  UsbRndisDevice->MaxTransferSize       = RndisInitMsgCmplt.MaxTransferSize;
  UsbRndisDevice->PacketAlignmentFactor = RndisInitMsgCmplt.PacketAlignmentFactor;

  DEBUG ((DEBUG_INFO, "Medium : %x \n", RndisInitMsgCmplt.Medium));
  DEBUG ((DEBUG_INFO, "MaxPacketsPerTransfer : %x \n", RndisInitMsgCmplt.MaxPacketsPerTransfer));
  DEBUG ((DEBUG_INFO, "MaxTransferSize : %x\n", RndisInitMsgCmplt.MaxTransferSize));
  DEBUG ((DEBUG_INFO, "PacketAlignmentFactor : %x\n", RndisInitMsgCmplt.PacketAlignmentFactor));

  return Status;
}

/**
  This function is called when UndiReset is invoked.

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

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_DEVICE_ERROR      The request failed due to a device error.

**/
EFI_STATUS
EFIAPI
RndisUndiReset (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EDKII_USB_ETHERNET_PROTOCOL  *UsbEthDriver;
  USB_RNDIS_DEVICE             *UsbRndisDevice;
  REMOTE_NDIS_RESET_MSG        RndisResetMsg;
  REMOTE_NDIS_RESET_CMPLT      RndisResetCmplt;
  EFI_STATUS                   Status;

  DEBUG ((DEBUG_INFO, "RndisUndiReset\n"));

  UsbEthDriver   = Nic->UsbEth;
  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (UsbEthDriver);

  ZeroMem (&RndisResetMsg, sizeof (REMOTE_NDIS_RESET_MSG));
  ZeroMem (&RndisResetCmplt, sizeof (REMOTE_NDIS_RESET_CMPLT));

  RndisResetMsg.MessageType   = RNDIS_RESET_MSG;
  RndisResetMsg.MessageLength = sizeof (REMOTE_NDIS_RESET_MSG);

  RndisResetCmplt.MessageType   = RNDIS_RESET_CMPLT;
  RndisResetCmplt.MessageLength = sizeof (REMOTE_NDIS_RESET_CMPLT);

  Status = RndisControlMsg (UsbRndisDevice, (REMOTE_NDIS_MSG_HEADER *)&RndisResetMsg, (REMOTE_NDIS_MSG_HEADER *)&RndisResetCmplt);

  UsbRndisDevice->RequestId = 1;          // Let's start with 1

  if (EFI_ERROR (Status) || (RndisResetCmplt.Status & 0x80000000)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  This function is called when UndiShutdown is invoked.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiShutdown (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  EDKII_USB_ETHERNET_PROTOCOL  *UsbEthDriver;
  USB_RNDIS_DEVICE             *UsbRndisDevice;
  REMOTE_NDIS_HALT_MSG         RndisHltMsg;
  EFI_STATUS                   Status;

  DEBUG ((DEBUG_INFO, "RndisUndiShutdown\n"));

  UsbEthDriver   = Nic->UsbEth;
  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (UsbEthDriver);

  ZeroMem (&RndisHltMsg, sizeof (REMOTE_NDIS_HALT_MSG));

  RndisHltMsg.MessageType   = RNDIS_HLT_MSG;
  RndisHltMsg.MessageLength = sizeof (REMOTE_NDIS_HALT_MSG);

  Status = RndisControlMsg (UsbRndisDevice, (REMOTE_NDIS_MSG_HEADER *)&RndisHltMsg, NULL);

  if (Status == EFI_DEVICE_ERROR) {
    Status = EFI_SUCCESS;
  }

  UsbRndisDevice->RequestId = 1;
  return Status;
}

/**
  Update the Media connection.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiGetStatus (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  Cdb->StatFlags &= ~(PXE_STATFLAGS_GET_STATUS_NO_MEDIA);
  return EFI_SUCCESS;
}

/**
  Transmit the data after appending RNDIS header.

  @param[in]      Cdb           A pointer to the command descriptor block.
  @param[in]      This          A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in]      BulkOutData   A pointer to the buffer of data that will be transmitted to USB
                                device or received from USB device.
  @param[in, out] DataLength    A pointer to the PacketLength.

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisUndiTransmit (
  IN      PXE_CDB                      *Cdb,
  IN      EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN      VOID                         *BulkOutData,
  IN OUT  UINTN                        *DataLength
  )
{
  EFI_STATUS              Status;
  USB_RNDIS_DEVICE        *UsbRndisDevice;
  REMOTE_NDIS_PACKET_MSG  *RndisPacketMsg;
  UINTN                   TransferLength;

  DEBUG ((DEBUG_INFO, "RndisUndiTransmit DataLength : %x\n", *DataLength));

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);

  RndisPacketMsg = AllocateZeroPool (sizeof (REMOTE_NDIS_PACKET_MSG) + *DataLength);
  if (RndisPacketMsg == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  RndisPacketMsg->MessageType   = RNDIS_PACKET_MSG;
  RndisPacketMsg->MessageLength = sizeof (REMOTE_NDIS_PACKET_MSG) + (UINT32)*DataLength;
  RndisPacketMsg->DataOffset    = sizeof (REMOTE_NDIS_PACKET_MSG) - 8;
  RndisPacketMsg->DataLength    = (UINT32)*DataLength;

  CopyMem (
    ((UINT8 *)RndisPacketMsg) + sizeof (REMOTE_NDIS_PACKET_MSG),
    BulkOutData,
    *DataLength
    );

  TransferLength = RndisPacketMsg->MessageLength;

  Status = RndisTransmitDataMsg (
             UsbRndisDevice,
             (REMOTE_NDIS_MSG_HEADER *)RndisPacketMsg,
             &TransferLength
             );

  DEBUG ((DEBUG_INFO, "\nRndisUndiTransmit TransferLength %lx\n", TransferLength));

  FreePool (RndisPacketMsg);

  return Status;
}

/**
  Receives and removes RNDIS header and returns the raw data.

  @param[in]      Cdb           A pointer to the command descriptor block.
  @param[in]      This          A pointer to the EDKII_USB_ETHERNET_PROTOCOL instance.
  @param[in, out] BulkInData    A pointer to the buffer of data that will be transmitted to USB
                                device or received from USB device.
  @param[in, out] DataLength    A pointer to the PacketLength.

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_BUFFER_TOO_SMALL  The user provided buffer is too small
  @retval EFI_NOT_FOUND         No buffer was found in the list.

**/
EFI_STATUS
EFIAPI
RndisUndiReceive (
  IN     PXE_CDB                      *Cdb,
  IN     EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN OUT VOID                         *BulkInData,
  IN OUT UINTN                        *DataLength
  )
{
  EFI_STATUS              Status;
  USB_RNDIS_DEVICE        *UsbRndisDevice;
  REMOTE_NDIS_PACKET_MSG  *RndisPacketMsg;
  UINTN                   TransferLength;
  VOID                    *Buffer;
  PACKET_LIST             *HeadPacket;
  PACKET_LIST             *PacketList;

  // Check if there is any outstanding packet to receive
  // The buffer allocated has a linked List followed by the packet.

  UsbRndisDevice = USB_RNDIS_DEVICE_FROM_THIS (This);
  Buffer         = NULL;
  HeadPacket     = NULL;

  while (1) {
    Buffer = AllocateZeroPool (sizeof (PACKET_LIST) + sizeof (REMOTE_NDIS_PACKET_MSG) + UsbRndisDevice->MaxTransferSize);
    if (Buffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    RndisPacketMsg                = (REMOTE_NDIS_PACKET_MSG *)(sizeof (PACKET_LIST) + (UINT8 *)Buffer);
    PacketList                    = (PACKET_LIST *)Buffer;
    PacketList->PacketStartBuffer = (UINT8 *)Buffer + sizeof (PACKET_LIST);
    // Save the original address for freeing it up
    PacketList->OrgBuffer = (UINT8 *)Buffer;
    TransferLength        = UsbRndisDevice->MaxTransferSize;

    Status = RndisReceiveDataMsg (
               UsbRndisDevice,
               (REMOTE_NDIS_MSG_HEADER *)RndisPacketMsg,
               &TransferLength
               );

    if (EFI_ERROR (Status) || (TransferLength == 0)) {
      FreePool (Buffer);
      break;
    }

    // Collect all the RNDIS packet in Linked list.
    if ((RndisPacketMsg->MessageType == RNDIS_PACKET_MSG) &&
        (RndisPacketMsg->DataOffset == sizeof (REMOTE_NDIS_PACKET_MSG) - RNDIS_RESERVED_BYTE_LENGTH) &&
        (TransferLength >= RndisPacketMsg->MessageLength))
    {
      // Insert Packet
      PacketList->RemainingLength = TransferLength;
      InsertTailList (&UsbRndisDevice->ReceivePacketList, Buffer);
    } else {
      FreePool (Buffer);
    }
  }

  while (!IsListEmpty (&UsbRndisDevice->ReceivePacketList)) {
    HeadPacket = (PACKET_LIST *)GetFirstNode (&UsbRndisDevice->ReceivePacketList);

    RndisPacketMsg = (REMOTE_NDIS_PACKET_MSG *)(UINT8 *)HeadPacket->PacketStartBuffer;

    PrintRndisMsg ((REMOTE_NDIS_MSG_HEADER *)RndisPacketMsg);

    // Check whether the packet is valid RNDIS packet.
    if ((HeadPacket->RemainingLength > sizeof (REMOTE_NDIS_PACKET_MSG)) && (RndisPacketMsg->MessageType == RNDIS_PACKET_MSG) &&
        (RndisPacketMsg->DataOffset == (sizeof (REMOTE_NDIS_PACKET_MSG) - RNDIS_RESERVED_BYTE_LENGTH)) &&
        (HeadPacket->RemainingLength >= RndisPacketMsg->MessageLength))
    {
      if (*DataLength >= RndisPacketMsg->DataLength) {
        CopyMem (
          BulkInData,
          (UINT8 *)RndisPacketMsg + (RndisPacketMsg->DataOffset + RNDIS_RESERVED_BYTE_LENGTH),
          RndisPacketMsg->DataLength
          );

        *DataLength =  RndisPacketMsg->DataLength;

        HeadPacket->RemainingLength   = HeadPacket->RemainingLength - RndisPacketMsg->MessageLength;
        HeadPacket->PacketStartBuffer = (UINT8 *)RndisPacketMsg + RndisPacketMsg->MessageLength;

        return EFI_SUCCESS;
      } else {
        *DataLength = RndisPacketMsg->DataLength;
        return EFI_BUFFER_TOO_SMALL;
      }
    }

    RemoveEntryList (&HeadPacket->PacketList);
    FreePool ((PACKET_LIST *)HeadPacket->OrgBuffer);
  }

  return EFI_NOT_FOUND;
}

/**
  This is a dummy function which just returns. Unimplemented EDKII_USB_ETHERNET_PROTOCOL functions
  point to this function.

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

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
EFIAPI
RndisDummyReturn (
  IN  PXE_CDB   *Cdb,
  IN  NIC_DATA  *Nic
  )
{
  DEBUG ((DEBUG_INFO, "RndisDummyReturn called\n"));
  return EFI_SUCCESS;
}

/**
  This function send the RNDIS command through the device's control endpoint

  @param[in]  UsbRndisDevice    A pointer to the USB_RNDIS_DEVICE instance.
  @param[in]  RndisMsg          A pointer to the REMOTE_NDIS_MSG_HEADER data.
  @param[out] RndisMsgResponse  A pointer to the REMOTE_NDIS_MSG_HEADER data for getting responses.

  @retval EFI_SUCCESS           The bulk transfer has been successfully executed.

**/
EFI_STATUS
RndisControlMsg (
  IN  USB_RNDIS_DEVICE        *UsbRndisDevice,
  IN  REMOTE_NDIS_MSG_HEADER  *RndisMsg,
  OUT REMOTE_NDIS_MSG_HEADER  *RndisMsgResponse
  )
{
  EFI_USB_IO_PROTOCOL           *UsbIo;
  EFI_USB_DEVICE_REQUEST        DevReq;
  UINT32                        UsbStatus;
  EFI_STATUS                    Status;
  UINT32                        SaveResponseType;
  UINT32                        SaveResponseLength;
  UINT32                        Index;
  REMOTE_NDIS_INITIALIZE_CMPLT  *RndisInitCmplt;

  UsbIo              = UsbRndisDevice->UsbIo;
  SaveResponseType   = 0;
  SaveResponseLength = 0;
  RndisInitCmplt     = (REMOTE_NDIS_INITIALIZE_CMPLT *)RndisMsgResponse;

  if (RndisMsgResponse != NULL) {
    SaveResponseType   = RndisMsgResponse->MessageType;
    SaveResponseLength = RndisMsgResponse->MessageLength;
  }

  ZeroMem (&DevReq, sizeof (EFI_USB_DEVICE_REQUEST));

  DevReq.RequestType = USB_REQ_TYPE_CLASS | USB_TARGET_INTERFACE;
  DevReq.Request     = SEND_ENCAPSULATED_COMMAND;
  DevReq.Value       = 0;
  DevReq.Index       = 0;
  DevReq.Length      = (UINT16)RndisMsg->MessageLength;

  PrintRndisMsg (RndisMsg);

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &DevReq,
                    EfiUsbDataOut,
                    USB_ETHERNET_TRANSFER_TIMEOUT,
                    RndisMsg,
                    RndisMsg->MessageLength,
                    &UsbStatus
                    );

  DEBUG ((DEBUG_INFO, "RndisControlMsg: UsbStatus : %x Status : %r RndisMsgResponse : %lx\n", UsbStatus, Status, RndisMsgResponse));

  // Error or no response expected
  if ((EFI_ERROR (Status)) || (RndisMsgResponse == NULL)) {
    DEBUG ((DEBUG_INFO, "RndisControlMsg: UsbStatus : %x Status : %r\n", UsbStatus, Status));
    return Status;
  }

  for (Index = 0; Index < (RNDIS_CONTROL_TIMEOUT/100); Index++) {
    ReadRndisResponseInterrupt (UsbRndisDevice);
    ZeroMem (&DevReq, sizeof (EFI_USB_DEVICE_REQUEST));

    DevReq.RequestType = USB_ENDPOINT_DIR_IN | USB_REQ_TYPE_CLASS | USB_TARGET_INTERFACE;
    DevReq.Request     = GET_ENCAPSULATED_RESPONSE;
    DevReq.Value       = 0;
    DevReq.Index       = 0;
    DevReq.Length      = (UINT16)RndisMsgResponse->MessageLength;

    Status = UsbIo->UsbControlTransfer (
                      UsbIo,
                      &DevReq,
                      EfiUsbDataIn,
                      USB_ETHERNET_TRANSFER_TIMEOUT,
                      RndisMsgResponse,
                      RndisMsgResponse->MessageLength,
                      &UsbStatus
                      );

    DEBUG ((DEBUG_INFO, "RndisControlMsg Response: UsbStatus : %x Status : %r \n", UsbStatus, Status));

    PrintRndisMsg (RndisMsgResponse);

    if (!EFI_ERROR (Status)) {
      if ((RndisInitCmplt->RequestID != ((REMOTE_NDIS_INITIALIZE_CMPLT *)RndisMsg)->RequestID) || (RndisInitCmplt->MessageType != SaveResponseType)) {
        DEBUG ((DEBUG_INFO, "Retry the response\n"));

        RndisMsgResponse->MessageType   = SaveResponseType;
        RndisMsgResponse->MessageLength = SaveResponseLength;
        continue;
      }
    }

    return Status;
  }

  DEBUG ((DEBUG_INFO, "RndisControlMsg: TimeOut\n"));

  return EFI_TIMEOUT;
}

/**
  This function send the RNDIS command through the device's Data endpoint

  @param[in]      UsbRndisDevice  A pointer to the USB_RNDIS_DEVICE instance.
  @param[in]      RndisMsg        A pointer to the REMOTE_NDIS_MSG_HEADER to send out.
  @param[in, out] TransferLength  The length of the RndisMsg data to transfer.

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
RndisTransmitDataMsg (
  IN      USB_RNDIS_DEVICE        *UsbRndisDevice,
  IN      REMOTE_NDIS_MSG_HEADER  *RndisMsg,
  IN OUT  UINTN                   *TransferLength
  )
{
  EFI_STATUS  Status;
  UINT32      UsbStatus;

  if (UsbRndisDevice->BulkInEndpoint == 0) {
    GetEndpoint (UsbRndisDevice->UsbIoCdcData, UsbRndisDevice);
  }

  PrintRndisMsg (RndisMsg);

  Status = UsbRndisDevice->UsbIoCdcData->UsbBulkTransfer (
                                           UsbRndisDevice->UsbIoCdcData,
                                           UsbRndisDevice->BulkOutEndpoint,
                                           RndisMsg,
                                           TransferLength,
                                           USB_TX_ETHERNET_BULK_TIMEOUT,
                                           &UsbStatus
                                           );

  if (Status == EFI_SUCCESS) {
    gStopBulkInCnt = MAXIMUM_STOPBULKIN_CNT;     // After sending cmd ,we will polling receive package for MAXIMUM_STOPBULKIN_CNT times
  }

  return Status;
}

/**
  This function send the RNDIS command through the device's Data endpoint

  @param[in]      UsbRndisDevice    A pointer to the USB_RNDIS_DEVICE instance.
  @param[in, out] RndisMsg          A pointer to the REMOTE_NDIS_MSG_HEADER to send out.
  @param[in, out] TransferLength    The length of the RndisMsg data to transfer.

  @retval EFI_SUCCESS     The request executed successfully.

**/
EFI_STATUS
RndisReceiveDataMsg (
  IN      USB_RNDIS_DEVICE        *UsbRndisDevice,
  IN OUT  REMOTE_NDIS_MSG_HEADER  *RndisMsg,
  IN OUT  UINTN                   *TransferLength
  )
{
  EFI_STATUS  Status;
  UINT32      UsbStatus;

  UsbStatus = 0;

  if (UsbRndisDevice->BulkInEndpoint == 0) {
    GetEndpoint (UsbRndisDevice->UsbIoCdcData, UsbRndisDevice);
  }

  // Use gStopBulkInCnt to stop BulkIn command
  if ((gStopBulkInCnt != 0) || LAN_BULKIN_CMD_CONTROL) {
    Status = UsbRndisDevice->UsbIoCdcData->UsbBulkTransfer (
                                             UsbRndisDevice->UsbIoCdcData,
                                             UsbRndisDevice->BulkInEndpoint,
                                             RndisMsg,
                                             TransferLength,
                                             USB_RX_ETHERNET_BULK_TIMEOUT,
                                             &UsbStatus
                                             );

    if (!EFI_ERROR (Status)) {
      gStopBulkInCnt = MINIMUM_STOPBULKIN_CNT;
    } else {
      gStopBulkInCnt--;
    }
  } else {
    Status          = EFI_TIMEOUT;
    *TransferLength = 0;
    gBlockBulkInCnt++;
  }

  if (gBlockBulkInCnt > BULKIN_CMD_POLLING_CNT) {
    gStopBulkInCnt  = MINIMUM_STOPBULKIN_CNT;
    gBlockBulkInCnt = 0;
  }

  PrintRndisMsg (RndisMsg);

  return Status;
}

/**
  Prints RNDIS Header and Data

  @param[in] RndisMsg    A pointer to the REMOTE_NDIS_MSG_HEADER data.

**/
VOID
PrintRndisMsg (
  IN  REMOTE_NDIS_MSG_HEADER  *RndisMsg
  )
{
  UINTN                    Length;
  REMOTE_NDIS_QUERY_CMPLT  *RndisQueryCmplt;

  Length = 0;

  switch (RndisMsg->MessageType) {
    case RNDIS_PACKET_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_PACKET_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_PACKET_MSG) + 0x14;
      break;
    case RNDIS_INITIALIZE_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_INITIALIZE_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_INITIALIZE_MSG);
      break;
    case RNDIS_INITIALIZE_CMPLT:
      DEBUG ((DEBUG_INFO, "RNDIS_INITIALIZE_CMPLT:\n"));
      Length = sizeof (REMOTE_NDIS_INITIALIZE_CMPLT);
      break;
    case RNDIS_HLT_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_HLT_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_HALT_MSG);
      break;
    case RNDIS_QUERY_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_QUERY_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_QUERY_MSG);
      break;
    case RNDIS_QUERY_CMPLT:
      DEBUG ((DEBUG_INFO, "RNDIS_QUERY_CMPLT:\n"));
      RndisQueryCmplt = (REMOTE_NDIS_QUERY_CMPLT *)RndisMsg;
      Length          = sizeof (REMOTE_NDIS_QUERY_CMPLT) + RndisQueryCmplt->InformationBufferLength;
      break;
    case RNDIS_SET_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_SET_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_SET_MSG);
      break;
    case RNDIS_SET_CMPLT:
      DEBUG ((DEBUG_INFO, "RNDIS_SET_CMPLT:\n"));
      Length = sizeof (REMOTE_NDIS_SET_CMPLT);
      break;
    case RNDIS_RESET_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_RESET_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_RESET_MSG);
      break;
    case RNDIS_RESET_CMPLT:
      DEBUG ((DEBUG_INFO, "RNDIS_RESET_CMPLT:\n"));
      Length = sizeof (REMOTE_NDIS_RESET_CMPLT);
      break;
    case RNDIS_INDICATE_STATUS_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_INDICATE_STATUS_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_INDICATE_STATUS_MSG);
      break;
    case RNDIS_KEEPALIVE_MSG:
      DEBUG ((DEBUG_INFO, "RNDIS_KEEPALIVE_MSG:\n"));
      Length = sizeof (REMOTE_NDIS_KEEPALIVE_MSG);
      break;
    case RNDIS_KEEPALIVE_CMPLT:
      DEBUG ((DEBUG_INFO, "RNDIS_KEEPALIVE_CMPLT:\n"));
      Length = sizeof (REMOTE_NDIS_KEEPALIVE_CMPLT);
  }

  if (Length) {
    UINTN  Index;
    Index = 0;
    for ( ; Length; Length -= 4, Index++) {
      DEBUG ((DEBUG_INFO, "%8X\t", *((UINT32 *)RndisMsg + Index)));
      if (((Index % 4) == 3) && (Index != 0)) {
        DEBUG ((DEBUG_INFO, "\n"));
      }

      if ((Length < 8) && (Length > 4)) {
        UINT32  Data32;
        Index++;
        Data32 = *((UINT32 *)RndisMsg + Index);
        DEBUG ((DEBUG_INFO, "%8X\t", Data32));
        break;
      }
    }

    if ((Index % 4) != 0) {
      DEBUG ((DEBUG_INFO, "\n"));
    }
  }
}
