Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions fujinet_pc.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -310,6 +312,7 @@ if(FUJINET_TARGET STREQUAL "ATARI")
lib/device/sio/voice.h lib/device/sio/voice.cpp
lib/device/sio/clock.h lib/device/sio/clock.cpp
lib/device/sio/siocpm.h lib/device/sio/siocpm.cpp
lib/device/sio/vm_telnet.h lib/device/sio/vm_telnet.cpp
lib/device/sio/pclink.h lib/device/sio/pclink.cpp
lib/device/sio/modem.h lib/device/sio/modem.cpp

Expand Down
2 changes: 1 addition & 1 deletion lib/bus/rc2014bus/rc2014bus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ void systemBus::service()
#if 0
if (_cpmDev != nullptr && _cpmDev->cpmActive)
{
_cpmDev->rc2014_handle_cpm();
_cpmDev->handle_cpm();
return; // break!
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion lib/bus/rc2014sio/rc2014sio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ void systemBus::service()
#if 0
if (_cpmDev != nullptr && _cpmDev->cpmActive)
{
_cpmDev->rc2014_handle_cpm();
_cpmDev->handle_cpm();
return; // break!
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion lib/bus/rs232/rs232.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ void systemBus::service()

if (_cpmDev != nullptr && _cpmDev->cpmActive)
{
_cpmDev->rs232_handle_cpm();
_cpmDev->handle_cpm();
return; // break!
}

Expand Down
2 changes: 1 addition & 1 deletion lib/bus/sio/sio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!
}

Expand Down
179 changes: 179 additions & 0 deletions lib/device/cpm/cpm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "cpm.h"

#include <cstring>

// ---------------------------------------------------------------------------
// 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;
}

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)
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.getche = &cpmDevice::s_getche;
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<std::mutex> lock(rxmtx);
return rxq.size();
}

size_t cpmQueueDevice::host_read(uint8_t *buf, size_t max)
{
std::lock_guard<std::mutex> 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<std::mutex> lock(txmtx);
for (size_t i = 0; i < len; i++)
txq.push(buf[i]);
}
txcv.notify_one();
}

int cpmQueueDevice::ep_kbhit()
{
std::lock_guard<std::mutex> lock(txmtx);
return txq.empty() ? 0 : 1;
}

uint8_t cpmQueueDevice::ep_getch()
{
std::unique_lock<std::mutex> 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<std::mutex> 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);
}
101 changes: 101 additions & 0 deletions lib/device/cpm/cpm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#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 <cstddef>
#include <cstdint>

#include "bus.h"
#include "../runcpm/runcpm_session.h"

#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#else
#include <queue>
#include <mutex>
#include <condition_variable>
#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 int s_getch();
static int s_getche();
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<uint8_t> rxq, txq;
std::mutex rxmtx, txmtx;
std::condition_variable txcv;
#endif
};

#endif // DEVICE_CPM_BASE_H
Loading
Loading