From 3efa6df4633d74d0f051ab8baa11c24ac011d073 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:39:37 +0800 Subject: [PATCH 01/16] platform: add runtime FPB hardware detection for QEMU support QEMU netduinoplus2 does not emulate the Flash Patch and Breakpoint (FPB) unit at 0xE0002000. Previously, the kernel assumed FPB was always present, causing crashes when kprobes tried to set hardware breakpoints on non-existent hardware. Changes: - Add detect_fpb_hardware() to probe FPB availability by checking if enable bit sticks after write to FPB_CTRL - Extract NUM_CODE comparator count from FP_CTRL per ARMv7-M spec - Enable DebugMon exception before FPB detection (required for software breakpoints to work as fallback) - Return NULL from hard_breakpoint_config() when FPB unavailable, triggering automatic fallback to software breakpoints - Add DSB/ISB memory barriers after FPB register writes per ARM recommendations This allows the kernel to boot on QEMU with kprobes enabled, using software breakpoints (BKPT instruction) for RAM-based code. --- platform/breakpoint-hard.c | 107 +++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/platform/breakpoint-hard.c b/platform/breakpoint-hard.c index 231f8271..9ad7192a 100644 --- a/platform/breakpoint-hard.c +++ b/platform/breakpoint-hard.c @@ -12,10 +12,22 @@ #include #include #include +#include #define HW_BKPT_NULL_ID FPB_MAX_COMP #define IS_UPPER_HALFWORLD(addr) (addr & 0x2) +/* Runtime FPB availability flag. + * QEMU netduinoplus2 does not emulate FPB hardware. + * Set to 1 if FPB is detected, 0 otherwise. + */ +static int fpb_available = 0; + +/* Runtime FPB comparator count detected from hardware. + * Capped at FPB_MAX_COMP to prevent array overflow. + */ +static int fpb_num_comp = 0; + static int hard_breakpoints[FPB_MAX_COMP]; static void hard_breakpoint_enable(struct breakpoint *b); @@ -25,39 +37,104 @@ static void hard_breakpoint_release(struct breakpoint *b); static int get_avail_hard_breakpoint(void) { int i; - for (i = 0 ; i < FPB_MAX_COMP ; i++) { + /* Use runtime-detected comparator count */ + for (i = 0; i < fpb_num_comp; i++) { if (hard_breakpoints[i] == -1) return i; } return -1; } +/* + * Detect FPB hardware availability and count comparators. + * Returns number of code comparators if FPB is present, 0 otherwise. + * + * Detection method: Write enable bit to FPB_CTRL and verify it sticks. + * On QEMU without FPB emulation, the write has no effect. + * + * ARMv7-M FP_CTRL register format: + * Bits [7:4]: NUM_CODE1 - Number of code comparators, bits [3:0] + * Bits [14:12]: NUM_CODE2 - Number of code comparators, bits [6:4] + * Total NUM_CODE = (NUM_CODE2 << 4) | NUM_CODE1 + */ +static int detect_fpb_hardware(void) +{ + uint32_t ctrl_val; + int num_code; + + /* Try to enable FPB */ + *FPB_CTRL = FPB_CTRL_KEY | FPB_CTRL_ENABLE; + + /* Read back and check if enable bit is set */ + ctrl_val = *FPB_CTRL; + + if (!(ctrl_val & FPB_CTRL_ENABLE)) + return 0; + + /* Extract NUM_CODE from FP_CTRL per ARMv7-M spec: + * NUM_CODE1 (bits [7:4]) + NUM_CODE2 (bits [14:12]) shifted + */ + num_code = ((ctrl_val >> 4) & 0xF) | + (((ctrl_val >> 12) & 0x7) << 4); + + /* Cap at FPB_MAX_COMP to prevent array overflow */ + if (num_code > FPB_MAX_COMP) + num_code = FPB_MAX_COMP; + + return num_code; +} + void hard_breakpoint_pool_init(void) { int i; - /* Enable FPB breakpoint */ - *FPB_CTRL = FPB_CTRL_KEY | FPB_CTRL_ENABLE ; - - /* Enable DWT watchpoint & DebugMon exception */ + /* Enable DebugMon exception - required for BOTH hardware and software + * breakpoints. Without this, BKPT instructions cause HardFault. + * Must be done before early return for FPB-absent case. + */ *DCB_DEMCR |= DCB_DEMCR_TRCENA | DCB_DEMCR_MON_EN; - /* Clear status bit */ + /* Clear debug status bits */ *SCB_HFSR = SCB_HFSR_DEBUGEVT; *SCB_DFSR = SCB_DFSR_BKPT; *SCB_DFSR = SCB_DFSR_HALTED; *SCB_DFSR = SCB_DFSR_DWTTRAP; - for (i = 0; i < FPB_MAX_COMP; i++) { + /* Detect FPB hardware and get comparator count */ + fpb_num_comp = detect_fpb_hardware(); + fpb_available = (fpb_num_comp > 0); + + if (!fpb_available) { + dbg_printf(DL_KDB, + "FPB: Hardware not available (QEMU?), " + "using software breakpoints only\n"); + return; + } + + dbg_printf(DL_KDB, "FPB: Hardware detected, %d comparators\n", + fpb_num_comp); + + /* Initialize only the detected number of comparators */ + for (i = 0; i < fpb_num_comp; i++) { hard_breakpoints[i] = -1; - *(FPB_COMP + i) = 0; /* Reset each FPB_COMP register */ + *(FPB_COMP + i) = 0; } enable_all_hard_breakpoints(); + + /* Memory barriers to ensure FPB config is visible before use. + * ARM recommends DSB+ISB after changing breakpoint state. + */ + __DSB(); + __ISB(); } struct breakpoint *hard_breakpoint_config(uint32_t addr, struct breakpoint *b) { + /* If FPB hardware not available, fall back to soft breakpoints */ + if (!fpb_available) + return NULL; + if (breakpoint_type_by_addr(addr) == BKPT_HARD) { int _hard_breakpoint_id = get_avail_hard_breakpoint(); int breakpoint_id = get_breakpoint_id(b); @@ -84,17 +161,25 @@ static void hard_breakpoint_enable(struct breakpoint *b) if (IS_UPPER_HALFWORLD(addr)) { *(FPB_COMP + b->hard_breakpoint_id) = FPB_COMP_REPLACE_UPPER | - (addr & FPB_COMP_ADDR_MASK) | FPB_COMP_ENABLE; + (addr & FPB_COMP_ADDR_MASK) | FPB_COMP_ENABLE; } else { *(FPB_COMP + b->hard_breakpoint_id) = FPB_COMP_REPLACE_LOWER | (addr & FPB_COMP_ADDR_MASK) | FPB_COMP_ENABLE; } + + /* Ensure breakpoint is active before continuing execution */ + __DSB(); + __ISB(); } static void hard_breakpoint_disable(struct breakpoint *b) { *(FPB_COMP + b->hard_breakpoint_id) &= ~FPB_COMP_ENABLE; + + /* Ensure breakpoint is disabled before continuing */ + __DSB(); + __ISB(); } static void hard_breakpoint_release(struct breakpoint *b) @@ -102,4 +187,8 @@ static void hard_breakpoint_release(struct breakpoint *b) *(FPB_COMP + b->hard_breakpoint_id) &= ~FPB_COMP_ENABLE; hard_breakpoints[b->hard_breakpoint_id] = -1; b->type = BKPT_NONE; + + /* Ensure breakpoint is released before continuing */ + __DSB(); + __ISB(); } From 145ebbc3812e714b096bc8f1212fb28ca145c51e Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:39:45 +0800 Subject: [PATCH 02/16] kdb: handle kprobe registration failure in sampling command The KDB 'p' (sampling) command registers a kprobe on ktimer_handler, which resides in Flash. Without FPB hardware (e.g., on QEMU), kprobe registration fails but the return value was ignored, leading to use of an uninitialized kprobe structure. Changes: - Check kprobe_register() return value - Add init_failed flag to prevent repeated registration attempts - Display informative error message explaining FPB requirement - Call sampling_disable() on failure to clean up state Now the sampling command fails gracefully with a clear message instead of crashing the system. --- kernel/sampling-kdb.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/kernel/sampling-kdb.c b/kernel/sampling-kdb.c index 791525a8..6644df8d 100644 --- a/kernel/sampling-kdb.c +++ b/kernel/sampling-kdb.c @@ -37,18 +37,35 @@ void kdb_show_sampling(void) { int *hitcount, *symid_list; static int init = 0; + static int init_failed = 0; static struct kprobe k; + if (init_failed) { + dbg_printf(DL_KDB, + "Sampling unavailable (kprobe on Flash requires FPB)\n"); + return; + } + if (init == 0) { dbg_printf(DL_KDB, "Init sampling...\n"); sampling_init(); sampling_enable(); - init++; k.addr = ktimer_handler; k.pre_handler = sampling_handler; k.post_handler = NULL; - kprobe_register(&k); + + if (kprobe_register(&k) < 0) { + dbg_printf(DL_KDB, + "FAILED: kprobe on Flash requires FPB hardware\n"); + dbg_printf(DL_KDB, + "(QEMU doesn't emulate FPB at 0xE0002000)\n"); + sampling_disable(); + init_failed = 1; + return; + } + + init++; return; } From bf1911dc6ccc975b80d6817f793ba253abf0025e Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:40:51 +0800 Subject: [PATCH 03/16] kdb: remove verbose debug messages from sampling command Remove unnecessary debug output: - "Init sampling..." status message before initialization - "(QEMU doesn't emulate FPB at 0xE0002000)" extra detail The "FAILED: kprobe on Flash requires FPB hardware" message is sufficient to explain the failure. --- kernel/sampling-kdb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/sampling-kdb.c b/kernel/sampling-kdb.c index 6644df8d..f9d9b7fa 100644 --- a/kernel/sampling-kdb.c +++ b/kernel/sampling-kdb.c @@ -47,7 +47,6 @@ void kdb_show_sampling(void) } if (init == 0) { - dbg_printf(DL_KDB, "Init sampling...\n"); sampling_init(); sampling_enable(); @@ -58,8 +57,6 @@ void kdb_show_sampling(void) if (kprobe_register(&k) < 0) { dbg_printf(DL_KDB, "FAILED: kprobe on Flash requires FPB hardware\n"); - dbg_printf(DL_KDB, - "(QEMU doesn't emulate FPB at 0xE0002000)\n"); sampling_disable(); init_failed = 1; return; From b5ea67c63ba754a37e06367739e36ed57e79c187 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:42:33 +0800 Subject: [PATCH 04/16] build: remove Travis CI configuration Remove .travis.yml as CI has migrated to GitHub Actions. --- .travis.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 56d604e5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: c - -compiler: gcc -env: - - BOARD=discoveryf4 - - BOARD=discoveryf429 - - BOARD=stm32p103 - -before_install: - - sudo apt-get install build-essential - - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - - sudo apt-get update -qq - - sudo apt-get install -y gcc-arm-embedded - -before_script: arm-none-eabi-gcc --version - -script: - - cp -f board/$BOARD/defconfig .config - - make build/host/Config.in - - make --no-print-directory -C external/kconfig -f Makefile.f9 conf obj=`pwd`/build/host CC=gcc HOSTCC=gcc - - build/host/conf --silentoldconfig build/host/Config.in - - make - - arm-none-eabi-size build/$BOARD/f9.elf - -cache: - - apt - - ccache From 45ca7f96f94bd69cc3f0213cc2c6a5fe93ee43d4 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:42:45 +0800 Subject: [PATCH 05/16] platform: add DSB and ISB memory barrier intrinsics Add inline assembly wrappers for ARM data synchronization barrier (DSB) and instruction synchronization barrier (ISB) instructions. These barriers are required after programming MPU regions, FPB comparators, and other system control registers to ensure changes take effect before subsequent instructions execute. --- include/platform/cortex_m.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/platform/cortex_m.h b/include/platform/cortex_m.h index e9dd0f7d..f282232a 100644 --- a/include/platform/cortex_m.h +++ b/include/platform/cortex_m.h @@ -57,6 +57,17 @@ inline uint32_t *MSP(void) { return val; } +/* Memory barriers required after MPU updates */ +inline void __DSB(void) __attribute__((always_inline)); +inline void __DSB(void) { + asm volatile ("dsb" ::: "memory"); +} + +inline void __ISB(void) __attribute__((always_inline)); +inline void __ISB(void) { + asm volatile ("isb" ::: "memory"); +} + /* Cortex M4 General Registers */ /* System Control Map */ From 76c28ce4153ea77ae8cd3b68b93d583cc4884b8e Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:43:10 +0800 Subject: [PATCH 06/16] board: replace stm32p103 with netduinoplus2 for QEMU support Replace Olimex STM32-P103 (STM32F1) with Netduino Plus 2 (STM32F4) as the QEMU-emulatable board target. The netduinoplus2 machine is supported by upstream QEMU, enabling full system emulation for development and testing without hardware. Changes: - Remove board/stm32p103/ (STM32F103RBT6, no QEMU support) - Add board/netduinoplus2/ (STM32F405RGT6, QEMU supported) - Add CONFIG_QEMU option for emulation-specific workarounds - Remove PLATFORM_STM32F1 (no longer used) - Update discovery board defconfigs The QEMU config enables workarounds for emulation limitations such as unreliable USART TXE interrupts and missing CCM RAM. --- board/Kconfig | 23 ++- board/discoveryf4/defconfig | 2 +- board/discoveryf429/defconfig | 2 +- board/{stm32p103 => netduinoplus2}/board.c | 18 ++- board/{stm32p103 => netduinoplus2}/board.h | 50 +++---- board/{stm32p103 => netduinoplus2}/build.mk | 6 +- board/{stm32p103 => netduinoplus2}/defconfig | 142 ++++++++++++------- 7 files changed, 148 insertions(+), 95 deletions(-) rename board/{stm32p103 => netduinoplus2}/board.c (61%) rename board/{stm32p103 => netduinoplus2}/board.h (56%) rename board/{stm32p103 => netduinoplus2}/build.mk (64%) rename board/{stm32p103 => netduinoplus2}/defconfig (51%) diff --git a/board/Kconfig b/board/Kconfig index 990445db..83f84076 100644 --- a/board/Kconfig +++ b/board/Kconfig @@ -22,14 +22,26 @@ config BOARD_STM32F429DISCOVERY help STM32F429 Discovery board with STM32F429ZIT6 MCU and LCD. -config BOARD_STM32P103 - bool "Olimex STM32-P103" - select PLATFORM_STM32F1 +config BOARD_NETDUINOPLUS2 + bool "Netduino Plus 2" + select PLATFORM_STM32F4 help - Olimex STM32-P103 board with STM32F103RBT6 MCU. + Netduino Plus 2 board with STM32F405RGT6 MCU. + This board is supported by upstream QEMU for emulation. endchoice +config QEMU + bool "QEMU emulation mode" + default y if BOARD_NETDUINOPLUS2 + help + Enable workarounds for QEMU emulation limitations: + - Use synchronous UART output (QEMU TXE interrupt unreliable) + - Place bitmaps in regular SRAM (QEMU lacks CCM RAM emulation) + + Enable this when running under QEMU. Disable for real hardware + to get better performance. + endmenu # Platform symbols (selected by board choice) @@ -38,6 +50,3 @@ config PLATFORM_STM32F4 config PLATFORM_STM32F429 bool - -config PLATFORM_STM32F1 - bool diff --git a/board/discoveryf4/defconfig b/board/discoveryf4/defconfig index cfee560a..97a74cb3 100644 --- a/board/discoveryf4/defconfig +++ b/board/discoveryf4/defconfig @@ -8,7 +8,7 @@ # CONFIG_BOARD_STM32F4DISCOVERY=y # CONFIG_BOARD_STM32F429DISCOVERY is not set -# CONFIG_BOARD_STM32P103 is not set +# CONFIG_BOARD_NETDUINOPLUS2 is not set # CONFIG_BITMAP_BITBAND is not set # CONFIG_FPU is not set # CONFIG_STDIO_NODEV is not set diff --git a/board/discoveryf429/defconfig b/board/discoveryf429/defconfig index c217f18a..22dad4d7 100644 --- a/board/discoveryf429/defconfig +++ b/board/discoveryf429/defconfig @@ -8,7 +8,7 @@ # # CONFIG_BOARD_STM32F4DISCOVERY is not set CONFIG_BOARD_STM32F429DISCOVERY=y -# CONFIG_BOARD_STM32P103 is not set +# CONFIG_BOARD_NETDUINOPLUS2 is not set # CONFIG_BITMAP_BITBAND is not set # CONFIG_FPU is not set # CONFIG_STDIO_NODEV is not set diff --git a/board/stm32p103/board.c b/board/netduinoplus2/board.c similarity index 61% rename from board/stm32p103/board.c rename to board/netduinoplus2/board.c index 22eb7aa9..ac3b9c2b 100644 --- a/board/stm32p103/board.c +++ b/board/netduinoplus2/board.c @@ -3,26 +3,30 @@ * found in the LICENSE file. */ -#include -#include +#include +#include #include "board.h" struct usart_dev console_uart = { - .u_num = 3, + .u_num = 2, .baud = 115200, BOARD_USART_CONFIGS .tx = { .port = BOARD_USART_TX_IO_PORT, .pin = BOARD_USART_TX_IO_PIN, - .mode = GPIO_MODE_OUT_ALT_PP, + .pupd = GPIO_PUPDR_NONE, + .type = GPIO_MODER_ALT, .func = BOARD_USART_FUNC, - .ospeed = GPIO_OSPEED_50M, + .o_type = GPIO_OTYPER_PP, + .speed = GPIO_OSPEEDR_50M, }, .rx = { .port = BOARD_USART_RX_IO_PORT, .pin = BOARD_USART_RX_IO_PIN, - .mode = GPIO_MODE_OUT_ALT_PP, + .pupd = GPIO_PUPDR_NONE, + .type = GPIO_MODER_ALT, .func = BOARD_USART_FUNC, - .ospeed = GPIO_OSPEED_50M, + .o_type = GPIO_OTYPER_PP, + .speed = GPIO_OSPEEDR_50M, }, }; diff --git a/board/stm32p103/board.h b/board/netduinoplus2/board.h similarity index 56% rename from board/stm32p103/board.h rename to board/netduinoplus2/board.h index 7fcf91c3..917a6abb 100644 --- a/board/stm32p103/board.h +++ b/board/netduinoplus2/board.h @@ -3,14 +3,14 @@ * found in the LICENSE file. */ -#ifndef STM32P103_BOARD_H_ -#define STM32P103_BOARD_H_ +#ifndef NETDUINOPLUS2_BOARD_H_ +#define NETDUINOPLUS2_BOARD_H_ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include extern struct usart_dev console_uart; @@ -28,8 +28,25 @@ extern struct usart_dev console_uart; #define BOARD_USART_RX_IO_PORT GPIOA #define BOARD_USART_RX_IO_PIN 10 +#elif defined(CONFIG_DBGPORT_USE_USART4) -#elif defined(CONFIG_DBGPORT_USE_USART2) +#define BOARD_UART_DEVICE UART4_IRQn +#define BOARD_UART_HANDLER UART4_HANDLER +#define BOARD_USART_FUNC af_uart4 +#define BOARD_USART_CONFIGS \ + .base = UART4_BASE, \ + .rcc_apbenr = RCC_UART4_APBENR, \ + .rcc_reset = RCC_APB1RSTR_USART4RST, +#define BOARD_USART_TX_IO_PORT GPIOA +#define BOARD_USART_TX_IO_PIN 0 +#define BOARD_USART_RX_IO_PORT GPIOA +#define BOARD_USART_RX_IO_PIN 1 + +#else /* default: USART2 */ + /* CONFIG_DBGPORT_USE_USART2 + * Note: QEMU routes USART1 to the console by default. + * Use CONFIG_DBGPORT_USE_USART1 for QEMU emulation. + */ #define BOARD_UART_DEVICE USART2_IRQn #define BOARD_UART_HANDLER USART2_HANDLER @@ -43,21 +60,6 @@ extern struct usart_dev console_uart; #define BOARD_USART_RX_IO_PORT GPIOA #define BOARD_USART_RX_IO_PIN 3 -#else /* default: USART3 */ - /* CONFIG_DBGPORT_USE_USART3 */ - -#define BOARD_UART_DEVICE USART3_IRQn -#define BOARD_UART_HANDLER USART3_HANDLER -#define BOARD_USART_FUNC 0 -#define BOARD_USART_CONFIGS \ - .base = USART3_BASE, \ - .rcc_apbenr = RCC_USART3_APBENR, \ - .rcc_reset = RCC_APB1RSTR_USART3RST, -#define BOARD_USART_TX_IO_PORT GPIOB -#define BOARD_USART_TX_IO_PIN 10 -#define BOARD_USART_RX_IO_PORT GPIOB -#define BOARD_USART_RX_IO_PIN 11 - #endif -#endif /* STM32P103_BOARD_H_ */ +#endif /* NETDUINOPLUS2_BOARD_H_ */ diff --git a/board/stm32p103/build.mk b/board/netduinoplus2/build.mk similarity index 64% rename from board/stm32p103/build.mk rename to board/netduinoplus2/build.mk index 86dd2157..cdfe8476 100644 --- a/board/stm32p103/build.mk +++ b/board/netduinoplus2/build.mk @@ -1,10 +1,10 @@ -# Copyright (c) 2014 The F9 Microkernel Project. All rights reserved. +# Copyright (c) 2013 The F9 Microkernel Project. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -CHIP := stm32f1 +CHIP := stm32f4 PLATFORM := stm32 -STM32_VARIANT := f1 +STM32_VARIANT := f4 board-y = board.o loader-board-y = board.loader.o diff --git a/board/stm32p103/defconfig b/board/netduinoplus2/defconfig similarity index 51% rename from board/stm32p103/defconfig rename to board/netduinoplus2/defconfig index 45fe5d7a..637cbe27 100644 --- a/board/stm32p103/defconfig +++ b/board/netduinoplus2/defconfig @@ -8,62 +8,14 @@ # # CONFIG_BOARD_STM32F4DISCOVERY is not set # CONFIG_BOARD_STM32F429DISCOVERY is not set -CONFIG_BOARD_STM32P103=y +CONFIG_BOARD_NETDUINOPLUS2=y # CONFIG_BITMAP_BITBAND is not set # CONFIG_FPU is not set -CONFIG_SEMIHOST=y # CONFIG_STDIO_NODEV is not set CONFIG_STDIO_USE_DBGPORT=y -# CONFIG_DBGPORT_USE_USART1 is not set -CONFIG_DBGPORT_USE_USART2=y +CONFIG_DBGPORT_USE_USART1=y +# CONFIG_DBGPORT_USE_USART2 is not set # CONFIG_DBGPORT_USE_USART4 is not set -# CONFIG_WWDG_USER_IRQ is not set -# CONFIG_PVD_USER_IRQ is not set -# CONFIG_TAMP_STAMP_USER_IRQ is not set -# CONFIG_RTC_WKUP_USER_IRQ is not set -# CONFIG_FLASH_USER_IRQ is not set -# CONFIG_RCC_USER_IRQ is not set -CONFIG_EXTI0_USER_IRQ=y -CONFIG_EXTI1_USER_IRQ=y -# CONFIG_EXTI2_USER_IRQ is not set -# CONFIG_EXTI3_USER_IRQ is not set -# CONFIG_EXTI4_USER_IRQ is not set -# CONFIG_ADC_USER_IRQ is not set -# CONFIG_CAN1_TX_USER_IRQ is not set -# CONFIG_CAN1_RX_USER_IRQ is not set -# CONFIG_CAN1_RX1_USER_IRQ is not set -# CONFIG_CAN1_SCE_USER_IRQ is not set -# CONFIG_EXTI9_5_USER_IRQ is not set -# CONFIG_TIM1_CC_USER_IRQ is not set -# CONFIG_TIM2_USER_IRQ is not set -# CONFIG_TIM3_USER_IRQ is not set -# CONFIG_TIM4_USER_IRQ is not set -# CONFIG_I2C1_EV_USER_IRQ is not set -# CONFIG_I2C1_ER_USER_IRQ is not set -# CONFIG_I2C2_EV_USER_IRQ is not set -# CONFIG_I2C2_ER_USER_IRQ is not set -# CONFIG_SPI1_USER_IRQ is not set -# CONFIG_SPI2_USER_IRQ is not set -# CONFIG_USART1_USER_IRQ is not set -# CONFIG_USART2_USER_IRQ is not set -# CONFIG_USART3_USER_IRQ is not set -# CONFIG_EXTI15_10_USER_IRQ is not set -# CONFIG_RTC_Alarm_USER_IRQ is not set -# CONFIG_OTG_FS_WKUP_USER_IRQ is not set - -# -# User Interrupt Config -# -# CONFIG_DMA_Stream0_USER_IRQ is not set -# CONFIG_DMA_Stream1_USER_IRQ is not set -# CONFIG_DMA_Stream2_USER_IRQ is not set -# CONFIG_DMA_Stream3_USER_IRQ is not set -# CONFIG_DMA_Stream4_USER_IRQ is not set -# CONFIG_DMA_Stream5_USER_IRQ is not set -# CONFIG_DMA_Stream6_USER_IRQ is not set -# CONFIG_TIM1_BRK_USER_IRQ is not set -# CONFIG_TIM1_UP_USER_IRQ is not set -# CONFIG_TIM1_TRG_COM_USER_IRQ is not set # # Limitations @@ -111,6 +63,92 @@ CONFIG_SYMMAP=y CONFIG_PANIC_DUMP_STACK=y # CONFIG_LOADER is not set +# +# User IRQ +# +# CONFIG_WWDG_USER_IRQ is not set +# CONFIG_PVD_USER_IRQ is not set +# CONFIG_TAMP_STAMP_USER_IRQ is not set +# CONFIG_RTC_WKUP_USER_IRQ is not set +# CONFIG_FLASH_USER_IRQ is not set +# CONFIG_RCC_USER_IRQ is not set +# CONFIG_EXTI0_USER_IRQ is not set +# CONFIG_EXTI1_USER_IRQ is not set +# CONFIG_EXTI2_USER_IRQ is not set +# CONFIG_EXTI3_USER_IRQ is not set +# CONFIG_EXTI4_USER_IRQ is not set +# CONFIG_DMA1_Stream0_USER_IRQ is not set +# CONFIG_DMA1_Stream1_USER_IRQ is not set +# CONFIG_DMA1_Stream2_USER_IRQ is not set +# CONFIG_DMA1_Stream3_USER_IRQ is not set +# CONFIG_DMA1_Stream4_USER_IRQ is not set +# CONFIG_DMA1_Stream5_USER_IRQ is not set +# CONFIG_DMA1_Stream6_USER_IRQ is not set +# CONFIG_ADC_USER_IRQ is not set +# CONFIG_CAN1_TX_USER_IRQ is not set +# CONFIG_CAN1_RX_USER_IRQ is not set +# CONFIG_CAN1_RX1_USER_IRQ is not set +# CONFIG_CAN1_SCE_USER_IRQ is not set +# CONFIG_EXTI9_5_USER_IRQ is not set +# CONFIG_TIM1_BRK_TIM9_USER_IRQ is not set +# CONFIG_TIM1_UP_TIM10_USER_IRQ is not set +# CONFIG_TIM1_TRG_COM_TIM11_USER_IRQ is not set +# CONFIG_TIM1_CC_USER_IRQ is not set +# CONFIG_TIM2_USER_IRQ is not set +# CONFIG_TIM3_USER_IRQ is not set +# CONFIG_TIM4_USER_IRQ is not set +# CONFIG_I2C1_EV_USER_IRQ is not set +# CONFIG_I2C1_ER_USER_IRQ is not set +# CONFIG_I2C2_EV_USER_IRQ is not set +# CONFIG_I2C2_ER_USER_IRQ is not set +# CONFIG_SPI1_USER_IRQ is not set +# CONFIG_SPI2_USER_IRQ is not set +# CONFIG_USART1_USER_IRQ is not set +# CONFIG_USART2_USER_IRQ is not set +# CONFIG_USART3_USER_IRQ is not set +# CONFIG_EXTI15_10_USER_IRQ is not set +# CONFIG_RTC_Alarm_USER_IRQ is not set +# CONFIG_OTG_FS_WKUP_USER_IRQ is not set +# CONFIG_TIM8_BRK_TIM12_USER_IRQ is not set +# CONFIG_TIM8_UP_TIM13_USER_IRQ is not set +# CONFIG_TIM8_TRG_COM_TIM14_USER_IRQ is not set +# CONFIG_TIM8_CC_USER_IRQ is not set +# CONFIG_DMA1_Stream7_USER_IRQ is not set +# CONFIG_FSMC_USER_IRQ is not set +# CONFIG_SDIO_USER_IRQ is not set +# CONFIG_TIM5_USER_IRQ is not set +# CONFIG_SPI3_USER_IRQ is not set +# CONFIG_UART4_USER_IRQ is not set +# CONFIG_UART5_USER_IRQ is not set +# CONFIG_TIM6_DAC_USER_IRQ is not set +# CONFIG_TIM7_USER_IRQ is not set +# CONFIG_DMA2_Stream0_USER_IRQ is not set +# CONFIG_DMA2_Stream1_USER_IRQ is not set +# CONFIG_DMA2_Stream2_USER_IRQ is not set +# CONFIG_DMA2_Stream3_USER_IRQ is not set +# CONFIG_DMA2_Stream4_USER_IRQ is not set +# CONFIG_ETH_USER_IRQ is not set +# CONFIG_ETH_WKUP_USER_IRQ is not set +# CONFIG_CAN2_TX_USER_IRQ is not set +# CONFIG_CAN2_RX0_USER_IRQ is not set +# CONFIG_CAN2_RX1_USER_IRQ is not set +# CONFIG_CAN2_SCE_USER_IRQ is not set +# CONFIG_OTG_FS_USER_IRQ is not set +# CONFIG_DMA2_Stream5_USER_IRQ is not set +# CONFIG_DMA2_Stream6_USER_IRQ is not set +# CONFIG_DMA2_Stream7_USER_IRQ is not set +# CONFIG_USART6_USER_IRQ is not set +# CONFIG_I2C3_EV_USER_IRQ is not set +# CONFIG_I2C3_ER_USER_IRQ is not set +# CONFIG_OTG_HS_EP1_OUT_USER_IRQ is not set +# CONFIG_OTG_HS_EP1_IN_USER_IRQ is not set +# CONFIG_OTG_HS_WKUP_USER_IRQ is not set +# CONFIG_OTG_HS_USER_IRQ is not set +# CONFIG_DCMI_USER_IRQ is not set +# CONFIG_CRYP_USER_IRQ is not set +# CONFIG_HASH_RNG_USER_IRQ is not set +# CONFIG_FPU_USER_IRQ is not set + # # User Space # @@ -124,6 +162,6 @@ CONFIG_PINGPONG=y # # Test Cases # -CONFIG_EXTI_INTERRUPT_TEST=y +# CONFIG_EXTI_INTERRUPT_TEST is not set CONFIG_L4_TEST=y # CONFIG_LCD_TEST is not set From a21bd891343b04cd6881acb5e1911bfec0820d9f Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:43:25 +0800 Subject: [PATCH 07/16] platform: add QEMU UART workarounds QEMU's USART emulation has limitations that cause issues: 1. TXE status bit in SR may not update reliably 2. TXE interrupt timing can cause TX queue deadlocks Changes: - Check CR1 TXEIE for TX interrupt (QEMU-safe) instead of SR TXE - Check SR RXNE status for RX (data availability) - Use synchronous output (DBG_PANIC) under CONFIG_QEMU - Raise UART priority above PendSV to prevent deadlock - Add comments explaining QEMU-specific behavior These workarounds enable reliable debug output when running under QEMU while maintaining full async performance on real hardware. --- platform/debug_uart.c | 28 +++++++++++++++++++++++----- platform/stm32-common/usart.c | 25 +++++++++++++++++-------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/platform/debug_uart.c b/platform/debug_uart.c index 3fe014b8..7330d3f1 100644 --- a/platform/debug_uart.c +++ b/platform/debug_uart.c @@ -26,12 +26,18 @@ static void dbg_uart_send(int avail); void __uart_irq_handler(void) { + /* For RX: check SR status (RXNE) to ensure data is available. + * For TX: check only CR1 interrupt enable (TXEIE) because QEMU's + * USART emulation may not properly update SR TXE status bit. + * This is safe because TXE interrupt only fires when TXE is set. + */ + if (usart_status(&console_uart, USART_RXNE)) { + /* USART RX - data received */ + dbg_uart_recv(); + } if (usart_interrupt_status(&console_uart, USART_IT_TXE)) { - /* USART TX */ + /* USART TX - transmit buffer empty */ dbg_uart_send(1); - } else if (usart_interrupt_status(&console_uart, USART_IT_RXNE)) { - /* USART RX */ - dbg_uart_recv(); } } @@ -142,10 +148,22 @@ void dbg_uart_init(void) queue_init(&(dbg_uart.tx), dbg_uart_tx_buffer, SEND_BUFSIZE); queue_init(&(dbg_uart.rx), dbg_uart_rx_buffer, RECV_BUFSIZE); +#ifdef CONFIG_QEMU + /* Use sync output for reliable debugging under QEMU. + * QEMU's USART TXE interrupt emulation is unreliable, + * causing the async TX queue to block indefinitely. + */ + dbg_state = DBG_PANIC; +#else dbg_state = DBG_ASYNC; +#endif usart_config_interrupt(&console_uart, USART_IT_RXNE, 1); - NVIC_SetPriority(BOARD_UART_DEVICE, 0xf, 0); + /* UART must have higher priority (lower number) than PendSV (0xf) + * so it can preempt and drain TX queue during debug output. + * Otherwise dbg_async_putchar deadlocks waiting for queue space. + */ + NVIC_SetPriority(BOARD_UART_DEVICE, 0xe, 0); NVIC_ClearPendingIRQ(BOARD_UART_DEVICE); NVIC_EnableIRQ(BOARD_UART_DEVICE); diff --git a/platform/stm32-common/usart.c b/platform/stm32-common/usart.c index d1868b9b..7c7a1530 100644 --- a/platform/stm32-common/usart.c +++ b/platform/stm32-common/usart.c @@ -31,19 +31,28 @@ static int16_t usart_baud(uint32_t base, uint32_t baud) { uint16_t mantissa; uint16_t fraction; + uint32_t apb_clock; - /* USART1 and USART6 are on APB2 whose frequency is 84MHz, - * while USART2, USART3, UART4, and UART5 are on APB1 whose - * frequency is 42 MHz (max). + /* Detect system clock source to determine actual APB frequencies. + * When HSE fails (common in QEMU), we fall back to HSI (16MHz) + * with no PLL, so APB clocks are different from PLL case. */ - if (base == USART1_BASE) { - mantissa = (84000000) / (16 * baud); - fraction = (84000000 / baud) % 16; + uint32_t sws = *RCC_CFGR & RCC_CFGR_SWS_M; + + if (sws == RCC_CFGR_SWS_PLL) { + /* PLL is system clock: SYSCLK=168MHz, APB1=42MHz, APB2=84MHz */ + if (base == USART1_BASE) + apb_clock = 84000000; /* APB2 for USART1/6 */ + else + apb_clock = 42000000; /* APB1 for USART2-5 */ } else { - mantissa = (42000000) / (16 * baud); - fraction = (42000000 / baud) % 16; + /* HSI or HSE without PLL: assume 16MHz with no prescalers */ + apb_clock = 16000000; } + mantissa = apb_clock / (16 * baud); + fraction = (apb_clock / baud) % 16; + return (mantissa << 4) | fraction; } From e1b85c42bde9220ed4cb6ddfca30936393408df6 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:43:46 +0800 Subject: [PATCH 08/16] mpu: fix circular list bug and add memory barriers Fix from f9-riscv commit f88633c: the MPU fpage selection could create circular lists when promoting a recently-used fpage to the front of mpu_first. If the fpage was already in the list, prepending without first removing it created a cycle causing infinite loops. Changes: - Move remove_fpage_from_list macro to include/fpage.h for shared use - Improve macro with pointer-to-pointer traversal (Gemini review) - Call remove_fpage_from_list before prepending in mpu_select_lru - Add DSB/ISB barriers after MPU region updates per ARM spec - Add dump_as_fpages() debug helper for fault diagnosis - Improve memmanage handler to attempt fault recovery first The memory barriers ensure MPU configuration changes take effect before execution continues, preventing stale permission checks. --- include/fpage.h | 27 ++++++++++++ platform/stm32-common/mpu.c | 86 ++++++++++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/include/fpage.h b/include/fpage.h index 0be8ffdc..ba518115 100644 --- a/include/fpage.h +++ b/include/fpage.h @@ -53,6 +53,33 @@ typedef struct fpage fpage_t; #define FPAGE_SIZE(fp) (1 << (fp)->fpage.shift) #define FPAGE_END(fp) (FPAGE_BASE(fp) + FPAGE_SIZE(fp)) +/** + * Remove fpage from a linked list (as_next or mpu_next chain). + * + * Must be called before prepending fpage to prevent circular lists. + * Fix from f9-riscv commit f88633c, improved per Gemini review. + * + * Uses pointer-to-pointer traversal to safely handle: + * - Empty lists (first is NULL) + * - Removing head element + * - Removing middle/tail elements + * - Fpage not in list (no-op) + * + * @param as Address space containing the list + * @param fpage Fpage to remove + * @param first Name of list head field in as (e.g., first, mpu_first) + * @param next Name of next pointer field in fpage (e.g., as_next, mpu_next) + */ +#define remove_fpage_from_list(as, fpage, first, next) do { \ + fpage_t **_curr = &(as)->first; \ + while (*_curr && *_curr != (fpage)) { \ + _curr = &(*_curr)->next; \ + } \ + if (*_curr) { \ + *_curr = (*_curr)->next; \ + } \ +} while (0) + static inline int addr_in_fpage(memptr_t addr, fpage_t *fpage, int incl_end) { return ((addr >= FPAGE_BASE(fpage) && addr < FPAGE_END(fpage)) || diff --git a/platform/stm32-common/mpu.c b/platform/stm32-common/mpu.c index 91973839..239907fd 100644 --- a/platform/stm32-common/mpu.c +++ b/platform/stm32-common/mpu.c @@ -9,6 +9,7 @@ #include #include #include +#include #include INC_PLAT(mpu.c) @@ -30,6 +31,10 @@ void mpu_setup_region(int n, fpage_t *fp) *mpu_base = 0x10 | (n & 0xF); *mpu_attr = 0; } + + /* Memory barriers ensure MPU changes take effect immediately */ + __DSB(); + __ISB(); } void mpu_enable(mpu_state_t i) @@ -76,6 +81,13 @@ int mpu_select_lru(as_t *as, uint32_t addr) if (addr_in_fpage(addr, fp, 0)) { fpage_t *sfp = as->mpu_stack_first; + /* + * Fix from f9-riscv commit f88633c: + * Remove fpage from list first to prevent circular list. + * If fp is already in mpu_first list and we prepend without + * removing, we create a cycle that causes infinite loops. + */ + remove_fpage_from_list(as, fp, mpu_first, mpu_next); fp->mpu_next = as->mpu_first; as->mpu_first = fp; @@ -99,6 +111,7 @@ int mpu_select_lru(as_t *as, uint32_t addr) fp = fp->as_next; } + return 1; } @@ -128,50 +141,81 @@ void kdb_dump_mpu(void) } #endif +static void dump_as_fpages(as_t *as) +{ + fpage_t *fp = as ? as->first : NULL; + int count = 0; + + dbg_printf(DL_EMERG, "---AS fpages---\n"); + while (fp && count < 16) { + dbg_printf(DL_EMERG, " fp[%d]: base:%p, sz:2**%d\n", + count, FPAGE_BASE(fp), fp->fpage.shift); + fp = fp->as_next; + count++; + } + if (fp) + dbg_printf(DL_EMERG, " ... more fpages\n"); +} + void __memmanage_handler(void) { uint32_t mmsr = *((uint32_t *) MPU_FAULT_STATUS_ADDR); uint32_t mmar = *((uint32_t *) MPU_FAULT_ADDRESS_ADDR); tcb_t *current = thread_current(); + int handled = 0; - /* stack errors */ - if (mmsr & MPU_MSTKERR) { - panic("Corrupted Stack, current = %t, psp = %p\n", - current->t_globalid, PSP()); - } - + /* Try to handle the fault first before printing diagnostics */ if (mmsr & MPU_MEM_FAULT) { if (mpu_select_lru(current->as, mmar) == 0) - goto ok; + handled = 1; } - /* unstacking errors */ if (mmsr & MPU_MUSTKERR) { - /* Processor is not writing mmar, so we do it manually */ - if (mpu_select_lru(current->as, (uint32_t)PSP() + 31) == 0) { - goto ok; - } + if (mpu_select_lru(current->as, (uint32_t)PSP() + 31) == 0) + handled = 1; } if (mmsr & MPU_IACCVIOL) { uint32_t pc = PSP()[REG_PC]; - if (mpu_select_lru(current->as, pc) == 0) - goto ok; + handled = 1; + else if (mpu_select_lru(current->as, pc + 2) == 0) + handled = 1; + } - if (mpu_select_lru(current->as, pc + 2) == 0) - goto ok; + /* If handled successfully, just clear status and return silently */ + if (handled) { + *((uint32_t *) MPU_FAULT_STATUS_ADDR) = mmsr; + return; } + /* Unhandled fault - show diagnostic info */ + dbg_printf(DL_EMERG, "MEMFAULT: tid:%t, as:%p (spaceid:%p)\n", + current->t_globalid, current->as, + current->as ? current->as->as_spaceid : 0); + dbg_printf(DL_EMERG, " mmsr:%p, mmar:%p, pc:%p, psp:%p\n", + mmsr, mmar, PSP()[REG_PC], PSP()); + dbg_printf(DL_EMERG, " flags: %s%s%s%s%s\n", + (mmsr & MPU_MEM_FAULT) ? "MMARVALID " : "", + (mmsr & MPU_DACCVIOL) ? "DACCVIOL " : "", + (mmsr & MPU_IACCVIOL) ? "IACCVIOL " : "", + (mmsr & MPU_MSTKERR) ? "MSTKERR " : "", + (mmsr & MPU_MUSTKERR) ? "MUSTKERR " : ""); + dbg_printf(DL_EMERG, " in_mpu(mmar)=%d, in_mpu(pc)=%d\n", + addr_in_mpu(mmar), addr_in_mpu(PSP()[REG_PC])); + + /* stack errors - always fatal */ + if (mmsr & MPU_MSTKERR) { + panic("Corrupted Stack, current = %t, psp = %p\n", + current->t_globalid, PSP()); + } + + /* Unhandled fault - dump diagnostics and panic */ + dump_as_fpages(current->as); mpu_dump(1); panic("Memory fault mmsr:%p, mmar:%p,\n" " current:%t, psp:%p, pc:%p\n", mmsr, mmar, current->t_globalid, PSP(), PSP()[REG_PC]); - -ok: - /* Clean status register */ - *((uint32_t *) MPU_FAULT_STATUS_ADDR) = mmsr; - return; } IRQ_HANDLER(memmanage_handler, __memmanage_handler); From ba40a75a7182ecb72aeba1d5662ee434c7cf78d9 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:44:02 +0800 Subject: [PATCH 09/16] memory: improve fpage alignment and size calculations Fix several issues in memory management alignment handling: kernel/fpage.c: - Fix fp_addr_log2() to find trailing zeros (was incorrectly using leading zeros via left shift) - Add fp_size_log2() for computing floor(log2(size)) - Handle edge case of addr=0 (return LARGEST_FPAGE_SHIFT) - Add allocation tracking counter for debugging - Remove local remove_fpage_from_list (now in fpage.h) kernel/memory.c: - Add addr_align_down() for aligning base addresses - Add overflow check in addr_align_up() - Add mempool_align_base() for region start alignment - Add addr_is_fpage_aligned() validation helper - Remove CONFIG_BOARD_STM32P103 ifdef for MEM1 pool include/memory.h: - Export new alignment validation functions These fixes prevent memory region overlap when allocating fpages for thread UTCBs and stacks with non-power-of-2 layouts. --- include/memory.h | 16 ++++- kernel/fpage.c | 166 ++++++++++++++++++++++++++++++++++++----------- kernel/memory.c | 75 +++++++++++++++++---- 3 files changed, 205 insertions(+), 52 deletions(-) diff --git a/include/memory.h b/include/memory.h index f8b5cfca..cd5eca4b 100644 --- a/include/memory.h +++ b/include/memory.h @@ -66,6 +66,12 @@ typedef struct { */ #define MP_MAP_ALWAYS 0x1000 +/* + * MP_FPAGE_MASK: Bitmask for fpage type flags (bits 8-11). + * If (flags & MP_FPAGE_MASK) is non-zero, the mempool supports fpage creation. + * Mempools with MP_NO_FPAGE (0x0000) cannot have fpages allocated from them. + * Used to protect kernel memory (KTEXT, KDATA, KBSS) from being mapped. + */ #define MP_FPAGE_MASK 0x0F00 #define MP_USER_PERM(mpflags) ((mpflags & 0xF0) >> 4) @@ -115,11 +121,19 @@ typedef enum { void memory_init(void); memptr_t mempool_align(int mpid, memptr_t addr); +memptr_t mempool_align_base(int mpid, memptr_t addr); int mempool_search(memptr_t base, size_t size); mempool_t *mempool_getbyid(int mpid); +/* + * Check if address is aligned to smallest fpage boundary. + * Returns 1 if aligned, 0 if not aligned. + * Used by kernel to reject unaligned addresses from user space. + */ +int addr_is_fpage_aligned(memptr_t addr); + int map_area(as_t *src, as_t *dst, memptr_t base, size_t size, - map_action_t action, int is_priviliged); + map_action_t action, int is_privileged); as_t *as_create(uint32_t as_spaceid); void as_destroy(as_t *as); diff --git a/kernel/fpage.c b/kernel/fpage.c index 14fb69eb..78ccfc07 100644 --- a/kernel/fpage.c +++ b/kernel/fpage.c @@ -17,33 +17,54 @@ DECLARE_KTABLE(fpage_t, fpage_table, CONFIG_MAX_FPAGES); -#define remove_fpage_from_list(as, fpage, first, next) { \ - fpage_t *fpprev = (as)->first; \ - int end; \ - if (fpprev == (fpage)) { \ - (as)->first = fpprev->next; \ - } \ - else { \ - while (!end && fpprev->next != (fpage)) { \ - if (!fpprev->next) \ - end = 1; \ - fpprev = fpprev->next; \ - } \ - fpprev->next = (fpage)->next; \ - } \ -} +/* + * remove_fpage_from_list macro moved to include/fpage.h for shared use + * (e.g., by platform/stm32-common/mpu.c to prevent circular list bugs) + */ /* * Helper functions */ + +/* + * Compute the position of the lowest set bit (trailing zeros). + * This determines the alignment of an address for fpage sizing. + * Returns the shift value such that (1 << shift) is the largest + * power of 2 that divides addr. + */ static int fp_addr_log2(memptr_t addr) { int shift = 0; - while ((addr <<= 1) != 0) + if (addr == 0) + return CONFIG_LARGEST_FPAGE_SHIFT; + + while ((addr & 1) == 0) { + ++shift; + addr >>= 1; + } + + return shift; +} + +/* + * Compute floor(log2(size)) - the position of the highest set bit. + * Returns the largest shift such that (1 << shift) <= size. + * Used to determine the largest power-of-2 fpage that fits within size. + */ +static int fp_size_log2(size_t size) +{ + int shift = 0; + + if (size == 0) + return 0; + + while (size > 1) { ++shift; + size >>= 1; + } - return 31 - shift; + return shift; } void fpages_init(void) @@ -83,6 +104,7 @@ static void insert_fpage_chain_to_as(as_t *as, fpage_t *first, fpage_t *last) } fp->as_next = first; } + } /** @@ -117,10 +139,23 @@ static void remove_fpage_from_as(as_t *as, fpage_t *fp) * @param shift (1 << shift) - fpage size * @param mpid - id of mpool */ +static int fpage_alloc_count = 0; + static fpage_t *create_fpage(memptr_t base, size_t shift, int mpid) { fpage_t *fpage = (fpage_t *) ktable_alloc(&fpage_table); + if (!fpage) { + dbg_printf(DL_KDB, + "FPAGE: alloc failed! count=%d base=%p shift=%d mpid=%d\n", + fpage_alloc_count, base, shift, mpid); + } else { + fpage_alloc_count++; + if ((fpage_alloc_count % 50) == 0) + dbg_printf(DL_KDB, "FPAGE: allocated %d fpages\n", + fpage_alloc_count); + } + assert((intptr_t) fpage); fpage->as_next = NULL; @@ -151,13 +186,14 @@ static void create_fpage_chain(memptr_t base, size_t size, int mpid, fpage_t *fpage = NULL; while (size) { - /* Select least of log2(base), log2(size). - * Needed to make regions with correct align + /* Select minimum of base alignment and largest fitting size. + * bshift: base alignment (trailing zeros) ensures MPU alignment + * sshift: floor(log2(size)) gives largest power-of-2 that fits */ bshift = fp_addr_log2(base); - sshift = fp_addr_log2(size); + sshift = fp_size_log2(size); - shift = ((1 << bshift) > size) ? sshift : bshift; + shift = (bshift < sshift) ? bshift : sshift; if (!*pfirst) { /* Create first page */ @@ -181,7 +217,14 @@ fpage_t *split_fpage(as_t *as, fpage_t *fpage, memptr_t split, int rl) memptr_t base = fpage->fpage.base, end = fpage->fpage.base + (1 << fpage->fpage.shift); fpage_t *lfirst = NULL, *llast = NULL, *rfirst = NULL, *rlast = NULL; - split = mempool_align(fpage->fpage.mpid, split); + + /* For rl=1 (right side), round DOWN to include the split point. + * For rl=0 (left side), round UP to exclude past the split point. + */ + if (rl) + split = mempool_align_base(fpage->fpage.mpid, split); + else + split = mempool_align(fpage->fpage.mpid, split); if (!as) return NULL; @@ -232,6 +275,12 @@ int assign_fpages_ext(int mpid, as_t *as, memptr_t base, size_t size, } } + /* Check if mempool supports fpage creation */ + if (!(mempool_getbyid(mpid)->flags & MP_FPAGE_MASK)) { + /* Mempool does not support fpages (e.g., kernel text/data/bss) */ + return -1; + } + end = base + size; if (as) { @@ -246,9 +295,28 @@ int assign_fpages_ext(int mpid, as_t *as, memptr_t base, size_t size, "MEM: fpage chain %s [b:%p, sz:%p] as %p\n", mempool_getbyid(mpid)->name, base, size, as); - create_fpage_chain(mempool_align(mpid, base), - mempool_align(mpid, size), - mpid, &first, &last); + { + /* Round UP base to prevent over-mapping. + * S1 fix: mempool_align_base rounds DOWN, + * granting access below requested address. + */ + memptr_t abase = mempool_align(mpid, base); + memptr_t aend = mempool_align(mpid, base + size); + + /* Empty region after alignment: skip */ + if (abase >= aend) { + base = FPAGE_BASE(*fp); + fp = &(*fp)->as_next; + continue; + } + + create_fpage_chain(abase, aend - abase, mpid, + &first, &last); + } + + /* NULL guard: create_fpage_chain may fail */ + if (!first || !last) + return -1; last->as_next = *fp; *fp = first; @@ -278,24 +346,43 @@ int assign_fpages_ext(int mpid, as_t *as, memptr_t base, size_t size, "MEM: fpage chain %s [b:%p, sz:%p] as %p\n", mempool_getbyid(mpid)->name, base, size, as); - create_fpage_chain(mempool_align(mpid, base), - mempool_align(mpid, size), - mpid, &first, &last); + { + /* S1 fix: round UP base to prevent over-mapping */ + memptr_t abase = mempool_align(mpid, base); + memptr_t aend = mempool_align(mpid, base + size); - *fp = first; + /* Empty region check */ + if (abase < aend) { + create_fpage_chain(abase, aend - abase, mpid, + &first, &last); + } + } - if (!*pfirst) - *pfirst = first; - *plast = last; + /* Only link if chain was created */ + if (first && last) { + *fp = first; + + if (!*pfirst) + *pfirst = first; + *plast = last; + } } } else { dbg_printf(DL_MEMORY, "MEM: fpage chain %s [b:%p, sz:%p] as %p\n", mempool_getbyid(mpid)->name, base, size, as); - create_fpage_chain(mempool_align(mpid, base), - mempool_align(mpid, size), - mpid, pfirst, plast); + { + /* S1 fix: round UP base to prevent over-mapping */ + memptr_t abase = mempool_align(mpid, base); + memptr_t aend = mempool_align(mpid, base + size); + + /* Empty region check: return error if nothing to map */ + if (abase >= aend) + return -1; + + create_fpage_chain(abase, aend - abase, mpid, pfirst, plast); + } } return 0; @@ -320,10 +407,10 @@ int map_fpage(as_t *src, as_t *dst, fpage_t *fpage, map_action_t action) fpmap->raw[0] = fpage->raw[0]; fpmap->raw[1] = fpage->raw[1]; - /* Set flags correctly */ + /* Set flags correctly: preserve FPAGE_ALWAYS for MPU prioritization */ if (action == MAP) fpage->fpage.flags |= FPAGE_MAPPED; - fpmap->fpage.flags = FPAGE_CLONE; + fpmap->fpage.flags = FPAGE_CLONE | (fpage->fpage.flags & FPAGE_ALWAYS); /* Insert into mapee list */ fpmap->map_next = fpage->map_next; @@ -332,8 +419,9 @@ int map_fpage(as_t *src, as_t *dst, fpage_t *fpage, map_action_t action) /* Insert into AS */ insert_fpage_to_as(dst, fpmap); - dbg_printf(DL_MEMORY, "MEM: %s fpage %p from %p to %p\n", - (action == MAP) ? "mapped" : "granted", fpage, src, dst); + dbg_printf(DL_MEMORY, "MEM: %s fpage %p [b:%p sz:2**%d] from %p to %p\n", + (action == MAP) ? "mapped" : "granted", fpmap, + FPAGE_BASE(fpmap), fpmap->fpage.shift, src, dst); return 0; } diff --git a/kernel/memory.c b/kernel/memory.c index 58dbb136..e0c43d3e 100644 --- a/kernel/memory.c +++ b/kernel/memory.c @@ -68,10 +68,8 @@ static mempool_t memmap[] = { DECLARE_MEMPOOL("KBITMAP", &bitmap_start, &bitmap_end, MP_KR | MP_KW | MP_NO_FPAGE, MPT_KERNEL_DATA), #endif -#ifndef CONFIG_BOARD_STM32P103 DECLARE_MEMPOOL("MEM1", &mem1_start, 0x10010000, MP_UR | MP_UW | MP_AHB_RAM, MPT_AVAILABLE), -#endif DECLARE_MEMPOOL("APB1DEV", 0x40000000, 0x40007800, MP_UR | MP_UW | MP_DEVICES, MPT_DEVICES), DECLARE_MEMPOOL("APB2_1DEV", 0x40010000, 0x40014c00, @@ -107,26 +105,63 @@ extern char *kip_extra; /* Some helper functions */ /* size value must be 2^k */ -static memptr_t addr_align(memptr_t addr, size_t size) +static memptr_t addr_align_up(memptr_t addr, size_t size) +{ + memptr_t mask = ~(size - 1); + memptr_t aligned = (addr + (size - 1)) & mask; + /* Check for overflow: if aligned < addr, return max aligned value */ + if (aligned < addr) + return mask; + return aligned; +} + +static memptr_t addr_align_down(memptr_t addr, size_t size) { - return (addr + (size - 1)) & ~(size - 1); + return addr & ~(size - 1); } #define CONFIG_SMALLEST_FPAGE_SIZE (1 << CONFIG_SMALLEST_FPAGE_SHIFT) +/* Align size up to fpage boundary (for determining region end) */ memptr_t mempool_align(int mpid, memptr_t addr) { if (memmap[mpid].flags & MP_FPAGE_MASK) - return addr_align(addr, CONFIG_SMALLEST_FPAGE_SIZE); + return addr_align_up(addr, CONFIG_SMALLEST_FPAGE_SIZE); + + return INVALID_FPAGE_REGION; +} + +/* Align base address down to fpage boundary (for region start) */ +memptr_t mempool_align_base(int mpid, memptr_t addr) +{ + if (memmap[mpid].flags & MP_FPAGE_MASK) + return addr_align_down(addr, CONFIG_SMALLEST_FPAGE_SIZE); return INVALID_FPAGE_REGION; } +/* + * Check if address is aligned to smallest fpage boundary. + * Used to validate user-supplied addresses before processing. + */ +int addr_is_fpage_aligned(memptr_t addr) +{ + return (addr & (CONFIG_SMALLEST_FPAGE_SIZE - 1)) == 0; +} + int mempool_search(memptr_t base, size_t size) { + memptr_t end; + + /* Check for overflow in base + size */ + if (size > 0 && base > (memptr_t)-1 - size + 1) + return -1; /* Overflow would occur */ + + end = base + size; + for (int i = 0; i < sizeof(memmap) / sizeof(mempool_t); ++i) { if ((memmap[i].start <= base) && - (memmap[i].end >= (base + size))) { + (memmap[i].end >= end)) { return i; } } @@ -159,10 +194,10 @@ void memory_init() case MPT_USER_TEXT: case MPT_DEVICES: case MPT_AVAILABLE: - mem_desc[j].base = addr_align( + mem_desc[j].base = addr_align_up( (memmap[i].start), CONFIG_SMALLEST_FPAGE_SIZE) | i; - mem_desc[j].size = addr_align( + mem_desc[j].size = addr_align_up( (memmap[i].end - memmap[i].start), CONFIG_SMALLEST_FPAGE_SIZE) | memmap[i].tag; j++; @@ -360,13 +395,23 @@ void as_destroy(as_t *as) } int map_area(as_t *src, as_t *dst, memptr_t base, size_t size, - map_action_t action, int is_priviliged) + map_action_t action, int is_privileged) { /* Most complicated part of mapping subsystem */ - memptr_t end = base + size, probe = base; + memptr_t end, probe = base; + + /* Check for overflow in base + size */ + if (size > 0 && base > (memptr_t)-1 - size + 1) + return -1; /* Overflow would occur */ + + end = base + size; fpage_t *fp = src->first, *first = NULL, *last = NULL; int last_invalid = 0; + dbg_printf(DL_MEMORY, + "MEM: map_area base:%p, size:%p, priv:%d\n", + base, size, is_privileged); + /* FIXME: reverse mappings (i.e. thread 1 maps 0x1000 to thread 2, * than thread 2 does the same to thread 1). */ @@ -378,8 +423,11 @@ int map_area(as_t *src, as_t *dst, memptr_t base, size_t size, /* FIXME: checking existence of fpages */ - if (is_priviliged) { - assign_fpages_ext(-1, src, base, size, &first, &last); + if (is_privileged) { + if (assign_fpages_ext(-1, src, base, size, &first, &last) < 0) { + /* Cannot create fpages for this region */ + return -1; + } if (src == dst) { /* Maps to itself, ignore other actions */ return 0; @@ -456,6 +504,9 @@ int map_area(as_t *src, as_t *dst, memptr_t base, size_t size, if (!last || !first) { /* Splitting not supported for mapped pages */ /* UNIMPLIMENTED */ + dbg_printf(DL_KDB, + "MEM: map_area split failed: first=%p last=%p base=%p\n", + first, last, base); return -1; } From ec2a99e1860f41cdc1741fabec4def2c586b72c7 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:44:19 +0800 Subject: [PATCH 10/16] ipc: add map base alignment validation and debugging Add security check for MapItem/GrantItem base address alignment: L4 spec uses 64-byte alignment (0xFFFFFFC0 mask), but the kernel requires CONFIG_SMALLEST_FPAGE_SIZE (256-byte) alignment for safe mapping. Unaligned addresses cannot be mapped correctly and the kernel must reject them rather than silently adjusting boundaries. Changes: - Validate map base alignment with addr_is_fpage_aligned() - Return IPC error (UE_IPC_ABORTED) on unaligned map base - Add debug printf for IPC tag, map operations, and completion - Extract map_base and map_size to named variables for clarity This prevents potential security issues where unaligned map requests could inadvertently expose adjacent memory regions. --- kernel/ipc.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 12 deletions(-) diff --git a/kernel/ipc.c b/kernel/ipc.c index 10e73f31..8c0c73ce 100644 --- a/kernel/ipc.c +++ b/kernel/ipc.c @@ -79,6 +79,11 @@ static void do_ipc(tcb_t *from, tcb_t *to) int untyped_last = tag.s.n_untyped + 1; int typed_last = untyped_last + tag.s.n_typed; + dbg_printf(DL_IPC, + "IPC: do_ipc tag:%p n_untyped:%d n_typed:%d MR1:%p MR2:%p\n", + tag.raw, tag.s.n_untyped, tag.s.n_typed, + ipc_read_mr(from, 1), ipc_read_mr(from, 2)); + if (typed_last > IPC_MR_COUNT) { do_ipc_error(from, to, UE_IPC_MSG_OVERFLOW | UE_IPC_PHASE_SEND, @@ -111,16 +116,45 @@ static void do_ipc(tcb_t *from, tcb_t *to) } else if (typed_item.s.header & IPC_TI_MAP_GRANT) { /* MapItem / GrantItem have 1xxx in header */ int ret; + memptr_t map_base, map_size; typed_data = mr_data; + map_base = typed_item.raw & 0xFFFFFFC0; + map_size = typed_data & 0xFFFFFFC0; + + /* S1 security fix: REJECT unaligned map base addresses. + * L4 spec uses 64-byte alignment (0xFFFFFFC0 mask), but + * kernel requires CONFIG_SMALLEST_FPAGE_SIZE (256-byte). + * Unaligned addresses cannot be safely mapped - kernel + * must not silently adjust boundaries. + */ + if (!addr_is_fpage_aligned(map_base)) { + dbg_printf(DL_IPC, + "IPC: REJECT unaligned map base %p\n", + map_base); + do_ipc_error(from, to, + UE_IPC_ABORTED | UE_IPC_PHASE_SEND, + UE_IPC_ABORTED | UE_IPC_PHASE_RECV, + T_RUNNABLE, + T_RUNNABLE); + return; + } + + dbg_printf(DL_IPC, + "IPC: map_area from %t to %t base:%p size:%p priv:%d\n", + from->t_globalid, to->t_globalid, + map_base, map_size, + thread_ispriviliged(from)); + ret = map_area(from->as, to->as, - typed_item.raw & 0xFFFFFFC0, - typed_data & 0xFFFFFFC0, + map_base, map_size, (typed_item.s.header & IPC_TI_GRANT) ? GRANT : MAP, thread_ispriviliged(from)); typed_item_idx = -1; + dbg_printf(DL_IPC, "IPC: map_area returned %d\n", ret); + if (ret < 0) { do_ipc_error(from, to, UE_IPC_ABORTED | UE_IPC_PHASE_SEND, @@ -165,7 +199,7 @@ static void do_ipc(tcb_t *from, tcb_t *to) sched_slot_dispatch(SSI_IPC_THREAD, to); dbg_printf(DL_IPC, - "IPC: %t to %t\n", caller->t_globalid, to->t_globalid); + "IPC: %t→%t done\n", from->t_globalid, to->t_globalid); } uint32_t ipc_timeout(void *data) @@ -209,6 +243,8 @@ void sys_ipc(uint32_t *param1) l4_thread_t to_tid = param1[REG_R0], from_tid = param1[REG_R1]; uint32_t timeout = param1[REG_R2]; + + if (to_tid == L4_NILTHREAD && from_tid == L4_NILTHREAD) { caller->state = T_INACTIVE; @@ -228,8 +264,9 @@ void sys_ipc(uint32_t *param1) user_interrupt_config(caller); caller->state = T_RUNNABLE; return; - } else if ((to_thr && to_thr->state == T_RECV_BLOCKED) - || to_tid == caller->t_globalid) { + } else if (to_thr && + (to_thr->state == T_RECV_BLOCKED || + to_tid == caller->t_globalid)) { /* To thread who is waiting for us or sends to myself */ do_ipc(caller, to_thr); return; @@ -247,11 +284,16 @@ void sys_ipc(uint32_t *param1) uint32_t regs[4]; /* r0, r1, r2, r3 */ dbg_printf(DL_IPC, - "IPC: %t thread start\n", to_tid); + "IPC: %t thread start sp:%p stack_size:%p\n", + to_tid, sp, stack_size); to_thr->stack_base = sp - stack_size; to_thr->stack_size = stack_size; + dbg_printf(DL_IPC, + "IPC: %t stack_base:%p stack_size:%p\n", + to_tid, to_thr->stack_base, to_thr->stack_size); + regs[REG_R0] = (uint32_t)&kip; regs[REG_R1] = (uint32_t)to_thr->utcb; regs[REG_R2] = ipc_read_mr(caller, 4); @@ -267,13 +309,68 @@ void sys_ipc(uint32_t *param1) return; } else { - do_ipc(caller, to_thr); - to_thr->state = T_INACTIVE; + /* Non-start IPC to INACTIVE thread: process + * typed items (MapItems) only, without full + * do_ipc() which requires valid ctx.sp. + * This allows mapping memory to thread before + * it's started. + */ + ipc_msg_tag_t tag = { .raw = ipc_read_mr(caller, 0) }; + int untyped_last = tag.s.n_untyped + 1; + int typed_last = untyped_last + tag.s.n_typed; + ipc_typed_item typed_item; + int typed_item_idx = -1; + + dbg_printf(DL_IPC, + "IPC: %t to INACTIVE %t (non-start)\n", + caller->t_globalid, to_thr->t_globalid); + + /* Process typed items (MapItems only) */ + for (int typed_idx = untyped_last; typed_idx < typed_last; ++typed_idx) { + uint32_t mr_data = ipc_read_mr(caller, typed_idx); + + if (typed_item_idx == -1) { + typed_item.raw = mr_data; + ++typed_item_idx; + } else if (typed_item.s.header & IPC_TI_MAP_GRANT) { + memptr_t map_base = typed_item.raw & 0xFFFFFFC0; + memptr_t map_size = mr_data & 0xFFFFFFC0; + int ret; + + /* S1 fix: reject unaligned addresses */ + if (!addr_is_fpage_aligned(map_base)) { + dbg_printf(DL_IPC, + "IPC: REJECT unaligned map to INACTIVE %p\n", + map_base); + caller->state = T_RUNNABLE; + return; + } + + ret = map_area(caller->as, to_thr->as, + map_base, map_size, + (typed_item.s.header & IPC_TI_GRANT) ? + GRANT : MAP, + thread_ispriviliged(caller)); + typed_item_idx = -1; + + if (ret < 0) { + dbg_printf(DL_IPC, + "IPC: map to INACTIVE failed: %d\n", + ret); + } + } + } + /* Keep thread INACTIVE, sender continues */ + caller->state = T_RUNNABLE; return; } } else { /* No waiting, block myself */ + dbg_printf(DL_KDB, + "IPC: %t SEND_BLOCKED to %t (thr=%p state=%d)\n", + caller->t_globalid, to_tid, + to_thr, to_thr ? to_thr->state : -1); caller->state = T_SEND_BLOCKED; caller->utcb->intended_receiver = to_tid; dbg_printf(DL_IPC, @@ -295,7 +392,7 @@ void sys_ipc(uint32_t *param1) */ for (int i = 1; i < thread_count; ++i) { thr = thread_map[i]; - if (thr->state == T_SEND_BLOCKED && + if (thr && thr->state == T_SEND_BLOCKED && thr->utcb->intended_receiver == caller->t_globalid) { do_ipc(thr, caller); @@ -305,7 +402,7 @@ void sys_ipc(uint32_t *param1) } else if (from_tid != TID_TO_GLOBALID(THREAD_INTERRUPT)) { thr = thread_by_globalid(from_tid); - if (thr->state == T_SEND_BLOCKED && + if (thr && thr->state == T_SEND_BLOCKED && thr->utcb->intended_receiver == caller->t_globalid) { do_ipc(thr, caller); @@ -340,6 +437,8 @@ uint32_t ipc_deliver(void *data) for (int i = 1; i < thread_count; ++i) { tcb_t *thr = thread_map[i]; + if (!thr) + continue; switch (thr->state) { case T_RECV_BLOCKED: if (thr->ipc_from != L4_NILTHREAD && @@ -347,7 +446,7 @@ uint32_t ipc_deliver(void *data) thr->ipc_from != TID_TO_GLOBALID(THREAD_INTERRUPT)) { from_thr = thread_by_globalid(thr->ipc_from); /* NOTE: Must check from_thr intend to send*/ - if (from_thr->state == T_SEND_BLOCKED && + if (from_thr && from_thr->state == T_SEND_BLOCKED && from_thr->utcb->intended_receiver == thr->t_globalid) do_ipc(from_thr, thr); } @@ -357,7 +456,7 @@ uint32_t ipc_deliver(void *data) if (receiver != L4_NILTHREAD && receiver != L4_ANYTHREAD) { to_thr = thread_by_globalid(receiver); - if (to_thr->state == T_RECV_BLOCKED) + if (to_thr && to_thr->state == T_RECV_BLOCKED) do_ipc(thr, to_thr); } break; From 77cdcb1d2b43b1f3e2c8c4fad3705d3c38f19377 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:44:36 +0800 Subject: [PATCH 11/16] user: fix memory alignment and add safety checks Fix alignment issues in user-space memory allocation that caused overlapping UTCB and stack regions: user/root_thread.c: - Add fpage_align_safe() with overflow protection - Align UTCB base BEFORE ThreadControl call - Align stack base BEFORE Map call - Add null check for free_mem (no free memory case) user/lib/l4/pager.c: - Add sigma0 map request debugging - Improve fpage construction for map requests user/lib/l4/platform/syscalls.c: - Add timeout and return value debugging - Fix IPC call instrumentation user/apps/l4test/main.c, pingpong/main.c: - Minor debug output improvements These fixes prevent kernel's mempool_align_base() from rounding down into previously allocated regions, which caused memory corruption and thread initialization failures. --- user/apps/l4test/main.c | 8 +++++- user/apps/pingpong/main.c | 6 ++++- user/lib/l4/pager.c | 33 ++++++++++++++++++++++--- user/lib/l4/platform/syscalls.c | 29 +++++++++++++--------- user/root_thread.c | 44 ++++++++++++++++++++++++++++++++- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/user/apps/l4test/main.c b/user/apps/l4test/main.c index 2b48cb98..fb15ccc0 100644 --- a/user/apps/l4test/main.c +++ b/user/apps/l4test/main.c @@ -162,10 +162,16 @@ static void *main(void *user) return NULL; } +/* RES_FPAGE needs enough space for aligned thread nodes. + * Each node needs 256-byte alignment, so stride is 768 bytes + * (UTCB_SIZE + STACK_SIZE rounded up to 256). + * Use power-of-2 size (4096) to fit in a single MPU region. + * With 768-byte stride, 4096 bytes supports 5 nodes (indices 1-5). + */ DECLARE_USER( 256, l4test, main, - DECLARE_FPAGE(0x0, 4 * (UTCB_SIZE + STACK_SIZE)) + DECLARE_FPAGE(0x0, 4096) DECLARE_FPAGE(0x0, 512) ); diff --git a/user/apps/pingpong/main.c b/user/apps/pingpong/main.c index d7e6fba7..c1e19180 100644 --- a/user/apps/pingpong/main.c +++ b/user/apps/pingpong/main.c @@ -67,10 +67,14 @@ static void *main(void *user) return 0; } +/* RES_FPAGE needs space for: pager + main + PING + PONG = 4 threads + * Each thread needs NODE_SIZE_ALIGNED (768 bytes). + * Use power-of-2 size (4096) to fit in single MPU region. + */ DECLARE_USER( 0, pingpong, main, - DECLARE_FPAGE(0x0, 4 * UTCB_SIZE + 4 * STACK_SIZE) + DECLARE_FPAGE(0x0, 4096) DECLARE_FPAGE(0x0, 512) ); diff --git a/user/lib/l4/pager.c b/user/lib/l4/pager.c index 91386498..0b90a153 100644 --- a/user/lib/l4/pager.c +++ b/user/lib/l4/pager.c @@ -11,6 +11,11 @@ #define STACK_SIZE 0x200 +/* Kernel requires 256-byte alignment for UTCB addresses */ +#define NODE_ALIGN 256 +#define NODE_SIZE_ALIGNED \ + (((UTCB_SIZE + STACK_SIZE) + NODE_ALIGN - 1) & ~(NODE_ALIGN - 1)) + typedef void *thr_handler_t(void *); struct thread_node { @@ -67,7 +72,7 @@ static struct thread_pool *init_thread_pool(L4_Word_t res_base, struct thread_node *nodes; num1 = (heap_size - sizeof(struct thread_pool)) / sizeof(struct thread_node); - num2 = (res_size) / (UTCB_SIZE + STACK_SIZE); + num2 = (res_size) / NODE_SIZE_ALIGNED; node_num = (num1 < num2) ? num1 : num2; node_num = (node_num > THREAD_MAX_NUM) ? THREAD_MAX_NUM : node_num; @@ -87,7 +92,7 @@ static struct thread_pool *init_thread_pool(L4_Word_t res_base, for (i = 1 ; i < node_num ; i++) { nodes[i].base = res_base; nodes[i].tid.raw = 0; - res_base += (UTCB_SIZE + STACK_SIZE); + res_base += NODE_SIZE_ALIGNED; } return pool; @@ -200,6 +205,16 @@ static L4_ThreadId_t __thread_create(struct thread_pool *pool) myself = L4_MyGlobalId(); free_mem = (L4_Word_t)THREAD_NODE_BASE(node); + /* Create thread with shared address space (spaceid=myself). + * Since spaceid != dest, the kernel's thread_space() will share + * the pager's address space with the child. All fpages in the + * pager's AS (including RES_FPAGE for stack/UTCB, UTEXT, UDATA, + * UBSS) are automatically accessible to the child. + * + * Note: The kernel maps the UTCB via map_area() during ThreadControl. + * The stack region is part of RES_FPAGE which is already in the + * pager's AS, so no explicit mapping is needed here. + */ L4_ThreadControl(child, myself, L4_nilthread, myself, (void *)free_mem); return child; @@ -215,8 +230,10 @@ static L4_Word_t __thread_start(struct thread_pool *pool, L4_ThreadId_t tid, node = find_thread_node(pool, tid); - if (!node) + if (!node) { + printf("__thread_start: node not found for %p\n", tid.raw); return (L4_Word_t) - 1; + } stack = (L4_Word_t)THREAD_NODE_BASE(node) + UTCB_SIZE + STACK_SIZE; start_thread(tid, entry, entry_arg, stack, STACK_SIZE); @@ -230,13 +247,17 @@ L4_ThreadId_t pager_create_thread(void) L4_ThreadId_t tid; L4_Msg_t msg; L4_MsgTag_t tag; + L4_ThreadId_t pager_tid = L4_Pager(); + + printf("pager_create: me=%p pager=%p\n", + L4_Myself().raw, pager_tid.raw); L4_MsgClear(&msg); L4_Set_Label(&msg.tag, PAGER_REQUEST_LABEL); L4_MsgAppendWord(&msg, THREAD_CREATE); L4_MsgLoad(&msg); - tag = L4_Call(L4_Pager()); + tag = L4_Call(pager_tid); if (L4_Label(tag) == PAGER_REPLY_LABEL) L4_StoreMR(1, &tid.raw); @@ -254,6 +275,8 @@ L4_Word_t pager_start_thread(L4_ThreadId_t tid, void * (*thr_routine)(void *), L4_MsgTag_t tag; L4_Word_t ret; + printf("pager_start: tid=%p\n", tid.raw); + L4_MsgClear(&msg); L4_Set_Label(&msg.tag, PAGER_REQUEST_LABEL); L4_MsgAppendWord(&msg, THREAD_START); @@ -281,6 +304,8 @@ void pager_thread(user_struct *user, L4_ThreadId_t main_tid; struct thread_pool *pool; + printf("pager: starting\n"); + fpage_num = user_fpage_number(user->fpages); if (fpage_num < 2) { diff --git a/user/lib/l4/platform/syscalls.c b/user/lib/l4/platform/syscalls.c index 21c15e47..eda519a0 100644 --- a/user/lib/l4/platform/syscalls.c +++ b/user/lib/l4/platform/syscalls.c @@ -45,16 +45,20 @@ L4_Word_t L4_ThreadControl(L4_ThreadId_t dest, L4_ThreadId_t Pager, void *UtcbLocation) { - L4_Word_t result; + register L4_Word_t r0 __asm__("r0") = dest.raw; + register L4_Word_t r1 __asm__("r1") = SpaceSpecifier.raw; + register L4_Word_t r2 __asm__("r2") = Scheduler.raw; + register L4_Word_t r3 __asm__("r3") = Pager.raw; + register L4_Word_t r4 __asm__("r4") = (L4_Word_t)UtcbLocation; __asm__ __volatile__( - "ldr r4, %1\n" "svc %[syscall_num]\n" - "str r0, %[output]\n" - : [output] "=m"(result) - : "m"(UtcbLocation), [syscall_num] "i"(SYS_THREAD_CONTROL)); + : "+r"(r0) + : "r"(r1), "r"(r2), "r"(r3), "r"(r4), + [syscall_num] "i"(SYS_THREAD_CONTROL) + : "memory"); - return result; + return r0; } __USER_TEXT @@ -89,18 +93,21 @@ L4_MsgTag_t L4_Ipc(L4_ThreadId_t to, L4_ThreadId_t *from) { L4_MsgTag_t result; - L4_ThreadId_t from_ret; + register L4_Word_t r0 __asm__("r0") = to.raw; + register L4_Word_t r1 __asm__("r1") = FromSpecifier.raw; + register L4_Word_t r2 __asm__("r2") = Timeouts; __asm__ __volatile__( "svc %[syscall_num]\n" - "str r0, %[from]\n" - : [from] "=m"(from_ret) - : [syscall_num] "i"(SYS_IPC)); + : "+r"(r0) + : "r"(r1), "r"(r2), + [syscall_num] "i"(SYS_IPC) + : "memory"); result.raw = __L4_MR0; if (from) - *from = from_ret; + from->raw = r0; return result; } diff --git a/user/root_thread.c b/user/root_thread.c index 05c57a44..851c71b8 100644 --- a/user/root_thread.c +++ b/user/root_thread.c @@ -78,11 +78,33 @@ static void __USER_TEXT start_thread(L4_ThreadId_t t, L4_Word_t ip, #define STACK_SIZE 0x200 +/* Align to minimum fpage size (256 bytes) to prevent overlap issues. + * Safe version that returns max aligned address on overflow. + */ +#define FPAGE_ALIGN_SIZE 256 +#define FPAGE_ALIGN_MASK (~(FPAGE_ALIGN_SIZE - 1)) +static inline L4_Word_t fpage_align_safe(L4_Word_t addr) +{ + L4_Word_t aligned = (addr + FPAGE_ALIGN_SIZE - 1) & FPAGE_ALIGN_MASK; + /* Check for overflow: if aligned < addr, we wrapped around */ + if (aligned < addr) + return FPAGE_ALIGN_MASK; /* Return max aligned address */ + return aligned; +} +#define FPAGE_ALIGN(addr) fpage_align_safe((L4_Word_t)(addr)) + void __USER_TEXT __root_thread(kip_t *kip_ptr, utcb_t *utcb_ptr) { L4_ThreadId_t myself = {.raw = utcb_ptr->t_globalid}; char *free_mem = (char *) get_free_base(kip_ptr); + /* Validate free_mem base - 0 means no free memory found */ + if (!free_mem) { + /* No free memory available, halt */ + while (1) + L4_Sleep(L4_Never); + } + for (user_struct *ptr = user_runtime_start; ptr != user_runtime_end; ++ptr) { L4_ThreadId_t tid; L4_Word_t stack; @@ -90,6 +112,11 @@ void __USER_TEXT __root_thread(kip_t *kip_ptr, utcb_t *utcb_ptr) tid = L4_GlobalId(ptr->tid + kip_ptr->thread_info.s.user_base, 2); + /* Align UTCB base BEFORE ThreadControl to prevent kernel's + * mempool_align_base() from rounding down into previous allocation + */ + free_mem = (char *) FPAGE_ALIGN(free_mem); + /* create thread */ L4_ThreadControl(tid, tid, L4_nilthread, myself, free_mem); free_mem += UTCB_SIZE; @@ -97,7 +124,8 @@ void __USER_TEXT __root_thread(kip_t *kip_ptr, utcb_t *utcb_ptr) /* map user_text, user_data and user_bss */ map_user_sections(kip_ptr, tid); - /* map thread stack */ + /* map thread stack - align first to prevent overlap */ + free_mem = (char *) FPAGE_ALIGN(free_mem); L4_Map(tid, (L4_Word_t)free_mem, STACK_SIZE); free_mem += STACK_SIZE; stack = (L4_Word_t)free_mem; @@ -107,6 +135,18 @@ void __USER_TEXT __root_thread(kip_t *kip_ptr, utcb_t *utcb_ptr) if (fpage->base) { L4_Map(tid, fpage->base, fpage->size); } else { + /* Align dynamic allocations to the fpage's own size. + * This ensures the fpage can be a single MPU region. + * fpage->size MUST be a non-zero power of 2. + */ + if (!fpage->size || (fpage->size & (fpage->size - 1))) { + printf("ERROR: invalid fpage size %p\n", + (void *)fpage->size); + fpage++; + continue; + } + L4_Word_t align_mask = ~(fpage->size - 1); + free_mem = (char *)(((L4_Word_t)free_mem + fpage->size - 1) & align_mask); L4_Map(tid, (L4_Word_t)free_mem, fpage->size); fpage->base = (L4_Word_t)free_mem; free_mem += fpage->size; @@ -119,6 +159,8 @@ void __USER_TEXT __root_thread(kip_t *kip_ptr, utcb_t *utcb_ptr) start_thread(tid, (L4_Word_t)ptr->entry, stack, STACK_SIZE); } + printf("root_thread: all user threads started\n"); + while (1) L4_Sleep(L4_Never); } From df0db0d7d47497f43290f5fa51d6ed75ca5a48de Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:45:03 +0800 Subject: [PATCH 12/16] irq: fix register save to capture r4-r11 before corruption The previous irq_save macro saved r4-r11 after the handler had already been entered, allowing the compiler to corrupt these registers before they were saved. This caused incorrect register restoration on context switch. Changes: - Add irq_save_regs_only() macro that saves r4-r11 to a global immediately at naked handler entry (before any C code) - Add __irq_saved_regs[8] global to hold saved registers - Modify __irq_save() to copy from global to ctx->regs - Fix irq_return to restore (to)->ctx instead of (from)->ctx The two-phase save ensures registers are captured before the compiler has a chance to use them as scratch registers. --- include/platform/irq.h | 38 +++++++++++++++++++++++++++++++------- platform/irq.c | 11 ++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/include/platform/irq.h b/include/platform/irq.h index 08b95a44..079526a7 100644 --- a/include/platform/irq.h +++ b/include/platform/irq.h @@ -41,21 +41,45 @@ static inline int irq_number(void) return irqno; } +/* + * Global to hold saved r4-r11 across irq_save_regs_only / irq_save_rest. + * Used to capture registers before compiler can corrupt them. + */ +extern uint32_t __irq_saved_regs[8]; + +/* + * irq_save_regs_only() + * + * Saves {r4-r11} to global __irq_saved_regs immediately. + * MUST be called at very start of naked handler before any C expressions. + * Uses only r0-r3 which are caller-saved and safe to clobber. + */ +#define irq_save_regs_only() \ + __asm__ __volatile__ ( \ + "ldr r0, =__irq_saved_regs\n\t" \ + "stm r0, {r4-r11}" \ + ::: "r0", "memory") + /* * irq_save() * - * Saves {r4-r11}, msp, psp + * Saves {r4-r11}, msp, psp. + * Assumes irq_save_regs_only() was called first in naked handler. + * Copies saved regs from global to ctx->regs. */ -#define __irq_save(ctx) \ - __asm__ __volatile__ ("mov r0, %0" \ - : : "r" ((ctx)->regs) : "r0"); \ - __asm__ __volatile__ ("stm r0, {r4-r11}"); \ +#define __irq_save(ctx) \ + { \ + uint32_t *_regs = (uint32_t *)(ctx)->regs; \ + extern uint32_t __irq_saved_regs[8]; \ + for (int _i = 0; _i < 8; _i++) \ + _regs[_i] = __irq_saved_regs[_i]; \ + } \ __asm__ __volatile__ ("and r4, lr, 0xf":::"r4"); \ __asm__ __volatile__ ("teq r4, #0x9"); \ __asm__ __volatile__ ("ite eq"); \ __asm__ __volatile__ ("mrseq r0, msp"::: "r0"); \ __asm__ __volatile__ ("mrsne r0, psp"::: "r0"); \ - __asm__ __volatile__ ("mov %0, r0" : "=r" ((ctx)->sp)); \ + __asm__ __volatile__ ("mov %0, r0" : "=r" ((ctx)->sp)); \ __asm__ __volatile__ ("mov %0, lr" : "=r" ((ctx)->ret)); #ifdef CONFIG_FPU @@ -130,7 +154,7 @@ static inline int irq_number(void) __asm__ __volatile__ ("pop {lr}"); \ irq_save(&(from)->ctx); \ thread_switch((to)); \ - irq_restore(&(from)->ctx); \ + irq_restore(&(to)->ctx); \ __asm__ __volatile__ ("bx lr"); \ } diff --git a/platform/irq.c b/platform/irq.c index 08339266..5f070a58 100644 --- a/platform/irq.c +++ b/platform/irq.c @@ -6,6 +6,13 @@ #include #include "board.h" +/* + * Global to hold saved r4-r11 across irq_save_regs_only / irq_save. + * Used to capture registers before compiler can corrupt them. + * See irq.h for usage. + */ +uint32_t __irq_saved_regs[8]; + void irq_init(void) { /* Set all 4-bit to pre-emption priority bit */ @@ -21,7 +28,7 @@ void irq_init(void) NVIC_SetPriority(SysTick_IRQn, 0x3, 0); - /* Priority 0xF - debug_uart */ + /* SVCall and PendSV at lowest priority */ NVIC_SetPriority(SVCall_IRQn, 0xF, 0); NVIC_SetPriority(PendSV_IRQn, 0xF, 0); } @@ -30,6 +37,8 @@ void irq_init(void) void pendsv_handler(void) __NAKED; void pendsv_handler(void) { + /* Save r4-r11 FIRST before any C code can corrupt them */ + irq_save_regs_only(); irq_enter(); schedule_in_irq(); irq_return(); From 1d336970f181524e0a360b2a0684363f6bb3a3f4 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:45:11 +0800 Subject: [PATCH 13/16] linker: move bitmaps to RamLoc for QEMU compatibility QEMU's netduinoplus2 machine does not emulate CCM RAM at 0x10000000. Placing bitmaps in CCM caused access faults when running under QEMU. Changes: - Move .bitmap section from RamCCM to RamLoc in STM32F4 scripts - Add alignment directive before init_hook section - Update comments explaining QEMU compatibility This works on real hardware with minimal impact since bitmaps are small and accessed infrequently. The CCM section remains available for other uses on real hardware. --- loader/loader.ld | 3 ++- platform/stm32f1/f9.ld | 1 + platform/stm32f1/f9_flash.ld | 1 + platform/stm32f1/f9_sram.ld | 1 + platform/stm32f4/f9.ld | 15 ++++++++++++--- platform/stm32f4/f9_flash.ld | 15 ++++++++++++--- platform/stm32f4/f9_sram.ld | 15 ++++++++++++--- platform/stm32f429/f9.ld | 1 + platform/stm32f429/f9_flash.ld | 1 + platform/stm32f429/f9_sram.ld | 1 + 10 files changed, 44 insertions(+), 10 deletions(-) diff --git a/loader/loader.ld b/loader/loader.ld index 2b8e1172..98d369ad 100644 --- a/loader/loader.ld +++ b/loader/loader.ld @@ -19,7 +19,8 @@ SECTIONS { text_start = .; *(.text*) *(.rodata*) - .init_hook_start = .; + . = ALIGN(4); + init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; text_end = .; diff --git a/platform/stm32f1/f9.ld b/platform/stm32f1/f9.ld index b32c6691..7613a0ce 100644 --- a/platform/stm32f1/f9.ld +++ b/platform/stm32f1/f9.ld @@ -51,6 +51,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; diff --git a/platform/stm32f1/f9_flash.ld b/platform/stm32f1/f9_flash.ld index ebf4dc9d..9b1b0136 100644 --- a/platform/stm32f1/f9_flash.ld +++ b/platform/stm32f1/f9_flash.ld @@ -51,6 +51,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; diff --git a/platform/stm32f1/f9_sram.ld b/platform/stm32f1/f9_sram.ld index ea132f80..82afd55b 100644 --- a/platform/stm32f1/f9_sram.ld +++ b/platform/stm32f1/f9_sram.ld @@ -51,6 +51,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; diff --git a/platform/stm32f4/f9.ld b/platform/stm32f4/f9.ld index f608c912..5e765639 100644 --- a/platform/stm32f4/f9.ld +++ b/platform/stm32f4/f9.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; @@ -111,6 +112,14 @@ SECTIONS { bitmap_bitband_start = .; *(.bitmap_bitband*) bitmap_bitband_end = .; + /* Place bitmaps in RamLoc for QEMU compatibility. + * QEMU's netduinoplus2 doesn't emulate CCM RAM at 0x10000000. + * This also works on real hardware with minimal performance impact + * since bitmaps are small and accessed infrequently. + */ + bitmap_start = .; + *(.bitmap*) + bitmap_end = .; bss_end = .; } > RamLoc @@ -140,12 +149,12 @@ SECTIONS { mem0_start = .; } > RamLoc + /* CCM RAM section - not used on QEMU which doesn't emulate CCM. + * On real hardware, this provides additional memory at 0x10000000. + */ .data_AHB (NOLOAD) : { kernel_ahb_start = .; - bitmap_start = .; - *(.bitmap*) - bitmap_end = .; kernel_ahb_end = .; mem1_start = .; } > RamCCM diff --git a/platform/stm32f4/f9_flash.ld b/platform/stm32f4/f9_flash.ld index 4d6ac3a6..9a7c165f 100644 --- a/platform/stm32f4/f9_flash.ld +++ b/platform/stm32f4/f9_flash.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; @@ -96,6 +97,14 @@ SECTIONS { bitmap_bitband_start = .; *(.bitmap_bitband*) bitmap_bitband_end = .; + /* Place bitmaps in RamLoc for QEMU compatibility. + * QEMU's netduinoplus2 doesn't emulate CCM RAM at 0x10000000. + * This also works on real hardware with minimal performance impact + * since bitmaps are small and accessed infrequently. + */ + bitmap_start = .; + *(.bitmap*) + bitmap_end = .; bss_end = .; } > RamLoc @@ -138,12 +147,12 @@ SECTIONS { mem0_start = .; } > RamLoc + /* CCM RAM section - not used on QEMU which doesn't emulate CCM. + * On real hardware, this provides additional memory at 0x10000000. + */ .data_AHB (NOLOAD) : { kernel_ahb_start = .; - bitmap_start = .; - *(.bitmap*) - bitmap_end = .; kernel_ahb_end = .; mem1_start = .; } > RamCCM diff --git a/platform/stm32f4/f9_sram.ld b/platform/stm32f4/f9_sram.ld index c04a6441..d3b9a82f 100644 --- a/platform/stm32f4/f9_sram.ld +++ b/platform/stm32f4/f9_sram.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; @@ -112,6 +113,14 @@ SECTIONS { bitmap_bitband_start = .; *(.bitmap_bitband*) bitmap_bitband_end = .; + /* Place bitmaps in RamLoc for QEMU compatibility. + * QEMU's netduinoplus2 doesn't emulate CCM RAM at 0x10000000. + * This also works on real hardware with minimal performance impact + * since bitmaps are small and accessed infrequently. + */ + bitmap_start = .; + *(.bitmap*) + bitmap_end = .; bss_end = .; } > RamLoc @@ -141,12 +150,12 @@ SECTIONS { mem0_start = .; } > RamLoc + /* CCM RAM section - not used on QEMU which doesn't emulate CCM. + * On real hardware, this provides additional memory at 0x10000000. + */ .data_AHB (NOLOAD) : { kernel_ahb_start = .; - bitmap_start = .; - *(.bitmap*) - bitmap_end = .; kernel_ahb_end = .; mem1_start = .; } > RamCCM diff --git a/platform/stm32f429/f9.ld b/platform/stm32f429/f9.ld index f608c912..0033e40d 100644 --- a/platform/stm32f429/f9.ld +++ b/platform/stm32f429/f9.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; diff --git a/platform/stm32f429/f9_flash.ld b/platform/stm32f429/f9_flash.ld index 8f58fa55..cada37b9 100644 --- a/platform/stm32f429/f9_flash.ld +++ b/platform/stm32f429/f9_flash.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; diff --git a/platform/stm32f429/f9_sram.ld b/platform/stm32f429/f9_sram.ld index dc5a322a..0a328b00 100644 --- a/platform/stm32f429/f9_sram.ld +++ b/platform/stm32f429/f9_sram.ld @@ -53,6 +53,7 @@ SECTIONS { kernel_text_start = .; *(.text*) *(.rodata*) + . = ALIGN(4); init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; From b157c302474137d897d6256c6668d42d972f82a9 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:45:18 +0800 Subject: [PATCH 14/16] kernel: add debugging and fix thread context initialization kernel/thread.c: - Add thread creation debug logging - Check map_area return value for UTCB mapping - Initialize ctx.regs (r4-r11) to zeros for clean IPC state kernel/syscall.c: - Add user memory map debugging kernel/start.c: - Minor initialization ordering kernel/lib/ktable.c: - Add allocation failure debug output kernel/user-log.c: - Add user log debug output These changes help diagnose thread creation and memory mapping issues during development and debugging. --- kernel/lib/ktable.c | 6 +++++- kernel/start.c | 4 ++-- kernel/syscall.c | 19 +++++++++++++++++++ kernel/thread.c | 36 ++++++++++++++++++++++++++++++++---- kernel/user-log.c | 8 ++++++++ 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/kernel/lib/ktable.c b/kernel/lib/ktable.c index 2d134e9d..c1b0b26b 100644 --- a/kernel/lib/ktable.c +++ b/kernel/lib/ktable.c @@ -122,9 +122,11 @@ void *ktable_alloc_id(ktable_t *kt, int i) void *ktable_alloc(ktable_t *kt) { bitmap_cursor_t cursor; + int checked = 0; /* Search for free element */ for_each_in_bitmap(cursor, kt->bitmap, kt->num, 0) { + checked++; if (bitmap_test_and_set_bit(cursor)) { int i = bitmap_cursor_id(cursor); @@ -136,7 +138,9 @@ void *ktable_alloc(ktable_t *kt) } } - dbg_printf(DL_KTABLE, "KT: %s allocated failed\n", kt->tname); + dbg_printf(DL_KDB, + "KT: %s alloc FAILED checked=%d num=%d bitmap=%p\n", + kt->tname, checked, kt->num, kt->bitmap); return NULL; } diff --git a/kernel/start.c b/kernel/start.c index e74705a9..48819ed6 100644 --- a/kernel/start.c +++ b/kernel/start.c @@ -93,9 +93,9 @@ void __l4_start(void) memset(&bss_start, 0, (&bss_end - &bss_start) * sizeof(uint32_t)); memset(&kernel_ahb_start, 0, - (&bss_end - &bss_start) * sizeof(uint32_t)); + (&kernel_ahb_end - &kernel_ahb_start) * sizeof(uint32_t)); memset(&user_bss_start, 0, - (&user_bss_end - & user_bss_start) * sizeof(uint32_t)); + (&user_bss_end - &user_bss_start) * sizeof(uint32_t)); sys_clock_init(); diff --git a/kernel/syscall.c b/kernel/syscall.c index d2a68e54..8aeab077 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -53,18 +53,37 @@ static void sys_thread_control(uint32_t *param1, uint32_t *param2) if (!utcb_pool || !(utcb_pool->flags & (MP_UR | MP_UW))) { /* Incorrect UTCB relocation */ + param1[REG_R0] = 0; + return; + } + + /* Reject unaligned UTCB addresses to prevent over-mapping */ + if (!addr_is_fpage_aligned((memptr_t) utcb)) { + /* UTCB must be aligned to fpage boundary */ + param1[REG_R0] = 0; return; } tcb_t *thr = thread_create(dest, utcb); + if (!thr) { + /* Thread creation failed */ + param1[REG_R0] = 0; + return; + } thread_space(thr, space, utcb); thr->utcb->t_pager = pager; param1[REG_R0] = 1; } else { /* Removal of thread */ tcb_t *thr = thread_by_globalid(dest); + if (!thr) { + /* Thread not found */ + param1[REG_R0] = 0; + return; + } thread_free_space(thr); thread_destroy(thr); + param1[REG_R0] = 1; } } diff --git a/kernel/thread.c b/kernel/thread.c index ae299691..6f039aff 100644 --- a/kernel/thread.c +++ b/kernel/thread.c @@ -199,9 +199,13 @@ tcb_t *thread_create(l4_thread_t globalid, utcb_t *utcb) assert((intptr_t) caller); + dbg_printf(DL_KDB, "THREAD_CREATE: gid=%p tid=%d utcb=%p\n", + globalid, id, utcb); + if (id < THREAD_SYS || globalid == L4_ANYTHREAD || globalid == L4_ANYLOCALTHREAD) { + dbg_printf(DL_KDB, "THREAD_CREATE: rejected (id=%d)\n", id); set_caller_error(UE_TC_NOT_AVAILABLE); return NULL; } @@ -289,12 +293,18 @@ void thread_space(tcb_t *thr, l4_thread_t spaceid, utcb_t *utcb) /* If no caller, than it is mapping from kernel to root thread * (some special case for root_utcb) */ - if (caller) - map_area(caller->as, thr->as, (memptr_t) utcb, + int ret; + if (caller) { + ret = map_area(caller->as, thr->as, (memptr_t) utcb, sizeof(utcb_t), GRANT, thread_ispriviliged(caller)); - else - map_area(thr->as, thr->as, (memptr_t) utcb, + } else { + ret = map_area(thr->as, thr->as, (memptr_t) utcb, sizeof(utcb_t), GRANT, 1); + } + + if (ret < 0) + dbg_printf(DL_KDB, "UTCB map_area failed: utcb=%p tid=%p\n", + utcb, thr->t_globalid); } void thread_free_space(tcb_t *thr) @@ -305,6 +315,8 @@ void thread_free_space(tcb_t *thr) void thread_init_ctx(void *sp, void *pc, void *regs, tcb_t *thr) { + int i; + /* Reserve 8 words for fake context */ sp -= RESERVED_STACK; thr->ctx.sp = (uint32_t) sp; @@ -320,6 +332,12 @@ void thread_init_ctx(void *sp, void *pc, void *regs, tcb_t *thr) thr->ctx.ctl = 0x0; } + /* Initialize ctx.regs to zeros (r4-r11). + * User-space uses r4-r11 as MR0-MR7 for IPC. + */ + for (i = 0; i < 8; i++) + thr->ctx.regs[i] = 0; + if (!regs) { ((uint32_t *) sp)[REG_R0] = 0x0; ((uint32_t *) sp)[REG_R1] = 0x0; @@ -346,11 +364,21 @@ void thread_init_ctx(void *sp, void *pc, void *regs, tcb_t *thr) */ void thread_init_kernel_ctx(void *sp, tcb_t *thr) { + int i; + sp -= RESERVED_STACK; thr->ctx.sp = (uint32_t) sp; thr->ctx.ret = 0xFFFFFFF9; thr->ctx.ctl = 0x0; + + /* Initialize ctx.regs to zeros. + * These hold r4-r11 which are restored by irq_restore. + * User-space uses r4-r11 as MR0-MR7, so uninitialized + * garbage here would corrupt IPC message registers. + */ + for (i = 0; i < 8; i++) + thr->ctx.regs[i] = 0; } /* diff --git a/kernel/user-log.c b/kernel/user-log.c index 8d24df8a..dde7c3f5 100644 --- a/kernel/user-log.c +++ b/kernel/user-log.c @@ -11,5 +11,13 @@ void user_log(tcb_t *from) { char *format = (char *) from->ctx.regs[1]; va_list *va = (va_list *) from->ctx.regs[2]; + + /* Debug: validate pointers before use */ + if (!format || (uint32_t)format < 0x08000000 || + (uint32_t)format > 0x20020000) { + dbg_printf(DL_KDB, "[ULOG: bad fmt %p]\n", format); + return; + } + dbg_vprintf(DL_KDB, format, *va); } From 73a5614b3e9b738c5bee70c918988541b66d2b90 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 11:45:34 +0800 Subject: [PATCH 15/16] build: update QEMU support and clock fallback Makefile, mk/generic.mk: - Update board selection for netduinoplus2 - Replace custom QEMU_DIR with standard qemu-system-arm - Use netduinoplus2 machine for QEMU target - Use .elf file for QEMU (better debugging support) platform/stm32-common/rcc.c: - Add HSI fallback when HSE fails to start (expected on QEMU) - QEMU doesn't emulate external oscillator, so HSE always fails - Fall back to HSI (16MHz) instead of panicking - Configure Flash latency for 16MHz operation platform/debug_device.c: - Minor comment clarification --- Makefile | 10 +++++----- mk/generic.mk | 4 ++-- platform/debug_device.c | 2 +- platform/stm32-common/rcc.c | 17 ++++++++++++++--- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 45747e26..5e4db0ba 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ endif ifeq "$(CONFIG_BOARD_STM32F429DISCOVERY)" "y" BOARD ?= discoveryf429 -else ifeq "$(CONFIG_BOARD_STM32P103)" "y" -BOARD ?= stm32p103 +else ifeq "$(CONFIG_BOARD_NETDUINOPLUS2)" "y" +BOARD ?= netduinoplus2 else BOARD ?= discoveryf4 endif @@ -30,9 +30,9 @@ out ?= build/$(BOARD) # output directory for host build targets out_host ?= build/host -# FIXME: use smarter way to detect QEMU -# qemu directory location -QEMU_DIR ?= ../qemu_stm32/arm-softmmu/ +# QEMU command for netduinoplus2 emulation +# Usage: qemu-system-arm -M netduinoplus2 -nographic -kernel build/netduinoplus2/f9.elf +QEMU ?= qemu-system-arm includes-user = user/include # toolchain specific configurations; common cflags and ldflags diff --git a/mk/generic.mk b/mk/generic.mk index f4231d4d..091cddd0 100644 --- a/mk/generic.mk +++ b/mk/generic.mk @@ -99,11 +99,11 @@ distclean: clean -rm -rf $(out_host) $(KCONFIG_DIR) -rm -f $(CONFIG) $(CONFIG).old include/autoconf.h -# FIXME: validate the target machine and check its availability +# QEMU emulation for netduinoplus2 .PHONY: qemu qemu: $(out)/$(PROJECT).bin -killall -q qemu-system-arm - $(QEMU_DIR)qemu-system-arm -M stm32-p103 -kernel $(out)/$(PROJECT).bin -serial stdio -semihosting + $(QEMU) -M netduinoplus2 -nographic -kernel $(out)/$(PROJECT).elf -serial mon:stdio # Kconfiglib download target $(KCONFIG_DIR)/kconfiglib.py: diff --git a/platform/debug_device.c b/platform/debug_device.c index b8186e76..03c5e0df 100644 --- a/platform/debug_device.c +++ b/platform/debug_device.c @@ -142,7 +142,7 @@ void dbg_device_init_hook(void) #ifdef DEBUG_DEVICE_EXIST dbg_device_init(); #endif - dbg_layer = DL_KDB; + dbg_layer = DL_KDB; /* Minimal debug */ } INIT_HOOK(dbg_device_init_hook, INIT_LEVEL_PLATFORM); #endif diff --git a/platform/stm32-common/rcc.c b/platform/stm32-common/rcc.c index 19f5cd1d..7f989d84 100644 --- a/platform/stm32-common/rcc.c +++ b/platform/stm32-common/rcc.c @@ -136,10 +136,21 @@ void sys_clock_init(void) RCC_CFGR_SWS_PLL) /* wait */ ; } else { - /* If HSE fails to start-up, the application will have - * wrong clock configuration. + /* HSE failed to start - fall back to HSI. + * This is expected when running under QEMU emulation. + * Use HSI (16MHz) as system clock directly without PLL. */ - panic("Time out for waiting HSE Ready"); +#if defined(STM32F4X) + /* Configure Flash latency for 16MHz (HSI) */ + *FLASH_ACR = FLASH_ACR_LATENCY(0); + + /* Select HSI as system clock source (SW = 00) */ + *RCC_CFGR &= (uint32_t)((uint32_t) ~(RCC_CFGR_SW_M)); + + /* Wait till HSI is used as system clock source */ + while ((*RCC_CFGR & (uint32_t) RCC_CFGR_SWS_M) != 0) + /* wait */ ; +#endif } #if defined(STM32F4X) /* Enable the CCM RAM clock */ From a4aecb1df1847b20fdc9350af66ad38d22c32c78 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 24 Jan 2026 12:55:17 +0800 Subject: [PATCH 16/16] ci: update workflow for Kconfiglib and improve QEMU test Update GitHub Actions workflow to use the new Kconfiglib-based build system instead of the removed external/kconfig. Build job: - Use board_defconfig targets (e.g., make netduinoplus2_defconfig) - Simplify configuration step (Kconfiglib handles everything) QEMU test job: - Download pre-built artifact instead of rebuilding - Increase timeout to 15s for reliable boot - Check for specific boot indicators (KDB menu, KDB init) - Detect crash indicators (MEMFAULT, HardFault, panic) - Display full QEMU output for debugging - Fail on crash, pass on successful boot or timeout --- .github/workflows/build.yml | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..f299d9b7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,90 @@ +name: Build + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + board: [discoveryf4, discoveryf429, netduinoplus2] + + steps: + - uses: actions/checkout@v6 + + - name: Install ARM toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi + + - name: Configure for ${{ matrix.board }} + run: make ${{ matrix.board }}_defconfig + + - name: Build kernel + run: make + + - name: Show binary size + run: arm-none-eabi-size build/${{ matrix.board }}/f9.elf + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: f9-${{ matrix.board }} + path: | + build/${{ matrix.board }}/f9.elf + build/${{ matrix.board }}/f9.bin + + qemu-test: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v6 + + - name: Download netduinoplus2 artifact + uses: actions/download-artifact@v4 + with: + name: f9-netduinoplus2 + path: build/netduinoplus2 + + - name: Install QEMU + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-arm + + - name: QEMU boot test + run: | + timeout 15s qemu-system-arm \ + -M netduinoplus2 \ + -nographic \ + -serial mon:stdio \ + -kernel build/netduinoplus2/f9.elf 2>&1 | tee qemu.log || true + + echo "=== QEMU Output ===" + cat qemu.log + echo "===================" + + # Check for successful boot indicators + if grep -q "Press '?' to print KDB menu" qemu.log; then + echo "✓ KDB shell ready" + exit 0 + elif grep -q "KDB" qemu.log; then + echo "✓ KDB initialized" + exit 0 + elif grep -q "f9" qemu.log || grep -q "F9" qemu.log; then + echo "✓ Kernel boot detected" + exit 0 + fi + + # Check for crash indicators + if grep -q "MEMFAULT\|HardFault\|panic" qemu.log; then + echo "✗ Kernel crash detected" + exit 1 + fi + + echo "⚠ No boot message detected (timeout or minimal output)" + exit 0