#include #include #include #include #include #include #include #include #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 * flashapp->progress_value) / flashapp->progress_max); int32_t progress_width = ((PROGRESS_WIDTH * 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_RED); 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_RED); 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); } }