blob: 0b4f7937add14f0be0cf3d9cc75e8d267661a987 [file] [log] [blame] [raw]
/* altairZ80_sio: MITS Altair serial I/O card
Written by Peter Schorn, 2001
Based on work by Charles E Owen ((c) 1997, Commercial use prohibited)
These functions support a simulated MITS 2SIO interface card.
The card had two physical I/O ports which could be connected
to any serial I/O device that would connect to a current loop,
RS232, or TTY interface. Available baud rates were jumper
selectable for each port from 110 to 9600.
All I/O is via programmed I/O. Each each has a status port
and a data port. A write to the status port can select
some options for the device (0x03 will reset the port).
A read of the status port gets the port status:
+---+---+---+---+---+---+---+---+
| X X X X X X O I |
+---+---+---+---+---+---+---+---+
I - A 1 in this bit position means a character has been received
on the data port and is ready to be read.
O - A 1 in this bit means the port is ready to receive a character
on the data port and transmit it out over the serial line.
A read to the data port gets the buffered character, a write
to the data port writes the character to the device.
*/
#include <stdio.h>
#include <ctype.h>
#include "altairZ80_defs.h"
#include "sim_sock.h"
#include "sim_tmxr.h"
#define UNIT_V_ANSI (UNIT_V_UF + 0) /* ANSI mode, strip bit 8 on output */
#define UNIT_ANSI (1 << UNIT_V_ANSI)
#define UNIT_V_UPPER (UNIT_V_UF + 1) /* uppper case mode */
#define UNIT_UPPER (1 << UNIT_V_UPPER)
#define UNIT_V_BS (UNIT_V_UF + 2) /* map delete to backspace */
#define UNIT_BS (1 << UNIT_V_BS)
#define Terminals 1 /* lines per mux */
TMLN TerminalLines[Terminals] = { { 0 } }; /* we only need one line */
TMXR altairTMXR = {Terminals, 0, &TerminalLines[0] }; /* mux descriptor */
t_stat sio_svc (UNIT *uptr);
t_stat sio_reset (DEVICE *dptr);
t_stat sio_attach (UNIT *uptr, char *cptr);
t_stat sio_detach (UNIT *uptr);
t_stat ptr_svc (UNIT *uptr);
t_stat ptr_reset (DEVICE *dptr);
t_stat ptp_svc (UNIT *uptr);
t_stat ptp_reset (DEVICE *dptr);
int32 nulldev(int32 io, int32 data);
int32 simh_dev(int32 io, int32 data);
int32 sio0d(int32 io, int32 data);
int32 sio0s(int32 io, int32 data);
int32 sio1d(int32 io, int32 data);
int32 sio1s(int32 io, int32 data);
void attachCPM();
extern t_stat sim_activate (UNIT *uptr, int32 interval);
extern t_stat sim_cancel (UNIT *uptr);
extern t_stat sim_poll_kbd (void);
extern t_stat sim_putchar (int32 out);
extern t_stat attach_unit (UNIT *uptr, char *cptr);
extern t_bool rtc_avail;
extern FILE *sim_log;
extern int32 sim_switches;
extern uint32 sim_os_msec (void);
extern uint8 M[MAXMEMSIZE];
/* 2SIO Standard I/O Data Structures */
UNIT sio_unit = { UDATA (&sio_svc, UNIT_ATTABLE, 0),
KBD_POLL_WAIT };
REG sio_reg[] = {
{ HRDATA (DATA, sio_unit.buf, 8) },
{ HRDATA (STAT, sio_unit.u3, 8) },
{ NULL } };
MTAB sio_mod[] = {
{ UNIT_ANSI, 0, "TTY", "TTY", NULL }, /* keep bit 8 as is for output */
{ UNIT_ANSI, UNIT_ANSI, "ANSI", "ANSI", NULL }, /* set bit 8 to 0 before output */
{ UNIT_UPPER, 0, "ALL", "ALL", NULL }, /* do not change case of input characters */
{ UNIT_UPPER, UNIT_UPPER, "UPPER", "UPPER", NULL }, /* change input characters to upper case */
{ UNIT_BS, 0, "BS", "BS", NULL }, /* map delete to backspace */
{ UNIT_BS, UNIT_BS, "DEL", "DEL", NULL }, /* map backspace to delete */
{ 0 } };
DEVICE sio_dev = {
"SIO", &sio_unit, sio_reg, sio_mod,
1, 10, 31, 1, 8, 8,
NULL, NULL, &sio_reset,
NULL, &sio_attach, &sio_detach };
UNIT ptr_unit = { UDATA (&ptr_svc, UNIT_SEQ + UNIT_ATTABLE + UNIT_ROABLE, 0),
KBD_POLL_WAIT };
REG ptr_reg[] = {
{ HRDATA (DATA, ptr_unit.buf, 8) },
{ HRDATA (STAT, ptr_unit.u3, 8) },
{ DRDATA (POS, ptr_unit.pos, 31) },
{ NULL } };
DEVICE ptr_dev = {
"PTR", &ptr_unit, ptr_reg, NULL,
1, 10, 31, 1, 8, 8,
NULL, NULL, &ptr_reset,
NULL, NULL, NULL };
UNIT ptp_unit = { UDATA (&ptp_svc, UNIT_SEQ + UNIT_ATTABLE, 0),
KBD_POLL_WAIT };
REG ptp_reg[] = {
{ HRDATA (DATA, ptp_unit.buf, 8) },
{ HRDATA (STAT, ptp_unit.u3, 8) },
{ DRDATA (POS, ptp_unit.pos, 31) },
{ NULL } };
DEVICE ptp_dev = {
"PTP", &ptp_unit, ptp_reg, NULL,
1, 10, 31, 1, 8, 8,
NULL, NULL, &ptp_reset,
NULL, NULL, NULL };
t_stat sio_attach (UNIT *uptr, char *cptr)
{
return tmxr_attach (&altairTMXR, uptr, cptr); /* attach mux */
}
/* Detach */
t_stat sio_detach (UNIT *uptr)
{
sio_unit.u3 = 0x02; /* Status */
sio_unit.buf = 0; /* Data */
return tmxr_detach (&altairTMXR, uptr);
}
/* Service routines to handle simulator functions */
/* service routine - actually gets char & places in buffer */
t_stat sio_svc (UNIT *uptr)
{
int32 temp;
sim_activate (&sio_unit, sio_unit.wait); /* continue poll */
if (sio_unit.flags & UNIT_ATT) {
if (sim_poll_kbd () == SCPE_STOP) { /* listen for ^E */
return SCPE_STOP;
}
temp = tmxr_poll_conn(&altairTMXR, &sio_unit); /* poll connection */
if (temp >= 0) {
altairTMXR.ldsc[temp] -> rcve = 1; /* enable receive */
}
tmxr_poll_rx(&altairTMXR); /* poll input */
tmxr_poll_tx(&altairTMXR); /* poll output */
}
else {
if ((temp = sim_poll_kbd ()) < SCPE_KFLAG) {
return temp; /* no char or error? */
}
sio_unit.buf = temp & 0xff; /* Save char */
sio_unit.u3 |= 0x01; /* Set status */
}
return SCPE_OK;
}
t_stat ptr_svc (UNIT *uptr)
{
return SCPE_OK;
}
t_stat ptp_svc (UNIT *uptr)
{
return SCPE_OK;
}
/* Reset routine */
t_stat sio_reset (DEVICE *dptr)
{
if (sio_unit.flags & UNIT_ATT) {
if (altairTMXR.ldsc[0]->conn > 0) {
tmxr_reset_ln(altairTMXR.ldsc[0]);
}
sio_unit.u3 = 0; /* Status */
}
else {
sio_unit.u3 = 0x02; /* Status */
}
sio_unit.buf = 0; /* Data */
sim_activate (&sio_unit, sio_unit.wait); /* activate unit */
return SCPE_OK;
}
t_stat ptr_reset (DEVICE *dptr)
{
ptr_unit.buf = 0;
ptr_unit.u3 = 0;
ptr_unit.pos = 0;
if (ptr_unit.flags & UNIT_ATT) { /* attached? */
rewind(ptr_dev.units -> fileref);
}
sim_cancel (&ptp_unit); /* deactivate unit */
return SCPE_OK;
}
t_stat ptp_reset (DEVICE *dptr)
{
ptp_unit.buf = 0;
ptp_unit.u3 = 0x02;
sim_cancel (&ptp_unit); /* deactivate unit */
return SCPE_OK;
}
/* I/O instruction handlers, called from the CPU module when an
IN or OUT instruction is issued.
Each function is passed an 'io' flag, where 0 means a read from
the port, and 1 means a write to the port. On input, the actual
input is passed as the return value, on output, 'data' is written
to the device.
*/
int32 sio0s(int32 io, int32 data)
{
if (io == 0) { /* IN */
if (sio_unit.flags & UNIT_ATT) {
sio_unit.u3 = (((tmxr_rqln(altairTMXR.ldsc[0]) > 0 ? 0x01 : 0) | /* read possible if character available */
(altairTMXR.ldsc[0]->conn == 0 ? 0 : 0x02))); /* write possible if connected */
}
return (sio_unit.u3);
}
else { /* OUT */
if (sio_unit.flags & UNIT_ATT) {
if (data == 0x03) { /* reset port! */
sio_unit.u3 = 0;
sio_unit.buf = 0;
}
}
else {
if (data == 0x03) { /* reset port! */
sio_unit.u3 = 0x02;
sio_unit.buf = 0;
}
}
return (0); /* ignored since OUT */
}
}
int32 sio0d(int32 io, int32 data)
{
if (io == 0) { /* IN */
if (sio_unit.flags & UNIT_ATT) {
sio_unit.buf = tmxr_getc_ln(altairTMXR.ldsc[0]) & 0xff;
}
sio_unit.u3 = sio_unit.u3 & 0xFE;
if (sio_unit.flags & UNIT_BS) {
if (sio_unit.buf == BACKSPACE_CHAR) {
sio_unit.buf = DELETE_CHAR;
}
}
else {
if (sio_unit.buf == DELETE_CHAR) {
sio_unit.buf = BACKSPACE_CHAR;
}
}
return ((sio_unit.flags & UNIT_UPPER) ? toupper(sio_unit.buf) : sio_unit.buf);
}
else { /* OUT */
if (sio_unit.flags & UNIT_ANSI) {
data &= 0x7f;
}
if (sio_unit.flags & UNIT_ATT) {
tmxr_putc_ln(altairTMXR.ldsc[0], data);
}
else {
sim_putchar(data);
}
return (0); /* ignored since OUT */
}
}
/* Port 2 controls the PTR/PTP devices */
int32 sio1s(int32 io, int32 data)
{
if (io == 0) {
/* reset I bit iff PTR unit not attached or no more data available. */
/* O bit is always set since write always possible. */
return ((ptr_unit.flags & UNIT_ATT) == 0) || (ptr_unit.u3 != 0) ? 0x02 : 0x03;
}
else { /* OUT */
if (data == 0x03) {
ptr_unit.u3 = 0;
ptr_unit.buf = 0;
ptr_unit.pos = 0;
ptp_unit.u3 = 0;
ptp_unit.buf = 0;
ptp_unit.pos = 0;
}
return (0); /* ignored since OUT */
}
}
int32 sio1d(int32 io, int32 data)
{
int32 temp;
if (io == 0) { /* IN */
if (((ptr_unit.flags & UNIT_ATT) == 0) || (ptr_unit.u3 != 0))
return (0); /* not attached or no more data available */
if ((temp = getc(ptr_dev.units -> fileref)) == EOF) { /* end of file? */
ptr_unit.u3 = 0x01;
return (CONTROLZ_CHAR); /* control Z denotes end of text file in CP/M */
}
ptr_unit.pos++;
return (temp & 0xFF);
}
else { /* OUT */
putc(data, ptp_dev.units -> fileref);
ptp_unit.pos++;
return (0); /* ignored since OUT */
}
}
int32 nulldev(int32 io, int32 data)
{
return (io == 0 ? 0xff : 0);
}
#define splimit 10
#define printTimeCmd 0
#define markTimeCmd 1
#define showTimeCmd 2
#define resetPtrCmd 3
#define attachCmd 4
#define detachCmd 5
#define resetCmd 6
#define cpmCommandLineLength 128
uint32 markTime[splimit];
int32 markTimeSP = 0;
char version[] = "SIMH001";
int32 versionPos = 0;
/* The CP/M commandline is used as the name of a file and PTR is attached to it */
void attachCPM() {
char cpmCommandLine[cpmCommandLineLength];
uint32 i, len = (M[0x80] & 0x7f) - 1; /* 0x80 contains length of commandline, discard first char */
for (i = 0; i < len; i++) {
cpmCommandLine[i] = (char)M[0x82+i]; /* the first char, typically ' ', is discarded */
}
cpmCommandLine[i] = 0; /* make C string */
sim_switches = SWMASK ('R');
attach_unit(&ptr_unit, cpmCommandLine);
}
/* port 0xfe is a device for communication SIMH <--> Altair machine */
int32 simh_dev(int32 io, int32 data) {
uint32 delta;
int32 result;
if (io == 0) { /* IN */
result = version[versionPos++];
if (result == 0) {
versionPos = 0;
}
return (result);
}
else { /* OUT */
switch(data) {
case printTimeCmd: /* print time */
if (rtc_avail) {
printf("Current time in milliseconds = %d.\n", sim_os_msec ());
if (sim_log) {
fprintf(sim_log, "Current time in milliseconds = %d.\n", sim_os_msec ());
}
}
break;
case markTimeCmd: /* mark time */
if (rtc_avail) {
if (markTimeSP < splimit) {
markTime[markTimeSP++] = sim_os_msec ();
}
else {
printf("Mark stack overflow.\n");
if (sim_log) {
fprintf(sim_log, "Mark stack overflow.\n");
}
}
}
break;
case showTimeCmd: /* show time difference */
if (rtc_avail) {
if (markTimeSP > 0) {
delta = sim_os_msec () - markTime[--markTimeSP];
printf("Delta to mark in milliseconds = %d.\n", delta);
if (sim_log) {
fprintf(sim_log, "Delta to mark in milliseconds = %d.\n", delta);
}
}
else {
printf("Missing mark.\n");
if (sim_log) {
fprintf(sim_log, "Missing mark.\n");
}
}
}
break;
case resetPtrCmd: /* reset ptr device */
ptr_reset(NULL);
break;
case attachCmd: /* attach ptr to the file with name at beginning of CP/M command line */
attachCPM();
break;
case detachCmd: /* detach ptr */
detach_unit(&ptr_unit);
break;
case resetCmd:
versionPos = 0;
break;
default:;
}
return 0; /* ignored, since OUT */
}
}