Skip to content

firmware: the block device maps sectors through the shared disk image engine - #89

Draft
wesbiggs wants to merge 8 commits into
SensoriumEmbedded:mainfrom
wesbiggs:shared-disk-blockdev
Draft

wesbiggs wants to merge 8 commits into
SensoriumEmbedded:mainfrom
wesbiggs:shared-disk-blockdev

Conversation

@wesbiggs

@wesbiggs wesbiggs commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

This is part 2 of a series of PRs to streamline and consolidate disk image (.d64/.d71/.d81) access, which will then be used to power the VDrive Special IO. It moves the TR block device onto the shared engine from #88.

Depends on #88. Only the last commit (f2e7a9e0) is new here, so please review that one. Once #88 merges, I'll rebase this branch onto main and it will shrink to that commit.

Rationale

TRBlockImage did its own track/sector arithmetic on top of the GEOS installer's format table, which knows only the three plain image sizes. A D64 with the error bytes some copiers append, or a 40-track D64, therefore mounted as no image.

State of affairs after this PR

The block device backend opens the image with the engine (d64_open_exact, added here) and reads and writes through d64_block_read/d64_block_write, so its geometry is the one VDrive uses. D64s with error bytes and 40-track D64s now mount. The error table is left unread: the driver has no status for a bad sector, so it gets the sector's bytes, as a drive that read it anyway would give them. A file of any other size still mounts as no image. The GEOS installer still has its own copy of the image code; the next PR moves it over.

Memory

  • The new backend functions are FLASHMEM, and the engine's are VD_COLD, which is FLASHMEM on the firmware. None of it is on the PHI2 path: block transfers run from the polling handshake, as before.

  • The one table added (d64_open_exact's track counts) is PROGMEM.

  • No malloc and no new RAM2 use. The image state lives in the existing static TRBlockImg, which now holds the engine's d64_t in place of a format pointer.

  • RAM, from the link report of the shipping builds (npm run build:tr, npm run build:tr-plus, so the TR+ carries the extension loader), at main (d2faddce) and at this commit:

    Image RAM1 free for local variables RAM1 padding RAM2 variables
    TR main 122748 → 122748 (no change) 6136 → 6008 (ITCM code +128) 17600 → 17600
    TR+ main 79868 → 79836 (−32, variables +32) 25864 → 25864 17600 → 17600

    MinimalBoot (TR and TR+) and the TR+ extension image report the same FLASH, RAM1 and RAM2 numbers before and after.

Docs

  • docs/TR_Block_Device.md: which image files mount, that error bytes are not read, and the 40-track D64's sectors per track.
  • docs/Architecture/Disk-Images.md: d64_open_exact, and the block device as the engine's first caller.

Testing

  • Source/Teensy/tests/trblock.test.js now runs the block device on a 40-track D64, and on a D64 and a D81 with error bytes, alongside the plain D64, D71 and D81. Truncated and padded files, the D81 with error bytes included, mount as no image.
  • Source/Teensy/tests/disk-image.test.js checks that d64_open_exact takes only the sizes in the table, with or without error bytes, and nothing between them.
  • npm test passes at this commit.
  • Host build only, not hardware tested. A GEOS launch beside GeoRAM, or on the TR+ under REU+USB Mouse/Disk, goes through this path.

Compatibility

The C64-side contract in docs/TR_Block_Device.md doesn't change: INFO still reports 1 D64, 2 D71, 3 D81, and the status codes are as before. A 40-track D64 reports as a D64 with its larger block count. No change to the remote protocol (docs/ControlComms.md), the cartridge register map, the EEPROM settings or any timing.

License

My own work, MIT licensed like the rest of the repository.

🤖 Generated with Claude Code

…VDrive will share

The GEOS installer and the TR block device each carry their own image
geometry, BAM and directory code, and VDrive brings a third, more
complete engine (40-track D64s, error bytes, BAM and directory growth
for all three kinds, tested against c1541). Bring VDrive's engine in
under Source/Teensy/DiskImage so the other two can move onto it.

Nothing uses it yet. The copy differs from teensyrom-vdrive's d64/ only
in what the firmware needs: SPDX lines, the placement macros moving into
d64.h, and its three constant tables marked D64_FLASH so they land in
PROGMEM rather than DTCM. DiskImage.h includes the code once for the
single translation unit the firmware and the host harnesses build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5t9wWXadFkQgKaCnjreF7
wesbiggs and others added 7 commits November 6, 2026 15:47
…a 1541 does

The allocator searched outward from the file's last sector and reported the
disk full when it reached the end of the allocation order, so a file
appended to after a scratch freed earlier tracks failed with blocks free.
A hint on the directory track, D71 track 53 or D64 tracks 36-40 failed on
its first new block. The search now restarts once from the start of the
order before it reports FULL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… write does

When the last data sector or the directory entry could not be written,
d64_close_write returned the error and left the entry unclosed with its
blocks allocated. A new file or a copy left a splat file and leaked blocks,
contrary to d64_copy's "nothing is left behind"; an append left the original
file unclosed, so the next validate deleted all of it. The close now aborts
the write as d64_write does on an error, which puts an appended file back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… does

d64_create with replace scratched the old file before writing the new one,
so a replace that ran out of room lost the old file. The old entry is now
kept in the write handle and scratched once the new file has closed; a
write that fails leaves it as it was. Both files need room at once, so a
replace can now fail on a nearly full disk where scratching first would
have fit, which is also what a 1541 does. A locked file is refused before
anything is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ritten

d64_create now checks the lock itself instead of relying on the scratch
it no longer does first; without the check the new file is written and
the close fails with both files left on the disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… image

It returned 0 both when the buffer was too small and when the header,
directory or BAM could not be read, so a caller could only report an
unreadable image as a buffer problem. It now returns the length, the new
D64_E_BUF for a small buffer, or the read's own error (D64_E_IO,
D64_E_LOOP for a looping directory chain).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It skips d64_format's clearing pass, so on a used image the old data stays
in every sector it does not write. The comment said what it writes but not
that the caller must guarantee the rest is already zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… engine

TRBlockImage did its own track/sector arithmetic on top of GEOS's format
table, which knows only the three plain sizes. A D64 with the error
bytes copiers append, or a 40-track D64, therefore mounted as no image.

The backend now opens the image with d64_open_exact and reads and writes
through d64_block_read/d64_block_write, so those images mount and the
geometry is the one VDrive uses. The error table is left unread: the
driver has no status for a bad sector, so it gets the sector's bytes.
Files of any other size still mount as no image; the harness now reads
one byte past the largest image so an oversized file cannot pass as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5t9wWXadFkQgKaCnjreF7
@wesbiggs
wesbiggs force-pushed the shared-disk-blockdev branch from f2e7a9e to ddd9bac Compare November 6, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant