mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
577 lines
19 KiB
C
577 lines
19 KiB
C
/* Copyright 2026 Armel F4HWN
|
|
* https://github.com/armel
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "driver/backlight.h"
|
|
#include "driver/gpio.h"
|
|
#include "driver/keyboard.h"
|
|
#include "driver/mb_flash.h"
|
|
#include "driver/st7565.h"
|
|
#include "driver/system.h"
|
|
#include "ui/helper.h"
|
|
#include "ui/multiboot.h"
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
#include "k5viewer.h"
|
|
#endif
|
|
|
|
static uint8_t gRunningSlot = 0xFFu;
|
|
static uint8_t gActiveBank = 0u;
|
|
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
static void mb_k5viewer_service(void)
|
|
{
|
|
/* The Multiboot selector is modal and does not return to APP_Update(). */
|
|
K5VIEWER_ParseInput();
|
|
K5VIEWER_Update(false);
|
|
}
|
|
#endif
|
|
|
|
static uint8_t mb_remember_boot_state(uint8_t slot, uint8_t bank)
|
|
{
|
|
gRunningSlot = slot;
|
|
gActiveBank = bank;
|
|
return bank;
|
|
}
|
|
|
|
uint8_t MB_GetRunningSlot(void)
|
|
{
|
|
return gRunningSlot;
|
|
}
|
|
|
|
uint8_t MB_GetActiveBank(void)
|
|
{
|
|
return gActiveBank;
|
|
}
|
|
|
|
static const char *mb_error_text(uint8_t err)
|
|
{
|
|
switch (err)
|
|
{
|
|
case MB_OK: return "OK";
|
|
case MB_ERR_MAGIC: return "empty";
|
|
case MB_ERR_VERSION: return "new header";
|
|
case MB_ERR_NOT_COMMITTED: return "incomplete";
|
|
case MB_ERR_SIZE: return "bad size";
|
|
case MB_ERR_CRC: return "CRC ERROR";
|
|
case MB_ERR_SPI: return "SPI ERROR";
|
|
case MB_ERR_SLOT: return "bad index";
|
|
case MB_ERR_AUTH: return "auth";
|
|
case MB_ERR_RAM_LOAD: return "RAM LOAD ERROR";
|
|
default: return "error";
|
|
}
|
|
}
|
|
|
|
static void mb_copy_label(char *dst, uint8_t cap, const char *src, uint8_t src_cap)
|
|
{
|
|
uint8_t n = 0;
|
|
while (n + 1u < cap && n < src_cap && src[n])
|
|
{
|
|
dst[n] = src[n];
|
|
n++;
|
|
}
|
|
dst[n] = 0;
|
|
}
|
|
|
|
static uint8_t mb_copy_slot_version(char *dst, uint8_t cap, const mb_slot_header_t *header)
|
|
{
|
|
uint8_t n = 0;
|
|
|
|
dst[0] = 0;
|
|
for (uint8_t i = 0; i + 1u < MB_VERSION_LEN && header->fw_version[i]; i++)
|
|
{
|
|
if (header->fw_version[i] == 'v' &&
|
|
header->fw_version[i + 1u] >= '0' &&
|
|
header->fw_version[i + 1u] <= '9')
|
|
{
|
|
if (n + 1u < cap)
|
|
dst[n++] = 'v';
|
|
i++;
|
|
while (n + 1u < cap && i < MB_VERSION_LEN)
|
|
{
|
|
const char c = header->fw_version[i];
|
|
if ((c < '0' || c > '9') && c != '.')
|
|
break;
|
|
dst[n++] = c;
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
dst[n] = 0;
|
|
return n;
|
|
}
|
|
|
|
/* "F4HWN MULTIBOOT" 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("F4HWN MULTIBOOT", 34, 0, true, true, 94);
|
|
|
|
/* Thin line dressing up the otherwise blank row between the status bar and
|
|
* the first content row. Drawn on gFrameBuffer[0] (line 0), which every
|
|
* multiboot screen leaves blank, so it shows on all of them. Bit 3 (~mid of
|
|
* the row) stays clear of the selected-slot capsule's top edge (bit 7). */
|
|
for (uint8_t x = 2u; x < LCD_WIDTH - 2u; x++)
|
|
gFrameBuffer[0][x] |= 0x08u;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Fixed selection capsule around the firmware name. The slot index stays in
|
|
* the normal font while the version is plain 3x5 metadata. */
|
|
#define MB_NAME_BOX_START 12u
|
|
#define MB_NAME_BOX_END 96u
|
|
#define MB_NAME_TEXT_X 14u
|
|
|
|
static void mb_invert_name(uint8_t line)
|
|
{
|
|
gFrameBuffer[line][MB_NAME_BOX_START] ^= 0x7Fu;
|
|
for (uint8_t x = MB_NAME_BOX_START + 1u; x < MB_NAME_BOX_END; x++)
|
|
{
|
|
gFrameBuffer[line][x] ^= 0xFFu;
|
|
gFrameBuffer[line - 1u][x] ^= 0x80u;
|
|
}
|
|
gFrameBuffer[line][MB_NAME_BOX_END] ^= 0x7Fu;
|
|
}
|
|
|
|
static void mb_show_message(const char *line1, const char *line2, const char *line3)
|
|
{
|
|
UI_DisplayClear();
|
|
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);
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
mb_k5viewer_service();
|
|
#endif
|
|
}
|
|
|
|
static void mb_wait_release(void)
|
|
{
|
|
uint8_t stable = 0;
|
|
while (stable < 10u)
|
|
{
|
|
if (!GPIO_IsPttPressed() && KEYBOARD_Poll() == KEY_INVALID)
|
|
stable++;
|
|
else
|
|
stable = 0;
|
|
SYSTEM_DelayMs(10);
|
|
}
|
|
}
|
|
|
|
static KEY_Code_t mb_get_key(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
mb_k5viewer_service();
|
|
#endif
|
|
KEY_Code_t key = KEYBOARD_Poll();
|
|
if (key != KEY_INVALID)
|
|
{
|
|
SYSTEM_DelayMs(30);
|
|
if (KEYBOARD_Poll() == key)
|
|
{
|
|
while (KEYBOARD_Poll() != KEY_INVALID)
|
|
SYSTEM_DelayMs(10);
|
|
return key;
|
|
}
|
|
}
|
|
SYSTEM_DelayMs(10);
|
|
}
|
|
}
|
|
|
|
/* Shown from the normal settings menu (SetCfg), not the boot selector, so it does
|
|
* NOT paint the "F4HWN MULTIBOOT" status banner - just a plain acknowledged message. */
|
|
void UI_MultibootShowConfigError(uint8_t err)
|
|
{
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
gKeyReading0 = KEY_INVALID;
|
|
gKeyReading1 = KEY_INVALID;
|
|
#endif
|
|
UI_DisplayClear();
|
|
UI_StatusClear();
|
|
UI_PrintStringSmallNormal("CFG ERROR", 2, 126, 2);
|
|
UI_PrintStringSmallNormal(mb_error_text(err), 2, 126, 4);
|
|
UI_PrintStringSmallNormal("Press any key", 2, 126, 6);
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
mb_k5viewer_service();
|
|
#endif
|
|
/* The MENU press that confirmed SetCfg may still be down; wait for a clean
|
|
* release first so it isn't consumed as the acknowledgement immediately. */
|
|
mb_wait_release();
|
|
(void)mb_get_key();
|
|
}
|
|
|
|
static void mb_scan_slots(mb_slot_header_t headers[MB_SLOT_COUNT], uint8_t status[MB_SLOT_COUNT])
|
|
{
|
|
mb_show_message("Scanning slots...", NULL, "Please wait");
|
|
for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++)
|
|
status[slot] = MB_ValidateSlot(slot, &headers[slot], NULL);
|
|
}
|
|
|
|
static void mb_render_slots(uint8_t selected,
|
|
const mb_slot_header_t headers[MB_SLOT_COUNT],
|
|
const uint8_t status[MB_SLOT_COUNT])
|
|
{
|
|
char name[13];
|
|
char version[8]; /* v + up to six version digits/dots in the 3x5 column. */
|
|
|
|
UI_DisplayClear();
|
|
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 */
|
|
char index[2];
|
|
uint8_t version_len = 0;
|
|
uint8_t version_x = 0;
|
|
|
|
memset(name, 0, sizeof(name));
|
|
memset(version, 0, sizeof(version));
|
|
/* Slot 0 is the auto-backed-up main firmware: label it 'M' (Main) so it
|
|
* reads apart from the numbered user slots 1..4. */
|
|
index[0] = (slot == 0u) ? 'M' : (char)('0' + slot);
|
|
index[1] = '\0';
|
|
|
|
if (status[slot] == MB_OK)
|
|
{
|
|
version_len = mb_copy_slot_version(version, sizeof(version), &headers[slot]);
|
|
if (version_len)
|
|
version_x = (uint8_t)(LCD_WIDTH - 2u - version_len * 4u);
|
|
|
|
if (headers[slot].name[0])
|
|
mb_copy_label(name, sizeof(name), headers[slot].name, MB_NAME_LEN);
|
|
else if (!version_len)
|
|
mb_copy_label(name, sizeof(name), headers[slot].fw_version, MB_VERSION_LEN);
|
|
}
|
|
else
|
|
mb_copy_label(name, sizeof(name), mb_error_text(status[slot]), 20u);
|
|
|
|
UI_PrintStringSmallNormal(index, 2u, 0, fbLine);
|
|
UI_PrintStringSmallNormal(name, MB_NAME_TEXT_X, 0, fbLine);
|
|
if (version_len)
|
|
GUI_DisplaySmallest(version, version_x,
|
|
(uint8_t)(fbLine * 8u + 1u), false, true);
|
|
|
|
/* Selected row: fixed rounded inverse capsule around the name only. */
|
|
if (slot == selected)
|
|
mb_invert_name(fbLine);
|
|
}
|
|
|
|
mb_key_hints("SELECT", "QUIT");
|
|
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
}
|
|
|
|
static void mb_draw_progress_outline(void)
|
|
{
|
|
/* Same rounded outline and hatch pattern as the scan progress gauge. */
|
|
gFrameBuffer[6][3] = 0x0Cu;
|
|
gFrameBuffer[6][4] = 0x12u;
|
|
gFrameBuffer[6][123] = 0x12u;
|
|
gFrameBuffer[6][124] = 0x0Cu;
|
|
for (uint8_t x = 5; x < 123u; x++)
|
|
gFrameBuffer[6][x] = 0x21u;
|
|
}
|
|
|
|
/* Full progress frame used while restoring a firmware slot. */
|
|
__attribute__((noinline)) static void mb_prepare_progress_screen(const char *title,
|
|
const char *detail)
|
|
{
|
|
UI_DisplayClear();
|
|
mb_status_bar();
|
|
UI_PrintStringSmallNormal(title, 2, 126, 1);
|
|
UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 3);
|
|
UI_PrintStringSmallNormal(detail, 2, 126, 5);
|
|
/* Empty gauge that the RAM copier fills as it reflashes. */
|
|
mb_draw_progress_outline();
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
mb_k5viewer_service();
|
|
#endif
|
|
}
|
|
|
|
static void mb_prepare_progress(uint8_t slot)
|
|
{
|
|
char slot_title[] = "Restore slot 0";
|
|
slot_title[13] = (char)('0' + slot);
|
|
const char *title = (slot == 0u) ? "Restore Main" : slot_title;
|
|
|
|
mb_prepare_progress_screen(title, "Writing / Verify");
|
|
}
|
|
|
|
/* Discreet "Main backup" screen shown once, at the first boot after a normal
|
|
* Flash-Firmware install, while the running firmware is copied into slot 0. */
|
|
static void mb_backup_prepare(void)
|
|
{
|
|
UI_DisplayClear();
|
|
UI_StatusClear();
|
|
UI_PrintStringSmallNormal("Init Main", 2, 126, 1);
|
|
UI_PrintStringSmallNormal("DO NOT POWER OFF", 2, 126, 3);
|
|
mb_draw_progress_outline();
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
}
|
|
|
|
static void mb_backup_progress(uint32_t done, uint32_t total)
|
|
{
|
|
uint32_t cols = total ? (done * 118u / total) : 118u;
|
|
if (cols > 118u)
|
|
cols = 118u;
|
|
for (uint32_t i = 0; i < cols; i++)
|
|
gFrameBuffer[6][5u + i] = 0x2Du;
|
|
ST7565_BlitFullScreen();
|
|
}
|
|
|
|
/* With no trustworthy bank, continuing would let normal boot-time settings
|
|
* writes modify an arbitrary bank. Keep the radio in a read-only error state;
|
|
* a power cycle can recover from a transient SPI fault. */
|
|
__attribute__((noreturn)) static void mb_state_error_halt(void)
|
|
{
|
|
BACKLIGHT_TurnOn();
|
|
mb_show_message("STATE ERROR", "Flash state unknown", "Restart radio");
|
|
for (;;)
|
|
SYSTEM_DelayMs(100);
|
|
}
|
|
|
|
static void mb_confirm_screen(uint8_t slot)
|
|
{
|
|
char slot_title[] = "Restore slot 0?";
|
|
slot_title[13] = (char)('0' + slot);
|
|
const char *title = (slot == 0u) ? "Restore Main?" : slot_title;
|
|
|
|
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;
|
|
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
/* The selector is entered from a boot key event. Clear that stale key so
|
|
* K5Viewer is allowed to publish the first selector frame immediately. */
|
|
gKeyReading0 = KEY_INVALID;
|
|
gKeyReading1 = KEY_INVALID;
|
|
#endif
|
|
|
|
/* 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);
|
|
|
|
/* Pre-select the exact firmware slot resolved at boot, independently of the
|
|
* active config bank (SetCfg can point the bank elsewhere), so the cursor
|
|
* lands on "where you are". An unknown or now-invalid slot falls back to the
|
|
* first valid slot below. */
|
|
selected = MB_GetRunningSlot();
|
|
if (selected >= MB_SLOT_COUNT || status[selected] != MB_OK)
|
|
{
|
|
for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++)
|
|
{
|
|
if (status[slot] == MB_OK)
|
|
{
|
|
selected = slot;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
mb_render_slots(selected, headers, status);
|
|
KEY_Code_t key = mb_get_key();
|
|
|
|
if (key == KEY_EXIT)
|
|
{
|
|
/* MENU was latched by BOOT_GetMode(). Do not let that stale boot
|
|
* key reach the normal application after leaving the selector. */
|
|
gKeyReading0 = KEY_INVALID;
|
|
gKeyReading1 = KEY_INVALID;
|
|
gDebounceCounter = 0;
|
|
return;
|
|
}
|
|
if (key == KEY_UP)
|
|
{
|
|
selected = (uint8_t)((selected + MB_SLOT_COUNT - 1u) % MB_SLOT_COUNT);
|
|
continue;
|
|
}
|
|
if (key == KEY_DOWN)
|
|
{
|
|
selected = (uint8_t)((selected + 1u) % MB_SLOT_COUNT);
|
|
continue;
|
|
}
|
|
if (key != KEY_MENU)
|
|
continue;
|
|
|
|
if (status[selected] != MB_OK)
|
|
{
|
|
mb_show_message("SLOT NOT VALID", mb_error_text(status[selected]), "Press any key");
|
|
(void)mb_get_key();
|
|
continue;
|
|
}
|
|
|
|
mb_confirm_screen(selected);
|
|
key = mb_get_key();
|
|
if (key != KEY_MENU)
|
|
continue;
|
|
|
|
/* Bind this slot to its own settings bank BEFORE reflashing. The
|
|
* write is verified (read-back); if it can't be confirmed we must NOT
|
|
* reflash - otherwise the next boot could resolve to the wrong bank
|
|
* (e.g. when two slots hold the same firmware image). */
|
|
if (MB_SetActiveSlot(selected) != MB_OK)
|
|
{
|
|
mb_show_message("STATE ERROR", "Marker not saved", "Press any key");
|
|
(void)mb_get_key();
|
|
continue;
|
|
}
|
|
|
|
mb_prepare_progress(selected);
|
|
uint8_t err = MB_RestoreSlot(selected, gFrameBuffer[6]);
|
|
|
|
/* Only reached when the final pre-erase validation refused the slot. */
|
|
status[selected] = err;
|
|
mb_show_message("RESTORE REFUSED", mb_error_text(err), "Press any key");
|
|
(void)mb_get_key();
|
|
mb_scan_slots(headers, status);
|
|
}
|
|
}
|
|
|
|
/* Adopt the running internal firmware as Main: back it up into slot 0 and point
|
|
* the marker at bank 0. Reached when the firmware was installed outside
|
|
* multiboot (fresh radio, or a plain Flash-Firmware). */
|
|
static uint8_t mb_adopt_internal_as_main(void)
|
|
{
|
|
mb_backup_prepare();
|
|
BACKLIGHT_TurnOn();
|
|
if (MB_BackupInternalToSlot0(mb_backup_progress) == MB_OK)
|
|
(void)MB_SetActiveSlot(MB_SLOT_BACKUP);
|
|
return MB_SLOT_BACKUP;
|
|
}
|
|
|
|
uint8_t MB_BootResolveState(void)
|
|
{
|
|
mb_state_t mark;
|
|
mb_mark_status_t ms = MB_MARK_IO;
|
|
|
|
for (uint8_t retry = 0; retry < 3u && ms == MB_MARK_IO; retry++)
|
|
{
|
|
ms = MB_ReadActiveState(&mark);
|
|
if (ms == MB_MARK_IO)
|
|
SYSTEM_DelayMs(10);
|
|
}
|
|
|
|
/* A reliably-read marker is authoritative: it carries the expected internal
|
|
* identity, so we don't even need the slot header. */
|
|
if (ms == MB_MARK_VALID)
|
|
{
|
|
if (MB_InternalMatchesState(&mark))
|
|
return mb_remember_boot_state(mark.firmware_slot, mark.config_bank);
|
|
|
|
/* Marker read fine but internal no longer carries its identity -> the
|
|
* firmware was replaced outside multiboot (a plain Flash-Firmware). Adopt
|
|
* it as Main. Deliberately NOT a content scan here: a build that merely
|
|
* duplicates a user slot (or a marker that already points at such a slot)
|
|
* must still refresh Main. */
|
|
return mb_remember_boot_state(mb_adopt_internal_as_main(), MB_SLOT_BACKUP);
|
|
}
|
|
|
|
/* Marker unreliable (MISSING / LEGACY / CORRUPT / IO): identify the running
|
|
* firmware by content, and never destroy Main on uncertainty - internal is
|
|
* adopted only when it matches no slot AND every read was clean, so a
|
|
* transient SPI error or a half-written marker can never destroy Main. */
|
|
|
|
/* An FMP1 record still names a coupled slot/bank; honour it before the
|
|
* content scan so a duplicate image in a lower slot cannot hijack the
|
|
* migration. Falls through to the scan below on mismatch or IO. */
|
|
if (ms == MB_MARK_LEGACY && mark.firmware_slot < MB_SLOT_COUNT)
|
|
{
|
|
mb_fw_match_t m = MB_FW_IO;
|
|
for (uint8_t retry = 0; retry < 3u && m == MB_FW_IO; retry++)
|
|
m = MB_InternalMatchesSlot(mark.firmware_slot);
|
|
if (m == MB_FW_MATCH)
|
|
{
|
|
(void)MB_SetActiveSlot(mark.firmware_slot);
|
|
return mb_remember_boot_state(mark.firmware_slot, mark.config_bank);
|
|
}
|
|
}
|
|
|
|
bool had_io = false;
|
|
for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++)
|
|
{
|
|
mb_fw_match_t m = MB_FW_IO;
|
|
for (uint8_t retry = 0; retry < 3u && m == MB_FW_IO; retry++)
|
|
m = MB_InternalMatchesSlot(slot);
|
|
if (m == MB_FW_MATCH)
|
|
{
|
|
(void)MB_SetActiveSlot(slot); /* record/repair the marker */
|
|
return mb_remember_boot_state(slot, slot);
|
|
}
|
|
if (m == MB_FW_IO)
|
|
had_io = true;
|
|
}
|
|
|
|
if (had_io || ms == MB_MARK_IO || ms == MB_MARK_CORRUPT)
|
|
{
|
|
/* Halt to protect an existing Main while the flash state is uncertain;
|
|
* but if slot 0 holds no valid backup there is nothing to protect, so
|
|
* fall through and adopt instead of bricking a first boot. */
|
|
uint8_t main_status = MB_SlotInfo(MB_SLOT_BACKUP, NULL);
|
|
bool main_exists = (main_status != MB_ERR_MAGIC &&
|
|
main_status != MB_ERR_NOT_COMMITTED);
|
|
if (main_exists)
|
|
mb_state_error_halt();
|
|
}
|
|
|
|
return mb_remember_boot_state(mb_adopt_internal_as_main(), MB_SLOT_BACKUP);
|
|
}
|