diff --git a/App/app/uart.c b/App/app/uart.c index 832c7b51..728c16e6 100644 --- a/App/app/uart.c +++ b/App/app/uart.c @@ -881,114 +881,6 @@ void UART_HandleCommand(uint32_t Port) break; #ifdef ENABLE_FEAT_F4HWN_MULTIBOOT - // Multiboot self-test (developer only, gated build). - case 0x0710: // backup: internal application -> external flash slot 0 - { - uint32_t size = 0, crc = 0; - MB_BackupToSlot0(&size, &crc); - struct __attribute__((packed)) { - Header_t Header; - uint32_t Size; - uint32_t Crc32; - } Reply; - Reply.Header.ID = 0x0711; - Reply.Header.Size = 8; - Reply.Size = size; - Reply.Crc32 = crc; - SendReply(Port, &Reply, sizeof(Reply)); // ack once the backup completed - break; - } - - case 0x0712: // restore: external flash slot 0 -> internal application + reset - { - // Returns only if validation failed (internal flash untouched); - // on success it reflashes and resets, so control never comes back. - uint8_t err = MB_RestoreSlot0(); - struct __attribute__((packed)) { - Header_t Header; - uint8_t Code; - } Reply; - Reply.Header.ID = 0x0713; - Reply.Header.Size = 1; - Reply.Code = err; - SendReply(Port, &Reply, sizeof(Reply)); // restore refused, report why - break; - } - - case 0x0714: // test: corrupt slot 0 image (exercise the CRC-refusal path) - { - uint16_t n = pUART_Command->Header.Size; - if (n > 128) - n = 128; - MB_CorruptSlot0(pUART_Command->Data, n); - struct __attribute__((packed)) { - Header_t Header; - uint16_t Count; - } Reply; - Reply.Header.ID = 0x0715; - Reply.Header.Size = 2; - Reply.Count = n; - SendReply(Port, &Reply, sizeof(Reply)); // ack: bytes corrupted - break; - } - - case 0x0716: // validate slot 0 (no reflash): report result code + computed CRC - { - uint32_t crc = 0; - uint8_t code = MB_ValidateSlot0(&crc); - struct __attribute__((packed)) { - Header_t Header; - uint32_t Crc32; // 4-byte aligned (offset 4): no unaligned write - uint8_t Code; - } Reply; - Reply.Header.ID = 0x0717; - Reply.Header.Size = 5; - Reply.Crc32 = crc; - Reply.Code = code; - SendReply(Port, &Reply, sizeof(Reply)); - break; - } - - case 0x0718: // ground-truth dump of external flash via the DMA driver - { - uint32_t addr = (uint32_t)pUART_Command->Data[0] - | ((uint32_t)pUART_Command->Data[1] << 8) - | ((uint32_t)pUART_Command->Data[2] << 16) - | ((uint32_t)pUART_Command->Data[3] << 24); - uint8_t len = pUART_Command->Data[4]; - if (len > 64) - len = 64; - struct __attribute__((packed)) { - Header_t Header; - uint32_t Addr; - uint8_t Len; - uint8_t Data[64]; - } Reply; - MB_DumpExt(addr, Reply.Data, len); - Reply.Header.ID = 0x0719; - Reply.Header.Size = 5 + len; // Addr(4) + Len(1) + data(len) - Reply.Addr = addr; - Reply.Len = len; - SendReply(Port, &Reply, sizeof(Header_t) + 5 + len); - break; - } - - case 0x071C: // diagnostic: snapshot SPI2 / DMA state (no flash access) - { - uint32_t st[4]; - MB_SpiState(st); - struct __attribute__((packed)) { - Header_t Header; - uint32_t V[4]; - } Reply; - Reply.Header.ID = 0x071D; - Reply.Header.Size = 16; - Reply.V[0] = st[0]; Reply.V[1] = st[1]; - Reply.V[2] = st[2]; Reply.V[3] = st[3]; - SendReply(Port, &Reply, sizeof(Reply)); - break; - } - // ---- M4 slot management ("Firmware Slots") ------------------------ case 0x0720: // slot info: read the 64-byte header only (fast, no CRC) { diff --git a/App/driver/mb_flash.c b/App/driver/mb_flash.c index 44453cd6..52abd97a 100644 --- a/App/driver/mb_flash.c +++ b/App/driver/mb_flash.c @@ -13,15 +13,11 @@ * limitations under the License. */ -#include +#include #include "driver/mb_flash.h" #include "py32f0xx.h" -#include "driver/py25q16.h" -#include "driver/st7565.h" -#include "ui/helper.h" -#include "version.h" /* Internal-flash program/erase keys (FLASH_KEY1 / FLASH_KEY2). */ #define MB_FLASH_KEY1 0x45670123u @@ -62,33 +58,6 @@ static const uint32_t mb_flash_timing[8] = { 0x1FFF32D8, 0x1FFF3238, 0x1FFF3238, 0x1FFF3238 }; -/* -------------------------------------------------------------------------- */ -/* Helpers (flash-resident). */ -/* -------------------------------------------------------------------------- */ - -/* zlib/PNG CRC-32 (poly 0xEDB88320), streaming. Seed 'crc' with 0xFFFFFFFF and - * XOR the final result with 0xFFFFFFFF. No lookup table (saves flash). */ -static uint32_t mb_crc32_update(uint32_t crc, const uint8_t *data, uint32_t len) -{ - while (len--) - { - crc ^= *data++; - for (int k = 0; k < 8; k++) - crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u))); - } - return crc; -} - -static void mb_copy_str(char *dst, uint32_t cap, const char *src) -{ - uint32_t i = 0; - if (src) - for (; i + 1 < cap && src[i]; i++) - dst[i] = src[i]; - for (; i < cap; i++) - dst[i] = 0; -} - /* -------------------------------------------------------------------------- */ /* Flash-resident preparation (runs while the flash is still readable). */ /* -------------------------------------------------------------------------- */ @@ -378,39 +347,6 @@ fatal_reset: /* Public API. */ /* -------------------------------------------------------------------------- */ -void MB_BackupToSlot0(uint32_t *out_size, uint32_t *out_crc32) -{ - const uint32_t imgBase = MB_SLOT0_EXT_BASE + MB_SLOT_IMG_OFFSET; - const uint32_t size = MB_INT_APP_SIZE; /* full region (self-test) */ - - /* CRC-32 of the internal image (memory-mapped, contiguous read). */ - uint32_t crc = mb_crc32_update(0xFFFFFFFFu, (const uint8_t *)MB_INT_APP_BASE, size) - ^ 0xFFFFFFFFu; - - /* Write the image first ... */ - PY25Q16_WriteBuffer(imgBase, (const void *)MB_INT_APP_BASE, size, false); - - /* ... then a valid header (COMMITTED) last, so a committed header always - * implies a fully written image. */ - mb_slot_header_t hdr; - memset(&hdr, 0, sizeof(hdr)); - hdr.magic = MB_SLOT_MAGIC; - hdr.hdr_version = MB_HDR_VERSION; - hdr.flags = MB_FLAG_COMMITTED; - hdr.image_size = size; - hdr.image_crc32 = crc; -#ifdef ENABLE_FEAT_F4HWN - mb_copy_str(hdr.name, MB_NAME_LEN, Edition); -#else - mb_copy_str(hdr.name, MB_NAME_LEN, "slot0"); -#endif - mb_copy_str(hdr.fw_version, MB_VERSION_LEN, Version); - PY25Q16_WriteBuffer(MB_SLOT0_EXT_BASE, &hdr, sizeof(hdr), false); - - if (out_size) *out_size = size; - if (out_crc32) *out_crc32 = crc; -} - /* Set when a polled SPI wait below times out (external flash unresponsive). */ static volatile int mb_spi_err; @@ -674,11 +610,6 @@ uint8_t MB_ValidateSlot(uint8_t slot, mb_slot_header_t *out_header, uint32_t *ou return mb_validate(slot, out_header ? out_header : &local, out_crc); } -uint8_t MB_ValidateSlot0(uint32_t *out_crc) -{ - return MB_ValidateSlot(0, NULL, out_crc); -} - uint8_t MB_RestoreSlot(uint8_t slot, uint8_t *progress_line) { mb_slot_header_t hdr; @@ -701,11 +632,6 @@ uint8_t MB_RestoreSlot(uint8_t slot, uint8_t *progress_line) return MB_OK; /* not reached */ } -uint8_t MB_RestoreSlot0(void) -{ - return MB_RestoreSlot(0, NULL); -} - /* -------------------------------------------------------------------------- */ /* M4 slot management (host tool). External flash only - never brick-critical.*/ /* -------------------------------------------------------------------------- */ @@ -750,49 +676,3 @@ uint8_t MB_SlotWrite(uint8_t slot, uint32_t offset, const uint8_t *data, uint32_ return MB_OK; } - -/* Middle of slot 0's image (32 KiB in, well within the 118 KiB image body). */ -#define MB_CORRUPT_OFFSET 0x8000u -#define MB_CORRUPT_MAX 128u - -void MB_CorruptSlot0(const uint8_t *data, uint32_t len) -{ - if (!data || len == 0) - return; - if (len > MB_CORRUPT_MAX) - len = MB_CORRUPT_MAX; - - /* Constrained to slot 0's image body: this address range cannot reach the - * slot header, nor calibration / EEPROM / RF log / voice regions. */ - PY25Q16_WriteBuffer(MB_SLOT0_EXT_BASE + MB_SLOT_IMG_OFFSET + MB_CORRUPT_OFFSET, - data, len, false); -} - -volatile uint8_t mb_mark_on = 0; - -void MB_Mark(const char *s) -{ - if (!mb_mark_on) - return; - memset(gFrameBuffer, 0, sizeof(gFrameBuffer)); - UI_PrintStringSmallNormal(s, 10, 0, 3); - ST7565_BlitFullScreen(); -} - -void MB_DumpExt(uint32_t addr, uint8_t *buf, uint32_t len) -{ - /* Trace the driver read step by step on the LCD so a freeze reveals where. */ - mb_mark_on = 1; - MB_Mark("DUMP begin"); - PY25Q16_ReadBufferSafe(addr, buf, len); - MB_Mark("DUMP done"); - mb_mark_on = 0; -} - -void MB_SpiState(uint32_t out[4]) -{ - out[0] = SPI2->CR1; /* bit 6 (SPE) = SPI enabled */ - out[1] = SPI2->CR2; /* bit0 RXDMAEN, bit1 TXDMAEN */ - out[2] = SPI2->SR; /* bit0 RXNE, bit1 TXE, bit7 BSY */ - out[3] = DMA1_Channel4->CCR; /* SPI2 RX DMA channel (bit0 EN) */ -} diff --git a/App/driver/mb_flash.h b/App/driver/mb_flash.h index 90a7e5a1..0a4afe8d 100644 --- a/App/driver/mb_flash.h +++ b/App/driver/mb_flash.h @@ -80,25 +80,6 @@ enum { MB_ERR_AUTH /* write refused: timestamp mismatch */ }; -/* - * Copy the live internal application image into external flash slot 0, writing - * the image first and then a valid header (COMMITTED) last. Runs entirely from - * flash and only writes the external SPI flash, so it is safe (no brick risk). - * Reports the stored image size and CRC-32 through the (optional) out params. - */ -void MB_BackupToSlot0(uint32_t *out_size, uint32_t *out_crc32); - -/* - * Validate slot 0 and, if valid, reflash the internal application from it and - * reset. Validation (magic, version, COMMITTED flag, size, image CRC-32) runs - * from flash *before* any erase: on failure it returns an MB_ERR_* code and the - * internal flash is left untouched. On success it NEVER RETURNS (the RAM-resident - * copier reflashes the internal application and triggers a system reset). The - * factory bootloader is never touched, so an interrupted copy is recoverable - * over USB/DFU. - */ -uint8_t MB_RestoreSlot0(void); - /* 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. */ @@ -123,46 +104,4 @@ uint8_t MB_SlotInfo(uint8_t slot, mb_slot_header_t *out_header); uint8_t MB_SlotErase(uint8_t slot); uint8_t MB_SlotWrite(uint8_t slot, uint32_t offset, const uint8_t *data, uint32_t len); - -/* - * Test helper: overwrite up to `len` (capped) bytes in the MIDDLE of slot 0's - * image with the supplied data, so the next MB_RestoreSlot0() fails its CRC-32 - * check and refuses. It writes ONLY inside slot 0's image body - never the - * header, and never anything outside the slot (calibration, EEPROM, RF log...). - * Developer aid to exercise the safe-refusal path without external tooling. - */ -void MB_CorruptSlot0(const uint8_t *data, uint32_t len); - -/* - * Validate slot 0 (header + image CRC-32) WITHOUT reflashing. Same checks as the - * restore path, entirely via polled reads (cannot hang). Returns MB_OK or an - * MB_ERR_* code, and reports the computed image CRC-32 through out_crc. - * Useful as a safe diagnostic from the host tool. - */ -uint8_t MB_ValidateSlot0(uint32_t *out_crc); - -/* - * Read `len` bytes of external flash at `addr` into `buf`, via the DMA driver - * (PY25Q16_ReadBuffer) - the same proven read path the backup uses internally. - * Ground-truth diagnostic to check what is actually stored in a slot. - */ -void MB_DumpExt(uint32_t addr, uint8_t *buf, uint32_t len); - -/* - * Diagnostic: snapshot the SPI2 / DMA hardware state WITHOUT any flash access - * (so it cannot hang). out[0]=SPI2->CR1, [1]=SPI2->SR, [2]=DMA RD chan CCR, - * [3]=DMA WR chan CCR. Reveals whether SPI2 is disabled/busy or a DMA channel - * is left armed when a read command runs. - */ -void MB_SpiState(uint32_t out[4]); - -/* - * On-screen trace marker (debug). Draws `s` on the LCD (SPI1, independent of the - * flash SPI2) so that when a flash read freezes the CPU, the frozen screen shows - * the last step reached. Only draws while mb_mark_on is set (i.e. during a Dump), - * so it does not flash the screen during normal writes. - */ -extern volatile uint8_t mb_mark_on; -void MB_Mark(const char *s); - #endif /* DRIVER_MB_FLASH_H */ diff --git a/App/driver/py25q16.c b/App/driver/py25q16.c index 2bc38221..876cdb53 100644 --- a/App/driver/py25q16.c +++ b/App/driver/py25q16.c @@ -27,12 +27,9 @@ #include "external/printf/printf.h" #include "misc.h" -#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT - #include "driver/mb_flash.h" - #define MBMARK(s) MB_Mark(s) -#else - #define MBMARK(s) -#endif +/* MBMARK was an on-screen SPI trace used while bringing up multiboot (M1/M2). + * The tracer is gone; keep the call sites as no-ops. */ +#define MBMARK(s) // #define DEBUG diff --git a/App/ui/multiboot.c b/App/ui/multiboot.c index 4de22c38..eb8580df 100644 --- a/App/ui/multiboot.c +++ b/App/ui/multiboot.c @@ -89,11 +89,37 @@ static void mb_format_slot_label(char *dst, uint8_t cap, const mb_slot_header_t dst[n] = 0; } +/* "MULTIBOOT" mode banner in the top status bar, shown on every screen - the + * same way the firmware puts mode labels there (inverse 3x5 capsule). */ +static void mb_status_bar(void) +{ + UI_StatusClear(); + GUI_DisplaySmallestInverse("MULTIBOOT", 47, 0, true, true, 83); +} + +/* Bottom key-hint line: each key name as an inverse 3x5 capsule label, its + * action in plain 3x5 text beside it. Drawn on the bottom line + * (gFrameBuffer[6] -> y = 6*8+1 = 49). MENU is pinned to the left and EXIT to + * the right, leaving an airy gap in the middle. "MENU"/"EXIT" are 4 chars + * (16 px); their capsule spans [x-2 .. x+16]. */ +static void mb_key_hints(const char *act_menu, const char *act_exit) +{ + const uint8_t sp = 6u; /* label <-> action gap */ + const uint8_t ae = (uint8_t)strlen(act_exit); + const uint8_t xm = 4u; /* MENU text; capsule at x=2 */ + const uint8_t xe = (uint8_t)(124u - ae * 4u - sp - 16u); /* EXIT action ends at x=124 */ + + GUI_DisplaySmallestInverse("MENU", xm, 6, false, true, (uint8_t)(xm + 16u)); + GUI_DisplaySmallest(act_menu, (uint8_t)(xm + 16u + sp), 49, false, true); + + GUI_DisplaySmallestInverse("EXIT", xe, 6, false, true, (uint8_t)(xe + 16u)); + GUI_DisplaySmallest(act_exit, (uint8_t)(xe + 16u + sp), 49, false, true); +} + static void mb_show_message(const char *line1, const char *line2, const char *line3) { UI_DisplayClear(); - UI_StatusClear(); - UI_PrintStringSmallNormal("MultiBoot", 2, 126, 0); + mb_status_bar(); if (line1) UI_PrintStringSmallNormal(line1, 2, 126, 2); if (line2) UI_PrintStringSmallNormal(line2, 2, 126, 4); if (line3) UI_PrintStringSmallNormal(line3, 2, 126, 6); @@ -147,14 +173,15 @@ static void mb_render_slots(uint8_t selected, char line[19]; /* 18 glyphs max: 18 * 7 px fits from x=2 to x=126. */ UI_DisplayClear(); - UI_StatusClear(); - UI_PrintStringSmallNormal("MultiBoot", 2, 126, 0); + mb_status_bar(); for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++) { + const uint8_t fbLine = (uint8_t)(slot + 1u); /* page 1 stays blank */ + memset(line, 0, sizeof(line)); - line[0] = (slot == selected) ? '>' : ' '; - line[1] = (char)('0' + slot); + line[0] = (char)('0' + slot); + line[1] = ' '; line[2] = ' '; if (status[slot] == MB_OK) @@ -162,25 +189,30 @@ static void mb_render_slots(uint8_t selected, else mb_copy_label(&line[3], sizeof(line) - 3u, mb_error_text(status[slot]), 20u); - UI_PrintStringSmallNormal(line, 2, 0, (uint8_t)(slot + 1u)); + UI_PrintStringSmallNormal(line, 2, 0, fbLine); + + /* Selected row: full-width inverse bar, like the firmware menu list. */ + if (slot == selected) + for (uint8_t x = 0; x < LCD_WIDTH; x++) + gFrameBuffer[fbLine][x] ^= 0xFFu; } - UI_PrintStringSmallNormal("MENU to select", 2, 126, 5); - UI_PrintStringSmallNormal("EXIT to go back", 2, 126, 6); + mb_key_hints("SELECT", "QUIT"); + ST7565_BlitStatusLine(); ST7565_BlitFullScreen(); } static void mb_prepare_progress(uint8_t slot) { - char title[] = "RESTORE SLOT 0"; + char title[] = "Restore slot 0"; title[13] = (char)('0' + slot); UI_DisplayClear(); - UI_StatusClear(); - UI_PrintStringSmallNormal(title, 2, 126, 0); - UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 2); - UI_PrintStringSmallNormal("Writing & Verify", 2, 126, 4); + mb_status_bar(); + UI_PrintStringSmallNormal(title, 2, 126, 1); + UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 3); + UI_PrintStringSmallNormal("Writing & Verify", 2, 126, 5); /* Same rounded outline and hatch pattern as the scan progress gauge. */ gFrameBuffer[6][3] = 0x0Cu; @@ -193,14 +225,29 @@ static void mb_prepare_progress(uint8_t slot) ST7565_BlitFullScreen(); } +static void mb_confirm_screen(uint8_t slot) +{ + char title[] = "Restore slot 0?"; + title[13] = (char)('0' + slot); + + UI_DisplayClear(); + mb_status_bar(); + UI_PrintStringSmallNormal(title, 2, 126, 3); + mb_key_hints("CONFIRM", "BACK"); + ST7565_BlitStatusLine(); + ST7565_BlitFullScreen(); +} + void UI_MultibootSelector(void) { mb_slot_header_t headers[MB_SLOT_COUNT]; uint8_t status[MB_SLOT_COUNT]; uint8_t selected = 0; - BACKLIGHT_TurnOn(); + /* Clear + blit the LCD BEFORE the backlight comes on, otherwise it reveals + * the random power-on contents of the display RAM for a moment. */ mb_show_message("Release keys", NULL, NULL); + BACKLIGHT_TurnOn(); mb_wait_release(); mb_scan_slots(headers, status); @@ -247,9 +294,7 @@ void UI_MultibootSelector(void) continue; } - char confirm[] = "Restore slot 0?"; - confirm[13] = (char)('0' + selected); - mb_show_message(confirm, "MENU to confirm", "EXIT to cancel"); + mb_confirm_screen(selected); key = mb_get_key(); if (key != KEY_MENU) continue;