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
|
||||
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)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM requires ENABLE_FEAT_F4HWN_MULTIBOOT.")
|
||||
if(ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY AND NOT ENABLE_FEAT_F4HWN_MULTIBOOT)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY requires ENABLE_FEAT_F4HWN_MULTIBOOT.")
|
||||
endif()
|
||||
|
||||
enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
|
||||
|
||||
+65
-19
@@ -31,7 +31,6 @@
|
||||
/* External SPI flash chip-select is on PA3 (see driver/py25q16.c). */
|
||||
#define MB_CS_PIN (1u << 3)
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
/* LCD control pins used only for RAM-resident progress updates. */
|
||||
#define MB_LCD_CS_PIN (1u << 2) /* PB2 */
|
||||
#define MB_LCD_A0_PIN (1u << 6) /* PA6 */
|
||||
@@ -40,7 +39,6 @@
|
||||
#define MB_PROGRESS_COLS 118u
|
||||
#define MB_PROGRESS_FIRST_COL 5u
|
||||
#define MB_PROGRESS_FILLED 0x2Du
|
||||
#endif
|
||||
|
||||
/* 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). */
|
||||
@@ -103,14 +101,21 @@ static void MB_PrepareInternalFlash(void)
|
||||
/* 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 */
|
||||
/* (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 */
|
||||
/* the linker stores in flash and the startup copies to RAM alongside .data. */
|
||||
/* polled mode, and resets the MCU when done. Normally it is placed in */
|
||||
/* .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.
|
||||
* Keep one copy of each in the copied RAM section: noinline/noclone prevents
|
||||
* 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
|
||||
* timeout and received 0xFF remain distinguishable. */
|
||||
@@ -150,7 +155,6 @@ MB_RAM_HELPER __attribute__((noreturn)) static void mb_ram_reset(void)
|
||||
for (;;) { }
|
||||
}
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
/* Minimal SPI1 LCD writer. A display timeout merely disables progress updates:
|
||||
* it must never abort or delay the safety-critical flash copy. */
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif /* !ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM */
|
||||
|
||||
__attribute__((section(".RamFunc"), noinline, used))
|
||||
__attribute__((section(MB_RAM_SECTION), noinline, used))
|
||||
static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize,
|
||||
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)));
|
||||
uint32_t remaining = imageSize;
|
||||
uint32_t regionRemaining = MB_INT_APP_SIZE;
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
uint32_t pagesDone = 0;
|
||||
uint32_t progressAccumulator = 0;
|
||||
uint32_t progressFilled = 0;
|
||||
uint32_t lcdEnabled = progressLine != NULL;
|
||||
#else
|
||||
(void)progressLine;
|
||||
#endif
|
||||
|
||||
|
||||
__disable_irq();
|
||||
@@ -349,7 +347,6 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
|
||||
if (!success)
|
||||
goto fatal_reset;
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
/* Advance the gauge without division (which could call a helper
|
||||
* in erased flash). Refresh once per 8 KiB internal sector. */
|
||||
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)
|
||||
lcdEnabled = mb_ram_progress_blit(progressLine);
|
||||
}
|
||||
#endif
|
||||
|
||||
intAddr += MB_FLASH_PAGE;
|
||||
extAddr += readSize;
|
||||
@@ -377,10 +373,8 @@ static void MB_RamReflash(uint32_t intAddr, uint32_t extAddr, uint32_t imageSize
|
||||
}
|
||||
|
||||
FLASH->CR |= FLASH_CR_LOCK;
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
if (lcdEnabled)
|
||||
mb_ram_lcd_clear();
|
||||
#endif
|
||||
mb_ram_reset();
|
||||
|
||||
fatal_reset:
|
||||
@@ -399,6 +393,39 @@ fatal_reset:
|
||||
/* Set when a polled SPI wait below times out (external flash unresponsive). */
|
||||
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).
|
||||
* 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. */
|
||||
@@ -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;
|
||||
|
||||
/* Valid: the RAM stub copies exactly image_size bytes, pads the partial
|
||||
* page with 0xFF and erases the remainder of the application region. */
|
||||
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY
|
||||
/* 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();
|
||||
|
||||
/* 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 */
|
||||
} mb_slot_header_t;
|
||||
|
||||
/* Restore validation result (MB_OK never returns - the radio resets). */
|
||||
/* Multiboot operation result (a successful restore resets before returning). */
|
||||
enum {
|
||||
MB_OK = 0,
|
||||
MB_ERR_MAGIC, /* no/invalid slot header */
|
||||
@@ -81,15 +81,15 @@ enum {
|
||||
MB_ERR_CRC, /* image CRC32 mismatch */
|
||||
MB_ERR_SPI, /* external flash read/write timed out*/
|
||||
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
|
||||
* 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.
|
||||
* The in-copier LCD progress code is compiled out when
|
||||
* ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM is defined (to reclaim RAM-resident
|
||||
* .RamFunc space); in that case progress_line is ignored. */
|
||||
* With ENABLE_FEAT_F4HWN_MULTIBOOT_OVERLAY, the copier is loaded over the
|
||||
* PY25Q16 sector cache only after validation and immediately before this call. */
|
||||
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);
|
||||
|
||||
|
||||
@@ -43,7 +43,15 @@
|
||||
#define PAGE_SIZE 0x100
|
||||
|
||||
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];
|
||||
#endif
|
||||
static uint8_t BlackHole[4] __attribute__((aligned(4)));
|
||||
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
|
||||
* 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);
|
||||
|
||||
#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_SLOT: return "bad slot";
|
||||
case MB_ERR_AUTH: return "auth";
|
||||
case MB_ERR_RAM_LOAD: return "RAM LOAD 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("DO NOT POWER OFF", 2, 126, 3);
|
||||
UI_PrintStringSmallNormal(detail, 2, 126, 5);
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
/* 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. */
|
||||
/* Empty gauge that the RAM copier fills as it reflashes. */
|
||||
mb_draw_progress_outline();
|
||||
#endif
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
@@ -409,11 +406,7 @@ void UI_MultibootSelector(void)
|
||||
}
|
||||
|
||||
mb_prepare_progress(selected);
|
||||
#ifndef ENABLE_FEAT_F4HWN_MULTIBOOT_LOW_RAM
|
||||
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. */
|
||||
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 _ebss; // End of .bss in RAM
|
||||
|
||||
// _eflash_used must be defined in the linker script immediately after the last
|
||||
// section with a FLASH load address (after .noncacheable). Example:
|
||||
//
|
||||
// .noncacheable : {
|
||||
// ...
|
||||
// } > RAM AT> FLASH
|
||||
// _eflash_used = LOADADDR(.noncacheable) + SIZEOF(.noncacheable);
|
||||
//
|
||||
// This gives the exact byte count that the linker reports as FLASH used.
|
||||
// _eflash_used is defined by the linker at the end of the final section with a
|
||||
// FLASH load image. This is currently .mb_ramfunc (empty without the overlay),
|
||||
// after the load images for .data and .noncacheable. It therefore gives the
|
||||
// exact byte count that the linker reports as FLASH used.
|
||||
extern uint8_t _eflash_used;
|
||||
|
||||
// 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;
|
||||
*ram_used = span(&_sdata, &_ebss) + heap_size + stack_size;
|
||||
|
||||
// FLASH: _eflash_used is placed by the linker script right after the last
|
||||
// section copied to FLASH (.data LMA + .noncacheable LMA).
|
||||
// FLASH: _eflash_used follows the final FLASH load image (.mb_ramfunc,
|
||||
// after the .data and .noncacheable load images).
|
||||
// Note: _etext is NOT usable here because this linker script places .rodata
|
||||
// sections AFTER _etext, making it an unreliable end-of-flash marker.
|
||||
*flash_used = span((void*)FLASH_BASE, &_eflash_used);
|
||||
|
||||
Reference in new issue
Block a user