381 lines
11 KiB
ArmAsm
381 lines
11 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 <toolchain.h>
|
|
#include <linker/sections.h>
|
|
#include <sw_isr_table.h>
|
|
#include <kernel_structs.h>
|
|
#include <arch/cpu.h>
|
|
|
|
GTEXT(_isr_wrapper)
|
|
GTEXT(_isr_demux)
|
|
|
|
GDATA(exc_nest_count)
|
|
SECTION_VAR(BSS, exc_nest_count)
|
|
.balign 4
|
|
.word 0
|
|
|
|
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
GDATA(saved_r0)
|
|
|
|
SECTION_VAR(BSS, saved_r0)
|
|
.balign 4
|
|
.word 0
|
|
#else
|
|
GDATA(saved_sp)
|
|
|
|
SECTION_VAR(BSS, saved_sp)
|
|
.balign 4
|
|
.word 0
|
|
#endif
|
|
|
|
#if defined(CONFIG_SYS_POWER_MANAGEMENT)
|
|
GTEXT(_sys_power_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, swap.s, swap_macros.s, 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
|
|
|
|
Registers not taken into account in the current implementation.
|
|
jli_base
|
|
ldi_base
|
|
ei_base
|
|
accl
|
|
acch
|
|
|
|
The context switch code adopts this standard so that it is easier to follow:
|
|
|
|
- r1 contains _kernel ASAP and is not overwritten over the lifespan of
|
|
the functions.
|
|
- 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 _Swap().
|
|
|
|
|
|
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 regisers 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 control structure (TCS).
|
|
|
|
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 a global called
|
|
saved_r0.
|
|
|
|
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 TCS. 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 TCS. 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
|
|
|
|
Restore interrupt lock level and 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 _Swap() is loaded in ilink and
|
|
the saved status32 in status32_p0, taking care to adjust the interrupt lock
|
|
state desired in status32_p0. The return value is put in r0.
|
|
|
|
o to any irq
|
|
|
|
The IRQ has saved the caller-saved registers in a stack frame, which must be
|
|
popped, and statu32 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 and the return value in r0.
|
|
|
|
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)
|
|
#if CONFIG_ARC_FIRQ
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
st r0,[saved_r0]
|
|
#endif
|
|
lr r0, [_ARC_V2_AUX_IRQ_ACT]
|
|
ffs r0, r0
|
|
cmp r0, 0
|
|
#if CONFIG_RGF_NUM_BANKS == 1
|
|
bnz rirq_path
|
|
/* 1-register bank FIRQ handling must save registers on stack */
|
|
lr r0,[_ARC_V2_STATUS32_P0]
|
|
push_s r0
|
|
mov r0,ilink
|
|
push_s r0
|
|
#ifdef CONFIG_CODE_DENSITY
|
|
lr r0, [_ARC_V2_JLI_BASE]
|
|
push_s r0
|
|
lr r0, [_ARC_V2_LDI_BASE]
|
|
push_s r0
|
|
lr r0, [_ARC_V2_EI_BASE]
|
|
push_s r0
|
|
#endif
|
|
mov r0,lp_count
|
|
push_s r0
|
|
lr r0, [_ARC_V2_LP_START]
|
|
push_s r0
|
|
lr r0, [_ARC_V2_LP_END]
|
|
push_s r0
|
|
push_s blink
|
|
push_s r13
|
|
push_s r12
|
|
push r11
|
|
push r10
|
|
push r9
|
|
push r8
|
|
push r7
|
|
push r6
|
|
push r5
|
|
push r4
|
|
push_s r3
|
|
push_s r2
|
|
push_s r1
|
|
ld r0,[saved_r0]
|
|
push_s r0
|
|
mov r3, _firq_exit
|
|
mov r2, _firq_enter
|
|
j_s [r2]
|
|
rirq_path:
|
|
mov r3, _rirq_exit
|
|
mov 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
|
|
mov r3, _rirq_exit
|
|
mov r2, _rirq_enter
|
|
j_s [r2]
|
|
#endif
|
|
|
|
#ifdef CONFIG_KERNEL_EVENT_LOGGER_SLEEP
|
|
GTEXT(_sys_k_event_logger_exit_sleep)
|
|
|
|
.macro log_sleep_k_event
|
|
clri r0 /* do not interrupt event logger operations */
|
|
push_s r0
|
|
push_s blink
|
|
jl _sys_k_event_logger_exit_sleep
|
|
pop_s blink
|
|
pop_s r0
|
|
seti r0
|
|
.endm
|
|
#else
|
|
#define log_sleep_k_event
|
|
#endif
|
|
#if defined(CONFIG_KERNEL_EVENT_LOGGER_INTERRUPT)
|
|
GTEXT(_sys_k_event_logger_interrupt)
|
|
|
|
.macro log_interrupt_k_event
|
|
clri r0 /* do not interrupt event logger operations */
|
|
push_s r0
|
|
push_s blink
|
|
jl _sys_k_event_logger_interrupt
|
|
pop_s blink
|
|
pop_s r0
|
|
seti r0
|
|
.endm
|
|
#else
|
|
#define log_interrupt_k_event
|
|
#endif
|
|
|
|
#if defined(CONFIG_SYS_POWER_MANAGEMENT)
|
|
.macro exit_tickless_idle
|
|
clri r0 /* do not interrupt exiting tickless idle operations */
|
|
push_s r1
|
|
push_s r0
|
|
mov_s r1, _kernel
|
|
ld_s r0, [r1, _kernel_offset_to_idle] /* requested idle duration */
|
|
breq r0, 0, _skip_sys_power_save_idle_exit
|
|
|
|
st 0, [r1, _kernel_offset_to_idle] /* zero idle duration */
|
|
push_s blink
|
|
jl _sys_power_save_idle_exit
|
|
pop_s blink
|
|
|
|
_skip_sys_power_save_idle_exit:
|
|
pop_s r0
|
|
pop_s r1
|
|
seti r0
|
|
.endm
|
|
#else
|
|
#define exit_tickless_idle
|
|
#endif
|
|
|
|
/* when getting here, r3 contains the interrupt exit stub to call */
|
|
SECTION_FUNC(TEXT, _isr_demux)
|
|
push_s r3
|
|
|
|
|
|
/* cannot be done before this point because we must be able to run C */
|
|
/* r0 is available to be stomped here, and exit_tickless_idle uses it */
|
|
exit_tickless_idle
|
|
log_interrupt_k_event
|
|
log_sleep_k_event
|
|
|
|
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
|
|
|
|
mov r1, _sw_isr_table
|
|
add3 r0, r1, r0 /* table entries are 8-bytes wide */
|
|
|
|
ld_s r1, [r0, 4] /* ISR into r1 */
|
|
jl_s.d [r1]
|
|
ld_s r0, [r0] /* delay slot: ISR parameter into r0 */
|
|
|
|
/* back from ISR, jump to exit stub */
|
|
pop_s r3
|
|
j_s [r3]
|
|
nop
|