Skip to content

Commit f47ffe2

Browse files
committed
updater: Use dialog to display nicer progress, improved error checking
- Switched to lower level flash APIs in anticipation to dangerous updates. - Only use internal memory to avoid the (slim) odds of hitting the PSRAM bug.
1 parent 3ccbe97 commit f47ffe2

File tree

1 file changed

+162
-112
lines changed

1 file changed

+162
-112
lines changed

updater/main/main.c

Lines changed: 162 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED
1515

1616
#define MAX_PARTITIONS (24) // ESP_PARTITION_TABLE_MAX_ENTRIES
17+
#define ALIGN_BLOCK(val, alignment) ((int)(((val) + (alignment - 1)) / alignment) * alignment)
1718

1819
#define RETRO_GO_IMG_MAGIC "RG_IMG_0"
1920
typedef struct
@@ -27,155 +28,204 @@ typedef struct
2728
char reserved[156];
2829
} img_info_t;
2930

30-
#define FORMAT(message...) ({snprintf(message_buffer, sizeof(message_buffer), message); message_buffer; })
31-
#define CONFIRM(title, message...) \
32-
{ \
33-
if (!rg_gui_confirm(_(title), FORMAT(message), false)) \
34-
return false; \
35-
rg_display_clear(C_BLACK); \
36-
}
37-
#define TRY(cond, error_message) \
38-
if (!(cond)) \
39-
{ \
40-
rg_gui_alert(_("Error"), error_message); \
41-
return false; \
42-
}
43-
4431
typedef struct
4532
{
46-
const esp_partition_info_t *src;
47-
const esp_partition_t *dst;
48-
} partition_pair_t;
33+
char name[17];
34+
struct {int offset, size;} src;
35+
struct {int offset, size;} dst;
36+
} flash_task_t;
4937

38+
static size_t gp_buffer_size = 0x20000;
39+
static void *gp_buffer = NULL;
5040
static rg_app_t *app;
51-
// static esp_partition_info_t device_partition_table[ESP_PARTITION_TABLE_MAX_ENTRIES];
5241

