From 5614d2511d7535f4d3b8c7fe1f2d4b6b8ecacf4a 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 1/5] [realtek-ambz2] Implement OTA --- builder/family/realtek-ambz2.py | 27 +++++- cores/realtek-ambz2/base/api/lt_ota.c | 115 +++++++++++++++++++++++++- cores/realtek-ambz2/base/lt_defs.h | 1 + 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/builder/family/realtek-ambz2.py b/builder/family/realtek-ambz2.py index 6a76b8f634..f67d97bd51 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},{image_bootloader}=flasher:boot,boot", f"{image_part_table},{image_part_table}=flasher:part_table,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 d96af85d1f..c826850dbd 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 4868572f39..0105a2a157 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 From 47404def64dae740afdac62b663f9884f8f5b419 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Jan 2026 19:09:58 -1000 Subject: [PATCH 2/5] Fix uf2 filename --- builder/family/realtek-ambz2.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builder/family/realtek-ambz2.py b/builder/family/realtek-ambz2.py index f67d97bd51..144fa94d69 100644 --- a/builder/family/realtek-ambz2.py +++ b/builder/family/realtek-ambz2.py @@ -437,7 +437,6 @@ def encode_for_define(data: bytes) -> str: 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} "" ""', @@ -445,8 +444,8 @@ def encode_for_define(data: bytes) -> str: UF2OTA=[ # 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", + # use same image for device OTA + f"{image_firmware_is},{image_firmware_is}=device:ota1,ota2", # having flashed an application image, update the bootloader and partition table (incl. keys) f"{image_bootloader},{image_bootloader}=flasher:boot,boot", f"{image_part_table},{image_part_table}=flasher:part_table,part_table", From a0386835b5d522c0db46de9427dbbb57a6af2745 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 24 Jan 2026 11:47:41 -1000 Subject: [PATCH 3/5] Fix encode_for_define for Windows compatibility Use C array initializer syntax instead of escaped string literal to avoid shell escaping differences between Windows and Unix. --- builder/family/realtek-ambz2.py | 4 ++-- cores/realtek-ambz2/base/api/lt_ota.c | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/builder/family/realtek-ambz2.py b/builder/family/realtek-ambz2.py index 144fa94d69..b1b3a76736 100644 --- a/builder/family/realtek-ambz2.py +++ b/builder/family/realtek-ambz2.py @@ -26,8 +26,8 @@ def get_public_key(private: bytes) -> bytes: 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) + '\\"' + # use C array initializer syntax to avoid shell escaping issues on Windows + return "{" + ",".join(f"0x{byte:02x}" for byte in data) + "}" public_key_bytes = get_public_key(ImageConfig(**board.get("image")).keys.decryption) diff --git a/cores/realtek-ambz2/base/api/lt_ota.c b/cores/realtek-ambz2/base/api/lt_ota.c index c826850dbd..0883d351ad 100644 --- a/cores/realtek-ambz2/base/api/lt_ota.c +++ b/cores/realtek-ambz2/base/api/lt_ota.c @@ -9,10 +9,12 @@ 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 +// IMAGE_PUBLIC_KEY is defined by the build script as an array initializer #define IMAGE_PUBLIC_KEY_OFFSET 32 #define IMAGE_PUBLIC_KEY_LENGTH 32 +static const uint8_t lt_image_public_key[] = IMAGE_PUBLIC_KEY; + typedef enum { INVALID = 0, DISABLED = 1, @@ -47,11 +49,11 @@ static lt_ota_image_state_t lt_ota_get_image_state(uint8_t index) { if (num_read != sizeof(public_key)) return INVALID; - if (memcmp(public_key, IMAGE_PUBLIC_KEY, sizeof(public_key)) == 0) + if (memcmp(public_key, lt_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) + if (memcmp(public_key, lt_image_public_key, sizeof(public_key)) == 0) return DISABLED; return INVALID; @@ -69,7 +71,7 @@ static bool lt_ota_set_image_enabled(uint8_t index, bool new_enabled) { 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]; + bool enabled = header[IMAGE_PUBLIC_KEY_OFFSET] == lt_image_public_key[0]; if (enabled != new_enabled) { // negate first byte of OTA signature header[0] = ~(header[0]); From 9f36a5990482f3f712e0bcf4de9978f90343ba59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 24 Jan 2026 11:58:42 -1000 Subject: [PATCH 4/5] Format lt_ota.c with clang-format --- cores/realtek-ambz2/base/api/lt_ota.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/realtek-ambz2/base/api/lt_ota.c b/cores/realtek-ambz2/base/api/lt_ota.c index 0883d351ad..5e7e6ce3be 100644 --- a/cores/realtek-ambz2/base/api/lt_ota.c +++ b/cores/realtek-ambz2/base/api/lt_ota.c @@ -1,9 +1,9 @@ /* Copyright (c) Kuba Szczodrzyński 2023-05-22. */ +#include #include -#include #include -#include +#include // from SDK extern uint32_t sys_update_ota_get_curr_fw_idx(void); From ede22fe94674631dc414c01f68289de9f970fbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 24 Jan 2026 23:11:04 +0100 Subject: [PATCH 5/5] Update cores/realtek-ambz2/base/api/lt_ota.c --- cores/realtek-ambz2/base/api/lt_ota.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/realtek-ambz2/base/api/lt_ota.c b/cores/realtek-ambz2/base/api/lt_ota.c index 5e7e6ce3be..8d9e2cd767 100644 --- a/cores/realtek-ambz2/base/api/lt_ota.c +++ b/cores/realtek-ambz2/base/api/lt_ota.c @@ -1,4 +1,4 @@ -/* Copyright (c) Kuba Szczodrzyński 2023-05-22. */ +/* Copyright (c) Martin Prokopič 2024-12-03. */ #include #include