/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/ar/amd64/cpufunc.cc
 * DESCRIPTION:     Routines to provide access to special AMD64 CPU instructions
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtos.hh>


/**
 * Instructs the processor to clear the interrupt flag.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::ClearInterruptFlag(VOID)
{
    __asm__ volatile("cli");
}

/**
 * Retrieves a various amount of information about the CPU.
 *
 * @param Registers
 *        Supplies a pointer to the structure containing all the necessary registers and leafs for CPUID.
 *
 * @return This routine returns TRUE if CPUID function could be executed, FALSE otherwise.
 *
 * @since XT 1.0
 */
XTCDECL
BOOLEAN
AR::CpuFunctions::CpuId(IN OUT PCPUID_REGISTERS Registers)
{
    UINT32 MaxLeaf;

    /* Get highest function ID available */
    __asm__ volatile("cpuid"
                     : "=a" (MaxLeaf)
                     : "a" (Registers->Leaf & 0x80000000)
                     : "rbx",
                       "rcx",
                       "rdx");

    /* Check if CPU supports this command */
    if(Registers->Leaf > MaxLeaf)
    {
        /* Cannot call it, return FALSE */
        return FALSE;
    }

    /* Execute CPUID function */
    __asm__ volatile("cpuid"
                     : "=a" (Registers->Eax),
                       "=b" (Registers->Ebx),
                       "=c" (Registers->Ecx),
                       "=d" (Registers->Edx)
                     : "a" (Registers->Leaf),
                       "c" (Registers->SubLeaf));

    /* Return TRUE */
    return TRUE;
}

/**
 * Partially flushes the Translation Lookaside Buffer (TLB)
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::FlushTlb(VOID)
{
    /* Flush the TLB by resetting the CR3 */
    WriteControlRegister(3, ReadControlRegister(3));
}

/**
 * Gets the RFLAGS register.
 *
 * @return This routine returns the RFLAGS register.
 *
 * @since XT 1.0
 */
XTCDECL
ULONG
AR::CpuFunctions::GetCpuFlags(VOID)
{
    ULONG_PTR Flags;

    /* Get RFLAGS register */
    __asm__ volatile("pushf\n"
                     "pop %0\n"
                     : "=rm" (Flags)
                     :
                     : "memory");

    /* Return flags */
    return Flags;
}

/**
 * Gets the address of the current stack register.
 *
 * @return This routine returns the current stack pointer.
 *
 * @since XT 1.0
 */
XTASSEMBLY
XTCDECL
ULONG_PTR
AR::CpuFunctions::GetStackPointer(VOID)
{
    /* Get current stack pointer */
    __asm__ volatile("movq %%rsp, %%rax\n"
                     "retq\n"
                     :
                     :
                     :);
}

/**
 * Halts the central processing unit (CPU).
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::Halt(VOID)
{
    __asm__ volatile("hlt");
}

/**
 * Checks whether interrupts are enabled or not.
 *
 * @return This routine returns TRUE if interrupts are enabled, or FALSE otherwise.
 *
 * @since XT 1.0
 */
XTCDECL
BOOLEAN
AR::CpuFunctions::InterruptsEnabled(VOID)
{
    ULONG_PTR Flags;

    /* Get RFLAGS register */
    Flags = GetCpuFlags();

    /* Check if interrupts are enabled and return result */
    return (Flags & X86_EFLAGS_IF_MASK) ? TRUE : FALSE;
}

/**
 * Invalidates the TLB (Translation Lookaside Buffer) for specified virtual address.
 *
 * @param Address
 *        Suuplies a virtual address whose associated TLB entry will be invalidated.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::InvalidateTlbEntry(IN PVOID Address)
{
    __asm__ volatile("invlpg (%0)"
                     :
                     : "b" (Address)
                     : "memory");
}

/**
 * Loads the value in the source operand into the global descriptor table register (GDTR).
 *
 * @param Source
 *        Specifies a memory location that contains the base address of GDT.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadGlobalDescriptorTable(IN PVOID Source)
{
    __asm__ volatile("lgdt %0"
                     :
                     : "m" (*(PSHORT)Source)
                     : "memory");
}

/**
 * Loads the value in the source operand into the interrupt descriptor table register (IDTR).
 *
 * @param Source
 *        Specifies a memory location that contains the base address of IDT.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadInterruptDescriptorTable(IN PVOID Source)
{
    __asm__ volatile("lidt %0"
                     :
                     : "m" (*(PSHORT)Source)
                     : "memory");
}

/**
 * Loads the value in the source operand into the local descriptor table register (LDTR).
 *
 * @param Source
 *        Specifies a selector value.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadLocalDescriptorTable(IN USHORT Source)
{
    __asm__ volatile("lldtw %0"
                     :
                     : "g" (Source));
}

/**
 * Loads the value in the source operand into the MXCSR register
 *
 * @param Source
 *        Supplies a source value to be loaded into the MXCSR register.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadMxcsrRegister(IN ULONG Source)
{
    __asm__ volatile("ldmxcsr %0"
                     :
                     : "m" (Source));
}

/**
 * Loads source data into specified segment.
 *
 * @param Segment
 *        Supplies a segment identification.
 *
 * @param Source
 *        Supplies a pointer to the memory area containing data that will be loaded into specified segment.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadSegment(IN USHORT Segment,
                     IN ULONG Source)
{
    switch(Segment)
    {
        case SEGMENT_CS:
            /* Load CS Segment */
            __asm__ volatile("mov %0, %%rax\n"
                             "push %%rax\n"
                             "lea label(%%rip), %%rax\n"
                             "push %%rax\n"
                             "lretq\n"
                             "label:"
                             :
                             : "ri" ((ULONGLONG)Source)
                             : "rax");
            break;
        case SEGMENT_DS:
            /* Load DS Segment */
            __asm__ volatile("movl %0, %%ds"
                             :
                             : "r" (Source));
            break;
        case SEGMENT_ES:
            /* Load ES Segment */
            __asm__ volatile("movl %0, %%es"
                             :
                             : "r" (Source));
            break;
        case SEGMENT_FS:
            /* Load FS Segment */
            __asm__ volatile("movl %0, %%fs"
                             :
                             : "r" (Source));
            break;
        case SEGMENT_GS:
            /* Load GS Segment */
            __asm__ volatile("movl %0, %%gs"
                             :
                             : "r" (Source));
            break;
            /* Load SS Segment */
        case SEGMENT_SS:
            __asm__ volatile("movl %0, %%ss"
                             :
                             : "r" (Source));
            break;
    }
}

/**
 * Loads Task Register (TR) with a segment selector that points to TSS.
 *
 * @param Source
 *        Supplies the segment selector in the GDT describing the TSS.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::LoadTaskRegister(USHORT Source)
{
    __asm__ volatile("ltr %0"
                     :
                     : "rm" (Source));
}

/**
 * Orders memory accesses as seen by other processors.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::MemoryBarrier(VOID)
{
    LONG Barrier;
    __asm__ volatile("lock; orl $0, %0;"
                     :
                     : "m"(Barrier));
}

/**
 * Reads the specified CPU control register and returns its value.
 *
 * @param ControlRegister
 *        Supplies a number of a control register which controls the general behavior of a CPU.
 *
 * @return This routine returns the value stored in the control register.
 *
 * @since XT 1.0
 */
XTCDECL
ULONG_PTR
AR::CpuFunctions::ReadControlRegister(IN USHORT ControlRegister)
{
    ULONG_PTR Value;

    /* Read a value from specified CR register */
    switch(ControlRegister)
    {
        case 0:
            /* Read value from CR0 */
            __asm__ volatile("mov %%cr0, %0"
                             : "=r" (Value)
                             :
                             : "memory");
            break;
        case 2:
            /* Read value from CR2 */
            __asm__ volatile("mov %%cr2, %0"
                             : "=r" (Value)
                             :
                             : "memory");
            break;
        case 3:
            /* Read value from CR3 */
            __asm__ volatile("mov %%cr3, %0"
                             : "=r" (Value)
                             :
                             : "memory");
            break;
        case 4:
            /* Read value from CR4 */
            __asm__ volatile("mov %%cr4, %0"
                             : "=r" (Value)
                             :
                             : "memory");
            break;
        case 8:
            /* Read value from CR8 */
            __asm__ volatile("mov %%cr8, %0"
                             : "=r" (Value)
                             :
                             : "memory");
            break;
        default:
            /* Invalid control register set */
            Value = 0;
            break;
    }

    /* Return value read from given CR register */
    return Value;
}

/**
 * Reads the specified CPU debug register and returns its value.
 *
 * @param DebugRegister
 *        Supplies a number of a debug register to read from.
 *
 * @return This routine returns the value stored in the specified debug register.
 *
 * @since XT 1.0
 */
XTCDECL
ULONG_PTR
AR::CpuFunctions::ReadDebugRegister(IN USHORT DebugRegister)
{
    ULONG_PTR Value;

    /* Read a value from specified DR register */
    switch(DebugRegister)
    {
        case 0:
            /* Read value from DR0 */
            __asm__ volatile("mov %%dr0, %0"
                             : "=r" (Value));
            break;
        case 1:
            /* Read value from DR1 */
            __asm__ volatile("mov %%dr1, %0"
                             : "=r" (Value));
            break;
        case 2:
            /* Read value from DR2 */
            __asm__ volatile("mov %%dr2, %0"
                             : "=r" (Value));
            break;
        case 3:
            /* Read value from DR3 */
            __asm__ volatile("mov %%dr3, %0"
                             : "=r" (Value));
            break;
        case 4:
            /* Read value from DR4 */
            __asm__ volatile("mov %%dr4, %0"
                             : "=r" (Value));
            break;
        case 5:
            /* Read value from DR5 */
            __asm__ volatile("mov %%dr5, %0"
                             : "=r" (Value));
            break;
        case 6:
            /* Read value from DR6 */
            __asm__ volatile("mov %%dr6, %0"
                             : "=r" (Value));
            break;
        case 7:
            /* Read value from DR7 */
            __asm__ volatile("mov %%dr7, %0"
                             : "=r" (Value));
            break;
        default:
            /* Invalid debug register set */
            Value = 0;
            break;
    }

    /* Return value read from given DR register */
    return Value;
}

/**
 * Reads quadword from a memory location specified by an offset relative to the beginning of the GS segment.
 *
 * @param Offset
 *        Specifies the offset from the beginning of GS segment.
 *
 * @return This routine returns the value read from the specified memory location relative to GS segment.
 *
 * @since XT 1.0
 */
XTCDECL
ULONGLONG
AR::CpuFunctions::ReadGSQuadWord(ULONG Offset)
{
    ULONGLONG Value;

    /* Read quadword from GS segment */
    __asm__ volatile("movq %%gs:%a[Offset], %q[Value]"
                     : [Value] "=r" (Value)
                     : [Offset] "ir" (Offset));
    return Value;
}

/**
 * Reads a 64-bit value from the requested Model Specific Register (MSR).
 *
 * @param Register
 *        Supplies the MSR to read.
 *
 * @return This routine returns the 64-bit MSR value.
 *
 * @since XT 1.0
 */
XTCDECL
ULONGLONG
AR::CpuFunctions::ReadModelSpecificRegister(IN ULONG Register)
{
    ULONG Low, High;

    __asm__ volatile("rdmsr"
                     : "=a" (Low),
                       "=d" (High)
                     : "c" (Register));

    return ((ULONGLONG)High << 32) | Low;
}

/**
 * Reads the contents of the MXCSR control/status register.
 *
 * @return This routine returns the contents of the MXCSR register as a 32-bit unsigned integer value.
 *
 * @since XT 1.0
 */
XTCDECL
UINT
AR::CpuFunctions::ReadMxCsrRegister(VOID)
{
    return __builtin_ia32_stmxcsr();
}

/**
 * Reads the current value of the CPU's time-stamp counter.
 *
 * @return This routine returns the current instruction cycle count since the processor was started.
 *
 * @since XT 1.0
 */
XTCDECL
ULONGLONG
AR::CpuFunctions::ReadTimeStampCounter(VOID)
{
    ULONGLONG Low, High;

    __asm__ volatile("rdtsc"
                     : "=a" (Low),
                       "=d" (High));

    return ((ULONGLONG)High << 32) | Low;
}

/**
 * Reads the current value of the CPU's time-stamp counter and processor ID.
 *
 * @param TscAux
 *        Supplies a pointer to a variable that receives the auxiliary TSC information (IA32_TSC_AUX).
 *
 * @return This routine returns the current instruction cycle count since the processor was last reset.
 *
 * @since XT 1.0
 */
