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