Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/workflows/bl616-tangcore-build.yml
Original file line number Diff line number Diff line change
@@ -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
103 changes: 103 additions & 0 deletions contrib/tangcore-bl616/README.md
Original file line number Diff line number Diff line change
@@ -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.
83 changes: 83 additions & 0 deletions contrib/tangcore-bl616/chd/chd_fatfs.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h> /* 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;
}
34 changes: 34 additions & 0 deletions contrib/tangcore-bl616/chd/chd_fatfs.h
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading