From af886f8aa10e600ca549bd1b7b22e2c4422b802e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 22 Apr 2026 10:53:35 +1000 Subject: [PATCH] ports/unix,windows: Add embedded romfs support with auto-mount. Add support for an embedded romfs image linked directly into the unix/windows binary. When built with ROMFS_IMG=path/to/romfs.img, the image is embedded as a .rodata section via objcopy and registered as a read-only filesystem at startup. Auto-mount the embedded romfs at /rom and (when present) prepend it to sys.path. If the romfs contains main.py or main.mpy, it is executed in preference to the file-system search after startup. The unix port also gains a 'make romfs ROMFS_DIR=...' target to build a romfs image from a directory using mpremote. The same plumbing is added to the windows port (with PE-COFF objcopy output and a separate vfs_rom_ioctl.c since the Windows variant uses MICROPY_VFS_ROM_IOCTL_USE_EXTERNAL=1 to avoid pulling the ioctl helper into main.c). Also adds MICROPY_ENABLE_COMPILER=0 guards in the unix main.c so the port can be built without the compiler when only pre-compiled .mpy modules are needed (relevant when shipping a frozen-only application binary). Signed-off-by: Andrew Leech --- ports/unix/Makefile | 31 ++ ports/unix/main.c | 282 ++++++++++++++++++- ports/unix/variants/mpconfigvariant_common.h | 2 +- ports/windows/Makefile | 32 ++- ports/windows/mpconfigport.h | 9 + ports/windows/vfs_rom_ioctl.c | 128 +++++++++ 6 files changed, 475 insertions(+), 9 deletions(-) create mode 100644 ports/windows/vfs_rom_ioctl.c diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 7d71fc3f955eb..c430e7c48388a 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -254,8 +254,26 @@ $(info Detected arm-linux-gnueabi-gcc. Disabling error message compression.) MICROPY_ROM_TEXT_COMPRESSION = 0 endif +# Build with embedded romfs by linking the binary directly +# Usage: make ROMFS_IMG=romfs.img +ifdef ROMFS_IMG +CFLAGS += -DMICROPY_ROMFS_EMBEDDED=1 +OBJ += $(BUILD)/romfs_data.o +endif + include $(TOP)/py/mkrules.mk +# Rule to build romfs_data.o from romfs.img (must be after mkrules.mk for OBJCOPY) +ifdef ROMFS_IMG +$(BUILD)/romfs_data.o: $(ROMFS_IMG) | $(BUILD) + $(Q)$(OBJCOPY) -I binary -O elf64-x86-64 -B i386:x86-64 \ + --rename-section .data=.rodata,alloc,load,readonly,data,contents \ + --add-section .note.GNU-stack=/dev/null \ + --redefine-sym _binary_$(subst .,_,$(subst /,_,$(ROMFS_IMG)))_start=romfs_embedded_data \ + --redefine-sym _binary_$(subst .,_,$(subst /,_,$(ROMFS_IMG)))_end=romfs_embedded_end \ + $< $@ +endif + .PHONY: test test_full_no_native test_full test//% test/% test-failures print-failures clean-failures test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py @@ -319,3 +337,16 @@ install: $(BUILD)/$(PROG) uninstall: -rm $(BINDIR)/$(PROG) + +# Build a romfs image from a directory +# Usage: make romfs ROMFS_DIR=path/to/directory +# Creates romfs.img in the current directory +romfs: + @if [ -z "$(ROMFS_DIR)" ]; then \ + echo "Error: ROMFS_DIR not specified"; \ + echo "Usage: make romfs ROMFS_DIR=path/to/directory"; \ + exit 1; \ + fi + $(PYTHON) -m mpremote romfs build $(ROMFS_DIR) + mv $(notdir $(ROMFS_DIR)).romfs romfs.img + diff --git a/ports/unix/main.c b/ports/unix/main.c index 9e9704aa801db..d4e0ff08558de 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -57,9 +57,15 @@ #include "stack_size.h" #include "shared/runtime/pyexec.h" +#if MICROPY_MODULE_FROZEN +#include "py/frozenmod.h" +#endif + // Command line options, with their defaults bool mp_compile_only = false; +#if MICROPY_ENABLE_COMPILER static uint emit_opt = MP_EMIT_OPT_NONE; +#endif #if MICROPY_ENABLE_GC // Heap size of GC heap (if enabled) @@ -110,6 +116,7 @@ static int handle_uncaught_exception(mp_obj_base_t *exc) { return 1; } +#if MICROPY_ENABLE_COMPILER #define LEX_SRC_STR (1) #define LEX_SRC_STDIN (4) @@ -276,7 +283,9 @@ static int do_str(const char *str) { int ret = pyexec_vstr(&vstr, true); return convert_pyexec_result(ret); } +#endif // MICROPY_ENABLE_COMPILER +#if !MICROPY_FROZEN_MAIN_MODULE static void print_help(char **argv) { printf( "usage: %s [] [-X ] [-c | -m | ]\n" @@ -316,6 +325,7 @@ static void print_help(char **argv) { printf(" (none)\n"); } } +#endif static int invalid_args(void) { fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n"); @@ -329,10 +339,12 @@ static void pre_process_options(int argc, char **argv) { if (strcmp(argv[a], "-c") == 0 || strcmp(argv[a], "-m") == 0) { break; // Everything after this is a command/module and arguments for it } + #if !MICROPY_FROZEN_MAIN_MODULE if (strcmp(argv[a], "-h") == 0) { print_help(argv); exit(0); } + #endif if (strcmp(argv[a], "--version") == 0) { printf(MICROPY_BANNER_NAME_AND_VERSION "; " MICROPY_BANNER_MACHINE "\n"); exit(0); @@ -342,6 +354,7 @@ static void pre_process_options(int argc, char **argv) { exit(invalid_args()); } if (0) { + #if MICROPY_ENABLE_COMPILER } else if (strcmp(argv[a + 1], "compile-only") == 0) { mp_compile_only = true; } else if (strcmp(argv[a + 1], "emit=bytecode") == 0) { @@ -352,6 +365,7 @@ static void pre_process_options(int argc, char **argv) { } else if (strcmp(argv[a + 1], "emit=viper") == 0) { emit_opt = MP_EMIT_OPT_VIPER; #endif + #endif // MICROPY_ENABLE_COMPILER #if MICROPY_ENABLE_GC } else if (strncmp(argv[a + 1], "heapsize=", sizeof("heapsize=") - 1) == 0) { char *end; @@ -407,11 +421,13 @@ static void pre_process_options(int argc, char **argv) { } } +#if MICROPY_ENABLE_COMPILER static void set_sys_argv(char *argv[], int argc, int start_arg) { for (int i = start_arg; i < argc; i++) { mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i]))); } } +#endif #if MICROPY_PY_SYS_EXECUTABLE extern mp_obj_str_t mp_sys_executable_obj; @@ -495,12 +511,14 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_init(); + #if MICROPY_ENABLE_COMPILER #if MICROPY_EMIT_NATIVE // Set default emitter options MP_STATE_VM(default_emit_opt) = emit_opt; #else (void)emit_opt; #endif + #endif #if MICROPY_VFS_POSIX { @@ -563,6 +581,12 @@ MP_NOINLINE int main_(int argc, char **argv) { } } + #if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL + // Add "/rom" and "/rom/lib" to sys.path if romfs is mounted + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom)); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom_slash_lib)); + #endif + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); #if defined(MICROPY_UNIX_COVERAGE) @@ -585,9 +609,169 @@ MP_NOINLINE int main_(int argc, char **argv) { sys_set_excecutable(argv[0]); #endif + #if MICROPY_ENABLE_COMPILER const int NOTHING_EXECUTED = -2; - int ret = NOTHING_EXECUTED; + #endif + int ret = 0; + #if MICROPY_ENABLE_COMPILER bool inspect = false; + #endif + + // Check if a frozen or romfs main module exists and should be run. + // Priority: 1) frozen main module, 2) /rom/main.py or /rom/main.mpy + // It runs when no -c, -m, -h, or script file is specified. + bool run_main = false; + bool main_is_frozen = false; + const char *main_path = "main"; // For frozen or import-based execution + + #if MICROPY_MODULE_FROZEN + { + int frozen_type; + void *frozen_data; + // Try main.py first, then main.mpy (frozen modules use filename with extension) + mp_import_stat_t frozen_stat = mp_find_frozen_module("main.py", &frozen_type, &frozen_data); + if (frozen_stat != MP_IMPORT_STAT_FILE) { + frozen_stat = mp_find_frozen_module("main.mpy", &frozen_type, &frozen_data); + } + if (frozen_stat == MP_IMPORT_STAT_FILE) { + run_main = true; + main_is_frozen = true; + } + } + #endif + + #if MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL + if (!run_main) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_import_stat_t stat = mp_vfs_import_stat("/rom/main.py"); + if (stat == MP_IMPORT_STAT_FILE) { + run_main = true; + main_path = "/rom/main.py"; + } else { + stat = mp_vfs_import_stat("/rom/main.mpy"); + if (stat == MP_IMPORT_STAT_FILE) { + run_main = true; + main_path = "/rom/main.mpy"; + } + } + nlr_pop(); + } + } + #endif + + if (run_main) { + // Check if any argument would bypass main + // -c and -m always bypass; -h bypasses only for romfs main (not frozen) + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strcmp(argv[a], "-c") == 0 || strcmp(argv[a], "-m") == 0) { + run_main = false; + break; + } else if (!main_is_frozen && strcmp(argv[a], "-h") == 0) { + run_main = false; + break; + } else if (strcmp(argv[a], "-X") == 0) { + if (a + 1 < argc) { + a++; + } + } + } + } + } + + if (run_main) { + // Process flags that affect execution + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + #if MICROPY_ENABLE_COMPILER + if (strcmp(argv[a], "-i") == 0) { + inspect = true; + } else + #endif + if (strcmp(argv[a], "-X") == 0 && a + 1 < argc) { + a++; + #if MICROPY_DEBUG_PRINTERS + } else if (strcmp(argv[a], "-v") == 0) { + mp_verbose_flag++; + #endif + #if MICROPY_ENABLE_COMPILER + } else if (strncmp(argv[a], "-O", 2) == 0) { + if (unichar_isdigit(argv[a][2])) { + MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; + } else { + MP_STATE_VM(mp_optimise_value) = 0; + for (char *p = argv[a] + 1; *p && *p == 'O'; p++, MP_STATE_VM(mp_optimise_value)++) {; + } + } + #endif + } else { + break; + } + } else { + break; + } + } + + // Build sys.argv: [main_path, remaining_args...] + mp_obj_list_append(mp_sys_argv, mp_obj_new_str(main_path, strlen(main_path))); + bool in_positional = false; + for (int a = 1; a < argc; a++) { + if (!in_positional && argv[a][0] == '-') { + #if MICROPY_ENABLE_COMPILER + if (strcmp(argv[a], "-i") == 0) { + continue; + } else + #endif + #if MICROPY_DEBUG_PRINTERS + if (strcmp(argv[a], "-v") == 0) { + continue; + } else + #endif + #if MICROPY_ENABLE_COMPILER + if (strncmp(argv[a], "-O", 2) == 0) { + continue; + } else + #endif + if (strcmp(argv[a], "-X") == 0 && a + 1 < argc) { + a++; + continue; + } + in_positional = true; + } else { + in_positional = true; + } + if (in_positional) { + mp_obj_list_append(mp_sys_argv, mp_obj_new_str_from_cstr(argv[a])); + } + } + + // Execute main module + if (main_is_frozen || (strlen(main_path) > 4 && strcmp(main_path + strlen(main_path) - 4, ".mpy") == 0)) { + // Frozen or .mpy: use import mechanism + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t import_args[4]; + import_args[0] = MP_OBJ_NEW_QSTR(MP_QSTR_main); + import_args[1] = import_args[2] = mp_const_none; + import_args[3] = mp_const_false; + mp_builtin___import__(4, import_args); + nlr_pop(); + ret = 0; + } else { + ret = handle_uncaught_exception(nlr.ret_val); + } + } + #if MICROPY_ENABLE_COMPILER + else { + // .py file in romfs: use lexer + ret = do_file(main_path); + } + #endif + goto done_execution; + } + + #if MICROPY_ENABLE_COMPILER for (int a = 1; a < argc; a++) { if (argv[a][0] == '-') { if (strcmp(argv[a], "-i") == 0) { @@ -693,11 +877,18 @@ MP_NOINLINE int main_(int argc, char **argv) { break; } } + #endif // MICROPY_ENABLE_COMPILER + + #if MICROPY_MODULE_FROZEN || (MICROPY_VFS_ROM && MICROPY_VFS_ROM_IOCTL) +done_execution: + #endif + #if MICROPY_ENABLE_COMPILER const char *inspect_env = getenv("MICROPYINSPECT"); if (inspect_env && inspect_env[0] != '\0') { inspect = true; } + if (ret == NOTHING_EXECUTED || inspect) { if (isatty(0) || inspect) { prompt_read_history(); @@ -707,6 +898,7 @@ MP_NOINLINE int main_(int argc, char **argv) { ret = execute_from_lexer(LEX_SRC_STDIN, NULL, MP_PARSE_FILE_INPUT, false); } } + #endif // MICROPY_ENABLE_COMPILER #if MICROPY_PY_SYS_SETTRACE MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; @@ -769,21 +961,97 @@ void nlr_jump_fail(void *val) { exit(1); } -#if MICROPY_VFS_ROM_IOCTL +#if MICROPY_VFS_ROM_IOCTL && !MICROPY_VFS_ROM_IOCTL_USE_EXTERNAL + +// RomFS image buffer and metadata +static const uint8_t *romfs_buf = NULL; +static size_t romfs_size = 0; +static mp_obj_t romfs_memoryview = MP_OBJ_NULL; -static uint8_t romfs_buf[4] = { 0xd2, 0xcd, 0x31, 0x00 }; // empty ROMFS -static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, sizeof(romfs_buf), romfs_buf); +#if MICROPY_ROMFS_EMBEDDED +// Embedded romfs data - symbols provided by objcopy from romfs.img +// Build will fail to link if ROMFS_IMG was specified but object not provided +extern const uint8_t romfs_embedded_data[]; +extern const uint8_t romfs_embedded_end[]; + +static void load_romfs_image(void) { + if (romfs_buf != NULL) { + return; + } + romfs_buf = romfs_embedded_data; + romfs_size = romfs_embedded_end - romfs_embedded_data; +} + +#else +// File-loading mode for development - load romfs.img from current directory +static const uint8_t empty_romfs[4] = { 0xd2, 0xcd, 0x31, 0x00 }; +static uint8_t *romfs_file_buf = NULL; + +static void load_romfs_image(void) { + if (romfs_buf != NULL) { + return; + } + + FILE *f = fopen("romfs.img", "rb"); + if (f == NULL) { + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + fseek(f, 0, SEEK_END); + long file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (file_size <= 0) { + fclose(f); + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + romfs_file_buf = malloc((size_t)file_size); + if (romfs_file_buf == NULL) { + fclose(f); + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + size_t read_size = fread(romfs_file_buf, 1, (size_t)file_size, f); + fclose(f); + + if (read_size != (size_t)file_size) { + free(romfs_file_buf); + romfs_file_buf = NULL; + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + romfs_buf = romfs_file_buf; + romfs_size = (size_t)file_size; +} +#endif // MICROPY_ROMFS_EMBEDDED mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + load_romfs_image(); + switch (mp_obj_get_int(args[0])) { case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: return MP_OBJ_NEW_SMALL_INT(1); - case MP_VFS_ROM_IOCTL_GET_SEGMENT: - return MP_OBJ_FROM_PTR(&romfs_obj); + case MP_VFS_ROM_IOCTL_GET_SEGMENT: { + // Create memoryview on first request + if (romfs_memoryview == MP_OBJ_NULL) { + mp_obj_array_t *view = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', romfs_size, (void *)romfs_buf)); + romfs_memoryview = MP_OBJ_FROM_PTR(view); + } + return romfs_memoryview; + } } return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); } -#endif +#endif // MICROPY_VFS_ROM_IOCTL && !MICROPY_VFS_ROM_IOCTL_USE_EXTERNAL diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 1ac59c95572dd..72c1c476b205e 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -117,4 +117,4 @@ #define MICROPY_PY_MACHINE_PIN_BASE (1) #define MICROPY_VFS_ROM (1) -#define MICROPY_VFS_ROM_IOCTL (0) +#define MICROPY_VFS_ROM_IOCTL (1) diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 8d371ed8ac369..0ad3211afd1e5 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -69,6 +69,7 @@ SRC_C = \ realpath.c \ init.c \ fmode.c \ + vfs_rom_ioctl.c \ $(wildcard $(VARIANT_DIR)/*.c) SHARED_SRC_C += $(addprefix shared/,\ @@ -104,9 +105,26 @@ ifeq ($(shell $(CC) -dumpmachine),i686-w64-mingw32) CFLAGS += -msse -mfpmath=sse -march=pentium4 endif +# Build with embedded romfs by linking the binary directly +# Usage: make ROMFS_IMG=romfs.img +ifdef ROMFS_IMG +CFLAGS += -DMICROPY_ROMFS_EMBEDDED=1 +OBJ += $(BUILD)/romfs_data.o +endif + include $(TOP)/py/mkrules.mk -.PHONY: test test_full +# Rule to build romfs_data.o from romfs.img (must be after mkrules.mk for OBJCOPY) +ifdef ROMFS_IMG +$(BUILD)/romfs_data.o: $(ROMFS_IMG) | $(BUILD) + $(Q)$(OBJCOPY) -I binary -O pe-x86-64 -B i386:x86-64 \ + --rename-section .data=.rodata,alloc,load,readonly,data,contents \ + --redefine-sym _binary_$(subst .,_,$(subst /,_,$(ROMFS_IMG)))_start=romfs_embedded_data \ + --redefine-sym _binary_$(subst .,_,$(subst /,_,$(ROMFS_IMG)))_end=romfs_embedded_end \ + $< $@ +endif + +.PHONY: test test_full romfs # Note for recent gcc versions like 13.2: # - mingw64-x86_64 gcc builds will pass the math_domain_special test @@ -126,3 +144,15 @@ $(BUILD)/$(PROG): $(BUILD)/micropython.res $(BUILD)/%.res: %.rc $(ECHO) "WINDRES $<" $(Q)$(WINDRES) $< -O coff -o $@ + +# Build a romfs image from a directory +# Usage: make romfs ROMFS_DIR=path/to/directory +# Creates romfs.img in the current directory +romfs: + @if [ -z "$(ROMFS_DIR)" ]; then \ + echo "Error: ROMFS_DIR not specified"; \ + echo "Usage: make romfs ROMFS_DIR=path/to/directory"; \ + exit 1; \ + fi + $(PYTHON) -m mpremote romfs build $(ROMFS_DIR) + mv $(notdir $(ROMFS_DIR)).romfs romfs.img diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 1edcd0eded155..141e5864c4ca0 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -79,6 +79,15 @@ #endif #define MICROPY_VFS (1) #define MICROPY_VFS_POSIX (1) +#ifndef MICROPY_VFS_ROM +#define MICROPY_VFS_ROM (1) +#endif +#ifndef MICROPY_VFS_ROM_IOCTL +#define MICROPY_VFS_ROM_IOCTL (1) +#endif +#ifndef MICROPY_VFS_ROM_IOCTL_USE_EXTERNAL +#define MICROPY_VFS_ROM_IOCTL_USE_EXTERNAL (1) // Use vfs_rom_ioctl.c instead of main.c +#endif #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) #define MICROPY_PY_DELATTR_SETATTR (1) diff --git a/ports/windows/vfs_rom_ioctl.c b/ports/windows/vfs_rom_ioctl.c new file mode 100644 index 0000000000000..39c37fd9613e8 --- /dev/null +++ b/ports/windows/vfs_rom_ioctl.c @@ -0,0 +1,128 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/objarray.h" +#include "extmod/vfs.h" + +#if MICROPY_VFS_ROM_IOCTL + +#include + +// RomFS image buffer and metadata +static const uint8_t *romfs_buf = NULL; +static size_t romfs_size = 0; +static mp_obj_t romfs_memoryview = MP_OBJ_NULL; + +#if MICROPY_ROMFS_EMBEDDED +// Embedded romfs data - symbols provided by objcopy from romfs.img +// Build will fail to link if ROMFS_IMG was specified but object not provided +extern const uint8_t romfs_embedded_data[]; +extern const uint8_t romfs_embedded_end[]; + +static void load_romfs_image(void) { + if (romfs_buf != NULL) { + return; + } + romfs_buf = romfs_embedded_data; + romfs_size = romfs_embedded_end - romfs_embedded_data; +} + +#else +// File-loading mode for development - load romfs.img from current directory +static const uint8_t empty_romfs[4] = { 0xd2, 0xcd, 0x31, 0x00 }; +static uint8_t *romfs_file_buf = NULL; + +static void load_romfs_image(void) { + if (romfs_buf != NULL) { + return; + } + + FILE *f = fopen("romfs.img", "rb"); + if (f == NULL) { + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + fseek(f, 0, SEEK_END); + long file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (file_size <= 0) { + fclose(f); + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + // Use m_new_maybe to avoid exception on allocation failure + romfs_file_buf = m_new_maybe(uint8_t, (size_t)file_size); + if (romfs_file_buf == NULL) { + fclose(f); + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + size_t read_size = fread(romfs_file_buf, 1, (size_t)file_size, f); + fclose(f); + + if (read_size != (size_t)file_size) { + m_del(uint8_t, romfs_file_buf, (size_t)file_size); + romfs_file_buf = NULL; + romfs_size = sizeof(empty_romfs); + romfs_buf = empty_romfs; + return; + } + + romfs_buf = romfs_file_buf; + romfs_size = (size_t)file_size; +} +#endif // MICROPY_ROMFS_EMBEDDED + +mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { + load_romfs_image(); + + switch (mp_obj_get_int(args[0])) { + case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS: + return MP_OBJ_NEW_SMALL_INT(1); + + case MP_VFS_ROM_IOCTL_GET_SEGMENT: { + // Create memoryview on first request + if (romfs_memoryview == MP_OBJ_NULL) { + mp_obj_array_t *view = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', romfs_size, (void *)romfs_buf)); + romfs_memoryview = MP_OBJ_FROM_PTR(view); + } + return romfs_memoryview; + } + } + + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); +} + +#endif // MICROPY_VFS_ROM_IOCTL