| name | bootloaders |
| description | Bootloaders embarqués — stages, vector table, dual-bank, secure boot, MCU bootloader, USB DFU, OTA |
| category | edge-ai |
| author | E.V.A |
| version | 1.0.0 |
Bootloaders Embarqués
Vue d'ensemble
Conception et implémentation de bootloaders pour systèmes embarqués : architecture multi-stage, vector table remapping, dual-bank (A/B) swap, secure boot, bootloader STM32, USB DFU, FOTA (Firmware Over-The-Air).
Architecture Bootloader
Stages
Power-On Reset
│
├── Stage 1 : ROM Bootloader (fabricant)
│ ├── Charge depuis UART, USB, SPI, CAN, NAND, SD
│ └── Vérifie signature (optionnel)
│
├── Stage 2 : Primary Bootloader (flash)
│ ├── Initialisation minimale (clocks, RAM, console)
│ ├── Vérifie l'intégrité du firmware (CRC, signature)
│ ├── Décision : boot normal vs update mode
│ └── Jump au firmware
│
├── Stage 3 : Application Firmware
│ └── RTOS, stack réseau, application
│
└── Fallback : Recovery Mode
└── Si le firmware est corrompu → attendre nouvelle image
Map mémoire typique (STM32 1MB Flash)
Adresse Taille Contenu
0x08000000 32KB Bootloader (Stage 2)
0x08008000 960KB Firmware Application
0x0807F000 4KB Configuration/Persistent
0x08080000 Fin flash
Avec dual-bank (A/B) :
Adresse Taille Contenu
0x08000000 32KB Bootloader (protégé)
0x08008000 480KB Bank A (firmware actif)
0x08080000 480KB Bank B (firmware backup)
0x080F8000 32KB Configuration
Bootloader Minimal (Cortex-M)
Startup
#include <stdint.h>
#define APP_ADDRESS 0x08008000
#define BOOTLOADER_SIZE 0x8000
#define FW_MAGIC 0xDEADBEEF
typedef struct __attribute__((packed)) {
uint32_t magic;
uint32_t version;
uint32_t size;
uint32_t crc32;
uint32_t timestamp;
uint32_t reserved[2];
uint8_t description[32];
} fw_header_t;
typedef void (*vector_func_t)(void);
typedef struct {
uint32_t *initial_sp;
vector_func_t reset;
vector_func_t nmi;
hard_fault;
} ;
{
crc = ;
( i = ; i < len; i++) {
crc ^= data[i];
( j = ; j < ; j++)
crc = (crc >> ) ^ (crc & ? : );
}
~crc;
}
{
*hdr = (*)addr;
(hdr->magic != FW_MAGIC) ;
calc = crc32((*)(addr + ()),
hdr->size);
(calc != hdr->crc32) ;
;
}
{
*vt = (*)addr;
__disable_irq();
( i = ; i < ; i++)
NVIC->ICER[i] = ;
SysTick->CTRL = ;
SCB->CPACR = ;
SCB->ICSR = SCB_ICSR_PENDSVCLR_Msk | SCB_ICSR_PENDSTCLR_Msk;
SCB->VTOR = addr;
__set_MSP(()vt->initial_sp);
vt->reset();
}
{
init_clocks();
init_uart();
init_led();
uart_puts();
uart_puts();
(verify_firmware(APP_ADDRESS)) {
uart_puts();
jump_to_app(APP_ADDRESS);
} {
uart_puts();
uart_puts();
wait_for_update();
}
() {
process_uart_update();
process_led();
}
}
Dual-Bank (A/B Swap)
Stratégie
Boot normal :
Bootloader → Bank A (active) → application
Update (FOTA) :
Bootloader → écrit Bank B (pendant que Bank A tourne)
→ Flag "swap" dans backup SRAM
→ Reset
Bootloader → lit flag → Bank B (active) → nouvelle app
Rollback (update failed) :
Bootloader → Bank A toujours valide
→ 3 tentatives de Bank B échouées → Bank A (fallback)
→ Reset flag
Implémentation
#define BACKUP_SRAM_BASE 0x40024000
#define BKP_FLAG_ADDR (BACKUP_SRAM_BASE + 0)
#define FLAG_BOOT_BANK_A 0xA5A5A5A5
#define FLAG_BOOT_BANK_B 0x5A5A5A5A
#define FLAG_BOOT_RECOVERY 0x00000000
void bkp_sram_enable(void) {
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_DBP;
RCC->CSR |= RCC_CSR_BACKUPSRAMEN;
__DSB();
}
uint32_t select_boot_bank(void) {
volatile uint32_t *flag = (uint32_t*)BKP_FLAG_ADDR;
if (*flag == FLAG_BOOT_BANK_A) {
if (verify_firmware(BANK_A_ADDR)) {
return BANK_A_ADDR;
}
*flag = FLAG_BOOT_BANK_B;
}
if (*flag == FLAG_BOOT_BANK_B) {
if (verify_firmware(BANK_B_ADDR)) {
return BANK_B_ADDR;
}
*flag = FLAG_BOOT_RECOVERY;
}
return 0;
}
void commit_update(void) {
volatile uint32_t *flag = (*)BKP_FLAG_ADDR;
*flag = (*flag == FLAG_BOOT_BANK_A) ? FLAG_BOOT_BANK_B : FLAG_BOOT_BANK_A;
NVIC_SystemReset();
}
{
*flag = (*)BKP_FLAG_ADDR;
*flag = (*flag == FLAG_BOOT_BANK_A) ? FLAG_BOOT_BANK_A : FLAG_BOOT_BANK_B;
NVIC_SystemReset();
}
Secure Boot
Chaîne de confiance
Boot ROM (fabricant) → vérifie signature Bootloader
↓ (signature RSA/Elliptic Curve)
Bootloader (flash) → vérifie signature Firmware
↓ (signature ECDSA)
Firmware Application
↓ (crypto vérifie chaque mise à jour)
OTA Update
Implémentation vérification ECDSA (micro-ecc)
#include "uECC.h"
static const uint8_t public_key[64] = {
0x...
};
bool verify_signature(const uint8_t *data, uint32_t len,
const uint8_t *signature) {
uint8_t hash[32];
sha256_calculate(data, len, hash);
return uECC_verify(public_key, hash, sizeof(hash),
signature, uECC_secp256r1());
}
bool verify_public_key(const uint8_t *key) {
uint8_t hash[32];
sha256_calculate(key, 64, hash);
return memcmp(hash, stored_hash, 32) == 0;
}
Anti-downgrade
uint32_t min_allowed_version = 0x01000000;
bool check_version(fw_header_t *hdr) {
if (hdr->version < min_allowed_version) {
uart_puts("ANTI-DOWNGRADE : version trop ancienne\n");
report_security_event();
return false;
}
write_otp_version(hdr->version);
return true;
}
Bootloader USB DFU (Device Firmware Update)
STM32 System Bootloader (ROM)
sudo dfu-util -l
sudo dfu-util -a 0 -s 0x08000000:leave -D firmware.bin
sudo dfu-util -a 0 -s 0x08080000 -D update.bin
Custom DFU
typedef enum {
DFU_DNLOAD = 1,
DFU_UPLOAD = 2,
DFU_GETSTATUS = 3,
DFU_CLRSTATUS = 4,
DFU_GETSTATE = 5,
DFU_ABORT = 6
} dfu_request_t;
typedef enum {
DFU_STATE_IDLE = 2,
DFU_STATE_DNLOAD_IDLE = 3,
DFU_STATE_DNLOAD_BUSY = 4,
DFU_STATE_DNLOAD_DONE = 5,
DFU_STATE_MANIFEST = 7,
DFU_STATE_ERROR = 10
} dfu_state_t;
void dfu_handle_request(uint8_t bRequest, uint16_t wValue,
uint16_t wIndex, uint16_t wLength) {
static uint32_t flash_addr = APP_ADDRESS;
static uint32_t total_bytes = 0;
switch (bRequest) {
case DFU_DNLOAD:
uint32_t block = wValue;
if (block == 0) {
flash_addr = flash_buf[0] | (flash_buf[] << );
total_bytes = ;
} {
flash_write(flash_addr + total_bytes,
flash_buf, wLength);
total_bytes += wLength;
}
;
DFU_GETSTATUS:
dfu_status.bState = DFU_STATE_DNLOAD_IDLE;
dfu_status.bStatus = ;
;
}
}
FOTA (Firmware Over-The-Air)
Architecture client
#define FOTA_CHUNK_SIZE 512
#define FOTA_RETRIES 3
typedef struct {
uint32_t total_size;
uint32_t received;
uint32_t crc32;
uint32_t version;
uint8_t signature[64];
} fota_session_t;
static fota_session_t session;
bool fota_start(const char *url, uint32_t version) {
if (version <= current_version) return false;
if (get_free_flash() < MAX_FIRMWARE_SIZE) {
uart_puts("FOTA: Pas assez de flash\n");
return false;
}
session.total_size = 0;
session.received = 0;
session.version = version;
flash_erase(BANK_B_ADDR, BANK_SIZE);
uart_puts("FOTA: Début téléchargement...\n");
return true;
}
bool fota_chunk(uint32_t offset, const uint8_t *data, uint32_t len) {
if (offset != session.received) {
;
}
flash_write(BANK_B_ADDR + offset, data, len);
session.received += len;
;
}
{
*hdr = (*)BANK_B_ADDR;
(!verify_firmware(BANK_B_ADDR)) {
uart_puts();
flash_erase(BANK_B_ADDR, BANK_SIZE);
;
}
(!verify_signature((*)BANK_B_ADDR,
hdr->size + (),
session.signature)) {
uart_puts();
flash_erase(BANK_B_ADDR, BANK_SIZE);
;
}
commit_update();
;
}
Bootloader pour divers MCU
STM32 (System Memory Bootloader)
ESP32 (ROM Bootloader)
i.MX RT (ROM Bootloader)
// NXP i.MX RT — boot from QSPI Flash, NAND, SD, eMMC
// ROM supporte flexSPI NOR, NAND, SD/MMC, SDP (Serial Download Protocol)
// Image header obligatoire (IVT + DCD)
// IVT (Image Vector Table) :
// 0x000 : Header (tag, length, version)
// 0x008 : Entry point
// 0x00C : Reserved
// 0x010 : DCD (Device Configuration Data) pointer
// 0x014 : Boot Data (start, size, plugin)
// 0x018 : Self pointer
// 0x01C : CSF (Command Sequence File) pointer
// 0x020 : Reserved
Linker Script pour Bootloader
/* Bootloader spécifique — il doit ignorer l'espace du firmware */
MEMORY {
FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 32K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
BACKUP (rw) : ORIGIN = 0x40024000, LENGTH = 4K
}
SECTIONS {
/* Vector table à l'adresse de boot */
.isr_vector : ALIGN(256) {
KEEP(*(.isr_vector))
} > FLASH_BL
/* Code bootloader */
.text : {
*(.text*)
*(.rodata*)
} > FLASH_BL
/* Données en RAM */
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > RAM AT > FLASH_BL
.bss : {
_sbss = .;
*(.bss*)
_ebss = .;
} > RAM
/* Section persistante dans backup SRAM */
.backup (NOLOAD) : {
*(.backup_data)
} > BACKUP
}
Pitfalls
- Vector table remap : SCB->VTOR doit être aligné sur 256 bytes (Cortex-M)
- Stack pointer : Toujours lire le SP depuis le vecteur du firmware — ne pas conserver celui du bootloader
- Peripherals state : Désactiver tous les périphériques avant de sauter au firmware (DMA, timers, IRQ)
- Flash wait states : Adapter les WS à la fréquence avant d'écrire en flash
- Hardfault dans le firmware : Le bootloader doit avoir son propre HardFault_Handler (pas celui du firmware)
- Watchdog : Désactiver IWDG avant de sauter au firmware (ou le firmware doit le gérer)
- CRC : Utiliser un CRC matériel (DMA) pour les gros firmwares — le CRC software est lent
- Signature : La clé publique dans le bootloader rend le bootloader non modifiable — flash protect
- Dual bank : Les deux banques doivent avoir la même taille — bien vérifier les adresses
- Rollback counter : Limiter le nombre de rollbacks (attaque DoS possible)
Ressources