From 44f38161919e0d838d43ccf9b71b79a4fca466c3 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 26 Jan 2026 17:07:33 -0800 Subject: [PATCH 01/14] Fixes for native sim --- .pre-commit-config.yaml | 2 +- main.c | 9 +- ports/zephyr-cp/Makefile | 8 +- ports/zephyr-cp/boards/board_aliases.cmake | 1 + .../native/native_sim/autogen_board_info.toml | 114 ++++++++++++++++++ .../native/native_sim/circuitpython.toml | 1 + ports/zephyr-cp/boards/native_sim.conf | 6 + ports/zephyr-cp/boards/native_sim.overlay | 39 ++++++ ports/zephyr-cp/common-hal/busio/UART.c | 2 +- .../common-hal/microcontroller/Processor.c | 4 + .../common-hal/zephyr_kernel/__init__.c | 1 + .../zephyr-cp/cptools/build_circuitpython.py | 7 +- ports/zephyr-cp/cptools/zephyr2cp.py | 47 +++++--- ports/zephyr-cp/supervisor/flash.c | 2 +- ports/zephyr-cp/supervisor/port.c | 12 +- shared/runtime/gchelper_generic.c | 8 +- supervisor/shared/background_callback.c | 2 + supervisor/shared/safe_mode.c | 12 ++ 18 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml create mode 100644 ports/zephyr-cp/boards/native/native_sim/circuitpython.toml create mode 100644 ports/zephyr-cp/boards/native_sim.conf create mode 100644 ports/zephyr-cp/boards/native_sim.overlay diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7e7956498d68..4db04e1d0eddb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: lib/mbedtls_errors/generate_errors.diff ) - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.4.1 hooks: - id: codespell args: [-w] diff --git a/main.c b/main.c index d636c851b700e..d187c56cee46d 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ #include "py/stackctrl.h" #include "shared/readline/readline.h" +#include "shared/runtime/gchelper.h" #include "shared/runtime/pyexec.h" #include "background.h" @@ -1161,13 +1162,7 @@ int __attribute__((used)) main(void) { void gc_collect(void) { gc_collect_start(); - // Load register values onto the stack. They get collected below with the rest of the stack. - size_t regs[SAVED_REGISTER_COUNT]; - mp_uint_t sp = cpu_get_regs_and_sp(regs); - - // This naively collects all object references from an approximate stack - // range. - gc_collect_root((void **)sp, ((mp_uint_t)port_stack_get_top() - sp) / sizeof(mp_uint_t)); + gc_helper_collect_regs_and_stack(); // This collects root pointers from the VFS mount table. Some of them may // have lost their references in the VM even though they are mounted. diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index 037c14afad4c3..d31068e4c92ea 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -8,7 +8,7 @@ BUILD ?= build-$(BOARD) TRANSLATION ?= en_US -.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash debug clean menuconfig all clean-all test +.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash debug run clean menuconfig all clean-all test $(BUILD)/zephyr-cp/zephyr/zephyr.elf: python cptools/pre_zephyr_build_prep.py $(BOARD) @@ -20,12 +20,18 @@ $(BUILD)/firmware.elf: $(BUILD)/zephyr-cp/zephyr/zephyr.elf $(BUILD)/firmware.hex: $(BUILD)/zephyr-cp/zephyr/zephyr.elf cp $(BUILD)/zephyr-cp/zephyr/zephyr.hex $@ +$(BUILD)/firmware.exe: $(BUILD)/zephyr-cp/zephyr/zephyr.elf + cp $(BUILD)/zephyr-cp/zephyr/zephyr.exe $@ + flash: $(BUILD)/zephyr-cp/zephyr/zephyr.elf west flash -d $(BUILD) debug: $(BUILD)/zephyr-cp/zephyr/zephyr.elf west debug -d $(BUILD) +run: $(BUILD)/firmware.exe + $^ + menuconfig: west build --sysbuild -d $(BUILD) -t menuconfig diff --git a/ports/zephyr-cp/boards/board_aliases.cmake b/ports/zephyr-cp/boards/board_aliases.cmake index 7fc973efc7852..954bce0b29823 100644 --- a/ports/zephyr-cp/boards/board_aliases.cmake +++ b/ports/zephyr-cp/boards/board_aliases.cmake @@ -1,6 +1,7 @@ set(pca10056_BOARD_ALIAS nrf52840dk/nrf52840) set(renesas_ek_ra6m5_BOARD_ALIAS ek_ra6m5) set(renesas_ek_ra8d1_BOARD_ALIAS ek_ra8d1) +set(native_native_sim_BOARD_ALIAS native_sim) set(nordic_nrf54l15dk_BOARD_ALIAS nrf54l15dk/nrf54l15/cpuapp) set(nordic_nrf54h20dk_BOARD_ALIAS nrf54h20dk/nrf54h20/cpuapp) set(nordic_nrf5340dk_BOARD_ALIAS nrf5340dk/nrf5340/cpuapp) diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml new file mode 100644 index 0000000000000..8a4eb3e6fb581 --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -0,0 +1,114 @@ +# This file is autogenerated when a board is built. Do not edit. Do commit it to git. Other scripts use its info. +name = "POSIX/Native Boards Native simulator - native_sim" + +[modules] +__future__ = false +_bleio = false +_eve = false +_pew = false +_pixelmap = false +_stage = false +adafruit_bus_device = false +adafruit_pixelbuf = false +aesio = false +alarm = false +analogbufio = false +analogio = false +atexit = false +audiobusio = false +audiocore = false +audiodelays = false +audiofilters = false +audiofreeverb = false +audioio = false +audiomixer = false +audiomp3 = false +audiopwmio = false +aurora_epaper = false +bitbangio = false +bitmapfilter = true # Zephyr board has busio +bitmaptools = true # Zephyr board has busio +bitops = false +board = false +busdisplay = true # Zephyr board has busio +busio = true # Zephyr board has busio +camera = false +canio = false +codeop = false +countio = false +digitalio = true +displayio = true # Zephyr board has busio +dotclockframebuffer = false +dualbank = false +epaperdisplay = true # Zephyr board has busio +floppyio = false +fontio = true # Zephyr board has busio +fourwire = true # Zephyr board has busio +framebufferio = true # Zephyr board has busio +frequencyio = false +getpass = false +gifio = false +gnss = false +hashlib = false +i2cdisplaybus = true # Zephyr board has busio +i2ctarget = false +imagecapture = false +ipaddress = false +is31fl3741 = false +jpegio = false +keypad = false +keypad_demux = false +locale = false +lvfontio = true # Zephyr board has busio +math = false +max3421e = false +mdns = false +memorymap = false +memorymonitor = false +microcontroller = true +mipidsi = false +msgpack = false +neopixel_write = false +nvm = false +onewireio = false +os = true +paralleldisplaybus = false +ps2io = false +pulseio = false +pwmio = false +qrio = false +rainbowio = true +random = true +rclcpy = false +rgbmatrix = false +rotaryio = false +rtc = false +sdcardio = true # Zephyr board has busio +sdioio = false +sharpdisplay = true # Zephyr board has busio +socketpool = false +spitarget = false +ssl = false +storage = true # Zephyr board has flash +struct = true +supervisor = true +synthio = false +terminalio = true # Zephyr board has busio +tilepalettemapper = true # Zephyr board has busio +time = true +touchio = false +traceback = true +uheap = false +usb = false +usb_cdc = false +usb_hid = false +usb_host = false +usb_midi = false +usb_video = false +ustack = false +vectorio = true # Zephyr board has busio +warnings = true +watchdog = false +wifi = false +zephyr_kernel = false +zlib = false diff --git a/ports/zephyr-cp/boards/native/native_sim/circuitpython.toml b/ports/zephyr-cp/boards/native/native_sim/circuitpython.toml new file mode 100644 index 0000000000000..3272dd4c5f319 --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim/circuitpython.toml @@ -0,0 +1 @@ +CIRCUITPY_BUILD_EXTENSIONS = ["elf"] diff --git a/ports/zephyr-cp/boards/native_sim.conf b/ports/zephyr-cp/boards/native_sim.conf new file mode 100644 index 0000000000000..baf808b51427d --- /dev/null +++ b/ports/zephyr-cp/boards/native_sim.conf @@ -0,0 +1,6 @@ +CONFIG_EMUL=y +CONFIG_GPIO=y +CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME=n + +# So we can test safe mode +CONFIG_NATIVE_SIM_REBOOT=y diff --git a/ports/zephyr-cp/boards/native_sim.overlay b/ports/zephyr-cp/boards/native_sim.overlay new file mode 100644 index 0000000000000..6a13f58379ac4 --- /dev/null +++ b/ports/zephyr-cp/boards/native_sim.overlay @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Device tree overlay for CircuitPython on native_sim. + * Adds simulated SRAM region required by CircuitPython build system. + */ + +#include + +/ { + sram0: memory@20000000 { + device_type = "memory"; + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x20000000 DT_SIZE_M(1)>; + zephyr,memory-region = "SRAM"; + }; + + chosen { + zephyr,sram = &sram0; + /delete-property/ zephyr,flash; + /delete-property/ zephyr,code-partition; + }; +}; + +&flash0 { + /delete-node/ partitions; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + circuitpy_partition: partition@0 { + label = "circuitpy"; + reg = <0x00000000 DT_SIZE_K(2048)>; + }; + }; +}; + +#include "../app.overlay" diff --git a/ports/zephyr-cp/common-hal/busio/UART.c b/ports/zephyr-cp/common-hal/busio/UART.c index b5aaadb3bb51d..9940853da50a5 100644 --- a/ports/zephyr-cp/common-hal/busio/UART.c +++ b/ports/zephyr-cp/common-hal/busio/UART.c @@ -126,7 +126,7 @@ void common_hal_busio_uart_set_baudrate(busio_uart_obj_t *self, uint32_t baudrat struct uart_config config; uart_config_get(self->uart_device, &config); config.baudrate = baudrate; - uart_config_set(self->uart_device, &config); + uart_configure(self->uart_device, &config); } mp_float_t common_hal_busio_uart_get_timeout(busio_uart_obj_t *self) { diff --git a/ports/zephyr-cp/common-hal/microcontroller/Processor.c b/ports/zephyr-cp/common-hal/microcontroller/Processor.c index ddc8b97056d2b..9f512a686ec14 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/Processor.c +++ b/ports/zephyr-cp/common-hal/microcontroller/Processor.c @@ -21,7 +21,11 @@ float common_hal_mcu_processor_get_temperature(void) { extern uint32_t SystemCoreClock; uint32_t common_hal_mcu_processor_get_frequency(void) { + #ifdef __ARM__ return SystemCoreClock; + #else + return CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; + #endif } float common_hal_mcu_processor_get_voltage(void) { diff --git a/ports/zephyr-cp/common-hal/zephyr_kernel/__init__.c b/ports/zephyr-cp/common-hal/zephyr_kernel/__init__.c index 042f06b0ed217..d5e6fd080062a 100644 --- a/ports/zephyr-cp/common-hal/zephyr_kernel/__init__.c +++ b/ports/zephyr-cp/common-hal/zephyr_kernel/__init__.c @@ -6,6 +6,7 @@ #include "bindings/zephyr_kernel/__init__.h" #include "py/runtime.h" +#include #include diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index a5e4d72b902bb..83f80945f3e00 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -277,9 +277,10 @@ async def build_circuitpython(): enable_mpy_native = False full_build = True usb_host = False + zephyr_board = cmake_args["BOARD"] board = cmake_args["BOARD_ALIAS"] if not board: - board = cmake_args["BOARD"] + board = zephyr_board translation = cmake_args["TRANSLATION"] if not translation: translation = "en_US" @@ -319,7 +320,7 @@ async def build_circuitpython(): ) board_autogen_task = tg.create_task( - zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir) + zephyr_dts_to_cp_board(zephyr_board, portdir, builddir, zephyrbuilddir) ) board_info = board_autogen_task.result() mpconfigboard_fn = board_tools.find_mpconfigboard(portdir, board) @@ -353,6 +354,8 @@ async def build_circuitpython(): "shared/readline/readline.c", "shared/runtime/buffer_helper.c", "shared/runtime/context_manager_helpers.c", + "shared/runtime/gchelper_generic.c", + "shared/runtime/gchelper_native.c", "shared/runtime/pyexec.c", "shared/runtime/interrupt_char.c", "shared/runtime/stdout_helpers.c", diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index 77404ec2bec3f..0f09c21e83f70 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -16,6 +16,7 @@ MANUAL_COMPAT_TO_DRIVER = { "renesas_ra_nv_flash": "flash", + "soc_nv_flash": "flash", "nordic_nrf_uarte": "serial", "nordic_nrf_uart": "serial", "nordic_nrf_twim": "i2c", @@ -370,7 +371,7 @@ def find_ram_regions(device_tree): @cpbuild.run_in_thread -def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 +def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa: C901 board_dir = builddir / "board" # Auto generate board files from device tree. @@ -384,7 +385,14 @@ def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 zephyr_board_dir = pathlib.Path(runners["config"]["board_dir"]) board_yaml = zephyr_board_dir / "board.yml" board_yaml = yaml.safe_load(board_yaml.read_text()) - board_info["vendor_id"] = board_yaml["board"]["vendor"] + if "board" not in board_yaml and "boards" in board_yaml: + for board in board_yaml["boards"]: + if board["name"] == board_id: + board_yaml = board + break + else: + board_yaml = board_yaml["board"] + board_info["vendor_id"] = board_yaml["vendor"] vendor_index = zephyr_board_dir.parent / "index.rst" if vendor_index.exists(): vendor_index = vendor_index.read_text() @@ -393,9 +401,9 @@ def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 else: vendor_name = board_info["vendor_id"] board_info["vendor"] = vendor_name - soc_name = board_yaml["board"]["socs"][0]["name"] + soc_name = board_yaml["socs"][0]["name"] board_info["soc"] = soc_name - board_name = board_yaml["board"]["full_name"] + board_name = board_yaml["full_name"] board_info["name"] = board_name # board_id_yaml = zephyr_board_dir / (zephyr_board_dir.name + ".yaml") # board_id_yaml = yaml.safe_load(board_id_yaml.read_text()) @@ -540,15 +548,18 @@ def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 board_names[(ioport, num)].append("BUTTON") board_names[(ioport, num)].extend(node2alias[key]) - a, b = all_ioports[:2] - i = 0 - while a[i] == b[i]: - i += 1 - shared_prefix = a[:i] - for ioport in ioports: - if not ioport.startswith(shared_prefix): - shared_prefix = "" - break + if len(all_ioports) > 1: + a, b = all_ioports[:2] + i = 0 + while a[i] == b[i]: + i += 1 + shared_prefix = a[:i] + for ioport in ioports: + if not ioport.startswith(shared_prefix): + shared_prefix = "" + break + else: + shared_prefix = all_ioports[0] pin_defs = [] pin_declarations = ["#pragma once"] @@ -658,8 +669,14 @@ def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 device, start, end, size, path = ram max_size = max(max_size, size) # We always start at the end of a Zephyr linker section so we need the externs and &. - ram_externs.append(f"extern uint32_t {start};") - start = "&" + start + if board_id in ["native_sim"]: + ram_externs.append("// This is a native board so we provide all of RAM for our heaps.") + ram_externs.append(f"static uint32_t _{device}[{size // 4}]; // {path}") + start = f"(const uint32_t *) (_{device})" + end = f"(const uint32_t *)(_{device} + {size // 4})" + else: + ram_externs.append(f"extern uint32_t {start};") + start = "&" + start ram_list.append(f" {start}, {end}, // {path}") ram_list = "\n".join(ram_list) ram_externs = "\n".join(ram_externs) diff --git a/ports/zephyr-cp/supervisor/flash.c b/ports/zephyr-cp/supervisor/flash.c index 8daef5fcdaa0d..38f35a5235afa 100644 --- a/ports/zephyr-cp/supervisor/flash.c +++ b/ports/zephyr-cp/supervisor/flash.c @@ -21,7 +21,7 @@ #include #define CIRCUITPY_PARTITION circuitpy_partition -static struct flash_area *filesystem_area = NULL; +static const struct flash_area *filesystem_area = NULL; #if !FIXED_PARTITION_EXISTS(CIRCUITPY_PARTITION) static struct flash_area _dynamic_area; diff --git a/ports/zephyr-cp/supervisor/port.c b/ports/zephyr-cp/supervisor/port.c index 799e44800cf9a..7be2beb1d2085 100644 --- a/ports/zephyr-cp/supervisor/port.c +++ b/ports/zephyr-cp/supervisor/port.c @@ -7,6 +7,7 @@ #include "supervisor/port.h" #include "mpconfigboard.h" +#include "supervisor/shared/tick.h" #include #include @@ -66,6 +67,10 @@ void port_wake_main_task_from_isr(void) { void port_yield(void) { k_yield(); + // Make sure time advances in the simulator. + #if defined(CONFIG_ARCH_POSIX) + k_busy_wait(100); + #endif } void port_boot_info(void) { @@ -73,14 +78,14 @@ void port_boot_info(void) { // Get stack limit address uint32_t *port_stack_get_limit(void) { - return k_current_get()->stack_info.start; + return (uint32_t *)k_current_get()->stack_info.start; } // Get stack top address uint32_t *port_stack_get_top(void) { _thread_stack_info_t stack_info = k_current_get()->stack_info; - return stack_info.start + stack_info.size - stack_info.delta; + return (uint32_t *)(stack_info.start + stack_info.size - stack_info.delta); } // Save and retrieve a word from memory that is preserved over reset. Used for safe mode. @@ -181,7 +186,10 @@ size_t port_heap_get_largest_free_size(void) { void assert_post_action(const char *file, unsigned int line) { printk("Assertion failed at %s:%u\n", file, line); + // Check that this is arm + #if defined(__arm__) __asm__ ("bkpt"); + #endif while (1) { ; } diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c index 45b2e4f7d848a..40c5865ed1c68 100644 --- a/shared/runtime/gchelper_generic.c +++ b/shared/runtime/gchelper_generic.c @@ -74,10 +74,10 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { #elif defined(__i386__) static void gc_helper_get_regs(gc_helper_regs_t arr) { - register long ebx asm ("ebx"); - register long esi asm ("esi"); - register long edi asm ("edi"); - register long ebp asm ("ebp"); + register long ebx __asm__ ("ebx"); + register long esi __asm__ ("esi"); + register long edi __asm__ ("edi"); + register long ebp __asm__ ("ebp"); #ifdef __clang__ // TODO: // This is dirty workaround for Clang. It tries to get around diff --git a/supervisor/shared/background_callback.c b/supervisor/shared/background_callback.c index ffeb78bbb8371..02aa9ae84d561 100644 --- a/supervisor/shared/background_callback.c +++ b/supervisor/shared/background_callback.c @@ -61,6 +61,7 @@ static int background_prevention_count; void PLACE_IN_ITCM(background_callback_run_all)(void) { port_background_task(); if (!background_callback_pending()) { + port_yield(); return; } CALLBACK_CRITICAL_BEGIN; @@ -87,6 +88,7 @@ void PLACE_IN_ITCM(background_callback_run_all)(void) { } --background_prevention_count; CALLBACK_CRITICAL_END; + port_yield(); } void background_callback_prevent(void) { diff --git a/supervisor/shared/safe_mode.c b/supervisor/shared/safe_mode.c index 5f24618f7f39b..a3e4de235c7bd 100644 --- a/supervisor/shared/safe_mode.c +++ b/supervisor/shared/safe_mode.c @@ -18,6 +18,8 @@ #include "supervisor/shared/translate/translate.h" #include "supervisor/shared/tick.h" +#include + #define SAFE_MODE_DATA_GUARD 0xad0000af #define SAFE_MODE_DATA_GUARD_MASK 0xff0000ff @@ -78,6 +80,7 @@ safe_mode_t wait_for_safe_mode_reset(void) { boot_in_safe_mode = true; break; } + port_yield(); diff = supervisor_ticks_ms64() - start_ticks; } #if CIRCUITPY_STATUS_LED @@ -99,10 +102,19 @@ void PLACE_IN_ITCM(safe_mode_on_next_reset)(safe_mode_t reason) { // Don't inline this so it's easy to break on it from GDB. void __attribute__((noinline, )) PLACE_IN_ITCM(reset_into_safe_mode)(safe_mode_t reason) { if (_safe_mode > SAFE_MODE_BROWNOUT && reason > SAFE_MODE_BROWNOUT) { + #if __ZEPHYR__ + printk("Already in safe mode\n"); + printk("Reason: %d\n", reason); + printk("Current safe mode: %d\n", _safe_mode); + while (true) { + k_cpu_idle(); + } + #else 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. } + #endif } safe_mode_on_next_reset(reason); From 069ad6aecffe6ae48b9bdc34ace41791d50a39d7 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 27 Jan 2026 16:19:40 -0800 Subject: [PATCH 02/14] Add basic native sim tests --- .github/workflows/run-tests.yml | 2 +- ports/zephyr-cp/CMakeLists.txt | 5 + ports/zephyr-cp/Makefile | 3 +- ports/zephyr-cp/boards/native_sim.conf | 14 + ports/zephyr-cp/boards/native_sim.overlay | 12 + ports/zephyr-cp/docs/perfetto-tracing.md | 150 +++++ ports/zephyr-cp/native_sim_i2c_emul_control.c | 169 +++++ ports/zephyr-cp/tests/TEST_IDEAS.md | 623 ++++++++++++++++++ ports/zephyr-cp/tests/conftest.py | 312 +++++++++ .../docs/i2c_emulator_cmdline_control.md | 306 +++++++++ ports/zephyr-cp/tests/test_basics.py | 199 ++++++ ports/zephyr-cp/tests/test_i2c.py | 98 +++ 12 files changed, 1891 insertions(+), 2 deletions(-) create mode 100644 ports/zephyr-cp/docs/perfetto-tracing.md create mode 100644 ports/zephyr-cp/native_sim_i2c_emul_control.c create mode 100644 ports/zephyr-cp/tests/TEST_IDEAS.md create mode 100644 ports/zephyr-cp/tests/conftest.py create mode 100644 ports/zephyr-cp/tests/docs/i2c_emulator_cmdline_control.md create mode 100644 ports/zephyr-cp/tests/test_basics.py create mode 100644 ports/zephyr-cp/tests/test_i2c.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 06fe3bf6aea29..ed0dd65a4f1d3 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -88,5 +88,5 @@ jobs: uses: ./.github/actions/deps/ports/zephyr-cp - name: Set up external uses: ./.github/actions/deps/external - - name: Run Zephyr build tests + - name: Run Zephyr tests run: make -C ports/zephyr-cp test diff --git a/ports/zephyr-cp/CMakeLists.txt b/ports/zephyr-cp/CMakeLists.txt index e35b4b7c764d2..0ba4a3c48b3de 100644 --- a/ports/zephyr-cp/CMakeLists.txt +++ b/ports/zephyr-cp/CMakeLists.txt @@ -5,6 +5,11 @@ project(circuitpython) target_sources(app PRIVATE zephyr_main.c) +# Add I2C emulator control for native_sim testing +if(CONFIG_BOARD_NATIVE_SIM) + target_sources(app PRIVATE native_sim_i2c_emul_control.c) +endif() + # From: https://github.com/zephyrproject-rtos/zephyr/blob/main/samples/application_development/external_lib/CMakeLists.txt # The external static library that we are linking with does not know # how to build for this platform so we export all the flags used in diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index d31068e4c92ea..15ae523fb72ca 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -46,5 +46,6 @@ all: clean-all: rm -rf build build-* -test: +test: build-native_native_sim/zephyr-cp/zephyr/zephyr.exe pytest cptools/tests + pytest tests/ -v diff --git a/ports/zephyr-cp/boards/native_sim.conf b/ports/zephyr-cp/boards/native_sim.conf index baf808b51427d..cc295949d0351 100644 --- a/ports/zephyr-cp/boards/native_sim.conf +++ b/ports/zephyr-cp/boards/native_sim.conf @@ -4,3 +4,17 @@ CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME=n # So we can test safe mode CONFIG_NATIVE_SIM_REBOOT=y + +CONFIG_TRACING=y +CONFIG_TRACING_PERFETTO=y +CONFIG_TRACING_SYNC=y +CONFIG_TRACING_BACKEND_POSIX=y +CONFIG_TRACING_GPIO=y + +# I2C emulation for testing +CONFIG_I2C_EMUL=y + +# EEPROM emulation for testing +CONFIG_EEPROM=y +CONFIG_EEPROM_AT24=y +CONFIG_EEPROM_AT2X_EMUL=y diff --git a/ports/zephyr-cp/boards/native_sim.overlay b/ports/zephyr-cp/boards/native_sim.overlay index 6a13f58379ac4..2a07108627fc9 100644 --- a/ports/zephyr-cp/boards/native_sim.overlay +++ b/ports/zephyr-cp/boards/native_sim.overlay @@ -36,4 +36,16 @@ }; }; +/* Add emulated I2C devices for testing */ +&i2c0 { + at24_eeprom: eeprom@50 { + compatible = "atmel,at24"; + reg = <0x50>; + size = <256>; + pagesize = <8>; + address-width = <8>; + timeout = <5>; + }; +}; + #include "../app.overlay" diff --git a/ports/zephyr-cp/docs/perfetto-tracing.md b/ports/zephyr-cp/docs/perfetto-tracing.md new file mode 100644 index 0000000000000..ff35d361f70bd --- /dev/null +++ b/ports/zephyr-cp/docs/perfetto-tracing.md @@ -0,0 +1,150 @@ +# Perfetto Tracing + +The Zephyr port supports Perfetto tracing for performance analysis. This document +describes how to capture, validate, and view traces. + +## Capturing Traces + +Traces are written to `circuitpython.perfetto-trace` in the port directory when +running with tracing enabled (e.g., on native_sim). + +## Validating Traces + +### Using trace_processor + +The Perfetto trace_processor tool can validate and query trace files: + +```bash +~/repos/perfetto/tools/trace_processor circuitpython.perfetto-trace +``` + +This will download the trace_processor binary if needed and open an interactive +SQL shell. If the trace loads successfully, you can query it: + +```sql +SELECT COUNT(*) FROM slice; +``` + +### Using the Perfetto UI + +Open https://ui.perfetto.dev and drag your trace file onto the page. + +## Debugging Invalid Traces + +### Common Error: Packets Skipped Due to Invalid Incremental State + +If trace_processor reports packets being skipped with messages like: + +``` +packet_skipped_seq_needs_incremental_state_invalid +``` + +This means packets have `SEQ_NEEDS_INCREMENTAL_STATE` (value 2) set but no +prior packet set `SEQ_INCREMENTAL_STATE_CLEARED` (value 1) to initialize the +incremental state. + +**Root Cause**: The process descriptor packet (which sets `SEQ_INCREMENTAL_STATE_CLEARED`) +must be emitted before any other trace packets. + +**Diagnosis**: Use protoc to inspect the raw trace: + +```bash +protoc --decode_raw < circuitpython.perfetto-trace | head -100 +``` + +Look for field 13 (sequence_flags) in the first few packets: + +- `13: 1` = SEQ_INCREMENTAL_STATE_CLEARED (good - should be first) +- `13: 2` = SEQ_NEEDS_INCREMENTAL_STATE (requires prior cleared packet) + +A valid trace should have the process descriptor with `13: 1` as one of the +first packets. + +**Fix**: Ensure `perfetto_start()` is called before any trace events are emitted. +The descriptor emit functions in `perfetto_encoder.c` should check: + +```c +if (!started) { + perfetto_start(); +} +``` + +### Analyzing Raw Trace Structure + +To understand the trace structure: + +```bash +# Count total packets +protoc --decode_raw < circuitpython.perfetto-trace | grep -c "^1 {" + +# Find all sequence_flags values +protoc --decode_raw < circuitpython.perfetto-trace | grep "13:" | sort | uniq -c + +# Look for track descriptors (field 60) +protoc --decode_raw < circuitpython.perfetto-trace | grep -A20 "60 {" + +# Look for process descriptors (field 3 inside track_descriptor) +protoc --decode_raw < circuitpython.perfetto-trace | grep -B5 "3 {" +``` + +### Key Protobuf Field Numbers + +TracePacket fields: + +| Field | Description | +|-------|-------------| +| 8 | timestamp | +| 10 | trusted_packet_sequence_id | +| 11 | track_event | +| 12 | interned_data | +| 13 | sequence_flags | +| 60 | track_descriptor | + +TrackDescriptor fields (inside field 60): + +| Field | Description | +|-------|-------------| +| 1 | uuid | +| 2 | name | +| 3 | process (ProcessDescriptor) | +| 4 | thread (ThreadDescriptor) | +| 5 | parent_uuid | + +## Build Verification + +After modifying tracing code, verify the build is updated: + +```bash +# Check source vs object file timestamps +ls -la zephyr/subsys/tracing/perfetto/perfetto_encoder.c +ls -la zephyr/build/zephyr/subsys/tracing/perfetto/CMakeFiles/subsys__tracing__perfetto.dir/perfetto_encoder.c.obj +``` + +The object file timestamp must be newer than the source file timestamp. If not, +rebuild the project before capturing a new trace. + +## Architecture + +The tracing implementation consists of: + +- `perfetto_encoder.c`: Encodes trace packets using nanopb +- `perfetto_top.c`: Implements Zephyr tracing hooks (sys_trace_*) +- `perfetto_encoder.h`: Public API and UUID definitions + +Key UUIDs: + +| Constant | Value | Description | +|----------|-------|-------------| +| PROCESS_UUID | 1 | Root process track | +| ISR_TRACK_UUID | 2 | Interrupt service routine track | +| TRACE_TRACK_UUID | 3 | Top-level trace track | + +### Initialization Flow + +1. `SYS_INIT` calls `perfetto_init()` at POST_KERNEL priority 0 +2. `perfetto_init()` calls `perfetto_encoder_init()` +3. `perfetto_initialized` is set to true +4. Thread hooks start firing +5. First emit function calls `perfetto_start()` +6. `perfetto_start()` emits process descriptor with `SEQ_INCREMENTAL_STATE_CLEARED` +7. Subsequent packets use `SEQ_NEEDS_INCREMENTAL_STATE` diff --git a/ports/zephyr-cp/native_sim_i2c_emul_control.c b/ports/zephyr-cp/native_sim_i2c_emul_control.c new file mode 100644 index 0000000000000..de467122a7bf5 --- /dev/null +++ b/ports/zephyr-cp/native_sim_i2c_emul_control.c @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Command-line control for enabling/disabling emulated I2C devices + * on native_sim. This allows testing device hot-plug and error scenarios. + */ + +#include +#include +#include +#include +#include +#include + +#include "nsi_cmdline.h" +#include "posix_native_task.h" + +LOG_MODULE_REGISTER(i2c_emul_control, LOG_LEVEL_INF); + +#define MAX_DISABLED_DEVICES 16 + +struct disabled_device { + const char *name; + const struct emul *emul; + struct i2c_emul_api mock_api; + bool disabled; +}; + +static struct disabled_device disabled_devices[MAX_DISABLED_DEVICES]; +static int num_disabled_devices = 0; + +static char *disabled_device_args[MAX_DISABLED_DEVICES]; +static int num_disabled_device_args = 0; + +/* + * Mock transfer function that returns -EIO (NACK) when device is disabled, + * or -ENOSYS to fall back to the real emulator. + */ +static int disabled_device_transfer(const struct emul *target, + struct i2c_msg *msgs, + int num_msgs, + int addr) { + ARG_UNUSED(msgs); + ARG_UNUSED(num_msgs); + ARG_UNUSED(addr); + + for (int i = 0; i < num_disabled_devices; i++) { + if (disabled_devices[i].emul == target) { + if (disabled_devices[i].disabled) { + LOG_DBG("Device %s is disabled, returning -EIO", + disabled_devices[i].name); + return -EIO; + } + break; + } + } + /* Fall back to normal emulator behavior */ + return -ENOSYS; +} + +int i2c_emul_control_disable_device(const char *name) { + const struct emul *emul = emul_get_binding(name); + if (!emul) { + LOG_ERR("Emulator '%s' not found", name); + return -ENODEV; + } + + if (emul->bus_type != EMUL_BUS_TYPE_I2C) { + LOG_ERR("Emulator '%s' is not an I2C device", name); + return -EINVAL; + } + + /* Find existing entry or create new one */ + int idx = -1; + for (int i = 0; i < num_disabled_devices; i++) { + if (disabled_devices[i].emul == emul) { + idx = i; + break; + } + } + + if (idx < 0) { + if (num_disabled_devices >= MAX_DISABLED_DEVICES) { + LOG_ERR("Too many disabled devices"); + return -ENOMEM; + } + idx = num_disabled_devices++; + disabled_devices[idx].name = name; + disabled_devices[idx].emul = emul; + disabled_devices[idx].mock_api.transfer = disabled_device_transfer; + + /* Install our mock_api to intercept transfers */ + emul->bus.i2c->mock_api = &disabled_devices[idx].mock_api; + } + + disabled_devices[idx].disabled = true; + LOG_INF("Disabled I2C emulator: %s", name); + return 0; +} + +int i2c_emul_control_enable_device(const char *name) { + for (int i = 0; i < num_disabled_devices; i++) { + if (strcmp(disabled_devices[i].name, name) == 0) { + disabled_devices[i].disabled = false; + LOG_INF("Enabled I2C emulator: %s", name); + return 0; + } + } + LOG_ERR("Device '%s' not in disabled list", name); + return -ENODEV; +} + +bool i2c_emul_control_is_disabled(const char *name) { + for (int i = 0; i < num_disabled_devices; i++) { + if (strcmp(disabled_devices[i].name, name) == 0) { + return disabled_devices[i].disabled; + } + } + return false; +} + +/* Command-line option handler */ +static void cmd_disable_i2c_device(char *argv, int offset) { + /* The value is at argv + offset (after the '=' in --disable-i2c=value) */ + char *value = argv + offset; + if (num_disabled_device_args < MAX_DISABLED_DEVICES) { + disabled_device_args[num_disabled_device_args++] = value; + } else { + printk("i2c_emul_control: Too many --disable-i2c arguments, ignoring: %s\n", value); + } +} + +static struct args_struct_t i2c_emul_args[] = { + { + .option = "disable-i2c", + .name = "device", + .type = 's', + .call_when_found = cmd_disable_i2c_device, + .descript = "Disable an emulated I2C device by name (can be repeated). " + "Example: --disable-i2c=bmi160" + }, + ARG_TABLE_ENDMARKER +}; + +static void register_cmdline_opts(void) { + nsi_add_command_line_opts(i2c_emul_args); +} + +/* Register command-line options early in boot */ +NATIVE_TASK(register_cmdline_opts, PRE_BOOT_1, 0); + +static int apply_disabled_devices(void) { + LOG_DBG("Applying %d disabled device(s)", num_disabled_device_args); + for (int i = 0; i < num_disabled_device_args; i++) { + int rc = i2c_emul_control_disable_device(disabled_device_args[i]); + if (rc != 0) { + LOG_WRN("Failed to disable I2C device '%s': %d", + disabled_device_args[i], rc); + } + } + return 0; +} + +/* + * Apply after emulators are initialized. + * I2C emulators are registered at POST_KERNEL level, so we need to run + * at APPLICATION level to ensure they exist. + */ +SYS_INIT(apply_disabled_devices, APPLICATION, 99); diff --git a/ports/zephyr-cp/tests/TEST_IDEAS.md b/ports/zephyr-cp/tests/TEST_IDEAS.md new file mode 100644 index 0000000000000..6ef71d7a5a041 --- /dev/null +++ b/ports/zephyr-cp/tests/TEST_IDEAS.md @@ -0,0 +1,623 @@ +# CircuitPython Simulator Test Ideas + +Test ideas for the native_sim simulator, organized by module/category. + +## Core Python / Interpreter + +### 1. Multiple file priority +Test that boot.py runs before code.py, and main.py is used as fallback when code.py doesn't exist. + +```python +# boot.py +print("boot.py ran") + +# code.py +print("code.py ran") +``` + +Expected output order: "boot.py ran" then "code.py ran" + +### 2. Exception handling +Verify tracebacks print correctly with file/line info, and exception types propagate properly. + +```python +def inner(): + raise ValueError("test error") + +def outer(): + inner() + +outer() +``` + +### 3. Memory / gc module +Test `gc.collect()`, `gc.mem_free()`, `gc.mem_alloc()`, and behavior under memory pressure. + +```python +import gc +gc.collect() +free_before = gc.mem_free() +data = [0] * 1000 +free_after = gc.mem_free() +assert free_after < free_before +del data +gc.collect() +free_final = gc.mem_free() +print("done") +``` + +### 4. Import system +Test importing frozen modules vs filesystem modules, verify import errors are clear. + +```python +import sys +import board +import digitalio +print(f"modules loaded: {len(sys.modules)}") +print("done") +``` + +--- + +## digitalio + +### 5. Input mode +Test reading GPIO input state. May require trace file injection or loopback configuration. + +```python +import board +import digitalio + +pin = digitalio.DigitalInOut(board.D0) +pin.direction = digitalio.Direction.INPUT +value = pin.value +print(f"input value: {value}") +print("done") +``` + +### 6. Pull resistors +Verify pull-up/pull-down configuration affects input readings. + +```python +import board +import digitalio + +pin = digitalio.DigitalInOut(board.D0) +pin.direction = digitalio.Direction.INPUT +pin.pull = digitalio.Pull.UP +value_up = pin.value +pin.pull = digitalio.Pull.DOWN +value_down = pin.value +print(f"pull-up: {value_up}, pull-down: {value_down}") +print("done") +``` + +### 7. Direction switching +Switch same pin between input/output modes multiple times. + +```python +import board +import digitalio + +pin = digitalio.DigitalInOut(board.D0) +pin.direction = digitalio.Direction.OUTPUT +pin.value = True +pin.direction = digitalio.Direction.INPUT +_ = pin.value +pin.direction = digitalio.Direction.OUTPUT +pin.value = False +print("done") +``` + +--- + +## time module + +### 8. time.sleep() precision +Verify sleep timing via Perfetto trace timestamps. Use GPIO transitions as timing markers. + +```python +import time +import board +import digitalio + +led = digitalio.DigitalInOut(board.LED) +led.direction = digitalio.Direction.OUTPUT + +led.value = True +time.sleep(0.05) # 50ms +led.value = False +time.sleep(0.1) # 100ms +led.value = True +time.sleep(0.05) # 50ms +led.value = False +print("done") +``` + +Verify trace shows: 50ms high, 100ms low, 50ms high pattern. + +### 9. time.monotonic() +Test monotonic clock increments correctly and never goes backward. + +```python +import time + +samples = [] +for _ in range(10): + samples.append(time.monotonic()) + time.sleep(0.01) + +# Verify monotonic increase +for i in range(1, len(samples)): + assert samples[i] > samples[i-1], "monotonic went backward!" + +elapsed = samples[-1] - samples[0] +assert 0.08 < elapsed < 0.15, f"unexpected elapsed: {elapsed}" +print("done") +``` + +### 10. time.localtime / struct_time +Test time structure operations if available. + +```python +import time + +t = time.localtime() +print(f"year: {t.tm_year}") +print(f"month: {t.tm_mon}") +print(f"day: {t.tm_mday}") +print("done") +``` + +--- + +## microcontroller + +### 11. microcontroller.cpu properties +Test frequency, temperature, UID properties. + +```python +import microcontroller + +print(f"frequency: {microcontroller.cpu.frequency}") +print(f"uid: {microcontroller.cpu.uid.hex()}") +print("done") +``` + +### 12. Safe mode +Trigger and verify safe mode entry (requires CONFIG_NATIVE_SIM_REBOOT=y). + +```python +import microcontroller +# This would trigger safe mode - test carefully +# microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE) +# microcontroller.reset() +print("done") +``` + +### 13. Reset reason +Test microcontroller.reset_reason after various reset types. + +```python +import microcontroller + +reason = microcontroller.reset_reason +print(f"reset reason: {reason}") +print("done") +``` + +--- + +## os module + +### 14. os.uname() +Verify returns correct system info for native_sim. + +```python +import os + +info = os.uname() +print(f"sysname: {info.sysname}") +print(f"machine: {info.machine}") +print(f"release: {info.release}") +print("done") +``` + +### 15. Filesystem operations +Test os.listdir(), os.stat(), os.remove(). + +```python +import os + +# List root +files = os.listdir("/") +print(f"root files: {files}") + +# Stat a file +if "code.py" in files: + stat = os.stat("/code.py") + print(f"code.py size: {stat[6]}") + +print("done") +``` + +### 16. os.getenv() +Test environment variable reading. + +```python +import os + +# May return None if not set +path = os.getenv("PATH") +print(f"PATH exists: {path is not None}") +print("done") +``` + +--- + +## board module + +### 17. board.board_id +Verify board ID matches expected value. + +```python +import board + +print(f"board_id: {board.board_id}") +assert board.board_id == "native_native_sim" +print("done") +``` + +### 18. Pin availability +Test that board.LED and other defined pins exist. + +```python +import board + +assert hasattr(board, "LED"), "board.LED missing" +print(f"LED pin: {board.LED}") +print("done") +``` + +--- + +## Filesystem + +### 19. File read/write +Create, read, and modify files on CIRCUITPY. + +```python +# Write a test file +with open("/test.txt", "w") as f: + f.write("hello world") + +# Read it back +with open("/test.txt", "r") as f: + content = f.read() + +assert content == "hello world" +print("done") +``` + +### 20. Directory operations +Test mkdir, rmdir, nested paths. + +```python +import os + +# Create directory +os.mkdir("/testdir") +assert "testdir" in os.listdir("/") + +# Create file in directory +with open("/testdir/file.txt", "w") as f: + f.write("nested") + +# Clean up +os.remove("/testdir/file.txt") +os.rmdir("/testdir") +assert "testdir" not in os.listdir("/") +print("done") +``` + +### 21. Large file handling +Test with larger files approaching flash limits. + +```python +import gc + +# Write 10KB file +data = "x" * 10240 +with open("/large.txt", "w") as f: + f.write(data) + +# Verify size +import os +stat = os.stat("/large.txt") +assert stat[6] == 10240 + +# Clean up +os.remove("/large.txt") +gc.collect() +print("done") +``` + +--- + +## Error Conditions + +### 22. Syntax error in code.py +Verify graceful error message when code.py has syntax error. + +```python +# code.py with intentional syntax error: +def broken( + print("missing close paren" +``` + +Expected: Clear syntax error message with line number. + +### 23. Runtime error +Verify traceback format shows file, line, and function. + +```python +def cause_error(): + x = 1 / 0 + +cause_error() +``` + +Expected: Traceback showing ZeroDivisionError with line info. + +### 24. Keyboard interrupt +Test Ctrl+C handling via PTY if applicable. + +```python +import time + +print("starting long loop") +for i in range(100): + print(f"iteration {i}") + time.sleep(0.1) +print("done") +``` + +Send Ctrl+C during execution, verify clean KeyboardInterrupt. + +--- + +## busio (if emulation available) + +### 25. busio.UART +Basic UART operations if configured. + +```python +import board +import busio + +# Check if UART pins exist +if hasattr(board, "TX") and hasattr(board, "RX"): + uart = busio.UART(board.TX, board.RX, baudrate=115200) + uart.write(b"test") + uart.deinit() +print("done") +``` + +### 26. busio.I2C scan +Scan for emulated I2C devices. + +```python +import board +import busio + +if hasattr(board, "SCL") and hasattr(board, "SDA"): + i2c = busio.I2C(board.SCL, board.SDA) + while not i2c.try_lock(): + pass + devices = i2c.scan() + print(f"I2C devices: {[hex(d) for d in devices]}") + i2c.unlock() + i2c.deinit() +print("done") +``` + +### 27. busio.SPI +Basic SPI transfer to emulated device. + +```python +import board +import busio + +if hasattr(board, "SCK") and hasattr(board, "MOSI"): + spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=getattr(board, "MISO", None)) + while not spi.try_lock(): + pass + spi.configure(baudrate=1000000) + result = bytearray(4) + spi.write_readinto(b"\x00\x00\x00\x00", result) + spi.unlock() + spi.deinit() +print("done") +``` + +--- + +## Serial/PTY Input + +These tests require extending conftest.py to support writing to the PTY, not just reading. + +### 28. Basic serial input +Test reading single characters from serial via PTY write. + +```python +import sys + +print("ready") +char = sys.stdin.read(1) +print(f"received: {repr(char)}") +print("done") +``` + +**Test harness**: After seeing "ready", write "A" to PTY, verify output shows "received: 'A'". + +### 29. input() function +Test the built-in input() function with PTY input. + +```python +print("ready") +name = input("Enter name: ") +print(f"hello {name}") +print("done") +``` + +**Test harness**: After seeing "Enter name:", write "World\n" to PTY, verify "hello World". + +### 30. Serial line buffering +Test reading a complete line with newline termination. + +```python +import sys + +print("ready") +line = sys.stdin.readline() +print(f"got: {repr(line)}") +print("done") +``` + +**Test harness**: Write "test line\n" to PTY, verify complete line received. + +### 31. usb_cdc.console data read +Test reading from usb_cdc.console.data if available. + +```python +import usb_cdc + +print("ready") +if usb_cdc.console: + while not usb_cdc.console.in_waiting: + pass + data = usb_cdc.console.read(usb_cdc.console.in_waiting) + print(f"console got: {data}") +print("done") +``` + +**Test harness**: Write bytes to PTY, verify they're received via usb_cdc.console. + +### 32. REPL interaction +Test entering REPL mode and executing commands interactively. + +```python +# No code.py - boots to REPL +``` + +**Test harness**: +1. Boot with empty/no code.py to get REPL prompt +2. Write "1 + 1\r\n" to PTY +3. Verify output contains "2" +4. Write "print('hello')\r\n" +5. Verify output contains "hello" + +### 33. Ctrl+C interrupt via PTY +Test sending Ctrl+C (0x03) to interrupt running code. + +```python +import time + +print("starting") +for i in range(100): + print(f"loop {i}") + time.sleep(0.1) +print("completed") # Should not reach this +``` + +**Test harness**: +1. Wait for "loop 5" in output +2. Write b"\x03" (Ctrl+C) to PTY +3. Verify KeyboardInterrupt raised +4. Verify "completed" NOT in output + +### 34. Ctrl+D soft reload via PTY +Test sending Ctrl+D (0x04) to trigger soft reload. + +```python +print("first run") +import time +time.sleep(10) # Long sleep to allow interrupt +print("done") +``` + +**Test harness**: +1. Wait for "first run" +2. Write b"\x04" (Ctrl+D) to PTY +3. Verify code restarts (see "first run" again or reload message) + +### 35. Serial input timeout +Test behavior when waiting for input with timeout. + +```python +import sys +import select + +print("ready") +# Poll for input with timeout +readable, _, _ = select.select([sys.stdin], [], [], 0.5) +if readable: + data = sys.stdin.read(1) + print(f"got: {repr(data)}") +else: + print("timeout") +print("done") +``` + +**Test harness**: Don't send anything, verify "timeout" appears. + +--- + +## Fixture Changes for PTY Input + +The `run_circuitpython` fixture in conftest.py needs to be extended: + +```python +@dataclass +class SimulatorResult: + output: str + trace_file: Path + pty_write_fd: int # New: file descriptor for writing to PTY + +def _run(code: str | None, timeout: float = 5.0, ...) -> SimulatorResult: + # Open PTY for both read AND write + pty_fd = os.open(pty_path, os.O_RDWR | os.O_NONBLOCK) + # ... +``` + +Or provide a callback/queue mechanism: + +```python +def _run(code, timeout=5.0, input_sequence=None): + """ + input_sequence: list of (trigger_text, bytes_to_send) tuples + When trigger_text is seen in output, send bytes_to_send to PTY + """ +``` + +--- + +## Implementation Priority + +Suggested order for implementation: + +### Phase 1: Basic module tests (no fixture changes) +1. **#17 board.board_id** - Quick sanity check +2. **#14 os.uname()** - Tests another module, simple +3. **#9 time.monotonic()** - Core timing functionality +4. **#8 time.sleep() precision** - Builds on GPIO trace infrastructure +5. **#1 Multiple file priority** - Tests interpreter boot sequence +6. **#19 File read/write** - Tests filesystem layer +7. **#3 Memory / gc** - Tests memory management +8. **#2 Exception handling** - Tests error reporting + +### Phase 2: PTY input tests (requires fixture extension) +9. **#28 Basic serial input** - Foundation for all input tests +10. **#33 Ctrl+C interrupt** - Important for interactive use +11. **#29 input() function** - Common user pattern +12. **#32 REPL interaction** - Tests interactive mode diff --git a/ports/zephyr-cp/tests/conftest.py b/ports/zephyr-cp/tests/conftest.py new file mode 100644 index 0000000000000..f26c7f0f45f55 --- /dev/null +++ b/ports/zephyr-cp/tests/conftest.py @@ -0,0 +1,312 @@ +# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Pytest fixtures for CircuitPython native_sim testing.""" + +import logging +import os +import re +import select +import subprocess +import time +from dataclasses import dataclass +from pathlib import Path + +import pytest +from perfetto.trace_processor import TraceProcessor + +logger = logging.getLogger(__name__) + +ZEPHYR_CP = Path(__file__).parent.parent +BUILD_DIR = ZEPHYR_CP / "build-native_native_sim" +BINARY = BUILD_DIR / "zephyr-cp/zephyr/zephyr.exe" + + +@dataclass +class InputTrigger: + """A trigger for sending input to the simulator. + + Attributes: + trigger: Text to match in output to trigger input, or None for immediate send. + data: Bytes to send when triggered. + sent: Whether this trigger has been sent (set internally). + """ + + trigger: str | None + data: bytes + sent: bool = False + + +@dataclass +class SimulatorResult: + """Result from running CircuitPython on the simulator.""" + + output: str + trace_file: Path + + +def parse_gpio_trace(trace_file: Path, pin_name: str = "gpio_emul.00") -> list[tuple[int, int]]: + """Parse GPIO trace from Perfetto trace file. + + Args: + trace_file: Path to the Perfetto trace file. + pin_name: Name of the GPIO pin track (e.g., "gpio_emul.00"). + + Returns: + List of (timestamp_ns, value) tuples for the specified GPIO pin. + """ + tp = TraceProcessor(file_path=str(trace_file)) + result = tp.query( + f''' + SELECT c.ts, c.value + FROM counter c + JOIN track t ON c.track_id = t.id + WHERE t.name = "{pin_name}" + ORDER BY c.ts + ''' + ) + return [(row.ts, int(row.value)) for row in result] + + +def _iter_uart_tx_slices(trace_file: Path) -> list[tuple[int, int, str, str]]: + """Return UART TX slices as (timestamp_ns, duration_ns, text, device_name).""" + tp = TraceProcessor(file_path=str(trace_file)) + result = tp.query( + """ + SELECT s.ts, s.dur, s.name, dev.name AS device_name + FROM slice s + JOIN track tx ON s.track_id = tx.id + JOIN track dev ON tx.parent_id = dev.id + JOIN track uart ON dev.parent_id = uart.id + WHERE tx.name = "TX" AND uart.name = "UART" + ORDER BY s.ts + """ + ) + return [ + (int(row.ts), int(row.dur or 0), row.name or "", row.device_name or "UART") + for row in result + ] + + +def log_uart_trace_output(trace_file: Path) -> None: + """Log UART TX output from Perfetto trace with timestamps for line starts.""" + if not logger.isEnabledFor(logging.INFO): + return + slices = _iter_uart_tx_slices(trace_file) + if not slices: + return + + buffers: dict[str, list[str]] = {} + line_start_ts: dict[str, int | None] = {} + + for ts, dur, text, device in slices: + if device not in buffers: + buffers[device] = [] + line_start_ts[device] = None + + if not text: + continue + + char_step = dur / max(len(text), 1) if dur > 0 else 0.0 + for idx, ch in enumerate(text): + if line_start_ts[device] is None: + line_start_ts[device] = int(ts + idx * char_step) + buffers[device].append(ch) + if ch == "\n": + line_text = "".join(buffers[device]).rstrip("\n") + logger.info( + "UART trace %s @%d ns: %s", + device, + line_start_ts[device], + repr(line_text), + ) + buffers[device] = [] + line_start_ts[device] = None + + for device, buf in buffers.items(): + if buf: + logger.info( + "UART trace %s @%d ns (partial): %s", + device, + line_start_ts[device] or 0, + repr("".join(buf)), + ) + + +@pytest.fixture +def native_sim_binary(): + """Return path to native_sim binary, skip if not built.""" + if not BINARY.exists(): + pytest.skip(f"native_sim not built: {BINARY}") + return BINARY + + +@pytest.fixture +def create_flash_image(tmp_path): + """Factory fixture to create FAT flash images.""" + + def _create(files: dict[str, str]) -> Path: + flash = tmp_path / "flash.bin" + + # Create 2MB empty file + flash.write_bytes(b"\x00" * (2 * 1024 * 1024)) + + # Format as FAT (mformat) + subprocess.run(["mformat", "-i", str(flash), "::"], check=True) + + # Copy files (mcopy) + for name, content in files.items(): + src = tmp_path / name + src.write_text(content) + subprocess.run(["mcopy", "-i", str(flash), str(src), f"::{name}"], check=True) + + return flash + + return _create + + +@pytest.fixture +def run_circuitpython(native_sim_binary, create_flash_image, tmp_path): + """Run CircuitPython with given code string and return output from PTY. + + Args: + code: Python code to write to code.py, or None for no code.py. + timeout: Timeout in seconds for the simulation. + erase_flash: If True, erase flash before running. + input_sequence: List of InputTrigger objects. When trigger text is seen + in output, the corresponding data is written to the PTY. If trigger + is None, the data is sent immediately when PTY is opened. + """ + + def _run( + code: str | None, + timeout: float = 5.0, + erase_flash: bool = False, + input_sequence: list[InputTrigger] | None = None, + disabled_i2c_devices: list[str] | None = None, + ) -> SimulatorResult: + files = {"code.py": code} if code is not None else {} + flash = create_flash_image(files) + triggers = list(input_sequence) if input_sequence else [] + trace_file = tmp_path / "trace.perfetto" + + cmd = [ + str(native_sim_binary), + f"--flash={flash}", + "--flash_rm", + "-no-rt", + "-wait_uart", + f"-stop_at={timeout}", + f"--trace-file={trace_file}", + ] + if erase_flash: + cmd.append("--flash_erase") + if disabled_i2c_devices: + for device in disabled_i2c_devices: + cmd.append(f"--disable-i2c={device}") + logger.info("Running: %s", " ".join(cmd)) + + # Start the process + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + pty_path = None + pty_fd = None + output = [] + stdout_lines = [] + + try: + # Read stdout to find the PTY path + start_time = time.time() + while time.time() - start_time < timeout + 5: + if proc.poll() is not None: + # Process exited + break + + # Check if stdout has data + ready, _, _ = select.select([proc.stdout], [], [], 0.1) + if ready: + line = proc.stdout.readline() + if not line: + break + + stdout_lines.append(line.rstrip()) + + # Look for PTY path + match = re.search(r"uart connected to pseudotty: (/dev/pts/\d+)", line) + if match: + pty_path = match.group(1) + # Open the PTY for reading and writing + pty_fd = os.open(pty_path, os.O_RDWR | os.O_NONBLOCK) + + # Send any immediate triggers (trigger=None) + for t in triggers: + if t.trigger is None and not t.sent: + os.write(pty_fd, t.data) + logger.info("PTY input (immediate): %r", t.data) + t.sent = True + break + + if pty_fd is None: + raise RuntimeError("Failed to find PTY path in output") + + def check_triggers(accumulated_output: str) -> None: + """Check accumulated output against triggers and send input.""" + for t in triggers: + if t.trigger is not None and not t.sent: + if t.trigger in accumulated_output: + os.write(pty_fd, t.data) + logger.info("PTY input (trigger %r): %r", t.trigger, t.data) + t.sent = True + + # Read from PTY until process exits or timeout + while time.time() - start_time < timeout + 1: + if proc.poll() is not None: + # Process exited, do one final read + try: + ready, _, _ = select.select([pty_fd], [], [], 0.1) + if ready: + data = os.read(pty_fd, 4096) + if data: + output.append(data.decode("utf-8", errors="replace")) + except (OSError, BlockingIOError): + pass + break + + # Check if PTY has data + try: + ready, _, _ = select.select([pty_fd], [], [], 0.1) + if ready: + data = os.read(pty_fd, 4096) + if data: + output.append(data.decode("utf-8", errors="replace")) + check_triggers("".join(output)) + except (OSError, BlockingIOError): + pass + + # Read any remaining stdout + remaining_stdout = proc.stdout.read() + if remaining_stdout: + stdout_lines.extend(remaining_stdout.rstrip().split("\n")) + + # Log stdout + for line in stdout_lines: + logger.info("stdout: %s", line) + + pty_output = "".join(output) + for line in pty_output.split("\n"): + logger.info("PTY output: %s", repr(line.strip())) + log_uart_trace_output(trace_file) + return SimulatorResult(output=pty_output, trace_file=trace_file) + + finally: + if pty_fd is not None: + os.close(pty_fd) + proc.terminate() + proc.wait(timeout=1) + + return _run diff --git a/ports/zephyr-cp/tests/docs/i2c_emulator_cmdline_control.md b/ports/zephyr-cp/tests/docs/i2c_emulator_cmdline_control.md new file mode 100644 index 0000000000000..8ee2925915c83 --- /dev/null +++ b/ports/zephyr-cp/tests/docs/i2c_emulator_cmdline_control.md @@ -0,0 +1,306 @@ +# Command-Line Control of Emulated I2C Devices in native_sim + +This document describes an approach for enabling/disabling emulated I2C devices +at runtime via command-line options in Zephyr's native_sim environment. + +## Background + +Zephyr's I2C emulation framework (`zephyr,i2c-emul-controller`) provides: + +1. **Bus emulation** - Fake I2C controller that routes transfers to emulated devices +2. **Device emulators** - Software implementations of I2C peripherals (sensors, etc.) +3. **Backend APIs** - Test interfaces for manipulating emulator state + +However, there's no built-in mechanism to enable/disable emulated devices from the +command line. This capability would be useful for: + +- Testing device hot-plug scenarios +- Simulating hardware failures +- Testing error handling paths in CircuitPython + +## Relevant Zephyr APIs + +### I2C Emulator Structure + +From `include/zephyr/drivers/i2c_emul.h`: + +```c +struct i2c_emul { + sys_snode_t node; + const struct emul *target; + const struct i2c_emul_api *api; + struct i2c_emul_api *mock_api; // If non-NULL, takes precedence + uint16_t addr; +}; + +struct i2c_emul_api { + i2c_emul_transfer_t transfer; +}; +``` + +Key insight: The `mock_api` field allows overriding the normal transfer function. +If `mock_api->transfer()` returns `-ENOSYS`, it falls back to the real API. + +### Command-Line Registration + +From `boards/native/native_sim/cmdline.h`: + +```c +void native_add_command_line_opts(struct args_struct_t *args); +``` + +### Emulator Lookup + +From `include/zephyr/drivers/emul.h`: + +```c +const struct emul *emul_get_binding(const char *name); +``` + +## Implementation Approach + +### 1. Create a Disabled Device Registry + +Track which devices are "disabled" (should NACK all transactions): + +```c +// i2c_emul_cmdline.c + +#include +#include +#include +#include "nsi_cmdline.h" + +#define MAX_DISABLED_DEVICES 16 + +static struct { + const char *name; + const struct emul *emul; + struct i2c_emul_api mock_api; + struct i2c_emul_api *original_mock_api; + bool disabled; +} disabled_devices[MAX_DISABLED_DEVICES]; + +static int num_disabled_devices = 0; +``` + +### 2. Mock Transfer Function + +Return `-EIO` (simulates NACK) when device is disabled: + +```c +static int disabled_device_transfer(const struct emul *target, + struct i2c_msg *msgs, + int num_msgs, + int addr) +{ + // Find this device in our registry + for (int i = 0; i < num_disabled_devices; i++) { + if (disabled_devices[i].emul == target) { + if (disabled_devices[i].disabled) { + // Device is disabled - simulate NACK + return -EIO; + } + break; + } + } + // Fall back to normal emulator behavior + return -ENOSYS; +} +``` + +### 3. Enable/Disable Functions + +```c +int i2c_emul_cmdline_disable_device(const char *name) +{ + const struct emul *emul = emul_get_binding(name); + if (!emul || emul->bus_type != EMUL_BUS_TYPE_I2C) { + return -ENODEV; + } + + // Find or create registry entry + int idx = -1; + for (int i = 0; i < num_disabled_devices; i++) { + if (disabled_devices[i].emul == emul) { + idx = i; + break; + } + } + + if (idx < 0) { + if (num_disabled_devices >= MAX_DISABLED_DEVICES) { + return -ENOMEM; + } + idx = num_disabled_devices++; + disabled_devices[idx].name = name; + disabled_devices[idx].emul = emul; + disabled_devices[idx].mock_api.transfer = disabled_device_transfer; + + // Save and replace mock_api + disabled_devices[idx].original_mock_api = emul->bus.i2c->mock_api; + emul->bus.i2c->mock_api = &disabled_devices[idx].mock_api; + } + + disabled_devices[idx].disabled = true; + return 0; +} + +int i2c_emul_cmdline_enable_device(const char *name) +{ + for (int i = 0; i < num_disabled_devices; i++) { + if (strcmp(disabled_devices[i].name, name) == 0) { + disabled_devices[i].disabled = false; + return 0; + } + } + return -ENODEV; +} +``` + +### 4. Command-Line Option Registration + +```c +static char *disabled_device_args[MAX_DISABLED_DEVICES]; +static int num_disabled_device_args = 0; + +static void cmd_disable_i2c_device(char *argv, int offset) +{ + ARG_UNUSED(offset); + if (num_disabled_device_args < MAX_DISABLED_DEVICES) { + disabled_device_args[num_disabled_device_args++] = argv; + } +} + +static struct args_struct_t i2c_emul_args[] = { + { + .option = "disable-i2c", + .name = "device_name", + .type = 's', + .call_when_found = cmd_disable_i2c_device, + .descript = "Disable an emulated I2C device (can be repeated)" + }, + ARG_TABLE_ENDMARKER +}; + +static void register_cmdline_opts(void) +{ + native_add_command_line_opts(i2c_emul_args); +} + +// Hook into native_sim initialization +NATIVE_TASK(register_cmdline_opts, PRE_BOOT_1, 0); + +static void apply_disabled_devices(void) +{ + for (int i = 0; i < num_disabled_device_args; i++) { + int rc = i2c_emul_cmdline_disable_device(disabled_device_args[i]); + if (rc != 0) { + printk("Warning: Failed to disable I2C device '%s': %d\n", + disabled_device_args[i], rc); + } + } +} + +// Apply after emulators are initialized +NATIVE_TASK(apply_disabled_devices, PRE_BOOT_3, 0); +``` + +### 5. Usage + +After building with this code: + +```bash +# Disable a sensor at startup +./build/zephyr/zephyr.exe --disable-i2c=bmi160@68 + +# Disable multiple devices +./build/zephyr/zephyr.exe --disable-i2c=bmi160@68 --disable-i2c=sht4x@44 +``` + +## Runtime Control Extension + +For runtime enable/disable (not just at startup), you could: + +### Option A: Use a Named Pipe / FIFO + +```c +// Create a FIFO that accepts commands +// "disable bmi160@68" or "enable bmi160@68" +static void *cmdline_control_thread(void *arg) +{ + int fd = open("/tmp/i2c_emul_control", O_RDONLY); + char buf[256]; + while (read(fd, buf, sizeof(buf)) > 0) { + if (strncmp(buf, "disable ", 8) == 0) { + i2c_emul_cmdline_disable_device(buf + 8); + } else if (strncmp(buf, "enable ", 7) == 0) { + i2c_emul_cmdline_enable_device(buf + 7); + } + } + return NULL; +} +``` + +### Option B: Signal Handler + +Use `SIGUSR1`/`SIGUSR2` with a config file that specifies which device to toggle. + +### Option C: Shared Memory + +Map a shared memory region that external tools can write to for device state. + +## Integration with CircuitPython Tests + +For pytest-based tests, you could: + +```python +import subprocess +import os + +class NativeSimProcess: + def __init__(self, exe_path): + self.exe_path = exe_path + self.control_fifo = "/tmp/i2c_emul_control" + + def start(self, disabled_devices=None): + args = [self.exe_path] + if disabled_devices: + for dev in disabled_devices: + args.extend(["--disable-i2c", dev]) + self.proc = subprocess.Popen(args, ...) + + def disable_device(self, name): + """Runtime disable via FIFO""" + with open(self.control_fifo, 'w') as f: + f.write(f"disable {name}\n") + + def enable_device(self, name): + """Runtime enable via FIFO""" + with open(self.control_fifo, 'w') as f: + f.write(f"enable {name}\n") +``` + +## Alternative: Device Tree Approach + +For compile-time configuration, use device tree overlays: + +```dts +// boards/native_sim_no_bmi160.overlay +&bmi160 { + status = "disabled"; +}; +``` + +Build separate variants: +```bash +west build -b native_sim -- -DDTC_OVERLAY_FILE=boards/native_sim_no_bmi160.overlay +``` + +## References + +- Zephyr I2C Emulation: `zephyr/drivers/i2c/i2c_emul.c` +- Emulator Framework: `zephyr/doc/hardware/emulator/bus_emulators.rst` +- Native Sim Docs: `zephyr/boards/native/native_sim/doc/index.rst` +- Command-line handling: `zephyr/boards/native/native_sim/cmdline.c` +- Example mock API usage: `zephyr/tests/drivers/sensor/bmi160/src/i2c.c` diff --git a/ports/zephyr-cp/tests/test_basics.py b/ports/zephyr-cp/tests/test_basics.py new file mode 100644 index 0000000000000..9abb7f451b722 --- /dev/null +++ b/ports/zephyr-cp/tests/test_basics.py @@ -0,0 +1,199 @@ +# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Test LED blink functionality on native_sim.""" + +from conftest import InputTrigger, parse_gpio_trace + + +def test_blank_flash_hello_world(run_circuitpython): + """Test that an erased flash shows code.py output header.""" + result = run_circuitpython(None, timeout=4, erase_flash=True) + + assert "Board ID:native_native_sim" in result.output + assert "UID:" in result.output + assert "code.py output:" in result.output + assert "Hello World" in result.output + assert "done" in result.output + + +BLINK_CODE = """\ +import time +import board +import digitalio + +led = digitalio.DigitalInOut(board.LED) +led.direction = digitalio.Direction.OUTPUT + +for i in range(3): + print(f"LED on {i}") + led.value = True + time.sleep(0.1) + print(f"LED off {i}") + led.value = False + time.sleep(0.1) + +print("done") +""" + + +def test_blink_output(run_circuitpython): + """Test blink program produces expected output and GPIO traces.""" + result = run_circuitpython(BLINK_CODE, timeout=5) + + # Check serial output + assert "LED on 0" in result.output + assert "LED off 0" in result.output + assert "LED on 2" in result.output + assert "LED off 2" in result.output + assert "done" in result.output + + # Check GPIO traces - LED is on gpio_emul.00 + gpio_trace = parse_gpio_trace(result.trace_file, "gpio_emul.00") + + # Deduplicate by timestamp (keep last value at each timestamp) + by_timestamp = {} + for ts, val in gpio_trace: + by_timestamp[ts] = val + sorted_trace = sorted(by_timestamp.items()) + + # Find transition points (where value changes), skipping initialization at ts=0 + transitions = [] + for i in range(1, len(sorted_trace)): + prev_ts, prev_val = sorted_trace[i - 1] + curr_ts, curr_val = sorted_trace[i] + if prev_val != curr_val and curr_ts > 0: + transitions.append((curr_ts, curr_val)) + + # We expect at least 6 transitions (3 on + 3 off) from the blink loop + assert len(transitions) >= 6, f"Expected at least 6 transitions, got {len(transitions)}" + + # Verify timing between consecutive transitions + # Each sleep is 0.1s = 100ms = 100,000,000 ns + expected_interval_ns = 100_000_000 + tolerance_ns = 20_000_000 # 20ms tolerance + + # Find a sequence of 6 consecutive transitions with ~100ms intervals (the blink loop) + # This filters out initialization and cleanup noise + blink_transitions = [] + for i in range(len(transitions) - 1): + interval = transitions[i + 1][0] - transitions[i][0] + if abs(interval - expected_interval_ns) < tolerance_ns: + if not blink_transitions: + blink_transitions.append(transitions[i]) + blink_transitions.append(transitions[i + 1]) + elif blink_transitions: + # Found end of blink sequence + break + + assert len(blink_transitions) >= 6, ( + f"Expected at least 6 blink transitions with ~100ms intervals, got {len(blink_transitions)}" + ) + + # Verify timing between blink transitions + for i in range(1, min(6, len(blink_transitions))): + prev_ts = blink_transitions[i - 1][0] + curr_ts = blink_transitions[i][0] + interval = curr_ts - prev_ts + assert abs(interval - expected_interval_ns) < tolerance_ns, ( + f"Transition interval {interval / 1_000_000:.1f}ms deviates from " + f"expected {expected_interval_ns / 1_000_000:.1f}ms by more than " + f"{tolerance_ns / 1_000_000:.1f}ms tolerance" + ) + + +# --- PTY Input Tests --- + + +INPUT_CODE = """\ +import sys + +print("ready") +char = sys.stdin.read(1) +print(f"received: {repr(char)}") +print("done") +""" + + +def test_basic_serial_input(run_circuitpython): + """Test reading single character from serial via PTY write.""" + result = run_circuitpython( + INPUT_CODE, + timeout=5.0, + input_sequence=[InputTrigger(trigger="ready", data=b"A")], + ) + + assert "ready" in result.output + assert "received: 'A'" in result.output + assert "done" in result.output + + +INPUT_FUNC_CODE = """\ +print("ready") +name = input("Enter name: ") +print(f"hello {name}") +print("done") +""" + + +def test_input_function(run_circuitpython): + """Test the built-in input() function with PTY input.""" + result = run_circuitpython( + INPUT_FUNC_CODE, + timeout=5.0, + input_sequence=[InputTrigger(trigger="Enter name:", data=b"World\r")], + ) + + assert "ready" in result.output + assert "Enter name:" in result.output + assert "hello World" in result.output + assert "done" in result.output + + +INTERRUPT_CODE = """\ +import time + +print("starting") +for i in range(100): + print(f"loop {i}") + time.sleep(0.1) +print("completed") +""" + + +def test_ctrl_c_interrupt(run_circuitpython): + """Test sending Ctrl+C (0x03) to interrupt running code.""" + result = run_circuitpython( + INTERRUPT_CODE, + timeout=15.0, + input_sequence=[InputTrigger(trigger="loop 5", data=b"\x03")], + ) + + assert "starting" in result.output + assert "loop 5" in result.output + assert "KeyboardInterrupt" in result.output + assert "completed" not in result.output + + +RELOAD_CODE = """\ +print("first run") +import time +time.sleep(1) +print("done") +""" + + +def test_ctrl_d_soft_reload(run_circuitpython): + """Test sending Ctrl+D (0x04) to trigger soft reload.""" + result = run_circuitpython( + RELOAD_CODE, + timeout=10.0, + input_sequence=[InputTrigger(trigger="first run", data=b"\x04")], + ) + + # Should see "first run" appear multiple times due to reload + # or see a soft reboot message + assert "first run" in result.output + # The soft reload should restart the code before "done" is printed + assert "done" in result.output + assert result.output.count("first run") > 1 diff --git a/ports/zephyr-cp/tests/test_i2c.py b/ports/zephyr-cp/tests/test_i2c.py new file mode 100644 index 0000000000000..594dfcc8f4d1c --- /dev/null +++ b/ports/zephyr-cp/tests/test_i2c.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Test I2C functionality on native_sim.""" + +I2C_SCAN_CODE = """\ +import board + +i2c = board.I2C() +while not i2c.try_lock(): + pass +devices = i2c.scan() +print(f"I2C devices: {[hex(d) for d in devices]}") +i2c.unlock() +i2c.deinit() +print("done") +""" + + +def test_i2c_scan(run_circuitpython): + """Test I2C bus scanning finds emulated devices. + + The AT24 EEPROM emulator responds to zero-length probe writes, + so it should appear in scan results at address 0x50. + """ + result = run_circuitpython(I2C_SCAN_CODE, timeout=5.0) + + assert "I2C devices:" in result.output + # AT24 EEPROM should be at address 0x50 + assert "0x50" in result.output + assert "done" in result.output + + +AT24_READ_CODE = """\ +import board + +i2c = board.I2C() +while not i2c.try_lock(): + pass + +# AT24 EEPROM at address 0x50 +AT24_ADDR = 0x50 + +# Read first byte from address 0 +result = bytearray(1) +try: + i2c.writeto_then_readfrom(AT24_ADDR, bytes([0x00]), result) + value = result[0] + print(f"AT24 byte 0: 0x{value:02X}") + # Fresh EEPROM should be 0xFF + if value == 0xFF: + print("eeprom_valid") + else: + print(f"unexpected value: expected 0xFF, got 0x{value:02X}") +except OSError as e: + print(f"I2C error: {e}") + +i2c.unlock() +i2c.deinit() +print("done") +""" + + +def test_i2c_at24_read(run_circuitpython): + """Test reading from AT24 EEPROM emulator.""" + result = run_circuitpython(AT24_READ_CODE, timeout=5.0) + + assert "AT24 byte 0: 0xFF" in result.output + assert "eeprom_valid" in result.output + assert "done" in result.output + + +def test_i2c_device_disabled(run_circuitpython): + """Test that disabled I2C device doesn't appear in scan.""" + result = run_circuitpython( + I2C_SCAN_CODE, + timeout=5.0, + disabled_i2c_devices=["eeprom@50"], + ) + + assert "I2C devices:" in result.output + # AT24 at 0x50 should NOT appear when disabled + assert "0x50" not in result.output + assert "done" in result.output + + +def test_i2c_device_disabled_communication_fails(run_circuitpython): + """Test that communication with disabled I2C device fails.""" + result = run_circuitpython( + AT24_READ_CODE, + timeout=5.0, + disabled_i2c_devices=["eeprom@50"], + ) + + # Should get an I2C error when trying to communicate + assert "I2C error" in result.output + assert "eeprom_valid" not in result.output + assert "done" in result.output From 23be085b5e79a0904789b41b3aa71f46f214359d Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 2 Feb 2026 12:05:42 -0800 Subject: [PATCH 03/14] Build native sim in CI explicitly --- .github/workflows/run-tests.yml | 2 ++ requirements-dev.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ed0dd65a4f1d3..4fded6bbbdf27 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -88,5 +88,7 @@ jobs: uses: ./.github/actions/deps/ports/zephyr-cp - name: Set up external uses: ./.github/actions/deps/external + - name: Build native sim target + run: make -C ports/zephyr-cp -j2 BOARD=native_native_sim - name: Run Zephyr tests run: make -C ports/zephyr-cp test diff --git a/requirements-dev.txt b/requirements-dev.txt index cdfb62da1fa76..566618d2c4721 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -42,3 +42,4 @@ setuptools # For zephyr port tomlkit pytest +perfetto From 4d2f974712cb595c9c5c825a8f5cb1c418db79c1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 2 Feb 2026 12:09:04 -0800 Subject: [PATCH 04/14] Dunder asm for Zephyr toolchain --- shared/runtime/gchelper_generic.c | 98 +++++++++++++++---------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c index 40c5865ed1c68..464aeaa9981de 100644 --- a/shared/runtime/gchelper_generic.c +++ b/shared/runtime/gchelper_generic.c @@ -43,12 +43,12 @@ #if defined(__x86_64__) static void gc_helper_get_regs(gc_helper_regs_t arr) { - register long rbx asm ("rbx"); - register long rbp asm ("rbp"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); - register long r14 asm ("r14"); - register long r15 asm ("r15"); + register long rbx __asm__ ("rbx"); + register long rbp __asm__ ("rbp"); + register long r12 __asm__ ("r12"); + register long r13 __asm__ ("r13"); + register long r14 __asm__ ("r14"); + register long r15 __asm__ ("r15"); #ifdef __clang__ // TODO: // This is dirty workaround for Clang. It tries to get around @@ -56,12 +56,12 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { // Application of this patch here is random, and done only to unbreak // MacOS build. Better, cross-arch ways to deal with Clang issues should // be found. - asm ("" : "=r" (rbx)); - asm ("" : "=r" (rbp)); - asm ("" : "=r" (r12)); - asm ("" : "=r" (r13)); - asm ("" : "=r" (r14)); - asm ("" : "=r" (r15)); + __asm__ ("" : "=r" (rbx)); + __asm__ ("" : "=r" (rbp)); + __asm__ ("" : "=r" (r12)); + __asm__ ("" : "=r" (r13)); + __asm__ ("" : "=r" (r14)); + __asm__ ("" : "=r" (r15)); #endif arr[0] = rbx; arr[1] = rbp; @@ -85,10 +85,10 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { // Application of this patch here is random, and done only to unbreak // MacOS build. Better, cross-arch ways to deal with Clang issues should // be found. - asm ("" : "=r" (ebx)); - asm ("" : "=r" (esi)); - asm ("" : "=r" (edi)); - asm ("" : "=r" (ebp)); + __asm__ ("" : "=r" (ebx)); + __asm__ ("" : "=r" (esi)); + __asm__ ("" : "=r" (edi)); + __asm__ ("" : "=r" (ebp)); #endif arr[0] = ebx; arr[1] = esi; @@ -105,16 +105,16 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wuninitialized" #endif - register long r4 asm ("r4"); - register long r5 asm ("r5"); - register long r6 asm ("r6"); - register long r7 asm ("r7"); - register long r8 asm ("r8"); - register long r9 asm ("r9"); - register long r10 asm ("r10"); - register long r11 asm ("r11"); - register long r12 asm ("r12"); - register long r13 asm ("r13"); + register long r4 __asm__ ("r4"); + register long r5 __asm__ ("r5"); + register long r6 __asm__ ("r6"); + register long r7 __asm__ ("r7"); + register long r8 __asm__ ("r8"); + register long r9 __asm__ ("r9"); + register long r10 __asm__ ("r10"); + register long r11 __asm__ ("r11"); + register long r12 __asm__ ("r12"); + register long r13 __asm__ ("r13"); arr[0] = r4; arr[1] = r5; arr[2] = r6; @@ -133,17 +133,17 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { #elif defined(__aarch64__) static void gc_helper_get_regs(gc_helper_regs_t arr) { - const register long x19 asm ("x19"); - const register long x20 asm ("x20"); - const register long x21 asm ("x21"); - const register long x22 asm ("x22"); - const register long x23 asm ("x23"); - const register long x24 asm ("x24"); - const register long x25 asm ("x25"); - const register long x26 asm ("x26"); - const register long x27 asm ("x27"); - const register long x28 asm ("x28"); - const register long x29 asm ("x29"); + const register long x19 __asm__ ("x19"); + const register long x20 __asm__ ("x20"); + const register long x21 __asm__ ("x21"); + const register long x22 __asm__ ("x22"); + const register long x23 __asm__ ("x23"); + const register long x24 __asm__ ("x24"); + const register long x25 __asm__ ("x25"); + const register long x26 __asm__ ("x26"); + const register long x27 __asm__ ("x27"); + const register long x28 __asm__ ("x28"); + const register long x29 __asm__ ("x29"); arr[0] = x19; arr[1] = x20; arr[2] = x21; @@ -163,18 +163,18 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { // for RV32I targets or gchelper_rv64i.s for RV64I targets. static void gc_helper_get_regs(gc_helper_regs_t arr) { - register uintptr_t s0 asm ("x8"); - register uintptr_t s1 asm ("x9"); - register uintptr_t s2 asm ("x18"); - register uintptr_t s3 asm ("x19"); - register uintptr_t s4 asm ("x20"); - register uintptr_t s5 asm ("x21"); - register uintptr_t s6 asm ("x22"); - register uintptr_t s7 asm ("x23"); - register uintptr_t s8 asm ("x24"); - register uintptr_t s9 asm ("x25"); - register uintptr_t s10 asm ("x26"); - register uintptr_t s11 asm ("x27"); + register uintptr_t s0 __asm__ ("x8"); + register uintptr_t s1 __asm__ ("x9"); + register uintptr_t s2 __asm__ ("x18"); + register uintptr_t s3 __asm__ ("x19"); + register uintptr_t s4 __asm__ ("x20"); + register uintptr_t s5 __asm__ ("x21"); + register uintptr_t s6 __asm__ ("x22"); + register uintptr_t s7 __asm__ ("x23"); + register uintptr_t s8 __asm__ ("x24"); + register uintptr_t s9 __asm__ ("x25"); + register uintptr_t s10 __asm__ ("x26"); + register uintptr_t s11 __asm__ ("x27"); arr[0] = s0; arr[1] = s1; arr[2] = s2; From 1dc9c83b54a5ad61ca7cfaccb49efa9b2d2fd7d6 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 09:34:07 -0800 Subject: [PATCH 05/14] Fetch submodules --- .github/workflows/run-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4fded6bbbdf27..01172041b6205 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -86,6 +86,11 @@ jobs: python-version: 3.13 - name: Set up Zephyr uses: ./.github/actions/deps/ports/zephyr-cp + - name: Set up submodules + id: set-up-submodules + uses: ./.github/actions/deps/submodules + with: + target: zephyr-cp - name: Set up external uses: ./.github/actions/deps/external - name: Build native sim target From df8b3e530197bf3c7857506a04253ecfcd3be3f2 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 09:36:24 -0800 Subject: [PATCH 06/14] Guard zephyr include. Add fetch-port-submodules make target --- ports/zephyr-cp/Makefile | 5 ++++- supervisor/shared/safe_mode.c | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index 15ae523fb72ca..622fe4901a99f 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -8,7 +8,7 @@ BUILD ?= build-$(BOARD) TRANSLATION ?= en_US -.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash debug run clean menuconfig all clean-all test +.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash debug run clean menuconfig all clean-all test fetch-port-submodules $(BUILD)/zephyr-cp/zephyr/zephyr.elf: python cptools/pre_zephyr_build_prep.py $(BOARD) @@ -38,6 +38,9 @@ menuconfig: clean: rm -rf $(BUILD) +fetch-port-submodules: + python ../../tools/ci_fetch_deps.py zephyr-cp + # Build all boards. The + prefix allows jobserver file descriptors to be passed through. # This enables parallel builds across all boards when invoked with `make -jN all`. all: diff --git a/supervisor/shared/safe_mode.c b/supervisor/shared/safe_mode.c index a3e4de235c7bd..36a0e99180b5c 100644 --- a/supervisor/shared/safe_mode.c +++ b/supervisor/shared/safe_mode.c @@ -18,7 +18,9 @@ #include "supervisor/shared/translate/translate.h" #include "supervisor/shared/tick.h" +#if __ZEPHYR__ #include +#endif #define SAFE_MODE_DATA_GUARD 0xad0000af #define SAFE_MODE_DATA_GUARD_MASK 0xff0000ff From 1ed4442e0533329f7d45310c7ccec9ba3d57fe28 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 11:00:25 -0800 Subject: [PATCH 07/14] Fix thunk issue --- ports/zephyr-cp/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/zephyr-cp/mpconfigport.h b/ports/zephyr-cp/mpconfigport.h index 5b5b077a37152..491b5293e2ebc 100644 --- a/ports/zephyr-cp/mpconfigport.h +++ b/ports/zephyr-cp/mpconfigport.h @@ -17,6 +17,9 @@ #define CIRCUITPY_DEBUG_TINYUSB 0 +// Disable native _Float16 handling for host builds. +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) + //////////////////////////////////////////////////////////////////////////////////////////////////// // This also includes mpconfigboard.h. From ac063d15fc1ae440fccd5834ec5918122ed2d0c0 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 11:44:48 -0800 Subject: [PATCH 08/14] Zephyr ifdef --- supervisor/shared/safe_mode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/shared/safe_mode.c b/supervisor/shared/safe_mode.c index 36a0e99180b5c..9650556546030 100644 --- a/supervisor/shared/safe_mode.c +++ b/supervisor/shared/safe_mode.c @@ -18,7 +18,7 @@ #include "supervisor/shared/translate/translate.h" #include "supervisor/shared/tick.h" -#if __ZEPHYR__ +#ifdef __ZEPHYR__ #include #endif @@ -104,7 +104,7 @@ void PLACE_IN_ITCM(safe_mode_on_next_reset)(safe_mode_t reason) { // Don't inline this so it's easy to break on it from GDB. void __attribute__((noinline, )) PLACE_IN_ITCM(reset_into_safe_mode)(safe_mode_t reason) { if (_safe_mode > SAFE_MODE_BROWNOUT && reason > SAFE_MODE_BROWNOUT) { - #if __ZEPHYR__ + #ifdef __ZEPHYR__ printk("Already in safe mode\n"); printk("Reason: %d\n", reason); printk("Current safe mode: %d\n", _safe_mode); From f7c66cc518a61c6bb23dc2c0a1891c3203198aa8 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 11:46:45 -0800 Subject: [PATCH 09/14] install mtools --- .github/actions/deps/ports/zephyr-cp/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/deps/ports/zephyr-cp/action.yml b/.github/actions/deps/ports/zephyr-cp/action.yml index 75ff232562c4d..5f52cc7f0c259 100644 --- a/.github/actions/deps/ports/zephyr-cp/action.yml +++ b/.github/actions/deps/ports/zephyr-cp/action.yml @@ -3,11 +3,11 @@ name: Fetch Zephyr port deps runs: using: composite steps: - - name: Get libusb + - name: Get libusb and mtools if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libusb-1.0-0-dev libudev-dev + sudo apt-get install -y libusb-1.0-0-dev libudev-dev mtools shell: bash - name: Setup Zephyr project uses: zephyrproject-rtos/action-zephyr-setup@v1 From 6ab3b0be848ef9ebe67a35399d64c7abf57f472c Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 14:58:31 -0800 Subject: [PATCH 10/14] Move other ports to gchelper --- ports/analog/Makefile | 3 +++ ports/atmel-samd/Makefile | 8 ++++++++ ports/broadcom/Makefile | 2 ++ ports/cxd56/Makefile | 4 ++++ ports/espressif/Makefile | 7 +++++++ ports/litex/Makefile | 5 +++++ ports/mimxrt10xx/Makefile | 6 +++++- ports/nordic/Makefile | 4 ++++ ports/raspberrypi/Makefile | 5 +++++ ports/renode/Makefile | 3 +++ ports/silabs/Makefile | 4 +++- ports/stm/Makefile | 3 +++ py/circuitpy_mpconfig.h | 4 ++++ 13 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ports/analog/Makefile b/ports/analog/Makefile index 0ae276985f7f4..4dd174d2f6cf2 100644 --- a/ports/analog/Makefile +++ b/ports/analog/Makefile @@ -156,6 +156,9 @@ endif SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S += $(STARTUPFILE) +SRC_S += shared/runtime/gchelper_thumb2.s + +SRC_C += shared/runtime/gchelper_native.c # Needed to compile some MAX32 headers CFLAGS += -D$(MCU_VARIANT_UPPER) \ diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index 9582eff212d60..ec414f3d6330e 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -284,6 +284,7 @@ SRC_C += \ lib/tinyusb/src/portable/microchip/samd/dcd_samd.c \ mphalport.c \ reset.c \ + shared/runtime/gchelper_native.c \ timer_handler.c \ $(SRC_PERIPHERALS) \ @@ -308,6 +309,12 @@ ifeq ($(CIRCUITPY_AUDIOBUSIO),1) SRC_C += peripherals/samd/i2s.c peripherals/samd/$(PERIPHERALS_CHIP_FAMILY)/i2s.c endif +ifeq ($(CHIP_FAMILY), samd21) +SRC_S += shared/runtime/gchelper_thumb1.s +else +SRC_S += shared/runtime/gchelper_thumb2.s +endif + SRC_S_UPPER = supervisor/shared/cpu_regs.S OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) @@ -317,6 +324,7 @@ ifeq ($(INTERNAL_LIBM),1) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) diff --git a/ports/broadcom/Makefile b/ports/broadcom/Makefile index f60ebbf045e1f..dda93c6fd7ce0 100644 --- a/ports/broadcom/Makefile +++ b/ports/broadcom/Makefile @@ -59,6 +59,8 @@ SRC_C += bindings/videocore/__init__.c \ SRC_S = peripherals/broadcom/boot$(SUFFIX).s +SRC_C += shared/runtime/gchelper_generic.c + OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) ifeq ($(INTERNAL_LIBM),1) diff --git a/ports/cxd56/Makefile b/ports/cxd56/Makefile index fdf3646c80d27..6305a5b1d4bc5 100644 --- a/ports/cxd56/Makefile +++ b/ports/cxd56/Makefile @@ -111,6 +111,9 @@ LDFLAGS = \ CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_CXD56 -DCFG_TUD_MIDI_RX_BUFSIZE=512 -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_MIDI_TX_BUFSIZE=512 -DCFG_TUD_CDC_TX_BUFSIZE=1024 -DCFG_TUD_MSC_BUFSIZE=512 $(CFLAGS_MOD) SRC_S_UPPER = supervisor/shared/cpu_regs.S +SRC_S = shared/runtime/gchelper_thumb2.s + +SRC_C += shared/runtime/gchelper_native.c SRC_C += \ background.c \ @@ -120,6 +123,7 @@ SRC_C += \ lib/tinyusb/src/portable/sony/cxd56/dcd_cxd56.c \ OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) ifeq ($(INTERNAL_LIBM),1) diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 8302726aad7a4..f83c2446df1a0 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -216,9 +216,12 @@ ifeq ($(IDF_TARGET_ARCH),xtensa) # `#include "xtensa/xtensa_api.h"`. CFLAGS += -mlongcalls -isystem esp-idf/components/xtensa/deprecated_include/ -Wno-error=cpp + CFLAGS += -DMICROPY_GCREGS_SETJMP=1 # Wrap longjmp with a patched version that protects register window update with a critical section LDFLAGS += -Wl,--wrap=longjmp + + SRC_C += shared/runtime/gchelper_generic.c else ifeq ($(IDF_TARGET_ARCH),riscv) ifeq ($(IDF_TARGET),esp32p4) @@ -230,6 +233,9 @@ else ifeq ($(IDF_TARGET_ARCH),riscv) LDFLAGS += \ -Lesp-idf/components/riscv/ld \ -Trom.api.ld + + SRC_C += shared/runtime/gchelper_native.c + SRC_S = shared/runtime/gchelper_rv32i.s endif @@ -594,6 +600,7 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) $(BUILD)/$(FATFS_DIR)/ff.o: COPT += -Os diff --git a/ports/litex/Makefile b/ports/litex/Makefile index 98abe985699cf..9ef34cd8ff8c1 100644 --- a/ports/litex/Makefile +++ b/ports/litex/Makefile @@ -81,6 +81,10 @@ SRC_S_UPPER = \ crt0-vexriscv.S \ supervisor/shared/cpu_regs.S +SRC_S = shared/runtime/gchelper_rv32i.s + +SRC_C += shared/runtime/gchelper_native.c + $(BUILD)/lib/tlsf/tlsf.o: CFLAGS += -Wno-cast-align ifneq ($(FROZEN_MPY_DIR),) @@ -94,6 +98,7 @@ ifeq ($(INTERNAL_LIBM),1) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) diff --git a/ports/mimxrt10xx/Makefile b/ports/mimxrt10xx/Makefile index e6928eead619d..9cb1df8014520 100644 --- a/ports/mimxrt10xx/Makefile +++ b/ports/mimxrt10xx/Makefile @@ -165,6 +165,10 @@ SRC_S_UPPER = \ sdk/devices/$(CHIP_FAMILY)/gcc/startup_$(CHIP_CORE).S \ supervisor/shared/cpu_regs.S +SRC_S = shared/runtime/gchelper_thumb2.s + +SRC_C += shared/runtime/gchelper_native.c + OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_SDK:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) @@ -172,7 +176,7 @@ ifeq ($(INTERNAL_LIBM),1) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S:.S=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) diff --git a/ports/nordic/Makefile b/ports/nordic/Makefile index aa614e097ba3c..cd1360b9fd425 100755 --- a/ports/nordic/Makefile +++ b/ports/nordic/Makefile @@ -147,6 +147,9 @@ $(patsubst %.c,$(BUILD)/%.o,$(SRC_DCD)): CFLAGS += -Wno-missing-prototypes endif # CIRCUITPY_USB_DEVICE SRC_S_UPPER = supervisor/shared/cpu_regs.S +SRC_S = shared/runtime/gchelper_thumb2.s + +SRC_C += shared/runtime/gchelper_native.c OBJ += $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_NRFX:.c=.o)) @@ -155,6 +158,7 @@ ifeq ($(INTERNAL_LIBM),1) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index b75d542325166..097a4e7728f70 100644 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -422,6 +422,8 @@ SRC_S_UPPER_CHIP_VARIANT := \ sdk/src/rp2_common/pico_float/float_aeabi_rp2040.S \ sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S \ +SRC_S = shared/runtime/gchelper_thumb1.s + PICO_LDFLAGS += \ $(PICO_WRAP_FLOAT_AEABI_FLAGS) \ $(PICO_WRAP_FLOAT_SCI_FLAGS) \ @@ -461,6 +463,8 @@ SRC_S_UPPER_CHIP_VARIANT := \ sdk/src/rp2_common/pico_float/float_sci_m33_vfp.S \ sdk/src/rp2_common/pico_float/float_common_m33.S \ +SRC_S = shared/runtime/gchelper_thumb2.s + PICO_LDFLAGS += $(PICO_WRAP_FLOAT_SCI_FLAGS) $(PICO_WRAP_DOUBLE_FLAGS) ifeq ($(CHIP_PACKAGE),A) @@ -473,6 +477,7 @@ endif endif +SRC_C += shared/runtime/gchelper_native.c SRC_SDK := \ src/common/hardware_claim/claim.c \ diff --git a/ports/renode/Makefile b/ports/renode/Makefile index 92541c03e77b9..40013ac4f661b 100644 --- a/ports/renode/Makefile +++ b/ports/renode/Makefile @@ -46,6 +46,9 @@ SRC_C += \ mphalport.c \ SRC_S_UPPER = supervisor/shared/cpu_regs.S +SRC_S = shared/runtime/gchelper_thumb1.s + +SRC_C += shared/runtime/gchelper_native.c OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) diff --git a/ports/silabs/Makefile b/ports/silabs/Makefile index 58929dd498fed..97b0e4c597fde 100644 --- a/ports/silabs/Makefile +++ b/ports/silabs/Makefile @@ -90,7 +90,9 @@ ifneq (,$(wildcard boards/$(BOARD)/sensor.c)) SRC_C += boards/$(BOARD)/sensor.c endif -SRC_S = boards/mp_efr32xg24_gchelper.s +SRC_S = shared/runtime/gchelper_thumb2.s + +SRC_C += shared/runtime/gchelper_native.c OBJ += $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) diff --git a/ports/stm/Makefile b/ports/stm/Makefile index 9db283767e0ec..f85991cfedb9e 100755 --- a/ports/stm/Makefile +++ b/ports/stm/Makefile @@ -214,8 +214,11 @@ endif SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S = \ + shared/runtime/gchelper_thumb2.s \ st_driver/cmsis_device_$(MCU_SERIES_LOWER)/Source/Templates/gcc/startup_$(MCU_VARIANT_LOWER).s +SRC_C += shared/runtime/gchelper_native.c + ifneq ($(FROZEN_MPY_DIR),) FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py') FROZEN_MPY_MPY_FILES := $(addprefix $(BUILD)/,$(FROZEN_MPY_PY_FILES:.py=.mpy)) diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 386d1ee3d1e12..7715e77eab59b 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -46,6 +46,10 @@ extern void common_hal_mcu_enable_interrupts(void); #define MICROPY_VFS_LFS1 (0) #define MICROPY_VFS_LFS2 (0) +#ifndef MICROPY_GCREGS_SETJMP +#define MICROPY_GCREGS_SETJMP (0) +#endif + // Sorted alphabetically for easy finding. // // default is 128; consider raising to reduce fragmentation. From 57ce5dac643181e638f016c54d2238bc37b80b25 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 3 Feb 2026 16:17:55 -0800 Subject: [PATCH 11/14] Fix AFLAGS issues --- ports/raspberrypi/Makefile | 12 +++++------- ports/silabs/Makefile | 3 ++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index 097a4e7728f70..1784d325d89b7 100644 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -396,6 +396,8 @@ OTHER_PICO_FLAGS := \ -Wl,--wrap=__aeabi_uidivmod \ -Wl,--wrap=__aeabi_uldivmod +SRC_S = shared/runtime/gchelper_thumb1.s + ifeq ($(CHIP_VARIANT),RP2040) CFLAGS += \ -march=armv6-m \ @@ -422,8 +424,6 @@ SRC_S_UPPER_CHIP_VARIANT := \ sdk/src/rp2_common/pico_float/float_aeabi_rp2040.S \ sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S \ -SRC_S = shared/runtime/gchelper_thumb1.s - PICO_LDFLAGS += \ $(PICO_WRAP_FLOAT_AEABI_FLAGS) \ $(PICO_WRAP_FLOAT_SCI_FLAGS) \ @@ -436,12 +436,12 @@ UF2_ID = 0xE48BFF56 DOUBLE_EABI = rp2040 endif ifeq ($(CHIP_VARIANT),RP2350) -CFLAGS += \ - -march=armv8-m.main+fp+dsp \ +AFLAGS = -march=armv8-m.main+fp+dsp \ -mthumb \ - -mabi=aapcs-linux \ -mcpu=cortex-m33 \ -mfloat-abi=softfp +CFLAGS += $(AFLAGS) \ + -mabi=aapcs-linux # ARM Secure family id UF2_ID = 0xe48bff59 @@ -463,8 +463,6 @@ SRC_S_UPPER_CHIP_VARIANT := \ sdk/src/rp2_common/pico_float/float_sci_m33_vfp.S \ sdk/src/rp2_common/pico_float/float_common_m33.S \ -SRC_S = shared/runtime/gchelper_thumb2.s - PICO_LDFLAGS += $(PICO_WRAP_FLOAT_SCI_FLAGS) $(PICO_WRAP_DOUBLE_FLAGS) ifeq ($(CHIP_PACKAGE),A) diff --git a/ports/silabs/Makefile b/ports/silabs/Makefile index 97b0e4c597fde..34b6346d0d428 100644 --- a/ports/silabs/Makefile +++ b/ports/silabs/Makefile @@ -90,7 +90,8 @@ ifneq (,$(wildcard boards/$(BOARD)/sensor.c)) SRC_C += boards/$(BOARD)/sensor.c endif -SRC_S = shared/runtime/gchelper_thumb2.s +AFLAGS = -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard +SRC_S = shared/runtime/gchelper_thumb1.s SRC_C += shared/runtime/gchelper_native.c From c5066abd787d7b4ad971f105fb16d6eca601181e Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 4 Feb 2026 09:21:37 -0800 Subject: [PATCH 12/14] Update autogen file after merge --- ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml index 8a4eb3e6fb581..2cf9d7127d57e 100644 --- a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -51,6 +51,7 @@ gifio = false gnss = false hashlib = false i2cdisplaybus = true # Zephyr board has busio +i2cioexpander = false i2ctarget = false imagecapture = false ipaddress = false From f545763d79cb37950a0e39fb3b6f35fc9eae5818 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 4 Feb 2026 10:58:38 -0800 Subject: [PATCH 13/14] Remove cpu_regs. We're using the MP stuff now --- main.c | 1 - ports/analog/Makefile | 2 - ports/atmel-samd/Makefile | 3 - ports/cxd56/Makefile | 2 - ports/espressif/Makefile | 3 - ports/litex/Makefile | 3 +- ports/mimxrt10xx/Makefile | 3 +- ports/nordic/Makefile | 2 - ports/raspberrypi/Makefile | 1 - ports/renode/Makefile | 2 - ports/stm/Makefile | 4 +- .../zephyr-cp/cptools/build_circuitpython.py | 2 - supervisor/shared/cpu_regs.S | 102 ------------------ supervisor/shared/cpu_regs.h | 38 ------- 14 files changed, 3 insertions(+), 165 deletions(-) delete mode 100644 supervisor/shared/cpu_regs.S delete mode 100644 supervisor/shared/cpu_regs.h diff --git a/main.c b/main.c index d187c56cee46d..7b936488fb794 100644 --- a/main.c +++ b/main.c @@ -31,7 +31,6 @@ #include "supervisor/cpu.h" #include "supervisor/filesystem.h" #include "supervisor/port.h" -#include "supervisor/shared/cpu_regs.h" #include "supervisor/shared/reload.h" #include "supervisor/shared/safe_mode.h" #include "supervisor/shared/serial.h" diff --git a/ports/analog/Makefile b/ports/analog/Makefile index 4dd174d2f6cf2..6b86bd7039636 100644 --- a/ports/analog/Makefile +++ b/ports/analog/Makefile @@ -154,7 +154,6 @@ LINKERFILE = linking/$(MCU_VARIANT_LOWER)_cktpy.ld LDFLAGS += -nostartfiles -specs=nano.specs endif -SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S += $(STARTUPFILE) SRC_S += shared/runtime/gchelper_thumb2.s @@ -255,7 +254,6 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) # List of sources for qstr extraction diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index ac1d50123b941..0aaf6e3d849bf 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -315,8 +315,6 @@ else SRC_S += shared/runtime/gchelper_thumb2.s endif -SRC_S_UPPER = supervisor/shared/cpu_regs.S - OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_ASF:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) @@ -325,7 +323,6 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) QSTR_GLOBAL_REQUIREMENTS += $(HEADER_BUILD)/sdiodata.h diff --git a/ports/cxd56/Makefile b/ports/cxd56/Makefile index 6305a5b1d4bc5..c4526f31252ac 100644 --- a/ports/cxd56/Makefile +++ b/ports/cxd56/Makefile @@ -110,7 +110,6 @@ LDFLAGS = \ CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_CXD56 -DCFG_TUD_MIDI_RX_BUFSIZE=512 -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_MIDI_TX_BUFSIZE=512 -DCFG_TUD_CDC_TX_BUFSIZE=1024 -DCFG_TUD_MSC_BUFSIZE=512 $(CFLAGS_MOD) -SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S = shared/runtime/gchelper_thumb2.s SRC_C += shared/runtime/gchelper_native.c @@ -124,7 +123,6 @@ SRC_C += \ OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) ifeq ($(INTERNAL_LIBM),1) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index f83c2446df1a0..ebc4fa3a4e1f3 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -591,8 +591,6 @@ FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py') FROZEN_MPY_MPY_FILES := $(addprefix $(BUILD)/,$(FROZEN_MPY_PY_FILES:.py=.mpy)) endif -SRC_S_UPPER = supervisor/shared/cpu_regs.S - OBJ += $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) ifeq ($(INTERNAL_LIBM),1) @@ -601,7 +599,6 @@ endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) $(BUILD)/$(FATFS_DIR)/ff.o: COPT += -Os $(filter $(PY_BUILD)/../extmod/vfs_fat_%.o, $(PY_O)): COPT += -Os diff --git a/ports/litex/Makefile b/ports/litex/Makefile index 9ef34cd8ff8c1..940d751e3d92f 100644 --- a/ports/litex/Makefile +++ b/ports/litex/Makefile @@ -78,8 +78,7 @@ SRC_C += lib/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.c endif SRC_S_UPPER = \ - crt0-vexriscv.S \ - supervisor/shared/cpu_regs.S + crt0-vexriscv.S SRC_S = shared/runtime/gchelper_rv32i.s diff --git a/ports/mimxrt10xx/Makefile b/ports/mimxrt10xx/Makefile index 9cb1df8014520..74667fd3d6446 100644 --- a/ports/mimxrt10xx/Makefile +++ b/ports/mimxrt10xx/Makefile @@ -162,8 +162,7 @@ SRC_C += \ endif SRC_S_UPPER = \ - sdk/devices/$(CHIP_FAMILY)/gcc/startup_$(CHIP_CORE).S \ - supervisor/shared/cpu_regs.S + sdk/devices/$(CHIP_FAMILY)/gcc/startup_$(CHIP_CORE).S SRC_S = shared/runtime/gchelper_thumb2.s diff --git a/ports/nordic/Makefile b/ports/nordic/Makefile index cd1360b9fd425..0d8d8878f4ca1 100755 --- a/ports/nordic/Makefile +++ b/ports/nordic/Makefile @@ -146,7 +146,6 @@ SRC_C += $(SRC_DCD) $(patsubst %.c,$(BUILD)/%.o,$(SRC_DCD)): CFLAGS += -Wno-missing-prototypes endif # CIRCUITPY_USB_DEVICE -SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S = shared/runtime/gchelper_thumb2.s SRC_C += shared/runtime/gchelper_native.c @@ -159,7 +158,6 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) # nrfx uses undefined preprocessor variables quite casually, so we can't do diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index 1784d325d89b7..cef2806c87497 100644 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -675,7 +675,6 @@ SRC_S_UPPER = sdk/src/rp2_common/hardware_irq/irq_handler_chain.S \ sdk/src/rp2_common/pico_double/double_aeabi_$(DOUBLE_EABI).S \ sdk/src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S \ sdk/src/rp2_common/pico_crt0/crt0.S \ - supervisor/shared/cpu_regs.S \ $(SRC_S_UPPER_CHIP_VARIANT) ifeq ($(CIRCUITPY_PICODVI),1) diff --git a/ports/renode/Makefile b/ports/renode/Makefile index 40013ac4f661b..c4ce66194e9d1 100644 --- a/ports/renode/Makefile +++ b/ports/renode/Makefile @@ -45,7 +45,6 @@ SRC_C += \ background.c \ mphalport.c \ -SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S = shared/runtime/gchelper_thumb1.s SRC_C += shared/runtime/gchelper_native.c @@ -57,7 +56,6 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) $(BUILD)/%.o: $(BUILD)/%.S diff --git a/ports/stm/Makefile b/ports/stm/Makefile index f85991cfedb9e..c0e64e2339de1 100755 --- a/ports/stm/Makefile +++ b/ports/stm/Makefile @@ -212,9 +212,8 @@ ifneq ($(CIRCUITPY_USB),0) endif endif -SRC_S_UPPER = supervisor/shared/cpu_regs.S SRC_S = \ - shared/runtime/gchelper_thumb2.s \ + shared/runtime/gchelper_thumb1.s \ st_driver/cmsis_device_$(MCU_SERIES_LOWER)/Source/Templates/gcc/startup_$(MCU_VARIANT_LOWER).s SRC_C += shared/runtime/gchelper_native.c @@ -232,7 +231,6 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) endif OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) $(BUILD)/$(FATFS_DIR)/ff.o: COPT += -Os diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 83f80945f3e00..5d3c7d7515da5 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -552,8 +552,6 @@ async def build_circuitpython(): source_files.append(portdir / "common-hal/zephyr_kernel/__init__.c") # source_files.append(srcdir / "ports" / port / "peripherals" / "nrf" / "nrf52840" / "pins.c") - assembly_files.append(srcdir / "supervisor/shared/cpu_regs.S") - source_files.extend(assembly_files) objects = [] diff --git a/supervisor/shared/cpu_regs.S b/supervisor/shared/cpu_regs.S deleted file mode 100644 index 90e5367ed5808..0000000000000 --- a/supervisor/shared/cpu_regs.S +++ /dev/null @@ -1,102 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/shared/cpu_regs.h" - -#ifdef __arm__ -.syntax unified -.thumb -.text -.align 2 - -@ uint cpu_get_regs_and_sp(r0=uint regs[SAVED_REGISTER_COUNT]) -.global cpu_get_regs_and_sp -.thumb -.thumb_func -.type cpu_get_regs_and_sp, %function -cpu_get_regs_and_sp: -#if __ARM_ARCH_ISA_THUMB == 2 -@ store registers into given array -#ifdef __arm__ -stmia r0!, {r4-r11} -#endif -#if defined(__aarch64__) && __aarch64__ == 1 -#error "aarch64 not supported" -stmia r0!, {x19-x28} -#endif -#ifdef __ARM_FP -#ifdef __arm__ -vstmia r0!, {s16-s31} -#endif -#if defined(__aarch64__) && __aarch64__ == 1 -vst1.64 {d8-d15}, [r0], #16 -#endif -#endif -#endif -// Thumb 1 can only store directly from R0-R7. This is M0 and M23 mostly. -#if __ARM_ARCH_ISA_THUMB == 1 -str r4, [r0, #0] -str r5, [r0, #4] -str r6, [r0, #8] -str r7, [r0, #12] -push {r1} -mov r1, r8 -str r1, [r0, #16] -mov r1, r9 -str r1, [r0, #20] -mov r1, r10 -str r1, [r0, #24] -mov r1, r11 -str r1, [r0, #28] -mov r1, r12 -str r1, [r0, #32] -mov r1, r13 -str r1, [r0, #36] -pop {r1} -#endif - -@ return the sp -mov r0, sp -bx lr -#endif - -#ifdef __riscv -#if __riscv_xlen == 32 -.global cpu_get_regs_and_sp -.type cpu_get_regs_and_sp, %function -cpu_get_regs_and_sp: -sw s0, 0(a0) -sw s1, 4(a0) -sw s2, 8(a0) -sw s3, 12(a0) -sw s4, 16(a0) -sw s5, 20(a0) -sw s6, 24(a0) -sw s7, 28(a0) -sw s8, 32(a0) -sw s9, 36(a0) -sw s10, 40(a0) -sw s11, 44(a0) -#ifdef __riscv_vector -sw fs0, 48(a0) -sw fs1, 52(a0) -sw fs2, 56(a0) -sw fs3, 60(a0) -sw fs4, 64(a0) -sw fs5, 68(a0) -sw fs6, 72(a0) -sw fs7, 76(a0) -sw fs8, 80(a0) -sw fs9, 84(a0) -sw fs10, 88(a0) -sw fs11, 92(a0) -#endif -move a0, sp -ret -#else -#error "Unsupported RISC-V bit length" -#endif -#endif diff --git a/supervisor/shared/cpu_regs.h b/supervisor/shared/cpu_regs.h deleted file mode 100644 index 8243c2388cd9b..0000000000000 --- a/supervisor/shared/cpu_regs.h +++ /dev/null @@ -1,38 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#ifdef __arm__ -#define INTEGER_REGS 10 -#ifdef __ARM_FP -#define FLOATING_POINT_REGS 16 -#endif -#endif - -#ifdef __aarch64__ -#define INTEGER_REGS 10 -#ifdef __ARM_FP -#define FLOATING_POINT_REGS 8 -#endif -#endif - -#ifdef __riscv -#define INTEGER_REGS 12 -#ifdef __riscv_vector -#define FLOATING_POINT_REGS 12 -#endif -#endif - -#ifndef INTEGER_REGS -#define INTEGER_REGS 0 -#endif - -#ifndef FLOATING_POINT_REGS -#define FLOATING_POINT_REGS 0 -#endif - -#define SAVED_REGISTER_COUNT (INTEGER_REGS + FLOATING_POINT_REGS) From f8c06e39ed97a32b67e0816f53d62762d3b096f2 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 4 Feb 2026 10:58:53 -0800 Subject: [PATCH 14/14] Fix board builds --- ports/espressif/boards/m5stack_cores3_se/board.c | 4 ++-- ports/stm/boards/meowbit_v121/mpconfigboard.mk | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/espressif/boards/m5stack_cores3_se/board.c b/ports/espressif/boards/m5stack_cores3_se/board.c index cf06bd8488d86..bf5ccd17f36fe 100644 --- a/ports/espressif/boards/m5stack_cores3_se/board.c +++ b/ports/espressif/boards/m5stack_cores3_se/board.c @@ -46,8 +46,8 @@ static bool display_init(void) { common_hal_fourwire_fourwire_construct( bus, spi, - &pin_GPIO35, // DC - &pin_GPIO3, // CS + MP_OBJ_FROM_PTR(&pin_GPIO35), // DC + MP_OBJ_FROM_PTR(&pin_GPIO3), // CS NULL, // RST 40000000, // baudrate 0, // polarity diff --git a/ports/stm/boards/meowbit_v121/mpconfigboard.mk b/ports/stm/boards/meowbit_v121/mpconfigboard.mk index 74759abed5483..419f073b5ea2c 100644 --- a/ports/stm/boards/meowbit_v121/mpconfigboard.mk +++ b/ports/stm/boards/meowbit_v121/mpconfigboard.mk @@ -23,6 +23,7 @@ LD_FILE = boards/STM32F401xe_boot.ld # LD_FILE = boards/STM32F401xe_fs.ld CIRCUITPY_AESIO = 0 +CIRCUITPY_CODEOP = 0 CIRCUITPY_BITMAPFILTER = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BLEIO_HCI = 0 @@ -30,6 +31,7 @@ CIRCUITPY_EPAPERDISPLAY = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_I2CDISPLAYBUS = 0 CIRCUITPY_KEYPAD_DEMUX = 0 +CIRCUITPY_PIXELMAP = 0 CIRCUITPY_SHARPDISPLAY = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 CIRCUITPY_ULAB = 0