/**  <at> file
  Implementation of driver entry point and driver binding protocol.

Copyright (c) 2004 - 2010, Intel Corporation. All rights reserved.<BR>
Copyright (c) 2011 - 2016, ARM Limited. All rights reserved.

This program and the accompanying materials are licensed
and made available under the terms and conditions of the BSD License which
accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <Library/NetLib.h>
#include <Library/DevicePathLib.h>
#include "MarvellYukon.h"
#include "if_msk.h"

STATIC LIST_ENTRY MarvellYukonDrvDataHead;

/**
  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.

   <at> param  This                   Protocol instance pointer.
   <at> param  ControllerHandle       Handle of device to test.
   <at> param  RemainingDevicePath    Optional parameter use to pick a specific child
                                                             device to start.

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

**/
EFI_STATUS
EFIAPI
MarvellYukonDriverSupported (
    IN EFI_DRIVER_BINDING_PROTOCOL    *This,
    IN EFI_HANDLE                     Controller,
    IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
    )
{
  EFI_STATUS              Status;
  EFI_PCI_IO_PROTOCOL     *PciIo;

  //
  // Test that the PCI IO Protocol is attached to the controller handle and no other driver is consuming it
  //
  Status = gBS->OpenProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        (VOID **) &PciIo,
        This->DriverBindingHandle,
        Controller,
        EFI_OPEN_PROTOCOL_BY_DRIVER
        );

  if (!EFI_ERROR (Status)) {
    //
    // Test whether the controller is on a supported NIC
    //
    Status = mskc_probe (PciIo);
    if (EFI_ERROR (Status)) {
      Status = EFI_UNSUPPORTED;
    } else {
      DEBUG ((EFI_D_NET, "Marvell Yukon: MarvellYukonDriverSupported: Supported Controller = %p\n", Controller));
    }

    gBS->CloseProtocol (
          Controller,
          &gEfiPciIoProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );
  }

  return Status;
}

/**
  Start this driver on Controller by opening PciIo and DevicePath protocols.
  Initialize PXE structures, create a copy of the Controller Device Path with the
  NIC's MAC address appended to it, install the NetworkInterfaceIdentifier protocol
  on the newly created Device Path.

  @param [in] pThis                   Protocol instance pointer.
  @param [in] Controller              Handle of device to work with.
  @param [in] pRemainingDevicePath    Not used, always produce all possible children.

  @retval EFI_SUCCESS                 This driver is added to Controller.
  @retval other                       This driver does not support this device.

**/
EFI_STATUS
EFIAPI
MarvellYukonDriverStart (
    IN EFI_DRIVER_BINDING_PROTOCOL * pThis,
    IN EFI_HANDLE Controller,
    IN EFI_DEVICE_PATH_PROTOCOL * pRemainingDevicePath
    )
{

  EFI_STATUS                       Status;
  EFI_DEVICE_PATH_PROTOCOL         *ParentDevicePath;
  MAC_ADDR_DEVICE_PATH             MacDeviceNode;
  VOID                            *ChildPciIo;
  YUKON_DRIVER                    *YukonDriver;
  struct msk_softc                *ScData;
  EFI_PCI_IO_PROTOCOL             *PciIo;
  UINTN                            Port;

  Status = gBS->OpenProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        (VOID **) &PciIo,
        pThis->DriverBindingHandle,
        Controller,
        EFI_OPEN_PROTOCOL_BY_DRIVER
        );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "Marvell Yukon: OpenProtocol: EFI_PCI_IO_PROTOCOL ERROR Status = %r\n", Status));
    gBS->FreePool (YukonDriver);
    return Status;
  }

  //
  // Initialize Marvell Yukon controller
  // Get number of ports and MAC address for each port
  //
  Status = mskc_attach (PciIo, &ScData);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = MarvellYukonAddControllerData (Controller, ScData);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  for (Port = 0; Port < ScData->msk_num_port; Port++) {

    Status = gBS->AllocatePool (EfiBootServicesData,
                                sizeof (YUKON_DRIVER),
                                (VOID**) &YukonDriver);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: AllocatePool() failed with Status = %r\n", Status));
      return Status;
    }

    if (ScData->msk_if[Port] == NULL) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: AllocatePool() failed with Status = %r\n", EFI_BAD_BUFFER_SIZE));
      return EFI_BAD_BUFFER_SIZE;
    }

    gBS->SetMem (YukonDriver, sizeof (YUKON_DRIVER), 0);
    EfiInitializeLock (&YukonDriver->Lock, TPL_NOTIFY);

    //
    //  Set the structure signature
    //
    YukonDriver->Signature = YUKON_DRIVER_SIGNATURE;

    //
    // Set MAC address
    //
    gBS->CopyMem (&YukonDriver->SnpMode.PermanentAddress, &(ScData->msk_if[Port])->MacAddress,
                  sizeof (EFI_MAC_ADDRESS));

    //
    // Set Port number
    //
    YukonDriver->Port = Port;

    //
    //  Initialize the simple network protocol
    //
    Status = InitializeSNPProtocol (YukonDriver);

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: InitializeSNPProtocol: ERROR Status = %r\n", Status));
      gBS->CloseProtocol (
            Controller,
            &gEfiPciIoProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );
    }

    //
    // Set Device Path
    //
    Status = gBS->OpenProtocol (
          Controller,
          &gEfiDevicePathProtocolGuid,
          (VOID **) &ParentDevicePath,
          pThis->DriverBindingHandle,
          Controller,
          EFI_OPEN_PROTOCOL_GET_PROTOCOL
          );

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: OpenProtocol:EFI_DEVICE_PATH_PROTOCOL error. Status = %r\n", Status));

      gBS->CloseProtocol (
            Controller,
            &gEfiPciIoProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );

      gBS->FreePool (YukonDriver);
      return Status;
    }

    gBS->SetMem (&MacDeviceNode, sizeof (MAC_ADDR_DEVICE_PATH), 0);
    MacDeviceNode.Header.Type = MESSAGING_DEVICE_PATH;
    MacDeviceNode.Header.SubType = MSG_MAC_ADDR_DP;

    SetDevicePathNodeLength (&MacDeviceNode, sizeof (MacDeviceNode));

    //
    // Assign fields for device path
    //
    gBS->CopyMem (&YukonDriver->SnpMode.CurrentAddress, &YukonDriver->SnpMode.PermanentAddress,
                  sizeof (EFI_MAC_ADDRESS));
    gBS->CopyMem (&MacDeviceNode.MacAddress, &YukonDriver->SnpMode.CurrentAddress, sizeof (EFI_MAC_ADDRESS));

    MacDeviceNode.IfType = YukonDriver->SnpMode.IfType;
    YukonDriver->DevicePath = AppendDevicePathNode (ParentDevicePath, &MacDeviceNode.Header);
    if (YukonDriver->DevicePath == NULL) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: AppendDevicePathNode: ERROR Status = %r\n", EFI_OUT_OF_RESOURCES));
      gBS->CloseProtocol (
            Controller,
            &gEfiPciIoProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );
      gBS->FreePool (YukonDriver);
      return EFI_OUT_OF_RESOURCES;
    }

    //
    //  Install both the simple network and device path protocols.
    //
    Status = gBS->InstallMultipleProtocolInterfaces (
          &YukonDriver->Controller,
          &gEfiSimpleNetworkProtocolGuid,
          &YukonDriver->Snp,
          &gEfiDevicePathProtocolGuid,
          YukonDriver->DevicePath,
          NULL
          );

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Marvell Yukon: InstallMultipleProtocolInterfaces error. Status = %r\n", Status));

      gBS->CloseProtocol (
            Controller,
            &gEfiPciIoProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );

      gBS->FreePool (YukonDriver->DevicePath);
      gBS->FreePool (YukonDriver);
      return Status;
    } else {

      //
      // Hook as a child device
      //
      Status = gBS->OpenProtocol (Controller,
                                  &gEfiPciIoProtocolGuid,
                                  &ChildPciIo,
                                  pThis->DriverBindingHandle,
                                  YukonDriver->Controller,
                                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "Marvell Yukon: OpenProtocol: child controller error. Status = %r\n", Status));

        gBS->UninstallMultipleProtocolInterfaces (
              Controller,
              &gEfiSimpleNetworkProtocolGuid,
              &YukonDriver->Snp,
              &gEfiDevicePathProtocolGuid,
              YukonDriver->DevicePath,
              NULL
              );

        gBS->CloseProtocol (
              Controller,
              &gEfiPciIoProtocolGuid,
              pThis->DriverBindingHandle,
              Controller
              );

        gBS->FreePool (YukonDriver->DevicePath);
        gBS->FreePool (YukonDriver);
        return Status;
      } else {
        DEBUG ((DEBUG_NET, "Marvell Yukon: MarvellYukonDriverSupported: New Controller Handle = %p\n",
               YukonDriver->Controller));
      }

      Status = MarvellYukonAddControllerData (YukonDriver->Controller, ScData);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "Marvell Yukon: Failed to register port %d with controller handle %p\n", Port,
               YukonDriver->Controller));
      }

    }

    if (!EFI_ERROR (Status)) {
      Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK,
                      &MarvellYukonNotifyExitBoot, YukonDriver, &YukonDriver->ExitBootEvent);
    }
  }

  return Status;
}

/**
  Stop this driver on Controller by removing NetworkInterfaceIdentifier protocol and
  closing the DevicePath and PciIo protocols on Controller.

  @param [in] pThis                   Protocol instance pointer.
  @param [in] Controller              Handle of device to stop driver on.
  @param [in] NumberOfChildren        How many children need to be stopped.
  @param [in] pChildHandleBuffer      Not used.

  @retval EFI_SUCCESS                 This driver is removed Controller.
  @retval EFI_DEVICE_ERROR            The device could not be stopped due to a device error.
  @retval other                       This driver was not removed from this device.

**/
EFI_STATUS
EFIAPI
MarvellYukonDriverStop (
    IN  EFI_DRIVER_BINDING_PROTOCOL * pThis,
    IN  EFI_HANDLE Controller,
    IN  UINTN NumberOfChildren,
    IN  EFI_HANDLE * ChildHandleBuffer
    )
{
  EFI_SIMPLE_NETWORK_PROTOCOL  *SimpleNetwork;
  EFI_STATUS                   Status;
  YUKON_DRIVER                 *YukonDriver;
  EFI_TPL                      OldTpl;
  UINTN                        ChildController;
  struct msk_softc             *ScData;

  if (pThis == NULL) {
    DEBUG ((EFI_D_ERROR, "Marvell Yukon: MarvellYukonDriverStop() failed with Status = %r\n", EFI_INVALID_PARAMETER));
    return EFI_INVALID_PARAMETER;
  }

  if (NumberOfChildren > 0 && ChildHandleBuffer == NULL) {
    DEBUG ((EFI_D_ERROR, "Marvell Yukon: MarvellYukonDriverStop() failed with Status = %r\n", EFI_INVALID_PARAMETER));
    return EFI_INVALID_PARAMETER;
  }

  for (ChildController = 0; ChildController < NumberOfChildren; ChildController ++) {

    Status = gBS->OpenProtocol (
          ChildHandleBuffer[ChildController],
          &gEfiSimpleNetworkProtocolGuid,
          (VOID **) &SimpleNetwork,
          pThis->DriverBindingHandle,
          Controller,
          EFI_OPEN_PROTOCOL_GET_PROTOCOL
          );

    if (!EFI_ERROR(Status)) {

      YukonDriver = YUKON_DEV_FROM_THIS_SNP (SimpleNetwork);

      Status = MarvellYukonGetControllerData (YukonDriver->Controller, &ScData);
      if (EFI_ERROR (Status)) {
        continue;
      }

      OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

      ASSERT (YukonDriver->Controller == ChildHandleBuffer[ChildController]);
      if (YukonDriver->SnpMode.State != EfiSimpleNetworkStopped) {

         //
         // Device in use, cannot stop driver instance
         //
         Status = EFI_DEVICE_ERROR;
         DEBUG ((DEBUG_ERROR,
                "Marvell Yukon: MarvellYukonDriverStop: Error: SNP is not stopped. Status %r\n", Status));
       } else {

         //
         // Unhook the child controller
         //
         Status = gBS->CloseProtocol (Controller,
                             &gEfiPciIoProtocolGuid,
                             pThis->DriverBindingHandle,
                             YukonDriver->Controller);

         if (EFI_ERROR (Status)) {
           DEBUG ((DEBUG_ERROR,
                  "Marvell Yukon: MarvellYukonDriverStop:Close Child EfiPciIoProtocol error. Status %r\n", Status));
         }

         Status = gBS->UninstallMultipleProtocolInterfaces (
              YukonDriver->Controller,
              &gEfiSimpleNetworkProtocolGuid,
              &YukonDriver->Snp,
              &gEfiDevicePathProtocolGuid,
              YukonDriver->DevicePath,
              NULL
              );

         if (EFI_ERROR(Status)){
           DEBUG ((DEBUG_ERROR,
                  "Marvell Yukon: MarvellYukonDriverStop:UninstallMultipleProtocolInterfaces error. Status %r\n",
                  Status));
         }

         MarvellYukonDelControllerData (YukonDriver->Controller);

         gBS->CloseEvent (YukonDriver->ExitBootEvent);
         gBS->FreePool (YukonDriver->DevicePath);
         gBS->FreePool (YukonDriver);
       }
       gBS->RestoreTPL (OldTpl);
    }
  }

  Status = gBS->CloseProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        pThis->DriverBindingHandle,
        Controller
        );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Marvell Yukon: MarvellYukonDriverStop:Close EfiPciIoProtocol error. Status %r\n", Status));
  }

  Status = MarvellYukonGetControllerData (Controller, &ScData);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  mskc_detach (ScData);
  gBS->FreePool (ScData);
  Status = MarvellYukonDelControllerData (Controller);

  return Status;
}

