From 02cb620c9b5eadc77fcaf698cf69d0545b6827f6 Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Wed, 17 Oct 2012 08:40:01 -0700 Subject: [PATCH] Change to support serial ports on multiplexer devices without any changes to existing multiplexer device emulation code. Added support for per line tcp listen ports. Added support for per line outgoing tcp/telnet connections. Removed DEV_NET from pdp11_dz and pdp11_vh emulators to allow proper restore of --- HP2100/hp2100_baci.c | 98 +- HP2100/hp2100_defs.h | 10 +- HP2100/hp2100_mpx.c | 195 +-- HP2100/hp2100_mux.c | 95 +- PDP11/pdp11_dc.c | 67 +- PDP11/pdp11_dz.c | 13 +- PDP11/pdp11_vh.c | 6 +- Visual Studio Projects/BuildROMs.vcproj | 2 +- scp.c | 184 ++- scp.h | 13 +- sim_console.c | 380 ++++- sim_console.h | 2 + sim_defs.h | 92 +- sim_serial.c | 329 +++- sim_serial.h | 31 +- sim_sock.c | 13 +- sim_sock.h | 5 + sim_timer.c | 100 +- sim_timer.h | 39 +- sim_tmxr.c | 2021 ++++++++++++++--------- sim_tmxr.h | 70 +- 21 files changed, 2360 insertions(+), 1405 deletions(-) diff --git a/HP2100/hp2100_baci.c b/HP2100/hp2100_baci.c index cbadb7cf..063705a6 100644 --- a/HP2100/hp2100_baci.c +++ b/HP2100/hp2100_baci.c @@ -29,8 +29,6 @@ 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model 25-Nov-08 JDB Revised for new multiplexer library SHOW routines - 19-Nov-08 JDB [serial] Removed DEV_NET to allow restoration of listening port - 17-Oct-08 JDB [serial] Added serial port support 11-Sep-08 JDB Fixed STC,C losing interrupt request on BREAK 07-Sep-08 JDB Fixed IN_LOOPBACK conflict with netinet/in.h Changed Telnet poll to connect immediately after reset or attach @@ -80,13 +78,13 @@ an "external rate" that is equivalent to 9600 baud, as most terminals were set to their maximum speeds. - We support the 12966A connected to an HP terminal emulator via Telnet or a - serial port. Internally, we model the BACI as a terminal multiplexer with - one line. The simulation is complicated by the half-duplex nature of the - card (there is only one FIFO, used selectively either for transmission or - reception) and the double-buffered UART (a Western Digital TR1863A), which - has holding registers as well as a shift registers for transmission and - reception. We model both sets of device registers. + We support the 12966A connected to an HP terminal emulator via Telnet. + Internally, we model the BACI as a terminal multiplexer with one line. The + simulation is complicated by the half-duplex nature of the card (there is + only one FIFO, used selectively either for transmission or reception) and the + double-buffered UART (a Western Digital TR1863A), which has holding registers + as well as a shift registers for transmission and reception. We model both + sets of device registers. During an output operation, the first character output to the card passes through the FIFO and into the transmitter holding register. Subsequent @@ -115,12 +113,12 @@ as an "optimized (fast) timing" option. Optimization makes three improvements: - 1. On output, characters in the FIFO are emptied into the line buffer as a + 1. On output, characters in the FIFO are emptied into the Telnet buffer as a block, rather than one character per service call, and on input, all of - the characters available in the line buffer are loaded into the FIFO as a - block. + the characters available in the Telnet buffer are loaded into the FIFO as + a block. - 2. The ENQ/ACK handshake is done locally, without involving the terminal + 2. The ENQ/ACK handshake is done locally, without involving the Telnet client. 3. Input occurring during an output operation is delayed until the second or @@ -320,7 +318,7 @@ /* Unit references */ #define baci_term baci_unit[0] /* terminal I/O unit */ -#define baci_poll baci_unit[1] /* line polling unit */ +#define baci_poll baci_unit[1] /* Telnet polling unit */ /* BACI state variables */ @@ -393,11 +391,11 @@ t_stat baci_detach (UNIT *uptr); baci_deb BACI debug list baci_dev BACI device descriptor - Two units are used: one to handle character I/O via the multiplexer library, - and another to poll for connections and input. The character I/O service - routine runs only when there are characters to read or write. It operates at - the approximate baud rate of the terminal (in CPU instructions per second) in - order to be compatible with the OS drivers. The line poll must run + Two units are used: one to handle character I/O via the Telnet library, and + another to poll for connections and input. The character I/O service routine + runs only when there are characters to read or write. It operates at the + approximate baud rate of the terminal (in CPU instructions per second) in + order to be compatible with the OS drivers. The Telnet poll must run continuously, but it can operate much more slowly, as the only requirement is that it must not present a perceptible lag to human input. To be compatible with CPU idling, it is co-scheduled with the master poll timer, which uses a @@ -406,14 +404,14 @@ t_stat baci_detach (UNIT *uptr); DEVICE baci_dev; -TMLN baci_ldsc = { 0 }; /* line descriptor */ -TMXR baci_desc = { 1, 0, 0, &baci_ldsc, NULL, &baci_dev }; /* device descriptor */ +TMLN baci_ldsc = { 0 }; /* line descriptor */ +TMXR baci_desc = { 1, 0, 0, &baci_ldsc }; /* device descriptor */ DIB baci_dib = { &baci_io, BACI, 0 }; UNIT baci_unit[] = { { UDATA (&baci_term_svc, UNIT_ATTABLE | UNIT_FASTTIME, 0) }, /* terminal I/O unit */ - { UDATA (&baci_poll_svc, UNIT_DIS, POLL_FIRST) } /* line poll unit */ + { UDATA (&baci_poll_svc, UNIT_DIS, POLL_FIRST) } /* Telnet poll unit */ }; REG baci_reg[] = { @@ -771,7 +769,7 @@ return stat_data; The terminal service routine is used to transmit and receive characters. In terminal mode, it is started when a character is ready for output or when - the line poll routine determines that there are characters ready for input + the Telnet poll routine determines that there are characters ready for input and stopped when there are no more characters to output or input. When the terminal is quiescent, this routine does not run. @@ -811,11 +809,11 @@ return stat_data; first character after an ENQ is not an ACK. Finally, fast timing enables buffer combining. For output, all characters - present in the FIFO are unloaded into the line buffer before initiating a - packet send. For input, all characters present in the line buffer are loaded - into the FIFO. This reduces network traffic and decreases simulator overhead - (there is only one service routine entry per block, rather than one per - character). + present in the FIFO are unloaded into the Telnet buffer before initiating a + packet send. For input, all characters present in the Telnet buffer are + loaded into the FIFO. This reduces network traffic and decreases simulator + overhead (there is only one service routine entry per block, rather than one + per character). In fast output mode, it is imperative that not less than 1500 instructions elapse between the first character load to the FIFO and the initiation of @@ -978,11 +976,12 @@ return status; } -/* BACI line poll service. +/* BACI Telnet poll service. - This service routine is used to poll for connections and incoming characters. - If characters are available, the terminal I/O service routine is scheduled. - It starts when the line is attached and stops when the line is detached. + This service routine is used to poll for Telnet connections and incoming + characters. If characters are available, the terminal I/O service routine is + scheduled. It starts when the socket is attached and stops when the socket + is detached. As there is only one line, we only poll for a new connection when the line is disconnected. @@ -1029,57 +1028,42 @@ baci_term.wait = service_time (baci_icw); /* set terminal I/O time if (baci_term.flags & UNIT_ATT) { /* device attached? */ baci_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&baci_poll, baci_poll.wait); /* start line poll immediately */ + sim_activate (&baci_poll, baci_poll.wait); /* start Telnet poll immediately */ } else - sim_cancel (&baci_poll); /* else stop line poll */ + sim_cancel (&baci_poll); /* else stop Telnet poll */ return SCPE_OK; } -/* Attach line */ +/* Attach controller */ t_stat baci_attach (UNIT *uptr, char *cptr) { t_stat status = SCPE_OK; -if (uptr->flags & UNIT_DIS) /* unit disabled? */ - return SCPE_UDIS; /* report it */ +status = tmxr_attach (&baci_desc, uptr, cptr); /* attach to socket */ -status = tmxr_attach (&baci_desc, uptr, cptr); /* try to attach to socket */ - -if (status == SCPE_ARG) /* invalid numeric port supplied? */ - status = tmxr_attach_line (uptr, 0, cptr, &baci_desc); /* try to attach to serial port */ - -if (status == SCPE_OK) { /* attach successful? */ - baci_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&baci_poll, baci_poll.wait); /* start line poll immediately */ +if (status == SCPE_OK) { + baci_poll.wait = POLL_FIRST; /* set up poll */ + sim_activate (&baci_poll, baci_poll.wait); /* start Telnet poll immediately */ } return status; } - -/* Detach line */ +/* Detach controller */ t_stat baci_detach (UNIT *uptr) { t_stat status; -if (uptr->flags & UNIT_DIS) /* unit disabled? */ - return SCPE_UDIS; /* report it */ - -status = tmxr_detach_line (uptr, 0, NULL, &baci_desc); /* attempt to detach serial line */ - -if (status == SCPE_UNATT) /* not attached to serial? */ - status = tmxr_detach (&baci_desc, uptr); /* attempt to detach listening socket */ - +status = tmxr_detach (&baci_desc, uptr); /* detach socket */ baci_ldsc.rcve = 0; /* disable line reception */ -sim_cancel (&baci_poll); /* stop line poll */ +sim_cancel (&baci_poll); /* stop Telnet poll */ return status; } - /* Local routines */ diff --git a/HP2100/hp2100_defs.h b/HP2100/hp2100_defs.h index 89b9a408..9ee83e32 100644 --- a/HP2100/hp2100_defs.h +++ b/HP2100/hp2100_defs.h @@ -32,7 +32,6 @@ 27-Oct-10 JDB Revised I/O signal enum values for concurrent signals Revised I/O macros for new signal handling 09-Oct-10 JDB Added DA and DC device select code assignments - 21-Oct-08 JDB [serial] Added "sim_unit_ref" external 07-Sep-08 JDB Added POLL_FIRST to indicate immediate connection attempt 15-Jul-08 JDB Rearranged declarations with hp2100_cpu.h 26-Jun-08 JDB Rewrote device I/O to model backplane signals @@ -457,11 +456,10 @@ extern uint32 dev_prl [2], dev_irq [2], dev_srq [2]; /* I/O signal vectors */ /* Simulator state */ -extern FILE *sim_deb; -extern FILE *sim_log; -extern int32 sim_step; -extern int32 sim_switches; -extern UNITREF sim_unit_ref; +extern FILE *sim_deb; +extern FILE *sim_log; +extern int32 sim_step; +extern int32 sim_switches; /* CPU functions */ diff --git a/HP2100/hp2100_mpx.c b/HP2100/hp2100_mpx.c index 7e4065a4..a4d91dd9 100644 --- a/HP2100/hp2100_mpx.c +++ b/HP2100/hp2100_mpx.c @@ -29,9 +29,7 @@ 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model 25-Nov-08 JDB Revised for new multiplexer library SHOW routines - 19-Nov-08 JDB [serial] Removed DEV_NET to allow restoration of listening port 14-Nov-08 JDB Cleaned up VC++ size mismatch warnings for zero assignments - 20-Oct-08 JDB [serial] Added serial port support 03-Oct-08 JDB Fixed logic for ENQ/XOFF transmit wait 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size @@ -59,8 +57,8 @@ character editing, echoing, ENQ/ACK handshaking, and read terminator detection, substantially reducing the load on the CPU over the earlier 12920 multiplexer. It was supported by HP under RTE-MIII, RTE-IVB, and RTE-6/VM. - Under simulation, it connects with HP terminal emulators via Telnet or serial - ports. + Under simulation, it connects with HP terminal emulators via Telnet to a + user-specified port. The single interface card contained a Z80 CPU, DMA controller, CTC, four two-channel SIO UARTs, 16K of RAM, 8K of ROM, and I/O backplane latches and @@ -201,7 +199,7 @@ #define MPX_CNTLS 2 /* number of control units */ #define mpx_cntl (mpx_unit [MPX_PORTS + 0]) /* controller unit */ -#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* polling unit */ +#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* Telnet polling unit */ /* Character constants */ @@ -613,16 +611,16 @@ t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc); mpx_dev MPX device descriptor The first eight units correspond to the eight multiplexer line ports. These - handle character I/O via the multiplexer library. A ninth unit acts as the - card controller, executing commands and transferring data to and from the I/O - buffers. A tenth unit is responsible for polling for connections and line - I/O. It also holds the master socket for Telnet connections. + handle character I/O via the Telnet library. A ninth unit acts as the card + controller, executing commands and transferring data to and from the I/O + buffers. A tenth unit is responsible for polling for connections and socket + I/O. It also holds the master socket. The character I/O service routines run only when there are characters to read or write. They operate at the approximate baud rates of the terminals (in CPU instructions per second) in order to be compatible with the OS drivers. The controller service routine runs only when a command is executing or a - data transfer to or from the CPU is in progress. The poll service must run + data transfer to or from the CPU is in progress. The Telnet poll must run continuously, but it may operate much more slowly, as the only requirement is that it must not present a perceptible lag to human input. To be compatible with CPU idling, it is co-scheduled with the master poll timer, which uses a @@ -634,9 +632,9 @@ t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc); DEVICE mpx_dev; -int32 mpx_order [MPX_PORTS] = { -1 }; /* connection order */ -TMLN mpx_ldsc [MPX_PORTS] = { { 0 } }; /* line descriptors */ -TMXR mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order, &mpx_dev }; /* device descriptor */ +int32 mpx_order [MPX_PORTS] = { -1 }; /* connection order */ +TMLN mpx_ldsc [MPX_PORTS] = { { 0 } }; /* line descriptors */ +TMXR mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order }; /* device descriptor */ DIB mpx_dib = { &mpx_io, MPX }; @@ -650,7 +648,7 @@ UNIT mpx_unit [] = { { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 6 */ { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 7 */ { UDATA (&mpx_cntl_svc, UNIT_DIS, 0) }, /* controller unit */ - { UDATA (&mpx_poll_svc, UNIT_ATTABLE | UNIT_DIS, POLL_FIRST) } /* line poll unit */ + { UDATA (&mpx_poll_svc, UNIT_ATTABLE | UNIT_DIS, POLL_FIRST) } /* Telnet poll unit */ }; REG mpx_reg [] = { @@ -1604,20 +1602,21 @@ return SCPE_OK; /* Multiplexer line service. The line service routine is used to transmit and receive characters. It is - started when a buffer is ready for output or when the poll service routine + started when a buffer is ready for output or when the Telnet poll routine determines that there are characters ready for input, and it is stopped when there are no more characters to output or input. When a line is quiescent, this routine does not run. Service times are selected to approximate the baud rate setting of the multiplexer port. "Fast timing" mode enables three optimizations. First, buffered characters - are transferred in blocks, rather than a character at a time; this reduces - line traffic and decreases simulator overhead (there is only one service - routine entry per block, rather than one per character). Second, ENQ/ACK - handshaking is done locally, without involving the client. Third, when - editing and echo is enabled, entering BS echoes a backspace, a space, and a - backspace, and entering DEL echoes a backslash, a carriage return, and a line - feed, providing better compatibility with prior RTE terminal drivers. + are transferred via Telnet in blocks, rather than a character at a time; this + reduces network traffic and decreases simulator overhead (there is only one + service routine entry per block, rather than one per character). Second, + ENQ/ACK handshaking is done locally, without involving the Telnet client. + Third, when editing and echo is enabled, entering BS echoes a backspace, a + space, and a backspace, and entering DEL echoes a backslash, a carriage + return, and a line feed, providing better compatibility with prior RTE + terminal drivers. Each read and write buffer begins with a reserved header byte that stores per-buffer information, such as whether handshaking should be suppressed @@ -1631,7 +1630,7 @@ return SCPE_OK; write buffer is freed, and a UI check is made if the controller is idle, in case a write buffer request is pending. - For input, the character is retrieved from the line buffer. If a BREAK was + For input, the character is retrieved from the Telnet buffer. If a BREAK was received, break status is set, and the character is discarded (the current multiplexer library implementation always returns a NUL with a BREAK indication). If the character is an XOFF, and XON/XOFF pacing is enabled, a @@ -1940,11 +1939,11 @@ return SCPE_OK; } -/* Poll service. +/* Telnet poll service. - This service routine is used to poll for connections and incoming characters. - It is started when the listening socket or a serial line is attached and is - stopped when the socket and all lines are detached. + This service routine is used to poll for Telnet connections and incoming + characters. It starts when the socket is attached and stops when the socket + is detached. Each line is then checked for a pending ENQ/ACK handshake. If one is pending, the ACK counter is incremented, and if it times out, another ENQ is @@ -2008,10 +2007,10 @@ return SCPE_OK; 1. Under simulation, we also clear the input buffer register, even though the hardware doesn't. - 2. We set up the first poll for connections to occur "immediately" upon - execution, so that clients will be connected before execution begins. - Otherwise, a fast program may access the multiplexer before the poll - service routine activates. + 2. We set up the first poll for Telnet connections to occur "immediately" + upon execution, so that clients will be connected before execution + begins. Otherwise, a fast program may access the multiplexer before the + poll service routine activates. 3. We must set the "emptying_flags" and "filling_flags" values here, because they cannot be initialized statically, even though the values are @@ -2031,129 +2030,83 @@ IOPRESET (&mpx_dib); /* PRESET device (does n mpx_ibuf = 0; /* clear input buffer */ -if (tmxr_mux_free (&mpx_desc)) /* any lines attached? */ - sim_cancel (&mpx_poll); /* no, so stop poll */ -else { /* attached or listening */ +if (mpx_poll.flags & UNIT_ATT) { /* network attached? */ mpx_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ + sim_activate (&mpx_poll, mpx_poll.wait); /* start Telnet poll immediately */ } +else + sim_cancel (&mpx_poll); /* else stop Telnet poll */ return SCPE_OK; } -/* Attach the multiplexer or a line. +/* Attach the multiplexer to a Telnet port. We are called by the ATTACH MPX command to attach the multiplexer to - the listening port indicated by and by ATTACH MPX to attach - line to serial port . Logically, it is the multiplexer device that - is attached; however, SIMH only allows units to be attached. This makes - sense for devices such as tape drives, where the attached media is a property - of a specific drive. In our case, though, the listening port is a property - of the multiplexer card, not of any given serial line. + the listening port indicated by . Logically, it is the multiplexer + device that is attached; however, SIMH only allows units to be attached. + This makes sense for devices such as tape drives, where the attached media is + a property of a specific drive. In our case, though, the listening port is a + property of the multiplexer card, not of any given serial line. As ATTACH + MPX is equivalent to ATTACH MPX0, the port would, by default, be attached to + the first serial line and be reported there in a SHOW MPX command. - To preserve the logical picture, we attach the listening port to the poll - unit (unit 9), which is normally disabled to inhibit its display. Serial - ports are attached to line units 0-7 normally. Attachment is reported by the - "mpx_status" routine below. + To preserve the logical picture, we attach the port to the Telnet poll unit, + which is normally disabled to inhibit its display. Attaching to a disabled + unit is not allowed, so we first enable the unit, then attach it, then + disable it again. Attachment is reported by the "mpx_status" routine below. - The connection poll service routine is synchronized with the other input - polling devices in the simulator to facilitate idling. - - Implementation notes: - - 1. ATTACH MPX will pass a pointer unit 0. This is because the common - simulator code treats ATTACH MPX as equivalent to ATTACH MPX0. We - differentiate these cases by examining the "sim_unit_ref" global to see - if a device was referenced. - - 2. Directly attempting to attach to units 8 (controller) or 9 (poll) will be - rejected. - - 3. If we are being called as part of RESTORE processing, we may see a - request to attach the poll unit (unit 9). This will occur if unit 9 was - attached when the SAVE was done. In this case, the SIM_SW_REST flag will - be set in "sim_switches", and we will allow the call to succeed. - - 4. If the poll unit is attached, it will be enabled as part of RESTORE - processing. We always unilaterally disable this unit to ensure that it - remains hidden. + The Telnet poll service routine is synchronized with the other input polling + devices in the simulator to facilitate idling. */ t_stat mpx_attach (UNIT *uptr, char *cptr) { t_stat status = SCPE_OK; -if ((uptr == &mpx_cntl) || /* attaching controller? */ - (uptr == &mpx_poll) && !(sim_switches & SIM_SW_REST)) /* or poll unit directly? */ - return SCPE_NOATT; /* disallow */ +if (uptr != mpx_unit) /* not unit 0? */ + return SCPE_NOATT; /* can't attach */ -if (sim_unit_ref == ref_dev || (uptr == &mpx_poll)) { /* device attach or poll restore request? */ - status = tmxr_attach (&mpx_desc, &mpx_poll, cptr); /* attach to socket */ - mpx_poll.flags = mpx_poll.flags | UNIT_DIS; /* disable unit */ - } - -else /* line attach request */ - status = tmxr_attach_line (uptr, 0, cptr, &mpx_desc); /* attach line */ +mpx_poll.flags = mpx_poll.flags & ~UNIT_DIS; /* enable unit */ +status = tmxr_attach (&mpx_desc, &mpx_poll, cptr); /* attach to socket */ +mpx_poll.flags = mpx_poll.flags | UNIT_DIS; /* disable unit */ if (status == SCPE_OK) { - mpx_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ + mpx_poll.wait = POLL_FIRST; /* set up poll */ + sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ } return status; } -/* Detach the multiplexer or a line. +/* Detach the multiplexer. - We are called by the DETACH MPX command to detach the listening port and all - Telnet sessions and by the DETACH MPX to detach a serial port from line - . We will also be called by DETACH ALL, RESTORE, and during simulator - shutdown. For DETACH ALL and RESTORE, we must not fail the call, or - processing of other units will cease. - - Implementation notes: + Normally, we are called by the DETACH MPX command, which is equivalent to + DETACH MPX0. However, we may be called with other units in two cases. - 1. Because DETACH MPX will pass unit 0, we check the "sim_unit_ref" global - to see if MPX or MPX0 was specified in the command. - - 2. Directly attempting to detach unit 8 (controller) will be rejected. We - cannot fail a direct DETACH MPX9 (poll unit), because we cannot tell that - case apart from a DETACH ALL (a RESTORE will have the SIM_SW_REST flag - set in "sim_switches"). - - 3. During simulator shutdown, we will be called for units 0-8 (detach_all in - scp.c calls the detach routines of all units that do NOT have - UNIT_ATTABLE), as well as for unit 9 if it is attached. + A DETACH ALL command will call us for unit 9 (the poll unit) if it is + attached. Also, during simulator shutdown, we will be called for units 0-8 + (detach_all in scp.c calls the detach routines of all units that do NOT have + UNIT_ATTABLE), as well as for unit 9 if it is attached. In both cases, it is + imperative that we return SCPE_OK, otherwise any remaining device detaches + will not be performed. */ t_stat mpx_detach (UNIT *uptr) { -uint32 ln; -t_stat status; -t_bool mux_free = TRUE; +t_stat status = SCPE_OK; +int32 i; -if (uptr == &mpx_cntl) /* detaching controller directly? */ - return SCPE_NOATT; /* disallow */ +if ((uptr == mpx_unit) || (uptr == &mpx_poll)) { /* base unit or poll unit? */ + status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */ -if (sim_unit_ref == ref_dev || uptr == &mpx_poll) /* device detach or detach all request? */ - status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */ + for (i = 0; i < MPX_PORTS; i++) { + mpx_ldsc [i].rcve = 0; /* disable line reception */ + sim_cancel (&mpx_unit [i]); /* cancel any scheduled I/O */ + } -else /* line detach request */ - status = tmxr_detach_line (uptr, 0, NULL, &mpx_desc); /* detach line */ - -if (status == SCPE_OK) { - for (ln = 0; ln < MPX_PORTS; ln++) /* loop through lines */ - if (tmxr_line_free (&mpx_ldsc[ln])) { /* is line free? */ - mpx_ldsc[ln].rcve = 0; /* disable rcv as line was reset */ - sim_cancel (&mpx_unit [ln]); /* cancel any scheduled I/O */ - } - - else - mux_free = FALSE; /* mux isn't free if line is in use */ - - if (mux_free && !(mpx_poll.flags & UNIT_ATT)) /* all lines free and not listening? */ - sim_cancel (&mpx_poll); /* stop poll */ + sim_cancel (&mpx_poll); /* stop Telnet poll */ } return status; @@ -2214,7 +2167,7 @@ return SCPE_OK; /* Local routines */ -/* Poll for new connections */ +/* Poll for new Telnet connections */ static void poll_connection (void) { diff --git a/HP2100/hp2100_mux.c b/HP2100/hp2100_mux.c index 354ce3d9..f341a51d 100644 --- a/HP2100/hp2100_mux.c +++ b/HP2100/hp2100_mux.c @@ -29,8 +29,6 @@ 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model 25-Nov-08 JDB Revised for new multiplexer library SHOW routines - 19-Nov-08 JDB [serial] Removed DEV_NET to allow restoration of listening port - 20-Oct-08 JDB [serial] Added serial port support 09-Oct-08 JDB "muxl_unit" defined one too many units (17 instead of 16) 10-Sep-08 JDB SHOW MUX CONN/STAT with SET MUX DIAG is no longer disallowed 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach @@ -329,7 +327,6 @@ t_stat muxo_svc (UNIT *uptr); t_stat muxc_reset (DEVICE *dptr); t_stat mux_attach (UNIT *uptr, char *cptr); t_stat mux_detach (UNIT *uptr); -t_stat muxl_detach (UNIT *uptr); t_stat mux_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc); @@ -433,8 +430,8 @@ DEVICE muxl_dev = { NULL, /* deposit routine */ &muxc_reset, /* reset routine */ NULL, /* boot routine */ - &mux_attach, /* attach routine */ - &muxl_detach, /* detach routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ &muxl_dib, /* device information block */ DEV_DISABLE, /* device flags */ 0, /* debug control flags */ @@ -459,9 +456,9 @@ DEVICE muxl_dev = { DEVICE muxu_dev; -int32 mux_order [MUX_LINES] = { -1 }; /* connection order */ -TMLN mux_ldsc [MUX_LINES] = { { 0 } }; /* line descriptors */ -TMXR mux_desc = { MUX_LINES, 0, 0, mux_ldsc, mux_order, &muxu_dev }; /* device descriptor */ +int32 mux_order [MUX_LINES] = { -1 }; /* connection order */ +TMLN mux_ldsc [MUX_LINES] = { { 0 } }; /* line descriptors */ +TMXR mux_desc = { MUX_LINES, 0, 0, mux_ldsc, mux_order }; /* device descriptor */ UNIT muxu_unit = { UDATA (&muxi_svc, UNIT_ATTABLE, 0), POLL_FIRST }; @@ -923,7 +920,7 @@ while (working_set) { (old & DTR) && /* DTR drop? */ !(muxc_ota[ln] & DTR)) { tmxr_linemsg (&mux_ldsc[ln], "\r\nLine hangup\r\n"); - tmxr_clear_ln (&mux_desc, &mux_ldsc[ln]); /* disconnect line */ + tmxr_reset_ln (&mux_ldsc[ln]); /* reset line */ muxc_lia[ln] = 0; /* dataset off */ } } /* end update */ @@ -1308,12 +1305,12 @@ IOPRESET (dibptr); /* PRESET device (does n muxc_chan = muxc_scan = 0; /* init modem scan */ -if (tmxr_mux_free (&mux_desc)) /* any lines attached? */ - sim_cancel (&muxu_unit); /* no, so stop poll */ -else { /* attached or listening */ +if (muxu_unit.flags & UNIT_ATT) { /* master att? */ muxu_unit.wait = POLL_FIRST; /* set up poll */ - sim_activate (&muxu_unit, muxu_unit.wait); /* start poll immediately */ + sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ } +else + sim_cancel (&muxu_unit); /* else stop */ for (i = 0; i < MUX_LINES; i++) mux_reset_ln (i); /* reset lines 0-15 */ @@ -1325,23 +1322,20 @@ return SCPE_OK; } -/* Attach master unit or line */ +/* Attach master unit */ t_stat mux_attach (UNIT *uptr, char *cptr) { t_stat status = SCPE_OK; -if (muxu_unit.flags & UNIT_DIAG) /* diag mode? */ - return SCPE_NOFNC; /* command not allowed */ +if (muxu_unit.flags & UNIT_DIAG) /* diag mode? */ + return SCPE_NOFNC; /* command not allowed */ -if (uptr == &muxu_unit) /* master unit? */ - status = tmxr_attach (&mux_desc, uptr, cptr); /* attach socket */ -else - status = tmxr_attach_line (uptr, 0, cptr, &mux_desc); /* attach line */ +status = tmxr_attach (&mux_desc, uptr, cptr); /* attach */ -if (status == SCPE_OK) { /* attach successful? */ - muxu_unit.wait = POLL_FIRST; /* set up poll */ - sim_activate (&muxu_unit, muxu_unit.wait); /* start poll immediately */ +if (status == SCPE_OK) { + muxu_unit.wait = POLL_FIRST; /* set up poll */ + sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ } return status; @@ -1352,45 +1346,13 @@ return status; t_stat mux_detach (UNIT *uptr) { -uint32 ln; -t_stat status; -t_bool free = TRUE; +int32 i; +t_stat r; -status = tmxr_detach (&mux_desc, uptr); /* detach unit */ - -if (status == SCPE_OK) { - for (ln = 0; ln < MUX_LINES; ln++) /* loop through lines */ - if (tmxr_line_free (&mux_ldsc[ln])) /* is line free? */ - mux_ldsc[ln].rcve = 0; /* yes, so disable rcv as line was reset */ - else - free = FALSE; /* mux isn't free if line is in use */ - - if (free) /* all lines free? */ - sim_cancel (uptr); /* stop poll */ - } - -return status; -} - - -/* Detach line */ - -t_stat muxl_detach (UNIT *uptr) -{ -uint32 ln; -t_stat status; - -status = tmxr_detach_line (uptr, 0, NULL, &mux_desc); /* detach line */ - -if (status == SCPE_OK) { - ln = uptr - muxl_unit; /* determine line number */ - mux_ldsc[ln].rcve = 0; /* disable line reception */ - - if (tmxr_mux_free (&mux_desc)) /* all lines free and not listening? */ - sim_cancel (&muxu_unit); /* stop poll */ - } - -return status; +r = tmxr_detach (&mux_desc, uptr); /* detach */ +for (i = 0; i < MUX_LINES; i++) mux_ldsc[i].rcve = 0; /* disable rcv */ +sim_cancel (uptr); /* stop poll */ +return r; } @@ -1405,7 +1367,7 @@ return status; for normal character transfers, which is undesirable. Therefore, to enable diagnostic mode, we must force a disconnect of the - master socket and all Telnet and serial lines, which clears the connection + master socket and any connected Telnet lines, which clears the connection flags on all lines. Then we set the "transmission enabled" flags on all lines to enable output character processing for the diagnostic. (Normally, all of the flags are set when the multiplexer is first attached. Until then, @@ -1418,12 +1380,9 @@ t_stat mux_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc) int32 ln; if (val) { /* set diag? */ - mux_detach (uptr); /* detach Telnet lines */ - - for (ln = 0; ln < MUX_LINES; ln++) { - muxl_detach (&muxl_unit[ln]); /* detach all serial lines */ - mux_ldsc[ln].xmte = 1; /* enable transmission on all lines */ - } + mux_detach (uptr); /* detach lines */ + for (ln = 0; ln < MUX_LINES; ln++) /* enable transmission */ + mux_ldsc[ln].xmte = 1; /* on all lines */ } else { /* set term */ for (ln = 0; ln < MUX_LINES; ln++) /* clear connections */ diff --git a/PDP11/pdp11_dc.c b/PDP11/pdp11_dc.c index 38f7eacc..1c653fe8 100644 --- a/PDP11/pdp11_dc.c +++ b/PDP11/pdp11_dc.c @@ -27,7 +27,6 @@ 18-Apr-2012 RMS Modified to use clock coscheduling 17-Aug-2011 RMS Added AUTOCONFIGURE modifier - 26-Nov-2008 JDB [serial] Added serial port support 19-Nov-2008 RMS Revised for common TMXR show routines Revised to autoconfigure vectors @@ -132,7 +131,6 @@ t_stat dci_svc (UNIT *uptr); t_stat dco_svc (UNIT *uptr); t_stat dcx_attach (UNIT *uptr, char *cptr); t_stat dcx_detach (UNIT *uptr); -t_stat dcl_detach (UNIT *uptr); t_stat dcx_set_lines (UNIT *uptr, int32 val, char *cptr, void *desc); void dcx_enbdis (int32 dis); void dci_clr_int (int32 ln); @@ -254,7 +252,7 @@ DEVICE dco_dev = { "DCO", dco_unit, dco_reg, dco_mod, DCX_LINES, 10, 31, 1, 8, 8, NULL, NULL, &dcx_reset, - NULL, &dcx_attach, &dcl_detach, + NULL, NULL, NULL, NULL, DEV_UBUS | DEV_DISABLE | DEV_DIS }; @@ -368,6 +366,8 @@ t_stat dci_svc (UNIT *uptr) { int32 ln, c, temp; +if ((uptr->flags & UNIT_ATT) == 0) /* attached? */ + return SCPE_OK; sim_activate (uptr, clk_cosched (tmxr_poll)); /* continue poll */ ln = tmxr_poll_conn (&dcx_desc); /* look for connect */ if (ln >= 0) { /* got one? */ @@ -510,12 +510,9 @@ t_stat dcx_reset (DEVICE *dptr) int32 ln; dcx_enbdis (dptr->flags & DEV_DIS); /* sync enables */ -// -if (tmxr_mux_free (&dcx_desc)) /* any lines attached? */ - sim_cancel (&dci_unit); /* no, so stop poll */ -else /* attached or listening */ - sim_activate (&dci_unit, tmxr_poll); /* start poll immediately */ -// +sim_cancel (&dci_unit); /* assume stop */ +if (dci_unit.flags & UNIT_ATT) /* if attached, */ + sim_activate (&dci_unit, tmxr_poll); /* activate */ for (ln = 0; ln < DCX_LINES; ln++) /* for all lines */ dcx_reset_ln (ln); return auto_config (dci_dev.name, dcx_desc.lines); /* auto config */ @@ -535,22 +532,16 @@ dco_clr_int (ln); return; } -/* Attach master unit or line */ +/* Attach master unit */ t_stat dcx_attach (UNIT *uptr, char *cptr) { t_stat r; -// -if (uptr == &dci_unit) /* master unit? */ - r = tmxr_attach (&dcx_desc, uptr, cptr); /* attach socket */ -else - r = tmxr_attach_line (uptr, 0, cptr, &dcx_desc); /* attach line */ -// - +r = tmxr_attach (&dcx_desc, uptr, cptr); /* attach */ if (r != SCPE_OK) /* error? */ return r; -sim_activate (&dci_unit, tmxr_poll); /* start poll */ +sim_activate (uptr, tmxr_poll); /* start poll */ return SCPE_OK; } @@ -560,48 +551,14 @@ t_stat dcx_detach (UNIT *uptr) { int32 i; t_stat r; -t_bool free = TRUE; r = tmxr_detach (&dcx_desc, uptr); /* detach */ - -// -if (r == SCPE_OK) { - for (i = 0; i < DCX_LINES; i++) /* loop through lines */ - if (tmxr_line_free (&dcx_ldsc[i])) /* is line free? */ - dcx_ldsc[i].rcve = 0; /* yes, so disable rcv as line was reset */ - else - free = FALSE; /* mux isn't free if line is in use */ - - if (free) /* all lines free? */ - sim_cancel (uptr); /* stop poll */ - } -// - +for (i = 0; i < DCX_LINES; i++) /* all lines, */ + dcx_ldsc[i].rcve = 0; /* disable rcv */ +sim_cancel (uptr); /* stop poll */ return r; } -/* Detach line */ - -// -t_stat dcl_detach (UNIT *uptr) -{ -uint32 ln; -t_stat status; - -status = tmxr_detach_line (uptr, 0, NULL, &dcx_desc); /* detach line */ - -if (status == SCPE_OK) { - ln = uptr - dco_unit; /* determine line number */ - dcx_ldsc[ln].rcve = 0; /* disable line reception */ - - if (tmxr_mux_free (&dcx_desc)) /* all lines free and not listening? */ - sim_cancel (&dci_unit); /* stop poll */ - } - -return status; -} -// - /* Enable/disable device */ void dcx_enbdis (int32 dis) diff --git a/PDP11/pdp11_dz.c b/PDP11/pdp11_dz.c index f2b14598..3067e18c 100644 --- a/PDP11/pdp11_dz.c +++ b/PDP11/pdp11_dz.c @@ -26,7 +26,6 @@ dz DZ11 terminal multiplexor 29-Dec-08 RMS Added MTAB_NC to SET LOG command (Walter Mueller) - 24-Nov-08 JDB [serial] Added serial port support 19-Nov-08 RMS Revised for common TMXR show routines 18-Jun-07 RMS Added UNIT_IDLE flag 29-Oct-06 RMS Synced poll and clock @@ -166,12 +165,16 @@ TMXR dz_desc = { DZ_MUXES * DZ_LINES, 0, 0, dz_ldsc }; /* mux descriptor */ #define DBG_INT 0x0002 /* display transfer requests */ #define DBG_XMT TMXR_DBG_XMT /* display Transmitted Data */ #define DBG_RCV TMXR_DBG_RCV /* display Received Data */ +#define DBG_TRC TMXR_DBG_TRC /* display trace routine calls */ +#define DBG_ASY TMXR_DBG_ASY /* display Asynchronous Activities */ DEBTAB dz_debug[] = { {"REG", DBG_REG}, {"INT", DBG_INT}, {"XMT", DBG_XMT}, {"RCV", DBG_RCV}, + {"TRC", DBG_TRC}, + {"ASY", DBG_ASY}, {0} }; @@ -232,12 +235,8 @@ MTAB dz_mod[] = { { TT_MODE, TT_MODE_7B, "7b", "7B", NULL }, { TT_MODE, TT_MODE_8B, "8b", "8B", NULL }, { TT_MODE, TT_MODE_7P, "7p", "7P", NULL }, -// - { MTAB_XTD | MTAB_VDV | MTAB_NC, ':', NULL, "CONNECT", - &tmxr_attach_line, NULL, &dz_desc }, { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", - &tmxr_detach_line, NULL, &dz_desc }, -// + &tmxr_dscln, NULL, &dz_desc }, { UNIT_ATT, UNIT_ATT, "summary", NULL, NULL, &tmxr_show_summ, (void *) &dz_desc }, { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, @@ -268,7 +267,7 @@ DEVICE dz_dev = { 1, DEV_RDX, 8, 1, DEV_RDX, 8, &tmxr_ex, &tmxr_dep, &dz_reset, NULL, &dz_attach, &dz_detach, - &dz_dib, DEV_FLTA | DEV_DISABLE | DEV_NET | DEV_UBUS | DEV_QBUS | DEV_DEBUG, + &dz_dib, DEV_FLTA | DEV_DISABLE | DEV_UBUS | DEV_QBUS | DEV_DEBUG, 0, dz_debug }; diff --git a/PDP11/pdp11_vh.c b/PDP11/pdp11_vh.c index 903f2725..414ad8d0 100644 --- a/PDP11/pdp11_vh.c +++ b/PDP11/pdp11_vh.c @@ -296,12 +296,16 @@ static TMLX vh_parm[VH_MUXES * VH_LINES] = { { 0 } }; #define DBG_INT 0x0002 /* display transfer requests */ #define DBG_XMT TMXR_DBG_XMT /* display Transmitted Data */ #define DBG_RCV TMXR_DBG_RCV /* display Received Data */ +#define DBG_TRC TMXR_DBG_TRC /* display trace routine calls */ +#define DBG_ASY TMXR_DBG_ASY /* display Asynchronous Activities */ DEBTAB vh_debug[] = { {"REG", DBG_REG}, {"INT", DBG_INT}, {"XMT", DBG_XMT}, {"RCV", DBG_RCV}, + {"TRC", DBG_TRC}, + {"ASY", DBG_ASY}, {0} }; @@ -407,7 +411,7 @@ DEVICE vh_dev = { &vh_attach, /* attach routine */ &vh_detach, /* detach routine */ (void *)&vh_dib,/* context */ - DEV_FLTA | DEV_DISABLE | DEV_DIS |DEV_NET | DEV_QBUS | DEV_UBUS | DEV_DEBUG, /* flags */ + DEV_FLTA | DEV_DISABLE | DEV_DIS | DEV_QBUS | DEV_UBUS | DEV_DEBUG, /* flags */ 0, vh_debug }; diff --git a/Visual Studio Projects/BuildROMs.vcproj b/Visual Studio Projects/BuildROMs.vcproj index 570688c6..684766a5 100644 --- a/Visual Studio Projects/BuildROMs.vcproj +++ b/Visual Studio Projects/BuildROMs.vcproj @@ -50,7 +50,7 @@ UsePrecompiledHeader="0" WarningLevel="3" Detect64BitPortabilityProblems="false" - DebugInformationFormat="4" + DebugInformationFormat="3" /> next) { +for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) { if (uptr == &sim_step_unit) fprintf (st, " Step timer"); else if ((dptr = find_dev_from_unit (uptr)) != NULL) { @@ -2309,10 +2315,10 @@ for (uptr = sim_clock_queue; uptr != NULL; uptr = uptr->next) { #if defined (SIM_ASYNCH_IO) pthread_mutex_lock (&sim_asynch_lock); fprintf (st, "asynchronous pending event queue\n"); -if (sim_asynch_queue == AIO_LIST_END) +if (sim_asynch_queue == QUEUE_LIST_END) fprintf (st, "Empty\n"); else { - for (uptr = sim_asynch_queue; uptr != AIO_LIST_END; uptr = uptr->a_next) { + for (uptr = sim_asynch_queue; uptr != QUEUE_LIST_END; uptr = uptr->a_next) { if ((dptr = find_dev_from_unit (uptr)) != NULL) { fprintf (st, " %s", sim_dname (dptr)); if (dptr->numunits > 1) fprintf (st, " unit %d", @@ -2418,6 +2424,8 @@ if (cptr && (*cptr != 0)) /* now eol? */ return SCPE_2MARG; for (i = 0; (dptr = sim_devices[i]) != NULL; i++) show_dev_modifiers (st, dptr, NULL, flag, cptr); +for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) + show_dev_modifiers (st, dptr, NULL, flag, cptr); return SCPE_OK; } @@ -2508,6 +2516,8 @@ if (cptr && (*cptr != 0)) /* now eol? */ return SCPE_2MARG; for (i = 0; (dptr = sim_devices[i]) != NULL; i++) show_dev_show_commands (st, dptr, NULL, flag, cptr); +for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) + show_dev_show_commands (st, dptr, NULL, flag, cptr); return SCPE_OK; } @@ -2685,6 +2695,13 @@ for (i = start; (dptr = sim_devices[i]) != NULL; i++) { return reason; } } +for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) { + if (dptr->reset != NULL) { + reason = dptr->reset (dptr); + if (reason != SCPE_OK) + return reason; + } + } return SCPE_OK; } @@ -2756,7 +2773,8 @@ if (dptr == NULL) /* found dev? */ return SCPE_NXDEV; if (uptr == NULL) /* valid unit? */ return SCPE_NXUN; -if (uptr->flags & UNIT_ATT) { /* already attached? */ +if ((uptr->flags & UNIT_ATT) && /* already attached? */ + !(uptr->flags & UNIT_ATTMULT)) { /* and only single attachable */ r = scp_detach_unit (dptr, uptr); /* detach it */ if (r != SCPE_OK) /* error? */ return r; @@ -2896,7 +2914,6 @@ DEVICE *dptr; UNIT *uptr; t_stat r; -sim_unit_ref = ref_unit_all; /* we will reference all units */ if ((start < 0) || (start > 1)) return SCPE_IERR; for (i = start; (dptr = sim_devices[i]) != NULL; i++) { /* loop thru dev */ @@ -2915,6 +2932,7 @@ for (i = start; (dptr = sim_devices[i]) != NULL; i++) { /* loop thru dev */ return SCPE_OK; } + /* Call device-specific or file-oriented detach unit routine */ t_stat scp_detach_unit (DEVICE *dptr, UNIT *uptr) @@ -3040,6 +3058,19 @@ char *sim_dname (DEVICE *dptr) return (dptr->lname? dptr->lname: dptr->name); } +/* Get unit display name */ + +char *sim_uname (UNIT *uptr) +{ +DEVICE *d = find_dev_from_unit(uptr); +static AIO_TLS char uname[CBUFSIZE]; + +if (d->numunits == 1) + return sim_dname (d); +sprintf (uname, "%s%d", sim_dname (d), (int)(uptr-d->units)); +return uname; +} + /* Save command sa[ve] filename save state to specified file @@ -3216,8 +3247,6 @@ t_bool force_restore = sim_switches & SWMASK ('F'); #define READ_I(xx) if (sim_fread (&xx, sizeof (xx), 1, rfile) == 0) \ return SCPE_IOERR; -sim_unit_ref = ref_unit_all; /* we will reference all units */ - fstat (fileno (rfile), &rstat); READ_S (buf); /* [V2.5+] read version */ v35 = v32 = FALSE; @@ -3612,7 +3641,7 @@ for (i = 1; (dptr = sim_devices[i]) != NULL; i++) { /* flush attached files sim_cancel (&sim_step_unit); /* cancel step timer */ sim_throt_cancel (); /* cancel throttle */ AIO_UPDATE_QUEUE; -if (sim_clock_queue != NULL) { /* update sim time */ +if (sim_clock_queue != QUEUE_LIST_END) { /* update sim time */ UPDATE_SIM_TIME (sim_clock_queue->time); } else { @@ -3643,10 +3672,15 @@ if (sim_log) /* log if enabled */ t_stat run_boot_prep (void) { +UNIT *uptr; + sim_interval = 0; /* reset queue */ sim_time = sim_rtime = 0; noqueue_time = 0; -sim_clock_queue = NULL; +for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = sim_clock_queue) { + sim_clock_queue = uptr->next; + uptr->next = NULL; + } return reset_all (0); } @@ -4605,6 +4639,12 @@ for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { (strcmp (cptr, dptr->lname) == 0))) return dptr; } +for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) { + if ((strcmp (cptr, dptr->name) == 0) || + (dptr->lname && + (strcmp (cptr, dptr->lname) == 0))) + return dptr; + } return NULL; } @@ -4617,8 +4657,6 @@ return NULL; result = pointer to device (null if no dev) *iptr = pointer to unit (null if nx unit) - The "sim_unit_ref" global is set to the type of reference specified by cptr - (device or unit). */ DEVICE *find_unit (char *cptr, UNIT **uptr) @@ -4628,13 +4666,11 @@ char *nptr, *tptr; t_stat r; DEVICE *dptr; -sim_unit_ref = ref_unit; /* assume unit reference */ if (uptr == NULL) /* arg error? */ return NULL; if ((dptr = find_dev (cptr))) { /* exact match? */ if (qdisable (dptr)) /* disabled? */ return NULL; - sim_unit_ref = ref_dev; /* flag device reference */ *uptr = dptr->units; /* unit 0 */ return dptr; } @@ -4660,6 +4696,29 @@ for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { /* base + unit#? */ return NULL; } +/* sim_register_internal_device Add device to internal device list + + Inputs: + dptr = pointer to device +*/ + +t_stat sim_register_internal_device (DEVICE *dptr) +{ +uint32 i; + +for (i = 0; (sim_devices[i] != NULL); ++i) + if (sim_devices[i] == dptr) + return SCPE_OK; +for (i = 0; i < sim_internal_device_count; ++i) + if (sim_internal_devices[i] == dptr) + return SCPE_OK; +++sim_internal_device_count; +sim_internal_devices = realloc(sim_internal_devices, (sim_internal_device_count+1)*sizeof(*sim_internal_devices)); +sim_internal_devices[sim_internal_device_count-1] = dptr; +sim_internal_devices[sim_internal_device_count] = NULL; +return SCPE_OK; +} + /* Find_dev_from_unit find device for unit Inputs: @@ -4681,6 +4740,13 @@ for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { return dptr; } } +for (i = 0; inumunits; j++) { + if (uptr == (dptr->units + j)) + return dptr; + } + } return NULL; } @@ -5028,13 +5094,13 @@ return 0; On an error, the endptr will equal the inptr. */ -t_value strtotv (char *inptr, char **endptr, uint32 radix) +t_value strtotv (const char *inptr, char **endptr, uint32 radix) { int32 nodigit; t_value val; uint32 c, digit; -*endptr = inptr; /* assume fails */ +*endptr = (char *)inptr; /* assume fails */ if ((radix < 2) || (radix > 36)) return 0; while (isspace (*inptr)) /* bypass white space */ @@ -5056,7 +5122,7 @@ for (c = *inptr; isalnum(c); c = *++inptr) { /* loop through char */ } if (nodigit) /* no digits? */ return 0; -*endptr = inptr; /* result pointer */ +*endptr = (char *)inptr; /* result pointer */ return val; } @@ -5110,6 +5176,7 @@ return SCPE_OK; /* Event queue package sim_activate add entry to event queue + sim_activate_after add entry to event queue after a specified amount of wall time sim_cancel remove entry from event queue sim_process_event process entries on event queue sim_is_active see if entry is on event queue @@ -5145,7 +5212,7 @@ t_stat reason; if (stop_cpu) /* stop CPU? */ return SCPE_STOP; AIO_UPDATE_QUEUE; -if (sim_clock_queue == NULL) { /* queue empty? */ +if (sim_clock_queue == QUEUE_LIST_END) { /* queue empty? */ UPDATE_SIM_TIME (noqueue_time); /* update sim time */ sim_interval = noqueue_time = NOQUEUE_WAIT; /* flag queue empty */ return SCPE_OK; @@ -5156,7 +5223,7 @@ do { sim_clock_queue = uptr->next; /* remove first */ uptr->next = NULL; /* hygiene */ uptr->time = 0; - if (sim_clock_queue != NULL) + if (sim_clock_queue != QUEUE_LIST_END) sim_interval = sim_clock_queue->time; else sim_interval = noqueue_time = NOQUEUE_WAIT; if (uptr->action != NULL) @@ -5180,13 +5247,18 @@ return reason; t_stat sim_activate (UNIT *uptr, int32 event_time) { +return _sim_activate (uptr, event_time); +} + +t_stat _sim_activate (UNIT *uptr, int32 event_time) +{ UNIT *cptr, *prvptr; int32 accum; -AIO_ACTIVATE (sim_activate, uptr, event_time); +AIO_ACTIVATE (_sim_activate, uptr, event_time); if (sim_is_active (uptr)) /* already active? */ return SCPE_OK; -if (sim_clock_queue == NULL) { +if (sim_clock_queue == QUEUE_LIST_END) { UPDATE_SIM_TIME (noqueue_time); } else { /* update sim time */ @@ -5195,7 +5267,7 @@ else { /* update sim time */ prvptr = NULL; accum = 0; -for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) { +for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) { if (event_time < (accum + cptr->time)) break; accum = accum + cptr->time; @@ -5210,7 +5282,7 @@ else { prvptr->next = uptr; } uptr->time = event_time - accum; -if (cptr != NULL) +if (cptr != QUEUE_LIST_END) cptr->time = cptr->time - uptr->time; sim_interval = sim_clock_queue->time; return SCPE_OK; @@ -5256,6 +5328,28 @@ else return sim_activate (uptr, urtime-rtimenow); } +/* sim_activate_after - activate (queue) event + + Inputs: + uptr = pointer to unit + usec_delay = relative timeout (in microseconds) + Outputs: + reason = result (SCPE_OK if ok) +*/ + +t_stat sim_activate_after (UNIT *uptr, int32 event_time) +{ +return _sim_activate_after (uptr, event_time); +} + +t_stat _sim_activate_after (UNIT *uptr, int32 usec_delay) +{ +if (sim_is_active_bool (uptr)) /* already active? */ + return SCPE_OK; +AIO_ACTIVATE (_sim_activate_after, uptr, usec_delay); +return sim_timer_activate_after (uptr, usec_delay); +} + /* sim_cancel - cancel (dequeue) event Inputs: @@ -5270,30 +5364,43 @@ t_stat sim_cancel (UNIT *uptr) UNIT *cptr, *nptr; AIO_VALIDATE; -if (sim_clock_queue == NULL) +if (sim_clock_queue == QUEUE_LIST_END) return SCPE_OK; UPDATE_SIM_TIME (sim_clock_queue->time); /* update sim time */ -nptr = NULL; +nptr = QUEUE_LIST_END; if (sim_clock_queue == uptr) nptr = sim_clock_queue = uptr->next; else { - for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) { + for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) { if (cptr->next == uptr) { nptr = cptr->next = uptr->next; break; /* end queue scan */ } } } -if (nptr != NULL) +if (nptr != QUEUE_LIST_END) nptr->time = nptr->time + uptr->time; uptr->next = NULL; /* hygiene */ uptr->time = 0; -if (sim_clock_queue != NULL) +if (sim_clock_queue != QUEUE_LIST_END) sim_interval = sim_clock_queue->time; else sim_interval = noqueue_time = NOQUEUE_WAIT; return SCPE_OK; } +/* sim_is_active_bool - test for entry in queue, return activation time + + Inputs: + uptr = pointer to unit + Outputs: + result = TRUE if active FALSE if inactive +*/ + +t_bool sim_is_active_bool (UNIT *uptr) +{ +return (uptr->next != NULL); +} + /* sim_is_active - test for entry in queue, return activation time Inputs: @@ -5305,11 +5412,10 @@ return SCPE_OK; int32 sim_is_active (UNIT *uptr) { UNIT *cptr; -int32 accum; +int32 accum = 0; AIO_VALIDATE; -accum = 0; -for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) { +for (cptr = sim_clock_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) { if (cptr == sim_clock_queue) { if (sim_interval > 0) accum = accum + sim_interval; @@ -5331,7 +5437,7 @@ return 0; double sim_gtime (void) { -if (sim_clock_queue == NULL) { +if (sim_clock_queue == QUEUE_LIST_END) { UPDATE_SIM_TIME (noqueue_time); } else { @@ -5342,7 +5448,7 @@ return sim_time; uint32 sim_grtime (void) { -if (sim_clock_queue == NULL) { +if (sim_clock_queue == QUEUE_LIST_END) { UPDATE_SIM_TIME (noqueue_time); } else { @@ -5364,7 +5470,7 @@ int32 cnt; UNIT *uptr; cnt = 0; -for (uptr = sim_clock_queue; uptr != NULL; uptr = uptr->next) +for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) cnt++; return cnt; } diff --git a/scp.h b/scp.h index 38ce92fb..26dfe5dd 100644 --- a/scp.h +++ b/scp.h @@ -24,7 +24,6 @@ in this Software without prior written authorization from Robert M Supnik. 05-Dec-10 MP Added macro invocation of sim_debug - 07-Jan-09 JDB Added UNITREF typedef 09-Aug-06 JDB Added assign_device and deassign_device 14-Jul-06 RMS Added sim_activate_abs 06-Jan-06 RMS Added fprint_stopped_gen @@ -53,10 +52,6 @@ #define CMD_OPT_SCH 004 /* search */ #define CMD_OPT_DFT 010 /* defaults */ -/* unit references */ - -typedef enum { ref_dev, ref_unit, ref_unit_all } UNITREF; - /* Command processors */ t_stat reset_cmd (int32 flag, char *ptr); @@ -90,9 +85,13 @@ t_stat echo_cmd (int32 flag, char *ptr); t_stat sim_process_event (void); t_stat sim_activate (UNIT *uptr, int32 interval); +t_stat _sim_activate (UNIT *uptr, int32 interval); t_stat sim_activate_abs (UNIT *uptr, int32 interval); t_stat sim_activate_notbefore (UNIT *uptr, int32 rtime); +t_stat sim_activate_after (UNIT *uptr, int32 usecs_walltime); +t_stat _sim_activate_after (UNIT *uptr, int32 usecs_walltime); t_stat sim_cancel (UNIT *uptr); +t_bool sim_is_active_bool (UNIT *uptr); int32 sim_is_active (UNIT *uptr); double sim_gtime (void); uint32 sim_grtime (void); @@ -104,6 +103,7 @@ t_stat deassign_device (DEVICE *dptr); t_stat reset_all (uint32 start_device); t_stat reset_all_p (uint32 start_device); char *sim_dname (DEVICE *dptr); +char *sim_uname (UNIT *dptr); t_stat get_yn (char *ques, t_stat deflt); char *get_sim_opt (int32 opt, char *cptr, t_stat *st); char *get_glyph (char *iptr, char *optr, char mchar); @@ -111,12 +111,13 @@ char *get_glyph_nc (char *iptr, char *optr, char mchar); t_value get_uint (char *cptr, uint32 radix, t_value max, t_stat *status); char *get_range (DEVICE *dptr, char *cptr, t_addr *lo, t_addr *hi, uint32 rdx, t_addr max, char term); -t_value strtotv (char *cptr, char **endptr, uint32 radix); +t_value strtotv (const char *cptr, char **endptr, uint32 radix); t_stat fprint_val (FILE *stream, t_value val, uint32 rdx, uint32 wid, uint32 fmt); CTAB *find_cmd (char *gbuf); DEVICE *find_dev (char *ptr); DEVICE *find_unit (char *ptr, UNIT **uptr); DEVICE *find_dev_from_unit (UNIT *uptr); +t_stat sim_register_internal_device (DEVICE *dptr); REG *find_reg (char *ptr, char **optr, DEVICE *dptr); CTAB *find_ctab (CTAB *tab, char *gbuf); C1TAB *find_c1tab (C1TAB *tab, char *gbuf); diff --git a/sim_console.c b/sim_console.c index 9592214f..2e4e4889 100644 --- a/sim_console.c +++ b/sim_console.c @@ -118,10 +118,20 @@ */ #include "sim_defs.h" -#include "sim_sock.h" #include "sim_tmxr.h" #include +/* Forward Declaraations of Platform specific routines */ + +t_stat sim_os_poll_kbd (void); +t_bool sim_os_poll_kbd_ready (int ms_timeout); +t_stat sim_os_putchar (int32 out); +t_stat sim_os_ttinit (void); +t_stat sim_os_ttrun (void); +t_stat sim_os_ttcmd (void); +t_stat sim_os_ttclose (void); +t_bool sim_os_ttisatty (void); + #define KMAP_WRU 0 #define KMAP_BRK 1 #define KMAP_DEL 2 @@ -136,8 +146,51 @@ int32 sim_del_char = '\b'; /* delete character */ #else int32 sim_del_char = 0177; #endif -TMLN sim_con_ldsc = { 0 }; /* console line descr */ -TMXR sim_con_tmxr = { 1, 0, 0, &sim_con_ldsc }; /* console line mux */ +t_stat sim_con_poll_svc (UNIT *uptr); /* console connection poll routine */ +t_stat sim_con_reset (DEVICE *dptr); /* console connection poll routine */ +UNIT sim_con_unit = { UDATA (&sim_con_poll_svc, 0, 0) }; /* console connection unit */ +/* debugging bitmaps */ +#define DBG_TRC TMXR_DBG_TRC /* trace routine calls */ +#define DBG_XMT TMXR_DBG_XMT /* display Transmitted Data */ +#define DBG_RCV TMXR_DBG_RCV /* display Received Data */ +#define DBG_ASY TMXR_DBG_ASY /* asynchronous thread activity */ + +DEBTAB sim_con_debug[] = { + {"TRC", DBG_TRC}, + {"XMT", DBG_XMT}, + {"RCV", DBG_RCV}, + {"ASY", DBG_ASY}, + {0} +}; + +MTAB sim_con_mod[] = { + { 0 }, +}; + +DEVICE sim_con_telnet = { + "CON-TEL", &sim_con_unit, NULL, sim_con_mod, + 1, 0, 0, 0, 0, 0, + NULL, NULL, sim_con_reset, NULL, NULL, NULL, + NULL, DEV_DEBUG, 0, sim_con_debug}; +TMLN sim_con_ldsc = { 0 }; /* console line descr */ +TMXR sim_con_tmxr = { 1, 0, 0, &sim_con_ldsc, NULL, &sim_con_telnet };/* console line mux */ + +/* Unit service for console connection polling */ + +t_stat sim_con_poll_svc (UNIT *uptr) +{ +if (sim_con_tmxr.master == 0) /* not Telnet? done */ + return SCPE_OK; +if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ + sim_con_ldsc.rcve = 1; /* rcv enabled */ +sim_activate_after(uptr, 1000000); /* check again in 1 second */ +return SCPE_OK; +} + +t_stat sim_con_reset (DEVICE *dptr) +{ +return sim_con_poll_svc (&dptr->units[0]); /* establish polling as needed */ +} extern volatile int32 stop_cpu; extern int32 sim_quiet; @@ -154,6 +207,8 @@ static CTAB set_con_tab[] = { { "PCHAR", &sim_set_pchar, 0 }, { "TELNET", &sim_set_telnet, 0 }, { "NOTELNET", &sim_set_notelnet, 0 }, + { "SERIAL", &sim_set_serial, 0 }, + { "NOSERIAL", &sim_set_noserial, 0 }, { "LOG", &sim_set_logon, 0 }, { "NOLOG", &sim_set_logoff, 0 }, { "DEBUG", &sim_set_debon, 0 }, @@ -182,6 +237,12 @@ static CTAB set_con_telnet_tab[] = { { NULL, NULL, 0 } }; +static CTAB set_con_serial_tab[] = { + { "LOG", &sim_set_cons_log, 0 }, + { "NOLOG", &sim_set_cons_nolog, 0 }, + { NULL, NULL, 0 } + }; + static int32 *cons_kmap[] = { &sim_int_char, &sim_brk_char, @@ -438,7 +499,10 @@ while (*cptr != 0) { /* do all mods */ if (r == SCPE_OK) { if (sim_con_tmxr.master) /* already open? */ sim_set_notelnet (0, NULL); /* close first */ - return tmxr_open_master (&sim_con_tmxr, gbuf); /* open master socket */ + r = tmxr_attach (&sim_con_tmxr, &sim_con_unit, gbuf);/* open master socket */ + if (r == SCPE_OK) + sim_activate_after(&sim_con_unit, 1000000); /* check for connection in 1 second */ + return r; } return SCPE_NOPARAM; } @@ -463,16 +527,22 @@ t_stat sim_show_telnet (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, ch { if (cptr && (*cptr != 0)) return SCPE_2MARG; -if (sim_con_tmxr.master == 0) +if ((sim_con_tmxr.master == 0) && + (sim_con_ldsc.serport == 0)) fprintf (st, "Connected to console window\n"); else { - if (sim_con_ldsc.conn == 0) - fprintf (st, "Listening on port %s\n", sim_con_tmxr.port); - else { - fprintf (st, "Listening on port %s, connected to socket %d\n", - sim_con_tmxr.port, sim_con_ldsc.conn); + if (sim_con_ldsc.serport) { + fprintf (st, "Connected to "); tmxr_fconns (st, &sim_con_ldsc, -1); } + else + if (sim_con_ldsc.conn == 0) + fprintf (st, "Listening on port %s\n", sim_con_tmxr.port); + else { + fprintf (st, "Listening on port %s, connected to socket %d\n", + sim_con_tmxr.port, sim_con_ldsc.conn); + tmxr_fconns (st, &sim_con_ldsc, -1); + } tmxr_fstats (st, &sim_con_ldsc, -1); } return SCPE_OK; @@ -541,6 +611,59 @@ else return SCPE_OK; } +/* Set console to Serial port (and parameters) */ + +t_stat sim_set_serial (int32 flag, char *cptr) +{ +char *cvptr, gbuf[CBUFSIZE], ubuf[CBUFSIZE]; +CTAB *ctptr; +t_stat r; + +if ((cptr == NULL) || (*cptr == 0)) + return SCPE_2FARG; +while (*cptr != 0) { /* do all mods */ + cptr = get_glyph_nc (cptr, gbuf, ','); /* get modifier */ + if ((cvptr = strchr (gbuf, '='))) /* = value? */ + *cvptr++ = 0; + get_glyph (gbuf, ubuf, 0); /* modifier to UC */ + if ((ctptr = find_ctab (set_con_serial_tab, ubuf))) { /* match? */ + r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ + if (r != SCPE_OK) + return r; + } + else { + SERHANDLE serport = sim_open_serial (gbuf, NULL, &r); + if (serport != INVALID_HANDLE) { + sim_close_serial (serport); + if (r == SCPE_OK) { + char cbuf[CBUFSIZE]; + if ((sim_con_tmxr.master) || /* already open? */ + (sim_con_ldsc.serport)) + sim_set_noserial (0, NULL); /* close first */ + sprintf(cbuf, "Connect=%s", gbuf); + r = tmxr_attach (&sim_con_tmxr, &sim_con_unit, cbuf);/* open master socket */ + sim_con_ldsc.rcve = 1; /* rcv enabled */ + if (r == SCPE_OK) + sim_activate_after(&sim_con_unit, 1000000); /* check for connection in 1 second */ + return r; + } + } + return SCPE_NOPARAM; + } + } +return SCPE_OK; +} + +/* Close console Serial port */ + +t_stat sim_set_noserial (int32 flag, char *cptr) +{ +if (cptr && (*cptr != 0)) /* too many arguments? */ + return SCPE_2MARG; +if (sim_con_ldsc.serport == 0) /* ignore if already closed */ + return SCPE_OK; +return tmxr_close_master (&sim_con_tmxr); /* close master socket */ +} /* Log File Open/Close/Show Support */ @@ -687,9 +810,12 @@ t_stat sim_poll_kbd (void) int32 c; c = sim_os_poll_kbd (); /* get character */ -if ((c == SCPE_STOP) || (sim_con_tmxr.master == 0)) /* ^E or not Telnet? */ +if ((c == SCPE_STOP) || /* ^E or not Telnet? */ + ((sim_con_tmxr.master == 0) && /* and not serial? */ + (sim_con_ldsc.serport == 0))) return c; /* in-window */ -if (sim_con_ldsc.conn == 0) { /* no Telnet conn? */ +if ((sim_con_ldsc.conn == 0) && /* no Telnet conn */ + (sim_con_ldsc.serport == 0)) { /* and no serial conn? */ if (!sim_con_ldsc.txbfd) /* unbuffered? */ return SCPE_LOST; /* connection lost */ if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ @@ -707,14 +833,16 @@ return SCPE_OK; t_stat sim_putchar (int32 c) { -if (sim_con_tmxr.master == 0) { /* not Telnet? */ +if ((sim_con_tmxr.master == 0) && /* not Telnet? */ + (sim_con_ldsc.serport == 0)) { /* and not serial port */ if (sim_log) /* log file? */ fputc (c, sim_log); return sim_os_putchar (c); /* in-window version */ } if (sim_log && !sim_con_ldsc.txlog) /* log file, but no line log? */ fputc (c, sim_log); -if (sim_con_ldsc.conn == 0) { /* no Telnet conn? */ +if ((sim_con_ldsc.serport == 0) && /* no serial port */ + (sim_con_ldsc.conn == 0)) { /* and no Telnet conn? */ if (!sim_con_ldsc.txbfd) /* unbuffered? */ return SCPE_LOST; /* connection lost */ if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ @@ -729,14 +857,16 @@ t_stat sim_putchar_s (int32 c) { t_stat r; -if (sim_con_tmxr.master == 0) { /* not Telnet? */ +if ((sim_con_tmxr.master == 0) && /* not Telnet? */ + (sim_con_ldsc.serport == 0)) { /* and not serial port */ if (sim_log) /* log file? */ fputc (c, sim_log); return sim_os_putchar (c); /* in-window version */ } if (sim_log && !sim_con_ldsc.txlog) /* log file, but no line log? */ fputc (c, sim_log); -if (sim_con_ldsc.conn == 0) { /* no Telnet conn? */ +if ((sim_con_ldsc.serport == 0) && /* no serial port */ + (sim_con_ldsc.conn == 0)) { /* and no Telnet conn? */ if (!sim_con_ldsc.txbfd) /* non-buffered Telnet conn? */ return SCPE_LOST; /* lost */ if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ @@ -791,6 +921,69 @@ else c = c & 0377; return c; } + +t_stat sim_ttinit (void) +{ +sim_register_internal_device (&sim_con_telnet); +tmxr_startup (); +return sim_os_ttinit (); +} + +t_stat sim_ttrun (void) +{ +if (!sim_con_tmxr.ldsc->uptr) /* If simulator didn't declare its input polling unit */ + sim_con_unit.flags &= ~UNIT_TM_POLL; /* we can't poll asynchronously */ +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) +pthread_mutex_lock (&sim_tmxr_poll_lock); +if (sim_asynch_enabled) { + pthread_attr_t attr; + + pthread_cond_init (&sim_console_startup_cond, NULL); + pthread_attr_init (&attr); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + pthread_create (&sim_console_poll_thread, &attr, _console_poll, NULL); + pthread_attr_destroy( &attr); + pthread_cond_wait (&sim_console_startup_cond, &sim_tmxr_poll_lock); /* Wait for thread to stabilize */ + pthread_cond_destroy (&sim_console_startup_cond); + sim_console_poll_running = TRUE; + } +pthread_mutex_unlock (&sim_tmxr_poll_lock); +#endif +tmxr_start_poll (); +return sim_os_ttrun (); +} + +t_stat sim_ttcmd (void) +{ +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) +pthread_mutex_lock (&sim_tmxr_poll_lock); +if (sim_console_poll_running) { + pthread_cond_signal (&sim_tmxr_poll_cond); + pthread_mutex_unlock (&sim_tmxr_poll_lock); + pthread_join (sim_console_poll_thread, NULL); + sim_console_poll_running = FALSE; + } +else + pthread_mutex_unlock (&sim_tmxr_poll_lock); +#endif +tmxr_stop_poll (); +return sim_os_ttcmd (); +} + +t_stat sim_ttclose (void) +{ +tmxr_shutdown (); +return sim_os_ttclose (); +} + +t_bool sim_ttisatty (void) +{ +return sim_os_ttisatty (); +} + + +/* Platform specific routine definitions */ + /* VMS routines, from Ben Thomas, with fixes from Robert Alan Byer */ #if defined (VMS) @@ -798,6 +991,7 @@ return c; #if defined(__VAX) #define sys$assign SYS$ASSIGN #define sys$qiow SYS$QIOW +#define sys$dassgn SYS$DASSGN #endif #include @@ -810,6 +1004,7 @@ return c; #define EFN 0 uint32 tty_chan = 0; +int buffered_character = 0; typedef struct { unsigned short sense_count; @@ -826,7 +1021,7 @@ typedef struct { SENSE_BUF cmd_mode = { 0 }; SENSE_BUF run_mode = { 0 }; -t_stat sim_ttinit (void) +t_stat sim_os_ttinit (void) { unsigned int status; IOSB iosb; @@ -869,17 +1064,19 @@ if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { -return sim_ttcmd (); +sim_ttcmd (); +sys$dassgn (tty_chan); +return SCPE_OK; } -t_bool sim_ttisatty (void) +t_bool sim_os_ttisatty (void) { return isatty (fileno (stdin)); } -t_stat sim_os_poll_kbd (void) +t_stat sim_os_poll_kbd_data (void) { unsigned int status, term[2]; unsigned char buf[4]; @@ -904,6 +1101,40 @@ if (sim_brk_char && (buf[0] == sim_brk_char)) return (buf[0] | SCPE_KFLAG); } +t_stat sim_os_poll_kbd (void) +{ +t_stat response; + +if (response = buffered_character) { + buffered_character = 0; + return response; + } +return sim_os_poll_kbd_data (); +} + +t_bool sim_os_poll_kbd_ready (int ms_timeout) +{ +unsigned int status, term[2]; +unsigned char buf[4]; +IOSB iosb; + +term[0] = 0; term[1] = 0; +status = sys$qiow (EFN, tty_chan, + IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR | IO$M_TIMED | IO$M_TRMNOECHO, + &iosb, 0, 0, buf, 1, (ms_timeout+999)/1000, term, 0, 0); +if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) + return FALSE; +if (buf[0] == sim_int_char) + buffered_character = SCPE_STOP; +else + if (sim_brk_char && (buf[0] == sim_brk_char)) + buffered_character = SCPE_BREAK; + else + buffered_character = (buf[0] | SCPE_KFLAG) +return TRUE; +} + + t_stat sim_os_putchar (int32 out) { unsigned int status; @@ -960,7 +1191,7 @@ ControlHandler(DWORD dwCtrlType) return FALSE; } -t_stat sim_ttinit (void) +t_stat sim_os_ttinit (void) { SetConsoleCtrlHandler( ControlHandler, TRUE ); std_input = GetStdHandle (STD_INPUT_HANDLE); @@ -971,7 +1202,7 @@ if ((std_input) && /* Not Background proces return SCPE_OK; } -t_stat sim_ttrun (void) +t_stat sim_os_ttrun (void) { if ((std_input) && /* If Not Background process? */ (std_input != INVALID_HANDLE_VALUE) && @@ -986,7 +1217,7 @@ SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); return SCPE_OK; } -t_stat sim_ttcmd (void) +t_stat sim_os_ttcmd (void) { if (sim_log) { fflush (sim_log); @@ -1000,12 +1231,12 @@ if ((std_input) && /* If Not Background pro return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { return SCPE_OK; } -t_bool sim_ttisatty (void) +t_bool sim_os_ttisatty (void) { DWORD Mode; @@ -1018,7 +1249,7 @@ int c = -1; DWORD nkbevents, nkbevent; INPUT_RECORD rec; -\ +sim_debug (DBG_TRC, &sim_con_telnet, "sim_os_poll_kbd()\n"); if ((std_input == NULL) || /* No keyboard for */ (std_input == INVALID_HANDLE_VALUE)) /* background processes */ @@ -1055,6 +1286,17 @@ if ((sim_brk_char && ((c & 0177) == sim_brk_char)) || (c & SCPE_BREAK)) return c | SCPE_KFLAG; } +t_bool sim_os_poll_kbd_ready (int ms_timeout) +{ +sim_debug (DBG_TRC, &sim_con_telnet, "sim_os_poll_kbd_ready()\n"); +if ((std_input == NULL) || /* No keyboard for */ + (std_input == INVALID_HANDLE_VALUE)) { /* background processes */ + Sleep (ms_timeout); + return FALSE; + } +return (WAIT_OBJECT_0 == WaitForSingleObject (std_input, ms_timeout)); +} + t_stat sim_os_putchar (int32 c) { DWORD unused; @@ -1070,27 +1312,27 @@ return SCPE_OK; #include -t_stat sim_ttinit (void) +t_stat sim_os_ttinit (void) { return SCPE_OK; } -t_stat sim_ttrun (void) +t_stat sim_os_ttrun (void) { return SCPE_OK; } -t_stat sim_ttcmd (void) +t_stat sim_os_ttcmd (void) { return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { return SCPE_OK; } -t_bool sim_ttisatty (void) +t_bool sim_os_ttisatty (void) { return 1; } @@ -1126,6 +1368,12 @@ if (sim_brk_char && ((c & 0177) == sim_brk_char)) return c | SCPE_KFLAG; } +t_bool sim_os_poll_kbd_ready (int ms_timeout) /* Don't know how to do this on this platform */ +{ +sim_os_ms_sleep (MIN(20,ms_timeout)); /* Wait a little */ +return TRUE; /* force a poll */ +} + t_stat sim_os_putchar (int32 c) { if (c != 0177) { @@ -1253,7 +1501,7 @@ int ps_getch(void) { /* Note that this only works if the call to sim_ttinit comes before any output to the console */ -t_stat sim_ttinit (void) { +t_stat sim_os_ttinit (void) { int i; /* this blank will later be replaced by the number of characters */ char title[50] = " "; @@ -1276,22 +1524,22 @@ t_stat sim_ttinit (void) { return SCPE_OK; } -t_stat sim_ttrun (void) +t_stat sim_os_ttrun (void) { return SCPE_OK; } -t_stat sim_ttcmd (void) +t_stat sim_os_ttcmd (void) { return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { return SCPE_OK; } -t_bool sim_ttisatty (void) +t_bool sim_os_ttisatty (void) { return 1; } @@ -1311,6 +1559,12 @@ if (sim_brk_char && ((c & 0177) == sim_brk_char)) return c | SCPE_KFLAG; } +t_bool sim_os_poll_kbd_ready (int ms_timeout) /* Don't know how to do this on this platform */ +{ +sim_os_ms_sleep (MIN(20,ms_timeout)); /* Wait a little */ +return TRUE; /* force a poll */ +} + t_stat sim_os_putchar (int32 c) { if (c != 0177) { @@ -1333,7 +1587,7 @@ struct tchars cmdtchars,runtchars; /* V7 editing */ struct ltchars cmdltchars,runltchars; /* 4.2 BSD editing */ int cmdfl,runfl; /* TTY flags */ -t_stat sim_ttinit (void) +t_stat sim_os_ttinit (void) { cmdfl = fcntl (0, F_GETFL, 0); /* get old flags and status */ runfl = cmdfl | FNDELAY; @@ -1360,7 +1614,7 @@ runltchars.t_lnextc = 0xFF; return SCPE_OK; /* return success */ } -t_stat sim_ttrun (void) +t_stat sim_os_ttrun (void) { runtchars.t_intrc = sim_int_char; /* in case changed */ fcntl (0, F_SETFL, runfl); /* non-block mode */ @@ -1374,7 +1628,7 @@ nice (10); /* lower priority */ return SCPE_OK; } -t_stat sim_ttcmd (void) +t_stat sim_os_ttcmd (void) { nice (-10); /* restore priority */ fcntl (0, F_SETFL, cmdfl); /* block mode */ @@ -1387,12 +1641,12 @@ if (ioctl (0, TIOCSLTC, &cmdltchars) < 0) return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { return sim_ttcmd (); } -t_bool sim_ttisatty (void) +t_bool sim_os_ttisatty (void) { return isatty (0); } @@ -1409,6 +1663,22 @@ if (sim_brk_char && (buf[0] == sim_brk_char)) else return (buf[0] | SCPE_KFLAG); } +t_bool sim_os_poll_kbd_ready (int ms_timeout) +{ +fd_set readfds; +struct timeval timeout; + +if (!isatty (0)) { /* skip if !tty */ + sim_os_ms_sleep (ms_timeout); + return FALSE; + } +FD_ZERO (&readfds); +FD_SET (0, &readfds); +timeout.tv_sec = (ms_timeout*1000)/1000000; +timeout.tv_usec = (ms_timeout*1000)%1000000; +return (1 == select (1, &readfds, NULL, NULL, &timeout)); +} + t_stat sim_os_putchar (int32 out) { char c; @@ -1428,7 +1698,7 @@ return SCPE_OK; struct termios cmdtty, runtty; static int prior_norm = 1; -t_stat sim_ttinit (void) +t_stat sim_os_ttinit (void) { if (!isatty (fileno (stdin))) /* skip if !tty */ return SCPE_OK; @@ -1470,7 +1740,7 @@ runtty.c_cc[VSTATUS] = 0; return SCPE_OK; } -t_stat sim_ttrun (void) +t_stat sim_os_ttrun (void) { if (!isatty (fileno (stdin))) /* skip if !tty */ return SCPE_OK; @@ -1485,7 +1755,7 @@ if (prior_norm) { /* at normal pri? */ return SCPE_OK; } -t_stat sim_ttcmd (void) +t_stat sim_os_ttcmd (void) { if (!isatty (fileno (stdin))) /* skip if !tty */ return SCPE_OK; @@ -1499,12 +1769,12 @@ if (tcsetattr (0, TCSAFLUSH, &cmdtty) < 0) return SCPE_OK; } -t_stat sim_ttclose (void) +t_stat sim_os_ttclose (void) { return sim_ttcmd (); } -t_bool sim_ttisatty(void) +t_bool sim_os_ttisatty (void) { return isatty (fileno (stdin)); } @@ -1521,6 +1791,22 @@ if (sim_brk_char && (buf[0] == sim_brk_char)) else return (buf[0] | SCPE_KFLAG); } +t_bool sim_os_poll_kbd_ready (int ms_timeout) +{ +fd_set readfds; +struct timeval timeout; + +if (!sim_os_ttisatty()) { /* skip if !tty */ + sim_os_ms_sleep (ms_timeout); + return FALSE; + } +FD_ZERO (&readfds); +FD_SET (0, &readfds); +timeout.tv_sec = (ms_timeout*1000)/1000000; +timeout.tv_usec = (ms_timeout*1000)%1000000; +return (1 == select (1, &readfds, NULL, NULL, &timeout)); +} + t_stat sim_os_putchar (int32 out) { char c; diff --git a/sim_console.h b/sim_console.h index 78d51dd7..9ad4342b 100644 --- a/sim_console.h +++ b/sim_console.h @@ -55,6 +55,8 @@ t_stat sim_set_console (int32 flag, char *cptr); t_stat sim_set_kmap (int32 flag, char *cptr); t_stat sim_set_telnet (int32 flag, char *cptr); t_stat sim_set_notelnet (int32 flag, char *cptr); +t_stat sim_set_serial (int32 flag, char *cptr); +t_stat sim_set_noserial (int32 flag, char *cptr); t_stat sim_set_logon (int32 flag, char *cptr); t_stat sim_set_logoff (int32 flag, char *cptr); t_stat sim_set_debon (int32 flag, char *cptr); diff --git a/sim_defs.h b/sim_defs.h index 3d32500e..4e81aed2 100644 --- a/sim_defs.h +++ b/sim_defs.h @@ -279,6 +279,14 @@ typedef uint32 t_addr; #define MATCH_CMD(ptr,cmd) strncmp ((ptr), (cmd), strlen (ptr)) +/* End of Linked List/Queue value */ +/* Chosen for 2 reasons: */ +/* 1 - to not be NULL, this allowing the NULL value to */ +/* indicate inclusion on a list */ +/* and */ +/* 2 - to not be a valid/possible pointer (alignment) */ +#define QUEUE_LIST_END ((void *)1) + /* Device data structure */ struct sim_device { @@ -376,25 +384,29 @@ struct sim_unit { /* Unit flags */ -#define UNIT_V_UF_31 12 /* dev spec, V3.1 */ -#define UNIT_V_UF 16 /* device specific */ -#define UNIT_V_RSV 31 /* reserved!! */ +#define UNIT_V_UF_31 12 /* dev spec, V3.1 */ +#define UNIT_V_UF 16 /* device specific */ +#define UNIT_V_RSV 31 /* reserved!! */ -#define UNIT_ATTABLE 000001 /* attachable */ -#define UNIT_RO 000002 /* read only */ -#define UNIT_FIX 000004 /* fixed capacity */ -#define UNIT_SEQ 000010 /* sequential */ -#define UNIT_ATT 000020 /* attached */ -#define UNIT_BINK 000040 /* K = power of 2 */ -#define UNIT_BUFABLE 000100 /* bufferable */ -#define UNIT_MUSTBUF 000200 /* must buffer */ -#define UNIT_BUF 000400 /* buffered */ -#define UNIT_ROABLE 001000 /* read only ok */ -#define UNIT_DISABLE 002000 /* disable-able */ -#define UNIT_DIS 004000 /* disabled */ -#define UNIT_RAW 010000 /* raw mode */ -#define UNIT_TEXT 020000 /* text mode */ -#define UNIT_IDLE 040000 /* idle eligible */ +#define UNIT_ATTABLE 0000001 /* attachable */ +#define UNIT_RO 0000002 /* read only */ +#define UNIT_FIX 0000004 /* fixed capacity */ +#define UNIT_SEQ 0000010 /* sequential */ +#define UNIT_ATT 0000020 /* attached */ +#define UNIT_BINK 0000040 /* K = power of 2 */ +#define UNIT_BUFABLE 0000100 /* bufferable */ +#define UNIT_MUSTBUF 0000200 /* must buffer */ +#define UNIT_BUF 0000400 /* buffered */ +#define UNIT_ROABLE 0001000 /* read only ok */ +#define UNIT_DISABLE 0002000 /* disable-able */ +#define UNIT_DIS 0004000 /* disabled */ +#define UNIT_RAW 0010000 /* raw mode */ +#define UNIT_TEXT 0020000 /* text mode */ +#define UNIT_IDLE 0040000 /* idle eligible */ +#define UNIT_ATTMULT 0100000 /* Allow multiple attach commands */ +#define UNIT_TM_POLL 0400000 /* TMXR Polling unit */ + /* This flag is ONLY set dynamically */ + /* it should NOT be set via initialization */ #define UNIT_UFMASK_31 (((1u << UNIT_V_RSV) - 1) & ~((1u << UNIT_V_UF_31) - 1)) #define UNIT_UFMASK (((1u << UNIT_V_RSV) - 1) & ~((1u << UNIT_V_UF) - 1)) @@ -573,20 +585,38 @@ extern int32 sim_asynch_check; extern int32 sim_asynch_latency; extern int32 sim_asynch_inst_latency; -#define AIO_LIST_END ((void *)1) /* Chosen to deliberately not be a valid pointer (alignment) */ +/* Thread local storage */ +#if defined(__GNUC__) && !defined(__APPLE__) +#define AIO_TLS __thread +#elif defined(__DECC_VER) || defined(_MSC_VER) +#define AIO_TLS __declspec(thread) +#else +/* Other compiler environment, then don't worry about thread local storage. */ +/* It is primarily used only used in debugging messages */ +#define AIO_TLS +#endif + #define AIO_INIT \ if (1) { \ sim_asynch_main_threadid = pthread_self(); \ /* Empty list/list end uses the point value (void *)1. \ This allows NULL in an entry's a_next pointer to \ indicate that the entry is not currently in any list */ \ - sim_asynch_queue = AIO_LIST_END; \ - } + sim_asynch_queue = QUEUE_LIST_END; \ + } \ + else \ + (void)0 #define AIO_CLEANUP \ if (1) { \ pthread_mutex_destroy(&sim_asynch_lock); \ pthread_cond_destroy(&sim_asynch_wake); \ - } + } \ + else \ + (void)0 +#define AIO_LOCK \ + pthread_mutex_lock(&sim_asynch_lock) +#define AIO_UNLOCK \ + pthread_mutex_unlock(&sim_asynch_lock) #if defined(__DECC_VER) #include @@ -616,13 +646,13 @@ extern int32 sim_asynch_inst_latency; #define AIO_QUEUE_VAL InterlockedCompareExchangePointer(&sim_asynch_queue, sim_asynch_queue, NULL) #define AIO_QUEUE_SET(val, queue) InterlockedCompareExchangePointer(&sim_asynch_queue, val, queue) #define AIO_UPDATE_QUEUE \ - if (AIO_QUEUE_VAL != AIO_LIST_END) { /* List !Empty */ \ + if (AIO_QUEUE_VAL != QUEUE_LIST_END) { /* List !Empty */ \ UNIT *q, *uptr; \ int32 a_event_time; \ do \ q = AIO_QUEUE_VAL; \ - while (q != AIO_QUEUE_SET(AIO_LIST_END, q)); \ - while (q != AIO_LIST_END) { /* List !Empty */ \ + while (q != AIO_QUEUE_SET(QUEUE_LIST_END, q)); \ + while (q != QUEUE_LIST_END) { /* List !Empty */ \ uptr = q; \ q = q->a_next; \ uptr->a_next = NULL; /* hygiene */ \ @@ -646,18 +676,18 @@ extern int32 sim_asynch_inst_latency; UNIT *q, *qe; \ uptr->a_event_time = event_time; \ uptr->a_activate_call = sim_activate; \ - uptr->a_next = AIO_LIST_END; /* Mark as on list */ \ + uptr->a_next = QUEUE_LIST_END; /* Mark as on list */ \ do { \ do \ q = AIO_QUEUE_VAL; \ - while (q != AIO_QUEUE_SET(AIO_LIST_END, q));/* Grab current list */ \ - for (qe = uptr; qe->a_next != AIO_LIST_END; qe = qe->a_next); \ + while (q != AIO_QUEUE_SET(QUEUE_LIST_END, q));/* Grab current list */\ + for (qe = uptr; qe->a_next != QUEUE_LIST_END; qe = qe->a_next); \ qe->a_next = q; /* append current list */\ do \ q = AIO_QUEUE_VAL; \ while (q != AIO_QUEUE_SET(uptr, q)); \ uptr = q; \ - } while (uptr != AIO_LIST_END); \ + } while (uptr != QUEUE_LIST_END); \ } \ if (sim_idle_wait) \ pthread_cond_signal (&sim_asynch_wake); \ @@ -671,7 +701,7 @@ extern int32 sim_asynch_inst_latency; if (1) { \ UNIT *uptr; \ pthread_mutex_lock (&sim_asynch_lock); \ - while (sim_asynch_queue != AIO_LIST_END) { /* List !Empty */ \ + while (sim_asynch_queue != QUEUE_LIST_END) { /* List !Empty */ \ int32 a_event_time; \ uptr = sim_asynch_queue; \ sim_asynch_queue = uptr->a_next; \ @@ -728,7 +758,9 @@ extern int32 sim_asynch_inst_latency; #define AIO_CHECK_EVENT #define AIO_INIT #define AIO_CLEANUP +#define AIO_RETURN_TIME(uptr) #define AIO_SET_INTERRUPT_LATENCY(instpersec) +#define AIO_TLS #endif /* SIM_ASYNCH_IO */ #endif diff --git a/sim_serial.c b/sim_serial.c index 1a57c0bc..ce30d1d2 100644 --- a/sim_serial.c +++ b/sim_serial.c @@ -41,7 +41,7 @@ sim_open_serial open a serial port sim_config_serial change baud rate and character framing configuration - sim_control_serial connect or disconnect a serial port (controls DTR) + sim_control_serial manipulate and/or return the modem bits on a serial port sim_read_serial read from a serial port sim_write_serial write to a serial port sim_close_serial close a serial port @@ -60,8 +60,8 @@ returned. - t_stat sim_config_serial (SERHANDLE port, SERCONFIG config) - ----------------------------------------------------------- + t_stat sim_config_serial (SERHANDLE port, const char *config) + ------------------------------------------------------------- The baud rate and framing parameters (character size, parity, and number of stop bits) of the serial port associated with "port" are set. If any @@ -70,13 +70,17 @@ is returned. If the configuration is successful, SCPE_OK is returned. - t_bool sim_control_serial (SERHANDLE port, t_bool connect) - ---------------------------------------------------------- + sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits) + ------------------------------------------------------------------------------------------------- - If "connect" is TRUE, the DTR (modem control) line of the serial port - associated with "port" is asserted. If "connect" is false, the line is - denied. If the DTR change is successful, the function returns TRUE. FALSE - is returned if an error occurs. + The DTR and RTS line of the serial port is set or cleared as indicated in + the respective bits_to_set or bits_to_clear parameters. If the + incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, + DSR and CTS are returned. + + If unreasonable or nonsense bits_to_set or bits_to_clear bits are + specified, then the return status is SCPE_ARG; + If an error occurs, SCPE_IOERR is returned. int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk) @@ -134,9 +138,17 @@ typedef struct serial_list { char desc[SER_DEV_DESC_MAX]; } SERIAL_LIST; +typedef struct serial_config { /* serial port configuration */ + uint32 baudrate; /* baud rate */ + uint32 charsize; /* character size in bits */ + char parity; /* parity (N/O/E/M/S) */ + uint32 stopbits; /* 0/1/2 stop bits (0 implies 1.5) */ + } SERCONFIG; + static int sim_serial_os_devices (int max, SERIAL_LIST* list); static SERHANDLE sim_open_os_serial (char *name); -static void sim_close_os_serial (SERHANDLE port); +static void sim_close_os_serial (SERHANDLE port); +static t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config); static struct open_serial_device { @@ -147,7 +159,17 @@ static struct open_serial_device { } *serial_open_devices = NULL; static int serial_open_device_count = 0; -static void _serial_add_to_open_list (SERHANDLE port, TMLN *line, const char *name, const char *desc) +static struct open_serial_device *_get_open_device (SERHANDLE port) +{ +int i; + +for (i=0; iname, b->name); } -static int sim_serial_devices(int max, SERIAL_LIST *list) +static int sim_serial_devices (int max, SERIAL_LIST *list) { int i, j, ports = sim_serial_os_devices(max, list); @@ -219,7 +242,7 @@ if (ports) /* Order the list returned alphabetically by the port name */ return ports; } -static char* sim_serial_getname(int number, char* name) +static char* sim_serial_getname (int number, char* name) { SERIAL_LIST list[SER_MAX_DEVICE]; int count = sim_serial_devices(SER_MAX_DEVICE, list); @@ -230,7 +253,7 @@ strcpy(name, list[number].name); return name; } -static char* sim_serial_getname_bydesc(char* desc, char* name) +static char* sim_serial_getname_bydesc (char* desc, char* name) { SERIAL_LIST list[SER_MAX_DEVICE]; int count = sim_serial_devices(SER_MAX_DEVICE, list); @@ -258,7 +281,7 @@ return NULL; } /* strncasecmp() is not available on all platforms */ -static int sim_serial_strncasecmp(char* string1, char* string2, size_t len) +static int sim_serial_strncasecmp (char* string1, char* string2, size_t len) { size_t i; unsigned char s1, s2; @@ -280,7 +303,7 @@ for (i=0; i min) min = len; for (i=0; imp->dptr->name, (int)(serial_open_devices[i].line->mp->ldsc-serial_open_devices[i].line), serial_open_devices[i].line->sername, d); - else - fprintf(st, " %s\tLn%02d %s\n", serial_open_devices[i].line->mp->dptr->name, (int)(serial_open_devices[i].line->mp->ldsc-serial_open_devices[i].line), serial_open_devices[i].line->sername); + fprintf(st, " %s\tLn%02d %s%s%s%s\tConfig: %s\n", serial_open_devices[i].line->mp->dptr->name, (int)(serial_open_devices[i].line->mp->ldsc-serial_open_devices[i].line), + serial_open_devices[i].line->destination, d ? " {" : "", d ? d : "", d ? ")" : "", serial_open_devices[i].line->serconfig); } } return SCPE_OK; } -SERHANDLE sim_open_serial (char *name, TMLN *lp) +SERHANDLE sim_open_serial (char *name, TMLN *lp, t_stat *stat) { -char temp1[1024], temp2[1024]; +char temp1[1024], temp2[1024], devname [1024]; char *savname = name; char *savdesc = NULL; -SERHANDLE port; +SERHANDLE port = INVALID_HANDLE; +char *config; +t_stat status; + +config = get_glyph_nc (name, devname, ';'); /* separate port name from optional config params */ + +if ((config == NULL) || (*config == '\0')) + config = "9600-8N1"; + +if (stat) + *stat = SCPE_OK; /* translate name of type "serX" to real device name */ -if ((strlen(name) <= 5) - && (tolower(name[0]) == 's') - && (tolower(name[1]) == 'e') - && (tolower(name[2]) == 'r') - && (isdigit(name[3])) - && (isdigit(name[4]) || (name[4] == '\0')) +if ((strlen(devname) <= 5) + && (tolower(devname[0]) == 's') + && (tolower(devname[1]) == 'e') + && (tolower(devname[2]) == 'r') + && (isdigit(devname[3])) + && (isdigit(devname[4]) || (devname[4] == '\0')) ) { - int num = atoi(&name[3]); + int num = atoi(&devname[3]); savname = sim_serial_getname(num, temp1); - if (savname == NULL) /* didn't translate */ - return INVALID_HANDLE; + if (savname == NULL) { /* didn't translate */ + if (stat) + *stat = SCPE_OPENERR; + return port; + } savdesc = sim_serial_getdesc_byname (savname, temp2); } else { /* are they trying to use device description? */ - savname = sim_serial_getname_bydesc(name, temp1); - if (savname == NULL) { /* didn't translate */ + savname = sim_serial_getname_bydesc(devname, temp1); + if (savname == NULL) { /* didn't translate */ /* probably is not serX and has no description */ - savname = sim_serial_getname_byname(name, temp1); + savname = sim_serial_getname_byname(devname, temp1); if (savname == NULL) /* didn't translate */ - savname = name; + savname = devname; else savdesc = sim_serial_getdesc_byname(savname, temp2); } else - savdesc = name; + savdesc = devname; } port = sim_open_os_serial (savname); + +if (port == INVALID_HANDLE) { + if (stat) + *stat = SCPE_OPENERR; + return port; + } + +status = sim_config_serial (port, config); /* set serial configuration */ + +if (status != SCPE_OK) { /* port configuration error? */ + sim_close_serial (port); /* close the port */ + if (stat) + *stat = status; + port = INVALID_HANDLE; /* report error */ + } + +if ((port != INVALID_HANDLE) && (*config) && (lp)) { + lp->serconfig = realloc (lp->serconfig, 1 + strlen (config)); + strcpy (lp->serconfig, config); + } if (port != INVALID_HANDLE) - _serial_add_to_open_list (port, lp, savname, savdesc); + _serial_add_to_open_list (port, lp, savname, config); + return port; } @@ -402,11 +457,51 @@ sim_close_os_serial (port); _serial_remove_from_open_list (port); } -/* Windows serial implementation */ +t_stat sim_config_serial (SERHANDLE port, const char *sconfig) +{ +const char *pptr; +char *sptr, *tptr; +SERCONFIG config = { 0 }; +t_bool arg_error = FALSE; +t_stat r; +struct open_serial_device *dev; +if ((sconfig == NULL) || (*sconfig == '\0')) + sconfig = "9600-8N1"; /* default settings */ +pptr = sconfig; + +config.baudrate = (uint32)strtotv (pptr, &sptr, 10); /* parse baud rate */ +arg_error = (pptr == sptr); /* check for bad argument */ + +if (*sptr) /* separator present? */ + sptr++; /* skip it */ + +config.charsize = (uint32)strtotv (sptr, &tptr, 10); /* parse character size */ +arg_error = arg_error || (sptr == tptr); /* check for bad argument */ + +if (*tptr) /* parity character present? */ + config.parity = toupper (*tptr++); /* save parity character */ + +config.stopbits = (uint32)strtotv (tptr, &sptr, 10); /* parse number of stop bits */ +arg_error = arg_error || (tptr == sptr); /* check for bad argument */ + +if (arg_error) /* bad conversions? */ + return SCPE_ARG; /* report argument error */ +if (strcmp (sptr, ".5") == 0) /* 1.5 stop bits requested? */ + config.stopbits = 0; /* code request */ + +r = sim_config_os_serial (port, config); +dev = _get_open_device (port); +if (dev) { + dev->line->serconfig = realloc (dev->line->serconfig, 1 + strlen (sconfig)); + strcpy (dev->line->serconfig, sconfig); + } +return r; +} #if defined (_WIN32) +/* Windows serial implementation */ /* Enumerate the available serial ports. @@ -566,7 +661,7 @@ return port; /* return port handle on 1.5 stop bits. */ -t_stat sim_config_serial (SERHANDLE port, SERCONFIG config) +t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config) { static const struct { char parity; @@ -628,20 +723,54 @@ return SCPE_OK; /* return success status /* Control a serial port. - The DTR line of the serial port is set or cleared. If "connect" is true, - then the line is set to enable the serial device. If "connect" is false, the - line is disabled to disconnect the device. If the line change was - successful, the function returns TRUE. + The DTR and RTS line of the serial port is set or cleared as indicated in + the respective bits_to_set or bits_to_clear parameters. If the + incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, + DSR and CTS are returned. + + If unreasonable or nonsense bits_to_set or bits_to_clear bits are + specified, then the return status is SCPE_ARG; + If an error occurs, SCPE_IOERR is returned. */ -t_bool sim_control_serial (SERHANDLE port, t_bool connect) +t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits) { -if (!EscapeCommFunction (port, connect ? SETDTR : CLRDTR)) { - sim_error_serial ("EscapeCommFunction", (int) GetLastError ()); - return FALSE; +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 (bits_to_set&TMXR_MDM_DTR) + if (!EscapeCommFunction (port, SETDTR)) { + sim_error_serial ("EscapeCommFunction", (int) GetLastError ()); + return SCPE_IOERR; + } +if (bits_to_clear&TMXR_MDM_DTR) + if (!EscapeCommFunction (port, CLRDTR)) { + sim_error_serial ("EscapeCommFunction", (int) GetLastError ()); + return SCPE_IOERR; + } +if (bits_to_set&TMXR_MDM_RTS) + if (!EscapeCommFunction (port, SETRTS)) { + sim_error_serial ("EscapeCommFunction", (int) GetLastError ()); + return SCPE_IOERR; + } +if (bits_to_clear&TMXR_MDM_RTS) + if (!EscapeCommFunction (port, CLRRTS)) { + sim_error_serial ("EscapeCommFunction", (int) GetLastError ()); + return SCPE_IOERR; + } +if (incoming_bits) { + DWORD ModemStat; + if (GetCommModemStatus (port, &ModemStat)) { + sim_error_serial ("GetCommModemStatus", (int) GetLastError ()); + return SCPE_IOERR; + } + *incoming_bits = ((ModemStat&MS_CTS_ON) ? TMXR_MDM_CTS : 0) | + ((ModemStat&MS_DSR_ON) ? TMXR_MDM_DSR : 0) | + ((ModemStat&MS_RING_ON) ? TMXR_MDM_RNG : 0) | + ((ModemStat&MS_RLSD_ON) ? TMXR_MDM_DCD : 0); } - -return TRUE; +return SCPE_OK; } @@ -733,11 +862,10 @@ return; +#elif defined (__unix__) || defined(__APPLE__) + /* UNIX implementation */ - -#elif defined (__unix__) - /* Enumerate the available serial ports. The serial port names generated by attempting to open /dev/ttyS0 thru @@ -772,6 +900,15 @@ for (i=0; (ports < max) && (i < 64); ++i) { close (port); } } +for (i=1; (ports < max) && (i < 64); ++i) { + sprintf (list[ports].name, "/dev/tty.serial%d", i); + port = open (list[ports].name, O_RDWR | O_NOCTTY | O_NONBLOCK); /* open the port */ + if (port != -1) { /* open OK? */ + if (isatty (port)) /* is device a TTY? */ + ++ports; + close (port); + } + } return ports; } @@ -907,7 +1044,7 @@ return port; /* return port fd for su */ -t_stat sim_config_serial (SERHANDLE port, SERCONFIG config) +t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config) { struct termios tio; int32 i; @@ -983,30 +1120,52 @@ return SCPE_OK; /* configuration set suc /* Control a serial port. - The DTR line of the serial port is set or cleared. If "connect" is true, - then the line is set to enable the serial device. If "connect" is false, the - line is disabled to disconnect the device. If the line change was - successful, the function returns TRUE. + The DTR and RTS line of the serial port is set or cleared as indicated in + the respective bits_to_set or bits_to_clear parameters. If the + incoming_bits parameter is not NULL, then the modem status bits DCD, RNG, + DSR and CTS are returned. + + If unreasonable or nonsense bits_to_set or bits_to_clear bits are + specified, then the return status is SCPE_ARG; + If an error occurs, SCPE_IOERR is returned. */ -t_bool sim_control_serial (SERHANDLE port, t_bool connect) +t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits) { -int request; -static const int dtr = TIOCM_DTR; +int bits; -if (connect) /* request for DTR set? */ - request = TIOCMBIS; /* use "set" control request */ -else /* DTR clear */ - request = TIOCMBIC; /* use "clear" control request */ - -if (ioctl (port, request, &dtr)) { /* set or clear the DTR line */ - if (errno != EINVAL) /* DTR control not supported? */ - sim_error_serial ("ioctl", errno); /* no, so report unexpected error */ - - return FALSE; /* return failure status */ +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 (bits_to_set) { + bits = ((bits_to_set&TMXR_MDM_DTR) ? TIOCM_DTR : 0) | + ((bits_to_set&TMXR_MDM_RTS) ? TIOCM_RTS : 0); + if (ioctl (port, TIOCMBIS, &bits)) { /* set the desired bits */ + sim_error_serial ("ioctl", errno); /* report unexpected error */ + return SCPE_IOERR; /* return failure status */ + } + } +if (bits_to_clear) { + bits = ((bits_to_clear&TMXR_MDM_DTR) ? TIOCM_DTR : 0) | + ((bits_to_clear&TMXR_MDM_RTS) ? TIOCM_RTS : 0); + if (ioctl (port, TIOCMBIC, &bits)) { /* clear the desired bits */ + sim_error_serial ("ioctl", errno); /* report unexpected error */ + return SCPE_IOERR; /* return failure status */ + } + } +if (incoming_bits) { + if (ioctl (port, TIOCMGET, &bits)) { /* get the modem bits */ + sim_error_serial ("ioctl", errno); /* report unexpected error */ + return SCPE_IOERR; /* return failure status */ + } + *incoming_bits = ((bits&TIOCM_CTS) ? TMXR_MDM_CTS : 0) | + ((bits&TIOCM_DSR) ? TMXR_MDM_DSR : 0) | + ((bits&TIOCM_RNG) ? TMXR_MDM_RNG : 0) | + ((bits&TIOCM_CAR) ? TMXR_MDM_DCD : 0); } -return TRUE; /* control request succeeded */ +return SCPE_OK; } @@ -1109,19 +1268,11 @@ return; } +#else /* Non-implemented stubs */ -#else - -/* Enumerate the available serial ports. - - The serial port names are extracted from the appropriate place in the - windows registry (HKLM\HARDWARE\DEVICEMAP\SERIALCOMM\). The resulting - list is sorted alphabetically by device name (COMn). The device description - is set to the OS internal name for the COM device. - -*/ +/* Enumerate the available serial ports. */ static int sim_serial_os_devices (int max, SERIAL_LIST* list) { @@ -1130,7 +1281,7 @@ return -1; /* Open a serial port */ -SERHANDLE sim_open_serial (char *name) +SERHANDLE sim_open_os_serial (char *name) { return INVALID_HANDLE; } @@ -1138,7 +1289,7 @@ return INVALID_HANDLE; /* Configure a serial port */ -t_stat sim_config_serial (SERHANDLE port, SERCONFIG config) +t_stat sim_config_os_serial (SERHANDLE port, SERCONFIG config) { return SCPE_IERR; } @@ -1146,9 +1297,9 @@ return SCPE_IERR; /* Control a serial port */ -t_bool sim_control_serial (SERHANDLE port, t_bool connect) +t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits) { -return FALSE; +return SCPE_NOFNC; } diff --git a/sim_serial.h b/sim_serial.h index e94a91e7..7eee1add 100644 --- a/sim_serial.h +++ b/sim_serial.h @@ -30,10 +30,9 @@ #ifndef _SIM_SERIAL_H_ #define _SIM_SERIAL_H_ 0 -/* Windows definitions */ - #if defined (_WIN32) +/* Windows definitions */ /* We need the basic Win32 definitions, but including "windows.h" also includes "winsock.h" as well. However, "sim_sock.h" explicitly includes "winsock2.h," @@ -52,13 +51,12 @@ typedef HANDLE SERHANDLE; #define INVALID_HANDLE INVALID_HANDLE_VALUE +#elif defined (__unix__) || defined(__APPLE__) + /* UNIX definitions */ -#elif defined (__unix__) - - #include -#include +#include #include #include @@ -67,29 +65,24 @@ typedef int SERHANDLE; #define INVALID_HANDLE -1 -/* Non-implemented definitions */ - #else +/* Non-implemented definitions */ + typedef int SERHANDLE; +#define INVALID_HANDLE -1 + #endif /* Common definitions */ -typedef struct serial_config { /* serial port configuration */ - uint32 baudrate; /* baud rate */ - uint32 charsize; /* character size in bits */ - char parity; /* parity (N/O/E/M/S) */ - uint32 stopbits; /* 0/1/2 stop bits (0 implies 1.5) */ - } SERCONFIG; - /* Global routines */ -#include "sim_tmxr.h" /* need TMLN definition */ +#include "sim_tmxr.h" /* need TMLN definition and modem definitions */ -extern SERHANDLE sim_open_serial (char *name, TMLN *lp); -extern t_stat sim_config_serial (SERHANDLE port, SERCONFIG config); -extern t_bool sim_control_serial (SERHANDLE port, t_bool connect); +extern SERHANDLE sim_open_serial (char *name, TMLN *lp, t_stat *status); +extern t_stat sim_config_serial (SERHANDLE port, const char *config); +extern t_stat sim_control_serial (SERHANDLE port, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits); extern int32 sim_read_serial (SERHANDLE port, char *buffer, int32 count, char *brk); extern int32 sim_write_serial (SERHANDLE port, char *buffer, int32 count); extern void sim_close_serial (SERHANDLE port); diff --git a/sim_sock.c b/sim_sock.c index 980070b6..9f6444d1 100644 --- a/sim_sock.c +++ b/sim_sock.c @@ -23,6 +23,8 @@ used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Robert M Supnik. + 15-Oct-12 MP Added definitions needed to detect possible tcp + connect failures 25-Sep-12 MP Reworked for RFC3493 interfaces supporting IPv6 and IPv4 22-Jun-10 RMS Fixed types in sim_accept_conn (from Mark Pizzolato) 19-Nov-05 RMS Added conditional for OpenBSD (from Federico G. Schwindt) @@ -538,7 +540,11 @@ if (err != 0) #if defined(AF_INET6) load_ws2 (); #endif /* endif AF_INET6 */ -#endif /* endif Win32 */ +#else /* Use native addrinfo APIs */ + p_getaddrinfo = (void *)getaddrinfo; + p_getnameinfo = (void *)getnameinfo; + p_freeaddrinfo = (void *)freeaddrinfo; +#endif /* endif _WIN32 */ #if defined (SIGPIPE) signal (SIGPIPE, SIG_IGN); /* no pipe signals */ #endif @@ -771,7 +777,10 @@ if (rbytes == SOCKET_ERROR) { err = WSAGetLastError (); if (err == WSAEWOULDBLOCK) /* no data */ return 0; - printf ("Sockets: read error %d\n", err); + if ((err != WSAETIMEDOUT) && /* expected errors after a connect failure */ + (err != WSAEHOSTUNREACH) && + (err != WSAECONNREFUSED)) + printf ("Sockets: read error %d\n", err); return -1; } return rbytes; diff --git a/sim_sock.h b/sim_sock.h index 250ac3c8..c4b8e25e 100644 --- a/sim_sock.h +++ b/sim_sock.h @@ -23,6 +23,8 @@ used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Robert M Supnik. + 15-Oct-12 MP Added definitions needed to detect possible tcp + connect failures 25-Sep-12 MP Reworked for RFC3493 interfaces supporting IPv6 and IPv4 04-Jun-08 RMS Addes sim_create_sock, for IBM 1130 14-Apr-05 RMS Added WSAEINPROGRESS (from Tim Riker) @@ -54,6 +56,9 @@ #define SOCKET int32 #define WSAEWOULDBLOCK EWOULDBLOCK #define WSAEINPROGRESS EINPROGRESS +#define WSAETIMEDOUT ETIMEDOUT +#define WSAECONNREFUSED ECONNREFUSED +#define WSAEHOSTUNREACH EHOSTUNREACH #define INVALID_SOCKET ((SOCKET)-1) #define SOCKET_ERROR -1 #include /* for fcntl, getpid */ diff --git a/sim_timer.c b/sim_timer.c index 963c090c..b98ce47b 100644 --- a/sim_timer.c +++ b/sim_timer.c @@ -79,8 +79,10 @@ #include "sim_defs.h" #include -t_bool sim_idle_enab = FALSE; /* global flag */ -volatile t_bool sim_idle_wait = FALSE; /* global flag */ +t_bool sim_idle_enab = FALSE; /* global flag */ +volatile t_bool sim_idle_wait = FALSE; /* global flag */ + +static int32 sim_calb_tmr = -1; /* the system calibrated timer */ static uint32 sim_idle_rate_ms = 0; static uint32 sim_os_sleep_min_ms = 0; @@ -866,3 +868,97 @@ switch (sim_throt_state) { sim_activate (uptr, sim_throt_wait); /* reschedule */ return SCPE_OK; } + +/* Instruction Execution rate. */ +/* returns a double since it is mostly used in double expressions and + to avoid overflow if/when strange timing delays might produce unexpected results */ + +double sim_timer_inst_per_sec (void) +{ +double inst_per_sec = SIM_INITIAL_IPS; + +if (sim_calb_tmr == -1) + return inst_per_sec; +inst_per_sec = ((double)rtc_currd[sim_calb_tmr])*rtc_hz[sim_calb_tmr]; +if (0 == inst_per_sec) + inst_per_sec = SIM_INITIAL_IPS; +return inst_per_sec; +} + +t_stat sim_timer_activate_after (UNIT *uptr, int32 usec_delay) +{ +int32 inst_delay; +double inst_per_sec; + +AIO_VALIDATE; +if (sim_is_active_bool (uptr)) /* already active? */ + return SCPE_OK; +inst_per_sec = sim_timer_inst_per_sec (); +inst_delay = (int32)((inst_per_sec*usec_delay)/1000000.0); +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_CLOCKS) +if ((sim_calb_tmr == -1) || /* if No timer initialized + (inst_delay < rtc_currd[sim_calb_tmr]) || /* or sooner than next clock tick? */ + (rtc_elapsed[sim_calb_tmr] < sim_idle_stable) || /* or not idle stable yet */ + (!(sim_asynch_enabled && sim_asynch_timer))) { /* or asynch disabled */ + sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after() - activating %s after %d instructions\n", + sim_uname(uptr), inst_delay); + return _sim_activate (uptr, inst_delay); /* queue it now */ + } +if (1) { + struct timespec now; + double d_now; + + clock_gettime (CLOCK_REALTIME, &now); + d_now = _timespec_to_double (&now); + /* Determine if this is a clock tick like invocation + or an ocaisional measured device delay */ + if ((uptr->a_usec_delay == usec_delay) && + (uptr->a_due_time != 0.0) && + (1)) { + double d_delay = ((double)usec_delay)/1000000.0; + + uptr->a_due_time += d_delay; + if (uptr->a_due_time < (d_now + d_delay*0.1)) { /* Accumulate lost time */ + uptr->a_skew += (d_now + d_delay*0.1) - uptr->a_due_time; + uptr->a_due_time = d_now + d_delay/10.0; + if (uptr->a_skew > 30.0) { /* Gap too big? */ + uptr->a_usec_delay = usec_delay; + uptr->a_skew = uptr->a_last_fired_time = 0.0; + uptr->a_due_time = d_now + (double)(usec_delay)/1000000.0; + } + if (uptr->a_skew > rtc_clock_skew_max[sim_calb_tmr]) + rtc_clock_skew_max[sim_calb_tmr] = uptr->a_skew; + } + else { + if (uptr->a_skew > 0.0) { /* Lost time to make up? */ + if (uptr->a_skew > d_delay*0.9) { + uptr->a_skew -= d_delay*0.9; + uptr->a_due_time -= d_delay*0.9; + } + else { + uptr->a_due_time -= uptr->a_skew; + uptr->a_skew = 0.0; + } + } + } + } + else { + uptr->a_usec_delay = usec_delay; + uptr->a_skew = uptr->a_last_fired_time = 0.0; + uptr->a_due_time = d_now + (double)(usec_delay)/1000000.0; + } + uptr->time = usec_delay; + + sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after() - queue addition %s at %.6f\n", + sim_uname(uptr), uptr->a_due_time); + } +pthread_mutex_lock (&sim_timer_lock); +sim_wallclock_entry = uptr; +pthread_mutex_unlock (&sim_timer_lock); +pthread_cond_signal (&sim_timer_wake); /* wake the timer thread to deal with it */ +return SCPE_OK; +#else +return _sim_activate (uptr, inst_delay); /* queue it now */ +#endif +} + diff --git a/sim_timer.h b/sim_timer.h index f628b2fd..166a9797 100644 --- a/sim_timer.h +++ b/sim_timer.h @@ -58,25 +58,28 @@ int clock_gettime(int clock_id, struct timespec *tp); #endif -#define SIM_NTIMERS 8 /* # timers */ -#define SIM_TMAX 500 /* max timer makeup */ +#define SIM_NTIMERS 8 /* # timers */ +#define SIM_TMAX 500 /* max timer makeup */ -#define SIM_IDLE_CAL 10 /* ms to calibrate */ -#define SIM_IDLE_MAX 10 /* max granularity idle */ -#define SIM_IDLE_STMIN 10 /* min sec for stability */ -#define SIM_IDLE_STDFLT 20 /* dft sec for stability */ -#define SIM_IDLE_STMAX 600 /* max sec for stability */ +#define SIM_INITIAL_IPS 50000 /* uncalibrated assumption */ + /* about instructions per second */ -#define SIM_THROT_WINIT 1000 /* cycles to skip */ -#define SIM_THROT_WST 10000 /* initial wait */ -#define SIM_THROT_WMUL 4 /* multiplier */ -#define SIM_THROT_WMIN 100 /* min wait */ -#define SIM_THROT_MSMIN 10 /* min for measurement */ -#define SIM_THROT_NONE 0 /* throttle parameters */ -#define SIM_THROT_MCYC 1 /* MegaCycles Per Sec */ -#define SIM_THROT_KCYC 2 /* KiloCycles Per Sec */ -#define SIM_THROT_PCT 3 /* Max Percent of host CPU */ -#define SIM_THROT_SPC 4 /* Specific periodic Delay */ +#define SIM_IDLE_CAL 10 /* ms to calibrate */ +#define SIM_IDLE_MAX 10 /* max granularity idle */ +#define SIM_IDLE_STMIN 10 /* min sec for stability */ +#define SIM_IDLE_STDFLT 20 /* dft sec for stability */ +#define SIM_IDLE_STMAX 600 /* max sec for stability */ + +#define SIM_THROT_WINIT 1000 /* cycles to skip */ +#define SIM_THROT_WST 10000 /* initial wait */ +#define SIM_THROT_WMUL 4 /* multiplier */ +#define SIM_THROT_WMIN 100 /* min wait */ +#define SIM_THROT_MSMIN 10 /* min for measurement */ +#define SIM_THROT_NONE 0 /* throttle parameters */ +#define SIM_THROT_MCYC 1 /* MegaCycles Per Sec */ +#define SIM_THROT_KCYC 2 /* KiloCycles Per Sec */ +#define SIM_THROT_PCT 3 /* Max Percent of host CPU */ +#define SIM_THROT_SPC 4 /* Specific periodic Delay */ t_bool sim_timer_init (void); void sim_timespec_diff (struct timespec *diff, struct timespec *min, struct timespec *sub); @@ -97,5 +100,7 @@ uint32 sim_os_msec (void); void sim_os_sleep (unsigned int sec); uint32 sim_os_ms_sleep (unsigned int msec); uint32 sim_os_ms_sleep_init (void); +t_stat sim_timer_activate_after (UNIT *uptr, int32 usec_delay); +double sim_timer_inst_per_sec (void); #endif diff --git a/sim_tmxr.c b/sim_tmxr.c index 62383622..283f9aac 100644 --- a/sim_tmxr.c +++ b/sim_tmxr.c @@ -26,14 +26,17 @@ Based on the original DZ11 simulator by Thord Nilson, as updated by Arthur Krewat. + 12-Oct-12 MP Revised serial port support to not require changes to + any code in TMXR library using code. Added support + for per line listener ports and outgoing tcp connections. 02-Jun-11 MP Fixed telnet option negotiation loop with some clients Added Option Negotiation and Debugging Support 17-Jan-11 MP Added Buffered line capabilities 16-Jan-11 MP Made option negotiation more reliable 20-Nov-08 RMS Added three new standardized SHOW routines - 05-Nov-08 JDB [bugfix] Moved logging call after connection check in tmxr_putc_ln - 03-Nov-08 JDB [bugfix] Added TMXR null check to tmxr_find_ldsc - 07-Oct-08 JDB [serial] Added serial port support + 05-Nov-08 JDB Moved logging call after connection check in tmxr_putc_ln + 03-Nov-08 JDB Added TMXR null check to tmxr_find_ldsc + 07-Oct-08 JDB Added initial serial port support 30-Sep-08 JDB Reverted tmxr_find_ldsc to original implementation 27-May-08 JDB Added line connection order to tmxr_poll_conn, added tmxr_set_lnorder and tmxr_show_lnorder @@ -60,224 +63,224 @@ This library includes: - tmxr_poll_conn - poll for connection - tmxr_reset_ln - reset line (drops Telnet connections only) - tmxr_clear_ln - clear line (drops Telnet and serial connections) - tmxr_getc_ln - get character for line - tmxr_poll_rx - poll receive - tmxr_putc_ln - put character for line - tmxr_poll_tx - poll transmit - tmxr_send_buffered_data - transmit buffered data - tmxr_open_master - open master connection - tmxr_close_master - close master connection - tmxr_attach - attach terminal multiplexor to listening port - tmxr_attach_line - attach line to serial port - tmxr_detach - detach terminal multiplexor to listening port - tmxr_detach_line - detach line from serial port - tmxr_line_free - return TRUE if line is disconnected - tmxr_mux_free - return TRUE if mux is disconnected - tmxr_ex - (null) examine - tmxr_dep - (null) deposit - tmxr_msg - send message to socket - tmxr_linemsg - send message to line - tmxr_fconns - output connection status - tmxr_fstats - output connection statistics - tmxr_set_log - enable logging for line - tmxr_set_nolog - disable logging for line - tmxr_show_log - show logging status for line - tmxr_dscln - disconnect line (SET routine) - tmxr_rqln - number of available characters for line - tmxr_tqln - number of buffered characters for line - tmxr_set_lnorder - set line connection order - tmxr_show_lnorder - show line connection order - tmxr_show_summ - show connection summary - tmxr_show_cstat - show line connections or status - tmxr_show_lines - show number of lines + tmxr_poll_conn - poll for connection + tmxr_reset_ln - reset line (drops Telnet/tcp and serial connections) + tmxr_getc_ln - get character for line + tmxr_poll_rx - poll receive + tmxr_putc_ln - put character for line + tmxr_poll_tx - poll transmit + tmxr_send_buffered_data - transmit buffered data + tmxr_set_modem_control_passthru - enable modem control on a multiplexer + tmxr_set_get_modem_bits - set and/or get a line modem bits + tmxr_set_config_line - set port speed, character size, parity and stop bits + tmxr_open_master - open master connection + tmxr_close_master - close master connection + tmxr_attach - attach terminal multiplexor to listening port + tmxr_detach - detach terminal multiplexor to listening port + tmxr_set_line_unit - set the unit which polls for input for a given line + tmxr_ex - (null) examine + tmxr_dep - (null) deposit + tmxr_msg - send message to socket + tmxr_linemsg - send message to line + tmxr_fconns - output connection status + tmxr_fstats - output connection statistics + tmxr_set_log - enable logging for line + tmxr_set_nolog - disable logging for line + tmxr_show_log - show logging status for line + tmxr_dscln - disconnect line (SET routine) + tmxr_rqln - number of available characters for line + tmxr_tqln - number of buffered characters for line + tmxr_set_lnorder - set line connection order + tmxr_show_lnorder - show line connection order + tmxr_show_summ - show connection summary + tmxr_show_cstat - show line connections or status + tmxr_show_lines - show number of lines + tmxr_show_open_devices - show info about all open tmxr devices All routines are OS-independent. - This library supports the simulation of multiple-line terminal multiplexers. - It may also be used to create single-line "multiplexers" to provide - additional terminals beyond the simulation console. Multiplexer lines may be - connected to terminal emulators supporting the Telnet protocol via sockets, - or to hardware terminals via host serial ports. Concurrent Telnet and serial - connections may be mixed on a given multiplexer. + This library supports the simulation of multiple-line terminal multiplexers. + It may also be used to create single-line "multiplexers" to provide + additional terminals beyond the simulation console. It may also be used to + create single-line or mutlti-line simulated synchronous (BiSync) devices. + Multiplexer lines may be connected to terminal emulators supporting the + Telnet protocol via sockets, or to hardware terminals via host serial + ports. Concurrent Telnet and serial connections may be mixed on a given + multiplexer. - When connecting via sockets, the simulated multiplexer is attached to a - listening port on the host system: + When connecting via sockets, the simulated multiplexer is attached to a + listening port on the host system: - sim> attach MUX 23 - Listening on port 23 (socket nnn) + sim> attach MUX 23 + Listening on port 23 - Once attached, the listening port must be polled for incoming connections. - When a connection attempt is received, it will be associated with the next - multiplexer line in the user-specified line order, or with the next line in - sequence if no order has been specified. Individual lines may be forcibly - disconnected either by: + Once attached, the listening port must be polled for incoming connections. + When a connection attempt is received, it will be associated with the next + multiplexer line in the user-specified line order, or with the next line in + sequence if no order has been specified. Individual lines may be connected + to serial ports or remote systems via TCP (telnet or not as desired), OR + they may have separate listening TCP ports. - sim> set MUX2 disconnect + Logging of Multiplexer Line output: + + The traffic going out multiplexer lines can be logged to files. A single + line multiplexer can log it's traffic with the following command: - or: + sim> atta MUX 23,Log=LogFileName + sim> atta MUX Connect=ser0,Log=LogFileName + + + A Multi-Line multiplexer + + Buffered Multiplexer Line: + + + Serial Port support: + + Serial ports may be specified as an operating system specific device names + or using simh generic serial names. simh generic names are of the form + serN, where N is from 0 thru one less than the maximum number of serial + ports on the local system. The mapping of simh generic port names to OS + specific names can be displayed using the following command: + + sim> show serial + Serial devices: + ser0 COM1 (\Device\Serial0) + ser1 COM3 (Winachcf0) + + sim> attach MUX Line=2,Connect=ser0 + + or equivalently + + sim> attach MUX Line=2,Connect=COM1 + + An optional configuration string may be present after the port name. If + present, it must be separated from the port name with a semicolon and has + this form: + + - + + where: + + rate = communication rate in bits per second + charsize = character size in bits (5-8, including optional parity) + parity = parity designator (N/E/O/M/S for no/even/odd/mark/space parity) + stopbits = number of stop bits (1, 1.5, or 2) + + As an example: + + 9600-8n1 + + The supported rates, sizes, and parity options are host-specific. If + a configuration string is not supplied, then the default of 9600-8N1 + is used. + + An attachment to a serial port with the '-V' switch will cause a + connection message to be output to the connected serial port. + This will help to confirm the correct port has been connected and + that the port settings are reasonable for the connected device. + This would be done as: + + sim> attach -V MUX Connect=SerN + + + Virtual Null Modem (direct wire) support: + + Direct computer to computer virtual connections may be established using + the telnet protocol or via raw tcp sockets. + + sim> attach MUX Line=2,Connect=host:port{;notelnet} + + Optional Per line tcp listening port support: + + Line specific tcp listening ports are supported. These are configured + using commands of the form: + + sim> attach MUX Line=2,port{;notelnet} + + The command syntax for a single line device (MX) is: + + sim> attach MX port{;notelnet} + sim> attach MX Connect=serN{;config} + sim> attach MX Connect=COM9{;config} + sim> attach MX Connect=host:port{;notelnet} + + The command syntax for ANY multi-line device is: + + sim> attach MX port{;notelnet} ; Defines the master listening port for the mux and optionally allows non-telnet (i.e. raw socket) operation for all lines. + sim> attach MX Line=n,port{;notelnet} ; Defines a line specific listen port for a particular line. Each line can have a separate listen port and the mux can have its own as well. Optionally disable telnet wire protocol (i.e. raw socket) + sim> attach MX Line=n,Connect=serN{;config} ; Connects line n to simh generic serial port N (port list visible with the sim> SHOW SERIAL command), the optional ";config" data specifies the speed, parity and stop bits for the connection + ; DTR (and RTS) will be raised at attach time and will drop at detach/disconnect time + sim> attach MX Line=n,Connect=host:port{;notelnet} ; Causes a connection to be established to the designated host:port. The actual connection will happen in a non-blocking fashion and will be completed and/or re-established by the normal tmxr_poll_conn activities + + All connections configured for any multiplexer device are unconfigured by: + + sim> detach MX ; detaches ALL connections/ports/sessions on the MUX. + + Console serial connections are achieved by: + + sim> set console serial=serN{;config} + or + sim> set console serial=COM2{;config} + + A line specific listening port (12366) can be specified by the following: + + sim> attach MUX Line=2,12366 + + A line specific remote telnet (or raw tcp) destination can be specified + by the following: + + sim> attach MUX Line=2,Connect=remotehost:port + + If a connection to a remotehost:port wants a raw binary data channel + (instead of a telnet session) the following would be used: + + sim> attach MUX Line=2,Connect=remotehost:port;notelnet + + A single line multiplexor can indicate any of the above line options + without specifying a line number: + + sim> attach MUX Connect=ser0;9600-8N1 + sim> attach MUX 12366 + sim> attach MUX Connect=remotehost:port + sim> attach MUX Connect=remotehost:port;notelnet + + A multiplexor can disconnect all (telnet, serial and outgoing) previous + attachments with: + + sim> detach MUX + + A device emulation may choose to implement a command interface to + disconnect specific individual lines. This would usually be done via + a Unit Modifier table entry (MTAB) which dispatches the command + "SET dev DISCONNECT[=line]" to tmxr_detach_line. sim> set MUX disconnect=2 - or the listening port and all Telnet sessions may be detached: + Full Modem Control serial port support. - sim> detach MUX + This library supports devices which wish to emulate full modem + control/signalling for serial ports. Any device emulation which wishes + to support this functionality for attached serial ports must call + "tmxr_set_modem_control_passthru" before any call to tmxr_attach. + This disables automatic DTR (&RTS) manipulation by this library. + Responsibility for manipulating DTR falls on the simulated operating + system. Calling tmxr_set_modem_control_passthru would usually be in + a device reset routine. + Once support for full modem control has been declared by a device + emulation for a particular TMXR device, the this library will make no + direct effort to manipulate modem bits while connected to serial ports. + The "tmxr_set_get_modem_bits" API exists to allow the device emulation + layer to query and control modem signals. The "tmxr_set_config_line" + API exists to allow the device emulation layer to change port settings + (baud rate, parity and stop bits). A modem_control enabled line + merely passes the VM's port status bits, data and settings through to + and from the serial port. + The "tmxr_set_get_modem_bits" and "tmxr_set_config_line" APIs will + ONLY work on a modem control enabled TMXR device. - When connecting via serial ports, individual multiplexer lines are attached - to specific host ports using port names appropriate for the host system: - - sim> attach MUX2 com1 (or /dev/ttyS0) - - or: - - sim> set MUX connect=2:com1 - - Serial port parameters may be optionally specified: - - sim> attach MUX2 com1;9600-8n1 - - If the port parameters are omitted, then the host system defaults for the - specified port are used. The port is allocated during the attach call, but - the actual connection is deferred until the multiplexer is polled for - connections. - - Individual lines may be disconnected either with: - - sim> detach MUX2 - - or: - - sim> set MUX2 disconnect - - or: - - sim> set MUX disconnect=2 - - - This library supports multiplexer device simulators that are modelled in - three possible ways: - - 1. as single-line devices (e.g., a second TTY) - - 2. as multi-line devices with a unit per line and a separate scanner unit - - 3. as multi-line devices with only a scanner unit - - Single-line devices may be attached either to a Telnet listening port or to a - serial port. The device attach routine may be passed either a port number or - a serial port name. This routine should call "tmxr_attach" first. If the - return value is SCPE_OK, then a port number was passed and was opened. If - the return value is SCPE_ARG, then a port number was not passed, and - "tmxr_attach_line" should be called. If that return value is SCPE_OK, then a - serial port name was passed and was opened. Otherwise, the attachment - failed, and the returned status code value should be reported. - - The device detach routine should call "tmxr_detach_line" first, passing 0 for - the "val" parameter. If the return value is SCPE_OK, then the attached - serial port was closed. If the return value is SCPE_UNATT, then a serial - port was not attached, and "tmxr_detach" should be called to close the Telnet - listening port. To maintain compatibility with earlier versions of this - library, "tmxr_detach" always returns SCPE_OK, regardless of whether a - listening port was attached. - - The system ATTACH and DETACH commands specify the device name, although unit - 0 is actually passed to the device attach and detach routines. The in-use - status of the multiplexer -- and therefore whether the multiplexer must be - polled for input -- may be determined by checking whether the UNIT_ATT flag - is present on unit 0. - - - Multi-line devices with a unit per line and a separate scanner unit attach - serial ports to the former and a Telnet listening port to the latter. Both - types of attachments may be made concurrently. The system ATTACH and DETACH - commands are used. - - The programmer may elect to use separate device attach routines for the lines - and the scanner or a common attach routine for both. In the latter case, if - the scanner unit is passed, "tmxr_attach" should be called. Otherwise, - "tmxr_attach_line" should be called, passing 0 as the "val" parameter. - - Similarly, either separate or common detach routines may be used. When a - line detach is intended, the detach routine should call "tmxr_detach_line" - for the specified unit. Reception on the specified line should then be - inhibited by clearing the "rcve" field. Finally, "tmxr_mux_free" should be - called to determine if the multiplexer is now free (listening port is - detached and no other serial connections exist). If it is, then the input - poll may be stopped. - - To detach the scanner, the detach routine should call "tmxr_detach". Then - "tmxr_line_free" should be called for each line, and reception on the line - should be inhibited if the routine returns TRUE. Finally, the multiplexer - poll should be stopped if the multiplexer is now free. - - The in-use status of the multiplexer cannot be determined solely by examining - the UNIT_ATT flag of the scanner unit, as that reflects only Telnet - connections. Each line must also be checked for serial connections. The - "tmxr_line_free" and "tmxr_mux_free" routines indicate respectively whether a - given line or the entire multiplexer is free. - - - Multi-line devices with only a scanner unit use the system ATTACH and DETACH - commands for the Telnet listening port. For serial ports, SET CONNECT - and SET DISCONNECT commands are used. These latter commands are - specified in the device MTAB structure and call "tmxr_attach_line" and - "tmxr_detach_line", respectively. Because MTAB processing passes the scanner - unit to these routines, the invocations pass a non-zero "val" parameter to - indicate that the unit should not be used, and that the line number should be - parsed from the command string. In this mode, "tmxr_detach_line" also serves - to disconnect Telnet sessions from lines, so no special processing or calls - to "tmxr_dscln" are required. - - In-use status of the multiplexer is determined in the same manner as the - unit-per-line case. - - - Implementation notes: - - 1. The system RESTORE command does not restore devices having the DEV_NET - flag. This flag indicates that the device employs host-specific port - names that are non-transportable across RESTOREs. - - If a multiplexer specifies DEV_NET, the device connection state will not - be altered when a RESTORE is done. That is, all current connections, - including Telnet sessions, will remain untouched, and connections - specified at the time of the SAVE will not be reestablished during the - RESTORE. If DEV_NET is not specified, then the system will attempt to - restore the attachment state present at the time of the SAVE, including - Telnet listening and serial ports. Telnet client sessions on individual - multiplexer lines cannot be reestablished by RESTORE and must be - reestablished manually. - - 2. Single-line multiplexers should have UNIT_ATTABLE on the unit - representing the line, and multi-line unit-per-line multiplexers should - not have UNIT_ATTABLE on the units representing the lines. UNIT_ATTABLE - does not affect the attachability when VM-specific attach routines are - employed. UNIT_ATTABLE does control the reporting of attached units for - the SHOW command. - - A single-line device will be either detached, attached to a listening - socket, or attached to a serial port. With UNIT_ATTABLE, the device will - be reported as "not attached," "attached to 23" (e.g.), or "attached to - COM1" (e.g.), which is desirable. - - A unit-per-line device will report the listening socket as attached to - the device (or to a separate device). The units representing lines - either will be connected to a Telnet session or attached to a serial - port. Telnet sessions are not reported by SHOW , so having - UNIT_ATTABLE present will cause each non-serial line to be reported as - "not attached," even if there may be a current Telnet connection. This - will be confusing to users. Without UNIT_ATTABLE, attachment status will - be reported only if the line is attached to a serial port, which is - preferable. - - 3. For devices without a unit per line, the MTAB entry that calls - "sim_attach_line" (e.g., CONNECT) should use the MTAB_NC flag to avoid - upper-casing the device name. Device names may be case-sensitive, - depending on the host system. */ @@ -367,7 +370,7 @@ lp->txdrp = 0; if (!lp->mp->buffered) { lp->txbfd = 0; lp->txbsz = TMXR_MAXBUF; - lp->txb = (char *)realloc(lp->txb, lp->txbsz); + lp->txb = (char *)realloc (lp->txb, lp->txbsz); } return; } @@ -375,35 +378,39 @@ return; /* Report a connection to a line. - A notification of the form: + If the indicated line (lp) is speaking the telnet wire protocol, a + notification of the form: Connected to the simulator device, line is sent to the newly connected line. If the device has only one line, the "line " part is omitted. If the device has not been defined, the " device" part is omitted. + */ -static void tmxr_report_connection (TMXR *mp, TMLN *lp, int32 i) +static void tmxr_report_connection (TMXR *mp, TMLN *lp) { int32 written, psave; char cmsg[80]; char dmsg[80] = ""; char lmsg[80] = ""; -char msgbuf[256]; +char msgbuf[256] = ""; -sprintf (cmsg, "\n\r\nConnected to the %s simulator ", sim_name); +if ((!lp->notelnet) || (sim_switches & SWMASK ('V'))) { + sprintf (cmsg, "\n\r\nConnected to the %s simulator ", sim_name); -if (mp->dptr) { /* device defined? */ - sprintf (dmsg, "%s device", /* report device name */ - sim_dname (mp->dptr)); + if (mp->dptr) { /* device defined? */ + sprintf (dmsg, "%s device", /* report device name */ + sim_dname (mp->dptr)); - if (mp->lines > 1) /* more than one line? */ - sprintf (lmsg, ", line %d", i); /* report the line number */ + if (mp->lines > 1) /* more than one line? */ + sprintf (lmsg, ", line %d", (int)(lp-mp->ldsc));/* report the line number */ + } + + sprintf (msgbuf, "%s%s%s\r\n\n", cmsg, dmsg, lmsg); } -sprintf (msgbuf, "%s%s%s\r\n\n", cmsg, dmsg, lmsg); - if (!mp->buffered) { lp->txbpi = 0; /* init buf pointers */ lp->txbpr = (int32)(lp->txbsz - strlen (msgbuf)); @@ -442,6 +449,8 @@ return; static void tmxr_report_disconnection (TMLN *lp) { +if (lp->notelnet) + return; tmxr_linemsg (lp, "\r\nDisconnected from the "); /* report disconnection */ tmxr_linemsg (lp, sim_name); tmxr_linemsg (lp, " simulator\r\n\n"); @@ -537,13 +546,14 @@ static TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp) { if (mp == NULL) /* invalid multiplexer descriptor? */ return NULL; /* programming error! */ - if (uptr) { /* called from SET? */ DEVICE *dptr = find_dev_from_unit (uptr); /* find device */ - if (dptr == NULL) return NULL; /* what?? */ + if (dptr == NULL) /* what?? */ + return NULL; val = (int32) (uptr - dptr->units); /* implicit line # */ } -if ((val < 0) || (val >= mp->lines)) return NULL; /* invalid line? */ +if ((val < 0) || (val >= mp->lines)) /* invalid line? */ + return NULL; return mp->ldsc + val; /* line descriptor */ } @@ -596,6 +606,70 @@ if (status) /* return value pointer return lp; /* return pointer to line descriptor */ } +/* Generate the Attach string which will fully configure the multiplexer + + Inputs: + old = pointer to the original configuration string which will be replaced + *mp = pointer to multiplexer + + Output: + a complete attach string for the current state of the multiplexer + +*/ +static char *growstring(char **string, size_t growth) +{ +*string = (char *)realloc (*string, 1 + (*string ? strlen (*string) : 0) + growth); +return *string + strlen(*string); +} + +static char *_mux_attach_string(char *old, TMXR *mp) +{ +char* tptr = NULL; +int32 i; +TMLN *lp; + +free (old); +tptr = (char *) calloc (1, 1); + +if (tptr == NULL) /* no more mem? */ + return tptr; + +if (mp->port) /* copy port */ + sprintf (growstring(&tptr, 13 + strlen (mp->port)), "%s%s", mp->port, mp->notelnet ? ";notelnet" : ""); +if (mp->buffered) + sprintf (growstring(&tptr, 32), ",Buffered=%d", mp->buffered); +if (mp->logfiletmpl[0]) /* logfile info */ + sprintf (growstring(&tptr, 7 + strlen (mp->logfiletmpl)), ",Log=%s", mp->logfiletmpl); +while ((*tptr == ',') || (*tptr == ' ')) + strcpy(tptr, tptr+1); +for (i=0; ilines; ++i) { + lp = mp->ldsc + i; + if (lp->destination || lp->port) { + if (mp->lines > 1) + sprintf (growstring(&tptr, 32), "%sLine=%d", *tptr ? ",," : "", i); + else + sprintf (growstring(&tptr, 32), "%s", *tptr ? "," : ""); + if (lp->destination) { + if (lp->serport) { + char portname[CBUFSIZE]; + + get_glyph_nc (lp->destination, portname, ';'); + sprintf (growstring(&tptr, 25 + strlen (lp->destination)), ",Connect=%s%s%s", portname, strcmp("9600-8N1", lp->serconfig) ? ";" : "", strcmp("9600-8N1", lp->serconfig) ? lp->serconfig : ""); + } + else + sprintf (growstring(&tptr, 18 + strlen (lp->destination)), ",Connect=%s%s", lp->destination, lp->notelnet ? ";notelnet" : ""); + } + if (lp->port) + sprintf (growstring(&tptr, 12 + strlen (lp->port)), ",%s%s", lp->port, lp->notelnet ? ";notelnet" : ""); + } + } +if (*tptr == '\0') { + free (tptr); + tptr = NULL; + } +return tptr; +} + /* Global routines */ @@ -614,18 +688,6 @@ return lp; /* return pointer to lin not -1 (indicating default order), then the order array is used to find an open line. Otherwise, a search is made of all lines in numerical sequence. - Implementation notes: - - 1. When a serial port is attached to a line, the connection is made pending - until we are called to poll for new connections. This is because - multiplexer service routines recognize new connections only as a result - of calls to this routine. - - 2. A pending serial (re)connection may also be deferred. This is needed - when a line clear drops DTR, as DTR must remain low for a period of time - in order to be recognized by the serial device. If the "cnms" value - specifies a time in the future, the connection is deferred until that - time is reached. This leaves DTR low for the necessary time. */ int32 tmxr_poll_conn (TMXR *mp) @@ -634,9 +696,8 @@ SOCKET newsock; TMLN *lp; int32 *op; int32 i, j; -uint32 current_time; char *address; -t_bool deferrals; +uint32 poll_time = sim_os_msec (); static char mantra[] = { TN_IAC, TN_WILL, TN_LINE, TN_IAC, TN_WILL, TN_SGA, @@ -645,33 +706,12 @@ static char mantra[] = { TN_IAC, TN_DO, TN_BIN }; -/* Check for a pending serial connection */ +tmxr_debug_trace (mp, "tmxr_poll_conn()"); -if (mp->pending) { /* is there a pending serial connection? */ - current_time = sim_os_msec (); /* get the current time */ - deferrals = FALSE; /* assume no deferrals */ +if ((poll_time - mp->last_poll_time) < TMXR_CONNECT_POLL_INTERVAL) + return -1; /* */ - for (i = 0; i < mp->lines; i++) { /* check each line in sequence */ - lp = mp->ldsc + i; /* get pointer to line descriptor */ - - if ((lp->serport != 0) && (lp->conn == 0)) /* have handle but no connection? */ - if (current_time < lp->cnms) /* time to connect hasn't arrived? */ - deferrals = TRUE; /* note the deferral */ - - else { /* line is ready to connect */ - tmxr_init_line (lp); /* init the line state */ - sim_control_serial (lp->serport, TRUE); /* connect line by raising DTR */ - lp->conn = 1; /* mark as connected */ - lp->cnms = current_time; /* record time of connection */ - tmxr_report_connection (mp, lp, i); /* report the connection to the line */ - mp->pending = mp->pending - 1; /* drop the pending count */ - return i; /* return the line number */ - } - } - - if (deferrals == FALSE) /* any deferred connections? */ - mp->pending = 0; /* no, and none pending, so correct count */ - } +mp->last_poll_time = poll_time; /* Check for a pending Telnet connection */ @@ -680,6 +720,7 @@ newsock = sim_accept_conn (mp->master, &address); /* poll connect */ if (newsock != INVALID_SOCKET) { /* got a live one? */ op = mp->lnorder; /* get line connection order list pointer */ i = mp->lines; /* play it safe in case lines == 0 */ + ++mp->sessions; /* count the new session */ for (j = 0; j < mp->lines; j++, i++) { /* find next avail line */ if (op && (*op >= 0) && (*op < mp->lines)) /* order list present and valid? */ @@ -688,85 +729,217 @@ if (newsock != INVALID_SOCKET) { /* got a live one? */ i = j; /* get next sequential line */ lp = mp->ldsc + i; /* get pointer to line descriptor */ - if (lp->conn == 0) /* is the line available? */ + if ((lp->conn == 0) && /* is the line available? */ + (lp->destination == NULL) && + (lp->master == 0)) break; /* yes, so stop search */ } if (i >= mp->lines) { /* all busy? */ tmxr_msg (newsock, "All connections busy\r\n"); sim_close_sock (newsock, 0); + free (address); } else { lp = mp->ldsc + i; /* get line desc */ tmxr_init_line (lp); /* init line */ lp->conn = newsock; /* record connection */ lp->ipad = address; /* ip address */ - sim_write_sock (newsock, mantra, sizeof(mantra)); - tmxr_debug (TMXR_DBG_XMT, lp, "Sending", mantra, sizeof(mantra)); - tmxr_report_connection (mp, lp, i); + lp->notelnet = mp->notelnet; /* apply mux default telnet setting */ + if (!lp->notelnet) { + sim_write_sock (newsock, mantra, sizeof(mantra)); + tmxr_debug (TMXR_DBG_XMT, lp, "Sending", mantra, sizeof(mantra)); + } + tmxr_report_connection (mp, lp); lp->cnms = sim_os_msec (); /* time of conn */ return i; } } /* end if newsock */ + +/* Look for per line listeners or outbound connecting sockets */ +for (i = 0; i < mp->lines; i++) { /* check each line in sequence */ + lp = mp->ldsc + i; /* get pointer to line descriptor */ + + if (0 == lp->master) { /* not listening? */ + if (lp->connecting) { /* connecting? */ + switch (sim_check_conn(lp->connecting, FALSE)) + { + case 1: /* successful connection */ + lp->conn = lp->connecting; /* it now looks normal */ + lp->connecting = 0; + lp->cnms = sim_os_msec (); + break; + case -1: /* failed connection */ + tmxr_reset_ln (lp); /* retry */ + break; + } + } + continue; /* skip this line */ + } + + /* Check for a pending Telnet connection */ + + newsock = sim_accept_conn (lp->master, &address); /* poll connect */ + + if (newsock != INVALID_SOCKET) { /* got a live one? */ + ++mp->sessions; /* count the new session */ + + if (lp->conn == 0) { /* is the line available? */ + tmxr_init_line (lp); /* init line */ + lp->conn = newsock; /* record connection */ + lp->ipad = address; /* ip address */ + if (!lp->notelnet) { + sim_write_sock (newsock, mantra, sizeof(mantra)); + tmxr_debug (TMXR_DBG_XMT, lp, "Sending", mantra, sizeof(mantra)); + } + tmxr_report_connection (mp, lp); + lp->cnms = sim_os_msec (); /* time of conn */ + return i; + } + else { + tmxr_msg (newsock, "Line connection busy\r\n"); + sim_close_sock (newsock, 0); + free (address); + } + } + } + return -1; /* no new connections made */ } /* Reset a line. - A Telnet session associated with line descriptor "lp" is disconnected, and - the socket is deallocated. If the line has a serial connection instead, then - no action is taken. - - This routine is provided for backward compatibility. Use "tmxr_clear_ln" in - new code to disconnect both Telnet and serial connections. + The telnet/tcp or serial session associated with multiplexer descriptor "mp" and + line descriptor "lp" is disconnected. An associated tcp socket is + deallocated; a serial port is not, although for non modem control serial lines + DTR is dropped and raised again after 500ms to signal the attached serial device. */ -void tmxr_reset_ln (TMLN *lp) +t_stat tmxr_reset_ln (TMLN *lp) { -if (lp->txlog) /* dump log */ - fflush (lp->txlog); -tmxr_send_buffered_data (lp); /* send buffered data */ - -if (!lp->serport) { /* Telnet connection? */ - sim_close_sock (lp->conn, 0); /* close socket */ - tmxr_init_line (lp); /* initialize line state */ - lp->conn = 0; /* remove socket */ - } -return; -} - -/* Clear a line connection. - - The Telnet or serial session associated with multiplexer descriptor "mp" and - line descriptor "lp" is disconnected. An associated Telnet socket is - deallocated; a serial port is not, although DTR is dropped to disconnect the - attached serial device. Serial lines will be scheduled for reconnection - after a short delay for DTR recognition. -*/ - -t_stat tmxr_clear_ln (TMXR *mp, TMLN *lp) -{ -if ((mp == NULL) || (lp == NULL)) /* no multiplexer or line descriptors? */ - return SCPE_IERR; /* programming error! */ +tmxr_debug_trace_line (lp, "tmxr_reset_ln()"); if (lp->txlog) /* logging? */ fflush (lp->txlog); /* flush log */ tmxr_send_buffered_data (lp); /* send any buffered data */ -if (lp->serport) { /* serial connection? */ - sim_control_serial (lp->serport, FALSE); /* disconnect line by dropping DTR */ - lp->cnms = sim_os_msec () + 500; /* reconnect 500 msec from now */ - mp->pending = mp->pending + 1; /* mark line reconnection as pending */ +if ((lp->serport) && (!lp->mp->modem_control)) { /* serial connection? */ + sim_control_serial (lp->serport, 0, TMXR_MDM_DTR|TMXR_MDM_RTS, NULL);/* drop DTR and RTS */ + sim_os_ms_sleep (TMXR_DTR_DROP_TIME); + sim_control_serial (lp->serport, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL);/* raise DTR and RTS */ } else /* Telnet connection */ - sim_close_sock (lp->conn, 0); /* close socket */ - -tmxr_init_line (lp); /* initialize line state */ -lp->conn = 0; /* remove socket or connection flag */ + if (lp->conn) { + sim_close_sock (lp->conn, 0); /* close socket */ + lp->conn = 0; + } +free(lp->ipad); +lp->ipad = NULL; +if ((lp->destination) && (!lp->serport)) { + if (lp->connecting) + sim_close_sock (lp->connecting, 0); + lp->connecting = sim_connect_sock (lp->destination, "localhost", NULL); + lp->ipad = malloc (1 + strlen (lp->destination)); + strcpy (lp->ipad, lp->destination); + lp->cnms = sim_os_msec (); + } +else { + tmxr_init_line (lp); /* initialize line state */ + lp->conn = 0; /* remove socket or connection flag */ + } return SCPE_OK; } +/* Enable modem control pass thru + + Inputs: + none + + Output: + none + + Implementation note: + + 1 Calling this API disables any actions on the part of this + library to directly manipulate DTR (&RTS) on serial ports. + + 2 Calling this API enables the tmxr_set_get_modem_bits and + tmxr_set_config_line APIs. + + +*/ +t_stat tmxr_set_modem_control_passthru (TMXR *mp) +{ +mp->modem_control = TRUE; +return SCPE_OK; +} + +/* Manipulate the modem control bits of a specific line + + Inputs: + *lp = pointer to terminal line descriptor + bits_to_set TMXR_MDM_DTR and/or TMXR_MDM_RTS as desired + bits_to_clear TMXR_MDM_DTR and/or TMXR_MDM_RTS as desired + + Output: + incoming_bits if non NULL, returns the current stat of DCD, + RNG, CTS and DSR + + Implementation note: + + If a line is connected to a serial port, then these valus 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. +*/ +t_stat tmxr_set_get_modem_bits (TMLN *lp, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits) +{ +t_stat r = SCPE_OK; + +tmxr_debug_trace_line (lp, "tmxr_set_get_modem_bits()"); + +if (!lp->mp->modem_control) /* This API ONLY works on modem_control enabled multiplexers */ + return SCPE_IERR; +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->serport) + return sim_control_serial (lp->serport, bits_to_set, bits_to_clear, incoming_bits); +if (lp->conn) { + if (bits_to_clear&TMXR_MDM_DTR) /* drop DTR? */ + tmxr_reset_ln (lp); + } +if (incoming_bits) { + if (lp->conn) + *incoming_bits = TMXR_MDM_DCD | TMXR_MDM_CTS | TMXR_MDM_DSR; + else + *incoming_bits = lp->mp->master ? (TMXR_MDM_CTS | TMXR_MDM_DSR) : 0; + } +return r; +} + +t_stat tmxr_set_config_line (TMLN *lp, char *config) +{ +t_stat r; + +tmxr_debug_trace_line (lp, "tmxr_set_config_line()"); +if (!lp->mp->modem_control) /* This API ONLY works on modem_control enabled multiplexers */ + return SCPE_IERR; +if (lp->serport) + r = sim_config_serial (lp->serport, config); +else { + lp->serconfig = (char *)realloc (lp->serconfig, 1 + strlen (config)); + strcpy (lp->serconfig, config); + r = SCPE_OK; + } +if (r == SCPE_OK) /* Record port state for proper restore */ + lp->mp->uptr->filename = _mux_attach_string (lp->mp->uptr->filename, lp->mp); +return r; +} + + /* Get character from specific line Inputs: @@ -786,7 +959,8 @@ int32 tmxr_getc_ln (TMLN *lp) int32 j, val = 0; uint32 tmp; -if (lp->conn && lp->rcve) { /* conn & enb? */ +tmxr_debug_trace_line (lp, "tmxr_getc_ln()"); +if ((lp->conn || lp->serport) && lp->rcve) { /* conn & enb? */ j = lp->rxbpi - lp->rxbpr; /* # input chrs */ if (j) { /* any? */ tmp = lp->rxb[lp->rxbpr]; /* get char */ @@ -816,9 +990,10 @@ void tmxr_poll_rx (TMXR *mp) int32 i, nbytes, j; 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->conn || !lp->rcve) /* skip if !conn */ + if (!(lp->conn || lp->serport) || !lp->rcve) /* skip if not connected */ continue; nbytes = 0; @@ -830,7 +1005,7 @@ for (i = 0; i < mp->lines; i++) { /* loop thru lines */ TMXR_MAXBUF - lp->rxbpi); if (nbytes < 0) /* line error? */ - tmxr_clear_ln (mp, lp); /* disconnect line */ + tmxr_reset_ln (lp); /* disconnect line */ else if (nbytes > 0) { /* if data rcvd */ @@ -840,105 +1015,103 @@ for (i = 0; i < mp->lines; i++) { /* loop thru lines */ lp->rxbpi = lp->rxbpi + nbytes; /* adv pointers */ lp->rxcnt = lp->rxcnt + nbytes; - if (lp->serport) /* is this a serial reception? */ - continue; /* yes, so no further processing needed */ - /* Examine new data, remove TELNET cruft before making input available */ - for (; j < lp->rxbpi; ) { /* loop thru char */ - signed char tmp = lp->rxb[j]; /* get char */ - switch (lp->tsta) { /* case tlnt state */ + if (!lp->notelnet) /* Are we looking for telnet interpretation? */ + for (; j < lp->rxbpi; ) { /* loop thru char */ + signed char tmp = lp->rxb[j]; /* get char */ + switch (lp->tsta) { /* case tlnt state */ - case TNS_NORM: /* normal */ - if (tmp == TN_IAC) { /* IAC? */ - lp->tsta = TNS_IAC; /* change state */ + case TNS_NORM: /* normal */ + if (tmp == TN_IAC) { /* IAC? */ + lp->tsta = TNS_IAC; /* change state */ + tmxr_rmvrc (lp, j); /* remove char */ + break; + } + if ((tmp == TN_CR) && lp->dstb) /* CR, no bin */ + lp->tsta = TNS_CRPAD; /* skip pad char */ + j = j + 1; /* advance j */ + break; + + case TNS_IAC: /* IAC prev */ + if (tmp == TN_IAC) { /* IAC + IAC */ + lp->tsta = TNS_NORM; /* treat as normal */ + j = j + 1; /* advance j */ + break; /* keep IAC */ + } + if (tmp == TN_BRK) { /* IAC + BRK? */ + lp->tsta = TNS_NORM; /* treat as normal */ + lp->rxb[j] = 0; /* char is null */ + lp->rbr[j] = 1; /* flag break */ + j = j + 1; /* advance j */ + break; + } + switch (tmp) { + case TN_WILL: /* IAC + WILL? */ + lp->tsta = TNS_WILL; + break; + case TN_WONT: /* IAC + WONT? */ + lp->tsta = TNS_WONT; + break; + case TN_DO: /* IAC + DO? */ + lp->tsta = TNS_DO; + break; + case TN_DONT: /* IAC + DONT? */ + lp->tsta = TNS_SKIP; /* IAC + other */ + break; + case TN_GA: case TN_EL: /* IAC + other 2 byte types */ + case TN_EC: case TN_AYT: + case TN_AO: case TN_IP: + case TN_NOP: + lp->tsta = TNS_NORM; /* ignore */ + break; + case TN_SB: /* IAC + SB sub-opt negotiation */ + case TN_DATAMK: /* IAC + data mark */ + case TN_SE: /* IAC + SE sub-opt end */ + lp->tsta = TNS_NORM; /* ignore */ + break; + } tmxr_rmvrc (lp, j); /* remove char */ break; - } - if ((tmp == TN_CR) && lp->dstb) /* CR, no bin */ - lp->tsta = TNS_CRPAD; /* skip pad char */ - j = j + 1; /* advance j */ - break; - case TNS_IAC: /* IAC prev */ - if (tmp == TN_IAC) { /* IAC + IAC */ - lp->tsta = TNS_NORM; /* treat as normal */ - j = j + 1; /* advance j */ - break; /* keep IAC */ - } - if (tmp == TN_BRK) { /* IAC + BRK? */ - lp->tsta = TNS_NORM; /* treat as normal */ - lp->rxb[j] = 0; /* char is null */ - lp->rbr[j] = 1; /* flag break */ - j = j + 1; /* advance j */ - break; - } - switch (tmp) { - case TN_WILL: /* IAC + WILL? */ - lp->tsta = TNS_WILL; - break; - case TN_WONT: /* IAC + WONT? */ - lp->tsta = TNS_WONT; - break; - case TN_DO: /* IAC + DO? */ - lp->tsta = TNS_DO; - break; - case TN_DONT: /* IAC + DONT? */ - lp->tsta = TNS_SKIP; /* IAC + other */ - break; - case TN_GA: case TN_EL: /* IAC + other 2 byte types */ - case TN_EC: case TN_AYT: - case TN_AO: case TN_IP: - case TN_NOP: - lp->tsta = TNS_NORM; /* ignore */ - break; - case TN_SB: /* IAC + SB sub-opt negotiation */ - case TN_DATAMK: /* IAC + data mark */ - case TN_SE: /* IAC + SE sub-opt end */ - lp->tsta = TNS_NORM; /* ignore */ - break; - } - tmxr_rmvrc (lp, j); /* remove char */ - break; - - case TNS_WILL: case TNS_WONT: /* IAC+WILL/WONT prev */ - if (tmp == TN_BIN) { /* BIN? */ - if (lp->tsta == TNS_WILL) - lp->dstb = 0; - else lp->dstb = 1; - } - tmxr_rmvrc (lp, j); /* remove it */ - lp->tsta = TNS_NORM; /* next normal */ - break; - - /* Negotiation with the HP terminal emulator "QCTerm" is not working. - QCTerm says "WONT BIN" but sends bare CRs. RFC 854 says: - - Note that "CR LF" or "CR NUL" is required in both directions - (in the default ASCII mode), to preserve the symmetry of the - NVT model. ...The protocol requires that a NUL be inserted - following a CR not followed by a LF in the data stream. - - Until full negotiation is implemented, we work around the problem - by checking the character following the CR in non-BIN mode and - strip it only if it is LF or NUL. This should not affect - conforming clients. - */ - - case TNS_CRPAD: /* only LF or NUL should follow CR */ - lp->tsta = TNS_NORM; /* next normal */ - if ((tmp == TN_LF) || /* CR + LF ? */ - (tmp == TN_NUL)) /* CR + NUL? */ + case TNS_WILL: case TNS_WONT: /* IAC+WILL/WONT prev */ + if (tmp == TN_BIN) { /* BIN? */ + if (lp->tsta == TNS_WILL) + lp->dstb = 0; + else lp->dstb = 1; + } tmxr_rmvrc (lp, j); /* remove it */ - break; + lp->tsta = TNS_NORM; /* next normal */ + break; - case TNS_DO: /* pending DO request */ - case TNS_SKIP: default: /* skip char */ - tmxr_rmvrc (lp, j); /* remove char */ - lp->tsta = TNS_NORM; /* next normal */ - break; - } /* end case state */ - } /* end for char */ + /* Negotiation with the HP terminal emulator "QCTerm" is not working. + QCTerm says "WONT BIN" but sends bare CRs. RFC 854 says: + + Note that "CR LF" or "CR NUL" is required in both directions + (in the default ASCII mode), to preserve the symmetry of the + NVT model. ...The protocol requires that a NUL be inserted + following a CR not followed by a LF in the data stream. + + Until full negotiation is implemented, we work around the problem + by checking the character following the CR in non-BIN mode and + strip it only if it is LF or NUL. This should not affect + conforming clients. + */ + + case TNS_CRPAD: /* only LF or NUL should follow CR */ + lp->tsta = TNS_NORM; /* next normal */ + if ((tmp == TN_LF) || /* CR + LF ? */ + (tmp == TN_NUL)) /* CR + NUL? */ + tmxr_rmvrc (lp, j); /* remove it */ + break; + + case TNS_DO: /* pending DO request */ + case TNS_SKIP: default: /* skip char */ + tmxr_rmvrc (lp, j); /* remove char */ + lp->tsta = TNS_NORM; /* next normal */ + break; + } /* end case state */ + } /* end for char */ if (nbytes != (lp->rxbpi-lp->rxbpr)) tmxr_debug (TMXR_DBG_RCV, lp, "Remaining", &(lp->rxb[lp->rxbpi]), lp->rxbpi-lp->rxbpr); } /* end else nbytes */ @@ -970,22 +1143,20 @@ return (lp->rxbpi - lp->rxbpr + ((lp->rxbpi < lp->rxbpr)? TMXR_MAXBUF: 0)); Implementation note: - 1. If the line is not connected, SCPE_LOST is returned. For serial - connections, this may also occur when the connection is pending, either - before the first "tmxr_poll_conn" call, or during a DTR drop deferral. + 1. If the line is not connected, SCPE_LOST is returned. */ t_stat tmxr_putc_ln (TMLN *lp, int32 chr) { +if ((lp->conn == 0) && /* no conn & not buffered? */ + (lp->serport == 0) && + (!lp->txbfd)) { + ++lp->txdrp; /* lost */ + return SCPE_LOST; + } if (lp->txlog) /* log if available */ fputc (chr, lp->txlog); -if ((lp->conn == 0) && (!lp->txbfd)) /* no conn & not buffered? */ - if (lp->txlog) /* if it was logged, we got it */ - return SCPE_OK; - else { - ++lp->txdrp; /* lost */ - return SCPE_LOST; - } +tmxr_debug_trace_line (lp, "tmxr_putc_ln()"); #define TXBUF_AVAIL(lp) (lp->txbsz - tmxr_tqln (lp)) #define TXBUF_CHAR(lp, c) { \ lp->txb[lp->txbpi++] = (char)(c); \ @@ -994,7 +1165,7 @@ if ((lp->conn == 0) && (!lp->txbfd)) /* no conn & not buffere lp->txbpr = (1+lp->txbpr)%lp->txbsz, ++lp->txdrp; \ } if ((lp->txbfd) || (TXBUF_AVAIL(lp) > 1)) { /* room for char (+ IAC)? */ - if (TN_IAC == (char) chr) /* char == IAC ? */ + if ((TN_IAC == (char) chr) && (!lp->notelnet)) /* char == IAC in telnet session? */ TXBUF_CHAR (lp, TN_IAC); /* stuff extra IAC char */ TXBUF_CHAR (lp, chr); /* buffer char & adv pointer */ if ((!lp->txbfd) && (TXBUF_AVAIL (lp) <= TMXR_GUARD))/* near full? */ @@ -1005,7 +1176,6 @@ if ((lp->txbfd) || (TXBUF_AVAIL(lp) > 1)) { /* room for char (+ IAC) return SCPE_STALL; /* char not sent */ } - /* Poll for output Inputs: @@ -1019,14 +1189,15 @@ void tmxr_poll_tx (TMXR *mp) int32 i, nbytes; TMLN *lp; +tmxr_debug_trace (mp, "tmxr_poll_tx()"); for (i = 0; i < mp->lines; i++) { /* loop thru lines */ lp = mp->ldsc + i; /* get line desc */ - if (lp->conn == 0) /* skip if !conn */ + if ((lp->conn == 0) && (lp->serport == 0)) /* skip if !conn */ continue; - nbytes = tmxr_send_buffered_data (lp); /* buffered bytes */ - if (nbytes == 0) /* buf empty? enab line */ - lp->xmte = 1; - } /* end for */ + nbytes = tmxr_send_buffered_data (lp); /* buffered bytes */ + if (nbytes == 0) /* buf empty? enab line */ + lp->xmte = 1; + } /* end for */ return; } @@ -1043,6 +1214,7 @@ int32 tmxr_send_buffered_data (TMLN *lp) { int32 nbytes, sbytes; +tmxr_debug_trace_line (lp, "tmxr_send_buffered_data()"); nbytes = tmxr_tqln(lp); /* avail bytes */ if (nbytes) { /* >0? write */ if (lp->txbpr < lp->txbpi) /* no wrap? */ @@ -1083,87 +1255,283 @@ return (lp->txbpi - lp->txbpr + ((lp->txbpi < lp->txbpr)? lp->txbsz: 0)); } -/* Open a master listening socket. +/* Open a master listening socket (and all of the other variances of connections). A listening socket for the port number described by "cptr" is opened for the multiplexer associated with descriptor "mp". If the open is successful, all - lines not currently possessing serial connections are initialized for Telnet - connections. + lines not currently otherwise connected (via serial, outgoing or direct + listener) are initialized for Telnet connections. + + Initialization for all connection styles (MUX wide listener, per line serial, + listener, outgoing, logging, buffering) are handled by this routine. + */ t_stat tmxr_open_master (TMXR *mp, char *cptr) { -int32 i; +int32 i, line = -1; +char tbuf[CBUFSIZE], listen[CBUFSIZE], destination[CBUFSIZE], + logfiletmpl[CBUFSIZE], buffered[CBUFSIZE], hostport[CBUFSIZE], + port[CBUFSIZE], option[CBUFSIZE]; SOCKET sock; +SERHANDLE serport; +char *tptr = cptr; +t_bool nolog, notelnet, listennotelnet, unbuffered; TMLN *lp; -t_stat r; +t_stat r = SCPE_ARG; -if (!isdigit(*cptr)) { - char gbuf[CBUFSIZE]; - char *init_cptr = cptr; +tmxr_debug_trace (mp, "tmxr_open_master()"); +while (*tptr) { + line = -1; + memset(logfiletmpl, '\0', sizeof(logfiletmpl)); + memset(listen, '\0', sizeof(listen)); + memset(destination, '\0', sizeof(destination)); + memset(buffered, '\0', sizeof(buffered)); + memset(port, '\0', sizeof(port)); + memset(option, '\0', sizeof(option)); + nolog = notelnet = listennotelnet = unbuffered = FALSE; + while (*tptr) { + tptr = get_glyph_nc (tptr, tbuf, ','); + if (!tbuf[0]) + break; + cptr = tbuf; + if (!isdigit(*cptr)) { + char gbuf[CBUFSIZE]; + char *init_cptr = cptr; - cptr = get_glyph (cptr, gbuf, '='); - if (0 == MATCH_CMD (gbuf, "LOG")) { - if ((NULL == cptr) || ('\0' == *cptr)) - return SCPE_2FARG; - strncpy(mp->logfiletmpl, cptr, sizeof(mp->logfiletmpl)-1); - for (i = 0; i < mp->lines; i++) { - lp = mp->ldsc + i; + cptr = get_glyph (cptr, gbuf, '='); + if (0 == MATCH_CMD (gbuf, "LINE")) { + if ((NULL == cptr) || ('\0' == *cptr)) + return SCPE_ARG; + line = (int32) get_uint (cptr, 10, mp->lines, &r); + if (r != SCPE_OK) + return SCPE_ARG; + continue; + } + if (0 == MATCH_CMD (gbuf, "LOG")) { + if ((NULL == cptr) || ('\0' == *cptr)) + return SCPE_2FARG; + strncpy(logfiletmpl, cptr, sizeof(logfiletmpl)-1); + continue; + } + if ((0 == MATCH_CMD (gbuf, "NOBUFFERED")) || + (0 == MATCH_CMD (gbuf, "UNBUFFERED"))) { + if ((NULL != cptr) && ('\0' != *cptr)) + return SCPE_2MARG; + unbuffered = TRUE; + continue; + } + if (0 == MATCH_CMD (gbuf, "BUFFERED")) { + if ((NULL == cptr) || ('\0' == *cptr)) + strcpy(buffered, "32768"); + else { + i = (int32) get_uint (cptr, 10, 1024*1024, &r); + if ((r != SCPE_OK) || (i == 0)) + return SCPE_ARG; + sprintf(buffered, "%d", i); + } + continue; + } + if (0 == MATCH_CMD (gbuf, "NOLOG")) { + if ((NULL != cptr) && ('\0' != *cptr)) + return SCPE_2MARG; + nolog = TRUE; + continue; + } + if (0 == MATCH_CMD (gbuf, "CONNECT")) { + if ((NULL == cptr) || ('\0' == *cptr)) + return SCPE_ARG; + serport = sim_open_serial (cptr, NULL, &r); + if (serport != INVALID_HANDLE) { + sim_close_serial (serport); + if (strchr (cptr, ';') && mp->modem_control) + return SCPE_ARG; + } + else { + memset (hostport, '\0', sizeof(hostport)); + strncpy (hostport, cptr, sizeof(hostport)-1); + if ((cptr = strchr (hostport, ';'))) + *(cptr++) = '\0'; + sock = sim_connect_sock (hostport, "localhost", NULL); + if (sock != INVALID_SOCKET) + sim_close_sock (sock, 0); + else + return SCPE_ARG; + if (cptr && (0 == MATCH_CMD (cptr, "NOTELNET"))) + notelnet = TRUE; + cptr = hostport; + } + strcpy(destination, cptr); + continue; + } + cptr = get_glyph (gbuf, port, ';'); + if (SCPE_OK != sim_parse_addr (port, NULL, 0, NULL, NULL, 0, NULL)) + return SCPE_ARG; + if (cptr && (0 == MATCH_CMD (cptr, "NOTELNET"))) + listennotelnet = TRUE; + cptr = init_cptr; + } + cptr = get_glyph_nc (cptr, port, ';'); + sock = sim_master_sock (port, &r); /* make master socket */ + if (r != SCPE_OK) + return r; + if (sock == INVALID_SOCKET) /* open error */ + return SCPE_OPENERR; + sim_close_sock (sock, 1); + strcpy(listen, port); + cptr = get_glyph (cptr, option, ';'); + if (cptr && (0 == MATCH_CMD (option, "NOTELNET"))) + listennotelnet = TRUE; + } + if (line == -1) { + if (logfiletmpl[0]) { + strncpy(mp->logfiletmpl, logfiletmpl, sizeof(mp->logfiletmpl)-1); + for (i = 0; i < mp->lines; i++) { + lp = mp->ldsc + i; + sim_close_logfile (&lp->txlogref); + lp->txlog = NULL; + lp->txlogname = (char *)realloc(lp->txlogname, CBUFSIZE); + if (mp->lines > 1) + sprintf(lp->txlogname, "%s_%d", mp->logfiletmpl, i); + else + strcpy(lp->txlogname, mp->logfiletmpl); + r = sim_open_logfile (lp->txlogname, TRUE, &lp->txlog, &lp->txlogref); + if (r == SCPE_OK) + setvbuf(lp->txlog, NULL, _IOFBF, 65536); + else { + free (lp->txlogname); + lp->txlogname = NULL; + break; + } + } + } + if (unbuffered) { + if (mp->buffered) { + mp->buffered = 0; + for (i = 0; i < mp->lines; i++) { /* default line buffers */ + lp = mp->ldsc + i; + lp->txbsz = TMXR_MAXBUF; + lp->txb = (char *)realloc(lp->txb, lp->txbsz); + lp->txbfd = lp->txbpi = lp->txbpr = 0; + } + } + } + if (buffered[0]) { + mp->buffered = atoi(buffered); + for (i = 0; i < mp->lines; i++) { /* initialize line buffers */ + lp = mp->ldsc + i; + lp->txbsz = mp->buffered; + lp->txbfd = 1; + lp->txb = (char *)realloc(lp->txb, lp->txbsz); + lp->txbpi = lp->txbpr = 0; + } + } + if (nolog) { + mp->logfiletmpl[0] = '\0'; + for (i = 0; i < mp->lines; i++) { /* close line logs */ + lp = mp->ldsc + i; + free(lp->txlogname); + lp->txlogname = NULL; + if (lp->txlog) { + sim_close_logfile (&lp->txlogref); + lp->txlog = NULL; + } + } + } + if (listen[0]) { + if (mp->port && (0 != strcmp (listen, mp->port))) { + sim_close_sock (mp->master, 1); + mp->master = 0; + free (mp->port); + mp->port = NULL; + } + sock = sim_master_sock (listen, &r); /* make master socket */ + if (r != SCPE_OK) + return r; + if (sock == INVALID_SOCKET) /* open error */ + return SCPE_OPENERR; + printf ("Listening on port %s\n", listen); + if (sim_log) + fprintf (sim_log, "Listening on port %s\n", listen); + mp->port = (char *)realloc (mp->port, 1 + strlen (listen)); + strcpy (mp->port, listen); /* save port */ + mp->master = sock; /* save master socket */ + mp->notelnet = listennotelnet; /* save desired telnet behavior flag */ + for (i = 0; i < mp->lines; i++) { /* initialize lines */ + lp = mp->ldsc + i; + + if (lp->serport == 0) { /* no serial port attached? */ + lp->mp = mp; /* set the back pointer */ + tmxr_init_line (lp); /* initialize line state */ + lp->conn = 0; /* clear the socket */ + } + } + } + if (destination[0]) { + if (mp->lines > 1) + return SCPE_ARG; /* ambiguous */ + lp = &mp->ldsc[0]; + lp->destination = malloc(1+strlen(destination)); + strcpy (lp->destination, destination); + serport = sim_open_serial (lp->destination, lp, &r); + if (serport != INVALID_HANDLE) { + lp->mp = mp; + lp->serport = serport; + lp->notelnet = TRUE; + tmxr_init_line (lp); /* init the line state */ + if (!lp->mp->modem_control) /* raise DTR and RTS for non modem control lines */ + sim_control_serial (lp->serport, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + lp->cnms = sim_os_msec (); /* record time of connection */ + if (sim_switches & SWMASK ('V')) { /* -V flag reports connection on port */ + sim_os_ms_sleep (TMXR_DTR_DROP_TIME); + tmxr_report_connection (mp, lp); /* report the connection to the line */ + } + } + else { + sock = sim_connect_sock (destination, "localhost", NULL); + if (sock != INVALID_SOCKET) { + lp->mp = mp; + lp->connecting = sock; + lp->ipad = malloc (1 + strlen (lp->destination)); + strcpy (lp->ipad, lp->destination); + lp->notelnet = notelnet; + lp->cnms = sim_os_msec (); /* record time of connection */ + tmxr_init_line (lp); /* init the line state */ + } + else + return SCPE_ARG; + } + } + } + else { /* line specific attach */ + lp = &mp->ldsc[line]; + lp->mp = mp; + if (logfiletmpl[0]) { sim_close_logfile (&lp->txlogref); lp->txlog = NULL; - lp->txlogname = realloc(lp->txlogname, CBUFSIZE); - if (mp->lines > 1) - sprintf(lp->txlogname, "%s_%d", mp->logfiletmpl, i); - else - strcpy(lp->txlogname, mp->logfiletmpl); + lp->txlogname = (char *)realloc (lp->txlogname, 1 + strlen (logfiletmpl)); + strcpy(lp->txlogname, mp->logfiletmpl); r = sim_open_logfile (lp->txlogname, TRUE, &lp->txlog, &lp->txlogref); if (r == SCPE_OK) setvbuf(lp->txlog, NULL, _IOFBF, 65536); else { free (lp->txlogname); lp->txlogname = NULL; - break; + return r; } } - return r; - } - if ((0 == MATCH_CMD (gbuf, "NOBUFFERED")) || - (0 == MATCH_CMD (gbuf, "UNBUFFERED"))) { - if (mp->buffered) { - mp->buffered = 0; - for (i = 0; i < mp->lines; i++) { /* default line buffers */ - lp = mp->ldsc + i; - lp->txbsz = TMXR_MAXBUF; - lp->txb = (char *)realloc(lp->txb, lp->txbsz); - lp->txbfd = lp->txbpi = lp->txbpr = 0; - } + if (unbuffered) { + lp->txbsz = TMXR_MAXBUF; + lp->txb = (char *)realloc (lp->txb, lp->txbsz); + lp->txbfd = lp->txbpi = lp->txbpr = 0; } - return SCPE_OK; - } - if (0 == MATCH_CMD (gbuf, "BUFFERED")) { - if ((NULL == cptr) || ('\0' == *cptr)) - mp->buffered = 32768; - else { - i = (int32) get_uint (cptr, 10, 1024*1024, &r); - if ((r != SCPE_OK) || (i == 0)) - return SCPE_ARG; - mp->buffered = i; - } - for (i = 0; i < mp->lines; i++) { /* initialize line buffers */ - lp = mp->ldsc + i; - lp->txbsz = mp->buffered; + if (buffered[0]) { + lp->txbsz = atoi(buffered); lp->txbfd = 1; - lp->txb = (char *)realloc(lp->txb, lp->txbsz); + lp->txb = (char *)realloc (lp->txb, lp->txbsz); lp->txbpi = lp->txbpr = 0; } - return SCPE_OK; - } - if (0 == MATCH_CMD (gbuf, "NOLOG")) { - if ((NULL != cptr) && ('\0' != *cptr)) - return SCPE_2MARG; - mp->logfiletmpl[0] = '\0'; - for (i = 0; i < mp->lines; i++) { /* close line logs */ - lp = mp->ldsc + i; + if (nolog) { free(lp->txlogname); lp->txlogname = NULL; if (lp->txlog) { @@ -1171,46 +1539,258 @@ if (!isdigit(*cptr)) { lp->txlog = NULL; } } - return SCPE_OK; + if (listen[0]) { + if (lp->master) { + sim_close_sock (lp->master, 1); + lp->master = 0; + } + if (lp->conn) { + sim_close_sock (lp->conn, 1); + lp->conn = 0; + } + if (lp->connecting) { + sim_close_sock (lp->connecting, 1); + lp->connecting = 0; + } + if (lp->serport) { + sim_close_serial (lp->serport); + lp->serport = 0; + free (lp->serconfig); + lp->serconfig = NULL; + } + free (lp->destination); + lp->destination = NULL; + sock = sim_master_sock (listen, &r); /* make master socket */ + if (r != SCPE_OK) + return r; + if (sock == INVALID_SOCKET) /* open error */ + return SCPE_OPENERR; + printf ("Line %d Listening on port %s\n", line, listen); + if (sim_log) + fprintf (sim_log, "Line %d Listening on port %s\n", line, listen); + lp->port = (char *)realloc (lp->port, 1 + strlen (listen)); + strcpy(lp->port, listen); /* save port */ + lp->master = sock; /* save master socket */ + if (listennotelnet) + lp->notelnet = listennotelnet; + else + lp->notelnet = mp->notelnet; + } + if (destination[0]) { + lp->destination = malloc(1+strlen(destination)); + strcpy (lp->destination, destination); + serport = sim_open_serial (lp->destination, lp, &r); + if (serport != INVALID_HANDLE) { + lp->serport = serport; + lp->notelnet = TRUE; + tmxr_init_line (lp); /* init the line state */ + if (!lp->mp->modem_control) /* raise DTR and RTS for non modem control lines */ + sim_control_serial (lp->serport, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + lp->cnms = sim_os_msec (); /* record time of connection */ + if (sim_switches & SWMASK ('V')) { /* -V flag reports connection on port */ + sim_os_ms_sleep (TMXR_DTR_DROP_TIME); + tmxr_report_connection (mp, lp); /* report the connection to the line */ + } + } + else { + sock = sim_connect_sock (destination, "localhost", NULL); + if (sock != INVALID_SOCKET) { + lp->connecting = sock; + lp->ipad = malloc (1 + strlen (lp->destination)); + strcpy (lp->ipad, lp->destination); + lp->notelnet = notelnet; + lp->cnms = sim_os_msec (); /* record time of connection */ + tmxr_init_line (lp); /* init the line state */ + } + else + return SCPE_ARG; + } + } + r = SCPE_OK; } - if (SCPE_OK != sim_parse_addr (gbuf, NULL, 0, NULL, NULL, 0, NULL)) - return SCPE_ARG; - cptr = init_cptr; } -sock = sim_master_sock (cptr, &r); /* make master socket */ -if (r != SCPE_OK) - return r; -if (sock == INVALID_SOCKET) /* open error */ - return SCPE_OPENERR; -printf ("Listening on port %s (socket %d)\n", cptr, sock); -if (sim_log) - fprintf (sim_log, "Listening on port %s (socket %d)\n", cptr, sock); -mp->port = calloc(1, 1+strlen(cptr)); /* save port */ -strcpy(mp->port, cptr); -mp->master = sock; /* save master socket */ -for (i = 0; i < mp->lines; i++) { /* initialize lines */ - lp = mp->ldsc + i; +return r; +} - if (lp->serport == 0) { /* no serial port attached? */ - lp->mp = mp; /* set the back pointer */ - tmxr_init_line (lp); /* initialize line state */ - lp->conn = 0; /* clear the socket */ + +/* Declare which unit polls for input + + Inputs: + *mp = the mux + line = the line number + *uptr_poll = the unit which polls + + Outputs: + none + + Implementation note: + + Only devices which poll on a unit different from the unit provided + at MUX attach time need call this function. Calling this API is + necessary for asynchronous multiplexer support and unnecessary + otherwise. + +*/ + +t_stat tmxr_set_line_unit (TMXR *mp, int line, UNIT *uptr_poll) +{ +if ((line < 0) || (line >= mp->lines)) + return SCPE_ARG; +mp->ldsc[line].uptr = uptr_poll; +return SCPE_OK; +} + +t_stat tmxr_set_console_input_unit (UNIT *uptr) +{ +extern TMLN sim_con_ldsc; + +sim_con_ldsc.uptr = uptr; +if (!(uptr->flags & UNIT_TM_POLL)) { + uptr->flags |= UNIT_TM_POLL; /* tag as polling unit */ + } +else + sim_cancel (uptr); +return SCPE_OK; +} + +/* Declare which unit polls for output + + Inputs: + *mp = the mux + line = the line number + *uptr_poll = the unit which polls for output + + Outputs: + none + + Implementation note: + + Only devices which poll on a unit different from the unit provided + at MUX attach time need call this function ABD different from the + unit which polls for input. Calling this API is necessary for + asynchronous multiplexer support and unnecessary otherwise. + +*/ + +t_stat tmxr_set_line_output_unit (TMXR *mp, int line, UNIT *uptr_poll) +{ +if ((line < 0) || (line >= mp->lines)) + return SCPE_ARG; +mp->ldsc[line].o_uptr = uptr_poll; +return SCPE_OK; +} + + +static TMXR **tmxr_open_devices = NULL; +static int tmxr_open_device_count = 0; + +t_stat tmxr_start_poll (void) +{ +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) +pthread_mutex_lock (&sim_tmxr_poll_lock); +if ((tmxr_open_device_count > 0) && + sim_asynch_enabled && + sim_is_running && + !sim_tmxr_poll_running) { + pthread_attr_t attr; + + pthread_cond_init (&sim_tmxr_startup_cond, NULL); + pthread_attr_init (&attr); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + pthread_create (&sim_tmxr_poll_thread, &attr, _tmxr_poll, NULL); + pthread_attr_destroy( &attr); + pthread_cond_wait (&sim_tmxr_startup_cond, &sim_tmxr_poll_lock); /* Wait for thread to stabilize */ + pthread_cond_destroy (&sim_tmxr_startup_cond); + sim_tmxr_poll_running = TRUE; + } +pthread_mutex_unlock (&sim_tmxr_poll_lock); +#endif +return SCPE_OK; +} + +t_stat tmxr_stop_poll (void) +{ +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) +pthread_mutex_lock (&sim_tmxr_poll_lock); +if (sim_tmxr_poll_running) { + pthread_cond_signal (&sim_tmxr_poll_cond); + pthread_mutex_unlock (&sim_tmxr_poll_lock); + pthread_join (sim_tmxr_poll_thread, NULL); + sim_tmxr_poll_running = FALSE; + /* Transitioning from asynch mode so kick all polling units onto the event queue */ + if (tmxr_open_device_count) { + int i, j; + + for (i=0; iuptr) + _sim_activate (mp->uptr, 0); + for (j = 0; j < mp->lines; ++j) + if (mp->ldsc[j].uptr) + _sim_activate (mp->ldsc[j].uptr, 0); + } } } +else + pthread_mutex_unlock (&sim_tmxr_poll_lock); +#endif return SCPE_OK; } +static void _tmxr_add_to_open_list (TMXR* mux) +{ +int i; +t_bool found = FALSE; + +#if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) +pthread_mutex_lock (&sim_tmxr_poll_lock); +#endif +for (i=0; iport); /* copy port */ -if (mp->buffered) - sprintf (bmsg, ", buffered=%d", mp->buffered); /* buffer info */ -if (mp->logfiletmpl[0]) - sprintf (lmsg, ", log=%s", mp->logfiletmpl); /* logfile info */ -sprintf (tptr, "%s%s%s", pmsg, bmsg, lmsg); /* assemble all */ -uptr->filename = tptr; /* save */ +mp->uptr = uptr; /* save unit for polling */ +uptr->filename = _mux_attach_string (uptr->filename, mp);/* save */ uptr->flags = uptr->flags | UNIT_ATT; /* no more errors */ +if (mp->lines > 1) + uptr->flags = uptr->flags | UNIT_ATTMULT; /* allow multiple attach commands */ if (mp->dptr == NULL) /* has device been set? */ mp->dptr = find_dev_from_unit (uptr); /* no, so set device now */ +_tmxr_add_to_open_list (mp); return SCPE_OK; } -/* Attach a line to a serial port. - - Attach a line of the multiplexer associated with descriptor "desc" to a - serial port. Two calling sequences are supported: - - 1. If "val" is zero, then "uptr" is implicitly associated with the line - number corresponding to the position of the unit in the zero-based array - of units belonging to the associated device, and "cptr" points to the - serial port name. For example, if "uptr" points to unit 3 in a given - device, and "cptr" points to the string "COM1", then line 3 will be - attached to serial port "COM1". - - 2. If "val" is non-zero, then "cptr" points to a string that is parsed for - an explicit line number and serial port name, and "uptr" is ignored. The - number and name are delimited by the character represented by "val". For - example, if "val" is 58 (':'), and "cptr" points to the string "3:COM1", - then line 3 will be attached to serial port "COM1". - - An optional configuration string may be present after the port name. If - present, it must be separated from the port name with a semicolon and has - this form: - - - - - where: - - rate = communication rate in bits per second - charsize = character size in bits (5-8, including optional parity) - parity = parity designator (N/E/O/M/S for no/even/odd/mark/space parity) - stopbits = number of stop bits (1, 1.5, or 2) - - As an example: - - 9600-8n1 - - The supported rates, sizes, and parity options are host-specific. If a - configuration string is not supplied, then host system defaults are used. - - If the serial port allocation is successful, then if "val" is zero, then the - port name is stored in the UNIT structure, and the UNIT_ATT flag is set. If - "val" is non-zero, then this is not done, as there is no unit corresponding - to the attached line. - - Implementation notes: - - 1. If the device associated with the unit referenced by "uptr" does not have - the DEV_NET flag set, then the optional configuration string is saved - with the port name in the UNIT structure. This allows a RESTORE to - reconfigure the attached serial port during reattachment. The combined - string will be displayed when the unit is SHOWed. - - If the unit has the DEV_NET flag, the optional configuration string is - removed before the attached port name is saved in the UNIT structure, as - RESTORE will not reattach the port, and so reconfiguration is not needed. - - 2. This function may be called as an MTAB processing routine. -*/ - -t_stat tmxr_attach_line (UNIT *uptr, int32 val, char *cptr, void *desc) +t_stat tmxr_startup (void) { -TMXR *mp = (TMXR *) desc; -TMLN *lp; -DEVICE *dptr; -char *pptr, *sptr, *tptr; -SERHANDLE serport; -SERCONFIG config = { 0 }; -t_stat status; -char portname [1024]; -t_bool arg_error = FALSE; +return SCPE_OK; +} -dptr = find_dev_from_unit (uptr); /* find associated device */ +t_stat tmxr_shutdown (void) +{ +if (tmxr_open_device_count) + return SCPE_IERR; +return SCPE_OK; +} -if (val) { /* explicit line? */ - if (cptr == NULL) /* arguments supplied? */ - return SCPE_ARG; /* report bad argument */ - uptr = NULL; /* indicate to get routine */ - tptr = strchr (cptr, (char) val); /* search for separator */ +t_stat tmxr_show_open_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, char* desc) +{ +int i, j; - if (tptr == NULL) /* not found? */ - return SCPE_ARG; /* report bad argument */ - else /* found */ - *tptr = '\0'; /* terminate for get_uint */ - } +if (0 == tmxr_open_device_count) + fprintf(st, "No Attached Multiplexer Devices\n"); +else { + for (i=0; iconn) /* line connected via Telnet? */ - return SCPE_NOFNC; /* command not allowed */ - -if (val) /* named line form? */ - cptr = tptr + 1; /* point at port name */ - -if ((cptr == NULL) || (!*cptr)) /* port name missing? */ - return SCPE_ARG; /* report it */ - -pptr = get_glyph_nc (cptr, portname, ';'); /* separate port name from optional params */ - -if (*pptr) { /* parameter string present? */ - config.baudrate = (uint32) strtotv (pptr, &sptr, 10); /* parse baud rate */ - arg_error = (pptr == sptr); /* check for bad argument */ - - if (*sptr) /* separator present? */ - sptr++; /* skip it */ - - config.charsize = (uint32) strtotv (sptr, &tptr, 10); /* parse character size */ - arg_error = arg_error || (sptr == tptr); /* check for bad argument */ - - if (*tptr) /* parity character present? */ - config.parity = toupper (*tptr++); /* save parity character */ - - config.stopbits = (uint32) strtotv (tptr, &sptr, 10); /* parse number of stop bits */ - arg_error = arg_error || (tptr == sptr); /* check for bad argument */ - - if (arg_error) /* bad conversions? */ - return SCPE_ARG; /* report argument error */ - - else if (strcmp (sptr, ".5") == 0) /* 1.5 stop bits requested? */ - config.stopbits = 0; /* code request */ - } - -serport = sim_open_serial (portname, lp); /* open the serial port */ - -if (serport == INVALID_HANDLE) /* not a valid port name */ - return SCPE_OPENERR; /* cannot attach */ - -else { /* good serial port */ - if (*pptr) { /* parameter string specified? */ - status = sim_config_serial (serport, config); /* set serial configuration */ - - if (status != SCPE_OK) { /* port configuration error? */ - sim_close_serial (serport); /* close the port */ - return status; /* report error */ + fprintf(st, "Multiplexer device: %s", mp->dptr->name); + fprintf(st, ", attached to %s, ", mp->uptr->filename); + if (mp->lines > 1) { + tmxr_show_lines(st, NULL, 0, mp); + fprintf(st, ", "); + } + tmxr_show_summ(st, NULL, 0, mp); + fprintf(st, ", sessions=%d\n", mp->sessions); + for (j = 0; j < mp->lines; j++) { + lp = mp->ldsc + j; + fprintf (st, "Line: %d", j); + if (lp->uptr && (lp->uptr != lp->mp->uptr)) + fprintf (st, " - Unit: %s\n", sim_uname (lp->uptr)); + else + fprintf (st, "\n"); + if ((!lp->conn) && (!lp->connecting) && (!lp->serport) && (!lp->master)) + continue; + tmxr_fconns (st, lp, j); + tmxr_fstats (st, lp, -1); } } - - if (val == 0) { /* unit implies line? */ - if (dptr && (dptr->flags & DEV_NET)) /* will RESTORE be inhibited? */ - cptr = portname; /* yes, so save just port name */ - } - if (mp->dptr == NULL) /* has device been set? */ - mp->dptr = dptr; /* no, so set device now */ - - tptr = (char *) malloc (strlen (cptr) + 1); /* get buffer for port name and maybe params */ - - if (tptr == NULL) { /* allocation problem? */ - sim_close_serial (serport); /* close the port */ - return SCPE_MEM; /* report allocation failure */ - } - - strcpy (tptr, cptr); /* copy the port name into the buffer */ - - if (val == 0) { /* unit implies line? */ - uptr->filename = tptr; /* save buffer pointer in UNIT */ - uptr->flags = uptr->flags | UNIT_ATT; /* mark unit as attached */ - } - - lp->mp = mp; /* set the back pointer */ - tmxr_init_line (lp); /* initialize the line state */ - lp->serport = serport; /* set serial port handle */ - lp->sername = tptr; /* set serial port name */ - lp->cnms = 0; /* schedule for immediate connection */ - lp->conn = 0; /* indicate no connection yet */ - mp->pending = mp->pending + 1; /* but connection is pending */ } - -return SCPE_OK; /* line has been connected */ +return SCPE_OK; } @@ -1410,7 +1865,7 @@ return SCPE_OK; /* line has been connect The listening socket associated with multiplexer descriptor "mp" is closed and deallocated. In addition, all current Telnet sessions are disconnected. - Serial sessions are not affected. + Serial and outgoing sessions are also disconnected. */ t_stat tmxr_close_master (TMXR *mp) @@ -1418,17 +1873,46 @@ t_stat tmxr_close_master (TMXR *mp) int32 i; TMLN *lp; -for (i = 0; i < mp->lines; i++) { /* loop thru conn */ +for (i = 0; i < mp->lines; i++) { /* loop thru conn */ lp = mp->ldsc + i; - if (!lp->serport && lp->conn) { /* not serial and is connected? */ + if (!lp->destination && lp->conn) { /* not serial and is connected? */ tmxr_report_disconnection (lp); /* report disconnection */ tmxr_reset_ln (lp); /* disconnect line */ + if (lp->master) + sim_close_sock (mp->master, 1); /* close master socket */ + lp->master = 0; + free (lp->port); + lp->port = NULL; + } + else { + if (lp->conn) { + tmxr_report_disconnection (lp); /* report disconnection */ + tmxr_reset_ln (lp); + } + if (lp->connecting) { + lp->conn = lp->connecting; + lp->connecting = 0; + tmxr_reset_ln (lp); + } + if (lp->serport) { + tmxr_reset_ln (lp); + sim_control_serial (lp->serport, 0, TMXR_MDM_DTR|TMXR_MDM_RTS, NULL);/* drop DTR and RTS */ + sim_close_serial (lp->serport); + lp->serport = 0; + free (lp->serconfig); + lp->serconfig = NULL; + } + free (lp->destination); + lp->destination = NULL; } } sim_close_sock (mp->master, 1); /* close master socket */ mp->master = 0; +free (mp->port); +mp->port = NULL; +_tmxr_remove_from_open_list (mp); return SCPE_OK; } @@ -1446,140 +1930,12 @@ t_stat tmxr_detach (TMXR *mp, UNIT *uptr) if (!(uptr->flags & UNIT_ATT)) /* attached? */ return SCPE_OK; tmxr_close_master (mp); /* close master socket */ -free (uptr->filename); /* free port string */ +free (uptr->filename); /* free setup string */ uptr->filename = NULL; uptr->flags = uptr->flags & ~UNIT_ATT; /* not attached */ return SCPE_OK; } - -/* Detach a line from serial port. - - Disconnect and detach a line of the multiplexer associated with descriptor - "desc" from a serial port. Two calling sequences are supported: - - 1. If "val" is zero, then "uptr" is implicitly associated with the line - number corresponding to the position of the unit in the zero-based array - of units belonging to the associated device, and "cptr" points to the - serial port name. For example, if "uptr" points to unit 3 in a given - device, then line 3 will be detached from the associated serial port. - - 2. If "val" is non-zero, then "cptr" points to a string that is parsed for - an explicit line number, and "uptr" is ignored. For example, if "cptr" - points to the string "3", then line 3 will be detached from the - associated serial port. - - Calling sequence 2 allows serial ports to be used with multiplexers that do - not implement unit-per-line. In this configuration, there is no unit - associated with a given line, so the ATTACH and DETACH commands cannot be - used. Instead, SET commands directed to the device must specify the line - number and port name to open (e.g., "SET CONNECT=:") or close - (e.g., "SET DISCONNECT="). These commands call "tmxr_attach_line" - and "tmxr_detach_line", respectively, with non-zero "val" parameters. - - As an aid for this configuration, we do not verify serial port connections - when "val" is non-zero. That is, we will disconnect the line without regard - to whether a serial or Telnet connection is present. Then, if a serial - connection was present, the serial port is closed. This allows the SET - DISCONNECT command to be used to disconnect (close) both Telnet and serial - sessions. - - Implementation notes: - - 1. If "val" is zero, and the specified line is not connected to a serial - port, then SCPE_UNATT is returned. This allows a common VM-provided - detach routine in a single-line device to attempt to detach a serial port - first, and then, if that fails, to detach a Telnet session via - "tmxr_detach". Note that the latter will always succeed, even if the - unit is not attached, and so cannot be called first. - - 2. If the serial connection was completed, we drop the line to ensure that a - modem will disconnect. This increments the pending connection count in - preparation for reconnecting. If the connection was not completed, it - will still be pending. In either case, we drop the pending connection - count, as we will be closing the serial port. - - 3. This function may be called as an MTAB processing routine. -*/ - -t_stat tmxr_detach_line (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -TMXR *mp = (TMXR *) desc; -TMLN *lp; -t_stat status; - -if (val) /* explicit line? */ - uptr = NULL; /* indicate to get routine */ - -lp = tmxr_get_ldsc (uptr, cptr, mp, &status); /* get referenced line */ - -if (lp == NULL) /* bad line number? */ - return status; /* report it */ - -if (uptr && lp->serport == 0) /* serial port attached to unit? */ - return SCPE_UNATT; /* no, so report status to caller */ - -if (lp->conn) { /* was connection made? */ - tmxr_report_disconnection (lp); /* report disconnection */ - tmxr_clear_ln (mp, lp); /* close line */ - } - -if (lp->serport) { /* serial port attached? */ - mp->pending = mp->pending - 1; /* drop pending connection count */ - - sim_close_serial (lp->serport); /* close serial port */ - lp->serport = 0; /* clear handle */ - free (lp->sername); /* free port name */ - lp->sername = 0; /* clear pointer */ - - if (uptr) { /* unit implies line? */ - uptr->filename = NULL; /* clear attached name pointer */ - uptr->flags = uptr->flags & ~UNIT_ATT; /* no longer attached */ - } - } - -return SCPE_OK; -} - - -/* Check if a line is free. - - A line is free if it is not connected to a Telnet session or a serial port. -*/ - -t_bool tmxr_line_free (TMLN *lp) -{ -return lp && (lp->conn == 0) && (lp->serport == 0); /* free if no connection */ -} - - -/* Check if a multiplexer is free. - - A multiplexer is free if it is not listening for new Telnet connections and - no lines are connected to serial ports. Note that if the listening socket is - detached, then no Telnet sessions can exist, so we only need to check for - serial connections on the lines. -*/ - -t_bool tmxr_mux_free (TMXR *mp) -{ -TMLN* lp; -int32 ln; - -if (mp == NULL || mp->master || mp->pending) /* listening for Telnet or serial connection? */ - return FALSE; /* not free */ - -for (ln = 0; ln < mp->lines; ln++) { /* check each line for serial connection */ - lp = mp->ldsc + ln; /* get pointer to line descriptor */ - - if (lp->serport) /* serial connection? */ - return FALSE; /* not free */ - } - -return TRUE; /* no connections, so mux is free */ -} - - /* Stub examine and deposit */ t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) @@ -1597,7 +1953,7 @@ return SCPE_NOFNC; void tmxr_msg (SOCKET sock, char *msg) { -if (sock) +if ((sock) && (sock != INVALID_SOCKET)) sim_write_sock (sock, msg, (int32)strlen (msg)); return; } @@ -1619,30 +1975,39 @@ return; void tmxr_fconns (FILE *st, TMLN *lp, int32 ln) { +int32 hr, mn, sc; +uint32 ctime; + if (ln >= 0) fprintf (st, "line %d: ", ln); -if (lp->conn) { - int32 hr, mn, sc; - uint32 ctime; +if ((lp->conn) || (lp->connecting)) /* tcp connection? */ + if (lp->destination) /* remote connection? */ + fprintf (st, "Remote port %s\n", lp->destination);/* print port name */ + else /* incoming connection */ + fprintf (st, "IP address %s\n", lp->ipad); - if (lp->serport) /* serial connection? */ - fprintf (st, "Serial port %s", lp->sername); /* print port name */ +if (lp->port) + fprintf (st, "Listening on port %s\n", lp->port); /* print port name */ - else { /* socket connection */ - fprintf (st, "IP address %s", lp->ipad); - } +if (lp->serport) /* serial connection? */ + fprintf (st, "Serial port %s\n", lp->destination); /* print port name */ +if (lp->cnms) { ctime = (sim_os_msec () - lp->cnms) / 1000; hr = ctime / 3600; mn = (ctime / 60) % 60; sc = ctime % 60; if (ctime) - fprintf (st, ", connected %02d:%02d:%02d\n", hr, mn, sc); + fprintf (st, " %s %02d:%02d:%02d\n", lp->connecting ? "Connecting for" : "Connected", hr, mn, sc); } -else fprintf (st, "line disconnected\n"); +else + fprintf (st, " Line disconnected\n"); + +if ((lp->serport == 0) && (lp->conn)) + fprintf (st, " %s\n", (lp->notelnet) ? "Telnet disabled (RAW data)" : "Telnet protocol"); if (lp->txlog) - fprintf (st, "Logging to %s\n", lp->txlogname); + fprintf (st, " Logging to %s\n", lp->txlogname); return; } @@ -1656,7 +2021,7 @@ static const char *dsab = "off"; if (ln >= 0) fprintf (st, "line %d:\b", ln); -if (!lp->conn) +if ((!lp->conn) && (!lp->connecting) && (!lp->serport)) fprintf (st, "line disconnected\n"); if (lp->rxcnt) fprintf (st, " input (%s) queued/total = %d/%d\n", @@ -1680,7 +2045,7 @@ return; /* Disconnect a line. Disconnect a line of the multiplexer associated with descriptor "desc" from a - Telnet session or a serial port. Two calling sequences are supported: + tcp session or a serial port. Two calling sequences are supported: 1. If "val" is zero, then "uptr" is implicitly associated with the line number corresponding to the position of the unit in the zero-based array @@ -1692,15 +2057,14 @@ return; an explicit line number, and "uptr" is ignored. For example, if "cptr" points to the string "3", then line 3 will be disconnected. - If the line was connected to a Telnet session, the socket associated with the + If the line was connected to a tcp session, the socket associated with the line will be closed. If the line was connected to a serial port, the port - will NOT be closed, but DTR will be dropped. The line will be reconnected - after a short delay to allow the serial device to recognize the DTR state - change. + will NOT be closed, but DTR will be dropped. After a 500ms delay DTR will + be raised again. Implementation notes: - 1. This function may be called as an MTAB processing routine. + 1. This function is usually called as an MTAB processing routine. */ t_stat tmxr_dscln (UNIT *uptr, int32 val, char *cptr, void *desc) @@ -1712,14 +2076,17 @@ t_stat status; if (val) /* explicit line? */ uptr = NULL; /* indicate to get routine */ +tmxr_debug_trace (mp, "tmxr_dscln()"); + lp = tmxr_get_ldsc (uptr, cptr, mp, &status); /* get referenced line */ if (lp == NULL) /* bad line number? */ return status; /* report it */ -if (lp->conn) { /* connection active? */ - tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n"); /* report closure */ - tmxr_clear_ln (mp, lp); /* drop the line */ +if ((lp->conn) || (lp->serport)) { /* connection active? */ + if (!lp->notelnet) + tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");/* report closure */ + tmxr_reset_ln (lp); /* drop the line */ } return SCPE_OK; @@ -1973,10 +2340,12 @@ int32 i, t; if (mp == NULL) return SCPE_IERR; for (i = t = 0; i < mp->lines; i++) - t = t + (mp->ldsc[i].conn != 0); -if (t == 1) - fprintf (st, "1 connection"); -else fprintf (st, "%d connections", t); + if ((mp->ldsc[i].conn != 0) || (mp->ldsc[i].serport != 0)) + t = t + 1; +if (mp->lines > 1) + fprintf (st, "%d connection%s", t, (t != 1) ? "s" : ""); +else + fprintf (st, "%s", (t == 1) ? "connected" : "disconnected"); return SCPE_OK; } @@ -1990,7 +2359,7 @@ int32 i, any; if (mp == NULL) return SCPE_IERR; for (i = any = 0; i < mp->lines; i++) { - if (mp->ldsc[i].conn) { + if ((mp->ldsc[i].conn != 0) || (mp->ldsc[i].serport != 0)) { any++; if (val) tmxr_fconns (st, &mp->ldsc[i], i); @@ -2053,7 +2422,7 @@ static void tmxr_buf_debug_char (char value) { if (tmxr_debug_buf_used+2 > tmxr_debug_buf_size) { tmxr_debug_buf_size += 1024; - tmxr_debug_buf = realloc(tmxr_debug_buf, tmxr_debug_buf_size); + tmxr_debug_buf = (char *)realloc (tmxr_debug_buf, tmxr_debug_buf_size); } tmxr_debug_buf[tmxr_debug_buf_used++] = value; tmxr_debug_buf[tmxr_debug_buf_used] = '\0'; @@ -2065,7 +2434,7 @@ while (*string) tmxr_buf_debug_char (*string++); } -void tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize) +void _tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize) { if ((lp->mp->dptr) && (dbits & lp->mp->dptr->dctrl)) { int i, j; diff --git a/sim_tmxr.h b/sim_tmxr.h index 38ade11c..6283402a 100644 --- a/sim_tmxr.h +++ b/sim_tmxr.h @@ -26,11 +26,11 @@ Based on the original DZ11 simulator by Thord Nilson, as updated by Arthur Krewat. + 10-Oct-12 MP Added extended attach support for serial, per line + listener and outgoing connections 17-Jan-11 MP Added buffered line capabilities 20-Nov-08 RMS Added three new standardized SHOW routines - 07-Oct-08 JDB [serial] Added serial port support to TMXR, TMLN, - added tmxr_attach_line, tmxr_detach_line, - tmxr_line_free, tmxr_mux_free + 07-Oct-08 JDB Added serial port support to TMXR, TMLN 27-May-08 JDB Added lnorder to TMXR structure, added tmxr_set_lnorder and tmxr_set_lnorder 14-May-08 JDB Added dptr to TMXR structure @@ -56,8 +56,32 @@ #define TMXR_MAXBUF 256 /* buffer size */ #define TMXR_GUARD 12 /* buffer guard */ +#define TMXR_DTR_DROP_TIME 500 /* milliseconds to drop DTR for 'pseudo' modem control */ +#define TMXR_CONNECT_POLL_INTERVAL 1000 /* milliseconds between connection polls */ + #define TMXR_DBG_XMT 0x10000 /* Debug Transmit Data */ #define TMXR_DBG_RCV 0x20000 /* Debug Received Data */ +#define TMXR_DBG_ASY 0x40000 /* Debug Asynchronous Activities */ +#define TMXR_DBG_TRC 0x80000 /* Debug trace routine calls */ + +/* Modem Control Bits */ + +#define TMXR_MDM_DTR 0x01 /* Data Terminal Ready */ +#define TMXR_MDM_RTS 0x02 /* Request To Send */ +#define TMXR_MDM_DCD 0x04 /* Data Carrier Detect */ +#define TMXR_MDM_RNG 0x08 /* Ring Indicator */ +#define TMXR_MDM_CTS 0x10 /* Clear To Send */ +#define TMXR_MDM_DSR 0x20 /* Data Set Ready */ +#define TMXR_MDM_INCOMING (TMXR_MDM_DCD|TMXR_MDM_RNG|TMXR_MDM_CTS|TMXR_MDM_DSR) /* Settable Modem Bits */ +#define TMXR_MDM_OUTGOING (TMXR_MDM_DTR|TMXR_MDM_DTR) /* Settable Modem Bits */ + +/* Unit flags */ + +#define TMUF_V_NOASYNCH (UNIT_V_UF + 12) /* Asynch Disabled unit */ +#define TMUF_NOASYNCH (1u << TMUF_V_NOASYNCH) /* This flag can be defined */ + /* statically in a unit's flag field */ + /* This will disable the unit from */ + /* supporting asynchronmous mux behaviors */ typedef struct tmln TMLN; typedef struct tmxr TMXR; @@ -65,11 +89,15 @@ typedef struct tmxr TMXR; struct tmln { SOCKET conn; /* line conn */ char *ipad; /* IP address */ + SOCKET master; /* line specific master socket */ + char *port; /* line specific listening port */ + int32 sessions; /* count of tcp connections received */ uint32 cnms; /* conn time */ int32 tsta; /* Telnet state */ int32 rcve; /* rcv enable */ int32 xmte; /* xmt enable */ int32 dstb; /* disable Tlnt bin */ + t_bool notelnet; /* raw binary data (no telnet interpretation) */ int32 rxbpr; /* rcv buf remove */ int32 rxbpi; /* rcv buf insert */ int32 rxcnt; /* rcv count */ @@ -86,8 +114,12 @@ struct tmln { char rbr[TMXR_MAXBUF]; /* rcv break */ char *txb; /* xmt buffer */ TMXR *mp; /* back pointer to mux */ + char *serconfig; /* line config */ SERHANDLE serport; /* serial port handle */ - char *sername; /* serial port name */ + SOCKET connecting; /* Outgoing socket while connecting */ + char *destination; /* Outgoing destination address:port */ + UNIT *uptr; /* input polling unit (default to mp->uptr) */ + UNIT *o_uptr; /* output polling unit (default to lp->uptr)*/ }; struct tmxr { @@ -97,14 +129,17 @@ struct tmxr { TMLN *ldsc; /* line descriptors */ int32 *lnorder; /* line connection order */ DEVICE *dptr; /* multiplexer device */ + UNIT *uptr; /* polling unit (connection) */ char logfiletmpl[FILENAME_MAX]; /* template logfile name */ int32 buffered; /* Buffered Line Behavior and Buffer Size Flag */ - uint32 pending; /* count of pending serial connections */ + int32 sessions; /* count of tcp connections received */ + uint32 last_poll_time; /* time of last connection poll */ + t_bool notelnet; /* default telnet capability for incoming connections */ + t_bool modem_control; /* multiplexer supports modem control behaviors */ }; int32 tmxr_poll_conn (TMXR *mp); -void tmxr_reset_ln (TMLN *lp); -t_stat tmxr_clear_ln (TMXR *mp, TMLN *lp); +t_stat tmxr_reset_ln (TMLN *lp); int32 tmxr_getc_ln (TMLN *lp); void tmxr_poll_rx (TMXR *mp); t_stat tmxr_putc_ln (TMLN *lp, int32 chr); @@ -114,10 +149,12 @@ t_stat tmxr_open_master (TMXR *mp, char *cptr); t_stat tmxr_close_master (TMXR *mp); t_stat tmxr_attach (TMXR *mp, UNIT *uptr, char *cptr); t_stat tmxr_detach (TMXR *mp, UNIT *uptr); -t_stat tmxr_attach_line (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat tmxr_detach_line (UNIT *uptr, int32 val, char *cptr, void *desc); -t_bool tmxr_line_free (TMLN *lp); -t_bool tmxr_mux_free (TMXR *mp); +t_stat tmxr_set_modem_control_passthru (TMXR *mp); +t_stat tmxr_set_get_modem_bits (TMLN *lp, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits); +t_stat tmxr_set_config_line (TMLN *lp, char *config); +t_stat tmxr_set_line_unit (TMXR *mp, int line, UNIT *uptr_poll); +t_stat tmxr_set_line_output_unit (TMXR *mp, int line, UNIT *uptr_poll); +t_stat tmxr_set_console_input_unit (UNIT *uptr); t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); void tmxr_msg (SOCKET sock, char *msg); @@ -135,7 +172,16 @@ t_stat tmxr_show_lnorder (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat tmxr_show_summ (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat tmxr_show_cstat (FILE *st, UNIT *uptr, int32 val, void *desc); t_stat tmxr_show_lines (FILE *st, UNIT *uptr, int32 val, void *desc); -void tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize); +t_stat tmxr_show_open_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, char* desc); +t_stat tmxr_startup (void); +t_stat tmxr_shutdown (void); +t_stat tmxr_start_poll (void); +t_stat tmxr_stop_poll (void); +void _tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize); +extern FILE *sim_deb; /* debug file */ +#define tmxr_debug(dbits, lp, msg, buf, bufsize) if (sim_deb && (lp)->mp->dptr && ((dbits) & (lp)->mp->dptr->dctrl)) _tmxr_debug (dbits, lp, msg, buf, bufsize); else (void)0 +#define tmxr_debug_trace(mp, msg) if (sim_deb && (mp)->dptr && (TMXR_DBG_TRC & (mp)->dptr->dctrl)) sim_debug (TMXR_DBG_TRC, mp->dptr, "%s\n", (msg)); else (void)0 +#define tmxr_debug_trace_line(lp, msg) if (sim_deb && (lp)->mp && (lp)->mp->dptr && (TMXR_DBG_TRC & (lp)->mp->dptr->dctrl)) sim_debug (TMXR_DBG_TRC, (lp)->mp->dptr, "%s\n", (msg)); else (void)0 #endif