mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Optimize multiboot RAM with a safe Overlay
This commit is contained in:
1 parent
4ffff5149f
commit
5b6174085b
12 files changed
+299
-53
No files matched your search
+3
-3
@@ -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
@@ -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'
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
|
|||||||
Reference in new issue
Block a user