diff --git a/ports/unix/gccollect.c b/ports/unix/gccollect.c index 8f0f5a6769d3d..16579b0b59e24 100644 --- a/ports/unix/gccollect.c +++ b/ports/unix/gccollect.c @@ -31,6 +31,10 @@ #include "shared/runtime/gchelper.h" +#if MICROPY_PY_MACHINE_TIMER +#include "shared/runtime/softtimer.h" +#endif + #if MICROPY_ENABLE_GC void gc_collect(void) { @@ -39,6 +43,9 @@ void gc_collect(void) { #if MICROPY_PY_THREAD mp_thread_gc_others(); #endif + #if MICROPY_PY_MACHINE_TIMER + soft_timer_gc_mark_all(); + #endif gc_collect_end(); } diff --git a/ports/unix/main.c b/ports/unix/main.c index 9e9704aa801db..9b717a1004af3 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -57,6 +57,11 @@ #include "stack_size.h" #include "shared/runtime/pyexec.h" +#if MICROPY_PY_MACHINE_TIMER +#include "shared/runtime/softtimer.h" +void soft_timer_deinit_port(void); +#endif + // Command line options, with their defaults bool mp_compile_only = false; static uint emit_opt = MP_EMIT_OPT_NONE; @@ -495,6 +500,10 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_init(); + #if MICROPY_PY_MACHINE_TIMER + soft_timer_init(); + #endif + #if MICROPY_EMIT_NATIVE // Set default emitter options MP_STATE_VM(default_emit_opt) = emit_opt; @@ -730,6 +739,10 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_bluetooth_deinit(); #endif + #if MICROPY_PY_MACHINE_TIMER + soft_timer_deinit_port(); + #endif + #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/unix/variants/zephyr_ble/manifest.py b/ports/unix/variants/zephyr_ble/manifest.py new file mode 100644 index 0000000000000..d27bf3c47fe92 --- /dev/null +++ b/ports/unix/variants/zephyr_ble/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/variants/manifest.py") + +include("$(MPY_DIR)/extmod/asyncio") diff --git a/ports/unix/variants/zephyr_ble/mpconfigvariant.h b/ports/unix/variants/zephyr_ble/mpconfigvariant.h new file mode 100644 index 0000000000000..9007651ac94e4 --- /dev/null +++ b/ports/unix/variants/zephyr_ble/mpconfigvariant.h @@ -0,0 +1,65 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Set base feature level. +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) + +// Enable extra Unix features. +#include "../mpconfigvariant_common.h" + +// Soft timer support (pthread-based backend). +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_SCHEDULER_STATIC_NODES (1) + +// PendSV-equivalent mutex for soft timer thread safety. +// soft_timer_handler() runs in the timer thread; PENDSV_ENTER/EXIT +// protect the timer heap from concurrent access by the main thread. +// Must be recursive: soft timer C callbacks may call soft_timer_insert(). +extern void mp_unix_pendsv_enter(void); +extern void mp_unix_pendsv_exit(void); +#define MICROPY_PY_PENDSV_ENTER mp_unix_pendsv_enter(); +#define MICROPY_PY_PENDSV_EXIT mp_unix_pendsv_exit(); + +// Register machine.Timer. +extern const struct _mp_obj_type_t machine_timer_type; +#define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, + +// When a callback is scheduled from the timer thread, wake the main thread +// so that blocking calls (e.g. time.sleep -> select) return via EINTR and +// process the pending callback promptly. +extern void mp_unix_wake_main_thread(void); +#define MICROPY_SCHED_HOOK_SCHEDULED mp_unix_wake_main_thread(); + +// Zephyr BLE configuration (for future use -- define here so variant is ready). +// Uncomment when the Zephyr BLE unix port implementation is available. +// #define MICROPY_PY_BLUETOOTH (1) +// #define MICROPY_BLUETOOTH_ZEPHYR (1) +// #define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (1) +// #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) +// #define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (1) +// #define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) +// #define MICROPY_BLUETOOTH_ZEPHYR_GATT_POOL (0) diff --git a/ports/unix/variants/zephyr_ble/mpconfigvariant.mk b/ports/unix/variants/zephyr_ble/mpconfigvariant.mk new file mode 100644 index 0000000000000..ea0589f40b56b --- /dev/null +++ b/ports/unix/variants/zephyr_ble/mpconfigvariant.mk @@ -0,0 +1,11 @@ +# Zephyr BLE variant for Unix port. +# Provides soft timer, machine.Timer, and (future) Zephyr BLE stack. + +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py + +# Soft timer backend (shared softtimer.c framework) and IRQ dispatch. +SHARED_SRC_C_EXTRA += runtime/softtimer.c +SHARED_SRC_C_EXTRA += runtime/mpirq.c + +# Link with pthread for the timer thread. +LDFLAGS += $(LIBPTHREAD) diff --git a/ports/unix/variants/zephyr_ble/softtimer_unix.c b/ports/unix/variants/zephyr_ble/softtimer_unix.c new file mode 100644 index 0000000000000..a5fec5c4aa0b4 --- /dev/null +++ b/ports/unix/variants/zephyr_ble/softtimer_unix.c @@ -0,0 +1,179 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Soft timer backend for Unix port using pthread + condition variable. +// +// The shared softtimer.c framework calls: +// soft_timer_get_ms() -- return current ms tick +// soft_timer_schedule_at_ms(ticks_ms) -- wake timer thread at given time +// +// soft_timer_handler() runs in the timer thread context (PendSV-equivalent). +// MICROPY_PY_PENDSV_ENTER/EXIT protect the timer heap via recursive mutex +// shared between the timer thread and the main thread. + +#include +#include +#include +#include +#include + +#include "py/runtime.h" +#include "shared/runtime/softtimer.h" + +static pthread_t main_thread; +static pthread_t timer_thread; +static pthread_mutex_t timer_mutex; // recursive, shared with PENDSV macros +static pthread_cond_t timer_cond; +static volatile bool timer_running; +static volatile uint32_t timer_target_ms; +static volatile bool timer_scheduled; + +// --- Port API for softtimer.c --- + +uint32_t soft_timer_get_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); +} + +void soft_timer_schedule_at_ms(uint32_t ticks_ms) { + pthread_mutex_lock(&timer_mutex); + timer_target_ms = ticks_ms; + timer_scheduled = true; + pthread_cond_signal(&timer_cond); + pthread_mutex_unlock(&timer_mutex); +} + +// --- Timer Thread --- + +static void *timer_thread_func(void *arg) { + (void)arg; + pthread_mutex_lock(&timer_mutex); + while (timer_running) { + if (!timer_scheduled) { + // No timer pending -- wait indefinitely for signal. + pthread_cond_wait(&timer_cond, &timer_mutex); + continue; + } + + // Calculate delay until target time. + uint32_t now = soft_timer_get_ms(); + int32_t delay = (int32_t)(timer_target_ms - now); + + if (delay > 0) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += delay / 1000; + ts.tv_nsec += (long)(delay % 1000) * 1000000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + int ret = pthread_cond_timedwait(&timer_cond, &timer_mutex, &ts); + if (ret == 0) { + // Signaled -- re-evaluate (target may have changed). + continue; + } + // ETIMEDOUT -- fall through to fire handler. + } + + // Timer expired -- fire handler. + timer_scheduled = false; + // soft_timer_handler modifies the heap, protected by this mutex + // (same mutex as PENDSV_ENTER/EXIT). + soft_timer_handler(); + } + pthread_mutex_unlock(&timer_mutex); + return NULL; +} + +// --- Init/Deinit --- + +// Empty signal handler -- the signal's only purpose is to interrupt blocking +// syscalls (select, poll, etc.) with EINTR so the main thread re-checks +// pending scheduled callbacks. +static void sigusr1_handler(int sig) { + (void)sig; +} + +void soft_timer_init(void) { + main_thread = pthread_self(); + + // Install SIGUSR1 handler to interrupt blocking syscalls. + struct sigaction sa; + sa.sa_handler = sigusr1_handler; + sa.sa_flags = 0; // No SA_RESTART -- we want EINTR. + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, NULL); + + // Initialize recursive mutex (PENDSV_ENTER may be called from callbacks + // that already hold this mutex via the timer thread). + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&timer_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_cond_init(&timer_cond, NULL); + timer_running = true; + timer_scheduled = false; + pthread_create(&timer_thread, NULL, timer_thread_func, NULL); +} + +// Called during interpreter shutdown to stop the timer thread. +void soft_timer_deinit_port(void) { + if (!timer_running) { + return; + } + pthread_mutex_lock(&timer_mutex); + timer_running = false; + pthread_cond_signal(&timer_cond); + pthread_mutex_unlock(&timer_mutex); + pthread_join(timer_thread, NULL); + pthread_mutex_destroy(&timer_mutex); + pthread_cond_destroy(&timer_cond); +} + +// --- PENDSV mutex (called from MICROPY_PY_PENDSV_ENTER/EXIT macros) --- + +void mp_unix_pendsv_enter(void) { + pthread_mutex_lock(&timer_mutex); +} + +void mp_unix_pendsv_exit(void) { + pthread_mutex_unlock(&timer_mutex); +} + +// --- Main thread wake (called from MICROPY_SCHED_HOOK_SCHEDULED) --- + +void mp_unix_wake_main_thread(void) { + // Send SIGUSR1 to the main thread to interrupt any blocking syscall + // (e.g. select() in time.sleep()) so it processes scheduled callbacks. + // No-op if called from the main thread itself. + if (!pthread_equal(pthread_self(), main_thread)) { + pthread_kill(main_thread, SIGUSR1); + } +}