mirror of
https://github.com/kbeckmann/game-and-watch-retro-go.git
synced 2025-12-17 19:16:02 +01:00
545 lines
17 KiB
C
545 lines
17 KiB
C
#include <odroid_system.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "githash.h"
|
|
#include "gui.h"
|
|
#include "gw_buttons.h"
|
|
#include "gw_flash.h"
|
|
#include "gw_lcd.h"
|
|
#include "gw_linker.h"
|
|
#include "main.h"
|
|
#include "rg_emulators.h"
|
|
#include "rg_favorites.h"
|
|
#include "utils.h"
|
|
#include "sha256.h"
|
|
|
|
#define DBG(...) printf(__VA_ARGS__)
|
|
// #define DBG(...)
|
|
|
|
#define STATUS_HEIGHT (33)
|
|
#define HEADER_HEIGHT (47)
|
|
#define IMAGE_BANNER_HEIGHT (32)
|
|
#define IMAGE_BANNER_WIDTH (ODROID_SCREEN_WIDTH)
|
|
|
|
static const int font_height = 8; //odroid_overlay_get_font_size();
|
|
static const int font_width = 8; //odroid_overlay_get_font_width();
|
|
|
|
#define LIST_X_OFFSET (0)
|
|
#define LIST_Y_OFFSET (STATUS_HEIGHT)
|
|
#define LIST_WIDTH (ODROID_SCREEN_WIDTH)
|
|
#define LIST_HEIGHT (ODROID_SCREEN_HEIGHT - STATUS_HEIGHT - HEADER_HEIGHT)
|
|
#define LIST_LINE_HEIGHT (font_height + 2)
|
|
#define LIST_LINE_COUNT (LIST_HEIGHT / LIST_LINE_HEIGHT)
|
|
|
|
#define PROGRESS_X_OFFSET (ODROID_SCREEN_WIDTH / 5 / 2)
|
|
#define PROGRESS_Y_OFFSET (LIST_Y_OFFSET + 6 * LIST_LINE_HEIGHT)
|
|
#define PROGRESS_WIDTH (4 * (PROGRESS_X_OFFSET * 2))
|
|
#define PROGRESS_HEIGHT (2 * LIST_LINE_HEIGHT)
|
|
|
|
// TODO: Make this nicer
|
|
extern OSPI_HandleTypeDef hospi1;
|
|
|
|
typedef enum {
|
|
FLASHAPP_INIT = 0x00,
|
|
FLASHAPP_IDLE = 0x01,
|
|
FLASHAPP_START = 0x02,
|
|
FLASHAPP_CHECK_HASH_RAM_NEXT = 0x03,
|
|
FLASHAPP_CHECK_HASH_RAM = 0x04,
|
|
FLASHAPP_ERASE_NEXT = 0x05,
|
|
FLASHAPP_ERASE = 0x06,
|
|
FLASHAPP_PROGRAM_NEXT = 0x07,
|
|
FLASHAPP_PROGRAM = 0x08,
|
|
FLASHAPP_CHECK_HASH_FLASH_NEXT = 0x09,
|
|
FLASHAPP_CHECK_HASH_FLASH = 0x0A,
|
|
|
|
FLASHAPP_TEST_NEXT = 0x0B,
|
|
FLASHAPP_TEST = 0x0C,
|
|
|
|
FLASHAPP_FINAL = 0x0D,
|
|
FLASHAPP_ERROR = 0x0E,
|
|
} flashapp_state_t;
|
|
|
|
typedef enum {
|
|
FLASHAPP_STATUS_BAD_HASH_RAM = 0xbad00001,
|
|
FLASHAPP_STATUS_BAD_HAS_FLASH = 0xbad00002,
|
|
FLASHAPP_STATUS_NOT_ALIGNED = 0xbad00003,
|
|
|
|
FLASHAPP_STATUS_IDLE = 0xcafe0000,
|
|
FLASHAPP_STATUS_DONE = 0xcafe0001,
|
|
FLASHAPP_STATUS_BUSY = 0xcafe0002,
|
|
} flashapp_status_t;
|
|
|
|
typedef struct {
|
|
tab_t tab;
|
|
uint32_t erase_address;
|
|
uint32_t erase_bytes_left;
|
|
uint32_t current_program_address;
|
|
uint32_t program_bytes_left;
|
|
uint8_t* program_buf;
|
|
uint32_t progress_max;
|
|
uint32_t progress_value;
|
|
} flashapp_t;
|
|
|
|
// framebuffer1 is used as an actual framebuffer.
|
|
// framebuffer2 and onwards is used as a buffer for the flash.
|
|
static uint8_t *flash_buffer = (uint8_t *) framebuffer2;
|
|
|
|
// Values below are read or written by the debugger
|
|
|
|
// Store state in a uint32_t
|
|
uint32_t flashapp_state;
|
|
|
|
// Set to non-zero to start programming
|
|
uint32_t program_start;
|
|
|
|
// Status register
|
|
uint32_t program_status;
|
|
|
|
// Number of bytes to program in the flash
|
|
uint32_t program_size;
|
|
|
|
// Where to program in the flash
|
|
uint32_t program_address;
|
|
|
|
// Control if chip should be erased or not
|
|
uint32_t program_erase;
|
|
|
|
// Number of bytes to be erased from program_address
|
|
int32_t program_erase_bytes;
|
|
|
|
// Current chunk index
|
|
uint32_t program_chunk_idx;
|
|
|
|
// Number of chunks
|
|
uint32_t program_chunk_count;
|
|
|
|
// The expected sha256 of the loaded binary
|
|
uint8_t program_expected_sha256[65];
|
|
|
|
// TODO: Expose properly
|
|
int odroid_overlay_draw_text_line(uint16_t x_pos,
|
|
uint16_t y_pos,
|
|
uint16_t width,
|
|
const char *text,
|
|
uint16_t color,
|
|
uint16_t color_bg);
|
|
|
|
static void draw_text_line_centered(uint16_t y_pos,
|
|
const char *text,
|
|
uint16_t color,
|
|
uint16_t color_bg)
|
|
{
|
|
int width = strlen(text) * font_width;
|
|
int x_pos = ODROID_SCREEN_WIDTH / 2 - width / 2;
|
|
|
|
odroid_overlay_draw_text_line(x_pos, y_pos, width, text, color, color_bg);
|
|
}
|
|
|
|
static void draw_progress(flashapp_t *flashapp)
|
|
{
|
|
char progress_str[16];
|
|
|
|
odroid_overlay_draw_fill_rect(0, LIST_Y_OFFSET, LIST_WIDTH, LIST_HEIGHT, C_BLACK);
|
|
draw_text_line_centered(LIST_Y_OFFSET + 2 * LIST_LINE_HEIGHT, flashapp->tab.name, C_GW_YELLOW, C_BLACK);
|
|
|
|
if (flashapp->progress_max != 0) {
|
|
int32_t progress_percent = (100 * (uint64_t)flashapp->progress_value) / flashapp->progress_max;
|
|
int32_t progress_width = (PROGRESS_WIDTH * (uint64_t)flashapp->progress_value) / flashapp->progress_max;
|
|
|
|
sprintf(progress_str, "%ld%%", progress_percent);
|
|
|
|
odroid_overlay_draw_fill_rect(PROGRESS_X_OFFSET,
|
|
PROGRESS_Y_OFFSET,
|
|
PROGRESS_WIDTH,
|
|
PROGRESS_HEIGHT,
|
|
C_GW_MAIN_COLOR);
|
|
|
|
odroid_overlay_draw_fill_rect(PROGRESS_X_OFFSET,
|
|
PROGRESS_Y_OFFSET,
|
|
progress_width,
|
|
PROGRESS_HEIGHT,
|
|
C_GW_YELLOW);
|
|
|
|
draw_text_line_centered(LIST_Y_OFFSET + 4 * LIST_LINE_HEIGHT, progress_str, C_GW_YELLOW, C_BLACK);
|
|
}
|
|
}
|
|
|
|
static void redraw(flashapp_t *flashapp)
|
|
{
|
|
// Re-use header, status and footer from the retro-go code
|
|
gui_draw_header(&flashapp->tab);
|
|
gui_draw_status(&flashapp->tab);
|
|
|
|
// Empty logo
|
|
odroid_overlay_draw_fill_rect(0, ODROID_SCREEN_HEIGHT - IMAGE_BANNER_HEIGHT - 15,
|
|
IMAGE_BANNER_WIDTH, IMAGE_BANNER_HEIGHT, C_GW_MAIN_COLOR);
|
|
|
|
draw_progress(flashapp);
|
|
lcd_swap();
|
|
}
|
|
|
|
|
|
static bool validate_erased(uint32_t address, uint32_t size)
|
|
{
|
|
assert((size & 0b11) == 0);
|
|
assert((address & 0b11) == 0);
|
|
|
|
uint32_t *flash_ptr_u32 = (uint32_t *)(0x90000000 + address);
|
|
|
|
for (uint32_t i = 0; i < size / 4; i++) {
|
|
if (flash_ptr_u32[i] != 0xffffffff) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint32_t xorshift32(uint32_t *state)
|
|
{
|
|
uint32_t x = *state;
|
|
x ^= x << 13;
|
|
x ^= x >> 17;
|
|
x ^= x << 5;
|
|
return *state = x;
|
|
}
|
|
|
|
static void generate_random(uint32_t *buf, uint32_t size, uint32_t seed)
|
|
{
|
|
for (int i = 0; i < size / 4; i++) {
|
|
buf[i] = xorshift32(&seed);
|
|
}
|
|
}
|
|
|
|
const uint32_t tests[][2] = {
|
|
// start, end
|
|
{ 0 * 1024, 4 * 1024 }, // 1 * 4k
|
|
{ 32 * 1024, 64 * 1024 }, // 1 * 32k
|
|
{ 64 * 1024, 128 * 1024 }, // 1 * 64k
|
|
{ 252 * 1024, 260 * 1024 }, // 8k = 2 * 4k
|
|
{ 384 * 1024, 508 * 1024 }, // 124k = 64k + 32k + 7 * 4k
|
|
};
|
|
|
|
static uint32_t test_read(uint32_t addr, uint32_t len)
|
|
{
|
|
const uint8_t *flash_ptr = (const uint8_t *) 0x90000000;
|
|
uint32_t t0;
|
|
uint32_t t1;
|
|
|
|
t0 = HAL_GetTick();
|
|
while (len != 0) {
|
|
uint32_t chunk_len = (len > sizeof(emulator_framebuffer)) ? sizeof(emulator_framebuffer) : len;
|
|
memcpy(emulator_framebuffer, flash_ptr, chunk_len);
|
|
flash_ptr += chunk_len;
|
|
len -= chunk_len;
|
|
}
|
|
t1 = HAL_GetTick();
|
|
|
|
return t1 - t0;
|
|
}
|
|
|
|
static void test_flash(flashapp_t *flashapp)
|
|
{
|
|
const uint8_t *flash_ptr = (const uint8_t *) 0x90000000;
|
|
uint32_t address;
|
|
uint32_t size;
|
|
uint32_t start;
|
|
uint32_t end;
|
|
|
|
const uint32_t rand_size = 512 * 1024;
|
|
|
|
sprintf(flashapp->tab.status, " Game and Watch Flash App TEST");
|
|
sprintf(flashapp->tab.name, "Erase and program..");
|
|
lcd_swap();
|
|
lcd_wait_for_vblank();
|
|
redraw(flashapp);
|
|
|
|
// Erase 512kB at 0
|
|
address = 0;
|
|
size = rand_size;
|
|
OSPI_DisableMemoryMappedMode();
|
|
OSPI_EraseSync(address, size);
|
|
OSPI_EnableMemoryMappedMode();
|
|
assert(validate_erased(address, size));
|
|
|
|
generate_random((uint32_t *) flash_buffer, rand_size, 0x12345678);
|
|
|
|
// Write and verify 512kB random data
|
|
address = 0;
|
|
size = rand_size;
|
|
OSPI_DisableMemoryMappedMode();
|
|
OSPI_Program(address, &flash_buffer[address], size);
|
|
OSPI_EnableMemoryMappedMode();
|
|
|
|
// Erase parts of the flash and verify that the non erased data is still intact
|
|
for (int i = 0; i < ARRAY_SIZE(tests); i++) {
|
|
start = tests[i][0];
|
|
end = tests[i][1];
|
|
size = end - start;
|
|
|
|
DBG("Erase test %d start=%08lx end=%08lx\n", i, start, end);
|
|
sprintf(flashapp->tab.name, "Erase test %d start=%08lx end=%08lx", i, start, end);
|
|
|
|
// Check that data is ok first
|
|
assert(memcmp(&flash_ptr[start], &flash_buffer[start], size) == 0);
|
|
|
|
// Erase
|
|
OSPI_DisableMemoryMappedMode();
|
|
OSPI_EraseSync(start, size);
|
|
OSPI_EnableMemoryMappedMode();
|
|
|
|
// Check that erased is actually erased
|
|
assert(validate_erased(start, size));
|
|
|
|
// Check that the rest of the data is still there
|
|
assert(memcmp(&flash_ptr[start + size], &flash_buffer[start + size], rand_size - end) == 0);
|
|
|
|
lcd_swap();
|
|
lcd_wait_for_vblank();
|
|
redraw(flashapp);
|
|
}
|
|
|
|
DBG("[OK] Erase ok\n");
|
|
sprintf(flashapp->tab.name, "Flash diag test OK");
|
|
redraw(flashapp);
|
|
|
|
// Do a read test of the first 1MB
|
|
uint32_t read_ms = test_read(0, 1024 * 1024);
|
|
sprintf(flashapp->tab.name, "All OK. Flash read: %ld.%02ld MB/s", 1024 / read_ms, (100 * 1024 / read_ms) % 100);
|
|
lcd_swap();
|
|
lcd_wait_for_vblank();
|
|
redraw(flashapp);
|
|
}
|
|
|
|
static void state_set(flashapp_state_t state_next)
|
|
{
|
|
printf("State: %ld -> %d\n", flashapp_state, state_next);
|
|
|
|
flashapp_state = state_next;
|
|
}
|
|
|
|
static void state_inc(void)
|
|
{
|
|
state_set(flashapp_state + 1);
|
|
}
|
|
|
|
static void flashapp_run(flashapp_t *flashapp)
|
|
{
|
|
uint8_t program_calculated_sha256[65];
|
|
|
|
switch (flashapp_state) {
|
|
case FLASHAPP_INIT:
|
|
// Clear variables shared with the host
|
|
program_size = 0;
|
|
program_address = 0;
|
|
program_status = 0;
|
|
program_erase = 0;
|
|
program_erase_bytes = 0;
|
|
program_chunk_idx = 1;
|
|
program_chunk_count = 1;
|
|
memset(program_expected_sha256, 0, sizeof(program_expected_sha256));
|
|
memset(program_calculated_sha256, 0, sizeof(program_calculated_sha256));
|
|
|
|
flashapp->progress_value = 0;
|
|
flashapp->progress_max = 0;
|
|
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_IDLE:
|
|
sprintf(flashapp->tab.name, "1. Waiting for data");
|
|
|
|
// Notify that we are ready to start
|
|
program_status = FLASHAPP_STATUS_IDLE;
|
|
flashapp->progress_value = 0;
|
|
flashapp->progress_max = 0;
|
|
|
|
// program_start is set by the flash script
|
|
switch (program_start) {
|
|
case 1: // Normal flash operation
|
|
program_start = 0;
|
|
state_inc();
|
|
break;
|
|
case 2: // Test flash
|
|
program_start = 0;
|
|
state_set(FLASHAPP_TEST_NEXT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case FLASHAPP_START:
|
|
program_status = FLASHAPP_STATUS_BUSY;
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_CHECK_HASH_RAM_NEXT:
|
|
sprintf(flashapp->tab.name, "2. Checking hash in RAM (%ld bytes)", program_size);
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_CHECK_HASH_RAM:
|
|
// Calculate sha256 hash of the RAM first
|
|
sha256_to_string(program_calculated_sha256, (const BYTE*) flash_buffer, program_size);
|
|
|
|
if (strncmp((char *)program_calculated_sha256, (char *)program_expected_sha256, 64) != 0) {
|
|
// Hashes don't match even in RAM, openocd loading failed.
|
|
sprintf(flashapp->tab.name, "*** Hash mismatch in RAM ***");
|
|
program_status = FLASHAPP_STATUS_BAD_HASH_RAM;
|
|
state_set(FLASHAPP_ERROR);
|
|
break;
|
|
} else {
|
|
sprintf(flashapp->tab.name, "3. Hash OK in RAM");
|
|
state_inc();
|
|
}
|
|
break;
|
|
case FLASHAPP_ERASE_NEXT:
|
|
OSPI_DisableMemoryMappedMode();
|
|
|
|
if (program_erase) {
|
|
if (program_erase_bytes == 0) {
|
|
sprintf(flashapp->tab.name, "4. Performing Chip Erase (takes time)");
|
|
} else {
|
|
flashapp->erase_address = program_address;
|
|
flashapp->erase_bytes_left = program_erase_bytes;
|
|
|
|
uint32_t smallest_erase = OSPI_GetSmallestEraseSize();
|
|
|
|
if (flashapp->erase_address & (smallest_erase - 1)) {
|
|
sprintf(flashapp->tab.name, "** Address not aligned to smallest erase size! **");
|
|
program_status = FLASHAPP_STATUS_NOT_ALIGNED;
|
|
state_set(FLASHAPP_ERROR);
|
|
break;
|
|
}
|
|
|
|
// Round size up to nearest erase size if needed ?
|
|
if ((flashapp->erase_bytes_left & (smallest_erase - 1)) != 0) {
|
|
flashapp->erase_bytes_left += smallest_erase - (flashapp->erase_bytes_left & (smallest_erase - 1));
|
|
}
|
|
|
|
sprintf(flashapp->tab.name, "4. Erasing %ld bytes...", flashapp->erase_bytes_left);
|
|
printf("Erasing %ld bytes at 0x%08lx\n", flashapp->erase_bytes_left, flashapp->erase_address);
|
|
flashapp->progress_max = program_erase_bytes;
|
|
flashapp->progress_value = 0;
|
|
}
|
|
state_inc();
|
|
} else {
|
|
state_set(FLASHAPP_PROGRAM_NEXT);
|
|
}
|
|
break;
|
|
case FLASHAPP_ERASE:
|
|
if (program_erase_bytes == 0) {
|
|
OSPI_NOR_WriteEnable();
|
|
OSPI_ChipErase();
|
|
state_inc();
|
|
} else {
|
|
if (OSPI_Erase(&flashapp->erase_address, &flashapp->erase_bytes_left)) {
|
|
flashapp->progress_max = 0;
|
|
state_inc();
|
|
}
|
|
flashapp->progress_value = flashapp->progress_max - flashapp->erase_bytes_left;
|
|
}
|
|
break;
|
|
case FLASHAPP_PROGRAM_NEXT:
|
|
sprintf(flashapp->tab.name, "5. Programming...");
|
|
flashapp->progress_value = 0;
|
|
flashapp->progress_max = program_size;
|
|
flashapp->current_program_address = program_address;
|
|
flashapp->program_bytes_left = program_size;
|
|
flashapp->program_buf = flash_buffer;
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_PROGRAM:
|
|
if (flashapp->program_bytes_left > 0) {
|
|
uint32_t dest_page = flashapp->current_program_address / 256;
|
|
uint32_t bytes_to_write = flashapp->program_bytes_left > 256 ? 256 : flashapp->program_bytes_left;
|
|
OSPI_NOR_WriteEnable();
|
|
OSPI_PageProgram(dest_page * 256, flashapp->program_buf, bytes_to_write);
|
|
flashapp->current_program_address += bytes_to_write;
|
|
flashapp->program_buf += bytes_to_write;
|
|
flashapp->program_bytes_left -= bytes_to_write;
|
|
flashapp->progress_value = program_size - flashapp->program_bytes_left;
|
|
} else {
|
|
state_inc();
|
|
}
|
|
break;
|
|
case FLASHAPP_CHECK_HASH_FLASH_NEXT:
|
|
sprintf(flashapp->tab.name, "6. Checking hash in FLASH");
|
|
OSPI_EnableMemoryMappedMode();
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_CHECK_HASH_FLASH:
|
|
// Calculate sha256 hash of the FLASH.
|
|
sha256_to_string(program_calculated_sha256,
|
|
(const BYTE*) (0x90000000 + program_address),
|
|
program_size);
|
|
|
|
if (strncmp((char *)program_calculated_sha256, (char *)program_expected_sha256, 64) != 0) {
|
|
// Hashes don't match in FLASH, programming failed.
|
|
sprintf(flashapp->tab.name, "*** Hash mismatch in FLASH ***");
|
|
program_status = FLASHAPP_STATUS_BAD_HAS_FLASH;
|
|
state_set(FLASHAPP_ERROR);
|
|
} else {
|
|
sprintf(flashapp->tab.name, "7. Hash OK in FLASH.");
|
|
|
|
if (program_chunk_idx != program_chunk_count) {
|
|
// More chunks will be programmed, skip the init state.
|
|
program_chunk_idx++;
|
|
state_set(FLASHAPP_IDLE);
|
|
} else {
|
|
sprintf(flashapp->tab.name, "Programming done!");
|
|
program_status = FLASHAPP_STATUS_DONE;
|
|
state_set(FLASHAPP_FINAL);
|
|
}
|
|
}
|
|
break;
|
|
case FLASHAPP_TEST_NEXT:
|
|
test_flash(flashapp);
|
|
state_inc();
|
|
break;
|
|
case FLASHAPP_TEST:
|
|
case FLASHAPP_FINAL:
|
|
case FLASHAPP_ERROR:
|
|
// Stay in state until reset.
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void flashapp_main(void)
|
|
{
|
|
flashapp_t flashapp = {};
|
|
|
|
SCB_InvalidateDCache();
|
|
SCB_DisableDCache();
|
|
|
|
odroid_system_init(0, 32000);
|
|
lcd_set_buffers(framebuffer1, framebuffer1);
|
|
|
|
while (true) {
|
|
if (program_chunk_count == 1) {
|
|
sprintf(flashapp.tab.status, " Game and Watch Flash App");
|
|
} else {
|
|
sprintf(flashapp.tab.status, " Game and Watch Flash App (%ld/%ld)",
|
|
program_chunk_idx, program_chunk_count);
|
|
}
|
|
|
|
// Run multiple times to skip rendering when programming
|
|
for (int i = 0; i < 128; i++) {
|
|
wdog_refresh();
|
|
flashapp_run(&flashapp);
|
|
if (flashapp_state != FLASHAPP_PROGRAM) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lcd_swap();
|
|
lcd_wait_for_vblank();
|
|
redraw(&flashapp);
|
|
}
|
|
}
|