/* pdp6_ge.c: GE DATANET-760 with four consoles. Copyright (c) 2023, Lars Brinkhoff 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 LARS BRINKHOFF 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. This implements the MIT AI lab interface to a GE DATANET-760 with four consoles. It consists of two somewhat independent IO bus devices: 070 GTYI for keyboard input, and 750 GTYO for display output. This file presents the two as a single GE device to SIMH users. */ #include "kx10_defs.h" #include "sim_tmxr.h" #ifndef NUM_DEVS_GE #define NUM_DEVS_GE 0 #endif #if NUM_DEVS_GE > 0 #define GTYI_DEVNUM 0070 /* GE console input. */ #define GTYO_DEVNUM 0750 /* GE console output. */ #define GE_CONSOLES 4 #define GTYI_PIA 00007 /* PI assignment. */ #define GTYI_DONE 00010 /* Input data ready. */ #define GTYI_STATUS (GTYI_PIA | GTYI_DONE) #define GTYO_PIA 00007 /* PI assignment. */ #define GTYO_DONE 00100 /* Output data ready. */ #define GTYO_FROB 00200 /* Set done? */ #define GTYO_STATUS (GTYO_PIA | GTYO_DONE) #define STATUS u3 #define DATA u4 #define PORT u5 #define LP u6 #define GE_SOH 001 /* Start of header/message. */ #define GE_STX 002 /* Start of text. */ #define GE_ETX 003 /* End of text. */ static t_stat gtyi_svc(UNIT *uptr); static t_stat gtyo_svc(UNIT *uptr); static t_stat gtyi_devio(uint32 dev, uint64 *data); static t_stat gtyo_devio(uint32 dev, uint64 *data); static t_stat ge_reset(DEVICE *dptr); static t_stat ge_attach(UNIT *uptr, CONST char *ptr); static t_stat ge_detach(UNIT *uptr); static t_stat ge_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); static const char *ge_description(DEVICE *dptr); static void gtyo_done(void); static void gtyo_soh(char data); static void gtyo_adr(char data); static void gtyo_status(char data); static void gtyo_stx(char data); static void gtyo_text(char data); static void gtyo_lp(char data); static void (*gtyo_process)(char data) = gtyo_soh; DIB gtyi_dib = { GTYI_DEVNUM, 1, >yi_devio, NULL }; DIB gtyo_dib = { GTYO_DEVNUM, 1, >yo_devio, NULL }; UNIT ge_unit[2] = { { UDATA(>yi_svc, UNIT_IDLE|UNIT_ATTABLE, 0), 1000 }, { UDATA(>yo_svc, UNIT_IDLE|UNIT_ATTABLE, 0), 1000 }, }; #define gtyi_unit (&ge_unit[0]) #define gtyo_unit (&ge_unit[1]) static REG ge_reg[] = { { NULL } }; static MTAB ge_mod[] = { { 0 } }; #define DEBUG_TRC 0x0000400 static DEBTAB ge_debug[] = { {"TRACE", DEBUG_TRC, "Routine trace"}, {"CMD", DEBUG_CMD, "Command Processing"}, {"CONO", DEBUG_CONO, "CONO instructions"}, {"CONI", DEBUG_CONI, "CONI instructions"}, {"DATAIO", DEBUG_DATAIO, "DATAI/O instructions"}, {"IRQ", DEBUG_IRQ, "Debug IRQ requests"}, {0}, }; DEVICE ge_dev = { "GE", ge_unit, ge_reg, ge_mod, 2, 8, 18, 1, 8, 36, NULL, NULL, &ge_reset, NULL, &ge_attach, &ge_detach, >yi_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX, 0, ge_debug, NULL, NULL, NULL, &ge_attach_help, NULL, &ge_description }; DEVICE gtyo_dev = { "GTYO", NULL, NULL, NULL, 0, 8, 18, 1, 8, 36, NULL, NULL, NULL, NULL, NULL, NULL, >yo_dib, DEV_DIS | DEV_MUX, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static TMLN ge_ldsc[GE_CONSOLES]; static TMXR ge_desc = { GE_CONSOLES, 0, 0, ge_ldsc }; static t_stat ge_reset(DEVICE *dptr) { sim_debug(DEBUG_TRC, dptr, "ge_reset()\n"); if (ge_dev.flags & DEV_DIS) gtyo_dev.flags |= DEV_DIS; else gtyo_dev.flags &= ~DEV_DIS; if (gtyi_unit->flags & UNIT_ATT) { sim_activate(gtyi_unit, 10); } else { sim_cancel(gtyi_unit); sim_cancel(gtyo_unit); } return SCPE_OK; } static t_stat ge_attach(UNIT *uptr, CONST char *cptr) { t_stat r; if (!cptr || !*cptr) return SCPE_ARG; ge_desc.buffered = 1000; r = tmxr_attach(&ge_desc, uptr, cptr); if (r != SCPE_OK) return r; sim_debug(DEBUG_TRC, &ge_dev, "activate connection\n"); gtyi_unit->STATUS = 0; gtyo_unit->STATUS = 0; gtyo_process = gtyo_soh; sim_activate(gtyi_unit, 10); return SCPE_OK; } static t_stat ge_detach(UNIT *uptr) { t_stat r; if (!(uptr->flags & UNIT_ATT)) return SCPE_OK; sim_cancel(gtyi_unit); sim_cancel(gtyo_unit); r = tmxr_detach(&ge_desc, uptr); uptr->filename = NULL; return r; } static void gtyi_poll(UNIT *uptr) { int32 ch; int i; tmxr_poll_rx(&ge_desc); for (i = 0; i < GE_CONSOLES; i++) { if (!ge_ldsc[i].rcve) continue; if (!ge_ldsc[i].conn) { ge_ldsc[i].rcve = 0; tmxr_reset_ln(&ge_ldsc[i]); sim_debug(DEBUG_CMD, &ge_dev, "Port %d connection lost\n", i); continue; } ch = tmxr_getc_ln(&ge_ldsc[i]); if (ch & TMXR_VALID) { ch &= 0177; sim_debug(DEBUG_CMD, &ge_dev, "Port %d got %03o\n", i, ch); if (ch >= 0141 && ch <= 0172) ch -= 0100; else if (ch == 0140 || (ch >= 0173 && ch <= 0174)) { sim_debug(DEBUG_CMD, &ge_dev, "Discard invalid character\n"); continue; } uptr->DATA = ch; uptr->PORT = i; uptr->STATUS |= GTYI_DONE; if (uptr->STATUS & 7) sim_debug(DEBUG_CMD, &ge_dev, "GTYI interrupt on channel %d\n", uptr->STATUS & 7); set_interrupt(GTYI_DEVNUM, uptr->STATUS); ge_ldsc[i].rcve = 0; break; } } } static t_stat gtyi_svc(UNIT *uptr) { int32 n; n = tmxr_poll_conn(&ge_desc); if (n >= 0) { sim_debug(DEBUG_CMD, &ge_dev, "got connection\n"); ge_ldsc[n].rcve = 1; } if ((uptr->STATUS & GTYI_DONE) == 0) gtyi_poll(uptr); sim_activate_after(uptr, 10000); return SCPE_OK; } static t_stat gtyo_svc(UNIT *uptr) { switch (tmxr_putc_ln(&ge_ldsc[uptr->PORT], uptr->DATA)) { case SCPE_OK: sim_debug(DEBUG_CMD, &ge_dev, "Sent %03o to console %d\n", uptr->DATA, uptr->PORT); gtyo_done(); break; case SCPE_LOST: ge_ldsc[uptr->PORT].rcve = 0; tmxr_reset_ln(&ge_ldsc[uptr->PORT]); sim_debug(DEBUG_CMD, &ge_dev, "lost\n"); break; case SCPE_STALL: sim_debug(DEBUG_CMD, &ge_dev, "stall\n"); sim_clock_coschedule(uptr, 1000); break; default: break; } tmxr_poll_tx(&ge_desc); return SCPE_OK; } static t_stat ge_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { const char helpString[] = /* The '*'s in the next line represent the standard text width of a help line */ /****************************************************************************/ " The %D device connects a secondary processor that is sharing memory with the.\n" " primary.\n\n" " The device must be attached to a receive port, this is done by using the\n" " ATTACH command to specify the receive port number.\n" "\n" "+sim> ATTACH %U port\n" "\n" ; return scp_help(st, dptr, uptr, flag, helpString, cptr); } static const char *ge_description(DEVICE *dptr) { return "GE DATANET-760"; } static void gtyo_done(void) { gtyo_unit->STATUS |= GTYO_DONE; set_interrupt(GTYO_DEVNUM, gtyo_unit->STATUS); if (gtyo_unit->STATUS & 7) sim_debug(DEBUG_CMD, &ge_dev, "GTYO interrupt on channel %d\n", gtyo_unit->STATUS & 7); } static void gtyo_soh(char data) { if (data == GE_SOH) { gtyo_process = gtyo_adr; gtyo_unit->LP = 0; } gtyo_done(); } static void gtyo_adr(char data) { switch(data) { case 0140: case 0150: case 0160: case 0170: gtyo_unit->PORT = (data >> 3) & 3; gtyo_process = gtyo_status; break; default: gtyo_process = gtyo_soh; break; } gtyo_done(); } static void gtyo_status(char data) { if (data == 0) gtyo_process = gtyo_stx; else gtyo_process = gtyo_soh; gtyo_done(); } static void gtyo_stx(char data) { if (data == GE_STX) gtyo_process = gtyo_text; gtyo_done(); } static void gtyo_text(char data) { if (data == GE_ETX) { gtyo_process = gtyo_lp; gtyo_done(); } else { gtyo_unit->DATA = data; sim_activate_after(gtyo_unit, 10000); } } static void gtyo_lp(char data) { if (gtyo_unit->LP != 0) sim_debug(DEBUG_CMD, &ge_dev, "Checksum mismatch\n"); gtyo_process = gtyo_soh; gtyo_done(); } t_stat gtyi_devio(uint32 dev, uint64 *data) { UNIT *uptr = gtyi_unit; switch(dev & 03) { case CONO: sim_debug(DEBUG_CONO, &ge_dev, "GTYI %012llo\n", *data); uptr->STATUS &= ~GTYI_PIA; uptr->STATUS |= *data & GTYI_PIA; break; case CONI: *data = uptr->STATUS & GTYI_STATUS; sim_debug(DEBUG_CONI, &ge_dev, "GTYI %012llo\n", *data); break; case DATAI: *data = uptr->DATA | (uptr->PORT << 18); sim_debug(DEBUG_DATAIO, &ge_dev, "GTYI %012llo\n", *data); uptr->STATUS &= ~GTYI_DONE; sim_debug(DEBUG_IRQ, &ge_dev, "Clear GTYI interrupt\n"); clr_interrupt(GTYI_DEVNUM); ge_ldsc[uptr->PORT].rcve = 1; sim_activate(gtyi_unit, 10); break; } return SCPE_OK; } t_stat gtyo_devio(uint32 dev, uint64 *data) { UNIT *uptr = gtyo_unit; int ch; switch(dev & 03) { case CONO: sim_debug(DEBUG_CONO, &ge_dev, "GTYO %012llo\n", *data); sim_debug(DEBUG_IRQ, &ge_dev, "Clear GTYO interrupt\n"); clr_interrupt(GTYO_DEVNUM); uptr->STATUS &= ~GTYO_PIA; uptr->STATUS |= *data & GTYO_PIA; if (*data & GTYO_FROB) gtyo_done(); break; case CONI: *data = uptr->STATUS & GTYO_STATUS; sim_debug(DEBUG_CONI, &ge_dev, "GTYO %012llo\n", *data); break; case DATAO: sim_debug(DEBUG_DATAIO, &ge_dev, "GTYO %012llo\n", *data); if (uptr->STATUS & GTYO_DONE) { sim_debug(DEBUG_IRQ, &ge_dev, "Clear GTYO interrupt\n"); clr_interrupt(GTYO_DEVNUM); uptr->STATUS &= ~GTYO_DONE; ch = *data & 0177; ch ^= 0177; ch = ((ch << 1) | (ch >> 6)) & 0177; if (ch >= 040 && ch <= 0137) sim_debug(DEBUG_DATAIO, &ge_dev, "Character %03o %c\n", ch, ch); else sim_debug(DEBUG_DATAIO, &ge_dev, "Character %03o\n", ch); uptr->LP ^= ch; sim_debug(DEBUG_DATAIO, &ge_dev, "LP %03o\n", uptr->LP); gtyo_process(ch); } break; } return SCPE_OK; } #endif