| /* 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 */ | |
| } | |
| } | |