Improve mb

This commit is contained in:
Armel FAUVEAU committed 2026-08-11 22:33:33 +02:00
1 parent a25d2fc75e
commit a2551d97dc
5 files changed
+67 -314

No files matched your search

-108
View File
@@ -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)
{
+1 -121
View File
@@ -13,15 +13,11 @@
* limitations under the License.
*/
#include <string.h>
#include <stddef.h>
#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) */
}
-61
View File
@@ -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 */
+3 -6
View File
@@ -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
+63 -18
View File
@@ -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;