Skip to content

fix(multicore): reset core1 on PSM PROC1 release so MicroPython _thread stops hanging#60

Merged
begeistert merged 2 commits into
masterfrom
fix/thread-multicore-reset-core1
Jul 17, 2026
Merged

fix(multicore): reset core1 on PSM PROC1 release so MicroPython _thread stops hanging#60
begeistert merged 2 commits into
masterfrom
fix/thread-multicore-reset-core1

Conversation

@begeistert

@begeistert begeistert commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Problema

_thread.start_new_thread en MicroPython (≥ v1.28.0) colgaba a core0 indefinidamente en WFE.

Antes de cada start_new_thread, MicroPython invoca multicore_reset_core1() de la pico-sdk, que:

  1. Fuerza el bit PROC1 en PSM.FRCE_OFF, espera el read-back, y lo limpia.
  2. Al liberar PROC1, en hardware real core1 sale de reset, reinicia su bootrom y empuja una palabra de sincronía (0) por el FIFO a core0.
  3. Core0 hace multicore_fifo_pop_blocking() esperando esa palabra.

En el emulador, escribir PSM.FRCE_OFF era un store de registro inerte: core1 nunca salía de reset, nunca mandaba el 0, y core0 dormía en WFE para siempre. El handshake §2.8.3 (HandleLaunchHandshake) ya estaba bien implementado, pero core0 moría antes de llegar a él (launchSeqPos=0), en el paso de reset.

Solución (~64 líneas, 3 archivos)

  • PsmPeripheral: nuevo callback OnProc1Released, disparado al detectar la transición set → clear del bit PROC1 (bit 16) en FRCE_OFF. El puente APB ya traduce los alias atómicos de hw_set_bits/hw_clear_bits a un read-modify-write sobre el offset base, así que la detección es directa y fiable.
  • SioPeripheral.SignalCore1BootromReady(): vacía el mailbox de entrada de core1, encola el 0 de "listo" en el FIFO Core1→Core0, y hace SetInterrupt(15) + EventRegistered (SEV) para despertar el WFE de core0.
  • RP2040Machine.ResetCore1FromPsm(): cablea el callback → resetea core1, limpia el estado de launch del SIO, y emite la señal de listo. La secuencia de launch posterior la maneja el handshake existente.

Validación con firmware real

Detalle clave descubierto durante la investigación: v1.21.0 NO reproduce el bug (su _thread no llama a multicore_reset_core1), por eso la validación usa MicroPython v1.28.0.

Verificación definitiva: con el fix desactivado el test cuelga; con el fix, pasa.

  • MulticoreResetTests (unit): ejercen el camino completo hw_set_bits/hw_clear_bits → PSM → SIO a través del bus, y verifican la palabra de listo y el wake por SEV.
  • MicroPythonThreadTests (integración): sobre v1.28.0 real, un thread worker corre en core1 (CORE1-ALIVE) y el thread principal continúa más allá de start_new_thread (MAIN-DONE) en vez de bloquearse.
  • micropython-v1.28.0.uf2 embebido en el assembly de tests (offline-capable) y añadido a la matriz de versiones soportadas.

Resultados

  • ✅ 536 tests unitarios · ✅ 172 de integración (3 skips preexistentes)
  • ✅ Sin regresiones en los tests multicore en C existentes (hello_multicore, multicore_fifo_irqs), que no ejercitaban multicore_reset_core1.

🤖 Generated with Claude Code

begeistert and others added 2 commits July 17, 2026 17:39
…ging

MicroPython (>= v1.28.0) runs pico-sdk's multicore_reset_core1() before every
_thread.start_new_thread: it forces PROC1 in PSM.FRCE_OFF, then blocks in
multicore_fifo_pop_blocking() waiting for core1's bootrom to push its "ready"
word (0). Writing FRCE_OFF was an inert register store, so core1 never left
reset, never signalled, and core0 slept in WFE forever.

Give PsmPeripheral an OnProc1Released callback that fires on the FRCE_OFF PROC1
set -> clear transition. RP2040Machine wires it to reset core1, clear the SIO
launch handshake, and inject the ready word (SioPeripheral.SignalCore1BootromReady:
drain core1's mailbox, enqueue 0 into core0's RX FIFO, SetInterrupt + SEV). That
unblocks the pop; the subsequent §2.8.3 launch sequence is already handled by
HandleLaunchHandshake.

MulticoreResetTests drives the full hw_set_bits/hw_clear_bits -> PSM -> SIO path
through the bus and asserts the ready word and the SEV wake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Older builds (<= v1.21.0) don't call multicore_reset_core1 in the _thread path,
so they can't exercise the core1-reset hang. Embed MicroPython v1.28.0 — the
version that reproduces it (as does the Wokwi pedestrian-crossing sketch,
project 469350452950129665) — and add MicroPythonThreadTests: a worker thread
must run on core1 and the main thread must continue past start_new_thread
instead of blocking in WFE. Add v1.28.0 to the version-compatibility matrix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@begeistert
begeistert requested a review from lmSeryi as a code owner July 17, 2026 23:42
@begeistert
begeistert merged commit e7c6715 into master Jul 17, 2026
4 checks passed
@begeistert
begeistert deleted the fix/thread-multicore-reset-core1 branch July 17, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant