/** @file
 Emu Bus driver

Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>
Portions copyright (c) 2011, Apple Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent


**/

#include "EmuBusDriverDxe.h"

//
// DriverBinding protocol global
//
EFI_DRIVER_BINDING_PROTOCOL  gEmuBusDriverBinding = {
  EmuBusDriverBindingSupported,
  EmuBusDriverBindingStart,
  EmuBusDriverBindingStop,
  0xa,
  NULL,
  NULL
};

EFI_STATUS
EFIAPI
EmuBusDriverBindingSupported (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath;
  EMU_THUNK_PROTOCOL        *EmuThunk;

  //
  // Check the contents of the first Device Path Node of RemainingDevicePath to make sure
  // it is a legal Device Path Node for this bus driver's children.
  //
  if (RemainingDevicePath != NULL) {
    //
    // Check if RemainingDevicePath is the End of Device Path Node,
    // if yes, go on checking other conditions
    //
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      //
      // If RemainingDevicePath isn't the End of Device Path Node,
      // check its validation
      //
      if ((RemainingDevicePath->Type != HARDWARE_DEVICE_PATH) ||
          (RemainingDevicePath->SubType != HW_VENDOR_DP) ||
          (DevicePathNodeLength (RemainingDevicePath) != sizeof (EMU_VENDOR_DEVICE_PATH_NODE)))
      {
        return EFI_UNSUPPORTED;
      }
    }
  }

  //
  // Open the IO Abstraction(s) needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEmuThunkProtocolGuid,
                  (VOID **)&EmuThunk,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close the I/O Abstraction(s) used to perform the supported test
  //
  gBS->CloseProtocol (
         ControllerHandle,
         &gEmuThunkProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

  //
  // Open the EFI Device Path protocol needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&ParentDevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close protocol, don't use device path protocol in the Support() function
  //
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiDevicePathProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

  return Status;
}

EFI_STATUS
EFIAPI
EmuBusDriverBindingStart (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS                   Status;
  EFI_STATUS                   InstallStatus;
  EMU_THUNK_PROTOCOL           *EmuThunk;
  EFI_DEVICE_PATH_PROTOCOL     *ParentDevicePath;
  EMU_IO_DEVICE                *EmuDevice;
  EMU_BUS_DEVICE               *EmuBusDevice;
  EMU_IO_THUNK_PROTOCOL        *EmuIoThunk;
  UINT16                       ComponentName[512];
  EMU_VENDOR_DEVICE_PATH_NODE  *Node;
  BOOLEAN                      CreateDevice;

  InstallStatus = EFI_UNSUPPORTED;
  Status        = EFI_UNSUPPORTED;

  //
  // Grab the protocols we need
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&ParentDevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEmuThunkProtocolGuid,
                  (VOID **)&EmuThunk,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  if (Status != EFI_ALREADY_STARTED) {
    EmuBusDevice = AllocatePool (sizeof (EMU_BUS_DEVICE));
    if (EmuBusDevice == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    EmuBusDevice->Signature           = EMU_BUS_DEVICE_SIGNATURE;
    EmuBusDevice->ControllerNameTable = NULL;

    AddUnicodeString2 (
      "eng",
      gEmuBusDriverComponentName.SupportedLanguages,
      &EmuBusDevice->ControllerNameTable,
      L"Emulator Bus Controller",
      TRUE
      );
    AddUnicodeString2 (
      "en",
      gEmuBusDriverComponentName2.SupportedLanguages,
      &EmuBusDevice->ControllerNameTable,
      L"Emulator Bus Controller",
      FALSE
      );

    Status = gBS->InstallMultipleProtocolInterfaces (
                    &ControllerHandle,
                    &gEfiCallerIdGuid,
                    EmuBusDevice,
                    NULL
                    );
    if (EFI_ERROR (Status)) {
      FreeUnicodeStringTable (EmuBusDevice->ControllerNameTable);
      gBS->FreePool (EmuBusDevice);
      return Status;
    }
  }

  for (Status = EFI_SUCCESS, EmuIoThunk = NULL; !EFI_ERROR (Status); ) {
    Status = EmuThunk->GetNextProtocol (TRUE, &EmuIoThunk);
    if (EFI_ERROR (Status)) {
      break;
    }

    CreateDevice = TRUE;
    if (RemainingDevicePath != NULL) {
      CreateDevice = FALSE;
      //
      // Check if RemainingDevicePath is the End of Device Path Node,
      // if yes, don't create any child device
      //
      if (!IsDevicePathEnd (RemainingDevicePath)) {
        //
        // If RemainingDevicePath isn't the End of Device Path Node,
        // check its validation
        //
        Node = (EMU_VENDOR_DEVICE_PATH_NODE *)RemainingDevicePath;
        if ((Node->VendorDevicePath.Header.Type == HARDWARE_DEVICE_PATH) &&
            (Node->VendorDevicePath.Header.SubType == HW_VENDOR_DP) &&
            (DevicePathNodeLength (&Node->VendorDevicePath.Header) == sizeof (EMU_VENDOR_DEVICE_PATH_NODE))
            )
        {
          if (CompareGuid (&Node->VendorDevicePath.Guid, EmuIoThunk->Protocol) && (Node->Instance == EmuIoThunk->Instance)) {
            CreateDevice = TRUE;
          }
        }
      }
    }

    if (CreateDevice) {
      //
      // Allocate instance structure, and fill in parent information.
      //
      EmuDevice = AllocatePool (sizeof (EMU_IO_DEVICE));
      if (EmuDevice == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      EmuDevice->Handle           = NULL;
      EmuDevice->ControllerHandle = ControllerHandle;
      EmuDevice->ParentDevicePath = ParentDevicePath;
      CopyMem (&EmuDevice->EmuIoThunk, EmuIoThunk, sizeof (EMU_IO_THUNK_PROTOCOL));

      EmuDevice->ControllerNameTable = NULL;

      StrnCpyS (
        ComponentName,
        sizeof (ComponentName) / sizeof (CHAR16),
        EmuIoThunk->ConfigString,
        sizeof (ComponentName) / sizeof (CHAR16)
        );

      EmuDevice->DevicePath = EmuBusCreateDevicePath (
                                ParentDevicePath,
                                EmuIoThunk->Protocol,
                                EmuIoThunk->Instance
                                );
      if (EmuDevice->DevicePath == NULL) {
        gBS->FreePool (EmuDevice);
        return EFI_OUT_OF_RESOURCES;
      }

      AddUnicodeString (
        "eng",
        gEmuBusDriverComponentName.SupportedLanguages,
        &EmuDevice->ControllerNameTable,
        ComponentName
        );

      EmuDevice->Signature = EMU_IO_DEVICE_SIGNATURE;

      InstallStatus = gBS->InstallMultipleProtocolInterfaces (
                             &EmuDevice->Handle,
                             &gEfiDevicePathProtocolGuid,
                             EmuDevice->DevicePath,
                             &gEmuIoThunkProtocolGuid,
                             &EmuDevice->EmuIoThunk,
                             NULL
                             );
      if (EFI_ERROR (InstallStatus)) {
        FreeUnicodeStringTable (EmuDevice->ControllerNameTable);
        gBS->FreePool (EmuDevice);
      } else {
        //
        // Open For Child Device
        //
        Status = gBS->OpenProtocol (
                        ControllerHandle,
                        &gEmuThunkProtocolGuid,
                        (VOID **)&EmuThunk,
                        This->DriverBindingHandle,
                        EmuDevice->Handle,
                        EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                        );
        if (!EFI_ERROR (Status)) {
          InstallStatus = EFI_SUCCESS;
        }
      }
    }
  }

  return InstallStatus;
}

EFI_STATUS
EFIAPI
EmuBusDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  UINTN                        NumberOfChildren,
  IN  EFI_HANDLE                   *ChildHandleBuffer
  )
{
  EFI_STATUS             Status;
  UINTN                  Index;
  BOOLEAN                AllChildrenStopped;
  EMU_IO_THUNK_PROTOCOL  *EmuIoThunk;
  EMU_BUS_DEVICE         *EmuBusDevice;
  EMU_IO_DEVICE          *EmuDevice;
  EMU_THUNK_PROTOCOL     *EmuThunk;

  //
  // Complete all outstanding transactions to Controller.
  // Don't allow any new transaction to Controller to be started.
  //

  if (NumberOfChildren == 0) {
    //
    // Close the bus driver
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiCallerIdGuid,
                    (VOID **)&EmuBusDevice,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    gBS->UninstallMultipleProtocolInterfaces (
           ControllerHandle,
           &gEfiCallerIdGuid,
           EmuBusDevice,
           NULL
           );

    FreeUnicodeStringTable (EmuBusDevice->ControllerNameTable);

    gBS->FreePool (EmuBusDevice);

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

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

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {
    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEmuIoThunkProtocolGuid,
                    (VOID **)&EmuIoThunk,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (!EFI_ERROR (Status)) {
      EmuDevice = EMU_IO_DEVICE_FROM_THIS (EmuIoThunk);

      Status = gBS->CloseProtocol (
                      ControllerHandle,
                      &gEmuThunkProtocolGuid,
                      This->DriverBindingHandle,
                      EmuDevice->Handle
                      );

      Status = gBS->UninstallMultipleProtocolInterfaces (
                      EmuDevice->Handle,
                      &gEfiDevicePathProtocolGuid,
                      EmuDevice->DevicePath,
                      &gEmuIoThunkProtocolGuid,
                      &EmuDevice->EmuIoThunk,
                      NULL
                      );

      if (EFI_ERROR (Status)) {
        gBS->OpenProtocol (
               ControllerHandle,
               &gEmuThunkProtocolGuid,
               (VOID **)&EmuThunk,
               This->DriverBindingHandle,
               EmuDevice->Handle,
               EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
               );
      } else {
        //
        // Close the child handle
        //
        FreeUnicodeStringTable (EmuDevice->ControllerNameTable);
        FreePool (EmuDevice);
      }
    }

    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/*++

Routine Description:
  Create a device path node using Guid and InstanceNumber and append it to
  the passed in RootDevicePath

Arguments:
  RootDevicePath - Root of the device path to return.

  Guid           - GUID to use in vendor device path node.

  InstanceNumber - Instance number to use in the vendor device path. This
                    argument is needed to make sure each device path is unique.

Returns:

  EFI_DEVICE_PATH_PROTOCOL

**/
EFI_DEVICE_PATH_PROTOCOL *
EmuBusCreateDevicePath (
  IN  EFI_DEVICE_PATH_PROTOCOL  *RootDevicePath,
  IN  EFI_GUID                  *Guid,
  IN  UINT16                    InstanceNumber
  )
{
  EMU_VENDOR_DEVICE_PATH_NODE  DevicePath;

  DevicePath.VendorDevicePath.Header.Type    = HARDWARE_DEVICE_PATH;
  DevicePath.VendorDevicePath.Header.SubType = HW_VENDOR_DP;
  SetDevicePathNodeLength (&DevicePath.VendorDevicePath.Header, sizeof (EMU_VENDOR_DEVICE_PATH_NODE));

  //
  // The GUID defines the Class
  //
  CopyMem (&DevicePath.VendorDevicePath.Guid, Guid, sizeof (EFI_GUID));

  //
  // Add an instance number so we can make sure there are no Device Path
  // duplication.
  //
  DevicePath.Instance = InstanceNumber;

  return AppendDevicePathNode (
           RootDevicePath,
           (EFI_DEVICE_PATH_PROTOCOL *)&DevicePath
           );
}

/**
  The user Entry Point for module EmuBusDriver. The user code starts with this function.

  @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.
  @retval other             Some error occurs when executing this entry point.

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

  Status = EfiLibInstallAllDriverProtocols (
             ImageHandle,
             SystemTable,
             &gEmuBusDriverBinding,
             ImageHandle,
             &gEmuBusDriverComponentName,
             NULL,
             NULL
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
