Yet another CP/M emulator part 1 of 3
D'Arcy J.M. Cain
darcy at druid.uucp
Fri Nov 16 07:36:12 AEST 1990
As promised in comp.os.cpm here is my CP/M emulator for UNIX systems. It
isn't quite complete but since many people seem to be trying to write this
sort of program lately I am leaving this to expand/enhance/fix/rip off.
#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 11/15/1990 20:30 UTC by darcy at druid
# Source directory /usr/darcy/work/Cpm
#
# existing files WILL be overwritten
#
# This is part 1 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 2497 -rw------- readme
# 1032 -rw-r----- Makefile
# 3852 -rw-r----- cpm-i386.h
# 26758 -rw-r----- cpm.c
# 7099 -rw-r----- dasm.c
# 32025 -rw-r----- decode.c
# 381 -rw-r----- mk_tests.c
# 127 -rw-r----- mkbin.c
#
if test -r _shar_seq_.tmp; then
echo 'Must unpack archives in sequence!'
echo Please unpack part `cat _shar_seq_.tmp` next
exit 1
fi
# ============= readme ==============
sed 's/^X//' << 'SHAR_EOF' > 'readme' &&
This is a quick overview of my CP/M emulator for Unix. I apologize if
it isn't completely up to date as the program is still evolving.
X
The system decodes most of the Z80 instruction set. mostly the I/O is
not implemented. Whenever the PC is >= 0xfec0 it executes a return no
matter what is in RAM at that location. Before calling the routine for
each instruction I look at the PC and end the program if it is zero or
perform a BDOS function if it is 0xfec0 or BIOS if it is >= 0xff00. Since
the emulator ignores the contents of memory as far as instruction decoding
is concerned, The system has the interesting property of being able to use
all of memory except the first 0x100 bytes for user programs by putting the
stack at 0xfffe. In fact the default stack when a program is run is set
to 0xfff0.
X
I use Unix commands to simulate some CP/M commands such as DIR, REN, TYPE
and handle things such as SAVE internally. I use the Unix file system for
drives. The user defines Unix directories to CP/M drive mappings. This
allows CP/M to use the same file system as the Unix but does force CP/M
conventions on the file names such as upper case, must have 1 and only 1
period, etc. It is easy to handle though since one can always link files
as necessary, even from CP/M since I also include a '!' command to do Unix
commands from within CP/M.
X
Programs that use IN and OUT opcodes will fail. All I/O must go through
the BDOS or the BIOS. The user can set up the following devices: Screen
(CON out), keyboard (CON in), RDR, PUN and LST. The default for screen
and keyboard is stdout and stdin. The user can set up Unix files for any
of these devices. The file of course can be an actual file, a device or even
a pipe to a command, even another CP/M command that has the keyboard reading
from the same pipe.
X
Mostly I tried for speed except where speedup would depend on the program
running on a specific processor. The main part of the decoding module is
a 256 entry switch table with sub-switches for the Z80 extensions.
X
It's not quite finished yet but I am running simple programs with it now.
What would be nice would be some sort of test suite. Anyone know of any?
X
As for copyright, if you use it say where you got it and send me any fixes
or enhancements if you feel like it. It's free and you get what you pay
for. Don't blame me if it breaks anything or doesn't do what you expect.
(In case it isn't obvious I am not a lia^H^Hawyer.)
X
D'Arcy J.M. Cain
UUCP: darcy at druid
X
SHAR_EOF
chmod 0600 readme ||
echo 'restore of readme failed'
Wc_c="`wc -c < 'readme'`"
test 2497 -eq "$Wc_c" ||
echo 'readme: original size 2497, current size' "$Wc_c"
# ============= Makefile ==============
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
X
# Makefile for cpm
# Written by D'Arcy J.M. Cain
#
X
DIST = readme Makefile cpm-i386.h cpm.c dasm.c decode.c mk_tests.c mkbin.c
NAME = cpm
CFLAGS= -O -Wall
BINARIES = cpm dcpm tcpm mk_tests
BIN = /usr/lbin
LBINS = $(foreach i, $(BINARIES), $(BIN)/$i)
X
all: $(BINARIES)
X
clean:
X rm -f *.o *.obj core
X
clobber: clean
X rm -f $(BINARIES) $(NAME).0?
X
install: all
X rm -f $(LBINS1)
X chmod 711 $(BINARIES)
X ln $(BINARIES) $(BIN)
X
$(NAME).01: $(DIST)
X shar -L30 -vxf -o $(NAME) $^
X
gen: $(NAME).gen
X
$(NAME).gen: $(DIST)
X mkscript $^ > $(NAME).gen
X
shar: $(NAME).01
X
strip: $(BINARIES)
X strip $(BINARIES)
X
mcs: $(BINARIES)
X mcs -d $(BINARIES)
X
cpm: cpm.o decode.o dasm.o
X $(CC) $(CFLAGS) cpm.o decode.o dasm.o -o cpm
X
tcpm: tcpm.o decode.o dasm.o
X $(CC) $(CFLAGS) tcpm.o decode.o dasm.o -o tcpm
X
tcpm.o: cpm.c
X $(CC) $(CFLAGS) -DCOMPILE_TEST -c cpm.c -o tcpm.o
X
dcpm: dcpm.o decode.o dasm.o
X $(CC) $(CFLAGS) dcpm.o decode.o dasm.o -o dcpm
X
dcpm.o: cpm.c
X $(CC) $(CFLAGS) -DCPM_DEBUG -c cpm.c -o dcpm.o
X
cpm.o decode.o dasm.o: cpm.h
SHAR_EOF
chmod 0640 Makefile ||
echo 'restore of Makefile failed'
Wc_c="`wc -c < 'Makefile'`"
test 1032 -eq "$Wc_c" ||
echo 'Makefile: original size 1032, current size' "$Wc_c"
# ============= cpm-i386.h ==============
sed 's/^X//' << 'SHAR_EOF' > 'cpm-i386.h' &&
/*
cpm-i386
X
Written by D'Arcy J.M. Cain
darcy at druid
X
This file is the header used by the CP/M emulator when running under
the following machine(s):
X
X System V Rel 3.2 on Intel 80386 processor
X
Link this file to cpm.h on the above system. Other entries will be added
as they are tested.
X
To use this program on systems which differ from the above in significant
ways, a header must be created such that the Z80 registers can be accessed
using the following register names:
X A, B, C, D, E, H, L, BC, DE, HL, IX, IY, SP, PC, AF and FLAGS
In addition the following flags sould be available:
X CARRY, BCD, PARITY, HALF_CARRY, ZERO and SIGN
Also the HL, SP, IX and IY registers should have a H and L version for
accessing half-words and TEMP should be a scratch variable accessed the
same way. See below for examples.
X
There should also be a variable IFF and an array of 0x10000 (65,536) bytes
called ram.
X
The two variables psw_bank and gr_bank should allow register context
switching for the AF register and the general registers
X
*/
X
typedef unsigned char byte;
typedef unsigned short word;
/* We use unsigned short to guarantee two byte words and roll-over */
X
#define lonyb(v) (v & 0xf)
#define hinyb(v) ((v >> 4) & 0xf)
#define when break; case
X
typedef union {
X word af;
X struct {
X byte flags, a;
X } b;
X struct {
X unsigned int carry:1;
X unsigned int bcd:1;
X unsigned int parity:1;
X unsigned int x1:1;
X unsigned int half:1;
X unsigned int x2:1;
X unsigned int zero:1;
X unsigned int sign:1;
X } bits;
} ACC;
X
typedef union {
X struct {
X word bc;
X word de;
X word hl;
X } w;
X struct {
X byte c, b;
X byte e, d;
X byte l, h;
X } b;
} GR;
X
typedef union {
X unsigned char half[2];
X unsigned short whole;
} REG;
X
#define TEMPH (reg.half[1])
#define TEMPL (reg.half[0])
#define TEMP (reg.whole)
#define SPH (sp.half[1])
#define SPL (sp.half[0])
#define SP (sp.whole)
#define IXH (ix.half[1])
#define IXL (ix.half[0])
#define IX (ix.whole)
#define IYH (iy.half[1])
#define IYL (iy.half[0])
#define IY (iy.whole)
X
#ifdef CPM_DATA
ACC acc[2];
GR gr[2];
word PC;
byte R, IV;
int gr_bank = 0, acc_bank = 0, IFF = 1;
REG reg, sp, ix, iy;
X
#ifdef COMPILE_TEST
byte ram[0x10000] = {
X 0x00, /* 0000: nop */
X 0x21, 0x00, 0x10, /* 0001: ld hl, 1000h */
X 0xe5, /* 0004: push hl */
X 0x3e, 0xbb, /* 0005: ld a, 0bbh */
X 0x87, /* 0007: add a, a */
X 0x8f, /* 0008: adc a, a */
X 0x3d, /* 0009: dec a */
X 0x2f, /* 000a: cpl */
X 0xb8, /* 000b: cp b */
X 0xcc, 0x14, 0x00, /* 000c: call z, 0014h */
X 0xbf, /* 000f: cp a */
X 0xcc, 0x14, 0x00, /* 0010: call z, 0014h */
X 0x00, /* 0013: nop */
X 0xc9, /* 0014: ret */
X 0x00 }; /* 0015: nop */
#define TEST_SIZE 24
#else
byte ram[0x10000];
#endif
X
byte page_zero[] = {
X 0xc3, 0x03, 0xff, /* JP BIOS+3 */
X 0x00, 0x00, /* reserved */
X 0xc3, 0xc0, 0xfe, /* JP BDOS */
};
X
#else
extern ACC acc[];
extern GR gr[];
extern word PC;
extern byte R, IV, ram[];
extern int gr_bank, acc_bank, IFF;
extern REG reg, sp, ix, iy;
#endif
X
#define AF (acc[acc_bank].af)
#define A (acc[acc_bank].b.a)
#define FLAGS (acc[acc_bank].b.flags)
#define CARRY (acc[acc_bank].bits.carry)
#define BCD (acc[acc_bank].bits.bcd)
#define PARITY (acc[acc_bank].bits.parity)
#define OVERFLOW (acc[acc_bank].bits.parity)
#define HALF_CARRY (acc[acc_bank].bits.half)
#define ZERO (acc[acc_bank].bits.zero)
#define SIGN (acc[acc_bank].bits.sign)
X
#define B (gr[gr_bank].b.b)
#define C (gr[gr_bank].b.c)
#define D (gr[gr_bank].b.d)
#define E (gr[gr_bank].b.e)
#define H (gr[gr_bank].b.h)
#define L (gr[gr_bank].b.l)
#define BC (gr[gr_bank].w.bc)
#define DE (gr[gr_bank].w.de)
#define HL (gr[gr_bank].w.hl)
X
#define BDOS 0xfec0
#define BIOS 0xff00
#define bios(x) (BIOS + (x * 3))
X
#define MAX_DRIVES 16
X
int decode(void);
const char *dasm(const byte *buf);
SHAR_EOF
chmod 0640 cpm-i386.h ||
echo 'restore of cpm-i386.h failed'
Wc_c="`wc -c < 'cpm-i386.h'`"
test 3852 -eq "$Wc_c" ||
echo 'cpm-i386.h: original size 3852, current size' "$Wc_c"
# ============= cpm.c ==============
sed 's/^X//' << 'SHAR_EOF' > 'cpm.c' &&
/*
cpm
X
CP/M emulator.
Written by D'Arcy J.M. Cain
darcy at druid
X
*/
X
#define CPM_DATA
X
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <ctype.h>
#include <termio.h>
#include <io.h>
#include <signal.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include "cpm.h"
X
#define FILLER_SIZE (16 - sizeof(FILE *))
X
#ifdef DEBUG
#define debug(x) fprintf(stderr, "%s: %4d %s\n", __FILE__, __LINE__, x)
#else
#define debug(x)
#endif
X
typedef struct {
X byte dr; /* drive: 0 = default, 1 = A, 2 = B, etc. */
X char name[8]; /* file name up to 8 characters */
X char typ[3]; /* file type up to 3 characters */
X byte ex, s1, s2, rc;
X FILE *fp; /* Unix file pointer */
X byte filler[FILLER_SIZE];
X byte cr, r0, r1, r2;
} FCB;
X
#ifdef CPM_DEBUG
#define dump_registers(x) \
X fprintf(x, reg_dump, A, BC, DE, HL, SP, PC,\
X SIGN ? 'S' : '-',\
X ZERO ? 'Z' : '-',\
X HALF_CARRY ? 'H' : '-',\
X PARITY ? 'P' : '-',\
X BCD ? 'N' : '-',\
X CARRY ? 'C' : '-',\
X ram[PC], dasm(ram + PC))
X
static int dasm_flag = 0;
static char *reg_dump =
X "A=%02.2x BC=%4.4x DE=%04.4x HL=%04.4x SP=%04.4x PC=%04.4x %c%c%c%c%c%c %02.2x %s\r\n";
#else
#define dump_registers(x)
#endif
X
X
struct termio old_term, termp;
static int console, user_break;
X
#ifndef COMPILE_TEST
static byte *dma;
static char *tail;
static int out_delim = '$', def_drive = 1;
static FILE *reader = NULL, *punch = NULL, *list = NULL;
#endif
X
/* clean up routine */
void cleanup(int sig)
{
X if (sig == SIGINT)
X {
X user_break = 1;
X signal(SIGINT, cleanup);
X return;
X }
X
X ioctl(console, TCSETA, &old_term);
X printf("\nWe now return you to your regularly scheduled OS\n");
X exit(0);
}
X
/* How CP/M drives map to Unix: */
/* below is an array of 17 strings. This corresponds to the allowable */
/* drive names in CP/M (A to P) plus the default drive which actually */
/* is a duplicate of one of the next 16. At startup, The current Unix */
/* directory is copied into cpm_drive[1] and becomes drive A. This may */
/* be modified by the startup sequence. As well, the other drives may */
/* be set up to other directories. At the end of the startup sequence */
/* the "strcpy(cpm_drive[0], cpm_drive[1]) causes drive A to be the CP/M */
/* default drive. From that point on, a switch to a new drive involves */
/* simply copying that drive's directory into cpm_drive[0]. I did this */
/* in this way since I expect changing drives to occur less frequently */
/* than accessing files. */
X
#ifndef COMPILE_TEST
static char cpm_drive[17][128];
X
/* Convert string to upper case */
static void strtoup(char *s)
{
X while (*s)
X {
X *s = toupper(*s);
X s++;
X }
}
X
/* take a string, terminate it at the first white space and return the
X string that follows. I.E: "DIR *.COM" puts a 0 in the first space
X and returns a pointer to "*.COM". Note that "DIR" returns a pointer
X to a NULL string - NOT a NULL pointer. */
static char *chop_cmd(char *buf)
{
X char *ptr = buf;
X
X /* discard leading space */
X while (isspace(*ptr))
X ptr++;
X
X /* quad left the string */
X strcpy(buf, ptr);
X
X /* terminate first word */
X ptr = buf;
X while (!isspace(*ptr) && *ptr)
X ptr++;
X
X /* is there more? */
X if (*ptr)
X {
X /* terminate first word */
X *ptr++ = 0;
X
X /* skip any leading space */
X while (isspace(*ptr))
X ptr++;
X
X }
X
X return(ptr);
}
X
/* given a drive unit (0 - 16) and a file name, returns Unix file name */
static char *mk_name(int dr, char *fname)
{
X static char full_name[148];
X
X sprintf(full_name, "%s/%s", cpm_drive[dr], fname);
X
X if (strchr(fname, '.') == NULL)
X strcat(full_name, ".");
X
X return(full_name);
}
X
/* given a file spec in standard CP/M format returns Unix file name */
static char *real_name(char *fname)
{
X /* does it include a drive letter? */
X if (fname[1] == ':')
X return(mk_name(*fname - '@', fname + 2));
X
X /* else use default drive */
X return(mk_name(0, fname));
}
X
X
/* given a pointer to an FCB, returns real file name */
char *fcb2real(FCB *buf)
{
X char temp[16], *p = temp;
X int k = 0;
X
X for (k = 0; k < 8; k++)
X if (!isspace(buf->name[k]))
X *p++ = buf->name[k];
X
X *p++ = '.';
X
X for (k = 0; k < 3; k++)
X if (!isspace(buf->typ[k]))
X *p++ = buf->typ[k];
X
X *p = 0;
X return(mk_name(buf->dr, temp));
}
X
/* calls system command with CP/M file name converted to Unix */
static void fsystem(const char *s, char *file)
{
X char command[256];
X
X sprintf(command, s, real_name(file));
X ioctl(console, TCSETA, &old_term);
X system(command);
X ioctl(console, TCSETA, &termp);
}
X
/* formats a CP/M file name into an FCB */
static void mk_fcb(FCB *buf, char *fname)
{
X char *p = fname;
X int k, l;
X
X /* clear FCB to start with */
X memset(buf, 0, 16);
X
X /* check for drive name */
X if (p[1] == ':')
X {
X debug("");
X buf->dr = *p - '@';
X p += 2;
X }
X
X k = l = 0;
X
X /* format primary name */
X for (k = 0; k < 8; k++)
X {
X debug("");
X
X if ((p[l] == '.') || (p[l] == 0))
X {
X debug("");
X
X while (k < 8)
X {
X debug("");
X buf->name[k++] = ' ';
X }
X }
X else if (p[l] == '*')
X {
X debug("");
X
X while (k < 8)
X buf->name[k++] = '?';
X
X debug("");
X while (p[l] && (p[l] != '.'))
X l++;
X
X debug("");
X }
X else
X buf->name[k] = p[l];
X
X debug("");
X l++;
X }
X
X debug("");
X
X /* format file type */
X for (k = 0; k < 3; k++)
X {
X debug("");
X if ((p[l] == '.') || (p[l] == 0))
X while (k < 3)
X buf->typ[k++] = ' ';
X else if (p[l] == '*')
X while (k < 3)
X buf->typ[k++] = '?';
X else
X buf->typ[k] = p[l];
X
X debug("");
X l++;
X }
X
X debug("");
X return;
}
X
/* add extension to file name. replace current one if necessary */
static void addext(char *s1, char *s2)
{
X char *p;
X
X if ((p = strchr(s1, '.')) == NULL)
X strcat(s1, ".");
X
X strcat(s1, s2);
}
#endif
X
/* get a character from the terminal */
byte getch(void)
{
X byte c = 0;
X
X while (read(console, &c, 1) != 1)
X ;
X
X return(c);
}
X
/* see if character waiting */
#define kbhit() ioctl(console, FIORDCHK, NULL)
X
/* get a string */
int get_str(char *buffer, int maxlen)
{
X int k = 0, c;
X
X while ((c = getch()) != '\r' && c != '\n')
X {
X if (k == maxlen)
X c = '\a';
X else if (c == '\b')
X {
X if (k)
X {
X fprintf(stderr, "\b \b");
X k--;
X }
X }
X else
X {
X fputc(c, stdout);
X buffer[k++] = c;
X }
X
X }
X
X fprintf(stderr, "\r\n");
X return(k);
}
X
#ifdef CPM_DEBUG
#define is_breakpoint(x) breakpoint(0, (x))
#define list_breakpoints() breakpoint(0, 0)
#define add_breakpoint(x) breakpoint(1, (x))
#define del_breakpoint(x) breakpoint(2, (x))
X
int breakpoint(int cmd, int bpoint)
{
X static int bp[64];
X int k;
X
X switch(cmd)
X {
X case 0:
X for (k = 0; k < 64; k++)
X {
X if (bp[k])
X {
X if (!bpoint)
X fprintf(stderr, "Breakpoint %2d: 0x%04.4x\r\n");
X else if (bp[k] == bpoint)
X return(1);
X }
X }
X
X return(0);
X break;
X
X case 1:
X for (k = 0; k < 64; k++)
X if (bp[k] == bpoint)
X return(k);
X
X for (k = 0; k < 64; k++)
X {
X if (!bp[k])
X {
X bp[k] = bpoint;
X return(k);
X }
X }
X
X fprintf(stderr, "Too many breakpoints\r\n");
X return(-1);
X break;
X
X case 2:
X for (k = 0; k < 64; k++)
X if (bp[k] == bpoint)
X bp[k] = 0;
X
X return(0);
X break;
X }
X
X return(-1);
}
X
X
int debugger()
{
X char entry[128], *ptr;
X
X user_break = 0;
X
X for (;;)
X {
X fprintf(stderr, "\r\nDEBUG> ");
X ptr = entry;
X
X while ((*ptr = getch()) != '\n')
X {
X if (*ptr == '\b')
X {
X if (ptr > entry)
X {
X fprintf(stderr, "\b \b");
X ptr--;
X }
X }
X else
X fputc(*ptr++, stdout);
X }
X
X *ptr = 0;
X strtoup(entry);
X fprintf(stderr, "\r\n");
X
X if (!*entry)
X ;
X else if (*entry == 'G')
X return(0);
X else if (*entry == 'Q')
X return(1);
X else if (*entry == 'R')
X dump_registers(stdout);
X else if (*entry == '+')
X add_breakpoint(atoi(entry + 1));
X else if (*entry == '-')
X del_breakpoint(atoi(entry + 1));
X else if (*entry == 'L')
X list_breakpoints();
#ifdef CPM_DEBUG
X else if (isdigit(*entry))
X dasm_flag = *entry - '0';
#endif
X
#if 0
X else if (*entry == '')
X else if (*entry == '')
X else if (*entry == '')
X else if (*entry == '')
#endif
X else
X fprintf(stderr, "\aUnknown command: %c\n", *entry);
X }
}
#endif
X
#ifndef COMPILE_TEST
/* run a program */
static int run(char *program)
{
X byte *mem_ptr = ram + 0x100;
X char *fn, fn2[128];
X int c, k, pc;
X FILE *fp;
X FCB *fcb = NULL;
X long f_pos;
X struct stat s;
X
X debug("Start run function");
X
X /* find the program name */
X strcpy((char *)(mem_ptr), program);
X addext((char *)(mem_ptr), "COM");
X
X /* open the command file - return error if not found */
X if ((fp = fopen((char *)(mem_ptr), "rb")) == NULL)
X return(-1);
X
X debug("");
X
X /* load command into memory */
X while (fread(mem_ptr, 1, 0x100, fp))
X {
X if (mem_ptr > (ram + 0xf000))
X {
X fprintf(stderr, "\aCommand file too big\r\n");
X return(-2);
X }
X
X mem_ptr += 0x100;
X }
X
X fclose(fp);
X debug("");
X
X /* set up registers and page zero */
X PC = 0x100;
X SP = 0xfff0;
X
X /* following for test purposes */
X A = 1;
X BC = 0x2345;
X DE = 0x6789;
X HL = 0x0abc;
X
X debug("");
X strcpy((char *)(ram + 0x80), tail);
X debug("");
X mem_ptr = (byte *)(chop_cmd(tail));
X debug("");
X mk_fcb((FCB *)(ram + 0x5c), tail);
X debug("");
X mk_fcb((FCB *)(ram + 0x6c), (char *)(mem_ptr));
X debug("");
X memcpy(ram, page_zero, sizeof(page_zero));
X debug("");
X dma = ram + 0x80;
X debug("");
X
X debug("");
X
X /* BDOS, BIOS and default stack */
X for (k = 0xfc00; k < 0x10000; k++)
X ram[k] = 0;
X
X debug("");
X
X /* run program. loop stops if PC = 0 - "JP 0" e.g. */
X while (PC)
X {
X
#ifdef CPM_DEBUG
X if (dasm_flag > 1)
X dump_registers(stderr);
X
X if ((user_break && debugger()) || is_breakpoint(PC))
#else
X if (user_break)
#endif
X {
X fprintf(stderr, "\r\n\n\a* Program Interrupted by user *\r\n", ram[PC]);
X dump_registers(stderr);
X return(-5);
X }
X
X debug("");
X pc = PC;
X
X /* check if PC = BDOS entry point */
X if (PC == BDOS)
X {
X /* do CP/M service if so */
X switch (C)
X {
X case 0: /* system reset */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: System reset\r\n");
#endif
X return(0);
X
X case 1: /* conin */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Console in\r\n");
#endif
X
X fputc((A = getch()), stdout);
X break;
X
X case 2: /* conout */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Console out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X fputc(E, stdout);
X break;
X
X case 3: /* RDR */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Reader in\r\n");
#endif
X
X if (reader != NULL)
X A = fgetc(reader);
X break;
X
X case 4: /* PUN */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Punch out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X if (punch != NULL)
X fputc(E, punch);
X break;
X
X case 5: /* LST */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: List out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X if (list != NULL)
X fputc(E, list);
X break;
X
X case 6: /* CONIO */
#ifdef CPM_DEBUG
X if (dasm_flag)
X {
X fprintf(stderr, "BDOS: Conio ");
X if (E == 0xff)
X fprintf(stderr, "in\r\n");
X else
X fprintf(stderr, "out (%c)\r\n", E >= ' ' ? E : '.');
X }
#endif
X
X if (E == 0xff)
X A = getch();
X else
X fputc(E, stdout);
X
X break;
X
X case 7: /* get IOBYTE */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Get IOBYTE\r\n");
#endif
X
X A = 0x95;
X break;
X
X case 8: /* set IOBYTE */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Set IOBYTE\r\n");
#endif
X
X break;
X
X case 28: /* write protect disk */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Write protect disk\r\n");
#endif
X
X break;
X
X case 9: /* prstr */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Print string\r\n");
#endif
X
X mem_ptr = ram + DE;
X while (*mem_ptr != out_delim)
X fputc(*mem_ptr++, stdout);
X break;
X
X case 10: /* rdstr */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Read console buffer\r\n");
#endif
X
X ram[DE + 1] = get_str((char *)(ram) + DE + 2, ram[DE]);
X break;
X
X case 11: /* CONSTAT */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Get console status\r\n");
#endif
X
X A = kbhit() ? 0xff : 0;
X break;
X
X case 12: /* VERSION */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Return version number\r\n");
#endif
X
X HL = 0x0022;
X break;
X
X case 13: /* RSTDSK */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Reset disk system\r\n");
#endif
X
X break;
X
X case 14: /* SELDSK */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Select disk %c:\r\n", E + 'A');
#endif
X
X k = E + 1;
X A = 0xff;
X
X if ((k < 1) || (k > 16))
X H = 4;
X else if (*cpm_drive[k] == 0)
X H = 1;
X else
X {
X def_drive = k;
X strcpy(cpm_drive[0], cpm_drive[k]);
X A = 0;
X }
X break;
X
X case 15: /* OPENF */
X fcb = (FCB *)(ram + DE);
X fn = fcb2real(fcb);
X memset(&fcb->fp, 0, 24);
X
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Open file %s\r\n", fn);
#endif
X
X A = 0xff;
X
X if (strchr(fn, '?') != NULL)
X HL = 9;
X else if ((fcb->dr < 0) || (fcb->dr > 16))
X HL = 4;
X else if (*cpm_drive[fcb->dr] == 0)
X HL = 1;
X else if ((fcb->fp = fopen(fn, "r+")) == NULL)
X HL = 0;
X else
X A = HL = 0;
X
X break;
X
X case 16:
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Close file\r\n");
#endif
X
X fcb = (FCB *)(ram + DE);
X
X if (fcb->fp != NULL)
X fclose(fcb->fp);
X
X fcb->fp = NULL;
X break;
X
X case 19:
X fcb = (FCB *)(ram + DE);
X
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Delete file\r\n", fcb2real(fcb));
#endif
X
X unlink(fcb2real(fcb));
X fcb->fp = NULL;
X break;
X
X case 20: /* READ */
X case 33: /* READ RANDOM */
#ifdef CPM_DEBUG
X if (dasm_flag)
X {
X fprintf(stderr, "BDOS: Read ");
X if (C == 20)
X fprintf(stderr, "sequential");
X else
X fprintf(stderr, "random");
X }
#endif
X
X if ((fcb = (FCB *)(ram + DE)) == NULL)
X {
X A = 9;
X break;
X }
X
X memset(dma, 0x1a, 0x80);
X
X if (C == 33)
X {
X f_pos = (fcb->r2 << 16) + (fcb->r1 << 8) + fcb->r0;
X fseek(fcb->fp, f_pos * 0x80, SEEK_SET);
X }
X
X if (fread(dma, 1, 0x80, fcb->fp) == 0)
X A = 1;
X else
X A = 0;
X
X break;
X
X case 21: /* WRITE */
X case 34: /* WRITE RANDOM */
X case 40: /* Write Random Zero Fill */
#ifdef CPM_DEBUG
X if (dasm_flag)
X {
X fprintf(stderr, "BDOS: Write ");
X if (C == 21)
X fprintf(stderr, "sequential\r\n");
X else if (C == 34)
X fprintf(stderr, "random\r\n");
X else
X fprintf(stderr, "random with zero fill\r\n");
X }
#endif
X
X if ((fcb = (FCB *)(ram + DE)) == NULL)
X {
X A = 9;
X break;
X }
X
X if (C == 34)
X {
X f_pos = (fcb->r2 << 16) + (fcb->r1 << 8) + fcb->r0;
X fseek(fcb->fp, f_pos * 0x80, SEEK_SET);
X }
X
X if (fwrite(dma, 1, 0x80, fcb->fp) == 0)
X A = 1;
X else
X A = 0;
X
X break;
X
X case 22: /* MAKEF */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Make file\r\n");
#endif
X
X fcb = (FCB *)(ram + DE);
X fn = fcb2real(fcb);
X
X if ((fcb->fp = fopen(fn, "r")) != NULL)
X {
X fclose(fcb->fp);
X A = 0xff;
X break;
X }
X
X memset(&fcb->fp, 0, 24);
X A = 0xff;
X
X if (strchr(fn, '?') != NULL)
X HL = 9;
X else if ((fcb->dr < 0) || (fcb->dr > 16))
X HL = 4;
X else if (*cpm_drive[fcb->dr] == 0)
X HL = 1;
X else if ((fcb->fp = fopen(fn, "w")) == NULL)
X HL = 0;
X else
X A = HL = 0;
X
X break;
X
X case 23: /* RENAME */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Rename file\r\n");
#endif
X
X fcb = (FCB *)(ram + DE);
X strcpy(fn2, fcb2real(fcb));
X fn = fcb2real(fcb + 16);
X
X if (link(fn2, fn) == -1)
X A = 0xff;
X else
X {
X unlink(fn2);
X A = 0;
X }
X break;
X
X case 24: /* get log in vector */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Get login vector\r\n");
#endif
X
X c = 1;
X HL = 0;
X
X for (k = 1; k <= 16; k++)
X {
X if (*cpm_drive[k])
X HL |= c;
X
X c <<= 1;
X }
X
X A = L;
X break;
X
X case 25:
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Return current disk\r\n");
#endif
X
X A = def_drive - 1;
X break;
X
X case 26:
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Set DMA address\r\n");
#endif
X
X dma = ram + DE;
X break;
X
X case 29: /* get R/O vector */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Get read only vector\r\n");
#endif
X
X HL = 0;
X break;
X
X case 35: /* get file size */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Compute file size\r\n");
#endif
X fcb = (FCB *)(ram + DE);
X if (stat(fcb2real(fcb), &s) == -1)
X {
X A = 0xff;
X break;
X }
X
X A = 0;
X /* fall through */
X
X case 36: /* set random record */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Set random record\r\n");
#endif
X
X if (C == 36)
X {
X if ((fcb = (FCB *)(ram + DE)) == NULL)
X break;
X
X s.st_size = ftell(fcb->fp);
X }
X
X s.st_size >>= 7;
X fcb->r0 = s.st_size & 0xff;
X s.st_size >>= 8;
X fcb->r1 = s.st_size & 0xff;
X s.st_size >>= 8;
X fcb->r2 = s.st_size & 0xff;
X
X break;
X
X case 37: /* reset drive */
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BDOS: Reset drive\r\n");
#endif
X
X A = 0;
X break;
X
X default:
X fprintf(stderr, "\a\r\nInvalid BDOS call %d\r\n", C);
X return(-3);
X }
X }
X else if (PC >= BIOS)
X {
X if (PC % 3)
X {
X fprintf(stderr, "\a\r\nInvalid BIOS jump 0%04.4x\r\n", pc);
X PC = pc;
X dump_registers(stderr);
X return(-5);
X }
X
#ifdef CPM_DEBUG
X if (dasm_flag)
X fprintf(stderr, "BIOS: Function %d\r\n", (PC - BIOS)/3);
#endif
X
X switch (PC)
X {
X case bios(0):
X return(0);
X
X default:
X PC = pc;
X fprintf(stderr, "Unimplemented BIOS jump 0%04.4xH\r\n", PC);
X dump_registers(stderr);
X return(-6);
X }
X }
X
X if (decode())
X {
X PC = pc;
X fprintf(stderr, "\a\r\nInvalid processor instruction 0x%02.2x\r\n", ram[PC]);
X dump_registers(stderr);
X return(-4);
X }
X
#ifdef CPM_DEBUG
X if (dasm_flag > 1 && pc >= BDOS)
X getch();
#endif
X }
X
X return(0);
}
#endif
X
FILE *open_device(char *dev, char *typ)
{
X FILE *fp;
X
X if (*dev == '!')
X fp = popen(dev + 1, typ);
X else
X fp = fopen(dev, typ);
X
X if (fp != NULL)
X return(fp);
X
X fprintf(stderr, "Error on %s\r\n", dev);
X perror("Can't open virtual device");
X exit(1);
X return(NULL);
}
X
#ifndef COMPILE_TEST
static int do_command(char *cmd_str)
{
X char entry[256];
X FILE *fp;
X
X if ((*cmd_str == ';') || (*cmd_str == '#'))
X return(0);
X
X strcpy(entry, cmd_str);
X
X if (*entry == '!')
X {
X int r;
X
X ioctl(console, TCSETA, &old_term);
X r = system(entry + 1);
X ioctl(console, TCSETA, &termp);
X return(r);
X }
X
X strtoup(entry);
X tail = chop_cmd(entry);
X user_break = 0;
X
X if ((isspace(entry[2]) || (entry[2] == 0)) && (entry[1] == ':'))
X {
X *entry -= '@';
X
X if ((*entry < 1) || (*entry > MAX_DRIVES) || (*cpm_drive[*entry] == 0))
X {
X fprintf(stderr, "\a\r\nInvalid drive specification\r\n");
SHAR_EOF
true || echo 'restore of cpm.c failed'
echo 'End of part 1, continue with part 2'
echo 2 > _shar_seq_.tmp
exit 0
--
D'Arcy J.M. Cain (darcy at druid) |
D'Arcy Cain Consulting | I support gun control.
West Hill, Ontario, Canada | Let's start with the government!
+ 416 281 6094 |
More information about the Alt.sources
mailing list