Skip to content

Kernal Replace: poll the Ethernet stack from loop(), not the PIT ISR - #85

Open
wesbiggs wants to merge 2 commits into
SensoriumEmbedded:mainfrom
wesbiggs:kernal-fnet-loop-poll
Open

wesbiggs wants to merge 2 commits into
SensoriumEmbedded:mainfrom
wesbiggs:kernal-fnet-loop-poll

Conversation

@wesbiggs

@wesbiggs wesbiggs commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Once Ethernet is initialized (a cable is not needed), NativeEthernet runs the whole FNET stack from a 1 ms IntervalTimer (PIT) ISR (FNET_POLL_TIME in NativeEthernet.h). Measured with late-ISR counters in isrPHI2, about 39 phi2 interrupts per second came in late (>1.1 µs) even though the GPIO interrupt has the higher priority. Masking IRQ_ENET changed nothing; masking IRQ_PIT removed them.

Under Kernal Replace, late interrupts hurt most on the first read after a $01 write, where KernalCheck senses HIRAM before it drives the bus. Programs that switch $01 constantly got corrupt reads, NMIs or froze:

  • My test RPG project game (keeps its map under the I/O area and the KERNAL)
  • CCGMS with SwiftLink over TCP, running under a replaced KERNAL (its NMI handler writes $01)

Change

While Kernal Replace serves the KERNAL and the stack is up, IRQ_PIT is masked and FnetLoopPollService() (Teensy.ino, FLASHMEM) calls fnet_poll() from loop() every ms. Late phi2 interrupts drop to about one every 2 s. "Serves the KERNAL" means the Kernal Replace Special IO, or fKernRepl set under Fab04_GlobalKernalReplace.

  • Starts at PreStart. loop() only sees the new handler ~0.4 s after a launch, and the ~15 late interrupts in that window were still enough to crash Quest for the King. InitHndlr_KERNALReplace_PreStart() therefore starts the loop poll at once (FnetLoopPollStart()), and SetUpMainMenuROM() ends it.
  • AT commands. Under Fab04_GlobalKernalReplace, SwiftLink can run while the KERNAL is replaced. connect, DHCP and DNS block waiting for the stack, so ProcessATCommand() unmasks IRQ_PIT while an AT command runs. The wrapper is compiled only under that flag.
  • TR+ only. Everything is guarded by Fab04_KernalReplace, so the plain TR build is unchanged (same link report as main).
  • Scope. Other handlers are unchanged, plain SwiftLink on the stock KERNAL included: its late-interrupt rate hasn't been measured, so it isn't in this PR.
  • Docs. docs/Architecture/Constraints.md describes the mechanism and its two consequences: TCP is serviced only as often as loop() comes round, and new blocking network calls that can run while the KERNAL is replaced need the PIT back.

Memory (link report, before → after)

TR+ (--no-extensions) TR
RAM1 free for local variables 85532 → 85532 unchanged
RAM1 padding 31800 → 31784 unchanged
RAM2 variables 17600 → 17600 unchanged
FLASH code 269004 → 269284 unchanged

Testing

  • Hardware: real C64 (breadbin), TR+ (PCB v0.4), with Ethernet initialized.
    • RPG plays and loads/saves under Kernal Replace.
    • CCGMS with SwiftLink over TCP under a replaced KERNAL: no stalls or flow-control problems; a repeatable first-page receive glitch remains, cause not yet found.
    • Tested as part of the teensyrom-vdrive firmware build (the same mechanism, with VDrive's handlers in the list).
  • Host: npm test passes; TR and TR+ firmware build, and TR+ also builds with Fab04_GlobalKernalReplace defined.
  • No host test added: the change is interrupt masking and polling cadence, which the existing harnesses can't exercise meaningfully.

🤖 Generated with Claude Code

wesbiggs added a commit to wesbiggs/teensyrom-vdrive that referenced this pull request Oct 31, 2026
The poll from loop() went upstream as SensoriumEmbedded/TeensyROM#85, in a
narrower form: Kernal Replace only, a FLASHMEM function, guarded by
Fab04_KernalReplace, the AT-command PIT unmask only under
Fab04_GlobalKernalReplace. firmware/kernal-fnet-poll.patch is that PR byte
for byte and make_tr_patch.sh applies it first; kernalreplace-fixes.patch
keeps only KernalFast and the LATE_ISR_TRACE trace, on top of it.

The VDrive-specific parts are edits in make_tr_patch.sh: the VDrive Special
IOs join the poll list, and the PIT unmask also compiles under Fab04_VDrive
(VDrive+SwiftLink runs AT commands with the poll on). Plain SwiftLink on the
stock KERNAL no longer switches to the loop poll, as in the PR: it was never
measured. Firmware builds for TR+ and TR, fw_host passes, and the patch
applies on fa509421 and on open PRs 81-83. Not on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wesbiggs and others added 2 commits November 1, 2026 09:12
Once Ethernet is initialized (no cable needed), NativeEthernet runs the whole
FNET stack from a 1 ms IntervalTimer (PIT) ISR. That made about 39 phi2
interrupts per second late (>1.1 us), even with the GPIO interrupt at the
higher priority; masking IRQ_ENET did not help, masking IRQ_PIT did. Late
interrupts hurt most on the first read after a $01 write, where Kernal
Replace senses HIRAM before it drives the bus: Quest for the King (which
switches $01 constantly) got corrupt reads, NMIs or froze, and so did CCGMS
with SwiftLink over TCP under a replaced KERNAL.

While Kernal Replace serves the KERNAL and the stack is up, mask IRQ_PIT and
call fnet_poll() from loop() every ms (FnetLoopPollService, FLASHMEM). Late
phi2 interrupts drop to about one every 2 s. PreStart starts it, because
loop() only sees the new handler ~0.4 s after a launch and ~15 late
interrupts in that window still crashed Quest for the King; the main menu
ends it. Under Fab04_GlobalKernalReplace SwiftLink can run meanwhile, and its
AT commands block waiting for the stack (connect, DHCP, DNS), so
ProcessATCommand() unmasks IRQ_PIT while one runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plain TR has no Kernal Replace (Fab04_KernalReplace is TR+ only), so the
poll there never started but still ran its check on every loop() and cost
flash and RAM1. Guard it with Fab04_KernalReplace: the TR image is now
byte-for-byte the size of main's. The ProcessATCommand() wrapper matters only
when SwiftLink can run while the KERNAL is replaced, i.e. under
Fab04_GlobalKernalReplace, so it is guarded by that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wesbiggs
wesbiggs force-pushed the kernal-fnet-loop-poll branch from f9faebd to aaa2b8c Compare November 1, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant