From ab35bf23a63639989db32b9a9079b948df5b556f Mon Sep 17 00:00:00 2001 From: "Peter D. Kaczorowski" Date: Mon, 29 Jun 2026 22:53:56 +0200 Subject: [PATCH 1/2] RunCPM: consolidate bus CP/M devices onto a shared cpmDevice base class All lib/device/*/cpm devices were near-identical and each embedded its own copy of the RunCPM engine. Introduce a single lib/device/cpm base class (cpmDevice, plus cpmQueueDevice for buses whose engine runs on its own task) and a single shared engine TU (runcpm_core.cpp via runcpm_session.h). Per-bus devices now override only four console-endpoint primitives. Buses without a working CP/M console (rc2014, rs232) inherit a default clean-exit endpoint instead of returning hardcoded values, so CP/M cold-boots and exits cleanly everywhere. Fold N:CPM:// onto the same shared engine. Built against the current RunCPM 5.8 (no engine version change). --- fujinet_pc.cmake | 2 + lib/bus/rc2014bus/rc2014bus.cpp | 2 +- lib/bus/rc2014sio/rc2014sio.cpp | 2 +- lib/bus/rs232/rs232.cpp | 2 +- lib/bus/sio/sio.cpp | 2 +- lib/device/cpm/cpm.cpp | 166 +++++ lib/device/cpm/cpm.h | 100 +++ lib/device/drivewire/cpm.cpp | 86 +-- lib/device/drivewire/cpm.h | 18 +- lib/device/iwm/cpm.cpp | 84 +-- lib/device/iwm/cpm.h | 18 +- lib/device/rc2014/rc2014cpm.cpp | 61 +- lib/device/rc2014/rc2014cpm.h | 13 +- lib/device/rs232/rs232cpm.cpp | 63 +- lib/device/rs232/rs232cpm.h | 18 +- lib/device/sio/siocpm.cpp | 64 +- lib/device/sio/siocpm.h | 25 +- lib/network-protocol/CPM.cpp | 150 +++-- lib/runcpm/abstraction_fujinet.h | 227 +------ lib/runcpm/abstraction_fujinet_apple2.h | 705 ---------------------- lib/runcpm/abstraction_network_protocol.h | 476 --------------- lib/runcpm/globals.h | 31 +- lib/runcpm/runcpm_core.cpp | 101 ++++ lib/runcpm/runcpm_session.h | 39 ++ src/CMakeLists.txt | 1 + 25 files changed, 613 insertions(+), 1843 deletions(-) create mode 100644 lib/device/cpm/cpm.cpp create mode 100644 lib/device/cpm/cpm.h delete mode 100644 lib/runcpm/abstraction_fujinet_apple2.h delete mode 100644 lib/runcpm/abstraction_network_protocol.h create mode 100644 lib/runcpm/runcpm_core.cpp create mode 100644 lib/runcpm/runcpm_session.h diff --git a/fujinet_pc.cmake b/fujinet_pc.cmake index 740cbe244..0eeb0fbf9 100644 --- a/fujinet_pc.cmake +++ b/fujinet_pc.cmake @@ -279,6 +279,8 @@ set(SOURCES src/main.cpp lib/device/network.h lib/device/netstream.h lib/device/siocpm.h + lib/runcpm/runcpm_session.h lib/runcpm/runcpm_core.cpp + lib/device/cpm/cpm.h lib/device/cpm/cpm.cpp lib/modem-sniffer/modem-sniffer.h lib/modem-sniffer/modem-sniffer.cpp lib/media/media.h lib/encoding/base64.h lib/encoding/base64.cpp diff --git a/lib/bus/rc2014bus/rc2014bus.cpp b/lib/bus/rc2014bus/rc2014bus.cpp index 9c628631a..eb39dd934 100644 --- a/lib/bus/rc2014bus/rc2014bus.cpp +++ b/lib/bus/rc2014bus/rc2014bus.cpp @@ -330,7 +330,7 @@ void systemBus::service() #if 0 if (_cpmDev != nullptr && _cpmDev->cpmActive) { - _cpmDev->rc2014_handle_cpm(); + _cpmDev->handle_cpm(); return; // break! } #endif diff --git a/lib/bus/rc2014sio/rc2014sio.cpp b/lib/bus/rc2014sio/rc2014sio.cpp index ee602c506..21240883f 100644 --- a/lib/bus/rc2014sio/rc2014sio.cpp +++ b/lib/bus/rc2014sio/rc2014sio.cpp @@ -267,7 +267,7 @@ void systemBus::service() #if 0 if (_cpmDev != nullptr && _cpmDev->cpmActive) { - _cpmDev->rc2014_handle_cpm(); + _cpmDev->handle_cpm(); return; // break! } #endif diff --git a/lib/bus/rs232/rs232.cpp b/lib/bus/rs232/rs232.cpp index 45a15e29d..8b2a59be9 100755 --- a/lib/bus/rs232/rs232.cpp +++ b/lib/bus/rs232/rs232.cpp @@ -179,7 +179,7 @@ void systemBus::service() if (_cpmDev != nullptr && _cpmDev->cpmActive) { - _cpmDev->rs232_handle_cpm(); + _cpmDev->handle_cpm(); return; // break! } diff --git a/lib/bus/sio/sio.cpp b/lib/bus/sio/sio.cpp index 4cf1fa1b6..e6c1766c0 100755 --- a/lib/bus/sio/sio.cpp +++ b/lib/bus/sio/sio.cpp @@ -395,7 +395,7 @@ void systemBus::service() } else if (_cpmDev != nullptr && _cpmDev->cpmActive && Config.get_cpm_enabled()) { - _cpmDev->sio_handle_cpm(); + _cpmDev->handle_cpm(); return; // break! } diff --git a/lib/device/cpm/cpm.cpp b/lib/device/cpm/cpm.cpp new file mode 100644 index 000000000..1e46bf0d8 --- /dev/null +++ b/lib/device/cpm/cpm.cpp @@ -0,0 +1,166 @@ +#include "cpm.h" + +#include + +// --------------------------------------------------------------------------- +// cpmDevice: drive the single shared engine through this device's endpoint. +// --------------------------------------------------------------------------- + +cpmDevice *cpmDevice::s_active = nullptr; + +int cpmDevice::s_kbhit() +{ + return s_active ? s_active->ep_kbhit() : 0; +} + +uint8_t cpmDevice::s_getch() +{ + return s_active ? s_active->ep_getch() : 0x03; +} + +void cpmDevice::s_putch(uint8_t c) +{ + if (s_active) + s_active->ep_putch(c); +} + +void cpmDevice::s_clrscr() +{ + if (s_active) + s_active->ep_clrscr(); +} + +void cpmDevice::handle_cpm() +{ + s_active = this; + + runcpm_console_ops ops; + ops.kbhit = &cpmDevice::s_kbhit; + ops.getch = &cpmDevice::s_getch; + ops.putch = &cpmDevice::s_putch; + ops.clrscr = &cpmDevice::s_clrscr; + + runcpm_session_run(&ops); + + cpmActive = false; + s_active = nullptr; +} + +// --------------------------------------------------------------------------- +// cpmQueueDevice: byte queues between the bus task and the engine task. +// --------------------------------------------------------------------------- + +#ifdef ESP_PLATFORM + +cpmQueueDevice::cpmQueueDevice() +{ + rxq = xQueueCreate(2048, sizeof(uint8_t)); + txq = xQueueCreate(2048, sizeof(uint8_t)); +} + +cpmQueueDevice::~cpmQueueDevice() +{ + if (rxq) + vQueueDelete(rxq); + if (txq) + vQueueDelete(txq); +} + +size_t cpmQueueDevice::host_available() +{ + return uxQueueMessagesWaiting(rxq); +} + +size_t cpmQueueDevice::host_read(uint8_t *buf, size_t max) +{ + size_t n = 0; + while (n < max && xQueueReceive(rxq, &buf[n], 0) == pdTRUE) + n++; + return n; +} + +void cpmQueueDevice::host_write(const uint8_t *buf, size_t len) +{ + for (size_t i = 0; i < len; i++) + xQueueSend(txq, &buf[i], portMAX_DELAY); +} + +int cpmQueueDevice::ep_kbhit() +{ + return uxQueueMessagesWaiting(txq) ? 1 : 0; +} + +uint8_t cpmQueueDevice::ep_getch() +{ + uint8_t c = 0; + xQueueReceive(txq, &c, portMAX_DELAY); + return c; +} + +void cpmQueueDevice::ep_putch(uint8_t c) +{ + xQueueSend(rxq, &c, portMAX_DELAY); +} + +#else // !ESP_PLATFORM + +cpmQueueDevice::cpmQueueDevice() = default; +cpmQueueDevice::~cpmQueueDevice() = default; + +size_t cpmQueueDevice::host_available() +{ + std::lock_guard lock(rxmtx); + return rxq.size(); +} + +size_t cpmQueueDevice::host_read(uint8_t *buf, size_t max) +{ + std::lock_guard lock(rxmtx); + size_t n = 0; + while (n < max && !rxq.empty()) + { + buf[n++] = rxq.front(); + rxq.pop(); + } + return n; +} + +void cpmQueueDevice::host_write(const uint8_t *buf, size_t len) +{ + { + std::lock_guard lock(txmtx); + for (size_t i = 0; i < len; i++) + txq.push(buf[i]); + } + txcv.notify_one(); +} + +int cpmQueueDevice::ep_kbhit() +{ + std::lock_guard lock(txmtx); + return txq.empty() ? 0 : 1; +} + +uint8_t cpmQueueDevice::ep_getch() +{ + std::unique_lock lock(txmtx); + txcv.wait(lock, [this] { return !txq.empty(); }); + uint8_t c = txq.front(); + txq.pop(); + return c; +} + +void cpmQueueDevice::ep_putch(uint8_t c) +{ + std::lock_guard lock(rxmtx); + rxq.push(c); +} + +#endif // ESP_PLATFORM + +void cpmQueueDevice::ep_clrscr() +{ + static const uint8_t seq[] = {0x1B, '[', '1', ';', '1', 'H', 0x1B, '[', '2', 'J'}; + for (uint8_t c : seq) + ep_putch(c); +} diff --git a/lib/device/cpm/cpm.h b/lib/device/cpm/cpm.h new file mode 100644 index 000000000..26d284067 --- /dev/null +++ b/lib/device/cpm/cpm.h @@ -0,0 +1,100 @@ +#ifndef DEVICE_CPM_BASE_H +#define DEVICE_CPM_BASE_H + +// Shared CP/M device base. +// +// Every bus that can run CP/M (SIO/Atari, IWM/Apple, DriveWire/CoCo, RS232, +// RC2014, and the N:CPM:// network adapter) drives the one shared RunCPM +// engine (lib/runcpm/runcpm_core.cpp) through runcpm_session_run(). The only +// thing that differs between buses is how a console byte gets to and from the +// far end of the link - a modem/stream endpoint. cpmDevice captures that as +// four virtual endpoint primitives (ep_kbhit/ep_getch/ep_putch/ep_clrscr) and +// turns them into the C callbacks the engine wants. +// +// The default endpoint is a "no console" stub that asks the session to exit at +// the first read - that is the right behaviour for a bus whose CP/M console is +// not wired up yet (RC2014, RS232): the engine cold-boots, the CCP tries to +// read a command, gets ^C and a clean exit instead of hanging. +// +// cpmQueueDevice adds a pair of byte queues for buses whose bus thread and CP/M +// engine run concurrently (IWM, DriveWire): the bus side calls host_read / +// host_write / host_available, the engine side blocks on the queues. + +#include +#include + +#include "bus.h" +#include "../runcpm/runcpm_session.h" + +#ifdef ESP_PLATFORM +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#else +#include +#include +#include +#endif + +class cpmDevice : public virtualDevice +{ +public: + bool cpmActive = false; + + // Set the link baud rate (and anything else) before a session starts. + // Buses that have nothing to do here leave it as the no-op default. + virtual void init_cpm(int baud) { (void)baud; } + + // Run one blocking CP/M session against this device's endpoint. Returns + // when the program exits CP/M (or an exit is requested). + void handle_cpm(); + +protected: + // Console endpoint - override per bus. The default is the "no console" + // stub used by buses without a working CP/M console. + virtual int ep_kbhit() { return 1; } + virtual uint8_t ep_getch() { runcpm_session_request_exit(); return 0x03; } + virtual void ep_putch(uint8_t) { } + virtual void ep_clrscr() { } + +private: + // The engine talks to plain C function pointers; these trampolines forward + // to the device that owns the running session (only one runs at a time). + static cpmDevice *s_active; + static int s_kbhit(); + static uint8_t s_getch(); + static void s_putch(uint8_t c); + static void s_clrscr(); +}; + +// Base for buses whose bus service and the CP/M engine run on separate tasks +// and shuttle bytes through queues (IWM/Apple, DriveWire/CoCo). +class cpmQueueDevice : public cpmDevice +{ +public: + cpmQueueDevice(); + ~cpmQueueDevice(); + + // Bus side (host) view of the link. + size_t host_available(); + size_t host_read(uint8_t *buf, size_t max); + void host_write(const uint8_t *buf, size_t len); + +protected: + // Engine side of the link. + int ep_kbhit() override; + uint8_t ep_getch() override; + void ep_putch(uint8_t c) override; + void ep_clrscr() override; + +private: +#ifdef ESP_PLATFORM + QueueHandle_t rxq = nullptr; // CP/M -> host + QueueHandle_t txq = nullptr; // host -> CP/M +#else + std::queue rxq, txq; + std::mutex rxmtx, txmtx; + std::condition_variable txcv; +#endif +}; + +#endif // DEVICE_CPM_BASE_H diff --git a/lib/device/drivewire/cpm.cpp b/lib/device/drivewire/cpm.cpp index 50cc4ddb4..8581d11b1 100644 --- a/lib/device/drivewire/cpm.cpp +++ b/lib/device/drivewire/cpm.cpp @@ -2,67 +2,24 @@ #ifdef ESP_PLATFORM -#define CCP_INTERNAL - #include "cpm.h" -#include "fnSystem.h" -#include "fnWiFi.h" -#include "fujiDevice.h" -#include "fnFS.h" -#include "fnFsSD.h" - -#include "../runcpm/globals.h" -#include "../runcpm/abstraction_fujinet_apple2.h" -#include "../runcpm/ram.h" // ram.h - Implements the RAM -#include "../runcpm/console.h" // console.h - implements console. -#include "../runcpm/cpu.h" // cpu.h - Implements the emulated CPU -#include "../runcpm/disk.h" // disk.h - Defines all the disk access abstraction functions -#include "../runcpm/host.h" // host.h - Custom host-specific BDOS call -#include "../runcpm/cpm.h" // cpm.h - Defines the CPM structures and calls -#ifdef CCP_INTERNAL -# include "../runcpm/ccp.h" // ccp.h - Defines a simple internal CCP -#endif +#include "../../include/debug.h" +// The CP/M engine runs on its own task; read()/write() shuttle bytes to and +// from it through the cpmQueueDevice byte queues. handle_cpm() runs one full +// CP/M session and returns when the program exits CP/M; the loop starts a fresh +// session afterwards, exactly like the old free-running cpmTask did. static void cpmTask(void *arg) { Debug_printf("cpmTask()\n"); while (1) { - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8_t *)malloc(MEMSIZE); - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); -#ifdef ESP_PLATFORM // OS vTaskDelay(100); -#endif - _puts(CCPHEAD); - _PatchCPM(); - _ccp(); + theCPM.handle_cpm(); } } -drivewireCPM::drivewireCPM() -{ - rxq = xQueueCreate(2048, sizeof(char)); - txq = xQueueCreate(2048, sizeof(char)); -} - -// drivewireCPM::~drivewireCPM() -// { -// if (cpmTaskHandle != NULL) -// { -// vTaskDelete(cpmTaskHandle); -// } - -// vQueueDelete(rxq); -// vQueueDelete(txq); -// } - void drivewireCPM::ready() { SYSTEM_BUS.write(0x01); @@ -80,7 +37,6 @@ void drivewireCPM::send_response() void drivewireCPM::boot() { -#ifdef ESP_PLATFORM if (cpmTaskHandle != NULL) { vTaskDelete(cpmTaskHandle); @@ -88,7 +44,6 @@ void drivewireCPM::boot() } xTaskCreatePinnedToCore(cpmTask, "cpmtask", 32768, NULL, 20, &cpmTaskHandle, 1); -#endif /* ESP_PLATFORM */ } void drivewireCPM::read() @@ -96,7 +51,7 @@ void drivewireCPM::read() uint8_t lenh = SYSTEM_BUS.read(); uint8_t lenl = SYSTEM_BUS.read(); uint16_t len = (lenh * 256) + lenl; - uint16_t mw = uxQueueMessagesWaiting(rxq); + uint16_t mw = host_available(); if (!len) return; @@ -104,18 +59,12 @@ void drivewireCPM::read() if (!mw) return; - response.clear(); - response.shrink_to_fit(); - - for (uint16_t i=0; i mw) + len = mw; -#ifdef ESP_PLATFORM - xQueueReceive(rxq, &b, portMAX_DELAY); -#endif /* ESP_PLATFORM */ - response += b; - } + response.resize(len); + size_t got = host_read((uint8_t *)&response[0], len); + response.resize(got); } void drivewireCPM::write() @@ -129,21 +78,16 @@ void drivewireCPM::write() for (uint16_t i=0;i> 8; status_response[1] = mw & 0xFF; diff --git a/lib/device/drivewire/cpm.h b/lib/device/drivewire/cpm.h index ceb6333c3..2f6aa5835 100644 --- a/lib/device/drivewire/cpm.h +++ b/lib/device/drivewire/cpm.h @@ -4,28 +4,18 @@ #ifdef ESP_PLATFORM -#include "bus.h" +#include +#include "../cpm/cpm.h" -#define FOLDERCHAR '/' - -// Silly typedefs that runcpm uses -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -class drivewireCPM : public virtualDevice +class drivewireCPM : public cpmQueueDevice { private: std::string response; -#ifdef ESP_PLATFORM TaskHandle_t cpmTaskHandle = NULL; -#endif /* ESP_PLATFORM */ public: - drivewireCPM(); - // virtual ~drivewireCPM(); virtual void process(); virtual void ready(); virtual void send_response(); @@ -38,4 +28,4 @@ class drivewireCPM : public virtualDevice extern drivewireCPM theCPM; #endif /* ESP_PLATFORM */ -#endif /* DRIVEWIRECPM_H */ \ No newline at end of file +#endif /* DRIVEWIRECPM_H */ diff --git a/lib/device/iwm/cpm.cpp b/lib/device/iwm/cpm.cpp index a600a1913..2e1934ee5 100644 --- a/lib/device/iwm/cpm.cpp +++ b/lib/device/iwm/cpm.cpp @@ -1,57 +1,30 @@ #ifdef BUILD_APPLE -#define CCP_INTERNAL #include "cpm.h" +#include + +#include "../../include/debug.h" #include "fnSystem.h" -#include "fnWiFi.h" -#include "fujiDevice.h" -#include "fnFS.h" -#include "fnFsSD.h" -#include "fnConfig.h" #include "compat_string.h" -#include "../runcpm/abstraction_fujinet_apple2.h" - -#include "../runcpm/globals.h" -#include "../runcpm/ram.h" // ram.h - Implements the RAM -#include "../runcpm/console.h" // console.h - implements console. -#include "../runcpm/cpu.h" // cpu.h - Implements the emulated CPU -#include "../runcpm/disk.h" // disk.h - Defines all the disk access abstraction functions -#include "../runcpm/host.h" // host.h - Custom host-specific BDOS call -#include "../runcpm/cpm.h" // cpm.h - Defines the CPM structures and calls -#ifdef CCP_INTERNAL -#include "../runcpm/ccp.h" // ccp.h - Defines a simple internal CCP -#endif - #define CPM_TASK_PRIORITY 10 +#ifdef ESP_PLATFORM // OS +// The CP/M engine runs on its own task; iwm_read/iwm_write shuttle bytes to and +// from it through the cpmQueueDevice byte queues. handle_cpm() runs one full +// CP/M session (it returns when the program exits CP/M); the loop starts a +// fresh session afterwards, exactly like the old free-running cpmTask did. static void cpmTask(void *arg) { Debug_printf("cpmTask()\n"); + iwmCPM *dev = static_cast(arg); while (1) { - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8_t *)malloc(MEMSIZE); - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); - _puts(CCPHEAD); - _PatchCPM(); - _ccp(); + dev->handle_cpm(); } } - -iwmCPM::iwmCPM() -{ -#ifdef ESP_PLATFORM // OS - rxq = xQueueCreate(2048, sizeof(char)); - txq = xQueueCreate(2048, sizeof(char)); #endif -} iwm_device_status_block_t iwmCPM::create_status_reply_packet() { @@ -76,12 +49,6 @@ iwm_device_info_block_t iwmCPM::create_dib_reply_packet() return dib; } -void iwmCPM::sio_status() -{ - // Nothing to do here - return; -} - void iwmCPM::iwm_open(iwm_decoded_cmd_t cmd) { spError_t err_result = SP_ERR::NOERROR; @@ -133,9 +100,7 @@ void iwmCPM::iwm_status(iwm_decoded_cmd_t cmd) return; break; case 'S': // Status -#ifdef ESP_PLATFORM // OS - mw = uxQueueMessagesWaiting(rxq); -#endif + mw = host_available(); if (mw > 512) mw = 512; @@ -148,6 +113,8 @@ void iwmCPM::iwm_status(iwm_decoded_cmd_t cmd) case 'B': #ifdef ESP_PLATFORM // OS data_buffer[0]=(cpmTaskHandle==NULL ? 1 : 0); +#else + data_buffer[0]=1; #endif data_len = 1; Debug_printf("CPM Task Running? %d %s", data_buffer[0],(data_buffer[0]) ? "=No" : "=Yes"); @@ -163,11 +130,7 @@ void iwmCPM::iwm_status(iwm_decoded_cmd_t cmd) void iwmCPM::iwm_read(iwm_decoded_cmd_t cmd) { -#ifdef ESP_PLATFORM // OS - unsigned short mw = uxQueueMessagesWaiting(rxq); -#else - unsigned short mw; -#endif + unsigned short mw = host_available(); Debug_printf("\r\nDevice %02x READ %04x bytes from address %06lx\n", id(), cmd.char_rw.length, cmd.char_rw.address); @@ -180,16 +143,7 @@ void iwmCPM::iwm_read(iwm_decoded_cmd_t cmd) cmd.char_rw.length = mw; } - data_len = 0; - for (int i = 0; i < cmd.char_rw.length; i++) - { - char b; -#ifdef ESP_PLATFORM // OS - xQueueReceive(rxq, &b, portMAX_DELAY); -#endif - data_buffer[i] = b; - data_len++; - } + data_len = host_read(data_buffer, cmd.char_rw.length); } else // no bytes waiting, just reply back with no data { @@ -206,13 +160,7 @@ void iwmCPM::iwm_write(iwm_decoded_cmd_t cmd) { Debug_printf("\nWRITE %u bytes\n", cmd.char_rw.length); - { - // DO write -#ifdef ESP_PLATFORM // OS - for (int i = 0; i < cmd.char_rw.length; i++) - xQueueSend(txq, &data_buffer[i], portMAX_DELAY); -#endif - } + host_write(data_buffer, cmd.char_rw.length); send_reply_packet(SP_ERR::NOERROR); } diff --git a/lib/device/iwm/cpm.h b/lib/device/iwm/cpm.h index a726cc7a9..f25685618 100644 --- a/lib/device/iwm/cpm.h +++ b/lib/device/iwm/cpm.h @@ -1,17 +1,9 @@ #ifndef IWMCPM_H #define IWMCPM_H -#include "bus.h" +#include "../cpm/cpm.h" - -#define FOLDERCHAR '/' - -// Silly typedefs that runcpm uses -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -class iwmCPM : public virtualDevice +class iwmCPM : public cpmQueueDevice { private: @@ -22,7 +14,6 @@ class iwmCPM : public virtualDevice void boot(); public: - iwmCPM(); void iwm_ctrl(iwm_decoded_cmd_t cmd) override; void iwm_open(iwm_decoded_cmd_t cmd) override; @@ -34,11 +25,6 @@ class iwmCPM : public virtualDevice void shutdown() override; iwm_device_info_block_t create_dib_reply_packet() override; iwm_device_status_block_t create_status_reply_packet() override; - bool cpmActive = false; - void init_cpm(int baud); - virtual void sio_status(); - void sio_handle_cpm(); - }; #endif /* IWMCPM_H */ diff --git a/lib/device/rc2014/rc2014cpm.cpp b/lib/device/rc2014/rc2014cpm.cpp index 2354060f7..35a897d46 100644 --- a/lib/device/rc2014/rc2014cpm.cpp +++ b/lib/device/rc2014/rc2014cpm.cpp @@ -1,27 +1,8 @@ #ifdef BUILD_RC2014 -#define CCP_INTERNAL - #include "rc2014cpm.h" #include "fnSystem.h" -#include "fnWiFi.h" -#include "fujiDevice.h" -#include "fnFS.h" -#include "fnFsSD.h" - -#include "../runcpm/globals.h" -#include "../runcpm/abstraction_fujinet.h" -#include "../runcpm/ram.h" // ram.h - Implements the RAM -#include "../runcpm/console.h" // console.h - implements console. -#include "../runcpm/cpu.h" // cpu.h - Implements the emulated CPU -#include "../runcpm/disk.h" // disk.h - Defines all the disk access abstraction functions -#include "../runcpm/host.h" // host.h - Custom host-specific BDOS call -#include "../runcpm/cpm.h" // cpm.h - Defines the CPM structures and calls -#ifdef CCP_INTERNAL -# include "../runcpm/ccp.h" // ccp.h - Defines a simple internal CCP -#endif - void rc2014CPM::rc2014_status() { @@ -29,45 +10,6 @@ void rc2014CPM::rc2014_status() return; } -void rc2014CPM::rc2014_handle_cpm() -{ - _puts(CCPHEAD); - _PatchCPM(); - Status = 0; -#ifdef CCP_INTERNAL - _ccp(); -#else - if (!_sys_exists((uint8 *)CCPname)) - { - _puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n"); - break; - } - _RamLoad((uint8 *)CCPname, CCPaddr); // Loads the CCP binary file into memory - Z80reset(); // Resets the Z80 CPU - SET_LOW_REGISTER(BC, _RamRead(0x0004)); // Sets C to the current drive/user - PC = CCPaddr; // Sets CP/M application jump point - Z80run(); // Starts simulation -#endif - if (Status == 1) // This is set by a call to BIOS 0 - ends CP/M - { - cpmActive = false; - free(RAM); - } -} - -void rc2014CPM::init_cpm(int baud) -{ - // fnUartBUS.set_baudrate(baud); // RC2014 SPI bus does not use UART - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8_t *)malloc(MEMSIZE); - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); -} - void rc2014CPM::rc2014_process(uint32_t commanddata, uint8_t checksum) { cmdFrame.commanddata = commanddata; @@ -80,6 +22,9 @@ void rc2014CPM::rc2014_process(uint32_t commanddata, uint8_t checksum) fnSystem.delay(10); rc2014_send_complete(); fnSystem.delay(5000); + // No CP/M console is wired to the RC2014 SPI bus yet, so the base + // class's default endpoint makes the session exit cleanly instead of + // hanging on the first console read. init_cpm(115200); cpmActive = true; break; diff --git a/lib/device/rc2014/rc2014cpm.h b/lib/device/rc2014/rc2014cpm.h index a94e1e02e..0202e0436 100644 --- a/lib/device/rc2014/rc2014cpm.h +++ b/lib/device/rc2014/rc2014cpm.h @@ -2,23 +2,14 @@ #ifndef RC2014CPM_H #define RC2014CPM_H -#include "bus.h" +#include "../cpm/cpm.h" - -#define FOLDERCHAR '/' - -class rc2014CPM : public virtualDevice +class rc2014CPM : public cpmDevice { private: void rc2014_status(); void rc2014_process(uint32_t commanddata, uint8_t checksum) override; - -public: - bool cpmActive = false; - void init_cpm(int baud); - void rc2014_handle_cpm(); - }; #endif /* RC2014CPM_H */ diff --git a/lib/device/rs232/rs232cpm.cpp b/lib/device/rs232/rs232cpm.cpp index 1062580c9..cc2e612b4 100644 --- a/lib/device/rs232/rs232cpm.cpp +++ b/lib/device/rs232/rs232cpm.cpp @@ -1,27 +1,8 @@ #ifdef BUILD_RS232 -#define CCP_INTERNAL - #include "rs232cpm.h" #include "fnSystem.h" -#include "fnWiFi.h" -#include "rs232Fuji.h" -#include "fnFS.h" -#include "fnFsSD.h" - -#include "../runcpm/globals.h" -#include "../runcpm/abstraction_fujinet.h" -#include "../runcpm/ram.h" // ram.h - Implements the RAM -#include "../runcpm/console.h" // console.h - implements console. -#include "../runcpm/cpu.h" // cpu.h - Implements the emulated CPU -#include "../runcpm/disk.h" // disk.h - Defines all the disk access abstraction functions -#include "../runcpm/host.h" // host.h - Custom host-specific BDOS call -#include "../runcpm/cpm.h" // cpm.h - Defines the CPM structures and calls -#ifdef CCP_INTERNAL -# include "../runcpm/ccp.h" // ccp.h - Defines a simple internal CCP -#endif - void rs232CPM::rs232_status(FujiStatusReq reqType) { @@ -29,47 +10,6 @@ void rs232CPM::rs232_status(FujiStatusReq reqType) return; } -void rs232CPM::rs232_handle_cpm() -{ - _puts(CCPHEAD); - _PatchCPM(); - Status = 0; -#ifdef CCP_INTERNAL - _ccp(); -#else - if (!_sys_exists((uint8 *)CCPname)) - { - _puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n"); - break; - } - _RamLoad((uint8 *)CCPname, CCPaddr); // Loads the CCP binary file into memory - Z80reset(); // Resets the Z80 CPU - SET_LOW_REGISTER(BC, _RamRead(0x0004)); // Sets C to the current drive/user - PC = CCPaddr; // Sets CP/M application jump point - Z80run(); // Starts simulation -#endif - if (Status == 1) // This is set by a call to BIOS 0 - ends CP/M - { - cpmActive = false; - free(RAM); - } -} - -void rs232CPM::init_cpm(int baud) -{ -#ifdef OBSOLETE - SYSTEM_BUS.setBaudrate(baud); -#endif /* OBSOLETE */ - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8_t *)malloc(MEMSIZE); - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); -} - void rs232CPM::rs232_process(FujiBusPacket &packet) { switch (packet.command()) @@ -79,6 +19,9 @@ void rs232CPM::rs232_process(FujiBusPacket &packet) fnSystem.delay(10); transaction_complete(); fnSystem.delay(5000); + // No CP/M console is wired to the RS232 bus yet, so the base class's + // default endpoint makes the session exit cleanly instead of hanging + // on the first console read. init_cpm(9600); cpmActive = true; break; diff --git a/lib/device/rs232/rs232cpm.h b/lib/device/rs232/rs232cpm.h index ea1bb3be6..3c806e400 100644 --- a/lib/device/rs232/rs232cpm.h +++ b/lib/device/rs232/rs232cpm.h @@ -2,27 +2,13 @@ #ifndef RS232CPM_H #define RS232CPM_H -#include "bus.h" +#include "../cpm/cpm.h" - -#define FOLDERCHAR '/' - -// Silly typedefs that runcpm uses -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -class rs232CPM : public virtualDevice +class rs232CPM : public cpmDevice { private: void rs232_status(FujiStatusReq reqType) override; void rs232_process(FujiBusPacket &packet) override; - -public: - bool cpmActive = false; - void init_cpm(int baud); - void rs232_handle_cpm(); - }; #endif /* RS232CPM_H */ diff --git a/lib/device/sio/siocpm.cpp b/lib/device/sio/siocpm.cpp index 409f99651..4aa5e151d 100644 --- a/lib/device/sio/siocpm.cpp +++ b/lib/device/sio/siocpm.cpp @@ -1,27 +1,8 @@ #ifdef BUILD_ATARI -#define CCP_INTERNAL - #include "siocpm.h" #include "fnSystem.h" -#include "fnWiFi.h" -#include "fujiDevice.h" -#include "fnFS.h" -#include "fnFsSD.h" - -#include "../runcpm/globals.h" -#include "../runcpm/abstraction_fujinet.h" // FN_CPM_LINK defined here (one of fnUartBUS, fnSioCom) -#include "../runcpm/ram.h" // ram.h - Implements the RAM -#include "../runcpm/console.h" // console.h - implements console. -#include "../runcpm/cpu.h" // cpu.h - Implements the emulated CPU -#include "../runcpm/disk.h" // disk.h - Defines all the disk access abstraction functions -#include "../runcpm/host.h" // host.h - Custom host-specific BDOS call -#include "../runcpm/cpm.h" // cpm.h - Defines the CPM structures and calls -#ifdef CCP_INTERNAL -# include "../runcpm/ccp.h" // ccp.h - Defines a simple internal CCP -#endif - void sioCPM::sio_status() { @@ -29,43 +10,30 @@ void sioCPM::sio_status() return; } -void sioCPM::sio_handle_cpm() +// Console endpoint: bytes go straight out the Atari SIO link. CP/M is a +// 7-bit world, so both directions are masked to 7 bits, exactly as the old +// _getch/_putch did. +int sioCPM::ep_kbhit() { - _puts(CCPHEAD); - _PatchCPM(); - Status = 0; -#ifdef CCP_INTERNAL - _ccp(); -#else - if (!_sys_exists((uint8 *)CCPname)) - { - _puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n"); - break; - } - _RamLoad((uint8 *)CCPname, CCPaddr); // Loads the CCP binary file into memory - Z80reset(); // Resets the Z80 CPU - SET_LOW_REGISTER(BC, _RamRead(0x0004)); // Sets C to the current drive/user - PC = CCPaddr; // Sets CP/M application jump point - Z80run(); // Starts simulation -#endif - if (Status == 1) // This is set by a call to BIOS 0 - ends CP/M + return SYSTEM_BUS.available(); +} + +uint8_t sioCPM::ep_getch() +{ + while (SYSTEM_BUS.available() <= 0) { - cpmActive = false; - free(RAM); } + return SYSTEM_BUS.read() & 0x7f; +} + +void sioCPM::ep_putch(uint8_t ch) +{ + SYSTEM_BUS.write(ch & 0x7f); } void sioCPM::init_cpm(int baud) { SYSTEM_BUS.setBaudrate(baud); - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8_t *)malloc(MEMSIZE); - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); } void sioCPM::sio_process(uint32_t commanddata, uint8_t checksum) diff --git a/lib/device/sio/siocpm.h b/lib/device/sio/siocpm.h index 7b19b7294..79fe7233a 100644 --- a/lib/device/sio/siocpm.h +++ b/lib/device/sio/siocpm.h @@ -2,28 +2,21 @@ #ifndef SIOCPM_H #define SIOCPM_H -#include "bus.h" +#include "../cpm/cpm.h" - -#define FOLDERCHAR '/' - -// Silly typedefs that runcpm uses -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -class sioCPM : public virtualDevice +class sioCPM : public cpmDevice { private: - void sio_status() override; void sio_process(uint32_t commanddata, uint8_t checksum) override; -public: - bool cpmActive = false; - void init_cpm(int baud); - void sio_handle_cpm(); + // Console endpoint: the CP/M console is the raw Atari SIO link. + int ep_kbhit() override; + uint8_t ep_getch() override; + void ep_putch(uint8_t ch) override; +public: + void init_cpm(int baud) override; }; -#endif /* SIOCPM_H */ \ No newline at end of file +#endif /* SIOCPM_H */ diff --git a/lib/network-protocol/CPM.cpp b/lib/network-protocol/CPM.cpp index 85cc26f77..920af27aa 100644 --- a/lib/network-protocol/CPM.cpp +++ b/lib/network-protocol/CPM.cpp @@ -1,71 +1,109 @@ /** * NetworkProtocolCPM — CP/M emulator as a network protocol adapter. * - * IMPORTANT: RUNCPM_STATIC_IMPL must be defined before any RunCPM header so - * that every RunCPM symbol in this TU gets static (internal) linkage. This - * lets the network-protocol CPM adapter and any bus-device CPM adapter (sio, - * drivewire, iwm, …) coexist in the same binary without linker conflicts. + * Like every other CP/M transport, this drives the single shared RunCPM engine + * (lib/runcpm/runcpm_core.cpp) through runcpm_session_run(). The only thing it + * owns is a pair of byte queues bridging the engine console to the network + * read()/write() buffers. */ -#define RUNCPM_STATIC_IMPL -#define CCP_INTERNAL - #include "CPM.h" #include "../../include/debug.h" #include "status_error_codes.h" -/* The network-protocol abstraction defines the static queue variables - * (_cpm_rxq / _cpm_txq) and the _kbhit / _getch / _putch / _clrscr - * functions. It must come before the RunCPM headers that use them. */ -#include "../runcpm/abstraction_network_protocol.h" - -/* Standard RunCPM header chain */ -#include "../runcpm/globals.h" -#include "../runcpm/ram.h" -#include "../runcpm/console.h" -#include "../runcpm/cpu.h" -#include "../runcpm/disk.h" -#include "../runcpm/host.h" -#include "../runcpm/cpm.h" -#include "../runcpm/ccp.h" +#include "../runcpm/runcpm_session.h" + +#ifdef ESP_PLATFORM +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#else +#include +#include +#include +#endif /* ------------------------------------------------------------------------- - * CPM task / thread entry point + * Console queues * - * Runs the CCP in a loop. Status == 1 is the conventional "exit" signal - * set by the CCP itself on a warm-boot, or by stopCPM() on close(). - * The outer loop re-boots CP/M (as a real machine would on warm-boot) - * unless a clean exit was requested. + * _cpm_txq : user -> CPM stdin (write() pushes; net_getch() pops) + * _cpm_rxq : CPM stdout -> user (net_putch() pushes; read() pops) * ------------------------------------------------------------------------- */ -static void _cpm_run(void) +#ifdef ESP_PLATFORM +static QueueHandle_t _cpm_rxq = nullptr; +static QueueHandle_t _cpm_txq = nullptr; +#else +static std::queue _cpm_rxq; +static std::queue _cpm_txq; +static std::mutex _cpm_rxmtx; +static std::mutex _cpm_txmtx; +static std::condition_variable _cpm_txcv; +#endif + +/* Set by _cpm_run() when the session ends; polled by status() to drive EOF. */ +static volatile bool _cpm_session_ended = false; + +/* ------------------------------------------------------------------------- + * Console endpoint — bridges the engine's console callbacks to the queues. + * ------------------------------------------------------------------------- */ +static int net_kbhit(void) +{ +#ifdef ESP_PLATFORM + if (_cpm_txq == nullptr) return 0; + return (int)uxQueueMessagesWaiting(_cpm_txq); +#else + std::lock_guard lk(_cpm_txmtx); + return (int)_cpm_txq.size(); +#endif +} + +static uint8_t net_getch(void) +{ + uint8_t c = 0; +#ifdef ESP_PLATFORM + if (_cpm_txq != nullptr) + xQueueReceive(_cpm_txq, &c, portMAX_DELAY); +#else + std::unique_lock lk(_cpm_txmtx); + _cpm_txcv.wait(lk, [] { return !_cpm_txq.empty(); }); + c = _cpm_txq.front(); + _cpm_txq.pop(); +#endif + return c; +} + +static void net_putch(uint8_t ch) { - while (true) +#ifdef ESP_PLATFORM + if (_cpm_rxq != nullptr) + xQueueSend(_cpm_rxq, &ch, portMAX_DELAY); +#else { - Status = Debug = 0; - Break = Step = Watch = -1; - - RAM = (uint8_t *)malloc(MEMSIZE); - if (!RAM) - break; - - memset(RAM, 0, MEMSIZE); - memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); - - _puts(CCPHEAD); - _PatchCPM(); - _ccp(); - - free(RAM); - RAM = nullptr; - - /* Status == 1: clean exit requested — stop looping */ - if (Status == 1) - break; - /* Status == 2: warm-boot — re-enter the CCP loop */ + std::lock_guard lk(_cpm_rxmtx); + _cpm_rxq.push(ch); } +#endif +} + +static void net_clrscr(void) +{ + /* VT100 cursor-home + clear-screen */ + net_putch(0x1B); net_putch('['); net_putch('1'); net_putch(';'); + net_putch('1'); net_putch('H'); net_putch(0x1B); net_putch('['); + net_putch('2'); net_putch('J'); +} + +/* ------------------------------------------------------------------------- + * CPM task / thread entry point + * ------------------------------------------------------------------------- */ +static void _cpm_run(void) +{ + runcpm_console_ops ops; + ops.kbhit = net_kbhit; + ops.getch = net_getch; + ops.putch = net_putch; + ops.clrscr = net_clrscr; + + runcpm_session_run(&ops); /* Notify the protocol object that the session ended from the CP/M side. */ _cpm_session_ended = true; @@ -147,16 +185,16 @@ void NetworkProtocolCPM::stopCPM() if (!running) return; running = false; - /* Signal the CCP to stop re-booting */ - Status = 1; + /* Signal the engine to stop re-booting and fall out of the session loop. */ + runcpm_session_request_exit(); - /* Unblock any _getch() call waiting for user input */ + /* Unblock any net_getch() call waiting for user input */ uint8_t sentinel = 0x03; // CTRL-C #ifdef ESP_PLATFORM if (_cpm_txq != nullptr) xQueueSend(_cpm_txq, &sentinel, pdMS_TO_TICKS(200)); - /* Give the FreeRTOS task time to notice Status==1 and self-delete */ + /* Give the FreeRTOS task time to notice the exit request and self-delete */ vTaskDelay(pdMS_TO_TICKS(300)); if (_cpm_rxq != nullptr) { vQueueDelete(_cpm_rxq); _cpm_rxq = nullptr; } diff --git a/lib/runcpm/abstraction_fujinet.h b/lib/runcpm/abstraction_fujinet.h index fa23fb617..55267fdb0 100644 --- a/lib/runcpm/abstraction_fujinet.h +++ b/lib/runcpm/abstraction_fujinet.h @@ -13,19 +13,19 @@ #include "../../include/debug.h" -#include "fnSystem.h" -#include "fnWiFi.h" #include "fnFsSD.h" -#ifdef ESP_PLATFORM -#include "IOChannel.h" -#endif -#include "fnTcpServer.h" -#include "fnTcpClient.h" -#include "fujiDevice.h" +#include "runcpm_session.h" + +// The active CP/M console endpoint, supplied by whichever transport opened the +// current session (the bus cpm device, or the N:CPM:// adapter). The engine +// only ever talks to the outside world through these four callbacks, which is +// what lets a single engine build drive every transport. +extern runcpm_console_ops g_runcpm_console; -// Why is CP/M writing directly to the SYSTEM_BUS? -#define FN_CPM_LINK SYSTEM_BUS +#ifndef FOLDERCHAR +#define FOLDERCHAR '/' +#endif #define HostOS 0x07 // FUJINET @@ -52,11 +52,6 @@ int dirPos; char full_filename[128]; -fnTcpClient client; -fnTcpServer *server; -bool teeMode = false; -unsigned short portActive = 0; - char *full_path(char *fn) { memset(full_filename, 0, sizeof(full_filename)); @@ -508,203 +503,41 @@ uint8_t _sys_makedisk(uint8_t drive) /* Console abstraction functions */ /*===============================================================================*/ +// +// The console is the only part of the abstraction that differs between +// transports, so it is the only part that is delegated. Each callback is +// supplied by the transport that owns the current session (the active +// cpmDevice endpoint, or the N:CPM:// adapter). Any per-transport quirks - +// 7-bit masking on the SIO link, queue plumbing on IWM/DriveWire, VT100 clear +// on N: - live in those endpoints, not here. -#ifdef BUILD_ATARI -#define BYPASS_BUS 1 -#endif - - -#ifdef BYPASS_BUS -#define _kbhit() SYSTEM_BUS.available() -#define _cread() SYSTEM_BUS.read() -#define _cwrite(ch) SYSTEM_BUS.write(ch) -#else -#define _kbhit() 0 -#define _cread() 0 -#define _cwrite(ch) -#endif // BYPASS_BUS - -uint8_t _getch(void) +int _kbhit(void) { - if (teeMode == true) - { - while (_kbhit() > 0) - { - if (client.available()) - { - uint8_t ch; - client.read(&ch, 1); - return ch & 0x7F; - } - } - return _cread() & 0x7F; - } - else - { - while (_kbhit() <= 0) - { - } - return _cread() & 0x7f; - } + return g_runcpm_console.kbhit ? g_runcpm_console.kbhit() : 0; } -uint8_t _getche(void) +uint8_t _getch(void) { - uint8_t ch = _getch() & 0x7f; - _cwrite(ch); - if (teeMode == true) - client.write(ch); - return ch; + return g_runcpm_console.getch ? g_runcpm_console.getch() : 0x03; // ^C if none } void _putch(uint8_t ch) { - _cwrite(ch & 0x7f); - if (teeMode == true) - client.write(ch); -} - -void _clrscr(void) -{ -} - -uint8_t bdos_networkConfig(uint16_t addr) -{ - // Response to FUJICMD_GET_ADAPTERCONFIG - struct - { - char ssid[32]; - char hostname[64]; - unsigned char localIP[4]; - unsigned char gateway[4]; - unsigned char netmask[4]; - unsigned char dnsIP[4]; - unsigned char macAddress[6]; - unsigned char bssid[6]; - char fn_version[15]; - } cfg; - - memset(&cfg, 0, sizeof(cfg)); - - strlcpy(cfg.fn_version, fnSystem.get_fujinet_version(true), sizeof(cfg.fn_version)); - - if (!fnWiFi.connected()) - { - strlcpy(cfg.ssid, "NOT CONNECTED", sizeof(cfg.ssid)); - } - else - { - strlcpy(cfg.hostname, fnSystem.Net.get_hostname().c_str(), sizeof(cfg.hostname)); - strlcpy(cfg.ssid, fnWiFi.get_current_ssid().c_str(), sizeof(cfg.ssid)); - fnWiFi.get_current_bssid(cfg.bssid); - fnSystem.Net.get_ip4_info(cfg.localIP, cfg.netmask, cfg.gateway); - fnSystem.Net.get_ip4_dns_info(cfg.dnsIP); - } - - fnWiFi.get_mac(cfg.macAddress); - - // Transfer to Z80 RAM. - memset(&RAM[addr], 0, sizeof(cfg)); - memcpy(&RAM[addr], &cfg, sizeof(cfg)); - - return 0; -} - -uint8_t bdos_readHostSlots(uint16_t addr) -{ - char hostSlots[8][32]; - memset(hostSlots, 0, sizeof(hostSlots)); - - for (int i = 0; i < 8; i++) - strlcpy(hostSlots[i], theFuji->get_host(i)->get_hostname(), 32); - - memset(&RAM[addr], 0, sizeof(hostSlots)); - memcpy(&RAM[addr], &hostSlots, sizeof(hostSlots)); - return 0; -} - -uint8_t bdos_readDeviceSlots(uint16_t addr) -{ - struct disk_slot - { - uint8_t hostSlot; - uint8_t mode; - char filename[MAX_DISPLAY_FILENAME_LEN]; - }; - disk_slot diskSlots[MAX_DISK_DEVICES]; - - // Load the data from our current device array - for (int i = 0; i < MAX_DISK_DEVICES; i++) - { - diskSlots[i].mode = theFuji->get_disk(i)->access_mode; - diskSlots[i].hostSlot = theFuji->get_disk(i)->host_slot; - strlcpy(diskSlots[i].filename, theFuji->get_disk(i)->filename, MAX_DISPLAY_FILENAME_LEN); - } - - // Transfer to Z80 RAM. - memset(&RAM[addr], 0, sizeof(diskSlots)); - memcpy(&RAM[addr], &diskSlots, sizeof(diskSlots)); - - return 0; -} - -uint8_t bios_tcpListen(uint16_t port) -{ - Debug_printf("Do we get here?\r\n"); - - if (client.connected()) - client.stop(); - - if (server != nullptr && port != portActive) - { - server->stop(); - delete server; - } - - server = new fnTcpServer(port,1); - - int res = server->begin(port); - if (res == 0) - { - Debug_printf("bios_tcpListen - failed to open port %u\nError (%d): %s\r\n", port, errno, strerror(errno)); - return true; - } - else - { - Debug_printf("bios_tcpListen - Now listening on port %u\r\n", port); - return false; - } -} - -uint8_t bios_tcpAvailable(void) -{ - if (server == nullptr) - return 0; - - return server->hasClient(); + if (g_runcpm_console.putch) + g_runcpm_console.putch(ch); } -uint8_t bios_tcpTeeAccept(void) +uint8_t _getche(void) { - if (server == nullptr) - return false; - - if (server->hasClient()) - client = server->accept(); - - teeMode = true; - - return client.connected(); + uint8_t ch = _getch(); + _putch(ch); + return ch; } -uint8_t bios_tcpDrop(void) +void _clrscr(void) { - if (server == nullptr) - return false; - - client.stop(); - - return true; + if (g_runcpm_console.clrscr) + g_runcpm_console.clrscr(); } #endif /* ABSTRACTION_FUJINET_H */ diff --git a/lib/runcpm/abstraction_fujinet_apple2.h b/lib/runcpm/abstraction_fujinet_apple2.h deleted file mode 100644 index 84193be06..000000000 --- a/lib/runcpm/abstraction_fujinet_apple2.h +++ /dev/null @@ -1,705 +0,0 @@ -/** - * Abstraction functions for #FujiNet - */ - -#ifndef ABSTRACTION_FUJINET_APPLE2_H -#define ABSTRACTION_FUJINET_APPLE2_H - -#include -#include -#include -#include "compat_string.h" - -#include "globals.h" - -#include "../../include/debug.h" - -#include "fnSystem.h" -#include "fnWiFi.h" -#include "fnFsSD.h" -#include "fnTcpServer.h" -#include "fnTcpClient.h" - -#include "iwm/iwmFuji.h" - -#define HostOS 0x07 // FUJINET - -#ifdef BUILD_COCO -// This file says "apple2" right in the name so CoCo should follow apple2 convention, right? -#include "drivewire/drivewireFuji.h" -#define MAX_A2DISK_DEVICES MAX_DWDISK_DEVICES -#endif - -// using namespace std; - -#ifdef ESP_PLATFORM // OS -QueueHandle_t rxq; -QueueHandle_t txq; -#endif - -typedef struct -{ - uint8_t dr; - uint8_t fn[8]; - uint8_t tp[3]; - uint8_t ex, s1, s2, rc; - uint8_t al[16]; - uint8_t cr, r0, r1, r2; -} CPM_FCB; - -typedef struct -{ - uint8_t dr; - uint8_t fn[8]; - uint8_t tp[3]; - uint8_t ex, s1, s2, rc; - uint8_t al[16]; -} CPM_DIRENTRY; - -int dirPos; - -char full_filename[128]; - -fnTcpClient client; -fnTcpServer *server; -bool teeMode = false; -unsigned short portActive = 0; - -char *full_path(char *fn) -{ - memset(full_filename, 0, sizeof(full_filename)); - strcpy(full_filename, "/CPM/"); - strcat(full_filename, fn); - return full_filename; -} - -/* Memory abstraction functions */ -/*===============================================================================*/ -bool _RamLoad(char *fn, uint16_t address) -{ - FILE *f = fnSDFAT.file_open(full_path(fn), "r"); - bool result = false; - uint8_t b; - - if (f) - { - while (!feof(f)) - { - if (fread(&b, sizeof(uint8_t), 1, f) == 1) - { - _RamWrite(address++, b); - result = true; - } - else - result = false; - } - fclose(f); - } - Debug_printf("CCP last address: %04x\r\n",address); - return (result); -} - -// -// Hardware functions, new in 5.x -// -void _HardwareOut(const uint32 Port, const uint32 Value) { - -} - -uint32 _HardwareIn(const uint32 Port) { - return 0; -} - -/* filesystem (disk) abstraction fuctions */ -/*===============================================================================*/ -FILE *rootdir; -FILE *userdir; - -bool _sys_exists(uint8* filename) -{ - return fnSDFAT.exists(full_path((char *)filename)); -} - -int _sys_fputc(uint8_t ch, FILE *f) -{ - return fputc(ch, f); -} - -void _sys_fflush(FILE *f) -{ - fflush(f); -} - -void _sys_fclose(FILE *f) -{ - fclose(f); -} - -int _sys_select(uint8_t *disk) -{ - return fnSDFAT.exists(full_path((char *)disk)); -} - -long _sys_filesize(uint8_t *fn) -{ - unsigned long fs = -1; - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "r"); - - if (fp) - { - fseek(fp, 0L, SEEK_END); - fs = ftell(fp); - } - - fclose(fp); - return fs; -} - -int _sys_openfile(uint8_t *fn) -{ - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (fp) - { - fclose(fp); - return 1; - } - else - return 0; -} - -int _sys_makefile(uint8_t *fn) -{ - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "w"); - if (fp) - { - fclose(fp); - return true; - } - else - return false; -} - -int _sys_deletefile(uint8_t *fn) -{ - return fnSDFAT.remove(full_path((char *)fn)); -} - -int _sys_renamefile(uint8_t *fn, uint8_t *newname) -{ - std::string from, to; - - from = std::string(full_path((char *)fn)); - to = std::string(full_path((char *)newname)); - - return fnSDFAT.rename(from.c_str(), to.c_str()); -} - -void _sys_logbuffer(uint8_t *buffer) -{ - // not implemented at present. -} - -bool _sys_extendfile(char *fn, unsigned long fpos) -{ - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "a"); - - if (!fp) - return false; - - long origSize = fnSDFAT.filesize(full_path(fn)); - - // This was patterned after the arduino abstraction, and I do not like how this works. - - if (fpos > origSize) - { - for (long i = 0; i < (origSize - fpos); ++i) - { - if (fwrite("\0", sizeof(uint8_t), 1, fp) != 1) - { - fclose(fp); - return false; - } - } - } - fclose(fp); - return true; -} - -uint8_t _sys_readseq(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f; - uint8_t bytesread; - uint8_t dmabuf[BlkSZ]; - int seekErr; - - f = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (!f) - { - result = 0x10; - return result; - } - - seekErr = fseek(f, fpos, SEEK_SET); - if (f) - { - if (fpos > 0 && seekErr != 0) - { - // EOF - result = 0x01; - } - else - { - // set DMA buffer to EOF - memset(dmabuf, 0x1a, BlkSZ); - bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); - if (bytesread) - memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); - result = bytesread ? 0x00 : 0x01; - } - } - else - { - result = 0x10; - } - fclose(f); - return (result); -} - -uint8_t _sys_writeseq(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f; - - if (_sys_extendfile((char *)fn, fpos)) - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - else - return result; - - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - if (fwrite(_RamSysAddr(dmaAddr), BlkSZ, sizeof(uint8_t), f)) - result = 0x00; - } - else - { - result = 0x01; - } - } - else - { - result = 0x10; - } - fclose(f); - return (result); -} - -uint8_t _sys_readrand(uint8_t *fn, long fpos) -{ - uint8 result = 0xff; - FILE *f; - uint8 bytesread; - uint8 dmabuf[BlkSZ]; - long extSize; - - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - memset(dmabuf, 0x1A, BlkSZ); - bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); - if (bytesread) - memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); - result = bytesread ? 0x00 : 0x01; - } - else - { - if (fpos >= 65536L * BlkSZ) - { - result = 0x06; // seek past 8MB (largest file size in CP/M) - } - else - { - extSize = _sys_filesize((uint8_t *)full_path((char *)fn)); - - // round file size up to next full logical extent - extSize = ExtSZ * ((extSize / ExtSZ) + ((extSize % ExtSZ) ? 1 : 0)); - if (fpos < extSize) - result = 0x01; // reading unwritten data - else - result = 0x04; // seek to unwritten extent - } - } - } - else - { - result = 0x10; - } - fclose(f); - return (result); -} - -uint8_t _sys_writerand(uint8_t *fn, long fpos) -{ - uint8 result = 0xff; - FILE *f; - - if (_sys_extendfile((char *)fn, fpos)) - { - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - } - else - return result; - - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - if (fwrite(_RamSysAddr(dmaAddr), BlkSZ, sizeof(uint8_t), f)) - result = 0x00; - } - else - { - result = 0x06; - } - } - else - { - result = 0x10; - } - fclose(f); - return (result); -} - -uint8_t findNextDirName[17]; -uint16_t fileRecords = 0; -uint16_t fileExtents = 0; -uint16_t fileExtentsUsed = 0; -uint16_t firstFreeAllocBlock; - -uint8_t _findnext(uint8_t isdir) -{ - uint8 result = 0xff; - bool isfile; - uint32 bytes; - fsdir_entry *entry; - - if (allExtents && fileRecords) - { - _mockupDirEntry(); - result = 0; - } - else - { - while ((entry = fnSDFAT.dir_read())) - { - strcpy((char *)findNextDirName, entry->filename); // careful watch for string overflow! - isfile = !entry->isDir; - bytes = entry->size; - if (!isfile) - continue; - _HostnameToFCBname(findNextDirName, fcbname); - if (match(fcbname, pattern)) - { - if (isdir) - { - // account for host files that aren't multiples of the block size - // by rounding their bytes up to the next multiple of blocks - if (bytes & (BlkSZ - 1)) - { - bytes = (bytes & ~(BlkSZ - 1)) + BlkSZ; - } - fileRecords = bytes / BlkSZ; - fileExtents = fileRecords / BlkEX + ((fileRecords & (BlkEX - 1)) ? 1 : 0); - fileExtentsUsed = 0; - firstFreeAllocBlock = firstBlockAfterDir; - _mockupDirEntry(); - } - else - { - fileRecords = 0; - fileExtents = 0; - fileExtentsUsed = 0; - firstFreeAllocBlock = firstBlockAfterDir; - } - _RamWrite(tmpFCB, filename[0] - '@'); - _HostnameToFCB(tmpFCB, findNextDirName); - result = 0x00; - break; - } - } - } - return (result); -} - -uint8_t _findfirst(uint8_t isdir) -{ - uint8 path[4] = {'?', FOLDERCHAR, '?', 0}; - path[0] = filename[0]; - path[2] = filename[2]; - fnSDFAT.dir_close(); - fnSDFAT.dir_open(full_path((char *)path), "*", 0); - _HostnameToFCBname(filename, pattern); - fileRecords = 0; - fileExtents = 0; - fileExtentsUsed = 0; - return (_findnext(isdir)); -} - -uint8_t _findnextallusers(uint8_t isdir) -{ - return _findnext(isdir); -} - -uint8_t _findfirstallusers(uint8_t isdir) -{ - dirPos = 0; - strcpy((char *)pattern, "???????????"); - fileRecords = 0; - fileExtents = 0; - fileExtentsUsed = 0; - return (_findnextallusers(isdir)); -} - -uint8_t _Truncate(char *fn, uint8_t rc) -{ - // Implement some other way. - return 0; -} - -void _MakeUserDir() -{ - uint8 dFolder = cDrive + 'A'; - uint8 uFolder = toupper(tohex(userCode)); - - uint8 path[4] = {dFolder, FOLDERCHAR, uFolder, 0}; - - if (fnSDFAT.exists(full_path((char *)path))) - { - return; - } - - fnSDFAT.create_path(full_path((char *)path)); -} - -uint8_t _sys_makedisk(uint8_t drive) -{ - uint8 result = 0; - if (drive < 1 || drive > 16) - { - result = 0xff; - } - else - { - uint8 dFolder = drive + '@'; - uint8 disk[2] = {dFolder, 0}; - - if (fnSDFAT.exists(full_path((char *)disk))) - return 0; - - if (!fnSDFAT.create_path(full_path((char *)disk))) - { - result = 0xfe; - } - else - { - uint8 path[4] = {dFolder, FOLDERCHAR, '0', 0}; - fnSDFAT.create_path(full_path((char *)path)); - } - } - return (result); -} - -/* Console abstraction functions */ -/*===============================================================================*/ - -int _kbhit(void) -{ -#ifdef ESP_PLATFORM // OS - return uxQueueMessagesWaiting(txq); -#else - return 0; -#endif -} - -uint8_t _getch(void) -{ - uint8_t c; -#ifdef ESP_PLATFORM // OS - xQueueReceive(txq,&c,portMAX_DELAY); -#endif - return c; -} - -uint8_t _getche(void) -{ - uint8_t c = _getch(); -#ifdef ESP_PLATFORM // OS - xQueueSend(rxq,&c,portMAX_DELAY); -#endif - return c; -} - -void _putch(uint8_t ch) -{ -#ifdef ESP_PLATFORM // OS - xQueueSend(rxq,&ch,portMAX_DELAY); -#endif -} - -void _clrscr(void) -{ - _putch(0x1B); - _putch('['); - _putch('1'); - _putch(';'); - _putch('1'); - _putch('H'); - _putch(0x1B); - _putch('['); - _putch('2'); - _putch('J'); -} - -uint8_t bdos_networkConfig(uint16_t addr) -{ - // Response to FUJICMD_GET_ADAPTERCONFIG - struct - { - char ssid[32]; - char hostname[64]; - unsigned char localIP[4]; - unsigned char gateway[4]; - unsigned char netmask[4]; - unsigned char dnsIP[4]; - unsigned char macAddress[6]; - unsigned char bssid[6]; - char fn_version[15]; - } cfg; - - memset(&cfg, 0, sizeof(cfg)); - - strlcpy(cfg.fn_version, fnSystem.get_fujinet_version(true), sizeof(cfg.fn_version)); - - if (!fnWiFi.connected()) - { - strlcpy(cfg.ssid, "NOT CONNECTED", sizeof(cfg.ssid)); - } - else - { - strlcpy(cfg.hostname, fnSystem.Net.get_hostname().c_str(), sizeof(cfg.hostname)); - strlcpy(cfg.ssid, fnWiFi.get_current_ssid().c_str(), sizeof(cfg.ssid)); - fnWiFi.get_current_bssid(cfg.bssid); - fnSystem.Net.get_ip4_info(cfg.localIP, cfg.netmask, cfg.gateway); - fnSystem.Net.get_ip4_dns_info(cfg.dnsIP); - } - - fnWiFi.get_mac(cfg.macAddress); - - // Transfer to Z80 RAM. - memset(&RAM[addr], 0, sizeof(cfg)); - memcpy(&RAM[addr], &cfg, sizeof(cfg)); - - return 0; -} - -uint8_t bdos_readHostSlots(uint16_t addr) -{ - char hostSlots[8][32]; - memset(hostSlots, 0, sizeof(hostSlots)); - - for (int i = 0; i < 8; i++) - strlcpy(hostSlots[i], theFuji->get_host(i)->get_hostname(), 32); - - memset(&RAM[addr], 0, sizeof(hostSlots)); - memcpy(&RAM[addr], &hostSlots, sizeof(hostSlots)); - return 0; -} - -uint8_t bdos_readDeviceSlots(uint16_t addr) -{ - struct disk_slot - { - uint8_t hostSlot; - uint8_t mode; - char filename[MAX_DISPLAY_FILENAME_LEN]; - }; - disk_slot diskSlots[MAX_A2DISK_DEVICES]; - - // Load the data from our current device array - for (int i = 0; i < MAX_A2DISK_DEVICES; i++) - { - diskSlots[i].mode = theFuji->get_disk(i)->access_mode; - diskSlots[i].hostSlot = theFuji->get_disk(i)->host_slot; - strlcpy(diskSlots[i].filename, theFuji->get_disk(i)->filename, MAX_DISPLAY_FILENAME_LEN); - } - - // Transfer to Z80 RAM. - memset(&RAM[addr], 0, sizeof(diskSlots)); - memcpy(&RAM[addr], &diskSlots, sizeof(diskSlots)); - - return 0; -} - -uint8_t bios_tcpListen(uint16_t port) -{ - Debug_printf("Do we get here?\r\n"); - - if (client.connected()) - client.stop(); - - if (server != nullptr && port != portActive) - { - server->stop(); - delete server; - } - - server = new fnTcpServer(port,1); - int res = server->begin(port); - if (res == 0) - { - Debug_printf("bios_tcpListen - failed to open port %u\nError (%d): %s\r\n", port, errno, strerror(errno)); - return true; - } - else - { - Debug_printf("bios_tcpListen - Now listening on port %u\r\n", port); - return false; - } -} - -uint8_t bios_tcpAvailable(void) -{ - if (server == nullptr) - return 0; - - return server->hasClient(); -} - -uint8_t bios_tcpTeeAccept(void) -{ - if (server == nullptr) - return false; - - if (server->hasClient()) - client = server->accept(); - - teeMode = true; - - return client.connected(); -} - -uint8_t bios_tcpDrop(void) -{ - if (server == nullptr) - return false; - - client.stop(); - - return true; -} - -#endif /* ABSTRACTION_FUJINET_H */ diff --git a/lib/runcpm/abstraction_network_protocol.h b/lib/runcpm/abstraction_network_protocol.h deleted file mode 100644 index 05a6c878a..000000000 --- a/lib/runcpm/abstraction_network_protocol.h +++ /dev/null @@ -1,476 +0,0 @@ -/** - * RunCPM I/O abstraction for NetworkProtocol CPM adapter. - * - * Console I/O is routed through per-TU static queues (_cpm_rxq / _cpm_txq). - * Filesystem I/O uses fnSDFAT, identical to the other FujiNet abstractions. - * - * Include this AFTER #define RUNCPM_STATIC_IMPL, BEFORE other RunCPM headers. - * All symbols are static so this TU coexists with bus-device CPM TUs. - */ - -#ifndef ABSTRACTION_NETWORK_PROTOCOL_H -#define ABSTRACTION_NETWORK_PROTOCOL_H - -#include -#include -#include - -#include "globals.h" -#include "../../include/debug.h" -#include "fnFsSD.h" - -#define FOLDERCHAR '/' -#define HostOS 0x07 // FUJINET - -typedef struct -{ - uint8_t dr; - uint8_t fn[8]; - uint8_t tp[3]; - uint8_t ex, s1, s2, rc; - uint8_t al[16]; - uint8_t cr, r0, r1, r2; -} CPM_FCB; - -typedef struct -{ - uint8_t dr; - uint8_t fn[8]; - uint8_t tp[3]; - uint8_t ex, s1, s2, rc; - uint8_t al[16]; -} CPM_DIRENTRY; - -/* ------------------------------------------------------------------------- - * Queue storage — TU-local due to RUNCPM_STATIC_IMPL / static keyword. - * - * _cpm_txq : user → CPM stdin (write() pushes; _getch() pops) - * _cpm_rxq : CPM stdout → user (_putch() pushes; read() pops) - * ------------------------------------------------------------------------- */ -#ifdef ESP_PLATFORM -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -static QueueHandle_t _cpm_rxq = nullptr; -static QueueHandle_t _cpm_txq = nullptr; -#else -#include -#include -#include -static std::queue _cpm_rxq; -static std::queue _cpm_txq; -static std::mutex _cpm_rxmtx; -static std::mutex _cpm_txmtx; -static std::condition_variable _cpm_txcv; -#endif - -/* Set by _cpm_run() when the CCP exits of its own accord (e.g. user typed EXIT). - * Polled by NetworkProtocolCPM::status() to drive EOF back to the host. */ -static volatile bool _cpm_session_ended = false; - -/* ------------------------------------------------------------------------- - * Path helpers - * ------------------------------------------------------------------------- */ -static char _cpm_full_filename[128]; - -static char *full_path(char *fn) -{ - memset(_cpm_full_filename, 0, sizeof(_cpm_full_filename)); - strcpy(_cpm_full_filename, "/CPM/"); - strcat(_cpm_full_filename, fn); - return _cpm_full_filename; -} - -/* ------------------------------------------------------------------------- - * Hardware stubs (required by RunCPM cpu.h) - * ------------------------------------------------------------------------- */ -static void _HardwareOut(const uint32 Port, const uint32 Value) { (void)Port; (void)Value; } -static uint32 _HardwareIn(const uint32 Port) { (void)Port; return 0; } - -/* ------------------------------------------------------------------------- - * Memory abstraction - * ------------------------------------------------------------------------- */ -static bool _RamLoad(char *fn, uint16_t address) -{ - FILE *f = fnSDFAT.file_open(full_path(fn), "r"); - bool result = false; - uint8_t b; - - if (f) - { - while (!feof(f)) - { - if (fread(&b, sizeof(uint8_t), 1, f) == 1) - { - _RamWrite(address++, b); - result = true; - } - else - result = false; - } - fclose(f); - } - Debug_printf("CCP last address: %04x\r\n", address); - return result; -} - -/* ------------------------------------------------------------------------- - * Filesystem abstraction (mirrors abstraction_fujinet_apple2.h) - * ------------------------------------------------------------------------- */ -static FILE *rootdir; -static FILE *userdir; - -static bool _sys_exists(uint8 *filename) -{ - return fnSDFAT.exists(full_path((char *)filename)); -} - -static int _sys_fputc(uint8_t ch, FILE *f) { return fputc(ch, f); } -static void _sys_fflush(FILE *f) { fflush(f); } -static void _sys_fclose(FILE *f) { fclose(f); } - -static int _sys_select(uint8_t *disk) -{ - return fnSDFAT.exists(full_path((char *)disk)); -} - -static long _sys_filesize(uint8_t *fn) -{ - long fs = -1; - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (fp) - { - fseek(fp, 0L, SEEK_END); - fs = ftell(fp); - fclose(fp); - } - return fs; -} - -static int _sys_openfile(uint8_t *fn) -{ - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (fp) { fclose(fp); return 1; } - return 0; -} - -static int _sys_makefile(uint8_t *fn) -{ - FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "w"); - if (fp) { fclose(fp); return 1; } - return 0; -} - -static int _sys_deletefile(uint8_t *fn) -{ - return fnSDFAT.remove(full_path((char *)fn)); -} - -static int _sys_renamefile(uint8_t *fn, uint8_t *newname) -{ - std::string from(full_path((char *)fn)); - std::string to(full_path((char *)newname)); - return fnSDFAT.rename(from.c_str(), to.c_str()); -} - -static void _sys_logbuffer(uint8_t *buffer) { (void)buffer; } - -static bool _sys_extendfile(char *fn, unsigned long fpos) -{ - FILE *fp = fnSDFAT.file_open(full_path(fn), "a"); - if (!fp) return false; - - long origSize = fnSDFAT.filesize(full_path(fn)); - if (fpos > (unsigned long)origSize) - { - for (long i = 0; i < (long)(fpos - origSize); ++i) - { - if (fwrite("\0", sizeof(uint8_t), 1, fp) != 1) - { - fclose(fp); - return false; - } - } - } - fclose(fp); - return true; -} - -static uint8_t _sys_readseq(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (!f) return 0x10; - - int seekErr = fseek(f, fpos, SEEK_SET); - if (fpos > 0 && seekErr != 0) - { - result = 0x01; - } - else - { - uint8_t dmabuf[BlkSZ]; - memset(dmabuf, 0x1a, BlkSZ); - uint8_t bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); - if (bytesread) - memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); - result = bytesread ? 0x00 : 0x01; - } - fclose(f); - return result; -} - -static uint8_t _sys_writeseq(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f; - - if (_sys_extendfile((char *)fn, fpos)) - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - else - return result; - - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - if (fwrite(_RamSysAddr(dmaAddr), BlkSZ, sizeof(uint8_t), f)) - result = 0x00; - } - else - result = 0x01; - fclose(f); - } - else - result = 0x10; - - return result; -} - -static uint8_t _sys_readrand(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - uint8_t dmabuf[BlkSZ]; - memset(dmabuf, 0x1A, BlkSZ); - uint8_t bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); - if (bytesread) - memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); - result = bytesread ? 0x00 : 0x01; - } - else - { - if (fpos >= 65536L * BlkSZ) - { - result = 0x06; - } - else - { - long extSize = _sys_filesize((uint8_t *)full_path((char *)fn)); - extSize = ExtSZ * ((extSize / ExtSZ) + ((extSize % ExtSZ) ? 1 : 0)); - result = (fpos < extSize) ? 0x01 : 0x04; - } - } - fclose(f); - } - else - result = 0x10; - - return result; -} - -static uint8_t _sys_writerand(uint8_t *fn, long fpos) -{ - uint8_t result = 0xff; - FILE *f; - - if (_sys_extendfile((char *)fn, fpos)) - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); - else - return result; - - if (f) - { - if (fseek(f, fpos, SEEK_SET) == 0) - { - if (fwrite(_RamSysAddr(dmaAddr), BlkSZ, sizeof(uint8_t), f)) - result = 0x00; - } - else - result = 0x06; - fclose(f); - } - else - result = 0x10; - - return result; -} - -static uint8_t findNextDirName[17]; -static uint16_t fileRecords = 0; -static uint16_t fileExtents = 0; -static uint16_t fileExtentsUsed = 0; -static uint16_t firstFreeAllocBlock; - -static uint8_t _findnext(uint8_t isdir) -{ - uint8_t result = 0xff; - fsdir_entry *entry; - - if (allExtents && fileRecords) - { - _mockupDirEntry(); - return 0; - } - - while ((entry = fnSDFAT.dir_read())) - { - strcpy((char *)findNextDirName, entry->filename); - if (entry->isDir) continue; - uint32_t bytes = entry->size; - _HostnameToFCBname(findNextDirName, fcbname); - if (match(fcbname, pattern)) - { - if (isdir) - { - if (bytes & (BlkSZ - 1)) - bytes = (bytes & ~(BlkSZ - 1)) + BlkSZ; - fileRecords = bytes / BlkSZ; - fileExtents = fileRecords / BlkEX + ((fileRecords & (BlkEX - 1)) ? 1 : 0); - fileExtentsUsed = 0; - firstFreeAllocBlock = firstBlockAfterDir; - _mockupDirEntry(); - } - else - { - fileRecords = fileExtents = fileExtentsUsed = 0; - firstFreeAllocBlock = firstBlockAfterDir; - } - _RamWrite(tmpFCB, filename[0] - '@'); - _HostnameToFCB(tmpFCB, findNextDirName); - result = 0x00; - break; - } - } - return result; -} - -static uint8_t _findfirst(uint8_t isdir) -{ - uint8_t path[4] = {'?', FOLDERCHAR, '?', 0}; - path[0] = filename[0]; - path[2] = filename[2]; - fnSDFAT.dir_close(); - fnSDFAT.dir_open(full_path((char *)path), "*", 0); - _HostnameToFCBname(filename, pattern); - fileRecords = fileExtents = fileExtentsUsed = 0; - return _findnext(isdir); -} - -static uint8_t _findnextallusers(uint8_t isdir) { return _findnext(isdir); } - -static uint8_t _findfirstallusers(uint8_t isdir) -{ - strcpy((char *)pattern, "???????????"); - fileRecords = fileExtents = fileExtentsUsed = 0; - return _findnextallusers(isdir); -} - -static uint8_t _Truncate(char *fn, uint8_t rc) { (void)fn; (void)rc; return 0; } - -static void _MakeUserDir() -{ - uint8_t dFolder = cDrive + 'A'; - uint8_t uFolder = toupper(tohex(userCode)); - uint8_t path[4] = {dFolder, FOLDERCHAR, uFolder, 0}; - - if (!fnSDFAT.exists(full_path((char *)path))) - fnSDFAT.create_path(full_path((char *)path)); -} - -static uint8_t _sys_makedisk(uint8_t drive) -{ - if (drive < 1 || drive > 16) return 0xff; - - uint8_t dFolder = drive + '@'; - uint8_t disk[2] = {dFolder, 0}; - - if (fnSDFAT.exists(full_path((char *)disk))) return 0; - if (!fnSDFAT.create_path(full_path((char *)disk))) return 0xfe; - - uint8_t path[4] = {dFolder, FOLDERCHAR, '0', 0}; - fnSDFAT.create_path(full_path((char *)path)); - return 0; -} - -/* ------------------------------------------------------------------------- - * Console abstraction — bridges RunCPM I/O to the per-TU queues - * ------------------------------------------------------------------------- */ - -static int _kbhit(void) -{ -#ifdef ESP_PLATFORM - if (_cpm_txq == nullptr) return 0; - return (int)uxQueueMessagesWaiting(_cpm_txq); -#else - std::lock_guard lk(_cpm_txmtx); - return (int)_cpm_txq.size(); -#endif -} - -static uint8_t _getch(void) -{ - uint8_t c = 0; -#ifdef ESP_PLATFORM - if (_cpm_txq != nullptr) - xQueueReceive(_cpm_txq, &c, portMAX_DELAY); -#else - std::unique_lock lk(_cpm_txmtx); - _cpm_txcv.wait(lk, [] { return !_cpm_txq.empty(); }); - c = _cpm_txq.front(); - _cpm_txq.pop(); -#endif - return c; -} - -static uint8_t _getche(void) -{ - uint8_t c = _getch(); - /* echo back through rxq so the terminal sees it */ -#ifdef ESP_PLATFORM - if (_cpm_rxq != nullptr) - xQueueSend(_cpm_rxq, &c, portMAX_DELAY); -#else - { - std::lock_guard lk(_cpm_rxmtx); - _cpm_rxq.push(c); - } -#endif - return c; -} - -static void _putch(uint8_t ch) -{ -#ifdef ESP_PLATFORM - if (_cpm_rxq != nullptr) - xQueueSend(_cpm_rxq, &ch, portMAX_DELAY); -#else - { - std::lock_guard lk(_cpm_rxmtx); - _cpm_rxq.push(ch); - } -#endif -} - -static void _clrscr(void) -{ - /* VT100 cursor-home + clear-screen */ - _putch(0x1B); _putch('['); _putch('1'); _putch(';'); - _putch('1'); _putch('H'); _putch(0x1B); _putch('['); - _putch('2'); _putch('J'); -} - -#endif /* ABSTRACTION_NETWORK_PROTOCOL_H */ diff --git a/lib/runcpm/globals.h b/lib/runcpm/globals.h index 5c4b9044a..ae04225d8 100644 --- a/lib/runcpm/globals.h +++ b/lib/runcpm/globals.h @@ -204,34 +204,12 @@ static uint16 physicalExtentBytes;// # bytes described by 1 directory entry #define tohex(x) ((x) < 10 ? (x) + 48 : (x) + 87) -/* When RUNCPM_STATIC_IMPL is defined, RunCPM symbols get internal (static) - * linkage so this translation unit can coexist with another TU that also - * includes the RunCPM headers (e.g. a bus-specific CPM device and the - * network-protocol CPM adapter built into the same binary). */ -#ifdef RUNCPM_STATIC_IMPL -#define RUNCPM_DECL static -#else +/* The engine is compiled exactly once (runcpm_core.cpp) with normal external + * linkage, so RunCPM symbols are plain (non-static). */ #define RUNCPM_DECL -#endif -/* Definition of externs/forward-declarations to prevent precedence - * compilation errors inside the RunCPM header chain. */ -#ifdef RUNCPM_STATIC_IMPL -/* Static (internal-linkage) forward declarations for RUNCPM_STATIC_IMPL mode. - * The definitions below (in console.h, disk.h, cpm.h, …) are also static, so - * these forward-decls must match in linkage to avoid a C++ constraint error. */ -static void _Bdos(void); -static void _Bios(void); -static void _HostnameToFCB(uint16 fcbaddr, uint8* filename); -static void _HostnameToFCBname(uint8* from, uint8* to); -static void _mockupDirEntry(void); -static uint8 match(uint8* fcbname, uint8* pattern); -static void _puts(const char* str); -#ifndef RAM_FAST -static uint8* _RamSysAddr(uint16 address); -static void _RamWrite(uint16 address, uint8 value); -#endif -#else /* !RUNCPM_STATIC_IMPL */ +/* Forward declarations to prevent precedence compilation errors inside the + * RunCPM header chain. */ #ifdef __cplusplus // If building on Arduino extern "C" { @@ -255,6 +233,5 @@ extern "C" #ifdef __cplusplus // If building on Arduino } #endif -#endif /* RUNCPM_STATIC_IMPL */ #endif diff --git a/lib/runcpm/runcpm_core.cpp b/lib/runcpm/runcpm_core.cpp new file mode 100644 index 000000000..c8accec10 --- /dev/null +++ b/lib/runcpm/runcpm_core.cpp @@ -0,0 +1,101 @@ +/** + * runcpm_core.cpp - the one and only build of the RunCPM engine. + * + * Historically every transport (the SIO/Atari bus device, the IWM/Apple and + * DriveWire/CoCo background tasks, the RS232 bus device and the N:CPM:// + * network adapter) #included the whole header-only engine into its own + * translation unit. That meant several independent 64K RAM images and several + * copies of the BDOS/BIOS/CCP code in the firmware, kept apart only by the + * RUNCPM_STATIC_IMPL "make every symbol static" hack. + * + * This file compiles the engine exactly once, with normal (external) linkage, + * for every platform. Transports no longer include the engine; they call + * runcpm_session_run() with a small set of console callbacks (see + * runcpm_session.h) and the engine talks to them through g_runcpm_console. + */ + +#include +#include +#include + +#define CCP_INTERNAL + +#include "runcpm_session.h" + +#include "globals.h" +#include "abstraction_fujinet.h" // filesystem + console-dispatch glue +#include "ram.h" // RAM access +#include "console.h" // _putcon/_puts built on the console callbacks +#include "cpu.h" // Z80 core + Status/Debug/Break/Step +#include "disk.h" // CP/M disk abstraction +#include "host.h" // custom host-specific BDOS call +#include "cpm.h" // CP/M structures and BDOS/BIOS +#include "ccp.h" // internal CCP + +// The live console endpoint. Declared extern in abstraction_fujinet.h and read +// by the _kbhit/_getch/_putch/_clrscr glue there. +runcpm_console_ops g_runcpm_console{}; + +// The engine owns a single 64K RAM image and is not re-entrant, so only one +// session may run at a time. g_busy guards that; g_exit lets another task ask +// the running session to stop. +static std::atomic g_busy{false}; +static volatile bool g_exit = false; + +bool runcpm_session_active(void) +{ + return g_busy.load(); +} + +void runcpm_session_request_exit(void) +{ + // Status == 1 is the engine's "BIOS BOOT / exit CP/M" signal; setting it + // makes the CCP fall out of its loop at the next iteration. g_exit also + // breaks our own warm-boot loop below. + g_exit = true; + Status = 1; +} + +bool runcpm_session_run(const runcpm_console_ops *ops) +{ + bool expected = false; + if (!g_busy.compare_exchange_strong(expected, true)) + return false; // a session is already running + + g_exit = false; + g_runcpm_console = *ops; + + // One-time machine setup for the whole session. + Status = Debug = 0; + Break = Step = -1; + RAM = (uint8 *)malloc(MEMSIZE); + if (RAM != nullptr) + { + memset(RAM, 0, MEMSIZE); + memset(filename, 0, sizeof(filename)); + memset(newname, 0, sizeof(newname)); + memset(fcbname, 0, sizeof(fcbname)); + memset(pattern, 0, sizeof(pattern)); + + // CCP loop: a warm boot (Status == 2, e.g. ^C at the prompt or a + // program that RETs) re-enters the CCP and reprints the banner, exactly + // like real CP/M. An exit (Status == 1) or an external exit request + // ends the session. + while (true) + { + _puts(CCPHEAD); + _PatchCPM(); + Status = 0; + _ccp(); + if (Status == 1 || g_exit) + break; + } + + free(RAM); + RAM = nullptr; + } + + g_runcpm_console = runcpm_console_ops{}; + g_busy.store(false); + return true; +} diff --git a/lib/runcpm/runcpm_session.h b/lib/runcpm/runcpm_session.h new file mode 100644 index 000000000..b74d87bfc --- /dev/null +++ b/lib/runcpm/runcpm_session.h @@ -0,0 +1,39 @@ +#ifndef RUNCPM_SESSION_H +#define RUNCPM_SESSION_H + +#include + +// Single shared entry point into the RunCPM engine. +// +// The engine itself is compiled exactly once (runcpm_core.cpp, global +// linkage). Every transport that wants to run CP/M - the SIO/Atari bus, the +// IWM/Apple and DriveWire/CoCo background tasks, the RS232 bus and the +// N:CPM:// network adapter - drives that one engine copy by supplying a small +// set of console callbacks and calling runcpm_session_run(). +// +// Only the four console primitives differ between transports; all of the BDOS, +// BIOS, disk and CCP logic is shared. The callbacks are deliberately plain C +// function pointers so the engine (compiled as C-style code) can call back into +// whichever device object owns the current session. +typedef struct runcpm_console_ops { + int (*kbhit)(void); // non-zero if a character is waiting + uint8_t (*getch)(void); // blocking read of one character + void (*putch)(uint8_t c); // write one character + void (*clrscr)(void); // clear screen; may be NULL +} runcpm_console_ops; + +// Run a full CP/M session using the supplied console callbacks. Blocks for the +// lifetime of the session (until the program exits CP/M or an exit is +// requested). Returns false immediately if another session is already active +// (the engine has a single 64K RAM image and is not re-entrant). +bool runcpm_session_run(const runcpm_console_ops *ops); + +// Ask the currently running session to terminate at the next CCP iteration. +// Safe to call from another task/transport (e.g. when the bus tears the link +// down out from under a blocked session). +void runcpm_session_request_exit(void); + +// True while a session is running. +bool runcpm_session_active(void); + +#endif // RUNCPM_SESSION_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e43b98892..b09bc2fa1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -145,6 +145,7 @@ FILE(GLOB_RECURSE SOURCES ${CMAKE_SOURCE_DIR}/lib/printer-emulator/*.cpp ${CMAKE_SOURCE_DIR}/lib/qrcode/*.c ${CMAKE_SOURCE_DIR}/lib/qrcode/*.cpp + ${CMAKE_SOURCE_DIR}/lib/runcpm/*.cpp ${CMAKE_SOURCE_DIR}/lib/sam/*.c ${CMAKE_SOURCE_DIR}/lib/sam/*.cpp ${CMAKE_SOURCE_DIR}/lib/task/*.cpp From a6ded6296d5f4920255abcc49a37ea276f87e8ff Mon Sep 17 00:00:00 2001 From: "Peter D. Kaczorowski" Date: Tue, 30 Jun 2026 00:14:16 +0200 Subject: [PATCH 2/2] RunCPM: upgrade engine to 6.9 on the shared cpmDevice base Bumps the vendored RunCPM engine from 5.8 to 6.9 while keeping the cpmDevice base-class architecture introduced in the previous commit. The engine-side files are a matched set taken from upstream RunCPM 6.9: ccp.h, cpm.h, cpu.h (+cpu_mhz.h, +debug.h), disk.h, console.h, host.h, ram.h, resource.h and globals.h, plus the 6.9 single-core glue (runcpm_core.cpp, runcpm_session.h, abstraction_fujinet_core.h, which replaces abstraction_fujinet.h). 6.9's console interface adds a getche primitive (blocking read + echo) and widens getch/getche/kbhit to int. Rather than add a per-bus override, cpmDevice synthesises getche generically (read endpoint byte, mask to 7 bits, echo via putch) so no transport had to change: the SIO, IWM, DriveWire, RS232 and RC2014 devices keep their existing ep_* overrides untouched. The N:CPM:// adapter (network-protocol/CPM.cpp), which is not a cpmDevice, gets the same small getche shim. Builds clean on desktop ATARI and APPLE (cpmQueueDevice path) and on the ESP32 fujinet-atari-v1 target (RAM 36.6%, Flash 24.2%). --- lib/device/cpm/cpm.cpp | 15 +- lib/device/cpm/cpm.h | 3 +- lib/network-protocol/CPM.cpp | 13 +- ...n_fujinet.h => abstraction_fujinet_core.h} | 218 +- lib/runcpm/ccp.h | 1792 ++++++--- lib/runcpm/console.h | 136 +- lib/runcpm/cpm.h | 3288 ++++++++++------- lib/runcpm/cpu.h | 1257 +++---- lib/runcpm/cpu_mhz.h | 122 + lib/runcpm/debug.h | 1080 ++++++ lib/runcpm/disk.h | 1249 ++++--- lib/runcpm/globals.h | 432 ++- lib/runcpm/host.h | 2 +- lib/runcpm/ram.h | 38 +- lib/runcpm/resource.h | 16 +- lib/runcpm/runcpm_core.cpp | 155 +- lib/runcpm/runcpm_session.h | 60 +- 17 files changed, 6379 insertions(+), 3497 deletions(-) rename lib/runcpm/{abstraction_fujinet.h => abstraction_fujinet_core.h} (60%) create mode 100644 lib/runcpm/cpu_mhz.h create mode 100644 lib/runcpm/debug.h diff --git a/lib/device/cpm/cpm.cpp b/lib/device/cpm/cpm.cpp index 1e46bf0d8..f4d29511b 100644 --- a/lib/device/cpm/cpm.cpp +++ b/lib/device/cpm/cpm.cpp @@ -13,11 +13,23 @@ int cpmDevice::s_kbhit() return s_active ? s_active->ep_kbhit() : 0; } -uint8_t cpmDevice::s_getch() +int cpmDevice::s_getch() { return s_active ? s_active->ep_getch() : 0x03; } +// getche = blocking read then echo. Generic for every transport: read a byte +// from the endpoint, mask to 7 bits (CP/M console is 7-bit), echo it back out +// the same endpoint. No per-bus override needed. +int cpmDevice::s_getche() +{ + if (!s_active) + return 0x03; + uint8_t ch = s_active->ep_getch() & 0x7f; + s_active->ep_putch(ch); + return ch; +} + void cpmDevice::s_putch(uint8_t c) { if (s_active) @@ -37,6 +49,7 @@ void cpmDevice::handle_cpm() runcpm_console_ops ops; ops.kbhit = &cpmDevice::s_kbhit; ops.getch = &cpmDevice::s_getch; + ops.getche = &cpmDevice::s_getche; ops.putch = &cpmDevice::s_putch; ops.clrscr = &cpmDevice::s_clrscr; diff --git a/lib/device/cpm/cpm.h b/lib/device/cpm/cpm.h index 26d284067..e2924a9f5 100644 --- a/lib/device/cpm/cpm.h +++ b/lib/device/cpm/cpm.h @@ -61,7 +61,8 @@ class cpmDevice : public virtualDevice // to the device that owns the running session (only one runs at a time). static cpmDevice *s_active; static int s_kbhit(); - static uint8_t s_getch(); + static int s_getch(); + static int s_getche(); static void s_putch(uint8_t c); static void s_clrscr(); }; diff --git a/lib/network-protocol/CPM.cpp b/lib/network-protocol/CPM.cpp index 920af27aa..bfa1cae3d 100644 --- a/lib/network-protocol/CPM.cpp +++ b/lib/network-protocol/CPM.cpp @@ -56,7 +56,7 @@ static int net_kbhit(void) #endif } -static uint8_t net_getch(void) +static int net_getch(void) { uint8_t c = 0; #ifdef ESP_PLATFORM @@ -71,6 +71,16 @@ static uint8_t net_getch(void) return c; } +static void net_putch(uint8_t ch); + +/* getche = blocking read then echo (CP/M console is 7-bit). */ +static int net_getche(void) +{ + uint8_t c = (uint8_t)(net_getch() & 0x7f); + net_putch(c); + return c; +} + static void net_putch(uint8_t ch) { #ifdef ESP_PLATFORM @@ -100,6 +110,7 @@ static void _cpm_run(void) runcpm_console_ops ops; ops.kbhit = net_kbhit; ops.getch = net_getch; + ops.getche = net_getche; ops.putch = net_putch; ops.clrscr = net_clrscr; diff --git a/lib/runcpm/abstraction_fujinet.h b/lib/runcpm/abstraction_fujinet_core.h similarity index 60% rename from lib/runcpm/abstraction_fujinet.h rename to lib/runcpm/abstraction_fujinet_core.h index 55267fdb0..4783bd27c 100644 --- a/lib/runcpm/abstraction_fujinet.h +++ b/lib/runcpm/abstraction_fujinet_core.h @@ -1,9 +1,11 @@ -/** - * Abstraction functions for #FujiNet +/* + * FujiNet RunCPM abstraction (shared core): the disk/SD + BDOS-helper layer, + * compiled once inside runcpm_core.cpp. Console I/O dispatches through the + * active transport's runcpm_console_ops (g_runcpm_console). */ -#ifndef ABSTRACTION_FUJINET_H -#define ABSTRACTION_FUJINET_H +#ifndef ABSTRACTION_FUJINET_CORE_H +#define ABSTRACTION_FUJINET_CORE_H #include #include @@ -13,21 +15,27 @@ #include "../../include/debug.h" +#include "fnSystem.h" #include "fnFsSD.h" #include "runcpm_session.h" -// The active CP/M console endpoint, supplied by whichever transport opened the -// current session (the bus cpm device, or the N:CPM:// adapter). The engine -// only ever talks to the outside world through these four callbacks, which is -// what lets a single engine build drive every transport. -extern runcpm_console_ops g_runcpm_console; +#define HostOS 0x07 // FUJINET +/* FujiNet: SD path separator (disks live under "/CPM///"). */ #ifndef FOLDERCHAR #define FOLDERCHAR '/' #endif -#define HostOS 0x07 // FUJINET +/* FujiNet: 6.9 _mockupDirEntry references FILEBASE; FujiNet uses bare names. */ +#ifndef FILEBASE +#define FILEBASE "" +#endif + +/* FujiNet: 6.9 calls millis() unconditionally; map it to the system clock. */ +#ifndef millis +#define millis() ((uint32)fnSystem.millis()) +#endif typedef struct { @@ -60,6 +68,33 @@ char *full_path(char *fn) return full_filename; } +/* Read file-handle cache. Stock _sys_readseq/_sys_readrand do a full + open/seek/read(128)/close per 128-byte record; on the SD card each open is + ~11 ms, so loading a .COM took seconds. Since one session runs at a time and + read loops hit the same file repeatedly, keep the last-read file open ("r") + and reuse it for the next read of the same path (~310 opens -> 1). Closed on + path change, before any mutating op, and at session boundaries. */ +static FILE *seq_cache_fp = nullptr; +static char seq_cache_path[sizeof(full_filename)] = {0}; + +static void _seq_cache_close(void) +{ + if (seq_cache_fp) + { + fclose(seq_cache_fp); + seq_cache_fp = nullptr; + } + seq_cache_path[0] = '\0'; +} + +/* Drop the cached handle if it refers to `path` (already built via full_path). + Called by the mutating ops so the next read reopens with fresh contents. */ +static void _seq_cache_invalidate(const char *path) +{ + if (seq_cache_fp && strcmp(seq_cache_path, path) == 0) + _seq_cache_close(); +} + // // Hardware functions, new in 5.x @@ -76,27 +111,23 @@ uint32 _HardwareIn(const uint32 Port) /* Memory abstraction functions */ /*===============================================================================*/ -bool _RamLoad(char *fn, uint16_t address) +/* FujiNet: 6.9 _RamLoad takes a maxsize and returns the byte count. */ +uint16 _RamLoad(uint8 *filename, uint16 address, uint16 maxsize) { - FILE *f = fnSDFAT.file_open(full_path(fn), "r"); - bool result = false; + FILE *f = fnSDFAT.file_open(full_path((char *)filename), "r"); + uint16 count = 0; uint8_t b; if (f) { - while (!feof(f)) + while ((!maxsize || count < maxsize) && fread(&b, sizeof(uint8_t), 1, f) == 1) { - if (fread(&b, sizeof(uint8_t), 1, f) == 1) - { - _RamWrite(address++, b); - result = true; - } - else - result = false; + _RamWrite(address++, b); + count++; } fclose(f); } - return (result); + return (count); } /* filesystem (disk) abstraction fuctions */ @@ -158,6 +189,7 @@ int _sys_openfile(uint8_t *fn) int _sys_makefile(uint8_t *fn) { + _seq_cache_invalidate(full_path((char *)fn)); FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "w"); if (fp) { @@ -170,6 +202,7 @@ int _sys_makefile(uint8_t *fn) int _sys_deletefile(uint8_t *fn) { + _seq_cache_invalidate(full_path((char *)fn)); return fnSDFAT.remove(full_path((char *)fn)); } @@ -180,6 +213,11 @@ int _sys_renamefile(uint8_t *fn, uint8_t *newname) from = std::string(full_path((char *)fn)); to = std::string(full_path((char *)newname)); + /* Invalidate both endpoints: the source is moving, and a stale handle on + the destination path (if any) must not survive the rename. */ + _seq_cache_invalidate(from.c_str()); + _seq_cache_invalidate(to.c_str()); + return fnSDFAT.rename(from.c_str(), to.c_str()); } @@ -190,6 +228,7 @@ void _sys_logbuffer(uint8_t *buffer) bool _sys_extendfile(char *fn, unsigned long fpos) { + _seq_cache_invalidate(full_path((char *)fn)); FILE *fp = fnSDFAT.file_open(full_path((char *)fn), "a"); if (!fp) @@ -222,35 +261,49 @@ uint8_t _sys_readseq(uint8_t *fn, long fpos) uint8_t dmabuf[BlkSZ]; int seekErr; - f = fnSDFAT.file_open(full_path((char *)fn), "r"); - if (!f) + const char *path = full_path((char *)fn); + + /* Reuse the cached handle when this record is from the same file as the + previous sequential read; otherwise (re)open and cache it. This is the + optimization that collapses a ~310-open .COM load into a single open -- + see the _seq_cache_* block above. */ + if (seq_cache_fp && strcmp(seq_cache_path, path) == 0) { - result = 0x10; - return result; + f = seq_cache_fp; } - seekErr = fseek(f, fpos, SEEK_SET); - if (f) + else { - if (fpos > 0 && seekErr != 0) - { - // EOF - result = 0x01; - } - else + _seq_cache_close(); + f = fnSDFAT.file_open(path, "r"); + if (!f) { - // set DMA buffer to EOF - memset(dmabuf, 0x1a, BlkSZ); - bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); - if (bytesread) - memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); - result = bytesread ? 0x00 : 0x01; + result = 0x10; + return result; } + seq_cache_fp = f; + strncpy(seq_cache_path, path, sizeof(seq_cache_path) - 1); + seq_cache_path[sizeof(seq_cache_path) - 1] = '\0'; + } + + seekErr = fseek(f, fpos, SEEK_SET); + if (fpos > 0 && seekErr != 0) + { + // EOF + result = 0x01; } else { - result = 0x10; + memset(dmabuf, 0x1a, BlkSZ); // pre-pad with CP/M EOF markers + // Read byte-wise (size=1) so fread returns the byte count for a final + // partial record too; (BlkSZ,1) would return 0 and drop those bytes. + bytesread = fread(&dmabuf[0], sizeof(uint8_t), BlkSZ, f); + if (bytesread) + memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); + result = bytesread ? 0x00 : 0x01; } - fclose(f); + /* Handle intentionally left open and cached for the next record; closed by + _seq_cache_close()/_seq_cache_invalidate() on file change, mutation, or + session end. */ return (result); } @@ -259,6 +312,7 @@ uint8_t _sys_writeseq(uint8_t *fn, long fpos) uint8_t result = 0xff; FILE *f; + _seq_cache_invalidate(full_path((char *)fn)); if (_sys_extendfile((char *)fn, fpos)) f = fnSDFAT.file_open(full_path((char *)fn), "r+"); else @@ -292,13 +346,39 @@ uint8_t _sys_readrand(uint8_t *fn, long fpos) uint8 dmabuf[BlkSZ]; long extSize; - f = fnSDFAT.file_open(full_path((char *)fn), "r+"); + const char *path = full_path((char *)fn); + + /* Reuse the shared read-handle cache (see the _seq_cache_* block above). + Random access is a pure read here -- random writes go through + _sys_writerand on their own "r+" handle -- so the read-only cached "r" + handle serves it too. This collapses an editor's per-record random reads + (a 39 KB file is ~310 reopens) into a single open, exactly as for the + sequential .COM-load path. */ + if (seq_cache_fp && strcmp(seq_cache_path, path) == 0) + { + f = seq_cache_fp; + } + else + { + _seq_cache_close(); + f = fnSDFAT.file_open(path, "r"); + if (f) + { + seq_cache_fp = f; + strncpy(seq_cache_path, path, sizeof(seq_cache_path) - 1); + seq_cache_path[sizeof(seq_cache_path) - 1] = '\0'; + } + } + if (f) { if (fseek(f, fpos, SEEK_SET) == 0) { memset(dmabuf, 0x1A, BlkSZ); - bytesread = fread(&dmabuf[0], BlkSZ, sizeof(uint8_t), f); + // FujiNet: byte-wise read (size=1) for correct partial-record + // counts; see _sys_readseq above. dmabuf pre-padded with 0x1A + // (CP/M EOF). + bytesread = fread(&dmabuf[0], sizeof(uint8_t), BlkSZ, f); if (bytesread) memcpy((uint8_t *)&RAM[dmaAddr], dmabuf, BlkSZ); result = bytesread ? 0x00 : 0x01; @@ -326,7 +406,9 @@ uint8_t _sys_readrand(uint8_t *fn, long fpos) { result = 0x10; } - fclose(f); + /* Handle intentionally left open and cached for the next read; closed by + _seq_cache_close()/_seq_cache_invalidate() on file change, mutation, or + session end. */ return (result); } @@ -335,6 +417,7 @@ uint8_t _sys_writerand(uint8_t *fn, long fpos) uint8 result = 0xff; FILE *f; + _seq_cache_invalidate(full_path((char *)fn)); if (_sys_extendfile((char *)fn, fpos)) { f = fnSDFAT.file_open(full_path((char *)fn), "r+"); @@ -377,7 +460,7 @@ uint8_t _findnext(uint8_t isdir) if (allExtents && fileRecords) { - _mockupDirEntry(); + _mockupDirEntry(0); // FujiNet: mode 0 = bare filename (no FILEBASE prefix) result = 0; } else @@ -404,7 +487,7 @@ uint8_t _findnext(uint8_t isdir) fileExtents = fileRecords / BlkEX + ((fileRecords & (BlkEX - 1)) ? 1 : 0); fileExtentsUsed = 0; firstFreeAllocBlock = firstBlockAfterDir; - _mockupDirEntry(); + _mockupDirEntry(0); // FujiNet: mode 0 = bare filename (no FILEBASE prefix) } else { @@ -503,41 +586,34 @@ uint8_t _sys_makedisk(uint8_t drive) /* Console abstraction functions */ /*===============================================================================*/ -// -// The console is the only part of the abstraction that differs between -// transports, so it is the only part that is delegated. Each callback is -// supplied by the transport that owns the current session (the active -// cpmDevice endpoint, or the N:CPM:// adapter). Any per-transport quirks - -// 7-bit masking on the SIO link, queue plumbing on IWM/DriveWire, VT100 clear -// on N: - live in those endpoints, not here. +/* + * Transport-agnostic console: every console primitive the RunCPM chain + * (console.h, cpm.h, ccp.h) calls is dispatched through the active transport's + * runcpm_console_ops, installed by runcpm_session_run() before the CCP starts. + * g_runcpm_console is defined in runcpm_core.cpp. + */ +extern "C" runcpm_console_ops g_runcpm_console; -int _kbhit(void) -{ - return g_runcpm_console.kbhit ? g_runcpm_console.kbhit() : 0; -} +#define _kbhit() (g_runcpm_console.kbhit()) -uint8_t _getch(void) +static inline uint8 _getch(void) { - return g_runcpm_console.getch ? g_runcpm_console.getch() : 0x03; // ^C if none + return (uint8)g_runcpm_console.getch(); } -void _putch(uint8_t ch) +static inline uint8 _getche(void) { - if (g_runcpm_console.putch) - g_runcpm_console.putch(ch); + return (uint8)g_runcpm_console.getche(); } -uint8_t _getche(void) +static inline void _putch(uint8 ch) { - uint8_t ch = _getch(); - _putch(ch); - return ch; + g_runcpm_console.putch(ch); } -void _clrscr(void) +static inline void _clrscr(void) { - if (g_runcpm_console.clrscr) - g_runcpm_console.clrscr(); + g_runcpm_console.clrscr(); } -#endif /* ABSTRACTION_FUJINET_H */ +#endif /* ABSTRACTION_FUJINET_CORE_H */ diff --git a/lib/runcpm/ccp.h b/lib/runcpm/ccp.h index bedd99f92..5a1792d58 100644 --- a/lib/runcpm/ccp.h +++ b/lib/runcpm/ccp.h @@ -8,115 +8,176 @@ // CP/M BDOS calls #include "cpm.h" -#define CmdFCB (BatchFCB + 36) // FCB for use by internal commands -#define ParFCB 0x005C // FCB for use by line parameters -#define SecFCB 0x006C // Secondary part of FCB for renaming files -#define Trampoline (CmdFCB + 36) // Trampoline for running external commands +// Memory Layout Definitions +#define CmdFCB (BatchFCB + 48) // FCB for use by internal commands +#define ParFCB 0x005C // FCB for use by line parameters +#define SecFCB 0x006C // Secondary part of FCB for renaming files +#define Trampoline (CmdFCB + 36) // Trampoline for running external commands -#define inBuf (BDOSjmppage - 256) // Input buffer location -#define cmdLen 125 // Maximum size of a command line (sz+rd+cmd+\0) +#define inBuf (BDOSjmppage - 256) // Input buffer location +#define cmdLen 125 // Maximum size of a command line (sz+rd+cmd+\0) -#define defDMA 0x0080 // Default DMA address -#define defLoad 0x0100 // Default load address +#define defDMA 0x0080 // Default DMA address +#define defLoad 0x0100 // Default load address + +#define Internals // Define to have internal commands + +// CCP Configuration and State +#define DEFAULT_PAGE_SIZE 22 +#define PROMPT_SIZE 8 +#define FCB_SIZE 36 +#define SEC_SIZE 128 +#define MAX_USER 15 // CCP global variables -RUNCPM_DECL uint8 pgSize = 22; // for TYPE -RUNCPM_DECL uint8 curDrive = 0; // 0 -> 15 = A -> P .. Current drive for the CCP (same as RAM[DSKByte]) -RUNCPM_DECL uint8 parDrive = 0; // 0 -> 15 = A -> P .. Drive for the first file parameter -RUNCPM_DECL uint8 curUser = 0; // 0 -> 15 .. Current user area to access -RUNCPM_DECL bool sFlag = FALSE; // Submit Flag -RUNCPM_DECL uint8 sRecs = 0; // Number of records on the Submit file -RUNCPM_DECL uint8 prompt[8] = "\r\n >"; -RUNCPM_DECL uint16 pbuf, perr; -RUNCPM_DECL uint8 blen; // Actual size of the typed command line (size of the buffer) - -static const char *Commands[] = -{ - // Standard CP/M commands - "DIR", - "ERA", - "TYPE", - "SAVE", - "REN", - "USER", - - // Extra CCP commands - "CLS", - "DEL", - "EXIT", - "PAGE", - "VOL", - NULL -}; +RUNCPM_DECL uint8 pageSize = DEFAULT_PAGE_SIZE; // for TYPE +RUNCPM_DECL uint8 currentDrive = 0; // 0 -> 15 = A -> P (Current drive for the CCP) +RUNCPM_DECL uint8 paramDrive = 0; // 0 -> 15 = A -> P (Drive for the first file parameter) +RUNCPM_DECL uint8 currentUser = 0; // 0 -> 15 (Current user area to access) +RUNCPM_DECL bool submitFlag = FALSE; // Submit Flag +RUNCPM_DECL uint8 submitRecords = 0; // Number of records on the Submit file +RUNCPM_DECL uint8 prompt[PROMPT_SIZE] = "\r\n >"; // Command prompt +RUNCPM_DECL uint16 cmdBufferPtr, errorPtr; // Pointer to the command buffer, and error position +RUNCPM_DECL uint8 bufferLen = 0; // Actual size of the typed command line + +/* FujiNet: renamed `Command` -> `CcpCommand` to avoid clashing with FujiNet's + `class Command` (lib/devrelay/types/Command.h) pulled into the shared core. */ +typedef struct { + const char *name; + uint8 (*handler)(void); +} CcpCommand; + +// Used to call BIOS from inside the CCP +RUNCPM_DECL void _ccp_bios(uint8 function) { + SET_LOW_REGISTER(PCX, function); + _Bios(); +} // _ccp_bios // Used to call BDOS from inside the CCP RUNCPM_DECL uint16 _ccp_bdos(uint8 function, uint16 de) { SET_LOW_REGISTER(BC, function); DE = de; _Bdos(); - + return (HL & 0xffff); } // _ccp_bdos +// --- New Helper Functions for Printing --- + +// Helper to print a byte in Hex +RUNCPM_DECL void _ccp_printHex8(uint8 v) { + uint8 nibble = v >> 4; + _ccp_bdos(C_WRITE, nibble > 9 ? nibble + 55 : nibble + 48); + nibble = v & 0x0F; + _ccp_bdos(C_WRITE, nibble > 9 ? nibble + 55 : nibble + 48); +} + +// Helper to print a word in Hex +RUNCPM_DECL void _ccp_printHex16(uint16 v) { + _ccp_printHex8(v >> 8); + _ccp_printHex8(v & 0xFF); +} + +// Helper to print a number in Decimal +RUNCPM_DECL void _ccp_printDec(uint32 v) { + char buf[10]; + uint8 i = 0; + if (v == 0) { + _ccp_bdos(C_WRITE, '0'); + return; + } + while (v) { + buf[i++] = (v % 10) + '0'; + v /= 10; + } + while (i) { + _ccp_bdos(C_WRITE, buf[--i]); + } +} + +#ifdef CPM3 +// Helper to print a 2-digit zero-padded decimal value +RUNCPM_DECL void _ccp_print2(uint8 v) { + _ccp_bdos(C_WRITE, '0' + (v / 10) % 10); + _ccp_bdos(C_WRITE, '0' + v % 10); +} + +// Prints a CP/M 3 date stamp (4 bytes at 'stamp': day word, hour BCD, +// minute BCD) as MM/DD/YYYY HH:MM. Day 1 = 1978-01-01. +RUNCPM_DECL void _ccp_printFileDate(uint16 stamp) { + uint16 days = _RamRead(stamp) | (_RamRead(stamp + 1) << 8); + uint8 bh = _RamRead(stamp + 2); + uint8 bm = _RamRead(stamp + 3); + uint16 year = 1978; + uint8 month; + static const uint8 mlen[12] = {31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + + if (days == 0) { // no stamp recorded + _puts(" -- "); + return; + } + days -= 1; // days since 1978-01-01 + for (;;) { + uint16 ylen = + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365; + if (days < ylen) + break; + days -= ylen; + ++year; + } + for (month = 0; month < 12; ++month) { + uint8 dm = mlen[month]; + if (month == 1 && + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) + dm = 29; + if (days < dm) + break; + days -= dm; + } + _ccp_print2(month + 1); + _ccp_bdos(C_WRITE, '/'); + _ccp_print2((uint8)days + 1); + _ccp_bdos(C_WRITE, '/'); + _ccp_printDec(year); + _ccp_bdos(C_WRITE, ' '); + _ccp_print2(((bh >> 4) & 0x0F) * 10 + (bh & 0x0F)); + _ccp_bdos(C_WRITE, ':'); + _ccp_print2(((bm >> 4) & 0x0F) * 10 + (bm & 0x0F)); +} +#endif + // Compares two strings (Atmel doesn't like strcmp) -RUNCPM_DECL uint8 _ccp_strcmp(char *stra, char *strb) { +RUNCPM_DECL uint8 _ccp_strEqual(const char *stra, const char *strb) { while (*stra && *strb && (*stra == *strb)) { ++stra; ++strb; } return (*stra == *strb); -} // _ccp_strcmp +} // _ccp_strEqual -// Gets the command ID number -RUNCPM_DECL uint8 _ccp_cnum(void) { - uint8 result = 255; - uint8 command[9]; - uint8 i = 0; - - if (!_RamRead(CmdFCB)) { // If a drive was set, then the command is external - while (i < 8 && _RamRead(CmdFCB + i + 1) != ' ') { - command[i] = _RamRead(CmdFCB + i + 1); - ++i; - } - command[i] = 0; - - i = 0; - - while (Commands[i]) { - if (_ccp_strcmp((char *)command, (char *)Commands[i])) { - result = i; - perr = defDMA + 2; - break; - } - ++i; - } - } - return (result); -} // _ccp_cnum - -// Returns true if character is a separator +// Returns true if character is a delimiter RUNCPM_DECL uint8 _ccp_delim(uint8 ch) { - return (ch == 0 || ch == ' ' || ch == '=' || ch == '.' || ch == ':' || ch == ';' || ch == '<' || ch == '>'); + return (ch == 0 || ch == ' ' || ch == '=' || ch == '.' || ch == ':' || + ch == ';' || ch == '<' || ch == '>'); } // Prints the FCB filename RUNCPM_DECL void _ccp_printfcb(uint16 fcb, uint8 compact) { uint8 i, ch; - + ch = _RamRead(fcb); if (ch && compact) { - _ccp_bdos( C_WRITE, ch + '@'); - _ccp_bdos( C_WRITE, ':'); + _ccp_bdos(C_WRITE, ch + '@'); + _ccp_bdos(C_WRITE, ':'); } - + for (i = 1; i < 12; ++i) { ch = _RamRead(fcb + i); - if ((ch == ' ') && compact) { + if ((ch == ' ') && compact) continue; - } - if (i == 9) { + if (i == 9) _ccp_bdos(C_WRITE, compact ? '.' : ' '); - } _ccp_bdos(C_WRITE, ch); } } // _ccp_printfcb @@ -124,85 +185,84 @@ RUNCPM_DECL void _ccp_printfcb(uint16 fcb, uint8 compact) { // Initializes the FCB RUNCPM_DECL void _ccp_initFCB(uint16 address, uint8 size) { uint8 i; - - for (i = 0; i < size; ++i) { + + for (i = 0; i < size; ++i) _RamWrite(address + i, 0x00); - } - - for (i = 0; i < 11; ++i) { + + for (i = 0; i < 11; ++i) _RamWrite(address + 1 + i, 0x20); - } } // _ccp_initFCB // Name to FCB +// Parses a filename from the command buffer into an FCB +// Handles drive specifiers (A:), wildcards (*, ?), and extensions RUNCPM_DECL uint8 _ccp_nameToFCB(uint16 fcb) { uint8 pad, plen, ch, n = 0; - + // Checks for a drive and places it on the Command FCB - if (_RamRead(pbuf + 1) == ':') { - ch = toupper(_RamRead(pbuf++)); - _RamWrite(fcb, ch - '@'); // Makes the drive 0x1-0xF for A-P - ++pbuf; // Points pbuf past the : - blen -= 2; + if (_RamRead(cmdBufferPtr + 1) == ':') { + ch = toupper(_RamRead(cmdBufferPtr++)); + _RamWrite(fcb, ch - '@'); // Makes the drive 0x1-0xF for A-P + ++cmdBufferPtr; // Points cmdBufferPtr past the : + bufferLen -= 2; } - if (blen) { + if (bufferLen) { ++fcb; - + + // Parse filename (up to 8 chars) plen = 8; pad = ' '; - ch = toupper(_RamRead(pbuf)); - - while (blen && plen) { - if (_ccp_delim(ch)) { + ch = toupper(_RamRead(cmdBufferPtr)); + + while (bufferLen && plen) { + if (_ccp_delim(ch)) break; - } - ++pbuf; - --blen; - if (ch == '*') { + ++cmdBufferPtr; + --bufferLen; + if (ch == '*') pad = '?'; - } if (pad == '?') { ch = pad; - n = n | 0x80; // Name is not unique + n = n | 0x80; // Name is not unique } --plen; ++n; _RamWrite(fcb++, ch); - ch = toupper(_RamRead(pbuf)); + ch = toupper(_RamRead(cmdBufferPtr)); } - - while (plen--) { + + // Pad remaining filename with spaces + while (plen--) _RamWrite(fcb++, pad); - } + + // Parse extension (up to 3 chars) plen = 3; pad = ' '; if (ch == '.') { - ++pbuf; - --blen; + ++cmdBufferPtr; + --bufferLen; } - - while (blen && plen) { - ch = toupper(_RamRead(pbuf)); - if (_ccp_delim(ch)) { + + while (bufferLen && plen) { + ch = toupper(_RamRead(cmdBufferPtr)); + if (_ccp_delim(ch)) break; - } - ++pbuf; - --blen; - if (ch == '*') { + ++cmdBufferPtr; + --bufferLen; + if (ch == '*') pad = '?'; - } if (pad == '?') { ch = pad; - n = n | 0x80; // Name is not unique + n = n | 0x80; // Name is not unique } --plen; ++n; _RamWrite(fcb++, ch); } - - while (plen--) { + + // Pad remaining extension with spaces + while (plen--) _RamWrite(fcb++, pad); - } } return (n); } // _ccp_nameToFCB @@ -212,7 +272,7 @@ RUNCPM_DECL uint16 _ccp_fcbtonum() { uint8 ch; uint16 n = 0; uint8 pos = ParFCB + 1; - + while (TRUE) { ch = _RamRead(pos++); if ((ch < '0') || (ch > '9')) { @@ -223,108 +283,241 @@ RUNCPM_DECL uint16 _ccp_fcbtonum() { return (n); } // _ccp_fcbtonum -// DIR command -RUNCPM_DECL void _ccp_dir(void) { +// Asks for a key to continue, used by TYPE and LDIR +RUNCPM_DECL void _ccp_askForKey(void) { + _puts("-- Press any key, ^C to quit --"); + _ccp_bios(B_CONIN); + _puts("\r"); + _puts(" \r"); +} + +#ifdef Internals +// DIR command - standard directory listing +RUNCPM_DECL uint8 _ccp_dir(void) { uint8 i; uint8 dirHead[6] = "A: "; uint8 dirSep[6] = " | "; - uint32 fcount = 0; // Number of files printed - uint32 ccount = 0; // Number of columns printed - - if (_RamRead(ParFCB + 1) == ' ') { - for (i = 1; i < 12; ++i) { + uint32 ccount = 0; // Number of columns printed + + if (_RamRead(ParFCB + 1) == ' ') + for (i = 1; i < 12; ++i) _RamWrite(ParFCB + i, '?'); - } - } dirHead[0] = _RamRead(ParFCB) ? _RamRead(ParFCB) + '@' : prompt[2]; - + _puts("\r\n"); if (!_SearchFirst(ParFCB, TRUE)) { _puts((char *)dirHead); _ccp_printfcb(tmpFCB, FALSE); - ++fcount; ++ccount; - + while (!_SearchNext(ParFCB, TRUE)) { if (!ccount) { - _puts( "\r\n"); - _puts( (char *)dirHead); + _puts("\r\n"); + _puts((char *)dirHead); } else { _puts((char *)dirSep); } _ccp_printfcb(tmpFCB, FALSE); - ++fcount; ++ccount; - if (ccount > 3) { + if (ccount > 3) ccount = 0; - } } } else { _puts("No file"); } + return 0; } // _ccp_dir -// ERA command -RUNCPM_DECL void _ccp_era(void) { - if (_ccp_bdos(F_DELETE, ParFCB)) { - _puts("\r\nNo file"); +// LDIR command (Long DIR) - directory listing with file sizes and optional +// checksum +RUNCPM_DECL uint8 _ccp_ldir(void) { + uint8 checksumOption = 0; + uint8 l = 0; + + // Check for /C option in ParFCB or SecFCB + if ((_RamRead(ParFCB + 1) == '/' && _RamRead(ParFCB + 2) == 'C') || + (_RamRead(SecFCB + 1) == '/' && _RamRead(SecFCB + 2) == 'C')) { + checksumOption = 1; + } + + // If ParFCB has /C, set it to list all files + if (_RamRead(ParFCB + 1) == '/' && _RamRead(ParFCB + 2) == 'C') { + for (uint8 i = 1; i < 12; ++i) + _RamWrite(ParFCB + i, '?'); + } else if (_RamRead(ParFCB + 1) == ' ') { + // If no pattern specified, list all files + for (uint8 i = 1; i < 12; ++i) + _RamWrite(ParFCB + i, '?'); } + + _puts("\r\n"); + if (!_SearchFirst(ParFCB, TRUE)) { + do { + _ccp_printfcb(tmpFCB, TRUE); + // Calculate length of printed filename for alignment + uint8 len = 2; // drive: + for (uint8 i = 1; i <= 8; ++i) { + uint8 ch = _RamRead(tmpFCB + i); + if (ch != ' ') + len++; + else + break; + } + len++; // . + for (uint8 i = 9; i <= 11; ++i) { + uint8 ch = _RamRead(tmpFCB + i); + if (ch != ' ') + len++; + else + break; + } + // Align to column 20 + uint8 target = 20; + while (len < target) { + _ccp_bdos(C_WRITE, ' '); + len++; + } + // Get file size +#ifdef CPM3 + // CP/M 3: obtain the size strictly through BDOS. F_SIZE (function + // 35) sets the random-record field to the number of 128-byte + // records, which is the file size CP/M actually tracks. + _ccp_bdos(F_SIZE, tmpFCB); + uint32 size = ((uint32)_RamRead(tmpFCB + 33) | + ((uint32)_RamRead(tmpFCB + 34) << 8) | + ((uint32)_RamRead(tmpFCB + 35) << 16)) * 128; +#else + long fsize = _FileSize(tmpFCB); + uint32 size = (fsize == -1) ? 0 : (uint32)fsize; +#endif + + // Print size in bytes, padded to 7 digits + // Optimized to remove sprintf + uint32 temp = size; + uint8 digits = 0; + if (temp == 0) digits = 1; + else { + while (temp > 0) { temp /= 10; digits++; } + } + + uint8 padding = 7 - digits; + while (padding > 0) { + _ccp_bdos(C_WRITE, ' '); + padding--; + } + _ccp_printDec(size); + _puts(" bytes"); + +#ifdef CPM3 + // CP/M 3: append the read/write status and date stamp, obtained + // strictly through BDOS function 102 (Read File Date Stamps). + if (!_ccp_bdos(F_TIMEDATE, tmpFCB)) { + _puts((_RamRead(tmpFCB + 9) & 0x80) ? " R/O " : " R/W "); + _ccp_printFileDate(tmpFCB + 28); // update stamp at FCB+28 + } else { + _puts(" R/W "); + _puts(" -- "); + } +#endif + + if (checksumOption) { + // Compute checksum + uint16 checksum = 0; + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(tmpFCB); + F->ex = 0; + F->cr = 0; + if (!_ccp_bdos(F_OPEN, tmpFCB)) { + // Optimization: Use direct pointer if available for checksum + uint8* dmaPtr = (uint8*)_RamSysAddr(dmaAddr); + while (!_ccp_bdos(F_READ, tmpFCB)) { + for (uint8 i = 0; i < 128; ++i) { + checksum += dmaPtr[i]; + } + } + _ccp_bdos(F_CLOSE, tmpFCB); + } + // Print checksum + _puts(" "); + _ccp_printHex16(checksum); + _puts("h"); + } + + _puts("\r\n"); + l++; + if (pageSize && (l == pageSize)) { + l = 0; + _ccp_askForKey(); + if (HIGH_REGISTER(AF) == 3) + break; + } + } while (!_SearchNext(ParFCB, TRUE)); + } else { + _puts("No file"); + } + return 0; +} // _ccp_ldir + +// ERA command - erases files +RUNCPM_DECL uint8 _ccp_era(void) { + if (_ccp_bdos(F_DELETE, ParFCB)) + _puts("\r\nNo file"); + return 0; } // _ccp_era -// TYPE command +// TYPE command - types a file to the console RUNCPM_DECL uint8 _ccp_type(void) { - uint8 i, c, l = 0, error = TRUE; - uint16 a, p = 0; - + uint8 i, c, l = 0, p = 0; + uint16 a = 0; + + _puts("\r\n"); if (!_ccp_bdos(F_OPEN, ParFCB)) { - _puts("\r\n"); - + while (!_ccp_bdos(F_READ, ParFCB)) { i = 128; a = dmaAddr; - + while (i) { c = _RamRead(a); - if (c == 0x1a) { + if (c == 0x1a) break; - } _ccp_bdos(C_WRITE, c); if (c == 0x0a) { ++l; - if (pgSize && (l == pgSize)) { + if (pageSize && (l == pageSize)) { l = 0; - p = _ccp_bdos(C_READ, 0x0000); - if (p == 3) { + _ccp_askForKey(); + p = HIGH_REGISTER(AF); + if (p == 3) break; - } } } --i; ++a; } - if (p == 3) { + if (p == 3) break; - } } - error = FALSE; + } else { + _puts("No file"); } - return (error); + return 0; } // _ccp_type -// SAVE command +// SAVE command - saves memory pages to a file RUNCPM_DECL uint8 _ccp_save(void) { uint8 error = TRUE; uint16 pages = _ccp_fcbtonum(); uint16 i, dma; - - if (pages < 256) { + + if (pages > 0 && pages < 256) { error = FALSE; - - while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces - ++pbuf; - --blen; + + while (_RamRead(cmdBufferPtr) == ' ' && bufferLen) { // Skips any leading spaces + ++cmdBufferPtr; + --bufferLen; } _ccp_nameToFCB(SecFCB); // Loads file name onto the ParFCB + _puts("\r\n"); if (_ccp_bdos(F_MAKE, SecFCB)) { _puts("Err: create"); } else { @@ -333,13 +526,13 @@ RUNCPM_DECL uint8 _ccp_save(void) { } else { pages *= 2; // Calculates the number of CP/M blocks to write dma = defLoad; - _puts("\r\n"); - + for (i = 0; i < pages; i++) { - _ccp_bdos( F_DMAOFF, dma); - _ccp_bdos( F_WRITE, SecFCB); + _ccp_bdos(F_DMAOFF, dma); + _ccp_bdos(F_WRITE, SecFCB); dma += 128; - _ccp_bdos( C_WRITE, '.'); + if (i % 2) + _ccp_bdos(C_WRITE, '.'); } _ccp_bdos(F_CLOSE, SecFCB); } @@ -348,61 +541,206 @@ RUNCPM_DECL uint8 _ccp_save(void) { return (error); } // _ccp_save -// REN command -RUNCPM_DECL void _ccp_ren(void) { +// REN command - renames a file +RUNCPM_DECL uint8 _ccp_ren(void) { uint8 ch, i; - - ++pbuf; - + + ++cmdBufferPtr; + --bufferLen; + _ccp_nameToFCB(SecFCB); - - for (i = 0; i < 12; ++i) { // Swap the filenames on the fcb + + for (i = 0; i < 12; ++i) { // Swap the filenames on the fcb ch = _RamRead(ParFCB + i); - _RamWrite( ParFCB + i, _RamRead(SecFCB + i)); - _RamWrite( SecFCB + i, ch); + _RamWrite(ParFCB + i, _RamRead(SecFCB + i)); + _RamWrite(SecFCB + i, ch); } - if (_ccp_bdos(F_RENAME, ParFCB)) { + if (_ccp_bdos(F_RENAME, ParFCB)) _puts("\r\nNo file"); - } + return 0; } // _ccp_ren -// USER command +// USER command - changes user area RUNCPM_DECL uint8 _ccp_user(void) { uint8 error = TRUE; - - curUser = (uint8)_ccp_fcbtonum(); - if (curUser < 16) { - _ccp_bdos(F_USERNUM, curUser); + + currentUser = (uint8)_ccp_fcbtonum(); + if (currentUser < 16) { + _ccp_bdos(F_USERNUM, currentUser); error = FALSE; } return (error); } // _ccp_user -// PAGE command +// CLS command - clears the screen +RUNCPM_DECL uint8 _ccp_cls(void) { + _clrscr(); + return (FALSE); +} // _ccp_cls + +// EXIT command - terminates RunCPM +RUNCPM_DECL uint8 _ccp_exit(void) { + _puts("\r\nTerminating RunCPM."); + _puts("\r\nCPU Halted."); + Status = STATUS_EXIT; + return (FALSE); +} // _ccp_exit + +// PAGE command - sets the paging size for TYPE and LDIR RUNCPM_DECL uint8 _ccp_page(void) { uint8 error = TRUE; uint16 r = _ccp_fcbtonum(); - + if (r < 256) { - pgSize = (uint8)r; + pageSize = (uint8)r; + _puts("\r\nPage size set to "); + _ccp_printDec(pageSize); + _puts(" lines"); + _puts("\r\n"); error = FALSE; } return (error); } // _ccp_page -// VOL command +// VER command - displays the CCP version +RUNCPM_DECL uint8 _ccp_ver(void) { + _puts(CCPHEAD); + return (FALSE); +} + +// DUMP command - dump memory or file in hex+ASCII, 128 bytes per screen, stop +// on ESC +RUNCPM_DECL uint8 _ccp_dump(void) { + uint8 param[17]; + uint8 i = 0, c; + uint16 addr = 0; + uint8 isHex = 1; + uint8 done = 0; + + // Extract parameter from ParFCB (filename or address) + for (i = 1; i < 13; ++i) { + c = _RamRead(ParFCB + i); + if (c == ' ' || c == 0) + break; + param[i - 1] = c; + } + param[i - 1] = 0; + + // Check if parameter is exactly 4 hex digits + if (i == 5) { + for (uint8 j = 0; j < 4; ++j) { + c = param[j]; + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'))) { + isHex = 0; + break; + } + } + } else { + isHex = 0; + } + + if (isHex) { + // Parse hex address + addr = 0; + for (uint8 j = 0; j < 4; ++j) { + c = param[j]; + addr <<= 4; + if (c >= '0' && c <= '9') + addr += c - '0'; + else if (c >= 'A' && c <= 'F') + addr += c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + addr += c - 'a' + 10; + } + // Dump memory + _puts("\r\n"); + while (!done) { + // Print address + _ccp_printHex16(addr); + _puts(": "); + + // Print hex bytes + // Optimization: Use direct pointer if available + uint8* ptr = (uint8*)_RamSysAddr(addr); + for (i = 0; i < 16; ++i) { + _ccp_printHex8(ptr[i]); + _ccp_bdos(C_WRITE, ' '); + } + _puts(" "); + // Print ASCII + for (i = 0; i < 16; ++i) { + uint8 b = ptr[i]; + _ccp_bdos(C_WRITE, (b >= 32 && b < 127) ? b : '.'); + } + _puts("\r\n"); + addr += 16; + if ((addr & 0x7F) == 0) { // Every 128 bytes, pause + _ccp_askForKey(); + if (HIGH_REGISTER(AF) == 3) // ^C + done = 1; + } + } + return 0; + } else { + // Assume file, try to open + if (_ccp_bdos(F_OPEN, ParFCB)) { + _puts("\r\nNo file"); + return 0; + } + _puts("\r\n"); + uint32 faddr = 0; + done = 0; + while (!done) { + // Read 128 bytes + if (_ccp_bdos(F_READ, ParFCB)) + break; + // Print 8 lines of 16 bytes + // Optimization: Use direct pointer to DMA buffer + uint8* dmaPtr = (uint8*)_RamSysAddr(dmaAddr); + for (uint8 l = 0; l < 8; ++l) { + // Print file offset (24-bit) + _ccp_printHex8((faddr >> 16) & 0xFF); + _ccp_printHex16(faddr & 0xFFFF); + _puts(": "); + + // Print hex + for (i = 0; i < 16; ++i) { + _ccp_printHex8(dmaPtr[l * 16 + i]); + _ccp_bdos(C_WRITE, ' '); + } + _puts(" "); + // Print ASCII + for (i = 0; i < 16; ++i) { + uint8 b = dmaPtr[l * 16 + i]; + _ccp_bdos(C_WRITE, (b >= 32 && b < 127) ? b : '.'); + } + _puts("\r\n"); + faddr += 16; + } + // Pause every 128 bytes + _ccp_askForKey(); + if (HIGH_REGISTER(AF) == 3) // ^C + done = 1; + } + return 0; + } +} // _ccp_dump + +// VOL command - shows the volume INFO.TXT information RUNCPM_DECL uint8 _ccp_vol(void) { uint8 error = FALSE; - uint8 letter = _RamRead(ParFCB) ? '@' + _RamRead(ParFCB) : 'A' + curDrive; + uint8 letter = _RamRead(ParFCB) ? '@' + _RamRead(ParFCB) : 'A' + currentDrive; uint8 folder[5] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 0}; - uint8 filename[13] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 'I', 'N', 'F', 'O', '.', 'T', 'X', 'T', 0}; + uint8 filename[13] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 'I', 'N', 'F', + 'O', '.', 'T', 'X', 'T', 0}; uint8 bytesread; uint8 i, j; - + _puts("\r\nVolumes on "); _putcon(folder[0]); _puts(":\r\n"); - + for (i = 0; i < 16; ++i) { folder[2] = i < 10 ? i + 48 : i + 55; if (_sys_exists(folder)) { @@ -413,9 +751,9 @@ RUNCPM_DECL uint8 _ccp_vol(void) { bytesread = (uint8)_sys_readseq(filename, 0); if (!bytesread) { for (j = 0; j < 128; ++j) { - if ((_RamRead(dmaAddr + j) < 32) || (_RamRead(dmaAddr + j) > 126)) { + if ((_RamRead(dmaAddr + j) < 32) || + (_RamRead(dmaAddr + j) > 126)) break; - } _putcon(_RamRead(dmaAddr + j)); } } @@ -425,56 +763,434 @@ RUNCPM_DECL uint8 _ccp_vol(void) { return (error); } // _ccp_vol -#ifdef HASLUA +// COPY command - copies a file +// Usage: COPY +RUNCPM_DECL uint8 _ccp_copy(void) { + if (_RamRead(ParFCB + 1) == ' ') { + _puts("\r\nNo source"); + return 0; + } + if (_RamRead(SecFCB + 1) == ' ') { + _puts("\r\nNo dest"); + return 0; + } -// External (.LUA) command -RUNCPM_DECL uint8 _ccp_lua(void) { - uint8 error = TRUE; - uint8 found, drive, user = 0; - uint16 loadAddr = defLoad; + // Move SecFCB to CmdFCB to avoid corruption when opening ParFCB + // ParFCB (0x5C) overlaps SecFCB (0x6C) when used as a full FCB + for (uint8 i = 0; i < 16; ++i) { + _RamWrite(CmdFCB + i, _RamRead(SecFCB + i)); + } + // Initialize the rest of CmdFCB + for (uint8 i = 16; i < 36; ++i) { + _RamWrite(CmdFCB + i, 0); + } + + if (_ccp_bdos(F_OPEN, ParFCB) == 255) { + _puts("\r\nSource not found"); + return 0; + } - _RamWrite( CmdFCB + 9, 'L'); - _RamWrite( CmdFCB + 10, 'U'); - _RamWrite( CmdFCB + 11, 'A'); + _ccp_bdos(F_DELETE, CmdFCB); // Delete dest if exists - drive = _RamRead(CmdFCB); - found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified - if (!found) { // If not found - if (!drive) { // and the search was on the default drive - _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 - if (curUser) { - user = curUser; // Save the current user - _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 - } - found = !_ccp_bdos(F_OPEN, CmdFCB); - if (!found) { // If still not found then - if (curUser) { // If current user not = 0 - _RamWrite(CmdFCB, 0x00); // look on current drive user 0 - found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again - } + if (_ccp_bdos(F_MAKE, CmdFCB) == 255) { + _puts("\r\nDir full"); + return 0; + } + + if (_ccp_bdos(F_OPEN, CmdFCB) == 255) { + _puts("\r\nErr open dest"); + return 0; + } + + _puts("\r\nCopying..."); + + // Use 128 byte buffer at defDMA + while(TRUE) { + _ccp_bdos(F_DMAOFF, defDMA); + if (_ccp_bdos(F_READ, ParFCB) != 0) break; // EOF (or error, assumed EOF for now) + if (_ccp_bdos(F_WRITE, CmdFCB) != 0) { + _puts("\r\nDisk full"); + break; + } + } + + _ccp_bdos(F_CLOSE, CmdFCB); + _puts(" Done."); + return 0; +} // _ccp_copy + +// ECHO command - prints text to console +// Usage: ECHO +RUNCPM_DECL uint8 _ccp_echo(void) { + // Find the start of the arguments in the input buffer + // inBuf + 2 is the start of the command line + uint16 ptr = inBuf + 2; + + // Skip leading spaces + while (_RamRead(ptr) == ' ') ptr++; + + // Skip the command itself (ECHO) + while (_RamRead(ptr) != ' ' && _RamRead(ptr) != 0) ptr++; + + // Skip spaces after command + while (_RamRead(ptr) == ' ') ptr++; + + _puts("\r\n"); + + // Print the rest + while (_RamRead(ptr) != 0) { + _ccp_bdos(C_WRITE, _RamRead(ptr++)); + } + + return 0; +} // _ccp_echo + +// POKE command - writes a byte to memory +// Usage: POKE (hex) +RUNCPM_DECL uint8 _ccp_poke(void) { + uint16 addr = 0; + uint16 val = 0; + uint8 c, i; + + // Parse Address from ParFCB (first argument) + for (i = 1; i <= 4; ++i) { + c = _RamRead(ParFCB + i); + if (c == ' ') break; + addr <<= 4; + if (c >= '0' && c <= '9') addr += (c - '0'); + else if (c >= 'A' && c <= 'F') addr += (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') addr += (c - 'a' + 10); + } + + // Parse Value from SecFCB (second argument) + for (i = 1; i <= 2; ++i) { + c = _RamRead(SecFCB + i); + if (c == ' ') break; + val <<= 4; + if (c >= '0' && c <= '9') val += (c - '0'); + else if (c >= 'A' && c <= 'F') val += (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') val += (c - 'a' + 10); + } + + _RamWrite(addr, (uint8)val); + _puts("\r\nOK"); + return 0; +} // _ccp_poke + +#ifdef CPM3 +// --- DATE command (CP/M 3 only) ------------------------------------------ +// Displays and sets the system date and time. All date/time access goes +// strictly through BDOS T_GET (105) and T_SET (104). + +static const char *_ccp_wday[7] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; +static const uint8 _ccp_dmlen[12] = {31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + +static uint8 _ccp_isleap(uint16 y) { + return (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)); +} + +// Converts a day count (1 = 1978-01-01) into year/month/day +static void _ccp_dayToYMD(uint16 days, uint16 *year, uint8 *month, uint8 *day) { + uint16 y = 1978; + uint8 m; + days -= 1; + for (;;) { + uint16 yl = _ccp_isleap(y) ? 366 : 365; + if (days < yl) + break; + days -= yl; + ++y; + } + for (m = 0; m < 12; ++m) { + uint8 dm = _ccp_dmlen[m]; + if (m == 1 && _ccp_isleap(y)) + dm = 29; + if (days < dm) + break; + days -= dm; + } + *year = y; + *month = m + 1; + *day = (uint8)days + 1; +} + +// Converts year/month/day into a day count (1 = 1978-01-01) +static uint16 _ccp_ymdToDay(uint16 year, uint8 month, uint8 day) { + uint32 days = 0; + uint16 yy; + uint8 mm; + for (yy = 1978; yy < year; ++yy) + days += _ccp_isleap(yy) ? 366 : 365; + for (mm = 1; mm < month; ++mm) { + days += _ccp_dmlen[mm - 1]; + if (mm == 2 && _ccp_isleap(year)) + days += 1; + } + days += day - 1; + return ((uint16)(days + 1)); +} + +// Prints a packed-BCD byte as two decimal digits +static void _ccp_printBCD(uint8 b) { + _ccp_bdos(C_WRITE, '0' + ((b >> 4) & 0x0F)); + _ccp_bdos(C_WRITE, '0' + (b & 0x0F)); +} + +// Reads the current date/time via BDOS and prints "Ddd MM/DD/YY HH:MM:SS" +static void _ccp_dateShow(void) { + uint16 dat = defDMA; // scratch DAT buffer in the DMA page + uint16 days, year; + uint8 month, day; + + _ccp_bdos(T_GET, dat); + days = _RamRead(dat) | (_RamRead(dat + 1) << 8); + _ccp_dayToYMD(days, &year, &month, &day); + _puts((char *)_ccp_wday[(days - 1) % 7]); // 1978-01-01 was a Sunday + _ccp_bdos(C_WRITE, ' '); + _ccp_print2(month); + _ccp_bdos(C_WRITE, '/'); + _ccp_print2(day); + _ccp_bdos(C_WRITE, '/'); + _ccp_print2(year % 100); + _ccp_bdos(C_WRITE, ' '); + _ccp_printBCD(_RamRead(dat + 2)); // hour + _ccp_bdos(C_WRITE, ':'); + _ccp_printBCD(_RamRead(dat + 3)); // minute + _ccp_bdos(C_WRITE, ':'); + _ccp_printBCD(_RamRead(dat + 4)); // second +} + +// Extracts up to 'max' decimal numbers from string s into out[] +static uint8 _ccp_parseNums(const char *s, uint8 *out, uint8 max) { + uint8 n = 0; + while (*s && n < max) { + if (*s >= '0' && *s <= '9') { + uint16 v = 0; + while (*s >= '0' && *s <= '9') { + v = v * 10 + (*s - '0'); + ++s; } + out[n++] = (uint8)v; + } else { + ++s; } } - if (found) { - _puts("\r\n"); - - _ccp_bdos(F_RUNLUA, CmdFCB); - if (user) { // If a user was selected - _ccp_bdos(F_USERNUM, curUser); // Set it back - user = 0; + return (n); +} + +// Validates and sets the date/time via BDOS T_SET. Returns 0 on success. +static uint8 _ccp_dateSet(uint8 mo, uint8 dy, uint8 yr, uint8 hh, uint8 mi, + uint8 ss) { + uint16 dat = defDMA; + uint16 year, days; + + if (mo < 1 || mo > 12 || dy < 1 || dy > 31 || hh > 23 || mi > 59 || + ss > 59) + return (1); + year = (yr >= 78) ? (1900 + yr) : (2000 + yr); + days = _ccp_ymdToDay(year, mo, dy); + _RamWrite(dat, days & 0xFF); + _RamWrite(dat + 1, (days >> 8) & 0xFF); + _RamWrite(dat + 2, ((hh / 10) << 4) | (hh % 10)); + _RamWrite(dat + 3, ((mi / 10) << 4) | (mi % 10)); + _RamWrite(dat + 4, ((ss / 10) << 4) | (ss % 10)); + // Per the manual, wait for a keystroke so the time can be set precisely + _puts("\r\nPress any key to set time"); + _ccp_bios(B_CONIN); + _ccp_bdos(T_SET, dat); + return (0); +} + +// Reads a line from the console into buf (NUL-terminated) +static void _ccp_readLine(char *buf, uint8 maxlen) { + uint8 n, k; + _RamWrite(inBuf, maxlen); + _ccp_bdos(C_READSTR, inBuf); + n = _RamRead(inBuf + 1); + for (k = 0; k < n && k < maxlen; ++k) + buf[k] = _RamRead(inBuf + 2 + k); + buf[k] = 0; +} + +// DATE command +RUNCPM_DECL uint8 _ccp_date(void) { + char arg[64]; + uint8 len = _RamRead(defDMA); + uint8 i = 0, j = 0; + uint8 nums[6]; + + // defDMA holds the command tail (the command word is already stripped), + // uppercased. Skip the leading spaces and copy the remaining argument. + while (i < len && _RamRead(defDMA + 1 + i) == ' ') + ++i; + while (i < len && j < (uint8)(sizeof(arg) - 1)) + arg[j++] = _RamRead(defDMA + 1 + i++); + arg[j] = 0; + + _puts("\r\n"); + + if (j == 0) { // DATE -> display once + _ccp_dateShow(); + return (0); + } + + if (arg[0] == 'C') { // DATE C / CONTINUOUS -> display until a key is pressed + uint8 lastSec = 0xFF; + while (!_ccp_bdos(C_STAT, 0)) { + _ccp_bdos(T_GET, defDMA); + uint8 sec = _RamRead(defDMA + 4); + if (sec != lastSec) { + lastSec = sec; + _ccp_bdos(C_WRITE, '\r'); + _ccp_dateShow(); + } } - _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was - cDrive = oDrive; // And restore cDrive - error = FALSE; + _ccp_bios(B_CONIN); // consume the key + return (0); } - if (user) { // If a user was selected - _ccp_bdos(F_USERNUM, curUser); // Set it back + + if (arg[0] == 'S' && arg[1] == 'E' && arg[2] == 'T') { // DATE SET + uint16 year; + uint8 mo, dy; + uint8 cd[3], ct[3]; + // Preload the current values so empty entries keep them + _ccp_bdos(T_GET, defDMA); + _ccp_dayToYMD(_RamRead(defDMA) | (_RamRead(defDMA + 1) << 8), &year, + &mo, &dy); + cd[0] = mo; + cd[1] = dy; + cd[2] = (uint8)(year % 100); + ct[0] = ((_RamRead(defDMA + 2) >> 4) & 0x0F) * 10 + + (_RamRead(defDMA + 2) & 0x0F); + ct[1] = ((_RamRead(defDMA + 3) >> 4) & 0x0F) * 10 + + (_RamRead(defDMA + 3) & 0x0F); + ct[2] = ((_RamRead(defDMA + 4) >> 4) & 0x0F) * 10 + + (_RamRead(defDMA + 4) & 0x0F); + _puts("Enter today's date (MM/DD/YY): "); + _ccp_readLine(arg, 20); + if (_ccp_parseNums(arg, nums, 3) >= 3) { + cd[0] = nums[0]; + cd[1] = nums[1]; + cd[2] = nums[2]; + } + _puts("\r\nEnter the time (HH:MM:SS): "); + _ccp_readLine(arg, 20); + if (_ccp_parseNums(arg, nums, 3) >= 3) { + ct[0] = nums[0]; + ct[1] = nums[1]; + ct[2] = nums[2]; + } + if (_ccp_dateSet(cd[0], cd[1], cd[2], ct[0], ct[1], ct[2])) { + _puts("\r\nInvalid date or time"); + return (0); + } + _puts("\r\n"); + _ccp_dateShow(); + return (0); } - _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was - - return (error); -} // _ccp_lua -#endif // ifdef HASLUA + + // Otherwise treat the tail as a time-specification MM/DD/YY HH:MM:SS + if (_ccp_parseNums(arg, nums, 6) < 6) { + _puts("Invalid date or time"); + return (0); + } + if (_ccp_dateSet(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5])) { + _puts("\r\nInvalid date or time"); + return (0); + } + _puts("\r\n"); + _ccp_dateShow(); + return (0); +} // _ccp_date +#endif // CPM3 + +#endif // Internals + +// ?/Help command +RUNCPM_DECL uint8 _ccp_hlp(void) { + _puts("\r\nCCP Commands:\r\n"); + _puts(" ? - Shows this list of commands\r\n"); + _puts(" CLS - Clears the screen\r\n"); + _puts(" COPY - Copies a file\r\n"); +#ifdef CPM3 + _puts(" DATE [spec|C|SET] - Shows or sets date/time (MM/DD/YY HH:MM:SS)\r\n"); +#endif + _puts(" DEL [] - Alias to ERA\r\n"); + _puts(" DIR [] - Lists file directory\r\n"); + _puts(" DUMP - Hex+ASCII dump of memory or file\r\n"); + _puts(" addr = 4 hex digits\r\n"); + _puts(" ECHO - Prints text to console\r\n"); + _puts(" ERA [] - Erases files\r\n"); + _puts(" EXIT - Terminates RunCPM\r\n"); + _puts(" LDIR [] [/C] - Lists file directory with sizes\r\n"); + _puts(" /C option includes 16 bit checksum\r\n"); + _puts(" PAGE [] - Sets the paging size for TYPE and LDIR\r\n"); + _puts(" n = 0 to 255, 0 disables paging\r\n"); + _puts(" POKE - Writes a byte to memory (hex)\r\n"); + _puts(" REN = - Renames files\r\n"); + _puts(" SAVE - Saves memory pages (256 bytes) to file\r\n"); + _puts(" TYPE - Displays file contents\r\n"); + _puts(" USER - Changes user area\r\n"); + _puts(" VER - Displays the current CCP version\r\n"); + _puts(" VOL [] - Shows the volume INFO.TXT information\r\n"); + return (FALSE); +} + +// List of CCP commands +static const CcpCommand Commands[] = { +#ifdef Internals + // Standard CP/M commands + {"DIR", _ccp_dir}, + {"ERA", _ccp_era}, + {"TYPE", _ccp_type}, + {"SAVE", _ccp_save}, + {"REN", _ccp_ren}, + {"USER", _ccp_user}, + + // Extra CCP commands + {"CLS", _ccp_cls}, + {"COPY", _ccp_copy}, + {"LDIR", _ccp_ldir}, + {"DEL", _ccp_era}, + {"ECHO", _ccp_echo}, + {"EXIT", _ccp_exit}, + {"PAGE", _ccp_page}, + {"POKE", _ccp_poke}, + {"VER", _ccp_ver}, + {"DUMP", _ccp_dump}, + {"VOL", _ccp_vol}, +#ifdef CPM3 + {"DATE", _ccp_date}, +#endif +#endif + {"?", _ccp_hlp}, + {NULL, NULL} // Sentinel +}; + +// Gets the command pointer +RUNCPM_DECL const CcpCommand *_ccp_cnum(void) { + uint8 command[9]; + uint8 i = 0; + + if (!_RamRead(CmdFCB)) { // If a drive was set, then the command is external + while (i < 8 && _RamRead(CmdFCB + i + 1) != ' ') { + command[i] = _RamRead(CmdFCB + i + 1); + ++i; + } + command[i] = 0; + i = 0; + while (Commands[i].name) { + if (_ccp_strEqual((char *)command, Commands[i].name)) { + errorPtr = defDMA + 2; + return &Commands[i]; + } + ++i; + } + } + return NULL; // External command +} // _ccp_cnum // External (.COM) command RUNCPM_DECL uint8 _ccp_ext(void) { @@ -483,32 +1199,30 @@ RUNCPM_DECL uint8 _ccp_ext(void) { uint16 loadAddr = defLoad; bool wasBlank = (_RamRead(CmdFCB + 9) == ' '); - //printf("\n\rwasBlank: %s", wasBlank ? "True" : "False"); - bool wasSUB = ((_RamRead(CmdFCB + 9) == 'S') && - (_RamRead(CmdFCB + 10) == 'U') && - (_RamRead(CmdFCB + 11) == 'B')); - //printf("\r\nwasSUB: %s", wasSUB ? "True" : "False"); + bool wasSUB = + ((_RamRead(CmdFCB + 9) == 'S') && (_RamRead(CmdFCB + 10) == 'U') && + (_RamRead(CmdFCB + 11) == 'B')); if (!wasSUB) { if (wasBlank) { - //first look for a .COM file - _RamWrite(CmdFCB + 9, 'C'); + _RamWrite(CmdFCB + 9, 'C'); // first look for a .COM file _RamWrite(CmdFCB + 10, 'O'); _RamWrite(CmdFCB + 11, 'M'); } - drive = _RamRead(CmdFCB); // Get the drive from the command FCB - found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified - if (!found) { // If not found - if (!drive) { // and the search was on the default drive - _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 - if (curUser) { - user = curUser; // Save the current user - _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 + drive = _RamRead(CmdFCB); // Get the drive from the command FCB + found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB + // drive, current or specified + if (!found) { // If not found + if (!drive) { // and the search was on the default drive + _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 + if (currentUser) { + user = currentUser; // Save the current user + _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then - if (curUser) { // If current user not = 0 + if (currentUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } @@ -516,31 +1230,31 @@ RUNCPM_DECL uint8 _ccp_ext(void) { } } if (!found) { - _RamWrite(CmdFCB, drive); // restore previous drive - _ccp_bdos(F_USERNUM, curUser); // restore to previous user + _RamWrite(CmdFCB, drive); // restore previous drive + _ccp_bdos(F_USERNUM, currentUser); // restore to previous user } } - //if .COM not found then look for a .SUB file - if ((wasBlank || wasSUB) && !found && !sFlag) { //don't auto-submit while executing a submit file - //_puts("\n\rLooking for .SUB file"); - + // if .COM not found then look for a .SUB file + if ((wasBlank || wasSUB) && !found && + !submitFlag) { // don't auto-submit while executing a submit file _RamWrite(CmdFCB + 9, 'S'); _RamWrite(CmdFCB + 10, 'U'); _RamWrite(CmdFCB + 11, 'B'); - - drive = _RamRead(CmdFCB); // Get the drive from the command FCB - found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified - if (!found) { // If not found - if (!drive) { // and the search was on the default drive - _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 - if (curUser) { - user = curUser; // Save the current user - _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 + + drive = _RamRead(CmdFCB); // Get the drive from the command FCB + found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB + // drive, current or specified + if (!found) { // If not found + if (!drive) { // and the search was on the default drive + _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 + if (currentUser) { + user = currentUser; // Save the current user + _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then - if (curUser) { // If current user not = 0 + if (currentUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } @@ -548,107 +1262,134 @@ RUNCPM_DECL uint8 _ccp_ext(void) { } } if (!found) { - _RamWrite(CmdFCB, drive); // restore previous drive - _ccp_bdos(F_USERNUM, curUser); // restore to previous user + _RamWrite(CmdFCB, drive); // restore previous drive + _ccp_bdos(F_USERNUM, currentUser); // restore to previous user } if (found) { - //_puts(".SUB file found!\r\n"); + //_puts(".SUB file found!\n"); + int i; + + // move FCB's (CmdFCB --> ParFCB --> SecFCB) + // Optimization: Use direct pointers for FCB copying + uint8* secPtr = (uint8*)_RamSysAddr(SecFCB); + uint8* parPtr = (uint8*)_RamSysAddr(ParFCB); + uint8* cmdPtr = (uint8*)_RamSysAddr(CmdFCB); - //move FCB's (CmdFCB --> ParFCB --> SecFCB) - for (int i = 0; i < 16; i++) { - //ParFCB to SecFCB - _RamWrite(SecFCB + i, _RamRead(ParFCB + i)); - //CmdFCB to ParFCB - _RamWrite(ParFCB + i, _RamRead(CmdFCB + i)); + for (i = 0; i < 16; i++) { + // ParFCB to SecFCB + secPtr[i] = parPtr[i]; + // CmdFCB to ParFCB + parPtr[i] = cmdPtr[i]; } // (Re)Initialize the CmdFCB _ccp_initFCB(CmdFCB, 36); - - //put 'SUBMIT.COM' in CmdFCB + + // put 'SUBMIT.COM' in CmdFCB const char *str = "SUBMIT COM"; int s = (int)strlen(str); - for (int i = 0; i < s; i++) { + for (i = 0; i < s; i++) _RamWrite(CmdFCB + i + 1, str[i]); - } - - //now try to find SUBMIT.COM file - found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified - if (!found) { // If not found - if (!drive) { // and the search was on the default drive - _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 - if (curUser) { - user = curUser; // Save the current user - _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 + + // now try to find SUBMIT.COM file + found = + !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB + // drive, current or specified + if (!found) { // If not found + if (!drive) { // and the search was on the default drive + _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 + if (currentUser) { + user = currentUser; // Save the current user + _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); - if (!found) { // If still not found then - if (curUser) { // If current user not = 0 - _RamWrite(CmdFCB, 0x00); // look on current drive user 0 + if (!found) { // If still not found then + if (currentUser) { // If current user not = 0 + _RamWrite(CmdFCB, + 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } } } } + if (found) { + // insert "@" into command buffer + // note: this is so the rest will be parsed correctly + bufferLen = _RamRead(defDMA); + if (bufferLen < cmdLen) { + bufferLen++; + _RamWrite(defDMA, bufferLen); + } + uint8 lc = '@'; + for (i = 0; i < bufferLen; i++) { + uint8 nc = _RamRead(defDMA + 1 + i); + _RamWrite(defDMA + 1 + i, lc); + lc = nc; + } + } } } - - if (found) { // Program was found somewhere + + if (found) { // Program was found somewhere _puts("\r\n"); - _ccp_bdos(F_DMAOFF, loadAddr); // Sets the DMA address for the loading - while (!_ccp_bdos(F_READ, CmdFCB)) { // Loads the program into memory + _ccp_bdos(F_DMAOFF, loadAddr); // Sets the DMA address for the loading + while (!_ccp_bdos(F_READ, CmdFCB)) { // Loads the program into memory loadAddr += 128; - if (loadAddr == BDOSjmppage) { // Breaks if it reaches the end of TPA + if (loadAddr == + BDOSjmppage) { // Breaks if it reaches the end of TPA _puts("\r\nNo Memory"); break; } - _ccp_bdos(F_DMAOFF, loadAddr); // Points the DMA offset to the next loadAddr + _ccp_bdos(F_DMAOFF, + loadAddr); // Points the DMA offset to the next loadAddr } - _ccp_bdos(F_DMAOFF, defDMA); // Points the DMA offset back to the default - - if (user) { // If a user was selected - _ccp_bdos(F_USERNUM, curUser); // Set it back + _ccp_bdos(F_DMAOFF, + defDMA); // Points the DMA offset back to the default + + if (user) { // If a user was selected + _ccp_bdos(F_USERNUM, currentUser); // Set it back user = 0; } - _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was - cDrive = oDrive; // And restore cDrive - + _RamWrite(CmdFCB, + drive); // Set the command FCB drive back to what it was + cDrive = oDrive; // And restore cDrive + // Place a trampoline to call the external command // as it may return using RET instead of JP 0000h loadAddr = Trampoline; - _RamWrite(loadAddr, CALL); // CALL 0100h + _RamWrite(loadAddr, CALL); // CALL 0100h _RamWrite16(loadAddr + 1, defLoad); - _RamWrite(loadAddr + 3, JP); // JP RETTOCCP - _RamWrite16(loadAddr + 4, BIOSjmppage + 0x33); - - Z80reset(); // Resets the Z80 CPU - SET_LOW_REGISTER(BC, _RamRead(DSKByte)); // Sets C to the current drive/user - PC = loadAddr; // Sets CP/M application jump point - SP = BDOSjmppage; // Sets the stack to the top of the TPA - - Z80run(); // Starts Z80 simulation - + _RamWrite(loadAddr + 3, JP); // JP USERF + _RamWrite16(loadAddr + 4, BIOSjmppage + B_USERF); + + Z80reset(); // Resets the Z80 CPU + SET_LOW_REGISTER(BC, + _RamRead(DSKByte)); // Sets C to the current drive/user + PC = loadAddr; // Sets CP/M application jump point + SP = BDOSjmppage; // Sets the stack to the top of the TPA + + Z80run(cpuDelayInstructions); // Starts Z80 simulation + PC = 0; // Resets the PC/SP after command execution + SP = 0; + error = FALSE; } - - if (user) { // If a user was selected - _ccp_bdos(F_USERNUM, curUser); // Set it back - } - _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was - - return(error); + + if (user) // If a user was selected + _ccp_bdos(F_USERNUM, currentUser); // Set it back + _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was + + return (error); } // _ccp_ext // Prints a command error RUNCPM_DECL void _ccp_cmdError() { uint8 ch; - + _puts("\r\n"); - - while ((ch = _RamRead(perr++))) { - if (ch == ' ') { + while ((ch = _RamRead(errorPtr++))) { + if (ch == ' ') break; - } _ccp_bdos(C_WRITE, toupper(ch)); } _puts("?\r\n"); @@ -658,240 +1399,233 @@ RUNCPM_DECL void _ccp_cmdError() { RUNCPM_DECL void _ccp_readInput(void) { uint8 i; uint8 chars; - - if (sFlag) { // Are we running a submit? - if (!sRecs) { // Are we already counting? - _ccp_bdos(F_OPEN, BatchFCB); // Open the batch file - sRecs = _RamRead(BatchFCB + 15); // Gets its record count - } - --sRecs; // Counts one less - _RamWrite(BatchFCB + 32, sRecs); // And sets to be the next read - _ccp_bdos( F_DMAOFF, defDMA); // Reset current DMA - _ccp_bdos( F_READ, BatchFCB); // And reads the last sector - chars = _RamRead(defDMA); // Then moves it to the input buffer - - for (i = 0; i <= chars; ++i) { - _RamWrite(inBuf + i + 1, _RamRead(defDMA + i)); + + if (submitFlag) { // Are we running a submit? + if (!submitRecords) { // Are we already counting? + _ccp_bdos(F_OPEN, BatchFCB); // Open the batch file + submitRecords = _RamRead(BatchFCB + 15); // Gets its record count } + --submitRecords; // Counts one less + _RamWrite(BatchFCB + 32, submitRecords); // And sets to be the next read + _ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA + _ccp_bdos(F_READ, BatchFCB); // And reads the last sector + chars = _RamRead(defDMA); // Then moves it to the input buffer + + for (i = 0; i <= chars; ++i) + _RamWrite(inBuf + i + 1, _RamRead(defDMA + i)); _RamWrite(inBuf + i + 1, 0); _puts((char *)_RamSysAddr(inBuf + 2)); - if (!sRecs) { - _ccp_bdos(F_DELETE, BatchFCB); // Deletes the submit file - sFlag = FALSE; // and clears the submit flag + if (!submitRecords) { + _ccp_bdos(F_DELETE, BatchFCB); // Deletes the submit file + submitFlag = FALSE; // and clears the submit flag } } else { - _ccp_bdos(C_READSTR, inBuf); // Reads the command line from console + _ccp_bdos(C_READSTR, inBuf); // Reads the command line from console + if (Debug) + Z80run(cpuDelayInstructions); } } // _ccp_readInput +// Parses the command line for drive/user changes (e.g., A:, 0:, A0:) +// Returns TRUE if a drive/user change was processed, FALSE otherwise +// Sets errorFlag if an invalid user was specified +RUNCPM_DECL bool _ccp_parseDriveUser(bool *errorFlag) { + uint8 i; + uint8 ch, tDrive = 0, tUser = currentUser, u = 0; + + *errorFlag = FALSE; + + for (i = 0; i < bufferLen; i++) { + ch = toupper(_RamRead(cmdBufferPtr + i)); + if ((ch >= 'A') && (ch <= 'P')) { + if (tDrive) { // if we've already specified a new drive + return FALSE; // not a DU: command + } else { + tDrive = ch - '@'; + } + } else if ((ch >= '0') && (ch <= '9')) { + tUser = u = (u * 10) + (ch - '0'); + } else if (ch == ':') { + if (i == bufferLen - 1) { // if we at the end of the command line + if (tUser >= 16) { // if invalid user + *errorFlag = TRUE; + return FALSE; + } + if (tDrive != 0) { + cDrive = oDrive = tDrive - 1; + _RamWrite(DSKByte, + (_RamRead(DSKByte) & 0xf0) | cDrive); + _ccp_bdos(DRV_SET, cDrive); + if (Status) + currentDrive = 0; + } + if (tUser != currentUser) { + currentUser = tUser; + _ccp_bdos(F_USERNUM, currentUser); + } + return TRUE; + } + return FALSE; + } else { // invalid character + return FALSE; // don't error; may be valid (non-DU:) command + } + } + return FALSE; +} + // Main CCP code RUNCPM_DECL void _ccp(void) { uint8 i; - - sFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000); - _ccp_bdos(DRV_SET, curDrive); - + + submitFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000); + _ccp_bdos(DRV_SET, currentDrive); + for (i = 0; i < 36; ++i) { _RamWrite(BatchFCB + i, _RamRead(tmpFCB + i)); } - + + // Loads an autoexec file if it exists and this is the first boot + // The file contents are loaded at ccpAddr+8 up to 126 bytes then the size + // loaded is stored at ccpAddr+7 +#ifdef CPM3 + if (chainLoad) { + // A program chained to a command via BDOS 47 (Chain To Program). + // Run that command before anything else. + uint16 cmd = inBuf + 2; + bufferLen = 0; + while (chainCmd[bufferLen] && bufferLen < 125) { + _RamWrite(cmd + bufferLen, chainCmd[bufferLen]); + ++bufferLen; + } + _RamWrite(cmd + bufferLen, 0x00); + _RamWrite(inBuf, cmdLen); + _RamWrite(inBuf + 1, bufferLen); + chainLoad = 0; + } else +#endif + if (firstBoot && !submitFlag) { + if (_sys_exists((uint8 *)AUTOEXEC)) { + uint16 cmd = inBuf + 2; + uint8 bytesread = (uint8)_RamLoad((uint8 *)AUTOEXEC, cmd, 125); + bufferLen = 0; + while (bufferLen < bytesread && _RamRead(cmd + bufferLen) > 31) + bufferLen++; + _RamWrite(cmd + bufferLen, 0x00); + _RamWrite(--cmd, bufferLen); + } else { + bufferLen = 0; + } + if (BOOTONLY) + firstBoot = FALSE; + } else { + _RamWrite(inBuf, 0); // Clears the buffer + _RamWrite(inBuf + 1, 0); // Clears the buffer + bufferLen = 0; + } + while (TRUE) { - curDrive = (uint8)_ccp_bdos(DRV_GET, 0x0000); // Get current drive - curUser = (uint8)_ccp_bdos(F_USERNUM, 0x00FF); // Get current user - _RamWrite(DSKByte, (curUser << 4) + curDrive); // Set user/drive on addr DSKByte - - parDrive = curDrive; // Initially the parameter drive is the same as the current drive - - snprintf((char *) prompt, sizeof(prompt), "\r\n%c%u%c", 'A' + curDrive, curUser, sFlag ? '$' : '>'); - _puts((char *)prompt); - - _RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line - _ccp_readInput(); - - blen = _RamRead(inBuf + 1); // Obtains the number of bytes read - - _ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA - if (blen) { - _RamWrite(inBuf + 2 + blen, 0); // "Closes" the read buffer with a \0 - pbuf = inBuf + 2; // Points pbuf to the first command character - - while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces - ++pbuf; - --blen; + currentDrive = (uint8)_ccp_bdos(DRV_GET, 0x0000); // Get current drive + currentUser = (uint8)_ccp_bdos(F_USERNUM, 0x00FF); // Get current user + _RamWrite(DSKByte, + (currentUser << 4) + currentDrive); // Set user/drive on addr DSKByte + + paramDrive = currentDrive; // Initially the parameter drive is the same as the + // current drive + + sprintf((char *)prompt, "\r\n%c%u%c", 'A' + currentDrive, currentUser, + submitFlag ? '$' : '>'); + if (!bufferLen) { + _puts((char *)prompt); + + _RamWrite(inBuf, + cmdLen); // Sets the buffer size to read the command line + _ccp_readInput(); + if (Status == STATUS_RETURN) + Status = STATUS_RUNNING; + bufferLen = _RamRead(inBuf + 1); // Obtains the number of bytes read + } + + _ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA + if (bufferLen) { + _RamWrite(inBuf + 2 + bufferLen, + 0); // "Closes" the read buffer with a \0 + cmdBufferPtr = inBuf + 2; // Points cmdBufferPtr to the first command character + + while (_RamRead(cmdBufferPtr) == ' ' && bufferLen) { // Skips any leading spaces + ++cmdBufferPtr; + --bufferLen; } - if (!blen) { // There were only spaces + if (!bufferLen) // There were only spaces continue; - } - if (_RamRead(pbuf) == ';') { // Found a comment line + if (_RamRead(cmdBufferPtr) == ';') { // Found a comment line + bufferLen = 0; // Ignore the rest of the line continue; } - + // parse for DU: command line shortcut - bool errorFlag = FALSE, continueFlag = FALSE; - uint8 ch, tDrive = 0, tUser = curUser, u = 0; - - for (i = 0; i < blen; i++) { - ch = toupper(_RamRead(pbuf + i)); - if ((ch >= 'A') && (ch <= 'P')) { - if (tDrive) { // if we've already specified a new drive - break; // not a DU: command - } else { - tDrive = ch - '@'; - } - } else if ((ch >= '0') && (ch <= '9')) { - tUser = u = (u * 10) + (ch - '0'); - } else if (ch == ':') { - if (i == blen - 1) { // if we at the end of the command line - if (tUser >= 16) { // if invalid user - errorFlag = TRUE; - break; - } - if (tDrive != 0) { - cDrive = oDrive = tDrive - 1; - _RamWrite(DSKByte, (_RamRead(DSKByte) & 0xf0) | cDrive); - _ccp_bdos(DRV_SET, cDrive); - if (Status) { - curDrive = 0; - } - } - if (tUser != curUser) { - curUser = tUser; - _ccp_bdos(F_USERNUM, curUser); - } - continueFlag = TRUE; - } - break; - } else { // invalid character - break; // don't error; may be valid (non-DU:) command - } - } - if (errorFlag) { - _ccp_cmdError(); // print command error + bool errorFlag = FALSE; + if (_ccp_parseDriveUser(&errorFlag)) { + bufferLen = 0; // ignore the rest of the line continue; } - if (continueFlag) { + if (errorFlag) { + _ccp_cmdError(); // print command error + bufferLen = 0; // ignore the rest of the line continue; } - _ccp_initFCB(CmdFCB, 36); // Initializes the command FCB - - perr = pbuf; // Saves the pointer in case there's an error - if (_ccp_nameToFCB(CmdFCB) > 8) { // Extracts the command from the buffer - _ccp_cmdError(); // Command name cannot be non-unique or have an extension + + _ccp_initFCB(CmdFCB, 36); // Initializes the command FCB + + errorPtr = cmdBufferPtr; // Saves the pointer in case there's an error + if (_ccp_nameToFCB(CmdFCB) > + 8) { // Extracts the command from the buffer + _ccp_cmdError(); // Command name cannot be non-unique or have an + // extension + bufferLen = 0; // ignore the rest of the line continue; } - _RamWrite(defDMA, blen); // Move the command line at this point to 0x0080 - - for (i = 0; i < blen; ++i) { - _RamWrite(defDMA + i + 1, toupper(_RamRead(pbuf + i))); - } - - while (i++ < 127) { // "Zero" the rest of the DMA buffer + _RamWrite(defDMA, + bufferLen); // Move the command line at this point to 0x0080 + + for (i = 0; i < bufferLen; ++i) + _RamWrite(defDMA + i + 1, toupper(_RamRead(cmdBufferPtr + i))); + while (i++ < 127) // "Zero" the rest of the DMA buffer _RamWrite(defDMA + i, 0); + _ccp_initFCB(ParFCB, 18); // Initializes the parameter FCB + _ccp_initFCB(SecFCB, 18); // Initializes the secondary FCB + + while (_RamRead(cmdBufferPtr) == ' ' && bufferLen) { // Skips any leading spaces + ++cmdBufferPtr; + --bufferLen; } - _ccp_initFCB( ParFCB, 18); // Initializes the parameter FCB - _ccp_initFCB( SecFCB, 18); // Initializes the secondary FCB - - while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces - ++pbuf; - --blen; + _ccp_nameToFCB( + ParFCB); // Loads the next file parameter onto the parameter FCB + + while (_RamRead(cmdBufferPtr) == ' ' && bufferLen) { // Skips any leading spaces + ++cmdBufferPtr; + --bufferLen; } - _ccp_nameToFCB(ParFCB); // Loads the next file parameter onto the parameter FCB - - while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces - ++pbuf; - --blen; + _ccp_nameToFCB( + SecFCB); // Loads the next file parameter onto the secondary FCB + + i = FALSE; // Checks if the command is valid and executes + + const CcpCommand *cmd = _ccp_cnum(); + if (cmd) { + i = cmd->handler(); + } else { + i = _ccp_ext(); } - _ccp_nameToFCB(SecFCB); // Loads the next file parameter onto the secondary FCB - - i = FALSE; // Checks if the command is valid and executes - - switch (_ccp_cnum()) { - // Standard CP/M commands - case 0: { // DIR - _ccp_dir(); - break; - } - - case 1: { // ERA - _ccp_era(); - break; - } - - case 2: { // TYPE - i = _ccp_type(); - break; - } - - case 3: { // SAVE - i = _ccp_save(); - break; - } - - case 4: { // REN - _ccp_ren(); - break; - } - - case 5: { // USER - i = _ccp_user(); - break; - } - - // Extra CCP commands - case 6: { // CLS - _clrscr(); - break; - } - - case 7: { // DEL is an alias to ERA - _ccp_era(); - break; - } - - case 8: { // EXIT - _puts( "Terminating RunCPM.\r\n"); - _puts( "CPU Halted.\r\n"); - Status = 1; - break; - } - - case 9: { // PAGE - i = _ccp_page(); - break; - } - - case 10: { // VOL - i = _ccp_vol(); - break; - } - - // External/Lua commands - case 255: { // It is an external command - i = _ccp_ext(); -#ifdef HASLUA - if (i) { - i = _ccp_lua(); - } -#endif // ifdef HASLUA - break; - } - - default: { - i = TRUE; - break; - } - } // switch - cDrive = oDrive = curDrive; // Restore cDrive and oDrive - if (i) { + + cDrive = oDrive = currentDrive; // Restore cDrive and oDrive + if (i) _ccp_cmdError(); - } } - if ((Status == 1) || (Status == 2)) { + bufferLen = 0; + if ((Status == STATUS_EXIT) || (Status == STATUS_RESTART)) break; - } } _puts("\r\n"); } // _ccp #endif // ifndef CCP_H - diff --git a/lib/runcpm/console.h b/lib/runcpm/console.h index 7c473d84e..e710d5050 100644 --- a/lib/runcpm/console.h +++ b/lib/runcpm/console.h @@ -1,50 +1,138 @@ #ifndef CONSOLE_H #define CONSOLE_H -/* see main.c for definition */ - #ifndef RUNCPM_DECL #define RUNCPM_DECL #endif -RUNCPM_DECL uint8 mask8bit = 0x7f; // TO be used for masking 8 bit characters (XMODEM related) - // If set to 0x7f, RunCPM masks the 8th bit of characters sent - // to the console. This is the standard CP/M behavior. - // If set to 0xff, RunCPM passes 8 bit characters. This is - // required for XMODEM to work. - // Use the CONSOLE7 and CONSOLE8 programs to change this on the fly. +/* see main.c for definition */ + +RUNCPM_DECL uint8 mask8bit = 0x7f; // TO be used for masking 8 bit characters (XMODEM related) + // If set to 0x7f, RunCPM masks the 8th bit of characters sent + // to the console. This is the standard CP/M behavior. + // If set to 0xff, RunCPM passes 8 bit characters. This is + // required for XMODEM to work. + // Use the CONSOLE7 and CONSOLE8 programs to change this on the fly. -RUNCPM_DECL uint8 _chready(void) // Checks if there's a character ready for input +RUNCPM_DECL void _putcon(uint8 ch) // Puts a character { - return(_kbhit() ? 0xff : 0x00); +#ifdef BUILD_ATARI + /* FujiNet: translate a bare Form Feed (^L) to the VT100 home+clear that + * _clrscr() uses, so ^L-clear works on VT100/ANSI consoles. Only in 7-bit + * mode; 8-bit transparent (XMODEM, mask8bit==0xff) passes 0x0C through. */ + if (mask8bit == 0x7f && (ch & 0x7f) == 0x0C) + { + _putch(0x1B); _putch('['); _putch('1'); _putch(';'); + _putch('1'); _putch('H'); _putch(0x1B); _putch('['); + _putch('2'); _putch('J'); + return; + } +#endif +#ifdef STREAMIO + if (consoleOutputActive) + _putch(ch & mask8bit); + if (streamOutputFile) + fputc(ch & mask8bit, streamOutputFile); +#else + _putch(ch & mask8bit); +#endif } -RUNCPM_DECL uint8 _getchNB(void) // Gets a character, non-blocking, no echo +RUNCPM_DECL void _puts(const char *str) // Puts a \0 terminated string { - return(_kbhit() ? _getch() : 0x00); + while (*str) + _putcon(*(str++)); } -RUNCPM_DECL void _putcon(uint8 ch) // Puts a character +RUNCPM_DECL void _puthex8(uint8 c) // Puts a HH hex string { - _putch(ch & mask8bit); + _putcon(tohex(c >> 4)); + _putcon(tohex(c & 0x0f)); } -RUNCPM_DECL void _puts(const char* str) // Puts a \0 terminated string +RUNCPM_DECL void _puthex16(uint16 w) // puts a HHHH hex string { - while (*str) - _putcon(*(str++)); + _puthex8(w >> 8); + _puthex8(w & 0x00ff); +} + +#ifdef STREAMIO +RUNCPM_DECL int _nextStreamInChar; + +RUNCPM_DECL void _getNextStreamInChar(void) { + _nextStreamInChar = streamInputFile ? fgetc(streamInputFile) : EOF; + if (EOF == _nextStreamInChar) { + streamInputActive = FALSE; + } +} + +RUNCPM_DECL uint8 _getStreamInChar(void) { + uint8 result = _nextStreamInChar; + _getNextStreamInChar(); + // TODO: delegate to abstrction_posix.h + if (0x0a == result) + result = 0x0d; + return result; +} + +RUNCPM_DECL uint8 _getStreamInCharEcho() { + uint8 result = _getStreamInChar(); + _putcon(result); + return result; +} + +RUNCPM_DECL void _streamioInit(void) { + _getNextStreamInChar(); } -RUNCPM_DECL void _puthex8(uint8 c) // Puts a HH hex string +RUNCPM_DECL void _streamioReset(void) { + if (streamOutputFile) + fclose(streamOutputFile); +} +#endif + +RUNCPM_DECL uint8 _chready(void) // Checks if there's a character ready for input +{ +#ifdef STREAMIO + if (streamInputActive) + return 0xff; + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return (_kbhit() ? 0xff : 0x00); +} + +RUNCPM_DECL uint8 _getconNB(void) // Gets a character, non-blocking, no echo { - _putcon(tohex(c >> 4)); - _putcon(tohex(c & 0x0f)); +#ifdef STREAMIO + if (streamInputActive) + return _getStreamInChar(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return (_kbhit() ? _getch() : 0x00); } -RUNCPM_DECL void _puthex16(uint16 w) // puts a HHHH hex string +RUNCPM_DECL uint8 _getcon(void) // Gets a character, blocking, no echo { - _puthex8(w >> 8); - _puthex8(w & 0x00ff); +#ifdef STREAMIO + if (streamInputActive) + return _getStreamInChar(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return _getch(); } -#endif \ No newline at end of file +RUNCPM_DECL uint8 _getconE(void) // Gets a character, blocking, with echo +{ +#ifdef STREAMIO + if (streamInputActive) + return _getStreamInCharEcho(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return _getche(); +} + +#endif diff --git a/lib/runcpm/cpm.h b/lib/runcpm/cpm.h index a0ef94345..29e258a2e 100755 --- a/lib/runcpm/cpm.h +++ b/lib/runcpm/cpm.h @@ -1,53 +1,138 @@ -#ifndef CPM_H + #ifndef CPM_H #define CPM_H -#include "printer.h" - #ifndef RUNCPM_DECL #define RUNCPM_DECL #endif +/* FujiNet: printer routing for BDOS C=5 (L_WRITE), see below. */ +#include "printer.h" + +enum eBIOSFunc { + // CP/M 2.2 Stuff + B_BOOT = 0, + B_WBOOT = 3, + B_CONST = 6, + B_CONIN = 9, + B_CONOUT = 12, + B_LIST = 15, + B_AUXOUT = 18, + B_READER = 21, + B_HOME = 24, + B_SELDSK = 27, + B_SETTRK = 30, + B_SETSEC = 33, + B_SETDMA = 36, + B_READ = 39, + B_WRITE = 42, + B_LISTST = 45, + B_SECTRAN = 48, + // CP/M 3.0 Stuff + B_CONOST = 51, + B_AUXIST = 54, + B_AUXOST = 57, + B_DEVTBL = 60, + B_DEVINI = 63, + B_DRVTBL = 66, + B_MULTIO = 69, + B_FLUSH = 72, + B_MOVE = 75, + B_TIME = 78, + B_SELMEM = 81, + B_SETBNK = 84, + B_XMOVE = 87, + B_USERF = 90, // Used by internal CCP to return to prompt + B_RESERV1 = 93, + B_RESERV2 = 96 +}; + enum eBDOSFunc { - F_BOOT = 0, - C_READ = 1, - C_WRITE = 2, - READER_IN = 3, - PUNCH_OUT = 4, - PRINT_OUT = 5, - DIRECT_IO = 6, - GET_IOBYTE = 7, - SET_IOBYTE = 8, - OUT_STRING = 9, - C_READSTR = 10, - C_STAT = 11, - GET_VERSION = 12, - DRV_ALLRESET = 13, - DRV_SET = 14, - F_OPEN = 15, - F_CLOSE = 16, - F_SEARCH_FIRST = 17, - F_SEARCH_NEXT = 18, - F_DELETE = 19, - F_READ = 20, - F_WRITE = 21, - F_MAKE = 22, - F_RENAME = 23, - DRV_LOGINVECTOR = 24, - DRV_GET = 25, - F_DMAOFF = 26, - DRV_GETADDRALLOC = 27, - DRV_WRITEPROTECT = 28, - DRV_GETROVECTOR = 29, - F_SETATTRIBUTES = 30, - DRV_GETDPB = 31, - F_USERNUM = 32, - F_READRANDOM = 33, - F_WRITERANDOM = 34, - F_COMPUTESIZE = 35, - F_SETRANDOM = 36, - DRV_RESET = 37, - F_WRITERANDOMZERO = 40, - F_RUNLUA = 254 + // CP/M 2.2 Stuff + P_TERMCPM = 0, + C_READ = 1, + C_WRITE = 2, + A_READ = 3, + A_WRITE = 4, + L_WRITE = 5, + C_RAWIO = 6, + A_STATIN = 7, + A_STATOUT = 8, + C_WRITESTR = 9, + C_READSTR = 10, + C_STAT = 11, + S_BDOSVER = 12, + DRV_ALLRESET = 13, + DRV_SET = 14, + F_OPEN = 15, + F_CLOSE = 16, + F_SFIRST = 17, + F_SNEXT = 18, + F_DELETE = 19, + F_READ = 20, + F_WRITE = 21, + F_MAKE = 22, + F_RENAME = 23, + DRV_LOGINVEC = 24, + DRV_GET = 25, + F_DMAOFF = 26, + DRV_ALLOCVEC = 27, + DRV_SETRO = 28, + DRV_ROVEC = 29, + F_ATTRIB = 30, + DRV_PDB = 31, + F_USERNUM = 32, + F_READRAND = 33, + F_WRITERAND = 34, + F_SIZE = 35, + F_RANDREC = 36, + DRV_RESET = 37, + DRV_ACCESS_MPM = 38, // This is an MP/M function that is not supported under CP/M 3. + DRV_FREE_MPM = 39, // This is an MP/M function that is not supported under CP/M 3. + F_WRITEZF = 40, + // CP/M 3.0 Stuff + F_TESTWRITE = 41, + F_LOCKFILE = 42, + F_UNLOCKFILE = 43, + F_MULTISEC = 44, + F_ERRMODE = 45, + DRV_SPACE = 46, + P_CHAIN = 47, + DRV_FLUSH = 48, + S_SCB = 49, + S_BIOS = 50, + P_LOAD = 59, + S_RSX = 60, + F_CLEANUP = 98, + F_TRUNCATE = 99, + DRV_SETLABEL = 100, + DRV_GETLABEL = 101, + F_TIMEDATE = 102, + F_WRITEXFCB = 103, + T_SET = 104, + T_GET = 105, + F_PASSWD = 106, + S_SERIAL = 107, + P_CODE = 108, + C_MODE = 109, + C_DELIMIT = 110, + C_WRITEBLK = 111, + L_WRITEBLK = 112, + F_PARSE = 152, + // RunCPM Stuff + F_PINMODE = 220, + F_DREAD = 221, + F_DWRITE = 222, + F_AREAD = 223, + F_AWRITE = 224, + F_SETMASK = 230, + F_BDOSCALL = 231, + F_UPTIME = 248, + F_MAKEDISK = 249, + F_HOSTOS = 250, + F_VERSION = 251, + F_CCPVERSION = 252, + F_CCPADDR = 253, + F_SETCPUSPEED = 254 }; /* see main.c for definition */ @@ -55,1322 +140,2029 @@ enum eBDOSFunc { #define JP 0xc3 #define CALL 0xcd #define RET 0xc9 -#define INa 0xdb // Triggers a BIOS call -#define OUTa 0xd3 // Triggers a BDOS call +#define INa 0xdb // Triggers a BIOS call +#define OUTa 0xd3 // Triggers a BDOS call +// Interruption handling +#define RST_08 0xcf // RST 08h - BIOS calls +#define RST_10 0xd7 // RST 10h - BDOS calls +#define RST_18 0xdf // RST 18h - Hardware calls +#define NOP 0x00 // No operation /* set up full PUN and LST filenames to be on drive A: user 0 */ #ifdef USE_PUN -char pun_file[17] = {'A', FOLDERCHAR, '0', FOLDERCHAR, 'P', 'U', 'N', '.', 'T', 'X', 'T', 0}; - +RUNCPM_DECL char pun_file[17] = {'A', FOLDERCHAR, '0', FOLDERCHAR, 'P', 'U', 'N', '.', 'T', 'X', 'T', 0}; #endif // ifdef USE_PUN -#ifdef USE_LST -char lst_file[17] = {'A', FOLDERCHAR, '0', FOLDERCHAR, 'L', 'S', 'T', '.', 'T', 'X', 'T', 0}; +#ifdef USE_LST +RUNCPM_DECL char lst_file[17] = {'A', FOLDERCHAR, '0', FOLDERCHAR, 'L', 'S', 'T', '.', 'T', 'X', 'T', 0}; #endif // ifdef USE_LST #ifdef PROFILE -unsigned long time_start = 0; -unsigned long time_now = 0; - +RUNCPM_DECL unsigned long time_start = 0; +RUNCPM_DECL unsigned long time_now = 0; #endif // ifdef PROFILE +RUNCPM_DECL void _PatchBIOS(void) { + uint16 i; + + // Patches in the BIOS jump destinations + for (i = 0; i < 99; i = i + 3) { + _RamWrite(BIOSjmppage + i, JP); + _RamWrite16(BIOSjmppage + i + 1, BIOSpage + i); + } + + // Patches in the BIOS page content + for (i = 0; i < 99; i = i + 3) { +#ifdef INT_HANDOFF + _RamWrite(BIOSpage + i, RST_08); + _RamWrite(BIOSpage + i + 1, RET); + _RamWrite(BIOSpage + i + 2, NOP); +#else + _RamWrite(BIOSpage + i, OUTa); + _RamWrite(BIOSpage + i + 1, 0xFF); + _RamWrite(BIOSpage + i + 2, RET); +#endif + } +} //_PatchBIOS + RUNCPM_DECL void _PatchCPM(void) { - uint16 i; - - // ********** Patch CP/M page zero into the memory ********** - - /* BIOS entry point */ - _RamWrite(0x0000, JP); /* JP BIOS+3 (warm boot) */ - _RamWrite16(0x0001, BIOSjmppage + 3); - if (Status != 2) { - /* IOBYTE - Points to Console */ - _RamWrite( IOByte, 0x3D); - - /* Current drive/user - A:/0 */ - _RamWrite( DSKByte, 0x00); - } - /* BDOS entry point (0x0005) */ - _RamWrite(0x0005, JP); - _RamWrite16(0x0006, BDOSjmppage + 0x06); - - // ********** Patch CP/M Version into the memory so the CCP can see it - _RamWrite16(BDOSjmppage, 0x1600); - _RamWrite16(BDOSjmppage + 2, 0x0000); - _RamWrite16(BDOSjmppage + 4, 0x0000); - - // Patches in the BDOS jump destination - _RamWrite(BDOSjmppage + 6, JP); - _RamWrite16(BDOSjmppage + 7, BDOSpage); - - // Patches in the BDOS page content - _RamWrite( BDOSpage, INa); - _RamWrite( BDOSpage + 1, 0xFF); - _RamWrite( BDOSpage + 2, RET); - - // Patches in the BIOS jump destinations - for (i = 0; i < 0x36; i = i + 3) { - _RamWrite(BIOSjmppage + i, JP); - _RamWrite16(BIOSjmppage + i + 1, BIOSpage + i); - } - - // Patches in the BIOS page content - for (i = 0; i < 0x36; i = i + 3) { - _RamWrite( BIOSpage + i, OUTa); - _RamWrite( BIOSpage + i + 1, 0xFF); - _RamWrite( BIOSpage + i + 2, RET); - } - // ********** Patch CP/M (fake) Disk Paramater Block after the BDOS call entry ********** - i = DPBaddr; - _RamWrite( i++, 64); // spt - Sectors Per Track - _RamWrite( i++, 0); - _RamWrite( i++, 5); // bsh - Data allocation "Block Shift Factor" - _RamWrite( i++, 0x1F); // blm - Data allocation Block Mask - _RamWrite( i++, 1); // exm - Extent Mask - _RamWrite( i++, 0xFF); // dsm - Total storage capacity of the disk drive - _RamWrite( i++, 0x07); - _RamWrite( i++, 255); // drm - Number of the last directory entry - _RamWrite( i++, 3); - _RamWrite( i++, 0xFF); // al0 - _RamWrite( i++, 0x00); // al1 - _RamWrite( i++, 0); // cks - Check area Size - _RamWrite( i++, 0); - _RamWrite( i++, 0x02); // off - Number of system reserved tracks at the beginning of the ( logical ) disk - _RamWrite( i++, 0x00); - blockShift = _RamRead(DPBaddr + 2); - blockMask = _RamRead(DPBaddr + 3); - extentMask = _RamRead(DPBaddr + 4); - numAllocBlocks = _RamRead16((DPBaddr + 5)) + 1; - extentsPerDirEntry = extentMask + 1; - - // ********** Patch CP/M (fake) Disk Parameter Header after the Disk Parameter Block ********** - _RamWrite( i++, 0); // Addr of the sector translation table - _RamWrite( i++, 0); - _RamWrite( i++, 0); // Workspace - _RamWrite( i++, 0); - _RamWrite( i++, 0); - _RamWrite( i++, 0); - _RamWrite( i++, 0); - _RamWrite( i++, 0); - _RamWrite( i++, 0x80); // Addr of the Sector Buffer - _RamWrite( i++, 0); - _RamWrite( i++, LOW_REGISTER(DPBaddr)); // Addr of the DPB Disk Parameter Block - _RamWrite( i++, HIGH_REGISTER(DPBaddr)); - _RamWrite( i++, 0); // Addr of the Directory Checksum Vector - _RamWrite( i++, 0); - _RamWrite( i++, 0); // Addr of the Allocation Vector - _RamWrite( i++, 0); - - // - - // figure out the number of the first allocation block - // after the directory for the phoney allocation block - // list in _findnext() - firstBlockAfterDir = 0; - i = 0x80; - - while (_RamRead(DPBaddr + 9) & i) { - firstBlockAfterDir++; - i >>= 1; - } - if (_RamRead(DPBaddr + 9) == 0xFF) { - i = 0x80; - - while (_RamRead(DPBaddr + 10) & i) { - firstBlockAfterDir++; - i >>= 1; - } - } - physicalExtentBytes = logicalExtentBytes * (extentMask + 1); + uint16 i; + + // ********** Patch CP/M page zero into the memory ********** + + /* BIOS entry point */ + _RamWrite(0x0000, JP); /* JP BIOS+3 (warm boot) */ + _RamWrite16(0x0001, BIOSjmppage + 3); + if (Status != STATUS_RESTART) { + /* IOBYTE - Points to Console */ + _RamWrite(IOByte, 0x3D); + + /* Current drive/user - A:/0 */ + _RamWrite(DSKByte, 0x00); + } + /* BDOS entry point (0x0005) */ + _RamWrite(0x0005, JP); + _RamWrite16(0x0006, BDOSjmppage + 0x06); + + // ********** Patch CP/M Version into the memory so the CCP can see it +#ifdef ABDOS + // Loads the ABDOS.SYS file into memory or throws an error if it doesn't exist + if (_sys_exists((uint8 *)"A/0/ABDOS.SYS")) { + _RamLoad((uint8 *)"A/0/ABDOS.SYS", BDOSjmppage, 0); + } else { + _puts("\r\nABDOS.SYS not found"); + exit(1); + } +#else + _RamWrite16(BDOSjmppage, 0x1600); + _RamWrite16(BDOSjmppage + 2, 0x0000); + _RamWrite16(BDOSjmppage + 4, 0x0000); + + // Patches in the BDOS jump destination + _RamWrite(BDOSjmppage + 6, JP); + _RamWrite16(BDOSjmppage + 7, BDOSpage); + + // Patches in the BDOS page content +#ifdef INT_HANDOFF + _RamWrite(BDOSpage, RST_10); + _RamWrite(BDOSpage + 1, RET); + _RamWrite(BDOSpage + 2, NOP); +#else + _RamWrite(BDOSpage, INa); + _RamWrite(BDOSpage + 1, 0xFF); + _RamWrite(BDOSpage + 2, RET); +#endif + + _PatchBIOS(); +#endif + + // ********** Patch CP/M (fake) Disk Parameter Block after the BDOS call entry ********** + i = DPBaddr; + _RamWrite(i++, 0x00); // DEFW spt - Sectors Per Track (256) + _RamWrite(i++, 0x01); + _RamWrite(i++, 0x05); // DEFB bsh - Data allocation "Block Shift Factor" (for 4096 block size) + _RamWrite(i++, 0x1F); // DEFB blm - Data allocation Block Mask (31 for 4096 block size) + _RamWrite(i++, 0x01); // DEFB exm - Data allocation Extent Mask (1 = total blocks > 256) + _RamWrite(i++, 0xF7); // DEFW dsm - Logical disk size in blocks - 1 (2039) + _RamWrite(i++, 0x07); + _RamWrite(i++, 0xFF); // DEFW drm - Maximum directory entries - 1 (1023) + _RamWrite(i++, 0x03); + _RamWrite(i++, 0xFF); // DEFB al0 - Reserved directory blocks + _RamWrite(i++, 0x00); // DEFB al1 - Reserved directory blocks + _RamWrite(i++, 0x00); // DEFW cks - Check area Size (0 for fixed disks) + _RamWrite(i++, 0x00); + _RamWrite(i++, 0x01); // DEFW off - Number of system reserved tracks at the beginning of the ( logical ) disk + _RamWrite(i++, 0x00); + blockShift = _RamRead(DPBaddr + 2); + blockMask = _RamRead(DPBaddr + 3); + extentMask = _RamRead(DPBaddr + 4); + numAllocBlocks = _RamRead16(DPBaddr + 5) + 1; + extentsPerDirEntry = extentMask + 1; + + // ********** Patch CP/M (fake) Disk Parameter Header after the Disk Parameter Block ********** + _RamWrite(i++, 0); // Addr of the sector translation table + _RamWrite(i++, 0); + _RamWrite(i++, 0); // Workspace + _RamWrite(i++, 0); + _RamWrite(i++, 0); + _RamWrite(i++, 0); + _RamWrite(i++, 0); + _RamWrite(i++, 0); + _RamWrite(i++, 0x80); // Addr of the Sector Buffer + _RamWrite(i++, 0); + _RamWrite(i++, LOW_REGISTER(DPBaddr)); // Addr of the DPB Disk Parameter Block + _RamWrite(i++, HIGH_REGISTER(DPBaddr)); + _RamWrite(i++, 0); // Addr of the Directory Checksum Vector + _RamWrite(i++, 0); + _RamWrite(i++, 0); // Addr of the Allocation Vector + _RamWrite(i++, 0); + + // + + // figure out the number of the first allocation block + // after the directory for the phoney allocation block + // list in _findnext() + firstBlockAfterDir = 0; + i = 0x80; + + while (_RamRead(DPBaddr + 9) & i) { + firstBlockAfterDir++; + i >>= 1; + } + if (_RamRead(DPBaddr + 9) == 0xFF) { + i = 0x80; + + while (_RamRead(DPBaddr + 10) & i) { + firstBlockAfterDir++; + i >>= 1; + } + } + physicalExtentBytes = logicalExtentBytes * (extentMask + 1); } // _PatchCPM #ifdef DEBUGLOG -uint8 LogBuffer[128]; +RUNCPM_DECL uint8 LogBuffer[128]; RUNCPM_DECL void _logRegs(void) { - uint8 J, I; - uint8 Flags[9] = {'S', 'Z', '5', 'H', '3', 'P', 'N', 'C'}; - uint8 c = HIGH_REGISTER(AF); - - if ((c < 32) || (c > 126)) { - c = 46; - } - - for (J = 0, I = LOW_REGISTER(AF); J < 8; ++J, I <<= 1) { - Flags[J] = I & 0x80 ? Flags[J] : '.'; - } - sprintf((char *)LogBuffer, " BC:%04x DE:%04x HL:%04x AF:%02x(%c)|%s| IX:%04x IY:%04x SP:%04x PC:%04x\r\n", - WORD16(BC), WORD16(DE), WORD16(HL), HIGH_REGISTER(AF), c, Flags, WORD16(IX), WORD16(IY), WORD16(SP), WORD16(PC)); - _sys_logbuffer(LogBuffer); + uint8 J, I; + uint8 Flags[9] = {'S', 'Z', '5', 'H', '3', 'P', 'N', 'C'}; + uint8 c = HIGH_REGISTER(AF); + + if ((c < 32) || (c > 126)) + c = 46; + + for (J = 0, I = LOW_REGISTER(AF); J < 8; ++J, I <<= 1) + Flags[J] = I & 0x80 ? Flags[J] : '.'; + sprintf((char *)LogBuffer, " BC:%04x DE:%04x HL:%04x AF:%02x(%c)|%s| IX:%04x IY:%04x SP:%04x PC:%04x\n", + WORD16(BC), WORD16(DE), WORD16(HL), HIGH_REGISTER(AF), c, Flags, WORD16(IX), WORD16(IY), WORD16(SP), WORD16(PC)); + _sys_logbuffer(LogBuffer); } // _logRegs -RUNCPM_DECL void _logMem(uint16 address, uint8 amount) { // Amount = number of 16 bytes lines, so 1 CP/M block = 8, not 128 - uint8 i, m, c, pos; - uint8 head = 8; - uint8 hexa[] = "0123456789ABCDEF"; - - for (i = 0; i < amount; ++i) { - pos = 0; - - for (m = 0; m < head; ++m) { - LogBuffer[pos++] = ' '; - } - sprintf((char *)LogBuffer, " %04x: ", address); - - for (m = 0; m < 16; ++m) { - c = _RamRead(address++); - LogBuffer[pos++] = hexa[c >> 4]; - LogBuffer[pos++] = hexa[c & 0x0f]; - LogBuffer[pos++] = ' '; - LogBuffer[m + head + 48] = c > 31 && c < 127 ? c : '.'; - } - pos += 16; - LogBuffer[pos++] = 0x0a; - LogBuffer[pos++] = 0x00; - _sys_logbuffer(LogBuffer); - } +RUNCPM_DECL void _logMem(uint16 address, uint8 amount) { // Amount = number of 16 bytes lines, so 1 CP/M block = 8, not 128 + uint8 i, m, c, pos; + uint8 head = 8; + uint8 hexa[] = "0123456789ABCDEF"; + + for (i = 0; i < amount; ++i) { + pos = 0; + + for (m = 0; m < head; ++m) + LogBuffer[pos++] = ' '; + sprintf((char *)LogBuffer, " %04x: ", address); + + for (m = 0; m < 16; ++m) { + c = _RamRead(address++); + LogBuffer[pos++] = hexa[c >> 4]; + LogBuffer[pos++] = hexa[c & 0x0f]; + LogBuffer[pos++] = ' '; + LogBuffer[m + head + 48] = c > 31 && c < 127 ? c : '.'; + } + pos += 16; + LogBuffer[pos++] = 0x0a; + LogBuffer[pos++] = 0x00; + _sys_logbuffer(LogBuffer); + } } // _logMem RUNCPM_DECL void _logChar(char *txt, uint8 c) { - uint8 asc[2]; + uint8 asc[2]; - asc[0] = c > 31 && c < 127 ? c : '.'; - asc[1] = 0; - sprintf((char *)LogBuffer, " %s = %02xh:%3d (%s)\r\n", txt, c, c, asc); - _sys_logbuffer(LogBuffer); + asc[0] = c > 31 && c < 127 ? c : '.'; + asc[1] = 0; + sprintf((char *)LogBuffer, " %s = %02xh:%3d (%s)\n", txt, c, c, asc); + _sys_logbuffer(LogBuffer); } // _logChar RUNCPM_DECL void _logBiosIn(uint8 ch) { -#ifdef LOGBIOS_NOT - if (ch == LOGBIOS_NOT) { - return; - } -#endif // ifdef LOGBIOS_NOT -#ifdef LOGBIOS_ONLY - if (ch != LOGBIOS_ONLY) { - return; - } -#endif // ifdef LOGBIOS_ONLY - static const char *BIOSCalls[18] = - { - "boot", "wboot", "const", "conin", "conout", "list", "punch/aux", "reader", "home", "seldsk", "settrk", "setsec", "setdma", - "read", "write", "listst", "sectran", "altwboot" - }; - int index = ch / 3; - - if (index < 18) { - sprintf((char *)LogBuffer, "\nBios call: %3d/%02xh (%s) IN:\r\n", ch, ch, BIOSCalls[index]); - _sys_logbuffer(LogBuffer); - } else { - sprintf((char *)LogBuffer, "\nBios call: %3d/%02xh IN:\r\n", ch, ch); - _sys_logbuffer(LogBuffer); - } - _logRegs(); + #ifdef LOGBIOS_NOT + if (ch == LOGBIOS_NOT) + return; + #endif // ifdef LOGBIOS_NOT + #ifdef LOGBIOS_ONLY + if (ch != LOGBIOS_ONLY) + return; + #endif // ifdef LOGBIOS_ONLY + static const char *BIOSCalls[33] = + { + "boot", "wboot", "const", "conin", "conout", "list", "punch/aux", "reader", "home", "seldsk", "settrk", "setsec", "setdma", + "read", "write", "listst", "sectran", "conost", "auxist", "auxost", "devtbl", "devini", "drvtbl", "multio", "flush", "move", + "time", "selmem", "setbnk", "xmove", "userf", "reserv1", "reserv2"}; + int index = ch / 3; + + if (index < 18) { + sprintf((char *)LogBuffer, "\nBios call: %3d/%02xh (%s) IN:\n", ch, ch, BIOSCalls[index]); + _sys_logbuffer(LogBuffer); + } else { + sprintf((char *)LogBuffer, "\nBios call: %3d/%02xh IN:\n", ch, ch); + _sys_logbuffer(LogBuffer); + } + _logRegs(); } // _logBiosIn RUNCPM_DECL void _logBiosOut(uint8 ch) { -#ifdef LOGBIOS_NOT - if (ch == LOGBIOS_NOT) { - return; - } -#endif // ifdef LOGBIOS_NOT -#ifdef LOGBIOS_ONLY - if (ch != LOGBIOS_ONLY) { - return; - } -#endif // ifdef LOGBIOS_ONLY - sprintf((char *)LogBuffer, " OUT:\r\n"); - _sys_logbuffer(LogBuffer); - _logRegs(); + #ifdef LOGBIOS_NOT + if (ch == LOGBIOS_NOT) + return; + #endif // ifdef LOGBIOS_NOT + #ifdef LOGBIOS_ONLY + if (ch != LOGBIOS_ONLY) + return; + #endif // ifdef LOGBIOS_ONLY + sprintf((char *)LogBuffer, " OUT:\n"); + _sys_logbuffer(LogBuffer); + _logRegs(); } // _logBiosOut RUNCPM_DECL void _logBdosIn(uint8 ch) { -#ifdef LOGBDOS_NOT - if (ch == LOGBDOS_NOT) { - return; - } -#endif // ifdef LOGBDOS_NOT -#ifdef LOGBDOS_ONLY - if (ch != LOGBDOS_ONLY) { - return; - } -#endif // ifdef LOGBDOS_ONLY - uint16 address = 0; - uint8 size = 0; - - static const char *CPMCalls[41] = - { - "System Reset", "Console Input", "Console Output", "Reader Input", "Punch Output", "List Output", "Direct I/O", - "Get IOByte", - "Set IOByte", "Print String", "Read Buffered", "Console Status", "Get Version", "Reset Disk", "Select Disk", "Open File", - "Close File", "Search First", "Search Next", "Delete File", "Read Sequential", "Write Sequential", "Make File", - "Rename File", - "Get Login Vector", "Get Current Disk", "Set DMA Address", "Get Alloc", "Write Protect Disk", "Get R/O Vector", - "Set File Attr", "Get Disk Params", - "Get/Set User", "Read Random", "Write Random", "Get File Size", "Set Random Record", "Reset Drive", "N/A", "N/A", - "Write Random 0 fill" - }; - - if (ch < 41) { - sprintf((char *)LogBuffer, "\nBdos call: %3d/%02xh (%s) IN from 0x%04x:\r\n", ch, ch, CPMCalls[ch], _RamRead16(SP) - 3); - _sys_logbuffer(LogBuffer); - } else { - sprintf((char *)LogBuffer, "\nBdos call: %3d/%02xh IN from 0x%04x:\r\n", ch, ch, _RamRead16(SP) - 3); - _sys_logbuffer(LogBuffer); - } - _logRegs(); - - switch (ch) { - case 2: - case 4: - case 5: - case 6: { - _logChar("E", LOW_REGISTER(DE)); - break; - } - - case 9: - case 10: { - address = DE; - size = 8; - break; - } - - case 15: - case 16: - case 17: - case 18: - case 19: - case 22: - case 23: - case 30: - case 35: - case 36: { - address = DE; - size = 3; - break; - } - - case 20: - case 21: - case 33: - case 34: - case 40: { - address = DE; - size = 3; - _logMem(address, size); - sprintf((char *)LogBuffer, "\r\n"); - _sys_logbuffer(LogBuffer); - address = dmaAddr; - size = 8; - break; - } - - default: { - break; - } - } // switch - if (size) { - _logMem(address, size); - } + #ifdef LOGBDOS_NOT + if (ch == LOGBDOS_NOT) + return; + #endif // ifdef LOGBDOS_NOT + #ifdef LOGBDOS_ONLY + if (ch != LOGBDOS_ONLY) + return; + #endif // ifdef LOGBDOS_ONLY + uint16 address = 0; + uint8 size = 0; + + static const char *CPMCalls[41] = + { + "System Reset", "Console Input", "Console Output", "Reader Input", "Punch Output", "List Output", "Direct I/O", + "Get IOByte", "Set IOByte", "Print String", "Read Buffered", "Console Status", "Get Version", "Reset Disk", + "Select Disk", "Open File", "Close File", "Search First", "Search Next", "Delete File", "Read Sequential", + "Write Sequential", "Make File", "Rename File", "Get Login Vector", "Get Current Disk", "Set DMA Address", + "Get Alloc", "Write Protect Disk", "Get R/O Vector", "Set File Attr", "Get Disk Params", "Get/Set User", + "Read Random", "Write Random", "Get File Size", "Set Random Record", "Reset Drive", "N/A", "N/A", + "Write Random 0 fill"}; + + if (ch < 41) { + sprintf((char *)LogBuffer, "\nBdos call: %3d/%02xh (%s) IN from 0x%04x:\n", ch, ch, CPMCalls[ch], _RamRead16(SP) - 3); + _sys_logbuffer(LogBuffer); + } else { + sprintf((char *)LogBuffer, "\nBdos call: %3d/%02xh IN from 0x%04x:\n", ch, ch, _RamRead16(SP) - 3); + _sys_logbuffer(LogBuffer); + } + _logRegs(); + + switch (ch) { + case 2: + case 4: + case 5: + case 6: { + _logChar("E", LOW_REGISTER(DE)); + break; + } + + case 9: + case 10: { + address = DE; + size = 8; + break; + } + + case 15: + case 16: + case 17: + case 18: + case 19: + case 22: + case 23: + case 30: + case 35: + case 36: { + address = DE; + size = 3; + break; + } + + case 20: + case 21: + case 33: + case 34: + case 40: { + address = DE; + size = 3; + _logMem(address, size); + sprintf((char *)LogBuffer, "\n"); + _sys_logbuffer(LogBuffer); + address = dmaAddr; + size = 8; + break; + } + + default: { + break; + } + } // switch + if (size) + _logMem(address, size); } // _logBdosIn RUNCPM_DECL void _logBdosOut(uint8 ch) { -#ifdef LOGBDOS_NOT - if (ch == LOGBDOS_NOT) { - return; - } -#endif // ifdef LOGBDOS_NOT -#ifdef LOGBDOS_ONLY - if (ch != LOGBDOS_ONLY) { - return; - } -#endif // ifdef LOGBDOS_ONLY - uint16 address = 0; - uint8 size = 0; - - sprintf((char *)LogBuffer, " OUT:\r\n"); - _sys_logbuffer(LogBuffer); - _logRegs(); - - switch (ch) { - case 1: - case 3: - case 6: { - _logChar("A", HIGH_REGISTER(AF)); - break; - } - - case 10: { - address = DE; - size = 8; - break; - } - - case 20: - case 21: - case 33: - case 34: - case 40: { - address = DE; - size = 3; - _logMem(address, size); - sprintf((char *)LogBuffer, "\r\n"); - _sys_logbuffer(LogBuffer); - address = dmaAddr; - size = 8; - break; - } - - case 26: { - address = dmaAddr; - size = 8; - break; - } - - case 35: - case 36: { - address = DE; - size = 3; - break; - } - - default: { - break; - } - } // switch - if (size) { - _logMem(address, size); - } + #ifdef LOGBDOS_NOT + if (ch == LOGBDOS_NOT) + return; + #endif // ifdef LOGBDOS_NOT + #ifdef LOGBDOS_ONLY + if (ch != LOGBDOS_ONLY) + return; + #endif // ifdef LOGBDOS_ONLY + uint16 address = 0; + uint8 size = 0; + + sprintf((char *)LogBuffer, " OUT:\n"); + _sys_logbuffer(LogBuffer); + _logRegs(); + + switch (ch) { + case 1: + case 3: + case 6: { + _logChar("A", HIGH_REGISTER(AF)); + break; + } + + case 10: { + address = DE; + size = 8; + break; + } + + case 20: + case 21: + case 33: + case 34: + case 40: { + address = DE; + size = 3; + _logMem(address, size); + sprintf((char *)LogBuffer, "\n"); + _sys_logbuffer(LogBuffer); + address = dmaAddr; + size = 8; + break; + } + + case 26: { + address = dmaAddr; + size = 8; + break; + } + + case 15: + case 16: + case 17: + case 18: + case 19: + case 22: + case 23: + case 30: + case 35: + case 36: { + address = DE; + size = 3; + break; + } + + default: { + break; + } + } // switch + if (size) + _logMem(address, size); } // _logBdosOut #endif // ifdef DEBUGLOG RUNCPM_DECL void _Bios(void) { - uint8 ch = LOW_REGISTER(PCX); - uint8 disk[2] = {'A', 0}; + uint8 ch = LOW_REGISTER(PCX); + uint8 disk[2] = {'A', 0}; #ifdef DEBUGLOG - _logBiosIn(ch); + _logBiosIn(ch); #endif - switch (ch) { - case 0x00: { - Status = 1; // 0 - BOOT - Ends RunCPM - break; - } - - case 0x03: { - Status = 2; // 1 - WBOOT - Back to CCP - break; - } - - case 0x06: { // 2 - CONST - Console status - SET_HIGH_REGISTER(AF, _chready()); - break; - } - - case 0x09: { // 3 - CONIN - Console input - SET_HIGH_REGISTER(AF, _getch()); -#ifdef DEBUG - if (HIGH_REGISTER(AF) == 4) { - Debug = 0; - } -#endif // ifdef DEBUG - break; - } - - case 0x0C: { // 4 - CONOUT - Console output - _putcon(LOW_REGISTER(BC)); - break; - } - - case 0x0F: { // 5 - LIST - List output - break; - } - - case 0x12: { // 6 - PUNCH/AUXOUT - Punch output - break; - } - - case 0x15: { // 7 - READER - Reader input (0x1a = device not implemented) - SET_HIGH_REGISTER(AF, 0x1a); - break; - } - - case 0x18: { // 8 - HOME - Home disk head - break; - } - - case 0x1B: { // 9 - SELDSK - Select disk drive - disk[0] += LOW_REGISTER(BC); - if (_sys_select(&disk[0])) { - HL = DPHaddr; - } else { - HL = 0x0000; - } - break; - } - - case 0x1E: { // 10 - SETTRK - Set track number - break; - } - - case 0x21: { // 11 - SETSEC - Set sector number - break; - } - - case 0x24: { // 12 - SETDMA - Set DMA address - HL = BC; - dmaAddr = BC; - break; - } - - case 0x27: { // 13 - READ - Read selected sector - SET_HIGH_REGISTER(AF, 0x00); - break; - } - - case 0x2A: { // 14 - WRITE - Write selected sector - SET_HIGH_REGISTER(AF, 0x00); - break; - } - - case 0x2D: { // 15 - LISTST - Get list device status - SET_HIGH_REGISTER(AF, 0x0ff); - break; - } - - case 0x30: { // 16 - SECTRAN - Sector translate - HL = BC; // HL=BC=No translation (1:1) - break; - } - - case 0x33: { // 17 - RETTOCCP - This allows programs ending in RET return to internal CCP - Status = 3; - break; - } - - default: { -#ifdef DEBUG // Show unimplemented BIOS calls only when debugging - _puts( "\r\nUnimplemented BIOS call.\r\n"); - _puts( "C = 0x"); - _puthex8(ch); - _puts("\r\n"); -#endif // ifdef DEBUG - break; - } - } // switch + switch (ch) { + case B_BOOT: { + Status = STATUS_EXIT; // 0 - Ends RunCPM + break; + } + case B_WBOOT: { + Status = STATUS_RESTART; // 1 - Back to CCP + break; + } + case B_CONST: { // 2 - Console status + SET_HIGH_REGISTER(AF, _chready()); + break; + } + case B_CONIN: { // 3 - Console input + SET_HIGH_REGISTER(AF, _getcon()); +#if RUNCPMDEBUG + if (HIGH_REGISTER(AF) == DEBUGKEY) + Debug = 1; +#endif // RUNCPMDEBUG + break; + } + case B_CONOUT: { // 4 - Console output + _putcon(LOW_REGISTER(BC)); + break; + } + case B_LIST: { // 5 - List output + break; + } + case B_AUXOUT: { // 6 - Aux/Punch output + break; + } + case B_READER: { // 7 - Reader input (returns 0x1a = device not implemented) + SET_HIGH_REGISTER(AF, 0x1a); + break; + } + case B_HOME: { // 8 - Home disk head + break; + } + case B_SELDSK: { // 9 - Select disk drive + disk[0] += LOW_REGISTER(BC); + HL = 0x0000; + if (_sys_select(&disk[0])) + HL = DPHaddr; + break; + } + case B_SETTRK: { // 10 - Set track number + break; + } + case B_SETSEC: { // 11 - Set sector number + break; + } + case B_SETDMA: { // 12 - Set DMA address + HL = BC; + dmaAddr = BC; + break; + } + case B_READ: { // 13 - Read selected sector + SET_HIGH_REGISTER(AF, 0x00); + break; + } + case B_WRITE: { // 14 - Write selected sector + SET_HIGH_REGISTER(AF, 0x00); + break; + } + case B_LISTST: { // 15 - Get list device status + SET_HIGH_REGISTER(AF, 0x0ff); + break; + } + case B_SECTRAN: { // 16 - Sector translate + HL = BC; // HL=BC=No translation (1:1) + break; + } + case B_CONOST: { // 17 - Return status of current screen output device + SET_HIGH_REGISTER(AF, 0x0ff); + break; + } + case B_AUXIST: { // 18 - Return status of current auxiliary input device + SET_HIGH_REGISTER(AF, 0x00); + break; + } + case B_AUXOST: { // 19 - Return status of current auxiliary output device + SET_HIGH_REGISTER(AF, 0x00); + break; + } + case B_DEVTBL: { // 20 - Return the address of the devices table, or 0 if not implemented + HL = 0x0000; + break; + } + case B_DEVINI: { // 21 - Reinitialise character device number C + break; + } + case B_DRVTBL: { // 22 - Return the address of the drive table + HL = 0x0FFFF; + break; + } + case B_MULTIO: { // 23 - Notify the BIOS of multi sector transfer + break; + } + case B_FLUSH: { // 24 - Write any pending data to disc + SET_HIGH_REGISTER(AF, 0x00); + break; + } + case B_MOVE: { // 25 - Move a block of memory + if (!isXmove) { + srcBank = dstBank = curBank; + srcBankBase = dstBankBase = curBankBase; + } + while (BC--) + RAM[dstBankBase + HL++] = RAM[srcBankBase + DE++]; + isXmove = FALSE; + break; + } + case B_TIME: { // 26 - Get/Set SCB time + break; + } + case B_SELMEM: { // 27 - Select memory bank + curBank = HIGH_REGISTER(AF); + curBankBase = ((uint32)curBank) << 16; // banks are 0-based: bank N -> RAM offset N*64K + break; + } + case B_SETBNK: { // 28 - Set the bank to be used for the next read/write sector operation + ioBank = HIGH_REGISTER(AF); + ioBankBase = ((uint32)ioBank) << 16; + break; // without this, SETBNK fell through into XMOVE and corrupted srcBank/dstBank/isXmove + } + case B_XMOVE: { // 29 - Preload banks for MOVE + srcBank = LOW_REGISTER(BC); + dstBank = HIGH_REGISTER(BC); + srcBankBase = ((uint32)srcBank) << 16; + dstBankBase = ((uint32)dstBank) << 16; + isXmove = TRUE; + break; + } + case B_USERF: { // 30 - This allows programs ending in RET return to internal CCP + Status = STATUS_RETURN; + break; + } + case B_RESERV1: + case B_RESERV2: { + break; + } + default: { +#if RUNCPMDEBUG // Show unimplemented BIOS calls only when debugging + _puts("\r\nUnimplemented BIOS call.\r\n"); + _puts("C = 0x"); + _puthex8(ch); + _puts("\r\n"); +#endif // RUNCPMDEBUG + break; + } + } // switch #ifdef DEBUGLOG - _logBiosOut(ch); + _logBiosOut(ch); #endif } // _Bios -RUNCPM_DECL void _Bdos(void) { - uint16 i; - uint8 j, chr, ch = LOW_REGISTER(BC); - uint8 trans_ch = 0x9b; - - (void)trans_ch; +/* Packed BCD helpers for the CP/M 3 clock (BDOS 104/105) */ +#define BCD2DEC(b) ((((b) >> 4) & 0x0F) * 10 + ((b) & 0x0F)) +#define DEC2BCD(d) ((uint8)((((d) / 10) << 4) | ((d) % 10))) -#ifdef DEBUGLOG - _logBdosIn(ch); -#endif +/* Difference (in seconds) between the host clock and the clock set via + BDOS 104 (T_SET). Lets a set time round-trip through BDOS 105 (T_GET). */ +static long clockOffset = 0; - HL = 0x0000; // HL is reset by the BDOS - SET_LOW_REGISTER(BC, LOW_REGISTER(DE)); // C ends up equal to E - - switch (ch) { - /* - C = 0 : System reset - Doesn't return. Reloads CP/M - */ - case F_BOOT: { - Status = 2; // Same as call to "BOOT" - break; - } - - /* - C = 1 : Console input - Gets a char from the console - Returns: A=Char - */ - case C_READ: { - HL = _getche(); -#ifdef DEBUG - if (HL == 4) { - Debug = 1; - } -#endif // ifdef DEBUG - break; - } - - /* - C = 2 : Console output - E = Char - Sends the char in E to the console - */ - case C_WRITE: { - _putcon(LOW_REGISTER(DE)); - break; - } - - /* - C = 3 : Auxiliary (Reader) input - Returns: A=Char - */ - case READER_IN: { - HL = 0x1a; - break; - } - - /* - C = 4 : Auxiliary (Punch) output - */ - case PUNCH_OUT: { -#ifdef USE_PUN - if (!pun_open) { - pun_dev = _sys_fopen_w((uint8 *)pun_file); - pun_open = TRUE; - } - if (pun_dev) { - _sys_fputc(LOW_REGISTER(DE), pun_dev); - } -#endif // ifdef USE_PUN - break; - } - - /* - C = 5 : Printer output - */ - case PRINT_OUT: { -#ifdef BUILD_ATARI - if (LOW_REGISTER(DE) != 0x0A) - { - trans_ch = LOW_REGISTER(DE) == 0x0D ? 0x9B : LOW_REGISTER(DE); - SYSTEM_BUS.getPrinter()->print_from_cpm(LOW_REGISTER(DE)); - } -#endif /* BUILD_ATARI */ -#ifdef BUILD_APPLE - SYSTEM_BUS.getPrinter()->print_from_cpm(LOW_REGISTER(DE)); -#endif /* BUILD_APPLE */ -#ifdef USE_LST - if (!lst_open) { - lst_dev = _sys_fopen_w((uint8 *)lst_file); - lst_open = TRUE; - } - if (lst_dev) { - _sys_fputc(LOW_REGISTER(DE), lst_dev); - } -#endif // ifdef USE_LST - break; - } - - /* - C = 6 : Direct console IO - E = 0xFF : Checks for char available and returns it, or 0x00 if none (read) - E = char : Outputs char (write) - Returns: A=Char or 0x00 (on read) - */ - case DIRECT_IO: { - if (LOW_REGISTER(DE) == 0xff) { - HL = _getchNB(); -#ifdef DEBUG - if (HL == 4) { - Debug = 1; - } -#endif // ifdef DEBUG - } else { - _putcon(LOW_REGISTER(DE)); - } - break; - } - - /* - C = 7 : Get IOBYTE - Gets the system IOBYTE - Returns: A = IOBYTE - */ - case GET_IOBYTE: { - HL = _RamRead(0x0003); - break; - } - - /* - C = 8 : Set IOBYTE - E = IOBYTE - Sets the system IOBYTE to E - */ - case SET_IOBYTE: { - _RamWrite(0x0003, LOW_REGISTER(DE)); - break; - } - - /* - C = 9 : Output string - DE = Address of string - Sends the $ terminated string pointed by (DE) to the screen - */ - case OUT_STRING: { - while ((ch = _RamRead(DE++)) != '$') { - _putcon(ch); - } - break; - } - - /* - C = 10 (0Ah) : Buffered input - DE = Address of buffer - Reads (DE) bytes from the console - Returns: A = Number os chars read - DE) = First char - */ - case C_READSTR: { - uint16 chrsMaxIdx = WORD16(DE); //index to max number of characters - uint16 chrsCntIdx = (chrsMaxIdx + 1) & 0xFFFF; //index to number of characters read - uint16 chrsIdx = (chrsCntIdx + 1) & 0xFFFF; //index to characters - //printf("\n\r chrsMaxIdx: %0X, chrsCntIdx: %0X", chrsMaxIdx, chrsCntIdx); - - static uint8 *last = 0; - if (!last) { - last = (uint8 *)calloc(1, 256); //allocate one (for now!) - } +/* Program return code, get/set via BDOS 108 (P_CODE). */ +static uint16 programRetCode = 0; -#ifdef PROFILE - if (time_start != 0) { - time_now = millis(); - printf(": %ld\r\n", time_now - time_start); - time_start = 0; - } -#endif // ifdef PROFILE - uint8 chrsMax = _RamRead(chrsMaxIdx); // Gets the max number of characters that can be read - uint8 chrsCnt = 0; // this is the number of characters read - uint8 curCol = 0; //this is the cursor column (relative to where it started) +/* Console mode word, get/set via BDOS 109 (C_MODE). RunCPM's console layer is + already raw (no ^S pause, no tab expansion, no ^C termination, no ^P echo), + so most "disable" bits are effectively always on. Bits 8-9 are honored by + function 11 (Get console status). */ +static uint16 consoleMode = 0; - while (chrsMax) { - // pre-backspace, retype & post backspace counts - uint8 preBS = 0, reType = 0, postBS = 0; +RUNCPM_DECL void _Bdos(void) { + uint8 ch = LOW_REGISTER(BC); + uint8 trans_ch = 0x9b; // FujiNet: CR->ATASCII EOL for printer (C=5) + (void)trans_ch; - chr = _getch(); //input a character +#ifdef DEBUGLOG + _logBdosIn(ch); +#endif - if (chr == 1) { // ^A - Move cursor one character to the left - if (curCol > 0) { - preBS++; //backspace one - } else { - _putcon('\007'); //ring the bell - } + HL = 0x0000; // HL is reset by the BDOS + SET_LOW_REGISTER(BC, LOW_REGISTER(DE)); // C ends up equal to E + + switch (ch) { +#ifndef ABDOS + /* + C = 0 : System reset + Doesn't return. Reloads CP/M + */ + case P_TERMCPM: { + Status = STATUS_RESTART; // Same as call to "BOOT" + break; + } + + /* + C = 1 : Console input + Gets a char from the console + Returns: A=Char + */ + case C_READ: { + HL = _getconE(); + #if RUNCPMDEBUG + if (HL == DEBUGKEY) + Debug = 1; + #endif // RUNCPMDEBUG + break; + } + + /* + C = 2 : Console output + E = Char + Sends the char in E to the console + */ + case C_WRITE: { + _putcon(LOW_REGISTER(DE)); + break; + } + + /* + C = 3 : Auxiliary (Reader) input + Returns: A=Char + */ + case A_READ: { + HL = 0x1a; + break; + } + + /* + C = 4 : Auxiliary (Punch) output + */ + case A_WRITE: { + #ifdef USE_PUN + if (!pun_open) { + pun_dev = _sys_fopen_w((uint8 *)pun_file); + pun_open = TRUE; + } + if (pun_dev) { + _sys_fputc(LOW_REGISTER(DE), pun_dev); + } + #endif // ifdef USE_PUN + break; + } + + /* + C = 5 : Printer output + */ + case L_WRITE: { + /* FujiNet: route printer output to the device printer. Atari translates + CR->ATASCII EOL and drops LF; Apple passes through. */ + #ifdef BUILD_ATARI + if (LOW_REGISTER(DE) != 0x0A) { + trans_ch = LOW_REGISTER(DE) == 0x0D ? 0x9B : LOW_REGISTER(DE); + SYSTEM_BUS.getPrinter()->print_from_cpm(LOW_REGISTER(DE)); + } + #endif /* BUILD_ATARI */ + #ifdef BUILD_APPLE + SYSTEM_BUS.getPrinter()->print_from_cpm(LOW_REGISTER(DE)); + #endif /* BUILD_APPLE */ + #ifdef USE_LST + if (!lst_open) { + lst_dev = _sys_fopen_w((uint8 *)lst_file); + lst_open = TRUE; + } + if (lst_dev) + _sys_fputc(LOW_REGISTER(DE), lst_dev); + #endif // ifdef USE_LST + break; + } + + /* + C = 6 : Direct console IO + E = 0xFF : Checks for char available and returns it, or 0x00 if none (read) + E = 0xFE : Return console input status. Zero if no character is waiting, nonzero otherwise. (CPM3) + E = 0xFD : Wait until a character is ready, return it without echoing. (CPM3) + E = char : Outputs char (write) + Returns: A=Char or 0x00 (on read) + */ + case C_RAWIO: { + uint8 e = LOW_REGISTER(DE); + if (e == 0xFF) { + // Check for char available and return it, or 0x00 if none (non-blocking read) + HL = _getconNB(); + #if RUNCPMDEBUG + if (HL == DEBUGKEY) + Debug = 1; + #endif // RUNCPMDEBUG + #ifdef CPM3 + } else if (e == 0xFE) { + // Return console input status. Zero if no character is waiting, nonzero otherwise. (CPM3) + HL = _chready() ? 0x00FF : 0x0000; + } else if (e == 0xFD) { + // Wait until a character is ready, return it without echoing. (CPM3) + HL = _getcon(); + #if RUNCPMDEBUG + if (HL == DEBUGKEY) + Debug = 1; + #endif // RUNCPMDEBUG + #endif // ifdef CPM3 + } else { + // E = char : Outputs char (write) + _putcon(e); + } + break; + } + + /* + C = 7 : Get IOBYTE (CPM2) + Gets the system IOBYTE + Returns: A = IOBYTE (CPM2) + C = 7 : Auxiliary Input status (CPM3) + 0FFh is returned if the Auxiliary Input device has a character ready; otherwise 0 is returned. + Returns: A=0 or 0FFh (CPM3) + */ + case A_STATIN: { + #ifdef CPM3 + HL = _chready() ? 0xFF : 0x00; + #else + HL = _RamRead(0x0003); + #endif + break; + } + + /* + C = 8 : Set IOBYTE (CPM2) + E = IOBYTE + Sets the system IOBYTE to E + C = 8 : Auxiliary Output status (CPM3) + 0FFh is returned if the Auxiliary Output device is ready for characters; otherwise 0 is returned. + Returns: A=0 or 0FFh (CPM3) + */ + case A_STATOUT: { + #ifdef CPM3 + HL = 0xFF; // Auxiliary Output device is always ready + #else + _RamWrite(0x0003, LOW_REGISTER(DE)); + #endif + break; + } + + /* + C = 9 : Output string + DE = Address of string + Sends the $ terminated string pointed by (DE) to the screen + Under CP/M 3 the termination character can be changed by the user via BDOS call 110 (C_DELIMIT). + */ + case C_WRITESTR: { + uint8 delim = outputDelimiter; + while ((ch = _RamRead(DE++)) != delim) + _putcon(ch); + break; + } + + /* + C = 10 (0Ah) : Buffered input + CP/M 2: + DE = Address of buffer + CP/M 3: + DE = Address of buffer + DE = 0 Use DMA address abd the buffer already contains data + if DE=address: + buffer: DEFB size + DEFB ? + DEFB bytes + if DE=0: + buffer: DEFB size + DEFB len + DEFB bytes + + Reads (DE) bytes from the console + Returns: A = Number of chars read + DE) = First char + */ + case C_READSTR: { + uint16 i; + uint8 j, chr; + // Under CP/M3 DE==0 means use the DMA buffer. Compute a common base address + // so the rest of the code can operate on either DE-provided buffer or DMA. + uint16 bufBase = (WORD16(DE) == 0) ? dmaAddr : WORD16(DE); + uint16 chrsMaxIdx = bufBase; // index to max number of characters + uint16 chrsCntIdx = (chrsMaxIdx + 1) & 0xFFFF; // index to number of characters read + uint16 chrsIdx = (chrsCntIdx + 1) & 0xFFFF; // index to characters + // printf("\n\r chrsMaxIdx: %0X, chrsCntIdx: %0X", chrsMaxIdx, chrsCntIdx); + + static uint8 *last = 0; + if (!last) + last = (uint8 *)calloc(1, 256); // allocate one (for now!) + + #ifdef PROFILE + if (time_start != 0) { + time_now = millis(); + printf(" (%ld)\n", time_now - time_start); + time_start = 0; + } + #endif // ifdef PROFILE + uint8 chrsMax = _RamRead(chrsMaxIdx); // Gets the max number of characters that can be read + uint8 chrsCnt = 0; // this is the number of characters read + #ifdef CPM3 + // CP/M3 behaviour: if DE==0 the DMA buffer may already contain data + // Layout when DE==0: [size][len][bytes...] + if (WORD16(DE) == 0) { + uint8 existingLen = _RamRead(chrsCntIdx); + if (existingLen) { // buffer already contains data, return immediately + HL = existingLen; + break; + } + // otherwise fall through and fill the DMA buffer interactively + } + #endif // ifdef CPM3 + uint8 curCol = 0; // this is the cursor column (relative to where it started) + + while (chrsMax) { + // pre-backspace, retype & post backspace counts + uint8 preBS = 0, reType = 0, postBS = 0; + + chr = _getcon(); // input a character + + if (chr == 1) { // ^A - Move cursor one character to the left + if (curCol > 0) { + preBS++; // backspace one + } else { + _putcon('\007'); // ring the bell } + } - if (chr == 2) { // ^B - Toggle between beginning & end of line - if (curCol) { - preBS = curCol; //move to beginning - } else { - reType = chrsCnt - curCol; //move to EOL - } + if (chr == 2) { // ^B - Toggle between beginning & end of line + if (curCol) { + preBS = curCol; // move to beginning + } else { + reType = chrsCnt - curCol; // move to EOL } + } - if ((chr == 3) && (chrsCnt == 0)) { // ^C - Abort string input - _puts("^C"); - Status = 2; - break; - } + if ((chr == 3) && (chrsCnt == 0)) { // ^C - Abort string input + _puts("^C"); + Status = STATUS_RESTART; + break; + } -#ifdef DEBUG - if (chr == 4) { // ^D - DEBUG - Debug = 1; + #if RUNCPMDEBUG + if (chr == DEBUGKEY) { // Enter debugger + Debug = 1; + break; + } + #endif // RUNCPMDEBUG - printf("\r\n curCol: %u, chrsCnt: %u, chrsMax: %u", curCol, chrsCnt, chrsMax); - _puts("#\r\n "); - reType = chrsCnt; - postBS = chrsCnt - curCol; - } -#endif // ifdef DEBUG + if (chr == 5) { // ^E - goto beginning of next line + _putcon('\n'); + preBS = curCol; + reType = postBS = chrsCnt; + } - if (chr == 5) { // ^E - goto beginning of next line - _puts("\r\n"); - preBS = curCol; - reType = postBS = chrsCnt; + if (chr == 6) { // ^F - Move the cursor one character forward + if (curCol < chrsCnt) { + reType++; + } else { + _putcon('\007'); // ring the bell } + } - if (chr == 6) { // ^F - Move the cursor one character forward - if (curCol < chrsCnt) { - reType++; - } else { - _putcon('\007'); //ring the bell - } - } - - if (chr == 7) { // ^G - Delete character at cursor - if (curCol < chrsCnt) { - //delete this character from buffer - for (i = curCol, j = i + 1; j < chrsCnt; i++, j++) { - ch = _RamRead(((chrsIdx + j) & 0xFFFF)); - _RamWrite((chrsIdx + i) & 0xFFFF, ch); - } - reType = postBS = chrsCnt - curCol; - chrsCnt--; - } else { - _putcon('\007'); //ring the bell + if (chr == 7) { // ^G - Delete character at cursor + if (curCol < chrsCnt) { + // delete this character from buffer + for (i = curCol, j = i + 1; j < chrsCnt; i++, j++) { + ch = _RamRead(((chrsIdx + j) & 0xFFFF)); + _RamWrite((chrsIdx + i) & 0xFFFF, ch); } + reType = postBS = chrsCnt - curCol; + chrsCnt--; + } else { + _putcon('\007'); // ring the bell } + } - if (((chr == 0x08) || (chr == 0x7F))) { // ^H and DEL - Delete one character to left of cursor - if (curCol > 0) { //not at BOL - if (curCol < chrsCnt) { //not at EOL - //delete previous character from buffer - for (i = curCol, j = i - 1; i < chrsCnt; i++, j++) { - ch = _RamRead(((chrsIdx + i) & 0xFFFF)); - _RamWrite((chrsIdx + j) & 0xFFFF, ch); - } - preBS++; //pre-backspace one - //note: does one extra to erase EOL - reType = postBS = chrsCnt - curCol + 1; - } else { - preBS = reType = postBS = 1; + if (((chr == 0x08) || (chr == 0x7F))) { // ^H and DEL - Delete one character to left of cursor + if (curCol > 0) { // not at BOL + if (curCol < chrsCnt) { // not at EOL + // delete previous character from buffer + for (i = curCol, j = i - 1; i < chrsCnt; i++, j++) { + ch = _RamRead(((chrsIdx + i) & 0xFFFF)); + _RamWrite((chrsIdx + j) & 0xFFFF, ch); } - chrsCnt--; + preBS++; // pre-backspace one + // note: does one extra to erase EOL + reType = postBS = chrsCnt - curCol + 1; } else { - _putcon('\007'); //ring the bell + preBS = reType = postBS = 1; } + chrsCnt--; + } else { + _putcon('\007'); // ring the bell } + } - if ((chr == 0x0A) || (chr == 0x0D)) { // ^J and ^M - Ends editing -#ifdef PROFILE - time_start = millis(); -#endif - break; - } - - if (chr == 0x0B) { // ^K - Delete to EOL from cursor - if (curCol < chrsCnt) { - reType = postBS = chrsCnt - curCol; - chrsCnt = curCol; //truncate buffer to here - } else { - _putcon('\007'); //ring the bell - } - } + if ((chr == 0x0A) || (chr == 0x0D)) { // ^J and ^M - Ends editing + #ifdef PROFILE + time_start = millis(); + #endif + break; + } - if (chr == 18) { // ^R - Retype the command line - _puts("#\b\r\n"); - preBS = curCol; //backspace to BOL - reType = chrsCnt; //retype everything - postBS = chrsCnt - curCol; //backspace to cursor column + if (chr == 0x0B) { // ^K - Delete to EOL from cursor + if (curCol < chrsCnt) { + reType = postBS = chrsCnt - curCol; + chrsCnt = curCol; // truncate buffer to here + } else { + _putcon('\007'); // ring the bell } + } - if (chr == 21) { // ^U - delete all characters - _puts("#\b\r\n"); - preBS = curCol; //backspace to BOL - chrsCnt = 0; - } + if (chr == 18) { // ^R - Retype the command line + _puts("#\b\n"); + preBS = curCol; // backspace to BOL + reType = chrsCnt; // retype everything + postBS = chrsCnt - curCol; // backspace to cursor column + } - if (chr == 23) { // ^W - recall last command - if (!curCol) { //if at beginning of command line - if (last[0]) { //and there's a last command - //restore last command - for (j = 0; j <= chrsCnt; j++) { - _RamWrite((chrsCntIdx + j) & 0xFFFF, last[j]); - } - //retype & backspace to greater of chrsCnt & last[0] - reType = postBS = (chrsCnt > last[0]) ? chrsCnt : last[0]; - chrsCnt = last[0]; - } else { - _putcon('\007'); //ring the bell - } - } else if (curCol < chrsCnt) { //if not at EOL - reType = chrsCnt - curCol; //move to EOL - } - } + if (chr == 21) { // ^U - delete all characters + _puts("#\b\n"); + preBS = curCol; // backspace to BOL + chrsCnt = 0; + } - if (chr == 24) { // ^X - delete all character left of the cursor - if (curCol > 0) { - //move rest of line to beginning of line - for (i = 0, j = curCol; j < chrsCnt;i++, j++) { - ch = _RamRead(((chrsIdx + j) & 0xFFFF)); - _RamWrite((chrsIdx +i) & 0xFFFF, ch); + if (chr == 23) { // ^W - recall last command + if (!curCol) { // if at beginning of command line + uint8 lastCnt = last[0]; + if (lastCnt) { // and there's a last command + // restore last command + for (j = 0; j <= lastCnt; j++) { + _RamWrite((chrsCntIdx + j) & 0xFFFF, last[j]); } - preBS = curCol; - reType = chrsCnt; - postBS = chrsCnt; - chrsCnt -= curCol; + // retype to greater of chrsCnt & lastCnt + reType = (chrsCnt > lastCnt) ? chrsCnt : lastCnt; + chrsCnt = lastCnt; // this is the restored length + // backspace to end of restored command + postBS = reType - chrsCnt; } else { - _putcon('\007'); //ring the bell + _putcon('\007'); // ring the bell } + } else if (curCol < chrsCnt) { // if not at EOL + reType = chrsCnt - curCol; // move to EOL } + } - if ((chr >= 0x20) && (chr <= 0x7E)) { //valid character - if (curCol < chrsCnt) { - //move rest of buffer one character right - for (i = chrsCnt, j = i - 1; i > curCol; i--, j--) { - ch = _RamRead(((chrsIdx + j) & 0xFFFF)); - _RamWrite((chrsIdx + i) & 0xFFFF, ch); - } + if (chr == 24) { // ^X - delete all character left of the cursor + if (curCol > 0) { + // move rest of line to beginning of line + for (i = 0, j = curCol; j < chrsCnt; i++, j++) { + ch = _RamRead(((chrsIdx + j) & 0xFFFF)); + _RamWrite((chrsIdx + i) & 0xFFFF, ch); } - //put the new character in the buffer - _RamWrite((chrsIdx + curCol) & 0xffff, chr); - - chrsCnt++; - reType = chrsCnt - curCol; - postBS = reType - 1; + preBS = curCol; + reType = chrsCnt; + postBS = chrsCnt; + chrsCnt -= curCol; + } else { + _putcon('\007'); // ring the bell } + } - //pre-backspace - for (i = 0; i < preBS; i++) { - _putcon('\b'); - curCol--; - } + if (chr == 31) { // ^? - help + _puts("\n\r^A Left ^B B/EOL ^C Abort ^E N/Lin ^F Right ^G Del@C ^H/Del BackSp\n\r"); + _puts("^K DelEOL ^R Retype ^U DelAll ^W Recall ^X DelBOL ^? Help\n\r"); + preBS = curCol; // backspace to BOL + reType = chrsCnt; // retype everything + postBS = chrsCnt - curCol; // backspace to cursor column + } - //retype - for (i = 0; i < reType; i++) { - if (curCol < chrsCnt) { - ch = _RamRead(((chrsIdx + curCol) & 0xFFFF)); - } else { - ch = ' '; + if (((chr >= 0x20) && (chr <= 0x7E)) || (chr == 0x1A)) { // valid character (allow ^Z) + if (curCol < chrsCnt) { + // move rest of buffer one character right + for (i = chrsCnt, j = i - 1; i > curCol; i--, j--) { + ch = _RamRead(((chrsIdx + j) & 0xFFFF)); + _RamWrite((chrsIdx + i) & 0xFFFF, ch); } - _putcon(ch); - curCol++; } + // put the new character in the buffer + _RamWrite((chrsIdx + curCol) & 0xffff, chr); - //post-backspace - for (i = 0; i < postBS; i++) { - _putcon('\b'); - curCol--; - } + chrsCnt++; + reType = chrsCnt - curCol; + postBS = reType - 1; + } + + // pre-backspace + for (i = 0; i < preBS; i++) { + _putcon('\b'); + curCol--; + } - if (chrsCnt == chrsMax) { // Reached the maximum count - break; + // retype + for (i = 0; i < reType; i++) { + if (curCol < chrsCnt) { + ch = _RamRead(((chrsIdx + curCol) & 0xFFFF)); + } else { + ch = ' '; } - } // while (chrsMax) + _putcon(ch); + curCol++; + } - // Save the number of characters read - _RamWrite(chrsCntIdx, chrsCnt); + // post-backspace + for (i = 0; i < postBS; i++) { + _putcon('\b'); + curCol--; + } - //if there are characters... - if (chrsCnt) { - //... then save this as last command - for (j = 0; j <= chrsCnt; j++) { - last[j] = _RamRead((chrsCntIdx + j) & 0xFFFF); - } + if (chrsCnt == chrsMax) // Reached the maximum count + break; + } // while (chrsMax) + + // Save the number of characters read + _RamWrite(chrsCntIdx, chrsCnt); + + // Return the number of characters read in A (low byte of HL) + HL = chrsCnt; + + // if there are characters... + if (chrsCnt) { + //... then save this as last command + for (j = 0; j <= chrsCnt; j++) { + last[j] = _RamRead((chrsCntIdx + j) & 0xFFFF); } -#if 0 - printf("\n\r chrsMaxIdx: %0X, chrsMax: %u, chrsCnt: %u", chrsMaxIdx, chrsMax, chrsCnt); - for (j = 0; j < chrsCnt + 2; j++) { - printf("\n\r chrsMaxIdx[%u]: %0.2x", j, last[j]); + } + _putcon('\r'); // Gives a visual feedback that read ended + break; + } + + /* + C = 11 (0Bh) : Get console status + Returns: A=0x00 or 0xFF + Under CP/M3 the console mode (BDOS 109) bits 8-9 override the result: + 1 = always ready, 2 = never ready, 0/3 = normal hardware status. + */ + case C_STAT: { +#ifdef CPM3 + uint8 sub = (consoleMode >> 8) & 0x03; + if (sub == 1) { + HL = 0xFF; // always returns true + break; + } + if (sub == 2) { + HL = 0x00; // always returns false + break; + } +#endif + HL = _chready(); + break; + } +#endif // ABDOS + + /* + C = 12 (0Ch) : Get version number + Returns: B=H=system type, A=L=version number + */ + case S_BDOSVER: { + #ifdef CPM3 + HL = 0x31; + #else + HL = 0x22; + #endif + break; + } + + /* + C = 13 (0Dh) : Reset disk system + */ + case DRV_ALLRESET: { + roVector = 0; // Make all drives R/W + loginVector = 0; + dmaAddr = 0x0080; + multiRecordCount = 1; // CP/M 3 BDOS resets multi-sector count on system reset + cDrive = 0; // userCode remains unchanged + HL = _CheckSUB(); // Checks if there's a $$$.SUB on the boot disk + break; + } + + /* + C = 14 (0Eh) : Select Disk + Returns: A=0x00 or 0xFF + */ + case DRV_SET: { + oDrive = cDrive; + cDrive = LOW_REGISTER(DE); + HL = _SelectDisk(LOW_REGISTER(DE) + 1); // +1 here is to allow SelectDisk to be used directly by disk.h as well + if (!HL) { + oDrive = cDrive; + } else { + if ((_RamRead(DSKByte) & 0x0f) == cDrive) { + cDrive = oDrive = 0; + _RamWrite(DSKByte, _RamRead(DSKByte) & 0xf0); + } else { + cDrive = oDrive; + } + } + break; + } + + /* + C = 15 (0Fh) : Open file + Entered with DE = address of FCB. + Returns: A = 0xFF on error or 0-3 on success (CP/M3 semantics). + On CP/M 3, a hardware error (when A=0xFF) may be returned in B/H. + If FCB->cr is 0xFF on entry, on return FCB->cr will contain the + last-record byte count (LRBC). + */ + case F_OPEN: { + HL = _OpenFile(DE); + break; + } + + /* + C = 16 (10h) : Close file + Entered with DE = address of FCB. + Returns: A = 0xFF on error or 0-3 on success. + On CP/M 3: If F5' (top bit of the 5th filename byte) is set then pending data + are written and the file remains open. + If A=0xFF, H/B contain hardware error. + */ + case F_CLOSE: { + HL = _CloseFile(DE); + break; + } + + /* + C = 17 (11h) : Search for first + */ + case F_SFIRST: { + HL = _SearchFirst(DE, TRUE); // TRUE = Creates a fake dir entry when finding the file + break; + } + + /* + C = 18 (12h) : Search for next + */ + case F_SNEXT: { + HL = _SearchNext(DE, TRUE); // TRUE = Creates a fake dir entry when finding the file + break; + } + + /* + C = 19 (13h) : Delete file + */ + case F_DELETE: { + HL = _DeleteFile(DE); + break; + } + + /* + C = 20 (14h) : Read sequential + DE = address of FCB + ToDo under CP/M 3 this can be a multiple of 128 bytes + Returns: A = return code + */ + case F_READ: { + HL = _ReadSeq(DE); + break; + } + + /* + C = 21 (15h) : Write sequential + DE = address of FCB + ToDo under CP/M 3 this can be a multiple of 128 bytes + Returns: A=return code + */ + case F_WRITE: { + HL = _WriteSeq(DE); + break; + } + + /* + C = 22 (16h) : Make file + */ + case F_MAKE: { + HL = _MakeFile(DE); + break; + } + + /* + C = 23 (17h) : Rename file + */ + case F_RENAME: { + HL = _RenameFile(DE); + break; + } + + /* + C = 24 (18h) : Return log-in vector (active drive map) + */ + case DRV_LOGINVEC: { + HL = loginVector; // (todo) improve this + break; + } + + /* + C = 25 (19h) : Return current disk + */ + case DRV_GET: { + HL = cDrive; + break; + } + + /* + C = 26 (1Ah) : Set DMA address + */ + case F_DMAOFF: { + dmaAddr = DE; + break; + } + + /* + C = 27 (1Bh) : Get ADDR(Alloc) + */ + case DRV_ALLOCVEC: { + HL = SCBaddr; + break; + } + + /* + C = 28 (1Ch) : Write protect current disk + */ + case DRV_SETRO: { + roVector = roVector | (1 << cDrive); + break; + } + + /* + C = 29 (1Dh) : Get R/O vector + */ + case DRV_ROVEC: { + HL = roVector; + break; + } + + /* + C = 30 (1Eh) : Set file attributes (does nothing) + */ + case F_ATTRIB: { + HL = 0; + break; + } + + /* + C = 31 (1Fh) : Get ADDR(Disk Parms) + */ + case DRV_PDB: { + HL = DPBaddr; + break; + } + + /* + C = 32 (20h) : Get/Set user code + */ + case F_USERNUM: { + if (LOW_REGISTER(DE) == 0xFF) { + HL = userCode; + } else { + _SetUser(DE); + } + break; + } + + /* + C = 33 (21h) : Read random + ToDo under CPM3, if A returns 0xFF, H returns hardware error + */ + case F_READRAND: { + HL = _ReadRand(DE); + break; + } + + /* + C = 34 (22h) : Write random + ToDo under CPM3, if A returns 0xFF, H returns hardware error + */ + case F_WRITERAND: { + HL = _WriteRand(DE); + break; + } + + /* + C = 35 (23h) : Compute file size + */ + case F_SIZE: { + HL = _GetFileSize(DE); + break; + } + + /* + C = 36 (24h) : Set random record + */ + case F_RANDREC: { + HL = _SetRandom(DE); + break; + } + + /* + C = 37 (25h) : Reset drive + */ + case DRV_RESET: { + roVector = roVector & ~DE; + break; + } + + /* + ToDo C = 38 (26h) : Access drives (CPM3) + This is an MP/M function that is not supported under CP/M 3. If called, the file + system returns a zero In register A indicating that the access request is successful. + */ + case DRV_ACCESS_MPM: { + HL = 0x0000; + break; + } + + /* + ToDo C = 39 (27h) : Free drives (CPM3) + This is an MP/M function that is not supported under CP/M 3. If called, the file + system returns a zero In register A indicating that the access request is successful. + */ + case DRV_FREE_MPM: { + HL = 0x0000; + break; + } + + /* + C = 40 (28h) : Write random with zero fill (we have no disk blocks, so just write random) + DE = address of FCB + Returns: A = return code + H = Physical Error + */ + case F_WRITEZF: { + HL = _WriteRand(DE); + break; + } + + /* + ToDo: C = 41 (29h) : Test and Write Record (CPM3) + DE = address of FCB + Returns: A = return code + H = Physical Error + */ + case F_TESTWRITE: { + break; + } + + /* + ToDo: C = 42 (2Ah) : Lock Record (CPM3) + DE = address of FCB + Returns: A = return code + H = Physical Error + */ + case F_LOCKFILE: { + break; + } + + /* + ToDo: C = 43 (2Bh) : Unlock Record (CPM3) + DE = address of FCB + Returns: A = return code + H = Physical Error + */ + case F_UNLOCKFILE: { + break; + } + + /* + C = 44 (2Ch) : Set number of records to read/write at once (CPM3) + E = Number of Sectors + Returns: A = return code (Returns A=0 if E was valid, 0FFh otherwise) + */ + case F_MULTISEC: { +#ifdef CPM3 + { + uint8 e = LOW_REGISTER(DE); + if ((e >= 1) && (e <= 127)) { + multiRecordCount = e; + HL = 0x0000; /* A = 0 => OK */ + } else { + HL = 0x00FF; /* A = 0xFF => invalid */ } + } +#else + /* Not supported under CP/M 2.2 */ + HL = 0x00FF; +#endif + break; + } + + /* + ToDo: C = 45 (2Dh) : Set BDOS Error Mode (CPM3) + E = BDOS Error Mode + E < 254 Compatibility mode; program is terminated and an error message printed. + E = 254 Error code is returned in H, error message is printed. + E = 255 Error code is returned in H, no error message is printed. + Returns: None + */ + case F_ERRMODE: { + break; + } + + /* + ToDo: C = 46 (2Eh) : Get Free Disk Space (CPM3) + E = Drive + Returns: A = return code + H = Physical Error + Binary result in the first 3 bytes of current DMA buffer + */ + case DRV_SPACE: { + break; + } + + /* + C = 47 (2Fh) : Chain to program (CPM3) + E = Chain flag (0xFF = pass current drive/user to the chained program, + otherwise the chained program starts at drive A: user 0) + The command line to run is stored null-terminated in the default DMA + buffer (0x0080). The call does not return to the caller: it warm boots + and the CCP runs the chained command. + */ + case P_CHAIN: { +#ifdef CPM3 + uint16 src = 0x0080; + uint8 n = 0; + uint8 c; + while (n < (uint8)(sizeof(chainCmd) - 1) && (c = _RamRead(src + n)) != 0) { + chainCmd[n] = c; + ++n; + } + chainCmd[n] = 0; + chainLoad = 1; + if (LOW_REGISTER(DE) != 0xFF) { // do not inherit drive/user + userCode = 0; + cDrive = oDrive = 0; + _RamWrite(DSKByte, 0x00); + } + Status = STATUS_RESTART; // warm boot into the CCP +#endif + break; + } + + /* + C = 48 (30h) : Flush Buffers (CPM3) + E = Purge flag + Returns: A = return code (0 = OK) + H = Physical Error + RunCPM opens, writes and closes each file record individually, so regular + file data is already on disk. The only host buffers held open across calls + are the LST: and PUN: device streams - flush those. + */ + case DRV_FLUSH: { +#ifdef USE_LST + if (lst_open) + _sys_fflush(lst_dev); +#endif +#ifdef USE_PUN + if (pun_open) + _sys_fflush(pun_dev); +#endif + HL = 0x0000; // A = 0 (OK), H = 0 (no physical error) + break; + } + + /* + C = 49 (31h) : Get/Set System Control (CPM3) + DE = SCB PB Address. SCBPB layout (bytes): + +0 offset (0-99 into the SCB) + +1 set (0xFF = set byte, 0xFE = set word, anything else = get) + +2 value (byte or word to store when setting) + Returns: A = byte at offset, HL = word at offset (on get). + The BDOS forces A = low byte of HL on return, so the byte + value naturally falls out of HL's low half. + */ + case S_SCB: { + uint16 pb = DE; + uint8 offset = _RamRead(pb + 0); + uint8 set = _RamRead(pb + 1); + if (set == 0xFF) { // set byte + _RamWrite(SCBaddr + offset, _RamRead(pb + 2)); + } else if (set == 0xFE) { // set word + _RamWrite16(SCBaddr + offset, _RamRead16(pb + 2)); + } else { // get byte/word + HL = _RamRead16(SCBaddr + offset); + } + break; + } + + /* + C = 50 (32h) : Direct BIOS Calls (CPM3) + DE = BIOS PB Address. BIOSPB layout (bytes): + +0 func (logical BIOS fn 0-32) +1 A +2 C +3 B +4 E +5 D +6 L +7 H + Returns: for SELDSK(9)/SECTRAN(16)/DEVTBL(20)/DRVTBL(22) the BIOS HL result + in HL (and A=L, B=H); for every other function the BIOS A result in A and L. + */ + case S_BIOS: { +#ifdef CPM3 + uint16 pb = DE; + uint8 fn = _RamRead(pb + 0); // logical BIOS function number + uint16 oldPC = PCX; + + // Load the Z80 registers the BIOS will see from the parameter block. + // BC/DE/HL are stored low-byte-first, so _RamRead16 reads them directly. + SET_HIGH_REGISTER(AF, _RamRead(pb + 1)); // A + BC = _RamRead16(pb + 2); // C,B + DE = _RamRead16(pb + 4); // E,D + HL = _RamRead16(pb + 6); // L,H + + // _Bios() dispatches on the low byte of PC, which equals (function * 3), + // i.e. the BIOS jump-table offset (B_WBOOT=3, B_SELMEM=81, ...). + SET_LOW_REGISTER(PCX, (uint8)(fn * 3)); + _Bios(); + PCX = oldPC; // restore PC so the caller resumes correctly + + // Map the BIOS return onto the BDOS return convention (A=L(HL), B=H(HL)). + // SELDSK/SECTRAN/DEVTBL/DRVTBL return in HL (leave HL as the BIOS set it); + // all others return in A, so place A into HL so A and L both hold it. + if (fn != 9 && fn != 16 && fn != 20 && fn != 22) + HL = HIGH_REGISTER(AF); +#endif // ifdef CPM3 + break; + } + + /* + ToDo: C = 59 (3Bh) : Load Overlay (CPM3) + DE = address of FCB + Returns: A = return code + H = Physical Error + */ + case P_LOAD: { + break; + } + + /* + C = 60 (3Ch) : Call Resident System Extension (RSX) (CPM3) + DE = RSX PB Address + Returns: A = return code + H = Physical Error + */ + case S_RSX: { +#ifdef CPM3 + // No RSX modules are loaded, so report the call as not handled + // (A = 0FFh). + HL = 0x00FF; +#endif // ifdef CPM3 + break; + } + + /* + ToDo: C = 98 (62h) : Free Blocks (CPM3) + Returns: A = return code + H = Physical Error + */ + case F_CLEANUP: { + break; + } + + /* + C = 99 (63h) : Truncate File (CPM3) + DE = address of FCB. The random record field (r0/r1/r2) holds the new + size in 128-byte records; the file is truncated to record * 128. + Returns: A = Directory code (0 = OK, 0xFF = error) + H = Extended or Physical Error + */ + case F_TRUNCATE: { +#ifdef CPM3 + HL = _TruncateFile(DE); +#endif + break; + } + + /* + ToDo: C = 100 (64h) : Set Directory Label (CPM3) + DE = address of FCB + Returns: A = Directory code + H = Extended or Physical Error + */ + case DRV_SETLABEL: { + break; + } + + /* + ToDo: C = 101 (65h) : Return Directory Label Data (CPM3) + E = Drive + Returns: A = Directory Label Data Byte or 0xFF + H = Physical Error + */ + case DRV_GETLABEL: { + break; + } + + /* + C = 102 (66h) : Read File Date Stamps and Password Mode (CPM3) + DE = address of FCB + Returns: A = Directory code (0xFF if the file was not found) + H = Physical Error + On success the FCB is filled in: + FCB+9 bit7 (t1') = set if the file is read-only + FCB+24..27 = create/access date stamp + FCB+28..31 = update date stamp + FCB+12 (ex) = password mode (0, no password support here) + Each date stamp is: day count (word, day 1 = 1978-01-01), hour (BCD), + minute (BCD). The stamps are derived from the host file via BDOS only. + */ + case F_TIMEDATE: { +#ifdef CPM3 + uint8 result = 0xff; + _FCBtoHostname(DE, &filename[0]); + unsigned long mt = _sys_filemtime(&filename[0]); + if (mt) { + time_t ft = (time_t)mt; + struct tm lt = *localtime(&ft); + struct tm base, fday; + time_t baseNoon, fNoon; + uint16 days; + uint8 bh = DEC2BCD(lt.tm_hour); + uint8 bm = DEC2BCD(lt.tm_min); + // Whole days between the file day (noon) and 1978-01-01 (noon) + memset(&base, 0, sizeof(base)); + base.tm_year = 78; + base.tm_mon = 0; + base.tm_mday = 1; + base.tm_hour = 12; + base.tm_isdst = -1; + baseNoon = mktime(&base); + fday = lt; + fday.tm_hour = 12; + fday.tm_min = 0; + fday.tm_sec = 0; + fday.tm_isdst = -1; + fNoon = mktime(&fday); + days = (uint16)((fNoon - baseNoon) / 86400 + 1); + // Create/access stamp (host only exposes one timestamp, reuse it) + _RamWrite(DE + 24, days & 0xff); + _RamWrite(DE + 25, (days >> 8) & 0xff); + _RamWrite(DE + 26, bh); + _RamWrite(DE + 27, bm); + // Update stamp + _RamWrite(DE + 28, days & 0xff); + _RamWrite(DE + 29, (days >> 8) & 0xff); + _RamWrite(DE + 30, bh); + _RamWrite(DE + 31, bm); + // Password mode (none supported) + _RamWrite(DE + 12, 0x00); + // Read-only attribute lives in t1' (high bit of the first type byte) + if (_sys_isreadonly(&filename[0])) + _RamWrite(DE + 9, _RamRead(DE + 9) | 0x80); + else + _RamWrite(DE + 9, _RamRead(DE + 9) & 0x7f); + result = 0x00; + } + HL = result; #endif - _putcon('\r'); // Gives a visual feedback that read ended - break; - } - - /* - C = 11 (0Bh) : Get console status - Returns: A=0x00 or 0xFF - */ - case C_STAT: { - HL = _chready(); - break; - } - - /* - C = 12 (0Ch) : Get version number - Returns: B=H=system type, A=L=version number - */ - case GET_VERSION: { - HL = 0x22; - break; - } - - /* - C = 13 (0Dh) : Reset disk system - */ - case DRV_ALLRESET: { - roVector = 0; // Make all drives R/W - loginVector = 0; - dmaAddr = 0x0080; - cDrive = 0; // userCode remains unchanged - HL = _CheckSUB(); // Checks if there's a $$$.SUB on the boot disk - break; - } - - /* - C = 14 (0Eh) : Select Disk - Returns: A=0x00 or 0xFF - */ - case DRV_SET: { - oDrive = cDrive; - cDrive = LOW_REGISTER(DE); - HL = _SelectDisk(LOW_REGISTER(DE) + 1); // +1 here is to allow SelectDisk to be used directly by disk.h as well - if (!HL) { - oDrive = cDrive; - } else { - if ((_RamRead(DSKByte) & 0x0f) == cDrive) { - cDrive = oDrive = 0; - _RamWrite(DSKByte, _RamRead(DSKByte) & 0xf0); - } else { - cDrive = oDrive; - } - } - break; - } - - /* - C = 15 (0Fh) : Open file - Returns: A=0x00 or 0xFF - */ - case F_OPEN: { - HL = _OpenFile(DE); - break; - } - - /* - C = 16 (10h) : Close file - */ - case F_CLOSE: { - HL = _CloseFile(DE); - break; - } - - /* - C = 17 (11h) : Search for first - */ - case F_SEARCH_FIRST: { - HL = _SearchFirst(DE, TRUE); // TRUE = Creates a fake dir entry when finding the file - break; - } - - /* - C = 18 (12h) : Search for next - */ - case F_SEARCH_NEXT: { - HL = _SearchNext(DE, TRUE); // TRUE = Creates a fake dir entry when finding the file - break; - } - - /* - C = 19 (13h) : Delete file - */ - case F_DELETE: { - HL = _DeleteFile(DE); - break; - } - - /* - C = 20 (14h) : Read sequential - */ - case F_READ: { - HL = _ReadSeq(DE); - break; - } - - /* - C = 21 (15h) : Write sequential - */ - case F_WRITE: { - HL = _WriteSeq(DE); - break; - } - - /* - C = 22 (16h) : Make file - */ - case F_MAKE: { - HL = _MakeFile(DE); - break; - } - - /* - C = 23 (17h) : Rename file - */ - case F_RENAME: { - HL = _RenameFile(DE); - break; - } - - /* - C = 24 (18h) : Return log-in vector (active drive map) - */ - case DRV_LOGINVECTOR: { - HL = loginVector; // (todo) improve this - break; - } - - /* - C = 25 (19h) : Return current disk - */ - case DRV_GET: { - HL = cDrive; - break; - } - - /* - C = 26 (1Ah) : Set DMA address - */ - case F_DMAOFF: { - dmaAddr = DE; - break; - } - - /* - C = 27 (1Bh) : Get ADDR(Alloc) - */ - case DRV_GETADDRALLOC: { - HL = SCBaddr; - break; - } - - /* - C = 28 (1Ch) : Write protect current disk - */ - case DRV_WRITEPROTECT: { - roVector = roVector | (1 << cDrive); - break; - } - - /* - C = 29 (1Dh) : Get R/O vector - */ - case DRV_GETROVECTOR: { - HL = roVector; - break; - } - - /* - C = 30 (1Eh) : Set file attributes (does nothing) - */ - case F_SETATTRIBUTES: { - HL = 0; - break; - } - - /* - C = 31 (1Fh) : Get ADDR(Disk Parms) - */ - case DRV_GETDPB: { - HL = DPBaddr; - break; - } - - /* - C = 32 (20h) : Get/Set user code - */ - case F_USERNUM: { - if (LOW_REGISTER(DE) == 0xFF) { - HL = userCode; - } else { - _SetUser(DE); - } - break; - } - - /* - C = 33 (21h) : Read random - */ - case F_READRANDOM: { - HL = _ReadRand(DE); - break; - } - - /* - C = 34 (22h) : Write random - */ - case F_WRITERANDOM: { - HL = _WriteRand(DE); - break; - } - - /* - C = 35 (23h) : Compute file size - */ - case F_COMPUTESIZE: { - HL = _GetFileSize(DE); - break; - } - - /* - C = 36 (24h) : Set random record - */ - case F_SETRANDOM: { - HL = _SetRandom(DE); - break; - } - - /* - C = 37 (25h) : Reset drive - */ - case DRV_RESET: { - roVector = roVector & ~DE; - break; - } - - /* ********* Function 38: Not supported by CP/M 2.2 ********* - ********* Function 39: Not supported by CP/M 2.2 ********* - ********* (todo) Function 40: Write random with zero fill ********* - */ - - /* - C = 40 (28h) : Write random with zero fill (we have no disk blocks, so just write random) - */ - case F_WRITERANDOMZERO: { - HL = _WriteRand(DE); - break; - } + break; + } + + /* + ToDo: C = 103 (67h) : Write File XFCB (CPM3) + DE = address of FCB + Returns: A = Directory code + H = Physical Error + */ + case F_WRITEXFCB: { + break; + } + + /* + C = 104 (68h) : Set Date and Time (CPM3) + DE = Date and Time (DAT) Address + DAT+0/1 = Day count (little-endian, day 1 = 1978-01-01) + DAT+2 = Hour (packed BCD) + DAT+3 = Minute (packed BCD) + DAT+4 = Second (packed BCD) + Returns: None + */ + case T_SET: { + uint16 days = _RamRead(DE) | (_RamRead(DE + 1) << 8); + uint8 hour = BCD2DEC(_RamRead(DE + 2)); + uint8 mins = BCD2DEC(_RamRead(DE + 3)); + uint8 secs = BCD2DEC(_RamRead(DE + 4)); + struct tm base; + time_t baseT, target; + memset(&base, 0, sizeof(base)); + base.tm_year = 78; // 1978 + base.tm_mon = 0; // January + base.tm_mday = 1; // 1st + base.tm_hour = 12; // noon, to avoid DST midnight ambiguity + base.tm_isdst = -1; + baseT = mktime(&base); + // baseT is noon on day 1; rewind to midnight, then add the DAT fields + target = baseT - 12 * 3600 + (time_t)(days - 1) * 86400 + + (time_t)hour * 3600 + (time_t)mins * 60 + secs; + clockOffset = (long)(target - time(NULL)); + break; + } + + /* + C = 105 (69h) : Get Date and Time (CPM3) + DE = Date and Time (DAT) Address (filled in, same layout as T_SET) + Returns: Date and Time (DAT) set + A = Seconds (in packed BCD format) + */ + case T_GET: { + time_t now = time(NULL) + clockOffset; + struct tm cur, base; + time_t curNoon, baseNoon; + uint16 days; + uint8 hour, mins, secs; + cur = *localtime(&now); + hour = DEC2BCD(cur.tm_hour); + mins = DEC2BCD(cur.tm_min); + secs = DEC2BCD(cur.tm_sec); + // Day count = whole days between today (noon) and 1978-01-01 (noon) + cur.tm_hour = 12; + cur.tm_min = 0; + cur.tm_sec = 0; + cur.tm_isdst = -1; + curNoon = mktime(&cur); + memset(&base, 0, sizeof(base)); + base.tm_year = 78; + base.tm_mon = 0; + base.tm_mday = 1; + base.tm_hour = 12; + base.tm_isdst = -1; + baseNoon = mktime(&base); + days = (uint16)((curNoon - baseNoon) / 86400 + 1); + _RamWrite(DE, days & 0xFF); + _RamWrite(DE + 1, (days >> 8) & 0xFF); + _RamWrite(DE + 2, hour); + _RamWrite(DE + 3, mins); + _RamWrite(DE + 4, secs); + HL = secs; // A (low byte of HL) = seconds in packed BCD + break; + } + + /* + ToDo: C = 106 (6Ah) : Set Default Password (CPM3) + DE = Password Address + Returns: None + */ + case F_PASSWD: { + break; + } + + /* + C = 107 (6Bh) : Return Serial Number (CPM3) + DE = Serial Number Field (6 bytes) + Returns: Serial number field set (printable ASCII) + */ + case S_SERIAL: { + static const uint8 serial[6] = {'R', 'u', 'n', 'C', 'P', 'M'}; + uint8 i; + for (i = 0; i < 6; ++i) + _RamWrite(DE + i, serial[i]); + break; + } + + /* + C = 108 (6Ch) : Get/Set Program Return Code (CPM3) + DE = 0xFFFF (Get) or Program Return Code (Set) + Returns: HL = Program Return Code (on Get) + */ + case P_CODE: { + if (WORD16(DE) == 0xFFFF) { + HL = programRetCode; + } else { + programRetCode = WORD16(DE); + } + break; + } + + /* + C = 109 (6Dh) : Get/Set Console Mode (CPM3) + DE = 0xFFFF (Get) or Console Mode (Set) + Returns: HL = Console Mode (on Get) + Console mode bits (CP/M3): 0 = fn 11 detects only ^C, 1 = ^S no pause, + 2 = no tab expand / no ^P echo, 3 = ^C does not terminate. RunCPM's + console is already raw, so those behaviours are intrinsic; bits 8-9 are + acted on by function 11 (Get console status). + */ + case C_MODE: { + if (WORD16(DE) == 0xFFFF) { + HL = consoleMode; + } else { + consoleMode = WORD16(DE); + } + break; + } + + /* + C = 110 (6Eh) : Get/Set Output Delimiter (CPM3) + DE = 0xFFFF (Get) or E = Delimiter (Set) + Returns: A = Output Delimiter (on Get) + The delimiter terminates strings printed by function 9 (default '$'). + */ + case C_DELIMIT: { + if (DE == 0xFFFF) { + HL = outputDelimiter; // A will receive low byte of HL + } else { + outputDelimiter = LOW_REGISTER(DE); + } + break; + } + + /* + C = 111 (6Fh) : Print Block (CPM3) + DE = address of a Character Control Block (CCB): + CCB+0 = buffer address (word) + CCB+2 = length in bytes (word) + Sends the block to the console using an explicit length (no delimiter). + Returns: None + */ + case C_WRITEBLK: { + uint16 addr = _RamRead16(DE); + uint16 len = _RamRead16(DE + 2); + while (len--) + _putcon(_RamRead(addr++)); + break; + } + + /* + ToDo: C = 112 (70h) : List Block (CPM3) + DE = address of CCB + Returns: None + */ + case L_WRITEBLK: { + break; + } + + /* + ToDo: C = 152 (98h) : List Block (CPM3) + DE = address of PFCB + Returns: HL = Return code + Parsed file control block + */ + case F_PARSE: { + break; + } #if defined board_digital_io - /* - C = 220 (DCh) : PinMode - */ - case 220: { - pinMode(HIGH_REGISTER(DE), LOW_REGISTER(DE)); - break; - } - - /* - C = 221 (DDh) : DigitalRead - */ - case 221: { - HL = digitalRead(HIGH_REGISTER(DE)); - break; - } - - /* - C = 222 (DEh) : DigitalWrite - */ - case 222: { - digitalWrite(HIGH_REGISTER(DE), LOW_REGISTER(DE)); - break; - } - - /* - C = 223 (DFh) : AnalogRead - */ - case 223: { - HL = analogRead(HIGH_REGISTER(DE)); - break; - } - + /* + C = 220 (DCh) : PinMode + */ + case F_PINMODE: { + pinMode(HIGH_REGISTER(DE), LOW_REGISTER(DE)); + break; + } + + /* + C = 221 (DDh) : DigitalRead + */ + case F_DREAD: { + HL = digitalRead(HIGH_REGISTER(DE)); + break; + } + + /* + C = 222 (DEh) : DigitalWrite + */ + case F_DWRITE: { + digitalWrite(HIGH_REGISTER(DE), LOW_REGISTER(DE)); + break; + } + + /* + C = 223 (DFh) : AnalogRead + */ + case F_AREAD: { + HL = analogRead(HIGH_REGISTER(DE)); + break; + } #endif // if defined board_digital_io #if defined board_analog_io - /* - C = 224 (E0h) : AnalogWrite - */ - case 224: { - analogWrite(HIGH_REGISTER(DE), LOW_REGISTER(DE)); - break; - } - + /* + C = 224 (E0h) : AnalogWrite + */ + case F_AWRITE: { + analogWrite(HIGH_REGISTER(DE), LOW_REGISTER(DE)); + break; + } #endif // if defined board_analog_io - /* - C = 230 (E6h) : Set 8 bit masking - */ - case 230: { - mask8bit = LOW_REGISTER(DE); - break; - } - - /* - C = 231 (E7h) : Host specific BDOS call - */ - case 231: { - HL = hostbdos(DE); - break; - } - - /* - C = 232 (E8h) : ESP32 specific BDOS call - */ + /* + C = 230 (E6h) : Set 8 bit masking + */ + case F_SETMASK: { + mask8bit = LOW_REGISTER(DE); + break; + } + + /* + C = 231 (E7h) : Host specific BDOS call + */ + case F_BDOSCALL: { + HL = hostbdos(DE); + break; + } + + /* + C = 232 (E8h) : ESP32 specific BDOS call + */ #if defined board_esp32 - case 232: { - HL = esp32bdos(DE); - break; - } + case 232: { + HL = esp32bdos(DE); + break; + } #endif // if defined board_esp32 #if defined board_stm32 - case 232: { - HL = stm32bdos(DE); - break; - } + case 232: { + HL = stm32bdos(DE); + break; + } #endif // if defined board_stm32 - /* - C = 249 (F9h) : MakeDisk - Makes a disk directory if not existent. - */ - case 249: { - HL = _MakeDisk(DE); - break; - } - - /* - C = 250 (FAh) : HostOS - Returns: A = 0x00 - Windows / 0x01 - Arduino / 0x02 - Posix / 0x03 - Dos / 0x04 - Teensy / 0x05 - ESP32 / 0x06 - STM32 - */ - case 250: { - HL = HostOS; - break; - } - - /* - C = 251 (FBh) : Version - Returns: A = 0xVv - Version in BCD representation: V.v - */ - case 251: { - HL = VersionBCD; - break; - } - - /* - C = 252 (FCh) : CCP version - Returns: A = 0x00-0x04 = DRI|CCPZ|ZCPR2|ZCPR3|Z80CCP / 0xVv = Internal version in BCD: V.v - */ - case 252: { - HL = VersionCCP; - break; - } - - /* - C = 253 (FDh) : CCP address - */ - case 253: { - HL = CCPaddr; - break; - } - -#ifdef HASLUA - - /* - C = 254 (FEh) : Run Lua file - */ - case 254: { - HL = _RunLua(DE); - break; - } - -#endif // ifdef HASLUA - - /* - Unimplemented calls get listed - */ - default: { -#ifdef DEBUG // Show unimplemented BDOS calls only when debugging - _puts( "\r\nUnimplemented BDOS call.\r\n"); - _puts( "C = 0x"); - _puthex8(ch); - _puts("\r\n"); -#endif // ifdef DEBUG - break; - } - } // switch - - // CP/M BDOS does this before returning - SET_HIGH_REGISTER( BC, HIGH_REGISTER(HL)); - SET_HIGH_REGISTER( AF, LOW_REGISTER(HL)); + /* + C = 248 (F8h) : Milliseconds Uptime + Returns the number of milliseconds (since the board started). + */ + case F_UPTIME: { + timer = millis(); + HL = timer & 0xFFFF; + DE = (timer >> 16) & 0xFFFF; + break; + } + + /* + C = 249 (F9h) : MakeDisk + Makes a disk directory if not existent. + */ + case F_MAKEDISK: { + HL = _MakeDisk(DE); + break; + } + + /* + C = 250 (FAh) : HostOS + Returns: A = 0x00 - Windows / 0x01 - Arduino / 0x02 - Posix / 0x03 - Dos / 0x04 - Teensy / 0x05 - ESP32 / 0x06 - STM32 + */ + case F_HOSTOS: { + HL = HostOS; + break; + } + + /* + C = 251 (FBh) : Version + Returns: A = 0xVv - Version in BCD representation: V.v + */ + case F_VERSION: { + HL = VersionBCD; + break; + } + + /* + C = 252 (FCh) : CCP version + Returns: A = 0x00-0x04 = DRI|CCPZ|ZCPR2|ZCPR3|Z80CCP / 0xVv = Internal version in BCD: V.v + */ + case F_CCPVERSION: { + HL = VersionCCP; + break; + } + + /* + C = 253 (FDh) : CCP address + */ + case F_CCPADDR: { + HL = CCPaddr; + break; + } + + /* + C = 254 (FEh) : Set CPU speed + DE = Number of instructions to trigger the delay + */ + case F_SETCPUSPEED: { + cpuDelayInstructions = DE; + break; + } + + /* + Unimplemented calls get listed + */ + default: { +#if RUNCPMDEBUG // Show unimplemented BDOS calls only when debugging + _puts("\r\nUnimplemented BDOS call.\r\n"); + _puts("C = 0x"); + _puthex8(ch); + _puts("\r\n"); +#endif // RUNCPMDEBUG + break; + } + } // switch + + // CP/M BDOS does this before returning + SET_HIGH_REGISTER(BC, HIGH_REGISTER(HL)); + SET_HIGH_REGISTER(AF, LOW_REGISTER(HL)); #ifdef DEBUGLOG - _logBdosOut(ch); + _logBdosOut(ch); #endif } // _Bdos #endif // ifndef CPM_H - diff --git a/lib/runcpm/cpu.h b/lib/runcpm/cpu.h index 1d53a9b00..394bc872a 100644 --- a/lib/runcpm/cpu.h +++ b/lib/runcpm/cpu.h @@ -1,14 +1,16 @@ #ifndef CPU_H #define CPU_H -/* see main.c for definition */ - -/* Fallback: globals.h normally defines RUNCPM_DECL before cpu.h is included. - * This guard handles the case where cpu.h is inspected in isolation. */ #ifndef RUNCPM_DECL #define RUNCPM_DECL #endif +/* Model 1 - Larger code, original */ +/* 6 MHz on an Arduino Due */ + +#define CPU_IS "Model 1" + +/* Register Definitions */ RUNCPM_DECL int32 PCX; /* external view of PC */ RUNCPM_DECL int32 AF; /* AF register */ RUNCPM_DECL int32 BC; /* BC register */ @@ -24,11 +26,18 @@ RUNCPM_DECL int32 DE1; /* alternate DE register */ RUNCPM_DECL int32 HL1; /* alternate HL register */ RUNCPM_DECL int32 IFF; /* Interrupt Flip Flop */ RUNCPM_DECL int32 IR; /* Interrupt (upper) / Refresh (lower) register */ -RUNCPM_DECL int32 Status = 0; /* Status of the CPU 0=running 1=end request 2=back to CCP */ +RUNCPM_DECL int32 Status = STATUS_RUNNING; /* Status of the CPU 0=running 1=end request 2=back to CCP */ RUNCPM_DECL int32 Debug = 0; -RUNCPM_DECL int32 Break = -1; RUNCPM_DECL int32 Step = -1; +/* FujiNet: 6.9 moved `Break`/`Watch` into the DEBUG-gated debug.h; keep them as + plain globals here since FujiNet assigns them unconditionally. `Watch` is + suppressed under DEBUG/iDEBUG to avoid clashing with debug.h. */ +RUNCPM_DECL int32 Break = -1; +#if !RUNCPMDEBUG && !defined(iDEBUG) +RUNCPM_DECL int32 Watch = -1; +#endif + #ifdef iDEBUG RUNCPM_DECL FILE* iLogFile; RUNCPM_DECL char iLogBuffer[256]; @@ -45,34 +54,35 @@ RUNCPM_DECL const char* iLogTxt; /* Functions needed by the soft CPU implementation */ -RUNCPM_DECL void cpu_out(const uint32 Port, const uint32 Value) { - if (Port == 0xFF) { +RUNCPM_DECL void cpu_out(const uint32 p, const uint32 v) { +#ifdef INT_HANDOFF + _HardwareOut(p, v); +#else + if (p == 0xFF) { _Bios(); } else { - _HardwareOut(Port, Value); + _HardwareOut(p, v); } +#endif } -RUNCPM_DECL uint32 cpu_in(const uint32 Port) { - uint32 Result; - if (Port == 0xFF) { +RUNCPM_DECL uint32 cpu_in(const uint32 p) { + uint32 v; +#ifdef INT_HANDOFF + v = _HardwareIn(p); +#else + if (p == 0xFF) { _Bdos(); - Result = HIGH_REGISTER(AF); + v = HIGH_REGISTER(AF); } else { - Result = _HardwareIn(Port); + v = _HardwareIn(p); } - return(Result); +#endif + return(v); } /* Z80 Custom soft core */ -/* simulator stop codes */ -#define STOP_HALT 0 /* HALT */ -#define STOP_IBKPT 1 /* breakpoint (program counter) */ -#define STOP_MEM 2 /* breakpoint (memory access) */ -#define STOP_INSTR 3 /* breakpoint (instruction access) */ -#define STOP_OPCODE 4 /* invalid operation encountered (8080, Z80, 8086) */ - #define ADDRMASK 0xffff #define FLAG_C 1 @@ -85,14 +95,12 @@ RUNCPM_DECL uint32 cpu_in(const uint32 Port) { #define SETFLAG(f,c) (AF = (c) ? AF | FLAG_ ## f : AF & ~FLAG_ ## f) #define TSTFLAG(f) ((AF & FLAG_ ## f) != 0) -#define PARITY(x) parityTable[(x) & 0xff] - #define SET_PVS(s) (((cbits >> 6) ^ (cbits >> 5)) & 4) #define SET_PV (SET_PVS(sum)) #define SET_PV2(x) ((temp == (x)) << 2) #define POP(x) { \ - uint32 y = RAM_PP(SP); \ + uint32 y = RAM_PP(SP); \ x = y + (RAM_PP(SP) << 8); \ } @@ -100,17 +108,19 @@ RUNCPM_DECL uint32 cpu_in(const uint32 Port) { if (cond) { \ PC = GET_WORD(PC); \ } else { \ - PC += 2; \ + PC++; \ + PC++; \ } \ } #define CALLC(cond) { \ if (cond) { \ - uint32 adrr = GET_WORD(PC); \ + uint32 a = GET_WORD(PC); \ PUSH(PC + 2); \ - PC = adrr; \ + PC = a; \ } else { \ - PC += 2; \ + PC++; \ + PC++; \ } \ } @@ -140,7 +150,10 @@ rrdrldTable[i] 0..255 (i << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) cpTable[i] 0..255 (i & 0x80) | (((i & 0xff) == 0) << 6) */ +//#define preTables // Use precomputed tables (increases the size of the binary by 4k or more) + /* parityTable[i] = (number of 1's in i is odd) ? 0 : 4, i = 0..255 */ +#ifdef preTables static const uint8 parityTable[256] = { 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, @@ -904,247 +917,89 @@ static const uint8 cpTable[256] = { 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, }; -#if defined(DEBUG) || defined(iDEBUG) -static const char* Mnemonics[256] = -{ - "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", - "EX AF,AF'", "ADD HL,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", - "DJNZ @h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", - "JR @h", "ADD HL,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", - "JR NZ,@h", "LD HL,#h", "LD (#h),HL", "INC HL", "INC H", "DEC H", "LD H,*h", "DAA", - "JR Z,@h", "ADD HL,HL", "LD HL,(#h)", "DEC HL", "INC L", "DEC L", "LD L,*h", "CPL", - "JR NC,@h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL),*h", "SCF", - "JR C,@h", "ADD HL,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", - "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,H", "LD B,L", "LD B,(HL)", "LD B,A", - "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,H", "LD C,L", "LD C,(HL)", "LD C,A", - "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,H", "LD D,L", "LD D,(HL)", "LD D,A", - "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,H", "LD E,L", "LD E,(HL)", "LD E,A", - "LD H,B", "LD H,C", "LD H,D", "LD H,E", "LD H,H", "LD H,L", "LD H,(HL)", "LD H,A", - "LD L,B", "LD L,C", "LD L,D", "LD L,E", "LD L,H", "LD L,L", "LD L,(HL)", "LD L,A", - "LD (HL),B", "LD (HL),C", "LD (HL),D", "LD (HL),E", "LD (HL),H", "LD (HL),L", "HALT", "LD (HL),A", - "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,H", "LD A,L", "LD A,(HL)", "LD A,A", - "ADD B", "ADD C", "ADD D", "ADD E", "ADD H", "ADD L", "ADD (HL)", "ADD A", - "ADC B", "ADC C", "ADC D", "ADC E", "ADC H", "ADC L", "ADC (HL)", "ADC A", - "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", - "SBC B", "SBC C", "SBC D", "SBC E", "SBC H", "SBC L", "SBC (HL)", "SBC A", - "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", - "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", - "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", - "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", - "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD *h", "RST 00h", - "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC *h", "RST 08h", - "RET NC", "POP DE", "JP NC,#h", "OUTA (*h)", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", - "RET C", "EXX", "JP C,#h", "INA (*h)", "CALL C,#h", "PFX_DD", "SBC *h", "RST 18h", - "RET PO", "POP HL", "JP PO,#h", "EX HL,(SP)", "CALL PO,#h", "PUSH HL", "AND *h", "RST 20h", - "RET PE", "LD PC,HL", "JP PE,#h", "EX DE,HL", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", - "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", - "RET M", "LD SP,HL", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" -}; - -static const char* MnemonicsCB[256] = -{ - "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", - "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", - "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", - "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", - "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", - "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", - "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (HL)", "SLL A", - "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", - "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(HL)", "BIT 0,A", - "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(HL)", "BIT 1,A", - "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(HL)", "BIT 2,A", - "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(HL)", "BIT 3,A", - "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(HL)", "BIT 4,A", - "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(HL)", "BIT 5,A", - "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(HL)", "BIT 6,A", - "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(HL)", "BIT 7,A", - "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(HL)", "RES 0,A", - "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(HL)", "RES 1,A", - "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(HL)", "RES 2,A", - "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(HL)", "RES 3,A", - "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(HL)", "RES 4,A", - "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(HL)", "RES 5,A", - "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(HL)", "RES 6,A", - "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(HL)", "RES 7,A", - "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(HL)", "SET 0,A", - "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(HL)", "SET 1,A", - "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(HL)", "SET 2,A", - "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(HL)", "SET 3,A", - "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(HL)", "SET 4,A", - "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(HL)", "SET 5,A", - "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(HL)", "SET 6,A", - "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(HL)", "SET 7,A" -}; - -static const char* MnemonicsED[256] = -{ - "DB EDh,00h", "DB EDh,01h", "DB EDh,02h", "DB EDh,03h", - "DB EDh,04h", "DB EDh,05h", "DB EDh,06h", "DB EDh,07h", - "DB EDh,08h", "DB EDh,09h", "DB EDh,0Ah", "DB EDh,0Bh", - "DB EDh,0Ch", "DB EDh,0Dh", "DB EDh,0Eh", "DB EDh,0Fh", - "DB EDh,10h", "DB EDh,11h", "DB EDh,12h", "DB EDh,13h", - "DB EDh,14h", "DB EDh,15h", "DB EDh,16h", "DB EDh,17h", - "DB EDh,18h", "DB EDh,19h", "DB EDh,1Ah", "DB EDh,1Bh", - "DB EDh,1Ch", "DB EDh,1Dh", "DB EDh,1Eh", "DB EDh,1Fh", - "DB EDh,20h", "DB EDh,21h", "DB EDh,22h", "DB EDh,23h", - "DB EDh,24h", "DB EDh,25h", "DB EDh,26h", "DB EDh,27h", - "DB EDh,28h", "DB EDh,29h", "DB EDh,2Ah", "DB EDh,2Bh", - "DB EDh,2Ch", "DB EDh,2Dh", "DB EDh,2Eh", "DB EDh,2Fh", - "DB EDh,30h", "DB EDh,31h", "DB EDh,32h", "DB EDh,33h", - "DB EDh,34h", "DB EDh,35h", "DB EDh,36h", "DB EDh,37h", - "DB EDh,38h", "DB EDh,39h", "DB EDh,3Ah", "DB EDh,3Bh", - "DB EDh,3Ch", "DB EDh,3Dh", "DB EDh,3Eh", "DB EDh,3Fh", - "IN B,(C)", "OUT (C),B", "SBC HL,BC", "LD (#h),BC", - "NEG", "RETN", "IM 0", "LD I,A", - "IN C,(C)", "OUT (C),C", "ADC HL,BC", "LD BC,(#h)", - "DB EDh,4Ch", "RETI", "DB EDh,4Eh", "LD R,A", - "IN D,(C)", "OUT (C),D", "SBC HL,DE", "LD (#h),DE", - "DB EDh,54h", "DB EDh,55h", "IM 1", "LD A,I", - "IN E,(C)", "OUT (C),E", "ADC HL,DE", "LD DE,(#h)", - "DB EDh,5Ch", "DB EDh,5Dh", "IM 2", "LD A,R", - "IN H,(C)", "OUT (C),H", "SBC HL,HL", "LD (#h),HL", - "DB EDh,64h", "DB EDh,65h", "DB EDh,66h", "RRD", - "IN L,(C)", "OUT (C),L", "ADC HL,HL", "LD HL,(#h)", - "DB EDh,6Ch", "DB EDh,6Dh", "DB EDh,6Eh", "RLD", - "IN F,(C)", "DB EDh,71h", "SBC HL,SP", "LD (#h),SP", - "DB EDh,74h", "DB EDh,75h", "DB EDh,76h", "DB EDh,77h", - "IN A,(C)", "OUT (C),A", "ADC HL,SP", "LD SP,(#h)", - "DB EDh,7Ch", "DB EDh,7Dh", "DB EDh,7Eh", "DB EDh,7Fh", - "DB EDh,80h", "DB EDh,81h", "DB EDh,82h", "DB EDh,83h", - "DB EDh,84h", "DB EDh,85h", "DB EDh,86h", "DB EDh,87h", - "DB EDh,88h", "DB EDh,89h", "DB EDh,8Ah", "DB EDh,8Bh", - "DB EDh,8Ch", "DB EDh,8Dh", "DB EDh,8Eh", "DB EDh,8Fh", - "DB EDh,90h", "DB EDh,91h", "DB EDh,92h", "DB EDh,93h", - "DB EDh,94h", "DB EDh,95h", "DB EDh,96h", "DB EDh,97h", - "DB EDh,98h", "DB EDh,99h", "DB EDh,9Ah", "DB EDh,9Bh", - "DB EDh,9Ch", "DB EDh,9Dh", "DB EDh,9Eh", "DB EDh,9Fh", - "LDI", "CPI", "INI", "OUTI", - "DB EDh,A4h", "DB EDh,A5h", "DB EDh,A6h", "DB EDh,A7h", - "LDD", "CPD", "IND", "OUTD", - "DB EDh,ACh", "DB EDh,ADh", "DB EDh,AEh", "DB EDh,AFh", - "LDIR", "CPIR", "INIR", "OTIR", - "DB EDh,B4h", "DB EDh,B5h", "DB EDh,B6h", "DB EDh,B7h", - "LDDR", "CPDR", "INDR", "OTDR", - "DB EDh,BCh", "DB EDh,BDh", "DB EDh,BEh", "DB EDh,BFh", - "DB EDh,C0h", "DB EDh,C1h", "DB EDh,C2h", "DB EDh,C3h", - "DB EDh,C4h", "DB EDh,C5h", "DB EDh,C6h", "DB EDh,C7h", - "DB EDh,C8h", "DB EDh,C9h", "DB EDh,CAh", "DB EDh,CBh", - "DB EDh,CCh", "DB EDh,CDh", "DB EDh,CEh", "DB EDh,CFh", - "DB EDh,D0h", "DB EDh,D1h", "DB EDh,D2h", "DB EDh,D3h", - "DB EDh,D4h", "DB EDh,D5h", "DB EDh,D6h", "DB EDh,D7h", - "DB EDh,D8h", "DB EDh,D9h", "DB EDh,DAh", "DB EDh,DBh", - "DB EDh,DCh", "DB EDh,DDh", "DB EDh,DEh", "DB EDh,DFh", - "DB EDh,E0h", "DB EDh,E1h", "DB EDh,E2h", "DB EDh,E3h", - "DB EDh,E4h", "DB EDh,E5h", "DB EDh,E6h", "DB EDh,E7h", - "DB EDh,E8h", "DB EDh,E9h", "DB EDh,EAh", "DB EDh,EBh", - "DB EDh,ECh", "DB EDh,EDh", "DB EDh,EEh", "DB EDh,EFh", - "DB EDh,F0h", "DB EDh,F1h", "DB EDh,F2h", "DB EDh,F3h", - "DB EDh,F4h", "DB EDh,F5h", "DB EDh,F6h", "DB EDh,F7h", - "DB EDh,F8h", "DB EDh,F9h", "DB EDh,FAh", "DB EDh,FBh", - "DB EDh,FCh", "DB EDh,FDh", "DB EDh,FEh", "DB EDh,FFh" -}; - -static const char* MnemonicsXX[256] = -{ - "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", - "EX AF,AF'", "ADD I%,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", - "DJNZ @h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", - "JR @h", "ADD I%,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", - "JR NZ,@h", "LD I%,#h", "LD (#h),I%", "INC I%", "INC I%h", "DEC I%h", "LD I%h,*h", "DAA", - "JR Z,@h", "ADD I%,I%", "LD I%,(#h)", "DEC I%", "INC I%l", "DEC I%l", "LD I%l,*h", "CPL", - "JR NC,@h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (I%+^h)", "DEC (I%+^h)", "LD (I%+^h),*h", "SCF", - "JR C,@h", "ADD I%,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", - "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,I%h", "LD B,I%l", "LD B,(I%+^h)", "LD B,A", - "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,I%h", "LD C,I%l", "LD C,(I%+^h)", "LD C,A", - "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,I%h", "LD D,I%l", "LD D,(I%+^h)", "LD D,A", - "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,I%h", "LD E,I%l", "LD E,(I%+^h)", "LD E,A", - "LD I%h,B", "LD I%h,C", "LD I%h,D", "LD I%h,E", "LD I%h,I%h", "LD I%h,I%l", "LD H,(I%+^h)", "LD I%h,A", - "LD I%l,B", "LD I%l,C", "LD I%l,D", "LD I%l,E", "LD I%l,I%h", "LD I%l,I%l", "LD L,(I%+^h)", "LD I%l,A", - "LD (I%+^h),B", "LD (I%+^h),C", "LD (I%+^h),D", "LD (I%+^h),E", "LD (I%+^h),H", "LD (I%+^h),L", "HALT", "LD (I%+^h),A", - "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,I%h", "LD A,I%l", "LD A,(I%+^h)", "LD A,A", - "ADD B", "ADD C", "ADD D", "ADD E", "ADD I%h", "ADD I%l", "ADD (I%+^h)", "ADD A", - "ADC B", "ADC C", "ADC D", "ADC E", "ADC I%h", "ADC I%l", "ADC (I%+^h)", "ADC,A", - "SUB B", "SUB C", "SUB D", "SUB E", "SUB I%h", "SUB I%l", "SUB (I%+^h)", "SUB A", - "SBC B", "SBC C", "SBC D", "SBC E", "SBC I%h", "SBC I%l", "SBC (I%+^h)", "SBC A", - "AND B", "AND C", "AND D", "AND E", "AND I%h", "AND I%l", "AND (I%+^h)", "AND A", - "XOR B", "XOR C", "XOR D", "XOR E", "XOR I%h", "XOR I%l", "XOR (I%+^h)", "XOR A", - "OR B", "OR C", "OR D", "OR E", "OR I%h", "OR I%l", "OR (I%+^h)", "OR A", - "CP B", "CP C", "CP D", "CP E", "CP I%h", "CP I%l", "CP (I%+^h)", "CP A", - "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD *h", "RST 00h", - "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC *h", "RST 08h", - "RET NC", "POP DE", "JP NC,#h", "OUTA (*h)", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", - "RET C", "EXX", "JP C,#h", "INA (*h)", "CALL C,#h", "PFX_DD", "SBC *h", "RST 18h", - "RET PO", "POP I%", "JP PO,#h", "EX I%,(SP)", "CALL PO,#h", "PUSH I%", "AND *h", "RST 20h", - "RET PE", "LD PC,I%", "JP PE,#h", "EX DE,I%", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", - "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", - "RET M", "LD SP,I%", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" -}; - -static const char* MnemonicsXCB[256] = -{ - "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (I%@h)", "RLC A", - "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (I%@h)", "RRC A", - "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (I%@h)", "RL A", - "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (I%@h)", "RR A", - "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (I%@h)", "SLA A", - "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (I%@h)", "SRA A", - "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (I%@h)", "SLL A", - "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (I%@h)", "SRL A", - "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(I%@h)", "BIT 0,A", - "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(I%@h)", "BIT 1,A", - "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(I%@h)", "BIT 2,A", - "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(I%@h)", "BIT 3,A", - "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(I%@h)", "BIT 4,A", - "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(I%@h)", "BIT 5,A", - "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(I%@h)", "BIT 6,A", - "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(I%@h)", "BIT 7,A", - "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(I%@h)", "RES 0,A", - "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(I%@h)", "RES 1,A", - "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(I%@h)", "RES 2,A", - "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(I%@h)", "RES 3,A", - "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(I%@h)", "RES 4,A", - "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(I%@h)", "RES 5,A", - "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(I%@h)", "RES 6,A", - "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(I%@h)", "RES 7,A", - "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(I%@h)", "SET 0,A", - "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(I%@h)", "SET 1,A", - "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(I%@h)", "SET 2,A", - "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(I%@h)", "SET 3,A", - "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(I%@h)", "SET 4,A", - "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(I%@h)", "SET 5,A", - "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(I%@h)", "SET 6,A", - "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(I%@h)", "SET 7,A" -}; - -static const char* CPMCalls[41] = -{ - "System Reset", "Console Input", "Console Output", "Reader Input", "Punch Output", "List Output", "Direct I/O", "Get IOByte", - "Set IOByte", "Print String", "Read Buffered", "Console Status", "Get Version", "Reset Disk", "Select Disk", "Open File", - "Close File", "Search First", "Search Next", "Delete File", "Read Sequential", "Write Sequential", "Make File", "Rename File", - "Get Login Vector", "Get Current Disk", "Set DMA Address", "Get Alloc", "Write Protect Disk", "Get R/O Vector", "Set File Attr", "Get Disk Params", - "Get/Set User", "Read Random", "Write Random", "Get File Size", "Set Random Record", "Reset Drive", "N/A", "N/A", "Write Random 0 fill" -}; +#else -RUNCPM_DECL int32 Watch = -1; +static uint8 parityTable[256]; +static uint8 incTable[257]; +static uint8 decTable[256]; +static uint8 cbitsTable[512]; +static uint16 cbitsDup8Table[512]; +static uint8 cbitsDup16Table[512]; +static uint8 cbits2Table[512]; +static uint16 rrcaTable[256]; +static uint16 rraTable[256]; +static uint16 addTable[512]; +static uint16 subTable[256]; +static uint16 andTable[256]; +static uint16 xororTable[256]; +static uint8 rotateShiftTable[256]; +static uint8 incZ80Table[257]; +static uint8 decZ80Table[256]; +static uint8 cbitsZ80Table[512]; +static uint8 cbitsZ80DupTable[512]; +static uint8 cbits2Z80Table[512]; +static uint8 cbits2Z80DupTable[512]; +static uint8 negTable[256]; +static uint16 rrdrldTable[256]; +static uint8 cpTable[256]; + +RUNCPM_DECL void initTables(void) { + // 256 bytes tables + for (int i = 0; i < 256; i++) { + char c = 0; + for (int j = 0; j < 8; j++) { + if (i & (1 << j)) + c++; + } + parityTable[i] = (c & 1) ? 0 : 4; + decTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0xf) << 4) | 2; + rrcaTable[i] = ((i & 1) << 15) | ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1); + rraTable[i] = ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1); + subTable[i] = ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | 2; + andTable[i] = (i << 8) | (i & 0xa8) | ((i == 0) << 6) | 0x10 | parityTable[i]; + xororTable[i] = (i << 8) | (i & 0xa8) | ((i == 0) << 6) | parityTable[i]; + rotateShiftTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i & 0xff]; + decZ80Table[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0xf) << 4) | ((i == 0x7f) << 2) | 2; + negTable[i] = (((i & 0x0f) != 0) << 4) | ((i == 0x80) << 2) | 2 | (i != 0); + rrdrldTable[i] = (i << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i]; + cpTable[i] = (i & 0x80) | (((i & 0xff) == 0) << 6); + } + // 257 bytes tables + for (int i = 0; i < 257; i++) { + incTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0) << 4); + incZ80Table[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0) << 4) | ((i == 0x80) << 2); + } + // 512 bytes tables + for (int i = 0; i < 512; i++) { + cbitsTable[i] = (i & 0x10) | ((i >> 8) & 1); + cbitsDup8Table[i] = (i & 0x10) | ((i >> 8) & 1) | ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6); + cbitsDup16Table[i] = (i & 0x10) | ((i >> 8) & 1) | (i & 0x28); + cbits2Table[i] = (i & 0x10) | ((i >> 8) & 1) | 2; + addTable[i] = ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6); + cbitsZ80Table[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1); + cbitsZ80DupTable[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | (i & 0xa8); + cbits2Z80Table[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2; + cbits2Z80DupTable[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2 | (i & 0xa8); + } +} #endif /* Memory management */ -static uint8 GET_BYTE(uint32 Addr) { - return _RamRead(Addr & ADDRMASK); +static uint8 GET_BYTE(uint16 a) { + return _RamRead(a); } -static void PUT_BYTE(uint32 Addr, uint32 Value) { - _RamWrite(Addr & ADDRMASK, Value); +static void PUT_BYTE(uint16 a, uint8 v) { + _RamWrite(a, v); } -static uint16 GET_WORD(uint32 a) { - return GET_BYTE(a) | (GET_BYTE(a + 1) << 8); +static uint16 GET_WORD(uint16 a) { + return _RamRead(a) | (_RamRead(a + 1) << 8); } -static void PUT_WORD(uint32 Addr, uint32 Value) { - _RamWrite(Addr, Value); - _RamWrite(++Addr, Value >> 8); +static void PUT_WORD(uint16 a, uint32 v) { + _RamWrite(a, v); + _RamWrite(++a, v >> 8); } #define RAM_MM(a) GET_BYTE(a--) @@ -1190,297 +1045,56 @@ static inline void Z80reset(void) { PC = 0; IFF = 0; IR = 0; - Status = 0; + Status = STATUS_RUNNING; Debug = 0; - Break = -1; Step = -1; -} - -#ifdef DEBUG -RUNCPM_DECL void watchprint(uint16 pos) { - uint8 I, J; - _puts("\r\n"); - _puts(" Watch : "); _puthex16(Watch); - _puts(" = "); _puthex8(_RamRead(Watch)); _putcon(':'); _puthex8(_RamRead(Watch + 1)); - _puts(" / "); - for (J = 0, I = _RamRead(Watch); J < 8; ++J, I <<= 1) _putcon(I & 0x80 ? '1' : '0'); - _putcon(':'); - for (J = 0, I = _RamRead(Watch + 1); J < 8; ++J, I <<= 1) _putcon(I & 0x80 ? '1' : '0'); -} - -RUNCPM_DECL void memdump(uint16 pos) { - uint16 h = pos; - uint16 c = pos; - uint8 l, i; - uint8 ch = pos & 0xff; - - _puts(" "); - for (i = 0; i < 16; ++i) { - _puthex8(ch++ & 0x0f); - _puts(" "); - } - _puts("\r\n"); - _puts(" "); - for (i = 0; i < 16; ++i) - _puts("---"); - _puts("\r\n"); - for (l = 0; l < 16; ++l) { - _puthex16(h); - _puts(" : "); - for (i = 0; i < 16; ++i) { - _puthex8(_RamRead(h++)); - _puts(" "); - } - for (i = 0; i < 16; ++i) { - ch = _RamRead(c++); - _putcon(ch > 31 && ch < 127 ? ch : '.'); - } - _puts("\r\n"); - } -} - -RUNCPM_DECL uint8 Disasm(uint16 pos) { - const char* txt; - char jr; - uint8 ch = _RamRead(pos); - uint8 count = 1; - uint8 C = 0; - - switch (ch) { - case 0xCB: ++pos; txt = MnemonicsCB[_RamRead(pos++)]; count++; break; - case 0xED: ++pos; txt = MnemonicsED[_RamRead(pos++)]; count++; break; - case 0xDD: ++pos; C = 'X'; - if (_RamRead(pos) != 0xCB) { - txt = MnemonicsXX[_RamRead(pos++)]; ++count; - } else { - ++pos; txt = MnemonicsXCB[_RamRead(pos++)]; count += 2; - } - break; - case 0xFD: ++pos; C = 'Y'; - if (_RamRead(pos) != 0xCB) { - txt = MnemonicsXX[_RamRead(pos++)]; ++count; - } else { - ++pos; txt = MnemonicsXCB[_RamRead(pos++)]; count += 2; - } - break; - default: txt = Mnemonics[_RamRead(pos++)]; - } - while (*txt != 0) { - switch (*txt) { - case '*': - txt += 2; - ++count; - _puthex8(_RamRead(pos++)); - break; - case '^': - txt += 2; - ++count; - _puthex8(_RamRead(pos++)); - break; - case '#': - txt += 2; - count += 2; - _puthex8(_RamRead(pos + 1)); - _puthex8(_RamRead(pos)); - break; - case '@': - txt += 2; - ++count; - jr = _RamRead(pos++); - _puthex16(pos + jr); - break; - case '%': - _putch(C); - ++txt; - break; - default: - _putch(*txt); - ++txt; - } - } - return(count); + #ifndef preTables + initTables(); + #endif } -RUNCPM_DECL void Z80debug(void) { - uint8 ch = 0; - uint16 pos, l; - static const char Flags[9] = "SZ5H3PNC"; - uint8 J, I; - unsigned int bpoint; - uint8 loop = TRUE; - uint8 res = 0; - - while (loop) { - pos = PC; - _puts("\r\n"); - _puts("BC:"); _puthex16(BC); - _puts(" DE:"); _puthex16(DE); - _puts(" HL:"); _puthex16(HL); - _puts(" AF:"); _puthex16(AF); - _puts(" : ["); - for (J = 0, I = LOW_REGISTER(AF); J < 8; ++J, I <<= 1) _putcon(I & 0x80 ? Flags[J] : '.'); - _puts("]\r\n"); - _puts("IX:"); _puthex16(IX); - _puts(" IY:"); _puthex16(IY); - _puts(" SP:"); _puthex16(SP); - _puts(" PC:"); _puthex16(PC); - _puts(" : "); - - Disasm(pos); - - if (PC == 0x0005) { - if (LOW_REGISTER(BC) > 40) { - _puts(" (Unknown)"); - } else { - _puts(" ("); - _puts(CPMCalls[LOW_REGISTER(BC)]); - _puts(")"); - } - } - - if (Watch != -1) { - watchprint(Watch); - } - - _puts("\r\n"); - _puts("Command|? : "); - ch = _getch(); - if (ch > 21 && ch < 127) - _putch(ch); - switch (ch) { - case 't': - loop = FALSE; - break; - case 'c': - loop = FALSE; - _puts("\r\n"); - Debug = 0; - break; - case 'b': - _puts("\r\n"); memdump(BC); break; - case 'd': - _puts("\r\n"); memdump(DE); break; - case 'h': - _puts("\r\n"); memdump(HL); break; - case 'p': - _puts("\r\n"); memdump(PC & 0xFF00); break; - case 's': - _puts("\r\n"); memdump(SP & 0xFF00); break; - case 'x': - _puts("\r\n"); memdump(IX & 0xFF00); break; - case 'y': - _puts("\r\n"); memdump(IY & 0xFF00); break; - case 'a': - _puts("\r\n"); memdump(dmaAddr); break; - case 'l': - _puts("\r\n"); - I = 16; - l = pos; - while (I > 0) { - _puthex16(l); - _puts(" : "); - l += Disasm(l); - _puts("\r\n"); - --I; - } - break; - case 'B': - _puts(" Addr: "); - res=scanf("%04x", &bpoint); - if (res) { - Break = bpoint; - _puts("Breakpoint set to "); - _puthex16(Break); - _puts("\r\n"); - } - break; - case 'C': - Break = -1; - _puts(" Breakpoint cleared\r\n"); - break; - case 'D': - _puts(" Addr: "); - res=scanf("%04x", &bpoint); - if(res) - memdump(bpoint); - break; - case 'L': - _puts(" Addr: "); - res=scanf("%04x", &bpoint); - if (res) { - I = 16; - l = bpoint; - while (I > 0) { - _puthex16(l); - _puts(" : "); - l += Disasm(l); - _puts("\r\n"); - --I; - } - } - break; - case 'T': - loop = FALSE; - Step = pos + 3; // This only works correctly with CALL - // If the called function messes with the stack, this will fail as well. - Debug = 0; - break; - case 'W': - _puts(" Addr: "); - res=scanf("%04x", &bpoint); - if (res) { - Watch = bpoint; - _puts("Watch set to "); - _puthex16(Watch); - _puts("\r\n"); - } - break; - case '?': - _puts("\r\n"); - _puts("Lowercase commands:\r\n"); - _puts(" t - traces to the next instruction\r\n"); - _puts(" c - Continue execution\r\n"); - _puts(" b - Dumps memory pointed by (BC)\r\n"); - _puts(" d - Dumps memory pointed by (DE)\r\n"); - _puts(" h - Dumps memory pointed by (HL)\r\n"); - _puts(" p - Dumps the page (PC) points to\r\n"); - _puts(" s - Dumps the page (SP) points to\r\n"); - _puts(" x - Dumps the page (IX) points to\r\n"); - _puts(" y - Dumps the page (IY) points to\r\n"); - _puts(" a - Dumps memory pointed by dmaAddr\r\n"); - _puts(" l - Disassembles from current PC\r\n"); - _puts("Uppercase commands:\r\n"); - _puts(" B - Sets breakpoint at address\r\n"); - _puts(" C - Clears breakpoint\r\n"); - _puts(" D - Dumps memory at address\r\n"); - _puts(" L - Disassembles at address\r\n"); - _puts(" T - Steps over a call\r\n"); - _puts(" W - Sets a byte/word watch\r\n"); - break; - default: - _puts(" ???\r\n"); - } - } -} +#if RUNCPMDEBUG || defined(iDEBUG) +#include "debug.h" #endif -static inline void Z80run(void) { +static inline void Z80run(uint32 cpu_delay) { uint32 temp = 0; - uint32 acu = 0; - uint32 sum = 0; - uint32 cbits = 0; + uint32 acu; + uint32 sum; + uint32 cbits; uint32 op = 0; - uint32 adr = 0; + uint32 adr; + + static uint32 instr_cnt = 0; + static uint32 last_millis = 0; + + if (last_millis == 0) last_millis = millis(); /* main instruction fetch/decode loop */ while (!Status) { /* loop until Status != 0 */ -#ifdef DEBUG - if (PC == Break) { - _puts(":BREAK at "); - _puthex16(Break); - _puts(":"); + /* Throttling to CPU_DELAY instructions */ + if (cpu_delay != 0) { + if (++instr_cnt >= cpu_delay) { + uint32 now = millis(); + if ((now - last_millis) < 10) { + uint32 delay_ms = 10 - (now - last_millis); +#ifdef _WIN32 + Sleep(delay_ms); +#elif defined(ARDUINO) + delay(delay_ms); +#else + usleep(delay_ms * 1000); +#endif + } + last_millis = millis(); + instr_cnt = 0; + } + } + +#if RUNCPMDEBUG + if (z80_check_breakpoints_on_exec(PC)) { Debug = 1; } if (PC == Step) { @@ -1489,10 +1103,17 @@ static inline void Z80run(void) { } if (Debug) Z80debug(); + if (Status) + break; #endif - PCX = PC; - INCR(1); /* Add one M1 cycle to refresh counter */ + PCX = PC; + INCR(1); /* Add one M1 cycle to refresh counter */ + + /* push instruction into trace (before it is executed) */ +#if RUNCPMDEBUG || defined(iDEBUG) + z80_trace_push(PCX); +#endif #ifdef iDEBUG iLogFile = fopen("iDump.log", "a"); @@ -1508,7 +1129,7 @@ static inline void Z80run(void) { } default: iLogTxt = Mnemonics[RAM[PCX & 0xffff]]; } - sprintf(iLogBuffer, "0x%04x : 0x%02x = %s\r\n", PCX, RAM[PCX & 0xffff], iLogTxt); + sprintf(iLogBuffer, "0x%04x : 0x%02x = %s\n", PCX, RAM[PCX & 0xffff], iLogTxt); fputs(iLogBuffer, iLogFile); fclose(iLogFile); #endif @@ -1519,8 +1140,8 @@ static inline void Z80run(void) { break; case 0x01: /* LD BC,nnnn */ - BC = GET_WORD(PC); - PC += 2; + BC = GET_WORD(PC++); + ++PC; break; case 0x02: /* LD (BC),A */ @@ -1553,9 +1174,9 @@ static inline void Z80run(void) { break; case 0x08: /* EX AF,AF' */ - temp = AF; - AF = AF1; - AF1 = temp; + AF ^= AF1; + AF1 ^= AF; + AF ^= AF1; break; case 0x09: /* ADD HL,BC */ @@ -1602,8 +1223,8 @@ static inline void Z80run(void) { break; case 0x11: /* LD DE,nnnn */ - DE = GET_WORD(PC); - PC += 2; + DE = GET_WORD(PC++); + ++PC; break; case 0x12: /* LD (DE),A */ @@ -1683,14 +1304,13 @@ static inline void Z80run(void) { break; case 0x21: /* LD HL,nnnn */ - HL = GET_WORD(PC); - PC += 2; + HL = GET_WORD(PC++); + ++PC; break; case 0x22: /* LD (nnnn),HL */ - temp = GET_WORD(PC); - PUT_WORD(temp, HL); - PC += 2; + PUT_WORD(GET_WORD(PC++), HL); + ++PC; break; case 0x23: /* INC HL */ @@ -1753,9 +1373,8 @@ static inline void Z80run(void) { break; case 0x2a: /* LD HL,(nnnn) */ - temp = GET_WORD(PC); - HL = GET_WORD(temp); - PC += 2; + HL = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x2b: /* DEC HL */ @@ -1790,14 +1409,13 @@ static inline void Z80run(void) { break; case 0x31: /* LD SP,nnnn */ - SP = GET_WORD(PC); - PC += 2; + SP = GET_WORD(PC++); + ++PC; break; case 0x32: /* LD (nnnn),A */ - temp = GET_WORD(PC); - PUT_BYTE(temp, HIGH_REGISTER(AF)); - PC += 2; + PUT_BYTE(GET_WORD(PC++), HIGH_REGISTER(AF)); + ++PC; break; case 0x33: /* INC SP */ @@ -1840,9 +1458,8 @@ static inline void Z80run(void) { break; case 0x3a: /* LD A,(nnnn) */ - temp = GET_WORD(PC); - SET_HIGH_REGISTER(AF, GET_BYTE(temp)); - PC += 2; + SET_HIGH_REGISTER(AF, GET_BYTE(GET_WORD(PC++))); + ++PC; break; case 0x3b: /* DEC SP */ @@ -2080,13 +1697,18 @@ static inline void Z80run(void) { break; case 0x76: /* HALT */ -#ifdef DEBUG - _puts("\r\n::CPU HALTED::"); // A halt is a good indicator of broken code +#if RUNCPMDEBUG + _puts("\r\n::CPU HALTED::\r\n"); // A halt is a good indicator of broken code _puts("Press any key..."); - _getch(); + _getcon(); + #ifdef DEBUGONHALT + _puts("\r\n"); + Debug = 1; + Z80debug(); + #endif #endif --PC; - goto end_decode; + Status = STATUS_EXIT; break; case 0x77: /* LD (HL),A */ @@ -2594,91 +2216,88 @@ static inline void Z80run(void) { switch ((op = GET_BYTE(PC)) & 7) { case 0: - ++PC; acu = HIGH_REGISTER(BC); break; case 1: - ++PC; acu = LOW_REGISTER(BC); break; case 2: - ++PC; acu = HIGH_REGISTER(DE); break; case 3: - ++PC; acu = LOW_REGISTER(DE); break; case 4: - ++PC; acu = HIGH_REGISTER(HL); break; case 5: - ++PC; acu = LOW_REGISTER(HL); break; case 6: - ++PC; acu = GET_BYTE(adr); break; - case 7: - ++PC; + default: acu = HIGH_REGISTER(AF); break; } + ++PC; switch (op & 0xc0) { case 0x00: /* shift/rotate */ switch (op & 0x38) { - case 0x00:/* RLC */ - temp = (acu << 1) | (acu >> 7); - cbits = temp & 1; - goto cbshflg1; - - case 0x08:/* RRC */ - temp = (acu >> 1) | (acu << 7); - cbits = temp & 0x80; - goto cbshflg1; - - case 0x10:/* RL */ - temp = (acu << 1) | TSTFLAG(C); - cbits = acu & 0x80; - goto cbshflg1; - - case 0x18:/* RR */ - temp = (acu >> 1) | (TSTFLAG(C) << 7); - cbits = acu & 1; - goto cbshflg1; - - case 0x20:/* SLA */ - temp = acu << 1; - cbits = acu & 0x80; - goto cbshflg1; - - case 0x28:/* SRA */ - temp = (acu >> 1) | (acu & 0x80); - cbits = acu & 1; - goto cbshflg1; - - case 0x30:/* SLIA */ - temp = (acu << 1) | 1; - cbits = acu & 0x80; - goto cbshflg1; - - case 0x38:/* SRL */ - temp = acu >> 1; - cbits = acu & 1; - cbshflg1: + case 0x00:/* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + break; + + case 0x08:/* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + break; + + case 0x10:/* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + break; + + case 0x18:/* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + break; + + case 0x20:/* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + break; + + case 0x28:/* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + break; + + case 0x30:/* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + break; + + case 0x38:/* SRL */ + temp = acu >> 1; + cbits = acu & 1; + break; + + default: + temp = acu; + cbits = 0; + } AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; - } break; case 0x40: /* BIT */ @@ -2729,7 +2348,7 @@ static inline void Z80run(void) { PUT_BYTE(adr, temp); break; - case 7: + default: SET_HIGH_REGISTER(AF, temp); break; } @@ -2754,6 +2373,10 @@ static inline void Z80run(void) { case 0xcf: /* RST 8 */ PUSH(PC); PC = 8; +#ifdef INT_HANDOFF + _Bios(); + POP(PC); +#endif break; case 0xd0: /* RET NC */ @@ -2792,6 +2415,10 @@ static inline void Z80run(void) { case 0xd7: /* RST 10H */ PUSH(PC); PC = 0x10; +#ifdef INT_HANDOFF + _Bdos(); + POP(PC); +#endif break; case 0xd8: /* RET C */ @@ -2800,15 +2427,15 @@ static inline void Z80run(void) { break; case 0xd9: /* EXX */ - temp = BC; - BC = BC1; - BC1 = temp; - temp = DE; - DE = DE1; - DE1 = temp; - temp = HL; - HL = HL1; - HL1 = temp; + BC ^= BC1; + BC1 ^= BC; + BC ^= BC1; + DE ^= DE1; + DE1 ^= DE; + DE ^= DE1; + HL ^= HL1; + HL1 ^= HL; + HL ^= HL1; break; case 0xda: /* JP C,nnnn */ @@ -2844,14 +2471,13 @@ static inline void Z80run(void) { break; case 0x21: /* LD IX,nnnn */ - IX = GET_WORD(PC); - PC += 2; + IX = GET_WORD(PC++); + ++PC; break; case 0x22: /* LD (nnnn),IX */ - temp = GET_WORD(PC); - PUT_WORD(temp, IX); - PC += 2; + PUT_WORD(GET_WORD(PC++), IX); + ++PC; break; case 0x23: /* INC IX */ @@ -2880,9 +2506,8 @@ static inline void Z80run(void) { break; case 0x2a: /* LD IX,(nnnn) */ - temp = GET_WORD(PC); - IX = GET_WORD(temp); - PC += 2; + IX = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x2b: /* DEC IX */ @@ -2941,8 +2566,7 @@ static inline void Z80run(void) { break; case 0x46: /* LD B,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(BC, GET_BYTE(adr)); + SET_HIGH_REGISTER(BC, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x4c: /* LD C,IXH */ @@ -2954,8 +2578,7 @@ static inline void Z80run(void) { break; case 0x4e: /* LD C,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_LOW_REGISTER(BC, GET_BYTE(adr)); + SET_LOW_REGISTER(BC, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x54: /* LD D,IXH */ @@ -2967,8 +2590,7 @@ static inline void Z80run(void) { break; case 0x56: /* LD D,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(DE, GET_BYTE(adr)); + SET_HIGH_REGISTER(DE, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x5c: /* LD E,IXH */ @@ -2980,8 +2602,7 @@ static inline void Z80run(void) { break; case 0x5e: /* LD E,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_LOW_REGISTER(DE, GET_BYTE(adr)); + SET_LOW_REGISTER(DE, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x60: /* LD IXH,B */ @@ -3008,8 +2629,7 @@ static inline void Z80run(void) { break; case 0x66: /* LD H,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(HL, GET_BYTE(adr)); + SET_HIGH_REGISTER(HL, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x67: /* LD IXH,A */ @@ -3040,8 +2660,7 @@ static inline void Z80run(void) { break; case 0x6e: /* LD L,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_LOW_REGISTER(HL, GET_BYTE(adr)); + SET_LOW_REGISTER(HL, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x6f: /* LD IXL,A */ @@ -3049,38 +2668,31 @@ static inline void Z80run(void) { break; case 0x70: /* LD (IX+dd),B */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(BC)); + PUT_BYTE(IX + (int8)RAM_PP(PC), HIGH_REGISTER(BC)); break; case 0x71: /* LD (IX+dd),C */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(BC)); + PUT_BYTE(IX + (int8)RAM_PP(PC), LOW_REGISTER(BC)); break; case 0x72: /* LD (IX+dd),D */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(DE)); + PUT_BYTE(IX + (int8)RAM_PP(PC), HIGH_REGISTER(DE)); break; case 0x73: /* LD (IX+dd),E */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(DE)); + PUT_BYTE(IX + (int8)RAM_PP(PC), LOW_REGISTER(DE)); break; case 0x74: /* LD (IX+dd),H */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(HL)); + PUT_BYTE(IX + (int8)RAM_PP(PC), HIGH_REGISTER(HL)); break; case 0x75: /* LD (IX+dd),L */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(HL)); + PUT_BYTE(IX + (int8)RAM_PP(PC), LOW_REGISTER(HL)); break; case 0x77: /* LD (IX+dd),A */ - adr = IX + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(AF)); + PUT_BYTE(IX + (int8)RAM_PP(PC), HIGH_REGISTER(AF)); break; case 0x7c: /* LD A,IXH */ @@ -3092,8 +2704,7 @@ static inline void Z80run(void) { break; case 0x7e: /* LD A,(IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(AF, GET_BYTE(adr)); + SET_HIGH_REGISTER(AF, GET_BYTE(IX + (int8)RAM_PP(PC))); break; case 0x84: /* ADD A,IXH */ @@ -3150,7 +2761,6 @@ static inline void Z80run(void) { case 0x94: /* SUB IXH */ SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ - [[fallthrough]]; case 0x9c: /* SBC A,IXH */ temp = HIGH_REGISTER(IX); @@ -3161,7 +2771,6 @@ static inline void Z80run(void) { case 0x95: /* SUB IXL */ SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ - [[fallthrough]]; case 0x9d: /* SBC A,IXL */ temp = LOW_REGISTER(IX); @@ -3187,8 +2796,7 @@ static inline void Z80run(void) { break; case 0xa6: /* AND (IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - AF = andTable[((AF >> 8)& GET_BYTE(adr)) & 0xff]; + AF = andTable[((AF >> 8)& GET_BYTE(IX + (int8)RAM_PP(PC))) & 0xff]; break; case 0xac: /* XOR IXH */ @@ -3200,8 +2808,7 @@ static inline void Z80run(void) { break; case 0xae: /* XOR (IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - AF = xororTable[((AF >> 8) ^ GET_BYTE(adr)) & 0xff]; + AF = xororTable[((AF >> 8) ^ GET_BYTE(IX + (int8)RAM_PP(PC))) & 0xff]; break; case 0xb4: /* OR IXH */ @@ -3213,8 +2820,7 @@ static inline void Z80run(void) { break; case 0xb6: /* OR (IX+dd) */ - adr = IX + (int8)RAM_PP(PC); - AF = xororTable[((AF >> 8) | GET_BYTE(adr)) & 0xff]; + AF = xororTable[((AF >> 8) | GET_BYTE(IX + (int8)RAM_PP(PC))) & 0xff]; break; case 0xbc: /* CP IXH */ @@ -3250,91 +2856,88 @@ static inline void Z80run(void) { switch ((op = GET_BYTE(PC)) & 7) { case 0: - ++PC; acu = HIGH_REGISTER(BC); break; case 1: - ++PC; acu = LOW_REGISTER(BC); break; case 2: - ++PC; acu = HIGH_REGISTER(DE); break; case 3: - ++PC; acu = LOW_REGISTER(DE); break; case 4: - ++PC; acu = HIGH_REGISTER(HL); break; case 5: - ++PC; acu = LOW_REGISTER(HL); break; case 6: - ++PC; acu = GET_BYTE(adr); break; - case 7: - ++PC; + default: acu = HIGH_REGISTER(AF); break; } + ++PC; switch (op & 0xc0) { case 0x00: /* shift/rotate */ switch (op & 0x38) { - case 0x00:/* RLC */ - temp = (acu << 1) | (acu >> 7); - cbits = temp & 1; - goto cbshflg2; - - case 0x08:/* RRC */ - temp = (acu >> 1) | (acu << 7); - cbits = temp & 0x80; - goto cbshflg2; - - case 0x10:/* RL */ - temp = (acu << 1) | TSTFLAG(C); - cbits = acu & 0x80; - goto cbshflg2; - - case 0x18:/* RR */ - temp = (acu >> 1) | (TSTFLAG(C) << 7); - cbits = acu & 1; - goto cbshflg2; - - case 0x20:/* SLA */ - temp = acu << 1; - cbits = acu & 0x80; - goto cbshflg2; - - case 0x28:/* SRA */ - temp = (acu >> 1) | (acu & 0x80); - cbits = acu & 1; - goto cbshflg2; - - case 0x30:/* SLIA */ - temp = (acu << 1) | 1; - cbits = acu & 0x80; - goto cbshflg2; - - case 0x38:/* SRL */ - temp = acu >> 1; - cbits = acu & 1; - cbshflg2: - AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; + case 0x00:/* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + break; + + case 0x08:/* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + break; + + case 0x10:/* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + break; + + case 0x18:/* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + break; + + case 0x20:/* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + break; + + case 0x28:/* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + break; + + case 0x30:/* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + break; + + case 0x38:/* SRL */ + temp = acu >> 1; + cbits = acu & 1; + break; + + default: + temp = acu; + cbits = 0; } + AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; break; case 0x40: /* BIT */ @@ -3385,7 +2988,7 @@ static inline void Z80run(void) { PUT_BYTE(adr, temp); break; - case 7: + default: SET_HIGH_REGISTER(AF, temp); break; } @@ -3481,9 +3084,9 @@ static inline void Z80run(void) { break; case 0xeb: /* EX DE,HL */ - temp = HL; - HL = DE; - DE = temp; + HL ^= DE; + DE ^= HL; + HL ^= DE; break; case 0xec: /* CALL PE,nnnn */ @@ -3514,9 +3117,8 @@ static inline void Z80run(void) { break; case 0x43: /* LD (nnnn),BC */ - temp = GET_WORD(PC); - PUT_WORD(temp, BC); - PC += 2; + PUT_WORD(GET_WORD(PC++), BC); + ++PC; break; case 0x44: /* NEG */ @@ -3584,9 +3186,8 @@ static inline void Z80run(void) { break; case 0x4b: /* LD BC,(nnnn) */ - temp = GET_WORD(PC); - BC = GET_WORD(temp); - PC += 2; + BC = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x4d: /* RETI */ @@ -3618,9 +3219,8 @@ static inline void Z80run(void) { break; case 0x53: /* LD (nnnn),DE */ - temp = GET_WORD(PC); - PUT_WORD(temp, DE); - PC += 2; + PUT_WORD(GET_WORD(PC++), DE); + ++PC; break; case 0x56: /* IM 1 */ @@ -3651,9 +3251,8 @@ static inline void Z80run(void) { break; case 0x5b: /* LD DE,(nnnn) */ - temp = GET_WORD(PC); - DE = GET_WORD(temp); - PC += 2; + DE = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x5e: /* IM 2 */ @@ -3684,9 +3283,8 @@ static inline void Z80run(void) { break; case 0x63: /* LD (nnnn),HL */ - temp = GET_WORD(PC); - PUT_WORD(temp, HL); - PC += 2; + PUT_WORD(GET_WORD(PC++), HL); + ++PC; break; case 0x67: /* RRD */ @@ -3715,9 +3313,8 @@ static inline void Z80run(void) { break; case 0x6b: /* LD HL,(nnnn) */ - temp = GET_WORD(PC); - HL = GET_WORD(temp); - PC += 2; + HL = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x6f: /* RLD */ @@ -3747,9 +3344,8 @@ static inline void Z80run(void) { break; case 0x73: /* LD (nnnn),SP */ - temp = GET_WORD(PC); - PUT_WORD(temp, SP); - PC += 2; + PUT_WORD(GET_WORD(PC++), SP); + ++PC; break; case 0x78: /* IN A,(C) */ @@ -3772,9 +3368,8 @@ static inline void Z80run(void) { break; case 0x7b: /* LD SP,(nnnn) */ - temp = GET_WORD(PC); - SP = GET_WORD(temp); - PC += 2; + SP = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0xa0: /* LDI */ @@ -4093,14 +3688,14 @@ static inline void Z80run(void) { break; case 0x21: /* LD IY,nnnn */ - IY = GET_WORD(PC); - PC += 2; + IY = GET_WORD(PC++); + ++PC; break; case 0x22: /* LD (nnnn),IY */ - temp = GET_WORD(PC); + temp = GET_WORD(PC++); PUT_WORD(temp, IY); - PC += 2; + ++PC; break; case 0x23: /* INC IY */ @@ -4129,9 +3724,8 @@ static inline void Z80run(void) { break; case 0x2a: /* LD IY,(nnnn) */ - temp = GET_WORD(PC); - IY = GET_WORD(temp); - PC += 2; + IY = GET_WORD(GET_WORD(PC++)); + ++PC; break; case 0x2b: /* DEC IY */ @@ -4190,8 +3784,7 @@ static inline void Z80run(void) { break; case 0x46: /* LD B,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(BC, GET_BYTE(adr)); + SET_HIGH_REGISTER(BC, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x4c: /* LD C,IYH */ @@ -4203,8 +3796,7 @@ static inline void Z80run(void) { break; case 0x4e: /* LD C,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_LOW_REGISTER(BC, GET_BYTE(adr)); + SET_LOW_REGISTER(BC, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x54: /* LD D,IYH */ @@ -4216,8 +3808,7 @@ static inline void Z80run(void) { break; case 0x56: /* LD D,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(DE, GET_BYTE(adr)); + SET_HIGH_REGISTER(DE, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x5c: /* LD E,IYH */ @@ -4229,8 +3820,7 @@ static inline void Z80run(void) { break; case 0x5e: /* LD E,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_LOW_REGISTER(DE, GET_BYTE(adr)); + SET_LOW_REGISTER(DE, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x60: /* LD IYH,B */ @@ -4257,8 +3847,7 @@ static inline void Z80run(void) { break; case 0x66: /* LD H,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(HL, GET_BYTE(adr)); + SET_HIGH_REGISTER(HL, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x67: /* LD IYH,A */ @@ -4289,8 +3878,7 @@ static inline void Z80run(void) { break; case 0x6e: /* LD L,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_LOW_REGISTER(HL, GET_BYTE(adr)); + SET_LOW_REGISTER(HL, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x6f: /* LD IYL,A */ @@ -4298,38 +3886,31 @@ static inline void Z80run(void) { break; case 0x70: /* LD (IY+dd),B */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(BC)); + PUT_BYTE(IY + (int8)RAM_PP(PC), HIGH_REGISTER(BC)); break; case 0x71: /* LD (IY+dd),C */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(BC)); + PUT_BYTE(IY + (int8)RAM_PP(PC), LOW_REGISTER(BC)); break; case 0x72: /* LD (IY+dd),D */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(DE)); + PUT_BYTE(IY + (int8)RAM_PP(PC), HIGH_REGISTER(DE)); break; case 0x73: /* LD (IY+dd),E */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(DE)); + PUT_BYTE(IY + (int8)RAM_PP(PC), LOW_REGISTER(DE)); break; case 0x74: /* LD (IY+dd),H */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(HL)); + PUT_BYTE(IY + (int8)RAM_PP(PC), HIGH_REGISTER(HL)); break; case 0x75: /* LD (IY+dd),L */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, LOW_REGISTER(HL)); + PUT_BYTE(IY + (int8)RAM_PP(PC), LOW_REGISTER(HL)); break; case 0x77: /* LD (IY+dd),A */ - adr = IY + (int8)RAM_PP(PC); - PUT_BYTE(adr, HIGH_REGISTER(AF)); + PUT_BYTE(IY + (int8)RAM_PP(PC), HIGH_REGISTER(AF)); break; case 0x7c: /* LD A,IYH */ @@ -4341,8 +3922,7 @@ static inline void Z80run(void) { break; case 0x7e: /* LD A,(IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - SET_HIGH_REGISTER(AF, GET_BYTE(adr)); + SET_HIGH_REGISTER(AF, GET_BYTE(IY + (int8)RAM_PP(PC))); break; case 0x84: /* ADD A,IYH */ @@ -4399,7 +3979,6 @@ static inline void Z80run(void) { case 0x94: /* SUB IYH */ SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ - [[fallthrough]]; case 0x9c: /* SBC A,IYH */ temp = HIGH_REGISTER(IY); @@ -4410,7 +3989,6 @@ static inline void Z80run(void) { case 0x95: /* SUB IYL */ SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ - [[fallthrough]]; case 0x9d: /* SBC A,IYL */ temp = LOW_REGISTER(IY); @@ -4436,8 +4014,7 @@ static inline void Z80run(void) { break; case 0xa6: /* AND (IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - AF = andTable[((AF >> 8)& GET_BYTE(adr)) & 0xff]; + AF = andTable[((AF >> 8)& GET_BYTE(IY + (int8)RAM_PP(PC))) & 0xff]; break; case 0xac: /* XOR IYH */ @@ -4449,8 +4026,7 @@ static inline void Z80run(void) { break; case 0xae: /* XOR (IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - AF = xororTable[((AF >> 8) ^ GET_BYTE(adr)) & 0xff]; + AF = xororTable[((AF >> 8) ^ GET_BYTE(IY + (int8)RAM_PP(PC))) & 0xff]; break; case 0xb4: /* OR IYH */ @@ -4462,8 +4038,7 @@ static inline void Z80run(void) { break; case 0xb6: /* OR (IY+dd) */ - adr = IY + (int8)RAM_PP(PC); - AF = xororTable[((AF >> 8) | GET_BYTE(adr)) & 0xff]; + AF = xororTable[((AF >> 8) | GET_BYTE(IY + (int8)RAM_PP(PC))) & 0xff]; break; case 0xbc: /* CP IYH */ @@ -4499,91 +4074,88 @@ static inline void Z80run(void) { switch ((op = GET_BYTE(PC)) & 7) { case 0: - ++PC; acu = HIGH_REGISTER(BC); break; case 1: - ++PC; acu = LOW_REGISTER(BC); break; case 2: - ++PC; acu = HIGH_REGISTER(DE); break; case 3: - ++PC; acu = LOW_REGISTER(DE); break; case 4: - ++PC; acu = HIGH_REGISTER(HL); break; case 5: - ++PC; acu = LOW_REGISTER(HL); break; case 6: - ++PC; acu = GET_BYTE(adr); break; - case 7: - ++PC; + default: acu = HIGH_REGISTER(AF); break; } + ++PC; switch (op & 0xc0) { case 0x00: /* shift/rotate */ switch (op & 0x38) { - case 0x00:/* RLC */ - temp = (acu << 1) | (acu >> 7); - cbits = temp & 1; - goto cbshflg3; - - case 0x08:/* RRC */ - temp = (acu >> 1) | (acu << 7); - cbits = temp & 0x80; - goto cbshflg3; - - case 0x10:/* RL */ - temp = (acu << 1) | TSTFLAG(C); - cbits = acu & 0x80; - goto cbshflg3; - - case 0x18:/* RR */ - temp = (acu >> 1) | (TSTFLAG(C) << 7); - cbits = acu & 1; - goto cbshflg3; - - case 0x20:/* SLA */ - temp = acu << 1; - cbits = acu & 0x80; - goto cbshflg3; - - case 0x28:/* SRA */ - temp = (acu >> 1) | (acu & 0x80); - cbits = acu & 1; - goto cbshflg3; - - case 0x30:/* SLIA */ - temp = (acu << 1) | 1; - cbits = acu & 0x80; - goto cbshflg3; - - case 0x38:/* SRL */ - temp = acu >> 1; - cbits = acu & 1; - cbshflg3: - AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; + case 0x00:/* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + break; + + case 0x08:/* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + break; + + case 0x10:/* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + break; + + case 0x18:/* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + break; + + case 0x20:/* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + break; + + case 0x28:/* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + break; + + case 0x30:/* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + break; + + case 0x38:/* SRL */ + temp = acu >> 1; + cbits = acu & 1; + break; + + default: + temp = acu; + cbits = 0; } + AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; break; case 0x40: /* BIT */ @@ -4634,7 +4206,7 @@ static inline void Z80run(void) { PUT_BYTE(adr, temp); break; - case 7: + default: SET_HIGH_REGISTER(AF, temp); break; } @@ -4682,9 +4254,8 @@ static inline void Z80run(void) { PC = 0x38; } } -end_decode: - ; } +#include "cpu_mhz.h" #endif diff --git a/lib/runcpm/cpu_mhz.h b/lib/runcpm/cpu_mhz.h new file mode 100644 index 000000000..20eb6d861 --- /dev/null +++ b/lib/runcpm/cpu_mhz.h @@ -0,0 +1,122 @@ +#ifndef CPU_MHZ_H +#define CPU_MHZ_H + +#ifndef RUNCPM_DECL +#define RUNCPM_DECL +#endif + +#include + +/* T-states for main Z80 instructions */ +static const uint8 z80_tstates_main[256] = { + 4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4, + 13, 10, 7, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4, + 12, 10, 16, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4, + 12, 10, 7, 6, 11, 11, 10, 4, 7, 7, 7, 7, 4, 4, 7, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 4, 4, 4, 4, 4, 4, 7, 4, 5, 5, 5, 5, 5, 5, 7, 4, + 4, 4, 4, 4, 4, 4, 7, 4, 5, 5, 5, 5, 5, 5, 7, 4, + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, + 5, 10, 10, 11, 10, 11, 7, 12, 10, 10, 10, 10, 7, 10, 7, 12, + 5, 10, 12, 11, 10, 11, 7, 12, 10, 12, 10, 10, 7, 10, 7, 12, + 5, 10, 12, 11, 10, 11, 7, 12, 10, 12, 10, 10, 7, 10, 7, 12, + 5, 10, 12, 11, 10, 11, 7, 12, 10, 12, 10, 10, 7, 10, 7, 11 +}; + +/* Run a small Z80 code and measure the time to estimate emulated clock. + This will load a small z80 code into RAM, run it until halt, then compute the + estimated clock at which the CPU is running */ +RUNCPM_DECL void Z80estimateClock(void) { + const uint8 testCode[] = { +#ifdef ARDUINO + 0x11, 0xF4, 0x01, // LD DE, 500 + 0x01, 0xE8, 0x03, // LD BC, 1000 +#else + 0x11, 0xE8, 0x03, // LD DE, 1000 + 0x01, 0x10, 0x27, // LD BC, 10000 +#endif + 0x0B, // DEC BC + 0x78, // LD A, B + 0xB1, // OR C + 0x20, 0xFB, // JR NZ, -5 + 0x1B, // DEC DE + 0x7A, // LD A, D + 0xB3, // OR E + 0x20, 0xF3, // JR NZ, -13 + 0x76 // HALT + }; + + uint64 time_start = 0; + uint64 time_now = 0; + + // Load test code into RAM at address 0x0000 + for (uint16 addr = 0; addr < sizeof(testCode); addr++) { + PUT_BYTE(addr, testCode[addr]); + } + + // Reset CPU + Z80reset(); + + // Start timing + time_start = millis(); + + // Run until HALT + while (Status == STATUS_RUNNING) { + Z80run(0); + } + + // End timing + time_now = millis(); + + // Calculate total T-states executed + uint8 t_ld_de = z80_tstates_main[0x11]; + uint8 t_ld_bc = z80_tstates_main[0x01]; + uint8 t_dec = z80_tstates_main[0x0B]; + uint8 t_ld_a = z80_tstates_main[0x78]; + uint8 t_or = z80_tstates_main[0xB1]; + uint8 t_jr = z80_tstates_main[0x20]; + uint8 t_halt = z80_tstates_main[0x76]; + +#ifdef ARDUINO + uint16 outer_iters = 500; + uint16 inner_iters = 1000; +#else + uint16 outer_iters = 1000; + uint16 inner_iters = 10000; +#endif + + uint64 inner_body = t_dec + t_ld_a + t_or + t_jr; + uint64 inner_exit = t_dec + t_ld_a + t_or + 7; // JR not taken + uint64 total_inner = t_ld_bc + (inner_iters - 1) * inner_body + inner_exit; + + uint64 outer_body = total_inner + t_dec + t_ld_a + t_or + t_jr; + uint64 outer_exit = total_inner + t_dec + t_ld_a + t_or + 7; // JR not taken + uint64 total_outer = t_ld_de + (outer_iters - 1) * outer_body + outer_exit; + + // Final T-states count (full count, not in millions) + uint64 tstates = total_outer + t_halt; + + // Calculate elapsed time in milliseconds + uint64 elapsedTime = time_now - time_start; + + // Estimate clock speed in Hz + if (elapsedTime == 0) elapsedTime = 1; // Prevent division by zero + uint64 estimatedHz = (tstates * 1000) / elapsedTime; + + // Convert to MHz + uint32 estimatedMHz = (uint32)(estimatedHz / 1000000); + char buffer[64]; + sprintf(buffer, "%llu T-states in %llu ms\r\n", tstates, elapsedTime); + _puts(buffer); + sprintf(buffer, "Estimated Z80 clock speed: %u MHz\r\n", estimatedMHz); + _puts(buffer); + + // Reset CPU + Z80reset(); +} + +#endif // CPU_MHZ_H \ No newline at end of file diff --git a/lib/runcpm/debug.h b/lib/runcpm/debug.h new file mode 100644 index 000000000..bf1afbf1f --- /dev/null +++ b/lib/runcpm/debug.h @@ -0,0 +1,1080 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#ifndef RUNCPM_DECL +#define RUNCPM_DECL +#endif + +/* Mnemonic tables for Z80 disassembly - shared by all CPU models */ +#if RUNCPMDEBUG || defined(iDEBUG) + +static const char* Mnemonics[256] = +{ + "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", + "EX AF,AF'", "ADD HL,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", + "DJNZ @h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", + "JR @h", "ADD HL,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", + "JR NZ,@h", "LD HL,#h", "LD (#h),HL", "INC HL", "INC H", "DEC H", "LD H,*h", "DAA", + "JR Z,@h", "ADD HL,HL", "LD HL,(#h)", "DEC HL", "INC L", "DEC L", "LD L,*h", "CPL", + "JR NC,@h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL),*h", "SCF", + "JR C,@h", "ADD HL,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", + "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,H", "LD B,L", "LD B,(HL)", "LD B,A", + "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,H", "LD C,L", "LD C,(HL)", "LD C,A", + "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,H", "LD D,L", "LD D,(HL)", "LD D,A", + "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,H", "LD E,L", "LD E,(HL)", "LD E,A", + "LD H,B", "LD H,C", "LD H,D", "LD H,E", "LD H,H", "LD H,L", "LD H,(HL)", "LD H,A", + "LD L,B", "LD L,C", "LD L,D", "LD L,E", "LD L,H", "LD L,L", "LD L,(HL)", "LD L,A", + "LD (HL),B", "LD (HL),C", "LD (HL),D", "LD (HL),E", "LD (HL),H", "LD (HL),L", "HALT", "LD (HL),A", + "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,H", "LD A,L", "LD A,(HL)", "LD A,A", + "ADD B", "ADD C", "ADD D", "ADD E", "ADD H", "ADD L", "ADD (HL)", "ADD A", + "ADC B", "ADC C", "ADC D", "ADC E", "ADC H", "ADC L", "ADC (HL)", "ADC A", + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", + "SBC B", "SBC C", "SBC D", "SBC E", "SBC H", "SBC L", "SBC (HL)", "SBC A", + "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", + "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", + "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", + "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", + "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD *h", "RST 00h", + "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC *h", "RST 08h", + "RET NC", "POP DE", "JP NC,#h", "OUTA (*h)", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", + "RET C", "EXX", "JP C,#h", "INA (*h)", "CALL C,#h", "PFX_DD", "SBC *h", "RST 18h", + "RET PO", "POP HL", "JP PO,#h", "EX HL,(SP)", "CALL PO,#h", "PUSH HL", "AND *h", "RST 20h", + "RET PE", "LD PC,HL", "JP PE,#h", "EX DE,HL", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", + "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", + "RET M", "LD SP,HL", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" +}; + +static const char* MnemonicsCB[256] = +{ + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", + "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (HL)", "SLL A", + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", + "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(HL)", "BIT 0,A", + "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(HL)", "BIT 1,A", + "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(HL)", "BIT 2,A", + "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(HL)", "BIT 3,A", + "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(HL)", "BIT 4,A", + "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(HL)", "BIT 5,A", + "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(HL)", "BIT 6,A", + "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(HL)", "BIT 7,A", + "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(HL)", "RES 0,A", + "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(HL)", "RES 1,A", + "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(HL)", "RES 2,A", + "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(HL)", "RES 3,A", + "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(HL)", "RES 4,A", + "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(HL)", "RES 5,A", + "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(HL)", "RES 6,A", + "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(HL)", "RES 7,A", + "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(HL)", "SET 0,A", + "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(HL)", "SET 1,A", + "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(HL)", "SET 2,A", + "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(HL)", "SET 3,A", + "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(HL)", "SET 4,A", + "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(HL)", "SET 5,A", + "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(HL)", "SET 6,A", + "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(HL)", "SET 7,A" +}; + +static const char* MnemonicsED[256] = +{ + "DB EDh,00h", "DB EDh,01h", "DB EDh,02h", "DB EDh,03h", + "DB EDh,04h", "DB EDh,05h", "DB EDh,06h", "DB EDh,07h", + "DB EDh,08h", "DB EDh,09h", "DB EDh,0Ah", "DB EDh,0Bh", + "DB EDh,0Ch", "DB EDh,0Dh", "DB EDh,0Eh", "DB EDh,0Fh", + "DB EDh,10h", "DB EDh,11h", "DB EDh,12h", "DB EDh,13h", + "DB EDh,14h", "DB EDh,15h", "DB EDh,16h", "DB EDh,17h", + "DB EDh,18h", "DB EDh,19h", "DB EDh,1Ah", "DB EDh,1Bh", + "DB EDh,1Ch", "DB EDh,1Dh", "DB EDh,1Eh", "DB EDh,1Fh", + "DB EDh,20h", "DB EDh,21h", "DB EDh,22h", "DB EDh,23h", + "DB EDh,24h", "DB EDh,25h", "DB EDh,26h", "DB EDh,27h", + "DB EDh,28h", "DB EDh,29h", "DB EDh,2Ah", "DB EDh,2Bh", + "DB EDh,2Ch", "DB EDh,2Dh", "DB EDh,2Eh", "DB EDh,2Fh", + "DB EDh,30h", "DB EDh,31h", "DB EDh,32h", "DB EDh,33h", + "DB EDh,34h", "DB EDh,35h", "DB EDh,36h", "DB EDh,37h", + "DB EDh,38h", "DB EDh,39h", "DB EDh,3Ah", "DB EDh,3Bh", + "DB EDh,3Ch", "DB EDh,3Dh", "DB EDh,3Eh", "DB EDh,3Fh", + "IN B,(C)", "OUT (C),B", "SBC HL,BC", "LD (#h),BC", + "NEG", "RETN", "IM 0", "LD I,A", + "IN C,(C)", "OUT (C),C", "ADC HL,BC", "LD BC,(#h)", + "DB EDh,4Ch", "RETI", "DB EDh,4Eh", "LD R,A", + "IN D,(C)", "OUT (C),D", "SBC HL,DE", "LD (#h),DE", + "DB EDh,54h", "DB EDh,55h", "IM 1", "LD A,I", + "IN E,(C)", "OUT (C),E", "ADC HL,DE", "LD DE,(#h)", + "DB EDh,5Ch", "DB EDh,5Dh", "IM 2", "LD A,R", + "IN H,(C)", "OUT (C),H", "SBC HL,HL", "LD (#h),HL", + "DB EDh,64h", "DB EDh,65h", "DB EDh,66h", "RRD", + "IN L,(C)", "OUT (C),L", "ADC HL,HL", "LD HL,(#h)", + "DB EDh,6Ch", "DB EDh,6Dh", "DB EDh,6Eh", "RLD", + "IN F,(C)", "DB EDh,71h", "SBC HL,SP", "LD (#h),SP", + "DB EDh,74h", "DB EDh,75h", "DB EDh,76h", "DB EDh,77h", + "IN A,(C)", "OUT (C),A", "ADC HL,SP", "LD SP,(#h)", + "DB EDh,7Ch", "DB EDh,7Dh", "DB EDh,7Eh", "DB EDh,7Fh", + "DB EDh,80h", "DB EDh,81h", "DB EDh,82h", "DB EDh,83h", + "DB EDh,84h", "DB EDh,85h", "DB EDh,86h", "DB EDh,87h", + "DB EDh,88h", "DB EDh,89h", "DB EDh,8Ah", "DB EDh,8Bh", + "DB EDh,8Ch", "DB EDh,8Dh", "DB EDh,8Eh", "DB EDh,8Fh", + "DB EDh,90h", "DB EDh,91h", "DB EDh,92h", "DB EDh,93h", + "DB EDh,94h", "DB EDh,95h", "DB EDh,96h", "DB EDh,97h", + "DB EDh,98h", "DB EDh,99h", "DB EDh,9Ah", "DB EDh,9Bh", + "DB EDh,9Ch", "DB EDh,9Dh", "DB EDh,9Eh", "DB EDh,9Fh", + "LDI", "CPI", "INI", "OUTI", + "DB EDh,A4h", "DB EDh,A5h", "DB EDh,A6h", "DB EDh,A7h", + "LDD", "CPD", "IND", "OUTD", + "DB EDh,ACh", "DB EDh,ADh", "DB EDh,AEh", "DB EDh,AFh", + "LDIR", "CPIR", "INIR", "OTIR", + "DB EDh,B4h", "DB EDh,B5h", "DB EDh,B6h", "DB EDh,B7h", + "LDDR", "CPDR", "INDR", "OTDR", + "DB EDh,BCh", "DB EDh,BDh", "DB EDh,BEh", "DB EDh,BFh", + "DB EDh,C0h", "DB EDh,C1h", "DB EDh,C2h", "DB EDh,C3h", + "DB EDh,C4h", "DB EDh,C5h", "DB EDh,C6h", "DB EDh,C7h", + "DB EDh,C8h", "DB EDh,C9h", "DB EDh,CAh", "DB EDh,CBh", + "DB EDh,CCh", "DB EDh,CDh", "DB EDh,CEh", "DB EDh,CFh", + "DB EDh,D0h", "DB EDh,D1h", "DB EDh,D2h", "DB EDh,D3h", + "DB EDh,D4h", "DB EDh,D5h", "DB EDh,D6h", "DB EDh,D7h", + "DB EDh,D8h", "DB EDh,D9h", "DB EDh,DAh", "DB EDh,DBh", + "DB EDh,DCh", "DB EDh,DDh", "DB EDh,DEh", "DB EDh,DFh", + "DB EDh,E0h", "DB EDh,E1h", "DB EDh,E2h", "DB EDh,E3h", + "DB EDh,E4h", "DB EDh,E5h", "DB EDh,E6h", "DB EDh,E7h", + "DB EDh,E8h", "DB EDh,E9h", "DB EDh,EAh", "DB EDh,EBh", + "DB EDh,ECh", "DB EDh,EDh", "DB EDh,EEh", "DB EDh,EFh", + "DB EDh,F0h", "DB EDh,F1h", "DB EDh,F2h", "DB EDh,F3h", + "DB EDh,F4h", "DB EDh,F5h", "DB EDh,F6h", "DB EDh,F7h", + "DB EDh,F8h", "DB EDh,F9h", "DB EDh,FAh", "DB EDh,FBh", + "DB EDh,FCh", "DB EDh,FDh", "DB EDh,FEh", "DB EDh,FFh" +}; + +static const char* MnemonicsXX[256] = +{ + "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", + "EX AF,AF'", "ADD I%,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", + "DJNZ @h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", + "JR @h", "ADD I%,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", + "JR NZ,@h", "LD I%,#h", "LD (#h),I%", "INC I%", "INC I%h", "DEC I%h", "LD I%h,*h", "DAA", + "JR Z,@h", "ADD I%,I%", "LD I%,(#h)", "DEC I%", "INC I%l", "DEC I%l", "LD I%l,*h", "CPL", + "JR NC,@h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (I%+^h)", "DEC (I%+^h)", "LD (I%+^h),*h", "SCF", + "JR C,@h", "ADD I%,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", + "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,I%h", "LD B,I%l", "LD B,(I%+^h)", "LD B,A", + "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,I%h", "LD C,I%l", "LD C,(I%+^h)", "LD C,A", + "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,I%h", "LD D,I%l", "LD D,(I%+^h)", "LD D,A", + "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,I%h", "LD E,I%l", "LD E,(I%+^h)", "LD E,A", + "LD I%h,B", "LD I%h,C", "LD I%h,D", "LD I%h,E", "LD I%h,I%h", "LD I%h,I%l", "LD H,(I%+^h)", "LD I%h,A", + "LD I%l,B", "LD I%l,C", "LD I%l,D", "LD I%l,E", "LD I%l,I%h", "LD I%l,I%l", "LD L,(I%+^h)", "LD I%l,A", + "LD (I%+^h),B", "LD (I%+^h),C", "LD (I%+^h),D", "LD (I%+^h),E", "LD (I%+^h),H", "LD (I%+^h),L", "HALT", "LD (I%+^h),A", + "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,I%h", "LD A,I%l", "LD A,(I%+^h)", "LD A,A", + "ADD B", "ADD C", "ADD D", "ADD E", "ADD I%h", "ADD I%l", "ADD (I%+^h)", "ADD A", + "ADC B", "ADC C", "ADC D", "ADC E", "ADC I%h", "ADC I%l", "ADC (I%+^h)", "ADC,A", + "SUB B", "SUB C", "SUB D", "SUB E", "SUB I%h", "SUB I%l", "SUB (I%+^h)", "SUB A", + "SBC B", "SBC C", "SBC D", "SBC E", "SBC I%h", "SBC I%l", "SBC (I%+^h)", "SBC A", + "AND B", "AND C", "AND D", "AND E", "AND I%h", "AND I%l", "AND (I%+^h)", "AND A", + "XOR B", "XOR C", "XOR D", "XOR E", "XOR I%h", "XOR I%l", "XOR (I%+^h)", "XOR A", + "OR B", "OR C", "OR D", "OR E", "OR I%h", "OR I%l", "OR (I%+^h)", "OR A", + "CP B", "CP C", "CP D", "CP E", "CP I%h", "CP I%l", "CP (I%+^h)", "CP A", + "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD *h", "RST 00h", + "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC *h", "RST 08h", + "RET NC", "POP DE", "JP NC,#h", "OUTA (*h)", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", + "RET C", "EXX", "JP C,#h", "INA (*h)", "CALL C,#h", "PFX_DD", "SBC *h", "RST 18h", + "RET PO", "POP I%", "JP PO,#h", "EX I%,(SP)", "CALL PO,#h", "PUSH I%", "AND *h", "RST 20h", + "RET PE", "LD PC,I%", "JP PE,#h", "EX DE,I%", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", + "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", + "RET M", "LD SP,I%", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" +}; + +static const char* MnemonicsXCB[256] = +{ + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (I%@h)", "RLC A", + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (I%@h)", "RRC A", + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (I%@h)", "RL A", + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (I%@h)", "RR A", + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (I%@h)", "SLA A", + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (I%@h)", "SRA A", + "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (I%@h)", "SLL A", + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (I%@h)", "SRL A", + "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(I%@h)", "BIT 0,A", + "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(I%@h)", "BIT 1,A", + "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(I%@h)", "BIT 2,A", + "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(I%@h)", "BIT 3,A", + "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(I%@h)", "BIT 4,A", + "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(I%@h)", "BIT 5,A", + "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(I%@h)", "BIT 6,A", + "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(I%@h)", "BIT 7,A", + "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(I%@h)", "RES 0,A", + "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(I%@h)", "RES 1,A", + "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(I%@h)", "RES 2,A", + "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(I%@h)", "RES 3,A", + "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(I%@h)", "RES 4,A", + "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(I%@h)", "RES 5,A", + "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(I%@h)", "RES 6,A", + "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(I%@h)", "RES 7,A", + "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(I%@h)", "SET 0,A", + "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(I%@h)", "SET 1,A", + "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(I%@h)", "SET 2,A", + "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(I%@h)", "SET 3,A", + "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(I%@h)", "SET 4,A", + "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(I%@h)", "SET 5,A", + "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(I%@h)", "SET 6,A", + "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(I%@h)", "SET 7,A" +}; + +static const char* CPMCalls[41] = +{ + "System Reset", "Console Input", "Console Output", "Reader Input", "Punch Output", "List Output", "Direct I/O", "Get IOByte", + "Set IOByte", "Print String", "Read Buffered", "Console Status", "Get Version", "Reset Disk", "Select Disk", "Open File", + "Close File", "Search First", "Search Next", "Delete File", "Read Sequential", "Write Sequential", "Make File", "Rename File", + "Get Login Vector", "Get Current Disk", "Set DMA Address", "Get Alloc", "Write Protect Disk", "Get R/O Vector", "Set File Attr", "Get Disk Params", + "Get/Set User", "Read Random", "Write Random", "Get File Size", "Set Random Record", "Reset Drive", "N/A", "N/A", "Write Random 0 fill" +}; + +RUNCPM_DECL int32 Watch = -1; + +#endif /* RUNCPMDEBUG || defined(iDEBUG) */ + +RUNCPM_DECL void watchprint(uint16 pos) { + uint8 I, J; + _puts("\r\n"); + _puts(" Watch : "); + _puthex16(Watch); + _puts(" = "); + _puthex8(_RamRead(Watch)); + _putcon(':'); + _puthex8(_RamRead(Watch + 1)); + _puts(" / "); + for (J = 0, I = _RamRead(Watch); J < 8; ++J, I <<= 1) + _putcon(I & 0x80 ? '1' : '0'); + _putcon(':'); + for (J = 0, I = _RamRead(Watch + 1); J < 8; ++J, I <<= 1) + _putcon(I & 0x80 ? '1' : '0'); +} + +RUNCPM_DECL void memdump(uint16 pos) { + uint16 h = pos; + uint16 c = pos; + uint8 l, i; + uint8 ch = pos & 0xff; + + _puts(" "); + for (i = 0; i < 16; ++i) { + _puthex8(ch++ & 0x0f); + _puts(" "); + } + _puts("\r\n"); + _puts(" "); + for (i = 0; i < 16; ++i) + _puts("---"); + _puts("\r\n"); + for (l = 0; l < 16; ++l) { + _puthex16(h); + _puts(" : "); + for (i = 0; i < 16; ++i) { + _puthex8(_RamRead(h++)); + _puts(" "); + } + for (i = 0; i < 16; ++i) { + ch = _RamRead(c++); + _putcon(ch > 31 && ch < 127 ? ch : '.'); + } + _puts("\r\n"); + } +} + +static int read_hex8(uint8 *out) { + unsigned int v = 0; + int count = 0; + int ch; + + /* Read characters until newline/carriage return */ + while (1) { + ch = _getcon(); + /* End on CR or LF */ + if (ch == '\r' || ch == '\n') + break; + + /* Backspace handling (BS=8, DEL=127) */ + if (ch == 8 || ch == 127) { + if (count > 0) { + /* erase last hex digit visually (basic backspace handling) */ + _putcon('\b'); + _putcon(' '); + _putcon('\b'); + v >>= 4; + --count; + } + continue; + } + + /* Accept 0-9 */ + if (ch >= '0' && ch <= '9') { + if (count < 2) { + v = (v << 4) | (unsigned int)(ch - '0'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Accept a-f */ + if (ch >= 'a' && ch <= 'f') { + if (count < 2) { + v = (v << 4) | (unsigned int)(10 + ch - 'a'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Accept A-F */ + if (ch >= 'A' && ch <= 'F') { + if (count < 2) { + v = (v << 4) | (unsigned int)(10 + ch - 'A'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Ignore 'x'/'X' to allow typing "0x..." */ + if (ch == 'x' || ch == 'X') + continue; + + /* Ignore any other characters */ + } + + /* move to next line visually */ + _putcon('\r'); + _putcon('\n'); + + if (count == 0) + return 0; /* no digits entered */ + + *out = (uint8)(v & 0xFFu); + return 1; +} + +static int read_hex16(uint16 *out) { + unsigned int v = 0; + int count = 0; + int ch; + + /* Read characters until newline/carriage return */ + while (1) { + ch = _getcon(); + /* End on CR or LF */ + if (ch == '\r' || ch == '\n') + break; + + /* Backspace handling (BS=8, DEL=127) */ + if (ch == 8 || ch == 127) { + if (count > 0) { + /* erase last hex digit visually (basic backspace handling) */ + _putcon('\b'); + _putcon(' '); + _putcon('\b'); + v >>= 4; + --count; + } + continue; + } + + /* Accept 0-9 */ + if (ch >= '0' && ch <= '9') { + if (count < 4) { + v = (v << 4) | (unsigned int)(ch - '0'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Accept a-f */ + if (ch >= 'a' && ch <= 'f') { + if (count < 4) { + v = (v << 4) | (unsigned int)(10 + ch - 'a'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Accept A-F */ + if (ch >= 'A' && ch <= 'F') { + if (count < 4) { + v = (v << 4) | (unsigned int)(10 + ch - 'A'); + ++count; + _putcon((char)ch); + } + continue; + } + + /* Ignore 'x'/'X' to allow typing "0x..." */ + if (ch == 'x' || ch == 'X') + continue; + + /* Ignore any other characters */ + } + + /* move to next line visually */ + _putcon('\r'); + _putcon('\n'); + + if (count == 0) + return 0; /* no digits entered */ + + *out = (uint16)(v & 0xFFFFu); + return 1; +} + +/* Read opcode prefixes from memory at 'pos', advance pos to the + first operand byte and return the mnemonic pointer. It also returns the + initial byte-count (prefixes + opcode bytes consumed so far) via *countp + and an optional prefix character ('X'/'Y' or 0) via *prefixp. + + Inputs: + posp - pointer to the position (will be advanced to operand start) + Outputs: + returns const char* mnemonic string (one of Mnemonics*, MnemonicsCB, ...) + *countp set to initial consumed bytes (1 or more) + *prefixp set to 'X' or 'Y' or 0 if applicable +*/ +static const char *GetMnemonicAt(uint16 *posp, uint8 *countp, char *prefixp) { + uint16 pos = *posp; + uint8 ch = _RamRead(pos); + uint8 count = 1; + char C = 0; + const char *txt; + + switch (ch) { + case 0xCB: + ++pos; + txt = MnemonicsCB[_RamRead(pos++)]; + count++; + break; + case 0xED: + ++pos; + txt = MnemonicsED[_RamRead(pos++)]; + count++; + break; + case 0xDD: + ++pos; + C = 'X'; + if (_RamRead(pos) != 0xCB) { + txt = MnemonicsXX[_RamRead(pos++)]; + ++count; + } else { + ++pos; + txt = MnemonicsXCB[_RamRead(pos++)]; + count += 2; + } + break; + case 0xFD: + ++pos; + C = 'Y'; + if (_RamRead(pos) != 0xCB) { + txt = MnemonicsXX[_RamRead(pos++)]; + ++count; + } else { + ++pos; + txt = MnemonicsXCB[_RamRead(pos++)]; + count += 2; + } + break; + default: + /* Normal opcode */ + txt = Mnemonics[_RamRead(pos++)]; + break; + } + + *posp = pos; /* advanced to first operand byte (if any) */ + *countp = count; /* consumed opcode/prefix bytes so far */ + if (prefixp) + *prefixp = C; + return txt; +} + +/* InstructionLength - compute the number of bytes used by the instruction at pos */ +static uint8 InstructionLength(uint16 pos) { + uint8 count = 0; + uint8 initial; + char C = 0; + const char *txt; + + /* Get mnemonic and advance pos to the operand area. initial counts prefixes/opcode */ + txt = GetMnemonicAt(&pos, &initial, &C); + count = initial; + + /* Walk the mnemonic-format string to count operand bytes */ + while (*txt != 0) { + switch (*txt) { + case '*': /* one immediate byte */ + case '^': /* one immediate byte */ + txt += 2; + ++count; + ++pos; + break; + case '#': /* two-byte immediate (word) */ + txt += 2; + count += 2; + pos += 2; + break; + case '@': /* relative displacement (one byte) */ + txt += 2; + ++count; + ++pos; + break; + case '%': /* prefix placeholder in mnemonic text, no operand */ + ++txt; + break; + default: + ++txt; + } + } + + return count; +} + +/* TextLength - compute the length of the text representation of the instruction at pos */ +static uint8 TextLength(uint16 pos) { + uint8 len = 0; + const char *txt = GetMnemonicAt(&pos, &len, NULL); + len = 0; + while (*txt != 0) { + switch (*txt) { + case '*': + case '^': + txt += 2; + len += 2; // 1 byte + 2 hex digits + break; + case '#': + txt += 2; + len += 4; // 2 bytes + 2 hex digits + break; + case '@': + txt += 2; + len += 4; // 1 byte + 2 hex digits + break; + case '%': + ++txt; + ++len; + break; + default: + ++txt; + ++len; + } + } + return len; +} + +/* Disassemble instruction at given address */ +RUNCPM_DECL uint8 Disasm(uint16 pos) { + /* New Disasm: print full opcode byte column, then mnemonic. */ + const char *txt; + uint8 Cflag = 0; + uint8 len = InstructionLength(pos); + uint16 op_pos = pos; + uint8 initial = 0; + + /* Print opcode bytes (up to len) */ + for (uint8 i = 0; i < len; ++i) { + _puthex8(_RamRead((pos + i) & 0xffff)); + _putcon(' '); + } + + /* pad bytes area to fixed column (use 3 chars per byte, target 12 chars) */ + int bytes_width = (int)len * 3; + int target = 12; /* enough for up to 4 bytes */ + for (int s = bytes_width; s < target; ++s) + _putcon(' '); + + /* Get mnemonic template (advances a temporary pos to operand start) */ + txt = GetMnemonicAt(&op_pos, &initial, (char *)&Cflag); + + /* Now print mnemonic with formatted operands. When we encounter operand markers + we consume bytes from op_pos (which points to first operand byte). */ + while (*txt != 0) { + switch (*txt) { + case '*': + case '^': { + /* single byte immediate */ + txt += 2; + uint8 v = _RamRead(op_pos++); + _puthex8(v); + break; + } + case '#': { + /* word immediate: print as 16-bit hex (little-endian) */ + txt += 2; + uint8 lo = _RamRead(op_pos); + uint8 hi = _RamRead(op_pos + 1); + uint16 w = (uint16)lo | ((uint16)hi << 8); + op_pos += 2; + _puthex16(w); + break; + } + case '@': { + /* relative displacement - show target address */ + char jr = (char)_RamRead(op_pos++); + uint16 target = (op_pos + jr) & 0xffff; + txt += 2; + _puthex16(target); + break; + } + case '%': + _putcon((char)Cflag); + ++txt; + break; + default: + _putcon(*txt); + ++txt; + } + } + + return (len); +} + +/* --- Simple instruction trace buffer and exec breakpoints --- + Implemented inline here to avoid changing platform Makefiles. + Trace records last N executed instructions (pc, bytes, len, reg snapshot). +*/ +/* FujiNet: the instruction-trace history (~20KB static .bss) is compiled out + unless RUNCPM_TRACE==1, to save ESP32 DRAM. */ +#ifndef RUNCPM_TRACE +#define RUNCPM_TRACE 0 +#endif + +#define TRACE_CAPACITY 512 +typedef struct { + uint16 pc; + uint8 len; + uint8 bytes[8]; + int32 AF, BC, DE, HL, IX, IY, SP; +} trace_entry_t; + +#if RUNCPM_TRACE +static trace_entry_t trace_buf[TRACE_CAPACITY]; +static uint32 trace_pos = 0; /* next write index */ +static int trace_enabled = 1; + +static void __attribute__((unused)) z80_trace_push(uint16 pc) { + if (!trace_enabled) + return; + trace_entry_t *e = &trace_buf[trace_pos++ % TRACE_CAPACITY]; + uint8 len = InstructionLength(pc); + if (len > 8) + len = 8; + e->pc = pc; + e->len = len; + for (uint8 i = 0; i < len; ++i) { + e->bytes[i] = _RamRead((pc + i) & 0xffff); + } + e->AF = AF; + e->BC = BC; + e->DE = DE; + e->HL = HL; + e->IX = IX; + e->IY = IY; + e->SP = SP; +} +#else +static inline void __attribute__((unused)) z80_trace_push(uint16 pc) { (void)pc; } +#endif /* RUNCPM_TRACE */ + +RUNCPM_DECL void z80_print_flags(uint16 AF) { + static const char Flags[9] = "SZ5H3PNC"; + uint8 J, I; + _puts(" ["); + for (J = 0, I = LOW_REGISTER(AF); J < 8; ++J, I <<= 1) + _putcon(I & 0x80 ? Flags[J] : '.'); + _puts("]"); +} + +#if RUNCPM_TRACE +static void z80_trace_dump(void) { + uint32 start = trace_pos; + uint32 i; + uint8 len; + _puts("\r\n--- Trace dump (most recent last) ---\r\n"); + for (i = 0; i < TRACE_CAPACITY; ++i) { + trace_entry_t *e = &trace_buf[(start + i) % TRACE_CAPACITY]; + /* skip empty entries (pc==0 and len==0) */ + if (e->len == 0 && e->pc == 0) + continue; + _puthex16(e->pc); + _puts(": "); + /* print disassembly for this address */ + Disasm(e->pc); + len = TextLength(e->pc); + /* pad to fixed column (target 16 chars) */ + int target = 16; + for (int s = len; s < target; ++s) + _putcon(' '); + _puts("BC:"); + _puthex16(e->BC); + _puts(" DE:"); + _puthex16(e->DE); + _puts(" HL:"); + _puthex16(e->HL); + _puts(" AF:"); + _puthex16(e->AF); + z80_print_flags(e->AF); + _puts(" SP:"); + _puthex16(e->SP); + _puts("\r\n"); + } + _puts("--- end trace ---\r\n"); +} +#else +static void __attribute__((unused)) z80_trace_dump(void) { + _puts("\r\nTrace disabled in this build.\r\n"); +} +#endif /* RUNCPM_TRACE */ + +/* Exec breakpoints: small fixed-size list. Only the bp_addrs[] list is used. */ +#define MAX_BREAKPOINTS 32 +static uint16 bp_addrs[MAX_BREAKPOINTS]; +static int bp_count = 0; + +static int z80_add_breakpoint(uint16 addr) { + /* prevent duplicates */ + for (int i = 0; i < bp_count; ++i) + if (bp_addrs[i] == addr) + return -2; + if (bp_count >= MAX_BREAKPOINTS) + return -1; + bp_addrs[bp_count++] = addr; + return 0; +} + +static void z80_clear_breakpoints(void) { + bp_count = 0; +} + +static int z80_remove_breakpoint(uint16 addr) { + for (int i = 0; i < bp_count; ++i) { + if (bp_addrs[i] == addr) { + /* shift remaining */ + for (int j = i; j + 1 < bp_count; ++j) + bp_addrs[j] = bp_addrs[j + 1]; + --bp_count; + return 0; + } + } + return -1; /* not found */ +} + +static int __attribute__((unused)) z80_check_breakpoints_on_exec(uint16 pc) { + for (int i = 0; i < bp_count; ++i) + if (bp_addrs[i] == pc) + return 1; + return 0; +} + +RUNCPM_DECL void Z80debug(void) { + uint8 ch = 0; + uint16 pos, l; + uint8 I; + uint16 bpoint; /* changed from unsigned int to 16-bit */ + uint8 loop = TRUE; + int res = 0; /* use a signed int for result checks */ + + _puts("\r\nDebug Mode - Press '?' for help"); + + while (loop && Debug) { + pos = PC; + _puts("\r\n"); + _puts("BC:"); + _puthex16(BC); + _puts(" DE:"); + _puthex16(DE); + _puts(" HL:"); + _puthex16(HL); + _puts(" AF:"); + _puthex16(AF); + _puts(" :"); + z80_print_flags(AF); + _puts("\r\n"); + _puts("IX:"); + _puthex16(IX); + _puts(" IY:"); + _puthex16(IY); + _puts(" SP:"); + _puthex16(SP); + _puts(" PC:"); + _puthex16(PC); + _puts(" : "); + + Disasm(pos); + + if (PC == 0x0005) { + if (LOW_REGISTER(BC) > 40) { + _puts(" (Unknown)"); + } else { + _puts(" ("); + _puts(CPMCalls[LOW_REGISTER(BC)]); + _puts(")"); + } + } + + if (Watch != -1) { + watchprint(Watch); + } + + _puts("\r\n"); + _puts("Command|? : "); + ch = _getcon(); + if (ch > 21 && ch < 127) + _putcon(ch); + switch (ch) { + case 't': + /* Trace to next instruction */ + loop = FALSE; + break; + case 'c': + /* Continue execution */ + loop = FALSE; + _puts("\r\n"); + Debug = 0; + break; + case 'b': + /* Dump memory pointed by (BC) */ + _puts("\r\n"); + memdump(BC); + break; + case 'd': + /* Dump memory pointed by (DE) */ + _puts("\r\n"); + memdump(DE); + break; + case 'h': + /* Dump memory pointed by (HL) */ + _puts("\r\n"); + memdump(HL); + break; + case 'p': + /* Dump memory page pointed by (PC) */ + _puts("\r\n"); + memdump(PC & 0xFF00); + break; + case 's': + /* Dump memory page pointed by (SP) */ + _puts("\r\n"); + memdump(SP & 0xFF00); + break; + case 'x': + /* Dump memory page pointed by (IX) */ + _puts("\r\n"); + memdump(IX & 0xFF00); + break; + case 'y': + /* Dump memory page pointed by (IY) */ + _puts("\r\n"); + memdump(IY & 0xFF00); + break; + case 'a': + /* Dump memory pointed by dmaAddr */ + _puts("\r\n"); + memdump(dmaAddr); + break; + case 'l': + /* Disassemble from current PC */ + _puts("\r\n"); + I = 16; + l = pos; + while (I > 0) { + _puthex16(l); + _puts(" : "); + l += Disasm(l); + _puts("\r\n"); + --I; + } + break; + case 'A': + /* Add breakpoint at address */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) { + if (z80_add_breakpoint(bpoint) == 0) { + _puts("Breakpoint added: "); + _puthex16(bpoint); + _puts("\r\n"); + } else { + _puts("Breakpoint list full\r\n"); + } + } else { + _puts("Invalid address\r\n"); + } + break; + case 'B': + /* List breakpoints set via 'A' */ + _puts(" Breakpoints:\r\n"); + if (bp_count == 0) { + _puts(" (none)\r\n"); + } else { + for (int i = 0; i < bp_count; ++i) { + uint16 a = bp_addrs[i]; + _puthex16(a); + _puts(" : "); + Disasm(a); + _puts("\r\n"); + } + } + break; + case 'C': + /* Clear all breakpoints */ + z80_clear_breakpoints(); + _puts(" Breakpoints cleared\r\n"); + break; + case 'D': + /* Dump memory at address */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) + memdump(bpoint); + else + _puts("Invalid address\r\n"); + break; + case 'E': + /* Erase breakpoint at address */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) { + if (z80_remove_breakpoint(bpoint) == 0) { + _puts("Breakpoint removed: "); + _puthex16(bpoint); + _puts("\r\n"); + } else { + _puts("Breakpoint not found\r\n"); + } + } else { + _puts("Invalid address\r\n"); + } + break; + case 'J': + /* Jump - set PC to address */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) { + PC = bpoint; + _puts("PC set to "); + _puthex16(PC); + _puts("\r\n"); + } else { + _puts("Invalid address\r\n"); + } + break; + case 'L': + /* Disassemble at address */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) { + I = 16; + l = bpoint; + while (I > 0) { + _puthex16(l); + _puts(" : "); + l += Disasm(l); + _puts("\r\n"); + --I; + } + } else { + _puts("Invalid address\r\n"); + } + break; + case 'M': + /* Modify memory byte at address until Enter is pressed */ + _puts("\r\n Addr: "); + res = read_hex16(&bpoint); + if (res) { + uint16 addr = bpoint; + uint8 val; + while (1) { + _puthex16(addr); + _puts(" : "); + val = _RamRead(addr); + _puthex8(val); + _puts(" -> "); + res = read_hex8(&val); + if (res) { + _RamWrite(addr, val); + addr = (addr + 1) & 0xFFFF; + } else { + break; /* exit on no input */ + } + } + } else { + _puts("Invalid address\r\n"); + } + break; + case 'R': + /* Dump recent trace */ + z80_trace_dump(); + break; + case 'T': + /* Step over a call */ + loop = FALSE; + Step = pos + InstructionLength(pos); + Debug = 0; + break; + case 'U': + /* Unwatch - clear any byte/word watch */ + Watch = -1; + _puts("\r\nWatch cleared\r\n"); + break; + case 'W': + /* Watch - set a byte/word watch */ + _puts(" Addr: "); + res = read_hex16(&bpoint); + if (res) { + Watch = bpoint; + _puts("Watch set to "); + _puthex16(Watch); + _puts("\r\n"); + } else { + _puts("Invalid address\r\n"); + } + break; + case 'X': + /* Exit RunCPM */ + _puts("\r\nExiting...\r\n"); + Debug = 0; + Status = 1; + break; + case '?': + /* Help */ + _puts("\r\n"); + _puts("Lowercase commands:\r\n"); + _puts(" t - traces to the next instruction\r\n"); + _puts(" c - Continue execution\r\n"); + _puts(" b - Dumps memory pointed by (BC)\r\n"); + _puts(" d - Dumps memory pointed by (DE)\r\n"); + _puts(" h - Dumps memory pointed by (HL)\r\n"); + _puts(" p - Dumps the page (PC) points to\r\n"); + _puts(" s - Dumps the page (SP) points to\r\n"); + _puts(" x - Dumps the page (IX) points to\r\n"); + _puts(" y - Dumps the page (IY) points to\r\n"); + _puts(" a - Dumps memory pointed by dmaAddr\r\n"); + _puts(" l - Disassembles from current PC\r\n"); + _puts("Uppercase commands:\r\n"); + _puts(" A - Add breakpoint at address\r\n"); + _puts(" B - List breakpoints\r\n"); + _puts(" C - Clear all breakpoints\r\n"); + _puts(" D - Dumps memory at address\r\n"); + _puts(" E - Erase breakpoint at address\r\n"); + _puts(" J - Jumps to address (sets PC)\r\n"); + _puts(" L - Disassembles at address\r\n"); + _puts(" M - Modify memory at address\r\n"); + _puts(" R - Dump recent trace\r\n"); + _puts(" T - Steps over a call\r\n"); + _puts(" U - Clears the byte/word watch\r\n"); + _puts(" W - Sets a byte/word watch\r\n"); + _puts(" X - Exit RunCPM\r\n"); + break; + default: + _puts(" ???\r\n"); + } + } +} + +#endif // ifndef DEBUG_H \ No newline at end of file diff --git a/lib/runcpm/disk.h b/lib/runcpm/disk.h index 7840b9446..87a59988a 100644 --- a/lib/runcpm/disk.h +++ b/lib/runcpm/disk.h @@ -1,3 +1,5 @@ +/* FujiNet: guard renamed DISK_H -> CPM_DISK_H to avoid colliding with the + bus-device disk headers (sio/disk.h, rc2014/disk.h, h89/disk.h). */ #ifndef CPM_DISK_H #define CPM_DISK_H @@ -8,11 +10,11 @@ /* see main.c for definition */ #ifdef __linux__ -#include + #include #endif #ifdef __DJGPP__ -#include + #include #endif /* @@ -21,619 +23,842 @@ Disk errors #define errWRITEPROT 1 #define errSELECT 2 -#define RW (roVector & (1 << cDrive)) +#define RW (roVector & (1 << cDrive)) // Prints out a BDOS error RUNCPM_DECL void _error(uint8 error) { - _puts("\r\nBdos Err on "); - _putcon('A' + cDrive); - _puts(": "); - switch (error) { - case errWRITEPROT: - _puts("R/O"); - break; - case errSELECT: - _puts("Select"); - break; - default: - _puts("\r\nCP/M ERR"); - break; - } - Status = _getch(); - _puts("\r\n"); - cDrive = oDrive = _RamRead(DSKByte) & 0x0f; - Status = 2; + _puts("\r\nBdos Err on "); + _putcon('A' + cDrive); + _puts(": "); + switch (error) { + case errWRITEPROT: + _puts("R/O"); + break; + case errSELECT: + _puts("Select"); + break; + default: + _puts("\r\nCP/M ERR"); + break; + } + Status = _getcon(); + _puts("\r\n"); + cDrive = oDrive = _RamRead(DSKByte) & 0x0f; + Status = STATUS_RESTART; } // Selects the disk to be used by the next disk function RUNCPM_DECL int _SelectDisk(uint8 dr) { - uint8 result = 0xff; - uint8 disk[2] = { 'A', 0 }; - - if (!dr || dr == '?') { - dr = cDrive; // This will set dr to defDisk in case no disk is passed - } else { - --dr; // Called from BDOS, set dr back to 0=A: format - } - - disk[0] += dr; - if (_sys_select(&disk[0])) { - loginVector = loginVector | (1 << (disk[0] - 'A')); - result = 0x00; - } else { - _error(errSELECT); - } - - return(result); + uint8 result = 0xff; + uint8 disk[2] = {'A', 0}; + + if (!dr || dr == '?') { + dr = cDrive; // This will set dr to defDisk in case no disk is passed + } else { + --dr; // Called from BDOS, set dr back to 0=A: format + } + + disk[0] += dr; + if (_sys_select(&disk[0])) { + loginVector = loginVector | (1 << (disk[0] - 'A')); + result = 0x00; + } else { + cDrive = oDrive = dr; + _error(errSELECT); + } + + return (result); } // Converts a FCB entry onto a host OS filename string -RUNCPM_DECL uint8 _FCBtoHostname(uint16 fcbaddr, uint8* filename) { - uint8 addDot = TRUE; - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 i = 0; - uint8 unique = TRUE; - uint8 c; - - if (F->dr && F->dr != '?') { - *(filename++) = (F->dr - 1) + 'A'; - } else { - *(filename++) = cDrive + 'A'; - } - *(filename++) = FOLDERCHAR; - - *(filename++) = toupper(tohex(userCode)); - *(filename++) = FOLDERCHAR; - - if (F->dr != '?') { - while (i < 8) { - c = F->fn[i] & 0x7F; +RUNCPM_DECL uint8 _FCBtoHostname(uint16 fcbaddr, uint8 *filename) { + uint8 addDot = TRUE; + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 i = 0; + uint8 unique = TRUE; + uint8 c; + + if (F->dr && F->dr != '?') { + *(filename++) = (F->dr - 1) + 'A'; + } else { + *(filename++) = cDrive + 'A'; + } + *(filename++) = FOLDERCHAR; + + *(filename++) = toupper(tohex(userCode)); + *(filename++) = FOLDERCHAR; + + if (F->dr != '?') { + while (i < 8) { + c = F->fn[i] & 0x7F; #ifdef NOSLASH - if (c == '/') - c = '_'; + if (c == '/') + c = '_'; #endif - if (c > 32) - *(filename++) = toupper(c); - if (c == '?') - unique = FALSE; - ++i; - } - i = 0; - while (i < 3) { - c = F->tp[i] & 0x7F; - if (c > 32) { - if (addDot) { - addDot = FALSE; - *(filename++) = '.'; // Only add the dot if there's an extension - } + if (c > 32) + *(filename++) = toupper(c); + if (c == '?') + unique = FALSE; + ++i; + } + i = 0; + while (i < 3) { + c = F->tp[i] & 0x7F; + if (c > 32) { + if (addDot) { + addDot = FALSE; + *(filename++) = '.'; // Only add the dot if there's an extension + } #ifdef NOSLASH - if (c == '/') - c = '_'; + if (c == '/') + c = '_'; #endif - *(filename++) = toupper(c); - } - if (c == '?') - unique = FALSE; - ++i; - } - } else { - for (i = 0; i < 8; ++i) { - *(filename++) = '?'; - } - *(filename++) = '.'; - for (i = 0; i < 3; ++i) { - *(filename++) = '?'; - } - unique = FALSE; - } - *filename = 0x00; - - return(unique); + *(filename++) = toupper(c); + } + if (c == '?') + unique = FALSE; + ++i; + } + } else { + for (i = 0; i < 8; ++i) + *(filename++) = '?'; + *(filename++) = '.'; + for (i = 0; i < 3; ++i) + *(filename++) = '?'; + unique = FALSE; + } + *filename = 0x00; + + return (unique); } -// Convers a host OS filename string onto a FCB entry -RUNCPM_DECL void _HostnameToFCB(uint16 fcbaddr, uint8* filename) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 i = 0; - - ++filename; - if (*filename == FOLDERCHAR) { // Skips the drive and / if needed - filename += 3; - } else { - --filename; - } - - while (*filename != 0 && *filename != '.') { - F->fn[i] = toupper(*filename); - ++filename; - ++i; - } - while (i < 8) { - F->fn[i] = ' '; - ++i; - } - if (*filename == '.') - ++filename; - i = 0; - while (*filename != 0) { - F->tp[i] = toupper(*filename); - ++filename; - ++i; - } - while (i < 3) { - F->tp[i] = ' '; - ++i; - } +// Converts a host OS filename string onto a FCB entry +RUNCPM_DECL void _HostnameToFCB(uint16 fcbaddr, uint8 *filename) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 i = 0; + + ++filename; + if (*filename == FOLDERCHAR) { // Skips the drive and / if needed + filename += 3; + } else { + --filename; + } + + while (*filename != 0 && *filename != '.') { + F->fn[i] = toupper(*filename); + ++filename; + ++i; + } + while (i < 8) { + F->fn[i] = ' '; + ++i; + } + if (*filename == '.') + ++filename; + i = 0; + while (*filename != 0) { + F->tp[i] = toupper(*filename); + ++filename; + ++i; + } + while (i < 3) { + F->tp[i] = ' '; + ++i; + } } // Converts a string name (AB.TXT) onto FCB name (AB TXT) -RUNCPM_DECL void _HostnameToFCBname(uint8* from, uint8* to) { - int i = 0; - - ++from; - if (*from == FOLDERCHAR) { // Skips the drive and / if needed - from += 3; - } else { - --from; - } - - while (*from != 0 && *from != '.') { - *to = toupper(*from); - ++to; ++from; ++i; - } - while (i < 8) { - *to = ' '; - ++to; ++i; - } - if (*from == '.') - ++from; - i = 0; - while (*from != 0) { - *to = toupper(*from); - ++to; ++from; ++i; - } - while (i < 3) { - *to = ' '; - ++to; ++i; - } - *to = 0; +RUNCPM_DECL void _HostnameToFCBname(uint8 *from, uint8 *to) { + int i = 0; + + ++from; + if (*from == FOLDERCHAR) { // Skips the drive and / if needed + from += 3; + } else { + --from; + } + + while (*from != 0 && *from != '.') { + *to = toupper(*from); + ++to; + ++from; + ++i; + } + while (i < 8) { + *to = ' '; + ++to; + ++i; + } + if (*from == '.') + ++from; + i = 0; + while (*from != 0) { + *to = toupper(*from); + ++to; + ++from; + ++i; + } + while (i < 3) { + *to = ' '; + ++to; + ++i; + } + *to = 0; } // Creates a fake directory entry for the current dmaAddr FCB -RUNCPM_DECL void _mockupDirEntry(void) { - CPM_DIRENTRY* DE = (CPM_DIRENTRY*)_RamSysAddr(dmaAddr); - uint8 blocks, i; - - for (i = 0; i < sizeof(CPM_DIRENTRY); ++i) { - _RamWrite(dmaAddr + i, 0x00); // zero out directory entry - } - _HostnameToFCB(dmaAddr, (uint8*)findNextDirName); - - if (allUsers) { - DE->dr = currFindUser; // set user code for return - } else { - DE->dr = userCode; - } - // does file fit in a single directory entry? - if (fileExtents <= extentsPerDirEntry) { - if (fileExtents) { - DE->ex = (fileExtents - 1 + fileExtentsUsed) % (MaxEX + 1); - DE->s2 = (fileExtents - 1 + fileExtentsUsed) / (MaxEX + 1); - DE->rc = fileRecords - (BlkEX * (fileExtents - 1)); - } - blocks = (fileRecords >> blockShift) + ((fileRecords & blockMask) ? 1 : 0); - fileRecords = 0; - fileExtents = 0; - fileExtentsUsed = 0; - } else { // no, max out the directory entry - DE->ex = (extentsPerDirEntry - 1 + fileExtentsUsed) % (MaxEX + 1); - DE->s2 = (extentsPerDirEntry - 1 + fileExtentsUsed) / (MaxEX + 1); - DE->rc = BlkEX; - blocks = numAllocBlocks < 256 ? 16 : 8; - // update remaining records and extents for next call - fileRecords -= BlkEX * extentsPerDirEntry; - fileExtents -= extentsPerDirEntry; - fileExtentsUsed += extentsPerDirEntry; - } - // phoney up an appropriate number of allocation blocks - if (numAllocBlocks < 256) { - for (i = 0; i < blocks; ++i) { - DE->al[i] = (uint8)firstFreeAllocBlock++; - } - } else { - for (i = 0; i < 2 * blocks; i += 2) { - DE->al[i] = firstFreeAllocBlock & 0xFF; - DE->al[i + 1] = firstFreeAllocBlock >> 8; - ++firstFreeAllocBlock; - } - } +RUNCPM_DECL void _mockupDirEntry(uint8 mode) { + CPM_DIRENTRY *DirEntry = (CPM_DIRENTRY *)_RamSysAddr(dmaAddr); + uint8 blocks, i; + + for (i = 0; i < sizeof(CPM_DIRENTRY); ++i) + _RamWrite(dmaAddr + i, 0x00); // zero out directory entry + unsigned char *shortName; + if (mode) { + shortName = (unsigned char *)&findNextDirName[strlen(FILEBASE)]; + } else { + shortName = (unsigned char *)&findNextDirName[0]; + } + _HostnameToFCB(dmaAddr, (uint8 *)shortName); + + if (allUsers) { + DirEntry->dr = currFindUser; // set user code for return + } else { + DirEntry->dr = userCode; + } + + /* Ensure S1 is deterministic (zero) — we already zeroed the entry above, + but make the intent explicit so callers/readers aren't surprised. */ + DirEntry->s1 = 0; + + // does file fit in a single directory entry? + if (fileExtents <= extentsPerDirEntry) { + if (fileExtents) { + DirEntry->ex = (fileExtents - 1 + fileExtentsUsed) % (MaxEX + 1); + DirEntry->s2 = (fileExtents - 1 + fileExtentsUsed) / (MaxEX + 1); + DirEntry->rc = fileRecords - (BlkEX * (fileExtents - 1)); + } + blocks = (fileRecords >> blockShift) + ((fileRecords & blockMask) ? 1 : 0); + fileRecords = 0; + fileExtents = 0; + fileExtentsUsed = 0; + } else { // no, max out the directory entry + DirEntry->ex = (extentsPerDirEntry - 1 + fileExtentsUsed) % (MaxEX + 1); + DirEntry->s2 = (extentsPerDirEntry - 1 + fileExtentsUsed) / (MaxEX + 1); + DirEntry->rc = BlkEX; + blocks = numAllocBlocks < 256 ? 16 : 8; + // update remaining records and extents for next call + fileRecords -= BlkEX * extentsPerDirEntry; + fileExtents -= extentsPerDirEntry; + fileExtentsUsed += extentsPerDirEntry; + } + + /* SAFETY: clamp blocks so we never overflow DirEntry->al[]. + On small disks AL is 16 bytes (one byte per block), + on large disks AL is 16 bytes but stored as 8 16-bit values (pairs). */ + uint8 maxBlocks = (numAllocBlocks < 256) ? 16 : 8; + if (blocks > maxBlocks) + blocks = maxBlocks; + + // phoney up an appropriate number of allocation blocks + if (numAllocBlocks < 256) { + for (i = 0; i < blocks; ++i) + DirEntry->al[i] = (uint8)firstFreeAllocBlock++; + } else { + for (i = 0; i < 2 * blocks; i += 2) { + DirEntry->al[i] = firstFreeAllocBlock & 0xFF; + DirEntry->al[i + 1] = firstFreeAllocBlock >> 8; + ++firstFreeAllocBlock; + } + } } // Matches a FCB name to a search pattern -RUNCPM_DECL uint8 match(uint8* fcbname, uint8* pattern) { - uint8 result = 1; - uint8 i; - - for (i = 0; i < 12; ++i) { - if (*pattern == '?' || *pattern == *fcbname) { - ++pattern; ++fcbname; - continue; - } else { - result = 0; - break; - } - } - return(result); +RUNCPM_DECL uint8 match(uint8 *fcbname, uint8 *pattern) { + uint8 result = 1; + uint8 i; + + for (i = 0; i < 12; ++i) { + if (*pattern == '?' || *pattern == *fcbname) { + ++pattern; + ++fcbname; + continue; + } else { + result = 0; + break; + } + } + return (result); } // Returns the size of a file RUNCPM_DECL long _FileSize(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - long r, l = -1; - - if (!_SelectDisk(F->dr)) { - _FCBtoHostname(fcbaddr, &filename[0]); - l = _sys_filesize(filename); - r = l % BlkSZ; - if (r) - l = l + BlkSZ - r; - } - return(l); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + long r, l = -1; + + if (!_SelectDisk(F->dr)) { + _FCBtoHostname(fcbaddr, &filename[0]); + l = _sys_filesize(filename); + if (l != -1) { + r = l % BlkSZ; + if (r) + l = l + BlkSZ - r; + } + } + return (l); } // Opens a file -RUNCPM_DECL uint8 _OpenFile(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - long len; - int32 i; - - if (!_SelectDisk(F->dr)) { - _FCBtoHostname(fcbaddr, &filename[0]); - if (_sys_openfile(&filename[0])) { - - len = _FileSize(fcbaddr) / BlkSZ; // Compute the len on the file in blocks - - F->s1 = 0x00; - F->s2 = 0x80; // set unmodified flag - - - F->rc = len > MaxRC ? MaxRC : (uint8)len; - for (i = 0; i < 16; ++i) // Clean up AL - F->al[i] = 0x00; - - result = 0x00; - } - } - return(result); +// Returns a 16-bit packed value: (hardware_error<<8) | result +// result (A) = 0-3 for success or 0xFF for error (CP/M3 semantics) +RUNCPM_DECL uint16 _OpenFile(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; // low byte -> A + uint8 hwerr = 0x00; // high byte -> B/H (hardware error when result==0xFF) + long records; + int32 i; + + if (!_SelectDisk(F->dr)) { + _FCBtoHostname(fcbaddr, &filename[0]); + if (!filename[4]) + return (((uint16)hwerr << 8) | result); // Invalid filename + + if (_sys_openfile(&filename[0])) { + /* Get raw file size and compute record counts (round up). */ + long rawsize = _sys_filesize(&filename[0]); + if (rawsize < 0) + return (((uint16)hwerr << 8) | result); + + records = (rawsize + (BlkSZ - 1)) / BlkSZ; // rounded-up records + + F->s1 = 0x00; + F->s2 = 0x80; // set unmodified flag + + /* rc = number of 128-byte records in the current logical extent. + Clamp to the extent size (BlkEX). */ + F->rc = (records > BlkEX) ? (uint8)BlkEX : (uint8)records; + + for (i = 0; i < 16; ++i) // Clean up AL + F->al[i] = 0x00; + + #ifdef CPM3 + /* CP/M3 behaviour: if CR was set to 0xFF on entry, return the + last-record byte count in CR. */ + if (F->cr == 0xFF) { + if (rawsize <= 0) { + F->cr = 0x00; + } else { + uint8 lrbc = rawsize % BlkSZ; + if (lrbc == 0) + lrbc = BlkSZ; + F->cr = lrbc; + } + } + #endif // ifdef CPM3 + + result = 0x00; + } + } else { + /* Disk selection failed; _SelectDisk has already triggered an error. + Report a hardware/select error in the high byte. */ + hwerr = errSELECT; + } + return (((uint16)hwerr << 8) | result); } // Closes a file -RUNCPM_DECL uint8 _CloseFile(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - if (!_SelectDisk(F->dr)) { - if (!(F->s2 & 0x80)) { // if file is modified - if (!RW) { - _FCBtoHostname(fcbaddr, &filename[0]); - if (fcbaddr == BatchFCB) - _Truncate((char*)filename, F->rc); // Truncate $$$.SUB to F->rc CP/M records so SUBMIT.COM can work - result = 0x00; - } else { - _error(errWRITEPROT); - } - } else { - result = 0x00; - } - } - return(result); +// Returns a 16-bit packed value: (hardware_error<<8) | result +// result (A) = 0-3 for success or 0xFF for error (CP/M3 semantics) +RUNCPM_DECL uint16 _CloseFile(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint8 hwerr = 0x00; + + if (!_SelectDisk(F->dr)) { + if (!(F->s2 & 0x80)) { // if file is modified + if (!RW) { + _FCBtoHostname(fcbaddr, &filename[0]); + if (!filename[4]) + return (((uint16)hwerr << 8) | result); // Invalid filename + if (fcbaddr == BatchFCB) + _Truncate((char *)filename, F->rc); // Truncate $$$.SUB to F->rc CP/M records so SUBMIT.COM can work + + /* Under CP/M3, if F5' (top bit of fn[4]) is set then the pending + data are written and the file is made consistent but remains open. + In both cases ensure the FCB is marked unmodified on success. */ +#ifdef CPM3 + if (F->fn[4] & 0x80) { + F->s2 |= 0x80; // mark unmodified (file made consistent) + result = 0x00; // success, file remains open + } else { + F->s2 |= 0x80; // mark unmodified (file made consistent) + result = 0x00; // success, file closed + } +#else + result = 0x00; +#endif // ifdef CPM3 + } else { +#ifdef CPM3 + /* CP/M3 should return a hardware error instead of invoking the + host error handler. */ + hwerr = errWRITEPROT; +#else + _error(errWRITEPROT); +#endif + } + } else { + result = 0x00; + } + } else { + hwerr = errSELECT; + } + return (((uint16)hwerr << 8) | result); } // Creates a file RUNCPM_DECL uint8 _MakeFile(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - uint8 i; - - if (!_SelectDisk(F->dr)) { - if (!RW) { - _FCBtoHostname(fcbaddr, &filename[0]); - if (_sys_makefile(&filename[0])) { - F->ex = 0x00; // Makefile also initializes the FCB (file becomes "open") - F->s1 = 0x00; - F->s2 = 0x00; // newly created files are already modified - F->rc = 0x00; - for (i = 0; i < 16; ++i) // Clean up AL - F->al[i] = 0x00; - F->cr = 0x00; - result = 0x00; - } - } else { - _error(errWRITEPROT); - } - } - return(result); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint8 i; + + if (!_SelectDisk(F->dr)) { + if (!RW) { + _FCBtoHostname(fcbaddr, &filename[0]); + if (!filename[4]) + return (result); // Invalid filename + if (_sys_makefile(&filename[0])) { + F->ex = 0x00; // Makefile also initializes the FCB (file becomes "open") + F->s1 = 0x00; + F->s2 = 0x00; // newly created files are already modified + F->rc = 0x00; + for (i = 0; i < 16; ++i) // Clean up AL + F->al[i] = 0x00; + F->cr = 0x00; + result = 0x00; + } + } else { + _error(errWRITEPROT); + } + } + return (result); } // Searches for the first directory file RUNCPM_DECL uint8 _SearchFirst(uint16 fcbaddr, uint8 isdir) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - if (!_SelectDisk(F->dr)) { - _FCBtoHostname(fcbaddr, &filename[0]); - allUsers = F->dr == '?'; - allExtents = F->ex == '?'; - if (allUsers) { - result = _findfirstallusers(isdir); - } else { - result = _findfirst(isdir); - } - } - return(result); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + + if (!_SelectDisk(F->dr)) { + _FCBtoHostname(fcbaddr, &filename[0]); + if (!filename[4]) + return (result); // Invalid filename + allUsers = F->dr == '?'; + allExtents = F->ex == '?'; + if (allUsers) { + result = _findfirstallusers(isdir); + } else { + result = _findfirst(isdir); + } + } + return (result); } // Searches for the next directory file RUNCPM_DECL uint8 _SearchNext(uint16 fcbaddr, uint8 isdir) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(tmpFCB); - uint8 result = 0xff; - - if (!_SelectDisk(F->dr)) { - if (allUsers) { - result = _findnextallusers(isdir); - } else { - result = _findnext(isdir); - } - } - return(result); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(tmpFCB); + uint8 result = 0xff; + + if (!_SelectDisk(F->dr)) { + if (allUsers) { + result = _findnextallusers(isdir); + } else { + result = _findnext(isdir); + } + } + return (result); } // Deletes a file RUNCPM_DECL uint8 _DeleteFile(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); #if defined(USE_PUN) || defined(USE_LST) - CPM_FCB* T = (CPM_FCB*)_RamSysAddr(tmpFCB); + CPM_FCB *T = (CPM_FCB *)_RamSysAddr(tmpFCB); #endif - uint8 result = 0xff; - uint8 deleted = 0xff; + uint8 result = 0xff; + uint8 deleted = 0xff; - if (!_SelectDisk(F->dr)) { - if (!RW) { - result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file - while (result != 0xff) { + if (!_SelectDisk(F->dr)) { + if (!RW) { + result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file + while (result != 0xff) { #ifdef USE_PUN - if (!strcmp((char*)T->fn, "PUN TXT") && pun_open) { - _sys_fclose(pun_dev); - pun_open = FALSE; - } + if (!strcmp((char *)T->fn, "PUN TXT") && pun_open) { + _sys_fclose(pun_dev); + pun_open = FALSE; + } #endif #ifdef USE_LST - if (!strcmp((char*)T->fn, "LST TXT") && lst_open) { - _sys_fclose(lst_dev); - lst_open = FALSE; - } + if (!strcmp((char *)T->fn, "LST TXT") && lst_open) { + _sys_fclose(lst_dev); + lst_open = FALSE; + } #endif - _FCBtoHostname(tmpFCB, &filename[0]); - if (_sys_deletefile(&filename[0])) - deleted = 0x00; - result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file - } - } else { - _error(errWRITEPROT); - } - } - return(deleted); + _FCBtoHostname(tmpFCB, &filename[0]); + if (_sys_deletefile(&filename[0])) { + deleted = 0x00; + } else { + _error(errWRITEPROT); + break; + } + result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file + } + } else { + _error(errWRITEPROT); + } + } + return (deleted); } // Renames a file RUNCPM_DECL uint8 _RenameFile(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - if (!_SelectDisk(F->dr)) { - if (!RW) { - _RamWrite(fcbaddr + 16, _RamRead(fcbaddr)); // Prevents rename from moving files among folders - _FCBtoHostname(fcbaddr + 16, &newname[0]); - _FCBtoHostname(fcbaddr, &filename[0]); - if (_sys_renamefile(&filename[0], &newname[0])) - result = 0x00; - } else { - _error(errWRITEPROT); - } - } - return(result); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + + if (!_SelectDisk(F->dr)) { + if (!RW) { + _RamWrite(fcbaddr + 16, _RamRead(fcbaddr)); // Prevents rename from moving files between folders + _FCBtoHostname(fcbaddr + 16, &newname[0]); + _FCBtoHostname(fcbaddr, &filename[0]); + if (!newname[4]) + return (result); // Invalid filename + if (!filename[4]) + return (result); // Invalid filename + if (_sys_renamefile(&filename[0], &newname[0])) + result = 0x00; + } else { + _error(errWRITEPROT); + } + } + return (result); } // Sequential read -RUNCPM_DECL uint8 _ReadSeq(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) + - (F->ex * BlkEX * BlkSZ) + - (F->cr * BlkSZ); - - if (!_SelectDisk(F->dr)) { - _FCBtoHostname(fcbaddr, &filename[0]); - result = _sys_readseq(&filename[0], fpos); - if (!result) { // Read succeeded, adjust FCB - ++F->cr; - if (F->cr > MaxCR) { - F->cr = 1; - ++F->ex; - } - if (F->ex > MaxEX) { - F->ex = 0; - ++F->s2; - } - if ((F->s2 & 0x7F) > MaxS2) - result = 0xfe; // (todo) not sure what to do - } - } - return(result); +// Returns a 16-bit value: (H = number of records processed, L = BDOS return code) +RUNCPM_DECL uint16 _ReadSeq(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint16 processed = 0; + + // multiRecordCount is always 1 under CP/M 2.2, so this loop runs once and + // behaves exactly like a single-record read; CP/M 3 may transfer several. + if (!_SelectDisk(F->dr)) { + _FCBtoHostname(fcbaddr, &filename[0]); + long saved_dma = dmaAddr; + uint8 toRead = multiRecordCount ? multiRecordCount : 1; + + for (uint8 i = 0; i < toRead; ++i) { + long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) + + (F->ex * BlkEX * BlkSZ) + + (F->cr * BlkSZ); + + dmaAddr = saved_dma + (processed * BlkSZ); + result = _sys_readseq(&filename[0], fpos); + + if (result != 0x00) { + // stop on first non-OK result + break; + } + + // Read succeeded, adjust FCB as for a single record + ++F->cr; + /* CR counts 0..(MaxCR-1) logically (0..127). When we reach MaxCR records + we must roll CR to 0 and advance EX. Use >= to catch MaxCR itself. */ + if (F->cr >= MaxCR) { + F->cr = 0; + ++F->ex; + } + if (F->ex > MaxEX) { + F->ex = 0; + ++F->s2; + } + /* strip possible high-bit and compare S2 low bits against allowed MaxS2 */ + if ((F->s2 & 0x7F) > MaxS2) { + result = 0xfe; + break; + } + + ++processed; + } + + dmaAddr = saved_dma; + } + + // 0xFF is a hardware error. On full success (result 0) H must be 0; only on a + // mid-transfer error does H carry the count of records read before the error. + // Under CP/M 2.2 (single record) H is always 0, so A alone is significant. + if (result == 0xFF) + return (uint16)0x00FF; + if (result == 0x00) + return (uint16)0x0000; + return (uint16)((processed << 8) | (uint16)result); } // Sequential write -RUNCPM_DECL uint8 _WriteSeq(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) + - (F->ex * BlkEX * BlkSZ) + - (F->cr * BlkSZ); - - if (!_SelectDisk(F->dr)) { - if (!RW) { - _FCBtoHostname(fcbaddr, &filename[0]); - result = _sys_writeseq(&filename[0], fpos); - if (!result) { // Write succeeded, adjust FCB - F->s2 &= 0x7F; // reset unmodified flag - ++F->cr; - if (F->cr > MaxCR) { - F->cr = 1; - ++F->ex; - } - if (F->ex > MaxEX) { - F->ex = 0; - ++F->s2; - } - if (F->s2 > MaxS2) - result = 0xfe; // (todo) not sure what to do - } - } else { - _error(errWRITEPROT); - } - } - return(result); +// Returns a 16-bit value: (H = number of records processed, L = BDOS return code) +RUNCPM_DECL uint16 _WriteSeq(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint16 processed = 0; + + // multiRecordCount is always 1 under CP/M 2.2, so this loop runs once and + // behaves exactly like a single-record write; CP/M 3 may transfer several. + if (!_SelectDisk(F->dr)) { + if (!RW) { + _FCBtoHostname(fcbaddr, &filename[0]); + long saved_dma = dmaAddr; + uint8 toWrite = multiRecordCount ? multiRecordCount : 1; + + for (uint8 i = 0; i < toWrite; ++i) { + long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) + + (F->ex * BlkEX * BlkSZ) + + (F->cr * BlkSZ); + + dmaAddr = saved_dma + (processed * BlkSZ); + result = _sys_writeseq(&filename[0], fpos); + + if (result != 0x00) { + break; + } + + /* clear unmodified flag (bit 7) */ + F->s2 &= 0x7F; + + /* advance record index; records are 0..(MaxCR-1) */ + ++F->cr; + + /* if we've rolled past the last record in the extent, + start at record 0 of the next extent */ + if (F->cr >= MaxCR) { + F->cr = 0; + ++F->ex; + /* first record in the new extent */ + F->rc = 1; + } else { + /* still in same extent - increment rc */ + ++F->rc; + } + + /* handle extent overflow -> advance S2/module */ + if (F->ex > MaxEX) { + F->ex = 0; + ++F->s2; + /* first record in the new module/extents group */ + F->rc = 1; + } + + /* check S2 numeric overflow (ignore high-bit flag) */ + if ((F->s2 & 0x7F) > MaxS2) { + result = 0xfe; + break; + } + + ++processed; + } + + dmaAddr = saved_dma; + } else { + _error(errWRITEPROT); + } + } + + // Under CP/M 2.2 (single record) H is always 0, so A alone is significant. + if (result == 0xFF) + return (uint16)0x00FF; + if (result == 0x00) + return (uint16)0x0000; // full success: A = 0, H = 0 (no records "before the error") + // partial transfer error: H = records written before the error, A = error code + return (uint16)((processed << 8) | (uint16)result); } // Random read -RUNCPM_DECL uint8 _ReadRand(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0; - long fpos = record * BlkSZ; - - if (!_SelectDisk(F->dr)) { - _FCBtoHostname(fcbaddr, &filename[0]); - result = _sys_readrand(&filename[0], fpos); - if (result == 0 || result == 1 || result == 4) { - // adjust FCB unless error #6 (seek past 8MB - max CP/M file & disk size) - F->cr = record & 0x7F; - F->ex = (record >> 7) & 0x1f; - if (F->s2 & 0x80) { - F->s2 = ((record >> 12) & MaxS2) | 0x80; - } else { - F->s2 = (record >> 12) & MaxS2; - } - } - } - return(result); +// Returns a 16-bit value: (H = number of records processed, L = BDOS return code) +RUNCPM_DECL uint16 _ReadRand(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint16 processed = 0; + + int32 startRecord = (F->r2 << 16) | (F->r1 << 8) | F->r0; + + // multiRecordCount is always 1 under CP/M 2.2, so this loop runs once and + // behaves exactly like a single-record read; CP/M 3 may transfer several. + if (!_SelectDisk(F->dr)) { + _FCBtoHostname(fcbaddr, &filename[0]); + long saved_dma = dmaAddr; + uint8 toRead = multiRecordCount ? multiRecordCount : 1; + + for (uint8 i = 0; i < toRead; ++i) { + int32 record = startRecord + processed; + long fpos = record * BlkSZ; + dmaAddr = saved_dma + (processed * BlkSZ); + result = _sys_readrand(&filename[0], fpos); + + // adjust FCB unless error #6 (seek past 8MB - max CP/M file & disk size) + if (!(result == 0 || result == 1 || result == 4)) { + break; + } + + // adjust FCB to the last record read + F->cr = record & (MaxCR - 1); + F->ex = (record >> 7) & MaxEX; + /* preserve 0x80 (unmodified) bit in s2 if previously present */ + F->s2 = ((record >> 12) & MaxS2) | (F->s2 & 0x80); + + ++processed; + } + + dmaAddr = saved_dma; + } + + if (result == 0xFF) + return (uint16)0x00FF; + // 0/1/4 are normal outcomes (data read, or reading unwritten data/extent); these + // return with H = 0, exactly as CP/M 2.2 does. Only a genuine mid-transfer error + // leaves H carrying the count of records read before the error. + if (result == 0x00 || result == 0x01 || result == 0x04) + return (uint16)(uint8)result; + return (uint16)((processed << 8) | (uint16)result); } // Random write -RUNCPM_DECL uint8 _WriteRand(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - - int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0; - long fpos = record * BlkSZ; - - if (!_SelectDisk(F->dr)) { - if (!RW) { - _FCBtoHostname(fcbaddr, &filename[0]); - result = _sys_writerand(&filename[0], fpos); - if (!result) { // Write succeeded, adjust FCB - F->cr = record & 0x7F; - F->ex = (record >> 7) & 0x1f; - F->s2 = (record >> 12) & MaxS2; // resets unmodified flag - } - } else { - _error(errWRITEPROT); - } - } - return(result); +// Returns a 16-bit value: (H = number of records processed, L = BDOS return code) +RUNCPM_DECL uint16 _WriteRand(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + uint16 processed = 0; + + int32 startRecord = (F->r2 << 16) | (F->r1 << 8) | F->r0; + + // multiRecordCount is always 1 under CP/M 2.2, so this loop runs once and + // behaves exactly like a single-record write; CP/M 3 may transfer several. + if (!_SelectDisk(F->dr)) { + if (!RW) { + _FCBtoHostname(fcbaddr, &filename[0]); + long saved_dma = dmaAddr; + uint8 toWrite = multiRecordCount ? multiRecordCount : 1; + + for (uint8 i = 0; i < toWrite; ++i) { + int32 record = startRecord + processed; + long fpos = record * BlkSZ; + dmaAddr = saved_dma + (processed * BlkSZ); + result = _sys_writerand(&filename[0], fpos); + + if (result != 0x00) { + break; + } + + F->cr = record & (MaxCR - 1); + F->ex = (record >> 7) & MaxEX; + F->s2 = (record >> 12) & MaxS2; // resets unmodified flag + + ++processed; + } + + dmaAddr = saved_dma; + } else { + _error(errWRITEPROT); + } + } + + // Under CP/M 2.2 (single record) H is always 0, so A alone is significant. + if (result == 0xFF) + return (uint16)0x00FF; + if (result == 0x00) + return (uint16)0x0000; // full success: A = 0, H = 0 + // partial transfer error: H = records written before the error, A = error code + return (uint16)((processed << 8) | (uint16)result); } // Returns the size of a CP/M file RUNCPM_DECL uint8 _GetFileSize(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0xff; - int32 count = _FileSize(DE) >> 7; - - if (count != -1) { - F->r0 = count & 0xff; - F->r1 = (count >> 8) & 0xff; - F->r2 = (count >> 16) & 0xff; - } - return(result); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + int32 count = _FileSize(DE) >> 7; + + if (count != -1) { + F->r0 = count & 0xff; + F->r1 = (count >> 8) & 0xff; + F->r2 = (count >> 16) & 0xff; + result = 0x00; + } + return (result); } +#ifdef CPM3 +// Truncates a file to the random record count held in the FCB (record * 128 +// bytes). Returns 0 on success, 0xFF on error. +RUNCPM_DECL uint8 _TruncateFile(uint16 fcbaddr) { + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0xff; + + if (!_SelectDisk(F->dr)) { + if (!RW) { + _FCBtoHostname(fcbaddr, &filename[0]); + long records = F->r0 | (F->r1 << 8) | ((long)F->r2 << 16); + if (!_sys_truncate(&filename[0], records * 128)) + result = 0x00; + } + } + return (result); +} +#endif + // Set the next random record RUNCPM_DECL uint8 _SetRandom(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - uint8 result = 0x00; + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + uint8 result = 0x00; - int32 count = F->cr & 0x7f; - count += (F->ex & 0x1f) << 7; - count += (F->s2 & MaxS2) << 12; + int32 count = F->cr & (MaxCR - 1); + count += (F->ex & MaxEX) << 7; + count += (F->s2 & MaxS2) << 12; - F->r0 = count & 0xff; - F->r1 = (count >> 8) & 0xff; - F->r2 = (count >> 16) & 0xff; + F->r0 = count & 0xff; + F->r1 = (count >> 8) & 0xff; + F->r2 = (count >> 16) & 0xff; - return(result); + return (result); } // Sets the current user area RUNCPM_DECL void _SetUser(uint8 user) { - userCode = user & 0x1f; // BDOS unoficially allows user areas 0-31 - // this may create folders from G-V if this function is called from an user program - // It is an unwanted behavior, but kept as BDOS does it + userCode = user & 0x1f; // BDOS unoficially allows user areas 0-31 + // this may create folders from G-V if this function is called from an user program + // It is an unwanted behavior, but kept as BDOS does it #ifdef NOHIGHUSER - if(userCode < 16) + if (userCode < 16) #endif - _MakeUserDir(); // Creates the user dir (0-F[G-V]) if needed + _MakeUserDir(); // Creates the user dir (0-F[G-V]) if needed } // Creates a disk directory folder RUNCPM_DECL uint8 _MakeDisk(uint16 fcbaddr) { - CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr); - return(_sys_makedisk(F->dr)); + CPM_FCB *F = (CPM_FCB *)_RamSysAddr(fcbaddr); + return (_sys_makedisk(F->dr)); } // Checks if there's a temp submit file present RUNCPM_DECL uint8 _CheckSUB(void) { - uint8 result; - uint8 oCode = userCode; // Saves the current user code (original BDOS does not do this) - _HostnameToFCB(tmpFCB, (uint8*)"$???????.???"); // The original BDOS in fact only looks for a file which start with $ + uint8 result; + uint8 oCode = userCode; // Saves the current user code (original BDOS does not do this) + _HostnameToFCB(tmpFCB, (uint8 *)"$???????.???"); // The original BDOS in fact only looks for a file which start with $ #ifdef BATCHA - _RamWrite(tmpFCB, 1); // Forces it to be checked on drive A: + _RamWrite(tmpFCB, 1); // Forces it to be checked on drive A: #endif #ifdef BATCH0 - userCode = 0; // Forces it to be checked on user 0 + userCode = 0; // Forces it to be checked on user 0 #endif - result = (_SearchFirst(tmpFCB, FALSE) == 0x00) ? 0xff : 0x00; - userCode = oCode; // Restores the current user code - return(result); + result = (_SearchFirst(tmpFCB, FALSE) == 0x00) ? 0xff : 0x00; + userCode = oCode; // Restores the current user code + return (result); } -#ifdef HASLUA -// Executes a Lua script -RUNCPM_DECL uint8 _RunLua(uint16 fcbaddr) { - uint8 luascript[17]; - uint8 result = 0xff; - - if (_FCBtoHostname(fcbaddr, &luascript[0])) { // Script name must be unique - if (!_SearchFirst(fcbaddr, FALSE)) { // and must exist - result = _RunLuaScript((char*)&luascript[0]); - } - } - - return(result); -} #endif - -#endif /* CPM_DISK_H */ diff --git a/lib/runcpm/globals.h b/lib/runcpm/globals.h index ae04225d8..8d3d9781a 100644 --- a/lib/runcpm/globals.h +++ b/lib/runcpm/globals.h @@ -3,235 +3,379 @@ /* Some definitions needed globally */ #ifdef __MINGW32__ -#include + #include #endif +/* Definition of which CPU to use: cpu.h, cpu2.h, cpu3.h, cpu4.h */ +#ifndef CPU + #define CPU "cpu1.h" +#endif + +/* Definition of CP/M version */ +// #define CPM3 // If defined, CP/M 3.0 will be emulated, otherwise CP/M 2.2 will be emulated + +/* CPU speed for throttling (0 = disabled/fastest, 500 = slow, smaller number = slower) */ +#define CPU_SPEED 0 // Defines the number of instructions to execute before checking the time + // and possibly delaying execution to throttle CPU speed + /* Definition for enabling incrementing the R register for each M1 cycle */ -#define DO_INCR +#define DO_INCR // Loses a bit of performance in favor or realistic R register emulation /* Definitions for enabling PUN: and LST: devices */ -//#define USE_PUN // The pun.txt and lst.txt files will appear on drive A: user 0 -//#define USE_LST +/* FujiNet: disabled - they need main.c globals FujiNet doesn't compile. */ +// #define USE_PUN // The pun.txt and lst.txt files will appear on drive A: user 0 +// #define USE_LST /* Definitions for file/console based debugging */ -//#define DEBUG // Enables the internal debugger (enabled by default on vstudio debug builds) -//#define iDEBUG // Enables instruction logging onto iDebug.log (for development debug only) -//#define DEBUGLOG // Writes extensive call trace information to RunCPM.log -//#define CONSOLELOG // Writes debug information to console instead of file -//#define LOGBIOS_NOT 01 // If defined will not log this BIOS function number -//#define LOGBIOS_ONLY 02 // If defines will log only this BIOS function number -//#define LOGBDOS_NOT 06 // If defined will not log this BDOS function number -//#define LOGBDOS_ONLY 22 // If defines will log only this BDOS function number +/* FujiNet: key the Z80 debugger off RUNCPMDEBUG, not the ambient -DDEBUG that + PlatformIO debug builds inject (it overflows ESP32 DRAM and leaks the banner). */ +#define RUNCPMDEBUG false +// #define DEBUG // Enables the internal debugger (enabled by default on visual studio debug builds) +// #define DEBUGONHALT // Enables the internal debugger when the CPU halts +// #define iDEBUG // Enables instruction logging onto iDebug.log (for development debug only) +// #define DEBUGLOG // Writes extensive call trace information to RunCPM.log +#define DEBUGKEY 4 // Key to trigger the debugger. 4 = ^D + +// #define CONSOLELOG // Writes debug information to console instead of file +// #define LOGBIOS_NOT 01 // If defined will not log this BIOS function number +// #define LOGBIOS_ONLY 02 // If defined will log only this BIOS function number +// #define LOGBDOS_NOT 06 // If defined will not log this BDOS function number +// #define LOGBDOS_ONLY 22 // If defined will log only this BDOS function number #define LogName "RunCPM.log" /* RunCPM version for the greeting header */ -#define VERSION "5.8" -#define VersionBCD 0x58 - -/* Definition of which CCP to use (must define only one) */ -#define CCP_INTERNAL // If this is defined, an internal CCP will emulated -//#define CCP_DR -//#define CCP_CCPZ -//#define CCP_ZCPR2 -//#define CCP_ZCPR3 -//#define CCP_Z80 +#define VERSION "6.9" +#define VersionBCD 0x69 + +/* Definition of which BDOS to use (not for Internal CCP, set to 60K CCPs by default) */ +// #define ABDOS // Based on work by Pavel Zampach (https://www.chstercius.cz/runcpm/) +// This requires ABDOS.SYS to be present on A: user 0 - see 'abdos' folder under 'tools' + +#ifdef _WIN32 // Windows needs a default CCP defined to compile as there's no Makefile + #define CCP_INTERNAL +// #define CCP_DR +// #define CCP_CCPZ +// #define CCP_ZCPR2 +// #define CCP_ZCPR3 +// #define CCP_Z80 +#endif /* Definition of the CCP memory information */ // #ifdef CCP_INTERNAL -#define CCPname "INTERNAL v2.6" // Will use the CCP from ccp.h -#define VersionCCP 0x26 // 0x10 and above reserved for Internal CCP -#define BatchFCB (tmpFCB + 36) -#define CCPaddr (BDOSjmppage-0x0800) + #define CCPname "CCP-INTERNAL v3.3" // Will use the CCP from ccp.h + #define VersionCCP 0x33 // 0x10 and above reserved for Internal CCP + #define BatchFCB (tmpFCB + 48) + #define CCPaddr BDOSjmppage // Internal CCP has size 0 #endif // #ifdef CCP_DR -#define CCPname "CCP-DR." STR(TPASIZE) "K" -#define VersionCCP 0x00 // Version to be used by INFO.COM -#define BatchFCB (CCPaddr + 0x7AC) // Position of the $$$.SUB fcb on this CCP -#define CCPaddr (BDOSjmppage-0x0800) // CCP memory address + #define CCPname "CCP-DR." STR(TPASIZE) "K" + #define VersionCCP 0x00 // Version to be used by INFO.COM + #define BatchFCB (CCPaddr + 0x7AC) // Position of the $$$.SUB fcb on this CCP + #define CCPaddr (BDOSjmppage - 0x0800) // CCP memory address #endif // #ifdef CCP_CCPZ -#define CCPname "CCP-CCPZ." STR(TPASIZE) "K" -#define VersionCCP 0x01 -#define BatchFCB (CCPaddr + 0x7A) // Position of the $$$.SUB fcb on this CCP -#define CCPaddr (BDOSjmppage-0x0800) + #define CCPname "CCP-CCPZ." STR(TPASIZE) "K" + #define VersionCCP 0x01 + #define BatchFCB (CCPaddr + 0x7A) // Position of the $$$.SUB fcb on this CCP + #define CCPaddr (BDOSjmppage - 0x0800) #endif // #ifdef CCP_ZCPR2 -#define CCPname "CCP-ZCP2." STR(TPASIZE) "K" -#define VersionCCP 0x02 -#define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP -#define CCPaddr (BDOSjmppage-0x0800) + #define CCPname "CCP-ZCP2." STR(TPASIZE) "K" + #define VersionCCP 0x02 + #define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP + #define CCPaddr (BDOSjmppage - 0x0800) #endif // #ifdef CCP_ZCPR3 -#define CCPname "CCP-ZCP3." STR(TPASIZE) "K" -#define VersionCCP 0x03 -#define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP -#define CCPaddr (BDOSjmppage-0x1000) + #define CCPname "CCP-ZCP3." STR(TPASIZE) "K" + #define VersionCCP 0x03 + #define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP + #define CCPaddr (BDOSjmppage - 0x1000) #endif // #ifdef CCP_Z80 -#define CCPname "CCP-Z80." STR(TPASIZE) "K" -#define VersionCCP 0x04 -#define BatchFCB (CCPaddr + 0x79E) // Position of the $$$.SUB fcb on this CCP -#define CCPaddr (BDOSjmppage-0x0800) + #define CCPname "CCP-Z80." STR(TPASIZE) "K" + #define VersionCCP 0x04 + #define BatchFCB (CCPaddr + 0x79E) // Position of the $$$.SUB fcb on this CCP + #define CCPaddr (BDOSjmppage - 0x0800) #endif // #ifndef CCPname -#error No CCP defined + #error No CCP defined, use 'make CCP= build' to define one of the available CCPs #endif // +#ifdef CCP_INTERNAL + #ifdef ABDOS + #error Internal CCP does not support ABDOS + #endif +#endif + #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) -#define CCPHEAD "\r\nRunCPM Version " VERSION " (CP/M 2.2 " STR(TPASIZE) "K)\r\n" +#if RUNCPMDEBUG + #define DBG " - DEBUG" +#else + #define DBG +#endif +#ifdef ABDOS + #define ABD " (ABDOS)" +#else + #define ABD +#endif +/* FujiNet: renamed `CPM` -> `CPM_VERSTR` to avoid clashing with FujiNet's `CPM` + enumerator (lib/bus/iwm/iwm.h) pulled into the shared core TU. Banner only. */ +#ifdef CPM3 + #define CPM_VERSTR "CP/M 3" +#else + #define CPM_VERSTR "CP/M 2.2" +#endif +/* FujiNet: rebranded banner. */ +#define CCPHEAD "\r\nFujiNet " CPM_VERSTR " - RunCPM " VERSION DBG ABD "\r\n" -#define NOSLASH // Will translate '/' to '_' on filenames to prevent directory errors +#define NOSLASH // Will translate '/' to '_' on filenames to prevent directory errors -//#define HASLUA // Will enable Lua scripting (BDOS call 254) - // Should be passed externally per-platform with -DHASLUA +// #define STREAMIO // Will enable command line flags to read +// console input from file and to log console output to file +// Should be passed externally per-platform with -DSTREAMIO -//#define PROFILE // For measuring time taken to run a CP/M command - // This should be enabled only for debugging purposes when trying to improve emulation speed +// #define PROFILE // For measuring time taken to run a CP/M command +// This should be enabled only for debugging purposes when trying to improve emulation speed -#define NOHIGHUSER // Prevents the creation of user folders above 'F' (15) by programs - // Original CP/M BDOS allows it, but I prefer to keep the folders clean +#define NOHIGHUSER // Prevents the creation of user folders above 'F' (15) by programs + // Original CP/M BDOS allows it, but I prefer to keep the folders clean /* Definition for CP/M 2.2 user number support */ -#define BATCHA // If this is defined, the $$$.SUB will be looked for on drive A: -//#define BATCH0 // If this is defined, the $$$.SUB will be looked for on user area 0 - // The default behavior of DRI's CP/M 2.2 was to have $$$.SUB created on the current drive/user while looking for it - // on drive A: current user, which made it complicated to run SUBMITs when not logged to drive A: user 0 +#define BATCHA // If this is defined, the $$$.SUB file will be looked for on drive A: +// #define BATCH0 // If this is defined, the $$$.SUB file will be looked for on user area 0 +// The default behavior of DRI's CP/M 2.2 was to have $$$.SUB created on the current drive/user while looking for it +// on drive A: current user, which made it complicated to run SUBMITs when not logged to drive A: user 0 /* Some environment and type definitions */ #ifndef TRUE -#define FALSE 0 -#define TRUE 1 + #define FALSE 0 + #define TRUE 1 #endif -typedef signed char int8; -typedef signed short int16; -typedef signed int int32; -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -#define LOW_DIGIT(x) ((x) & 0xf) -#define HIGH_DIGIT(x) (((x) >> 4) & 0xf) -#define LOW_REGISTER(x) ((x) & 0xff) +/* Define Status types */ +#define STATUS_RUNNING 0 +#define STATUS_EXIT 1 +#define STATUS_RESTART 2 +#define STATUS_RETURN 3 + +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef signed long long int64; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +#define LOW_DIGIT(x) ((x) & 0xf) +#define HIGH_DIGIT(x) (((x) >> 4) & 0xf) +#define LOW_REGISTER(x) ((x) & 0xff) #define HIGH_REGISTER(x) (((x) >> 8) & 0xff) -#define SET_LOW_REGISTER(x, v) x = (((x) & 0xff00) | ((v) & 0xff)) +#define SET_LOW_REGISTER(x, v) x = (((x) & 0xff00) | ((v) & 0xff)) #define SET_HIGH_REGISTER(x, v) x = (((x) & 0xff) | (((v) & 0xff) << 8)) -#define WORD16(x) ((x) & 0xffff) +#define WORD16(x) ((x) & 0xffff) /* CP/M Page 0 definitions */ #define IOByte 0x03 #define DSKByte 0x04 /* CP/M disk definitions */ -#define BlkSZ 128 // CP/M block size -#define BlkEX 128 // Number of blocks on an extension +#define BlkSZ 128 // CP/M block size +#define BlkEX 128 // Number of blocks on an extension #define ExtSZ (BlkSZ * BlkEX) -#define BlkS2 4096 // Number of blocks on a S2 (module) -#define MaxEX 31 // Maximum value the EX field can take -#define MaxS2 15 // Maximum value the S2 (modules) field can take - Can be set to 63 to emulate CP/M Plus -#define MaxCR 128 // Maximum value the CR field can take -#define MaxRC 128 // Maximum value the RC field can take +#define BlkS2 4096 // Number of blocks on a S2 (module) +#define MaxEX 31 // Maximum value the EX field can take +#define MaxS2 15 // Maximum value the S2 (modules) field can take - Can be set to 63 to emulate CP/M Plus +#define MaxCR 128 // Maximum value the CR field can take +#define MaxRC 128 // Maximum value the RC field can take /* CP/M memory definitions */ -#define RAM_FAST // If this is defined, all RAM function calls become direct access (see below) - // This saves about 2K on the Arduino code and should bring speed improvements - -#define TPASIZE 60 // Can be 60 for CP/M 2.2 compatibility or more, up to 64 for extra memory - // Values other than 60 or 64 would require rebuilding the CCP - // For TPASIZE<60 CCP ORG = (SIZEK * 1024) - 0x0C00 +#define TPASIZE 60 // Can be 60 for batter CP/M 2.2 compatibility or 64 for extra memory + // Values other than 60 or 64 would require rebuilding the CCP + // For TPASIZE<60 CCP ORG = (SIZEK * 1024) - 0x0C00 -#define MEMSIZE 64 * 1024 // RAM(plus ROM) needs to be 64K to avoid compatibility issues +#ifndef BANKS + /* FujiNet: 1 bank = 64K; the 6.9 default of 8 (512K) won't fit ESP32. */ + #define BANKS 1 // Number of memory banks available (defined in Makefile per platform) +#endif +static uint8 curBank = 0; // Number of the current RAM bank in use (0-based, 0 to BANKS-1, as in CP/M 3) +static uint8 isXmove = FALSE; // Used by BIOS +static uint8 srcBank = 0; // Source bank for memory MOVE +static uint8 dstBank = 0; // Destination bank for memory MOVE +static uint8 ioBank = 0; // Destination bank for sector IO +static uint32 curBankBase = 0; +static uint32 srcBankBase = 0; +static uint32 dstBankBase = 0; +static uint32 ioBankBase = 0; + +#define PAGESIZE (64 * 1024) // RAM(plus ROM) needs to be 64K to avoid compatibility issues +#define MEMSIZE (PAGESIZE * BANKS) // Total RAM size + +#if BANKS == 1 + #define RAM_FAST // If this is defined, all RAM function calls become direct access (see below) + // This saves about 2K on the Arduino code and should bring speed improvements + // This feature is only available if there is only one bank of RAM +#endif -#ifdef RAM_FAST // Makes all function calls to memory access into direct RAM access (less calls / less code) - static uint8 *RAM; - #define _RamSysAddr(a) &RAM[a] - #define _RamRead(a) RAM[a] - #define _RamRead16(a) ((RAM[(a & 0xffff) + 1] << 8) | RAM[a & 0xffff]) - #define _RamWrite(a, v) RAM[a] = v - #define _RamWrite16(a, v) RAM[a] = (v) & 0xff; RAM[a + 1] = (v) >> 8 +#ifdef RAM_FAST // Makes all function calls to memory access into direct RAM access (less calls / less code) +/* FujiNet: RAM is a heap pointer (consumer does malloc/free) to keep the 64K + buffer off the ESP32 static-BSS budget. */ +static uint8 *RAM; + #define _RamSysAddr(a) &RAM[a] + #define _RamRead(a) RAM[a] + #define _RamRead16(a) ((RAM[((a) & 0xffff) + 1] << 8) | RAM[(a) & 0xffff]) + #define _RamWrite(a, v) RAM[a] = v + #define _RamWrite16(a, v) \ + RAM[a] = (v) & 0xff; \ + RAM[(a) + 1] = (v) >> 8 #endif -// Size of the allocated pages (Minimum size = 1 page = 256 bytes) +// If this is defined, the emulator will use interrupt-based BIOS/BDOS calls instead of +// the legacy IN/OUT method for handing control to the emulated BIOS/BDOS routines +/* FujiNet: keep the legacy IN/OUT port-0xFF handoff (driven by the + _HardwareIn/_HardwareOut in the abstraction layer). */ +// #define INT_HANDOFF -// BIOS Pages (always on the top of memory) -#define BIOSpage (MEMSIZE - 256) -#define BIOSjmppage (BIOSpage - 256) +// Size of the allocated pages (Minimum size = 1 page = 256 bytes) -// BDOS Pages (depend on TPASIZE) -#define BDOSpage (TPASIZE * 1024) - 768 -#define BDOSjmppage (BDOSpage - 256) +// BIOS Pages (512 bytes from the top of memory) +#define BIOSjmppage (PAGESIZE - 512) +#define BIOSpage (BIOSjmppage + 256) + +// BDOS Pages (depends on TPASIZE for external CCPs) +#if defined CCP_INTERNAL + #define BDOSjmppage (BIOSjmppage - 256) + #define BDOSpage (BDOSjmppage + 16) +#else + #define BDOSjmppage (TPASIZE * 1024) - 1024 + #define BDOSpage (BDOSjmppage + 256) +#endif -#define DPBaddr (BIOSpage + 64) // Address of the Disk Parameter Block (Hardcoded in BIOS) -#define DPHaddr (DPBaddr + 15) // Address of the Disk Parameter Header +#define DPBaddr (BIOSpage + 128) // Address of the Disk Parameter Block (Hardcoded in BIOS) +#define DPHaddr (DPBaddr + 15) // Address of the Disk Parameter Header -#define SCBaddr (BDOSpage + 16) // Address of the System Control Block -#define tmpFCB (BDOSpage + 64) // Address of the temporary FCB +#ifdef ABDOS + #define SCBaddr (BDOSpage + 480) // Address of the System Control Block + #define tmpFCB (BDOSpage + 444) // Address of the temporary FCB +#else + #define SCBaddr (BDOSpage + 3) // Address of the System Control Block + #define tmpFCB (BDOSpage + 16) // Address of the temporary FCB +#endif /* Definition of global variables */ -static uint8 filename[17]; // Current filename in host filesystem format -static uint8 newname[17]; // New filename in host filesystem format -static uint8 fcbname[13]; // Current filename in CP/M format -static uint8 pattern[13]; // File matching pattern in CP/M format -static uint16 dmaAddr = 0x0080; // Current dmaAddr -static uint8 oDrive = 0; // Old selected drive -static uint8 cDrive = 0; // Currently selected drive -static uint8 userCode = 0; // Current user code -static uint16 roVector = 0; -static uint16 loginVector = 0; -static uint8 allUsers = FALSE; // true when dr is '?' in BDOS search first -static uint8 allExtents = FALSE; // true when ex is '?' in BDOS search first -static uint8 currFindUser = 0; // user number of current directory in BDOS search first on all user numbers -static uint8 blockShift; // disk allocation block shift -static uint8 blockMask; // disk allocation block mask -static uint8 extentMask; // disk extent mask -static uint16 firstBlockAfterDir; // first allocation block after directory -static uint16 numAllocBlocks; // # of allocation blocks on disk -static uint8 extentsPerDirEntry; // # of logical (16K) extents in a directory entry -#define logicalExtentBytes (16*1024UL) -static uint16 physicalExtentBytes;// # bytes described by 1 directory entry - -#define tohex(x) ((x) < 10 ? (x) + 48 : (x) + 87) - -/* The engine is compiled exactly once (runcpm_core.cpp) with normal external - * linkage, so RunCPM symbols are plain (non-static). */ +static uint8 filename[17]; // Current filename in host filesystem format +static uint8 newname[17]; // New filename in host filesystem format +static uint8 fcbname[13]; // Current filename in CP/M format +static uint8 pattern[13]; // File matching pattern in CP/M format +static uint16 dmaAddr = 0x0080; // Current dmaAddr +static uint8 oDrive = 0; // Old selected drive +static uint8 cDrive = 0; // Currently selected drive +static uint8 userCode = 0; // Current user code +static uint16 roVector = 0; +static uint16 loginVector = 0; +static uint8 allUsers = FALSE; // true when dr is '?' in BDOS search first +static uint8 allExtents = FALSE; // true when ex is '?' in BDOS search first +static uint8 currFindUser = 0; // user number of current directory in BDOS search first on all user numbers +static uint8 blockShift; // disk allocation block shift +static uint8 blockMask; // disk allocation block mask +static uint8 extentMask; // disk extent mask +static uint16 firstBlockAfterDir; // first allocation block after directory +static uint16 numAllocBlocks; // # of allocation blocks on disk +static uint8 extentsPerDirEntry; // # of logical (16K) extents in a directory entry +#define logicalExtentBytes (16 * 1024UL) +static uint16 physicalExtentBytes; // # bytes described by 1 directory entry +static uint16 cpuDelayInstructions = CPU_SPEED; + +// Output delimiter used by BDOS C_WRITESTR (default '$') +static uint8 outputDelimiter = 0x24; +// Number of 128-byte records to transfer at once (set by BDOS F_MULTISEC). +// Only CP/M 3 ever changes this; under CP/M 2.2 it stays 1, so the shared +// read/write loops in disk.h behave exactly like single-record transfers. +static uint8 multiRecordCount = 1; /* default = 1 record */ + +#define tohex(x) ((x) < 10 ? (x) + 48 : (x) + 87) + +/* definition of an autoexec functionality */ +static uint8 firstBoot = TRUE; // True if this is the first boot +#define AUTOEXEC "AUTOEXEC.TXT" // Name of the autoexec file +#define BOOTONLY FALSE // If TRUE, the autoexec file will only be loaded on the first boot + +#ifdef CPM3 +/* BDOS function 47 (Chain To Program) state: the command line a program + chained to, to be run by the CCP after the next warm boot */ +static uint8 chainCmd[128]; +static uint8 chainLoad = 0; +#endif + +static uint32 timer; + +#ifdef STREAMIO + #include +static FILE *streamInputFile = NULL; +static FILE *streamOutputFile = NULL; +static uint8 streamInputActive = FALSE; +static uint8 consoleOutputActive = TRUE; +#endif + +/* FujiNet: RUNCPM_DECL gives RunCPM's file-scope symbols internal linkage under + * RUNCPM_STATIC_IMPL, so a header-only consumer TU can coexist with the shared + * core in one binary. The core headers tag definitions with RUNCPM_DECL. */ +#ifdef RUNCPM_STATIC_IMPL +#define RUNCPM_DECL static +#else #define RUNCPM_DECL +#endif -/* Forward declarations to prevent precedence compilation errors inside the - * RunCPM header chain. */ +/* Definition of externs/forward-declarations to prevent precedence + * compilation errors inside the RunCPM header chain. */ +#ifdef RUNCPM_STATIC_IMPL +/* Forward-decls must match the static linkage of the later definitions. */ +static void _Bdos(void); +static void _Bios(void); +static void _HostnameToFCB(uint16 fcbaddr, uint8 *filename); +static void _HostnameToFCBname(uint8 *from, uint8 *to); +static void _mockupDirEntry(uint8 mode); +static uint8 match(uint8 *fcbname, uint8 *pattern); +static void _puts(const char *str); +#ifndef RAM_FAST +static uint8 *_RamSysAddr(uint16 address); +static void _RamWrite(uint16 address, uint8 value); +#endif +#else /* !RUNCPM_STATIC_IMPL */ #ifdef __cplusplus // If building on Arduino -extern "C" -{ +extern "C" { #endif #ifndef RAM_FAST - extern uint8* _RamSysAddr(uint16 address); - extern void _RamWrite(uint16 address, uint8 value); +extern uint8 *_RamSysAddr(uint16 address); +extern void _RamWrite(uint16 address, uint8 value); #endif - extern void _Bdos(void); - extern void _Bios(void); +extern void _Bdos(void); +extern void _Bios(void); - extern void _HostnameToFCB(uint16 fcbaddr, uint8* filename); - extern void _HostnameToFCBname(uint8* from, uint8* to); - extern void _mockupDirEntry(void); - extern uint8 match(uint8* fcbname, uint8* pattern); +extern void _HostnameToFCB(uint16 fcbaddr, uint8 *filename); +extern void _HostnameToFCBname(uint8 *from, uint8 *to); +extern void _mockupDirEntry(uint8 mode); +extern uint8 match(uint8 *fcbname, uint8 *pattern); - extern void _puts(const char* str); +extern void _puts(const char *str); #ifdef __cplusplus // If building on Arduino } #endif +#endif /* RUNCPM_STATIC_IMPL */ #endif diff --git a/lib/runcpm/host.h b/lib/runcpm/host.h index 3874c6148..9b484b13b 100644 --- a/lib/runcpm/host.h +++ b/lib/runcpm/host.h @@ -6,7 +6,7 @@ #endif RUNCPM_DECL uint8 hostbdos(uint16 dmaaddr) { - return(0x00); + return (0x00); } #endif \ No newline at end of file diff --git a/lib/runcpm/ram.h b/lib/runcpm/ram.h index 2452923dd..c121f7370 100644 --- a/lib/runcpm/ram.h +++ b/lib/runcpm/ram.h @@ -4,28 +4,46 @@ /* see main.c for definition */ #ifndef RAM_FAST -static uint8 RAM[MEMSIZE]; // Definition of the emulated RAM - -uint8* _RamSysAddr(uint16 address) { - return(&RAM[address]); +static uint8 RAM[MEMSIZE]; // Definition of the emulated RAM + +uint8 *_RamSysAddr(uint16 address) { + if (address < CCPaddr) { + return (&RAM[curBankBase + address]); + } else { + return (&RAM[address]); + } } uint8 _RamRead(uint16 address) { - return(RAM[address]); + if (address < CCPaddr) { + return (RAM[curBankBase + address]); + } else { + return (RAM[address]); + } } uint16 _RamRead16(uint16 address) { - return(RAM[address] + (RAM[address + 1] << 8)); + if (address < CCPaddr) { + uint32 bankAddress = curBankBase + address; + + return (RAM[bankAddress] + (RAM[bankAddress + 1] << 8)); + } else { + return (RAM[address] + (RAM[address + 1] << 8)); + } } void _RamWrite(uint16 address, uint8 value) { - RAM[address] = value; + if (address < CCPaddr) { + RAM[curBankBase + address] = value; + } else { + RAM[address] = value; + } } void _RamWrite16(uint16 address, uint16 value) { - // Z80 is a "little indian" (8 bit era joke) - _RamWrite(address, value & 0xff); - _RamWrite(address + 1, (value >> 8) & 0xff); + // Z80 is a "little indian" (8 bit era joke) + _RamWrite(address, value & 0xff); + _RamWrite(address + 1, (value >> 8) & 0xff); } #endif diff --git a/lib/runcpm/resource.h b/lib/runcpm/resource.h index 2796f2e76..4cf9ea280 100644 --- a/lib/runcpm/resource.h +++ b/lib/runcpm/resource.h @@ -2,15 +2,15 @@ // Microsoft Visual C++ generated include file. // Used by RunCPM.rc // -#define IDI_ICON1 101 +#define IDI_ICON1 101 // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif + #ifndef APSTUDIO_READONLY_SYMBOLS + #define _APS_NEXT_RESOURCE_VALUE 102 + #define _APS_NEXT_COMMAND_VALUE 40001 + #define _APS_NEXT_CONTROL_VALUE 1001 + #define _APS_NEXT_SYMED_VALUE 101 + #endif #endif diff --git a/lib/runcpm/runcpm_core.cpp b/lib/runcpm/runcpm_core.cpp index c8accec10..865c346a2 100644 --- a/lib/runcpm/runcpm_core.cpp +++ b/lib/runcpm/runcpm_core.cpp @@ -1,101 +1,104 @@ -/** - * runcpm_core.cpp - the one and only build of the RunCPM engine. - * - * Historically every transport (the SIO/Atari bus device, the IWM/Apple and - * DriveWire/CoCo background tasks, the RS232 bus device and the N:CPM:// - * network adapter) #included the whole header-only engine into its own - * translation unit. That meant several independent 64K RAM images and several - * copies of the BDOS/BIOS/CCP code in the firmware, kept apart only by the - * RUNCPM_STATIC_IMPL "make every symbol static" hack. - * - * This file compiles the engine exactly once, with normal (external) linkage, - * for every platform. Transports no longer include the engine; they call - * runcpm_session_run() with a small set of console callbacks (see - * runcpm_session.h) and the engine talks to them through g_runcpm_console. +/* + * The single shared RunCPM core: the only TU that compiles the Z80/CP/M engine + * (built without RUNCPM_STATIC_IMPL, so all state/tables/functions are external + * and defined here once). Transports include runcpm_session.h only and drive + * it as console back-ends. One session at a time, enforced by g_busy. */ +#define CCP_INTERNAL + +/* Use cpu.h's precomputed const Z80 tables (placed in flash) instead of + building them into ~11 KB of DRAM .bss at boot. Must precede cpu.h. */ +#define preTables + #include -#include -#include +#include +#include +#if !defined(_WIN32) && !defined(ARDUINO) +#include // cpu.h's Z80 throttle calls usleep() on POSIX/ESP-IDF +#endif -#define CCP_INTERNAL +#include "fnSystem.h" +#include "fnFS.h" +#include "fnFsSD.h" #include "runcpm_session.h" +/* RunCPM header chain (external linkage). abstraction_fujinet_core.h supplies + the disk/SD family and the console shims that dispatch through + g_runcpm_console. */ #include "globals.h" -#include "abstraction_fujinet.h" // filesystem + console-dispatch glue -#include "ram.h" // RAM access -#include "console.h" // _putcon/_puts built on the console callbacks -#include "cpu.h" // Z80 core + Status/Debug/Break/Step -#include "disk.h" // CP/M disk abstraction -#include "host.h" // custom host-specific BDOS call -#include "cpm.h" // CP/M structures and BDOS/BIOS -#include "ccp.h" // internal CCP - -// The live console endpoint. Declared extern in abstraction_fujinet.h and read -// by the _kbhit/_getch/_putch/_clrscr glue there. -runcpm_console_ops g_runcpm_console{}; - -// The engine owns a single 64K RAM image and is not re-entrant, so only one -// session may run at a time. g_busy guards that; g_exit lets another task ask -// the running session to stop. -static std::atomic g_busy{false}; -static volatile bool g_exit = false; +#include "abstraction_fujinet_core.h" +#include "ram.h" +#include "console.h" +#include "cpu.h" +#include "disk.h" +#include "host.h" +#include "cpm.h" +#include "ccp.h" -bool runcpm_session_active(void) -{ - return g_busy.load(); -} +/* Active transport's console back-end; the shims in abstraction_fujinet_core.h + route _getch/_putch/etc. through it. */ +extern "C" runcpm_console_ops g_runcpm_console = {}; -void runcpm_session_request_exit(void) -{ - // Status == 1 is the engine's "BIOS BOOT / exit CP/M" signal; setting it - // makes the CCP fall out of its loop at the next iteration. g_exit also - // breaks our own warm-boot loop below. - g_exit = true; - Status = 1; -} +/* Single-instance interlock against clobbering the shared RAM/state. */ +static std::atomic g_busy{false}; -bool runcpm_session_run(const runcpm_console_ops *ops) +extern "C" bool runcpm_session_run(const runcpm_console_ops *ops) { - bool expected = false; - if (!g_busy.compare_exchange_strong(expected, true)) - return false; // a session is already running + if (ops == nullptr) + return false; + + if (g_busy.exchange(true)) + return false; - g_exit = false; g_runcpm_console = *ops; - // One-time machine setup for the whole session. - Status = Debug = 0; - Break = Step = -1; - RAM = (uint8 *)malloc(MEMSIZE); - if (RAM != nullptr) + /* CCP + warm-boot loop: STATUS_EXIT ends the session, STATUS_RESTART warm- + boots CP/M as real hardware would. */ + while (true) { - memset(RAM, 0, MEMSIZE); + Status = Debug = 0; + Break = Step = Watch = -1; + + /* Drop any cached sequential-read handle so a file replaced between + boots is never read through a stale handle. */ + _seq_cache_close(); + + RAM = (uint8_t *)malloc(MEMSIZE); + if (!RAM) + break; + + memset(RAM, 0, MEMSIZE); memset(filename, 0, sizeof(filename)); - memset(newname, 0, sizeof(newname)); - memset(fcbname, 0, sizeof(fcbname)); - memset(pattern, 0, sizeof(pattern)); - - // CCP loop: a warm boot (Status == 2, e.g. ^C at the prompt or a - // program that RETs) re-enters the CCP and reprints the banner, exactly - // like real CP/M. An exit (Status == 1) or an external exit request - // ends the session. - while (true) - { - _puts(CCPHEAD); - _PatchCPM(); - Status = 0; - _ccp(); - if (Status == 1 || g_exit) - break; - } + memset(newname, 0, sizeof(newname)); + memset(fcbname, 0, sizeof(fcbname)); + memset(pattern, 0, sizeof(pattern)); + + _puts(CCPHEAD); + _PatchCPM(); + _ccp(); free(RAM); RAM = nullptr; + + if (Status == STATUS_EXIT) + break; } - g_runcpm_console = runcpm_console_ops{}; + _seq_cache_close(); + g_busy.store(false); return true; } + +extern "C" void runcpm_session_request_exit(void) +{ + /* Stop at the next warm boot; caller must unblock its own getch. */ + Status = STATUS_EXIT; +} + +extern "C" bool runcpm_session_active(void) +{ + return g_busy.load(); +} diff --git a/lib/runcpm/runcpm_session.h b/lib/runcpm/runcpm_session.h index b74d87bfc..4a58eb728 100644 --- a/lib/runcpm/runcpm_session.h +++ b/lib/runcpm/runcpm_session.h @@ -1,39 +1,43 @@ #ifndef RUNCPM_SESSION_H #define RUNCPM_SESSION_H -#include - -// Single shared entry point into the RunCPM engine. -// -// The engine itself is compiled exactly once (runcpm_core.cpp, global -// linkage). Every transport that wants to run CP/M - the SIO/Atari bus, the -// IWM/Apple and DriveWire/CoCo background tasks, the RS232 bus and the -// N:CPM:// network adapter - drives that one engine copy by supplying a small -// set of console callbacks and calling runcpm_session_run(). -// -// Only the four console primitives differ between transports; all of the BDOS, -// BIOS, disk and CCP logic is shared. The callbacks are deliberately plain C -// function pointers so the engine (compiled as C-style code) can call back into -// whichever device object owns the current session. -typedef struct runcpm_console_ops { - int (*kbhit)(void); // non-zero if a character is waiting - uint8_t (*getch)(void); // blocking read of one character - void (*putch)(uint8_t c); // write one character - void (*clrscr)(void); // clear screen; may be NULL +/* + * Public, RunCPM-internals-free entry point to the single shared RunCPM core + * (runcpm_core.cpp). Exactly one TU compiles the engine and owns its state; + * transports include only this header and drive it as thin console back-ends. + * One session runs at a time, enforced by a busy interlock in the core. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Console primitives a transport supplies for the active session. */ +typedef struct runcpm_console_ops +{ + int (*getch)(void); /* blocking read, no echo (0..255) */ + int (*getche)(void); /* blocking read, with echo */ + int (*kbhit)(void); /* non-blocking: nonzero if a char is ready */ + void (*putch)(uint8_t c); /* write one byte */ + void (*clrscr)(void); /* clear screen (may be a no-op) */ } runcpm_console_ops; -// Run a full CP/M session using the supplied console callbacks. Blocks for the -// lifetime of the session (until the program exits CP/M or an exit is -// requested). Returns false immediately if another session is already active -// (the engine has a single 64K RAM image and is not re-entrant). +/* Run a CP/M session; blocks until it ends. Returns false immediately if a + * session is already active, true once the session it started has finished. */ bool runcpm_session_run(const runcpm_console_ops *ops); -// Ask the currently running session to terminate at the next CCP iteration. -// Safe to call from another task/transport (e.g. when the bus tears the link -// down out from under a blocked session). +/* Ask the active session to stop at the next opportunity. Thread-safe; the + * caller must still unblock its own getch (e.g. feed a CR/CTRL-C). */ void runcpm_session_request_exit(void); -// True while a session is running. +/* True while a session is running. */ bool runcpm_session_active(void); -#endif // RUNCPM_SESSION_H +#ifdef __cplusplus +} +#endif + +#endif /* RUNCPM_SESSION_H */