checkpoint: keyboard copro code works -ish with dvhstx text mode

This commit is contained in:
Jeff Epler 2025-02-24 09:09:23 -06:00
parent f356fba7d8
commit a6851c595e
21 changed files with 9688 additions and 1 deletions

View file

@ -1 +1 @@
depends=Adafruit ILI9341, Adafruit BusIO, SD, Adafruit NeoPixel, Adafruit VS1053 Library, Adafruit BluefruitLE nRF51, Adafruit seesaw Library, Ethernet, Adafruit IO Arduino, FastLED, Adafruit LiquidCrystal, Adafruit SoftServo, TinyWireM, Adafruit AM radio library, WaveHC, Adafruit LED Backpack Library, MAX31850 OneWire, Adafruit VC0706 Serial Camera Library, RTClib, Adafruit SleepyDog Library, Adafruit Thermal Printer Library, Adafruit Zero I2S Library, Adafruit EPD, Adafruit SSD1351 library, Adafruit FONA Library, Adafruit Motor Shield V2 Library, Adafruit NeoMatrix, Adafruit Soundboard library, Adafruit Circuit Playground, ArduinoJson, Adafruit TCS34725, Adafruit Pixie, Adafruit GPS Library, TinyGPS, WiFi101, Adafruit DotStar, Adafruit Si7021 Library, Adafruit WS2801 Library, Mouse, Keyboard, Time, IRremote, Adafruit LSM9DS0 Library, Adafruit Arcada Library, MIDIUSB, PubSubClient, Adafruit LIS2MDL, Adafruit NeoPXL8, Adafruit MCP23017 Arduino Library, Adafruit MLX90640, LiquidCrystal, Adafruit NeoTrellis M4 Library, RGB matrix Panel, Adafruit MLX90614 Library, Adafruit RGB LCD Shield Library, MAX6675 library, Adafruit MP3, Adafruit Keypad, Adafruit Arcada GifDecoder, Keypad, Neosegment, Encoder, Adafruit TiCoServo, Adafruit Trellis Library, FauxmoESP, Adafruit LSM303 Accel, Adafruit LSM303DLH Mag, Adafruit LSM303DLHC, CapacitiveSensor, Adafruit Zero PDM Library, Adafruit DMA neopixel library, elapsedMillis, DST RTC, Adafruit SHARP Memory Display, Adafruit SPIFlash, BSEC Software Library, WiiChuck, Adafruit DPS310, Adafruit AHTX0, RotaryEncoder, Adafruit MCP9808 Library, LSM303, Adafruit Protomatter, Adafruit IS31FL3741 Library, Sensirion I2C SCD4x, Adafruit TestBed, Bounce2, Adafruit AHRS, Adafruit DRV2605 Library, STM32duino VL53L4CD, PicoDVI - Adafruit Fork, Adafruit MMA8451 Library, Adafruit TSC2007, GFX Library for Arduino, Adafruit PyCamera Library, Adafruit ADG72x, Adafruit BNO055, Adafruit SHT4x Library, Adafruit VCNL4200 Library, Adafruit GC9A01A
depends=Adafruit ILI9341, Adafruit BusIO, SD, Adafruit NeoPixel, Adafruit VS1053 Library, Adafruit BluefruitLE nRF51, Adafruit seesaw Library, Ethernet, Adafruit IO Arduino, FastLED, Adafruit LiquidCrystal, Adafruit SoftServo, TinyWireM, Adafruit AM radio library, WaveHC, Adafruit LED Backpack Library, MAX31850 OneWire, Adafruit VC0706 Serial Camera Library, RTClib, Adafruit SleepyDog Library, Adafruit Thermal Printer Library, Adafruit Zero I2S Library, Adafruit EPD, Adafruit SSD1351 library, Adafruit FONA Library, Adafruit Motor Shield V2 Library, Adafruit NeoMatrix, Adafruit Soundboard library, Adafruit Circuit Playground, ArduinoJson, Adafruit TCS34725, Adafruit Pixie, Adafruit GPS Library, TinyGPS, WiFi101, Adafruit DotStar, Adafruit Si7021 Library, Adafruit WS2801 Library, Mouse, Keyboard, Time, IRremote, Adafruit LSM9DS0 Library, Adafruit Arcada Library, MIDIUSB, PubSubClient, Adafruit LIS2MDL, Adafruit NeoPXL8, Adafruit MCP23017 Arduino Library, Adafruit MLX90640, LiquidCrystal, Adafruit NeoTrellis M4 Library, RGB matrix Panel, Adafruit MLX90614 Library, Adafruit RGB LCD Shield Library, MAX6675 library, Adafruit MP3, Adafruit Keypad, Adafruit Arcada GifDecoder, Keypad, Neosegment, Encoder, Adafruit TiCoServo, Adafruit Trellis Library, FauxmoESP, Adafruit LSM303 Accel, Adafruit LSM303DLH Mag, Adafruit LSM303DLHC, CapacitiveSensor, Adafruit Zero PDM Library, Adafruit DMA neopixel library, elapsedMillis, DST RTC, Adafruit SHARP Memory Display, Adafruit SPIFlash, BSEC Software Library, WiiChuck, Adafruit DPS310, Adafruit AHTX0, RotaryEncoder, Adafruit MCP9808 Library, LSM303, Adafruit Protomatter, Adafruit IS31FL3741 Library, Sensirion I2C SCD4x, Adafruit TestBed, Bounce2, Adafruit AHRS, Adafruit DRV2605 Library, STM32duino VL53L4CD, PicoDVI - Adafruit Fork, Adafruit MMA8451 Library, Adafruit TSC2007, GFX Library for Arduino, Adafruit PyCamera Library, Adafruit ADG72x, Adafruit BNO055, Adafruit SHT4x Library, Adafruit VCNL4200 Library, Adafruit GC9A01A, Adafruit DVI HSTX

View file

@ -0,0 +1,265 @@
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT
// pio-usb is required for rp2040 host
#include "pio_usb.h"
#include "Adafruit_TinyUSB.h"
#include "pico/stdlib.h"
#include "Adafruit_dvhstx.h"
DVHSTXText3 display(DVHSTX_PINOUT_DEFAULT);
// Pin D+ for host, D- = D+ + 1
#ifndef PIN_USB_HOST_DP
#define PIN_USB_HOST_DP 16
#endif
// Pin for enabling Host VBUS. comment out if not used
#ifndef PIN_5V_EN
#define PIN_5V_EN 18
#endif
#ifndef PIN_5V_EN_STATE
#define PIN_5V_EN_STATE 1
#endif
// USB Host object
Adafruit_USBH_Host USBHost;
// Serial output for RunCPM
SerialPIO pio_serial(1 /* RX of the sibling board */, SerialPIO::NOPIN);
void setup() {
// ensure text generation interrupt takes place on core0
display.begin();
display.println("Hello hstx\n");
}
void loop() {
}
void setup1() {
while(!display) ;
delay(10);
display.println("Hello hstx in setup1\n");
// override tools menu CPU frequency setting
//set_sys_clock_khz(264'000, true);
Serial.begin(115200);
#if 0
while ( !Serial ) delay(10); // wait for native usb
Serial.println("Core1 setup to run TinyUSB host with pio-usb");
#endif
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE);
#endif
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
pio_cfg.pin_dp = PIN_USB_HOST_DP;
pio_cfg.tx_ch = dma_claim_unused_channel(true);
dma_channel_unclaim(pio_cfg.tx_ch);
USBHost.configure_pio_usb(1, &pio_cfg);
// run host stack on controller (rhport) 1
// Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
// host bit-banging processing works done in core1 to free up core0 for other works
USBHost.begin(1);
// this `begin` is a void function, no way to check for failure!
pio_serial.begin(115200);
display.println("end of setup1\n");
display.show_cursor();
}
int old_ascii = -1;
uint32_t repeat_timeout;
// this matches Linux default of 500ms to first repeat, 1/20s thereafter
const uint32_t default_repeat_time = 50;
const uint32_t initial_repeat_time = 500;
void send_ascii(uint8_t code, uint32_t repeat_time=default_repeat_time) {
old_ascii = code;
repeat_timeout = millis() + repeat_time;
if (code >= 32 && code < 127) {
display.printf("%c", code);
} else {
display.printf("\\x%02x", code);
}
pio_serial.write(code);
}
void loop1()
{
static bool last_serial;
if (!last_serial && Serial) {
last_serial = true;
Serial.println("Hello host serial");
}
uint32_t now = millis();
uint32_t deadline = repeat_timeout - now;
if (old_ascii >= 0 && deadline > INT32_MAX) {
send_ascii(old_ascii);
deadline = repeat_timeout - now;
} else if (old_ascii < 0) {
deadline = UINT32_MAX;
}
tuh_task_ext(deadline, false);
}
// Invoked when device with hid interface is mounted
// Report descriptor is also available for use.
// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough
// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,
// it will be skipped therefore report_desc = NULL, desc_len = 0
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
(void)desc_report;
(void)desc_len;
uint16_t vid, pid;
tuh_vid_pid_get(dev_addr, &vid, &pid);
Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid);
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) {
Serial.printf("HID Keyboard\r\n");
if (!tuh_hid_receive_report(dev_addr, instance)) {
Serial.printf("Error: cannot request to receive report\r\n");
}
}
}
// Invoked when device with hid interface is un-mounted
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
}
#define FLAG_ALPHABETIC (1)
#define FLAG_SHIFT (2)
#define FLAG_NUMLOCK (4)
#define FLAG_CTRL (8)
#define FLAG_LUT (16)
const char * const lut[] = {
"!@#$%^&*()", /* 0 - shifted numeric keys */
"\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */
"\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */
"\12\13\10\22", /* 3 - arrow keys RLDU */
"/*-+\n1234567890.", /* 4 - keypad w/numlock */
"/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */
};
struct keycode_mapper {
uint8_t first, last, code, flags;
} keycode_to_ascii[] = {
{ HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, },
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, },
{ HID_KEY_1, HID_KEY_9, '1', 0, },
{ HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, },
{ HID_KEY_0, HID_KEY_0, '0', 0, },
{ HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL },
{ HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, },
{ HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, },
{ HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT },
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT },
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT },
};
bool report_contains(const hid_keyboard_report_t &report, uint8_t key) {
for (int i = 0; i < 6; i++) {
if (report.keycode[i] == key) return true;
}
return false;
}
hid_keyboard_report_t old_report;
static bool caps, num;
static uint8_t old_leds;
void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t &report) {
bool alt = report.modifier & 0x44;
bool shift = report.modifier & 0x22;
bool ctrl = report.modifier & 0x11;
uint8_t code = 0;
if (report.keycode[0] == 1 && report.keycode[1] == 1) {
// keyboard says it has exceeded max kro
return;
}
// something was pressed or release, so cancel any key repeat
old_ascii = -1;
for (auto keycode : report.keycode) {
if (keycode == 0) continue;
if (report_contains(old_report, keycode)) continue;
/* key is newly pressed */
if (keycode == HID_KEY_NUM_LOCK) {
Serial.println("toggle numlock");
num = !num;
} else if (keycode == HID_KEY_CAPS_LOCK) {
Serial.println("toggle capslock");
caps = !caps;
} else {
for (const auto &mapper : keycode_to_ascii) {
if (!(keycode >= mapper.first && keycode <= mapper.last))
continue;
if (mapper.flags & FLAG_SHIFT && !shift)
continue;
if (mapper.flags & FLAG_NUMLOCK && !num)
continue;
if (mapper.flags & FLAG_CTRL && !ctrl)
continue;
if (mapper.flags & FLAG_LUT) {
code = lut[mapper.code][keycode - mapper.first];
} else {
code = keycode - mapper.first + mapper.code;
}
if (mapper.flags & FLAG_ALPHABETIC) {
if (shift ^ caps) {
code ^= ('a' ^ 'A');
}
}
if (ctrl) code &= 0x1f;
if (alt) code ^= 0x80;
send_ascii(code, initial_repeat_time);
break;
}
}
}
uint8_t leds = (caps << 1) | num;
if (leds != old_leds) {
old_leds = leds;
Serial.printf("Send LEDs report %d (dev:instance = %d:%d)\r\n", leds, dev_addr, instance);
// no worky
auto r = tuh_hid_set_report(dev_addr, instance/*idx*/, 0/*report_id*/, HID_REPORT_TYPE_OUTPUT/*report_type*/, &old_leds, sizeof(old_leds));
Serial.printf("set_report() -> %d\n", (int)r);
}
old_report = report;
}
// Invoked when received report from device via interrupt endpoint
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
if ( len != sizeof(hid_keyboard_report_t) ) {
Serial.printf("report len = %u NOT 8, probably something wrong !!\r\n", len);
} else {
process_event(dev_addr, instance, *(hid_keyboard_report_t*)report);
}
// continue to request to receive report
if (!tuh_hid_receive_report(dev_addr, instance)) {
Serial.printf("Error: cannot request to receive report\r\n");
}
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Mockba the Borg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,31 @@
<!--
SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
SPDX-License-Identifier: MIT
-->
This is a port of runcpm to the raspberry pi pico.
It is based on:
* [RunCPM](https://github.com/MockbaTheBorg/RunCPM/)
* [RunCPM_RPi_Pico](https://github.com/guidol70/RunCPM_RPi_Pico)
It works on a Raspberry Pi Pico (or Pico W). It uses the internal flash
for storage, and can be mounted as USB storage.
If your Pico is placed on the Pico DV carrier board, you also get a 100x30
character screen to enjoy your CP/M output on!
First, build for your device. You must
* Use the Philhower Pico Core
* In the Tools menu, select
* A flash size option that includes at least 512kB for filesystem
* USB Stack: Adafruit TinyUSB
After it boots the first time, you need to
* Format the flash device on your host computer
* Create the folder "<DEVICE>/A/0"
* Put something useful in that folder, such as [Zork](http://www.retroarchive.org/cpm/games/zork123_80.zip)
* Files must respect the "8.3" naming convention (8 letters filename + 3 letters extension) and be all uppercase
* Safely eject the drive, then reset the emulator.
* Now at the "A0>" prompt you can run "ZORK1".

View file

@ -0,0 +1,531 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef ABSTRACT_H
#define ABSTRACT_H
#ifdef PROFILE
#define printf(a, b) Serial.println(b)
#endif
// #if defined ARDUINO_SAM_DUE || defined ADAFRUIT_GRAND_CENTRAL_M4
#define HostOS 0x01
// #endif
#if defined CORE_TEENSY
#define HostOS 0x04
#endif
#if defined ESP32
#define HostOS 0x05
#endif
#if defined _STM32_DEF_
#define HostOS 0x06
#endif
/* Memory abstraction functions */
/*===============================================================================*/
bool _RamLoad(char* filename, uint16 address) {
File32 f;
bool result = false;
if (f = SD.open(filename, FILE_READ)) {
while (f.available())
_RamWrite(address++, f.read());
f.close();
result = true;
}
return(result);
}
/* Filesystem (disk) abstraction fuctions */
/*===============================================================================*/
File32 rootdir, userdir;
#define FOLDERCHAR '/'
typedef struct {
uint8 dr;
uint8 fn[8];
uint8 tp[3];
uint8 ex, s1, s2, rc;
uint8 al[16];
uint8 cr, r0, r1, r2;
} CPM_FCB;
typedef struct {
uint8 dr;
uint8 fn[8];
uint8 tp[3];
uint8 ex, s1, s2, rc;
uint8 al[16];
} CPM_DIRENTRY;
static DirFat_t fileDirEntry;
bool _sys_exists(uint8* filename) {
return(SD.exists((const char *)filename));
}
File32 _sys_fopen_w(uint8* filename) {
return(SD.open((char*)filename, O_CREAT | O_WRITE));
}
int _sys_fputc(uint8 ch, File32& f) {
return(f.write(ch));
}
void _sys_fflush(File32& f) {
f.flush();
}
void _sys_fclose(File32& f) {
f.close();
}
int _sys_select(uint8* disk) {
uint8 result = FALSE;
File32 f;
digitalWrite(LED, HIGH ^ LEDinv);
if (f = SD.open((char*)disk, O_READ)) {
if (f.isDirectory())
result = TRUE;
f.close();
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
long _sys_filesize(uint8* filename) {
long l = -1;
File32 f;
digitalWrite(LED, HIGH ^ LEDinv);
if (f = SD.open((char*)filename, O_RDONLY)) {
l = f.size();
f.close();
}
digitalWrite(LED, LOW ^ LEDinv);
return(l);
}
int _sys_openfile(uint8* filename) {
File32 f;
int result = 0;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_READ);
if (f) {
f.dirEntry(&fileDirEntry);
f.close();
result = 1;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
int _sys_makefile(uint8* filename) {
File32 f;
int result = 0;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_CREAT | O_WRITE);
if (f) {
f.close();
result = 1;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
int _sys_deletefile(uint8* filename) {
digitalWrite(LED, HIGH ^ LEDinv);
return(SD.remove((char*)filename));
digitalWrite(LED, LOW ^ LEDinv);
}
int _sys_renamefile(uint8* filename, uint8* newname) {
File32 f;
int result = 0;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_WRITE | O_APPEND);
if (f) {
if (f.rename((char*)newname)) {
f.close();
result = 1;
}
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
#ifdef DEBUGLOG
void _sys_logbuffer(uint8* buffer) {
#ifdef CONSOLELOG
puts((char*)buffer);
#else
File32 f;
uint8 s = 0;
while (*(buffer + s)) // Computes buffer size
++s;
if (f = SD.open(LogName, O_CREAT | O_APPEND | O_WRITE)) {
f.write(buffer, s);
f.flush();
f.close();
}
#endif
}
#endif
bool _sys_extendfile(char* fn, unsigned long fpos)
{
uint8 result = true;
File32 f;
unsigned long i;
digitalWrite(LED, HIGH ^ LEDinv);
if (f = SD.open(fn, O_WRITE | O_APPEND)) {
if (fpos > f.size()) {
for (i = 0; i < f.size() - fpos; ++i) {
if (f.write((uint8)0) != 1) {
result = false;
break;
}
}
}
f.close();
} else {
result = false;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
uint8 _sys_readseq(uint8* filename, long fpos) {
uint8 result = 0xff;
File32 f;
uint8 bytesread;
uint8 dmabuf[BlkSZ];
uint8 i;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_READ);
if (f) {
if (f.seek(fpos)) {
for (i = 0; i < BlkSZ; ++i)
dmabuf[i] = 0x1a;
bytesread = f.read(&dmabuf[0], BlkSZ);
if (bytesread) {
for (i = 0; i < BlkSZ; ++i)
_RamWrite(dmaAddr + i, dmabuf[i]);
}
result = bytesread ? 0x00 : 0x01;
} else {
result = 0x01;
}
f.close();
} else {
result = 0x10;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
uint8 _sys_writeseq(uint8* filename, long fpos) {
uint8 result = 0xff;
File32 f;
digitalWrite(LED, HIGH ^ LEDinv);
if (_sys_extendfile((char*)filename, fpos))
f = SD.open((char*)filename, O_RDWR);
if (f) {
if (f.seek(fpos)) {
if (f.write(_RamSysAddr(dmaAddr), BlkSZ))
result = 0x00;
} else {
result = 0x01;
}
f.close();
} else {
result = 0x10;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
uint8 _sys_readrand(uint8* filename, long fpos) {
uint8 result = 0xff;
File32 f;
uint8 bytesread;
uint8 dmabuf[BlkSZ];
uint8 i;
long extSize;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_READ);
if (f) {
if (f.seek(fpos)) {
for (i = 0; i < BlkSZ; ++i)
dmabuf[i] = 0x1a;
bytesread = f.read(&dmabuf[0], BlkSZ);
if (bytesread) {
for (i = 0; i < BlkSZ; ++i)
_RamWrite(dmaAddr + i, dmabuf[i]);
}
result = bytesread ? 0x00 : 0x01;
} else {
if (fpos >= 65536L * BlkSZ) {
result = 0x06; // seek past 8MB (largest file size in CP/M)
} else {
extSize = f.size();
// 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
}
}
f.close();
} else {
result = 0x10;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
uint8 _sys_writerand(uint8* filename, long fpos) {
uint8 result = 0xff;
File32 f;
digitalWrite(LED, HIGH ^ LEDinv);
if (_sys_extendfile((char*)filename, fpos)) {
f = SD.open((char*)filename, O_RDWR);
}
if (f) {
if (f.seek(fpos)) {
if (f.write(_RamSysAddr(dmaAddr), BlkSZ))
result = 0x00;
} else {
result = 0x06;
}
f.close();
} else {
result = 0x10;
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
static uint8 findNextDirName[13];
static uint16 fileRecords = 0;
static uint16 fileExtents = 0;
static uint16 fileExtentsUsed = 0;
static uint16 firstFreeAllocBlock;
uint8 _findnext(uint8 isdir) {
File32 f;
uint8 result = 0xff;
bool isfile;
uint32 bytes;
digitalWrite(LED, HIGH ^ LEDinv);
if (allExtents && fileRecords) {
_mockupDirEntry();
result = 0;
} else {
while (f = userdir.openNextFile()) {
f.getName((char*)&findNextDirName[0], 13);
isfile = !f.isDirectory();
bytes = f.size();
f.dirEntry(&fileDirEntry);
f.close();
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;
}
}
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
uint8 _findfirst(uint8 isdir) {
uint8 path[4] = { '?', FOLDERCHAR, '?', 0 };
path[0] = filename[0];
path[2] = filename[2];
if (userdir)
userdir.close();
userdir = SD.open((char*)path); // Set directory search to start from the first position
_HostnameToFCBname(filename, pattern);
fileRecords = 0;
fileExtents = 0;
fileExtentsUsed = 0;
return(_findnext(isdir));
}
uint8 _findnextallusers(uint8 isdir) {
uint8 result = 0xFF;
char dirname[13];
bool done = false;
while (!done) {
while (!userdir) {
userdir = rootdir.openNextFile();
if (!userdir) {
done = true;
break;
}
userdir.getName(dirname, sizeof dirname);
if (userdir.isDirectory() && strlen(dirname) == 1 && isxdigit(dirname[0])) {
currFindUser = dirname[0] <= '9' ? dirname[0] - '0' : toupper(dirname[0]) - 'A' + 10;
break;
}
userdir.close();
}
if (userdir) {
result = _findnext(isdir);
if (result) {
userdir.close();
} else {
done = true;
}
} else {
result = 0xFF;
done = true;
}
}
return result;
}
uint8 _findfirstallusers(uint8 isdir) {
uint8 path[2] = { '?', 0 };
path[0] = filename[0];
if (rootdir)
rootdir.close();
if (userdir)
userdir.close();
rootdir = SD.open((char*)path); // Set directory search to start from the first position
strcpy((char*)pattern, "???????????");
if (!rootdir)
return 0xFF;
fileRecords = 0;
fileExtents = 0;
fileExtentsUsed = 0;
return(_findnextallusers(isdir));
}
uint8 _Truncate(char* filename, uint8 rc) {
File32 f;
int result = 0;
digitalWrite(LED, HIGH ^ LEDinv);
f = SD.open((char*)filename, O_WRITE | O_APPEND);
if (f) {
if (f.truncate(rc * BlkSZ)) {
f.close();
result = 1;
}
}
digitalWrite(LED, LOW ^ LEDinv);
return(result);
}
void _MakeUserDir() {
uint8 dFolder = cDrive + 'A';
uint8 uFolder = toupper(tohex(userCode));
uint8 path[4] = { dFolder, FOLDERCHAR, uFolder, 0 };
digitalWrite(LED, HIGH ^ LEDinv);
SD.mkdir((char*)path);
digitalWrite(LED, LOW ^ LEDinv);
}
uint8 _sys_makedisk(uint8 drive) {
uint8 result = 0;
if (drive < 1 || drive>16) {
result = 0xff;
} else {
uint8 dFolder = drive + '@';
uint8 disk[2] = { dFolder, 0 };
digitalWrite(LED, HIGH ^ LEDinv);
if (!SD.mkdir((char*)disk)) {
result = 0xfe;
} else {
uint8 path[4] = { dFolder, FOLDERCHAR, '0', 0 };
SD.mkdir((char*)path);
}
digitalWrite(LED, LOW ^ LEDinv);
}
return(result);
}
/* Hardware abstraction functions */
/*===============================================================================*/
void _HardwareOut(const uint32 Port, const uint32 Value) {
}
uint32 _HardwareIn(const uint32 Port) {
return 0;
}
/* Console abstraction functions */
/*===============================================================================*/
#include "arduino_hooks.h"
int _kbhit(void) {
if (_kbhit_hook && _kbhit_hook()) { return true; }
return(Serial.available());
}
uint8 _getch(void) {
while(true) {
if(_kbhit_hook && _kbhit_hook()) { return _getch_hook(); }
if(Serial.available()) { return Serial.read(); }
}
}
uint8 _getche(void) {
uint8 ch = _getch();
_putch(ch);
return(ch);
}
void _putch(uint8 ch) {
Serial.write(ch);
if(_putch_hook) _putch_hook(ch);
}
void _clrscr(void) {
Serial.print("\e[H\e[J");
}
#endif

View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#include "arduino_hooks.h"
bool (*_kbhit_hook)(void);
uint8_t (*_getch_hook)(void);
void (*_putch_hook)(uint8_t ch);

View file

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#pragma once
#include <stdbool.h>
#include <stdint.h>
extern bool (*_kbhit_hook)(void);
extern uint8_t (*_getch_hook)(void);
extern void (*_putch_hook)(uint8_t ch);

View file

@ -0,0 +1,891 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef CCP_H
#define CCP_H
// CP/M BDOS calls
#include "cpm.h"
#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 defDMA 0x0080 // Default DMA address
#define defLoad 0x0100 // Default load address
// CCP global variables
uint8 pgSize = 22; // for TYPE
uint8 curDrive = 0; // 0 -> 15 = A -> P .. Current drive for the CCP (same as RAM[DSKByte])
uint8 parDrive = 0; // 0 -> 15 = A -> P .. Drive for the first file parameter
uint8 curUser = 0; // 0 -> 15 .. Current user area to access
bool sFlag = FALSE; // Submit Flag
uint8 sRecs = 0; // Number of records on the Submit file
uint8 prompt[8] = "\r\n >";
uint16 pbuf, perr;
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
};
// Used to call BDOS from inside the CCP
uint16 _ccp_bdos(uint8 function, uint16 de) {
SET_LOW_REGISTER(BC, function);
DE = de;
_Bdos();
return (HL & 0xffff);
} // _ccp_bdos
// Compares two strings (Atmel doesn't like strcmp)
uint8 _ccp_strcmp(char *stra, char *strb) {
while (*stra && *strb && (*stra == *strb)) {
++stra;
++strb;
}
return (*stra == *strb);
} // _ccp_strcmp
// Gets the command ID number
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
uint8 _ccp_delim(uint8 ch) {
return (ch == 0 || ch == ' ' || ch == '=' || ch == '.' || ch == ':' || ch == ';' || ch == '<' || ch == '>');
}
// Prints the FCB filename
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, ':');
}
for (i = 1; i < 12; ++i) {
ch = _RamRead(fcb + i);
if ((ch == ' ') && compact)
continue;
if (i == 9)
_ccp_bdos(C_WRITE, compact ? '.' : ' ');
_ccp_bdos(C_WRITE, ch);
}
} // _ccp_printfcb
// Initializes the FCB
void _ccp_initFCB(uint16 address, uint8 size) {
uint8 i;
for (i = 0; i < size; ++i)
_RamWrite(address + i, 0x00);
for (i = 0; i < 11; ++i)
_RamWrite(address + 1 + i, 0x20);
} // _ccp_initFCB
// Name to FCB
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 (blen) {
++fcb;
plen = 8;
pad = ' ';
ch = toupper(_RamRead(pbuf));
while (blen && plen) {
if (_ccp_delim(ch))
break;
++pbuf;
--blen;
if (ch == '*')
pad = '?';
if (pad == '?') {
ch = pad;
n = n | 0x80; // Name is not unique
}
--plen;
++n;
_RamWrite(fcb++, ch);
ch = toupper(_RamRead(pbuf));
}
while (plen--)
_RamWrite(fcb++, pad);
plen = 3;
pad = ' ';
if (ch == '.') {
++pbuf;
--blen;
}
while (blen && plen) {
ch = toupper(_RamRead(pbuf));
if (_ccp_delim(ch))
break;
++pbuf;
--blen;
if (ch == '*')
pad = '?';
if (pad == '?') {
ch = pad;
n = n | 0x80; // Name is not unique
}
--plen;
++n;
_RamWrite(fcb++, ch);
}
while (plen--)
_RamWrite(fcb++, pad);
}
return (n);
} // _ccp_nameToFCB
// Converts the ParFCB name to a number
uint16 _ccp_fcbtonum() {
uint8 ch;
uint16 n = 0;
uint8 pos = ParFCB + 1;
while (TRUE) {
ch = _RamRead(pos++);
if ((ch < '0') || (ch > '9')) {
break;
}
n = (n * 10) + (ch - '0');
}
return (n);
} // _ccp_fcbtonum
// DIR command
void _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)
_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);
} else {
_puts((char *)dirSep);
}
_ccp_printfcb(tmpFCB, FALSE);
++fcount;
++ccount;
if (ccount > 3)
ccount = 0;
}
} else {
_puts("No file");
}
} // _ccp_dir
// ERA command
void _ccp_era(void) {
if (_ccp_bdos(F_DELETE, ParFCB))
_puts("\r\nNo file");
} // _ccp_era
// TYPE command
uint8 _ccp_type(void) {
uint8 i, c, l = 0, error = TRUE;
uint16 a, p = 0;
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)
break;
_ccp_bdos(C_WRITE, c);
if (c == 0x0a) {
++l;
if (pgSize && (l == pgSize)) {
l = 0;
p = _ccp_bdos(C_READ, 0x0000);
if (p == 3)
break;
}
}
--i;
++a;
}
if (p == 3)
break;
}
error = FALSE;
}
return (error);
} // _ccp_type
// SAVE command
uint8 _ccp_save(void) {
uint8 error = TRUE;
uint16 pages = _ccp_fcbtonum();
uint16 i, dma;
if (pages < 256) {
error = FALSE;
while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces
++pbuf;
--blen;
}
_ccp_nameToFCB(SecFCB); // Loads file name onto the ParFCB
if (_ccp_bdos(F_MAKE, SecFCB)) {
_puts("Err: create");
} else {
if (_ccp_bdos(F_OPEN, SecFCB)) {
_puts("Err: open");
} 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);
dma += 128;
_ccp_bdos( C_WRITE, '.');
}
_ccp_bdos(F_CLOSE, SecFCB);
}
}
}
return (error);
} // _ccp_save
// REN command
void _ccp_ren(void) {
uint8 ch, i;
++pbuf;
_ccp_nameToFCB(SecFCB);
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);
}
if (_ccp_bdos(F_RENAME, ParFCB))
_puts("\r\nNo file");
} // _ccp_ren
// USER command
uint8 _ccp_user(void) {
uint8 error = TRUE;
curUser = (uint8)_ccp_fcbtonum();
if (curUser < 16) {
_ccp_bdos(F_USERNUM, curUser);
error = FALSE;
}
return (error);
} // _ccp_user
// PAGE command
uint8 _ccp_page(void) {
uint8 error = TRUE;
uint16 r = _ccp_fcbtonum();
if (r < 256) {
pgSize = (uint8)r;
error = FALSE;
}
return (error);
} // _ccp_page
// VOL command
uint8 _ccp_vol(void) {
uint8 error = FALSE;
uint8 letter = _RamRead(ParFCB) ? '@' + _RamRead(ParFCB) : 'A' + curDrive;
uint8 folder[5] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 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)) {
_putcon(i < 10 ? ' ' : '1');
_putcon(i < 10 ? folder[2] : 38 + i);
_puts(": ");
filename[2] = i < 10 ? i + 48 : i + 55;
bytesread = (uint8)_sys_readseq(filename, 0);
if (!bytesread) {
for (j = 0; j < 128; ++j) {
if ((_RamRead(dmaAddr + j) < 32) || (_RamRead(dmaAddr + j) > 126))
break;
_putcon(_RamRead(dmaAddr + j));
}
}
_puts("\r\n");
}
}
return (error);
} // _ccp_vol
// ?/Help command
uint8 _ccp_hlp(void) {
_puts("\r\nCCP Commands:\r\n");
_puts("\t? - Shows this list of commands\r\n");
_puts("\tCLS - Clears the screen\r\n");
_puts("\tDEL - Alias to ERA\r\n");
_puts("\tEXIT - Terminates RunCPM\r\n");
_puts("\tPAGE [<n>] - Sets the page size for TYPE\r\n");
_puts("\t or disables paging if no parameter passed\r\n");
_puts("\tVOL [drive] - Shows the volume information\r\n");
_puts("\t which comes from each volume's INFO.TXT");
return(FALSE);
}
#ifdef HASLUA
// External (.LUA) command
uint8 _ccp_lua(void) {
uint8 error = TRUE;
uint8 found, drive, user = 0;
uint16 loadAddr = defLoad;
_RamWrite( CmdFCB + 9, 'L');
_RamWrite( CmdFCB + 10, 'U');
_RamWrite( CmdFCB + 11, 'A');
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 (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;
}
_RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was
cDrive = oDrive; // And restore cDrive
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);
} // _ccp_lua
#endif // ifdef HASLUA
// External (.COM) command
uint8 _ccp_ext(void) {
bool error = TRUE, found = FALSE;
uint8 drive = 0, user = 0;
uint16 loadAddr = defLoad;
bool wasBlank = (_RamRead(CmdFCB + 9) == ' ');
bool wasSUB = ((_RamRead(CmdFCB + 9) == 'S') &&
(_RamRead(CmdFCB + 10) == 'U') &&
(_RamRead(CmdFCB + 11) == 'B'));
if (!wasSUB) {
if (wasBlank) {
_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
}
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 (!found) {
_RamWrite(CmdFCB, drive); // restore previous drive
_ccp_bdos(F_USERNUM, curUser); // 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
_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
}
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 (!found) {
_RamWrite(CmdFCB, drive); // restore previous drive
_ccp_bdos(F_USERNUM, curUser); // restore to previous user
}
if (found) {
//_puts(".SUB file found!\n");
int i;
//move FCB's (CmdFCB --> ParFCB --> SecFCB)
for (i = 0; i < 16; i++) {
//ParFCB to SecFCB
_RamWrite(SecFCB + i, _RamRead(ParFCB + i));
//CmdFCB to ParFCB
_RamWrite(ParFCB + i, _RamRead(CmdFCB + i));
}
// (Re)Initialize the CmdFCB
_ccp_initFCB(CmdFCB, 36);
//put 'SUBMIT.COM' in CmdFCB
const char *str = "SUBMIT COM";
int s = (int)strlen(str);
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
}
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 (found) {
//insert "@" into command buffer
//note: this is so the rest will be parsed correctly
blen = _RamRead(defDMA);
if (blen < cmdLen) {
blen++;
_RamWrite(defDMA, blen);
}
uint8 lc = '@';
for (i = 0; i < blen; i++) {
uint8 nc = _RamRead(defDMA + 1 + i);
_RamWrite(defDMA + 1 + i, lc);
lc = nc;
}
}
}
}
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
loadAddr += 128;
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, defDMA); // Points the DMA offset back to the default
if (user) { // If a user was selected
_ccp_bdos(F_USERNUM, curUser); // Set it back
user = 0;
}
_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
_RamWrite16(loadAddr + 1, defLoad);
_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(); // Starts Z80 simulation
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);
} // _ccp_ext
// Prints a command error
void _ccp_cmdError() {
uint8 ch;
_puts("\r\n");
while ((ch = _RamRead(perr++))) {
if (ch == ' ')
break;
_ccp_bdos(C_WRITE, toupper(ch));
}
_puts("?\r\n");
} // _ccp_cmdError
// Reads input, either from the $$$.SUB or console
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));
_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
}
} else {
_ccp_bdos(C_READSTR, inBuf); // Reads the command line from console
if (Debug)
Z80run();
}
} // _ccp_readInput
// Main CCP code
void _ccp(void) {
uint8 i;
sFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000);
_ccp_bdos(DRV_SET, curDrive);
for (i = 0; i < 36; ++i)
_RamWrite(BatchFCB + i, _RamRead(tmpFCB + i));
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
sprintf((char *) 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;
}
if (!blen) // There were only spaces
continue;
if (_RamRead(pbuf) == ';') // Found a comment 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
continue;
}
if (continueFlag) {
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
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 + i, 0);
_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(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
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;
}
case 11: { // HELP
i = _ccp_hlp();
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)
_ccp_cmdError();
}
if ((Status == 1) || (Status == 2))
break;
}
_puts("\r\n");
} // _ccp
#endif // ifndef CCP_H

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef CONSOLE_H
#define CONSOLE_H
extern int _kbhit(void);
uint8_t _getch(void);
void _putch(uint8 ch);
/* see main.c for definition */
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.
uint8 _chready(void) // Checks if there's a character ready for input
{
return(_kbhit() ? 0xff : 0x00);
}
uint8 _getchNB(void) // Gets a character, non-blocking, no echo
{
return(_kbhit() ? _getch() : 0x00);
}
void _putcon(uint8 ch) // Puts a character
{
_putch(ch & mask8bit);
}
void _puts(const char* str) // Puts a \0 terminated string
{
while (*str)
_putcon(*(str++));
}
void _puthex8(uint8 c) // Puts a HH hex string
{
_putcon(tohex(c >> 4));
_putcon(tohex(c & 0x0f));
}
void _puthex16(uint16 w) // puts a HHHH hex string
{
_puthex8(w >> 8);
_puthex8(w & 0x00ff);
}
void _putdec(uint16_t w) {
char buf[] = " 0";
size_t i=sizeof(buf)-1;
while(w) {
assert(i > 0);
buf[--i] = '0' + (w % 10);
w /= 10;
}
_puts(buf);
}
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,635 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef DISK_H
#define DISK_H
/* see main.c for definition */
#ifdef __linux__
#include <sys/stat.h>
#endif
#ifdef __DJGPP__
#include <dirent.h>
#endif
/*
Disk errors
*/
#define errWRITEPROT 1
#define errSELECT 2
#define RW (roVector & (1 << cDrive))
// Prints out a BDOS error
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;
}
// Selects the disk to be used by the next disk function
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);
}
// Converts a FCB entry onto a host OS filename string
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 = '_';
#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
}
#ifdef NOSLASH
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);
}
// Convers a host OS filename string onto a FCB entry
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)
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
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;
}
}
}
// Matches a FCB name to a search pattern
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
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);
}
// Opens a file
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);
}
// Closes a file
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);
}
// Creates a file
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);
}
// Searches for the first directory file
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);
}
// Searches for the next directory file
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);
}
// Deletes a file
uint8 _DeleteFile(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
#if defined(USE_PUN) || defined(USE_LST)
CPM_FCB* T = (CPM_FCB*)_RamSysAddr(tmpFCB);
#endif
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) {
#ifdef USE_PUN
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;
}
#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);
}
// Renames a file
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);
}
// Sequential read
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);
}
// Sequential write
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);
}
// Random read
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);
}
// Random write
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 the size of a CP/M file
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);
}
// Set the next random record
uint8 _SetRandom(uint16 fcbaddr) {
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;
F->r0 = count & 0xff;
F->r1 = (count >> 8) & 0xff;
F->r2 = (count >> 16) & 0xff;
return(result);
}
// Sets the current user area
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
#ifdef NOHIGHUSER
if(userCode < 16)
#endif
_MakeUserDir(); // Creates the user dir (0-F[G-V]) if needed
}
// Creates a disk directory folder
uint8 _MakeDisk(uint16 fcbaddr) {
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
return(_sys_makedisk(F->dr));
}
// Checks if there's a temp submit file present
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 $
#ifdef BATCHA
_RamWrite(tmpFCB, 1); // Forces it to be checked on drive A:
#endif
#ifdef BATCH0
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);
}
#ifdef HASLUA
// Executes a Lua script
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

View file

@ -0,0 +1,252 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef GLOBALS_H
#define GLOBALS_H
/* Some definitions needed globally */
#ifdef __MINGW32__
#include <ctype.h>
#endif
/* Definition for enabling incrementing the R register for each M1 cycle */
#define DO_INCR
/* 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
/* 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
#define LogName "RunCPM.log"
/* RunCPM version for the greeting header */
#define VERSION "6.0"
#define VersionBCD 0x60
/* 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
/* Definition of the CCP memory information */
//
#ifdef CCP_INTERNAL
#define CCPname "INTERNAL v3.0" // Will use the CCP from ccp.h
#define VersionCCP 0x30 // 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
#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)
#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)
#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)
#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)
#endif
//
#ifndef CCPname
#error No CCP defined
#endif
//
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
// #define CCPHEAD "\r\nRunCPM Version " VERSION " (CP/M " STR(TPASIZE) "K)\r\n"
#define CCPHEAD "\r\nRunCPM [" TEXT_BOLD "v" VERSION TEXT_NORMAL "] => CCP:[" TEXT_BOLD CCPname TEXT_NORMAL "] TPA:" TEXT_BOLD STR(TPASIZE) "K" TEXT_NORMAL "\r\n"
#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 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
/* 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
/* Some environment and type definitions */
#ifndef TRUE
#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 HIGH_REGISTER(x) (((x) >> 8) & 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)
/* 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 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
/* CP/M memory definitions */
#define TPASIZE 64 // 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 BANKS 1 // Number of memory banks available
static uint8 curBank = 1; // Number of the current RAM bank in use (1-x, not 0-x)
static uint8 isXmove = FALSE; // Used by BIOS
static uint8 srcBank = 1; // Source bank for memory MOVE
static uint8 dstBank = 1; // Destination bank for memory MOVE
static uint8 ioBank = 1; // Destination bank for sector IO
#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
#endif
#ifdef RAM_FAST // Makes all function calls to memory access into direct RAM access (less calls / less code)
static uint8 RAM[MEMSIZE];
#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)
// 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 + 128) // Address of the Disk Parameter Block (Hardcoded in BIOS)
#define DPHaddr (DPBaddr + 15) // Address of the Disk Parameter Header
#define SCBaddr (BDOSpage + 3) // Address of the System Control Block
#define tmpFCB (BDOSpage + 16) // Address of the temporary FCB
/* 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)
/* Definition of externs to prevent precedence compilation errors */
#ifdef __cplusplus // If building on Arduino
extern "C"
{
#endif
#ifndef RAM_FAST
extern uint8* _RamSysAddr(uint16 address);
extern void _RamWrite(uint16 address, uint8 value);
#endif
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 _puts(const char* str);
#ifdef __cplusplus // If building on Arduino
}
#endif
#endif

View file

@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#include <SdFat.h> // SDFat - Adafruit Fork
#include <PicoDVI.h>
#include "../../console.h"
#include "../../arduino_hooks.h"
#ifndef USE_DISPLAY
#define USE_DISPLAY (1)
#endif
#ifndef USE_MSC
#define USE_MSC (0)
#endif
#if USE_DISPLAY
DVItext1 display(DVI_RES_800x240p30, adafruit_feather_dvi_cfg);
#endif
#define SPI_CLOCK (20'000'000)
#define SD_CS_PIN (10)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
DedicatedSpiCard blockdevice;
FatFileSystem SD; // Filesystem object from SdFat
// =========================================================================================
// Define Board-Data
// GP25 green onboard LED
// =========================================================================================
#define LED (13)
#define LEDinv (false)
#define board_pico
#define board_analog_io
#define board_digital_io
// FUNCTIONS REQUIRED FOR USB MASS STORAGE ---------------------------------
#if USE_MSC
Adafruit_USBD_MSC usb_msc; // USB mass storage object
static bool msc_changed = true; // Is set true on filesystem changes
// Callback on READ10 command.
int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) {
return blockdevice.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize : -1;
}
// Callback on WRITE10 command.
int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) {
digitalWrite(LED_BUILTIN, HIGH);
return blockdevice.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1;
}
// Callback on WRITE10 completion.
void msc_flush_cb(void) {
blockdevice.syncBlocks(); // Sync with blockdevice
SD.cacheClear(); // Clear filesystem cache to force refresh
digitalWrite(LED_BUILTIN, LOW);
msc_changed = true;
}
#endif
#if USE_DISPLAY
uint16_t underCursor = ' ';
void putch_display(uint8_t ch) {
auto x = display.getCursorX();
auto y = display.getCursorY();
display.drawPixel(x, y, underCursor);
if(ch == 8) {
if(x > 0) {
display.setCursor(--x, y);
display.drawPixel(x, y, ' ');
}
} else {
display.write(ch);
}
x = display.getCursorX();
y = display.getCursorY();
underCursor = display.getPixel(x, y);
display.drawPixel(x, y, 0xDB);
}
#endif
uint8_t getch_serial1(void) {
while(true) {
int r = Serial1.read();
if(r != -1) {
return r;
}
}
}
bool kbhit_serial1(void) {
return Serial1.available();
}
bool port_init_early() {
#if USE_DISPLAY
vreg_set_voltage(VREG_VOLTAGE_1_20);
delay(10);
if (!display.begin()) { return false; }
_putch_hook = putch_display;
#endif
_getch_hook = getch_serial1;
_kbhit_hook = kbhit_serial1;
// USB mass storage / filesystem setup (do BEFORE Serial init)
if (!blockdevice.begin(SD_CONFIG)) { _puts("Failed to initialize SD card"); return false; }
#if USE_MSC
// Set disk vendor id, product id and revision
usb_msc.setID("Adafruit", "Internal Flash", "1.0");
// Set disk size, block size is 512 regardless of blockdevice page size
usb_msc.setCapacity(blockdevice.sectorCount(), 512);
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
usb_msc.setUnitReady(true); // MSC is ready for read/write
if (!usb_msc.begin()) {
_puts("Failed to initialize USB MSC"); return false;
}
#endif
return true;
}
bool port_flash_begin() {
if (!SD.begin(&blockdevice, true, 1)) { // Start filesystem on the blockdevice
_puts("!SD.begin()"); return false;
}
return true;
}

View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef HOST_H
#define HOST_H
uint8 hostbdos(uint16 dmaaddr) {
return(0x00);
}
#endif

View file

@ -0,0 +1,115 @@
// Copyright (c) 2016 - Marcelo Dantas
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
// Only build this code if not on Arduino
#ifndef ARDUINO
/* globals.h must be the first file included - it defines the bare essentials */
#include "globals.h"
/* Any system specific includes should go here - this will define system functions used by the abstraction */
/* all the RunCPM includes must go after the system specific includes */
/*
abstraction.h - Adds all system dependent calls and definitions needed by RunCPM
This should be the only file modified for portability. Any other file
shoud be kept the same.
*/
#ifdef _WIN32
#include "abstraction_vstudio.h"
#else
#include "abstraction_posix.h"
#endif
// AUX: device configuration
#ifdef USE_PUN
FILE* pun_dev;
int pun_open = FALSE;
#endif
// PRT: device configuration
#ifdef USE_LST
FILE* lst_dev;
int lst_open = FALSE;
#endif
#include "ram.h" // ram.h - Implements the RAM
#include "console.h" // console.h - Defines all the console abstraction functions
#include "cpu.h" // cpu.h - Implements the emulated CPU
#include "disk.h" // disk.h - Defines all the disk access abstraction functions
#include "host.h" // host.h - Custom host-specific BDOS call
#include "cpm.h" // cpm.h - Defines the CPM structures and calls
#ifdef CCP_INTERNAL
#include "ccp.h" // ccp.h - Defines a simple internal CCP
#endif
int main(int argc, char* argv[]) {
#ifdef DEBUGLOG
_sys_deletefile((uint8*)LogName);
#endif
_host_init(argc, &argv[0]);
_console_init();
_clrscr();
_puts(" CP/M Emulator v" VERSION " by Marcelo Dantas\r\n");
_puts(" Built " __DATE__ " - " __TIME__ "\r\n");
#ifdef HASLUA
_puts(" with Lua scripting support\r\n");
#endif
_puts("-----------------------------------------\r\n");
_puts("BIOS at 0x");
_puthex16(BIOSjmppage);
_puts(" - ");
_puts("BDOS at 0x");
_puthex16(BDOSjmppage);
_puts("\r\n");
_puts("CCP " CCPname " at 0x");
_puthex16(CCPaddr);
_puts("\r\n");
#if BANKS > 1
_puts("Banked Memory: ");
_puthex8(BANKS);
_puts(" banks\r\n");
#endif
while (TRUE) {
_puts(CCPHEAD);
_PatchCPM(); // Patches the CP/M entry points and other things in
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(DSKByte)); // 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
break;
#ifdef USE_PUN
if (pun_dev)
_sys_fflush(pun_dev);
#endif
#ifdef USE_LST
if (lst_dev)
_sys_fflush(lst_dev);
#endif
}
_puts("\r\n");
_console_reset();
return(0);
}
#endif

View file

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
#ifndef RAM_H
#define RAM_H
/* see main.c for definition */
#ifndef RAM_FAST
static uint8 RAM[MEMSIZE]; // Definition of the emulated RAM
uint8* _RamSysAddr(uint16 address) {
if (address < CCPaddr) {
return(&RAM[address * curBank]);
} else {
return(&RAM[address]);
}
}
uint8 _RamRead(uint16 address) {
if (address < CCPaddr) {
return(RAM[address * curBank]);
} else {
return(RAM[address]);
}
}
uint16 _RamRead16(uint16 address) {
if (address < CCPaddr) {
return(RAM[address * curBank] + (RAM[(address * curBank) + 1] << 8));
} else {
return(RAM[address] + (RAM[address + 1] << 8));
}
}
void _RamWrite(uint16 address, uint8 value) {
if (address < CCPaddr) {
RAM[address * curBank] = 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);
}
#endif
#endif

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by RunCPM.rc
//
#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
#endif

View file

@ -0,0 +1,226 @@
// SPDX-FileCopyrightText: 2023 Mockba the Borg
//
// SPDX-License-Identifier: MIT
// only AVR and ARM CPU
// #include <MemoryFree.h>
// =========================================================================================
// Guido Lehwalder's Code-Revision-Number
// =========================================================================================
#define GL_REV "GL20230303.0"
#include <SPI.h>
#include <SdFat.h> // One SD library to rule them all - Greinman SdFat from Library Manager
#include <Adafruit_SPIFlash.h>
#ifndef USE_VT100
#define USE_VT100 (0)
#endif
#if USE_VT100
#define TEXT_BOLD "\033[1m"
#define TEXT_NORMAL "\033[0m"
#else
#define TEXT_BOLD ""
#define TEXT_NORMAL ""
#endif
#include "globals.h"
// =========================================================================================
// Board definitions go into the "hardware" folder, if you use a board different than the
// Arduino DUE, choose/change a file from there and reference that file here
// =========================================================================================
// Raspberry Pi Pico - normal (LED = GPIO25)
#include "hardware/pico/feather_dvi.h"
#ifndef BOARD_TEXT
#define BOARD_TEXT USB_MANUFACTURER " " USB_PRODUCT
#endif
#include "abstraction_arduino.h"
// Raspberry Pi Pico W(iFi) (LED = GPIO32)
// #include "hardware/pico/pico_w_sd_spi.h"
// =========================================================================================
// Delays for LED blinking
// =========================================================================================
#define sDELAY 100
#define DELAY 1200
// =========================================================================================
// Serial port speed
// =========================================================================================
#define SERIALSPD 115200
// =========================================================================================
// PUN: device configuration
// =========================================================================================
#ifdef USE_PUN
File32 pun_dev;
int pun_open = FALSE;
#endif
// =========================================================================================
// LST: device configuration
// =========================================================================================
#ifdef USE_LST
File32 lst_dev;
int lst_open = FALSE;
#endif
#include "ram.h"
#include "console.h"
#include "cpu.h"
#include "disk.h"
#include "host.h"
#include "cpm.h"
#ifdef CCP_INTERNAL
#include "ccp.h"
#endif
void setup(void) {
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW ^ LEDinv);
// =========================================================================================
// Serial Port Definition
// =========================================================================================
// Serial =USB / Serial1 =UART0/COM1 / Serial2 =UART1/COM2
Serial1.setRX(1); // Pin 2
Serial1.setTX(0); // Pin 1
Serial1.begin(SERIALSPD);
Serial2.setRX(5); // Pin 7
Serial2.setTX(4); // Pin 6
Serial2.begin(SERIALSPD);
// or
// Serial1.setRX(17); // Pin 22
// Serial1.setTX(16); // Pin 21
// Serial2.setRX(21); // Pin 27
// Serial2.setTX(20); // Pin 26
// =========================================================================================
if (!port_init_early()) {
return;
}
#if defined(WAIT_SERIAL)
// _clrscr();
// _puts("Opening serial-port...\r\n");
Serial.begin(SERIALSPD);
while (!Serial) { // Wait until serial is connected
digitalWrite(LED, HIGH ^ LEDinv);
delay(sDELAY);
digitalWrite(LED, LOW ^ LEDinv);
delay(DELAY);
}
#endif
#ifdef DEBUGLOG
_sys_deletefile((uint8 *)LogName);
#endif
// =========================================================================================
// Printing the Startup-Messages
// =========================================================================================
_clrscr();
// if (bootup_press == 1)
// { _puts("Recognized " TEXT_BOLD "#" TEXT_NORMAL " key as pressed! :)\r\n\r\n");
// }
_puts("CP/M Emulator " TEXT_BOLD "v" VERSION "" TEXT_NORMAL " by " TEXT_BOLD "Marcelo Dantas" TEXT_NORMAL "\r\n");
_puts("----------------------------------------------\r\n");
_puts(" running on [" TEXT_BOLD BOARD_TEXT TEXT_NORMAL "]\r\n");
_puts("----------------------------------------------\r\n");
_puts("BIOS at [" TEXT_BOLD "0x");
_puthex16(BIOSjmppage);
// _puts(" - ");
_puts("" TEXT_NORMAL "]\r\n");
_puts("BDOS at [" TEXT_BOLD "0x");
_puthex16(BDOSjmppage);
_puts("" TEXT_NORMAL "]\r\n");
_puts("CCP " CCPname " at [" TEXT_BOLD "0x");
_puthex16(CCPaddr);
_puts("" TEXT_NORMAL "]\r\n");
#if BANKS > 1
_puts("Banked Memory [" TEXT_BOLD "");
_puthex8(BANKS);
_puts("" TEXT_NORMAL "]banks\r\n");
#endif
// Serial.printf("Free Memory [" TEXT_BOLD "%d bytes" TEXT_NORMAL "]\r\n", freeMemory());
_puts("CPU-Clock [" TEXT_BOLD);
_putdec((clock_get_hz( clk_sys ) + 500'000) / 1'000'000);
_puts(TEXT_NORMAL "] MHz\r\n");
_puts("Init Storage [ " TEXT_BOLD "");
if (port_flash_begin()) {
_puts("OK " TEXT_NORMAL "]\r\n");
_puts("----------------------------------------------");
if (VersionCCP >= 0x10 || SD.exists(CCPname)) {
while (true) {
_puts(CCPHEAD);
_PatchCPM();
Status = 0;
#ifndef CCP_INTERNAL
if (!_RamLoad((char *)CCPname, CCPaddr)) {
_puts("Unable to load the CCP.\r\nCPU halted.\r\n");
break;
}
Z80reset();
SET_LOW_REGISTER(BC, _RamRead(DSKByte));
PC = CCPaddr;
Z80run();
#else
_ccp();
#endif
if (Status == 1)
break;
#ifdef USE_PUN
if (pun_dev)
_sys_fflush(pun_dev);
#endif
#ifdef USE_LST
if (lst_dev)
_sys_fflush(lst_dev);
#endif
}
} else {
_puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n");
}
} else {
_puts("ERR " TEXT_NORMAL "]\r\nUnable to initialize SD card.\r\nCPU halted.\r\n");
}
}
// if loop is reached, blink LED forever to signal error
void loop(void) {
digitalWrite(LED, HIGH^LEDinv);
delay(DELAY);
digitalWrite(LED, LOW^LEDinv);
delay(DELAY);
digitalWrite(LED, HIGH^LEDinv);
delay(DELAY);
digitalWrite(LED, LOW^LEDinv);
delay(DELAY * 4);
}