/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/ar/amd64/procsup.cc
 * DESCRIPTION:     AMD64 processor functionality support
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtos.hh>


/**
 * Gets the base address of the kernel boot stack.
 *
 * @return This routine returns a pointer to the kernel boot stack.
 *
 * @since XT 1.0
 */
XTAPI
PVOID
AR::ProcessorSupport::GetBootStack(VOID)
{
    /* Return base address of kernel boot stack */
    return (PVOID)((ULONG_PTR)BootStack + KERNEL_STACK_SIZE);
}

XTAPI
VOID
AR::ProcessorSupport::GetTrampolineInformation(IN TRAMPOLINE_TYPE TrampolineType,
                                               OUT PVOID *TrampolineCode,
                                               OUT PULONG TrampolineSize)
{
    /* Get trampoline information */
    switch(TrampolineType)
    {
        case TrampolineApStartup:
            /* Get AP startup trampoline information */
            *TrampolineCode = (PVOID)ArStartApplicationProcessor;
            *TrampolineSize = (ULONG_PTR)ArStartApplicationProcessorEnd -
                              (ULONG_PTR)ArStartApplicationProcessor;
            break;
        case TrampolineEnableXpa:
            /* Get Enable XPA trampoline information */
            *TrampolineCode = (PVOID)ArEnableExtendedPhysicalAddressing;
            *TrampolineSize = (ULONG_PTR)ArEnableExtendedPhysicalAddressingEnd -
                              (ULONG_PTR)ArEnableExtendedPhysicalAddressing;
            break;
        default:
            /* Unknown trampoline type */
            *TrampolineCode = NULLPTR;
            *TrampolineSize = 0;
            break;
    }
}

/**
 * Identifies processor type (vendor, model, stepping) as well as looks for available CPU features and stores them
 * in Processor Control Block (PRCB).
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::IdentifyProcessor(VOID)
{
    PKPROCESSOR_CONTROL_BLOCK Prcb;
    CPUID_REGISTERS CpuRegisters;
    CPUID_SIGNATURE CpuSignature;

    /* Get current processor control block */
    Prcb = KE::Processor::GetCurrentProcessorControlBlock();

    /* Get CPU vendor by issueing CPUID instruction */
    RtlZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
    CpuRegisters.Leaf = CPUID_GET_VENDOR_STRING;
    AR::CpuFunctions::CpuId(&CpuRegisters);

    /* Store CPU vendor in processor control block */
    Prcb->CpuId.Vendor = (CPU_VENDOR)CpuRegisters.Ebx;
    *(PULONG)&Prcb->CpuId.VendorName[0] = CpuRegisters.Ebx;
    *(PULONG)&Prcb->CpuId.VendorName[4] = CpuRegisters.Edx;
    *(PULONG)&Prcb->CpuId.VendorName[8] = CpuRegisters.Ecx;
    Prcb->CpuId.VendorName[12] = '\0';

    /* Get CPU standard features */
    RtlZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
    CpuRegisters.Leaf = CPUID_GET_STANDARD1_FEATURES;
    AR::CpuFunctions::CpuId(&CpuRegisters);

    /* Store CPU signature in processor control block */
    CpuSignature = *(PCPUID_SIGNATURE)&CpuRegisters.Eax;
    Prcb->CpuId.Family = CpuSignature.Family;
    Prcb->CpuId.Model = CpuSignature.Model;
    Prcb->CpuId.Stepping = CpuSignature.Stepping;

    /* CPU vendor specific quirks */
    if(Prcb->CpuId.Vendor == CPU_VENDOR_AMD)
    {
        /* AMD CPU */
        if(CpuSignature.Family == 0xF)
        {
            Prcb->CpuId.Family += CpuSignature.ExtendedFamily;
            Prcb->CpuId.Model += (CpuSignature.ExtendedModel << 4);
        }
    }
    else if(Prcb->CpuId.Vendor == CPU_VENDOR_INTEL)
    {
        /* Intel CPU */
        if(CpuSignature.Family == 0xF)
        {
            Prcb->CpuId.Family += CpuSignature.ExtendedFamily;
        }

        if((CpuSignature.Family == 0x6) || (CpuSignature.Family == 0xF))
        {
            Prcb->CpuId.Model += (CpuSignature.ExtendedModel << 4);
        }
    }
    else
    {
        /* Unknown CPU vendor */
        Prcb->CpuId.Vendor = CPU_VENDOR_UNKNOWN;
    }

    /* Identify processor features */
    IdentifyProcessorFeatures();
}

/**
 * Identifies processor features and stores them in Processor Control Block (PRCB).
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::IdentifyProcessorFeatures(VOID)
{
    ULONG MaxExtendedLeaf, MaxStandardLeaf;
    PKPROCESSOR_CONTROL_BLOCK Prcb;
    CPUID_REGISTERS CpuRegisters;

    /* Get current processor control block */
    Prcb = KE::Processor::GetCurrentProcessorControlBlock();

    /* Get maximum CPUID standard leaf */
    RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
    CpuRegisters.Leaf = CPUID_GET_VENDOR_STRING;
    AR::CpuFunctions::CpuId(&CpuRegisters);
    MaxStandardLeaf = CpuRegisters.Eax;

    /* Get maximum CPUID extended leaf */
    RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
    CpuRegisters.Leaf = CPUID_GET_EXTENDED_MAX;
    AR::CpuFunctions::CpuId(&CpuRegisters);
    MaxExtendedLeaf = CpuRegisters.Eax;

    /* Check if CPU supports standard features leaf */
    if(MaxStandardLeaf >= CPUID_GET_STANDARD1_FEATURES)
    {
        /* Get CPU standard features */
        RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
        CpuRegisters.Leaf = CPUID_GET_STANDARD1_FEATURES;
        AR::CpuFunctions::CpuId(&CpuRegisters);

        /* Store CPU standard features in processor control block */
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SSE3) Prcb->CpuId.FeatureBits |= KCF_SSE3;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_VMX) Prcb->CpuId.FeatureBits |= KCF_VMX;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SSSE3) Prcb->CpuId.FeatureBits |= KCF_SSSE3;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SSE4_1) Prcb->CpuId.FeatureBits |= KCF_SSE41;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SSE4_2) Prcb->CpuId.FeatureBits |= KCF_SSE42;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_X2APIC) Prcb->CpuId.FeatureBits |= KCF_X2APIC;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_POPCNT) Prcb->CpuId.FeatureBits |= KCF_POPCNT;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_TSC_DEADLINE) Prcb->CpuId.FeatureBits |= KCF_TSC_DEADLINE;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_AES) Prcb->CpuId.FeatureBits |= KCF_AES;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_XSAVE) Prcb->CpuId.FeatureBits |= KCF_XSAVE;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_AVX) Prcb->CpuId.FeatureBits |= KCF_AVX;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_RDRAND) Prcb->CpuId.FeatureBits |= KCF_RDRAND;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_VME) Prcb->CpuId.FeatureBits |= KCF_VME;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_PSE) Prcb->CpuId.FeatureBits |= KCF_LARGE_PAGE;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_TSC) Prcb->CpuId.FeatureBits |= KCF_RDTSC;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_PAE) Prcb->CpuId.FeatureBits |= KCF_PAE;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_MCE) Prcb->CpuId.FeatureBits |= KCF_MCE;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_CX8) Prcb->CpuId.FeatureBits |= KCF_CMPXCHG8B;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_APIC) Prcb->CpuId.FeatureBits |= KCF_APIC;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_SEP) Prcb->CpuId.FeatureBits |= KCF_FAST_SYSCALL;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_MTRR) Prcb->CpuId.FeatureBits |= KCF_MTRR;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_PGE) Prcb->CpuId.FeatureBits |= KCF_GLOBAL_PAGE;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_MCA) Prcb->CpuId.FeatureBits |= KCF_MCA;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_CMOV) Prcb->CpuId.FeatureBits |= KCF_CMOV;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_PAT) Prcb->CpuId.FeatureBits |= KCF_PAT;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_PSE36) Prcb->CpuId.FeatureBits |= KCF_PSE36;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_CLFLUSH) Prcb->CpuId.FeatureBits |= KCF_CLFLUSH;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_FXSR) Prcb->CpuId.FeatureBits |= KCF_FXSR;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_ACPI) Prcb->CpuId.FeatureBits |= KCF_ACPI;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_MMX) Prcb->CpuId.FeatureBits |= KCF_MMX;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_SSE) Prcb->CpuId.FeatureBits |= KCF_SSE;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_SSE2) Prcb->CpuId.FeatureBits |= KCF_SSE2;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_HTT) Prcb->CpuId.FeatureBits |= KCF_SMT;
    }

    /* Check if CPU supports standard7 features leaf */
    if(MaxStandardLeaf >= CPUID_GET_STANDARD7_FEATURES)
    {
        /* Get CPU standard features */
        RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
        CpuRegisters.Leaf = CPUID_GET_STANDARD7_FEATURES;
        AR::CpuFunctions::CpuId(&CpuRegisters);

        /* Store CPU standard7 features in processor control block */
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_FSGSBASE) Prcb->CpuId.FeatureBits |= KCF_FSGSBASE;
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_AVX2) Prcb->CpuId.FeatureBits |= KCF_AVX2;
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_SMEP) Prcb->CpuId.FeatureBits |= KCF_SMEP;
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_RDSEED) Prcb->CpuId.FeatureBits |= KCF_RDSEED;
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_SMAP) Prcb->CpuId.FeatureBits |= KCF_SMAP;
        if(CpuRegisters.Ebx & CPUID_FEATURES_EBX_SHA) Prcb->CpuId.FeatureBits |= KCF_SHA;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_LA57) Prcb->CpuId.FeatureBits |= KCF_LA57;
    }

    /* Check if CPU supports power management leaf */
    if(MaxStandardLeaf >= CPUID_GET_POWER_MANAGEMENT)
    {
        /* Get CPU power management features */
        RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
        CpuRegisters.Leaf = CPUID_GET_POWER_MANAGEMENT;
        AR::CpuFunctions::CpuId(&CpuRegisters);

        /* Store CPU power management features in processor control block */
        if(CpuRegisters.Eax & CPUID_FEATURES_EAX_ARAT) Prcb->CpuId.FeatureBits |= KCF_ARAT;
    }

    /* Check if CPU supports extended features leaf */
    if(MaxExtendedLeaf >= CPUID_GET_EXTENDED_FEATURES)
    {
        /* Get CPU extended features */
        RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
        CpuRegisters.Leaf = CPUID_GET_EXTENDED_FEATURES;
        AR::CpuFunctions::CpuId(&CpuRegisters);

        /* Store CPU extended features in processor control block */
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SVM) Prcb->CpuId.ExtendedFeatureBits |= KCF_SVM;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_SSE4A) Prcb->CpuId.ExtendedFeatureBits |= KCF_SSE4A;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_FMA4) Prcb->CpuId.ExtendedFeatureBits |= KCF_FMA4;
        if(CpuRegisters.Ecx & CPUID_FEATURES_ECX_TOPOLOGY_EXTENSIONS) Prcb->CpuId.ExtendedFeatureBits |= KCF_TOPOEXT;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_SYSCALL_SYSRET) Prcb->CpuId.ExtendedFeatureBits |= KCF_SYSCALL;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_NX) Prcb->CpuId.ExtendedFeatureBits |= KCF_NX_BIT;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_RDTSCP) Prcb->CpuId.ExtendedFeatureBits |= KCF_RDTSCP;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_LONG_MODE) Prcb->CpuId.ExtendedFeatureBits |= KCF_64BIT;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_3DNOW_EXT) Prcb->CpuId.ExtendedFeatureBits |= KCF_3DNOW_EXT;
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_3DNOW) Prcb->CpuId.ExtendedFeatureBits |= KCF_3DNOW;
    }

    /* Check if CPU supports advanced power management leaf */
    if(MaxExtendedLeaf >= CPUID_GET_ADVANCED_POWER_MANAGEMENT)
    {
        /* Get CPU advanced power management features */
        RTL::Memory::ZeroMemory(&CpuRegisters, sizeof(CPUID_REGISTERS));
        CpuRegisters.Leaf = CPUID_GET_ADVANCED_POWER_MANAGEMENT;
        AR::CpuFunctions::CpuId(&CpuRegisters);

        /* Store CPU advanced power management features in processor control block */
        if(CpuRegisters.Edx & CPUID_FEATURES_EDX_TSCI) Prcb->CpuId.ExtendedFeatureBits |= KCF_INVARIANT_TSC;
    }
}

/**
 * Initializes the kernel's Global Descriptor Table (GDT).
 *
 * @param Gdt
 *        Supplies a pointer to the GDT to use.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeGdt(IN PKPROCESSOR_BLOCK ProcessorBlock)
{
    /* Initialize GDT entries */
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_NULL, 0x0, 0x0, KGDT_TYPE_NONE, KGDT_DPL_SYSTEM, 1);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R0_CODE, 0x0, 0x0, KGDT_TYPE_CODE, KGDT_DPL_SYSTEM, 1);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R0_DATA, 0x0, 0x0, KGDT_TYPE_DATA, KGDT_DPL_SYSTEM, 1);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R3_CMCODE, 0x0, 0xFFFFFFFF, KGDT_TYPE_CODE, KGDT_DPL_USER, 2);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R3_DATA, 0x0, 0xFFFFFFFF, KGDT_TYPE_DATA, KGDT_DPL_USER, 2);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R3_CODE, 0x0, 0x0, KGDT_TYPE_CODE, KGDT_DPL_USER, 1);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_SYS_TSS, (ULONG_PTR)ProcessorBlock->TssBase,
                sizeof(KTSS) - 1, AMD64_TSS, KGDT_DPL_SYSTEM, 0);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R3_CMTEB, 0x0, 0x0FFF, KGDT_TYPE_DATA, KGDT_DPL_USER, 2);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_R0_LDT, 0x0, 0xFFFFFFFF, KGDT_TYPE_CODE, KGDT_DPL_SYSTEM, 0);
    SetGdtEntry(ProcessorBlock->GdtBase, KGDT_ALIAS, (ULONG_PTR)ProcessorBlock->GdtBase,
                (GDT_ENTRIES * sizeof(KGDTENTRY)) - 1, KGDT_TYPE_DATA, KGDT_DPL_SYSTEM, 0);
}

