[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' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           test -d core2_firmware && rm -rf core2_firmware || true | ||||
|           mkdir core2_firmware | ||||
|           ./scripts/assets.py copro lib/STM32CubeWB core2_firmware STM32WB5x | ||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz core2_firmware | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             make -C assets copro_bundle | ||||
|             tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
| @ -213,8 +213,8 @@ jobs: | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             make -C assets clean | ||||
|             make -C assets | ||||
|             make assets_rebuild assets_manifest | ||||
|             git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|  | ||||
							
								
								
									
										17
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,8 +1,7 @@ | ||||
| PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||
| 
 | ||||
| include			$(PROJECT_ROOT)/make/git.mk | ||||
| 
 | ||||
| COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x | ||||
| include			$(PROJECT_ROOT)/assets/copro.mk | ||||
| 
 | ||||
| PROJECT_SOURCE_DIRECTORIES := \
 | ||||
| 	$(PROJECT_ROOT)/applications \
 | ||||
| @ -97,7 +96,13 @@ updater_package_bin: firmware_all updater | ||||
| 
 | ||||
| .PHONY: updater_package | ||||
| updater_package: firmware_all updater assets_manifest | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy -t $(TARGET) -p firmware updater -s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources --bundlever "$(VERSION_STRING)" | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy \
 | ||||
| 	-t $(TARGET) -p firmware updater \
 | ||||
| 	-s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources \
 | ||||
| 	--bundlever "$(VERSION_STRING)" \
 | ||||
| 	--radio $(COPRO_STACK_BIN_PATH) \
 | ||||
| 	--radiotype $(COPRO_STACK_TYPE) \
 | ||||
| 	--obdata $(PROJECT_ROOT)/scripts/ob.data | ||||
| 
 | ||||
| .PHONY: assets_manifest | ||||
| assets_manifest: | ||||
| @ -109,7 +114,7 @@ assets_rebuild: | ||||
| 
 | ||||
| .PHONY: flash_radio | ||||
| flash_radio: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2radio 0x080D7000 $(COPRO_DIR)/stm32wb5x_BLE_Stack_light_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2radio $(COPRO_STACK_BIN_PATH) --addr=$(COPRO_STACK_ADDR) | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: flash_radio_fus | ||||
| @ -125,8 +130,8 @@ flash_radio_fus: | ||||
| 
 | ||||
| .PHONY: flash_radio_fus_please_i_m_not_going_to_complain | ||||
| flash_radio_fus_please_i_m_not_going_to_complain: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: lint | ||||
|  | ||||
| @ -148,7 +148,7 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | ||||
| static void bt_cli_scan_callback(GapAddress address, void* context) { | ||||
|     furi_assert(context); | ||||
|     osMessageQueueId_t queue = context; | ||||
|     osMessageQueuePut(queue, &address, NULL, 250); | ||||
|     osMessageQueuePut(queue, &address, 0, 250); | ||||
| } | ||||
| 
 | ||||
| static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { | ||||
|  | ||||
| @ -319,6 +319,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { | ||||
| int32_t bt_srv() { | ||||
|     Bt* bt = bt_alloc(); | ||||
| 
 | ||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||
|         FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode"); | ||||
|         ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); | ||||
|         furi_record_create("bt", bt); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     // Read keys
 | ||||
|     if(!bt_keys_storage_load(bt)) { | ||||
|         FURI_LOG_W(TAG, "Failed to load bonding keys"); | ||||
|  | ||||
| @ -36,9 +36,9 @@ void updater_scene_main_on_enter(void* context) { | ||||
|     * will be missing from UI, however, /ext will be fully operational. So, until it's fixed, this | ||||
|     * should remain commented out. */ | ||||
|     // If (somehow) we started after SD card is mounted, initiate update immediately
 | ||||
|     //if(storage_sd_status(updater->storage) == FSE_OK) {
 | ||||
|     //    view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate);
 | ||||
|     //}
 | ||||
|     if(storage_sd_status(updater->storage) == FSE_OK) { | ||||
|         view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate); | ||||
|     } | ||||
| 
 | ||||
|     updater_main_set_view_dispatcher(main_view, updater->view_dispatcher); | ||||
|     view_dispatcher_switch_to_view(updater->view_dispatcher, UpdaterViewMain); | ||||
| @ -64,13 +64,6 @@ bool updater_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|     } else if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case UpdaterCustomEventStartUpdate: | ||||
|             if(!update_task_is_running(updater->update_task) && | ||||
|                update_task_init(updater->update_task)) { | ||||
|                 update_task_start(updater->update_task); | ||||
|             } | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case UpdaterCustomEventRetryUpdate: | ||||
|             if(!update_task_is_running(updater->update_task) && | ||||
|                (update_task_get_state(updater->update_task)->stage != UpdateTaskStageCompleted)) | ||||
|  | ||||
| @ -15,13 +15,18 @@ static const char* update_task_stage_descr[] = { | ||||
|     [UpdateTaskStageValidateDFUImage] = "Checking DFU file", | ||||
|     [UpdateTaskStageFlashWrite] = "Writing flash", | ||||
|     [UpdateTaskStageFlashValidate] = "Validating", | ||||
|     [UpdateTaskStageRadioImageValidate] = "Checking radio image", | ||||
|     [UpdateTaskStageRadioErase] = "Removing radio stack", | ||||
|     [UpdateTaskStageRadioWrite] = "Writing radio stack", | ||||
|     [UpdateTaskStageRadioCommit] = "Applying radio stack", | ||||
|     [UpdateTaskStageRadioInstall] = "Installing radio stack", | ||||
|     [UpdateTaskStageRadioBusy] = "Core2 is updating", | ||||
|     [UpdateTaskStageOBValidation] = "Validating opt. bytes", | ||||
|     [UpdateTaskStageLfsBackup] = "Backing up LFS", | ||||
|     [UpdateTaskStageLfsRestore] = "Restoring LFS", | ||||
|     [UpdateTaskStageResourcesUpdate] = "Updating resources", | ||||
|     [UpdateTaskStageCompleted] = "Completed!", | ||||
|     [UpdateTaskStageError] = "Error", | ||||
|     [UpdateTaskStageOBError] = "OB error, pls report", | ||||
| }; | ||||
| 
 | ||||
| static void update_task_set_status(UpdateTask* update_task, const char* status) { | ||||
| @ -37,7 +42,10 @@ static void update_task_set_status(UpdateTask* update_task, const char* status) | ||||
| 
 | ||||
| void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress) { | ||||
|     if(stage != UpdateTaskStageProgress) { | ||||
|         update_task->state.stage = stage; | ||||
|         // do not override more specific error states
 | ||||
|         if((update_task->state.stage < UpdateTaskStageError) || (stage < UpdateTaskStageError)) { | ||||
|             update_task->state.stage = stage; | ||||
|         } | ||||
|         update_task->state.current_stage_idx++; | ||||
|         update_task_set_status(update_task, NULL); | ||||
|     } | ||||
| @ -53,7 +61,7 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui | ||||
|             progress, | ||||
|             update_task->state.current_stage_idx, | ||||
|             update_task->state.total_stages, | ||||
|             update_task->state.stage == UpdateTaskStageError, | ||||
|             update_task->state.stage >= UpdateTaskStageError, | ||||
|             update_task->status_change_cb_state); | ||||
|     } | ||||
| } | ||||
| @ -116,6 +124,7 @@ UpdateTask* update_task_alloc() { | ||||
|     update_task->storage = furi_record_open("storage"); | ||||
|     update_task->file = storage_file_alloc(update_task->storage); | ||||
|     update_task->status_change_cb = NULL; | ||||
|     string_init(update_task->update_path); | ||||
| 
 | ||||
|     FuriThread* thread = update_task->thread = furi_thread_alloc(); | ||||
| 
 | ||||
| @ -152,12 +161,6 @@ void update_task_free(UpdateTask* update_task) { | ||||
|     free(update_task); | ||||
| } | ||||
| 
 | ||||
| bool update_task_init(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     string_init(update_task->update_path); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool update_task_parse_manifest(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); | ||||
| @ -224,4 +227,4 @@ UpdateTaskState const* update_task_get_state(UpdateTask* update_task) { | ||||
| UpdateManifest const* update_task_get_manifest(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     return update_task->manifest; | ||||
| } | ||||
| } | ||||
| @ -19,13 +19,18 @@ typedef enum { | ||||
|     UpdateTaskStageValidateDFUImage, | ||||
|     UpdateTaskStageFlashWrite, | ||||
|     UpdateTaskStageFlashValidate, | ||||
|     UpdateTaskStageRadioImageValidate, | ||||
|     UpdateTaskStageRadioErase, | ||||
|     UpdateTaskStageRadioWrite, | ||||
|     UpdateTaskStageRadioCommit, | ||||
|     UpdateTaskStageRadioInstall, | ||||
|     UpdateTaskStageRadioBusy, | ||||
|     UpdateTaskStageOBValidation, | ||||
|     UpdateTaskStageLfsBackup, | ||||
|     UpdateTaskStageLfsRestore, | ||||
|     UpdateTaskStageResourcesUpdate, | ||||
|     UpdateTaskStageCompleted, | ||||
|     UpdateTaskStageError, | ||||
|     UpdateTaskStageOBError | ||||
| } UpdateTaskStage; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -50,8 +55,6 @@ UpdateTask* update_task_alloc(); | ||||
| 
 | ||||
| void update_task_free(UpdateTask* update_task); | ||||
| 
 | ||||
| bool update_task_init(UpdateTask* update_task); | ||||
| 
 | ||||
| void update_task_set_progress_cb(UpdateTask* update_task, updateProgressCb cb, void* state); | ||||
| 
 | ||||
| bool update_task_start(UpdateTask* update_task); | ||||
|  | ||||
| @ -9,99 +9,17 @@ | ||||
| #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 "UpdWorkerBackup" | ||||
| 
 | ||||
| #define CHECK_RESULT(x) \ | ||||
|     if(!(x)) {          \ | ||||
|         break;          \ | ||||
|     } | ||||
| 
 | ||||
| #define STM_DFU_VENDOR_ID 0x0483 | ||||
| #define STM_DFU_PRODUCT_ID 0xDF11 | ||||
| /* Written into DFU file by build pipeline */ | ||||
| #define FLIPPER_ZERO_DFU_DEVICE_CODE 0xFFFF | ||||
| 
 | ||||
| #define EXT_PATH "/ext" | ||||
| 
 | ||||
| static const DfuValidationParams flipper_dfu_params = { | ||||
|     .device = FLIPPER_ZERO_DFU_DEVICE_CODE, | ||||
|     .product = STM_DFU_PRODUCT_ID, | ||||
|     .vendor = STM_DFU_VENDOR_ID, | ||||
| }; | ||||
| 
 | ||||
| static void update_task_dfu_progress(const uint8_t progress, void* context) { | ||||
|     UpdateTask* update_task = context; | ||||
|     update_task_set_progress(update_task, UpdateTaskStageProgress, progress); | ||||
| } | ||||
| 
 | ||||
| static bool page_task_compare_flash( | ||||
|     const uint8_t i_page, | ||||
|     const uint8_t* update_block, | ||||
|     uint16_t update_block_len) { | ||||
|     const size_t page_addr = furi_hal_flash_get_base() + furi_hal_flash_get_page_size() * i_page; | ||||
|     return (memcmp(update_block, (void*)page_addr, update_block_len) == 0); | ||||
| } | ||||
| 
 | ||||
| /* Verifies a flash operation address for fitting into writable memory
 | ||||
|  */ | ||||
| static bool check_address_boundaries(const size_t address) { | ||||
|     const size_t min_allowed_address = furi_hal_flash_get_base(); | ||||
|     const size_t max_allowed_address = (size_t)furi_hal_flash_get_free_end_address(); | ||||
|     return ((address >= min_allowed_address) && (address < max_allowed_address)); | ||||
| } | ||||
| 
 | ||||
| int32_t update_task_worker_flash_writer(void* context) { | ||||
|     furi_assert(context); | ||||
|     UpdateTask* update_task = context; | ||||
|     bool success = false; | ||||
|     DfuUpdateTask page_task = { | ||||
|         .address_cb = &check_address_boundaries, | ||||
|         .progress_cb = &update_task_dfu_progress, | ||||
|         .task_cb = &furi_hal_flash_program_page, | ||||
|         .context = update_task, | ||||
|     }; | ||||
| 
 | ||||
|     update_task->state.current_stage_idx = 0; | ||||
|     update_task->state.total_stages = 4; | ||||
| 
 | ||||
|     do { | ||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); | ||||
| 
 | ||||
|         if(!string_empty_p(update_task->manifest->firmware_dfu_image)) { | ||||
|             update_task_set_progress(update_task, UpdateTaskStageValidateDFUImage, 0); | ||||
|             CHECK_RESULT( | ||||
|                 update_task_open_file(update_task, update_task->manifest->firmware_dfu_image)); | ||||
|             CHECK_RESULT( | ||||
|                 dfu_file_validate_crc(update_task->file, &update_task_dfu_progress, update_task)); | ||||
| 
 | ||||
|             const uint8_t valid_targets = | ||||
|                 dfu_file_validate_headers(update_task->file, &flipper_dfu_params); | ||||
|             if(valid_targets == 0) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             update_task_set_progress(update_task, UpdateTaskStageFlashWrite, 0); | ||||
|             CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
| 
 | ||||
|             page_task.task_cb = &page_task_compare_flash; | ||||
| 
 | ||||
|             update_task_set_progress(update_task, UpdateTaskStageFlashValidate, 0); | ||||
|             CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
|         } | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); | ||||
| 
 | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); | ||||
| 
 | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!success) { | ||||
|         update_task_set_progress(update_task, UpdateTaskStageError, update_task->state.progress); | ||||
|     } | ||||
| 
 | ||||
|     return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; | ||||
| } | ||||
| 
 | ||||
| static bool update_task_pre_update(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
|     string_t backup_file_path; | ||||
| @ -143,7 +61,8 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|     string_t file_path; | ||||
|     string_init(file_path); | ||||
| 
 | ||||
|     update_task->state.total_stages = 2; | ||||
|     // status text is too long, too few stages to bother with a counter
 | ||||
|     update_task->state.total_stages = 0; | ||||
| 
 | ||||
|     do { | ||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); | ||||
| @ -184,6 +103,7 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|             } | ||||
|             tar_archive_free(archive); | ||||
|         } | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(file_path); | ||||
							
								
								
									
										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 | ||||
| /core2_firmware | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| PROJECT_ROOT		= $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) | ||||
| 
 | ||||
| include				$(PROJECT_ROOT)/assets/assets.mk | ||||
| include				$(PROJECT_ROOT)/assets/copro.mk | ||||
| 
 | ||||
| .PHONY: all | ||||
| all: icons protobuf dolphin manifest | ||||
| @ -35,7 +36,13 @@ manifest: | ||||
| .PHONY: dolphin | ||||
| dolphin: $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | ||||
| 
 | ||||
| .PHONY: copro_bundle | ||||
| copro_bundle: | ||||
| 	@mkdir -p $(COPRO_BUNDLE_DIR) | ||||
| 	@$(ASSETS_COMPILER) copro $(COPRO_CUBE_DIR) $(COPRO_BUNDLE_DIR) $(COPRO_MCU_FAMILY) --cube_ver=$(COPRO_CUBE_VERSION) --stack_type=$(COPRO_STACK_TYPE) --stack_file=$(COPRO_STACK_BIN) --stack_addr=$(COPRO_STACK_ADDR) | ||||
| 
 | ||||
| clean: | ||||
| 	@echo "\tCLEAN\t" | ||||
| 	@$(RM) $(ASSETS_COMPILED_DIR)/* | ||||
| 	@$(RM) -rf $(COPRO_BUNDLE_DIR) | ||||
| 	@$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | ||||
|  | ||||
							
								
								
									
										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 <update_util/update_manifest.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #include <toolbox/path.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| static FATFS* pfs = NULL; | ||||
| 
 | ||||
| @ -27,7 +28,6 @@ static bool flipper_update_init() { | ||||
|     furi_hal_delay_init(); | ||||
| 
 | ||||
|     furi_hal_spi_init(); | ||||
|     furi_hal_crc_init(false); | ||||
| 
 | ||||
|     MX_FATFS_Init(); | ||||
|     if(!hal_sd_detect()) { | ||||
| @ -62,17 +62,15 @@ static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* m | ||||
|     uint32_t bytes_read = 0; | ||||
|     const uint16_t MAX_READ = 0xFFFF; | ||||
| 
 | ||||
|     furi_hal_crc_reset(); | ||||
|     uint32_t crc = 0; | ||||
|     do { | ||||
|         uint16_t size_read = 0; | ||||
|         if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { | ||||
|             break; | ||||
|         } | ||||
|         crc = furi_hal_crc_feed(img + bytes_read, size_read); | ||||
|         crc = crc32_calc_buffer(crc, img + bytes_read, size_read); | ||||
|         bytes_read += size_read; | ||||
|     } while(bytes_read == MAX_READ); | ||||
|     furi_hal_crc_reset(); | ||||
| 
 | ||||
|     do { | ||||
|         if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { | ||||
|  | ||||
| @ -6,7 +6,9 @@ | ||||
| #include "shci.h" | ||||
| #include "shci_tl.h" | ||||
| #include "app_debug.h" | ||||
| 
 | ||||
| #include <furi_hal.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #define TAG "Core2" | ||||
| 
 | ||||
| @ -27,22 +29,13 @@ PLACE_IN_SECTION("MB_MEM2") | ||||
| ALIGN(4) | ||||
| static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; | ||||
| 
 | ||||
| typedef enum { | ||||
|     // Stage 1: core2 startup and FUS
 | ||||
|     BleGlueStatusStartup, | ||||
|     BleGlueStatusBroken, | ||||
|     BleGlueStatusFusStarted, | ||||
|     // Stage 2: radio stack
 | ||||
|     BleGlueStatusRadioStackStarted, | ||||
|     BleGlueStatusRadioStackMissing | ||||
| } BleGlueStatus; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osMutexId_t shci_mtx; | ||||
|     osSemaphoreId_t shci_sem; | ||||
|     FuriThread* thread; | ||||
|     BleGlueStatus status; | ||||
|     BleGlueKeyStorageChangedCallback callback; | ||||
|     BleGlueC2Info c2_info; | ||||
|     void* context; | ||||
| } BleGlue; | ||||
| 
 | ||||
| @ -111,33 +104,96 @@ void ble_glue_init() { | ||||
|      */ | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { | ||||
|     bool ret = false; | ||||
| const BleGlueC2Info* ble_glue_get_c2_info() { | ||||
|     return &ble_glue->c2_info; | ||||
| } | ||||
| 
 | ||||
|     size_t countdown = 1000; | ||||
|     while(countdown > 0) { | ||||
|         if(ble_glue->status == BleGlueStatusFusStarted) { | ||||
|             ret = true; | ||||
|             break; | ||||
| BleGlueStatus ble_glue_get_c2_status() { | ||||
|     return ble_glue->status; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_update_c2_fw_info() { | ||||
|     WirelessFwInfo_t wireless_info; | ||||
|     SHCI_GetWirelessFwInfo(&wireless_info); | ||||
|     BleGlueC2Info* local_info = &ble_glue->c2_info; | ||||
| 
 | ||||
|     local_info->VersionMajor = wireless_info.VersionMajor; | ||||
|     local_info->VersionMinor = wireless_info.VersionMinor; | ||||
|     local_info->VersionMajor = wireless_info.VersionMajor; | ||||
|     local_info->VersionMinor = wireless_info.VersionMinor; | ||||
|     local_info->VersionSub = wireless_info.VersionSub; | ||||
|     local_info->VersionBranch = wireless_info.VersionBranch; | ||||
|     local_info->VersionReleaseType = wireless_info.VersionReleaseType; | ||||
| 
 | ||||
|     local_info->MemorySizeSram2B = wireless_info.MemorySizeSram2B; | ||||
|     local_info->MemorySizeSram2A = wireless_info.MemorySizeSram2A; | ||||
|     local_info->MemorySizeSram1 = wireless_info.MemorySizeSram1; | ||||
|     local_info->MemorySizeFlash = wireless_info.MemorySizeFlash; | ||||
| 
 | ||||
|     local_info->StackType = wireless_info.StackType; | ||||
| 
 | ||||
|     local_info->FusVersionMajor = wireless_info.FusVersionMajor; | ||||
|     local_info->FusVersionMinor = wireless_info.FusVersionMinor; | ||||
|     local_info->FusVersionSub = wireless_info.FusVersionSub; | ||||
|     local_info->FusMemorySizeSram2B = wireless_info.FusMemorySizeSram2B; | ||||
|     local_info->FusMemorySizeSram2A = wireless_info.FusMemorySizeSram2A; | ||||
|     local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_dump_stack_info() { | ||||
|     const BleGlueC2Info* c2_info = &ble_glue->c2_info; | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Core2: FUS: %d.%d.%d, mem %d/%d, flash %d pages", | ||||
|         c2_info->FusVersionMajor, | ||||
|         c2_info->FusVersionMinor, | ||||
|         c2_info->FusVersionSub, | ||||
|         c2_info->FusMemorySizeSram2B, | ||||
|         c2_info->FusMemorySizeSram2A, | ||||
|         c2_info->FusMemorySizeFlash); | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Core2: Stack: %d.%d.%d, branch %d, reltype %d, stacktype %d, flash %d pages", | ||||
|         c2_info->VersionMajor, | ||||
|         c2_info->VersionMinor, | ||||
|         c2_info->VersionSub, | ||||
|         c2_info->VersionBranch, | ||||
|         c2_info->VersionReleaseType, | ||||
|         c2_info->StackType, | ||||
|         c2_info->MemorySizeFlash); | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_wait_for_c2_start(int32_t timeout) { | ||||
|     bool started = false; | ||||
| 
 | ||||
|     do { | ||||
|         // TODO: use mutex?
 | ||||
|         started = ble_glue->status == BleGlueStatusC2Started; | ||||
|         if(!started) { | ||||
|             timeout--; | ||||
|             osDelay(1); | ||||
|         } | ||||
|         countdown--; | ||||
|         osDelay(1); | ||||
|     } | ||||
|     } while(!started && (timeout > 0)); | ||||
| 
 | ||||
|     if(ble_glue->status == BleGlueStatusFusStarted) { | ||||
|         SHCI_GetWirelessFwInfo(info); | ||||
|     if(started) { | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
|             "C2 boot completed, mode: %s", | ||||
|             ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); | ||||
|         ble_glue_update_c2_fw_info(); | ||||
|         ble_glue_dump_stack_info(); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "Failed to start FUS"); | ||||
|         FURI_LOG_E(TAG, "C2 startup failed"); | ||||
|         ble_glue->status = BleGlueStatusBroken; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|     return started; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_start() { | ||||
|     furi_assert(ble_glue); | ||||
| 
 | ||||
|     if(ble_glue->status != BleGlueStatusFusStarted) { | ||||
|     if(ble_glue->status != BleGlueStatusC2Started) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -145,7 +201,7 @@ bool ble_glue_start() { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     if(ble_app_init()) { | ||||
|         FURI_LOG_I(TAG, "Radio stack started"); | ||||
|         ble_glue->status = BleGlueStatusRadioStackStarted; | ||||
|         ble_glue->status = BleGlueStatusRadioStackRunning; | ||||
|         ret = true; | ||||
|         if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { | ||||
|             FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); | ||||
| @ -167,7 +223,7 @@ bool ble_glue_is_alive() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return ble_glue->status >= BleGlueStatusFusStarted; | ||||
|     return ble_glue->status >= BleGlueStatusC2Started; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_is_radio_stack_ready() { | ||||
| @ -175,26 +231,42 @@ bool ble_glue_is_radio_stack_ready() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return ble_glue->status == BleGlueStatusRadioStackStarted; | ||||
|     return ble_glue->status == BleGlueStatusRadioStackRunning; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_radio_stack_fw_launch_started() { | ||||
|     bool ret = false; | ||||
|     // Get FUS status
 | ||||
|     SHCI_FUS_GetState_ErrorCode_t err_code = 0; | ||||
|     uint8_t state = SHCI_C2_FUS_GetState(&err_code); | ||||
|     if(state == FUS_STATE_VALUE_IDLE) { | ||||
|         // When FUS is running we can't read radio stack version correctly
 | ||||
|         // Trying to start radio stack fw, which leads to reset
 | ||||
|         FURI_LOG_W(TAG, "FUS is running. Restart to launch Radio Stack"); | ||||
| BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { | ||||
|     furi_check(desired_mode > BleGlueC2ModeUnknown); | ||||
| 
 | ||||
|     if(desired_mode == ble_glue->c2_info.mode) { | ||||
|         return BleGlueCommandResultOK; | ||||
|     } | ||||
| 
 | ||||
|     if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) { | ||||
|         if((ble_glue->c2_info.VersionMajor == 0) && (ble_glue->c2_info.VersionMinor == 0)) { | ||||
|             FURI_LOG_W(TAG, "Stack isn't installed!"); | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs(); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status); | ||||
|         } else { | ||||
|             ret = true; | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         return BleGlueCommandResultRestartPending; | ||||
|     } | ||||
|     return ret; | ||||
|     if((ble_glue->c2_info.mode == BleGlueC2ModeStack) && (desired_mode == BleGlueC2ModeFUS)) { | ||||
|         SHCI_FUS_GetState_ErrorCode_t error_code = 0; | ||||
|         uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|         FURI_LOG_D(TAG, "FUS state: %X, error = %x", fus_state, error_code); | ||||
|         if(fus_state == SHCI_FUS_CMD_NOT_SUPPORTED) { | ||||
|             // Second call to SHCI_C2_FUS_GetState() restarts whole MCU & boots FUS
 | ||||
|             fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|             FURI_LOG_D(TAG, "FUS state#2: %X, error = %x", fus_state, error_code); | ||||
|             return BleGlueCommandResultRestartPending; | ||||
|         } | ||||
|         return BleGlueCommandResultOK; | ||||
|     } | ||||
| 
 | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { | ||||
| @ -228,8 +300,15 @@ static void ble_glue_sys_user_event_callback(void* pPayload) { | ||||
|         (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); | ||||
| 
 | ||||
|     if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { | ||||
|         FURI_LOG_I(TAG, "Fus started"); | ||||
|         ble_glue->status = BleGlueStatusFusStarted; | ||||
|         FURI_LOG_I(TAG, "Core2 started"); | ||||
|         SHCI_C2_Ready_Evt_t* p_c2_ready_evt = (SHCI_C2_Ready_Evt_t*)p_sys_event->payload; | ||||
|         if(p_c2_ready_evt->sysevt_ready_rsp == WIRELESS_FW_RUNNING) { | ||||
|             ble_glue->c2_info.mode = BleGlueC2ModeStack; | ||||
|         } else if(p_c2_ready_evt->sysevt_ready_rsp == FUS_FW_RUNNING) { | ||||
|             ble_glue->c2_info.mode = BleGlueC2ModeFUS; | ||||
|         } | ||||
| 
 | ||||
|         ble_glue->status = BleGlueStatusC2Started; | ||||
|         furi_hal_power_insomnia_exit(); | ||||
|     } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { | ||||
|         FURI_LOG_E(TAG, "Error during initialization"); | ||||
| @ -308,3 +387,61 @@ void shci_cmd_resp_wait(uint32_t timeout) { | ||||
|         osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_reinit_c2() { | ||||
|     return SHCI_C2_Reinit() == SHCI_Success; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_delete() { | ||||
|     FURI_LOG_I(TAG, "Erasing stack"); | ||||
|     SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete(); | ||||
|     FURI_LOG_I(TAG, "Cmd res = %x", erase_stat); | ||||
|     if(erase_stat == SHCI_Success) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     ble_glue_fus_get_status(); | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr) { | ||||
|     FURI_LOG_I(TAG, "Installing stack"); | ||||
|     SHCI_CmdStatus_t write_stat = SHCI_C2_FUS_FwUpgrade(src_addr, dst_addr); | ||||
|     FURI_LOG_I(TAG, "Cmd res = %x", write_stat); | ||||
|     if(write_stat == SHCI_Success) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     ble_glue_fus_get_status(); | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_get_status() { | ||||
|     furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); | ||||
|     SHCI_FUS_GetState_ErrorCode_t error_code = 0; | ||||
|     uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|     FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code); | ||||
|     if((error_code != 0) || (fus_state == FUS_STATE_VALUE_ERROR)) { | ||||
|         return BleGlueCommandResultError; | ||||
|     } else if( | ||||
|         (fus_state >= FUS_STATE_VALUE_FW_UPGRD_ONGOING) && | ||||
|         (fus_state <= FUS_STATE_VALUE_SERVICE_ONGOING_END)) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     return BleGlueCommandResultOK; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_wait_operation() { | ||||
|     furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); | ||||
|     bool wip; | ||||
|     do { | ||||
|         BleGlueCommandResult fus_status = ble_glue_fus_get_status(); | ||||
|         if(fus_status == BleGlueCommandResultError) { | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         wip = fus_status == BleGlueCommandResultOperationOngoing; | ||||
|         if(wip) { | ||||
|             osDelay(20); | ||||
|         } | ||||
|     } while(wip); | ||||
| 
 | ||||
|     return BleGlueCommandResultOK; | ||||
| } | ||||
| @ -2,12 +2,53 @@ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { | ||||
|     BleGlueC2ModeUnknown = 0, | ||||
|     BleGlueC2ModeFUS, | ||||
|     BleGlueC2ModeStack, | ||||
| } BleGlueC2Mode; | ||||
| 
 | ||||
| typedef struct { | ||||
|     BleGlueC2Mode mode; | ||||
|     /**
 | ||||
|      * Wireless Info | ||||
|      */ | ||||
|     uint8_t VersionMajor; | ||||
|     uint8_t VersionMinor; | ||||
|     uint8_t VersionSub; | ||||
|     uint8_t VersionBranch; | ||||
|     uint8_t VersionReleaseType; | ||||
|     uint8_t MemorySizeSram2B; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeSram2A; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeSram1; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeFlash; /*< Multiple of 4K */ | ||||
|     uint8_t StackType; | ||||
|     /**
 | ||||
|      * Fus Info | ||||
|      */ | ||||
|     uint8_t FusVersionMajor; | ||||
|     uint8_t FusVersionMinor; | ||||
|     uint8_t FusVersionSub; | ||||
|     uint8_t FusMemorySizeSram2B; /*< Multiple of 1K */ | ||||
|     uint8_t FusMemorySizeSram2A; /*< Multiple of 1K */ | ||||
|     uint8_t FusMemorySizeFlash; /*< Multiple of 4K */ | ||||
| } BleGlueC2Info; | ||||
| 
 | ||||
| typedef enum { | ||||
|     // Stage 1: core2 startup and FUS
 | ||||
|     BleGlueStatusStartup, | ||||
|     BleGlueStatusBroken, | ||||
|     BleGlueStatusC2Started, | ||||
|     // Stage 2: radio stack
 | ||||
|     BleGlueStatusRadioStackRunning, | ||||
|     BleGlueStatusRadioStackMissing | ||||
| } BleGlueStatus; | ||||
| 
 | ||||
| typedef void ( | ||||
|     *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); | ||||
| 
 | ||||
| @ -26,7 +67,15 @@ bool ble_glue_start(); | ||||
|  */ | ||||
| bool ble_glue_is_alive(); | ||||
| 
 | ||||
| bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); | ||||
| /** Waits for C2 to reports its mode to callback
 | ||||
|  * | ||||
|  * @return     true if it reported before reaching timeout | ||||
|  */ | ||||
| bool ble_glue_wait_for_c2_start(int32_t timeout); | ||||
| 
 | ||||
| BleGlueStatus ble_glue_get_c2_status(); | ||||
| 
 | ||||
| const BleGlueC2Info* ble_glue_get_c2_info(); | ||||
| 
 | ||||
| /** Is core2 radio stack present and ready
 | ||||
|  * | ||||
| @ -46,12 +95,30 @@ void ble_glue_set_key_storage_changed_callback( | ||||
| /** Stop SHCI thread */ | ||||
| void ble_glue_thread_stop(); | ||||
| 
 | ||||
| bool ble_glue_reinit_c2(); | ||||
| 
 | ||||
| typedef enum { | ||||
|     BleGlueCommandResultUnknown, | ||||
|     BleGlueCommandResultOK, | ||||
|     BleGlueCommandResultError, | ||||
|     BleGlueCommandResultRestartPending, | ||||
|     BleGlueCommandResultOperationOngoing, | ||||
| } BleGlueCommandResult; | ||||
| 
 | ||||
| /** Restart MCU to launch radio stack firmware if necessary
 | ||||
|  * | ||||
|  * @return      true on radio stack start command | ||||
|  */ | ||||
| bool ble_glue_radio_stack_fw_launch_started(); | ||||
| BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_delete(); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_get_status(); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_wait_operation(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| #endif | ||||
| @ -55,7 +55,6 @@ void furi_hal_init() { | ||||
|     FURI_LOG_I(TAG, "Speaker OK"); | ||||
| 
 | ||||
|     furi_hal_crypto_init(); | ||||
|     furi_hal_crc_init(true); | ||||
| 
 | ||||
|     // USB
 | ||||
| #ifndef FURI_RAM_EXEC | ||||
|  | ||||
| @ -16,6 +16,9 @@ | ||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR \ | ||||
|     { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } | ||||
| 
 | ||||
| /* Time, in ms, to wait for mode transition before crashing */ | ||||
| #define C2_MODE_SWITCH_TIMEOUT 10000 | ||||
| 
 | ||||
| osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||
| static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; | ||||
| 
 | ||||
| @ -99,7 +102,7 @@ void furi_hal_bt_unlock_core2() { | ||||
|     furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); | ||||
| } | ||||
| 
 | ||||
| static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) { | ||||
| static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { | ||||
|     bool supported = false; | ||||
|     if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { | ||||
|         furi_hal_bt_stack = FuriHalBtStackHciLayer; | ||||
| @ -128,21 +131,22 @@ bool furi_hal_bt_start_radio_stack() { | ||||
|     } | ||||
| 
 | ||||
|     do { | ||||
|         // Wait until FUS is started or timeout
 | ||||
|         WirelessFwInfo_t info = {}; | ||||
|         if(!ble_glue_wait_for_fus_start(&info)) { | ||||
|             FURI_LOG_E(TAG, "FUS start failed"); | ||||
|         // Wait until C2 is started or timeout
 | ||||
|         if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { | ||||
|             FURI_LOG_E(TAG, "Core2 start failed"); | ||||
|             LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|             ble_glue_thread_stop(); | ||||
|             break; | ||||
|         } | ||||
|         // If FUS is running, start radio stack fw
 | ||||
|         if(ble_glue_radio_stack_fw_launch_started()) { | ||||
|             // If FUS is running do nothing and wait for system reset
 | ||||
|             furi_crash("Waiting for FUS to launch radio stack firmware"); | ||||
| 
 | ||||
|         // If C2 is running, start radio stack fw
 | ||||
|         if(!furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)) { | ||||
|             break; | ||||
|         } | ||||
|         // Check weather we support radio stack
 | ||||
|         if(!furi_hal_bt_radio_stack_is_supported(&info)) { | ||||
| 
 | ||||
|         // Check whether we support radio stack
 | ||||
|         const BleGlueC2Info* c2_info = ble_glue_get_c2_info(); | ||||
|         if(!furi_hal_bt_radio_stack_is_supported(c2_info)) { | ||||
|             FURI_LOG_E(TAG, "Unsupported radio stack"); | ||||
|             // Don't stop SHCI for crypto enclave support
 | ||||
|             break; | ||||
| @ -232,7 +236,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, | ||||
|     ble_app_thread_stop(); | ||||
|     gap_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Reset SHCI"); | ||||
|     SHCI_C2_Reinit(); | ||||
|     ble_glue_reinit_c2(); | ||||
|     osDelay(100); | ||||
|     ble_glue_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Start BT initialization"); | ||||
| @ -404,3 +408,18 @@ void furi_hal_bt_stop_scan() { | ||||
|         gap_stop_scan(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { | ||||
|     BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode); | ||||
|     if(fw_start_res == BleGlueCommandResultOK) { | ||||
|         return true; | ||||
|     } else if(fw_start_res == BleGlueCommandResultRestartPending) { | ||||
|         // Do nothing and wait for system reset
 | ||||
|         osDelay(C2_MODE_SWITCH_TIMEOUT); | ||||
|         furi_crash("Waiting for FUS->radio stack transition"); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| @ -34,6 +34,7 @@ void furi_hal_crc_init(bool synchronize) { | ||||
| void furi_hal_crc_reset() { | ||||
|     furi_check(hal_crc_control.state == CRC_State_Ready); | ||||
|     if(hal_crc_control.mtx) { | ||||
|         furi_check(osMutexGetOwner(hal_crc_control.mtx) == osThreadGetId()); | ||||
|         osMutexRelease(hal_crc_control.mtx); | ||||
|     } | ||||
|     LL_CRC_ResetCRCCalculationUnit(CRC); | ||||
| @ -84,5 +85,9 @@ uint32_t furi_hal_crc_feed(void* data, uint16_t length) { | ||||
| 
 | ||||
| bool furi_hal_crc_acquire(uint32_t timeout) { | ||||
|     furi_assert(hal_crc_control.mtx); | ||||
|     return osMutexAcquire(hal_crc_control.mtx, timeout) == osOK; | ||||
|     if(osMutexAcquire(hal_crc_control.mtx, timeout) == osOK) { | ||||
|         LL_CRC_ResetCRCCalculationUnit(CRC); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,8 @@ | ||||
| 
 | ||||
| #include <stm32wbxx.h> | ||||
| 
 | ||||
| #define FURI_HAL_TAG "FuriHalFlash" | ||||
| #define TAG "FuriHalFlash" | ||||
| 
 | ||||
| #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" | ||||
| #define FURI_HAL_FLASH_READ_BLOCK 8 | ||||
| #define FURI_HAL_FLASH_WRITE_BLOCK 8 | ||||
| @ -14,13 +15,17 @@ | ||||
| #define FURI_HAL_FLASH_CYCLES_COUNT 10000 | ||||
| #define FURI_HAL_FLASH_TIMEOUT 1000 | ||||
| #define FURI_HAL_FLASH_KEY1 0x45670123U | ||||
| 
 | ||||
| #define FURI_HAL_FLASH_KEY2 0xCDEF89ABU | ||||
| #define FURI_HAL_FLASH_TOTAL_PAGES 256 | ||||
| #define FURI_HAL_FLASH_SR_ERRORS                                                               \ | ||||
|     (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ | ||||
|      FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR) | ||||
| 
 | ||||
| //#define FURI_HAL_FLASH_OB_START_ADDRESS 0x1FFF8000
 | ||||
| #define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B | ||||
| #define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F | ||||
| #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) | ||||
| 
 | ||||
| #define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) | ||||
| #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__)                                             \ | ||||
|     (((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \ | ||||
| @ -88,7 +93,7 @@ static void furi_hal_flash_unlock() { | ||||
|     WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1); | ||||
|     WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2); | ||||
| 
 | ||||
|     /* verify Flash is unlock */ | ||||
|     /* verify Flash is unlocked */ | ||||
|     furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U); | ||||
| } | ||||
| 
 | ||||
| @ -386,4 +391,125 @@ int16_t furi_hal_flash_get_page_number(size_t address) { | ||||
|     } | ||||
| 
 | ||||
|     return (address - flash_base) / FURI_HAL_FLASH_PAGE_SIZE; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 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 <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
 | ||||
|  */ | ||||
| void furi_hal_flash_init(); | ||||
| @ -64,7 +83,7 @@ size_t furi_hal_flash_get_free_page_count(); | ||||
| 
 | ||||
| /** Erase Flash
 | ||||
|  * | ||||
|  * @warning    locking operation with critical section, stales execution | ||||
|  * @warning    locking operation with critical section, stalls execution | ||||
|  * | ||||
|  * @param      page  The page to erase | ||||
|  * | ||||
| @ -74,7 +93,7 @@ bool furi_hal_flash_erase(uint8_t page); | ||||
| 
 | ||||
| /** Write double word (64 bits)
 | ||||
|  * | ||||
|  * @warning locking operation with critical section, stales execution | ||||
|  * @warning locking operation with critical section, stalls execution | ||||
|  * | ||||
|  * @param      address  destination address, must be double word aligned. | ||||
|  * @param      data     data to write | ||||
| @ -85,7 +104,7 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data); | ||||
| 
 | ||||
| /** Write aligned page data (up to page size)
 | ||||
|  * | ||||
|  * @warning locking operation with critical section, stales execution | ||||
|  * @warning locking operation with critical section, stalls execution | ||||
|  * | ||||
|  * @param      address  destination address, must be page aligned. | ||||
|  * @param      data     data to write | ||||
| @ -99,5 +118,27 @@ bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 | ||||
|  * | ||||
|  * @return     page number, -1 for invalid address | ||||
|  */ | ||||
| int16_t furi_hal_flash_get_page_number(size_t address); | ||||
| 
 | ||||
| int16_t furi_hal_flash_get_page_number(size_t address); | ||||
| /** 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); | ||||
|     } | ||||
| 
 | ||||
|     WirelessFwInfo_t pWirelessInfo; | ||||
|     if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) { | ||||
|     if(furi_hal_bt_is_alive()) { | ||||
|         const BleGlueC2Info* ble_c2_info = ble_glue_get_c2_info(); | ||||
|         out("radio_alive", "true", false, context); | ||||
|         out("radio_mode", ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack", false, context); | ||||
| 
 | ||||
|         // FUS Info
 | ||||
|         string_printf(value, "%d", pWirelessInfo.FusVersionMajor); | ||||
|         string_printf(value, "%d", ble_c2_info->FusVersionMajor); | ||||
|         out("radio_fus_major", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.FusVersionMinor); | ||||
|         string_printf(value, "%d", ble_c2_info->FusVersionMinor); | ||||
|         out("radio_fus_minor", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.FusVersionSub); | ||||
|         string_printf(value, "%d", ble_c2_info->FusVersionSub); | ||||
|         out("radio_fus_sub", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.FusMemorySizeSram2B); | ||||
|         string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2B); | ||||
|         out("radio_fus_sram2b", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.FusMemorySizeSram2A); | ||||
|         string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2A); | ||||
|         out("radio_fus_sram2a", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.FusMemorySizeFlash * 4); | ||||
|         string_printf(value, "%dK", ble_c2_info->FusMemorySizeFlash * 4); | ||||
|         out("radio_fus_flash", string_get_cstr(value), false, context); | ||||
| 
 | ||||
|         // Stack Info
 | ||||
|         string_printf(value, "%d", pWirelessInfo.StackType); | ||||
|         string_printf(value, "%d", ble_c2_info->StackType); | ||||
|         out("radio_stack_type", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.VersionMajor); | ||||
|         string_printf(value, "%d", ble_c2_info->VersionMajor); | ||||
|         out("radio_stack_major", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.VersionMinor); | ||||
|         string_printf(value, "%d", ble_c2_info->VersionMinor); | ||||
|         out("radio_stack_minor", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.VersionSub); | ||||
|         string_printf(value, "%d", ble_c2_info->VersionSub); | ||||
|         out("radio_stack_sub", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.VersionBranch); | ||||
|         string_printf(value, "%d", ble_c2_info->VersionBranch); | ||||
|         out("radio_stack_branch", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%d", pWirelessInfo.VersionReleaseType); | ||||
|         string_printf(value, "%d", ble_c2_info->VersionReleaseType); | ||||
|         out("radio_stack_release", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.MemorySizeSram2B); | ||||
|         string_printf(value, "%dK", ble_c2_info->MemorySizeSram2B); | ||||
|         out("radio_stack_sram2b", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.MemorySizeSram2A); | ||||
|         string_printf(value, "%dK", ble_c2_info->MemorySizeSram2A); | ||||
|         out("radio_stack_sram2a", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.MemorySizeSram1); | ||||
|         string_printf(value, "%dK", ble_c2_info->MemorySizeSram1); | ||||
|         out("radio_stack_sram1", string_get_cstr(value), false, context); | ||||
|         string_printf(value, "%dK", pWirelessInfo.MemorySizeFlash * 4); | ||||
|         string_printf(value, "%dK", ble_c2_info->MemorySizeFlash * 4); | ||||
|         out("radio_stack_flash", string_get_cstr(value), false, context); | ||||
| 
 | ||||
|         // Mac address
 | ||||
|  | ||||
| @ -64,7 +64,7 @@ void furi_hal_vcp_init() { | ||||
|     vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); | ||||
| 
 | ||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||
|         FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode="); | ||||
|         FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -39,7 +39,6 @@ template <unsigned int N> struct STOP_EXTERNING_ME {}; | ||||
| #include "furi_hal_uart.h" | ||||
| #include "furi_hal_info.h" | ||||
| #include "furi_hal_random.h" | ||||
| #include "furi_hal_crc.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
|  | ||||
| @ -16,6 +16,7 @@ | ||||
| 
 | ||||
| #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) | ||||
| #define FURI_HAL_BT_STACK_VERSION_MINOR (13) | ||||
| #define FURI_HAL_BT_C2_START_TIMEOUT 1000 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -207,6 +208,12 @@ bool furi_hal_bt_start_scan(GapScanCallback callback, void* context); | ||||
| /** Stop MAC addresses scan */ | ||||
| void furi_hal_bt_stop_scan(); | ||||
| 
 | ||||
| /** Check & switch C2 to given mode
 | ||||
|  * | ||||
|  * @param[in]  mode  mode to switch into | ||||
|  */ | ||||
| bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -28,6 +28,7 @@ typedef enum { | ||||
|     FuriHalRtcFlagDebug = (1 << 0), | ||||
|     FuriHalRtcFlagFactoryReset = (1 << 1), | ||||
|     FuriHalRtcFlagLock = (1 << 2), | ||||
|     FuriHalRtcFlagC2Update = (1 << 3), | ||||
| } FuriHalRtcFlag; | ||||
| 
 | ||||
| typedef enum { | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| Subproject commit 528461f8276f06783d46461bfb31d77aa8bac419 | ||||
| Subproject commit a9e29b431f6dac95b6fc860a717834f35b7f78e5 | ||||
| @ -97,7 +97,8 @@ CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.cpp) | ||||
| 
 | ||||
| # USB Stack
 | ||||
| CFLAGS			+= -I$(LIB_DIR)/libusb_stm32/inc | ||||
| C_SOURCES		+= $(wildcard $(LIB_DIR)/libusb_stm32/src/*.c) | ||||
| C_SOURCES		+= $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c | ||||
| C_SOURCES		+= $(LIB_DIR)/libusb_stm32/src/usbd_core.c | ||||
| 
 | ||||
| # protobuf
 | ||||
| CFLAGS			+= -I$(LIB_DIR)/nanopb | ||||
|  | ||||
							
								
								
									
										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 <furi_hal.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| #define VALID_WHOLE_FILE_CRC 0xFFFFFFFF | ||||
| #define DFU_SUFFIX_VERSION 0x011A | ||||
| #define DFU_DATA_BUFFER_MAX_LEN 512 | ||||
| #define DFU_SIGNATURE "DfuSe" | ||||
| 
 | ||||
| bool dfu_file_validate_crc(File* dfuf, const DfuPageTaskProgressCb progress_cb, void* context) { | ||||
|     if(!storage_file_is_open(dfuf) || !storage_file_seek(dfuf, 0, true)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_crc_reset(); | ||||
| 
 | ||||
|     uint32_t file_crc = 0; | ||||
| 
 | ||||
|     uint8_t* data_buffer = malloc(DFU_DATA_BUFFER_MAX_LEN); | ||||
|     uint16_t data_buffer_valid_len; | ||||
| 
 | ||||
|     uint32_t file_size = storage_file_size(dfuf); | ||||
| 
 | ||||
|     /* Feed file contents per sector into CRC calc */ | ||||
|     furi_hal_crc_acquire(osWaitForever); | ||||
|     for(uint32_t fptr = 0; fptr < file_size;) { | ||||
|         data_buffer_valid_len = storage_file_read(dfuf, data_buffer, DFU_DATA_BUFFER_MAX_LEN); | ||||
|         if(data_buffer_valid_len == 0) { | ||||
|             break; | ||||
|         } | ||||
|         fptr += data_buffer_valid_len; | ||||
| 
 | ||||
|         if((fptr % DFU_DATA_BUFFER_MAX_LEN == 0)) { | ||||
|             progress_cb(fptr * 100 / file_size, context); | ||||
|         } | ||||
| 
 | ||||
|         file_crc = furi_hal_crc_feed(data_buffer, data_buffer_valid_len); | ||||
|     } | ||||
|     furi_hal_crc_reset(); | ||||
|     free(data_buffer); | ||||
|     uint32_t file_crc = crc32_calc_file(dfuf, progress_cb, context); | ||||
| 
 | ||||
|     /* Last 4 bytes of DFU file = CRC of previous file contents, inverted
 | ||||
|      * If we calculate whole file CRC32, incl. embedded CRC, | ||||
|  | ||||
| @ -14,6 +14,9 @@ | ||||
| #define MANIFEST_KEY_RADIO_VERSION "Radio version" | ||||
| #define MANIFEST_KEY_RADIO_CRC "Radio CRC" | ||||
| #define MANIFEST_KEY_ASSETS_FILE "Resources" | ||||
| #define MANIFEST_KEY_OB_REFERENCE "OB reference" | ||||
| #define MANIFEST_KEY_OB_MASK "OB mask" | ||||
| #define MANIFEST_KEY_OB_WRITE_MASK "OB write mask" | ||||
| 
 | ||||
| UpdateManifest* update_manifest_alloc() { | ||||
|     UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); | ||||
| @ -23,6 +26,9 @@ UpdateManifest* update_manifest_alloc() { | ||||
|     string_init(update_manifest->staged_loader_file); | ||||
|     string_init(update_manifest->resource_bundle); | ||||
|     update_manifest->target = 0; | ||||
|     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
|     memset(update_manifest->ob_compare_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
|     memset(update_manifest->ob_write_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
|     update_manifest->valid = false; | ||||
|     return update_manifest; | ||||
| } | ||||
| @ -75,8 +81,8 @@ static bool | ||||
|         flipper_format_read_hex( | ||||
|             flipper_file, | ||||
|             MANIFEST_KEY_RADIO_VERSION, | ||||
|             (uint8_t*)&update_manifest->radio_version, | ||||
|             sizeof(uint32_t)); | ||||
|             update_manifest->radio_version.raw, | ||||
|             sizeof(UpdateManifestRadioVersion)); | ||||
|         flipper_format_read_hex( | ||||
|             flipper_file, | ||||
|             MANIFEST_KEY_RADIO_CRC, | ||||
| @ -85,6 +91,22 @@ static bool | ||||
|         flipper_format_read_string( | ||||
|             flipper_file, MANIFEST_KEY_ASSETS_FILE, update_manifest->resource_bundle); | ||||
| 
 | ||||
|         flipper_format_read_hex( | ||||
|             flipper_file, | ||||
|             MANIFEST_KEY_OB_REFERENCE, | ||||
|             update_manifest->ob_reference.bytes, | ||||
|             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
|         flipper_format_read_hex( | ||||
|             flipper_file, | ||||
|             MANIFEST_KEY_OB_MASK, | ||||
|             update_manifest->ob_compare_mask.bytes, | ||||
|             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
|         flipper_format_read_hex( | ||||
|             flipper_file, | ||||
|             MANIFEST_KEY_OB_WRITE_MASK, | ||||
|             update_manifest->ob_write_mask.bytes, | ||||
|             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
| 
 | ||||
|         update_manifest->valid = | ||||
|             (!string_empty_p(update_manifest->firmware_dfu_image) || | ||||
|              !string_empty_p(update_manifest->radio_image) || | ||||
| @ -94,6 +116,41 @@ static bool | ||||
|     return update_manifest->valid; | ||||
| } | ||||
| 
 | ||||
| // Verifies that mask values are same for adjacent words (value & inverted)
 | ||||
| static bool ob_data_check_mask_valid(const FuriHalFlashRawOptionByteData* mask) { | ||||
|     bool mask_valid = true; | ||||
|     for(size_t idx = 0; mask_valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) { | ||||
|         mask_valid &= mask->obs[idx].values.base == mask->obs[idx].values.complementary_value; | ||||
|     } | ||||
|     return mask_valid; | ||||
| } | ||||
| 
 | ||||
| // Verifies that all reference values have no unmasked bits
 | ||||
| static bool ob_data_check_masked_values_valid( | ||||
|     const FuriHalFlashRawOptionByteData* data, | ||||
|     const FuriHalFlashRawOptionByteData* mask) { | ||||
|     bool valid = true; | ||||
|     for(size_t idx = 0; valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) { | ||||
|         valid &= (data->obs[idx]. dword & mask->obs[idx].dword) == | ||||
|                  data->obs[idx].dword; | ||||
|     } | ||||
|     return valid; | ||||
| } | ||||
| 
 | ||||
| bool update_manifest_has_obdata(UpdateManifest* update_manifest) { | ||||
|     bool ob_data_valid = false; | ||||
|     // do we have at least 1 value?
 | ||||
|     for(size_t idx = 0; !ob_data_valid && (idx < FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); ++idx) { | ||||
|         ob_data_valid |= update_manifest->ob_reference.bytes[idx] != 0; | ||||
|     } | ||||
|     // sanity checks
 | ||||
|     ob_data_valid &= ob_data_check_mask_valid(&update_manifest->ob_write_mask); | ||||
|     ob_data_valid &= ob_data_check_mask_valid(&update_manifest->ob_compare_mask); | ||||
|     ob_data_valid &= ob_data_check_masked_values_valid( | ||||
|         &update_manifest->ob_reference, &update_manifest->ob_compare_mask); | ||||
|     return ob_data_valid; | ||||
| } | ||||
| 
 | ||||
| bool update_manifest_init(UpdateManifest* update_manifest, const char* manifest_filename) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     FlipperFormat* flipper_file = flipper_format_file_alloc(storage); | ||||
|  | ||||
| @ -7,12 +7,26 @@ extern "C" { | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <m-string.h> | ||||
| #include <furi_hal_flash.h> | ||||
| 
 | ||||
| /* Paths don't include /ext -- because at startup SD card is mounted as root */ | ||||
| #define UPDATE_DIR_DEFAULT_REL_PATH "/update" | ||||
| #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf" | ||||
| #define UPDATE_MAINFEST_DEFAULT_PATH UPDATE_DIR_DEFAULT_REL_PATH "/" UPDATE_MANIFEST_DEFAULT_NAME | ||||
| 
 | ||||
| typedef union { | ||||
|     uint8_t raw[6]; | ||||
|     struct { | ||||
|         uint8_t major; | ||||
|         uint8_t minor; | ||||
|         uint8_t sub; | ||||
|         uint8_t branch; | ||||
|         uint8_t release; | ||||
|         uint8_t type; | ||||
|     } version; | ||||
| } UpdateManifestRadioVersion; | ||||
| _Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVersion size error"); | ||||
| 
 | ||||
| typedef struct { | ||||
|     string_t version; | ||||
|     uint32_t target; | ||||
| @ -21,9 +35,12 @@ typedef struct { | ||||
|     string_t firmware_dfu_image; | ||||
|     string_t radio_image; | ||||
|     uint32_t radio_address; | ||||
|     uint32_t radio_version; | ||||
|     UpdateManifestRadioVersion radio_version; | ||||
|     uint32_t radio_crc; | ||||
|     string_t resource_bundle; | ||||
|     FuriHalFlashRawOptionByteData ob_reference; | ||||
|     FuriHalFlashRawOptionByteData ob_compare_mask; | ||||
|     FuriHalFlashRawOptionByteData ob_write_mask; | ||||
|     bool valid; | ||||
| } UpdateManifest; | ||||
| 
 | ||||
| @ -38,6 +55,8 @@ bool update_manifest_init_mem( | ||||
|     const uint8_t* manifest_data, | ||||
|     const uint16_t length); | ||||
| 
 | ||||
| bool update_manifest_has_obdata(UpdateManifest* update_manifest); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -7,6 +7,7 @@ | ||||
| #include <m-string.h> | ||||
| #include <loader/loader.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_PREFIX = "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"; | ||||
| @ -156,8 +157,6 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { | ||||
|         path_extract_dirname(manifest_file_path, stage_path); | ||||
|         path_append(stage_path, string_get_cstr(manifest->staged_loader_file)); | ||||
| 
 | ||||
|         const uint16_t READ_BLOCK = 0x1000; | ||||
|         uint8_t* read_buffer = malloc(READ_BLOCK); | ||||
|         uint32_t crc = 0; | ||||
|         do { | ||||
|             if(!storage_file_open( | ||||
| @ -166,19 +165,10 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { | ||||
|             } | ||||
| 
 | ||||
|             result = UpdatePrepareResultStageIntegrityError; | ||||
|             furi_hal_crc_acquire(osWaitForever); | ||||
| 
 | ||||
|             uint16_t bytes_read = 0; | ||||
|             do { | ||||
|                 bytes_read = storage_file_read(file, read_buffer, READ_BLOCK); | ||||
|                 crc = furi_hal_crc_feed(read_buffer, bytes_read); | ||||
|             } while(bytes_read == READ_BLOCK); | ||||
| 
 | ||||
|             furi_hal_crc_reset(); | ||||
|             crc = crc32_calc_file(file, NULL, NULL); | ||||
|         } while(false); | ||||
| 
 | ||||
|         string_clear(stage_path); | ||||
|         free(read_buffer); | ||||
|         storage_file_free(file); | ||||
| 
 | ||||
|         if(crc == manifest->staged_loader_crc) { | ||||
|  | ||||
| @ -50,6 +50,26 @@ class Main(App): | ||||
|         self.parser_copro.add_argument("cube_dir", help="Path to Cube folder") | ||||
|         self.parser_copro.add_argument("output_dir", help="Path to output folder") | ||||
|         self.parser_copro.add_argument("mcu", help="MCU series as in copro folder") | ||||
|         self.parser_copro.add_argument( | ||||
|             "--cube_ver", dest="cube_ver", help="Cube version", required=True | ||||
|         ) | ||||
|         self.parser_copro.add_argument( | ||||
|             "--stack_type", dest="stack_type", help="Stack type", required=True | ||||
|         ) | ||||
|         self.parser_copro.add_argument( | ||||
|             "--stack_file", | ||||
|             dest="stack_file", | ||||
|             help="Stack file name in copro folder", | ||||
|             required=True, | ||||
|         ) | ||||
|         self.parser_copro.add_argument( | ||||
|             "--stack_addr", | ||||
|             dest="stack_addr", | ||||
|             help="Stack flash address, as per release_notes", | ||||
|             type=lambda x: int(x, 16), | ||||
|             default=0, | ||||
|             required=False, | ||||
|         ) | ||||
|         self.parser_copro.set_defaults(func=self.copro) | ||||
| 
 | ||||
|         self.parser_dolphin = self.subparsers.add_parser( | ||||
| @ -203,13 +223,15 @@ class Main(App): | ||||
|         manifest_file = os.path.join(directory_path, "Manifest") | ||||
|         old_manifest = Manifest() | ||||
|         if os.path.exists(manifest_file): | ||||
|             self.logger.info("old manifest is present, loading for compare") | ||||
|             self.logger.info("Manifest is present, loading to compare") | ||||
|             old_manifest.load(manifest_file) | ||||
|         self.logger.info(f'Creating new Manifest for directory "{directory_path}"') | ||||
|         self.logger.info( | ||||
|             f'Creating temporary Manifest for directory "{directory_path}"' | ||||
|         ) | ||||
|         new_manifest = Manifest() | ||||
|         new_manifest.create(directory_path) | ||||
| 
 | ||||
|         self.logger.info(f"Comparing new manifest with old") | ||||
|         self.logger.info(f"Comparing new manifest with existing") | ||||
|         only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) | ||||
|         for record in only_in_old: | ||||
|             self.logger.info(f"Only in old: {record}") | ||||
| @ -233,9 +255,14 @@ class Main(App): | ||||
|         self.logger.info(f"Bundling coprocessor binaries") | ||||
|         copro = Copro(self.args.mcu) | ||||
|         self.logger.info(f"Loading CUBE info") | ||||
|         copro.loadCubeInfo(self.args.cube_dir) | ||||
|         copro.loadCubeInfo(self.args.cube_dir, self.args.cube_ver) | ||||
|         self.logger.info(f"Bundling") | ||||
|         copro.bundle(self.args.output_dir) | ||||
|         copro.bundle( | ||||
|             self.args.output_dir, | ||||
|             self.args.stack_file, | ||||
|             self.args.stack_type, | ||||
|             self.args.stack_addr, | ||||
|         ) | ||||
|         self.logger.info(f"Complete") | ||||
| 
 | ||||
|         return 0 | ||||
|  | ||||
| @ -91,6 +91,7 @@ class Main(App): | ||||
|                         self.args.resources, | ||||
|                     ) | ||||
|                 ) | ||||
|             bundle_args.extend(self.other_args) | ||||
|             self.logger.info( | ||||
|                 f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" | ||||
|             ) | ||||
|  | ||||
| @ -7,6 +7,7 @@ import os | ||||
| 
 | ||||
| from flipper.app import App | ||||
| from flipper.cube import CubeProgrammer | ||||
| from flipper.assets.coprobin import CoproBinary | ||||
| 
 | ||||
| STATEMENT = "AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE" | ||||
| 
 | ||||
| @ -68,10 +69,15 @@ class Main(App): | ||||
|         ) | ||||
|         self._addArgsSWD(self.parser_core2radio) | ||||
|         self.parser_core2radio.add_argument( | ||||
|             "radio_address", type=str, help="Radio Stack Binary Address" | ||||
|             "radio", type=str, help="Radio Stack Binary" | ||||
|         ) | ||||
|         self.parser_core2radio.add_argument( | ||||
|             "radio", type=str, help="Radio Stack Binary" | ||||
|             "--addr", | ||||
|             dest="radio_address", | ||||
|             help="Radio Stack Binary Address, as per release_notes", | ||||
|             type=lambda x: int(x, 16), | ||||
|             default=0, | ||||
|             required=False, | ||||
|         ) | ||||
|         self.parser_core2radio.set_defaults(func=self.core2radio) | ||||
| 
 | ||||
| @ -144,14 +150,27 @@ class Main(App): | ||||
|         return 0 | ||||
| 
 | ||||
|     def core2radio(self): | ||||
|         if int(self.args.radio_address, 16) > 0x080E0000: | ||||
|         stack_info = CoproBinary(self.args.radio) | ||||
|         if not stack_info.is_stack(): | ||||
|             self.logger.error("Not a Radio Stack") | ||||
|             return 1 | ||||
|         self.logger.info(f"Will flash {stack_info.img_sig.get_version()}") | ||||
| 
 | ||||
|         radio_address = self.args.radio_address | ||||
|         if not radio_address: | ||||
|             radio_address = stack_info.get_flash_load_addr() | ||||
|             self.logger.warning( | ||||
|                 f"Radio address not provided, guessed as 0x{radio_address:X}" | ||||
|             ) | ||||
|         if radio_address > 0x080E0000: | ||||
|             self.logger.error(f"I KNOW WHAT YOU DID LAST SUMMER") | ||||
|             return 1 | ||||
| 
 | ||||
|         cp = CubeProgrammer(self._getCubeParams()) | ||||
|         self.logger.info(f"Removing Current Radio Stack") | ||||
|         cp.deleteCore2RadioStack() | ||||
|         self.logger.info(f"Flashing Radio Stack") | ||||
|         cp.flashCore2(self.args.radio_address, self.args.radio) | ||||
|         cp.flashCore2(radio_address, self.args.radio) | ||||
|         self.logger.info(f"Complete") | ||||
|         return 0 | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ class App: | ||||
|         self.init() | ||||
| 
 | ||||
|     def __call__(self, args=None): | ||||
|         self.args, _ = self.parser.parse_known_args(args=args) | ||||
|         self.args, self.other_args = self.parser.parse_known_args(args=args) | ||||
|         # configure log output | ||||
|         self.log_level = logging.DEBUG if self.args.debug else logging.INFO | ||||
|         self.logger.setLevel(self.log_level) | ||||
|  | ||||
| @ -2,9 +2,12 @@ import logging | ||||
| import datetime | ||||
| import shutil | ||||
| import json | ||||
| from os.path import basename | ||||
| 
 | ||||
| import xml.etree.ElementTree as ET | ||||
| from flipper.utils import * | ||||
| from flipper.assets.coprobin import CoproBinary, get_stack_type | ||||
| 
 | ||||
| 
 | ||||
| CUBE_COPRO_PATH = "Projects/STM32WB_Copro_Wireless_Binaries" | ||||
| 
 | ||||
| @ -13,14 +16,7 @@ MANIFEST_TEMPLATE = { | ||||
|     "copro": { | ||||
|         "fus": {"version": {"major": 1, "minor": 2, "sub": 0}, "files": []}, | ||||
|         "radio": { | ||||
|             "version": { | ||||
|                 "type": 3, | ||||
|                 "major": 1, | ||||
|                 "minor": 13, | ||||
|                 "sub": 0, | ||||
|                 "branch": 0, | ||||
|                 "release": 5, | ||||
|             }, | ||||
|             "version": {}, | ||||
|             "files": [], | ||||
|         }, | ||||
|     }, | ||||
| @ -35,7 +31,7 @@ class Copro: | ||||
|         self.mcu_copro = None | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
| 
 | ||||
|     def loadCubeInfo(self, cube_dir): | ||||
|     def loadCubeInfo(self, cube_dir, cube_version): | ||||
|         if not os.path.isdir(cube_dir): | ||||
|             raise Exception(f'"{cube_dir}" doesn\'t exists') | ||||
|         self.cube_dir = cube_dir | ||||
| @ -51,8 +47,8 @@ class Copro: | ||||
|         if not cube_version or not cube_version.startswith("FW.WB"): | ||||
|             raise Exception(f"Incorrect Cube package or version info") | ||||
|         cube_version = cube_version.replace("FW.WB.", "", 1) | ||||
|         if cube_version != "1.13.1": | ||||
|             raise Exception(f"Unknonwn cube version") | ||||
|         if cube_version != cube_version: | ||||
|             raise Exception(f"Unsupported cube version") | ||||
|         self.version = cube_version | ||||
| 
 | ||||
|     def addFile(self, array, filename, **kwargs): | ||||
| @ -63,14 +59,32 @@ class Copro: | ||||
|             {"name": filename, "sha256": file_sha256(destination_file), **kwargs} | ||||
|         ) | ||||
| 
 | ||||
|     def bundle(self, output_dir): | ||||
|     def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None): | ||||
|         if not os.path.isdir(output_dir): | ||||
|             raise Exception(f'"{output_dir}" doesn\'t exists') | ||||
|         self.output_dir = output_dir | ||||
|         stack_file = os.path.join(self.mcu_copro, stack_file_name) | ||||
|         manifest_file = os.path.join(self.output_dir, "Manifest.json") | ||||
|         # Form Manifest | ||||
|         manifest = dict(MANIFEST_TEMPLATE) | ||||
|         manifest["manifest"]["timestamp"] = timestamp() | ||||
|         copro_bin = CoproBinary(stack_file) | ||||
|         self.logger.info(f"Bundling {copro_bin.img_sig.get_version()}") | ||||
|         stack_type_code = get_stack_type(stack_type) | ||||
|         manifest["copro"]["radio"]["version"].update( | ||||
|             { | ||||
|                 "type": stack_type_code, | ||||
|                 "major": copro_bin.img_sig.version_major, | ||||
|                 "minor": copro_bin.img_sig.version_minor, | ||||
|                 "sub": copro_bin.img_sig.version_sub, | ||||
|                 "branch": copro_bin.img_sig.version_branch, | ||||
|                 "release": copro_bin.img_sig.version_build, | ||||
|             } | ||||
|         ) | ||||
|         if not stack_addr: | ||||
|             stack_addr = copro_bin.get_flash_load_addr() | ||||
|             self.logger.info(f"Using guessed flash address 0x{stack_addr:x}") | ||||
| 
 | ||||
|         # Old FUS Update | ||||
|         self.addFile( | ||||
|             manifest["copro"]["fus"]["files"], | ||||
| @ -88,8 +102,8 @@ class Copro: | ||||
|         # BLE Full Stack | ||||
|         self.addFile( | ||||
|             manifest["copro"]["radio"]["files"], | ||||
|             "stm32wb5x_BLE_Stack_light_fw.bin", | ||||
|             address="0x080D7000", | ||||
|             stack_file_name, | ||||
|             address=f"0x{stack_addr:X}", | ||||
|         ) | ||||
|         # Save manifest to | ||||
|         json.dump(manifest, open(manifest_file, "w")) | ||||
|  | ||||
							
								
								
									
										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.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 | ||||
| import os | ||||
| import shutil | ||||
| import zlib | ||||
| import tarfile | ||||
| import math | ||||
| 
 | ||||
| 
 | ||||
| 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("-v", dest="version", 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("--stage", dest="stage", required=True) | ||||
|         self.parser_generate.add_argument( | ||||
|             "--radio", dest="radiobin", default="", required=False | ||||
|         ) | ||||
|         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( | ||||
|             "--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) | ||||
| 
 | ||||
|     def generate(self): | ||||
| @ -49,11 +61,27 @@ class Main(App): | ||||
|         radiobin_basename = basename(self.args.radiobin) | ||||
|         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): | ||||
|             os.makedirs(self.args.directory) | ||||
| 
 | ||||
|         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: | ||||
|             shutil.copyfile( | ||||
|                 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("Firmware", dfu_basename) | ||||
|         file.writeKey("Radio", radiobin_basename or "") | ||||
|         file.writeKey("Radio address", self.int2ffhex(self.args.radioaddr or 0)) | ||||
|         file.writeKey("Radio version", self.int2ffhex(self.args.radioversion or 0)) | ||||
|         file.writeKey("Radio address", self.int2ffhex(radio_addr)) | ||||
|         file.writeKey("Radio version", self.int2ffhex(radio_version)) | ||||
|         if radiobin_basename: | ||||
|             file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin))) | ||||
|         else: | ||||
|             file.writeKey("Radio CRC", self.int2ffhex(0)) | ||||
|         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)) | ||||
| 
 | ||||
|         return 0 | ||||
| @ -90,9 +127,34 @@ class Main(App): | ||||
|         ) as tarball: | ||||
|             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 | ||||
|     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]) | ||||
| 
 | ||||
|     @staticmethod | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger