From cfd00d69d09d97bafdc6c0f1942a9b9ef876e2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Prokopi=C4=8D?= Date: Sun, 1 Dec 2024 16:15:12 +0100 Subject: [PATCH] [realtek-ambz2] Implement OTA --- builder/family/realtek-ambz2.py | 31 +++++-- cores/realtek-ambz2/base/api/lt_ota.c | 115 +++++++++++++++++++++++++- cores/realtek-ambz2/base/lt_defs.h | 1 + 3 files changed, 138 insertions(+), 9 deletions(-) diff --git a/builder/family/realtek-ambz2.py b/builder/family/realtek-ambz2.py index e593bec4f..98c5c908b 100644 --- a/builder/family/realtek-ambz2.py +++ b/builder/family/realtek-ambz2.py @@ -3,6 +3,7 @@ from os.path import isfile, join from shutil import copyfile +from ltchiptool.soc.ambz2.util.models.config import ImageConfig from platformio.platform.base import PlatformBase from platformio.platform.board import PlatformBoardConfig from SCons.Script import DefaultEnvironment, Environment @@ -15,6 +16,22 @@ COMPONENT_DIR = join("$SDK_DIR", "component") + +# Get image decryption public key +def get_public_key(private: bytes) -> bytes: + from ltchiptool.util.curve25519 import X25519PrivateKey + + key = X25519PrivateKey.from_private_bytes(private) + return key.public_key() + + +def encode_for_define(data: bytes) -> str: + # we need to escape both shell and the C string + return '\\"' + "".join(f"\\\\x{byte:02x}" for byte in data) + '\\"' + + +public_key_bytes = get_public_key(ImageConfig(**board.get("image")).keys.decryption) + # Flags queue.AppendPublic( CCFLAGS=[ @@ -39,6 +56,7 @@ ("__ARM_ARCH_8M_MAIN__", "1"), ("CONFIG_BUILD_RAM", "1"), "V8M_STKOVF", + ("IMAGE_PUBLIC_KEY", encode_for_define(public_key_bytes)), ], CPPPATH=[ # allow including from GCC instead of RTL SDK @@ -419,17 +437,20 @@ image_part_table = "${BUILD_DIR}/image_part_table.${FLASH_PART_TABLE_OFFSET}.bin" image_bootloader = "${BUILD_DIR}/image_bootloader.${FLASH_BOOT_OFFSET}.bin" image_firmware_is = "${BUILD_DIR}/image_firmware_is.${FLASH_OTA1_OFFSET}.bin" +image_firmware_is_ota = "${BUILD_DIR}/image_firmware_is_ota.${FLASH_OTA1_OFFSET}.bin" env.Replace( # linker command (dual .bin outputs) LINK='${LTCHIPTOOL} link2bin ${BOARD_JSON} "" ""', # UF2OTA input list UF2OTA=[ - # same OTA images for flasher and device - f"{image_firmware_is},{image_firmware_is}=device:ota1,ota2;flasher:ota1,ota2", + # use unmodified image for flasher + f"{image_firmware_is},{image_firmware_is}=flasher:ota1,ota2", + # use patched OTA image for device + f"{image_firmware_is_ota},{image_firmware_is_ota}=device:ota1,ota2", # having flashed an application image, update the bootloader and partition table (incl. keys) - f"{image_bootloader}=device:boot;flasher:boot", - f"{image_part_table}=device:part_table;flasher:part_table", + f"{image_bootloader}=flasher:boot", + f"{image_part_table}=flasher:part_table", # clearing headers of the "other" OTA image (hence the indexes are swapped) - f"{image_ota_clear},{image_ota_clear}=device:ota2,ota1;flasher:ota2,ota1", + f"{image_ota_clear},{image_ota_clear}=flasher:ota2,ota1", ], ) diff --git a/cores/realtek-ambz2/base/api/lt_ota.c b/cores/realtek-ambz2/base/api/lt_ota.c index d96af85d1..c826850db 100644 --- a/cores/realtek-ambz2/base/api/lt_ota.c +++ b/cores/realtek-ambz2/base/api/lt_ota.c @@ -2,23 +2,130 @@ #include #include +#include +#include + +// from SDK +extern uint32_t sys_update_ota_get_curr_fw_idx(void); + +#define FLASH_SECTOR_SIZE 0x1000 +// IMAGE_PUBLIC_KEY is defined by the build script +#define IMAGE_PUBLIC_KEY_OFFSET 32 +#define IMAGE_PUBLIC_KEY_LENGTH 32 + +typedef enum { + INVALID = 0, + DISABLED = 1, + ENABLED = 2, +} lt_ota_image_state_t; + +static bool lt_ota_get_image_offset(uint8_t index, uint32_t *offset) { + switch (index) { + case 1: + *offset = FLASH_OTA1_OFFSET; + break; + case 2: + *offset = FLASH_OTA2_OFFSET; + break; + default: + return false; + } + return true; +} + +static uint8_t lt_ota_get_other_index(uint8_t index) { + return index ^ 0b11; // 1 -> 2, 2 -> 1 +} + +static lt_ota_image_state_t lt_ota_get_image_state(uint8_t index) { + uint32_t offset; + if (!lt_ota_get_image_offset(index, &offset)) + return INVALID; + + uint8_t public_key[IMAGE_PUBLIC_KEY_LENGTH]; + uint32_t num_read = lt_flash_read(offset + IMAGE_PUBLIC_KEY_OFFSET, public_key, sizeof(public_key)); + if (num_read != sizeof(public_key)) + return INVALID; + + if (memcmp(public_key, IMAGE_PUBLIC_KEY, sizeof(public_key)) == 0) + return ENABLED; + + public_key[0] = ~(public_key[0]); + if (memcmp(public_key, IMAGE_PUBLIC_KEY, sizeof(public_key)) == 0) + return DISABLED; + + return INVALID; +} + +static bool lt_ota_set_image_enabled(uint8_t index, bool new_enabled) { + uint32_t offset; + if (!lt_ota_get_image_offset(index, &offset)) + return false; + + _irqL irqL; + uint8_t *header = (uint8_t *)malloc(FLASH_SECTOR_SIZE); + + rtw_enter_critical(NULL, &irqL); + device_mutex_lock(RT_DEV_LOCK_FLASH); + flash_stream_read(<_flash_obj, offset, FLASH_SECTOR_SIZE, header); + + bool enabled = header[IMAGE_PUBLIC_KEY_OFFSET] == IMAGE_PUBLIC_KEY[0]; + if (enabled != new_enabled) { + // negate first byte of OTA signature + header[0] = ~(header[0]); + // negate first byte of public key + header[IMAGE_PUBLIC_KEY_OFFSET] = ~(header[IMAGE_PUBLIC_KEY_OFFSET]); + + // write to flash + hal_flash_sector_erase(lt_flash_obj.phal_spic_adaptor, offset); + hal_flash_burst_write(lt_flash_obj.phal_spic_adaptor, FLASH_SECTOR_SIZE, offset, header); + } + + device_mutex_unlock(RT_DEV_LOCK_FLASH); + rtw_exit_critical(NULL, &irqL); + free(header); + + return true; +} + +// public interface implementation lt_ota_type_t lt_ota_get_type() { return OTA_TYPE_DUAL; } bool lt_ota_is_valid(uint8_t index) { - return false; + return lt_ota_get_image_state(index) != INVALID; } uint8_t lt_ota_dual_get_current() { - return 0; + // ambz2 uses virtual memory, so we can't use function address to determine active image + // use the SDK instead + return sys_update_ota_get_curr_fw_idx(); } uint8_t lt_ota_dual_get_stored() { - return 0; + // bootloader prioritizes FW1 if both are valid + return lt_ota_get_image_state(1) == ENABLED ? 1 : 2; } bool lt_ota_switch(bool revert) { - return false; + uint8_t current = lt_ota_dual_get_current(); + uint8_t stored = lt_ota_dual_get_stored(); + if ((current == stored) == revert) + return true; + + uint8_t to_enable = lt_ota_get_other_index(stored); + uint8_t to_disable = stored; + + if (!lt_ota_is_valid(to_enable)) + return false; + + // enable first, so there is always at least one enabled image + if (!lt_ota_set_image_enabled(to_enable, true)) + return false; + if (!lt_ota_set_image_enabled(to_disable, false)) + return false; + + return true; } diff --git a/cores/realtek-ambz2/base/lt_defs.h b/cores/realtek-ambz2/base/lt_defs.h index 4868572f3..0105a2a15 100644 --- a/cores/realtek-ambz2/base/lt_defs.h +++ b/cores/realtek-ambz2/base/lt_defs.h @@ -7,4 +7,5 @@ #define LT_HAS_LWIP2 1 #define LT_HAS_MBEDTLS 1 #define LT_HAS_PRINTF 1 +#define LT_HAS_OTA 1 #define LT_HW_BLE 1