/*++

Copyright (c) 2012 Minoca Corp.

    This file is licensed under the terms of the GNU General Public License
    version 3. Alternative licensing terms are available. Contact
    info@minocacorp.com for details. See the LICENSE file at the root of this
    project for complete licensing information.

Module Name:

    trap.S

Abstract:

    This module implements interrupt and exception trap management, such as
    saving and restoring registers.

Author:

    Evan Green 11-Aug-2012

Environment:

    Kernel mode

--*/

##
## ------------------------------------------------------------------- Includes
##

#include <minoca/kernel/arm.inc>

##
## ---------------------------------------------------------------- Definitions
##

##
## ---------------------------------------------------------------------- Code
##

ASSEMBLY_FILE_HEADER

##
## VOID
## ArpInitializeExceptionStacks (
##     PVOID ExceptionStacksBase,
##     ULONG ExceptionStackSize
##     )
##

/*++

Routine Description:

    This routine initializes the stack pointer for all privileged ARM modes. It
    switches into each mode and initializes the banked r13. This function
    should be called with interrupts disabled and returns with interrupts
    disabled.

Arguments:

    ExceptionStacksBase - Supplies a pointer to the lowest address that should
        be used for exception stacks. Each stack takes up 16 bytes and there are
        4 modes, so at least 64 bytes are needed.

    ExceptionStackSize - Supplies the size of each exception stack.

Return Value:

    None.

--*/

FUNCTION ArpInitializeExceptionStacks

    ##
    ## Load R1 with an individual stack size.
    ##

    add     %r0, %r0, %r1

    ##
    ## Disable interrupts and switch into IRQ mode. Note that this also
    ## clobbers the flags register.
    ##

    mov     %r2, #(PSR_FLAG_IRQ | ARM_MODE_IRQ)
    msr     CPSR_cxsf, %r2
    mov     %sp, %r0
    add     %r0, %r0, %r1

    ##
    ## Initialize the FIQ stack.
    ##

    mov     %r2, #(PSR_FLAG_IRQ | ARM_MODE_FIQ)
    msr     CPSR_cxsf, %r2
    mov     %sp, %r0
    add     %r0, %r0, %r1

    ##
    ## Initialize the undefined instruction stack.
    ##

    mov     %r2, #(PSR_FLAG_IRQ | ARM_MODE_UNDEF)
    msr     CPSR_cxsf, %r2
    mov     %sp, %r0
    add     %r0, %r0, %r1

    ##
    ## Initialize the data fetch abort stack.
    ##

    mov     %r2, #(PSR_FLAG_IRQ | ARM_MODE_ABORT)
    msr     CPSR_cxsf, %r2
    mov     %sp, %r0

    ##
    ## Switch back to SVC mode and return.
    ##

    mov     %r2, #(PSR_FLAG_IRQ | ARM_MODE_SVC)
    msr     CPSR_cxsf, %r2
    bx      %lr

END_FUNCTION ArpInitializeExceptionStacks

##
## VOID
## ArpUndefinedInstructionEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by an undefined
    instruction. It uses a largely separate code path from normal exceptions
    to avoid recursively breaking into the debugger.

Arguments:

    None.

Return Value:

    None.

--*/

FUNCTION ArpUndefinedInstructionEntry

    ##
    ## Save state and create a trap frame.
    ##

    ARM_ENTER_INTERRUPT

    ##
    ## Call the main dispatch routine routine with a pointer to the trap frame
    ## as the only parameter. Align the stack down even though it shouldn't be
    ## strictly necessary in case something bad happened.
    ##

    mov     %r0, %sp
    mov     %r4, %sp
    and     %r1, %r4, #0xFFFFFFF0       @ Align stack down for fear of badness.
    mov     %sp, %r1
    bl      KeDispatchUndefinedInstructionException
    mov     %sp, %r4

    ##
    ## Restore state and return.
    ##

    ARM_EXIT_INTERRUPT

END_FUNCTION ArpUndefinedInstructionEntry

##
## INTN
## ArpSoftwareInterruptEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by a software
    interrupt (a system call).

Arguments:

    None.

Return Value:

    STATUS_SUCCESS or positive integer on success.

    Error status code on failure.

--*/

FUNCTION ArpSoftwareInterruptEntry
    srsdb   %sp!, #ARM_MODE_SVC                 @ Push lr and spsr.
    tst     %r2, %r2                            @ See if "full" is zero.
    bne     ArpSoftwareInterruptEntrySlow       @ Go to slow path if non-zero.
    sub     %sp, #(TRAP_FRAME_SIZE - 8)         @ Make space for rest of frame.
    mov     %r2, %sp                            @ Get stack/trap frame param.
    cps     #ARM_MODE_SYSTEM                    @ Switch to system mode.
    str     %lr, [%r2, #TRAP_USERLR]            @ Save usermode LR.
    str     %sp, [%r2, #TRAP_USERSP]            @ Save usermode SP.
    cpsie   i, #ARM_MODE_SVC                    @ Enable interrupts, svc mode.

    ##
    ## Save R0 and R1. These are needed by the signal dispatcher and if the
    ## system call gets restarted.
    ##

    stmdb   %sp!, {%r0, %r1}                    @ Save the parameters.

    ##
    ## Set the exception CPSR to something wild as a hint that this trap frame
    ## is incomplete.
    ##

    mov     %r3, #0xFFFFFFFF                    @ Create -1.
    str     %r3, [%r2, #TRAP_EXCEPTION_CPSR]    @ Save into exception CPSR.

    ##
    ## The system call routine takes three parameters: the system call number,
    ## system call parameter, and a pointer to the trap frame. User-mode
    ## already set up the first two parameters in R0 and R1, and they were
    ## preserved throughout the context save process. R2 already holds the trap
    ## frame from above.
    ##

    bl      KeSystemCallHandler                 @ Handle system call.

    ##
    ## Determine whether or not a signal is pending on the thread. Use the
    ## special macro to get the current thread so that R0 is not trashed. Then
    ## scrub the volatiles that will no longer be used so that user mode does
    ## not get any leaked kernel mode values.
    ##

    ARM_GET_CURRENT_THREAD                      @ Get current thread in R1.
    eor     %r2, %r2                            @ Scrub volatile R2.
    eor     %r3, %r3                            @ Scrub volatile R3.
    eor     %r12, %r12                          @ Scrub volatile R12.
    ldr     %r1, [%r1, #THREAD_SIGNAL_PENDING]  @ Load signal pending status.
    cmp     %r1, #ThreadSignalPending           @ Compare to signal pending.
    beq     ArpSoftwareInterruptFastSignal      @ Jump to dispatch signal.

    ##
    ## Restore the user mode stack and link registers. Do not restore or
    ## clobber R0 as it holds the system call's return value.
    ##

    add     %r1, %sp, #8                        @ Get trap frame pointer.
    cpsid   i, #ARM_MODE_SYSTEM                 @ Switch to system mode.
    ldr     %sp, [%r1, #TRAP_USERSP]            @ Restore usermode SP.
    ldr     %lr, [%r1, #TRAP_USERLR]            @ Restore usermode LR.
    cpsid   i, #ARM_MODE_SVC                    @ Switch back to svc mode.

    ##
    ## Pop off the saved R0 and R1 and most of the trap frame.
    ##

    add     %sp, #TRAP_FRAME_SIZE               @ Pop up to PC/Cpsr.

    ##
    ## Clear the remainig volatile register to avoid leaking kernel state.
    ##

    eor     %r1, %r1
    rfeia   %sp!                                @ Restore PC and CPSR. Bye!

    ##
    ## Save the full trap frame. The CPSR, PC, user SP, user LR and exception
    ## CPSR are the only values saved in the trap frame. The exception CPSR is
    ## a dummy value, but keep it so that the signal dispatcher knows this trap
    ## frame came from the fast path. R0 currently holds the system call's
    ## return value. Do not scrub it. Just save it in the trap frame.
    ##

ArpSoftwareInterruptFastSignal:
    eor     %lr, %lr                            @ Zero SVC link register.
    eor     %r1, %r1                            @ Scrub volatile R1.
    str     %r0, [%sp, #(TRAP_R0 + 8)]          @ Save the return value in R0.
    mrs     %r0, cpsr                           @ Get the "exception" CPSR.
    str     %r0, [%sp, #(TRAP_EXCEPTION_CPSR + 8)] @ Save "exception" CPSR.
    add     %r0, %sp, #(TRAP_PC + 8)            @ Get location after SVC LR.
    stmdb   %r0!, {%r1-%r12, %lr}               @ Push registers and CPSR.
    str     %sp, [%sp, #8]                      @ Save SP.

    ##
    ## Jump down to the slow signal path which starts an IT block. The compare
    ## that dropped the fast path to dispatch a signal should have set the zero
    ## flag and none of the instructions above should have changed the flags,
    ## making the IT block safe to jump to.
    ##

    b       ArpSoftwareInterruptSlowSignal

    ##
    ## Perform the slow version of the system call that builds up a complete
    ## trap frame. This is used for system calls like fork and exec, where the
    ## trap frame is used in the system call.
    ##

ArpSoftwareInterruptEntrySlow:
    mov     %lr, #0                              @ Zero SVC link register.
    stmdb   %sp!, {%r1-%r12, %lr}                @ Push general registers.
    mrs     %r2, cpsr                            @ Get the "exception" CPSR.
    stmdb   %sp!, {%r0, %r2}                     @ Push exception CPSR and R0.
    sub     %sp, #12                             @ Account for pushes.
    mov     %r2, %sp                             @ Get stack/trap frame.
    cps     #ARM_MODE_SYSTEM                     @ Switch to system mode.
    str     %lr, [%r2, #TRAP_USERLR]             @ Save usermode SP.
    str     %sp, [%r2, #TRAP_USERSP]             @ Save usermode LR.
    cpsie   i, #ARM_MODE_SVC                     @ Enable interrupts, svc mode.

    ##
    ## Save R0 and R1. These are needed by the signal dispatcher if the system
    ## call gets restarted. It is not good enough to have them saved in the
    ## trap frame as KeSystemCallHandler may restore an old trap frame.
    ##

    stmdb   %sp!, {%r0, %r1}                     @ Save the parameters.
    str     %sp, [%r2]                           @ Save SP in the trap frame.

    ##
    ## Just like in the fast case, R0 and R1 are preserved as the first two
    ## parameters and R2 gets a pointer to the trap frame during the save.
    ##

    bl      KeSystemCallHandler

    ##
    ## Save the return value in the trap frame. It will either get restored on
    ## exit or preserved for later if a signal is applied.
    ##

    str     %r0, [%sp, #(TRAP_R0 + 8)]

    ##
    ## Determine whether or not a signal is pending on the thread.
    ##

    ARM_GET_CURRENT_THREAD                       @ Get current thread in R1.
    ldr     %r1, [%r1, #THREAD_SIGNAL_PENDING]   @ Load signal pending status.
    cmp     %r1, #ThreadSignalPending            @ Compare to signal pending.

ArpSoftwareInterruptSlowSignal:

    ##
    ## The dispatch routine takes the trap frame, system call number and
    ## system call parameter.
    ##

    ITTTT   EQ                                   @ If the zero flag is set...
    ldreq   %r2, [%sp, #4]                       @ Get system call parameter.
    ldreq   %r1, [%sp]                           @ Get system call number.
    addeq   %r0, %sp, #8                         @ Get trap frame.
    bleq    PsDispatchPendingSignalsOnCurrentThread @ Dispatch signal.

    ##
    ## A slow exit or an exit from a signal dispatch restores the full trap
    ## frame.
    ##

ArpSoftwareInterruptEntrySlowEnd:

    ##
    ## Pop off the saved system call number and parameter and the SVC stack
    ## pointer. The stack pointer should be the location of the saved system
    ## call number, which isn't needed.
    ##

    add     %sp, %sp, #12

    ##
    ## Restore the user mode stack and link registers.
    ##

    mov     %r0, %sp                            @ Get SVC stack pointer.
    cpsid   i, #ARM_MODE_SYSTEM                 @ Switch to system mode.
    ldr     %sp, [%r0, #(TRAP_USERSP - 4)]      @ Restore usermode SP.
    ldr     %lr, [%r0, #(TRAP_USERLR - 4)]      @ Resotre usermode LR.
    cpsid   i, #ARM_MODE_SVC                    @ Switch back to svc mode.
    ldr     %r0, [%sp, #(TRAP_R0 - 4)]          @ Restore R0.
    add     %sp, %sp, #16                       @ Pop up to R1.
    ldmia   %sp!, {%r1-%r12, %lr}               @ Restore general registers.
    rfeia   %sp!                                @ Restore PC and CPSR.

END_FUNCTION ArpSoftwareInterruptEntry

##
## VOID
## ArpPrefetchAbortEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by a prefetch abort
    (page fault).

Arguments:

    None.

Return Value:

    None.

--*/

FUNCTION ArpPrefetchAbortEntry

    ##
    ## In ARM mode, prefect aborts save the PC-4 in LR. For Thumb mode, data
    ## aborts save the PC in LR. Thus, the PC is always 4 bytes head of the
    ## faulting address.
    ##

    sub     %lr, %lr, #4                @ Prefetches go too far by 4.

    ##
    ## Save state and create a trap frame.
    ##

    ARM_ENTER_INTERRUPT

    ##
    ## Call the main dispatch routine routine with a pointer to the trap frame
    ## and 1 to indicate a prefetch abort.
    ##

    mov     %r0, %sp
    mov     %r1, #1
    blx     KeDispatchException

    ##
    ## Restore state and return.
    ##

    ARM_EXIT_INTERRUPT

END_FUNCTION ArpPrefetchAbortEntry

##
## VOID
## ArpDataAbortEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by a data abort (page
    fault).

Arguments:

    None.

Return Value:

    None.

--*/

FUNCTION ArpDataAbortEntry

    ##
    ## In ARM mode, data aborts save the PC in LR. For Thumb mode, data aborts
    ## save the PC+4 in LR. Thus, the PC is always 8 bytes head of the faulting
    ## address.
    ##

    sub     %lr, %lr, #8

    ##
    ## Save state and create a trap frame.
    ##

    ARM_ENTER_INTERRUPT

    ##
    ## Call the main dispatch routine routine with a pointer to the trap frame
    ## and 0 to indicate a prefetch abort.
    ##

    mov     %r0, %sp
    mov     %r1, #0
    blx     KeDispatchException

    ##
    ## Restore state and return.
    ##

    ARM_EXIT_INTERRUPT

END_FUNCTION ArpDataAbortEntry

##
## VOID
## ArpIrqEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by an external
    interrupt on the IRQ pin.

Arguments:

    None.

Return Value:

    None.

--*/

FUNCTION ArpIrqEntry
    b       ArpCommonInterruptEntry

END_FUNCTION ArpIrqEntry

##
## VOID
## ArpFiqEntry (
##     VOID
##     )
##

/*++

Routine Description:

    This routine directly handles an exception generated by an external
    interrupt on the FIQ pin.

Arguments:

    None.

Return Value:

    None.

--*/

FUNCTION ArpFiqEntry
    b       ArpCommonInterruptEntry

END_FUNCTION ArpFiqEntry

##
## --------------------------------------------------------- Internal Functions
##

##
## This code is entered as the result of any interrupt or exception. Its job is
## to transition back to the SVC stack and then call the real interrupt
## dispatch routine.
##

ArpCommonInterruptEntry:

    ##
    ## Save state and create a trap frame.
    ##

    ARM_ENTER_INTERRUPT

    ##
    ## Call the main dispatch routine routine with a pointer to the trap frame
    ## as the only parameter. Align the stack down in case the exception
    ## interrupted something unaligned.
    ##

    mov     %r0, %sp
    mov     %r4, %sp
    and     %r5, %r4, #0xFFFFFFF0
    mov     %sp, %r5
    bl      KeDispatchException
    mov     %sp, %r4

    ##
    ## Restore state and return.
    ##

    ARM_EXIT_INTERRUPT

