From fe083ada7cd30e0794334118b483b9ab9c7a73d2 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 14 Sep 2026 13:08:40 -0700 Subject: [PATCH 1/9] supervisor: add port_gc_deinit(), called after the VM heap is torn down stop_mp() runs the heap finalisers in gc_deinit(). A port that keeps memory outside the VM heap which heap objects point into, such as native code copied into executable RAM, can only free it after that point. reset_port() runs before stop_mp(), which is too early. The weak default does nothing. Co-Authored-By: Claude Opus 5 (1M context) --- main.c | 1 + supervisor/port.h | 5 +++++ supervisor/shared/port.c | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/main.c b/main.c index c860e739275..8b1ebd4b9fe 100644 --- a/main.c +++ b/main.c @@ -249,6 +249,7 @@ static void stop_mp(void) { qstr_reset(); gc_deinit(); + port_gc_deinit(); port_free(_heap); _heap = NULL; diff --git a/supervisor/port.h b/supervisor/port.h index 2437edafb14..3d927318a1c 100644 --- a/supervisor/port.h +++ b/supervisor/port.h @@ -115,6 +115,11 @@ void port_boot_info(void); // A default weak implementation is provided that does nothing. void port_gc_collect(void); +// Called once the VM heap is torn down and its finalisers have run. Ports free +// memory here that heap objects may point into. +// A default weak implementation is provided that does nothing. +void port_gc_deinit(void); + // Most ports that implement CIRCUITPY_BOOT_BUTTON use a generic version of // this function to sense the button. Ports that need to can override this // function to provide their own implementation. diff --git a/supervisor/shared/port.c b/supervisor/shared/port.c index 15b0751c757..4e3e12891d1 100644 --- a/supervisor/shared/port.c +++ b/supervisor/shared/port.c @@ -119,6 +119,10 @@ MP_WEAK bool port_boot_button_pressed(void) { MP_WEAK void port_gc_collect(void) { } +// Ports may provide an implementation of this function if it is needed +MP_WEAK void port_gc_deinit(void) { +} + // Allocates an object in the port heap, not the VM heap, and also sets type, for mp_obj_malloc{,_var} macros. MP_NOINLINE void *mp_obj_port_malloc_helper(size_t num_bytes, const mp_obj_type_t *type) { mp_obj_base_t *base = (mp_obj_base_t *)port_malloc_zero(num_bytes, false); From 46a941a5a49cc498794e8cd295e7bb0cc42eccfc Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 7 Sep 2026 11:14:43 -0700 Subject: [PATCH 2/9] espressif: commit native machine code into executable RAM The default MP_PLAT_ALLOC_EXEC places native code on the GC heap, which on these chips is DRAM or PSRAM and cannot be fetched as instructions. Define MP_PLAT_COMMIT_EXEC, used by py/persistentcode.c for native .mpy files, to copy the finished code into a heap_caps_malloc(MALLOC_CAP_EXEC) block, applying the relocations against the final address first. Blocks are kept on a list and freed together from port_gc_deinit(), once the heap finalisers have run. Modelled on esp_native_code_commit() in MicroPython's ports/esp32/main.c, including the ESP32-S2 esp_ptr_executable() workaround for espressif/esp-idf#14835. The copy is word-wise because Xtensa executable RAM is not byte-addressable. With CONFIG_ESP_SYSTEM_MEMPROT=y, the current default, the heap component disables CONFIG_HEAP_HAS_EXEC_HEAP and MALLOC_CAP_EXEC allocations return NULL, so loading native code fails with MemoryError rather than a fault. The sdkconfig side is the next commit. Only compiled when MICROPY_PERSISTENT_CODE_LOAD_NATIVE is set. Co-Authored-By: Claude Fable 5.1 --- ports/espressif/mpconfigport.h | 6 ++++ ports/espressif/supervisor/port.c | 57 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index f0106f26b2c..afcf4a9896a 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -74,3 +74,9 @@ extern portMUX_TYPE background_task_mutex; #ifndef CIRCUITPY_ESP32P4_SWAP_LSFS #define CIRCUITPY_ESP32P4_SWAP_LSFS (0) #endif + +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +// Loaded native code is copied out of the GC heap into executable RAM. +void *esp_native_code_commit(void *buf, size_t len, void *reloc); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) +#endif diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 34cfba32b7e..f0308975a1e 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -15,6 +15,9 @@ #include "supervisor/shared/serial.h" #include "py/mpprint.h" #include "py/runtime.h" +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +#include "py/persistentcode.h" +#endif #include "esp_mac.h" #include "freertos/FreeRTOS.h" @@ -87,6 +90,10 @@ #include "esp_rom_efuse.h" #include "esp_timer.h" +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE && defined(CONFIG_IDF_TARGET_ESP32S2) +#include "esp_memory_utils.h" +#endif + #ifdef CONFIG_IDF_TARGET_ESP32 #include "hal/efuse_hal.h" #include "esp32/rom/efuse.h" @@ -347,6 +354,56 @@ size_t port_heap_get_largest_free_size(void) { return free_size; } +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +// Loaded native code, kept outside the GC heap until port_gc_deinit(). +typedef struct _native_code_node_t { + struct _native_code_node_t *next; + uint32_t data[]; +} native_code_node_t; + +static native_code_node_t *native_code_head = NULL; + +void port_gc_deinit(void) { + while (native_code_head != NULL) { + native_code_node_t *next = native_code_head->next; + heap_caps_free(native_code_head); + native_code_head = next; + } +} + +// Copy `len` bytes of machine code from `buf` into executable memory and return +// the executable address, applying the relocations in `reloc` (if any) against +// that address first. Raises MemoryError when no executable memory is available. +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { + len = (len + 3) & ~3; + size_t len_node = sizeof(native_code_node_t) + len; + native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if defined(CONFIG_IDF_TARGET_ESP32S2) + // The S2 can hand out MALLOC_CAP_EXEC memory that the CPU cannot fetch from. + if (node != NULL && !esp_ptr_executable(node)) { + heap_caps_free(node); + node = NULL; + } + #endif + if (node == NULL) { + m_malloc_fail(len_node); + } + node->next = native_code_head; + native_code_head = node; + void *p = node->data; + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)p); + } + // Word copy: Xtensa executable RAM is not byte-addressable. + const uint32_t *src = buf; + uint32_t *dst = p; + for (size_t i = 0; i < len / 4; i++) { + dst[i] = src[i]; + } + return p; +} +#endif + void reset_port_early(void) { // esp-camera adds an I2C device on the ESP I2C bus, and keeps it there. This // is unlike busio.I2C, which adds and removes the device on each operation. From f9d0c6b5f2af765974dbc196eaa3c7d77fa493f4 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 7 Sep 2026 11:14:43 -0700 Subject: [PATCH 3/9] espressif: turn off memory protection when native code is enabled Add esp-idf-config/sdkconfig-native.defaults with CONFIG_ESP_SYSTEM_MEMPROT=n and include it in SDKCONFIG_DEFAULTS only when CIRCUITPY_LOAD_NATIVE=1 or CIRCUITPY_ENABLE_MPY_NATIVE=1. Nothing changes for builds without either. On ESP-IDF 6 the PMS (S2, S3, C3) and PMP (C6, C5) memory protection marks RAM non-executable and sets CONFIG_HEAP_HAS_EXEC_HEAP=n, so MALLOC_CAP_EXEC allocations always fail and native code cannot run. Turning it off is a security trade-off: a write-anywhere bug becomes an execute-anywhere bug. It is opt-in per build for that reason. MicroPython ships every esp32 board with CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n (ports/esp32/boards/sdkconfig.base). check-sdkconfig.py refuses a build that has either flag on while CONFIG_ESP_SYSTEM_MEMPROT is still set, so a board sdkconfig cannot silently re-enable it and produce firmware that faults on import. tools/update_sdkconfig.py loads the same defaults file for boards with either flag, so regenerating a board sdkconfig does not copy the setting into it. Co-Authored-By: Claude Fable 5.1 --- ports/espressif/Makefile | 9 ++++++++- .../esp-idf-config/sdkconfig-native.defaults | 15 +++++++++++++++ ports/espressif/tools/check-sdkconfig.py | 11 +++++++++++ ports/espressif/tools/update_sdkconfig.py | 6 ++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 ports/espressif/esp-idf-config/sdkconfig-native.defaults diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index b772e384410..881bfbf45e6 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -872,7 +872,12 @@ ifneq ($(CIRCUITPY_BLEIO_NATIVE),0) BLE_SDKCONFIG := ;esp-idf-config/sdkconfig-ble.defaults endif -SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig +# Loaded native code executes from RAM, which memory protection forbids. +ifneq (,$(filter 1,$(CIRCUITPY_ENABLE_MPY_NATIVE) $(CIRCUITPY_LOAD_NATIVE))) + NATIVE_SDKCONFIG := ;esp-idf-config/sdkconfig-native.defaults +endif + +SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG)$(NATIVE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig # create the config headers .PHONY: do-sdkconfig @@ -884,6 +889,8 @@ $(BUILD)/esp-idf/config/sdkconfig.h: boards/$(BOARD)/sdkconfig boards/$(BOARD)/m $(Q)$(PYTHON) tools/check-sdkconfig.py \ CIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK) \ CIRCUITPY_STORAGE_EXTEND=$(CIRCUITPY_STORAGE_EXTEND) \ + CIRCUITPY_ENABLE_MPY_NATIVE=$(CIRCUITPY_ENABLE_MPY_NATIVE) \ + CIRCUITPY_LOAD_NATIVE=$(CIRCUITPY_LOAD_NATIVE) \ $@ # build a lib diff --git a/ports/espressif/esp-idf-config/sdkconfig-native.defaults b/ports/espressif/esp-idf-config/sdkconfig-native.defaults new file mode 100644 index 00000000000..c3eed046a61 --- /dev/null +++ b/ports/espressif/esp-idf-config/sdkconfig-native.defaults @@ -0,0 +1,15 @@ +# +# Espressif IoT Development Framework Configuration +# +# +# Component config +# +# +# ESP System Settings +# +# CONFIG_ESP_SYSTEM_MEMPROT is not set +# end of ESP System Settings + +# end of Component config + +# end of Espressif IoT Development Framework Configuration diff --git a/ports/espressif/tools/check-sdkconfig.py b/ports/espressif/tools/check-sdkconfig.py index 12254a71d01..f16dc3c871a 100755 --- a/ports/espressif/tools/check-sdkconfig.py +++ b/ports/espressif/tools/check-sdkconfig.py @@ -34,6 +34,17 @@ def validate(sdk_config, circuitpy_config): f"{var} is incompatible with {partition_table=} (no ota_1 partition)" ) + # Native machine code executes from RAM; the PMS/PMP memory protection + # forbids that and makes MALLOC_CAP_EXEC allocations fail. + if ( + circuitpy_config.get("CIRCUITPY_ENABLE_MPY_NATIVE") + or circuitpy_config.get("CIRCUITPY_LOAD_NATIVE") + ) and sdk_config.get("CONFIG_ESP_SYSTEM_MEMPROT"): + raise SystemExit( + "CIRCUITPY_ENABLE_MPY_NATIVE=1 / CIRCUITPY_LOAD_NATIVE=1 require CONFIG_ESP_SYSTEM_MEMPROT=n " + "(see esp-idf-config/sdkconfig-native.defaults)" + ) + # Add more checks here for other things we want to verify. return diff --git a/ports/espressif/tools/update_sdkconfig.py b/ports/espressif/tools/update_sdkconfig.py index d46514873bd..70cc4008df7 100644 --- a/ports/espressif/tools/update_sdkconfig.py +++ b/ports/espressif/tools/update_sdkconfig.py @@ -164,6 +164,7 @@ def update(debug, board, update_all): # noqa: C901 too complex psram_size = "0" uf2_bootloader = None ble_enabled = None + native_enabled = False for line in board_make.read_text().split("\n"): if "=" not in line or line.startswith("#"): continue @@ -192,6 +193,8 @@ def update(debug, board, update_all): # noqa: C901 too complex uf2_bootloader = not (value == "0") elif key == "CIRCUITPY_BLEIO_NATIVE": ble_enabled = not (value == "0") + elif key in ("CIRCUITPY_LOAD_NATIVE", "CIRCUITPY_ENABLE_MPY_NATIVE"): + native_enabled = native_enabled or value == "1" os.environ["IDF_TARGET"] = target os.environ["COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE"] = ( @@ -239,6 +242,9 @@ def update(debug, board, update_all): # noqa: C901 too complex if ble_enabled: ble_config = pathlib.Path("esp-idf-config/sdkconfig-ble.defaults") sdkconfigs.append(ble_config) + if native_enabled: + native_config = pathlib.Path("esp-idf-config/sdkconfig-native.defaults") + sdkconfigs.append(native_config) board_config = pathlib.Path(f"boards/{board}/sdkconfig") # Don't include the board file in cp defaults. The board may have custom # overrides. From 0f967225baf02254f7b67ec62974b5a1d49a65ca Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 7 Sep 2026 11:14:43 -0700 Subject: [PATCH 4/9] py/mpconfig: separate the native prelude on windowed Xtensa when only loading MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE is keyed on MICROPY_EMIT_XTENSAWIN alone. A build that loads native .mpy without the emitter on the ESP32-S2/S3 left it at 0, so persistentcode.c read the prelude of every loaded @micropython.native function byte-wise out of IRAM. Windowed Xtensa cannot do that: the first call of a native (non-viper) function took a LoadStoreError and the board reset. Viper functions carry no prelude, which is why viper alone passed. Key the define on the compiler target as well. Upstream has the same expression and does not hit this because its esp32 port always builds the emitter. Co-Authored-By: Claude Fable 5.1 --- py/mpconfig.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/py/mpconfig.h b/py/mpconfig.h index c630adaee1a..39a8681d73a 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -556,7 +556,12 @@ typedef uint64_t mp_uint_t; // Some architectures cannot read byte-wise from executable memory. In this case // the prelude for a native function (which usually sits after the machine code) // must be separated and placed somewhere where it can be read byte-wise. +// CIRCUITPY-CHANGE: also when only loading native code on windowed Xtensa. +#if defined(__XTENSA_WINDOWED_ABI__) +#define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN || MICROPY_PERSISTENT_CODE_LOAD_NATIVE) +#else #define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN) +#endif // Convenience definition for whether any inline assembler emitter is enabled #define MICROPY_EMIT_INLINE_ASM (MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_INLINE_RV32) From 7217d48100b4e7e85d78efcefc8928273e1d31b3 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 7 Sep 2026 11:14:43 -0700 Subject: [PATCH 5/9] espressif: load native .mpy on the Metro ESP32-S2 and ESP32-S3 Turn on CIRCUITPY_LOAD_NATIVE for the two Xtensa boards. Host-compiled xtensawin viper mandelbrot (160x120, 64 iterations): Metro ESP32-S3 172 ms, Metro ESP32-S2 206 ms, against 4,867 ms and 7,520 ms float bytecode. @micropython.viper from source raises SyntaxError and an armv7emsp .mpy raises ValueError. Co-Authored-By: Claude Fable 5.1 --- ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk | 2 ++ ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk index 20fa567f1bc..76959036b15 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk @@ -12,3 +12,5 @@ CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 2MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 80m + +CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk index 2fd5a40f259..f5b83576a28 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk @@ -12,3 +12,5 @@ CIRCUITPY_ESP_FLASH_SIZE = 16MB CIRCUITPY_ESP_PSRAM_MODE = opi CIRCUITPY_ESP_PSRAM_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 8MB + +CIRCUITPY_LOAD_NATIVE = 1 From 178dd6446e50f8fe53b1dfc96c8e650718d8638e Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 7 Sep 2026 14:17:56 -0700 Subject: [PATCH 6/9] espressif: load native .mpy on the ESP32-C5-DevKitC-1-N8R8 Turn on CIRCUITPY_LOAD_NATIVE for the RISC-V devkit. Host-compiled rv32imc viper mandelbrot (160x120, 64 iterations): 159 ms against 7,535 ms float bytecode and 3,569 ms fixed-point bytecode. @micropython.viper from source raises SyntaxError and an armv7emsp .mpy raises ValueError. Same code path as the Xtensa boards, no port change needed. Co-Authored-By: Claude Fable 5.1 --- .../boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk index 0da008596e3..2f6d03d9f17 100644 --- a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk @@ -10,3 +10,5 @@ CIRCUITPY_ESP_FLASH_SIZE = 8MB CIRCUITPY_ESP_PSRAM_SIZE = 8MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 40m + +CIRCUITPY_LOAD_NATIVE = 1 From 27f85daa4b81193b87917155024367a828574842 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 14 Sep 2026 12:33:05 -0700 Subject: [PATCH 7/9] espressif: load native .mpy on the QT Py ESP32-C3 and Feather ESP32-C6 Turn on CIRCUITPY_LOAD_NATIVE for the two Adafruit RISC-V boards. Same rv32imc loader path as the ESP32-C5-DevKitC-1-N8R8. Both build; neither was run on hardware. Co-Authored-By: Claude Opus 5 (1M context) --- .../adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk | 2 ++ ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk index 28821ed7b48..ca4cdabf875 100644 --- a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk @@ -9,3 +9,5 @@ CIRCUITPY_ESP_FLASH_SIZE = 4MB # Board was originally defined with a 2MB firmware, almost 2MB user filesystem. Leave it that way. CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT = 1 + +CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk b/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk index a17418eff19..1ff7e4e205e 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk @@ -12,3 +12,5 @@ CIRCUITPY_ESP_USB_SERIAL_JTAG = 1 # Not enough pins. CIRCUITPY_PARALLELDISPLAYBUS = 0 CIRCUITPY_RGBMATRIX = 0 + +CIRCUITPY_LOAD_NATIVE = 1 From 56a8dac1d1630c1600ccdd0d2d97e9eeec1c6747 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 14 Sep 2026 19:58:13 -0700 Subject: [PATCH 8/9] espressif: load native .mpy by default Turn CIRCUITPY_LOAD_NATIVE on in mpconfigport.mk for every espressif chip and drop the five per-board lines. Boards without the flash space opt out in their own mpconfigboard.mk. Co-Authored-By: Claude Opus 5 (1M context) --- .../adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk | 2 -- ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk | 2 -- ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk | 2 -- ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk | 2 -- .../boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk | 2 -- ports/espressif/mpconfigport.mk | 3 +++ 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk index ca4cdabf875..28821ed7b48 100644 --- a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk @@ -9,5 +9,3 @@ CIRCUITPY_ESP_FLASH_SIZE = 4MB # Board was originally defined with a 2MB firmware, almost 2MB user filesystem. Leave it that way. CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT = 1 - -CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk index 76959036b15..20fa567f1bc 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32s2/mpconfigboard.mk @@ -12,5 +12,3 @@ CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 2MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 80m - -CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk index f5b83576a28..2fd5a40f259 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk @@ -12,5 +12,3 @@ CIRCUITPY_ESP_FLASH_SIZE = 16MB CIRCUITPY_ESP_PSRAM_MODE = opi CIRCUITPY_ESP_PSRAM_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 8MB - -CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk b/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk index 1ff7e4e205e..a17418eff19 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_qtpy_esp32c3/mpconfigboard.mk @@ -12,5 +12,3 @@ CIRCUITPY_ESP_USB_SERIAL_JTAG = 1 # Not enough pins. CIRCUITPY_PARALLELDISPLAYBUS = 0 CIRCUITPY_RGBMATRIX = 0 - -CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk index 2f6d03d9f17..0da008596e3 100644 --- a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk @@ -10,5 +10,3 @@ CIRCUITPY_ESP_FLASH_SIZE = 8MB CIRCUITPY_ESP_PSRAM_SIZE = 8MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 40m - -CIRCUITPY_LOAD_NATIVE = 1 diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index ee28bba9918..cce516bcc7e 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -395,6 +395,9 @@ CIRCUITPY_AUDIOIO = 0 endif #### end chip-specific choices ######################################## +# By default, load native .mpy files. Boards without the flash space turn it off. +CIRCUITPY_LOAD_NATIVE ?= 1 + # By default, enable dualbank, and it'll be disabled for small flash sizes CIRCUITPY_DUALBANK ?= 1 From 540f61415a9c6592d89debd314d4d331fe7b65a8 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Mon, 14 Sep 2026 19:58:13 -0700 Subject: [PATCH 9/9] espressif: update_sdkconfig.py follows the loader default The loader is now on by default in mpconfigport.mk, so a board file no longer names CIRCUITPY_LOAD_NATIVE. Default it to on here too and let a board's "= 0" turn it off, so the native sdkconfig defaults stay out of the board sdkconfig files. CIRCUITPY_ENABLE_MPY_NATIVE = 1 still counts on its own. Co-Authored-By: Claude Opus 5 (1M context) --- ports/espressif/tools/update_sdkconfig.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ports/espressif/tools/update_sdkconfig.py b/ports/espressif/tools/update_sdkconfig.py index 70cc4008df7..f262a00e58b 100644 --- a/ports/espressif/tools/update_sdkconfig.py +++ b/ports/espressif/tools/update_sdkconfig.py @@ -164,7 +164,8 @@ def update(debug, board, update_all): # noqa: C901 too complex psram_size = "0" uf2_bootloader = None ble_enabled = None - native_enabled = False + load_native = True # Matches the mpconfigport.mk default. + mpy_native = False for line in board_make.read_text().split("\n"): if "=" not in line or line.startswith("#"): continue @@ -193,8 +194,10 @@ def update(debug, board, update_all): # noqa: C901 too complex uf2_bootloader = not (value == "0") elif key == "CIRCUITPY_BLEIO_NATIVE": ble_enabled = not (value == "0") - elif key in ("CIRCUITPY_LOAD_NATIVE", "CIRCUITPY_ENABLE_MPY_NATIVE"): - native_enabled = native_enabled or value == "1" + elif key == "CIRCUITPY_LOAD_NATIVE": + load_native = not (value == "0") + elif key == "CIRCUITPY_ENABLE_MPY_NATIVE": + mpy_native = value == "1" os.environ["IDF_TARGET"] = target os.environ["COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE"] = ( @@ -242,7 +245,7 @@ def update(debug, board, update_all): # noqa: C901 too complex if ble_enabled: ble_config = pathlib.Path("esp-idf-config/sdkconfig-ble.defaults") sdkconfigs.append(ble_config) - if native_enabled: + if load_native or mpy_native: native_config = pathlib.Path("esp-idf-config/sdkconfig-native.defaults") sdkconfigs.append(native_config) board_config = pathlib.Path(f"boards/{board}/sdkconfig")