Zth (libzth)
Loading...
Searching...
No Matches
arch_arm.h
Go to the documentation of this file.
1#ifndef ZTH_CONTEXT_ARCH_ARM_H
2#define ZTH_CONTEXT_ARCH_ARM_H
3/*
4 * SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 */
8
9#ifndef ZTH_CONTEXT_CONTEXT_H
10# error This file must be included by libzth/context/context.h.
11#endif
12
13#ifdef __cplusplus
14
15# include "libzth/regs.h"
16# include "libzth/worker.h"
17
18# if defined(ZTH_ARM_HAVE_MPU) && defined(ZTH_OS_BAREMETAL) && !defined(ZTH_HAVE_MMAN)
19# define ZTH_ARM_DO_STACK_GUARD
20# endif
21
22# ifdef ZTH_ARM_DO_STACK_GUARD
23# define ZTH_ARM_STACK_GUARD_BITS 5
24# define ZTH_ARM_STACK_GUARD_SIZE (1 << ZTH_ARM_STACK_GUARD_BITS)
25# endif
26
27// Using the fp reg alias does not seem to work well...
28# ifdef __thumb__
29# define REG_FP "r7"
30# else
31# define REG_FP "r11"
32# endif
33
34namespace zth {
35namespace impl {
36
37template <typename Impl>
38class ContextArch : public ContextBase<Impl> {
39public:
41 using base::impl;
42
43protected:
44 constexpr explicit ContextArch(ContextAttr const& attr) noexcept
45 : base(attr)
46# ifdef ZTH_ARM_DO_STACK_GUARD
47 , m_guard()
48# endif
49 {}
50
51public:
52 // cppcheck-suppress duplInheritedMember
53 static size_t pageSize() noexcept
54 {
55# ifdef ZTH_ARM_DO_STACK_GUARD
57 return (size_t)ZTH_ARM_STACK_GUARD_SIZE;
58# endif
59 return std::max(sizeof(void*) * 2U, base::pageSize());
60 }
61
62 // cppcheck-suppress duplInheritedMember
63 size_t calcStackSize(size_t size) noexcept
64 {
65# ifndef ZTH_ARM_USE_PSP
66 // If using PSP switch, signals and interrupts are not
67 // executed on the fiber stack, but on the MSP.
68 size += MINSIGSTKSZ;
69# endif
71# if defined(ZTH_HAVE_MMAN)
72 // Both ends of the stack are guarded using mprotect().
73 size += impl().pageSize() * 2U;
74# elif defined(ZTH_ARM_DO_STACK_GUARD)
75 // Only the end of the stack is protected using MPU.
76 size += impl().pageSize();
77# endif
78 }
79
80 return size;
81 }
82
83 // cppcheck-suppress duplInheritedMember
84 void stackAlign(Stack& stack) noexcept
85 {
87 if(!stack.p || !stack.size)
88 return;
89
90# ifdef ZTH_ARM_DO_STACK_GUARD
92 // Stack grows down, exclude the page at the end (lowest address).
93 int dummy __attribute__((unused)) = 0;
94 zth_assert(Impl::stackGrowsDown(&dummy));
95 m_guard = stack.p;
96 size_t const ps = impl().pageSize();
97 // There should be enough room, as computed by calcStackSize().
98 zth_assert(stack.size > ps);
99 stack.p += ps;
100 stack.size -= ps;
101 }
102# endif
103
104 // Stack must be double-word aligned. This should already be
105 // guaranteed by page alignment, but check anyway.
106 zth_assert(((uintptr_t)stack.p & (sizeof(void*) * 2U - 1U)) == 0);
107 zth_assert(((uintptr_t)stack.size & (sizeof(void*) * 2U - 1U)) == 0);
108 }
109
110
111
113 // ARM MPU for stack guard
114
115 __attribute__((always_inline)) static void* sp() noexcept
116 {
117 void* sp_reg;
118 asm("mov %0, sp;" : "=r"(sp_reg));
119 return sp_reg;
120 }
121
122# ifdef ZTH_ARM_DO_STACK_GUARD
123 int stackGuardInit() noexcept
124 {
125 // Don't do anything upon init, enable guards after context
126 // switching.
127 return 0;
128 }
129
130 void stackGuardDeinit() noexcept {}
131
132 void* guard()
133 {
134 return m_guard;
135 }
136
137 void* setGuard(void* guard)
138 {
139 void* old = m_guard;
140 m_guard = guard;
141 return old;
142 }
143
144private:
145# define REG_MPU_RBAR_ADDR_UNUSED ((unsigned)(((unsigned)-ZTH_ARM_STACK_GUARD_SIZE) >> 5))
146
147# pragma GCC diagnostic push
148# pragma GCC diagnostic ignored "-Wconversion"
149
150 static int stackGuardRegion() noexcept
151 {
153 return -1;
154
155 static int region = 0;
156
157 if(likely(region))
158 return region;
159
160 region = reg_mpu_type().field.dregion - 1;
161 reg_mpu_rnr rnr;
162 reg_mpu_rasr rasr;
163
164 // Do not try to use region 0, leave at least one unused for other application
165 // purposes.
166 for(; region > 0; --region) {
167 rnr.field.region = region;
168 rnr.write();
169
170 rasr.read();
171 if(!rasr.field.enable)
172 // This one is free.
173 break;
174 }
175
176 if(region > 0) {
177 reg_mpu_rbar rbar;
178 // Initialize at the top of the address space, so it is effectively unused.
179 rbar.field.addr = REG_MPU_RBAR_ADDR_UNUSED;
180 rbar.write();
181
182 rasr.field.size = ZTH_ARM_STACK_GUARD_BITS - 1;
183 rasr.field.xn = 0;
184 rasr.field.ap = 0; // no access
185
186 // Internal RAM should be normal, shareable, write-through.
187 rasr.field.tex = 0;
188 rasr.field.c = 1;
189 rasr.field.b = 0;
190 rasr.field.s = 1;
191
192 rasr.field.enable = 1;
193 rasr.write();
194
195 reg_mpu_ctrl ctrl;
196 if(!ctrl.field.enable) {
197 // MPU was not enabled at all.
198 ctrl.field.privdefena = 1; // Assume we are currently privileged.
199 ctrl.field.enable = 1;
200 ctrl.write();
201 __isb();
202 }
203 zth_dbg(context, "Using MPU region %d as stack guard", region);
204 } else {
205 region = -1;
206 zth_dbg(context, "Cannot find free MPU region for stack guard");
207 }
208
209 return region;
210 }
211
212 static void stackGuardImpl(void* guard) noexcept
213 {
215 return;
216
217 int const region = stackGuardRegion();
218 if(unlikely(region < 0))
219 return;
220
221 // Must be aligned to guard size
222 zth_assert(((uintptr_t)guard & (ZTH_ARM_STACK_GUARD_SIZE - 1)) == 0);
223
224 reg_mpu_rbar rbar(0);
225
226 if(likely(guard)) {
227 rbar.field.addr = (uintptr_t)guard >> 5;
228 zth_dbg(context, "Set MPU stack guard to %p (sp=%p)", guard, sp());
229 } else {
230 // Initialize at the top of the address space, so it is effectively unused.
231 rbar.field.addr = REG_MPU_RBAR_ADDR_UNUSED;
232 zth_dbg(context, "Disabled MPU stack guard");
233 }
234
235 rbar.field.valid = 1;
236 rbar.field.region = region;
237 rbar.write();
238
239 if(Config::Debug) {
240 // Only required if the settings should be in effect immediately.
241 // However, it takes some time, which may not be worth it in a release
242 // build. If skipped, it will take affect, but only after a few
243 // instructions, which are already in the pipeline.
244 __dsb();
245 __isb();
246 }
247 }
248
249public:
250 void stackGuard() noexcept
251 {
252 // The guard is outside of the usable stack area.
253 stackGuardImpl(m_guard);
254 }
255
256 void stackGuard(Stack const& stack) noexcept
257 {
258 // The guard is at the end of the usable stack area.
259 stackGuardImpl(stack.p);
260 }
261
262 void* stackGuard(void* p) noexcept
263 {
264 void* prev = setGuard(p);
265 stackGuard();
266
267 if(!Config::Debug && !p) {
268 // The guard is disabled. For non-debug builds, the
269 // barriers are not in place by stackGuardImpl(). In
270 // that case, do it here, to make sure that you can
271 // rely on that the guard is really disabled when this
272 // function returns.
273 __dsb();
274 __isb();
275 }
276
277 return prev;
278 }
279
280# pragma GCC diagnostic pop
281# endif // ZTH_ARM_DO_STACK_GUARD
282
283
284
286 // jmp_buf fiddling for sjlj
287
288# ifdef ZTH_OS_BAREMETAL
289private:
290 static size_t get_lr_offset() noexcept
291 {
292 static size_t lr_offset = 0;
293
294 if(unlikely(lr_offset == 0)) {
295 // Not initialized yet.
296
297 // env is filled with several registers. The number of registers depends on
298 // the ARM architecture and Thumb mode, and compile settings for newlib. We
299 // have to change the sp and lr registers in this env. These follow other
300 // caller-saved registers, but may be placed before VPU registers.
301 //
302 // Instead of hardcoding the offset, we try to find it dynamically. The sp
303 // and lr registers are always next to each other. We assume here that the
304 // sp is not part of the VPU registers, so this is our marker.
305
306 static_assert(
307 sizeof(jmp_buf) >= sizeof(void*) * 4U,
308 "jmp_buf size too small to find lr offset");
309
310 jmp_buf test_env = {};
311 ::setjmp(test_env);
312
313 uintptr_t sp_ = 0;
314 asm volatile("mov %0, sp;" : "=r"(sp_));
315 zth_assert(sp_);
316
317 // Note that jmp_buf may be an array type with other elements than void*.
318 // NOLINTNEXTLINE
319 uintptr_t const* test_env_ = reinterpret_cast<uintptr_t const*>(test_env);
320# pragma GCC diagnostic push
321# pragma GCC diagnostic ignored "-Wsizeof-array-div"
322 size_t sp_offset = sizeof(jmp_buf) / sizeof(void*) - 2U;
323# pragma GCC diagnostic pop
324 for(; sp_offset > 0 && test_env_[sp_offset] != sp_; sp_offset--)
325 ;
326
327 if(sp_offset == 0) {
328 // Should not be possible.
329 zth_abort("Invalid lr offset");
330 }
331
332 lr_offset = sp_offset + 1U;
333 }
334
335 return lr_offset;
336 }
337
338public:
339 // cppcheck-suppress duplInheritedMember
340 static void** sp(Stack const& stack) noexcept
341 {
342 int dummy __attribute__((unused)) = 0;
343 zth_assert(Impl::stackGrowsDown(&dummy));
344 // sp must be dword aligned
345 return (void**)((uintptr_t)(stack.p + stack.size - sizeof(void*)) & ~(uintptr_t)7);
346 }
347
348 // cppcheck-suppress duplInheritedMember
349 static void stack_push(void**& sp, void* p) noexcept
350 {
351 *sp = p;
352 sp--;
353 }
354
355 // cppcheck-suppress duplInheritedMember
356 static void set_sp(jmp_buf& env, void** sp) noexcept
357 {
358 // NOLINTNEXTLINE
359 uintptr_t* env_ = reinterpret_cast<uintptr_t*>(env);
360 env_[get_lr_offset() - 1U] = (uintptr_t)sp;
361 }
362
363 // cppcheck-suppress duplInheritedMember
364 static void set_pc(jmp_buf& env, void* pc) noexcept
365 {
366 // NOLINTNEXTLINE
367 uintptr_t* env_ = reinterpret_cast<uintptr_t*>(env);
368 env_[get_lr_offset()] = (uintptr_t)pc; // lr = pc after return
369 }
370
371 // cppcheck-suppress duplInheritedMember
372 __attribute__((naked)) static void context_trampoline_from_jmp_buf() noexcept
373 {
374 // The this pointer is saved on the stack, as r0 is not part of
375 // the jmp_buf. Load it and call context_entry according to
376 // normal ABI.
377
378 // clang-format off
379 asm volatile(
380 "ldr r0, [sp]\n"
381 // Terminate stack frame list here, only for debugging purposes.
382 "mov " REG_FP ", #0\n"
383 "mov lr, #0\n"
384 "push {" REG_FP ", lr}\n"
385 "mov " REG_FP ", sp\n"
386 "bl context_entry\n"
387 );
388 // clang-format on
389 }
390
391# if defined(ZTH_CONTEXT_SJLJ)
392# if defined(__ARM_FP) && !defined(__SOFTFP__) && NEWLIB_VERSION < 40300
393# define ZTH_CONTEXT_FPU
394 inline __attribute__((always_inline)) void context_push_regs() noexcept
395 {
396 // newlib does not do saving/restoring of FPU registers.
397 // We have to do it here ourselves.
398 asm volatile("vstm %0, {d8-d15}" : : "r"(m_fpu_env));
399 }
400
401 inline __attribute__((always_inline)) void context_pop_regs() noexcept
402 {
403 asm volatile("vldm %0, {d8-d15}" : : "r"(m_fpu_env));
404 }
405# endif
406
407# ifdef ZTH_ARM_USE_PSP
408 inline __attribute__((always_inline)) void
409 context_prepare_jmp(Impl& to, jmp_buf& env) noexcept
410 {
411 // Use the PSP for fibers and the MSP for the worker.
412 // So, if there is no stack in the Context, assume it is a worker, running
413 // from MSP. Otherwise it is a fiber using PSP. Both are executed privileged.
414 if(unlikely(!impl().stackUsable() || !to.stackUsable())) {
415 unsigned int control;
416 asm("mrs %0, control\n" : "=r"(control));
417
418 if(to.stackUsable())
419 control |= 2U; // set SPSEL to PSP
420 else
421 control &= ~2U; // set SPSEL to MSR
422
423 // As the SP changes, do not return from this function,
424 // but actually do the longjmp.
425 asm volatile(
426 "msr control, %1\n"
427 "isb\n"
428 "mov r0, %0\n"
429 "movs r1, #1\n"
430 "b longjmp\n"
431 :
432 : "r"(env), "r"(control)
433 : "r0", "r1", "memory");
434 }
435 }
436# endif
437# endif // ZTH_CONTEXT_SJLJ
438# endif // ZTH_OS_BAREMETAL
439
440# ifdef ZTH_STACK_SWITCH
441private:
442 __attribute__((naked, noinline)) static void*
443 stack_switch_msp(void* UNUSED_PAR(arg), UNUSED_PAR(void* (*f)(void*) noexcept)) noexcept
444 {
445 // clang-format off
446 asm volatile(
447 "push {r4, " REG_FP ", lr}\n" // Save pc and variables
448#ifdef __cpp_exceptions
449 ".save {r4, " REG_FP ", lr}\n"
450#endif
451 "add " REG_FP ", sp, #0\n"
452
453 "mrs r4, control\n" // Save current control register
454 "bic r3, r4, #2\n" // Set to use MSP
455 "msr control, r3\n" // Enable MSP
456 "isb\n"
457 "and r4, r4, #2\n" // r4 is set to only have the PSP bit
458
459 // We are on MSP now.
460
461 "push {" REG_FP ", lr}\n" // Save frame pointer on MSP
462#ifdef __cpp_exceptions
463 ".setfp " REG_FP ", sp\n"
464#endif
465 "add " REG_FP ", sp, #0\n"
466
467 "blx r1\n" // Call f(arg)
468
469 "mrs r3, control\n" // Save current control register
470 "orr r3, r3, r4\n" // Set to use previous PSP setting
471 "msr control, r3\n" // Enable PSP setting
472 "isb\n"
473
474 // We are back on the previous stack.
475
476 "pop {r4, " REG_FP ", pc}\n" // Return to caller
477 );
478 // clang-format on
479 }
480
481 __attribute__((naked, noinline)) static void* stack_switch_psp(
482 void* UNUSED_PAR(arg), UNUSED_PAR(void* (*f)(void*) noexcept),
483 void* UNUSED_PAR(sp)) noexcept
484 {
485 // clang-format off
486 asm volatile(
487 "push {r4, " REG_FP ", lr}\n" // Save pc and variables
488#ifdef __cpp_exceptions
489 ".save {r4, " REG_FP ", lr}\n"
490#endif
491 "add " REG_FP ", sp, #0\n"
492
493 "mov r4, sp\n" // Save previous stack pointer
494 "mov sp, r2\n" // Set new stack pointer
495 "push {" REG_FP ", lr}\n" // Save previous frame pointer on new stack
496#ifdef __cpp_exceptions
497 ".setfp " REG_FP ", sp\n"
498#endif
499 "add " REG_FP ", sp, #0\n"
500
501 "blx r1\n" // Call f(arg)
502
503 "mov sp, r4\n" // Restore previous stack
504 "pop {r4, " REG_FP ", pc}\n" // Return to caller
505 );
506 // clang-format on
507 }
508
509public:
510 void* stack_switch(void* stack, size_t size, void* (*f)(void*) noexcept, void* arg) noexcept
511 {
512 if(!stack) {
514
515 if(worker)
516 worker->contextSwitchDisable();
517
518 void* res = stack_switch_msp(arg, f);
519
520 if(worker)
521 worker->contextSwitchEnable();
522
523 return res;
524 } else {
525 void* sp = (void*)(((uintptr_t)stack + size) & ~(uintptr_t)7);
526# ifdef ZTH_ARM_HAVE_MPU
527 void* prevGuard = nullptr;
528
529 if(zth::Config::EnableStackGuard && size >= ZTH_ARM_STACK_GUARD_SIZE * 2) {
530 void* guard = (void*)(((uintptr_t)stack
531 + 2 * ZTH_ARM_STACK_GUARD_SIZE - 1)
532 & ~(ZTH_ARM_STACK_GUARD_SIZE - 1));
533 prevGuard = setGuard(guard);
534 stackGuard();
535 }
536# endif
537
538 void* res = stack_switch_psp(arg, f, sp);
539
540# ifdef ZTH_ARM_HAVE_MPU
541 if(prevGuard) {
542 setGuard(prevGuard);
543 stackGuard();
544 }
545# endif
546 return res;
547 }
548 }
549# endif // ZTH_STACK_SWITCH
550
551private:
552# ifdef ZTH_ARM_HAVE_MPU
553 // clang-format off
554 ZTH_REG_DEFINE(uint32_t, reg_mpu_type, 0xE000ED90,
555 reserved1 : 8,
556 region : 8,
557 dregion : 8,
558 reserved2 : 7,
559 separate : 1)
560
561 ZTH_REG_DEFINE(uint32_t, reg_mpu_ctrl, 0xE000ED94,
562 reserved : 29,
563 privdefena : 1,
564 hfnmiena : 1,
565 enable : 1)
566
567 ZTH_REG_DEFINE(uint32_t, reg_mpu_rnr, 0xE000ED98,
568 reserved : 24,
569 region : 8)
570
571 ZTH_REG_DEFINE(uint32_t, reg_mpu_rbar, 0xE000ED9C,
572 addr : 27,
573 valid : 1,
574 region : 4)
575
576 ZTH_REG_DEFINE(uint32_t, reg_mpu_rasr, 0xE000EDA0,
577 reserved1 : 3,
578 xn : 1,
579 reserved2 : 1,
580 ap : 3,
581 reserved3 : 2,
582 tex : 3,
583 s : 1,
584 c : 1,
585 b : 1,
586 srd : 8,
587 reserved4 : 2,
588 size : 5,
589 enable : 1)
590 // clang-format on
591# endif // ZTH_ARM_HAVE_MPU
592
593private:
594# ifdef ZTH_ARM_DO_STACK_GUARD
595 void* m_guard;
596# endif
597# ifdef ZTH_CONTEXT_FPU
598 double m_fpu_env[8];
599# endif
600};
601
602} // namespace impl
603} // namespace zth
604
605# if !defined(__cpp_exceptions) && defined(__NEWLIB__)
606// It seems that newlib depends on this function by setjmp.o, even when exceptions are disabled. It
607// would pull in all unwinding code, which is dead code anyway. Forcibly define the symbol here to
608// prevent that.
609EXTERN_C void __aeabi_unwind_cpp_pr0()
610{
611 zth_abort("No exceptions");
612}
613# endif
614
615#endif // __cplusplus
616#endif // ZTH_CONTEXT_ARCH_ARM_H
#define REG_FP
Definition arch_arm.h:31
static safe_ptr< singleton_type >::type instance() noexcept
Return the only instance of T within this thread.
Definition util.h:1181
The class that manages the fibers within this thread.
Definition worker.h:35
void contextSwitchDisable() noexcept
Definition worker.h:151
void contextSwitchEnable(bool enable=true) noexcept
Definition worker.h:142
Impl & impl() noexcept
Return Impl this.
Definition context.h:90
void stackAlign(Stack &stack) noexcept
Definition arch_arm.h:84
constexpr ContextArch(ContextAttr const &attr) noexcept
Definition arch_arm.h:44
static size_t pageSize() noexcept
Definition arch_arm.h:53
size_t calcStackSize(size_t size) noexcept
Definition arch_arm.h:63
ContextBase< Impl > base
Definition arch_arm.h:40
static void * sp() noexcept
Definition arch_arm.h:115
Base class of the Context.
Definition context.h:68
static void stack_push(void **&sp, void *p) noexcept
Push data into the stack.
void stackGuardDeinit() noexcept
Release the guards around the memory.
Definition context.h:391
static size_t pageSize() noexcept
Get system's page size.
Definition context.h:212
Impl & impl() noexcept
Return Impl this.
Definition context.h:90
void context_pop_regs() noexcept
Post-sjlj context restoring.
Definition context.h:476
Stack const & stack() const noexcept
Return the stack address.
Definition context.h:226
void * stack_switch(void *stack, size_t size, void *(*f)(void *) noexcept, void *arg) noexcept
Definition context.h:506
int stackGuardInit() noexcept
Initialize guards around the stack memory.
Definition context.h:370
void stackGuard() noexcept
Configure the guard for the current fiber.
Definition context.h:412
static void set_sp(jmp_buf &env, void **sp) noexcept
Set the stack pointer in a jmp_buf.
void context_prepare_jmp(Impl &to, jmp_buf &env) noexcept
Pre-sjlj jump.
Definition context.h:482
static void set_pc(jmp_buf &env, void *sp) noexcept
Set the program counter in a jmp_buf.
ContextAttr & attr() noexcept
Return the context attributes, requested by the user.
Definition context.h:120
static void context_trampoline_from_jmp_buf() noexcept
Entry point to jump to from a (sig)jmp_buf.
Stack const & stackUsable() const noexcept
Return the start of the actual usable stack.
Definition context.h:234
void stackAlign(Stack &stack) noexcept
Compute and modify the stack alignment and size, within the allocated space.
Definition context.h:306
void context_push_regs() noexcept
Pre-sjlj context saving.
Definition context.h:471
void zth_abort(char const *fmt,...)
Aborts the process after printing the given printf() formatted message.
Definition util.cpp:337
constexpr auto guard(T &&g, char const *name=nullptr)
Create a guard from a function.
Definition fsm14.h:537
#define ZTH_REG_DEFINE(T, name, addr, fields...)
Define a hardware reference helper class, with bitfields.
Definition regs.h:116
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:194
#define UNUSED_PAR(name)
Definition macros.h:78
static bool const Debug
This is a debug build when set to true.
Definition config.h:56
static bool const EnableThreads
Add (Worker) thread support when true.
Definition config.h:84
static bool const EnableStackGuard
When true, enable stack guards.
Definition config.h:142
Stack information.
Definition context.h:89
char * p
Definition context.h:105
size_t size
Definition context.h:106
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:217
#define likely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:45
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:60