/* * 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 #include #include #include #include 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]