/*
 * COPYRIGHT:       See COPYING.ARM in the top level directory
 * PROJECT:         ReactOS UEFI Boot Library
 * FILE:            boot/environ/lib/bootlib.c
 * PURPOSE:         Boot Library Initialization
 * PROGRAMMER:      Alex Ionescu (alex.ionescu@reactos.org)
 */

/* INCLUDES ******************************************************************/

#include "bl.h"

/* DATA VARIABLES ************************************************************/

BL_LIBRARY_PARAMETERS BlpLibraryParameters;
PBL_DEVICE_DESCRIPTOR BlpBootDevice;
PWCHAR BlpApplicationBaseDirectory;
PBOOT_APPLICATION_PARAMETER_BLOCK BlpApplicationParameters;
BL_LOADED_APPLICATION_ENTRY BlpApplicationEntry;
BOOLEAN BlpLibraryParametersInitialized;
ULONG BlpApplicationFlags;

ULONG PdPersistAllocations;
LIST_ENTRY BlpPdListHead;

/* FUNCTIONS *****************************************************************/

/*++
 * @name InitializeLibrary
 *
 *     The InitializeLibrary function initializes the Boot Library.
 *
 * @param  BootParameters
 *         Pointer to the Boot Application Parameter Block.
 *
 * @param  LibraryParameters
 *         Pointer to the Boot Library Parameters.
 *
 * @return NT_SUCCESS if the boot library was loaded correctly, relevant error
 *         otherwise.
 *
 *--*/
NTSTATUS
InitializeLibrary (
    _In_ PBOOT_APPLICATION_PARAMETER_BLOCK BootAppParameters,
    _In_ PBL_LIBRARY_PARAMETERS LibraryParameters
    )
{
    NTSTATUS Status;
    PBL_MEMORY_DATA MemoryData;
    PBL_APPLICATION_ENTRY AppEntry;
    PBL_FIRMWARE_DESCRIPTOR FirmwareDescriptor;
    LARGE_INTEGER BootFrequency;
    ULONG_PTR ParamPointer;

    /* Validate correct Boot Application data */
    ParamPointer = (ULONG_PTR)BootAppParameters;
    if (!(BootAppParameters) ||
        (BootAppParameters->Signature[0] != BOOT_APPLICATION_SIGNATURE_1) ||
        (BootAppParameters->Signature[1] != BOOT_APPLICATION_SIGNATURE_2) ||
        (BootAppParameters->Size < sizeof(*BootAppParameters)))
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Quickie;
    }

    /* Get sub-structures */
    MemoryData = (PBL_MEMORY_DATA)(ParamPointer + BootAppParameters->MemoryDataOffset);
    FirmwareDescriptor = (PBL_FIRMWARE_DESCRIPTOR)(ParamPointer + BootAppParameters->FirmwareParametersOffset);
    AppEntry = (PBL_APPLICATION_ENTRY)(ParamPointer + BootAppParameters->AppEntryOffset);
    BlpBootDevice = (PBL_DEVICE_DESCRIPTOR)(ParamPointer + BootAppParameters->BootDeviceOffset);
    BlpApplicationBaseDirectory = LibraryParameters->ApplicationBaseDirectory;

    /* Initialize the firmware table */
    Status = BlpFwInitialize(0, FirmwareDescriptor);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Find boot application entry */
    if (strncmp(AppEntry->Signature, BL_APP_ENTRY_SIGNATURE, 7))
    {
        Status = STATUS_INVALID_PARAMETER_9;
        goto Quickie;
    }

    /* Read parameters */
    BlpApplicationParameters = BootAppParameters;
    BlpLibraryParameters = *LibraryParameters;

    /* Check if the caller sent us their internal BCD options */
    if (AppEntry->Flags & BL_APPLICATION_ENTRY_BCD_OPTIONS_INTERNAL)
    {
        /* These are external to us now, as far as we are concerned */
        AppEntry->Flags &= ~BL_APPLICATION_ENTRY_BCD_OPTIONS_INTERNAL;
        AppEntry->Flags |= BL_APPLICATION_ENTRY_BCD_OPTIONS_EXTERNAL;
    }

    /* Save the application entry flags */
    BlpApplicationEntry.Flags = AppEntry->Flags;

    /* Copy the GUID and point to the options */
    BlpApplicationEntry.Guid = AppEntry->Guid;
    BlpApplicationEntry.BcdData = &AppEntry->BcdData;

    /* Everything has been captured */
    BlpLibraryParametersInitialized = TRUE;

    /* Initialize the architecture (PM or RM) switching */
    Status = BlpArchInitialize(0);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Initialize the memory manager */
    Status = BlpMmInitialize(MemoryData,
                             BootAppParameters->MemoryTranslationType,
                             LibraryParameters);
    if (!NT_SUCCESS(Status))
    {
        EfiPrintf(L"MM init failed!\r\n");
        goto Quickie;
    }

    /* Initialize firmware now that the heap, etc works */
    Status = BlpFwInitialize(1, FirmwareDescriptor);
    if (!NT_SUCCESS(Status))
    {
        /* Destroy memory manager in phase 1 */
        //BlpMmDestroy(1);
        EfiPrintf(L"Firmware2 init failed!\r\n");
        return Status;
    }

    /* Modern systems have an undocumented BCD system for the boot frequency */
    Status = BlGetBootOptionInteger(BlpApplicationEntry.BcdData,
                                    0x15000075,
                                    (PULONGLONG)&BootFrequency.QuadPart);
    if (NT_SUCCESS(Status) && (BootFrequency.QuadPart))
    {
        /* Use it if present */
        BlpTimePerformanceFrequency = BootFrequency.QuadPart;
    }
    else
    {
        /* Use the TSC for calibration */
        Status = BlpTimeCalibratePerformanceCounter();
        if (!NT_SUCCESS(Status))
        {
            /* Destroy memory manager in phase 1 */
            EfiPrintf(L"TSC calibration failed\r\n");
            //BlpMmDestroy(1);
            return Status;
        }
    }

    /* Now setup the rest of the architecture (IDT, etc) */
    Status = BlpArchInitialize(1);
    if (!NT_SUCCESS(Status))
    {
        /* Destroy memory manager in phase 1 */
        EfiPrintf(L"Arch2 init failed\r\n");
        //BlpMmDestroy(1);
        return Status;
    }

#ifdef BL_TPM_SUPPORT
    /* Initialize support for Trusted Platform Module v1.2 */
    BlpTpmInitialize();
#endif

#ifdef BL_TPM_SUPPORT
    /* Initialize the event manager */
    EnSubsystemInitialized = 1;
    InitializeListHead(&EnEventNotificationList);
#endif

    /* Initialize the I/O Manager */
    Status = BlpIoInitialize();
    if (!NT_SUCCESS(Status))
    {
        /* Destroy memory manager in phase 1 and the event manager */
        EfiPrintf(L"IO init failed\r\n");
#ifdef BL_TPM_SUPPORT
        if (EnSubsystemInitialized)
        {
            BlpEnDestroy();
        }
#endif
        //BlpMmDestroy(1);
        return Status;
    }

#ifdef BL_NET_SUPPORT
    /* Initialize the network stack */
    Status = BlNetInitialize();
    if (!NT_SUCCESS(Status))
    {
        /* Destroy the I/O, event, and memory managers in phase 1 */
        BlpIoDestroy();
#ifdef BL_TPM_SUPPORT
        if (EnSubsystemInitialized)
        {
            BlpEnDestroy();
        }
#endif
        BlpMmDestroy(1);
        return Status;
    }
#endif

    /* Initialize the utility library */
    Status = BlUtlInitialize();
    if (!NT_SUCCESS(Status))
    {
        /* Destroy the network, I/O, event, and memory managers in phase 1 */
#ifdef BL_NET_SUPPORT
        BlNetDestroy();
#endif
        //BlpIoDestroy();
#ifdef BL_TPM_SUPPORT
        if (EnSubsystemInitialized)
        {
            BlpEnDestroy();
        }
#endif
        //BlpMmDestroy(1);
        EfiPrintf(L"Util init failed\r\n");
        return Status;
    }

#ifdef BL_KD_SUPPORT
    /* Initialize PCI Platform Support */
    PltInitializePciConfiguration();
#endif

#ifdef BL_SECURE_BOOT_SUPPORT
    /* Read the current SecureBoot Policy*/
    Status = BlSecureBootSetActivePolicyFromPersistedData();
    if (!NT_SUCCESS(Status))
    {
        /* Destroy everything that we've currently set up */
#ifdef BL_KD_SUPPORT
        PltDestroyPciConfiguration();
#endif
#ifdef BL_NET_SUPPORT
        BlNetDestroy();
#endif
        BlpIoDestroy();
#ifdef BL_TPM_SUPPORT
        if (EnSubsystemInitialized)
        {
            BlpEnDestroy();
        }
#endif
        BlpMmDestroy(1);
        return Status;
    }
#endif

#ifdef BL_TPM_SUPPORT
    /* Initialize phase 0 of the security subsystem */
    SipInitializePhase0();
#endif

#ifdef BL_KD_SUPPORT
    /* Bring up the boot debugger, now that SecureBoot has been processed */
    BlBdInitialize();
#endif

#ifdef BL_ETW_SUPPORT
    /* Initialize internal logging */
    BlpLogInitialize();
#endif

    /* Are graphics enabled? */
    if (!(LibraryParameters->LibraryFlags & BL_LIBRARY_FLAG_NO_DISPLAY))
    {
        /* Initialize the graphics library */
        BlpDisplayInitialize(LibraryParameters->LibraryFlags);
    }

    /* Initialize the boot application persistent data */
    PdPersistAllocations = 0;
    InitializeListHead(&BlpPdListHead);

#ifdef BL_TPM_SUPPORT
    /* Now setup the security subsystem in phase 1 */
    BlpSiInitialize(1);
#endif

    /* Setup the text, UI and font resources */
    Status = BlpResourceInitialize();
    if (!NT_SUCCESS(Status))
    {
        /* Tear down everything if this failed */
        if (!(LibraryParameters->LibraryFlags & BL_LIBRARY_FLAG_NO_DISPLAY))
        {
//            BlpDisplayDestroy();
        }
#ifdef BL_KD_SUPPORT
        BlpBdDestroy();
        PltDestroyPciConfiguration();
#endif
#ifdef BL_NET_SUPPORT
        BlNetDestroy();
#endif
        //BlpIoDestroy();
#ifdef BL_TPM_SUPPORT
        if (EnSubsystemInitialized)
        {
            BlpEnDestroy();
        }
#endif
        //BlpMmDestroy(1);
        return Status;
    }

#if BL_BITLOCKER_SUPPORT
    /* Setup the boot cryptography library */
    g_pEnvironmentData = &SymCryptEnvironmentWindowsBootLibrary;
    if (SymCryptEnvWindowsBootLibInit)
    {
        SymCryptEnvWindowsBootLibInit();
    }
#endif

    /* We are fully initialized, remember this and exit with success */
    BlpLibraryParameters.LibraryFlags |= BL_LIBRARY_FLAG_INITIALIZATION_COMPLETED;
    Status = STATUS_SUCCESS;

Quickie:
    return Status;
}

/*++
 * @name BlInitializeLibrary
 *
 *     The BlInitializeLibrary function initializes, or re-initializes, the
 *     Boot Library.
 *
 * @param  BootParameters
 *         Pointer to the Boot Application Parameter Block.
 *
 * @param  LibraryParameters
 *         Pointer to the Boot Library Parameters.
 *
 * @return NT_SUCCESS if the boot library was loaded correctly, relevant error
 *         otherwise.
 *
 *--*/
NTSTATUS
BlInitializeLibrary(
    _In_ PBOOT_APPLICATION_PARAMETER_BLOCK BootAppParameters,
    _In_ PBL_LIBRARY_PARAMETERS LibraryParameters
    )
{
    NTSTATUS Status;

    /* Are we re-initializing the library? */
    if (LibraryParameters->LibraryFlags & BL_LIBRARY_FLAG_REINITIALIZE)
    {
        /* From scratch? */
        BlpLibraryParameters = *LibraryParameters;
        if (LibraryParameters->LibraryFlags & BL_LIBRARY_FLAG_REINITIALIZE_ALL)
        {
#ifdef BL_TPM_SUPPORT
            /* Reinitialize the TPM security enclave as BCD hash changed */
            BlpSiInitialize(1);
#endif
#ifdef BL_KD_SUPPORT
            /* Reinitialize the boot debugger as BCD debug options changed */
            BlBdInitialize();
#endif

            /* Reparse the bad page list now that the BCD has been reloaded */
            BlMmRemoveBadMemory();

            /* Reparse the low/high physical address limits as well */
            BlpMmInitializeConstraints();

            /* Redraw the graphics console as needed */
            BlpDisplayInitialize(LibraryParameters->LibraryFlags);

            /* Reinitialize resources (language may have changed) */
            BlpResourceInitialize();
        }

        /* Nothing to do, we're done */
        Status = STATUS_SUCCESS;
    }
    else
    {
        /* Nope, this is first time initialization */
        Status = InitializeLibrary(BootAppParameters, LibraryParameters);
    }

    /* Return initialization status */
    return Status;
}

VOID
BlDestroyLibrary (
    VOID
    )
{
    EfiPrintf(L"Destroy not yet implemented\r\n");
    return;
}

PGUID
BlGetApplicationIdentifier (
    VOID
    )
{
    /* Return the GUID, if one was present */
    return (BlpApplicationEntry.Flags & BL_APPLICATION_ENTRY_FLAG_NO_GUID) ?
            NULL : &BlpApplicationEntry.Guid;
}

NTSTATUS
BlGetApplicationBaseAndSize (
    _Out_ PVOID* ImageBase, 
    _Out_ PULONG ImageSize
    )
{
    /* Fail if output parameters are missing */
    if (!ImageBase || !ImageSize)
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Return the requested data */
    *ImageBase = (PVOID)(ULONG_PTR)BlpApplicationParameters->ImageBase;
    *ImageSize = BlpApplicationParameters->ImageSize;
    return STATUS_SUCCESS;
}

VOID
BlDestroyBootEntry (
    _In_ PBL_LOADED_APPLICATION_ENTRY AppEntry
    )
{
    /* Check if we had allocated BCD options */
    if (AppEntry->Flags & BL_APPLICATION_ENTRY_BCD_OPTIONS_INTERNAL)
    {
        BlMmFreeHeap(AppEntry->BcdData);
    }

    /* Free the entry itself */
    BlMmFreeHeap(AppEntry);
}

NTSTATUS
BlPdQueryData (
    _In_ const GUID* DataGuid,
    _In_ PVOID Unknown,
    _Inout_ PBL_PD_DATA_BLOB DataBlob
    )
{
    /* Check for invalid or missing parameters */
    if (!(DataBlob) ||
        !(DataGuid) ||
        ((DataBlob->BlobSize) && !(DataBlob->Data)))
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Check if there's no persistent data blobs */
    if (IsListEmpty(&BlpPdListHead))
    {
        return STATUS_NOT_FOUND;
    }

    /* Not yet handled, TODO */
    EfiPrintf(L"Boot persistent data not yet implemented\r\n");
    return STATUS_NOT_IMPLEMENTED;
}
