home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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
- *
- */
-
- #include <stdio.h>
- #include <signal.h>
- #if defined(COHERENT) && !defined(_I386)
- #include <sys/fcntl.h>
- #else
- #include <fcntl.h>
- #endif
- #ifndef COHERENT
- #include <malloc.h>
- #include <memory.h>
- #endif
- #include <time.h>
- #include "sim.h"
- #include "simglb.h"
-
- /*
- * 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) */
-
- static struct dskdef disks[16] = {
- { "disks/drivea.cpm", &drivea, 77, 26 },
- { "disks/driveb.cpm", &driveb, 77, 26 },
- { "disks/drivec.cpm", &drivec, 77, 26 },
- { "disks/drived.cpm", &drived, 77, 26 },
- { "disks/drivee.cpm", &drivee, -1, -1 },
- { "disks/drivef.cpm", &drivef, -1, -1 },
- { "disks/driveg.cpm", &driveg, -1, -1 },
- { "disks/driveh.cpm", &driveh, -1, -1 },
- { "disks/drivei.cpm", &drivei, -1, -1 },
- { "disks/drivej.cpm", &drivej, -1, -1 },
- { "disks/drivek.cpm", &drivek, -1, -1 },
- { "disks/drivel.cpm", &drivel, -1, -1 },
- { "disks/drivem.cpm", &drivem, -1, -1 },
- { "disks/driven.cpm", &driven, -1, -1 },
- { "disks/driveo.cpm", &driveo, -1, -1 },
- { "disks/drivep.cpm", &drivep, -1, -1 }
- };
-
- /*
- * MMU:
- * ===
- *
- * +--------+
- * 16KB | common |
- * +--------+
- * +--------+ +--------+ .......... +--------+
- * | | | | | |
- * 48KB | | | | .......... | |
- * | bank 0 | | bank 1 | | bank n |
- * +--------+ +--------+ .......... +--------+
- */
- #define MAXSEG 16 /* max. number of memory banks */
- #define SEGSIZ 49152 /* size of one bank = 48KBytes */
- static char *mmu[MAXSEG]; /* MMU with pointers to the banks */
- static int selbnk; /* current bank */
- static int maxbnk; /* number of initialized banks */
-
- /*
- * Forward declaration of the I/O handlers for all used ports
- */
- BYTE io_trap();
- BYTE cond_in(), cond_out(), cons_in(), cons_out();
- BYTE prtd_in(), prtd_out(), prts_in(), prts_out();
- BYTE auxd_in(), auxd_out(), auxs_in(), auxs_out();
- BYTE fdcd_in(), fdcd_out();
- BYTE fdct_in(), fdct_out();
- BYTE fdcs_in(), fdcs_out();
- BYTE fdco_in(), fdco_out();
- BYTE fdcx_in(), fdcx_out();
- BYTE dmal_in(), dmal_out();
- BYTE dmah_in(), dmah_out();
- BYTE mmui_in(), mmui_out(), mmus_in(), mmus_out();
- BYTE clkc_in(), clkc_out(), clkd_in(), clkd_out();
-
- /*
- * 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 trap handler
- */
- static BYTE io_trap()
- {
- cpu_error = IOTRAP;
- cpu_state = STOPPED;
- }
-
- /*
- * I/O handler for read console status:
- * 0xff : input available
- * 0x00 : no input available
- */
- static BYTE cons_in()
- {
- register int flags, readed;
-
- if (last_char)
- return((BYTE) 0xff);
- if (cntl_c)
- return((BYTE) 0xff);
- if (cntl_bs)
- return((BYTE) 0xff);
- else {
- flags = fcntl(0, F_GETFL, 0);
- fcntl(0, F_SETFL, flags | O_NDELAY);
- readed = read(0, &last_char, 1);
- fcntl(0, F_SETFL, flags);
- if (readed == 1)
- return((BYTE) 0xff);
- else
- return((BYTE) 0);
- }
- }
-
- /*
- * 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;
-
- if (read(auxin, &c, 1) == 1)
- return((BYTE) c);
- else {
- aux_in_eof = 0xff;
- return((BYTE) 0x1a); /* CP/M EOF */
- }
- }
-
- /*
- * I/O handler for write aux data:
- * write output to pipe "auxout"
- */
- static BYTE auxd_out(data)
- BYTE data;
- {
- if (data != '\r')
- write(auxout, (char *) &data, 1);
- }
-
- /*
- * I/O handler for read FDC drive:
- * return the current drive
- */
- static BYTE fdcd_in()
- {
- return((BYTE) drive);
- }
-
- /*
- * I/O handler for write FDC drive:
- * set the current drive
- */
- static BYTE fdcd_out(data)
- register BYTE data;
- {
- drive = data;
- }
-
- /*
- * I/O handler for read FDC track:
- * return the current track
- */
- static BYTE fdct_in()
- {
- return((BYTE) track);
- }
-
- /*
- * I/O handler for write FDC track:
- * set the current track
- */
- static BYTE fdct_out(data)
- register BYTE data;
- {
- track = data;
- }
-
- /*
- * I/O handler for read FDC sector
- * return the current sector
- */
- static BYTE fdcs_in()
- {
- return((BYTE) sector);
- }
-
- /*
- * I/O handler for write FDC sector:
- * set the current sector
- */
- static BYTE fdcs_out(data)
- register BYTE data;
- {
- sector = data;
- }
-
- /*
- * I/O handler for read FDC command:
- * always returns 0
- */
- static BYTE fdco_in()
- {
- return((BYTE) 0);
- }
-
- /*
- * 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);
- }
-