328 lines
9.5 KiB
ArmAsm
328 lines
9.5 KiB
ArmAsm
/*
|
|
* Copyright (c) 2014-2015 Wind River Systems, Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief Wrapper around ISRs with logic for context switching
|
|
*
|
|
*
|
|
* Wrapper installed in vector table for handling dynamic interrupts that accept
|
|
* a parameter.
|
|
*/
|
|
|
|
#include <offsets_short.h>
|
|
#include <zephyr/toolchain.h>
|
|
#include <zephyr/linker/sections.h>
|
|
#include <zephyr/sw_isr_table.h>
|
|
#include <zephyr/kernel_structs.h>
|
|
#include <zephyr/arch/cpu.h>
|
|
#include <swap_macros.h>
|
|
#include <zephyr/arch/arc/asm-compat/assembler.h>
|
|
|
|
GTEXT(_isr_wrapper)
|
|
GTEXT(_isr_demux)
|
|
|
|
#if defined(CONFIG_PM)
|
|
GTEXT(z_pm_save_idle_exit)
|
|
#endif
|
|
|
|
/*
|
|
The symbols in this file are not real functions, and neither are
|
|
_rirq_enter/_firq_enter: they are jump points.
|
|
|
|
The flow is the following:
|
|
|
|
ISR -> _isr_wrapper -- + -> _rirq_enter -> _isr_demux -> ISR -> _rirq_exit
|
|
|
|
|
+ -> _firq_enter -> _isr_demux -> ISR -> _firq_exit
|
|
|
|
Context switch explanation:
|
|
|
|
The context switch code is spread in these files:
|
|
|
|
isr_wrapper.s, switch.s, swap_macros.h, fast_irq.s, regular_irq.s
|
|
|
|
IRQ stack frame layout:
|
|
|
|
high address
|
|
|
|
status32
|
|
pc
|
|
lp_count
|
|
lp_start
|
|
lp_end
|
|
blink
|
|
r13
|
|
...
|
|
sp -> r0
|
|
|
|
low address
|
|
|
|
The context switch code adopts this standard so that it is easier to follow:
|
|
|
|
- r2 contains _kernel.current ASAP, and the incoming thread when we
|
|
transition from outgoing thread to incoming thread
|
|
|
|
Not loading _kernel into r0 allows loading _kernel without stomping on
|
|
the parameter in r0 in arch_switch().
|
|
|
|
|
|
ARCv2 processors have two kinds of interrupts: fast (FIRQ) and regular. The
|
|
official documentation calls the regular interrupts 'IRQs', but the internals
|
|
of the kernel call them 'RIRQs' to differentiate from the 'irq' subsystem,
|
|
which is the interrupt API/layer of abstraction.
|
|
|
|
For FIRQ, there are two cases, depending upon the value of
|
|
CONFIG_RGF_NUM_BANKS.
|
|
|
|
CONFIG_RGF_NUM_BANKS==1 case:
|
|
Scratch registers are pushed onto the current stack just as they are with
|
|
RIRQ. See the above frame layout. Unlike RIRQ, the status32_p0 and ilink
|
|
registers are where status32 and the program counter are located, so these
|
|
need to be pushed.
|
|
|
|
CONFIG_RGF_NUM_BANKS!=1 case:
|
|
The FIRQ handler has its own register bank for general purpose registers,
|
|
and thus it doesn't have to save them on a stack. The 'loop' registers
|
|
(lp_count, lp_end, lp_start), however, are not present in the
|
|
second bank. The handler saves these special registers in unused callee saved
|
|
registers (to avoid stack accesses). It is possible to register a FIRQ
|
|
handler that operates outside of the kernel, but care must be taken to only
|
|
use instructions that only use the banked registers.
|
|
|
|
The kernel is able to handle transitions to and from FIRQ, RIRQ and threads.
|
|
The contexts are saved 'lazily': the minimum amount of work is
|
|
done upfront, and the rest is done when needed:
|
|
|
|
o RIRQ
|
|
|
|
All needed registers to run C code in the ISR are saved automatically
|
|
on the outgoing thread's stack: loop, status32, pc, and the caller-
|
|
saved GPRs. That stack frame layout is pre-determined. If returning
|
|
to a thread, the stack is popped and no registers have to be saved by
|
|
the kernel. If a context switch is required, the callee-saved GPRs
|
|
are then saved in the thread's stack.
|
|
|
|
o FIRQ
|
|
|
|
First, a FIRQ can be interrupting a lower-priority RIRQ: if this is
|
|
the case, the FIRQ does not take a scheduling decision and leaves it
|
|
the RIRQ to handle. This limits the amount of code that has to run at
|
|
interrupt-level.
|
|
|
|
CONFIG_RGF_NUM_BANKS==1 case:
|
|
Registers are saved on the stack frame just as they are for RIRQ.
|
|
Context switch can happen just as it does in the RIRQ case, however,
|
|
if the FIRQ interrupted a RIRQ, the FIRQ will return from interrupt
|
|
and let the RIRQ do the context switch. At entry, one register is
|
|
needed in order to have code to save other registers. r0 is saved
|
|
first in the stack and restored later
|
|
|
|
CONFIG_RGF_NUM_BANKS!=1 case:
|
|
During early initialization, the sp in the 2nd register bank is made to
|
|
refer to _firq_stack. This allows for the FIRQ handler to use its own
|
|
stack. GPRs are banked, loop registers are saved in unused callee saved
|
|
regs upon interrupt entry. If returning to a thread, loop registers are
|
|
restored and the CPU switches back to bank 0 for the GPRs. If a context
|
|
switch is needed, at this point only are all the registers saved.
|
|
First, a stack frame with the same layout as the automatic RIRQ one is
|
|
created and then the callee-saved GPRs are saved in the stack.
|
|
status32_p0 and ilink are saved in this case, not status32 and pc.
|
|
To create the stack frame, the FIRQ handling code must first go back to
|
|
using bank0 of registers, since that is where the registers containing
|
|
the exiting thread are saved. Care must be taken not to touch any
|
|
register before saving them: the only one usable at that point is the
|
|
stack pointer.
|
|
|
|
o coop
|
|
|
|
When a coop context switch is done, the callee-saved registers are
|
|
saved in the stack. The other GPRs do not need to be saved, since the
|
|
compiler has already placed them on the stack.
|
|
|
|
For restoring the contexts, there are six cases. In all cases, the
|
|
callee-saved registers of the incoming thread have to be restored. Then, there
|
|
are specifics for each case:
|
|
|
|
From coop:
|
|
|
|
o to coop
|
|
|
|
Do a normal function call return.
|
|
|
|
o to any irq
|
|
|
|
The incoming interrupted thread has an IRQ stack frame containing the
|
|
caller-saved registers that has to be popped. status32 has to be
|
|
restored, then we jump to the interrupted instruction.
|
|
|
|
From FIRQ:
|
|
|
|
When CONFIG_RGF_NUM_BANKS==1, context switch is done as it is for RIRQ.
|
|
When CONFIG_RGF_NUM_BANKS!=1, the processor is put back to using bank0,
|
|
not bank1 anymore, because it had to save the outgoing context from
|
|
bank0, and now has to load the incoming one into bank0.
|
|
|
|
o to coop
|
|
|
|
The address of the returning instruction from arch_switch() is loaded
|
|
in ilink and the saved status32 in status32_p0.
|
|
|
|
o to any irq
|
|
|
|
The IRQ has saved the caller-saved registers in a stack frame, which
|
|
must be popped, and status32 and pc loaded in status32_p0 and ilink.
|
|
|
|
From RIRQ:
|
|
|
|
o to coop
|
|
|
|
The interrupt return mechanism in the processor expects a stack frame,
|
|
but the outgoing context did not create one. A fake one is created
|
|
here, with only the relevant values filled in: pc, status32.
|
|
|
|
There is a discrepancy between the ABI from the ARCv2 docs,
|
|
including the way the processor pushes GPRs in pairs in the IRQ stack
|
|
frame, and the ABI GCC uses. r13 should be a callee-saved register,
|
|
but GCC treats it as caller-saved. This means that the processor pushes
|
|
it in the stack frame along with r12, but the compiler does not save it
|
|
before entering a function. So, it is saved as part of the callee-saved
|
|
registers, and restored there, but the processor restores it _a second
|
|
time_ when popping the IRQ stack frame. Thus, the correct value must
|
|
also be put in the fake stack frame when returning to a thread that
|
|
context switched out cooperatively.
|
|
|
|
o to any irq
|
|
|
|
Both types of IRQs already have an IRQ stack frame: simply return from
|
|
interrupt.
|
|
*/
|
|
|
|
SECTION_FUNC(TEXT, _isr_wrapper)
|
|
#ifdef CONFIG_ARC_FIRQ
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
/* free r0 here, use r0 to check whether irq is firq.
|
|
* for rirq, as sp will not change and r0 already saved, this action
|
|
* in fact is useless
|
|
* for firq, r0 will be restored later
|
|
*/
|
|
push r0
|
|
#endif
|
|
lr r0, [_ARC_V2_AUX_IRQ_ACT]
|
|
ffs r0, r0
|
|
cmp r0, 0
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
bnz rirq_path
|
|
pop r0
|
|
/* 1-register bank FIRQ handling must save registers on stack */
|
|
_create_irq_stack_frame
|
|
lr r0, [_ARC_V2_STATUS32_P0]
|
|
st_s r0, [sp, ___isf_t_status32_OFFSET]
|
|
st ilink, [sp, ___isf_t_pc_OFFSET]
|
|
|
|
mov_s r3, _firq_exit
|
|
mov_s r2, _firq_enter
|
|
j_s [r2]
|
|
rirq_path:
|
|
add sp, sp, 4
|
|
mov_s r3, _rirq_exit
|
|
mov_s r2, _rirq_enter
|
|
j_s [r2]
|
|
#else
|
|
mov.z r3, _firq_exit
|
|
mov.z r2, _firq_enter
|
|
mov.nz r3, _rirq_exit
|
|
mov.nz r2, _rirq_enter
|
|
j_s [r2]
|
|
#endif
|
|
#else
|
|
MOVR r3, _rirq_exit
|
|
MOVR r2, _rirq_enter
|
|
j_s [r2]
|
|
#endif
|
|
|
|
/* r0, r1, and r3 will be used in exit_tickless_idle macro */
|
|
.macro exit_tickless_idle
|
|
#if defined(CONFIG_PM)
|
|
clri r0 /* do not interrupt exiting tickless idle operations */
|
|
MOVR r1, _kernel
|
|
breq r3, 0, _skip_pm_save_idle_exit
|
|
|
|
st 0, [r1, _kernel_offset_to_idle] /* zero idle duration */
|
|
PUSHR blink
|
|
jl z_pm_save_idle_exit
|
|
POPR blink
|
|
|
|
_skip_pm_save_idle_exit:
|
|
seti r0
|
|
#endif
|
|
.endm
|
|
|
|
/* when getting here, r3 contains the interrupt exit stub to call */
|
|
SECTION_FUNC(TEXT, _isr_demux)
|
|
PUSHR r3
|
|
|
|
/* according to ARCv2 ISA, r25, r30, r58, r59 are caller-saved
|
|
* scratch registers, possibly used by interrupt handlers
|
|
*/
|
|
PUSHR r25
|
|
PUSHR r30
|
|
#ifdef CONFIG_ARC_HAS_ACCL_REGS
|
|
PUSHR r58
|
|
#ifndef CONFIG_64BIT
|
|
PUSHR r59
|
|
#endif /* !CONFIG_64BIT */
|
|
#endif
|
|
|
|
#ifdef CONFIG_SCHED_THREAD_USAGE
|
|
bl z_sched_usage_stop
|
|
#endif
|
|
|
|
#ifdef CONFIG_TRACING_ISR
|
|
bl sys_trace_isr_enter
|
|
#endif
|
|
/* cannot be done before this point because we must be able to run C */
|
|
exit_tickless_idle
|
|
|
|
lr r0, [_ARC_V2_ICAUSE]
|
|
/* handle software triggered interrupt */
|
|
lr r3, [_ARC_V2_AUX_IRQ_HINT]
|
|
brne r3, r0, irq_hint_handled
|
|
sr 0, [_ARC_V2_AUX_IRQ_HINT]
|
|
irq_hint_handled:
|
|
|
|
sub r0, r0, 16
|
|
|
|
MOVR r1, _sw_isr_table
|
|
/* SW ISR table entries are 8-bytes wide for 32bit ISA and
|
|
* 16-bytes wide for 64bit ISA */
|
|
ASLR r0, r0, (ARC_REGSHIFT + 1)
|
|
ADDR r0, r1, r0
|
|
/* ISR into r1 */
|
|
LDR r1, r0, ARC_REGSZ
|
|
jl_s.d [r1]
|
|
/* delay slot: ISR parameter into r0 */
|
|
LDR r0, r0
|
|
|
|
#ifdef CONFIG_TRACING_ISR
|
|
bl sys_trace_isr_exit
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARC_HAS_ACCL_REGS
|
|
#ifndef CONFIG_64BIT
|
|
POPR r59
|
|
#endif /* !CONFIG_64BIT */
|
|
POPR r58
|
|
#endif
|
|
|
|
POPR r30
|
|
POPR r25
|
|
|
|
/* back from ISR, jump to exit stub */
|
|
POPR r3
|
|
j_s [r3]
|
|
nop_s
|