Optimize multiboot RAM with a safe Overlay

This commit is contained in:
Armel FAUVEAU committed 2026-08-19 17:52:47 +02:00
1 parent 4ffff5149f
commit 5b6174085b
12 files changed
+299 -53

No files matched your search

+3 -3
View File
@@ -241,10 +241,10 @@ enable_feature(ENABLE_FEAT_F4HWN_MULTIBOOT
driver/mb_flash.c driver/mb_flash.c
ui/multiboot.c ui/multiboot.c
) )
enable_feature(ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM) enable_feature(ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY)
if(ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM AND NOT ENABLE_FEAT_F4HWN_MULTIBOOT) if(ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY AND NOT ENABLE_FEAT_F4HWN_MULTIBOOT)
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM requires ENABLE_FEAT_F4HWN_MULTIBOOT.") message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY requires ENABLE_FEAT_F4HWN_MULTIBOOT.")
endif() endif()
enable_feature(ENABLE_FEAT_F4HWN_QRCODE) enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
+65 -19
View File
@@ -31,7 +31,6 @@
/* External SPI flash chip-select is on PA3 (see driver/py25q16.c). */ /* External SPI flash chip-select is on PA3 (see driver/py25q16.c). */
#define MB_CS_PIN (1u << 3) #define MB_CS_PIN (1u << 3)
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
/* LCD control pins used only for RAM-resident progress updates. */ /* LCD control pins used only for RAM-resident progress updates. */
#define MB_LCD_CS_PIN (1u << 2) /* PB2 */ #define MB_LCD_CS_PIN (1u << 2) /* PB2 */
#define MB_LCD_A0_PIN (1u << 6) /* PA6 */ #define MB_LCD_A0_PIN (1u << 6) /* PA6 */
@@ -40,7 +39,6 @@
#define MB_PROGRESS_COLS 118u #define MB_PROGRESS_COLS 118u
#define MB_PROGRESS_FIRST_COL 5u #define MB_PROGRESS_FIRST_COL 5u
#define MB_PROGRESS_FILLED 0x2Du #define MB_PROGRESS_FILLED 0x2Du
#endif
/* Number of erase/program retries per page before giving up (and resetting /* Number of erase/program retries per page before giving up (and resetting
* anyway - the region is already erased, so USB recovery is the only option). */ * anyway - the region is already erased, so USB recovery is the only option). */
@@ -103,14 +101,21 @@ static void MB_PrepareInternalFlash(void)
/* during which the flash bus is unavailable. It must therefore NOT fetch any */ /* during which the flash bus is unavailable. It must therefore NOT fetch any */
/* code from flash nor read any flash data: it uses raw register access only */ /* code from flash nor read any flash data: it uses raw register access only */
/* (no external calls), reads the source from the external SPI flash in */ /* (no external calls), reads the source from the external SPI flash in */
/* polled mode, and resets the MCU when done. It is placed in .RamFunc, which */ /* polled mode, and resets the MCU when done. Normally it is placed in */
/* the linker stores in flash and the startup copies to RAM alongside .data. */ /* .RamFunc, which startup copies to RAM alongside .data. With the overlay */
/* enabled it is linked in .MBRamFunc and copied over the PY25Q16 sector cache */
/* only immediately before use. */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* These primitives are called repeatedly while application flash is offline. /* These primitives are called repeatedly while application flash is offline.
* Keep one copy of each in the copied RAM section: noinline/noclone prevents * Keep one copy of each in the copied RAM section: noinline/noclone prevents
* GCC from silently duplicating one back into MB_RamReflash. */ * GCC from silently duplicating one back into MB_RamReflash. */
#define MB_RAM_HELPER __attribute__((section(".RamFunc"), noinline, noclone, used)) #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY
#define MB_RAM_SECTION ".MBRamFunc"
#else
#define MB_RAM_SECTION ".RamFunc"
#endif
#define MB_RAM_HELPER __attribute__((section(MB_RAM_SECTION), noinline, noclone, used))
/* Polled single-byte SPI2 transfer. The result is returned through out so /* Polled single-byte SPI2 transfer. The result is returned through out so
* timeout and received 0xFF remain distinguishable. */ * timeout and received 0xFF remain distinguishable. */
@@ -150,7 +155,6 @@ MB_RAM_HELPER __attribute__((noreturn)) static void mb_ram_reset(void)
for (;;) { } for (;;) { }
} }
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
/* Minimal SPI1 LCD writer. A display timeout merely disables progress updates: /* Minimal SPI1 LCD writer. A display timeout merely disables progress updates:
* it must never abort or delay the safety-critical flash copy. */ * it must never abort or delay the safety-critical flash copy. */
MB_RAM_HELPER static bool mb_ram_lcd_spi(uint8_t v) MB_RAM_HELPER static bool mb_ram_lcd_spi(uint8_t v)
@@ -220,9 +224,7 @@ __attribute__((always_inline)) static inline void mb_ram_lcd_clear(void)
GPIOB->BSRR = MB_LCD_CS_PIN; GPIOB->BSRR = MB_LCD_CS_PIN;
} }
} }
#endif /* !ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM */ __attribute__((section(MB_RAM_SECTION), noinline, used))
__attribute__((section(".RamFunc"), noinline, used))
static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize, static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize,
uint8_t *progressLine) uint8_t *progressLine)
{ {
@@ -231,14 +233,10 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
uint8_t buf[MB_FLASH_PAGE] __attribute__((aligned(4))); uint8_t buf[MB_FLASH_PAGE] __attribute__((aligned(4)));
uint32_t remaining = imageSize; uint32_t remaining = imageSize;
uint32_t regionRemaining = MB_INT_APP_SIZE; uint32_t regionRemaining = MB_INT_APP_SIZE;
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
uint32_t pagesDone = 0; uint32_t pagesDone = 0;
uint32_t progressAccumulator = 0; uint32_t progressAccumulator = 0;
uint32_t progressFilled = 0; uint32_t progressFilled = 0;
uint32_t lcdEnabled = progressLine != NULL; uint32_t lcdEnabled = progressLine != NULL;
#else
(void)progressLine;
#endif
__disable_irq(); __disable_irq();
@@ -349,7 +347,6 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
if (!success) if (!success)
goto fatal_reset; goto fatal_reset;
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
/* Advance the gauge without division (which could call a helper /* Advance the gauge without division (which could call a helper
* in erased flash). Refresh once per 8 KiB internal sector. */ * in erased flash). Refresh once per 8 KiB internal sector. */
if (lcdEnabled) if (lcdEnabled)
@@ -368,7 +365,6 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
if ((pagesDone & 31u) == 0u || regionRemaining == MB_FLASH_PAGE) if ((pagesDone & 31u) == 0u || regionRemaining == MB_FLASH_PAGE)
lcdEnabled = mb_ram_progress_blit(progressLine); lcdEnabled = mb_ram_progress_blit(progressLine);
} }
#endif
intAddr += MB_FLASH_PAGE; intAddr += MB_FLASH_PAGE;
extAddr += readSize; extAddr += readSize;
@@ -377,10 +373,8 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
} }
FLASH->CR |= FLASH_CR_LOCK; FLASH->CR |= FLASH_CR_LOCK;
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
if (lcdEnabled) if (lcdEnabled)
mb_ram_lcd_clear(); mb_ram_lcd_clear();
#endif
mb_ram_reset(); mb_ram_reset();
fatal_reset: fatal_reset:
@@ -399,6 +393,39 @@ fatal_reset:
/* Set when a polled SPI wait below times out (external flash unresponsive). */ /* Set when a polled SPI wait below times out (external flash unresponsive). */
static volatile int mb_spi_err; static volatile int mb_spi_err;
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY
/* Linker-provided load/execution bounds for the restore stub. Its execution
* address aliases the PY25Q16 sector cache; its load image remains in flash. */
extern uint8_t __mb_ramfunc_load_start;
extern uint8_t __mb_ramfunc_start;
extern uint8_t __mb_ramfunc_end;
static bool mb_load_ram_reflash_overlay(void)
{
const volatile uint8_t *src = &__mb_ramfunc_load_start;
volatile uint8_t *dst = &__mb_ramfunc_start;
volatile uint8_t *end = &__mb_ramfunc_end;
/* Volatile byte copies prevent a library memcpy call. The copy itself runs
* while internal flash is still fully available. */
while (dst < end)
*dst++ = *src++;
/* Cortex-M0+ has no instruction cache, but the barriers ensure that every
* store is visible before the following BLX starts fetching the stub. */
__DSB();
__ISB();
src = &__mb_ramfunc_load_start;
dst = &__mb_ramfunc_start;
while (dst < end)
if (*dst++ != *src++)
return false;
return true;
}
#endif
/* Polled single-byte SPI2 transfer (flash-resident; runs in normal context). /* Polled single-byte SPI2 transfer (flash-resident; runs in normal context).
* Bounded so a wedged SPI can never freeze the firmware: on timeout it sets * Bounded so a wedged SPI can never freeze the firmware: on timeout it sets
* mb_spi_err and returns 0xFF, letting the caller fail gracefully. */ * mb_spi_err and returns 0xFF, letting the caller fail gracefully. */
@@ -668,8 +695,27 @@ uint8_t MB_RestoreSlot(uint8_t slot, uint8_t *progress_line)
const uint32_t slotBase = MB_SLOT0_EXT_BASE + (uint32_t)slot * MB_SLOT_STRIDE; const uint32_t slotBase = MB_SLOT0_EXT_BASE + (uint32_t)slot * MB_SLOT_STRIDE;
/* Valid: the RAM stub copies exactly image_size bytes, pads the partial #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY
* page with 0xFF and erases the remainder of the application region. */ /* No PY25Q16 driver access is allowed after this point: the sector cache is
* about to become executable storage and the RAM stub always ends in reset.
* Mask IRQs first, then explicitly stop SPI2 DMA before overwriting the
* cache. Keeping both operations local avoids relying on validation's
* current polled-SPI implementation and closes any IRQ re-arm window. */
const uint32_t primask = __get_PRIMASK();
__disable_irq();
mb_spi_polled_mode();
PY25Q16_InvalidateCache();
if (!mb_load_ram_reflash_overlay())
{
__set_PRIMASK(primask);
return MB_ERR_RAM_LOAD;
}
#endif
/* Valid and, for the overlay path, safely loaded: the RAM stub copies
* exactly image_size bytes, pads the partial page with 0xFF and erases the
* remainder of the application region. Keep flash locked until this point
* so an overlay-copy failure can return without changing flash state. */
MB_PrepareInternalFlash(); MB_PrepareInternalFlash();
/* Call through a volatile pointer so the compiler emits an absolute 'blx' /* Call through a volatile pointer so the compiler emits an absolute 'blx'
+5 -5
View File
@@ -71,7 +71,7 @@ typedef struct __attribute__((packed)) {
uint8_t reserved[16]; /* pad to 64 bytes, future use */ uint8_t reserved[16]; /* pad to 64 bytes, future use */
} mb_slot_header_t; } mb_slot_header_t;
/* Restore validation result (MB_OK never returns - the radio resets). */ /* Multiboot operation result (a successful restore resets before returning). */
enum { enum {
MB_OK = 0, MB_OK = 0,
MB_ERR_MAGIC, /* no/invalid slot header */ MB_ERR_MAGIC, /* no/invalid slot header */
@@ -81,15 +81,15 @@ enum {
MB_ERR_CRC, /* image CRC32 mismatch */ MB_ERR_CRC, /* image CRC32 mismatch */
MB_ERR_SPI, /* external flash read/write timed out*/ MB_ERR_SPI, /* external flash read/write timed out*/
MB_ERR_SLOT, /* slot index out of range */ MB_ERR_SLOT, /* slot index out of range */
MB_ERR_AUTH /* write refused: timestamp mismatch */ MB_ERR_AUTH, /* write refused: timestamp mismatch */
MB_ERR_RAM_LOAD /* restore stub RAM copy mismatch */
}; };
/* Multi-slot API used by the boot selector. Validation always covers the full /* Multi-slot API used by the boot selector. Validation always covers the full
* image CRC before restore. progress_line may point to a 128-byte LCD page; the * image CRC before restore. progress_line may point to a 128-byte LCD page; the
* RAM copier then fills it while reflashing. Pass NULL to disable LCD updates. * RAM copier then fills it while reflashing. Pass NULL to disable LCD updates.
* The in-copier LCD progress code is compiled out when * With ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY, the copier is loaded over the
* ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM is defined (to reclaim RAM-resident * PY25Q16 sector cache only after validation and immediately before this call. */
* .RamFunc space); in that case progress_line is ignored. */
uint8_t MB_ValidateSlot(uint8_t slot, mb_slot_header_t *out_header, uint32_t *out_crc); uint8_t MB_ValidateSlot(uint8_t slot, mb_slot_header_t *out_header, uint32_t *out_crc);
uint8_t MB_RestoreSlot(uint8_t slot, uint8_t *progress_line); uint8_t MB_RestoreSlot(uint8_t slot, uint8_t *progress_line);
+8
View File
@@ -43,7 +43,15 @@
#define PAGE_SIZE 0x100 #define PAGE_SIZE 0x100
static uint32_t SectorCacheAddr = 0x1000000; static uint32_t SectorCacheAddr = 0x1000000;
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY
/* The restore-only RAM stub is copied over this cache immediately before it
* erases internal flash. A reset always follows, so the cache is never needed
* again after the overlay becomes active. */
static uint8_t SectorCache[SECTOR_SIZE]
__attribute__((section(".bss.mb_workspace"), aligned(4), used));
#else
static uint8_t SectorCache[SECTOR_SIZE]; static uint8_t SectorCache[SECTOR_SIZE];
#endif
static uint8_t BlackHole[4] __attribute__((aligned(4))); static uint8_t BlackHole[4] __attribute__((aligned(4)));
static volatile bool TC_Flag; static volatile bool TC_Flag;
+2 -1
View File
@@ -28,7 +28,8 @@ void PY25Q16_SectorErase(uint32_t Address);
/* Drop the internal single-sector write cache. Call after erasing/programming /* Drop the internal single-sector write cache. Call after erasing/programming
* flash behind the driver's back (e.g. the raw multiboot slot/profile ops) so a * flash behind the driver's back (e.g. the raw multiboot slot/profile ops) so a
* later write cannot skip or resurrect data based on a stale cached sector. */ * later write cannot skip or resurrect data based on a stale cached sector. It
* is also called before multiboot reuses the cache storage as a RAM overlay. */
void PY25Q16_InvalidateCache(void); void PY25Q16_InvalidateCache(void);
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT
+2 -9
View File
@@ -39,6 +39,7 @@ static const char *mb_error_text(uint8_t err)
case MB_ERR_SPI: return "SPI ERROR"; case MB_ERR_SPI: return "SPI ERROR";
case MB_ERR_SLOT: return "bad slot"; case MB_ERR_SLOT: return "bad slot";
case MB_ERR_AUTH: return "auth"; case MB_ERR_AUTH: return "auth";
case MB_ERR_RAM_LOAD: return "RAM LOAD ERROR";
default: return "error"; default: return "error";
} }
} }
@@ -262,12 +263,8 @@ __attribute__((noinline)) static void mb_prepare_progress_screen(const char *tit
UI_PrintStringSmallNormal(title, 2, 126, 1); UI_PrintStringSmallNormal(title, 2, 126, 1);
UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 3); UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 3);
UI_PrintStringSmallNormal(detail, 2, 126, 5); UI_PrintStringSmallNormal(detail, 2, 126, 5);
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM /* Empty gauge that the RAM copier fills as it reflashes. */
/* Empty gauge that the RAM copier fills as it reflashes. Without the
* in-copier progress code the bar would never move, so it is left out and
* the static "DO NOT POWER OFF" screen stands on its own. */
mb_draw_progress_outline(); mb_draw_progress_outline();
#endif
ST7565_BlitStatusLine(); ST7565_BlitStatusLine();
ST7565_BlitFullScreen(); ST7565_BlitFullScreen();
} }
@@ -409,11 +406,7 @@ void UI_MultibootSelector(void)
} }
mb_prepare_progress(selected); mb_prepare_progress(selected);
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
uint8_t err = MB_RestoreSlot(selected, gFrameBuffer[6]); uint8_t err = MB_RestoreSlot(selected, gFrameBuffer[6]);
#else
uint8_t err = MB_RestoreSlot(selected, NULL);
#endif
/* Only reached when the final pre-erase validation refused the slot. */ /* Only reached when the final pre-erase validation refused the slot. */
status[selected] = err; status[selected] = err;
+6 -11
View File
@@ -115,15 +115,10 @@ extern uint8_t _edata; // End of .data in RAM
extern uint8_t _sbss; // Start of .bss in RAM extern uint8_t _sbss; // Start of .bss in RAM
extern uint8_t _ebss; // End of .bss in RAM extern uint8_t _ebss; // End of .bss in RAM
// _eflash_used must be defined in the linker script immediately after the last // _eflash_used is defined by the linker at the end of the final section with a
// section with a FLASH load address (after .noncacheable). Example: // FLASH load image. This is currently .mb_ramfunc (empty without the overlay),
// // after the load images for .data and .noncacheable. It therefore gives the
// .noncacheable : { // exact byte count that the linker reports as FLASH used.
// ...
// } > RAM AT> FLASH
// _eflash_used = LOADADDR(.noncacheable) + SIZEOF(.noncacheable);
//
// This gives the exact byte count that the linker reports as FLASH used.
extern uint8_t _eflash_used; extern uint8_t _eflash_used;
// Absolute symbols: their *address* IS the numeric size value (ARM/CMSIS convention). // Absolute symbols: their *address* IS the numeric size value (ARM/CMSIS convention).
@@ -152,8 +147,8 @@ static void build_usage(uint32_t* ram_used, uint32_t* flash_used)
const uint32_t stack_size = (uint32_t)(uintptr_t)&_Min_Stack_Size; const uint32_t stack_size = (uint32_t)(uintptr_t)&_Min_Stack_Size;
*ram_used = span(&_sdata, &_ebss) + heap_size + stack_size; *ram_used = span(&_sdata, &_ebss) + heap_size + stack_size;
// FLASH: _eflash_used is placed by the linker script right after the last // FLASH: _eflash_used follows the final FLASH load image (.mb_ramfunc,
// section copied to FLASH (.data LMA + .noncacheable LMA). // after the .data and .noncacheable load images).
// Note: _etext is NOT usable here because this linker script places .rodata // Note: _etext is NOT usable here because this linker script places .rodata
// sections AFTER _etext, making it an unreliable end-of-flash marker. // sections AFTER _etext, making it an unreliable end-of-flash marker.
*flash_used = span((void*)FLASH_BASE, &_eflash_used); *flash_used = span((void*)FLASH_BASE, &_eflash_used);
+19
View File
@@ -122,6 +122,25 @@ endif()
# Post build processing # Post build processing
# #
# The restore stub executes while application flash is unavailable. Reject an
# overlay build if the linked stub branches outside its RAM section or if its
# input section refers to any external code/data. This is deliberately a hard
# build gate: either condition could make a restore unrecoverable without DFU.
if(ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY)
add_custom_command(
TARGET ${EXE_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
"-DOBJDUMP=${CMAKE_OBJDUMP}"
"-DELF=$<TARGET_FILE:${EXE_NAME}>"
"-DMAP=${CMAKE_CURRENT_BINARY_DIR}/${EXE_NAME}.map"
"-DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
-P "${CMAKE_SOURCE_DIR}/cmake/check_mb_ramfunc.cmake"
COMMENT "Checking multiboot RAM stub isolation"
VERBATIM
)
endif()
# Generate .bin file # Generate .bin file
add_custom_command( add_custom_command(
TARGET ${EXE_NAME} TARGET ${EXE_NAME}
+2 -2
View File
@@ -93,7 +93,7 @@
"ENABLE_FEAT_F4HWN_LOGO_SAV": false, "ENABLE_FEAT_F4HWN_LOGO_SAV": false,
"ENABLE_FEAT_F4HWN_MENU_CAT": false, "ENABLE_FEAT_F4HWN_MENU_CAT": false,
"ENABLE_FEAT_F4HWN_MULTIBOOT": false, "ENABLE_FEAT_F4HWN_MULTIBOOT": false,
"ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM": false, "ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY": false,
"ENABLE_AGC_SHOW_DATA": false, "ENABLE_AGC_SHOW_DATA": false,
"ENABLE_UART_RW_BK_REGS": false, "ENABLE_UART_RW_BK_REGS": false,
"ENABLE_SWD": false, "ENABLE_SWD": false,
@@ -144,7 +144,7 @@
"ENABLE_FEAT_F4HWN_MENU_CAT": true, "ENABLE_FEAT_F4HWN_MENU_CAT": true,
"ENABLE_FEAT_F4HWN_ACTION_PICKER": true, "ENABLE_FEAT_F4HWN_ACTION_PICKER": true,
"ENABLE_FEAT_F4HWN_MULTIBOOT": true, "ENABLE_FEAT_F4HWN_MULTIBOOT": true,
"ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM": false, "ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY": true,
"ENABLE_SWD": true, "ENABLE_SWD": true,
"EDITION_STRING": "Fusion", "EDITION_STRING": "Fusion",
"TARGET": "f4hwn.fusion" "TARGET": "f4hwn.fusion"
+48 -3
View File
@@ -159,7 +159,54 @@ SECTIONS
*(.noncacheable*) *(.noncacheable*)
. = ALIGN(4); . = ALIGN(4);
} >RAM AT> FLASH } >RAM AT> FLASH
_eflash_used = LOADADDR(.noncacheable) + SIZEOF(.noncacheable);
/* Optional multiboot overlay. The restore stub is linked at the same RAM
address as the 4 KiB PY25Q16 sector cache, but its load image remains in
FLASH and is copied into the cache only immediately before reflashing. */
OVERLAY : NOCROSSREFS AT (LOADADDR(.noncacheable) + SIZEOF(.noncacheable))
{
.mb_ramfunc
{
. = ALIGN(4);
__mb_ramfunc_start = .;
KEEP(*(.MBRamFunc))
KEEP(*(.MBRamFunc*))
. = ALIGN(4);
__mb_ramfunc_end = .;
}
.mb_workspace
{
. = ALIGN(4);
__mb_workspace_start = .;
KEEP(*(.bss.mb_workspace))
KEEP(*(.bss.mb_workspace*))
. = ALIGN(4);
__mb_workspace_end = .;
}
} >RAM
__mb_ramfunc_load_start = LOADADDR(.mb_ramfunc);
__mb_ramfunc_size = SIZEOF(.mb_ramfunc);
__mb_workspace_size = SIZEOF(.mb_workspace);
ASSERT(__mb_ramfunc_size <= __mb_workspace_size,
"Multiboot RAM stub does not fit in PY25Q16 sector-cache overlay")
ASSERT((__mb_ramfunc_size == 0) || (__mb_workspace_size == 0x1000),
"Multiboot overlay workspace is not the 4 KiB sector cache")
ASSERT((__mb_ramfunc_size == 0) ||
(ADDR(.mb_ramfunc) == ADDR(.mb_workspace)),
"Multiboot RAM stub and workspace do not share the same VMA")
ASSERT((__mb_ramfunc_size == 0) ||
(LOADADDR(.mb_ramfunc) != ADDR(.mb_ramfunc)),
"Multiboot RAM stub load and execution addresses must differ")
ASSERT((LOADADDR(.mb_ramfunc) + SIZEOF(.mb_ramfunc)) <=
(ORIGIN(FLASH) + LENGTH(FLASH)),
"FLASH overflowed by multiboot RAM-stub load image")
/* .mb_ramfunc is the final section with a FLASH load image. The workspace
member is NOLOAD/BSS storage and therefore contributes RAM only. */
_eflash_used = LOADADDR(.mb_ramfunc) + SIZEOF(.mb_ramfunc);
/* Uninitialized data section */ /* Uninitialized data section */
. = ALIGN(4); . = ALIGN(4);
@@ -200,5 +247,3 @@ SECTIONS
.ARM.attributes 0 : { *(.ARM.attributes) } .ARM.attributes 0 : { *(.ARM.attributes) }
} }
+138
View File
@@ -0,0 +1,138 @@
# Validate the multiboot restore stub after linking. The application flash is
# unavailable while this code runs, so every direct control-flow target and
# every symbolic code/data reference must remain inside .mb_ramfunc.
foreach(required OBJDUMP ELF MAP BINARY_DIR)
if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "")
message(FATAL_ERROR "check_mb_ramfunc: missing -D${required}=...")
endif()
endforeach()
if(NOT EXISTS "${ELF}")
message(FATAL_ERROR "check_mb_ramfunc: ELF not found: ${ELF}")
endif()
if(NOT EXISTS "${MAP}")
message(FATAL_ERROR "check_mb_ramfunc: map file not found: ${MAP}")
endif()
execute_process(
COMMAND "${OBJDUMP}" -h "${ELF}"
RESULT_VARIABLE headers_result
OUTPUT_VARIABLE headers
ERROR_VARIABLE headers_error
)
if(NOT headers_result EQUAL 0)
message(FATAL_ERROR
"check_mb_ramfunc: objdump section scan failed (${headers_result}):\n${headers_error}")
endif()
string(REGEX MATCH
"[ \t]\\.mb_ramfunc[ \t]+([0-9A-Fa-f]+)[ \t]+([0-9A-Fa-f]+)"
section_header "${headers}")
if(section_header STREQUAL "")
message(FATAL_ERROR "check_mb_ramfunc: .mb_ramfunc not found in ${ELF}")
endif()
set(section_size_hex "${CMAKE_MATCH_1}")
set(section_start_hex "${CMAKE_MATCH_2}")
math(EXPR section_size "0x${section_size_hex}")
math(EXPR section_start "0x${section_start_hex}")
math(EXPR section_end "${section_start} + ${section_size}")
math(EXPR section_end_hex "${section_end}" OUTPUT_FORMAT HEXADECIMAL)
if(section_size EQUAL 0)
message(FATAL_ERROR "check_mb_ramfunc: .mb_ramfunc is empty in an overlay build")
endif()
execute_process(
COMMAND "${OBJDUMP}" --no-show-raw-insn -d -j .mb_ramfunc "${ELF}"
RESULT_VARIABLE disassembly_result
OUTPUT_VARIABLE disassembly
ERROR_VARIABLE disassembly_error
)
if(NOT disassembly_result EQUAL 0)
message(FATAL_ERROR
"check_mb_ramfunc: disassembly failed (${disassembly_result}):\n${disassembly_error}")
endif()
# Check all Thumb branch forms. An indirect BLX/BX cannot be proven safe; only
# BX LR (a normal function return) is accepted. Direct branches must stay in the
# linked RAM section, including compiler-generated tail calls and veneers.
string(REPLACE "\n" ";" disassembly_lines "${disassembly}")
set(branch_count 0)
foreach(line IN LISTS disassembly_lines)
if(line MATCHES
"^[ \t]*[0-9A-Fa-f]+:[ \t]+([A-Za-z0-9.]+)([ \t]+(.*))?")
set(mnemonic "${CMAKE_MATCH_1}")
set(operands "${CMAKE_MATCH_3}")
if(mnemonic MATCHES
"^(b|bl|blx|bx|beq|bne|bcs|bcc|bhs|blo|bmi|bpl|bvs|bvc|bhi|bls|bge|blt|bgt|ble)(\\.[nw])?$"
OR mnemonic MATCHES "^(cbz|cbnz)$")
math(EXPR branch_count "${branch_count} + 1")
if(mnemonic MATCHES "^bx(\\.[nw])?$")
string(STRIP "${operands}" register_name)
if(NOT register_name STREQUAL "lr" AND NOT register_name STREQUAL "r14")
message(FATAL_ERROR
"check_mb_ramfunc: unsafe indirect branch in RAM stub:\n${line}")
endif()
continue()
endif()
# BL/B operands contain the address directly; CBZ/CBNZ put it after
# the register and comma. In both forms it is the final numeric
# operand before objdump's optional <symbol> annotation.
if(NOT operands MATCHES
"(^|[, \t])((0[xX])?[0-9A-Fa-f]+)([ \t]+<[^>]+>)?[ \t]*$")
message(FATAL_ERROR
"check_mb_ramfunc: unresolved/indirect branch in RAM stub:\n${line}")
endif()
set(target_hex "${CMAKE_MATCH_2}")
string(REGEX REPLACE "^0[xX]" "" target_hex "${target_hex}")
math(EXPR target "0x${target_hex}")
if(target LESS section_start OR NOT target LESS section_end)
message(FATAL_ERROR
"check_mb_ramfunc: branch leaves .mb_ramfunc:\n${line}\n"
"Allowed range: 0x${section_start_hex}..${section_end_hex}")
endif()
endif()
endif()
endforeach()
# Locate the exact input object from the linker map rather than assuming a
# generator-specific CMakeFiles path. Any relocation in .MBRamFunc would be an
# external symbolic reference (code or data), because all approved helpers share
# this same input section and their local branches are assembler-resolved.
file(READ "${MAP}" map_contents)
string(REGEX MATCH
"[^\r\n]*\\.MBRamFunc[^\r\n]*mb_flash\\.c\\.(obj|o)"
object_line "${map_contents}")
if(object_line STREQUAL "")
message(FATAL_ERROR
"check_mb_ramfunc: mb_flash object not found in ${MAP} "
"(the overlay safety gate requires a non-LTO input object)")
endif()
string(REGEX MATCH "[^ \t]+mb_flash\\.c\\.(obj|o)" object_path "${object_line}")
if(NOT IS_ABSOLUTE "${object_path}")
set(object_path "${BINARY_DIR}/${object_path}")
endif()
if(NOT EXISTS "${object_path}")
message(FATAL_ERROR "check_mb_ramfunc: input object not found: ${object_path}")
endif()
execute_process(
COMMAND "${OBJDUMP}" -r -j .MBRamFunc "${object_path}"
RESULT_VARIABLE relocations_result
OUTPUT_VARIABLE relocations
ERROR_VARIABLE relocations_error
)
if(NOT relocations_result EQUAL 0)
message(FATAL_ERROR
"check_mb_ramfunc: relocation scan failed (${relocations_result}):\n${relocations_error}")
endif()
if(relocations MATCHES "R_ARM_")
message(FATAL_ERROR
"check_mb_ramfunc: .MBRamFunc has an external code/data reference:\n${relocations}")
endif()
message(STATUS
"Multiboot RAM stub isolation OK: ${section_size} bytes, ${branch_count} checked branches")
+1
View File
@@ -13,6 +13,7 @@ set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}g++) set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy)
set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump)
set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size) set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size)
set(CMAKE_EXECUTABLE_SUFFIX_ASM ".elf") set(CMAKE_EXECUTABLE_SUFFIX_ASM ".elf")