53-
static bool do_update(const char *filename)
42+
#define FORMAT(message...) ({snprintf(message_buffer, sizeof(message_buffer), message); message_buffer; })
43+
#define TRY(cond, error_message...) \
44+
RG_LOGD("Line: %s", #cond); \
45+
if (!(cond)) \
46+
{ \
47+
rg_gui_alert(_("Error"), FORMAT(error_message)); \
48+
goto fail; \
49+
}
50+
51+
static bool fread_at(void *output, int offset, int length, FILE *fp)
52+
{
53+
if (offset < 0 && !(fseek(fp, 0, SEEK_END) == 0 && fseek(fp, offset, SEEK_CUR) == 0))
54+
return false;
55+
else if (offset >= 0 && !(fseek(fp, offset, SEEK_SET) == 0))
56+
return false;
57+
return fread(output, length, 1, fp) == 1;
58+
}
59+
60+
static bool parse_file(esp_partition_info_t *partition_table, size_t *num_partitions, FILE *fp)
5461
{
5562
char message_buffer[256];
56-
esp_partition_info_t partition_table[16]; // ESP_PARTITION_TABLE_MAX_ENTRIES
57-
int num_partitions = 0;
5863
img_info_t img_info;
59-
void *buffer;
60-
int filesize = 0;
61-
FILE *fp;
62-
63-
RG_LOGI("Filename: %s", filename);
64-
rg_display_clear(C_BLACK);
65-
66-
TRY(fp = fopen(filename, "rb"), "File open failed");
67-
TRY(fseek(fp, 0, SEEK_END) == 0, "File seek failed");
68-
TRY(fseek(fp, -sizeof(img_info_t), SEEK_CUR) == 0, "File seek failed");
69-
filesize = ftell(fp); // Size without the footer
70-
TRY(fread(&img_info, sizeof(img_info), 1, fp), "File read failed");
64+
int _num_partitions;
7165

72-
img_info.name[sizeof(img_info.name) - 1] = 0; // Just in case
66+
TRY(fread_at(&img_info, -sizeof(img_info_t), sizeof(img_info_t), fp), "File read failed");
67+
img_info.name[sizeof(img_info.name) - 1] = 0; // Just in case
7368
img_info.version[sizeof(img_info.version) - 1] = 0; // Just in case
7469
img_info.target[sizeof(img_info.target) - 1] = 0; // Just in case
7570

76-
if (memcmp(img_info.magic, RETRO_GO_IMG_MAGIC, 8) != 0) // Invalid image
71+
if (memcmp(img_info.magic, RETRO_GO_IMG_MAGIC, 8) == 0) // Valid retro-go image
7772
{
78-
CONFIRM("Warning", "File is not a valid Retro-Go image.\nContinue anyway?");
73+
if (strcasecmp(img_info.target, RG_TARGET_NAME) != 0) // Wrong target
74+
{
75+
FORMAT("The file appears to be for a different device.\n"
76+
"Current device: %s\n"
77+
"Image device: %s\n\n"
78+
"Do you want to continue anyway?",
79+
RG_TARGET_NAME,
80+
img_info.target);
81+
if (!rg_gui_confirm("Warning", message_buffer, false))
82+
goto fail;
83+
}
84+
// TODO: Validate checksum
85+
FORMAT("Current version: %s\nNew version: %s\n\nContinue?", app->version, img_info.version);
86+
if (!rg_gui_confirm("Warning", message_buffer, false))
87+
goto fail;
7988
}
80-
else if (strcasecmp(img_info.target, RG_TARGET_NAME) != 0) // Wrong target
89+
else if (!rg_gui_confirm("Warning", "File is not a valid Retro-Go image.\nContinue anyway?", false))
8190
{
82-
CONFIRM("Warning",
83-
"The file appears to be for a different device.\n"
84-
"Current device: %s\n"
85-
"Image device: %s\n\n"
86-
"Do you want to continue anyway?",
87-
RG_TARGET_NAME,
88-
img_info.target);
91+
goto fail;
92+
}
93+
// TODO: Also support images that truncate the first 0x1000, just in case
94+
TRY(fread_at(gp_buffer, ESP_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_MAX_LEN, fp), "File read failed");
95+
TRY(esp_partition_table_verify((const esp_partition_info_t *)gp_buffer, true, &_num_partitions) == ESP_OK, "File is not a valid ESP32 image.");
96+
memcpy(partition_table, gp_buffer, sizeof(esp_partition_info_t) * _num_partitions);
97+
*num_partitions = _num_partitions;
98+
return true;
99+
fail:
100+
return false;
101+
}
102+
103+
static bool do_flash(flash_task_t *queue, size_t queue_count, FILE *fp)
104+
{
105+
char message_buffer[256];
106+
rg_gui_option_t lines[queue_count + 4];
107+
// size_t lines_count = 0;
108+
109+
for (size_t i = 0; i < queue_count; ++i)
110+
lines[i] = (rg_gui_option_t){0, queue[i].name, NULL, RG_DIALOG_FLAG_NORMAL, NULL};
111+
lines[queue_count + 0] = (rg_gui_option_t)RG_DIALOG_SEPARATOR;
112+
lines[queue_count + 1] = (rg_gui_option_t){5, "Proceed!", NULL, RG_DIALOG_FLAG_NORMAL, NULL};
113+
lines[queue_count + 2] = (rg_gui_option_t)RG_DIALOG_END;
114+
115+
if (rg_gui_dialog("Partitions to be updated:", lines, -1) != 5)
116+
goto fail;
117+
118+
#define TRY_F(msg, cond, err) \
119+
RG_LOGD("Line: %s", #cond); \
120+
rg_display_clear(C_BLACK); \
121+
lines[i].flags = RG_DIALOG_FLAG_NORMAL; \
122+
lines[i].value = msg; \
123+
rg_gui_draw_dialog("Progress", lines, queue_count, i); \
124+
if (!(cond)) \
125+
{ \
126+
lines[i].value = err; \
127+
rg_gui_draw_dialog("Progress", lines, queue_count, i); \
128+
errors++; \
129+
break; \
89130
}
90-
else // Valid image
131+
132+
int errors = 0;
133+
134+
for (size_t i = 0; i < queue_count; ++i)
135+
lines[i].value = "Pending", lines[i].flags = RG_DIALOG_FLAG_DISABLED;
136+
137+
for (size_t i = 0; i < queue_count; ++i)
91138
{
92-
CONFIRM("Version", "Current version: %s\nNew version: %s\n\nContinue?", app->version, img_info.version);
139+
const flash_task_t *t = &queue[i];
140+
TRY_F("Erasing", spi_flash_erase_range(t->dst.offset, t->dst.size) == ESP_OK || true, "Erase err");
141+
int offset = 0, size = t->src.size;
142+
while (size > 0)
143+
{
144+
int chunk_size = ALIGN_BLOCK(RG_MIN(size, gp_buffer_size), 0x1000);
145+
TRY_F("Reading", fread_at(gp_buffer, t->src.offset + offset, chunk_size, fp), "Read err");
146+
TRY_F("Writing", spi_flash_write(t->dst.offset + offset, gp_buffer, chunk_size) == ESP_OK, "Write err");
147+
offset += chunk_size;
148+
size -= chunk_size;
149+
}
150+
TRY_F("Complete", size == 0, "Failed");
93151
}
94152

95-
// TODO: Also support images that truncate the first 0x1000, just in case
96-
TRY(fseek(fp, ESP_PARTITION_TABLE_OFFSET, SEEK_SET) == 0, "File seek failed");
97-
TRY(fread(&partition_table, sizeof(partition_table), 1, fp), "File read failed");
98-
TRY(esp_partition_table_verify(partition_table, true, &num_partitions) == ESP_OK, "File is not a valid ESP32 image.\nCannot continue.");
153+
lines[queue_count + 0] = (rg_gui_option_t){0, FORMAT(" - %d errors - ", errors), NULL, RG_DIALOG_FLAG_NORMAL, NULL};
154+
lines[queue_count + 1] = (rg_gui_option_t){0, "Complete!", NULL, RG_DIALOG_FLAG_NORMAL, NULL};
155+
lines[queue_count + 2] = (rg_gui_option_t)RG_DIALOG_END;
156+
rg_gui_dialog("Complete", lines, -1);
157+
return true;
158+
fail:
159+
return false;
160+
}
99161

100-
const char *current_partition = esp_ota_get_running_partition()->label;
101-
partition_pair_t queue[num_partitions];
162+
static bool do_update_dangerous(const char *filename)
163+
{
164+
return false;
165+
}
166+
167+
static bool do_update(const char *filename)
168+
{
169+
esp_partition_info_t partition_table[MAX_PARTITIONS] = {0};
170+
size_t num_partitions = 0;
171+
flash_task_t queue[MAX_PARTITIONS] = {0};
102172
size_t queue_count = 0;
173+
char message_buffer[256];
174+
FILE *fp = NULL;
103175

176+
RG_LOGI("Filename: %s", filename);
177+
rg_display_clear(C_BLACK);
178+
179+
TRY(fp = fopen(filename, "rb"), "File open failed");
180+
if (!parse_file(partition_table, &num_partitions, fp))
181+
goto fail;
182+
183+
const char *current_partition = esp_ota_get_running_partition()->label;
104184
// At this time we only flash partitions of type app and subtype ota_X
105-
for (int i = 0; i < num_partitions; ++i)
185+
for (size_t i = 0; i < num_partitions; ++i)
106186
{
107187
const esp_partition_info_t *src = &partition_table[i];
108188
const esp_partition_t *dst = esp_partition_find_first(src->type, ESP_PARTITION_SUBTYPE_ANY, (char *)src->label);
109189

110190
if (src->type != PART_TYPE_APP || (src->subtype & 0xF0) != PART_SUBTYPE_OTA_FLAG)
111-
RG_LOGW("Skipping partition %.15s: Unsupported type.", (char *)src->label);
191+
RG_LOGW("Skipping partition %.16s: Unsupported type.", (char *)src->label);
112192
else if (!dst)
113-
RG_LOGW("Skipping partition %.15s: No match found.", (char *)src->label);
193+
RG_LOGW("Skipping partition %.16s: No match found.", (char *)src->label);
114194
else if ((dst->subtype & 0xF0) != PART_SUBTYPE_OTA_FLAG)
115-
RG_LOGW("Skipping partition %.15s: Not an OTA partition.", (char *)src->label);
195+
RG_LOGW("Skipping partition %.16s: Not an OTA partition.", (char *)src->label);
116196
else if (strncmp(current_partition, dst->label, 16) == 0)
117-
RG_LOGW("Skipping partition %.15s: Currently running.", (char *)src->label);
197+
RG_LOGW("Skipping partition %.16s: Currently running.", (char *)src->label);
118198
else if (dst->size < src->pos.size)
119-
RG_LOGW("Skipping partition %.15s: New partition is bigger.", (char *)src->label);
199+
RG_LOGW("Skipping partition %.16s: New partition is bigger.", (char *)src->label);
120200
else
121201
{
122-
queue[queue_count].src = src;
123-
queue[queue_count].dst = dst;
124-
queue_count++;
202+
RG_LOGI("Partition %.16s can be updated!", (char *)src->label);
203+
flash_task_t *task = memset(&queue[queue_count++], 0, sizeof(flash_task_t));
204+
memcpy(task->name, src->label, 16);
205+
task->src.offset = src->pos.offset;
206+
task->src.size = src->pos.size;
207+
task->dst.offset = dst->address;
208+
task->dst.size = dst->size;
125209
}
126210
}
127211

128-
TRY(queue_count > 0, "Found no updatable partition!");
129-
130-
int pos = 0;
131-
for (size_t i = 0; i < queue_count; ++i)
132-
pos += snprintf(message_buffer + pos, sizeof(message_buffer) - pos, "- %s\n", queue[i].dst->label);
133-
pos += snprintf(message_buffer + pos, sizeof(message_buffer) - pos, "\nProceed?");
134-
135-
if (!rg_gui_confirm("Partitions to be updated:", message_buffer, false))
136-
return false;
137-
138-
TRY(buffer = malloc(2 * 1024 * 1024), "Memory allocation failed");
139-
140-
// TODO: Implement scrolling. Maybe on rg_gui side?
141-
// Or at least support continue writing on the same line. "... Complete"
142-
int y_pos = 9999;
143-
#define SCREEN_PRINTF(color, message...) \
144-
if (y_pos + 16 > rg_display_get_height()) \
145-
rg_display_clear(C_BLACK), y_pos = 0; \
146-
y_pos += rg_gui_draw_text(0, y_pos, 0, FORMAT(message), color, C_BLACK, 0).height;
147-
148-
SCREEN_PRINTF(C_WHITE, "Starting...");
212+
rg_display_clear(C_BLACK);
149213

150-
for (size_t i = 0; i < queue_count; ++i)
214+
if (queue_count == 0)
151215
{
152-
const esp_partition_info_t *src = queue[i].src;
153-
const esp_partition_t *dst = queue[i].dst;
154-
esp_ota_handle_t ota_handle;
155-
esp_err_t ret;
156-
if ((ret = esp_ota_begin(queue[i].dst, 0, &ota_handle)) != ESP_OK)
157-
{
158-
SCREEN_PRINTF(C_RED, "Skipping %s: %s", dst->label, esp_err_to_name(ret));
159-
continue;
160-
}
161-
SCREEN_PRINTF(C_WHITE, "Reading %s from file...", dst->label);
162-
if (fseek(fp, src->pos.offset, SEEK_SET) != 0 || !fread(buffer, src->pos.size, 1, fp))
163-
{
164-
SCREEN_PRINTF(C_RED, "File read error");
165-
continue;
166-
}
167-
SCREEN_PRINTF(C_WHITE, "Writing %s to flash...", dst->label);
168-
if ((ret = esp_ota_write(ota_handle, buffer, src->pos.size)) == ESP_OK) {
169-
SCREEN_PRINTF(C_GREEN, "Complete!");
170-
} else {
171-
SCREEN_PRINTF(C_RED, "Failed: %s", esp_err_to_name(ret));
172-
}
173-
esp_ota_end(ota_handle);
216+
// Try dangerous update
217+
// goto fail;
174218
}
175-
free(buffer);
176219

177-
rg_gui_alert(_("All done"), "The process is complete");
220+
if (!do_flash(queue, queue_count, fp))
221+
goto fail;
222+
223+
fclose(fp);
178224
return true;
225+
fail:
226+
if (fp)
227+
fclose(fp);
228+
return false;
179229
}
180230

181231
void app_main(void)
@@ -187,22 +237,22 @@ void app_main(void)
187237
.isLauncher = true,
188238
});
189239

190-
// if (spi_flash_read(ESP_PARTITION_TABLE_OFFSET, &device_partition_table, sizeof(device_partition_table)) != ESP_OK)
191-
// {
192-
// rg_gui_alert(_("Error"), "Failed to read device's partition table!");
193-
// rg_system_exit();
194-
// }
240+
gp_buffer = rg_alloc(gp_buffer_size, MEM_FAST);
241+
if (!gp_buffer)
242+
RG_PANIC("Memory allocation failed");
195243

196244
// const char *filename = app->romPath;
197245
while (true)
198246
{
199247
char *filename = rg_gui_file_picker("Select update", RG_BASE_PATH_UPDATES, NULL, true, true);
248+
rg_display_clear(C_BLACK);
200249
if (!filename || !*filename)
201250
break;
202251
if (do_update(filename))
203252
break;
204253
free(filename);
205254
}
206255

256+
free(gp_buffer);
207257
rg_system_exit();
208258
}

0 commit comments

Comments
 (0)