diff options
author | Damien George <damien@micropython.org> | 2021-07-09 12:38:25 +1000 |
---|---|---|
committer | Damien George <damien@micropython.org> | 2021-07-12 17:07:36 +1000 |
commit | 4d546713ec8858cbf908de45de11cbfc46a20971 (patch) | |
tree | fda10704c197bf3500f09297d29239c15f878fab /shared/runtime | |
parent | 925878b2f83e09987bd1688746b92898ee8bbcac (diff) | |
download | micropython-4d546713ec8858cbf908de45de11cbfc46a20971.tar.gz micropython-4d546713ec8858cbf908de45de11cbfc46a20971.zip |
shared: Introduce new top-level dir and move 1st party lib code there.
This commit moves all first-party code developed for this project from lib/
to shared/, so that lib/ now only contains third-party code.
The following directories are moved as-is from lib to shared:
lib/libc -> shared/libc
lib/memzip -> shared/memzip
lib/netutils -> shared/netutils
lib/timeutils -> shared/timeutils
lib/upytesthelper -> shared/upytesthelper
All files in lib/embed/ have been moved to shared/libc/.
lib/mp-readline has been moved to shared/readline.
lib/utils has been moved to shared/runtime, with the exception of
lib/utils/printf.c which has been moved to shared/libc/printf.c.
Signed-off-by: Damien George <damien@micropython.org>
Diffstat (limited to 'shared/runtime')
-rw-r--r-- | shared/runtime/gchelper.h | 50 | ||||
-rw-r--r-- | shared/runtime/gchelper_generic.c | 183 | ||||
-rw-r--r-- | shared/runtime/gchelper_m0.s | 61 | ||||
-rw-r--r-- | shared/runtime/gchelper_m3.s | 55 | ||||
-rw-r--r-- | shared/runtime/gchelper_native.c | 47 | ||||
-rw-r--r-- | shared/runtime/interrupt_char.c | 38 | ||||
-rw-r--r-- | shared/runtime/interrupt_char.h | 32 | ||||
-rw-r--r-- | shared/runtime/mpirq.c | 135 | ||||
-rw-r--r-- | shared/runtime/mpirq.h | 82 | ||||
-rw-r--r-- | shared/runtime/pyexec.c | 716 | ||||
-rw-r--r-- | shared/runtime/pyexec.h | 59 | ||||
-rw-r--r-- | shared/runtime/semihosting.c | 132 | ||||
-rw-r--r-- | shared/runtime/semihosting.h | 51 | ||||
-rw-r--r-- | shared/runtime/stdout_helpers.c | 62 | ||||
-rw-r--r-- | shared/runtime/sys_stdio_mphal.c | 175 |
15 files changed, 1878 insertions, 0 deletions
diff --git a/shared/runtime/gchelper.h b/shared/runtime/gchelper.h new file mode 100644 index 0000000000..645ee837f5 --- /dev/null +++ b/shared/runtime/gchelper.h @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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_LIB_UTILS_GCHELPER_H +#define MICROPY_INCLUDED_LIB_UTILS_GCHELPER_H + +#include <stdint.h> + +#if MICROPY_GCREGS_SETJMP +#include <setjmp.h> +typedef jmp_buf gc_helper_regs_t; +#else + +#if defined(__x86_64__) +typedef uintptr_t gc_helper_regs_t[6]; +#elif defined(__i386__) +typedef uintptr_t gc_helper_regs_t[4]; +#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) +typedef uintptr_t gc_helper_regs_t[10]; +#elif defined(__aarch64__) +typedef uintptr_t gc_helper_regs_t[11]; // x19-x29 +#endif + +#endif + +void gc_helper_collect_regs_and_stack(void); + +#endif // MICROPY_INCLUDED_LIB_UTILS_GCHELPER_H diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c new file mode 100644 index 0000000000..3e7e33ab18 --- /dev/null +++ b/shared/runtime/gchelper_generic.c @@ -0,0 +1,183 @@ +/* + * 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 <stdio.h> + +#include "py/mpstate.h" +#include "py/gc.h" +#include "lib/utils/gchelper.h" + +#if MICROPY_ENABLE_GC + +// Even if we have specific support for an architecture, it is +// possible to force use of setjmp-based implementation. +#if !MICROPY_GCREGS_SETJMP + +// We capture here callee-save registers, i.e. ones which may contain +// interesting values held there by our callers. It doesn't make sense +// to capture caller-saved registers, because they, well, put on the +// stack already by the caller. +#if defined(__x86_64__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long rbx asm ("rbx"); + register long rbp asm ("rbp"); + register long r12 asm ("r12"); + register long r13 asm ("r13"); + register long r14 asm ("r14"); + register long r15 asm ("r15"); + #ifdef __clang__ + // TODO: + // This is dirty workaround for Clang. It tries to get around + // uncompliant (wrt to GCC) behavior of handling register variables. + // Application of this patch here is random, and done only to unbreak + // MacOS build. Better, cross-arch ways to deal with Clang issues should + // be found. + asm ("" : "=r" (rbx)); + asm ("" : "=r" (rbp)); + asm ("" : "=r" (r12)); + asm ("" : "=r" (r13)); + asm ("" : "=r" (r14)); + asm ("" : "=r" (r15)); + #endif + arr[0] = rbx; + arr[1] = rbp; + arr[2] = r12; + arr[3] = r13; + arr[4] = r14; + arr[5] = r15; +} + +#elif defined(__i386__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long ebx asm ("ebx"); + register long esi asm ("esi"); + register long edi asm ("edi"); + register long ebp asm ("ebp"); + #ifdef __clang__ + // TODO: + // This is dirty workaround for Clang. It tries to get around + // uncompliant (wrt to GCC) behavior of handling register variables. + // Application of this patch here is random, and done only to unbreak + // MacOS build. Better, cross-arch ways to deal with Clang issues should + // be found. + asm ("" : "=r" (ebx)); + asm ("" : "=r" (esi)); + asm ("" : "=r" (edi)); + asm ("" : "=r" (ebp)); + #endif + arr[0] = ebx; + arr[1] = esi; + arr[2] = edi; + arr[3] = ebp; +} + +#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) + +// Fallback implementation, prefer gchelper_m0.s or gchelper_m3.s + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register long r4 asm ("r4"); + register long r5 asm ("r5"); + register long r6 asm ("r6"); + register long r7 asm ("r7"); + register long r8 asm ("r8"); + register long r9 asm ("r9"); + register long r10 asm ("r10"); + register long r11 asm ("r11"); + register long r12 asm ("r12"); + register long r13 asm ("r13"); + arr[0] = r4; + arr[1] = r5; + arr[2] = r6; + arr[3] = r7; + arr[4] = r8; + arr[5] = r9; + arr[6] = r10; + arr[7] = r11; + arr[8] = r12; + arr[9] = r13; +} + +#elif defined(__aarch64__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + const register long x19 asm ("x19"); + const register long x20 asm ("x20"); + const register long x21 asm ("x21"); + const register long x22 asm ("x22"); + const register long x23 asm ("x23"); + const register long x24 asm ("x24"); + const register long x25 asm ("x25"); + const register long x26 asm ("x26"); + const register long x27 asm ("x27"); + const register long x28 asm ("x28"); + const register long x29 asm ("x29"); + arr[0] = x19; + arr[1] = x20; + arr[2] = x21; + arr[3] = x22; + arr[4] = x23; + arr[5] = x24; + arr[6] = x25; + arr[7] = x26; + arr[8] = x27; + arr[9] = x28; + arr[10] = x29; +} + +#else + +#error "Architecture not supported for gc_helper_get_regs. Set MICROPY_GCREGS_SETJMP to use the fallback implementation." + +#endif + +#else // !MICROPY_GCREGS_SETJMP + +// Even if we have specific support for an architecture, it is +// possible to force use of setjmp-based implementation. + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + setjmp(arr); +} + +#endif // MICROPY_GCREGS_SETJMP + +// Explicitly mark this as noinline to make sure the regs variable +// is effectively at the top of the stack: otherwise, in builds where +// LTO is enabled and a lot of inlining takes place we risk a stack +// layout where regs is lower on the stack than pointers which have +// just been allocated but not yet marked, and get incorrectly sweeped. +MP_NOINLINE void gc_helper_collect_regs_and_stack(void) { + gc_helper_regs_t regs; + gc_helper_get_regs(regs); + // GC stack (and regs because we captured them) + void **regs_ptr = (void **)(void *)®s; + gc_collect_root(regs_ptr, ((uintptr_t)MP_STATE_THREAD(stack_top) - (uintptr_t)®s) / sizeof(uintptr_t)); +} + +#endif // MICROPY_ENABLE_GC diff --git a/shared/runtime/gchelper_m0.s b/shared/runtime/gchelper_m0.s new file mode 100644 index 0000000000..db0d9738d1 --- /dev/null +++ b/shared/runtime/gchelper_m0.s @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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. + */ + + .syntax unified + .cpu cortex-m0 + .thumb + + .section .text + .align 2 + + .global gc_helper_get_regs_and_sp + .type gc_helper_get_regs_and_sp, %function + +@ uint gc_helper_get_regs_and_sp(r0=uint regs[10]) +gc_helper_get_regs_and_sp: + @ store registers into given array + str r4, [r0, #0] + str r5, [r0, #4] + str r6, [r0, #8] + str r7, [r0, #12] + mov r1, r8 + str r1, [r0, #16] + mov r1, r9 + str r1, [r0, #20] + mov r1, r10 + str r1, [r0, #24] + mov r1, r11 + str r1, [r0, #28] + mov r1, r12 + str r1, [r0, #32] + mov r1, r13 + str r1, [r0, #36] + + @ return the sp + mov r0, sp + bx lr + + .size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp diff --git a/shared/runtime/gchelper_m3.s b/shared/runtime/gchelper_m3.s new file mode 100644 index 0000000000..5220fa0883 --- /dev/null +++ b/shared/runtime/gchelper_m3.s @@ -0,0 +1,55 @@ +/* + * 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. + */ + + .syntax unified + .cpu cortex-m3 + .thumb + + .section .text + .align 2 + + .global gc_helper_get_regs_and_sp + .type gc_helper_get_regs_and_sp, %function + +@ uint gc_helper_get_regs_and_sp(r0=uint regs[10]) +gc_helper_get_regs_and_sp: + @ store registers into given array + str r4, [r0], #4 + str r5, [r0], #4 + str r6, [r0], #4 + str r7, [r0], #4 + str r8, [r0], #4 + str r9, [r0], #4 + str r10, [r0], #4 + str r11, [r0], #4 + str r12, [r0], #4 + str r13, [r0], #4 + + @ return the sp + mov r0, sp + bx lr + + .size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp diff --git a/shared/runtime/gchelper_native.c b/shared/runtime/gchelper_native.c new file mode 100644 index 0000000000..6bf386b519 --- /dev/null +++ b/shared/runtime/gchelper_native.c @@ -0,0 +1,47 @@ +/* + * 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 <stdio.h> + +#include "py/mpstate.h" +#include "py/gc.h" +#include "lib/utils/gchelper.h" + +#if MICROPY_ENABLE_GC + +// provided by gchelper_*.s +uintptr_t gc_helper_get_regs_and_sp(uintptr_t *regs); + +MP_NOINLINE void gc_helper_collect_regs_and_stack(void) { + // get the registers and the sp + gc_helper_regs_t regs; + uintptr_t sp = gc_helper_get_regs_and_sp(regs); + + // trace the stack, including the registers (since they live on the stack in this function) + gc_collect_root((void **)sp, ((uint32_t)MP_STATE_THREAD(stack_top) - sp) / sizeof(uint32_t)); +} + +#endif diff --git a/shared/runtime/interrupt_char.c b/shared/runtime/interrupt_char.c new file mode 100644 index 0000000000..7014ae6a8f --- /dev/null +++ b/shared/runtime/interrupt_char.c @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-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/obj.h" +#include "py/mpstate.h" + +#if MICROPY_KBD_EXCEPTION + +int mp_interrupt_char = -1; + +void mp_hal_set_interrupt_char(int c) { + mp_interrupt_char = c; +} + +#endif diff --git a/shared/runtime/interrupt_char.h b/shared/runtime/interrupt_char.h new file mode 100644 index 0000000000..cb086ead92 --- /dev/null +++ b/shared/runtime/interrupt_char.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-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. + */ +#ifndef MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H +#define MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H + +extern int mp_interrupt_char; +void mp_hal_set_interrupt_char(int c); + +#endif // MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H diff --git a/shared/runtime/mpirq.c b/shared/runtime/mpirq.c new file mode 100644 index 0000000000..02139f24dc --- /dev/null +++ b/shared/runtime/mpirq.c @@ -0,0 +1,135 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Daniel Campora + * 2018 Tobias Badertscher + * + * 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 <stdio.h> + +#include "py/runtime.h" +#include "py/gc.h" +#include "lib/utils/mpirq.h" + +#if MICROPY_ENABLE_SCHEDULER + +/****************************************************************************** + DECLARE PUBLIC DATA + ******************************************************************************/ + +const mp_arg_t mp_irq_init_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, +}; + +/****************************************************************************** + DECLARE PRIVATE DATA + ******************************************************************************/ + +/****************************************************************************** + DEFINE PUBLIC FUNCTIONS + ******************************************************************************/ + +mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent) { + mp_irq_obj_t *self = m_new0(mp_irq_obj_t, 1); + mp_irq_init(self, methods, parent); + return self; +} + +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent) { + self->base.type = &mp_irq_type; + self->methods = (mp_irq_methods_t *)methods; + self->parent = parent; + self->handler = mp_const_none; + self->ishard = false; +} + +void mp_irq_handler(mp_irq_obj_t *self) { + if (self->handler != mp_const_none) { + if (self->ishard) { + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_1(self->handler, self->parent); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so that it doesn't run again + self->methods->trigger(self->parent, 0); + self->handler = mp_const_none; + printf("Uncaught exception in IRQ callback handler\n"); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + gc_unlock(); + mp_sched_unlock(); + } else { + // Schedule call to user function + mp_sched_schedule(self->handler, self->parent); + } + } +} + +/******************************************************************************/ +// MicroPython bindings + +STATIC mp_obj_t mp_irq_flags(mp_obj_t self_in) { + mp_irq_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->methods->info(self->parent, MP_IRQ_INFO_FLAGS)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_irq_flags_obj, mp_irq_flags); + +STATIC mp_obj_t mp_irq_trigger(size_t n_args, const mp_obj_t *args) { + mp_irq_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_t ret_obj = mp_obj_new_int(self->methods->info(self->parent, MP_IRQ_INFO_TRIGGERS)); + if (n_args == 2) { + // Set trigger + self->methods->trigger(self->parent, mp_obj_get_int(args[1])); + } + return ret_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_irq_trigger_obj, 1, 2, mp_irq_trigger); + +STATIC mp_obj_t mp_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 0, false); + mp_irq_handler(MP_OBJ_TO_PTR(self_in)); + return mp_const_none; +} + +STATIC const mp_rom_map_elem_t mp_irq_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_flags), MP_ROM_PTR(&mp_irq_flags_obj) }, + { MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&mp_irq_trigger_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_irq_locals_dict, mp_irq_locals_dict_table); + +const mp_obj_type_t mp_irq_type = { + { &mp_type_type }, + .name = MP_QSTR_irq, + .call = mp_irq_call, + .locals_dict = (mp_obj_dict_t *)&mp_irq_locals_dict, +}; + +#endif // MICROPY_ENABLE_SCHEDULER diff --git a/shared/runtime/mpirq.h b/shared/runtime/mpirq.h new file mode 100644 index 0000000000..dd423c0101 --- /dev/null +++ b/shared/runtime/mpirq.h @@ -0,0 +1,82 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Daniel Campora + * + * 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_LIB_UTILS_MPIRQ_H +#define MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H + +#include "py/runtime.h" + +/****************************************************************************** + DEFINE CONSTANTS + ******************************************************************************/ + +enum { + MP_IRQ_ARG_INIT_handler = 0, + MP_IRQ_ARG_INIT_trigger, + MP_IRQ_ARG_INIT_hard, + MP_IRQ_ARG_INIT_NUM_ARGS, +}; + +/****************************************************************************** + DEFINE TYPES + ******************************************************************************/ + +typedef mp_uint_t (*mp_irq_trigger_fun_t)(mp_obj_t self, mp_uint_t trigger); +typedef mp_uint_t (*mp_irq_info_fun_t)(mp_obj_t self, mp_uint_t info_type); + +enum { + MP_IRQ_INFO_FLAGS, + MP_IRQ_INFO_TRIGGERS, +}; + +typedef struct _mp_irq_methods_t { + mp_irq_trigger_fun_t trigger; + mp_irq_info_fun_t info; +} mp_irq_methods_t; + +typedef struct _mp_irq_obj_t { + mp_obj_base_t base; + mp_irq_methods_t *methods; + mp_obj_t parent; + mp_obj_t handler; + bool ishard; +} mp_irq_obj_t; + +/****************************************************************************** + DECLARE EXPORTED DATA + ******************************************************************************/ + +extern const mp_arg_t mp_irq_init_args[]; +extern const mp_obj_type_t mp_irq_type; + +/****************************************************************************** + DECLARE PUBLIC FUNCTIONS + ******************************************************************************/ + +mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent); +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent); +void mp_irq_handler(mp_irq_obj_t *self); + +#endif // MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c new file mode 100644 index 0000000000..4446b36b61 --- /dev/null +++ b/shared/runtime/pyexec.c @@ -0,0 +1,716 @@ +/* + * 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "py/compile.h" +#include "py/runtime.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/frozenmod.h" +#include "py/mphal.h" +#if MICROPY_HW_ENABLE_USB +#include "irq.h" +#include "usb.h" +#endif +#include "lib/mp-readline/readline.h" +#include "lib/utils/pyexec.h" +#include "genhdr/mpversion.h" + +pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; +int pyexec_system_exit = 0; + +#if MICROPY_REPL_INFO +STATIC bool repl_display_debugging_info = 0; +#endif + +#define EXEC_FLAG_PRINT_EOF (1) +#define EXEC_FLAG_ALLOW_DEBUGGING (2) +#define EXEC_FLAG_IS_REPL (4) +#define EXEC_FLAG_SOURCE_IS_RAW_CODE (8) +#define EXEC_FLAG_SOURCE_IS_VSTR (16) +#define EXEC_FLAG_SOURCE_IS_FILENAME (32) +#define EXEC_FLAG_SOURCE_IS_READER (64) + +// parses, compiles and executes the code in the lexer +// frees the lexer before returning +// EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output +// EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code +// EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile) +STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { + int ret = 0; + #if MICROPY_REPL_INFO + uint32_t start = 0; + #endif + + #ifdef MICROPY_BOARD_BEFORE_PYTHON_EXEC + MICROPY_BOARD_BEFORE_PYTHON_EXEC(input_kind, exec_flags); + #endif + + // by default a SystemExit exception returns 0 + pyexec_system_exit = 0; + + nlr_buf_t nlr; + nlr.ret_val = NULL; + if (nlr_push(&nlr) == 0) { + mp_obj_t module_fun; + #if MICROPY_MODULE_FROZEN_MPY + if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { + // source is a raw_code object, create the function + module_fun = mp_make_function_from_raw_code(source, MP_OBJ_NULL, MP_OBJ_NULL); + } else + #endif + { + #if MICROPY_ENABLE_COMPILER + mp_lexer_t *lex; + if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) { + const vstr_t *vstr = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0); + } else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { + lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source); + } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { + lex = mp_lexer_new_from_file(source); + } else { + lex = (mp_lexer_t *)source; + } + // source is a lexer, parse and compile the script + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); + #else + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); + #endif + } + + // execute code + mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us + #if MICROPY_REPL_INFO + start = mp_hal_ticks_ms(); + #endif + mp_call_function_0(module_fun); + mp_hal_set_interrupt_char(-1); // disable interrupt + mp_handle_pending(true); // handle any pending exceptions (and any callbacks) + nlr_pop(); + ret = 1; + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); + } + } else { + // uncaught exception + mp_hal_set_interrupt_char(-1); // disable interrupt + mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) + + if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { + const mp_reader_t *reader = source; + reader->close(reader->data); + } + + // print EOF after normal output + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); + } + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // at the moment, the value of SystemExit is unused + ret = pyexec_system_exit; + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + ret = 0; + } + } + + #if MICROPY_REPL_INFO + // display debugging info if wanted + if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) { + mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly + printf("took " UINT_FMT " ms\n", ticks); + // qstr info + { + size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; + qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); + printf("qstr:\n n_pool=%u\n n_qstr=%u\n " + "n_str_data_bytes=%u\n n_total_bytes=%u\n", + (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); + } + + #if MICROPY_ENABLE_GC + // run collection and print GC info + gc_collect(); + gc_dump_info(); + #endif + } + #endif + + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); + } + + #ifdef MICROPY_BOARD_AFTER_PYTHON_EXEC + MICROPY_BOARD_AFTER_PYTHON_EXEC(input_kind, exec_flags, nlr.ret_val, &ret); + #endif + + return ret; +} + +#if MICROPY_ENABLE_COMPILER + +// This can be configured by a port (and even configured to a function to be +// computed dynamically) to indicate the maximum number of bytes that can be +// held in the stdin buffer. +#ifndef MICROPY_REPL_STDIN_BUFFER_MAX +#define MICROPY_REPL_STDIN_BUFFER_MAX (256) +#endif + +typedef struct _mp_reader_stdin_t { + bool eof; + uint16_t window_max; + uint16_t window_remain; +} mp_reader_stdin_t; + +STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) { + mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; + + if (reader->eof) { + return MP_READER_EOF; + } + + int c = mp_hal_stdin_rx_chr(); + + if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { + reader->eof = true; + mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host + if (c == CHAR_CTRL_C) { + #if MICROPY_KBD_EXCEPTION + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); + #else + mp_raise_type(&mp_type_KeyboardInterrupt); + #endif + } else { + return MP_READER_EOF; + } + } + + if (--reader->window_remain == 0) { + mp_hal_stdout_tx_strn("\x01", 1); // indicate window available to host + reader->window_remain = reader->window_max; + } + + return c; +} + +STATIC void mp_reader_stdin_close(void *data) { + mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; + if (!reader->eof) { + reader->eof = true; + mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host + for (;;) { + int c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { + break; + } + } + } +} + +STATIC void mp_reader_new_stdin(mp_reader_t *reader, mp_reader_stdin_t *reader_stdin, uint16_t buf_max) { + // Make flow-control window half the buffer size, and indicate to the host that 2x windows are + // free (sending the window size implicitly indicates that a window is free, and then the 0x01 + // indicates that another window is free). + size_t window = buf_max / 2; + char reply[3] = { window & 0xff, window >> 8, 0x01 }; + mp_hal_stdout_tx_strn(reply, sizeof(reply)); + + reader_stdin->eof = false; + reader_stdin->window_max = window; + reader_stdin->window_remain = window; + reader->data = reader_stdin; + reader->readbyte = mp_reader_stdin_readbyte; + reader->close = mp_reader_stdin_close; +} + +STATIC int do_reader_stdin(int c) { + if (c != 'A') { + // Unsupported command. + mp_hal_stdout_tx_strn("R\x00", 2); + return 0; + } + + // Indicate reception of command. + mp_hal_stdout_tx_strn("R\x01", 2); + + mp_reader_t reader; + mp_reader_stdin_t reader_stdin; + mp_reader_new_stdin(&reader, &reader_stdin, MICROPY_REPL_STDIN_BUFFER_MAX); + int exec_flags = EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_READER; + return parse_compile_execute(&reader, MP_PARSE_FILE_INPUT, exec_flags); +} + +#if MICROPY_REPL_EVENT_DRIVEN + +typedef struct _repl_t { + // This structure originally also held current REPL line, + // but it was moved to MP_STATE_VM(repl_line) as containing + // root pointer. Still keep structure in case more state + // will be added later. + // vstr_t line; + bool cont_line; + bool paste_mode; +} repl_t; + +repl_t repl; + +STATIC int pyexec_raw_repl_process_char(int c); +STATIC int pyexec_friendly_repl_process_char(int c); + +void pyexec_event_repl_init(void) { + MP_STATE_VM(repl_line) = vstr_new(32); + repl.cont_line = false; + repl.paste_mode = false; + // no prompt before printing friendly REPL banner or entering raw REPL + readline_init(MP_STATE_VM(repl_line), ""); + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + pyexec_raw_repl_process_char(CHAR_CTRL_A); + } else { + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + } +} + +STATIC int pyexec_raw_repl_process_char(int c) { + if (c == CHAR_CTRL_A) { + // reset raw REPL + if (vstr_len(MP_STATE_VM(repl_line)) == 2 && vstr_str(MP_STATE_VM(repl_line))[0] == CHAR_CTRL_E) { + int ret = do_reader_stdin(vstr_str(MP_STATE_VM(repl_line))[1]); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + goto reset; + } + mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); + goto reset; + } else if (c == CHAR_CTRL_B) { + // change to friendly REPL + pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + return 0; + } else if (c == CHAR_CTRL_C) { + // clear line + vstr_reset(MP_STATE_VM(repl_line)); + return 0; + } else if (c == CHAR_CTRL_D) { + // input finished + } else { + // let through any other raw 8-bit value + vstr_add_byte(MP_STATE_VM(repl_line), c); + return 0; + } + + // indicate reception of command + mp_hal_stdout_tx_str("OK"); + + if (MP_STATE_VM(repl_line)->len == 0) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } + + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + +reset: + vstr_reset(MP_STATE_VM(repl_line)); + mp_hal_stdout_tx_str(">"); + + return 0; +} + +STATIC int pyexec_friendly_repl_process_char(int c) { + if (repl.paste_mode) { + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\r\n"); + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + goto input_restart; + } else { + // add char to buffer and echo + vstr_add_byte(MP_STATE_VM(repl_line), c); + if (c == '\r') { + mp_hal_stdout_tx_str("\r\n=== "); + } else { + char buf[1] = {c}; + mp_hal_stdout_tx_strn(buf, 1); + } + return 0; + } + } + + int ret = readline_process_char(c); + + if (!repl.cont_line) { + + if (ret == CHAR_CTRL_A) { + // change to raw REPL + pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; + mp_hal_stdout_tx_str("\r\n"); + pyexec_raw_repl_process_char(CHAR_CTRL_A); + return 0; + } else if (ret == CHAR_CTRL_B) { + // reset friendly REPL + mp_hal_stdout_tx_str("\r\n"); + mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); + #if MICROPY_PY_BUILTINS_HELP + mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + #endif + goto input_restart; + } else if (ret == CHAR_CTRL_C) { + // break + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); + vstr_reset(MP_STATE_VM(repl_line)); + repl.paste_mode = true; + return 0; + } + + if (ret < 0) { + return 0; + } + + if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + goto exec; + } + + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + repl.cont_line = true; + readline_note_newline("... "); + return 0; + + } else { + + if (ret == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + repl.cont_line = false; + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + goto exec; + } + + if (ret < 0) { + return 0; + } + + if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + readline_note_newline("... "); + return 0; + } + + exec:; + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + + input_restart: + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + readline_init(MP_STATE_VM(repl_line), ">>> "); + return 0; + } +} + +uint8_t pyexec_repl_active; +int pyexec_event_repl_process_char(int c) { + pyexec_repl_active = 1; + int res; + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + res = pyexec_raw_repl_process_char(c); + } else { + res = pyexec_friendly_repl_process_char(c); + } + pyexec_repl_active = 0; + return res; +} + +#else // MICROPY_REPL_EVENT_DRIVEN + +int pyexec_raw_repl(void) { + vstr_t line; + vstr_init(&line, 32); + +raw_repl_reset: + mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); + + for (;;) { + vstr_reset(&line); + mp_hal_stdout_tx_str(">"); + for (;;) { + int c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_A) { + // reset raw REPL + if (vstr_len(&line) == 2 && vstr_str(&line)[0] == CHAR_CTRL_E) { + int ret = do_reader_stdin(vstr_str(&line)[1]); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + vstr_reset(&line); + mp_hal_stdout_tx_str(">"); + continue; + } + goto raw_repl_reset; + } else if (c == CHAR_CTRL_B) { + // change to friendly REPL + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(&line); + pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + return 0; + } else if (c == CHAR_CTRL_C) { + // clear line + vstr_reset(&line); + } else if (c == CHAR_CTRL_D) { + // input finished + break; + } else { + // let through any other raw 8-bit value + vstr_add_byte(&line, c); + } + } + + // indicate reception of command + mp_hal_stdout_tx_str("OK"); + + if (line.len == 0) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(&line); + return PYEXEC_FORCED_EXIT; + } + + int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + } +} + +int pyexec_friendly_repl(void) { + vstr_t line; + vstr_init(&line, 32); + +friendly_repl_reset: + mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); + #if MICROPY_PY_BUILTINS_HELP + mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + #endif + + // to test ctrl-C + /* + { + uint32_t x[4] = {0x424242, 0xdeaddead, 0x242424, 0xdeadbeef}; + for (;;) { + nlr_buf_t nlr; + printf("pyexec_repl: %p\n", x); + mp_hal_set_interrupt_char(CHAR_CTRL_C); + if (nlr_push(&nlr) == 0) { + for (;;) { + } + } else { + printf("break\n"); + } + } + } + */ + + for (;;) { + input_restart: + + #if MICROPY_HW_ENABLE_USB + if (usb_vcp_is_enabled()) { + // If the user gets to here and interrupts are disabled then + // they'll never see the prompt, traceback etc. The USB REPL needs + // interrupts to be enabled or no transfers occur. So we try to + // do the user a favor and reenable interrupts. + if (query_irq() == IRQ_STATE_DISABLED) { + enable_irq(IRQ_STATE_ENABLED); + mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n"); + } + } + #endif + + // If the GC is locked at this point there is no way out except a reset, + // so force the GC to be unlocked to help the user debug what went wrong. + if (MP_STATE_THREAD(gc_lock_depth) != 0) { + MP_STATE_THREAD(gc_lock_depth) = 0; + } + + vstr_reset(&line); + int ret = readline(&line, ">>> "); + mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; + + if (ret == CHAR_CTRL_A) { + // change to raw REPL + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(&line); + pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; + return 0; + } else if (ret == CHAR_CTRL_B) { + // reset friendly REPL + mp_hal_stdout_tx_str("\r\n"); + goto friendly_repl_reset; + } else if (ret == CHAR_CTRL_C) { + // break + mp_hal_stdout_tx_str("\r\n"); + continue; + } else if (ret == CHAR_CTRL_D) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(&line); + return PYEXEC_FORCED_EXIT; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); + vstr_reset(&line); + for (;;) { + char c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\r\n"); + break; + } else { + // add char to buffer and echo + vstr_add_byte(&line, c); + if (c == '\r') { + mp_hal_stdout_tx_str("\r\n=== "); + } else { + mp_hal_stdout_tx_strn(&c, 1); + } + } + } + parse_input_kind = MP_PARSE_FILE_INPUT; + } else if (vstr_len(&line) == 0) { + continue; + } else { + // got a line with non-zero length, see if it needs continuing + while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { + vstr_add_byte(&line, '\n'); + ret = readline(&line, "... "); + if (ret == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + break; + } + } + } + + ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + } +} + +#endif // MICROPY_REPL_EVENT_DRIVEN +#endif // MICROPY_ENABLE_COMPILER + +int pyexec_file(const char *filename) { + return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME); +} + +int pyexec_file_if_exists(const char *filename) { + #if MICROPY_MODULE_FROZEN + if (mp_frozen_stat(filename) == MP_IMPORT_STAT_FILE) { + return pyexec_frozen_module(filename); + } + #endif + if (mp_import_stat(filename) != MP_IMPORT_STAT_FILE) { + return 1; // success (no file is the same as an empty file executing without fail) + } + return pyexec_file(filename); +} + +#if MICROPY_MODULE_FROZEN +int pyexec_frozen_module(const char *name) { + void *frozen_data; + int frozen_type = mp_find_frozen_module(name, strlen(name), &frozen_data); + + switch (frozen_type) { + #if MICROPY_MODULE_FROZEN_STR + case MP_FROZEN_STR: + return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, 0); + #endif + + #if MICROPY_MODULE_FROZEN_MPY + case MP_FROZEN_MPY: + return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_RAW_CODE); + #endif + + default: + printf("could not find module '%s'\n", name); + return false; + } +} +#endif + +#if MICROPY_REPL_INFO +mp_obj_t pyb_set_repl_info(mp_obj_t o_value) { + repl_display_debugging_info = mp_obj_get_int(o_value); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj, pyb_set_repl_info); +#endif diff --git a/shared/runtime/pyexec.h b/shared/runtime/pyexec.h new file mode 100644 index 0000000000..981e7dca9f --- /dev/null +++ b/shared/runtime/pyexec.h @@ -0,0 +1,59 @@ +/* + * 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_LIB_UTILS_PYEXEC_H +#define MICROPY_INCLUDED_LIB_UTILS_PYEXEC_H + +#include "py/obj.h" + +typedef enum { + PYEXEC_MODE_FRIENDLY_REPL, + PYEXEC_MODE_RAW_REPL, +} pyexec_mode_kind_t; + +extern pyexec_mode_kind_t pyexec_mode_kind; + +// Set this to the value (eg PYEXEC_FORCED_EXIT) that will be propagated through +// the pyexec functions if a SystemExit exception is raised by the running code. +// It will reset to 0 at the start of each execution (eg each REPL entry). +extern int pyexec_system_exit; + +#define PYEXEC_FORCED_EXIT (0x100) + +int pyexec_raw_repl(void); +int pyexec_friendly_repl(void); +int pyexec_file(const char *filename); +int pyexec_file_if_exists(const char *filename); +int pyexec_frozen_module(const char *name); +void pyexec_event_repl_init(void); +int pyexec_event_repl_process_char(int c); +extern uint8_t pyexec_repl_active; + +#if MICROPY_REPL_INFO +mp_obj_t pyb_set_repl_info(mp_obj_t o_value); +MP_DECLARE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj); +#endif + +#endif // MICROPY_INCLUDED_LIB_UTILS_PYEXEC_H diff --git a/shared/runtime/semihosting.c b/shared/runtime/semihosting.c new file mode 100644 index 0000000000..18c7f5d57a --- /dev/null +++ b/shared/runtime/semihosting.c @@ -0,0 +1,132 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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 "semihosting.h" + +// Resources: +// http://embed.rs/articles/2016/semi-hosting-rust/ +// https://wiki.dlang.org/Minimal_semihosted_ARM_Cortex-M_%22Hello_World%22 +// https://github.com/arduino/OpenOCD/blob/master/src/target/arm_semihosting.c + +#define SYS_OPEN 0x01 +#define SYS_WRITEC 0x03 +#define SYS_WRITE 0x05 +#define SYS_READC 0x07 + +// Constants: +#define OPEN_MODE_READ (0) // mode "r" +#define OPEN_MODE_WRITE (4) // mode "w" + +#ifndef __thumb__ +#error Semihosting is only implemented for ARM microcontrollers. +#endif + +static int mp_semihosting_stdout; + +static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { + // A semihosting call works as follows, similar to a SVCall: + // * the call is invoked by a special breakpoint: 0xAB + // * the command is placed in r0 + // * a pointer to the arguments is placed in r1 + // * the return value is placed in r0 + // Note that because it uses the breakpoint instruction, applications + // will hang if they're not connected to a debugger. And they'll be + // stuck in a breakpoint if semihosting is not specifically enabled in + // the debugger. + // Also note that semihosting is extremely slow (sometimes >100ms per + // call). + register uint32_t num_reg __asm__ ("r0") = num; + register const void *args_reg __asm__ ("r1") = arg; + __asm__ __volatile__ ( + "bkpt 0xAB\n" // invoke semihosting call + : "+r" (num_reg) // call number and result + : "r" (args_reg) // arguments + : "memory"); // make sure args aren't optimized away + return num_reg; // r0, which became the result +} + +static int mp_semihosting_open_console(uint32_t mode) { + struct { + char *name; + uint32_t mode; + uint32_t name_len; + } args = { + .name = ":tt", // magic path to console + .mode = mode, // e.g. "r", "w" (see OPEN_MODE_* constants) + .name_len = 3, // strlen(":tt") + }; + return mp_semihosting_call(SYS_OPEN, &args); +} + +void mp_semihosting_init() { + mp_semihosting_stdout = mp_semihosting_open_console(OPEN_MODE_WRITE); +} + +int mp_semihosting_rx_char() { + return mp_semihosting_call(SYS_READC, NULL); +} + +static void mp_semihosting_tx_char(char c) { + mp_semihosting_call(SYS_WRITEC, &c); +} + +uint32_t mp_semihosting_tx_strn(const char *str, size_t len) { + if (len == 0) { + return 0; // nothing to do + } + if (len == 1) { + mp_semihosting_tx_char(*str); // maybe faster? + return 0; + } + + struct { + uint32_t fd; + const char *str; + uint32_t len; + } args = { + .fd = mp_semihosting_stdout, + .str = str, + .len = len, + }; + return mp_semihosting_call(SYS_WRITE, &args); +} + +uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len) { + // Write chunks of data until (excluding) the first '\n' character, + // insert a '\r' character, and then continue with the next chunk + // (starting with '\n'). + // Doing byte-by-byte writes would be easier to implement but is far + // too slow. + size_t start = 0; + for (size_t i = 0; i < len; i++) { + if (str[i] == '\n') { + mp_semihosting_tx_strn(str + start, i - start); + mp_semihosting_tx_char('\r'); + start = i; + } + } + return mp_semihosting_tx_strn(str + start, len - start); +} diff --git a/shared/runtime/semihosting.h b/shared/runtime/semihosting.h new file mode 100644 index 0000000000..d053a03eda --- /dev/null +++ b/shared/runtime/semihosting.h @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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_LIB_UTILS_SEMIHOSTING_H +#define MICROPY_INCLUDED_LIB_UTILS_SEMIHOSTING_H + +/* + +To use semi-hosting for a replacement UART: +- Add lib/semihosting/semihosting.c to the Makefile sources. +- Call mp_semihosting_init() in main(), around the time UART is initialized. +- Replace mp_hal_stdin_rx_chr and similar in mphalport.c with the semihosting equivalent. +- Include lib/semihosting/semihosting.h in the relevant files. + +Then make sure the debugger is attached and enables semihosting. In OpenOCD this is +done with ARM semihosting enable followed by reset. The terminal will need further +configuration to work with MicroPython (bash: stty raw -echo). + +*/ + +#include <stddef.h> +#include <stdint.h> + +void mp_semihosting_init(); +int mp_semihosting_rx_char(); +uint32_t mp_semihosting_tx_strn(const char *str, size_t len); +uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_LIB_UTILS_SEMIHOSTING_H diff --git a/shared/runtime/stdout_helpers.c b/shared/runtime/stdout_helpers.c new file mode 100644 index 0000000000..6a24cc2eba --- /dev/null +++ b/shared/runtime/stdout_helpers.c @@ -0,0 +1,62 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 <string.h> +#include "py/mphal.h" + +/* + * Extra stdout functions + * These can be either optimized for a particular port, or reference + * implementation below can be used. + */ + +// Send "cooked" string of given length, where every occurrence of +// LF character is replaced with CR LF ("\n" is converted to "\r\n"). +// This is an optimised version to reduce the number of calls made +// to mp_hal_stdout_tx_strn. +void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) { + const char *last = str; + while (len--) { + if (*str == '\n') { + if (str > last) { + mp_hal_stdout_tx_strn(last, str - last); + } + mp_hal_stdout_tx_strn("\r\n", 2); + ++str; + last = str; + } else { + ++str; + } + } + if (str > last) { + mp_hal_stdout_tx_strn(last, str - last); + } +} + +// Send zero-terminated string +void mp_hal_stdout_tx_str(const char *str) { + mp_hal_stdout_tx_strn(str, strlen(str)); +} diff --git a/shared/runtime/sys_stdio_mphal.c b/shared/runtime/sys_stdio_mphal.c new file mode 100644 index 0000000000..e72facb981 --- /dev/null +++ b/shared/runtime/sys_stdio_mphal.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2019 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 <stdio.h> +#include <string.h> + +#include "py/obj.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +// TODO make stdin, stdout and stderr writable objects so they can +// be changed by Python code. This requires some changes, as these +// objects are in a read-only module (py/modsys.c). + +/******************************************************************************/ +// MicroPython bindings + +#define STDIO_FD_IN (0) +#define STDIO_FD_OUT (1) +#define STDIO_FD_ERR (2) + +typedef struct _sys_stdio_obj_t { + mp_obj_base_t base; + int fd; +} sys_stdio_obj_t; + +#if MICROPY_PY_SYS_STDIO_BUFFER +STATIC const sys_stdio_obj_t stdio_buffer_obj; +#endif + +void stdio_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "<io.FileIO %d>", self->fd); +} + +STATIC mp_uint_t stdio_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->fd == STDIO_FD_IN) { + for (uint i = 0; i < size; i++) { + int c = mp_hal_stdin_rx_chr(); + if (c == '\r') { + c = '\n'; + } + ((byte *)buf)[i] = c; + } + return size; + } else { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } +} + +STATIC mp_uint_t stdio_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->fd == STDIO_FD_OUT || self->fd == STDIO_FD_ERR) { + mp_hal_stdout_tx_strn_cooked(buf, size); + return size; + } else { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } +} + +STATIC mp_uint_t stdio_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)self_in; + if (request == MP_STREAM_POLL) { + return mp_hal_stdio_poll(arg); + } else { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +STATIC mp_obj_t stdio_obj___exit__(size_t n_args, const mp_obj_t *args) { + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(stdio_obj___exit___obj, 4, 4, stdio_obj___exit__); + +// TODO gc hook to close the file if not already closed + +STATIC const mp_rom_map_elem_t stdio_locals_dict_table[] = { + #if MICROPY_PY_SYS_STDIO_BUFFER + { MP_ROM_QSTR(MP_QSTR_buffer), MP_ROM_PTR(&stdio_buffer_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)}, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&stdio_obj___exit___obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(stdio_locals_dict, stdio_locals_dict_table); + +STATIC const mp_stream_p_t stdio_obj_stream_p = { + .read = stdio_read, + .write = stdio_write, + .ioctl = stdio_ioctl, + .is_text = true, +}; + +STATIC const mp_obj_type_t stdio_obj_type = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + // TODO .make_new? + .print = stdio_obj_print, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &stdio_obj_stream_p, + .locals_dict = (mp_obj_dict_t *)&stdio_locals_dict, +}; + +const sys_stdio_obj_t mp_sys_stdin_obj = {{&stdio_obj_type}, .fd = STDIO_FD_IN}; +const sys_stdio_obj_t mp_sys_stdout_obj = {{&stdio_obj_type}, .fd = STDIO_FD_OUT}; +const sys_stdio_obj_t mp_sys_stderr_obj = {{&stdio_obj_type}, .fd = STDIO_FD_ERR}; + +#if MICROPY_PY_SYS_STDIO_BUFFER +STATIC mp_uint_t stdio_buffer_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + for (uint i = 0; i < size; i++) { + ((byte *)buf)[i] = mp_hal_stdin_rx_chr(); + } + return size; +} + +STATIC mp_uint_t stdio_buffer_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + mp_hal_stdout_tx_strn(buf, size); + return size; +} + +STATIC const mp_stream_p_t stdio_buffer_obj_stream_p = { + .read = stdio_buffer_read, + .write = stdio_buffer_write, + .ioctl = stdio_ioctl, + .is_text = false, +}; + +STATIC const mp_obj_type_t stdio_buffer_obj_type = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + .print = stdio_obj_print, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &stdio_buffer_obj_stream_p, + .locals_dict = (mp_obj_dict_t *)&stdio_locals_dict, +}; + +STATIC const sys_stdio_obj_t stdio_buffer_obj = {{&stdio_buffer_obj_type}, .fd = 0}; // fd unused +#endif |