/*
 * Copyright (C) 2014 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

  /* GCC ARM assembler */

  .text
  .code 32
  .align 4  /* 0 */

  /* Constants */

  .equ  USERMODE,   0x10
  .equ  FIQMODE,    0x11
  .equ  IRQMODE,    0x12
  .equ  SVCMODE,    0x13
  .equ  ABORTMODE,  0x17
  .equ  UNDEFMODE,  0x1b
  .equ  SYSTEMMODE, 0x1f
  .equ  MODEMASK,   0x1f
  .equ  NOINT,      0xc0           /* Disable both IRQ & FIR */
  .equ  TBIT,       0x20
  .equ  IRQMASK,    0x80
  .equ  FIQMASK,    0x40


  /* External references */

  .extern  sched_active_thread
  .extern  sched_context_switch_request
  .extern  sched_run

/* Public functions declared in this file */
  .global  arm_irq_handler
  .global  cpu_switch_context_exit
  .global  task_return
  .global  ctx_switch

ctx_switch:
    /* Save return address on stack */
    stmfd   sp!, {lr}

    /* disable interrupts */
    mov     lr, #NOINT|SVCMODE
    msr     CPSR_c, lr

ctx_switch2:
    /* get stack pointer from user mode into lr */
    stmfd   sp, {sp}^
    sub     sp, sp, #4
    ldmfd   sp!, {lr}

    /* save registers on user mode stack pointed to by lr */
    stmfd   lr, {r0-r14}^
    sub     lr, lr, #60

    /* restore saved return address from stack */
    ldmfd   sp!, {r1}

    /* get spsr into register */
    mrs r0, spsr

    /* store return address and spsr on user mode stack */
    stmfd   lr!, {r0, r1}

    /* save user mode stack pointer in *sched_active_thread */
    ldr     r1, =sched_active_thread   /* r1 = &sched_active_thread */
    ldr     r1, [r1]                /* r1 = *r1 = sched_active_thread */

    str     lr, [r1]                /* store stack pointer in tasks tcb*/
    /* now the calling task has all its registers saved on its stack and it's SP is saved in its tcb */


    /* call scheduler so sched_active_thread points to the next task */
    bl      sched_run
    b       task_return
    /* never coming back */

cpu_switch_context_exit:
    mov r0, #NOINT|SVCMODE
    msr cpsr, r0

    /* call scheduler so sched_active_thread points to the next task */
    bl      sched_run

    /* continue in task_return: */

task_return:
    /* load tcb->stackpointer in r0 */
    ldr     r0, =sched_active_thread   /* r0 = &sched_active_thread */
    ldr     r0, [r0]                /* r0 = *r0 = sched_active_thread */
    ldr     r0, [r0]

    /* restore saved spsr and return address from tasks stack */
    ldmfd   r0!, {r1,lr}
    msr     SPSR, r1

    /* Restore all registers, PC & cpsr */
    ldmfd   r0, {r0-lr}^

    /* return to task */
    movs pc, lr

/*----------------------------------------------------------------------------
*
*----------------------------------------------------------------------------*/
arm_irq_handler:
    sub     lr, lr, #4

    /* save interrupted tasks PC onto stack */
    stmfd  sp!, {lr}

    /* save all registers on isr stack */
    stmfd  sp!, {r0-r12}

    /* save spsr on stack */
    MRS R0, SPSR
    STMFD  SP!, {R0}

    MRS R1, CPSR
    MSR SPSR, R1

    /* jump into vic interrupt */
    mov    r0, #0xffffff00    /* lpc23xx */
    ldr    r0, [r0]
    add    lr,pc,#4

    mov     pc, r0

    /* restore spsr from stack */
    LDMFD  SP!, {R0}
    MSR SPSR, R0

    /* check if context switch was requested by irq */
    ldr r0, =sched_context_switch_request
    ldr r0, [r0]

    cmp r0, #0x00
    bne     switch_context_int

exit_irq_int:
    /* recover general purpose registers */
    ldmfd  sp!, {r0-r12}

    /* recover tasks PC into lr */
    ldmfd  sp!, {lr}

    /* now jump back into task */
    movs pc, lr

switch_context_int:
    /* recover general purpose registers */
    ldmfd  sp!, {r0-r12}

    b ctx_switch2
