From 7ce305fca32de6482de063237ddf111f196aad66 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 27 Apr 2022 18:53:48 +0300 Subject: [PATCH] [FL-2269] Core2 OTA (#1144) * C2OTA: wip * Update Cube to 1.13.3 * Fixed prio * Functional Core2 updater * Removed hardware CRC usage; code cleanup & linter fixes * Moved hardcoded stack params to copro.mk * Fixing CI bundling of core2 fw * Removed last traces of hardcoded radio stack * OB processing draft * Python scripts cleanup * Support for comments in ob data * Sacrificed SD card icon in favor of faster update. Waiting for Storage fix * Additional handling for OB mismatched values * Description for new furi_hal apis; spelling fixes * Rework of OB write, WIP * Properly restarting OB verification loop * Split update_task_workers.c * Checking OBs after enabling post-update mode * Moved OB verification before flashing * Removed ob.data for custom stacks * Fixed progress calculation for OB * Removed unnecessary OB mask cast Co-authored-by: Aleksandr Kutuzov --- .github/workflows/build.yml | 14 +- Makefile | 17 +- applications/bt/bt_cli.c | 2 +- applications/bt/bt_service/bt.c | 7 + .../updater/scenes/updater_scene_main.c | 13 +- applications/updater/util/update_task.c | 23 +- applications/updater/util/update_task.h | 9 +- ..._workers.c => update_task_worker_backup.c} | 92 +---- .../updater/util/update_task_worker_flasher.c | 363 ++++++++++++++++++ assets/.gitignore | 1 + assets/Makefile | 7 + assets/copro.mk | 12 + firmware/targets/f7/Src/update.c | 8 +- firmware/targets/f7/ble_glue/ble_glue.c | 221 +++++++++-- firmware/targets/f7/ble_glue/ble_glue.h | 75 +++- firmware/targets/f7/furi_hal/furi_hal.c | 1 - firmware/targets/f7/furi_hal/furi_hal_bt.c | 43 ++- firmware/targets/f7/furi_hal/furi_hal_crc.c | 7 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 134 ++++++- firmware/targets/f7/furi_hal/furi_hal_flash.h | 49 ++- firmware/targets/f7/furi_hal/furi_hal_info.c | 37 +- firmware/targets/f7/furi_hal/furi_hal_vcp.c | 2 +- firmware/targets/furi_hal_include/furi_hal.h | 1 - .../targets/furi_hal_include/furi_hal_bt.h | 7 + .../targets/furi_hal_include/furi_hal_rtc.h | 1 + lib/STM32CubeWB | 2 +- lib/lib.mk | 3 +- lib/toolbox/crc32_calc.c | 38 ++ lib/toolbox/crc32_calc.h | 18 + lib/update_util/dfu_file.c | 34 +- lib/update_util/update_manifest.c | 61 ++- lib/update_util/update_manifest.h | 21 +- lib/update_util/update_operation.c | 14 +- scripts/assets.py | 37 +- scripts/dist.py | 1 + scripts/flash.py | 27 +- scripts/flipper/app.py | 2 +- scripts/flipper/assets/copro.py | 42 +- scripts/flipper/assets/coprobin.py | 187 +++++++++ scripts/flipper/assets/obdata.py | 208 ++++++++++ scripts/update.py | 76 +++- 41 files changed, 1622 insertions(+), 295 deletions(-) rename applications/updater/util/{update_task_workers.c => update_task_worker_backup.c} (59%) create mode 100644 applications/updater/util/update_task_worker_flasher.c create mode 100644 assets/copro.mk create mode 100644 lib/toolbox/crc32_calc.c create mode 100644 lib/toolbox/crc32_calc.h create mode 100644 scripts/flipper/assets/coprobin.py create mode 100644 scripts/flipper/assets/obdata.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22f5ce5a..1a0a91e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,11 +121,11 @@ jobs: - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - test -d core2_firmware && rm -rf core2_firmware || true - mkdir core2_firmware - ./scripts/assets.py copro lib/STM32CubeWB core2_firmware STM32WB5x - tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz core2_firmware + uses: ./.github/actions/docker + with: + run: | + make -C assets copro_bundle + tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -213,8 +213,8 @@ jobs: with: run: | set -e - make -C assets clean - make -C assets + make assets_rebuild assets_manifest + git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) - name: 'Build the firmware in docker' uses: ./.github/actions/docker diff --git a/Makefile b/Makefile index ca3caa22..13692347 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) include $(PROJECT_ROOT)/make/git.mk - -COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x +include $(PROJECT_ROOT)/assets/copro.mk PROJECT_SOURCE_DIRECTORIES := \ $(PROJECT_ROOT)/applications \ @@ -97,7 +96,13 @@ updater_package_bin: firmware_all updater .PHONY: updater_package updater_package: firmware_all updater assets_manifest - @$(PROJECT_ROOT)/scripts/dist.py copy -t $(TARGET) -p firmware updater -s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources --bundlever "$(VERSION_STRING)" + @$(PROJECT_ROOT)/scripts/dist.py copy \ + -t $(TARGET) -p firmware updater \ + -s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources \ + --bundlever "$(VERSION_STRING)" \ + --radio $(COPRO_STACK_BIN_PATH) \ + --radiotype $(COPRO_STACK_TYPE) \ + --obdata $(PROJECT_ROOT)/scripts/ob.data .PHONY: assets_manifest assets_manifest: @@ -109,7 +114,7 @@ assets_rebuild: .PHONY: flash_radio flash_radio: - @$(PROJECT_ROOT)/scripts/flash.py core2radio 0x080D7000 $(COPRO_DIR)/stm32wb5x_BLE_Stack_light_fw.bin + @$(PROJECT_ROOT)/scripts/flash.py core2radio $(COPRO_STACK_BIN_PATH) --addr=$(COPRO_STACK_ADDR) @$(PROJECT_ROOT)/scripts/ob.py set .PHONY: flash_radio_fus @@ -125,8 +130,8 @@ flash_radio_fus: .PHONY: flash_radio_fus_please_i_m_not_going_to_complain flash_radio_fus_please_i_m_not_going_to_complain: - @$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin - @$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin + @$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin + @$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw.bin @$(PROJECT_ROOT)/scripts/ob.py set .PHONY: lint diff --git a/applications/bt/bt_cli.c b/applications/bt/bt_cli.c index 4c68e597..64d69a12 100644 --- a/applications/bt/bt_cli.c +++ b/applications/bt/bt_cli.c @@ -148,7 +148,7 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { static void bt_cli_scan_callback(GapAddress address, void* context) { furi_assert(context); osMessageQueueId_t queue = context; - osMessageQueuePut(queue, &address, NULL, 250); + osMessageQueuePut(queue, &address, 0, 250); } static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index 8ce7f9ce..5636cece 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -319,6 +319,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { int32_t bt_srv() { Bt* bt = bt_alloc(); + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode"); + ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); + furi_record_create("bt", bt); + return 0; + } + // Read keys if(!bt_keys_storage_load(bt)) { FURI_LOG_W(TAG, "Failed to load bonding keys"); diff --git a/applications/updater/scenes/updater_scene_main.c b/applications/updater/scenes/updater_scene_main.c index 31a212e7..165028c4 100644 --- a/applications/updater/scenes/updater_scene_main.c +++ b/applications/updater/scenes/updater_scene_main.c @@ -36,9 +36,9 @@ void updater_scene_main_on_enter(void* context) { * will be missing from UI, however, /ext will be fully operational. So, until it's fixed, this * should remain commented out. */ // If (somehow) we started after SD card is mounted, initiate update immediately - //if(storage_sd_status(updater->storage) == FSE_OK) { - // view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate); - //} + if(storage_sd_status(updater->storage) == FSE_OK) { + view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate); + } updater_main_set_view_dispatcher(main_view, updater->view_dispatcher); view_dispatcher_switch_to_view(updater->view_dispatcher, UpdaterViewMain); @@ -64,13 +64,6 @@ bool updater_scene_main_on_event(void* context, SceneManagerEvent event) { } else if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case UpdaterCustomEventStartUpdate: - if(!update_task_is_running(updater->update_task) && - update_task_init(updater->update_task)) { - update_task_start(updater->update_task); - } - consumed = true; - break; - case UpdaterCustomEventRetryUpdate: if(!update_task_is_running(updater->update_task) && (update_task_get_state(updater->update_task)->stage != UpdateTaskStageCompleted)) diff --git a/applications/updater/util/update_task.c b/applications/updater/util/update_task.c index 197d42f5..9fde086a 100644 --- a/applications/updater/util/update_task.c +++ b/applications/updater/util/update_task.c @@ -15,13 +15,18 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageValidateDFUImage] = "Checking DFU file", [UpdateTaskStageFlashWrite] = "Writing flash", [UpdateTaskStageFlashValidate] = "Validating", + [UpdateTaskStageRadioImageValidate] = "Checking radio image", + [UpdateTaskStageRadioErase] = "Removing radio stack", [UpdateTaskStageRadioWrite] = "Writing radio stack", - [UpdateTaskStageRadioCommit] = "Applying radio stack", + [UpdateTaskStageRadioInstall] = "Installing radio stack", + [UpdateTaskStageRadioBusy] = "Core2 is updating", + [UpdateTaskStageOBValidation] = "Validating opt. bytes", [UpdateTaskStageLfsBackup] = "Backing up LFS", [UpdateTaskStageLfsRestore] = "Restoring LFS", [UpdateTaskStageResourcesUpdate] = "Updating resources", [UpdateTaskStageCompleted] = "Completed!", [UpdateTaskStageError] = "Error", + [UpdateTaskStageOBError] = "OB error, pls report", }; static void update_task_set_status(UpdateTask* update_task, const char* status) { @@ -37,7 +42,10 @@ static void update_task_set_status(UpdateTask* update_task, const char* status) void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress) { if(stage != UpdateTaskStageProgress) { - update_task->state.stage = stage; + // do not override more specific error states + if((update_task->state.stage < UpdateTaskStageError) || (stage < UpdateTaskStageError)) { + update_task->state.stage = stage; + } update_task->state.current_stage_idx++; update_task_set_status(update_task, NULL); } @@ -53,7 +61,7 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui progress, update_task->state.current_stage_idx, update_task->state.total_stages, - update_task->state.stage == UpdateTaskStageError, + update_task->state.stage >= UpdateTaskStageError, update_task->status_change_cb_state); } } @@ -116,6 +124,7 @@ UpdateTask* update_task_alloc() { update_task->storage = furi_record_open("storage"); update_task->file = storage_file_alloc(update_task->storage); update_task->status_change_cb = NULL; + string_init(update_task->update_path); FuriThread* thread = update_task->thread = furi_thread_alloc(); @@ -152,12 +161,6 @@ void update_task_free(UpdateTask* update_task) { free(update_task); } -bool update_task_init(UpdateTask* update_task) { - furi_assert(update_task); - string_init(update_task->update_path); - return true; -} - bool update_task_parse_manifest(UpdateTask* update_task) { furi_assert(update_task); update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); @@ -224,4 +227,4 @@ UpdateTaskState const* update_task_get_state(UpdateTask* update_task) { UpdateManifest const* update_task_get_manifest(UpdateTask* update_task) { furi_assert(update_task); return update_task->manifest; -} +} \ No newline at end of file diff --git a/applications/updater/util/update_task.h b/applications/updater/util/update_task.h index 2325824c..8afc3121 100644 --- a/applications/updater/util/update_task.h +++ b/applications/updater/util/update_task.h @@ -19,13 +19,18 @@ typedef enum { UpdateTaskStageValidateDFUImage, UpdateTaskStageFlashWrite, UpdateTaskStageFlashValidate, + UpdateTaskStageRadioImageValidate, + UpdateTaskStageRadioErase, UpdateTaskStageRadioWrite, - UpdateTaskStageRadioCommit, + UpdateTaskStageRadioInstall, + UpdateTaskStageRadioBusy, + UpdateTaskStageOBValidation, UpdateTaskStageLfsBackup, UpdateTaskStageLfsRestore, UpdateTaskStageResourcesUpdate, UpdateTaskStageCompleted, UpdateTaskStageError, + UpdateTaskStageOBError } UpdateTaskStage; typedef struct { @@ -50,8 +55,6 @@ UpdateTask* update_task_alloc(); void update_task_free(UpdateTask* update_task); -bool update_task_init(UpdateTask* update_task); - void update_task_set_progress_cb(UpdateTask* update_task, updateProgressCb cb, void* state); bool update_task_start(UpdateTask* update_task); diff --git a/applications/updater/util/update_task_workers.c b/applications/updater/util/update_task_worker_backup.c similarity index 59% rename from applications/updater/util/update_task_workers.c rename to applications/updater/util/update_task_worker_backup.c index d0abbc18..0ca1a998 100644 --- a/applications/updater/util/update_task_workers.c +++ b/applications/updater/util/update_task_worker_backup.c @@ -9,99 +9,17 @@ #include #include #include +#include + +#define TAG "UpdWorkerBackup" #define CHECK_RESULT(x) \ if(!(x)) { \ break; \ } -#define STM_DFU_VENDOR_ID 0x0483 -#define STM_DFU_PRODUCT_ID 0xDF11 -/* Written into DFU file by build pipeline */ -#define FLIPPER_ZERO_DFU_DEVICE_CODE 0xFFFF - #define EXT_PATH "/ext" -static const DfuValidationParams flipper_dfu_params = { - .device = FLIPPER_ZERO_DFU_DEVICE_CODE, - .product = STM_DFU_PRODUCT_ID, - .vendor = STM_DFU_VENDOR_ID, -}; - -static void update_task_dfu_progress(const uint8_t progress, void* context) { - UpdateTask* update_task = context; - update_task_set_progress(update_task, UpdateTaskStageProgress, progress); -} - -static bool page_task_compare_flash( - const uint8_t i_page, - const uint8_t* update_block, - uint16_t update_block_len) { - const size_t page_addr = furi_hal_flash_get_base() + furi_hal_flash_get_page_size() * i_page; - return (memcmp(update_block, (void*)page_addr, update_block_len) == 0); -} - -/* Verifies a flash operation address for fitting into writable memory - */ -static bool check_address_boundaries(const size_t address) { - const size_t min_allowed_address = furi_hal_flash_get_base(); - const size_t max_allowed_address = (size_t)furi_hal_flash_get_free_end_address(); - return ((address >= min_allowed_address) && (address < max_allowed_address)); -} - -int32_t update_task_worker_flash_writer(void* context) { - furi_assert(context); - UpdateTask* update_task = context; - bool success = false; - DfuUpdateTask page_task = { - .address_cb = &check_address_boundaries, - .progress_cb = &update_task_dfu_progress, - .task_cb = &furi_hal_flash_program_page, - .context = update_task, - }; - - update_task->state.current_stage_idx = 0; - update_task->state.total_stages = 4; - - do { - CHECK_RESULT(update_task_parse_manifest(update_task)); - - if(!string_empty_p(update_task->manifest->firmware_dfu_image)) { - update_task_set_progress(update_task, UpdateTaskStageValidateDFUImage, 0); - CHECK_RESULT( - update_task_open_file(update_task, update_task->manifest->firmware_dfu_image)); - CHECK_RESULT( - dfu_file_validate_crc(update_task->file, &update_task_dfu_progress, update_task)); - - const uint8_t valid_targets = - dfu_file_validate_headers(update_task->file, &flipper_dfu_params); - if(valid_targets == 0) { - break; - } - - update_task_set_progress(update_task, UpdateTaskStageFlashWrite, 0); - CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); - - page_task.task_cb = &page_task_compare_flash; - - update_task_set_progress(update_task, UpdateTaskStageFlashValidate, 0); - CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); - } - - update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); - - furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); - - success = true; - } while(false); - - if(!success) { - update_task_set_progress(update_task, UpdateTaskStageError, update_task->state.progress); - } - - return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; -} - static bool update_task_pre_update(UpdateTask* update_task) { bool success = false; string_t backup_file_path; @@ -143,7 +61,8 @@ static bool update_task_post_update(UpdateTask* update_task) { string_t file_path; string_init(file_path); - update_task->state.total_stages = 2; + // status text is too long, too few stages to bother with a counter + update_task->state.total_stages = 0; do { CHECK_RESULT(update_task_parse_manifest(update_task)); @@ -184,6 +103,7 @@ static bool update_task_post_update(UpdateTask* update_task) { } tar_archive_free(archive); } + success = true; } while(false); string_clear(file_path); diff --git a/applications/updater/util/update_task_worker_flasher.c b/applications/updater/util/update_task_worker_flasher.c new file mode 100644 index 00000000..22e94fd0 --- /dev/null +++ b/applications/updater/util/update_task_worker_flasher.c @@ -0,0 +1,363 @@ +#include "update_task.h" +#include "update_task_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "UpdWorkerRAM" + +#define CHECK_RESULT(x) \ + if(!(x)) { \ + break; \ + } + +#define STM_DFU_VENDOR_ID 0x0483 +#define STM_DFU_PRODUCT_ID 0xDF11 +/* Written into DFU file by build pipeline */ +#define FLIPPER_ZERO_DFU_DEVICE_CODE 0xFFFF +/* Time, in ms, to wait for system restart by C2 before crashing */ +#define C2_MODE_SWITCH_TIMEOUT 10000 + +static const DfuValidationParams flipper_dfu_params = { + .device = FLIPPER_ZERO_DFU_DEVICE_CODE, + .product = STM_DFU_PRODUCT_ID, + .vendor = STM_DFU_VENDOR_ID, +}; + +static void update_task_file_progress(const uint8_t progress, void* context) { + UpdateTask* update_task = context; + update_task_set_progress(update_task, UpdateTaskStageProgress, progress); +} + +static bool page_task_compare_flash( + const uint8_t i_page, + const uint8_t* update_block, + uint16_t update_block_len) { + const size_t page_addr = furi_hal_flash_get_base() + furi_hal_flash_get_page_size() * i_page; + return (memcmp(update_block, (void*)page_addr, update_block_len) == 0); +} + +/* Verifies a flash operation address for fitting into writable memory + */ +static bool check_address_boundaries(const size_t address) { + const size_t min_allowed_address = furi_hal_flash_get_base(); + const size_t max_allowed_address = (size_t)furi_hal_flash_get_free_end_address(); + return ((address >= min_allowed_address) && (address < max_allowed_address)); +} + +static bool update_task_write_dfu(UpdateTask* update_task) { + DfuUpdateTask page_task = { + .address_cb = &check_address_boundaries, + .progress_cb = &update_task_file_progress, + .task_cb = &furi_hal_flash_program_page, + .context = update_task, + }; + + bool success = false; + do { + update_task_set_progress(update_task, UpdateTaskStageValidateDFUImage, 0); + CHECK_RESULT( + update_task_open_file(update_task, update_task->manifest->firmware_dfu_image)); + CHECK_RESULT( + dfu_file_validate_crc(update_task->file, &update_task_file_progress, update_task)); + + const uint8_t valid_targets = + dfu_file_validate_headers(update_task->file, &flipper_dfu_params); + if(valid_targets == 0) { + break; + } + + update_task_set_progress(update_task, UpdateTaskStageFlashWrite, 0); + CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); + + page_task.task_cb = &page_task_compare_flash; + + update_task_set_progress(update_task, UpdateTaskStageFlashValidate, 0); + CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); + success = true; + } while(false); + + return success; +} + +static bool update_task_write_stack_data(UpdateTask* update_task) { + furi_check(storage_file_is_open(update_task->file)); + const size_t FLASH_PAGE_SIZE = furi_hal_flash_get_page_size(); + + uint32_t stack_size = storage_file_size(update_task->file); + storage_file_seek(update_task->file, 0, true); + + if(!check_address_boundaries(update_task->manifest->radio_address) || + !check_address_boundaries(update_task->manifest->radio_address + stack_size)) { + return false; + } + + update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0); + uint8_t* fw_block = malloc(FLASH_PAGE_SIZE); + uint16_t bytes_read = 0; + uint32_t element_offs = 0; + + while(element_offs < stack_size) { + uint32_t n_bytes_to_read = FLASH_PAGE_SIZE; + if((element_offs + n_bytes_to_read) > stack_size) { + n_bytes_to_read = stack_size - element_offs; + } + + bytes_read = storage_file_read(update_task->file, fw_block, n_bytes_to_read); + if(bytes_read == 0) { + break; + } + + int16_t i_page = + furi_hal_flash_get_page_number(update_task->manifest->radio_address + element_offs); + if(i_page < 0) { + break; + } + + if(!furi_hal_flash_program_page(i_page, fw_block, bytes_read)) { + break; + } + + element_offs += bytes_read; + update_task_set_progress( + update_task, UpdateTaskStageProgress, element_offs * 100 / stack_size); + } + + free(fw_block); + return element_offs == stack_size; +} + +static void update_task_wait_for_restart(UpdateTask* update_task) { + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); + osDelay(C2_MODE_SWITCH_TIMEOUT); + furi_crash("C2 timeout"); +} + +static bool update_task_write_stack(UpdateTask* update_task) { + bool success = false; + do { + FURI_LOG_W(TAG, "Writing stack"); + update_task_set_progress(update_task, UpdateTaskStageRadioImageValidate, 0); + CHECK_RESULT(update_task_open_file(update_task, update_task->manifest->radio_image)); + CHECK_RESULT( + crc32_calc_file(update_task->file, &update_task_file_progress, update_task) == + update_task->manifest->radio_crc); + + CHECK_RESULT(update_task_write_stack_data(update_task)); + update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 0); + CHECK_RESULT( + ble_glue_fus_stack_install(update_task->manifest->radio_address, 0) != + BleGlueCommandResultError); + update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 80); + CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); + update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100); + /* ...system will restart here. */ + update_task_wait_for_restart(update_task); + success = true; + } while(false); + return success; +} + +static bool update_task_remove_stack(UpdateTask* update_task) { + bool success = false; + do { + FURI_LOG_W(TAG, "Removing stack"); + update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30); + CHECK_RESULT(ble_glue_fus_stack_delete() != BleGlueCommandResultError); + update_task_set_progress(update_task, UpdateTaskStageRadioErase, 80); + CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); + update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100); + /* ...system will restart here. */ + update_task_wait_for_restart(update_task); + success = true; + } while(false); + return success; +} + +static bool update_task_manage_radiostack(UpdateTask* update_task) { + bool success = false; + do { + CHECK_RESULT(ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)); + + const BleGlueC2Info* c2_state = ble_glue_get_c2_info(); + + const UpdateManifestRadioVersion* radio_ver = &update_task->manifest->radio_version; + bool stack_version_match = (c2_state->VersionMajor == radio_ver->version.major) && + (c2_state->VersionMinor == radio_ver->version.minor) && + (c2_state->VersionSub == radio_ver->version.sub) && + (c2_state->VersionBranch == radio_ver->version.branch) && + (c2_state->VersionReleaseType == radio_ver->version.release); + bool stack_missing = (c2_state->VersionMajor == 0) && (c2_state->VersionMinor == 0); + + if(c2_state->mode == BleGlueC2ModeStack) { + /* Stack type is not available when we have FUS running. */ + bool total_stack_match = stack_version_match && + (c2_state->StackType == radio_ver->version.type); + if(total_stack_match) { + /* Nothing to do. */ + FURI_LOG_W(TAG, "Stack version is up2date"); + furi_hal_rtc_reset_flag(FuriHalRtcFlagC2Update); + success = true; + break; + } else { + /* Version or type mismatch. Let's boot to FUS and start updating. */ + FURI_LOG_W(TAG, "Restarting to FUS"); + furi_hal_rtc_set_flag(FuriHalRtcFlagC2Update); + CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeFUS)); + /* ...system will restart here. */ + update_task_wait_for_restart(update_task); + } + } else if(c2_state->mode == BleGlueC2ModeFUS) { + /* OK, we're in FUS mode. */ + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); + FURI_LOG_W(TAG, "Waiting for FUS to settle"); + ble_glue_fus_wait_operation(); + if(stack_version_match) { + /* We can't check StackType with FUS, but partial version matches */ + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagC2Update)) { + /* This flag was set when full version was checked. + * And something in versions of the stack didn't match. + * So, clear the flag and drop the stack. */ + furi_hal_rtc_reset_flag(FuriHalRtcFlagC2Update); + FURI_LOG_W(TAG, "Forcing stack removal (match)"); + CHECK_RESULT(update_task_remove_stack(update_task)); + } else { + /* We might just had the stack installed. + * Let's start it up to check its version */ + FURI_LOG_W(TAG, "Starting stack to check full version"); + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 40); + CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)); + /* ...system will restart here. */ + update_task_wait_for_restart(update_task); + } + } else { + if(stack_missing) { + /* Install stack. */ + CHECK_RESULT(update_task_write_stack(update_task)); + } else { + CHECK_RESULT(update_task_remove_stack(update_task)); + } + } + } + } while(false); + + return success; +} + +bool update_task_validate_optionbytes(UpdateTask* update_task) { + update_task_set_progress(update_task, UpdateTaskStageOBValidation, 0); + + bool match = true; + bool ob_dirty = false; + const UpdateManifest* manifest = update_task->manifest; + const FuriHalFlashRawOptionByteData* device_data = furi_hal_flash_ob_get_raw_ptr(); + for(size_t idx = 0; idx < FURI_HAL_FLASH_OB_TOTAL_VALUES; ++idx) { + update_task_set_progress( + update_task, UpdateTaskStageProgress, idx * 100 / FURI_HAL_FLASH_OB_TOTAL_VALUES); + const uint32_t ref_value = manifest->ob_reference.obs[idx].values.base; + const uint32_t device_ob_value = device_data->obs[idx].values.base; + const uint32_t device_ob_value_masked = device_ob_value & + manifest->ob_compare_mask.obs[idx].values.base; + if(ref_value != device_ob_value_masked) { + match = false; + FURI_LOG_E( + TAG, + "OB MISMATCH: #%d: real %08X != %08X (exp.), full %08X", + idx, + device_ob_value_masked, + ref_value, + device_ob_value); + + /* any bits we are allowed to write?.. */ + bool can_patch = ((device_ob_value_masked ^ ref_value) & + manifest->ob_write_mask.obs[idx].values.base) != 0; + + if(can_patch) { + /* patch & restart loop */ + const uint32_t patched_value = + /* take all non-writable bits from real value */ + (device_ob_value & ~(manifest->ob_write_mask.obs[idx].values.base)) | + /* take all writable bits from reference value */ + (manifest->ob_reference.obs[idx].values.base & + manifest->ob_write_mask.obs[idx].values.base); + + FURI_LOG_W(TAG, "Fixing up OB byte #%d to %08X", idx, patched_value); + ob_dirty = true; + + bool is_fixed = furi_hal_flash_ob_set_word(idx, patched_value) && + ((device_data->obs[idx].values.base & + manifest->ob_compare_mask.obs[idx].values.base) == ref_value); + + if(!is_fixed) { + /* Things are so bad that fixing what we are allowed to still doesn't match + * reference value + */ + FURI_LOG_W( + TAG, + "OB #%d is FUBAR (fixed&masked %08X, not %08X)", + idx, + patched_value, + ref_value); + } + } + } else { + FURI_LOG_I( + TAG, + "OB MATCH: #%d: real %08X == %08X (exp.)", + idx, + device_ob_value_masked, + ref_value); + } + } + if(!match) { + update_task_set_progress(update_task, UpdateTaskStageOBError, 95); + } + + if(ob_dirty) { + FURI_LOG_W(TAG, "OB were changed, applying"); + furi_hal_flash_ob_apply(); + } + return match; +} + +int32_t update_task_worker_flash_writer(void* context) { + furi_assert(context); + UpdateTask* update_task = context; + bool success = false; + + update_task->state.current_stage_idx = 0; + update_task->state.total_stages = 0; + + do { + CHECK_RESULT(update_task_parse_manifest(update_task)); + + if(!string_empty_p(update_task->manifest->radio_image)) { + CHECK_RESULT(update_task_manage_radiostack(update_task)); + } + + bool check_ob = update_manifest_has_obdata(update_task->manifest); + if(check_ob) { + update_task->state.total_stages++; + CHECK_RESULT(update_task_validate_optionbytes(update_task)); + } + + if(!string_empty_p(update_task->manifest->firmware_dfu_image)) { + update_task->state.total_stages += 4; + CHECK_RESULT(update_task_write_dfu(update_task)); + } + + furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); + + update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); + success = true; + } while(false); + + return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; +} \ No newline at end of file diff --git a/assets/.gitignore b/assets/.gitignore index 0a0acede..eb20456f 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1 +1,2 @@ /headers +/core2_firmware diff --git a/assets/Makefile b/assets/Makefile index 19d148fd..2c743601 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -1,6 +1,7 @@ PROJECT_ROOT = $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) include $(PROJECT_ROOT)/assets/assets.mk +include $(PROJECT_ROOT)/assets/copro.mk .PHONY: all all: icons protobuf dolphin manifest @@ -35,7 +36,13 @@ manifest: .PHONY: dolphin dolphin: $(DOLPHIN_EXTERNAL_OUTPUT_DIR) +.PHONY: copro_bundle +copro_bundle: + @mkdir -p $(COPRO_BUNDLE_DIR) + @$(ASSETS_COMPILER) copro $(COPRO_CUBE_DIR) $(COPRO_BUNDLE_DIR) $(COPRO_MCU_FAMILY) --cube_ver=$(COPRO_CUBE_VERSION) --stack_type=$(COPRO_STACK_TYPE) --stack_file=$(COPRO_STACK_BIN) --stack_addr=$(COPRO_STACK_ADDR) + clean: @echo "\tCLEAN\t" @$(RM) $(ASSETS_COMPILED_DIR)/* + @$(RM) -rf $(COPRO_BUNDLE_DIR) @$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR) diff --git a/assets/copro.mk b/assets/copro.mk new file mode 100644 index 00000000..004bd8d9 --- /dev/null +++ b/assets/copro.mk @@ -0,0 +1,12 @@ +COPRO_CUBE_VERSION := 1.13.3 +COPRO_MCU_FAMILY := STM32WB5x +COPRO_STACK_BIN := stm32wb5x_BLE_Stack_light_fw.bin +# See __STACK_TYPE_CODES in scripts/flipper/assets/coprobin.py +COPRO_STACK_TYPE := ble_light +# Keep 0 for auto, or put a value from release_notes for chosen stack +COPRO_STACK_ADDR := 0 + +COPRO_BUNDLE_DIR := $(ASSETS_DIR)/core2_firmware +COPRO_CUBE_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB +COPRO_FIRMWARE_DIR := $(COPRO_CUBE_DIR)/Projects/STM32WB_Copro_Wireless_Binaries/$(COPRO_MCU_FAMILY) +COPRO_STACK_BIN_PATH := $(COPRO_FIRMWARE_DIR)/$(COPRO_STACK_BIN) diff --git a/firmware/targets/f7/Src/update.c b/firmware/targets/f7/Src/update.c index 3e94fbec..4b3ee3ad 100644 --- a/firmware/targets/f7/Src/update.c +++ b/firmware/targets/f7/Src/update.c @@ -7,7 +7,8 @@ #include #include -#include +#include +#include static FATFS* pfs = NULL; @@ -27,7 +28,6 @@ static bool flipper_update_init() { furi_hal_delay_init(); furi_hal_spi_init(); - furi_hal_crc_init(false); MX_FATFS_Init(); if(!hal_sd_detect()) { @@ -62,17 +62,15 @@ static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* m uint32_t bytes_read = 0; const uint16_t MAX_READ = 0xFFFF; - furi_hal_crc_reset(); uint32_t crc = 0; do { uint16_t size_read = 0; if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { break; } - crc = furi_hal_crc_feed(img + bytes_read, size_read); + crc = crc32_calc_buffer(crc, img + bytes_read, size_read); bytes_read += size_read; } while(bytes_read == MAX_READ); - furi_hal_crc_reset(); do { if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 9142a261..64643dea 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -6,7 +6,9 @@ #include "shci.h" #include "shci_tl.h" #include "app_debug.h" + #include +#include #define TAG "Core2" @@ -27,22 +29,13 @@ PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; -typedef enum { - // Stage 1: core2 startup and FUS - BleGlueStatusStartup, - BleGlueStatusBroken, - BleGlueStatusFusStarted, - // Stage 2: radio stack - BleGlueStatusRadioStackStarted, - BleGlueStatusRadioStackMissing -} BleGlueStatus; - typedef struct { osMutexId_t shci_mtx; osSemaphoreId_t shci_sem; FuriThread* thread; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; + BleGlueC2Info c2_info; void* context; } BleGlue; @@ -111,33 +104,96 @@ void ble_glue_init() { */ } -bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { - bool ret = false; +const BleGlueC2Info* ble_glue_get_c2_info() { + return &ble_glue->c2_info; +} - size_t countdown = 1000; - while(countdown > 0) { - if(ble_glue->status == BleGlueStatusFusStarted) { - ret = true; - break; +BleGlueStatus ble_glue_get_c2_status() { + return ble_glue->status; +} + +static void ble_glue_update_c2_fw_info() { + WirelessFwInfo_t wireless_info; + SHCI_GetWirelessFwInfo(&wireless_info); + BleGlueC2Info* local_info = &ble_glue->c2_info; + + local_info->VersionMajor = wireless_info.VersionMajor; + local_info->VersionMinor = wireless_info.VersionMinor; + local_info->VersionMajor = wireless_info.VersionMajor; + local_info->VersionMinor = wireless_info.VersionMinor; + local_info->VersionSub = wireless_info.VersionSub; + local_info->VersionBranch = wireless_info.VersionBranch; + local_info->VersionReleaseType = wireless_info.VersionReleaseType; + + local_info->MemorySizeSram2B = wireless_info.MemorySizeSram2B; + local_info->MemorySizeSram2A = wireless_info.MemorySizeSram2A; + local_info->MemorySizeSram1 = wireless_info.MemorySizeSram1; + local_info->MemorySizeFlash = wireless_info.MemorySizeFlash; + + local_info->StackType = wireless_info.StackType; + + local_info->FusVersionMajor = wireless_info.FusVersionMajor; + local_info->FusVersionMinor = wireless_info.FusVersionMinor; + local_info->FusVersionSub = wireless_info.FusVersionSub; + local_info->FusMemorySizeSram2B = wireless_info.FusMemorySizeSram2B; + local_info->FusMemorySizeSram2A = wireless_info.FusMemorySizeSram2A; + local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash; +} + +static void ble_glue_dump_stack_info() { + const BleGlueC2Info* c2_info = &ble_glue->c2_info; + FURI_LOG_I( + TAG, + "Core2: FUS: %d.%d.%d, mem %d/%d, flash %d pages", + c2_info->FusVersionMajor, + c2_info->FusVersionMinor, + c2_info->FusVersionSub, + c2_info->FusMemorySizeSram2B, + c2_info->FusMemorySizeSram2A, + c2_info->FusMemorySizeFlash); + FURI_LOG_I( + TAG, + "Core2: Stack: %d.%d.%d, branch %d, reltype %d, stacktype %d, flash %d pages", + c2_info->VersionMajor, + c2_info->VersionMinor, + c2_info->VersionSub, + c2_info->VersionBranch, + c2_info->VersionReleaseType, + c2_info->StackType, + c2_info->MemorySizeFlash); +} + +bool ble_glue_wait_for_c2_start(int32_t timeout) { + bool started = false; + + do { + // TODO: use mutex? + started = ble_glue->status == BleGlueStatusC2Started; + if(!started) { + timeout--; + osDelay(1); } - countdown--; - osDelay(1); - } + } while(!started && (timeout > 0)); - if(ble_glue->status == BleGlueStatusFusStarted) { - SHCI_GetWirelessFwInfo(info); + if(started) { + FURI_LOG_I( + TAG, + "C2 boot completed, mode: %s", + ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); + ble_glue_update_c2_fw_info(); + ble_glue_dump_stack_info(); } else { - FURI_LOG_E(TAG, "Failed to start FUS"); + FURI_LOG_E(TAG, "C2 startup failed"); ble_glue->status = BleGlueStatusBroken; } - return ret; + return started; } bool ble_glue_start() { furi_assert(ble_glue); - if(ble_glue->status != BleGlueStatusFusStarted) { + if(ble_glue->status != BleGlueStatusC2Started) { return false; } @@ -145,7 +201,7 @@ bool ble_glue_start() { furi_hal_power_insomnia_enter(); if(ble_app_init()) { FURI_LOG_I(TAG, "Radio stack started"); - ble_glue->status = BleGlueStatusRadioStackStarted; + ble_glue->status = BleGlueStatusRadioStackRunning; ret = true; if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); @@ -167,7 +223,7 @@ bool ble_glue_is_alive() { return false; } - return ble_glue->status >= BleGlueStatusFusStarted; + return ble_glue->status >= BleGlueStatusC2Started; } bool ble_glue_is_radio_stack_ready() { @@ -175,26 +231,42 @@ bool ble_glue_is_radio_stack_ready() { return false; } - return ble_glue->status == BleGlueStatusRadioStackStarted; + return ble_glue->status == BleGlueStatusRadioStackRunning; } -bool ble_glue_radio_stack_fw_launch_started() { - bool ret = false; - // Get FUS status - SHCI_FUS_GetState_ErrorCode_t err_code = 0; - uint8_t state = SHCI_C2_FUS_GetState(&err_code); - if(state == FUS_STATE_VALUE_IDLE) { - // When FUS is running we can't read radio stack version correctly - // Trying to start radio stack fw, which leads to reset - FURI_LOG_W(TAG, "FUS is running. Restart to launch Radio Stack"); +BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { + furi_check(desired_mode > BleGlueC2ModeUnknown); + + if(desired_mode == ble_glue->c2_info.mode) { + return BleGlueCommandResultOK; + } + + if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) { + if((ble_glue->c2_info.VersionMajor == 0) && (ble_glue->c2_info.VersionMinor == 0)) { + FURI_LOG_W(TAG, "Stack isn't installed!"); + return BleGlueCommandResultError; + } SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs(); if(status) { FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status); - } else { - ret = true; + return BleGlueCommandResultError; } + return BleGlueCommandResultRestartPending; } - return ret; + if((ble_glue->c2_info.mode == BleGlueC2ModeStack) && (desired_mode == BleGlueC2ModeFUS)) { + SHCI_FUS_GetState_ErrorCode_t error_code = 0; + uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); + FURI_LOG_D(TAG, "FUS state: %X, error = %x", fus_state, error_code); + if(fus_state == SHCI_FUS_CMD_NOT_SUPPORTED) { + // Second call to SHCI_C2_FUS_GetState() restarts whole MCU & boots FUS + fus_state = SHCI_C2_FUS_GetState(&error_code); + FURI_LOG_D(TAG, "FUS state#2: %X, error = %x", fus_state, error_code); + return BleGlueCommandResultRestartPending; + } + return BleGlueCommandResultOK; + } + + return BleGlueCommandResultError; } static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { @@ -228,8 +300,15 @@ static void ble_glue_sys_user_event_callback(void* pPayload) { (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { - FURI_LOG_I(TAG, "Fus started"); - ble_glue->status = BleGlueStatusFusStarted; + FURI_LOG_I(TAG, "Core2 started"); + SHCI_C2_Ready_Evt_t* p_c2_ready_evt = (SHCI_C2_Ready_Evt_t*)p_sys_event->payload; + if(p_c2_ready_evt->sysevt_ready_rsp == WIRELESS_FW_RUNNING) { + ble_glue->c2_info.mode = BleGlueC2ModeStack; + } else if(p_c2_ready_evt->sysevt_ready_rsp == FUS_FW_RUNNING) { + ble_glue->c2_info.mode = BleGlueC2ModeFUS; + } + + ble_glue->status = BleGlueStatusC2Started; furi_hal_power_insomnia_exit(); } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { FURI_LOG_E(TAG, "Error during initialization"); @@ -308,3 +387,61 @@ void shci_cmd_resp_wait(uint32_t timeout) { osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); } } + +bool ble_glue_reinit_c2() { + return SHCI_C2_Reinit() == SHCI_Success; +} + +BleGlueCommandResult ble_glue_fus_stack_delete() { + FURI_LOG_I(TAG, "Erasing stack"); + SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete(); + FURI_LOG_I(TAG, "Cmd res = %x", erase_stat); + if(erase_stat == SHCI_Success) { + return BleGlueCommandResultOperationOngoing; + } + ble_glue_fus_get_status(); + return BleGlueCommandResultError; +} + +BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr) { + FURI_LOG_I(TAG, "Installing stack"); + SHCI_CmdStatus_t write_stat = SHCI_C2_FUS_FwUpgrade(src_addr, dst_addr); + FURI_LOG_I(TAG, "Cmd res = %x", write_stat); + if(write_stat == SHCI_Success) { + return BleGlueCommandResultOperationOngoing; + } + ble_glue_fus_get_status(); + return BleGlueCommandResultError; +} + +BleGlueCommandResult ble_glue_fus_get_status() { + furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); + SHCI_FUS_GetState_ErrorCode_t error_code = 0; + uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); + FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code); + if((error_code != 0) || (fus_state == FUS_STATE_VALUE_ERROR)) { + return BleGlueCommandResultError; + } else if( + (fus_state >= FUS_STATE_VALUE_FW_UPGRD_ONGOING) && + (fus_state <= FUS_STATE_VALUE_SERVICE_ONGOING_END)) { + return BleGlueCommandResultOperationOngoing; + } + return BleGlueCommandResultOK; +} + +BleGlueCommandResult ble_glue_fus_wait_operation() { + furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); + bool wip; + do { + BleGlueCommandResult fus_status = ble_glue_fus_get_status(); + if(fus_status == BleGlueCommandResultError) { + return BleGlueCommandResultError; + } + wip = fus_status == BleGlueCommandResultOperationOngoing; + if(wip) { + osDelay(20); + } + } while(wip); + + return BleGlueCommandResultOK; +} \ No newline at end of file diff --git a/firmware/targets/f7/ble_glue/ble_glue.h b/firmware/targets/f7/ble_glue/ble_glue.h index 646caa8a..e5c2738d 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.h +++ b/firmware/targets/f7/ble_glue/ble_glue.h @@ -2,12 +2,53 @@ #include #include -#include #ifdef __cplusplus extern "C" { #endif +typedef enum { + BleGlueC2ModeUnknown = 0, + BleGlueC2ModeFUS, + BleGlueC2ModeStack, +} BleGlueC2Mode; + +typedef struct { + BleGlueC2Mode mode; + /** + * Wireless Info + */ + uint8_t VersionMajor; + uint8_t VersionMinor; + uint8_t VersionSub; + uint8_t VersionBranch; + uint8_t VersionReleaseType; + uint8_t MemorySizeSram2B; /*< Multiple of 1K */ + uint8_t MemorySizeSram2A; /*< Multiple of 1K */ + uint8_t MemorySizeSram1; /*< Multiple of 1K */ + uint8_t MemorySizeFlash; /*< Multiple of 4K */ + uint8_t StackType; + /** + * Fus Info + */ + uint8_t FusVersionMajor; + uint8_t FusVersionMinor; + uint8_t FusVersionSub; + uint8_t FusMemorySizeSram2B; /*< Multiple of 1K */ + uint8_t FusMemorySizeSram2A; /*< Multiple of 1K */ + uint8_t FusMemorySizeFlash; /*< Multiple of 4K */ +} BleGlueC2Info; + +typedef enum { + // Stage 1: core2 startup and FUS + BleGlueStatusStartup, + BleGlueStatusBroken, + BleGlueStatusC2Started, + // Stage 2: radio stack + BleGlueStatusRadioStackRunning, + BleGlueStatusRadioStackMissing +} BleGlueStatus; + typedef void ( *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); @@ -26,7 +67,15 @@ bool ble_glue_start(); */ bool ble_glue_is_alive(); -bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); +/** Waits for C2 to reports its mode to callback + * + * @return true if it reported before reaching timeout + */ +bool ble_glue_wait_for_c2_start(int32_t timeout); + +BleGlueStatus ble_glue_get_c2_status(); + +const BleGlueC2Info* ble_glue_get_c2_info(); /** Is core2 radio stack present and ready * @@ -46,12 +95,30 @@ void ble_glue_set_key_storage_changed_callback( /** Stop SHCI thread */ void ble_glue_thread_stop(); +bool ble_glue_reinit_c2(); + +typedef enum { + BleGlueCommandResultUnknown, + BleGlueCommandResultOK, + BleGlueCommandResultError, + BleGlueCommandResultRestartPending, + BleGlueCommandResultOperationOngoing, +} BleGlueCommandResult; + /** Restart MCU to launch radio stack firmware if necessary * * @return true on radio stack start command */ -bool ble_glue_radio_stack_fw_launch_started(); +BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode); + +BleGlueCommandResult ble_glue_fus_stack_delete(); + +BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr); + +BleGlueCommandResult ble_glue_fus_get_status(); + +BleGlueCommandResult ble_glue_fus_wait_operation(); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index b3362ba8..03031a93 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -55,7 +55,6 @@ void furi_hal_init() { FURI_LOG_I(TAG, "Speaker OK"); furi_hal_crypto_init(); - furi_hal_crc_init(true); // USB #ifndef FURI_RAM_EXEC diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index a8b85495..83d25ec9 100755 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -16,6 +16,9 @@ #define FURI_HAL_BT_DEFAULT_MAC_ADDR \ { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } +/* Time, in ms, to wait for mode transition before crashing */ +#define C2_MODE_SWITCH_TIMEOUT 10000 + osMutexId_t furi_hal_bt_core2_mtx = NULL; static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; @@ -99,7 +102,7 @@ void furi_hal_bt_unlock_core2() { furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); } -static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) { +static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { bool supported = false; if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { furi_hal_bt_stack = FuriHalBtStackHciLayer; @@ -128,21 +131,22 @@ bool furi_hal_bt_start_radio_stack() { } do { - // Wait until FUS is started or timeout - WirelessFwInfo_t info = {}; - if(!ble_glue_wait_for_fus_start(&info)) { - FURI_LOG_E(TAG, "FUS start failed"); + // Wait until C2 is started or timeout + if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { + FURI_LOG_E(TAG, "Core2 start failed"); LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); ble_glue_thread_stop(); break; } - // If FUS is running, start radio stack fw - if(ble_glue_radio_stack_fw_launch_started()) { - // If FUS is running do nothing and wait for system reset - furi_crash("Waiting for FUS to launch radio stack firmware"); + + // If C2 is running, start radio stack fw + if(!furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)) { + break; } - // Check weather we support radio stack - if(!furi_hal_bt_radio_stack_is_supported(&info)) { + + // Check whether we support radio stack + const BleGlueC2Info* c2_info = ble_glue_get_c2_info(); + if(!furi_hal_bt_radio_stack_is_supported(c2_info)) { FURI_LOG_E(TAG, "Unsupported radio stack"); // Don't stop SHCI for crypto enclave support break; @@ -232,7 +236,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, ble_app_thread_stop(); gap_thread_stop(); FURI_LOG_I(TAG, "Reset SHCI"); - SHCI_C2_Reinit(); + ble_glue_reinit_c2(); osDelay(100); ble_glue_thread_stop(); FURI_LOG_I(TAG, "Start BT initialization"); @@ -404,3 +408,18 @@ void furi_hal_bt_stop_scan() { gap_stop_scan(); } } + +bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { + BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode); + if(fw_start_res == BleGlueCommandResultOK) { + return true; + } else if(fw_start_res == BleGlueCommandResultRestartPending) { + // Do nothing and wait for system reset + osDelay(C2_MODE_SWITCH_TIMEOUT); + furi_crash("Waiting for FUS->radio stack transition"); + return true; + } + + FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res); + return false; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_crc.c b/firmware/targets/f7/furi_hal/furi_hal_crc.c index 321809d9..322a83bb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crc.c @@ -34,6 +34,7 @@ void furi_hal_crc_init(bool synchronize) { void furi_hal_crc_reset() { furi_check(hal_crc_control.state == CRC_State_Ready); if(hal_crc_control.mtx) { + furi_check(osMutexGetOwner(hal_crc_control.mtx) == osThreadGetId()); osMutexRelease(hal_crc_control.mtx); } LL_CRC_ResetCRCCalculationUnit(CRC); @@ -84,5 +85,9 @@ uint32_t furi_hal_crc_feed(void* data, uint16_t length) { bool furi_hal_crc_acquire(uint32_t timeout) { furi_assert(hal_crc_control.mtx); - return osMutexAcquire(hal_crc_control.mtx, timeout) == osOK; + if(osMutexAcquire(hal_crc_control.mtx, timeout) == osOK) { + LL_CRC_ResetCRCCalculationUnit(CRC); + return true; + } + return false; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index aaf7f1a2..1d9c3fd9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -6,7 +6,8 @@ #include -#define FURI_HAL_TAG "FuriHalFlash" +#define TAG "FuriHalFlash" + #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" #define FURI_HAL_FLASH_READ_BLOCK 8 #define FURI_HAL_FLASH_WRITE_BLOCK 8 @@ -14,13 +15,17 @@ #define FURI_HAL_FLASH_CYCLES_COUNT 10000 #define FURI_HAL_FLASH_TIMEOUT 1000 #define FURI_HAL_FLASH_KEY1 0x45670123U - #define FURI_HAL_FLASH_KEY2 0xCDEF89ABU #define FURI_HAL_FLASH_TOTAL_PAGES 256 #define FURI_HAL_FLASH_SR_ERRORS \ (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR) +//#define FURI_HAL_FLASH_OB_START_ADDRESS 0x1FFF8000 +#define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B +#define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F +#define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) + #define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \ (((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \ @@ -88,7 +93,7 @@ static void furi_hal_flash_unlock() { WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1); WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2); - /* verify Flash is unlock */ + /* verify Flash is unlocked */ furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); } @@ -386,4 +391,125 @@ int16_t furi_hal_flash_get_page_number(size_t address) { } return (address - flash_base) / FURI_HAL_FLASH_PAGE_SIZE; -} \ No newline at end of file +} + +uint32_t furi_hal_flash_ob_get_word(size_t word_idx, bool complementary) { + furi_check(word_idx <= FURI_HAL_FLASH_OB_TOTAL_WORDS); + const uint32_t* ob_data = (const uint32_t*)(OPTION_BYTE_BASE); + size_t raw_word_idx = word_idx * 2; + if(complementary) { + raw_word_idx += 1; + } + return ob_data[raw_word_idx]; +} + +void furi_hal_flash_ob_unlock() { + furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) != 0U); + furi_hal_flash_begin(true); + WRITE_REG(FLASH->OPTKEYR, FURI_HAL_FLASH_OPT_KEY1); + __ISB(); + WRITE_REG(FLASH->OPTKEYR, FURI_HAL_FLASH_OPT_KEY2); + /* verify OB area is unlocked */ + furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) == 0U); +} + +void furi_hal_flash_ob_lock() { + furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) == 0U); + SET_BIT(FLASH->CR, FLASH_CR_OPTLOCK); + furi_hal_flash_end(true); + furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) != 0U); +} + +typedef enum { + FuriHalFlashObInvalid, + FuriHalFlashObRegisterUserRead, + FuriHalFlashObRegisterPCROP1AStart, + FuriHalFlashObRegisterPCROP1AEnd, + FuriHalFlashObRegisterWRPA, + FuriHalFlashObRegisterWRPB, + FuriHalFlashObRegisterPCROP1BStart, + FuriHalFlashObRegisterPCROP1BEnd, + FuriHalFlashObRegisterIPCCMail, + FuriHalFlashObRegisterSecureFlash, + FuriHalFlashObRegisterC2Opts, +} FuriHalFlashObRegister; + +typedef struct { + FuriHalFlashObRegister ob_reg; + uint32_t* ob_register_address; +} FuriHalFlashObMapping; + +#define OB_REG_DEF(INDEX, REG) \ + { .ob_reg = INDEX, .ob_register_address = (uint32_t*)(REG) } + +static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_TOTAL_WORDS] = { + OB_REG_DEF(FuriHalFlashObRegisterUserRead, (&FLASH->OPTR)), + OB_REG_DEF(FuriHalFlashObRegisterPCROP1AStart, (&FLASH->PCROP1ASR)), + OB_REG_DEF(FuriHalFlashObRegisterPCROP1AEnd, (&FLASH->PCROP1AER)), + OB_REG_DEF(FuriHalFlashObRegisterWRPA, (&FLASH->WRP1AR)), + OB_REG_DEF(FuriHalFlashObRegisterWRPB, (&FLASH->WRP1BR)), + OB_REG_DEF(FuriHalFlashObRegisterPCROP1BStart, (&FLASH->PCROP1BSR)), + OB_REG_DEF(FuriHalFlashObRegisterPCROP1BEnd, (&FLASH->PCROP1BER)), + + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), + + OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (NULL)), + OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)), + OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)), +}; + +void furi_hal_flash_ob_apply() { + furi_hal_flash_ob_unlock(); + /* OBL_LAUNCH: When set to 1, this bit forces the option byte reloading. + * It cannot be written if OPTLOCK is set */ + SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH); + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + furi_hal_flash_ob_lock(); +} + +bool furi_hal_flash_ob_set_word(size_t word_idx, const uint32_t value) { + furi_check(word_idx < FURI_HAL_FLASH_OB_TOTAL_WORDS); + + const FuriHalFlashObMapping* reg_def = &furi_hal_flash_ob_reg_map[word_idx]; + if(reg_def->ob_register_address == NULL) { + FURI_LOG_E(TAG, "Attempt to set RO OB word %d", word_idx); + return false; + } + + FURI_LOG_W( + TAG, + "Setting OB reg %d for word %d (addr 0x%08X) to 0x%08X", + reg_def->ob_reg, + word_idx, + reg_def->ob_register_address, + value); + + /* 1. Clear OPTLOCK option lock bit with the clearing sequence */ + furi_hal_flash_ob_unlock(); + + /* 2. Write the desired options value in the options registers */ + *reg_def->ob_register_address = value; + + /* 3. Check that no Flash memory operation is on going by checking the BSY && PESD */ + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + while(LL_FLASH_IsActiveFlag_OperationSuspended()) { + osThreadYield(); + }; + + /* 4. Set the Options start bit OPTSTRT */ + SET_BIT(FLASH->CR, FLASH_CR_OPTSTRT); + + /* 5. Wait for the BSY bit to be cleared. */ + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + furi_hal_flash_ob_lock(); + return true; +} + +const FuriHalFlashRawOptionByteData* furi_hal_flash_ob_get_raw_ptr() { + return (const FuriHalFlashRawOptionByteData*)OPTION_BYTE_BASE; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/firmware/targets/f7/furi_hal/furi_hal_flash.h index 004c1340..eac80d95 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.h +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.h @@ -4,6 +4,25 @@ #include #include +#define FURI_HAL_FLASH_OB_RAW_SIZE_BYTES 0x80 +#define FURI_HAL_FLASH_OB_SIZE_WORDS (FURI_HAL_FLASH_OB_RAW_SIZE_BYTES / sizeof(uint32_t)) +#define FURI_HAL_FLASH_OB_TOTAL_VALUES (FURI_HAL_FLASH_OB_SIZE_WORDS / 2) + +typedef union { + uint8_t bytes[FURI_HAL_FLASH_OB_RAW_SIZE_BYTES]; + union { + struct { + uint32_t base; + uint32_t complementary_value; + } values; + uint64_t dword; + } obs[FURI_HAL_FLASH_OB_TOTAL_VALUES]; +} FuriHalFlashRawOptionByteData; + +_Static_assert( + sizeof(FuriHalFlashRawOptionByteData) == FURI_HAL_FLASH_OB_RAW_SIZE_BYTES, + "UpdateManifestOptionByteData size error"); + /** Init flash, applying necessary workarounds */ void furi_hal_flash_init(); @@ -64,7 +83,7 @@ size_t furi_hal_flash_get_free_page_count(); /** Erase Flash * - * @warning locking operation with critical section, stales execution + * @warning locking operation with critical section, stalls execution * * @param page The page to erase * @@ -74,7 +93,7 @@ bool furi_hal_flash_erase(uint8_t page); /** Write double word (64 bits) * - * @warning locking operation with critical section, stales execution + * @warning locking operation with critical section, stalls execution * * @param address destination address, must be double word aligned. * @param data data to write @@ -85,7 +104,7 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data); /** Write aligned page data (up to page size) * - * @warning locking operation with critical section, stales execution + * @warning locking operation with critical section, stalls execution * * @param address destination address, must be page aligned. * @param data data to write @@ -99,5 +118,27 @@ bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 * * @return page number, -1 for invalid address */ +int16_t furi_hal_flash_get_page_number(size_t address); -int16_t furi_hal_flash_get_page_number(size_t address); \ No newline at end of file +/** Writes OB word, using non-compl. index of register in Flash, OPTION_BYTE_BASE + * + * @warning locking operation with critical section, stalls execution + * + * @param word_idx OB word number + * @param value data to write + * @return true if value was written, false for read-only word + */ +bool furi_hal_flash_ob_set_word(size_t word_idx, const uint32_t value); + +/** Forces a reload of OB data from flash to registers + * + * @warning Initializes system restart + * + */ +void furi_hal_flash_ob_apply(); + +/** Get raw OB storage data + * + * @return pointer to read-only data of OB (raw + complementary values) + */ +const FuriHalFlashRawOptionByteData* furi_hal_flash_ob_get_raw_ptr(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 77eab128..bbb3907d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -66,44 +66,45 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { out("firmware_target", string_get_cstr(value), false, context); } - WirelessFwInfo_t pWirelessInfo; - if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) { + if(furi_hal_bt_is_alive()) { + const BleGlueC2Info* ble_c2_info = ble_glue_get_c2_info(); out("radio_alive", "true", false, context); + out("radio_mode", ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack", false, context); // FUS Info - string_printf(value, "%d", pWirelessInfo.FusVersionMajor); + string_printf(value, "%d", ble_c2_info->FusVersionMajor); out("radio_fus_major", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.FusVersionMinor); + string_printf(value, "%d", ble_c2_info->FusVersionMinor); out("radio_fus_minor", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.FusVersionSub); + string_printf(value, "%d", ble_c2_info->FusVersionSub); out("radio_fus_sub", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.FusMemorySizeSram2B); + string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2B); out("radio_fus_sram2b", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.FusMemorySizeSram2A); + string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2A); out("radio_fus_sram2a", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.FusMemorySizeFlash * 4); + string_printf(value, "%dK", ble_c2_info->FusMemorySizeFlash * 4); out("radio_fus_flash", string_get_cstr(value), false, context); // Stack Info - string_printf(value, "%d", pWirelessInfo.StackType); + string_printf(value, "%d", ble_c2_info->StackType); out("radio_stack_type", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.VersionMajor); + string_printf(value, "%d", ble_c2_info->VersionMajor); out("radio_stack_major", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.VersionMinor); + string_printf(value, "%d", ble_c2_info->VersionMinor); out("radio_stack_minor", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.VersionSub); + string_printf(value, "%d", ble_c2_info->VersionSub); out("radio_stack_sub", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.VersionBranch); + string_printf(value, "%d", ble_c2_info->VersionBranch); out("radio_stack_branch", string_get_cstr(value), false, context); - string_printf(value, "%d", pWirelessInfo.VersionReleaseType); + string_printf(value, "%d", ble_c2_info->VersionReleaseType); out("radio_stack_release", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.MemorySizeSram2B); + string_printf(value, "%dK", ble_c2_info->MemorySizeSram2B); out("radio_stack_sram2b", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.MemorySizeSram2A); + string_printf(value, "%dK", ble_c2_info->MemorySizeSram2A); out("radio_stack_sram2a", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.MemorySizeSram1); + string_printf(value, "%dK", ble_c2_info->MemorySizeSram1); out("radio_stack_sram1", string_get_cstr(value), false, context); - string_printf(value, "%dK", pWirelessInfo.MemorySizeFlash * 4); + string_printf(value, "%dK", ble_c2_info->MemorySizeFlash * 4); out("radio_stack_flash", string_get_cstr(value), false, context); // Mac address diff --git a/firmware/targets/f7/furi_hal/furi_hal_vcp.c b/firmware/targets/f7/furi_hal/furi_hal_vcp.c index a4694625..ac8ee730 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_vcp.c +++ b/firmware/targets/f7/furi_hal/furi_hal_vcp.c @@ -64,7 +64,7 @@ void furi_hal_vcp_init() { vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { - FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode="); + FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode"); return; } diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 76e96ef6..a1e4a90f 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -39,7 +39,6 @@ template struct STOP_EXTERNING_ME {}; #include "furi_hal_uart.h" #include "furi_hal_info.h" #include "furi_hal_random.h" -#include "furi_hal_crc.h" #ifdef __cplusplus extern "C" { diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 912132d2..74e8d321 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -16,6 +16,7 @@ #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) #define FURI_HAL_BT_STACK_VERSION_MINOR (13) +#define FURI_HAL_BT_C2_START_TIMEOUT 1000 #ifdef __cplusplus extern "C" { @@ -207,6 +208,12 @@ bool furi_hal_bt_start_scan(GapScanCallback callback, void* context); /** Stop MAC addresses scan */ void furi_hal_bt_stop_scan(); +/** Check & switch C2 to given mode + * + * @param[in] mode mode to switch into + */ +bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 5b6a7780..21c69b24 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -28,6 +28,7 @@ typedef enum { FuriHalRtcFlagDebug = (1 << 0), FuriHalRtcFlagFactoryReset = (1 << 1), FuriHalRtcFlagLock = (1 << 2), + FuriHalRtcFlagC2Update = (1 << 3), } FuriHalRtcFlag; typedef enum { diff --git a/lib/STM32CubeWB b/lib/STM32CubeWB index 528461f8..a9e29b43 160000 --- a/lib/STM32CubeWB +++ b/lib/STM32CubeWB @@ -1 +1 @@ -Subproject commit 528461f8276f06783d46461bfb31d77aa8bac419 +Subproject commit a9e29b431f6dac95b6fc860a717834f35b7f78e5 diff --git a/lib/lib.mk b/lib/lib.mk index c1ee2cc0..1470c89f 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -97,7 +97,8 @@ CPP_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*/*.cpp) # USB Stack CFLAGS += -I$(LIB_DIR)/libusb_stm32/inc -C_SOURCES += $(wildcard $(LIB_DIR)/libusb_stm32/src/*.c) +C_SOURCES += $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c +C_SOURCES += $(LIB_DIR)/libusb_stm32/src/usbd_core.c # protobuf CFLAGS += -I$(LIB_DIR)/nanopb diff --git a/lib/toolbox/crc32_calc.c b/lib/toolbox/crc32_calc.c new file mode 100644 index 00000000..c8ae3524 --- /dev/null +++ b/lib/toolbox/crc32_calc.c @@ -0,0 +1,38 @@ +#include "crc32_calc.h" +#include + +#define CRC_DATA_BUFFER_MAX_LEN 512 + +uint32_t crc32_calc_buffer(uint32_t crc, const void* buffer, size_t size) { + // TODO: consider removing dependency on LFS + return ~lfs_crc(~crc, buffer, size); +} + +uint32_t crc32_calc_file(File* file, const FileCrcProgressCb progress_cb, void* context) { + furi_check(storage_file_is_open(file) && storage_file_seek(file, 0, true)); + + uint32_t file_crc = 0; + + uint8_t* data_buffer = malloc(CRC_DATA_BUFFER_MAX_LEN); + uint16_t data_buffer_valid_len; + + uint32_t file_size = storage_file_size(file); + + /* Feed file contents per sector into CRC calc */ + for(uint32_t fptr = 0; fptr < file_size;) { + data_buffer_valid_len = storage_file_read(file, data_buffer, CRC_DATA_BUFFER_MAX_LEN); + if(data_buffer_valid_len == 0) { + break; + } + fptr += data_buffer_valid_len; + + if(progress_cb && (fptr % CRC_DATA_BUFFER_MAX_LEN == 0)) { + progress_cb(fptr * 100 / file_size, context); + } + + file_crc = crc32_calc_buffer(file_crc, data_buffer, data_buffer_valid_len); + } + free(data_buffer); + + return file_crc; +} diff --git a/lib/toolbox/crc32_calc.h b/lib/toolbox/crc32_calc.h new file mode 100644 index 00000000..9455ade5 --- /dev/null +++ b/lib/toolbox/crc32_calc.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +uint32_t crc32_calc_buffer(uint32_t crc, const void* buffer, size_t size); + +typedef void (*FileCrcProgressCb)(const uint8_t progress, void* context); + +uint32_t crc32_calc_file(File* file, const FileCrcProgressCb progress_cb, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/dfu_file.c b/lib/update_util/dfu_file.c index 2fcf95ac..d6f31b60 100644 --- a/lib/update_util/dfu_file.c +++ b/lib/update_util/dfu_file.c @@ -1,42 +1,14 @@ #include "dfu_file.h" + #include +#include #define VALID_WHOLE_FILE_CRC 0xFFFFFFFF #define DFU_SUFFIX_VERSION 0x011A -#define DFU_DATA_BUFFER_MAX_LEN 512 #define DFU_SIGNATURE "DfuSe" bool dfu_file_validate_crc(File* dfuf, const DfuPageTaskProgressCb progress_cb, void* context) { - if(!storage_file_is_open(dfuf) || !storage_file_seek(dfuf, 0, true)) { - return false; - } - - furi_hal_crc_reset(); - - uint32_t file_crc = 0; - - uint8_t* data_buffer = malloc(DFU_DATA_BUFFER_MAX_LEN); - uint16_t data_buffer_valid_len; - - uint32_t file_size = storage_file_size(dfuf); - - /* Feed file contents per sector into CRC calc */ - furi_hal_crc_acquire(osWaitForever); - for(uint32_t fptr = 0; fptr < file_size;) { - data_buffer_valid_len = storage_file_read(dfuf, data_buffer, DFU_DATA_BUFFER_MAX_LEN); - if(data_buffer_valid_len == 0) { - break; - } - fptr += data_buffer_valid_len; - - if((fptr % DFU_DATA_BUFFER_MAX_LEN == 0)) { - progress_cb(fptr * 100 / file_size, context); - } - - file_crc = furi_hal_crc_feed(data_buffer, data_buffer_valid_len); - } - furi_hal_crc_reset(); - free(data_buffer); + uint32_t file_crc = crc32_calc_file(dfuf, progress_cb, context); /* Last 4 bytes of DFU file = CRC of previous file contents, inverted * If we calculate whole file CRC32, incl. embedded CRC, diff --git a/lib/update_util/update_manifest.c b/lib/update_util/update_manifest.c index 3d9ec9c3..3d05d0ba 100644 --- a/lib/update_util/update_manifest.c +++ b/lib/update_util/update_manifest.c @@ -14,6 +14,9 @@ #define MANIFEST_KEY_RADIO_VERSION "Radio version" #define MANIFEST_KEY_RADIO_CRC "Radio CRC" #define MANIFEST_KEY_ASSETS_FILE "Resources" +#define MANIFEST_KEY_OB_REFERENCE "OB reference" +#define MANIFEST_KEY_OB_MASK "OB mask" +#define MANIFEST_KEY_OB_WRITE_MASK "OB write mask" UpdateManifest* update_manifest_alloc() { UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); @@ -23,6 +26,9 @@ UpdateManifest* update_manifest_alloc() { string_init(update_manifest->staged_loader_file); string_init(update_manifest->resource_bundle); update_manifest->target = 0; + memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); + memset(update_manifest->ob_compare_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); + memset(update_manifest->ob_write_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); update_manifest->valid = false; return update_manifest; } @@ -75,8 +81,8 @@ static bool flipper_format_read_hex( flipper_file, MANIFEST_KEY_RADIO_VERSION, - (uint8_t*)&update_manifest->radio_version, - sizeof(uint32_t)); + update_manifest->radio_version.raw, + sizeof(UpdateManifestRadioVersion)); flipper_format_read_hex( flipper_file, MANIFEST_KEY_RADIO_CRC, @@ -85,6 +91,22 @@ static bool flipper_format_read_string( flipper_file, MANIFEST_KEY_ASSETS_FILE, update_manifest->resource_bundle); + flipper_format_read_hex( + flipper_file, + MANIFEST_KEY_OB_REFERENCE, + update_manifest->ob_reference.bytes, + FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); + flipper_format_read_hex( + flipper_file, + MANIFEST_KEY_OB_MASK, + update_manifest->ob_compare_mask.bytes, + FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); + flipper_format_read_hex( + flipper_file, + MANIFEST_KEY_OB_WRITE_MASK, + update_manifest->ob_write_mask.bytes, + FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); + update_manifest->valid = (!string_empty_p(update_manifest->firmware_dfu_image) || !string_empty_p(update_manifest->radio_image) || @@ -94,6 +116,41 @@ static bool return update_manifest->valid; } +// Verifies that mask values are same for adjacent words (value & inverted) +static bool ob_data_check_mask_valid(const FuriHalFlashRawOptionByteData* mask) { + bool mask_valid = true; + for(size_t idx = 0; mask_valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) { + mask_valid &= mask->obs[idx].values.base == mask->obs[idx].values.complementary_value; + } + return mask_valid; +} + +// Verifies that all reference values have no unmasked bits +static bool ob_data_check_masked_values_valid( + const FuriHalFlashRawOptionByteData* data, + const FuriHalFlashRawOptionByteData* mask) { + bool valid = true; + for(size_t idx = 0; valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) { + valid &= (data->obs[idx]. dword & mask->obs[idx].dword) == + data->obs[idx].dword; + } + return valid; +} + +bool update_manifest_has_obdata(UpdateManifest* update_manifest) { + bool ob_data_valid = false; + // do we have at least 1 value? + for(size_t idx = 0; !ob_data_valid && (idx < FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); ++idx) { + ob_data_valid |= update_manifest->ob_reference.bytes[idx] != 0; + } + // sanity checks + ob_data_valid &= ob_data_check_mask_valid(&update_manifest->ob_write_mask); + ob_data_valid &= ob_data_check_mask_valid(&update_manifest->ob_compare_mask); + ob_data_valid &= ob_data_check_masked_values_valid( + &update_manifest->ob_reference, &update_manifest->ob_compare_mask); + return ob_data_valid; +} + bool update_manifest_init(UpdateManifest* update_manifest, const char* manifest_filename) { Storage* storage = furi_record_open("storage"); FlipperFormat* flipper_file = flipper_format_file_alloc(storage); diff --git a/lib/update_util/update_manifest.h b/lib/update_util/update_manifest.h index 7d4757c4..d03c71d2 100644 --- a/lib/update_util/update_manifest.h +++ b/lib/update_util/update_manifest.h @@ -7,12 +7,26 @@ extern "C" { #include #include #include +#include /* Paths don't include /ext -- because at startup SD card is mounted as root */ #define UPDATE_DIR_DEFAULT_REL_PATH "/update" #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf" #define UPDATE_MAINFEST_DEFAULT_PATH UPDATE_DIR_DEFAULT_REL_PATH "/" UPDATE_MANIFEST_DEFAULT_NAME +typedef union { + uint8_t raw[6]; + struct { + uint8_t major; + uint8_t minor; + uint8_t sub; + uint8_t branch; + uint8_t release; + uint8_t type; + } version; +} UpdateManifestRadioVersion; +_Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVersion size error"); + typedef struct { string_t version; uint32_t target; @@ -21,9 +35,12 @@ typedef struct { string_t firmware_dfu_image; string_t radio_image; uint32_t radio_address; - uint32_t radio_version; + UpdateManifestRadioVersion radio_version; uint32_t radio_crc; string_t resource_bundle; + FuriHalFlashRawOptionByteData ob_reference; + FuriHalFlashRawOptionByteData ob_compare_mask; + FuriHalFlashRawOptionByteData ob_write_mask; bool valid; } UpdateManifest; @@ -38,6 +55,8 @@ bool update_manifest_init_mem( const uint8_t* manifest_data, const uint16_t length); +bool update_manifest_has_obdata(UpdateManifest* update_manifest); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 2fd510b5..beea98d2 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -7,6 +7,7 @@ #include #include #include +#include static const char* UPDATE_ROOT_DIR = "/ext" UPDATE_DIR_DEFAULT_REL_PATH; static const char* UPDATE_PREFIX = "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"; @@ -156,8 +157,6 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { path_extract_dirname(manifest_file_path, stage_path); path_append(stage_path, string_get_cstr(manifest->staged_loader_file)); - const uint16_t READ_BLOCK = 0x1000; - uint8_t* read_buffer = malloc(READ_BLOCK); uint32_t crc = 0; do { if(!storage_file_open( @@ -166,19 +165,10 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } result = UpdatePrepareResultStageIntegrityError; - furi_hal_crc_acquire(osWaitForever); - - uint16_t bytes_read = 0; - do { - bytes_read = storage_file_read(file, read_buffer, READ_BLOCK); - crc = furi_hal_crc_feed(read_buffer, bytes_read); - } while(bytes_read == READ_BLOCK); - - furi_hal_crc_reset(); + crc = crc32_calc_file(file, NULL, NULL); } while(false); string_clear(stage_path); - free(read_buffer); storage_file_free(file); if(crc == manifest->staged_loader_crc) { diff --git a/scripts/assets.py b/scripts/assets.py index bb7b493d..1363900f 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -50,6 +50,26 @@ class Main(App): self.parser_copro.add_argument("cube_dir", help="Path to Cube folder") self.parser_copro.add_argument("output_dir", help="Path to output folder") self.parser_copro.add_argument("mcu", help="MCU series as in copro folder") + self.parser_copro.add_argument( + "--cube_ver", dest="cube_ver", help="Cube version", required=True + ) + self.parser_copro.add_argument( + "--stack_type", dest="stack_type", help="Stack type", required=True + ) + self.parser_copro.add_argument( + "--stack_file", + dest="stack_file", + help="Stack file name in copro folder", + required=True, + ) + self.parser_copro.add_argument( + "--stack_addr", + dest="stack_addr", + help="Stack flash address, as per release_notes", + type=lambda x: int(x, 16), + default=0, + required=False, + ) self.parser_copro.set_defaults(func=self.copro) self.parser_dolphin = self.subparsers.add_parser( @@ -203,13 +223,15 @@ class Main(App): manifest_file = os.path.join(directory_path, "Manifest") old_manifest = Manifest() if os.path.exists(manifest_file): - self.logger.info("old manifest is present, loading for compare") + self.logger.info("Manifest is present, loading to compare") old_manifest.load(manifest_file) - self.logger.info(f'Creating new Manifest for directory "{directory_path}"') + self.logger.info( + f'Creating temporary Manifest for directory "{directory_path}"' + ) new_manifest = Manifest() new_manifest.create(directory_path) - self.logger.info(f"Comparing new manifest with old") + self.logger.info(f"Comparing new manifest with existing") only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) for record in only_in_old: self.logger.info(f"Only in old: {record}") @@ -233,9 +255,14 @@ class Main(App): self.logger.info(f"Bundling coprocessor binaries") copro = Copro(self.args.mcu) self.logger.info(f"Loading CUBE info") - copro.loadCubeInfo(self.args.cube_dir) + copro.loadCubeInfo(self.args.cube_dir, self.args.cube_ver) self.logger.info(f"Bundling") - copro.bundle(self.args.output_dir) + copro.bundle( + self.args.output_dir, + self.args.stack_file, + self.args.stack_type, + self.args.stack_addr, + ) self.logger.info(f"Complete") return 0 diff --git a/scripts/dist.py b/scripts/dist.py index de1a02c8..ccede12b 100755 --- a/scripts/dist.py +++ b/scripts/dist.py @@ -91,6 +91,7 @@ class Main(App): self.args.resources, ) ) + bundle_args.extend(self.other_args) self.logger.info( f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" ) diff --git a/scripts/flash.py b/scripts/flash.py index cc5c5e17..fb05b8b0 100755 --- a/scripts/flash.py +++ b/scripts/flash.py @@ -7,6 +7,7 @@ import os from flipper.app import App from flipper.cube import CubeProgrammer +from flipper.assets.coprobin import CoproBinary STATEMENT = "AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE" @@ -68,10 +69,15 @@ class Main(App): ) self._addArgsSWD(self.parser_core2radio) self.parser_core2radio.add_argument( - "radio_address", type=str, help="Radio Stack Binary Address" + "radio", type=str, help="Radio Stack Binary" ) self.parser_core2radio.add_argument( - "radio", type=str, help="Radio Stack Binary" + "--addr", + dest="radio_address", + help="Radio Stack Binary Address, as per release_notes", + type=lambda x: int(x, 16), + default=0, + required=False, ) self.parser_core2radio.set_defaults(func=self.core2radio) @@ -144,14 +150,27 @@ class Main(App): return 0 def core2radio(self): - if int(self.args.radio_address, 16) > 0x080E0000: + stack_info = CoproBinary(self.args.radio) + if not stack_info.is_stack(): + self.logger.error("Not a Radio Stack") + return 1 + self.logger.info(f"Will flash {stack_info.img_sig.get_version()}") + + radio_address = self.args.radio_address + if not radio_address: + radio_address = stack_info.get_flash_load_addr() + self.logger.warning( + f"Radio address not provided, guessed as 0x{radio_address:X}" + ) + if radio_address > 0x080E0000: self.logger.error(f"I KNOW WHAT YOU DID LAST SUMMER") return 1 + cp = CubeProgrammer(self._getCubeParams()) self.logger.info(f"Removing Current Radio Stack") cp.deleteCore2RadioStack() self.logger.info(f"Flashing Radio Stack") - cp.flashCore2(self.args.radio_address, self.args.radio) + cp.flashCore2(radio_address, self.args.radio) self.logger.info(f"Complete") return 0 diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index ea62f674..bd0cbc37 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -16,7 +16,7 @@ class App: self.init() def __call__(self, args=None): - self.args, _ = self.parser.parse_known_args(args=args) + self.args, self.other_args = self.parser.parse_known_args(args=args) # configure log output self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.logger.setLevel(self.log_level) diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index edf6bdd7..2c4c1a9f 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -2,9 +2,12 @@ import logging import datetime import shutil import json +from os.path import basename import xml.etree.ElementTree as ET from flipper.utils import * +from flipper.assets.coprobin import CoproBinary, get_stack_type + CUBE_COPRO_PATH = "Projects/STM32WB_Copro_Wireless_Binaries" @@ -13,14 +16,7 @@ MANIFEST_TEMPLATE = { "copro": { "fus": {"version": {"major": 1, "minor": 2, "sub": 0}, "files": []}, "radio": { - "version": { - "type": 3, - "major": 1, - "minor": 13, - "sub": 0, - "branch": 0, - "release": 5, - }, + "version": {}, "files": [], }, }, @@ -35,7 +31,7 @@ class Copro: self.mcu_copro = None self.logger = logging.getLogger(self.__class__.__name__) - def loadCubeInfo(self, cube_dir): + def loadCubeInfo(self, cube_dir, cube_version): if not os.path.isdir(cube_dir): raise Exception(f'"{cube_dir}" doesn\'t exists') self.cube_dir = cube_dir @@ -51,8 +47,8 @@ class Copro: if not cube_version or not cube_version.startswith("FW.WB"): raise Exception(f"Incorrect Cube package or version info") cube_version = cube_version.replace("FW.WB.", "", 1) - if cube_version != "1.13.1": - raise Exception(f"Unknonwn cube version") + if cube_version != cube_version: + raise Exception(f"Unsupported cube version") self.version = cube_version def addFile(self, array, filename, **kwargs): @@ -63,14 +59,32 @@ class Copro: {"name": filename, "sha256": file_sha256(destination_file), **kwargs} ) - def bundle(self, output_dir): + def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None): if not os.path.isdir(output_dir): raise Exception(f'"{output_dir}" doesn\'t exists') self.output_dir = output_dir + stack_file = os.path.join(self.mcu_copro, stack_file_name) manifest_file = os.path.join(self.output_dir, "Manifest.json") # Form Manifest manifest = dict(MANIFEST_TEMPLATE) manifest["manifest"]["timestamp"] = timestamp() + copro_bin = CoproBinary(stack_file) + self.logger.info(f"Bundling {copro_bin.img_sig.get_version()}") + stack_type_code = get_stack_type(stack_type) + manifest["copro"]["radio"]["version"].update( + { + "type": stack_type_code, + "major": copro_bin.img_sig.version_major, + "minor": copro_bin.img_sig.version_minor, + "sub": copro_bin.img_sig.version_sub, + "branch": copro_bin.img_sig.version_branch, + "release": copro_bin.img_sig.version_build, + } + ) + if not stack_addr: + stack_addr = copro_bin.get_flash_load_addr() + self.logger.info(f"Using guessed flash address 0x{stack_addr:x}") + # Old FUS Update self.addFile( manifest["copro"]["fus"]["files"], @@ -88,8 +102,8 @@ class Copro: # BLE Full Stack self.addFile( manifest["copro"]["radio"]["files"], - "stm32wb5x_BLE_Stack_light_fw.bin", - address="0x080D7000", + stack_file_name, + address=f"0x{stack_addr:X}", ) # Save manifest to json.dump(manifest, open(manifest_file, "w")) diff --git a/scripts/flipper/assets/coprobin.py b/scripts/flipper/assets/coprobin.py new file mode 100644 index 00000000..64f0b8c8 --- /dev/null +++ b/scripts/flipper/assets/coprobin.py @@ -0,0 +1,187 @@ +import struct +import math +import os, os.path +import sys + + +# From STM32CubeWB\Middlewares\ST\STM32_WPAN\interface\patterns\ble_thread\shci\shci.h +__STACK_TYPE_CODES = { + "BLE_FULL": 0x01, + "BLE_HCI": 0x02, + "BLE_LIGHT": 0x03, + "BLE_BEACON": 0x04, + "BLE_BASIC": 0x05, + "BLE_FULL_EXT_ADV": 0x06, + "BLE_HCI_EXT_ADV": 0x07, + "THREAD_FTD": 0x10, + "THREAD_MTD": 0x11, + "ZIGBEE_FFD": 0x30, + "ZIGBEE_RFD": 0x31, + "MAC": 0x40, + "BLE_THREAD_FTD_STATIC": 0x50, + "BLE_THREAD_FTD_DYAMIC": 0x51, + "802154_LLD_TESTS": 0x60, + "802154_PHY_VALID": 0x61, + "BLE_PHY_VALID": 0x62, + "BLE_LLD_TESTS": 0x63, + "BLE_RLV": 0x64, + "802154_RLV": 0x65, + "BLE_ZIGBEE_FFD_STATIC": 0x70, + "BLE_ZIGBEE_RFD_STATIC": 0x71, + "BLE_ZIGBEE_FFD_DYNAMIC": 0x78, + "BLE_ZIGBEE_RFD_DYNAMIC": 0x79, + "RLV": 0x80, + "BLE_MAC_STATIC": 0x90, +} + + +class CoproException(ValueError): + pass + + +# Formats based on AN5185 +class CoproFooterBase: + SIG_BIN_SIZE = 5 * 4 + _SIG_BIN_COMMON_SIZE = 2 * 4 + + def get_version(self): + return f"Version {self.version_major}.{self.version_minor}.{self.version_sub}, branch {self.version_branch}, build {self.version_build} (magic {self.magic:X})" + + def get_details(self): + raise CoproException("Not implemented") + + def __init__(self, raw: bytes): + if len(raw) != self.SIG_BIN_SIZE: + raise CoproException("Invalid footer size") + sig_common_part = raw[-self._SIG_BIN_COMMON_SIZE :] + parts = struct.unpack("BBBBI", sig_common_part) + self.version_major = parts[3] + self.version_minor = parts[2] + self.version_sub = parts[1] + # AN5185 mismatch: swapping byte halves + self.version_build = parts[0] & 0x0F + self.version_branch = (parts[0] & 0xF0) >> 4 + self.magic = parts[4] + + +class CoproFusFooter(CoproFooterBase): + FUS_MAGIC_IMG_STACK = 0x23372991 + FUS_MAGIC_IMG_FUS = 0x32279221 + FUS_MAGIC_IMG_OTHER = 0x42769811 + + FUS_BASE = 0x80F4000 + FLASH_PAGE_SIZE = 4 * 1024 + + def __init__(self, raw: bytes): + super().__init__(raw) + if self.magic not in ( + self.FUS_MAGIC_IMG_OTHER, + self.FUS_MAGIC_IMG_FUS, + self.FUS_MAGIC_IMG_STACK, + ): + raise CoproException(f"Invalid FUS img magic {self.magic:x}") + own_data = raw[: -self._SIG_BIN_COMMON_SIZE] + parts = struct.unpack("IIBBBB", own_data) + self.info1 = parts[0] + self.info2 = parts[1] + self.sram2b_1ks = parts[5] + self.sram2a_1ks = parts[4] + self.flash_4ks = parts[2] + + def get_details(self): + return f"SRAM2b={self.sram2b_1ks}k SRAM2a={self.sram2a_1ks}k flash={self.flash_4ks}p" + + def is_stack(self): + return self.magic == self.FUS_MAGIC_IMG_STACK + + def get_flash_pages(self, fullsize): + return math.ceil(fullsize / self.FLASH_PAGE_SIZE) + + def get_flash_base(self, fullsize): + if not self.is_stack(): + raise CoproException("Not a stack image") + return self.FUS_BASE - self.get_flash_pages(fullsize) * self.FLASH_PAGE_SIZE + + +class CoproSigFooter(CoproFooterBase): + SIG_MAGIC_ST = 0xD3A12C5E + SIG_MAGIC_CUSTOMER = 0xE2B51D4A + + def __init__(self, raw: bytes): + super().__init__(raw) + if self.magic not in (self.SIG_MAGIC_ST, self.SIG_MAGIC_CUSTOMER): + raise CoproException(f"Invalid FUS img magic {self.magic:x}") + own_data = raw[: -self._SIG_BIN_COMMON_SIZE] + parts = struct.unpack("IIBBH", own_data) + self.reserved_1 = parts[0] + self.reserved_2 = parts[1] + self.size = parts[2] + self.source = parts[3] + self.reserved_34 = parts[4] + + def get_details(self): + return f"Signature Src {self.source:x} size {self.size:x}" + + +class CoproBinary: + def __init__(self, binary_path): + self.binary_path = binary_path + self.img_sig_footer = None + self.img_sig = None + self.binary_size = -1 + self._load() + + def _load(self): + with open(self.binary_path, "rb") as fin: + whole_file = fin.read() + self.binary_size = len(whole_file) + + img_sig_footer_bin = whole_file[-CoproFooterBase.SIG_BIN_SIZE :] + self.img_sig_footer = CoproSigFooter(img_sig_footer_bin) + img_sig_size = self.img_sig_footer.size + CoproSigFooter.SIG_BIN_SIZE + img_sig_bin = whole_file[ + -(img_sig_size + CoproFusFooter.SIG_BIN_SIZE) : -img_sig_size + ] + self.img_sig = CoproFusFooter(img_sig_bin) + + def is_valid(self): + return self.img_sig_footer is not None and self.img_sig is not None + + def is_stack(self): + return self.img_sig and self.img_sig.is_stack() + + def get_flash_load_addr(self): + if not self.is_stack(): + raise CoproException("Not a stack image") + return self.img_sig.get_flash_base(self.binary_size) + + +def get_stack_type(typestr: str): + stack_code = __STACK_TYPE_CODES.get(typestr.upper(), None) + if stack_code is None: + raise CoproException(f"Unknown stack type {typestr}. See shci.h") + return stack_code + + +def _load_bin(binary_path: str): + print(binary_path) + copro_bin = CoproBinary(binary_path) + print(copro_bin.img_sig.get_version()) + if copro_bin.img_sig.is_stack(): + print(f"\t>> FLASH AT {copro_bin.get_flash_load_addr():X}\n") + + +def main(): + coprodir = ( + sys.argv[1] + if len(sys.argv) > 1 + else "../../../lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x" + ) + for fn in os.listdir(coprodir): + if not fn.endswith(".bin"): + continue + _load_bin(os.path.join(coprodir, fn)) + + +if __name__ == "__main__": + main() diff --git a/scripts/flipper/assets/obdata.py b/scripts/flipper/assets/obdata.py new file mode 100644 index 00000000..0f7f5c19 --- /dev/null +++ b/scripts/flipper/assets/obdata.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 + +import logging +import struct + +from enum import Enum +from dataclasses import dataclass +from typing import Tuple +from array import array + + +class OBException(ValueError): + pass + + +@dataclass +class OBParams: + word_idx: int + bits: Tuple[int, int] + name: str + + +_OBS_descr = ( + OBParams(0, (0, 8), "RDP"), + OBParams(0, (8, 9), "ESE"), + OBParams(0, (9, 12), "BOR_LEV"), + OBParams(0, (12, 13), "nRST_STOP"), + OBParams(0, (13, 14), "nRST_STDBY"), + OBParams(0, (14, 15), "nRSTSHDW"), + OBParams(0, (15, 16), "UNUSED1"), + OBParams(0, (16, 17), "IWDGSW"), + OBParams(0, (17, 18), "IWDGSTOP"), + OBParams(0, (18, 19), "IWGDSTDBY"), # ST's typo: IWDGSTDBY + OBParams(0, (18, 19), "IWDGSTDBY"), # ST's typo: IWDGSTDBY + OBParams(0, (19, 20), "WWDGSW"), + OBParams(0, (20, 23), "UNUSED2"), + OBParams(0, (23, 24), "nBOOT1"), + OBParams(0, (24, 25), "SRAM2PE"), + OBParams(0, (25, 26), "SRAM2RST"), + OBParams(0, (26, 27), "nSWBOOT0"), + OBParams(0, (27, 28), "nBOOT0"), + OBParams(0, (28, 29), "UNUSED3"), + OBParams(0, (29, 32), "AGC_TRIM"), + OBParams(1, (0, 9), "PCROP1A_STRT"), + OBParams(1, (9, 32), "UNUSED"), + OBParams(2, (0, 9), "PCROP1A_END"), + OBParams(2, (9, 31), "UNUSED"), + OBParams(2, (31, 32), "PCROP_RDP"), + OBParams(3, (0, 8), "WRP1A_STRT"), + OBParams(3, (8, 16), "UNUSED1"), + OBParams(3, (16, 24), "WRP1A_END"), + OBParams(3, (24, 32), "UNUSED2"), + OBParams(4, (0, 8), "WRP1B_STRT"), + OBParams(4, (8, 16), "UNUSED1"), + OBParams(4, (16, 24), "WRP1B_END"), + OBParams(4, (24, 32), "UNUSED2"), + OBParams(5, (0, 9), "PCROP1B_STRT"), + OBParams(5, (9, 32), "UNUSED"), + OBParams(6, (0, 9), "PCROP1B_END"), + OBParams(6, (9, 32), "UNUSED"), + OBParams(13, (0, 14), "IPCCDBA"), + OBParams(13, (14, 32), "UNUSED"), + OBParams(14, (0, 8), "SFSA"), + OBParams(14, (8, 9), "FSD"), + OBParams(14, (9, 12), "UNUSED1"), + OBParams(14, (12, 13), "DDS"), + OBParams(14, (13, 32), "UNUSED2"), + OBParams(15, (0, 18), "SBRV"), + OBParams(15, (18, 23), "SBRSA"), + OBParams(15, (23, 24), "BRSD"), + OBParams(15, (24, 25), "UNUSED1"), + OBParams(15, (25, 30), "SNBRSA"), + OBParams(15, (30, 31), "NBRSD"), + OBParams(15, (31, 32), "C2OPT"), +) + + +_OBS = dict((param.name, param) for param in _OBS_descr) + + +@dataclass +class EncodedOBValue: + value: int + mask: int + params: OBParams + + +class OptionByte: + class OBMode(Enum): + IGNORE = 0 + READ = 1 + READ_WRITE = 2 + + @classmethod + def from_str(cls, value): + if value == "r": + return cls.READ + elif value == "rw": + return cls.READ_WRITE + else: + raise OBException(f"Unknown OB check mode '{value}'") + + def __init__(self, obstr): + parts = obstr.split(":") + if len(parts) != 3: + raise OBException(f"Invalid OB value definition {obstr}") + self.name = parts[0] + self.value = int(parts[1], 16) + self.mode = OptionByte.OBMode.from_str(parts[2].strip()) + self.descr = _OBS.get(self.name, None) + if self.descr is None: + raise OBException(f"Missing OB descriptor for {self.name}") + + def encode(self): + startbit, endbit = self.descr.bits + value_mask = 2 ** (endbit - startbit) - 1 + value_corrected = self.value & value_mask + + value_shifted = value_corrected << startbit + value_mask_shifted = value_mask << startbit + return EncodedOBValue(value_shifted, value_mask_shifted, self) + + def __repr__(self): + return f"" + + +@dataclass +class ObReferenceValues: + reference: bytes + compare_mask: bytes + write_mask: bytes + + +class ObReferenceValuesGenerator: + def __init__(self): + self.compare_mask = array("I", [0] * 16) + self.write_mask = array("I", [0] * 16) + self.ref_values = array("I", [0] * 16) + + def __repr__(self): + return ( + f"