From d092ee84808945afa39bee010e00baa70bad7c9e Mon Sep 17 00:00:00 2001 From: tonyzhangnxp Date: Tue, 27 Feb 2024 17:32:20 +0800 Subject: [PATCH] ports/mimxrt: Add thread support. Signed-off-by: tonyzhangnxp --- ports/mimxrt/Makefile | 4 + .../boards/ADAFRUIT_METRO_M7/mpconfigboard.mk | 2 + .../boards/MIMXRT1010_EVK/mpconfigboard.mk | 2 + .../boards/MIMXRT1015_EVK/mpconfigboard.mk | 2 + .../boards/MIMXRT1170_EVK/mpconfigboard.h | 3 + .../boards/MIMXRT1170_EVK/mpconfigboard.mk | 2 + .../boards/OLIMEX_RT1010/mpconfigboard.mk | 1 + ports/mimxrt/gccollect.c | 50 ++++ ports/mimxrt/gccollect.h | 44 ++++ ports/mimxrt/main.c | 19 +- ports/mimxrt/mpconfigport.h | 27 ++ ports/mimxrt/mphalport.c | 20 +- ports/mimxrt/mphalport.h | 2 +- ports/mimxrt/mpthreadport.c | 99 ++++++++ ports/mimxrt/mpthreadport.h | 53 ++++ ports/mimxrt/pendsv.c | 105 +++++++- ports/mimxrt/pybthread.c | 238 ++++++++++++++++++ ports/mimxrt/pybthread.h | 79 ++++++ ports/mimxrt/systick.c | 12 + 19 files changed, 754 insertions(+), 10 deletions(-) create mode 100644 ports/mimxrt/gccollect.c create mode 100644 ports/mimxrt/gccollect.h create mode 100644 ports/mimxrt/mpthreadport.c create mode 100644 ports/mimxrt/mpthreadport.h create mode 100644 ports/mimxrt/pybthread.c create mode 100644 ports/mimxrt/pybthread.h diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index dd1f5aa7c6..c606da04bd 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -215,6 +215,10 @@ SRC_C += \ systick.c \ ticks.c \ tusb_port.c \ + mpthreadport.c \ + pybthread.c\ + gccollect.c\ + SHARED_SRC_C += \ shared/libc/printf.c \ diff --git a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk index a8f7add6d2..e60a5de5d0 100644 --- a/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk +++ b/ports/mimxrt/boards/ADAFRUIT_METRO_M7/mpconfigboard.mk @@ -8,3 +8,5 @@ MICROPY_HW_FLASH_SIZE ?= 0x800000 # 8MB MICROPY_PY_NETWORK_NINAW10 ?= 1 MICROPY_PY_SSL ?= 1 MICROPY_SSL_MBEDTLS ?= 1 + +CFLAGS += -DMICROPY_PY_THREAD=0 \ No newline at end of file diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk index dd52585906..df3e1957e9 100644 --- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.mk @@ -14,6 +14,8 @@ else JLINK_CONNECTION_SETTINGS = -USB endif +CFLAGS += -DMICROPY_PY_THREAD=0 + deploy_jlink: $(BUILD)/firmware.hex $(Q)$(TOUCH) $(JLINK_COMMANDER_SCRIPT) $(ECHO) "ExitOnError 1" > $(JLINK_COMMANDER_SCRIPT) diff --git a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk index 34e5cdee51..133e0b664b 100644 --- a/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1015_EVK/mpconfigboard.mk @@ -6,3 +6,5 @@ MICROPY_HW_FLASH_TYPE = qspi_nor_flash MICROPY_HW_FLASH_SIZE = 0x1000000 # 16MB MICROPY_BOOT_BUFFER_SIZE = (32 * 1024) + +CFLAGS += -DMICROPY_PY_THREAD=0 \ No newline at end of file diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h index 131c5e1725..4ae0f5246f 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h @@ -3,11 +3,14 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-1070evk" +#if MICROPY_PY_THREAD +#else #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(bool); \ mp_handle_pending(true); \ } while (0); +#endif // MIMXRT1170_EVK has 2 user LEDs #define MICROPY_HW_LED1_PIN (pin_GPIO_AD_04) diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk index 1488730f7c..963e2448b6 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.mk @@ -14,6 +14,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +CFLAGS += -DMICROPY_PY_THREAD=1 + FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py CFLAGS += -DCPU_MIMXRT1176DVMAA_cm7 \ diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk index 58429d2981..fa5d2686c6 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk +++ b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.mk @@ -7,6 +7,7 @@ MICROPY_HW_FLASH_SIZE = 0x200000 # 2MB MICROPY_HW_FLASH_RESERVED ?= 0x1000 # 4KB CFLAGS += -DMICROPY_HW_FLASH_DQS=kFlexSPIReadSampleClk_LoopbackInternally +CFLAGS += -DMICROPY_PY_THREAD=0 SRC_C += \ hal/flexspi_nor_flash.c \ diff --git a/ports/mimxrt/gccollect.c b/ports/mimxrt/gccollect.c new file mode 100644 index 0000000000..083642bce3 --- /dev/null +++ b/ports/mimxrt/gccollect.c @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * 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. + */ + +#include "py/gc.h" +#include "py/mpthread.h" +#include "shared/runtime/gchelper.h" +#include "shared/runtime/softtimer.h" +#include "gccollect.h" + +void gc_collect(void) { + // start the GC + gc_collect_start(); + + // trace the stack and registers + gc_helper_collect_regs_and_stack(); + + // trace root pointers from any threads + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif + + // trace soft timer nodes + soft_timer_gc_mark_all(); + + // end the GC + gc_collect_end(); +} diff --git a/ports/mimxrt/gccollect.h b/ports/mimxrt/gccollect.h new file mode 100644 index 0000000000..4992a96897 --- /dev/null +++ b/ports/mimxrt/gccollect.h @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32_GCCOLLECT_H +#define MICROPY_INCLUDED_STM32_GCCOLLECT_H + +// variables defining memory layout +// (these probably belong somewhere else...) +extern uint32_t _etext; +extern uint32_t _sidata; +extern uint32_t _ram_start; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern uint32_t _heap_start; +extern uint32_t _heap_end; +extern uint32_t _sstack; +extern uint32_t _estack; +extern uint32_t _ram_end; + +#endif // MICROPY_INCLUDED_STM32_GCCOLLECT_H diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 761c491742..066693ef12 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -56,6 +56,10 @@ #include "systick.h" #include "extmod/modnetwork.h" +#if MICROPY_PY_THREAD +static pyb_thread_t pyb_thread_main; +#endif + extern uint8_t _sstack, _estack, _gc_heap_start, _gc_heap_end; void board_init(void); @@ -64,7 +68,9 @@ int main(void) { board_init(); ticks_init(); pendsv_init(); - + #if MICROPY_PY_THREAD + pyb_thread_init(&pyb_thread_main); + #endif #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function // because the system timeout list (next_timeout) is only ever reset by BSS clearing. @@ -96,6 +102,11 @@ int main(void) { led_init(); #endif + // Python threading init + #if MICROPY_PY_THREAD + mp_thread_init(); + #endif + mp_stack_set_top(&_estack); mp_stack_set_limit(&_estack - &_sstack - 1024); @@ -163,12 +174,6 @@ int main(void) { return 0; } -void gc_collect(void) { - gc_collect_start(); - gc_helper_collect_regs_and_stack(); - gc_collect_end(); -} - void nlr_jump_fail(void *val) { for (;;) { } diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index c67b010c01..18ee8db3b4 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -134,6 +134,11 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) // #define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) +#ifndef MICROPY_PY_THREAD +#define MICROPY_PY_THREAD (1) +#endif + + #ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) #endif @@ -182,6 +187,24 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type; #define MP_STATE_PORT MP_STATE_VM +#if MICROPY_PY_THREAD + +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + if (pyb_thread_enabled) { \ + MP_THREAD_GIL_EXIT(); \ + pyb_thread_yield(); \ + MP_THREAD_GIL_ENTER(); \ + } else { \ + __WFE(); \ + } \ + } while (0); + +#define MICROPY_THREAD_YIELD() pyb_thread_yield() + +#else // Miscellaneous settings #ifndef MICROPY_EVENT_POLL_HOOK #define MICROPY_EVENT_POLL_HOOK \ @@ -192,6 +215,10 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type; } while (0); #endif +#define MICROPY_THREAD_YIELD() + +#endif + #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #define MP_HAL_CLEANINVALIDATE_DCACHE(addr, size) \ diff --git a/ports/mimxrt/mphalport.c b/ports/mimxrt/mphalport.c index be4b1d8b5c..28f7fb460a 100644 --- a/ports/mimxrt/mphalport.c +++ b/ports/mimxrt/mphalport.c @@ -34,7 +34,7 @@ #include "ticks.h" #include "tusb.h" #include "fsl_snvs_lp.h" - +#include "led.h" #ifndef MICROPY_HW_STDIN_BUFFER_LEN #define MICROPY_HW_STDIN_BUFFER_LEN 512 #endif @@ -112,6 +112,24 @@ int mp_hal_stdin_rx_chr(void) { } } +NORETURN void boardctrl_fatal_error(const char *msg) { + for (volatile uint delay = 0; delay < 10000000; delay++) { + } + led_state(1, 1); + + mp_hal_stdout_tx_strn("\nFATAL ERROR:\n", 14); + mp_hal_stdout_tx_strn(msg, strlen(msg)); + for (uint i = 0;;) { + led_toggle(1); + for (volatile uint delay = 0; delay < 10000000; delay++) { + } + if (i >= 16) { + // to conserve power + __WFI(); + } + } +} + mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { mp_uint_t ret = len; bool did_write = false; diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index 7f5a228fad..f02d51bfc9 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -154,5 +154,5 @@ void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac(int idx, uint8_t buf[6]); void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); void mp_hal_get_unique_id(uint8_t id[]); - +NORETURN void boardctrl_fatal_error(const char *msg); #endif // MICROPY_INCLUDED_MIMXRT_MPHALPORT_H diff --git a/ports/mimxrt/mpthreadport.c b/ports/mimxrt/mpthreadport.c new file mode 100644 index 0000000000..621b4311bf --- /dev/null +++ b/ports/mimxrt/mpthreadport.c @@ -0,0 +1,99 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * 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. + */ + +#include + +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mpthread.h" +#include "gccollect.h" + +#if MICROPY_PY_THREAD + +// the mutex controls access to the linked list +static mp_thread_mutex_t thread_mutex; + +void mp_thread_init(void) { + mp_thread_mutex_init(&thread_mutex); + mp_thread_set_state(&mp_state_ctx.thread); +} + +void mp_thread_gc_others(void) { + mp_thread_mutex_lock(&thread_mutex, 1); + for (pyb_thread_t *th = pyb_thread_all; th != NULL; th = th->all_next) { + gc_collect_root((void **)&th, 1); + gc_collect_root(&th->arg, 1); + gc_collect_root(&th->stack, 1); + if (th != pyb_thread_cur) { + gc_collect_root(th->stack, th->stack_len); + } + } + mp_thread_mutex_unlock(&thread_mutex); +} + +mp_uint_t mp_thread_get_id(void) { + return (uint32_t)pyb_thread_cur; +} + +mp_uint_t mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { + if (*stack_size == 0) { + *stack_size = 4096; // default stack size + } else if (*stack_size < 2048) { + *stack_size = 2048; // minimum stack size + } + + // round stack size to a multiple of the word size + size_t stack_len = *stack_size / sizeof(uint32_t); + *stack_size = stack_len * sizeof(uint32_t); + + // allocate stack and linked-list node (must be done outside thread_mutex lock) + uint32_t *stack = m_new(uint32_t, stack_len); + pyb_thread_t *th = m_new_obj(pyb_thread_t); + + mp_thread_mutex_lock(&thread_mutex, 1); + + // create thread + uint32_t id = pyb_thread_new(th, stack, stack_len, entry, arg); + if (id == 0) { + mp_thread_mutex_unlock(&thread_mutex); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread")); + } + + mp_thread_mutex_unlock(&thread_mutex); + + // adjust stack_size to provide room to recover from hitting the limit + *stack_size -= 1024; + + return id; +} + +void mp_thread_start(void) { +} + +void mp_thread_finish(void) { +} + +#endif // MICROPY_PY_THREAD diff --git a/ports/mimxrt/mpthreadport.h b/ports/mimxrt/mpthreadport.h new file mode 100644 index 0000000000..e2b39979fb --- /dev/null +++ b/ports/mimxrt/mpthreadport.h @@ -0,0 +1,53 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * 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. + */ + +#include "py/mpthread.h" +#include "pybthread.h" + +typedef pyb_mutex_t mp_thread_mutex_t; + +void mp_thread_init(void); +void mp_thread_gc_others(void); + +static inline void mp_thread_set_state(struct _mp_state_thread_t *state) { + pyb_thread_set_local(state); +} + +static inline struct _mp_state_thread_t *mp_thread_get_state(void) { + return pyb_thread_get_local(); +} + +static inline void mp_thread_mutex_init(mp_thread_mutex_t *m) { + pyb_mutex_init(m); +} + +static inline int mp_thread_mutex_lock(mp_thread_mutex_t *m, int wait) { + return pyb_mutex_lock(m, wait); +} + +static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *m) { + pyb_mutex_unlock(m); +} diff --git a/ports/mimxrt/pendsv.c b/ports/mimxrt/pendsv.c index 46d13a5aca..4ae17fea67 100644 --- a/ports/mimxrt/pendsv.c +++ b/ports/mimxrt/pendsv.c @@ -30,11 +30,17 @@ #include "pendsv.h" #include "irq.h" +void *pendsv_object; + #if defined(PENDSV_DISPATCH_NUM_SLOTS) +uint32_t pendsv_dispatch_active; pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; #endif void pendsv_init(void) { + #if defined(PENDSV_DISPATCH_NUM_SLOTS) + pendsv_dispatch_active = false; + #endif // set PendSV interrupt at lowest priority NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); } @@ -42,10 +48,11 @@ void pendsv_init(void) { #if defined(PENDSV_DISPATCH_NUM_SLOTS) void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { pendsv_dispatch_table[slot] = f; + pendsv_dispatch_active = true; SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; } -void PendSV_Handler(void) { +void pendsv_dispatch_handler(void) { for (size_t i = 0; i < PENDSV_DISPATCH_NUM_SLOTS; ++i) { if (pendsv_dispatch_table[i] != NULL) { pendsv_dispatch_t f = pendsv_dispatch_table[i]; @@ -55,3 +62,99 @@ void PendSV_Handler(void) { } } #endif + +__attribute__((naked)) void PendSV_Handler(void) { + // Handle a PendSV interrupt + // + // For the case of an asynchronous exception, re-jig the + // stack so that when we return from this interrupt handler + // it returns instead to nlr_jump with argument pendsv_object + // note that stack has a different layout if DEBUG is enabled + // + // For the case of a thread switch, swap stacks. + // + // on entry to this (naked) function, stack has the following layout: + // + // stack layout with DEBUG disabled: + // sp[6]: pc=r15 + // sp[5]: lr=r14 + // sp[4]: r12 + // sp[3]: r3 + // sp[2]: r2 + // sp[1]: r1 + // sp[0]: r0 + // + // stack layout with DEBUG enabled: + // sp[8]: pc=r15 + // sp[7]: lr=r14 + // sp[6]: r12 + // sp[5]: r3 + // sp[4]: r2 + // sp[3]: r1 + // sp[2]: r0 + // sp[1]: 0xfffffff9 + // sp[0]: ? + + __asm volatile ( + #if defined(PENDSV_DISPATCH_NUM_SLOTS) + // Check if there are any pending calls to dispatch to + "ldr r1, pendsv_dispatch_active_ptr\n" + "ldr r0, [r1]\n" + "cmp r0, #0\n" + "beq .no_dispatch\n" + "mov r2, #0\n" + "str r2, [r1]\n" // clear pendsv_dispatch_active + "b pendsv_dispatch_handler\n" // jump to the handler + ".no_dispatch:\n" + #endif + + // Check if there is an active object to throw via nlr_jump + "ldr r1, pendsv_object_ptr\n" + "ldr r0, [r1]\n" + "cmp r0, #0\n" + "beq .no_obj\n" + #if defined(PENDSV_DEBUG) + "str r0, [sp, #8]\n" // store to r0 on stack + #else + "str r0, [sp, #0]\n" // store to r0 on stack + #endif + "mov r0, #0\n" + "str r0, [r1]\n" // clear pendsv_object + "ldr r0, nlr_jump_ptr\n" + #if defined(PENDSV_DEBUG) + "str r0, [sp, #32]\n" // store to pc on stack + #else + "str r0, [sp, #24]\n" // store to pc on stack + #endif + "bx lr\n" // return from interrupt; will return to nlr_jump + ".no_obj:\n" // pendsv_object==NULL + + #if MICROPY_PY_THREAD + // Do a thread context switch + "push {r4-r11, lr}\n" + "vpush {s16-s31}\n" + "mrs r5, primask\n" // save PRIMASK in r5 + "cpsid i\n" // disable interrupts while we change stacks + "mov r0, sp\n" // pass sp to save + "mov r4, lr\n" // save lr because we are making a call + "bl pyb_thread_next\n" // get next thread to execute + "mov lr, r4\n" // restore lr + "mov sp, r0\n" // switch stacks + "msr primask, r5\n" // re-enable interrupts + "vpop {s16-s31}\n" + "pop {r4-r11, lr}\n" + "bx lr\n" // return from interrupt; will return to new thread + #else + // Spurious pendsv, just return + "bx lr\n" + #endif + + // Data + ".align 2\n" + #if defined(PENDSV_DISPATCH_NUM_SLOTS) + "pendsv_dispatch_active_ptr: .word pendsv_dispatch_active\n" + #endif + "pendsv_object_ptr: .word pendsv_object\n" + "nlr_jump_ptr: .word nlr_jump\n" + ); +} diff --git a/ports/mimxrt/pybthread.c b/ports/mimxrt/pybthread.c new file mode 100644 index 0000000000..bcf0dc21fe --- /dev/null +++ b/ports/mimxrt/pybthread.c @@ -0,0 +1,238 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Damien P. George + * + * 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. + */ + +#include "py/obj.h" + +#include "gccollect.h" +#include "irq.h" +#include "pybthread.h" +#include "mphalport.h" + +#if MICROPY_PY_THREAD + +#define PYB_MUTEX_UNLOCKED ((void *)0) +#define PYB_MUTEX_LOCKED ((void *)1) + +#ifndef MICROPY_BOARD_FATAL_ERROR +#define MICROPY_BOARD_FATAL_ERROR boardctrl_fatal_error +#endif + +// These macros are used when we only need to protect against a thread +// switch; other interrupts are still allowed to proceed. +#define RAISE_IRQ_PRI() raise_irq_pri(IRQ_PRI_PENDSV) +#define RESTORE_IRQ_PRI(state) restore_irq_pri(state) + +volatile int pyb_thread_enabled; +pyb_thread_t *volatile pyb_thread_all; +pyb_thread_t *volatile pyb_thread_cur; + +static inline void pyb_thread_add_to_runable(pyb_thread_t *thread) { + thread->run_prev = pyb_thread_cur->run_prev; + thread->run_next = pyb_thread_cur; + pyb_thread_cur->run_prev->run_next = thread; + pyb_thread_cur->run_prev = thread; +} + +static inline void pyb_thread_remove_from_runable(pyb_thread_t *thread) { + if (thread->run_next == thread) { + MICROPY_BOARD_FATAL_ERROR("deadlock"); + } + thread->run_prev->run_next = thread->run_next; + thread->run_next->run_prev = thread->run_prev; +} + +void pyb_thread_init(pyb_thread_t *thread) { + pyb_thread_enabled = 0; + pyb_thread_all = thread; + pyb_thread_cur = thread; + thread->sp = NULL; // will be set when this thread switches out + thread->local_state = 0; // will be set by mp_thread_init + thread->arg = NULL; + thread->stack = &_sstack; + thread->stack_len = ((uint32_t)&_estack - (uint32_t)&_sstack) / sizeof(uint32_t); + thread->all_next = NULL; + thread->run_prev = thread; + thread->run_next = thread; + thread->queue_next = NULL; +} + +void pyb_thread_deinit() { + uint32_t irq_state = disable_irq(); + pyb_thread_enabled = 0; + pyb_thread_all = pyb_thread_cur; + pyb_thread_cur->all_next = NULL; + pyb_thread_cur->run_prev = pyb_thread_cur; + pyb_thread_cur->run_next = pyb_thread_cur; + enable_irq(irq_state); +} + +static void pyb_thread_terminate(void) { + uint32_t irq_state = disable_irq(); + pyb_thread_t *thread = pyb_thread_cur; + // take current thread off the run list + pyb_thread_remove_from_runable(thread); + // take current thread off the list of all threads + for (pyb_thread_t **n = (pyb_thread_t **)&pyb_thread_all;; n = &(*n)->all_next) { + if (*n == thread) { + *n = thread->all_next; + break; + } + } + // clean pointers as much as possible to help GC + thread->all_next = NULL; + thread->queue_next = NULL; + thread->stack = NULL; + if (pyb_thread_all->all_next == NULL) { + // only 1 thread left + pyb_thread_enabled = 0; + } + // thread switch will occur after we enable irqs + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + enable_irq(irq_state); + // should not return + MICROPY_BOARD_FATAL_ERROR("could not terminate"); +} + +uint32_t pyb_thread_new(pyb_thread_t *thread, void *stack, size_t stack_len, void *entry, void *arg) { + uint32_t *stack_top = (uint32_t *)stack + stack_len; // stack is full descending + *--stack_top = 0x01000000; // xPSR (thumb bit set) + *--stack_top = (uint32_t)entry & 0xfffffffe; // pc (must have bit 0 cleared, even for thumb code) + *--stack_top = (uint32_t)pyb_thread_terminate; // lr + *--stack_top = 0; // r12 + *--stack_top = 0; // r3 + *--stack_top = 0; // r2 + *--stack_top = 0; // r1 + *--stack_top = (uint32_t)arg; // r0 + *--stack_top = 0xfffffff9; // lr (return to thread mode, non-FP, use MSP) + stack_top -= 8; // r4-r11 + stack_top -= 16; // s16-s31 (we assume all threads use FP registers) + thread->sp = stack_top; + thread->local_state = 0; + thread->arg = arg; + thread->stack = stack; + thread->stack_len = stack_len; + thread->queue_next = NULL; + uint32_t irq_state = disable_irq(); + pyb_thread_enabled = 1; + thread->all_next = pyb_thread_all; + pyb_thread_all = thread; + pyb_thread_add_to_runable(thread); + enable_irq(irq_state); + return (uint32_t)thread; // success +} + +void pyb_thread_dump(const mp_print_t *print) { + if (!pyb_thread_enabled) { + mp_printf(print, "THREAD: only main thread\n"); + } else { + mp_printf(print, "THREAD:\n"); + for (pyb_thread_t *th = pyb_thread_all; th != NULL; th = th->all_next) { + bool runable = false; + for (pyb_thread_t *th2 = pyb_thread_cur;; th2 = th2->run_next) { + if (th == th2) { + runable = true; + break; + } + if (th2->run_next == pyb_thread_cur) { + break; + } + } + mp_printf(print, " id=%p sp=%p sz=%u", th, th->stack, th->stack_len); + if (runable) { + mp_printf(print, " (runable)"); + } + mp_printf(print, "\n"); + } + } +} + +// should only be called from pendsv_isr_handler +void *pyb_thread_next(void *sp) { + pyb_thread_cur->sp = sp; + pyb_thread_cur = pyb_thread_cur->run_next; + pyb_thread_cur->timeslice = 4; // in milliseconds + return pyb_thread_cur->sp; +} + +void pyb_mutex_init(pyb_mutex_t *m) { + *m = PYB_MUTEX_UNLOCKED; +} + +int pyb_mutex_lock(pyb_mutex_t *m, int wait) { + uint32_t irq_state = RAISE_IRQ_PRI(); + if (*m == PYB_MUTEX_UNLOCKED) { + // mutex is available + *m = PYB_MUTEX_LOCKED; + RESTORE_IRQ_PRI(irq_state); + } else { + // mutex is locked + if (!wait) { + RESTORE_IRQ_PRI(irq_state); + return 0; // failed to lock mutex + } + if (*m == PYB_MUTEX_LOCKED) { + *m = pyb_thread_cur; + } else { + for (pyb_thread_t *n = *m;; n = n->queue_next) { + if (n->queue_next == NULL) { + n->queue_next = pyb_thread_cur; + break; + } + } + } + pyb_thread_cur->queue_next = NULL; + // take current thread off the run list + pyb_thread_remove_from_runable(pyb_thread_cur); + // thread switch will occur after we enable irqs + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + RESTORE_IRQ_PRI(irq_state); + // when we come back we have the mutex + } + return 1; // have mutex +} + +void pyb_mutex_unlock(pyb_mutex_t *m) { + uint32_t irq_state = RAISE_IRQ_PRI(); + if (*m == PYB_MUTEX_LOCKED) { + // no threads are blocked on the mutex + *m = PYB_MUTEX_UNLOCKED; + } else { + // at least one thread is blocked on this mutex + pyb_thread_t *th = *m; + if (th->queue_next == NULL) { + // no other threads are blocked + *m = PYB_MUTEX_LOCKED; + } else { + // at least one other thread is still blocked + *m = th->queue_next; + } + // put unblocked thread on runable list + pyb_thread_add_to_runable(th); + } + RESTORE_IRQ_PRI(irq_state); +} + +#endif // MICROPY_PY_THREAD diff --git a/ports/mimxrt/pybthread.h b/ports/mimxrt/pybthread.h new file mode 100644 index 0000000000..4532b0de7b --- /dev/null +++ b/ports/mimxrt/pybthread.h @@ -0,0 +1,79 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32_PYBTHREAD_H +#define MICROPY_INCLUDED_STM32_PYBTHREAD_H + +#include "py/mpprint.h" + +typedef struct _pyb_thread_t { + void *sp; + uint32_t local_state; + void *arg; // thread Python args, a GC root pointer + void *stack; // pointer to the stack + size_t stack_len; // number of words in the stack + uint32_t timeslice; + struct _pyb_thread_t *all_next; + struct _pyb_thread_t *run_prev; + struct _pyb_thread_t *run_next; + struct _pyb_thread_t *queue_next; +} pyb_thread_t; + +typedef pyb_thread_t *pyb_mutex_t; + +extern volatile int pyb_thread_enabled; +extern pyb_thread_t *volatile pyb_thread_all; +extern pyb_thread_t *volatile pyb_thread_cur; + +void pyb_thread_init(pyb_thread_t *th); +void pyb_thread_deinit(); +uint32_t pyb_thread_new(pyb_thread_t *th, void *stack, size_t stack_len, void *entry, void *arg); +void pyb_thread_dump(const mp_print_t *print); + +static inline uint32_t pyb_thread_get_id(void) { + return (uint32_t)pyb_thread_cur; +} + +static inline void pyb_thread_set_local(void *value) { + pyb_thread_cur->local_state = (uint32_t)value; +} + +static inline void *pyb_thread_get_local(void) { + return (void *)pyb_thread_cur->local_state; +} + +static inline void pyb_thread_yield(void) { + if (pyb_thread_cur->run_next == pyb_thread_cur) { + __WFI(); + } else { + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + } +} + +void pyb_mutex_init(pyb_mutex_t *m); +int pyb_mutex_lock(pyb_mutex_t *m, int wait); +void pyb_mutex_unlock(pyb_mutex_t *m); + +#endif // MICROPY_INCLUDED_STM32_PYBTHREAD_H diff --git a/ports/mimxrt/systick.c b/ports/mimxrt/systick.c index 8b0f5eb7b2..d2c263d157 100644 --- a/ports/mimxrt/systick.c +++ b/ports/mimxrt/systick.c @@ -51,6 +51,18 @@ void SysTick_Handler(void) { if (soft_timer_next == uw_tick) { pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); } + + #if MICROPY_PY_THREAD + if (pyb_thread_enabled) { + if (pyb_thread_cur->timeslice == 0) { + if (pyb_thread_cur->run_next != pyb_thread_cur) { + SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + } + } else { + --pyb_thread_cur->timeslice; + } + } + #endif } bool systick_has_passed(uint32_t start_tick, uint32_t delay_ms) {