blob: c569ec7713637f9508c2f3ee748a75c109312948 [file] [log] [blame] [edit]
#include "firmware.hpp"
#include "graphics/color.hpp"
#include <cmath>
#include "quadspi.h"
#include "CDCCommandStream.h"
#include "USBManager.h"
#include "file.hpp"
#include <cstring>
#include <stdio.h>
#include <stdlib.h>
using namespace blit;
enum State {stFlashFile, stSaveFile, stFlashCDC, stLS, stMassStorage};
constexpr uint32_t qspi_flash_sector_size = 64 * 1024;
Vec2 file_list_scroll_offset(20.0f, 0.0f);
extern CDCCommandStream g_commandStream;
FlashLoader flashLoader;
extern USBManager g_usbManager;
std::vector<blit::FileInfo> files;
int32_t max_width_size = 0;
SortBy file_sort = SortBy::name;
uint8_t buffer[PAGE_SIZE];
uint8_t verify_buffer[PAGE_SIZE];
State state = stFlashFile;
FIL file;
// error dialog
int selected_dialog_option = 0;
bool flash_from_sd_to_qspi_flash(const char *filename);
void erase_qspi_flash(uint32_t start_sector, uint32_t size_bytes) {
uint32_t sector_count = (size_bytes / qspi_flash_sector_size) + 1;
progress.show("Erasing flash sectors...", sector_count);
for(uint32_t sector = 0; sector < sector_count; sector++) {
qspi_sector_erase((start_sector + sector) * qspi_flash_sector_size);
progress.update(sector);
}
progress.hide();
}
void sort_file_list() {
using Iterator = std::vector<FileInfo>::iterator;
using Compare = bool(const FileInfo &, const FileInfo &);
if (file_sort == SortBy::name) {
// Sort by filename
std::sort<Iterator, Compare>(files.begin(), files.end(), [](const auto &a, const auto &b) { return a.name < b.name; });
}
if (file_sort == SortBy::size) {
// Sort by filesize
std::sort<Iterator, Compare>(files.begin(), files.end(), [](const auto &a, const auto &b) { return a.size < b.size; });
}
}
void load_file_list() {
files.clear();
max_width_size = 0;
for(auto &file : ::list_files("/")) {
if(file.flags & blit::FileFlags::directory)
continue;
if(file.name.length() < 4)
continue;
if(file.name.compare(file.name.length() - 4, 4, ".bin") == 0 || file.name.compare(file.name.length() - 4, 4, ".BIN") == 0) {
files.push_back(file);
max_width_size = std::max(max_width_size, screen.measure_text(std::to_string(file.size), minimal_font).w);
}
}
sort_file_list();
if(persist.selected_menu_item > files.size()) {
persist.selected_menu_item = files.size() - 1;
}
}
void mass_storage_overlay(uint32_t time)
{
static uint8_t uActivityAnim = 0;
screen.pen = Pen(0, 0, 0, 200);
screen.clear();
screen.pen = Pen(255, 255, 255);
char buffer[128];
sprintf(buffer, "Mass Storage mode (%s)", g_usbManager.GetStateName());
screen.text(buffer, minimal_font, Rect(Point(0), screen.bounds), true, TextAlign::center_center);
if(uActivityAnim)
{
screen.pen = Pen(0, 255, 0, uActivityAnim);
screen.circle(Point(320-6, 6), 6);
uActivityAnim = uActivityAnim>>1;
}
else
{
if(g_usbManager.HasHadActivity())
uActivityAnim = 255;
}
}
void init() {
blit::set_screen_mode(ScreenMode::hires);
load_file_list();
// 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);
}
void render(uint32_t time) {
screen.pen = Pen(5, 8, 12);
screen.clear();
screen.pen = Pen(0, 0, 0, 100);
screen.rectangle(Rect(10, 0, 100, 240));
// list files on SD card
if(!files.empty()) {
int y = 115 - file_list_scroll_offset.y;
// adjust alignment rect for vertical spacing
const int text_align_height = ROW_HEIGHT + minimal_font.spacing_y;
const int size_x = 115;
uint32_t i = 0;
for(auto &file : files) {
if(i++ == persist.selected_menu_item)
screen.pen = Pen(235, 245, 255);
else
screen.pen = Pen(80, 100, 120);
screen.text(file.name, minimal_font, Rect(file_list_scroll_offset.x, y, 100 - 20, text_align_height), true, TextAlign::center_v);
screen.line(Point(size_x - 4, y), Point(size_x - 4, y + ROW_HEIGHT));
screen.text(std::to_string(file.size), minimal_font, Rect(size_x, y, max_width_size, text_align_height), true, TextAlign::center_right);
y += ROW_HEIGHT;
}
}
else {
screen.text("No Files Found.", minimal_font, ROW(0));
}
if(state == stMassStorage)
mass_storage_overlay(time);
// error dialog overlay
if(persist.reset_error) {
screen.pen = Pen(0, 0, 0, 200);
screen.clear();
screen.pen = Pen(255, 255, 255);
screen.text("Oops!\n\nRestart game?", minimal_font, Point(screen.bounds.w / 2, screen.bounds.h / 2), true, TextAlign::center_center);
screen.pen = selected_dialog_option == 0 ? Pen(235, 245, 255) : Pen(80, 100, 120);
screen.text("No", minimal_font, Point(screen.bounds.w / 3, screen.bounds.h - 80), true, TextAlign::center_center);
screen.pen = selected_dialog_option == 1 ? Pen(235, 245, 255) : Pen(80, 100, 120);
screen.text("Yes", minimal_font, Point(screen.bounds.w / 3 * 2, screen.bounds.h - 80), true, TextAlign::center_center);
}
progress.draw();
}
void update(uint32_t time)
{
if(persist.reset_error) {
// only two options
if(buttons.pressed & Button::DPAD_RIGHT)
selected_dialog_option ^= 1;
else if(buttons.pressed & Button::DPAD_LEFT)
selected_dialog_option ^= 1;
if(buttons.released & Button::A) {
if(selected_dialog_option == 1) // yes
blit_switch_execution(0);
else
persist.reset_target = prtFirmware;
persist.reset_error = false;
}
return;
}
if(state == stLS) {
load_file_list();
state = stFlashFile;
}
bool button_home = buttons.pressed & Button::HOME;
if(state == stFlashFile)
{
static uint32_t lastRepeat = 0;
bool button_a = buttons.released & Button::A;
bool button_x = buttons.pressed & Button::X;
bool button_y = buttons.pressed & Button::Y;
bool button_up = buttons.pressed & Button::DPAD_UP;
bool button_down = buttons.pressed & Button::DPAD_DOWN;
if(time - lastRepeat > 150 || button_up || button_down) {
button_up = buttons & Button::DPAD_UP;
button_down = buttons & Button::DPAD_DOWN;
lastRepeat = time;
}
if(button_home)
{
// switch to mass media
g_usbManager.SetType(USBManager::usbtMSC);
state = stMassStorage;
}
if(button_up)
{
if(persist.selected_menu_item > 0) {
persist.selected_menu_item--;
} else {
persist.selected_menu_item = files.size() - 1;
}
}
if(button_down)
{
if(persist.selected_menu_item < (files.size() - 1)) {
persist.selected_menu_item++;
} else {
persist.selected_menu_item = 0;
}
}
// scroll list towards selected item
file_list_scroll_offset.y += ((persist.selected_menu_item * 10) - file_list_scroll_offset.y) / 5.0f;
if(button_a)
{
if(flash_from_sd_to_qspi_flash(files[persist.selected_menu_item].name.c_str())) {
blit_switch_execution(0);
}
}
if (button_x) {
file_sort = SortBy::name;
sort_file_list();
}
if (button_y) {
file_sort = SortBy::size;
sort_file_list();
}
}
else if(state == stMassStorage)
{
bool switch_back = g_usbManager.GetState() == USBManager::usbsMSCUnmounted;
// allow switching back manually if it was never mounted
if(button_home && g_usbManager.GetState() == USBManager::usbsMSCInititalising)
switch_back = true;
if(switch_back)
{
// Switch back to CDC
g_usbManager.SetType(USBManager::usbtCDC);
load_file_list();
state = stFlashFile;
}
}
}
// Flash(): Flash a file from the SDCard to external flash
bool flash_from_sd_to_qspi_flash(const char *filename)
{
FIL file;
FRESULT res = f_open(&file, filename, FA_READ);
if(res != FR_OK)
return false;
// get file length
FSIZE_t bytes_total = f_size(&file);
FSIZE_t bytes_flashed = 0;
size_t offset = 0;
if(!bytes_total)
{
f_close(&file);
return false;
}
// erase the sectors needed to write the image
erase_qspi_flash(0, bytes_total);
progress.show("Copying from SD card to flash...", bytes_total);
while(bytes_flashed < bytes_total)
{
// limited ram so a bit at a time
UINT bytes_read = 0;
res = f_read(&file, (void *)buffer, BUFFER_SIZE, &bytes_read);
if(res != FR_OK)
break;
if(qspi_write_buffer(offset, buffer, bytes_read) != QSPI_OK)
break;
if(qspi_read_buffer(offset, verify_buffer, bytes_read) != QSPI_OK)
break;
// compare buffers
bool verified = true;
for(uint32_t uB = 0; verified && uB < bytes_read; uB++)
verified = buffer[uB] == verify_buffer[uB];
if(!verified)
break;
offset += bytes_read;
bytes_flashed += bytes_read;
progress.update(bytes_flashed);
}
f_close(&file);
progress.hide();
return bytes_flashed == bytes_total;
}
//////////////////////////////////////////////////////////////////////
// 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)
{
//printf("streamInit()\n\r");
bool bNeedStream = true;
switch(uCommand)
{
case CDCCommandHandler::CDCFourCCMake<'P', 'R', 'O', 'G'>::value:
state = stFlashCDC;
m_parseState = stFilename;
m_uParseIndex = 0;
break;
case CDCCommandHandler::CDCFourCCMake<'S', 'A', 'V', 'E'>::value:
state = stSaveFile;
m_parseState = stFilename;
m_uParseIndex = 0;
break;
case CDCCommandHandler::CDCFourCCMake<'_', '_', 'L', 'S'>::value:
state = stLS;
bNeedStream = false;
break;
}
return bNeedStream;
}
// FlashData() Flash data to the QSPI flash
// Note: currently qspi_write_buffer only works for sizes of 256 max
bool FlashData(uint32_t uOffset, uint8_t *pBuffer, uint32_t uLen)
{
bool bResult = false;
if(QSPI_OK == qspi_write_buffer(uOffset, pBuffer, uLen))
{
if(QSPI_OK == qspi_read_buffer(uOffset, verify_buffer, uLen))
{
// compare buffers
bResult = true;
for(uint32_t uB = 0; bResult && uB < uLen; uB++)
bResult = pBuffer[uB] == verify_buffer[uB];
}
}
progress.update(uOffset + uLen);
return bResult;
}
// SaveData() Saves date to file on SDCard
bool SaveData(uint8_t *pBuffer, uint32_t uLen)
{
UINT uWritten;
FRESULT res = f_write(&file, pBuffer, uLen, &uWritten);
progress.update(f_tell(&file));
return !res && (uWritten == uLen);
}
// 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
{
printf("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 = stData;
m_uParseIndex = 0;
char *pEndPtr;
m_uFilelen = strtoul(m_sFilelen, &pEndPtr, 10);
if(m_uFilelen)
{
// init file or flash
switch(state)
{
case stSaveFile:
{
FRESULT res = f_open(&file, m_sFilename, FA_CREATE_ALWAYS | FA_WRITE);
if(res)
{
printf("Failed to create file (%s)\n\r", m_sFilename);
result = srError;
}
else
progress.show("Saving " + std::string(m_sFilename) + " to SD card...", m_uFilelen);
}
break;
case stFlashCDC:
erase_qspi_flash(0, m_uFilelen);
progress.show("Saving " + std::string(m_sFilename) + " to flash...", m_uFilelen);
break;
default:
break;
}
}
else
{
printf("Failed to parse filelen\n\r");
result =srError;
}
}
}
}
else
{
printf("Failed to read filelen\n\r");
result =srError;
}
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)
{
uWriteLen = uByteOffset+1;
bEOS = true;
}
else
if(uByteOffset == PAGE_SIZE-1)
uWriteLen = PAGE_SIZE;
if(uWriteLen)
{
switch(state)
{
case stSaveFile:
// save data
if(!SaveData(buffer, uWriteLen))
{
printf("Failed to save to SDCard\n\r");
result = srError;
}
// end of stream close up
if(bEOS)
{
f_close(&file);
load_file_list();
state = stFlashFile;
if(result != srError)
result = srFinish;
progress.hide();
}
break;
case stFlashCDC:
{
// save data
volatile uint32_t uPage = (m_uParseIndex / PAGE_SIZE);
if(!FlashData(uPage*PAGE_SIZE, buffer, uWriteLen))
{
printf("Failed to write to flash\n\r");
result = srError;
}
// end of stream close up
if(bEOS)
{
if(result != srError)
{
result = srFinish;
blit_switch_execution(0);
}
else
state = stFlashFile;
progress.hide();
}
}
break;
default:
break;
}
}
m_uParseIndex++;
m_uBytesHandled = m_uParseIndex;
}
break;
}
}
if(result == srError) {
state = stFlashFile;
progress.hide();
}
return result;
}