Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
de4fca2
feat: open-retro-storage-frontpanel spi
erichelgeson Jun 8, 2026
5390f97
feat: open-retro-storage-frontpanel i2c
erichelgeson Jun 8, 2026
7d44660
panel: remove dead platform_has_i2c()
erichelgeson Jun 9, 2026
027fa3a
panel: enable I2C panel on RAM-capable v2 profiles
erichelgeson Jun 9, 2026
ebc9104
panel: gate software SHA-256 on RP2040
erichelgeson Jun 9, 2026
c6ef2ec
panel: use dbgmsg for periodic link stats and per-request traces
erichelgeson Jun 9, 2026
4e10cb8
panel: dedupe scsi bus busy check into panel_protocol
erichelgeson Jun 9, 2026
36c3184
panel: remove debug cruft
erichelgeson Jun 17, 2026
cf7079e
zip: fix ZipParser cross-instance static parse state
erichelgeson Jul 16, 2026
11216f3
panel: extract front panel firmware from the update package
erichelgeson Jul 16, 2026
94f656e
build: include front panel firmware bins in the release zip
erichelgeson Jul 16, 2026
805fd32
ci: fetch pinned front panel firmware for release packaging
erichelgeson Jul 16, 2026
098cb87
panel: parse -preN prerelease tags from the ESP32 firmware version
erichelgeson Jul 18, 2026
9346a04
panel: switch to the unified bluescsi-frontpanel.bin
erichelgeson Jul 20, 2026
84c8274
panel: say when EnableFrontPanel is set on a build without panel support
erichelgeson Jul 20, 2026
186f768
panel: browse and load CDs by .cue instead of .bin
erichelgeson Jul 21, 2026
72884fb
initiator: keep panel status reporting out of non-panel builds
erichelgeson Jul 21, 2026
dbdce26
fix: images with 60+ char names broke cycling - raise MAX_FILE_PATH t…
erichelgeson Jul 21, 2026
851eafc
fix: keep firmware_update() under the stack-frame gate after MAX_FILE…
erichelgeson Jul 21, 2026
6fc2e95
panel: place I2C and SHA-256 panel code in flash
erichelgeson Aug 2, 2026
013e266
fix: raise core 0 stack to 3KB on RP2040 as well
erichelgeson Aug 2, 2026
6ebb919
panel: bound panel-supplied I2C transfer sizes
erichelgeson Aug 2, 2026
4346e14
panel: refresh the device snapshot only while the bus is idle
erichelgeson Aug 2, 2026
fa52bf2
panel: keep the image-switch chain off the stack limit, guard file ops
erichelgeson Aug 2, 2026
e044613
panel: resync protocol defs with panel firmware, fix stale comments
erichelgeson Aug 29, 2026
0e778cf
panel: keep the write payload buffer owned until dispatch finishes
erichelgeson Aug 29, 2026
7b2de10
panel: release the download file handle when the transfer ends
erichelgeson Aug 29, 2026
cdb814b
panel: move startup and SD-hotplug code to flash on RP2350
erichelgeson Aug 29, 2026
2afe7e7
panel: refresh the directory listing after a file upload
erichelgeson Aug 29, 2026
af21e42
fix: move VERIFY and Wi-Fi bring-up to flash so Audio SPDIF links
erichelgeson Aug 30, 2026
e25c15b
panel: eject what the eject button ejects, and let it close again
erichelgeson Aug 31, 2026
eb601f5
panel: flag log, ini and crash files as not loadable
erichelgeson Aug 31, 2026
6a25f79
initiator: report image filename, speed and skip reasons to the panel
erichelgeson Aug 31, 2026
bd8b42a
initiator: tick blink_poll from delay_with_poll so the LED keeps moving
erichelgeson Aug 31, 2026
389a13c
panel: note the initiator header is shared verbatim with the panel
erichelgeson Aug 31, 2026
0484fb8
panel: report the operating mode on the status poll
erichelgeson Aug 31, 2026
806b4dd
panel: give the initiator its own synchronous status command
erichelgeson Aug 31, 2026
49da728
panel: stop suspending the panel IRQ while the initiator holds the bus
erichelgeson Aug 31, 2026
2226e73
panel: name the drive being imaged in the initiator summary
erichelgeson Aug 31, 2026
8f7f555
ci: fetch the panel firmware with the built-in token
erichelgeson Sep 2, 2026
5f8330e
ci: update the existing release for a re-pushed tag instead of leavin…
erichelgeson Sep 3, 2026
4b77863
ci: stamp PR artifacts with the branch sha, not the merge sha
erichelgeson Sep 3, 2026
0d6b2f6
build: pico-sdk rel4 - fixes SHA-256 hashes of files 512 MiB and over
erichelgeson Sep 3, 2026
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
71 changes: 61 additions & 10 deletions .github/workflows/firmware_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ jobs:
name: Build firmware on Ubuntu
runs-on: ubuntu-latest

# A pull request build checks out the merge commit, so stamp artifacts with
# the PR head sha - the one a reporter can actually look up.
env:
DIST_SHA: ${{ github.event.pull_request.head.sha || github.sha }}

steps:
- name: Check out code from GitHub
uses: actions/checkout@v6
Expand All @@ -32,6 +37,17 @@ jobs:
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
continue-on-error: true

- name: Download front panel firmware
env:
GH_TOKEN: ${{ github.token }}
run: |
cd BlueSCSI
PANEL_TAG=$(cat utils/frontpanel_version.txt)
mkdir -p panel-fw
gh release download "$PANEL_TAG" --repo polpo/open-retro-storage-frontpanel \
-p 'bluescsi-frontpanel.bin' -D panel-fw
ls -la panel-fw/

- name: Build firmware
run: |
cd BlueSCSI
Expand Down Expand Up @@ -79,21 +95,56 @@ jobs:
gh api repos/${GITHUB_REPOSITORY}/releases/tags/latest | jq -r '.assets[] | [.url] | @tsv' | xargs -n 1 gh api -X DELETE || true
gh release upload --repo ${GITHUB_REPOSITORY} --clobber latest *

- name: Upload to newly created release
- name: Upload to tagged release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ startsWith(github.ref, 'refs/tags/') && github.repository == 'BlueSCSI/BlueSCSI-v2' }}
run: |
set +e
RELEASE=$(basename ${{github.ref}})
RELEASE="${GITHUB_REF#refs/tags/}"
if [[ "$RELEASE" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}$ ]]; then
RELEASE_FLAGS="--latest"
# Dated release: land as a draft so the changelog can be written
# before it is published.
DATED=1
CREATE_FLAGS="--draft --latest"
else
RELEASE_FLAGS="--prerelease"
# Beta or test tag: publish it, and let a re-push of the same tag
# update that release rather than adding another one.
DATED=0
CREATE_FLAGS="--prerelease"
fi
gh release create --repo ${GITHUB_REPOSITORY} --draft -t "$RELEASE" $RELEASE_FLAGS "$RELEASE" BlueSCSI/dist/*
status=$?
set -e
if [ $status -ne 0 ]; then
gh release upload --repo ${GITHUB_REPOSITORY} --clobber "$RELEASE" BlueSCSI/dist/*

# A draft release is not attached to its tag, so `gh release create`
# succeeds on a tag that already has a release and leaves a second,
# untagged one behind - which is what users then never see. Look the
# tag up first and replace the assets on the release it already has.
ID=$(gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
--jq "[.[] | select(.tag_name == \"$RELEASE\")] | sort_by(.draft) | .[0].id // empty")

if [ -z "$ID" ]; then
gh release create "$RELEASE" --repo "${GITHUB_REPOSITORY}" -t "$RELEASE" \
$CREATE_FLAGS BlueSCSI/dist/*
exit 0
fi

echo "Updating release $ID for tag $RELEASE"
if [ "$DATED" = "0" ]; then
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/releases/${ID}" \
-F draft=false -F prerelease=true >/dev/null
fi

# Asset names carry the build date and sha, so --clobber alone would
# leave the previous build sitting next to the new one.
gh api "repos/${GITHUB_REPOSITORY}/releases/${ID}" --jq '.assets[].url' \
| xargs -r -n 1 gh api -X DELETE

# Upload by release id: a draft cannot be addressed by tag.
UPLOAD=$(gh api "repos/${GITHUB_REPOSITORY}/releases/${ID}" --jq '.upload_url' | sed 's/{.*}//')
for f in BlueSCSI/dist/*; do
name=$(basename "$f")
curl -sSf -X POST "${UPLOAD}?name=$(printf '%s' "$name" | jq -sRr @uri)" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$f" >/dev/null
echo "uploaded $name"
done
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ test/test_report.json
.direnv/
build/
test/
panel-fw/
69 changes: 53 additions & 16 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ set(AUDIO_SPDIF_SOURCES
lib/BlueSCSI_platform_RP2MCU/audio_spdif.cpp
)

# Front panel sources (SPI slave for Ultra/Ultra Wide, I2C slave for v2;
# panel_spi.cpp / panel_i2c.cpp self-gate on ENABLE_PANEL_SPI / ENABLE_PANEL_I2C).
set(PANEL_SOURCES
lib/BlueSCSI_platform_RP2MCU/panel_spi.cpp
lib/BlueSCSI_platform_RP2MCU/panel_i2c.cpp
lib/BlueSCSI_platform_RP2MCU/panel_protocol.cpp
lib/BlueSCSI_platform_RP2MCU/panel_sha256_sw.cpp
)

# SCSI2SD library sources
set(SCSI2SD_SOURCES
lib/SCSI2SD/src/firmware/scsi.c
Expand Down Expand Up @@ -396,41 +405,65 @@ elseif(BLUESCSI_TARGET STREQUAL "Pico_2_Audio_SPDIF")
set_target_properties(BlueSCSI PROPERTIES OUTPUT_NAME "BlueSCSI_Pico_2_Audio_SPDIF")

elseif(BLUESCSI_TARGET STREQUAL "Ultra")
# BlueSCSI Ultra (RP2350B with I2S audio + network)
target_sources(BlueSCSI PRIVATE ${NETWORK_SOURCES} ${AUDIO_I2S_SOURCES})
# BlueSCSI Ultra (RP2350B with I2S audio + network + front panel)
target_sources(BlueSCSI PRIVATE ${NETWORK_SOURCES} ${AUDIO_I2S_SOURCES} ${PANEL_SOURCES})
target_compile_definitions(BlueSCSI PRIVATE
BLUESCSI_ULTRA
BLUESCSI_MCU_RP23XX
ENABLE_AUDIO_OUTPUT
ENABLE_AUDIO_OUTPUT_I2S
ENABLE_PANEL_SPI
BLUESCSI_NETWORK
BLUESCSI_DAYNAPORT
BLUESCSI_RM2
CYW43_PIO_CLOCK_DIV_DYNAMIC=1
LOGBUFSIZE=65536
)
target_link_libraries(BlueSCSI PRIVATE pico_cyw43_arch_poll pico_async_context_poll)
target_link_libraries(BlueSCSI PRIVATE pico_cyw43_arch_poll pico_async_context_poll pico_sha256)
set_target_properties(BlueSCSI PROPERTIES OUTPUT_NAME "BlueSCSI_Ultra")

elseif(BLUESCSI_TARGET STREQUAL "Ultra_Wide")
# BlueSCSI Ultra Wide (RP2350B with wide SCSI + I2S audio)
target_sources(BlueSCSI PRIVATE ${AUDIO_I2S_SOURCES})
# BlueSCSI Ultra Wide (RP2350B with wide SCSI + I2S audio + front panel)
target_sources(BlueSCSI PRIVATE ${AUDIO_I2S_SOURCES} ${PANEL_SOURCES})
target_compile_definitions(BlueSCSI PRIVATE
BLUESCSI_ULTRA_WIDE
BLUESCSI_MCU_RP23XX
ENABLE_AUDIO_OUTPUT
ENABLE_AUDIO_OUTPUT_I2S
ENABLE_PANEL_SPI
RP2MCU_USE_CPU_PARITY
RP2MCU_SCSI_ACCEL_WIDE
CYW43_PIO_CLOCK_DIV_DYNAMIC=1
LOGBUFSIZE=32768
)
target_link_libraries(BlueSCSI PRIVATE pico_sha256)
set_target_properties(BlueSCSI PROPERTIES OUTPUT_NAME "BlueSCSI_Ultra_Wide")

else()
message(FATAL_ERROR "Unknown BLUESCSI_TARGET: ${BLUESCSI_TARGET}")
endif()

# Front panel is available as a runtime option (the [SCSI] EnableFrontPanel ini
# key, default off) on the BlueSCSI v2 profiles that have the SRAM for it:
# Pico (RP2040 base) and all three RP2350 profiles (Pico_2 / _DaynaPORT /
# _Audio_SPDIF). The panel needs ~16 KB of private static buffers (full-size, so
# the async I2C ISR never shares memory with a live SCSI transfer); on RP2040 +
# WiFi the CYW43 stack leaves too little SRAM, so Pico_DaynaPORT and
# Pico_Audio_SPDIF are excluded. (Pico_Audio_SPDIF also routes SPDIF out on
# GPIO17 = the panel's SCL, so they couldn't coexist there anyway.)
# Ultra/Ultra Wide use the SPI panel (ENABLE_PANEL_SPI) instead.
if(BLUESCSI_TARGET MATCHES "^Pico"
AND NOT BLUESCSI_TARGET STREQUAL "Pico_DaynaPORT"
AND NOT BLUESCSI_TARGET STREQUAL "Pico_Audio_SPDIF")
target_sources(BlueSCSI PRIVATE ${PANEL_SOURCES})
target_compile_definitions(BlueSCSI PRIVATE ENABLE_PANEL_I2C)
target_link_libraries(BlueSCSI PRIVATE pico_i2c_slave)
if(BLUESCSI_TARGET MATCHES "^Pico_2")
# RP2350 uses the hardware SHA-256 block for the firmware-relay hash path.
target_link_libraries(BlueSCSI PRIVATE pico_sha256)
endif()
endif()

# =============================================================================
# Linker Script Configuration
# =============================================================================
Expand Down Expand Up @@ -490,17 +523,21 @@ if(PICO_PLATFORM STREQUAL "rp2040")
target_compile_definitions(BlueSCSI PRIVATE PICO_CRT0_ALLOCATE_SPACERS=0)
endif()

# RP2350 SCRATCH_Y is 4KB but the SDK default stack is only 2KB (0x800).
# The image-opening call chain (scsiDiskOpenHDDImage + SdFat + SDIO) uses ~1.5KB
# alone, and RP2350's hardware MSPLIM catches the overflow that RP2040 silently
# ignores. Increase to 3KB — not 4KB because Audio SPDIF targets place ~768 bytes
# of lookup tables in .scratch_y. Keep core 1's stack at default in SCRATCH_X.
if(PICO_PLATFORM STREQUAL "rp2350-arm-s")
target_compile_definitions(BlueSCSI PRIVATE
PICO_STACK_SIZE=0xC00
PICO_CORE1_STACK_SIZE=0x800
)
endif()
# The SDK default stack is only 2KB (0x800), and the image-opening call chain
# (switchNextImage -> scsiDiskOpenHDDImage -> cdromValidateCueSheet, plus SdFat
# and CUEParser below them) measures ~1.7KB on its own — reachable from a plain
# host-driven eject, with no front panel involved. RP2350's hardware MSPLIM
# faults on overflow; RP2040 silently corrupts .bss instead, so it needs the
# headroom at least as much. Increase to 3KB on both — not 4KB because Audio
# SPDIF targets place ~768 bytes of lookup tables in .scratch_y. Keep core 1's
# stack at default in SCRATCH_X.
# Core 1's stack must stay pinned at the 2KB default: it lives in SCRATCH_X, and
# PICO_CORE1_STACK_SIZE otherwise inherits PICO_STACK_SIZE, which overflows
# SCRATCH_X on the Audio SPDIF and DaynaPORT targets.
target_compile_definitions(BlueSCSI PRIVATE
PICO_STACK_SIZE=0xC00
PICO_CORE1_STACK_SIZE=0x800
)

# =============================================================================
# Bootloader Build
Expand Down
4 changes: 2 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
pico-sdk = pkgs.fetchFromGitHub {
owner = "bluescsi";
repo = "pico-sdk-internal";
rev = "v2.2.0-UltraSupport-rel3";
rev = "v2.2.0-UltraSupport-rel4";
fetchSubmodules = true;
hash = "sha256-C4ZCVNMlRJkDwh9h90YVmnwqFCT4ldcdHnIMskRFXhM=";
hash = "sha256-1tcaowoFu0n6ixrkALBwYrvjfnXfgEkQqUm7H7NpyGc=";
};

pico-extras = pkgs.fetchFromGitHub {
Expand Down
36 changes: 33 additions & 3 deletions lib/BlueSCSI_platform_RP2MCU/BlueSCSI_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ extern "C" {
#include <pico/bootrom.h>
#include "scsi_accel_target.h"
#include "custom_timings.h"
#ifdef ENABLE_PANEL_SPI
#include "panel_spi.h"
#endif
#ifdef ENABLE_PANEL_I2C
#include "panel_i2c.h"
#endif
#include <BlueSCSI_settings.h>
#include <minIni.h>

Expand Down Expand Up @@ -1189,6 +1195,26 @@ void platform_post_sd_card_init()
// one-time control setup for DMA channels and second core
audio_setup();
#endif // ENABLE_AUDIO_OUTPUT

#ifdef ENABLE_PANEL_SPI
// Initialize front panel SPI interface after SD card is ready
panel_spi_init();
#endif // ENABLE_PANEL_SPI
#ifdef ENABLE_PANEL_I2C
// Initialize front panel I2C slave (v2) only when enabled in the INI; it
// claims GPIO16/17 exclusively (no buttons / SPDIF on those pins).
if (g_scsi_settings.getSystem()->enableFrontPanel) {
panel_i2c_init();
}
#elif !defined(ENABLE_PANEL_SPI)
// No panel support in this build (RP2040 network/SPDIF: the panel's
// buffers don't fit alongside CYW43, and SPDIF out shares the panel's SCL
// pin). Say so instead of silently ignoring the setting.
if (g_scsi_settings.getSystem()->enableFrontPanel) {
logmsg("EnableFrontPanel is set, but this firmware has no front panel support");
logmsg("-- the front panel needs the Pico (non-network) build, or any Pico 2 board");
}
#endif // ENABLE_PANEL_I2C
}

bool platform_is_initiator_mode_enabled()
Expand Down Expand Up @@ -1768,6 +1794,13 @@ void platform_poll()
}
#endif

#ifdef ENABLE_PANEL_SPI
panel_spi_poll();
#endif
#ifdef ENABLE_PANEL_I2C
panel_i2c_poll(); // no-op until panel_i2c_init() runs (front panel enabled)
#endif

#if defined(ENABLE_AUDIO_OUTPUT_SPDIF) || defined(ENABLE_AUDIO_OUTPUT_I2S)
audio_poll();
#endif // ENABLE_AUDIO_OUTPUT_SPDIF
Expand All @@ -1777,9 +1810,6 @@ void platform_reset_mcu()
{
watchdog_reboot(0, 0, 2000);
}
bool platform_has_i2c() {
return is2023a;
}
bool disable_i2c = false;
void platform_disable_i2c() {
gpio_conf(GPIO_I2C_SCL, GPIO_FUNC_SIO, true, false, false, false, false);
Expand Down
11 changes: 11 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/BlueSCSI_platform_gpio_ultra.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,14 @@
// Read SCSI data bus
#define SCSI_IN_DATA() \
(~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT

// Front panel SPI interface (SPI0 on expansion header)
#ifdef ENABLE_PANEL_SPI
#define PANEL_SPI spi0
#define PANEL_SPI_RX 32
#define PANEL_SPI_CS 33
#define PANEL_SPI_SCK 34
#define PANEL_SPI_TX 35
#define PANEL_DMA_IRQ_IDX 3
#define PANEL_DMA_IRQ_NUM DMA_IRQ_3
#endif
11 changes: 10 additions & 1 deletion lib/BlueSCSI_platform_RP2MCU/BlueSCSI_platform_gpio_ultra_wide.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,13 @@ static inline bool scsi_check_parity_16bit(uint32_t w)
#define SCSI_IN_DATA() \
(~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT


// Front panel SPI interface (SPI1 on GPIO 44-47)
#ifdef ENABLE_PANEL_SPI
#define PANEL_SPI spi1
#define PANEL_SPI_RX 44
#define PANEL_SPI_CS 41
#define PANEL_SPI_SCK 46
#define PANEL_SPI_TX 47
#define PANEL_DMA_IRQ_IDX 0
#define PANEL_DMA_IRQ_NUM DMA_IRQ_0
#endif
Loading