Skip to content

Switch QEMU from NETDUINOPLUS2 to B-L475E-IOT01A#185

Merged
jserv merged 1 commit intomasterfrom
qemu-target
Mar 25, 2026
Merged

Switch QEMU from NETDUINOPLUS2 to B-L475E-IOT01A#185
jserv merged 1 commit intomasterfrom
qemu-target

Conversation

@jserv
Copy link
Copy Markdown
Member

@jserv jserv commented Mar 25, 2026

This replaces Netduino Plus 2 QEMU target with B-L475E-IOT01A Discovery (STM32L475VG) to gain proper FPU and MPU emulation. This eliminates the mock infrastructure (mpu_mock, scb_mock, safe_mmio) that was papering over the old target's incomplete peripheral emulation.

Platform changes:

  • Add STM32L4 platform support (registers, GPIO, RCC, USART, NVIC, linker scripts with correct L4 memory map and no CCM RAM)
  • Fix L4 register addresses: USART1 at 0x40013800, RCC enable registers at correct L4 offsets, GPIO clocks on AHB2 (not AHB1), PLL source set to MSI (not HSE), USART_IT_CR offsets for L4
  • Add full L4 USART register ops (ISR/RDR/TDR layout)
  • Add ARM semihosting for console I/O under QEMU

FPU support:

  • Fix irq_restore: single asm block with __builtin_offsetof prevents compiler from reusing clobbered registers after ldm {r4-r11}
  • Add -mgeneral-regs-only to kernel CFLAGS to prevent GCC from emitting VFP instructions for non-FP operations in handler mode
  • Add FPU lazy context save/restore (d8-d15) in PendSV handler

IPC fix:

  • Fix L4_Ipc naked function: reload UTCB MR pointer from current_utcb after SVC return instead of reusing LR (which the exception frame restores to the caller's return address)

KDB:

  • Add USART1 RX interrupt path for KDB input under QEMU semihosting (CONFIG_KDB_UART_INPUT, gated on CONFIG_QEMU)
  • Improve hard fault handler to read correct stack (MSP vs PSP)

Build/test:

  • Update qemu-test.py: auto-detect board, add -semihosting for L4, fail on unsupported boards instead of silent fallback
  • Remove Netduino Plus 2 board, STM32F1 platform, mock infrastructure

Summary by cubic

Switches QEMU to the B-L475E-IOT01A (STM32L475VG) for accurate FPU/MPU emulation, adds full STM32L4 support with a semihosting console, and removes the Netduino Plus 2 target and mock peripherals. This improves kernel correctness and makes CI and local testing faster and simpler.

  • New Features

    • Adds STM32L4 platform (registers, GPIO, RCC, NVIC, USART, linker scripts) with correct MSI-based clocks and L4 USART layout; new platform/stm32l4/usart.c.
    • Defaults to QEMU -M b-l475e-iot01a with semihosting I/O; Kconfig adds STDIO_SEMIHOSTING and DEBUG_DEV_SEMIHOSTING and defaults to semihosting under QEMU; optional USART1 RX for KDB input; new helpers scripts/qemu-kdb.sh and scripts/qemu-kdb-auto.py.
    • Hardens FPU/IRQ: fixes irq_restore with a single asm block, adds lazy d8–d15 save/restore, and builds kernel with -mgeneral-regs-only; early CCR setup enables STKALIGN and clears UNALIGN_TRP.
    • Improves fault/KDB/IPC: correct MSP/PSP stack selection, KDB notification handling with a semihosting-backed debug device, and a fixed L4_Ipc naked path that reloads the UTCB MR pointer; moves t_pager into TCB with UTCB sync for unprivileged callers.
    • CI/Tooling: adds qemu-build.yml to build and release a minimal QEMU; build/test jobs download prebuilt QEMU v10.2.2; scripts/qemu-test.py auto-detects the board and enables semihosting; POSIX job trims options to fit 96KB SRAM; docs and workflows updated to b-l475e-iot01a.
    • Cleans up: removes STM32F1 platform, Netduino Plus 2 board, and mpu_mock, scb_mock, safe_mmio.
  • Migration

    • Build/run with board b-l475e-iot01a. QEMU: qemu-system-arm -M b-l475e-iot01a -nographic -semihosting -serial mon:stdio -kernel build/b-l475e-iot01a/f9.elf.
    • Console defaults to semihosting; for interactive KDB, enable CONFIG_KDB_UART_INPUT or use scripts/qemu-kdb.sh/scripts/qemu-kdb-auto.py to trigger ?.

Written for commit 9e4b78c. Summary will update on new commits.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

28 issues found across 83 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="kernel/init.c">

<violation number="1" location="kernel/init.c:65">
P1: Do not dereference `fault_pc` in the HardFault handler; reading `inst[0]` can cause a nested fault and lock up fault handling.</violation>
</file>

<file name="include/platform/stm32l4/systick.h">

<violation number="1" location="include/platform/stm32l4/systick.h:6">
P1: `CORE_CLOCK` is set to 168MHz in the STM32L4 header, but L4 clock init configures 80MHz, so SysTick timing calculations will be wrong.</violation>
</file>

<file name="kernel/memory.c">

<violation number="1" location="kernel/memory.c:381">
P2: The loop limit was reduced from `i < 8` to `i < 7`, permanently wasting MPU region 7. Since the Cortex-M MPU only has 8 regions and region 7 is not reserved for any other purpose (the cleanup loop still clears it to NULL), this appears to be an unintended off-by-one. The stack fpage loop above still uses `i < 8` as its limit, creating an inconsistency.</violation>
</file>

<file name="board/Kconfig">

<violation number="1" location="board/Kconfig:41">
P2: The documented QEMU command for B-L475E-IOT01A omits `-semihosting`, which conflicts with the new QEMU mode behavior and can lead to missing console output when users run the command directly.</violation>
</file>

<file name="include/platform/stm32l4/registers.h">

<violation number="1" location="include/platform/stm32l4/registers.h:69">
P0: SYSCFG base address overlaps USART1 base, so SYSCFG register accesses target the wrong peripheral.</violation>

<violation number="2" location="include/platform/stm32l4/registers.h:791">
P1: SYSCFG reset bit mask is set to USART1's bit, causing reset operations to hit the wrong peripheral.</violation>

<violation number="3" location="include/platform/stm32l4/registers.h:873">
P1: SYSCFG clock-enable mask uses USART1's bit, so SYSCFG clock control is incorrect.</violation>
</file>

<file name="kernel/thread.c">

<violation number="1" location="kernel/thread.c:275">
P1: When the cycle guard fires, the thread is returned but not linked into the parent's child chain. A later `thread_destroy` on this thread will NULL-dereference while walking the sibling list. Either undo `thread_init` (remove from thread_map) and return `NULL`, or still link the thread despite the warning.</violation>
</file>

<file name="platform/stm32-common/gpio-l4.c">

<violation number="1" location="platform/stm32-common/gpio-l4.c:145">
P2: Use atomic BSRR set writes instead of ODR read-modify-write in `gpio_out_high` to avoid losing concurrent GPIO updates.</violation>

<violation number="2" location="platform/stm32-common/gpio-l4.c:150">
P2: Use atomic BSRR reset writes instead of ODR read-modify-write in `gpio_out_low` to prevent race-prone GPIO clears.</violation>
</file>

<file name="include/platform/stm32l4/hwtimer.h">

<violation number="1" location="include/platform/stm32l4/hwtimer.h:6">
P2: The header guard macro uses `STM32F4` in an STM32L4 header. Rename it to `STM32L4` to avoid incorrect guard identity and possible include conflicts.</violation>
</file>

<file name="scripts/qemu-kdb-auto.py">

<violation number="1" location="scripts/qemu-kdb-auto.py:67">
P1: QEMU stdin is captured by a pipe but terminal input is never forwarded, so manual KDB input (and further interactive commands) cannot work.</violation>
</file>

<file name="include/platform/stm32l4/gpio.h">

<violation number="1" location="include/platform/stm32l4/gpio.h:62">
P2: `af_usart6` is exposed for STM32L4 even though this target defines USART6 as unavailable; remove this alias to avoid advertising an invalid peripheral mapping.</violation>
</file>

<file name="include/platform/stm32l4/usart.h">

<violation number="1" location="include/platform/stm32l4/usart.h:6">
P1: This stm32l4 header reuses the stm32f4 include guard macro, creating a real guard collision with `include/platform/stm32f4/usart.h`. Use an L4-specific guard name to avoid include-order-dependent header omission.</violation>
</file>

<file name="include/platform/stm32l4/syscfg.h">

<violation number="1" location="include/platform/stm32l4/syscfg.h:6">
P2: Use an STM32L4-specific include guard; reusing the STM32F4 guard can cause this header to be silently excluded when both platform headers are seen.</violation>
</file>

<file name="platform/stm32l4/Kconfig">

<violation number="1" location="platform/stm32l4/Kconfig:1">
P2: This new Kconfig file is an exact duplicate of `platform/stm32f4/Kconfig`, creating high-maintenance duplicated platform config that can drift over time.</violation>
</file>

<file name="platform/stm32l4/f9_sram.ld">

<violation number="1" location="platform/stm32l4/f9_sram.ld:117">
P3: This comment is stale and references the removed `netduinoplus2` target, which makes the linker rationale misleading for STM32L4 maintenance.</violation>
</file>

<file name="include/platform/stm32l4/nvic_private.h">

<violation number="1" location="include/platform/stm32l4/nvic_private.h:2">
P3: This file duplicates the STM32F429 NVIC private table verbatim, creating high-maintenance copy-paste logic likely to drift.</violation>
</file>

<file name="scripts/qemu-test.py">

<violation number="1" location="scripts/qemu-test.py:160">
P2: Board auto-detection uses raw substring matching on the whole path, which can select the wrong machine for custom paths.</violation>

<violation number="2" location="scripts/qemu-test.py:194">
P1: The `script` wrapper is invoked with invalid arguments, so QEMU fails to launch on hosts that have `script` installed.</violation>
</file>

<file name="include/platform/stm32l4/exti.h">

<violation number="1" location="include/platform/stm32l4/exti.h:1">
P1: The stm32l4 EXTI header reuses the stm32f4 include guard macro, so including both headers can silently drop one header due to guard collision.</violation>

<violation number="2" location="include/platform/stm32l4/exti.h:4">
P1: This L4 header includes the F4 GPIO header, which drags in F4 register definitions instead of L4 ones.</violation>
</file>

<file name="include/platform/stm32l4/rcc.h">

<violation number="1" location="include/platform/stm32l4/rcc.h:13">
P2: `RCC_FLAG_MSIRDY` is encoded for the wrong register, so `RCC_GetFlagStatus` checks `RCC_CSR` instead of `RCC_CR`.</violation>
</file>

<file name="user/lib/l4/platform/syscalls.c">

<violation number="1" location="user/lib/l4/platform/syscalls.c:153">
P2: Hard-coded `#48` offset for `utcb->mr_low[0]` is fragile and inconsistent with the `__builtin_offsetof` approach used in `irq_restore` (per the PR description). Pass the offset as an `"i"` operand to keep the asm in sync with the struct layout.

This offset appears three times in this function and would silently break if any field is added before `mr_low` in `utcb_t`.</violation>
</file>

<file name="platform/stm32l4/usart.c">

<violation number="1" location="platform/stm32l4/usart.c:100">
P1: USART init sets the wrong CR1 UE bit for STM32L4, so the peripheral may never be enabled.</violation>
</file>

<file name="scripts/qemu-kdb.sh">

<violation number="1" location="scripts/qemu-kdb.sh:42">
P1: QEMU stdin is wired to a FIFO without a persistent writer, which can block startup or feed EOF between key events.</violation>

<violation number="2" location="scripts/qemu-kdb.sh:48">
P3: Use `printf` instead of `echo` so only `?` is sent to KDB (no trailing newline).</violation>
</file>

<file name="platform/stm32-common/rcc.c">

<violation number="1" location="platform/stm32-common/rcc.c:171">
P2: The PLL-to-SYSCLK switch timeout has no fallback, unlike the PLL lock timeout which properly does `goto fallback_clock`. If the switch fails, Flash wait states remain configured for 80MHz while the system actually runs on HSI/MSI, risking bus faults or incorrect peripheral timing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@f9micro f9micro deleted a comment from cubic-dev-ai bot Mar 25, 2026
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

19 issues found across 85 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/qemu-test.py">

<violation number="1" location="scripts/qemu-test.py:196">
P1: The `script(1)` wrapper is built with invalid arguments, so QEMU launch can fail whenever `script` is present.</violation>
</file>

<file name="kernel/thread.c">

<violation number="1" location="kernel/thread.c:275">
P1: Returning `thr` after detecting a child-chain cycle leaves the thread in `thread_map` with `t_parent = caller` but not linked into the caller's child/sibling chain. If `thread_destroy` is later called on this thread, it will NULL-dereference while walking the parent's child list trying to unlink it.

Either destroy the thread and return `NULL`, or link it into the chain before returning.</violation>
</file>

<file name="include/platform/stm32l4/exti.h">

<violation number="1" location="include/platform/stm32l4/exti.h:1">
P1: Header guard collides with `stm32f4/exti.h`; one platform header can suppress the other based on include order.</violation>

<violation number="2" location="include/platform/stm32l4/exti.h:4">
P1: Including `stm32f4/gpio.h` in the STM32L4 EXTI header mixes incompatible register definitions and can produce wrong hardware addresses.</violation>
</file>

<file name="include/platform/stm32l4/systick.h">

<violation number="1" location="include/platform/stm32l4/systick.h:1">
P2: The include guard was renamed to an F4 macro in the STM32L4 header, which collides with the existing STM32F4 systick header and can cause the wrong header to be skipped.</violation>

<violation number="2" location="include/platform/stm32l4/systick.h:6">
P1: `CORE_CLOCK` is set to 168MHz in the STM32L4 systick header, but the L4 clock configuration is 80MHz. This will skew systick-based timing and timeout calculations.</violation>
</file>

<file name="platform/stm32l4/f9.ld">

<violation number="1" location="platform/stm32l4/f9.ld:159">
P1: mem1_start is now placed in RamLoc, but MEM1 is defined as the 0x10000000 AHB/CCM pool. This makes the MEM1 range inverted (start > end), corrupting the memory map and allowing bogus memory descriptors.</violation>
</file>

<file name="include/platform/stm32l4/registers.h">

<violation number="1" location="include/platform/stm32l4/registers.h:69">
P1: `SYSCFG_BASE` is mapped to the same APB2 offset as `USART1_BASE`, so SYSCFG register writes alias USART1 space and hit the wrong peripheral.</violation>
</file>

<file name="user/lib/l4/pager.c">

<violation number="1" location="user/lib/l4/pager.c:580">
P2: Handle the NULL return from init_thread_pool before using pool to avoid a NULL dereference when RES_FPAGE is too small.</violation>
</file>

<file name="include/platform/stm32l4/syscfg.h">

<violation number="1" location="include/platform/stm32l4/syscfg.h:6">
P2: Header guard macro uses STM32F4 name in an STM32L4 header, which risks include collisions and incorrect guard behavior. Rename it to an STM32L4-specific guard.</violation>
</file>

<file name="kernel/memory.c">

<violation number="1" location="kernel/memory.c:385">
P2: Loop limit reduced from `i < 8` to `i < 7`, permanently wasting one MPU region. With only 8 hardware MPU regions available and the code explicitly noting their scarcity, this means region 7 is always cleared, never assigned an fpage. If this is intentional (e.g., reserving region 7 for a specific purpose on the new board), it should be documented. Otherwise, this is an off-by-one that increases unnecessary memory faults for address spaces that need all 8 regions.</violation>
</file>

<file name="include/platform/stm32l4/hwtimer.h">

<violation number="1" location="include/platform/stm32l4/hwtimer.h:6">
P3: Header guard name doesn’t match the STM32L4 header; use an STM32L4-specific guard to avoid collisions or mismatched expectations.</violation>
</file>

<file name="scripts/qemu-kdb-auto.py">

<violation number="1" location="scripts/qemu-kdb-auto.py:84">
P1: The wrapper disconnects user keyboard input from QEMU, so `--no-trigger` manual mode and interactive KDB input do not work.</violation>

<violation number="2" location="scripts/qemu-kdb-auto.py:117">
P2: `line_buffer` grows without bound even though it is only needed for one-time boot marker detection.</violation>
</file>

<file name="platform/stm32l4/Kconfig">

<violation number="1" location="platform/stm32l4/Kconfig:1">
P2: This file is an exact duplicate of `platform/stm32f4/Kconfig`, introducing high-maintenance copy/paste configuration that is likely to drift.</violation>

<violation number="2" location="platform/stm32l4/Kconfig:32">
P3: The interrupt prompt label is misspelled (`EXIT1` instead of `EXTI1`), which makes the Kconfig menu misleading.</violation>
</file>

<file name="scripts/qemu-kdb.sh">

<violation number="1" location="scripts/qemu-kdb.sh:37">
P1: QEMU stdin is attached to a FIFO before any writer is opened, which can block startup and prevent the VM from booting until input is written.</violation>
</file>

<file name="platform/stm32l4/f9_sram.ld">

<violation number="1" location="platform/stm32l4/f9_sram.ld:158">
P1: Setting `mem1_start` from the RamLoc location creates an inverted MEM1 range (`start > 0x10010000`), which underflows available-memory size calculations.</violation>
</file>

<file name="platform/stm32-common/gpio-l4.c">

<violation number="1" location="platform/stm32-common/gpio-l4.c:145">
P2: Use BSRR for single-pin set operations instead of ODR read-modify-write to avoid lost concurrent GPIO updates.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

dbg_printf(DL_KDB,
"THREAD_CREATE: child chain cycle caller=%t child=%p\n",
caller->t_globalid, caller->t_child);
return thr;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Returning thr after detecting a child-chain cycle leaves the thread in thread_map with t_parent = caller but not linked into the caller's child/sibling chain. If thread_destroy is later called on this thread, it will NULL-dereference while walking the parent's child list trying to unlink it.

Either destroy the thread and return NULL, or link it into the chain before returning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/thread.c, line 275:

<comment>Returning `thr` after detecting a child-chain cycle leaves the thread in `thread_map` with `t_parent = caller` but not linked into the caller's child/sibling chain. If `thread_destroy` is later called on this thread, it will NULL-dereference while walking the parent's child list trying to unlink it.

Either destroy the thread and return `NULL`, or link it into the chain before returning.</comment>

<file context>
@@ -233,9 +264,16 @@ tcb_t *thread_create(l4_thread_t globalid, utcb_t *utcb)
+            dbg_printf(DL_KDB,
+                       "THREAD_CREATE: child chain cycle caller=%t child=%p\n",
+                       caller->t_globalid, caller->t_child);
+            return thr;
+        }
         t->t_sibling = thr;
</file context>
Suggested change
return thr;
thread_deinit(thr);
return NULL;
Fix with Cubic

sys.stdout.write(chunk)
sys.stdout.flush()

line_buffer += chunk
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: line_buffer grows without bound even though it is only needed for one-time boot marker detection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/qemu-kdb-auto.py, line 117:

<comment>`line_buffer` grows without bound even though it is only needed for one-time boot marker detection.</comment>

<file context>
@@ -0,0 +1,158 @@
+                    sys.stdout.write(chunk)
+                    sys.stdout.flush()
+
+                    line_buffer += chunk
+
+                    # Detect boot completion
</file context>
Fix with Cubic

@jserv jserv force-pushed the qemu-target branch 3 times, most recently from 786ea60 to 80e1bd8 Compare March 25, 2026 11:04
This replaces Netduino Plus 2 (STM32F4) QEMU target with B-L475E-IOT01A
Discovery (STM32L475VG) for proper FPU/MPU emulation, eliminating the
mock infrastructure (mpu_mock, scb_mock, safe_mmio) that was papering
over the old target's incomplete peripheral emulation.

Platform changes:
- Add STM32L4 platform support (registers, GPIO, RCC, USART, NVIC,
  linker scripts with correct L4 memory map and no CCM RAM)
- Fix L4 register addresses: USART1 at 0x40013800, RCC enable
  registers at correct L4 offsets, GPIO clocks on AHB2 (not AHB1),
  PLL source set to MSI (not HSE), USART_IT_CR offsets for L4
- Add full L4 USART register ops (ISR/RDR/TDR layout)
- Add ARM semihosting for console I/O under QEMU

FPU support:
- Fix irq_restore: single asm block with __builtin_offsetof prevents
  compiler from reusing clobbered registers after ldm {r4-r11}
- Add -mgeneral-regs-only to kernel CFLAGS to prevent GCC from
  emitting VFP instructions for non-FP operations in handler mode
- Add FPU lazy context save/restore (d8-d15) in PendSV handler

IPC fix:
- Fix L4_Ipc naked function: reload UTCB MR pointer from
  current_utcb after SVC return instead of reusing LR (which the
  exception frame restores to the caller's return address)

KDB:
- Add USART1 RX interrupt path for KDB input under QEMU semihosting
  (CONFIG_KDB_UART_INPUT, gated on CONFIG_QEMU)
- Improve hard fault handler to read correct stack (MSP vs PSP)

Build/test:
- Update qemu-test.py: auto-detect board, add -semihosting for L4,
  fail on unsupported boards instead of silent fallback
- Bootstrap Kconfig tools in CI test job before direct Python usage
- Update CI workflow: replace netduinoplus2 with b-l475e-iot01a
- Remove Netduino Plus 2 board, STM32F1 platform, mock infrastructure
@jserv jserv merged commit c0471dc into master Mar 25, 2026
9 checks passed
@jserv jserv deleted the qemu-target branch March 25, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant