Skip to content

Commit e17b33e

Browse files
committed
ports/qemu: Add Zephyr threading support for MPS2-AN385.
Enable threading on QEMU ARM Cortex-M3 target using Zephyr kernel integration. Tested with 33/33 thread tests passing (disable_irq skipped as it requires hardware interrupt control). Key changes: - Makefile: Add Zephyr kernel build integration - main.c: Zephyr kernel initialization - mpconfigport.h: Thread configuration with GIL - zephyr_arch_qemu.c: QEMU-specific Zephyr architecture implementation - modtime.c: time.time() returning seconds since boot Build: make BOARD=MPS2_AN385 Test: make BOARD=MPS2_AN385 test Signed-off-by: Andrew Leech <[email protected]>
1 parent 14a5e32 commit e17b33e

File tree

11 files changed

+590
-2
lines changed

11 files changed

+590
-2
lines changed

ports/qemu/Makefile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,62 @@ MICROPY_FLOAT_IMPL ?= float
4646
include $(TOP)/py/py.mk
4747
include $(TOP)/extmod/extmod.mk
4848

49+
################################################################################
50+
# Zephyr Threading Integration (optional, enabled per-board)
51+
52+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
53+
54+
# Set architecture for Zephyr (only ARM Cortex-M supported currently)
55+
ZEPHYR_ARCH := arm
56+
57+
# Enable idle thread for k_msleep() support in EVENT_POLL_HOOK
58+
MICROPY_ZEPHYR_USE_IDLE_THREAD = 1
59+
60+
# Include Zephyr kernel build
61+
include $(TOP)/extmod/zephyr_kernel/zephyr_kernel.mk
62+
63+
# Add Zephyr includes and flags
64+
# ZEPHYR_CFLAGS includes -include of the config header, which is CRITICAL
65+
# because any file that includes <zephyr/kernel.h> needs the CONFIG_* defines
66+
CFLAGS += $(ZEPHYR_INC)
67+
CFLAGS += $(ZEPHYR_CFLAGS)
68+
# Define MICROPY_ZEPHYR_THREADING as a preprocessor symbol for C code
69+
CFLAGS += -DMICROPY_ZEPHYR_THREADING=1
70+
CFLAGS += -DMICROPY_ZEPHYR_USE_IDLE_THREAD=1
71+
72+
# Add QEMU-specific Zephyr architecture implementation
73+
SRC_C += zephyr_arch_qemu.c
74+
75+
# Ensure Zephyr objects depend on generated headers
76+
$(addprefix $(BUILD)/, $(SRC_THIRDPARTY_C:.c=.o)): $(ZEPHYR_GEN_HEADERS)
77+
78+
# Pattern-specific CFLAGS for Zephyr objects
79+
$(BUILD)/$(TOP)/lib/zephyr/%.o: CFLAGS += -include $(TOP)/extmod/zephyr_kernel/zephyr_config_cortex_m.h -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-return-type
80+
$(BUILD)/$(TOP)/extmod/zephyr_kernel/%.o: CFLAGS += -Wno-unused-parameter -Wno-unused-function
81+
82+
# Build Zephyr assembly files (context switching, PSP initialization)
83+
ZEPHYR_SWAP_HELPER_O = $(BUILD)/swap_helper.o
84+
ZEPHYR_PSP_SWITCH_O = $(BUILD)/zephyr_psp_switch.o
85+
OBJ += $(ZEPHYR_SWAP_HELPER_O)
86+
OBJ += $(ZEPHYR_PSP_SWITCH_O)
87+
OBJ += $(BUILD)/zephyr_offsets.o
88+
89+
# Common assembler flags for Zephyr .S files
90+
ZEPHYR_AS_FLAGS = -x assembler-with-cpp -D_ASMLANGUAGE \
91+
$(filter-out -std=% -Wdouble-promotion -Wfloat-conversion -Werror,$(CFLAGS)) \
92+
-include $(TOP)/extmod/zephyr_kernel/zephyr_config_cortex_m.h \
93+
-Wa,-mimplicit-it=thumb
94+
95+
$(ZEPHYR_SWAP_HELPER_O): $(TOP)/lib/zephyr/arch/arm/core/cortex_m/swap_helper.S $(ZEPHYR_GEN_HEADERS)
96+
$(ECHO) "CC $<"
97+
$(Q)$(CC) $(ZEPHYR_AS_FLAGS) -c -o $@ $<
98+
99+
$(ZEPHYR_PSP_SWITCH_O): $(TOP)/extmod/zephyr_kernel/zephyr_psp_switch.S
100+
$(ECHO) "CC $<"
101+
$(Q)$(CC) $(ZEPHYR_AS_FLAGS) -c -o $@ $<
102+
103+
endif # MICROPY_ZEPHYR_THREADING
104+
49105
GIT_SUBMODULES += lib/berkeley-db-1.xx
50106

51107
CFLAGS += -DMICROPY_HEAP_SIZE=$(MICROPY_HEAP_SIZE)
@@ -172,6 +228,11 @@ CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -We
172228
-ffunction-sections -fdata-sections
173229
CFLAGS += $(CFLAGS_EXTRA)
174230

231+
# Remove -Werror for Zephyr threading builds (Zephyr headers have many macro redefinitions)
232+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
233+
CFLAGS := $(filter-out -Werror,$(CFLAGS))
234+
endif
235+
175236
LDFLAGS += -T $(LDSCRIPT) -Wl,--gc-sections -Wl,-Map=$(@:.elf=.map)
176237

177238
# Debugging/Optimization
@@ -235,6 +296,8 @@ OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o))
235296

236297
# List of sources for qstr extraction
237298
SRC_QSTR += $(SRC_C) $(LIBM_SRC_C)
299+
# Add Zephyr MicroPython sources for root pointer scanning (e.g., mp_thread_list_head)
300+
SRC_QSTR += $(ZEPHYR_MP_SRC_C)
238301

239302
################################################################################
240303
# Main targets
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
QEMU_ARCH = arm
22
QEMU_MACHINE = mps2-an385
33

4+
# Enable Zephyr threading for this board
5+
MICROPY_ZEPHYR_THREADING = 1
6+
47
CFLAGS += -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
58
CFLAGS += -DQEMU_SOC_MPS2
69
CFLAGS += -DMICROPY_HW_MCU_NAME='"Cortex-M3"'
10+
# Cortex-M3 has no FPU - disable FPU in Zephyr config
11+
CFLAGS += -D__FPU_PRESENT=0
712

813
LDSCRIPT = mcu/arm/mps2.ld
914

1015
SRC_BOARD_O = shared/runtime/gchelper_native.o shared/runtime/gchelper_thumb2.o
1116

1217
MPY_CROSS_FLAGS += -march=armv7m
18+
19+
# Zephyr submodule required for threading
20+
ifeq ($(MICROPY_ZEPHYR_THREADING),1)
21+
GIT_SUBMODULES += lib/zephyr
22+
endif

ports/qemu/main.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,100 @@
3333
#include "shared/runtime/gchelper.h"
3434
#include "shared/runtime/pyexec.h"
3535

36+
#if MICROPY_ZEPHYR_THREADING
37+
#include <zephyr/kernel.h>
38+
#endif
39+
3640
#if MICROPY_HEAP_SIZE <= 0
3741
#error MICROPY_HEAP_SIZE must be a positive integer.
3842
#endif
3943

4044
static uint32_t gc_heap[MICROPY_HEAP_SIZE / sizeof(uint32_t)];
4145

46+
#if MICROPY_ZEPHYR_THREADING
47+
// Zephyr threading entry point - called by Zephyr kernel after z_cstart()
48+
// This function runs in z_main_thread context after kernel initialization
49+
void micropython_main_thread_entry(void *p1, void *p2, void *p3) {
50+
(void)p1;
51+
(void)p2;
52+
(void)p3;
53+
54+
// NOTE: We're now running in z_main_thread context, not boot/dummy context
55+
// This means k_thread_create() and other threading operations are safe
56+
57+
goto micropython_soft_reset;
58+
59+
micropython_soft_reset:
60+
// Threading early init - Phase 1 (set thread-local state FIRST)
61+
// Must be called before ANY code that accesses MP_STATE_THREAD()
62+
// This includes mp_cstack_init_with_top() and gc_init()
63+
if (!mp_thread_init_early()) {
64+
mp_printf(&mp_plat_print, "Failed to initialize threading (early phase)\n");
65+
for (;;) {}
66+
}
67+
68+
// Stack limit init
69+
// After zephyr_psp_init (called from Reset_Handler), main thread runs on
70+
// z_main_stack (via PSP). Get stack info from z_main_thread which was
71+
// initialized by prepare_multithreading() in zephyr_cstart.c.
72+
extern struct k_thread z_main_thread;
73+
char *stack_top = (char *)z_main_thread.stack_info.start + z_main_thread.stack_info.size;
74+
size_t stack_size = z_main_thread.stack_info.size;
75+
mp_cstack_init_with_top(stack_top, stack_size);
76+
77+
// GC init
78+
gc_init(gc_heap, (char *)gc_heap + sizeof(gc_heap));
79+
80+
// Threading init - Phase 2 (allocate main thread on GC heap)
81+
// Requires GC to be initialized for m_new_obj() heap allocation
82+
char stack_dummy;
83+
if (!mp_thread_init(&stack_dummy)) {
84+
mp_printf(&mp_plat_print, "Failed to initialize threading (phase 2)\n");
85+
for (;;) {}
86+
}
87+
88+
// Enable SysTick interrupt now that threading is fully initialized
89+
extern void mp_zephyr_arch_enable_systick_interrupt(void);
90+
mp_zephyr_arch_enable_systick_interrupt();
91+
92+
// MicroPython init
93+
mp_init();
94+
95+
// Run REPL loop
96+
for (;;) {
97+
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
98+
if (pyexec_raw_repl() != 0) {
99+
break;
100+
}
101+
} else {
102+
if (pyexec_friendly_repl() != 0) {
103+
break;
104+
}
105+
}
106+
}
107+
108+
mp_printf(&mp_plat_print, "MPY: soft reboot\n");
109+
110+
mp_thread_deinit();
111+
gc_sweep_all();
112+
mp_deinit();
113+
114+
goto micropython_soft_reset;
115+
}
116+
#endif // MICROPY_ZEPHYR_THREADING
117+
42118
int main(int argc, char **argv) {
119+
#if MICROPY_ZEPHYR_THREADING
120+
// Initialize Zephyr architecture layer (configures SysTick for Zephyr timing)
121+
extern void mp_zephyr_arch_init(void);
122+
mp_zephyr_arch_init();
123+
124+
// Transfer control to Zephyr kernel
125+
// z_cstart() initializes Zephyr kernel and never returns
126+
extern void z_cstart(void);
127+
z_cstart(); // Never returns - Zephyr takes over and calls micropython_main_thread_entry()
128+
#else
129+
// Non-threading build: simple main loop
43130
mp_cstack_init_with_sp_here(10240);
44131
gc_init(gc_heap, (char *)gc_heap + MICROPY_HEAP_SIZE);
45132

@@ -63,11 +150,17 @@ int main(int argc, char **argv) {
63150
gc_sweep_all();
64151
mp_deinit();
65152
}
153+
#endif
154+
155+
return 0;
66156
}
67157

68158
void gc_collect(void) {
69159
gc_collect_start();
70160
gc_helper_collect_regs_and_stack();
161+
#if MICROPY_ZEPHYR_THREADING
162+
mp_thread_gc_others();
163+
#endif
71164
gc_collect_end();
72165
}
73166

ports/qemu/mcu/arm/errorhandler.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,18 @@ __attribute__((naked)) MP_NORETURN void DebugMon_Handler(void) {
187187
exception_handler(DEBUG_MONITOR);
188188
}
189189

190-
__attribute__((naked)) MP_NORETURN void PendSV_Handler(void) {
190+
// Debug flag to trace PendSV entry (incremented in PendSV handler)
191+
volatile uint32_t pendsv_entry_count = 0;
192+
193+
__attribute__((naked)) void PendSV_Handler(void) {
194+
#if MICROPY_ZEPHYR_THREADING
195+
// Zephyr context switch - jump directly to z_arm_pendsv
196+
// DO NOT modify any registers or stack - z_arm_pendsv expects exact
197+
// exception entry state. Any register modification will corrupt the context switch.
198+
__asm volatile ("b z_arm_pendsv");
199+
#else
191200
exception_handler(PENDING_SV);
201+
#endif
192202
}
193203

194204
__attribute__((naked, weak)) MP_NORETURN void SysTick_Handler(void) {

ports/qemu/mcu/arm/startup.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss;
3737

38-
__attribute__((naked)) void Reset_Handler(void) {
38+
void Reset_Handler(void) {
3939
// set stack pointer
4040
__asm volatile ("ldr r0, =_estack");
4141
__asm volatile ("mov sp, r0");
@@ -53,6 +53,14 @@ __attribute__((naked)) void Reset_Handler(void) {
5353
__asm volatile ("dsb");
5454
__asm volatile ("isb");
5555
#endif
56+
#if MICROPY_ZEPHYR_THREADING
57+
// Switch to PSP for threading - must be done before main thread runs
58+
// zephyr_psp_init is provided by zephyr_psp_switch.S and sets up PSP
59+
// NOTE: Cannot call printf/semihosting here - it will fail because
60+
// heap/IO not initialized. Just trust the asm function works.
61+
extern void zephyr_psp_init(void);
62+
zephyr_psp_init();
63+
#endif
5664
// jump to board initialisation
5765
void _start(void);
5866
_start();

ports/qemu/mcu/arm/ticks.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626

2727
#include <stdint.h>
28+
#include "py/mpconfig.h"
2829

2930
// CPU frequency
3031
#ifndef CPU_FREQ_HZ
@@ -80,6 +81,18 @@ uintptr_t ticks_us(void) {
8081
#endif
8182
}
8283

84+
// Make this handler weak when Zephyr threading is enabled
85+
// so the Zephyr handler from cortex_m_arch.c takes precedence
86+
#if MICROPY_ZEPHYR_THREADING
87+
__attribute__((weak))
88+
#endif
8389
void SysTick_Handler(void) {
8490
_ticks_ms++;
8591
}
92+
93+
#if MICROPY_ZEPHYR_THREADING
94+
// Hook called by Zephyr's SysTick_Handler to maintain _ticks_ms counter
95+
void mp_zephyr_port_systick_hook(void) {
96+
_ticks_ms++;
97+
}
98+
#endif

ports/qemu/modtime.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2025 MicroPython Developers
7+
*
8+
* Time module implementation for QEMU bare-metal port.
9+
* Provides relative time since boot (no RTC/wall-clock).
10+
*/
11+
12+
#include "py/obj.h"
13+
14+
// External functions from ticks.c
15+
extern uintptr_t ticks_ms(void);
16+
extern uintptr_t ticks_us(void);
17+
18+
// Return the number of seconds since boot (not epoch - no RTC available).
19+
static mp_obj_t mp_time_time_get(void) {
20+
// Return seconds since boot as a float
21+
mp_float_t seconds = (mp_float_t)ticks_ms() / 1000.0f;
22+
return mp_obj_new_float(seconds);
23+
}

ports/qemu/mpconfigport.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
#define MICROPY_VFS (1)
6363
#define MICROPY_VFS_ROM (1)
6464
#define MICROPY_VFS_ROM_IOCTL (0)
65+
#define MICROPY_PY_TIME_TIME_TIME_NS (1)
66+
#define MICROPY_PY_TIME_INCLUDEFILE "ports/qemu/modtime.c"
6567

6668
// type definitions for the specific machine
6769

@@ -79,4 +81,36 @@ typedef long mp_off_t;
7981
// We need an implementation of the log2 function which is not a macro.
8082
#define MP_NEED_LOG2 (1)
8183

84+
// Zephyr threading configuration
85+
#if MICROPY_ZEPHYR_THREADING
86+
#define MICROPY_PY_THREAD (1)
87+
#define MICROPY_PY_THREAD_GIL (1)
88+
#define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32)
89+
90+
// Include Zephyr config for CONFIG_* symbols (doesn't require zephyr includes)
91+
#include "extmod/zephyr_kernel/zephyr_config_cortex_m.h"
92+
93+
// Note: mpthreadport.h is included via py/mpthread.h when needed in actual C code.
94+
// Do NOT include it here as it requires zephyr/kernel.h which isn't available
95+
// during QSTR preprocessing (QSTR_GEN_CFLAGS doesn't have Zephyr includes).
96+
97+
// Event poll hook with GIL release for cooperative scheduling
98+
#define MICROPY_EVENT_POLL_HOOK \
99+
do { \
100+
extern void mp_handle_pending(bool); \
101+
mp_handle_pending(true); \
102+
MP_THREAD_GIL_EXIT(); \
103+
extern void k_yield(void); \
104+
k_yield(); \
105+
MP_THREAD_GIL_ENTER(); \
106+
} while (0);
107+
108+
// GIL cooperative scheduling: The VM's GIL bounce code does:
109+
// MP_THREAD_GIL_EXIT(); MP_THREAD_GIL_ENTER();
110+
// Without a yield between unlock and lock, the same thread immediately
111+
// re-acquires the GIL before others can run. We enable k_yield() after
112+
// GIL unlock in mpthread_zephyr.c (MICROPY_THREAD_YIELD_AFTER_GIL_UNLOCK).
113+
#define MICROPY_THREAD_YIELD_AFTER_GIL_UNLOCK (1)
114+
#endif // MICROPY_ZEPHYR_THREADING
115+
82116
#define MP_STATE_PORT MP_STATE_VM

0 commit comments

Comments
 (0)