diff options
author | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
---|---|---|
committer | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
commit | 4fd287655a72b9aea14cdac715ad5b90ed082ed2 (patch) | |
tree | 65d393bc0e699dd12d05b29ba568e04cea666207 /circuitpython/shared/runtime | |
parent | 0150f70ce9c39e9e6dd878766c0620c85e47bed0 (diff) |
add circuitpython code
Diffstat (limited to 'circuitpython/shared/runtime')
19 files changed, 2108 insertions, 0 deletions
diff --git a/circuitpython/shared/runtime/buffer_helper.c b/circuitpython/shared/runtime/buffer_helper.c new file mode 100644 index 0000000..facd05e --- /dev/null +++ b/circuitpython/shared/runtime/buffer_helper.c @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries + * + * 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 "shared/runtime/buffer_helper.h" + +void normalize_buffer_bounds(int32_t *start, int32_t end, size_t *length) { + if (end < 0) { + end += *length; + } else if (((size_t)end) > *length) { + end = *length; + } + if (*start < 0) { + *start += *length; + } + if (end < *start) { + *length = 0; + } else { + *length = end - *start; + } +} diff --git a/circuitpython/shared/runtime/buffer_helper.h b/circuitpython/shared/runtime/buffer_helper.h new file mode 100644 index 0000000..9611dc3 --- /dev/null +++ b/circuitpython/shared/runtime/buffer_helper.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries + * + * 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_BUFFER_HELPER_H +#define MICROPY_INCLUDED_LIB_UTILS_BUFFER_HELPER_H + +#include <stdint.h> +#include <string.h> + +void normalize_buffer_bounds(int32_t *start, int32_t end, size_t *length); + +#endif // MICROPY_INCLUDED_LIB_UTILS_BUFFER_HELPER_H diff --git a/circuitpython/shared/runtime/context_manager_helpers.c b/circuitpython/shared/runtime/context_manager_helpers.c new file mode 100644 index 0000000..d489ce9 --- /dev/null +++ b/circuitpython/shared/runtime/context_manager_helpers.c @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries + * + * 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 "shared/runtime/context_manager_helpers.h" + +#include "py/obj.h" + +STATIC mp_obj_t default___enter__(mp_obj_t self_in) { + return self_in; +} +MP_DEFINE_CONST_FUN_OBJ_1(default___enter___obj, default___enter__); diff --git a/circuitpython/shared/runtime/context_manager_helpers.h b/circuitpython/shared/runtime/context_manager_helpers.h new file mode 100644 index 0000000..286ab75 --- /dev/null +++ b/circuitpython/shared/runtime/context_manager_helpers.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries + * + * 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_CONTEXT_MANAGER_HELPERS_H +#define MICROPY_INCLUDED_LIB_UTILS_CONTEXT_MANAGER_HELPERS_H + +#include "py/obj.h" + +MP_DECLARE_CONST_FUN_OBJ_1(default___enter___obj); + +#endif // MICROPY_INCLUDED_LIB_UTILS_CONTEXT_MANAGER_HELPERS_H diff --git a/circuitpython/shared/runtime/gchelper.h b/circuitpython/shared/runtime/gchelper.h new file mode 100644 index 0000000..645ee83 --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/gchelper_generic.c b/circuitpython/shared/runtime/gchelper_generic.c new file mode 100644 index 0000000..dcd35f9 --- /dev/null +++ b/circuitpython/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 "shared/runtime/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/circuitpython/shared/runtime/gchelper_m0.s b/circuitpython/shared/runtime/gchelper_m0.s new file mode 100644 index 0000000..db0d973 --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/gchelper_m3.s b/circuitpython/shared/runtime/gchelper_m3.s new file mode 100644 index 0000000..5220fa0 --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/gchelper_native.c b/circuitpython/shared/runtime/gchelper_native.c new file mode 100644 index 0000000..1e4af9c --- /dev/null +++ b/circuitpython/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 "shared/runtime/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/circuitpython/shared/runtime/interrupt_char.c b/circuitpython/shared/runtime/interrupt_char.c new file mode 100644 index 0000000..de42184 --- /dev/null +++ b/circuitpython/shared/runtime/interrupt_char.c @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: 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" +#include "shared/runtime/interrupt_char.h" + +#if MICROPY_KBD_EXCEPTION + +int mp_interrupt_char = -1; + +void mp_hal_set_interrupt_char(int c) { + mp_interrupt_char = c; +} + +// Check to see if we've been CTRL-C'ed by autoreload or the user. +bool mp_hal_is_interrupted(void) { + return MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_FROM_PTR(NULL); +} + +#endif diff --git a/circuitpython/shared/runtime/interrupt_char.h b/circuitpython/shared/runtime/interrupt_char.h new file mode 100644 index 0000000..41b0bd8 --- /dev/null +++ b/circuitpython/shared/runtime/interrupt_char.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: 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 + +#include <stdbool.h> + +extern int mp_interrupt_char; +void mp_hal_set_interrupt_char(int c); +bool mp_hal_is_interrupted(void); + +#endif // MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H diff --git a/circuitpython/shared/runtime/mpirq.c b/circuitpython/shared/runtime/mpirq.c new file mode 100644 index 0000000..8e474bf --- /dev/null +++ b/circuitpython/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 "shared/runtime/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/circuitpython/shared/runtime/mpirq.h b/circuitpython/shared/runtime/mpirq.h new file mode 100644 index 0000000..dd423c0 --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/pyexec.c b/circuitpython/shared/runtime/pyexec.c new file mode 100644 index 0000000..b1f94db --- /dev/null +++ b/circuitpython/shared/runtime/pyexec.c @@ -0,0 +1,796 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: 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/gc_long_lived.h" +#include "py/frozenmod.h" +#include "py/mphal.h" +#if MICROPY_HW_ENABLE_USB +#include "irq.h" +#include "usb.h" +#endif +#include "shared/readline/readline.h" +#include "shared/runtime/pyexec.h" +#include "genhdr/mpversion.h" + +#if CIRCUITPY_ATEXIT +#include "shared-module/atexit/__init__.h" +#endif + +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 << 0) +#define EXEC_FLAG_ALLOW_DEBUGGING (1 << 1) +#define EXEC_FLAG_IS_REPL (1 << 2) +#define EXEC_FLAG_SOURCE_IS_RAW_CODE (1 << 3) +#define EXEC_FLAG_SOURCE_IS_VSTR (1 << 4) +#define EXEC_FLAG_SOURCE_IS_FILENAME (1 << 5) +#define EXEC_FLAG_SOURCE_IS_READER (1 << 6) +#define EXEC_FLAG_SOURCE_IS_ATEXIT (1 << 7) + +// 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, mp_uint_t exec_flags, pyexec_result_t *result) { + 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 CIRCUITPY_ATEXIT + if (!(exec_flags & EXEC_FLAG_SOURCE_IS_ATEXIT)) + #endif + { + #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; + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(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); + // Clear the parse tree because it has a heap pointer we don't need anymore. + *((uint32_t volatile *)&parse_tree.chunk) = 0; + #else + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); + #endif + } + + // If the code was loaded from a file it's likely to be running for a while so we'll long + // live it and collect any garbage before running. + if (input_kind == MP_PARSE_FILE_INPUT) { + module_fun = make_obj_long_lived(module_fun, 6); + gc_collect(); + } + } + + // 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 + #if CIRCUITPY_ATEXIT + if (exec_flags & EXEC_FLAG_SOURCE_IS_ATEXIT) { + atexit_callback_t *callback = (atexit_callback_t *)source; + mp_call_function_n_kw(callback->func, callback->n_pos, callback->n_kw, callback->args); + } else + #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 = 0; + 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_get_type((mp_obj_t)nlr.ret_val)), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // at the moment, the value of SystemExit is unused + ret = pyexec_system_exit; + #if CIRCUITPY_ALARM + } else if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type((mp_obj_t)nlr.ret_val)), &mp_type_DeepSleepRequest)) { + ret = PYEXEC_DEEP_SLEEP; + #endif + } else if ((mp_obj_t)nlr.ret_val == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) { + ret = PYEXEC_RELOAD; + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + ret = PYEXEC_EXCEPTION; + } + + } + if (result != NULL) { + result->return_code = ret; + #if CIRCUITPY_ALARM + // Don't set the exception object if we exited for deep sleep. + if (ret != 0 && ret != PYEXEC_DEEP_SLEEP) { + #else + if (ret != 0) { + #endif + mp_obj_t return_value = (mp_obj_t)nlr.ret_val; + result->exception = return_value; + result->exception_line = -1; + + if (mp_obj_is_exception_instance(return_value)) { + size_t n, *values; + mp_obj_exception_get_traceback(return_value, &n, &values); + if (values != NULL) { + result->exception_line = values[n - 2]; + } + } + } + } + + #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=" UINT_FMT "\n n_qstr=" UINT_FMT "\n " + "n_str_data_bytes=" UINT_FMT "\n n_total_bytes=" UINT_FMT "\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, NULL); +} + +#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, NULL); + 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(MICROPY_FULL_VERSION_INFO); + mp_hal_stdout_tx_str("\r\n"); + // mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + 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, NULL); + 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("\r\n"); + mp_hal_stdout_tx_str(MICROPY_FULL_VERSION_INFO); + mp_hal_stdout_tx_str("\r\n"); + // mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + + // 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); + + nlr_buf_t nlr; + nlr.ret_val = NULL; + int ret = 0; + if (nlr_push(&nlr) == 0) { + ret = readline(&line, ">>> "); + } else { + // Uncaught exception + mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) + + // Print exceptions but stay in the REPL. There are very few delayed + // exceptions. The WatchDogTimer can raise one though. + mp_hal_stdout_tx_str("\r\n"); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + 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, NULL); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + } +} + +#endif // MICROPY_REPL_EVENT_DRIVEN +#endif // MICROPY_ENABLE_COMPILER + +int pyexec_file(const char *filename, pyexec_result_t *result) { + return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME, result); +} + +int pyexec_file_if_exists(const char *filename, pyexec_result_t *result) { + #if MICROPY_MODULE_FROZEN + if (mp_find_frozen_module(filename, NULL, NULL) == MP_IMPORT_STAT_FILE) { + return pyexec_frozen_module(filename, result); + } + #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, result); +} + +#if MICROPY_MODULE_FROZEN +int pyexec_frozen_module(const char *name, pyexec_result_t *result) { + void *frozen_data; + int frozen_type; + mp_find_frozen_module(name, &frozen_type, &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, result); + #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, result); + #endif + + default: + printf("could not find module '%s'\n", name); + return false; + } +} +#endif + +#if CIRCUITPY_ATEXIT +int pyexec_exit_handler(const void *source, pyexec_result_t *result) { + return parse_compile_execute(source, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_ATEXIT, result); +} +#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/circuitpython/shared/runtime/pyexec.h b/circuitpython/shared/runtime/pyexec.h new file mode 100644 index 0000000..d31a7fe --- /dev/null +++ b/circuitpython/shared/runtime/pyexec.h @@ -0,0 +1,72 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: 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; + +typedef struct { + int return_code; + mp_obj_t exception; + int exception_line; +} pyexec_result_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) +#define PYEXEC_EXCEPTION (0x200) +#define PYEXEC_DEEP_SLEEP (0x400) +#define PYEXEC_RELOAD (0x800) + +int pyexec_raw_repl(void); +int pyexec_friendly_repl(void); +int pyexec_file(const char *filename, pyexec_result_t *result); +int pyexec_file_if_exists(const char *filename, pyexec_result_t *result); +int pyexec_frozen_module(const char *name, pyexec_result_t *result); +void pyexec_event_repl_init(void); +int pyexec_event_repl_process_char(int c); +extern uint8_t pyexec_repl_active; + +#if CIRCUITPY_ATEXIT +int pyexec_exit_handler(const void *source, pyexec_result_t *result); +#endif + +#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/circuitpython/shared/runtime/semihosting.c b/circuitpython/shared/runtime/semihosting.c new file mode 100644 index 0000000..18c7f5d --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/semihosting.h b/circuitpython/shared/runtime/semihosting.h new file mode 100644 index 0000000..d053a03 --- /dev/null +++ b/circuitpython/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/circuitpython/shared/runtime/stdout_helpers.c b/circuitpython/shared/runtime/stdout_helpers.c new file mode 100644 index 0000000..500b974 --- /dev/null +++ b/circuitpython/shared/runtime/stdout_helpers.c @@ -0,0 +1,36 @@ +#include <string.h> +#include <unistd.h> +#include "py/mpconfig.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. +void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) { + bool last_cr = false; + while (len > 0) { + size_t i = 0; + if (str[0] == '\n' && !last_cr) { + mp_hal_stdout_tx_strn("\r", 1); + i = 1; + } + // Lump all characters on the next line together. + while ((last_cr || str[i] != '\n') && i < len) { + last_cr = str[i] == '\r'; + i++; + } + mp_hal_stdout_tx_strn(str, i); + str = &str[i]; + len -= i; + } +} + +// Send zero-terminated string +void mp_hal_stdout_tx_str(const char *str) { + mp_hal_stdout_tx_strn(str, strlen(str)); +} diff --git a/circuitpython/shared/runtime/sys_stdio_mphal.c b/circuitpython/shared/runtime/sys_stdio_mphal.c new file mode 100644 index 0000000..580d97f --- /dev/null +++ b/circuitpython/shared/runtime/sys_stdio_mphal.c @@ -0,0 +1,183 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: 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 + +STATIC 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 = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) + .read = stdio_read, + .write = stdio_write, + .ioctl = stdio_ioctl, + .is_text = true, +}; + +STATIC const mp_obj_type_t stdio_obj_type = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_FileIO, + // TODO .make_new? + .print = stdio_obj_print, + .locals_dict = (mp_obj_dict_t *)&stdio_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &stdio_obj_stream_p, + ), +}; + +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 = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) + .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, + .flags = MP_TYPE_FLAG_EXTENDED, + .print = stdio_obj_print, + .locals_dict = (mp_obj_dict_t *)&stdio_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &stdio_buffer_obj_stream_p, + ), +}; + +STATIC const sys_stdio_obj_t stdio_buffer_obj = {{&stdio_buffer_obj_type}, .fd = 0}; // fd unused +#endif |