Skip to content

GEOS 128 launches from the TR on a C128 with the USB mouse, no disk drive - #76

Merged
SensoriumEmbedded merged 17 commits into
SensoriumEmbedded:mainfrom
kfox:geos128-launch
Oct 29, 2026
Merged

SensoriumEmbedded merged 17 commits into
SensoriumEmbedded:mainfrom
kfox:geos128-launch

Conversation

@kfox

@kfox kfox commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

GEOS 128 launches from the TR on a C128 the way GEOS 64 does on a C64: from the user's own boot disk image, with the USB mouse, and with no disk drive or crt. Stacked on #75 and #74, so until they merge the diff here includes them.

What the user does. Put the GEOS 128 .D81 in /GEOS128 on the SD card, then pick GEOS 128 +C128 under Utilities on a C128. As with GEOS 64, the TR patches a -TR copy and GEOS saves to that copy. The row shows on every machine; on a C64, picking it from the menu, the app or an NFC tag is refused with "GEOS 128 needs a C128", because the boot ROM can't run there.

How it starts

  • The TR builds a small C128 function ROM (tools/geos/geos128boot.asm) in RAM and starts as a C128 cartridge.
  • The ROM pulls the patched boot image page by page into bank 1 through a new block device command, BOOT ($05), then enters it as GEOS's own ROM loader does. If the image can't be read, the border flashes.
  • GEOS 128 uses a C128 build of the TR disk driver, which brings I/O in through the MMU and holds the CPU at 1 MHz while it talks to the block window.
  • The ROM's chips are listed like a C128 crt's, so the GeoRAM preload no longer lands on them.
  • Launching no longer copies the menu record over the ROM.
  • GeosReadFile, GeosBuildBootImage and the clock helpers are FLASHMEM: with a second caller they are no longer inlined, and would otherwise sit in ITCM.

Verified on hardware on a TR+ in an NTSC C128, with no IEC drive and with a function ROM board in U36:

  • Boots to the DeskTop in 40 and in 80 columns.
  • The USB mouse works in both.
  • GeoWrite 128 saves a document that reopens after a reset and relaunch.
  • 10 of 10 launches reach the DeskTop with no power cycle.
  • A C128 crt boots normally afterwards.
  • GEOS 64 still reaches its DeskTop.

Host tests. geos128boot-run.test.mjs runs the committed ROM on the 6502 emulator with the C128's MMU and shared RAM, against a block device that follows docs/TR_Block_Device.md. It checks that every page lands in bank 1 and GEOS is entered. It also covers a busy device, a stale result, glitched reads, a refused page and a device that never answers. npm test passes 299 of 299.

Reviewed at medium per change, with each review's fix commit at low, then a final medium pass over the branch. After the maintainer's review, a medium pass over #75 and this PR together added a test that LaunchGeos refuses GEOS 128 on a C64 before anything else, now that the row is listed there; that test fails with the check removed.

Memory, from CI's build of this head against main: RAM1 code grows 64 B on the TR+ and 128 B on the TR; RAM1 variables, free-for-locals and RAM2 are unchanged.

kfox added 2 commits October 27, 2026 08:27
Each GEOS target now names its own TR disk driver, the start-up runs that set its date and time, and where its boot file keeps the C128 reset key. GeosBuildBootImage reads the boot file, swaps the transport, breaks the reset key and sets the clock. GeosBuildBootPrg wraps that image in GEOS 64's BASIC stub, as before.

GEOS 128 sets the clock and the date in one 44-byte run, behind a store of zero that the patched run takes over to load X. Its 1581 driver keeps NewDisk after ReadBlock. The transport swap no longer requires the routines it replaces to lie inside the run it overwrites, since one left outside is only unused. The checks that nothing it leaves alone points into the run stay.

CONFIGURE is turned off for any auto-exec file whose name contains CONFIG, which catches GEOS 128's "128 CONFIGURE", "128 CONFIG 2.0r" and "64 CONFIG 2.0r".

GEOS 64's boot PRG and patched disk are byte-identical to before on the stock GEOS 64 D81.

@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 GEOS 128, booting it straight from the SD card over the TR block device with the mouse working in both column modes is a real milestone, and the function ROM running under the emulator in the test suite is a lovely touch.

Memory: +1,632 B of RAM1 code (ITCM) on the TR+ and +1,696 B on the TR, nearly all of it GEOS helpers that stopped inlining once they gained a second caller (item 2). Variables, free-for-locals and RAM2 are unchanged on both boards; flash grows by 2.5 KB on the TR+ and 3.5 KB on the TR. MinimalBoot and the extension image are unchanged.

There are 2 things I'd like you to look at before merging. Inline suggestions are attached for everything except GeosReadFile, whose line isn't in this PR's diff.

1. Let the GEOS 128 row show on every machine (Source/Teensy/Teensy.ino:560, Source/Teensy/MainMenuItems.h:190)

I'd rather not hide the row on a C64, for four reasons:

  • It's one more thing to remember on menu edits: TeensyDirItemCount drops whatever row is last in dirUtilities, and only the comment keeps GEOS 128 there. A row added below it would swap places with it silently.
  • The remote and NFC paths can still reach it: a remote launch by name goes through RemoteControl.ino:257, which counts the table's full size, so the listing isn't really a barrier.
  • There's already a catch behind it for non-128 systems: LaunchGeos refuses with "GEOS 128 needs a C128" and the menu stays where it was.
  • It's advertising: a C64 owner browsing Utilities gets to see that the TR does GEOS 128.

Suggested fix: remove TeensyDirItemCount and put its three call sites (DriveDirLoad.ino, FileTransfer.ino, nfcScan.ino) back to Size/sizeof(StructMenuItem), drop the "last: hidden" comment and the NFC-count test in menu-selection.test.js (the SetMenu test stays), and keep HostIsC128 for the launcher gate. Then name the row the way the other setup-specific rows are named, with the suffix aligned like +SwiftLink and +REU: "GEOS 128 +C128". The two sentences in the description about the row being hidden would go with it.

Feel free to argue your point to keep it, I'm flexible. :)

2. Three GEOS helpers moved from flash to ITCM (Source/Teensy/GEOS/GeosBoot.h:113, :278, :348)

GeosBuildBootImage (876 B), GeosReadFile (352 B) and Geos128SetClock (316 B) were inlined into FLASHMEM callers before this PR. With GeosBuildC128Boot as a second caller they're emitted out of line, and none carries FLASHMEM, so all three sit in ITCM on both boards: 1,544 B.

Suggested fix: FLASHMEM static ... on each. All three run from the launcher in the main loop, never from an interrupt, and the host harnesses already see an empty FLASHMEM from GeosDiskPatch.h. Saves 1,544 B of ITCM on each board and brings the PR's ITCM cost under 200 B.

Hardware testing still to do, all low risk and low priority: for one of us to possibly get to before release, not gates, and anything I noted that isn't listed is covered by your follow-ups or fine as it is

Your runs on the NTSC C128 cover a lot: DeskTop in 40 and 80 columns with the USB mouse in both, GeoWrite 128 saving and reopening after a reset and relaunch, 10 launches in a row without a power cycle, a C128 cartridge booting afterwards, GEOS 64 still booting, and the row hidden on a C64.

  • A PAL C128.
  • A plain TeensyROM (not +) in a C128.
  • The DeskTop showing the TR's date and time. The patched start-up run ends with X at 0 where the stock code left X alone, and the DeskTop clock is the quickest way to see that nothing after the run minds.
  • Saving and reloading in 80 columns, with the 2 MHz clock in play around the driver's 1 MHz switch.
  • A remote launch of GEOS 128 from the app on a C128.
  • The failure paths: no card, no /GEOS128, a D81 without the boot files, and a second launch after a successful one.
  • Mouse replug and a card pull during a GEOS 128 session.
  • After item 1: GEOS 128 picked on a C64 from the menu, the app and an NFC tag shows the refusal and leaves the menu usable; the renamed row launches on a C128.
  • After item 2: GEOS 64 and GEOS 128 both still launch.

Comment thread Source/Teensy/GEOS/GeosBoot.h Resolved
Comment thread Source/Teensy/GEOS/GeosBoot.h Resolved
Comment thread Source/Teensy/Teensy.ino Resolved
Comment thread Source/Teensy/DriveDirLoad.ino Resolved
Comment thread Source/Teensy/FileTransfer.ino Resolved
Comment thread Source/Teensy/nfcScan.ino Resolved
Comment thread Source/Teensy/MainMenuItems.h Resolved
@kfox
kfox marked this pull request as draft October 29, 2026 18:02
kfox added 14 commits October 29, 2026 13:03
Travis's review of SensoriumEmbedded#75: the GEOS 64 clock helpers keep FLASHMEM now that they are reached through function pointers.
trdrive.asm assembled with C128 defined brings I/O in through the MMU ($FF00=$7E) instead of $01, and holds the CPU at 1 MHz ($D030) while it talks to the block window. DoneWithIO puts both back. trdrive.py --c128 emits it as TRDriver128.h. The GEOS 64 build and TRDriver.h are unchanged.
…image

BOOT ($05) reads page ADDR1:ADDR0 of a boot image into the window as READ does, padding a short last page with zeros. Past the end it fails with $E2, and with no boot image with $E1. TRBlockMount takes the boot image with the disk image, and it goes with that image. GEOS 64 starts from a PRG and mounts none.
geos128boot.asm is SensoriumEmbedded#63's ROM stub without the unpacker. The cold start takes SEI first and copies the loader into the RAM both banks share. The loader pulls the boot image page by page through BOOT into bank 1 at 1 MHz, with the screen off, then enters it as GEOS's own ROM loader does. If the image cannot be read, the border flashes. The top of ROMH holds the CPU vectors: IRQ restarts the cold start and NMI returns. geos128boot.mjs emits Geos128Boot.h with the offsets where the firmware writes the load address, page count and entry.
The GEOS 128 row follows GEOS 64. It is shown only once the menu has reported a C128, and selecting it on a C64 is refused, because the boot ROM cannot run there. The launch builds the boot ROM's two chips in RAM_Image with the boot image after them. It tells the ROM where that image loads, how many pages it has and where to enter it, and mounts the copy with the image as BOOT's to serve. Then it starts as a C128 cart. A boot file that would reach the shared RAM below $2000 or I/O at $D000 is refused. The GEOS 128 target names its deskTop, mouse driver, disk driver, clock run and reset key.
It runs the documented NMOS opcodes, decimal mode included, against a bus
the test supplies, so a test can mock a cartridge's registers and run the
real ROM code against them in CI. It passes Klaus Dormann's 6502 functional
test (not committed: GPL-3).
…k device

The committed ROM is run on the 6502 emulator, with the C128's MMU and
shared RAM and a block device that follows docs/TR_Block_Device.md. The
tests check that it loads every page into bank 1 and enters GEOS. They
also cover a busy device, a stale result, glitched reads, a refused page
and a device that never answers.
With no crt loaded, AllocRAMBanks takes RAM_Image as free, so the GeoRAM
handler, which starts before the C128 resets, loaded georam.ram over the
boot ROM and the boot image. The C128 then found no function ROM and
booted to BASIC. GEOS 128 now lists its ROM as a C128 crt's two chips,
ending past the boot image, and launches through SetC128Type. GEOS 64 is
unaffected: its PRG has been transferred before the handler starts.
A random NFC pick from a TeensyROM directory counted the directory's full size, so on a C64 it could pick the GEOS 128 row the menu hides and fail with "needs a C128". It now counts the rows the menu lists.
Travis's review of SensoriumEmbedded#76: the GEOS 128 row shows on every machine, named +C128 like the other rows that need something, and LaunchGeos's refusal covers a C64; GeosReadFile, GeosBuildBootImage and Geos128SetClock keep FLASHMEM now that they have a second caller.
@kfox
kfox marked this pull request as ready for review October 29, 2026 18:13
@SensoriumEmbedded
SensoriumEmbedded merged commit 9a25262 into SensoriumEmbedded:main Oct 29, 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