/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/ar/i686/archsup.S
 * DESCRIPTION:     Provides i686 architecture features not implementable in C.
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 *                  Aiken Harris <harraiken91@gmail.com>
 */

#include <xtkmapi.h>
#include <xtadk.h>

.altmacro
.text


/**
 * Creates a task, trap or interrupt handler for the specified vector.
 *
 * @param Vector
 *        Supplies a vector number.
 *
 * @param Type
 *        Specifies whether the handler is designed to handle an interrupt, a task or a trap.
 *
 * @return This macro does not return any value.
 *
 * @since XT 1.0
 */
.macro ArCreateHandler Vector Type
.global _Ar\Type\Vector
_Ar\Type\Vector:
     /* Check handler type */
    .ifc \Type,Task
_Ar\Type\Vector\()Start:
        /* Clear the Task Switch flag */
        clts

        /* Allocate the trap frame and inject the hardware vector for the dispatcher */
        sub $KTRAP_FRAME_SIZE, %esp
        movl $\Vector, KTRAP_FRAME_Vector(%esp)

        /* Pass the trap frame pointer as an argument and clear the direction flag */
        push %esp
        cld

        /* Pass control to the trap dispatcher */
        call _ArDispatchTrap

        /* Discard the argument and deallocate the trap frame */
        add $4, %esp
        add $KTRAP_FRAME_SIZE, %esp

        /* Hardware task return */
        iretl

        /* Spin back to the entry point to rearm the task gate */
        jmp _Ar\Type\Vector\()Start
    .else
        /* Check handler type */
        .ifc \Type,Trap
            /* Push fake error code for non-error vector traps */
            .if \Vector != 8 && \Vector != 10 && \Vector != 11 && \Vector != 12 && \Vector != 13 && \Vector != 14 && \Vector != 17 && \Vector != 30
                push $0
            .endif
        .else
            /* Push fake error code for interrupts */
            push $0
        .endif

        /* Push vector number */
        push $\Vector

        /* Push General Purpose Registers */
        push %ebp
        push %edi
        push %esi
        push %edx
        push %ecx
        push %ebx
        push %eax

        /* Reserve space for other registers and point RBP to the trap frame */
        sub $(KTRAP_FRAME_SIZE - KTRAP_FRAME_REGISTERS_SIZE), %esp
        lea (%esp), %ebp

        /* Store segment selectors */
        mov %gs, KTRAP_FRAME_SegGs(%ebp)
        mov %fs, KTRAP_FRAME_SegFs(%ebp)
        mov %es, KTRAP_FRAME_SegEs(%ebp)
        mov %ds, KTRAP_FRAME_SegDs(%ebp)

        /* Store debug registers */
        mov %dr7, %eax
        mov %eax, KTRAP_FRAME_Dr7(%ebp)
        mov %dr6, %eax
        mov %eax, KTRAP_FRAME_Dr6(%ebp)
        mov %dr3, %eax
        mov %eax, KTRAP_FRAME_Dr3(%ebp)
        mov %dr2, %eax
        mov %eax, KTRAP_FRAME_Dr2(%ebp)
        mov %dr1, %eax
        mov %eax, KTRAP_FRAME_Dr1(%ebp)
        mov %dr0, %eax
        mov %eax, KTRAP_FRAME_Dr0(%ebp)

        /* Store CR2 and CR3 */
        mov %cr3, %eax
        mov %eax, KTRAP_FRAME_Cr3(%ebp)
        mov %cr2, %eax
        mov %eax, KTRAP_FRAME_Cr2(%ebp)

        /* Test previous mode */
        movl $0, KTRAP_FRAME_PreviousMode(%ebp)
        mov KTRAP_FRAME_SegCs(%ebp), %ax
        and $3, %al
        mov %al, KTRAP_FRAME_PreviousMode(%ebp)
        jz Dispatch\Type\Vector

        /* Load Kernel PB selector into FS */
        mov $KGDT_R0_PB, %ax
        mov %ax, %fs

        /* Set sane data segment selectors */
        mov $(KGDT_R3_DATA | RPL_MASK), %ax
        mov %ax, %ds
        mov %ax, %es

Dispatch\Type\Vector:
        /* Push Frame Pointer and clear direction flag */
        push %esp
        cld

        .ifc \Type,Trap
            /* Pass to the trap dispatcher */
            call _ArDispatchTrap
        .else
            /* Pass to the interrupt dispatcher */
            call _ArDispatchInterrupt
        .endif

        /* Clean up the stack */
        add $4, %esp

        /* Test previous mode and disable interrupts before user mode return */
        testb $1, KTRAP_FRAME_PreviousMode(%ebp)
        jz RestoreState\Type\Vector
        cli

RestoreState\Type\Vector:
        /* Restore segment selectors */
        mov KTRAP_FRAME_SegDs(%ebp), %ds
        mov KTRAP_FRAME_SegEs(%ebp), %es
        mov KTRAP_FRAME_SegFs(%ebp), %fs
        mov KTRAP_FRAME_SegGs(%ebp), %gs

        /* Free stack space */
        add $(KTRAP_FRAME_SIZE - KTRAP_FRAME_REGISTERS_SIZE), %esp

        /* Pop General Purpose Registers */
        pop %eax
        pop %ebx
        pop %ecx
        pop %edx
        pop %esi
        pop %edi
        pop %ebp

        /* Skip error code and vector number, then return */
        add $(2 * 4), %esp
        iretl
    .endif
