/** @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 "UsbCdcNcm.h"

/**
  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);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->AllocatePool (EfiBootServicesData, Tmp.TotalLength, (VOID **)ConfigDesc);
  ASSERT_EFI_ERROR (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 == USB_DESC_TYPE_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] UsbEthDriver  A pointer to the USB_ETHERNET_DRIVER instance.

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

  Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &Interface);
  ASSERT_EFI_ERROR (Status);

  if (Interface.NumEndpoints == 0) {
    Status = UsbSetInterface (UsbIo, Interface.InterfaceNumber, 1, &Result);
    ASSERT_EFI_ERROR (Status);

    Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &Interface);
    ASSERT_EFI_ERROR (Status);
  }

  for (Index = 0; Index < Interface.NumEndpoints; Index++) {
    Status = UsbIo->UsbGetEndpointDescriptor (UsbIo, Index, &Endpoint);
    ASSERT_EFI_ERROR (Status);

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

        break;
      case USB_ENDPOINT_INTERRUPT:
        UsbEthDriver->InterruptEndpoint = Endpoint.EndpointAddress;
        break;
    }
  }
}

/**
  This function is used to manage a USB device with the bulk transfer pipe. The endpoint is Bulk in.

  @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] Packet        A pointer to the buffer of data that will be transmitted to USB
                                device or received from USB device.
  @param[in, out] PacketLength  A pointer to the PacketLength.

  @retval EFI_SUCCESS           The bulk transfer has been successfully executed.
  @retval EFI_DEVICE_ERROR      The transfer failed. The transfer status is returned in status.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES  The request could not be submitted due to a lack of resources.
  @retval EFI_TIMEOUT           The control transfer fails due to timeout.

**/
EFI_STATUS
EFIAPI
UsbEthNcmReceive (
  IN     PXE_CDB                      *Cdb,
  IN     EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN OUT VOID                         *Packet,
  IN OUT UINTN                        *PacketLength
  )
{
  EFI_STATUS                   Status;
  USB_ETHERNET_DRIVER          *UsbEthDriver;
  EFI_USB_IO_PROTOCOL          *UsbIo;
  UINT32                       TransStatus;
  UINT8                        Index;
  UINTN                        BulkDataLength;
  UINTN                        TotalLength;
  USB_NCM_TRANSFER_HEADER_16   *Nth;
  USB_NCM_DATAGRAM_POINTER_16  *Ndp;
  USB_NCM_DATA_GRAM            *Datagram;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);
  TotalLength  = 0;

  if (UsbEthDriver->TotalDatagram == UsbEthDriver->NowDatagram) {
    Status = gBS->HandleProtocol (
                    UsbEthDriver->UsbCdcDataHandle,
                    &gEfiUsbIoProtocolGuid,
                    (VOID **)&UsbIo
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (UsbEthDriver->BulkInEndpoint == 0) {
      GetEndpoint (UsbIo, UsbEthDriver);
    }

    BulkDataLength = USB_NCM_MAX_NTB_SIZE;
    SetMem (UsbEthDriver->BulkBuffer, BulkDataLength, 0);
    UsbEthDriver->NowDatagram   = 0;
    UsbEthDriver->TotalDatagram = 0;

    Status = UsbIo->UsbBulkTransfer (
                      UsbIo,
                      UsbEthDriver->BulkInEndpoint,
                      UsbEthDriver->BulkBuffer,
                      &BulkDataLength,
                      USB_ETHERNET_BULK_TIMEOUT,
                      &TransStatus
                      );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    Nth                         = (USB_NCM_TRANSFER_HEADER_16 *)UsbEthDriver->BulkBuffer;
    Ndp                         = (USB_NCM_DATAGRAM_POINTER_16 *)((UINT8 *)UsbEthDriver->BulkBuffer + Nth->NdpIndex);
    Datagram                    = (USB_NCM_DATA_GRAM *)((UINT8 *)Ndp + sizeof (USB_NCM_DATAGRAM_POINTER_16));
    UsbEthDriver->TotalDatagram = (UINT8)((Ndp->Length - 8) / 4 - 1);

    for (Index = 0; Index < UsbEthDriver->TotalDatagram; Index++) {
      TotalLength += Datagram->DatagramLength;
      Datagram     = (USB_NCM_DATA_GRAM *)((UINT8 *)Datagram + sizeof (USB_NCM_DATA_GRAM));
    }

    if (TotalLength < USB_ETHERNET_FRAME_SIZE) {
      Datagram = (USB_NCM_DATA_GRAM *)((UINT8 *)Ndp + sizeof (USB_NCM_DATAGRAM_POINTER_16));

      TotalLength = 0;
      for (Index = 0; Index < UsbEthDriver->TotalDatagram; Index++) {
        CopyMem ((UINT8 *)Packet + TotalLength, (UINT8 *)UsbEthDriver->BulkBuffer + Datagram->DatagramIndex, Datagram->DatagramLength);
        TotalLength += Datagram->DatagramLength;
        Datagram     = (USB_NCM_DATA_GRAM *)((UINT8 *)Datagram + sizeof (USB_NCM_DATA_GRAM));
      }

      *PacketLength             = TotalLength;
      UsbEthDriver->NowDatagram = UsbEthDriver->TotalDatagram;
    } else {
      UsbEthDriver->NowDatagram++;

      Datagram = (USB_NCM_DATA_GRAM *)((UINT8 *)Ndp + sizeof (USB_NCM_DATAGRAM_POINTER_16));
      CopyMem (Packet, (UINT8 *)UsbEthDriver->BulkBuffer + Datagram->DatagramIndex, Datagram->DatagramLength);
      *PacketLength = Datagram->DatagramLength;
    }

    return Status;
  } else {
    UsbEthDriver->NowDatagram++;

    Nth      = (USB_NCM_TRANSFER_HEADER_16 *)UsbEthDriver->BulkBuffer;
    Ndp      = (USB_NCM_DATAGRAM_POINTER_16 *)((UINT8 *)UsbEthDriver->BulkBuffer + Nth->NdpIndex);
    Datagram = (USB_NCM_DATA_GRAM *)((UINT8 *)Ndp + sizeof (USB_NCM_DATAGRAM_POINTER_16));
    Datagram = (USB_NCM_DATA_GRAM *)((UINT8 *)Datagram + sizeof (USB_NCM_DATA_GRAM) * (UsbEthDriver->NowDatagram - 1));

    CopyMem (Packet, (UINT8 *)UsbEthDriver->BulkBuffer + Datagram->DatagramIndex, Datagram->DatagramLength);
    *PacketLength = Datagram->DatagramLength;

    return EFI_SUCCESS;
  }
}

/**
  This function is used to manage a USB device with the bulk transfer pipe. The endpoint is Bulk out.

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

  @retval EFI_SUCCESS           The bulk transfer has been successfully executed.
  @retval EFI_DEVICE_ERROR      The transfer failed. The transfer status is returned in status.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES  The request could not be submitted due to a lack of resources.
  @retval EFI_TIMEOUT           The control transfer fails due to timeout.

**/
EFI_STATUS
EFIAPI
UsbEthNcmTransmit (
  IN      PXE_CDB                      *Cdb,
  IN      EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN      VOID                         *Packet,
  IN OUT  UINTN                        *PacketLength
  )
{
  EFI_STATUS                   Status;
  USB_ETHERNET_DRIVER          *UsbEthDriver;
  EFI_USB_IO_PROTOCOL          *UsbIo;
  UINT32                       TransStatus;
  USB_NCM_TRANSFER_HEADER_16   *Nth;
  USB_NCM_DATAGRAM_POINTER_16  *Ndp;
  USB_NCM_DATA_GRAM            *Datagram;
  UINT8                        *TotalPacket;
  UINTN                        TotalLength;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

  Status = gBS->HandleProtocol (
                  UsbEthDriver->UsbCdcDataHandle,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **)&UsbIo
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (UsbEthDriver->BulkOutEndpoint == 0) {
    GetEndpoint (UsbIo, UsbEthDriver);
  }

  TotalLength = (UINTN)(USB_NCM_NTH_LENGTH + USB_NCM_NDP_LENGTH + (*PacketLength));

  Status = gBS->AllocatePool (EfiBootServicesData, TotalLength, (VOID **)&TotalPacket);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  SetMem (TotalPacket, TotalLength, 0);

  Nth               = (USB_NCM_TRANSFER_HEADER_16 *)TotalPacket;
  Nth->Signature    = USB_NCM_NTH_SIGN_16;
  Nth->HeaderLength = USB_NCM_NTH_LENGTH;
  Nth->Sequence     = UsbEthDriver->BulkOutSequence++;
  Nth->BlockLength  = (UINT16)TotalLength;
  Nth->NdpIndex     = Nth->HeaderLength;

  Ndp               = (USB_NCM_DATAGRAM_POINTER_16 *)((UINT8 *)TotalPacket + Nth->NdpIndex);
  Ndp->Signature    = USB_NCM_NDP_SIGN_16;
  Ndp->Length       = USB_NCM_NDP_LENGTH;
  Ndp->NextNdpIndex = 0x00;

  Datagram                 = (USB_NCM_DATA_GRAM *)((UINT8 *)Ndp + sizeof (USB_NCM_DATAGRAM_POINTER_16));
  Datagram->DatagramIndex  = Nth->HeaderLength + Ndp->Length;
  Datagram->DatagramLength = (UINT16)*PacketLength;

  CopyMem (TotalPacket + Datagram->DatagramIndex, Packet, *PacketLength);

  *PacketLength = TotalLength;

  Status = UsbIo->UsbBulkTransfer (
                    UsbIo,
                    UsbEthDriver->BulkOutEndpoint,
                    TotalPacket,
                    PacketLength,
                    USB_ETHERNET_BULK_TIMEOUT,
                    &TransStatus
                    );
  FreePool (TotalPacket);
  return Status;
}

/**
  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 == USB_CDC_NETWORK_CONNECTION) {
    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]  Request           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
UsbEthNcmInterrupt (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN BOOLEAN                      IsNewTransfer,
  IN UINTN                        PollingInterval,
  IN EFI_USB_DEVICE_REQUEST       *Request
  )
{
  EFI_STATUS           Status;
  USB_ETHERNET_DRIVER  *UsbEthDriver;
  UINTN                DataLength;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);
  DataLength   = 0;

  if (IsNewTransfer) {
    DataLength = sizeof (EFI_USB_DEVICE_REQUEST) + sizeof (USB_CONNECT_SPEED_CHANGE);
    Status     = UsbEthDriver->UsbIo->UsbAsyncInterruptTransfer (
                                        UsbEthDriver->UsbIo,
                                        UsbEthDriver->InterruptEndpoint,
                                        IsNewTransfer,
                                        PollingInterval,
                                        DataLength,
                                        (EFI_ASYNC_USB_TRANSFER_CALLBACK)InterruptCallback,
                                        Request
                                        );
  } else {
    Status = UsbEthDriver->UsbIo->UsbAsyncInterruptTransfer (
                                    UsbEthDriver->UsbIo,
                                    UsbEthDriver->InterruptEndpoint,
                                    IsNewTransfer,
                                    0,
                                    0,
                                    NULL,
                                    NULL
                                    );
  }

  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_ETHERNET_DRIVER          *UsbEthDriver;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthDescriptor;
  CHAR16                       *Data;
  CHAR16                       *DataPtr;
  CHAR16                       TmpStr[1];
  UINT8                        Index;
  UINT8                        Hi;
  UINT8                        Low;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  Status = UsbEthDriver->UsbIo->UsbGetStringDescriptor (
                                  UsbEthDriver->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;
}

/**
  Get the USB NCM max NTB 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           Get the USB NCM max NTB size successfully.

**/
EFI_STATUS
EFIAPI
UsbEthNcmBulkSize (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT UINTN                        *BulkSize
  )
{
  *BulkSize = USB_NCM_MAX_NTB_SIZE;
  return EFI_SUCCESS;
}

/**
  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_ETHERNET_DRIVER  *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  Status = GetFunctionalDescriptor (UsbEthDriver->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_ETHERNET_DRIVER  *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  Status = GetFunctionalDescriptor (UsbEthDriver->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
GetUsbEthFunDescriptor (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  OUT USB_ETHERNET_FUN_DESCRIPTOR  *UsbEthFunDescriptor
  )
{
  EFI_STATUS           Status;
  USB_ETHERNET_DRIVER  *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  Status = GetFunctionalDescriptor (UsbEthDriver->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
SetUsbEthMcastFilter (
  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_ETHERNET_DRIVER          *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_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       = UsbEthDriver->NumOfInterface;
  Request.Length      = Value * 6;

  return UsbEthDriver->UsbIo->UsbControlTransfer (
                                UsbEthDriver->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
SetUsbEthPowerFilter (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN UINT16                       Value,
  IN UINT16                       Length,
  IN VOID                         *PatternFilter
  )
{
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TransStatus;
  USB_ETHERNET_DRIVER     *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  return UsbEthDriver->UsbIo->UsbControlTransfer (
                                UsbEthDriver->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
GetUsbEthPowerFilter (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN  UINT16                       Value,
  OUT BOOLEAN                      *PatternActive
  )
{
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TransStatus;
  USB_ETHERNET_DRIVER     *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

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

BIT_MAP  gTable[] = {
  { PXE_OPFLAGS_RECEIVE_FILTER_UNICAST,            USB_ETH_PACKET_TYPE_DIRECTED      },
  { PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST,          USB_ETH_PACKET_TYPE_BROADCAST     },
  { PXE_OPFLAGS_RECEIVE_FILTER_FILTERED_MULTICAST, USB_ETH_PACKET_TYPE_MULTICAST     },
  { PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS,        USB_ETH_PACKET_TYPE_PROMISCUOUS   },
  { PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST,      USB_ETH_PACKET_TYPE_ALL_MULTICAST },
};

/**
  Convert value between PXE receive filter and USB ETH packet filter.

  @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; (Index < Count) && (gTable[Index].Src != 0); Index++) {
    if (gTable[Index].Src & Value) {
      *CdcFilter |= gTable[Index].Dst;
    }
  }
}

/**
  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
SetUsbEthPacketFilter (
  IN EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN UINT16                       Value
  )
{
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TransStatus;
  USB_ETHERNET_DRIVER     *UsbEthDriver;
  UINT16                  CommandFilter;

  UsbEthDriver  = USB_ETHERNET_DEV_FROM_THIS (This);
  CommandFilter = 0;

  ConvertFilter (Value, &CommandFilter);

  Request.RequestType = USB_ETHERNET_SET_REQ_TYPE;
  Request.Request     = SET_ETH_PACKET_FILTER_REQ;
  Request.Value       = CommandFilter;
  Request.Index       = UsbEthDriver->NumOfInterface;
  Request.Length      = USB_ETH_PACKET_FILTER_LENGTH;

  return UsbEthDriver->UsbIo->UsbControlTransfer (
                                UsbEthDriver->UsbIo,
                                &Request,
                                EfiUsbNoData,
                                USB_ETHERNET_TRANSFER_TIMEOUT,
                                NULL,
                                USB_ETH_PACKET_FILTER_LENGTH,
                                &TransStatus
                                );
}

/**
  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
GetUsbEthStatistic (
  IN  EDKII_USB_ETHERNET_PROTOCOL  *This,
  IN  UINT16                       FeatureSelector,
  OUT VOID                         *Statistic
  )
{
  EFI_STATUS                   Status;
  EFI_USB_DEVICE_REQUEST       Request;
  UINT32                       TransStatus;
  USB_ETHERNET_FUN_DESCRIPTOR  UsbEthFunDescriptor;
  USB_ETHERNET_DRIVER          *UsbEthDriver;

  UsbEthDriver = USB_ETHERNET_DEV_FROM_THIS (This);

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

  if (UsbEthFunDescriptor.EthernetStatistics == 0) {
    return EFI_UNSUPPORTED;
  }

  Request.RequestType = USB_ETHERNET_GET_REQ_TYPE;
  Request.Request     = GET_ETH_STATISTIC_REQ;
  Request.Value       = FeatureSelector;
  Request.Index       = UsbEthDriver->NumOfInterface;
  Request.Length      = USB_ETH_STATISTIC;

  return UsbEthDriver->UsbIo->UsbControlTransfer (
                                UsbEthDriver->UsbIo,
                                &Request,
                                EfiUsbDataIn,
                                USB_ETHERNET_TRANSFER_TIMEOUT,
                                Statistic,
                                USB_ETH_STATISTIC,
                                &TransStatus
                                );
}
