You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
9.8 KiB
335 lines
9.8 KiB
/* |
|
* 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
|
|
|