From 86b6640350ecd616fc5a02bae98d37ede7a9e153 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:58:48 +0000 Subject: [PATCH 1/2] Initial plan From ac80378c6f018787723713a921791ad27964b55b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:15:09 +0000 Subject: [PATCH 2/2] fix: re-enable config_fs_size guard in mount_config_filesystem and set FLASH_FS env vars The if(config_fs_size > 0) check was commented out, causing dmffs to be invoked even when no config filesystem was embedded in ROM (size=0). dmffs reads flash location from FLASH_FS_ADDR/FLASH_FS_SIZE env vars and would hang when those were unset and size was 0, causing the Renode CI test to timeout without ever printing DMOD-Boot started. Changes: - Re-enable size guard so dmffs is only mounted when config_fs_size > 0 - Set FLASH_FS_ADDR and FLASH_FS_SIZE env vars via dmenv_seti before calling dmvfs_mount_fs("dmffs") - this is what dmffs actually reads - Thread dmenv_ctx through mount_embedded_filesystems and mount_config_filesystem function signatures - Remove now-unused mount_opts snprintf - Add docs/dmgpio-renode-hard-fault.md documenting the hard fault that occurs when dmgpio_port accesses STM32 GPIO hardware in Renode after dmdevfs is mounted with a proper config filesystem Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- docs/dmgpio-renode-hard-fault.md | 135 +++++++++++++++++++++++++++++++ src/main.c | 30 +++---- 2 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 docs/dmgpio-renode-hard-fault.md diff --git a/docs/dmgpio-renode-hard-fault.md b/docs/dmgpio-renode-hard-fault.md new file mode 100644 index 0000000..47bcae9 --- /dev/null +++ b/docs/dmgpio-renode-hard-fault.md @@ -0,0 +1,135 @@ +# Bug Report: Hard Fault When Mounting dmgpio via dmdevfs in Renode + +## Summary + +After fixing the infinite-loop in `mount_config_filesystem()` (see companion commit), the firmware +running in Renode triggers a **hard fault** at the point where `dmdevfs` initialises the `dmgpio` +device driver. This document records the investigation findings so that the relevant bug can be +filed against the `dmgpio` or `dmdevfs` repository. + +--- + +## Environment + +| Item | Value | +|------|-------| +| Board | STM32F746G Discovery (`stm32f746g-disco`) | +| Renode platform | `platforms/boards/stm32f7_discovery-bb.repl` | +| dmgpio version | `0.4` (`dmgpio_port-v0.4-stm32f7`) | +| dmod-boot branch | `master` (commit `41e1b11…`) | + +--- + +## How to Reproduce + +1. Build `dmod-boot` for the `stm32f746g-disco` board **with** an embedded config + filesystem that contains `board/stm32f746g-disco.ini` (i.e. a proper dmffs image): + + ```bash + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DBOARD=stm32f746g-disco \ + -DDMBOOT_EMULATION=ON \ + -DDMBOOT_CONFIG_DIR= \ + -S . -B build + cmake --build build + cmake --build build --target install-firmware + cmake --build build --target connect # start Renode + cmake --build build --target monitor-gdb # observe output + ``` + +2. Observe that the firmware log shows `dmgpio_port` being initialised (via `dmdevfs`) and + then the `HardFault_Handler` fires — the log output stops with no further progress. + +--- + +## Root Cause Analysis + +### Boot sequence up to the fault + +``` +main() + └─ mount_embedded_filesystems() + ├─ dmvfs_mount_fs("dmramfs", "/", NULL) – OK + ├─ mount_config_filesystem() + │ └─ dmvfs_mount_fs("dmffs", "/configs", …) – OK (when config fs is embedded) + └─ dmvfs_mount_fs("dmdevfs", "/dev", "/configs") – triggers hard fault +``` + +`dmdevfs` is initialised with `/configs` as the configuration directory. During its mount +procedure it reads `board/stm32f746g-disco.ini` and calls the `dmgpio_port` driver's +initialisation entry point. + +### What dmgpio_port does during initialisation + +`dmgpio_port` (STM32F7 GPIO port driver, version 0.4) performs the following steps in its +`init` / `begin_configuration` / `finish_configuration` path: + +1. Enables GPIO port clocks by writing to RCC AHB1ENR (`0x40023830`). +2. Configures GPIO pin modes by writing to GPIOx_MODER registers + (e.g. GPIOB at `0x40020400`, GPIOC at `0x40020800`, GPIOI at `0x40022000`, etc.). +3. Optionally configures SYSCFG EXTICR registers for interrupt routing. + +### The fault in Renode + +Renode's `stm32f7_discovery-bb.repl` platform does include GPIO peripheral models, **but** +certain register accesses can trigger a hard fault because: + +* **Unimplemented peripheral region**: The specific GPIO port address used by + `dmgpio_port` for the STM32F746G Discovery may not be fully modelled. Accesses to + unmapped or partially-modelled MMIO addresses in Renode result in a CPU exception. +* **Register access before clock enable**: If `dmgpio_port` reads a GPIO register before + confirming the clock is enabled (which the Renode model enforces differently from real + hardware), the emulated peripheral may return an invalid value causing the driver to + enter an invalid state or fault. +* **Missing peripheral in `.repl`**: `stm32f7_discovery-bb.repl` may omit specific + peripherals that `dmgpio_port` requires (e.g. SYSCFG, a specific GPIO bank, or the + full EXTI controller). + +The hard fault fires inside `dmvfs_mount_fs("dmdevfs", "/dev", "/configs")` before +`dmlog_puts(ctx, "DMOD-Boot started\n")` is ever reached, so the Renode CI test fails. + +--- + +## Proposed Fix (for dmgpio / dmdevfs) + +**Option A – dmgpio_port** + +`dmgpio_port` should gracefully handle the case where the underlying peripheral is not +accessible (e.g. because the code is running in an emulator that does not support the +peripheral). Suggested changes: + +* Check the `DMBOOT_EMULATION` environment variable on startup. If set to `1`, skip any + hardware-register access that is not strictly required for emulation testing. +* OR: wrap low-level register reads/writes in a `__attribute__((weak))` HAL layer so that + platform ports (including Renode stubs) can provide no-op implementations. + +**Option B – dmdevfs** + +`dmdevfs` should not hard-fault when a device driver's `init` call fails or causes an +exception. Suggested change: + +* Catch / tolerate driver initialisation failures (return an error code from + `dmdevfs_mount` instead of propagating the exception to the caller). +* This makes `dmdevfs` robust against partially-emulated hardware without requiring + every driver to be emulation-aware. + +--- + +## Relevant Files + +| Repository | File | Line(s) | +|------------|------|---------| +| `dmod-boot` | `src/main.c` | `mount_embedded_filesystems()` → `dmvfs_mount_fs("dmdevfs", …)` | +| `dmod-boot` | `modules/modules.dmd` / `configs/board/stm32f746g-disco/flash.dmd` | `dmgpio board/stm32f746g-disco.ini` | +| `dmgpio` | `dmgpio_port` v0.4 | driver init / `begin_configuration` | +| `dmdevfs` | mount procedure | driver registration / init call | + +--- + +## Workaround (do NOT use in production) + +Removing `dmgpio board/stm32f746g-disco.ini` from +`configs/board/stm32f746g-disco/flash.dmd` prevents the driver from being loaded and +avoids the fault during emulation tests — **but this is a workaround, not a fix**, and +it means GPIO is unavailable at runtime. The proper fix must be in `dmgpio` or +`dmdevfs` as described above. diff --git a/src/main.c b/src/main.c index f1cb26d..1541ca8 100644 --- a/src/main.c +++ b/src/main.c @@ -267,23 +267,23 @@ static void setup_embedded_user_data(dmenv_ctx_t dmenv_ctx) } } -static void mount_config_filesystem(void) +static void mount_config_filesystem(dmenv_ctx_t env_ctx) { void* config_fs_start = &__config_fs_start; void* config_fs_end = &__config_fs_end; size_t config_fs_size = (size_t)((uintptr_t)config_fs_end - (uintptr_t)config_fs_start); - // if(config_fs_size > 0) + if(config_fs_size > 0) { DMOD_LOG_INFO("Config filesystem found in ROM: addr=0x%X, size=%u bytes\n", (uintptr_t)config_fs_start, (unsigned int)config_fs_size); + + // dmffs reads flash location from FLASH_FS_ADDR and FLASH_FS_SIZE environment + // variables. Set these before mounting so dmffs can find the embedded filesystem image. + dmenv_seti(env_ctx, "FLASH_FS_ADDR", (uint32_t)(uintptr_t)config_fs_start); + dmenv_seti(env_ctx, "FLASH_FS_SIZE", (uint32_t)config_fs_size); - // Mount the dmffs filesystem at /configs/ - char mount_opts[128]; - Dmod_SnPrintf(mount_opts, sizeof(mount_opts), "flash_addr=0x%X;flash_size=%u", - (uintptr_t)config_fs_start, (unsigned int)config_fs_size); - - if(dmvfs_mount_fs("dmffs", "/configs", mount_opts)) + if(dmvfs_mount_fs("dmffs", "/configs", NULL)) { DMOD_LOG_INFO("Config filesystem mounted at /configs/\n"); } @@ -292,16 +292,16 @@ static void mount_config_filesystem(void) DMOD_LOG_ERROR("Failed to mount config filesystem at /configs/\n"); } } - // else - // { - // DMOD_LOG_INFO("No config filesystem embedded in ROM\n"); - // } + else + { + DMOD_LOG_INFO("No config filesystem embedded in ROM\n"); + } } -static void mount_embedded_filesystems(void) +static void mount_embedded_filesystems(dmenv_ctx_t env_ctx) { dmvfs_mount_fs("dmramfs", "/", NULL); - mount_config_filesystem(); + mount_config_filesystem(env_ctx); dmvfs_mount_fs("dmdevfs", "/dev", "/configs"); } @@ -486,7 +486,7 @@ int main(int argc, char** argv) Dmod_Context_t* mainModule = load_embedded_modules_dmp(); // Mount embedded filesystems - mount_embedded_filesystems(); + mount_embedded_filesystems(dmenv_ctx); // Load startup.dmp if embedded in ROM load_embedded_startup_dmp();