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