Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | /* * 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> #include <swap_macros.h> GTEXT(_isr_wrapper) GTEXT(_isr_demux) #if defined(CONFIG_SYS_POWER_MANAGEMENT) GTEXT(z_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, switch.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 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 z_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 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 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 z_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) #if defined(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 an action like nop. * for firq, r0 will be restored later */ st r0, [sp] #endif lr r0, [_ARC_V2_AUX_IRQ_ACT] ffs r0, r0 cmp r0, 0 #if CONFIG_RGF_NUM_BANKS == 1 bnz rirq_path ld r0, [sp] /* 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] lr r0, [_ARC_V2_ERET] st_s r0, [sp, ___isf_t_pc_OFFSET] 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 #if defined(CONFIG_TRACING) GTEXT(z_sys_trace_isr_enter) .macro log_interrupt_k_event clri r0 /* do not interrupt event logger operations */ push_s r0 push_s blink jl z_sys_trace_isr_enter 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 z_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 /* according to ARCv2 ISA, r25, r30, r58, r59 are caller-saved * scratch registers, possibly used by interrupt handlers */ push r25 push r30 #ifdef CONFIG_ARC_HAS_ACCL_REGS push r58 push r59 #endif #ifdef CONFIG_EXECUTION_BENCHMARKING bl read_timer_start_of_isr #endif /* 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 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 */ #ifdef CONFIG_EXECUTION_BENCHMARKING push_s r0 push_s r1 bl read_timer_end_of_isr pop_s r1 pop_s r0 #endif jl_s.d [r1] ld_s r0, [r0] /* delay slot: ISR parameter into r0 */ #ifdef CONFIG_ARC_HAS_ACCL_REGS pop r59 pop r58 #endif pop r30 pop r25 /* back from ISR, jump to exit stub */ pop_s r3 j_s [r3] nop |