← index #18729PR #7177
Related · high · value 0.989
QUERY · ISSUE

zephyr/mphalport: Scheduled callbacks not processed during time.sleep_ms() on TrustZone NS builds

openby calinfajaopened 2026-01-27updated 2026-03-02
port-zephyr

Port, board and/or hardware

Zephyr port. Confirmed on nrf5340dk/nrf5340/cpuapp/ns (TrustZone non-secure mode with CONFIG_ARM_TRUSTZONE_M=y and CONFIG_BUILD_WITH_TFM=y).

May also affect other TrustZone NS targets (nrf9160, etc.). Standard non-TrustZone builds may not be affected if k_poll_signal_raise works reliably from ISR context.

MicroPython version

MicroPython v1.27.0 (tag: v1.27.0)

The mp_hal_wait_sem-based mp_hal_delay_ms was introduced in commit d12085985 (Apr 2021) — "zephyr: Run scheduled callbacks at REPL and during mp_hal_delay_ms."

Reproduction

Soft IRQ timer callbacks scheduled via mp_sched_schedule() are not executed during time.sleep_ms() on TrustZone NS builds.

from machine import Timer
import time

fired = [False]
def callback(t):
    fired[0] = True

t = Timer(-1)
t.init(period=100, mode=Timer.ONE_SHOT, callback=callback)
time.sleep_ms(200)
t.deinit()
print('Timer callback fired:', fired[0])  # Prints False on NS builds

How it should work (but doesn't on NS builds):

The Zephyr port has a signal mechanism wired up:

  1. Timer ISR fires machine_timer_callback()
  2. mp_irq_dispatch() with ishard=false calls mp_sched_schedule()
  3. mp_sched_schedule() triggers MICROPY_SCHED_HOOK_SCHEDULED = mp_hal_signal_event()
  4. mp_hal_signal_event() calls k_poll_signal_raise(&wait_signal, 0)
  5. This should wake k_poll() in mp_hal_wait_sem(), causing the loop to iterate and call mp_handle_pending(true)

Why it fails on TrustZone NS builds:

On boards built with CONFIG_ARM_TRUSTZONE_M=y and CONFIG_BUILD_WITH_TFM=y, the MicroPython code runs in the non-secure partition. k_poll_signal_raise() called from ISR context in the non-secure partition does not reliably wake k_poll() — TFM manages secure/non-secure interrupt routing and the signal never reaches the waiting thread.

The result is that k_poll() blocks for the full timeout, dt >= timeout_ms is true, and the function returns without processing the queued callback.

Expected behaviour

Timer callback fires at 100ms and is processed during the 200ms sleep. fired[0] should be True after time.sleep_ms(200) returns.

Observed behaviour

On TrustZone NS builds: the callback is queued by the ISR but never processed during time.sleep_ms(). fired[0] is still False after the sleep returns. The callback only executes later when the REPL processes pending callbacks.

On non-TrustZone builds: may work correctly if k_poll_signal_raise reliably wakes k_poll from ISR context (not verified).

Additional Information

Proposed fix: Replace the inline mp_hal_delay_ms with a standalone function that sleeps in 1ms increments, calling mp_handle_pending(true) every iteration. This avoids relying on k_poll_signal_raise working from ISR context, which is not reliable across all Zephyr security configurations.

mphalport.h — change inline to extern declaration:

// Implemented in mphalport.c - processes scheduled callbacks during delay
void mp_hal_delay_ms(mp_uint_t ms);

mphalport.c — add standalone implementation:

// Process scheduled callbacks during delay by sleeping in 1ms increments.
// This ensures soft IRQ timer callbacks are executed during time.sleep_ms()
// regardless of whether k_poll_signal_raise works in the target's security context.
void mp_hal_delay_ms(mp_uint_t ms) {
    uint64_t dt;
    uint32_t t0 = mp_hal_ticks_ms();
    for (;;) {
        mp_handle_pending(true);
        MP_THREAD_GIL_EXIT();
        uint32_t t1 = mp_hal_ticks_ms();
        dt = t1 - t0;
        if (dt + 1 >= ms) {
            k_yield();
            MP_THREAD_GIL_ENTER();
            t1 = mp_hal_ticks_ms();
            dt = t1 - t0;
            break;
        } else {
            k_msleep(1);
            MP_THREAD_GIL_ENTER();
        }
    }
    if (dt < ms) {
        mp_hal_delay_us(500);
    }
}

Trade-off: 1ms wake-ups increase CPU activity during delays compared to a single k_poll. This is acceptable because time.sleep_ms() is an active user-requested delay where callback responsiveness matters more than power savings. mp_hal_wait_sem() is left unchanged — it is still used for REPL input waiting, where the REPL loop processes pending callbacks on each iteration.

Note: mp_hal_wait_sem() has the same underlying issue (no mp_handle_pending after k_poll timeout return), but the impact is lower since the REPL loop naturally processes pending callbacks. This fix targets mp_hal_delay_ms specifically where the impact is observable.

I can submit a PR with this fix if the approach is acceptable.

Code of Conduct

  • Yes, I agree to follow the MicroPython Code of Conduct
CANDIDATE · PULL REQUEST

zephyr: run async/scheduled events during REPL and sleep

mergedby dpgeorgeopened 2021-04-28updated 2021-04-30
port-zephyr

This uses Zephyr's k_poll API to wait efficiently for an event signal, and an optional semaphore.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied