diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 9caf24af4..94918c939 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -41,6 +41,14 @@ jobs: apt-packages: ccache gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib pipx python3-requests - os: ubuntu-22.04 + pico-sdk: true + name: PicoSystem (.blit) + cache-key: picosystem-bl + release-suffix: PicoSystem-blit + cmake-args: -D32BLIT_DIR=$GITHUB_WORKSPACE -DPICO_SDK_PATH=$GITHUB_WORKSPACE/pico-sdk -DPICO_BOARD=pimoroni_picosystem -DBLIT_EXECUTABLE_PICO_BLIT=1 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + apt-packages: ccache gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib python3-setuptools + + - os: ubuntu-20.04 pico-sdk: true name: PicoVision cache-key: picovision diff --git a/32blit-config.cmake b/32blit-config.cmake index 18bfb9402..92fa55fe6 100644 --- a/32blit-config.cmake +++ b/32blit-config.cmake @@ -75,15 +75,50 @@ if (NOT DEFINED BLIT_ONCE) function(blit_executable_args) set(SOURCES) + # enable one of the pico builds if neither set + if(NOT BLIT_EXECUTABLE_PICO_STANDALONE_UF2 AND NOT BLIT_EXECUTABLE_PICO_BLIT) + set(BLIT_EXECUTABLE_PICO_STANDALONE_UF2 TRUE) + endif() + + # global overrides if(DEFINED BLIT_EXECUTABLE_INTERNAL_FLASH) set(INTERNAL_FLASH ${BLIT_EXECUTABLE_INTERNAL_FLASH}) else() set(INTERNAL_FLASH FALSE) endif() + if(DEFINED BLIT_EXECUTABLE_PICO_STANDALONE_UF2) + set(PICO_STANDALONE_UF2 ${BLIT_EXECUTABLE_PICO_STANDALONE_UF2}) + else() + set(PICO_STANDALONE_UF2 FALSE) + endif() + + if(DEFINED BLIT_EXECUTABLE_PICO_BLIT) + set(PICO_BLIT ${BLIT_EXECUTABLE_PICO_BLIT}) + else() + set(PICO_BLIT FALSE) + endif() + + if(DEFINED BLIT_EXECUTABLE_PICO_BLIT_OFFSET_KB) + set(PICO_BLIT_OFFSET_KB ${BLIT_EXECUTABLE_PICO_BLIT_OFFSET_KB}) + endif() + foreach(arg IN LISTS ARGN) if(arg STREQUAL "INTERNAL_FLASH") set(${arg} TRUE) + elseif(arg STREQUAL "PICO_STANDALONE_UF2") + set(${arg} TRUE) + set(PICO_BLIT FALSE) # can't build both + elseif(arg STREQUAL "PICO_BLIT") + set(${arg} TRUE) + set(PICO_STANDALONE_UF2 FALSE) + # args with value + elseif(arg STREQUAL "PICO_BLIT_OFFSET_KB") + set(prev_arg ${arg}) + # value for args with one + elseif(prev_arg STREQUAL "PICO_BLIT_OFFSET_KB") + set(${prev_arg} ${arg}) + unset(prev_arg) else() list(APPEND SOURCES ${arg}) endif() @@ -91,6 +126,9 @@ if (NOT DEFINED BLIT_ONCE) set(SOURCES ${SOURCES} PARENT_SCOPE) set(INTERNAL_FLASH ${INTERNAL_FLASH} PARENT_SCOPE) + set(PICO_STANDALONE_UF2 ${PICO_STANDALONE_UF2} PARENT_SCOPE) + set(PICO_BLIT ${PICO_BLIT} PARENT_SCOPE) + set(PICO_BLIT_OFFSET_KB ${PICO_BLIT_OFFSET_KB} PARENT_SCOPE) endfunction() if (32BLIT_HW) diff --git a/32blit-pico/CMakeLists.txt b/32blit-pico/CMakeLists.txt index 28053a1d0..af09fb405 100644 --- a/32blit-pico/CMakeLists.txt +++ b/32blit-pico/CMakeLists.txt @@ -8,6 +8,11 @@ include (pico_sdk_import.cmake) set(32BLIT_PICO 1 PARENT_SCOPE) +if(PICO_PLATFORM MATCHES "^rp2350") + set(32BLIT_PICO_2350 1) + set(32BLIT_PICO_2350 ${32BLIT_PICO_2350} PARENT_SCOPE) +endif() + # prevent find_package errors in pico_add_uf2_output later set(PICO_SDK_VERSION_MAJOR ${PICO_SDK_VERSION_MAJOR} PARENT_SCOPE) set(PICO_SDK_VERSION_MINOR ${PICO_SDK_VERSION_MINOR} PARENT_SCOPE) @@ -34,11 +39,13 @@ endfunction() add_library(BlitHalPico INTERFACE) target_sources(BlitHalPico INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/blit_launch.cpp ${CMAKE_CURRENT_LIST_DIR}/display.cpp ${CMAKE_CURRENT_LIST_DIR}/file.cpp ${CMAKE_CURRENT_LIST_DIR}/led.cpp ${CMAKE_CURRENT_LIST_DIR}/main.cpp ${CMAKE_CURRENT_LIST_DIR}/multiplayer.cpp + ${CMAKE_CURRENT_LIST_DIR}/usb.cpp ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c ) @@ -47,6 +54,7 @@ target_link_libraries(BlitHalPico INTERFACE pico_multicore pico_stdlib pico_unique_id pico_rand tinyusb_device FatFsBlitAPI + LauncherShared ) target_include_directories(BlitHalPico INTERFACE @@ -170,23 +178,110 @@ endif() target_compile_definitions(BlitHalPico INTERFACE ${BLIT_BOARD_DEFINITIONS}) target_link_libraries(BlitHalPico INTERFACE ${BLIT_BOARD_LIBRARIES}) +set(BLIT_BOARD_DEFINITIONS ${BLIT_BOARD_DEFINITIONS} PARENT_SCOPE) + +# some file paths we need later +if(32BLIT_PICO_2350) + set(LINKER_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/memmap_blit_2350.ld.in PARENT_SCOPE) +else() + set(LINKER_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/memmap_blit.ld.in PARENT_SCOPE) +endif() +set(STARTUP_SRC ${CMAKE_CURRENT_LIST_DIR}/startup.S ${CMAKE_CURRENT_LIST_DIR}/startup.cpp PARENT_SCOPE) +set(LAUNCHERSHARED_DIR ${CMAKE_CURRENT_LIST_DIR}/../launcher-shared PARENT_SCOPE) + # functions function(blit_executable NAME) message(STATUS "Processing ${NAME}") blit_executable_args(${ARGN}) add_executable(${NAME} ${SOURCES}) - target_compile_definitions(${NAME} PRIVATE PICO_EMBED_XIP_SETUP=1) - target_link_libraries(${NAME} BlitHalPico BlitEngine) + target_link_libraries(${NAME} BlitEngine pico_platform_headers) pico_enable_stdio_uart(${NAME} 1) pico_enable_stdio_usb(${NAME} 0) - pico_add_extra_outputs(${NAME}) - - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.uf2 - DESTINATION bin - ) + if(PICO_STANDALONE_UF2) + # standalone .uf2 + # TODO: import pico hal if standalone build + if(NOT TARGET LauncherShared) + add_subdirectory(${LAUNCHERSHARED_DIR} launcher-shared) + endif() + + target_compile_definitions(${NAME} PRIVATE BLIT_PICO_STANDALONE=1 PICO_EMBED_XIP_SETUP=1) + target_link_libraries(${NAME} BlitHalPico) + + pico_add_extra_outputs(${NAME}) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.uf2 + DESTINATION bin + ) + elseif(PICO_BLIT) + # loadable .blit + set_property(TARGET ${NAME} PROPERTY BLIT_PICO_BLIT TRUE) + + # calculate and verify flash offset + if(NOT PICO_BLIT_OFFSET_KB) + if(32BLIT_PICO_2350) + # if we build the file for a 4MB offset, we can use address translation + set(PICO_BLIT_OFFSET_KB 4096) + else() + set(PICO_BLIT_OFFSET_KB 512) + endif() + endif() + + math(EXPR offset_mod "${PICO_BLIT_OFFSET_KB} % 64") + if(${PICO_BLIT_OFFSET_KB} LESS 256 OR offset_mod) + message(FATAL_ERROR "Blit offset should be >= 256k and a multiple of 64k") + endif() + + math(EXPR FLASH_OFFSET_BYTES "${PICO_BLIT_OFFSET_KB} * 1024") + + # customise linker script + configure_file(${LINKER_SCRIPT} memmap_blit.ld) + set(LINKER_SCRIPT_OUT ${CMAKE_CURRENT_BINARY_DIR}/memmap_blit.ld) + + target_compile_definitions(${NAME} PRIVATE ${BLIT_BOARD_DEFINITIONS}) # need these for framebuffer config + target_compile_options(${NAME} PRIVATE -ffunction-sections -fdata-sections) + + # set device id in header, these should match the BlitDevice enum + if(32BLIT_PICO_2350) + set_source_files_properties(${STARTUP_SRC} PROPERTIES COMPILE_DEFINITIONS DEVICE_ID=3) + else() + set_source_files_properties(${STARTUP_SRC} PROPERTIES COMPILE_DEFINITIONS DEVICE_ID=2) + endif() + + # minimal pico-sdk libs + target_link_libraries(${NAME} boot_picobin_headers pico_bit_ops pico_clib_interface pico_cxx_options pico_divider pico_double pico_int64_ops pico_float pico_malloc pico_mem_ops pico_runtime_headers) + target_compile_definitions(${NAME} PRIVATE PICO_TIME_DEFAULT_ALARM_POOL_DISABLED) # avoid pulling timer and irq code + + target_link_options(${NAME} PRIVATE --specs=nosys.specs LINKER:--script=${LINKER_SCRIPT_OUT} LINKER:--gc-sections) + set_property(TARGET ${NAME} APPEND PROPERTY LINK_DEPENDS ${LINKER_SCRIPT_OUT}) + + target_sources(${NAME} PRIVATE ${STARTUP_SRC}) + + pico_add_bin_output(${NAME}) + pico_add_dis_output(${NAME}) + + # Ideally we want the .blit filename to match the .elf, but TARGET_FILE_BASE_NAME isn't always available + # (This only affects the firmware updater as it's the only thing setting a custom OUTPUT_NAME) + if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set(BLIT_FILENAME ${NAME}.blit) + else() + set(BLIT_FILENAME $.blit) + endif() + + # no relocs, just copy it + set(BIN_NAME "$>,$,$>.bin") + add_custom_command(TARGET ${NAME} POST_BUILD + VERBATIM + COMMENT "Building ${NAME}.blit" + COMMAND cp ${BIN_NAME} ${BLIT_FILENAME} + ) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BLIT_FILENAME} + DESTINATION bin + ) + endif() endfunction() function(blit_metadata TARGET FILE) @@ -208,20 +303,42 @@ function(blit_metadata TARGET FILE) include(${CMAKE_CURRENT_BINARY_DIR}/metadata.cmake) - # create metadata/binary info source at build time - set(METADATA_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_binary_info.cpp") + get_property(PICO_BLIT TARGET ${TARGET} PROPERTY BLIT_PICO_BLIT) - add_custom_command( - OUTPUT ${METADATA_SOURCE} - COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${32BLIT_TOOLS_EXECUTABLE} metadata --force --config ${FILE} --pico-bi ${METADATA_SOURCE} - DEPENDS ${FILE} - VERBATIM - ) + if(NOT PICO_BLIT) + # create metadata/binary info source at build time + set(METADATA_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_binary_info.cpp") - # add the generated source - target_sources(${TARGET} PRIVATE ${METADATA_SOURCE}) + add_custom_command( + OUTPUT ${METADATA_SOURCE} + COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${32BLIT_TOOLS_EXECUTABLE} metadata --force --config ${FILE} --pico-bi ${METADATA_SOURCE} + DEPENDS ${FILE} + VERBATIM + ) - # avoid the fallback to target name - target_compile_definitions(${TARGET} PRIVATE PICO_NO_BI_PROGRAM_NAME=1) + # add the generated source + target_sources(${TARGET} PRIVATE ${METADATA_SOURCE}) + + # avoid the fallback to target name + target_compile_definitions(${TARGET} PRIVATE PICO_NO_BI_PROGRAM_NAME=1) + else() + # real .blit metadata + if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set(BLIT_FILENAME ${TARGET}.blit) + else() + set(BLIT_FILENAME $.blit) + endif() + + add_custom_command( + TARGET ${TARGET} POST_BUILD + COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${32BLIT_TOOLS_EXECUTABLE} metadata --config ${FILE} --file ${CMAKE_CURRENT_BINARY_DIR}/${BLIT_FILENAME} + VERBATIM + ) + + # force relink on change so that the post-build commands are rerun + set_property(TARGET ${TARGET} APPEND PROPERTY LINK_DEPENDS ${FILE} ${METADATA_DEPENDS}) + endif() endfunction() + +add_subdirectory(loader) diff --git a/32blit-pico/blit_launch.cpp b/32blit-pico/blit_launch.cpp new file mode 100644 index 000000000..2a42e0b9f --- /dev/null +++ b/32blit-pico/blit_launch.cpp @@ -0,0 +1,474 @@ +#include + +#include "pico/stdlib.h" +#include "hardware/flash.h" +#include "hardware/sync.h" +#include "pico/multicore.h" + +#ifdef PICO_RP2350 +#include "hardware/structs/qmi.h" +#include "hardware/xip_cache.h" +#endif + +#include "blit_launch.hpp" +#include "file.hpp" + +#include "engine/engine.hpp" +#include "engine/file.hpp" + +#ifdef PICO_RP2350 +#define DEVICE_ID BlitDevice::RP2350 +#define FLASH_BASE XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE +#else +#define DEVICE_ID BlitDevice::RP2040 +#define FLASH_BASE XIP_NOCACHE_NOALLOC_BASE +#endif + +// code related to blit files and launching + +extern int (*do_tick)(uint32_t time); + +void disable_user_code(); + +extern bool core1_started; + +static uint32_t requested_launch_offset = 0; +static uint32_t current_game_offset = 0; + +static uint32_t get_installed_file_size(uint32_t offset) { + auto header = (BlitGameHeader *)(FLASH_BASE + offset); + + // check header magic + device + if(header->magic != blit_game_magic || header->device_id != DEVICE_ID) + return 0; + + auto size = header->end; + + // check metadata + auto meta_offset = offset + size; + if(memcmp((char *)(FLASH_BASE + meta_offset), "BLITMETA", 8) == 0) { + // add metadata size + size += *(uint16_t *)(FLASH_BASE + meta_offset + 8) + 10; + } + + return size; +} + +static uint32_t calc_num_blocks(uint32_t size) { + return (size - 1) / game_block_size + 1; +} + +static uint32_t find_flash_offset(uint32_t requested_size) { + uint32_t free_off = 0; // 0 is invalid as that's where the loader is + + // FIXME: avoid flash storage + + for(uint32_t off = 256 * 1024; off < PICO_FLASH_SIZE_BYTES;) { + auto size = get_installed_file_size(off); + + if(!size) { + // empty, store offset + if(!free_off) + free_off = off; + + off += game_block_size; + continue; + } + + if(free_off) { + // end of free space, check if large enough + auto found_space = off - free_off; + + if(found_space >= requested_size) + return free_off; + + free_off = 0; + } + + // skip to end + off += calc_num_blocks(size) * game_block_size; + } + + // last chance + if(free_off && PICO_FLASH_SIZE_BYTES - free_off >= requested_size) + return free_off; + + return 0; +} + +static bool read_file_metadata(void *file, RawMetadata &meta, RawTypeMetadata &type_meta) { + // read header and check magic + BlitGameHeader header; + if(read_file(file, 0, sizeof(header), (char *)&header) != sizeof(header)) + return false; + + if(header.magic != blit_game_magic) + return false; + + // read and check metadata header + auto meta_offset = header.end; + + char meta_header[10]; + if(read_file(file, meta_offset, 10, meta_header) != 10) + return false; + + if(memcmp(meta_header, "BLITMETA", 8) != 0) + return false; + + // read the reset of the metadata header + if(read_file(file, meta_offset + 10, sizeof(RawMetadata), (char *)&meta) != sizeof(RawMetadata)) + return false; + + // check for type data + meta_offset += 10 + sizeof(RawMetadata); + if(read_file(file, meta_offset, 8, meta_header) != 8) + return false; + + if(memcmp(meta_header, "BLITTYPE", 8) == 0) { + if(read_file(file, meta_offset + 8, sizeof(RawTypeMetadata), (char *)&type_meta) != sizeof(RawTypeMetadata)) + return false; + } + + return true; +} + +static uint32_t find_installed_blit(RawMetadata &meta) { + for(uint32_t off = 0; off < PICO_FLASH_SIZE_BYTES;) { + auto size = get_installed_file_size(off); + + if(!size) { + off += game_block_size; + continue; + } + + auto header = (BlitGameHeader *)(FLASH_BASE + off); + auto flash_meta = (RawMetadata *)(FLASH_BASE + off + header->end + 10); + + // check CRC ant title + if(meta.crc32 == flash_meta->crc32 && strcmp(meta.title, flash_meta->title) == 0) { + return off; + } + + off += calc_num_blocks(size) * game_block_size; + } + + return ~0u; +} + +static bool cleanup_duplicates(RawMetadata &meta, RawTypeMetadata &type_meta, uint32_t new_offset) { + bool ret = false; + for(uint32_t off = 0; off < PICO_FLASH_SIZE_BYTES;) { + auto size = get_installed_file_size(off); + + if(!size) { + off += game_block_size; + continue; + } + + auto header = (BlitGameHeader *)(FLASH_BASE + off); + auto flash_meta = (RawMetadata *)(FLASH_BASE + off + header->end + 10); + + // check title and author, ignore the current copy of the game + if(off != new_offset && strcmp(meta.title, flash_meta->title) == 0 && strcmp(meta.author, flash_meta->author) == 0) { + erase_game(off); + + if(off == current_game_offset) + ret = true; + } + + off += calc_num_blocks(size) * game_block_size; + } + + return ret; +} + +// 32blit API + +RawMetadata *get_running_game_metadata() { +#ifdef BUILD_LOADER + if(!current_game_offset) + return nullptr; + + auto game_ptr = reinterpret_cast(FLASH_BASE + current_game_offset); + + auto header = reinterpret_cast(game_ptr); + + if(header->magic == blit_game_magic) { + auto end_ptr = game_ptr + header->end; + if(memcmp(end_ptr, "BLITMETA", 8) == 0) + return reinterpret_cast(end_ptr + 10); + } + +#endif + return nullptr; +} + +bool launch_file(const char *path) { + uint32_t flash_offset = ~0u; + + if(strncmp(path, "flash:/", 7) == 0) // from flash + flash_offset = atoi(path + 7) * game_block_size; + else { + // from storage + auto file = open_file(path, blit::OpenMode::read); + + if(!file) + return false; + + // read file metadata and try to find matching installed gat + RawMetadata meta; + RawTypeMetadata type_meta = {}; + + if(read_file_metadata(file, meta, type_meta)) + flash_offset = find_installed_blit(meta); + + // flash if not found + if(flash_offset == ~0u) { + BlitWriter writer; + + uint32_t file_offset = 0; + uint32_t len = get_file_length(file); + + writer.init(len); + + // read in small chunks + uint8_t buf[FLASH_PAGE_SIZE]; + + while(file_offset < len) { + auto bytes_read = read_file(file, file_offset, FLASH_PAGE_SIZE, (char *)buf); + if(bytes_read <= 0) + break; + + if(!writer.write(buf, bytes_read)) + break; + + file_offset += bytes_read; + } + + close_file(file); + + // didn't write everything, fail launch + if(writer.get_remaining() > 0) + return false; + + flash_offset = writer.get_flash_offset(); + + cleanup_duplicates(meta, type_meta, flash_offset); + } else + close_file(file); + } + + auto header = (BlitGameHeader *)(FLASH_BASE + flash_offset); + // check header magic + device + if(header->magic != blit_game_magic || header->device_id != DEVICE_ID) + return false; + + if(!header->init || !header->render || !header->tick) + return false; + + requested_launch_offset = flash_offset; + return true; +} + +blit::CanLaunchResult can_launch(const char *path) { +#ifdef BUILD_LOADER + if(strncmp(path, "flash:/", 7) == 0) { + // assume anything flashed is compatible for now + return blit::CanLaunchResult::Success; + } + + // get the extension + std::string_view sv(path); + auto last_dot = sv.find_last_of('.'); + auto ext = last_dot == std::string::npos ? "" : std::string(sv.substr(last_dot + 1)); + for(auto &c : ext) + c = tolower(c); + + if(ext == "blit") { + BlitGameHeader header; + auto file = open_file(path, blit::OpenMode::read); + + if(!file) + return blit::CanLaunchResult::InvalidFile; + + auto bytes_read = read_file(file, 0, sizeof(header), (char *)&header); + + if(bytes_read == sizeof(header) && header.magic == blit_game_magic && header.device_id == DEVICE_ID) { + close_file(file); + return blit::CanLaunchResult::Success; + } + + close_file(file); + return blit::CanLaunchResult::IncompatibleBlit; + } +#endif + + return blit::CanLaunchResult::UnknownType; +} + +void delayed_launch() { + if(!requested_launch_offset) + return; + + auto header = (BlitGameHeader *)(FLASH_BASE + requested_launch_offset); + +#ifdef PICO_RP2350 + uint32_t header_offset = *(uint32_t *)(FLASH_BASE + requested_launch_offset + sizeof(BlitGameHeader)); + if(header_offset != requested_launch_offset) { + // setup translation + uint32_t size = 4 * 1024 * 1024; // TODO: use (rounded) blit size + qmi_hw->atrans[1] = (size >> 12) << QMI_ATRANS1_SIZE_LSB + | (requested_launch_offset >> 12) << QMI_ATRANS1_BASE_LSB; + + // invalidate cache + xip_cache_invalidate_range(4 * 1024 * 1024, size); + + // FIXME: handle previous blit also using translation on failure + } +#endif + + // save in case launch fails + uint32_t last_game_offset = current_game_offset; + + current_game_offset = requested_launch_offset; + requested_launch_offset = 0; + + if(!header->init(0)) { + blit::debugf("failed to init game!\n"); + current_game_offset = last_game_offset; + return; + } + + blit::render = header->render; + do_tick = header->tick; +} + +void list_installed_games(std::function callback) { + for(uint32_t off = 0; off < PICO_FLASH_SIZE_BYTES;) { + auto size = get_installed_file_size(off); + + if(!size) { + off += game_block_size; + continue; + } + + callback((const uint8_t *)(FLASH_BASE + off), off / game_block_size, size); + + off += calc_num_blocks(size) * game_block_size; + } +} + +void erase_game(uint32_t offset) { +#ifdef BUILD_LOADER + // check alignment + if(offset & (game_block_size - 1)) + return; + + // check in bounds + // TODO: prevent erasing fs if flash storage is used? + if(offset >= PICO_FLASH_SIZE_BYTES) + return; + + auto size = get_installed_file_size(offset); + + // fall back to one block if size unknown + auto num_blocks = size == 0 ? 1 : calc_num_blocks(size); + + // do erase + auto status = save_and_disable_interrupts(); + + if(core1_started) + multicore_lockout_start_blocking(); // pause core1 + + // the real erase size is smaller than the one baked into the API... + static_assert(game_block_size % FLASH_SECTOR_SIZE == 0); + flash_range_erase(offset, num_blocks * game_block_size); + + if(core1_started) + multicore_lockout_end_blocking(); // resume core1 + + restore_interrupts(status); +#endif +} + +// .blit file writer +void BlitWriter::init(uint32_t file_len) { + this->file_len = file_len; + file_offset = flash_offset = 0; +} + +bool BlitWriter::write(const uint8_t *buf, uint32_t len) { + if(!flash_offset) { + if(!prepare_write(buf)) + return false; + } + + if(file_offset >= file_len) + return false; + + // write page + auto status = save_and_disable_interrupts(); + + if(core1_started) + multicore_lockout_start_blocking(); // pause core1 + + // assuming len <= page size and buf size == page size + flash_range_program(flash_offset + file_offset, buf, FLASH_PAGE_SIZE); + + if(core1_started) + multicore_lockout_end_blocking(); // resume core1 + + restore_interrupts(status); + + file_offset += len; + + return true; +} + +uint32_t BlitWriter::get_remaining() const { + return file_len - file_offset; +} + +uint32_t BlitWriter::get_flash_offset() const { + return flash_offset; +} + +bool BlitWriter::prepare_write(const uint8_t *buf) { + auto header = (BlitGameHeader *)buf; + + if(header->magic != blit_game_magic || header->device_id != DEVICE_ID) { + blit::debugf("Invalid blit header!"); + return false; + } + + // currently non-relocatable, so base address is stored after header + flash_offset = *(uint32_t *)(buf + sizeof(BlitGameHeader)); + flash_offset &= 0xFFFFFF; + +#ifdef PICO_RP2350 + if(flash_offset == 4 * 1024 * 1024) { + // we can use address translation for this, so flash in any free space + flash_offset = find_flash_offset(file_len); + } +#endif + + printf("PROG: flash off %lu\n", flash_offset); + + disable_user_code(); + + // erase flash + auto status = save_and_disable_interrupts(); + + if(core1_started) + multicore_lockout_start_blocking(); // pause core1 + + auto erase_size = ((file_len - 1) / FLASH_SECTOR_SIZE) + 1; + flash_range_erase(flash_offset, erase_size * FLASH_SECTOR_SIZE); + + if(core1_started) + multicore_lockout_end_blocking(); // resume core1 + + restore_interrupts(status); + + return true; +} diff --git a/32blit-pico/blit_launch.hpp b/32blit-pico/blit_launch.hpp new file mode 100644 index 000000000..0b6a36398 --- /dev/null +++ b/32blit-pico/blit_launch.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "executable.hpp" +#include "engine/api_private.hpp" + +// this is the 32blit's flash erase size, some parts of the API depend on this... +static constexpr unsigned int game_block_size = 64 * 1024; + +RawMetadata *get_running_game_metadata(); + +bool launch_file(const char *path); +blit::CanLaunchResult can_launch(const char *path); +void delayed_launch(); + +void list_installed_games(std::function callback); + +void erase_game(uint32_t offset); + +class BlitWriter final { +public: + void init(uint32_t file_len); + + bool write(const uint8_t *buf, uint32_t len); + + uint32_t get_remaining() const; + uint32_t get_flash_offset() const; + +private: + bool prepare_write(const uint8_t *buf); + + uint32_t file_len; + uint32_t file_offset; + + uint32_t flash_offset; +}; \ No newline at end of file diff --git a/32blit-pico/config.h b/32blit-pico/config.h index a5f948d27..d7df19cbd 100644 --- a/32blit-pico/config.h +++ b/32blit-pico/config.h @@ -164,6 +164,15 @@ #define DISPLAY_HEIGHT 240 #endif +#if ALLOW_HIRES && DOUBLE_BUFFERED_HIRES +#define FRAMEBUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT * 2) +#elif ALLOW_HIRES +#define FRAMEBUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT) +#else +// height rounded up to handle the 135px display +#define FRAMEBUFFER_SIZE ((DISPLAY_WIDTH / 2) * ((DISPLAY_HEIGHT + 1) / 2) * 2) // double-buffered +#endif + #ifndef LCD_CS_PIN #define LCD_CS_PIN PICO_DEFAULT_SPI_CSN_PIN #endif diff --git a/32blit-pico/display.cpp b/32blit-pico/display.cpp index fafd4570e..3950e021f 100644 --- a/32blit-pico/display.cpp +++ b/32blit-pico/display.cpp @@ -4,26 +4,17 @@ using namespace blit; -// height rounded up to handle the 135px display -// this is in bytes -static const int lores_page_size = (DISPLAY_WIDTH / 2) * ((DISPLAY_HEIGHT + 1) / 2) * 2; - -#if ALLOW_HIRES && DOUBLE_BUFFERED_HIRES -static const int fb_size = DISPLAY_WIDTH * DISPLAY_HEIGHT * 2; -#elif ALLOW_HIRES -static const int fb_size = DISPLAY_WIDTH * DISPLAY_HEIGHT; -#else -static const int fb_size = lores_page_size; // double-buffered -#endif - SurfaceInfo cur_surf_info; bool fb_double_buffer = true; -#if defined(BLIT_BOARD_PIMORONI_PICOVISION) -static const uint16_t *screen_fb = nullptr; +#if defined(BUILD_LOADER) || defined(BLIT_BOARD_PIMORONI_PICOVISION) +uint16_t *screen_fb = nullptr; +static uint32_t max_fb_size = 0; +static Size max_fb_bounds(DISPLAY_WIDTH, DISPLAY_HEIGHT); #else -uint16_t screen_fb[fb_size]; +uint16_t screen_fb[FRAMEBUFFER_SIZE]; +static const uint32_t max_fb_size = FRAMEBUFFER_SIZE * sizeof(uint16_t); #endif static const Size lores_screen_size(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2); @@ -32,7 +23,7 @@ static const Size hires_screen_size(DISPLAY_WIDTH, DISPLAY_HEIGHT); ScreenMode cur_screen_mode = ScreenMode::lores; int get_display_page_size() { - return cur_surf_info.bounds.area() * pixel_format_stride[int(cur_surf_info.format)]; + return max_fb_size / 2; } // blit api @@ -55,6 +46,8 @@ bool set_screen_mode_format(ScreenMode new_mode, SurfaceTemplate &new_surf_templ if(new_surf_template.format == (PixelFormat)-1) new_surf_template.format = DEFAULT_SCREEN_FORMAT; + int min_buffers = 1; + switch(new_mode) { case ScreenMode::lores: if(new_surf_template.bounds.empty()) @@ -62,26 +55,36 @@ bool set_screen_mode_format(ScreenMode new_mode, SurfaceTemplate &new_surf_templ else new_surf_template.bounds /= 2; +#ifdef BUILD_LOADER + if(new_surf_template.bounds.w > max_fb_bounds.w / 2) + new_surf_template.bounds.w = max_fb_bounds.w / 2; +#endif + min_buffers = 2; break; case ScreenMode::hires: case ScreenMode::hires_palette: -#if ALLOW_HIRES if(new_surf_template.bounds.empty()) new_surf_template.bounds = hires_screen_size; - break; -#else - return false; // no hires for scanvideo +#ifdef BUILD_LOADER + if(new_surf_template.bounds.w > max_fb_bounds.w) + new_surf_template.bounds.w = max_fb_bounds.w; #endif + break; } // check the framebuffer is large enough for mode auto fb_size = uint32_t(new_surf_template.bounds.area()) * pixel_format_stride[int(new_surf_template.format)]; +// TODO: more generic "doesn't have a framebuffer"? +#ifndef BLIT_BOARD_PIMORONI_PICOVISION + if(max_fb_size < fb_size * min_buffers) + return false; +#endif if(!display_mode_supported(new_mode, new_surf_template)) return false; - fb_double_buffer = fb_size * 2 <= sizeof(screen_fb); + fb_double_buffer = fb_size * 2 <= max_fb_size; if(!fb_double_buffer) screen.data = new_surf_template.data; @@ -98,3 +101,12 @@ bool set_screen_mode_format(ScreenMode new_mode, SurfaceTemplate &new_surf_templ void set_screen_palette(const Pen *colours, int num_cols) { } + +void set_framebuffer(uint8_t *data, uint32_t max_size, Size max_bounds) { +#if defined(BUILD_LOADER) && !defined(BLIT_BOARD_PIMORONI_PICOVISION) + screen_fb = (uint16_t *)data; + screen.data = data; + max_fb_size = max_size; + max_fb_bounds = max_bounds; +#endif +} diff --git a/32blit-pico/display.hpp b/32blit-pico/display.hpp index 1cbed84c8..2d1338373 100644 --- a/32blit-pico/display.hpp +++ b/32blit-pico/display.hpp @@ -9,7 +9,9 @@ extern blit::ScreenMode cur_screen_mode; extern bool fb_double_buffer; -#ifndef BLIT_BOARD_PIMORONI_PICOVISION +#if defined(BUILD_LOADER) || defined(BLIT_BOARD_PIMORONI_PICOVISION) +extern uint16_t *screen_fb; +#else extern uint16_t screen_fb[]; #endif @@ -30,3 +32,5 @@ blit::SurfaceInfo &set_screen_mode(blit::ScreenMode mode); bool set_screen_mode_format(blit::ScreenMode new_mode, blit::SurfaceTemplate &new_surf_template); void set_screen_palette(const blit::Pen *colours, int num_cols); + +void set_framebuffer(uint8_t *data, uint32_t max_size, blit::Size max_bounds); diff --git a/32blit-pico/display/dbi.cpp b/32blit-pico/display/dbi.cpp index 4b6d3be6b..66e6dc41c 100644 --- a/32blit-pico/display/dbi.cpp +++ b/32blit-pico/display/dbi.cpp @@ -289,6 +289,9 @@ static void prepare_write() { static void update() { dma_channel_wait_for_finish_blocking(dma_channel); + if(!frame_buffer) + return; + if(!write_mode) prepare_write(); diff --git a/32blit-pico/display/picovision.cpp b/32blit-pico/display/picovision.cpp index e8f52702b..8d20bd8c0 100644 --- a/32blit-pico/display/picovision.cpp +++ b/32blit-pico/display/picovision.cpp @@ -40,7 +40,7 @@ static bool display_enabled = false; static uint8_t need_mode_change = 2; static int cur_resolution = 0; -static volatile bool do_render = true; +static volatile bool do_render = false; static uint16_t blend_buf[512]; @@ -464,4 +464,7 @@ void display_mode_changed(blit::ScreenMode new_mode, blit::SurfaceTemplate &new_ } need_mode_change = 2; // make sure to update both banks + + if(!display_enabled) + do_render = true; } diff --git a/32blit-pico/file.cpp b/32blit-pico/file.cpp index ba43d01c2..f27196ba3 100644 --- a/32blit-pico/file.cpp +++ b/32blit-pico/file.cpp @@ -8,8 +8,10 @@ #include "ff.h" #include "diskio.h" +#include "blit_launch.hpp" #include "file.hpp" #include "storage.hpp" +#include "executable.hpp" static FATFS fs; static bool initialised = false; @@ -89,18 +91,22 @@ const char *get_save_path() { app_name = "_unknown"; - // fint the program name in the binary info - extern binary_info_t *__binary_info_start, *__binary_info_end; + if(auto meta = get_running_game_metadata()) + app_name = meta->title; + else { + // find the program name in the binary info + extern binary_info_t *__binary_info_start, *__binary_info_end; - for(auto tag_ptr = &__binary_info_start; tag_ptr != &__binary_info_end ; tag_ptr++) { - if((*tag_ptr)->type != BINARY_INFO_TYPE_ID_AND_STRING || (*tag_ptr)->tag != BINARY_INFO_TAG_RASPBERRY_PI) - continue; + for(auto tag_ptr = &__binary_info_start; tag_ptr != &__binary_info_end ; tag_ptr++) { + if((*tag_ptr)->type != BINARY_INFO_TYPE_ID_AND_STRING || (*tag_ptr)->tag != BINARY_INFO_TAG_RASPBERRY_PI) + continue; - auto id_str_tag = (binary_info_id_and_string_t *)*tag_ptr; + auto id_str_tag = (binary_info_id_and_string_t *)*tag_ptr; - if(id_str_tag->id == BINARY_INFO_ID_RP_PROGRAM_NAME) { - app_name = id_str_tag->value; - break; + if(id_str_tag->id == BINARY_INFO_ID_RP_PROGRAM_NAME) { + app_name = id_str_tag->value; + break; + } } } diff --git a/32blit-pico/loader/CMakeLists.txt b/32blit-pico/loader/CMakeLists.txt new file mode 100644 index 000000000..00197c234 --- /dev/null +++ b/32blit-pico/loader/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable(blit-loader loader.cpp) +target_link_libraries(blit-loader BlitHalPico BlitEngine) +target_link_options(blit-loader PUBLIC -specs=nano.specs -u _printf_float) +target_compile_definitions(blit-loader PRIVATE BUILD_LOADER PICO_EMBED_XIP_SETUP=1) + +if(PICO_PLATFORM MATCHES "^rp2350") + pico_set_linker_script(blit-loader ${CMAKE_CURRENT_LIST_DIR}/../memmap_blit_loader_2350.ld) +else() + pico_set_linker_script(blit-loader ${CMAKE_CURRENT_LIST_DIR}/../memmap_blit_loader.ld) +endif() + +pico_enable_stdio_uart(blit-loader 1) +pico_enable_stdio_usb(blit-loader 0) + +pico_add_extra_outputs(blit-loader) + +pico_set_program_name(blit-loader "32blit loader") +pico_set_program_description(blit-loader "Loads 32blit games") +pico_set_program_url(blit-loader "https://github.com/32blit/32blit-sdk") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/blit-loader.uf2 + DESTINATION bin +) diff --git a/32blit-pico/loader/loader.cpp b/32blit-pico/loader/loader.cpp new file mode 100644 index 000000000..a8703ab0b --- /dev/null +++ b/32blit-pico/loader/loader.cpp @@ -0,0 +1,30 @@ +#include "32blit.hpp" +#include "engine/api_private.hpp" + +using namespace blit; + +void init() { + bool done = false; + // launch the first thing we find + // TODO: find launcher + api.list_installed_games([&done](const uint8_t *ptr, uint32_t block, uint32_t size){ + if(!done) { + auto path = "flash:/" + std::to_string(block) + ".blit"; + done = api.launch(path.c_str()); + } + }); + + if(!done) { + // fall back to launcher in storage + // TODO: auto-update + if(file_exists("launcher.blit")) { + api.launch("launcher.blit"); + } + } +} + +void render(uint32_t time) { +} + +void update(uint32_t time) { +} diff --git a/32blit-pico/main.cpp b/32blit-pico/main.cpp index 2f33434ae..d323640a9 100644 --- a/32blit-pico/main.cpp +++ b/32blit-pico/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "hardware/clocks.h" #include "hardware/structs/rosc.h" @@ -11,6 +12,7 @@ #include "audio.hpp" #include "binary_info.hpp" +#include "blit_launch.hpp" #include "config.h" #include "display.hpp" #include "file.hpp" @@ -26,6 +28,10 @@ using namespace blit; static blit::AudioChannel channels[CHANNEL_COUNT]; +int (*do_tick)(uint32_t time) = blit::tick; + +static alarm_id_t home_hold_alarm_id = 0; + // override terminate handler to save ~20-30k namespace __cxxabiv1 { std::terminate_handler __terminate_handler = std::abort; @@ -62,6 +68,25 @@ const char *get_launch_path() { static GameMetadata get_metadata() { GameMetadata ret; + // check for blit metadata + if(auto meta = get_running_game_metadata()) { + // this is identical to the 32blit-stm code + ret.title = meta->title; + ret.author = meta->author; + ret.description = meta->description; + ret.version = meta->version; + + if(memcmp(meta + 1, "BLITTYPE", 8) == 0) { + auto type_meta = reinterpret_cast(reinterpret_cast(meta) + sizeof(*meta) + 8); + ret.url = type_meta->url; + ret.category = type_meta->category; + } else { + ret.url = ""; + ret.category = "none"; + } + return ret; + } + // parse binary info extern binary_info_t *__binary_info_start, *__binary_info_end; @@ -101,7 +126,12 @@ static GameMetadata get_metadata() { return ret; } +static uint8_t *get_screen_data() { + return screen.data; +} + // blit API +[[gnu::section(".rodata.api_const")]] static const blit::APIConst blit_api_const { blit::api_version_major, blit::api_version_minor, @@ -136,8 +166,8 @@ static const blit::APIConst blit_api_const { nullptr, // decode_jpeg_buffer nullptr, // decode_jpeg_file - nullptr, // launch - nullptr, // erase_game + ::launch_file, + ::erase_game, nullptr, // get_type_handler_metadata ::get_launch_path, @@ -160,10 +190,14 @@ static const blit::APIConst blit_api_const { nullptr, // cdc_write nullptr, // cdc_read - nullptr, // list_installed_games - nullptr, // can_launch + ::list_installed_games, + ::can_launch, + + ::get_screen_data, + ::set_framebuffer, }; +[[gnu::section(".bss.api_data")]] static blit::APIData blit_api_data; namespace blit { @@ -176,6 +210,36 @@ void init(); void render(uint32_t); void update(uint32_t); +void disable_user_code() { + // TODO: handle re-enabling + do_tick = blit::tick; + blit::render = ::render; +} + +[[maybe_unused]] +static int64_t home_hold_callback(alarm_id_t id, void *user_data) { + home_hold_alarm_id = 0; + + ::init(); // re-initialising the loader is effectively a reset + + return 0; +} + +static void check_home_button() { +#ifdef BUILD_LOADER + if((api_data.buttons & Button::HOME) && !home_hold_alarm_id) { + // start timer for exit/reset + home_hold_alarm_id = add_alarm_in_ms(1000, home_hold_callback, nullptr, false); + debugf("home down at %i alarm %i\n", ::now(), home_hold_alarm_id); + } else if(!(api_data.buttons & Button::HOME) && home_hold_alarm_id) { + // released, cancel timer + debugf("home up at %i alarm %i\n", ::now(), home_hold_alarm_id); + cancel_alarm(home_hold_alarm_id); + home_hold_alarm_id = 0; + } +#endif +} + bool core1_started = false; #ifdef ENABLE_CORE1 @@ -239,7 +303,9 @@ int main() { #endif #endif +#ifndef BUILD_LOADER blit::set_screen_mode(ScreenMode::lores); +#endif blit::render = ::render; blit::update = ::update; @@ -250,12 +316,19 @@ int main() { while(true) { auto now = ::now(); update_display(now); + update_input(); - int ms_to_next_update = tick(::now()); + check_home_button(); + + int ms_to_next_update = do_tick(::now()); + update_led(); update_usb(); update_multiplayer(); + // do requested launch when no user code is running + delayed_launch(); + if(ms_to_next_update > 1 && !display_render_needed()) best_effort_wfe_or_timeout(make_timeout_time_ms(ms_to_next_update - 1)); } diff --git a/32blit-pico/memmap_blit.ld.in b/32blit-pico/memmap_blit.ld.in new file mode 100644 index 000000000..6af2bd5bb --- /dev/null +++ b/32blit-pico/memmap_blit.ld.in @@ -0,0 +1,294 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000 + ${FLASH_OFFSET_BYTES}, LENGTH = 2048k + RAM(rwx) : ORIGIN = 0x20008000, LENGTH = 224k + /*SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k*/ +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* No second stage bootloader as we're using a seperate loader + Instead, there's a header here + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /*.boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + */ + + .header : { + KEEP (*(.header)) + . = ALIGN(256); + } > FLASH + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + /*.scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y);*/ + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + /*.stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + *(.stack*) + } > SCRATCH_Y*/ + + .flash_end : { + LONG(0x12345678) /*workaround for __flash_binary_end pointing past the end*/ + PROVIDE(__flash_binary_end = .); + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + /*__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop);*/ + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check if data + heap + stack exceeds RAM limit */ + /*ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")*/ + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ + + __flash_binary_size = __flash_binary_end - __flash_binary_start; +} + diff --git a/32blit-pico/memmap_blit_2350.ld.in b/32blit-pico/memmap_blit_2350.ld.in new file mode 100644 index 000000000..98526cab8 --- /dev/null +++ b/32blit-pico/memmap_blit_2350.ld.in @@ -0,0 +1,289 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000 + ${FLASH_OFFSET_BYTES}, LENGTH = 4096k + RAM(rwx) : ORIGIN = 0x20008000, LENGTH = 480k + /*SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k*/ +} + +ENTRY(_entry_point) + +SECTIONS +{ + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* 32blit header */ + .header : { + KEEP (*(.header)) + . = ALIGN(256); + } > FLASH + + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *libgcc.a:cmse_nonsecure_call.o + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + *(.srodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + *(.sdata*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + PROVIDE(__global_pointer$ = . + 2K); + *(.sbss*) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + /*.scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y);*/ + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + /*.stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y*/ + + .flash_end : { + LONG(0x12345678) /*workaround for __flash_binary_end pointing past the end*/ + PROVIDE(__flash_binary_end = .); + } > FLASH =0xaa + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + /*__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop);*/ + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check if data + heap + stack exceeds RAM limit */ + /*ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")*/ + + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") + ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") + + /* todo assert on extra code */ + + __flash_binary_size = __flash_binary_end - __flash_binary_start; +} diff --git a/32blit-pico/memmap_blit_loader.ld b/32blit-pico/memmap_blit_loader.ld new file mode 100644 index 000000000..b1c4e4c35 --- /dev/null +++ b/32blit-pico/memmap_blit_loader.ld @@ -0,0 +1,301 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 256k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 32k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + + . = ALIGN(256); + __api_const_start = .; + KEEP (*(*.api_const)) + __api_const_end = .; + + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .api_data : { + __api_data_start = .; + KEEP (*(*.api_data)) + } + + .ram_vector_table (NOLOAD): { + . = ALIGN(256); + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + KEEP(*(.embedded_end_block*)) + PROVIDE(__flash_binary_end = .); + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ + + ASSERT(__api_const_start == ORIGIN(FLASH) + 512, "api_const must be 512 bytes from the start of flash") + ASSERT(__api_data_start == ORIGIN(RAM), "api_data must be at the start of RAM") +} \ No newline at end of file diff --git a/32blit-pico/memmap_blit_loader_2350.ld b/32blit-pico/memmap_blit_loader_2350.ld new file mode 100644 index 000000000..adddbbd71 --- /dev/null +++ b/32blit-pico/memmap_blit_loader_2350.ld @@ -0,0 +1,317 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 256k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 32k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + + . = ALIGN(256); + __api_const_start = .; + KEEP (*(*.api_const)) + __api_const_end = .; + + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *libgcc.a:cmse_nonsecure_call.o + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + /* Note the boot2 section is optional, and should be discarded if there is + no reference to it *inside* the binary, as it is not called by the + bootrom. (The bootrom performs a simple best-effort XIP setup and + leaves it to the binary to do anything more sophisticated.) However + there is still a size limit of 256 bytes, to ensure the boot2 can be + stored in boot RAM. + + Really this is a "XIP setup function" -- the name boot2 is historic and + refers to its dual-purpose on RP2040, where it also handled vectoring + from the bootrom into the user image. + */ + + .boot2 : { + __boot2_start__ = .; + *(.boot2) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ <= 256, + "ERROR: Pico second stage bootloader must be no more than 256 bytes in size") + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + *(.srodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .api_data : { + __api_data_start = .; + KEEP (*(*.api_data)) + } + + .ram_vector_table (NOLOAD): { + . = ALIGN(256); + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + *(.sdata*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + PROVIDE(__global_pointer$ = . + 2K); + *(.sbss*) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + KEEP(*(.embedded_end_block*)) + PROVIDE(__flash_binary_end = .); + } > FLASH =0xaa + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") + ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") + + /* todo assert on extra code */ + + ASSERT(__api_const_start == ORIGIN(FLASH) + 512, "api_const must be 512 bytes from the start of flash") + ASSERT(__api_data_start == ORIGIN(RAM), "api_data must be at the start of RAM") +} \ No newline at end of file diff --git a/32blit-pico/multiplayer.cpp b/32blit-pico/multiplayer.cpp index 32cacf3f9..ce3eb198e 100644 --- a/32blit-pico/multiplayer.cpp +++ b/32blit-pico/multiplayer.cpp @@ -8,12 +8,49 @@ static bool multiplayer_enabled = false; static bool peer_connected = false; -static uint8_t cur_header[8]; -static int header_pos = 0; - static uint16_t mp_buffer_len, mp_buffer_off; static uint8_t *mp_buffer = nullptr; +CDCCommand::Status CDCHandshakeCommand::update() { + uint8_t b; + usb_cdc_read(&b, 1); + peer_connected = b != 0; + + if(peer_connected) + send_multiplayer_handshake(true); + + return Status::Done; +} + +void CDCUserCommand::init() { + mp_buffer_off = 0; +} + +CDCCommand::Status CDCUserCommand::update() { + // read length + if(!mp_buffer) { + if(usb_cdc_read_available() < 2) + return Status::Continue; + + usb_cdc_read((uint8_t *)&mp_buffer_len, 2); + mp_buffer = new uint8_t[mp_buffer_len]; + } + + // read data + mp_buffer_off += usb_cdc_read(mp_buffer + mp_buffer_off, mp_buffer_len - mp_buffer_off); + + if(mp_buffer_off == mp_buffer_len) { + if(blit::api_data.message_received) + blit::api_data.message_received(mp_buffer, mp_buffer_len); + + delete[] mp_buffer; + mp_buffer = nullptr; + return Status::Done; + } + + return Status::Continue; +} + void send_multiplayer_handshake(bool is_reply) { uint8_t val = 0; if(multiplayer_enabled) @@ -28,60 +65,6 @@ void update_multiplayer() { if(!usb_cdc_connected()) { peer_connected = false; } - - while(usb_cdc_read_available()) { - // match header - if(header_pos < 8) { - usb_cdc_read(cur_header + header_pos, 1); - - const char *expected = "32BL"; - if(header_pos >= 4 || cur_header[header_pos] == expected[header_pos]) - header_pos++; - else - header_pos = 0; - } else { - - // get USER packet - if(mp_buffer) { - mp_buffer_off += usb_cdc_read(mp_buffer + mp_buffer_off, mp_buffer_len - mp_buffer_off); - - if(mp_buffer_off == mp_buffer_len) { - if(blit::api_data.message_received) - blit::api_data.message_received(mp_buffer, mp_buffer_len); - - delete[] mp_buffer; - mp_buffer = nullptr; - header_pos = 0; - } - continue; - } - - // got header - if(memcmp(cur_header + 4, "MLTI", 4) == 0) { - // handshake packet - uint8_t b; - usb_cdc_read(&b, 1); - peer_connected = b != 0; - - if(peer_connected) - send_multiplayer_handshake(true); - - // done - header_pos = 0; - } else if(memcmp(cur_header + 4, "USER", 4) == 0) { - if(usb_cdc_read_available() < 2) - break; - - usb_cdc_read((uint8_t *)&mp_buffer_len, 2); - mp_buffer_off = 0; - mp_buffer = new uint8_t[mp_buffer_len]; - - } else { - printf("got: %c%c%c%c%c%c%c%c\n", cur_header[0], cur_header[1], cur_header[2], cur_header[3], cur_header[4], cur_header[5], cur_header[6], cur_header[7]); - header_pos = 0; - } - } - } } bool is_multiplayer_connected() { diff --git a/32blit-pico/multiplayer.hpp b/32blit-pico/multiplayer.hpp index af6639ef6..4174bb45e 100644 --- a/32blit-pico/multiplayer.hpp +++ b/32blit-pico/multiplayer.hpp @@ -1,6 +1,18 @@ #pragma once #include +#include "usb.hpp" + +class CDCHandshakeCommand final : public CDCCommand { + void init() override {} + Status update() override; +}; + +class CDCUserCommand final : public CDCCommand { + void init() override; + Status update() override; +}; + void update_multiplayer(); void send_multiplayer_handshake(bool is_reply = false); diff --git a/32blit-pico/startup.S b/32blit-pico/startup.S new file mode 100644 index 000000000..26a637ddf --- /dev/null +++ b/32blit-pico/startup.S @@ -0,0 +1,77 @@ +#include "engine/api_version.h" + +.cpu cortex-m0 +.thumb + +.section .header, "a" + +.word 0x54494C42 +.word do_render // render +.word _ZN4blit4tickEm // tick +.word _entry_point // init +.word __flash_binary_size +.word DEVICE_ID // device_id + padding +.hword BLIT_API_VERSION_MAJOR +.hword BLIT_API_VERSION_MINOR +.word __flash_binary_start // unused + +.section .rodata, "a" +.align 4 +.global _ZN4blit3apiE +_ZN4blit3apiE: + .word 0x10000200 + +.global _ZN4blit8api_dataE +_ZN4blit8api_dataE: + .word 0x20000000 + +// based on pico-sdk crt0.S +.section .reset, "ax" + +.type _entry_point,%function +.thumb_func +.global _entry_point +_entry_point: + push {r4, lr} + // .data copy + adr r4, data_cpy_table + + // assume there is at least one entry +1: + ldmia r4!, {r1-r3} + cmp r1, #0 + beq 2f + bl data_cpy + b 1b +2: + + // Zero out the BSS + ldr r1, =__bss_start__ + ldr r2, =__bss_end__ + movs r0, #0 + b bss_fill_test +bss_fill_loop: + stm r1!, {r0} +bss_fill_test: + cmp r1, r2 + bne bss_fill_loop + +platform_entry: // symbol for stack traces + bl do_init + pop {r4, pc} + +data_cpy_loop: + ldm r1!, {r0} + stm r2!, {r0} +data_cpy: + cmp r2, r3 + blo data_cpy_loop + bx lr + +.align 2 +data_cpy_table: +.word __etext +.word __data_start__ +.word __data_end__ + +.word 0 // null terminator diff --git a/32blit-pico/startup.cpp b/32blit-pico/startup.cpp new file mode 100644 index 000000000..83002e921 --- /dev/null +++ b/32blit-pico/startup.cpp @@ -0,0 +1,74 @@ +#include "engine/engine.hpp" +#include "engine/api_private.hpp" + +#include "config.h" + +#ifndef BLIT_BOARD_PIMORONI_PICOVISION +[[gnu::section(".uninitialized_data.framebuffer")]] +static uint16_t screen_fb[FRAMEBUFFER_SIZE]; +#endif + +extern void init(); +extern void update(uint32_t time); +extern void render(uint32_t time); + +// override terminate handler to save ~20-30k +namespace __cxxabiv1 { + std::terminate_handler __terminate_handler = std::abort; +} + +extern "C" bool do_init() { +#ifndef IGNORE_API_VERSION + if(blit::api.version_major != blit::api_version_major) + return false; + + if(blit::api.version_minor < blit::api_version_minor) + return false; +#endif + + // preinit/init funcs (based on pico-sdk runtime.c) + // we're not calling the preinit funcs on RP2350 as they're all low-level init that should've been done by the loader + // (on RP2040 they init the ROM functions, so we do need to call them) +#ifndef PICO_RP2350 + // Start and end points of the constructor list, + // defined by the linker script. + extern void (*__preinit_array_start)(); + extern void (*__preinit_array_end)(); + // Call each function in the list. + // We have to take the address of the symbols, as __preinit_array_start *is* + // the first function pointer, not the address of it. + for (void (**p)(void) = &__preinit_array_start; p < &__preinit_array_end; ++p) { + (*p)(); + } +#endif + + // Start and end points of the constructor list, + // defined by the linker script. + extern void (*__init_array_start)(void); + extern void (*__init_array_end)(void); + + // Call each function in the list. + // We have to take the address of the symbols, as __init_array_start *is* + // the first function pointer, not the address of it. + for (void (**p)(void) = &__init_array_start; p < &__init_array_end; ++p) { + (*p)(); + } + + blit::update = update; + blit::render = render; + +#ifndef BLIT_BOARD_PIMORONI_PICOVISION + blit::api.set_framebuffer((uint8_t *)screen_fb, sizeof(screen_fb), {DISPLAY_WIDTH, DISPLAY_HEIGHT}); +#endif + + blit::set_screen_mode(blit::ScreenMode::lores); + + init(); + + return true; +} + +extern "C" void do_render(uint32_t time) { + blit::screen.data = blit::api.get_screen_data(); + render(time); +} diff --git a/32blit-pico/usb.cpp b/32blit-pico/usb.cpp new file mode 100644 index 000000000..7920eac47 --- /dev/null +++ b/32blit-pico/usb.cpp @@ -0,0 +1,340 @@ +#include +#include +#include +#include + +#include "hardware/flash.h" + +#include "usb.hpp" +#include "blit_launch.hpp" +#include "multiplayer.hpp" + +#include "engine/engine.hpp" +#include "executable.hpp" + +#define MAX_FILENAME 256+1 +#define MAX_FILELEN 16+1 + +void init(); + +static uint8_t cur_header[8]; +static int header_pos = 0; +static CDCCommand *cur_command = nullptr; + +static constexpr uint32_t to_cmd_id(const char str[4]) { + return str[0] | str[1] << 8 | str[2] << 16 | str[3] << 24; +} + +class CDCParseBuffer final { +public: + void reset() { + offset = 0; + } + + uint8_t *get_data() { + return buffer; + } + + uint8_t *get_current_ptr() { + return buffer + offset; + } + + void add_read(uint32_t count) { + offset += count; + assert(offset <= buffer_size); + } + + uint32_t get_offset() const { + return offset; + } + +private: + static const size_t buffer_size = std::max((unsigned)std::max(MAX_FILELEN, MAX_FILENAME), FLASH_PAGE_SIZE); + uint8_t buffer[buffer_size]; + + uint32_t offset = 0; +}; + +class CDCProgCommand final : public CDCCommand { +public: + CDCProgCommand(CDCParseBuffer &buf) : buf(buf) {} + + void init() override { + parse_state = ParseState::Filename; + buf.reset(); + } + + Status update() override { + + while(true) { + switch(parse_state) { + case ParseState::Filename: { + auto buf_ptr = buf.get_current_ptr(); + if(!usb_cdc_read(buf_ptr, 1)) + return Status::Continue; + + // end of string + if(*buf_ptr == 0) { + printf("PROG: file %s\n", buf.get_data()); + parse_state = ParseState::Length; + buf.reset(); + continue; + } + + buf.add_read(1); + + // too long + if(buf.get_offset() == MAX_FILENAME) + return Status::Error; + + break; + } + + case ParseState::Length: { + auto buf_ptr = buf.get_current_ptr(); + if(!usb_cdc_read(buf_ptr, 1)) + return Status::Continue; + + // end of string + if(*buf_ptr == 0) { + auto file_len = strtoul((const char *)buf.get_data(), nullptr, 10); + printf("PROG: len %lu\n", file_len); + parse_state = ParseState::Data; + buf.reset(); + + writer.init(file_len); + continue; + } + + buf.add_read(1); + + // too long + if(buf.get_offset() == MAX_FILELEN) + return Status::Error; + + break; + } + + case ParseState::Data: { + // read data + auto max = std::min(uint32_t(FLASH_PAGE_SIZE), writer.get_remaining()) - buf.get_offset(); + auto read = usb_cdc_read(buf.get_current_ptr(), max); + + if(!read) + return Status::Continue; + + buf.add_read(read); + + // got full page or final part of file + auto buf_off = buf.get_offset(); + if(buf_off == FLASH_PAGE_SIZE || buf_off == writer.get_remaining()) { + if(!writer.write(buf.get_data(), buf_off)) + return Status::Error; + + buf.reset(); + } + + // end of file + if(writer.get_remaining() == 0) { + // send response + auto block = writer.get_flash_offset() >> 16; + uint8_t res_data[]{'3', '2', 'B', 'L', '_', '_', 'O', 'K', uint8_t(block), uint8_t(block >> 8)}; + usb_cdc_write(res_data, sizeof(res_data)); + usb_cdc_flush_write(); + + // reinit loader + ::init(); + + return Status::Done; + } + + break; + } + } + } + + return Status::Continue; + } + + enum class ParseState { + Filename, + Length, + Data + } parse_state = ParseState::Filename; + + CDCParseBuffer &buf; + + BlitWriter writer; +}; + +class CDCListCommand final : public CDCCommand { + void init() override { + } + + Status update() override { + blit::api.list_installed_games([](const uint8_t *ptr, uint32_t block, uint32_t size) { + // bit of a mismatch between the API and the CDC API + // this wants offset in bytes and the size WITHOUT metadata + uint32_t offset_bytes = block * game_block_size; + uint32_t header_size = ((BlitGameHeader *)ptr)->end & 0x1FFFFFF; + + usb_cdc_write((uint8_t *)&offset_bytes, sizeof(uint32_t)); + usb_cdc_write((uint8_t *)&header_size, sizeof(uint32_t)); + usb_cdc_flush_write(); + + // write metadata if found + auto meta = ptr + header_size; + + if(memcmp(meta, "BLITMETA", 8) == 0) { + auto meta_size = *(uint16_t *)(meta + 8); + usb_cdc_write(meta, meta_size + 10); + } else { + // no meta, write header + 0 len + usb_cdc_write((const uint8_t *)"BLITMETA\0", 10); + } + usb_cdc_flush_write(); + }); + + // end marker + uint32_t end = 0xFFFFFFFF; + usb_cdc_write((uint8_t *)&end, sizeof(uint32_t)); + usb_cdc_flush_write(); + + return Status::Done; + } +}; + +class CDCLaunchCommand final : public CDCCommand { +public: + CDCLaunchCommand(CDCParseBuffer &buf) : buf(buf) {} + + void init() override { + buf.reset(); + } + + Status update() override { + while(true) { + auto buf_ptr = buf.get_current_ptr(); + if(!usb_cdc_read(buf_ptr, 1)) + return Status::Continue; + + // end of string + if(*buf_ptr == 0) { + blit::api.launch((const char *)buf.get_data()); + return Status::Done; + } + + buf.add_read(1); + + // too long + if(buf.get_offset() == MAX_FILENAME) + return Status::Error; + } + + return Status::Continue; + } + + CDCParseBuffer &buf; +}; + +class CDCEraseCommand final : public CDCCommand { +public: + CDCEraseCommand(CDCParseBuffer &buf) : buf(buf) {} + + void init() override { + buf.reset(); + } + + Status update() override { + while(true) { + auto buf_ptr = buf.get_current_ptr(); + if(!usb_cdc_read(buf_ptr, 1)) + return Status::Continue; + + buf.add_read(1); + + // end of word + if(buf.get_offset() == 4) { + blit::api.erase_game(*(uint32_t *)buf.get_data()); + return Status::Done; + } + } + + return Status::Continue; + } + + CDCParseBuffer &buf; +}; + + +static CDCHandshakeCommand handshake_command; +static CDCUserCommand user_command; + +#if defined(BUILD_LOADER) && !defined(USB_HOST) +#define FLASH_COMMANDS +static CDCParseBuffer parse_buffer; +static CDCProgCommand prog_command(parse_buffer); +static CDCListCommand list_command; +static CDCLaunchCommand launch_command(parse_buffer); +static CDCEraseCommand erase_command(parse_buffer); +#endif + +const std::tuple cdc_commands[]{ + {to_cmd_id("MLTI"), &handshake_command}, + {to_cmd_id("USER"), &user_command}, + +#ifdef FLASH_COMMANDS + {to_cmd_id("PROG"), &prog_command}, + {to_cmd_id("__LS"), &list_command}, + {to_cmd_id("LNCH"), &launch_command}, + {to_cmd_id("ERSE"), &erase_command}, +#endif +}; + +void usb_cdc_update() { + while(usb_cdc_read_available()) { + // command in progress + if(cur_command) { + auto res = cur_command->update(); + + if(res == CDCCommand::Status::Continue) { + break; + } else { + // done/error + cur_command = nullptr; + } + + continue; + } + + // match header + if(header_pos < 8) { + usb_cdc_read(cur_header + header_pos, 1); + + const char *expected = "32BL"; + if(header_pos >= 4 || cur_header[header_pos] == expected[header_pos]) + header_pos++; + else + header_pos = 0; + } else { + + // got header + auto command_id = to_cmd_id((char *)cur_header + 4); + + // find command + for(auto &cmd : cdc_commands) { + if(std::get<0>(cmd) == command_id) { + cur_command = std::get<1>(cmd); + break; + } + } + + if(!cur_command) + printf("got: %c%c%c%c%c%c%c%c\n", cur_header[0], cur_header[1], cur_header[2], cur_header[3], cur_header[4], cur_header[5], cur_header[6], cur_header[7]); + else + cur_command->init(); + + header_pos = 0; + } + } +} diff --git a/32blit-pico/usb.hpp b/32blit-pico/usb.hpp index 59e7016ef..49cecb545 100644 --- a/32blit-pico/usb.hpp +++ b/32blit-pico/usb.hpp @@ -2,6 +2,18 @@ #include +class CDCCommand { +public: + enum class Status { + Done = 0, + Continue, + Error + }; + + virtual void init() = 0; + virtual Status update() = 0; +}; + void init_usb(); void update_usb(); @@ -12,3 +24,5 @@ uint16_t usb_cdc_read(uint8_t *data, uint16_t len); uint32_t usb_cdc_read_available(); void usb_cdc_write(const uint8_t *data, uint16_t len); void usb_cdc_flush_write(); + +void usb_cdc_update(); diff --git a/32blit-pico/usb/device.cpp b/32blit-pico/usb/device.cpp index 9675351d7..e507286f9 100644 --- a/32blit-pico/usb/device.cpp +++ b/32blit-pico/usb/device.cpp @@ -97,6 +97,8 @@ void init_usb() { void update_usb() { tud_task(); + + usb_cdc_update(); } void usb_debug(const char *message) { diff --git a/32blit-pico/usb/host.cpp b/32blit-pico/usb/host.cpp index 200910c55..089408298 100644 --- a/32blit-pico/usb/host.cpp +++ b/32blit-pico/usb/host.cpp @@ -178,6 +178,8 @@ void init_usb() { void update_usb() { tuh_task(); + usb_cdc_update(); + // TODO: resend multiplayer handshake } diff --git a/32blit-sdl/System.cpp b/32blit-sdl/System.cpp index cc698c202..9525d102e 100644 --- a/32blit-sdl/System.cpp +++ b/32blit-sdl/System.cpp @@ -231,6 +231,9 @@ static const blit::APIConst blit_api_const { nullptr, // list_installed_games nullptr, // can_launch + + nullptr, // get_screen_data + nullptr, // set_framebuffer }; static blit::APIData blit_api_data; diff --git a/32blit/engine/api_private.hpp b/32blit/engine/api_private.hpp index f45db2a6b..bfd6ea2ef 100644 --- a/32blit/engine/api_private.hpp +++ b/32blit/engine/api_private.hpp @@ -146,6 +146,10 @@ namespace blit { // if launch is expected to succeed on this file // files this returns success for should be .blit files or have a registered handler (get_type_handler_metadata should return valid metadata) CanLaunchResult (*can_launch)(const char *path); + + // low level framebuffer + uint8_t *(*get_screen_data)(); // used to get current screen.data before render if firmware does page-flipping + void (*set_framebuffer)(uint8_t *data, uint32_t max_size, Size max_bounds); // pass framebuffer over if allocated on the "user" side of the API }; struct APIData { @@ -173,7 +177,7 @@ namespace blit { // raw i2c access void (*i2c_completed)(uint8_t address, uint8_t reg, const uint8_t *data, uint16_t len); // callback when done - COMPAT_PAD(uintptr_t, pad5, 5); + COMPAT_PAD(uintptr_t, pad5, 7); }; #ifdef BLIT_API_SPLIT_COMPAT diff --git a/CMakeLists.txt b/CMakeLists.txt index 62ca9dd83..635161455 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,19 +12,18 @@ else() endif() find_package (32BLIT CONFIG REQUIRED PATHS .) -add_subdirectory(utilities) add_subdirectory(launcher-shared) +add_subdirectory(utilities) + if(32BLIT_HW) add_subdirectory(32blit-stm32) add_subdirectory(firmware) add_subdirectory(firmware-update) endif() -if(NOT 32BLIT_PICO) - add_subdirectory(launcher) -endif() +add_subdirectory(launcher) # if the examples are there, build them too if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/examples/CMakeLists.txt) diff --git a/launcher-shared/executable.hpp b/launcher-shared/executable.hpp index c8b6219a3..428b57549 100644 --- a/launcher-shared/executable.hpp +++ b/launcher-shared/executable.hpp @@ -3,7 +3,7 @@ constexpr uint32_t blit_game_magic = 0x54494C42; // "BLIT" -#ifdef TARGET_32BLIT_HW +#if defined(TARGET_32BLIT_HW) || defined(PICO_BUILD) // TODO: generic "is hardware" define? using BlitRenderFunction = void(*)(uint32_t); using BlitTickFunction = int(*)(uint32_t); using BlitInitFunction = bool(*)(uint32_t); @@ -17,6 +17,7 @@ enum class BlitDevice : uint8_t { STM32H7_32BlitOld = 0, // 32blit hw, old header STM32H7_32Blit = 1, // 32blit hw RP2040 = 2, // any RP2040-based device + RP2350 = 3, }; // should match the layout in startup_user.s diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 592509a73..7f0387285 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -2,7 +2,11 @@ cmake_minimum_required(VERSION 3.9...3.26) project (launcher) find_package (32BLIT CONFIG REQUIRED PATHS ..) -blit_executable(launcher launcher.cpp theme.cpp credits.cpp) +blit_executable(launcher + PICO_BLIT_OFFSET_KB 256 + launcher.cpp theme.cpp credits.cpp +) + target_link_libraries(launcher LauncherShared) blit_assets_yaml(launcher assets.yml) blit_metadata(launcher metadata.yml) diff --git a/utilities/picosystem-hardware-test/CMakeLists.txt b/utilities/picosystem-hardware-test/CMakeLists.txt index 465ea66bd..3f2d58443 100644 --- a/utilities/picosystem-hardware-test/CMakeLists.txt +++ b/utilities/picosystem-hardware-test/CMakeLists.txt @@ -7,5 +7,5 @@ if(NOT PICO_BOARD STREQUAL "pimoroni_picosystem") return() endif() -blit_executable (picosystem-hardware-test hardware-test.cpp) +blit_executable (picosystem-hardware-test hardware-test.cpp PICO_STANDALONE_UF2) blit_metadata (picosystem-hardware-test metadata.yml)