Просмотр исходного кода

[FL-2580] FuriHal: add more supported radio stacks (#1301)

* FuriHal: add more supported radio stacks
* Bt: correct ble stack enum value
* Bt: update cli testing commands implementation
* Scripts: always emitting ob data to update manifest; added ob_custradio.data for non-light radio stacks
* Scripts: added stack type whitelist & disclaimer message
* ble: remove scanner
* ble: remove HCI and advances ble stacks support
* bt: correctly close RPC session before bt reinit
* Scripts: update bundler: estimating flash layout & refusing to build dangerous packages; app frame: not adding redundant log handlers
* Docs: additional details on bundling updates; fixed updater error codes
* Docs: wording fixes for OTA.md

Co-authored-by: hedger <hedger@nanode.su>
Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: SG <who.just.the.doctor@gmail.com>
あく 3 лет назад
Родитель
Сommit
936a2f64b2

+ 2 - 1
Makefile

@@ -102,7 +102,8 @@ updater_package: firmware_all updater assets_manifest
 	--bundlever "$(VERSION_STRING)" \
 	--radio $(COPRO_STACK_BIN_PATH) \
 	--radiotype $(COPRO_STACK_TYPE) \
-	--obdata $(PROJECT_ROOT)/scripts/ob.data
+	$(COPRO_DISCLAIMER) \
+	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA)
 
 .PHONY: assets_manifest
 assets_manifest:

+ 34 - 56
applications/bt/bt_cli.c

@@ -3,14 +3,9 @@
 #include <applications/cli/cli.h>
 #include <lib/toolbox/args.h>
 
+#include "ble.h"
 #include "bt_settings.h"
-
-static const char* bt_cli_address_types[] = {
-    "Public Device Address",
-    "Random Device Address",
-    "Public Identity Address",
-    "Random (Static) Identity Address",
-};
+#include "bt_service/bt.h"
 
 static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) {
     UNUSED(cli);
@@ -38,7 +33,9 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
             break;
         }
 
-        furi_hal_bt_stop_advertising();
+        Bt* bt = furi_record_open("bt");
+        bt_disconnect(bt);
+        furi_hal_bt_reinit();
         printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power);
         printf("Press CTRL+C to stop\r\n");
         furi_hal_bt_start_tone_tx(channel, 0x19 + power);
@@ -47,6 +44,9 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
             osDelay(250);
         }
         furi_hal_bt_stop_tone_tx();
+
+        bt_set_profile(bt, BtProfileSerial);
+        furi_record_close("bt");
     } while(false);
 }
 
@@ -60,7 +60,9 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
             break;
         }
 
-        furi_hal_bt_stop_advertising();
+        Bt* bt = furi_record_open("bt");
+        bt_disconnect(bt);
+        furi_hal_bt_reinit();
         printf("Receiving carrier at %d channel\r\n", channel);
         printf("Press CTRL+C to stop\r\n");
 
@@ -73,6 +75,9 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
         }
 
         furi_hal_bt_stop_packet_test();
+
+        bt_set_profile(bt, BtProfileSerial);
+        furi_record_close("bt");
     } while(false);
 }
 
@@ -102,7 +107,9 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
             break;
         }
 
-        furi_hal_bt_stop_advertising();
+        Bt* bt = furi_record_open("bt");
+        bt_disconnect(bt);
+        furi_hal_bt_reinit();
         printf(
             "Transmitting %d pattern packet at %d channel at %d M datarate\r\n",
             pattern,
@@ -117,6 +124,8 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
         furi_hal_bt_stop_packet_test();
         printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets());
 
+        bt_set_profile(bt, BtProfileSerial);
+        furi_record_close("bt");
     } while(false);
 }
 
@@ -135,7 +144,9 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
             break;
         }
 
-        furi_hal_bt_stop_advertising();
+        Bt* bt = furi_record_open("bt");
+        bt_disconnect(bt);
+        furi_hal_bt_reinit();
         printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate);
         printf("Press CTRL+C to stop\r\n");
         furi_hal_bt_start_packet_rx(channel, datarate);
@@ -147,37 +158,10 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
         }
         uint16_t packets_received = furi_hal_bt_stop_packet_test();
         printf("Received %hu packets", packets_received);
-    } while(false);
-}
-
-static void bt_cli_scan_callback(GapAddress address, void* context) {
-    furi_assert(context);
-    osMessageQueueId_t queue = context;
-    osMessageQueuePut(queue, &address, 0, 250);
-}
 
-static void bt_cli_command_scan(Cli* cli, string_t args, void* context) {
-    UNUSED(context);
-    UNUSED(args);
-    osMessageQueueId_t queue = osMessageQueueNew(20, sizeof(GapAddress), NULL);
-    furi_hal_bt_start_scan(bt_cli_scan_callback, queue);
-
-    GapAddress address = {};
-    bool exit = false;
-    while(!exit) {
-        if(osMessageQueueGet(queue, &address, NULL, 250) == osOK) {
-            if(address.type < sizeof(bt_cli_address_types)) {
-                printf("Found new device. Type: %s, MAC: ", bt_cli_address_types[address.type]);
-                for(uint8_t i = 0; i < sizeof(address.mac) - 1; i++) {
-                    printf("%02X:", address.mac[i]);
-                }
-                printf("%02X\r\n", address.mac[sizeof(address.mac) - 1]);
-            }
-        }
-        exit = cli_cmd_interrupt_received(cli);
-    }
-    furi_hal_bt_stop_scan();
-    osMessageQueueDelete(queue);
+        bt_set_profile(bt, BtProfileSerial);
+        furi_record_close("bt");
+    } while(false);
 }
 
 static void bt_cli_print_usage() {
@@ -185,13 +169,12 @@ static void bt_cli_print_usage() {
     printf("bt <cmd> <args>\r\n");
     printf("Cmd list:\r\n");
     printf("\thci_info\t - HCI info\r\n");
-    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) &&
-       furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) {
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
         printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n");
         printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n");
-        printf("\ttx_pt <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n");
-        printf("\trx_pt <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n");
-        printf("\tscan\t - start scanner\r\n");
+        printf(
+            "\ttx_packet <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n");
+        printf("\trx_packet <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n");
     }
 }
 
@@ -213,28 +196,23 @@ static void bt_cli(Cli* cli, string_t args, void* context) {
             bt_cli_command_hci_info(cli, args, NULL);
             break;
         }
-        if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) &&
-           furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) {
-            if(string_cmp_str(cmd, "carrier_tx") == 0) {
+        if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
+            if(string_cmp_str(cmd, "tx_carrier") == 0) {
                 bt_cli_command_carrier_tx(cli, args, NULL);
                 break;
             }
-            if(string_cmp_str(cmd, "carrier_rx") == 0) {
+            if(string_cmp_str(cmd, "rx_carrier") == 0) {
                 bt_cli_command_carrier_rx(cli, args, NULL);
                 break;
             }
-            if(string_cmp_str(cmd, "packet_tx") == 0) {
+            if(string_cmp_str(cmd, "tx_packet") == 0) {
                 bt_cli_command_packet_tx(cli, args, NULL);
                 break;
             }
-            if(string_cmp_str(cmd, "packet_rx") == 0) {
+            if(string_cmp_str(cmd, "rx_packet") == 0) {
                 bt_cli_command_packet_rx(cli, args, NULL);
                 break;
             }
-            if(string_cmp_str(cmd, "scan") == 0) {
-                bt_cli_command_scan(cli, args, NULL);
-                break;
-            }
         }
 
         bt_cli_print_usage();

+ 2 - 2
applications/bt/bt_debug_app/bt_debug_app.c

@@ -97,8 +97,8 @@ void bt_debug_app_free(BtDebugApp* app) {
 
 int32_t bt_debug_app(void* p) {
     UNUSED(p);
-    if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) {
-        FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests.");
+    if(!furi_hal_bt_is_testing_supported()) {
+        FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent.");
         DialogsApp* dialogs = furi_record_open("dialogs");
         dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack");
         return 255;

+ 23 - 16
applications/bt/bt_service/bt.c

@@ -293,17 +293,20 @@ static void bt_show_warning(Bt* bt, const char* text) {
     dialog_message_show(bt->dialogs, bt->dialog_message);
 }
 
+static void bt_close_rpc_connection(Bt* bt) {
+    if(bt->profile == BtProfileSerial && bt->rpc_session) {
+        FURI_LOG_I(TAG, "Close RPC connection");
+        osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
+        rpc_session_close(bt->rpc_session);
+        furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
+        bt->rpc_session = NULL;
+    }
+}
+
 static void bt_change_profile(Bt* bt, BtMessage* message) {
-    FuriHalBtStack stack = furi_hal_bt_get_radio_stack();
-    if(stack == FuriHalBtStackLight) {
+    if(furi_hal_bt_is_ble_gatt_gap_supported()) {
         bt_settings_load(&bt->bt_settings);
-        if(bt->profile == BtProfileSerial && bt->rpc_session) {
-            FURI_LOG_I(TAG, "Close RPC connection");
-            osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
-            rpc_session_close(bt->rpc_session);
-            furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
-            bt->rpc_session = NULL;
-        }
+        bt_close_rpc_connection(bt);
 
         FuriHalBtProfile furi_profile;
         if(message->data.profile == BtProfileHidKeyboard) {
@@ -331,6 +334,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
     osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT);
 }
 
+static void bt_close_connection(Bt* bt) {
+    bt_close_rpc_connection(bt);
+    osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT);
+}
+
 int32_t bt_srv() {
     Bt* bt = bt_alloc();
 
@@ -350,14 +358,8 @@ int32_t bt_srv() {
     if(!furi_hal_bt_start_radio_stack()) {
         FURI_LOG_E(TAG, "Radio stack start failed");
     }
-    FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack();
 
-    if(stack_type == FuriHalBtStackUnknown) {
-        bt_show_warning(bt, "Unsupported radio stack");
-        bt->status = BtStatusUnavailable;
-    } else if(stack_type == FuriHalBtStackHciLayer) {
-        bt->status = BtStatusUnavailable;
-    } else if(stack_type == FuriHalBtStackLight) {
+    if(furi_hal_bt_is_ble_gatt_gap_supported()) {
         if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
             FURI_LOG_E(TAG, "BLE App start failed");
         } else {
@@ -366,6 +368,9 @@ int32_t bt_srv() {
             }
             furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
         }
+    } else {
+        bt_show_warning(bt, "Unsupported radio stack");
+        bt->status = BtStatusUnavailable;
     }
 
     furi_record_create("bt", bt);
@@ -392,6 +397,8 @@ int32_t bt_srv() {
             bt_keys_storage_save(bt);
         } else if(message.type == BtMessageTypeSetProfile) {
             bt_change_profile(bt, &message);
+        } else if(message.type == BtMessageTypeDisconnect) {
+            bt_close_connection(bt);
         } else if(message.type == BtMessageTypeForgetBondedDevices) {
             bt_keys_storage_delete(bt);
         }

+ 6 - 0
applications/bt/bt_service/bt.h

@@ -33,6 +33,12 @@ typedef void (*BtStatusChangedCallback)(BtStatus status, void* context);
  */
 bool bt_set_profile(Bt* bt, BtProfile profile);
 
+/** Disconnect from Central
+ *
+ * @param bt        Bt instance
+ */
+void bt_disconnect(Bt* bt);
+
 /** Set callback for Bluetooth status change notification
  *
  * @param bt        Bt instance

+ 10 - 0
applications/bt/bt_service/bt_api.c

@@ -14,6 +14,16 @@ bool bt_set_profile(Bt* bt, BtProfile profile) {
     return result;
 }
 
+void bt_disconnect(Bt* bt) {
+    furi_assert(bt);
+
+    // Send message
+    BtMessage message = {.type = BtMessageTypeDisconnect};
+    furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
+    // Wait for unlock
+    osEventFlagsWait(bt->api_event, BT_API_UNLOCK_EVENT, osFlagsWaitAny, osWaitForever);
+}
+
 void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
     furi_assert(bt);
 

+ 1 - 0
applications/bt/bt_service/bt_i.h

@@ -25,6 +25,7 @@ typedef enum {
     BtMessageTypePinCodeShow,
     BtMessageTypeKeysStorageUpdated,
     BtMessageTypeSetProfile,
+    BtMessageTypeDisconnect,
     BtMessageTypeForgetBondedDevices,
 } BtMessageType;
 

+ 1 - 2
applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c

@@ -39,8 +39,7 @@ void bt_settings_scene_start_on_enter(void* context) {
     VariableItemList* var_item_list = app->var_item_list;
     VariableItem* item;
 
-    FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack();
-    if(stack_type == FuriHalBtStackLight) {
+    if(furi_hal_bt_is_ble_gatt_gap_supported()) {
         item = variable_item_list_add(
             var_item_list,
             "Bluetooth",

+ 1 - 1
applications/updater/util/update_task.c

@@ -26,7 +26,7 @@ static const char* update_task_stage_descr[] = {
     [UpdateTaskStageResourcesUpdate] = "Updating resources",
     [UpdateTaskStageCompleted] = "Restarting...",
     [UpdateTaskStageError] = "Error",
-    [UpdateTaskStageOBError] = "OB Err, report",
+    [UpdateTaskStageOBError] = "OB, report",
 };
 
 typedef struct {

+ 4 - 2
assets/copro.mk

@@ -1,8 +1,10 @@
 COPRO_CUBE_VERSION	:= 1.13.3
 COPRO_MCU_FAMILY	:= STM32WB5x
-COPRO_STACK_BIN		:= stm32wb5x_BLE_Stack_light_fw.bin
+COPRO_STACK_BIN 	?= stm32wb5x_BLE_Stack_light_fw.bin
 #  See __STACK_TYPE_CODES in scripts/flipper/assets/coprobin.py
-COPRO_STACK_TYPE	:= ble_light
+COPRO_STACK_TYPE	?= ble_light
+COPRO_DISCLAIMER	?=
+COPRO_OB_DATA		?= ob.data
 #  Keep 0 for auto, or put a value from release_notes for chosen stack
 COPRO_STACK_ADDR	:= 0
 

+ 49 - 13
documentation/OTA.md

@@ -86,20 +86,56 @@ Even if something goes wrong, Updater gives you an option to retry failed operat
 |                         |        | **50**     | Package has mismatching HW target          |
 |                         |        | **60**     | Missing DFU file                           |
 |                         |        | **80**     | Missing radio firmware file                |
-| Checking DFU file       |  **2** | **0**      | Error opening DFU file                     |
-|                         |        | **1-98**   | Error reading DFU file                     |
-|                         |        | **99-100** | Corrupted DFU file                         |
-| Writing flash           |  **3** | **0-100**  | Block read/write error                     |
-| Validating flash        |  **4** | **0-100**  | Block read/write error                     |
-| Checking radio FW       |  **5** | **0-99**   | Error reading radio firmware file          |
+| Backing up LFS          |  **2** | **0-100**  | FS read/write error                        |
+| Checking radio FW       |  **3** | **0-99**   | Error reading radio firmware file          |
 |                         |        | **100**    | CRC mismatch                               |
-| Uninstalling radio FW   |  **6** | **0**      | SHCI Install command error                 |
+| Uninstalling radio FW   |  **4** | **0**      | SHCI Delete command error                  |
 |                         |        | **80**     | Error awaiting command status              |
-| Writing radio FW        |  **7** | **0-100**  | Block read/write error                     |
-| Installing radio FW     |  **8** | **0**      | SHCI Install command error                 |
+| Writing radio FW        |  **5** | **0-100**  | Block read/write error                     |
+| Installing radio FW     |  **6** | **0**      | SHCI Install command error                 |
 |                         |        | **80**     | Error awaiting command status              |
-| Radio is updating       |  **9** | **10**     | Error waiting for operation completion     |
-| Validating opt. bytes   | **10** | **yy**     | Option byte code                           |
-| Backing up LFS          | **11** | **0-100**  | Block read/write error                     |
-| Restoring LFS           | **12** | **0-100**  | Block read/write error                     |
+| Radio is updating       |  **7** | **10**     | Error waiting for operation completion     |
+| Validating opt. bytes   |  **8** | **yy**     | Option byte code                           |
+| Checking DFU file       |  **9** | **0**      | Error opening DFU file                     |
+|                         |        | **1-98**   | Error reading DFU file                     |
+|                         |        | **99-100** | Corrupted DFU file                         |
+| Writing flash           | **10** | **0-100**  | Block read/write error                     |
+| Validating flash        | **11** | **0-100**  | Block read/write error                     |
+| Restoring LFS           | **12** | **0-100**  | FS read/write error                        |
 | Updating resources      | **13** | **0-100**  | SD card read/write error                   |
+
+
+# Building update packages
+
+
+## Full package
+
+To build a basic update package, run `make COMPACT=1 DEBUG=0 updater_package`
+
+
+## Customizing update bundles
+
+Default update packages are built with Bluetooth Light stack. 
+You can pick a different stack, if your firmware version supports it, and build a bundle with it passing stack type and binary name to `make`: 
+
+`make updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full`  
+
+Note that `COPRO_OB_DATA` must point to a valid file in `scripts` folder containing reference Option Byte data matching to your radio stack type.
+
+In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line.
+
+
+## Building partial update packages
+
+You can customize package contents by calling `scripts/update.py` directly. 
+For example, to build a package only for installing BLE FULL stack:
+
+```shell
+scripts/update.py generate \
+	-t f7 -d r13.3_full -v "BLE FULL 13.3" \
+	--stage dist/f7/flipper-z-f7-updater-*.bin \
+	--radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \
+	--radiotype ble_full
+```
+
+For full list of options, check `scripts/update.py generate` help.

+ 0 - 40
firmware/targets/f7/ble_glue/gap.c

@@ -43,11 +43,6 @@ typedef enum {
     GapCommandKillThread,
 } GapCommand;
 
-typedef struct {
-    GapScanCallback callback;
-    void* context;
-} GapScan;
-
 // Identity root key
 static const uint8_t gap_irk[16] =
     {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
@@ -56,7 +51,6 @@ static const uint8_t gap_erk[16] =
     {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
 
 static Gap* gap = NULL;
-static GapScan* gap_scan = NULL;
 
 static void gap_advertise_start(GapState new_state);
 static int32_t gap_app(void* context);
@@ -169,23 +163,6 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) {
             aci_gap_slave_security_req(event->Connection_Handle);
         } break;
 
-        case EVT_LE_ADVERTISING_REPORT: {
-            if(gap_scan) {
-                GapAddress address;
-                hci_le_advertising_report_event_rp0* evt =
-                    (hci_le_advertising_report_event_rp0*)meta_evt->data;
-                for(uint8_t i = 0; i < evt->Num_Reports; i++) {
-                    Advertising_Report_t* rep = &evt->Advertising_Report[i];
-                    address.type = rep->Address_Type;
-                    // Original MAC addres is in inverted order
-                    for(uint8_t j = 0; j < sizeof(address.mac); j++) {
-                        address.mac[j] = rep->Address[sizeof(address.mac) - j - 1];
-                    }
-                    gap_scan->callback(address, gap_scan->context);
-                }
-            }
-        } break;
-
         default:
             break;
         }
@@ -550,23 +527,6 @@ GapState gap_get_state() {
     return state;
 }
 
-void gap_start_scan(GapScanCallback callback, void* context) {
-    furi_assert(callback);
-    gap_scan = malloc(sizeof(GapScan));
-    gap_scan->callback = callback;
-    gap_scan->context = context;
-    // Scan interval 250 ms
-    hci_le_set_scan_parameters(1, 4000, 200, 0, 0);
-    hci_le_set_scan_enable(1, 1);
-}
-
-void gap_stop_scan() {
-    furi_assert(gap_scan);
-    hci_le_set_scan_enable(0, 1);
-    free(gap_scan);
-    gap_scan = NULL;
-}
-
 void gap_thread_stop() {
     if(gap) {
         osMutexAcquire(gap->state_mutex, osWaitForever);

+ 0 - 11
firmware/targets/f7/ble_glue/gap.h

@@ -33,13 +33,6 @@ typedef struct {
 
 typedef bool (*GapEventCallback)(GapEvent event, void* context);
 
-typedef struct {
-    uint8_t type;
-    uint8_t mac[6];
-} GapAddress;
-
-typedef void (*GapScanCallback)(GapAddress address, void* context);
-
 typedef enum {
     GapStateUninitialized,
     GapStateIdle,
@@ -88,10 +81,6 @@ GapState gap_get_state();
 
 void gap_thread_stop();
 
-void gap_start_scan(GapScanCallback callback, void* context);
-
-void gap_stop_scan();
-
 #ifdef __cplusplus
 }
 #endif

+ 25 - 20
firmware/targets/f7/furi_hal/furi_hal_bt.c

@@ -104,15 +104,18 @@ void furi_hal_bt_unlock_core2() {
 
 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;
-        supported = true;
-    } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
+    if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
         if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
            info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
             furi_hal_bt_stack = FuriHalBtStackLight;
             supported = true;
         }
+    } else if(info->StackType == INFO_STACK_TYPE_BLE_FULL) {
+        if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
+           info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
+            furi_hal_bt_stack = FuriHalBtStackFull;
+            supported = true;
+        }
     } else {
         furi_hal_bt_stack = FuriHalBtStackUnknown;
     }
@@ -168,6 +171,22 @@ FuriHalBtStack furi_hal_bt_get_radio_stack() {
     return furi_hal_bt_stack;
 }
 
+bool furi_hal_bt_is_ble_gatt_gap_supported() {
+    if(furi_hal_bt_stack == FuriHalBtStackLight || furi_hal_bt_stack == FuriHalBtStackFull) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool furi_hal_bt_is_testing_supported() {
+    if(furi_hal_bt_stack == FuriHalBtStackFull) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
 bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
     furi_assert(event_cb);
     furi_assert(profile < FuriHalBtProfileNumber);
@@ -178,7 +197,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb,
             FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start");
             break;
         }
-        if(furi_hal_bt_stack != FuriHalBtStackLight) {
+        if(!furi_hal_bt_is_ble_gatt_gap_supported()) {
             FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack");
             break;
         }
@@ -209,7 +228,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb,
             break;
         }
         // Start selected profile services
-        if(furi_hal_bt_stack == FuriHalBtStackLight) {
+        if(furi_hal_bt_is_ble_gatt_gap_supported()) {
             profile_config[profile].start();
         }
         ret = true;
@@ -411,20 +430,6 @@ void furi_hal_bt_stop_rx() {
     aci_hal_rx_stop();
 }
 
-bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) {
-    if(furi_hal_bt_stack != FuriHalBtStackHciLayer) {
-        return false;
-    }
-    gap_start_scan(callback, context);
-    return true;
-}
-
-void furi_hal_bt_stop_scan() {
-    if(furi_hal_bt_stack == FuriHalBtStackHciLayer) {
-        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) {

+ 14 - 13
firmware/targets/furi_hal_include/furi_hal_bt.h

@@ -15,7 +15,7 @@
 #include "furi_hal_bt_serial.h"
 
 #define FURI_HAL_BT_STACK_VERSION_MAJOR (1)
-#define FURI_HAL_BT_STACK_VERSION_MINOR (13)
+#define FURI_HAL_BT_STACK_VERSION_MINOR (12)
 #define FURI_HAL_BT_C2_START_TIMEOUT 1000
 
 #ifdef __cplusplus
@@ -24,8 +24,8 @@ extern "C" {
 
 typedef enum {
     FuriHalBtStackUnknown,
-    FuriHalBtStackHciLayer,
     FuriHalBtStackLight,
+    FuriHalBtStackFull,
 } FuriHalBtStack;
 
 typedef enum {
@@ -58,6 +58,18 @@ bool furi_hal_bt_start_radio_stack();
  */
 FuriHalBtStack furi_hal_bt_get_radio_stack();
 
+/** Check if radio stack supports BLE GAT/GAP
+ *
+ * @return  true if supported
+ */
+bool furi_hal_bt_is_ble_gatt_gap_supported();
+
+/** Check if radio stack supports testing
+ *
+ * @return  true if supported
+ */
+bool furi_hal_bt_is_testing_supported();
+
 /** Start BLE app
  *
  * @param profile   FuriHalBtProfile instance
@@ -206,17 +218,6 @@ float furi_hal_bt_get_rssi();
  */
 uint32_t furi_hal_bt_get_transmitted_packets();
 
-/** Start MAC addresses scan
- * @note Works only with HciLayer 2nd core firmware
- *
- * @param callback  GapScanCallback instance
- * @param context   pointer to context
- */
-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

+ 10 - 6
scripts/flipper/app.py

@@ -15,16 +15,20 @@ class App:
         # Application specific initialization
         self.init()
 
-    def __call__(self, args=None):
+    def __call__(self, args=None, skip_logger_init=False):
         self.args, self.other_args = self.parser.parse_known_args(args=args)
         # configure log output
+        # if skip_logger_init:
         self.log_level = logging.DEBUG if self.args.debug else logging.INFO
         self.logger.setLevel(self.log_level)
-        self.handler = logging.StreamHandler(sys.stdout)
-        self.handler.setLevel(self.log_level)
-        self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
-        self.handler.setFormatter(self.formatter)
-        self.logger.addHandler(self.handler)
+        if not self.logger.hasHandlers():
+            self.handler = logging.StreamHandler(sys.stdout)
+            self.handler.setLevel(self.log_level)
+            self.formatter = logging.Formatter(
+                "%(asctime)s [%(levelname)s] %(message)s"
+            )
+            self.handler.setFormatter(self.formatter)
+            self.logger.addHandler(self.handler)
 
         # execute requested function
         self.before()

+ 34 - 0
scripts/ob_custradio.data

@@ -0,0 +1,34 @@
+RDP:0xAA:r
+BOR_LEV:0x4:rw
+nBOOT0:0x1:r
+nBOOT1:0x1:rw
+nSWBOOT0:0x1:rw
+SRAM2RST:0x0:rw
+SRAM2PE:0x1:rw
+nRST_STOP:0x1:rw
+nRST_STDBY:0x1:rw
+nRSTSHDW:0x1:rw
+WWDGSW:0x1:rw
+IWGDSTDBY:0x1:rw
+IWDGSTOP:0x1:rw
+IWDGSW:0x1:rw
+IPCCDBA:0x0:rw
+ESE:0x1:r
+#SFSA:0xD7:r
+FSD:0x0:r
+DDS:0x1:r
+#C2OPT:0x1:r
+#NBRSD:0x0:r
+#SNBRSA:0xD:r
+#BRSD:0x0:r
+#SBRSA:0x12:r
+#SBRV:0x35C00:r
+PCROP1A_STRT:0x1FF:r
+PCROP1A_END:0x0:r
+PCROP_RDP:0x1:rw
+PCROP1B_STRT:0x1FF:r
+PCROP1B_END:0x0:r
+WRP1A_STRT:0xFF:r
+WRP1A_END:0x0:r
+WRP1B_STRT:0xFF:r
+WRP1B_END:0x0:r

+ 63 - 8
scripts/update.py

@@ -3,7 +3,7 @@
 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 flipper.assets.obdata import OptionBytesData, ObReferenceValues
 from os.path import basename, join, exists
 import os
 import shutil
@@ -21,6 +21,16 @@ class Main(App):
     RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT
     RESOURCE_FILE_NAME = "resources.tar"
 
+    WHITELISTED_STACK_TYPES = set(
+        map(
+            get_stack_type,
+            ["BLE_FULL", "BLE_LIGHT", "BLE_BASIC"],
+        )
+    )
+
+    FLASH_BASE = 0x8000000
+    MIN_LFS_PAGES = 6
+
     def init(self):
         self.subparsers = self.parser.add_subparsers(help="sub-command help")
 
@@ -53,6 +63,9 @@ class Main(App):
         )
 
         self.parser_generate.add_argument("--obdata", dest="obdata", required=False)
+        self.parser_generate.add_argument(
+            "--I-understand-what-I-am-doing", dest="disclaimer", required=False
+        )
 
         self.parser_generate.set_defaults(func=self.generate)
 
@@ -70,10 +83,20 @@ class Main(App):
                 raise ValueError("Missing --radiotype")
             radio_meta = CoproBinary(self.args.radiobin)
             radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype)
+            if (
+                get_stack_type(self.args.radiotype) not in self.WHITELISTED_STACK_TYPES
+                and self.args.disclaimer != "yes"
+            ):
+                self.logger.error(
+                    f"You are trying to bundle a non-standard stack type '{self.args.radiotype}'."
+                )
+                self.disclaimer()
+                return 1
+
             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"
+                    f"Using guessed radio address 0x{radio_addr:08X}, verify with Release_Notes"
                     " or specify --radioaddr"
                 )
 
@@ -81,7 +104,9 @@ class Main(App):
             os.makedirs(self.args.directory)
 
         shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename))
+        dfu_size = 0
         if self.args.dfu:
+            dfu_size = os.stat(self.args.dfu).st_size
             shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename))
         if radiobin_basename:
             shutil.copyfile(
@@ -93,6 +118,12 @@ class Main(App):
                 self.args.resources, join(self.args.directory, resources_basename)
             )
 
+        if not self.layout_check(dfu_size, radio_addr):
+            self.logger.warn("Memory layout looks suspicious")
+            if not self.args.disclaimer == "yes":
+                self.disclaimer()
+                return 2
+
         file = FlipperFormatFile()
         file.setHeader(
             "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION
@@ -111,19 +142,43 @@ class Main(App):
         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"
-        )
+        obvalues = ObReferenceValues((), (), ())
         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.writeComment(
+                "NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE"
+            )
+        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
 
+    def layout_check(self, fw_size, radio_addr):
+        if fw_size == 0 or radio_addr == 0:
+            self.logger.info("Cannot validate layout for partial package")
+            return True
+
+        lfs_span = radio_addr - self.FLASH_BASE - fw_size
+        self.logger.debug(f"Expected LFS size: {lfs_span}")
+        lfs_span_pages = lfs_span / (4 * 1024)
+        if lfs_span_pages < self.MIN_LFS_PAGES:
+            self.logger.warn(
+                f"Expected LFS size is too small (~{int(lfs_span_pages)} pages)"
+            )
+            return False
+        return True
+
+    def disclaimer(self):
+        self.logger.error(
+            "You might brick you device into a state in which you'd need an SWD programmer to fix it."
+        )
+        self.logger.error(
+            "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes"
+        )
+
     def package_resources(self, srcdir: str, dst_name: str):
         with tarfile.open(
             dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT