371 lines
8.2 KiB
ArmAsm
371 lines
8.2 KiB
ArmAsm
/*
|
|
* Copyright (c) 2014 Wind River Systems, Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief Handling of transitions to-and-from fast IRQs (FIRQ)
|
|
*
|
|
* This module implements the code for handling entry to and exit from Fast IRQs.
|
|
*
|
|
* See isr_wrapper.S for details.
|
|
*/
|
|
|
|
#include <kernel_structs.h>
|
|
#include <offsets_short.h>
|
|
#include <toolchain.h>
|
|
#include <arch/cpu.h>
|
|
#include <swap_macros.h>
|
|
|
|
GTEXT(_firq_enter)
|
|
GTEXT(_firq_exit)
|
|
GTEXT(_firq_stack_setup)
|
|
GTEXT(_firq_stack_suspend)
|
|
GTEXT(_firq_stack_resume)
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
GDATA(_firq_stack)
|
|
GDATA(_saved_firq_stack)
|
|
|
|
SECTION_VAR(NOINIT, _firq_stack)
|
|
.space CONFIG_FIRQ_STACK_SIZE
|
|
#else
|
|
GDATA(saved_r0)
|
|
#endif
|
|
|
|
.macro _firq_return
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
b _firq_no_reschedule
|
|
#else
|
|
rtie
|
|
#endif
|
|
.endm
|
|
|
|
/**
|
|
*
|
|
* @brief Work to be done before handing control to a FIRQ ISR
|
|
*
|
|
* The processor switches to a second register bank so registers from the
|
|
* current bank do not have to be preserved yet. The only issue is the LP_START/
|
|
* LP_COUNT/LP_END registers, which are not banked. These can be saved
|
|
* in available callee saved registers.
|
|
*
|
|
* If all FIRQ ISRs are programmed such that there are no use of the LP
|
|
* registers (ie. no LPcc instruction), and CONFIG_ARC_STACK_CHECKING is
|
|
* not set, then the kernel can be configured to not save and restore them.
|
|
*
|
|
* When entering a FIRQ, interrupts might as well be locked: the processor is
|
|
* running at its highest priority, and cannot be interrupted by any other
|
|
* interrupt. An exception, however, can be taken.
|
|
*
|
|
* Assumption by _isr_demux: r3 is untouched by _firq_enter.
|
|
*
|
|
* @return N/A
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _firq_enter)
|
|
|
|
/*
|
|
* ATTENTION:
|
|
* If CONFIG_RGF_NUM_BANKS>1, firq uses a 2nd register bank so GPRs do
|
|
* not need to be saved.
|
|
* If CONFIG_RGF_NUM_BANKS==1, firq must use the stack to save registers.
|
|
* This has already been done by _isr_wrapper.
|
|
*/
|
|
|
|
#ifdef CONFIG_ARC_STACK_CHECKING
|
|
/* disable stack checking */
|
|
lr r2, [_ARC_V2_STATUS32]
|
|
bclr r2, r2, _ARC_V2_STATUS32_SC_BIT
|
|
kflag r2
|
|
#endif
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
#ifndef CONFIG_FIRQ_NO_LPCC
|
|
/*
|
|
* Save LP_START/LP_COUNT/LP_END because called handler might use.
|
|
* Save these in callee saved registers to avoid using memory.
|
|
* These will be saved by the compiler if it needs to spill them.
|
|
*/
|
|
mov r23,lp_count
|
|
lr r24, [_ARC_V2_LP_START]
|
|
lr r25, [_ARC_V2_LP_END]
|
|
#endif
|
|
#endif
|
|
|
|
j @_isr_demux
|
|
|
|
/**
|
|
*
|
|
* @brief Work to be done exiting a FIRQ
|
|
*
|
|
* @return N/A
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _firq_exit)
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
#ifndef CONFIG_FIRQ_NO_LPCC
|
|
/* restore lp_count, lp_start, lp_end from r23-r25 */
|
|
mov lp_count,r23
|
|
sr r24, [_ARC_V2_LP_START]
|
|
sr r25, [_ARC_V2_LP_END]
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_PREEMPT_ENABLED
|
|
mov_s r1, _kernel
|
|
ld_s r2, [r1, _kernel_offset_to_current]
|
|
|
|
#if CONFIG_NUM_IRQ_PRIO_LEVELS > 1
|
|
/* check if we're a nested interrupt: if so, let the interrupted
|
|
* interrupt handle the reschedule */
|
|
|
|
lr r3, [_ARC_V2_AUX_IRQ_ACT]
|
|
|
|
/* the OS on ARCv2 always runs in kernel mode, so assume bit31 [U] in
|
|
* AUX_IRQ_ACT is always 0: if the contents of AUX_IRQ_ACT is not 1, it
|
|
* means that another bit is set so an interrupt was interrupted.
|
|
*/
|
|
|
|
breq r3, 1, _firq_check_for_swap
|
|
|
|
_firq_return
|
|
#endif
|
|
|
|
.balign 4
|
|
_firq_check_for_swap:
|
|
/*
|
|
* Non-preemptible thread ? Do not schedule (see explanation of
|
|
* preempt field in kernel_struct.h).
|
|
*/
|
|
ldh_s r0, [r2, _thread_offset_to_preempt]
|
|
brhs r0, _NON_PREEMPT_THRESHOLD, _firq_no_reschedule
|
|
|
|
/* Check if the current thread (in r2) is the cached thread */
|
|
ld_s r0, [r1, _kernel_offset_to_ready_q_cache]
|
|
brne r0, r2, _firq_reschedule
|
|
|
|
/* fall to no rescheduling */
|
|
|
|
#endif /* CONFIG_PREEMPT_ENABLED */
|
|
|
|
.balign 4
|
|
_firq_no_reschedule:
|
|
/*
|
|
* Keeping this code block close to those that use it allows using brxx
|
|
* instruction instead of a pair of cmp and bxx
|
|
*/
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
add sp,sp,4 /* don't need r0 from stack */
|
|
pop_s r1
|
|
pop_s r2
|
|
pop_s r3
|
|
pop r4
|
|
pop r5
|
|
pop r6
|
|
pop r7
|
|
pop r8
|
|
pop r9
|
|
pop r10
|
|
pop r11
|
|
pop_s r12
|
|
pop_s r13
|
|
pop_s blink
|
|
pop_s r0
|
|
sr r0, [_ARC_V2_LP_END]
|
|
pop_s r0
|
|
sr r0, [_ARC_V2_LP_START]
|
|
pop_s r0
|
|
mov lp_count,r0
|
|
ld r0,[saved_r0]
|
|
add sp,sp,8 /* don't need ilink & status32_po from stack */
|
|
#endif
|
|
rtie
|
|
|
|
#ifdef CONFIG_PREEMPT_ENABLED
|
|
|
|
.balign 4
|
|
_firq_reschedule:
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
/*
|
|
* We know there is no interrupted interrupt of lower priority at this
|
|
* point, so when switching back to register bank 0, it will contain the
|
|
* registers from the interrupted thread.
|
|
*/
|
|
|
|
/* chose register bank #0 */
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
kflag r0
|
|
|
|
/* we're back on the outgoing thread's stack */
|
|
_create_irq_stack_frame
|
|
|
|
/*
|
|
* In a FIRQ, STATUS32 of the outgoing thread is in STATUS32_P0 and the
|
|
* PC in ILINK: save them in status32/pc respectively.
|
|
*/
|
|
|
|
lr r0, [_ARC_V2_STATUS32_P0]
|
|
st_s r0, [sp, ___isf_t_status32_OFFSET]
|
|
|
|
st ilink, [sp, ___isf_t_pc_OFFSET] /* ilink into pc */
|
|
#endif
|
|
|
|
mov_s r1, _kernel
|
|
ld_s r2, [r1, _kernel_offset_to_current]
|
|
|
|
_save_callee_saved_regs
|
|
|
|
st _CAUSE_FIRQ, [r2, _thread_offset_to_relinquish_cause]
|
|
|
|
ld_s r2, [r1, _kernel_offset_to_ready_q_cache]
|
|
st_s r2, [r1, _kernel_offset_to_current]
|
|
|
|
#ifdef CONFIG_ARC_STACK_CHECKING
|
|
/* Use stack top and down registers from restored context */
|
|
add r3, r2, _K_THREAD_NO_FLOAT_SIZEOF
|
|
sr r3, [_ARC_V2_KSTACK_TOP]
|
|
ld_s r3, [r2, _thread_offset_to_stack_top]
|
|
sr r3, [_ARC_V2_KSTACK_BASE]
|
|
#endif
|
|
/*
|
|
* _load_callee_saved_regs expects incoming thread in r2.
|
|
* _load_callee_saved_regs restores the stack pointer.
|
|
*/
|
|
_load_callee_saved_regs
|
|
|
|
ld_s r3, [r2, _thread_offset_to_relinquish_cause]
|
|
|
|
breq r3, _CAUSE_RIRQ, _firq_return_from_rirq
|
|
nop
|
|
breq r3, _CAUSE_FIRQ, _firq_return_from_firq
|
|
nop
|
|
|
|
/* fall through */
|
|
|
|
.balign 4
|
|
_firq_return_from_coop:
|
|
|
|
ld_s r3, [r2, _thread_offset_to_intlock_key]
|
|
st 0, [r2, _thread_offset_to_intlock_key]
|
|
|
|
/* pc into ilink */
|
|
pop_s r0
|
|
mov ilink, r0
|
|
|
|
pop_s r0 /* status32 into r0 */
|
|
/*
|
|
* There are only two interrupt lock states: locked and unlocked. When
|
|
* entering _Swap(), they are always locked, so the IE bit is unset in
|
|
* status32. If the incoming thread had them locked recursively, it
|
|
* means that the IE bit should stay unset. The only time the bit
|
|
* has to change is if they were not locked recursively.
|
|
*/
|
|
and.f r3, r3, (1 << 4)
|
|
or.nz r0, r0, _ARC_V2_STATUS32_IE
|
|
sr r0, [_ARC_V2_STATUS32_P0]
|
|
|
|
ld_s r0, [r2, _thread_offset_to_return_value]
|
|
rtie
|
|
|
|
.balign 4
|
|
_firq_return_from_rirq:
|
|
_firq_return_from_firq:
|
|
|
|
_pop_irq_stack_frame
|
|
|
|
ld ilink, [sp, -4] /* status32 into ilink */
|
|
sr ilink, [_ARC_V2_STATUS32_P0]
|
|
ld ilink, [sp, -8] /* pc into ilink */
|
|
|
|
/* LP registers are already restored, just switch back to bank 0 */
|
|
rtie
|
|
|
|
#endif /* CONFIG_PREEMPT_ENABLED */
|
|
|
|
/**
|
|
*
|
|
* @brief Install the FIRQ stack in register bank 1 if CONFIG_RGF_NUM_BANK!=1
|
|
*
|
|
* @return N/A
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _firq_stack_setup)
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
or r0, r0, _ARC_V2_STATUS32_RB(1)
|
|
kflag r0
|
|
|
|
mov sp, _firq_stack
|
|
add sp, sp, CONFIG_FIRQ_STACK_SIZE
|
|
|
|
/*
|
|
* We have to reload r0 here, because it is bank1 r0 which contains
|
|
* garbage, not bank0 r0 containing the previous value of status32.
|
|
*/
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
kflag r0
|
|
#endif
|
|
|
|
j_s [blink]
|
|
|
|
/**
|
|
*
|
|
* @brief Save the FIRQ context if CONFIG_RGF_NUM_BANK!=1
|
|
*
|
|
* @return N/A
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _firq_stack_suspend)
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
/* Switch to bank 1 */
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
or r0, r0, _ARC_V2_STATUS32_RB(1)
|
|
kflag r0
|
|
|
|
st sp, [_saved_firq_stack]
|
|
|
|
/* Switch back to bank 0 */
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
kflag r0
|
|
#endif
|
|
|
|
j_s [blink]
|
|
|
|
/**
|
|
*
|
|
* @brief Restore the FIRQ context if CONFIG_RGF_NUM_BANK!=1
|
|
*
|
|
* @return N/A
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _firq_stack_resume)
|
|
|
|
#if CONFIG_RGF_NUM_BANKS != 1
|
|
/* Switch to bank 1 */
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
or r0, r0, _ARC_V2_STATUS32_RB(1)
|
|
kflag r0
|
|
|
|
ld sp, [_saved_firq_stack]
|
|
|
|
/* Switch back to bank 0 */
|
|
lr r0, [_ARC_V2_STATUS32]
|
|
and r0, r0, ~_ARC_V2_STATUS32_RB(7)
|
|
kflag r0
|
|
#endif
|
|
|
|
j_s [blink]
|