XTCDECL
ULONGLONG
AR::CpuFunctions::ReadTimeStampCounterProcessor(OUT PULONG TscAux)
{
    ULONG Low, High;

    /* Execute the RDTSCP instruction */
    __asm__ volatile("rdtscp"
                     : "=a" (Low),
                       "=d" (High),
                       "=c" (*TscAux)
    );

    /* Combine the two 32-bit registers into a single 64-bit unsigned integer and return the value */
    return ((ULONGLONG)High << 32) | Low;
}

/**
 * Orders memory accesses as seen by other processors, without fence.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::ReadWriteBarrier(VOID)
{
    __asm__ volatile(""
                     :
                     :
                     : "memory");
}

/**
 * Performs a Bit Scan Forward instruction to locate the most significant set bit.
 *
 * @param Index
 *        Receives the zero-based index of the highest set bit when one is found.
 *
 * @param Mask
 *        Supplies the bitmap to scan.
 *
 * @return This routine returns TRUE when a set bit was found, otherwise FALSE.
 *
 * @since XT 1.0
 */
XTCDECL
BOOLEAN
AR::CpuFunctions::ScanForwardBit(OUT PULONG Index,
                                 IN ULONG Mask)
{
    /* Defer to the BSF instruction */
    __asm__("bsfl %[Mask], %[Index]" : [Index] "=r" (*Index) : [Mask] "mr" (Mask));

    /* Report whether the input had any bit set */
    return Mask ? TRUE : FALSE;
}

/**
 * Performs a Bit Scan Reverse instruction to locate the most significant set bit.
 *
 * @param Index
 *        Receives the zero-based index of the highest set bit when one is found.
 *
 * @param Mask
 *        Supplies the bitmap to scan.
 *
 * @return This routine returns TRUE when a set bit was found, otherwise FALSE.
 *
 * @since XT 1.0
 */
XTCDECL
BOOLEAN
AR::CpuFunctions::ScanReverseBit(OUT PULONG Index,
                                 IN ULONG Mask)
{
    /* Defer to the BSR instruction */
    __asm__("bsrl %[Mask], %[Index]" : [Index] "=r" (*Index) : [Mask] "mr" (Mask));

    /* Report whether the input had any bit set */
    return Mask ? TRUE : FALSE;
}

/**
 * Instructs the processor to set the interrupt flag.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::SetInterruptFlag(VOID)
{
    __asm__ volatile("sti");
}

/**
 * Stores GDT register into the given memory area.
 *
 * @param Destination
 *        Supplies a pointer to the memory area where GDT will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::StoreGlobalDescriptorTable(OUT PVOID Destination)
{
    __asm__ volatile("sgdt %0"
                     : "=m" (*(PSHORT)Destination)
                     :
                     : "memory");
}

/**
 * Stores IDT register into the given memory area.
 *
 * @param Destination
 *        Supplies a pointer to the memory area where IDT will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::StoreInterruptDescriptorTable(OUT PVOID Destination)
{
    __asm__ volatile("sidt %0"
                     : "=m" (*(PSHORT)Destination)
                     :
                     : "memory");
}

/**
 * Stores LDT register into the given memory area.
 *
 * @param Destination
 *        Supplies a pointer to the memory area where LDT will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::StoreLocalDescriptorTable(OUT PVOID Destination)
{
    __asm__ volatile("sldt %0"
                     : "=m" (*(PSHORT)Destination)
                     :
                     : "memory");
}

/**
 * Stores specified segment into the given memory area.
 *
 * @param Segment
 *        Supplies a segment identification.
 *
 * @param Destination
 *        Supplies a pointer to the memory area where segment data will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::StoreSegment(IN USHORT Segment,
                               OUT PVOID Destination)
{
    switch(Segment)
    {
        case SEGMENT_CS:
            __asm__ volatile("movl %%cs, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        case SEGMENT_DS:
            __asm__ volatile("movl %%ds, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        case SEGMENT_ES:
            __asm__ volatile("movl %%es, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        case SEGMENT_FS:
            __asm__ volatile("movl %%fs, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        case SEGMENT_GS:
            __asm__ volatile("movl %%gs, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        case SEGMENT_SS:
            __asm__ volatile("movl %%ss, %0"
                             : "=r" (*(PUINT)Destination));
            break;
        default:
            Destination = NULLPTR;
            break;
    }
}

/**
 * Stores TR into the given memory area.
 *
 * @param Destination
 *        Supplies a pointer to the memory area where TR will be stores.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::StoreTaskRegister(OUT PVOID Destination)
{
    __asm__ volatile("str %0"
                     : "=m" (*(PULONG)Destination)
                     :
                     : "memory");
}

/**
 * Writes a value to the specified CPU control register.
 *
 * @param ControlRegister
 *        Supplies a number of a control register which controls the general behavior of a CPU.
 *
 * @param Value
 *        Suplies a value to write to the CR register.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::WriteControlRegister(IN USHORT ControlRegister,
                                       IN UINT_PTR Value)
{
    /* Write a value into specified control register */
    switch(ControlRegister)
    {
        case 0:
            /* Write value to CR0 */
            __asm__ volatile("mov %0, %%cr0"
                             :
                             : "r"(Value)
                             : "memory");
            break;
        case 2:
            /* Write value to CR2 */
            __asm__ volatile("mov %0, %%cr2"
                             :
                             : "r"(Value)
                             : "memory");
            break;
        case 3:
            /* Write value to CR3 */
            __asm__ volatile("mov %0, %%cr3"
                             :
                             : "r"(Value)
                             : "memory");
            break;
        case 4:
            /* Write value to CR4 */
            __asm__ volatile("mov %0, %%cr4"
                             :
                             : "r"(Value)
                             : "memory");
            break;
        case 8:
            /* Write value to CR8 */
            __asm__ volatile("mov %0, %%cr8"
                             :
                             : "r"(Value)
                             : "memory");
            break;
    }
}

/**
 * Writes a value to the specified CPU debug register.
 *
 * @param DebugRegister
 *        Supplies a number of a debug register for write operation.
 *
 * @param Value
 *        Suplies a value to write to the specified DR register.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::WriteDebugRegister(IN USHORT DebugRegister,
                                     IN UINT_PTR Value)
{
    /* Write a value into specified debug register */
    switch(DebugRegister)
    {
        case 0:
            /* Write value to DR0 */
            __asm__ volatile("mov %0, %%dr0"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 1:
            /* Write value to DR1 */
            __asm__ volatile("mov %0, %%dr1"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 2:
            /* Write value to DR2 */
            __asm__ volatile("mov %0, %%dr2"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 3:
            /* Write value to DR3 */
            __asm__ volatile("mov %0, %%dr3"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 4:
            /* Write value to DR4 */
            __asm__ volatile("mov %0, %%dr4"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 5:
            /* Write value to DR5 */
            __asm__ volatile("mov %0, %%dr5"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 6:
            /* Write value to DR6 */
            __asm__ volatile("mov %0, %%dr6"
                             :
                             : "r" (Value)
                             : "memory");
            break;
        case 7:
            /* Write value to DR7 */
            __asm__ volatile("mov %0, %%dr7"
                             :
                             : "r" (Value)
                             : "memory");
            break;
    }
}

/**
 * Writes the specified value to the program status and control (EFLAGS) register.
 *
 * @param Value
 *        The value to write to the EFLAGS register.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::WriteEflagsRegister(IN UINT_PTR Value)
{
    __asm__ volatile("push %0\n"
                     "popf"
                     :
                     : "rim" (Value));
}

/**
 * Writes a 64-bit value to the requested Model Specific Register (MSR).
 *
 * @param Register
 *        Supplies the MSR register to write.
 *
 * @param Value
 *        Supplies the 64-bit value to write.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::WriteModelSpecificRegister(IN ULONG Register,
                                             IN ULONGLONG Value)
{
    ULONG Low = Value & 0xFFFFFFFF;
    ULONG High = Value >> 32;

    __asm__ volatile("wrmsr"
                     :
                     : "c" (Register),
                       "a" (Low),
                       "d" (High));
}

/**
 * Yields a current thread running on the processor.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
AR::CpuFunctions::YieldProcessor(VOID)
{
    __asm__ volatile("pause"
                     :
                     :
                     : "memory");
}
