From 5cd1e8b7ac42dec642853dccda1e75eea7eb706b Mon Sep 17 00:00:00 2001 From: Paul Koning Date: Sat, 8 Jan 2022 14:24:42 -0800 Subject: [PATCH] TMXR, PDP11, PDP10, VAX: Add DDCMP sync framer support This adds support for the "framer" device, which is a USB-connected device built around a Raspberry Pico that connects to a synchronous line, either RS-232 or DEC "integral modem" coax connection. It implements the framing portion of DDCMP: clock recovery for the integral modem case, byte sync, and DDCMP frame handling including CRC. The actual DDCMP protocol state machine, with its handling of sequencing, timeout and retransmit, etc. is left to the host software. All the design files for the framer may be found at https://github.com/pkoning2/ddcmp . This commit adds code to drive the framer from the TMXR library, allowing it to be used either from simulated DMC-11 or simulated DUP-11 devices. Both have been tested, using RSTS/E, RSX-11/M+, and TOPS-20. Fixed the one-digit limit on eth device names, the limit is now 2. --- PDP11/ddcmp_sync.md | 9 + PDP11/pdp11_dmc.c | 33 ++- PDP11/pdp11_dup.c | 19 +- README.md | 5 + scp.c | 3 + sim_ether.c | 57 +++-- sim_ether.h | 4 +- sim_tmxr.c | 547 +++++++++++++++++++++++++++++++++++++++----- sim_tmxr.h | 10 +- 9 files changed, 601 insertions(+), 86 deletions(-) create mode 100644 PDP11/ddcmp_sync.md diff --git a/PDP11/ddcmp_sync.md b/PDP11/ddcmp_sync.md new file mode 100644 index 00000000..d2f514ce --- /dev/null +++ b/PDP11/ddcmp_sync.md @@ -0,0 +1,9 @@ +# DDCMP synchronous line interface + +The DDCMP synchronous interface supported by the DUP and DMC device emulations, is a USB connected device that implements synchronous links for use by DDCMP. + +When used with SIMH, the synchronous interface allows an emulated DMC-11 or DUP-11 (including KMC/DUP) to be connected to a system that uses a real synchronous link for DDCMP, for example an actual DMC-11 device. The interface can support either RS-232 connectons for the DUP-11 or DMC-11 "remote" line cards, or the "integral modem" coaxial cable connection implemented in the DMC "local" line card. Speeds in the range 500 to 56k bits per second (for RS-232) or 56k to 1M bits per second (for integral modem) are supported. + +The DDCMP synchronous line interface is an open source design. All the design files (schematics, circuit board layout, firmware, and documentation) may be found on GitHub at [this link](https://github.com/pkoning2/ddcmp "DDCMP Framer on GitHub"). + +Note that at the moment, no boards, kits, or assembled units are offered. If you want a framer, you would need to obtain a circuit board and assemble it. The documentation in the GitHub design files explains how to do this. The design uses all "through hole" parts, so modest electronics assembly skills suffice to build a unit. diff --git a/PDP11/pdp11_dmc.c b/PDP11/pdp11_dmc.c index 7d2d507c..bc063407 100644 --- a/PDP11/pdp11_dmc.c +++ b/PDP11/pdp11_dmc.c @@ -1250,6 +1250,8 @@ MTAB dmc_mod[] = { &set_addr, &show_addr, NULL, "Bus address" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 1, "VECTOR", "VECTOR", &set_vec, &show_vec, NULL, "Interrupt vector" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SYNC", NULL, + NULL, &tmxr_show_sync, NULL, "Display attachable DDCMP synchronous links" }, { 0 } }; extern DEVICE dmp_dev; @@ -1891,6 +1893,18 @@ const char helpString[] = "\n" "+sim> ATTACH %U port,UDP\n" "\n" + " Communication may alternately use the DDCMP synchronous framer device.\n" + " The DDCMP synchronous device is a USB device that can send and\n" + " receive DDCMP frames over either RS-232 or coax synchronous lines.\n" + " Refer to https://github.com/pkoning2/ddcmp for documentation.\n" + "\n" + "+sim> ATTACH %U SYNC=ifname:mode:speed\n" + "\n" + " Communicate via the synchronous DDCMP framer interface \"ifname\", \n" + " and framer mode \"mode\" -- one of INTEGRAL, RS232_DTE, or\n" + " RS232_DCE. The \"speed\" argument is the bit rate for the line.\n" + " In SYNC mode, the \"PEER\" parameter is not used and need not be set.\n" + " You can use \"SHOW SYNC\" to see the list of synchronous DDCMP devices.\n" "2 Examples\n" " To configure two simulators to talk to each other use the following\n" " example:\n" @@ -1905,6 +1919,8 @@ const char helpString[] = "+sim> SET %U PEER=LOCALHOST:1111\n" "+sim> ATTACH %U 2222\n" "\n" + " To communicate with an \"integral modem\" DMC or similar, at 56 kbps:\n" + "+sim> ATTACH %U SYNC=sync0:INTEGRAL:56000\n" "1 Monitoring\n" " The %D device and %U line configuration and state can be displayed with\n" " one of the available show commands.\n" @@ -3972,14 +3988,21 @@ if (!cptr || !*cptr) return SCPE_ARG; if (!(uptr->flags & UNIT_ATTABLE)) return SCPE_NOATT; -if (!peer[0]) { - sim_printf ("Peer must be specified before attach\n"); - return SCPE_ARG; +if (0 == strncasecmp (cptr, "SYNC", 4)) { + sprintf (attach_string, "Line=%d,%s", dmc, cptr); + ans = tmxr_open_master (mp, attach_string); +} +else { + if (!peer[0]) { + sim_printf ("Peer must be specified before attach\n"); + return SCPE_ARG; } -sprintf (attach_string, "Line=%d,Connect=%s,%s", dmc, peer, cptr); -ans = tmxr_open_master (mp, attach_string); /* open master socket */ + sprintf (attach_string, "Line=%d,Connect=%s,%s", dmc, peer, cptr); + ans = tmxr_open_master (mp, attach_string); /* open master socket */ +} if (ans != SCPE_OK) return ans; +mp->dptr = dptr; strncpy (port, cptr, sizeof(dmc_port[0])); uptr->filename = (char *)malloc (strlen(port)+1); strcpy (uptr->filename, port); diff --git a/PDP11/pdp11_dup.c b/PDP11/pdp11_dup.c index d46f2d8d..3f6dbd52 100644 --- a/PDP11/pdp11_dup.c +++ b/PDP11/pdp11_dup.c @@ -405,6 +405,8 @@ static MTAB dup_mod[] = { NULL, &tmxr_show_cstat, (void *) &dup_desc, "Display current connections" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n", &dup_setnl, &tmxr_show_lines, (void *) &dup_desc, "Display number of lines" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SYNC", NULL, + NULL, &tmxr_show_sync, NULL, "Display attachable DDCMP synchronous links" }, { 0 } }; @@ -554,8 +556,8 @@ switch ((PA >> 1) & 03) { /* case on PA<2:1> */ dup_set_modem (dup, data); dup_rxcsr[dup] &= ~RXCSR_WRITEABLE; dup_rxcsr[dup] |= (data & RXCSR_WRITEABLE); - if ((dup_rxcsr[dup] & RXCSR_M_RTS) && /* Upward transition of RTS */ - (!(orig_val & RXCSR_M_RTS))) /* Enables Receive on the line */ + if ((dup_rxcsr[dup] & RXCSR_M_DTR) && /* Upward transition of DTR */ + (!(orig_val & RXCSR_M_DTR))) /* Enables Receive on the line */ dup_desc.ldsc[dup].rcve = TRUE; if ((dup_rxcsr[dup] & RXCSR_M_RTS) && /* Upward transition of RTS */ (!(orig_val & RXCSR_M_RTS)) && /* while receiver is enabled and */ @@ -1560,6 +1562,17 @@ const char helpString[] = " the peer. Alternatively, UDP can be used by specifying UDP on the\n" " ATTACH command.\n" "\n" + " Communication may alternately use the DDCMP synchronous framer device.\n" + " The DDCMP synchronous device is a USB device that can send and\n" + " receive DDCMP frames over either RS-232 or coax synchronous lines.\n" + " Refer to https://github.com/pkoning2/ddcmp for documentation.\n" + "\n" + "+sim> ATTACH %U SYNC=ifname:mode:speed\n" + "\n" + " Communicate via the synchronous DDCMP framer interface \"ifname\", \n" + " and framer mode \"mode\" -- one of INTEGRAL, RS232_DTE, or\n" + " RS232_DCE. The \"speed\" argument is the bit rate for the line.\n" + " You can use \"SHOW SYNC\" to see the list of synchronous DDCMP devices.\n" "2 Examples\n" " To configure two simulators to talk to each other use the following\n" " example:\n" @@ -1572,6 +1585,8 @@ const char helpString[] = "+sim> SET %D ENABLE\n" "+sim> ATTACH %U 2222,connect=LOCALHOST:1111\n" "\n" + " To communicate with an \"integral modem\" DMC or similar, at 56 kbps:\n" + "+sim> ATTACH %U SYNC=sync0:INTEGRAL:56000\n" "1 Monitoring\n" " The %D device and %U line configuration and state can be displayed with\n" " one of the available show commands.\n" diff --git a/README.md b/README.md index db2454ec..4239150b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ . . [New Simulators](#new-simulators) . . [Simulator Front Panel API](#simulator-front-panel-api) . . [New Functionality](#new-functionality) +. . . . [DDCMP Synchronous host physical device support - framer](#ddcmp-synchronous-host-physical-device-support---framer) . . . . [Remote Console Facility](#remote-console-facility) . . . . [VAX/PDP11 Enhancements](#vaxpdp11-enhancements) . . . . [PDP11 Specific Enhancements](#pdp11-specific-enhancements) @@ -110,6 +111,10 @@ The sim_frontpanel API provides a programmatic interface to start and control an ### New Functionality +#### DDCMP Synchronous host physical device support - framer +Paul Koning has implemented a USB hardware device which can interface transport DDCMP packets across a synchronous line +to physical host systems with native synchronous devices or other simulators using framer devices. + #### Remote Console Facility A new capability has been added which allows a TELNET Connection to a user designated port so that some out of band commands can be entered to manipulate and/or adjust a running simulator. The commands which enable and control this capability are SET REMOTE TELNET=port, SET REMOTE CONNECTIONS=n, SET REMOTE TIMEOUT=seconds, and SHOW REMOTE. diff --git a/scp.c b/scp.c index fcd3a609..0e1d4fe5 100644 --- a/scp.c +++ b/scp.c @@ -1575,6 +1575,7 @@ static const char simh_help2[] = "+sh{ow} {arg,...} show unit parameters\n" "+sh{ow} ethernet show ethernet devices\n" "+sh{ow} serial show serial devices\n" + "+sh{ow} synchronous show DDCMP synchronous interface devices\n" "+sh{ow} multiplexer {dev} show open multiplexer device info\n" "+sh{ow} video show video capabilities\n" "+sh{ow} clocks show calibrated timer information\n" @@ -1603,6 +1604,7 @@ static const char simh_help2[] = #define HLP_SHOW_ASYNCH "*Commands SHOW" #define HLP_SHOW_ETHERNET "*Commands SHOW" #define HLP_SHOW_SERIAL "*Commands SHOW" +#define HLP_SHOW_SYNC "*Commands SHOW" #define HLP_SHOW_MULTIPLEXER "*Commands SHOW" #define HLP_SHOW_VIDEO "*Commands SHOW" #define HLP_SHOW_CLOCKS "*Commands SHOW" @@ -2643,6 +2645,7 @@ static SHTAB show_glob_tab[] = { { "ASYNCH", &sim_show_asynch, 0, HLP_SHOW_ASYNCH }, { "ETHERNET", ð_show_devices, 0, HLP_SHOW_ETHERNET }, { "SERIAL", &sim_show_serial, 0, HLP_SHOW_SERIAL }, + { "SYNCHRONOUS", &tmxr_show_sync_devices, 0, HLP_SHOW_SYNC }, { "MULTIPLEXER", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER }, { "MUX", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER }, { "VIDEO", &vid_show, 0, HLP_SHOW_VIDEO }, diff --git a/sim_ether.c b/sim_ether.c index 95e9489f..ca963484 100644 --- a/sim_ether.c +++ b/sim_ether.c @@ -383,6 +383,9 @@ /* Internal routine - forward declaration */ static int _eth_get_system_id (char *buf, size_t buf_size); +static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname, int set_on); + +static const unsigned char framer_oui[3] = { 0xaa, 0x00, 0x03 }; /*============================================================================*/ /* OS-independant ethernet routines */ @@ -780,13 +783,11 @@ return eth_show (st, uptr, val, NULL); } #if defined (USE_NETWORK) || defined (USE_SHARED) -/* Internal routine - forward declaration */ -static int _eth_devices (int max, ETH_LIST* dev); /* get ethernet devices on host */ static const char* _eth_getname(int number, char* name, char *desc) { ETH_LIST list[ETH_MAX_DEVICE]; - int count = _eth_devices(ETH_MAX_DEVICE, list); + int count = eth_devices(ETH_MAX_DEVICE, list, FALSE); if ((number < 0) || (count <= number)) return NULL; @@ -803,7 +804,7 @@ static const char* _eth_getname(int number, char* name, char *desc) const char* eth_getname_bydesc(const char* desc, char* name, char *ndesc) { ETH_LIST list[ETH_MAX_DEVICE]; - int count = _eth_devices(ETH_MAX_DEVICE, list); + int count = eth_devices(ETH_MAX_DEVICE, list, FALSE); int i; size_t j=strlen(desc); @@ -829,7 +830,7 @@ const char* eth_getname_bydesc(const char* desc, char* name, char *ndesc) char* eth_getname_byname(const char* name, char* temp, char *desc) { ETH_LIST list[ETH_MAX_DEVICE]; - int count = _eth_devices(ETH_MAX_DEVICE, list); + int count = eth_devices(ETH_MAX_DEVICE, list, FALSE); size_t n; int i, found; @@ -849,7 +850,7 @@ char* eth_getname_byname(const char* name, char* temp, char *desc) char* eth_getdesc_byname(char* name, char* temp) { ETH_LIST list[ETH_MAX_DEVICE]; - int count = _eth_devices(ETH_MAX_DEVICE, list); + int count = eth_devices(ETH_MAX_DEVICE, list, FALSE); size_t n; int i, found; @@ -894,7 +895,7 @@ t_stat eth_show (FILE* st, UNIT* uptr, int32 val, CONST void* desc) ETH_LIST list[ETH_MAX_DEVICE]; int number; - number = _eth_devices(ETH_MAX_DEVICE, list); + number = eth_devices(ETH_MAX_DEVICE, list, FALSE); fprintf(st, "ETH devices:\n"); if (number == -1) fprintf(st, " network support not available in simulator\n"); @@ -965,6 +966,8 @@ t_stat eth_filter_hash (ETH_DEV* dev, int addr_count, ETH_MAC* const addresses, {return SCPE_NOFNC;} const char *eth_version (void) {return NULL;} +int eth_devices(int max, ETH_LIST* list, ETH_BOOL framers) + {return 0;} void eth_show_dev (FILE* st, ETH_DEV* dev) {} t_stat eth_show (FILE* st, UNIT* uptr, int32 val, CONST void* desc) @@ -1112,13 +1115,14 @@ for (i=0; inext, ++used) { + for (used=0, dev=alldevs; dev && (used < max); dev=dev->next) { + edev.eth_api = ETH_API_PCAP; + eth_get_nic_hw_addr (&edev, dev->name, 0); + if ((memcmp (edev.host_nic_phy_hw_addr, framer_oui, 3) == 0) != framers) + continue; if ((dev->flags & PCAP_IF_LOOPBACK) || (!strcmp("any", dev->name))) continue; strlcpy(list[used].name, dev->name, sizeof(list[used].name)); @@ -1137,6 +1145,7 @@ else { strlcpy(list[used].desc, dev->description, sizeof(list[used].desc)); else strlcpy(list[used].desc, "No description available", sizeof(list[used].desc)); + ++used; } /* free device list */ @@ -1152,6 +1161,9 @@ if ((used == 0) && (errbuf[0])) { sim_printf ("Eth: pcap_findalldevs warning: %s\n", errbuf); } +if (framers) + return used; /* don't add pseudo-ethernet devices */ + #ifdef HAVE_TAP_NETWORK if (used < max) { #if defined(__OpenBSD__) @@ -1736,7 +1748,7 @@ static int pcap_mac_if_vms(const char *AdapterName, unsigned char MACAddress[6]) } #endif /* defined (__VMS) && !defined(__VAX) */ -static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname) +static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname, int set_on) { memset(&dev->host_nic_phy_hw_addr, 0, sizeof(dev->host_nic_phy_hw_addr)); dev->have_host_nic_phy_addr = 0; @@ -1766,13 +1778,15 @@ static void eth_get_nic_hw_addr(ETH_DEV* dev, const char *devname) NULL}; memset(command, 0, sizeof(command)); - /* try to force an otherwise unused interface to be turned on */ - for (i=0; turnon[i]; ++i) { - snprintf(command, sizeof(command), turnon[i], (int)(sizeof(command) - (2 + strlen(patterns[i]))), devname); - get_glyph_nc (command, tool, 0); - if (sim_get_tool_path (tool)[0]) { - if (NULL != (f = popen(command, "r"))) - pclose(f); + if (set_on) { + /* try to force an otherwise unused interface to be turned on */ + for (i=0; turnon[i]; ++i) { + snprintf(command, sizeof(command), turnon[i], (int)(sizeof(command) - (2 + strlen(patterns[i]))), devname); + get_glyph_nc (command, tool, 0); + if (sim_get_tool_path (tool)[0]) { + if (NULL != (f = popen(command, "r"))) + pclose(f); + } } } for (i=0; patterns[i] && (0 == dev->have_host_nic_phy_addr); ++i) { @@ -2500,12 +2514,13 @@ if (bufsz < ETH_MAX_JUMBO_FRAME) /* initialize device */ eth_zero(dev); -/* translate name of type "ethX" to real device name */ -if ((strlen(name) == 4) +/* translate name of type "eth" to real device name */ +if ((strlen(name) == 4 || strlen(name) == 5) && (tolower(name[0]) == 'e') && (tolower(name[1]) == 't') && (tolower(name[2]) == 'h') && isdigit(name[3]) + && (strlen(name) == 4 || isdigit(name[4])) ) { num = atoi(&name[3]); savname = _eth_getname(num, temp, desc); @@ -2545,7 +2560,7 @@ if (!strcmp (desc, "No description available")) sim_messagef (SCPE_OK, "Eth: opened OS device %s%s%s\n", savname, desc[0] ? " - " : "", desc); /* get the NIC's hardware MAC address */ -eth_get_nic_hw_addr(dev, savname); +eth_get_nic_hw_addr(dev, savname, 1); /* save name of device */ dev->name = (char *)malloc(strlen(savname)+1); @@ -4368,7 +4383,7 @@ int bpf_compile_skip_count = 0; memset (ð_tst, 0, sizeof(eth_tst)); -eth_device_count = _eth_devices(ETH_MAX_DEVICE, eth_list); +eth_device_count = eth_devices(ETH_MAX_DEVICE, eth_list, FALSE); eth_opened = 0; for (eth_num=0; eth_numloopback) return loop_read (lp, &(lp->rxb[i]), length); if (lp->serport) /* serial port connection? */ return sim_read_serial (lp->serport, &(lp->rxb[i]), length, &(lp->rbr[i])); -else /* Telnet connection */ - return sim_read_sock (lp->sock, &(lp->rxb[i]), length); +else { + if (lp->framer) + return tmxr_framer_read (lp, &(lp->rxb[i]), length); + else /* Telnet connection */ + return sim_read_sock (lp->sock, &(lp->rxb[i]), length); + } } @@ -712,23 +757,27 @@ if (lp->serport) { /* serial port connectio written = sim_write_serial (lp->serport, &(lp->txb[i]), length); } else { - if (lp->sock) { /* Telnet connection */ - written = sim_write_sock (lp->sock, &(lp->txb[i]), length); - - if (written == SOCKET_ERROR) { /* did an error occur? */ - lp->txdone = TRUE; - if (lp->datagram) - return written; /* ignore errors on datagram sockets */ - else - return -1; /* return error indication */ - } - } + if (lp->framer) + written = tmxr_framer_write (lp, &(lp->txb[i]), length); else { - if ((lp->conn == TMXR_LINE_DISABLED) || - ((lp->conn == 0) && lp->txbfd)){ - written = length; /* Count here output timing is correct */ - if (lp->conn == TMXR_LINE_DISABLED) - lp->txdrp += length; /* Record as having been dropped on the floor */ + if (lp->sock) { /* Telnet connection */ + written = sim_write_sock (lp->sock, &(lp->txb[i]), length); + + if (written == SOCKET_ERROR) { /* did an error occur? */ + lp->txdone = TRUE; + if (lp->datagram) + return written; /* ignore errors on datagram sockets */ + else + return -1; /* return error indication */ + } + } + else { + if ((lp->conn == TMXR_LINE_DISABLED) || + ((lp->conn == 0) && lp->txbfd)){ + written = length; /* Count here output timing is correct */ + if (lp->conn == TMXR_LINE_DISABLED) + lp->txdrp += length; /* Record as having been dropped on the floor */ + } } } } @@ -1091,6 +1140,9 @@ if (mp->master) { for (j = 0; j < mp->lines; j++, i++) { /* find next avail line */ lp = mp->ldsc + j; /* get pointer to line descriptor */ + if (lp->framer) + continue; + if ((lp->conn == FALSE) && /* is the line available? */ (lp->destination == NULL) && (lp->master == 0) && @@ -1172,6 +1224,17 @@ for (i = 0; i < mp->lines; i++) { /* check each line in se return i; } + /* Framer: report connected. */ + if (lp->framer) { + if (lp->framer->connect_pending) { + /* Say "connected" when first asked */ + lp->framer->connect_pending = FALSE; + lp->conn = TRUE; /* record connection */ + return i; + } + continue; + } + /* Don't service network connections for loopbacked lines */ if (lp->loopback) @@ -1660,10 +1723,14 @@ return SCPE_OK; Implementation note: - If a line is connected to a serial port, then these values affect - and reflect the state of the serial port. If the line is connected - to a network socket (or could be) then the network session state is - set, cleared and/or returned. + If a line is connected to a serial port, then these values + affect and reflect the state of the serial port. If the line + is connected to a network socket (or could be) then the network + session state is set, cleared and/or returned. If the line is + connected to a DDCMP sync framer, only DTR and RTS set/clear + are acted on, and the returned modem state bits are constructed + based on the framer state. For the framer, setting DTR starts + the framer, and clearing DTR stops it. */ t_stat tmxr_set_get_modem_bits (TMLN *lp, int32 bits_to_set, int32 bits_to_clear, int32 *status_bits) { @@ -1676,6 +1743,37 @@ if ((bits_to_set & ~(TMXR_MDM_OUTGOING)) || /* Assure only settable bits (bits_to_clear & ~(TMXR_MDM_OUTGOING)) || (bits_to_set & bits_to_clear)) /* and can't set and clear the same bits */ return SCPE_ARG; +if (lp->framer) { + /* DDCMP framer attached, ignore set except for DTR and RTS. + * Given the most recently received framer status, report DSR if + * the framer is currently on, and CTS and Carrier Detect if a + * carrier has been received. + */ + bits_to_set &= TMXR_MDM_DTR | TMXR_MDM_RTS; + bits_to_clear &= TMXR_MDM_DTR | TMXR_MDM_RTS; + if ((bits_to_set & TMXR_MDM_DTR) && !(lp->modembits & TMXR_MDM_DTR)) { + /* DTR being set, start framer if we're using one. Use DMC + * mode for now. + */ + tmxr_start_framer (lp, TRUE); + } + else { + if ((bits_to_clear & TMXR_MDM_DTR) && (lp->modembits & TMXR_MDM_DTR)) + /* DTR being cleared, stop framer if we're using one. */ + tmxr_stop_framer (lp); + } + incoming_state = lp->modembits | bits_to_set; + incoming_state &= ~bits_to_clear; + if (lp->framer->status.on) + incoming_state |= TMXR_MDM_DSR; + if (lp->framer->status.on & ON_SYN) + /* Carrier detected */ + incoming_state |= TMXR_MDM_CTS | TMXR_MDM_DCD; + lp->modembits = incoming_state; + if (status_bits) + *status_bits = incoming_state; + return SCPE_OK; +} before_modem_bits = lp->modembits; lp->modembits |= bits_to_set; lp->modembits &= ~bits_to_clear; @@ -2023,7 +2121,7 @@ TMLN *lp; tmxr_debug_trace (mp, "tmxr_poll_rx()"); for (i = 0; i < mp->lines; i++) { /* loop thru lines */ lp = mp->ldsc + i; /* get line desc */ - if (!(lp->sock || lp->serport || lp->loopback) || + if (!(lp->sock || lp->serport || lp->loopback || lp->framer) || !(lp->rcve)) /* skip if not connected */ continue; @@ -2031,9 +2129,11 @@ for (i = 0; i < mp->lines; i++) { /* loop thru lines */ if (lp->rxbpi == 0) /* need input? */ nbytes = tmxr_read (lp, /* yes, read */ lp->rxbsz - TMXR_GUARD); /* leave spc for Telnet cruft */ - else if (lp->tsta) /* in Telnet seq? */ - nbytes = tmxr_read (lp, /* yes, read to end */ - lp->rxbsz - lp->rxbpi); + else { + if (lp->tsta) /* in Telnet seq? */ + nbytes = tmxr_read (lp, /* yes, read to end */ + lp->rxbsz - lp->rxbpi); + } if (nbytes < 0) { /* line error? */ if (!lp->datagram) { /* ignore errors reading UDP sockets */ @@ -2538,6 +2638,17 @@ return 0; /* not done */ static void _mux_detach_line (TMLN *lp, t_bool close_listener, t_bool close_connecting) { +if (lp->framer) { + /* DDCMP framer in use, close that up. Begin by making sure it is + * stopped. + */ + tmxr_stop_framer (lp); + /* Finished with the framer's Ethernet interface */ + eth_close (lp->framer->eth); + free (lp->framer->eth); + free (lp->framer); + lp->framer = NULL; + } if (close_listener && lp->master) { sim_close_sock (lp->master); lp->master = 0; @@ -2700,6 +2811,21 @@ if (lp->o_uptr) return SCPE_OK; } +static const char* _tmxr_getname(int number, char* name) +{ + ETH_LIST list[ETH_MAX_DEVICE]; + int count = eth_devices(ETH_MAX_DEVICE, list, TRUE); + + if ((number < 0) || (count <= number)) + return NULL; + if (list[number].eth_api != ETH_API_PCAP) { + sim_printf ("Tmxr: Synchronous line device not found. You may need to run as root\n"); + return NULL; + } + + strcpy(name, list[number].name); + return name; +} /* Open a master listening socket (and all of the other variances of connections). @@ -2719,6 +2845,12 @@ int32 i, line, nextline = -1; char tbuf[CBUFSIZE], listen[CBUFSIZE], destination[CBUFSIZE], logfiletmpl[CBUFSIZE], buffered[CBUFSIZE], hostport[CBUFSIZE], port[CBUFSIZE], option[CBUFSIZE], speed[CBUFSIZE], dev_name[CBUFSIZE]; +char framer[CBUFSIZE],fr_eth[CBUFSIZE]; +int num; +int8 fr_mode; +int32 fr_speed; +FRAMER *framer_s; +ETH_DEV *eth; SOCKET sock; SERHANDLE serport; CONST char *tptr = cptr; @@ -2752,6 +2884,7 @@ while (*tptr) { memset(port, '\0', sizeof(port)); memset(option, '\0', sizeof(option)); memset(speed, '\0', sizeof(speed)); + memset(framer, '\0', sizeof(framer)); nolog = loopback = disabled = FALSE; datagram = mp->datagram; packet = mp->packet; @@ -2852,6 +2985,13 @@ while (*tptr) { strlcpy (destination, cptr, sizeof(destination)); continue; } + if (0 == MATCH_CMD (gbuf, "SYNC")) { + if ((NULL == cptr) || ('\0' == *cptr)) + return sim_messagef (SCPE_2FARG, "Missing Framer Specifier\n"); + strlcpy (framer, cptr, sizeof(framer)); + nomessage = notelnet = datagram = TRUE; + continue; + } if (0 == MATCH_CMD (gbuf, "DISABLED")) { if ((NULL != cptr) && ('\0' != *cptr)) return sim_messagef (SCPE_2FARG, "Unexpected Disabled Specifier: %s\n", cptr); @@ -2922,11 +3062,13 @@ while (*tptr) { } } if (disabled) { - if (destination[0] || listen[0] || loopback) - return sim_messagef (SCPE_ARG, "Can't disable line with%s%s%s%s%s\n", destination[0] ? " CONNECT=" : "", destination, listen[0] ? " " : "", listen, loopback ? " LOOPBACK" : ""); + if (destination[0] || listen[0] || loopback || framer[0]) + return sim_messagef (SCPE_ARG, "Can't disable line with%s%s%s%s%s%s%s\n", destination[0] ? " CONNECT=" : "", destination, listen[0] ? " " : "", listen, loopback ? " LOOPBACK" : "", framer[0] ? " SYNC=" : "", framer); } if (destination[0]) { /* Validate destination */ + if (framer[0]) + return sim_messagef (SCPE_ARG, "Can't combine CONNECT=%s with SYNC=%s\n", destination, framer); serport = sim_open_serial (destination, NULL, &r); if (serport != INVALID_HANDLE) { sim_close_serial (serport); @@ -2961,9 +3103,62 @@ while (*tptr) { return sim_messagef (SCPE_ARG, "Invalid destination: %s\n", hostport); } } + if (framer[0]) { + if (listen[0] || loopback || (!notelnet) || (!datagram)) + return sim_messagef (SCPE_ARG, "Can't combined SYNC=%s with%s%s%s%s%s\n", framer, + listen[0] ? " " : "", listen, loopback ? " LOOPBACK" : "", + notelnet ? "" : " TELNET", + datagram ? "" : " STREAM"); + /* Validate framer spec */ + cptr = get_glyph_nc (framer, fr_eth, ':'); + cptr = get_glyph (cptr, option, ':'); + if (0 == MATCH_CMD (option, "INTEGRAL") || + 0 == MATCH_CMD (option, "COAX")) + fr_mode = 1; + else { + if (0 == MATCH_CMD (option, "LOOPBACK")) + fr_mode = 1 | 4; /* Integral modem, loopback */ + else + if (0 == MATCH_CMD (option, "RS232_DCE")) + fr_mode = 2; + else + if (0 == MATCH_CMD (option, "RS232_DTE")) + fr_mode = 0; + else + return sim_messagef (SCPE_ARG, "Invalid framer mode: %s\n", cptr); + } + /* Speed is a third value in the SYNC argument. We don't + * use the SPEED parameter because that only accepts the + * standard UART rates, which for the most part are not normal + * DDCMP line rates. + */ + fr_speed = 0; + if (cptr) + fr_speed = atoi (cptr); + /* Note that the framer ignores the speed parameter for DTE + * mode, but we require it here in order to have a speed value + * to control the scheduling machinery. It would be possible + * to make it optional and default it to some useable value + * like 56k, or to default it that way and then later set it + * according to the measured data rate reported by the framer. + * For now, don't bother. + * + * Also, while the minimum speed is usually 500 bps, it is 56k + * for integral modem mode -- the transformer coupling does + * not work reliably at speeds lower than that, and this + * matches the lowest speed supported by DEC hardware for that + * interface type. + */ + if (fr_speed < 500 || fr_speed > 1000000 || + (fr_speed < 56000 && (fr_mode & 1))) { + return sim_messagef (SCPE_ARG, "Invalid framer speed %d\n", fr_speed); + } + } if (line == -1) { if (disabled) return sim_messagef (SCPE_ARG, "Must specify line to disable\n"); + if (framer[0]) + return sim_messagef (SCPE_ARG, "Must specify line for framer\n"); if (modem_control != mp->modem_control) return SCPE_ARG; if (logfiletmpl[0]) { @@ -3016,6 +3211,8 @@ while (*tptr) { } } } + if (lp->framer) + continue; /* skip framer lines */ if ((listen[0]) && (!datagram)) { sock = sim_master_sock (listen, &r); /* make master socket */ if (r) @@ -3145,6 +3342,57 @@ while (*tptr) { else { /* line specific attach */ lp = &mp->ldsc[line]; lp->mp = mp; + if (framer[0]) { + /* translate name of type "sync" to real eth device name */ + if ((strlen(fr_eth) == 5 || strlen(fr_eth) == 6) + && (tolower(fr_eth[0]) == 's') + && (tolower(fr_eth[1]) == 'y') + && (tolower(fr_eth[2]) == 'n') + && (tolower(fr_eth[3]) == 'c') + && isdigit(fr_eth[4]) + && (strlen(fr_eth) == 5 || isdigit(fr_eth[5])) + ) { + num = atoi(&fr_eth[4]); + if (_tmxr_getname(num, fr_eth) == NULL) /* didn't translate */ + return SCPE_OPENERR; + } + /* Open the Ethernet device */ + framer_s = (FRAMER *)malloc (sizeof (FRAMER)); + memset (framer_s, 0, sizeof (*framer_s)); + eth = (ETH_DEV *)malloc (sizeof (ETH_DEV)); + memset (eth, 0, sizeof (*eth)); + eth->dptr = mp->dptr; + framer_s->eth = eth; + framer_s->connect_pending = TRUE; + r = eth_open (eth, fr_eth, mp->dptr, 0); + if (r != SCPE_OK) { + sim_messagef (r, "Eth open error %d\n", r); + free (eth); + free (framer_s); + return r; + } + /* Set the filters: our address, not all multi, not promiscuous */ + r = eth_filter (eth, 1, &(eth->host_nic_phy_hw_addr), 0, 0); + if (r != SCPE_OK) { + sim_messagef (r, "Eth set address filter error %d\n", r); + eth_close (eth); + free (eth); + free (framer_s); + return r; + } + lp = &mp->ldsc[line]; + lp->framer = framer_s; + lp->datagram = lp->notelnet = TRUE; + /* Remember these parameters for later; the framer is + * started separately; in the DMC/DMP emulation this is + * done at DDCMP startup to allow a DMP driver to select + * the mode via the device API. */ + framer_s->fmode = fr_mode; + framer_s->fspeed = fr_speed; + /* Set the scheduling parameters from the line speed */ + lp->txdeltausecs = lp->rxdeltausecs = (uint32) (8000000 / fr_speed); + tmxr_init_line (lp); /* initialize line state */ + } if (logfiletmpl[0]) { sim_close_logfile (&lp->txlogref); lp->txlog = NULL; @@ -3182,7 +3430,7 @@ while (*tptr) { } if ((listen[0]) && (!datagram)) { if ((mp->lines == 1) && (mp->master)) - return sim_messagef (SCPE_ARG, "Single Line MUX can have either line specific OR MUS listener but NOT both\n"); + return sim_messagef (SCPE_ARG, "Single Line MUX can have either line specific OR MUX listener but NOT both\n"); sock = sim_master_sock (listen, &r); /* make master socket */ if (r) return sim_messagef (SCPE_ARG, "Invalid Listen Specification: %s\n", listen); @@ -3219,34 +3467,36 @@ while (*tptr) { tmxr_report_connection (mp, lp); /* report the connection to the line */ } else { - lp->datagram = datagram; - if (datagram) { - if (listen[0]) { - lp->port = (char *)realloc (lp->port, 1 + strlen (listen)); - strcpy (lp->port, listen); /* save port */ + if (!lp->framer) { + lp->datagram = datagram; + if (datagram) { + if (listen[0]) { + lp->port = (char *)realloc (lp->port, 1 + strlen (listen)); + strcpy (lp->port, listen); /* save port */ + } + else + return sim_messagef (SCPE_ARG, "Missing listen port for Datagram socket\n"); + } + sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | + (packet ? SIM_SOCK_OPT_NODELAY : 0)); + if (sock != INVALID_SOCKET) { + _mux_detach_line (lp, FALSE, TRUE); + lp->destination = (char *)malloc(1+strlen(hostport)); + strcpy (lp->destination, hostport); + if (!lp->modem_control || (lp->modembits & TMXR_MDM_DTR)) { + lp->connecting = sock; + lp->ipad = (char *)malloc (1 + strlen (lp->destination)); + strcpy (lp->ipad, lp->destination); + } + else + sim_close_sock (sock); + lp->notelnet = notelnet; + lp->nomessage = nomessage; + tmxr_init_line (lp); /* init the line state */ } else - return sim_messagef (SCPE_ARG, "Missing listen port for Datagram socket\n"); + return sim_messagef (SCPE_ARG, "Can't open %s socket on %s%s%s\n", datagram ? "Datagram" : "Stream", datagram ? listen : "", datagram ? "<->" : "", hostport); } - sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | - (packet ? SIM_SOCK_OPT_NODELAY : 0)); - if (sock != INVALID_SOCKET) { - _mux_detach_line (lp, FALSE, TRUE); - lp->destination = (char *)malloc(1+strlen(hostport)); - strcpy (lp->destination, hostport); - if (!lp->modem_control || (lp->modembits & TMXR_MDM_DTR)) { - lp->connecting = sock; - lp->ipad = (char *)malloc (1 + strlen (lp->destination)); - strcpy (lp->ipad, lp->destination); - } - else - sim_close_sock (sock); - lp->notelnet = notelnet; - lp->nomessage = nomessage; - tmxr_init_line (lp); /* init the line state */ - } - else - return sim_messagef (SCPE_ARG, "Can't open %s socket on %s%s%s\n", datagram ? "Datagram" : "Stream", datagram ? listen : "", datagram ? "<->" : "", hostport); } } if (loopback) { @@ -4305,7 +4555,7 @@ TMLN *lp; for (i = 0; i < mp->lines; i++) { /* loop thru conn */ lp = mp->ldsc + i; - if (!lp->destination && lp->sock) { /* not serial and is connected? */ + if (!lp->destination && lp->sock) { /* not serial and is connected? */ tmxr_report_disconnection (lp); /* report disconnection */ tmxr_reset_ln (lp); /* disconnect line */ } @@ -4978,7 +5228,7 @@ static const char *dsab = "off"; if (ln >= 0) fprintf (st, "Line %d:", ln); -if ((!lp->sock) && (!lp->connecting) && (!lp->serport)) +if ((!lp->sock) && (!lp->connecting) && (!lp->serport) && (!lp->framer)) fprintf (st, " not connected\n"); else { if (ln >= 0) @@ -5410,6 +5660,35 @@ if (any == 0) return SCPE_OK; } +/* Show synchronous devices */ + +t_stat tmxr_show_sync_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char *desc) +{ +return tmxr_show_sync (st, uptr, val, NULL); +} + +t_stat tmxr_show_sync (FILE* st, UNIT* uptr, int32 val, CONST void *desc) +{ + ETH_LIST list[ETH_MAX_DEVICE]; + int number, fcnt = 0; + + number = eth_devices(ETH_MAX_DEVICE, list, TRUE); + fprintf(st, "DDCMP synchronous link devices:\n"); + if (number == -1) + fprintf(st, " network support not available in simulator\n"); + else + if (number == 0) + fprintf(st, " no dddcmp synchronous link devices are available\n"); + else { + int i; + for (i=0; ilines > 1) { } return stat; } + +static int framer_await_status (TMLN *line, int cnt) +{ +int i, stat, try, flen; +ETH_PACK framer_rpkt; + +i = line->framer->status_cnt; +try = 0; +while (try < 5) { + stat = eth_read (line->framer->eth, &framer_rpkt, NULL); + if (stat) { + flen = framer_rpkt.msg[14] + (framer_rpkt.msg[15] << 8); + if (framer_rpkt.msg[18] == 021) { + /* DC1, so it's a framer status message. Save it. */ + if (flen > sizeof (struct status_msg_t)) + flen = sizeof (struct status_msg_t); + memcpy (&line->framer->status, framer_rpkt.msg + 18, flen); + line->framer->status_cnt++; + continue; + } + } + if (i != line->framer->status_cnt) + return 1; + try++; + sim_os_ms_sleep (50); + } +tmxr_debug_trace_line (line, "no status received\n"); +return 0; +} + +static void tmxr_setup_framer(TMLN *line, ETH_PACK *packet, int len) +{ +/* First clear everything */ +memset (packet, 0, sizeof (*packet)); +/* Set up the MAC header */ +memcpy (&packet->msg[0], line->framer->eth->physical_addr, 6); +memcpy (&packet->msg[6], line->framer->eth->physical_addr, 6); +/* Framer address is one higher than host interface MAC address */ +packet->msg[5]++; +/* Set ethertype 60-06 */ +packet->msg[12] = 0x60; +packet->msg[13] = 0x06; +/* Set length */ +packet->msg[14] = len & 0xff; +packet->msg[15] = len >> 8; +/* Set length and padded length */ +len += 16; /* Add in header length */ +if (len < 60) + len = 60; +packet->len = len; +packet->crc_len = len + 4; +} + +void tmxr_start_framer (TMLN *line, int dmc_mode) +{ +t_stat ret; +int cnt; +ETH_PACK framer_start; + +if (!line->framer) + /* Not a framer line, NOP */ + return; + +tmxr_setup_framer (line, &framer_start, 8); +framer_start.msg[16] = 0x11; +framer_start.msg[17] = 1; /* Command 1: start framer */ +/* Set DMC mode (DDCMP 3.1) if requested */ +if (dmc_mode) + line->framer->fmode |= 32; +else + line->framer->fmode &= ~32; +/* Set mode in the command buffer */ +framer_start.msg[18] = line->framer->fmode & 0xff; +framer_start.msg[19] = line->framer->fmode >> 8; +/* Set speed in the command buffer */ +framer_start.msg[20] = line->framer->fspeed & 0xff; +framer_start.msg[21] = (line->framer->fspeed >> 8) & 0xff; +framer_start.msg[22] = (line->framer->fspeed >> 16) & 0xff; +framer_start.msg[23] = line->framer->fspeed >> 24; +/* Send the request */ +cnt = line->framer->status_cnt; +ret = eth_write (line->framer->eth, &framer_start, NULL); +framer_await_status (line, cnt); +} + +void tmxr_stop_framer (TMLN *line) +{ +t_stat ret; +int cnt; +ETH_PACK framer_stop; + +if (!line->framer) + /* Not a framer line, NOP */ + return; + +tmxr_setup_framer (line, &framer_stop, 2); +framer_stop.msg[16] = 0x11; +framer_stop.msg[17] = 2; /* Command 2: stop framer */ +/* Send the request */ +cnt = line->framer->status_cnt; +ret = eth_write (line->framer->eth, &framer_stop, NULL); +/* Mark the framer off right now */ +line->framer->status.on = 0; +framer_await_status (line, cnt); +} + +static int tmxr_framer_read (TMLN *line, char *buf, int nbytes) +{ +int stat, flen, fstat; +ETH_PACK framer_rpkt; + +while (1) { + stat = eth_read (line->framer->eth, &framer_rpkt, NULL); + if (!stat) + return 0; + /* Size reported by framer includes status, subtract that */ + flen = (framer_rpkt.msg[14] + (framer_rpkt.msg[15] << 8)) - 2; + fstat = framer_rpkt.msg[16] + (framer_rpkt.msg[17] << 8); + if (framer_rpkt.msg[18] == 021) { + /* DC1, so it's a framer status message. Save it. */ + if (flen > sizeof (struct status_msg_t)) + flen = sizeof (struct status_msg_t); + memcpy (&line->framer->status, + framer_rpkt.msg + 18, flen); + /* report interesting bits */ + sim_debug (TMXR_DBG_RCV, line->dptr, + "framer status, on %d, last_cmd_sts %d\n", + line->framer->status.on, + line->framer->status.last_cmd_sts); + line->framer->status_cnt++; + /* Look for another packet */ + continue; + } + else { + /* Real DDCMP packet. Pass the buffer pointer/len */ + if (flen > nbytes) + flen = nbytes; + memcpy (buf, framer_rpkt.msg + 18, flen); + return flen; + } + } +} + +static int tmxr_framer_write (TMLN *line, const char *buf, int32 length) +{ +t_stat ret; +ETH_PACK framer_tx; + +tmxr_setup_framer (line, &framer_tx, length); +memcpy (&framer_tx.msg[16], buf, length); +/* Send the request */ +ret = eth_write (line->framer->eth, &framer_tx, NULL); +/* Always report the whole thing was written */ +return length; +} + diff --git a/sim_tmxr.h b/sim_tmxr.h index b2b6d863..00933e16 100644 --- a/sim_tmxr.h +++ b/sim_tmxr.h @@ -123,7 +123,8 @@ typedef struct SERPORT *SERHANDLE; #define TMLN_SPD_80000_BPS 125 /* usec per character */ #define TMLN_SPD_115200_BPS 86 /* usec per character */ - +/* Internal struct */ +struct framer_data; typedef struct tmln TMLN; typedef struct tmxr TMXR; @@ -206,6 +207,7 @@ struct tmln { DEVICE *dptr; /* line specific device */ EXPECT expect; /* Expect rules */ SEND send; /* Send input state */ + struct framer_data *framer; /* ddcmp framer data */ }; struct tmxr { @@ -297,6 +299,8 @@ t_stat tmxr_show_summ (FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat tmxr_show_cstat (FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat tmxr_show_lines (FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat tmxr_show_open_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char* desc); +t_stat tmxr_show_sync_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char *desc); +t_stat tmxr_show_sync (FILE* st, UNIT* uptr, int32 val, CONST void *desc); t_stat tmxr_flush_log_files (void); t_stat tmxr_activate (UNIT *uptr, int32 interval); t_stat tmxr_activate_abs (UNIT *uptr, int32 interval); @@ -317,6 +321,10 @@ t_stat tmxr_shutdown (void); t_stat tmxr_sock_test (DEVICE *dptr); t_stat tmxr_start_poll (void); t_stat tmxr_stop_poll (void); +/* Framer support. These are a NOP if called on a non-framer line. */ +void tmxr_start_framer (TMLN *line, int dmc_mode); +void tmxr_stop_framer (TMLN *line); + void _tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize); #define tmxr_debug(dbits, lp, msg, buf, bufsize) do {if (sim_deb && (lp)->mp && (lp)->mp->dptr && ((dbits) & (lp)->mp->dptr->dctrl)) _tmxr_debug (dbits, lp, msg, buf, bufsize); } while (0) #define tmxr_debug_msg(dbits, lp, msg) do {if (sim_deb && (lp)->mp && (lp)->mp->dptr && ((dbits) & (lp)->mp->dptr->dctrl)) sim_debug (dbits, (lp)->mp->dptr, "%s", msg); } while (0)