/*
* Z80SIM - a Z80-CPU simulator
*
* Copyright (C) 1987-92 by Udo Munk
*
* This modul contains a complex I/O-simulation for the Z80-CPU
* simulation. Because this is an example, what you can do with
* the CPU-emulation, you may change this modul for your needs,
* and use it, or parts of it, in your own I/O-simulations.
*
* History:
* 28-SEP-87 Development on TARGON/35 with AT&T Unix System V.3
* 19-MAY-89 Additions for CP/M 3.0 und MP/M
* 23-DEC-90 Ported to COHERENT 3.0
* 10-JUN-92 Some optimization done
* 25-JUN-92 Flush output of stdout only at every OUT to port 0
* 25-JUN-92 Comments in english and ported to COHERENT 4.0
*/
/*
* This module contains the I/O handlers for a simulation
* of the hardware required for a CP/M system.
*
* Used I/O ports:
*
* 0 - console status
* 1 - console data
*
* 2 - printer status
* 3 - printer data
*
* 4 - auxilary status
* 5 - auxilary data
*
* 10 - FDC drive
* 11 - FDC track
* 12 - FDC sector
* 13 - FDC command
* 14 - FDC status
*
* 15 - DMA destination address low
* 16 - DMA destination address high
*
* 20 - MMU initialization
* 21 - MMU bank select
*
* 25 - clock command
* 26 - clock data
*
*/
/*
* Structure to describe a emulated floppy disk drive:
* pointer to filename
* pointer to file descriptor
* number of tracks
* number of sectors
*/
struct dskdef {
char *fn;
int *fd;
unsigned int tracks;
unsigned int sectors;
};
static BYTE drive; /* current drive A..P (0..15) */
static BYTE track; /* current track (0..255) */
static BYTE sector; /* current sektor (0..255) */
static BYTE status; /* status of last I/O operation on FDC */
static BYTE dmadl; /* current DMA adresse destination low */
static BYTE dmadh; /* current DMA adresse destination high */
static BYTE clkcmd; /* clock command */
static int drivea; /* fd for file "drivea.cpm" */
static int driveb; /* fd for file "driveb.cpm" */
static int drivec; /* fd for file "drivec.cpm" */
static int drived; /* fd for file "drived.cpm" */
static int drivee; /* fd for file "drivee.cpm" */
static int drivef; /* fd for file "drivef.cpm" */
static int driveg; /* fd for file "driveg.cpm" */
static int driveh; /* fd for file "driveh.cpm" */
static int drivei; /* fd for file "drivei.cpm" */
static int drivej; /* fd for file "drivej.cpm" */
static int drivek; /* fd for file "drivek.cpm" */
static int drivel; /* fd for file "drivel.cpm" */
static int drivem; /* fd for file "drivem.cpm" */
static int driven; /* fd for file "driven.cpm" */
static int driveo; /* fd for file "driveo.cpm" */
static int drivep; /* fd for file "drivep.cpm" */
static int printer; /* fd for file "printer.cpm" */
static int auxin; /* fd for pipe "auxin" */
static int auxout; /* fd for pipe "auxout" */
static int aux_in_eof; /* status of pipe "auxin" (<>0 means EOF) */
static int pid_rec; /* PID of the receiving process for auxiliary */
static char last_char; /* buffer for 1 character (console status) */
/*
* This array contains two function pointer for every
* active port, one for input and one for output.
*/
static BYTE (*port[256][2]) () = {
{ cons_in, cons_out }, /* port 0 */
{ cond_in, cond_out }, /* port 1 */
{ prts_in, prts_out }, /* port 2 */
{ prtd_in, prtd_out }, /* port 3 */
{ auxs_in, auxs_out }, /* port 4 */
{ auxd_in, auxd_out }, /* port 5 */
{ io_trap, io_trap }, /* port 6 */
{ io_trap, io_trap }, /* port 7 */
{ io_trap, io_trap }, /* port 8 */
{ io_trap, io_trap }, /* port 9 */
{ fdcd_in, fdcd_out }, /* port 10 */
{ fdct_in, fdct_out }, /* port 11 */
{ fdcs_in, fdcs_out }, /* port 12 */
{ fdco_in, fdco_out }, /* port 13 */
{ fdcx_in, fdcx_out }, /* port 14 */
{ dmal_in, dmal_out }, /* port 15 */
{ dmah_in, dmah_out }, /* port 16 */
{ io_trap, io_trap }, /* port 17 */
{ io_trap, io_trap }, /* port 18 */
{ io_trap, io_trap }, /* port 19 */
{ mmui_in, mmui_out }, /* port 20 */
{ mmus_in, mmus_out }, /* port 21 */
{ io_trap, io_trap }, /* port 22 */
{ io_trap, io_trap }, /* port 23 */
{ io_trap, io_trap }, /* port 24 */
{ clkc_in, clkc_out }, /* port 25 */
{ clkd_in, clkd_out } /* port 26 */
};
/*
* This function initializes the I/O handlers:
* 1. Initialize all unused ports with the I/O trap handler.
* 2. Initialize the MMU with NULL pointers.
* 3. Open the files which emulates the disk drives. The file
* for drive A must be opened, or CP/M can't be booted.
* Errors for opening one of the other 15 drives results
* in a NULL pointer for fd in the dskdef structure,
* so that this drive can't be used.
* 4. Create and open the file "printer.cpm" for emulation
* of a printer.
* 5. Fork the process for receiving from the serial port.
* 6. Open the named pipes "auxin" and "auxout" for simulation
* of a serial port.
*/
void init_io()
{
void exit(), perror();
register int i;
for (i = 27; i <= 255; i++) {
port[i][0] = io_trap;
port[i][1] = io_trap;
}
for (i = 0; i < MAXSEG; i++)
mmu[i] = NULL;
if ((*disks[0].fd = open(disks[0].fn, O_RDWR)) == -1) {
perror("file disks/drivea.cpm");
exit(1);
}
for (i = 1; i <= 15; i++)
if ((*disks[i].fd = open(disks[i].fn, O_RDWR)) == -1)
disks[i].fd = NULL;
if ((printer = creat("printer.cpm", 0644)) == -1) {
perror("file printer.cpm");
exit(1);
}
pid_rec = fork();
switch (pid_rec) {
case -1:
puts("can't fork");
exit(1);
case 0:
execlp("receive", "receive", "auxiliary.cpm", 0);
puts("can't exec receive process");
exit(1);
}
if ((auxin = open("auxin", O_RDONLY | O_NDELAY)) == -1) {
perror("pipe auxin");
exit(1);
}
if ((auxout = open("auxout", O_WRONLY)) == -1) {
perror("pipe auxout");
exit(1);
}
}
/*
* This function stops the I/O handlers:
*
* 1. The files emulating the disk drives are closed.
* 2. The file "printer.com" emulating a printer is closed.
* 3. The named pipes "auxin" and "auxout" are closed.
* 4. The receiving process for the serial port is stopped.
*/
void exit_io()
{
register int i;
for (i = 0; i <= 15; i++)
if (disks[i].fd != NULL)
close(*disks[i].fd);
close(printer);
close(auxin);
close(auxout);
kill(pid_rec, SIGHUP);
}
/*
* This function is called for every IN opcode from the
* CPU emulation. It calls the right handler for the
* port, from which input is wanted.
*/
BYTE io_in(adr)
register BYTE adr;
{
return((*port[adr][0]) ());
}
/*
* This function is called for every OUT opcode from the
* CPU emulation. It calls the right handler for the port,
* to which output is wanted.
*/
BYTE io_out(adr, data)
register BYTE adr, data;
{
(*port[adr][1]) (data);
}
/*
* I/O handler for write console status:
* no reaction
*/
static BYTE cons_out(data)
register BYTE data;
{
data = data;
}
/*
* I/O handler for read console data:
* read one character from the terminal without echo
* and character transformations
*/
static BYTE cond_in()
{
char c;
aborted:
if (last_char) {
c = last_char;
last_char = '\0';
} else if (cntl_c) {
cntl_c--;
c = 0x03;
} else if (cntl_bs) {
cntl_bs--;
c = 0x1c;
} else if (read(0, &c, 1) != 1) {
goto aborted;
}
return((BYTE) c);
}
/*
* I/O handler for write console data:
* the output is written to the terminal
*/
static BYTE cond_out(data)
register BYTE data;
{
putchar(data & 0x7f);
if (data == '\f')
printf("\033H\033J");
fflush(stdout);
}
/*
* I/O handler for read printer status:
* the printer is ready all the time
*/
static BYTE prts_in()
{
return((BYTE) 0xff);
}
/*
* I/O handler for write printer status:
* no reaction
*/
static BYTE prts_out(data)
register BYTE data;
{
data = data;
}
/*
* I/O handler for read printer data:
* always read a 0 from the printer
*/
static BYTE prtd_in()
{
return((BYTE) 0);
}
/*
* I/O handler for write printer data:
* the output is written to file "printer.cpm"
*/
static BYTE prtd_out(data)
BYTE data;
{
if (data != '\r')
write(printer, (char *) &data, 1);
}
/*
* I/O handler for read aux status:
* return EOF status of the aux device
*/
static BYTE auxs_in()
{
return((BYTE) aux_in_eof);
}
/*
* I/O handler for write aux status:
* change EOF status of the aux device
*/
static BYTE auxs_out(data)
register BYTE data;
{
aux_in_eof = data;
}
/*
* I/O handler for read aux data:
* read next byte from pipe "auxin"
*/
static BYTE auxd_in()
{
char c;
/*
* I/O handler for write FDC command:
* transfer one sector in the wanted direction,
* 0 = read, 1 = write
*
* The status byte of the FDC is set as follows:
* 0 - ok
* 1 - illegal drive
* 2 - illegal track
* 3 - illegal sector
* 4 - seek error
* 5 - read error
* 6 - write error
* 7 - illegal command to FDC
*/
static BYTE fdco_out(data)
register BYTE data;
{
register long pos;
long lseek();
if (disks[drive].fd == NULL) {
status = 1;
return;
}
if (track > disks[drive].tracks) {
status = 2;
return;
}
if (sector > disks[drive].sectors) {
status = 3;
return;
}
pos = (((long)track) * ((long)disks[drive].sectors) + sector - 1) << 7;
if (lseek(*disks[drive].fd, pos, 0) == -1L) {
status = 4;
return;
}
switch (data) {
case 0: /* read */
if (read(*disks[drive].fd, (char *) ram + (dmadh << 8) + dmadl, 128) != 128)
status = 5;
else
status = 0;
break;
case 1: /* write */
if (write(*disks[drive].fd, (char *) ram + (dmadh << 8) + dmadl, 128) != 128)
status = 6;
else
status = 0;
break;
default: /* illegal command */
status = 7;
break;
}
}
/*
* I/O handler for read FDC status:
* returns status of last FDC operation,
* 0 = ok, else some error
*/
static BYTE fdcx_in()
{
return((BYTE) status);
}
/*
* I/O handler for write FDC status:
* no reaction
*/
static BYTE fdcx_out(data)
register BYTE data;
{
data = data;
}
/*
* I/O handler for read lower byte of DMA address:
* return lower byte of current DMA address
*/
static BYTE dmal_in()
{
return((BYTE) dmadl);
}
/*
* I/O handler for write lower byte of DMA address:
* set lower byte of DMA address
*/
static BYTE dmal_out(data)
register BYTE data;
{
dmadl = data;
}
/*
* I/O handler for read higher byte of DMA address:
* return higher byte of current DMA address
*/
static BYTE dmah_in()
{
return((BYTE) dmadh);
}
/*
* I/O handler for write higher byte of DMA address:
* set higher byte of the DMA address
*/
static BYTE dmah_out(data)
register BYTE data;
{
dmadh = data;
}
/*
* I/O handler for read MMU initialization:
* return number of initialized MMU banks
*/
static BYTE mmui_in()
{
return((BYTE) maxbnk);
}
/*
* I/O handler for write MMU initialization:
* for the FIRST call the memory for the wanted number of banks
* is allocated and pointers to the memory is stored in the MMU array
*/
static BYTE mmui_out(data)
register BYTE data;
{
register int i;
if (mmu[0] != NULL)
return;
if (data > MAXSEG) {
printf("Try to init %d banks, available %d banks\n", data, MAXSEG);
exit(1);
}
for (i = 0; i < data; i++) {
if ((mmu[i] = malloc(SEGSIZ)) == NULL) {
printf("can't allocate memory for bank %d\n", i+1);
exit(1);
}
}
maxbnk = data;
}
/*
* I/O handler for read MMU bank select:
* return current selected MMU bank
*/
static BYTE mmus_in()
{
return((BYTE) selbnk);
}
/*
* I/O handler for write MMU bank select:
* if the current selected bank is not equal the wanted bank,
* the current bank is saved. Then the memory of the wanted
* bank is copied into the CPU address space and this bank is
* set to be the current one now.
*/
static BYTE mmus_out(data)
register BYTE data;
{
if (data > maxbnk)
{
printf("Try to select unallocated bank %d\n", data);
exit(1);
}
if (data == selbnk)
return;
memcpy(mmu[selbnk], (char *) ram, SEGSIZ);
memcpy((char *) ram, mmu[data], SEGSIZ);
selbnk = data;
}
/*
* I/O handler for read clock command:
* return last clock command
*/
static BYTE clkc_in()
{
return(clkcmd);
}
/*
* I/O handler for write clock command:
* set the wanted clock command
*/
static BYTE clkc_out(data)
register BYTE data;
{
clkcmd = data;
}
/*
* I/O handler for read clock data:
* dependent from the last clock command the following
* informations are given from the system clock:
* 0 - secounds in BCD
* 1 - minutes in BCD
* 2 - hours in BCD
* 3 - low byte number of days since 1.1.1978
* 4 - high byte number of days since 1.1.1978
* for every other clock command a 0 is returned
*/
static BYTE clkd_in()
{
register struct tm *t;
register int val;
extern long time();
long Time;
time(&Time);
t = localtime(&Time);
switch(clkcmd) {
case 0: /* secounds in BCD */
val = to_bcd(t->tm_sec);
break;
case 1: /* minutes in BCD */
val = to_bcd(t->tm_min);
break;
case 2: /* hours in BCD */
val = to_bcd(t->tm_hour);
break;
case 3: /* low byte days */
val = get_date(t) & 255;
break;
case 4: /* high byte days */
val = get_date(t) >> 8;
break;
default:
val = 0;
break;
}
return((BYTE) val);
}
/*
* I/O handler for write clock data:
* under UNIX the system clock only can be set by the
* super user, so we do nothing here
*/
static BYTE clkd_out(data)
register BYTE data;
{
data = data;
}
/*
* Convert a integer to BCD
*/
static int to_bcd(val)
register int val;
{
register int i = 0;
while (val >= 10) {
i += val / 10;
i <<= 4;
val %= 10;
}
i += val;
return (i);
}
/*
* Calculate number of days since 1.1.1978
*/
static int get_date(t)
register struct tm *t;
{
register int i;
register int val = 0;
for (i = 1978; i < 1900 + t->tm_year; i++) {
val += 365;
if (i % 4 == 0)
val++;
}
val += t->tm_yday + 1;
return(val);
}