.endm

/* Populate common interrupt, task and trap handlers */
.irp i,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
    .irp j,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
        ArCreateHandler 0x\i\j Interrupt
        .if 0x\i\j == 0x02 || 0x\i\j == 0x08
            ArCreateHandler 0x\i\j Task
        .else
            ArCreateHandler 0x\i\j Trap
        .endif
    .endr
.endr

/* Define array of pointers to the interrupt handlers */
.global _ArInterruptEntry
_ArInterruptEntry:
.irp i,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
    .irp j,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
        .long _ArInterrupt0x\i\j
    .endr
.endr

/* Define array of pointers to the trap handlers */
.global _ArTrapEntry
_ArTrapEntry:
.irp i,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
    .irp j,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
        .if 0x\i\j == 0x02 || 0x\i\j == 0x08
            .long _ArTask0x\i\j
        .else
            .long _ArTrap0x\i\j
        .endif
    .endr
.endr

/**
 * Enables eXtended Physical Addressing (XPA). On i386, this is just a stub.
 *
 * @param PageMap
 *        Supplies a pointer to the page map to be used.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
.global ArEnableExtendedPhysicalAddressing
ArEnableExtendedPhysicalAddressing:

.global ArEnableExtendedPhysicalAddressingEnd
ArEnableExtendedPhysicalAddressingEnd:

/**
 * Handles a spurious interrupt allowing it to end up.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
.global _ArHandleSpuriousInterrupt
_ArHandleSpuriousInterrupt:
    iret

/**
 * Starts an application processor (AP).
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
.global _ArStartApplicationProcessor
_ArStartApplicationProcessor:
    /* Enter 16-bit real mode */
    .code16

    /* Disable interrupts and clear direction flag */
    cli
    cld

    /* Establish a flat addressing baseline */
    movw %cs, %ax
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %ss

    /* Calculate absolute physical base address */
    xorl %ebx, %ebx
    movw %cs, %bx
    shll $4, %ebx

    /* Set up a temporary stack for the AP initialization */
    movl %ebx, %esp
    addl $0x1000, %esp

    /* Load the temporary Global Descriptor Table */
    leal (ApTemporaryGdtDesc - _ArStartApplicationProcessor)(%ebx), %eax
    movl %eax, (ApTemporaryGdtBase - _ArStartApplicationProcessor)
    lgdtl (ApTemporaryGdtSize - _ArStartApplicationProcessor)

    /* Enable Protected Mode */
    movl %cr0, %eax
    orl $0x01, %eax
    movl %eax, %cr0

    /* Far return to enter 32-bit protected mode */
    leal (ApEnterProtectedMode - _ArStartApplicationProcessor)(%ebx), %eax
    pushl $KGDT_R0_CODE
    pushl %eax
    lretl

ApEnterProtectedMode:
    /* Enter 32-bit protected mode */
    .code32

    /* Setup all data segment registers */
    movw $KGDT_R0_DATA, %ax
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %ss
    xorw %ax, %ax
    movw %ax, %fs
    movw %ax, %gs

    /* Locate PROCESSOR_START_BLOCK structure */
    leal (_ArStartApplicationProcessorEnd - _ArStartApplicationProcessor)(%ebx), %edi

    /* Load CR4 from BSP, but mask PCIDE and PGE */
    movl PROCESSOR_START_BLOCK_Cr4(%edi), %eax
    andl $~(CR4_PGE | CR4_PCIDE), %eax
    movl %eax, %cr4

    /* Load the Kernel Page Directory Base from BSP */
    movl PROCESSOR_START_BLOCK_Cr3(%edi), %eax
    movl %eax, %cr3

    /* Enable Paging */
    movl %cr0, %eax
    orl $CR0_PG, %eax
    movl %eax, %cr0

    /* Load dedicated Stack for AP */
    movl PROCESSOR_START_BLOCK_InitialStack(%edi), %esp

    /* Save the pointer to PROCESSOR_START_BLOCK */
    movl %edi, %ecx
    pushl %edi

    /* Call the EntryPoint routine */
    movl PROCESSOR_START_BLOCK_EntryPoint(%edi), %eax
    call *%eax

    /* Fire the breakpoint and halt if the entry point returns */
.ApNoReturnPoint:
    int $0x03
    hlt
    jmp .ApNoReturnPoint

/* Data section for temporary GDT */
.align 8
ApTemporaryGdtSize: .short _ArStartApplicationProcessorEnd - ApTemporaryGdtDesc - 1
ApTemporaryGdtBase: .long 0x00000000
ApTemporaryGdtDesc: .quad 0x0000000000000000, 0x00CF9A000000FFFF, 0x00CF92000000FFFF

.global _ArStartApplicationProcessorEnd
_ArStartApplicationProcessorEnd:
