/* * 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) GDATA(exc_nest_count) #if CONFIG_RGF_NUM_BANKS == 1 GDATA(saved_r0) #else GDATA(saved_sp) #endif /** * * @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 ld r1, [exc_nest_count] add r0, r1, 1 st r0, [exc_nest_count] cmp r1, 0 bgt.d firq_nest mov r0, sp mov r1, _kernel ld sp, [r1, _kernel_offset_to_irq_stack] #if CONFIG_RGF_NUM_BANKS != 1 b firq_nest_1 firq_nest: mov r1, ilink lr r0, [_ARC_V2_STATUS32] and r0, r0, ~_ARC_V2_STATUS32_RB(7) kflag r0 st sp, [saved_sp] lr ilink, [_ARC_V2_STATUS32] or ilink, ilink, _ARC_V2_STATUS32_RB(1) kflag ilink mov r0, sp ld sp, [saved_sp] mov ilink, r1 firq_nest_1: #else firq_nest: #endif push_s r0 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 /* check if we're a nested interrupt: if so, let the interrupted * interrupt handle the reschedule */ mov r1, exc_nest_count ld r0, [r1] sub r0, r0, 1 cmp r0, 0 bne.d _firq_no_reschedule st r0, [r1] #ifdef CONFIG_STACK_SENTINEL bl _check_stack_sentinel #endif #ifdef CONFIG_PREEMPT_ENABLED mov_s r1, _kernel ld_s r2, [r1, _kernel_offset_to_current] /* * 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: pop sp /* * 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 #ifdef CONFIG_CODE_DENSITY pop_s r0 sr r0, [_ARC_V2_EI_BASE] pop_s r0 sr r0, [_ARC_V2_LDI_BASE] pop_s r0 sr r0, [_ARC_V2_JLI_BASE] #endif 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: pop sp #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 base registers from restored context */ ld r3, [r2, _thread_offset_to_stack_base] sr r3, [_ARC_V2_KSTACK_BASE] ld r3, [r2, _thread_offset_to_stack_top] sr r3, [_ARC_V2_KSTACK_TOP] #endif /* * _load_callee_saved_regs expects incoming thread in r2. * _load_callee_saved_regs restores the stack pointer. */ _load_callee_saved_regs #if defined(CONFIG_MPU_STACK_GUARD) || defined(CONFIG_USERSPACE) push_s r2 mov r0, r2 bl configure_mpu_thread pop_s r2 #endif 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 */