Skip to content

RP2350 FreeRTOS SMP Port Bugs

Tracked issues with the community FreeRTOS SMP port for RP2350 (Cortex-M33) that affect picodroid. These are upstream bugs in third_party/FreeRTOS-Kernel/portable/ThirdParty/Community-Supported-Ports/GCC/RP2350_ARM_NTZ/.

1. configTICK_CORE=1 breaks task scheduling on core 0

Status: Worked around (configTICK_CORE=0 + hardware timer alarm)

When configTICK_CORE=1 (tick on core 1, matching the RP2040 configuration), core 0 never starts its first task. The PDB task on core 1 runs normally but the JVM task on core 0 is never scheduled.

Root cause: The RP2350 port uses the generic ARM_CM33_NTZ vPortStartFirstTask which triggers SVC to load the first task context. Unlike the RP2040 port (which directly reads pxCurrentTCBs[get_core_num()] from assembly), the SVC-based approach does not properly select a task for core 0 when it is not the tick core.

Workaround: Use configTICK_CORE=0 (tick on core 0). This means the tick freezes during each flash erase/program window (with_xip_disabled! disables core 0 interrupts) — see bug #2.

2. Tick freeze during flash operations stalls the scheduler

Status: Superseded (PDB task moved to core 0; residual freeze handled by busywait reads)

With configTICK_CORE=0, disabling interrupts on core 0 freezes the FreeRTOS tick, and any tick-dependent operation (timeouts, delays, queue receives) stops advancing for the duration.

The original design parked core 0 in a RAM spin loop (park_for_flash()) for the whole install while core 1 wrote flash, compensating with a TIMER0 alarm ISR on core 1 (timer_alarm.rs) that fired every 1 ms independently of FreeRTOS. Both mechanisms were retired when the PDB task moved to core 0 (see bug #3): the JVM task now blocks on a FreeRTOS notification with interrupts enabled, so the tick keeps running between flash operations. Parking has since swapped cores: a dedicated flashpark parker task pinned to core 1 blocks on a FreeRTOS task notification (hal/rp/core1_park.rs); before each erase/program window core 0 notifies it and waits for the parked ack in SRAM, and core 1 spins in a RAM-resident loop with interrupts masked until the window closes. (On testbench_rp2350w, core 1 also hosts the cyw43 WiFi task, at a priority below the parker.) The tick still freezes inside each erase/program window (with_xip_disabled!), so install-time reads use a hardware-timer busywait (pdb_usb::queue_read_byte_busywait) instead of tick-based timeouts, and a successful install ends in a chip reset.

3. Cross-core FreeRTOS IPC is unreliable

Status: Avoided (not used on RP2350 install path)

xTaskNotify and xQueueSend from core 0 to a blocked task on core 1 silently fail to wake the target task. The notification/data is delivered (the value is set) but the doorbell interrupt that triggers PendSV on core 1 either isn’t sent or isn’t processed. This was tested with both task notifications and queues.

Calling xTaskNotify from core 1 to core 0 also causes the FreeRTOS SMP scheduler to deadlock — vTaskDelay on core 0 stops completing entirely.

Workaround: The install path on RP2350 avoids nearly all cross-core FreeRTOS API calls. Core 1 signals core 0 via atomic flags + notify_jvm() (which works because the JVM task checks STOP_JVM at opcode boundaries). Core 0’s one cross-core call is the FreeRTOS task notification that wakes the dedicated core-1 parker task (flashpark, hal/rp/core1_park.rs); the parker acks back through SRAM atomics, not a FreeRTOS wake. That notification replaced the retired TIMER0-alarm ISR signal, and it is viable because cross-core doorbell/IPI delivery has since been repaired — the same fix that exposed the XIP-window lockup the parker exists to prevent (see core1_park.rs’s design notes).

4. Tight busy-wait on core 1 starves core 0’s scheduler

Status: Avoided (core 1 blocks in FreeRTOS during install wait)

When core 1 runs a tight loop (even with NOP gaps or hardware timer delays), vTaskDelay on core 0 never completes. This occurs regardless of whether the loop accesses shared memory or peripherals. The FreeRTOS SMP scheduler appears to require both cores to periodically enter FreeRTOS-managed blocking states for tick processing to work correctly.

Workaround: Core 1’s resident tasks block in the scheduler: the flashpark parker waits on a FreeRTOS task notification (core1_park.rs), and on testbench_rp2350w the cyw43 task blocks between driver polls. The only tight loop core 1 ever runs is the deliberate RAM-resident park spin inside a flash window, entered and exited via the core1_park.rs handshake. The TIMER0 alarm this workaround once relied on is retired.


Ideal fix

All four bugs stem from configTICK_CORE=0 being forced by bug #1. If the RP2350 port’s vPortStartFirstTask / SVC handler were fixed to properly select tasks for non-tick cores, configTICK_CORE=1 would work and bugs #2–#4 would not apply (the tick would run on core 1, unaffected by core 0 parking).

The RP2040 port handles this correctly by reading pxCurrentTCBs[get_core_num()] directly in assembly rather than relying on SVC. Porting that approach to the Cortex-M33 SVC handler (or using a similar direct context-load) would be the root fix.