fix(bootloader): remove broken PROTO_SET_DELAY boot-delay feature (#27081)

The bootloader boot-delay feature has been mechanically broken on
every modern FMU board since the STM32F7/H7 transition. It has three
independent bugs that prevent it from ever working:

1. Offset mismatch: BOOT_DELAY_ADDRESS is hardcoded to 0x1a0, but the
   NuttX vector table is 504 B (F76x) to 664 B (H743) long. The
   linker places _bootdelay_signature at ALIGN(32) past end of
   vectors (e.g. 0x2a0 on CubeOrange), never at 0x1a0. The bootloader
   reads random exception_common pointers in place of the magic and
   never matches BOOT_DELAY_SIGNATURE1/2. Verified on CubeOrange with
   objdump of cubepilot_cubeorange_default.elf.

2. Flash cache never flushes: fc_write() stores arbitrary writes in
   cache line 1 and only flushes on a very specific condition tied
   to the sequential firmware upload flow. A standalone write during
   PROTO_SET_DELAY is cached forever. fc_read() then returns the
   cached value, so the post-write verify lies and the bootloader
   reports success. Nothing ever reaches flash.

3. H7 write granularity: the STM32H7 flash controller requires a
   full 32-byte program cycle per write. Single 32-bit writes from
   flash_func_write_word() would not be accepted by the controller
   even if they reached it.

The feature has been silently dead on every H7/F7 FMU board for
years and no one noticed, which is strong evidence nothing actually
depends on it. Rather than fix it (which would mean rewriting
PROTO_SET_DELAY, the flash cache path, and the H7 flash programming
path), remove it.

Changes:
- bl.c: PROTO_SET_DELAY case now immediately NACKs (goto cmd_bad)
  so clients that still send the command get a clear rejection
  instead of the previous silent fake-success. The opcode stays in
  the protocol enum for backwards compatibility.
- bl.h: drop BOOT_DELAY_SIGNATURE1/2 and BOOT_DELAY_MAX.
- stm/stm32_common/main.c, nxp/imxrt_common/main.c: drop the
  startup boot-delay sig check block.
- image_toc.c: decouple find_toc() from BOOT_DELAY_ADDRESS.
  BOARD_IMAGE_TOC_OFFSET is now the required define when
  BOOTLOADER_USE_TOC is enabled. The body is wrapped in #ifdef
  BOOTLOADER_USE_TOC and falls back to a stub returning false when
  the TOC is not in use (no upstream board currently enables it).
- Linker scripts: strip EXTERN(_bootdelay_signature) and the
  FILL/. += 8 block from all 142 affected .ld files across boards/.
- hw_config.h: strip the #define BOOT_DELAY_ADDRESS and its comment
  block entry from all 48 affected boards.
- Tools/px4_uploader.py, Tools/teensy_uploader.py: remove --boot-delay,
  set_boot_delay(), and SET_BOOT_DELAY client-side counterpart.

Smoke-built on cubepilot_cubeorange_default and
cubepilot_cubeorange_bootloader; no link errors, no unresolved
symbols, flash usage unchanged.

Tested:
- New BL, new FW
- Old BL, old FW
- Old BL, new FW
- New BL, old FW
This commit is contained in:
Julian Oes
2026-04-29 13:04:03 +12:00
committed by GitHub
parent 0546d1c0e7
commit 1a3d232e7b
196 changed files with 33 additions and 1274 deletions

View File

@@ -104,7 +104,7 @@
#define PROTO_GET_OTP 0x2a // read a byte from OTP at the given address
#define PROTO_GET_SN 0x2b // read a word from UDID area ( Serial) at the given address
#define PROTO_GET_CHIP 0x2c // read chip version (MCU IDCODE)
#define PROTO_SET_DELAY 0x2d // set minimum boot delay
#define PROTO_SET_DELAY 0x2d // set boot delay (deprecated; always NACKed)
#define PROTO_GET_CHIP_DES 0x2e // read chip version In ASCII
#define PROTO_GET_VERSION 0x2f // read version
#define PROTO_BOOT 0x30 // boot the application
@@ -1021,49 +1021,19 @@ bootloader(unsigned timeout)
}
break;
#ifdef BOOT_DELAY_ADDRESS
case PROTO_SET_DELAY: {
/*
Allow for the bootloader to setup a
boot delay signature which tells the
board to delay for at least a
specified number of seconds on boot.
*/
int v = cin_wait(100);
if (v < 0) {
goto cmd_bad;
}
uint8_t boot_delay = v & 0xFF;
if (boot_delay > BOOT_DELAY_MAX) {
goto cmd_bad;
}
// expect EOC
if (!wait_for_eoc(2)) {
goto cmd_bad;
}
uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS + 4);
if (sig1 != BOOT_DELAY_SIGNATURE1 ||
sig2 != BOOT_DELAY_SIGNATURE2) {
goto cmd_bad;
}
uint32_t value = (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00) | boot_delay;
flash_func_write_word(BOOT_DELAY_ADDRESS, value);
if (flash_func_read_word(BOOT_DELAY_ADDRESS) != value) {
goto cmd_fail;
}
}
break;
#endif
case PROTO_SET_DELAY:
/*
* Boot delay used to let the bootloader pause before
* starting the app by writing a signature next to the
* vector table. It never worked correctly on modern
* FMUs (signature was placed past where the bootloader
* looked, H7 flash granularity prevented single-word
* writes, and the flash cache never flushed the write),
* so it has been removed. NACK so a client that still
* sends it gets a clear rejection instead of silent
* fake success.
*/
goto cmd_bad;
// finalise programming and boot the system
//

View File

@@ -89,12 +89,6 @@ extern int buf_get(void);
#define LED_ACTIVITY 1
#define LED_BOOTLOADER 2
#ifdef BOOT_DELAY_ADDRESS
# define BOOT_DELAY_SIGNATURE1 0x92c2ecff
# define BOOT_DELAY_SIGNATURE2 0xc5057d5d
# define BOOT_DELAY_MAX 30
#endif
#define MAX_DES_LENGTH 20
#define MAX_VERSION_LENGTH 32

View File

@@ -41,6 +41,13 @@
#include "image_toc.h"
#include "bl.h"
#include "crypto.h"
#ifdef BOOTLOADER_USE_TOC
#ifndef BOARD_IMAGE_TOC_OFFSET
# error "BOARD_IMAGE_TOC_OFFSET must be defined when BOOTLOADER_USE_TOC is enabled"
#endif
/* Helper macros to define flash start and end addresses, based on info from
* hw_config.h
@@ -50,7 +57,7 @@
bool find_toc(const image_toc_entry_t **toc_entries, uint8_t *len)
{
const uintptr_t toc_start_u32 = APP_LOAD_ADDRESS + BOOT_DELAY_ADDRESS + 8;
const uintptr_t toc_start_u32 = APP_LOAD_ADDRESS + BOARD_IMAGE_TOC_OFFSET;
const image_toc_start_t *toc_start = (const image_toc_start_t *)toc_start_u32;
const image_toc_entry_t *entry = (const image_toc_entry_t *)(toc_start_u32 + sizeof(image_toc_start_t));
@@ -105,3 +112,14 @@ bool find_toc(const image_toc_entry_t **toc_entries, uint8_t *len)
*len = 0;
return false;
}
#else // BOOTLOADER_USE_TOC
bool find_toc(const image_toc_entry_t **toc_entries, uint8_t *len)
{
(void)toc_entries;
(void)len;
return false;
}
#endif // BOOTLOADER_USE_TOC

View File

@@ -744,33 +744,6 @@ bootloader_main(void)
board_set_rtc_signature(0);
}
#ifdef BOOT_DELAY_ADDRESS
{
/*
if a boot delay signature is present then delay the boot
by at least that amount of time in seconds. This allows
for an opportunity for a companion computer to load a
new firmware, while still booting fast by sending a BOOT
command
*/
uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS + 4);
if (sig2 == BOOT_DELAY_SIGNATURE2 &&
(sig1 & 0xFFFFFF00) == (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00)) {
unsigned boot_delay = sig1 & 0xFF;
if (boot_delay <= BOOT_DELAY_MAX) {
try_boot = false;
if (timeout < boot_delay * 1000) {
timeout = boot_delay * 1000;
}
}
}
}
#endif
/*
* Check if the force-bootloader pins are strapped; if strapped,
* don't try booting.

View File

@@ -687,33 +687,6 @@ bootloader_main(void)
board_set_rtc_signature(0);
}
#ifdef BOOT_DELAY_ADDRESS
{
/*
if a boot delay signature is present then delay the boot
by at least that amount of time in seconds. This allows
for an opportunity for a companion computer to load a
new firmware, while still booting fast by sending a BOOT
command
*/
uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS + 4);
if (sig2 == BOOT_DELAY_SIGNATURE2 &&
(sig1 & 0xFFFFFF00) == (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00)) {
unsigned boot_delay = sig1 & 0xFF;
if (boot_delay <= BOOT_DELAY_MAX) {
try_boot = false;
if (timeout < boot_delay * 1000) {
timeout = boot_delay * 1000;
}
}
}
}
#endif
/*
* Check if the force-bootloader pins are strapped; if strapped,
* don't try booting.