/**
 * Initializes the kernel's Interrupt Descriptor Table (IDT).
 *
 * @param ProcessorBlock
 *        Supplies a pointer to the processor block to use.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeIdt(IN PKPROCESSOR_BLOCK ProcessorBlock)
{
    UINT Vector;

    /* Fill in all vectors */
    for(Vector = 0; Vector < IDT_ENTRIES; Vector++)
    {
        /* Set the IDT to handle unexpected interrupts */
        SetIdtGate(ProcessorBlock->IdtBase, Vector, (PVOID)ArInterruptEntry[Vector], KGDT_R0_CODE,
                   KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    }

    /* Setup IDT handlers for known interrupts and traps */
    SetIdtGate(ProcessorBlock->IdtBase, 0x00, (PVOID)ArTrapEntry[0x00], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x01, (PVOID)ArTrapEntry[0x01], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x02, (PVOID)ArTrapEntry[0x02], KGDT_R0_CODE, KIDT_IST_NMI, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x03, (PVOID)ArTrapEntry[0x03], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING3, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x04, (PVOID)ArTrapEntry[0x04], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING3, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x05, (PVOID)ArTrapEntry[0x05], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x06, (PVOID)ArTrapEntry[0x06], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x07, (PVOID)ArTrapEntry[0x07], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x08, (PVOID)ArTrapEntry[0x08], KGDT_R0_CODE, KIDT_IST_PANIC, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x09, (PVOID)ArTrapEntry[0x09], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x0A, (PVOID)ArTrapEntry[0x0A], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x0B, (PVOID)ArTrapEntry[0x0B], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x0C, (PVOID)ArTrapEntry[0x0C], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x0D, (PVOID)ArTrapEntry[0x0D], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x0E, (PVOID)ArTrapEntry[0x0E], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x10, (PVOID)ArTrapEntry[0x10], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x11, (PVOID)ArTrapEntry[0x11], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x12, (PVOID)ArTrapEntry[0x12], KGDT_R0_CODE, KIDT_IST_MCA, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x13, (PVOID)ArTrapEntry[0x13], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x1F, (PVOID)ArTrapEntry[0x1F], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING0, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x2C, (PVOID)ArTrapEntry[0x2C], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING3, AMD64_INTERRUPT_GATE);
    SetIdtGate(ProcessorBlock->IdtBase, 0x2D, (PVOID)ArTrapEntry[0x2D], KGDT_R0_CODE, KIDT_IST_RESERVED, KIDT_ACCESS_RING3, AMD64_INTERRUPT_GATE);
}

/**
 * Initializes AMD64 processor specific structures.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeProcessor(IN PVOID ProcessorStructures)
{
    PVOID KernelBootStack, KernelFaultStack, KernelNmiStack;
    KDESCRIPTOR GdtDescriptor, IdtDescriptor;
    PKPROCESSOR_BLOCK ProcessorBlock;
    PKGDTENTRY Gdt;
    PKIDTENTRY Idt;
    PKTSS Tss;

    /* Check if processor structures buffer provided */
    if(ProcessorStructures)
    {
        /* Assign CPU structures from provided buffer */
        InitializeProcessorStructures(ProcessorStructures, &Gdt, &Tss, &ProcessorBlock,
                                      &KernelBootStack, &KernelFaultStack, &KernelNmiStack);

        /* Use global IDT */
        Idt = InitialIdt;
    }
    else
    {
        /* Use initial structures */
        Gdt = InitialGdt;
        Idt = InitialIdt;
        Tss = &InitialTss;
        KernelBootStack = (PVOID)((ULONG_PTR)&BootStack + KERNEL_STACK_SIZE);
        KernelFaultStack = (PVOID)((ULONG_PTR)&FaultStack + KERNEL_STACK_SIZE);
        KernelNmiStack = (PVOID)((ULONG_PTR)&NmiStack + KERNEL_STACK_SIZE);
        ProcessorBlock = &InitialProcessorBlock;
    }

    /* Initialize processor block */
    InitializeProcessorBlock(ProcessorBlock, Gdt, Idt, Tss, KernelFaultStack);

    /* Initialize GDT, IDT and TSS */
    InitializeGdt(ProcessorBlock);
    InitializeIdt(ProcessorBlock);
    InitializeTss(ProcessorBlock, KernelBootStack, KernelFaultStack, KernelNmiStack);

    /* Set GDT and IDT descriptors */
    GdtDescriptor.Base = Gdt;
    GdtDescriptor.Limit = (GDT_ENTRIES * sizeof(KGDTENTRY)) - 1;
    IdtDescriptor.Base = Idt;
    IdtDescriptor.Limit = (IDT_ENTRIES * sizeof(KIDTENTRY)) - 1;

    /* Load GDT, IDT and TSS */
    AR::CpuFunctions::LoadGlobalDescriptorTable(&GdtDescriptor.Limit);
    AR::CpuFunctions::LoadInterruptDescriptorTable(&IdtDescriptor.Limit);
    AR::CpuFunctions::LoadTaskRegister((UINT)KGDT_SYS_TSS);

    /* Initialize segment registers */
    InitializeSegments();

    /* Set GS base */
    AR::CpuFunctions::WriteModelSpecificRegister(X86_MSR_GSBASE, (ULONGLONG)ProcessorBlock);
    AR::CpuFunctions::WriteModelSpecificRegister(X86_MSR_KERNEL_GSBASE, (ULONGLONG)ProcessorBlock);

    /* Initialize processor registers */
    InitializeProcessorRegisters();

    /* Identify processor */
    IdentifyProcessor();
}

/**
 * Initializes processor block.
 *
 * @param ProcessorBlock
 *        Supplies a pointer to the processor block to initialize.
 *
 * @param Gdt
 *        Supplies a pointer to the GDT for this processor block.
 *
 * @param Idt
 *        Supplies a pointer to the IDT for this processor block.
 *
 * @param Tss
 *        Supplies a pointer to the TSS for this processor block.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeProcessorBlock(OUT PKPROCESSOR_BLOCK ProcessorBlock,
                                               IN PKGDTENTRY Gdt,
                                               IN PKIDTENTRY Idt,
                                               IN PKTSS Tss,
                                               IN PVOID DpcStack)
{
    /* Set processor block and processor control block */
    ProcessorBlock->Self = ProcessorBlock;
    ProcessorBlock->CurrentPrcb = &ProcessorBlock->Prcb;

    /* Set GDT, IDT and TSS descriptors */
    ProcessorBlock->GdtBase = (PKGDTENTRY)(PVOID)Gdt;
    ProcessorBlock->IdtBase = Idt;
    ProcessorBlock->TssBase = Tss;
    ProcessorBlock->Prcb.RspBase = Tss->Rsp0;

    /* Setup DPC stack */
    ProcessorBlock->Prcb.DpcStack = DpcStack;

    /* Setup processor control block */
    ProcessorBlock->Prcb.CpuNumber = ProcessorBlock->CpuNumber;
    ProcessorBlock->Prcb.SetMember = 1ULL << ProcessorBlock->CpuNumber;
    ProcessorBlock->Prcb.MultiThreadProcessorSet = 1ULL << ProcessorBlock->CpuNumber;

    /* Clear DR6 and DR7 registers */
    ProcessorBlock->Prcb.ProcessorState.SpecialRegisters.KernelDr6 = 0;
    ProcessorBlock->Prcb.ProcessorState.SpecialRegisters.KernelDr7 = 0;

    /* Set process and thread information */
    ProcessorBlock->Prcb.CurrentThread = &(KE::KThread::GetInitialThread())->ThreadControlBlock;
    ProcessorBlock->Prcb.CurrentThread->ApcState.Process = &(KE::KProcess::GetInitialProcess())->ProcessControlBlock;
    ProcessorBlock->Prcb.IdleThread = &(KE::KThread::GetInitialThread())->ThreadControlBlock;
    ProcessorBlock->Prcb.NextThread = NULLPTR;

    /* Set initial MXCSR register value */
    ProcessorBlock->Prcb.MxCsr = INITIAL_MXCSR;

    /* Set initial runlevel and mark processor as started */
    ProcessorBlock->RunLevel = PASSIVE_LEVEL;
    ProcessorBlock->Started = TRUE;
}

/**
 * Initializes processor registers and other boot structures.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeProcessorRegisters(VOID)
{
    ULONGLONG PatAttributes;

    /* Enable FXSAVE restore */
    AR::CpuFunctions::WriteControlRegister(4, AR::CpuFunctions::ReadControlRegister(4) | CR4_FXSR);

    /* Enable XMMI exceptions */
    AR::CpuFunctions::WriteControlRegister(4, AR::CpuFunctions::ReadControlRegister(4) | CR4_XMMEXCPT);

    /* Set debugger extension */
    AR::CpuFunctions::WriteControlRegister(4, AR::CpuFunctions::ReadControlRegister(4) | CR4_DE);

    /* Enable large pages */
    AR::CpuFunctions::WriteControlRegister(4, AR::CpuFunctions::ReadControlRegister(4) | CR4_PSE);

    /* Enable write-protection */
    AR::CpuFunctions::WriteControlRegister(0, AR::CpuFunctions::ReadControlRegister(0) | CR0_WP);

    /* Set alignment mask */
    AR::CpuFunctions::WriteControlRegister(0, AR::CpuFunctions::ReadControlRegister(0) | CR0_AM);

    /* Disable FPU monitoring */
    AR::CpuFunctions::WriteControlRegister(0, AR::CpuFunctions::ReadControlRegister(0) & ~CR0_MP);

    /* Disable x87 FPU exceptions */
    AR::CpuFunctions::WriteControlRegister(0, AR::CpuFunctions::ReadControlRegister(0) & ~CR0_NE);

    /* Flush the TLB */
    AR::CpuFunctions::FlushTlb();

    /* Initialize system call MSRs */
    AR::Traps::InitializeSystemCallMsrs();
    
    /* Enable No-Execute (NXE) in EFER MSR */
    AR::CpuFunctions::WriteModelSpecificRegister(X86_MSR_EFER,
                                                 CpuFunctions::ReadModelSpecificRegister(X86_MSR_EFER) | X86_MSR_EFER_NXE);

    /* Initialize Page Attribute Table */
    PatAttributes = (PAT_TYPE_WB << 0) | (PAT_TYPE_USWC << 8) | (PAT_TYPE_WEAK_UC << 16) | (PAT_TYPE_STRONG_UC << 24) |
                    (PAT_TYPE_WB << 32) | (PAT_TYPE_USWC << 40) | (PAT_TYPE_WEAK_UC << 48) | (PAT_TYPE_STRONG_UC << 56);
    AR::CpuFunctions::WriteModelSpecificRegister(X86_MSR_PAT, PatAttributes);

    /* Initialize MXCSR register */
    AR::CpuFunctions::LoadMxcsrRegister(INITIAL_MXCSR);
}

