Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use stack limit registers on ARMv8-M for main and fiber stacks #1168

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions examples/nucleo_u575zi-q/fiber_overflow/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2024, Niklas Hauser
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#include <modm/board.hpp>
#include <modm/processing.hpp>

using namespace Board;
using namespace std::chrono_literals;

modm::Fiber<> blinky([]
{
while(1)
{
Board::LedBlue::toggle();
modm::this_fiber::sleep_for(0.5s);
}
});

modm::Fiber<> bad_fiber([]
{

MODM_LOG_INFO << "\nReboot!\nPush the button to overflow the stack!" << modm::endl;

while(1)
{
// cause stack overflow on button push
if (Button::read()) asm volatile ("push {r0-r7}");
modm::this_fiber::yield();
}
});

// On fiber stack overflow this handler will be called
extern "C" void
UsageFault_Handler()
{
// check if the fault is a stack overflow *from the fiber stack*
if (SCB->CFSR & SCB_CFSR_STKOF_Msk and __get_PSP() == __get_PSPLIM())
{
// lower the priority of the usage fault to allow the UART interrupts to work
NVIC_SetPriority(UsageFault_IRQn, (1ul << __NVIC_PRIO_BITS) - 1ul);
// raise an assertion to report this overflow
modm_assert(false, "fbr.stkof", "Fiber stack overflow", modm::this_fiber::get_id());
}
else HardFault_Handler();
}

int
main()
{
Board::initialize();
// Enable the UsageFault handler
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;

modm::fiber::Scheduler::run();

return 0;
}
10 changes: 10 additions & 0 deletions examples/nucleo_u575zi-q/fiber_overflow/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<library>
<extends>modm:nucleo-u575zi-q</extends>
<options>
<option name="modm:build:build.path">../../../build/nucleo_u575zi-q/fiber_overflow</option>
</options>
<modules>
<module>modm:build:scons</module>
<module>modm:processing:fiber</module>
</modules>
</library>
4 changes: 4 additions & 0 deletions src/modm/platform/core/cortex/linker.macros
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ __{{memory.name}}_end = ORIGIN({{memory.name|upper}}) + LENGTH({{memory.name|upp
%#

MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }};
EXCEPTION_FRAME_SIZE = {{ exception_frame_size }};
%% endmacro


Expand Down Expand Up @@ -78,6 +79,9 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }};
. += {{start}};
%% endif
__stack{{suffix}}_start = .;
__main{{suffix}}_stack_bottom = .;
/* main stack must fit one entire exception after the limit */
__main{{suffix}}_stack_limit = . + EXCEPTION_FRAME_SIZE;

. += MAIN_STACK_SIZE;
. = ALIGN(8);
Expand Down
5 changes: 5 additions & 0 deletions src/modm/platform/core/cortex/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ def common_vector_table(env):

- `vector_table`: [position] = Full vector name (ie. *with* `_Handler` or `_IRQHandler` suffix)
- `vector_table_location`: rom or ram
- `vector_table_size`: in Bytes
- `highest_irq`: highest IRQ number + 1
- `core`: cortex-m{0,3,4,7}{,+,f,fd}
- `exception_frame_size`: in Bytes.

The system vectors start at -16, so you must add 16 to `highest_irq` to get
the total number of vectors in the table!
Expand Down Expand Up @@ -57,12 +59,14 @@ def common_vector_table(env):
interrupts[int(vector["position"])] = vector["name"] + "_IRQHandler"

highest_irq = max(interrupts.keys()) + 1
with_fpu = env.get("float-abi", "soft") != "soft"
properties = {
"core": core,
"highest_irq": highest_irq,
"vector_table_location": common_vector_table_location(env),
"vector_table": interrupts,
"vector_table_size": (16 + highest_irq) * 4,
"exception_frame_size": 0x6C if with_fpu else 0x20
}
return properties

Expand Down Expand Up @@ -333,6 +337,7 @@ def build(env):
"with_assert": env.has_module(":architecture:assert"),
"with_fpu": env.get("float-abi", "soft") != "soft",
"with_multicore": env.has_module(":platform:multicore"),
"with_msplim": sum(c.isnumeric() for c in env.substitutions["core"]) == 2,
})
env.outbasepath = "modm/src/modm/platform/core"

Expand Down
4 changes: 4 additions & 0 deletions src/modm/platform/core/cortex/reset_handler.sx.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Reset_Handler:
// Some bootloaders do not reset the stack pointer back to the VTOR entry
ldr r0,=__main_stack_top
mov sp,r0
%% if with_msplim
ldr r0,=__main_stack_limit
msr msplim,r0
%% endif
%% if with_fpu
// Enable FPU early so the compiler may use FPU registers for copying
// SCB->CPACR = ((3UL << 10*2) | (3UL << 11*2));
Expand Down
1 change: 1 addition & 0 deletions src/modm/platform/core/sam/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ def build(env):

def post_build(env):
env.substitutions = env.query("::cortex-m:linkerscript")
env.substitutions.update(env.query("::cortex-m:vector_table"))
env.outbasepath = "modm/link"
env.template("../cortex/ram.ld.in", "linkerscript.ld")
15 changes: 12 additions & 3 deletions src/modm/processing/fiber/context_arm_m.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,18 @@ modm_context_start(modm_context_t*)
"orr r1, r1, #2 \n\t" // Set SPSEL
%% endif
"msr control, r1 \n\t"

%#
%% if is_cm0
"ldr r1, [r0] \n\t"
"mov sp, r1 \n\t" // Set PSP to ctx->sp
%% elif with_psplim
"ldm r0, {r1-r2} \n\t"
"msr psplim, r2 \n\t" // Set PSPLIM to ctx->bottom
"mov sp, r1 \n\t" // Set PSP to ctx->sp
%% else
"ldr sp, [r0] \n\t" // Set PSP to ctx->sp
%% endif

%#
MODM_POP_CONTEXT()
);
}
Expand All @@ -213,13 +217,18 @@ modm_context_jump(modm_context_t*, modm_context_t*)
%% else
"str sp, [r0] \n\t" // Store the SP in from->sp
%% endif
%#
%% if is_cm0
"ldr r2, [r1] \n\t"
"mov sp, r2 \n\t" // Restore SP from to->sp
%% elif with_psplim
"ldm r1, {r1-r2} \n\t"
"msr psplim, r2 \n\t" // Set PSPLIM to ctx->bottom
"mov sp, r1 \n\t" // Set PSP to ctx->sp
%% else
"ldr sp, [r1] \n\t" // Restore SP from to->sp
%% endif

%#
MODM_POP_CONTEXT()
);
}
Expand Down
1 change: 1 addition & 0 deletions src/modm/processing/fiber/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def build(env):
"is_avr": core.startswith("avr"),
"is_windows": env[":target"].identifier.family == "windows",
"is_darwin": env[":target"].identifier.family == "darwin",
"with_psplim": core.startswith("cortex-m") and sum(c.isnumeric() for c in core) == 2,
"core": core,
"with_fpu": with_fpu,
"target": env[":target"].identifier,
Expand Down
33 changes: 33 additions & 0 deletions src/modm/processing/fiber/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,39 @@ Note that stack usage measurement through watermarking can be inaccurate if the
registers contain the watermark value.


### ARMv8-M Stack Limit Registers

On ARMv8-M devices, the PSPLIM register is set to the bottom of the fiber stack
so that stack overflows are reliably detected and cause a STKOF UsageFault if
enabled, or a HardFault if not, both on the main stack.

Currently no recovery strategy is implementable, since accessing the scheduler
is not interrupt-safe and any acquired resources of the offending fiber are not
tracked and can thus also not be released. Therefore, no default implementation
to handle the UsageFault is provided.

Here is an example handler if you wish to experiment with solutions:

```cpp
// Enable the UsageFault handler *before* modm::fiber::Scheduler::run();
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;

// On fiber stack overflow this handler will be called
extern "C" void UsageFault_Handler()
{
// check if the fault is a stack overflow *from the fiber stack*
if (SCB->CFSR & SCB_CFSR_STKOF_Msk and __get_PSP() == __get_PSPLIM())
{
// lower the priority of the usage fault to allow the UART interrupts to work
NVIC_SetPriority(UsageFault_IRQn, (1ul << __NVIC_PRIO_BITS) - 1ul);
// raise an assertion to report this overflow
modm_assert(false, "fbr.stkof", "Fiber stack overflow", modm::this_fiber::get_id());
}
else HardFault_Handler();
}
```


## Scheduling

The scheduler `run()` function will suspend execution of the call site, usually
Expand Down
2 changes: 1 addition & 1 deletion src/modm/processing/fiber/scheduler.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected:
{
%% if core.startswith("cortex-m")
// Ensure that calling this in an interrupt gives a different ID
if (const auto irq = __get_IPSR(); irq) return irq;
if (const auto irq = __get_IPSR(); irq >= 16) return irq;
%% endif
return reinterpret_cast<uintptr_t>(current);
}
Expand Down
Loading