diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_KernalReplace.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_KernalReplace.c index 3ba276c8..d3e657ce 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_KernalReplace.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_KernalReplace.c @@ -334,6 +334,8 @@ FLASHMEM void FreeKernalBin() #endif } +void FnetLoopPollStart(); //Teensy.ino + FLASHMEM void InitHndlr_KERNALReplace_PreStart() //not called automatically via init; called earlier, manually, from KERNALPreStart() { #ifdef Fab04_GlobalKernalReplace @@ -400,6 +402,7 @@ FLASHMEM void InitHndlr_KERNALReplace_PreStart() //not called automatically via #else fBusSnoop = KernalCheckArm(); #endif + FnetLoopPollStart(); //Ethernet poll off the PIT timer from now on (see FnetLoopPollService in Teensy.ino) } FLASHMEM void InitHndlr_KernalReplace() diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/Swift_ATcommands.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/Swift_ATcommands.c index 24be470f..e35f6be6 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/Swift_ATcommands.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/Swift_ATcommands.c @@ -410,7 +410,11 @@ FLASHMEM ATRespCode AT_Hook(char* CmdArg) } +#ifdef Fab04_GlobalKernalReplace +FLASHMEM ATRespCode ProcessATCommandInner() //wrapped by ProcessATCommand() below +#else FLASHMEM ATRespCode ProcessATCommand() +#endif { char* CmdMsg = TxMsg; //local pointer for manipulation stcATCommand ATCommands[] = @@ -466,3 +470,15 @@ FLASHMEM ATRespCode ProcessATCommand() AddToPETSCIIStrToRxQueueLN(TxMsg); return ATRC_ERROR; } + +#ifdef Fab04_GlobalKernalReplace +extern volatile bool FnetLoopPoll; +FLASHMEM ATRespCode ProcessATCommand() +{ //SwiftLink can run while the KERNAL is replaced: connect/DHCP/DNS block in here waiting for the stack, which then needs + //its PIT poll timer (masked while the poll runs from loop(), see FnetLoopPollService in Teensy.ino) + if (FnetLoopPoll) NVIC_ENABLE_IRQ(IRQ_PIT); + ATRespCode r = ProcessATCommandInner(); + if (FnetLoopPoll) NVIC_DISABLE_IRQ(IRQ_PIT); + return r; +} +#endif diff --git a/Source/Teensy/Teensy.ino b/Source/Teensy/Teensy.ino index 3530c17f..8c250d30 100644 --- a/Source/Teensy/Teensy.ino +++ b/Source/Teensy/Teensy.ino @@ -55,6 +55,16 @@ uint8_t RAM_Image[RAM_ImageSize]; //Main RAM1 file storage buffer volatile uint8_t BtnPressed = false; volatile uint8_t EmulateVicCycles = false; uint8_t CurrentIOHandler = IOH_None; +#ifdef Fab04_KernalReplace +volatile bool FnetLoopPoll = false; //Ethernet stack polled from loop(), PIT timer masked (see FnetLoopPollService) +volatile bool FnetPollHold = false; //set by Kernal Replace PreStart, before CurrentIOHandler changes; cleared with the main menu +void FnetLoopPollService(); +FLASHMEM void FnetLoopPollStart() +{ //start polling from loop() right away: loop() only sees the new handler ~0.4 s after the launch, and late phi2 interrupts in that time crashed programs + FnetPollHold = true; + if (fnet_netif_get_default() != 0 && !FnetLoopPoll) { NVIC_DISABLE_IRQ(IRQ_PIT); FnetLoopPoll = true; } +} +#endif StructMenuItem *DriveDirMenu = NULL; uint16_t NumDrvDirMenuItems = 0; char DriveDirPath[MaxPathLength]; @@ -377,6 +387,10 @@ void loop() if (IO1[rwRegPwrUpDefaults2] & rpud2TRContEnabled) if (USBHostSerial.available()) ServiceSerial(&USBHostSerial); +#ifdef Fab04_KernalReplace + FnetLoopPollService(); +#endif + #ifdef FeatTCPListen if (NetListenEnable) { @@ -399,11 +413,40 @@ void loop() if (IOHandler[CurrentIOHandler]->PollingHndlr != NULL) IOHandler[CurrentIOHandler]->PollingHndlr(); } +#ifdef Fab04_KernalReplace +FLASHMEM void FnetLoopPollService() +{ //While Kernal Replace serves the KERNAL, run the Ethernet stack poll from here instead of NativeEthernet's 1ms PIT timer ISR: + //with the poll in the PIT interrupt, ~39 phi2 interrupts/s were late (>1.1us) even with the GPIO interrupt at a higher priority, + //and a late one on the HIRAM sense after a $01 write corrupts the read; called from loop() they are down to about one every 2s. + //(The PIT is only used by that timer, except for ASID, which masks it itself.) + static uint32_t lastPollUs = 0; + bool want = FnetPollHold || (CurrentIOHandler == IOH_KernalReplace) +#ifdef Fab04_GlobalKernalReplace + || (fKernRepl != NULL) +#endif + ; + want = want && (fnet_netif_get_default() != 0); //stack initialized + if (want != FnetLoopPoll) + { + FnetLoopPoll = want; + if (want) NVIC_DISABLE_IRQ(IRQ_PIT); else NVIC_ENABLE_IRQ(IRQ_PIT); + } + if (FnetLoopPoll && micros() - lastPollUs >= 1000) + { + lastPollUs = micros(); + fnet_poll(); + } +} +#endif + FLASHMEM void SetUpMainMenuROM() { PendingfBusSnoop = fBusSnoop = NULL; #ifdef Fab04_GlobalKernalReplace fKernRepl = NULL; +#endif +#ifdef Fab04_KernalReplace + FnetPollHold = false; #endif SetDMADeassert; SetIRQDeassert; diff --git a/docs/Architecture/Constraints.md b/docs/Architecture/Constraints.md index d9423bd5..978176d0 100644 --- a/docs/Architecture/Constraints.md +++ b/docs/Architecture/Constraints.md @@ -81,6 +81,10 @@ Beyond priority, Ethernet/PIT interrupts are fully **disabled** (not just deprio - **Firmware self-flash** — `Flash/FXUtil.cpp:175-176` disables both, and goes further by fully `detachInterrupt()`-ing the button and PHI2 pins entirely just above (lines 173-174) — there's no valid handler to service PHI2 while live firmware is being overwritten. - **ASID (MIDI SID) playback** — `IOH_ASID.c:579-580` disables both during ASID streaming, which has its own tight audio-rate timing requirement. +The priority alone does not keep the Ethernet stack out of `isrPHI2`'s way. NativeEthernet runs the whole FNET stack (`fnet_poll()`) from a 1 ms `IntervalTimer` (PIT) ISR, and with the stack initialized (no cable needed) about 39 PHI2 interrupts per second came in late (>1.1 µs), with the GPIO interrupt at the higher priority and with `IRQ_ENET` masked. A handler that serves bytes on reads it has to sense first (Kernal Replace's HIRAM sense) gets corrupt reads, NMIs or freezes from those. So while **Kernal Replace** serves the KERNAL (its Special IO, or `fKernRepl` with `Fab04_GlobalKernalReplace`), `IRQ_PIT` is masked and `FnetLoopPollService()` (`Teensy.ino`) calls `fnet_poll()` from `loop()` every ms instead: late PHI2 interrupts drop to about one every 2 s. Kernal Replace starts this in `InitHndlr_KERNALReplace_PreStart()` (`FnetLoopPollStart()`), since `loop()` sees the new handler only ~0.4 s after a launch; `SetUpMainMenuROM()` ends it. Two consequences: +- TCP is serviced only as often as `loop()` comes round meanwhile, so a `PollingHndlr` that blocks also stalls the network. +- Code that blocks waiting for the stack (`connect`, DHCP, DNS) needs the PIT poll back: `ProcessATCommand()` (`Swift_ATcommands.c`) unmasks `IRQ_PIT` while an AT command runs (SwiftLink under `Fab04_GlobalKernalReplace`). New blocking network calls that can run while the KERNAL is replaced need the same. + ## The main `loop()` is single-threaded — a blocking call anywhere in it stalls every other main-loop-driven task `Teensy.ino`'s `loop()` dispatches `ServiceSerial()`/`ProcessCommand()` (incoming serial/network