diff options
Diffstat (limited to 'circuitpython/supervisor')
73 files changed, 8958 insertions, 0 deletions
diff --git a/circuitpython/supervisor/background_callback.h b/circuitpython/supervisor/background_callback.h new file mode 100644 index 0000000..651ac02 --- /dev/null +++ b/circuitpython/supervisor/background_callback.h @@ -0,0 +1,93 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler 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 CIRCUITPY_INCLUDED_SUPERVISOR_BACKGROUND_CALLBACK_H +#define CIRCUITPY_INCLUDED_SUPERVISOR_BACKGROUND_CALLBACK_H + +#include <stdbool.h> + +/** Background callbacks are a linked list of tasks to call in the background. + * + * Include a member of type `background_callback_t` inside an object + * which needs to queue up background work, and zero-initialize it. + * + * To schedule the work, use background_callback_add, with fun as the + * function to call and data pointing to the object itself. + * + * Next time run_background_tasks_if_tick is called, the callback will + * be run and removed from the linked list. + * + * Queueing a task that is already queued does nothing. Unconditionally + * re-queueing it from its own background task will cause it to run during the + * very next background-tasks invocation, leading to a CircuitPython freeze, so + * don't do that. + * + * background_callback_add can be called from interrupt context. + */ +typedef void (*background_callback_fun)(void *data); +typedef struct background_callback { + background_callback_fun fun; + void *data; + struct background_callback *next; + struct background_callback *prev; +} background_callback_t; + +/* Add a background callback for which 'fun' and 'data' were previously set */ +void background_callback_add_core(background_callback_t *cb); + +/* Add a background callback to the given function with the given data. When + * the callback involves an object on the GC heap, the 'data' must be a pointer + * to that object itself, not an internal pointer. Otherwise, it can be the + * case that no other references to the object itself survive, and the object + * becomes garbage collected while an outstanding background callback still + * exists. + */ +void background_callback_add(background_callback_t *cb, background_callback_fun fun, void *data); + +/* Run all background callbacks. Normally, this is done by the supervisor + * whenever the list is non-empty */ +void background_callback_run_all(void); + +/* True when a background callback is pending. Helpful for checking background state when + * interrupts are disabled. */ +bool background_callback_pending(void); + +/* During soft reset, remove all pending callbacks and clear the critical section flag */ +void background_callback_reset(void); + +/* Sometimes background callbacks must be blocked. Use these functions to + * bracket the section of code where this is the case. These calls nest, and + * begins must be balanced with ends. + */ +void background_callback_begin_critical_section(void); +void background_callback_end_critical_section(void); + +/* + * Background callbacks may stop objects from being collected + */ +void background_callback_gc_collect(void); + +#endif diff --git a/circuitpython/supervisor/board.h b/circuitpython/supervisor/board.h new file mode 100644 index 0000000..e3f0af7 --- /dev/null +++ b/circuitpython/supervisor/board.h @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 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_SUPERVISOR_BOARD_H +#define MICROPY_INCLUDED_SUPERVISOR_BOARD_H + +#include <stdbool.h> + +#include "supervisor/shared/safe_mode.h" + +// Returns true if the user initiates safe mode in a board specific way. +// Also add BOARD_USER_SAFE_MODE in mpconfigboard.h to explain the board specific +// way. +bool board_requests_safe_mode(void); + +// Initializes board related state once on start up. +void board_init(void); + +// Reset the state of off MCU components such as neopixels. +void reset_board(void); + +// Deinit the board. This should put the board in deep sleep durable, low power +// state. It should not prevent the user access method from working (such as +// disabling USB, BLE or flash) because CircuitPython may continue to run. +void board_deinit(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_BOARD_H diff --git a/circuitpython/supervisor/cpu.h b/circuitpython/supervisor/cpu.h new file mode 100755 index 0000000..c4f8131 --- /dev/null +++ b/circuitpython/supervisor/cpu.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_SUPERVISOR_CPU_H +#define MICROPY_INCLUDED_SUPERVISOR_CPU_H + +// Adds up to 10 pointers from the CPUs registers to regs. This is used to make sure no actively +// used heap memory is freed. Its usually implemented in assembly. +mp_uint_t cpu_get_regs_and_sp(mp_uint_t *regs); + +#endif // MICROPY_INCLUDED_SUPERVISOR_CPU_H diff --git a/circuitpython/supervisor/fatfs_port.h b/circuitpython/supervisor/fatfs_port.h new file mode 100644 index 0000000..e76ced5 --- /dev/null +++ b/circuitpython/supervisor/fatfs_port.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_SUPERVISOR_FATFS_PORT_H +#define MICROPY_INCLUDED_SUPERVISOR_FATFS_PORT_H + +#include "lib/oofatfs/ff.h" + +void override_fattime(DWORD time); + +#endif // MICROPY_INCLUDED_SUPERVISOR_FATFS_PORT_H diff --git a/circuitpython/supervisor/filesystem.h b/circuitpython/supervisor/filesystem.h new file mode 100644 index 0000000..6f4faf0 --- /dev/null +++ b/circuitpython/supervisor/filesystem.h @@ -0,0 +1,48 @@ +/* + * 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_SUPERVISOR_FILESYSTEM_H +#define MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H + +#include <stdbool.h> + +#include "extmod/vfs_fat.h" + +extern volatile bool filesystem_flush_requested; + +void filesystem_background(void); +void filesystem_tick(void); +bool filesystem_init(bool create_allowed, bool force_create); +void filesystem_flush(void); +bool filesystem_present(void); +void filesystem_set_internal_writable_by_usb(bool usb_writable); +void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_protection); +void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable); +void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection); +bool filesystem_is_writable_by_python(fs_user_mount_t *vfs); +bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs); + +#endif // MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H diff --git a/circuitpython/supervisor/flash.h b/circuitpython/supervisor/flash.h new file mode 100644 index 0000000..21d76c9 --- /dev/null +++ b/circuitpython/supervisor/flash.h @@ -0,0 +1,53 @@ +/* + * 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_SUPERVISOR_FLASH_H +#define MICROPY_INCLUDED_SUPERVISOR_FLASH_H + +#include <stdint.h> +#include <stdbool.h> + +#include "py/mpconfig.h" + +#if INTERNAL_FLASH_FILESYSTEM +#include "supervisor/shared/internal_flash.h" +#else +#include "supervisor/shared/external_flash/external_flash.h" +#endif + +void supervisor_flash_init(void); +uint32_t supervisor_flash_get_block_size(void); +uint32_t supervisor_flash_get_block_count(void); + +// these return 0 on success, non-zero on error +mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); +mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); + +struct _fs_user_mount_t; +void supervisor_flash_init_vfs(struct _fs_user_mount_t *vfs); +void supervisor_flash_flush(void); +void supervisor_flash_release_cache(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_FLASH_H diff --git a/circuitpython/supervisor/flash_root_pointers.h b/circuitpython/supervisor/flash_root_pointers.h new file mode 100644 index 0000000..a426b9c --- /dev/null +++ b/circuitpython/supervisor/flash_root_pointers.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_FLASH_ROOT_POINTERS_H +#define MICROPY_INCLUDED_SUPERVISOR_FLASH_ROOT_POINTERS_H + +#ifdef EXTERNAL_FLASH_DEVICES +#include "supervisor/shared/external_flash/external_flash_root_pointers.h" +#else +#include "supervisor/internal_flash_root_pointers.h" +#endif + +#endif // MICROPY_INCLUDED_SUPERVISOR_FLASH_ROOT_POINTERS_H diff --git a/circuitpython/supervisor/linker.h b/circuitpython/supervisor/linker.h new file mode 100644 index 0000000..58068c1 --- /dev/null +++ b/circuitpython/supervisor/linker.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 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. + */ + +// These macros are used to place code and data into different linking sections. + +#ifndef MICROPY_INCLUDED_SUPERVISOR_LINKER_H +#define MICROPY_INCLUDED_SUPERVISOR_LINKER_H + +#if defined(IMXRT10XX) || defined(FOMU) || defined(STM32H7) || defined(RASPBERRYPI) +#define PLACE_IN_DTCM_DATA(name) name __attribute__((section(".dtcm_data." #name))) +#define PLACE_IN_DTCM_BSS(name) name __attribute__((section(".dtcm_bss." #name))) +#define PLACE_IN_ITCM(name) __attribute__((section(".itcm." #name))) name +#else +#define PLACE_IN_DTCM_DATA(name) name +#define PLACE_IN_DTCM_BSS(name) name +#define PLACE_IN_ITCM(name) name +#endif + +#endif // MICROPY_INCLUDED_SUPERVISOR_LINKER_H diff --git a/circuitpython/supervisor/memory.h b/circuitpython/supervisor/memory.h new file mode 100644 index 0000000..0aa38ee --- /dev/null +++ b/circuitpython/supervisor/memory.h @@ -0,0 +1,77 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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. + */ + +// Basic allocations outside them for areas such as the VM heap and stack. +// supervisor/shared/memory.c has a basic implementation for a continuous chunk of memory. Add it +// to a SRC_ in a Makefile to use it. + +#ifndef MICROPY_INCLUDED_SUPERVISOR_MEMORY_H +#define MICROPY_INCLUDED_SUPERVISOR_MEMORY_H + +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> + +typedef struct { + uint32_t *ptr; +} supervisor_allocation; + + + +void free_memory(supervisor_allocation *allocation); + +// Find the allocation with the given ptr, NULL if not found. When called from the context of a +// supervisor_move_memory() callback, finds the allocation that had that ptr *before* the move, but +// the returned allocation already contains the ptr after the move. +// When called with NULL, may return either NULL or an unused allocation whose ptr is NULL (this is +// a feature used internally in allocate_memory to save code size). Passing the return value to +// free_memory() is a permissible no-op in either case. +supervisor_allocation *allocation_from_ptr(void *ptr); + +supervisor_allocation *allocate_remaining_memory(void); + +// Allocate a piece of a given length in bytes. If high_address is true then it should be allocated +// at a lower address from the top of the stack. Otherwise, addresses will increase starting after +// statically allocated memory. If movable is false, memory will be taken from outside the GC heap +// and will stay stationary until freed. While the VM is running, this will fail unless a previous +// allocation of exactly matching length has recently been freed. If movable is true, memory will be +// taken from either outside or inside the GC heap, and when the VM exits, will be moved outside. +// The ptr of the returned supervisor_allocation will change at that point. If you need to be +// notified of that, add your own callback function at the designated place near the end of +// supervisor_move_memory(). +supervisor_allocation *allocate_memory(uint32_t length, bool high_address, bool movable); + +static inline size_t align32_size(size_t size) { + return (size + 3) & ~3; +} + +size_t get_allocation_length(supervisor_allocation *allocation); + +// Called after the GC heap is freed, transfers movable allocations from the GC heap to the +// supervisor heap and compacts the supervisor heap. +void supervisor_move_memory(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_MEMORY_H diff --git a/circuitpython/supervisor/messages/default.h b/circuitpython/supervisor/messages/default.h new file mode 100644 index 0000000..8cdd671 --- /dev/null +++ b/circuitpython/supervisor/messages/default.h @@ -0,0 +1,99 @@ +/* + * 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_SUPERVISOR_MESSAGES_DEFAULT_H +#define MICROPY_SUPERVISOR_MESSAGES_DEFAULT_H + +#ifndef MSG_OUTPUT_SUFFIX +#define MSG_OUTPUT_SUFFIX " output:\r\n" +#endif + +#ifndef MSG_NEWLINE +#define MSG_NEWLINE "\r\n" +#endif + +#ifndef MSG_AUTORELOAD_ON +#define MSG_AUTORELOAD_ON "Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\r\n" +#endif + +#ifndef MSG_AUTORELOAD_OFF +#define MSG_AUTORELOAD_OFF "Auto-reload is off.\r\n" +#endif + +#ifndef MSG_SAFE_MODE_ON +#define MSG_SAFE_MODE_ON "Running in safe mode! Auto-reload is off.\r\n" +#endif + +#ifndef MSG_SAFE_MODE_NO_MAIN +#define MSG_SAFE_MODE_NO_MAIN "Running in safe mode! Not running saved code.\r\n" +#endif + +#ifndef MSG_SAFE_MODE_USER_REQUESTED +#define MSG_SAFE_MODE_USER_REQUESTED "You requested starting safe mode by " +#endif + +#ifndef MSG_SAFE_MODE_USER_EXIT +#define MSG_SAFE_MODE_USER_EXIT "To exit, please reset the board without " +#endif + +#ifndef MSG_BAD_SAFE_MODE +#define MSG_BAD_SAFE_MODE "You are running in safe mode which means something really bad happened." +#endif + +#ifndef MSG_SAFE_MODE_CRASH +#define MSG_SAFE_MODE_CRASH "Looks like our core CircuitPython code crashed hard. Whoops!" +#endif + +#ifndef MSG_SAFE_MODE_FILE_ISSUE +#define MSG_SAFE_MODE_FILE_ISSUE "Please file an issue here with the contents of your CIRCUITPY drive:" +#endif + +#ifndef MSG_SAFE_MODE_ISSUE_LINK +#define MSG_SAFE_MODE_ISSUE_LINK "https://github.com/adafruit/circuitpython/issues" +#endif + +#ifndef MSG_SAFE_MODE_BROWN_OUT_LINE_1 +#define MSG_SAFE_MODE_BROWN_OUT_LINE_1 "The microcontroller's power dipped. Please make sure your power supply provides" +#endif + +#ifndef MSG_SAFE_MODE_BROWN_OUT_LINE_2 +#define MSG_SAFE_MODE_BROWN_OUT_LINE_2 "enough power for the whole circuit and press reset (after ejecting CIRCUITPY)." +#endif + +#ifndef MSG_WAIT_BEFORE_REPL +#define MSG_WAIT_BEFORE_REPL "Press any key to enter the REPL. Use CTRL-D to reload." +#endif + +// Be careful, some tools depend on this. +#ifndef MSG_SOFT_REBOOT +#define MSG_SOFT_REBOOT "soft reboot" +#endif + +#ifndef MSG_DOUBLE_FILE_EXTENSION +#define MSG_DOUBLE_FILE_EXTENSION "WARNING: Your code filename has two extensions\r\n" +#endif + +#endif // MICROPY_SUPERVISOR_MESSAGES_DEFAULT_H diff --git a/circuitpython/supervisor/port.h b/circuitpython/supervisor/port.h new file mode 100644 index 0000000..0a8cdfd --- /dev/null +++ b/circuitpython/supervisor/port.h @@ -0,0 +1,106 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016-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_SUPERVISOR_PORT_H +#define MICROPY_INCLUDED_SUPERVISOR_PORT_H + +#include "py/mpconfig.h" + +#include "supervisor/memory.h" +#include "supervisor/shared/safe_mode.h" + +// Provided by the linker; +extern uint32_t _ezero; + +// This file defines core methods that must be implemented by a port. +extern uint32_t _estack; + +// Stored at the end of the bss section (which includes the heap). +extern uint32_t _ebss; + +safe_mode_t port_init(void); + +// Reset the microcontroller completely. +void reset_cpu(void) NORETURN; + +// Reset the microcontroller state. +void reset_port(void); + +// Reset to the bootloader +void reset_to_bootloader(void) NORETURN; + +// Get stack limit address +uint32_t *port_stack_get_limit(void); + +// Get stack top address +uint32_t *port_stack_get_top(void); + +// True if stack is not located inside heap (at the top) +bool port_has_fixed_stack(void); + +// Get heap bottom address +uint32_t *port_heap_get_bottom(void); + +// Get heap top address +uint32_t *port_heap_get_top(void); + +// Save and retrieve a word from memory that is preserved over reset. Used for safe mode. +void port_set_saved_word(uint32_t); +uint32_t port_get_saved_word(void); + +// Get the raw tick count since start up. A tick is 1/1024 of a second, a common low frequency +// clock rate. If subticks is not NULL then the port will fill in the number of subticks where each +// tick is 32 subticks (for a resolution of 1/32768 or 30.5ish microseconds.) +uint64_t port_get_raw_ticks(uint8_t *subticks); + +// Enable 1/1024 second tick. +void port_enable_tick(void); + +// Disable 1/1024 second tick. +void port_disable_tick(void); + +// Wake the CPU after the given number of ticks or sooner. Only the last call to this will apply. +// Only the common sleep routine should use it. +void port_interrupt_after_ticks(uint32_t ticks); + +// Sleep the CPU until an interrupt is received. We call this idle because it +// may not be a system level sleep. +void port_idle_until_interrupt(void); + +// Execute port specific actions during background tasks. +void port_background_task(void); + +// Take port specific actions at the beginning and end of background tasks. +// This is used e.g., to set a monitoring pin for debug purposes. "Actual +// work" should be done in port_background_task() instead. +void port_start_background_task(void); +void port_finish_background_task(void); + +// Some ports need special handling to wake the main task from an interrupt +// context or other task. The port must implement the necessary code in this +// function. A default weak implementation is provided that does nothing. +void port_wake_main_task(void); +#endif // MICROPY_INCLUDED_SUPERVISOR_PORT_H diff --git a/circuitpython/supervisor/serial.h b/circuitpython/supervisor/serial.h new file mode 100644 index 0000000..a6646e9 --- /dev/null +++ b/circuitpython/supervisor/serial.h @@ -0,0 +1,62 @@ +/* + * 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_SUPERVISOR_SERIAL_H +#define MICROPY_INCLUDED_SUPERVISOR_SERIAL_H + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> + +#include "py/mpconfig.h" + +#ifdef CIRCUITPY_BOOT_OUTPUT_FILE +#include "py/misc.h" + +extern vstr_t *boot_output; +#endif + + +void serial_early_init(void); +void serial_init(void); +void serial_write(const char *text); +// Only writes up to given length. Does not check for null termination at all. +void serial_write_substring(const char *text, uint32_t length); +char serial_read(void); +bool serial_bytes_available(void); +bool serial_connected(void); + +// These have no-op versions that are weak and the port can override. They work +// in tandem with the cross-port mechanics like USB and BLE. +void port_serial_init(void); +bool port_serial_connected(void); +char port_serial_read(void); +bool port_serial_bytes_available(void); +void port_serial_write_substring(const char *text, uint32_t length); + +int debug_uart_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SERIAL_H diff --git a/circuitpython/supervisor/shared/background_callback.c b/circuitpython/supervisor/shared/background_callback.c new file mode 100644 index 0000000..e53edb5 --- /dev/null +++ b/circuitpython/supervisor/shared/background_callback.c @@ -0,0 +1,148 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler 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 <string.h> + +#include "py/gc.h" +#include "py/mpconfig.h" +#include "supervisor/background_callback.h" +#include "supervisor/linker.h" +#include "supervisor/port.h" +#include "supervisor/shared/tick.h" +#include "shared-bindings/microcontroller/__init__.h" + +STATIC volatile background_callback_t *volatile callback_head, *volatile callback_tail; + +#define CALLBACK_CRITICAL_BEGIN (common_hal_mcu_disable_interrupts()) +#define CALLBACK_CRITICAL_END (common_hal_mcu_enable_interrupts()) + +MP_WEAK void port_wake_main_task(void) { +} + +void background_callback_add_core(background_callback_t *cb) { + CALLBACK_CRITICAL_BEGIN; + if (cb->prev || callback_head == cb) { + CALLBACK_CRITICAL_END; + return; + } + cb->next = 0; + cb->prev = (background_callback_t *)callback_tail; + if (callback_tail) { + callback_tail->next = cb; + } + if (!callback_head) { + callback_head = cb; + } + callback_tail = cb; + CALLBACK_CRITICAL_END; + + port_wake_main_task(); +} + +void background_callback_add(background_callback_t *cb, background_callback_fun fun, void *data) { + cb->fun = fun; + cb->data = data; + background_callback_add_core(cb); +} + +bool PLACE_IN_ITCM(background_callback_pending)(void) { + return callback_head != NULL; +} + +static bool in_background_callback; +void PLACE_IN_ITCM(background_callback_run_all)() { + if (!background_callback_pending()) { + return; + } + CALLBACK_CRITICAL_BEGIN; + if (in_background_callback) { + CALLBACK_CRITICAL_END; + return; + } + in_background_callback = true; + background_callback_t *cb = (background_callback_t *)callback_head; + callback_head = NULL; + callback_tail = NULL; + while (cb) { + background_callback_t *next = cb->next; + cb->next = cb->prev = NULL; + background_callback_fun fun = cb->fun; + void *data = cb->data; + CALLBACK_CRITICAL_END; + // Leave the critical section in order to run the callback function + if (fun) { + fun(data); + } + CALLBACK_CRITICAL_BEGIN; + cb = next; + } + in_background_callback = false; + CALLBACK_CRITICAL_END; +} + +void background_callback_begin_critical_section() { + CALLBACK_CRITICAL_BEGIN; +} + +void background_callback_end_critical_section() { + CALLBACK_CRITICAL_END; +} + +void background_callback_reset() { + CALLBACK_CRITICAL_BEGIN; + background_callback_t *cb = (background_callback_t *)callback_head; + while (cb) { + background_callback_t *next = cb->next; + memset(cb, 0, sizeof(*cb)); + cb = next; + } + callback_head = NULL; + callback_tail = NULL; + in_background_callback = false; + CALLBACK_CRITICAL_END; +} + +void background_callback_gc_collect(void) { + // We don't enter the callback critical section here. We rely on + // gc_collect_ptr _NOT_ entering background callbacks, so it is not + // possible for the list to be cleared. + // + // However, it is possible for the list to be extended. We make the + // minor assumption that no newly added callback is for a + // collectable object. That is, we only plug the hole where an + // object becomes collectable AFTER it is added but before the + // callback is run, not the hole where an object was ALREADY + // collectable but adds a background task for itself. + // + // It's necessary to traverse the whole list here, as the callbacks + // themselves can be in non-gc memory, and some of the cb->data + // objects themselves might be in non-gc memory. + background_callback_t *cb = (background_callback_t *)callback_head; + while (cb) { + gc_collect_ptr(cb->data); + cb = cb->next; + } +} diff --git a/circuitpython/supervisor/shared/bluetooth/bluetooth.c b/circuitpython/supervisor/shared/bluetooth/bluetooth.c new file mode 100644 index 0000000..426aad9 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/bluetooth.c @@ -0,0 +1,301 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 <string.h> + +#include "supervisor/shared/bluetooth/bluetooth.h" + +#include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Adapter.h" +#if defined(CIRCUITPY_BOOT_BUTTON) +#include "shared-bindings/digitalio/DigitalInOut.h" +#endif +#include "shared-bindings/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/ResetReason.h" +#include "shared-module/storage/__init__.h" + +#include "common-hal/_bleio/__init__.h" + +#include "supervisor/shared/status_leds.h" +#include "supervisor/shared/tick.h" + +#include "py/mpstate.h" + +#if CIRCUITPY_BLE_FILE_SERVICE +#include "supervisor/shared/bluetooth/file_transfer.h" +#include "bluetooth/ble_drv.h" +#endif + +#if CIRCUITPY_SERIAL_BLE +#include "supervisor/shared/bluetooth/serial.h" +#include "bluetooth/ble_drv.h" +#endif + +// This standard advertisement advertises the CircuitPython editing service and a CIRCUITPY short name. +const uint8_t public_advertising_data[] = { 0x02, 0x01, 0x06, // 0-2 Flags + 0x02, 0x0a, 0xec, // 3-5 TX power level -20 + #if CIRCUITPY_BLE_FILE_SERVICE + 0x03, 0x02, 0xbb, 0xfe, // 6 - 9 Incomplete service list (File Transfer service) + #endif + 0x0e, 0xff, 0x22, 0x08, // 10 - 13 Adafruit Manufacturer Data + 0x0a, 0x04, 0x00, // 14 - 16 Creator ID / Creation ID + CIRCUITPY_CREATOR_ID & 0xff, // 17 - 20 Creator ID + (CIRCUITPY_CREATOR_ID >> 8) & 0xff, + (CIRCUITPY_CREATOR_ID >> 16) & 0xff, + (CIRCUITPY_CREATOR_ID >> 24) & 0xff, + CIRCUITPY_CREATION_ID & 0xff, // 21 - 24 Creation ID + (CIRCUITPY_CREATION_ID >> 8) & 0xff, + (CIRCUITPY_CREATION_ID >> 16) & 0xff, + (CIRCUITPY_CREATION_ID >> 24) & 0xff, + 0x05, 0x08, 0x43, 0x49, 0x52, 0x43 // 25 - 31 - Short name +}; +const uint8_t private_advertising_data[] = { 0x02, 0x01, 0x06, // 0-2 Flags + 0x02, 0x0a, 0x00 // 3-5 TX power level 0 +}; +// This scan response advertises the full CIRCPYXXXX device name. +uint8_t circuitpython_scan_response_data[] = { + 0x0a, 0x09, 0x43, 0x49, 0x52, 0x50, 0x59, 0x00, 0x00, 0x00, 0x00, + #if CIRCUITPY_SERIAL_BLE + 0x11, 0x06, 0x6e, 0x68, 0x74, 0x79, 0x50, 0x74, 0x69, 0x75, 0x63, 0x72, 0x69, 0x43, 0x01, 0x00, 0xaf, 0xad + #endif +}; + + +#if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE +STATIC bool boot_in_discovery_mode = false; +STATIC bool advertising = false; +STATIC bool ble_started = false; + +#define WORKFLOW_UNSET 0 +#define WORKFLOW_ENABLED 1 +#define WORKFLOW_DISABLED 2 + +STATIC uint8_t workflow_state = WORKFLOW_UNSET; +STATIC bool was_connected = false; + +STATIC void supervisor_bluetooth_start_advertising(void) { + if (workflow_state != WORKFLOW_ENABLED) { + return; + } + bool is_connected = common_hal_bleio_adapter_get_connected(&common_hal_bleio_adapter_obj); + if (is_connected) { + return; + } + bool bonded = common_hal_bleio_adapter_is_bonded_to_central(&common_hal_bleio_adapter_obj); + #if CIRCUITPY_USB + // Don't advertise when we have USB instead of BLE. + if (!bonded && !boot_in_discovery_mode) { + return; + } + #endif + uint32_t timeout = 0; + float interval = 0.1f; + int tx_power = 0; + const uint8_t *adv = private_advertising_data; + size_t adv_len = sizeof(private_advertising_data); + const uint8_t *scan_response = NULL; + size_t scan_response_len = 0; + // Advertise with less power when doing so publicly to reduce who can hear us. This will make it + // harder for someone with bad intentions to pair from a distance. + if (!bonded) { + tx_power = -20; + adv = public_advertising_data; + adv_len = sizeof(public_advertising_data); + scan_response = circuitpython_scan_response_data; + scan_response_len = sizeof(circuitpython_scan_response_data); + } + uint32_t status = _common_hal_bleio_adapter_start_advertising(&common_hal_bleio_adapter_obj, + true, + bonded, // Advertise anonymously if we are bonded + timeout, + interval, + adv, + adv_len, + scan_response, + scan_response_len, + tx_power, + NULL); + // This may fail if we are already advertising. + advertising = status == NRF_SUCCESS; +} + +#endif // CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + +#define BLE_DISCOVERY_DATA_GUARD 0xbb0000bb +#define BLE_DISCOVERY_DATA_GUARD_MASK 0xff0000ff + +void supervisor_bluetooth_init(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + uint32_t reset_state = port_get_saved_word(); + uint32_t ble_mode = 0; + if ((reset_state & BLE_DISCOVERY_DATA_GUARD_MASK) == BLE_DISCOVERY_DATA_GUARD) { + ble_mode = (reset_state & ~BLE_DISCOVERY_DATA_GUARD_MASK) >> 8; + } + const mcu_reset_reason_t reset_reason = common_hal_mcu_processor_get_reset_reason(); + boot_in_discovery_mode = false; + if (reset_reason != RESET_REASON_POWER_ON && + reset_reason != RESET_REASON_RESET_PIN && + reset_reason != RESET_REASON_UNKNOWN && + reset_reason != RESET_REASON_SOFTWARE) { + return; + } + + if (ble_mode == 0) { + port_set_saved_word(BLE_DISCOVERY_DATA_GUARD | (0x01 << 8)); + } + // Wait for a while to allow for reset. + + #ifdef CIRCUITPY_BOOT_BUTTON + digitalio_digitalinout_obj_t boot_button; + common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON); + common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP); + #endif + #if CIRCUITPY_STATUS_LED + status_led_init(); + #endif + uint64_t start_ticks = supervisor_ticks_ms64(); + uint64_t diff = 0; + if (ble_mode != 0) { + #ifdef CIRCUITPY_STATUS_LED + new_status_color(0x0000ff); + #endif + common_hal_bleio_adapter_erase_bonding(&common_hal_bleio_adapter_obj); + boot_in_discovery_mode = true; + reset_state = 0x0; + } + #if !CIRCUITPY_USB + // Boot into discovery if USB isn't available and we aren't bonded already. + // Checking here allows us to have the status LED solidly on even if no button was + // pressed. + bool bonded = common_hal_bleio_adapter_is_bonded_to_central(&common_hal_bleio_adapter_obj); + if (!bonded) { + boot_in_discovery_mode = true; + } + #endif + while (diff < 1000) { + #ifdef CIRCUITPY_STATUS_LED + // Blink on for 50 and off for 100 + bool led_on = boot_in_discovery_mode || (diff % 150) <= 50; + if (led_on) { + new_status_color(0x0000ff); + } else { + new_status_color(BLACK); + } + #endif + #ifdef CIRCUITPY_BOOT_BUTTON + if (!common_hal_digitalio_digitalinout_get_value(&boot_button)) { + boot_in_discovery_mode = true; + break; + } + #endif + diff = supervisor_ticks_ms64() - start_ticks; + } + #if CIRCUITPY_STATUS_LED + new_status_color(BLACK); + status_led_deinit(); + #endif + port_set_saved_word(reset_state); + #endif +} + +void supervisor_bluetooth_background(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + if (!ble_started) { + return; + } + bool is_connected = common_hal_bleio_adapter_get_connected(&common_hal_bleio_adapter_obj); + if (was_connected && !is_connected) { + #if CIRCUITPY_BLE_FILE_SERVICE + supervisor_bluetooth_file_transfer_disconnected(); + #endif + } + was_connected = is_connected; + if (!is_connected) { + supervisor_bluetooth_start_advertising(); + return; + } + + #if CIRCUITPY_BLE_FILE_SERVICE + supervisor_bluetooth_file_transfer_background(); + #endif + #endif +} + +void supervisor_start_bluetooth(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + + if (workflow_state != WORKFLOW_ENABLED) { + return; + } + + common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, true); + + #if CIRCUITPY_BLE_FILE_SERVICE + supervisor_start_bluetooth_file_transfer(); + #endif + + #if CIRCUITPY_SERIAL_BLE + supervisor_start_bluetooth_serial(); + #endif + + // Mark as started so that the background call does something. + ble_started = true; + + // Kick off advertisements + supervisor_bluetooth_background(); + + #endif +} + +void supervisor_stop_bluetooth(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + + if (!ble_started && workflow_state != WORKFLOW_ENABLED) { + return; + } + + #if CIRCUITPY_SERIAL_BLE + supervisor_stop_bluetooth_serial(); + #endif + + #endif +} + +void supervisor_bluetooth_enable_workflow(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + if (workflow_state == WORKFLOW_DISABLED) { + return; + } + + workflow_state = WORKFLOW_ENABLED; + #endif +} + +void supervisor_bluetooth_disable_workflow(void) { + #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_SERIAL_BLE + workflow_state = WORKFLOW_DISABLED; + #endif +} diff --git a/circuitpython/supervisor/shared/bluetooth/bluetooth.h b/circuitpython/supervisor/shared/bluetooth/bluetooth.h new file mode 100644 index 0000000..9de8271 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/bluetooth.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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_SUPERVISOR_SHARED_BLUETOOTH_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_H + +#include <stdbool.h> + +void supervisor_bluetooth_background(void); +void supervisor_bluetooth_init(void); +void supervisor_start_bluetooth(void); +void supervisor_stop_bluetooth(void); + +// Enable only works if it hasn't been set yet. +void supervisor_bluetooth_enable_workflow(void); +void supervisor_bluetooth_disable_workflow(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_H diff --git a/circuitpython/supervisor/shared/bluetooth/file_transfer.c b/circuitpython/supervisor/shared/bluetooth/file_transfer.c new file mode 100644 index 0000000..a6a2f80 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/file_transfer.c @@ -0,0 +1,756 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 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 <string.h> + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "shared/timeutils/timeutils.h" + +#include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Adapter.h" +#include "shared-bindings/_bleio/Characteristic.h" +#include "shared-bindings/_bleio/PacketBuffer.h" +#include "shared-bindings/_bleio/Service.h" +#include "shared-bindings/_bleio/UUID.h" +#include "shared-module/storage/__init__.h" + +#include "bluetooth/ble_drv.h" + +#include "common-hal/_bleio/__init__.h" + +#include "supervisor/fatfs_port.h" +#include "supervisor/shared/reload.h" +#include "supervisor/shared/bluetooth/file_transfer.h" +#include "supervisor/shared/bluetooth/file_transfer_protocol.h" +#include "supervisor/shared/tick.h" +#include "supervisor/usb.h" + +#include "py/mpstate.h" +#include "py/stackctrl.h" + +STATIC bleio_service_obj_t supervisor_ble_service; +STATIC bleio_uuid_obj_t supervisor_ble_service_uuid; +STATIC bleio_characteristic_obj_t supervisor_ble_version_characteristic; +STATIC bleio_uuid_obj_t supervisor_ble_version_uuid; +STATIC bleio_characteristic_obj_t supervisor_ble_transfer_characteristic; +STATIC bleio_uuid_obj_t supervisor_ble_transfer_uuid; + +// This is the base UUID for the file transfer service. +const uint8_t file_transfer_base_uuid[16] = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x00, 0xaf, 0xad }; + +STATIC mp_obj_list_t characteristic_list; +STATIC mp_obj_t characteristic_list_items[2]; +// 2 * 10 ringbuf packets, 512 for a disk sector and 12 for the file transfer write header. +#define PACKET_BUFFER_SIZE (2 * 10 + 512 + 12) +// uint32_t so its aligned +STATIC uint32_t _buffer[PACKET_BUFFER_SIZE / 4 + 1]; +STATIC uint32_t _outgoing1[BLE_GATTS_VAR_ATTR_LEN_MAX / 4]; +STATIC uint32_t _outgoing2[BLE_GATTS_VAR_ATTR_LEN_MAX / 4]; +STATIC ble_drv_evt_handler_entry_t static_handler_entry; +STATIC bleio_packet_buffer_obj_t _transfer_packet_buffer; + +void supervisor_start_bluetooth_file_transfer(void) { + supervisor_ble_service_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_service_uuid, 0xfebb, NULL); + + // We know we'll only be 1 characteristic so we can statically allocate it. + characteristic_list.base.type = &mp_type_list; + characteristic_list.alloc = sizeof(characteristic_list_items) / sizeof(characteristic_list_items[0]); + characteristic_list.len = 0; + characteristic_list.items = characteristic_list_items; + mp_seq_clear(characteristic_list.items, 0, characteristic_list.alloc, sizeof(*characteristic_list.items)); + + _common_hal_bleio_service_construct(&supervisor_ble_service, &supervisor_ble_service_uuid, false /* is secondary */, &characteristic_list); + + // Version number + supervisor_ble_version_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_version_uuid, 0x0100, file_transfer_base_uuid); + common_hal_bleio_characteristic_construct(&supervisor_ble_version_characteristic, + &supervisor_ble_service, + 0, // handle (for remote only) + &supervisor_ble_version_uuid, + CHAR_PROP_READ, + SECURITY_MODE_OPEN, + SECURITY_MODE_NO_ACCESS, + 4, // max length + true, // fixed length + NULL, // no initial value + NULL); // no description + + uint32_t version = 4; + mp_buffer_info_t bufinfo; + bufinfo.buf = &version; + bufinfo.len = sizeof(version); + common_hal_bleio_characteristic_set_value(&supervisor_ble_version_characteristic, &bufinfo); + + // Active filename. + supervisor_ble_transfer_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_transfer_uuid, 0x0200, file_transfer_base_uuid); + common_hal_bleio_characteristic_construct(&supervisor_ble_transfer_characteristic, + &supervisor_ble_service, + 0, // handle (for remote only) + &supervisor_ble_transfer_uuid, + CHAR_PROP_READ | CHAR_PROP_WRITE_NO_RESPONSE | CHAR_PROP_NOTIFY, + SECURITY_MODE_ENC_NO_MITM, + SECURITY_MODE_ENC_NO_MITM, + BLE_GATTS_VAR_ATTR_LEN_MAX, // max length + false, // fixed length + NULL, // no initial valuen + NULL); + + _common_hal_bleio_packet_buffer_construct( + &_transfer_packet_buffer, &supervisor_ble_transfer_characteristic, + _buffer, PACKET_BUFFER_SIZE, + _outgoing1, _outgoing2, BLE_GATTS_VAR_ATTR_LEN_MAX, + &static_handler_entry); +} + +#define COMMAND_SIZE 1024 + +#define ANY_COMMAND 0x00 +#define THIS_COMMAND 0x01 + +// FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution. +// This function truncates the time the time to a resolution storable by FATFS and fills in the +// FATFS encoded version into fattime. +STATIC uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { + timeutils_struct_time_t tm; + uint64_t seconds_since_epoch = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(input_time); + timeutils_seconds_since_epoch_to_struct_time(seconds_since_epoch, &tm); + uint64_t truncated_time = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970((seconds_since_epoch / 2) * 2 * 1000000000); + + *fattime = ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | + (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + return truncated_time; +} + +// Used by read and write. +STATIC FIL active_file; +STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { + struct read_command *command = (struct read_command *)raw_buf; + size_t header_size = sizeof(struct read_command); + size_t response_size = sizeof(struct read_data); + uint8_t data_buffer[response_size]; + struct read_data response; + response.command = READ_DATA; + response.status = STATUS_OK; + if (command->path_length > (COMMAND_SIZE - response_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->path_length) { + return THIS_COMMAND; + } + + char *path = (char *)((uint8_t *)command) + header_size; + path[command->path_length] = '\0'; + + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + FRESULT result = f_open(fs, &active_file, path, FA_READ); + if (result != FR_OK) { + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); + return ANY_COMMAND; + } + uint32_t total_length = f_size(&active_file); + // Write out the response header. + uint32_t offset = command->chunk_offset; + uint32_t chunk_size = command->chunk_size; + chunk_size = MIN(chunk_size, total_length - offset); + response.chunk_offset = offset; + response.total_length = total_length; + response.data_size = chunk_size; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); + f_lseek(&active_file, offset); + // Write out the chunk contents. We can do this in small pieces because PacketBuffer + // will assemble them into larger packets of its own. + size_t chunk_end = offset + chunk_size; + while (offset < chunk_end) { + size_t quantity_read; + size_t read_amount = MIN(response_size, chunk_end - offset); + f_read(&active_file, data_buffer, read_amount, &quantity_read); + offset += quantity_read; + // TODO: Do something if the read fails + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, data_buffer, quantity_read, NULL, 0); + } + if (offset >= total_length) { + f_close(&active_file); + return ANY_COMMAND; + } + return READ_PACING; +} + +STATIC uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) { + struct read_pacing *command = (struct read_pacing *)raw_buf; + struct read_data response; + response.command = READ_DATA; + response.status = STATUS_OK; + size_t response_size = sizeof(struct read_data); + + uint32_t total_length = f_size(&active_file); + // Write out the response header. + uint32_t chunk_size = MIN(command->chunk_size, total_length - command->chunk_offset); + response.chunk_offset = command->chunk_offset; + response.total_length = total_length; + response.data_size = chunk_size; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); + f_lseek(&active_file, command->chunk_offset); + // Write out the chunk contents. We can do this in small pieces because PacketBuffer + // will assemble them into larger packets of its own. + size_t chunk_offset = 0; + uint8_t data[20]; + while (chunk_offset < chunk_size) { + size_t quantity_read; + size_t read_size = MIN(chunk_size - chunk_offset, sizeof(data)); + FRESULT result = f_read(&active_file, &data, read_size, &quantity_read); + if (quantity_read == 0 || result != FR_OK) { + // TODO: If we can't read everything, then the file must have been shortened. Maybe we + // should return 0s to pad it out. + break; + } + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&data, quantity_read, NULL, 0); + chunk_offset += quantity_read; + } + if ((chunk_offset + chunk_size) >= total_length) { + f_close(&active_file); + return ANY_COMMAND; + } + return READ_PACING; +} + +// Used by write and write data to know when the write is complete. +STATIC size_t total_write_length; +STATIC uint64_t _truncated_time; + +// Returns true if usb is active and replies with an error if so. If not, it grabs +// the USB mass storage lock and returns false. Make sure to release the lock with +// usb_msc_unlock() when the transaction is complete. +STATIC bool _usb_active(void *response, size_t response_size) { + // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. + #if CIRCUITPY_USB && CIRCUITPY_USB_MSC + if (storage_usb_enabled() && !usb_msc_lock()) { + // Status is always the second byte of the response. + ((uint8_t *)response)[1] = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)response, response_size, NULL, 0); + return true; + } + #endif + return false; +} + +STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { + struct write_command *command = (struct write_command *)raw_buf; + size_t header_size = sizeof(struct write_command); + struct write_pacing response; + response.command = WRITE_PACING; + response.status = STATUS_OK; + if (command->path_length > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->path_length) { + return THIS_COMMAND; + } + total_write_length = command->total_length; + + char *path = (char *)command->path; + path[command->path_length] = '\0'; + if (_usb_active(&response, sizeof(struct write_pacing))) { + return ANY_COMMAND; + } + + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + DWORD fattime; + _truncated_time = truncate_time(command->modification_time, &fattime); + override_fattime(fattime); + FRESULT result = f_open(fs, &active_file, path, FA_WRITE | FA_OPEN_ALWAYS); + if (result != FR_OK) { + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + override_fattime(0); + return ANY_COMMAND; + } + // Write out the pacing response. + + // Align the next chunk to a sector boundary. + uint32_t offset = command->offset; + size_t chunk_size = MIN(total_write_length - offset, 512 - (offset % 512)); + // Special case when truncating the file. (Deleting stuff off the end.) + if (chunk_size == 0) { + f_lseek(&active_file, offset); + f_truncate(&active_file); + f_close(&active_file); + override_fattime(0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + } + response.offset = offset; + response.free_space = chunk_size; + response.truncated_time = _truncated_time; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + if (chunk_size == 0) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + return ANY_COMMAND; + } + + return WRITE_DATA; +} + +STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { + struct write_data *command = (struct write_data *)raw_buf; + size_t header_size = sizeof(struct write_data); + struct write_pacing response; + response.command = WRITE_PACING; + response.status = STATUS_OK; + if (command->data_size > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + override_fattime(0); + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->data_size) { + return THIS_COMMAND; + } + uint32_t offset = command->offset; + f_lseek(&active_file, offset); + UINT actual; + f_write(&active_file, command->data, command->data_size, &actual); + if (actual < command->data_size) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + override_fattime(0); + return ANY_COMMAND; + } + offset += command->data_size; + // Align the next chunk to a sector boundary. + size_t chunk_size = MIN(total_write_length - offset, 512); + response.offset = offset; + response.free_space = chunk_size; + response.truncated_time = _truncated_time; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + if (total_write_length == offset) { + f_truncate(&active_file); + f_close(&active_file); + override_fattime(0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + return ANY_COMMAND; + } + return WRITE_DATA; +} + +STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) { + FF_DIR dir; + FRESULT res = f_opendir(fs, &dir, path); + FILINFO file_info; + // Check the stack since we're putting paths on it. + if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { + return FR_INT_ERR; + } + while (res == FR_OK) { + res = f_readdir(&dir, &file_info); + if (res != FR_OK || file_info.fname[0] == '\0') { + break; + } + size_t pathlen = strlen(path); + size_t fnlen = strlen(file_info.fname); + TCHAR full_path[pathlen + 1 + fnlen]; + memcpy(full_path, path, pathlen); + full_path[pathlen] = '/'; + size_t full_pathlen = pathlen + 1 + fnlen; + memcpy(full_path + pathlen + 1, file_info.fname, fnlen); + full_path[full_pathlen] = '\0'; + if ((file_info.fattrib & AM_DIR) != 0) { + res = _delete_directory_contents(fs, full_path); + } + if (res != FR_OK) { + break; + } + res = f_unlink(fs, full_path); + } + f_closedir(&dir); + return res; +} + +STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { + const struct delete_command *command = (struct delete_command *)raw_buf; + size_t header_size = sizeof(struct delete_command); + struct delete_status response; + response.command = DELETE_STATUS; + response.status = STATUS_OK; + if (command->path_length > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); + return ANY_COMMAND; + } + if (_usb_active(&response, sizeof(struct delete_status))) { + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->path_length) { + return THIS_COMMAND; + } + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + char *path = (char *)((uint8_t *)command) + header_size; + path[command->path_length] = '\0'; + FILINFO file; + FRESULT result = f_stat(fs, path, &file); + if (result == FR_OK) { + if ((file.fattrib & AM_DIR) != 0) { + result = _delete_directory_contents(fs, path); + } + if (result == FR_OK) { + result = f_unlink(fs, path); + } + } + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + if (result != FR_OK) { + response.status = STATUS_ERROR; + } + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + } + return ANY_COMMAND; +} + +// NULL-terminate the path and remove any trailing /. Older versions of the +// protocol require it but newer ones do not. +STATIC void _terminate_path(char *path, size_t path_length) { + // -1 because fatfs doesn't want a trailing / + if (path[path_length - 1] == '/') { + path[path_length - 1] = '\0'; + } else { + path[path_length] = '\0'; + } +} + +STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { + const struct mkdir_command *command = (struct mkdir_command *)raw_buf; + size_t header_size = sizeof(struct mkdir_command); + struct mkdir_status response; + response.command = MKDIR_STATUS; + response.status = STATUS_OK; + if (command->path_length > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); + return ANY_COMMAND; + } + if (_usb_active(&response, sizeof(struct mkdir_status))) { + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->path_length) { + return THIS_COMMAND; + } + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + char *path = (char *)command->path; + _terminate_path(path, command->path_length); + + DWORD fattime; + response.truncated_time = truncate_time(command->modification_time, &fattime); + override_fattime(fattime); + FRESULT result = f_mkdir(fs, path); + override_fattime(0); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + if (result != FR_OK) { + response.status = STATUS_ERROR; + } + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + } + return ANY_COMMAND; +} + +STATIC void send_listdir_entry_header(const struct listdir_entry *entry, mp_int_t max_packet_size) { + mp_int_t response_size = sizeof(struct listdir_entry); + if (max_packet_size >= response_size) { + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)entry, response_size, NULL, 0); + return; + } + // Split into 16 + 12 size packets to fit into 20 byte minimum packet size. + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)entry, 16, NULL, 0); + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, ((const uint8_t *)entry) + 16, response_size - 16, NULL, 0); +} + +STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { + const struct listdir_command *command = (struct listdir_command *)raw_buf; + struct listdir_entry *entry = (struct listdir_entry *)raw_buf; + size_t header_size = sizeof(struct listdir_command); + mp_int_t max_packet_size = common_hal_bleio_packet_buffer_get_outgoing_packet_length(&_transfer_packet_buffer); + if (max_packet_size < 0) { + // -1 means we're disconnected + return ANY_COMMAND; + } + // We reuse the command buffer so that we can produce long packets without + // making the stack large. + if (command->path_length > (COMMAND_SIZE - header_size - 1)) { // -1 for the null we'll write + // TODO: throw away any more packets of path. + entry->command = LISTDIR_ENTRY; + entry->status = STATUS_ERROR; + send_listdir_entry_header(entry, max_packet_size); + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + command->path_length) { + return THIS_COMMAND; + } + + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + char *path = (char *)&command->path; + _terminate_path(path, command->path_length); + // mp_printf(&mp_plat_print, "list %s\n", path); + FF_DIR dir; + FRESULT res = f_opendir(fs, &dir, path); + + entry->command = LISTDIR_ENTRY; + entry->status = STATUS_OK; + entry->path_length = 0; + entry->entry_number = 0; + entry->entry_count = 0; + entry->flags = 0; + + if (res != FR_OK) { + entry->status = STATUS_ERROR_NO_FILE; + send_listdir_entry_header(entry, max_packet_size); + return ANY_COMMAND; + } + FILINFO file_info; + res = f_readdir(&dir, &file_info); + char *fn = file_info.fname; + size_t total_entries = 0; + while (res == FR_OK && fn[0] != 0) { + res = f_readdir(&dir, &file_info); + total_entries += 1; + } + // Rewind the directory. + f_readdir(&dir, NULL); + entry->entry_count = total_entries; + for (size_t i = 0; i < total_entries; i++) { + res = f_readdir(&dir, &file_info); + entry->entry_number = i; + uint64_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9), + (file_info.fdate >> 5) & 0xf, + file_info.fdate & 0x1f, + file_info.ftime >> 11, + (file_info.ftime >> 5) & 0x1f, + (file_info.ftime & 0x1f) * 2) * 1000000000ULL; + entry->truncated_time = truncated_time; + if ((file_info.fattrib & AM_DIR) != 0) { + entry->flags = 1; // Directory + entry->file_size = 0; + } else { + entry->flags = 0; + entry->file_size = file_info.fsize; + } + + size_t name_length = strlen(file_info.fname); + entry->path_length = name_length; + send_listdir_entry_header(entry, max_packet_size); + size_t fn_offset = 0; + while (fn_offset < name_length) { + size_t fn_size = MIN(name_length - fn_offset, 4); + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, ((uint8_t *)file_info.fname) + fn_offset, fn_size, NULL, 0); + fn_offset += fn_size; + } + } + f_closedir(&dir); + entry->path_length = 0; + entry->entry_number = entry->entry_count; + entry->flags = 0; + entry->file_size = 0; + send_listdir_entry_header(entry, max_packet_size); + return ANY_COMMAND; +} + +STATIC uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) { + const struct move_command *command = (struct move_command *)raw_buf; + size_t header_size = sizeof(struct move_command); + struct move_status response; + response.command = MOVE_STATUS; + response.status = STATUS_OK; + // +2 for null terminators. + uint32_t total_path_length = command->old_path_length + command->new_path_length + 1; + if (total_path_length > (COMMAND_SIZE - header_size - 1)) { + // TODO: throw away any more packets of path. + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + return ANY_COMMAND; + } + if (_usb_active(&response, sizeof(struct move_status))) { + return ANY_COMMAND; + } + // We need to receive another packet to have the full path. + if (command_len < header_size + total_path_length) { + return THIS_COMMAND; + } + FATFS *fs = &((fs_user_mount_t *)MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + char *old_path = (char *)command->paths; + old_path[command->old_path_length] = '\0'; + + char *new_path = old_path + command->old_path_length + 1; + new_path[command->new_path_length] = '\0'; + + // mp_printf(&mp_plat_print, "move %s to %s\n", old_path, new_path); + + FRESULT result = f_rename(fs, old_path, new_path); + #if CIRCUITPY_USB_MSC + usb_msc_unlock(); + #endif + if (result != FR_OK) { + response.status = STATUS_ERROR; + } + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); + if (result == FR_OK) { + // Don't reload until everything is written out of the packet buffer. + common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); + } + return ANY_COMMAND; +} + +// Background state that must live across background calls. After the _process +// helpers to force them to not use them. +STATIC uint8_t current_command[COMMAND_SIZE] __attribute__ ((aligned(4))); +STATIC volatile size_t current_offset; +STATIC uint8_t next_command; +STATIC bool running = false; +void supervisor_bluetooth_file_transfer_background(void) { + if (running) { + return; + } + running = true; + mp_int_t size = 1; + while (size > 0) { + size = common_hal_bleio_packet_buffer_readinto(&_transfer_packet_buffer, current_command + current_offset, COMMAND_SIZE - current_offset); + + if (size == 0) { + break; + } + autoreload_suspend(AUTORELOAD_SUSPEND_BLE); + // TODO: If size < 0 return an error. + current_offset += size; + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "buffer[:%d]:", current_offset); + for (size_t i = 0; i < current_offset; i++) { + mp_printf(&mp_plat_print, " (%x %c)", current_command[i], current_command[i]); + } + mp_printf(&mp_plat_print, "\n"); + #endif + uint8_t current_state = current_command[0]; + // mp_printf(&mp_plat_print, "current command 0x%02x\n", current_state); + // Check for protocol error. + if (next_command != ANY_COMMAND && next_command != THIS_COMMAND && ((current_state & 0xf) != 0) && current_state != next_command) { + uint8_t response[2]; + response[0] = next_command; + response[1] = STATUS_ERROR_PROTOCOL; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, response, 2, NULL, 0); + autoreload_resume(AUTORELOAD_SUSPEND_BLE); + break; + } + switch (current_state) { + case READ: + next_command = _process_read(current_command, current_offset); + break; + case READ_PACING: + next_command = _process_read_pacing(current_command, current_offset); + break; + case WRITE: + next_command = _process_write(current_command, current_offset); + break; + case WRITE_DATA: + next_command = _process_write_data(current_command, current_offset); + break; + case DELETE: + next_command = _process_delete(current_command, current_offset); + break; + case MKDIR: + next_command = _process_mkdir(current_command, current_offset); + break; + case LISTDIR: + next_command = _process_listdir(current_command, current_offset); + break; + case MOVE: + next_command = _process_move(current_command, current_offset); + break; + } + // Preserve the offset if we are waiting for more from this command. + if (next_command != THIS_COMMAND) { + current_offset = 0; + } + if (next_command == ANY_COMMAND) { + autoreload_resume(AUTORELOAD_SUSPEND_BLE); + // Trigger a reload if the command may have mutated the file system. + if (current_state == WRITE || + current_state == WRITE_DATA || + current_state == DELETE || + current_state == MKDIR || + current_state == MOVE) { + autoreload_trigger(); + } + } + } + running = false; +} + +void supervisor_bluetooth_file_transfer_disconnected(void) { + next_command = ANY_COMMAND; + current_offset = 0; + f_close(&active_file); + autoreload_resume(AUTORELOAD_SUSPEND_BLE); +} diff --git a/circuitpython/supervisor/shared/bluetooth/file_transfer.h b/circuitpython/supervisor/shared/bluetooth/file_transfer.h new file mode 100644 index 0000000..e27924e --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/file_transfer.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_H + +#include <stdbool.h> + +void supervisor_bluetooth_file_transfer_background(void); +void supervisor_start_bluetooth_file_transfer(void); +void supervisor_bluetooth_file_transfer_disconnected(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_H diff --git a/circuitpython/supervisor/shared/bluetooth/file_transfer_protocol.h b/circuitpython/supervisor/shared/bluetooth/file_transfer_protocol.h new file mode 100644 index 0000000..3bc1f61 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/file_transfer_protocol.h @@ -0,0 +1,180 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_PROTOCOL_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_PROTOCOL_H + +#include <stdint.h> + +// See https://github.com/adafruit/Adafruit_CircuitPython_BLE_File_Transfer +// for full protocol documentation and a Python client API. + +// Each struct is packed so that no padding is added by the compiler. (structs +// may having padding at the end in order to align a particular element when in +// an array of the struct.) So, be careful that types added are aligned. Otherwise, +// the compiler may generate more code than necessary. + +// 0x00 - 0x0f are never used by the protocol as a command +#define READ 0x10 +struct read_command { + uint8_t command; + uint8_t reserved; + uint16_t path_length; + uint32_t chunk_offset; + uint32_t chunk_size; + uint8_t path[]; +} __attribute__((packed)); + +#define READ_DATA 0x11 +struct read_data { + uint8_t command; + uint8_t status; + uint16_t reserved; + uint32_t chunk_offset; + uint32_t total_length; + uint32_t data_size; + uint8_t data[]; +} __attribute__((packed)); + +#define READ_PACING 0x12 +struct read_pacing { + uint8_t command; + uint8_t status; + uint16_t reserved; + uint32_t chunk_offset; + uint32_t chunk_size; +} __attribute__((packed)); + +#define WRITE 0x20 +struct write_command { + uint8_t command; + uint8_t reserved; + uint16_t path_length; + uint32_t offset; + uint64_t modification_time; + uint32_t total_length; + uint8_t path[]; +} __attribute__((packed)); + +#define WRITE_PACING 0x21 +struct write_pacing { + uint8_t command; + uint8_t status; + uint16_t reserved; + uint32_t offset; + uint64_t truncated_time; + uint32_t free_space; +} __attribute__((packed)); + +#define WRITE_DATA 0x22 +struct write_data { + uint8_t command; + uint8_t status; + uint16_t reserved; + uint32_t offset; + uint32_t data_size; + uint8_t data[]; +} __attribute__((packed)); + +#define DELETE 0x30 +struct delete_command { + uint8_t command; + uint8_t reserved; + uint16_t path_length; + uint8_t path[]; +} __attribute__((packed)); + +#define DELETE_STATUS 0x31 +struct delete_status { + uint8_t command; + uint8_t status; +} __attribute__((packed)); + +#define MKDIR 0x40 +struct mkdir_command { + uint8_t command; + uint8_t reserved; + uint16_t path_length; + uint32_t reserved2; + uint64_t modification_time; + uint8_t path[]; +} __attribute__((packed)); + +#define MKDIR_STATUS 0x41 +struct mkdir_status { + uint8_t command; + uint8_t status; + uint16_t reserved; + uint32_t reserved2; + uint64_t truncated_time; +} __attribute__((packed)); + +#define LISTDIR 0x50 +struct listdir_command { + uint8_t command; + uint8_t reserved; + uint16_t path_length; + uint8_t path[]; +} __attribute__((packed)); + +#define LISTDIR_ENTRY 0x51 +struct listdir_entry { + uint8_t command; + uint8_t status; + uint16_t path_length; + uint32_t entry_number; + uint32_t entry_count; + uint32_t flags; + uint64_t truncated_time; + uint32_t file_size; + uint8_t path[]; +} __attribute__((packed)); + +#define MOVE 0x60 +struct move_command { + uint8_t command; + uint8_t reserved; + uint16_t old_path_length; + uint16_t new_path_length; + // paths is two strings. The first is old_path and then a reserved byte. + // The last path is new_path. + uint8_t paths[]; +} __attribute__((packed)); + +#define MOVE_STATUS 0x61 +struct move_status { + uint8_t command; + uint8_t status; +} __attribute__((packed)); + +#define STATUS_OK 0x01 +#define STATUS_ERROR 0x02 +#define STATUS_ERROR_NO_FILE 0x03 +#define STATUS_ERROR_PROTOCOL 0x04 +#define STATUS_ERROR_READONLY 0x05 + + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_FILE_TRANSFER_PROTOCOL_H diff --git a/circuitpython/supervisor/shared/bluetooth/serial.c b/circuitpython/supervisor/shared/bluetooth/serial.c new file mode 100644 index 0000000..2edf3c7 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/serial.c @@ -0,0 +1,211 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 <string.h> + +#include "genhdr/mpversion.h" +#include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Adapter.h" +#include "shared-bindings/_bleio/Characteristic.h" +#include "shared-bindings/_bleio/CharacteristicBuffer.h" +#include "shared-bindings/_bleio/PacketBuffer.h" +#include "shared-bindings/_bleio/Service.h" +#include "shared-bindings/_bleio/UUID.h" +#include "shared-module/storage/__init__.h" +#include "supervisor/shared/bluetooth/serial.h" + +#include "common-hal/_bleio/__init__.h" + +#include "py/mpstate.h" + +STATIC bleio_service_obj_t supervisor_ble_circuitpython_service; +STATIC bleio_uuid_obj_t supervisor_ble_circuitpython_service_uuid; +STATIC bleio_characteristic_obj_t supervisor_ble_circuitpython_rx_characteristic; +STATIC bleio_uuid_obj_t supervisor_ble_circuitpython_rx_uuid; +STATIC bleio_characteristic_obj_t supervisor_ble_circuitpython_tx_characteristic; +STATIC bleio_uuid_obj_t supervisor_ble_circuitpython_tx_uuid; +STATIC bleio_characteristic_obj_t supervisor_ble_circuitpython_version_characteristic; +STATIC bleio_uuid_obj_t supervisor_ble_circuitpython_version_uuid; + +// This is the base UUID for the CircuitPython service. +const uint8_t circuitpython_base_uuid[16] = {0x6e, 0x68, 0x74, 0x79, 0x50, 0x74, 0x69, 0x75, 0x63, 0x72, 0x69, 0x43, 0x00, 0x00, 0xaf, 0xad }; + +STATIC mp_obj_list_t characteristic_list; +STATIC mp_obj_t characteristic_list_items[3]; + +STATIC uint32_t _outgoing1[BLE_GATTS_VAR_ATTR_LEN_MAX / 4]; +STATIC uint32_t _outgoing2[BLE_GATTS_VAR_ATTR_LEN_MAX / 4]; +STATIC ble_drv_evt_handler_entry_t rx_static_handler_entry; +STATIC ble_drv_evt_handler_entry_t tx_static_handler_entry; +STATIC bleio_packet_buffer_obj_t _tx_packet_buffer; +STATIC uint32_t _incoming[64]; +STATIC bleio_characteristic_buffer_obj_t _rx_buffer; + +// Internal enabling so we can disable while printing BLE debugging. +STATIC bool _enabled; + +void supervisor_start_bluetooth_serial(void) { + supervisor_ble_circuitpython_service_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_circuitpython_service_uuid, 0x0001, circuitpython_base_uuid); + + // We know we'll only be N characteristics so we can statically allocate it. + characteristic_list.base.type = &mp_type_list; + characteristic_list.alloc = sizeof(characteristic_list_items) / sizeof(characteristic_list_items[0]); + characteristic_list.len = 0; + characteristic_list.items = characteristic_list_items; + mp_seq_clear(characteristic_list.items, 0, characteristic_list.alloc, sizeof(*characteristic_list.items)); + + supervisor_ble_circuitpython_service.base.type = &bleio_service_type; + _common_hal_bleio_service_construct(&supervisor_ble_circuitpython_service, &supervisor_ble_circuitpython_service_uuid, false /* is secondary */, &characteristic_list); + + // RX + supervisor_ble_circuitpython_rx_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_circuitpython_rx_uuid, 0x0002, circuitpython_base_uuid); + common_hal_bleio_characteristic_construct(&supervisor_ble_circuitpython_rx_characteristic, + &supervisor_ble_circuitpython_service, + 0, // handle (for remote only) + &supervisor_ble_circuitpython_rx_uuid, + CHAR_PROP_WRITE | CHAR_PROP_WRITE_NO_RESPONSE, + SECURITY_MODE_NO_ACCESS, + SECURITY_MODE_ENC_NO_MITM, + BLE_GATTS_VAR_ATTR_LEN_MAX, // max length + false, // fixed length + NULL, // no initial value + NULL); + + // TX + supervisor_ble_circuitpython_tx_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_circuitpython_tx_uuid, 0x0003, circuitpython_base_uuid); + common_hal_bleio_characteristic_construct(&supervisor_ble_circuitpython_tx_characteristic, + &supervisor_ble_circuitpython_service, + 0, // handle (for remote only) + &supervisor_ble_circuitpython_tx_uuid, + CHAR_PROP_NOTIFY, + SECURITY_MODE_ENC_NO_MITM, + SECURITY_MODE_NO_ACCESS, + BLE_GATTS_VAR_ATTR_LEN_MAX, // max length + false, // fixed length + NULL, // no initial value + NULL); + + // Version number + const char *version = MICROPY_GIT_TAG; + mp_buffer_info_t bufinfo; + bufinfo.buf = (uint8_t *)version; + bufinfo.len = strlen(version); + + supervisor_ble_circuitpython_version_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&supervisor_ble_circuitpython_version_uuid, 0x0100, circuitpython_base_uuid); + common_hal_bleio_characteristic_construct(&supervisor_ble_circuitpython_version_characteristic, + &supervisor_ble_circuitpython_service, + 0, // handle (for remote only) + &supervisor_ble_circuitpython_version_uuid, + CHAR_PROP_READ, + SECURITY_MODE_OPEN, + SECURITY_MODE_NO_ACCESS, + bufinfo.len, // max length + true, // fixed length + NULL, // no initial value + NULL); // no description + + common_hal_bleio_characteristic_set_value(&supervisor_ble_circuitpython_version_characteristic, &bufinfo); + + // Use a PacketBuffer to transmit so that we glom characters to transmit + // together and save BLE overhead. + _common_hal_bleio_packet_buffer_construct( + &_tx_packet_buffer, &supervisor_ble_circuitpython_tx_characteristic, + NULL, 0, + _outgoing1, _outgoing2, BLE_GATTS_VAR_ATTR_LEN_MAX, + &tx_static_handler_entry); + + // Use a CharacteristicBuffer for rx so we can read a single character at a time. + _common_hal_bleio_characteristic_buffer_construct(&_rx_buffer, + &supervisor_ble_circuitpython_rx_characteristic, + 0.1f, + (uint8_t *)_incoming, sizeof(_incoming) * sizeof(uint32_t), + &rx_static_handler_entry); + + _enabled = true; +} + +void supervisor_stop_bluetooth_serial(void) { + if (common_hal_bleio_packet_buffer_deinited(&_tx_packet_buffer)) { + return; + } + if (!_enabled) { + return; + } + common_hal_bleio_packet_buffer_flush(&_tx_packet_buffer); +} + +bool ble_serial_connected(void) { + return _tx_packet_buffer.conn_handle != BLE_CONN_HANDLE_INVALID; +} + +bool ble_serial_available(void) { + return _enabled && + !common_hal_bleio_characteristic_buffer_deinited(&_rx_buffer) && + common_hal_bleio_characteristic_buffer_rx_characters_available(&_rx_buffer); +} + +char ble_serial_read_char(void) { + if (common_hal_bleio_characteristic_buffer_deinited(&_rx_buffer)) { + return -1; + } + if (!_enabled) { + return -1; + } + uint8_t c; + common_hal_bleio_characteristic_buffer_read(&_rx_buffer, &c, 1, NULL); + return c; +} + +void ble_serial_write(const char *text, size_t len) { + if (common_hal_bleio_packet_buffer_deinited(&_tx_packet_buffer)) { + return; + } + if (!_enabled) { + return; + } + size_t sent = 0; + while (sent < len) { + uint16_t packet_size = MIN(len - sent, (size_t)common_hal_bleio_packet_buffer_get_outgoing_packet_length(&_tx_packet_buffer)); + mp_int_t written = common_hal_bleio_packet_buffer_write(&_tx_packet_buffer, (const uint8_t *)text + sent, packet_size, NULL, 0); + // Error, so we drop characters to transmit. + if (written < 0) { + break; + } + sent += written; + } +} + +void ble_serial_enable(void) { + _enabled = true; +} + +void ble_serial_disable(void) { + _enabled = false; +} diff --git a/circuitpython/supervisor/shared/bluetooth/serial.h b/circuitpython/supervisor/shared/bluetooth/serial.h new file mode 100644 index 0000000..cc73a15 --- /dev/null +++ b/circuitpython/supervisor/shared/bluetooth/serial.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_SUPERVISOR_SHARED_BLUETOOTH_SERIAL_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_SERIAL_H + +#include <stdbool.h> + +void supervisor_start_bluetooth_serial(void); +void supervisor_stop_bluetooth_serial(void); + +bool ble_serial_connected(void); +bool ble_serial_available(void); +char ble_serial_read_char(void); +void ble_serial_write(const char *text, size_t len); +void ble_serial_enable(void); +void ble_serial_disable(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BLUETOOTH_SERIAL_H diff --git a/circuitpython/supervisor/shared/board.c b/circuitpython/supervisor/shared/board.c new file mode 100644 index 0000000..427c179 --- /dev/null +++ b/circuitpython/supervisor/shared/board.c @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Dan Halbert 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 "supervisor/shared/board.h" + +#if CIRCUITPY_DIGITALIO && CIRCUITPY_NEOPIXEL_WRITE + +#include <string.h> + +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/neopixel_write/__init__.h" + +void board_reset_user_neopixels(const mcu_pin_obj_t *pin, size_t count) { + // Turn off on-board NeoPixel string + uint8_t empty[count * 3]; + memset(empty, 0, count * 3); + digitalio_digitalinout_obj_t neopixel_pin; + common_hal_digitalio_digitalinout_construct(&neopixel_pin, pin); + common_hal_digitalio_digitalinout_switch_to_output(&neopixel_pin, false, + DRIVE_MODE_PUSH_PULL); + common_hal_neopixel_write(&neopixel_pin, empty, count * 3); + common_hal_digitalio_digitalinout_deinit(&neopixel_pin); +} + +#endif diff --git a/circuitpython/supervisor/shared/board.h b/circuitpython/supervisor/shared/board.h new file mode 100644 index 0000000..def5f48 --- /dev/null +++ b/circuitpython/supervisor/shared/board.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Dan Halbert 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_SUPERVISOR_SHARED_BOARD_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_BOARD_H + +#include <stddef.h> + +#include "shared-bindings/microcontroller/Pin.h" + +void board_reset_user_neopixels(const mcu_pin_obj_t *pin, size_t count); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_BOARD_H diff --git a/circuitpython/supervisor/shared/cpu.c b/circuitpython/supervisor/shared/cpu.c new file mode 100644 index 0000000..d1e4114 --- /dev/null +++ b/circuitpython/supervisor/shared/cpu.c @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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 <stdint.h> + +#include "supervisor/shared/cpu.h" + +bool cpu_interrupt_active(void) { + #if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'M') + // Check VECTACTIVE in ICSR. We don't need to disable interrupts because if + // one occurs after we read, we won't continue until it is resolved. + return (*((volatile uint32_t *)0xE000ED04) & 0x1ff) != 0; + #else + // We don't know. + return false; + #endif +} diff --git a/circuitpython/supervisor/shared/cpu.h b/circuitpython/supervisor/shared/cpu.h new file mode 100644 index 0000000..9e2bed1 --- /dev/null +++ b/circuitpython/supervisor/shared/cpu.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 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_SUPERVISOR_SHARED_CPU_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_CPU_H + +#include <stdbool.h> +#include <stddef.h> + +// True when we're in an interrupt handler. +bool cpu_interrupt_active(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_CPU_H diff --git a/circuitpython/supervisor/shared/display.c b/circuitpython/supervisor/shared/display.c new file mode 100644 index 0000000..a8a9b05 --- /dev/null +++ b/circuitpython/supervisor/shared/display.c @@ -0,0 +1,347 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "supervisor/shared/display.h" + +#include <string.h> + +#include "py/mpstate.h" +#include "shared-bindings/displayio/Bitmap.h" +#include "shared-bindings/displayio/Group.h" +#include "shared-bindings/displayio/Palette.h" +#include "shared-bindings/displayio/TileGrid.h" +#include "supervisor/memory.h" + +#if CIRCUITPY_RGBMATRIX +#include "shared-module/displayio/__init__.h" +#endif + +#if CIRCUITPY_SHARPDISPLAY +#include "shared-module/displayio/__init__.h" +#include "shared-bindings/sharpdisplay/SharpMemoryFramebuffer.h" +#include "shared-module/sharpdisplay/SharpMemoryFramebuffer.h" +#endif + +#if CIRCUITPY_REPL_LOGO +extern uint32_t blinka_bitmap_data[]; +extern displayio_bitmap_t blinka_bitmap; +#endif +extern displayio_group_t circuitpython_splash; + +#if CIRCUITPY_TERMINALIO +static supervisor_allocation *tilegrid_tiles = NULL; +#endif + +void supervisor_start_terminal(uint16_t width_px, uint16_t height_px) { + // Default the scale to 2 because we may show blinka without the terminal for + // languages that don't have font support. + uint8_t scale = 2; + + #if CIRCUITPY_TERMINALIO + displayio_tilegrid_t *grid = &supervisor_terminal_text_grid; + bool tall = height_px > width_px; + bool reset_tiles = false; + #if CIRCUITPY_REPL_LOGO + uint16_t terminal_width_px = tall ? width_px : width_px - blinka_bitmap.width; + uint16_t terminal_height_px = tall ? height_px - blinka_bitmap.height : height_px; + #else + uint16_t terminal_width_px = width_px; + uint16_t terminal_height_px = height_px; + #endif + uint16_t width_in_tiles = terminal_width_px / grid->tile_width; + // determine scale based on h + if (width_in_tiles < 80) { + scale = 1; + } + + width_in_tiles = terminal_width_px / (grid->tile_width * scale); + if (width_in_tiles < 1) { + width_in_tiles = 1; + } + uint16_t height_in_tiles = terminal_height_px / (grid->tile_height * scale); + uint16_t remaining_pixels = tall ? 0 : terminal_height_px % (grid->tile_height * scale); + if (height_in_tiles < 1 || remaining_pixels > 0) { + height_in_tiles += 1; + } + + uint16_t total_tiles = width_in_tiles * height_in_tiles; + + // check if the terminal tile dimensions are the same + if ((grid->width_in_tiles != width_in_tiles) || + (grid->height_in_tiles != height_in_tiles)) { + reset_tiles = true; + } + // Reuse the previous allocation if possible + if (tilegrid_tiles) { + if (get_allocation_length(tilegrid_tiles) != align32_size(total_tiles)) { + free_memory(tilegrid_tiles); + tilegrid_tiles = NULL; + reset_tiles = true; + } + } + if (!tilegrid_tiles) { + tilegrid_tiles = allocate_memory(align32_size(total_tiles), false, true); + reset_tiles = true; + if (!tilegrid_tiles) { + return; + } + } + + if (reset_tiles) { + uint8_t *tiles = (uint8_t *)tilegrid_tiles->ptr; + + #if CIRCUITPY_REPL_LOGO + grid->y = tall ? blinka_bitmap.height : 0; + grid->x = tall ? 0 : blinka_bitmap.width; + #else + grid->y = 0; + grid->x = 0; + #endif + grid->top_left_y = 0; + if (remaining_pixels > 0) { + grid->y -= (grid->tile_height - remaining_pixels); + } + grid->width_in_tiles = width_in_tiles; + grid->height_in_tiles = height_in_tiles; + assert(width_in_tiles > 0); + assert(height_in_tiles > 0); + grid->pixel_width = width_in_tiles * grid->tile_width; + grid->pixel_height = height_in_tiles * grid->tile_height; + grid->tiles = tiles; + + grid->full_change = true; + + common_hal_terminalio_terminal_construct(&supervisor_terminal, grid, &supervisor_terminal_font); + } + #endif + + circuitpython_splash.scale = scale; +} + +void supervisor_stop_terminal(void) { + #if CIRCUITPY_TERMINALIO + if (tilegrid_tiles != NULL) { + free_memory(tilegrid_tiles); + tilegrid_tiles = NULL; + supervisor_terminal_text_grid.tiles = NULL; + supervisor_terminal.tilegrid = NULL; + } + #endif +} + +void supervisor_display_move_memory(void) { + #if CIRCUITPY_TERMINALIO + if (tilegrid_tiles != NULL) { + supervisor_terminal_text_grid.tiles = (uint8_t *)tilegrid_tiles->ptr; + } else { + supervisor_terminal_text_grid.tiles = NULL; + } + #endif + + #if CIRCUITPY_DISPLAYIO + for (uint8_t i = 0; i < CIRCUITPY_DISPLAY_LIMIT; i++) { + #if CIRCUITPY_RGBMATRIX + if (displays[i].rgbmatrix.base.type == &rgbmatrix_RGBMatrix_type) { + rgbmatrix_rgbmatrix_obj_t *pm = &displays[i].rgbmatrix; + common_hal_rgbmatrix_rgbmatrix_reconstruct(pm, NULL); + } + #endif + #if CIRCUITPY_SHARPDISPLAY + if (displays[i].bus_base.type == &sharpdisplay_framebuffer_type) { + sharpdisplay_framebuffer_obj_t *sharp = &displays[i].sharpdisplay; + common_hal_sharpdisplay_framebuffer_reconstruct(sharp); + } + #endif + } + #endif +} + +#if CIRCUITPY_REPL_LOGO +uint32_t blinka_bitmap_data[32] = { + 0x00000011, 0x11000000, + 0x00000111, 0x53100000, + 0x00000111, 0x56110000, + 0x00000111, 0x11140000, + 0x00000111, 0x20002000, + 0x00000011, 0x13000000, + 0x00000001, 0x11200000, + 0x00000000, 0x11330000, + 0x00000000, 0x01122000, + 0x00001111, 0x44133000, + 0x00032323, 0x24112200, + 0x00111114, 0x44113300, + 0x00323232, 0x34112200, + 0x11111144, 0x44443300, + 0x11111111, 0x11144401, + 0x23232323, 0x21111110 +}; + +displayio_bitmap_t blinka_bitmap = { + .base = {.type = &displayio_bitmap_type }, + .width = 16, + .height = 16, + .data = blinka_bitmap_data, + .stride = 2, + .bits_per_value = 4, + .x_shift = 3, + .x_mask = 0x7, + .bitmask = 0xf, + .read_only = true +}; + +_displayio_color_t blinka_colors[7] = { + { + .rgb888 = 0x000000, + .rgb565 = 0x0000, + .luma = 0x00, + .chroma = 0, + .transparent = true + }, + { + .rgb888 = 0x8428bc, + .rgb565 = 0x8978, + .luma = 0xff, // We cheat the luma here. It is actually 0x60 + .hue = 184, + .chroma = 148 + }, + { + .rgb888 = 0xff89bc, + .rgb565 = 0xFCB8, + .luma = 0xb5, + .hue = 222, + .chroma = 118 + }, + { + .rgb888 = 0x7beffe, + .rgb565 = 0x869F, + .luma = 0xe0, + .hue = 124, + .chroma = 131 + }, + { + .rgb888 = 0x51395f, + .rgb565 = 0x5A0D, + .luma = 0x47, + .hue = 185, + .chroma = 38 + }, + { + .rgb888 = 0xffffff, + .rgb565 = 0xffff, + .luma = 0xff, + .chroma = 0 + }, + { + .rgb888 = 0x0736a0, + .rgb565 = 0x01f5, + .luma = 0x44, + .hue = 147, + .chroma = 153 + }, +}; + +displayio_palette_t blinka_palette = { + .base = {.type = &displayio_palette_type }, + .colors = blinka_colors, + .color_count = 7, + .needs_refresh = false +}; + +displayio_tilegrid_t blinka_sprite = { + .base = {.type = &displayio_tilegrid_type }, + .bitmap = &blinka_bitmap, + .pixel_shader = &blinka_palette, + .x = 0, + .y = 0, + .pixel_width = 16, + .pixel_height = 16, + .bitmap_width_in_tiles = 1, + .width_in_tiles = 1, + .height_in_tiles = 1, + .tile_width = 16, + .tile_height = 16, + .top_left_x = 16, + .top_left_y = 16, + .tiles = 0, + .partial_change = false, + .full_change = false, + .hidden = false, + .hidden_by_parent = false, + .moved = false, + .inline_tiles = true, + .in_group = true +}; +#endif + +#if CIRCUITPY_TERMINALIO +#if CIRCUITPY_REPL_LOGO +mp_obj_t members[] = { &blinka_sprite, &supervisor_terminal_text_grid, }; +mp_obj_list_t splash_children = { + .base = {.type = &mp_type_list }, + .alloc = 2, + .len = 2, + .items = members, +}; +#else +mp_obj_t members[] = { &supervisor_terminal_text_grid, }; +mp_obj_list_t splash_children = { + .base = {.type = &mp_type_list }, + .alloc = 1, + .len = 1, + .items = members, +}; +#endif +#else +#if CIRCUITPY_REPL_LOGO +mp_obj_t members[] = { &blinka_sprite }; +mp_obj_list_t splash_children = { + .base = {.type = &mp_type_list }, + .alloc = 1, + .len = 1, + .items = members, +}; +#else +mp_obj_t members[] = {}; +mp_obj_list_t splash_children = { + .base = {.type = &mp_type_list }, + .alloc = 0, + .len = 0, + .items = members, +}; +#endif +#endif + +displayio_group_t circuitpython_splash = { + .base = {.type = &displayio_group_type }, + .x = 0, + .y = 0, + .scale = 2, + .members = &splash_children, + .item_removed = false, + .in_group = false, + .hidden = false, + .hidden_by_parent = false +}; diff --git a/circuitpython/supervisor/shared/display.h b/circuitpython/supervisor/shared/display.h new file mode 100644 index 0000000..4110cfe --- /dev/null +++ b/circuitpython/supervisor/shared/display.h @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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_SUPERVISOR_SHARED_DISPLAY_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_DISPLAY_H + +#include <stdint.h> + +#if CIRCUITPY_TERMINALIO + +#include "shared-bindings/displayio/Bitmap.h" +#include "shared-bindings/displayio/TileGrid.h" +#include "shared-bindings/fontio/BuiltinFont.h" +#include "shared-bindings/terminalio/Terminal.h" + +// These are autogenerated resources. + +// This is fixed so it doesn't need to be in RAM. +extern const fontio_builtinfont_t supervisor_terminal_font; + +// These will change so they must live in RAM. +extern displayio_bitmap_t supervisor_terminal_font_bitmap; +extern displayio_tilegrid_t supervisor_terminal_text_grid; +extern terminalio_terminal_obj_t supervisor_terminal; + +#endif + +void supervisor_start_terminal(uint16_t width_px, uint16_t height_px); +void supervisor_stop_terminal(void); + +void supervisor_display_move_memory(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_DISPLAY_H diff --git a/circuitpython/supervisor/shared/external_flash/common_commands.h b/circuitpython/supervisor/shared/external_flash/common_commands.h new file mode 100644 index 0000000..37efd8c --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/common_commands.h @@ -0,0 +1,48 @@ +/* + * 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_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H +#define MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H + +#define CMD_READ_JEDEC_ID 0x9f +#define CMD_READ_DATA 0x03 +#define CMD_FAST_READ_DATA 0x0B +#define CMD_SECTOR_ERASE 0x20 +// #define CMD_SECTOR_ERASE CMD_READ_JEDEC_ID +#define CMD_DISABLE_WRITE 0x04 +#define CMD_ENABLE_WRITE 0x06 +#define CMD_PAGE_PROGRAM 0x02 +// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID +#define CMD_READ_STATUS 0x05 +#define CMD_READ_STATUS2 0x35 +#define CMD_WRITE_STATUS_BYTE1 0x01 +#define CMD_WRITE_STATUS_BYTE2 0x31 +#define CMD_DUAL_READ 0x3b +#define CMD_QUAD_READ 0x6b +#define CMD_ENABLE_RESET 0x66 +#define CMD_RESET 0x99 +#define CMD_WAKE 0xab + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H diff --git a/circuitpython/supervisor/shared/external_flash/device.h b/circuitpython/supervisor/shared/external_flash/device.h new file mode 100644 index 0000000..bbf6bfd --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/device.h @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICE_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICE_H + +#include <stdbool.h> +#include <stdint.h> + +typedef struct { + uint32_t total_size; + uint16_t start_up_time_us; + + // Three response bytes to 0x9f JEDEC ID command. + uint8_t manufacturer_id; + uint8_t memory_type; + uint8_t capacity; + + // Max clock speed for all operations and the fastest read mode. + uint8_t max_clock_speed_mhz; + + // Bitmask for Quad Enable bit if present. 0x00 otherwise. This is for the highest byte in the + // status register. + uint8_t quad_enable_bit_mask; + + bool has_sector_protection : 1; + + // Supports the 0x0b fast read command with 8 dummy cycles. + bool supports_fast_read : 1; + + // Supports the fast read, quad output command 0x6b with 8 dummy cycles. + bool supports_qspi : 1; + + // Supports the quad input page program command 0x32. This is known as 1-1-4 because it only + // uses all four lines for data. + bool supports_qspi_writes : 1; + + // Requires a separate command 0x31 to write to the second byte of the status register. + // Otherwise two byte are written via 0x01. + bool write_status_register_split : 1; + + // True when the status register is a single byte. This implies the Quad Enable bit is in the + // first byte and the Read Status Register 2 command (0x35) is unsupported. + bool single_status_byte : 1; + + // Does not support using a ready bit within the status register + bool no_ready_bit : 1; + + // Does not support the erase command (0x20) + bool no_erase_cmd : 1; + + // Device does not have a reset command + bool no_reset_cmd : 1; +} external_flash_device; + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICE_H diff --git a/circuitpython/supervisor/shared/external_flash/devices.h.jinja b/circuitpython/supervisor/shared/external_flash/devices.h.jinja new file mode 100644 index 0000000..7cad90d --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/devices.h.jinja @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICES_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICES_H + +{% for device in nvms %} +#define {{ device.sku }} { \ + .total_size = {{ device.total_size }}, \ + .start_up_time_us = {{ device.start_up_time_us }}, \ + .manufacturer_id = {{ device.manufacturer_id }}, \ + .memory_type = {{ device.memory_type }}, \ + .capacity = {{ device.capacity }}, \ + .max_clock_speed_mhz = {{ device.max_clock_speed_mhz }}, \ + .quad_enable_bit_mask = {{ device.quad_enable_bit_mask }}, \ + .has_sector_protection = {{ device.has_sector_protection | lower() }}, \ + .supports_fast_read = {{ device.supports_fast_read | lower() }}, \ + .supports_qspi = {{ device["6b_quad_read"] | lower() }}, \ + .supports_qspi_writes = {{ device["32_qspi_write"] | lower() }}, \ + .write_status_register_split = {{ device.write_status_register_split | lower() }}, \ + .single_status_byte = {{ (device.quad_enable_status_byte == 1) | lower() }}, \ + .no_ready_bit = {{ (device.no_ready_bit == 1) | lower() }}, \ + .no_erase_cmd = {{ (device.no_erase_cmd == 1) | lower() }}, \ + .no_reset_cmd = {{ (device.no_reset_cmd == 1) | lower() }}, \ +} +{% endfor %} + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_DEVICES_H diff --git a/circuitpython/supervisor/shared/external_flash/external_flash.c b/circuitpython/supervisor/shared/external_flash/external_flash.c new file mode 100644 index 0000000..7da45fd --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/external_flash.c @@ -0,0 +1,593 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 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 "supervisor/shared/external_flash/external_flash.h" + +#include <stdint.h> +#include <string.h> +#include "genhdr/devices.h" +#include "supervisor/flash.h" +#include "supervisor/spi_flash_api.h" +#include "supervisor/shared/external_flash/common_commands.h" +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "py/misc.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "lib/oofatfs/ff.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "supervisor/memory.h" + +#define NO_SECTOR_LOADED 0xFFFFFFFF + +// The currently cached sector in the cache, ram or flash based. +static uint32_t current_sector; + +STATIC const external_flash_device possible_devices[] = {EXTERNAL_FLASH_DEVICES}; +#define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices) + +static const external_flash_device *flash_device = NULL; + +// Track which blocks (up to 32) in the current sector currently live in the +// cache. +static uint32_t dirty_mask; + +static supervisor_allocation *supervisor_cache = NULL; + +// Wait until both the write enable and write in progress bits have cleared. +static bool wait_for_flash_ready(void) { + bool ok = true; + // Both the write enable and write in progress bits should be low. + if (flash_device->no_ready_bit) { + // For NVM without a ready bit in status register + return ok; + } + uint8_t read_status_response[1] = {0x00}; + do { + ok = spi_flash_read_command(CMD_READ_STATUS, read_status_response, 1); + } while (ok && (read_status_response[0] & 0x3) != 0); + return ok; +} + +// Turn on the write enable bit so we can program and erase the flash. +static bool write_enable(void) { + return spi_flash_command(CMD_ENABLE_WRITE); +} + +// Read data_length's worth of bytes starting at address into data. +static bool read_flash(uint32_t address, uint8_t *data, uint32_t data_length) { + if (flash_device == NULL) { + return false; + } + if (!wait_for_flash_ready()) { + return false; + } + return spi_flash_read_data(address, data, data_length); +} + +// Writes data_length's worth of bytes starting at address from data. Assumes +// that the sector that address resides in has already been erased. So make sure +// to run erase_sector. +static bool write_flash(uint32_t address, const uint8_t *data, uint32_t data_length) { + if (flash_device == NULL) { + return false; + } + // Don't bother writing if the data is all 1s. Thats equivalent to the flash + // state after an erase. + if (!flash_device->no_erase_cmd) { + // Only do this if the device has an erase command + bool all_ones = true; + for (uint16_t i = 0; i < data_length; i++) { + if (data[i] != 0xff) { + all_ones = false; + break; + } + } + if (all_ones) { + return true; + } + } + + for (uint32_t bytes_written = 0; + bytes_written < data_length; + bytes_written += SPI_FLASH_PAGE_SIZE) { + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + + if (!spi_flash_write_data(address + bytes_written, (uint8_t *)data + bytes_written, + SPI_FLASH_PAGE_SIZE)) { + return false; + } + } + return true; +} + +static bool page_erased(uint32_t sector_address) { + // Check the first few bytes to catch the common case where there is data + // without using a bunch of memory. + if (flash_device->no_erase_cmd) { + // skip this if device doesn't have an erase command. + return true; + } + uint8_t short_buffer[4]; + if (read_flash(sector_address, short_buffer, 4)) { + for (uint16_t i = 0; i < 4; i++) { + if (short_buffer[i] != 0xff) { + return false; + } + } + } else { + return false; + } + + // Now check the full length. + uint8_t full_buffer[FILESYSTEM_BLOCK_SIZE]; + if (read_flash(sector_address, full_buffer, FILESYSTEM_BLOCK_SIZE)) { + for (uint16_t i = 0; i < FILESYSTEM_BLOCK_SIZE; i++) { + if (short_buffer[i] != 0xff) { + return false; + } + } + } else { + return false; + } + return true; +} + +// Erases the given sector. Make sure you copied all of the data out of it you +// need! Also note, sector_address is really 24 bits. +static bool erase_sector(uint32_t sector_address) { + // Before we erase the sector we need to wait for any writes to finish and + // and then enable the write again. + if (flash_device->no_erase_cmd) { + // skip this if device doesn't have an erase command. + return true; + } + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + if (flash_device->no_erase_cmd) { + return true; + } + spi_flash_sector_command(CMD_SECTOR_ERASE, sector_address); + return true; +} + +// Sector is really 24 bits. +static bool copy_block(uint32_t src_address, uint32_t dest_address) { + // Copy page by page to minimize RAM buffer. + uint16_t page_size = SPI_FLASH_PAGE_SIZE; + uint8_t buffer[page_size]; + for (uint32_t i = 0; i < FILESYSTEM_BLOCK_SIZE / page_size; i++) { + if (!read_flash(src_address + i * page_size, buffer, page_size)) { + return false; + } + if (!write_flash(dest_address + i * page_size, buffer, page_size)) { + return false; + } + } + return true; +} + +void supervisor_flash_init(void) { + if (flash_device != NULL) { + return; + } + + // Delay to give the SPI Flash time to get going. + // TODO(tannewt): Only do this when we know power was applied vs a reset. + uint16_t max_start_up_delay_us = 0; + for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { + if (possible_devices[i].start_up_time_us > max_start_up_delay_us) { + max_start_up_delay_us = possible_devices[i].start_up_time_us; + } + } + common_hal_mcu_delay_us(max_start_up_delay_us); + + spi_flash_init(); + + #ifdef EXTERNAL_FLASH_NO_JEDEC + // For NVM that don't have JEDEC response + spi_flash_command(CMD_WAKE); + for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { + const external_flash_device *possible_device = &possible_devices[i]; + flash_device = possible_device; + break; + } + #else + // The response will be 0xff if the flash needs more time to start up. + uint8_t jedec_id_response[3] = {0xff, 0xff, 0xff}; + while (jedec_id_response[0] == 0xff) { + spi_flash_read_command(CMD_READ_JEDEC_ID, jedec_id_response, 3); + } + for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { + const external_flash_device *possible_device = &possible_devices[i]; + if (jedec_id_response[0] == possible_device->manufacturer_id && + jedec_id_response[1] == possible_device->memory_type && + jedec_id_response[2] == possible_device->capacity) { + flash_device = possible_device; + break; + } + } + #endif + if (flash_device == NULL) { + return; + } + + // We don't know what state the flash is in so wait for any remaining writes and then reset. + uint8_t read_status_response[1] = {0x00}; + // The write in progress bit should be low. + do { + spi_flash_read_command(CMD_READ_STATUS, read_status_response, 1); + } while ((read_status_response[0] & 0x1) != 0); + if (!flash_device->single_status_byte) { + // The suspended write/erase bit should be low. + do { + spi_flash_read_command(CMD_READ_STATUS2, read_status_response, 1); + } while ((read_status_response[0] & 0x80) != 0); + } + + if (!(flash_device->no_reset_cmd)) { + spi_flash_command(CMD_ENABLE_RESET); + spi_flash_command(CMD_RESET); + } + + // Wait 30us for the reset + common_hal_mcu_delay_us(30); + + spi_flash_init_device(flash_device); + + // Activity LED for flash writes. + #ifdef MICROPY_HW_LED_MSC + gpio_set_pin_function(SPI_FLASH_CS_PIN, GPIO_PIN_FUNCTION_OFF); + gpio_set_pin_direction(MICROPY_HW_LED_MSC, GPIO_DIRECTION_OUT); + // There's already a pull-up on the board. + gpio_set_pin_level(MICROPY_HW_LED_MSC, false); + #endif + + if (flash_device->has_sector_protection) { + write_enable(); + + // Turn off sector protection + uint8_t data[1] = {0x00}; + spi_flash_write_command(CMD_WRITE_STATUS_BYTE1, data, 1); + } + + // Turn off writes in case this is a microcontroller only reset. + spi_flash_command(CMD_DISABLE_WRITE); + + wait_for_flash_ready(); + + current_sector = NO_SECTOR_LOADED; + dirty_mask = 0; + MP_STATE_VM(flash_ram_cache) = NULL; +} + +// The size of each individual block. +uint32_t supervisor_flash_get_block_size(void) { + return FILESYSTEM_BLOCK_SIZE; +} + +// The total number of available blocks. +uint32_t supervisor_flash_get_block_count(void) { + // We subtract one erase sector size because we may use it as a staging area + // for writes. + return (flash_device->total_size - SPI_FLASH_ERASE_SIZE) / FILESYSTEM_BLOCK_SIZE; +} + +// Flush the cache that was written to the scratch portion of flash. Only used +// when ram is tight. +static bool flush_scratch_flash(void) { + if (current_sector == NO_SECTOR_LOADED) { + return true; + } + // First, copy out any blocks that we haven't touched from the sector we've + // cached. + bool copy_to_scratch_ok = true; + uint32_t scratch_sector = flash_device->total_size - SPI_FLASH_ERASE_SIZE; + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + copy_to_scratch_ok = copy_to_scratch_ok && + copy_block(current_sector + i * FILESYSTEM_BLOCK_SIZE, + scratch_sector + i * FILESYSTEM_BLOCK_SIZE); + } + } + if (!copy_to_scratch_ok) { + // TODO(tannewt): Do more here. We opted to not erase and copy bad data + // in. We still risk losing the data written to the scratch sector. + return false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Finally, copy the new version into it. + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + copy_block(scratch_sector + i * FILESYSTEM_BLOCK_SIZE, + current_sector + i * FILESYSTEM_BLOCK_SIZE); + } + return true; +} + +// Attempts to allocate a new set of page buffers for caching a full sector in +// ram. Each page is allocated separately so that the GC doesn't need to provide +// one huge block. We can free it as we write if we want to also. +static bool allocate_ram_cache(void) { + uint8_t blocks_per_sector = SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + + uint32_t table_size = blocks_per_sector * pages_per_block * sizeof(uint32_t); + // Attempt to allocate outside the heap first. + supervisor_cache = allocate_memory(table_size + SPI_FLASH_ERASE_SIZE, false, false); + if (supervisor_cache != NULL) { + MP_STATE_VM(flash_ram_cache) = (uint8_t **)supervisor_cache->ptr; + uint8_t *page_start = (uint8_t *)supervisor_cache->ptr + table_size; + + for (uint8_t i = 0; i < blocks_per_sector; i++) { + for (uint8_t j = 0; j < pages_per_block; j++) { + uint32_t offset = i * pages_per_block + j; + MP_STATE_VM(flash_ram_cache)[offset] = page_start + offset * SPI_FLASH_PAGE_SIZE; + } + } + return true; + } + + if (MP_STATE_MEM(gc_pool_start) == 0) { + return false; + } + + MP_STATE_VM(flash_ram_cache) = m_malloc_maybe(blocks_per_sector * pages_per_block * sizeof(uint32_t), false); + if (MP_STATE_VM(flash_ram_cache) == NULL) { + return false; + } + // Declare i and j outside the loops in case we fail to allocate everything + // we need. In that case we'll give it back. + uint8_t i = 0; + uint8_t j = 0; + bool success = true; + for (i = 0; i < blocks_per_sector; i++) { + for (j = 0; j < pages_per_block; j++) { + uint8_t *page_cache = m_malloc_maybe(SPI_FLASH_PAGE_SIZE, false); + if (page_cache == NULL) { + success = false; + break; + } + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j] = page_cache; + } + if (!success) { + break; + } + } + // We couldn't allocate enough so give back what we got. + if (!success) { + // We add 1 so that we delete 0 when i is 1. Going to zero (i >= 0) + // would never stop because i is unsigned. + i++; + for (; i > 0; i--) { + for (; j > 0; j--) { + m_free(MP_STATE_VM(flash_ram_cache)[(i - 1) * pages_per_block + (j - 1)]); + } + j = pages_per_block; + } + m_free(MP_STATE_VM(flash_ram_cache)); + MP_STATE_VM(flash_ram_cache) = NULL; + } + return success; +} + +static void release_ram_cache(void) { + if (supervisor_cache != NULL) { + free_memory(supervisor_cache); + supervisor_cache = NULL; + } else if (MP_STATE_MEM(gc_pool_start)) { + m_free(MP_STATE_VM(flash_ram_cache)); + } + MP_STATE_VM(flash_ram_cache) = NULL; +} + +// Flush the cached sector from ram onto the flash. We'll free the cache unless +// keep_cache is true. +static bool flush_ram_cache(bool keep_cache) { + if (current_sector == NO_SECTOR_LOADED) { + if (!keep_cache) { + release_ram_cache(); + } + return true; + } + // First, copy out any blocks that we haven't touched from the sector + // we've cached. If we don't do this we'll erase the data during the sector + // erase below. + bool copy_to_ram_ok = true; + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + for (uint8_t j = 0; j < pages_per_block; j++) { + copy_to_ram_ok = read_flash( + current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], + SPI_FLASH_PAGE_SIZE); + if (!copy_to_ram_ok) { + break; + } + } + } + if (!copy_to_ram_ok) { + break; + } + } + + if (!copy_to_ram_ok) { + return false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Lastly, write all the data in ram that we've cached. + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + for (uint8_t j = 0; j < pages_per_block; j++) { + write_flash(current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], + SPI_FLASH_PAGE_SIZE); + if (!keep_cache && supervisor_cache == NULL && MP_STATE_MEM(gc_pool_start)) { + m_free(MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j]); + } + } + } + // We're done with the cache for now so give it back. + if (!keep_cache) { + release_ram_cache(); + } + return true; +} + +// Delegates to the correct flash flush method depending on the existing cache. +// TODO Don't blink the status indicator if we don't actually do any writing (hard to tell right now). +static void spi_flash_flush_keep_cache(bool keep_cache) { + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, true); + #endif + // If we've cached to the flash itself flush from there. + if (MP_STATE_VM(flash_ram_cache) == NULL) { + flush_scratch_flash(); + } else { + flush_ram_cache(keep_cache); + } + current_sector = NO_SECTOR_LOADED; + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, false); + #endif +} + +void supervisor_external_flash_flush(void) { + spi_flash_flush_keep_cache(true); +} + +void supervisor_flash_release_cache(void) { + spi_flash_flush_keep_cache(false); +} + +static int32_t convert_block_to_flash_addr(uint32_t block) { + if (0 <= block && block < supervisor_flash_get_block_count()) { + // a block in partition 1 + return block * FILESYSTEM_BLOCK_SIZE; + } + // bad block + return -1; +} + +static bool external_flash_read_block(uint8_t *dest, uint32_t block) { + int32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); + uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // We're reading from the currently cached sector. + if (current_sector == this_sector && (mask & dirty_mask) > 0) { + if (MP_STATE_VM(flash_ram_cache) != NULL) { + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (int i = 0; i < pages_per_block; i++) { + memcpy(dest + i * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], + SPI_FLASH_PAGE_SIZE); + } + return true; + } else { + uint32_t scratch_address = flash_device->total_size - SPI_FLASH_ERASE_SIZE + block_index * FILESYSTEM_BLOCK_SIZE; + return read_flash(scratch_address, dest, FILESYSTEM_BLOCK_SIZE); + } + } + return read_flash(address, dest, FILESYSTEM_BLOCK_SIZE); +} + +static bool external_flash_write_block(const uint8_t *data, uint32_t block) { + // Non-MBR block, copy to cache + int32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + // Wait for any previous writes to finish. + wait_for_flash_ready(); + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); + uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // Flush the cache if we're moving onto a sector or we're writing the + // same block again. + if (current_sector != this_sector || (mask & dirty_mask) > 0) { + // Check to see if we'd write to an erased page. In that case we + // can write directly. + if (page_erased(address)) { + return write_flash(address, data, FILESYSTEM_BLOCK_SIZE); + } + if (current_sector != NO_SECTOR_LOADED) { + supervisor_flash_flush(); + } + if (MP_STATE_VM(flash_ram_cache) == NULL && !allocate_ram_cache()) { + erase_sector(flash_device->total_size - SPI_FLASH_ERASE_SIZE); + wait_for_flash_ready(); + } + current_sector = this_sector; + dirty_mask = 0; + } + dirty_mask |= mask; + // Copy the block to the appropriate cache. + if (MP_STATE_VM(flash_ram_cache) != NULL) { + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (int i = 0; i < pages_per_block; i++) { + memcpy(MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], + data + i * SPI_FLASH_PAGE_SIZE, + SPI_FLASH_PAGE_SIZE); + } + return true; + } else { + uint32_t scratch_address = flash_device->total_size - SPI_FLASH_ERASE_SIZE + block_index * FILESYSTEM_BLOCK_SIZE; + return write_flash(scratch_address, data, FILESYSTEM_BLOCK_SIZE); + } +} + +mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!external_flash_read_block(dest + i * FILESYSTEM_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!external_flash_write_block(src + i * FILESYSTEM_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +void MP_WEAK external_flash_setup(void) { +} diff --git a/circuitpython/supervisor/shared/external_flash/external_flash.h b/circuitpython/supervisor/shared/external_flash/external_flash.h new file mode 100644 index 0000000..7966b64 --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/external_flash.h @@ -0,0 +1,55 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_H + +#include <stdbool.h> +#include <stdint.h> + +#include "py/mpconfig.h" + +// Erase sector size. +#define SPI_FLASH_SECTOR_SIZE (0x1000 - 100) + +// These are common across all NOR Flash. +#define SPI_FLASH_ERASE_SIZE (1 << 12) +#define SPI_FLASH_PAGE_SIZE (256) + +#define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms +#define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2) + +#ifndef SPI_FLASH_MAX_BAUDRATE +#define SPI_FLASH_MAX_BAUDRATE 8000000 +#endif + +void supervisor_external_flash_flush(void); + +// Configure anything that needs to get set up before the external flash +// is init'ed. For example, if GPIO needs to be configured to enable the +// flash chip, as is the case on some boards. +void external_flash_setup(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_H diff --git a/circuitpython/supervisor/shared/external_flash/external_flash_root_pointers.h b/circuitpython/supervisor/shared/external_flash/external_flash_root_pointers.h new file mode 100644 index 0000000..0613c9a --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/external_flash_root_pointers.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_ROOT_POINTERS_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_ROOT_POINTERS_H + +#include <stdint.h> + +// We use this when we can allocate the whole cache in RAM. +#define FLASH_ROOT_POINTERS \ + uint8_t **flash_ram_cache; \ + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_EXTERNAL_FLASH_ROOT_POINTERS_H diff --git a/circuitpython/supervisor/shared/external_flash/qspi_flash.c b/circuitpython/supervisor/shared/external_flash/qspi_flash.c new file mode 100644 index 0000000..293654b --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/qspi_flash.c @@ -0,0 +1,57 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017, 2018 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 "supervisor/spi_flash_api.h" + +#include "supervisor/shared/external_flash/common_commands.h" +#include "supervisor/shared/external_flash/qspi_flash.h" + +void check_quad_enable(const external_flash_device *device) { + if (device->quad_enable_bit_mask == 0x00) { + return; + } + + // Verify that QSPI mode is enabled. + uint8_t status; + if (device->single_status_byte) { + spi_flash_read_command(CMD_READ_STATUS, &status, 1); + } else { + spi_flash_read_command(CMD_READ_STATUS2, &status, 1); + } + + // Check the quad enable bit. + if ((status & device->quad_enable_bit_mask) == 0) { + uint8_t full_status[2] = {0x00, device->quad_enable_bit_mask}; + spi_flash_command(CMD_ENABLE_WRITE); + if (device->write_status_register_split) { + spi_flash_write_command(CMD_WRITE_STATUS_BYTE2, full_status + 1, 1); + } else if (device->single_status_byte) { + spi_flash_write_command(CMD_WRITE_STATUS_BYTE1, full_status + 1, 1); + } else { + spi_flash_write_command(CMD_WRITE_STATUS_BYTE1, full_status, 2); + } + } +} diff --git a/circuitpython/supervisor/shared/external_flash/qspi_flash.h b/circuitpython/supervisor/shared/external_flash/qspi_flash.h new file mode 100644 index 0000000..c0c089e --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/qspi_flash.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_EXTERNAL_FLASH_QSPI_FLASH_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_QSPI_FLASH_H + +void check_quad_enable(const external_flash_device *device); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_EXTERNAL_FLASH_QSPI_FLASH_H diff --git a/circuitpython/supervisor/shared/external_flash/spi_flash.c b/circuitpython/supervisor/shared/external_flash/spi_flash.c new file mode 100644 index 0000000..b47508e --- /dev/null +++ b/circuitpython/supervisor/shared/external_flash/spi_flash.c @@ -0,0 +1,166 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 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 "supervisor/spi_flash_api.h" + +#include <stdint.h> +#include <string.h> + +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/external_flash/common_commands.h" +#include "supervisor/shared/external_flash/external_flash.h" +#include "py/mpconfig.h" + +digitalio_digitalinout_obj_t cs_pin; +busio_spi_obj_t supervisor_flash_spi_bus; + +const external_flash_device *flash_device; +uint32_t spi_flash_baudrate; + +// Enable the flash over SPI. +static bool flash_enable(void) { + if (common_hal_busio_spi_try_lock(&supervisor_flash_spi_bus)) { + common_hal_digitalio_digitalinout_set_value(&cs_pin, false); + return true; + } + return false; +} + +// Disable the flash over SPI. +static void flash_disable(void) { + common_hal_digitalio_digitalinout_set_value(&cs_pin, true); + common_hal_busio_spi_unlock(&supervisor_flash_spi_bus); +} + +static bool transfer(uint8_t *command, uint32_t command_length, uint8_t *data_in, uint8_t *data_out, uint32_t data_length) { + if (!flash_enable()) { + return false; + } + bool status = common_hal_busio_spi_write(&supervisor_flash_spi_bus, command, command_length); + if (status) { + if (data_in != NULL && data_out != NULL) { + status = common_hal_busio_spi_transfer(&supervisor_flash_spi_bus, data_out, data_in, data_length); + } else if (data_out != NULL) { + status = common_hal_busio_spi_read(&supervisor_flash_spi_bus, data_out, data_length, 0xff); + } else if (data_in != NULL) { + status = common_hal_busio_spi_write(&supervisor_flash_spi_bus, data_in, data_length); + } + } + flash_disable(); + return status; +} + +static bool transfer_command(uint8_t command, uint8_t *data_in, uint8_t *data_out, uint32_t data_length) { + return transfer(&command, 1, data_in, data_out, data_length); +} + +bool spi_flash_command(uint8_t command) { + return transfer_command(command, NULL, NULL, 0); +} + +bool spi_flash_read_command(uint8_t command, uint8_t *data, uint32_t data_length) { + return transfer_command(command, NULL, data, data_length); +} + +bool spi_flash_write_command(uint8_t command, uint8_t *data, uint32_t data_length) { + return transfer_command(command, data, NULL, data_length); +} + +// Pack the low 24 bits of the address into a uint8_t array. +static void address_to_bytes(uint32_t address, uint8_t *bytes) { + bytes[0] = (address >> 16) & 0xff; + bytes[1] = (address >> 8) & 0xff; + bytes[2] = address & 0xff; +} + +bool spi_flash_sector_command(uint8_t command, uint32_t address) { + uint8_t request[4] = {command, 0x00, 0x00, 0x00}; + address_to_bytes(address, request + 1); + return transfer(request, 4, NULL, NULL, 0); +} + +bool spi_flash_write_data(uint32_t address, uint8_t *data, uint32_t data_length) { + uint8_t request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; + common_hal_busio_spi_configure(&supervisor_flash_spi_bus, spi_flash_baudrate, 0, 0, 8); + // Write the SPI flash write address into the bytes following the command byte. + address_to_bytes(address, request + 1); + if (!flash_enable()) { + return false; + } + bool status = common_hal_busio_spi_write(&supervisor_flash_spi_bus, request, 4); + if (status) { + status = common_hal_busio_spi_write(&supervisor_flash_spi_bus, data, data_length); + } + flash_disable(); + return status; +} + +bool spi_flash_read_data(uint32_t address, uint8_t *data, uint32_t data_length) { + uint8_t request[5] = {CMD_READ_DATA, 0x00, 0x00, 0x00}; + uint8_t command_length = 4; + if (flash_device->supports_fast_read) { + request[0] = CMD_FAST_READ_DATA; + command_length = 5; + } + common_hal_busio_spi_configure(&supervisor_flash_spi_bus, spi_flash_baudrate, 0, 0, 8); + // Write the SPI flash read address into the bytes following the command byte. + address_to_bytes(address, request + 1); + if (!flash_enable()) { + return false; + } + bool status = common_hal_busio_spi_write(&supervisor_flash_spi_bus, request, command_length); + if (status) { + status = common_hal_busio_spi_read(&supervisor_flash_spi_bus, data, data_length, 0xff); + } + flash_disable(); + return status; +} + +void spi_flash_init(void) { + cs_pin.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&cs_pin, SPI_FLASH_CS_PIN); + + // Set CS high (disabled). + common_hal_digitalio_digitalinout_switch_to_output(&cs_pin, true, DRIVE_MODE_PUSH_PULL); + common_hal_digitalio_digitalinout_never_reset(&cs_pin); + + supervisor_flash_spi_bus.base.type = &busio_spi_type; + common_hal_busio_spi_construct(&supervisor_flash_spi_bus, SPI_FLASH_SCK_PIN, SPI_FLASH_MOSI_PIN, SPI_FLASH_MISO_PIN, false); + common_hal_busio_spi_never_reset(&supervisor_flash_spi_bus); + + return; +} + +void spi_flash_init_device(const external_flash_device *device) { + flash_device = device; + spi_flash_baudrate = device->max_clock_speed_mhz * 1000000; + if (spi_flash_baudrate > SPI_FLASH_MAX_BAUDRATE) { + spi_flash_baudrate = SPI_FLASH_MAX_BAUDRATE; + } + common_hal_busio_spi_configure(&supervisor_flash_spi_bus, spi_flash_baudrate, 0, 0, 8); + return; +} diff --git a/circuitpython/supervisor/shared/filesystem.c b/circuitpython/supervisor/shared/filesystem.c new file mode 100644 index 0000000..820dbe9 --- /dev/null +++ b/circuitpython/supervisor/shared/filesystem.c @@ -0,0 +1,203 @@ +/* + * 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 "supervisor/filesystem.h" + +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/ff.h" +#include "lib/oofatfs/diskio.h" + +#include "py/mpstate.h" + +#include "supervisor/flash.h" + +static mp_vfs_mount_t _mp_vfs; +static fs_user_mount_t _internal_vfs; + +static volatile uint32_t filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; +volatile bool filesystem_flush_requested = false; + +void filesystem_background(void) { + if (filesystem_flush_requested) { + filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; + // Flush but keep caches + supervisor_flash_flush(); + filesystem_flush_requested = false; + } +} + +inline void filesystem_tick(void) { + if (filesystem_flush_interval_ms == 0) { + // 0 means not turned on. + return; + } + if (filesystem_flush_interval_ms == 1) { + filesystem_flush_requested = true; + filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; + } else { + filesystem_flush_interval_ms--; + } +} + + +static void make_empty_file(FATFS *fatfs, const char *path) { + FIL fp; + f_open(fatfs, &fp, path, FA_WRITE | FA_CREATE_ALWAYS); + f_close(&fp); +} + + +static void make_sample_code_file(FATFS *fatfs) { + #if CIRCUITPY_FULL_BUILD + FIL fs; + UINT char_written = 0; + const byte buffer[] = "print(\"Hello World!\")\n"; + // Create or modify existing code.py file + f_open(fatfs, &fs, "/code.py", FA_WRITE | FA_CREATE_ALWAYS); + f_write(&fs, buffer, sizeof(buffer) - 1, &char_written); + f_close(&fs); + #else + make_empty_file(fatfs, "/code.py"); + #endif +} + +// we don't make this function static because it needs a lot of stack and we +// want it to be executed without using stack within main() function +bool filesystem_init(bool create_allowed, bool force_create) { + // init the vfs object + fs_user_mount_t *vfs_fat = &_internal_vfs; + vfs_fat->blockdev.flags = 0; + supervisor_flash_init_vfs(vfs_fat); + + // try to mount the flash + FRESULT res = f_mount(&vfs_fat->fatfs); + if ((res == FR_NO_FILESYSTEM && create_allowed) || force_create) { + // No filesystem so create a fresh one, or reformat has been requested. + uint8_t working_buf[FF_MAX_SS]; + BYTE formats = FM_FAT; + #if FF_FS_EXFAT + formats |= FM_EXFAT | FM_FAT32; + #endif + res = f_mkfs(&vfs_fat->fatfs, formats, 0, working_buf, sizeof(working_buf)); + if (res != FR_OK) { + return false; + } + // Flush the new file system to make sure it's repaired immediately. + supervisor_flash_flush(); + + // set label + #ifdef CIRCUITPY_DRIVE_LABEL + res = f_setlabel(&vfs_fat->fatfs, CIRCUITPY_DRIVE_LABEL); + #else + res = f_setlabel(&vfs_fat->fatfs, "CIRCUITPY"); + #endif + if (res != FR_OK) { + return false; + } + + // inhibit file indexing on MacOS + res = f_mkdir(&vfs_fat->fatfs, "/.fseventsd"); + if (res != FR_OK) { + return false; + } + make_empty_file(&vfs_fat->fatfs, "/.metadata_never_index"); + make_empty_file(&vfs_fat->fatfs, "/.Trashes"); + make_empty_file(&vfs_fat->fatfs, "/.fseventsd/no_log"); + // make a sample code.py file + make_sample_code_file(&vfs_fat->fatfs); + + // create empty lib directory + res = f_mkdir(&vfs_fat->fatfs, "/lib"); + if (res != FR_OK) { + return false; + } + + // and ensure everything is flushed + supervisor_flash_flush(); + } else if (res != FR_OK) { + return false; + } + mp_vfs_mount_t *vfs = &_mp_vfs; + vfs->str = "/"; + vfs->len = 1; + vfs->obj = MP_OBJ_FROM_PTR(vfs_fat); + vfs->next = NULL; + MP_STATE_VM(vfs_mount_table) = vfs; + + // The current directory is used as the boot up directory. + // It is set to the internal flash filesystem by default. + MP_STATE_PORT(vfs_cur) = vfs; + + return true; +} + +void filesystem_flush(void) { + // Reset interval before next flush. + filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; + supervisor_flash_flush(); + // Don't keep caches because this is called when starting or stopping the VM. + supervisor_flash_release_cache(); +} + +void filesystem_set_internal_writable_by_usb(bool writable) { + fs_user_mount_t *vfs = &_internal_vfs; + + filesystem_set_writable_by_usb(vfs, writable); +} + +void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable) { + if (usb_writable) { + vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_USB_WRITABLE; + } else { + vfs->blockdev.flags &= ~MP_BLOCKDEV_FLAG_USB_WRITABLE; + } +} + +bool filesystem_is_writable_by_python(fs_user_mount_t *vfs) { + return (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED) == 0 || + (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_USB_WRITABLE) == 0; +} + +bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs) { + return (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED) == 0 || + (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_USB_WRITABLE) != 0; +} + +void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_protection) { + filesystem_set_concurrent_write_protection(&_internal_vfs, concurrent_write_protection); +} + +void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection) { + if (concurrent_write_protection) { + vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; + } else { + vfs->blockdev.flags &= ~MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; + } +} + +bool filesystem_present(void) { + return true; +} diff --git a/circuitpython/supervisor/shared/flash.c b/circuitpython/supervisor/shared/flash.c new file mode 100644 index 0000000..dfd7cf2 --- /dev/null +++ b/circuitpython/supervisor/shared/flash.c @@ -0,0 +1,227 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft + * + * 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 "supervisor/flash.h" + +#include "extmod/vfs_fat.h" +#include "py/runtime.h" +#include "lib/oofatfs/ff.h" +#include "supervisor/flash.h" +#include "supervisor/shared/tick.h" + +#define VFS_INDEX 0 + +#define PART1_START_BLOCK (0x1) + +// there is a singleton Flash object +const mp_obj_type_t supervisor_flash_type; +STATIC const mp_obj_base_t supervisor_flash_obj = {&supervisor_flash_type}; + +STATIC mp_obj_t supervisor_flash_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return singleton object + return (mp_obj_t)&supervisor_flash_obj; +} + +static uint32_t flash_get_block_count(void) { + return PART1_START_BLOCK + supervisor_flash_get_block_count(); +} + +static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { + buf[0] = boot; + + if (num_blocks == 0) { + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + } else { + buf[1] = 0xff; + buf[2] = 0xff; + buf[3] = 0xff; + } + + buf[4] = type; + + if (num_blocks == 0) { + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + } else { + buf[5] = 0xff; + buf[6] = 0xff; + buf[7] = 0xff; + } + + buf[8] = start_block; + buf[9] = start_block >> 8; + buf[10] = start_block >> 16; + buf[11] = start_block >> 24; + + buf[12] = num_blocks; + buf[13] = num_blocks >> 8; + buf[14] = num_blocks >> 16; + buf[15] = num_blocks >> 24; +} + +static mp_uint_t flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + if (block_num == 0) { + // fake the MBR so we can decide on our own partition table + + for (int i = 0; i < 446; i++) { + dest[i] = 0; + } + + build_partition(dest + 446, 0, 0x01 /* FAT12 */, PART1_START_BLOCK, supervisor_flash_get_block_count()); + build_partition(dest + 462, 0, 0, 0, 0); + build_partition(dest + 478, 0, 0, 0, 0); + build_partition(dest + 494, 0, 0, 0, 0); + + dest[510] = 0x55; + dest[511] = 0xaa; + if (num_blocks > 1) { + dest += 512; + num_blocks -= 1; + // Fall through and do a read from flash. + } else { + return 0; // Done and ok. + } + } + return supervisor_flash_read_blocks(dest, block_num - PART1_START_BLOCK, num_blocks); +} + +static volatile bool filesystem_dirty = false; + +static mp_uint_t flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + if (block_num == 0) { + if (num_blocks > 1) { + return 1; // error + } + // can't write MBR, but pretend we did + return 0; + } else { + if (!filesystem_dirty) { + // Turn on ticks so that we can flush after a period of time elapses. + supervisor_enable_tick(); + filesystem_dirty = true; + } + return supervisor_flash_write_blocks(src, block_num - PART1_START_BLOCK, num_blocks); + } +} + +void supervisor_flash_flush(void) { + #if INTERNAL_FLASH_FILESYSTEM + port_internal_flash_flush(); + #else + supervisor_external_flash_flush(); + #endif + // Turn off ticks now that our filesystem has been flushed. + if (filesystem_dirty) { + supervisor_disable_tick(); + } + filesystem_dirty = false; +} + +STATIC mp_obj_t supervisor_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + mp_uint_t ret = flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_readblocks_obj, supervisor_flash_obj_readblocks); + +STATIC mp_obj_t supervisor_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + mp_uint_t ret = flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_writeblocks_obj, supervisor_flash_obj_writeblocks); + +static bool flash_ioctl(size_t cmd, mp_int_t *out_value) { + *out_value = 0; + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + supervisor_flash_init(); + break; + case MP_BLOCKDEV_IOCTL_DEINIT: + supervisor_flash_flush(); + break; // TODO properly + case MP_BLOCKDEV_IOCTL_SYNC: + supervisor_flash_flush(); + break; + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + *out_value = flash_get_block_count(); + break; + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + *out_value = supervisor_flash_get_block_size(); + break; + default: + return false; + } + return true; +} + +STATIC mp_obj_t supervisor_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { + mp_int_t cmd = mp_obj_get_int(cmd_in); + mp_int_t out_value; + if (flash_ioctl(cmd, &out_value)) { + return MP_OBJ_NEW_SMALL_INT(out_value); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_ioctl_obj, supervisor_flash_obj_ioctl); + +STATIC const mp_rom_map_elem_t supervisor_flash_obj_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&supervisor_flash_obj_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&supervisor_flash_obj_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&supervisor_flash_obj_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(supervisor_flash_obj_locals_dict, supervisor_flash_obj_locals_dict_table); + +const mp_obj_type_t supervisor_flash_type = { + { &mp_type_type }, + .name = MP_QSTR_Flash, + .make_new = supervisor_flash_obj_make_new, + .locals_dict = (struct _mp_obj_dict_t *)&supervisor_flash_obj_locals_dict, +}; + +void supervisor_flash_init_vfs(fs_user_mount_t *vfs) { + vfs->base.type = &mp_fat_vfs_type; + vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL; + vfs->fatfs.drv = vfs; + vfs->fatfs.part = 1; // flash filesystem lives on first fake partition + vfs->blockdev.readblocks[0] = (mp_obj_t)&supervisor_flash_obj_readblocks_obj; + vfs->blockdev.readblocks[1] = (mp_obj_t)&supervisor_flash_obj; + vfs->blockdev.readblocks[2] = (mp_obj_t)flash_read_blocks; // native version + vfs->blockdev.writeblocks[0] = (mp_obj_t)&supervisor_flash_obj_writeblocks_obj; + vfs->blockdev.writeblocks[1] = (mp_obj_t)&supervisor_flash_obj; + vfs->blockdev.writeblocks[2] = (mp_obj_t)flash_write_blocks; // native version + vfs->blockdev.u.ioctl[0] = (mp_obj_t)&supervisor_flash_obj_ioctl_obj; + vfs->blockdev.u.ioctl[1] = (mp_obj_t)&supervisor_flash_obj; + vfs->blockdev.u.ioctl[2] = (mp_obj_t)flash_ioctl; // native version +} diff --git a/circuitpython/supervisor/shared/internal_flash.h b/circuitpython/supervisor/shared/internal_flash.h new file mode 100644 index 0000000..80257a2 --- /dev/null +++ b/circuitpython/supervisor/shared/internal_flash.h @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Scott Shawcroft, for Adafruit Industries LLC + * + * 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_SUPERVISOR_SHARED_INTERNAL_FLASH_H +#define MICROPY_INCLUDED_SUPERVISOR_SHARED_INTERNAL_FLASH_H + +#include "supervisor/internal_flash.h" // This is per-port. + +void port_internal_flash_flush(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SHARED_INTERNAL_FLASH_H diff --git a/circuitpython/supervisor/shared/lock.c b/circuitpython/supervisor/shared/lock.c new file mode 100644 index 0000000..36971ba --- /dev/null +++ b/circuitpython/supervisor/shared/lock.c @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dan Halbert 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-bindings/microcontroller/__init__.h" +#include "supervisor/shared/lock.h" + +void supervisor_acquire_lock(supervisor_lock_t *lock) { + while (!supervisor_try_lock(lock)) { + RUN_BACKGROUND_TASKS; + } +} + +bool supervisor_try_lock(supervisor_lock_t *lock) { + bool grabbed_lock = false; + common_hal_mcu_disable_interrupts(); + if (!*lock) { + *lock = true; + grabbed_lock = true; + } + common_hal_mcu_enable_interrupts(); + return grabbed_lock; +} + +void supervisor_release_lock(supervisor_lock_t *lock) { + common_hal_mcu_disable_interrupts(); + *lock = false; + common_hal_mcu_enable_interrupts(); +} diff --git a/circuitpython/supervisor/shared/lock.h b/circuitpython/supervisor/shared/lock.h new file mode 100644 index 0000000..89f81cf --- /dev/null +++ b/circuitpython/supervisor/shared/lock.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dan Halbert 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_SUPERVISOR_LOCK_H +#define MICROPY_INCLUDED_SUPERVISOR_LOCK_H + +typedef volatile bool supervisor_lock_t; + +void supervisor_acquire_lock(supervisor_lock_t *lock); +bool supervisor_try_lock(supervisor_lock_t *lock); +void supervisor_release_lock(supervisor_lock_t *lock); + +#endif // MICROPY_INCLUDED_SUPERVISOR_LOCK_H diff --git a/circuitpython/supervisor/shared/memory.c b/circuitpython/supervisor/shared/memory.c new file mode 100644 index 0000000..ff0382f --- /dev/null +++ b/circuitpython/supervisor/shared/memory.c @@ -0,0 +1,341 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 "supervisor/memory.h" +#include "supervisor/port.h" + +#include <string.h> + +#include "py/gc.h" +#include "supervisor/shared/display.h" + +enum { + CIRCUITPY_SUPERVISOR_IMMOVABLE_ALLOC_COUNT = + 0 + // stack + heap + + 2 + + #if INTERNAL_FLASH_FILESYSTEM == 0 + + 1 + #endif + + #if CIRCUITPY_USB + + 1 // device_descriptor_allocation + + 1 // configuration_descriptor_allocation + + 1 // string_descriptors_allocation + #endif + + #if CIRCUITPY_USB_HID + + 1 // hid_report_descriptor_allocation + + 1 // hid_devices_allocation + #endif + + #if CIRCUITPY_USB_VENDOR + + 1 // usb_vendor_add_descriptor + #endif + , + + CIRCUITPY_SUPERVISOR_MOVABLE_ALLOC_COUNT = + 0 + // next_code_allocation + + 1 + // prev_traceback_allocation + + 1 + #if CIRCUITPY_DISPLAYIO + #if CIRCUITPY_TERMINALIO + + 1 + #endif + + CIRCUITPY_DISPLAY_LIMIT * ( + // Maximum needs of one display: max(4 if RGBMATRIX, 1 if SHARPDISPLAY, 0) + #if CIRCUITPY_RGBMATRIX + 4 + #elif CIRCUITPY_SHARPDISPLAY + 1 + #else + 0 + #endif + ) + #endif + , + + CIRCUITPY_SUPERVISOR_ALLOC_COUNT = CIRCUITPY_SUPERVISOR_IMMOVABLE_ALLOC_COUNT + CIRCUITPY_SUPERVISOR_MOVABLE_ALLOC_COUNT +}; + +// The lowest two bits of a valid length are always zero, so we can use them to mark an allocation +// as a hole (freed by the client but not yet reclaimed into the free middle) and as movable. +#define FLAGS 3 +#define HOLE 1 +#define MOVABLE 2 + +static supervisor_allocation allocations[CIRCUITPY_SUPERVISOR_ALLOC_COUNT]; +supervisor_allocation *old_allocations; + +typedef struct _supervisor_allocation_node { + struct _supervisor_allocation_node *next; + size_t length; + // We use uint32_t to ensure word (4 byte) alignment. + uint32_t data[]; +} supervisor_allocation_node; + +supervisor_allocation_node *low_head; +supervisor_allocation_node *high_head; + +// Intermediate (void*) is to suppress -Wcast-align warning. Alignment will always be correct +// because this only reverses how (alloc)->ptr was obtained as &(node->data[0]). +#define ALLOCATION_NODE(alloc) ((supervisor_allocation_node *)(void *)((char *)((alloc)->ptr) - sizeof(supervisor_allocation_node))) + +void free_memory(supervisor_allocation *allocation) { + if (allocation == NULL || allocation->ptr == NULL) { + return; + } + supervisor_allocation_node *node = ALLOCATION_NODE(allocation); + if (node == low_head) { + do { + low_head = low_head->next; + } while (low_head != NULL && (low_head->length & HOLE)); + } else if (node == high_head) { + do { + high_head = high_head->next; + } while (high_head != NULL && (high_head->length & HOLE)); + } else { + // Check if it's in the list of embedded allocations. + supervisor_allocation_node **emb = &MP_STATE_VM(first_embedded_allocation); + while (*emb != NULL && *emb != node) { + emb = &((*emb)->next); + } + if (*emb != NULL) { + // Found, remove it from the list. + *emb = node->next; + m_free(node + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + , sizeof(supervisor_allocation_node) + (node->length & ~FLAGS) + #endif + ); + } else { + // Else it must be within the low or high ranges and becomes a hole. + node->length = ((node->length & ~FLAGS) | HOLE); + } + } + allocation->ptr = NULL; +} + +supervisor_allocation *allocation_from_ptr(void *ptr) { + // When called from the context of supervisor_move_memory() (old_allocations != NULL), search + // by old pointer to give clients a way of mapping from old to new pointer. But not if + // ptr == NULL, then the caller wants an allocation whose current ptr is NULL. + supervisor_allocation *list = (old_allocations && ptr) ? old_allocations : &allocations[0]; + for (size_t index = 0; index < CIRCUITPY_SUPERVISOR_ALLOC_COUNT; index++) { + if (list[index].ptr == ptr) { + return &allocations[index]; + } + } + return NULL; +} + +supervisor_allocation *allocate_remaining_memory(void) { + return allocate_memory((uint32_t)-1, false, false); +} + +static supervisor_allocation_node *find_hole(supervisor_allocation_node *node, size_t length) { + for (; node != NULL; node = node->next) { + if (node->length == (length | HOLE)) { + break; + } + } + return node; +} + +static supervisor_allocation_node *allocate_memory_node(uint32_t length, bool high, bool movable) { + if (CIRCUITPY_SUPERVISOR_MOVABLE_ALLOC_COUNT == 0) { + assert(!movable); + } + // supervisor_move_memory() currently does not support movable allocations on the high side, it + // must be extended first if this is ever needed. + assert(!(high && movable)); + uint32_t *low_address = low_head ? low_head->data + low_head->length / 4 : port_heap_get_bottom(); + uint32_t *high_address = high_head ? (uint32_t *)high_head : port_heap_get_top(); + // Special case for allocate_remaining_memory(), avoids computing low/high_address twice. + if (length == (uint32_t)-1) { + length = (high_address - low_address) * 4 - sizeof(supervisor_allocation_node); + } + if (length == 0 || length % 4 != 0) { + return NULL; + } + // 1. Matching hole on the requested side? + supervisor_allocation_node *node = find_hole(high ? high_head : low_head, length); + if (!node) { + // 2. Enough free space in the middle? + if ((high_address - low_address) * 4 >= (int32_t)(sizeof(supervisor_allocation_node) + length)) { + if (high) { + high_address -= (sizeof(supervisor_allocation_node) + length) / 4; + node = (supervisor_allocation_node *)high_address; + node->next = high_head; + high_head = node; + } else { + node = (supervisor_allocation_node *)low_address; + node->next = low_head; + low_head = node; + } + } else { + // 3. Matching hole on the other side? + node = find_hole(high ? low_head : high_head, length); + if (!node) { + // 4. GC allocation? + if (movable && gc_alloc_possible()) { + node = m_malloc_maybe(sizeof(supervisor_allocation_node) + length, true); + if (node) { + node->next = MP_STATE_VM(first_embedded_allocation); + MP_STATE_VM(first_embedded_allocation) = node; + } + } + if (!node) { + // 5. Give up. + return NULL; + } + } + } + } + node->length = length; + if (movable) { + node->length |= MOVABLE; + } + return node; +} + +supervisor_allocation *allocate_memory(uint32_t length, bool high, bool movable) { + supervisor_allocation_node *node = allocate_memory_node(length, high, movable); + if (!node) { + return NULL; + } + // Find the first free allocation. + supervisor_allocation *alloc = allocation_from_ptr(NULL); + if (!alloc) { + // We should free node again to avoid leaking, but something is wrong anyway if clients try + // to make more allocations than available, so don't bother. + return NULL; + } + alloc->ptr = &(node->data[0]); + return alloc; +} + +size_t get_allocation_length(supervisor_allocation *allocation) { + return ALLOCATION_NODE(allocation)->length & ~FLAGS; +} + + +void supervisor_move_memory(void) { + // This whole function is not needed when there are no movable allocations, let it be optimized + // out. + if (CIRCUITPY_SUPERVISOR_MOVABLE_ALLOC_COUNT == 0) { + return; + } + // This must be called exactly after freeing the heap, so that the embedded allocations, if any, + // are now in the free region. + assert(MP_STATE_VM(first_embedded_allocation) == NULL || ( + (low_head == NULL || low_head < MP_STATE_VM(first_embedded_allocation)) && + (high_head == NULL || MP_STATE_VM(first_embedded_allocation) < high_head))); + + // Save the old pointers for allocation_from_ptr(). + supervisor_allocation old_allocations_array[CIRCUITPY_SUPERVISOR_ALLOC_COUNT]; + memcpy(old_allocations_array, allocations, sizeof(allocations)); + + // Compact the low side. Traverse the list repeatedly, finding movable allocations preceded by a + // hole and swapping them, until no more are found. This is not the most runtime-efficient way, + // but probably the shortest and simplest code. + bool acted; + do { + acted = false; + supervisor_allocation_node **nodep = &low_head; + while (*nodep != NULL && (*nodep)->next != NULL) { + if (((*nodep)->length & MOVABLE) && ((*nodep)->next->length & HOLE)) { + supervisor_allocation_node *oldnode = *nodep; + supervisor_allocation_node *start = oldnode->next; + supervisor_allocation *alloc = allocation_from_ptr(&(oldnode->data[0])); + assert(alloc != NULL); + alloc->ptr = &(start->data[0]); + oldnode->next = start->next; + size_t holelength = start->length; + size_t size = sizeof(supervisor_allocation_node) + (oldnode->length & ~FLAGS); + memmove(start, oldnode, size); + supervisor_allocation_node *newhole = (supervisor_allocation_node *)(void *)((char *)start + size); + newhole->next = start; + newhole->length = holelength; + *nodep = newhole; + acted = true; + } + nodep = &((*nodep)->next); + } + } while (acted); + // Any holes bubbled to the top can be absorbed into the free middle. + while (low_head != NULL && (low_head->length & HOLE)) { + low_head = low_head->next; + } + ; + + // Don't bother compacting the high side, there are no movable allocations and no holes there in + // current usage. + + // Promote the embedded allocations to top-level ones, compacting them at the beginning of the + // now free region (or possibly in matching holes). + // The linked list is unordered, but allocations must be processed in order to avoid risking + // overwriting each other. To that end, repeatedly find the lowest element of the list, remove + // it from the list, and process it. This ad-hoc selection sort results in substantially shorter + // code than using the qsort() function from the C library. + while (MP_STATE_VM(first_embedded_allocation)) { + // First element is first candidate. + supervisor_allocation_node **pminnode = &MP_STATE_VM(first_embedded_allocation); + // Iterate from second element (if any) on. + for (supervisor_allocation_node **pnode = &(MP_STATE_VM(first_embedded_allocation)->next); *pnode != NULL; pnode = &(*pnode)->next) { + if (*pnode < *pminnode) { + pminnode = pnode; + } + } + // Remove from list. + supervisor_allocation_node *node = *pminnode; + *pminnode = node->next; + // Process. + size_t length = (node->length & ~FLAGS); + supervisor_allocation *alloc = allocation_from_ptr(&(node->data[0])); + assert(alloc != NULL); + // This may overwrite the header of node if it happened to be there already, but not the + // data. + supervisor_allocation_node *new_node = allocate_memory_node(length, false, true); + // There must be enough free space. + assert(new_node != NULL); + memmove(&(new_node->data[0]), &(node->data[0]), length); + alloc->ptr = &(new_node->data[0]); + } + + // Notify clients that their movable allocations may have moved. + old_allocations = &old_allocations_array[0]; + + #if CIRCUITPY_DISPLAYIO + supervisor_display_move_memory(); + #endif + + // Add calls to further clients here. + old_allocations = NULL; +} diff --git a/circuitpython/supervisor/shared/micropython.c b/circuitpython/supervisor/shared/micropython.c new file mode 100644 index 0000000..ebc0aef --- /dev/null +++ b/circuitpython/supervisor/shared/micropython.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 <string.h> + +#include "supervisor/serial.h" +#include "lib/oofatfs/ff.h" +#include "py/mpconfig.h" +#include "py/mphal.h" +#include "py/mpstate.h" +#include "py/runtime.h" +#include "py/stream.h" + +#include "supervisor/shared/status_leds.h" + +#if CIRCUITPY_WATCHDOG +#include "shared-bindings/watchdog/__init__.h" +#define WATCHDOG_EXCEPTION_CHECK() (MP_STATE_VM(mp_pending_exception) == &mp_watchdog_timeout_exception) +#else +#define WATCHDOG_EXCEPTION_CHECK() 0 +#endif + +int mp_hal_stdin_rx_chr(void) { + for (;;) { + #ifdef MICROPY_VM_HOOK_LOOP + MICROPY_VM_HOOK_LOOP + #endif + mp_handle_pending(true); + if (serial_bytes_available()) { + toggle_rx_led(); + return serial_read(); + } + } +} + +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + toggle_tx_led(); + + #ifdef CIRCUITPY_BOOT_OUTPUT_FILE + if (boot_output != NULL) { + // Ensure boot_out.txt is capped at 1 filesystem block and ends with a newline + if (len + boot_output->len > 508) { + vstr_add_str(boot_output, "...\n"); + boot_output = NULL; + } else { + vstr_add_strn(boot_output, str, len); + } + } + #endif + + serial_write_substring(str, len); +} + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if ((poll_flags & MP_STREAM_POLL_RD) && serial_bytes_available()) { + ret |= MP_STREAM_POLL_RD; + } + return ret; +} diff --git a/circuitpython/supervisor/shared/reload.c b/circuitpython/supervisor/shared/reload.c new file mode 100644 index 0000000..e1ae2e6 --- /dev/null +++ b/circuitpython/supervisor/shared/reload.c @@ -0,0 +1,113 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 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 "reload.h" + +#include "py/mphal.h" +#include "py/mpstate.h" +#include "supervisor/shared/reload.h" +#include "supervisor/shared/tick.h" + +supervisor_allocation *next_code_allocation; +#include "shared-bindings/supervisor/Runtime.h" + +// True if user has disabled autoreload. +static bool autoreload_enabled = false; + +// Non-zero if autoreload is temporarily off, due to an AUTORELOAD_SUSPEND_... reason. +static uint32_t autoreload_suspended = 0; + +volatile uint32_t last_autoreload_trigger = 0; + +void reload_initiate(supervisor_run_reason_t run_reason) { + supervisor_set_run_reason(run_reason); + + // Raise reload exception, in case code is running. + MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception)); + #if MICROPY_ENABLE_SCHEDULER + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } + #endif +} + +void autoreload_reset() { + last_autoreload_trigger = 0; +} + +void autoreload_enable() { + autoreload_enabled = true; + last_autoreload_trigger = 0; +} + +void autoreload_disable() { + autoreload_enabled = false; +} + +void autoreload_suspend(uint32_t suspend_reason_mask) { + autoreload_suspended |= suspend_reason_mask; +} + +void autoreload_resume(uint32_t suspend_reason_mask) { + autoreload_suspended &= ~suspend_reason_mask; +} + +inline bool autoreload_is_enabled() { + return autoreload_enabled; +} + +void autoreload_trigger() { + if (autoreload_enabled & !autoreload_suspended) { + last_autoreload_trigger = supervisor_ticks_ms32(); + // Guard against the rare time that ticks is 0; + if (last_autoreload_trigger == 0) { + last_autoreload_trigger += 1; + } + // Initiate a reload of the VM immediately. Later code will pause to + // wait for the autoreload to become ready. Doing the VM exit + // immediately is clearer for the user. + reload_initiate(RUN_REASON_AUTO_RELOAD); + } +} + +bool autoreload_ready() { + if (last_autoreload_trigger == 0 || autoreload_suspended != 0) { + return false; + } + // Wait for autoreload interval before reloading + uint32_t now = supervisor_ticks_ms32(); + uint32_t diff; + if (now >= last_autoreload_trigger) { + diff = now - last_autoreload_trigger; + } else { + diff = now + (0xffffffff - last_autoreload_trigger); + } + return diff > CIRCUITPY_AUTORELOAD_DELAY_MS; +} + +bool autoreload_pending(void) { + return last_autoreload_trigger != 0; +} diff --git a/circuitpython/supervisor/shared/reload.h b/circuitpython/supervisor/shared/reload.h new file mode 100644 index 0000000..cb3385e --- /dev/null +++ b/circuitpython/supervisor/shared/reload.h @@ -0,0 +1,84 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 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_SUPERVISOR_AUTORELOAD_H +#define MICROPY_INCLUDED_SUPERVISOR_AUTORELOAD_H + +#include "supervisor/memory.h" +#include "py/obj.h" +#include "shared-bindings/supervisor/RunReason.h" + +enum { + SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS = 0x1, + SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR = 0x2, + SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS = 0x4, + SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR = 0x8, + SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD = 0x10, + SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET = 0x20, +}; + +enum { + AUTORELOAD_SUSPEND_REPL = 0x1, + AUTORELOAD_SUSPEND_BLE = 0x2, + AUTORELOAD_SUSPEND_USB = 0x4 +}; + +typedef struct { + uint8_t options; + char filename[]; +} next_code_info_t; + +extern supervisor_allocation *next_code_allocation; + +// Helper for exiting the VM and reloading immediately. +void reload_initiate(supervisor_run_reason_t run_reason); + +// Enabled state is user controllable and very sticky. We don't reset it. +void autoreload_enable(void); +void autoreload_disable(void); +bool autoreload_is_enabled(void); + +// Start the autoreload process. +void autoreload_trigger(void); +// True when the autoreload should occur. (A trigger happened and the delay has +// passed.) +bool autoreload_ready(void); +// Reset the autoreload timer in preparation for another trigger. Call when the +// last trigger starts being executed. +void autoreload_reset(void); +// True when a trigger has occurred but we're still delaying in case another +// trigger occurs. +bool autoreload_pending(void); + +// Temporarily turn autoreload off, for the given reason(s). Autoreload triggers +// will still be tracked so resuming with autoreload ready with cause an +// immediate reload. +// Used during the REPL or during parts of BLE workflow. +void autoreload_suspend(uint32_t suspend_reason_mask); +// Allow autoreloads again, for the given reason(s). +void autoreload_resume(uint32_t suspend_reason_mask); + +#endif // MICROPY_INCLUDED_SUPERVISOR_AUTORELOAD_H diff --git a/circuitpython/supervisor/shared/rgb_led_colors.h b/circuitpython/supervisor/shared/rgb_led_colors.h new file mode 100644 index 0000000..56dc0f4 --- /dev/null +++ b/circuitpython/supervisor/shared/rgb_led_colors.h @@ -0,0 +1,30 @@ + +#define COLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b)) +// For brightness == 255 (full). This will be adjusted downward for various different RGB indicators, +// which vary in brightness. +#define INTENSITY (0x30) + +#define BLACK COLOR(0, 0, 0) +#define GREEN COLOR(0, INTENSITY, 0) +#define BLUE COLOR(0, 0, INTENSITY) +#define CYAN COLOR(0, INTENSITY, INTENSITY) +#define RED COLOR(INTENSITY, 0, 0) +#define ORANGE COLOR(INTENSITY, INTENSITY * 2 / 3, 0) +#define YELLOW COLOR(INTENSITY, INTENSITY, 0) +#define PURPLE COLOR(INTENSITY, 0, INTENSITY) +#define WHITE COLOR(INTENSITY, INTENSITY, INTENSITY) + +#define ALL_DONE GREEN +#define EXCEPTION RED +#define SAFE_MODE YELLOW +#define REPL_RUNNING WHITE + +#define ALL_DONE_BLINKS 1 +#define EXCEPTION_BLINKS 2 +#define SAFE_MODE_BLINKS 3 + +#define OFF_ON_RATIO 3 + +#define LED_SLEEP_TIME_MS 5000u + +#define BLINK_TIME_MS 100u diff --git a/circuitpython/supervisor/shared/safe_mode.c b/circuitpython/supervisor/shared/safe_mode.c new file mode 100644 index 0000000..7922cff --- /dev/null +++ b/circuitpython/supervisor/shared/safe_mode.c @@ -0,0 +1,238 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 "supervisor/shared/safe_mode.h" + +#include "mphalport.h" + +#if defined(CIRCUITPY_BOOT_BUTTON) +#include "shared-bindings/digitalio/DigitalInOut.h" +#endif +#include "shared-bindings/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/ResetReason.h" + +#include "supervisor/serial.h" +#include "supervisor/shared/rgb_led_colors.h" +#include "supervisor/shared/status_leds.h" +#include "supervisor/shared/translate.h" +#include "supervisor/shared/tick.h" + +#define SAFE_MODE_DATA_GUARD 0xad0000af +#define SAFE_MODE_DATA_GUARD_MASK 0xff0000ff + +static safe_mode_t current_safe_mode; + +safe_mode_t wait_for_safe_mode_reset(void) { + uint32_t reset_state = port_get_saved_word(); + safe_mode_t safe_mode = NO_SAFE_MODE; + if ((reset_state & SAFE_MODE_DATA_GUARD_MASK) == SAFE_MODE_DATA_GUARD) { + safe_mode = (reset_state & ~SAFE_MODE_DATA_GUARD_MASK) >> 8; + } + if (safe_mode != NO_SAFE_MODE) { + port_set_saved_word(SAFE_MODE_DATA_GUARD); + current_safe_mode = safe_mode; + return safe_mode; + } else { + current_safe_mode = 0; + } + + const mcu_reset_reason_t reset_reason = common_hal_mcu_processor_get_reset_reason(); + if (reset_reason != RESET_REASON_POWER_ON && + reset_reason != RESET_REASON_RESET_PIN && + reset_reason != RESET_REASON_UNKNOWN && + reset_reason != RESET_REASON_SOFTWARE) { + return NO_SAFE_MODE; + } + #ifdef CIRCUITPY_SKIP_SAFE_MODE_WAIT + return NO_SAFE_MODE; + #endif + port_set_saved_word(SAFE_MODE_DATA_GUARD | (MANUAL_SAFE_MODE << 8)); + // Wait for a while to allow for reset. + + #if CIRCUITPY_STATUS_LED + status_led_init(); + #endif + #ifdef CIRCUITPY_BOOT_BUTTON + digitalio_digitalinout_obj_t boot_button; + common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON); + common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP); + #endif + uint64_t start_ticks = supervisor_ticks_ms64(); + uint64_t diff = 0; + bool boot_in_safe_mode = false; + while (diff < 1000) { + #ifdef CIRCUITPY_STATUS_LED + // Blink on for 100, off for 100 + bool led_on = (diff % 250) < 125; + if (led_on) { + new_status_color(SAFE_MODE); + } else { + new_status_color(BLACK); + } + #endif + #ifdef CIRCUITPY_BOOT_BUTTON + if (!common_hal_digitalio_digitalinout_get_value(&boot_button)) { + boot_in_safe_mode = true; + break; + } + #endif + diff = supervisor_ticks_ms64() - start_ticks; + } + #if CIRCUITPY_STATUS_LED + new_status_color(BLACK); + status_led_deinit(); + #endif + if (boot_in_safe_mode) { + return USER_SAFE_MODE; + } + // Restore the original state of the saved word if no reset occured during our wait period. + port_set_saved_word(reset_state); + return NO_SAFE_MODE; +} + +void safe_mode_on_next_reset(safe_mode_t reason) { + port_set_saved_word(SAFE_MODE_DATA_GUARD | (reason << 8)); +} + +// Don't inline this so it's easy to break on it from GDB. +void __attribute__((noinline,)) reset_into_safe_mode(safe_mode_t reason) { + if (current_safe_mode > BROWNOUT && reason > BROWNOUT) { + while (true) { + // This very bad because it means running in safe mode didn't save us. Only ignore brownout + // because it may be due to a switch bouncing. + } + } + + safe_mode_on_next_reset(reason); + reset_cpu(); +} + + + +void print_safe_mode_message(safe_mode_t reason) { + if (reason == NO_SAFE_MODE) { + return; + } + + serial_write("\r\n"); + serial_write_compressed(translate("You are in safe mode because:\n")); + + const compressed_string_t *message = NULL; + + // First check for safe mode reasons that do not necessarily reflect bugs. + + switch (reason) { + case USER_SAFE_MODE: + #ifdef BOARD_USER_SAFE_MODE_ACTION + message = BOARD_USER_SAFE_MODE_ACTION; + #elif defined(CIRCUITPY_BOOT_BUTTON) + message = translate("pressing boot button at start up.\n"); + #endif + if (message != NULL) { + // Output a user safe mode string if it's set. + serial_write_compressed(translate("You requested starting safe mode by ")); + serial_write_compressed(message); + serial_write_compressed(translate("To exit, please reset the board without ")); + // The final piece is printed below. + } + break; + case MANUAL_SAFE_MODE: + message = translate("You pressed the reset button during boot. Press again to exit safe mode."); + break; + case PROGRAMMATIC_SAFE_MODE: + message = translate("The `microcontroller` module was used to boot into safe mode. Press reset to exit safe mode."); + break; + case BROWNOUT: + message = translate("The microcontroller's power dipped. Make sure your power supply provides\nenough power for the whole circuit and press reset (after ejecting CIRCUITPY)."); + break; + case USB_TOO_MANY_ENDPOINTS: + message = translate("USB devices need more endpoints than are available."); + break; + case USB_TOO_MANY_INTERFACE_NAMES: + message = translate("USB devices specify too many interface names."); + break; + case USB_BOOT_DEVICE_NOT_INTERFACE_ZERO: + message = translate("Boot device must be first device (interface #0)."); + break; + case WATCHDOG_RESET: + message = translate("Watchdog timer expired."); + break; + case NO_CIRCUITPY: + message = translate("CIRCUITPY drive could not be found or created."); + break; + default: + break; + } + + if (message) { + serial_write_compressed(message); + serial_write("\r\n"); + return; + } + + // Something worse happened. + + serial_write_compressed(translate("CircuitPython core code crashed hard. Whoops!\n")); + + switch (reason) { + case HARD_CRASH: + message = translate("Crash into the HardFault_Handler."); + break; + case MICROPY_NLR_JUMP_FAIL: + message = translate("NLR jump failed. Likely memory corruption."); + break; + case MICROPY_FATAL_ERROR: + message = translate("Fatal error."); + break; + case NO_HEAP: + message = translate("CircuitPython was unable to allocate the heap."); + break; + case HEAP_OVERWRITTEN: + message = translate("The CircuitPython heap was corrupted because the stack was too small.\nIncrease the stack size if you know how. If not:"); + break; + case GC_ALLOC_OUTSIDE_VM: + message = translate("Attempted heap allocation when VM not running."); + break; + #ifdef SOFTDEVICE_PRESENT + // defined in ports/nrf/bluetooth/bluetooth_common.mk + // will print "Unknown reason" if somehow encountered on other ports + case NORDIC_SOFT_DEVICE_ASSERT: + message = translate("Nordic system firmware failure assertion."); + break; + #endif + case FLASH_WRITE_FAIL: + message = translate("Failed to write internal flash."); + break; + case MEM_MANAGE: + message = translate("Invalid memory access."); + break; + default: + message = translate("Unknown reason."); + break; + } + serial_write_compressed(message); + serial_write_compressed(translate("\nPlease file an issue with the contents of your CIRCUITPY drive at \nhttps://github.com/adafruit/circuitpython/issues\n")); +} diff --git a/circuitpython/supervisor/shared/safe_mode.h b/circuitpython/supervisor/shared/safe_mode.h new file mode 100644 index 0000000..0c8d018 --- /dev/null +++ b/circuitpython/supervisor/shared/safe_mode.h @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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_SUPERVISOR_SAFE_MODE_H +#define MICROPY_INCLUDED_SUPERVISOR_SAFE_MODE_H + +#include "py/mpconfig.h" + +typedef enum { + NO_SAFE_MODE = 0, + BROWNOUT, + HARD_CRASH, + USER_SAFE_MODE, + HEAP_OVERWRITTEN, + MANUAL_SAFE_MODE, + MICROPY_NLR_JUMP_FAIL, + MICROPY_FATAL_ERROR, + GC_ALLOC_OUTSIDE_VM, + PROGRAMMATIC_SAFE_MODE, + NORDIC_SOFT_DEVICE_ASSERT, + FLASH_WRITE_FAIL, + MEM_MANAGE, + WATCHDOG_RESET, + USB_TOO_MANY_ENDPOINTS, + USB_TOO_MANY_INTERFACE_NAMES, + USB_BOOT_DEVICE_NOT_INTERFACE_ZERO, + NO_HEAP, + NO_CIRCUITPY, +} safe_mode_t; + +safe_mode_t wait_for_safe_mode_reset(void); + +void safe_mode_on_next_reset(safe_mode_t reason); +void reset_into_safe_mode(safe_mode_t reason) NORETURN; + +void print_safe_mode_message(safe_mode_t reason); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SAFE_MODE_H diff --git a/circuitpython/supervisor/shared/serial.c b/circuitpython/supervisor/shared/serial.c new file mode 100644 index 0000000..af90fce --- /dev/null +++ b/circuitpython/supervisor/shared/serial.c @@ -0,0 +1,297 @@ +/* + * 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 <stdarg.h> +#include <string.h> + +#include "py/mpconfig.h" + +#include "supervisor/shared/cpu.h" +#include "supervisor/shared/display.h" +#include "shared-bindings/terminalio/Terminal.h" +#include "supervisor/serial.h" +#include "supervisor/usb.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-module/usb_cdc/__init__.h" + +#if CIRCUITPY_SERIAL_BLE +#include "supervisor/shared/bluetooth/serial.h" +#endif + +#if CIRCUITPY_USB +#include "tusb.h" +#endif + +/* + * Note: DEBUG_UART currently only works on STM32 and nRF. + * Enabling on another platform will cause a crash. + */ + +#if defined(CIRCUITPY_DEBUG_UART_TX) || defined(CIRCUITPY_DEBUG_UART_RX) +#include "py/mpprint.h" +#include "shared-bindings/busio/UART.h" +busio_uart_obj_t debug_uart; +byte buf_array[64]; +#endif + +#if CIRCUITPY_USB_VENDOR +bool tud_vendor_connected(void); +#endif + +#if defined(CIRCUITPY_DEBUG_UART_TX) +STATIC void debug_uart_print_strn(void *env, const char *str, size_t len) { + (void)env; + int uart_errcode; + common_hal_busio_uart_write(&debug_uart, (const uint8_t *)str, len, &uart_errcode); +} + +const mp_print_t debug_uart_print = {NULL, debug_uart_print_strn}; +#endif + +int debug_uart_printf(const char *fmt, ...) { + #if defined(CIRCUITPY_DEBUG_UART_TX) + // Skip prints that occur before debug serial is started. It's better than + // crashing. + if (common_hal_busio_uart_deinited(&debug_uart)) { + return 0; + } + va_list ap; + va_start(ap, fmt); + int ret = mp_vprintf(&debug_uart_print, fmt, ap); + va_end(ap); + return ret; + #else + return 0; + #endif +} + +MP_WEAK void port_serial_init(void) { +} + +MP_WEAK bool port_serial_connected(void) { + return false; +} + +MP_WEAK char port_serial_read(void) { + return -1; +} + +MP_WEAK bool port_serial_bytes_available(void) { + return false; +} + +MP_WEAK void port_serial_write_substring(const char *text, uint32_t length) { + (void)text; + (void)length; +} + +void serial_early_init(void) { + #if defined(CIRCUITPY_DEBUG_UART_TX) || defined(CIRCUITPY_DEBUG_UART_RX) + debug_uart.base.type = &busio_uart_type; + + #if defined(CIRCUITPY_DEBUG_UART_RX) + const mcu_pin_obj_t *rx = MP_OBJ_TO_PTR(CIRCUITPY_DEBUG_UART_RX); + #else + const mcu_pin_obj_t *rx = NULL; + #endif + + #if defined(CIRCUITPY_DEBUG_UART_TX) + const mcu_pin_obj_t *tx = MP_OBJ_TO_PTR(CIRCUITPY_DEBUG_UART_TX); + #else + const mcu_pin_obj_t *tx = NULL; + #endif + + common_hal_busio_uart_construct(&debug_uart, tx, rx, NULL, NULL, NULL, + false, 115200, 8, BUSIO_UART_PARITY_NONE, 1, 1.0f, 64, + buf_array, true); + common_hal_busio_uart_never_reset(&debug_uart); + + // Do an initial print so that we can confirm the serial output is working. + debug_uart_printf("Serial debug setup\r\n"); + #endif +} + +void serial_init(void) { + port_serial_init(); +} + +bool serial_connected(void) { + #if CIRCUITPY_USB_VENDOR + if (tud_vendor_connected()) { + return true; + } + #endif + + #if defined(CIRCUITPY_DEBUG_UART_TX) && defined(CIRCUITPY_DEBUG_UART_RX) + return true; + #endif + + #if CIRCUITPY_SERIAL_BLE + if (ble_serial_connected()) { + return true; + } + #endif + + #if CIRCUITPY_USB_CDC + if (usb_cdc_console_enabled() && tud_cdc_connected()) { + return true; + } + #elif CIRCUITPY_USB + if (tud_cdc_connected()) { + return true; + } + #endif + + if (port_serial_connected()) { + return true; + } + return false; +} + +char serial_read(void) { + #if CIRCUITPY_USB_VENDOR + if (tud_vendor_connected() && tud_vendor_available() > 0) { + char tiny_buffer; + tud_vendor_read(&tiny_buffer, 1); + return tiny_buffer; + } + #endif + + #if defined(CIRCUITPY_DEBUG_UART_RX) + if (common_hal_busio_uart_rx_characters_available(&debug_uart)) { + int uart_errcode; + char text; + common_hal_busio_uart_read(&debug_uart, (uint8_t *)&text, 1, &uart_errcode); + return text; + } + #endif + + #if CIRCUITPY_SERIAL_BLE + if (ble_serial_available() > 0) { + return ble_serial_read_char(); + } + #endif + + #if CIRCUITPY_USB_CDC + if (!usb_cdc_console_enabled()) { + return -1; + } + #endif + #if CIRCUITPY_USB + return (char)tud_cdc_read_char(); + #endif + + if (port_serial_bytes_available() > 0) { + return port_serial_read(); + } + return -1; +} + +bool serial_bytes_available(void) { + #if CIRCUITPY_USB_VENDOR + if (tud_vendor_connected() && tud_vendor_available() > 0) { + return true; + } + #endif + + #if defined(CIRCUITPY_DEBUG_UART_RX) + if (common_hal_busio_uart_rx_characters_available(&debug_uart)) { + return true; + } + #endif + + #if CIRCUITPY_SERIAL_BLE + if (ble_serial_available()) { + return true; + } + #endif + + #if CIRCUITPY_USB_CDC + if (usb_cdc_console_enabled() && tud_cdc_available() > 0) { + return true; + } + #endif + #if CIRCUITPY_USB + if (tud_cdc_available() > 0) { + return true; + } + #endif + + if (port_serial_bytes_available() > 0) { + return true; + } + return false; +} + +void serial_write_substring(const char *text, uint32_t length) { + if (length == 0) { + return; + } + #if CIRCUITPY_TERMINALIO + int errcode; + common_hal_terminalio_terminal_write(&supervisor_terminal, (const uint8_t *)text, length, &errcode); + #endif + + #if CIRCUITPY_USB_VENDOR + if (tud_vendor_connected()) { + tud_vendor_write(text, length); + } + #endif + + #if defined(CIRCUITPY_DEBUG_UART_TX) + int uart_errcode; + + common_hal_busio_uart_write(&debug_uart, (const uint8_t *)text, length, &uart_errcode); + #endif + + #if CIRCUITPY_SERIAL_BLE + ble_serial_write(text, length); + #endif + + #if CIRCUITPY_USB_CDC + if (!usb_cdc_console_enabled()) { + return; + } + #endif + + #if CIRCUITPY_USB + uint32_t count = 0; + while (count < length && tud_cdc_connected()) { + count += tud_cdc_write(text + count, length - count); + // If we're in an interrupt, then don't wait for more room. Queue up what we can. + if (cpu_interrupt_active()) { + break; + } + usb_background(); + } + #endif + + port_serial_write_substring(text, length); +} + +void serial_write(const char *text) { + serial_write_substring(text, strlen(text)); +} diff --git a/circuitpython/supervisor/shared/stack.c b/circuitpython/supervisor/shared/stack.c new file mode 100644 index 0000000..d2188e0 --- /dev/null +++ b/circuitpython/supervisor/shared/stack.c @@ -0,0 +1,112 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 "stack.h" + +#include "py/mpconfig.h" +#include "py/runtime.h" +#include "supervisor/cpu.h" +#include "supervisor/port.h" +#include "supervisor/shared/safe_mode.h" + +extern uint32_t _estack; + +// Requested size. +static uint32_t next_stack_size = CIRCUITPY_DEFAULT_STACK_SIZE; +static uint32_t current_stack_size = 0; +// Actual location and size, may be larger than requested. +static uint32_t *stack_limit = NULL; +static size_t stack_length = 0; + +#define EXCEPTION_STACK_SIZE 1024 + +static void allocate_stack(void) { + + if (port_has_fixed_stack()) { + stack_limit = port_stack_get_limit(); + stack_length = (port_stack_get_top() - stack_limit) * sizeof(uint32_t); + current_stack_size = stack_length; + } else { + mp_uint_t regs[10]; + mp_uint_t sp = cpu_get_regs_and_sp(regs); + + mp_uint_t c_size = (mp_uint_t)port_stack_get_top() - sp; + supervisor_allocation *stack_alloc = allocate_memory(c_size + next_stack_size + EXCEPTION_STACK_SIZE, true, false); + if (stack_alloc == NULL) { + stack_alloc = allocate_memory(c_size + CIRCUITPY_DEFAULT_STACK_SIZE + EXCEPTION_STACK_SIZE, true, false); + current_stack_size = CIRCUITPY_DEFAULT_STACK_SIZE; + } else { + current_stack_size = next_stack_size; + } + stack_limit = stack_alloc->ptr; + stack_length = get_allocation_length(stack_alloc); + } + + *stack_limit = STACK_CANARY_VALUE; +} + +inline bool stack_ok(void) { + return stack_limit == NULL || *stack_limit == STACK_CANARY_VALUE; +} + +inline void assert_heap_ok(void) { + if (!stack_ok()) { + reset_into_safe_mode(HEAP_OVERWRITTEN); + } +} + +void stack_init(void) { + allocate_stack(); +} + +void stack_resize(void) { + if (stack_limit == NULL) { + return; + } + if (next_stack_size == current_stack_size) { + *stack_limit = STACK_CANARY_VALUE; + return; + } + free_memory(allocation_from_ptr(stack_limit)); + stack_limit = NULL; + allocate_stack(); +} + +uint32_t *stack_get_bottom(void) { + return stack_limit; +} + +size_t stack_get_length(void) { + return stack_length; +} + +void set_next_stack_size(uint32_t size) { + next_stack_size = size; +} + +uint32_t get_current_stack_size(void) { + return current_stack_size; +} diff --git a/circuitpython/supervisor/shared/stack.h b/circuitpython/supervisor/shared/stack.h new file mode 100644 index 0000000..98cc5a1 --- /dev/null +++ b/circuitpython/supervisor/shared/stack.h @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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_SUPERVISOR_STACK_H +#define MICROPY_INCLUDED_SUPERVISOR_STACK_H + +#include <stddef.h> + +#include "supervisor/memory.h" + +void stack_init(void); +void stack_resize(void); +// Actual stack location and size, may be larger than requested. +uint32_t *stack_get_bottom(void); +size_t stack_get_length(void); +// Next/current requested stack size. +void set_next_stack_size(uint32_t size); +uint32_t get_current_stack_size(void); +bool stack_ok(void); + +// Use this after any calls into a library which may use a lot of stack. This will raise a Python +// exception when the stack has likely overwritten a portion of the heap. +void assert_heap_ok(void); + +#ifndef STACK_CANARY_VALUE +#define STACK_CANARY_VALUE 0x017829ef +#endif + +#endif // MICROPY_INCLUDED_SUPERVISOR_STACK_H diff --git a/circuitpython/supervisor/shared/status_leds.c b/circuitpython/supervisor/shared/status_leds.c new file mode 100644 index 0000000..07ff963 --- /dev/null +++ b/circuitpython/supervisor/shared/status_leds.c @@ -0,0 +1,366 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017-2021 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 "supervisor/shared/status_leds.h" + +#include <string.h> + +#include "mphalport.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/tick.h" +#include "py/obj.h" + + +#ifdef CIRCUITPY_STATUS_LED_POWER +#include "shared-bindings/digitalio/DigitalInOut.h" +static digitalio_digitalinout_obj_t _status_power; +#endif + +#ifdef MICROPY_HW_NEOPIXEL +uint8_t rgb_status_brightness = 63; +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/neopixel_write/__init__.h" + +#ifndef MICROPY_HW_NEOPIXEL_COUNT +#define MICROPY_HW_NEOPIXEL_COUNT (1) +#endif + +static uint64_t next_start_raw_ticks; +static uint8_t status_neopixel_color[3 * MICROPY_HW_NEOPIXEL_COUNT]; +static digitalio_digitalinout_obj_t status_neopixel; + +#elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) +uint8_t rgb_status_brightness = 50; + +#ifndef MICROPY_HW_APA102_COUNT +#define MICROPY_HW_APA102_COUNT (1) +#endif + + #define APA102_BUFFER_LENGTH (4 + 4 * MICROPY_HW_APA102_COUNT + 4) +static uint8_t status_apa102_color[APA102_BUFFER_LENGTH]; + + #if CIRCUITPY_BITBANG_APA102 + #include "shared-bindings/bitbangio/SPI.h" +static bitbangio_spi_obj_t status_apa102 = { + .base = { + .type = &bitbangio_spi_type, + }, +}; + #else + #include "shared-bindings/busio/SPI.h" +busio_spi_obj_t status_apa102 = { + .base = { + .type = &busio_spi_type, + }, +}; + #endif + +#elif CIRCUITPY_PWM_RGB_LED + #include "shared-bindings/pwmio/PWMOut.h" + #include "shared-bindings/microcontroller/Pin.h" + +pwmio_pwmout_obj_t rgb_status_r = { + .base = { + .type = &pwmio_pwmout_type, + }, +}; +pwmio_pwmout_obj_t rgb_status_g = { + .base = { + .type = &pwmio_pwmout_type, + }, +}; +pwmio_pwmout_obj_t rgb_status_b = { + .base = { + .type = &pwmio_pwmout_type, + }, +}; + +uint8_t rgb_status_brightness = 0xFF; + +uint16_t status_rgb_color[3] = { + 0 /* red */, 0 /* green */, 0 /* blue */ +}; +#elif CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_STATUS) +#include "shared-bindings/digitalio/DigitalInOut.h" +digitalio_digitalinout_obj_t single_color_led; + +uint8_t rgb_status_brightness = 0xff; + +#ifndef MICROPY_HW_LED_STATUS_INVERTED +#define MICROPY_HW_LED_STATUS_INVERTED (0) +#endif + +#endif + +#if CIRCUITPY_DIGITALIO && (defined(MICROPY_HW_LED_RX) || defined(MICROPY_HW_LED_TX)) +#include "shared-bindings/digitalio/DigitalInOut.h" + +#ifdef MICROPY_HW_LED_RX +digitalio_digitalinout_obj_t rx_led; +#endif + +#ifdef MICROPY_HW_LED_TX +digitalio_digitalinout_obj_t tx_led; +#endif +#endif + +#if CIRCUITPY_STATUS_LED +static uint32_t current_status_color = 0; +#endif + +static bool status_led_init_in_progress = false; +void status_led_init() { + if (status_led_init_in_progress) { + // Avoid recursion. + return; + } + status_led_init_in_progress = true; + + #ifdef CIRCUITPY_STATUS_LED_POWER + common_hal_digitalio_digitalinout_construct(&_status_power, CIRCUITPY_STATUS_LED_POWER); + common_hal_digitalio_digitalinout_switch_to_output(&_status_power, + CIRCUITPY_STATUS_LED_POWER_INVERTED == 0, DRIVE_MODE_PUSH_PULL); + #endif + + #ifdef MICROPY_HW_NEOPIXEL + common_hal_digitalio_digitalinout_construct(&status_neopixel, MICROPY_HW_NEOPIXEL); + common_hal_digitalio_digitalinout_switch_to_output(&status_neopixel, false, DRIVE_MODE_PUSH_PULL); + #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) + // Set every byte to 0xff except the start 4 bytes that make up the header. + memset(status_apa102_color + 4, 0xff, APA102_BUFFER_LENGTH - 4); + #if CIRCUITPY_BITBANG_APA102 + shared_module_bitbangio_spi_construct(&status_apa102, + MICROPY_HW_APA102_SCK, + MICROPY_HW_APA102_MOSI, + NULL); + #else + if (!common_hal_busio_spi_deinited(&status_apa102)) { + common_hal_busio_spi_deinit(&status_apa102); + } + common_hal_busio_spi_construct(&status_apa102, + MICROPY_HW_APA102_SCK, + MICROPY_HW_APA102_MOSI, + NULL, + false); + #endif + #if CIRCUITPY_BITBANG_APA102 + shared_module_bitbangio_spi_try_lock(&status_apa102); + // Use 1MHz for clock rate. Some APA102's are spec'd 800kHz-1200kHz, + // though many can run much faster. bitbang will probably run slower. + shared_module_bitbangio_spi_configure(&status_apa102, 1000000, 0, 0, 8); + #else + common_hal_busio_spi_try_lock(&status_apa102); + common_hal_busio_spi_configure(&status_apa102, 1000000, 0, 0, 8); + #endif + + + #elif CIRCUITPY_PWM_RGB_LED + if (common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_R)) { + pwmout_result_t red_result = common_hal_pwmio_pwmout_construct(&rgb_status_r, CIRCUITPY_RGB_STATUS_R, 0, 50000, false); + + if (PWMOUT_OK == red_result) { + common_hal_pwmio_pwmout_never_reset(&rgb_status_r); + } + } + + if (common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_G)) { + pwmout_result_t green_result = common_hal_pwmio_pwmout_construct(&rgb_status_g, CIRCUITPY_RGB_STATUS_G, 0, 50000, false); + + if (PWMOUT_OK == green_result) { + common_hal_pwmio_pwmout_never_reset(&rgb_status_g); + } + } + + if (common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_B)) { + pwmout_result_t blue_result = common_hal_pwmio_pwmout_construct(&rgb_status_b, CIRCUITPY_RGB_STATUS_B, 0, 50000, false); + + if (PWMOUT_OK == blue_result) { + common_hal_pwmio_pwmout_never_reset(&rgb_status_b); + } + } + + #elif defined(MICROPY_HW_LED_STATUS) + common_hal_digitalio_digitalinout_construct(&single_color_led, MICROPY_HW_LED_STATUS); + common_hal_digitalio_digitalinout_switch_to_output( + &single_color_led, MICROPY_HW_LED_STATUS_INVERTED == 0, DRIVE_MODE_PUSH_PULL); + #endif + + #if CIRCUITPY_DIGITALIO && CIRCUITPY_STATUS_LED + // Force a write of the current status color. + uint32_t rgb = current_status_color; + current_status_color = 0x1000000; // Not a valid color + new_status_color(rgb); + #endif + + status_led_init_in_progress = false; +} + +void status_led_deinit() { + #ifdef MICROPY_HW_NEOPIXEL + // Make sure the pin stays low for the reset period. The pin reset may pull + // it up and stop the reset period. + while (port_get_raw_ticks(NULL) < next_start_raw_ticks) { + } + common_hal_reset_pin(MICROPY_HW_NEOPIXEL); + + #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) + #if CIRCUITPY_BITBANG_APA102 + shared_module_bitbangio_spi_deinit(&status_apa102); + #else + common_hal_busio_spi_deinit(&status_apa102); + #endif + + #elif CIRCUITPY_PWM_RGB_LED + if (!common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_R)) { + common_hal_pwmio_pwmout_deinit(&rgb_status_r); + } + + if (!common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_G)) { + common_hal_pwmio_pwmout_deinit(&rgb_status_g); + } + + if (!common_hal_mcu_pin_is_free(CIRCUITPY_RGB_STATUS_B)) { + common_hal_pwmio_pwmout_deinit(&rgb_status_b); + } + + #elif defined(MICROPY_HW_LED_STATUS) + common_hal_digitalio_digitalinout_deinit(&single_color_led); + #endif + + #ifdef CIRCUITPY_STATUS_LED_POWER + common_hal_digitalio_digitalinout_deinit(&_status_power); + #endif +} + +void new_status_color(uint32_t rgb) { + #if CIRCUITPY_STATUS_LED + if (current_status_color == rgb) { + return; + } + uint32_t rgb_adjusted = color_brightness(rgb, rgb_status_brightness); + current_status_color = rgb; + #endif + + #ifdef MICROPY_HW_NEOPIXEL + for (size_t i = 0; i < MICROPY_HW_NEOPIXEL_COUNT; i++) { + status_neopixel_color[3 * i + 0] = (rgb_adjusted >> 8) & 0xff; + status_neopixel_color[3 * i + 1] = (rgb_adjusted >> 16) & 0xff; + status_neopixel_color[3 * i + 2] = rgb_adjusted & 0xff; + } + common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3 * MICROPY_HW_NEOPIXEL_COUNT); + next_start_raw_ticks = port_get_raw_ticks(NULL) + 2; + #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) + for (size_t i = 0; i < MICROPY_HW_APA102_COUNT; i++) { + // Skip 4 + offset to skip the header bytes too. + status_apa102_color[4 * i + 4 + 1] = rgb_adjusted & 0xff; + status_apa102_color[4 * i + 4 + 2] = (rgb_adjusted >> 8) & 0xff; + status_apa102_color[4 * i + 4 + 3] = (rgb_adjusted >> 16) & 0xff; + } + + #if CIRCUITPY_BITBANG_APA102 + shared_module_bitbangio_spi_write(&status_apa102, status_apa102_color, APA102_BUFFER_LENGTH); + #else + common_hal_busio_spi_write(&status_apa102, status_apa102_color, APA102_BUFFER_LENGTH); + #endif + + #elif CIRCUITPY_PWM_RGB_LED + uint8_t red_u8 = (rgb_adjusted >> 16) & 0xFF; + uint8_t green_u8 = (rgb_adjusted >> 8) & 0xFF; + uint8_t blue_u8 = rgb_adjusted & 0xFF; + + #ifdef CIRCUITPY_RGB_STATUS_INVERTED_PWM + status_rgb_color[0] = (1 << 16) - 1 - ((uint16_t)(red_u8 << 8) + red_u8); + status_rgb_color[1] = (1 << 16) - 1 - ((uint16_t)(green_u8 << 8) + green_u8); + status_rgb_color[2] = (1 << 16) - 1 - ((uint16_t)(blue_u8 << 8) + blue_u8); + #else + status_rgb_color[0] = (uint16_t)(red_u8 << 8) + red_u8; + status_rgb_color[1] = (uint16_t)(green_u8 << 8) + green_u8; + status_rgb_color[2] = (uint16_t)(blue_u8 << 8) + blue_u8; + #endif + + common_hal_pwmio_pwmout_set_duty_cycle(&rgb_status_r, status_rgb_color[0]); + common_hal_pwmio_pwmout_set_duty_cycle(&rgb_status_g, status_rgb_color[1]); + common_hal_pwmio_pwmout_set_duty_cycle(&rgb_status_b, status_rgb_color[2]); + #elif CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_STATUS) + common_hal_digitalio_digitalinout_set_value( + &single_color_led, (rgb_adjusted > 0) ^ MICROPY_HW_LED_STATUS_INVERTED); + #endif +} + +uint32_t color_brightness(uint32_t color, uint8_t brightness) { + #if CIRCUITPY_STATUS_LED + uint32_t result = ((color & 0xff0000) * brightness / 255) & 0xff0000; + result += ((color & 0xff00) * brightness / 255) & 0xff00; + result += ((color & 0xff) * brightness / 255) & 0xff; + return result; + #else + return color; + #endif +} + +void set_status_brightness(uint8_t level) { + #if CIRCUITPY_STATUS_LED + rgb_status_brightness = level; + // This is only called by user code and we're never controlling the status + // LED when user code is running. So, we don't need to update the current + // state (there is none.) + #endif +} + +void init_rxtx_leds(void) { + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_RX) + common_hal_digitalio_digitalinout_construct(&rx_led, MICROPY_HW_LED_RX); + common_hal_digitalio_digitalinout_switch_to_output(&rx_led, true, DRIVE_MODE_PUSH_PULL); + common_hal_digitalio_digitalinout_never_reset(&rx_led); + #endif + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_TX) + common_hal_digitalio_digitalinout_construct(&tx_led, MICROPY_HW_LED_TX); + common_hal_digitalio_digitalinout_switch_to_output(&tx_led, true, DRIVE_MODE_PUSH_PULL); + common_hal_digitalio_digitalinout_never_reset(&tx_led); + #endif +} + +void deinit_rxtx_leds(void) { + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_RX) + common_hal_digitalio_digitalinout_deinit(&rx_led); + #endif + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_TX) + common_hal_digitalio_digitalinout_deinit(&tx_led); + #endif +} + +void toggle_rx_led(void) { + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_RX) + common_hal_digitalio_digitalinout_set_value(&rx_led, !common_hal_digitalio_digitalinout_get_value(&rx_led)); + #endif +} + + +void toggle_tx_led(void) { + #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_TX) + common_hal_digitalio_digitalinout_set_value(&tx_led, !common_hal_digitalio_digitalinout_get_value(&tx_led)); + #endif +} diff --git a/circuitpython/supervisor/shared/status_leds.h b/circuitpython/supervisor/shared/status_leds.h new file mode 100644 index 0000000..99aa027 --- /dev/null +++ b/circuitpython/supervisor/shared/status_leds.h @@ -0,0 +1,62 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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_SUPERVISOR_STATUS_LEDS_H +#define MICROPY_INCLUDED_SUPERVISOR_STATUS_LEDS_H + +#include <stdint.h> +#include <stdbool.h> + +#include "shared/runtime/pyexec.h" +#include "supervisor/port.h" + +#include "py/mpconfig.h" +#include "rgb_led_colors.h" + +#include "supervisor/shared/safe_mode.h" + +// Overall, the time module must be implemented. +// To work with a DotStar, one must have MICROPY_HW_APA102_SCK and +// MICROPY_HW_APA102_MOSI defined and bitbangio.SPI or busio.SPI implemented. +// To work with a NeoPixel, one must have MICROPY_HW_NEOPIXEL defined and +// neopixel_write implemented. + +#define CIRCUITPY_PWM_RGB_LED (defined(CIRCUITPY_RGB_STATUS_R) || defined(CIRCUITPY_RGB_STATUS_G) || defined(CIRCUITPY_RGB_STATUS_B)) +#define CIRCUITPY_STATUS_LED ((CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_STATUS)) || defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)) || CIRCUITPY_PWM_RGB_LED) + +void status_led_init(void); +void status_led_deinit(void); +void new_status_color(uint32_t rgb); + +uint32_t color_brightness(uint32_t color, uint8_t brightness); +void set_status_brightness(uint8_t level); + +void init_rxtx_leds(void); +void deinit_rxtx_leds(void); +void toggle_rx_led(void); +void toggle_tx_led(void); + +#endif // MICROPY_INCLUDED_SUPERVISOR_STATUS_LEDS_H diff --git a/circuitpython/supervisor/shared/tick.c b/circuitpython/supervisor/shared/tick.c new file mode 100644 index 0000000..104083f --- /dev/null +++ b/circuitpython/supervisor/shared/tick.c @@ -0,0 +1,178 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler 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 "supervisor/shared/tick.h" + +#include "shared/runtime/interrupt_char.h" +#include "py/mphal.h" +#include "py/mpstate.h" +#include "py/runtime.h" +#include "supervisor/linker.h" +#include "supervisor/filesystem.h" +#include "supervisor/background_callback.h" +#include "supervisor/port.h" +#include "supervisor/shared/stack.h" + +#if CIRCUITPY_BLEIO_HCI +#include "common-hal/_bleio/__init__.h" +#endif + +#if CIRCUITPY_DISPLAYIO +#include "shared-module/displayio/__init__.h" +#endif + +#if CIRCUITPY_GAMEPADSHIFT +#include "shared-module/gamepadshift/__init__.h" +#endif + +#if CIRCUITPY_KEYPAD +#include "shared-module/keypad/__init__.h" +#endif + +#include "shared-bindings/microcontroller/__init__.h" + +#if CIRCUITPY_WATCHDOG +#include "shared-bindings/watchdog/__init__.h" +#define WATCHDOG_EXCEPTION_CHECK() (MP_STATE_VM(mp_pending_exception) == &mp_watchdog_timeout_exception) +#else +#define WATCHDOG_EXCEPTION_CHECK() 0 +#endif + +static volatile uint64_t PLACE_IN_DTCM_BSS(background_ticks); + +static background_callback_t tick_callback; + +static volatile uint64_t last_finished_tick = 0; + +static volatile size_t tick_enable_count = 0; + +static void supervisor_background_tasks(void *unused) { + port_start_background_task(); + + assert_heap_ok(); + + #if CIRCUITPY_BLEIO_HCI + bleio_hci_background(); + #endif + + #if CIRCUITPY_DISPLAYIO + displayio_background(); + #endif + + filesystem_background(); + + port_background_task(); + + assert_heap_ok(); + + last_finished_tick = port_get_raw_ticks(NULL); + + port_finish_background_task(); +} + +bool supervisor_background_tasks_ok(void) { + return port_get_raw_ticks(NULL) - last_finished_tick < 1024; +} + +void supervisor_tick(void) { + #if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0 + filesystem_tick(); + #endif + + #ifdef CIRCUITPY_GAMEPAD_TICKS + if (!(port_get_raw_ticks(NULL) & CIRCUITPY_GAMEPAD_TICKS)) { + #if CIRCUITPY_GAMEPADSHIFT + gamepadshift_tick(); + #endif + } + #endif + + #if CIRCUITPY_KEYPAD + keypad_tick(); + #endif + + background_callback_add(&tick_callback, supervisor_background_tasks, NULL); +} + +uint64_t supervisor_ticks_ms64() { + uint64_t result; + result = port_get_raw_ticks(NULL); + result = result * 1000 / 1024; + return result; +} + +uint32_t supervisor_ticks_ms32() { + return supervisor_ticks_ms64(); +} + + +void PLACE_IN_ITCM(supervisor_run_background_tasks_if_tick)() { + background_callback_run_all(); +} + +void mp_hal_delay_ms(mp_uint_t delay_ms) { + uint64_t start_tick = port_get_raw_ticks(NULL); + // Adjust the delay to ticks vs ms. + uint64_t delay_ticks = (delay_ms * (uint64_t)1024) / 1000; + uint64_t end_tick = start_tick + delay_ticks; + int64_t remaining = delay_ticks; + + // Loop until we've waited long enough or we've been CTRL-Ced by autoreload + // or the user. + while (remaining > 0 && !mp_hal_is_interrupted()) { + RUN_BACKGROUND_TASKS; + remaining = end_tick - port_get_raw_ticks(NULL); + // We break a bit early so we don't risk setting the alarm before the time when we call + // sleep. + if (remaining < 1) { + break; + } + port_interrupt_after_ticks(remaining); + // Idle until an interrupt happens. + port_idle_until_interrupt(); + remaining = end_tick - port_get_raw_ticks(NULL); + } +} + +void supervisor_enable_tick(void) { + common_hal_mcu_disable_interrupts(); + if (tick_enable_count == 0) { + port_enable_tick(); + } + tick_enable_count++; + common_hal_mcu_enable_interrupts(); +} + +void supervisor_disable_tick(void) { + common_hal_mcu_disable_interrupts(); + if (tick_enable_count > 0) { + tick_enable_count--; + } + if (tick_enable_count == 0) { + port_disable_tick(); + } + common_hal_mcu_enable_interrupts(); +} diff --git a/circuitpython/supervisor/shared/tick.h b/circuitpython/supervisor/shared/tick.h new file mode 100644 index 0000000..d805aeb --- /dev/null +++ b/circuitpython/supervisor/shared/tick.h @@ -0,0 +1,75 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler 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 __INCLUDED_SUPERVISOR_TICK_H +#define __INCLUDED_SUPERVISOR_TICK_H + +#include <stdint.h> +#include <stdbool.h> + +/** @brief To be called once every ms + * + * The port must call supervisor_tick once per millisecond to perform regular tasks. + * This is called from the SysTick interrupt or similar, and is safe to call in an + * interrupt context. + */ +extern void supervisor_tick(void); + +/** @brief Get the lower 32 bits of the time in milliseconds + * + * This can be more efficient than supervisor_ticks_ms64, for sites where a wraparound + * of ~49.5 days is not harmful. + */ +extern uint32_t supervisor_ticks_ms32(void); + +/** @brief Get the full time in milliseconds + * + * Because common ARM mcus cannot atomically work with 64-bit quantities, this + * function must briefly disable interrupts in order to return the value. If + * only relative durations of less than about ~49.5 days need to be considered, + * then it may be possible to use supervisor_ticks_ms32() instead. + */ +extern uint64_t supervisor_ticks_ms64(void); + +/** @brief Run background ticks, but only about every millisecond. + * + * Normally, this is not called directly. Instead use the RUN_BACKGROUND_TASKS + * macro. + */ +extern void supervisor_run_background_if_tick(void); + +extern void supervisor_enable_tick(void); +extern void supervisor_disable_tick(void); + +/** + * @brief Return true if tick-based background tasks ran within the last 1s + * + * Note that when ticks are not enabled, this function can return false; this is + * intended. + */ +extern bool supervisor_background_tasks_ok(void); + +#endif diff --git a/circuitpython/supervisor/shared/traceback.c b/circuitpython/supervisor/shared/traceback.c new file mode 100644 index 0000000..4413794 --- /dev/null +++ b/circuitpython/supervisor/shared/traceback.c @@ -0,0 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Christian Walther + * + * 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 "traceback.h" + +supervisor_allocation *prev_traceback_allocation; diff --git a/circuitpython/supervisor/shared/traceback.h b/circuitpython/supervisor/shared/traceback.h new file mode 100644 index 0000000..dd3c72c --- /dev/null +++ b/circuitpython/supervisor/shared/traceback.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Christian Walther + * + * 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_SUPERVISOR_TRACEBACK_H +#define MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H + +#include "supervisor/memory.h" + +extern supervisor_allocation *prev_traceback_allocation; + +#endif // MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H diff --git a/circuitpython/supervisor/shared/translate.c b/circuitpython/supervisor/shared/translate.c new file mode 100644 index 0000000..fefda46 --- /dev/null +++ b/circuitpython/supervisor/shared/translate.c @@ -0,0 +1,152 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 "supervisor/shared/translate.h" + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#ifndef NO_QSTR +#include "genhdr/compression.generated.h" +#endif + +#include "py/misc.h" +#include "py/mpprint.h" +#include "supervisor/serial.h" + +void serial_write_compressed(const compressed_string_t *compressed) { + mp_printf(MP_PYTHON_PRINTER, "%S", compressed); +} + +STATIC void get_word(int n, const mchar_t **pos, const mchar_t **end) { + int len = minlen; + int i = 0; + *pos = words; + while (wlencount[i] <= n) { + n -= wlencount[i]; + *pos += len * wlencount[i]; + i++; + len++; + } + *pos += len * n; + *end = *pos + len; +} + +STATIC int put_utf8(char *buf, int u) { + if (u <= 0x7f) { + *buf = u; + return 1; + } else if (word_start <= u && u <= word_end) { + uint n = (u - word_start); + const mchar_t *pos, *end; + get_word(n, &pos, &end); + int ret = 0; + // note that at present, entries in the words table are + // guaranteed not to represent words themselves, so this adds + // at most 1 level of recursive call + for (; pos < end; pos++) { + int len = put_utf8(buf, *pos); + buf += len; + ret += len; + } + return ret; + } else if (u <= 0x07ff) { + *buf++ = 0b11000000 | (u >> 6); + *buf = 0b10000000 | (u & 0b00111111); + return 2; + } else { // u <= 0xffff + *buf++ = 0b11100000 | (u >> 12); + *buf++ = 0b10000000 | ((u >> 6) & 0b00111111); + *buf = 0b10000000 | (u & 0b00111111); + return 3; + } +} + +uint16_t decompress_length(const compressed_string_t *compressed) { + #ifndef NO_QSTR + #if (compress_max_length_bits <= 8) + return 1 + (compressed->data >> (8 - compress_max_length_bits)); + #else + return 1 + ((compressed->data * 256 + compressed->tail[0]) >> (16 - compress_max_length_bits)); + #endif + #endif +} + +char *decompress(const compressed_string_t *compressed, char *decompressed) { + uint8_t this_byte = compress_max_length_bits / 8; + uint8_t this_bit = 7 - compress_max_length_bits % 8; + uint8_t b = (&compressed->data)[this_byte] << (compress_max_length_bits % 8); + uint16_t length = decompress_length(compressed); + + // Stop one early because the last byte is always NULL. + for (uint16_t i = 0; i < length - 1;) { + uint32_t bits = 0; + uint8_t bit_length = 0; + uint32_t max_code = lengths[0]; + uint32_t searched_length = lengths[0]; + while (true) { + bits <<= 1; + if ((0x80 & b) != 0) { + bits |= 1; + } + b <<= 1; + bit_length += 1; + if (this_bit == 0) { + this_bit = 7; + this_byte += 1; + b = (&compressed->data)[this_byte]; // This may read past the end but its never used. + } else { + this_bit -= 1; + } + if (max_code > 0 && bits < max_code) { + break; + } + max_code = (max_code << 1) + lengths[bit_length]; + searched_length += lengths[bit_length]; + } + i += put_utf8(decompressed + i, values[searched_length + bits - max_code]); + } + + decompressed[length - 1] = '\0'; + return decompressed; +} + +inline +// gcc10 -flto has issues with this being always_inline for debug builds. +#if CIRCUITPY_DEBUG < 1 +__attribute__((always_inline)) +#endif +const compressed_string_t *translate(const char *original) { + #ifndef NO_QSTR + #define QDEF(id, hash, len, str) + #define TRANSLATION(id, firstbyte, ...) if (strcmp(original, id) == 0) { static const compressed_string_t v = { .data = firstbyte, .tail = { __VA_ARGS__ } }; return &v; } else + #include "genhdr/qstrdefs.generated.h" +#undef TRANSLATION +#undef QDEF + #endif + return NULL; +} diff --git a/circuitpython/supervisor/shared/translate.h b/circuitpython/supervisor/shared/translate.h new file mode 100644 index 0000000..da58e1e --- /dev/null +++ b/circuitpython/supervisor/shared/translate.h @@ -0,0 +1,93 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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_SUPERVISOR_TRANSLATE_H +#define MICROPY_INCLUDED_SUPERVISOR_TRANSLATE_H + +#include <stdint.h> + +// The format of the compressed data is: +// - the size of the uncompressed string in UTF-8 bytes, encoded as a +// (compress_max_length_bits)-bit number. compress_max_length_bits is +// computed during dictionary generation time, and happens to be 8 +// for all current platforms. However, it'll probably end up being +// 9 in some translations sometime in the future. This length excludes +// the trailing NUL, though notably decompress_length includes it. +// +// - followed by the huffman encoding of the individual UTF-16 code +// points that make up the string. The trailing "\0" is not +// represented by a huffman code, but is implied by the length. +// (building the huffman encoding on UTF-16 code points gave better +// compression than building it on UTF-8 bytes) +// +// - code points starting at 128 (word_start) and potentially extending +// to 255 (word_end) (but never interfering with the target +// language's used code points) stand for dictionary entries in a +// dictionary with size up to 256 code points. The dictionary entries +// are computed with a heuristic based on frequent substrings of 2 to +// 9 code points. These are called "words" but are not, grammatically +// speaking, words. They're just spans of code points that frequently +// occur together. They are ordered shortest to longest. +// +// - dictionary entries are non-overlapping, and the _ending_ index of each +// entry is stored in an array. A count of words of each length, from +// minlen to maxlen, is given in the array called wlencount. From +// this small array, the start and end of the N'th word can be +// calculated by an efficient, small loop. (A bit of time is traded +// to reduce the size of this table indicating lengths) +// +// The "data" / "tail" construct is so that the struct's last member is a +// "flexible array". However, the _only_ member is not permitted to be +// a flexible member, so we have to declare the first byte as a separte +// member of the structure. +// +// For translations where length needs 8 bits, this saves about 1.5 +// bytes per string on average compared to a structure of {uint16_t, +// flexible array}, but is also future-proofed against strings with +// UTF-8 length above 256, with a savings of about 1.375 bytes per +// string. +typedef struct compressed_string { + uint8_t data; + const uint8_t tail[]; +} compressed_string_t; + +// Return the compressed, translated version of a source string +// Usually, due to LTO, this is optimized into a load of a constant +// pointer. +const compressed_string_t *translate(const char *c); +void serial_write_compressed(const compressed_string_t *compressed); +char *decompress(const compressed_string_t *compressed, char *decompressed); +uint16_t decompress_length(const compressed_string_t *compressed); + + +// Map MicroPython's error messages to our translations. +#if defined(MICROPY_ENABLE_DYNRUNTIME) && MICROPY_ENABLE_DYNRUNTIME +#define MP_ERROR_TEXT(x) (x) +#else +#define MP_ERROR_TEXT(x) translate(x) +#endif + +#endif // MICROPY_INCLUDED_SUPERVISOR_TRANSLATE_H diff --git a/circuitpython/supervisor/shared/usb/tusb_config.h b/circuitpython/supervisor/shared/usb/tusb_config.h new file mode 100644 index 0000000..13b4367 --- /dev/null +++ b/circuitpython/supervisor/shared/usb/tusb_config.h @@ -0,0 +1,167 @@ +/**************************************************************************/ +/*! + @file tusb_config.h + @author hathach (tinyusb.org) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2013, hathach (tinyusb.org) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +/**************************************************************************/ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#include "py/mpconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------+ +// COMMON CONFIGURATION +// --------------------------------------------------------------------+ + +// When debugging TinyUSB, only output to the UART debug link. +#if CIRCUITPY_DEBUG_TINYUSB > 0 && defined(CIRCUITPY_DEBUG_UART_TX) +#define CFG_TUSB_DEBUG CIRCUITPY_DEBUG_TINYUSB +#define CFG_TUSB_DEBUG_PRINTF debug_uart_printf +#endif + +/*------------- RTOS -------------*/ +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif +// #define CFG_TUD_TASK_QUEUE_SZ 16 + +// --------------------------------------------------------------------+ +// DEVICE CONFIGURATION +// --------------------------------------------------------------------+ + +#if CIRCUITPY_USB_DEVICE_INSTANCE == 0 +#if USB_HIGHSPEED +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) +#endif +#elif CIRCUITPY_USB_DEVICE_INSTANCE == 1 +#if USB_HIGHSPEED +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE) +#endif +#endif + +// Vendor name included in Inquiry response, max 8 bytes +#define CFG_TUD_MSC_VENDOR USB_MANUFACTURER_8 + +// Product name included in Inquiry response, max 16 bytes +#define CFG_TUD_MSC_PRODUCT USB_PRODUCT_16 +#define CFG_TUD_ENDPOINT0_SIZE 64 + +// ------------- CLASS -------------// + +// Will be set to 2 in supervisor.mk if CIRCUITPY_USB_CDC is set. +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif + +#define CFG_TUD_MSC CIRCUITPY_USB_MSC +#define CFG_TUD_HID CIRCUITPY_USB_HID +#define CFG_TUD_MIDI CIRCUITPY_USB_MIDI +#define CFG_TUD_VENDOR CIRCUITPY_USB_VENDOR +#define CFG_TUD_CUSTOM_CLASS 0 + +/*------------------------------------------------------------------*/ +/* CLASS DRIVER + *------------------------------------------------------------------*/ + +// Product revision string included in Inquiry response, max 4 bytes +#define CFG_TUD_MSC_PRODUCT_REV "1.0" + + +// --------------------------------------------------------------------+ +// USB RAM PLACEMENT +// --------------------------------------------------------------------+ +#if !defined(CIRCUITPY_TUSB_ATTR_USBRAM) +#define CIRCUITPY_TUSB_ATTR_USBRAM ".bss.usbram" +#endif + +#define CFG_TUSB_ATTR_USBRAM __attribute__((section(CIRCUITPY_TUSB_ATTR_USBRAM))) + + +#if !defined(CIRCUITPY_TUSB_MEM_ALIGN) +#define CIRCUITPY_TUSB_MEM_ALIGN 4 +#endif + +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(CIRCUITPY_TUSB_MEM_ALIGN))) + +// -------------------------------------------------------------------- +// HOST CONFIGURATION +// -------------------------------------------------------------------- + +#if CIRCUITPY_USB_HOST + +#if CIRCUITPY_USB_HOST_INSTANCE == 0 +#if USB_HIGHSPEED +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST) +#endif +#elif CIRCUITPY_USB_HOST_INSTANCE == 1 +#if USB_HIGHSPEED +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST) +#endif +#endif + +// Size of buffer to hold descriptors and other data used for enumeration +#ifndef CFG_TUH_ENUMERATION_BUFSIZE +#define CFG_TUH_ENUMERATION_BUFSIZE 256 +#endif + +#define CFG_TUH_HUB 1 +#define CFG_TUH_CDC 0 +#define CFG_TUH_MSC 0 +#define CFG_TUH_VENDOR 0 + +// max device support (excluding hub device) +#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports + +// Number of endpoints per device +#define CFG_TUH_ENDPOINT_MAX 8 + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/circuitpython/supervisor/shared/usb/usb.c b/circuitpython/supervisor/shared/usb/usb.c new file mode 100644 index 0000000..a188544 --- /dev/null +++ b/circuitpython/supervisor/shared/usb/usb.c @@ -0,0 +1,321 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 hathach 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 "py/objstr.h" +#include "py/runtime.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "supervisor/background_callback.h" +#include "supervisor/port.h" +#include "supervisor/serial.h" +#include "supervisor/usb.h" +#include "supervisor/shared/workflow.h" +#include "shared/runtime/interrupt_char.h" +#include "shared/readline/readline.h" + +#if CIRCUITPY_STORAGE +#include "shared-module/storage/__init__.h" +#endif + +#if CIRCUITPY_USB_CDC +#include "shared-module/usb_cdc/__init__.h" +#endif + +#if CIRCUITPY_USB_HID +#include "shared-module/usb_hid/__init__.h" +#endif + +#if CIRCUITPY_USB_MIDI +#include "shared-module/usb_midi/__init__.h" +#endif + +#include "tusb.h" + +#if CIRCUITPY_USB_VENDOR +#include "usb_vendor_descriptors.h" + +// The WebUSB support being conditionally added to this file is based on the +// tinyusb demo examples/device/webusb_serial. + +static bool web_serial_connected = false; + +#define URL "www.tinyusb.org/examples/webusb-serial" + +const tusb_desc_webusb_url_t desc_webusb_url = +{ + .bLength = 3 + sizeof(URL) - 1, + .bDescriptorType = 3, // WEBUSB URL type + .bScheme = 1, // 0: http, 1: https + .url = URL +}; + +#endif + +bool usb_enabled(void) { + return tusb_inited(); +} + +MP_WEAK void post_usb_init(void) { +} + +void usb_init(void) { + init_usb_hardware(); + + tusb_init(); + + post_usb_init(); + + #if MICROPY_KBD_EXCEPTION && CIRCUITPY_USB_CDC + // Set Ctrl+C as wanted char, tud_cdc_rx_wanted_cb() usb_callback will be invoked when Ctrl+C is received + // This usb_callback always got invoked regardless of mp_interrupt_char value since we only set it once here + + // Don't watch for ctrl-C if there is no REPL. + if (usb_cdc_console_enabled()) { + // Console will always be itf 0. + tud_cdc_set_wanted_char(CHAR_CTRL_C); + } + #endif +} + +// Set up USB defaults before any USB changes are made in boot.py +void usb_set_defaults(void) { + #if CIRCUITPY_STORAGE && CIRCUITPY_USB_MSC + storage_usb_set_defaults(); + #endif + + #if CIRCUITPY_USB_CDC + usb_cdc_set_defaults(); + #endif + + #if CIRCUITPY_USB_HID + usb_hid_set_defaults(); + #endif + + #if CIRCUITPY_USB_MIDI + usb_midi_set_defaults(); + #endif +}; + +// Some dynamic USB data must be saved after boot.py. How much is needed? +size_t usb_boot_py_data_size(void) { + size_t size = 0; + + #if CIRCUITPY_USB_HID + size += usb_hid_report_descriptor_length(); + #endif + + return size; +} + +// Fill in the data to save. +void usb_get_boot_py_data(uint8_t *temp_storage, size_t temp_storage_size) { + #if CIRCUITPY_USB_HID + usb_hid_build_report_descriptor(temp_storage, temp_storage_size); + #endif +} + +// After VM is gone, save data into non-heap storage (storage_allocations). +void usb_return_boot_py_data(uint8_t *temp_storage, size_t temp_storage_size) { + #if CIRCUITPY_USB_HID + usb_hid_save_report_descriptor(temp_storage, temp_storage_size); + #endif + + // Now we can also build the rest of the descriptors and place them in storage_allocations. + usb_build_descriptors(); +} + +// Call this when ready to run code.py or a REPL, and a VM has been started. +void usb_setup_with_vm(void) { + #if CIRCUITPY_USB_HID + usb_hid_setup_devices(); + #endif + + #if CIRCUITPY_USB_MIDI + usb_midi_setup_ports(); + #endif +} + +void usb_disconnect(void) { + tud_disconnect(); +} + +void usb_background(void) { + if (usb_enabled()) { + #if CFG_TUSB_OS == OPT_OS_NONE + tud_task(); + #if CIRCUITPY_USB_HOST + tuh_task(); + #endif + #endif + // No need to flush if there's no REPL. + #if CIRCUITPY_USB_CDC + if (usb_cdc_console_enabled()) { + // Console will always be itf 0. + tud_cdc_write_flush(); + } + #endif + } +} + +static background_callback_t usb_callback; +static void usb_background_do(void *unused) { + usb_background(); +} + +void usb_background_schedule(void) { + background_callback_add(&usb_callback, usb_background_do, NULL); +} + +void usb_irq_handler(int instance) { + if (instance == CIRCUITPY_USB_DEVICE_INSTANCE) { + tud_int_handler(instance); + } else if (instance == CIRCUITPY_USB_HOST_INSTANCE) { + #if CIRCUITPY_USB_HOST + tuh_int_handler(instance); + #endif + } + + usb_background_schedule(); +} + +// --------------------------------------------------------------------+ +// tinyusb callbacks +// --------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) { + #if CIRCUITPY_USB_MSC + usb_msc_mount(); + #endif +} + +// Invoked when device is unmounted +void tud_umount_cb(void) { + #if CIRCUITPY_USB_MSC + usb_msc_umount(); + #endif +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allows us to perform remote wakeup +// USB Specs: Within 7ms, device must draw an average current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) { +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) { +} + +// Invoked when cdc when line state changed e.g connected/disconnected +// Use to reset to DFU when disconnect with 1200 bps +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { + (void)itf; // interface ID, not used + + // DTR = false is counted as disconnected + if (!dtr) { + cdc_line_coding_t coding; + // Use whichever CDC is itf 0. + tud_cdc_get_line_coding(&coding); + + if (coding.bit_rate == 1200) { + reset_to_bootloader(); + } + } +} + +#if CIRCUITPY_USB_VENDOR +// --------------------------------------------------------------------+ +// WebUSB use vendor class +// --------------------------------------------------------------------+ + +bool tud_vendor_connected(void) { + return web_serial_connected; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bRequest) + { + case VENDOR_REQUEST_WEBUSB: + // match vendor request in BOS descriptor + // Get landing page url + return tud_control_xfer(rhport, request, (void *)&desc_webusb_url, desc_webusb_url.bLength); + + case VENDOR_REQUEST_MICROSOFT: + if (request->wIndex == 7) { + // Get Microsoft OS 2.0 compatible descriptor + // let's just hope the target architecture always has the same endianness + uint16_t total_len; + memcpy(&total_len, vendor_ms_os_20_descriptor() + 8, 2); + + return tud_control_xfer(rhport, request, (void *)vendor_ms_os_20_descriptor(), total_len); + } else { + return false; + } + + case 0x22: + // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to + // connect and disconnect. + web_serial_connected = (request->wValue != 0); + + // response with status OK + return tud_control_status(rhport, request); + + default: + // stall unknown request + return false; + } + + return true; +} +#endif // CIRCUITPY_USB_VENDOR + + +#if MICROPY_KBD_EXCEPTION + +/** + * Callback invoked when received an "wanted" char. + * @param itf Interface index (for multiple cdc interfaces) + * @param wanted_char The wanted char (set previously) + */ + +// Only called when console is enabled. +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { + // Workaround for using shared/runtime/interrupt_char.c + // Compare mp_interrupt_char with wanted_char and ignore if not matched + if (mp_interrupt_char == wanted_char) { + tud_cdc_n_read_flush(itf); // flush read fifo + mp_sched_keyboard_interrupt(); + } +} + +#endif diff --git a/circuitpython/supervisor/shared/usb/usb_desc.c b/circuitpython/supervisor/shared/usb/usb_desc.c new file mode 100644 index 0000000..9fe1ead --- /dev/null +++ b/circuitpython/supervisor/shared/usb/usb_desc.c @@ -0,0 +1,365 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 hathach 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 "lib/tinyusb/src/tusb.h" + +#include "py/objstr.h" +#include "py/runtime.h" +#include "supervisor/memory.h" +#include "supervisor/shared/safe_mode.h" +#include "supervisor/usb.h" + +#if CIRCUITPY_USB_CDC +#include "shared-bindings/usb_cdc/__init__.h" +#endif + +#if CIRCUITPY_USB_HID +#include "shared-bindings/usb_hid/__init__.h" +#endif + +#if CIRCUITPY_USB_MIDI +#include "shared-bindings/usb_midi/__init__.h" +#endif + +#if CIRCUITPY_USB_MSC && CIRCUITPY_STORAGE +#include "shared-bindings/storage/__init__.h" +#endif + +#include "shared-bindings/microcontroller/Processor.h" + + +// Table for collecting interface strings (interface names) as descriptor is built. +// We reuse the same table after collection, replacing the char string pointers with le16 string pointers. +#define MAX_INTERFACE_STRINGS 16 +// slot 0 is always the Language ID +typedef union { + const char *char_str; + const uint16_t *descriptor; +} interface_string_t; +static interface_string_t collected_interface_strings[MAX_INTERFACE_STRINGS]; + +static size_t collected_interface_strings_length; +static uint8_t current_interface_string; + +static supervisor_allocation *device_descriptor_allocation; +static supervisor_allocation *configuration_descriptor_allocation; +static supervisor_allocation *string_descriptors_allocation; + +static const char manufacturer_name[] = USB_MANUFACTURER; +static const char product_name[] = USB_PRODUCT; + +// Serial number string is UID length * 2 (2 nibbles per byte) + 1 byte for null termination. +static char serial_number_hex_string[COMMON_HAL_MCU_PROCESSOR_UID_LENGTH * 2 + 1]; + + +static const uint8_t device_descriptor_template[] = { + 0x12, // 0 bLength + 0x01, // 1 bDescriptorType (Device) + 0x00, 0x02, // 2,3 bcdUSB 2.00 + 0x00, // 4 bDeviceClass (Use class information in the Interface Descriptors) + 0x00, // 5 bDeviceSubClass + 0x00, // 6 bDeviceProtocol + 0x40, // 7 bMaxPacketSize0 64 + 0xFF, 0xFF, // 8,9 idVendor [SET AT RUNTIME: lo,hi] +#define DEVICE_VID_LO_INDEX (8) +#define DEVICE_VID_HI_INDEX (9) + 0xFF, 0xFF, // 10,11 idProduct [SET AT RUNTIME: lo,hi] +#define DEVICE_PID_LO_INDEX (10) +#define DEVICE_PID_HI_INDEX (11) + 0x00, 0x01, // 12,13 bcdDevice 2.00 + 0xFF, // 14 iManufacturer (String Index) [SET AT RUNTIME] +#define DEVICE_MANUFACTURER_STRING_INDEX (14) + 0xFF, // 15 iProduct (String Index) [SET AT RUNTIME] +#define DEVICE_PRODUCT_STRING_INDEX (15) + 0xFF, // 16 iSerialNumber (String Index) [SET AT RUNTIME] +#define DEVICE_SERIAL_NUMBER_STRING_INDEX (16) + 0x01, // 17 bNumConfigurations 1 +}; + +static const uint8_t configuration_descriptor_template[] = { + 0x09, // 0 bLength + 0x02, // 1 bDescriptorType (Configuration) + 0xFF, 0xFF, // 2,3 wTotalLength [SET AT RUNTIME: lo, hi] +#define CONFIG_TOTAL_LENGTH_LO_INDEX (2) +#define CONFIG_TOTAL_LENGTH_HI_INDEX (3) + 0xFF, // 4 bNumInterfaces [SET AT RUNTIME] +#define CONFIG_NUM_INTERFACES_INDEX (4) + 0x01, // 5 bConfigurationValue + 0x00, // 6 iConfiguration (String Index) + 0x80, // 7 bmAttributes + 0x32, // 8 bMaxPower 100mA +}; + +static void usb_build_device_descriptor(uint16_t vid, uint16_t pid) { + device_descriptor_allocation = + allocate_memory(align32_size(sizeof(device_descriptor_template)), + /*high_address*/ false, /*movable*/ false); + uint8_t *device_descriptor = (uint8_t *)device_descriptor_allocation->ptr; + memcpy(device_descriptor, device_descriptor_template, sizeof(device_descriptor_template)); + + device_descriptor[DEVICE_VID_LO_INDEX] = vid & 0xFF; + device_descriptor[DEVICE_VID_HI_INDEX] = vid >> 8; + device_descriptor[DEVICE_PID_LO_INDEX] = pid & 0xFF; + device_descriptor[DEVICE_PID_HI_INDEX] = pid >> 8; + + usb_add_interface_string(current_interface_string, manufacturer_name); + device_descriptor[DEVICE_MANUFACTURER_STRING_INDEX] = current_interface_string; + current_interface_string++; + + usb_add_interface_string(current_interface_string, product_name); + device_descriptor[DEVICE_PRODUCT_STRING_INDEX] = current_interface_string; + current_interface_string++; + + usb_add_interface_string(current_interface_string, serial_number_hex_string); + device_descriptor[DEVICE_SERIAL_NUMBER_STRING_INDEX] = current_interface_string; + current_interface_string++; +} + +static void usb_build_configuration_descriptor(void) { + size_t total_descriptor_length = sizeof(configuration_descriptor_template); + + // CDC should be first, for compatibility with Adafruit Windows 7 drivers. + // In the past, the order has been CDC, MSC, MIDI, HID, so preserve that order. + #if CIRCUITPY_USB_CDC + if (usb_cdc_console_enabled()) { + total_descriptor_length += usb_cdc_descriptor_length(); + } + if (usb_cdc_data_enabled()) { + total_descriptor_length += usb_cdc_descriptor_length(); + } + #endif + + #if CIRCUITPY_USB_MSC + if (storage_usb_enabled()) { + total_descriptor_length += storage_usb_descriptor_length(); + } + #endif + + #if CIRCUITPY_USB_HID + if (usb_hid_enabled()) { + total_descriptor_length += usb_hid_descriptor_length(); + } + #endif + + #if CIRCUITPY_USB_MIDI + if (usb_midi_enabled()) { + total_descriptor_length += usb_midi_descriptor_length(); + } + #endif + + #if CIRCUITPY_USB_VENDOR + if (usb_vendor_enabled()) { + total_descriptor_length += usb_vendor_descriptor_length(); + } + #endif + + + // Now we now how big the configuration descriptor will be, so we can allocate space for it. + configuration_descriptor_allocation = + allocate_memory(align32_size(total_descriptor_length), + /*high_address*/ false, /*movable*/ false); + uint8_t *configuration_descriptor = (uint8_t *)configuration_descriptor_allocation->ptr; + + // Copy the template, which is the first part of the descriptor, and fix up its length. + + memcpy(configuration_descriptor, configuration_descriptor_template, sizeof(configuration_descriptor_template)); + + configuration_descriptor[CONFIG_TOTAL_LENGTH_LO_INDEX] = total_descriptor_length & 0xFF; + configuration_descriptor[CONFIG_TOTAL_LENGTH_HI_INDEX] = (total_descriptor_length >> 8) & 0xFF; + + // Number interfaces and endpoints. + // Endpoint 0 is already used for USB control, + // so start with 1 for the current endpoint and for the number of in and out endpoints + // already in use. + + descriptor_counts_t descriptor_counts = { + .current_interface = 0, + .current_endpoint = 1, + .num_in_endpoints = 1, + .num_out_endpoints = 1, + }; + + uint8_t *descriptor_buf_remaining = configuration_descriptor + sizeof(configuration_descriptor_template); + + #if CIRCUITPY_USB_CDC + if (usb_cdc_console_enabled()) { + // Concatenate and fix up the CDC REPL descriptor. + descriptor_buf_remaining += usb_cdc_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string, true /*console*/); + + } + if (usb_cdc_data_enabled()) { + // Concatenate and fix up the CDC data descriptor. + descriptor_buf_remaining += usb_cdc_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string, false /*console*/); + } + #endif + + #if CIRCUITPY_USB_MSC + if (storage_usb_enabled()) { + // Concatenate and fix up the MSC descriptor. + descriptor_buf_remaining += storage_usb_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string); + } + #endif + + #if CIRCUITPY_USB_HID + if (usb_hid_enabled()) { + if (usb_hid_boot_device() > 0 && descriptor_counts.current_interface > 0) { + // Hosts using boot devices generally to expect them to be at interface zero, + // and will not work properly otherwise. + reset_into_safe_mode(USB_BOOT_DEVICE_NOT_INTERFACE_ZERO); + } + descriptor_buf_remaining += usb_hid_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string, + usb_hid_report_descriptor_length(), usb_hid_boot_device()); + } + #endif + + #if CIRCUITPY_USB_MIDI + if (usb_midi_enabled()) { + // Concatenate and fix up the MIDI descriptor. + descriptor_buf_remaining += usb_midi_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string); + } + #endif + + #if CIRCUITPY_USB_VENDOR + if (usb_vendor_enabled()) { + descriptor_buf_remaining += usb_vendor_add_descriptor( + descriptor_buf_remaining, &descriptor_counts, ¤t_interface_string); + } + #endif + + // Now we know how many interfaces have been used. + configuration_descriptor[CONFIG_NUM_INTERFACES_INDEX] = descriptor_counts.current_interface; + + // Did we run out of endpoints? + if (descriptor_counts.current_endpoint > USB_NUM_ENDPOINT_PAIRS || + descriptor_counts.num_in_endpoints > USB_NUM_IN_ENDPOINTS || + descriptor_counts.num_out_endpoints > USB_NUM_OUT_ENDPOINTS) { + reset_into_safe_mode(USB_TOO_MANY_ENDPOINTS); + } +} + +// str must not be on the heap. +void usb_add_interface_string(uint8_t interface_string_index, const char str[]) { + if (interface_string_index > MAX_INTERFACE_STRINGS) { + reset_into_safe_mode(USB_TOO_MANY_INTERFACE_NAMES); + } + + collected_interface_strings[interface_string_index].char_str = str; + collected_interface_strings_length += strlen(str); +} + +static const uint16_t language_id[] = { + 0x0304, + 0x0409, +}; + +static void usb_build_interface_string_table(void) { + // Allocate space for the le16 String descriptors. + // Space needed is 2 bytes for String Descriptor header, then 2 bytes for each character + string_descriptors_allocation = + allocate_memory(align32_size(current_interface_string * 2 + collected_interface_strings_length * 2), + /*high_address*/ false, /*movable*/ false); + uint16_t *string_descriptors = (uint16_t *)string_descriptors_allocation->ptr; + + + uint16_t *string_descriptor = string_descriptors; + + // Language ID is always the 0th string descriptor. + collected_interface_strings[0].descriptor = language_id; + + // Build the le16 versions of all the descriptor strings. + // Start at 1 to skip the Language ID. + for (uint8_t string_index = 1; string_index < current_interface_string; string_index++) { + const char *str = collected_interface_strings[string_index].char_str; + const size_t str_len = strlen(str); + // 1 word for descriptor type and length, 1 word for each character. + const uint8_t descriptor_size_words = 1 + str_len; + const uint8_t descriptor_size_bytes = descriptor_size_words * 2; + string_descriptor[0] = 0x0300 | descriptor_size_bytes; + + // Convert to le16. + for (size_t i = 0; i < str_len; i++) { + string_descriptor[i + 1] = str[i]; + } + + // Save ptr to string descriptor with le16 str. + collected_interface_strings[string_index].descriptor = string_descriptor; + + // Move to next descriptor slot. + string_descriptor += descriptor_size_words; + } +} + +// After boot.py runs, the USB devices to be used have been chosen, and the descriptors can be set up. +// This is called after the VM is finished, because it uses storage_allocations. +void usb_build_descriptors(void) { + uint8_t raw_id[COMMON_HAL_MCU_PROCESSOR_UID_LENGTH]; + common_hal_mcu_processor_get_uid(raw_id); + + for (int i = 0; i < COMMON_HAL_MCU_PROCESSOR_UID_LENGTH; i++) { + for (int j = 0; j < 2; j++) { + uint8_t nibble = (raw_id[i] >> (j * 4)) & 0xf; + serial_number_hex_string[i * 2 + (1 - j)] = nibble_to_hex_upper[nibble]; + } + } + + // Null-terminate the string. + serial_number_hex_string[sizeof(serial_number_hex_string) - 1] = '\0'; + + current_interface_string = 1; + collected_interface_strings_length = 0; + + usb_build_device_descriptor(USB_VID, USB_PID); + usb_build_configuration_descriptor(); + usb_build_interface_string_table(); +} + +// Invoked when GET DEVICE DESCRIPTOR is received. +// Application return pointer to descriptor +uint8_t const *tud_descriptor_device_cb(void) { + return (uint8_t *)device_descriptor_allocation->ptr; +} + +// Invoked when GET CONFIGURATION DESCRIPTOR is received. +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; // for multiple configurations + return (uint8_t *)configuration_descriptor_allocation->ptr; +} + +// Invoked when GET STRING DESCRIPTOR request is received. +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + if (index > MAX_INTERFACE_STRINGS) { + return NULL; + } + return collected_interface_strings[index].descriptor; +} diff --git a/circuitpython/supervisor/shared/usb/usb_msc_flash.c b/circuitpython/supervisor/shared/usb/usb_msc_flash.c new file mode 100644 index 0000000..f183033 --- /dev/null +++ b/circuitpython/supervisor/shared/usb/usb_msc_flash.c @@ -0,0 +1,287 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 hathach 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 "tusb.h" +// // #include "supervisor/flash.h" + +// For updating fatfs's cache +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/diskio.h" +#include "lib/oofatfs/ff.h" +#include "py/mpstate.h" + +#include "shared-module/storage/__init__.h" +#include "supervisor/filesystem.h" +#include "supervisor/shared/reload.h" + +#define MSC_FLASH_BLOCK_SIZE 512 + +static bool ejected[1] = {true}; + +// Lock to track if something else is using the filesystem when USB is plugged in. If so, the drive +// will be made available once the lock is released. +static bool _usb_msc_lock = false; +static bool _usb_connected_while_locked = false; + +STATIC void _usb_msc_uneject(void) { + for (uint8_t i = 0; i < sizeof(ejected); i++) { + ejected[i] = false; + } +} + +void usb_msc_mount(void) { + // Reset the ejection tracking every time we're plugged into USB. This allows for us to battery + // power the device, eject, unplug and plug it back in to get the drive. + if (_usb_msc_lock) { + _usb_connected_while_locked = true; + return; + } + _usb_msc_uneject(); + _usb_connected_while_locked = false; +} + +void usb_msc_umount(void) { +} + +bool usb_msc_ejected(void) { + bool all_ejected = true; + for (uint8_t i = 0; i < sizeof(ejected); i++) { + all_ejected &= ejected[i]; + } + return all_ejected; +} + +bool usb_msc_lock(void) { + if ((storage_usb_enabled() && !usb_msc_ejected()) || _usb_msc_lock) { + return false; + } + _usb_msc_lock = true; + return true; +} + +void usb_msc_unlock(void) { + if (!_usb_msc_lock) { + // Mismatched unlock. + return; + } + if (_usb_connected_while_locked) { + _usb_msc_uneject(); + } + _usb_msc_lock = false; +} + +// The root FS is always at the end of the list. +static fs_user_mount_t *get_vfs(int lun) { + // TODO(tannewt): Return the mount which matches the lun where 0 is the end + // and is counted in reverse. + if (lun > 0) { + return NULL; + } + mp_vfs_mount_t *current_mount = MP_STATE_VM(vfs_mount_table); + if (current_mount == NULL) { + return NULL; + } + while (current_mount->next != NULL) { + current_mount = current_mount->next; + } + return current_mount->obj; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 have their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, uint16_t bufsize) { + const void *response = NULL; + int32_t resplen = 0; + + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Host is about to read/write etc ... better not to disconnect disk + resplen = 0; + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + + // return len must not larger than bufsize + if (resplen > bufsize) { + resplen = bufsize; + } + + // copy response to stack's buffer if any + if (response && (resplen > 0)) { + memcpy(buffer, response, resplen); + } + + return resplen; +} + +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + fs_user_mount_t *vfs = get_vfs(lun); + disk_ioctl(vfs, GET_SECTOR_COUNT, block_count); + disk_ioctl(vfs, GET_SECTOR_SIZE, block_size); +} + +bool tud_msc_is_writable_cb(uint8_t lun) { + if (lun > 1) { + return false; + } + + fs_user_mount_t *vfs = get_vfs(lun); + if (vfs == NULL) { + return false; + } + if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL || !filesystem_is_writable_by_usb(vfs)) { + return false; + } + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + (void)offset; + + const uint32_t block_count = bufsize / MSC_FLASH_BLOCK_SIZE; + + fs_user_mount_t *vfs = get_vfs(lun); + disk_read(vfs, buffer, lba, block_count); + + return block_count * MSC_FLASH_BLOCK_SIZE; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + (void)lun; + (void)offset; + autoreload_suspend(AUTORELOAD_SUSPEND_USB); + + const uint32_t block_count = bufsize / MSC_FLASH_BLOCK_SIZE; + + fs_user_mount_t *vfs = get_vfs(lun); + disk_write(vfs, buffer, lba, block_count); + // Since by getting here we assume the mount is read-only to + // MicroPython let's update the cached FatFs sector if it's the one + // we just wrote. + #if FF_MAX_SS != FF_MIN_SS + if (vfs->ssize == MSC_FLASH_BLOCK_SIZE) { + #else + // The compiler can optimize this away. + if (FF_MAX_SS == FILESYSTEM_BLOCK_SIZE) { + #endif + if (lba == vfs->fatfs.winsect && lba > 0) { + memcpy(vfs->fatfs.win, + buffer + MSC_FLASH_BLOCK_SIZE * (vfs->fatfs.winsect - lba), + MSC_FLASH_BLOCK_SIZE); + } + } + + return block_count * MSC_FLASH_BLOCK_SIZE; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void tud_msc_write10_complete_cb(uint8_t lun) { + (void)lun; + + // This write is complete; initiate an autoreload. + autoreload_resume(AUTORELOAD_SUSPEND_USB); + autoreload_trigger(); +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + (void)lun; + + memcpy(vendor_id, CFG_TUD_MSC_VENDOR, strlen(CFG_TUD_MSC_VENDOR)); + memcpy(product_id, CFG_TUD_MSC_PRODUCT, strlen(CFG_TUD_MSC_PRODUCT)); + memcpy(product_rev, CFG_TUD_MSC_PRODUCT_REV, strlen(CFG_TUD_MSC_PRODUCT_REV)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (lun > 1) { + return false; + } + + fs_user_mount_t *current_mount = get_vfs(lun); + if (current_mount == NULL) { + return false; + } + if (ejected[lun]) { + // Set 0x3a for media not present. + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); + return false; + } + + return true; +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + if (lun > 1) { + return false; + } + fs_user_mount_t *current_mount = get_vfs(lun); + if (current_mount == NULL) { + return false; + } + if (load_eject) { + if (!start) { + // Eject but first flush. + if (disk_ioctl(current_mount, CTRL_SYNC, NULL) != RES_OK) { + return false; + } else { + ejected[lun] = true; + } + } else { + // We can only load if it hasn't been ejected. + return !ejected[lun]; + } + } else { + if (!start) { + // Stop the unit but don't eject. + if (disk_ioctl(current_mount, CTRL_SYNC, NULL) != RES_OK) { + return false; + } + } + // Always start the unit, even if ejected. Whether media is present is a separate check. + } + + return true; +} diff --git a/circuitpython/supervisor/shared/usb/usb_vendor_descriptors.h b/circuitpython/supervisor/shared/usb/usb_vendor_descriptors.h new file mode 100644 index 0000000..0b41c09 --- /dev/null +++ b/circuitpython/supervisor/shared/usb/usb_vendor_descriptors.h @@ -0,0 +1,39 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 USB_DESCRIPTORS_H_ +#define USB_DESCRIPTORS_H_ + +#include <stdint.h> + +enum +{ + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2 +}; + +size_t vendor_ms_os_20_descriptor_length(void); +uint8_t const *vendor_ms_os_20_descriptor(void); + +#endif /* USB_DESCRIPTORS_H_ */ diff --git a/circuitpython/supervisor/shared/workflow.c b/circuitpython/supervisor/shared/workflow.c new file mode 100644 index 0000000..8d2c0f7 --- /dev/null +++ b/circuitpython/supervisor/shared/workflow.c @@ -0,0 +1,60 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 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 <stdbool.h> +#include "py/mpconfig.h" +#include "supervisor/workflow.h" +#include "supervisor/shared/workflow.h" + +#if CIRCUITPY_USB +#include "tusb.h" +#endif + +void supervisor_workflow_reset(void) { +} + +// Return true as soon as USB communication with host has started, +// even before enumeration is done. +// Not that some chips don't notice when USB is unplugged after first being plugged in, +// so this is not perfect, but tud_suspended() check helps. +bool supervisor_workflow_connecting(void) { + #if CIRCUITPY_USB + return tud_connected() && !tud_suspended(); + #else + return false; + #endif +} + +// Return true if host has completed connection to us (such as USB enumeration). +bool supervisor_workflow_active(void) { + #if CIRCUITPY_USB + // Eventually there might be other non-USB workflows, such as BLE. + // tud_ready() checks for usb mounted and not suspended. + return tud_ready(); + #else + return false; + #endif +} diff --git a/circuitpython/supervisor/shared/workflow.h b/circuitpython/supervisor/shared/workflow.h new file mode 100644 index 0000000..b3c817f --- /dev/null +++ b/circuitpython/supervisor/shared/workflow.h @@ -0,0 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 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. + */ + +#pragma once + +extern bool supervisor_workflow_connecting(void); diff --git a/circuitpython/supervisor/spi_flash_api.h b/circuitpython/supervisor/spi_flash_api.h new file mode 100644 index 0000000..1af8373 --- /dev/null +++ b/circuitpython/supervisor/spi_flash_api.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC + * + * 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_SUPERVISOR_SPI_FLASH_H +#define MICROPY_INCLUDED_SUPERVISOR_SPI_FLASH_H + +#include <stdbool.h> +#include <stdint.h> + +#include "shared/external_flash/device.h" + +#include "shared-bindings/busio/SPI.h" + +extern busio_spi_obj_t supervisor_flash_spi_bus; // Used to share SPI bus on some boards + +// This API is implemented for both normal SPI peripherals and QSPI peripherals. + +bool spi_flash_command(uint8_t command); +bool spi_flash_read_command(uint8_t command, uint8_t *response, uint32_t length); +bool spi_flash_write_command(uint8_t command, uint8_t *data, uint32_t length); +bool spi_flash_sector_command(uint8_t command, uint32_t address); +bool spi_flash_write_data(uint32_t address, uint8_t *data, uint32_t data_length); +bool spi_flash_read_data(uint32_t address, uint8_t *data, uint32_t data_length); +void spi_flash_init(void); +void spi_flash_init_device(const external_flash_device *device); + +#endif // MICROPY_INCLUDED_SUPERVISOR_SPI_FLASH_H diff --git a/circuitpython/supervisor/stub/filesystem.c b/circuitpython/supervisor/stub/filesystem.c new file mode 100644 index 0000000..920a757 --- /dev/null +++ b/circuitpython/supervisor/stub/filesystem.c @@ -0,0 +1,80 @@ +/* + * 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 "supervisor/filesystem.h" + + +void filesystem_background(void) { + return; +} + +void filesystem_tick(void) { + return; +} + +bool filesystem_init(bool create_allowed, bool force_create) { + (void)create_allowed; + (void)force_create; + return true; +} + +void filesystem_flush(void) { +} + +void filesystem_set_internal_writable_by_usb(bool writable) { + (void)writable; + return; +} + +void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable) { + (void)vfs; + (void)usb_writable; + return; +} + +bool filesystem_is_writable_by_python(fs_user_mount_t *vfs) { + (void)vfs; + return true; +} + +bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs) { + return true; +} + +void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_protection) { + (void)concurrent_write_protection; + return; +} + +void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection) { + (void)vfs; + (void)concurrent_write_protection; + return; +} + +bool filesystem_present(void) { + return false; +} diff --git a/circuitpython/supervisor/stub/internal_flash.c b/circuitpython/supervisor/stub/internal_flash.c new file mode 100644 index 0000000..3bb7149 --- /dev/null +++ b/circuitpython/supervisor/stub/internal_flash.c @@ -0,0 +1,74 @@ +/* + * 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 "supervisor/flash.h" + +#include <stdint.h> +#include <string.h> + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "py/mphal.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "lib/oofatfs/ff.h" + +void supervisor_flash_init(void) { +} + +uint32_t supervisor_flash_get_block_size(void) { + return 0; +} + +uint32_t supervisor_flash_get_block_count(void) { + return 0; +} + +mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) { + return 0; // success +} + +mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + return 0; // success +} + +#if (0) +// See definition in supervisor/flash.c +void supervisor_flash_init_vfs(struct _fs_user_mount_t *vfs) { + return; +} + +// See definition in supervisor/flash.c +void supervisor_flash_flush(void) { + return; +} +#endif + +void supervisor_flash_release_cache(void) { +} + +void port_internal_flash_flush(void) { + return; +} diff --git a/circuitpython/supervisor/stub/safe_mode.c b/circuitpython/supervisor/stub/safe_mode.c new file mode 100644 index 0000000..b62cc05 --- /dev/null +++ b/circuitpython/supervisor/stub/safe_mode.c @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "supervisor/shared/safe_mode.h" + +#include <stdlib.h> + +safe_mode_t wait_for_safe_mode_reset(void) { + return NO_SAFE_MODE; +} + +void reset_into_safe_mode(safe_mode_t reason) { + (void)reason; + abort(); +} + +void print_safe_mode_message(safe_mode_t reason) { + (void)reason; +} diff --git a/circuitpython/supervisor/stub/stack.c b/circuitpython/supervisor/stub/stack.c new file mode 100644 index 0000000..2abc197 --- /dev/null +++ b/circuitpython/supervisor/stub/stack.c @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 "supervisor/shared/stack.h" + +bool stack_ok(void) { + return true; +} + +void assert_heap_ok(void) { +} + +void stack_init(void) { +} + +void stack_resize(void) { +} + +void set_next_stack_size(uint32_t size) { + (void)size; +} + +uint32_t get_current_stack_size(void) { + return 0; +} diff --git a/circuitpython/supervisor/supervisor.mk b/circuitpython/supervisor/supervisor.mk new file mode 100644 index 0000000..f4ca11d --- /dev/null +++ b/circuitpython/supervisor/supervisor.mk @@ -0,0 +1,203 @@ +SRC_SUPERVISOR = \ + main.c \ + supervisor/port.c \ + supervisor/shared/background_callback.c \ + supervisor/shared/board.c \ + supervisor/shared/cpu.c \ + supervisor/shared/flash.c \ + supervisor/shared/lock.c \ + supervisor/shared/memory.c \ + supervisor/shared/micropython.c \ + supervisor/shared/reload.c \ + supervisor/shared/safe_mode.c \ + supervisor/shared/serial.c \ + supervisor/shared/stack.c \ + supervisor/shared/status_leds.c \ + supervisor/shared/tick.c \ + supervisor/shared/traceback.c \ + supervisor/shared/translate.c \ + supervisor/shared/workflow.c + +ifeq ($(DISABLE_FILESYSTEM),1) +SRC_SUPERVISOR += supervisor/stub/filesystem.c +else +SRC_SUPERVISOR += supervisor/shared/filesystem.c +endif + +NO_USB ?= $(wildcard supervisor/usb.c) + +INTERNAL_FLASH_FILESYSTEM ?= 0 +CFLAGS += -DINTERNAL_FLASH_FILESYSTEM=$(INTERNAL_FLASH_FILESYSTEM) + +QSPI_FLASH_FILESYSTEM ?= 0 +CFLAGS += -DQSPI_FLASH_FILESYSTEM=$(QSPI_FLASH_FILESYSTEM) + +SPI_FLASH_FILESYSTEM ?= 0 +CFLAGS += -DSPI_FLASH_FILESYSTEM=$(SPI_FLASH_FILESYSTEM) + +ifeq ($(CIRCUITPY_BLEIO),1) + SRC_SUPERVISOR += supervisor/shared/bluetooth/bluetooth.c + CIRCUITPY_CREATOR_ID ?= $(USB_VID) + CIRCUITPY_CREATION_ID ?= $(USB_PID) + CFLAGS += -DCIRCUITPY_CREATOR_ID=$(CIRCUITPY_CREATOR_ID) + CFLAGS += -DCIRCUITPY_CREATION_ID=$(CIRCUITPY_CREATION_ID) + ifeq ($(CIRCUITPY_BLE_FILE_SERVICE),1) + SRC_SUPERVISOR += supervisor/shared/bluetooth/file_transfer.c + endif + ifeq ($(CIRCUITPY_SERIAL_BLE),1) + SRC_SUPERVISOR += supervisor/shared/bluetooth/serial.c + endif +endif + +# Choose which flash filesystem impl to use. +# (Right now INTERNAL_FLASH_FILESYSTEM and (Q)SPI_FLASH_FILESYSTEM are mutually exclusive. +# But that might not be true in the future.) +ifeq ($(INTERNAL_FLASH_FILESYSTEM),1) + ifeq ($(DISABLE_FILESYSTEM),1) + SRC_SUPERVISOR += supervisor/stub/internal_flash.c + else + SRC_SUPERVISOR += supervisor/internal_flash.c + endif +else + CFLAGS += -DEXTERNAL_FLASH_DEVICES=$(EXTERNAL_FLASH_DEVICES) \ + + SRC_SUPERVISOR += supervisor/shared/external_flash/external_flash.c + ifeq ($(SPI_FLASH_FILESYSTEM),1) + SRC_SUPERVISOR += supervisor/shared/external_flash/spi_flash.c + endif + ifeq ($(QSPI_FLASH_FILESYSTEM),1) + SRC_SUPERVISOR += supervisor/qspi_flash.c supervisor/shared/external_flash/qspi_flash.c + endif + +$(HEADER_BUILD)/devices.h : ../../supervisor/shared/external_flash/devices.h.jinja ../../tools/gen_nvm_devices.py | $(HEADER_BUILD) + $(STEPECHO) "GEN $@" + $(Q)install -d $(BUILD)/genhdr + $(Q)$(PYTHON) ../../tools/gen_nvm_devices.py $< $@ + +$(BUILD)/supervisor/shared/external_flash/external_flash.o: $(HEADER_BUILD)/devices.h + +endif + +ifneq ($(wildcard supervisor/serial.c),) + SRC_SUPERVISOR += supervisor/serial.c +endif + +ifeq ($(CIRCUITPY_USB),1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/cdc/cdc_device.c \ + lib/tinyusb/src/common/tusb_fifo.c \ + lib/tinyusb/src/device/usbd.c \ + lib/tinyusb/src/device/usbd_control.c \ + lib/tinyusb/src/tusb.c \ + supervisor/usb.c \ + supervisor/shared/usb/usb_desc.c \ + supervisor/shared/usb/usb.c \ + + ifeq ($(CIRCUITPY_USB_CDC), 1) + SRC_SUPERVISOR += \ + shared-bindings/usb_cdc/__init__.c \ + shared-bindings/usb_cdc/Serial.c \ + shared-module/usb_cdc/__init__.c \ + shared-module/usb_cdc/Serial.c \ + + endif + + ifeq ($(CIRCUITPY_USB_HID), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/hid/hid_device.c \ + shared-bindings/usb_hid/__init__.c \ + shared-bindings/usb_hid/Device.c \ + shared-module/usb_hid/__init__.c \ + shared-module/usb_hid/Device.c \ + + endif + + ifeq ($(CIRCUITPY_USB_MIDI), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/midi/midi_device.c \ + shared-bindings/usb_midi/__init__.c \ + shared-bindings/usb_midi/PortIn.c \ + shared-bindings/usb_midi/PortOut.c \ + shared-module/usb_midi/__init__.c \ + shared-module/usb_midi/PortIn.c \ + shared-module/usb_midi/PortOut.c \ + + endif + + ifeq ($(CIRCUITPY_USB_MSC), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/msc/msc_device.c \ + supervisor/shared/usb/usb_msc_flash.c \ + + endif + + ifeq ($(CIRCUITPY_USB_VENDOR), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/vendor/vendor_device.c \ + + endif + + ifeq ($(CIRCUITPY_USB_HOST), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/host/hub.c \ + lib/tinyusb/src/host/usbh.c \ + lib/tinyusb/src/host/usbh_control.c \ + + endif +endif + +SRC_TINYUSB = $(filter lib/tinyusb/%.c, $(SRC_SUPERVISOR)) +$(patsubst %.c,$(BUILD)/%.o,$(SRC_TINYUSB)): CFLAGS += -Wno-missing-prototypes + +SUPERVISOR_O = $(addprefix $(BUILD)/, $(SRC_SUPERVISOR:.c=.o)) + +ifeq ($(CIRCUITPY_DISPLAYIO), 1) + SRC_SUPERVISOR += \ + supervisor/shared/display.c + + ifeq ($(CIRCUITPY_TERMINALIO), 1) + SUPERVISOR_O += $(BUILD)/autogen_display_resources.o + endif +endif + +# Preserve double quotes in these values by single-quoting them. + +USB_INTERFACE_NAME ?= "CircuitPython" +CFLAGS += -DUSB_INTERFACE_NAME='$(USB_INTERFACE_NAME)' + +ifneq ($(USB_VID),) +CFLAGS += -DUSB_VID=$(USB_VID) +CFLAGS += -DUSB_PID=$(USB_PID) +CFLAGS += -DUSB_MANUFACTURER='$(USB_MANUFACTURER)' +USB_MANUFACTURER_8 := "$(shell echo $(USB_MANUFACTURER) | cut -c 1-8)" +# Length-limited versions of strings for MSC names. +CFLAGS += -DUSB_MANUFACTURER_8='$(USB_MANUFACTURER_8)' +USB_PRODUCT_16 := "$(shell echo $(USB_PRODUCT) | cut -c 1-16)" +CFLAGS += -DUSB_PRODUCT_16='$(USB_PRODUCT_16)' +CFLAGS += -DUSB_PRODUCT='$(USB_PRODUCT)' + +endif + +# In the following URL, don't include the https:// prefix. +# It gets added automatically. +USB_WEBUSB_URL ?= "circuitpython.org" + +ifeq ($(CIRCUITPY_USB_CDC),1) +# Inform TinyUSB there will be up to two CDC devices. +CFLAGS += -DCFG_TUD_CDC=2 +endif + +USB_HIGHSPEED ?= 0 +CFLAGS += -DUSB_HIGHSPEED=$(USB_HIGHSPEED) + +$(BUILD)/supervisor/shared/translate.o: $(HEADER_BUILD)/qstrdefs.generated.h + +CIRCUITPY_DISPLAY_FONT ?= "../../tools/fonts/ter-u12n.bdf" + +$(BUILD)/autogen_display_resources.c: ../../tools/gen_display_resources.py $(HEADER_BUILD)/qstrdefs.generated.h Makefile | $(HEADER_BUILD) + $(STEPECHO) "GEN $@" + $(Q)install -d $(BUILD)/genhdr + $(Q)$(PYTHON) ../../tools/gen_display_resources.py \ + --font $(CIRCUITPY_DISPLAY_FONT) \ + --sample_file $(HEADER_BUILD)/qstrdefs.generated.h \ + --output_c_file $(BUILD)/autogen_display_resources.c diff --git a/circuitpython/supervisor/usb.h b/circuitpython/supervisor/usb.h new file mode 100644 index 0000000..420f423 --- /dev/null +++ b/circuitpython/supervisor/usb.h @@ -0,0 +1,88 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 hathach 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_SUPERVISOR_USB_H +#define MICROPY_INCLUDED_SUPERVISOR_USB_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +// Ports must call this as frequently as they can in order to keep the USB +// connection alive and responsive. Normally this is called from background +// tasks after the USB IRQ handler is executed, but in specific circumstances +// it may be necessary to call it directly. +void usb_background(void); + +// Schedule usb background +void usb_background_schedule(void); + +// Ports must call this from their particular USB IRQ handler +void usb_irq_handler(int instance); + +// Only inits the USB peripheral clocks and pins. The peripheral will be initialized by +// TinyUSB. +void init_usb_hardware(void); + +// Temporary hook for code after init. Only used for RP2040. +void post_usb_init(void); + +// Indexes and counts updated as descriptors are built. +typedef struct { + size_t current_interface; + size_t current_endpoint; + size_t num_in_endpoints; + size_t num_out_endpoints; +} descriptor_counts_t; + +// Shared implementation. +bool usb_enabled(void); +void usb_add_interface_string(uint8_t interface_string_index, const char str[]); +void usb_build_descriptors(void); +void usb_disconnect(void); +void usb_init(void); +void usb_set_defaults(void); +size_t usb_boot_py_data_size(void); +void usb_get_boot_py_data(uint8_t *temp_storage, size_t temp_storage_size); +void usb_return_boot_py_data(uint8_t *temp_storage, size_t temp_storage_size); + +// Further initialization that must be done with a VM present. +void usb_setup_with_vm(void); + + +// Propagate plug/unplug events to the MSC logic. +#if CIRCUITPY_USB_MSC +void usb_msc_mount(void); +void usb_msc_umount(void); +bool usb_msc_ejected(void); + +// Locking MSC prevents presenting the drive on plug-in when in use by something +// else (likely BLE.) +bool usb_msc_lock(void); +void usb_msc_unlock(void); +#endif + +#endif // MICROPY_INCLUDED_SUPERVISOR_USB_H diff --git a/circuitpython/supervisor/workflow.h b/circuitpython/supervisor/workflow.h new file mode 100755 index 0000000..3190039 --- /dev/null +++ b/circuitpython/supervisor/workflow.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 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. + */ + +#pragma once + +void supervisor_workflow_reset(void); + +// True when the user could be actively iterating on their code. +bool supervisor_workflow_active(void); |