/*
 * PlatformDriverOverride.c
 *
 * Funcs for installing or overriding EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL
 * to enable putting our drivers to highest priority when connecting devices.
 *
 * dmazar, 9/11/2012
 *
 */

#include "Platform.h"
#include <Protocol/PlatformDriverOverride.h>

#ifndef DEBUG_ALL
#define DEBUG_PLO 1
#else
#define DEBUG_PLO DEBUG_ALL
#endif

#if DEBUG_PLO == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_PLO, __VA_ARGS__)
#endif

/** NULL terminated list of driver's handles that will be served by EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL. */
EFI_HANDLE  *mPriorityDrivers = NULL;

/** Saved original EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.GetDriver when doing override. */
EFI_PLATFORM_DRIVER_OVERRIDE_GET_DRIVER mOrigPlatformGetDriver = NULL;
EFI_PLATFORM_DRIVER_OVERRIDE_DRIVER_LOADED mOrigPlatformDriverLoaded = NULL;


//////////////////////////////////////////////////////////////////////
//
// Our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL implementation
//

/** Our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.GetDriver implementation. */
EFI_STATUS
EFIAPI
OurPlatformGetDriver (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN     EFI_HANDLE                                     ControllerHandle,
  IN OUT EFI_HANDLE                                     *DriverImageHandle
  )
{
  EFI_HANDLE     *HandlePtr;
  
  if (ControllerHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  if (mPriorityDrivers == NULL) {
    return EFI_NOT_FOUND;
  }
  
  // if first call - return first
  if (*DriverImageHandle == NULL) {
    *DriverImageHandle = mPriorityDrivers[0];
    return EFI_SUCCESS;
  }
  
  // search through our list
  for (HandlePtr = mPriorityDrivers; *HandlePtr != NULL; HandlePtr++) {
    if (*HandlePtr == *DriverImageHandle) {
      // we should return next after that
      HandlePtr++;
      if (*HandlePtr == NULL) {
        return EFI_NOT_FOUND;
      }
      *DriverImageHandle = *HandlePtr;
      return EFI_SUCCESS;
    }
  }
  
  return EFI_INVALID_PARAMETER;
}
///
// Our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.GetDriverPath implementation.
///
EFI_STATUS
EFIAPI
OurPlatformGetDriverPath (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN     EFI_HANDLE                                     ControllerHandle,
  IN OUT EFI_DEVICE_PATH_PROTOCOL                       **DriverImagePath
  )
{
  return EFI_UNSUPPORTED;
}

///
// Our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.DriverLoaded implementation.
///
EFI_STATUS
EFIAPI
OurPlatformDriverLoaded (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL          *This,
  IN EFI_HANDLE                                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL                       *DriverImagePath,
  IN EFI_HANDLE                                     DriverImageHandle
  )
{
#if DEBUG_DRIVER_OVERRIDE
  EFI_STATUS              Status;
  CHAR16                           *DriverName;
  EFI_COMPONENT_NAME_PROTOCOL      *CompName;
  EFI_BLOCK_IO_PROTOCOL         *BlkIo = NULL;
  
  Status = gBS->HandleProtocol (
                                ControllerHandle,
                                &gEfiBlockIoProtocolGuid,
                                (VOID **) &BlkIo
                                );
  if (EFI_ERROR(Status)) {
    return EFI_UNSUPPORTED;
  }
  
  Status = gBS->OpenProtocol(
                             DriverImageHandle,
                             &gEfiComponentNameProtocolGuid,
                             (VOID**)&CompName,
                             gImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
  
  if (EFI_ERROR(Status)) {
    DBG(" CompName %r\n", Status);
    return EFI_UNSUPPORTED;
  }
  Status = CompName->GetDriverName(CompName, "eng", &DriverName);
  if (!EFI_ERROR(Status)) {
    DBG(" DriverName=%s at Controller=%x\n", DriverName, ControllerHandle);
  }

#endif
  if (mOrigPlatformDriverLoaded) {
    return mOrigPlatformDriverLoaded(This, ControllerHandle, DriverImagePath, DriverImageHandle);
  }
  return EFI_UNSUPPORTED;
}

/** Our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL structure. */
EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL mOurPlatformDriverOverrideProtocol = {
  OurPlatformGetDriver,
  OurPlatformGetDriverPath,
  OurPlatformDriverLoaded
};


//////////////////////////////////////////////////////////////////////
//
// Overriding EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL if already installed
//
// EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.GetDriver override.
//
//////////

EFI_STATUS
EFIAPI
OvrPlatformGetDriver(
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN     EFI_HANDLE                                     ControllerHandle,
  IN OUT EFI_HANDLE                                     *DriverImageHandle
)
{
  EFI_HANDLE     *HandlePtr;

  if (mPriorityDrivers == NULL) {
    return mOrigPlatformGetDriver(This, ControllerHandle, DriverImageHandle);
  }

  // if first call - return first
  if (*DriverImageHandle == NULL) {
    *DriverImageHandle = mPriorityDrivers[0];
    return EFI_SUCCESS;
  }
  
  // search through our list
  for (HandlePtr = mPriorityDrivers; *HandlePtr != NULL; HandlePtr++) {
    if (*HandlePtr == *DriverImageHandle) {
      // we should return next after that
      HandlePtr++;
      if (*HandlePtr == NULL) {
        // our list is exhausted - we'll pass call to original
        *DriverImageHandle = NULL;
        break;
      }
      *DriverImageHandle = *HandlePtr;
      return EFI_SUCCESS;
    }
  }

  // not found in our list, or was last in our list - call original
  return mOrigPlatformGetDriver(This, ControllerHandle, DriverImageHandle);
}


//////////////////////////////////////////////////////////////////////
//
// Public funcs
//
// Registers given PriorityDrivers (NULL terminated) to highest priority during connecting controllers.
// Does this by installing our EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL
// or by overriding existing EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL.GetDriver.
//
////

VOID RegisterDriversToHighestPriority(IN EFI_HANDLE *PriorityDrivers)
{
  EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL *PlatformDriverOverride;
  EFI_STATUS                            Status;
  
  mPriorityDrivers = PriorityDrivers;
  Status = gBS->LocateProtocol(&gEfiPlatformDriverOverrideProtocolGuid, NULL, (VOID **) &PlatformDriverOverride);
  if (EFI_ERROR(Status)) {
    DBG("PlatformDriverOverrideProtocol not found. Installing ... ");
    Status = gBS->InstallMultipleProtocolInterfaces (
                                                     &gImageHandle,
                                                     &gEfiPlatformDriverOverrideProtocolGuid,
                                                     &mOurPlatformDriverOverrideProtocol,
                                                     NULL
                                                     );
    DBG("%r\n", Status);
    return;
  }
  
  mOrigPlatformGetDriver = PlatformDriverOverride->GetDriver;
  PlatformDriverOverride->GetDriver = OvrPlatformGetDriver;
  DBG("PlatformDriverOverrideProtocol->GetDriver overriden\n");
  mOrigPlatformDriverLoaded = PlatformDriverOverride->DriverLoaded;
  PlatformDriverOverride->DriverLoaded = OurPlatformDriverLoaded;
  
}

