| /* |
| * 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) |
| |
| #if CONFIG_RGF_NUM_BANKS == 1 |
| GDATA(saved_r0) |
| |
| SECTION_VAR(BSS, saved_r0) |
| .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 |
| (fibers/task). 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 fiber, 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 fiber, 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_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 |
| 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 |
| |
| #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] |
| 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 |