/*++

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. The label below is used by the software
    // interrupt code, not this function, to reuse a bit of code.
    //

ArpFullRestore:
    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). Upon entry, R0 holds the system call number,
    and R1 holds the system call parameter.

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.
    sub     %sp, #(TRAP_FRAME_SIZE - 8 + 4)     @ Make space for rest of frame.
    add     %r2, %sp, #4                        @ 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 if the system call gets restarted.
    // Save R0 into R2 since R0 is used for the return value.
    //

    str     %r0, [%r2, #TRAP_R2]
    str     %r1, [%r2, #TRAP_R1]

    CFI_OFFSET(r1, TRAP_R1 + 4)
    CFI_OFFSET(r0, TRAP_R2 + 4)
    CFI_OFFSET(sp, TRAP_USERSP + 4)
    CFI_OFFSET(lr, TRAP_USERLR + 4)
    CFI_OFFSET(pc, TRAP_PC + 4)

    //
    // 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 four parameters: the system call number,
    // system call parameter, a pointer to the trap frame, and a pointer to an
    // outgoing boolean.
    //

    mov     %r3, %sp                            @ Set boolean pointer parameter.
    bl      KeSystemCallHandler                 @ Handle system call.
    ldmia   %sp!, {%r1}                         @ Pop signal pending boolean.

    CFI_OFFSET(r1, TRAP_R1)
    CFI_OFFSET(r0, TRAP_R2)
    CFI_OFFSET(sp, TRAP_USERSP)
    CFI_OFFSET(lr, TRAP_USERLR)
    CFI_OFFSET(pc, TRAP_PC)

    //
    // Determine whether or not a signal is pending on the thread. Go do the
    // slow full save if there is one.
    //

    eor     %r12, %r12                          @ Scrub volatile R12.
    tst     %r1, %r1                            @ See if r1 is FALSE.
    eor     %r3, %r3                            @ Scrub volatile R3.
    bne     ArpSoftwareInterruptSignalCheck     @ Jump to dispatch signal.
    eor     %r2, %r2                            @ Scrub volatile R2.

ArpSoftwareInterruptRestore:

    //
    // See if the trap frame was upgraded to a complete one. If so, go do the
    // slow restore. Add 1 to the exception CPSR to see if it was -1 (the
    // magic value indicating an incomplete trap frame).
    //

    ldr     %r1, [%sp, #TRAP_EXCEPTION_CPSR]    @ Get hint about frame complete.
    adds    %r1, %r1, #1                        @ Add 1 and set flags.
    bne     ArpFullRestore                      @ Do a full restore if needed.

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

    mov     %r1, %sp                            @ 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 most of the trap frame.
    //

    add     %sp, #(TRAP_FRAME_SIZE - 8)         @ Pop up to PC/Cpsr.
    CFI_UNDEFINED(r0)
    CFI_UNDEFINED(r1)
    CFI_UNDEFINED(sp)
    CFI_OFFSET(lr, 0)
    CFI_UNDEFINED(pc)

    //
    // Clear the remaining volatile register to avoid leaking kernel state.
    //

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

ArpSoftwareInterruptSignalCheck:

    //
    // See if the trap frame is already complete as a result of the system
    // call operation (like restore from signal).
    //

    ldr     %r1, [%sp, #TRAP_EXCEPTION_CPSR]    @ Get hint about frame complete.
    adds    %r1, %r1, #1                        @ Add 1 and set flags.
    bne     ArpSoftwareInterruptSignalDispatch  @ Skip the save if zero now.

    //
    // Save the full trap frame. The CPSR, PC, user SP, user LR, exception CPSR,
    // R1, and R2 are the only values saved in the trap frame. R0 currently
    // holds the system call's return value. Save it in the trap frame.
    //

    CFI_UNDEFINED(lr)
    eor     %lr, %lr                            @ Zero SVC link register.
    str     %r0, [%sp, #TRAP_R0]                @ Save the return value in R0.
    str     %lr, [%sp, #TRAP_EXCEPTION_CPSR]    @ Indicate frame is complete.
    add     %r0, %sp, #TRAP_PC                  @ Get location after SVC LR.
    stmdb   %r0!, {%r3-%r12, %lr}               @ Push registers and CPSR.
    str     %sp, [%sp]                          @ Save SP.

    //
    // The dispatch routine takes the trap frame and either dispatches a signal
    // or tinkers with the trap frame to restart the system call.
    //

ArpSoftwareInterruptSignalDispatch:
    mov     %r0, %sp                            @ Pass the trap frame.
    bl      PsApplyPendingSignalsOrRestart      @ Dispatch or restart.
    b       ArpSoftwareInterruptRestore         @ Jump in the line.

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.
//

FUNCTION 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

    CFI_DEF_CFA(r4, 0)

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

    CFI_DEF_CFA(sp, 0)

    //
    // Restore state and return.
    //

    ARM_EXIT_INTERRUPT

END_FUNCTION ArpCommonInterruptEntry

