/*!
    @file EfiPlatform.c

    @details Some of API in this module can be called from the host. See the
        description of each API.

    @brief EFI specific platform API.

    @author Satoshi Tanda

    @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
 */
#include "EfiPlatform.h"
#include <Guid/EventGroup.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Pi/PiDxeCis.h>
#include <Protocol/MpService.h>
#include <Protocol/LoadedImage.h>
#include "EfiLogger.h"
#include "../../MiniVisor.h"

//
// Maps conversion between MV_STATUS and NTSTATUS.
//
typedef struct _STATUS_MAPPING
{
    MV_STATUS MvStatus;
    EFI_STATUS EfiStatus;
} STATUS_MAPPING;

static CONST STATUS_MAPPING k_StatusMapping[] =
{
    { MV_STATUS_SUCCESS,                EFI_SUCCESS, },
    { MV_STATUS_UNSUCCESSFUL,           EFI_ABORTED, },
    { MV_STATUS_ACCESS_DENIED,          EFI_ACCESS_DENIED, },
    { MV_STATUS_INSUFFICIENT_RESOURCES, EFI_OUT_OF_RESOURCES, },
    { MV_STATUS_HV_OPERATION_FAILED,    EFI_UNSUPPORTED, },
};

//
// The multi-processor protocol. Only available during the boot-time.
//
static EFI_MP_SERVICES_PROTOCOL* g_MpServices;

/*!
    @brief Converts MV_STATUS to EFI_STATUS.

    @param[in] Status - The MV_STATUS to convert from.

    @return The converted EFI_STATUS.
 */
static
EFI_STATUS
ConvertMvToEfiStatus (
    MV_STATUS Status
    )
{
    for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
    {
        if (Status == k_StatusMapping[i].MvStatus)
        {
            return k_StatusMapping[i].EfiStatus;
        }
    }

    //
    // Update the mapping when this assert hits.
    //
    MV_ASSERT(FALSE);
    return EFI_ABORTED;
}

/*!
    @brief Converts EFI_STATUS to MV_STATUS.

    @param[in] Status - The EFI_STATUS to convert from.

    @return The converted MV_STATUS.
 */
static
MV_STATUS
ConvertEfiToMvStatus (
    EFI_STATUS Status
    )
{
    for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
    {
        if (Status == k_StatusMapping[i].EfiStatus)
        {
            return k_StatusMapping[i].MvStatus;
        }
    }
    return MV_STATUS_UNSUCCESSFUL;
}

/*!
    @brief Displays information about the current module.

    @details Use of this API at the run-time is not allowed.

    @return EFI_SUCCESS on success; otherwise, an appropriate error code.
 */
static
EFI_STATUS
PrintLoadedImageInformation (
    )
{
    EFI_STATUS status;
    EFI_LOADED_IMAGE_PROTOCOL* loadedImageInfo;
    CHAR16* devicePath;

    devicePath = NULL;

    status = gBS->OpenProtocol(gImageHandle,
                               &gEfiLoadedImageProtocolGuid,
                               (VOID**)&loadedImageInfo,
                               gImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL);
    if (EFI_ERROR(status))
    {
        LOG_ERROR("OpenProtocol failed : %r", status);
        goto Exit;
    }

    devicePath = ConvertDevicePathToText(loadedImageInfo->FilePath, TRUE, TRUE);
    if (devicePath == NULL)
    {
        LOG_ERROR("ConvertDevicePathToText failed");
        status = EFI_OUT_OF_RESOURCES;
        goto Exit;
    }

    LOG_INFO("%s - %llx:%llx",
             devicePath,
             loadedImageInfo->ImageBase,
             MV_ADD2PTR(loadedImageInfo->ImageBase, loadedImageInfo->ImageSize));

Exit:
    if (devicePath != NULL)
    {
        FreePool(devicePath);
    }
    return status;
}

/*!
    @brief The platform specific module entry point.

    @param[in] ImageHandle - The handle of this module.

    @param[in] SystemTable - The boot service table pointer.

    @return EFI_SUCCESS on success; otherwise, an appropriate error code.
 */
EFI_STATUS
EFIAPI
DriverEntry (
    EFI_HANDLE ImageHandle,
    EFI_SYSTEM_TABLE* SystemTable
    )
{
    ASSERT(ImageHandle == gImageHandle);
    ASSERT(SystemTable->BootServices == gBS);

    return ConvertMvToEfiStatus(InitializeMiniVisor());
}

/*!
    @brief The platform specific module unload callback.

    @param[in] ImageHandle - The handle of this module.

    @return Always EFI_SUCCESS.
 */
EFI_STATUS
EFIAPI
DriverUnload (
    EFI_HANDLE ImageHandle
    )
{
    CleanupMiniVisor();
    return EFI_SUCCESS;
}

MV_STATUS
InitializePlatform (
    )
{
    EFI_STATUS status;
    BOOLEAN isLoggerInitialized;

    status = InitializeLogger();
    if (EFI_ERROR(status))
    {
        LOG_EARLY_ERROR("InitializeLogger failed : %r", status);
        goto Exit;
    }
    isLoggerInitialized = TRUE;

    PrintLoadedImageInformation();

    //
    // Locate the protocol for multi-processor handling. UEFI on a Hyper-V VM
    // does not implement this and fails.
    //
    status = gBS->LocateProtocol(&gEfiMpServiceProtocolGuid,
                                 NULL,
                                 &g_MpServices);
    if (EFI_ERROR(status))
    {
        LOG_ERROR("LocateProtocol failed : %r", status);
        goto Exit;
    }

Exit:
    if (EFI_ERROR(status))
    {
        if (isLoggerInitialized != FALSE)
        {
            CleanupLogger();
        }
    }
    return ConvertEfiToMvStatus(status);
}

VOID
CleanupPlatform (
    )
{
    CleanupLogger();
}

UINT32
GetActiveProcessorCount (
    )
{
    EFI_STATUS status;
    UINTN numberOfProcessors;
    UINTN numberOfEnabledProcessors;

    status = g_MpServices->GetNumberOfProcessors(g_MpServices,
                                                 &numberOfProcessors,
                                                 &numberOfEnabledProcessors);
    if (EFI_ERROR(status))
    {
        MV_PANIC();
    }

    return (UINT32)numberOfEnabledProcessors;
}

UINT32
GetCurrentProcessorNumber (
    )
{
    EFI_STATUS status;
    UINTN processorNumber;

    status = g_MpServices->WhoAmI(g_MpServices, &processorNumber);
    if (EFI_ERROR(status))
    {
        MV_PANIC();
    }

    return (UINT32)processorNumber;
}

UINT64
GetPhysicalAddress (
    VOID* VirualAddress
    )
{
    //
    // Assume the current CR3 uses the identity mapping. This is the case during
    // the boot time or execution of the host.
    //
    return (UINT64)VirualAddress;
}

VOID*
GetVirtualAddress (
    UINT64 PhysicalAddress
    )
{
    //
    // This function assume the current CR3 uses the identity mapping. This is
    // the case during the boot time or execution of the host.
    //
    return (VOID*)PhysicalAddress;
}

VOID*
AllocateSystemMemory (
    UINT64 PageCount
    )
{
    VOID* pages;

    pages = AllocateRuntimePages(PageCount);
    if (pages == NULL)
    {
        goto Exit;
    }
    ZeroMem(pages, PageCount * EFI_PAGE_SIZE);

Exit:
    return pages;
}

VOID
FreeSystemMemory (
    VOID* Pages,
    UINT64 PageCount
    )
{
    FreePages(Pages, PageCount);
}

VOID*
ReserveVirtualAddress (
    UINT64 PageCount
    )
{
    return AllocateSystemMemory(PageCount);
}

VOID
FreeReservedVirtualAddress (
    VOID* BaseAddress,
    UINT64 PageCount
    )
{
    FreeSystemMemory(BaseAddress, PageCount);
}

VOID
RunOnAllProcessors (
    USER_PASSIVE_CALLBACK* Callback,
    VOID* Context
    )
{
    EFI_STATUS status;

    Callback(Context);
    if (GetActiveProcessorCount() == 1)
    {
        goto Exit;
    }

    status = g_MpServices->StartupAllAPs(g_MpServices,
                                         Callback,
                                         TRUE,
                                         NULL,
                                         0,
                                         Context,
                                         NULL);
    if (EFI_ERROR(status))
    {
        MV_PANIC();
    }

Exit:
    return;
}

VOID
InitializeSystemSpinLock (
    SPIN_LOCK* SpinLock
    )
{
    (VOID)InitializeSpinLock(SpinLock);
}

UINT8
AcquireSystemSpinLock (
    SPIN_LOCK* SpinLock
    )
{
    //
    // This function does not raise TPL as it is not available at the run time.
    //
    (VOID)AcquireSpinLock(SpinLock);
    return 0;
}

VOID
ReleaseSystemSpinLock (
    SPIN_LOCK* SpinLock,
    UINT8 PreviousContext
    )
{
    ASSERT(PreviousContext == 0);
    (VOID)ReleaseSpinLock(SpinLock);
}
