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 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 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/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 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/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/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 */ 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/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/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; 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/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; } diff --git a/kernel/sampling-kdb.c b/kernel/sampling-kdb.c index 791525a8..f9d9b7fa 100644 --- a/kernel/sampling-kdb.c +++ b/kernel/sampling-kdb.c @@ -37,18 +37,32 @@ 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"); + sampling_disable(); + init_failed = 1; + return; + } + + init++; return; } 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); } 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/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/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(); } 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/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/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(); 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); 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 */ 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; } 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 = .; 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); }