Ver Fonte

Implement FW switching

0xchocolate há 2 anos atrás
pai
commit
fb4155d8a2

+ 1 - 1
esp_flasher_app.h

@@ -4,7 +4,7 @@
 extern "C" {
 #endif
 
-#define ESP_FLASHER_APP_VERSION "v1.1"
+#define ESP_FLASHER_APP_VERSION "v1.2"
 
 typedef struct EspFlasherApp EspFlasherApp;
 

+ 12 - 2
esp_flasher_app_i.h

@@ -33,11 +33,18 @@ typedef enum SelectedFlashOptions {
     SelectedFlashPart,
     SelectedFlashNvs,
     SelectedFlashBootApp0,
-    SelectedFlashApp,
+    SelectedFlashAppA,
+    SelectedFlashAppB,
     SelectedFlashCustom,
     NUM_FLASH_OPTIONS
 } SelectedFlashOptions;
 
+typedef enum {
+    SwitchNotSet,
+    SwitchToFirmwareA,
+    SwitchToFirmwareB,
+} SwitchFirmware;
+
 struct EspFlasherApp {
     Gui* gui;
     ViewDispatcher* view_dispatcher;
@@ -59,13 +66,16 @@ struct EspFlasherApp {
     bool reset;
     bool boot;
 
+    SwitchFirmware switch_fw;
+
     bool selected_flash_options[NUM_FLASH_OPTIONS];
     int num_selected_flash_options;
     char bin_file_path_boot[100];
     char bin_file_path_part[100];
     char bin_file_path_nvs[100];
     char bin_file_path_boot_app0[100];
-    char bin_file_path_app[100];
+    char bin_file_path_app_a[100];
+    char bin_file_path_app_b[100];
     char bin_file_path_custom[100];
     FuriThread* flash_worker;
     bool flash_worker_busy;

+ 93 - 7
esp_flasher_worker.c

@@ -74,6 +74,85 @@ static esp_loader_error_t _flash_file(EspFlasherApp* app, char* filepath, uint32
     return ESP_LOADER_SUCCESS;
 }
 
+// This in-app FW switch "exploits" the otadata (boot_app0)
+// - the first four bytes of each array are the counter and the last four bytes are just a CRC of that counter
+// - the bootloader will just boot whichever app has the highest counter in the otadata partition
+//   so we'll just pick 1 for A, and then B will use either 0 or 2 depending on whether it's the slot in use
+
+#define MAGIC_PAYLOAD_SIZE (32)
+
+const uint8_t magic_payload_app_a[MAGIC_PAYLOAD_SIZE] = {0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                                         0x9a, 0x98, 0x43, 0x47};
+
+const uint8_t magic_payload_app_b_unset[MAGIC_PAYLOAD_SIZE] = {
+    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+const uint8_t magic_payload_app_b_set[MAGIC_PAYLOAD_SIZE] = {
+    0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x74, 0x37, 0xf6, 0x55};
+
+// return true if "switching" fw selected instead of flashing new fw
+// (this does not indicate success)
+static bool _switch_fw(EspFlasherApp* app) {
+    if(app->switch_fw == SwitchNotSet) {
+        return false;
+    }
+
+    esp_loader_error_t err;
+    char user_msg[256];
+
+    loader_port_debug_print("Preparing to set flags for firmware A\n");
+    err = esp_loader_flash_start(
+        ESP_ADDR_BOOT_APP0 + ESP_ADDR_OTADATA_OFFSET_APP_A,
+        MAGIC_PAYLOAD_SIZE,
+        MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Setting flags for firmware A\n");
+    const uint8_t* which_payload_app_a = magic_payload_app_a;
+    err = esp_loader_flash_write((void*)which_payload_app_a, MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Preparing to set flags for firmware B\n");
+    err = esp_loader_flash_start(
+        ESP_ADDR_BOOT_APP0 + ESP_ADDR_OTADATA_OFFSET_APP_B,
+        MAGIC_PAYLOAD_SIZE,
+        MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Setting flags for firmware B\n");
+    const uint8_t* which_payload_app_b =
+        (app->switch_fw == SwitchToFirmwareB ? magic_payload_app_b_set :
+                                               magic_payload_app_b_unset);
+    err = esp_loader_flash_write((void*)which_payload_app_b, MAGIC_PAYLOAD_SIZE);
+    if(err != ESP_LOADER_SUCCESS) {
+        snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err);
+        loader_port_debug_print(user_msg);
+        return true;
+    }
+
+    loader_port_debug_print("Finished programming\n");
+    return true;
+}
+
 typedef struct {
     SelectedFlashOptions selected;
     const char* description;
@@ -85,7 +164,7 @@ static void _flash_all_files(EspFlasherApp* app) {
     esp_loader_error_t err;
     const int num_steps = app->num_selected_flash_options;
 
-#define NUM_FLASH_ITEMS 6
+#define NUM_FLASH_ITEMS 7
     FlashItem items[NUM_FLASH_ITEMS] = {
         {SelectedFlashBoot,
          "bootloader",
@@ -94,7 +173,8 @@ static void _flash_all_files(EspFlasherApp* app) {
         {SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART},
         {SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS},
         {SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0},
-        {SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP},
+        {SelectedFlashAppA, "firmware A", app->bin_file_path_app_a, ESP_ADDR_APP_A},
+        {SelectedFlashAppB, "firmware B", app->bin_file_path_app_b, ESP_ADDR_APP_B},
         {SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0},
         /* if you add more entries, update NUM_FLASH_ITEMS above! */
     };
@@ -167,12 +247,16 @@ static int32_t esp_flasher_flash_bin(void* context) {
 
     if(!err) {
         loader_port_debug_print("Connected\n");
-        _flash_all_files(app);
+        if(!_switch_fw(app)) {
+            _flash_all_files(app);
+        }
+        app->switch_fw = SwitchNotSet;
 #if 0
         loader_port_debug_print("Restoring transmission rate\n");
         furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200);
 #endif
-        loader_port_debug_print("Done flashing. Please reset the board manually if it doesn't auto-reset.\n");
+        loader_port_debug_print(
+            "Done flashing. Please reset the board manually if it doesn't auto-reset.\n");
 
         // auto-reset for supported boards
         loader_port_reset_target();
@@ -222,10 +306,10 @@ static int32_t esp_flasher_reset(void* context) {
     _setRTS(false);
     _initRTS();
 
-    if (app->reset) {
+    if(app->reset) {
         loader_port_debug_print("Resetting board\n");
         loader_port_reset_target();
-    } else if (app->boot) {
+    } else if(app->boot) {
         loader_port_debug_print("Entering bootloader\n");
         loader_port_enter_bootloader();
     }
@@ -245,7 +329,7 @@ void esp_flasher_worker_start_thread(EspFlasherApp* app) {
     furi_thread_set_name(app->flash_worker, "EspFlasherFlashWorker");
     furi_thread_set_stack_size(app->flash_worker, 2048);
     furi_thread_set_context(app->flash_worker, app);
-    if (app->reset || app->boot) {
+    if(app->reset || app->boot) {
         furi_thread_set_callback(app->flash_worker, esp_flasher_reset);
     } else {
         furi_thread_set_callback(app->flash_worker, esp_flasher_flash_bin);
@@ -280,6 +364,8 @@ void loader_port_reset_target(void) {
 }
 
 void loader_port_enter_bootloader(void) {
+    // adapted from custom usb-jtag-serial reset in esptool
+    // (works on official wifi dev board)
     _setDTR(true);
     loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS);
     _setRTS(true);

+ 5 - 1
esp_flasher_worker.h

@@ -14,7 +14,11 @@
 #define ESP_ADDR_PART 0x8000
 #define ESP_ADDR_NVS 0x9000
 #define ESP_ADDR_BOOT_APP0 0xE000
-#define ESP_ADDR_APP 0x10000
+#define ESP_ADDR_APP_A 0x10000
+#define ESP_ADDR_APP_B 0x150000
+
+#define ESP_ADDR_OTADATA_OFFSET_APP_A 0x0
+#define ESP_ADDR_OTADATA_OFFSET_APP_B 0x1000
 
 void esp_flasher_worker_start_thread(EspFlasherApp* app);
 void esp_flasher_worker_stop_thread(EspFlasherApp* app);

+ 39 - 13
scenes/esp_flasher_scene_browse.c

@@ -7,7 +7,8 @@ enum SubmenuIndex {
     SubmenuIndexPart,
     SubmenuIndexNvs,
     SubmenuIndexBootApp0,
-    SubmenuIndexApp,
+    SubmenuIndexAppA,
+    SubmenuIndexAppB,
     SubmenuIndexCustom,
     SubmenuIndexFlash,
 };
@@ -97,19 +98,35 @@ static void esp_flasher_scene_browse_callback(void* context, uint32_t index) {
         }
         view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
         break;
-    case SubmenuIndexApp:
-        app->selected_flash_options[SelectedFlashApp] =
-            !app->selected_flash_options[SelectedFlashApp];
+    case SubmenuIndexAppA:
+        app->selected_flash_options[SelectedFlashAppA] =
+            !app->selected_flash_options[SelectedFlashAppA];
         if(dialog_file_browser_show(
                app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
             strncpy(
-                app->bin_file_path_app,
+                app->bin_file_path_app_a,
                 furi_string_get_cstr(selected_filepath),
-                sizeof(app->bin_file_path_app));
+                sizeof(app->bin_file_path_app_a));
         }
-        if(app->bin_file_path_app[0] == '\0') {
+        if(app->bin_file_path_app_a[0] == '\0') {
             // if user didn't select a file, leave unselected
-            app->selected_flash_options[SelectedFlashApp] = false;
+            app->selected_flash_options[SelectedFlashAppA] = false;
+        }
+        view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
+        break;
+    case SubmenuIndexAppB:
+        app->selected_flash_options[SelectedFlashAppB] =
+            !app->selected_flash_options[SelectedFlashAppB];
+        if(dialog_file_browser_show(
+               app->dialogs, selected_filepath, predefined_filepath, &browser_options)) {
+            strncpy(
+                app->bin_file_path_app_b,
+                furi_string_get_cstr(selected_filepath),
+                sizeof(app->bin_file_path_app_b));
+        }
+        if(app->bin_file_path_app_b[0] == '\0') {
+            // if user didn't select a file, leave unselected
+            app->selected_flash_options[SelectedFlashAppB] = false;
         }
         view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu);
         break;
@@ -157,7 +174,8 @@ static void esp_flasher_scene_browse_callback(void* context, uint32_t index) {
 #define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")"
 #define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")"
 #define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")"
-#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")"
+#define STR_APP_A "FirmwareA(" TOSTRING(ESP_ADDR_APP_A) ")"
+#define STR_APP_B "FirmwareB(" TOSTRING(ESP_ADDR_APP_B) ")"
 #define STR_CUSTOM "Custom"
 #define STR_FLASH_S3 "[>] FLASH (ESP32-S3)"
 #define STR_FLASH "[>] FLASH"
@@ -213,9 +231,16 @@ static void _refresh_submenu(EspFlasherApp* app) {
         app);
     submenu_add_item(
         submenu,
-        app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP :
-                                                        STR_UNSELECT " " STR_APP,
-        SubmenuIndexApp,
+        app->selected_flash_options[SelectedFlashAppA] ? STR_SELECT " " STR_APP_A :
+                                                         STR_UNSELECT " " STR_APP_A,
+        SubmenuIndexAppA,
+        esp_flasher_scene_browse_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        app->selected_flash_options[SelectedFlashAppB] ? STR_SELECT " " STR_APP_B :
+                                                         STR_UNSELECT " " STR_APP_B,
+        SubmenuIndexAppB,
         esp_flasher_scene_browse_callback,
         app);
     // TODO: custom addr
@@ -241,7 +266,8 @@ void esp_flasher_scene_browse_on_enter(void* context) {
     app->bin_file_path_part[0] = '\0';
     app->bin_file_path_nvs[0] = '\0';
     app->bin_file_path_boot_app0[0] = '\0';
-    app->bin_file_path_app[0] = '\0';
+    app->bin_file_path_app_a[0] = '\0';
+    app->bin_file_path_app_b[0] = '\0';
     app->bin_file_path_custom[0] = '\0';
 
     _refresh_submenu(app);

+ 22 - 0
scenes/esp_flasher_scene_start.c

@@ -2,6 +2,8 @@
 
 enum SubmenuIndex {
     SubmenuIndexEspFlasherFlash,
+    SubmenuIndexEspFlasherSwitchA,
+    SubmenuIndexEspFlasherSwitchB,
     SubmenuIndexEspFlasherAbout,
     SubmenuIndexEspFlasherReset,
     SubmenuIndexEspFlasherBootloader,
@@ -25,6 +27,18 @@ void esp_flasher_scene_start_on_enter(void* context) {
         SubmenuIndexEspFlasherFlash,
         esp_flasher_scene_start_submenu_callback,
         app);
+    submenu_add_item(
+        submenu,
+        "Switch to Firmware A",
+        SubmenuIndexEspFlasherSwitchA,
+        esp_flasher_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu,
+        "Switch to Firmware B",
+        SubmenuIndexEspFlasherSwitchB,
+        esp_flasher_scene_start_submenu_callback,
+        app);
     submenu_add_item(
         submenu,
         "Reset Board",
@@ -62,6 +76,14 @@ bool esp_flasher_scene_start_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SubmenuIndexEspFlasherFlash) {
             scene_manager_next_scene(app->scene_manager, EspFlasherSceneBrowse);
             consumed = true;
+        } else if(event.event == SubmenuIndexEspFlasherSwitchA) {
+            app->switch_fw = SwitchToFirmwareA;
+            scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);
+            consumed = true;
+        } else if(event.event == SubmenuIndexEspFlasherSwitchB) {
+            app->switch_fw = SwitchToFirmwareB;
+            scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);
+            consumed = true;
         } else if(event.event == SubmenuIndexEspFlasherReset) {
             app->reset = true;
             scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput);