diff --git a/README.md b/README.md index bac04eb2..e922cdb2 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Compatible with C64 and C128 machines/variants, NTSC and PAL supported. * **[NFC Loading System](docs/NFC_Loader.md)** * **[TeensyROM Web Browser](docs/Browser_Usage.md)** * **[GEOS 64](docs/GEOS64_Usage.md)** + * **[GEOS 128](docs/GEOS128_Usage.md)** * **[Custom BASIC Commands](docs/Custom_BASIC_Commands.md)** * **[TR Host Extensions (TR+ only)](docs/TR_Host_Extensions.md)** * **[USB/Ethernet Remote Command Protocol](docs/ControlComms.md)** diff --git a/Source/Teensy/GEOS/Geos128Boot.h b/Source/Teensy/GEOS/Geos128Boot.h index 7c87ac4c..27d86070 100644 --- a/Source/Teensy/GEOS/Geos128Boot.h +++ b/Source/Teensy/GEOS/Geos128Boot.h @@ -32,3 +32,7 @@ static const uint8_t Geos128BootRoml[245] PROGMEM = { static const uint8_t Geos128BootVectors[7] PROGMEM = { 0x40, 0xf9, 0xff, 0xf9, 0xff, 0x0a, 0x80 }; +static const uint16_t Geos128BootPageAt[11] PROGMEM = { + 0x0077, 0x007c, 0x0081, 0x008e, 0x00a1, 0x00a4, 0x00b1, 0x00b4, + 0x00bf, 0x00c4, 0x00c7 +}; diff --git a/Source/Teensy/GEOS/GeosBoot.h b/Source/Teensy/GEOS/GeosBoot.h index abb4d6ce..5450c1a2 100644 --- a/Source/Teensy/GEOS/GeosBoot.h +++ b/Source/Teensy/GEOS/GeosBoot.h @@ -15,12 +15,18 @@ #define GEOS_AUTO_EXEC 14 #define GEOS_SYSTEM_FILE 4 #define GEOS_CONFIG_NAME "CONFIG" +#define GEOS_VLIR 1 +#define GEOS_PROBE_RECORD 0 +#define GEOS_DRIVER_RECORD 4 #define GEOS_BASIC_START 0x0801 #define GEOS_MEMORY_END 0x10000 #define GEOS_MAX_FILE_BLOCKS 3200 #define GEOS_DEVICE_8 8 #define GEOS128_SHARED_RAM_END 0x2000 // the C128 boot ROM's loader runs in the RAM below, which both banks share #define GEOS128_IO_START 0xd000 // and writes bank 1 with I/O mapped in from here up +// The block device's page: beside GeoRAM, or beside the REU, whose registers repeat across $DF00-$DFFF +#define GEOS_REGS_BESIDE_GEORAM 0xdf +#define GEOS_REGS_BESIDE_REU 0xde enum GeosDriverVector { VecInitForIO, VecDoneWithIO, VecExitTurbo, VecPurgeTurbo, VecEnterTurbo, VecChangeDiskDevice, VecNewDisk, VecReadBlock, VecWriteBlock, VecVerWriteBlock, VecCount = 10 }; @@ -50,7 +56,7 @@ static bool GeosInDriverArea(uint16_t Address) { return Address >= GEOS_DRIVER_A // Whether a stock disk driver's tables start here: the vectors and the jump slots all point into the driver, and // the type byte names a 1541, 1571 or 1581 -static bool GeosIsDriverAt(const uint8_t *At) +FLASHMEM static bool GeosIsDriverAt(const uint8_t *At) { uint8_t Type = At[GEOS_TYPE_BYTE]; @@ -79,7 +85,7 @@ static int32_t GeosFindDriver(const uint8_t *Data, uint32_t Size) // The TR's driver goes over the stock driver's code from InitForIO up to ReadBlock, provided no vector or jump slot // it leaves alone points there, and every vector it implements is pointed at its own routines. Replaced routines // outside that run stay where they are, unused: GEOS 128 keeps NewDisk after ReadBlock. -static bool GeosReplaceTransport(uint8_t *Driver, const struct GeosDiskDriver *With) +FLASHMEM static bool GeosReplaceTransport(uint8_t *Driver, const struct GeosDiskDriver *With) { uint16_t Start = GeosWord(Driver + 2 * VecInitForIO), End = GeosWord(Driver + 2 * VecReadBlock); uint8_t *Code = Driver + (Start - GEOS_DRIVER_AREA); @@ -109,6 +115,71 @@ static bool GeosReplaceTransport(uint8_t *Driver, const struct GeosDiskDriver *W return true; } +// CONFIGURE's drive-type probe, which asks each IEC device what drive it is +static const uint8_t GeosStockProbe[] PROGMEM = { 0xa9, 0xe5, 0x85, 0x03, 0xa9, 0x80, 0x85, 0x02, 0x20 }; +// What the TR puts over it: drive A, at device 8, is a 1581, and no other drive is there +static const uint8_t GeosTRProbe[] PROGMEM = { 0xa9, 0x00, 0xae, 0x89, 0x84, 0xe0, 0x08, 0xd0, 0x02, 0xa9, 0x03, 0x60 }; + +// Finds Pattern once in Body; NULL when it is absent or ambiguous +FLASHMEM static uint8_t *GeosFindOnce(uint8_t *Body, uint32_t Length, const uint8_t *Pattern, uint32_t Size) +{ + uint8_t *Found = NULL; + + for (uint32_t i = 0; i + Size <= Length; i++) + { + if (memcmp(Body + i, Pattern, Size)) continue; + if (Found) return NULL; + Found = Body + i; + } + return Found; +} + +FLASHMEM static uint32_t GeosBytesInBlock(const uint8_t *Block) { return Block[0] ? GEOS_DATA_PER_BLOCK : (Block[1] > 1 ? Block[1] - 1 : 0); } + +// A file's bytes along its sector chain from At; What names it in a refusal +FLASHMEM static bool GeosReadChain(struct GeosDisk *Disk, struct GeosTS At, const char *What, uint8_t *Out, + uint32_t Capacity, uint32_t *Length) +{ + uint8_t Block[GEOS_SECTOR_SIZE]; + + *Length = 0; + for (int Blocks = 0; At.Track; Blocks++) + { + uint32_t Used; + if (Blocks > GEOS_MAX_FILE_BLOCKS || At.Track > Disk->Format->Tracks) + { + snprintf(Disk->Error, sizeof Disk->Error, PSTR("%s's chain is broken"), What); + return false; + } + if (!GeosReadTS(Disk, At.Track, At.Sector, Block)) return false; + Used = GeosBytesInBlock(Block); + if (*Length + Used > Capacity) + { + snprintf(Disk->Error, sizeof Disk->Error, PSTR("%s is too large"), What); + return false; + } + memcpy(Out + *Length, Block + 2, Used); + *Length += Used; + At = (struct GeosTS){ Block[0], Block[1] }; + } + return true; +} + +// Writes Data back over the chain GeosReadChain read it from +FLASHMEM static bool GeosRewriteChain(struct GeosDisk *Disk, struct GeosTS At, const uint8_t *Data) +{ + uint8_t Block[GEOS_SECTOR_SIZE]; + + for (uint32_t Done = 0; At.Track; Done += GEOS_DATA_PER_BLOCK) + { + if (!GeosReadTS(Disk, At.Track, At.Sector, Block)) return false; + memcpy(Block + 2, Data + Done, GeosBytesInBlock(Block)); + if (!GeosWriteTS(Disk, At.Track, At.Sector, Block)) return false; + At = (struct GeosTS){ Block[0], Block[1] }; + } + return true; +} + // A file's bytes, found by name in the directory and followed along its sector chain FLASHMEM static bool GeosReadFile(struct GeosDisk *Disk, const char *Name, uint8_t *Out, uint32_t Capacity, uint32_t *Length) { @@ -130,20 +201,7 @@ FLASHMEM static bool GeosReadFile(struct GeosDisk *Disk, const char *Name, uint8 } } if (!At.Track) return GeosFail(Disk, PSTR("the boot file is not on the disk")); - - *Length = 0; - for (int Blocks = 0; At.Track; Blocks++) - { - uint32_t Used; - if (Blocks > GEOS_MAX_FILE_BLOCKS || At.Track > Disk->Format->Tracks) return GeosFail(Disk, PSTR("the boot file's chain is broken")); - if (!GeosReadTS(Disk, At.Track, At.Sector, Block)) return false; - Used = Block[0] ? GEOS_DATA_PER_BLOCK : (Block[1] > 1 ? Block[1] - 1 : 0); - if (*Length + Used > Capacity) return GeosFail(Disk, PSTR("the boot file is too large")); - memcpy(Out + *Length, Block + 2, Used); - *Length += Used; - At = (struct GeosTS){ Block[0], Block[1] }; - } - return true; + return GeosReadChain(Disk, At, PSTR("the boot file"), Out, Capacity, Length); } static bool GeosNameContains(const uint8_t *Name, const char *Part) @@ -157,9 +215,45 @@ static bool GeosNameContains(const uint8_t *Name, const char *Part) return false; } -// Gives AUTO_EXEC configuration files the type of a file GEOS leaves alone, the way the stock disk's own -// configuration probes the IEC bus and empties drive A; returns whether anything was retyped -static inline bool GeosDisableConfigure(struct GeosDisk *Disk, bool *Changed) +// Whether Slot is the target's stock CONFIGURE, set up for the TR now or before: its probe answers what the TR has +// and the 1581 driver it installs for drive A is the TR's. The driver goes first, so a write cut short leaves the +// stock probe, and next time the driver no longer looks stock and the file is put aside instead. +FLASHMEM static bool GeosSetUpConfigure(struct GeosDisk *Disk, const uint8_t *Slot, uint8_t *Scratch, uint32_t Capacity, + bool *Ours, bool *Changed) +{ + const char *Name = Disk->Target->ConfigureName; + uint8_t Index[GEOS_SECTOR_SIZE], *Driver, *At; + struct GeosTS Probe, Driven; + uint32_t ProbeLength, DriverLength; + uint16_t ReadBlock; + + *Ours = false; + if (Slot[SlotStructure] != GEOS_VLIR || !GeosSlotNamed(Slot, Name)) return true; + if (!GeosReadTS(Disk, Slot[SlotFirst], Slot[SlotFirst + 1], Index)) return false; + Probe = (struct GeosTS){ Index[2 + 2 * GEOS_PROBE_RECORD], Index[3 + 2 * GEOS_PROBE_RECORD] }; + Driven = (struct GeosTS){ Index[2 + 2 * GEOS_DRIVER_RECORD], Index[3 + 2 * GEOS_DRIVER_RECORD] }; + if (!Probe.Track || !Driven.Track) return true; + if (!GeosReadChain(Disk, Probe, Name, Scratch, Capacity, &ProbeLength)) return false; + if (GeosFindOnce(Scratch, ProbeLength, GeosTRProbe, sizeof GeosTRProbe)) return *Ours = true; + At = GeosFindOnce(Scratch, ProbeLength, GeosStockProbe, sizeof GeosStockProbe); + if (!At || At + sizeof GeosTRProbe > Scratch + ProbeLength) return true; + + Driver = Scratch + ProbeLength; + if (!GeosReadChain(Disk, Driven, Name, Driver, Capacity - ProbeLength, &DriverLength)) return false; + if (DriverLength <= GEOS_TYPE_BYTE || !GeosIsDriverAt(Driver)) return true; + ReadBlock = GeosWord(Driver + 2 * VecReadBlock); + if (ReadBlock < GEOS_DRIVER_AREA || ReadBlock > GEOS_DRIVER_AREA + DriverLength) return true; + if (!GeosReplaceTransport(Driver, Disk->Target->DiskDriver)) return true; + memcpy(At, GeosTRProbe, sizeof GeosTRProbe); + if (!GeosRewriteChain(Disk, Driven, Driver) || !GeosRewriteChain(Disk, Probe, Scratch)) return false; + *Changed = true; + return *Ours = true; +} + +// Keeps GEOS's configuration files from probing a drive that is not there: the stock CONFIGURE is set up to find +// the TR and run, and every other one stops being auto-executed. Scratch holds CONFIGURE's records meanwhile. +// Returns whether anything changed. +static inline bool GeosPrepareConfigure(struct GeosDisk *Disk, uint8_t *Scratch, uint32_t Capacity, bool *Changed) { struct GeosTS Chain[GEOS_MAX_DIR_SECTORS]; uint8_t Block[GEOS_SECTOR_SIZE]; @@ -174,10 +268,15 @@ static inline bool GeosDisableConfigure(struct GeosDisk *Disk, bool *Changed) if (!GeosReadTS(Disk, Chain[i].Track, Chain[i].Sector, Block)) return false; for (int e = 0; e < GEOS_ENTRIES_PER_DIR; e++) { - uint8_t *Slot = Block + e * GEOS_DIR_ENTRY_SIZE; - if (!(Slot[SlotType] & CBM_TYPE_MASK) || Slot[SlotGeosType] != GEOS_AUTO_EXEC) continue; + uint8_t *Slot = Block + e * GEOS_DIR_ENTRY_SIZE, Type = Slot[SlotGeosType]; + bool Ours; + if (!(Slot[SlotType] & CBM_TYPE_MASK)) continue; + if (Type != GEOS_AUTO_EXEC && Type != GEOS_SYSTEM_FILE) continue; if (!GeosNameContains(Slot + SlotName, GEOS_CONFIG_NAME)) continue; - Slot[SlotGeosType] = GEOS_SYSTEM_FILE; + if (!GeosSetUpConfigure(Disk, Slot, Scratch, Capacity, &Ours, Changed)) return false; + Type = Ours ? GEOS_AUTO_EXEC : GEOS_SYSTEM_FILE; + if (Slot[SlotGeosType] == Type) continue; + Slot[SlotGeosType] = Type; Retyped = true; } if (!Retyped) continue; @@ -294,20 +393,6 @@ FLASHMEM static void Geos128SetClock(uint8_t *Run, const struct GeosDate *Now) static const struct GeosClockRun Geos128ClockRuns[] = { { Geos128StartUp, sizeof Geos128StartUp, Geos128SetClock } }; -// Finds Pattern once in Body; NULL when it is absent or ambiguous -static uint8_t *GeosFindOnce(uint8_t *Body, uint32_t Length, const uint8_t *Pattern, uint32_t Size) -{ - uint8_t *Found = NULL; - - for (uint32_t i = 0; i + Size <= Length; i++) - { - if (memcmp(Body + i, Pattern, Size)) continue; - if (Found) return NULL; - Found = Body + i; - } - return Found; -} - // Sets the date and time GEOS starts with. Leaves the body as it is, and returns false, for a boot file whose // start-up code is not the one the target knows static bool GeosSetClock(uint8_t *Body, uint32_t Length, const struct GeosTarget *Target, const struct GeosDate *Now) @@ -389,9 +474,10 @@ static inline bool GeosBuildBootPrg(struct GeosDisk *Disk, const struct GeosDate } // The C128 boot ROM, its ROML chip then its ROMH chip, followed by the boot image it pulls through BOOT a page at a -// time. Image and ImageSize say where that image starts, without its load address, for TRBlockMount. -static inline bool GeosBuildC128Boot(struct GeosDisk *Disk, const struct GeosDate *Now, uint8_t *Out, uint32_t Capacity, - const uint8_t **Image, uint32_t *ImageSize, bool *ClockSet) +// time from the block device at RegsPage. Image and ImageSize say where that image starts, without its load address, +// for TRBlockMount. +static inline bool GeosBuildC128Boot(struct GeosDisk *Disk, const struct GeosDate *Now, uint8_t RegsPage, uint8_t *Out, + uint32_t Capacity, const uint8_t **Image, uint32_t *ImageSize, bool *ClockSet) { uint8_t *Roml = Out, *Romh = Out + GEOS128_BOOT_CHIP_SIZE, *File = Out + 2 * GEOS128_BOOT_CHIP_SIZE; uint32_t Length, Pages, Load; @@ -410,6 +496,7 @@ static inline bool GeosBuildC128Boot(struct GeosDisk *Disk, const struct GeosDat GeosPutWord(Roml + GEOS128_BOOT_PARAMS, Load); GeosPutWord(Roml + GEOS128_BOOT_PARAMS + 2, Pages); GeosPutWord(Roml + GEOS128_BOOT_PARAMS + 4, Disk->Target->BootEntry); + for (size_t i = 0; i < sizeof Geos128BootPageAt / sizeof Geos128BootPageAt[0]; i++) Roml[Geos128BootPageAt[i]] = RegsPage; *Image = File + 2; *ImageSize = Length - 2; return true; diff --git a/Source/Teensy/GEOS/GeosDiskPatch.h b/Source/Teensy/GEOS/GeosDiskPatch.h index ad827138..bd840f35 100644 --- a/Source/Teensy/GEOS/GeosDiskPatch.h +++ b/Source/Teensy/GEOS/GeosDiskPatch.h @@ -68,6 +68,7 @@ struct GeosTarget const struct GeosClockRun *ClockRuns; uint8_t ClockRunCount; uint16_t ResetKey; // where the boot file holds the C128's reset key, or 0 + const char *ConfigureName; }; struct GeosBootDisk { bool HasDeskTop, HasDriver; }; @@ -336,6 +337,20 @@ FLASHMEM static uint16_t GeosDriverBlocks(const struct GeosTarget *Target) return GeosDriverDataBlocks(Target) + 1; } +// Block i of the driver's data chain, linked to Next unless it is the last; returns how many of its bytes are used +FLASHMEM static uint16_t GeosDriverBlock(const struct GeosTarget *Target, uint16_t i, struct GeosTS Next, uint8_t *Block) +{ + uint16_t Start = i * GEOS_DATA_PER_BLOCK, Length = Target->DriverSize - Start; + bool Last = i + 1 == GeosDriverDataBlocks(Target); + + if (Length > GEOS_DATA_PER_BLOCK) Length = GEOS_DATA_PER_BLOCK; + memset(Block, 0, GEOS_SECTOR_SIZE); + Block[0] = Last ? 0 : Next.Track; + Block[1] = Last ? Length + 1 : Next.Sector; + memcpy(Block + 2, Target->DriverCode + Start, Length); + return Length + 2; +} + // Writes the driver's data chain and info block; returns the first data sector, the info sector and the block count. FLASHMEM static bool GeosWriteDriver(struct GeosDisk *Disk, struct GeosTS *First, struct GeosTS *Info, uint16_t *Blocks) { @@ -348,15 +363,8 @@ FLASHMEM static bool GeosWriteDriver(struct GeosDisk *Disk, struct GeosTS *First *First = Here; for (uint16_t i = 0; i < DataBlocks; i++) { - uint16_t Start = i * GEOS_DATA_PER_BLOCK, Length = Target->DriverSize - Start; - if (Length > GEOS_DATA_PER_BLOCK) Length = GEOS_DATA_PER_BLOCK; - bool Last = i + 1 == DataBlocks; - - if (!Last && !GeosAllocate(Disk, &Next)) return false; - memset(Block, 0, sizeof Block); - Block[0] = Last ? 0 : Next.Track; - Block[1] = Last ? Length + 1 : Next.Sector; - memcpy(Block + 2, Target->DriverCode + Start, Length); + if (i + 1 < DataBlocks && !GeosAllocate(Disk, &Next)) return false; + GeosDriverBlock(Target, i, Next, Block); if (!GeosWriteTS(Disk, Here.Track, Here.Sector, Block)) return false; Here = Next; } @@ -404,7 +412,7 @@ struct GeosSlotRef { int Sector, Slot; }; struct GeosDirScan { - struct GeosSlotRef Free, FirstInputDriver; + struct GeosSlotRef Free, FirstInputDriver, Ours; bool HasFree, HasInputDriver, HasDeskTop, HasOurs; }; @@ -425,7 +433,7 @@ FLASHMEM static bool GeosScanDirectory(struct GeosDisk *Disk, const struct GeosT if (!Slot[SlotType] && !Scan->HasFree) { Scan->Free = Here; Scan->HasFree = true; } if (!InUse) continue; - if (GeosSlotNamed(Slot, Target->DriverName)) Scan->HasOurs = true; + if (GeosSlotNamed(Slot, Target->DriverName)) { Scan->Ours = Here; Scan->HasOurs = true; } if (GeosSlotNamed(Slot, Target->DeskTopName)) Scan->HasDeskTop = true; if (Slot[SlotGeosType] == Target->InputType && !Scan->HasInputDriver) { Scan->FirstInputDriver = Here; Scan->HasInputDriver = true; } } @@ -535,3 +543,54 @@ FLASHMEM static inline bool GeosInstallDriver(struct GeosDisk *Disk, const struc if (!Scan.HasInputDriver || !GeosSlotBefore(Scan.FirstInputDriver, Scan.Free)) return true; return GeosSwapEntries(Disk, Chain, Scan.FirstInputDriver, Scan.Free); } + +// Makes the copy's TR mouse driver the firmware's, in place: a copy outlives the firmware that set it up, and an older +// driver may not find the registers where this one puts them. One of another length is refused, the image unchanged. +FLASHMEM static inline bool GeosRefreshDriver(struct GeosDisk *Disk, bool *Changed) +{ + const struct GeosTarget *Target = Disk->Target; + const uint16_t DataBlocks = GeosDriverDataBlocks(Target); + struct GeosTS Chain[GEOS_MAX_DIR_SECTORS], First, Info, At; + uint8_t Block[GEOS_SECTOR_SIZE], Wanted[GEOS_SECTOR_SIZE]; + const uint8_t *Slot; + struct GeosDirScan Scan; + uint16_t Blocks = 0; + int Length; + + *Changed = false; + if (!GeosReadDirectory(Disk, Chain, &Length, &Scan)) return false; + if (!Scan.HasOurs) return true; + if (!GeosReadTS(Disk, Chain[Scan.Ours.Sector].Track, Chain[Scan.Ours.Sector].Sector, Block)) return false; + Slot = Block + Scan.Ours.Slot * GEOS_DIR_ENTRY_SIZE; + First = (struct GeosTS){ Slot[SlotFirst], Slot[SlotFirst + 1] }; + Info = (struct GeosTS){ Slot[SlotInfo], Slot[SlotInfo + 1] }; + for (At = First; At.Track && At.Track <= Disk->Format->Tracks && Blocks <= DataBlocks; Blocks++) + { + if (!GeosReadTS(Disk, At.Track, At.Sector, Block)) return false; + At = (struct GeosTS){ Block[0], Block[1] }; + } + if (At.Track || Blocks != DataBlocks || !Info.Track || Info.Track > Disk->Format->Tracks) + { + snprintf(Disk->Error, sizeof Disk->Error, PSTR("its %s is an old one; delete this copy"), Target->DriverName); + return false; + } + + for (uint16_t i = 0; First.Track; i++) + { + struct GeosTS Next; + uint16_t Used; + if (!GeosReadTS(Disk, First.Track, First.Sector, Block)) return false; + Next = (struct GeosTS){ Block[0], Block[1] }; + Used = GeosDriverBlock(Target, i, Next, Wanted); + if (memcmp(Block, Wanted, Used)) + { + if (!GeosWriteTS(Disk, First.Track, First.Sector, Wanted)) return false; + *Changed = true; + } + First = Block[0] ? Next : (struct GeosTS){ 0, 0 }; + } + if (!GeosReadTS(Disk, Info.Track, Info.Sector, Block)) return false; + if (!memcmp(Block, Target->DriverInfo, GEOS_SECTOR_SIZE)) return true; + *Changed = true; + return GeosWriteTS(Disk, Info.Track, Info.Sector, Target->DriverInfo); +} diff --git a/Source/Teensy/GEOS/GeosTargets.h b/Source/Teensy/GEOS/GeosTargets.h index 953a3210..cce12510 100644 --- a/Source/Teensy/GEOS/GeosTargets.h +++ b/Source/Teensy/GEOS/GeosTargets.h @@ -39,12 +39,15 @@ static const struct GeosDiskDriver TRDiskDriver128 = TRDriver128Relocations, sizeof TRDriver128Relocations / sizeof TRDriver128Relocations[0], TRDriver128Entries, }; +static const char Geos64ConfigureName[] PROGMEM = "CONFIGURE"; +static const char Geos128ConfigureName[] PROGMEM = "128 CONFIGURE"; + static const struct GeosLauncher Geos64Launcher = { "GEOS 64", "/GEOS64", GeosC64, { "DESK TOP", TRMouse64Name, TRMouse64Code, TRMouse64Info, sizeof TRMouse64Code, GEOS_INPUT_64, GEOS64_BOOT_FILE, GEOS64_BOOT_ENTRY, &TRDiskDriver64, - Geos64ClockRuns, sizeof Geos64ClockRuns / sizeof Geos64ClockRuns[0], 0 }, + Geos64ClockRuns, sizeof Geos64ClockRuns / sizeof Geos64ClockRuns[0], 0, Geos64ConfigureName }, }; static const struct GeosLauncher Geos128Launcher = @@ -52,5 +55,6 @@ static const struct GeosLauncher Geos128Launcher = "GEOS 128", "/GEOS128", GeosC128, { "128 DESKTOP", TRMouse128Name, TRMouse128Code, TRMouse128Info, sizeof TRMouse128Code, GEOS_INPUT_128, GEOS128_BOOT_FILE, GEOS128_BOOT_ENTRY, &TRDiskDriver128, - Geos128ClockRuns, sizeof Geos128ClockRuns / sizeof Geos128ClockRuns[0], GEOS128_RESET_KEY }, + Geos128ClockRuns, sizeof Geos128ClockRuns / sizeof Geos128ClockRuns[0], GEOS128_RESET_KEY, + Geos128ConfigureName }, }; diff --git a/Source/Teensy/GEOS/TRDriver.h b/Source/Teensy/GEOS/TRDriver.h index ce4e66b3..fc5b13cb 100644 --- a/Source/Teensy/GEOS/TRDriver.h +++ b/Source/Teensy/GEOS/TRDriver.h @@ -9,45 +9,48 @@ #endif #define TR_DRIVER_BASE 0x9000 -static const uint8_t TRDriverCode[501] PROGMEM = { - 0x08, 0x68, 0x8d, 0xeb, 0x91, 0x78, 0xa5, 0x01, 0x8d, 0xec, 0x91, 0xa9, 0x36, 0x85, 0x01, 0xad, - 0x1a, 0xd0, 0x8d, 0xf4, 0x91, 0xa9, 0x00, 0x8d, 0x1a, 0xd0, 0xa9, 0x7f, 0x8d, 0x19, 0xd0, 0x8d, - 0x0d, 0xdc, 0x8d, 0x0d, 0xdd, 0x60, 0x78, 0xad, 0x0d, 0xdc, 0xad, 0x0d, 0xdd, 0xad, 0xf4, 0x91, - 0x8d, 0x1a, 0xd0, 0xad, 0xec, 0x91, 0x85, 0x01, 0xad, 0xeb, 0x91, 0x48, 0x28, 0x60, 0xa2, 0x00, +static const uint8_t TRDriverCode[543] PROGMEM = { + 0x08, 0x68, 0x8d, 0x15, 0x92, 0x78, 0xa5, 0x01, 0x8d, 0x16, 0x92, 0xa9, 0x36, 0x85, 0x01, 0xad, + 0x1a, 0xd0, 0x8d, 0x1e, 0x92, 0xa9, 0x00, 0x8d, 0x1a, 0xd0, 0xa9, 0x7f, 0x8d, 0x19, 0xd0, 0x8d, + 0x0d, 0xdc, 0x8d, 0x0d, 0xdd, 0x60, 0x78, 0xad, 0x0d, 0xdc, 0xad, 0x0d, 0xdd, 0xad, 0x1e, 0x92, + 0x8d, 0x1a, 0xd0, 0xad, 0x16, 0x92, 0x85, 0x01, 0xad, 0x15, 0x92, 0x48, 0x28, 0x60, 0xa2, 0x00, 0x8a, 0x60, 0xa2, 0x00, 0xa0, 0x00, 0x60, 0x8d, 0x89, 0x84, 0x85, 0xba, 0xa2, 0x00, 0x60, 0x20, 0x9c, 0x90, 0xe0, 0x00, 0xd0, 0x03, 0x20, 0x88, 0x91, 0xa0, 0x00, 0x60, 0x20, 0x88, 0x91, 0x20, 0xd8, 0x90, 0x8a, 0x48, 0x20, 0x88, 0x91, 0x68, 0xaa, 0xa0, 0x00, 0x60, 0x20, 0xbb, 0x91, 0xb0, - 0x26, 0xa9, 0x01, 0x20, 0x15, 0x91, 0xe0, 0x00, 0xd0, 0xdf, 0xa9, 0x00, 0xa2, 0x0b, 0x20, 0xe4, - 0x91, 0xa2, 0x40, 0x20, 0xd5, 0x91, 0xa0, 0x00, 0x91, 0x0a, 0xa2, 0x41, 0x20, 0xd5, 0x91, 0xc8, + 0x26, 0xa9, 0x01, 0x20, 0x15, 0x91, 0xe0, 0x00, 0xd0, 0xdf, 0xa9, 0x00, 0xa2, 0x0b, 0x20, 0x0e, + 0x92, 0xa2, 0x40, 0x20, 0xff, 0x91, 0xa0, 0x00, 0x91, 0x0a, 0xa2, 0x41, 0x20, 0xff, 0x91, 0xc8, 0x91, 0x0a, 0xa2, 0x00, 0xa0, 0x00, 0x60, 0xa2, 0x0d, 0xa0, 0x00, 0x60, 0x20, 0xbb, 0x91, 0xb0, - 0xf6, 0xa9, 0x01, 0x20, 0x15, 0x91, 0xe0, 0x00, 0xd0, 0x2d, 0xa0, 0x00, 0xa9, 0x00, 0x8d, 0xf0, - 0x91, 0xad, 0xf0, 0x91, 0xa2, 0x0b, 0x20, 0xe4, 0x91, 0xa2, 0x00, 0xbd, 0x40, 0xdf, 0xdd, 0x40, - 0xdf, 0xd0, 0xf8, 0x91, 0x0a, 0xc8, 0xe8, 0xe0, 0x40, 0xd0, 0xf0, 0xee, 0xf0, 0x91, 0xad, 0xf0, - 0x91, 0xc9, 0x04, 0xd0, 0xdc, 0xa2, 0x00, 0x60, 0x20, 0xbb, 0x91, 0xb0, 0xba, 0xa0, 0x00, 0xa9, - 0x00, 0x8d, 0xf0, 0x91, 0xad, 0xf0, 0x91, 0xa2, 0x0b, 0x20, 0xe4, 0x91, 0xa2, 0x00, 0xb1, 0x0a, + 0xf6, 0xa9, 0x01, 0x20, 0x15, 0x91, 0xe0, 0x00, 0xd0, 0x2d, 0xa0, 0x00, 0xa9, 0x00, 0x8d, 0x1a, + 0x92, 0xad, 0x1a, 0x92, 0xa2, 0x0b, 0x20, 0x0e, 0x92, 0xa2, 0x00, 0xbd, 0x40, 0xdf, 0xdd, 0x40, + 0xdf, 0xd0, 0xf8, 0x91, 0x0a, 0xc8, 0xe8, 0xe0, 0x40, 0xd0, 0xf0, 0xee, 0x1a, 0x92, 0xad, 0x1a, + 0x92, 0xc9, 0x04, 0xd0, 0xdc, 0xa2, 0x00, 0x60, 0x20, 0xbb, 0x91, 0xb0, 0xba, 0xa0, 0x00, 0xa9, + 0x00, 0x8d, 0x1a, 0x92, 0xad, 0x1a, 0x92, 0xa2, 0x0b, 0x20, 0x0e, 0x92, 0xa2, 0x00, 0xb1, 0x0a, 0x9d, 0x40, 0xdf, 0xdd, 0x40, 0xdf, 0xf0, 0x05, 0xdd, 0x40, 0xdf, 0xd0, 0x15, 0xc8, 0xe8, 0xe0, - 0x40, 0xd0, 0xeb, 0xee, 0xf0, 0x91, 0xad, 0xf0, 0x91, 0xc9, 0x04, 0xd0, 0xd7, 0xa9, 0x02, 0x4c, - 0x15, 0x91, 0xa2, 0x0d, 0x60, 0x8d, 0xee, 0x91, 0xee, 0xf1, 0x91, 0xd0, 0x03, 0xee, 0xf1, 0x91, - 0xad, 0xf1, 0x91, 0xa2, 0x0c, 0x20, 0xe4, 0x91, 0xa5, 0x04, 0xa2, 0x09, 0x20, 0xe4, 0x91, 0xa5, - 0x05, 0xa2, 0x0a, 0x20, 0xe4, 0x91, 0xad, 0xee, 0x91, 0xa2, 0x08, 0x20, 0xe4, 0x91, 0xa9, 0x00, - 0x8d, 0xf2, 0x91, 0x8d, 0xf3, 0x91, 0xa2, 0x0d, 0x20, 0xd1, 0x91, 0x8d, 0xef, 0x91, 0x20, 0xd1, - 0x91, 0xcd, 0xef, 0x91, 0xd0, 0x04, 0xc9, 0x80, 0xd0, 0x0d, 0xee, 0xf2, 0x91, 0xd0, 0xe7, 0xee, - 0xf3, 0x91, 0xd0, 0xe2, 0xa2, 0x0d, 0x60, 0x48, 0xa2, 0x0e, 0x20, 0xd1, 0x91, 0xcd, 0xf1, 0x91, + 0x40, 0xd0, 0xeb, 0xee, 0x1a, 0x92, 0xad, 0x1a, 0x92, 0xc9, 0x04, 0xd0, 0xd7, 0xa9, 0x02, 0x4c, + 0x15, 0x91, 0xa2, 0x0d, 0x60, 0x8d, 0x18, 0x92, 0xee, 0x1b, 0x92, 0xd0, 0x03, 0xee, 0x1b, 0x92, + 0xad, 0x1b, 0x92, 0xa2, 0x0c, 0x20, 0x0e, 0x92, 0xa5, 0x04, 0xa2, 0x09, 0x20, 0x0e, 0x92, 0xa5, + 0x05, 0xa2, 0x0a, 0x20, 0x0e, 0x92, 0xad, 0x18, 0x92, 0xa2, 0x08, 0x20, 0x0e, 0x92, 0xa9, 0x00, + 0x8d, 0x1c, 0x92, 0x8d, 0x1d, 0x92, 0xa2, 0x0d, 0x20, 0xfb, 0x91, 0x8d, 0x19, 0x92, 0x20, 0xfb, + 0x91, 0xcd, 0x19, 0x92, 0xd0, 0x04, 0xc9, 0x80, 0xd0, 0x0d, 0xee, 0x1c, 0x92, 0xd0, 0xe7, 0xee, + 0x1d, 0x92, 0xd0, 0xe2, 0xa2, 0x0d, 0x60, 0x48, 0xa2, 0x0e, 0x20, 0xfb, 0x91, 0xcd, 0x1b, 0x92, 0xf0, 0x04, 0x68, 0x4c, 0x5a, 0x91, 0x68, 0xc9, 0x5a, 0xf0, 0x07, 0xc9, 0xe2, 0xf0, 0x06, 0xa2, 0x0d, 0x60, 0xa2, 0x00, 0x60, 0xa2, 0x02, 0x60, 0xad, 0x4e, 0x90, 0xc9, 0x03, 0xd0, 0x2b, 0xa5, - 0x04, 0xc9, 0x28, 0xd0, 0x25, 0xa5, 0x05, 0xd0, 0x21, 0xa0, 0x04, 0xb1, 0x0a, 0x8d, 0xed, 0x91, - 0x98, 0x18, 0x69, 0x8c, 0xa8, 0xb1, 0x0a, 0x48, 0xad, 0xed, 0x91, 0x91, 0x0a, 0x98, 0x38, 0xe9, - 0x8c, 0xa8, 0x68, 0x91, 0x0a, 0xc8, 0xc0, 0x1d, 0xd0, 0xe1, 0x60, 0xa0, 0x02, 0xb9, 0x10, 0xdf, - 0xd9, 0x10, 0xdf, 0xd0, 0xf8, 0xd9, 0xe8, 0x91, 0xd0, 0x05, 0x88, 0x10, 0xf0, 0x18, 0x60, 0x38, - 0x60, 0xbd, 0x00, 0xdf, 0x60, 0x20, 0xd1, 0x91, 0x8d, 0xef, 0x91, 0x20, 0xd1, 0x91, 0xcd, 0xef, - 0x91, 0xd0, 0xf2, 0x60, 0x9d, 0x00, 0xdf, 0x60, 0x54, 0x52, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00 + 0x04, 0xc9, 0x28, 0xd0, 0x25, 0xa5, 0x05, 0xd0, 0x21, 0xa0, 0x04, 0xb1, 0x0a, 0x8d, 0x17, 0x92, + 0x98, 0x18, 0x69, 0x8c, 0xa8, 0xb1, 0x0a, 0x48, 0xad, 0x17, 0x92, 0x91, 0x0a, 0x98, 0x38, 0xe9, + 0x8c, 0xa8, 0x68, 0x91, 0x0a, 0xc8, 0xc0, 0x1d, 0xd0, 0xe1, 0x60, 0xa2, 0xdf, 0x20, 0xdf, 0x91, + 0x90, 0x07, 0xa2, 0xde, 0x20, 0xdf, 0x91, 0xb0, 0x15, 0x8e, 0xfd, 0x91, 0x8e, 0x10, 0x92, 0x8e, + 0xbd, 0x90, 0x8e, 0xc0, 0x90, 0x8e, 0xf2, 0x90, 0x8e, 0xf5, 0x90, 0x8e, 0xfa, 0x90, 0x60, 0x8e, + 0xe9, 0x91, 0x8e, 0xec, 0x91, 0xa0, 0x02, 0xb9, 0x10, 0xdf, 0xd9, 0x10, 0xdf, 0xd0, 0xf8, 0xd9, + 0x12, 0x92, 0xd0, 0x05, 0x88, 0x10, 0xf0, 0x18, 0x60, 0x38, 0x60, 0xbd, 0x00, 0xdf, 0x60, 0x20, + 0xfb, 0x91, 0x8d, 0x19, 0x92, 0x20, 0xfb, 0x91, 0xcd, 0x19, 0x92, 0xd0, 0xf2, 0x60, 0x9d, 0x00, + 0xdf, 0x60, 0x54, 0x52, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -static const uint16_t TRDriverRelocations[57] PROGMEM = { +static const uint16_t TRDriverRelocations[68] PROGMEM = { 0x0003, 0x0009, 0x0013, 0x002e, 0x0034, 0x0039, 0x0050, 0x0057, 0x005d, 0x0060, 0x0065, 0x006d, 0x0074, 0x007f, 0x0084, 0x008d, 0x009d, 0x00a4, 0x00af, 0x00b2, 0x00b7, 0x00cc, 0x00cf, 0x00d9, 0x00e2, 0x00e5, 0x00ea, 0x0104, 0x0107, 0x0110, 0x0116, 0x0119, 0x011e, 0x0121, 0x0126, 0x012d, 0x0134, 0x0137, 0x013c, 0x0141, 0x0144, 0x0149, 0x014c, 0x014f, 0x0152, 0x015b, 0x0160, 0x016b, - 0x016e, 0x0174, 0x019e, 0x01a9, 0x01c6, 0x01d6, 0x01d9, 0x01dc, 0x01df + 0x016e, 0x0174, 0x019e, 0x01a9, 0x01be, 0x01c5, 0x01ca, 0x01cd, 0x01d0, 0x01d3, 0x01d6, 0x01d9, 0x01dc, 0x01e0, 0x01e3, 0x01f0, + 0x0200, 0x0203, 0x0206, 0x0209 }; static const uint16_t TRDriverEntries[13] PROGMEM = { 0x0000, 0x0026, 0x0041, 0x0041, 0x003e, 0x0047, 0x0042, 0x004f, 0x005c, 0x0042, 0x0042, 0x0042, 0x006c diff --git a/Source/Teensy/GEOS/TRDriver128.h b/Source/Teensy/GEOS/TRDriver128.h index 1d90f956..2b9bfdf9 100644 --- a/Source/Teensy/GEOS/TRDriver128.h +++ b/Source/Teensy/GEOS/TRDriver128.h @@ -9,46 +9,50 @@ #endif #define TR_DRIVER128_BASE 0x9000 -static const uint8_t TRDriver128Code[522] PROGMEM = { - 0x08, 0x68, 0x8d, 0xff, 0x91, 0x78, 0xad, 0x00, 0xff, 0x8d, 0x00, 0x92, 0xa9, 0x7e, 0x8d, 0x00, - 0xff, 0xad, 0x30, 0xd0, 0x8d, 0x01, 0x92, 0xa9, 0x00, 0x8d, 0x30, 0xd0, 0xad, 0x1a, 0xd0, 0x8d, - 0x09, 0x92, 0xa9, 0x00, 0x8d, 0x1a, 0xd0, 0xa9, 0x7f, 0x8d, 0x19, 0xd0, 0x8d, 0x0d, 0xdc, 0x8d, - 0x0d, 0xdd, 0x60, 0x78, 0xad, 0x0d, 0xdc, 0xad, 0x0d, 0xdd, 0xad, 0x09, 0x92, 0x8d, 0x1a, 0xd0, - 0xad, 0x01, 0x92, 0x8d, 0x30, 0xd0, 0xad, 0x00, 0x92, 0x8d, 0x00, 0xff, 0xad, 0xff, 0x91, 0x48, +static const uint8_t TRDriver128Code[564] PROGMEM = { + 0x08, 0x68, 0x8d, 0x29, 0x92, 0x78, 0xad, 0x00, 0xff, 0x8d, 0x2a, 0x92, 0xa9, 0x7e, 0x8d, 0x00, + 0xff, 0xad, 0x30, 0xd0, 0x8d, 0x2b, 0x92, 0xa9, 0x00, 0x8d, 0x30, 0xd0, 0xad, 0x1a, 0xd0, 0x8d, + 0x33, 0x92, 0xa9, 0x00, 0x8d, 0x1a, 0xd0, 0xa9, 0x7f, 0x8d, 0x19, 0xd0, 0x8d, 0x0d, 0xdc, 0x8d, + 0x0d, 0xdd, 0x60, 0x78, 0xad, 0x0d, 0xdc, 0xad, 0x0d, 0xdd, 0xad, 0x33, 0x92, 0x8d, 0x1a, 0xd0, + 0xad, 0x2b, 0x92, 0x8d, 0x30, 0xd0, 0xad, 0x2a, 0x92, 0x8d, 0x00, 0xff, 0xad, 0x29, 0x92, 0x48, 0x28, 0x60, 0xa2, 0x00, 0x8a, 0x60, 0xa2, 0x00, 0xa0, 0x00, 0x60, 0x8d, 0x89, 0x84, 0x85, 0xba, 0xa2, 0x00, 0x60, 0x20, 0xb0, 0x90, 0xe0, 0x00, 0xd0, 0x03, 0x20, 0x9c, 0x91, 0xa0, 0x00, 0x60, 0x20, 0x9c, 0x91, 0x20, 0xec, 0x90, 0x8a, 0x48, 0x20, 0x9c, 0x91, 0x68, 0xaa, 0xa0, 0x00, 0x60, 0x20, 0xcf, 0x91, 0xb0, 0x26, 0xa9, 0x01, 0x20, 0x29, 0x91, 0xe0, 0x00, 0xd0, 0xdf, 0xa9, 0x00, - 0xa2, 0x0b, 0x20, 0xf8, 0x91, 0xa2, 0x40, 0x20, 0xe9, 0x91, 0xa0, 0x00, 0x91, 0x0a, 0xa2, 0x41, - 0x20, 0xe9, 0x91, 0xc8, 0x91, 0x0a, 0xa2, 0x00, 0xa0, 0x00, 0x60, 0xa2, 0x0d, 0xa0, 0x00, 0x60, + 0xa2, 0x0b, 0x20, 0x22, 0x92, 0xa2, 0x40, 0x20, 0x13, 0x92, 0xa0, 0x00, 0x91, 0x0a, 0xa2, 0x41, + 0x20, 0x13, 0x92, 0xc8, 0x91, 0x0a, 0xa2, 0x00, 0xa0, 0x00, 0x60, 0xa2, 0x0d, 0xa0, 0x00, 0x60, 0x20, 0xcf, 0x91, 0xb0, 0xf6, 0xa9, 0x01, 0x20, 0x29, 0x91, 0xe0, 0x00, 0xd0, 0x2d, 0xa0, 0x00, - 0xa9, 0x00, 0x8d, 0x05, 0x92, 0xad, 0x05, 0x92, 0xa2, 0x0b, 0x20, 0xf8, 0x91, 0xa2, 0x00, 0xbd, + 0xa9, 0x00, 0x8d, 0x2f, 0x92, 0xad, 0x2f, 0x92, 0xa2, 0x0b, 0x20, 0x22, 0x92, 0xa2, 0x00, 0xbd, 0x40, 0xdf, 0xdd, 0x40, 0xdf, 0xd0, 0xf8, 0x91, 0x0a, 0xc8, 0xe8, 0xe0, 0x40, 0xd0, 0xf0, 0xee, - 0x05, 0x92, 0xad, 0x05, 0x92, 0xc9, 0x04, 0xd0, 0xdc, 0xa2, 0x00, 0x60, 0x20, 0xcf, 0x91, 0xb0, - 0xba, 0xa0, 0x00, 0xa9, 0x00, 0x8d, 0x05, 0x92, 0xad, 0x05, 0x92, 0xa2, 0x0b, 0x20, 0xf8, 0x91, + 0x2f, 0x92, 0xad, 0x2f, 0x92, 0xc9, 0x04, 0xd0, 0xdc, 0xa2, 0x00, 0x60, 0x20, 0xcf, 0x91, 0xb0, + 0xba, 0xa0, 0x00, 0xa9, 0x00, 0x8d, 0x2f, 0x92, 0xad, 0x2f, 0x92, 0xa2, 0x0b, 0x20, 0x22, 0x92, 0xa2, 0x00, 0xb1, 0x0a, 0x9d, 0x40, 0xdf, 0xdd, 0x40, 0xdf, 0xf0, 0x05, 0xdd, 0x40, 0xdf, 0xd0, - 0x15, 0xc8, 0xe8, 0xe0, 0x40, 0xd0, 0xeb, 0xee, 0x05, 0x92, 0xad, 0x05, 0x92, 0xc9, 0x04, 0xd0, - 0xd7, 0xa9, 0x02, 0x4c, 0x29, 0x91, 0xa2, 0x0d, 0x60, 0x8d, 0x03, 0x92, 0xee, 0x06, 0x92, 0xd0, - 0x03, 0xee, 0x06, 0x92, 0xad, 0x06, 0x92, 0xa2, 0x0c, 0x20, 0xf8, 0x91, 0xa5, 0x04, 0xa2, 0x09, - 0x20, 0xf8, 0x91, 0xa5, 0x05, 0xa2, 0x0a, 0x20, 0xf8, 0x91, 0xad, 0x03, 0x92, 0xa2, 0x08, 0x20, - 0xf8, 0x91, 0xa9, 0x00, 0x8d, 0x07, 0x92, 0x8d, 0x08, 0x92, 0xa2, 0x0d, 0x20, 0xe5, 0x91, 0x8d, - 0x04, 0x92, 0x20, 0xe5, 0x91, 0xcd, 0x04, 0x92, 0xd0, 0x04, 0xc9, 0x80, 0xd0, 0x0d, 0xee, 0x07, - 0x92, 0xd0, 0xe7, 0xee, 0x08, 0x92, 0xd0, 0xe2, 0xa2, 0x0d, 0x60, 0x48, 0xa2, 0x0e, 0x20, 0xe5, - 0x91, 0xcd, 0x06, 0x92, 0xf0, 0x04, 0x68, 0x4c, 0x6e, 0x91, 0x68, 0xc9, 0x5a, 0xf0, 0x07, 0xc9, + 0x15, 0xc8, 0xe8, 0xe0, 0x40, 0xd0, 0xeb, 0xee, 0x2f, 0x92, 0xad, 0x2f, 0x92, 0xc9, 0x04, 0xd0, + 0xd7, 0xa9, 0x02, 0x4c, 0x29, 0x91, 0xa2, 0x0d, 0x60, 0x8d, 0x2d, 0x92, 0xee, 0x30, 0x92, 0xd0, + 0x03, 0xee, 0x30, 0x92, 0xad, 0x30, 0x92, 0xa2, 0x0c, 0x20, 0x22, 0x92, 0xa5, 0x04, 0xa2, 0x09, + 0x20, 0x22, 0x92, 0xa5, 0x05, 0xa2, 0x0a, 0x20, 0x22, 0x92, 0xad, 0x2d, 0x92, 0xa2, 0x08, 0x20, + 0x22, 0x92, 0xa9, 0x00, 0x8d, 0x31, 0x92, 0x8d, 0x32, 0x92, 0xa2, 0x0d, 0x20, 0x0f, 0x92, 0x8d, + 0x2e, 0x92, 0x20, 0x0f, 0x92, 0xcd, 0x2e, 0x92, 0xd0, 0x04, 0xc9, 0x80, 0xd0, 0x0d, 0xee, 0x31, + 0x92, 0xd0, 0xe7, 0xee, 0x32, 0x92, 0xd0, 0xe2, 0xa2, 0x0d, 0x60, 0x48, 0xa2, 0x0e, 0x20, 0x0f, + 0x92, 0xcd, 0x30, 0x92, 0xf0, 0x04, 0x68, 0x4c, 0x6e, 0x91, 0x68, 0xc9, 0x5a, 0xf0, 0x07, 0xc9, 0xe2, 0xf0, 0x06, 0xa2, 0x0d, 0x60, 0xa2, 0x00, 0x60, 0xa2, 0x02, 0x60, 0xad, 0x4e, 0x90, 0xc9, 0x03, 0xd0, 0x2b, 0xa5, 0x04, 0xc9, 0x28, 0xd0, 0x25, 0xa5, 0x05, 0xd0, 0x21, 0xa0, 0x04, 0xb1, - 0x0a, 0x8d, 0x02, 0x92, 0x98, 0x18, 0x69, 0x8c, 0xa8, 0xb1, 0x0a, 0x48, 0xad, 0x02, 0x92, 0x91, - 0x0a, 0x98, 0x38, 0xe9, 0x8c, 0xa8, 0x68, 0x91, 0x0a, 0xc8, 0xc0, 0x1d, 0xd0, 0xe1, 0x60, 0xa0, - 0x02, 0xb9, 0x10, 0xdf, 0xd9, 0x10, 0xdf, 0xd0, 0xf8, 0xd9, 0xfc, 0x91, 0xd0, 0x05, 0x88, 0x10, - 0xf0, 0x18, 0x60, 0x38, 0x60, 0xbd, 0x00, 0xdf, 0x60, 0x20, 0xe5, 0x91, 0x8d, 0x04, 0x92, 0x20, - 0xe5, 0x91, 0xcd, 0x04, 0x92, 0xd0, 0xf2, 0x60, 0x9d, 0x00, 0xdf, 0x60, 0x54, 0x52, 0x42, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x0a, 0x8d, 0x2c, 0x92, 0x98, 0x18, 0x69, 0x8c, 0xa8, 0xb1, 0x0a, 0x48, 0xad, 0x2c, 0x92, 0x91, + 0x0a, 0x98, 0x38, 0xe9, 0x8c, 0xa8, 0x68, 0x91, 0x0a, 0xc8, 0xc0, 0x1d, 0xd0, 0xe1, 0x60, 0xa2, + 0xdf, 0x20, 0xf3, 0x91, 0x90, 0x07, 0xa2, 0xde, 0x20, 0xf3, 0x91, 0xb0, 0x15, 0x8e, 0x11, 0x92, + 0x8e, 0x24, 0x92, 0x8e, 0xd1, 0x90, 0x8e, 0xd4, 0x90, 0x8e, 0x06, 0x91, 0x8e, 0x09, 0x91, 0x8e, + 0x0e, 0x91, 0x60, 0x8e, 0xfd, 0x91, 0x8e, 0x00, 0x92, 0xa0, 0x02, 0xb9, 0x10, 0xdf, 0xd9, 0x10, + 0xdf, 0xd0, 0xf8, 0xd9, 0x26, 0x92, 0xd0, 0x05, 0x88, 0x10, 0xf0, 0x18, 0x60, 0x38, 0x60, 0xbd, + 0x00, 0xdf, 0x60, 0x20, 0x0f, 0x92, 0x8d, 0x2e, 0x92, 0x20, 0x0f, 0x92, 0xcd, 0x2e, 0x92, 0xd0, + 0xf2, 0x60, 0x9d, 0x00, 0xdf, 0x60, 0x54, 0x52, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; -static const uint16_t TRDriver128Relocations[59] PROGMEM = { +static const uint16_t TRDriver128Relocations[70] PROGMEM = { 0x0003, 0x000a, 0x0015, 0x0020, 0x003b, 0x0041, 0x0047, 0x004d, 0x0064, 0x006b, 0x0071, 0x0074, 0x0079, 0x0081, 0x0088, 0x0093, 0x0098, 0x00a1, 0x00b1, 0x00b8, 0x00c3, 0x00c6, 0x00cb, 0x00e0, 0x00e3, 0x00ed, 0x00f6, 0x00f9, 0x00fe, 0x0118, 0x011b, 0x0124, 0x012a, 0x012d, 0x0132, 0x0135, 0x013a, 0x0141, 0x0148, 0x014b, 0x0150, 0x0155, 0x0158, 0x015d, 0x0160, 0x0163, 0x0166, 0x016f, - 0x0174, 0x017f, 0x0182, 0x0188, 0x01b2, 0x01bd, 0x01da, 0x01ea, 0x01ed, 0x01f0, 0x01f3 + 0x0174, 0x017f, 0x0182, 0x0188, 0x01b2, 0x01bd, 0x01d2, 0x01d9, 0x01de, 0x01e1, 0x01e4, 0x01e7, 0x01ea, 0x01ed, 0x01f0, 0x01f4, + 0x01f7, 0x0204, 0x0214, 0x0217, 0x021a, 0x021d }; static const uint16_t TRDriver128Entries[13] PROGMEM = { 0x0000, 0x0033, 0x0055, 0x0055, 0x0052, 0x005b, 0x0056, 0x0063, 0x0070, 0x0056, 0x0056, 0x0056, 0x0080 diff --git a/Source/Teensy/GEOS/TRMouse128.h b/Source/Teensy/GEOS/TRMouse128.h index 52c27ba1..a457c517 100644 --- a/Source/Teensy/GEOS/TRMouse128.h +++ b/Source/Teensy/GEOS/TRMouse128.h @@ -9,27 +9,30 @@ #endif static const char TRMouse128Name[] PROGMEM = "TR USB MOUSE"; -static const uint8_t TRMouse128Code[207] PROGMEM = { - 0x4c, 0x10, 0xfd, 0x4c, 0x1a, 0xfd, 0x4c, 0x2f, 0xfd, 0x4c, 0x1a, 0xfd, 0x80, 0x00, 0x00, 0x00, - 0xa9, 0x00, 0x85, 0x3b, 0xa9, 0x08, 0x85, 0x3a, 0x85, 0x3c, 0x60, 0xa2, 0x02, 0xbd, 0x03, 0xdf, - 0xdd, 0x2c, 0xfd, 0xd0, 0x05, 0xca, 0x10, 0xf5, 0x18, 0x60, 0x38, 0x60, 0x54, 0x52, 0x4d, 0x20, - 0x1b, 0xfd, 0xb0, 0x10, 0xad, 0x00, 0xdf, 0x8d, 0x0d, 0xfd, 0xad, 0x01, 0xdf, 0x8d, 0x0f, 0xfd, - 0x24, 0x30, 0x30, 0x01, 0x60, 0xad, 0x02, 0xdf, 0x29, 0x01, 0xf0, 0x04, 0xa9, 0x00, 0xf0, 0x02, - 0xa9, 0x80, 0xcd, 0x0c, 0xfd, 0xf0, 0x0c, 0x8d, 0x0c, 0xfd, 0x8d, 0x05, 0x85, 0xa5, 0x39, 0x09, - 0x20, 0x85, 0x39, 0xa2, 0x00, 0xad, 0x0d, 0xfd, 0x10, 0x01, 0xca, 0x8e, 0x0e, 0xfd, 0x24, 0x3f, - 0x10, 0x06, 0x0e, 0x0d, 0xfd, 0x2e, 0x0e, 0xfd, 0x18, 0xa5, 0x3a, 0x6d, 0x0d, 0xfd, 0x85, 0x3a, - 0xa5, 0x3b, 0x6d, 0x0e, 0xfd, 0x85, 0x3b, 0xad, 0x0f, 0xfd, 0x18, 0x65, 0x3c, 0x2c, 0x0f, 0xfd, - 0x30, 0x0a, 0xb0, 0x04, 0xc9, 0xe4, 0x90, 0x08, 0xa9, 0xe3, 0xd0, 0x04, 0xb0, 0x02, 0xa9, 0xff, - 0x85, 0x3c, 0xa2, 0x01, 0xad, 0x0e, 0xfd, 0x30, 0x08, 0x0d, 0x0d, 0xfd, 0xf0, 0x04, 0xe8, 0xd0, - 0x01, 0xca, 0xad, 0x0f, 0xfd, 0xf0, 0x05, 0x30, 0x06, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xbd, - 0xc6, 0xfd, 0x8d, 0x06, 0x85, 0x60, 0x03, 0x02, 0x01, 0x04, 0xff, 0x00, 0x05, 0x06, 0x07 +static const uint8_t TRMouse128Code[241] PROGMEM = { + 0x4c, 0x10, 0xfd, 0x4c, 0x1a, 0xfd, 0x4c, 0x51, 0xfd, 0x4c, 0x1a, 0xfd, 0x80, 0x00, 0x00, 0x00, + 0xa9, 0x00, 0x85, 0x3b, 0xa9, 0x08, 0x85, 0x3a, 0x85, 0x3c, 0x60, 0xa2, 0xdf, 0xa0, 0x02, 0xb9, + 0x10, 0xdf, 0xd9, 0x4e, 0xfd, 0xd0, 0x05, 0x88, 0x10, 0xf5, 0x30, 0x02, 0xa2, 0xde, 0x8e, 0x3e, + 0xfd, 0x8e, 0x58, 0xfd, 0x8e, 0x5e, 0xfd, 0x8e, 0x69, 0xfd, 0xa0, 0x02, 0xb9, 0x03, 0xdf, 0xd9, + 0x4b, 0xfd, 0xd0, 0x05, 0x88, 0x10, 0xf5, 0x18, 0x60, 0x38, 0x60, 0x54, 0x52, 0x4d, 0x54, 0x52, + 0x42, 0x20, 0x1b, 0xfd, 0xb0, 0x10, 0xad, 0x00, 0xdf, 0x8d, 0x0d, 0xfd, 0xad, 0x01, 0xdf, 0x8d, + 0x0f, 0xfd, 0x24, 0x30, 0x30, 0x01, 0x60, 0xad, 0x02, 0xdf, 0x29, 0x01, 0xf0, 0x04, 0xa9, 0x00, + 0xf0, 0x02, 0xa9, 0x80, 0xcd, 0x0c, 0xfd, 0xf0, 0x0c, 0x8d, 0x0c, 0xfd, 0x8d, 0x05, 0x85, 0xa5, + 0x39, 0x09, 0x20, 0x85, 0x39, 0xa2, 0x00, 0xad, 0x0d, 0xfd, 0x10, 0x01, 0xca, 0x8e, 0x0e, 0xfd, + 0x24, 0x3f, 0x10, 0x06, 0x0e, 0x0d, 0xfd, 0x2e, 0x0e, 0xfd, 0x18, 0xa5, 0x3a, 0x6d, 0x0d, 0xfd, + 0x85, 0x3a, 0xa5, 0x3b, 0x6d, 0x0e, 0xfd, 0x85, 0x3b, 0xad, 0x0f, 0xfd, 0x18, 0x65, 0x3c, 0x2c, + 0x0f, 0xfd, 0x30, 0x0a, 0xb0, 0x04, 0xc9, 0xe4, 0x90, 0x08, 0xa9, 0xe3, 0xd0, 0x04, 0xb0, 0x02, + 0xa9, 0xff, 0x85, 0x3c, 0xa2, 0x01, 0xad, 0x0e, 0xfd, 0x30, 0x08, 0x0d, 0x0d, 0xfd, 0xf0, 0x04, + 0xe8, 0xd0, 0x01, 0xca, 0xad, 0x0f, 0xfd, 0xf0, 0x05, 0x30, 0x06, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, + 0xe8, 0xbd, 0xe8, 0xfd, 0x8d, 0x06, 0x85, 0x60, 0x03, 0x02, 0x01, 0x04, 0xff, 0x00, 0x05, 0x06, + 0x07 }; static const uint8_t TRMouse128Info[256] PROGMEM = { 0x00, 0xff, 0x03, 0x15, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x01, 0x91, 0x80, 0x02, 0x10, 0x40, 0x04, 0x10, 0x20, 0x04, 0x10, 0x20, 0x04, 0x10, 0x20, 0x07, 0xff, 0xe0, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x02, 0x00, 0x40, 0x01, 0x81, 0x80, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xf7, 0x80, 0x04, 0x94, - 0x80, 0x04, 0xe7, 0x80, 0x83, 0x0f, 0x00, 0x00, 0xfd, 0xcf, 0xfd, 0x00, 0x00, 0x54, 0x52, 0x20, + 0x80, 0x04, 0xe7, 0x80, 0x83, 0x0f, 0x00, 0x00, 0xfd, 0xf1, 0xfd, 0x00, 0x00, 0x54, 0x52, 0x20, 0x55, 0x53, 0x42, 0x20, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x56, 0x31, 0x2e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x65, 0x6e, 0x73, 0x79, 0x52, 0x4f, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/Source/Teensy/GEOS/TRMouse64.h b/Source/Teensy/GEOS/TRMouse64.h index b68cc617..12844049 100644 --- a/Source/Teensy/GEOS/TRMouse64.h +++ b/Source/Teensy/GEOS/TRMouse64.h @@ -9,27 +9,29 @@ #endif static const char TRMouse64Name[] PROGMEM = "TR USB MOUSE"; -static const uint8_t TRMouse64Code[197] PROGMEM = { - 0x4c, 0x90, 0xfe, 0x4c, 0x9a, 0xfe, 0x4c, 0xaf, 0xfe, 0x4c, 0x9a, 0xfe, 0x80, 0x00, 0x00, 0x00, - 0xa9, 0x00, 0x85, 0x3b, 0xa9, 0x08, 0x85, 0x3a, 0x85, 0x3c, 0x60, 0xa2, 0x02, 0xbd, 0x03, 0xdf, - 0xdd, 0xac, 0xfe, 0xd0, 0x05, 0xca, 0x10, 0xf5, 0x18, 0x60, 0x38, 0x60, 0x54, 0x52, 0x4d, 0x20, - 0x9b, 0xfe, 0xb0, 0x10, 0xad, 0x00, 0xdf, 0x8d, 0x8d, 0xfe, 0xad, 0x01, 0xdf, 0x8d, 0x8f, 0xfe, - 0x24, 0x30, 0x30, 0x01, 0x60, 0xad, 0x02, 0xdf, 0x29, 0x01, 0xf0, 0x04, 0xa9, 0x00, 0xf0, 0x02, - 0xa9, 0x80, 0xcd, 0x8c, 0xfe, 0xf0, 0x0c, 0x8d, 0x8c, 0xfe, 0x8d, 0x05, 0x85, 0xa5, 0x39, 0x09, - 0x20, 0x85, 0x39, 0xa2, 0x00, 0xad, 0x8d, 0xfe, 0x10, 0x01, 0xca, 0x8e, 0x8e, 0xfe, 0x18, 0xa5, - 0x3a, 0x6d, 0x8d, 0xfe, 0x85, 0x3a, 0xa5, 0x3b, 0x6d, 0x8e, 0xfe, 0x85, 0x3b, 0xad, 0x8f, 0xfe, - 0x18, 0x65, 0x3c, 0x2c, 0x8f, 0xfe, 0x30, 0x0a, 0xb0, 0x04, 0xc9, 0xe4, 0x90, 0x08, 0xa9, 0xe3, - 0xd0, 0x04, 0xb0, 0x02, 0xa9, 0xff, 0x85, 0x3c, 0xa2, 0x01, 0xad, 0x8e, 0xfe, 0x30, 0x08, 0x0d, - 0x8d, 0xfe, 0xf0, 0x04, 0xe8, 0xd0, 0x01, 0xca, 0xad, 0x8f, 0xfe, 0xf0, 0x05, 0x30, 0x06, 0xe8, - 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xbd, 0x3c, 0xff, 0x8d, 0x06, 0x85, 0x60, 0x03, 0x02, 0x01, 0x04, - 0xff, 0x00, 0x05, 0x06, 0x07 +static const uint8_t TRMouse64Code[231] PROGMEM = { + 0x4c, 0x90, 0xfe, 0x4c, 0x9a, 0xfe, 0x4c, 0xd1, 0xfe, 0x4c, 0x9a, 0xfe, 0x80, 0x00, 0x00, 0x00, + 0xa9, 0x00, 0x85, 0x3b, 0xa9, 0x08, 0x85, 0x3a, 0x85, 0x3c, 0x60, 0xa2, 0xdf, 0xa0, 0x02, 0xb9, + 0x10, 0xdf, 0xd9, 0xce, 0xfe, 0xd0, 0x05, 0x88, 0x10, 0xf5, 0x30, 0x02, 0xa2, 0xde, 0x8e, 0xbe, + 0xfe, 0x8e, 0xd8, 0xfe, 0x8e, 0xde, 0xfe, 0x8e, 0xe9, 0xfe, 0xa0, 0x02, 0xb9, 0x03, 0xdf, 0xd9, + 0xcb, 0xfe, 0xd0, 0x05, 0x88, 0x10, 0xf5, 0x18, 0x60, 0x38, 0x60, 0x54, 0x52, 0x4d, 0x54, 0x52, + 0x42, 0x20, 0x9b, 0xfe, 0xb0, 0x10, 0xad, 0x00, 0xdf, 0x8d, 0x8d, 0xfe, 0xad, 0x01, 0xdf, 0x8d, + 0x8f, 0xfe, 0x24, 0x30, 0x30, 0x01, 0x60, 0xad, 0x02, 0xdf, 0x29, 0x01, 0xf0, 0x04, 0xa9, 0x00, + 0xf0, 0x02, 0xa9, 0x80, 0xcd, 0x8c, 0xfe, 0xf0, 0x0c, 0x8d, 0x8c, 0xfe, 0x8d, 0x05, 0x85, 0xa5, + 0x39, 0x09, 0x20, 0x85, 0x39, 0xa2, 0x00, 0xad, 0x8d, 0xfe, 0x10, 0x01, 0xca, 0x8e, 0x8e, 0xfe, + 0x18, 0xa5, 0x3a, 0x6d, 0x8d, 0xfe, 0x85, 0x3a, 0xa5, 0x3b, 0x6d, 0x8e, 0xfe, 0x85, 0x3b, 0xad, + 0x8f, 0xfe, 0x18, 0x65, 0x3c, 0x2c, 0x8f, 0xfe, 0x30, 0x0a, 0xb0, 0x04, 0xc9, 0xe4, 0x90, 0x08, + 0xa9, 0xe3, 0xd0, 0x04, 0xb0, 0x02, 0xa9, 0xff, 0x85, 0x3c, 0xa2, 0x01, 0xad, 0x8e, 0xfe, 0x30, + 0x08, 0x0d, 0x8d, 0xfe, 0xf0, 0x04, 0xe8, 0xd0, 0x01, 0xca, 0xad, 0x8f, 0xfe, 0xf0, 0x05, 0x30, + 0x06, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xbd, 0x5e, 0xff, 0x8d, 0x06, 0x85, 0x60, 0x03, 0x02, + 0x01, 0x04, 0xff, 0x00, 0x05, 0x06, 0x07 }; static const uint8_t TRMouse64Info[256] PROGMEM = { 0x00, 0xff, 0x03, 0x15, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x01, 0x91, 0x80, 0x02, 0x10, 0x40, 0x04, 0x10, 0x20, 0x04, 0x10, 0x20, 0x04, 0x10, 0x20, 0x07, 0xff, 0xe0, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x02, 0x00, 0x40, 0x01, 0x81, 0x80, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xf7, 0x80, 0x04, 0x94, - 0x80, 0x04, 0xe7, 0x80, 0x83, 0x0a, 0x00, 0x80, 0xfe, 0x45, 0xff, 0x00, 0x00, 0x54, 0x52, 0x20, + 0x80, 0x04, 0xe7, 0x80, 0x83, 0x0a, 0x00, 0x80, 0xfe, 0x67, 0xff, 0x00, 0x00, 0x54, 0x52, 0x20, 0x55, 0x53, 0x42, 0x20, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x56, 0x31, 0x2e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x65, 0x6e, 0x73, 0x79, 0x52, 0x4f, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/Source/Teensy/GeosLauncher.ino b/Source/Teensy/GeosLauncher.ino index 2954eb9b..4bba1049 100644 --- a/Source/Teensy/GeosLauncher.ino +++ b/Source/Teensy/GeosLauncher.ino @@ -268,6 +268,16 @@ FLASHMEM static bool GeosUseC128Boot(const uint8_t *ImageEnd, StructMenuItem *It return SetC128Type(Item); } +// GEOS runs beside GeoRAM unless Special IO is set to the REU with the TR's registers beside it, which GEOS's own +// CONFIGURE then finds +uint8_t GeosIOHandler() +{ +#ifdef Fab04_REU + if (IO1[rwRegNextIOHndlr] == IOH_REU_TRPage) return IOH_REU_TRPage; +#endif + return IOH_GeoRAM; +} + FLASHMEM static bool GeosStart(const struct GeosLauncher *Launcher, const char *Name, StructMenuItem *Item) { char Path[MaxNamePathLength]; @@ -287,10 +297,10 @@ FLASHMEM static bool GeosStart(const struct GeosLauncher *Launcher, const char * return false; } Disk = GeosDiskOn(Launcher, &Image); - Built = Launcher->Machine == GeosC128 - ? GeosBuildC128Boot(&Disk, &Now, RAM_Image, RAM_ImageSize, &Boot, &Length, &ClockSet) - : GeosBuildBootPrg(&Disk, &Now, RAM_Image, RAM_ImageSize, &Length, &ClockSet); - Built = Built && GeosDisableConfigure(&Disk, &Changed); + Built = GeosRefreshDriver(&Disk, &Changed) && GeosPrepareConfigure(&Disk, RAM_Image, RAM_ImageSize, &Changed) && + (Launcher->Machine == GeosC128 + ? GeosBuildC128Boot(&Disk, &Now, GeosIOHandler() == IOH_GeoRAM ? GEOS_REGS_BESIDE_GEORAM : GEOS_REGS_BESIDE_REU, RAM_Image, RAM_ImageSize, &Boot, &Length, &ClockSet) + : GeosBuildBootPrg(&Disk, &Now, RAM_Image, RAM_ImageSize, &Length, &ClockSet)); if (!Built) SendMsgPrintfln(PSTR("%s:\r\n%s"), Name, Disk.Error); Image.close(); if (!Built) return false; diff --git a/Source/Teensy/IOHandlers.ino b/Source/Teensy/IOHandlers.ino index b71843f3..2055a5d4 100644 --- a/Source/Teensy/IOHandlers.ino +++ b/Source/Teensy/IOHandlers.ino @@ -44,7 +44,8 @@ void IOHandlerNextInit() void IOHandlerSelectInit() { //called after cart loaded, PRG x-fer finished, or exit to basic (rsIOHWSelInit) PendingfBusSnoop = NULL; //clean slate for whatever InitHndlr below is about to stage - if (IO1[rWRegCurrMenuWAIT] == rmtTeensy && MenuSource[SelItemFullIdx].IOHndlrAssoc != IOH_None) + if (IO1[rWRegCurrMenuWAIT] == rmtTeensy && MenuSource[SelItemFullIdx].ItemType == rtGEOS) IOHandlerInit(GeosIOHandler()); + else if (IO1[rWRegCurrMenuWAIT] == rmtTeensy && MenuSource[SelItemFullIdx].IOHndlrAssoc != IOH_None) { Printf_dbg("IO Handler set by Teensy Menu\n"); IOHandlerInit(MenuSource[SelItemFullIdx].IOHndlrAssoc); diff --git a/Source/Teensy/MinimalBoot/Common/IOHandlers.h b/Source/Teensy/MinimalBoot/Common/IOHandlers.h index ae1d60b8..b6c61a44 100644 --- a/Source/Teensy/MinimalBoot/Common/IOHandlers.h +++ b/Source/Teensy/MinimalBoot/Common/IOHandlers.h @@ -164,6 +164,9 @@ stcIOHandlers* IOHandler[] = //Synch order/qty with enum enumIOHandlers #endif &IOHndlr_Debug, //IOH_Debug, &IOHndlr_GeoRAM, //IOH_GeoRAM, + #ifdef Fab04_REU + &IOHndlr_REU_TRPage, //IOH_REU_TRPage, + #endif //*not* manually selectable, see LastSelectableIOH diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c index 36e46597..6b7110f5 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c @@ -749,3 +749,43 @@ FLASHMEM void PollingHndlr_REU() #endif } +//______________________________________________________________________________________________ +// The REU with the TR's USB mouse and block device beside it in IO1, where a GEOS that found the REU looks for them + +void IO2Hndlr_REU_TRPage(uint8_t Address, bool R_Wn); +void PollingHndlr_REU_TRPage(); +void InitHndlr_REU_TRPage(); + +stcIOHandlers IOHndlr_REU_TRPage = +{ + "REU+USB Mouse/Disk", //Name of handler + &InitHndlr_REU_TRPage, //Called once at handler startup + &TRPageIO, //IO1 R/W handler + &IO2Hndlr_REU_TRPage, //IO2 R/W handler + NULL, //ROML Read handler, in addition to any ROM data sent + NULL, //ROMH Read handler, in addition to any ROM data sent + &PollingHndlr_REU_TRPage, //Polled in main routine + NULL, //called at the end of EVERY c64 cycle +}; + +FLASHMEM void InitHndlr_REU_TRPage() +{ + InitUSBMouse(); + InitTRBlock(); + InitHndlr_REU(); +} + +FLASHMEM void PollingHndlr_REU_TRPage() +{ + PollUSBMouse(); + PollTRBlock(); + PollingHndlr_REU(); +} + +// A 1764's unused registers read $FF. The TR's GEOS drivers look for the block device's ID at $DF10 first, which +// must not answer there beside the REU. +void IO2Hndlr_REU_TRPage(uint8_t Address, bool R_Wn) +{ + if (R_Wn && NumREU_Banks && (Address & 0x1f) >= REUReg_NumRegs) DataPortWriteWaitLog(0xff); + else IO2Hndlr_REU(Address, R_Wn); +} diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/TRBlockIO.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/TRBlockIO.c index c9dfb6d6..af7253f4 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/TRBlockIO.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/TRBlockIO.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -// The TR block device (docs/TR_Block_Device.md) in IO2, the page GeoRAM leaves free, beside the USB -// mouse's registers. The ISR side only fills registers and the window; the SD card is touched -// only from the main loop: PollTRBlock from GeoRAM's polling handler, and SDFullInit. +// The TR block device (docs/TR_Block_Device.md) beside the USB mouse's registers, in IO2 beside GeoRAM +// and in IO1 beside the REU. The ISR side only fills registers and the window; the SD card is touched +// only from the main loop: PollTRBlock from those two handlers' polling handlers, and SDFullInit. #include "../../../BlockDevice/TRBlockImage.h" @@ -68,7 +68,7 @@ FLASHMEM static bool TRBlockOpen() return true; } -// Close and reopen around an SD.begin that can run while GeoRAM has an image mounted, as SDFullInit does. +// Close and reopen around an SD.begin that can run while an image is mounted, as SDFullInit does. // SD.begin rebuilds the volume in place under any open file and drops its cached, unwritten sectors. FLASHMEM void TRBlockClose() { diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/USBMouse.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/USBMouse.c index c3612801..175f947e 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/USBMouse.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/USBMouse.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// A USB mouse on the TR's USB host port, read through registers the GeoRAM handler serves -// at $DF00, in the part of the IO2 page that GeoRAM leaves free. +// A USB mouse on the TR's USB host port, read through registers at $DF00 beside GeoRAM, in the +// part of IO2 that GeoRAM leaves free, and at $DE00 beside the REU (IOH_REU_TRPage). // It cannot be a 1351: the SID's POT lines are not on the cartridge port, so only software // with a TR driver sees it (tools/geos). // diff --git a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h index b1923116..0e8fc4ae 100644 --- a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h +++ b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h @@ -443,7 +443,12 @@ enum enumIOHandlers //Synch order/qty with IOHandler[] (IOHandlers.h) IOH_KernalReplace, #endif IOH_Debug, + #ifdef Fab04_REU + IOH_GeoRAM, + IOH_REU_TRPage, //last manually selectable, see LastSelectableIOH + #else IOH_GeoRAM, //last manually selectable, see LastSelectableIOH + #endif //*not* manually selectable, see LastSelectableIOH @@ -477,4 +482,8 @@ enum enumIOHandlers //Synch order/qty with IOHandler[] (IOHandlers.h) IOH_Num_Handlers //always last }; -#define LastSelectableIOH IOH_GeoRAM //127 max. Used in IOH_TeensyROM.c for Special IO display/select +#ifdef Fab04_REU + #define LastSelectableIOH IOH_REU_TRPage //127 max. Used in IOH_TeensyROM.c for Special IO display/select +#else + #define LastSelectableIOH IOH_GeoRAM +#endif diff --git a/Source/Teensy/tests/geos-boot.test.js b/Source/Teensy/tests/geos-boot.test.js index 995c6b4b..bb7b232e 100644 --- a/Source/Teensy/tests/geos-boot.test.js +++ b/Source/Teensy/tests/geos-boot.test.js @@ -30,6 +30,8 @@ const DIR_SLOT = 32; const SLOT_TYPE = 2; const SLOT_FIRST = 3; const SLOT_NAME = 5; +const SLOT_STRUCTURE = 23; +const VLIR = 1; const SLOT_GEOS_TYPE = 24; const PAD = 0xa0; const USR_CLOSED = 0x83; @@ -56,6 +58,9 @@ const ENTRIES = arrayOf('TRDriverEntries'); const ENTRIES_128 = arrayIn(DRIVER128_HEADER)('TRDriver128Entries'); const BOOT_ROML = arrayIn(BOOT_ROM_HEADER)('Geos128BootRoml'); const BOOT_VECTORS = arrayIn(BOOT_ROM_HEADER)('Geos128BootVectors'); +const BOOT_PAGE_AT = arrayIn(BOOT_ROM_HEADER)('Geos128BootPageAt'); +const REGS_BESIDE_GEORAM = 0xdf; +const REGS_BESIDE_REU = 0xde; const BOOT_CHIP = 0x2000; const BOOT_PARAMS = 0x21; @@ -144,10 +149,40 @@ class Disk { const header = offsetOf(DIR_TRACK, 0); this.image[header] = DIR_TRACK; this.image[header + 1] = FIRST_DIR_SECTOR; + this.slotsPerSector = SECTOR / DIR_SLOT; this.image[offsetOf(DIR_TRACK, FIRST_DIR_SECTOR) + 1] = 0xff; } addFile(name, data, geosType = 0) { + return this.addSlot(name, this.writeChain(data), geosType); + } + + // A VLIR file: an index block naming each record's chain + addVlir(name, records, geosType = 0) { + const index = Buffer.alloc(SECTOR - 2); + records.forEach((data, i) => { + const first = this.writeChain(data); + index[2 * i] = first.track; + index[2 * i + 1] = first.sector; + }); + const slot = this.addSlot(name, this.writeChain(index), geosType); + this.image[slot + SLOT_STRUCTURE] = VLIR; + return slot; + } + + // A record's bytes, read back along its chain from the index block + record(slot, i, image = this.image) { + let o = offsetOf(image[slot + SLOT_FIRST], image[slot + SLOT_FIRST + 1]); + const chunks = []; + for (let at = offsetOf(image[o + 2 + 2 * i], image[o + 3 + 2 * i]); ; ) { + const last = !image[at]; + chunks.push(image.subarray(at + 2, last ? at + image[at + 1] + 1 : at + SECTOR)); + if (last) return Buffer.concat(chunks); + at = offsetOf(image[at], image[at + 1]); + } + } + + writeChain(data) { const sectors = []; for (let at = 0; at < data.length || !sectors.length; at += SECTOR - 2) sectors.push(this.nextSector++); sectors.forEach((n, i) => { @@ -160,10 +195,15 @@ class Disk { this.image[o + 1] = last ? chunk.length + 1 : next % SECTORS_PER_TRACK; chunk.copy(this.image, o + 2); }); + return { track: FIRST_FILE_TRACK + Math.floor(sectors[0] / SECTORS_PER_TRACK), sector: sectors[0] % SECTORS_PER_TRACK }; + } + + addSlot(name, first, geosType) { + assert.ok(this.slots.length < this.slotsPerSector, 'the directory has one sector'); const slot = offsetOf(DIR_TRACK, FIRST_DIR_SECTOR) + this.slots.length * DIR_SLOT; this.image[slot + SLOT_TYPE] = USR_CLOSED; - this.image[slot + SLOT_FIRST] = FIRST_FILE_TRACK + Math.floor(sectors[0] / SECTORS_PER_TRACK); - this.image[slot + SLOT_FIRST + 1] = sectors[0] % SECTORS_PER_TRACK; + this.image[slot + SLOT_FIRST] = first.track; + this.image[slot + SLOT_FIRST + 1] = first.sector; this.image.fill(PAD, slot + SLOT_NAME, slot + SLOT_NAME + 16); this.image.write(name, slot + SLOT_NAME, 'latin1'); this.image[slot + SLOT_GEOS_TYPE] = geosType; @@ -172,8 +212,8 @@ class Disk { } } -function run(args) { - const r = spawnSync(args[0], args.slice(1), { encoding: 'utf8' }); +function run(args, env = {}) { + const r = spawnSync(args[0], args.slice(1), { encoding: 'utf8', env: { ...process.env, ...env } }); return { status: r.status, out: r.stdout, err: r.stderr }; } @@ -184,9 +224,9 @@ function withHarness(body) { assert.equal(built.status, 0, built.err); const asCpp = run(['c++', '-x', 'c++', '-std=gnu++17', '-Wall', '-Werror', '-fsyntax-only', HARNESS_SOURCE]); assert.equal(asCpp.status, 0, asCpp.err); - const launch = (disk, time = [], version = '64') => { + const launch = (disk, time = [], version = '64', env = {}) => { fs.writeFileSync(path.join(dir, 'in.d81'), disk.image); - const r = run([path.join(dir, 'harness'), version, path.join(dir, 'in.d81'), path.join(dir, 'out.prg'), path.join(dir, 'out.d81'), ...time.map(String)]); + const r = run([path.join(dir, 'harness'), version, path.join(dir, 'in.d81'), path.join(dir, 'out.prg'), path.join(dir, 'out.d81'), ...time.map(String)], env); const out = r.status === 0 ? fs.readFileSync(path.join(dir, 'out.prg')) : null; const image = r.status === 0 ? fs.readFileSync(path.join(dir, 'out.d81')) : null; const boot = r.out.match(/ boot=(\d+),(\d+)/); @@ -328,14 +368,13 @@ test('a disk with no boot file is refused', { skip }, () => { }); }); -test('CONFIGURE files stop being auto-executed, and nothing else changes', { skip }, () => { +test('configuration files other than the stock CONFIGURE stop being auto-executed, and nothing else changes', { skip }, () => { withHarness((launch) => { const disk = standardDisk(); const configure = disk.addFile('CONFIGURE', Buffer.from([1, 2, 3]), AUTO_EXEC); const second = disk.addFile('CONFIG 2.0r', Buffer.from([1, 2, 3]), AUTO_EXEC); const geos128 = ['128 CONFIGURE', '128 CONFIG 2.0r', '64 CONFIG 2.0r'].map((name) => disk.addFile(name, Buffer.from([1, 2, 3]), AUTO_EXEC)); const other = disk.addFile('pad color mgr', Buffer.from([1, 2, 3]), AUTO_EXEC); - const plain = disk.addFile('CONFIGURE.DOC', Buffer.from([1, 2, 3]), 0); const first = launch(disk); assert.equal(first.status, 0, first.err); @@ -344,7 +383,12 @@ test('CONFIGURE files stop being auto-executed, and nothing else changes', { ski assert.equal(first.image[second + SLOT_GEOS_TYPE], SYSTEM_FILE); for (const slot of geos128) assert.equal(first.image[slot + SLOT_GEOS_TYPE], SYSTEM_FILE, 'GEOS 128 names CONFIG after a prefix'); assert.equal(first.image[other + SLOT_GEOS_TYPE], AUTO_EXEC, 'other auto-exec files stay'); - assert.equal(first.image[plain + SLOT_GEOS_TYPE], 0, 'a CONFIG file that is not auto-exec stays'); + + const documented = standardDisk(); + const plain = documented.addFile('CONFIGURE.DOC', Buffer.from([1, 2, 3]), 0); + const left = launch(documented); + assert.match(left.out, /changed=0/); + assert.equal(left.image[plain + SLOT_GEOS_TYPE], 0, 'a CONFIG file that is not auto-exec stays'); const again = launch({ image: first.image }); assert.match(again.out, /changed=0/); @@ -432,6 +476,7 @@ test('the GEOS 128 boot ROM is told where the boot image goes, how many pages it assert.equal(roml.readUInt16LE(BOOT_PARAMS + 2), Math.ceil(size / SECTOR), 'a short last page counts'); assert.equal(roml.readUInt16LE(BOOT_PARAMS + 4), 0x2f3c); assert.deepEqual([...roml.subarray(BOOT_PARAMS + 6, BOOT_ROML.length)], BOOT_ROML.slice(BOOT_PARAMS + 6)); + for (const at of BOOT_PAGE_AT) assert.equal(roml[at], REGS_BESIDE_GEORAM, `the block device's page at ROML ${at}`); assert.equal(roml.subarray(BOOT_ROML.length).every((b) => b === 0), true); assert.deepEqual([...romh.subarray(BOOT_CHIP - BOOT_VECTORS.length)], BOOT_VECTORS, 'the CPU vectors end ROMH'); assert.equal(romh.subarray(0, BOOT_CHIP - BOOT_VECTORS.length).every((b) => b === 0), true, 'ROMH is not a function ROM of its own'); @@ -452,3 +497,235 @@ test('a GEOS 128 boot file that would reach I/O or the shared RAM is refused', { assert.equal(fits(0x2000, BODY_SIZE).status, 0, 'loaded right above the shared RAM'); }); }); + +test('the GEOS 128 boot ROM reaches the block device on the page the launch puts it', { skip }, () => { + withHarness((launch) => { + const r = launch(geos128Disk(), [], '128', { REGS_PAGE: REGS_BESIDE_REU.toString(16) }); + assert.equal(r.status, 0, r.err); + const roml = [...r.rom.subarray(0, BOOT_ROML.length)]; + assert.ok(BOOT_PAGE_AT.length > 0); + for (const at of BOOT_PAGE_AT) assert.equal(roml[at], REGS_BESIDE_REU, `ROML ${at}`); + const beside = launch(geos128Disk(), [], '128'); + const moved = roml.flatMap((byte, at) => (byte === beside.rom[at] ? [] : [at])); + assert.deepEqual(moved, BOOT_PAGE_AT, 'nothing else in the ROM changes with the page'); + }); +}); + +// CONFIGURE as the stock disks have it: record 0 its code from $0406, with the drive-type probe in it, and record 4 +// the 1581 driver it installs for drive A, shorter than the driver area +const STOCK_PROBE = [0xa9, 0xe5, 0x85, 0x03, 0xa9, 0x80, 0x85, 0x02, 0x20, 0x1e, 0x0c]; +const TR_PROBE = [0xa9, 0x00, 0xae, 0x89, 0x84, 0xe0, 0x08, 0xd0, 0x02, 0xa9, 0x03, 0x60]; +const PROBE_AT = { 64: 0x0c33 - 0x0406, 128: 0x0b7f - 0x0406 }; +const RECORD_0_SIZE = 3504; +const RECORD_4_SIZE = { 64: 3179, 128: 2997 }; +const PROBE_RECORD = 0; +const DRIVER_RECORD = 4; + +function configure(version, { probes = [PROBE_AT[version]], driver } = {}) { + const code = Buffer.alloc(RECORD_0_SIZE); + for (let i = 0; i < code.length; i++) code[i] = (i * 7 + 3) & 0xff; + for (const at of probes) Buffer.from(STOCK_PROBE).copy(code, at); + const stock = (version === '128' ? stockDriver128() : stockDriver()).subarray(0, RECORD_4_SIZE[version]); + return [code, ...[1, 2, 3].map((n) => Buffer.alloc(300 * n, n)), driver ?? stock, Buffer.alloc(1782, 5)]; +} + +const STOCK_CONFIGURE = { 64: 'CONFIGURE', 128: '128 CONFIGURE' }; +const diskFor = (version) => (version === '128' ? geos128Disk() : standardDisk()); +const bootDriver = (version, r) => r.prg.subarray(version === '128' ? 2 + GEOS128.driverAt : 2 + LOAD_ADDRESS - BASIC_START + DRIVER_AT_IN_BODY); + +for (const version of ['64', '128']) { + test(`GEOS ${version}: the stock CONFIGURE is set up to find the TR's drive and runs, installing the TR's driver for drive A`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const records = configure(version); + const slot = disk.addVlir(STOCK_CONFIGURE[version], records, AUTO_EXEC); + + const first = launch(disk, [], version); + assert.equal(first.status, 0, first.err); + assert.match(first.out, /changed=1/); + assert.equal(first.image[slot + SLOT_GEOS_TYPE], AUTO_EXEC); + const code = disk.record(slot, PROBE_RECORD, first.image); + const probe = Buffer.from(records[PROBE_RECORD]); + Buffer.from(TR_PROBE).copy(probe, PROBE_AT[version]); + assert.equal(code.equals(probe), true, 'the probe answers for the TR, and nothing else in record 0 changes'); + const driver = disk.record(slot, DRIVER_RECORD, first.image); + assert.equal(driver.length, RECORD_4_SIZE[version]); + assert.equal(driver.equals(bootDriver(version, first).subarray(0, driver.length)), true, 'the TR transport, as the boot image has it'); + for (const i of [1, 2, 3, 5]) assert.equal(disk.record(slot, i, first.image).equals(records[i]), true, `record ${i}`); + + const again = launch({ image: first.image }, [], version); + assert.match(again.out, /changed=0/); + assert.equal(again.image.equals(first.image), true); + }); + }); + + test(`GEOS ${version}: a copy whose stock CONFIGURE was put aside gets it set up and running again`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const slot = disk.addVlir(STOCK_CONFIGURE[version], configure(version), SYSTEM_FILE); + const r = launch(disk, [], version); + assert.equal(r.status, 0, r.err); + assert.equal(r.image[slot + SLOT_GEOS_TYPE], AUTO_EXEC); + assert.equal(disk.record(slot, PROBE_RECORD, r.image).subarray(PROBE_AT[version], PROBE_AT[version] + TR_PROBE.length).equals(Buffer.from(TR_PROBE)), true); + }); + }); + + test(`GEOS ${version}: a CONFIGURE that is not the stock one, or not as the stock one is laid out, is put aside untouched`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const other = version === '128' ? '64' : '128'; + const shortDriver = (version === '128' ? stockDriver128() : stockDriver()).subarray(0, READ_BLOCK - DRIVER_AREA - 1); + const notADriver = Buffer.from(configure(version)[DRIVER_RECORD]); + notADriver[TYPE_BYTE] = 0; + const odd = [ + ['the other version', STOCK_CONFIGURE[other], configure(version)], + ['another configuration program', 'CONFIG 2.0r', configure(version)], + ['no probe', STOCK_CONFIGURE[version], configure(version, { probes: [] })], + ['the probe twice', STOCK_CONFIGURE[version], configure(version, { probes: [PROBE_AT[version], 0x100] })], + ['record 4 not a driver', STOCK_CONFIGURE[version], configure(version, { driver: notADriver })], + ['record 4 ending before ReadBlock', STOCK_CONFIGURE[version], configure(version, { driver: shortDriver })], + ].map(([why, name, records]) => ({ why, records, slot: disk.addVlir(name, records, AUTO_EXEC) })); + const r = launch(disk, [], version); + assert.equal(r.status, 0, r.err); + for (const { why, records, slot } of odd) { + assert.equal(r.image[slot + SLOT_GEOS_TYPE], SYSTEM_FILE, why); + records.forEach((data, i) => assert.equal(disk.record(slot, i, r.image).equals(data), true, `${why}: record ${i}`)); + } + + const plain = diskFor(version); + const sequential = plain.addFile(STOCK_CONFIGURE[version], configure(version)[PROBE_RECORD], AUTO_EXEC); + assert.equal(launch(plain, [], version).image[sequential + SLOT_GEOS_TYPE], SYSTEM_FILE, 'a sequential file'); + + const more = diskFor(version); + const pinned = Buffer.from(configure(version)[DRIVER_RECORD]); + pinned.writeUInt16LE(READ_BLOCK - 1, 2 * 10); + const stillOdd = [ + ['the probe too near the end of record 0 for the TR probe', configure(version, { probes: [RECORD_0_SIZE - STOCK_PROBE.length + 2] })], + ['a vector the transport leaves alone pointing into it', configure(version, { driver: pinned })], + ['laid out as the stock one but sequential', configure(version)], + ].map(([why, records]) => ({ why, records, slot: more.addVlir(STOCK_CONFIGURE[version], records, AUTO_EXEC) })); + more.image[stillOdd[2].slot + SLOT_STRUCTURE] = 0; + const m = launch(more, [], version); + assert.equal(m.status, 0, m.err); + for (const { why, records, slot } of stillOdd) { + assert.equal(m.image[slot + SLOT_GEOS_TYPE], SYSTEM_FILE, why); + records.forEach((data, i) => assert.equal(more.record(slot, i, m.image).equals(data), true, `${why}: record ${i}`)); + } + }); + }); + + test(`GEOS ${version}: a set-up cut short after the driver was written is put aside next time`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const records = configure(version); + const slot = disk.addVlir(STOCK_CONFIGURE[version], records, AUTO_EXEC); + const first = launch(disk, [], version); + assert.equal(first.status, 0, first.err); + + const cut = { image: Buffer.from(first.image) }; + const index = offsetOf(cut.image[slot + SLOT_FIRST], cut.image[slot + SLOT_FIRST + 1]); + let at = offsetOf(cut.image[index + 2 + 2 * PROBE_RECORD], cut.image[index + 3 + 2 * PROBE_RECORD]); + for (let done = 0; ; done += SECTOR - 2) { + records[PROBE_RECORD].copy(cut.image, at + 2, done, done + (cut.image[at] ? SECTOR - 2 : cut.image[at + 1] - 1)); + if (!cut.image[at]) break; + at = offsetOf(cut.image[at], cut.image[at + 1]); + } + const driver = disk.record(slot, DRIVER_RECORD, cut.image); + const r = launch(cut, [], version); + assert.equal(r.status, 0, r.err); + assert.equal(r.image[slot + SLOT_GEOS_TYPE], SYSTEM_FILE); + assert.equal(disk.record(slot, PROBE_RECORD, r.image).equals(records[PROBE_RECORD]), true); + assert.equal(disk.record(slot, DRIVER_RECORD, r.image).equals(driver), true); + }); + }); + + test(`GEOS ${version}: a record whose last block says it holds no bytes is set up without overrunning it`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const records = configure(version); + const slot = disk.addVlir(STOCK_CONFIGURE[version], records, AUTO_EXEC); + const index = offsetOf(disk.image[slot + SLOT_FIRST], disk.image[slot + SLOT_FIRST + 1]); + let at = offsetOf(disk.image[index + 2 + 2 * PROBE_RECORD], disk.image[index + 3 + 2 * PROBE_RECORD]); + while (disk.image[at]) at = offsetOf(disk.image[at], disk.image[at + 1]); + disk.image[at + 1] = 0; + + const r = launch(disk, [], version); + assert.equal(r.status, 0, r.err); + assert.equal(r.image[slot + SLOT_GEOS_TYPE], AUTO_EXEC); + assert.equal(r.image[at + 1], 0); + assert.equal(r.image.subarray(at + 2, at + SECTOR).equals(disk.image.subarray(at + 2, at + SECTOR)), true, 'the empty last block keeps its bytes'); + }); + }); +} + +const MOUSE_NAME = 'TR USB MOUSE'; +const SLOT_INFO = 21; +const INPUT_TYPE = { 64: 10, 128: 15 }; +const MOUSE = Object.fromEntries(['64', '128'].map((version) => { + const array = arrayIn(path.join(FIRMWARE_ROOT, `GEOS/TRMouse${version}.h`)); + return [version, { code: Buffer.from(array(`TRMouse${version}Code`)), info: Buffer.from(array(`TRMouse${version}Info`)) }]; +})); + +// The TR mouse driver as a copy holds it: its code in a chain, its info block in a sector of its own +function addMouse(disk, version, code, info) { + const slot = disk.addFile(MOUSE_NAME, code, INPUT_TYPE[version]); + const at = disk.writeChain(Buffer.alloc(SECTOR - 2)); + info.copy(disk.image, offsetOf(at.track, at.sector)); + disk.image[slot + SLOT_INFO] = at.track; + disk.image[slot + SLOT_INFO + 1] = at.sector; + return { slot, info: offsetOf(at.track, at.sector) }; +} + +for (const version of ['64', '128']) { + test(`GEOS ${version}: a copy's older TR mouse driver is replaced in place by the firmware's`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + const old = Buffer.from(MOUSE[version].code.subarray(0, MOUSE[version].code.length - 30)).fill(0x60, 0, 5); + const oldInfo = Buffer.from(MOUSE[version].info).fill(0, 0x49, 0x4b); + const { slot, info } = addMouse(disk, version, old, oldInfo); + + const first = launch(disk, [], version); + assert.equal(first.status, 0, first.err); + assert.match(first.out, /mouse=1/); + const data = offsetOf(first.image[slot + SLOT_FIRST], first.image[slot + SLOT_FIRST + 1]); + assert.equal(first.image[data], 0, 'still one data block'); + assert.equal(first.image.subarray(data + 2, data + first.image[data + 1] + 1).equals(MOUSE[version].code), true, 'the firmware driver'); + assert.equal(first.image.subarray(info, info + SECTOR).equals(MOUSE[version].info), true, 'the firmware info block'); + + const again = launch({ image: first.image }, [], version); + assert.match(again.out, /mouse=0/); + assert.equal(again.image.equals(first.image), true); + }); + }); + + test(`GEOS ${version}: a copy whose TR mouse driver takes another number of blocks is refused, to be deleted`, { skip }, () => { + withHarness((launch) => { + const disk = diskFor(version); + addMouse(disk, version, Buffer.alloc(SECTOR + 44, 0x60), MOUSE[version].info); + const r = launch(disk, [], version); + assert.equal(r.status, 1); + assert.match(r.err, /TR USB MOUSE is an old one; delete this copy/); + }); + }); + + test(`GEOS ${version}: a copy whose TR mouse driver points off the disk is refused, the disk unchanged`, { skip }, () => { + withHarness((launch) => { + for (const [why, damage] of [ + ['no info block', (image, { slot }) => (image[slot + SLOT_INFO] = 0)], + ['an info block past the last track', (image, { slot }) => (image[slot + SLOT_INFO] = TRACKS + 1)], + ['a data block linked past the last track', (image, { slot }) => { + const data = offsetOf(image[slot + SLOT_FIRST], image[slot + SLOT_FIRST + 1]); + image[data] = TRACKS + 1; + image[data + 1] = 0; + }], + ]) { + const disk = diskFor(version); + const mouse = addMouse(disk, version, Buffer.from(MOUSE[version].code).fill(0x60, 0, 5), MOUSE[version].info); + damage(disk.image, mouse); + const r = launch(disk, [], version); + assert.equal(r.status, 1, why); + assert.match(r.err, /an old one; delete this copy/, why); + } + }); + }); +} diff --git a/Source/Teensy/tests/geos_boot_harness.c b/Source/Teensy/tests/geos_boot_harness.c index 291232f1..0c32b997 100644 --- a/Source/Teensy/tests/geos_boot_harness.c +++ b/Source/Teensy/tests/geos_boot_harness.c @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT // geos_boot_harness 64|128 IN.d81 OUT OUT.d81 [HOUR MINUTE]: builds the GEOS 64 boot PRG, or the GEOS 128 boot ROM -// and the boot image behind it, from the disk, retypes its CONFIGURE files, sets the clock to 2026-10-21 at -// HOUR:MINUTE (15:07 if not given), and writes both. Prints "changed=<0|1> clock=<0|1>", and for GEOS 128 -// " boot=," where BOOT's image lies in OUT; exits 1 with the refusal on stderr. +// and the boot image behind it, from the disk, sets up its CONFIGURE files, sets the clock to 2026-10-21 at +// HOUR:MINUTE (15:07 if not given), and writes both. Prints "changed=<0|1> clock=<0|1> mouse=<0|1>", the last whether +// the TR mouse driver on the disk was brought up to date, and for GEOS 128 +// " boot=," where BOOT's image lies in OUT; exits 1 with the refusal on stderr. REGS_PAGE (hex) is the +// block device's page the GEOS 128 boot ROM is built for, beside GeoRAM if not set. #include #include "../GEOS/GeosTargets.h" @@ -11,15 +13,19 @@ #define MAX_PRG (128 * 1024) static uint8_t Image[MAX_IMAGE + 1], Prg[MAX_PRG]; +static size_t Size; +// Like the SD card's file, a block past the image's end can be neither read nor written static bool ReadBlock(void *Context, uint32_t Offset, uint8_t *Block) { + if (Offset + GEOS_SECTOR_SIZE > Size) return false; memcpy(Block, (uint8_t *)Context + Offset, GEOS_SECTOR_SIZE); return true; } static bool WriteBlock(void *Context, uint32_t Offset, const uint8_t *Block) { + if (Offset + GEOS_SECTOR_SIZE > Size) return false; memcpy((uint8_t *)Context + Offset, Block, GEOS_SECTOR_SIZE); return true; } @@ -30,9 +36,9 @@ int main(int argc, char **argv) struct GeosDisk Disk = { 0 }; uint32_t Length, BootSize = 0; const uint8_t *Boot = NULL; - bool Is128, Changed, ClockSet, Built; + const char *RegsPage = getenv("REGS_PAGE"); + bool Is128, Changed, Refreshed, ClockSet, Built; FILE *File; - size_t Size; if ((argc != 5 && argc != 7) || !(File = fopen(argv[2], "rb"))) return 2; Is128 = !strcmp(argv[1], "128"); @@ -49,14 +55,15 @@ int main(int argc, char **argv) Disk.Context = Image; Disk.Read = ReadBlock; Disk.Write = WriteBlock; - Built = Is128 ? GeosBuildC128Boot(&Disk, &Now, Prg, sizeof Prg, &Boot, &BootSize, &ClockSet) - : GeosBuildBootPrg(&Disk, &Now, Prg, sizeof Prg, &Length, &ClockSet); - if (!Built || !GeosDisableConfigure(&Disk, &Changed)) + Built = GeosRefreshDriver(&Disk, &Refreshed) && GeosPrepareConfigure(&Disk, Prg, sizeof Prg, &Changed) && (Is128 ? GeosBuildC128Boot(&Disk, &Now, RegsPage ? (uint8_t)strtol(RegsPage, NULL, 16) : GEOS_REGS_BESIDE_GEORAM, + Prg, sizeof Prg, &Boot, &BootSize, &ClockSet) + : GeosBuildBootPrg(&Disk, &Now, Prg, sizeof Prg, &Length, &ClockSet)); + if (!Built) { fprintf(stderr, "%s\n", Disk.Error); return 1; } - printf("changed=%d clock=%d", Changed, ClockSet); + printf("changed=%d clock=%d mouse=%d", Changed, ClockSet, Refreshed); if (Is128) { Length = (uint32_t)(Boot - Prg) + BootSize; diff --git a/Source/Teensy/tests/menu-selection.test.js b/Source/Teensy/tests/menu-selection.test.js index c5b3aefe..29353842 100644 --- a/Source/Teensy/tests/menu-selection.test.js +++ b/Source/Teensy/tests/menu-selection.test.js @@ -32,3 +32,15 @@ test('GEOS 128, listed on every machine, starts only once the menu has reported assert.match(functionBody(source, 'LaunchGeos'), /^\{[^{}]*?\bif\s*\(\s*Launcher->Machine\s*==\s*GeosC128\s*&&\s*!\s*HostIsC128\s*\(\s*\)\s*\)\s*\{[^}]*\breturn\s+false\s*;/); }); + +test('GEOS starts under the handler the launch pointed its boot ROM at, not the item\'s own GeoRAM', () => { + // A GEOS 128 boot ROM pointed at the page beside the REU finds no block device under GeoRAM + const launcher = blankComments(fs.readFileSync(path.join(FIRMWARE_ROOT, 'GeosLauncher.ino'), 'utf8')); + assert.match(functionBody(launcher, 'GeosStart'), + /\bGeosIOHandler\s*\(\s*\)\s*==\s*IOH_GeoRAM\s*\?\s*GEOS_REGS_BESIDE_GEORAM\s*:\s*GEOS_REGS_BESIDE_REU\b/); + const handlers = blankComments(fs.readFileSync(path.join(FIRMWARE_ROOT, 'IOHandlers.ino'), 'utf8')); + const select = functionBody(handlers, 'IOHandlerSelectInit'); + const geos = select.search(/\bItemType\s*==\s*rtGEOS\s*\)\s*IOHandlerInit\s*\(\s*GeosIOHandler\s*\(\s*\)\s*\)/); + assert.notEqual(geos, -1, 'a GEOS item does not take its handler from GeosIOHandler'); + assert.ok(geos < select.indexOf('IOHndlrAssoc'), 'the item\'s own handler is chosen before GeosIOHandler is asked'); +}); diff --git a/Source/Teensy/tests/reu-trpage.test.js b/Source/Teensy/tests/reu-trpage.test.js new file mode 100644 index 00000000..ebd2f409 --- /dev/null +++ b/Source/Teensy/tests/reu-trpage.test.js @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +// The REU's IO2 page under "REU+USB Mouse/Disk", where the TR's GEOS drivers look for the block device first. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; + +import { commandExists } from '../../../tools/lib/toolchain.mjs'; + +const HARNESS_SOURCE = path.join(import.meta.dirname, 'reu_trpage_harness.cpp'); + +const NOT_CI = ['', '0', 'false']; +const inCi = !NOT_CI.includes(process.env.CI ?? ''); +const skip = !commandExists('c++') && !inCi && 'c++ is not on PATH'; + +const BANKS = 16; +const BLOCK_ID = 0x10; + +let harnessPath; +function harness() { + if (harnessPath) return harnessPath; + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'reu-trpage-')); + harnessPath = path.join(dir, 'harness'); + // the REU's DMA loops spin on an empty body, which the firmware's own build allows + const built = spawnSync('c++', ['-std=gnu++17', '-Wall', '-Wextra', '-Werror', '-Wno-empty-body', '-o', harnessPath, + HARNESS_SOURCE], { encoding: 'utf8' }); + assert.equal(built.status, 0, built.stderr); + return harnessPath; +} + +function run(lines) { + const ran = spawnSync(harness(), { input: lines.join('\n') + '\n', encoding: 'utf8' }); + assert.equal(ran.status, 0, ran.stderr); + return ran.stdout.split('\n').filter(Boolean); +} + +const read = (offset) => `r ${offset.toString(16)}`; + +test('beside an REU, its registers answer and the rest of each 32 bytes reads $FF, as a 1764\'s do', { skip }, () => { + const offsets = [0x09, 0x0a, 0x0b, BLOCK_ID, BLOCK_ID + 2, 0x1f, 0x2a, 0x20 + BLOCK_ID, 0xff]; + assert.deepEqual(run(['init 0', `banks ${BANKS}`, ...offsets.map(read)]), + ['1f', '3f', 'ff', 'ff', 'ff', 'ff', '3f', 'ff', 'ff']); +}); + +test('with no RAM for the REU, nothing in IO2 answers, so software finds no REU there', { skip }, () => { + assert.deepEqual(run(['init 0', 'banks 0', ...[0x00, 0x0a, BLOCK_ID].map(read)]), ['open', 'open', 'open']); +}); diff --git a/Source/Teensy/tests/reu_trpage_harness.cpp b/Source/Teensy/tests/reu_trpage_harness.cpp new file mode 100644 index 00000000..a0b37992 --- /dev/null +++ b/Source/Teensy/tests/reu_trpage_harness.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +// reu_trpage_harness: the REU's IO2 page as IOH_REU_TRPage serves it, driven from stdin one command a line: +// init 0 the handler starts, with no RAM to give the REU +// banks N the REU has N banks (0: none could be allocated) +// r OFFSET an IO2 read, printing the byte driven or "open" +#include +#include +#include + +#define FLASHMEM +#define RAM_Bank_Size 0x2000 +#define MaxPathLength 256 +#define SetIRQAssert +#define SetIRQDeassert +#define SetDMAAssert +#define SetDMADeassert +#define ReadGPIO6 0 +#define GP6_Phi2(Port) ((Port) & 0) +#define ARM_DWT_CYCCNT 0u +#define WaitUntil_nS(nS) +#define GPIO6_ISR Discard +#define CORE_PIN1_BITMASK 0 +#define NVIC_CLEAR_PENDING(Irq) +#define IRQ_GPIO6789 0 + +struct StructMenuItem; +enum DMA_Trans_RnW { DMA_READ, DMA_WRITE }; +enum DMA_Addr_Mode { DMA_ADDR_INCREMENT, DMA_ADDR_FIXED }; +struct stcIOHandlers +{ + char Name[20]; + void (*InitHndlr)(); + void (*IO1Hndlr)(uint8_t Address, bool R_Wn); + void (*IO2Hndlr)(uint8_t Address, bool R_Wn); + void (*ROMLHndlr)(uint32_t Address); + void (*ROMHHndlr)(uint32_t Address); + void (*PollingHndlr)(); + void (*CycleHndlr)(); +}; +static struct { template void printf(const char *, Args...) {} } Serial; + +static uint32_t Discard, StartCycCnt, LastCycCnt; +static uint32_t micros() { return 0; } +static const int eepAdREUFilename = 0; +static void EEPreadStr(int, char *Out) { *Out = 0; } +static bool (*fBusSnoop)(uint16_t Address, bool R_Wn), (*PendingfBusSnoop)(uint16_t Address, bool R_Wn); +void (*fSpecialBtnChange)(bool Up_nDn); +bool DMA_RnW, DMA_FixC64Addr; +uint32_t DMA_Length, DMA_Count, DMA_StartAddr; +StructMenuItem *DriveDirMenu; +bool DMAByte(uint8_t *) { return true; } +static uint8_t AllocRAMBanks(uint8_t **, uint8_t, uint8_t) { return 0; } +static void FreeRAMBanks(uint8_t **, uint8_t *Count) { *Count = 0; } +static void LoadRAMBanks(uint8_t **, uint8_t, const char *) {} +static void SaveRAMBanks(uint8_t **, uint8_t, const char *) {} +static void InitUSBMouse() {} +static void InitTRBlock() {} +static void PollUSBMouse() {} +static void PollTRBlock() {} +static void TRPageIO(uint8_t, bool) {} + +static int Driven; +static void DataPortWriteWaitLog(uint8_t Data) { Driven = Data; } +static uint8_t DataPortWaitRead() { return 0; } + +#include "../MinimalBoot/Common/IO_Handlers/IOH_REU.c" + +int main() +{ + char Command[16]; + unsigned Value; + + while (scanf("%15s %x", Command, &Value) == 2) + { + if (!strcmp(Command, "init")) IOHndlr_REU_TRPage.InitHndlr(); + else if (!strcmp(Command, "banks")) NumREU_Banks = Value; + else if (!strcmp(Command, "r")) + { + Driven = -1; + IOHndlr_REU_TRPage.IO2Hndlr(Value, true); + if (Driven < 0) printf("open\n"); + else printf("%02x\n", Driven); + } + else return 1; + } + return 0; +} diff --git a/docs/GEOS128_Usage.md b/docs/GEOS128_Usage.md new file mode 100644 index 00000000..74ebea92 --- /dev/null +++ b/docs/GEOS128_Usage.md @@ -0,0 +1,83 @@ +# GEOS 128 on the TeensyROM + +## What it is +* Starts GEOS 128 from your own boot disk image on the SD card, with a USB mouse, and no disk drive. + * GEOS itself is not included: you supply the disk image. +* Needs a C128. Runs on the TR and the TR+. +* Plug a USB mouse into the TeensyROM's USB host port. + * It is not a 1351 mouse: only GEOS, with the TR mouse driver the TeensyROM adds, sees it. +* Find it in the main menu: `F1` (Teensy Memory), then **Utilities**, then **GEOS 128 +C128**. + * The C128 restarts in 128 mode and GEOS 128 starts from your boot disk. + * GEOS 128 starts in 40 or 80 columns according to the 40/80 DISPLAY key, as it does from a disk drive, and can switch from its own menu. +* For GEOS 64, see [GEOS 64 on the TeensyROM](GEOS64_Usage.md). The two work the same way, each from its own folder. + +## SD card setup +* Make a folder named `/GEOS128` on the SD card. +* Copy your GEOS 128 boot disk image into it, under any file name ending in `.D81` (for example `GEOS128.D81`). + * The TeensyROM finds the boot disk by what is on it, not by its name: the image must hold `128 DESKTOP` and the boot file `GEOBOOT128`. + * D81 images only: the image with the deskTop and the applications on one disk. + * Don't end the name in `-TR` (`GEOS-TR.D81`): that name is kept for the copy described below, and is skipped. + * Versions that name their deskTop differently (Gateway, Wheels) are not found. +* If the folder holds more than one boot disk, the first one found is used. Only the first 8 `.D81` files in the folder are checked. + +## The -TR copy +* GEOS saves to the disk it starts from, so the TeensyROM never starts from your original image. +* The first time you select GEOS 128, it makes a copy next to the original with `-TR` added to the name (`GEOS128.D81` becomes `GEOS128-TR.D81`) and adds the TR USB mouse driver to the copy. Your original is not changed. +* Later launches reuse the `-TR` copy, so your saved work is still there. + * Each launch brings the copy's TR USB mouse driver up to date with the firmware's. +* To start over from your original, delete the `-TR` copy. The next launch makes a new one. + +## RAM expansion: GeoRAM or REU +GEOS 128 runs beside one of two RAM expansions, chosen by the **Special IO** setting in the Settings menu (`F8`). Either one also carries the TeensyROM's USB mouse and disk, so no other Special IO is needed. See [Selecting and associating Special IO](General_Usage.md#selecting-and-associating-special-io). + +| Special IO | RAM expansion | Boards | +|--|--|--| +| `REU+USB Mouse/Disk` | REU | TR+ only | +| anything else | GeoRAM | TR and TR+ | + +### REU (recommended on a TR+) +* Set Special IO to `REU+USB Mouse/Disk`, then select GEOS 128. +* GEOS's own `128 CONFIGURE` runs at start-up, finds the REU (128K to 512K, as much as fits) and sets it up as GEOS does a Commodore REU of that size. GEOS knows only the REU as a RAM expansion, so this is the way to give it one. +* At launch, the TeensyROM loads the REU pre-load/save image into the REU. Choose that file with `` in the file browser; the Settings menu shows the current one. If the file is not there, the REU starts empty. +* To save the REU to that file, press and release the Alt button (to the left of the menu button). + +### GeoRAM +* With any other Special IO setting, GEOS 128 runs with GeoRAM (128K to 512K, as much as fits). +* At launch, the TeensyROM loads `SD:/georam.ram` into GeoRAM. +* **(TR+ only)** To save GeoRAM, press and release the Alt button. + * The contents are written to `georam.ram` on the SD card, and the LED flashes for about 1.5 seconds once the save is done. + * The previous `georam.ram` is kept as `georam.bak`. + * If the power goes off during a save, the next launch loads the newest complete save: the one just made if it was fully written, otherwise the previous one. +* `128 CONFIGURE` runs at start-up as with the REU, but it does not find GeoRAM, so GEOS runs as it does with no RAM expansion. GeoRAM is there for programs that use it themselves. + +### Either way +* `128 CONFIGURE` is set up in the `-TR` copy to find the TeensyROM as drive A. +* Other configuration programs on the disk are kept from running at start-up, since they look for a disk drive. + +## Date and time +* GEOS starts with the date and time of the TeensyROM's real-time clock, adjusted by the Time Zone setting. + * Set the clock and time zone in the Settings menu (`F8`, page 6). See [General Usage](General_Usage.md#the-settings-menu). + * Without Ethernet, the clock can also be set over USB with the `SetRTCToken` command. See [Remote Command Protocol](ControlComms.md). + +## Messages +If GEOS 128 can't start, the TeensyROM stays at the menu and shows one of these: + +| Message | Meaning | +|--|--| +| GEOS 128 needs a C128 | The TeensyROM is in a C64. | +| GEOS 128 starts from your boot disk image on the SD card. Insert the card. | No SD card was found. | +| Copy your GEOS 128 boot disk image (.D81) to /GEOS128 on the SD card, then select this again. | There is no `/GEOS128` folder, or no D81 in it holds `128 DESKTOP`. | +| ``: not a D64, D71 or D81 without error bytes | The file's size is not that of a disk image (a D81 is 819,200 bytes). | +| ``: (other reason) | The image could not be read, or its directory is damaged. | +| Could not add the mouse driver to a copy of ``: the disk is full | No room for the driver. Free some space on your original image, or use another one. "the directory is full" is the same for directory entries. | +| Could not add the mouse driver to a copy of ``: the SD card could not be written | The copy could not be written to the SD card. | +| `-TR.D81` has no desktop; remove it to start over | The `-TR` copy has lost `128 DESKTOP`. Delete it. | +| `-TR.D81` was set aside and could not be put back | Power was lost while the copy was being updated, and the TeensyROM could not restore it. Look for `-TR.D81.BAK` in `/GEOS128`. | +| `-TR.D81`: its TR USB MOUSE is an old one; delete this copy | The copy's mouse driver is from a TeensyROM firmware whose driver cannot be updated in place. Delete the `-TR` copy; the next launch makes a new one. | +| `-TR.D81`: the boot file is not on the disk | The image has no `GEOBOOT128`. | +| `-TR.D81`: the boot file's disk driver is not one the TR can replace | This `GEOBOOT128` is not a version the TeensyROM can start. | +| `-TR.D81`: the boot file's reset key is not where this version keeps it | This `GEOBOOT128` is not a version the TeensyROM can start. | + +
+ +[Back to main ReadMe](/README.md) diff --git a/docs/GEOS64_Usage.md b/docs/GEOS64_Usage.md index 166d1342..249e1b1e 100644 --- a/docs/GEOS64_Usage.md +++ b/docs/GEOS64_Usage.md @@ -7,6 +7,7 @@ * Plug a USB mouse into the TeensyROM's USB host port. * It is not a 1351 mouse: only GEOS, with the TR mouse driver the TeensyROM adds, sees it. * Find it in the main menu: `F1` (Teensy Memory), then **Utilities**, then **GEOS 64**. +* On a C128, GEOS 128 starts the same way: see [GEOS 128 on the TeensyROM](GEOS128_Usage.md). ## SD card setup * Make a folder named `/GEOS64` on the SD card. @@ -21,16 +22,35 @@ * GEOS saves to the disk it starts from, so the TeensyROM never starts from your original image. * The first time you select GEOS 64, it makes a copy next to the original with `-TR` added to the name (`GEOS64.D81` becomes `GEOS64-TR.D81`) and adds the TR USB mouse driver to the copy. Your original is not changed. * Later launches reuse the `-TR` copy, so your saved work is still there. + * Each launch brings the copy's TR USB mouse driver up to date with the firmware's. * To start over from your original, delete the `-TR` copy. The next launch makes a new one. -## GeoRAM -* GEOS 64 runs with GeoRAM (128K to 512K, as much as fits) as its Special IO, selected automatically. +## RAM expansion: GeoRAM or REU +GEOS 64 runs beside one of two RAM expansions, chosen by the **Special IO** setting in the Settings menu (`F8`). Either one also carries the TeensyROM's USB mouse and disk, so no other Special IO is needed. See [Selecting and associating Special IO](General_Usage.md#selecting-and-associating-special-io). + +| Special IO | RAM expansion | Boards | +|--|--|--| +| `REU+USB Mouse/Disk` | REU | TR+ only | +| anything else | GeoRAM | TR and TR+ | + +### REU (recommended on a TR+) +* Set Special IO to `REU+USB Mouse/Disk`, then select GEOS 64. +* GEOS's own `CONFIGURE` runs at start-up, finds the REU (128K to 512K, as much as fits) and sets it up as GEOS does a Commodore REU of that size. GEOS knows only the REU as a RAM expansion, so this is the way to give it one. +* At launch, the TeensyROM loads the REU pre-load/save image into the REU. Choose that file with `` in the file browser; the Settings menu shows the current one. If the file is not there, the REU starts empty. +* To save the REU to that file, press and release the Alt button (to the left of the menu button). + +### GeoRAM +* With any other Special IO setting, GEOS 64 runs with GeoRAM (128K to 512K, as much as fits). * At launch, the TeensyROM loads `SD:/georam.ram` into GeoRAM. -* **(TR+ only)** To save GeoRAM, press and release the Alt button (to the left of the menu button). +* **(TR+ only)** To save GeoRAM, press and release the Alt button. * The contents are written to `georam.ram` on the SD card, and the LED flashes for about 1.5 seconds once the save is done. * The previous `georam.ram` is kept as `georam.bak`. * If the power goes off during a save, the next launch loads the newest complete save: the one just made if it was fully written, otherwise the previous one. -* GEOS's `CONFIGURE` does not run at start-up, since it looks for a disk drive, so GEOS does not set GeoRAM up as a RAM disk. +* `CONFIGURE` runs at start-up as with the REU, but it does not find GeoRAM, so GEOS runs as it does with no RAM expansion. GeoRAM is there for programs that use it themselves. + +### Either way +* `CONFIGURE` is set up in the `-TR` copy to find the TeensyROM as drive A. +* Other configuration programs on the disk (such as `CONFIG 2.0r`) are kept from running at start-up, since they look for a disk drive. ## Date and time * GEOS starts with the date and time of the TeensyROM's real-time clock, adjusted by the Time Zone setting. @@ -50,6 +70,7 @@ If GEOS 64 can't start, the TeensyROM stays at the menu and shows one of these: | Could not add the mouse driver to a copy of ``: the SD card could not be written | The copy could not be written to the SD card. | | `-TR.D81` has no desktop; remove it to start over | The `-TR` copy has lost `DESK TOP`. Delete it. | | `-TR.D81` was set aside and could not be put back | Power was lost while the copy was being updated, and the TeensyROM could not restore it. Look for `-TR.D81.BAK` in `/GEOS64`. | +| `-TR.D81`: its TR USB MOUSE is an old one; delete this copy | The copy's mouse driver is from a TeensyROM firmware whose driver cannot be updated in place. Delete the `-TR` copy; the next launch makes a new one. | | `-TR.D81`: the boot file is not on the disk | The image has no `GEOBOOT`. | | `-TR.D81`: the boot file's disk driver is not one the TR can replace | This `GEOBOOT` is not a version the TeensyROM can start. | diff --git a/docs/General_Usage.md b/docs/General_Usage.md index ce7fd584..e56be4f3 100644 --- a/docs/General_Usage.md +++ b/docs/General_Usage.md @@ -124,7 +124,7 @@ * See viewer usage/navigation doc via TR on your C64 * **.D64/.D71/.D81 files:** * Single File load/exec only (no multi-file/writebacks) - * Exception (FW 0.9.1 and later): GEOS 64 boots from a .D81 boot disk in `/GEOS64` with full disk read/write. See [GEOS 64 on the TeensyROM](GEOS64_Usage.md). + * Exception (FW 0.9.1 and later): GEOS 64 and GEOS 128 boot from a .D81 boot disk in `/GEOS64` and `/GEOS128` with full disk read/write. See [GEOS 64 on the TeensyROM](GEOS64_Usage.md) and [GEOS 128 on the TeensyROM](GEOS128_Usage.md). * **.HEX files:** * Used for TeensyROM firmware updates (see below) @@ -261,6 +261,13 @@ * The setting stays in memory and will be re-loaded for any app until changed * We recommend disabling NFC if Special IO is selected to avoid interference. * If a selected CRT file is associated with different Special IO (i.e. Epyx, EasyFlash etc), that Special IO will be loaded instead + * RAM expansions: + * **GeoRAM**: Berkeley Softworks geoRAM, 128K to 512K (as much as fits). Its window is at `$DE00` and its registers at `$DFFE`/`$DFFF`. The TeensyROM's USB mouse and [block device](TR_Block_Device.md) registers sit beside it at `$DF00`. + * Loads `SD:/georam.ram` at launch. **(TR+ only)** Press and release the Alt button to save it there; the previous save is kept as `georam.bak`. + * **512KB REU** **(TR+ only)**: a Commodore REU (1700/1764/1750) at `$DF00`, 128K to 512K (as much as fits). + * Loads the REU pre-load/save image at launch (choose it with `` in the file browser). Press and release the Alt button to save it there. + * **REU+USB Mouse/Disk** **(TR+ only)**: the same REU at `$DF00`, with the USB mouse and block device registers moved to `$DE00`. The REU's unused registers read `$FF`, as a 1764's do. + * Made for GEOS: with it selected, GEOS 64 and GEOS 128 run with the REU, which GEOS's own CONFIGURE finds. With any other setting they run with GeoRAM. See [GEOS 64](GEOS64_Usage.md#ram-expansion-georam-or-reu) and [GEOS 128](GEOS128_Usage.md#ram-expansion-georam-or-reu). ## Firmware updates TR and TR+ have separate firmware files, so first pick the one for your hardware: diff --git a/docs/TR_Block_Device.md b/docs/TR_Block_Device.md index 9f88596c..e29479c5 100644 --- a/docs/TR_Block_Device.md +++ b/docs/TR_Block_Device.md @@ -19,13 +19,13 @@ This document is the whole contract. The firmware side is [`TRBlock.h`](../Sourc ## What it is and is not * One image at a time, chosen by the firmware (for GEOS, when the menu launches it). The C64 cannot choose or replace the image. * Blocks are 256 bytes. A block is addressed by two bytes whose meaning depends on the image kind ([Disk addresses](#disk-addresses)). -* It is available while the GeoRAM handler is active, which is also where the USB mouse registers are. It is not available under other handlers, the REU included, and not under a Host Extension. +* It is available while the GeoRAM handler or the `REU+USB Mouse/Disk` handler (TR+ only) is active, which is also where the USB mouse registers are. It is not available under other handlers, the plain REU included, and not under a Host Extension. * It is not an IEC drive. Software that talks to the IEC bus cannot see it. ## Where the registers are -The registers share IO2, the page GeoRAM leaves free, with the USB mouse registers, which start the page and identify it. The base `B` is `$DF00`. +The registers share a page with the USB mouse registers, which start it and identify it. The base `B` is `$DF00` beside GeoRAM, in the IO2 page GeoRAM leaves free, and `$DE00` beside the REU, whose registers repeat across IO2. -The block device is present when `TRB` is at `B+$10..B+$12`. A handler that does not carry it leaves those bytes unanswered, which reads as whatever the bus last held, so check all three bytes. +The block device is present when `TRB` is at `B+$10..B+$12`. A handler that does not carry it leaves those bytes unanswered, which reads as whatever the bus last held, so check all three bytes. Look at `$DF10` before `$DE10`: beside the REU, `$DF10..$DF12` read `$FF`, as a 1764's unused registers do, while `$DE10` beside GeoRAM is its window, which can hold any bytes. Everything below is an offset from `B`. @@ -116,7 +116,7 @@ The firmware does this arithmetic so that a 6502 driver does not have to. A driv ## A reader's checklist A driver that follows this document: -* checks `TRB` at `$DF10`; +* checks `TRB` at `$DF10`, then at `$DE10`; * writes TOKEN, the address, PAGE, then CMD; * reads STATUS until two reads agree and the value is not `$80`, then checks ECHO; * moves a block as four PAGEs of the window; diff --git a/tools/geos/geos128boot-run.test.mjs b/tools/geos/geos128boot-run.test.mjs index ce68d4df..64a9b389 100644 --- a/tools/geos/geos128boot-run.test.mjs +++ b/tools/geos/geos128boot-run.test.mjs @@ -14,7 +14,10 @@ const MMU_RAM_SHARE = 0xd506; const KERNAL_RESET_SHARE = 0x04; const FUNCTION_ROM_AT_8000 = 0x08; const COLD_START = 0x8000; -const BLOCK = 0xdf00; +const BESIDE_GEORAM = 0xdf; +const BESIDE_REU = 0xde; +const REU_REGISTERS = 0x0b; +const REU_GHOSTS = 0x1f; const STATUS = { BUSY: 0x80, DONE: 0x5a, PAST_END: 0xe2 }; const CMD_BOOT = 5; const PAGE = 256; @@ -30,12 +33,13 @@ function readHeader() { vectors: define('GEOS128_BOOT_VECTORS'), roml: bytes('Geos128BootRoml'), romVectors: bytes('Geos128BootVectors'), + pageAt: [...text.match(/Geos128BootPageAt\[\d+\] PROGMEM = \{([^}]*)\}/)[1].matchAll(/0x[0-9a-f]+/g)].map((m) => parseInt(m[0], 16)), }; } -// The cartridge as GeosBuildC128Boot fills it: ROML with the load address, page count and entry patched in, ROMH -// holding only the vectors. -function cartridge({ load, pages, entry }) { +// The cartridge as GeosBuildC128Boot fills it: ROML with the load address, page count, entry and the block device's +// page patched in, ROMH holding only the vectors. +function cartridge({ load, pages, entry, page }) { const h = readHeader(); const roml = new Uint8Array(h.chipSize); const romh = new Uint8Array(h.chipSize); @@ -45,10 +49,11 @@ function cartridge({ load, pages, entry }) { roml[h.params + 2 * i] = word & 0xff; roml[h.params + 2 * i + 1] = word >> 8; } + for (const at of h.pageAt) roml[at] = page; return { roml, romh }; } -// The block device's registers at $DF00 as docs/TR_Block_Device.md describes them, serving BOOT from image. +// The block device's registers as docs/TR_Block_Device.md describes them, serving BOOT from image. // busyReads: STATUS reads $80 this many times after each CMD. staleEcho: for this many ECHO reads after STATUS turns // done, the device still shows the previous command's result, its token and its page. glitchEvery: every nth read // comes back with bits ORed in, as the Kawari does. echoGlitch: ORed into the first stale ECHO read. @@ -114,8 +119,11 @@ function blockDevice(image, { busyReads = 0, staleEcho = 0, glitchEvery = 0, ech } // A C128 as the boot ROM sees it: two 64K RAM banks behind the MMU's configuration register, the shared RAM at the -// bottom, I/O at $D000 (the block device at $DF00) and the cartridge's function ROM at $8000 and $C000. -function c128(cart, device) { +// bottom, I/O at $D000 (the block device on its page, and beside an REU, the REU's registers at $DF00) and the +// cartridge's function ROM at $8000 and $C000. +function c128(cart, device, page) { + const block = page << 8; + const reu = (offset) => ((offset & REU_GHOSTS) < REU_REGISTERS ? 0 : 0xff); const ram = [new Uint8Array(0x10000), new Uint8Array(0x10000)]; const io = new Map([[MMU_RAM_SHARE, KERNAL_RESET_SHARE]]); const ioWrites = []; @@ -143,7 +151,8 @@ function c128(cart, device) { read(address) { if (address === MMU_CR) return machine.cr; if (ioMapped(address)) { - if (address >= BLOCK) return device.read(address - BLOCK); + if ((address & 0xff00) === block) return device.read(address - block); + if (page === BESIDE_REU && address >= BESIDE_GEORAM << 8) return reu(address & 0xff); return io.get(address) ?? 0xff; } const rom = romAt(address); @@ -158,7 +167,7 @@ function c128(cart, device) { } if (ioMapped(address)) { ioWrites.push([address, value]); - if (address >= BLOCK) device.write(address - BLOCK, value); + if ((address & 0xff00) === block) device.write(address - block, value); else io.set(address, value); return; } @@ -177,9 +186,9 @@ function bootImage(pages, last = PAGE) { } // The kernal's cold start calls the function ROM with IRQs on and the ROM mapped at $8000. -function boot({ load = 0x2b19, pages = 3, entry = 0x2f3c, image = bootImage(pages), device: options, limit } = {}) { +function boot({ load = 0x2b19, pages = 3, entry = 0x2f3c, image = bootImage(pages), device: options, limit, page = BESIDE_GEORAM } = {}) { const device = blockDevice(image, options); - const machine = c128(cartridge({ load, pages, entry }), device); + const machine = c128(cartridge({ load, pages, entry, page }), device, page); const cpu = new Mos6502(machine.bus); cpu.pc = COLD_START; cpu.p &= ~0x04; @@ -208,6 +217,13 @@ test('the boot ROM pulls every page into bank 1 and enters GEOS with I/O in, the assert.equal(b.device.commands, 150); }); +test('the boot ROM pulls the image from the block device beside an REU, and writes nothing to the REU', () => { + const b = boot({ pages: 5, page: BESIDE_REU }); + run(b, entered); + assertLoaded(b); + assert.deepEqual(b.machine.ioWrites.filter(([a]) => a >= BESIDE_GEORAM << 8), []); +}); + test('the boot ROM writes no bank 0 RAM outside the shared bottom 8K, and nothing in bank 1 but the image', () => { const b = boot(); run(b, entered); diff --git a/tools/geos/geos128boot.asm b/tools/geos/geos128boot.asm index 4aa43a5e..c3857430 100644 --- a/tools/geos/geos128boot.asm +++ b/tools/geos/geos128boot.asm @@ -2,7 +2,7 @@ ; A C128 function ROM that starts GEOS 128 with no drive: it pulls the boot image the firmware built through the ; TR block device's BOOT command (docs/TR_Block_Device.md) into RAM bank 1, then enters it as GEOS's own ROM ; loader does. geos128boot.mjs turns it into Source/Teensy/GEOS/Geos128Boot.h; build with acme. -; The firmware fills in bootLoad, bootPages and bootEntry. +; The firmware fills in bootLoad, bootPages and bootEntry, and puts the block device's page in every register address. MMU_CR = $ff00 MMU_RAM_SHARE = $d506 @@ -22,7 +22,8 @@ DEVICE_8 = 8 ; in the common RAM the loader shares between the banks, where #63's loader ran LOADER = $1300 -BLOCK = $df00 +!ifndef BLOCK_PAGE { BLOCK_PAGE = $df } +BLOCK = BLOCK_PAGE * 256 REG_CMD = BLOCK + $08 REG_ADDR0 = BLOCK + $09 REG_ADDR1 = BLOCK + $0a diff --git a/tools/geos/geos128boot.mjs b/tools/geos/geos128boot.mjs index eb00e7d8..b62d261b 100644 --- a/tools/geos/geos128boot.mjs +++ b/tools/geos/geos128boot.mjs @@ -1,7 +1,8 @@ #!/usr/bin/env node // SPDX-License-Identifier: MIT // node tools/geos/geos128boot.mjs [OUT.h]: assembles the GEOS 128 boot ROM (geos128boot.asm) and emits it for the -// firmware, by default to Source/Teensy/GEOS/Geos128Boot.h. Needs ACME (found as the C64 build finds it). +// firmware, by default to Source/Teensy/GEOS/Geos128Boot.h, with where in ROML the block device's page goes. Needs +// ACME (found as the C64 build finds it). import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; @@ -17,12 +18,17 @@ const ROML = 0x8000; const ROMH = 0xc000; const CHIP_MASK = 0x1fff; const BYTES_PER_LINE = 16; +const WORDS_PER_LINE = 8; +// the block device's page beside GeoRAM, as the source assembles it, and one it can move to +const PAGE = 0xdf; +const OTHER_PAGE = 0xde; -function assemble() { +function assemble(page = PAGE) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'geos128boot-')); try { const out = path.join(dir, 'rom.bin'), labels = path.join(dir, 'rom.lbl'); - const r = spawnSync(resolveAcme(path.join(ROOT, 'tools/.cache')), ['-f', 'plain', '-o', out, '-l', labels, SOURCE], { encoding: 'utf8' }); + const args = [`-DBLOCK_PAGE=${page}`, '-f', 'plain', '-o', out, '-l', labels, SOURCE]; + const r = spawnSync(resolveAcme(path.join(ROOT, 'tools/.cache')), args, { encoding: 'utf8' }); if (r.status !== 0 || r.stderr) throw new Error(`acme: ${r.stdout}${r.stderr}`); const found = Object.fromEntries([...fs.readFileSync(labels, 'utf8').matchAll(/^\s*(\w+)\s*=\s*\$([0-9a-f]+)/gm)] .map((m) => [m[1], parseInt(m[2], 16)])); @@ -42,6 +48,24 @@ function cArray(name, bytes) { const hex4 = (n) => `0x${n.toString(16).padStart(4, '0')}`; +function cWords(name, words) { + const rows = []; + for (let i = 0; i < words.length; i += WORDS_PER_LINE) rows.push(' ' + words.slice(i, i + WORDS_PER_LINE).map(hex4).join(', ')); + return `static const uint16_t ${name}[${words.length}] PROGMEM = {\n${rows.join(',\n')}\n};\n`; +} + +// The ROML offsets that hold the block device's page: every byte that moves with it, and moves by exactly that +function pageOffsets(code) { + const moved = assemble(OTHER_PAGE).rom.subarray(0, code.length); + const offsets = []; + code.forEach((byte, i) => { + if (byte === moved[i]) return; + if (byte !== PAGE || moved[i] !== OTHER_PAGE) throw new Error(`ROML byte ${hex4(i)} changes with the page but is not it`); + offsets.push(i); + }); + return offsets; +} + export function header() { const { rom, labels } = assemble(); const code = rom.subarray(0, labels.loaderEnd - ROML); @@ -59,7 +83,8 @@ export function header() { + `#define GEOS128_BOOT_PARAMS ${hex4(labels.loaderRom - ROML)} // load address, page count, entry: words in ROML\n` + `#define GEOS128_BOOT_VECTORS ${hex4((labels.return - ROMH) & CHIP_MASK)} // in ROMH\n` + cArray('Geos128BootRoml', code) - + cArray('Geos128BootVectors', vectors); + + cArray('Geos128BootVectors', vectors) + + cWords('Geos128BootPageAt', pageOffsets(code)); } if (process.argv[1] === fileURLToPath(import.meta.url)) { diff --git a/tools/geos/trdrive-run.test.mjs b/tools/geos/trdrive-run.test.mjs new file mode 100644 index 00000000..b57b21d6 --- /dev/null +++ b/tools/geos/trdrive-run.test.mjs @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { Mos6502 } from '../lib/mos6502.mjs'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const GEOS_DIR = path.join(root, 'Source/Teensy/GEOS'); + +const ENTRY = { ReadBlock: 7, WriteBlock: 8 }; +const R1 = 0x04; +const R4 = 0x0a; +const BUFFER = 0x8000; +const CALLER = 0x0200; +const ERR_DEV_NOT_FOUND = 13; +const BESIDE_GEORAM = 0xdf00; +const BESIDE_REU = 0xde00; +const REU_REGISTERS = 0x0b; +const REU_GHOSTS = 0x1f; +const PAGE = 256; +const QUARTER = 64; + +function readDriver(header, prefix) { + const text = fs.readFileSync(path.join(GEOS_DIR, header), 'utf8'); + const array = (name) => [...text.match(new RegExp(`${name}\\[\\d+\\] PROGMEM = \\{([^}]*)\\}`))[1].matchAll(/0x[0-9a-f]+/g)].map((m) => parseInt(m[0], 16)); + const base = parseInt(text.match(/#define TR_DRIVER\w*_BASE (0x[0-9a-f]+)/)[1], 16); + return { base, code: array(`${prefix}Code`), entries: array(`${prefix}Entries`) }; +} + +const DRIVERS = [ + ['GEOS 64', readDriver('TRDriver.h', 'TRDriver')], + ['GEOS 128', readDriver('TRDriver128.h', 'TRDriver128')], +]; + +// The TR block device (docs/TR_Block_Device.md) answering READ and WRITE at once, its blocks keyed by ADDR1:ADDR0. +// A garbled device reads each window byte back wrong once after it is written, as some machines latch it. +function blockDevice(blocks, { garbled = false } = {}) { + const regs = { addr0: 0, addr1: 0, page: 0, token: 0 }; + const unsettled = new Set(); + let buffer = new Uint8Array(PAGE), status = 0, echo = 0; + return { + write(offset, value) { + if (offset >= 0x40 && offset < 0x80) { + buffer[regs.page * QUARTER + offset - 0x40] = value; + if (garbled) unsettled.add(offset); + } + else if (offset === 0x09) regs.addr0 = value; + else if (offset === 0x0a) regs.addr1 = value; + else if (offset === 0x0b) regs.page = value & 3; + else if (offset === 0x0c) regs.token = value; + else if (offset === 0x08) { + const key = (regs.addr1 << 8) | regs.addr0; + if (value === 1) buffer = Uint8Array.from(blocks.get(key) ?? new Uint8Array(PAGE)); + if (value === 2) blocks.set(key, Uint8Array.from(buffer)); + status = 0x5a; + echo = regs.token; + } + }, + read(offset) { + if (unsettled.delete(offset)) return buffer[regs.page * QUARTER + offset - 0x40] ^ 0xff; + if (offset >= 0x40 && offset < 0x80) return buffer[regs.page * QUARTER + offset - 0x40]; + if (offset === 0x0d) return status; + if (offset === 0x0e) return echo; + if (offset >= 0x10 && offset < 0x13) return 'TRB'.charCodeAt(offset - 0x10); + return 0; + }, + }; +} + +// What answers at $DF00 beside an REU: its registers, holding what a program left in them, ghosted every 32 bytes, +// the unused ones reading $FF +function reuRegisters(regs) { + return { + write() {}, + read: (offset) => ((offset & REU_GHOSTS) < REU_REGISTERS ? regs[offset & REU_GHOSTS] : 0xff), + }; +} + +// GeoRAM's window: RAM, holding whatever the page it shows holds +function geoRamWindow(bytes) { + return { write: (offset, value) => (bytes[offset] = value), read: (offset) => bytes[offset] }; +} + +function machine(driver, pages) { + const ram = new Uint8Array(0x10000); + const ioWrites = []; + ram.set(driver.code, driver.base); + const bus = { + read: (address) => (pages[address & 0xff00] ? pages[address & 0xff00].read(address & 0xff) : ram[address]), + write(address, value) { + const page = pages[address & 0xff00]; + if (!page) ram[address] = value; + else { + ioWrites.push(address); + page.write(address & 0xff, value); + } + }, + }; + return { ram, ioWrites, pages, driver, bus }; +} + +function call(m, entry, track, sector) { + const cpu = new Mos6502(m.bus); + const at = m.driver.base + m.driver.entries[entry]; + m.ram.set([0x20, at & 0xff, at >> 8], CALLER); + m.ram.set([track, sector], R1); + m.ram.set([BUFFER & 0xff, BUFFER >> 8], R4); + cpu.pc = CALLER; + cpu.run((c) => c.pc === CALLER + 3); + return cpu.x; +} + +const sectorOf = (seed) => Uint8Array.from({ length: PAGE }, (_, i) => (i * 13 + seed) & 0xff); +const key = (track, sector) => (sector << 8) | track; + +for (const [name, driver] of DRIVERS) { + test(`${name}: the driver reads through the registers beside GeoRAM, not the GeoRAM window that shows "TRB" too`, () => { + const window = new Uint8Array(PAGE); + window.set([...'TRB'].map((c) => c.charCodeAt(0)), 0x10); + const m = machine(driver, {}); + m.pages[BESIDE_GEORAM] = blockDevice(new Map([[key(1, 2), sectorOf(1)]])); + m.pages[BESIDE_REU] = geoRamWindow(window); + assert.equal(call(m, ENTRY.ReadBlock, 1, 2), 0); + assert.deepEqual(m.ram.subarray(BUFFER, BUFFER + PAGE), sectorOf(1)); + assert.deepEqual(m.ioWrites.filter((a) => a < BESIDE_GEORAM), [], 'nothing written to the GeoRAM window'); + }); + + test(`${name}: the driver reads and writes through the registers beside an REU, whatever the REU's own registers hold`, () => { + const blocks = new Map([[key(3, 4), sectorOf(2)]]); + const m = machine(driver, {}); + m.pages[BESIDE_REU] = blockDevice(blocks); + m.pages[BESIDE_GEORAM] = reuRegisters([0x10, 0x00, 0x54, 0x52, 0x42, 0x54, 0x52, 0x42, 0, 0, 0]); + assert.equal(call(m, ENTRY.ReadBlock, 3, 4), 0); + assert.deepEqual(m.ram.subarray(BUFFER, BUFFER + PAGE), sectorOf(2)); + + m.ram.set(sectorOf(3), BUFFER); + assert.equal(call(m, ENTRY.WriteBlock, 5, 6), 0); + assert.deepEqual(blocks.get(key(5, 6)), sectorOf(3)); + assert.deepEqual(m.ioWrites.filter((a) => a >= BESIDE_GEORAM), [], 'nothing written to the REU'); + }); + + test(`${name}: a window byte beside an REU that reads back wrong once is checked again on that page`, () => { + const blocks = new Map(); + const m = machine(driver, {}); + m.pages[BESIDE_REU] = blockDevice(blocks, { garbled: true }); + m.pages[BESIDE_GEORAM] = reuRegisters(new Array(REU_REGISTERS).fill(0)); + m.ram.set(sectorOf(6), BUFFER); + assert.equal(call(m, ENTRY.WriteBlock, 7, 8), 0); + assert.deepEqual(blocks.get(key(7, 8)), sectorOf(6)); + }); + + test(`${name}: the driver finds the registers again when they move, and reports a missing device`, () => { + const m = machine(driver, {}); + m.pages[BESIDE_GEORAM] = blockDevice(new Map([[key(1, 0), sectorOf(4)]])); + assert.equal(call(m, ENTRY.ReadBlock, 1, 0), 0); + + m.pages[BESIDE_GEORAM] = reuRegisters(new Array(REU_REGISTERS).fill(0)); + m.pages[BESIDE_REU] = blockDevice(new Map([[key(1, 0), sectorOf(5)]])); + assert.equal(call(m, ENTRY.ReadBlock, 1, 0), 0); + assert.deepEqual(m.ram.subarray(BUFFER, BUFFER + PAGE), sectorOf(5)); + + m.pages[BESIDE_REU] = reuRegisters(new Array(REU_REGISTERS).fill(0)); + assert.equal(call(m, ENTRY.ReadBlock, 1, 0), ERR_DEV_NOT_FOUND); + }); +} diff --git a/tools/geos/trdrive.asm b/tools/geos/trdrive.asm index a2d74e03..32017c2d 100644 --- a/tools/geos/trdrive.asm +++ b/tools/geos/trdrive.asm @@ -17,7 +17,7 @@ HEADER_SWAP = $8c HEADER_FIRST = 4 HEADER_END = $1d -; offsets from BASE_GEOS_RAM's page +; offsets from the registers' page REG_CMD = $08 REG_ADDR0 = $09 REG_ADDR1 = $0a @@ -51,7 +51,10 @@ IO_IN_BANK_1 = $7e VIC_CLOCK = $d030 CLOCK_1MHZ = 0 -BASE_GEOS_RAM = $df +; beside GeoRAM, and beside the REU, whose unused registers read $FF across $DF0B-$DF1F +PAGE_BESIDE_GEORAM = $df +PAGE_BESIDE_REU = $de +REG_ID = $10 * = ORG @@ -189,8 +192,10 @@ readPage jsr putReg ldx #0 windowRead - lda BASE_GEOS_RAM * 256 + WINDOW,x - cmp BASE_GEOS_RAM * 256 + WINDOW,x +windowLoad + lda PAGE_BESIDE_GEORAM * 256 + WINDOW,x +windowCheck + cmp PAGE_BESIDE_GEORAM * 256 + WINDOW,x bne windowRead sta (r4),y iny @@ -218,10 +223,13 @@ writePage ldx #0 windowWrite lda (r4),y - sta BASE_GEOS_RAM * 256 + WINDOW,x - cmp BASE_GEOS_RAM * 256 + WINDOW,x +windowStore + sta PAGE_BESIDE_GEORAM * 256 + WINDOW,x +windowVerify + cmp PAGE_BESIDE_GEORAM * 256 + WINDOW,x beq windowStored - cmp BASE_GEOS_RAM * 256 + WINDOW,x +windowRetry + cmp PAGE_BESIDE_GEORAM * 256 + WINDOW,x bne windowFault windowStored iny @@ -333,25 +341,48 @@ swapField swapped rts -; carry clear when the TRB id answers +; carry clear when the TRB id answers on either page, with every register access pointed at that page findDevice + ldx #PAGE_BESIDE_GEORAM + jsr hasId + bcc usePage + ldx #PAGE_BESIDE_REU + jsr hasId + bcs absent +usePage + stx getReg + 2 + stx putReg + 2 + stx windowLoad + 2 + stx windowCheck + 2 + stx windowStore + 2 + stx windowVerify + 2 + stx windowRetry + 2 +absent + rts + +; X = page; carry clear when the TRB id reads there +hasId + stx idLoad + 2 + stx idCheck + 2 ldy #2 probe - lda BASE_GEOS_RAM * 256 + $10,y - cmp BASE_GEOS_RAM * 256 + $10,y +idLoad + lda PAGE_BESIDE_GEORAM * 256 + REG_ID,y +idCheck + cmp PAGE_BESIDE_GEORAM * 256 + REG_ID,y bne probe cmp trbId,y - bne absent + bne notHere dey bpl probe clc rts -absent +notHere sec rts getReg - lda BASE_GEOS_RAM * 256,x + lda PAGE_BESIDE_GEORAM * 256,x rts ; a read that comes back the same twice; on some machines the TR's byte arrives with the last one's bits ORed in @@ -364,7 +395,7 @@ getStable rts putReg - sta BASE_GEOS_RAM * 256,x + sta PAGE_BESIDE_GEORAM * 256,x rts trbId !byte $54, $52, $42 diff --git a/tools/geos/trmouse-run.test.mjs b/tools/geos/trmouse-run.test.mjs new file mode 100644 index 00000000..7b35ba31 --- /dev/null +++ b/tools/geos/trmouse-run.test.mjs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { Mos6502 } from '../lib/mos6502.mjs'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +const UPDATE_MOUSE = 6; +const MOUSE_ON = 0x30; +const POINTER_ON = 0x80; +const MOUSE_X = 0x3a; +const MOUSE_Y = 0x3c; +const PRESS_FLAG = 0x39; +const MOUSE_BIT = 0x20; +const LEFT_BUTTON = 0x01; +const CALLER = 0x0200; +const BESIDE_GEORAM = 0xdf00; +const BESIDE_REU = 0xde00; +const REU_REGISTERS = 0x0b; +const REU_GHOSTS = 0x1f; +const START = 100; + +const id = (text) => [...text].map((c) => c.charCodeAt(0)); + +function readDriver(header, prefix, base) { + const text = fs.readFileSync(path.join(root, 'Source/Teensy/GEOS', header), 'utf8'); + const code = [...text.match(new RegExp(`${prefix}Code\\[\\d+\\] PROGMEM = \\{([^}]*)\\}`))[1].matchAll(/0x[0-9a-f]+/g)].map((m) => parseInt(m[0], 16)); + return { base, code }; +} + +const DRIVERS = [ + ['GEOS 64', readDriver('TRMouse64.h', 'TRMouse64', 0xfe80)], + ['GEOS 128', readDriver('TRMouse128.h', 'TRMouse128', 0xfd00)], +]; + +// The TR's page as USBMouse.c and the block device serve it: X and Y movement, the buttons, "TRM", and "TRB" at $10 +function trPage(dx, dy, buttons = 0) { + const page = new Uint8Array(256); + page.set([dx & 0xff, dy & 0xff, buttons, ...id('TRM')]); + page.set(id('TRB'), 0x10); + return (offset) => page[offset]; +} + +// An REU's registers at $DF00, holding what a program left in them, ghosted every 32 bytes, the unused ones reading $FF +const reuRegisters = (regs) => (offset) => ((offset & REU_GHOSTS) < REU_REGISTERS ? regs[offset & REU_GHOSTS] : 0xff); + +function update(driver, pages) { + const ram = new Uint8Array(0x10000); + ram.set(driver.code, driver.base); + ram.set([0x20, (driver.base + UPDATE_MOUSE) & 0xff, (driver.base + UPDATE_MOUSE) >> 8], CALLER); + ram[MOUSE_ON] = POINTER_ON; + ram[MOUSE_X] = START; + ram[MOUSE_Y] = START; + const cpu = new Mos6502({ + read: (a) => (pages[a & 0xff00] ? pages[a & 0xff00](a & 0xff) : ram[a]), + write: (a, v) => { + if (pages[a & 0xff00]) throw new Error(`the driver wrote $${a.toString(16)}`); + ram[a] = v; + }, + }); + cpu.pc = CALLER; + cpu.run((c) => c.pc === CALLER + 3); + return { x: ram[MOUSE_X] - START, y: ram[MOUSE_Y] - START, pressed: (ram[PRESS_FLAG] & MOUSE_BIT) !== 0 }; +} + +for (const [name, driver] of DRIVERS) { + test(`${name}: the mouse moves by the registers beside GeoRAM, not by a GeoRAM window that shows the TR's IDs`, () => { + const window = new Uint8Array(256); + window.set([9, 9, 0, ...id('TRM')]); + window.set(id('TRB'), 0x10); + assert.deepEqual(update(driver, { [BESIDE_GEORAM]: trPage(3, 4), [BESIDE_REU]: (o) => window[o] }), { x: 3, y: 4, pressed: false }); + }); + + test(`${name}: the mouse moves by the registers beside an REU, even when the REU's address registers hold "TRM"`, () => { + const reu = reuRegisters([0x10, 0, 9, ...id('TRM'), 0, 0, 0, 0, 0]); + assert.deepEqual(update(driver, { [BESIDE_GEORAM]: reu, [BESIDE_REU]: trPage(5, 6) }), { x: 5, y: 6, pressed: false }); + }); + + test(`${name}: the button reads from the registers beside an REU, not from the REU's own register at the same offset`, () => { + const reu = reuRegisters([0x10, 0, 0, ...id('TRM'), 0, 0, 0, 0, 0]); + assert.equal(update(driver, { [BESIDE_GEORAM]: reu, [BESIDE_REU]: trPage(0, 0, LEFT_BUTTON) }).pressed, true); + }); + + test(`${name}: with no TR registers on either page the mouse stays put`, () => { + const reu = reuRegisters([0x10, 0, 9, ...id('TRM'), 0, 0, 0, 0, 0]); + assert.deepEqual(update(driver, { [BESIDE_GEORAM]: reu, [BESIDE_REU]: () => 7 }), { x: 0, y: 0, pressed: false }); + }); +} diff --git a/tools/geos/trmouse128.asm b/tools/geos/trmouse128.asm index 04e3a0e1..0d35995a 100644 --- a/tools/geos/trmouse128.asm +++ b/tools/geos/trmouse128.asm @@ -17,11 +17,14 @@ BUTTON_UP = $80 NO_DIRECTION = $ff OFF_TOP = 228 ; the kernal reads a mouseYPos this high as above the top -regs = $df00 +; beside GeoRAM, and beside the REU, whose unused registers read $FF across $DF0B-$DF1F +PAGE_BESIDE_GEORAM = $df +PAGE_BESIDE_REU = $de regX = 0 regY = 1 regButtons = 2 regID = 3 +regBlockID = $10 ; the TR block device's, which shares the page and tells it from an REU's LEFT_BUTTON = $01 !ifndef MOUSE_BASE { MOUSE_BASE = $fd00 } @@ -46,36 +49,50 @@ SlowMouse SetMouse rts -; The ID tells whether the registers are there, beside GeoRAM. -; $DF03-$DF05 are REU address registers, which have no side effects when read. -; Carry set: what reads there is not a mouse. +; The block device's ID picks the page: beside an REU, $DF03-$DF05 are its address registers and can hold anything. +; Carry set: what reads there is not a mouse; otherwise every register read points at the page. FindRegs - ldx #2 -- lda regs+regID,x - cmp trID,x + ldx #PAGE_BESIDE_GEORAM + ldy #2 +- lda PAGE_BESIDE_GEORAM*256+regBlockID,y + cmp trbID,y bne + - dex + dey + bpl - + bmi ++ ++ ldx #PAGE_BESIDE_REU +++ stx mouseID+2 + stx readX+2 + stx readY+2 + stx readButtons+2 + ldy #2 +- +mouseID lda PAGE_BESIDE_GEORAM*256+regID,y + cmp trmID,y + bne + + dey bpl - clc rts + sec rts -trID !text "TRM" +trmID !text "TRM" +trbID !text "TRB" UpdateMouse jsr FindRegs bcs noMouse - lda regs+regX +readX lda PAGE_BESIDE_GEORAM*256+regX sta deltaX - lda regs+regY +readY lda PAGE_BESIDE_GEORAM*256+regY sta deltaY bit mouseOn bmi readButtons noMouse rts ; movement while the pointer is off is dropped readButtons - lda regs+regButtons + lda PAGE_BESIDE_GEORAM*256+regButtons and #LEFT_BUTTON beq + lda #0 ; pressed