From e8bceec8e2e681bec741796fa99d3dcd22e64ad8 Mon Sep 17 00:00:00 2001 From: Charles Anthony Date: Sat, 15 Feb 2020 13:19:37 -0800 Subject: [PATCH] H316: Implement the IMP host interface. - From Charles Anthony passed through Lars Brinkhoff - Cleaned up for cross platform builds - Mark Pizzolato --- H316/h316_hi.c | 404 ++++++++++++++++++++++++++++++++++++--- H316/h316_imp.h | 20 +- H316/h316_mi.c | 7 + H316/h316_udp.c | 14 +- H316/tests/dps8m_imp.cmd | 43 +++++ 5 files changed, 451 insertions(+), 37 deletions(-) create mode 100644 H316/tests/dps8m_imp.cmd diff --git a/H316/h316_hi.c b/H316/h316_hi.c index 5a5e53d0..4b7874ec 100644 --- a/H316/h316_hi.c +++ b/H316/h316_hi.c @@ -50,6 +50,44 @@ IMPLEMENT THIS MODULE!!! */ + +// 1822 sect 4.5 Host cable connections +// +// IMP Ready +// +// IMP Ready Test Host --> IMP trigger IMP Master Ready +// IMP Master Ready IMP --> Host +// +// Host Ready +// +// Host Ready Test IMP --> Host trigger Host Master Ready +// Host Master Ready IMP --> Host +// +// Host to IMP Data +// +// Host-to-IMP Data Line Host --> IMP +// There's-Your-Host-Bit Host --> IMP +// Ready-For-Next-Host-Bit IMP --> Host +// Last-Host-Bit Host --> IMP +// +// IMP to Host Data +// +// IMP-to-Host Data Line IMP --> Host +// There's-Your-IMP-Bit IMP --> Host +// Ready-For-Next-IMP-Bit Host --> IMP +// Last-IMP-Bit IMP --> Host +// + +// Last-IMP-Bit is implemented as an out-of-band flag in UDP_PACKET +#define PFLG_FINAL 00001 + +// TODO +// +// For the nonce, assume ready bits are always on. We need an out-of-band +// packet exchange to model the ready bit behavior. (This could also reset +// the UDP_PACKET sequence numbers.) + + #ifdef VM_IMPTIP #include "h316_defs.h" // H316 emulator definitions #include "h316_imp.h" // ARPAnet IMP/TIP definitions @@ -66,7 +104,8 @@ int32 hi1_io (int32 inst, int32 fnc, int32 dat, int32 dev); int32 hi2_io (int32 inst, int32 fnc, int32 dat, int32 dev); int32 hi3_io (int32 inst, int32 fnc, int32 dat, int32 dev); int32 hi4_io (int32 inst, int32 fnc, int32 dat, int32 dev); -t_stat hi_service (UNIT *uptr); +t_stat hi_rx_service (UNIT *uptr); +void hi_rx_local (uint16 line, uint16 txnext, uint16 txcount); t_stat hi_reset (DEVICE *dptr); t_stat hi_attach (UNIT *uptr, CONST char *cptr); t_stat hi_detach (UNIT *uptr); @@ -80,7 +119,7 @@ t_stat hi_detach (UNIT *uptr); // Host interface data blocks ... // The HIDB is our own internal data structure for each host. It keeps data // about the TCP/IP connection, buffers, etc. -#define HI_HIDB(N) {0, 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE} +#define HI_HIDB(N) {FALSE, FALSE, 0, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, HI_TXBPS} HIDB hi1_db = HI_HIDB(1), hi2_db = HI_HIDB(2); HIDB hi3_db = HI_HIDB(3), hi4_db = HI_HIDB(4); @@ -98,7 +137,7 @@ DIB hi3_dib = HI_DIB(3), hi4_dib = HI_DIB(4); // The UNIT data also contains four "user" fields which devices can reuse for // any purpose and we take advantage of that to store the line number. #define hline u3 // our host line number is stored in user data 3 -#define HI_UNIT(N) {UDATA (&hi_service, UNIT_ATTABLE, 0), HI_POLL_DELAY, N, 0, 0, 0} +#define HI_UNIT(N) {UDATA (&hi_rx_service, UNIT_ATTABLE, 0), HI_POLL_DELAY, N, 0, 0, 0} UNIT hi1_unit = HI_UNIT(1), hi2_unit = HI_UNIT(2); UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4); @@ -106,17 +145,19 @@ UNIT hi3_unit = HI_UNIT(3), hi4_unit = HI_UNIT(4); // These are the simh device "registers" - they c can be viewed with the // "EXAMINE HIxn STATE" command and modified by "DEPOSIT HIxn ..." #define HI_REG(N) { \ - { DRDATA (POLL, hi##N##_unit.wait, 24), REG_NZ + PV_LEFT }, \ + { DRDATA (POLL, hi##N##_unit.wait, 24), REG_NZ + PV_LEFT }, \ { FLDATA (RXIRQ, dev_ext_int, INT_V_HI##N##RX-INT_V_EXTD) }, \ { FLDATA (RXIEN, dev_ext_enb, INT_V_HI##N##RX-INT_V_EXTD) }, \ { DRDATA (RXTOT, hi##N##_db.rxtotal,32), REG_RO + PV_LEFT }, \ { FLDATA (TXIRQ, dev_ext_int, INT_V_HI##N##TX-INT_V_EXTD) }, \ { FLDATA (TXIEN, dev_ext_enb, INT_V_HI##N##TX-INT_V_EXTD) }, \ { DRDATA (TXTOT, hi##N##_db.txtotal,32), REG_RO + PV_LEFT }, \ - { FLDATA (LLOOP, hi##N##_db.lloop, 0), PV_RZRO }, \ + { FLDATA (LLOOP, hi##N##_db.iloop, 0), PV_RZRO }, \ { FLDATA (ERROR, hi##N##_db.error, 0), PV_RZRO }, \ { FLDATA (READY, hi##N##_db.ready, 0), PV_RZRO }, \ { FLDATA (FULL, hi##N##_db.full , 0), PV_RZRO }, \ + { DRDATA (LINK, hi##N##_db.link, 32), REG_RO + PV_LEFT }, \ + { DRDATA (BPS, hi##N##_db.bps, 32), REG_NZ + PV_LEFT }, \ { NULL } \ } REG hi1_reg[] = HI_REG(1), hi2_reg[] = HI_REG(2); @@ -184,8 +225,9 @@ HIDB *const hi_hidbs [HI_NUM] = {&hi1_db, &hi2_db, &hi3_db, &hi4_db }; // Reset receiver (clear flags AND initialize all data) ... void hi_reset_rx (uint16 host) { - PHIDB(host)->lloop = PHIDB(host)->error = PHIDB(host)->enabled = FALSE; - PHIDB(host)->ready = PHIDB(host)->eom = FALSE; + PHIDB(host)->iloop = PHIDB(host)->error = PHIDB(host)->enabled = FALSE; + PHIDB(host)->ready = TRUE; // XXX + PHIDB(host)->eom = FALSE; PHIDB(host)->rxtotal = 0; CLR_RX_IRQ(host); CLR_RX_IEN(host); } @@ -193,11 +235,255 @@ void hi_reset_rx (uint16 host) // Reset transmitter (clear flags AND initialize all data) ... void hi_reset_tx (uint16 host) { - PHIDB(host)->lloop = PHIDB(host)->enabled = PHIDB(host)->full = FALSE; + PHIDB(host)->iloop = PHIDB(host)->enabled = PHIDB(host)->full = FALSE; PHIDB(host)->txtotal = 0; CLR_TX_IRQ(host); CLR_TX_IEN(host); } +// Get the DMC control words (starting address, end and length) for the channel. +void hi_get_dmc (uint16 dmc, uint16 *pnext, uint16 *plast, uint16 *pcount) +{ + uint16 dmcad; + if ((dmc(DMC1+DMC_MAX-1))) { + *pnext = *plast = *pcount = 0; return; + } + dmcad = DMC_BASE + (dmc-DMC1)*2; + *pnext = M[dmcad] & X_AMASK; *plast = M[dmcad+1] & X_AMASK; + *pcount = (*plast - *pnext + 1) & DMASK; +} + +// Update the DMC words to show "count" words transferred. +void hi_update_dmc (uint32 dmc, uint32 count) +{ + uint16 dmcad, next; + if ((dmc(DMC1+DMC_MAX-1))) return; + dmcad = DMC_BASE + (dmc-DMC1)*2; + next = M[dmcad]; + M[dmcad] = (next & DMA_IN) | ((next+count) & X_AMASK); +} + +// Link error recovery ... +void hi_link_error (uint16 line) +{ + // Any physical I/O error, either for the UDP link or a COM port, prints a + // message and detaches the modem. It's up to the user to decide what to do + // after that... + fprintf(stderr,"HI%d - UNRECOVERABLE I/O ERROR!\n", line); + hi_reset_rx(line); hi_reset_tx(line); + sim_cancel(PUNIT(line)); hi_detach(PUNIT(line)); + PHIDB(line)->link = NOLINK; +} + + + +//////////////////////////////////////////////////////////////////////////////// +/////////////////// D E B U G G I N G R O U T I N E S //////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Log a modem input or output including DMC words ... +void hi_debug_hio (uint16 line, uint32 dmc, const char *ptext) +{ + uint16 next, last, count; + if (!ISHDBG(line, IMP_DBG_IOT)) return; + hi_get_dmc(dmc, &next, &last, &count); + sim_debug(IMP_DBG_IOT, PDEVICE(line), + "start %s (PC=%06o, next=%06o, last=%06o, count=%d)\n", + ptext, PC-1, next, last, count); +} + +// Log the contents of a message sent or received ... +void hi_debug_msg (uint16 line, uint16 next, uint16 count, const char *ptext) +{ + uint16 i; char buf[CBUFSIZE]; int len = 0; + if (!ISHDBG(line, MI_DBG_MSG)) return; + sim_debug(MI_DBG_MSG, PDEVICE(line), "message %s (length=%d)\n", ptext, count); + for (i = 1, len = 0; i <= count; ++i) { + len += sprintf(buf+len, "%06o ", M[next+i-1]); + if (((i & 7) == 0) || (i == count)) { + sim_debug(MI_DBG_MSG, PDEVICE(line), "- %s\n", buf); len = 0; + } + } +} + + +//////////////////////////////////////////////////////////////////////////////// +///////////////// T R A N S M I T A N D R E C E I V E ////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Start the transmitter ... +void hi_start_tx (uint16 line, uint16 flags) +{ + // This handles all the work of the "start host output" OCP, including + // extracting the packet from H316 memory, EXCEPT for actually setting the + // transmit done interrupt. That's handled by the RTC polling routine after + // a delay that we calculate.. + uint16 next, last, count; uint32 nbits; t_stat ret; + + // Get the DMC words for this channel and update the next pointer as if the + // transfer actually occurred. + hi_get_dmc(PDIB(line)->txdmc, &next, &last, &count); + hi_update_dmc(PDIB(line)->txdmc, count); + hi_debug_msg (line, next, count, "sent"); + + // Transmit the data, handling both the interface loopback AND the line loop + // back flags in the process. Note that in particular the interface loop back + // does NOT require that the host be attached! + if (PHIDB(line)->iloop) { + hi_rx_local(line, next, count); + } else if (PHIDB(line)->link != NOLINK) { +//for (int i = 0; i < count; i ++) fprintf (stderr, "%06o\r\n", M[next + i]); + // The host interface needs some out-of-band data bits. The format + // of the data going out over the wire is: + // struct + // uint16 flags; + // uint16 data [MAXDATA - 1]; + // Put the packet into a temp buffer for assembly. + uint16 *tmp = (uint16 *)malloc ((count + 1) * sizeof (*tmp)); + uint16 i; + + tmp [0] = flags; + for (i = 0; i < count; i ++) + tmp [i + 1] = M [next+i]; + //ret = udp_send(PDEVICE(line), PHIDB(line)->link, &M[next], count); + ret = udp_send(PDEVICE(line), PHIDB(line)->link, tmp, count); + free (tmp); + if (ret != SCPE_OK && ret != 66) hi_link_error(line); + } + +// XXX the host interface is significantly faster... Need new math here. +// 1822 pg 4-9 100 KBS + + // Do some fancy math to figure out how long, in RTC ticks, it would actually + // take to transmit a packet of this length with a real modem and phone line. + // Note that the "+12" is an approximation for the modem overhead, including + // DLE, STX, ETX and checksum bytes, that would be added to the packet. + nbits = (((uint32) count)*2UL + 12UL) * 8UL; + PHIDB(line)->txdelay = (nbits * 1000000UL) / (PHIDB(line)->bps * rtc_interval); + //fprintf(stderr,"HI%d - transmit packet, length=%d, bits=%ld, interval=%ld, delay=%ld\n", line, count, nbits, rtc_interval, PHIDB(line)->txdelay); + + // That's it - we're done until it's time for the TX done interrupt ... + CLR_TX_IRQ(line); +} + +// Poll for transmitter done interrupts ... +void hi_poll_tx (uint16 line, uint32 quantum) +{ + // This routine is called, via the RTC service, to count down the interval + // until the transmitter finishes. When it hits zero, an interrupt occurs. + if (PHIDB(line)->txdelay == 0) return; + if (PHIDB(line)->txdelay <= quantum) { + SET_TX_IRQ(line); PHIDB(line)->txdelay = 0; PHIDB(line)->txtotal++; + sim_debug(IMP_DBG_IOT, PDEVICE(line), "transmit done (message #%d, intreq=%06o)\n", PHIDB(line)->txtotal, dev_ext_int); + } else + PHIDB(line)->txdelay -= quantum; +} + +// Start the receiver ... +void hi_start_rx (uint16 line) +{ + // "Starting" the receiver simply sets the RX pending flag. Nothing else + // needs to be done (nothing else _can_ be done!) until we actually receive + // a real packet. + + // We check for the case of another receive already pending, but I don't + // think the real hardware detected this or considered it an error condition. + if (PHIDB(line)->rxpending) { + sim_debug(IMP_DBG_WARN,PDEVICE(line),"start input while input already pending\n"); + } + PHIDB(line)->rxpending = TRUE; PHIDB(line)->rxerror = FALSE; + CLR_RX_IRQ(line); +} + +// Poll for receiver data ... +void hi_poll_rx (uint16 line) +{ + // This routine is called by hi_rx_service to poll for any packets received. + // This is done regardless of whether a receive is pending on the line. If + // a packet is waiting AND a receive is pending then we'll store it and finish + // the receive operation. If a packet is waiting but no receive is pending + // then the packet is discarded... + uint16 next, last, maxbuf; uint16 *pdata; int16 count; + uint16 *tmp = NULL; + uint16 i; + + // If the modem isn't attached, then the read never completes! + if (PHIDB(line)->link == NOLINK) return; + + // Get the DMC words for this channel, or zeros if no read is pending ... + if (PHIDB(line)->rxpending) { + hi_get_dmc(PDIB(line)->rxdmc, &next, &last, &maxbuf); + pdata = &M[next]; + } else { + next = last = maxbuf = 0; pdata = NULL; + } + // Try to read a packet. If we get nothing then just return. + // The host interface needs some out-of-band data bits. The format + // of the data coming over the wire is: + // struct + // uint16 flags; + // uint16 data [MAXDATA - 1]; + // Read the packet into a temp buffer for disassembly. + tmp = (uint16 *)malloc (MAXDATA * sizeof (*tmp)); + + //count = udp_receive(PDEVICE(line), PHIDB(line)->link, pdata, maxbuf); + count = udp_receive(PDEVICE(line), PHIDB(line)->link, tmp, maxbuf+1); + if (count == 0) {free (tmp); return; } + if (count < 0) {free (tmp); hi_link_error(line); return; } + + PHIDB(line)->eom = !! tmp[0] & PFLG_FINAL; + for (i = 0; i < count - 1; i ++) + * (pdata + i) = tmp [i + 1]; + free (tmp); + tmp = NULL; + // Now would be a good time to worry about whether a receive is pending! + if (!PHIDB(line)->rxpending) { + sim_debug(IMP_DBG_WARN, PDEVICE(line), "data received with no input pending\n"); + return; + } + + // We really got a packet! Update the DMC pointers to reflect the actual + // size of the packet received. If the packet length would have exceeded the + // receiver buffer, then that sets the error flag too. + if (count > maxbuf) { + sim_debug(IMP_DBG_WARN, PDEVICE(line), "receiver overrun (length=%d maxbuf=%d)\n", count, maxbuf); + PHIDB(line)->rxerror = TRUE; count = maxbuf; + } + hi_update_dmc(PDIB(line)->rxdmc, count); + hi_debug_msg (line, next, count, "received"); + + // Assert the interrupt request and we're done! + SET_RX_IRQ(line); PHIDB(line)->rxpending = FALSE; PHIDB(line)->rxtotal++; + sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PHIDB(line)->rxtotal, dev_ext_int); +} + + +// Receive cross patched data ... +void hi_rx_local (uint16 line, uint16 txnext, uint16 txcount) +{ + // This routine is invoked by the hi_start_tx() function when this modem has + // the "interface cross patch" bit set. This flag causes the modem to talk to + // to itself, and data sent by the transmitter goes directly to the receiver. + // The modem is bypassed completely and in fact need not even be connected. + // This is essentially a special case of the hi_poll_rx() routine and it's a + // shame they don't share more code, but that's the way it is. + // Get the DMC words for this channel, or zeros if no read is pending ... + uint16 rxnext, rxlast, maxbuf; + + // If no read is pending, then just throw away the data ... + if (!PHIDB(line)->rxpending) return; + + // Get the DMC words for the receiver and copy data from one buffer to the other. + hi_get_dmc(PDIB(line)->rxdmc, &rxnext, &rxlast, &maxbuf); + if (txcount > maxbuf) {txcount = maxbuf; PHIDB(line)->rxerror = TRUE;} + memmove(&M[rxnext], &M[txnext], txcount * sizeof(uint16)); + + // Update the receiver DMC pointers, assert IRQ and we're done! + hi_update_dmc(PDIB(line)->rxdmc, txcount); + hi_debug_msg (line, rxnext, txcount, "received"); + SET_RX_IRQ(line); PHIDB(line)->rxpending = FALSE; PHIDB(line)->rxtotal++; + sim_debug(IMP_DBG_IOT, PDEVICE(line), "receive done (message #%d, intreq=%06o)\n", PHIDB(line)->rxtotal, dev_ext_int); +} + //////////////////////////////////////////////////////////////////////////////// @@ -222,25 +508,32 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev) switch (fnc) { case 000: // HnROUT - start regular host output ... - sim_debug(IMP_DBG_IOT, PDEVICE(host), "start regular output (PC=%06o)\n", PC-1); - return dat; + hi_debug_hio(host, PDIB(host)->txdmc, "output"); + hi_start_tx(host, 0); return dat; case 001: // HnIN - start host input ... - sim_debug(IMP_DBG_IOT, PDEVICE(host), "start input (PC=%06o)\n", PC-1); + hi_debug_hio(host, PDIB(host)->rxdmc, "input"); + hi_start_rx(host); return dat; return dat; case 002: // HnFOUT - start final host output ... sim_debug(IMP_DBG_IOT, PDEVICE(host), "start final output (PC=%06o)\n", PC-1); + hi_start_tx(host, PFLG_FINAL); return dat; return dat; case 003: // HnXP - cross patch ... sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable cross patch (PC=%06o)\n", PC-1); + PHIDB(host)->iloop = TRUE; + udp_set_link_loopback (PDEVICE(host), PHIDB(host)->link, TRUE); return dat; case 004: // HnUNXP - un-cross patch ... sim_debug(IMP_DBG_IOT, PDEVICE(host), "disable cross patch (PC=%06o)\n", PC-1); + PHIDB(host)->iloop = FALSE; + udp_set_link_loopback (PDEVICE(host), PHIDB(host)->link, FALSE); return dat; case 005: +//fprintf (stderr, "HnENAB unimp.\r\n"); // HnENAB - enable ... sim_debug(IMP_DBG_IOT, PDEVICE(host), "enable host (PC=%06o)\n", PC-1); return dat; @@ -251,18 +544,22 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev) switch (fnc) { case 000: // HnERR - skip on host error ... - sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on error (PC=%06o %s)\n", PC-1, "NOSKIP"); - return dat; + sim_debug(IMP_DBG_IOT,PDEVICE(host),"skip on error (PC=%06o, %s)\n", + PC-1, PHIDB(host)->rxerror ? "SKIP" : "NOSKIP"); + return PHIDB(host)->rxerror ? IOSKIP(dat) : dat; case 001: // HnRDY - skip on host ready ... - sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on ready (PC=%06o %s)\n", PC-1, "NOSKIP"); - return dat; + //sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on ready (PC=%06o %s)\n", PC-1, PHIDB(host)->ready ? "SKIP" : "NOSKIP"); +//fprintf (stderr, "HnRDY unimpl.; always ready\r\n"); + return PHIDB(host)->ready ? IOSKIP(dat) : dat; case 002: // HnEOM - skip on end of message ... - sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on end of message (PC=%06o %s)\n", PC-1, "NOSKIP"); + sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on end of message (PC=%06o %s)\n", PC-1, PHIDB(host)->eom ? "SKIP" : "NOSKIP"); + return PHIDB(host)->eom ? IOSKIP(dat) : dat; return dat; case 005: // HnFULL - skip on host buffer full ... +fprintf (stderr, "HnFULL unimp.\r\n"); sim_debug(IMP_DBG_IOT, PDEVICE(host), "skip on buffer full (PC=%06o %s)\n", PC-1, "NOSKIP"); return dat; } @@ -280,9 +577,27 @@ int32 hi_io (uint16 host, int32 inst, int32 fnc, int32 dat, int32 dev) /////////////////// H O S T E V E N T S E R V I C E //////////////////// //////////////////////////////////////////////////////////////////////////////// -// Unit service ... -t_stat hi_service (UNIT *uptr) +// Receiver service ... +t_stat hi_rx_service (UNIT *uptr) { + // This is the standard simh "service" routine that's called when an event + // queue entry expires. It just polls the receiver and reschedules itself. + // That's it! + uint16 line = uptr->hline; + hi_poll_rx(line); + sim_activate(uptr, uptr->wait); + return SCPE_OK; +} + +// Transmitter service ... +t_stat hi_tx_service (uint32 quantum) +{ + // This is the special transmitter service routine that's called by the RTC + // service every time the RTC is updated. This routine polls ALL the modem + // transmitters (or at least any which are active) and figures out whether it + // is time for an interrupt. + uint32 i; + for (i = 1; i <= HI_NUM; ++i) hi_poll_tx(i, quantum); return SCPE_OK; } @@ -299,25 +614,62 @@ t_stat hi_reset (DEVICE *dptr) UNIT *uptr = dptr->units; uint16 host= uptr->hline; hi_reset_rx(host); hi_reset_tx(host); + sim_cancel(uptr); + if ((uptr->flags & UNIT_ATT) != 0) sim_activate(uptr, uptr->wait); return SCPE_OK; } // Attach (connect) ... t_stat hi_attach (UNIT *uptr, CONST char *cptr) { - // simh calls this routine for (what else?) the ATTACH command. - uint16 host = uptr->hline; - fprintf(stderr,"HI%d - host interface not yet implemented\n", host); - return SCPE_IERR; + // simh calls this routine for (what else?) the ATTACH command. There are + // three distinct formats for ATTACH - + // + // ATTACH -p HIn COHnn - attach MIn to a physical COM port + // ATTACH HIn llll:w.x.y.z:rrrr - connect via UDP to a remote simh host + // + t_stat ret; char *pfn; uint16 host = uptr->hline; + t_bool fport = sim_switches & SWMASK('P'); + + // If we're already attached, then detach ... + if ((uptr->flags & UNIT_ATT) != 0) detach_unit(uptr); + + // The physical (COM port) attach isn't implemented yet ... + if (fport) { + fprintf(stderr,"HI%d - physical COM support is not yet implemented\n", host); + return SCPE_ARG; + } + + // Make a copy of the "file name" argument. udp_create() actually modifies + // the string buffer we give it, so we make a copy now so we'll have something + // to display in the "SHOW HIn ..." command. + pfn = (char *) calloc (CBUFSIZE, sizeof (char)); + if (pfn == NULL) return SCPE_MEM; + strncpy (pfn, cptr, CBUFSIZE); + + // Create the UDP connection. + ret = udp_create(PDEVICE(host), cptr, &(PHIDB(host)->link)); + if (ret != SCPE_OK) {free(pfn); return ret;}; + + // Reset the flags and start polling ... + uptr->flags |= UNIT_ATT; uptr->filename = pfn; + return hi_reset(find_dev_from_unit(uptr)); } // Detach (connect) ... t_stat hi_detach (UNIT *uptr) { - // simh calls this routine for (you guessed it!) the DETACH command. - uint16 host = uptr->hline; - fprintf(stderr,"HI%d - host interface not yet implemented\n", host); - return SCPE_IERR; + // simh calls this routine for (you guessed it!) the DETACH command. This + // disconnects the modem from any UDP connection or COM port and effectively + // makes the modem "off line". A disconnected modem acts like a real modem + // with its phone line unplugged. + t_stat ret; uint16 line = uptr->hline; + if ((uptr->flags & UNIT_ATT) == 0) return SCPE_OK; + ret = udp_release(PDEVICE(line), PHIDB(line)->link); + if (ret != SCPE_OK) return ret; + PHIDB(line)->link = NOLINK; uptr->flags &= ~UNIT_ATT; + free (uptr->filename); uptr->filename = NULL; + return hi_reset(PDEVICE(line)); } diff --git a/H316/h316_imp.h b/H316/h316_imp.h index 96e431f0..8aadb1ce 100644 --- a/H316/h316_imp.h +++ b/H316/h316_imp.h @@ -36,7 +36,8 @@ #define MI_MAX_MSG 256 // longest possible modem message (words!) #define HI_MAX_MSG 256 // longest possible host message (words!) #define MI_RXPOLL 100 // RX polling delay for UDP messages -#define MI_TXBPS 56000UL // default TX speed (bits per second) +#define MI_TXBPS 56000UL // default modem TX speed (bits per second) +#define HI_TXBPS 100000UL // default host TX speed (bits per second) #define HI_POLL_DELAY 1000 // polling delay for messages // Modem interface, line #1 ... @@ -158,16 +159,21 @@ typedef struct _MIDB MIDB; // One of these is allocated to every host interface ... struct _HIDB { // Receiver (HOST -> IMP) data ... + t_bool rxpending; // TRUE if a read is pending on this line + t_bool rxerror; // TRUE if any modem error detected uint32 rxtotal; // total host messages received // Transmitter (IMP -> HOST) data ... + uint32 txdelay; // RTC ticks until TX done interrupt uint32 txtotal; // total host messages sent // Other data ... - t_bool lloop; // local loop back enabled + t_bool iloop; // local loop back enabled t_bool enabled; // TRUE if the host is enabled t_bool error; // TRUE for any host error t_bool ready; // TRUE if the host is ready t_bool full; // TRUE if the host buffer is full t_bool eom; // TRUE when end of message is reached + int32 link; // h316_udp link number + uint32 bps; // simulated line speed or COM port baud rate }; typedef struct _HIDB HIDB; @@ -187,6 +193,16 @@ typedef struct _HIDB HIDB; extern uint32 rtc_interval; extern t_stat mi_tx_service (uint32 quantum); +// This constant determines the longest possible IMP data payload that can be +// sent. Most IMP messages are trivially small - 68 words or so - but, when one +// IMP asks for a reload the neighbor IMP sends the entire memory image in a +// single message! That message is about 14K words long. +// The next thing you should worry about is whether the underlying IP network +// can actually send a UDP packet of this size. It turns out that there's no +// simple answer to that - it'll be fragmented for sure, but as long as all +// the fragments arrive intact then the destination should reassemble them. +#define MAXDATA 16384 // longest possible IMP packet (in H316 words) + // Prototypes for UDP modem/host interface emulation routines ... #define NOLINK (-1) t_stat udp_create (DEVICE *pdtr, const char *premote, int32 *plink); diff --git a/H316/h316_mi.c b/H316/h316_mi.c index 618f1892..414ebde1 100644 --- a/H316/h316_mi.c +++ b/H316/h316_mi.c @@ -630,6 +630,13 @@ int32 mi_io (uint16 line, int32 inst, int32 fnc, int32 dat, int32 dev) return IOBADFNC(dat); } + + +//////////////////////////////////////////////////////////////////////////////// +/////////////////// H O S T E V E N T S E R V I C E //////////////////// +//////////////////////////////////////////////////////////////////////////////// + + // Receiver service ... t_stat mi_rx_service (UNIT *uptr) { diff --git a/H316/h316_udp.c b/H316/h316_udp.c index 150fcbb5..4d215555 100644 --- a/H316/h316_udp.c +++ b/H316/h316_udp.c @@ -136,15 +136,6 @@ // Local constants ... #define MAXLINKS 10 // maximum number of simultaneous connections -// This constant determines the longest possible IMP data payload that can be -// sent. Most IMP messages are trivially small - 68 words or so - but, when one -// IMP asks for a reload the neighbor IMP sends the entire memory image in a -// single message! That message is about 14K words long. -// The next thing you should worry about is whether the underlying IP network -// can actually send a UDP packet of this size. It turns out that there's no -// simple answer to that - it'll be fragmented for sure, but as long as all -// the fragments arrive intact then the destination should reassemble them. -#define MAXDATA 16384 // longest possible IMP packet (in H316 words) // UDP connection data structure ... // One of these blocks is allocated for every simulated modem link. @@ -336,6 +327,11 @@ t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count) // Send it and we're outta here ... iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen); + if (iret == 111) + { + fprintf (stderr, "link %d got connection refused\n", link); + return SCPE_OK; + } if (iret != SCPE_OK) return udp_error(link, "tmxr_put_packet_ln()"); sim_debug(IMP_DBG_UDP, dptr, "link %d - packet sent (sequence=%d, length=%d)\n", link, ntohl(pkt.sequence), ntohs(pkt.count)); return SCPE_OK; diff --git a/H316/tests/dps8m_imp.cmd b/H316/tests/dps8m_imp.cmd new file mode 100644 index 00000000..be83e87a --- /dev/null +++ b/H316/tests/dps8m_imp.cmd @@ -0,0 +1,43 @@ +;; *** IMP NODE #2 SETUP *** + +; IMP #2 connects to IMP #2 via modem line 1 on both ends ... +; Also, a host is connected to host line 1 + +; Set the simulator configuration ... +echo Creating standard configuration for IMP #2 ... +do impconfig.cmd +SET IMP NUM=2 + +; Load the IMP code ... +echo Loading IMP code ... +do impcode.cmd + +; Port numbering scheme. +; The host is 45xx +; IMPs are 44xx +; +; port abcd +; iom +; a 4 +; b 4 +; c iom number +; d line # 1-5 mi1-mi5 6-9 hi1-h15 +; host +; a 4 +; b 5 +; c host number +; d 0 + +; Start up the modem links! +echo Attaching modem links ... +ATTACH MI1 4421::4431 + +; Start up the host links! +echo Attaching host links ... +ATTACH HI1 4426::4500 +;set hi1 debug=warn;udp;io +;set mi1 debug=udp +; And we're done .. +;echo Type GO to start ... +Go +