| /* | |
| Copyright (c) 2015-2016, John Forecast | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of John Forecast shall not | |
| be used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from John Forecast. | |
| */ | |
| /* cdc1700_dev1.c: equipment number 1 I/O device support | |
| * Simh devices: tti, tto, ptr, ptp, cdr | |
| */ | |
| #include "cdc1700_defs.h" | |
| extern char INTprefix[]; | |
| extern void fw_IOunderwayData(IO_DEVICE *, uint16); | |
| extern void fw_IOcompleteData(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); | |
| extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); | |
| extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); | |
| extern void fw_setForced(IO_DEVICE *, uint16); | |
| extern void fw_clearForced(IO_DEVICE *, uint16); | |
| extern void rebuildPending(void); | |
| extern void RaiseExternalInterrupt(DEVICE *); | |
| extern t_bool doDirectorFunc(DEVICE *, t_bool); | |
| extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); | |
| extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); | |
| extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); | |
| extern t_stat set_protected(UNIT *, int32, CONST char *, void *); | |
| extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); | |
| extern uint16 Areg, IOAreg; | |
| extern t_bool IOFWinitialized; | |
| t_stat tti_svc(UNIT *); | |
| t_stat tto_svc(UNIT *); | |
| t_stat tti_reset(DEVICE *); | |
| t_stat tto_reset(DEVICE *); | |
| void TTIstate(const char *, DEVICE *, IO_DEVICE *); | |
| void TTOstate(const char *, DEVICE *, IO_DEVICE *); | |
| void TTstate(const char *, DEVICE *, IO_DEVICE *); | |
| uint16 TTrebuild(void); | |
| t_bool TTreject(IO_DEVICE *, t_bool, uint8); | |
| enum IOstatus TTin(IO_DEVICE *, uint8); | |
| enum IOstatus TTout(IO_DEVICE *, uint8); | |
| t_stat tt_help(FILE *, DEVICE *, UNIT *, int32, const char *); | |
| /* | |
| 1711-A/B, 1712-A Teletypewriter | |
| Addresses | |
| Computer Instruction | |
| Q Register Output From A Input to A | |
| 0090 Write Read | |
| 0091 Director Function Director Status | |
| Operations: | |
| Director Function | |
| 15 10 9 8 7 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | X | | | X | X | X | | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | |
| | | | | | | Clr Controller | |
| | | | | | Clr Interrupts | |
| | | | | Data Interrupt Req. | |
| | | | Interrupt on EOP | |
| | | Interrupt on Alarm | |
| | Select Write Mode | |
| Select Read Mode | |
| Status Response: | |
| Director Status | |
| 15 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | | | | X | X | | | | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | |
| | | | | | | | | | Ready | |
| | | | | | | | | Busy | |
| | | | | | | | Interrupt | |
| | | | | | | Data | |
| | | | | | End of Operation | |
| | | | | Alarm | |
| | | | Lost Data | |
| | | Read Mode | |
| | Motor On | |
| Manual Interrupt | |
| The director status bits are distributed across the 3 IO_DEVICE data | |
| structures (TTdev, TTIdev and TTOdev). Global status bits will be held | |
| in TTdev and per-device status bits in the appropriate input or output | |
| device: | |
| IO_ST_READY TTdev | |
| IO_ST_BUSY TTdev | |
| IO_ST_INT TTIdev/TTOdev | |
| IO_ST_DATA TTIdev/TTOdev | |
| IO_ST_EOP TTIdev/TTOdev (equivalent to "Not Busy") | |
| IO_ST_ALARM TTIdev/TTOdev | |
| IO_ST_LOST TTdev | |
| IO_1711_RMODE TTIdev (set)/TTOdev (clear) | |
| IO_1711_MON TTdev | |
| IO_1711_MANUAL TTdev | |
| */ | |
| IO_DEVICE TTIdev = IODEV("TTI", "1711-A", 1711, 1, 1, 0, | |
| NULL, NULL, NULL, NULL, NULL, | |
| TTIstate, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| MASK_REGISTER1, 0, 0, 0, 0, NULL); | |
| IO_DEVICE TTOdev = IODEV("TTO", "1711-A", 1711, 1, 1, 0, | |
| NULL, NULL, NULL, NULL, NULL, | |
| TTOstate, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| MASK_REGISTER1, 0, 0, 0, 0, NULL); | |
| IO_DEVICE TTdev = IODEV("TT", "1711-A", 1711, 1, 1, 0, | |
| TTreject, TTin, TTout, NULL, NULL, | |
| TTstate, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| 0, 0, 0, 0, 0, NULL); | |
| /* | |
| * Define usage for "private IO_DEVICE data areas | |
| */ | |
| #define iod_holdFull iod_private4 | |
| #define iod_indelay iod_private9 | |
| #define iod_rmode iod_private4 | |
| #define IODP_TTI_XFER 0x01 /* Transfer to data hold reg */ | |
| #define IODP_TTI_MOTION 0x02 /* Paper motion delay */ | |
| #define IO_1711_CONTR (IO_1711_MANUAL | IO_1711_MON | IO_ST_LOST | \ | |
| IO_ST_BUSY | IO_ST_READY); | |
| #define IO_1711_IDEVICE (IO_ST_ALARM | IO_ST_EOP | IO_ST_INT | IO_ST_DATA) | |
| #define IO_1711_ODEVICE (IO_ST_ALARM | IO_ST_EOP | IO_ST_INT | IO_ST_DATA) | |
| /* TTI data structures | |
| tti_dev TTI device descriptor | |
| tti_unit TTI unit descriptor | |
| tti_reg TTI register list | |
| tti_mod TTI modifiers list | |
| */ | |
| uint8 tti_manualIntr = 0x7; | |
| #define TTUF_V_HDX (TTUF_V_UF + 0) | |
| #define TTUF_HDX (1 << TTUF_V_HDX) | |
| UNIT tti_unit = { UDATA(&tti_svc, UNIT_IDLE+TT_MODE_KSR+TTUF_HDX, 0), KBD_POLL_WAIT }; | |
| REG tti_reg[] = { | |
| { HRDATAD(MODE, TTdev.iod_rmode, 1, "Read/Write mode (Read == TRUE)") }, | |
| { HRDATAD(FUNCTION, TTdev.FUNCTION, 16, "Last director function issued") }, | |
| { HRDATAD(STATUS, TTdev.STATUS, 16, "Director status register") }, | |
| { HRDATAD(IENABLE, TTIdev.IENABLE, 16, "Interrupts enabled") }, | |
| { HRDATAD(INTRKEY, tti_manualIntr, 8, "Manual interrupt keycode") }, | |
| { NULL } | |
| }; | |
| MTAB tti_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "1711-A Console Terminal (Input)" }, | |
| { TTUF_HDX, 0, "full duplex", "FDX", | |
| NULL, NULL, NULL, "Set TT device to full duplex" }, | |
| { TTUF_HDX, TTUF_HDX, "half duplex", "HDX", | |
| NULL, NULL, NULL, "Set TT device to half duplex" }, | |
| { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, | |
| NULL, &show_addr, NULL, "Display equipment address" }, | |
| { 0 } | |
| }; | |
| DEBTAB tti_deb[] = { | |
| { "TRACE", DBG_DTRACE, "Trace device I/O requests" }, | |
| { "STATE", DBG_DSTATE, "Display device state changes" }, | |
| { "INTR", DBG_DINTR, "Display device interrupt requests" }, | |
| { "LOCATION", DBG_DLOC, "Display address of I/O instructions" }, | |
| { "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejects" }, | |
| { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, | |
| { NULL } | |
| }; | |
| DEVICE tti_dev = { | |
| "TTI", &tti_unit, tti_reg, tti_mod, | |
| 1, 10, 31, 1, 8, 8, | |
| NULL, NULL, &tti_reset, | |
| NULL, NULL, NULL, | |
| &TTdev, | |
| DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV, 0, tti_deb, | |
| NULL, NULL, &tt_help, NULL, NULL, NULL | |
| }; | |
| /* TTO data structures | |
| tto_dev TTO device descriptor | |
| tto_unit TTO unit descriptor | |
| tto_reg TTO register list | |
| tto_mod TTO modifiers list | |
| */ | |
| UNIT tto_unit = { UDATA(&tto_svc, TT_MODE_KSR+TTUF_HDX, 0), TT_OUT_WAIT }; | |
| REG tto_reg[] = { | |
| { HRDATAD(MODE, TTdev.iod_rmode, 1, "Read/Write mode (Read == TRUE)") }, | |
| { HRDATAD(FUNCTION, TTdev.FUNCTION, 16, "Last director function issued") }, | |
| { HRDATAD(STATUS, TTdev.STATUS, 16, "Director status register") }, | |
| { HRDATAD(IENABLE, TTOdev.IENABLE, 16, "Interrupts enabled") }, | |
| { NULL } | |
| }; | |
| MTAB tto_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "1711-A Console Terminal (Output)" }, | |
| { TTUF_HDX, 0, "full duplex", "FDX", | |
| NULL, NULL, NULL, "Set TT device to full duplex" }, | |
| { TTUF_HDX, TTUF_HDX, "half duplex", "HDX", | |
| NULL, NULL, NULL, "Set TT device to half duplex" }, | |
| { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, | |
| NULL, &show_addr, NULL, "Display equipment address" }, | |
| { 0 } | |
| }; | |
| DEBTAB tto_deb[] = { | |
| { "TRACE", DBG_DTRACE, "Trace device I/O requests" }, | |
| { "STATE", DBG_DSTATE, "Display devide state changes" }, | |
| { "INTR", DBG_DINTR, "Display device interrupt requests" }, | |
| { "LOCATION", DBG_DLOC, "Display address for I/O instructions" }, | |
| { "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejects" }, | |
| { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, | |
| { NULL } | |
| }; | |
| DEVICE tto_dev = { | |
| "TTO", &tto_unit, tto_reg, tto_mod, | |
| 1, 10, 31, 1, 8, 8, | |
| NULL, NULL, &tto_reset, | |
| NULL, NULL, NULL, | |
| &TTdev, | |
| DEV_DEBUG | DEV_NOEQUIP | DEV_OUTDEV, 0, tto_deb, | |
| NULL, NULL, &tt_help, NULL, NULL, NULL | |
| }; | |
| /* | |
| * Support routines for terminal physical input device | |
| */ | |
| /* | |
| * Dump the current state of the TTI device | |
| */ | |
| void TTIstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
| { | |
| char temp[32]; | |
| temp[0] = '\0'; | |
| if (TTIdev.iod_holdFull) | |
| sprintf(temp, ", Hold full (%02X)", tti_unit.buf & 0xFF); | |
| fprintf(DBGOUT, | |
| "%s[TTI %s: Func: %04X, Sta: %04X, Ena: %04X, Dly: %c%s]\r\n", | |
| INTprefix, where, | |
| TTIdev.FUNCTION, TTIdev.STATUS, TTIdev.IENABLE, | |
| TTIdev.iod_indelay + '0', temp); | |
| } | |
| /* Unit service */ | |
| t_stat tti_svc(UNIT *uptr) | |
| { | |
| int32 c, out; | |
| if (TTIdev.iod_indelay != 0) { | |
| /* | |
| * Waiting for functions related to character input: | |
| * | |
| * 1. Transfering the character from the TTY to the hold buffer. | |
| * 2. Wait for carriage control motion (CR, LF etc) | |
| */ | |
| if ((TTIdev.iod_indelay & IODP_TTI_XFER) != 0) { | |
| TTIdev.iod_indelay &= ~IODP_TTI_XFER; | |
| TTIdev.iod_holdFull = TRUE; | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() transfer complete]\r\n", INTprefix); | |
| if ((TTIdev.iod_indelay & IODP_TTI_MOTION) != 0) { | |
| sim_activate(uptr, TT_IN_MOTION); | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() motion delay]\r\n", INTprefix); | |
| return SCPE_OK; | |
| } | |
| } | |
| if ((TTIdev.iod_indelay & IODP_TTI_MOTION) != 0) { | |
| TTIdev.iod_indelay &= ~IODP_TTI_MOTION; | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() motion delay complete\r\n", INTprefix); | |
| } | |
| TTdev.STATUS &= IO_ST_BUSY; | |
| TTIdev.STATUS |= IO_ST_EOP; | |
| fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Motion delay"); | |
| TTrebuild(); | |
| /* | |
| * Resume normal polling | |
| */ | |
| sim_activate(uptr, uptr->wait); | |
| return SCPE_OK; | |
| } | |
| /* | |
| * Restart the poller. | |
| */ | |
| sim_activate(uptr, uptr->wait); | |
| if ((c = sim_poll_kbd()) < SCPE_KFLAG) | |
| return c; /* No character or error */ | |
| out = c & 0xfF; | |
| if (out == tti_manualIntr) { | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() manual interrupt]\r\n", INTprefix); | |
| TTdev.STATUS |= IO_1711_MANUAL; | |
| TTIdev.STATUS |= IO_ST_INT; | |
| RaiseExternalInterrupt(&tti_dev); | |
| return SCPE_OK; | |
| } | |
| if ((c & SCPE_BREAK) != 0) | |
| c = 0; | |
| else c = sim_tt_inpcvt(c, TT_GET_MODE(uptr->flags) | TTUF_KSR); | |
| if (TTIdev.iod_holdFull) { | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() hold register full]\r\n", INTprefix); | |
| TTIdev.STATUS |= IO_ST_ALARM | IO_ST_LOST; | |
| fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Lost char"); | |
| TTrebuild(); | |
| return SCPE_OK; | |
| } | |
| if (((uptr->flags & TTUF_HDX) != 0) && out && | |
| ((out = sim_tt_outcvt(out, TT_GET_MODE(uptr->flags) | TTUF_KSR)) >= 0)) { | |
| sim_putchar(out); | |
| tto_unit.pos++; | |
| } | |
| uptr->buf = c; | |
| uptr->pos++; | |
| /* | |
| * Start a delay while the input character is transferred from the TTY to | |
| * the hold buffer. | |
| */ | |
| TTIdev.iod_indelay = IODP_TTI_XFER; | |
| if ((out == '\r') || (out == '\n') || (out == '\f')) | |
| TTIdev.iod_indelay |= IODP_TTI_MOTION; | |
| sim_cancel(uptr); | |
| sim_activate(uptr, TT_IN_XFER); | |
| TTdev.STATUS |= IO_ST_BUSY; | |
| TTIdev.STATUS |= IO_ST_DATA; | |
| if ((tti_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, "%s[TTI: tti_svc() transfer started]\r\n", INTprefix); | |
| if ((tti_dev.dctrl & DBG_DSTATE) != 0) | |
| TTIstate("tti_svc", &tti_dev, &TTIdev); | |
| } | |
| fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Input char"); | |
| TTrebuild(); | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat tti_reset(DEVICE *dptr) | |
| { | |
| /*** Reset TT? ***/ | |
| TTIdev.STATUS = IO_1711_RMODE; | |
| TTIdev.iod_holdFull = FALSE; | |
| TTIdev.iod_indelay = 0; | |
| tti_unit.buf = 0; | |
| if (!sim_is_running) | |
| sim_activate(&tti_unit, tti_unit.wait); | |
| return SCPE_OK; | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus TTIin(IO_DEVICE *iod, uint8 reg) | |
| { | |
| /* | |
| * The logical TT device driver only passes INP operations for the data | |
| * register (0x90). | |
| */ | |
| TTIdev.iod_holdFull = FALSE; | |
| Areg = tti_unit.buf; | |
| TTdev.STATUS &= ~IO_ST_BUSY; | |
| TTIdev.STATUS |= IO_ST_EOP; | |
| TTIdev.STATUS &= ~(IO_ST_INT | IO_ST_DATA); | |
| TTrebuild(); | |
| rebuildPending(); | |
| return IO_REPLY; | |
| } | |
| void TTIcint(void) | |
| { | |
| /* | |
| * Clear all interrupt enables | |
| */ | |
| TTIdev.iod_ienable = 0; | |
| TTIdev.iod_oldienable = 0; | |
| /* | |
| * Clear all pending interrupts | |
| */ | |
| TTIdev.STATUS &= ~IO_1711_IDEVICE; | |
| if (TTIdev.iod_holdFull) | |
| TTIdev.STATUS |= IO_ST_DATA; | |
| } | |
| void TTIccont(void) | |
| { | |
| tti_reset(&tti_dev); | |
| TTIdev.iod_holdFull = FALSE; | |
| TTIcint(); | |
| } | |
| /* | |
| * Support routines for terminal physical output device | |
| */ | |
| /* | |
| * Dump the current state of the TTO device | |
| */ | |
| void TTOstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
| { | |
| char temp[32]; | |
| temp[0] = '\0'; | |
| if (TTOdev.iod_holdFull) | |
| sprintf(temp, ", Hold full (%02X)", tto_unit.buf & 0xFF); | |
| fprintf(DBGOUT, | |
| "%s[TTO %s: Func: %04X, Sta: %04X, Ena: %04X%s]\r\n", | |
| INTprefix, where, | |
| TTOdev.FUNCTION, TTOdev.STATUS, TTOdev.IENABLE, temp); | |
| } | |
| /* Unit service */ | |
| t_stat tto_svc(UNIT *uptr) | |
| { | |
| int32 c; | |
| t_stat r; | |
| c = sim_tt_outcvt(uptr->buf, TT_GET_MODE(uptr->flags) | TTUF_KSR); | |
| if (c >= 0) { | |
| if ((r = sim_putchar_s(c)) != SCPE_OK) { | |
| sim_activate(uptr, uptr->wait); /* Try again later */ | |
| return (r == SCPE_STALL) ? SCPE_OK : r; | |
| } | |
| } | |
| TTOdev.iod_holdFull = FALSE; | |
| TTOdev.STATUS |= IO_ST_EOP | IO_ST_DATA; | |
| TTdev.STATUS &= ~IO_ST_BUSY; | |
| if ((tto_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, "%s[TTO: tto_svc()]\r\n", INTprefix); | |
| if ((tto_dev.dctrl & DBG_DSTATE) != 0) | |
| TTOstate("tto_svc", &tto_dev, &TTOdev); | |
| } | |
| uptr->pos++; | |
| fw_IOintr(FALSE, &tto_dev, &TTOdev, 0, 0, 0xFFFF, "Output done"); | |
| TTrebuild(); | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat tto_reset(DEVICE *dptr) | |
| { | |
| /*** Reset TT? ***/ | |
| TTOdev.STATUS = IO_ST_DATA; | |
| TTOdev.iod_holdFull = FALSE; | |
| return SCPE_OK; | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus TTOout(IO_DEVICE *iod, uint8 reg) | |
| { | |
| /* | |
| * The logical TT device driver only passes OUT operations for the data | |
| * register (0x90). | |
| */ | |
| sim_activate(&tto_unit, tto_unit.wait); | |
| tto_unit.buf = Areg; | |
| TTOdev.iod_holdFull = TRUE; | |
| TTOdev.STATUS &= ~(IO_ST_EOP | IO_ST_INT | IO_ST_DATA); | |
| TTdev.STATUS |= IO_ST_BUSY; | |
| TTrebuild(); | |
| rebuildPending(); | |
| return IO_REPLY; | |
| } | |
| void TTOcint(void) | |
| { | |
| /* | |
| * Clear all interrupt enables | |
| */ | |
| TTOdev.iod_ienable = 0; | |
| TTOdev.iod_oldienable = 0; | |
| /* | |
| * Clear all pending interrupts | |
| */ | |
| TTOdev.STATUS &= ~IO_1711_ODEVICE; | |
| if (!TTOdev.iod_holdFull) | |
| TTOdev.STATUS |= IO_ST_DATA; | |
| } | |
| void TTOccont(void) | |
| { | |
| tto_reset(&tto_dev); | |
| sim_cancel(&tto_unit); | |
| TTOcint(); | |
| } | |
| /* | |
| * Support routines for the terminal logical device | |
| */ | |
| /* | |
| * Dump the current internal state of the TT device. Note that "dev" is | |
| * not used by this routine since it is a logical device which has no | |
| * associated physical device. | |
| */ | |
| void TTstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
| { | |
| fprintf(DBGOUT, | |
| "%s[TT %s: Func: %04X, Sta: %04X, Mode: %c]\r\n", | |
| INTprefix, where, iod->FUNCTION, iod->STATUS, | |
| iod->iod_rmode ? 'R' : 'W'); | |
| if ((tti_dev.dctrl & DBG_DSTATE) != 0) | |
| TTIstate(where, &tti_dev, &TTIdev); | |
| if ((tto_dev.dctrl & DBG_DSTATE) != 0) | |
| TTOstate(where, &tto_dev, &TTOdev); | |
| } | |
| /* | |
| * Reset routine | |
| */ | |
| void TTreset(void) | |
| { | |
| TTdev.STATUS = IO_1711_MON | IO_ST_READY; | |
| TTdev.iod_rmode = TRUE; | |
| } | |
| /* Rebuild the director status register */ | |
| uint16 TTrebuild(void) | |
| { | |
| TTdev.STATUS &= IO_1711_CONTR; | |
| if (TTdev.iod_rmode) { | |
| TTdev.STATUS |= (TTIdev.STATUS & IO_1711_IDEVICE) | IO_1711_RMODE; | |
| } else TTdev.STATUS |= TTOdev.STATUS & IO_1711_ODEVICE; | |
| TTdev.STATUS |= IO_1711_MON | IO_ST_READY; | |
| return TTdev.STATUS; | |
| } | |
| /* Check if I/O should be rejected */ | |
| t_bool TTreject(IO_DEVICE *iod, t_bool output, uint8 reg) | |
| { | |
| if (reg == 0) { | |
| if (output) | |
| return (TTdev.STATUS & IO_ST_BUSY) != 0; | |
| return !TTIdev.iod_holdFull; | |
| } | |
| if (output) { | |
| uint16 func = Areg & IO_1711_DIRMSK; | |
| if (func != 0) { | |
| if ((func & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA | \ | |
| IO_DIR_CINT | IO_DIR_CCONT)) != 0) | |
| return FALSE; | |
| /* | |
| * Select read/write mode must be set - reject if both are set. | |
| */ | |
| if ((func & (IO_1711_SREAD | IO_1711_SWRITE)) == | |
| (IO_1711_SREAD | IO_1711_SWRITE)) | |
| return TRUE; | |
| return (TTdev.STATUS & IO_ST_BUSY) != 0; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus TTin(IO_DEVICE *iod, uint8 reg) | |
| { | |
| /* | |
| * Certain invalid operations will already have been rejected in TTreject(). | |
| */ | |
| if (reg == 0) | |
| return TTIin(&TTIdev, reg); | |
| Areg = TTrebuild(); | |
| return IO_REPLY; | |
| } | |
| enum IOstatus TTout(IO_DEVICE *iod, uint8 reg) | |
| { | |
| t_bool rmode, changed = FALSE; | |
| DEVICE *dptr; | |
| /* | |
| * Certain invalid operation will already have been rejected in TTreject(). | |
| */ | |
| if (reg == 0) | |
| return TTOout(&TTOdev, reg); | |
| if ((IOAreg & IO_DIR_CCONT) != 0) { | |
| /* | |
| * Clear both sides of the controller and switch to read-mode. | |
| */ | |
| if (((tti_dev.dctrl & DBG_DSTATE) != 0) || | |
| ((tto_dev.dctrl & DBG_DSTATE)!= 0)) | |
| fprintf(DBGOUT, "%s[TT: Controller Reset\r\n", INTprefix); | |
| TTIccont(); | |
| TTOccont(); | |
| TTdev.STATUS &= ~IO_ST_BUSY; | |
| TTdev.iod_rmode = TRUE; | |
| } | |
| if ((IOAreg & IO_DIR_CINT) != 0) { | |
| /* | |
| * Clear interrupts for the currently active mode | |
| */ | |
| if (TTdev.iod_rmode) | |
| TTIcint(); | |
| else TTOcint(); | |
| TTdev.STATUS &= ~IO_1711_MANUAL; | |
| } | |
| /* | |
| * If Clear Controller or Clear Interrupts was set, don't process | |
| * read/write select bits. | |
| */ | |
| if ((IOAreg & (IO_DIR_CINT | IO_DIR_CCONT)) == 0) { | |
| /* | |
| * Check for switching modes. We've already rejected a request to select | |
| * both modes. | |
| */ | |
| if ((IOAreg & IO_1711_SREAD) != 0) | |
| TTdev.iod_rmode = TRUE; | |
| if ((IOAreg & IO_1711_SWRITE) != 0) { | |
| TTdev.iod_rmode = FALSE; | |
| TTIdev.STATUS &= ~IO_ST_LOST; | |
| } | |
| } | |
| rebuildPending(); | |
| rmode = TTdev.iod_rmode; | |
| if ((IOAreg & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA)) != 0) { | |
| iod = rmode ? &TTIdev : &TTOdev; | |
| dptr = rmode ? &tti_dev : &tto_dev; | |
| iod->iod_oldienable = iod->iod_ienable; | |
| iod->iod_ienable |= IOAreg & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA); | |
| changed = iod->iod_ienable != iod->iod_oldienable; | |
| } | |
| if (changed) | |
| if (!rmode) | |
| fw_IOintr(FALSE, dptr, iod, 0, 0, 0xFFFF, "Can output"); | |
| TTrebuild(); | |
| return IO_REPLY; | |
| } | |
| t_stat tt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
| { | |
| const char helpString[] = | |
| /****************************************************************************/ | |
| " The TTI/TTO device is a 1711-A teletype console. The device is\n" | |
| " implemented as 2 seperate devices within the simulator; TTI for input\n" | |
| " and TTO for output.\n" | |
| "1 Hardware Description\n" | |
| " The 1711-A consists of a teletype console terminal with an extra button\n" | |
| " which is used to generate a 'manual interrupt'. By default, the\n" | |
| " simulator uses the 'Control+G' combination to generate the interrupt.\n" | |
| " This key combination may be changed by:\n\n" | |
| "+sim> DEPOSIT TTI INTRKEY keycodeValue\n\n" | |
| "2 Equipment Address\n" | |
| " The console device is part of the low-speed package and, as such, is at\n" | |
| " fixed equipment address 1, station 1.\n" | |
| "2 $Registers\n" | |
| "\n" | |
| " These registers contain the emulated state of the device. These values\n" | |
| " don't necessarily relate to any detail of the original device being\n" | |
| " emulated but are merely internal details of the emulation.\n" | |
| "1 Configuration\n" | |
| " A %D device is configured with various simh SET commands\n" | |
| "2 $Set commands\n"; | |
| return scp_help(st, dptr, uptr, flag, helpString, cptr); | |
| } | |
| t_stat ptr_svc(UNIT *); | |
| t_stat ptr_reset(DEVICE *); | |
| t_stat ptr_attach(UNIT *, CONST char *); | |
| t_stat ptr_detach(UNIT *); | |
| enum IOstatus PTRin(IO_DEVICE *, uint8); | |
| enum IOstatus PTRout(IO_DEVICE *, uint8); | |
| t_stat ptr_help(FILE *, DEVICE *, UNIT *, int32, const char *); | |
| /* | |
| 1721-A/B/C/D, 1722-A/B Paper Tape Reader | |
| Addresses | |
| Computer Instruction | |
| Q Register Output From A Input to A | |
| 00A0 Read | |
| 00A1 Director Function Director Status | |
| Operations: | |
| Director Function | |
| 15 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | X | X | X | X | | | | X | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | |
| | | | | | Clr Controller | |
| | | | | Clr Interrupts | |
| | | | Data Interrupt Req. | |
| | | Interrupt on Alarm | |
| | Start Motion | |
| Stop Motion | |
| Status Response: | |
| Director Status | |
| 15 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | | | | | | | X | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | |
| | | | | | | | | | Ready | |
| | | | | | | | | Busy | |
| | | | | | | | Interrupt | |
| | | | | | | Data | |
| | | | | | Alarm | |
| | | | | Lost Data | |
| | | | Protected | |
| | | Existence Code | |
| | Paper Motion Failure | |
| Power On | |
| */ | |
| IO_DEVICE PTRdev = IODEV(NULL, "1721-A", 1721, 1, 2, 0, | |
| fw_reject, PTRin, PTRout, NULL, NULL, | |
| NULL, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| MASK_REGISTER1, 0, 0, 0, 0, NULL); | |
| /* | |
| * Define usage for "private" IO_DEVICE data areas. | |
| */ | |
| #define iod_PTRmotion iod_private | |
| /* | |
| * Current state of the device (STOPPED/STARTED). | |
| */ | |
| #define IODP_PTRSTOPPED 0x0000 | |
| #define IODP_PTRSTARTED 0x0001 | |
| #define IODP_PTR_MASK 0x0001 | |
| /* PTR data structures | |
| ptr_dev PTR device descriptor | |
| ptr_unit PTR unit descriptor | |
| ptr_reg PTR register list | |
| ptr_mod PTR modifiers list | |
| */ | |
| UNIT ptr_unit = { | |
| UDATA(&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), PTR_IN_WAIT | |
| }; | |
| REG ptr_reg[] = { | |
| { HRDATAD(FUNCTION, PTRdev.FUNCTION, 16, "Last director function issued") }, | |
| { HRDATAD(STATUS, PTRdev.STATUS, 16, "Director status register") }, | |
| { HRDATAD(IENABLE, PTRdev.IENABLE, 16, "Interrupts enabled") }, | |
| { NULL } | |
| }; | |
| MTAB ptr_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "1721-A Paper Tape Reader" }, | |
| { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, | |
| NULL, &show_addr, NULL, "Display equipment address" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", | |
| &set_stoponrej, NULL, NULL, "Stop simulation if I/O is rejected" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", | |
| &clr_stoponrej, NULL, NULL, "Don't stop simulation if I/O is rejected" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", | |
| &set_protected, NULL, NULL, "Device is protected (unimplemented)" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", | |
| &clear_protected, NULL, NULL, "Device is unprotected (unimplemented)" }, | |
| { 0 } | |
| }; | |
| DEBTAB ptr_deb[] = { | |
| { "TRACE", DBG_DTRACE, "Traced device I/O requests" }, | |
| { "STATE", DBG_DSTATE, "Display device state changes" }, | |
| { "LOCATION", DBG_DLOC, "Display address of I/O instructions" }, | |
| { "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejected" }, | |
| { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, | |
| { NULL } | |
| }; | |
| DEVICE ptr_dev = { | |
| "PTR", &ptr_unit, ptr_reg, ptr_mod, | |
| 1, 10, 31, 1, 8, 8, | |
| NULL, NULL, &ptr_reset, | |
| NULL, &ptr_attach, &ptr_detach, | |
| &PTRdev, | |
| DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_PROTECT, 0, ptr_deb, | |
| NULL, NULL, &ptr_help, NULL, NULL, NULL | |
| }; | |
| /* | |
| * Dump the current internal state of the PTR device. | |
| */ | |
| const char *PTRprivateState[2] = { | |
| "", "In Motion" | |
| }; | |
| void PTRstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
| { | |
| fprintf(DBGOUT, | |
| "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Private: %s\r\n", | |
| INTprefix, dev->name, where, | |
| iod->FUNCTION, iod->STATUS, iod->IENABLE, | |
| PTRprivateState[iod->iod_PTRmotion & IODP_PTR_MASK]); | |
| } | |
| /* Unit service */ | |
| t_stat ptr_svc(UNIT *uptr) | |
| { | |
| int32 temp; | |
| if ((ptr_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, "%s[PTR: ptr_svc() entry]\r\n", INTprefix); | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("svc entry", &ptr_dev, &PTRdev); | |
| } | |
| if ((uptr->flags & UNIT_ATT) == 0) { | |
| if ((ptr_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, | |
| "%s[PTR: ptr_svc() exit - no attached file]\r\n", INTprefix); | |
| return SCPE_OK; | |
| } | |
| if ((temp = getc(uptr->fileref)) == EOF) { | |
| if (feof(uptr->fileref)) { | |
| if ((ptr_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[PTR: ptr_svc() exit - EOF]\r\n", INTprefix); | |
| /* | |
| * We've run off the end of the tape. Indicate motion failure and alarm | |
| * status and generate an interrupt if requested. | |
| */ | |
| fw_IOintr(FALSE, &ptr_dev, &PTRdev, IO_ST_ALARM | IO_1721_MOTIONF, IO_ST_READY, 0xFFFF, "End of tape"); | |
| return SCPE_OK; | |
| } else perror("PTR I/O error"); | |
| clearerr(uptr->fileref); | |
| if ((ptr_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, "%s[PTR: ptr_svc() exit - Read Error]\r\n", INTprefix); | |
| return SCPE_IOERR; | |
| } | |
| uptr->buf = temp & 0xFF; | |
| uptr->pos++; | |
| fw_IOcompleteData(FALSE, &ptr_dev, &PTRdev, 0xFFFF, "Read Complete"); | |
| if ((ptr_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, | |
| "%s[PTR: ptr_svc() exit => %2X]\r\n", INTprefix, uptr->buf); | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("svc exit", &ptr_dev, &PTRdev); | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat ptr_reset(DEVICE *dptr) | |
| { | |
| DEVRESET(&PTRdev); | |
| PTRdev.iod_PTRmotion = IODP_PTRSTOPPED; | |
| if ((ptr_unit.flags & UNIT_ATT) != 0) | |
| fw_setForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); | |
| ptr_unit.buf = 0; | |
| return SCPE_OK; | |
| } | |
| /* Attach routine */ | |
| t_stat ptr_attach(UNIT *uptr, CONST char *cptr) | |
| { | |
| t_stat r; | |
| if ((r = attach_unit(uptr, cptr)) != SCPE_OK) | |
| return r; | |
| fw_setForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); | |
| return SCPE_OK; | |
| } | |
| /* Detach routine */ | |
| t_stat ptr_detach(UNIT *uptr) | |
| { | |
| if ((uptr->flags & UNIT_ATT) == 0) | |
| return SCPE_OK; | |
| fw_clearForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); | |
| return detach_unit(uptr); | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus PTRin(IO_DEVICE *iod, uint8 reg) | |
| { | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("before", &ptr_dev, &PTRdev); | |
| /* | |
| * The framework only passes IN operations for the data register (0x90) | |
| */ | |
| Areg = (Areg & 0xFF00) | ptr_unit.buf; | |
| fw_IOunderwayData(&PTRdev, 0); | |
| if (PTRdev.iod_PTRmotion == IODP_PTRSTARTED) | |
| sim_activate(&ptr_unit, ptr_unit.wait); | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("after", &ptr_dev, &PTRdev); | |
| return IO_REPLY; | |
| } | |
| enum IOstatus PTRout(IO_DEVICE *iod, uint8 reg) | |
| { | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("before", &ptr_dev, &PTRdev); | |
| switch (reg) { | |
| case 0x00: | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("after", &ptr_dev, &PTRdev); | |
| return IO_REJECT; | |
| case 0x01: | |
| doDirectorFunc(&ptr_dev, FALSE); | |
| if ((IOAreg & IO_DIR_START) != 0) { | |
| fw_setForced(&PTRdev, IO_ST_BUSY); | |
| PTRdev.iod_PTRmotion = IODP_PTRSTARTED; | |
| } | |
| if ((IOAreg & IO_DIR_STOP) != 0) { | |
| fw_clearForced(&PTRdev, IO_ST_BUSY); | |
| PTRdev.iod_PTRmotion = IODP_PTRSTOPPED; | |
| } | |
| if (PTRdev.iod_PTRmotion == IODP_PTRSTARTED) | |
| sim_activate(&ptr_unit, ptr_unit.wait); | |
| } | |
| if ((ptr_dev.dctrl & DBG_DSTATE) != 0) | |
| PTRstate("after", &ptr_dev, &PTRdev); | |
| return IO_REPLY; | |
| } | |
| t_stat ptr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
| { | |
| const char helpString[] = | |
| /****************************************************************************/ | |
| " The %D device is a 1721-A paper tape reader.\n" | |
| "1 Hardware Description\n" | |
| " The 1721-A consists of a controller and a physical paper tape reader.\n" | |
| "2 Equipment Address\n" | |
| " The paper tape reader is part of the low-speed package and, as such, is\n" | |
| " at fixed equipment address 1, station 2.\n" | |
| "2 $Registers\n" | |
| "\n" | |
| " These registers contain the emulated state of the device. These values\n" | |
| " don't necessarily relate to any detail of the original device being\n" | |
| " emulated but are merely internal details of the emulation. STATUS always\n" | |
| " contains the current status of the device as it would be read by an\n" | |
| " application program.\n" | |
| "1 Configuration\n" | |
| " A %D device is configured with various simh SET and ATTACH commands\n" | |
| "2 $Set commands\n"; | |
| return scp_help(st, dptr, uptr, flag, helpString, cptr); | |
| } | |
| t_stat ptp_svc(UNIT *); | |
| t_stat ptp_reset(DEVICE *); | |
| enum IOstatus PTPin(IO_DEVICE *, uint8); | |
| enum IOstatus PTPout(IO_DEVICE *, uint8); | |
| t_stat ptp_help(FILE *, DEVICE *, UNIT *, int32, const char *); | |
| /* | |
| 1723-A/B, 1724-A/B Paper Tape Punch | |
| Addresses | |
| Computer Instruction | |
| Q Register Output From A Input to A | |
| 00C0 Write | |
| 00C1 Director Function Director Status | |
| Operations: | |
| Director Function | |
| 15 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | X | X | X | X | | | | X | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | |
| | | | | | Clr Controller | |
| | | | | Clr Interrupts | |
| | | | Data Interrupt Req. | |
| | | Interrupt on Alarm | |
| | Start Motion | |
| Stop Motion | |
| Status Response: | |
| Director Status | |
| 15 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | | | | | | X | | X | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | |
| | | | | | | | | | Ready | |
| | | | | | | | | Busy | |
| | | | | | | | Interrupt | |
| | | | | | | Data | |
| | | | | | Alarm | |
| | | | | Protected | |
| | | | Existence Code | |
| | | Tape Break | |
| | Power On | |
| Tape Supply Low | |
| */ | |
| IO_DEVICE PTPdev = IODEV(NULL, "1723-A", 1723, 1, 4, 0, | |
| fw_reject, PTPin, PTPout, NULL, NULL, | |
| NULL, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| MASK_REGISTER1, 0, 0, 0, 0, NULL); | |
| /* | |
| * Define usage for "private" IO_DEVICE data areas. | |
| */ | |
| #define iod_PTPdelay iod_private | |
| /* | |
| * Current delay state of the device. | |
| */ | |
| #define IODP_PTPINTRWAIT 0x0001 | |
| #define IODP_PTPDATAWAIT 0x0002 | |
| #define IODP_PTP_MASK (IODP_PTPINTRWAIT | IODP_PTPDATAWAIT) | |
| /* PTP data structures | |
| ptp_dev PTP device descriptor | |
| ptp_unit PTP unit descriptor | |
| ptp_reg PTP register list | |
| ptp_mod PTP modifiers list | |
| */ | |
| UNIT ptp_unit = { | |
| UDATA(&ptp_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), PTP_OUT_WAIT | |
| }; | |
| REG ptp_reg[] = { | |
| { HRDATAD(FUNCTION, PTPdev.FUNCTION, 16, "Last director function issued") }, | |
| { HRDATAD(STATUS, PTPdev.STATUS, 16, "Director status register") }, | |
| { HRDATAD(IENABLE, PTPdev.IENABLE, 16, "Interrupts enabled") }, | |
| { NULL } | |
| }; | |
| MTAB ptp_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "1723-A Paper Tape Punch" }, | |
| { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, | |
| NULL, &show_addr, NULL, "Display equipment address" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", | |
| &set_stoponrej, NULL, NULL, "Stop simulation if I/O is rejected" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", | |
| &clr_stoponrej, NULL, NULL, "Don't stop simulation if I/O is rejected" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", | |
| &set_protected, NULL, NULL, "Device is protected (unimplemented)" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", | |
| &clear_protected, NULL, NULL, "Device is unprotected (unimplemented)" }, | |
| { 0 } | |
| }; | |
| DEBTAB ptp_deb[] = { | |
| { "TRACE", DBG_DTRACE, "Trace device I/O requests" }, | |
| { "STATE", DBG_DSTATE, "Display device state changes" }, | |
| { "LOCATION", DBG_DLOC, "Display address of I/O instructions" }, | |
| { "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejects" }, | |
| { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, | |
| { NULL } | |
| }; | |
| DEVICE ptp_dev = { | |
| "PTP", &ptp_unit, ptp_reg, ptp_mod, | |
| 1, 10, 31, 1, 8, 8, | |
| NULL, NULL, &ptp_reset, | |
| NULL, NULL, NULL, | |
| &PTPdev, | |
| DEV_DEBUG | DEV_NOEQUIP | DEV_OUTDEV | DEV_PROTECT, 0, ptp_deb, | |
| NULL, NULL, &ptp_help, NULL, NULL, NULL | |
| }; | |
| /* | |
| * Dump the current internal state of the PTP device. | |
| */ | |
| const char *PTPprivateState[4] = { | |
| "", "INTRWAIT", "DATAWAIT", "DATAWAIT,INTRWAIT" | |
| }; | |
| void PTPstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
| { | |
| fprintf(DBGOUT, | |
| "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Private: %s\r\n", | |
| INTprefix, dev->name, where, | |
| iod->FUNCTION, iod->STATUS, iod->IENABLE, | |
| PTPprivateState[iod->iod_PTPdelay & IODP_PTP_MASK]); | |
| } | |
| /* Unit service */ | |
| t_stat ptp_svc(UNIT *uptr) | |
| { | |
| if ((ptp_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, "%s[PTP: ptp_svc() entry]\r\n", INTprefix); | |
| if ((ptp_dev.dctrl & DBG_DSTATE) != 0) | |
| PTPstate("svc entry", &ptp_dev, &PTPdev); | |
| } | |
| if ((PTPdev.iod_PTPdelay & IODP_PTPINTRWAIT) != 0) { | |
| /* | |
| * Generate an interrupt indicating that the motor is up to speed. | |
| */ | |
| PTPdev.iod_PTPdelay &= ~IODP_PTPINTRWAIT; | |
| fw_IOintr(FALSE, &ptp_dev, &PTPdev, 0, 0, 0xFFFF, "Up to speed"); | |
| if ((PTPdev.iod_PTPdelay & IODP_PTP_MASK) != 0) | |
| sim_activate(&ptp_unit, ptp_unit.wait); | |
| goto done; | |
| } | |
| if ((PTPdev.iod_PTPdelay & IODP_PTPDATAWAIT) != 0) { | |
| /* | |
| * Now process the actual output of data to be punched. | |
| */ | |
| PTPdev.iod_PTPdelay &= ~IODP_PTPDATAWAIT; | |
| if ((uptr->flags & UNIT_ATT) != 0) { | |
| if (putc(uptr->buf, uptr->fileref) == EOF) { | |
| perror("PTP I/O error"); | |
| clearerr(uptr->fileref); | |
| } else uptr->pos++; | |
| } | |
| fw_IOcompleteData(FALSE, &ptp_dev, &PTPdev, 0xFFFF, "Output complete"); | |
| } | |
| done: | |
| if ((ptp_dev.dctrl & DBG_DTRACE) != 0) { | |
| fprintf(DBGOUT, "%s[PTP: ptp_svc() exit]\r\n", INTprefix); | |
| if ((ptp_dev.dctrl & DBG_DSTATE) != 0) | |
| PTPstate("svc exit", &ptp_dev, &PTPdev); | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat ptp_reset(DEVICE *dptr) | |
| { | |
| DEVRESET(&PTPdev); | |
| PTPdev.iod_PTPdelay = 0; | |
| fw_setForced(&PTPdev, IO_1723_POWERON | IO_ST_READY); | |
| ptp_unit.buf = 0; | |
| if (!sim_is_running) | |
| sim_activate(&ptp_unit, ptp_unit.wait); | |
| return SCPE_OK; | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus PTPin(IO_DEVICE *iod, uint8 reg) | |
| { | |
| /* | |
| * The framework only passes IN operations for the data register (0x90) | |
| */ | |
| return IO_REJECT; | |
| } | |
| enum IOstatus PTPout(IO_DEVICE *iod, uint8 reg) | |
| { | |
| if ((ptp_dev.dctrl & DBG_DSTATE) != 0) | |
| PTPstate("before", &ptp_dev, &PTPdev); | |
| switch (reg) { | |
| case 0x00: | |
| PTPdev.iod_PTPdelay |= IODP_PTPDATAWAIT; | |
| ptp_unit.buf = Areg; | |
| fw_IOunderwayData(&PTPdev, IO_ST_INT); | |
| rebuildPending(); | |
| sim_activate(&ptp_unit, ptp_unit.wait); | |
| if ((ptp_dev.dctrl & DBG_DSTATE) != 0) | |
| PTPstate("after", &ptp_dev, &PTPdev); | |
| break; | |
| case 0x01: | |
| /* | |
| * Check for illegal combination of commands | |
| */ | |
| if (STARTSTOP(Areg)) | |
| return IO_REJECT; | |
| if (doDirectorFunc(&ptp_dev, FALSE)) { | |
| /* | |
| * The device interrupt mask has been explicitly changed. If the | |
| * interrupt on data was just set and the device is ready, generate | |
| * a delayed interrupt. | |
| */ | |
| if ((ICHANGED(&PTPdev) & IO_DIR_DATA) != 0) { | |
| if ((PTPdev.STATUS & IO_ST_READY) != 0) { | |
| if ((PTPdev.iod_PTPdelay & IODP_PTP_MASK) == 0) { | |
| if ((ptp_dev.dctrl & DBG_DTRACE) != 0) | |
| fprintf(DBGOUT, | |
| "%sPTP: Mask change interrupt\n", INTprefix); | |
| sim_activate(&ptp_unit, ptp_unit.wait); | |
| PTPdev.iod_PTPdelay |= IODP_PTPINTRWAIT; | |
| } | |
| } | |
| } | |
| } | |
| if (IOAreg != 0) { | |
| if ((IOAreg & (IO_DIR_START | IO_DIR_STOP)) != 0) { | |
| sim_activate(&ptp_unit, 5 * ptp_unit.wait); | |
| PTPdev.iod_PTPdelay |= IODP_PTPINTRWAIT; | |
| if ((IOAreg & IO_DIR_START) != 0) { | |
| fw_setForced(&PTPdev, IO_ST_BUSY); | |
| PTPdev.STATUS |= IO_ST_DATA; | |
| } | |
| if ((IOAreg & IO_DIR_STOP) != 0) { | |
| fw_clearForced(&PTPdev, IO_ST_BUSY); | |
| PTPdev.STATUS &= ~IO_ST_DATA; | |
| } | |
| } | |
| } | |
| if ((ptp_dev.dctrl & DBG_DSTATE) != 0) | |
| PTPstate("after", &ptp_dev, &PTPdev); | |
| break; | |
| } | |
| return IO_REPLY; | |
| } | |
| t_stat ptp_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
| { | |
| const char helpString[] = | |
| /****************************************************************************/ | |
| " The %D device is a 1723-A paper tape punch.\n" | |
| "1 Hardware Description\n" | |
| " The 1723-A consists of a controller and a physical paper tape punch.\n" | |
| "2 Equipment Address\n" | |
| " The paper tape reader is part of the low-speed package and, as such, is\n" | |
| " at fixed equipment address 1, station 4.\n" | |
| "2 $Registers\n" | |
| "\n" | |
| " These registers contain the emulated state of the device. These values\n" | |
| " don't necessarily relate to any detail of the original device being\n" | |
| " emulated but are merely internal details of the emulation. STATUS always\n" | |
| " contains the current status of the device as it would be read by an\n" | |
| " application program.\n" | |
| "1 Configuration\n" | |
| " A %D device is configured with various simh SET and ATTACH commands\n" | |
| "2 $Set commands\n"; | |
| return scp_help(st, dptr, uptr, flag, helpString, cptr); | |
| } | |
| t_stat cdr_svc(UNIT *); | |
| t_stat cdr_reset(DEVICE *); | |
| enum IOstatus CDRin(IO_DEVICE *, uint8); | |
| enum IOstatus CDRout(IO_DEVICE *, uint8); | |
| /* | |
| 1729-A/B Card Reader | |
| Addresses | |
| Computer Instruction | |
| Q Register Output From A Input to A | |
| 00E0 Read | |
| 00E1 Director Function Director Status | |
| Operations: | |
| Director Function | |
| 15 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | X | X | X | X | | | | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | |
| | | | | | | Clr Controller | |
| | | | | | Clr Interrupts | |
| | | | | Data Interrupt Req. | |
| | | | Interrupt on End of Record | |
| | | Interrupt on Alarm | |
| | Start Motion | |
| Stop Motion | |
| Status Response: | |
| Director Status | |
| 15 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | X | X | X | X | X | X | | | | | | | | | | | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | |
| | | | | | | | | | Ready | |
| | | | | | | | | Busy | |
| | | | | | | | Interrupt | |
| | | | | | | Data | |
| | | | | | End Of Record | |
| | | | | Alarm | |
| | | | Lost Data | |
| | | Protected | |
| | Existence Code | |
| Read Station Empty | |
| */ | |
| IO_DEVICE CDRdev = IODEV(NULL, "1729", 1729, 1, 6, 0, | |
| fw_reject, CDRin, CDRout, NULL, NULL, | |
| NULL, NULL, NULL, NULL, | |
| 0xF, 2, | |
| MASK_REGISTER0 | MASK_REGISTER1, | |
| MASK_REGISTER1, 0, 0, 0, 0, NULL); | |
| /* CDR data structures | |
| cdr_dev CDR device descriptor | |
| cdr_unit CDR unit descriptor | |
| cdr_reg CDR register list | |
| cdr_mod CDR modifiers list | |
| */ | |
| UNIT cdr_unit = { | |
| UDATA(&cdr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), SERIAL_IN_WAIT | |
| }; | |
| REG cdr_reg[] = { | |
| { HRDATAD(FUNCTION, CDRdev.FUNCTION, 16, "Last director function issued") }, | |
| { HRDATAD(STATUS, CDRdev.STATUS, 16, "Director status register") }, | |
| { HRDATAD(IENABLE, CDRdev.IENABLE, 16, "Interrupts enabled") }, | |
| { NULL } | |
| }; | |
| MTAB cdr_mod[] = { | |
| { MTAB_XTD | MTAB_VDV, 0, "1729 Card Reader" }, | |
| { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, | |
| NULL, &show_addr, NULL, "Display equipment address" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", | |
| &set_protected, NULL, NULL, "Device is protected (unimplemented)" }, | |
| { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", | |
| &clear_protected, NULL, NULL, "Device is unprotected (unimplemented)" }, | |
| { 0 } | |
| }; | |
| DEBTAB cdr_deb[] = { | |
| { "TRACE", DBG_DTRACE, "Trace device I/O requests" }, | |
| { "STATE", DBG_DSTATE, "Display device state changes" }, | |
| { "LOCATION", DBG_DLOC, "Display address for I/O instructions" }, | |
| { "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejects" }, | |
| { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, | |
| { NULL } | |
| }; | |
| DEVICE cdr_dev = { | |
| "CDR", &cdr_unit, cdr_reg, cdr_mod, | |
| 1, 10, 31, 1, 8, 8, | |
| NULL, NULL, &cdr_reset, | |
| NULL, NULL, NULL, | |
| &CDRdev, | |
| DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_PROTECT, 0, cdr_deb, | |
| NULL, NULL, NULL, NULL, NULL, NULL | |
| }; | |
| /* Unit service */ | |
| t_stat cdr_svc(UNIT *uptr) | |
| { | |
| /*** TODO: Implement Card Reader support ***/ | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat cdr_reset(DEVICE *dptr) | |
| { | |
| DEVRESET(&CDRdev); | |
| ptr_unit.buf = 0; | |
| if (!sim_is_running) | |
| sim_activate(&cdr_unit, cdr_unit.wait); | |
| return SCPE_OK; | |
| } | |
| /* Perform I/O */ | |
| enum IOstatus CDRin(IO_DEVICE *iod, uint8 reg) | |
| { | |
| /* | |
| * The framework only passes IN operations for the data register (0x90) | |
| */ | |
| Areg = cdr_unit.buf; | |
| CDRdev.STATUS &= IO_ST_BUSY | IO_ST_DATA; | |
| return IO_REPLY; | |
| } | |
| enum IOstatus CDRout(IO_DEVICE *iod, uint8 reg) | |
| { | |
| switch (reg) { | |
| case 0x00: | |
| return IO_REJECT; | |
| case 0x01: | |
| doDirectorFunc(&cdr_dev, FALSE); | |
| /*** TODO: Process local director functions ***/ | |
| break; | |
| } | |
| return IO_REPLY; | |
| } | |
| /* | |
| * Return device 1 interrupt status. If any of the sub-devices have their | |
| * interrupt status active, return the device 1 interrupt mask bit. | |
| */ | |
| uint16 dev1INTR(DEVICE *dptr) | |
| { | |
| uint16 status; | |
| status = TTIdev.STATUS | TTOdev.STATUS | PTRdev.STATUS | PTPdev.STATUS | CDRdev.STATUS; | |
| return (status & IO_ST_INT) != 0 ? 1 << 1 : 0; | |
| } | |
| /* | |
| * Update a buffer indicating which device 1 stations are asserting interrupt | |
| * status. | |
| */ | |
| void dev1Interrupts(char *buf) | |
| { | |
| buf[0] = '\0'; | |
| if ((TTIdev.STATUS & IO_ST_INT) != 0) | |
| strcat(buf, " TTI"); | |
| if ((TTOdev.STATUS & IO_ST_INT) != 0) | |
| strcat(buf, "TTO"); | |
| if ((PTRdev.STATUS & IO_ST_INT) != 0) | |
| strcat(buf, " PTR"); | |
| if ((PTPdev.STATUS & IO_ST_INT) != 0) | |
| strcat(buf, " PTP"); | |
| if ((CDRdev.STATUS & IO_ST_INT) != 0) | |
| strcat(buf, " CDR"); | |
| } |