STM32F3: Implement self-update

This commit is contained in:
zvecr 2023-02-16 06:17:34 +00:00
parent 636a93390f
commit 8fe4f9e009
8 changed files with 347 additions and 49 deletions

View file

@ -34,7 +34,7 @@ Not all features are implemented for all MCUs, following is supported MCUs and i
| K32L2 | ✔ | ✔ | | | | |
| LPC55 | ✔ | ✔ | | | ✔ | |
| iMXRT | ✔ | ✔ | ✔ | | ✔ | |
| STM32F3 | ✔ | ✔ | | | ✔ | |
| STM32F3 | ✔ | ✔ | | | ✔ | |
| STM32F4 | ✔ | ✔ | ✔ | ✔ | ✔ | |
## Build and Flash

View file

@ -13,5 +13,13 @@ flash-dfu-util: $(BUILD)/$(OUTNAME).bin
# Self-update
#------------------------------------------
self-update:
@echo "not implemented yet"
# direction containing Makefile for building update app
SELF_DIR = apps/self_update
$(SELF_DIR)/bootloader_bin.c: $(BUILD)/$(OUTNAME).bin
$(PYTHON3) $(TOP)/lib/uf2/utils/uf2conv.py --carray $^ -o $@
# remove bootloader_bin.c at the end to force re-generate each time
self-update: $(SELF_DIR)/bootloader_bin.c
make -C $(SELF_DIR) BOARD=$(BOARD) LOG=$(LOG) LOGGER=$(LOGGER) self-update
@rm -f $^

View file

@ -0,0 +1,25 @@
PORT = stm32f3
OUTNAME = update-tinyuf2-$(BOARD)
# skip bootloader src
BUILD_APPLICATION = 1
# skip tinyusb src
BUILD_NO_TINYUSB = 1
CFLAGS += -DTINYUF2_SELF_UPDATE -Wno-error=stringop-overread
include ../../../make.mk
include ../../port.mk
SRC_C += \
apps/self_update/self_update.c \
$(CURRENT_PATH)/bootloader_bin.c
include ../../../rules.mk
self-update: $(BUILD)/$(OUTNAME).uf2
$(BUILD)/$(OUTNAME).uf2: $(BUILD)/$(OUTNAME).hex
@echo CREATE $@
$(PYTHON3) $(TOP)/lib/uf2/utils/uf2conv.py -f $(UF2_FAMILY_ID) -c -o $@ $^

View file