/**
  Process exit boot event.

  @param [in] Event                   Event id.
  @param [in] Context                 Driver context.

**/
VOID
EFIAPI
MarvellYukonNotifyExitBoot (
  IN EFI_EVENT Event,
  IN VOID *Context
  )
{
  YUKON_DRIVER    *YukonDriver;
  EFI_STATUS      Status;

  if (Context == NULL) {
    DEBUG ((DEBUG_ERROR,
           "Marvell Yukon: MarvellYukonNotifyExitBoot() failed with Status = %r\n", EFI_INVALID_PARAMETER));
  } else {

    YukonDriver = Context;

    if (YukonDriver->SnpMode.State != EfiSimpleNetworkStopped) {
      Status  = YukonDriver->Snp.Shutdown(&YukonDriver->Snp);
      if (!EFI_ERROR (Status)) {
        YukonDriver->Snp.Stop(&YukonDriver->Snp);
      }
    }
  }
}

/**
  Get driver's data structure associated with controller

  @param [in] Controller           Controller Id.
  @param [out] Data                Driver's data.

**/
EFI_STATUS
EFIAPI
MarvellYukonGetControllerData (
  IN EFI_HANDLE Controller,
  OUT struct msk_softc **Data
  )
{
  MSK_LINKED_DRV_BUF *DrvNode;
  EFI_STATUS         Status;

  Status = MarvellYukonFindControllerNode (Controller, &DrvNode);
  if (!EFI_ERROR (Status)) {
    *Data = DrvNode->Data;
  }
  return Status;
}

/**
  Add driver's data structure associated with controller

  @param [in] Controller           Controller Id.
  @param [in] Data                 Driver's data.

**/
EFI_STATUS
EFIAPI
MarvellYukonAddControllerData (
  IN EFI_HANDLE Controller,
  IN struct msk_softc *Data
  )
{
  MSK_LINKED_DRV_BUF *DrvNode;
  EFI_STATUS         Status;

  Status = MarvellYukonFindControllerNode (Controller, &DrvNode);
  if (EFI_ERROR (Status)) {
    Status = gBS->AllocatePool (EfiBootServicesData,
                                sizeof (MSK_LINKED_DRV_BUF),
                                (VOID**) &DrvNode);
    if (!EFI_ERROR (Status)) {
      DrvNode->Signature = MSK_DRV_SIGNATURE;
      DrvNode->Controller = Controller;
      DrvNode->Data = Data;
      InsertTailList (&MarvellYukonDrvDataHead, &DrvNode->Link);
    }
  } else {
    Status = EFI_ALREADY_STARTED;
  }

  return Status;
}

/**
  Delete driver's data structure associated with controller

  @param [in] Controller           Controller Id.

**/
EFI_STATUS
EFIAPI
MarvellYukonDelControllerData (
  IN EFI_HANDLE Controller
  )
{
  MSK_LINKED_DRV_BUF *DrvNode;
  EFI_STATUS         Status;

  Status = MarvellYukonFindControllerNode (Controller, &DrvNode);
  if (!EFI_ERROR (Status)) {
    RemoveEntryList (&DrvNode->Link);
    gBS->FreePool (DrvNode);
  }

  return Status;
}

/**
  Find node associated with controller

  @param [in] Controller           Controller Id.
  @param [out] DrvLinkedBuff       Controller's node.

**/
EFI_STATUS
EFIAPI
MarvellYukonFindControllerNode (
  IN EFI_HANDLE Controller,
  OUT MSK_LINKED_DRV_BUF **DrvLinkedBuff
  )
{
  MSK_LINKED_DRV_BUF *DrvBuffNode;
  EFI_STATUS         Status;
  LIST_ENTRY         *Node;

  Status = EFI_NOT_FOUND;

  Node = GetFirstNode (&MarvellYukonDrvDataHead);
  while (!IsNull (&MarvellYukonDrvDataHead, Node)) {
    DrvBuffNode = MSK_DRV_INFO_FROM_THIS (Node);
    if (DrvBuffNode->Controller == Controller) {
      *DrvLinkedBuff = DrvBuffNode;
      Status = EFI_SUCCESS;
      break;
    }
    Node = GetNextNode (&MarvellYukonDrvDataHead, Node);
  }

  return Status;
}

//
// Simple Network Protocol Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL gMarvellYukonDriverBinding = {
  MarvellYukonDriverSupported,
  MarvellYukonDriverStart,
  MarvellYukonDriverStop,
  0xa,
  NULL,
  NULL
};

/**
  The Marvell Yukon driver entry point.

   <at> param ImageHandle             The driver image handle.
   <at> param SystemTable             The system table.

   <at> retval EFI_SUCCESS            Initialization routine has found and initialized
                                      hardware successfully.
   <at> retval Other                  Return value from HandleProtocol for
                                      DeviceIoProtocol or LoadedImageProtocol

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

  DEBUG ((EFI_D_NET, "Marvell Yukon: InitializeMarvellYukonDriver()\n"));

  if (SystemTable == NULL) {
    DEBUG ((DEBUG_ERROR,
           "Marvell Yukon: InitializeMarvellYukonDriver() failed with Status = %r\n", EFI_INVALID_PARAMETER));
    return EFI_INVALID_PARAMETER;
  }

  Status = EfiLibInstallDriverBindingComponentName2 (
        ImageHandle,
        SystemTable,
        &gMarvellYukonDriverBinding,
        NULL,
        &gSimpleNetworkComponentName,
        &gSimpleNetworkComponentName2
        );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "Marvell Yukon: InitializeMarvellYukonDriver(): Driver binding failed\n"));
    return Status;
  }

  InitializeListHead (&MarvellYukonDrvDataHead);

  return Status;
}