RunCPM 6.9: single shared core, N:CPM telnet console (:8677), RSX bar commands - #1371
RunCPM 6.9: single shared core, N:CPM telnet console (:8677), RSX bar commands#1371peterkaczorowski wants to merge 8 commits into
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. RunCPM 6.9 Refactoring Consolidate the three duplicate RunCPM engines (SIO 'G', N:CPM://, telnet) into ONE shared core: runcpm_core.cpp is the only TU that compiles the Z80/CP/M chain (external linkage); transports are thin console back-ends via runcpm_session.h's runcpm_console_ops + a g_busy single-instance interlock. preTables moves the ~11KB Z80 flag tables to flash and consolidation reclaims ~22KB, fixing the ESP32 dram0_0_seg overflow. abstraction_fujinet_core.h holds the shared disk/SD section. 2. Add N:CPM telnet console on port 8677 New vm_telnet.cpp exposes a loopback TELNET server (127.0.0.1:8677) bridged through libtelnet to the shared core, so an Atari reaches CP/M via N:TELNET://127.0.0.1:8677. Started from fn_service_loop only after lwIP is up (esp_netif_init in fnWiFi.start), and the acceptor waits for an IP then is pinned to core 0 so the priority-17 SIO loop on core 1 cannot starve it. Avoids the boot loop and "port open but no banner" failures seen on real hardware. 3. Add RSX bar commands New vm_bar.cpp/.h implement the VM-generic bar-command gate (|ftp, |wget, |fn, |apt, |help) wired into ccp.h via vm_bar_io trampolines built from the core's _getch/_putch/_puts, so the bar talks to whichever transport is active. Named vm_ (not cpm_) so future non-CP/M VMs can reuse the same telnet console + bar plumbing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…bol collisions The single-shared-RunCPM-core refactor left four per-bus CP/M transports still including the RunCPM header chain directly, and the shared core TU (runcpm_core.cpp) is linked on every target because N:CPM:// is universal. This broke the APPLE, COCO, RS232 and Windows ATARI CI builds. - runcpm core: rename RunCPM's `Command` struct -> `CcpCommand` (ccp.h) and the `CPM` banner macro -> `CPM_VERSTR` (globals.h) to avoid collisions with FujiNet's `class Command` (devrelay) and `CPM` enumerator (iwm.h) that the bus headers pull into the core TU on APPLE/COCO. - iwm/cpm.cpp, drivewire/cpm.cpp: convert to thin queue-based console back-ends over file-static rxq/txq (ESP-only), running the shared session loop. - rs232cpm.cpp, rc2014cpm.cpp: convert to no-op console back-ends. These buses have no character-stream console API; historically their console was a stub (BYPASS_BUS is defined only for BUILD_ATARI) that busy-looped forever. getch now returns CP/M EOF so the session ends gracefully instead of hanging. - vm_bar.cpp: include the mongoose-pulling HTTP client header after the FujiNet bus headers so NetSIO.h parses before mongoose's `poll` macro is defined (fixes the Windows ATARI NetSIO.h "function-like macro" error). Verified: PC builds for ATARI, APPLE, RS232, COCO and the ESP32 ATARI firmware link (RAM 36.7%, no DRAM overflow). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On ESP32 the VM worker (cpmtel) is pinned to core 1 at priority 20, above the SIO bus pump fnLoop (priority 17, core 1) and IDLE1 (priority 0). CP/M full-screen programs (vi, etc.) poll console status (BDOS 6/11 -> _kbhit) in a tight loop while waiting for a keypress. _vm_kbhit returned immediately, so the priority-20 worker never blocked: it starved fnLoop (all SIO timed out, including the Atari's own N:TELNET reads back into this console -> "Timeout after 2000 ms" / "Retry attempts failed") and IDLE1 (task watchdog never fed, deleted tasks never reaped), wedging the device into an unrecoverable hang where even the safe-reset button stops responding. taskYIELD() cannot fix this (it only yields to equal/higher priority, and the worker is the highest-priority task on core 1), so block for one tick (vTaskDelay(1)) when no input is pending. Cost is idle-path latency only: real Z80 computation does not poll kbhit, and a screen redraw polls it once after many putch calls, so throughput is unaffected. DIR worked before because the CCP blocks in getch (xQueueReceive, portMAX_DELAY) between commands; only the non-blocking kbhit poll path was affected. Never seen on FujiNet-PC because each console there is a real OS thread, time-sliced across cores with no watchdog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The idle CP/M telnet pump spun core 0 at priority 10 with fnSystem.delay(2) (a no-op vTaskDelay(0) at 100 Hz), starving the priority-1 reset-button poller for the whole session. A real one-tick block on idle iterations hands core 0 to the button task. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_sys_readseq/_sys_readrand reopened the file for every 128-byte record (~310 opens for a 39 KB program at ~11 ms each on SD). A shared read-only handle, invalidated before writes and at session boundaries, makes it one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The banner now reads "FujiNet CP/M 2.2 - RunCPM 6.9", keeping the version macros so it still tracks the actual build. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Closing in favour of a focused, individually-buildable stack of 3 PRs off
Suggested review/merge order: #1391 → #1392 → #1393. One note on why #1391 is two topics in one PR rather than separate "6.9 bump" and "shared-core refactor" PRs: they are inseparable. RunCPM 6.9 carries substantially more Each PR builds clean on |
Summary
'G',N:CPM://, telnet) into ONE shared core.runcpm_core.cppis now the only TU that compiles the Z80/CP/M chain (external linkage); the transports are thin console back-ends that plug into it throughruncpm_session.h'sruncpm_console_ops, guarded by ag_busysingle-instance interlock.abstraction_fujinet_core.hholds the shared disk/SD section.preTablesmoves the ~11 KB Z80 flag tables to flash and the consolidation reclaims ~22 KB, fixing the ESP32dram0_0_segoverflow.vm_telnet.cppexposes a loopback TELNET server (127.0.0.1:8677) bridged through libtelnet to the shared core, so an Atari reaches CP/M viaN:TELNET://127.0.0.1:8677. It is started fromfn_service_looponly after lwIP is up (esp_netif_initinsidefnWiFi.start); the acceptor waits for an IP and is pinned to core 0 so the priority-17 SIO loop on core 1 cannot starve it.vm_bar.cpp/.himplement the VM-generic bar-command gate (|ftp,|wget,|fn,|apt,|help) wired intoccp.hviavm_bar_iotrampolines built from the core's_getch/_putch/_puts, so the bar talks to whichever transport is active. Namedvm_(notcpm_) so future non-CP/M VMs can reuse the same telnet console + bar plumbing.ESP32 hardware bring-up fixes
Found and fixed on real Atari FujiNet (fujinet-atari-v1) hardware:
assert tcpip_send_msg_wait_sem ... Invalid mbox): the telnet acceptor opened a socket before lwIP existed. Movedvm_telnet_start()out ofmain_setupintofn_service_loop, after the WiFi/BT block, gated on the network stack being up.fnWiFi.connected()beforebind()/listen().Test plan
cmake --build build --target distlinks clean;nmshows a single copy of the Z80 tables (cbitsZ80Table/cbitsTable/cbits2Table).telnet 127.0.0.1 8677→ CP/M banner,DIR, run a.COM,|help, clean exit../build.sh -blinks (RAM 36.7%, nodram0_0_segoverflow).N:TELNET://127.0.0.1:8677reaches CP/M (banner + session); bind restored to loopback127.0.0.1:8677.'G'CP/M andN:CPM://regression on hardware.🤖 Generated with Claude Code