From 81985d20c98e1550c355263f1d6c7bd6150ee0b4 Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Tue, 9 Sep 2025 15:40:51 +0100 Subject: [PATCH] shared/runtime/mpirq: Check separate hard IRQ stack correctly. On the zephyr port, hard IRQ handlers run with a separate stack on a different thread, so each call to mp_irq_dispatch() and mp_irq_handler() has to be wrapped with adjustments to the stack-limit checker. Move these adjustments into the shared mp_irq_dispatch(), introducing MICROPY_STACK_SIZE_HARD_IRQ which a port can define to non-zero if it uses a separate stack for hard IRQ handlers. We only need wrap the hard dispatch case. This should reduce binary size on zephyr without affecting other ports. Signed-off-by: Chris Webb --- ports/zephyr/machine_i2c_target.c | 7 ------- ports/zephyr/machine_pin.c | 15 --------------- ports/zephyr/machine_timer.c | 16 ---------------- ports/zephyr/mpconfigport.h | 2 ++ py/mpconfig.h | 7 +++++++ shared/runtime/mpirq.c | 15 +++++++++++++++ 6 files changed, 24 insertions(+), 38 deletions(-) diff --git a/ports/zephyr/machine_i2c_target.c b/ports/zephyr/machine_i2c_target.c index 236f133488..c7786e7df9 100644 --- a/ports/zephyr/machine_i2c_target.c +++ b/ports/zephyr/machine_i2c_target.c @@ -114,14 +114,7 @@ static inline size_t mp_machine_i2c_target_get_index(machine_i2c_target_obj_t *s } static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq) { - char dummy; - void *orig_top = MP_STATE_THREAD(stack_top); - mp_uint_t orig_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = &dummy; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; mp_irq_handler(&irq->base); - MP_STATE_THREAD(stack_top) = orig_top; - MP_STATE_THREAD(stack_limit) = orig_limit; } static size_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf) { diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index e0718588d6..1ae262419f 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -64,22 +64,7 @@ void machine_pin_deinit(void) { static void gpio_callback_handler(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { machine_pin_irq_obj_t *irq = CONTAINER_OF(cb, machine_pin_irq_obj_t, callback); - #if MICROPY_STACK_CHECK - // This callback executes in an ISR context so the stack-limit check must be changed to - // use the ISR stack for the duration of this function (so that hard IRQ callbacks work). - char *orig_stack_top = MP_STATE_THREAD(stack_top); - size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = (void *)&irq; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; - #endif - mp_irq_handler(&irq->base); - - #if MICROPY_STACK_CHECK - // Restore original stack-limit checking values. - MP_STATE_THREAD(stack_top) = orig_stack_top; - MP_STATE_THREAD(stack_limit) = orig_stack_limit; - #endif } static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { diff --git a/ports/zephyr/machine_timer.c b/ports/zephyr/machine_timer.c index 410e86762b..4b660849f6 100644 --- a/ports/zephyr/machine_timer.c +++ b/ports/zephyr/machine_timer.c @@ -66,16 +66,6 @@ static mp_obj_t machine_timer_deinit(mp_obj_t self_in); static void machine_timer_callback(struct k_timer *timer) { machine_timer_obj_t *self = (machine_timer_obj_t *)k_timer_user_data_get(timer); - #if MICROPY_STACK_CHECK - // This callback executes in an ISR context so the stack-limit check must - // be changed to use the ISR stack for the duration of this function (so - // that hard IRQ callbacks work). - char *orig_stack_top = MP_STATE_THREAD(stack_top); - size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); - MP_STATE_THREAD(stack_top) = (void *)&self; - MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; - #endif - if (mp_irq_dispatch(self->callback, MP_OBJ_FROM_PTR(self), self->ishard) < 0) { // Uncaught exception; disable the callback so it doesn't run again. self->mode = TIMER_MODE_ONE_SHOT; @@ -84,12 +74,6 @@ static void machine_timer_callback(struct k_timer *timer) { if (self->mode == TIMER_MODE_ONE_SHOT) { machine_timer_deinit(self); } - - #if MICROPY_STACK_CHECK - // Restore original stack-limit checking values. - MP_STATE_THREAD(stack_top) = orig_stack_top; - MP_STATE_THREAD(stack_limit) = orig_stack_limit; - #endif } static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index fbf8dbcc7a..f6b78e64f0 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -47,6 +47,8 @@ #define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_ENABLE_SOURCE_LINE (1) #define MICROPY_STACK_CHECK (1) +#define MICROPY_STACK_CHECK_MARGIN (512) +#define MICROPY_STACK_SIZE_HARD_IRQ (CONFIG_ISR_STACK_SIZE) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_FINALISER (MICROPY_VFS) #define MICROPY_HELPER_REPL (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index ae44b461d3..f4b7c10583 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -717,6 +717,13 @@ #define MICROPY_STACK_CHECK_MARGIN (0) #endif +// The size of a separate stack used for hard IRQ handlers, which should be +// checked instead of the main stack when running a hard callback. 0 implies +// there is no separate ISR stack to check. +#ifndef MICROPY_STACK_SIZE_HARD_IRQ +#define MICROPY_STACK_SIZE_HARD_IRQ (0) +#endif + // Whether to have an emergency exception buffer #ifndef MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0) diff --git a/shared/runtime/mpirq.c b/shared/runtime/mpirq.c index 0b3489c514..4d848ae7e9 100644 --- a/shared/runtime/mpirq.c +++ b/shared/runtime/mpirq.c @@ -69,6 +69,15 @@ int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard) { int result = 0; if (handler != mp_const_none) { if (ishard) { + #if MICROPY_STACK_CHECK && MICROPY_STACK_SIZE_HARD_IRQ > 0 + // This callback executes in an ISR context so the stack-limit + // check must be changed to use the ISR stack for the duration + // of this function. + char *orig_stack_top = MP_STATE_THREAD(stack_top); + size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); + mp_cstack_init_with_sp_here(MICROPY_STACK_SIZE_HARD_IRQ); + #endif + // When executing code within a handler we must lock the scheduler to // prevent any scheduled callbacks from running, and lock the GC to // prevent any memory allocations. @@ -85,6 +94,12 @@ int mp_irq_dispatch(mp_obj_t handler, mp_obj_t parent, bool ishard) { } gc_unlock(); mp_sched_unlock(); + + #if MICROPY_STACK_CHECK && MICROPY_STACK_SIZE_HARD_IRQ > 0 + // Restore original stack-limit checking values. + MP_STATE_THREAD(stack_top) = orig_stack_top; + MP_STATE_THREAD(stack_limit) = orig_stack_limit; + #endif } else { // Schedule call to user function mp_sched_schedule(handler, parent);