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

#include <xtos.hh>


/**
 * Initializes CPU architecture dependent context of a thread.
 *
 * @param Thread
 *        Supplies a pointer to the thread being initialized.
 *
 * @param SystemRoutine
 *        Supplies a pointer to the routine called during first scheduling.
 *
 * @param StartRoutine
 *        Supplies a pointer to the routine called during thread startup.
 *
 * @param StartContext
 *        Supplies a pointer to a context data that will be passed to start routine.
 *
 * @param ContextRecord
 *        Supplies a pointer to a context record which stores the initial state of the user mode thread.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::KThread::InitializeThreadContext(IN PKTHREAD Thread,
                                     IN PKSYSTEM_ROUTINE SystemRoutine,
                                     IN PKSTART_ROUTINE StartRoutine,
                                     IN PVOID StartContext,
                                     IN PCONTEXT ContextRecord)
{
    PKTHREAD_INIT_FRAME ThreadFrame;
    CONTEXT ContextFrame;

    /* Set initial thread frame */
    ThreadFrame = ((PKTHREAD_INIT_FRAME)Thread->InitialStack) - 1;

    /* Fill floating point save area with zeroes */
    RTL::Memory::ZeroMemory(&ThreadFrame->NpxFrame, sizeof(FLOATING_SAVE_AREA));

    /* Check if context provided for this thread */
    if(ContextRecord)
    {
        /* Make a local copy of the context to avoid mutating caller's memory */
        RTL::Memory::CopyMemory(&ContextFrame, ContextRecord, sizeof(CONTEXT));

        /* Disable debug registers and enable context registers */
        ContextFrame.ContextFlags |= CONTEXT_CONTROL;
        ContextFrame.ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;

        /* Align the stack and reserve space for 4 parameters and return value */
        ContextFrame.Rsp = (ContextFrame.Rsp & ~15) - 40;

        /* Set CS and SS segments for user mode */
        ContextFrame.SegCs = KGDT_R3_CODE | RPL_MASK;
        ContextFrame.SegSs = KGDT_R3_DATA | RPL_MASK;

        /* Fill exception and trap frames with zeroes */
        RTL::Memory::ZeroMemory(&ThreadFrame->ExceptionFrame, sizeof(KEXCEPTION_FRAME));
        RTL::Memory::ZeroMemory(&ThreadFrame->TrapFrame, sizeof(KTRAP_FRAME));

        /* Translate Context frame to Trap frame */
        KE::Processor::RestoreProcessorContext(&ThreadFrame->TrapFrame, &ThreadFrame->ExceptionFrame,
                                               &ContextFrame, ContextFrame.ContextFlags);

        /* This is user mode thread */
        Thread->PreviousMode = UserMode;
        ThreadFrame->TrapFrame.PreviousMode = UserMode;

        /* Enable floating point state */
        Thread->NpxState = NPX_STATE_SCRUB;

        /* Set initial floating point state */
        ThreadFrame->NpxFrame.ControlWord = 0x27F;
        ThreadFrame->NpxFrame.DataOffset = 0;
        ThreadFrame->NpxFrame.DataSelector = 0;
        ThreadFrame->NpxFrame.ErrorOffset = 0;
        ThreadFrame->NpxFrame.ErrorOpcode = 0;
        ThreadFrame->NpxFrame.ErrorSelector = 0;
        ThreadFrame->NpxFrame.StatusWord = 0;
        ThreadFrame->NpxFrame.TagWord = 0xFFFF;

        /* Set initial MXCSR register value */
        ThreadFrame->TrapFrame.MxCsr = INITIAL_MXCSR;

        /* Clear DR6 and DR7 registers */
        ThreadFrame->TrapFrame.Dr6 = 0;
        ThreadFrame->TrapFrame.Dr7 = 0;

        /* Terminate the Exception Handler List */
        ThreadFrame->TrapFrame.ExceptionFrame = (ULONGLONG)NULLPTR;

        /* Initialize exception frame */
        ThreadFrame->ExceptionFrame.P1Home = (ULONGLONG)StartContext;
        ThreadFrame->ExceptionFrame.P2Home = (ULONGLONG)StartRoutine;
        ThreadFrame->ExceptionFrame.P3Home = (ULONGLONG)SystemRoutine;
        ThreadFrame->ExceptionFrame.P4Home = (ULONGLONG)SystemRoutine;

        /* Set the routine that will handle the thread finishing its initialization and transition it to UserMode */
        ThreadFrame->StartFrame.Return = (ULONG64)SwitchToUserMode;
    }
    else
    {
        /* This is kernel mode thread */
        Thread->PreviousMode = KernelMode;

        /* Disable floating point state */
        Thread->NpxState = NPX_STATE_UNUSED;

        /* Set the routine that will handle a system thread that unexpectedly finished its execution */
        ThreadFrame->StartFrame.Return = (ULONG64)HandleSystemThreadExit;
    }

    /* Initialize thread startup information */
    ThreadFrame->StartFrame.P1Home = (ULONGLONG)StartContext;
    ThreadFrame->StartFrame.P2Home = (ULONGLONG)StartRoutine;
    ThreadFrame->StartFrame.P3Home = (ULONGLONG)SystemRoutine;
    ThreadFrame->StartFrame.P4Home = (ULONGLONG)SystemRoutine;

    /* Initialize switch frame */
    ThreadFrame->SwitchFrame.ApcBypass = APC_LEVEL;
    ThreadFrame->SwitchFrame.MxCsr = INITIAL_MXCSR;
    ThreadFrame->SwitchFrame.Rbp = (ULONGLONG)&ThreadFrame->TrapFrame;
    ThreadFrame->SwitchFrame.Return = (ULONGLONG)RunThread;

    /* Set thread stack boundaries */
    Thread->InitialStack = (PVOID)&ThreadFrame->NpxFrame;
    Thread->KernelStack = &ThreadFrame->SwitchFrame;
}

/**
 * Serves as the initial execution point for all threads after first context switch.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTASSEMBLY
XTAPI
VOID
KE::KThread::RunThread(VOID)
{
    /* Initialize execution context, adjust runlevel and dispatch the thread */
    __asm__ volatile("xorq %%rbx, %%rbx\n"
                     "xorq %%r10, %%r10\n"
                     "xorq %%r11, %%r11\n"
                     "xorq %%r12, %%r12\n"
                     "xorq %%r13, %%r13\n"
                     "xorq %%r14, %%r14\n"
                     "xorq %%r15, %%r15\n"
                     "xorq %%rbp, %%rbp\n"
                     "xorq %%rdi, %%rdi\n"
                     "xorq %%rsi, %%rsi\n"
                     "subq $32, %%rsp\n"
                     "movb $%c[RunLevel], %%cl\n"
                     "callq %P[LowerRunLevel]\n"
                     "addq $32, %%rsp\n"
                     "movq 8(%%rsp), %%rcx\n"
                     "movq 0(%%rsp), %%rdx\n"
                     "movq 24(%%rsp), %%rax\n"
                     "subq $32, %%rsp\n"
                     "callq *%%rax\n"
                     "addq $32, %%rsp\n"
                     "addq $40, %%rsp\n"
                     "ret\n"
                     :
                     : [RunLevel] "i" (APC_LEVEL),
                       [LowerRunLevel] "i" (KE::RunLevel::LowerRunLevel)
                     : "memory");
}
