From 2db2ca51fc79eac8a6c1ddc6535c32ef869e80c0 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 14:31:22 -0500 Subject: [PATCH 01/14] firmware: share the REU's RAM bank allocation, preload and save Moves the RAM1/RAM2 8K bank allocator, the image preload and the numbered button save out of IOH_REU.c into RAMBanks.c, so the GeoRAM handler can use the same code. The REU keeps its own bank array and count: SerUSBIO's busy check reads NumREU_Banks to mean the REU's DMA is live, which a GeoRAM must not trip. Behavior is unchanged except that the save writes whole banks instead of a byte at a time. --- Source/Teensy/MinimalBoot/Common/IOHandlers.h | 1 + .../MinimalBoot/Common/IO_Handlers/IOH_REU.c | 182 ++--------------- .../MinimalBoot/Common/IO_Handlers/RAMBanks.c | 184 ++++++++++++++++++ 3 files changed, 196 insertions(+), 171 deletions(-) create mode 100644 Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c diff --git a/Source/Teensy/MinimalBoot/Common/IOHandlers.h b/Source/Teensy/MinimalBoot/Common/IOHandlers.h index 88bc75b7..c3f060ef 100644 --- a/Source/Teensy/MinimalBoot/Common/IOHandlers.h +++ b/Source/Teensy/MinimalBoot/Common/IOHandlers.h @@ -109,6 +109,7 @@ struct stcIOHandlers #ifndef MinimumBuild #include "IO_Handlers/IOH_MIDI.c" #include "IO_Handlers/IOH_Debug.c" + #include "IO_Handlers/RAMBanks.c" #ifdef Fab04_REU #include "IO_Handlers/IOH_REU.c" #endif diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c index fe43dcac..b602bf91 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_REU.c @@ -29,8 +29,7 @@ #ifdef USE_RAM12 - #define REU_RAM_Bank_Size 0x2000 - #define REU_Size (NumREU_Banks * REU_RAM_Bank_Size) //Dynamically allocated based on NumREU_Banks, each bank is 8k (0x2000) + #define REU_Size (NumREU_Banks * RAM_Bank_Size) //Dynamically allocated based on NumREU_Banks, each bank is 8k (0x2000) #define REU_RAM_READ(a,d) d=REU_Bank[a>>13][a&0x1fff] #define REU_RAM_WRITE(a,d) REU_Bank[a>>13][a&0x1fff]=d #define MIN_REU_BANKS 16 // 128K, smallest real REU (1700) @@ -76,11 +75,6 @@ stcIOHandlers IOHndlr_REU = extern bool PerformDMA(DMA_Trans_RnW RnW, uint16_t StartAddr, uint8_t *Buffer, uint32_t Length, DMA_Addr_Mode FixC64Addr); extern bool CloseDMA(); extern void (*fSpecialBtnChange)(bool Up_nDn); //Pointer to function called when Special Button Changes -extern void EEPreadStr(uint16_t addr, char* buf); -extern RegMenuTypes RegMenuTypeFromFileName(char** ptrptrFileName); -extern bool SDFullInit(); -extern bool USBFileSystemWait(); -extern FS *FSfromSourceID(RegMenuTypes SourceID); //ref: https://codebase64.net/doku.php?id=base:reu_registers //tests: @@ -145,18 +139,12 @@ extern bool DMA_RnW, DMA_FixC64Addr; extern uint32_t DMA_Length, DMA_Count, DMA_StartAddr; extern bool DMAByte(uint8_t *Data); -extern void FreeDriveDirMenu(); -extern uint8_t RAM_Image[]; extern StructCrtChip CrtChips[]; // Removal breaks downstream code, so keep it here for now. -extern uint8_t NumCrtChips; extern StructMenuItem *DriveDirMenu; -extern uint8_t *ptrRAM_ImageEnd; // points to end of CRT-used RAM1 buffer void FreeREU() -{ //free banks allocated in RAM2 and reset NumREUBanks - for(uint16_t cnt=0; cnt < NumREU_Banks; cnt++) - if((uint32_t)REU_Bank[cnt] >= 0x20200000) free(REU_Bank[cnt]); - NumREU_Banks = 0; +{ + FreeRAMBanks(REU_Bank, &NumREU_Banks); } void DMAByte_BASkip(uint8_t *Data1) @@ -456,83 +444,12 @@ FLASHMEM void ReadWriteREU(bool RnW, uint32_t REUAddr, uint8_t *REUBuf, uint16_t FLASHMEM void Save_REU() { - if (NumREU_Banks == 0) return; // nothing to save: probably due to failure to allocate 128K - - //Create filename based on current. reu000.reu becomes reu001.reu, etc +#ifndef USE_RAM12 + #error Save_REU saves RAM1/2 banks only +#endif char Filename[MaxPathLength]; EEPreadStr(eepAdREUFilename, Filename); - char *ptrFileName = Filename; //pointer to move past SD/USB/TR: - RegMenuTypes MenuSourceID = RegMenuTypeFromFileName(&ptrFileName); - - if (MenuSourceID == rmtSD) SDFullInit(); // SD.begin(BUILTIN_SDCARD); with retry if presence detected - if (MenuSourceID == rmtUSBDrive) USBFileSystemWait(); //wait up to 1.5 sec in case USB drive just changed or powered up - //rmtTeensy not allowed, no reu files in Teensy Mem - - FS *sourceFS = FSfromSourceID(MenuSourceID); - - char Extension[20]; - char *pDot = strrchr(ptrFileName, '.'); //find last dot - if (pDot == NULL) pDot = ptrFileName + strlen(ptrFileName); //if no extension, set to end of filename - strcpy(Extension, pDot); //copy extension - *pDot = 0; //terminate at dot/extension - - bool IsNum = true; //are last 3digits of filename all numbers? - if (strlen(ptrFileName)>=3) - { - for(char *charloc=pDot-3; charloc'9') IsNum = false; - //Serial.printf("%08x: %c %02x\n", (uint32_t)charloc, *charloc, *charloc); - } - } - else IsNum = false; - - - uint16_t NewNum = 0; - if (IsNum) - { //find current number and then terminate it - NewNum = atoi(pDot-3); - *(pDot-3) = 0; //terminate number - } - - //now see if the file exists, or itterate until it doesn't - char NewFilename[MaxPathLength]; - - do sprintf(NewFilename, "%s%03d%s", ptrFileName, NewNum++, Extension); - while (sourceFS->exists(NewFilename)); - - Serial.printf("Saving REU: %s\n", NewFilename); - File SaveFile = sourceFS->open(NewFilename, FILE_WRITE); - if (!SaveFile) - { - Serial.println("Unable to open!"); - return; - } - - //uint32_t StartmS = millis(); - uint8_t NextByte; - - for (uint32_t CharNum=0; CharNum= 0x20200000) - free(REU_Bank[unused_bank]); - NumREU_Banks = NewNumREU_Banks; - } - - Printf_dbg_reu("After resizing to power of 2, %luK bytes allocated\n", (uint32_t)NumREU_Banks * REU_RAM_Bank_Size / 1024); - - if (NumREU_Banks < MIN_REU_BANKS) // If REU is not 128k or larger + NumREU_Banks = AllocRAMBanks(REU_Bank, MAX_REU_BANKS, MIN_REU_BANKS); + if (NumREU_Banks == 0) { - Serial.printf("Unable to allocate minimum REU size of 128K\n" - "REU is disabled\n"); - FreeREU(); + Serial.printf("REU is disabled\n"); return; } @@ -623,37 +491,9 @@ uint8_t *pRAM_Image = ptrRAM_ImageEnd; //start at end of any CRT-used RAM1 fSpecialBtnChange = &SpecialBtn_REU; //REU RAM is ready; enable button now so a failed/missing preload below doesn't skip it Serial.printf("REU Size: %luK bytes\n", (uint32_t)REU_Size / 1024); - //pre-load REU into RAM here: char Filename[MaxPathLength]; EEPreadStr(eepAdREUFilename, Filename); - char *ptrFileName = Filename; //pointer to move past SD/USB/TR: - Serial.printf("Loading REU: %s\n", Filename); - RegMenuTypes MenuSourceID = RegMenuTypeFromFileName(&ptrFileName); - - if (MenuSourceID == rmtSD) SDFullInit(); // SD.begin(BUILTIN_SDCARD); with retry if presence detected - if (MenuSourceID == rmtUSBDrive) USBFileSystemWait(); //wait up to 1.5 sec in case USB drive just changed or powered up - //rmtTeensy not allowed, no reu files in Teensy Mem - - FS *sourceFS = FSfromSourceID(MenuSourceID); - File LoadFile = sourceFS->open(ptrFileName, FILE_READ); - if (!LoadFile) - { - Serial.println("Not found!"); - return; - } - - if (LoadFile.size() > REU_Size) - Serial.printf("Too Large! Loading first %luK\n", (uint32_t)REU_Size/1024); - - //uint32_t StartmS = millis(); - uint32_t CharNum = 0; - for (uint8_t Bank = 0; Bank < NumREU_Banks && LoadFile.available(); Bank++) - CharNum += LoadFile.read(REU_Bank[Bank], REU_RAM_Bank_Size); - //Serial.printf("Read %lu Bytes in %lumS\n", CharNum, millis()-StartmS); - Serial.printf("Read %lu Bytes into REU\n", CharNum); - LoadFile.close(); - //delay(250); - + LoadRAMBanks(REU_Bank, NumREU_Banks, Filename); #elif defined(USE_PSRAM) //higher clock speed and shorter CS setup/hold do help, but still not enough for NUVIEs w/ sound diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c new file mode 100644 index 00000000..7f2f01b5 --- /dev/null +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -0,0 +1,184 @@ +// MIT License +// +// Copyright (c) 2026 Travis Smith +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software +// and associated documentation files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +// the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +// RAM expansion memory (REU, GeoRAM): 8k banks taken from the free end of RAM1's RAM_Image, +// then from RAM2, preloaded from and saved to an image file. + +#define RAM_Bank_Size 0x2000 + +extern void EEPreadStr(uint16_t addr, char* buf); +extern RegMenuTypes RegMenuTypeFromFileName(char** ptrptrFileName); +extern bool SDFullInit(); +extern bool USBFileSystemWait(); +extern FS *FSfromSourceID(RegMenuTypes SourceID); +extern void FreeDriveDirMenu(); +extern uint8_t RAM_Image[]; +extern uint8_t NumCrtChips; +extern uint8_t *ptrRAM_ImageEnd; // points to end of CRT-used RAM1 buffer + +FLASHMEM void FreeRAMBanks(uint8_t *Bank[], uint8_t *NumBanks) +{ //free banks allocated in RAM2 + for(uint16_t cnt=0; cnt < *NumBanks; cnt++) + if((uint32_t)Bank[cnt] >= 0x20200000) free(Bank[cnt]); + *NumBanks = 0; +} + +// Returns the number of banks allocated: a power of 2 up to MaxBanks, or 0 if fewer than MinBanks fit. +FLASHMEM uint8_t AllocRAMBanks(uint8_t *Bank[], uint8_t MaxBanks, uint8_t MinBanks) +{ + uint8_t NumBanks = 0; + FreeDriveDirMenu(); //free/clear prev loaded directory to make space. Doing it regardless to preserve continuity + + if (NumCrtChips == 0) ptrRAM_ImageEnd = RAM_Image; //If no CRT loaded, RAM1 is entirely free + uint8_t *pRAM_Image = ptrRAM_ImageEnd; //start at end of any CRT-used RAM1 + + //First use RAM_Image from RAM1: + while (((uint32_t)pRAM_Image - (uint32_t)RAM_Image + RAM_Bank_Size <= RAM_ImageSize) + && (NumBanks < MaxBanks)) + { + Bank[NumBanks] = pRAM_Image; + pRAM_Image += RAM_Bank_Size; + NumBanks++; + } + + //allocate the rest from RAM2 + while(NumBanks < MaxBanks && (NULL != (Bank[NumBanks] = (uint8_t*)malloc(RAM_Bank_Size)))) + NumBanks++; + + //resize to a power of 2 if needed and free unused banks + if (NumBanks & (NumBanks-1)) //not a power of 2 + { + // largest power less than or equal to NumBanks + uint8_t NewNumBanks = 1 << (7 - (__builtin_clz(NumBanks) - 24)); + Serial.printf("Resizing from %d banks to %d banks\n", NumBanks, NewNumBanks); + + for (uint8_t unused_bank = NewNumBanks; unused_bank < NumBanks; unused_bank++) + if((uint32_t)Bank[unused_bank] >= 0x20200000) + free(Bank[unused_bank]); + NumBanks = NewNumBanks; + } + + if (NumBanks < MinBanks) + { + Serial.printf("Unable to allocate minimum size of %dK\n", MinBanks * RAM_Bank_Size / 1024); + FreeRAMBanks(Bank, &NumBanks); + } + return NumBanks; +} + +// Path is source-prefixed ("SD:/reu.reu") +FLASHMEM static FS *RAMBanksFS(char *Path, char **ptrFileName) +{ + *ptrFileName = Path; //pointer to move past SD/USB/TR: + RegMenuTypes MenuSourceID = RegMenuTypeFromFileName(ptrFileName); + + if (MenuSourceID == rmtSD) SDFullInit(); // SD.begin(BUILTIN_SDCARD); with retry if presence detected + if (MenuSourceID == rmtUSBDrive) USBFileSystemWait(); //wait up to 1.5 sec in case USB drive just changed or powered up + //rmtTeensy not allowed, no RAM image files in Teensy Mem + + return FSfromSourceID(MenuSourceID); +} + +FLASHMEM void LoadRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *SourcePath) +{ + char Filename[MaxPathLength]; + strlcpy(Filename, SourcePath, MaxPathLength); + Serial.printf("Loading: %s\n", Filename); + char *ptrFileName; + FS *sourceFS = RAMBanksFS(Filename, &ptrFileName); + File LoadFile = sourceFS->open(ptrFileName, FILE_READ); + if (!LoadFile) + { + Serial.println("Not found!"); + return; + } + + if (LoadFile.size() > (uint32_t)NumBanks * RAM_Bank_Size) + Serial.printf("Too Large! Loading first %luK\n", (uint32_t)NumBanks * RAM_Bank_Size / 1024); + + uint32_t CharNum = 0; + for (uint8_t BankNum = 0; BankNum < NumBanks && LoadFile.available(); BankNum++) + CharNum += LoadFile.read(Bank[BankNum], RAM_Bank_Size); + Serial.printf("Read %lu Bytes\n", CharNum); + LoadFile.close(); +} + +// Saves to the first free numbered name: reu.reu becomes reu000.reu, reu000.reu becomes reu001.reu, etc +FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *SourcePath) +{ + if (NumBanks == 0) return; // nothing to save: probably due to failure to allocate + + char Filename[MaxPathLength]; + strlcpy(Filename, SourcePath, MaxPathLength); + char *ptrFileName; + FS *sourceFS = RAMBanksFS(Filename, &ptrFileName); + + char Extension[20]; + char *pDot = strrchr(ptrFileName, '.'); //find last dot + if (pDot == NULL) pDot = ptrFileName + strlen(ptrFileName); //if no extension, set to end of filename + strlcpy(Extension, pDot, sizeof(Extension)); //copy extension + *pDot = 0; //terminate at dot/extension + + bool IsNum = true; //are last 3digits of filename all numbers? + if (strlen(ptrFileName)>=3) + { + for(char *charloc=pDot-3; charloc'9') IsNum = false; + } + else IsNum = false; + + uint16_t NewNum = 0; + if (IsNum) + { //find current number and then terminate it + NewNum = atoi(pDot-3); + *(pDot-3) = 0; //terminate number + } + + //now see if the file exists, or itterate until it doesn't + char NewFilename[MaxPathLength]; + + do sprintf(NewFilename, "%s%03d%s", ptrFileName, NewNum++, Extension); + while (sourceFS->exists(NewFilename)); + + Serial.printf("Saving: %s\n", NewFilename); + File SaveFile = sourceFS->open(NewFilename, FILE_WRITE); + if (!SaveFile) + { + Serial.println("Unable to open!"); + return; + } + + for (uint8_t BankNum = 0; BankNum < NumBanks; BankNum++) + SaveFile.write(Bank[BankNum], RAM_Bank_Size); + Serial.printf("Saved %dKBytes\n", NumBanks * RAM_Bank_Size / 1024); + SaveFile.close(); + + //Flash LED to ack save + uint32_t LastmS = millis(); + bool LEDState = true; + + for (uint32_t FlashCount=0; FlashCount<20; FlashCount++) + { + while(millis()-LastmS < 75); + LastmS = millis(); + if ((LEDState = !LEDState)) SetLEDOn; + else SetLEDOff; + } +} From 2759010b488e3c5798ffd0d71b88b7bb5a2139c6 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 14:47:01 -0500 Subject: [PATCH 02/14] firmware: GeoRAM IO handler A Berkeley Softworks geoRAM: the $DE00 page window onto RAM banks from RAMBanks.c, with the page ($DFFE) and 16K block ($DFFF) registers decoded across $DF80-$DFFF the way VICE does, leaving $DF00-$DF7F free. No DMA, so it is in every full build, TR and TR+, and serves C64 and C128 mode alike. Up to 512K, as much as fits beside a loaded crt; preloads SD:/georam.ram, and the special button saves the next free georamNNN.ram. Freed with the REU wherever the menu reclaims RAM. --- Source/Teensy/MinimalBoot/Common/IOHandlers.h | 2 + .../Common/IO_Handlers/IOH_GeoRAM.c | 97 +++++++++++++++++++ Source/Teensy/MinimalBoot/Common/Menu_Regs.h | 1 + Source/Teensy/RemoteControl.ino | 3 +- Source/Teensy/Teensy.ino | 3 +- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c diff --git a/Source/Teensy/MinimalBoot/Common/IOHandlers.h b/Source/Teensy/MinimalBoot/Common/IOHandlers.h index c3f060ef..7aaa3a8d 100644 --- a/Source/Teensy/MinimalBoot/Common/IOHandlers.h +++ b/Source/Teensy/MinimalBoot/Common/IOHandlers.h @@ -110,6 +110,7 @@ struct stcIOHandlers #include "IO_Handlers/IOH_MIDI.c" #include "IO_Handlers/IOH_Debug.c" #include "IO_Handlers/RAMBanks.c" + #include "IO_Handlers/IOH_GeoRAM.c" #ifdef Fab04_REU #include "IO_Handlers/IOH_REU.c" #endif @@ -158,6 +159,7 @@ stcIOHandlers* IOHandler[] = //Synch order/qty with enum enumIOHandlers #ifdef Fab04_KernalReplace &IOHndlr_KernalReplace, //IOH_KernalReplace, #endif + &IOHndlr_GeoRAM, //IOH_GeoRAM, &IOHndlr_Debug, //IOH_Debug, //*not* manually selectable, see LastSelectableIOH diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c new file mode 100644 index 00000000..845f2006 --- /dev/null +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +// Berkeley Softworks geoRAM: a 256-byte window at $DE00 onto the page selected by two +// write-only registers, $DFFE (page 0-63 within a 16K block) and $DFFF (16K block). +// No DMA, so it runs on every TR, in C64 and C128 mode. +// ref: VICE src/c64/cart/georam.c + +void IO1Hndlr_GeoRAM(uint8_t Address, bool R_Wn); +void IO2Hndlr_GeoRAM(uint8_t Address, bool R_Wn); +void InitHndlr_GeoRAM(); +extern void (*fSpecialBtnChange)(bool Up_nDn); + +stcIOHandlers IOHndlr_GeoRAM = +{ + "GeoRAM", //Name of handler + &InitHndlr_GeoRAM, //Called once at handler startup + &IO1Hndlr_GeoRAM, //IO1 R/W handler + &IO2Hndlr_GeoRAM, //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 + NULL, //Polled in main routine + NULL, //called at the end of EVERY c64 cycle +}; + +#define GeoRAM_Filename "SD:/georam.ram" +#define MAX_GEORAM_BANKS 64 // 512K, the original geoRAM +#define MIN_GEORAM_BANKS 16 // 128K +#define GeoRAM_PagesPerBlock 64 +#define GeoRAM_BanksPerBlock (16384 / RAM_Bank_Size) + +// VICE decodes the registers across $DF80-$DFFF (A0 picks which); $DF00-$DF7F is left free. +#define GeoRAM_RegsBase 0x80 +#define GeoRAM_RegBlockBit 0x01 + +uint8_t NumGeoRAM_Banks = 0; +uint8_t *GeoRAM_Bank[MAX_GEORAM_BANKS]; +uint8_t GeoRAM_Block, GeoRAM_PageNum; +static uint8_t GeoRAM_NoRAM[256]; // the window while no RAM is allocated +uint8_t *GeoRAM_Page = GeoRAM_NoRAM; + +static void GeoRAM_SelectPage() +{ + if (NumGeoRAM_Banks == 0) return; + // the unused high register bits wrap, as on real hardware + uint32_t Offset = ((GeoRAM_Block & (NumGeoRAM_Banks / GeoRAM_BanksPerBlock - 1)) * GeoRAM_PagesPerBlock + + (GeoRAM_PageNum & (GeoRAM_PagesPerBlock - 1))) * 256; + GeoRAM_Page = GeoRAM_Bank[Offset / RAM_Bank_Size] + (Offset % RAM_Bank_Size); +} + +FLASHMEM void FreeGeoRAM() +{ + FreeRAMBanks(GeoRAM_Bank, &NumGeoRAM_Banks); + GeoRAM_Page = GeoRAM_NoRAM; +} + +FLASHMEM void SpecialBtn_GeoRAM(bool Up_nDn) +{ + // on button release save GeoRAM contents + if(Up_nDn) SaveRAMBanks(GeoRAM_Bank, NumGeoRAM_Banks, GeoRAM_Filename); +} + +FLASHMEM void InitHndlr_GeoRAM() +{ + FreeGeoRAM(); + GeoRAM_Block = GeoRAM_PageNum = 0; + NumGeoRAM_Banks = AllocRAMBanks(GeoRAM_Bank, MAX_GEORAM_BANKS, MIN_GEORAM_BANKS); + if (NumGeoRAM_Banks == 0) + { + Serial.printf("GeoRAM is disabled\n"); + return; + } + GeoRAM_SelectPage(); + fSpecialBtnChange = &SpecialBtn_GeoRAM; + Serial.printf("GeoRAM Size: %dK bytes\n", NumGeoRAM_Banks * RAM_Bank_Size / 1024); + LoadRAMBanks(GeoRAM_Bank, NumGeoRAM_Banks, GeoRAM_Filename); +} + +void IO1Hndlr_GeoRAM(uint8_t Address, bool R_Wn) +{ + if (R_Wn) DataPortWriteWaitLog(GeoRAM_Page[Address]); + else + { + uint8_t Data = DataPortWaitRead(); + GeoRAM_Page[Address] = Data; + TraceLogAddValidData(Data); + } +} + +void IO2Hndlr_GeoRAM(uint8_t Address, bool R_Wn) +{ + if (R_Wn || Address < GeoRAM_RegsBase) return; // write-only registers + uint8_t Data = DataPortWaitRead(); + if (Address & GeoRAM_RegBlockBit) GeoRAM_Block = Data; + else GeoRAM_PageNum = Data; + GeoRAM_SelectPage(); + TraceLogAddValidData(Data); +} diff --git a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h index 166ecfe0..63aaa8ba 100644 --- a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h +++ b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h @@ -437,6 +437,7 @@ enum enumIOHandlers //Synch order/qty with IOHandler[] (IOHandlers.h) #ifdef Fab04_KernalReplace IOH_KernalReplace, #endif + IOH_GeoRAM, IOH_Debug, //last manually selectable, see LastSelectableIOH //*not* manually selectable, see LastSelectableIOH diff --git a/Source/Teensy/RemoteControl.ino b/Source/Teensy/RemoteControl.ino index e3076195..0fc6d965 100644 --- a/Source/Teensy/RemoteControl.ino +++ b/Source/Teensy/RemoteControl.ino @@ -230,7 +230,8 @@ void RemoteLaunch(RegMenuTypes MenuSourceID, const char *FileNamePath, bool DoCa FreeCrtChips(); #ifdef Fab04_REU FreeREU(); -#endif +#endif + FreeGeoRAM(); FreeSwiftlinkBuffs(); InitDriveDirMenu(); diff --git a/Source/Teensy/Teensy.ino b/Source/Teensy/Teensy.ino index 99d05145..7cc1cf90 100644 --- a/Source/Teensy/Teensy.ino +++ b/Source/Teensy/Teensy.ino @@ -420,7 +420,8 @@ FLASHMEM void SetUpMainMenuROM() FreeCrtChips(); #ifdef Fab04_REU FreeREU(); -#endif +#endif + FreeGeoRAM(); FreeSwiftlinkBuffs(); RedirectEmptyDriveDirMenu(); free((void*)MIDIRxBuf); MIDIRxBuf=NULL; From 3bfb688cbd80545637f4875bc4a0a9b281636c4e Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 14:49:06 -0500 Subject: [PATCH 03/14] firmware: RAM bank save reports a failed write instead of acking it A short write (card full, drive pulled) printed "Saved" and flashed the save LED. Load stops at a read error rather than adding -1 to its count. --- .../MinimalBoot/Common/IO_Handlers/RAMBanks.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c index 7f2f01b5..856581d0 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -115,7 +115,11 @@ FLASHMEM void LoadRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source uint32_t CharNum = 0; for (uint8_t BankNum = 0; BankNum < NumBanks && LoadFile.available(); BankNum++) - CharNum += LoadFile.read(Bank[BankNum], RAM_Bank_Size); + { + int BytesRead = LoadFile.read(Bank[BankNum], RAM_Bank_Size); + if (BytesRead <= 0) break; + CharNum += BytesRead; + } Serial.printf("Read %lu Bytes\n", CharNum); LoadFile.close(); } @@ -166,7 +170,14 @@ FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source } for (uint8_t BankNum = 0; BankNum < NumBanks; BankNum++) - SaveFile.write(Bank[BankNum], RAM_Bank_Size); + { + if (SaveFile.write(Bank[BankNum], RAM_Bank_Size) != RAM_Bank_Size) + { + Serial.printf("Write failed after %dKBytes!\n", BankNum * RAM_Bank_Size / 1024); + SaveFile.close(); + return; + } + } Serial.printf("Saved %dKBytes\n", NumBanks * RAM_Bank_Size / 1024); SaveFile.close(); From 6ecc7db082f8d1e836eea9c957a2454025c61efa Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 14:49:17 -0500 Subject: [PATCH 04/14] firmware: drop RAMBanks.c comments that restate the code --- .../MinimalBoot/Common/IO_Handlers/RAMBanks.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c index 856581d0..e15d37e2 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -34,7 +34,7 @@ extern uint8_t NumCrtChips; extern uint8_t *ptrRAM_ImageEnd; // points to end of CRT-used RAM1 buffer FLASHMEM void FreeRAMBanks(uint8_t *Bank[], uint8_t *NumBanks) -{ //free banks allocated in RAM2 +{ for(uint16_t cnt=0; cnt < *NumBanks; cnt++) if((uint32_t)Bank[cnt] >= 0x20200000) free(Bank[cnt]); *NumBanks = 0; @@ -44,12 +44,11 @@ FLASHMEM void FreeRAMBanks(uint8_t *Bank[], uint8_t *NumBanks) FLASHMEM uint8_t AllocRAMBanks(uint8_t *Bank[], uint8_t MaxBanks, uint8_t MinBanks) { uint8_t NumBanks = 0; - FreeDriveDirMenu(); //free/clear prev loaded directory to make space. Doing it regardless to preserve continuity + FreeDriveDirMenu(); if (NumCrtChips == 0) ptrRAM_ImageEnd = RAM_Image; //If no CRT loaded, RAM1 is entirely free uint8_t *pRAM_Image = ptrRAM_ImageEnd; //start at end of any CRT-used RAM1 - //First use RAM_Image from RAM1: while (((uint32_t)pRAM_Image - (uint32_t)RAM_Image + RAM_Bank_Size <= RAM_ImageSize) && (NumBanks < MaxBanks)) { @@ -58,11 +57,9 @@ FLASHMEM uint8_t AllocRAMBanks(uint8_t *Bank[], uint8_t MaxBanks, uint8_t MinBan NumBanks++; } - //allocate the rest from RAM2 while(NumBanks < MaxBanks && (NULL != (Bank[NumBanks] = (uint8_t*)malloc(RAM_Bank_Size)))) NumBanks++; - //resize to a power of 2 if needed and free unused banks if (NumBanks & (NumBanks-1)) //not a power of 2 { // largest power less than or equal to NumBanks @@ -86,7 +83,7 @@ FLASHMEM uint8_t AllocRAMBanks(uint8_t *Bank[], uint8_t MaxBanks, uint8_t MinBan // Path is source-prefixed ("SD:/reu.reu") FLASHMEM static FS *RAMBanksFS(char *Path, char **ptrFileName) { - *ptrFileName = Path; //pointer to move past SD/USB/TR: + *ptrFileName = Path; RegMenuTypes MenuSourceID = RegMenuTypeFromFileName(ptrFileName); if (MenuSourceID == rmtSD) SDFullInit(); // SD.begin(BUILTIN_SDCARD); with retry if presence detected @@ -135,10 +132,10 @@ FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source FS *sourceFS = RAMBanksFS(Filename, &ptrFileName); char Extension[20]; - char *pDot = strrchr(ptrFileName, '.'); //find last dot + char *pDot = strrchr(ptrFileName, '.'); if (pDot == NULL) pDot = ptrFileName + strlen(ptrFileName); //if no extension, set to end of filename - strlcpy(Extension, pDot, sizeof(Extension)); //copy extension - *pDot = 0; //terminate at dot/extension + strlcpy(Extension, pDot, sizeof(Extension)); + *pDot = 0; bool IsNum = true; //are last 3digits of filename all numbers? if (strlen(ptrFileName)>=3) @@ -155,7 +152,6 @@ FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source *(pDot-3) = 0; //terminate number } - //now see if the file exists, or itterate until it doesn't char NewFilename[MaxPathLength]; do sprintf(NewFilename, "%s%03d%s", ptrFileName, NewNum++, Extension); @@ -181,7 +177,6 @@ FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source Serial.printf("Saved %dKBytes\n", NumBanks * RAM_Bank_Size / 1024); SaveFile.close(); - //Flash LED to ack save uint32_t LastmS = millis(); bool LEDState = true; From 89c578cfcbceaf946b7e3dbd91dbda29c8e7b3d7 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 15:41:45 -0500 Subject: [PATCH 05/14] firmware: a disabled GeoRAM leaves $DE00 open instead of answering from one shared page --- Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c index 845f2006..90d0ad35 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -77,6 +77,7 @@ FLASHMEM void InitHndlr_GeoRAM() void IO1Hndlr_GeoRAM(uint8_t Address, bool R_Wn) { + if (NumGeoRAM_Banks == 0) return; // disabled: $DE00 stays open bus, so software sees no geoRAM if (R_Wn) DataPortWriteWaitLog(GeoRAM_Page[Address]); else { From 8686274b1d3c0e6e72bc0f286279bcaa544c1130 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 15:41:55 -0500 Subject: [PATCH 06/14] firmware: FreeGeoRAM stops the IO handlers using the banks before freeing them --- Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c index 90d0ad35..9f9fed67 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -35,7 +35,7 @@ stcIOHandlers IOHndlr_GeoRAM = uint8_t NumGeoRAM_Banks = 0; uint8_t *GeoRAM_Bank[MAX_GEORAM_BANKS]; uint8_t GeoRAM_Block, GeoRAM_PageNum; -static uint8_t GeoRAM_NoRAM[256]; // the window while no RAM is allocated +static uint8_t GeoRAM_NoRAM[256]; // the window until a page of allocated RAM is selected uint8_t *GeoRAM_Page = GeoRAM_NoRAM; static void GeoRAM_SelectPage() @@ -49,8 +49,12 @@ static void GeoRAM_SelectPage() FLASHMEM void FreeGeoRAM() { - FreeRAMBanks(GeoRAM_Bank, &NumGeoRAM_Banks); + // the handler is still installed and the C64 may be running: the IO ISRs must stop + // touching the banks before the first one is freed + uint8_t NumBanks = NumGeoRAM_Banks; + NumGeoRAM_Banks = 0; GeoRAM_Page = GeoRAM_NoRAM; + FreeRAMBanks(GeoRAM_Bank, &NumBanks); } FLASHMEM void SpecialBtn_GeoRAM(bool Up_nDn) From 1c8cf3217c31093398f217b62eaaa9be270ac00f Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 15:42:06 -0500 Subject: [PATCH 07/14] firmware: GeoRAM state is file-local --- Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c index 9f9fed67..787b86b3 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -32,11 +32,11 @@ stcIOHandlers IOHndlr_GeoRAM = #define GeoRAM_RegsBase 0x80 #define GeoRAM_RegBlockBit 0x01 -uint8_t NumGeoRAM_Banks = 0; -uint8_t *GeoRAM_Bank[MAX_GEORAM_BANKS]; -uint8_t GeoRAM_Block, GeoRAM_PageNum; +static uint8_t NumGeoRAM_Banks = 0; +static uint8_t *GeoRAM_Bank[MAX_GEORAM_BANKS]; +static uint8_t GeoRAM_Block, GeoRAM_PageNum; static uint8_t GeoRAM_NoRAM[256]; // the window until a page of allocated RAM is selected -uint8_t *GeoRAM_Page = GeoRAM_NoRAM; +static uint8_t *GeoRAM_Page = GeoRAM_NoRAM; static void GeoRAM_SelectPage() { From b27c082c2ce4c24525f049459359bac6bb03c447 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 15:42:06 -0500 Subject: [PATCH 08/14] firmware: drop trailing spaces after the FreeGeoRAM calls --- Source/Teensy/RemoteControl.ino | 2 +- Source/Teensy/Teensy.ino | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Teensy/RemoteControl.ino b/Source/Teensy/RemoteControl.ino index 0fc6d965..cc3dfd46 100644 --- a/Source/Teensy/RemoteControl.ino +++ b/Source/Teensy/RemoteControl.ino @@ -231,7 +231,7 @@ void RemoteLaunch(RegMenuTypes MenuSourceID, const char *FileNamePath, bool DoCa #ifdef Fab04_REU FreeREU(); #endif - FreeGeoRAM(); + FreeGeoRAM(); FreeSwiftlinkBuffs(); InitDriveDirMenu(); diff --git a/Source/Teensy/Teensy.ino b/Source/Teensy/Teensy.ino index 7cc1cf90..0dd28899 100644 --- a/Source/Teensy/Teensy.ino +++ b/Source/Teensy/Teensy.ino @@ -421,7 +421,7 @@ FLASHMEM void SetUpMainMenuROM() #ifdef Fab04_REU FreeREU(); #endif - FreeGeoRAM(); + FreeGeoRAM(); FreeSwiftlinkBuffs(); RedirectEmptyDriveDirMenu(); free((void*)MIDIRxBuf); MIDIRxBuf=NULL; From 9b91191b2a9f15be1661734253897128d6c84eed Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 16:23:12 -0500 Subject: [PATCH 09/14] firmware: GeoRAM is the last selectable handler, after Debug Placed before Debug it moved Debug's number, and that number is what eepAdNextIOHndlr stores, so a board saved with Debug as its default came up on GeoRAM. After Debug, with LastSelectableIOH moved to it, every selectable handler keeps the number it had. --- Source/Teensy/MinimalBoot/Common/IOHandlers.h | 2 +- Source/Teensy/MinimalBoot/Common/Menu_Regs.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IOHandlers.h b/Source/Teensy/MinimalBoot/Common/IOHandlers.h index 7aaa3a8d..722267c2 100644 --- a/Source/Teensy/MinimalBoot/Common/IOHandlers.h +++ b/Source/Teensy/MinimalBoot/Common/IOHandlers.h @@ -159,8 +159,8 @@ stcIOHandlers* IOHandler[] = //Synch order/qty with enum enumIOHandlers #ifdef Fab04_KernalReplace &IOHndlr_KernalReplace, //IOH_KernalReplace, #endif - &IOHndlr_GeoRAM, //IOH_GeoRAM, &IOHndlr_Debug, //IOH_Debug, + &IOHndlr_GeoRAM, //IOH_GeoRAM, //*not* manually selectable, see LastSelectableIOH diff --git a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h index 63aaa8ba..a3c616da 100644 --- a/Source/Teensy/MinimalBoot/Common/Menu_Regs.h +++ b/Source/Teensy/MinimalBoot/Common/Menu_Regs.h @@ -437,8 +437,8 @@ enum enumIOHandlers //Synch order/qty with IOHandler[] (IOHandlers.h) #ifdef Fab04_KernalReplace IOH_KernalReplace, #endif - IOH_GeoRAM, - IOH_Debug, //last manually selectable, see LastSelectableIOH + IOH_Debug, + IOH_GeoRAM, //last manually selectable, see LastSelectableIOH //*not* manually selectable, see LastSelectableIOH @@ -472,4 +472,4 @@ enum enumIOHandlers //Synch order/qty with IOHandler[] (IOHandlers.h) IOH_Num_Handlers //always last }; -#define LastSelectableIOH IOH_Debug //127 max. Used in IOH_TeensyROM.c for Special IO display/select +#define LastSelectableIOH IOH_GeoRAM //127 max. Used in IOH_TeensyROM.c for Special IO display/select From 12d176d2b60ab753506afad8cc134e2129e963aa Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 22 Oct 2026 19:17:49 -0500 Subject: [PATCH 10/14] firmware: GeoRAM drops its 256-byte no-RAM page GeoRAM_NoRAM was never read: the IO handlers return while there are no banks. Init now points the window at the first page before it publishes the bank count, so that stays true without it; the two are volatile so the stores keep that order. --- .../Common/IO_Handlers/IOH_GeoRAM.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c index 787b86b3..19f60399 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -32,11 +32,12 @@ stcIOHandlers IOHndlr_GeoRAM = #define GeoRAM_RegsBase 0x80 #define GeoRAM_RegBlockBit 0x01 -static uint8_t NumGeoRAM_Banks = 0; +// volatile: the IO ISRs read the page only while there are banks, so the two stores +// must reach memory in the order written +static volatile uint8_t NumGeoRAM_Banks = 0; static uint8_t *GeoRAM_Bank[MAX_GEORAM_BANKS]; static uint8_t GeoRAM_Block, GeoRAM_PageNum; -static uint8_t GeoRAM_NoRAM[256]; // the window until a page of allocated RAM is selected -static uint8_t *GeoRAM_Page = GeoRAM_NoRAM; +static uint8_t * volatile GeoRAM_Page; static void GeoRAM_SelectPage() { @@ -53,7 +54,6 @@ FLASHMEM void FreeGeoRAM() // touching the banks before the first one is freed uint8_t NumBanks = NumGeoRAM_Banks; NumGeoRAM_Banks = 0; - GeoRAM_Page = GeoRAM_NoRAM; FreeRAMBanks(GeoRAM_Bank, &NumBanks); } @@ -67,16 +67,17 @@ FLASHMEM void InitHndlr_GeoRAM() { FreeGeoRAM(); GeoRAM_Block = GeoRAM_PageNum = 0; - NumGeoRAM_Banks = AllocRAMBanks(GeoRAM_Bank, MAX_GEORAM_BANKS, MIN_GEORAM_BANKS); - if (NumGeoRAM_Banks == 0) + uint8_t NumBanks = AllocRAMBanks(GeoRAM_Bank, MAX_GEORAM_BANKS, MIN_GEORAM_BANKS); + if (NumBanks == 0) { Serial.printf("GeoRAM is disabled\n"); return; } - GeoRAM_SelectPage(); + GeoRAM_Page = GeoRAM_Bank[0]; + NumGeoRAM_Banks = NumBanks; fSpecialBtnChange = &SpecialBtn_GeoRAM; - Serial.printf("GeoRAM Size: %dK bytes\n", NumGeoRAM_Banks * RAM_Bank_Size / 1024); - LoadRAMBanks(GeoRAM_Bank, NumGeoRAM_Banks, GeoRAM_Filename); + Serial.printf("GeoRAM Size: %dK bytes\n", NumBanks * RAM_Bank_Size / 1024); + LoadRAMBanks(GeoRAM_Bank, NumBanks, GeoRAM_Filename); } void IO1Hndlr_GeoRAM(uint8_t Address, bool R_Wn) From 9d1c7b98468ace632b43b2b0ebd8a4396e841e14 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 22 Oct 2026 19:28:16 -0500 Subject: [PATCH 11/14] firmware: GeoRAM saves back to georam.ram, keeping the last one as georam.bak The button saved to the REU's numbered names (georam000.ram, ...), but preload reads only georam.ram, so a saved GeoRAM never came back. The new image is written to georam.tmp first and renamed into place only after the write succeeds. The REU keeps its numbered saves. --- .../Common/IO_Handlers/IOH_GeoRAM.c | 2 +- .../MinimalBoot/Common/IO_Handlers/RAMBanks.c | 93 ++++++++++++++----- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c index 19f60399..49997e10 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_GeoRAM.c @@ -60,7 +60,7 @@ FLASHMEM void FreeGeoRAM() FLASHMEM void SpecialBtn_GeoRAM(bool Up_nDn) { // on button release save GeoRAM contents - if(Up_nDn) SaveRAMBanks(GeoRAM_Bank, NumGeoRAM_Banks, GeoRAM_Filename); + if(Up_nDn) SaveRAMBanksReplacing(GeoRAM_Bank, NumGeoRAM_Banks, GeoRAM_Filename); } FLASHMEM void InitHndlr_GeoRAM() diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c index e15d37e2..3c4caa79 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -121,6 +121,45 @@ FLASHMEM void LoadRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source LoadFile.close(); } +FLASHMEM static bool WriteRAMBanksFile(FS *sourceFS, const char *FileName, uint8_t *Bank[], uint8_t NumBanks) +{ + Serial.printf("Saving: %s\n", FileName); + sourceFS->remove(FileName); // FILE_WRITE appends to an existing file + File SaveFile = sourceFS->open(FileName, FILE_WRITE); + if (!SaveFile) + { + Serial.println("Unable to open!"); + return false; + } + + for (uint8_t BankNum = 0; BankNum < NumBanks; BankNum++) + { + if (SaveFile.write(Bank[BankNum], RAM_Bank_Size) != RAM_Bank_Size) + { + Serial.printf("Write failed after %dKBytes!\n", BankNum * RAM_Bank_Size / 1024); + SaveFile.close(); + return false; + } + } + Serial.printf("Saved %dKBytes\n", NumBanks * RAM_Bank_Size / 1024); + SaveFile.close(); + return true; +} + +FLASHMEM static void FlashSavedLED() +{ + uint32_t LastmS = millis(); + bool LEDState = true; + + for (uint32_t FlashCount=0; FlashCount<20; FlashCount++) + { + while(millis()-LastmS < 75); + LastmS = millis(); + if ((LEDState = !LEDState)) SetLEDOn; + else SetLEDOff; + } +} + // Saves to the first free numbered name: reu.reu becomes reu000.reu, reu000.reu becomes reu001.reu, etc FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *SourcePath) { @@ -157,34 +196,38 @@ FLASHMEM void SaveRAMBanks(uint8_t *Bank[], uint8_t NumBanks, const char *Source do sprintf(NewFilename, "%s%03d%s", ptrFileName, NewNum++, Extension); while (sourceFS->exists(NewFilename)); - Serial.printf("Saving: %s\n", NewFilename); - File SaveFile = sourceFS->open(NewFilename, FILE_WRITE); - if (!SaveFile) - { - Serial.println("Unable to open!"); - return; - } + if (WriteRAMBanksFile(sourceFS, NewFilename, Bank, NumBanks)) FlashSavedLED(); +} - for (uint8_t BankNum = 0; BankNum < NumBanks; BankNum++) - { - if (SaveFile.write(Bank[BankNum], RAM_Bank_Size) != RAM_Bank_Size) - { - Serial.printf("Write failed after %dKBytes!\n", BankNum * RAM_Bank_Size / 1024); - SaveFile.close(); - return; - } - } - Serial.printf("Saved %dKBytes\n", NumBanks * RAM_Bank_Size / 1024); - SaveFile.close(); +FLASHMEM static void WithExtension(char *Dest, const char *FileName, const char *Extension) +{ + strlcpy(Dest, FileName, MaxPathLength); + char *pDot = strrchr(Dest, '.'); + if (pDot != NULL) *pDot = 0; + strlcat(Dest, Extension, MaxPathLength); +} - uint32_t LastmS = millis(); - bool LEDState = true; +// Overwrites the file, keeping the one it replaces as .bak: georam.ram becomes georam.bak +FLASHMEM void SaveRAMBanksReplacing(uint8_t *Bank[], uint8_t NumBanks, const char *SourcePath) +{ + if (NumBanks == 0) return; // nothing to save: probably due to failure to allocate - for (uint32_t FlashCount=0; FlashCount<20; FlashCount++) + char Filename[MaxPathLength]; + strlcpy(Filename, SourcePath, MaxPathLength); + char *ptrFileName; + FS *sourceFS = RAMBanksFS(Filename, &ptrFileName); + + char TempFilename[MaxPathLength], BackupFilename[MaxPathLength]; + WithExtension(TempFilename, ptrFileName, ".tmp"); + WithExtension(BackupFilename, ptrFileName, ".bak"); + + if (!WriteRAMBanksFile(sourceFS, TempFilename, Bank, NumBanks)) return; + sourceFS->remove(BackupFilename); + sourceFS->rename(ptrFileName, BackupFilename); // fails harmlessly when there is no previous save + if (!sourceFS->rename(TempFilename, ptrFileName)) { - while(millis()-LastmS < 75); - LastmS = millis(); - if ((LEDState = !LEDState)) SetLEDOn; - else SetLEDOff; + Serial.printf("Unable to rename %s to %s!\n", TempFilename, ptrFileName); + return; } + FlashSavedLED(); } From 9d413205c2e131d8bf9d1f68231311f6f5af06a4 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 22 Oct 2026 20:36:06 -0500 Subject: [PATCH 12/14] firmware: GeoRAM rotates georam.ram to .bak only when there is one, and puts it back if the save cannot take its place If the last rename failed, georam.ram was gone: the next session preloaded nothing, and saving that blank RAM removed georam.bak, the last good image. --- Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c index 3c4caa79..e7d75094 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -222,11 +222,16 @@ FLASHMEM void SaveRAMBanksReplacing(uint8_t *Bank[], uint8_t NumBanks, const cha WithExtension(BackupFilename, ptrFileName, ".bak"); if (!WriteRAMBanksFile(sourceFS, TempFilename, Bank, NumBanks)) return; - sourceFS->remove(BackupFilename); - sourceFS->rename(ptrFileName, BackupFilename); // fails harmlessly when there is no previous save + bool HadSave = sourceFS->exists(ptrFileName); + if (HadSave) + { // rename refuses an existing target + sourceFS->remove(BackupFilename); + sourceFS->rename(ptrFileName, BackupFilename); + } if (!sourceFS->rename(TempFilename, ptrFileName)) { Serial.printf("Unable to rename %s to %s!\n", TempFilename, ptrFileName); + if (HadSave) sourceFS->rename(BackupFilename, ptrFileName); return; } FlashSavedLED(); From 76c13e52cd688b46616a69e91725ae80ae8579b8 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 22 Oct 2026 20:36:07 -0500 Subject: [PATCH 13/14] firmware: a remote launch finds its TR menu target before freeing the running handler's memory A launch is accepted while a handler is busy. One naming a TR menu path that does not exist returned after the CRT chips, REU, GeoRAM and SwiftLink buffers were freed, without resetting to the menu, leaving the running program on freed memory. --- Source/Teensy/RemoteControl.ino | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Source/Teensy/RemoteControl.ino b/Source/Teensy/RemoteControl.ino index cc3dfd46..9ae54f1f 100644 --- a/Source/Teensy/RemoteControl.ino +++ b/Source/Teensy/RemoteControl.ino @@ -226,22 +226,15 @@ void RemoteLaunch(RegMenuTypes MenuSourceID, const char *FileNamePath, bool DoCa } //DriveDirPath's buffer can't hold both "/" and the filename starting at index 1 } - //free mem for DriveDirMenu in case current (non-tr) handler is using it all - FreeCrtChips(); -#ifdef Fab04_REU - FreeREU(); -#endif - FreeGeoRAM(); - FreeSwiftlinkBuffs(); - InitDriveDirMenu(); + int16_t MenuNum = -1; + StructMenuItem* DefTRMenu = TeensyROMMenu; //default to root menu + uint16_t NumMenuItems = sizeof(TeensyROMMenu)/sizeof(StructMenuItem); + // A launch can arrive while a handler is running the C64: one that finds no + // target must return before anything that handler uses is freed. if (MenuSourceID == rmtTeensy) { //point MenuSource to directory - int16_t MenuNum = -1; - StructMenuItem* DefTRMenu = TeensyROMMenu; //default to root menu - uint16_t NumMenuItems = sizeof(TeensyROMMenu)/sizeof(StructMenuItem); - if(strcmp(DriveDirPath, "/") !=0 ) {//find dir menu MenuNum = FindTRMenuItem(DefTRMenu, NumMenuItems, DriveDirPath); @@ -265,7 +258,19 @@ void RemoteLaunch(RegMenuTypes MenuSourceID, const char *FileNamePath, bool DoCa return; } Printf_dbg("TR File Num = %d\n", MenuNum); + } + //free mem for DriveDirMenu in case current (non-tr) handler is using it all + FreeCrtChips(); +#ifdef Fab04_REU + FreeREU(); +#endif + FreeGeoRAM(); + FreeSwiftlinkBuffs(); + InitDriveDirMenu(); + + if (MenuSourceID == rmtTeensy) + { //point to item # matching filename MenuSource = DefTRMenu; SetNumItems(NumMenuItems); From 4210842589247c3f2361adcb942a20778818f5e1 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Sun, 25 Oct 2026 19:32:07 -0500 Subject: [PATCH 14/14] fix: review findings for 5ec90dd --- Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c index e7d75094..684290d0 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/RAMBanks.c @@ -30,7 +30,6 @@ extern bool USBFileSystemWait(); extern FS *FSfromSourceID(RegMenuTypes SourceID); extern void FreeDriveDirMenu(); extern uint8_t RAM_Image[]; -extern uint8_t NumCrtChips; extern uint8_t *ptrRAM_ImageEnd; // points to end of CRT-used RAM1 buffer FLASHMEM void FreeRAMBanks(uint8_t *Bank[], uint8_t *NumBanks)