Skip to content

RunCPM 6.9: single shared core, N:CPM telnet console (:8677), RSX bar commands - #1371

Closed
peterkaczorowski wants to merge 8 commits into
FujiNetWIFI:masterfrom
peterkaczorowski:update-runcpm-6.9
Closed

RunCPM 6.9: single shared core, N:CPM telnet console (:8677), RSX bar commands#1371
peterkaczorowski wants to merge 8 commits into
FujiNetWIFI:masterfrom
peterkaczorowski:update-runcpm-6.9

Conversation

@peterkaczorowski

Copy link
Copy Markdown
Contributor

Summary

  • RunCPM 6.9 refactoring — consolidate the three duplicate RunCPM engines (SIO 'G', N:CPM://, telnet) into ONE shared core. runcpm_core.cpp is now the only TU that compiles the Z80/CP/M chain (external linkage); the transports are thin console back-ends that plug into it through runcpm_session.h's runcpm_console_ops, guarded by a g_busy single-instance interlock. abstraction_fujinet_core.h holds the shared disk/SD section. preTables moves the ~11 KB Z80 flag tables to flash and the consolidation reclaims ~22 KB, fixing the ESP32 dram0_0_seg overflow.
  • 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. It is started from fn_service_loop only after lwIP is up (esp_netif_init inside fnWiFi.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.
  • 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.

ESP32 hardware bring-up fixes

Found and fixed on real Atari FujiNet (fujinet-atari-v1) hardware:

  • Boot loop (assert tcpip_send_msg_wait_sem ... Invalid mbox): the telnet acceptor opened a socket before lwIP existed. Moved vm_telnet_start() out of main_setup into fn_service_loop, after the WiFi/BT block, gated on the network stack being up.
  • Listen-before-IP: acceptor now waits for fnWiFi.connected() before bind()/listen().
  • "Port open but no CP/M banner": the priority-10 acceptor pinned to core 1 was permanently starved by the priority-17 SIO loop. Pinned the acceptor to core 0.

Test plan

  • FujiNet-PC: cmake --build build --target dist links clean; nm shows a single copy of the Z80 tables (cbitsZ80Table/cbitsTable/cbits2Table).
  • FujiNet-PC: telnet 127.0.0.1 8677 → CP/M banner, DIR, run a .COM, |help, clean exit.
  • ESP32: ./build.sh -b links (RAM 36.7%, no dram0_0_seg overflow).
  • ESP32 on hardware: boots without loop; N:TELNET://127.0.0.1:8677 reaches CP/M (banner + session); bind restored to loopback 127.0.0.1:8677.
  • SIO 'G' CP/M and N:CPM:// regression on hardware.

🤖 Generated with Claude Code

peterkaczorowski and others added 8 commits June 21, 2026 08:41
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>
@peterkaczorowski

Copy link
Copy Markdown
Contributor Author

Closing in favour of a focused, individually-buildable stack of 3 PRs off master (per @FozzTexx's request to split this into one topic per branch):

  1. RunCPM: upgrade to 6.9 and consolidate transports onto one shared core #1391 — RunCPM 6.9 + single shared stateless core
  2. RunCPM: loopback TELNET console on 127.0.0.1:8677 #1392 — loopback TELNET console (127.0.0.1:8677) — stacked on RunCPM: upgrade to 6.9 and consolidate transports onto one shared core #1391
  3. RunCPM: RSX-style bar-command gate for the CCP #1393 — RSX-style bar-command gate for the CCP — stacked on RunCPM: loopback TELNET console on 127.0.0.1:8677 #1392

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 static global state than 6.5, and each CP/M transport TU keeps its own private copy via internal linkage. Five copies of 6.9's larger state overflow the ESP32 DRAM .dram0.bss by ~58 KB, so a standalone "6.9 bump" PR does not link. The shared-core consolidation is what makes 6.9 fit (back to RAM 36.8%), so the two must land together.

Each PR builds clean on fujinet-atari-v1 (RAM 36.8%) and the three together reconstruct exactly the change set from this PR.

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