Skip to content

GEOS on the TR+'s REU: Special IO "REU+USB Mouse/Disk" - #81

Merged
SensoriumEmbedded merged 19 commits into
SensoriumEmbedded:mainfrom
kfox:geos-reu
Nov 3, 2026
Merged

SensoriumEmbedded merged 19 commits into
SensoriumEmbedded:mainfrom
kfox:geos-reu

Conversation

@kfox

@kfox kfox commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

GEOS 64 and GEOS 128 run with the TR+'s REU as their RAM expansion, found and set up by GEOS's own CONFIGURE, with the TR still drive A and no IEC drive.

What the user does. On a TR+, set Special IO to REU+USB Mouse/Disk in Settings, then pick GEOS 64 or GEOS 128 as before. With any other Special IO setting GEOS runs with GeoRAM, as it does now.

How it works

  • A new TR+ Special IO handler, REU+USB Mouse/Disk, keeps the REU at $DF00 and moves the USB mouse and block device registers to $DE00. Its unused REU registers read $FF, as a 1764's do, so TRB at $DF10 tells the two layouts apart.
  • The TR disk driver, the mouse driver and the GEOS 128 boot ROM find their page: the drivers check TRB at $DF10, then $DE10. The boot ROM's page is written in by the firmware at launch.
  • The -TR copy runs the stock CONFIGURE again. Its drive probe is patched to report the TR as a 1581 at device 8, and its 1581 driver record gets the TR transport. Copies made before this change, where CONFIGURE was put aside, are set up the same way. Any other CONFIG auto-exec, or a CONFIGURE not laid out like the stock one, is still kept from running.
  • A copy's TR USB MOUSE driver is brought up to date in place at each launch, since an older one looks for the mouse where the REU now sits. One that takes a different number of blocks is refused with "its TR USB MOUSE is an old one; delete this copy".
  • Docs:
    • docs/TR_Block_Device.md describes the second page.
    • A new docs/GEOS128_Usage.md covers GEOS 128.
    • docs/GEOS64_Usage.md and the GEOS 128 page share a "RAM expansion: GeoRAM or REU" section, which says which Special IO gives which RAM expansion on which board and recommends the REU on a TR+.
    • docs/General_Usage.md lists GeoRAM, 512KB REU and REU+USB Mouse/Disk under Special IO.
    • README.md links the GEOS 128 page.

Verified on hardware on a TR+ in an NTSC C128, with no IEC drive:

  • GEOS 64 in C64 mode on the REU: CONFIGURE reports 512K (ramExpSize = 8), drive A is the TR, DeskTop up.
  • GEOS 128 on the REU in 40 and in 80 columns, with the USB mouse; a file duplicated on the DeskTop.
  • GEOS 64 and GEOS 128 on GeoRAM still reach the DeskTop, and the duplicated file is there after relaunching.
  • NTSC read margin, per Constraints.md: a Dbg_SerTimChg build with a C64-mode loop reading the REU's address and length registers back, $DF10-$DF1F, and the TR page's TRM/TRB, 8 s at each step (1,400 to 5,100 rounds a second, by layout), while nS_PLAprop is raised. From the default of 150, the stock 512KB REU handler first fails at 260, this handler at 250 (one error at 240), and GeoRAM's TR page, as shipped, at 240.

Host tests. The disk and mouse drivers run on the 6502 emulator against a modeled REU and GeoRAM page, and the boot ROM with either page. The disk patcher tests cover the probe and driver records, the retyping, a copy set up by an earlier release, and the mouse refresh and its refusals. New harnesses cover the handler's IO2 page (the REU's registers, $FF across the rest of each 32 bytes, nothing without an REU) and which handler a GEOS launch runs under; each fails with the code it covers removed. npm test passes 337 of 337 on macOS, and under GCC in a linux/amd64 node:24 container 295 pass with 8 skipped for tools and disks it lacks. The firmware builds for TR and TR+ in the same container.

Reviewed at medium per change, with each review's fix commit at low, then a final medium pass over the branch, then a pass by Auditor, Adversary, Steward and Pragmatist lanes per the guidance in #79, whose fix commits got a low review. The docs commit was reviewed at low against the code; it changes no code (git diff c9fceda3 5c8de5b7 -- ':!*.md' is empty).

Memory, from CI's build of this head against main's: RAM1 code grows 768 B on the TR+, almost all of it the new handler's bus-cycle code (its IO2 reads share the stock REU's path); the TR's is unchanged. RAM1 variables, free-for-locals and RAM2 are unchanged.

kfox added 15 commits October 29, 2026 19:54
… beside an REU

Under an REU the registers move to $DE00, since the REU's own repeat
across $DF00-$DFFF. A copy of the driver lives on the user's disk, in
CONFIGURE's 1581 driver, and outlives a change of Special IO, so it
looks for the TRB id at $DF10 and then $DE10 on every transfer instead
of being told the page when it is installed. $DF10 comes first because
$DE10 is GeoRAM's window, which can hold any bytes; an REU's unused
registers read $FF there.
…r beside an REU

Beside an REU, $DF03-$DF05 are its address registers, which hold
whatever the last transfer left and so can read "TRM". The block
device's TRB id at $DF10 picks the page instead: an REU's unused
registers read $FF there, and GeoRAM's window at $DE10 is never asked.
… launch gives it

The boot ROM is built at every launch, which knows which handler it
starts, so the firmware writes the page into the ROM instead of the ROM
searching for it: a glitch-safe search does not fit in the one page the
cold start copies. geos128boot.mjs assembles the ROM at two pages and
lists the bytes that move, as trdrive.py does for relocations. The
launch still asks for the page beside GeoRAM.
…R as drive A

CONFIGURE is what finds an REU and sets GEOS up to use it, so it has to
run. Its drive-type probe now answers a 1581 at device 8 and nothing
elsewhere, and the 1581 driver it installs for drive A, its record 4,
gets the TR transport the boot image already has.

The stock file is known by the target's name for it and by the probe in
record 0, so a copy made before this, where CONFIGURE was retyped, is set
up the same way. Anything else named CONFIG is still kept from running,
including a CONFIGURE whose records are not laid out as the stock one.

The synthetic test disk has one directory sector; the old CONFIGURE test
put a ninth file past it, so its check that CONFIGURE.DOC stays as it
was looked at a slot no directory held.
The copy outlives the firmware that made it, and a mouse driver from
before the REU route reads $DF03 for its ID, which beside an REU is an
address register. The driver is rewritten in place over its own blocks;
one taking another number of blocks is refused with a message to delete
the copy, since growing it means allocating on the user's disk.
The REU answers at $DF00 as before, and the USB mouse and block device
move to IO1, the page an REU leaves free. A GEOS launch takes this
handler when it is the Special IO setting, and GeoRAM otherwise, and
builds the GEOS 128 boot ROM for the page that handler puts the
registers on.

Beside the REU, $DF0B-$DF1F read $FF, as a 1764's unused registers do,
so the TRB id the GEOS drivers look for at $DF10 cannot appear there.
…d not at all without an REU

The TR's GEOS drivers decide which page holds the block device by what $DF10 reads, and no test ran IO2Hndlr_REU_TRPage itself: dropping its $FF answer, its bank check or its 32-byte mirror left the suite green.
…osen for

Both the C128 boot ROM's register page and the handler come from GeosIOHandler(), and no test held either: dropping IOHandlerSelectInit's GEOS branch puts GEOS under the item's GeoRAM while the boot ROM looks beside the REU, and the suite stayed green.
Giving GeosIsDriverAt and GeosReplaceTransport a second caller took them out of their inlined callers and into ITCM, 334 bytes on both TR and TR+, and the probe patterns and CONFIGURE names landed in DTCM. The build's memory report showed it; marking them FLASHMEM and PROGMEM, with GeosFindOnce and GeosBytesInBlock beside them, returns 320 bytes of RAM1 code on each target and leaves the TR's below where it was before the REU change.
@kfox
kfox marked this pull request as draft October 30, 2026 04:05
@kfox
kfox marked this pull request as ready for review October 30, 2026 04:08

@SensoriumEmbedded SensoriumEmbedded left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for bringing GEOS to the TR+'s REU, running GEOS 128 with the REU and the TR as drive A is great to see! :)

I reviewed this merged onto current main (136baa8) and built both targets. As always, feel free to push back on any that are inappropriate or would take significant memory/code to resolve.

Memory: on the TR+, RAM1 code grows 768 B (the padding absorbs it, free for local variables is unchanged), almost all of it the new handler's bus-cycle code. Plain TR grows 64 B. Flash grows about 2.1 KB on the TR+ and 1.4 KB on the TR.

There are 3 (small) things I'd like you to look at before merging. A test fix for Windows, a simpler handler order, and getting some read time back on the PHI2 path, suggestions are in-line.

1. The REU+USB harness test fails on Windows (Source/Teensy/tests/reu-trpage.test.js:36)

The test splits the harness output on \n, and a MinGW build writes CRLF, so both new tests fail under npm test on Windows.

Suggested fix: split(/\r?\n/), the same as 518a2cf.

2. Handler order (Source/Teensy/MinimalBoot/Common/Menu_Regs.h:446, Source/Teensy/MinimalBoot/Common/IOHandlers.h:166)

Putting IOH_REU_TRPage just above IOH_GeoRAM leaves GeoRAM as the last selectable handler on both boards, so the second IOH_GeoRAM line and the gate on LastSelectableIOH aren't needed. IOHandler[] moves to match. Only GeoRAM's number on the TR+ moves, and GeoRAM hasn't been in a release yet.

3. REU register reads take longer under REU+USB (Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c:787)

IO2Hndlr_REU_TRPage runs 10 instructions (two of them taken branches) before it reaches the shared REU read code, against 4 for the stock REU: about 7-12 nS at 816 MHz on every REU register read. Your sweep fits that (this handler first fails at nS_PLAprop 250 with one error at 240, the stock REU at 260), though its loop also read $DF10-$DF1F, so it doesn't say which path failed first. Constraints.md's "An IO1 read has to put its data on the bus early" asks to keep that path as short as possible.

Suggested fix: drop the wrapper, point the new handler's IO2 slot at &IO2Hndlr_REU, and answer the unused registers in IO2Hndlr_REU itself. Two lines in IOH_REU.c are outside the diff, so they're here rather than in-line. With the other externs (after fSpecialBtnChange, around line 77), since IOH_REU.c is included before the sketch defines it:

extern uint8_t CurrentIOHandler;

and in the read branch (line 563):

      if (Address < REUReg_NumRegs) DataPortWriteWaitLog(REURegs[Address]);  
      else if (CurrentIOHandler == IOH_REU_TRPage) DataPortWriteWaitLog(0xff); // a 1764's unused registers; the TR's GEOS drivers look for the block device at $DF10 first

I built this for the TR+: a read of a real register compiles to the same instructions as the stock REU on main, and the $FF reads take a few instructions more than in the wrapper, which only the drivers' probe reads. Final Cartridge III, Retro Replay and Super Snapshot, which also use IO2Hndlr_REU, are unchanged, and ITCM drops 56 B. The REU+USB harness then takes the real IOH_REU_TRPage from Menu_Regs.h (suggestion in-line); the test passes on Windows.

Hardware testing: Just to document. I'll hit these at some point, not a request or requirement! :)

Your tests on a TR+ in an NTSC C128 cover GEOS 64 and GEOS 128 (40 and 80 columns) on the REU with the mouse and the TR as drive A, GEOS 64 and 128 still on GeoRAM, and the NTSC read-margin sweep.

  • PAL: REU transfers under GEOS 128 on a PAL C128, and GEOS 64 on PAL
  • GEOS 64 on a C64
  • A -TR copy made by current main, launched under REU+USB and under GeoRAM
  • REU save during GEOS (Special button), then reloaded on the next launch
  • Remote launch and NFC with REU+USB selected
  • Non-GEOS REU software with REU+USB selected
  • Special IO in Settings wraps both ways onto the new entry, and the choice survives a power cycle
  • Plain TR: GEOS 64 and 128 still run on GeoRAM
  • The GEOS 128 failure paths

Thank you!

Comment thread Source/Teensy/tests/reu-trpage.test.js Resolved
@kfox

kfox commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, Travis! All three are in: 96d5141 (CRLF), 39c6244 (handler order), f0ceb18 (REU reads through the stock path). CI is green.

On the bench TR+ in the NTSC C128, with the Docker build of f0ceb18:

  • Special IO in Settings wraps both ways: from GeoRAM, a gives None, and from None, A gives GeoRAM, then REU+USB Mouse/Disk, then Debug.
  • GEOS 128 in 80 columns boots to the desktop under REU+USB, with the TR as drive A.

On CI's merge build, TR+ RAM1 code is +768 B over main and the TR's is unchanged. I've updated that line of the description.

@SensoriumEmbedded

Copy link
Copy Markdown
Owner

Thank you Kelly, looks great! :)
Thanks for the other updates and extra testing, much appreciated!

@SensoriumEmbedded
SensoriumEmbedded merged commit 12419a3 into SensoriumEmbedded:main Nov 3, 2026
4 checks passed
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.

2 participants