/** @file
  Implementation of driver entry point and driver binding protocol.

Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "MnpDriver.h"
#include "MnpImpl.h"
#include "MnpVlan.h"

EFI_DRIVER_BINDING_PROTOCOL  gMnpDriverBinding = {
  MnpDriverBindingSupported,
  MnpDriverBindingStart,
  MnpDriverBindingStop,
  0xa,
  NULL,
  NULL
};

/**
  Callback function which provided by user to remove one node in NetDestroyLinkList process.

  @param[in]    Entry           The entry to be removed.
  @param[in]    Context         Pointer to the callback context corresponds to the Context in NetDestroyLinkList.

  @retval EFI_SUCCESS           The entry has been removed successfully.
  @retval Others                Fail to remove the entry.

**/
EFI_STATUS
EFIAPI
MnpDestroyServiceDataEntry (
  IN LIST_ENTRY  *Entry,
  IN VOID        *Context
  )
{
  MNP_SERVICE_DATA  *MnpServiceData;

  MnpServiceData = MNP_SERVICE_DATA_FROM_LINK (Entry);
  return MnpDestroyServiceData (MnpServiceData);
}

/**
  Callback function which provided by user to remove one node in NetDestroyLinkList process.

  @param[in]    Entry           The entry to be removed.
  @param[in]    Context         Pointer to the callback context corresponds to the Context in NetDestroyLinkList.

  @retval EFI_SUCCESS           The entry has been removed successfully.
  @retval Others                Fail to remove the entry.

**/
EFI_STATUS
EFIAPI
MnpDestroyServiceChildEntry (
  IN LIST_ENTRY  *Entry,
  IN VOID        *Context
  )
{
  MNP_SERVICE_DATA  *MnpServiceData;

  MnpServiceData = MNP_SERVICE_DATA_FROM_LINK (Entry);
  return MnpDestroyServiceChild (MnpServiceData);
}

/**
  Test to see if this driver supports ControllerHandle. This service
  is called by the EFI boot service ConnectController(). In
  order to make drivers as small as possible, there are a few calling
  restrictions for this service. ConnectController() must
  follow these calling restrictions. If any other agent wishes to call
  Supported() it must also follow these calling restrictions.

  @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 Others                   This driver does not support this device.

**/
EFI_STATUS
EFIAPI
MnpDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS                   Status;
  EFI_SIMPLE_NETWORK_PROTOCOL  *Snp;

  //
  // Test to open the Simple Network protocol BY_DRIVER.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiSimpleNetworkProtocolGuid,
                  (VOID **)&Snp,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close the opened SNP protocol.
  //
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiSimpleNetworkProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

  return EFI_SUCCESS;
}

/**
  Start this driver on ControllerHandle. This service is called by the
  EFI boot service ConnectController(). In order to make drivers as small
  as possible, there are a few calling restrictions for this service.
  ConnectController() must follow these calling restrictions. If any other
  agent wishes to call Start() it must also follow these calling restrictions.

  @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_ALREADY_STARTED   This driver is already running on ControllerHandle.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory for Mnp Service Data.
  @retval Others                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
MnpDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS        Status;
  MNP_SERVICE_DATA  *MnpServiceData;
  MNP_DEVICE_DATA   *MnpDeviceData;
  LIST_ENTRY        *Entry;
  VLAN_TCI          *VlanVariable;
  UINTN             NumberOfVlan;
  UINTN             Index;

  VlanVariable = NULL;

  //
  // Initialize the Mnp Device Data
  //
  MnpDeviceData = AllocateZeroPool (sizeof (MNP_DEVICE_DATA));
  if (MnpDeviceData == NULL) {
    DEBUG ((DEBUG_ERROR, "MnpDriverBindingStart(): Failed to allocate the Mnp Device Data.\n"));

    return EFI_OUT_OF_RESOURCES;
  }

  Status = MnpInitializeDeviceData (MnpDeviceData, This->DriverBindingHandle, ControllerHandle);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "MnpDriverBindingStart: MnpInitializeDeviceData failed, %r.\n", Status));

    FreePool (MnpDeviceData);
    return Status;
  }

  //
  // Check whether NIC driver has already produced VlanConfig protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiVlanConfigProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (!EFI_ERROR (Status)) {
    //
    // NIC hardware already implement VLAN,
    // no need to provide software VLAN implementation in MNP driver
    //
    MnpDeviceData->NumberOfVlan = 0;
    ZeroMem (&MnpDeviceData->VlanConfig, sizeof (EFI_VLAN_CONFIG_PROTOCOL));
    MnpServiceData = MnpCreateServiceData (MnpDeviceData, 0, 0);
    Status         = (MnpServiceData != NULL) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  //
  // Install VLAN Config Protocol
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ControllerHandle,
                  &gEfiVlanConfigProtocolGuid,
                  &MnpDeviceData->VlanConfig,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  //
  // Get current VLAN configuration from EFI Variable
  //
  NumberOfVlan = 0;
  Status       = MnpGetVlanVariable (MnpDeviceData, &NumberOfVlan, &VlanVariable);
  if (EFI_ERROR (Status)) {
    //
    // No VLAN is set, create a default MNP service data for untagged frame
    //
    MnpDeviceData->NumberOfVlan = 0;
    MnpServiceData              = MnpCreateServiceData (MnpDeviceData, 0, 0);
    Status                      = (MnpServiceData != NULL) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  //
  // Create MNP service data for each VLAN
  //
  MnpDeviceData->NumberOfVlan = NumberOfVlan;
  for (Index = 0; Index < NumberOfVlan; Index++) {
    MnpServiceData = MnpCreateServiceData (
                       MnpDeviceData,
                       VlanVariable[Index].Bits.Vid,
                       (UINT8)VlanVariable[Index].Bits.Priority
                       );

    if (MnpServiceData == NULL) {
      Status = EFI_OUT_OF_RESOURCES;

      goto Exit;
    }
  }

Exit:
  if (VlanVariable != NULL) {
    FreePool (VlanVariable);
  }

  if (EFI_ERROR (Status)) {
    //
    // Destroy all MNP service data
    //
    while (!IsListEmpty (&MnpDeviceData->ServiceList)) {
      Entry          = GetFirstNode (&MnpDeviceData->ServiceList);
      MnpServiceData = MNP_SERVICE_DATA_FROM_LINK (Entry);
      MnpDestroyServiceData (MnpServiceData);
    }

    //
    // Uninstall the VLAN Config Protocol if any
    //
    if (MnpDeviceData->VlanConfig.Set != NULL) {
      gBS->UninstallMultipleProtocolInterfaces (
             MnpDeviceData->ControllerHandle,
             &gEfiVlanConfigProtocolGuid,
             &MnpDeviceData->VlanConfig,
             NULL
             );
    }

    //
    // Destroy Mnp Device Data
    //
    MnpDestroyDeviceData (MnpDeviceData, This->DriverBindingHandle);
    FreePool (MnpDeviceData);
  }

  return Status;
}

/**
  Stop this driver on ControllerHandle. This service is called by the
  EFI boot service DisconnectController(). In order to make drivers as
  small as possible, there are a few calling restrictions for this service.
  DisconnectController() must follow these calling restrictions. If any other
  agent wishes to call Stop() it must also follow these calling restrictions.

  @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 EFI_DEVICE_ERROR       The device could not be stopped due to a device error.

**/
EFI_STATUS
EFIAPI
MnpDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer OPTIONAL
  )
{
  EFI_STATUS                    Status;
  EFI_SERVICE_BINDING_PROTOCOL  *ServiceBinding;
  EFI_VLAN_CONFIG_PROTOCOL      *VlanConfig;
  MNP_DEVICE_DATA               *MnpDeviceData;
  MNP_SERVICE_DATA              *MnpServiceData;
  LIST_ENTRY                    *List;
  UINTN                         ListLength;

  //
  // Try to retrieve MNP service binding protocol from the ControllerHandle
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiManagedNetworkServiceBindingProtocolGuid,
                  (VOID **)&ServiceBinding,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    //
    // Retrieve VLAN Config Protocol from the ControllerHandle
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiVlanConfigProtocolGuid,
                    (VOID **)&VlanConfig,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "MnpDriverBindingStop: try to stop unknown Controller.\n"));
      return EFI_DEVICE_ERROR;
    }

    MnpDeviceData = MNP_DEVICE_DATA_FROM_THIS (VlanConfig);
  } else {
    MnpServiceData = MNP_SERVICE_DATA_FROM_THIS (ServiceBinding);
    MnpDeviceData  = MnpServiceData->MnpDeviceData;
  }

  if (NumberOfChildren == 0) {
    //
    // Destroy all MNP service data
    //
    List   = &MnpDeviceData->ServiceList;
    Status = NetDestroyLinkList (
               List,
               MnpDestroyServiceDataEntry,
               NULL,
               &ListLength
               );
    if (EFI_ERROR (Status) || (ListLength != 0)) {
      return EFI_DEVICE_ERROR;
    }

    //
    // Uninstall the VLAN Config Protocol if any
    //
    if (MnpDeviceData->VlanConfig.Set != NULL) {
      gBS->UninstallMultipleProtocolInterfaces (
             MnpDeviceData->ControllerHandle,
             &gEfiVlanConfigProtocolGuid,
             &MnpDeviceData->VlanConfig,
             NULL
             );
    }

    //
    // Destroy Mnp Device Data
    //
    MnpDestroyDeviceData (MnpDeviceData, This->DriverBindingHandle);
    FreePool (MnpDeviceData);

    if (gMnpControllerNameTable != NULL) {
      FreeUnicodeStringTable (gMnpControllerNameTable);
      gMnpControllerNameTable = NULL;
    }

    return EFI_SUCCESS;
  }

  //
  // Stop all MNP child
  //
  List   = &MnpDeviceData->ServiceList;
  Status = NetDestroyLinkList (
             List,
             MnpDestroyServiceChildEntry,
             NULL,
             &ListLength
             );
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Creates a child handle with a set of I/O services.

  @param[in]       This              Protocol instance pointer.
  @param[in, out]  ChildHandle       Pointer to the handle of the child to create. If
                                     it is NULL, then a new handle is created. If
                                     it is not NULL, then the I/O services are added
                                     to the existing child handle.

  @retval EFI_SUCCESS                The protocol was added to ChildHandle.
  @retval EFI_INVALID_PARAMETER      ChildHandle is NULL.
  @retval EFI_OUT_OF_RESOURCES       There are not enough resources available to
                                     create the child.
  @retval Others                     The child handle was not created.

**/
EFI_STATUS
EFIAPI
MnpServiceBindingCreateChild (
  IN     EFI_SERVICE_BINDING_PROTOCOL  *This,
  IN OUT EFI_HANDLE                    *ChildHandle
  )
{
  EFI_STATUS         Status;
  MNP_SERVICE_DATA   *MnpServiceData;
  MNP_INSTANCE_DATA  *Instance;
  VOID               *MnpSb;
  EFI_TPL            OldTpl;

  if ((This == NULL) || (ChildHandle == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  MnpServiceData = MNP_SERVICE_DATA_FROM_THIS (This);

  //
  // Allocate buffer for the new instance.
  //
  Instance = AllocateZeroPool (sizeof (MNP_INSTANCE_DATA));
  if (Instance == NULL) {
    DEBUG ((DEBUG_ERROR, "MnpServiceBindingCreateChild: Failed to allocate memory for the new instance.\n"));

    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Init the instance data.
  //
  MnpInitializeInstanceData (MnpServiceData, Instance);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  ChildHandle,
                  &gEfiManagedNetworkProtocolGuid,
                  &Instance->ManagedNetwork,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    DEBUG (
      (DEBUG_ERROR,
       "MnpServiceBindingCreateChild: Failed to install the MNP protocol, %r.\n",
       Status)
      );

    goto ErrorExit;
  }

  //
  // Save the instance's childhandle.
  //
  Instance->Handle = *ChildHandle;

  Status = gBS->OpenProtocol (
                  MnpServiceData->ServiceHandle,
                  &gEfiManagedNetworkServiceBindingProtocolGuid,
                  (VOID **)&MnpSb,
                  gMnpDriverBinding.DriverBindingHandle,
                  Instance->Handle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  //
  // Add the child instance into ChildrenList.
  //
  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  InsertTailList (&MnpServiceData->ChildrenList, &Instance->InstEntry);
  MnpServiceData->ChildrenNumber++;

  gBS->RestoreTPL (OldTpl);

ErrorExit:

  if (EFI_ERROR (Status)) {
    if (Instance->Handle != NULL) {
      gBS->UninstallMultipleProtocolInterfaces (
             Instance->Handle,
             &gEfiManagedNetworkProtocolGuid,
             &Instance->ManagedNetwork,
             NULL
             );
    }

    FreePool (Instance);
  }

  return Status;
}

/**
  Destroys a child handle with a set of I/O services.

  The DestroyChild() function does the opposite of CreateChild(). It removes a
  protocol that was installed by CreateChild() from ChildHandle. If the removed
  protocol is the last protocol on ChildHandle, then ChildHandle is destroyed.

  @param[in]  This               Pointer to the EFI_SERVICE_BINDING_PROTOCOL
                                 instance.
  @param[in]  ChildHandle        Handle of the child to destroy.

  @retval EFI_SUCCESS            The protocol was removed from ChildHandle.
  @retval EFI_UNSUPPORTED        ChildHandle does not support the protocol that
                                 is being removed.
  @retval EFI_INVALID_PARAMETER  ChildHandle is NULL.
  @retval EFI_ACCESS_DENIED      The protocol could not be removed from the
                                 ChildHandle because its services are being
                                 used.
  @retval Others                 The child handle was not destroyed.

**/
EFI_STATUS
EFIAPI
MnpServiceBindingDestroyChild (
  IN EFI_SERVICE_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                    ChildHandle
  )
{
  EFI_STATUS                    Status;
  MNP_SERVICE_DATA              *MnpServiceData;
  EFI_MANAGED_NETWORK_PROTOCOL  *ManagedNetwork;
  MNP_INSTANCE_DATA             *Instance;
  EFI_TPL                       OldTpl;

  if ((This == NULL) || (ChildHandle == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  MnpServiceData = MNP_SERVICE_DATA_FROM_THIS (This);

  //
  // Try to retrieve ManagedNetwork Protocol from ChildHandle.
  //
  Status = gBS->OpenProtocol (
                  ChildHandle,
                  &gEfiManagedNetworkProtocolGuid,
                  (VOID **)&ManagedNetwork,
                  gMnpDriverBinding.DriverBindingHandle,
                  ChildHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  Instance = MNP_INSTANCE_DATA_FROM_THIS (ManagedNetwork);

  //
  // MnpServiceBindingDestroyChild may be called twice: first called by
  // MnpServiceBindingStop, second called by uninstalling the MNP protocol
  // in this ChildHandle. Use destroyed to make sure the resource clean code
  // will only excecute once.
  //
  if (Instance->Destroyed) {
    return EFI_SUCCESS;
  }

  Instance->Destroyed = TRUE;

  //
  // Close the Simple Network protocol.
  //
  gBS->CloseProtocol (
         MnpServiceData->ServiceHandle,
         &gEfiManagedNetworkServiceBindingProtocolGuid,
         MnpServiceData->MnpDeviceData->ImageHandle,
         ChildHandle
         );

  //
  // Uninstall the ManagedNetwork protocol.
  //
  Status = gBS->UninstallMultipleProtocolInterfaces (
                  ChildHandle,
                  &gEfiManagedNetworkProtocolGuid,
                  &Instance->ManagedNetwork,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    DEBUG (
      (DEBUG_ERROR,
       "MnpServiceBindingDestroyChild: Failed to uninstall the ManagedNetwork protocol, %r.\n",
       Status)
      );

    Instance->Destroyed = FALSE;
    return Status;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  //
  // Reset the configuration.
  //
  ManagedNetwork->Configure (ManagedNetwork, NULL);

  //
  // Try to flush the RcvdPacketQueue.
  //
  MnpFlushRcvdDataQueue (Instance);

  //
  // Clean the RxTokenMap.
  //
  NetMapClean (&Instance->RxTokenMap);

  //
  // Remove this instance from the ChildrenList.
  //
  RemoveEntryList (&Instance->InstEntry);
  MnpServiceData->ChildrenNumber--;

  gBS->RestoreTPL (OldTpl);

  FreePool (Instance);

  return Status;
}

/**
  The entry point for Mnp driver which installs the driver binding and component
  name protocol on its ImageHandle.

  @param[in]  ImageHandle  The image handle of the driver.
  @param[in]  SystemTable  The system table.

  @retval EFI_SUCCESS      The driver binding and component name protocols are
                           successfully installed.
  @retval Others           Other errors as indicated.

**/
EFI_STATUS
EFIAPI
MnpDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gMnpDriverBinding,
           ImageHandle,
           &gMnpComponentName,
           &gMnpComponentName2
           );
}