/**
 * Initializes i686 processor specific structures with provided memory buffer.
 *
 * @param ProcessorStructures
 *        Supplies a pointer to the allocated buffer with processor structures.
 *
 * @param Gdt
 *        Supplies a pointer to the GDT.
 *
 * @param Tss
 *        Supplies a pointer to the TSS.
 *
 * @param ProcessorBlock
 *        Supplies a pointer to the processor block.
 *
 * @param KernelBootStack
 *        Supplies a pointer to the kernel boot stack.
 *
 * @param KernelFaultStack
 *        Supplies a pointer to the kernel fault stack.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeProcessorStructures(IN PVOID ProcessorStructures,
                                                    OUT PKGDTENTRY *Gdt,
                                                    OUT PKTSS *Tss,
                                                    OUT PKPROCESSOR_BLOCK *ProcessorBlock,
                                                    OUT PVOID *KernelBootStack,
                                                    OUT PVOID *KernelFaultStack,
                                                    OUT PVOID *KernelNmiStack)
{
    UINT_PTR Address;

    /* Align address to page size boundary and move to kernel boot stack */
    Address = ROUND_UP((UINT_PTR)ProcessorStructures, MM_PAGE_SIZE) + KERNEL_STACK_SIZE;

    /* Assign a space for kernel boot stack and advance */
    if(KernelBootStack != NULLPTR)
    {
        /* Return kernel boot stack address */
        *KernelBootStack = (PVOID)Address;
    }
    Address += KERNEL_STACK_SIZE;

    /* Assign a space for kernel fault stack and advance */
    if(KernelFaultStack != NULLPTR)
    {
        /* Return kernel fault stack address */
        *KernelFaultStack = (PVOID)Address;
    }
    Address += KERNEL_STACK_SIZE;

    /* Assign a space for kernel NMI stack, no advance needed as stack grows down */
    if(KernelNmiStack != NULLPTR)
    {
        /* Return kernel NMI stack address */
        *KernelNmiStack = (PVOID)Address;
    }

    /* Assign a space for GDT and advance */
    if(Gdt != NULLPTR)
    {
        /* Return GDT base address */
        *Gdt = (PKGDTENTRY)(PVOID)Address;
    }
    Address += (GDT_ENTRIES * sizeof(KGDTENTRY));

    /* Assign a space for TSS and advance */
    if(Tss != NULLPTR)
    {
        *Tss = (PKTSS)(PVOID)Address;
    }
    Address += sizeof(KTSS);

    /* Assign a space for Processor Block and advance */
    if(ProcessorBlock != NULLPTR)
    {
        /* Return processor block address */
        *ProcessorBlock = (PKPROCESSOR_BLOCK)(PVOID)Address;
    }
}

/**
 * Initializes segment registers.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeSegments(VOID)
{
    /* Initialize segments */
    AR::CpuFunctions::LoadSegment(SEGMENT_CS, KGDT_R0_CODE);
    AR::CpuFunctions::LoadSegment(SEGMENT_DS, KGDT_R3_DATA | RPL_MASK);
    AR::CpuFunctions::LoadSegment(SEGMENT_ES, KGDT_R3_DATA | RPL_MASK);
    AR::CpuFunctions::LoadSegment(SEGMENT_FS, KGDT_R3_CMTEB | RPL_MASK);
    AR::CpuFunctions::LoadSegment(SEGMENT_GS, KGDT_R3_DATA | RPL_MASK);
    AR::CpuFunctions::LoadSegment(SEGMENT_SS, KGDT_R0_DATA);
}

/**
 * Initializes the kernel's Task State Segment (TSS).
 *
 * @param Tss
 *        Supplies a pointer to the TSS to use.
 *
 * @param Gdt
 *        Supplies a pointer to the GDT to use.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::InitializeTss(IN PKPROCESSOR_BLOCK ProcessorBlock,
                                    IN PVOID KernelBootStack,
                                    IN PVOID KernelFaultStack,
                                    IN PVOID KernelNmiStack)
{
    /* Fill TSS with zeroes */
    RtlZeroMemory(ProcessorBlock->TssBase, sizeof(KTSS));

    /* Setup I/O map and stacks for ring0 & traps */
    ProcessorBlock->TssBase->IoMapBase = sizeof(KTSS);
    ProcessorBlock->TssBase->Rsp0 = (ULONG_PTR)KernelBootStack;
    ProcessorBlock->TssBase->Ist[KIDT_IST_PANIC] = (ULONG_PTR)KernelFaultStack;
    ProcessorBlock->TssBase->Ist[KIDT_IST_MCA] = (ULONG_PTR)KernelFaultStack;
    ProcessorBlock->TssBase->Ist[KIDT_IST_NMI] = (ULONG_PTR)KernelNmiStack;
}

/**
 * Fills in an AMD64 GDT entry.
 *
 * @param Gdt
 *        Supplies a pointer to the GDT.
 *
 * @param Selector
 *        Specifies a segment selector of the GDT entry.
 *
 * @param Base
 *        Specifies a base address value of the descriptor.
 *
 * @param Limit
 *        Specifies a descriptor limit.
 *
 * @param Type
 *        Specifies a type of the descriptor.
 *
 * @param Dpl
 *        Specifies the descriptor privilege level.
 *
 * @param SegmentMode
 *        Specifies a segment mode of the descriptor.
 *
 * @return This routine does not return any value
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::SetGdtEntry(IN PKGDTENTRY Gdt,
                                  IN USHORT Selector,
                                  IN ULONG_PTR Base,
                                  IN ULONG Limit,
                                  IN UCHAR Type,
                                  IN UCHAR Dpl,
                                  IN UCHAR SegmentMode)
{
    PKGDTENTRY GdtEntry;
    UCHAR Granularity;

    /* Set the granularity flag depending on descriptor limit */
    if(Limit < 0x100000)
    {
        /* Limit is in 1B blocks */
        Granularity = 0;
    }
    else
    {
        /* Limit is in 4KB blocks */
        Granularity = 1;
        Limit >>= 12;
    }

    /* Get GDT entry */
    GdtEntry = (PKGDTENTRY)((ULONG_PTR)Gdt + (Selector & ~RPL_MASK));

    /* Set GDT descriptor base */
    GdtEntry->BaseLow = (Base & 0xFFFF);
    GdtEntry->Bytes.BaseMiddle = ((Base >> 16) & 0xFF);
    GdtEntry->Bytes.BaseHigh = ((Base >> 24) & 0xFF);
    GdtEntry->BaseUpper = (Base >> 32);

    /* Set descriptor limit */
    GdtEntry->LimitLow = (Limit & 0xFFFF);
    GdtEntry->Bits.LimitHigh = ((Limit >> 16) & 0xF);

    /* Initialize GDT entry */
    GdtEntry->Bits.DefaultBig = !!(SegmentMode & 2);
    GdtEntry->Bits.Dpl = (Dpl & 0x3);
    GdtEntry->Bits.Granularity = Granularity;
    GdtEntry->Bits.LongMode = !!(SegmentMode & 1);
    GdtEntry->Bits.Present = (Type != 0);
    GdtEntry->Bits.System = 0;
    GdtEntry->Bits.Type = (Type & 0x1F);
    GdtEntry->MustBeZero = 0;
}

/**
 * Updates an existing AMD64 GDT entry with new base address.
 *
 * @param Gdt
 *        Supplies a pointer to the GDT.
 *
 * @param Selector
 *        Specifies a segment selector of the GDT entry.
 *
 * @param Base
 *        Specifies a base address value of the descriptor.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::SetGdtEntryBase(IN PKGDTENTRY Gdt,
                                      IN USHORT Selector,
                                      IN ULONG_PTR Base)
{
    PKGDTENTRY GdtEntry;

    /* Get GDT entry */
    GdtEntry = (PKGDTENTRY)((ULONG_PTR)Gdt + (Selector & ~RPL_MASK));

    /* Set new GDT descriptor base */
    GdtEntry->BaseLow = (Base & 0xFFFF);
    GdtEntry->Bytes.BaseMiddle = ((Base >> 16) & 0xFF);
    GdtEntry->Bytes.BaseHigh = ((Base >> 24) & 0xFF);
    GdtEntry->BaseUpper = (Base >> 32);
}

/**
 * Fills in a call, interrupt, task or trap gate entry.
 *
 * @param Idt
 *        Supplies a pointer to IDT structure, where gate is located.
 *
 * @param Vector
 *        Supplies a gate vector pointing to the interrupt gate in the IDT
 *
 * @param Handler
 *        Supplies a pointer to the interrupt handler of the specified gate.
 *
 * @param Selector
 *        Supplies the code selector the gate should run in.
 *
 * @param Ist
 *        Supplies the interrupt stack table entry the gate should run in.
 *
 * @param Access
 *        Supplies the gate access rights.
 *
 * @param Type
 *        Supplies the gate type.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
AR::ProcessorSupport::SetIdtGate(IN PKIDTENTRY Idt,
                                 IN USHORT Vector,
                                 IN PVOID Handler,
                                 IN USHORT Selector,
                                 IN USHORT Ist,
                                 IN USHORT Dpl,
                                 IN USHORT Type)
{
    /* Set the handler's address */
    Idt[Vector].OffsetLow = ((ULONG_PTR)Handler & 0xFFFF);
    Idt[Vector].OffsetMiddle = (((ULONG_PTR)Handler >> 16) & 0xFFFF);
    Idt[Vector].OffsetHigh = (ULONG_PTR)Handler >> 32;

    /* Set the code segment selector */
    Idt[Vector].Selector = Selector;

    /* Initialize the gate's attributes and flags */
    Idt[Vector].Access = 0;
    Idt[Vector].Dpl = Dpl;
    Idt[Vector].IstIndex = Ist;
    Idt[Vector].Present = 1;
    Idt[Vector].Reserved1 = 0;
    Idt[Vector].Type = Type;
}
