blob: 4436a27dc9d907252ee18f2e3660997369d96d0d [file] [log] [blame] [edit]
#include <cmath>
#include <cstring>
#include <list>
#include "firmware.hpp"
#include "quadspi.h"
#include "CDCCommandStream.h"
#include "usbd_cdc_if.h"
#include "file.hpp"
#include "executable.hpp"
#include "dialog.hpp"
#include "engine/api_private.hpp"
using namespace blit;
constexpr uint32_t qspi_flash_sector_size = 64 * 1024;
constexpr uint32_t qspi_flash_size = 32768 * 1024;
constexpr uint32_t qspi_flash_address = 0x90000000;
// resevered space for temp/cached files
constexpr uint32_t qspi_tmp_reserved = 4 * 1024 * 1024;
extern CDCCommandStream g_commandStream;
FlashLoader flashLoader;
CDCEraseHandler cdc_erase_handler;
CDCLaunchHandler cdc_launch_handler;
struct GameInfo {
char title[25], author[17];
char category[17];
uint32_t size = 0, checksum = 0;
uint32_t offset = ~0;
};
struct HandlerInfo {
uint32_t offset, meta_offset;
char type[5];
};
std::list<GameInfo> game_list;
std::list<HandlerInfo> handlers; // flashed games that can "launch" files
std::list<std::tuple<uint16_t, uint16_t>> free_space; // block start, count
uint32_t launcher_offset = ~0;
bool flash_scanned = false;
Dialog dialog;
bool cached_file_in_tmp = false;
// metadata
static bool parse_flash_metadata(uint32_t offset, GameInfo &info) {
auto meta_offset = offset + info.size;
auto game_offset = offset;
uint8_t buf[10];
if(qspi_read_buffer(meta_offset, buf, 10) != QSPI_OK)
return false;
if(memcmp(buf, "BLITMETA", 8) != 0)
return false;
RawMetadata raw_meta;
if(qspi_read_buffer(meta_offset + 10, reinterpret_cast<uint8_t *>(&raw_meta), sizeof(RawMetadata)) != QSPI_OK) {
return false;
}
info.size += *reinterpret_cast<uint16_t *>(buf + 8) + 10;
info.checksum = raw_meta.crc32;
memcpy(info.title, raw_meta.title, sizeof(info.title));
memcpy(info.author, raw_meta.author, sizeof(info.author));
offset = meta_offset + sizeof(RawMetadata) + 10;
if(qspi_read_buffer(offset, buf, 8) != QSPI_OK)
return true;
if(memcmp(buf, "BLITTYPE", 8) != 0) {
memcpy(info.category, "none", 5);
return true;
}
// type chunk
RawTypeMetadata type_meta;
if(qspi_read_buffer(offset + 8, reinterpret_cast<uint8_t *>(&type_meta), sizeof(RawTypeMetadata)) != QSPI_OK)
return false;
memcpy(info.category, type_meta.category, sizeof(info.category));
offset += 8 + sizeof(RawTypeMetadata);
// register handler
HandlerInfo handler;
handler.offset = game_offset;
handler.meta_offset = meta_offset;
handler.type[4] = 0;
for(int i = 0; i < type_meta.num_filetypes; i++) {
qspi_read_buffer(offset, (uint8_t *)handler.type, 5);
offset += 5;
handlers.push_back(handler);
}
return true;
}
static bool parse_file_metadata(FIL &fh, GameInfo &info) {
BlitGameHeader header;
UINT bytes_read;
bool result = false;
f_lseek(&fh, 0);
f_read(&fh, &header, sizeof(header), &bytes_read);
// skip relocation data
int off = 0;
if(header.magic == 0x4F4C4552 /* RELO */) {
f_lseek(&fh, 4);
uint32_t num_relocs;
f_read(&fh, (void *)&num_relocs, 4, &bytes_read);
off = num_relocs * 4 + 8;
f_lseek(&fh, off);
// re-read header
f_read(&fh, &header, sizeof(header), &bytes_read);
}
if(header.magic == blit_game_magic) {
uint8_t buf[10];
f_lseek(&fh, (header.end - qspi_flash_address) + off);
f_read(&fh, buf, 10, &bytes_read);
if(bytes_read == 10 && memcmp(buf, "BLITMETA", 8) == 0) {
// don't bother reading the whole thing since we don't want the images
RawMetadata raw_meta;
f_read(&fh, &raw_meta, sizeof(RawMetadata), &bytes_read);
info.size += *reinterpret_cast<uint16_t *>(buf + 8) + 10;
info.checksum = raw_meta.crc32;
memcpy(info.title, raw_meta.title, sizeof(info.title));
memcpy(info.author, raw_meta.author, sizeof(info.author));
result = true;
}
// read category
f_read(&fh, buf, 8, &bytes_read);
if(bytes_read == 8 && memcmp(buf, "BLITTYPE", 8) == 0) {
RawTypeMetadata type_meta;
f_read(&fh, &type_meta, sizeof(RawTypeMetadata), &bytes_read);
memcpy(info.category, type_meta.category, sizeof(info.category));
}
}
return result;
}
static int calc_num_blocks(uint32_t size) {
return (size - 1) / qspi_flash_sector_size + 1;
}
static void erase_qspi_flash(uint32_t start_offset, uint32_t size_bytes) {
uint32_t sector_count = calc_num_blocks(size_bytes);
progress.show("Erasing flash sectors...", sector_count);
for(uint32_t sector = 0; sector < sector_count; sector++) {
qspi_sector_erase(start_offset + sector * qspi_flash_sector_size);
progress.update(sector);
}
progress.hide();
}
// returns true is there is a valid header here
static bool read_flash_game_header(uint32_t offset, BlitGameHeader &header) {
if(qspi_read_buffer(offset, reinterpret_cast<uint8_t *>(&header), sizeof(header)) != QSPI_OK)
return false;
if(header.magic != blit_game_magic)
return false;
// make sure end/size is sensible
if(header.end <= qspi_flash_address)
return false;
return true;
}
static void scan_flash() {
game_list.clear();
free_space.clear();
handlers.clear();
GameInfo game;
uint32_t free_start = 0xFFFFFFFF;
for(uint32_t offset = 0; offset < qspi_flash_size - qspi_tmp_reserved;) {
BlitGameHeader header;
if(!read_flash_game_header(offset, header)) {
if(free_start == 0xFFFFFFFF)
free_start = offset;
offset += qspi_flash_sector_size;
continue;
}
game.offset = offset;
game.size = header.end - qspi_flash_address;
// check for valid metadata
if(parse_flash_metadata(offset, game)) {
// find the launcher
if(strcmp(game.category, "launcher") == 0)
launcher_offset = offset;
// remove old firmware updates
if(strcmp(game.category, "firmware") == 0 && persist.reset_target == prtFirmware) {
int size_blocks = calc_num_blocks(game.size);
erase_qspi_flash(offset, size_blocks * qspi_flash_sector_size);
offset += size_blocks * qspi_flash_sector_size;
continue;
}
}
game_list.push_back(game);
// add free space to list
if(free_start != 0xFFFFFFFF) {
auto start_block = free_start / qspi_flash_sector_size;
auto end_block = offset / qspi_flash_sector_size;
free_space.emplace_back(start_block, end_block - start_block);
free_start = 0xFFFFFFFF;
}
offset += calc_num_blocks(game.size) * qspi_flash_sector_size;
}
// final free
if(free_start != 0xFFFFFFFF) {
auto start_block = free_start / qspi_flash_sector_size;
auto end_block = (qspi_flash_size - qspi_tmp_reserved) / qspi_flash_sector_size;
free_space.emplace_back(start_block, end_block - start_block);
}
}
static bool cleanup_duplicates(GameInfo &new_game, uint32_t new_game_offset) {
bool is_launcher = strcmp(new_game.category, "launcher") == 0;
bool ret = false;
for(auto &game : game_list) {
if(game.offset == new_game_offset)
continue;
bool erased = false;
if(strcmp(game.title, new_game.title) == 0 && strcmp(game.author, new_game.author) == 0) {
erase_qspi_flash(game.offset, game.size);
erased = true;
} else if(is_launcher && strcmp(game.category, "launcher") == 0) {
// flashing a launcher, remove previous launchers
erase_qspi_flash(game.offset, game.size);
erased = true;
}
// we just erased the thing that was running
if(erased && game.offset == persist.last_game_offset)
ret = true;
}
return ret;
}
// returns address to flash file to
static uint32_t get_flash_offset_for_file(uint32_t file_size) {
int file_blocks = calc_num_blocks(file_size);
for(auto space : free_space) {
if(std::get<1>(space) >= file_blocks)
return std::get<0>(space) * qspi_flash_sector_size;
}
// TODO: handle flash full
return 0;
}
static bool flash_buffer(uint32_t offset, uint8_t *buffer, size_t size) {
uint8_t verify_buffer[SD_BUFFER_SIZE];
if(qspi_write_buffer(offset, buffer, size) != QSPI_OK)
return false;
if(qspi_read_buffer(offset, verify_buffer, size) != QSPI_OK)
return false;
// compare buffers
bool verified = true;
for(size_t i = 0; i < size && verified; i++)
verified = buffer[i] == verify_buffer[i];
return verified;
}
// apply relocations to a chunk of a file
static void apply_relocs(uint32_t file_offset, uint32_t flash_base_offset, uint8_t *buffer, size_t buf_len, const std::vector<uint32_t> &relocation_offsets, size_t &cur_reloc) {
if(cur_reloc >= relocation_offsets.size())
return;
for(auto off = file_offset; off < file_offset + buf_len; off += 4) {
if(off == relocation_offsets[cur_reloc]) {
*(uint32_t *)(buffer + off - file_offset) += flash_base_offset;
cur_reloc++;
}
}
}
// Flash a file from the SDCard to external flash
static uint32_t flash_from_sd_to_qspi_flash(FIL &file, uint32_t flash_offset) {
FRESULT res;
// get file length
FSIZE_t bytes_total = f_size(&file);
UINT bytes_read = 0;
FSIZE_t bytes_flashed = 0;
// check for prepended relocation info
char buf[4];
f_lseek(&file, 0);
f_read(&file, buf, 4, &bytes_read);
std::vector<uint32_t> relocation_offsets;
size_t cur_reloc = 0;
bool has_relocs = false;
if(memcmp(buf, "RELO", 4) == 0) {
uint32_t num_relocs;
f_read(&file, (void *)&num_relocs, 4, &bytes_read);
relocation_offsets.reserve(num_relocs);
for(auto i = 0u; i < num_relocs; i++) {
uint32_t reloc_offset;
f_read(&file, (void *)&reloc_offset, 4, &bytes_read);
relocation_offsets.push_back(reloc_offset - qspi_flash_address);
}
bytes_total -= num_relocs * 4 + 8; // size of relocation data
has_relocs = true;
} else {
f_lseek(&file, 0);
}
if(!has_relocs)
flash_offset = 0;
else if(flash_offset == 0xFFFFFFFF)
flash_offset = get_flash_offset_for_file(bytes_total);
// erase the sectors needed to write the image
erase_qspi_flash(flash_offset, bytes_total);
progress.show("Copying from SD card to flash...", bytes_total);
const int buffer_size = SD_BUFFER_SIZE;
uint8_t buffer[buffer_size];
while(bytes_flashed < bytes_total) {
// limited ram so a bit at a time
res = f_read(&file, (void *)buffer, buffer_size, &bytes_read);
if(res != FR_OK)
break;
// relocation patching
apply_relocs(bytes_flashed, flash_offset, buffer, bytes_read, relocation_offsets, cur_reloc);
if(!flash_buffer(bytes_flashed + flash_offset, buffer, bytes_read))
break;
bytes_flashed += bytes_read;
progress.update(bytes_flashed);
}
progress.hide();
// update free space
for(auto &space : free_space) {
if(std::get<0>(space) == flash_offset / qspi_flash_sector_size) {
auto size = calc_num_blocks(bytes_total);
std::get<0>(space) += size;
std::get<1>(space) -= size;
}
}
return bytes_flashed == bytes_total ? flash_offset : 0xFFFFFFFF;
}
// runs a .blit file, flashing it if required
static bool launch_game_from_sd(const char *path, bool auto_delete = false) {
if(is_qspi_memorymapped()) {
qspi_disable_memorymapped_mode();
blit_disable_user_code(); // assume user running
}
uint32_t launch_offset = 0xFFFFFFFF;
uint32_t flash_offset = launch_offset;
FIL file;
FRESULT res = f_open(&file, path, FA_READ);
if(res != FR_OK)
return false;
// get size
// this is a little duplicated...
FSIZE_t bytes_total = f_size(&file);
char buf[8];
UINT read;
f_read(&file, buf, 8, &read);
if(memcmp(buf, "RELO", 4) == 0) {
auto num_relocs = *(uint32_t *)(buf + 4);
bytes_total -= num_relocs * 4 + 8;
}
GameInfo meta;
if(parse_file_metadata(file, meta)) {
for(auto &flash_game : game_list) {
// if a game with the same name/crc is already installed, launch that one instead of flashing it again
if(flash_game.checksum == meta.checksum && strcmp(flash_game.title, meta.title) == 0) {
launch_offset = flash_game.offset;
break;
} else if(strcmp(flash_game.title, meta.title) == 0 && strcmp(flash_game.author, meta.author) == 0) {
// same game, different version
if(calc_num_blocks(flash_game.size) >= calc_num_blocks(bytes_total)) {
flash_offset = flash_game.offset;
break;
}
}
}
// cleanup any other duplicates if we're going to flash
if(launch_offset == 0xFFFFFFFF)
cleanup_duplicates(meta, launch_offset);
}
if(launch_offset == 0xFFFFFFFF)
launch_offset = flash_from_sd_to_qspi_flash(file, flash_offset);
f_close(&file);
if(launch_offset != 0xFFFFFFFF) {
if(auto_delete)
::remove_file(path);
return blit_switch_execution(launch_offset, true);
}
blit_enable_user_code();
return false;
}
// launches a file using the approprite handler
static bool launch_file_from_sd(const char *path) {
persist.launch_path[0] = 0;
if(strncmp(path, "flash:/", 7) == 0) {
return blit_switch_execution(atoi(path + 7) * qspi_flash_sector_size, true);
}
uint32_t launch_offset = 0xFFFFFFFF;
// get the extension (assume there is one)
std::string_view sv(path);
auto ext = std::string(sv.substr(sv.find_last_of('.') + 1));
for(auto &c : ext)
c = tolower(c);
if(ext != "blit") {
// find the handler
for(auto &handler : handlers) {
if(handler.type == ext) {
launch_offset = handler.offset;
break;
}
}
if(launch_offset == 0xFFFFFFFF)
return false;
// set the path to the file to launch
strncpy(persist.launch_path, path, sizeof(persist.launch_path));
return blit_switch_execution(launch_offset, true);
}
// .blit file, install/launch
if(launch_game_from_sd(path))
return true;
return false;
}
static void erase_flash_game(uint32_t offset) {
// reject unaligned
if(offset & (qspi_flash_sector_size - 1))
return;
// attempt to get size, falling back to a single sector
int erase_size = 1;
for(auto &game : game_list) {
if(game.offset == offset) {
erase_size = calc_num_blocks(game.size);
break;
}
}
bool flash_mapped = is_qspi_memorymapped();
if(flash_mapped) {
blit_disable_user_code();
qspi_disable_memorymapped_mode();
}
erase_qspi_flash(offset, erase_size * qspi_flash_sector_size);
// rescan
scan_flash();
if(flash_mapped) {
qspi_enable_memorymapped_mode();
blit_enable_user_code();
}
}
static void *get_type_handler_metadata(const char *filetype) {
for(auto &handler : handlers) {
if(strncmp(filetype, handler.type, 4) == 0)
return (void *)(qspi_flash_address + handler.meta_offset);
}
return nullptr;
}
static const uint8_t *flash_to_tmp(const std::string &filename, uint32_t &size) {
// one file at a time
// TODO: this could be improved
if(cached_file_in_tmp)
return nullptr;
FIL f;
if(f_open(&f, filename.c_str(), FA_READ) != FR_OK)
return nullptr;
size = f_size(&f);
if(f_size(&f) > qspi_tmp_reserved) {
f_close(&f);
return nullptr;
}
// only called through the API, game will be running
blit_disable_user_code();
qspi_disable_memorymapped_mode();
auto flash_offset = qspi_flash_size - qspi_tmp_reserved;
erase_qspi_flash(flash_offset, size);
progress.show("Copying file to cache...", size);
const int buffer_size = SD_BUFFER_SIZE;
uint8_t buffer[buffer_size];
FSIZE_t bytes_flashed = 0;
while(bytes_flashed < size) {
UINT bytes_read;
auto res = f_read(&f, (void *)buffer, buffer_size, &bytes_read);
if(res != FR_OK)
break;
if(!flash_buffer(bytes_flashed + flash_offset, buffer, bytes_read))
break;
bytes_flashed += bytes_read;
progress.update(bytes_flashed);
}
progress.hide();
qspi_enable_memorymapped_mode();
blit_enable_user_code();
f_close(&f);
if(bytes_flashed < size)
return nullptr;
return (const uint8_t *)(qspi_flash_address + flash_offset);
}
static bool blit_is_launcher_running() {
return launcher_offset == persist.last_game_offset;
}
static void tmp_file_closed(const uint8_t *ptr) {
cached_file_in_tmp = false;
}
static void start_launcher() {
if(launcher_offset == 0xFFFFFFFF)
return;
// if the launcher fails to start it's incompatible, ignore it
if(!blit_switch_execution(launcher_offset, false))
launcher_offset = 0xFFFFFFFF;
}
void init() {
api.launch = launch_file_from_sd;
api.erase_game = erase_flash_game;
api.get_type_handler_metadata = get_type_handler_metadata;
api.flash_to_tmp = flash_to_tmp;
api.tmp_file_closed = tmp_file_closed;
scan_flash();
flash_scanned = true;
set_screen_mode(ScreenMode::hires);
// register PROG
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'P', 'R', 'O', 'G'>::value, &flashLoader);
// register SAVE
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'S', 'A', 'V', 'E'>::value, &flashLoader);
// register LS
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'_', '_', 'L', 'S'>::value, &flashLoader);
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'E', 'R', 'S', 'E'>::value, &cdc_erase_handler);
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'L', 'N', 'C', 'H'>::value, &cdc_launch_handler);
// check for updates
if(::file_exists("firmware-update.blit")) {
// TODO: -vx.x.x?
if(launch_game_from_sd("firmware-update.blit", true))
return;
}
// then launcher updates
if(::file_exists("launcher.blit")) {
if(launch_game_from_sd("launcher.blit", true))
return;
}
// auto-launch
if(persist.reset_target == prtGame) {
if(!blit_switch_execution(persist.last_game_offset, false)) {
// failed to start, notify user and switch to launcher
dialog.show("Oops!", "Failed to launch game!", [](bool yes) {
start_launcher();
}, false);
}
// error reset handling
} else if(persist.reset_error) {
dialog.show("Oops!", "Restart game?", [](bool yes){
if(yes)
blit_switch_execution(persist.last_game_offset, false);
else
start_launcher();
persist.reset_error = false;
});
} else
start_launcher();
}
void render(uint32_t time) {
if(flash_scanned && launcher_offset == 0xFFFFFFFF) {
screen.pen = Pen(0, 0, 0);
screen.clear();
screen.pen = Pen(255, 255, 255);
screen.text(
"Please flash a launcher!\n\nUse \"32blit flash launcher.blit\"\nor place launcher.blit on your SD card.",
minimal_font, Point(screen.bounds.w / 2, screen.bounds.h / 2), true, TextAlign::center_center
);
}
progress.draw();
dialog.draw();
}
void update(uint32_t time) {
if(dialog.update())
return;
// no game or launcher running, fix this
if(launcher_offset != 0xFFFFFFFF && !blit_user_code_running())
start_launcher();
}
// below here are the CDC handlers
static void cdc_flash_list() {
bool mapped = is_qspi_memorymapped();
if(mapped)
qspi_disable_memorymapped_mode();
// scan through flash and send offset, size and metadata
for(uint32_t offset = 0; offset < qspi_flash_size;) {
BlitGameHeader header;
if(!read_flash_game_header(offset, header)) {
offset += qspi_flash_sector_size;
continue;
}
uint32_t size = header.end - qspi_flash_address;
// metadata header
uint8_t buf[10];
if(qspi_read_buffer(offset + size, buf, 10) != QSPI_OK)
break;
while(CDC_Transmit_HS((uint8_t *)&offset, 4) == USBD_BUSY){}
while(CDC_Transmit_HS((uint8_t *)&size, 4) == USBD_BUSY){}
uint16_t metadata_len = 0;
if(memcmp(buf, "BLITMETA", 8) == 0)
metadata_len = *reinterpret_cast<uint16_t *>(buf + 8);
while(CDC_Transmit_HS((uint8_t *)"BLITMETA", 8) == USBD_BUSY){}
while(CDC_Transmit_HS((uint8_t *)&metadata_len, 2) == USBD_BUSY){}
// send metadata
uint32_t metadata_offset = offset + size + 10;
while(metadata_len) {
int chunk_size = std::min(256, (int)metadata_len);
uint8_t metadata_buf[256];
if(qspi_read_buffer(metadata_offset, metadata_buf, chunk_size) != QSPI_OK)
break;
while(CDC_Transmit_HS(metadata_buf, chunk_size) == USBD_BUSY){}
metadata_offset += chunk_size;
metadata_len -= chunk_size;
}
offset += calc_num_blocks(size) * qspi_flash_sector_size;
}
// end marker
uint32_t end = 0xFFFFFFFF;
while(CDC_Transmit_HS((uint8_t *)&end, 4) == USBD_BUSY){}
if(mapped)
qspi_enable_memorymapped_mode();
}
// erase command handler
CDCCommandHandler::StreamResult CDCEraseHandler::StreamData(CDCDataStream &dataStream) {
uint32_t offset;
if(!dataStream.Get(offset))
return srNeedData;
erase_flash_game(offset);
return srFinish;
}
CDCCommandHandler::StreamResult CDCLaunchHandler::StreamData(CDCDataStream &dataStream) {
uint8_t byte;
while(dataStream.Get(byte)) {
if(path_off == MAX_FILENAME) {
path_off = 0;
return srError;
}
path[path_off++] = byte;
if(byte == 0) {
path_off = 0;
launch_file_from_sd(path);
return srFinish;
}
}
return srContinue;
}
//////////////////////////////////////////////////////////////////////
// Streaming Code
// The streaming code works with a simple state machine,
// current state is in m_parseState, the states parse index is
// in m_uParseState
//////////////////////////////////////////////////////////////////////
// StreamInit() Initialise state machine
bool FlashLoader::StreamInit(CDCFourCC uCommand) {
switch(uCommand) {
case CDCCommandHandler::CDCFourCCMake<'P', 'R', 'O', 'G'>::value:
dest = Destination::Flash;
m_parseState = stFilename;
m_uParseIndex = 0;
flash_mapped = is_qspi_memorymapped();
if(flash_mapped) {
blit_disable_user_code();
qspi_disable_memorymapped_mode();
}
return true;
case CDCCommandHandler::CDCFourCCMake<'S', 'A', 'V', 'E'>::value:
dest = Destination::SD;
m_parseState = stFilename;
m_uParseIndex = 0;
blit_disable_user_code();
return true;
case CDCCommandHandler::CDCFourCCMake<'_', '_', 'L', 'S'>::value:
cdc_flash_list();
return false;
}
return false;
}
// StreamData() Handle streamed data
// State machine has three states:
// stFilename : Parse filename
// stLength : Parse length, this is sent as an ascii string
// stData : The binary data (.bin file)
CDCCommandHandler::StreamResult FlashLoader::StreamData(CDCDataStream &dataStream)
{
CDCCommandHandler::StreamResult result = srContinue;
uint8_t byte;
while(dataStream.GetStreamLength() && result == srContinue)
{
switch (m_parseState)
{
case stFilename:
if(m_uParseIndex < MAX_FILENAME)
{
while(result == srContinue && m_parseState == stFilename && dataStream.Get(byte))
{
m_sFilename[m_uParseIndex++] = byte;
if (byte == 0)
{
m_parseState = stLength;
m_uParseIndex = 0;
}
}
}
else
{
debugf("Failed to read filename\n\r");
result =srError;
}
break;
case stLength:
if(m_uParseIndex < MAX_FILELEN)
{
while(result == srContinue && m_parseState == stLength && dataStream.Get(byte))
{
m_sFilelen[m_uParseIndex++] = byte;
if (byte == 0)
{
m_parseState = stRelocs;
m_uParseIndex = 0;
char *pEndPtr;
m_uFilelen = strtoul(m_sFilelen, &pEndPtr, 10);
if(!m_uFilelen) {
debugf("Failed to parse filelen\n\r");
result =srError;
}
}
}
}
else
{
debugf("Failed to read filelen\n\r");
result =srError;
}
break;
case stRelocs: {
uint32_t word;
if(dest == Destination::SD) {
// writing to SD, pass the relocs through
m_parseState = stData;
if(!prepare_for_data())
result = srError;
} else if(m_uParseIndex > 1 && m_uParseIndex == num_relocs + 2) {
m_parseState = stData;
m_uParseIndex = 0;
// got relocs, prepare to flash
if(!prepare_for_data())
result = srError;
} else {
// break out of loop as we need more data
if(dataStream.GetStreamLength() < 4)
return srContinue;
while(result == srContinue && dataStream.Get(word)) {
if(m_uParseIndex == 0 && word != 0x4F4C4552 /*RELO*/) {
debugf("Missing relocation header\n");
result = srError;
} else if(m_uParseIndex == 1) {
num_relocs = word;
m_uFilelen -= num_relocs * 4 + 8;
relocation_offsets.clear();
relocation_offsets.reserve(num_relocs);
} else if(m_uParseIndex)
relocation_offsets.push_back(word - qspi_flash_address);
m_uParseIndex++;
// done
if(m_uParseIndex == num_relocs + 2)
break;
}
}
break;
}
case stData:
while((result == srContinue) && (m_parseState == stData) && (m_uParseIndex <= m_uFilelen) && dataStream.Get(byte)) {
uint32_t uByteOffset = m_uParseIndex % PAGE_SIZE;
buffer[uByteOffset] = byte;
// check buffer needs writing
volatile uint32_t uWriteLen = 0;
bool bEOS = false;
if (m_uParseIndex == m_uFilelen-1) {
// end of file
uWriteLen = uByteOffset+1;
bEOS = true;
} else if(uByteOffset == PAGE_SIZE-1)
uWriteLen = PAGE_SIZE; // buffer full
else {
// keep filling buffer
m_uParseIndex++;
continue;
}
if(dest == Destination::SD) {
// save data
UINT uWritten;
FRESULT res = f_write(&file, buffer, uWriteLen, &uWritten);
if(res != FR_OK || uWritten != uWriteLen) {
debugf("Failed to save to SDCard\n\r");
result = srError;
}
} else {
// flash
uint32_t offset = (m_uParseIndex / PAGE_SIZE) * PAGE_SIZE;
// relocation patching
apply_relocs(offset, flash_start_offset, buffer, uWriteLen, relocation_offsets, cur_reloc);
// save data
if(!flash_buffer(flash_start_offset + offset, buffer, uWriteLen)) {
debugf("Failed to write to flash\n\r");
result = srError;
}
}
progress.update(m_uParseIndex + 1);
if(bEOS) {
if(result != srError) {
while(CDC_Transmit_HS((uint8_t *)"32BL__OK", 8) == USBD_BUSY){}
if(dest == Destination::Flash) {
// return the block we used
uint16_t block = flash_start_offset / qspi_flash_sector_size;
while(CDC_Transmit_HS((uint8_t *)&block, 2) == USBD_BUSY){}
}
result = srFinish;
}
progress.hide();
handle_data_end(result != srError);
}
m_uParseIndex++;
m_uBytesHandled = m_uParseIndex;
}
break;
}
}
if(result == srError) {
progress.hide();
if(flash_mapped) {
qspi_enable_memorymapped_mode();
flash_mapped = false;
}
blit_enable_user_code();
}
return result;
}
bool FlashLoader::prepare_for_data() {
// about to start receiving data
if(dest == Destination::SD) {
FRESULT res = f_open(&file, m_sFilename, FA_CREATE_ALWAYS | FA_WRITE);
if(res) {
debugf("Failed to create file (%s)\n\r", m_sFilename);
return false;
} else {
char buf[300];
snprintf(buf, 300, "Saving %s to SD card...", m_sFilename);
progress.show(buf, m_uFilelen);
}
} else {
// flash
cur_reloc = 0;
flash_start_offset = get_flash_offset_for_file(m_uFilelen);
// erase
erase_qspi_flash(flash_start_offset, m_uFilelen);
char buf[300];
snprintf(buf, 300, "Saving %s to flash...", m_sFilename);
progress.show(buf, m_uFilelen);
}
return true;
}
void FlashLoader::handle_data_end(bool success) {
// done receiving data
if(dest == Destination::SD) {
f_close(&file);
blit_enable_user_code();
} else {
// flash
if(success) {
// clean up old version(s)
BlitGameHeader header;
read_flash_game_header(flash_start_offset, header);
GameInfo meta;
meta.size = header.end - qspi_flash_address;
if(parse_flash_metadata(flash_start_offset, meta)) {
bool erased_running = cleanup_duplicates(meta, flash_start_offset);
if(erased_running || strcmp(meta.category, "firmware") == 0) {
// if we just erased the thing we were running, we need to reset to not crash
// also need to do firmware updates immediately
// update the launcher offset
if(strcmp(meta.category, "launcher") == 0)
launcher_offset = flash_start_offset;
blit_switch_execution(flash_start_offset, true);
return;
}
}
scan_flash();
if(flash_mapped) {
qspi_enable_memorymapped_mode();
flash_mapped = false;
}
blit_enable_user_code();
if(blit_is_launcher_running()) {
auto game_header = (BlitGameHeader *) (qspi_flash_address + persist.last_game_offset);
auto init = (BlitInitFunction)((uint8_t *)game_header->init + persist.last_game_offset);
init(persist.last_game_offset);
}
}
}
}