commit
2fb8574af7
20 changed files with 9657 additions and 0 deletions
248
runcpm-rp2040-dvi-usb/keyboard-copro/keyboard-copro.ino
Normal file
248
runcpm-rp2040-dvi-usb/keyboard-copro/keyboard-copro.ino
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup1() {
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
while ( !Serial ) delay(10); // wait for native usb
|
||||||
|
Serial.println("Core1 setup to run TinyUSB host with pio-usb");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB
|
||||||
|
uint32_t cpu_hz = clock_get_hz(clk_sys);
|
||||||
|
if ( cpu_hz != 120000000UL && cpu_hz != 240000000UL ) {
|
||||||
|
while ( !Serial ) delay(10); // wait for native usb
|
||||||
|
Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz);
|
||||||
|
Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n");
|
||||||
|
while (1) delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int old_ascii = -1;
|
||||||
|
uint32_t repeat_timeout;
|
||||||
|
const uint32_t repeat_time = 150;
|
||||||
|
|
||||||
|
void send_ascii(uint8_t code) {
|
||||||
|
old_ascii = code;
|
||||||
|
repeat_timeout = millis() + repeat_time;
|
||||||
|
if (code > 32 && code < 127) {
|
||||||
|
Serial.printf("'%c'\r\n", code);
|
||||||
|
} else {
|
||||||
|
Serial.printf("'\\x%02x'\r\n", code);
|
||||||
|
}
|
||||||
|
pio_serial.write(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop1()
|
||||||
|
{
|
||||||
|
uint32_t now = millis();
|
||||||
|
uint32_t deadline = repeat_timeout - now;
|
||||||
|
if (old_ascii >= 0 && deadline > INT32_MAX) {
|
||||||
|
repeat_timeout += repeat_time;
|
||||||
|
deadline = repeat_timeout - now;
|
||||||
|
send_ascii(old_ascii);
|
||||||
|
} 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 */
|
||||||
|
"\3\4\2\1", /* 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
bool caps = old_report.reserved & 1;
|
||||||
|
bool num = old_report.reserved & 2;
|
||||||
|
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) {
|
||||||
|
num = !num;
|
||||||
|
} else if (keycode == HID_KEY_CAPS_LOCK) {
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t leds = (caps | (num << 1));
|
||||||
|
if (leds != old_report.reserved) {
|
||||||
|
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*/, &leds, sizeof(leds));
|
||||||
|
Serial.printf("set_report() -> %d\n", (int)r);
|
||||||
|
}
|
||||||
|
old_report = report;
|
||||||
|
old_report.reserved = leds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
runcpm-rp2040-dvi-usb/runcpm-pico/LICENSE
Normal file
21
runcpm-rp2040-dvi-usb/runcpm-pico/LICENSE
Normal 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.
|
||||||
31
runcpm-rp2040-dvi-usb/runcpm-pico/README.md
Normal file
31
runcpm-rp2040-dvi-usb/runcpm-pico/README.md
Normal 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".
|
||||||
531
runcpm-rp2040-dvi-usb/runcpm-pico/abstraction_arduino.h
Normal file
531
runcpm-rp2040-dvi-usb/runcpm-pico/abstraction_arduino.h
Normal 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();
|
||||||
|
Serial.write(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
|
||||||
10
runcpm-rp2040-dvi-usb/runcpm-pico/arduino_hooks.c
Normal file
10
runcpm-rp2040-dvi-usb/runcpm-pico/arduino_hooks.c
Normal 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);
|
||||||
|
|
||||||
13
runcpm-rp2040-dvi-usb/runcpm-pico/arduino_hooks.h
Normal file
13
runcpm-rp2040-dvi-usb/runcpm-pico/arduino_hooks.h
Normal 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);
|
||||||
|
|
||||||
891
runcpm-rp2040-dvi-usb/runcpm-pico/ccp.h
Normal file
891
runcpm-rp2040-dvi-usb/runcpm-pico/ccp.h
Normal 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
|
||||||
64
runcpm-rp2040-dvi-usb/runcpm-pico/console.h
Normal file
64
runcpm-rp2040-dvi-usb/runcpm-pico/console.h
Normal 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
|
||||||
1782
runcpm-rp2040-dvi-usb/runcpm-pico/cpm.h
Normal file
1782
runcpm-rp2040-dvi-usb/runcpm-pico/cpm.h
Normal file
File diff suppressed because it is too large
Load diff
4637
runcpm-rp2040-dvi-usb/runcpm-pico/cpu.h
Normal file
4637
runcpm-rp2040-dvi-usb/runcpm-pico/cpu.h
Normal file
File diff suppressed because it is too large
Load diff
635
runcpm-rp2040-dvi-usb/runcpm-pico/disk.h
Normal file
635
runcpm-rp2040-dvi-usb/runcpm-pico/disk.h
Normal 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
|
||||||
252
runcpm-rp2040-dvi-usb/runcpm-pico/globals.h
Normal file
252
runcpm-rp2040-dvi-usb/runcpm-pico/globals.h
Normal 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 [\e[1mv" VERSION "\e[0m] => CCP:[\e[1m" CCPname "\e[0m] TPA:[\e[1m" STR(TPASIZE) "K\e[0m]\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
|
||||||
126
runcpm-rp2040-dvi-usb/runcpm-pico/hardware/pico/feather_dvi.h
Normal file
126
runcpm-rp2040-dvi-usb/runcpm-pico/hardware/pico/feather_dvi.h
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
// 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 <Adafruit_TinyUSB.h>
|
||||||
|
#include <PicoDVI.h>
|
||||||
|
#include "../../console.h"
|
||||||
|
#include "../../arduino_hooks.h"
|
||||||
|
|
||||||
|
#undef USE_DISPLAY
|
||||||
|
#define USE_DISPLAY (1)
|
||||||
|
|
||||||
|
#if USE_DISPLAY
|
||||||
|
DVItext1 display(DVI_RES_800x240p60, 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
|
||||||
|
Adafruit_USBD_MSC usb_msc; // USB mass storage object
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// 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 ---------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _puthex32(uint32_t x) {
|
||||||
|
_puthex16(x >> 16);
|
||||||
|
_puthex16(x & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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_30);
|
||||||
|
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("!blockdevice.begin()"); return false; }
|
||||||
|
// 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("!usb_msc.begin()"); return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool port_flash_begin() {
|
||||||
|
if (!SD.begin(&blockdevice, true, 1)) { // Start filesystem on the blockdevice
|
||||||
|
_puts("!SD.begin()"); return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
12
runcpm-rp2040-dvi-usb/runcpm-pico/host.h
Normal file
12
runcpm-rp2040-dvi-usb/runcpm-pico/host.h
Normal 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
|
||||||
115
runcpm-rp2040-dvi-usb/runcpm-pico/main.c
Normal file
115
runcpm-rp2040-dvi-usb/runcpm-pico/main.c
Normal 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
|
||||||
52
runcpm-rp2040-dvi-usb/runcpm-pico/ram.h
Normal file
52
runcpm-rp2040-dvi-usb/runcpm-pico/ram.h
Normal 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
|
||||||
20
runcpm-rp2040-dvi-usb/runcpm-pico/resource.h
Normal file
20
runcpm-rp2040-dvi-usb/runcpm-pico/resource.h
Normal 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
|
||||||
217
runcpm-rp2040-dvi-usb/runcpm-pico/runcpm-pico.ino
Normal file
217
runcpm-rp2040-dvi-usb/runcpm-pico/runcpm-pico.ino
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// only AVR and ARM CPU
|
||||||
|
// #include <MemoryFree.h>
|
||||||
|
|
||||||
|
#include "globals.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>
|
||||||
|
#include <Adafruit_TinyUSB.h>
|
||||||
|
|
||||||
|
#define TEXT_BOLD "\033[1m"
|
||||||
|
#define TEXT_NORMAL "\033[0m"
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// 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\e[0m\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);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue