/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/modules/xtos/xtos.cc
 * DESCRIPTION:     XTOS boot protocol support
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtos.hh>


/* XTOS module information */
MODULE_AUTHOR(L"Rafal Kupiec <belliash@codingworkshop.eu.org>");
MODULE_DESCRIPTION(L"XTOS boot protocol support");
MODULE_DEPENDENCY(L"acpi framebuf pecoff");
MODULE_LICENSE(L"GPLv3");
MODULE_VERSION(L"0.1");


/**
 * Starts the operating system according to the provided parameters using XTOS boot protocol.
 *
 * @param Parameters
 *        Input parameters with detailed system configuration like boot device or kernel path.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Xtos::BootSystem(IN PXTBL_BOOT_PARAMETERS Parameters)
{
    EFI_GUID PeCoffProtocolGuid = XT_PECOFF_IMAGE_PROTOCOL_GUID;
    EFI_HANDLE DiskHandle, ProtocolHandle;
    PEFI_FILE_HANDLE FsHandle, BootDir;
    PWCHAR SystemPath;
    EFI_STATUS Status;

    /* Print debug message */
    XtLdrProtocol->Debug.Print(L"XTOS boot protocol activated\n");

    /* Open the XT PE/COFF protocol */
    Status = XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID *)&PeCoffProtocol, &PeCoffProtocolGuid);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open loader protocol */
        XtLdrProtocol->Debug.Print(L"ERROR: Unable to load PE/COFF image protocol\n");
        return STATUS_EFI_PROTOCOL_ERROR;
    }

    /* Check device path */
    if(Parameters->DevicePath == NULLPTR)
    {
        /* No device path set */
        XtLdrProtocol->Debug.Print(L"ERROR: No device path provided, unable to boot system\n");
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Check if system path is set */
    if(Parameters->SystemPath != NULLPTR)
    {
        /* Make sure system path begins with backslash, the only separator supported by EFI */
        if(Parameters->SystemPath[0] == '/')
        {
            /* Replace directory separator if needed */
            Parameters->SystemPath[0] = '\\';
        }

        /* Validate system path */
        SystemPath = &Parameters->SystemPath[1];
        while(*SystemPath)
        {
            /* Make sure it does not point to any subdirectory and not contains special characters */
            if(((*SystemPath | 32) - 'a' >= 26) && ((*SystemPath - '0') >= 10))
            {
                /* Invalid path specified */
                XtLdrProtocol->Debug.Print(L"ERROR: System path does not point to the valid XTOS installation\n");
                return STATUS_EFI_INVALID_PARAMETER;
            }
            /* Check next character in the path */
            SystemPath++;
        }
    }
    else
    {
        /* Fallback to '/ExectOS' by default */
        XtLdrProtocol->Debug.Print(L"WARNING: No system path set, falling back to defaults\n");
        Parameters->SystemPath = (PWCHAR)L"\\ExectOS";
    }

    /* Check if kernel file is set */
    if(Parameters->KernelFile == NULLPTR)
    {
        /* No kernel filename set, fallback to default */
        XtLdrProtocol->Debug.Print(L"WARNING: No kernel file specified, falling back to defaults\n");
        Parameters->KernelFile = (PWCHAR)L"xtoskrnl.exe";
    }

    /* Check if provided any kernel boot arguments */
    if(Parameters->Parameters == NULLPTR)
    {
        /* No argument supplied */
        Parameters->Parameters = (PWCHAR)L"";
    }

    /* Print a debug message */
    XtLdrProtocol->Debug.Print(L"[XTOS] ARC Path: %S\n"
                               L"[XTOS] System Path: %S\n"
                               L"[XTOS] Kernel File: %S\n"
                               L"[XTOS] Boot Arguments: %S\n",
                               Parameters->ArcName, Parameters->SystemPath,
                               Parameters->KernelFile, Parameters->Parameters);

    /* Open EFI volume */
    Status = XtLdrProtocol->Disk.OpenVolume(Parameters->DevicePath, &DiskHandle, &FsHandle);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open a volume */
        XtLdrProtocol->Debug.Print(L"ERROR: Unable to open boot volume\n");
        return Status;
    }

    /* System path has to point to the boot directory */
    XtLdrProtocol->WideString.Concatenate(Parameters->SystemPath, (PWCHAR)L"\\Boot", 0);

    /* Open XTOS system boot directory */
    Status = FsHandle->Open(FsHandle, &BootDir, Parameters->SystemPath, EFI_FILE_MODE_READ, 0);
    FsHandle->Close(FsHandle);

    /* Check if system path directory opened successfully */
    if(Status == STATUS_EFI_NOT_FOUND)
    {
        /* Directory not found, nothing to load */
        XtLdrProtocol->Debug.Print(L"ERROR: System boot directory not found\n");

        /* Close volume */
        XtLdrProtocol->Disk.CloseVolume(&DiskHandle);
        return Status;
    }
    else if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open directory */
        XtLdrProtocol->Debug.Print(L"ERROR: Unable to open system boot directory\n");
        XtLdrProtocol->Disk.CloseVolume(&DiskHandle);
        return Status;
    }

    /* Start boot sequence */
    return RunBootSequence(BootDir, Parameters);
}

/**
 * Returns information about frame buffer in XTOS compatible format.
 *
 * @param InformationBlock
 *        A pointer to memory area containing XT structure where all the information will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
Xtos::GetDisplayInformation(OUT PSYSTEM_RESOURCE_FRAMEBUFFER FrameBufferResource,
                            IN PEFI_PHYSICAL_ADDRESS FrameBufferBase,
                            IN PULONG_PTR FrameBufferSize,
                            IN PXTBL_FRAMEBUFFER_MODE_INFORMATION FrameBufferModeInfo)
{
    /* Fill in frame buffer resource */
    FrameBufferResource->Header.PhysicalAddress = (PVOID)*FrameBufferBase;
    FrameBufferResource->Header.ResourceType = SystemResourceFrameBuffer;
    FrameBufferResource->Header.ResourceSize = sizeof(SYSTEM_RESOURCE_FRAMEBUFFER);
    FrameBufferResource->BufferSize = *FrameBufferSize;
    FrameBufferResource->Width = FrameBufferModeInfo->Width;
    FrameBufferResource->Height = FrameBufferModeInfo->Height;
    FrameBufferResource->Depth = FrameBufferModeInfo->Depth;
    FrameBufferResource->BitsPerPixel = FrameBufferModeInfo->BitsPerPixel;
    FrameBufferResource->PixelsPerScanLine = FrameBufferModeInfo->PixelsPerScanLine;
    FrameBufferResource->Pitch = FrameBufferModeInfo->Pitch;
    FrameBufferResource->Pixels.BlueShift = FrameBufferModeInfo->PixelInformation.BlueShift;
    FrameBufferResource->Pixels.BlueSize = FrameBufferModeInfo->PixelInformation.BlueSize;
    FrameBufferResource->Pixels.GreenShift = FrameBufferModeInfo->PixelInformation.GreenShift;
    FrameBufferResource->Pixels.GreenSize = FrameBufferModeInfo->PixelInformation.GreenSize;
    FrameBufferResource->Pixels.RedShift = FrameBufferModeInfo->PixelInformation.RedShift;
    FrameBufferResource->Pixels.RedSize = FrameBufferModeInfo->PixelInformation.RedSize;
    FrameBufferResource->Pixels.ReservedShift = FrameBufferModeInfo->PixelInformation.ReservedShift;
    FrameBufferResource->Pixels.ReservedSize = FrameBufferModeInfo->PixelInformation.ReservedSize;
}

XTCDECL
EFI_STATUS
Xtos::GetMemoryDescriptorList(IN PXTBL_PAGE_MAPPING PageMap,
                              IN EFI_PHYSICAL_ADDRESS PhysicalBase,
                              IN PVOID VirtualBase,
                              OUT PLIST_ENTRY MemoryDescriptorList)
{
    PLOADER_MEMORY_DESCRIPTOR Descriptor;
    PXTBL_MEMORY_MAPPING MemoryMapping;
    PLIST_ENTRY ListEntry;

    /* Initialize the descriptor pointer to the start of the allocated physical buffer */
    Descriptor = (PLOADER_MEMORY_DESCRIPTOR)PhysicalBase;

    /* Get the first entry from the internal boot loader memory map */
    ListEntry = PageMap->MemoryMap.Flink;

    /* Iterate through the internal memory map and populate the loader descriptor list */
    while(ListEntry != &PageMap->MemoryMap)
    {
        /* Retrieve the internal memory mapping record from the current list entry */
        MemoryMapping = CONTAIN_RECORD(ListEntry, XTBL_MEMORY_MAPPING, ListEntry);

        /* Transfer memory type and address information to the kernel descriptor */
        Descriptor->MemoryType = MemoryMapping->MemoryType;
        Descriptor->BasePage = (UINT_PTR)(MemoryMapping->PhysicalAddress / EFI_PAGE_SIZE);
        Descriptor->PageCount = (ULONG)MemoryMapping->NumberOfPages;

        /* Link the entry */
        XtLdrProtocol->LinkedList.InsertTail(MemoryDescriptorList, &Descriptor->ListEntry);

        /* Move to the next slot in the allocated buffer */
        Descriptor++;
        ListEntry = ListEntry->Flink;
    }

    /* Convert all physical link pointers in the list to their corresponding virtual addresses */
    XtLdrProtocol->Memory.PhysicalListToVirtual(PageMap, MemoryDescriptorList, (PVOID)PhysicalBase, VirtualBase);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

XTCDECL
EFI_STATUS
Xtos::GetSystemResourcesList(IN PXTBL_PAGE_MAPPING PageMap,
                             IN EFI_PHYSICAL_ADDRESS PhysicalBase,
                             IN PVOID VirtualBase,
                             IN PVOID FrameBufferVirtualBase,
                             OUT PLIST_ENTRY SystemResourcesList)
{
    XTSTATUS Status;
    EFI_HANDLE ProtocolHandle;
    EFI_GUID AcpiGuid = XT_ACPI_PROTOCOL_GUID;
    EFI_GUID FrameBufGuid = XT_FRAMEBUFFER_PROTOCOL_GUID;
    PXTBL_ACPI_PROTOCOL AcpiProtocol;
    PXTBL_FRAMEBUFFER_PROTOCOL FrameBufProtocol;
    XTBL_FRAMEBUFFER_MODE_INFORMATION FbModeInfo;
    EFI_PHYSICAL_ADDRESS FbAddress;
    EFI_PHYSICAL_ADDRESS OriginalPhysicalBase;
    ULONG_PTR FbSize;
    PSYSTEM_RESOURCE_FRAMEBUFFER FrameBufferResource;
    PSYSTEM_RESOURCE_ACPI AcpiResource;

    /* Save original physical base */
    OriginalPhysicalBase = PhysicalBase;

    AcpiResource = (PSYSTEM_RESOURCE_ACPI)PhysicalBase;
    XtLdrProtocol->Memory.ZeroMemory(AcpiResource, sizeof(SYSTEM_RESOURCE_ACPI));

    /* Load ACPI protocol */
    Status = XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&AcpiProtocol, &AcpiGuid);
    if(Status != STATUS_EFI_SUCCESS)
    {
        return Status;
    }

    AcpiResource->Header.ResourceType = SystemResourceAcpi;
    AcpiResource->Header.ResourceSize = sizeof(SYSTEM_RESOURCE_ACPI);

    /* Get APIC and XSDP/RSDP addresses */
    AcpiProtocol->GetApicBase(&AcpiResource->ApicBase);
    AcpiProtocol->GetAcpiDescriptionPointer(&AcpiResource->Header.PhysicalAddress);

    /* No need to map ACPI */
    AcpiResource->Header.VirtualAddress = 0;

    XtLdrProtocol->LinkedList.InsertTail(SystemResourcesList, &AcpiResource->Header.ListEntry);

    /* Close ACPI protocol */
    XtLdrProtocol->Protocol.Close(&ProtocolHandle, &AcpiGuid);

    PhysicalBase = PhysicalBase + sizeof(SYSTEM_RESOURCE_ACPI);
    FrameBufferResource = (PSYSTEM_RESOURCE_FRAMEBUFFER)PhysicalBase;
    XtLdrProtocol->Memory.ZeroMemory(FrameBufferResource, sizeof(SYSTEM_RESOURCE_FRAMEBUFFER));

    /* Load FrameBuffer protocol */
    Status = XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&FrameBufProtocol, &FrameBufGuid);
    if(Status == STATUS_EFI_SUCCESS)
    {
        /* Get FrameBuffer information */
        Status = FrameBufProtocol->GetDisplayInformation(&FbAddress, &FbSize, &FbModeInfo);
        if(Status == STATUS_EFI_SUCCESS)
        {

            /* Store information about FrameBuffer device */
            GetDisplayInformation(FrameBufferResource, &FbAddress, &FbSize, &FbModeInfo);
        }
    }
    if(Status != STATUS_EFI_SUCCESS)
    {
        return Status;
    }

    /* Assign the pre-mapped virtual address to the resource block */
    FrameBufferResource->Header.VirtualAddress = FrameBufferVirtualBase;
    XtLdrProtocol->Protocol.Close(&ProtocolHandle, &FrameBufGuid);

    XtLdrProtocol->LinkedList.InsertTail(SystemResourcesList, &FrameBufferResource->Header.ListEntry);

    /* Convert list pointers to virtual */
    XtLdrProtocol->Memory.PhysicalListToVirtual(PageMap, SystemResourcesList, (PVOID)OriginalPhysicalBase, VirtualBase);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Checks if APIC is present in the system and finds its base address.
 *
 * @param MemoryMappings
 *        Supplies a pointer to linked list containing all memory mappings.
 *
 * @return This routine returns an EFI status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Xtos::InitializeApicBase(IN PXTBL_PAGE_MAPPING PageMap)
{
    EFI_GUID AcpiGuid = XT_ACPI_PROTOCOL_GUID;
    PXTBL_ACPI_PROTOCOL AcpiProtocol;
    EFI_HANDLE ProtocolHandle;
    PVOID ApicBaseAddress;
    EFI_STATUS Status;

    /* Open ACPI protocol */
    Status = XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&AcpiProtocol, &AcpiGuid);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* ACPI protocol not found */
        return Status;
    }

    /* Get APIC base address */
    Status = AcpiProtocol->GetApicBase(&ApicBaseAddress);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to get APIC base address */
        return Status;
    }

    /* Map APIC base address */
    XtLdrProtocol->Memory.MapVirtualMemory(PageMap, APIC_BASE, (ULONGLONG)ApicBaseAddress, 1, LoaderFirmwarePermanent);
    return STATUS_EFI_SUCCESS;
}

/**
 * Initializes and maps the kernel initialization block.
 *
 * @param MemoryMappings
 *        Supplies a pointer to linked list containing all memory mappings.
 *
 * @param VirtualAddress
 *        Supplies a pointer to the next valid, free and available virtual address.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Xtos::InitializeLoaderBlock(IN PXTBL_PAGE_MAPPING PageMap,
                            IN OUT PVOID *VirtualAddress,
                            IN PXTBL_BOOT_PARAMETERS Parameters)
{
    EFI_PHYSICAL_ADDRESS FbPhysicalAddress, PhysicalBlock, PhysicalDescriptor, PhysicalResources;
    PVOID FbVirtualAddress, VirtualBlock, VirtualResources, VirtualDescriptor;
    UINT BlockPages, DescriptorPages, FbPages, ParametersSize, ResourcesPages;
    XTBL_FRAMEBUFFER_MODE_INFORMATION FbModeInfo;
    PXTBL_FRAMEBUFFER_PROTOCOL FrameBufProtocol;
    PKERNEL_INITIALIZATION_BLOCK LoaderBlock;
    EFI_HANDLE ProtocolHandle;
    EFI_STATUS Status;
    ULONG_PTR FbSize;

    EFI_GUID FrameBufGuid = XT_FRAMEBUFFER_PROTOCOL_GUID;

    /* Initialize Framebuffer information */
    FbPhysicalAddress = 0;
    FbSize = 0;
    FbVirtualAddress = NULLPTR;
    FbPages = 0;

    /* Calculate size of parameters */
    ParametersSize = (XtLdrProtocol->WideString.Length(Parameters->Parameters, 0) + 1) * sizeof(WCHAR);

    /* Calculate number of pages needed for initialization block */
    BlockPages = EFI_SIZE_TO_PAGES(sizeof(KERNEL_INITIALIZATION_BLOCK) + ParametersSize);
    ResourcesPages = EFI_SIZE_TO_PAGES(sizeof(SYSTEM_RESOURCE_ACPI) + sizeof(SYSTEM_RESOURCE_FRAMEBUFFER));

    /* Query Framebuffer size for allocation */
    if(XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&FrameBufProtocol, &FrameBufGuid) == STATUS_EFI_SUCCESS)
    {
        /* Get FrameBuffer information */
        FrameBufProtocol->GetDisplayInformation(&FbPhysicalAddress, &FbSize, &FbModeInfo);
        XtLdrProtocol->Protocol.Close(&ProtocolHandle, &FrameBufGuid);
    }
    FbPages = EFI_SIZE_TO_PAGES(FbSize);

    /* Precommit page map to allocate memory */
    XtLdrProtocol->Memory.CommitPageMap(PageMap);

    /* Calculate number of pages needed for memory descriptor list */
    DescriptorPages = EFI_SIZE_TO_PAGES(PageMap->MapSize * sizeof(LOADER_MEMORY_DESCRIPTOR) * 2);

    /* Allocate memory for the kernel initialization block and boot parameters */
    Status = XtLdrProtocol->Memory.AllocatePages(AllocateAnyPages, BlockPages, &PhysicalBlock);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, return status code */
        return Status;
    }

    /* Allocate memory for the system resources data structures */
    Status = XtLdrProtocol->Memory.AllocatePages(AllocateAnyPages, ResourcesPages, &PhysicalResources);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, return status code */
        return Status;
    }

    /* Allocate memory for the memory descriptor list */
    Status = XtLdrProtocol->Memory.AllocatePages(AllocateAnyPages, DescriptorPages, &PhysicalDescriptor);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, return status code */
        return Status;
    }

    /* Map the Kernel Initialization Block into virtual memory and advance the virtual address pointer */
    VirtualBlock = *VirtualAddress;
    XtLdrProtocol->Memory.MapVirtualMemory(PageMap, (ULONGLONG)VirtualBlock, PhysicalBlock, BlockPages, LoaderSystemBlock);
    *VirtualAddress = (PUINT8)*VirtualAddress + (BlockPages * EFI_PAGE_SIZE);

    /* Map the system resources physical memory into virtual address space and update the allocation pointer */
    VirtualResources = *VirtualAddress;
    XtLdrProtocol->Memory.MapVirtualMemory(PageMap, (ULONGLONG)VirtualResources, PhysicalResources, ResourcesPages, LoaderFirmwarePermanent);
    *VirtualAddress = (PUINT8)*VirtualAddress + (ResourcesPages * EFI_PAGE_SIZE);

    /* Check if a framebuffer was detected and requires memory mapping */
    if(FbPages > 0)
    {
        /* Map the framebuffer physical memory range into virtual address space */
        FbVirtualAddress = *VirtualAddress;
        XtLdrProtocol->Memory.MapVirtualMemory(PageMap, (ULONGLONG)FbVirtualAddress, FbPhysicalAddress, FbPages, LoaderFirmwarePermanent);
        *VirtualAddress = (PUINT8)*VirtualAddress + (FbPages * EFI_PAGE_SIZE);
    }

    /* Map the allocated physical memory for memory descriptors into the virtual address space */
    VirtualDescriptor = *VirtualAddress;
    XtLdrProtocol->Memory.MapVirtualMemory(PageMap, (ULONGLONG)VirtualDescriptor, PhysicalDescriptor, DescriptorPages, LoaderMemoryData);
    *VirtualAddress = (PUINT8)*VirtualAddress + (DescriptorPages * EFI_PAGE_SIZE);

    /* Set basic loader block properties */
    XtLdrProtocol->Memory.ZeroMemory((PVOID)PhysicalBlock, sizeof(KERNEL_INITIALIZATION_BLOCK) + ParametersSize);
    LoaderBlock = (PKERNEL_INITIALIZATION_BLOCK)PhysicalBlock;
    LoaderBlock->BlockSize = sizeof(KERNEL_INITIALIZATION_BLOCK);
    LoaderBlock->BlockVersion = INITIALIZATION_BLOCK_VERSION;
    LoaderBlock->ProtocolVersion = BOOT_PROTOCOL_VERSION;

    /* Set LoaderInformation block properties */
    LoaderBlock->LoaderInformation.DbgPrint = (PVOID)XtLdrProtocol->Debug.Print;

    /* Set FirmwareInformation block properties */
    LoaderBlock->FirmwareInformation.FirmwareType = SystemFirmwareEfi;
    // LoaderBlock->FirmwareInformation.EfiFirmware.EfiVersion = EfiSystemTable->Hdr.Revision;
    LoaderBlock->FirmwareInformation.EfiFirmware.EfiRuntimeServices = NULLPTR;

    /* Copy parameters to kernel initialization block */
    LoaderBlock->KernelParameters = (PWCHAR)((UINT_PTR)VirtualBlock + sizeof(KERNEL_INITIALIZATION_BLOCK));
    XtLdrProtocol->Memory.CopyMemory((PVOID)((UINT_PTR)LoaderBlock + sizeof(KERNEL_INITIALIZATION_BLOCK)),
                                     Parameters->Parameters, ParametersSize);

    /* Commit mappings */
    XtLdrProtocol->Memory.CommitPageMap(PageMap);

    /* Initialize system resources list */
    XtLdrProtocol->LinkedList.InitializeHead(&LoaderBlock->SystemResourcesListHead);
    Status = GetSystemResourcesList(PageMap, PhysicalResources, VirtualResources, FbVirtualAddress, &LoaderBlock->SystemResourcesListHead);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to initialize system resources list, return status code */
        return Status;
    }

    /* Initialize memory descriptor list */
    XtLdrProtocol->LinkedList.InitializeHead(&LoaderBlock->MemoryDescriptorListHead);
    Status = GetMemoryDescriptorList(PageMap, PhysicalDescriptor, VirtualDescriptor, &LoaderBlock->MemoryDescriptorListHead);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to initialize memory descriptor list, return status code */
        return Status;
    }

    /* Set boot image size */
    LoaderBlock->BootImageSize = (PFN_NUMBER)(((ULONGLONG)*VirtualAddress - KSEG0_BASE) / EFI_PAGE_SIZE);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

