/** @file
  Connect to and disconnect from the various network layers

  Copyright (c) 2011, Intel Corporation. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Socket.h"


/**
  Connect to the network service bindings

  Walk the network service protocols on the controller handle and
  locate any that are not in use.  Create ::ESL_SERVICE structures to
  manage the network layer interfaces for the socket driver.  Tag
  each of the network interfaces that are being used.  Finally, this
  routine calls ESL_SOCKET_BINDING::pfnInitialize to prepare the network
  interface for use by the socket layer.

  @param [in] BindingHandle    Handle for protocol binding.
  @param [in] Controller       Handle of device to work with.

  @retval EFI_SUCCESS          This driver is added to Controller.
  @retval EFI_OUT_OF_RESOURCES No more memory available.
  @retval EFI_UNSUPPORTED      This driver does not support this device.

**/
EFI_STATUS
EFIAPI
EslServiceConnect (
  IN EFI_HANDLE BindingHandle,
  IN EFI_HANDLE Controller
  )
{
  BOOLEAN bInUse;
  EFI_STATUS ExitStatus;
  UINTN LengthInBytes;
  UINT8 * pBuffer;
  CONST ESL_SOCKET_BINDING * pEnd;
  VOID * pJunk;
  ESL_SERVICE ** ppServiceListHead;
  ESL_SERVICE * pService;
  CONST ESL_SOCKET_BINDING * pSocketBinding;
  EFI_SERVICE_BINDING_PROTOCOL * pServiceBinding;
  EFI_STATUS Status;
  EFI_TPL TplPrevious;

  DBG_ENTER ( );

  //
  //  Assume the list is empty
  //
  ExitStatus = EFI_UNSUPPORTED;
  bInUse = FALSE;

  //
  //  Walk the list of network connection points
  //
  pSocketBinding = &cEslSocketBinding[0];
  pEnd = &pSocketBinding[ cEslSocketBindingEntries ];
  while ( pEnd > pSocketBinding ) {
    //
    //  Determine if the controller supports the network protocol
    //
    Status = gBS->OpenProtocol (
                    Controller,
                    pSocketBinding->pNetworkBinding,
                    (VOID**)&pServiceBinding,
                    BindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if ( !EFI_ERROR ( Status )) {
      //
      //  Determine if the socket layer is already connected
      //
      Status = gBS->OpenProtocol (
                      Controller,
                      (EFI_GUID *)pSocketBinding->pTagGuid,
                      &pJunk,
                      BindingHandle,
                      Controller,
                      EFI_OPEN_PROTOCOL_GET_PROTOCOL
                      );
      if ( EFI_UNSUPPORTED == Status ) {
        //
        //  Allocate a service structure since the tag is not present
        //
        LengthInBytes = sizeof ( *pService );
        Status = gBS->AllocatePool (
                        EfiRuntimeServicesData,
                        LengthInBytes,
                        (VOID **) &pService
                        );
        if ( !EFI_ERROR ( Status )) {
          DEBUG (( DEBUG_POOL | DEBUG_INIT,
                    "0x%08x: Allocate pService, %d bytes\r\n",
                    pService,
                    LengthInBytes ));

          //
          //  Set the structure signature and service binding
          //
          ZeroMem ( pService, LengthInBytes );
          pService->Signature = SERVICE_SIGNATURE;
          pService->pSocketBinding = pSocketBinding;
          pService->Controller = Controller;
          pService->pServiceBinding = pServiceBinding;

          //
          //  Mark the controller in use
          //
          if ( !bInUse ) {
            Status = gBS->InstallMultipleProtocolInterfaces (
                            &Controller,
                            &gEfiCallerIdGuid,
                            NULL,
                            NULL
                            );
            if ( !EFI_ERROR ( Status )) {
              DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                        "Installed: gEfiCallerIdGuid on   0x%08x\r\n",
                        Controller ));
              bInUse = TRUE;
            }
            else {
              if ( EFI_INVALID_PARAMETER == Status ) {
                Status = EFI_SUCCESS;
              }
            }
          }
          if ( !EFI_ERROR ( Status )) {
            //
            //  Mark the network service protocol in use
            //
            Status = gBS->InstallMultipleProtocolInterfaces (
                            &Controller,
                            pSocketBinding->pTagGuid,
                            pService,
                            NULL
                            );
            if ( !EFI_ERROR ( Status )) {
              DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                        "Installed: %s TagGuid on   0x%08x\r\n",
                        pSocketBinding->pName,
                        Controller ));

              //
              //  Synchronize with the socket layer
              //
              RAISE_TPL ( TplPrevious, TPL_SOCKETS );

              //
              //  Connect the service to the list
              //
              pBuffer = (UINT8 *)&mEslLayer;
              pBuffer = &pBuffer[ pSocketBinding->ServiceListOffset ];
              ppServiceListHead = (ESL_SERVICE **)pBuffer;
              pService->pNext = *ppServiceListHead;
              *ppServiceListHead = pService;

              //
              //  Release the socket layer synchronization
              //
              RESTORE_TPL ( TplPrevious );

              //
              //  At least one service was made available
              //
              ExitStatus = EFI_SUCCESS;
            }
            else {
              DEBUG (( DEBUG_ERROR | DEBUG_POOL | DEBUG_INIT,
                        "ERROR - Failed to install %s TagGuid on 0x%08x, Status: %r\r\n",
                        pSocketBinding->pName,
                        Controller,
                        Status ));
            }

            if ( EFI_ERROR ( Status )) {
              //
              //  The controller is no longer in use
              //
              if ( bInUse ) {
                gBS->UninstallMultipleProtocolInterfaces (
                          Controller,
                          &gEfiCallerIdGuid,
                          NULL,
                          NULL );
                DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                            "Removed:   gEfiCallerIdGuid from 0x%08x\r\n",
                            Controller ));
              }
            }
          }
          else {
            DEBUG (( DEBUG_ERROR | DEBUG_INIT,
                      "ERROR - Failed to install gEfiCallerIdGuid on 0x%08x, Status: %r\r\n",
                      Controller,
                      Status ));
          }

          //
          //  Release the service if necessary
          //
          if ( EFI_ERROR ( Status )) {
            gBS->FreePool ( pService );
            DEBUG (( DEBUG_POOL | DEBUG_INIT,
                      "0x%08x: Free pService, %d bytes\r\n",
                      pService,
                      sizeof ( *pService )));
            pService = NULL;
          }
        }
        else {
          DEBUG (( DEBUG_ERROR | DEBUG_INIT,
                    "ERROR - Failed service allocation, Status: %r\r\n",
                    Status ));
          ExitStatus = EFI_OUT_OF_RESOURCES;
          break;
        }
      }
    }
  
    //
    //  Set the next network protocol
    //
    pSocketBinding += 1;
  }
  
  //
  //  Display the driver start status
  //
  DBG_EXIT_STATUS ( ExitStatus );
  return ExitStatus;
}


/**
  Shutdown the connections to the network layer by locating the
  tags on the network interfaces established by ::EslServiceConnect.
  This routine shutdowns any activity on the network interface and
  then frees the ::ESL_SERVICE structures.

  @param [in] BindingHandle    Handle for protocol binding.
  @param [in] Controller       Handle of device to stop driver on.

  @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
EslServiceDisconnect (
  IN  EFI_HANDLE BindingHandle,
  IN  EFI_HANDLE Controller
  )
{
  UINT8 * pBuffer;
  CONST ESL_SOCKET_BINDING * pEnd;
  ESL_PORT * pPort;
  ESL_SERVICE * pPreviousService;
  ESL_SERVICE * pService;
  ESL_SERVICE ** ppServiceListHead;
  CONST ESL_SOCKET_BINDING * pSocketBinding;
  EFI_STATUS Status;
  EFI_TPL TplPrevious;
  
  DBG_ENTER ( );

  //
  //  Walk the list of network connection points in reverse order
  //
  pEnd = &cEslSocketBinding[0];
  pSocketBinding = &pEnd[ cEslSocketBindingEntries ];
  while ( pEnd < pSocketBinding ) {
    //
    //  Set the next network protocol
    //
    pSocketBinding -= 1;

    //
    //  Determine if the driver connected
    //
    Status = gBS->OpenProtocol (
                    Controller,
                    (EFI_GUID *)pSocketBinding->pTagGuid,
                    (VOID **)&pService,
                    BindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if ( !EFI_ERROR ( Status )) {

      //
      //  Synchronize with the socket layer
      //
      RAISE_TPL ( TplPrevious, TPL_SOCKETS );

      //
      //  Walk the list of ports
      //
      pPort = pService->pPortList;
      while ( NULL != pPort ) {
        //
        //  Remove the port from the port list
        //
        pPort->pService = NULL;
        pService->pPortList = pPort->pLinkService;
  
        //
        //  Close the port
        //
        EslSocketPortCloseStart ( pPort,
                                  TRUE,
                                  DEBUG_POOL | DEBUG_INIT );

        //
        //  Set the next port
        //
        pPort = pService->pPortList;
      }
    
      //
      //  Remove the service from the service list
      //
      pBuffer = (UINT8 *)&mEslLayer;
      pBuffer = &pBuffer[ pService->pSocketBinding->ServiceListOffset ];
      ppServiceListHead = (ESL_SERVICE **)pBuffer;
      pPreviousService = *ppServiceListHead;
      if ( pService == pPreviousService ) {
        //
        //  Remove the service from the beginning of the list
        //
        *ppServiceListHead = pService->pNext;
      }
      else {
        //
        //  Remove the service from the middle of the list
        //
        while ( NULL != pPreviousService ) {
          if ( pService == pPreviousService->pNext ) {
            pPreviousService->pNext = pService->pNext;
            break;
          }
          pPreviousService = pPreviousService->pNext;
        }
      }

      //
      //  Release the socket layer synchronization
      //
      RESTORE_TPL ( TplPrevious );

      //
      //  Break the driver connection
      //
      Status = gBS->UninstallMultipleProtocolInterfaces (
                Controller,
                pSocketBinding->pTagGuid,
                pService,
                NULL );
      if ( !EFI_ERROR ( Status )) {
        DEBUG (( DEBUG_POOL | DEBUG_INIT,
                    "Removed:   %s TagGuid from 0x%08x\r\n",
                    pSocketBinding->pName,
                    Controller ));
      }
      else {
        DEBUG (( DEBUG_ERROR | DEBUG_POOL | DEBUG_INIT,
                    "ERROR - Failed to removed %s TagGuid from 0x%08x, Status: %r\r\n",
                    pSocketBinding->pName,
                    Controller,
                    Status ));
      }

      //
      //  Free the service structure
      //
      Status = gBS->FreePool ( pService );
      if ( !EFI_ERROR ( Status )) {
        DEBUG (( DEBUG_POOL | DEBUG_INIT,
                  "0x%08x: Free pService, %d bytes\r\n",
                  pService,
                  sizeof ( *pService )));
      }
      else {
        DEBUG (( DEBUG_POOL | DEBUG_INIT,
                  "ERROR - Failed to free pService 0x%08x, Status: %r\r\n",
                  pService,
                  Status ));
      }
      pService = NULL;
    }
  }

  //
  //  The controller is no longer in use
  //
  gBS->UninstallMultipleProtocolInterfaces (
            Controller,
            &gEfiCallerIdGuid,
            NULL,
            NULL );
  DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Removed:   gEfiCallerIdGuid from 0x%08x\r\n",
              Controller ));

  //
  //  The driver is disconnected from the network controller
  //
  Status = EFI_SUCCESS;

  //
  //  Display the driver start status
  //
  DBG_EXIT_STATUS ( Status );
  return Status;
}



/**
Initialize the service layer

@param [in] ImageHandle       Handle for the image.

**/
VOID
EFIAPI
EslServiceLoad (
  IN EFI_HANDLE ImageHandle
  )
{
  ESL_LAYER * pLayer;

  //
  //  Save the image handle
  //
  pLayer = &mEslLayer;
  ZeroMem ( pLayer, sizeof ( *pLayer ));
  pLayer->Signature = LAYER_SIGNATURE;
  pLayer->ImageHandle = ImageHandle;

  //
  //  Connect the service binding protocol to the image handle
  //
  pLayer->pServiceBinding = &mEfiServiceBinding;
}


/**
  Shutdown the service layer

**/
VOID
EFIAPI
EslServiceUnload (
  VOID
  )
{
  ESL_LAYER * pLayer;

  //
  //  Undo the work by ServiceLoad
  //
  pLayer = &mEslLayer;
  pLayer->ImageHandle = NULL;
  pLayer->pServiceBinding = NULL;
}
