From b20dd5e1b09622e191167cef1f5fdb400031e4ef Mon Sep 17 00:00:00 2001 From: Romain TISSERAND Date: Tue, 25 Aug 2026 02:58:20 +0200 Subject: [PATCH 1/2] Add BL616 TangCore build smoke test + reference libchdr integration Compile+link CI job against the real Xuantie/T-Head toolchain and nand2mario's real bouffalo_sdk fork/firmware-bl616 (Sipeed Tang Console 60K/Primer 25K's onboard BL616 companion MCU), not the vanilla riscv64-unknown-elf-gcc proxy rv32-ram-budget.yml uses. Pinned to fixed commits since firmware-bl616/bouffalo_sdk are repos we don't control. contrib/tangcore-bl616/ has the FatFS core_file_callbacks bridge and a firmware-overlay proving the vendored libchdr actually links (a real call site was needed - an unreferenced build links "clean" by getting dead-stripped, which looked like success but proved nothing on the first attempt). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KMYbZzB8mioFmotWGFnAXG --- .github/workflows/bl616-tangcore-build.yml | 85 +++ contrib/tangcore-bl616/README.md | 74 +++ contrib/tangcore-bl616/chd/chd_fatfs.c | 75 +++ contrib/tangcore-bl616/chd/chd_fatfs.h | 23 + .../firmware-overlay/CMakeLists.txt | 72 +++ .../tangcore-bl616/firmware-overlay/main.cpp | 608 ++++++++++++++++++ 6 files changed, 937 insertions(+) create mode 100644 .github/workflows/bl616-tangcore-build.yml create mode 100644 contrib/tangcore-bl616/README.md create mode 100644 contrib/tangcore-bl616/chd/chd_fatfs.c create mode 100644 contrib/tangcore-bl616/chd/chd_fatfs.h create mode 100644 contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt create mode 100644 contrib/tangcore-bl616/firmware-overlay/main.cpp diff --git a/.github/workflows/bl616-tangcore-build.yml b/.github/workflows/bl616-tangcore-build.yml new file mode 100644 index 0000000..0b64615 --- /dev/null +++ b/.github/workflows/bl616-tangcore-build.yml @@ -0,0 +1,85 @@ +name: BL616 TangCore build + +# Compile+link smoke test: proves libchdr keeps building against the real +# firmware running on Sipeed Tang Console 60K / Primer 25K's onboard BL616 +# companion MCU (nand2mario/tangcore, nand2mario/firmware-bl616), using the +# real Xuantie/T-Head toolchain and nand2mario's real bouffalo_sdk fork - +# not the vanilla riscv64-unknown-elf-gcc proxy used by rv32-ram-budget.yml. +# +# No BL616 hardware in CI, so this cannot prove chd_open()/chd_read() work +# at runtime - only that the real toolchain/SDK/firmware combination still +# compiles and fully links libchdr in. See contrib/tangcore-bl616/README.md. +# +# Clones fixed commits of firmware-bl616/bouffalo_sdk/toolchain, not branch +# heads - those are nand2mario's repos, not ours, so pinning keeps this job +# from going red over changes we didn't make. Bump the SHAs in this file and +# in contrib/tangcore-bl616/README.md together when picking up upstream +# changes is wanted. + +on: [push, pull_request] + +env: + TOOLCHAIN_SHA: c4afe91cbd01bf7dce525e0d23b4219c8691e8f0 + BOUFFALO_SDK_SHA: 7f44f9ea6b4ccf96db8c5236c8024b68e2a76df7 + FIRMWARE_BL616_SHA: a5a6ea1cf7c81f32c1c3f0f91ea9d913be5ba078 + +jobs: + bl616-tangcore-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - name: Cache toolchain/SDK/firmware clones + id: cache-tangcore + uses: actions/cache@v4 + with: + path: tangcore-work + key: tangcore-work-${{ env.TOOLCHAIN_SHA }}-${{ env.BOUFFALO_SDK_SHA }}-${{ env.FIRMWARE_BL616_SHA }} + + - name: Clone toolchain, SDK, firmware (pinned commits) + if: steps.cache-tangcore.outputs.cache-hit != 'true' + run: | + set -euo pipefail + mkdir -p tangcore-work + cd tangcore-work + + git init -q toolchain_gcc_t-head_linux + git -C toolchain_gcc_t-head_linux remote add origin https://github.com/bouffalolab/toolchain_gcc_t-head_linux.git + git -C toolchain_gcc_t-head_linux fetch --depth 1 origin "$TOOLCHAIN_SHA" + git -C toolchain_gcc_t-head_linux checkout -q FETCH_HEAD + + git init -q bouffalo_sdk + git -C bouffalo_sdk remote add origin https://github.com/nand2mario/bouffalo_sdk.git + git -C bouffalo_sdk fetch --depth 1 origin "$BOUFFALO_SDK_SHA" + git -C bouffalo_sdk checkout -q FETCH_HEAD + git -C bouffalo_sdk submodule update --init --recursive --depth 1 + + git init -q firmware-bl616 + git -C firmware-bl616 remote add origin https://github.com/nand2mario/firmware-bl616.git + git -C firmware-bl616 fetch --depth 1 origin "$FIRMWARE_BL616_SHA" + git -C firmware-bl616 checkout -q FETCH_HEAD + + - name: Vendor libchdr into firmware-bl616 + run: | + set -euo pipefail + F=tangcore-work/firmware-bl616 + + mkdir -p "$F/thirdparty/libchdr" + cp -r include src "$F/thirdparty/libchdr/" + mkdir -p "$F/thirdparty/libchdr/deps" + cp -r deps/lzma-26.02 deps/miniz-3.1.2 deps/zstd-1.5.7 "$F/thirdparty/libchdr/deps/" + + mkdir -p "$F/chd" + cp contrib/tangcore-bl616/chd/chd_fatfs.h contrib/tangcore-bl616/chd/chd_fatfs.c "$F/chd/" + cp contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt "$F/CMakeLists.txt" + cp contrib/tangcore-bl616/firmware-overlay/main.cpp "$F/main.cpp" + + - name: Build (TANG_BOARD=console60k) + run: | + export PATH="${{ github.workspace }}/tangcore-work/toolchain_gcc_t-head_linux/bin:$PATH" + riscv64-unknown-elf-gcc --version + cd tangcore-work/firmware-bl616 + make 2>&1 | tee build.log + echo "---- memory region summary ----" + grep -A 7 "Memory region" build.log diff --git a/contrib/tangcore-bl616/README.md b/contrib/tangcore-bl616/README.md new file mode 100644 index 0000000..8b89099 --- /dev/null +++ b/contrib/tangcore-bl616/README.md @@ -0,0 +1,74 @@ +# TangCore / BL616 integration (nand2mario) + +Reference integration proving libchdr builds and links against the real +firmware running on Sipeed Tang Console 60K / Primer 25K's onboard BL616 +companion MCU ([nand2mario/tangcore](https://github.com/nand2mario/tangcore), +[nand2mario/firmware-bl616](https://github.com/nand2mario/firmware-bl616)). + +This is a **compile+link smoke test only** - there is no BL616 hardware in +CI, so it cannot prove `chd_open()`/`chd_read()` work at runtime. What it +does prove: libchdr keeps building cleanly against the real Xuantie/T-Head +toolchain and nand2mario's real bouffalo_sdk fork, on every libchdr change. +See `.github/workflows/bl616-tangcore-build.yml`. + +## Contents + +- `chd/chd_fatfs.{h,c}` - a `core_file_callbacks` implementation backed by + FatFS (`f_open`/`f_read`/`f_lseek`/`f_close`), the bridge libchdr needs to + open a CHD from an SD card or USB drive under this firmware. +- `firmware-overlay/{CMakeLists.txt,main.cpp}` - full copies of + `firmware-bl616`'s own files, modified to vendor libchdr in and add a + `chd_link_probe()` call. The probe is deliberately not a no-op: an + unreferenced library builds and links "clean" by silently getting + dead-stripped, which proved nothing the first time this was tried. The + probe calls `chd_fatfs_open()` on a path that doesn't exist (safe - no SD + is mounted yet at that point in `main()`), forcing the linker to fully + resolve libchdr against this toolchain's libc. + +## Pinned versions + +CI clones fixed commits, not branch heads - `firmware-bl616` and +`bouffalo_sdk` are nand2mario's own repos we don't control, so pinning keeps +libchdr's CI from going red over changes we didn't make. Bump these +manually when picking up upstream changes is actually wanted. + +| repo | commit | +|---|---| +| [bouffalolab/toolchain_gcc_t-head_linux](https://github.com/bouffalolab/toolchain_gcc_t-head_linux) | `c4afe91cbd01bf7dce525e0d23b4219c8691e8f0` | +| [nand2mario/bouffalo_sdk](https://github.com/nand2mario/bouffalo_sdk) | `7f44f9ea6b4ccf96db8c5236c8024b68e2a76df7` | +| [nand2mario/firmware-bl616](https://github.com/nand2mario/firmware-bl616) | `a5a6ea1cf7c81f32c1c3f0f91ea9d913be5ba078` | + +## Reproducing locally + +```sh +git clone https://github.com/bouffalolab/toolchain_gcc_t-head_linux.git +git clone --recurse-submodules https://github.com/nand2mario/bouffalo_sdk.git +git clone https://github.com/nand2mario/firmware-bl616.git +# check out the pinned commits above in each, then: + +export PATH="$PWD/toolchain_gcc_t-head_linux/bin:$PATH" + +mkdir -p firmware-bl616/thirdparty/libchdr +cp -r /path/to/libchdr/include /path/to/libchdr/src firmware-bl616/thirdparty/libchdr/ +mkdir -p firmware-bl616/thirdparty/libchdr/deps +cp -r /path/to/libchdr/deps/lzma-26.02 /path/to/libchdr/deps/miniz-3.1.2 /path/to/libchdr/deps/zstd-1.5.7 \ + firmware-bl616/thirdparty/libchdr/deps/ + +cp /path/to/libchdr/contrib/tangcore-bl616/chd/*.{h,c} firmware-bl616/chd/ +cp /path/to/libchdr/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt firmware-bl616/CMakeLists.txt +cp /path/to/libchdr/contrib/tangcore-bl616/firmware-overlay/main.cpp firmware-bl616/main.cpp + +cd firmware-bl616 +make # BL_SDK_BASE defaults to ../bouffalo_sdk, TANG_BOARD defaults to console60k +``` + +## Status (2026-08-25) + +Compiles and links clean, `LOWRAM_TARGET=1`. Real flash cost: +142.5KB (whole +codec suite) out of a 4MB budget. Static SRAM cost of linking libchdr in is +negligible (~80B) - the real dynamic heap cost (~250KB-class, per +`project_avhuff_wip` memory) only shows up once `chd_open()` actually +succeeds, which needs a mounted filesystem this smoke test doesn't have. +Not yet wired into an actual TangCore core loader - no CD-capable core +exists in nand2mario's ecosystem yet (`mdtang`/Genesis and `pctang`/PC-XT +have no CD-ROM support), that's being built separately. diff --git a/contrib/tangcore-bl616/chd/chd_fatfs.c b/contrib/tangcore-bl616/chd/chd_fatfs.c new file mode 100644 index 0000000..558df7b --- /dev/null +++ b/contrib/tangcore-bl616/chd/chd_fatfs.c @@ -0,0 +1,75 @@ +#include "chd_fatfs.h" + +#include /* SEEK_SET / SEEK_CUR / SEEK_END */ + +static uint64_t chd_fatfs_fsize(void *argp) +{ + FIL *fil = (FIL *)argp; + return (uint64_t)f_size(fil); +} + +static size_t chd_fatfs_fread(void *ptr, size_t size, size_t nmemb, void *argp) +{ + FIL *fil = (FIL *)argp; + UINT br = 0; + UINT btr = (UINT)(size * nmemb); + + if (btr == 0) + return 0; + + if (f_read(fil, ptr, btr, &br) != FR_OK) + return 0; + + return (size_t)(br / size); +} + +static int chd_fatfs_fclose(void *argp) +{ + FIL *fil = (FIL *)argp; + return (f_close(fil) == FR_OK) ? 0 : -1; +} + +static int chd_fatfs_fseek(void *argp, int64_t offset, int whence) +{ + FIL *fil = (FIL *)argp; + FSIZE_t abs_offset; + + switch (whence) { + case SEEK_SET: + if (offset < 0) + return -1; + abs_offset = (FSIZE_t)offset; + break; + case SEEK_CUR: + abs_offset = (FSIZE_t)((int64_t)f_tell(fil) + offset); + break; + case SEEK_END: + abs_offset = (FSIZE_t)((int64_t)f_size(fil) + offset); + break; + default: + return -1; + } + + return (f_lseek(fil, abs_offset) == FR_OK) ? 0 : -1; +} + +const core_file_callbacks chd_fatfs_callbacks = { + .fsize = chd_fatfs_fsize, + .fread = chd_fatfs_fread, + .fclose = chd_fatfs_fclose, + .fseek = chd_fatfs_fseek, +}; + +chd_error chd_fatfs_open(const char *path, FIL *fil, chd_file **chd) +{ + chd_error err; + + if (f_open(fil, path, FA_READ) != FR_OK) + return CHDERR_FILE_NOT_FOUND; + + err = chd_open_core_file_callbacks(&chd_fatfs_callbacks, fil, CHD_OPEN_READ, NULL, chd); + if (err != CHDERR_NONE) + f_close(fil); + + return err; +} diff --git a/contrib/tangcore-bl616/chd/chd_fatfs.h b/contrib/tangcore-bl616/chd/chd_fatfs.h new file mode 100644 index 0000000..bdbb313 --- /dev/null +++ b/contrib/tangcore-bl616/chd/chd_fatfs.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ff.h" +#include "libchdr/chd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Bridges libchdr's core_file_callbacks onto FatFS. One FIL per open CHD - + * caller owns the FIL's storage (e.g. embed in the same struct as the chd_file*) + * since libchdr only ever sees it through the opaque argp pointer. */ + +extern const core_file_callbacks chd_fatfs_callbacks; + +/* Opens path via f_open(FA_READ) into *fil, then hands it to libchdr as the + * argp for chd_fatfs_callbacks. On any failure, *fil is closed if it was + * opened and *chd is left untouched. */ +chd_error chd_fatfs_open(const char *path, FIL *fil, chd_file **chd); + +#ifdef __cplusplus +} +#endif diff --git a/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt b/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt new file mode 100644 index 0000000..b748fb3 --- /dev/null +++ b/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.15) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CONFIG_FREERTOS 1) +set(CONFIG_CHERRYUSB 1) +set(CONFIG_CHERRYUSB_HOST 1) +set(CONFIG_BSP_SDH_SDCARD 1) +set(CONFIG_FATFS 1) +set(CONFIG_FATFS_USBH 1) +set(CONFIG_FATFS_SDH_SDCARD 1) + +if(TANG_BOARD STREQUAL "mega60k") + add_definitions(-DTANG_MEGA60K) +elseif(TANG_BOARD STREQUAL "mega138k") + add_definitions(-DTANG_MEGA138K) +elseif(TANG_BOARD STREQUAL "console60k") + add_definitions(-DTANG_CONSOLE60K) +elseif(TANG_BOARD STREQUAL "console138k") + add_definitions(-DTANG_CONSOLE138K) +elseif(TANG_BOARD STREQUAL "primer25k") + add_definitions(-DTANG_PRIMER25K) +elseif(TANG_BOARD STREQUAL "nano20k") + add_definitions(-DTANG_NANO20K) +endif() + +# libchdr: LOWRAM_TARGET on (real ~365KB free SRAM budget on this board, see +# project memory - whole-hunk decode does not fit, streaming-decode design does) +add_definitions( + -DLOWRAM_TARGET=1 + -DWANT_RAW_DATA_SECTOR=1 + -DWANT_SUBCODE=1 + -DVERIFY_BLOCK_CRC=1 + -DMINIZ_NO_ARCHIVE_APIS + -DMINIZ_NO_DEFLATE_APIS + -DMINIZ_NO_STDIO + -DMINIZ_NO_TIME +) + +find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE}) + +# Add include directories +sdk_add_include_directories( + . usb fpga ui core utils chd + thirdparty/libchdr/include + thirdparty/libchdr/src +) + +# Add source files +file(GLOB_RECURSE MY_SOURCES + "usb/*.cpp" + "fpga/*.cpp" + "ui/*.cpp" + "core/*.cpp" + "utils/*.cpp" +) + +file(GLOB LIBCHDR_SOURCES + "chd/*.c" + "thirdparty/libchdr/src/*.c" + "thirdparty/libchdr/deps/lzma-26.02/src/LzmaDec.c" + "thirdparty/libchdr/deps/miniz-3.1.2/miniz.c" + "thirdparty/libchdr/deps/zstd-1.5.7/zstddeclib.c" +) + +target_sources(app PRIVATE ${MY_SOURCES} ${LIBCHDR_SOURCES}) +sdk_set_main_file(main.cpp) + +project(tangcore CXX) + diff --git a/contrib/tangcore-bl616/firmware-overlay/main.cpp b/contrib/tangcore-bl616/firmware-overlay/main.cpp new file mode 100644 index 0000000..c24c645 --- /dev/null +++ b/contrib/tangcore-bl616/firmware-overlay/main.cpp @@ -0,0 +1,608 @@ +/* + * TangCore firmware for BL616 MCU + * + * (c) 2025, nand2mario + * + * This source code is licensed under the Apache 2.0 license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#include +#include +#include + +extern "C" { +#include "board.h" +#include "bl616_glb.h" +#include "bflb_gpio.h" +#include "bflb_uart.h" +#include "bflb_clock.h" +#include "bl616_clock.h" + +#include "usbh_core.h" +#include "ff.h" +#include "fatfs_diskio_register.h" +} + +#include "file_chooser.h" +#include "programmer.h" +#include "usb_gamepad.h" +#include "utils.h" +#include "cores.h" +#include "overlay.h" +#include "chd_fatfs.h" +#include "init.h" +#include "menu_manager.h" + +// Uncomment this to enable UART console (use with caution. it may interfere with MCU-FPGA communication) +#define UART_CONSOLE + +///////////////////////////////////////////////////////////////////////////////// +// Global state + +int option_osd_key = OPTION_OSD_KEY_SELECT_RIGHT; +int16_t active_core = -1; // firmware detected this core as active +bool core_running; // a rom is loaded and running on the core +struct core_info *core; + +// UART +struct bflb_device_s *gpio_dev; +struct bflb_device_s *uart0_dev; +struct bflb_device_s *uart1_dev; + +// USB and fatfs +struct usbh_msc *msc; +const char *drv = "sd:"; + +// Tasks and shared state +TaskHandle_t main_task_handle; +TaskHandle_t uart1_rx_task_handle; + +#ifdef TANG_CONSOLE60K +const char *BOARD_NAME = "console60k"; +#elif defined(TANG_CONSOLE138K) +const char *BOARD_NAME = "console138k"; +#elif defined(TANG_MEGA60K) +const char *BOARD_NAME = "mega60k"; +#elif defined(TANG_MEGA138K) +const char *BOARD_NAME = "mega138k"; +#elif defined(TANG_PRIMER25K) +const char *BOARD_NAME = "primer25k"; +#elif defined(TANG_NANO20K) +const char *BOARD_NAME = "nano20k"; +#else +const char *BOARD_NAME = "unknown"; +#endif + +// Override system printf() to send to FPGA +int __attribute__((weak)) putchar(int ch) { + fpga_tx_header(0x05, 2); + fpga_tx_byte(ch); + return ch; +} + + +///////////////////////////////////////////////////////////////////////////////// +// Core loading and other file system operations + +// nand2mario: these USB data structures cannot be CACHED as they are written to by hardware +USB_NOCACHE_RAM_SECTION FATFS fs; +USB_NOCACHE_RAM_SECTION FIL fcore; +USB_NOCACHE_RAM_SECTION BYTE __attribute__((aligned(64))) fbuf[BLOCK_SIZE]; + +FRESULT res_sd; +FileChooser file_chooser; + +// #define PAGESIZE 22 +// #define TOPLINE 2 +// #define PWD_SIZE 1024 +// char pwd[PWD_SIZE]; +// one page of file names to display +// char file_names[PAGESIZE][256]; +// int file_dir[PAGESIZE]; // this file is a directory +// int file_sizes[PAGESIZE]; +// int file_len; // number of files on this page + +///////////////////////////////////////////////////////////////////////////////// +// Menu display and user interaction + +// Menus for "NES", "SNES" ... entries +// dir: initial dir including the drive name (e.g. "sd:nes", "usb:cores") +// return 0: user chose a ROM (*choice), 1: no choice made, -1: error +// file chosen: pwd / file_name[*choice] +static int menu_loadrom(const char *dir) { + string fname; + file_chooser.rootdir = dir; + file_chooser.curdir = dir; + file_chooser.msg_return = "<< Return to main menu"; + bool r = file_chooser.choose_file(fname); + if (!r) { + overlay_status("No file chosen"); + return 1; + } + + // now proceed to load the core and ROM + joy1_state = 0; joy2_state = 0; // clear joypad states + + // load core if in cores/ dir + if (fname.find(string(drv) + "cores") == 0) { + overlay_status("Core: %s", fname.c_str()); + fpga_program(fname.c_str()); + _overlay_on = 1; // turn on overlay after core is loaded + return 0; // return to main menu + } + + // find core info entry + core_info *core = NULL; + string path = fname.substr(fname.find(":")+1); + for (int i = 0; i < core_info_list.size(); i++) { + core_info *c = &core_info_list[i]; + if (path.find(c->rom_dir) == 0) { + overlay_status("ROM for: %s", c->display_name); + core = c; + break; + } + } + if (core == NULL) { + overlay_status("Core not found: %s", path.c_str()); + return -1; + } + + // user chose a ROM file + active_core = get_core_id(); + + // load core if needed + if (core != NULL) { + if (active_core != core->id) { // active core is not what we need + string fname_core; + if (find_core_for_board(fname_core, core->core_file)) { + // load core + fpga_program(fname_core.c_str()); + _overlay_on = 1; + + // allow 2 seconds for core to start + uint64_t start = bflb_mtimer_get_time_ms(); + while (bflb_mtimer_get_time_ms() - start < 2000) { + send_blank_packet(); + active_core = get_core_id(); + if (active_core == core->id) + break; + } + } + } + + // Attemp to load ROM + if (active_core == core->id) { + overlay_status("Loading ROM: %s\n", fname.c_str()); + core->load_rom(fname.c_str()); + return 1; + } else { + overlay_status("Core failed to load\n"); + delay(1000); + return -1; + } + } + return -1; +} + +static void menu_options(void) { + // to be implemented +} + +// keep sending HID state to core until OSD is turned on +static void send_hid_to_core(void) { + uint16_t hid1_old = 0, hid2_old = 0; + bool first = true; + dprint("Start sending HID to core..."); + while (1) { + uint16_t joy1=0, joy2=0, hid1=0, hid2=0; + get_joypad_states(&joy1, &joy2, &hid1, &hid2); + if (first || hid1 != hid1_old || hid2 != hid2_old) { // send HID if changed + fpga_tx_header(0x09, 5); + fpga_tx_byte(hid1 >> 8); + fpga_tx_byte(hid1 & 0xff); + fpga_tx_byte(hid2 >> 8); + fpga_tx_byte(hid2 & 0xff); + hid1_old = hid1; + hid2_old = hid2; + first = false; + } + if (joy1 == OSD_KEY_CODE || joy2 == OSD_KEY_CODE || hid1 == OSD_KEY_CODE || hid2 == OSD_KEY_CODE) { + break; + } + if (overlay_on()) // turned off by keyboard + break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + dprint("Stopped sending HID to core."); +} + +// // (R L X A RT LT DN UP START SELECT Y B) +// Return: 1 button B pressed, 4: button A pressed, 2: next page, 3: previous page +// active is the entry chosen +int joy_choice(int start_line, int len, int *active, int overlay_key_code) { + if (*active < 0 || *active >= len) + *active = 0; + uint16_t joy1=0, joy2=0, hid1=0, hid2=0; + int last = *active; + + get_joypad_states(&joy1, &joy2, &hid1, &hid2); + joy1 |= hid1; + joy2 |= hid2; + + if ((joy1 == overlay_key_code) || (joy2 == overlay_key_code)) { + overlay_status("OSD: %s", overlay_on() ? "ON" : "OFF"); + overlay(!overlay_on()); // toggle OSD + delay(300); + } + + if (!overlay_on()) { // keep sending HID state to core when OSD is off + send_hid_to_core(); + return 0; + } + + if ((joy1 & 0x10) || (joy2 & 0x10)) { + if (*active > 0) (*active)--; + } + if ((joy1 & 0x20) || (joy2 & 0x20)) { + if (*active < len-1) (*active)++; + } + if ((joy1 & 0x40) || (joy2 & 0x40)) + return 3; // previous page + if ((joy1 & 0x80) || (joy2 & 0x80)) + return 2; // next page + if ((joy1 & 0x100) || (joy2 & 0x100)) + return 4; // button A pressed + if ((joy1 & 0x1) || (joy2 & 0x1)) + return 1; // button B pressed + + overlay_cursor(0, start_line + (*active)); + overlay_printf(">"); + + // overlay_cursor(0, 27); + // overlay_printf(" j1=%04x j2=%04x h1=%04x h2=%04x", joy1, joy2, hid1, hid2); + if (last != *active) { + overlay_cursor(0, start_line + last); + overlay_printf(" "); + delay(100); // button debounce + } + return 0; +} + +#define MAIN_TASK_STACK_SIZE 2048 +#define MAIN_TASK_PRIORITY 3 +#define UART1_RX_TASK_STACK_SIZE 512 +#define UART1_RX_TASK_PRIORITY 3 + +// Receive joypad updates and other UART responses from the FPGA +static void uart1_rx_task(void *pvParameters) +{ + uint8_t buffer[5]; + uint8_t pos = 0; + uint8_t type = 0; + uint16_t len = 0; + + while (1) { + if (bflb_uart_rxavailable(uart1_dev)) { + uint8_t ch = bflb_uart_getchar(uart1_dev); + + if (pos == 0) { // expecting 0xAA + if (ch == 0xAA) + pos++; + } else if (pos == 1) { // len msb + len = (uint16_t)ch << 8; + pos++; + } else if (pos == 2) { // len lsb + len += ch; + pos++; + } else if (pos == 3) { // command type + type = ch; + pos++; + + ////// pos >= 4 ////// + } else if (type == 1) { // response to command 1 (get core ID) + if (xSemaphoreTake(state_mutex, portMAX_DELAY) == pdTRUE) { + core_id = ch; + xSemaphoreGive(state_mutex); + } + pos = 0; + } else if (type == 2) { // config string + // skip for now + if (pos == len+2) + pos = 0; + else + pos++; + } else if (type == 3) { // periodic joypad state + buffer[pos-4] = ch; + // Complete packet received + if (pos == 7) { + // Combine bytes into 16-bit values + uint16_t joy1 = (buffer[0] << 8) | buffer[1]; + uint16_t joy2 = (buffer[2] << 8) | buffer[3]; + + // Update global state with mutex protection + if (xSemaphoreTake(state_mutex, portMAX_DELAY) == pdTRUE) { + joy1_state = joy1; + joy2_state = joy2; + xSemaphoreGive(state_mutex); + } + pos = 0; // Reset for next packet + } else + pos++; + } else if (type == 4) { // floppy write + if (pos < 6) + buffer[pos-4] = ch; + else + fbuf[pos-6] = ch; + if (pos == 6+511) { + uint16_t drive = buffer[0] >> 7; + uint16_t sector = (buffer[0] & 0x7f) << 8 | buffer[1]; + if (floppy[drive]) { + UINT br; + f_lseek(&f_floppy[drive], sector * 512); + if (f_write(&f_floppy[drive], fbuf, 512, &br) != FR_OK) { + overlay_status("Failed to write floppy"); + } + } + pos = 0; // reset for next packet + } else + pos++; + } else if (type == 5) { // floppy read + buffer[pos-4] = ch; + if (pos == 5) { + uint16_t drive = buffer[0] >> 7; + uint16_t sector = (buffer[0] & 0x7f) << 8 | buffer[1]; + if (floppy[drive]) { + UINT br; + f_lseek(&f_floppy[drive], sector * 512); + if (f_read(&f_floppy[drive], fbuf, 512, &br) == FR_OK) { + fpga_tx_header(0x0a, br+1); + for (UINT i = 0; i < br; i++) { + fpga_tx_byte(fbuf[i]); + } + } else { + overlay_status("Failed to read floppy"); + } + } + pos = 0; // reset for next packet + } else + pos++; + + } else { + pos = 0; // Reset if we get out of sync + } + } + + vTaskDelay(pdMS_TO_TICKS(1)); + } +} + +// Display main menu and call other menu functions +static void main_task(void *pvParameters) +{ + uint32_t last_redraw_time = 0; + // volatile uint32_t *reg_gpio0 = (volatile uint32_t *)0x200008c4; // bl616 reference 4.8.5 + // volatile uint32_t *reg_gpio1 = (volatile uint32_t *)0x200008c8; + // volatile uint32_t *reg_gpio2 = (volatile uint32_t *)0x200008cc; + // volatile uint32_t *reg_gpio3 = (volatile uint32_t *)0x200008d0; + + // wait for drive to be ready + uint64_t start = bflb_mtimer_get_time_ms(); + FRESULT res; + overlay_status("Mounting sd card...", drv); + while ((res = f_mount(&fs, "sd:", 1)) != FR_OK && bflb_mtimer_get_time_ms() - start < 500) + delay(100); + + if (res == FR_OK) { + overlay_status("SD card mounted in %d ms", bflb_mtimer_get_time_ms() - start); + } else { + overlay_status("SD not found. Mounting USB..."); + drv = "usb:"; + start = bflb_mtimer_get_time_ms(); + while ((res = f_mount(&fs, "usb:", 1)) != FR_OK && bflb_mtimer_get_time_ms() - start < 2000) + delay(100); + if (res != FR_OK) { + overlay_status("Failed to mount USB drive"); + } else { + overlay_status("USB drive mounted in %d ms", bflb_mtimer_get_time_ms() - start); + } + } + + // load monitor core at startup + string fname; + if (find_core_for_board(fname, "monitor.bin")) { + fpga_program(fname.c_str()); + } else { + overlay_status("No monitor.bin found for board."); + } + + int line_start; + int menu_cnt = main_menu_config.size(); + line_start = 13 - (menu_cnt+2+2) / 2; // 2 lines for version, 2 lines for "TangCore" + + while (1) { + bool redraw = true; + int choice = 0; + for (;;) { + uint32_t now = bflb_mtimer_get_time_ms(); + if (active_core == -1) { + // send_blank_packet(); + active_core = get_core_id(); // 200ms timeout + overlay_status("core_id=%d", active_core); + if (active_core >= 0) redraw = true; // redraw immediately if core is detected + } + // if (core < 0) continue; // do not draw or process input if core is not ready + if (now - last_redraw_time > 5000) + redraw = true; + if (redraw) { + active_core = get_core_id(); // allow jtag to change core underneath us + overlay(overlay_on()); // set correct overlay state + overlay_clear(); + + int line = line_start; + overlay_cursor(0, line++); + // 01234567890123456789012345678901 + overlay_printf(" -== TangCore ==-"); + line++; + + // display all menu items + for (int i = 0; i < menu_cnt; i++) { + overlay_cursor(2, line++); + if (main_menu_config[i] > 0) { + for (int j = 0; core_info_list[j].id != 0; j++) { + if (core_info_list[j].id == main_menu_config[i]) { + overlay_printf("%s", core_info_list[j].display_name); + break; + } + } + } else if (main_menu_config[i] == -1) { + overlay_printf("Cores"); + } else if (main_menu_config[i] == -2) { + overlay_printf("Options"); + } + } + + line++; + overlay_cursor(2, line++); + overlay_printf("Version: "); + overlay_printf(__DATE__); + last_redraw_time = now; + redraw = false; + + // print some debug stats to UART + // uint16_t joy1=0, joy2=0; + // get_joypad_states(&joy1, &joy2); + // overlay_status("core=%d, j1=%04x, j2=%04x", active_core, joy1, joy2); + // overlay_status("Mtimer frequency: %d MHz", bflb_mtimer_get_freq() / 1000000); + // overlay_status("CPU frequency: %d MHz", bflb_clk_get_system_clock(BL_SYSTEM_CLOCK_MCU_CLK) / 1000000); + // overlay_status("GPIO0-3 status: %08x %08x %08x %08x", *reg_gpio0, *reg_gpio1, *reg_gpio2, *reg_gpio3); + } + + bool before_overlay = overlay_on(); + int r = joy_choice(line_start+2, menu_cnt, &choice, OSD_KEY_CODE); + if (r == 1) break; + + if (!before_overlay && overlay_on() && active_core > 0) { + // overlay is turned back on, now display the pop-up menu + DEBUG("Displaying pop-up menu\n"); + menu_clear(); + // Menu *menu = core->create_menu(core->rom_dir) + core_info *core = find_core_by_id(active_core); + if (core != NULL) { + DEBUG("Found core_info. Displaying menu\n"); + Menu *menu; + if (active_core == 6) { + menu = create_pcxt_menu(std::string(drv).append(core->rom_dir).c_str()); + } else { + menu = create_default_menu(std::string(drv).append(core->rom_dir).c_str()); + } + std::unique_ptr menu_ptr(menu); + push_menu(std::move(menu_ptr)); + menu->do_redraw(); + menu_input_loop(); + redraw = true; + } + } + + delay(20); + } + + if (main_menu_config[choice] > 0) { + // Load rom or core from USB drive + struct core_info *core = NULL; + for (int i = 0; core_info_list[i].id != 0; i++) { + if (core_info_list[i].id == main_menu_config[choice]) { + core = &core_info_list[i]; + break; + } + } + if (core) { + std::string dir = std::string(drv).append(core->rom_dir); + menu_loadrom(dir.c_str()); + } + } else if (main_menu_config[choice] == -1) { + // load cores manually + std::string dir = std::string(drv).append("cores"); + menu_loadrom(dir.c_str()); + } else if (main_menu_config[choice] == -2) { + // Options + menu_options(); + } + + delay(300); + } +} + +static void print_system_info(void) { + // this is viewable with scripts/liveuart.py + overlay_status("TangCore %s", __DATE__); + overlay_status("TangBoard: %s", BOARD_NAME); + overlay_status("System clock: %u MHz", bflb_clk_get_system_clock(BL_SYSTEM_CLOCK_MCU_CLK) / 1000000); + // UART registers + // Clock comes from XCLK/160M/BCLK and goes through a divider and becomes UART_CLK + overlay_status("GLB_UART_CFG0: %08x", BL_RD_WORD(0x20000150)); + overlay_status("GLB_UART_CFG1: %08x", BL_RD_WORD(0x20000154)); + overlay_status("GLB_UART_CFG2: %08x", BL_RD_WORD(0x20000158)); + overlay_status("UART0 clock: %u", Clock_Peripheral_Clock_Get(BL_PERIPHERAL_CLOCK_UART0)); + overlay_status("UART1 clock: %u", Clock_Peripheral_Clock_Get(BL_PERIPHERAL_CLOCK_UART1)); + + // 10.3.5: baudrate = UART_clk / (uart_prd + 1) + // This causes memory exception. + // overlay_status("UART_BIT_PRD: %08x", BL_RD_WORD(0x40010008)); + // 01234567890123456789012345678901 + // overlay_status(" "); +} + +// Initialize things, then start main_task and uart1_rx_task to do actual work +// TEMPORARY link-verification probe: forces the linker to fully pull in and +// resolve libchdr against this toolchain/libc (LOWRAM_TARGET codec paths +// included). Not a real feature - no SD card is mounted yet at this point in +// boot, so this always fails to open and returns cleanly. Remove once real +// CHD loading is wired into a core loader. +static void chd_link_probe(void) +{ + FIL fil; + chd_file *chd = NULL; + chd_error err = chd_fatfs_open("sd:/__chd_link_probe__.chd", &fil, &chd); + DEBUG("chd_link_probe: chd_fatfs_open -> %d\n", (int)err); + if (chd) + chd_close(chd); +} + +int main(void) +{ + /* Board init */ + board_init(); + init_core_list(); + + // Initialize GPIO and UART + init_gpio_and_uart(); + + print_system_info(); + + chd_link_probe(); + + // Create mutex for joypad states + state_mutex = xSemaphoreCreateMutex(); + + overlay_status("Initializing SDH..."); + fatfs_sdh_driver_register(); // calls SDH_Init() + // f_mount(&fs_sd, "sd:", 0); // registers SDMMC drive + + // Initializing USB host... + overlay_status("Initializing USB host..."); + usbh_initialize(); + fatfs_usbh_driver_register(); + usb_gamepad_init(); + + overlay_status("Creating tasks..."); + // Create the tasks + xTaskCreate(main_task, "main_task", MAIN_TASK_STACK_SIZE, NULL, MAIN_TASK_PRIORITY, &main_task_handle); + xTaskCreate(uart1_rx_task, "uart1_rx_task", UART1_RX_TASK_STACK_SIZE, NULL, UART1_RX_TASK_PRIORITY, &uart1_rx_task_handle); + + vTaskStartScheduler(); + + while (1) { + } +} From 1a4cb545bd3759970c3624e2ea90901da8b910a8 Mon Sep 17 00:00:00 2001 From: Romain TISSERAND Date: Tue, 25 Aug 2026 16:36:28 +0200 Subject: [PATCH 2/2] Fix licensing: patch instead of vendoring nand2mario's Apache-2.0 files firmware-bl616/bouffalo_sdk are nand2mario's Apache-2.0 repos, not ours. The previous commit stored full modified copies of his CMakeLists.txt and main.cpp under contrib/ - that's redistributing his copyrighted files wholesale, not the minimal way to express "libchdr adds these lines". Replace with a 92-line unified diff (contrib/tangcore-bl616/patches/), applied via git apply against a freshly-cloned pinned commit in CI - the standard way to distribute a modification without redistributing the whole file. Verified it applies cleanly and reproduces byte-identical files. Fixed a caching bug this surfaced: firmware-bl616 must never be cached post-patch (would either replay the patch onto an already-patched tree, or freeze a stale libchdr copy into the cache instead of testing the current checkout) - only the two untouched clones (toolchain, SDK) are cached now, firmware-bl616 is always cloned fresh. Re-verified the full corrected pipeline end-to-end, including a second run to confirm idempotency, and the resulting build is unchanged. chd_fatfs.{h,c} are unaffected - 100% original code, no third-party content, now carrying proper BSD-3-Clause headers matching the rest of libchdr. contrib/tangcore-bl616/README.md documents the licensing situation explicitly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KMYbZzB8mioFmotWGFnAXG --- .github/workflows/bl616-tangcore-build.yml | 26 +- contrib/tangcore-bl616/README.md | 63 +- contrib/tangcore-bl616/chd/chd_fatfs.c | 8 + contrib/tangcore-bl616/chd/chd_fatfs.h | 11 + .../firmware-overlay/CMakeLists.txt | 72 --- .../tangcore-bl616/firmware-overlay/main.cpp | 608 ------------------ .../firmware-bl616-libchdr-integration.patch | 92 +++ 7 files changed, 176 insertions(+), 704 deletions(-) delete mode 100644 contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt delete mode 100644 contrib/tangcore-bl616/firmware-overlay/main.cpp create mode 100644 contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch diff --git a/.github/workflows/bl616-tangcore-build.yml b/.github/workflows/bl616-tangcore-build.yml index 0b64615..81ed34a 100644 --- a/.github/workflows/bl616-tangcore-build.yml +++ b/.github/workflows/bl616-tangcore-build.yml @@ -30,14 +30,21 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Cache toolchain/SDK/firmware clones + # Only the two big, unmodified-by-us clones are cached. firmware-bl616 + # itself is tiny (~260KB) and gets patched+vendored in place every run + # - caching it would either replay the patch onto an already-patched + # tree (fails) or freeze a stale libchdr copy into the cache instead of + # testing the current checkout, so it's always cloned fresh. + - name: Cache toolchain/SDK clones id: cache-tangcore uses: actions/cache@v4 with: - path: tangcore-work - key: tangcore-work-${{ env.TOOLCHAIN_SHA }}-${{ env.BOUFFALO_SDK_SHA }}-${{ env.FIRMWARE_BL616_SHA }} + path: | + tangcore-work/toolchain_gcc_t-head_linux + tangcore-work/bouffalo_sdk + key: tangcore-toolchain-sdk-${{ env.TOOLCHAIN_SHA }}-${{ env.BOUFFALO_SDK_SHA }} - - name: Clone toolchain, SDK, firmware (pinned commits) + - name: Clone toolchain + SDK (pinned commits) if: steps.cache-tangcore.outputs.cache-hit != 'true' run: | set -euo pipefail @@ -55,16 +62,23 @@ jobs: git -C bouffalo_sdk checkout -q FETCH_HEAD git -C bouffalo_sdk submodule update --init --recursive --depth 1 + - name: Clone firmware-bl616 (pinned commit, always fresh) + run: | + set -euo pipefail + cd tangcore-work + rm -rf firmware-bl616 git init -q firmware-bl616 git -C firmware-bl616 remote add origin https://github.com/nand2mario/firmware-bl616.git git -C firmware-bl616 fetch --depth 1 origin "$FIRMWARE_BL616_SHA" git -C firmware-bl616 checkout -q FETCH_HEAD - - name: Vendor libchdr into firmware-bl616 + - name: Apply integration patch + vendor libchdr into firmware-bl616 run: | set -euo pipefail F=tangcore-work/firmware-bl616 + git -C "$F" apply "${{ github.workspace }}/contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch" + mkdir -p "$F/thirdparty/libchdr" cp -r include src "$F/thirdparty/libchdr/" mkdir -p "$F/thirdparty/libchdr/deps" @@ -72,8 +86,6 @@ jobs: mkdir -p "$F/chd" cp contrib/tangcore-bl616/chd/chd_fatfs.h contrib/tangcore-bl616/chd/chd_fatfs.c "$F/chd/" - cp contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt "$F/CMakeLists.txt" - cp contrib/tangcore-bl616/firmware-overlay/main.cpp "$F/main.cpp" - name: Build (TANG_BOARD=console60k) run: | diff --git a/contrib/tangcore-bl616/README.md b/contrib/tangcore-bl616/README.md index 8b89099..73a1c39 100644 --- a/contrib/tangcore-bl616/README.md +++ b/contrib/tangcore-bl616/README.md @@ -11,26 +11,54 @@ does prove: libchdr keeps building cleanly against the real Xuantie/T-Head toolchain and nand2mario's real bouffalo_sdk fork, on every libchdr change. See `.github/workflows/bl616-tangcore-build.yml`. +## Licensing - what's ours, what's theirs + +libchdr is BSD-3-Clause (`LICENSE.txt`). `firmware-bl616` and `bouffalo_sdk` +are nand2mario's own repos, Apache License 2.0. Nothing here changes +libchdr's own license - same model as `deps/` (each vendored third party +keeps its own license, separate from libchdr's top-level one) - but it's +worth being explicit since this directory touches someone else's project +directly: + +- **`chd/chd_fatfs.{h,c}`** - 100% original code, not derived from any + third-party source. BSD-3-Clause, same as the rest of libchdr (see the + file headers). +- **`patches/firmware-bl616-libchdr-integration.patch`** - a small (92-line) + unified diff against nand2mario's actual `CMakeLists.txt` and `main.cpp` + from `firmware-bl616` (Apache-2.0, © nand2mario). **This directory does + NOT contain copies of his files** - only the diff, applied at build time + in CI (`git apply`) against a freshly-cloned, pinned commit. This is + deliberate: a patch is the standard, minimal way to distribute a + modification to someone else's code without redistributing the whole + file, and it keeps this repo from carrying content that isn't ours. + Applying the patch reproduces two files that remain Apache-2.0/© + nand2mario, modified - not BSD-3-Clause libchdr content. Nothing under + `contrib/` is compiled into libchdr itself, so none of this reaches + libchdr's own build artifacts (`libchdr.so`/`chdr-static.a`). + ## Contents - `chd/chd_fatfs.{h,c}` - a `core_file_callbacks` implementation backed by FatFS (`f_open`/`f_read`/`f_lseek`/`f_close`), the bridge libchdr needs to open a CHD from an SD card or USB drive under this firmware. -- `firmware-overlay/{CMakeLists.txt,main.cpp}` - full copies of - `firmware-bl616`'s own files, modified to vendor libchdr in and add a - `chd_link_probe()` call. The probe is deliberately not a no-op: an - unreferenced library builds and links "clean" by silently getting - dead-stripped, which proved nothing the first time this was tried. The - probe calls `chd_fatfs_open()` on a path that doesn't exist (safe - no SD - is mounted yet at that point in `main()`), forcing the linker to fully - resolve libchdr against this toolchain's libc. +- `patches/firmware-bl616-libchdr-integration.patch` - modifies + `firmware-bl616`'s `CMakeLists.txt` (vendors libchdr in, sets + `LOWRAM_TARGET=1`) and `main.cpp` (adds a `chd_link_probe()` call). The + probe is deliberately not a no-op: an unreferenced library builds and + links "clean" by silently getting dead-stripped, which proved nothing the + first time this was tried locally. The probe calls `chd_fatfs_open()` on + a path that doesn't exist (safe - no SD is mounted yet at that point in + `main()`), forcing the linker to fully resolve libchdr against this + toolchain's libc. ## Pinned versions CI clones fixed commits, not branch heads - `firmware-bl616` and `bouffalo_sdk` are nand2mario's own repos we don't control, so pinning keeps libchdr's CI from going red over changes we didn't make. Bump these -manually when picking up upstream changes is actually wanted. +manually when picking up upstream changes is actually wanted (the patch +above may need regenerating if `firmware-bl616`'s `CMakeLists.txt`/ +`main.cpp` have since diverged). | repo | commit | |---|---| @@ -48,17 +76,18 @@ git clone https://github.com/nand2mario/firmware-bl616.git export PATH="$PWD/toolchain_gcc_t-head_linux/bin:$PATH" -mkdir -p firmware-bl616/thirdparty/libchdr -cp -r /path/to/libchdr/include /path/to/libchdr/src firmware-bl616/thirdparty/libchdr/ -mkdir -p firmware-bl616/thirdparty/libchdr/deps +cd firmware-bl616 +git apply /path/to/libchdr/contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch + +mkdir -p thirdparty/libchdr +cp -r /path/to/libchdr/include /path/to/libchdr/src thirdparty/libchdr/ +mkdir -p thirdparty/libchdr/deps cp -r /path/to/libchdr/deps/lzma-26.02 /path/to/libchdr/deps/miniz-3.1.2 /path/to/libchdr/deps/zstd-1.5.7 \ - firmware-bl616/thirdparty/libchdr/deps/ + thirdparty/libchdr/deps/ -cp /path/to/libchdr/contrib/tangcore-bl616/chd/*.{h,c} firmware-bl616/chd/ -cp /path/to/libchdr/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt firmware-bl616/CMakeLists.txt -cp /path/to/libchdr/contrib/tangcore-bl616/firmware-overlay/main.cpp firmware-bl616/main.cpp +mkdir -p chd +cp /path/to/libchdr/contrib/tangcore-bl616/chd/*.{h,c} chd/ -cd firmware-bl616 make # BL_SDK_BASE defaults to ../bouffalo_sdk, TANG_BOARD defaults to console60k ``` diff --git a/contrib/tangcore-bl616/chd/chd_fatfs.c b/contrib/tangcore-bl616/chd/chd_fatfs.c index 558df7b..afb983c 100644 --- a/contrib/tangcore-bl616/chd/chd_fatfs.c +++ b/contrib/tangcore-bl616/chd/chd_fatfs.c @@ -1,3 +1,11 @@ +/* license:BSD-3-Clause + * copyright-holders:Romain Tisserand + * + * chd_fatfs.c + * + * See chd_fatfs.h. Original code, not derived from any third-party source. + */ + #include "chd_fatfs.h" #include /* SEEK_SET / SEEK_CUR / SEEK_END */ diff --git a/contrib/tangcore-bl616/chd/chd_fatfs.h b/contrib/tangcore-bl616/chd/chd_fatfs.h index bdbb313..4c75acf 100644 --- a/contrib/tangcore-bl616/chd/chd_fatfs.h +++ b/contrib/tangcore-bl616/chd/chd_fatfs.h @@ -1,3 +1,14 @@ +/* license:BSD-3-Clause + * copyright-holders:Romain Tisserand + * + * chd_fatfs.h + * + * Bridges libchdr's core_file_callbacks onto FatFS, for firmware targets + * (e.g. nand2mario/firmware-bl616) that access CHD files through a FatFS + * volume instead of a hosted libc filesystem. Original code, not derived + * from any third-party source. + */ + #pragma once #include "ff.h" diff --git a/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt b/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt deleted file mode 100644 index b748fb3..0000000 --- a/contrib/tangcore-bl616/firmware-overlay/CMakeLists.txt +++ /dev/null @@ -1,72 +0,0 @@ -cmake_minimum_required(VERSION 3.15) - -# Set C++ standard -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CONFIG_FREERTOS 1) -set(CONFIG_CHERRYUSB 1) -set(CONFIG_CHERRYUSB_HOST 1) -set(CONFIG_BSP_SDH_SDCARD 1) -set(CONFIG_FATFS 1) -set(CONFIG_FATFS_USBH 1) -set(CONFIG_FATFS_SDH_SDCARD 1) - -if(TANG_BOARD STREQUAL "mega60k") - add_definitions(-DTANG_MEGA60K) -elseif(TANG_BOARD STREQUAL "mega138k") - add_definitions(-DTANG_MEGA138K) -elseif(TANG_BOARD STREQUAL "console60k") - add_definitions(-DTANG_CONSOLE60K) -elseif(TANG_BOARD STREQUAL "console138k") - add_definitions(-DTANG_CONSOLE138K) -elseif(TANG_BOARD STREQUAL "primer25k") - add_definitions(-DTANG_PRIMER25K) -elseif(TANG_BOARD STREQUAL "nano20k") - add_definitions(-DTANG_NANO20K) -endif() - -# libchdr: LOWRAM_TARGET on (real ~365KB free SRAM budget on this board, see -# project memory - whole-hunk decode does not fit, streaming-decode design does) -add_definitions( - -DLOWRAM_TARGET=1 - -DWANT_RAW_DATA_SECTOR=1 - -DWANT_SUBCODE=1 - -DVERIFY_BLOCK_CRC=1 - -DMINIZ_NO_ARCHIVE_APIS - -DMINIZ_NO_DEFLATE_APIS - -DMINIZ_NO_STDIO - -DMINIZ_NO_TIME -) - -find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE}) - -# Add include directories -sdk_add_include_directories( - . usb fpga ui core utils chd - thirdparty/libchdr/include - thirdparty/libchdr/src -) - -# Add source files -file(GLOB_RECURSE MY_SOURCES - "usb/*.cpp" - "fpga/*.cpp" - "ui/*.cpp" - "core/*.cpp" - "utils/*.cpp" -) - -file(GLOB LIBCHDR_SOURCES - "chd/*.c" - "thirdparty/libchdr/src/*.c" - "thirdparty/libchdr/deps/lzma-26.02/src/LzmaDec.c" - "thirdparty/libchdr/deps/miniz-3.1.2/miniz.c" - "thirdparty/libchdr/deps/zstd-1.5.7/zstddeclib.c" -) - -target_sources(app PRIVATE ${MY_SOURCES} ${LIBCHDR_SOURCES}) -sdk_set_main_file(main.cpp) - -project(tangcore CXX) - diff --git a/contrib/tangcore-bl616/firmware-overlay/main.cpp b/contrib/tangcore-bl616/firmware-overlay/main.cpp deleted file mode 100644 index c24c645..0000000 --- a/contrib/tangcore-bl616/firmware-overlay/main.cpp +++ /dev/null @@ -1,608 +0,0 @@ -/* - * TangCore firmware for BL616 MCU - * - * (c) 2025, nand2mario - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -#include -#include -#include - -extern "C" { -#include "board.h" -#include "bl616_glb.h" -#include "bflb_gpio.h" -#include "bflb_uart.h" -#include "bflb_clock.h" -#include "bl616_clock.h" - -#include "usbh_core.h" -#include "ff.h" -#include "fatfs_diskio_register.h" -} - -#include "file_chooser.h" -#include "programmer.h" -#include "usb_gamepad.h" -#include "utils.h" -#include "cores.h" -#include "overlay.h" -#include "chd_fatfs.h" -#include "init.h" -#include "menu_manager.h" - -// Uncomment this to enable UART console (use with caution. it may interfere with MCU-FPGA communication) -#define UART_CONSOLE - -///////////////////////////////////////////////////////////////////////////////// -// Global state - -int option_osd_key = OPTION_OSD_KEY_SELECT_RIGHT; -int16_t active_core = -1; // firmware detected this core as active -bool core_running; // a rom is loaded and running on the core -struct core_info *core; - -// UART -struct bflb_device_s *gpio_dev; -struct bflb_device_s *uart0_dev; -struct bflb_device_s *uart1_dev; - -// USB and fatfs -struct usbh_msc *msc; -const char *drv = "sd:"; - -// Tasks and shared state -TaskHandle_t main_task_handle; -TaskHandle_t uart1_rx_task_handle; - -#ifdef TANG_CONSOLE60K -const char *BOARD_NAME = "console60k"; -#elif defined(TANG_CONSOLE138K) -const char *BOARD_NAME = "console138k"; -#elif defined(TANG_MEGA60K) -const char *BOARD_NAME = "mega60k"; -#elif defined(TANG_MEGA138K) -const char *BOARD_NAME = "mega138k"; -#elif defined(TANG_PRIMER25K) -const char *BOARD_NAME = "primer25k"; -#elif defined(TANG_NANO20K) -const char *BOARD_NAME = "nano20k"; -#else -const char *BOARD_NAME = "unknown"; -#endif - -// Override system printf() to send to FPGA -int __attribute__((weak)) putchar(int ch) { - fpga_tx_header(0x05, 2); - fpga_tx_byte(ch); - return ch; -} - - -///////////////////////////////////////////////////////////////////////////////// -// Core loading and other file system operations - -// nand2mario: these USB data structures cannot be CACHED as they are written to by hardware -USB_NOCACHE_RAM_SECTION FATFS fs; -USB_NOCACHE_RAM_SECTION FIL fcore; -USB_NOCACHE_RAM_SECTION BYTE __attribute__((aligned(64))) fbuf[BLOCK_SIZE]; - -FRESULT res_sd; -FileChooser file_chooser; - -// #define PAGESIZE 22 -// #define TOPLINE 2 -// #define PWD_SIZE 1024 -// char pwd[PWD_SIZE]; -// one page of file names to display -// char file_names[PAGESIZE][256]; -// int file_dir[PAGESIZE]; // this file is a directory -// int file_sizes[PAGESIZE]; -// int file_len; // number of files on this page - -///////////////////////////////////////////////////////////////////////////////// -// Menu display and user interaction - -// Menus for "NES", "SNES" ... entries -// dir: initial dir including the drive name (e.g. "sd:nes", "usb:cores") -// return 0: user chose a ROM (*choice), 1: no choice made, -1: error -// file chosen: pwd / file_name[*choice] -static int menu_loadrom(const char *dir) { - string fname; - file_chooser.rootdir = dir; - file_chooser.curdir = dir; - file_chooser.msg_return = "<< Return to main menu"; - bool r = file_chooser.choose_file(fname); - if (!r) { - overlay_status("No file chosen"); - return 1; - } - - // now proceed to load the core and ROM - joy1_state = 0; joy2_state = 0; // clear joypad states - - // load core if in cores/ dir - if (fname.find(string(drv) + "cores") == 0) { - overlay_status("Core: %s", fname.c_str()); - fpga_program(fname.c_str()); - _overlay_on = 1; // turn on overlay after core is loaded - return 0; // return to main menu - } - - // find core info entry - core_info *core = NULL; - string path = fname.substr(fname.find(":")+1); - for (int i = 0; i < core_info_list.size(); i++) { - core_info *c = &core_info_list[i]; - if (path.find(c->rom_dir) == 0) { - overlay_status("ROM for: %s", c->display_name); - core = c; - break; - } - } - if (core == NULL) { - overlay_status("Core not found: %s", path.c_str()); - return -1; - } - - // user chose a ROM file - active_core = get_core_id(); - - // load core if needed - if (core != NULL) { - if (active_core != core->id) { // active core is not what we need - string fname_core; - if (find_core_for_board(fname_core, core->core_file)) { - // load core - fpga_program(fname_core.c_str()); - _overlay_on = 1; - - // allow 2 seconds for core to start - uint64_t start = bflb_mtimer_get_time_ms(); - while (bflb_mtimer_get_time_ms() - start < 2000) { - send_blank_packet(); - active_core = get_core_id(); - if (active_core == core->id) - break; - } - } - } - - // Attemp to load ROM - if (active_core == core->id) { - overlay_status("Loading ROM: %s\n", fname.c_str()); - core->load_rom(fname.c_str()); - return 1; - } else { - overlay_status("Core failed to load\n"); - delay(1000); - return -1; - } - } - return -1; -} - -static void menu_options(void) { - // to be implemented -} - -// keep sending HID state to core until OSD is turned on -static void send_hid_to_core(void) { - uint16_t hid1_old = 0, hid2_old = 0; - bool first = true; - dprint("Start sending HID to core..."); - while (1) { - uint16_t joy1=0, joy2=0, hid1=0, hid2=0; - get_joypad_states(&joy1, &joy2, &hid1, &hid2); - if (first || hid1 != hid1_old || hid2 != hid2_old) { // send HID if changed - fpga_tx_header(0x09, 5); - fpga_tx_byte(hid1 >> 8); - fpga_tx_byte(hid1 & 0xff); - fpga_tx_byte(hid2 >> 8); - fpga_tx_byte(hid2 & 0xff); - hid1_old = hid1; - hid2_old = hid2; - first = false; - } - if (joy1 == OSD_KEY_CODE || joy2 == OSD_KEY_CODE || hid1 == OSD_KEY_CODE || hid2 == OSD_KEY_CODE) { - break; - } - if (overlay_on()) // turned off by keyboard - break; - vTaskDelay(pdMS_TO_TICKS(10)); - } - dprint("Stopped sending HID to core."); -} - -// // (R L X A RT LT DN UP START SELECT Y B) -// Return: 1 button B pressed, 4: button A pressed, 2: next page, 3: previous page -// active is the entry chosen -int joy_choice(int start_line, int len, int *active, int overlay_key_code) { - if (*active < 0 || *active >= len) - *active = 0; - uint16_t joy1=0, joy2=0, hid1=0, hid2=0; - int last = *active; - - get_joypad_states(&joy1, &joy2, &hid1, &hid2); - joy1 |= hid1; - joy2 |= hid2; - - if ((joy1 == overlay_key_code) || (joy2 == overlay_key_code)) { - overlay_status("OSD: %s", overlay_on() ? "ON" : "OFF"); - overlay(!overlay_on()); // toggle OSD - delay(300); - } - - if (!overlay_on()) { // keep sending HID state to core when OSD is off - send_hid_to_core(); - return 0; - } - - if ((joy1 & 0x10) || (joy2 & 0x10)) { - if (*active > 0) (*active)--; - } - if ((joy1 & 0x20) || (joy2 & 0x20)) { - if (*active < len-1) (*active)++; - } - if ((joy1 & 0x40) || (joy2 & 0x40)) - return 3; // previous page - if ((joy1 & 0x80) || (joy2 & 0x80)) - return 2; // next page - if ((joy1 & 0x100) || (joy2 & 0x100)) - return 4; // button A pressed - if ((joy1 & 0x1) || (joy2 & 0x1)) - return 1; // button B pressed - - overlay_cursor(0, start_line + (*active)); - overlay_printf(">"); - - // overlay_cursor(0, 27); - // overlay_printf(" j1=%04x j2=%04x h1=%04x h2=%04x", joy1, joy2, hid1, hid2); - if (last != *active) { - overlay_cursor(0, start_line + last); - overlay_printf(" "); - delay(100); // button debounce - } - return 0; -} - -#define MAIN_TASK_STACK_SIZE 2048 -#define MAIN_TASK_PRIORITY 3 -#define UART1_RX_TASK_STACK_SIZE 512 -#define UART1_RX_TASK_PRIORITY 3 - -// Receive joypad updates and other UART responses from the FPGA -static void uart1_rx_task(void *pvParameters) -{ - uint8_t buffer[5]; - uint8_t pos = 0; - uint8_t type = 0; - uint16_t len = 0; - - while (1) { - if (bflb_uart_rxavailable(uart1_dev)) { - uint8_t ch = bflb_uart_getchar(uart1_dev); - - if (pos == 0) { // expecting 0xAA - if (ch == 0xAA) - pos++; - } else if (pos == 1) { // len msb - len = (uint16_t)ch << 8; - pos++; - } else if (pos == 2) { // len lsb - len += ch; - pos++; - } else if (pos == 3) { // command type - type = ch; - pos++; - - ////// pos >= 4 ////// - } else if (type == 1) { // response to command 1 (get core ID) - if (xSemaphoreTake(state_mutex, portMAX_DELAY) == pdTRUE) { - core_id = ch; - xSemaphoreGive(state_mutex); - } - pos = 0; - } else if (type == 2) { // config string - // skip for now - if (pos == len+2) - pos = 0; - else - pos++; - } else if (type == 3) { // periodic joypad state - buffer[pos-4] = ch; - // Complete packet received - if (pos == 7) { - // Combine bytes into 16-bit values - uint16_t joy1 = (buffer[0] << 8) | buffer[1]; - uint16_t joy2 = (buffer[2] << 8) | buffer[3]; - - // Update global state with mutex protection - if (xSemaphoreTake(state_mutex, portMAX_DELAY) == pdTRUE) { - joy1_state = joy1; - joy2_state = joy2; - xSemaphoreGive(state_mutex); - } - pos = 0; // Reset for next packet - } else - pos++; - } else if (type == 4) { // floppy write - if (pos < 6) - buffer[pos-4] = ch; - else - fbuf[pos-6] = ch; - if (pos == 6+511) { - uint16_t drive = buffer[0] >> 7; - uint16_t sector = (buffer[0] & 0x7f) << 8 | buffer[1]; - if (floppy[drive]) { - UINT br; - f_lseek(&f_floppy[drive], sector * 512); - if (f_write(&f_floppy[drive], fbuf, 512, &br) != FR_OK) { - overlay_status("Failed to write floppy"); - } - } - pos = 0; // reset for next packet - } else - pos++; - } else if (type == 5) { // floppy read - buffer[pos-4] = ch; - if (pos == 5) { - uint16_t drive = buffer[0] >> 7; - uint16_t sector = (buffer[0] & 0x7f) << 8 | buffer[1]; - if (floppy[drive]) { - UINT br; - f_lseek(&f_floppy[drive], sector * 512); - if (f_read(&f_floppy[drive], fbuf, 512, &br) == FR_OK) { - fpga_tx_header(0x0a, br+1); - for (UINT i = 0; i < br; i++) { - fpga_tx_byte(fbuf[i]); - } - } else { - overlay_status("Failed to read floppy"); - } - } - pos = 0; // reset for next packet - } else - pos++; - - } else { - pos = 0; // Reset if we get out of sync - } - } - - vTaskDelay(pdMS_TO_TICKS(1)); - } -} - -// Display main menu and call other menu functions -static void main_task(void *pvParameters) -{ - uint32_t last_redraw_time = 0; - // volatile uint32_t *reg_gpio0 = (volatile uint32_t *)0x200008c4; // bl616 reference 4.8.5 - // volatile uint32_t *reg_gpio1 = (volatile uint32_t *)0x200008c8; - // volatile uint32_t *reg_gpio2 = (volatile uint32_t *)0x200008cc; - // volatile uint32_t *reg_gpio3 = (volatile uint32_t *)0x200008d0; - - // wait for drive to be ready - uint64_t start = bflb_mtimer_get_time_ms(); - FRESULT res; - overlay_status("Mounting sd card...", drv); - while ((res = f_mount(&fs, "sd:", 1)) != FR_OK && bflb_mtimer_get_time_ms() - start < 500) - delay(100); - - if (res == FR_OK) { - overlay_status("SD card mounted in %d ms", bflb_mtimer_get_time_ms() - start); - } else { - overlay_status("SD not found. Mounting USB..."); - drv = "usb:"; - start = bflb_mtimer_get_time_ms(); - while ((res = f_mount(&fs, "usb:", 1)) != FR_OK && bflb_mtimer_get_time_ms() - start < 2000) - delay(100); - if (res != FR_OK) { - overlay_status("Failed to mount USB drive"); - } else { - overlay_status("USB drive mounted in %d ms", bflb_mtimer_get_time_ms() - start); - } - } - - // load monitor core at startup - string fname; - if (find_core_for_board(fname, "monitor.bin")) { - fpga_program(fname.c_str()); - } else { - overlay_status("No monitor.bin found for board."); - } - - int line_start; - int menu_cnt = main_menu_config.size(); - line_start = 13 - (menu_cnt+2+2) / 2; // 2 lines for version, 2 lines for "TangCore" - - while (1) { - bool redraw = true; - int choice = 0; - for (;;) { - uint32_t now = bflb_mtimer_get_time_ms(); - if (active_core == -1) { - // send_blank_packet(); - active_core = get_core_id(); // 200ms timeout - overlay_status("core_id=%d", active_core); - if (active_core >= 0) redraw = true; // redraw immediately if core is detected - } - // if (core < 0) continue; // do not draw or process input if core is not ready - if (now - last_redraw_time > 5000) - redraw = true; - if (redraw) { - active_core = get_core_id(); // allow jtag to change core underneath us - overlay(overlay_on()); // set correct overlay state - overlay_clear(); - - int line = line_start; - overlay_cursor(0, line++); - // 01234567890123456789012345678901 - overlay_printf(" -== TangCore ==-"); - line++; - - // display all menu items - for (int i = 0; i < menu_cnt; i++) { - overlay_cursor(2, line++); - if (main_menu_config[i] > 0) { - for (int j = 0; core_info_list[j].id != 0; j++) { - if (core_info_list[j].id == main_menu_config[i]) { - overlay_printf("%s", core_info_list[j].display_name); - break; - } - } - } else if (main_menu_config[i] == -1) { - overlay_printf("Cores"); - } else if (main_menu_config[i] == -2) { - overlay_printf("Options"); - } - } - - line++; - overlay_cursor(2, line++); - overlay_printf("Version: "); - overlay_printf(__DATE__); - last_redraw_time = now; - redraw = false; - - // print some debug stats to UART - // uint16_t joy1=0, joy2=0; - // get_joypad_states(&joy1, &joy2); - // overlay_status("core=%d, j1=%04x, j2=%04x", active_core, joy1, joy2); - // overlay_status("Mtimer frequency: %d MHz", bflb_mtimer_get_freq() / 1000000); - // overlay_status("CPU frequency: %d MHz", bflb_clk_get_system_clock(BL_SYSTEM_CLOCK_MCU_CLK) / 1000000); - // overlay_status("GPIO0-3 status: %08x %08x %08x %08x", *reg_gpio0, *reg_gpio1, *reg_gpio2, *reg_gpio3); - } - - bool before_overlay = overlay_on(); - int r = joy_choice(line_start+2, menu_cnt, &choice, OSD_KEY_CODE); - if (r == 1) break; - - if (!before_overlay && overlay_on() && active_core > 0) { - // overlay is turned back on, now display the pop-up menu - DEBUG("Displaying pop-up menu\n"); - menu_clear(); - // Menu *menu = core->create_menu(core->rom_dir) - core_info *core = find_core_by_id(active_core); - if (core != NULL) { - DEBUG("Found core_info. Displaying menu\n"); - Menu *menu; - if (active_core == 6) { - menu = create_pcxt_menu(std::string(drv).append(core->rom_dir).c_str()); - } else { - menu = create_default_menu(std::string(drv).append(core->rom_dir).c_str()); - } - std::unique_ptr menu_ptr(menu); - push_menu(std::move(menu_ptr)); - menu->do_redraw(); - menu_input_loop(); - redraw = true; - } - } - - delay(20); - } - - if (main_menu_config[choice] > 0) { - // Load rom or core from USB drive - struct core_info *core = NULL; - for (int i = 0; core_info_list[i].id != 0; i++) { - if (core_info_list[i].id == main_menu_config[choice]) { - core = &core_info_list[i]; - break; - } - } - if (core) { - std::string dir = std::string(drv).append(core->rom_dir); - menu_loadrom(dir.c_str()); - } - } else if (main_menu_config[choice] == -1) { - // load cores manually - std::string dir = std::string(drv).append("cores"); - menu_loadrom(dir.c_str()); - } else if (main_menu_config[choice] == -2) { - // Options - menu_options(); - } - - delay(300); - } -} - -static void print_system_info(void) { - // this is viewable with scripts/liveuart.py - overlay_status("TangCore %s", __DATE__); - overlay_status("TangBoard: %s", BOARD_NAME); - overlay_status("System clock: %u MHz", bflb_clk_get_system_clock(BL_SYSTEM_CLOCK_MCU_CLK) / 1000000); - // UART registers - // Clock comes from XCLK/160M/BCLK and goes through a divider and becomes UART_CLK - overlay_status("GLB_UART_CFG0: %08x", BL_RD_WORD(0x20000150)); - overlay_status("GLB_UART_CFG1: %08x", BL_RD_WORD(0x20000154)); - overlay_status("GLB_UART_CFG2: %08x", BL_RD_WORD(0x20000158)); - overlay_status("UART0 clock: %u", Clock_Peripheral_Clock_Get(BL_PERIPHERAL_CLOCK_UART0)); - overlay_status("UART1 clock: %u", Clock_Peripheral_Clock_Get(BL_PERIPHERAL_CLOCK_UART1)); - - // 10.3.5: baudrate = UART_clk / (uart_prd + 1) - // This causes memory exception. - // overlay_status("UART_BIT_PRD: %08x", BL_RD_WORD(0x40010008)); - // 01234567890123456789012345678901 - // overlay_status(" "); -} - -// Initialize things, then start main_task and uart1_rx_task to do actual work -// TEMPORARY link-verification probe: forces the linker to fully pull in and -// resolve libchdr against this toolchain/libc (LOWRAM_TARGET codec paths -// included). Not a real feature - no SD card is mounted yet at this point in -// boot, so this always fails to open and returns cleanly. Remove once real -// CHD loading is wired into a core loader. -static void chd_link_probe(void) -{ - FIL fil; - chd_file *chd = NULL; - chd_error err = chd_fatfs_open("sd:/__chd_link_probe__.chd", &fil, &chd); - DEBUG("chd_link_probe: chd_fatfs_open -> %d\n", (int)err); - if (chd) - chd_close(chd); -} - -int main(void) -{ - /* Board init */ - board_init(); - init_core_list(); - - // Initialize GPIO and UART - init_gpio_and_uart(); - - print_system_info(); - - chd_link_probe(); - - // Create mutex for joypad states - state_mutex = xSemaphoreCreateMutex(); - - overlay_status("Initializing SDH..."); - fatfs_sdh_driver_register(); // calls SDH_Init() - // f_mount(&fs_sd, "sd:", 0); // registers SDMMC drive - - // Initializing USB host... - overlay_status("Initializing USB host..."); - usbh_initialize(); - fatfs_usbh_driver_register(); - usb_gamepad_init(); - - overlay_status("Creating tasks..."); - // Create the tasks - xTaskCreate(main_task, "main_task", MAIN_TASK_STACK_SIZE, NULL, MAIN_TASK_PRIORITY, &main_task_handle); - xTaskCreate(uart1_rx_task, "uart1_rx_task", UART1_RX_TASK_STACK_SIZE, NULL, UART1_RX_TASK_PRIORITY, &uart1_rx_task_handle); - - vTaskStartScheduler(); - - while (1) { - } -} diff --git a/contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch b/contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch new file mode 100644 index 0000000..19528df --- /dev/null +++ b/contrib/tangcore-bl616/patches/firmware-bl616-libchdr-integration.patch @@ -0,0 +1,92 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8e7d0fe..b748fb3 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -26,11 +26,26 @@ elseif(TANG_BOARD STREQUAL "nano20k") + add_definitions(-DTANG_NANO20K) + endif() + ++# libchdr: LOWRAM_TARGET on (real ~365KB free SRAM budget on this board, see ++# project memory - whole-hunk decode does not fit, streaming-decode design does) ++add_definitions( ++ -DLOWRAM_TARGET=1 ++ -DWANT_RAW_DATA_SECTOR=1 ++ -DWANT_SUBCODE=1 ++ -DVERIFY_BLOCK_CRC=1 ++ -DMINIZ_NO_ARCHIVE_APIS ++ -DMINIZ_NO_DEFLATE_APIS ++ -DMINIZ_NO_STDIO ++ -DMINIZ_NO_TIME ++) ++ + find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE}) + + # Add include directories + sdk_add_include_directories( +- . usb fpga ui core utils ++ . usb fpga ui core utils chd ++ thirdparty/libchdr/include ++ thirdparty/libchdr/src + ) + + # Add source files +@@ -42,7 +57,15 @@ file(GLOB_RECURSE MY_SOURCES + "utils/*.cpp" + ) + +-target_sources(app PRIVATE ${MY_SOURCES}) ++file(GLOB LIBCHDR_SOURCES ++ "chd/*.c" ++ "thirdparty/libchdr/src/*.c" ++ "thirdparty/libchdr/deps/lzma-26.02/src/LzmaDec.c" ++ "thirdparty/libchdr/deps/miniz-3.1.2/miniz.c" ++ "thirdparty/libchdr/deps/zstd-1.5.7/zstddeclib.c" ++) ++ ++target_sources(app PRIVATE ${MY_SOURCES} ${LIBCHDR_SOURCES}) + sdk_set_main_file(main.cpp) + + project(tangcore CXX) +diff --git a/main.cpp b/main.cpp +index e349734..c24c645 100644 +--- a/main.cpp ++++ b/main.cpp +@@ -31,6 +31,7 @@ extern "C" { + #include "utils.h" + #include "cores.h" + #include "overlay.h" ++#include "chd_fatfs.h" + #include "init.h" + #include "menu_manager.h" + +@@ -554,6 +555,21 @@ static void print_system_info(void) { + } + + // Initialize things, then start main_task and uart1_rx_task to do actual work ++// TEMPORARY link-verification probe: forces the linker to fully pull in and ++// resolve libchdr against this toolchain/libc (LOWRAM_TARGET codec paths ++// included). Not a real feature - no SD card is mounted yet at this point in ++// boot, so this always fails to open and returns cleanly. Remove once real ++// CHD loading is wired into a core loader. ++static void chd_link_probe(void) ++{ ++ FIL fil; ++ chd_file *chd = NULL; ++ chd_error err = chd_fatfs_open("sd:/__chd_link_probe__.chd", &fil, &chd); ++ DEBUG("chd_link_probe: chd_fatfs_open -> %d\n", (int)err); ++ if (chd) ++ chd_close(chd); ++} ++ + int main(void) + { + /* Board init */ +@@ -565,6 +581,8 @@ int main(void) + + print_system_info(); + ++ chd_link_probe(); ++ + // Create mutex for joypad states + state_mutex = xSemaphoreCreateMutex(); +