Skip to content

Commit

Permalink
Implemented a self-updater for retro-go to reflash itself
Browse files Browse the repository at this point in the history
It parses the .fw format (could also add .img support) and reflash the partitions when it can.

It is not able to change partition layout or update the updater itself, so if it's buggy you're SOL.

It does no crc check at the moment.

To access the updater/flasher:

- Check for update in the launcher, it will reboot into it after downloading the file
- Or hold down any button during boot and select "Reboot to updater".
  • Loading branch information
ducalex committed Jul 19, 2024
1 parent e91117d commit 6ec0667
Show file tree
Hide file tree
Showing 7 changed files with 523 additions and 2 deletions.
4 changes: 2 additions & 2 deletions components/retro-go/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
#endif

#ifndef RG_APP_UPDATER
#define RG_APP_UPDATER RG_APP_FACTORY
// #define RG_APP_UPDATER "updater"
// #define RG_APP_UPDATER RG_APP_FACTORY
#define RG_APP_UPDATER "updater"
#endif

#ifndef RG_PATH_MAX
Expand Down
3 changes: 3 additions & 0 deletions components/retro-go/rg_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ static void enter_recovery_mode(void)
{0, "Reset all settings", NULL, RG_DIALOG_FLAG_NORMAL, NULL},
{1, "Reboot to factory ", NULL, RG_DIALOG_FLAG_NORMAL, NULL},
{2, "Reboot to launcher", NULL, RG_DIALOG_FLAG_NORMAL, NULL},
{3, "Reboot to updater ", NULL, RG_DIALOG_FLAG_NORMAL, NULL},
RG_DIALOG_END,
};
while (true)
Expand All @@ -264,6 +265,8 @@ static void enter_recovery_mode(void)
break;
case 1:
rg_system_switch_app(RG_APP_FACTORY, 0, 0, 0);
case 3:
rg_system_switch_app(RG_APP_UPDATER, 0, 0, 0);
case 2:
default:
rg_system_exit();
Expand Down
1 change: 1 addition & 0 deletions rg_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
PROJECT_APPS = {
# Project name Type, SubType, Size
'launcher': [0, 0, 917504],
'updater': [0, 0, 589824],
'retro-core': [0, 0, 917504],
'prboom-go': [0, 0, 786432],
'gwenesis': [0, 0, 983040],
Expand Down
4 changes: 4 additions & 0 deletions updater/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.5)
set(COMPONENTS "main retro-go app_trace bootloader esptool_py")
include(../base.cmake)
project(updater)
4 changes: 4 additions & 0 deletions updater/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set(COMPONENT_SRCDIRS ".")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()
rg_setup_compile_options(-O2 -Wno-error=format -Wno-error=char-subscripts -mfix-esp32-psram-cache-issue)
339 changes: 339 additions & 0 deletions updater/main/COPYING

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions updater/main/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#include <rg_system.h>
#include <sys/time.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <esp_partition.h>

#if defined(RG_TARGET_ODROID_GO)
#define DOWNLOAD_LOCATION RG_STORAGE_ROOT "/odroid/firmware"
#else
#define DOWNLOAD_LOCATION RG_STORAGE_ROOT "/espgbc/firmware"
#endif

const char ODROID_HEADER[] = "ODROIDGO_FIRMWARE_V00_01";
const char ESPLAY_HEADER[] = "ESPLAY_FIRMWARE_V00_01";

#define FIRMWARE_HEADER_SIZE (24)
#define FIRMWARE_DESCRIPTION_SIZE (40)
#define FIRMWARE_PARTS_MAX (20)
#define FIRMWARE_TILE_SIZE (86 * 48)

typedef struct
{
uint8_t type;
uint8_t subtype;
uint8_t _reserved0;
uint8_t _reserved1;
char label[16];
uint32_t flags;
uint32_t length;
uint32_t dataLength;
uint8_t data[];
} fw_partition_t;

typedef struct
{
char header[FIRMWARE_HEADER_SIZE];
char description[FIRMWARE_DESCRIPTION_SIZE];
uint16_t tile[FIRMWARE_TILE_SIZE];
// uint32_t checksum;
} fw_t;


void app_main(void)
{
rg_app_t *app = rg_system_init(32000, NULL, NULL);

if (!rg_storage_ready())
{
rg_display_clear(C_SKY_BLUE);
rg_gui_alert("SD Card Error", "Storage mount failed.\nMake sure the card is FAT32.");
// What do at this point? Reboot? Switch back to launcher?
goto launcher;
}

const char *filename;

if (app->romPath && strlen(app->romPath) && rg_storage_exists(app->romPath))
filename = app->romPath;
else
filename = rg_gui_file_picker("Select update", DOWNLOAD_LOCATION, NULL, true);

RG_LOGI("Filename: %s", filename);

if (!filename)
goto launcher;

FILE *fp = fopen(filename, "rb");
if (!fp)
{
rg_gui_alert("Error", "File open failed");
goto launcher;
}

// In the final version we'll read in chunk but since we have 4MB of PSRAM let's use it for this POC
void *buffer = malloc(2 * 1024 * 1024);

This comment has been minimized.

Copy link
@tomvanbraeckel

tomvanbraeckel Jul 20, 2024

Contributor

This is doing 2MB instead of 4MB, I think :-)

This comment has been minimized.

Copy link
@ducalex

ducalex Jul 21, 2024

Author Owner

It is, for a few reasons:

  • We can't assume PSRAM to be fully empty at this point, so allocating 4MB will fail
  • We don't currently have partitions larger than 1MB, 2MB gives us a nice buffer

Agreed the comment could be misleading as to my intent there.

This comment has been minimized.

Copy link
@tomvanbraeckel

tomvanbraeckel Jul 21, 2024

Contributor

Oh ok, yes indeed, I understood it as "4MB of PSRAM let's use it [the 4MB of PSRAM" :-)

fw_t *fw = calloc(1, sizeof(fw_t));
if (!fw || !buffer)
{
rg_gui_alert("Error", "Out of memory");
goto launcher;
}

// No need to check this one, the header check will tell us if all is good
fread(fw, sizeof(fw_t), 1, fp);

if (memcmp(fw->header, ODROID_HEADER, 24) == 0)
{
// All good
}
else if (memcmp(fw->header, ESPLAY_HEADER, 22) == 0)
{
// ESPLAY header is 2 bytes shorter, just shift the data and everything else is good
memmove((void *)fw + 2, fw, sizeof(fw_t) - 2);
fseek(fp, sizeof(fw_t) - 2, SEEK_SET);
}
else
{
rg_gui_alert("Error", "Invalid file format!");
goto launcher;
}

if (!rg_gui_confirm("Flash update?", fw->description, false))
{
goto launcher;
}

// Here we should check the checksum blah blah blah


// Copy the firmware
for (fw_partition_t fw_part; fread(&fw_part, sizeof(fw_part), 1, fp);)
{
const esp_partition_t *part = esp_partition_find_first(fw_part.type, fw_part.subtype, fw_part.label);
size_t nextEntry = ftell(fp) + fw_part.dataLength;

rg_display_clear(C_BLACK);
rg_gui_draw_text(0, 32, RG_SCREEN_WIDTH, part->label, C_WHITE, C_BLACK, RG_TEXT_BIGGER|RG_TEXT_ALIGN_CENTER);

if (!part)
{
// We don't seem to have this partition and we can't rewrite the partition table to add it...
rg_gui_alert("Unknown partition", fw_part.label);
}
else if (strncmp(part->label, "updater", 8) == 0)
{
// Can't update self
rg_gui_alert("Skipping self", fw_part.label);
}
else if (fw_part.dataLength > part->size)
{
rg_gui_alert("Size mismatch, can't flash", fw_part.label);
}
else if (fw_part.dataLength > 0)
{
if (fw_part.length != part->size)
{
rg_gui_draw_text(0, 96, RG_SCREEN_WIDTH, "Warning: Size mismatch", C_ORANGE, C_BLACK, RG_TEXT_BIGGER|RG_TEXT_ALIGN_CENTER);
}

if (fread(buffer, fw_part.dataLength, 1, fp))
{
rg_gui_draw_text(0, 64, RG_SCREEN_WIDTH, "Erasing....", C_WHITE, C_BLACK, RG_TEXT_BIGGER|RG_TEXT_ALIGN_CENTER);
esp_partition_erase_range(part, 0, part->size);
rg_gui_draw_text(0, 64, RG_SCREEN_WIDTH, "Writing....", C_WHITE, C_BLACK, RG_TEXT_BIGGER|RG_TEXT_ALIGN_CENTER);
esp_partition_write(part, 0, buffer, fw_part.dataLength);
}
else
{
rg_gui_alert("File read error", fw_part.label);
}
}
else
{
// Do nothing, could be a data partition
}

fseek(fp, nextEntry, SEEK_SET);
}

fclose(fp);

rg_display_clear(C_BLACK);
rg_gui_alert("Update complete", "All done!");

launcher:
rg_system_switch_app(RG_APP_LAUNCHER, 0, 0, 0);
}

0 comments on commit 6ec0667

Please sign in to comment.