/** @file
  This file contains code for USB network binding driver

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

#include "DriverBinding.h"

PXE_SW_UNDI  *gPxe = NULL;
NIC_DEVICE   *gLanDeviceList[MAX_LAN_INTERFACE];
UINT32       gRateLimitingCredit;
UINT32       gRateLimitingPollTimer;
BOOLEAN      gRateLimitingEnable;

EFI_DRIVER_BINDING_PROTOCOL  gNetworkCommonDriverBinding = {
  NetworkCommonSupported,
  NetworkCommonDriverStart,
  NetworkCommonDriverStop,
  NETWORK_COMMON_DRIVER_VERSION,
  NULL,
  NULL
};

/**
  Create MAC Device Path

  @param[in, out] Dev             A pointer to the EFI_DEVICE_PATH_PROTOCOL instance.
  @param[in]      BaseDev         A pointer to the EFI_DEVICE_PATH_PROTOCOL instance.
  @param[in]      Nic             A pointer to the Network interface controller data.

  @retval EFI_OUT_OF_RESOURCES    The device path could not be created successfully due to a lack of resources.
  @retval EFI_SUCCESS             MAC device path created successfully.

**/
EFI_STATUS
CreateMacDevicePath (
  IN OUT  EFI_DEVICE_PATH_PROTOCOL  **Dev,
  IN      EFI_DEVICE_PATH_PROTOCOL  *BaseDev,
  IN      NIC_DATA                  *Nic
  )
{
  EFI_STATUS                Status;
  MAC_ADDR_DEVICE_PATH      MacAddrNode;
  EFI_DEVICE_PATH_PROTOCOL  *EndNode;
  UINT8                     *DevicePath;
  UINT16                    TotalLength;
  UINT16                    BaseLength;

  ZeroMem (&MacAddrNode, sizeof (MAC_ADDR_DEVICE_PATH));
  CopyMem (&MacAddrNode.MacAddress, &Nic->MacAddr, sizeof (EFI_MAC_ADDRESS));

  MacAddrNode.Header.Type      = MESSAGING_DEVICE_PATH;
  MacAddrNode.Header.SubType   = MSG_MAC_ADDR_DP;
  MacAddrNode.Header.Length[0] = (UINT8)sizeof (MacAddrNode);
  MacAddrNode.Header.Length[1] = 0;

  EndNode = BaseDev;

  while (!IsDevicePathEnd (EndNode)) {
    EndNode = NextDevicePathNode (EndNode);
  }

  BaseLength  = (UINT16)((UINTN)(EndNode) - (UINTN)(BaseDev));
  TotalLength = (UINT16)(BaseLength + sizeof (MacAddrNode) + sizeof (EFI_DEVICE_PATH_PROTOCOL));

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

  *Dev = (EFI_DEVICE_PATH_PROTOCOL *)DevicePath;
  CopyMem (DevicePath, (CHAR8 *)BaseDev, BaseLength);
  DevicePath += BaseLength;
  CopyMem (DevicePath, (CHAR8 *)&MacAddrNode, sizeof (MacAddrNode));
  DevicePath += sizeof (MacAddrNode);
  CopyMem (DevicePath, (CHAR8 *)EndNode, sizeof (EFI_DEVICE_PATH_PROTOCOL));

  return EFI_SUCCESS;
}

/**
  Network Common Driver Binding Support.

  @param[in]  This                    Protocol instance pointer.
  @param[in]  ControllerHandle        Handle of device to test.
  @param[in]  RemainingDevicePath     Optional parameter use to pick a specific child
                                      device to start.

  @retval EFI_SUCCESS                 This driver supports this device.
  @retval EFI_ALREADY_STARTED         This driver is already running on this device.
  @retval other                       This driver does not support this device.

**/
EFI_STATUS
EFIAPI
NetworkCommonSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS                   Status;
  EDKII_USB_ETHERNET_PROTOCOL  *UsbEth;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEdkIIUsbEthProtocolGuid,
                  (VOID **)&UsbEth,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  gBS->CloseProtocol (
         ControllerHandle,
         &gEdkIIUsbEthProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );
  return Status;
}

/**
  Network Common Driver Binding Start.

  @param[in]  This                    Protocol instance pointer.
  @param[in]  ControllerHandle        Handle of device to bind driver to.
  @param[in]  RemainingDevicePath     Optional parameter use to pick a specific child
                                      device to start.

  @retval EFI_SUCCESS                 This driver is added to ControllerHandle
  @retval EFI_DEVICE_ERROR            This driver could not be started due to a device error
  @retval EFI_OUT_OF_RESOURCES        The driver could not install successfully due to a lack of resources.
  @retval other                       This driver does not support this device

**/
EFI_STATUS
EFIAPI
NetworkCommonDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS                   Status;
  EFI_DEVICE_PATH_PROTOCOL     *UsbEthPath;
  EDKII_USB_ETHERNET_PROTOCOL  *UsbEth;
  EFI_MAC_ADDRESS              MacAddress;
  UINTN                        BulkDataSize;
  NIC_DEVICE                   *NicDevice;
  UINT8                        *TmpPxePointer;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEdkIIUsbEthProtocolGuid,
                  (VOID **)&UsbEth,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&UsbEthPath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return Status;
  }

  ZeroMem (&MacAddress, sizeof (EFI_MAC_ADDRESS));

  Status = UsbEth->UsbEthMacAddress (UsbEth, &MacAddress);
  ASSERT_EFI_ERROR (Status);
  Status = UsbEth->UsbEthMaxBulkSize (UsbEth, &BulkDataSize);

  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return Status;
  }

  NicDevice = AllocateZeroPool (sizeof (NIC_DEVICE) + BulkDataSize + 4096);
  if (!NicDevice) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return EFI_OUT_OF_RESOURCES;
  }

  // for alignment adjustment
  if (gPxe == NULL) {
    TmpPxePointer = NULL;
    TmpPxePointer = AllocateZeroPool (sizeof (PXE_SW_UNDI) + 16);
    if (!TmpPxePointer) {
      if (NicDevice != NULL) {
        FreePool (NicDevice);
      }

      gBS->CloseProtocol (
             ControllerHandle,
             &gEfiDevicePathProtocolGuid,
             This->DriverBindingHandle,
             ControllerHandle
             );
      gBS->CloseProtocol (
             ControllerHandle,
             &gEdkIIUsbEthProtocolGuid,
             This->DriverBindingHandle,
             ControllerHandle
             );

      return EFI_OUT_OF_RESOURCES;
    } else {
      // check for paragraph alignment here
      if (((UINTN)TmpPxePointer & 0x0F) != 0) {
        gPxe = (PXE_SW_UNDI *)(TmpPxePointer + 8);
      } else {
        gPxe = (PXE_SW_UNDI *)TmpPxePointer;
      }

      if (gPxe == NULL) {
        if (NicDevice != NULL) {
          FreePool (NicDevice);
        }

        gBS->CloseProtocol (
               ControllerHandle,
               &gEfiDevicePathProtocolGuid,
               This->DriverBindingHandle,
               ControllerHandle
               );
        gBS->CloseProtocol (
               ControllerHandle,
               &gEdkIIUsbEthProtocolGuid,
               This->DriverBindingHandle,
               ControllerHandle
               );
        return EFI_OUT_OF_RESOURCES;
      }

      PxeStructInit (gPxe);
    }
  }

  NicDevice->NiiProtocol.Id    = (UINT64)(UINTN)(gPxe);
  NicDevice->NiiProtocol.IfNum = gPxe->IFcnt | gPxe->IFcntExt << 8;

  UpdateNicNum (&NicDevice->NicInfo, gPxe);

  NicDevice->NicInfo.Signature = NIC_DATA_SIGNATURE;

  NicDevice->NicInfo.UsbEth         = UsbEth;
  NicDevice->NicInfo.MaxSegmentSize = (UINT16)BulkDataSize;
  NicDevice->NicInfo.CableDetect    = 0;
  NicDevice->ReceiveBuffer          = ALIGN_POINTER ((VOID *)NicDevice, 4096);

  CopyMem ((CHAR8 *)&(NicDevice->NicInfo.MacAddr), (CHAR8 *)&MacAddress, sizeof (MacAddress));

  NicDevice->NicInfo.TxBufferCount = 0;

  if (NicDevice->NiiProtocol.IfNum < MAX_LAN_INTERFACE) {
    gLanDeviceList[NicDevice->NiiProtocol.IfNum] = NicDevice;
  } else {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );

    if (TmpPxePointer != NULL) {
      FreePool (TmpPxePointer);
    }

    if (NicDevice != NULL) {
      FreePool (NicDevice);
    }

    return EFI_DEVICE_ERROR;
  }

  Status = CreateMacDevicePath (
             &NicDevice->DevPath,
             UsbEthPath,
             &NicDevice->NicInfo
             );

  if (EFI_ERROR (Status)) {
    UpdateNicNum (NULL, gPxe);
    if (TmpPxePointer != NULL) {
      FreePool (TmpPxePointer);
    }
  }

  NicDevice->Signature                 = UNDI_DEV_SIGNATURE;
  NicDevice->NiiProtocol.Revision      = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION;
  NicDevice->NiiProtocol.Type          = EfiNetworkInterfaceUndi;
  NicDevice->NiiProtocol.MajorVer      = PXE_ROMID_MAJORVER;
  NicDevice->NiiProtocol.MinorVer      = PXE_ROMID_MINORVER;
  NicDevice->NiiProtocol.ImageSize     = 0;
  NicDevice->NiiProtocol.ImageAddr     = 0;
  NicDevice->NiiProtocol.Ipv6Supported = TRUE;

  NicDevice->NiiProtocol.StringId[0] = 'U';
  NicDevice->NiiProtocol.StringId[1] = 'N';
  NicDevice->NiiProtocol.StringId[2] = 'D';
  NicDevice->NiiProtocol.StringId[3] = 'I';
  NicDevice->DeviceHandle            = NULL;

  NicDevice->NicInfo.RateLimitingEnable      = gRateLimitingEnable;
  NicDevice->NicInfo.RateLimitingCreditCount = 0;
  NicDevice->NicInfo.RateLimitingCredit      = gRateLimitingCredit;
  NicDevice->NicInfo.RateLimitingPollTimer   = gRateLimitingPollTimer;
  NicDevice->NicInfo.RateLimiter             = NULL;

  ZeroMem (&NicDevice->NicInfo.Request, sizeof (EFI_USB_DEVICE_REQUEST));

  Status = UsbEth->UsbEthInterrupt (UsbEth, TRUE, NETWORK_COMMON_POLLING_INTERVAL, &NicDevice->NicInfo.Request);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &NicDevice->DeviceHandle,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  &NicDevice->NiiProtocol,
                  &gEfiDevicePathProtocolGuid,
                  NicDevice->DevPath,
                  NULL
                  );

  if (EFI_ERROR (Status)) {
    if (NicDevice->NiiProtocol.IfNum < MAX_LAN_INTERFACE) {
      gLanDeviceList[NicDevice->NiiProtocol.IfNum] = NULL;
    }

    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );

    if (TmpPxePointer != NULL) {
      FreePool (TmpPxePointer);
    }

    if (NicDevice->DevPath != NULL) {
      FreePool (NicDevice->DevPath);
    }

    if (NicDevice != NULL) {
      FreePool (NicDevice);
    }

    return EFI_DEVICE_ERROR;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEdkIIUsbEthProtocolGuid,
                  (VOID **)&UsbEth,
                  This->DriverBindingHandle,
                  NicDevice->DeviceHandle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );

  return Status;
}

/**
  Network Common Driver Binding Stop.

  @param[in]  This                  Protocol instance pointer.
  @param[in]  ControllerHandle      Handle of device to stop driver on
  @param[in]  NumberOfChildren      Number of Handles in ChildHandleBuffer. If number of
                                    children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer     List of Child Handles to Stop.

  @retval EFI_SUCCESS               This driver is removed ControllerHandle
  @retval other                     This driver was not removed from this device

**/
EFI_STATUS
EFIAPI
NetworkCommonDriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  UINTN                        NumberOfChildren,
  IN  EFI_HANDLE                   *ChildHandleBuffer
  )
{
  EFI_STATUS                                 Status;
  BOOLEAN                                    AllChildrenStopped;
  UINTN                                      Index;
  EDKII_USB_ETHERNET_PROTOCOL                *UsbEth;
  NIC_DEVICE                                 *NicDevice;
  EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL  *NiiProtocol;

  if (NumberOfChildren == 0) {
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                    (VOID **)&NiiProtocol,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );

    if (EFI_ERROR (Status)) {
      gBS->CloseProtocol (
             ControllerHandle,
             &gEfiDevicePathProtocolGuid,
             This->DriverBindingHandle,
             ControllerHandle
             );
      gBS->CloseProtocol (
             ControllerHandle,
             &gEdkIIUsbEthProtocolGuid,
             This->DriverBindingHandle,
             ControllerHandle
             );
      return EFI_SUCCESS;
    }

    NicDevice = UNDI_DEV_FROM_THIS (NiiProtocol);
    Status    = gBS->UninstallMultipleProtocolInterfaces (
                       ControllerHandle,
                       &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                       &NicDevice->NiiProtocol,
                       &gEfiDevicePathProtocolGuid,
                       NicDevice->DevPath,
                       NULL
                       );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    FreePool (NicDevice->DevPath);
    FreePool (NicDevice);

    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {
    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                    (VOID **)&NiiProtocol,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
      continue;
    }

    NicDevice = UNDI_DEV_FROM_THIS (NiiProtocol);

    gBS->CloseProtocol (
           ControllerHandle,
           &gEdkIIUsbEthProtocolGuid,
           This->DriverBindingHandle,
           ChildHandleBuffer[Index]
           );

    Status = gBS->UninstallMultipleProtocolInterfaces (
                    ChildHandleBuffer[Index],
                    &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                    &NicDevice->NiiProtocol,
                    &gEfiDevicePathProtocolGuid,
                    NicDevice->DevPath,
                    NULL
                    );
    if (EFI_ERROR (Status)) {
      Status = gBS->OpenProtocol (
                      ControllerHandle,
                      &gEdkIIUsbEthProtocolGuid,
                      (VOID **)&UsbEth,
                      This->DriverBindingHandle,
                      ChildHandleBuffer[Index],
                      EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                      );
    } else {
      FreePool (NicDevice->DevPath);
      FreePool (NicDevice);
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }

  return Status;
}

/**
  Entrypoint of Network Common Driver.

  This function is the entrypoint of Network Common Driver. It installs Driver Binding
  Protocols together with Component Name Protocols.

  @param[in]  ImageHandle       The firmware allocated handle for the EFI image.
  @param[in]  SystemTable       A pointer to the EFI System Table.

  @retval EFI_SUCCESS           The entry point is executed successfully.

**/
EFI_STATUS
EFIAPI
NetworkCommonEntry (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  gNetworkCommonDriverBinding.DriverBindingHandle = ImageHandle;
  gNetworkCommonDriverBinding.ImageHandle         = ImageHandle;
  gRateLimitingEnable                             = PcdGetBool (PcdEnableUsbNetworkRateLimiting);
  gRateLimitingCredit                             = PcdGet32 (PcdUsbNetworkRateLimitingCredit);
  gRateLimitingPollTimer                          = PcdGet32 (PcdUsbNetworkRateLimitingFactor);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &gNetworkCommonDriverBinding.DriverBindingHandle,
                  &gEfiDriverBindingProtocolGuid,
                  &gNetworkCommonDriverBinding,
                  &gEfiComponentName2ProtocolGuid,
                  &gNetworkCommonComponentName2,
                  NULL
                  );
  return Status;
}