@ -32,24 +32,24 @@
//
//--------------------------------------------------------------------+
#define FLASH_CACHE_SIZE 512
#define FLASH_CACHE_INVALID_ADDR 0xffffffff
// no caching
// #define FLASH_CACHE_SIZE 512
// #define FLASH_CACHE_INVALID_ADDR 0xffffffff
// define flash space, reserve first 8 sectors for bootloader up to 3FFF
#define BOARD_FLASH_SECTORS 64
#define BOARD_FIRST_FLASH_SECTOR_TO_ERASE 8
#define FLASH_BASE_ADDR 0x08000000UL
#define APP_LOAD_ADDRESS 0x08004000
enum
{
SECTOR_COUNT = (BOARD_FLASH_SIZE / BOARD_PAGE_SIZE),
BOOTLOADER_SECTOR_COUNT = ((BOARD_FLASH_APP_START - FLASH_BASE_ADDR) / BOARD_PAGE_SIZE)
};
/* flash parameters */
#define SIZE 2048
static uint8_t erasedSectors[BOARD_FLASH_SECTORS];
static uint8_t erased_sectors[SECTOR_COUNT] = { 0 };
uint32_t flash_func_sector_size(unsigned sector)
{
if (sector < BOARD_FLASH_SECTORS) {
return SIZE;
if (sector < SECTOR_COUNT) {
return BOARD_PAGE_SIZE;
}
return 0;
@ -71,59 +71,76 @@ static bool is_blank(uint32_t addr, uint32_t size)
return true;
}
//ADDR data
void flash_write(uint32_t dst, const uint8_t *src, int len)
static bool flash_erase(uint32_t addr)
{
// assume sector 0-7 (bootloader) is same size as sector 1
uint32_t addr = APP_LOAD_ADDRESS;
// starting address from 0x08000000
uint32_t sector_addr = FLASH_BASE_ADDR;
bool erased = false;
uint32_t sector = 0;
int erased = false;
uint32_t size = 0;
for ( unsigned i = 0; i < BOARD_FLASH_SECTORS; i++ )
for ( uint32_t i = 0; i < SECTOR_COUNT; i++ )
{
TUF2_ASSERT(sector_addr < FLASH_BASE_ADDR + BOARD_FLASH_SIZE);
size = flash_func_sector_size(i);
if ( addr + size > dst )
if ( sector_addr + size > addr )
{
sector = i + 1;
erased = erasedSectors[i];
erasedSectors[i] = 1; // don't erase anymore - we will continue writing here!
sector = i;
erased = erased_sectors[i];
erased_sectors[i] = 1; // don't erase anymore - we will continue writing here!
break;
}
addr += size;
sector_addr += size;
}
if (sector == 0)
(void)sector;
#ifndef TINYUF2_SELF_UPDATE
// skip erasing sector0 if not self-update
TUF2_ASSERT(sector);
#endif
if ( !erased && !is_blank(sector_addr, size))
{
TUF2_LOG1("invalid sector\r\n");
}
HAL_FLASH_Unlock();
if (!erased && !is_blank(addr, size))
{
uint32_t SectorError = 0;
TUF2_LOG1("Erase: %08lX size = %lu\n", addr, size);
TUF2_LOG1("Erase: %08lX size = %lu KB ... ", sector_addr, size / 1024);
FLASH_EraseInitTypeDef EraseInit;
EraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInit.PageAddress = addr;
EraseInit.NbPages = ((0x08040000 - addr)/size);
EraseInit.PageAddress = sector_addr;
EraseInit.NbPages = ((BOARD_FLASH_APP_START - sector_addr)/size);
uint32_t SectorError = 0;
HAL_FLASHEx_Erase(&EraseInit, &SectorError);
FLASH_WaitForLastOperation(HAL_MAX_DELAY);
if (SectorError != 0xFFFFFFFF)
{
TUF2_LOG1("failed to erase!\r\n");
}
TUF2_LOG1("OK\r\n");
TUF2_ASSERT( (SectorError != 0xFFFFFFFF) && is_blank(sector_addr, size) );
}
return true;
}
static void flash_write(uint32_t dst, const uint8_t *src, int len)
{
flash_erase(dst);
TUF2_LOG1("Write flash at address %08lX\r\n", dst);
for (int i = 0; i < len; i += 4)
{
uint32_t data = *( (uint32_t*) ((void*) (src + i)) );
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dst + i, (uint64_t) data);
if ( HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dst + i, (uint64_t) data) != HAL_OK )
{
TUF2_LOG1("Failed to write flash at address %08lX\r\n", dst + i);
break;
}
if ( FLASH_WaitForLastOperation(HAL_MAX_DELAY) != HAL_OK )
{
TUF2_LOG1("Waiting on last operation failed\r\n");
return;
}
}
// verify contents
@ -159,7 +176,9 @@ void board_flash_flush(void)
void board_flash_write (uint32_t addr, void const *data, uint32_t len)
{
// TODO skip matching contents
HAL_FLASH_Unlock();
flash_write(addr, data, len);
HAL_FLASH_Lock();
}
void board_flash_erase_app(void)
@ -175,9 +194,61 @@ bool board_flash_protect_bootloader(bool protect)
}
#ifdef TINYUF2_SELF_UPDATE
bool is_new_bootloader_valid(const uint8_t * bootloader_bin, uint32_t bootloader_len)
{
// at least larger than vector table
if (bootloader_len < 512 ) return false;
// similar to board_app_valid() check
if((((*(uint32_t*)bootloader_bin) - BOARD_RAM_START) <= BOARD_RAM_SIZE))
{
return true;
}
return false;
}
void board_self_update(const uint8_t * bootloader_bin, uint32_t bootloader_len)
{
(void) bootloader_bin;
(void) bootloader_len;
// check if the bootloader payload is valid
if ( is_new_bootloader_valid(bootloader_bin, bootloader_len) )
{
#if TINYUF2_PROTECT_BOOTLOADER
// Note: Don't protect bootloader when done, leave that to the new bootloader
// since it may or may not enable protection.
board_flash_protect_bootloader(false);
#endif
// keep writing until flash contents matches new bootloader data
while( memcmp((const void*) FLASH_BASE_ADDR, bootloader_bin, bootloader_len) )
{
uint32_t sector_addr = FLASH_BASE_ADDR;
const uint8_t * data = bootloader_bin;
uint32_t len = bootloader_len;
for ( uint32_t i = 0; i < BOOTLOADER_SECTOR_COUNT && len > 0; i++ )
{
uint32_t const size = (flash_func_sector_size(i) < len ? flash_func_sector_size(i) : len);
board_flash_write(sector_addr, data, size);
sector_addr += size;
data += size;
len -= size;
}
}
}
// self-destruct: write 0 to first 2 entry of vector table
// Note: write bit from 1 to 0 does not need to erase in advance
__disable_irq();
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOARD_FLASH_APP_START , 0);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOARD_FLASH_APP_START+4, 0);
HAL_FLASH_Lock();
// reset to run new bootloader
NVIC_SystemReset();
}
#endif

View file

@ -39,6 +39,8 @@
#define BOARD_FLASH_APP_START 0x08004000
#endif
#define BOARD_PAGE_SIZE 0x800
#define BOARD_RAM_START 0x20000000
#define BOARD_RAM_SIZE 0x9FFF

View file

@ -0,0 +1,188 @@
/*
*****************************************************************************
**
** File : LinkerScript.ld
**
** Abstract : Linker script for STM32F303VCTx Device with
** 256KByte FLASH, 40KByte RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
**
** Distribution: The file is distributed as is, without any warranty
** of any kind.
**
** (c)Copyright Ac6.
** You may use this file as-is or modify it according to the needs of your
** project. Distribution of this file (unmodified or modified) is not
** permitted. Ac6 permit registered System Workbench for MCU users the
** rights to distribute the assembled, compiled & linked contents of this
** file as part of an application binary file, provided that it is built
** using the System Workbench for MCU toolchain.
**
*****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of RAM */
_board_dfu_dbl_tap = _estack;
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x800;; /* required amount of heap */
_Min_Stack_Size = 0x800;; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x8004000, LENGTH = 256K - 16K /* must match BOARD_FLASH_APP_START */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K - 4 /* reserve 4 bytes for double tap */
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 8K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
_siccmram = LOADADDR(.ccmram);
/* CCM-RAM section
*
* IMPORTANT NOTE!
* If initialized variables will be placed in this section,
* the startup code needs to be modified to copy the init-values.
*/
.ccmram :
{
. = ALIGN(4);
_sccmram = .; /* create a global symbol at ccmram start */
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .; /* create a global symbol at ccmram end */
} >CCMRAM AT> FLASH
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}

View file

@ -43,7 +43,7 @@ _Min_Stack_Size = 0x800;; /* required amount of stack */
MEMORY
{
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 256K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K - 4
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K - 4 /* reserve 4 bytes for double tap */
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 8K
}

View file

@ -20,7 +20,11 @@ CFLAGS += \
CFLAGS += -Wno-error=cast-align -Wno-error=unused-parameter
# default linker file
LD_FILES ?= $(PORT_DIR)/linker/stm32f3_boot.ld
ifdef BUILD_APPLICATION
LD_FILES ?= $(PORT_DIR)/linker/stm32f3_app.ld
else
LD_FILES ?= $(PORT_DIR)/linker/stm32f3_boot.ld
endif
# Port source
SRC_C += \