OpenCores
URL https://opencores.org/ocsvn/c0or1k/c0or1k/trunk

Subversion Repositories c0or1k

[/] [c0or1k/] [trunk/] [src/] [arch/] [arm/] [vectors.S.ARM] - Rev 6

Compare with Previous | Blame | View Log

/*
 * The vectors page. Includes all exception handlers.
 *
 * Copyright (C) 2007 Bahadir Balban
 */

#include INC_ARCH(asm.h)
#include INC_ARCH(asm-macros.S)

.section .data.vectors
__vector_vaddr:

BEGIN_PROC(arm_high_vector)
        b       arm_reset_exception
        b       arm_undef_exception_reentrant
        b       arm_swi_exception
        b       arm_prefetch_abort_exception_reentrant
        b       arm_data_abort_exception_reentrant
        nop
        b       arm_irq_exception_reentrant_with_schedule
        b       arm_fiq_exception
END_PROC(arm_high_vector)

.balign 4

/*
 * vect_reset
 *
 * Upon Entry:
 * - All registers are undefined and insignificant,
 * - FIQ/IRQs are disabled.
 * - PC:        0x00000000
 *
 *
 * PURPOSE:
 * CPU always starts executing from this vector
 * upon a HW reset. It may also be used as a SW reset.
 */
BEGIN_PROC(arm_reset_exception)
END_PROC(arm_reset_exception)


#if defined(CONFIG_SUBARCH_V5)
        .macro disable_irqs rx
                mrs     \rx, cpsr_fc
                orr     \rx, #ARM_IRQ_BIT
                msr     cpsr_fc, \rx
        .endm
        .macro enable_irqs rx
                mrs     \rx, cpsr_fc
                bic     \rx, #ARM_IRQ_BIT
                msr     cpsr_fc, \rx
        .endm
#endif

#if defined (CONFIG_SUBARCH_V7) || defined(CONFIG_SUBARCH_V6)
        .macro disable_irqs rx
                cpsid   ia
        .endm
        .macro enable_irqs rx
                cpsie   ia
        .endm
#endif

#if defined (CONFIG_SUBARCH_V7)
        .macro clear_exclusive
                clrex
        .endm
#else
        .macro clear_exclusive
        .endm
#endif

        /* Only works in SVC MODE. Know what you are doing! */
        .macro get_current rx
                bic     \rx, sp, #0xFF0
                bic     \rx, \rx, #0xF
        .endm
        /* Saves the address of system call argument registers pushed to stack
         * to the current task's ktcb. */
        .macro  ktcb_ref_saved_regs regs_addr, ktcb, regs_off
                get_current \ktcb
                ldr     \regs_off, =syscall_regs_offset
                ldr     \regs_off, [\regs_off]
                str     \regs_addr, [\ktcb, \regs_off]
        .endm
        /* Depending on the SPSR condition determines whether irqs should be enabled
         * during abort handling. If abort occured in userspace it orders irqs
         * should be enabled. Else if irqs come from kernel mode, it orders irqs are
         * enabled only if they were alreday enabled before the abort. */
        .macro  can_abort_enable_irqs temp1, r_spsr
                and \temp1, \r_spsr, #ARM_MODE_MASK
                cmp \temp1, #ARM_MODE_USR        @ Usermode indicates irqs can be enabled.
                beq 1f                      @ Z flag set. Which indicates "can enable"
                and \temp1, \r_spsr, #ARM_IRQ_BIT @ Clear irq bit indicates irqs were enabled
                cmp \temp1, #0              @ before the abort and can be safely enabled.
        1:                                  @ Z flag must be set for "can enable" here.
        .endm

        /* Pushes the user sp and lr to stack, updates the stack pointer */
        .macro push_user_sp_lr sp
                @ stack state: (Low) |..|..|->(Original)| (High)
                stmfd   \sp, {sp, lr}^  @ Push USR banked regs to stack.
                nop                     @ Need a NOOP after push/popping user registers.
                @ stack state: (Low) |SP_USR|LR_USR|->(Original)| (High)
                sub     \sp, \sp, #8    @ Adjust SP, since stack op on banked regs is no writeback.
                @ stack state: (Low) |->SP_USR|LR_USR|(Original)| (High)
        .endm

        .macro is_psr_usr rx
                and     \rx, \rx, #ARM_MODE_MASK
                cmp     \rx, #ARM_MODE_USR
        .endm

/* These really both read the same unified FSR and FAR registers */
#if defined (CONFIG_SUBARCH_V5)
        .macro  cp15_read_ifsr rx
                mrc     p15, 0, \rx, c5, c0, 0  @ Read FSR (Tells why the fault occured)

        .endm
        .macro cp15_read_ifar rx
                mrc     p15, 0, \rx, c6, c0, 0  @ Read FAR (Contains the faulted data address)
        .endm
        .macro  cp15_read_dfsr rx
                mrc     p15, 0, \rx, c5, c0, 0  @ Read FSR (Tells why the fault occured)

        .endm
        .macro cp15_read_dfar rx
                mrc     p15, 0, \rx, c6, c0, 0  @ Read FAR (Contains the faulted data address)
        .endm
#endif

/* These read the distinguished IFSR, IFAR, DFSR and DFAR registers */
#if defined (CONFIG_SUBARCH_V6) || defined (CONFIG_SUBARCH_V7)
        .macro  cp15_read_ifsr rx
                mrc     p15, 0, \rx, c5, c0, 1  @ Read IFSR (Tells why the fault occured)

        .endm
        .macro cp15_read_ifar rx
                mrc     p15, 0, \rx, c6, c0, 2  @ Read IFAR (Contains the faulted data address)
        .endm
        .macro  cp15_read_dfsr rx
                mrc     p15, 0, \rx, c5, c0, 0  @ Read DFSR (Tells why the fault occured)

        .endm
        .macro cp15_read_dfar rx
                mrc     p15, 0, \rx, c6, c0, 0  @ Read DFAR (Contains the faulted data address)
        .endm
#endif

#define UNDEF_R0        0
#define UNDEF_SPSR      -4
#define UNDEF_R14       -8

/*
 * vect_undef
 *
 * Upon Entry:
 * - R14:       Address of next instruction after undefined instruction
 * - PC:        0x00000004
 * - IRQs are disabled (CPSR[7] = 1)
 *
 *
 * PURPOSE:
 * A co-processor instruction not supported by the core can be
 * emulated here. Also unrecognised/invalid instructions are handled.
 */
BEGIN_PROC(arm_undef_exception_reentrant)
        clear_exclusive
        str     lr, [sp, #UNDEF_R14]    @ Store undef address
        mrs     lr, spsr                @ Get SPSR
        str     lr, [sp, #UNDEF_SPSR]   @ Store SPSR
        str     r0, [sp, #UNDEF_R0]     @ Store r0
        @ NOTE: Can increase undef nest here.
        mov     r0, sp                  @ Keep current sp point in R0
        mrs     lr, cpsr                @ Change to SVC mode.
        bic     lr, #ARM_MODE_MASK
        orr     lr, lr, #ARM_MODE_SVC
        msr     cpsr_fc, r14
        @ FIXME: Ensure 8-byte stack here.
        str     lr, [sp, #-8]!  @ Save lr_svc 2 words down from interrupted SP_SVC
        @ Transfer Undef state to SVC
        ldr     lr, [r0, #UNDEF_R14]
        str     lr, [sp, #4]
        @ Stack state:  |LR_SVC<-|LR_UNDEF|{original SP_SVC}|
        ldr     lr, [r0, #UNDEF_SPSR]
        ldr     r0, [r0, #UNDEF_R0]
        stmfd   sp!, {r0-r3,r12,lr}
        @ Stack state:  |R0<-|R1|R2|R3|R12|UNDEF_SPSR|LR_SVC|LR_DUNDEF|{original SP_SVC}|
        push_user_sp_lr sp      @ NOTE: These must be pushed to avoid trashing them if preempted
        @ Stack state: |SP_USR<-|LR_USR|R0<-|R1|R2|R3|R12|UNDEF_SPSR|LR_SVC|LR_DUNDEF|{original SP_SVC}|

        @ All undef state saved. Can safely enable irqs here, if need be.
        ldr     r3, [sp, #28]           @ Load UNDEF_SPSR
        can_abort_enable_irqs r0, r3    @ Judge if irqs can be enabled depending on prev state.
        bne     1f                      @ Branch here based on previous irq judgement.
        enable_irqs r3
1:
        /* Now check in what mode exception occured, and return that mode's LR in R4
         * Also poplulate r0,r1,r2 parameters for undefined_instr_handler
        */
        ldr     r1, [sp, #28]           @ Load UNDEF_SPSR
        is_psr_usr r0                   @ Test if UNDEF_SPSR was user mode.
        ldrne   r2, [sp, #32]           @ Abort occured in kernel, load LR_SVC
        ldreq   r2, [sp, #4]            @ Abort occured in user, load LR_USR
        ldr     r0, [sp, #36]           @ Load LR_UNDEF saved previously.
        mov     lr, pc
        ldr     pc, =undefined_instr_handler    @ Jump to function outside this page.
        disable_irqs r0                 @ Disable irqs to avoid corrupting spsr.
                                        @ (i.e. an interrupt could overwrite spsr with current psr)
        ldmfd   sp, {sp, lr}^           @ Restore user sp and lr which might have been corrupt on preemption
        nop                             @ User reg mod requires nop
        add     sp, sp, #8              @ Update SP.
        ldmfd   sp!, {r0-r3,r12,lr}     @ Restore previous context. (note, lr has spsr)
        msr     spsr_cxsf, r14          @ Restore spsr register from lr.
        @ Stack state: |LR_SVC<-|LR_PREV(UNDEF)|{original SP_SVC}|
        ldmfd   sp!, {r14, pc}^         @ Return, restoring cpsr. Note r14 gets r14_svc,
                                        @ and pc gets lr_undef. Saved at #4 and #8 offsets
                                        @ down from where svc stack had left.
END_PROC(arm_undef_exception_reentrant)

/*
 * vect_swi
 *
 * Upon Entry:
 * - R14:       Address of next instruction after the SWI
 * - PC:        0x00000008
 * - R0-R12:    Depending on the system call some of them contain
 *              indicators of what the exception means.
 * - IRQs are disabled (CPSR[7] = 1)
 * - SWI instruction's bits [7:0] may contain SWI indicator
 *
 * PURPOSE:
 * Used for trapping into a debugger or OS kernel via system calls.
 * Argument registers from R0 up to R12 and [7:0] of the causing SWI
 * instruction contains hints of what to do with this exception. What
 * R0-R12 contains depends on what userspace has put in them. Note this
 * is the only exception that userspace can generate and thus has control
 * on what it put into r0-rx.
 *
 * RECAP:
 * Normally across a function call, only r0-r3 are used for passing parameters.
 * Why r0-r3 only but not r4, r5...? See APCS (ARM procedure call standard)
 * Short answer: r4-r12 must be preserved across procedures but r0-r3 can be
 * trashed because they're set aside for argument passing. Arguments more than 4
 * go on the stack. Note APCS is a *suggestion*, rather than enforcement. So if
 * a userspace stub library is created that say, preserves and uses r0-r9 for a
 * system call, and the system call handler (this) knows about it, it is a
 * perfectly valid setup. In fact this is what we do here, we don't strictly use
 * r0-r3. Depending on the system call, the set of input registers (and output
 * registers to return results from the system call) may be redefined. These are
 * documented for each system call in the reference manual.
 * Another caveat to note in SWI usage is that we use the address offset of the
 * SWI instruction to see which offset it has in the system call vector, to
 * determine the correct system call, rather than [7:0] bits of the SWI.
 */
BEGIN_PROC(arm_swi_exception)
        clear_exclusive
        sub     lr, lr, #4      @ Get address of swi instruction user executed.
        stmfd   sp, {r0-r12,sp,lr}^ @ Push arguments, LR_USR and SP_USR to stack.
        nop

        @ Future optimisation 1:
        @ For all syscalls we need not push any more than r8 but we push up to
        @ r12 because upon a fork, a child's easiest way to restore user
        @ registers is to pop it from stack during return_from_syscall. In future
        @ fork function could return back to here, save all context into child
        @ from actual registers instead of reading from stack, and then return.

        @ Future optimisation 2:
        @ SP_USR MUST be pushed here, otherwise a kernel preemption could
        @ cause user mode of another process to overwrite SP_USR. The reason we
        @ save it here is because the preemption path does not currently save it
        @ if it is a kernel preemption. User SP can also be used here, as the
        @ user might have pushed data to its stack to be used by system calls.
        @ But we dont plan to pass data to kernel in this way, so saving of
        @ SP_USR can be done in preemption path as an optimisation.

        /*
         * The LR_usr is important here, because the user application uses a BL
         * to jump to the system call SWI, so the LR_usr contains the return
         * address, i.e. the next instruction after the *jumping* instruction to
         * the system call SWI (not the one after the swi itself, which is in
         * LR_svc).
         */

        sub     sp, sp, #60     @ stmfd on user registers can't writeback the SP. We do it manually.
        mrs     r0, spsr_fc     @ psr also need saving in case this context is interrupted.
        stmfd   sp!, {r0}
        enable_irqs r0
        mov     r0, sp          @ Current SP has pointer to all saved context.
        ktcb_ref_saved_regs r0, r1, r2 @ Save syscall context pointer in ktcb
        mov     r1, lr          @ Pass swi instruction address in LR as arg1
        mov     lr, pc
        ldr     pc, =syscall

.global return_from_syscall;    @ Newly created threads use this path to return,
return_from_syscall:            @ if they duplicated another thread's address space.
        disable_irqs r1         @ Not disabling irqs at this point causes the SP_USR and spsr
                                @ to get corrupt causing havoc.
        ldmfd   sp!, {r1}
        msr     spsr, r1
        add     sp, sp, #4      @ Skip, r0's location, since r0 already has returned result.
                                @ Note we're obliged to preserve at least r3-r8 because they're MRs.
        ldmfd   sp!, {r1-r12}   @ Restore r1-r8 pushed to stack earlier. r0 already has return result.
        ldmfd   sp, {sp}^       @ Restore user stack pointer, which might have been corrupt on preemption
        nop
        add     sp, sp, #4      @ Update sp.
        ldmfd   sp!, {lr}       @ Load userspace return address
        movs    pc, lr
END_PROC(arm_swi_exception)

/* Minimal abort state saved on data abort stack right after abort vector enters: */
#define ABT_R0          0
#define ABT_SPSR        -4
#define ABT_R14         -8

/* Minimal prefetch abort state saved on abort stack upon entry. */
#define ABT_R0          0
#define ABT_SPSR        -4
#define ABT_R14         -8

/*
 * vect_pabt
 *
 * Upon Entry:
 * - R14_abt:   Address of next instruction after aborted instruction
 * - R14_usr:   Address of return instruction in last function call**
 * - PC:        0x0000000c
 * - IRQs are disabled (CPSR[7] = 1)
 *
 *
 * PURPOSE:
 * Used for handling instructions that caused *memory aborts* during
 * the *prefetching* of the instruction. The instruction is also marked
 * as invalid by the core. It handles the cause for the memory abort.
 *
 * (One reason why a memory abort would occur is when we were entering
 * into a new page region that contained executable code and was not
 * present in memory, or its physical-to-virtual translation was not
 * present in the page tables. See other causes for memory aborts)
 *
 * **In case abort occured in userspace. This is useful if the abort
 * was due to a null/invalid function pointer call. Since R14_abt
 * includes the aborting instruction itself, R14_usr gives the clue to
 * where this call came from.
 */
BEGIN_PROC(arm_prefetch_abort_exception_reentrant)
        clear_exclusive
        sub     lr, lr, #4              @ lr-4 points at aborted instruction
        str     lr, [r13, #ABT_R14]     @ Store abort address.
        mrs     lr, spsr                @ Get SPSR
        str     lr, [r13, #ABT_SPSR]    @ Store SPSR
        str     r0, [r13, #ABT_R0]      @ Store R0 to use as temp register.
        mov     r0, r13                 @ SP to R0
        mrs     lr, cpsr                @ Change to SVC mode.
        bic     lr, #ARM_MODE_MASK
        orr     lr, lr, #ARM_MODE_SVC
        msr     cpsr_fc, r14
        @ FIXME: Ensure 8-byte stack here.
        str     lr, [sp, #-8]!  @ NOTE: Switched mode! Save LR_SVC 2 words down from SP_SVC.
transfer_pabt_state_to_svc:     @ Move data saved on PABT stack to SVC stack.
        ldr     lr, [r0, #ABT_R14]
        str     lr, [sp, #4]
        @ Stack state: |LR_SVC<-|LR_PABT|{original SP_SVC}|
        ldr     lr, [r0, #ABT_SPSR]
        ldr     r0, [r0, #ABT_R0]
        stmfd   sp!, {r0-r3,r12,lr}
        @ Stack state:  |R0<-|R1|R2|R3|R12|PABT_SPSR|LR_SVC|LR_PABT|{original SP_SVC}|
        push_user_sp_lr sp      @ NOTE: These must be pushed to avoid trashing if preempted
        @ Stack state:  |SP_USR<-|LR_USR|R0|R1|R2|R3|R12|PABT_SPSR|LR_SVC|LR_PABT|{original SP_SVC}|
read_pabt_state:
        cp15_read_ifsr r1       @ Reads FSR on ARMv5, IFSR on ARMv6-v7. Fault status information
        cp15_read_ifar r2       @ Reads FAR on ARMv5, IFAR on ARMv6-v7. Fault address information
        @ All abort state and (FAR/FSR) saved. Can safely enable irqs here, if need be.
        ldr     r3, [sp, #28]           @ Load PABT_SPSR
        can_abort_enable_irqs r0, r3    @ Judge if irqs can be enabled depending on prev state.
        bne     1f                      @ Branch here based on previous irq judgement.
        enable_irqs r3
1:
        ldr     r3, [sp, #28]           @ Load PABT_SPSR to r3, the spsr for the aborted mode
        ldr     r0, [sp, #36]           @ Load LR_PABT - 4 saved previously. (Address that aborted)
        mov     lr, pc
        ldr     pc, =prefetch_abort_handler @ Jump to function outside this page.
        disable_irqs r0                 @ Disable irqs to avoid corrupting spsr.
                                        @ (i.e. an interrupt could overwrite spsr with current psr)
        ldmfd   sp, {sp, lr}^           @ Restore user sp and lr which might have been corrupt on preemption
        nop                             @ User reg mod requires nop
        add     sp, sp, #8              @ Update SP.
        ldmfd   sp!, {r0-r3,r12,lr}     @ Restore previous context. (note, lr has spsr)
        msr     spsr_cxsf, r14          @ Restore spsr register from lr.
        @ Stack state: |LR_SVC<-|LR_PREV(PABT)|{original SP_SVC}|
        ldmfd   r13!, {r14, pc}^        @ Return, restoring cpsr. Note r14 gets r14_svc,
                                        @ and pc gets lr_dabt. Saved at #4 and #8 offsets
                                        @ down from where svc stack had left.
END_PROC(arm_prefetch_abort_exception_reentrant)

/*
 * vect_dabt
 *
 * Upon Entry:
 * - R14_abt:   Address of next instruction after aborted instruction
 * - PC:        0x00000010
 * - IRQs are disabled (CPSR[7] = 1)
 *
 *
 * PURPOSE:
 * Used for handling instructions that caused *memory aborts* during
 * the *execution* of the current instruction. This may happen if the
 * instruction accessed a memory address (e.g LDR/STR) that is not
 * defined as part of the currently executing process (aka illegal
 * access). Another possibility is the address is within the address
 * space of the process, but it is not mapped, i.e. does not have
 * physical-to-virtual translation entry in the page tables.
 */
BEGIN_PROC(arm_data_abort_exception)
        sub     lr, lr, #8      @ lr-8 points at aborted instruction
        mrc     p15, 0, r2, c5, c0, 0 @ Read FSR
        mrc     p15, 0, r1, c6, c0, 0 @ Read FAR
        mov     r0, lr          @ Get data abort address
        mov     r5, lr          @ Save it in r5 in case r0 will get trashed
        mov     lr, pc          @ Save return address
        ldr     pc, =data_abort_handler @ Jump to function outside this page.
1:
        b       1b
END_PROC(arm_data_abort_exception)

/*
 * The method of saving abort state to svc stack is identical with that of
 * reentrant irq vector. Natural to this, Restoring of the previous state
 * is also identical.
 */
BEGIN_PROC(arm_data_abort_exception_reentrant)
        clear_exclusive
        sub     lr, lr, #8              @ Get abort address
        str     lr, [r13, #ABT_R14]     @ Store abort address
        mrs     lr, spsr                @ Get SPSR
        str     lr, [r13, #ABT_SPSR]    @ Store SPSR
        str     r0, [r13, #ABT_R0]      @ Store r0
        @ NOTE: Can increase data abort nest here.
        mov     r0, r13                 @ Keep current sp point in R0
        mrs     lr, cpsr                @ Change to SVC mode.
        bic     lr, #ARM_MODE_MASK
        orr     lr, lr, #ARM_MODE_SVC
        msr     cpsr_fc, r14
        @ FIXME: Ensure 8-byte stack here.
        str     lr, [sp, #-8]!  @ Save lr_svc 2 words down from interrupted SP_SVC
transfer_dabt_state_to_svc:
        ldr     lr, [r0, #ABT_R14]
        str     lr, [sp, #4]
        @ Stack state:  |LR_SVC<-|LR_DABT|{original SP_SVC}|
        ldr     lr, [r0, #ABT_SPSR]
        ldr     r0, [r0, #ABT_R0]
        stmfd   sp!, {r0-r3,r12,lr}
        @ Stack state:  |R0<-|R1|R2|R3|R12|DABT_SPSR|LR_SVC|LR_DABT|{original SP_SVC}|
        push_user_sp_lr sp
        @ Stack state:  |SP_USR<-|LR_USR|R0|R1|R2|R3|R12|DABT_SPSR|LR_SVC|LR_DABT|{original SP_SVC}|
read_dabt_state:
        cp15_read_dfsr r1       @ Read DFSR (Tells why the fault occured)
        cp15_read_dfar r2       @ Read DFAR (Contains the faulted data address)
        @ All abort state and (FAR/FSR) saved. Can safely enable irqs here, if need be.
        ldr     r3, [sp, #28]           @ Load DABT_SPSR
        can_abort_enable_irqs r0, r3    @ Judge if irqs can be enabled depending on prev state.
        bne     1f                      @ Branch here based on previous irq judgement.
        enable_irqs r3
1:
        ldr     r0, [sp, #36]           @ Load LR_DABT saved previously.
        mov     lr, pc
        ldr     pc, =data_abort_handler @ Jump to function outside this page.
        disable_irqs r0                 @ Disable irqs to avoid corrupting spsr.
        ldmfd   sp, {sp, lr}^           @ Restore user sp and lr which might have been corrupt on preemption
        nop                             @ User reg mod requires nop
        add     sp, sp, #8              @ Update SP.
        ldmfd   sp!, {r0-r3,r12,lr}     @ Restore previous context. (note, lr has spsr)
        msr     spsr_cxsf, r14          @ Restore spsr register from lr.
        @ Stack state: |LR_SVC<-|LR_PREV(DABT)|{original SP_SVC}|
        ldmfd   r13!, {r14, pc}^        @ Return, restoring cpsr. Note r14 gets r14_svc,
                                        @ and pc gets lr_dabt. Saved at #4 and #8 offsets
                                        @ down from where svc stack had left.
END_PROC(arm_data_abort_exception_reentrant)

/*
 * vect_irq
 *
 * Upon Entry:
 * - R14:       Address of next instruction after interrupted instruction.
 * - PC:        0x00000018
 * - IRQs are disabled (CPSR[7] = 1)
 * - A vectored interrupt controller would also provide where to jump in
 *   order to handle the interrupt, or an irq controller in general would
 *   provide registers that indicate what kind of interrupt has occured.
 *
 *
 * PURPOSE:
 * Used for handling IRQs. IRQs have lower priority compared to other
 * types of exceptions.
 */

/* The most basic handler where neither context switching nor re-entry can occur. */
BEGIN_PROC(arm_irq_exception_basic)
        sub     lr, lr, #4
        stmfd   sp!, {r0-r3,lr}
        mov     lr, pc
        ldr     pc, =do_irq
        ldmfd   sp!, {r0-r3, pc}^
END_PROC(arm_irq_exception)

/* Minimal IRQ state saved on irq stack right after irq vector enters: */
#define IRQ_R0          0
#define IRQ_SPSR        -4
#define IRQ_R14         -8

/* A reentrant handler that uses svc mode stack to prevent banked lr_irq corruption. */
BEGIN_PROC(arm_irq_exception_reentrant)
        sub     lr, lr, #4
@ Save minimal state to irq stack:
        str     r14, [r13, #IRQ_R14]    @ Save lr_irq
        mrs     r14, spsr               @ Copy spsr
        str     r14, [r13, #IRQ_SPSR]   @ Save spsr on irq stack
        str     r0, [r13, #IRQ_R0]      @ Save r0.
        mov     r0, r13                 @ Using r0 to keep banked sp_irq when mode is switched.
        mrs     r14, cpsr               @ Get current psr (irq)
        bic     r14, #ARM_MODE_MASK     @ Clear mode part from psr
        orr     r14, r14, #ARM_MODE_SVC @ Write SVC mode bits.
        msr     cpsr_fc, r14            @ Change to SVC mode.
        str     r14, [r13, #-8]!        @ Save lr_svc 2 words down from where svc stack left.
@ Transfer minimal irq state saved to svc stack:
        ldr     r14, [r0, #IRQ_R14]     @ Load lr_irq to lr using r0 that contains sp_irq.
        str     r14, [r13, #4]          @ Save lr_irq 1 word down from where svc stack left.
        ldr     r14, [r0, #IRQ_SPSR]    @ Load irq spsr.
        ldr     r0, [r0, #IRQ_R0]       @ Restore r0.
        stmfd   sp!, {r0-r3,r12,lr}     @ Save all of rest of irq context to svc stack.
        bl      do_irq                  @ Read irq number etc. Free to re-enable irqs here.
        ldmfd   sp!, {r0-r3-r12,lr}     @ Restore previous context. (note, lr has spsr)
        msr     spsr_cxsf, lr           @ Restore spsr register from lr.
        ldmfd   r13!, {r14, pc}^        @ Return, restoring cpsr. Note r14 gets r14_svc,
                                        @ and pc gets lr_irq. Saved at #4 and #8 offsets
                                        @ down from where svc stack had left.
END_PROC(arm_irq_exception_reentrant)

        .macro  need_resched rx, ry
        get_current \rx
        ldr     \ry, =need_resched_offset
        ldr     \ry, [\ry]
        ldr     \ry, [\rx, \ry]
        cmp     \ry, #1
        .endm

/*
 * Keeps the PSR of the last pre-empted process. This helps to tell
 * what mode the process was in when it was preempted.
 */
.global preempted_psr;
preempted_psr:
.word   0
.word   0
.word   0
.word   0

/* Keeps track of how many nests of irqs have happened. */
.global current_irq_nest_count;
current_irq_nest_count:
.word   0
.word   0
.word   0
.word   0

#if defined (CONFIG_SMP)
        @ Rx contains the address of per cpu variable
        .macro per_cpu adr, temp, varname
                get_cpuid \temp
                ldr \adr, =\varname
                add \adr, \adr, \temp, lsl #2
        .endm
#else
        .macro per_cpu adr, temp, varname
                ldr \adr, =\varname
        .endm
#endif

/*
 * FIXME: current_irq_nest_count also counts for any preempt_disable() calls.
 * However this nesting check assumes all nests come from real irqs.
 * We should make this check just the real ones.
 */
#define IRQ_NESTING_MAX                 32
        .macro  inc_irq_cnt_with_overnest_check rx, ry
        per_cpu \rx, \ry, current_irq_nest_count @ Get per-cpu address of variable
        ldr     \ry, [\rx]
        add     \ry, \ry, #1                    @ No need for atomic inc since irqs are disabled.
        str     \ry, [\rx]
        cmp     \ry, #IRQ_NESTING_MAX           @ Check no more than max nests, and die miserably if so.
        ldrge   pc, =irq_overnest_error
        .endm

        @ This decrement need not be atomic because if you are *decrementing* this, then it means
        @ Preemption is already *disabled*. Ruling out preemption, only race could be against irqs.
        @ If an irq preempts it during decrement and modifies it, it is still responsible to change
        @ it back to the original value as it was when we read it, before it returns. So effectively
        @ anything that runs during the decrement does not affect the value of the count.
        .macro  dec_irq_nest_cnt rx, ry
        per_cpu \ry, \rx, current_irq_nest_count
        ldr     \rx, [\ry]
        sub     \rx, \rx, #1
        str     \rx, [\ry]
        .endm
        .macro in_process_context rx, ry
        per_cpu \rx, \ry, current_irq_nest_count
        ldr     \rx, [\rx]
        cmp     \rx, #0
        .endm
        /* If interrupted a process (as opposed to another irq), saves spsr value to preempted_psr */
        .macro cmp_and_save_process_psr rx, ry
        in_process_context \rx, \ry             @ If nest count is 0, a running process is preempted.
        bne 9999f                               @ Branch ahead if not a process
        per_cpu \rx, \ry, preempted_psr         @ Get per-cpu preempted psr
        mrs     \ry, SPSR                       @ Re-read spsr since register was trashed
        str     \ry, [\rx]                      @ Store it in per-cpu preempted psr
        9999:
        .endm

        /*
         * Clear irq bits on register.
         *
         * If ARMv5, only I-bit is cleared, but if ARMv6-v7,
         * A-bit is also cleared.
         */
        .macro clr_irq_bits_on_reg rx
                bic     \rx, #ARM_IRQ_BIT
#if defined (CONFIG_SUBARCH_V6) || defined (CONFIG_SUBARCH_V7)
                bic     \rx, #ARM_A_BIT
#endif
        .endm

#define CONTEXT_PSR             0
#define CONTEXT_R0              4
#define CONTEXT_R1              8
#define CONTEXT_R2              12
#define CONTEXT_R3              16
#define CONTEXT_R4              20
#define CONTEXT_R5              24
#define CONTEXT_R6              28
#define CONTEXT_R7              32
#define CONTEXT_R8              36
#define CONTEXT_r9              40
#define CONTEXT_R10             44
#define CONTEXT_R11             48
#define CONTEXT_R12             52
#define CONTEXT_R13             56
#define CONTEXT_R14             60
#define CONTEXT_PC              64

/*
 * TODO: Optimization:
 * May use SRS/RFE on irq exception _only_. But not
 * yet aware of its implications. Only irq handler can
 * do it because RFE enables interrupts unconditionally.
 */
BEGIN_PROC(arm_irq_exception_reentrant_with_schedule)
        clear_exclusive
        sub     lr, lr, #4
        str     lr, [r13, #IRQ_R14]     @ Save lr_irq
        mrs     r14, spsr               @ Copy spsr
        str     r14, [r13, #IRQ_SPSR]   @ Save spsr on irq stack
        str     r0, [r13, #IRQ_R0]      @ Save r0.
        cmp_and_save_process_psr r0, r14 @ R14 should have spsr here.
        inc_irq_cnt_with_overnest_check r0, r14
        mov     r0, r13                 @ Using r0 to keep banked sp_irq when mode is switched.
        mrs     r14, cpsr               @ Get current psr (irq)
        bic     r14, #ARM_MODE_MASK     @ Clear mode part from psr
        orr     r14, r14, #ARM_MODE_SVC @ Write SVC mode bits.
        msr     cpsr_fc, r14            @ Change to SVC mode.
        @ FIXME: Ensure 8-byte aligned stack here! Make sure to restore original state later!
        str     r14, [r13, #-8]!        @ Save lr_svc 2 words down from where svc stack left. SP updated.
@ Transfer minimal irq state to svc stack:
        ldr     r14, [r0, #IRQ_R14]     @ Load lr_irq to lr using r0 that contains sp_irq.
        str     r14, [r13, #4]          @ Save lr_irq 1 word down from where svc stack left.
        ldr     r14, [r0, #IRQ_SPSR]    @ Load irq spsr.
        ldr     r0, [r0, #IRQ_R0]       @ Restore r0.
        stmfd   sp!, {r0-r3,r12,lr}     @ Save all of rest of irq context to svc stack.
        mov     lr, pc
        ldr     pc, =do_irq             @ Read irq number etc. Free to re-enable irqs here.
        @ stack state: (Low) r0|r1|r2|r3|r12|SPSR|LR_SVC|LR_IRQ| (High)

/*
 * Decision point for taking the preemption path
 */
#if !defined(CONFIG_PREEMPT_DISABLE)
        per_cpu r0, r1, current_irq_nest_count
        ldr     r0, [r0]
        cmp     r0, #1                  @ Expect 1 as lowest since each irq increase preempt cnt by 1.
        bgt     return_to_prev_context  @ if (irq_nest > 1) return_to_prev_context();
        need_resched r0, r1             @ if (irq_nest == 1 && need_resched) schedule();
        beq     preemption_path         @ if (irq_nest == 1 && !need_resched) return_to_prev_context();
#endif

/*
 * Return to previous context path
 */
return_to_prev_context:
        dec_irq_nest_cnt r0, r1
        disable_irqs r0                 @ Disable irqs to avoid corrupting spsr.
        ldmfd   sp!, {r0-r3,r12,lr}     @ Restore previous context. (note, lr has spsr)
        msr     spsr_cxsf, r14          @ Restore spsr register from lr.
        @ stack state: (Low) |LR_SVC<-|LR_PREV(IRQ)|{original SP_SVC}| (High)
        ldmfd   r13!, {r14, pc}^        @ Return, restoring cpsr. Note r14 gets r14_svc,
                                        @ and pc gets lr_irq. Saved at #4 and #8 offsets
                                        @ down from where svc stack had left.

/*
 * Preemption path
 */
#if !defined(CONFIG_PREEMPT_DISABLE)
preemption_path:
        disable_irqs r0                 @ Interrupts can corrupt stack state.
        get_current r0                  @ Get the interrupted process
        @ stack state: (Low) |->r0|r1|r2|r3|r12|SPSR|LR_SVC|LR_IRQ(<return_address>)| (High)
save_interrupted_context:
        add     sp, sp, #4
        @ stack state: (Low) |r0|->r1|r2|r3|r12|SPSR|LR_SVC|LR_IRQ(<return_address>)| (High)
        ldmfd   sp!, {r1-r3, r12, lr}
        @ stack state: (Low) |r0|..|..|..|..|..|->LR_SVC|LR_IRQ(<return_address>)| (High)
        str     lr, [r0, #CONTEXT_PSR]
        is_psr_usr lr
        add     r0, r0, #CONTEXT_R1     @ Points at register save location for #CONTEXT_R1
        stmia   r0!, {r1-r12}
        ldmfd   sp!, {r1-r2}            @ At this point SP_SVC is at its original svc location.
        @ stack state: (Low) |r0|..|..|..|..|..|..|..|->(Original)| (High)
        @ register state: r0 = (register save loc for #CONTEXT_R13) r1 = LR_SVC, r2 = LR_IRQ
        beq     save_usr_context
save_svc_context:
        stmib   r0, {r1-r2}             @ Save LR_SVC and LR_RETURN in advancing locations.
        str     sp, [r0]                @ Current sp is where sp_svc has left, and r0 at #CONTEXT_SP loc.
        sub     r0, r0, #CONTEXT_R13    @ Go back to first word from SP position.
        ldr     r1, [sp, #-32]          @ Load r0 from stack
        str     r1, [r0, #CONTEXT_R0]   @ Save r0
        b       prepare_schedule        @ All registers saved.
save_usr_context:
        sub     r0, r0, #CONTEXT_R13
        str     r2, [r0, #CONTEXT_PC]   @ Save Program counter
        @ LR_SVC need restoring because it won't be pushed to context frame. SP_SVC is already up-to-date.
        mov     lr, r1
        stmfd   sp, {sp, lr}^   @ Push USR banked regs to stack.
        @ stack state: (Low) |r0|..|..|..|..|..|SP_USR|LR_USR|->(Original)| (High)
        nop                     @ Need a NOP after twiddling with usr registers.
        sub     sp, sp, #8      @ Adjust SP, since stack op on banked regs is no writeback.
        @ stack state: (Low) |r0|..|..|..|..|..|->SP_USR|LR_USR|(Original)| (High)
        ldmfd   sp!, {r1-r2}    @ Pop USR Banked regs.
        @ stack state: (Low) |r0|..|..|..|..|..|..|..|->(Original)| (High)
        str     r1, [r0, #CONTEXT_R13]  @ Save SP_USR to context frame.
        str     r2, [r0, #CONTEXT_R14]  @ Save LR_USR to context frame.
        ldr     r1, [sp, #-32]
        str     r1, [r0, #CONTEXT_R0]
        @ stack state: (Low) |..|..|..|..|..|..|..|..|->(Original)| (High)
prepare_schedule:
        mov lr, pc
        ldr pc, =schedule
1:
        b       1b      /* To catch if schedule returns in irq mode */
#endif /* End of !CONFIG_PREEMPT_DISABLE */

END_PROC(arm_irq_exception_reentrant_with_schedule)

/*
 * Context switch implementation.
 *
 * Upon entry:
 *
 * - r0 = current ktcb ptr, r1 = next ktcb ptr. r2 and r3 = insignificant.
 * - The current mode is always SVC, but the call may be coming from interrupt
 *   or process context.
 * - If coming from interrupt, the interrupted context is already copied to current
 *   ktcb in the irq handler, before coming here. Interrupted context can be SVC or USR.
 *
 * PURPOSE: Handles all paths from irq exception, thread_switch system call,
 * and sleeping in the kernel.
 *
 * NOTES:
 * - If coming from interrupt, the interrupted context is already copied to current
 *   ktcb in the irq handler, before coming here. Interrupted context can be SVC or USR.
 * - If coming from a process context, the current process context need saving here.
 * - From irq contexts, preemption is disabled, i.e. preemption count is 1. This is because
 *   irqs naturally increase preemption count. From process context preemption count is 0.
 *   Process context disables preemption during schedule(), but re-enables before calling
 *   switch_to(). Irq and process contexts are distinguished by preemption_count.
 *   Furthermore, irqs are also disabled shortly before calling switch_to() from both contexts.
 *   This happens at points where stack state would be irrecoverable if an irq occured.
 */
BEGIN_PROC(arch_context_switch)
        clear_exclusive
        in_process_context r2, r3       @ Note this depends on preempt count being 0.
        beq     save_process_context    @ Voluntary switch needs explicit saving of current state.
        dec_irq_nest_cnt r2, r3         @ Soon leaving irq context, so reduce preempt count here.
        b       load_next_context       @ Interrupted context already saved by irq handler.
save_process_context:           @ Voluntary process schedules enter here:
        mrs     r2, cpsr_fc
        str     r2, [r0]
        stmib   r0, {r0-r14}    @ Voluntary scheduling always in SVC mode, so using svc regs.
        str     r14, [r0, #CONTEXT_PC]  @ Store R15 as R14. R14 has return address for switch_to().
load_next_context:
        @ stack state: (Low) |..|..|..|..|..|..|..|..|..|->(Original)| (High)
        mov     sp, r1
        ldr     r0, [sp, #CONTEXT_PSR]  @ Load r0 with SPSR
        clr_irq_bits_on_reg r0          @ Enable irqs on will-be-restored context.
        msr     spsr_fcxs, r0           @ Restore spsr from r0.
        is_psr_usr r0
        bne load_next_context_svc       @ Loading user context is different than svc.
load_next_context_usr:
        ldmib   sp, {r0-r14}^           @ Load all including banked user regs.
        ldr     lr, [sp, #CONTEXT_PC]   @ Load value of PC to r14
        orr     sp, sp, #0xFF0
        orr     sp, sp, #0x8            @ 8-byte aligned.
        movs    pc, lr                  @ Jump to user changing modes.
load_next_context_svc:
        ldmib   sp, {r0-r15}^           @ Switch to svc context and jump, loading R13 and R14 from stack.
                                        @ This is OK since the jump is to current context.
END_PROC(arch_context_switch)


/*
 * vect_fiq
 *
 * Upon Entry:
 * - R14:       Address of next instruction after interrupted instruction.
 * - PC:        0x00000014
 * - FIQs are disabled (CPSR[6] = 1)
 * - IRQs are disabled (CPSR[7] = 1)
 * - As in IRQ, the irq controller would provide registers that indicate
 *   what kind of interrupt has occured.
 *
 * PURPOSE:
 * Handling of high-priority interrupts. FIQs have highest priority after
 * reset and data abort exceptions. They're mainly used for achieving
 * low-latency interrupts, e.g. for DMA.
 */
BEGIN_PROC(arm_fiq_exception)
END_PROC(arm_fiq_exception)

/* * * * * * * * * * * * * * * * * * * * * * * *
 * External functions with absolute addresses  *
 * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * NOTE: Notes on relative and absolute symbols on this file:
 *
 * Note that branches (B and BL) are *RELATIVE* on ARM. So no need to take any
 * special action to access symbols within this file, even though this page
 * (in virtual memory) is relocated to another address at run-time (high or low
 * vectors) - this is an address other than where it is linked at, at
 * compile-time.
 *
 * To access external symbols from this file, (e.g. calling some function in the
 * kernel) one needs to use the: `LDR, pc, =external_symbol' pseudo-instruction,
 * (note the "=") and use absolute addressing. This automatically generates an
 * inline data word within the current module and indirectly loads the value in
 * that word to resolve the undefined reference. All other methods, (LDR, B
 * instructions, or ADR pseudoinstruction) generate relative addresses, and they
 * will complain for external symbols because a relative offset cannot be
 * calculated for an unknown distance. In conclusion, relative branches are
 * useful for accessing symbols on this page, but they mean nothing outside this
 * page, because the page is relocated at run-time. So, wherever you access
 * *relatively* outside this page, would be *relative* to where this page is at
 * that moment.
 */

/* * * * * * * * * * * * * * * * *
 * Stacks for Exception Vectors  *
 * * * * * * * * * * * * * * * * */
.global __stacks_end;
.global __abt_stack_high;
.global __irq_stack_high;
.global __fiq_stack_high;
.global __und_stack_high;

/*
 * These are also linked at high vectors, just as any other symbol
 * on this page.
 */
.balign 4
.equ __abt_stack_high, (__abt_stack - __vector_vaddr + 0xFFFF0000);
.equ __irq_stack_high, (__irq_stack - __vector_vaddr + 0xFFFF0000);
.equ __fiq_stack_high, (__fiq_stack - __vector_vaddr + 0xFFFF0000);
.equ __und_stack_high, (__und_stack - __vector_vaddr + 0xFFFF0000);

/*
 * NOTE: This could be cache line aligned.
 * (use a macro, e.g. ____arm_asm_cache_aligned)
 */
.balign 4

/* 16 bytes each per-cpu, up to 8 cpus */
__stacks_end:   .space 128
__abt_stack:    .space 128
__irq_stack:    .space 128
__fiq_stack:    .space 128
__und_stack:    .space 128


Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.