mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-23 05:14:16 +03:00
[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…
Reference in New Issue
Block a user