[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:
hedger 2022-04-27 18:53:48 +03:00 committed by GitHub
parent 81aeda86db
commit 7ce305fca3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1622 additions and 295 deletions

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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");

View file

@ -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))

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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);

View 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
View file

@ -1 +1,2 @@
/headers
/core2_firmware

View file

@ -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
View 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)

View file

@ -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)) {

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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

View file

@ -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;
}

View file

@ -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" {

View file

@ -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

View file

@ -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

View file

@ -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
View 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
View 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

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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}"
)

View file

@ -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

View file

@ -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)

View file

@ -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"))

View 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()

View 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()

View file

@ -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