Zephyr Project API 4.4.99
A Scalable Open Source RTOS
Loading...
Searching...
No Matches
arm-m-switch.h
Go to the documentation of this file.
1/* Copyright 2025 The ChromiumOS Authors
2 * Copyright 2026 Arm Limited and/or its affiliates <open-source-office@arm.com>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
17#ifndef ZEPHYR_INCLUDE_ARCH_ARM_ARM_M_SWITCH_H_
18#define ZEPHYR_INCLUDE_ARCH_ARM_ARM_M_SWITCH_H_
19
20#include <stdint.h>
21#include <cmsis_core.h>
24
25/* GCC/gas and clang have a code generation bugglet on thumb:
26 * The R7 register is the ABI-defined frame pointer, though it's
27 * usually unused in zephyr due to -fomit-frame-pointer (and the fact
28 * the DWARF on ARM doesn't really need it). But when it IS enabled,
29 * e.g. due to tests that pass CONFIG_FRAME_POINTER, GCC is unable to
30 * allow its use in the clobber list of an asm() block
31 * (Presumably it can't generate spill/fill code without using the frame?).
32 *
33 * When absolutely needed, this kconfig unmasks a workaround where we
34 * spill/fill R7 around the switch manually.
35 */
36#ifdef CONFIG_ARM_FP_CLOBBER_WORKAROUND
37#define _R7_CLOBBER_OPT(expr) expr
38#else
39#define _R7_CLOBBER_OPT(expr) /**/
40#endif
41
42/* Should probably be in kconfig, basically this is testing whether or
43 * not the toolchain will allow a "g" flag (DSP state) to an "msr
44 * adsp_" instruction.
45 */
46#if defined(CONFIG_CPU_CORTEX_M4) || defined(CONFIG_CPU_CORTEX_M7) || defined(CONFIG_ARMV8_M_DSP)
47#define _ARM_M_SWITCH_HAVE_DSP
48#endif
49
70void *arm_m_new_stack(char *base, uint32_t sz, void *entry, void *arg0, void *arg1, void *arg2,
71 void *arg3);
72
85
93void arm_m_exc_exit(void);
94
112
122
125
126void z_arm_configure_dynamic_mpu_regions(struct k_thread *thread);
127
129extern uintptr_t z_arm_tls_ptr;
130
133
135/* Global pointers to the frame locations for the callee-saved
136 * registers. Set in arm_m_must_switch(), and used by the fixup
137 * assembly in arm_m_exc_exit.
138 */
139struct arm_m_cs_ptrs {
141 void *out, *in, *lr_save, *lr_fixup;
142};
144
146extern struct arm_m_cs_ptrs arm_m_cs_ptrs;
147
157static inline void arm_m_exc_tail(void)
158{
159#ifdef CONFIG_MULTITHREADING
160 /* Dirty trickery. We defer as much interrupt-exit work until
161 * the very last moment, when the top-level ISR returns back
162 * into user code. We do this by replacing the topmost (!) LR
163 * return address in the stack frame with our fixup code at
164 * arm_m_exc_exit(). By running after the ISR return, it
165 * knows that the callee-save registers r4-r11 (which need to
166 * be saved to the outgoing thread) are restored.
167 *
168 * Obviously this only works if the ISR is "ABI-compliant
169 * enough". It doesn't have to have pushed a complete frame,
170 * but it does have to have put LR into its standard location.
171 * In practice generated code does (because it has to store LR
172 * somewhere so it can call other functions and then pop it to
173 * return), so this works even on code built with
174 * -fomit-frame-pointer. If an app needs a direct interrupt
175 * and can't meet these requirents, it can always skip this
176 * call and return directly (reschedule is optional for direct
177 * interrupts anyway).
178 *
179 * Finally note the call to check_stack_sentinel here: that is
180 * normally called from context switch at the end, but will
181 * toss an exception, which we can't allow (without hardship)
182 * on the path from here to interrupt exit. It will mess up
183 * our bookkeeping around EXC_RETURN, so do it early.
184 */
185 void z_check_stack_sentinel(void);
186
187 if (IS_ENABLED(CONFIG_STACK_SENTINEL)) {
188 z_check_stack_sentinel();
189 }
190
191#ifndef CONFIG_SMP
192 /* Fast path: with nothing new to run, return straight to the
193 * interrupted thread instead of detouring through arm_m_exc_exit().
194 * This is the same predicate z_sched_next_handle() evaluates there,
195 * only earlier: a nested interrupt that readies a thread patches the
196 * same (topmost) LR slot from its own tail, so no wakeup is lost.
197 */
198 if (_kernel.ready_q.cache == _current) {
199 return;
200 }
201#endif
202
203 void *isr_lr = (void *)*arm_m_exc_lr_ptr;
204
205 if (isr_lr != arm_m_cs_ptrs.lr_fixup) {
206 /* We need to return to arm_m_exc_exit only if an exception is returning to thread
207 * mode with PSP. Note that it is possible to get an exception in arm_m_exc_exit
208 * after interrupts are enabled but, before branching to lr (0xFFFFFFFD) and, at
209 * this point the exception pushes an ESF on MSP. If we write arm_m_exc_exit at top
210 * of MSP at this point, we are corrupting the XPSR of the ESF which will result in
211 * a usage fault. So, make sure that we do this only if we are returning to thread
212 * mode and using PSP to do so.
213 */
214 if ((((uint32_t)isr_lr & 0xFFFFFF00U) == 0xFFFFFF00U)
215 && (((uint32_t)isr_lr & 0xC) == 0xC)) {
216 arm_m_cs_ptrs.lr_save = isr_lr;
218 }
219 }
220#endif
221}
222
234static ALWAYS_INLINE void arm_m_switch(void *switch_to, void **switched_from)
235{
236#if defined(CONFIG_USERSPACE) || defined(CONFIG_MPU_STACK_GUARD)
237 z_arm_configure_dynamic_mpu_regions(_current);
238#endif
239
240#ifdef CONFIG_THREAD_LOCAL_STORAGE
241 z_arm_tls_ptr = _current->tls;
242#endif
243
244#if defined(CONFIG_USERSPACE) && defined(CONFIG_USE_SWITCH)
245 /* Set things up to write the CONTROL.nPRIV bit. We know the outgoing
246 * thread is in privileged mode (because you can't reach a
247 * context switch unless you're in the kernel!).
248 */
249 extern uint32_t arm_m_switch_control;
250 CONTROL_Type control = {.w = __get_CONTROL()};
251
252 __ASSERT_NO_MSG(!control.b.nPRIV);
253 arm_m_switch_control = control.w | (_current->arch.mode & 1);
254#endif
255
256 /* new switch handle in r4, old switch handle pointer in r5.
257 * r6-r8 are used by the code here, and r9-r11 are
258 * unsaved/clobbered (they are very likely to be caller-saved
259 * registers in the enclosing function that the compiler can
260 * avoid using, i.e. we can let it make the call and avoid a
261 * double-spill). But all registers are restored fully
262 * (because we might be switching to an interrupt-saved frame)
263 */
264 register uint32_t r4 __asm__("r4") = (uint32_t)switch_to;
265 register uint32_t r5 __asm__("r5") = (uint32_t)switched_from;
266 __asm__ volatile(_R7_CLOBBER_OPT("push {r7};")
267 /* Construct and push a {r12, lr, pc} group at the top
268 * of the frame, where PC points to the final restore location
269 * at the end of this sequence.
270 */
271 "mov r6, r12;"
272 "mov r7, lr;"
273 "ldr r8, =3f;" /* address of restore PC */
274 "orr r8, r8, #1;" /* set thumb bit */
275 "push {r6-r8};"
276 "sub sp, sp, #24;" /* skip over space for r6-r11 */
277 "push {r0-r5};"
278 "mov r2, #0x01000000;" /* APSR (only care about thumb bit) */
279 "mov r0, #0;" /* Leave r0 zero for code blow */
280#ifdef CONFIG_BUILTIN_STACK_GUARD
281 "mrs r1, psplim;"
282 "push {r1-r2};"
283 "msr psplim, r0;" /* zero it so we can move the stack */
284#else
285 "push {r2};"
286#endif
287
288#ifdef CONFIG_FPU
289 /* Push FPU state (if active) to our outgoing stack */
290 " mrs r8, control;" /* read CONTROL.FPCA */
291 " and r7, r8, #4;" /* r7 == have_fpu */
292 " cbz r7, 1f;"
293 " bic r8, r8, #4;" /* clear CONTROL.FPCA */
294 " msr control, r8;"
295 " vmrs r6, fpscr;"
296 " push {r6};"
297 " vpush {s0-s31};"
298 "1: push {r7};" /* have_fpu word */
299
300 /* Pop FPU state (if present) from incoming frame in r4 */
301 " ldm r4!, {r7};" /* have_fpu word */
302 " cbz r7, 2f;"
303 " vldm r4!, {s0-s31};" /* (note: sets FPCA bit for us) */
304 " ldm r4!, {r6};"
305 " vmsr fpscr, r6;"
306 "2:;"
307#endif
308
309#if defined(CONFIG_USERSPACE) && defined(CONFIG_USE_SWITCH)
310 " ldr r8, =arm_m_switch_control;"
311 " ldr r8, [r8];"
312#endif
313
314 /* Save the outgoing switch handle (which is SP), swap stacks,
315 * and enable interrupts. The restore process is
316 * interruptible code (running in the incoming thread) once
317 * the stack is valid.
318 */
319 "str sp, [r5];"
320 "mov sp, r4;"
321 "msr basepri, r0;"
322
323#if defined(CONFIG_USERSPACE) && defined(CONFIG_USE_SWITCH)
324 " msr control, r8;" /* Now we can drop privilege */
325#endif
326
327 /* Restore is super simple: pop the flags (and stack limit if
328 * enabled) then slurp in the whole GPR set in two
329 * instructions. (The instruction encoding disallows popping
330 * both LR and PC in a single instruction)
331 */
332#ifdef CONFIG_BUILTIN_STACK_GUARD
333 "pop {r1-r2};"
334 "msr psplim, r1;"
335#else
336 "pop {r2};"
337#endif
338#ifdef _ARM_M_SWITCH_HAVE_DSP
339 "msr apsr_nzcvqg, r2;" /* bonkers syntax */
340#else
341 "msr apsr_nzcvq, r2;" /* not even source-compatible! */
342#endif
343 "pop {r0-r12, lr};"
344 "pop {pc};"
345
346 "3:" /* Label for restore address */
347 _R7_CLOBBER_OPT("pop {r7};")::"r"(r4),
348 "r"(r5)
349 : "r6", "r8", "r9", "r10",
350#ifndef CONFIG_ARM_FP_CLOBBER_WORKAROUND
351 "r7",
352#endif
353 "r11");
354}
355
356#ifdef CONFIG_USE_SWITCH
366static ALWAYS_INLINE void arch_switch(void *switch_to, void **switched_from)
367{
368 arm_m_switch(switch_to, switched_from);
369}
370#endif
371
372#endif /* ZEPHYR_INCLUDE_ARCH_ARM_ARM_M_SWITCH_H_ */
bool arm_m_iciit_check(uint32_t msp, uint32_t psp, uint32_t lr)
Recover an interrupted IT/ICI instruction after a context switch.
static void arm_m_exc_tail(void)
ISR-tail helper that patches the stacked LR for deferred switch fixup.
Definition arm-m-switch.h:157
void * arm_m_new_stack(char *base, uint32_t sz, void *entry, void *arg0, void *arg1, void *arg2, void *arg3)
Create an initial switch frame on a new thread's stack.
static ALWAYS_INLINE void arm_m_switch(void *switch_to, void **switched_from)
Core Cortex-M context switch routine.
Definition arm-m-switch.h:234
static ALWAYS_INLINE void arch_switch(void *switch_to, void **switched_from)
Public arch-level wrapper for the Cortex-M switch routine.
Definition arm-m-switch.h:366
void arm_m_iciit_stub(void)
Undefined-instruction stub used to force IT/ICI recovery.
void arm_m_exc_exit(void)
Assembly stub that completes the Cortex-M context restore.
uint32_t * arm_m_exc_lr_ptr
Pointer to the stacked LR word used by the ISR tail fixup path.
struct arm_m_cs_ptrs arm_m_cs_ptrs
Global instance with current callee-saved frame pointers.
bool arm_m_must_switch(void)
Evaluate whether an interrupt should trigger a context switch.
uint32_t arm_m_switch_stack_buffer
Backing storage used when relocating stacks during switch operations.
#define IS_ENABLED(config_macro)
Check for macro definition in compiler-visible expressions.
Definition util_macro.h:154
#define ALWAYS_INLINE
Definition common.h:186
__UINT32_TYPE__ uint32_t
Definition stdint.h:90
__UINTPTR_TYPE__ uintptr_t
Definition stdint.h:105
Thread Structure.
Definition thread.h:258