[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:
@@ -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 {
|
||||
|
Reference in New Issue
Block a user