diff --git a/core/thread.c b/core/thread.c index 4332cb9e91..6ce6042e2d 100644 --- a/core/thread.c +++ b/core/thread.c @@ -285,7 +285,7 @@ kernel_pid_t thread_create(char *stack, int stacksize, uint8_t priority, thread->sp = thread_stack_init(function, arg, stack, stacksize); #if defined(DEVELHELP) || IS_ACTIVE(SCHED_TEST_STACK) || \ - defined(MODULE_MPU_STACK_GUARD) + defined(MODULE_MPU_STACK_GUARD) || defined(MODULE_CORTEXM_STACK_LIMIT) thread->stack_start = stack; #endif diff --git a/cpu/cortexm_common/Makefile.features b/cpu/cortexm_common/Makefile.features index 6a6ca14f8e..d264693a9d 100644 --- a/cpu/cortexm_common/Makefile.features +++ b/cpu/cortexm_common/Makefile.features @@ -36,6 +36,7 @@ else ifeq ($(CPU_CORE),cortex-m3) RUST_TARGET = thumbv7m-none-eabi else ifeq ($(CPU_CORE),cortex-m33) CPU_ARCH := armv8m + FEATURES_PROVIDED += cortexm_stack_limit #RUST_TARGET = thumbv8m.main-none-eabi else ifeq ($(CPU_CORE),cortex-m4) CPU_ARCH := armv7m diff --git a/cpu/cortexm_common/thread_arch.c b/cpu/cortexm_common/thread_arch.c index be0188df45..3a762f43da 100644 --- a/cpu/cortexm_common/thread_arch.c +++ b/cpu/cortexm_common/thread_arch.c @@ -297,6 +297,13 @@ void NORETURN cpu_switch_context_exit(void) UNREACHABLE(); } +#ifdef MODULE_CORTEXM_STACK_LIMIT +static void* __attribute__((used)) _get_new_stacksize(unsigned int *args) { + thread_t* t = (thread_t*) args; + return thread_get_stackstart(t); +} +#endif + #if CPU_CORE_CORTEXM_FULL_THUMB void __attribute__((naked)) __attribute__((used)) isr_pendsv(void) { __asm__ volatile ( @@ -337,8 +344,14 @@ void __attribute__((naked)) __attribute__((used)) isr_pendsv(void) { /* current thread context is now saved */ "restore_context: \n" /* Label to skip thread state saving */ - - "ldr r0, [r0] \n" /* load tcb->sp to register 1 */ +#ifdef MODULE_CORTEXM_STACK_LIMIT + "mov r4, r0 \n" /* Save content of R0 into R4*/ + "bl _get_new_stacksize \n" /* Get the new lower limit stack in R0 */ + "msr psplim, r0 \n" /* Set the PSP lower limit stack */ + "ldr r0, [r4] \n" /* Load tcb->sp to register 0 */ +#else + "ldr r0, [r0] \n" /* load tcb->sp to register 0 */ +#endif "ldmia r0!, {r4-r11,lr} \n" /* restore other registers, including lr */ #ifdef MODULE_CORTEXM_FPU "tst lr, #0x10 \n" diff --git a/cpu/cortexm_common/vectors_cortexm.c b/cpu/cortexm_common/vectors_cortexm.c index 35e6cddcf4..e06ffdfca3 100644 --- a/cpu/cortexm_common/vectors_cortexm.c +++ b/cpu/cortexm_common/vectors_cortexm.c @@ -99,6 +99,11 @@ void reset_handler_default(void) uint32_t *dst; const uint32_t *src = &_etext; +#ifdef __ARM_ARCH_8M_MAIN__ + /* Set the lower limit of the exception stack into MSPLIM register */ + __set_MSPLIM((uint32_t)&_sstack); +#endif + cortexm_init_fpu(); #ifdef MODULE_PUF_SRAM diff --git a/features.yaml b/features.yaml index 4ac9190fc1..5608438a4d 100644 --- a/features.yaml +++ b/features.yaml @@ -79,6 +79,8 @@ groups: - name: cpu_check_address help: The @ref cpu_check_address can be used to check if accessing a given address would cause a bus fault. + - name: cortexm_stack_limit + help: ARM Cortex-M PSPLIM/MSPLIM registers are available. - name: cortexm_svc help: ARM Cortex-M Supervisor Calls are available. - name: cortexm_fpu diff --git a/makefiles/features_existing.inc.mk b/makefiles/features_existing.inc.mk index 66fdfb684f..17c6a8cc1e 100644 --- a/makefiles/features_existing.inc.mk +++ b/makefiles/features_existing.inc.mk @@ -46,6 +46,7 @@ FEATURES_EXISTING := \ can_rx_mailbox \ cortexm_fpu \ cortexm_mpu \ + cortexm_stack_limit \ cortexm_svc \ cpp \ cpu_arm7tdmi_gba \ diff --git a/makefiles/features_modules.inc.mk b/makefiles/features_modules.inc.mk index a0a6d64ac5..49bd2b9ccb 100644 --- a/makefiles/features_modules.inc.mk +++ b/makefiles/features_modules.inc.mk @@ -78,6 +78,10 @@ ifneq (,$(filter periph_rtc,$(USEMODULE))) USEMODULE += rtc_utils endif +# select cortexm_stack_limit pseudomodule if the corresponding +# feature is used +USEMODULE += $(filter cortexm_stack_limit, $(FEATURES_USED)) + # select cortexm_svc pseudomodule if the corresponding feature is used USEMODULE += $(filter cortexm_svc, $(FEATURES_USED)) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 77d7ca1e90..a59b3cb89e 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -35,6 +35,16 @@ PSEUDOMODULES += board_software_reset PSEUDOMODULES += arduino_pwm PSEUDOMODULES += arduino_serial_stdio +## @defgroup pseudomodule_arm_stack_limit arm_stack_limit +## @{ +## @brief Set MSP/PSP stack lower limit +## +## Use PSPLIM and MSPLIM ARM registers to set the lower limit of a stack +## This is a protection mechanism to catch stack overflow early before it +## can corrupt adjacent memory. Only available on ARMv8-M architecture. +PSEUDOMODULES += cortexm_stack_limit +## @} + PSEUDOMODULES += can_mbox PSEUDOMODULES += can_pm PSEUDOMODULES += can_raw diff --git a/tests/cpu/cortexm_stack_limit/Makefile b/tests/cpu/cortexm_stack_limit/Makefile new file mode 100644 index 0000000000..acc9c1ab58 --- /dev/null +++ b/tests/cpu/cortexm_stack_limit/Makefile @@ -0,0 +1,11 @@ +BOARD ?= nrf5340dk-app + +include ../Makefile.cpu_common + +FEATURES_REQUIRED += cortexm_stack_limit + +include $(RIOTBASE)/Makefile.include + +ifeq (llvm,$(TOOLCHAIN)) + CFLAGS += -Wno-infinite-recursion +endif diff --git a/tests/cpu/cortexm_stack_limit/main.c b/tests/cpu/cortexm_stack_limit/main.c new file mode 100644 index 0000000000..1f303a019f --- /dev/null +++ b/tests/cpu/cortexm_stack_limit/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 Mesotic SAS + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the cortexm_stack_limit pseudo-module + * + * @author Dylan Laduranty + * + * @} + */ + +#include + +#include "cpu.h" +#include "thread.h" +#include "board.h" + +#define CANARY_VALUE 0xdeadbeef + +static struct { + unsigned int canary; + char stack[THREAD_STACKSIZE_MAIN]; +} buf; + +/* Tell modern GCC (12.x) to not complain that this infinite recursion is + * bound to overflow the stack - this is exactly what this test wants to do :) + * + * Also, tell older versions of GCC that do not know about -Winfinit-recursion + * that it is safe to ignore `GCC diagnostics ignored "-Winfinit-recursion"`. + * They behave as intended in this case :) + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Winfinite-recursion" +static int recurse(int counter) +{ + if (buf.canary != CANARY_VALUE) { +#ifdef LED0_ON + LED0_ON; +#endif +#ifdef LED1_ON + LED1_ON; +#endif +#ifdef LED2_ON + LED2_ON; +#endif +#ifdef LED3_ON + LED3_ON; +#endif + + for (;;) { + thread_sleep(); + } + } + + counter++; + /* Recursing twice here prevents the compiler from optimizing-out the recursion. */ + return recurse(counter) + recurse(counter); +} +#pragma GCC diagnostic pop + +static void *thread(void *arg) +{ + (void) arg; + + recurse(0); + + return NULL; +} + +int main(void) +{ + puts("\nCortex-M Stack limit test\n"); + + puts("If the test fails, all onboard LEDs will be on"); + +#ifdef MODULE_CORTEXM_STACK_LIMIT + puts("The cortexm_stack_limit module is present. Expect the board to crash"\ + " without onboard LEDs on.\n"); +#else + puts("The cortexm_stack_limit module is missing! Expect the test to crash"\ + " with all onboard LEDs on\n"); +#endif + + buf.canary = CANARY_VALUE; + thread_create(buf.stack, sizeof(buf.stack), THREAD_PRIORITY_MAIN + 1, + 0, thread, NULL, "thread"); + + return 0; +}