diff --git a/esp_flasher_app.h b/esp_flasher_app.h index 809fcc6..3a30d16 100644 --- a/esp_flasher_app.h +++ b/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; diff --git a/esp_flasher_app_i.h b/esp_flasher_app_i.h index 5b31334..37264a2 100644 --- a/esp_flasher_app_i.h +++ b/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; diff --git a/esp_flasher_worker.c b/esp_flasher_worker.c index 86f9394..d47fe05 100644 --- a/esp_flasher_worker.c +++ b/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); diff --git a/esp_flasher_worker.h b/esp_flasher_worker.h index 44461e7..0dba16f 100644 --- a/esp_flasher_worker.h +++ b/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); diff --git a/scenes/esp_flasher_scene_browse.c b/scenes/esp_flasher_scene_browse.c index 88a9b25..8b223b2 100644 --- a/scenes/esp_flasher_scene_browse.c +++ b/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); diff --git a/scenes/esp_flasher_scene_start.c b/scenes/esp_flasher_scene_start.c index 2d91f2d..8edce57 100644 --- a/scenes/esp_flasher_scene_start.c +++ b/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);