diff --git a/.github/workflows/bl616-tangcore-build.yml b/.github/workflows/bl616-tangcore-build.yml new file mode 100644 index 0000000..81ed34a --- /dev/null +++ b/.github/workflows/bl616-tangcore-build.yml @@ -0,0 +1,97 @@ +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 + + # 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/toolchain_gcc_t-head_linux + tangcore-work/bouffalo_sdk + key: tangcore-toolchain-sdk-${{ env.TOOLCHAIN_SHA }}-${{ env.BOUFFALO_SDK_SHA }} + + - name: Clone toolchain + SDK (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 + + - 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: 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" + 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/" + + - 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..73a1c39 --- /dev/null +++ b/contrib/tangcore-bl616/README.md @@ -0,0 +1,103 @@ +# 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`. + +## 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. +- `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 (the patch +above may need regenerating if `firmware-bl616`'s `CMakeLists.txt`/ +`main.cpp` have since diverged). + +| 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" + +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 \ + thirdparty/libchdr/deps/ + +mkdir -p chd +cp /path/to/libchdr/contrib/tangcore-bl616/chd/*.{h,c} chd/ + +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..afb983c --- /dev/null +++ b/contrib/tangcore-bl616/chd/chd_fatfs.c @@ -0,0 +1,83 @@ +/* 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 */ + +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..4c75acf --- /dev/null +++ b/contrib/tangcore-bl616/chd/chd_fatfs.h @@ -0,0 +1,34 @@ +/* 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" +#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/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(); +