453 lines
9 KiB
C
453 lines
9 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <glob.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <errno.h>
|
|
#include "intel_8080_emulator.h"
|
|
#include "globals.h"
|
|
|
|
#define FBASE 0xff00
|
|
#define COLDSTART (FBASE + 4) /* see bdos.asm */
|
|
#define CBASE (FBASE - (7*1024))
|
|
|
|
static uint16_t dma;
|
|
static int exitcode = 0;
|
|
|
|
struct fcb
|
|
{
|
|
cpm_filename_t filename; /* includes drive */
|
|
uint8_t extent;
|
|
uint8_t s1;
|
|
uint8_t s2;
|
|
uint8_t recordcount;
|
|
uint8_t d[16];
|
|
uint8_t currentrecord;
|
|
uint8_t r[3];
|
|
};
|
|
|
|
static void bios_getchar(void);
|
|
|
|
static uint16_t get_de(void)
|
|
{
|
|
return i8080_read_reg16(DE);
|
|
}
|
|
|
|
static uint8_t get_c(void)
|
|
{
|
|
return i8080_read_reg8(C);
|
|
}
|
|
|
|
static uint8_t get_d(void)
|
|
{
|
|
return i8080_read_reg8(D);
|
|
}
|
|
|
|
static uint8_t get_e(void)
|
|
{
|
|
return i8080_read_reg8(E);
|
|
}
|
|
|
|
static uint8_t get_a(void)
|
|
{
|
|
return i8080_read_reg8(A);
|
|
}
|
|
|
|
static void set_a(uint8_t a)
|
|
{
|
|
i8080_write_reg8(A, a);
|
|
}
|
|
|
|
static void set_result(uint16_t result)
|
|
{
|
|
i8080_write_reg16(HL, result);
|
|
|
|
i8080_write_reg8(A, result);
|
|
uint8_t f = i8080_read_reg8(FLAGS);
|
|
f &= ~(1<<6) & ~(1<<7);
|
|
if (!result)
|
|
f |= 1<<6;
|
|
if (result & 0x80)
|
|
f |= 1<<7;
|
|
i8080_write_reg8(FLAGS, f);
|
|
|
|
i8080_write_reg8(B, result);
|
|
}
|
|
|
|
void bios_coldboot(void)
|
|
{
|
|
memcpy(&ram[FBASE], bdos_data, bdos_len);
|
|
i8080_write_reg16(PC, COLDSTART);
|
|
}
|
|
|
|
static void bios_warmboot(void)
|
|
{
|
|
int word;
|
|
int offset = 1;
|
|
dma = 0x0080;
|
|
|
|
if (!user_command_line[0])
|
|
fatal("running the CCP isn't supported");
|
|
|
|
static bool terminate_next_time = false;
|
|
if (terminate_next_time)
|
|
exit(exitcode);
|
|
terminate_next_time = true;
|
|
|
|
i8080_write_reg16(PC, 0x0100);
|
|
|
|
/* Push the magic exit code onto the stack. */
|
|
i8080_write_reg16(SP, FBASE-4);
|
|
ram[FBASE-4] = (FBASE-2) & 0xFF;
|
|
ram[FBASE-3] = (FBASE-2) >> 8;
|
|
ram[FBASE-2] = 0xD3; // out (??), a
|
|
ram[FBASE-1] = 0xFE; // exit emulator
|
|
|
|
int fd = open(user_command_line[0], O_RDONLY);
|
|
if (fd == -1)
|
|
fatal("couldn't open program: %s", strerror(errno));
|
|
read(fd, &ram[0x0100], 0xFE00);
|
|
close(fd);
|
|
|
|
for (word = 1; user_command_line[word]; word++)
|
|
{
|
|
if (word > 1)
|
|
{
|
|
ram[0x0080 + offset] = ' ';
|
|
offset++;
|
|
}
|
|
|
|
const char* pin = user_command_line[word];
|
|
while (*pin)
|
|
{
|
|
if (offset > 125)
|
|
fatal("user command line too long");
|
|
ram[0x0080 + offset] = toupper(*pin++);
|
|
offset++;
|
|
}
|
|
}
|
|
ram[0x0080] = offset;
|
|
ram[0x0080+offset] = 0;
|
|
}
|
|
|
|
static void bios_const(void)
|
|
{
|
|
struct pollfd pollfd = { 0, POLLIN, 0 };
|
|
poll(&pollfd, 1, 0);
|
|
set_a((pollfd.revents & POLLIN) ? 0xff : 0);
|
|
}
|
|
|
|
static void bios_getchar(void)
|
|
{
|
|
char c = 0;
|
|
(void) read(0, &c, 1);
|
|
if (c == '\n')
|
|
c = '\r';
|
|
set_a(c);
|
|
}
|
|
|
|
static void bios_putchar(void)
|
|
{
|
|
char c = get_c();
|
|
(void) write(1, &c, 1);
|
|
}
|
|
|
|
static void bios_entry(uint8_t bios_call)
|
|
{
|
|
switch (bios_call)
|
|
{
|
|
case 0: bios_coldboot(); return;
|
|
case 1: bios_warmboot(); return;
|
|
case 2: bios_const(); return; // const
|
|
case 3: bios_getchar(); return; // conin
|
|
case 4: bios_putchar(); return; // conout
|
|
|
|
case 0xFE: exit(0); // magic emulator exit
|
|
}
|
|
|
|
showregs();
|
|
fatal("unimplemented bios entry %d", bios_call);
|
|
}
|
|
|
|
static void bdos_getchar(void)
|
|
{
|
|
bios_getchar();
|
|
set_result(get_a());
|
|
}
|
|
|
|
static void bdos_putchar(void)
|
|
{
|
|
uint8_t c = get_e();
|
|
(void) write(1, &c, 1);
|
|
}
|
|
|
|
static void bdos_consoleio(void)
|
|
{
|
|
uint8_t c = get_e();
|
|
if (c == 0xff)
|
|
{
|
|
bios_const();
|
|
if (get_a() == 0xff)
|
|
bios_getchar();
|
|
}
|
|
else
|
|
bdos_putchar();
|
|
}
|
|
|
|
static void bdos_printstring(void)
|
|
{
|
|
uint16_t de = get_de();
|
|
for (;;)
|
|
{
|
|
uint8_t c = ram[de++];
|
|
if (c == '$')
|
|
break;
|
|
(void) write(1, &c, 1);
|
|
}
|
|
}
|
|
|
|
static void bdos_consolestatus(void)
|
|
{
|
|
bios_const();
|
|
set_result(get_a());
|
|
}
|
|
|
|
void bdos_readline(void)
|
|
{
|
|
fflush(stdout);
|
|
|
|
uint16_t de = i8080_read_reg16(DE);
|
|
uint8_t maxcount = ram[de+0];
|
|
int count = read(0, &ram[de+2], maxcount);
|
|
if ((count > 0) && (ram[de+2+count-1] == '\n'))
|
|
count--;
|
|
ram[de+1] = count;
|
|
set_result(count);
|
|
}
|
|
|
|
static struct fcb* fcb_at(uint16_t address)
|
|
{
|
|
struct fcb* fcb = (struct fcb*) &ram[address];
|
|
|
|
/* Autoselect the current drive. */
|
|
if (fcb->filename.drive == 0)
|
|
fcb->filename.drive = ram[4] + 1;
|
|
|
|
return fcb;
|
|
}
|
|
|
|
static struct fcb* find_fcb(void)
|
|
{
|
|
return fcb_at(i8080_read_reg16(DE));
|
|
}
|
|
|
|
static int get_current_record(struct fcb* fcb)
|
|
{
|
|
return (fcb->extent * 128) + fcb->currentrecord;
|
|
}
|
|
|
|
static void set_current_record(struct fcb* fcb, int record, int total)
|
|
{
|
|
int extents = total / 128;
|
|
fcb->extent = record / 128;
|
|
if (fcb->extent < extents)
|
|
fcb->recordcount = 128;
|
|
else
|
|
fcb->recordcount = total % 128;
|
|
fcb->currentrecord = record % 128;
|
|
}
|
|
|
|
static void bdos_resetdisk(void)
|
|
{
|
|
dma = 0x0080;
|
|
ram[4] = 0; /* select drive A */
|
|
set_result(0xff);
|
|
}
|
|
|
|
static void bdos_selectdisk(void)
|
|
{
|
|
uint8_t e = get_e();
|
|
ram[4] = e;
|
|
}
|
|
|
|
static void bdos_getdisk(void)
|
|
{
|
|
set_result(ram[4]);
|
|
}
|
|
|
|
static void bdos_openfile(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
struct file* f = file_open(&fcb->filename);
|
|
if (f)
|
|
{
|
|
set_current_record(fcb, 0, file_getrecordcount(f));
|
|
set_result(0);
|
|
}
|
|
else
|
|
set_result(0xff);
|
|
}
|
|
|
|
static void bdos_makefile(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
struct file* f = file_create(&fcb->filename);
|
|
if (f)
|
|
{
|
|
set_current_record(fcb, 0, 0);
|
|
set_result(0);
|
|
}
|
|
else
|
|
set_result(0xff);
|
|
}
|
|
|
|
static void bdos_closefile(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
struct file* f = file_open(&fcb->filename);
|
|
if (file_getrecordcount(f) < 128)
|
|
file_setrecordcount(f, fcb->recordcount);
|
|
int result = file_close(&fcb->filename);
|
|
set_result(result ? 0xff : 0);
|
|
}
|
|
|
|
static void bdos_renamefile(void)
|
|
{
|
|
struct fcb* srcfcb = fcb_at(i8080_read_reg16(DE));
|
|
struct fcb* destfcb = fcb_at(i8080_read_reg16(DE)+16);
|
|
int result = file_rename(&srcfcb->filename, &destfcb->filename);
|
|
set_result(result ? 0xff : 0);
|
|
}
|
|
|
|
static void bdos_findnext(void)
|
|
{
|
|
struct fcb* fcb = (struct fcb*) &ram[dma];
|
|
memset(fcb, 0, sizeof(struct fcb));
|
|
int i = file_findnext(&fcb->filename);
|
|
set_result(i ? 0xff : 0);
|
|
}
|
|
|
|
static void bdos_findfirst(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
int i = file_findfirst(&fcb->filename);
|
|
if (i == 0)
|
|
bdos_findnext();
|
|
else
|
|
set_result(i ? 0xff : 0);
|
|
}
|
|
|
|
static void bdos_deletefile(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
int i = file_delete(&fcb->filename);
|
|
set_result(i ? 0xff : 0);
|
|
}
|
|
|
|
typedef int readwrite_cb(struct file* f, uint8_t* ptr, uint16_t record);
|
|
|
|
static void bdos_readwritesequential(readwrite_cb* readwrite)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
|
|
struct file* f = file_open(&fcb->filename);
|
|
int here = get_current_record(fcb);
|
|
int i = readwrite(f, &ram[dma], here);
|
|
set_current_record(fcb, here+1, file_getrecordcount(f));
|
|
if (i == -1)
|
|
set_result(0xff);
|
|
else if (i == 0)
|
|
set_result(1);
|
|
else
|
|
set_result(0);
|
|
}
|
|
|
|
static void bdos_readwriterandom(readwrite_cb* readwrite)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
|
|
uint16_t record = fcb->r[0] + (fcb->r[1]<<8);
|
|
struct file* f = file_open(&fcb->filename);
|
|
int i = readwrite(f, &ram[dma], record);
|
|
set_current_record(fcb, record, file_getrecordcount(f));
|
|
if (i == -1)
|
|
set_result(0xff);
|
|
else if (i == 0)
|
|
set_result(1);
|
|
else
|
|
set_result(0);
|
|
}
|
|
|
|
static void bdos_filelength(void)
|
|
{
|
|
struct fcb* fcb = find_fcb();
|
|
struct file* f = file_open(&fcb->filename);
|
|
|
|
int length = file_getrecordcount(f);
|
|
fcb->r[0] = length;
|
|
fcb->r[1] = length>>8;
|
|
fcb->r[2] = length>>16;
|
|
}
|
|
|
|
static void bdos_getsetuser(void)
|
|
{
|
|
if (get_e() == 0xff)
|
|
set_result(0);
|
|
}
|
|
|
|
static void bdos_entry(uint8_t bdos_call)
|
|
{
|
|
switch (bdos_call)
|
|
{
|
|
case 1: bdos_getchar(); return;
|
|
case 2: bdos_putchar(); return;
|
|
case 6: bdos_consoleio(); return;
|
|
case 9: bdos_printstring(); return;
|
|
case 10: bdos_readline(); return;
|
|
case 11: bdos_consolestatus(); return;
|
|
case 12: set_result(0x0022); return; // get CP/M version
|
|
case 13: bdos_resetdisk(); return; // reset disk system
|
|
case 14: bdos_selectdisk(); return; // select disk
|
|
case 15: bdos_openfile(); return;
|
|
case 16: bdos_closefile(); return;
|
|
case 17: bdos_findfirst(); return;
|
|
case 18: bdos_findnext(); return;
|
|
case 19: bdos_deletefile(); return;
|
|
case 20: bdos_readwritesequential(file_read); return;
|
|
case 21: bdos_readwritesequential(file_write); return;
|
|
case 22: bdos_makefile(); return;
|
|
case 23: bdos_renamefile(); return;
|
|
case 24: set_result(0xffff); return; // get login vector
|
|
case 25: bdos_getdisk(); return; // get current disk
|
|
case 26: dma = get_de(); return; // set DMA
|
|
case 27: set_result(0); return; // get allocation vector
|
|
case 29: set_result(0x0000); return; // get read-only vector
|
|
case 31: set_result(0); return; // get disk parameter block
|
|
case 32: bdos_getsetuser(); return;
|
|
case 33: bdos_readwriterandom(file_read); return;
|
|
case 34: bdos_readwriterandom(file_write); return;
|
|
case 35: bdos_filelength(); return;
|
|
case 40: bdos_readwriterandom(file_write); return;
|
|
case 45: return; // set hardware error action
|
|
case 108: exitcode = get_d(); return; // set exit code
|
|
}
|
|
|
|
showregs();
|
|
fatal("unimplemented bdos entry %d", bdos_call);
|
|
}
|
|
|
|
void biosbdos_entry(int syscall)
|
|
{
|
|
if (syscall == 0xff)
|
|
bdos_entry(i8080_read_reg16(BC));
|
|
else
|
|
bios_entry(syscall);
|
|
}
|
|
|