diff --git a/firmware/spade/docker/dockerfile b/firmware/spade/docker/dockerfile index 11f63e3dbe..891a9e1829 100644 --- a/firmware/spade/docker/dockerfile +++ b/firmware/spade/docker/dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.19 -RUN apk add git python3 clang make cmake entr uglify-js gcc-arm-none-eabi g++-arm-none-eabi gdb-multiarch +RUN apk add git python3 clang make cmake entr uglify-js gcc-arm-none-eabi g++-arm-none-eabi gdb-multiarch gcc COPY ./importBuildRepos.sh /opt/importBuildRepos.sh diff --git a/firmware/spade/src/CMakeLists.txt b/firmware/spade/src/CMakeLists.txt index 07b307e59a..cd9f070945 100644 --- a/firmware/spade/src/CMakeLists.txt +++ b/firmware/spade/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_BUILD_TYPE Release) +set(CMAKE_BUILD_TYPE Debug) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-void-pointer-to-int-cast -Wno-int-to-void-pointer-cast -Wno-pointer-sign -Werror=implicit-function-declaration") add_executable(spade "${SPADE_TARGET}/main.c") diff --git a/firmware/spade/src/rpi/CMakeLists.txt b/firmware/spade/src/rpi/CMakeLists.txt index 8ab9ccce77..4d3fb315bd 100644 --- a/firmware/spade/src/rpi/CMakeLists.txt +++ b/firmware/spade/src/rpi/CMakeLists.txt @@ -1,30 +1,29 @@ pico_sdk_init() -add_definitions(-DSPADE_EMBEDDED -DSPADE_AUDIO -DPICO_NO_BI_PROGRAM_BUILD_DATE) -set(DCMAKE_BUILD_TYPE Release) +add_definitions(-DSPADE_EMBEDDED -DSPADE_AUDIO -DPICO_NO_BI_PROGRAM_BUILD_DATE -DPICO_USE_STACK_GUARDS) +set(DCMAKE_BUILD_TYPE Debug) target_compile_definitions(spade PRIVATE - # compile time configuration of I2S - PICO_AUDIO_I2S_MONO_INPUT=1 - USE_AUDIO_I2S=1 - PICO_AUDIO_I2S_DATA_PIN=9 - PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 - # PICO_DEFAULT_UART=0 - # PICO_DEFAULT_UART_TX_PIN=28 - # PICO_DEFAULT_UART_RX_PIN=29 + # compile time configuration of I2S + PICO_AUDIO_I2S_MONO_INPUT=1 + USE_AUDIO_I2S=1 + PICO_AUDIO_I2S_DATA_PIN=9 + PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 + # PICO_DEFAULT_UART=0 + # PICO_DEFAULT_UART_TX_PIN=28 + # PICO_DEFAULT_UART_RX_PIN=29 ) target_link_libraries(spade PRIVATE - pico_stdlib - pico_audio_i2s - pico_multicore - hardware_spi - hardware_timer - hardware_pwm - hardware_adc + pico_stdlib + pico_audio_i2s + pico_multicore + hardware_spi + hardware_timer + hardware_pwm + hardware_adc ) pico_enable_stdio_usb(spade 1) pico_enable_stdio_uart(spade 0) # create map/bin/hex file etc. -pico_add_extra_outputs(spade) - +pico_add_extra_outputs(spade) \ No newline at end of file diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-core.a b/firmware/spade/src/rpi/jerry/lib/libjerry-core.a index ef160b9e45..f265b60d91 100644 Binary files a/firmware/spade/src/rpi/jerry/lib/libjerry-core.a and b/firmware/spade/src/rpi/jerry/lib/libjerry-core.a differ diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a b/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a index 4d304f8849..7bb75d99c2 100644 Binary files a/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a and b/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a differ diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a b/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a index f0a1789bd2..dfe455fd3b 100644 Binary files a/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a and b/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a differ diff --git a/firmware/spade/src/rpi/jerry/refresh.sh b/firmware/spade/src/rpi/jerry/refresh.sh index b9b8f3ab9d..6fd77cab31 100755 --- a/firmware/spade/src/rpi/jerry/refresh.sh +++ b/firmware/spade/src/rpi/jerry/refresh.sh @@ -1,11 +1,12 @@ cd ~/jerryscript_build rm -rf example-* -# CMAKE_ASM_COMPILER=arm-none-eabi-gcc -# CMAKE_C_COMPILER=arm-none-eabi-gcc -# CMAKE_CXX_COMPILER=arm-none-eabi-g++ -# CMAKE_LINKER=arm-none-eabi-ld -# CMAKE_OBJCOPY=arm-none-eabi-objcopy +export CC=arm-none-eabi-gcc +export CMAKE_ASM_COMPILER=arm-none-eabi-gcc +export CMAKE_C_COMPILER=arm-none-eabi-gcc +export CMAKE_CXX_COMPILER=arm-none-eabi-g++ +export CMAKE_LINKER=arm-none-eabi-ld +export CMAKE_OBJCOPY=arm-none-eabi-objcopy # --debug \ python3 jerryscript/tools/build.py \ diff --git a/firmware/spade/src/rpi/main.c b/firmware/spade/src/rpi/main.c index fb4692b088..fe226cf383 100644 --- a/firmware/spade/src/rpi/main.c +++ b/firmware/spade/src/rpi/main.c @@ -72,6 +72,7 @@ typedef struct { uint8_t last_state; uint8_t ring_i; } ButtonState; +// W, S, A, D, I, K, J, L uint button_pins[] = { 5, 7, 6, 8, 12, 14, 13, 15 }; static ButtonState button_states[ARR_LEN(button_pins)] = {0}; @@ -248,6 +249,25 @@ static int load_new_scripts(void) { } #endif +typedef enum { + NEW_SLOT, + RUN_GAME, + DELETE_CONFIRM + } Welcome_Screen; + + int count_digits(uint32_t number) { + if (number < 10) return 1; + if (number < 100) return 2; + if (number < 1000) return 3; + if (number < 10000) return 4; + if (number < 100000) return 5; + if (number < 1000000) return 6; + if (number < 10000000) return 7; + if (number < 100000000) return 8; + if (number < 1000000000) return 9; + return 10; + } + int main() { timer_hw->dbgpause = 0; @@ -265,74 +285,173 @@ int main() { jerry_init(JERRY_INIT_MEM_STATS); init(sprite_free_jerry_object); // TODO: document - while(!save_read()) { - // No game stored in memory - strcpy(errorbuf, " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " PLEASE UPLOAD \n" - " A GAME \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " sprig.hackclub.com \n"); - render_errorbuf(); - st7735_fill_start(); - render(st7735_fill_send); - st7735_fill_finish(); + // Start a core to listen for keypresses. + multicore_reset_core1(); - load_new_scripts(); - } + // update_save_version(); // init here to avoid irqs on other core - // Start a core to listen for keypresses. - multicore_launch_core1(core1_entry); + multicore_launch_core1(core1_entry); - /** - * We get a bunch of fake keypresses at startup, so we need to - * drain them from the FIFO queue. - * - * What really needs to be done here is to have button_init - * record when it starts so that we can ignore keypresses after - * that timestamp. - */ - sleep_ms(50); - while (multicore_fifo_rvalid()) multicore_fifo_pop_blocking(); + /** + * We get a bunch of fake keypresses at startup, so we need to + * drain them from the FIFO queue. + * + * What really needs to be done here is to have button_init + * record when it starts so that we can ignore keypresses after + * that timestamp. + */ + sleep_ms(50); + while (multicore_fifo_rvalid()) multicore_fifo_pop_blocking(); - /** - * Wait for a keypress to start the game. - * - * This is important so games with e.g. infinite loops don't - * brick the device as soon as they start up. - */ - while(!multicore_fifo_rvalid()) { - strcpy(errorbuf, " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " PRESS ANY BUTTON \n" - " TO RUN \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " sprig.hackclub.com \n"); - render_errorbuf(); - st7735_fill_start(); - render(st7735_fill_send); - st7735_fill_finish(); + int games_i = 0; - load_new_scripts(); - } + int games_len = 8; + Game* games = malloc(games_len * sizeof(Game)); + + Welcome_Screen welcome_state = NEW_SLOT; + + for (;;) { + + if (games_i >= games_len && games_i != 0) { + games_i = games_len - 1; + } + + if (welcome_state == DELETE_CONFIRM) { + // no-op + } else if (games_len == 0) { + welcome_state = NEW_SLOT; + } else { + welcome_state = RUN_GAME; + set_game(games[games_i]); + } + + /** + * Wait for a keypress to start the game. + * This is important so games with e.g. infinite loops don't + * brick the device as soon as they start up. + */ + + if (multicore_fifo_rvalid()) { + uint32_t c = multicore_fifo_pop_blocking(); + + if (welcome_state == DELETE_CONFIRM) { + if (c == 7) { // S + welcome_state = RUN_GAME; + } else if (c == 5) { // W + delete_game(games[games_i]); + welcome_state = RUN_GAME; + continue; + } + } else if (c == 6) { // A + if (games_i > 0) games_i--; + } else if (c == 8) { // D + if (games_i < games_len - 1) games_i++; + } else if (c == 7 && welcome_state == RUN_GAME) { // S + welcome_state = DELETE_CONFIRM; + } else if (welcome_state == RUN_GAME && c == 5) { // other button + break; + } + } + + games_len = get_games(&games, games_len); + + switch (welcome_state) { + case NEW_SLOT: + strcpy(errorbuf, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Please upload \n" + " a game. \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " sprig.hackclub.com \n" + ); + break; + case RUN_GAME: { + char game_padding[] = " "; + char size_padding[] = " "; + + game_padding[ + 20 + - count_digits(games_i+1) + - count_digits(games_len) + - 8 // 7 at first + 1 slash + ] = '\0'; + + size_padding[ + 20 + - count_digits(GAME_SLOTS(games[games_i].size_b)) + - count_digits(MAX_SLOTS) + - 8 // 7 at first + 1 slash + ] = '\0'; + + // 6lines + char game_split_lines[] = { + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + }; + + for (int i = 0; i*17 < strlen(games[games_i].name); i++) { // 20 - 3 buffer = 17 + memcpy(&game_split_lines[i*21 + 1], &games[games_i].name[i*17], + strlen(games[games_i].name) - i*17 < 17 ? strlen(games[games_i].name) - i*17 : 17); + } + + + sprintf(errorbuf, + " \n" + " \n" + "%s" + " \n" + " Game: %d/%d%s\n" + " Size: %lu/%d%s\n" + " \n" + " W: PLAY \n" + " S: DELETE \n" + " <- A , D -> \n", + game_split_lines, + games_i + 1, games_len, game_padding, + GAME_SLOTS(games[games_i].size_b), MAX_SLOTS, size_padding); + break; + } + case DELETE_CONFIRM: { + strcpy(errorbuf, " \n" + " \n" + " \n" + " \n" + " \n" + " Do you really \n" + " want to delete \n" + " this game? \n" + " \n" + " \n" + " W: confirm \n" + " S: exit \n" + " \n" + " \n" + " sprig.hackclub.com \n" + ); + break; + } + } + + render_errorbuf(); + st7735_fill_start(); + render(st7735_fill_send); + st7735_fill_finish(); + + load_new_scripts(); + } // Wow, we can actually run a game now! diff --git a/firmware/spade/src/rpi/upload.h b/firmware/spade/src/rpi/upload.h index 401572742a..8223780746 100644 --- a/firmware/spade/src/rpi/upload.h +++ b/firmware/spade/src/rpi/upload.h @@ -1,28 +1,337 @@ #include "shared/sprig_engine/script.h" #include "hardware/flash.h" +#include static void core1_entry(void); +typedef enum { + Location_FLASH + // TODO: will add SD + // TODO: swap games into SD if flash overfilled +} Game_Location; + +typedef struct { + char name[100]; // todo: we need to set frontend limits on game names + Game_Location location; + uint8_t slot; + uint32_t size_b; +} Game; + +Game current_game = (Game) {}; +int slot = 0; + // rationale: half engine, half games? // NOTE: this has to be a multiple of 4096 (FLASH_SECTOR_SIZE) -#define FLASH_TARGET_OFFSET (800 * 1024) +#define SLOT_SIZE FLASH_SECTOR_SIZE +#define MAX_SLOTS 150 +#define FLASH_TARGET_START (800*1024) +#define FLASH_TARGET_OFFSET(slot_i) (FLASH_TARGET_START + slot_i * SLOT_SIZE) + +#define GAME_SLOTS(bytes) ((bytes + ARR_LEN(engine_script) - 1) / SLOT_SIZE + 1) + +#define METADATA_ENTRY_SIZE (256) // you can program up to one page at a time. (256 bytes). +#define METADATA_SIZE (32 * METADATA_ENTRY_SIZE) // first entry is for version // TODO: make sure this works when changing to 32 +#define METADATA_MAX_ENTRIES (METADATA_SIZE / METADATA_ENTRY_SIZE - 1) +#define METADATA_CONTENTS(index) ((const Game *) (XIP_BASE + FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE + index * METADATA_ENTRY_SIZE)) +#define METADATA_OFFSET ((uint32_t) (FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE)) + +// i think what happened here is only FLASH_TARGET_START was cast to a Game*. +// METADATA_SIZE was a signed int. this coerced METADATA_OFFSET into a signed int, +// which was then passed into a function that accepted a uint32_t. +// this messed up the pointer and caused it to overwrite application code in the flash (XIP) +// #define METADATA_OFFSET ((const Game*) FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE) + + +#define METADATA_START METADATA_CONTENTS(-1) -const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET); +#define FLASH_VERSION ((const char *) (XIP_BASE + FLASH_TARGET_START - METADATA_SIZE)) -uint16_t SPRIG_MAGIC[FLASH_PAGE_SIZE/2] = { 1337, 42, 69, 420, 420, 1337 }; +#define FLASH_TARGET_CONTENTS(slot_i) ((const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET(slot_i))) -static const char *save_read(void) { - if (memcmp(&SPRIG_MAGIC, flash_target_contents, sizeof(SPRIG_MAGIC)) != 0) { +#define ZEROS(bytes) ((char[bytes]){}) + +static const int memory_is_ones(const void *memory, size_t count) { + // this is taken from gcc libiberty + register const unsigned char *s = (const unsigned char*)memory; + + while (count-- > 0) + { + if (*s++ != 0xFF) + return 0; + } + return 1; +} + +// sprig magic is still here because it's useful for debugging (binvis.io) + easier to not get rid of it lol +uint16_t SPRIG_MAGIC[6] = { 1337, 42, 69, 420, 420, 1337 }; + +static const char *save_read() { + if (memcmp(&SPRIG_MAGIC, FLASH_TARGET_CONTENTS(slot), sizeof(SPRIG_MAGIC)) != 0) { puts("no magic :("); return NULL; } // add a page to get what's after the magic - const char *save = flash_target_contents + FLASH_PAGE_SIZE; + const char *save = FLASH_TARGET_CONTENTS(slot) + FLASH_PAGE_SIZE; return save; } +static void erase_user_portion_of_flash_this_is_dangerous() { + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase( + (uint32_t) FLASH_TARGET_START - METADATA_SIZE - METADATA_ENTRY_SIZE, + SLOT_SIZE * MAX_SLOTS + METADATA_SIZE - METADATA_ENTRY_SIZE); // TODO: check that this is valid + restore_interrupts(interrupts); +} + +// save versions != spade versions +// increment in the value returned means different scheme for saving games +static int get_save_version(const char* version) { + if (memory_is_ones(version, METADATA_ENTRY_SIZE)) return 1; + if (strcmp(version, "1.1.0") == 0) return 2; + + // something weird happened!? + return -1; +} + +// must be run with core 1 disabled - irq risk +static void flash_write_save_version(const char* version) { + void *metadata_first_sector = malloc(FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector, METADATA_START, FLASH_SECTOR_SIZE); + strcpy(metadata_first_sector, version); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, FLASH_SECTOR_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, metadata_first_sector, FLASH_SECTOR_SIZE); + restore_interrupts(interrupts); + + free(metadata_first_sector); +} + +// must be run with core 1 disabled - irq risk +static int update_save_version() { // TODO: handle totally empty flash + int version = get_save_version(FLASH_VERSION); + if (version == get_save_version(SPADE_VERSION)) return 0; + + switch (version) { // recursive if need to update multiple versions. + case 1: // 1.0.0 or less + { + if (memcmp(&SPRIG_MAGIC, FLASH_TARGET_CONTENTS(0), sizeof(SPRIG_MAGIC)) == 0) { + // add flash metadata for first game + Game game = { + .slot = 0, + .size_b = strlen(FLASH_TARGET_CONTENTS(0)), + .name = "Legacy Game", + .location = Location_FLASH + }; + + void *metadata_first_sector = malloc(FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector, METADATA_START, FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector + METADATA_ENTRY_SIZE, &game, sizeof(Game)); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, FLASH_SECTOR_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, metadata_first_sector, + FLASH_SECTOR_SIZE); + restore_interrupts(interrupts); + + free(metadata_first_sector); + } + + flash_write_save_version("1.1.0"); + return update_save_version(); + } + case 2: + default: + return 1; + } + +} + +// returns len of games. games should be pointer to array of games (Game**) +static int get_games(Game** games, int games_len) { + int games_i = -1; + + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + + volatile int is_zeros = memcmp(METADATA_CONTENTS(i), ZEROS(sizeof(Game)), sizeof(Game)); + volatile int is_ones = memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE); + if (is_ones) continue; + + if (games_len <= ++games_i) { + games_len *= 2; + *games = realloc(*games, games_len * sizeof(Game)); + } + + (*games)[games_i] = *METADATA_CONTENTS(i); + } + + return games_i + 1; +} + +static int get_available_game_slots() { + int available_metadata_slots = 0; + int available_flash_slots = MAX_SLOTS; + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + volatile int is_ones = memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE); + if (is_ones) + available_metadata_slots++; + else + available_flash_slots -= (int) GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + } + + // minimum of both + return available_flash_slots < available_metadata_slots ? available_flash_slots : available_metadata_slots; +} + + +static int get_first_open_flash_slot_at_end() { + int lowest_open_slot = -1; + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (!memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE)) { + int slot_after_entry = METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + + if (slot_after_entry > lowest_open_slot) { + lowest_open_slot = slot_after_entry; + } + } + } + return lowest_open_slot + 1; +} + +static int get_available_flash_slots_at_end() { + return MAX_SLOTS - get_first_open_flash_slot_at_end(); +} + +static int get_first_open_metadata_slot() { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE)) + return i; + } + + return -1; +} + +static void set_game(Game aGame) { + // this is fine for now. + // in future, include logic to swap games in SD to flash + current_game = aGame; + slot = aGame.slot; +} + +static int get_game_index_by_name(char* name) { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (!memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE) + && strcmp(METADATA_CONTENTS(i)->name, name) == 0) { + return i; + } + } + + return -1; +} + +static void delete_game(Game aGame) { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (memcmp(METADATA_CONTENTS(i), &aGame, sizeof(Game)) == 0) { // this is the metadata slot + char ones[METADATA_ENTRY_SIZE]; + for (int j = 0; j < METADATA_ENTRY_SIZE; j++) { + ones[j] = 0xFF; + } + + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + memcpy(new_metadata + METADATA_ENTRY_SIZE * (i + 1), &ones, METADATA_ENTRY_SIZE); + + multicore_reset_core1(); + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + + free(new_metadata); + restore_interrupts(interrupts); + multicore_launch_core1(core1_entry); + } + } +} + +// TODO: proofread/test +// TODO: this does not work! +void consolidate_flash_games() { + multicore_reset_core1(); + + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + + int last_contiguous_game = 0; + + int prev_write = -1; + + for (;;) { + // infinite loop terminated by hitting end: + + int write = 0; + + int read = MAX_SLOTS; + int read_metadata_i = 0; + + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + + int empty = 1; + + for (int j = 0; j < METADATA_MAX_ENTRIES; j++) { + if (METADATA_CONTENTS(j)->slot == + METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b)) { + empty = 0; + } + } + + if (!empty) continue; + + write = METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + + if (write == prev_write) { + + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + + restore_interrupts(interrupts); + + free(new_metadata); + multicore_launch_core1(core1_entry); + return; + } + + for (int j = 0; j < METADATA_MAX_ENTRIES; j++) { + if (METADATA_CONTENTS(j)->slot > write && METADATA_CONTENTS(j)->slot < read) { + read = METADATA_CONTENTS(j)->slot; + read_metadata_i = j; + } + } + + prev_write = write; + + break; + } + + for (int i = 0; i < GAME_SLOTS(METADATA_CONTENTS(read_metadata_i)->size_b); i++) { + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(FLASH_TARGET_OFFSET(read + i), SLOT_SIZE); + flash_range_program(FLASH_TARGET_OFFSET(read + FLASH_SECTOR_SIZE * i), FLASH_TARGET_CONTENTS(write + i), + SLOT_SIZE); + + restore_interrupts(interrupts); + } + + ((Game*) (new_metadata + METADATA_ENTRY_SIZE*read))->slot = write; + + } +} typedef enum { + UplProg_Init, UplProg_Header, UplProg_Body, } UplProg; @@ -31,16 +340,20 @@ static struct { uint32_t len, len_i; char buf[256]; int page; + char name[100]; + uint8_t name_i; } upl_state = {0}; static void upl_flush_buf(void) { - uint32_t interrupts = save_and_disable_interrupts(); - flash_range_program(FLASH_TARGET_OFFSET + (upl_state.page++) * 256, + puts("wtf?? 6"); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_program(FLASH_TARGET_OFFSET(slot) + (upl_state.page++) * 256, (void *)upl_state.buf, 256); restore_interrupts(interrupts); memset(upl_state.buf, 0, sizeof(upl_state.buf)); - printf("wrote page (%d/%d)\n", + printf("wrote page (%d/%lu)\n", upl_state.page, (upl_state.len/(FLASH_PAGE_SIZE + 1))); } @@ -49,58 +362,120 @@ static int upl_stdin_read(void) { memset(&upl_state, 0, sizeof(upl_state)); int timeout = 1000; // 1ms; we're already in upload mode + for (;;) { int c = getchar_timeout_us(timeout); if (c == PICO_ERROR_TIMEOUT) return 0; switch (upl_state.prog) { + case UplProg_Init: { + // irqs on other core? + multicore_reset_core1(); + + upl_state.prog = UplProg_Header; + } // falls through case UplProg_Header: { - ((char *)(&upl_state.len))[upl_state.len_i++] = c; - if (upl_state.len_i >= sizeof(uint32_t)) { - printf("ok reading %d chars\n", upl_state.len); - upl_state.prog = UplProg_Body; - upl_state.len_i = 0; - upl_state.page = 1; // skip first, that's for magic - - int char_len = upl_state.len + sizeof (engine_script); // sizeof script includes the null term, we still need to remove from script - upl_state.len = char_len; - // one to round up, one for magic - int page_len = (char_len/FLASH_PAGE_SIZE + 2) * FLASH_PAGE_SIZE ; - int sector_len = (page_len/FLASH_SECTOR_SIZE + 1) * FLASH_SECTOR_SIZE; - - // irqs on other core? - multicore_reset_core1(); - - uint32_t interrupts = save_and_disable_interrupts(); - flash_range_erase(FLASH_TARGET_OFFSET, sector_len); - restore_interrupts(interrupts); + puts("wahoo header"); - for (int i = 0; i < sizeof(engine_script) - 1; i++) { - upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = engine_script[i]; - if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { - puts("flushin buf (wit da code!)"); - upl_flush_buf(); - } + if (upl_state.name_i < sizeof(upl_state.name) / sizeof(char)) // read game + { + ((char *) (&upl_state.name))[upl_state.name_i++] = c; + puts(upl_state.name); } + else { + ((char *) (&upl_state.len))[upl_state.len_i++] = c; + if (upl_state.len_i >= sizeof(uint32_t)) { + printf("ok reading %lu chars\n", upl_state.len); + upl_state.prog = UplProg_Body; + upl_state.len_i = 0; + upl_state.page = 1; // skip first, that's for magic - puts("cleared flash"); - } + { + Game game; + strcpy(game.name, upl_state.name); + game.size_b = upl_state.len; + game.location = Location_FLASH; + + + // not enough slots + if (get_available_game_slots() < GAME_SLOTS(upl_state.len)) { + puts("no available game slots!"); + printf("we need %lu slots but we only have %d",GAME_SLOTS(upl_state.len), get_available_game_slots()); + return 0; // ERROR! + } else if (get_available_flash_slots_at_end() < GAME_SLOTS(upl_state.len)) { + puts("consolidating!"); + consolidate_flash_games(); + game.slot = get_first_open_flash_slot_at_end(); + } else { + game.slot = get_first_open_flash_slot_at_end(); + } + + slot = game.slot; + + int metadata_i; + int search_result = get_game_index_by_name(game.name); + + if (search_result != -1) { + metadata_i = search_result; + } else { + metadata_i = get_first_open_metadata_slot(); + } + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + memcpy(new_metadata + METADATA_ENTRY_SIZE * (metadata_i + 1), &game, sizeof(Game)); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + restore_interrupts(interrupts); + + printf("metadata_i: %d,\n" + "slot: %d\n" + "size_b: %lu", metadata_i, slot, game.size_b); + + free(new_metadata); + } + + uint32_t char_len = upl_state.len + + sizeof(engine_script); // sizeof script includes the null term, we still need to remove from script + upl_state.len = char_len; + // one to round up, one for magic + uint32_t page_len = (char_len / FLASH_PAGE_SIZE + 2) * FLASH_PAGE_SIZE; + uint32_t sector_len = (page_len / FLASH_SECTOR_SIZE + 1) * FLASH_SECTOR_SIZE; + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(FLASH_TARGET_OFFSET(slot), sector_len); + restore_interrupts(interrupts); + + for (int i = 0; i < sizeof(engine_script) - 1; i++) { + upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = engine_script[i]; + if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { + puts("flushin buf (wit da code!)"); + upl_flush_buf(); + } + } + + puts("cleared flash"); + } + } } break; case UplProg_Body: { - // printf("upl char (%d/%d)\n", upl_state.len_i, upl_state.len); + + // printf("upl char (%d/%d)\n", upl_state.len_i, upl_state.len); upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = c; + if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { puts("flushin buf"); upl_flush_buf(); } - if (upl_state.len_i == upl_state.len - 1) { + if (upl_state.len_i == upl_state.len - 1) { upl_flush_buf(); uint32_t interrupts = save_and_disable_interrupts(); - flash_range_program(FLASH_TARGET_OFFSET, (void *)SPRIG_MAGIC, FLASH_PAGE_SIZE); + flash_range_program(FLASH_TARGET_OFFSET(slot), (void *)SPRIG_MAGIC, FLASH_PAGE_SIZE); restore_interrupts(interrupts); - + // printf("read in %d chars\n", upl_state.len); puts("ALL_GOOD"); memset(&upl_state, 0, sizeof(upl_state)); diff --git a/firmware/spade/src/version.json b/firmware/spade/src/version.json index 688e939808..07cd7643a2 100644 --- a/firmware/spade/src/version.json +++ b/firmware/spade/src/version.json @@ -1,3 +1,3 @@ { - "version": "1.0.0" + "version": "1.1.0" } \ No newline at end of file diff --git a/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl b/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl index 1e0bb29ace..22dd26df86 100644 --- a/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl +++ b/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl @@ -3,10 +3,12 @@ "active_layer": 31, "active_layer_preset": "", "auto_track_width": true, + "hidden_netclasses": [], "hidden_nets": [], "high_contrast_mode": 0, "net_color_mode": 1, "opacity": { + "images": 0.6, "pads": 1.0, "tracks": 1.0, "vias": 1.0, @@ -65,8 +67,14 @@ "visible_layers": "ffffeff_ffffffff", "zone_display_mode": 0 }, + "git": { + "repo_password": "", + "repo_type": "", + "repo_username": "", + "ssh_key": "" + }, "meta": { - "filename": "pigame_pico.kicad_prl", + "filename": "sprig_console.kicad_prl", "version": 3 }, "project": { diff --git a/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro b/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro index 660288e18b..78dfe06ed1 100644 --- a/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro +++ b/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro @@ -1,5 +1,6 @@ { "board": { + "3dviewports": [], "design_settings": { "defaults": { "board_outline_line_width": 0.09999999999999999, @@ -135,7 +136,15 @@ "zones_allow_external_fillets": false, "zones_use_no_outline": true }, - "layer_presets": [] + "ipc2581": { + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "" + }, + "layer_presets": [], + "viewports": [] }, "boards": [], "cvpcb": { @@ -322,15 +331,21 @@ "bus_label_syntax": "error", "bus_to_bus_conflict": "error", "bus_to_net_conflict": "error", + "conflicting_netclasses": "error", "different_unit_footprint": "error", "different_unit_net": "error", "duplicate_reference": "error", "duplicate_sheet_names": "error", + "endpoint_off_grid": "warning", "extra_units": "error", "global_label_dangling": "warning", "hier_label_mismatch": "error", "label_dangling": "error", "lib_symbol_issues": "warning", + "missing_bidi_pin": "warning", + "missing_input_pin": "warning", + "missing_power_pin": "error", + "missing_unit": "warning", "multiple_net_names": "warning", "net_not_bus_member": "warning", "no_connect_connected": "warning", @@ -340,6 +355,7 @@ "pin_to_pin": "warning", "power_pin_not_driven": "error", "similar_labels": "warning", + "simulation_model_issue": "ignore", "unannotated": "error", "unit_value_mismatch": "error", "unresolved_variable": "error", @@ -351,13 +367,13 @@ "pinned_symbol_libs": [] }, "meta": { - "filename": "pigame_pico.kicad_pro", + "filename": "sprig_console.kicad_pro", "version": 1 }, "net_settings": { "classes": [ { - "bus_width": 12.0, + "bus_width": 12, "clearance": 0.2, "diff_pair_gap": 0.25, "diff_pair_via_gap": 0.25, @@ -371,28 +387,93 @@ "track_width": 0.25, "via_diameter": 0.8, "via_drill": 0.4, - "wire_width": 6.0 + "wire_width": 6 } ], "meta": { - "version": 2 + "version": 3 }, - "net_colors": null + "net_colors": null, + "netclass_assignments": null, + "netclass_patterns": [] }, "pcbnew": { "last_paths": { "gencad": "", "idf": "", "netlist": "", + "plot": "", + "pos_files": "", "specctra_dsn": "", "step": "", + "svg": "", "vrml": "" }, "page_layout_descr_file": "" }, "schematic": { "annotate_start_num": 0, + "bom_fmt_presets": [], + "bom_fmt_settings": { + "field_delimiter": ",", + "keep_line_breaks": false, + "keep_tabs": false, + "name": "CSV", + "ref_delimiter": ",", + "ref_range_delimiter": "", + "string_delimiter": "\"" + }, + "bom_presets": [], + "bom_settings": { + "exclude_dnp": false, + "fields_ordered": [ + { + "group_by": false, + "label": "Reference", + "name": "Reference", + "show": true + }, + { + "group_by": true, + "label": "Value", + "name": "Value", + "show": true + }, + { + "group_by": false, + "label": "Datasheet", + "name": "Datasheet", + "show": true + }, + { + "group_by": false, + "label": "Footprint", + "name": "Footprint", + "show": true + }, + { + "group_by": false, + "label": "Qty", + "name": "${QUANTITY}", + "show": true + }, + { + "group_by": true, + "label": "DNP", + "name": "${DNP}", + "show": true + } + ], + "filter_string": "", + "group_symbols": true, + "name": "Grouped By Value", + "sort_asc": true, + "sort_field": "Reference" + }, + "connection_grid_size": 50.0, "drawing": { + "dashed_lines_dash_length_ratio": 12.0, + "dashed_lines_gap_length_ratio": 3.0, "default_line_thickness": 6.0, "default_text_size": 50.0, "field_names": [], @@ -403,6 +484,11 @@ "intersheets_ref_suffix": "", "junction_size_choice": 3, "label_size_ratio": 0.375, + "operating_point_overlay_i_precision": 3, + "operating_point_overlay_i_range": "~A", + "operating_point_overlay_v_precision": 3, + "operating_point_overlay_v_range": "~V", + "overbar_offset_ratio": 1.23, "pin_symbol_size": 25.0, "text_offset_ratio": 0.15 }, @@ -424,7 +510,12 @@ "page_layout_descr_file": "", "plot_directory": "", "spice_adjust_passive_values": false, + "spice_current_sheet_as_root": false, "spice_external_command": "spice \"%I\"", + "spice_model_current_sheet_as_root": true, + "spice_save_all_currents": false, + "spice_save_all_dissipations": false, + "spice_save_all_voltages": false, "subpart_first_id": 65, "subpart_id_separator": 0 }, diff --git a/scripts/gardenshed/gdb-server.sh b/scripts/gardenshed/gdb-server.sh index 664c4c99a3..112f404afc 100755 --- a/scripts/gardenshed/gdb-server.sh +++ b/scripts/gardenshed/gdb-server.sh @@ -1,3 +1,3 @@ #!/bin/bash -sudo openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program ./spade.elf verify reset" +sudo openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "set USE_CORE 0" -c "program ./spade.elf verify reset" diff --git a/src/components/navbar-editor.tsx b/src/components/navbar-editor.tsx index 3102f2ef7c..b31c92dd81 100644 --- a/src/components/navbar-editor.tsx +++ b/src/components/navbar-editor.tsx @@ -350,7 +350,13 @@ export default function EditorNavbar(props: EditorNavbarProps) { spinnyIcon={uploadState.value === "LOADING"} loading={uploadState.value === "LOADING"} onClick={() => - upload(codeMirror.value?.state.doc.toString() ?? "") + upload(codeMirror.value?.state.doc.toString() ?? "", + props.persistenceState.value.kind == "PERSISTED" + && props.persistenceState.value.game != "LOADING" + ? props.persistenceState.value.game.name + : props.persistenceState.value.kind == "SHARED" ? props.persistenceState.value.name + : "Untitled Game" + ) } > Run on Device diff --git a/src/lib/upload.ts b/src/lib/upload.ts index 36b3f6af8f..06b46312e9 100644 --- a/src/lib/upload.ts +++ b/src/lib/upload.ts @@ -32,7 +32,7 @@ const getPort = async (): Promise => { export const logSerialOutput = (value: string) => (value.trim().length > 0) && console.log(`%c< ${value.trim()}`, 'color: #999') -export const uploadToSerial = async (message: string, +export const uploadToSerial = async (name: string, message: string, writer: WritableStreamDefaultWriter, reader: ReadableStreamDefaultReader) => { @@ -61,6 +61,14 @@ export const uploadToSerial = async (message: string, console.log('[UPLOAD > SERIAL] Checkpoint 2') await writer.ready + + console.log('[UPLOAD > SERIAL] Checkpoint 2 - writing name') + // send name + padding to total 128b + // TODO: game titles shouldn't be able to have special characters + const nameString = new Uint8Array(100) + new TextEncoder().encodeInto(name + "\0".repeat(100 - name.length), nameString) + await writer.write(nameString) + console.log('[UPLOAD > SERIAL] Checkpoint 2 - writing length') await writer.write(new Uint32Array([ buf.length ]).buffer) @@ -132,7 +140,7 @@ export const getIsLegacySerial = async ( } } -export const upload = async (code: string): Promise => { +export const upload = async (code: string, name: string): Promise => { if (uploadState.value === 'LOADING') return uploadState.value = 'LOADING' @@ -173,7 +181,7 @@ export const upload = async (code: string): Promise => { console.log("[UPLOAD] Version up to date!") } - await uploadToSerial(code, writer, reader) + await uploadToSerial(name, code, writer, reader) console.log('[UPLOAD] Waiting on stream close and writer lock release...') //reader.releaseLock()