| /* |
| * Copyright (c) 2014 Wind River Systems, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief Handling of transitions to-and-from regular IRQs (RIRQ) |
| * |
| * This module implements the code for handling entry to and exit from regular |
| * IRQs. |
| * |
| * See isr_wrapper.S for details. |
| */ |
| |
| #include <zephyr/kernel_structs.h> |
| #include <offsets_short.h> |
| #include <zephyr/toolchain.h> |
| #include <zephyr/linker/sections.h> |
| #include <zephyr/arch/cpu.h> |
| #include <swap_macros.h> |
| #include <zephyr/arch/arc/asm-compat/assembler.h> |
| |
| GTEXT(_rirq_enter) |
| GTEXT(_rirq_exit) |
| GTEXT(_rirq_newthread_switch) |
| |
| /* |
| |
| =========================================================== |
| RETURN FROM INTERRUPT TO COOPERATIVE THREAD |
| =========================================================== |
| |
| That's a special case because: |
| 1. We return from IRQ handler to a cooperative thread |
| 2. During IRQ handling context switch did happen |
| 3. Returning to a thread which previously gave control |
| to another thread because of: |
| - Calling k_sleep() |
| - Explicitly yielding |
| - Bumping into locked sync primitive etc |
| |
| What (3) means is before passing control to another thread our thread |
| in question: |
| a. Stashed all precious caller-saved registers on its stack |
| b. Pushed return address to the top of the stack as well |
| |
| That's how thread's stack looks like right before jumping to another thread: |
| ----------------------------->8--------------------------------- |
| PRE-CONTEXT-SWITCH STACK |
| |
| lower_addr, let's say: 0x1000 |
| |
| -------------------------------------- |
| SP -> | Return address; PC (Program Counter), in fact value taken from |
| | BLINK register in arch_switch() |
| -------------------------------------- |
| | STATUS32 value, we explicitly save it here for later usage, read-on |
| -------------------------------------- |
| | Caller-saved registers: some of R0-R12 |
| -------------------------------------- |
| |... |
| |... |
| |
| higher_addr, let's say: 0x2000 |
| ----------------------------->8--------------------------------- |
| |
| When context gets switched the kernel saves callee-saved registers in the |
| thread's stack right on top of pre-switch contents so that's what we have: |
| ----------------------------->8--------------------------------- |
| POST-CONTEXT-SWITCH STACK |
| |
| lower_addr, let's say: 0x1000 |
| |
| -------------------------------------- |
| SP -> | Callee-saved registers: see struct _callee_saved_stack{} |
| | |- R13 |
| | |- R14 |
| | | ... |
| | \- FP |
| | ... |
| -------------------------------------- |
| | Return address; PC (Program Counter) |
| -------------------------------------- |
| | STATUS32 value |
| -------------------------------------- |
| | Caller-saved registers: some of R0-R12 |
| -------------------------------------- |
| |... |
| |... |
| |
| higher_addr, let's say: 0x2000 |
| ----------------------------->8--------------------------------- |
| |
| So how do we return in such a complex scenario. |
| |
| First we restore callee-saved regs with help of _load_callee_saved_regs(). |
| Now we're back to PRE-CONTEXT-SWITCH STACK (see above). |
| |
| Logically our next step is to load return address from the top of the stack |
| and jump to that address to continue execution of the desired thread, but |
| we're still in interrupt handling mode and the only way to return to normal |
| execution mode is to execute "rtie" instruction. And here we need to deal |
| with peculiarities of return from IRQ on ARCv2 cores. |
| |
| Instead of simple jump to a return address stored in the tip of thread's stack |
| (with subsequent interrupt enable) ARCv2 core additionally automatically |
| restores some registers from stack. Most important ones are |
| PC ("Program Counter") which holds address of the next instruction to execute |
| and STATUS32 which holds imortant flags including global interrupt enable, |
| zero, carry etc. |
| |
| To make things worse depending on ARC core configuration and run-time setup |
| of certain features different set of registers will be restored. |
| |
| Typically those same registers are automatically saved on stack on entry to |
| an interrupt, but remember we're returning to the thread which was |
| not interrupted by interrupt and so on its stack there're no automatically |
| saved registers, still inevitably on RTIE execution register restoration |
| will happen. So if we do nothing special we'll end-up with that: |
| ----------------------------->8--------------------------------- |
| lower_addr, let's say: 0x1000 |
| |
| -------------------------------------- |
| # | Return address; PC (Program Counter) |
| | -------------------------------------- |
| | | STATUS32 value |
| | -------------------------------------- |
| | |
| sizeof(_irq_stack_frame) |
| | |
| | | Caller-saved registers: R0-R12 |
| V -------------------------------------- |
| |... |
| SP -> | < Some data on thread's stack> |
| |... |
| |
| higher_addr, let's say: 0x2000 |
| ----------------------------->8--------------------------------- |
| |
| I.e. we'll go much deeper down the stack over needed return address, read |
| some value from unexpected location in stack and will try to jump there. |
| Nobody knows were we end-up then. |
| |
| To work-around that problem we need to mimic existance of IRQ stack frame |
| of which we really only need return address obviously to return where we |
| need to. For that we just shift SP so that it points sizeof(_irq_stack_frame) |
| above like that: |
| ----------------------------->8--------------------------------- |
| lower_addr, let's say: 0x1000 |
| |
| SP -> | |
| A | < Some unrelated data > |
| | | |
| | |
| sizeof(_irq_stack_frame) |
| | |
| | -------------------------------------- |
| | | Return address; PC (Program Counter) |
| | -------------------------------------- |
| # | STATUS32 value |
| -------------------------------------- |
| | Caller-saved registers: R0-R12 |
| -------------------------------------- |
| |... |
| | < Some data on thread's stack> |
| |... |
| |
| higher_addr, let's say: 0x2000 |
| ----------------------------->8--------------------------------- |
| |
| Indeed R0-R13 "restored" from IRQ stack frame will contain garbage but |
| it makes no difference because we're returning to execution of code as if |
| we're returning from yet another function call and so we will restore |
| all needed registers from the stack. |
| |
| One other important remark here is R13. |
| |
| CPU hardware automatically save/restore registers in pairs and since we |
| wanted to save/restore R12 in IRQ stack frame as a caller-saved register we |
| just happen to do that for R13 as well. But given compiler treats it as |
| a callee-saved register we save/restore it separately in _callee_saved_stack |
| structure. And when we restore callee-saved registers from stack we among |
| other registers recover R13. But later on return from IRQ with RTIE |
| instruction, R13 will be "restored" again from fake IRQ stack frame and |
| if we don't copy correct R13 value to fake IRQ stack frame R13 value |
| will be corrupted. |
| |
| */ |
| |
| /** |
| * @brief Work to be done before handing control to an IRQ ISR |
| * |
| * The processor pushes automatically all registers that need to be saved. |
| * However, since the processor always runs at kernel privilege there is no |
| * automatic switch to the IRQ stack: this must be done in software. |
| * |
| * Assumption by _isr_demux: r3 is untouched by _rirq_enter. |
| */ |
| |
| SECTION_FUNC(TEXT, _rirq_enter) |
| |
| /* the ISR will be handled in separate interrupt stack, |
| * so stack checking must be diabled, or exception will |
| * be caused |
| */ |
| _disable_stack_checking r2 |
| clri |
| |
| /* check whether irq stack is used, if |
| * not switch to isr stack |
| */ |
| _check_and_inc_int_nest_counter r0, r1 |
| |
| bne.d rirq_nest |
| MOVR r0, sp |
| |
| _get_curr_cpu_irq_stack sp |
| rirq_nest: |
| PUSHR r0 |
| |
| seti |
| j _isr_demux |
| |
| |
| /** |
| * @brief Work to be done exiting an IRQ |
| */ |
| |
| SECTION_FUNC(TEXT, _rirq_exit) |
| clri |
| |
| POPR sp |
| |
| _dec_int_nest_counter r0, r1 |
| |
| _check_nest_int_by_irq_act r0, r1 |
| |
| jne _rirq_no_switch |
| |
| /* sp is struct k_thread **old of z_arc_switch_in_isr which is a wrapper of |
| * z_get_next_switch_handle. r0 contains the 1st thread in ready queue. If it isn't NULL, |
| * then do switch to this thread. |
| */ |
| _get_next_switch_handle |
| |
| CMPR r0, 0 |
| beq _rirq_no_switch |
| |
| #ifdef CONFIG_ARC_SECURE_FIRMWARE |
| /* here need to remember SEC_STAT.IRM bit */ |
| lr r3, [_ARC_V2_SEC_STAT] |
| push_s r3 |
| #endif |
| |
| /* r2 is old thread |
| * _thread_arch.relinquish_cause is 32 bit despite of platform bittnes |
| */ |
| _st32_huge_offset _CAUSE_RIRQ, r2, _thread_offset_to_relinquish_cause, r1 |
| |
| _irq_store_old_thread_callee_regs |
| |
| /* mov new thread (r0) to r2 */ |
| MOVR r2, r0 |
| |
| /* _rirq_newthread_switch required by exception handling */ |
| .align 4 |
| _rirq_newthread_switch: |
| |
| _load_new_thread_callee_regs |
| |
| breq r3, _CAUSE_RIRQ, _rirq_switch_from_rirq |
| nop_s |
| breq r3, _CAUSE_FIRQ, _rirq_switch_from_firq |
| nop_s |
| |
| /* fall through */ |
| |
| .align 4 |
| _rirq_switch_from_coop: |
| |
| /* for a cooperative switch, it's not in irq, so |
| * need to set some regs for irq return |
| */ |
| _set_misc_regs_irq_switch_from_coop |
| |
| /* |
| * See verbose explanation of |
| * RETURN FROM INTERRUPT TO COOPERATIVE THREAD above |
| */ |
| |
| /* carve fake stack */ |
| SUBR sp, sp, ___isf_t_pc_OFFSET |
| |
| |
| #ifdef CONFIG_ARC_HAS_ZOL |
| /* reset zero-overhead loops */ |
| STR 0, sp, ___isf_t_lp_end_OFFSET |
| #endif /* CONFIG_ARC_HAS_ZOL */ |
| |
| /* |
| * r13 is part of both the callee and caller-saved register sets because |
| * the processor is only able to save registers in pair in the regular |
| * IRQ prologue. r13 thus has to be set to its correct value in the IRQ |
| * stack frame. |
| */ |
| STR r13, sp, ___isf_t_r13_OFFSET |
| |
| #ifdef CONFIG_INSTRUMENT_THREAD_SWITCHING |
| PUSHR blink |
| |
| bl z_thread_mark_switched_in |
| |
| POPR blink |
| #endif |
| /* stack now has the IRQ stack frame layout, pointing to sp */ |
| /* rtie will pop the rest from the stack */ |
| rtie |
| |
| .align 4 |
| _rirq_switch_from_firq: |
| _rirq_switch_from_rirq: |
| |
| _set_misc_regs_irq_switch_from_irq |
| |
| #ifdef CONFIG_INSTRUMENT_THREAD_SWITCHING |
| PUSHR blink |
| |
| bl z_thread_mark_switched_in |
| |
| POPR blink |
| #endif |
| _rirq_no_switch: |
| rtie |