diff --git a/firmware/config/boards/common_make.sh b/firmware/config/boards/common_make.sh index 73782bb858..15613e943f 100755 --- a/firmware/config/boards/common_make.sh +++ b/firmware/config/boards/common_make.sh @@ -18,21 +18,6 @@ FW_DIR=$(readlink -f $FW_DIR) echo "FW dir is $FW_DIR" cd $FW_DIR -mkdir -p .dep # ChibiOS build's DEPDIR -mkdir -p build # ChibiOS build's BUILDDIR -echo "Calling make for the main firmware..." -make -j20 -r PROJECT_BOARD=$PROJECT_BOARD PROJECT_CPU=$PROJECT_CPU BOARD_DIR=$BOARD_DIR -[ -e build/fome.hex ] || { echo "FAILED to compile by $SCRIPT_NAME with $PROJECT_BOARD $DEBUG_LEVEL_OPT and $EXTRA_PARAMS"; exit 1; } -if [ "${USE_OPENBLT-no}" = "yes" ]; then - # TODO: why is this rm necessary? - rm -f pch/pch.h.gch/* - rm engine_modules_generated* - rm modules_list_generated* - echo "Calling make for the bootloader..." - cd bootloader; make -j20 PROJECT_BOARD=$PROJECT_BOARD PROJECT_CPU=$PROJECT_CPU BOARD_DIR=$BOARD_DIR; cd .. - [ -e bootloader/blbuild/fome_bl.hex ] || { echo "FAILED to compile OpenBLT by $SCRIPT_NAME with $PROJECT_BOARD"; exit 1; } -fi - if uname | grep "NT"; then export HEX2DFU=../misc/encedo_hex2dfu/hex2dfu.exe else @@ -44,6 +29,36 @@ mkdir -p deliver rm -rf deliver/* mkdir -p deliver/temp +# Build bootloader first (if OpenBLT) so we can embed its image in the main firmware +if [ "${USE_OPENBLT-no}" = "yes" ]; then + mkdir -p .dep + mkdir -p build + echo "Calling make for the bootloader..." + cd bootloader; make -j20 PROJECT_BOARD=$PROJECT_BOARD PROJECT_CPU=$PROJECT_CPU BOARD_DIR=$BOARD_DIR; cd .. + [ -e bootloader/blbuild/fome_bl.hex ] || { echo "FAILED to compile OpenBLT by $SCRIPT_NAME with $PROJECT_BOARD"; exit 1; } + + # Add a checksum to the bootloader image + $FW_DIR/config/boards/write_firmware_checksum.sh bootloader/blbuild/fome_bl.elf deliver/temp/bootloader + + # Generate bootloader image header for embedding in main firmware + # static const ensures the image is stored in flash, not copied to RAM + echo "Generating bootloader_image.h..." + xxd -i deliver/temp/bootloader.bin \ + | sed 's/deliver_temp_bootloader_bin/bootloader_image/g; s/^unsigned/static const unsigned/' \ + > generated/bootloader_image.h + + # Clean up shared build artifacts before firmware build + rm -f pch/pch.h.gch/* + rm -f engine_modules_generated* + rm -f modules_list_generated* +fi + +mkdir -p .dep # ChibiOS build's DEPDIR +mkdir -p build # ChibiOS build's BUILDDIR +echo "Calling make for the main firmware..." +make -j20 -r PROJECT_BOARD=$PROJECT_BOARD PROJECT_CPU=$PROJECT_CPU BOARD_DIR=$BOARD_DIR +[ -e build/fome.hex ] || { echo "FAILED to compile by $SCRIPT_NAME with $PROJECT_BOARD $DEBUG_LEVEL_OPT and $EXTRA_PARAMS"; exit 1; } + # Add a checksum to the main firmware image $FW_DIR/config/boards/write_firmware_checksum.sh build/fome.elf deliver/temp/main_firmware @@ -58,13 +73,9 @@ fi # bootloader and combined image if [ "$USE_OPENBLT" = "yes" ]; then - # Add a checksum to the bootloader image - $FW_DIR/config/boards/write_firmware_checksum.sh bootloader/blbuild/fome_bl.elf deliver/temp/bootloader - + # Bootloader checksum was already added before firmware build (above) cp deliver/temp/bootloader.srec deliver/fome_bl.srec - echo "$SCRIPT_NAME: invoking hex2dfu for OpenBLT" - echo "$SCRIPT_NAME: invoking hex2dfu for combined OpenBLT+FOME image" $HEX2DFU -i deliver/temp/bootloader.hex -i deliver/temp/main_firmware.hex -b deliver/fome.bin fi diff --git a/firmware/controllers/system/bootloader_updater.cpp b/firmware/controllers/system/bootloader_updater.cpp index 2d164daea1..c6ba3daecf 100644 --- a/firmware/controllers/system/bootloader_updater.cpp +++ b/firmware/controllers/system/bootloader_updater.cpp @@ -3,17 +3,109 @@ #include "bootloader_updater.h" #include "crc_accelerator.h" -void checkBootloaderIntegrity() { +#if EFI_USE_OPENBLT && CORTEX_MODEL == 7 +#include "flash_int.h" + +// This header is generated during the build from the checksummed bootloader binary +#include "bootloader_image.h" + +static const size_t checksumOffset = 0x1C; + +static uint32_t getStoredCrc(const uint8_t* image) { + return *reinterpret_cast(image + checksumOffset); +} + +static void flashBootloader() { + // Verify the embedded image has a valid integrity check before we trust it + if (!checkFirmwareImageIntegrity(reinterpret_cast(bootloader_image))) { + efiPrintf("Bootloader update: embedded image integrity check FAILED, aborting"); + return; + } + + uint32_t embeddedCrc = getStoredCrc(bootloader_image); + uint32_t flashCrc = getStoredCrc(reinterpret_cast(FLASH_BASE)); + bool currentBootloaderValid = checkFirmwareImageIntegrity(FLASH_BASE); + + if (currentBootloaderValid && embeddedCrc == flashCrc) { + efiPrintf("Bootloader is up to date (CRC 0x%08x)", (unsigned int)flashCrc); + + // Bootloader is valid, but make sure boot address points to it + if (getBootAddress() != FLASH_BASE) { + efiPrintf("Boot address was 0x%08x, correcting to bootloader", (unsigned int)getBootAddress()); + postBootloaderUpdate(); + } + + return; + } + + if (!currentBootloaderValid) { + efiPrintf("Bootloader update: current bootloader is CORRUPT, updating..."); + } else { + efiPrintf( + "Bootloader update: CRC mismatch (flash 0x%08x, embedded 0x%08x), updating...", + (unsigned int)flashCrc, + (unsigned int)embeddedCrc); + } + + // Set boot address to firmware so MCU stays bootable during the update + preBootloaderUpdate(); + + // Erase the bootloader flash sector(s) + int eraseResult = intFlashErase(FLASH_BASE, bootloader_image_len); + if (eraseResult != FLASH_RETURN_SUCCESS) { + efiPrintf("Bootloader update: erase FAILED (%d)", eraseResult); + // Boot address stays at firmware - MCU is still bootable + return; + } + + // Write the embedded bootloader image + int writeResult = intFlashWrite(FLASH_BASE, reinterpret_cast(bootloader_image), bootloader_image_len); + if (writeResult != FLASH_RETURN_SUCCESS) { + efiPrintf("Bootloader update: write FAILED (%d)", writeResult); + // Boot address stays at firmware - MCU is still bootable + return; + } + + // Verify the written data matches + if (!intFlashCompare(FLASH_BASE, reinterpret_cast(bootloader_image), bootloader_image_len)) { + efiPrintf("Bootloader update: verify FAILED - data mismatch after write"); + // Boot address stays at firmware - MCU is still bootable + return; + } + + // Verify the written image passes integrity check + if (!checkFirmwareImageIntegrity(FLASH_BASE)) { + efiPrintf("Bootloader update: verify FAILED - integrity check failed after write"); + // Boot address stays at firmware - MCU is still bootable + return; + } + + // Success - restore boot address to bootloader + postBootloaderUpdate(); + efiPrintf("Bootloader update: SUCCESS (new CRC 0x%08x)", (unsigned int)embeddedCrc); +} +#endif // EFI_USE_OPENBLT && CORTEX_MODEL == 7 + +void updateBootloader() { #if CORTEX_MODEL == 7 - efiPrintf("Current boot address: 0x%08x", getBootAddress()); + efiPrintf("Current boot address: 0x%08x", (unsigned int)getBootAddress()); #endif // Bootloader always lives in the first page of flash - FLASH_BASE - bool bootloaderIntact = checkFirmwareImageIntegrity(FLASH_BASE); - - if (bootloaderIntact) { + if (checkFirmwareImageIntegrity(FLASH_BASE)) { efiPrintf("Bootloader integrity check OK!"); } else { efiPrintf("Bootloader integrity check failed - here be dragons!"); } + + // Check our own firmware image too - not much we can do, but good to know + if (checkFirmwareImageIntegrity(SCB->VTOR)) { + efiPrintf("Firmware integrity check OK!"); + } else { + efiPrintf("Firmware integrity check failed!"); + } + +#if EFI_USE_OPENBLT && CORTEX_MODEL == 7 + flashBootloader(); +#endif } diff --git a/firmware/controllers/system/bootloader_updater.h b/firmware/controllers/system/bootloader_updater.h index 4813632029..3f18e8fee4 100644 --- a/firmware/controllers/system/bootloader_updater.h +++ b/firmware/controllers/system/bootloader_updater.h @@ -1,3 +1,3 @@ #pragma once -void checkBootloaderIntegrity(); +void updateBootloader(); diff --git a/firmware/hw_layer/ports/mpu_util.h b/firmware/hw_layer/ports/mpu_util.h index 72dcee935c..f3e3b00f44 100644 --- a/firmware/hw_layer/ports/mpu_util.h +++ b/firmware/hw_layer/ports/mpu_util.h @@ -78,6 +78,12 @@ BOR_Result_t BOR_Set(BOR_Level_t BORValue); uintptr_t getBootAddress(); bool setBootAddress(uintptr_t address); +// Bootloader update safety functions (F7/H7 only) +// Sets boot address to firmware so MCU remains bootable if power is lost during bootloader erase/write +void preBootloaderUpdate(); +// Restores boot address to bootloader after successful update +void postBootloaderUpdate(); + void assertInterruptPriority(const char* func, uint8_t expectedPrio); extern "C" { diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index d9495b7601..3eeb578993 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -486,6 +486,20 @@ bool setBootAddress(uintptr_t address) { return status == HAL_OK; } + +void preBootloaderUpdate() { + // Set boot address to the main firmware so that if power is lost during + // bootloader erase/write, the MCU still boots into working firmware + setBootAddress(SCB->VTOR); + efiPrintf("Boot address set to firmware: 0x%08x", (uintptr_t)SCB->VTOR); +} + +void postBootloaderUpdate() { + // Restore boot address to the bootloader at the start of flash + setBootAddress(FLASH_BASE); + efiPrintf("Boot address restored to bootloader: 0x%08x", (uintptr_t)FLASH_BASE); +} + #endif // CORTEX_MODEL == 7 void baseMCUInit(void) { diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index 5ce776e3d7..adf2f538c1 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -216,7 +216,7 @@ void runRusEfi() { initMainLoop(); #if EFI_USE_OPENBLT - checkBootloaderIntegrity(); + updateBootloader(); #endif runMainLoop();