From f7a4d639399ba0884b30cea7dee17196ff5bb58f Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Wed, 22 Jan 2025 14:34:40 +0100 Subject: [PATCH 1/2] H316: Import IMP interface. --- H316/h316_imp.h | 220 +++++++++++++++++++++++ H316/h316_udp.c | 451 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 671 insertions(+) create mode 100644 H316/h316_imp.h create mode 100644 H316/h316_udp.c diff --git a/H316/h316_imp.h b/H316/h316_imp.h new file mode 100644 index 00000000..ffdf104d --- /dev/null +++ b/H316/h316_imp.h @@ -0,0 +1,220 @@ +/* h316_imp.h- BBN ARPAnet IMP/TIP Definitions + + Copyright (c) 2013, Robert Armstrong, bob@jfcl.com + + 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 + ROBERT ARMSTRONG 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 Robert Armstrong shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert Armstrong. + + 21-May-13 RLA New file. +*/ +#ifdef VM_IMPTIP +#ifndef H316_IMP_H_ +#define H316_IMP_H_ 0 +#include "sim_defs.h" + +// Common modem and host parameters ... +#define MI_NUM 5 // number of modem interfaces +#define HI_NUM 4 // number of host interfaces +#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 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 ... +#define MI1 071 // IO address for modem interface #1 +#define MI1_RX_DMC (DMC1-1+ 1) // DMC channel for modem 1 receive +#define MI1_TX_DMC (DMC1-1+ 6) // DMC channel for modem 1 transmit +#define INT_V_MI1RX (INT_V_EXTD+15) // modem 1 receive interrupt +#define INT_V_MI1TX (INT_V_EXTD+10) // modem 1 transmit interrupt + +// Modem interface, line #2 ... +#define MI2 072 // IO address for modem interface #2 +#define MI2_RX_DMC (DMC1-1+ 2) // DMC channel for modem 2 receive +#define MI2_TX_DMC (DMC1-1+ 7) // DMC channel for modem 2 transmit +#define INT_V_MI2RX (INT_V_EXTD+14) // modem 2 receive interrupt +#define INT_V_MI2TX (INT_V_EXTD+ 9) // modem 2 transmit interrupt + +// Modem interface, line #3 ... +#define MI3 073 // IO address for modem interface #3 +#define MI3_RX_DMC (DMC1-1+ 3) // DMC channel for modem 3 receive +#define MI3_TX_DMC (DMC1-1+ 8) // DMC channel for modem 3 transmit +#define INT_V_MI3RX (INT_V_EXTD+13) // modem 3 receive interrupt +#define INT_V_MI3TX (INT_V_EXTD+ 8) // modem 3 transmit interrupt + +// Modem interface, line #4 ... +#define MI4 074 // IO address for modem interface #4 +#define MI4_RX_DMC (DMC1-1+ 4) // DMC channel for modem 4 receive +#define MI4_TX_DMC (DMC1-1+ 9) // DMC channel for modem 4 transmit +#define INT_V_MI4RX (INT_V_EXTD+12) // modem 4 receive interrupt +#define INT_V_MI4TX (INT_V_EXTD+ 7) // modem 4 transmit interrupt + +// Modem interface, line #5 ... +#define MI5 075 // IO address for modem interface #5 +#define MI5_RX_DMC (DMC1-1+ 5) // DMC channel for modem 5 receive +#define MI5_TX_DMC (DMC1-1+10) // DMC channel for modem 5 transmit +#define INT_V_MI5RX (INT_V_EXTD+11) // modem 5 receive interrupt +#define INT_V_MI5TX (INT_V_EXTD+ 6) // modem 5 transmit interrupt + +// Host interface, line #1 ... +#define HI1 070 // device address for host interface #1 +#define HI1_RX_DMC (DMC1+13-1) // DMC channel for host 1 receive +#define HI1_TX_DMC (DMC1+11-1) // DMC channel for host 1 transmit +#define INT_V_HI1RX (INT_V_EXTD+ 3) // host 1 receive interrupt +#define INT_V_HI1TX (INT_V_EXTD+ 5) // host 1 transmit interrupt + +// Host interface, line #2 ... +#define HI2 060 // device address for host interface #2 +#define HI2_RX_DMC (DMC1-1+14) // DMC channel for host 2 receive +#define HI2_TX_DMC (DMC1-1+12) // DMC channel for host 2 transmit +#define INT_V_HI2RX (INT_V_EXTD+ 2) // host 2 receive interrupt +#define INT_V_HI2TX (INT_V_EXTD+ 4) // host 2 transmit interrupt + +// Host interface, line #3 ... +#define HI3 051 // device address for host interface #3 +#define HI3_RX_DMC (DMC1-1+16) // DMC channel for host 3 receive +#define HI3_TX_DMC (DMC1-1+15) // DMC channel for host 3 transmit +#define INT_V_HI3RX (INT_V_EXTD+ 6) // host 3 receive interrupt +#define INT_V_HI3TX (INT_V_EXTD+11) // host 3 transmit interrupt + +// Host interface, line #4 ... +#define HI4 050 // device address for host interface #4 +#define HI4_RX_DMC (DMC1-1+10) // DMC channel for host 4 receive +#define HI4_TX_DMC (DMC1-1+ 5) // DMC channel for host 4 transmit +#define INT_V_HI4RX (INT_V_EXTD+ 7) // host 4 receive interrupt +#define INT_V_HI4TX (INT_V_EXTD+12) // host 4 transmit interrupt + +// IMP defaults ... +#define IMP 041 // IMP device IO address (41 & 42 actually!) +#define INT_V_TASK (INT_V_EXTD+ 0) // task switch interrupt number +#define IMP_STATION 1 // default station number + +// RTC defaults ... +#define RTC 040 // real time clock IO address +#define INT_V_RTC (INT_V_EXTD+ 1) // RTC interrupt number +#define RTC_INTERVAL 20UL // default RTC interval (20us == 50kHz) +#define RTC_QUANTUM 32UL // default RTC quantum (32 ticks) + +// WDT defaults ... +#define WDT 026 // watchdog timer IO address +#define WDT_VECTOR 000062 // WDT timeout vector +#define WDT_DELAY 0 // default WDT timeout (in milliseconds) + +// Debugging flags ... +// In general, these bits are used as arguments for sim_debug(). Bits that +// begin with "IMP_DBG_xyz" are shared by more than one device (e.g. IMP_DBG_UDP) +// and must have unique bit assignments. Bits prefixed with a device name (e.g. +// "MI_DBG_xyz") apply to that device only. +#define IMP_DBG_WARN 0x0001 // all: print warnings +#define IMP_DBG_IOT 0x0002 // all: trace all program I/O instructions +#define IMP_DBG_UDP 0x0004 // all: trace UDP packets +#define MI_DBG_MSG 0x8000 // modem: decode and print all messages +#define HI_DBG_MSG 0x8000 // host interface: decode and print all messages +#define WDT_DBG_LIGHTS 0x8000 // wdt: show status light changes + +// Synonyms for DIB and UNIT fields ... +#define rxdmc chan // dib->rxdmc +#define txdmc chan2 // dib->txdmc +#define rxint inum // dib->rxint +#define txint inum2 // dib->txint + +// 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) + +// Modem interface data block .... +// One of these is allocated to every modem interface to keep track of the +// current state, COM port, UDP connection , etc... +struct _MIDB { + // Receiver 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 number of H316 words received + // Transmitter data ... + uint32 txtotal; // total number of H316 words transmitted + uint32 txdelay; // RTC ticks until TX done interrupt + // Other data ... + t_bool lloop; // line loop back enabled + t_bool iloop; // interface loop back enabled + int32 link; // h316_udp link number + uint32 bps; // simulated line speed or COM port baud rate +}; +typedef struct _MIDB MIDB; + +// Host interface data block ... +// 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 + uint16 rxdata[MAXDATA]; // UDP packet received. + uint16 rxnext; // Index to next word in UDP packet. + uint16 rxsize; // Size of UDP packet. + // Transmitter (IMP -> HOST) data ... + uint32 txdelay; // RTC ticks until TX done interrupt + uint32 txtotal; // total host messages sent + // Other data ... + 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; + +// I can't believe Bob managed to live without these, but I can't! +#ifndef LOBYTE // these are in winsock.h too! +#define LOBYTE(x) ((uint8) ( (x) & 0xFF)) +#define HIBYTE(x) ((uint8) (((x) >> 8) & 0xFF)) +#define MKWORD(h,l) ((uint16) ( (((h)&0xFF) << 8) | ((l)&0xFF) )) +#define LOWORD(x) ((uint16) ( (x) & 0xFFFF)) +#define HIWORD(x) ((uint16) (((x) >> 16) & 0xFFFF)) +#define MKLONG(h,l) ((uint32) ( (((h)&0xFFFF) << 16) | ((l)&0xFFFF) )) +#endif + +// Prototypes for the RTC module ... +// I really hate sharing things like this, but it's the only way to get the +// modem transmitter timing exactly right! +extern uint32 rtc_interval; +extern t_stat mi_tx_service (uint32 quantum); +extern t_stat hi_tx_service (uint32 quantum); + +// Prototypes for UDP modem/host interface emulation routines ... +#define NOLINK (-1) +t_stat udp_create (DEVICE *pdtr, const char *premote, int32 *plink); +t_stat udp_release (DEVICE *dptr, int32 link); +t_stat udp_send (DEVICE *pdtr, int32 link, uint16 *pdata, uint16 count); +t_stat udp_set_link_loopback (DEVICE *dptr, int32 link, t_bool enable_loopback); +int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbufg); + +#endif // #ifndef _H316_IMP_H_ +#endif // #ifdef VM_IMPTIP diff --git a/H316/h316_udp.c b/H316/h316_udp.c new file mode 100644 index 00000000..5451e98d --- /dev/null +++ b/H316/h316_udp.c @@ -0,0 +1,451 @@ +/* h316_udp.c: IMP/TIP Modem and Host Interface socket routines using UDP + + Copyright (c) 2013 Robert Armstrong, bob@jfcl.com + + 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 + ROBERT ARMSTRONG 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 Robert Armstrong shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert Armstrong. + + + REVISION HISTORY + + udp socket routines + + 26-Jun-13 RLA Rewritten from TCP version + 26-Nov-13 MP Rewritten to use TMXR layer packet semantics thus + allowing portability to all simh hosts. + 2-Dec-13 RLA Improve error recovery if the other simh is restarted + + OVERVIEW + + This module emulates low level communications between two virtual modems + using UDP packets over the modern network connections. It's used by both + the IMP modem interface and the host interface modules to implement IMP to + IMP and IMP to HOST connections. + + TCP vs UDP + + Why UDP and not TCP? TCP has a couple of advantages after all - it's + stream oriented, which is intrinsically like a modem, and it handles all + the network "funny stuff" for us. TCP has a couple of problems too - first, + it's inherently asymmetrical. There's a "server" end which opens a master + socket and passively listens for connections, and a "client" end which + actively attempts to connect. That's annoying, but it can be worked around. + + The big problem with TCP is that even though it treats the data like a stream + it's internally buffering it, and you really have absolutely no control over + when TCP will decide to send its buffer. Google "nagle algorithm" to get an + idea. Yes, you can set TCP_NODELAY to disable Nagle, but the data's still + buffered and it doesn't fix the problem. What's the issue with buffering? + It introduces completely unpredictable delays into the message traffic. A + transmitting IMP could send two or three (or ten or twenty!) messages before + TCP actually decides to try to deliver them to the destination. + + And it turns out that IMPs are extraordinarily sensitive to line speed. The + IMP firmware actually goes to the trouble of measuring the effective line + speed by using the RTC to figure out how long it takes to send a message. + One thing that screws up the IMP to no end is variation in the effective + line speed. I guess they had a lot of trouble with AT&T Long Lines back in + the Old Days, and the IMP has quite a bit of code to monitor line quality. + Even fairly minor variations in speed will cause it to mark the line as + "down" and sent a trouble report back to BBN. And no, I'm not making this up! + + UDP gives us a few advantages. First, it's inherently packet oriented so + we can simply grab the entire packet from the transmitting IMP's memory, wrap + a little extra information around it, and ship it off in one datagram. The + receiving IMP gets the whole packet at once and it can simply BLT it into + the receiving IMP's memory. No fuss, no muss, no need convert the packet + into a stream, add word counts, wait for complete packets, etc. And UDP is + symmetrical - both ends listen and send in the same way. There's no need for + master sockets, passive (server) and active (client) ends, or any of that. + + Also UDP has no buffering - the packet goes out on the wire when we send it. + The data doesn't wait around in some buffer for TCP to decide when it wants + to let it go. The latency and delay for UDP is much more predictable and + consistent, at least for local networks. If you're actually sending the + packets out on the big, wide, Internet then all bets are off on that. + + UDP has a few problems that we have to worry about. First, it's not + guaranteed delivery so just because one IMP sends a packet doesn't mean that + the other end will ever see it. Surprisingly that's not a problem for us. + Phone lines have noise and dropouts, and real modems lose packets too. The + IMP code is completely happy and able to deal with that, and generally we + don't worry about dropped packets at all. + + There are other issues with UDP - it doesn't guarantee packet order, so the + sending IMP might transmit packets 1, 2 and 3 and the receiving IMP will get + 1, 3 then 2. THAT would never happen with a real modem and we have to shield + the IMP code from any such eventuality. Also, with UDP packets can be + duplicated so the receiving IMP might see 1, 2, 2, 3 (or even 1, 3, 2, 1!). + Again, a modem would never do that and we have to prevent it from happening. + Both cases are easily dealt with by adding a sequence number to the header + we wrap around the IMP's packet. Out of sequence or duplicate packets can + be detected and are simply dropped. If necessary, the IMP will deal with + retransmitting them in its own time. + + One more thing about UDP - there is no way to tell whether a connection is + established or not and for that matter there is no "connection" at all + (that's why it's a "connectionless" protocol, after all!). We simply send + packets out and there's no way to know whether anybody is hearing them. The + real IMP modem hardware had no carrier detect or other dataset control + functions, so it was identical in that respect. An IMP sent messages out the + modem and, unless it received a message back, it had no way to know whether + the IMP on the other end was hearing them. + + + INTERFACE + + This module provides a simplified UDP socket interface. These functions are + implemented - + + udp_create define a connection to the remote IMP + udp_release release a connection + udp_send send an IMP message to the other end + udp_receive receive (w/o blocking!) a message if available + + Note that each connection is assigned a unique "handle", a small integer, + which is used as an index into our internal connection data table. There + is a limit on the maximum number of connections available, as set my the + MAXLINKS parameter. Also, notice that all links are intrinsically full + duplex and bidirectional - data can be sent and received in both directions + independently. Real modems and host cards were exactly the same. + +*/ +#ifdef VM_IMPTIP +#include "sim_defs.h" // simh machine independent definitions +#include "sim_tmxr.h" // The MUX layer exposes packet send and receive semantics +#include "h316_imp.h" // ARPAnet IMP/TIP definitions + +// Local constants ... +#define MAXLINKS 10 // maximum number of simultaneous connections +// UDP connection data structure ... +// One of these blocks is allocated for every simulated modem link. +struct _UDP_LINK { + t_bool used; // TRUE if this UDP_LINK is in use + char rhostport[90]; // Remote host:port + char lport[64]; // Local port + uint32 rxsequence; // next message sequence number for receive + uint32 txsequence; // next message sequence number for transmit + DEVICE *dptr; // Device associated with link +}; +typedef struct _UDP_LINK UDP_LINK; + +// This magic number is stored at the beginning of every UDP message and is +// checked on receive. It's hardly foolproof, but its a simple attempt to +// guard against other applications dumping unsolicited UDP messages into our +// receiver socket... +#define MAGIC ((uint32) (((((('H' << 8) | '3') << 8) | '1') << 8) | '6')) + +// UDP wrapper data structure ... +// This is the UDP packet which is actually transmitted or received. It +// contains the actual IMP packet, plus whatever additional information we +// need to keep track of things. NOTE THAT ALL DATA IN THIS PACKET, INCLUDING +// THE H316 MEMORY WORDS, ARE SENT AND RECEIVED WITH NETWORK BYTE ORDER! +struct _UDP_PACKET { + uint32 magic; // UDP "magic number" (see above) + uint32 sequence; // UDP packet sequence number + uint16 count; // number of H316 words to follow + uint16 data[MAXDATA]; // and the actual H316 data words/IMP packet +}; +typedef struct _UDP_PACKET UDP_PACKET; +#define UDP_HEADER_LEN (2*sizeof(uint32) + sizeof(uint16)) + +// Locals ... +UDP_LINK udp_links[MAXLINKS] = { {0} }; // data for every active connection +TMLN udp_lines[MAXLINKS] = { {0} }; // line descriptors +TMXR udp_tmxr = { MAXLINKS, NULL, 0, udp_lines};// datagram mux + +int32 udp_find_free_link (void) +{ + // Find a free UDP_LINK block, initialize it and return its index. If none + // are free, then return -1 ... + int32 i; + for (i = 0; i < MAXLINKS; ++i) { + if (udp_links[i].used == 0) { + memset(&udp_links[i], 0, sizeof(UDP_LINK)); + return i; + } + } + return NOLINK; +} + +t_stat udp_parse_remote (int32 link, const char *premote) +{ + // This routine will parse a remote address string in any of these forms - + // + // llll:w.x.y.z:rrrr + // llll:name.domain.com:rrrr + // llll::rrrr + // w.x.y.z:rrrr + // name.domain.com:rrrr + // + // In all examples, "llll" is the local port number that we use for listening, + // and "rrrr" is the remote port number that we use for transmitting. The + // local port is optional and may be omitted, in which case it defaults to the + // same as the remote port. This works fine if the other IMP is actually on + // a different host, but don't try that with localhost - you'll be talking to + // yourself!! In both cases, "w.x.y.z" is a dotted IP for the remote machine + // and "name.domain.com" is its name (which will be looked up to get the IP). + // If the host name/IP is omitted then it defaults to "localhost". + char *end; int32 lport, rport; + char host[64], port[16]; + + if (*premote == '\0') return SCPE_2FARG; + memset (udp_links[link].lport, 0, sizeof(udp_links[link].lport)); + memset (udp_links[link].rhostport, 0, sizeof(udp_links[link].rhostport)); + // Handle the llll::rrrr case first + if (2 == sscanf (premote, "%d::%d", &lport, &rport)) { + if ((lport < 1) || (lport >65535) || (rport < 1) || (rport >65535)) return SCPE_ARG; + snprintf (udp_links[link].lport, sizeof (udp_links[link].lport) - 1, "%d", lport); + snprintf (udp_links[link].rhostport, sizeof (udp_links[link].rhostport) - 1, "localhost:%d", rport); + return SCPE_OK; + } + + // Look for the local port number and save it away. + lport = strtoul(premote, &end, 10); + if ((*end == ':') && (lport > 0)) { + snprintf (udp_links[link].lport, sizeof (udp_links[link].lport) - 1, "%d", lport); + premote = end+1; + } + + if (sim_parse_addr (premote, host, sizeof(host), "localhost", port, sizeof(port), NULL, NULL)) + return SCPE_ARG; + snprintf (udp_links[link].rhostport, sizeof (udp_links[link].rhostport) - 1, "%s:%s", host, port); + if (udp_links[link].lport[0] == '\0') + strlcpy (udp_links[link].lport, port, sizeof (udp_links[link].lport)); + + if ((strcmp (udp_links[link].lport, port) == 0) && + (strcmp ("localhost", host) == 0)) + return sim_messagef (SCPE_ARG, "WARNING - use different transmit and receive ports!\n"); + + return SCPE_OK; +} + +t_stat udp_create (DEVICE *dptr, const char *premote, int32 *pln) +{ + // Create a logical UDP link to the specified remote system. The "remote" + // string specifies both the remote host name or IP and a port number. The + // port number is both the port we send datagrams to, and also the port we + // listen on for incoming datagrams. UDP doesn't have any real concept of a + // "connection" of course, and this routine simply creates the necessary + // sockets in this host. We have no way of knowing whether the remote host is + // listening or even if it exists. + // + // We return SCPE_OK if we're successful and an error code if we aren't. If + // we are successful, then the ln parameter is assigned the link number, + // which is a handle used to identify this connection to all future udp_xyz() + // calls. + t_stat ret; + char linkinfo[256]; + int32 link = udp_find_free_link(); + if (link < 0) return SCPE_MEM; + + // Parse the remote name and set up the ipaddr and port ... + if ((ret = udp_parse_remote(link, premote)) != SCPE_OK) return ret; + + // Create the socket connection to the destination ... + sprintf(linkinfo, "Buffer=%d,Line=%d,%s,UDP,Connect=%s", (int)(sizeof(UDP_PACKET)+sizeof(int32)), link, udp_links[link].lport, udp_links[link].rhostport); + ret = tmxr_open_master (&udp_tmxr, linkinfo); + if (ret != SCPE_OK) return ret; + + // All done - mark the TCP_LINK data as "used" and return the index. + udp_links[link].used = TRUE; *pln = link; + udp_lines[link].dptr = udp_links[link].dptr = dptr; // save device + udp_tmxr.uptr = dptr->units; + udp_tmxr.last_poll_time = 1; // h316's use of TMXR doesn't poll periodically for connects + (void)tmxr_poll_conn (&udp_tmxr); // force connection initialization now + udp_tmxr.last_poll_time = 1; // h316's use of TMXR doesn't poll periodically for connects + sim_debug(IMP_DBG_UDP, dptr, "link %d - listening on port %s and sending to %s\n", link, udp_links[link].lport, udp_links[link].rhostport); + return SCPE_OK; +} + +t_stat udp_release (DEVICE *dptr, int32 link) +{ + // Close a link that was created by udp_create() and release any resources + // allocated to it. We always return SCPE_OK unless the link specified is + // already unused. + if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; + if (!udp_links[link].used) return SCPE_IERR; + if (dptr != udp_links[link].dptr) return SCPE_IERR; + + tmxr_detach_ln (&udp_lines[link]); + udp_links[link].used = FALSE; + sim_debug(IMP_DBG_UDP, dptr, "link %d - closed\n", link); + + return SCPE_OK; +} + +t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count) +{ + // This routine does all the work of sending an IMP data packet. pdata + // is a pointer (usually into H316 simulated memory) to the IMP packet data, + // count is the length of the data (in H316 words, not bytes!), and pdest is + // the destination socket. There are two things worthy of note here - first, + // notice that the H316 words are sent in network order, so the remote simh + // doesn't necessarily need to have the same endian-ness as this machine. + // Second, notice that transmitting sockets are NOT set to non blocking so + // this routine might wait, but we assume the wait will never be too long. + UDP_PACKET pkt; int pktlen; uint16 i; t_stat iret; + if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; + if (!udp_links[link].used) return SCPE_IERR; + if ((pdata == NULL) || (count == 0) || (count > MAXDATA)) return SCPE_IERR; + if (dptr != udp_links[link].dptr) return SCPE_IERR; + + // Build the UDP packet, filling in our own header information and copying + // the H316 words from memory. REMEMBER THAT EVERYTHING IS IN NETWORK ORDER! + pkt.magic = htonl(MAGIC); + pkt.sequence = htonl(udp_links[link].txsequence++); + pkt.count = htons(count); + for (i = 0; i < count; ++i) pkt.data[i] = htons(*pdata++); + pktlen = UDP_HEADER_LEN + count*sizeof(uint16); + + // Send it and we're outta here ... + iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen); + if (iret != SCPE_OK) return sim_messagef(iret, "UDP%d - tmxr_put_packet_ln() failed with error %s\n", link, sim_error_text(iret)); + 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; +} + +t_stat udp_set_link_loopback (DEVICE *dptr, int32 link, t_bool enable_loopback) +{ + // Enable or disable the local (interface) loopback on this link... + if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; + if (!udp_links[link].used) return SCPE_IERR; + if (dptr != udp_links[link].dptr) return SCPE_IERR; + + return tmxr_set_line_loopback (&udp_lines[link], enable_loopback); +} + +int32 udp_receive_packet (int32 link, UDP_PACKET *ppkt) +{ + // This routine will do the hard part of receiving a UDP packet. If it's + // successful the packet length, in bytes, is returned. The receiver socket + // is non-blocking, so if no packet is available then zero will be returned + // instead. Lastly, if a fatal socket I/O error occurs, -1 is returned. + // + // Note that this routine only receives the packet - it doesn't handle any + // of the checking for valid packets, unexpected packets, duplicate or out of + // sequence packets. That's strictly the caller's problem! + size_t pktsiz; + const uint8 *pbuf; + t_stat ret; + + udp_lines[link].rcve = TRUE; // Enable receiver + tmxr_poll_rx (&udp_tmxr); + ret = tmxr_get_packet_ln (&udp_lines[link], &pbuf, &pktsiz); + udp_lines[link].rcve = FALSE; // Disable receiver + if (ret != SCPE_OK) { + sim_messagef (ret, "UDP%d - tmxr_get_packet_ln() failed with error %s\n", link, sim_error_text(ret)); + return NOLINK; + } + if (pbuf == NULL) return 0; + // Got a packet, so copy it to the packet buffer + memcpy (ppkt, pbuf, pktsiz); + return pktsiz; +} + +int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbuf) +{ + // Receive an IMP packet from the virtual modem. pdata is a pointer usually + // directly into H316 simulated memory) to where the IMP packet data should + // be stored, and maxbuf is the maximum length of that buffer in H316 words + // (not bytes!). If a message is successfully received then this routine + // returns the length, again in H316 words, of the IMP packet. The caller + // can detect buffer overflows by comparing this result to maxbuf. If no + // packets are waiting right now then zero is returned, and -1 is returned + // in the event of any fatal socket I/O error. + // + // This routine also handles checking for unsolicited messages and duplicate + // or out of sequence messages. All of these are unceremoniously discarded. + // + // One final note - it's explicitly allowed for pdata to be null and/or + // maxbuf to be zero. In either case the received package is discarded, but + // the actual length of the discarded package is still returned. + UDP_PACKET pkt; int32 pktlen, explen, implen, i; uint32 magic, pktseq; + if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; + if (!udp_links[link].used) return SCPE_IERR; + if (dptr != udp_links[link].dptr) return SCPE_IERR; + + while ((pktlen = udp_receive_packet(link, &pkt)) > 0) { + // First do some header checks for a valid UDP packet ... + if (((size_t)pktlen) < UDP_HEADER_LEN) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/o header (length=%d)\n", link, pktlen); + continue; + } + magic = ntohl(pkt.magic); + if (magic != MAGIC) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/bad magic number (magic=%08x)\n", link, magic); + continue; + } + implen = ntohs(pkt.count); + explen = UDP_HEADER_LEN + implen*sizeof(uint16); + if (explen != pktlen) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet length wrong (expected=%d received=%d)\n", link, explen, pktlen); + continue; + } + + // Now the hard part = check the sequence number. The rxsequence value is + // the number of the next packet we expect to receive - that's the number + // this packet should have. If this packet's sequence is less than that, + // then this packet is out of order or a duplicate and we discard it. If + // this packet is greater than that, then we must have missed one or two + // packets. In that case we MUST update rxsequence to match this one; + // otherwise the two ends could never resynchronize after a lost packet. + // + // And there's one final complication to worry about - if the simh on the + // other end is restarted for some reason, then his sequence numbers will + // reset to zero. In that case we'll never recover synchronization without + // special efforts. The hack is to check for a packet sequence number of + // zero and, if we find it, force synchronization. This improves the + // situation, but I freely admit that it's possible to think of a number of + // cases where this also fails. The only absolute solution is to implement + // a more complicated system with non-IMP control messages exchanged between + // the modem emulation on both ends. That'd be nice, but I'll leave it as + // an exercise for later. + pktseq = ntohl(pkt.sequence); + if ((pktseq == 0) && (udp_links[link].rxsequence != 0)) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - remote modem restarted\n", link); + } else if (pktseq < udp_links[link].rxsequence) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 1 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq); + continue; // discard this packet! + } + else if (pktseq != udp_links[link].rxsequence) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 2 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq); + } + udp_links[link].rxsequence = pktseq+1; + + // It's a valid packet - if there's no buffer then just discard it. + if ((pdata == NULL) || (maxbuf == 0)) { + sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet discarded (no buffer available)\n", link); + return implen; + } + + // Copy the data to the H316 memory and we're done! + sim_debug(IMP_DBG_UDP, dptr, "link %d - packet received (sequence=%d, length=%d)\n", link, pktseq, pktlen); + for (i = 0; i < (implen < maxbuf ? implen : maxbuf); ++i) + *pdata++ = ntohs(pkt.data[i]); + return implen; + } + + // Here if pktlen <= 0 ... + return pktlen; +} + +#endif // ifdef VM_IMPTIP From 4130d591f62e394ea9dd70825cb0a7c26910f5c1 Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Tue, 10 Dec 2024 16:31:20 +0100 Subject: [PATCH 2/2] PDP10: Talk to the host interface of an IMP emulator. --- PDP10/CMakeLists.txt | 14 +++ PDP10/kx10_imp.c | 129 +++++++++++++++++++++++-- Visual Studio Projects/PDP10-KA.vcproj | 6 +- Visual Studio Projects/PDP10-KI.vcproj | 6 +- Visual Studio Projects/PDP10-KL.vcproj | 6 +- Visual Studio Projects/PDP10-KS.vcproj | 6 +- cmake/simh-simulators.cmake | 1 + makefile | 20 ++-- 8 files changed, 170 insertions(+), 18 deletions(-) diff --git a/PDP10/CMakeLists.txt b/PDP10/CMakeLists.txt index f755cf63..90eb0064 100644 --- a/PDP10/CMakeLists.txt +++ b/PDP10/CMakeLists.txt @@ -14,6 +14,11 @@ if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMak add_subdirectory(unit-tests) endif () + +set(IMPUDP + ${H316D}/h316_udp.c) + + add_simulator(pdp10-ka SOURCES ${KA10D}/kx10_cpu.c @@ -60,10 +65,13 @@ add_simulator(pdp10-ka ${KA10D}/kx10_ddc.c ka10_dd.c ${DISPLAY340} + ${IMPUDP} INCLUDES ${KA10D} + ${H316D} DEFINES KA=1 + VM_IMPTIP FEATURE_INT64 FEATURE_VIDEO FEATURE_DISPLAY @@ -103,10 +111,12 @@ add_simulator(pdp10-ki ${KI10D}/kx10_ddc.c ${KI10D}/kx10_tym.c ${DISPLAY340} + ${IMPUDP} INCLUDES ${KI10D} DEFINES KI=1 + VM_IMPTIP FEATURE_INT64 FEATURE_VIDEO FEATURE_DISPLAY @@ -138,10 +148,12 @@ add_simulator(pdp10-kl ${KL10D}/kl10_nia.c ${KL10D}/kx10_disk.c ${KL10D}/kl10_dn.c + ${IMPUDP} INCLUDES ${KL10D} DEFINES KL=1 + VM_IMPTIP FEATURE_INT64 LABEL PDP10 PKG_FAMILY pdp10_family @@ -164,10 +176,12 @@ add_simulator(pdp10-ks ${KS10D}/ks10_kmc.c ${KS10D}/ks10_dup.c ${KS10D}/kx10_imp.c + ${IMPUDP} INCLUDES ${KS10D} DEFINES KS=1 + VM_IMPTIP FEATURE_INT64 LABEL PDP10 PKG_FAMILY pdp10_family diff --git a/PDP10/kx10_imp.c b/PDP10/kx10_imp.c index 12be4291..f4d5a770 100644 --- a/PDP10/kx10_imp.c +++ b/PDP10/kx10_imp.c @@ -40,6 +40,8 @@ #define UNIT_M_DTYPE 3 #define UNIT_DTYPE (UNIT_M_DTYPE << UNIT_V_DTYPE) #define GET_DTYPE(x) (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE) +#define UNIT_V_NCP (UNIT_V_UF + 3) /* NCP flag */ +#define UNIT_NCP (1 << UNIT_V_NCP) #define TYPE_MIT 0 /* MIT Style KAIMP ITS */ #define TYPE_BBN 1 /* BBN style interface TENEX */ @@ -475,6 +477,7 @@ struct imp_device { struct imp_stats stats; uint8 sbuffer[ETH_FRAME_SIZE]; /* Temp send buffer */ uint8 rbuffer[ETH_FRAME_SIZE]; /* Temp receive buffer */ + int rpos; ETH_DEV etherface; ETH_QUE ReadQ; int imp_error; @@ -482,6 +485,7 @@ struct imp_device { int rfnm_count; /* Number of pending RFNM packets */ int pia; /* PIA channels */ struct arp_entry arp_table[IMP_ARPTAB_SIZE]; + int32 link; /* Link for UDP. */ } imp_data; extern int32 tmxr_poll; @@ -541,6 +545,16 @@ const char *imp_description (DEVICE *dptr); static char *ipv4_inet_ntoa(struct in_addr ip); static int ipv4_inet_aton(const char *str, struct in_addr *inp); +#define MAXDATA 16348 +extern t_stat udp_create (DEVICE *dptr, const char *premote, int32 *pln); +extern t_stat udp_release (DEVICE *dptr, int32 link); +extern t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count); +extern int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbuf); +// Last-IMP-Bit is implemented as an out-of-band flag in UDP_PACKET +#define PFLG_FINAL 00001 +// Host or IMP Ready bit. +#define PFLG_READY 00002 + #if KS uint16 imp_icsr; uint16 imp_idb; @@ -595,6 +609,10 @@ MTAB imp_mod[] = { "Use DHCP to set IP address"}, { MTAB_XTD|MTAB_VDV, 0, "DHCPIP", NULL, NULL, &imp_show_dhcpip, NULL, "DHCP info" }, + { UNIT_NCP, 0, "TCP", "TCP", NULL, NULL, NULL, + "Use TCP/IP protocol"}, + { UNIT_NCP, UNIT_NCP, "NCP", "NCP", NULL, NULL, NULL, + "Use NCP protocol"}, #if KS { UNIT_DTYPE, (TYPE_UNI << UNIT_V_DTYPE), "UNI", "UNI", NULL, NULL, NULL, "Standard Unibus transfers"}, @@ -860,6 +878,13 @@ static void check_interrupts (UNIT *uptr) set_interrupt_mpx(DEVNUM, imp_data.pia >> 3, imp_mpx_lvl + 1); } +static void imp_send_host_ready(DEVICE *dptr, struct imp_device *imp) +{ + t_stat r; + uint16 data = PFLG_READY; + r = udp_send (dptr, imp->link, &data, 1); +} + t_stat imp_devio(uint32 dev, uint64 *data) { DEVICE *dptr = &imp_dev; @@ -893,8 +918,12 @@ t_stat imp_devio(uint32 dev, uint64 *data) } if (*data & IMPHEC) { /* Clear host error. */ /* Only if there has been a CONI lately. */ - if (last_coni - sim_gtime() < CONI_TIMEOUT) + if (last_coni - sim_gtime() < CONI_TIMEOUT) { uptr->STATUS &= ~IMPHER; + uptr->STATUS |= IMPHR; + if (uptr->flags & UNIT_NCP) + imp_send_host_ready(dptr, &imp_data); + } } if (*data & IMIIHE) /* Inhibit interrupt on host error. */ uptr->STATUS |= IMPIHE; @@ -1380,6 +1409,35 @@ t_stat imp_tim_srv(UNIT * uptr) return SCPE_OK; } +void +imp_receive_udp (DEVICE *dev, struct imp_device *imp) +{ + static uint16 data[MAXDATA]; + int i; + int32 count; + + count = udp_receive(dev, imp->link, data, MAXDATA); + if (count == 0) + return; + if (data[0] & PFLG_READY) + imp_unit[0].STATUS |= IMPR; + else + imp_unit[0].STATUS &= IMPR; + for (i = 1; i < count; i++) { + imp->rbuffer[imp->rpos++] = data[i] >> 8; + imp->rbuffer[imp->rpos++] = data[i] & 0xFF; + } + if ((data[0] & PFLG_FINAL) == 0 || imp->rpos == 0) + return; + imp_unit[0].STATUS |= IMPIB; + imp_unit[0].IPOS = 0; + imp_unit[0].ILEN = 8*imp->rpos; + imp->rpos = 0; + if (!sim_is_active(&imp_unit[0])) + sim_activate(&imp_unit[0], 100); + return; +} + void imp_packet_in(struct imp_device *imp) { @@ -1389,6 +1447,11 @@ imp_packet_in(struct imp_device *imp) int n; int pad; + if (imp_unit[0].flags & UNIT_NCP) { + imp_receive_udp (&imp_dev, imp); + return; + } + if (eth_read (&imp_data.etherface, &read_buffer, NULL) <= 0) { /* Any pending packet notifications? */ if (imp->rfnm_count != 0) { @@ -1608,6 +1671,24 @@ imp_packet_in(struct imp_device *imp) } } +void +imp_send_udp (struct imp_device *imp, int len) +{ + static uint16 data[MAXDATA]; + t_stat r; + int i, j; + + data[0] = PFLG_FINAL; + if ((imp_unit[0].STATUS & IMPHER) == 0) + data[0] |= PFLG_READY; + + for (i = 0, j = 0; i < len/2; i++, j+=2) + data[i+1] = (imp->sbuffer[j] << 8) + imp->sbuffer[j+1]; + if (len&1) + data[i+1] = imp->sbuffer[j] << 8; + r = udp_send (&imp_dev, imp->link, data, ((uint16)len+3)/2); +} + void imp_send_packet (struct imp_device *imp, int len) { @@ -1619,6 +1700,12 @@ imp_send_packet (struct imp_device *imp, int len) int lk; int mt; + if (imp_unit[0].flags & UNIT_NCP) { + imp_send_udp(imp, len); + sim_activate(uptr, tmxr_poll); + return; + } + lk = 0; n = len; switch (imp->sbuffer[0] & 0xF) { @@ -3132,6 +3219,16 @@ t_stat imp_attach(UNIT* uptr, CONST char* cptr) char* tptr; char buf[32]; + if (cptr == NULL || *cptr == 0) + return SCPE_ARG; + + if (uptr->flags & UNIT_NCP) { + status = udp_create (&imp_dev, cptr, &imp_data.link); + if (status != SCPE_OK) + return status; + imp_data.rpos = 0; + } + #if !KS /* Set to correct device number */ switch(GET_DTYPE(imp_unit[0].flags)) { @@ -3146,6 +3243,16 @@ t_stat imp_attach(UNIT* uptr, CONST char* cptr) break; } #endif + + /* Start out in a "host not ready" state. */ + uptr->STATUS &= ~IMPHR; + + if (uptr->flags & UNIT_NCP) { + /* Don't do the Ethernet stuff below. */ + uptr->flags |= UNIT_ATT; + return SCPE_OK; + } + if (!(uptr->flags & UNIT_DHCP) && imp_data.ip == 0) return sim_messagef (SCPE_NOATT, "%s: An IP Address must be specified when DHCP is disabled\n", imp_dev.name); @@ -3227,17 +3334,20 @@ t_stat imp_attach(UNIT* uptr, CONST char* cptr) t_stat imp_detach(UNIT* uptr) { - if (uptr->flags & UNIT_ATT) { + if (uptr->flags & UNIT_NCP) + udp_release (&imp_dev, imp_data.link); /* If DHCP, release our IP address */ - if (uptr->flags & UNIT_DHCP) { + else if (uptr->flags & UNIT_DHCP) { imp_dhcp_release(&imp_data); } sim_cancel (uptr+1); /* stop the packet timing services */ sim_cancel (uptr+2); /* stop the clock timer services */ - eth_close (&imp_data.etherface); - free(uptr->filename); - uptr->filename = NULL; + if (!(uptr->flags & UNIT_NCP)) { + eth_close (&imp_data.etherface); + free(uptr->filename); + uptr->filename = NULL; + } uptr->flags &= ~UNIT_ATT; } return SCPE_OK; @@ -3260,6 +3370,13 @@ fprintf (st, "GW points to the default\nrouter. If DHCP is enabled these "); fprintf (st, "will be set from DHCP when the IMP is attached.\nIf IP is set "); fprintf (st, "and DHCP is enabled, when the IMP is attached it will inform\n"); fprintf (st, "the local DHCP server of it's address.\n\n"); +fprintf (st, "There is a second way to interact with a network.\n"); +fprintf (st, "If the NCP modifier is enabled, you must ATTACH\n"); +fprintf (st, "the IMP device to ::\n"); +fprintf (st, "It is expected that there is an IMP emulator at \n"); +fprintf (st, "listening to and talking to the PDP-10\n"); +fprintf (st, "at . This network will only accept the NCP\n"); +fprintf (st, "protocol that existed before TCP/IP.\n"); fprint_set_help (st, dptr); fprint_show_help (st, dptr); eth_attach_help(st, dptr, uptr, flag, cptr); diff --git a/Visual Studio Projects/PDP10-KA.vcproj b/Visual Studio Projects/PDP10-KA.vcproj index e8659cfe..53db8814 100644 --- a/Visual Studio Projects/PDP10-KA.vcproj +++ b/Visual Studio Projects/PDP10-KA.vcproj @@ -41,7 +41,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../PDP10/;../PDP11/;../VAX/;./;../;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2" - PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KA=1;USE_DISPLAY;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO" + PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KA=1;USE_DISPLAY;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO;VM_IMPTIP" KeepComments="false" BasicRuntimeChecks="0" RuntimeLibrary="1" @@ -309,6 +309,10 @@ RelativePath="..\PDP10\kx10_imp.c" > + + diff --git a/Visual Studio Projects/PDP10-KI.vcproj b/Visual Studio Projects/PDP10-KI.vcproj index 6554e28a..5c9ade50 100644 --- a/Visual Studio Projects/PDP10-KI.vcproj +++ b/Visual Studio Projects/PDP10-KI.vcproj @@ -41,7 +41,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../PDP10/;../PDP11/;../VAX/;./;../;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2" - PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KI=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;USE_DISPLAY;DISPLAY_TYPE=DIS_TYPE30;PIX_SCALE=RES_HALF;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC" + PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KI=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;USE_DISPLAY;DISPLAY_TYPE=DIS_TYPE30;PIX_SCALE=RES_HALF;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;VM_IMPTIP" KeepComments="false" BasicRuntimeChecks="0" RuntimeLibrary="1" @@ -245,6 +245,10 @@ RelativePath="..\PDP10\kx10_imp.c" > + + diff --git a/Visual Studio Projects/PDP10-KL.vcproj b/Visual Studio Projects/PDP10-KL.vcproj index 7a49cf4f..a177d09e 100644 --- a/Visual Studio Projects/PDP10-KL.vcproj +++ b/Visual Studio Projects/PDP10-KL.vcproj @@ -41,7 +41,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../PDP10;.;..;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2" - PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KL=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO" + PreprocessorDefinitions="USE_INT64;USE_SIM_CARD;KL=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO;VM_IMPTIP" KeepComments="false" BasicRuntimeChecks="0" RuntimeLibrary="1" @@ -249,6 +249,10 @@ RelativePath="..\PDP10\kx10_imp.c" > + + diff --git a/Visual Studio Projects/PDP10-KS.vcproj b/Visual Studio Projects/PDP10-KS.vcproj index e57decfb..2ecdfc98 100644 --- a/Visual Studio Projects/PDP10-KS.vcproj +++ b/Visual Studio Projects/PDP10-KS.vcproj @@ -41,7 +41,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../PDP10/;../PDP11/;../VAX/;./;../;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2" - PreprocessorDefinitions="USE_INT64;KS=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO" + PreprocessorDefinitions="USE_INT64;KS=1;USE_SHARED;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;PTW32_STATIC_LIB;USE_READER_THREAD;SIM_ASYNCH_IO;VM_IMPTIP" KeepComments="false" BasicRuntimeChecks="0" RuntimeLibrary="1" @@ -237,6 +237,10 @@ RelativePath="..\PDP10\kx10_imp.c" > + + diff --git a/cmake/simh-simulators.cmake b/cmake/simh-simulators.cmake index 3fc7e6aa..2d8a0446 100644 --- a/cmake/simh-simulators.cmake +++ b/cmake/simh-simulators.cmake @@ -9,6 +9,7 @@ ## ## ------------------------------------------------------------ set(B5500D "${CMAKE_SOURCE_DIR}/B5500") +set(H316D "${CMAKE_SOURCE_DIR}/H316") set(I7000D "${CMAKE_SOURCE_DIR}/I7000") set(I7010D "${CMAKE_SOURCE_DIR}/I7000") set(IBM360D "${CMAKE_SOURCE_DIR}/IBM360") diff --git a/makefile b/makefile index d40abfbb..8b83aa29 100644 --- a/makefile +++ b/makefile @@ -1543,6 +1543,9 @@ PDP6_OPT += -DPIDP10=1 PDP6 += ${PDP6D}/ka10_pipanel.c endif +H316D = ${SIMHD}/H316 +IMPUDP = ${H316D}/h316_udp.c + PDP10D = ${SIMHD}/PDP10 KA10D = ${SIMHD}/PDP10 ifneq (,${DISPLAY_OPT}) @@ -1563,8 +1566,8 @@ KA10 = ${KA10D}/kx10_cpu.c ${KA10D}/kx10_sys.c ${KA10D}/kx10_df.c \ ${PDP10D}/ka10_ai.c ${KA10D}/ka10_iii.c ${KA10D}/kx10_disk.c \ ${PDP10D}/ka10_pclk.c ${PDP10D}/ka10_tv.c ${KA10D}/kx10_ddc.c \ ${PDP10D}/ka10_dd.c \ - ${DISPLAYL} ${DISPLAY340} -KA10_OPT = -DKA=1 -DUSE_INT64 -I ${KA10D} -DUSE_SIM_CARD ${NETWORK_OPT} ${DISPLAY_OPT} ${KA10_DISPLAY_OPT} + ${DISPLAYL} ${DISPLAY340} ${IMPUDP} +KA10_OPT = -DKA=1 -DUSE_INT64 -DVM_IMPTIP -I ${KA10D} -I${H316D} -DUSE_SIM_CARD ${NETWORK_OPT} ${DISPLAY_OPT} ${KA10_DISPLAY_OPT} ifneq (${PANDA_LIGHTS},) # ONLY for Panda display. KA10_OPT += -DPANDA_LIGHTS @@ -1587,8 +1590,9 @@ KI10 = ${KI10D}/kx10_cpu.c ${KI10D}/kx10_sys.c ${KI10D}/kx10_df.c \ ${KI10D}/kx10_dt.c ${KI10D}/kx10_dk.c ${KI10D}/kx10_cr.c \ ${KI10D}/kx10_cp.c ${KI10D}/kx10_tu.c ${KI10D}/kx10_rs.c \ ${KI10D}/kx10_imp.c ${KI10D}/kx10_dpy.c ${KI10D}/kx10_disk.c \ - ${KI10D}/kx10_ddc.c ${KI10D}/kx10_tym.c ${DISPLAYL} ${DISPLAY340} -KI10_OPT = -DKI=1 -DUSE_INT64 -I ${KI10D} -DUSE_SIM_CARD ${NETWORK_OPT} ${DISPLAY_OPT} ${KI10_DISPLAY_OPT} + ${KI10D}/kx10_ddc.c ${KI10D}/kx10_tym.c ${DISPLAYL} ${DISPLAY340} \ + ${IMPUDP} +KI10_OPT = -DKI=1 -DUSE_INT64 -DVM_IMPTIP -I ${KI10D} -DUSE_SIM_CARD ${NETWORK_OPT} ${DISPLAY_OPT} ${KI10_DISPLAY_OPT} ifneq (${PANDA_LIGHTS},) # ONLY for Panda display. KI10_OPT += -DPANDA_LIGHTS @@ -1608,8 +1612,8 @@ KL10 = ${KL10D}/kx10_cpu.c ${KL10D}/kx10_sys.c ${KL10D}/kx10_df.c \ ${KL10D}/kx10_rp.c ${KL10D}/kx10_tu.c ${KL10D}/kx10_rs.c \ ${KL10D}/kx10_imp.c ${KL10D}/kl10_fe.c ${KL10D}/ka10_pd.c \ ${KL10D}/ka10_ch10.c ${KL10D}/kl10_nia.c ${KL10D}/kx10_disk.c \ - ${KL10D}/kl10_dn.c -KL10_OPT = -DKL=1 -DUSE_INT64 -I ${KL10D} -DUSE_SIM_CARD ${NETWORK_OPT} + ${KL10D}/kl10_dn.c ${IMPUDP} +KL10_OPT = -DKL=1 -DUSE_INT64 -DVM_IMPTIP -I ${KL10D} -DUSE_SIM_CARD ${NETWORK_OPT} ifneq (${PIDP10},) KS10_OPT += -DPIDP10=1 KS10 += ${KS10D}/ka10_pipanel.c @@ -1620,8 +1624,8 @@ KS10 = ${KS10D}/kx10_cpu.c ${KS10D}/kx10_sys.c ${KS10D}/kx10_disk.c \ ${KS10D}/ks10_cty.c ${KS10D}/ks10_uba.c ${KS10D}/kx10_rh.c \ ${KS10D}/kx10_rp.c ${KS10D}/kx10_tu.c ${KS10D}/ks10_dz.c \ ${KS10D}/ks10_tcu.c ${KS10D}/ks10_lp.c ${KS10D}/ks10_ch11.c \ - ${KS10D}/ks10_kmc.c ${KS10D}/ks10_dup.c ${KS10D}/kx10_imp.c -KS10_OPT = -DKS=1 -DUSE_INT64 -I ${KS10D} ${NETWORK_OPT} + ${KS10D}/ks10_kmc.c ${KS10D}/ks10_dup.c ${KS10D}/kx10_imp.c ${IMPUDP} +KS10_OPT = -DKS=1 -DUSE_INT64 -DVM_IMPTIP -I ${KS10D} ${NETWORK_OPT} ifneq (${PIDP10},) KS10_OPT += -DPIDP10=1 KS10 += ${KS10D}/ka10_pipanel.c