Skip to content

Commit edee905

Browse files
committed
docs: Add FreeRTOS implementation summary.
Document covering the journey from master branch to FreeRTOS SMP, including architecture, service task framework API, and usage notes. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent 348ba1b commit edee905

File tree

1 file changed

+338
-0
lines changed

1 file changed

+338
-0
lines changed

FREERTOS_IMPL_SUMMARY.md

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# MicroPython FreeRTOS Threading Backend: Implementation Summary
2+
3+
**Date:** December 2025
4+
**Branch:** `freertos`
5+
**Status:** Functional on RP2 (Pico W)
6+
7+
---
8+
9+
## Executive Summary
10+
11+
This document summarizes the implementation of a universal FreeRTOS threading backend for MicroPython, currently integrated with the RP2 (Raspberry Pi Pico) port. The implementation replaces the limited Pico SDK multicore approach (2 threads max, one per core) with FreeRTOS SMP, enabling:
12+
13+
- Unlimited Python threads (limited only by RAM)
14+
- True parallel execution on both RP2040 cores
15+
- Background service tasks for USB, WiFi, and soft timers
16+
- A reusable threading backend designed for other ports (STM32, mimxrt, etc.)
17+
18+
---
19+
20+
## Motivation: Why FreeRTOS?
21+
22+
### Master Branch Limitations
23+
24+
The upstream RP2 port uses Pico SDK's multicore API:
25+
- Limited to exactly 2 threads (one per core)
26+
- GIL disabled (`MICROPY_PY_THREAD_GIL=0`)
27+
- No priority-based preemption
28+
- USB/WiFi compete with Python for CPU time
29+
30+
### FreeRTOS Benefits
31+
32+
- **N threads**: Not limited by core count
33+
- **SMP scheduling**: FreeRTOS distributes work across cores
34+
- **Priority preemption**: USB/WiFi run at higher priority, remain responsive
35+
- **Industry standard**: Well-documented, actively maintained
36+
- **Portable**: Same backend works on STM32, mimxrt, nRF, etc.
37+
38+
---
39+
40+
## Journey from Master
41+
42+
### Phase 1: Scaffolding and Core Backend
43+
44+
Created `extmod/freertos/` with shared infrastructure:
45+
46+
| File | Purpose |
47+
|------|---------|
48+
| `mpthreadport.h` | Thread API definitions, data structures |
49+
| `mpthreadport.c` | Thread creation, GC integration, mutex implementation |
50+
| `mp_freertos_hal.h/c` | HAL functions (delay, ticks, yield) |
51+
| `mp_freertos_service.h/c` | Service task framework for deferred callbacks |
52+
53+
### Phase 2: QEMU Validation
54+
55+
Built and tested on QEMU ARM (MPS2-AN385) to validate the core backend without hardware. All 32 thread tests pass (1 skip: `disable_irq.py`).
56+
57+
### Phase 3: STM32 Integration
58+
59+
Integrated with STM32 port (NUCLEO-F429ZI). Validated threading, GC scanning, and stress tests on real hardware.
60+
61+
### Phase 4: RP2 Integration
62+
63+
Major restructuring of the RP2 port:
64+
65+
1. **Replaced Pico SDK multicore with FreeRTOS SMP**
66+
- FreeRTOS-Kernel-SMP from Pico SDK
67+
- Hardware spinlocks 0 and 1 reserved for FreeRTOS
68+
- `configNUMBER_OF_CORES = 2`
69+
70+
2. **Resolved PendSV Handler Conflict**
71+
- Both MicroPython's soft timers and FreeRTOS use PendSV
72+
- Solution: Assembly wrapper that chains both handlers
73+
- Uses `configUSE_DYNAMIC_EXCEPTION_HANDLERS`
74+
75+
3. **Replaced PendSV Dispatch with Service Task**
76+
- Soft timers and scheduled callbacks now run in a high-priority FreeRTOS task
77+
- Avoids interrupt-level conflicts
78+
79+
4. **WiFi Investigation and Fix**
80+
- Initial builds hung during WiFi scan
81+
- Root cause: `cyw43_yield()` blocked forever waiting for events
82+
- Fix: Changed yield behavior for FreeRTOS SMP context
83+
84+
### Phase 5: True SMP Investigation
85+
86+
Initial FreeRTOS builds pinned all threads to core 0 for safety. Investigation into enabling true dual-core Python execution:
87+
88+
| Configuration | Result |
89+
|--------------|--------|
90+
| GIL=1, core 0 only | Works, but no parallelism |
91+
| GIL=1, tskNO_AFFINITY | Works, threads migrate but still serialize |
92+
| GIL=0, tskNO_AFFINITY | Works with caveats (see below) |
93+
94+
**Key Finding:** Master's GIL=0 implementation has the same race condition vulnerability. The official thread tests require 4 threads which master can't run, hiding the issue.
95+
96+
**Final Configuration:** GIL=0 with `tskNO_AFFINITY` - matches master's semantics but with N threads instead of 2.
97+
98+
---
99+
100+
## Architecture Overview
101+
102+
### Thread Model
103+
104+
```
105+
┌──────────────────────────────────────────────────────────────────┐
106+
│ FreeRTOS SMP Scheduler │
107+
│ (runs on both RP2040 cores) │
108+
├──────────────────────────────────────────────────────────────────┤
109+
│ Priority 3: Service Task (soft timers, scheduled callbacks) │
110+
├──────────────────────────────────────────────────────────────────┤
111+
│ Priority 1: Main MicroPython Task │
112+
│ Priority 1: Python Thread 1 │
113+
│ Priority 1: Python Thread 2 │
114+
│ Priority 1: ... (unlimited threads) │
115+
├──────────────────────────────────────────────────────────────────┤
116+
│ Priority 0: FreeRTOS Idle Task │
117+
└──────────────────────────────────────────────────────────────────┘
118+
```
119+
120+
### Memory Management
121+
122+
All thread memory is GC-allocated using static FreeRTOS APIs:
123+
124+
```c
125+
// Thread creation (mp_thread_create)
126+
tcb = m_new(StaticTask_t, 1); // GC-allocated TCB
127+
stack = m_new(StackType_t, stack_len); // GC-allocated stack
128+
xTaskCreateStatic(...); // FreeRTOS uses our memory
129+
```
130+
131+
**Benefits:**
132+
- Thread stacks are scanned by GC (no dangling references)
133+
- No separate FreeRTOS heap needed
134+
- Memory automatically reclaimed when threads finish
135+
136+
### Thread Cleanup (Reaper)
137+
138+
A thread cannot free its own stack while running. The "reaper" mechanism cleans up finished threads:
139+
140+
1. Thread marks itself `FINISHED` and yields forever
141+
2. Next `mp_thread_create()` calls reaper
142+
3. Reaper iterates thread list, frees `FINISHED` threads
143+
4. Memory returned to GC heap
144+
145+
---
146+
147+
## Service Task Framework
148+
149+
### Purpose
150+
151+
Replaces PendSV-based deferred execution (soft timers, scheduled callbacks) with a FreeRTOS task. This avoids conflicts with FreeRTOS's own PendSV usage.
152+
153+
### API
154+
155+
```c
156+
// Initialize (called once during startup)
157+
void mp_freertos_service_init(void);
158+
159+
// Schedule a callback to run in service task context
160+
// slot: Dispatch slot index (0 to MICROPY_FREERTOS_SERVICE_MAX_SLOTS-1)
161+
// callback: Function to call (runs at high priority)
162+
void mp_freertos_service_schedule(size_t slot, mp_freertos_dispatch_t callback);
163+
164+
// Suspend/resume dispatch processing (for critical sections)
165+
void mp_freertos_service_suspend(void);
166+
void mp_freertos_service_resume(void);
167+
```
168+
169+
### How C Modules Use Service Tasks
170+
171+
C modules that need deferred callback execution should:
172+
173+
1. **Define a dispatch slot** in `mpconfigport.h`:
174+
```c
175+
#define MP_FREERTOS_SLOT_SOFT_TIMER 0
176+
#define MP_FREERTOS_SLOT_PENDSV 1
177+
#define MP_FREERTOS_SLOT_MY_DRIVER 2
178+
```
179+
180+
2. **Schedule work** from ISR or task context:
181+
```c
182+
void my_driver_irq_handler(void) {
183+
// Set up data for callback...
184+
mp_freertos_service_schedule(MP_FREERTOS_SLOT_MY_DRIVER, my_deferred_handler);
185+
}
186+
```
187+
188+
3. **Implement the callback**:
189+
```c
190+
static void my_deferred_handler(void) {
191+
// Runs at high priority, but in task context (can call FreeRTOS APIs)
192+
// Process deferred work here
193+
}
194+
```
195+
196+
### ISR Context Detection
197+
198+
Ports must provide `mp_freertos_service_in_isr()` to detect interrupt context:
199+
200+
```c
201+
// Example for Cortex-M (in mphalport.c or similar)
202+
bool mp_freertos_service_in_isr(void) {
203+
uint32_t ipsr;
204+
__asm volatile ("mrs %0, ipsr" : "=r" (ipsr));
205+
return ipsr != 0;
206+
}
207+
```
208+
209+
This allows `mp_freertos_service_schedule()` to use the correct FreeRTOS API (`FromISR` variants in ISR context).
210+
211+
---
212+
213+
## Current Configuration (RP2)
214+
215+
### Key Settings
216+
217+
| Setting | Value | File |
218+
|---------|-------|------|
219+
| `MICROPY_PY_THREAD` | 1 | mpconfigport.h |
220+
| `MICROPY_PY_THREAD_GIL` | 0 | mpconfigport.h |
221+
| `MP_THREAD_CORE_AFFINITY` | tskNO_AFFINITY | mpthreadport.h |
222+
| `configNUMBER_OF_CORES` | 2 | FreeRTOSConfig.h |
223+
| FreeRTOS heap | 8KB | FreeRTOSConfig.h |
224+
| Main task stack | 8KB | main.c |
225+
226+
### Threading Semantics
227+
228+
With GIL=0 and true SMP:
229+
230+
- **Multiple Python threads execute in parallel on both cores**
231+
- **Mutable objects (dict, list, set, bytearray) are NOT thread-safe**
232+
- **Users MUST protect shared mutable objects with locks**
233+
234+
Check `sys.implementation._thread` at runtime:
235+
- `"GIL"` - GIL enabled, mutable objects implicitly protected
236+
- `"unsafe"` - GIL disabled, explicit locking required
237+
238+
---
239+
240+
## Usage Examples
241+
242+
### Safe Multi-threaded Access
243+
244+
```python
245+
import _thread
246+
import time
247+
248+
shared_data = {}
249+
lock = _thread.allocate_lock()
250+
251+
def worker(name, count):
252+
for i in range(count):
253+
with lock: # REQUIRED for GIL=0
254+
shared_data[f"{name}_{i}"] = i
255+
time.sleep(0.001)
256+
257+
# Start threads on potentially different cores
258+
_thread.start_new_thread(worker, ("A", 100))
259+
_thread.start_new_thread(worker, ("B", 100))
260+
```
261+
262+
### Checking Thread Safety Mode
263+
264+
```python
265+
import sys
266+
267+
if hasattr(sys.implementation, '_thread'):
268+
if sys.implementation._thread == "unsafe":
269+
print("GIL disabled - use locks for shared objects")
270+
else:
271+
print("GIL enabled - implicit protection")
272+
else:
273+
print("Threading not available")
274+
```
275+
276+
---
277+
278+
## Files Modified/Created
279+
280+
### New Files (extmod/freertos/)
281+
282+
| File | Lines | Purpose |
283+
|------|-------|---------|
284+
| mpthreadport.h | 136 | Thread API, data structures |
285+
| mpthreadport.c | ~400 | Thread lifecycle, GC integration |
286+
| mp_freertos_hal.h | 85 | HAL API declarations |
287+
| mp_freertos_hal.c | 125 | Delay, ticks, yield implementation |
288+
| mp_freertos_service.h | 129 | Service task API |
289+
| mp_freertos_service.c | 208 | Service task implementation |
290+
291+
### RP2 Port Modifications
292+
293+
| File | Changes |
294+
|------|---------|
295+
| CMakeLists.txt | FreeRTOS integration, conditional compilation |
296+
| FreeRTOSConfig.h | SMP configuration, tick rate, priorities |
297+
| freertos_hooks.c | Static allocation callbacks, stack overflow hook |
298+
| main.c | FreeRTOS task creation, scheduler startup |
299+
| mpconfigport.h | Threading config, backend selection |
300+
| mpthreadport_rp2.c | Atomic sections (FreeRTOS critical + IRQ disable) |
301+
| pendsv.c | PendSV wrapper for FreeRTOS coexistence |
302+
| mphalport.c | ISR detection, FreeRTOS-aware delays |
303+
304+
---
305+
306+
## Test Status
307+
308+
### Passing
309+
310+
- All 32 standard thread tests (on QEMU and hardware)
311+
- WiFi scan and connect (Pico W)
312+
- Soft timers via service task
313+
- GC under threading load
314+
- Concurrent dict access with explicit locks
315+
316+
### Known Limitations
317+
318+
- `mutate_dict` tests fail without locks (expected with GIL=0)
319+
- `disable_irq.py` skipped (incompatible with FreeRTOS)
320+
321+
---
322+
323+
## Future Work
324+
325+
1. **Service framework for USB/WiFi** - Move TinyUSB and CYW43 to dedicated high-priority tasks
326+
2. **STM32 port completion** - Finalize FreeRTOS integration for STM32F4/F7
327+
3. **mimxrt/nRF ports** - Apply same pattern to other FreeRTOS-capable ports
328+
4. **Thread pool API** - Higher-level threading primitives
329+
5. **Debug instrumentation removal** - Remove temporary debug counters from service task
330+
331+
---
332+
333+
## References
334+
335+
- `FREERTOS_THREADING_REQUIREMENTS.md` - Technical specification (v1.5)
336+
- `FREERTOS_IMPLEMENTATION_PLAN.md` - Phased implementation plan
337+
- `ports/rp2/FREERTOS_STATUS.md` - RP2-specific integration notes
338+
- FreeRTOS SMP Documentation: https://www.freertos.org/symmetric-multiprocessing-introduction.html

0 commit comments

Comments
 (0)