XTCDECL
EFI_STATUS
Xtos::InitializeModule(IN EFI_HANDLE ImageHandle,
                       IN PEFI_SYSTEM_TABLE SystemTable)
{
    EFI_GUID Guid = XT_XTOS_BOOT_PROTOCOL_GUID;
    EFI_STATUS Status;

    /* Open the XTLDR protocol */
    Status = BlGetXtLdrProtocol(SystemTable, ImageHandle, &XtLdrProtocol);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open loader protocol */
        return STATUS_EFI_PROTOCOL_ERROR;
    }

    /* Set routines available via XTOS boot protocol */
    BootProtocol.BootSystem = Xtos::BootSystem;

    /* Register XTOS boot protocol */
    XtLdrProtocol->Boot.RegisterProtocol(L"XTOS", &Guid);

    /* Install XTOS protocol */
    return XtLdrProtocol->Protocol.Install(&BootProtocol, &Guid);
}

/**
 * Loads XTOS PE/COFF module.
 *
 * @param SystemDir
 *        An EFI handle to the opened system directory containing a module that will be loaded.
 *
 * @param FileName
 *        An on disk filename of the module that will be loaded.
 *
 * @param VirtualAddress
 *        Optional virtual address pointing to the memory area where PE/COFF file will be loaded.
 *
 * @param MemoryType
 *        Supplies the type of memory to be assigned to the memory descriptor.
 *
 * @param ImageContext
 *        Supplies pointer to the memory area where loaded PE/COFF image context will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Xtos::LoadModule(IN PEFI_FILE_HANDLE SystemDir,
                 IN PWCHAR FileName,
                 IN PVOID VirtualAddress,
                 IN LOADER_MEMORY_TYPE MemoryType,
                 OUT PPECOFF_IMAGE_CONTEXT *ImageContext)
{
    PEFI_FILE_HANDLE ModuleHandle;
    USHORT MachineType, SubSystem;
    EFI_STATUS Status;

    /* Print debug message */
    XtLdrProtocol->Debug.Print(L"Loading %S ... \n", FileName);

    /* Open module file */
    Status = SystemDir->Open(SystemDir, &ModuleHandle, FileName, EFI_FILE_MODE_READ, 0);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Unable to open the file */
        XtLdrProtocol->Debug.Print(L"ERROR: Failed to open '%S'\n", FileName);
        return Status;
    }

    /* Load the PE/COFF image file */
    Status = PeCoffProtocol->LoadImage(ModuleHandle, MemoryType, VirtualAddress, (PVOID*)ImageContext);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Unable to load the file */
        XtLdrProtocol->Debug.Print(L"ERROR: Failed to load '%S'\n", FileName);
        return Status;
    }

    /* Close image file */
    ModuleHandle->Close(ModuleHandle);

    /* Check PE/COFF image machine type compatibility */
    PeCoffProtocol->GetMachineType(*ImageContext, &MachineType);
    if(MachineType != _ARCH_IMAGE_MACHINE_TYPE)
    {
        /* Machine type mismatch */
        XtLdrProtocol->Debug.Print(L"ERROR: Loaded incompatible PE/COFF image (machine type mismatch)\n");
        return STATUS_EFI_INCOMPATIBLE_VERSION;
    }

    /* Check PE/COFF image subsystem */
    PeCoffProtocol->GetSubSystem(*ImageContext, &SubSystem);
    if(SubSystem != PECOFF_IMAGE_SUBSYSTEM_XT_NATIVE_KERNEL &&
       SubSystem != PECOFF_IMAGE_SUBSYSTEM_XT_NATIVE_APPLICATION &&
       SubSystem != PECOFF_IMAGE_SUBSYSTEM_XT_NATIVE_DRIVER)
    {
        XtLdrProtocol->Debug.Print(L"WARNING: Loaded PE/COFF image with non-XT subsystem set\n");
    }

    /* Print debug message */
    XtLdrProtocol->Debug.Print(L"Loaded %S at PA: %P, VA: %P\n", FileName,
                               (*ImageContext)->PhysicalAddress, (*ImageContext)->VirtualAddress);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * This routine initiates an XTOS boot sequence.
 *
 * @param BootDir
 *        An EFI handle to the XTOS boot directory.
 *
 * @param Parameters
 *        Input parameters with detailed system configuration like boot device or kernel path.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Xtos::RunBootSequence(IN PEFI_FILE_HANDLE BootDir,
                      IN PXTBL_BOOT_PARAMETERS Parameters)
{
    EFI_GUID LoadedImageGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
    EFI_GUID FrameBufGuid = XT_FRAMEBUFFER_PROTOCOL_GUID;
    PKERNEL_INITIALIZATION_BLOCK KernelParameters;
    PXTBL_FRAMEBUFFER_PROTOCOL FrameBufProtocol;
    PPECOFF_IMAGE_CONTEXT ImageContext = NULLPTR;
    PEFI_LOADED_IMAGE_PROTOCOL ImageProtocol;
    PVOID VirtualAddress;
    PXT_ENTRY_POINT KernelEntryPoint;
    EFI_HANDLE ProtocolHandle;
    EFI_STATUS Status;
    XTBL_PAGE_MAPPING PageMap;
    BOOLEAN IdentityMapping;

    /* Initialize XTOS startup sequence */
    XtLdrProtocol->Debug.Print(L"Initializing XTOS startup sequence\n");

    /* Load FrameBuffer protocol */
    Status = XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&FrameBufProtocol, &FrameBufGuid);
    if(Status == STATUS_EFI_SUCCESS)
    {
        /* Make sure FrameBuffer is initialized */
        FrameBufProtocol->Initialize();
        FrameBufProtocol->SetScreenResolution(0, 0);
    }

    /* Close FrameBuffer protocol */
    XtLdrProtocol->Protocol.Close(&ProtocolHandle, &FrameBufGuid);

    /* Determine whether to use a sequential or an identity mapping strategy */
    IdentityMapping = DetermineMappingStrategy();

    /* Set base virtual memory area for the kernel mappings */
    VirtualAddress = (PVOID)(KSEG0_BASE);

    /* Initialize virtual memory mappings */
    XtLdrProtocol->Memory.InitializePageMap(&PageMap, DeterminePagingLevel(Parameters->Parameters), Size4K);

    /* Map all EFI memory regions */
    Status = XtLdrProtocol->Memory.MapEfiMemory(&PageMap, &VirtualAddress, IdentityMapping, NULLPTR);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Mapping failed */
        return Status;
    }

    /* Check mapping strategy */
    if(IdentityMapping)
    {
        /* Adjust virtual address to skip the identity-mapped physical range */
        VirtualAddress = (PVOID)((ULONGLONG)VirtualAddress + 0x800000000);
    }

    /* Load the kernel */
    Status = LoadModule(BootDir, Parameters->KernelFile, VirtualAddress, LoaderSystemCode, &ImageContext);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to load the kernel */
        return Status;
    }

    /* Add kernel image memory mapping */
    Status = XtLdrProtocol->Memory.MapVirtualMemory(&PageMap, (ULONGLONG)ImageContext->VirtualAddress,
                                                    (ULONGLONG)ImageContext->PhysicalAddress, ImageContext->ImagePages,
                                                    LoaderSystemCode);
    if(Status != STATUS_EFI_SUCCESS)
    {
        return Status;
    }

    /* Set next valid virtual address right after the kernel */
    VirtualAddress = (PUINT8)VirtualAddress + (ImageContext->ImagePages * EFI_PAGE_SIZE);

    /* Find and map APIC base address */
    Status = InitializeApicBase(&PageMap);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to setup kernel initialization block */
        XtLdrProtocol->Debug.Print(L"Failed to initialize APIC (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Build page map */
    Status = BuildPageMap(&PageMap);
    if(Status != STATUS_EFI_SUCCESS)
    {
        XtLdrProtocol->Debug.Print(L"Failed to build page map (Status code: %zX)\n", Status);
        return Status;
    }

    /* Store virtual address of kernel initialization block for future kernel call */
    KernelParameters = (PKERNEL_INITIALIZATION_BLOCK)VirtualAddress;

    /* Setup and map kernel initialization block */
    Status = InitializeLoaderBlock(&PageMap, &VirtualAddress, Parameters);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to setup kernel initialization block */
        XtLdrProtocol->Debug.Print(L"Failed to setup kernel initialization block (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Get kernel entry point */
    PeCoffProtocol->GetEntryPoint(ImageContext, (PVOID*)&KernelEntryPoint);

    /* Close boot directory handle */
    BootDir->Close(BootDir);

    /* Enable paging */
    XtLdrProtocol->Protocol.Open(&ProtocolHandle, (PVOID*)&ImageProtocol, &LoadedImageGuid);
    Status = EnablePaging(&PageMap);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to enable paging */
        XtLdrProtocol->Debug.Print(L"Failed to enable paging (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Call XTOS kernel */
    XtLdrProtocol->Debug.Print(L"Booting the XTOS kernel\n");
    KernelEntryPoint(KernelParameters);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * This routine is the entry point of the XT EFI boot loader module.
 *
 * @param ImageHandle
 *        Firmware-allocated handle that identifies the image.
 *
 * @param SystemTable
 *        Provides the EFI system table.
 *
 * @return This routine returns status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
XtLdrModuleMain(IN EFI_HANDLE ImageHandle,
                IN PEFI_SYSTEM_TABLE SystemTable)
{
    /* Initialize XTOS module */
    return Xtos::InitializeModule(ImageHandle, SystemTable);
}
