[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 <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									81aeda86db
								
							
						
					
					
						commit
						7ce305fca3
					
				
							
								
								
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -121,11 +121,11 @@ jobs: | |||||||
| 
 | 
 | ||||||
|       - name: 'Bundle core2 firmware' |       - name: 'Bundle core2 firmware' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         run: | |         uses: ./.github/actions/docker | ||||||
|           test -d core2_firmware && rm -rf core2_firmware || true |         with: | ||||||
|           mkdir core2_firmware |           run: | | ||||||
|           ./scripts/assets.py copro lib/STM32CubeWB core2_firmware STM32WB5x |             make -C assets copro_bundle | ||||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz core2_firmware |             tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||||
| 
 | 
 | ||||||
|       - name: 'Upload artifacts to update server' |       - name: 'Upload artifacts to update server' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
| @ -213,8 +213,8 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           run: | |           run: | | ||||||
|             set -e |             set -e | ||||||
|             make -C assets clean |             make assets_rebuild assets_manifest | ||||||
|             make -C assets |             git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) | ||||||
| 
 | 
 | ||||||
|       - name: 'Build the firmware in docker' |       - name: 'Build the firmware in docker' | ||||||
|         uses: ./.github/actions/docker |         uses: ./.github/actions/docker | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,8 +1,7 @@ | |||||||
| PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||||
| 
 | 
 | ||||||
| include			$(PROJECT_ROOT)/make/git.mk | include			$(PROJECT_ROOT)/make/git.mk | ||||||
| 
 | include			$(PROJECT_ROOT)/assets/copro.mk | ||||||
| COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x |  | ||||||
| 
 | 
 | ||||||
| PROJECT_SOURCE_DIRECTORIES := \
 | PROJECT_SOURCE_DIRECTORIES := \
 | ||||||
| 	$(PROJECT_ROOT)/applications \
 | 	$(PROJECT_ROOT)/applications \
 | ||||||
| @ -97,7 +96,13 @@ updater_package_bin: firmware_all updater | |||||||
| 
 | 
 | ||||||
| .PHONY: updater_package | .PHONY: updater_package | ||||||
| updater_package: firmware_all updater assets_manifest | 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 | .PHONY: assets_manifest | ||||||
| assets_manifest: | assets_manifest: | ||||||
| @ -109,7 +114,7 @@ assets_rebuild: | |||||||
| 
 | 
 | ||||||
| .PHONY: flash_radio | .PHONY: flash_radio | ||||||
| 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 | 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||||
| 
 | 
 | ||||||
| .PHONY: flash_radio_fus | .PHONY: flash_radio_fus | ||||||
| @ -125,8 +130,8 @@ flash_radio_fus: | |||||||
| 
 | 
 | ||||||
| .PHONY: flash_radio_fus_please_i_m_not_going_to_complain | .PHONY: flash_radio_fus_please_i_m_not_going_to_complain | ||||||
| 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_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_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.bin | ||||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||||
| 
 | 
 | ||||||
| .PHONY: lint | .PHONY: lint | ||||||
|  | |||||||
| @ -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) { | static void bt_cli_scan_callback(GapAddress address, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     osMessageQueueId_t queue = 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) { | static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { | ||||||
|  | |||||||
| @ -319,6 +319,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { | |||||||
| int32_t bt_srv() { | int32_t bt_srv() { | ||||||
|     Bt* bt = bt_alloc(); |     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
 |     // Read keys
 | ||||||
|     if(!bt_keys_storage_load(bt)) { |     if(!bt_keys_storage_load(bt)) { | ||||||
|         FURI_LOG_W(TAG, "Failed to load bonding keys"); |         FURI_LOG_W(TAG, "Failed to load bonding keys"); | ||||||
|  | |||||||
| @ -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 |     * will be missing from UI, however, /ext will be fully operational. So, until it's fixed, this | ||||||
|     * should remain commented out. */ |     * should remain commented out. */ | ||||||
|     // If (somehow) we started after SD card is mounted, initiate update immediately
 |     // If (somehow) we started after SD card is mounted, initiate update immediately
 | ||||||
|     //if(storage_sd_status(updater->storage) == FSE_OK) {
 |     if(storage_sd_status(updater->storage) == FSE_OK) { | ||||||
|     //    view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate);
 |         view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate); | ||||||
|     //}
 |     } | ||||||
| 
 | 
 | ||||||
|     updater_main_set_view_dispatcher(main_view, updater->view_dispatcher); |     updater_main_set_view_dispatcher(main_view, updater->view_dispatcher); | ||||||
|     view_dispatcher_switch_to_view(updater->view_dispatcher, UpdaterViewMain); |     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) { |     } else if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case UpdaterCustomEventStartUpdate: |         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: |         case UpdaterCustomEventRetryUpdate: | ||||||
|             if(!update_task_is_running(updater->update_task) && |             if(!update_task_is_running(updater->update_task) && | ||||||
|                (update_task_get_state(updater->update_task)->stage != UpdateTaskStageCompleted)) |                (update_task_get_state(updater->update_task)->stage != UpdateTaskStageCompleted)) | ||||||
|  | |||||||
| @ -15,13 +15,18 @@ static const char* update_task_stage_descr[] = { | |||||||
|     [UpdateTaskStageValidateDFUImage] = "Checking DFU file", |     [UpdateTaskStageValidateDFUImage] = "Checking DFU file", | ||||||
|     [UpdateTaskStageFlashWrite] = "Writing flash", |     [UpdateTaskStageFlashWrite] = "Writing flash", | ||||||
|     [UpdateTaskStageFlashValidate] = "Validating", |     [UpdateTaskStageFlashValidate] = "Validating", | ||||||
|  |     [UpdateTaskStageRadioImageValidate] = "Checking radio image", | ||||||
|  |     [UpdateTaskStageRadioErase] = "Removing radio stack", | ||||||
|     [UpdateTaskStageRadioWrite] = "Writing 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", |     [UpdateTaskStageLfsBackup] = "Backing up LFS", | ||||||
|     [UpdateTaskStageLfsRestore] = "Restoring LFS", |     [UpdateTaskStageLfsRestore] = "Restoring LFS", | ||||||
|     [UpdateTaskStageResourcesUpdate] = "Updating resources", |     [UpdateTaskStageResourcesUpdate] = "Updating resources", | ||||||
|     [UpdateTaskStageCompleted] = "Completed!", |     [UpdateTaskStageCompleted] = "Completed!", | ||||||
|     [UpdateTaskStageError] = "Error", |     [UpdateTaskStageError] = "Error", | ||||||
|  |     [UpdateTaskStageOBError] = "OB error, pls report", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void update_task_set_status(UpdateTask* update_task, const char* status) { | 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) { | void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress) { | ||||||
|     if(stage != UpdateTaskStageProgress) { |     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->state.current_stage_idx++; | ||||||
|         update_task_set_status(update_task, NULL); |         update_task_set_status(update_task, NULL); | ||||||
|     } |     } | ||||||
| @ -53,7 +61,7 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui | |||||||
|             progress, |             progress, | ||||||
|             update_task->state.current_stage_idx, |             update_task->state.current_stage_idx, | ||||||
|             update_task->state.total_stages, |             update_task->state.total_stages, | ||||||
|             update_task->state.stage == UpdateTaskStageError, |             update_task->state.stage >= UpdateTaskStageError, | ||||||
|             update_task->status_change_cb_state); |             update_task->status_change_cb_state); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -116,6 +124,7 @@ UpdateTask* update_task_alloc() { | |||||||
|     update_task->storage = furi_record_open("storage"); |     update_task->storage = furi_record_open("storage"); | ||||||
|     update_task->file = storage_file_alloc(update_task->storage); |     update_task->file = storage_file_alloc(update_task->storage); | ||||||
|     update_task->status_change_cb = NULL; |     update_task->status_change_cb = NULL; | ||||||
|  |     string_init(update_task->update_path); | ||||||
| 
 | 
 | ||||||
|     FuriThread* thread = update_task->thread = furi_thread_alloc(); |     FuriThread* thread = update_task->thread = furi_thread_alloc(); | ||||||
| 
 | 
 | ||||||
| @ -152,12 +161,6 @@ void update_task_free(UpdateTask* update_task) { | |||||||
|     free(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) { | bool update_task_parse_manifest(UpdateTask* update_task) { | ||||||
|     furi_assert(update_task); |     furi_assert(update_task); | ||||||
|     update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); |     update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); | ||||||
|  | |||||||
| @ -19,13 +19,18 @@ typedef enum { | |||||||
|     UpdateTaskStageValidateDFUImage, |     UpdateTaskStageValidateDFUImage, | ||||||
|     UpdateTaskStageFlashWrite, |     UpdateTaskStageFlashWrite, | ||||||
|     UpdateTaskStageFlashValidate, |     UpdateTaskStageFlashValidate, | ||||||
|  |     UpdateTaskStageRadioImageValidate, | ||||||
|  |     UpdateTaskStageRadioErase, | ||||||
|     UpdateTaskStageRadioWrite, |     UpdateTaskStageRadioWrite, | ||||||
|     UpdateTaskStageRadioCommit, |     UpdateTaskStageRadioInstall, | ||||||
|  |     UpdateTaskStageRadioBusy, | ||||||
|  |     UpdateTaskStageOBValidation, | ||||||
|     UpdateTaskStageLfsBackup, |     UpdateTaskStageLfsBackup, | ||||||
|     UpdateTaskStageLfsRestore, |     UpdateTaskStageLfsRestore, | ||||||
|     UpdateTaskStageResourcesUpdate, |     UpdateTaskStageResourcesUpdate, | ||||||
|     UpdateTaskStageCompleted, |     UpdateTaskStageCompleted, | ||||||
|     UpdateTaskStageError, |     UpdateTaskStageError, | ||||||
|  |     UpdateTaskStageOBError | ||||||
| } UpdateTaskStage; | } UpdateTaskStage; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -50,8 +55,6 @@ UpdateTask* update_task_alloc(); | |||||||
| 
 | 
 | ||||||
| void update_task_free(UpdateTask* update_task); | 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); | void update_task_set_progress_cb(UpdateTask* update_task, updateProgressCb cb, void* state); | ||||||
| 
 | 
 | ||||||
| bool update_task_start(UpdateTask* update_task); | bool update_task_start(UpdateTask* update_task); | ||||||
|  | |||||||
| @ -9,99 +9,17 @@ | |||||||
| #include <update_util/lfs_backup.h> | #include <update_util/lfs_backup.h> | ||||||
| #include <update_util/update_operation.h> | #include <update_util/update_operation.h> | ||||||
| #include <toolbox/tar/tar_archive.h> | #include <toolbox/tar/tar_archive.h> | ||||||
|  | #include <toolbox/crc32_calc.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "UpdWorkerBackup" | ||||||
| 
 | 
 | ||||||
| #define CHECK_RESULT(x) \ | #define CHECK_RESULT(x) \ | ||||||
|     if(!(x)) {          \ |     if(!(x)) {          \ | ||||||
|         break;          \ |         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" | #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) { | static bool update_task_pre_update(UpdateTask* update_task) { | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     string_t backup_file_path; |     string_t backup_file_path; | ||||||
| @ -143,7 +61,8 @@ static bool update_task_post_update(UpdateTask* update_task) { | |||||||
|     string_t file_path; |     string_t file_path; | ||||||
|     string_init(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 { |     do { | ||||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); |         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); |             tar_archive_free(archive); | ||||||
|         } |         } | ||||||
|  |         success = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     string_clear(file_path); |     string_clear(file_path); | ||||||
							
								
								
									
										363
									
								
								applications/updater/util/update_task_worker_flasher.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								applications/updater/util/update_task_worker_flasher.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,363 @@ | |||||||
|  | #include "update_task.h" | ||||||
|  | #include "update_task_i.h" | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
|  | #include <update_util/dfu_file.h> | ||||||
|  | #include <update_util/lfs_backup.h> | ||||||
|  | #include <update_util/update_operation.h> | ||||||
|  | #include <toolbox/tar/tar_archive.h> | ||||||
|  | #include <toolbox/crc32_calc.h> | ||||||
|  | 
 | ||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								assets/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								assets/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | |||||||
| /headers | /headers | ||||||
|  | /core2_firmware | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| PROJECT_ROOT		= $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) | PROJECT_ROOT		= $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) | ||||||
| 
 | 
 | ||||||
| include				$(PROJECT_ROOT)/assets/assets.mk | include				$(PROJECT_ROOT)/assets/assets.mk | ||||||
|  | include				$(PROJECT_ROOT)/assets/copro.mk | ||||||
| 
 | 
 | ||||||
| .PHONY: all | .PHONY: all | ||||||
| all: icons protobuf dolphin manifest | all: icons protobuf dolphin manifest | ||||||
| @ -35,7 +36,13 @@ manifest: | |||||||
| .PHONY: dolphin | .PHONY: dolphin | ||||||
| dolphin: $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | 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: | clean: | ||||||
| 	@echo "\tCLEAN\t" | 	@echo "\tCLEAN\t" | ||||||
| 	@$(RM) $(ASSETS_COMPILED_DIR)/* | 	@$(RM) $(ASSETS_COMPILED_DIR)/* | ||||||
|  | 	@$(RM) -rf $(COPRO_BUNDLE_DIR) | ||||||
| 	@$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | 	@$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								assets/copro.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								assets/copro.mk
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||||
| @ -7,7 +7,8 @@ | |||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| 
 | 
 | ||||||
| #include <update_util/update_manifest.h> | #include <update_util/update_manifest.h> | ||||||
| #include <lib/toolbox/path.h> | #include <toolbox/path.h> | ||||||
|  | #include <toolbox/crc32_calc.h> | ||||||
| 
 | 
 | ||||||
| static FATFS* pfs = NULL; | static FATFS* pfs = NULL; | ||||||
| 
 | 
 | ||||||
| @ -27,7 +28,6 @@ static bool flipper_update_init() { | |||||||
|     furi_hal_delay_init(); |     furi_hal_delay_init(); | ||||||
| 
 | 
 | ||||||
|     furi_hal_spi_init(); |     furi_hal_spi_init(); | ||||||
|     furi_hal_crc_init(false); |  | ||||||
| 
 | 
 | ||||||
|     MX_FATFS_Init(); |     MX_FATFS_Init(); | ||||||
|     if(!hal_sd_detect()) { |     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; |     uint32_t bytes_read = 0; | ||||||
|     const uint16_t MAX_READ = 0xFFFF; |     const uint16_t MAX_READ = 0xFFFF; | ||||||
| 
 | 
 | ||||||
|     furi_hal_crc_reset(); |  | ||||||
|     uint32_t crc = 0; |     uint32_t crc = 0; | ||||||
|     do { |     do { | ||||||
|         uint16_t size_read = 0; |         uint16_t size_read = 0; | ||||||
|         if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { |         if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { | ||||||
|             break; |             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; |         bytes_read += size_read; | ||||||
|     } while(bytes_read == MAX_READ); |     } while(bytes_read == MAX_READ); | ||||||
|     furi_hal_crc_reset(); |  | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { |         if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { | ||||||
|  | |||||||
| @ -6,7 +6,9 @@ | |||||||
| #include "shci.h" | #include "shci.h" | ||||||
| #include "shci_tl.h" | #include "shci_tl.h" | ||||||
| #include "app_debug.h" | #include "app_debug.h" | ||||||
|  | 
 | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
|  | #include <shci/shci.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "Core2" | #define TAG "Core2" | ||||||
| 
 | 
 | ||||||
| @ -27,22 +29,13 @@ PLACE_IN_SECTION("MB_MEM2") | |||||||
| ALIGN(4) | ALIGN(4) | ||||||
| static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; | 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 { | typedef struct { | ||||||
|     osMutexId_t shci_mtx; |     osMutexId_t shci_mtx; | ||||||
|     osSemaphoreId_t shci_sem; |     osSemaphoreId_t shci_sem; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     BleGlueStatus status; |     BleGlueStatus status; | ||||||
|     BleGlueKeyStorageChangedCallback callback; |     BleGlueKeyStorageChangedCallback callback; | ||||||
|  |     BleGlueC2Info c2_info; | ||||||
|     void* context; |     void* context; | ||||||
| } BleGlue; | } BleGlue; | ||||||
| 
 | 
 | ||||||
| @ -111,33 +104,96 @@ void ble_glue_init() { | |||||||
|      */ |      */ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { | const BleGlueC2Info* ble_glue_get_c2_info() { | ||||||
|     bool ret = false; |     return &ble_glue->c2_info; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     size_t countdown = 1000; | BleGlueStatus ble_glue_get_c2_status() { | ||||||
|     while(countdown > 0) { |     return ble_glue->status; | ||||||
|         if(ble_glue->status == BleGlueStatusFusStarted) { | } | ||||||
|             ret = true; | 
 | ||||||
|             break; | 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--; |     } while(!started && (timeout > 0)); | ||||||
|         osDelay(1); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if(ble_glue->status == BleGlueStatusFusStarted) { |     if(started) { | ||||||
|         SHCI_GetWirelessFwInfo(info); |         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 { |     } else { | ||||||
|         FURI_LOG_E(TAG, "Failed to start FUS"); |         FURI_LOG_E(TAG, "C2 startup failed"); | ||||||
|         ble_glue->status = BleGlueStatusBroken; |         ble_glue->status = BleGlueStatusBroken; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ret; |     return started; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_start() { | bool ble_glue_start() { | ||||||
|     furi_assert(ble_glue); |     furi_assert(ble_glue); | ||||||
| 
 | 
 | ||||||
|     if(ble_glue->status != BleGlueStatusFusStarted) { |     if(ble_glue->status != BleGlueStatusC2Started) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -145,7 +201,7 @@ bool ble_glue_start() { | |||||||
|     furi_hal_power_insomnia_enter(); |     furi_hal_power_insomnia_enter(); | ||||||
|     if(ble_app_init()) { |     if(ble_app_init()) { | ||||||
|         FURI_LOG_I(TAG, "Radio stack started"); |         FURI_LOG_I(TAG, "Radio stack started"); | ||||||
|         ble_glue->status = BleGlueStatusRadioStackStarted; |         ble_glue->status = BleGlueStatusRadioStackRunning; | ||||||
|         ret = true; |         ret = true; | ||||||
|         if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { |         if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { | ||||||
|             FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); |             FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); | ||||||
| @ -167,7 +223,7 @@ bool ble_glue_is_alive() { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ble_glue->status >= BleGlueStatusFusStarted; |     return ble_glue->status >= BleGlueStatusC2Started; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_is_radio_stack_ready() { | bool ble_glue_is_radio_stack_ready() { | ||||||
| @ -175,26 +231,42 @@ bool ble_glue_is_radio_stack_ready() { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ble_glue->status == BleGlueStatusRadioStackStarted; |     return ble_glue->status == BleGlueStatusRadioStackRunning; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_radio_stack_fw_launch_started() { | BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { | ||||||
|     bool ret = false; |     furi_check(desired_mode > BleGlueC2ModeUnknown); | ||||||
|     // Get FUS status
 | 
 | ||||||
|     SHCI_FUS_GetState_ErrorCode_t err_code = 0; |     if(desired_mode == ble_glue->c2_info.mode) { | ||||||
|     uint8_t state = SHCI_C2_FUS_GetState(&err_code); |         return BleGlueCommandResultOK; | ||||||
|     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
 |     if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) { | ||||||
|         FURI_LOG_W(TAG, "FUS is running. Restart to launch Radio Stack"); |         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(); |         SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs(); | ||||||
|         if(status) { |         if(status) { | ||||||
|             FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status); |             FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status); | ||||||
|         } else { |             return BleGlueCommandResultError; | ||||||
|             ret = true; |  | ||||||
|         } |         } | ||||||
|  |         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) { | 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); |         (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); | ||||||
| 
 | 
 | ||||||
|     if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { |     if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { | ||||||
|         FURI_LOG_I(TAG, "Fus started"); |         FURI_LOG_I(TAG, "Core2 started"); | ||||||
|         ble_glue->status = BleGlueStatusFusStarted; |         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(); |         furi_hal_power_insomnia_exit(); | ||||||
|     } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { |     } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { | ||||||
|         FURI_LOG_E(TAG, "Error during initialization"); |         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); |         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; | ||||||
|  | } | ||||||
| @ -2,12 +2,53 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <shci/shci.h> |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #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 ( | typedef void ( | ||||||
|     *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); |     *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_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
 | /** Is core2 radio stack present and ready
 | ||||||
|  * |  * | ||||||
| @ -46,11 +95,29 @@ void ble_glue_set_key_storage_changed_callback( | |||||||
| /** Stop SHCI thread */ | /** Stop SHCI thread */ | ||||||
| void ble_glue_thread_stop(); | 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
 | /** Restart MCU to launch radio stack firmware if necessary
 | ||||||
|  * |  * | ||||||
|  * @return      true on radio stack start command |  * @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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
|  | |||||||
| @ -55,7 +55,6 @@ void furi_hal_init() { | |||||||
|     FURI_LOG_I(TAG, "Speaker OK"); |     FURI_LOG_I(TAG, "Speaker OK"); | ||||||
| 
 | 
 | ||||||
|     furi_hal_crypto_init(); |     furi_hal_crypto_init(); | ||||||
|     furi_hal_crc_init(true); |  | ||||||
| 
 | 
 | ||||||
|     // USB
 |     // USB
 | ||||||
| #ifndef FURI_RAM_EXEC | #ifndef FURI_RAM_EXEC | ||||||
|  | |||||||
| @ -16,6 +16,9 @@ | |||||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR \ | #define FURI_HAL_BT_DEFAULT_MAC_ADDR \ | ||||||
|     { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } |     { 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; | osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||||
| static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; | 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); |     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; |     bool supported = false; | ||||||
|     if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { |     if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { | ||||||
|         furi_hal_bt_stack = FuriHalBtStackHciLayer; |         furi_hal_bt_stack = FuriHalBtStackHciLayer; | ||||||
| @ -128,21 +131,22 @@ bool furi_hal_bt_start_radio_stack() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Wait until FUS is started or timeout
 |         // Wait until C2 is started or timeout
 | ||||||
|         WirelessFwInfo_t info = {}; |         if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { | ||||||
|         if(!ble_glue_wait_for_fus_start(&info)) { |             FURI_LOG_E(TAG, "Core2 start failed"); | ||||||
|             FURI_LOG_E(TAG, "FUS start failed"); |  | ||||||
|             LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); |             LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||||
|             ble_glue_thread_stop(); |             ble_glue_thread_stop(); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         // If FUS is running, start radio stack fw
 | 
 | ||||||
|         if(ble_glue_radio_stack_fw_launch_started()) { |         // If C2 is running, start radio stack fw
 | ||||||
|             // If FUS is running do nothing and wait for system reset
 |         if(!furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)) { | ||||||
|             furi_crash("Waiting for FUS to launch radio stack firmware"); |             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"); |             FURI_LOG_E(TAG, "Unsupported radio stack"); | ||||||
|             // Don't stop SHCI for crypto enclave support
 |             // Don't stop SHCI for crypto enclave support
 | ||||||
|             break; |             break; | ||||||
| @ -232,7 +236,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, | |||||||
|     ble_app_thread_stop(); |     ble_app_thread_stop(); | ||||||
|     gap_thread_stop(); |     gap_thread_stop(); | ||||||
|     FURI_LOG_I(TAG, "Reset SHCI"); |     FURI_LOG_I(TAG, "Reset SHCI"); | ||||||
|     SHCI_C2_Reinit(); |     ble_glue_reinit_c2(); | ||||||
|     osDelay(100); |     osDelay(100); | ||||||
|     ble_glue_thread_stop(); |     ble_glue_thread_stop(); | ||||||
|     FURI_LOG_I(TAG, "Start BT initialization"); |     FURI_LOG_I(TAG, "Start BT initialization"); | ||||||
| @ -404,3 +408,18 @@ void furi_hal_bt_stop_scan() { | |||||||
|         gap_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; | ||||||
|  | } | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ void furi_hal_crc_init(bool synchronize) { | |||||||
| void furi_hal_crc_reset() { | void furi_hal_crc_reset() { | ||||||
|     furi_check(hal_crc_control.state == CRC_State_Ready); |     furi_check(hal_crc_control.state == CRC_State_Ready); | ||||||
|     if(hal_crc_control.mtx) { |     if(hal_crc_control.mtx) { | ||||||
|  |         furi_check(osMutexGetOwner(hal_crc_control.mtx) == osThreadGetId()); | ||||||
|         osMutexRelease(hal_crc_control.mtx); |         osMutexRelease(hal_crc_control.mtx); | ||||||
|     } |     } | ||||||
|     LL_CRC_ResetCRCCalculationUnit(CRC); |     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) { | bool furi_hal_crc_acquire(uint32_t timeout) { | ||||||
|     furi_assert(hal_crc_control.mtx); |     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; | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,7 +6,8 @@ | |||||||
| 
 | 
 | ||||||
| #include <stm32wbxx.h> | #include <stm32wbxx.h> | ||||||
| 
 | 
 | ||||||
| #define FURI_HAL_TAG "FuriHalFlash" | #define TAG "FuriHalFlash" | ||||||
|  | 
 | ||||||
| #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" | #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" | ||||||
| #define FURI_HAL_FLASH_READ_BLOCK 8 | #define FURI_HAL_FLASH_READ_BLOCK 8 | ||||||
| #define FURI_HAL_FLASH_WRITE_BLOCK 8 | #define FURI_HAL_FLASH_WRITE_BLOCK 8 | ||||||
| @ -14,13 +15,17 @@ | |||||||
| #define FURI_HAL_FLASH_CYCLES_COUNT 10000 | #define FURI_HAL_FLASH_CYCLES_COUNT 10000 | ||||||
| #define FURI_HAL_FLASH_TIMEOUT 1000 | #define FURI_HAL_FLASH_TIMEOUT 1000 | ||||||
| #define FURI_HAL_FLASH_KEY1 0x45670123U | #define FURI_HAL_FLASH_KEY1 0x45670123U | ||||||
| 
 |  | ||||||
| #define FURI_HAL_FLASH_KEY2 0xCDEF89ABU | #define FURI_HAL_FLASH_KEY2 0xCDEF89ABU | ||||||
| #define FURI_HAL_FLASH_TOTAL_PAGES 256 | #define FURI_HAL_FLASH_TOTAL_PAGES 256 | ||||||
| #define FURI_HAL_FLASH_SR_ERRORS                                                               \ | #define FURI_HAL_FLASH_SR_ERRORS                                                               \ | ||||||
|     (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ |     (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) |      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_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) | ||||||
| #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__)                                             \ | #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__)                                             \ | ||||||
|     (((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \ |     (((__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_KEY1); | ||||||
|     WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2); |     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); |     furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -387,3 +392,124 @@ int16_t furi_hal_flash_get_page_number(size_t address) { | |||||||
| 
 | 
 | ||||||
|     return (address - flash_base) / FURI_HAL_FLASH_PAGE_SIZE; |     return (address - flash_base) / FURI_HAL_FLASH_PAGE_SIZE; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | |||||||
| @ -4,6 +4,25 @@ | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
| 
 | 
 | ||||||
|  | #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
 | /** Init flash, applying necessary workarounds
 | ||||||
|  */ |  */ | ||||||
| void furi_hal_flash_init(); | void furi_hal_flash_init(); | ||||||
| @ -64,7 +83,7 @@ size_t furi_hal_flash_get_free_page_count(); | |||||||
| 
 | 
 | ||||||
| /** Erase Flash
 | /** Erase Flash
 | ||||||
|  * |  * | ||||||
|  * @warning    locking operation with critical section, stales execution |  * @warning    locking operation with critical section, stalls execution | ||||||
|  * |  * | ||||||
|  * @param      page  The page to erase |  * @param      page  The page to erase | ||||||
|  * |  * | ||||||
| @ -74,7 +93,7 @@ bool furi_hal_flash_erase(uint8_t page); | |||||||
| 
 | 
 | ||||||
| /** Write double word (64 bits)
 | /** 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      address  destination address, must be double word aligned. | ||||||
|  * @param      data     data to write |  * @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)
 | /** 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      address  destination address, must be page aligned. | ||||||
|  * @param      data     data to write |  * @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 |  * @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); | ||||||
|  | 
 | ||||||
|  | /** 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(); | ||||||
|  | |||||||
| @ -66,44 +66,45 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { | |||||||
|         out("firmware_target", string_get_cstr(value), false, context); |         out("firmware_target", string_get_cstr(value), false, context); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     WirelessFwInfo_t pWirelessInfo; |     if(furi_hal_bt_is_alive()) { | ||||||
|     if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) { |         const BleGlueC2Info* ble_c2_info = ble_glue_get_c2_info(); | ||||||
|         out("radio_alive", "true", false, context); |         out("radio_alive", "true", false, context); | ||||||
|  |         out("radio_mode", ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack", false, context); | ||||||
| 
 | 
 | ||||||
|         // FUS Info
 |         // 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); |         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); |         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); |         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); |         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); |         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); |         out("radio_fus_flash", string_get_cstr(value), false, context); | ||||||
| 
 | 
 | ||||||
|         // Stack Info
 |         // 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); |         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); |         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); |         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); |         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); |         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); |         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); |         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); |         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); |         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); |         out("radio_stack_flash", string_get_cstr(value), false, context); | ||||||
| 
 | 
 | ||||||
|         // Mac address
 |         // Mac address
 | ||||||
|  | |||||||
| @ -64,7 +64,7 @@ void furi_hal_vcp_init() { | |||||||
|     vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); |     vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); | ||||||
| 
 | 
 | ||||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { |     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; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -39,7 +39,6 @@ template <unsigned int N> struct STOP_EXTERNING_ME {}; | |||||||
| #include "furi_hal_uart.h" | #include "furi_hal_uart.h" | ||||||
| #include "furi_hal_info.h" | #include "furi_hal_info.h" | ||||||
| #include "furi_hal_random.h" | #include "furi_hal_random.h" | ||||||
| #include "furi_hal_crc.h" |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
| 
 | 
 | ||||||
| #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) | #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) | ||||||
| #define FURI_HAL_BT_STACK_VERSION_MINOR (13) | #define FURI_HAL_BT_STACK_VERSION_MINOR (13) | ||||||
|  | #define FURI_HAL_BT_C2_START_TIMEOUT 1000 | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -207,6 +208,12 @@ bool furi_hal_bt_start_scan(GapScanCallback callback, void* context); | |||||||
| /** Stop MAC addresses scan */ | /** Stop MAC addresses scan */ | ||||||
| void furi_hal_bt_stop_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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ typedef enum { | |||||||
|     FuriHalRtcFlagDebug = (1 << 0), |     FuriHalRtcFlagDebug = (1 << 0), | ||||||
|     FuriHalRtcFlagFactoryReset = (1 << 1), |     FuriHalRtcFlagFactoryReset = (1 << 1), | ||||||
|     FuriHalRtcFlagLock = (1 << 2), |     FuriHalRtcFlagLock = (1 << 2), | ||||||
|  |     FuriHalRtcFlagC2Update = (1 << 3), | ||||||
| } FuriHalRtcFlag; | } FuriHalRtcFlag; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| Subproject commit 528461f8276f06783d46461bfb31d77aa8bac419 | Subproject commit a9e29b431f6dac95b6fc860a717834f35b7f78e5 | ||||||
| @ -97,7 +97,8 @@ CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.cpp) | |||||||
| 
 | 
 | ||||||
| # USB Stack
 | # USB Stack
 | ||||||
| CFLAGS			+= -I$(LIB_DIR)/libusb_stm32/inc | 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
 | # protobuf
 | ||||||
| CFLAGS			+= -I$(LIB_DIR)/nanopb | CFLAGS			+= -I$(LIB_DIR)/nanopb | ||||||
|  | |||||||
							
								
								
									
										38
									
								
								lib/toolbox/crc32_calc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/toolbox/crc32_calc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | #include "crc32_calc.h" | ||||||
|  | #include <littlefs/lfs_util.h> | ||||||
|  | 
 | ||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								lib/toolbox/crc32_calc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								lib/toolbox/crc32_calc.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
|  | #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 | ||||||
| @ -1,42 +1,14 @@ | |||||||
| #include "dfu_file.h" | #include "dfu_file.h" | ||||||
|  | 
 | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
|  | #include <toolbox/crc32_calc.h> | ||||||
| 
 | 
 | ||||||
| #define VALID_WHOLE_FILE_CRC 0xFFFFFFFF | #define VALID_WHOLE_FILE_CRC 0xFFFFFFFF | ||||||
| #define DFU_SUFFIX_VERSION 0x011A | #define DFU_SUFFIX_VERSION 0x011A | ||||||
| #define DFU_DATA_BUFFER_MAX_LEN 512 |  | ||||||
| #define DFU_SIGNATURE "DfuSe" | #define DFU_SIGNATURE "DfuSe" | ||||||
| 
 | 
 | ||||||
| bool dfu_file_validate_crc(File* dfuf, const DfuPageTaskProgressCb progress_cb, void* context) { | 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)) { |     uint32_t file_crc = crc32_calc_file(dfuf, progress_cb, context); | ||||||
|         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); |  | ||||||
| 
 | 
 | ||||||
|     /* Last 4 bytes of DFU file = CRC of previous file contents, inverted
 |     /* Last 4 bytes of DFU file = CRC of previous file contents, inverted
 | ||||||
|      * If we calculate whole file CRC32, incl. embedded CRC, |      * If we calculate whole file CRC32, incl. embedded CRC, | ||||||
|  | |||||||
| @ -14,6 +14,9 @@ | |||||||
| #define MANIFEST_KEY_RADIO_VERSION "Radio version" | #define MANIFEST_KEY_RADIO_VERSION "Radio version" | ||||||
| #define MANIFEST_KEY_RADIO_CRC "Radio CRC" | #define MANIFEST_KEY_RADIO_CRC "Radio CRC" | ||||||
| #define MANIFEST_KEY_ASSETS_FILE "Resources" | #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_alloc() { | ||||||
|     UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); |     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->staged_loader_file); | ||||||
|     string_init(update_manifest->resource_bundle); |     string_init(update_manifest->resource_bundle); | ||||||
|     update_manifest->target = 0; |     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; |     update_manifest->valid = false; | ||||||
|     return update_manifest; |     return update_manifest; | ||||||
| } | } | ||||||
| @ -75,8 +81,8 @@ static bool | |||||||
|         flipper_format_read_hex( |         flipper_format_read_hex( | ||||||
|             flipper_file, |             flipper_file, | ||||||
|             MANIFEST_KEY_RADIO_VERSION, |             MANIFEST_KEY_RADIO_VERSION, | ||||||
|             (uint8_t*)&update_manifest->radio_version, |             update_manifest->radio_version.raw, | ||||||
|             sizeof(uint32_t)); |             sizeof(UpdateManifestRadioVersion)); | ||||||
|         flipper_format_read_hex( |         flipper_format_read_hex( | ||||||
|             flipper_file, |             flipper_file, | ||||||
|             MANIFEST_KEY_RADIO_CRC, |             MANIFEST_KEY_RADIO_CRC, | ||||||
| @ -85,6 +91,22 @@ static bool | |||||||
|         flipper_format_read_string( |         flipper_format_read_string( | ||||||
|             flipper_file, MANIFEST_KEY_ASSETS_FILE, update_manifest->resource_bundle); |             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 = |         update_manifest->valid = | ||||||
|             (!string_empty_p(update_manifest->firmware_dfu_image) || |             (!string_empty_p(update_manifest->firmware_dfu_image) || | ||||||
|              !string_empty_p(update_manifest->radio_image) || |              !string_empty_p(update_manifest->radio_image) || | ||||||
| @ -94,6 +116,41 @@ static bool | |||||||
|     return update_manifest->valid; |     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) { | bool update_manifest_init(UpdateManifest* update_manifest, const char* manifest_filename) { | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     FlipperFormat* flipper_file = flipper_format_file_alloc(storage); |     FlipperFormat* flipper_file = flipper_format_file_alloc(storage); | ||||||
|  | |||||||
| @ -7,12 +7,26 @@ extern "C" { | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
|  | #include <furi_hal_flash.h> | ||||||
| 
 | 
 | ||||||
| /* Paths don't include /ext -- because at startup SD card is mounted as root */ | /* Paths don't include /ext -- because at startup SD card is mounted as root */ | ||||||
| #define UPDATE_DIR_DEFAULT_REL_PATH "/update" | #define UPDATE_DIR_DEFAULT_REL_PATH "/update" | ||||||
| #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf" | #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf" | ||||||
| #define UPDATE_MAINFEST_DEFAULT_PATH UPDATE_DIR_DEFAULT_REL_PATH "/" UPDATE_MANIFEST_DEFAULT_NAME | #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 { | typedef struct { | ||||||
|     string_t version; |     string_t version; | ||||||
|     uint32_t target; |     uint32_t target; | ||||||
| @ -21,9 +35,12 @@ typedef struct { | |||||||
|     string_t firmware_dfu_image; |     string_t firmware_dfu_image; | ||||||
|     string_t radio_image; |     string_t radio_image; | ||||||
|     uint32_t radio_address; |     uint32_t radio_address; | ||||||
|     uint32_t radio_version; |     UpdateManifestRadioVersion radio_version; | ||||||
|     uint32_t radio_crc; |     uint32_t radio_crc; | ||||||
|     string_t resource_bundle; |     string_t resource_bundle; | ||||||
|  |     FuriHalFlashRawOptionByteData ob_reference; | ||||||
|  |     FuriHalFlashRawOptionByteData ob_compare_mask; | ||||||
|  |     FuriHalFlashRawOptionByteData ob_write_mask; | ||||||
|     bool valid; |     bool valid; | ||||||
| } UpdateManifest; | } UpdateManifest; | ||||||
| 
 | 
 | ||||||
| @ -38,6 +55,8 @@ bool update_manifest_init_mem( | |||||||
|     const uint8_t* manifest_data, |     const uint8_t* manifest_data, | ||||||
|     const uint16_t length); |     const uint16_t length); | ||||||
| 
 | 
 | ||||||
|  | bool update_manifest_has_obdata(UpdateManifest* update_manifest); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @ -7,6 +7,7 @@ | |||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| #include <lib/toolbox/path.h> | #include <lib/toolbox/path.h> | ||||||
|  | #include <lib/toolbox/crc32_calc.h> | ||||||
| 
 | 
 | ||||||
| static const char* UPDATE_ROOT_DIR = "/ext" UPDATE_DIR_DEFAULT_REL_PATH; | static const char* UPDATE_ROOT_DIR = "/ext" UPDATE_DIR_DEFAULT_REL_PATH; | ||||||
| static const char* UPDATE_PREFIX = "/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_extract_dirname(manifest_file_path, stage_path); | ||||||
|         path_append(stage_path, string_get_cstr(manifest->staged_loader_file)); |         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; |         uint32_t crc = 0; | ||||||
|         do { |         do { | ||||||
|             if(!storage_file_open( |             if(!storage_file_open( | ||||||
| @ -166,19 +165,10 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             result = UpdatePrepareResultStageIntegrityError; |             result = UpdatePrepareResultStageIntegrityError; | ||||||
|             furi_hal_crc_acquire(osWaitForever); |             crc = crc32_calc_file(file, NULL, NULL); | ||||||
| 
 |  | ||||||
|             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(); |  | ||||||
|         } while(false); |         } while(false); | ||||||
| 
 | 
 | ||||||
|         string_clear(stage_path); |         string_clear(stage_path); | ||||||
|         free(read_buffer); |  | ||||||
|         storage_file_free(file); |         storage_file_free(file); | ||||||
| 
 | 
 | ||||||
|         if(crc == manifest->staged_loader_crc) { |         if(crc == manifest->staged_loader_crc) { | ||||||
|  | |||||||
| @ -50,6 +50,26 @@ class Main(App): | |||||||
|         self.parser_copro.add_argument("cube_dir", help="Path to Cube folder") |         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("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("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_copro.set_defaults(func=self.copro) | ||||||
| 
 | 
 | ||||||
|         self.parser_dolphin = self.subparsers.add_parser( |         self.parser_dolphin = self.subparsers.add_parser( | ||||||
| @ -203,13 +223,15 @@ class Main(App): | |||||||
|         manifest_file = os.path.join(directory_path, "Manifest") |         manifest_file = os.path.join(directory_path, "Manifest") | ||||||
|         old_manifest = Manifest() |         old_manifest = Manifest() | ||||||
|         if os.path.exists(manifest_file): |         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) |             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 = Manifest() | ||||||
|         new_manifest.create(directory_path) |         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) |         only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) | ||||||
|         for record in only_in_old: |         for record in only_in_old: | ||||||
|             self.logger.info(f"Only in old: {record}") |             self.logger.info(f"Only in old: {record}") | ||||||
| @ -233,9 +255,14 @@ class Main(App): | |||||||
|         self.logger.info(f"Bundling coprocessor binaries") |         self.logger.info(f"Bundling coprocessor binaries") | ||||||
|         copro = Copro(self.args.mcu) |         copro = Copro(self.args.mcu) | ||||||
|         self.logger.info(f"Loading CUBE info") |         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") |         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") |         self.logger.info(f"Complete") | ||||||
| 
 | 
 | ||||||
|         return 0 |         return 0 | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ class Main(App): | |||||||
|                         self.args.resources, |                         self.args.resources, | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|  |             bundle_args.extend(self.other_args) | ||||||
|             self.logger.info( |             self.logger.info( | ||||||
|                 f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" |                 f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" | ||||||
|             ) |             ) | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import os | |||||||
| 
 | 
 | ||||||
| from flipper.app import App | from flipper.app import App | ||||||
| from flipper.cube import CubeProgrammer | from flipper.cube import CubeProgrammer | ||||||
|  | from flipper.assets.coprobin import CoproBinary | ||||||
| 
 | 
 | ||||||
| STATEMENT = "AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE" | STATEMENT = "AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE" | ||||||
| 
 | 
 | ||||||
| @ -68,10 +69,15 @@ class Main(App): | |||||||
|         ) |         ) | ||||||
|         self._addArgsSWD(self.parser_core2radio) |         self._addArgsSWD(self.parser_core2radio) | ||||||
|         self.parser_core2radio.add_argument( |         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( |         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) |         self.parser_core2radio.set_defaults(func=self.core2radio) | ||||||
| 
 | 
 | ||||||
| @ -144,14 +150,27 @@ class Main(App): | |||||||
|         return 0 |         return 0 | ||||||
| 
 | 
 | ||||||
|     def core2radio(self): |     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") |             self.logger.error(f"I KNOW WHAT YOU DID LAST SUMMER") | ||||||
|             return 1 |             return 1 | ||||||
|  | 
 | ||||||
|         cp = CubeProgrammer(self._getCubeParams()) |         cp = CubeProgrammer(self._getCubeParams()) | ||||||
|         self.logger.info(f"Removing Current Radio Stack") |         self.logger.info(f"Removing Current Radio Stack") | ||||||
|         cp.deleteCore2RadioStack() |         cp.deleteCore2RadioStack() | ||||||
|         self.logger.info(f"Flashing Radio Stack") |         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") |         self.logger.info(f"Complete") | ||||||
|         return 0 |         return 0 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ class App: | |||||||
|         self.init() |         self.init() | ||||||
| 
 | 
 | ||||||
|     def __call__(self, args=None): |     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 |         # configure log output | ||||||
|         self.log_level = logging.DEBUG if self.args.debug else logging.INFO |         self.log_level = logging.DEBUG if self.args.debug else logging.INFO | ||||||
|         self.logger.setLevel(self.log_level) |         self.logger.setLevel(self.log_level) | ||||||
|  | |||||||
| @ -2,9 +2,12 @@ import logging | |||||||
| import datetime | import datetime | ||||||
| import shutil | import shutil | ||||||
| import json | import json | ||||||
|  | from os.path import basename | ||||||
| 
 | 
 | ||||||
| import xml.etree.ElementTree as ET | import xml.etree.ElementTree as ET | ||||||
| from flipper.utils import * | from flipper.utils import * | ||||||
|  | from flipper.assets.coprobin import CoproBinary, get_stack_type | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| CUBE_COPRO_PATH = "Projects/STM32WB_Copro_Wireless_Binaries" | CUBE_COPRO_PATH = "Projects/STM32WB_Copro_Wireless_Binaries" | ||||||
| 
 | 
 | ||||||
| @ -13,14 +16,7 @@ MANIFEST_TEMPLATE = { | |||||||
|     "copro": { |     "copro": { | ||||||
|         "fus": {"version": {"major": 1, "minor": 2, "sub": 0}, "files": []}, |         "fus": {"version": {"major": 1, "minor": 2, "sub": 0}, "files": []}, | ||||||
|         "radio": { |         "radio": { | ||||||
|             "version": { |             "version": {}, | ||||||
|                 "type": 3, |  | ||||||
|                 "major": 1, |  | ||||||
|                 "minor": 13, |  | ||||||
|                 "sub": 0, |  | ||||||
|                 "branch": 0, |  | ||||||
|                 "release": 5, |  | ||||||
|             }, |  | ||||||
|             "files": [], |             "files": [], | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
| @ -35,7 +31,7 @@ class Copro: | |||||||
|         self.mcu_copro = None |         self.mcu_copro = None | ||||||
|         self.logger = logging.getLogger(self.__class__.__name__) |         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): |         if not os.path.isdir(cube_dir): | ||||||
|             raise Exception(f'"{cube_dir}" doesn\'t exists') |             raise Exception(f'"{cube_dir}" doesn\'t exists') | ||||||
|         self.cube_dir = cube_dir |         self.cube_dir = cube_dir | ||||||
| @ -51,8 +47,8 @@ class Copro: | |||||||
|         if not cube_version or not cube_version.startswith("FW.WB"): |         if not cube_version or not cube_version.startswith("FW.WB"): | ||||||
|             raise Exception(f"Incorrect Cube package or version info") |             raise Exception(f"Incorrect Cube package or version info") | ||||||
|         cube_version = cube_version.replace("FW.WB.", "", 1) |         cube_version = cube_version.replace("FW.WB.", "", 1) | ||||||
|         if cube_version != "1.13.1": |         if cube_version != cube_version: | ||||||
|             raise Exception(f"Unknonwn cube version") |             raise Exception(f"Unsupported cube version") | ||||||
|         self.version = cube_version |         self.version = cube_version | ||||||
| 
 | 
 | ||||||
|     def addFile(self, array, filename, **kwargs): |     def addFile(self, array, filename, **kwargs): | ||||||
| @ -63,14 +59,32 @@ class Copro: | |||||||
|             {"name": filename, "sha256": file_sha256(destination_file), **kwargs} |             {"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): |         if not os.path.isdir(output_dir): | ||||||
|             raise Exception(f'"{output_dir}" doesn\'t exists') |             raise Exception(f'"{output_dir}" doesn\'t exists') | ||||||
|         self.output_dir = output_dir |         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") |         manifest_file = os.path.join(self.output_dir, "Manifest.json") | ||||||
|         # Form Manifest |         # Form Manifest | ||||||
|         manifest = dict(MANIFEST_TEMPLATE) |         manifest = dict(MANIFEST_TEMPLATE) | ||||||
|         manifest["manifest"]["timestamp"] = timestamp() |         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 |         # Old FUS Update | ||||||
|         self.addFile( |         self.addFile( | ||||||
|             manifest["copro"]["fus"]["files"], |             manifest["copro"]["fus"]["files"], | ||||||
| @ -88,8 +102,8 @@ class Copro: | |||||||
|         # BLE Full Stack |         # BLE Full Stack | ||||||
|         self.addFile( |         self.addFile( | ||||||
|             manifest["copro"]["radio"]["files"], |             manifest["copro"]["radio"]["files"], | ||||||
|             "stm32wb5x_BLE_Stack_light_fw.bin", |             stack_file_name, | ||||||
|             address="0x080D7000", |             address=f"0x{stack_addr:X}", | ||||||
|         ) |         ) | ||||||
|         # Save manifest to |         # Save manifest to | ||||||
|         json.dump(manifest, open(manifest_file, "w")) |         json.dump(manifest, open(manifest_file, "w")) | ||||||
|  | |||||||
							
								
								
									
										187
									
								
								scripts/flipper/assets/coprobin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								scripts/flipper/assets/coprobin.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||||
							
								
								
									
										208
									
								
								scripts/flipper/assets/obdata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								scripts/flipper/assets/obdata.py
									
									
									
									
									
										Normal file
									
								
							| @ -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"<OB {self.name}, 0x{self.value:x}, {self.mode} at 0x{id(self):X}>" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @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"<OBRefs REFS=[{' '.join(hex(v) for v in self.ref_values)}] " | ||||||
|  |             f"CMPMASK=[{' '.join(hex(v) for v in self.compare_mask)}] " | ||||||
|  |             f"WRMASK=[{' '.join(hex(v) for v in self.write_mask)}] " | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def export_values(self): | ||||||
|  |         export_cmpmask = array("I") | ||||||
|  |         for value in self.compare_mask: | ||||||
|  |             export_cmpmask.append(value) | ||||||
|  |             export_cmpmask.append(value) | ||||||
|  |         export_wrmask = array("I") | ||||||
|  |         for value in self.write_mask: | ||||||
|  |             export_wrmask.append(value) | ||||||
|  |             export_wrmask.append(value) | ||||||
|  |         export_refvals = array("I") | ||||||
|  |         for cmpmask, refval in zip(self.compare_mask, self.ref_values): | ||||||
|  |             export_refvals.append(refval) | ||||||
|  |             export_refvals.append((refval ^ 0xFFFFFFFF) & cmpmask) | ||||||
|  |         return export_refvals, export_cmpmask, export_wrmask | ||||||
|  | 
 | ||||||
|  |     def export(self): | ||||||
|  |         return ObReferenceValues(*map(lambda a: a.tobytes(), self.export_values())) | ||||||
|  | 
 | ||||||
|  |     def apply(self, ob): | ||||||
|  |         ob_params = ob.descr | ||||||
|  |         encoded_ob = ob.encode() | ||||||
|  |         self.compare_mask[ob_params.word_idx] |= encoded_ob.mask | ||||||
|  |         self.ref_values[ob_params.word_idx] |= encoded_ob.value | ||||||
|  |         if ob.mode == OptionByte.OBMode.READ_WRITE: | ||||||
|  |             self.write_mask[ob_params.word_idx] |= encoded_ob.mask | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OptionBytesData: | ||||||
|  |     def __init__(self, obfname): | ||||||
|  |         self.obs = list() | ||||||
|  |         with open(obfname, "rt") as obfin: | ||||||
|  |             self.obs = list( | ||||||
|  |                 OptionByte(line) for line in obfin if not line.startswith("#") | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     def gen_values(self): | ||||||
|  |         obref = ObReferenceValuesGenerator() | ||||||
|  |         converted_refs = list(obref.apply(ob) for ob in self.obs) | ||||||
|  |         return obref | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     with open("../../../../logs/obs.bin", "rb") as obsbin: | ||||||
|  |         ob_sample = obsbin.read(128) | ||||||
|  |         ob_sample_arr = array("I", ob_sample) | ||||||
|  |     print(ob_sample_arr) | ||||||
|  | 
 | ||||||
|  |     obd = OptionBytesData("../../ob.data") | ||||||
|  |     print(obd.obs) | ||||||
|  |     # print(obd.gen_values().export()) | ||||||
|  |     ref, mask, wrmask = obd.gen_values().export_values() | ||||||
|  |     for idx in range(len(ob_sample_arr)): | ||||||
|  |         real_masked = ob_sample_arr[idx] & mask[idx] | ||||||
|  |         print( | ||||||
|  |             f"#{idx}: ref {ref[idx]:08x} real {real_masked:08x} ({ob_sample_arr[idx]:08x} & {mask[idx]:08x}) match {ref[idx]==real_masked}" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     # print(ob_sample) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
| @ -2,11 +2,14 @@ | |||||||
| 
 | 
 | ||||||
| from flipper.app import App | from flipper.app import App | ||||||
| from flipper.utils.fff import FlipperFormatFile | from flipper.utils.fff import FlipperFormatFile | ||||||
|  | from flipper.assets.coprobin import CoproBinary, get_stack_type | ||||||
|  | from flipper.assets.obdata import OptionBytesData | ||||||
| from os.path import basename, join, exists | from os.path import basename, join, exists | ||||||
| import os | import os | ||||||
| import shutil | import shutil | ||||||
| import zlib | import zlib | ||||||
| import tarfile | import tarfile | ||||||
|  | import math | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Main(App): | class Main(App): | ||||||
| @ -28,19 +31,28 @@ class Main(App): | |||||||
|         self.parser_generate.add_argument("-d", dest="directory", required=True) |         self.parser_generate.add_argument("-d", dest="directory", required=True) | ||||||
|         self.parser_generate.add_argument("-v", dest="version", required=True) |         self.parser_generate.add_argument("-v", dest="version", required=True) | ||||||
|         self.parser_generate.add_argument("-t", dest="target", required=True) |         self.parser_generate.add_argument("-t", dest="target", required=True) | ||||||
|         self.parser_generate.add_argument("--dfu", dest="dfu", required=False) |         self.parser_generate.add_argument( | ||||||
|  |             "--dfu", dest="dfu", default="", required=False | ||||||
|  |         ) | ||||||
|         self.parser_generate.add_argument("-r", dest="resources", required=False) |         self.parser_generate.add_argument("-r", dest="resources", required=False) | ||||||
|         self.parser_generate.add_argument("--stage", dest="stage", required=True) |         self.parser_generate.add_argument("--stage", dest="stage", required=True) | ||||||
|         self.parser_generate.add_argument( |         self.parser_generate.add_argument( | ||||||
|             "--radio", dest="radiobin", default="", required=False |             "--radio", dest="radiobin", default="", required=False | ||||||
|         ) |         ) | ||||||
|         self.parser_generate.add_argument( |         self.parser_generate.add_argument( | ||||||
|             "--radioaddr", dest="radioaddr", required=False |             "--radioaddr", | ||||||
|  |             dest="radioaddr", | ||||||
|  |             type=lambda x: int(x, 16), | ||||||
|  |             default=0, | ||||||
|  |             required=False, | ||||||
|         ) |         ) | ||||||
|  | 
 | ||||||
|         self.parser_generate.add_argument( |         self.parser_generate.add_argument( | ||||||
|             "--radiover", dest="radioversion", required=False |             "--radiotype", dest="radiotype", required=False | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  |         self.parser_generate.add_argument("--obdata", dest="obdata", required=False) | ||||||
|  | 
 | ||||||
|         self.parser_generate.set_defaults(func=self.generate) |         self.parser_generate.set_defaults(func=self.generate) | ||||||
| 
 | 
 | ||||||
|     def generate(self): |     def generate(self): | ||||||
| @ -49,11 +61,27 @@ class Main(App): | |||||||
|         radiobin_basename = basename(self.args.radiobin) |         radiobin_basename = basename(self.args.radiobin) | ||||||
|         resources_basename = "" |         resources_basename = "" | ||||||
| 
 | 
 | ||||||
|  |         radio_version = 0 | ||||||
|  |         radio_meta = None | ||||||
|  |         radio_addr = self.args.radioaddr | ||||||
|  |         if self.args.radiobin: | ||||||
|  |             if not self.args.radiotype: | ||||||
|  |                 raise ValueError("Missing --radiotype") | ||||||
|  |             radio_meta = CoproBinary(self.args.radiobin) | ||||||
|  |             radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype) | ||||||
|  |             if radio_addr == 0: | ||||||
|  |                 radio_addr = radio_meta.get_flash_load_addr() | ||||||
|  |                 self.logger.info( | ||||||
|  |                     f"Using guessed radio address 0x{radio_addr:X}, verify with Release_Notes" | ||||||
|  |                     " or specify --radioaddr" | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|         if not exists(self.args.directory): |         if not exists(self.args.directory): | ||||||
|             os.makedirs(self.args.directory) |             os.makedirs(self.args.directory) | ||||||
| 
 | 
 | ||||||
|         shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) |         shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) | ||||||
|         shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename)) |         if self.args.dfu: | ||||||
|  |             shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename)) | ||||||
|         if radiobin_basename: |         if radiobin_basename: | ||||||
|             shutil.copyfile( |             shutil.copyfile( | ||||||
|                 self.args.radiobin, join(self.args.directory, radiobin_basename) |                 self.args.radiobin, join(self.args.directory, radiobin_basename) | ||||||
| @ -73,13 +101,22 @@ class Main(App): | |||||||
|         file.writeKey("Loader CRC", self.int2ffhex(self.crc(self.args.stage))) |         file.writeKey("Loader CRC", self.int2ffhex(self.crc(self.args.stage))) | ||||||
|         file.writeKey("Firmware", dfu_basename) |         file.writeKey("Firmware", dfu_basename) | ||||||
|         file.writeKey("Radio", radiobin_basename or "") |         file.writeKey("Radio", radiobin_basename or "") | ||||||
|         file.writeKey("Radio address", self.int2ffhex(self.args.radioaddr or 0)) |         file.writeKey("Radio address", self.int2ffhex(radio_addr)) | ||||||
|         file.writeKey("Radio version", self.int2ffhex(self.args.radioversion or 0)) |         file.writeKey("Radio version", self.int2ffhex(radio_version)) | ||||||
|         if radiobin_basename: |         if radiobin_basename: | ||||||
|             file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin))) |             file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin))) | ||||||
|         else: |         else: | ||||||
|             file.writeKey("Radio CRC", self.int2ffhex(0)) |             file.writeKey("Radio CRC", self.int2ffhex(0)) | ||||||
|         file.writeKey("Resources", resources_basename) |         file.writeKey("Resources", resources_basename) | ||||||
|  |         file.writeComment( | ||||||
|  |             "NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE" | ||||||
|  |         ) | ||||||
|  |         if self.args.obdata: | ||||||
|  |             obd = OptionBytesData(self.args.obdata) | ||||||
|  |             obvalues = obd.gen_values().export() | ||||||
|  |             file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) | ||||||
|  |             file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) | ||||||
|  |             file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) | ||||||
|         file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) |         file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) | ||||||
| 
 | 
 | ||||||
|         return 0 |         return 0 | ||||||
| @ -90,9 +127,34 @@ class Main(App): | |||||||
|         ) as tarball: |         ) as tarball: | ||||||
|             tarball.add(srcdir, arcname="") |             tarball.add(srcdir, arcname="") | ||||||
| 
 | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def copro_version_as_int(coprometa, stacktype): | ||||||
|  |         major = coprometa.img_sig.version_major | ||||||
|  |         minor = coprometa.img_sig.version_minor | ||||||
|  |         sub = coprometa.img_sig.version_sub | ||||||
|  |         branch = coprometa.img_sig.version_branch | ||||||
|  |         release = coprometa.img_sig.version_build | ||||||
|  |         stype = get_stack_type(stacktype) | ||||||
|  |         return ( | ||||||
|  |             major | ||||||
|  |             | (minor << 8) | ||||||
|  |             | (sub << 16) | ||||||
|  |             | (branch << 24) | ||||||
|  |             | (release << 32) | ||||||
|  |             | (stype << 40) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def bytes2ffhex(value: bytes): | ||||||
|  |         return " ".join(f"{b:02X}" for b in value) | ||||||
|  | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def int2ffhex(value: int): |     def int2ffhex(value: int): | ||||||
|         hexstr = "%08X" % value |         n_hex_bytes = 4 | ||||||
|  |         if value: | ||||||
|  |             n_hex_bytes = math.ceil(math.ceil(math.log2(value)) / 8) * 2 | ||||||
|  |         fmtstr = f"%0{n_hex_bytes}X" | ||||||
|  |         hexstr = fmtstr % value | ||||||
|         return " ".join(list(Main.batch(hexstr, 2))[::-1]) |         return " ".join(list(Main.batch(hexstr, 2))[::-1]) | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger