It also modifies the HP 2100 and PDP11 multiplexers to add serial support as demonstrations of the capability that, one day, might be extended to all simulators. I have tested the HP support, but I relied on Holger Veit to test the DEC stuff, so I can't guarantee that it works. I also relied on Holger to test under Linux, so the same caveat applies. The changes needed in the device simulators are relatively small. For example, if you look at the patches for "hp2100_baci.c", you'll note that most of them are documentation changes. The only things of note are: - an expansion of the TMXR initializer - additional code in the "attach" routine to try attaching a serial port if attaching a socket fails - additional code in the "detach" routine for the same reasons The HP MPX device (hp2100_mpx.c) needs a tiny bit of additional support from the ATTACH and DETACH commands. Specifically, SCP was modified to set a flag ("sim_unit_ref") to indicate whether ATTACH MPX or ATTACH MPX0 was done, i.e., to differentiate between a device and a unit attach (recall that SCP treats these as both referring to unit 0). This is needed because the socket attaches (logically) to the device, whereas a serial port attaches to a line. Without this flag, the attach routine cannot differentiate between ATTACH MPX and ATTACH MPX0, as the distinction is lost by the time the VM's attach routine is called. This support isn't needed for the HP MUX device because the socket attaches to a different device than the lines do. MPX also requires a bit more work due to the capability to mix serial and Telnet lines on the same multiplexer (BACI is a single-line terminal device). The attached PDF contains revisions to the "Writing a Simulator for the SIMH System" publication that documents the additions and changes to the multiplexer library for serial port support. User documentation for serial port support currently exists only in the initial comments in "sim_tmxr.c"; I will add the appropriate text to the "SIMH User's Guide" if we decide to add this to the release version.
2737 lines
128 KiB
C
2737 lines
128 KiB
C
/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator
|
|
|
|
Copyright (c) 2008-2012, J. David Bryan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of the author shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from the author.
|
|
|
|
MPX 12792C 8-channel multiplexer card
|
|
|
|
10-Feb-12 JDB Deprecated DEVNO in favor of SC
|
|
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
|
|
26-Jun-08 JDB Rewrote device I/O to model backplane signals
|
|
26-May-08 JDB Created MPX device
|
|
|
|
References:
|
|
- HP 12792B 8-Channel Asynchronous Multiplexer Subsystem Installation and
|
|
Reference Manual (12792-90020, Jul-1984)
|
|
- HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem User's Manual
|
|
(5955-8867, Jun-1993)
|
|
- HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem Configuration Guide
|
|
(5955-8868, Jun-1993)
|
|
- HP 1000 series 8-channel Multiplexer Firmware External Reference Specification
|
|
(October 19, 1982)
|
|
- HP 12792/12040 Multiplexer Firmware Source (24999-18312, revision C)
|
|
- Zilog Components Data Book (00-2034-04, 1985)
|
|
|
|
|
|
The 12792A/B/C/D was an eight-line asynchronous serial multiplexer that
|
|
connected terminals, modems, serial line printers, and "black box" devices
|
|
that used the RS-232 standard to the CPU. It used an on-board microprocessor
|
|
and provided input and output buffering to support block-mode reads from HP
|
|
264x and 262x terminals at speeds up to 19.2K baud. The card handled
|
|
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.
|
|
|
|
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
|
|
control circuitry. The card executed a high-level command set, and data
|
|
transfer to and from the CPU was via the on-board DMA controller and the DCPC
|
|
in the CPU.
|
|
|
|
The 12792 for the M/E/F series and the 12040 multiplexer for the A/L series
|
|
differed only in backplane design. Early ROMs were card-specific, but later
|
|
ones were interchangeable; the code would determine whether it was executing
|
|
on an MEF card or an AL card.
|
|
|
|
Four major firmware revisions were made. These were labelled "A", "B", "C",
|
|
and "D". The A, B, and C revisions were interchangeable from the perspective
|
|
of the OS driver; the D was different and required an updated driver.
|
|
Specifically:
|
|
|
|
Op. Sys. Driver Part Number Rev
|
|
-------- ------ -------------------- ---
|
|
RTE-MIII DVM00 12792-16002 Rev.2032 A
|
|
RTE-IVB DVM00 12792-16002 Rev.5000 ABC
|
|
|
|
RTE-6/VM DVM00 12792-16002 Rev.5000 ABC
|
|
RTE-6/VM DV800 92084-15068 Rev.6000 D
|
|
|
|
RTE-A IDM00 92077-16754 Rev.5020 ABC
|
|
RTE-A ID800 92077-16887 Rev.6200 D
|
|
|
|
Revisions A-C have an upward-compatible command set that partitions each OS
|
|
request into several sub-commands. Each command is initiated by setting the
|
|
control flip-flop on the card, which causes a non-maskable interrupt (NMI) on
|
|
the card's Z80 processor.
|
|
|
|
The D-revision firmware uses a completely different command set. The
|
|
commands are slightly modified versions of the original EXEC calls (read,
|
|
write, and control) and are generally passed to the card directly for action.
|
|
|
|
This simulation supports the C revision. D-revision support may be added
|
|
later.
|
|
|
|
Twelve programmable baud rates are supported by the multiplexer. These
|
|
"realistic" rates are simulated by scheduling I/O service based on the
|
|
appropriate number of 1000 E-Series instructions for the rate selected.
|
|
|
|
The simulation provides both the "realistic timing" described above, as well
|
|
as an optimized "fast timing" option. Optimization makes three improvements:
|
|
|
|
1. Buffered characters are transferred in blocks.
|
|
|
|
2. ENQ/ACK handshaking is done locally without involving the client.
|
|
|
|
3. BS and DEL respond visually more like prior RTE terminal drivers.
|
|
|
|
HP did not offer a functional diagnostic for the 12792. Instead, a Z80
|
|
program that tested the operation of the hardware was downloaded to the card,
|
|
and a "go/no-go" status was returned to indicate the hardware condition.
|
|
Because this is a functional simulation of the multiplexer and not a Z80
|
|
emulation, the diagnostic cannot be used to test the implementation.
|
|
|
|
Implementation notes:
|
|
|
|
1. The 12792 had two baud-rate generators that were assigned to lines by the
|
|
wiring configuration in the I/O cable connector hood. Two of the four
|
|
CTC counters were used to implement the BRGs for all eight lines. Only
|
|
subsets of the configurable rates were allowed for lines connected to the
|
|
same BRG, and assigning mutually incompatible rates caused corruption of
|
|
the rates on lines assigned earlier. Under simulation, any baud rate may
|
|
be assigned to any line without interaction, and assignments of lines to
|
|
BRGs is not implemented.
|
|
|
|
2. Revisions B and C added support for the 37214A Systems Modem subsystem
|
|
and the RTE-A Virtual Control Panel (VCP). Under simulation, the modem
|
|
commands return status codes indicating that no modems are present, and
|
|
the VCP commands are not implemented.
|
|
*/
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "hp2100_defs.h"
|
|
#include "sim_sock.h"
|
|
#include "sim_tmxr.h"
|
|
|
|
|
|
/* Bitfield constructor.
|
|
|
|
Given a bitfield starting bit number and width in bits, declare two
|
|
constants: one for the starting bit number, and one for the positioned field
|
|
mask. That is, given a definition such as:
|
|
|
|
BITFIELD(SMALLFIELD,5,2)
|
|
|
|
...this macro produces:
|
|
|
|
static const uint32 SMALLFIELD_V = 5;
|
|
static const uint32 SMALLFIELD = ((1 << (2)) - 1) << (5);
|
|
|
|
The latter reduces to 3 << 5, or 0x00000060.
|
|
|
|
Note: C requires constant expressions in initializers for objects with static
|
|
storage duration, so initializing a static object with a BITFIELD value is
|
|
illegal (a "static const" object is not a constant!).
|
|
*/
|
|
|
|
#define BITFIELD(NAME,STARTBIT,BITWIDTH) \
|
|
static const uint32 NAME ## _V = STARTBIT; \
|
|
static const uint32 NAME = ((1 << (BITWIDTH)) - 1) << (STARTBIT);
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define MPX_DATE_CODE 2416 /* date code for C firmware */
|
|
|
|
#define RD_BUF_SIZE 514 /* read buffer size */
|
|
#define WR_BUF_SIZE 514 /* write buffer size */
|
|
|
|
#define RD_BUF_LIMIT 254 /* read buffer limit */
|
|
#define WR_BUF_LIMIT 254 /* write buffer limit */
|
|
|
|
#define KEY_DEFAULT 255 /* default port key */
|
|
|
|
|
|
/* Service times:
|
|
|
|
DATA_DELAY = 1.25 us (Z80 DMA data word transfer time)
|
|
PARAM_DELAY = 25 us (STC to STF for first word of two-word command)
|
|
CMD_DELAY = 400 us (STC to STF for one or two-word command execution)
|
|
*/
|
|
|
|
#define DATA_DELAY 2 /* data transfer time */
|
|
#define PARAM_DELAY 40 /* parameter request time */
|
|
#define CMD_DELAY 630 /* command completion time */
|
|
|
|
|
|
/* Unit references */
|
|
|
|
#define MPX_PORTS 8 /* number of visible units */
|
|
#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 */
|
|
|
|
|
|
/* Character constants */
|
|
|
|
#define EOT '\004'
|
|
#define ENQ '\005'
|
|
#define ACK '\006'
|
|
#define BS '\010'
|
|
#define LF '\012'
|
|
#define CR '\015'
|
|
#define DC1 '\021'
|
|
#define DC2 '\022'
|
|
#define DC3 '\023'
|
|
#define ESC '\033'
|
|
#define RS '\036'
|
|
#define DEL '\177'
|
|
|
|
#define XON DC1
|
|
#define XOFF DC3
|
|
|
|
|
|
/* Device flags */
|
|
|
|
#define DEV_V_REV_D (DEV_V_UF + 0) /* firmware revision D (not implemented) */
|
|
|
|
#define DEV_REV_D (1 << DEV_V_REV_D)
|
|
|
|
|
|
/* Unit flags */
|
|
|
|
#define UNIT_V_FASTTIME (UNIT_V_UF + 0) /* fast timing mode */
|
|
#define UNIT_V_CAPSLOCK (UNIT_V_UF + 1) /* caps lock mode */
|
|
|
|
#define UNIT_FASTTIME (1 << UNIT_V_FASTTIME)
|
|
#define UNIT_CAPSLOCK (1 << UNIT_V_CAPSLOCK)
|
|
|
|
|
|
/* Debug flags */
|
|
|
|
#define DEB_CMDS (1 << 0) /* commands and status */
|
|
#define DEB_CPU (1 << 1) /* CPU I/O */
|
|
#define DEB_BUF (1 << 2) /* buffer gets and puts */
|
|
#define DEB_XFER (1 << 3) /* character reads and writes */
|
|
|
|
|
|
/* Multiplexer commands for revisions A/B/C.
|
|
|
|
Commands are either one or two words in length. The one-word format is:
|
|
|
|
+-------------------------------+-------------------------------+
|
|
| 0 . 1 | command opcode | command parameter |
|
|
+-------------------------------+-------------------------------+
|
|
15 - 8 7 - 0
|
|
|
|
The two-word format is:
|
|
|
|
+-------------------------------+-------------------------------+
|
|
| 1 . 1 | command opcode | command value |
|
|
+-------------------------------+-------------------------------+
|
|
| command parameter |
|
|
+---------------------------------------------------------------+
|
|
15 - 8 7 - 0
|
|
|
|
Commands implemented by firmware revision:
|
|
|
|
Rev Cmd Value Operation Status Value(s) Returned
|
|
--- --- ----- ------------------------------- -------------------------------
|
|
ABC 100 - No operation 000000
|
|
ABC 101 - Reset to power-on defaults 100000
|
|
ABC 102 - Enable unsolicited input None, unless UI pending
|
|
ABC 103 1 Disable unsolicited interrupts 000000
|
|
ABC 103 2 Abort DMA transfer 000000
|
|
ABC 104 - Acknowledge Second word of UI status
|
|
ABC 105 key Cancel first receive buffer 000000
|
|
ABC 106 key Cancel all received buffers 000000
|
|
ABC 107 - Fast binary read (none)
|
|
|
|
-BC 140 chr VCP put byte 000000
|
|
-BC 141 - VCP put buffer 000000
|
|
-BC 142 - VCP get byte Character from port 0
|
|
-BC 143 - VCP get buffer 000120
|
|
-BC 144 - Exit VCP mode 000000
|
|
-BC 157 - Enter VCP mode 000000
|
|
|
|
ABC 300 - No operation 000000
|
|
ABC 301 key Request write buffer 000000 or 000376
|
|
ABC 302 key Write data to buffer (none)
|
|
ABC 303 key Set port key 000000 or date code of firmware
|
|
ABC 304 key Set receive type 000000
|
|
ABC 305 key Set character count 000000
|
|
ABC 306 key Set flow control 000000
|
|
ABC 307 key Read data from buffer (none)
|
|
ABC 310 - Download executable (none)
|
|
|
|
-BC 311 key Connect line 000000 or 140000 if no modem
|
|
-BC 312 key Disconnect line 000000 or 140000 if no modem
|
|
-BC 315 key Get modem/port status modem status or 000200 if no modem
|
|
-BC 316 key Enable/disable modem loopback 000000 or 140000 if no modem
|
|
-BC 320 key Terminate active receive buffer 000000
|
|
*/
|
|
|
|
|
|
/* One-word command codes */
|
|
|
|
#define CMD_NOP 0100 /* No operation */
|
|
#define CMD_RESET 0101 /* Reset firmware to power-on defaults */
|
|
#define CMD_ENABLE_UI 0102 /* Enable unsolicited input */
|
|
#define CMD_DISABLE 0103 /* Disable interrupts / Abort DMA Transfer */
|
|
#define CMD_ACK 0104 /* Acknowledge */
|
|
#define CMD_CANCEL 0105 /* Cancel first receive buffer */
|
|
#define CMD_CANCEL_ALL 0106 /* Cancel all received buffers */
|
|
#define CMD_BINARY_READ 0107 /* Fast binary read */
|
|
|
|
#define CMD_VCP_PUT 0140 /* VCP put byte */
|
|
#define CMD_VCP_PUT_BUF 0141 /* VCP put buffer */
|
|
#define CMD_VCP_GET 0142 /* VCP get byte */
|
|
#define CMD_VCP_GET_BUF 0143 /* VCP get buffer */
|
|
#define CMD_VCP_EXIT 0144 /* Exit VCP mode */
|
|
#define CMD_VCP_ENTER 0157 /* Enter VCP mode */
|
|
|
|
|
|
/* Two-word command codes */
|
|
|
|
#define CMD_REQ_WRITE 0301 /* Request write buffer */
|
|
#define CMD_WRITE 0302 /* Write data to buffer */
|
|
#define CMD_SET_KEY 0303 /* Set port key */
|
|
#define CMD_SET_RCV 0304 /* Set receive type */
|
|
#define CMD_SET_COUNT 0305 /* Set character count */
|
|
#define CMD_SET_FLOW 0306 /* Set flow control */
|
|
#define CMD_READ 0307 /* Read data from buffer */
|
|
#define CMD_DL_EXEC 0310 /* Download executable */
|
|
|
|
#define CMD_CN_LINE 0311 /* Connect line */
|
|
#define CMD_DC_LINE 0312 /* Disconnect line */
|
|
#define CMD_GET_STATUS 0315 /* Get modem/port status */
|
|
#define CMD_LOOPBACK 0316 /* Enable/disable modem loopback */
|
|
#define CMD_TERM_BUF 0320 /* Terminate active receive buffer */
|
|
|
|
|
|
/* Sub-command codes */
|
|
|
|
#define SUBCMD_UI 1 /* Disable unsolicited interrupts */
|
|
#define SUBCMD_DMA 2 /* Abort DMA transfer */
|
|
|
|
#define CMD_TWO_WORDS 0200 /* two-word command flag */
|
|
|
|
|
|
/* Unsolicited interrupt reasons */
|
|
|
|
#define UI_REASON_V 8 /* interrupt reason */
|
|
#define UI_REASON (((1 << 8) - 1) << (UI_REASON_V)) /* (UI_REASON_V must be a constant!) */
|
|
|
|
BITFIELD (UI_PORT, 0, 3) /* interrupt port number */
|
|
|
|
#define UI_WRBUF_AVAIL (1 << UI_REASON_V) /* Write buffer available */
|
|
#define UI_LINE_CONN (2 << UI_REASON_V) /* Modem line connected */
|
|
#define UI_LINE_DISC (3 << UI_REASON_V) /* Modem line disconnected */
|
|
#define UI_BRK_RECD (4 << UI_REASON_V) /* Break received */
|
|
#define UI_RDBUF_AVAIL (5 << UI_REASON_V) /* Read buffer available */
|
|
|
|
|
|
/* Return status to CPU */
|
|
|
|
#define ST_OK 0000000 /* Command OK */
|
|
#define ST_DIAG_OK 0000015 /* Diagnostic passes */
|
|
#define ST_VCP_SIZE 0000120 /* VCP buffer size = 80 chars */
|
|
#define ST_NO_SYSMDM 0000200 /* No systems modem card */
|
|
#define ST_TEST_OK 0100000 /* Self test OK */
|
|
#define ST_NO_MODEM 0140000 /* No modem card on port */
|
|
#define ST_BAD_KEY 0135320 /* Bad port key = 0xBAD0 */
|
|
|
|
|
|
/* Bit flags */
|
|
|
|
#define RS_OVERFLOW 0040000 /* Receive status: buffer overflow occurred */
|
|
#define RS_PARTIAL 0020000 /* Receive status: buffer is partial */
|
|
#define RS_ETC_RS 0014000 /* Receive status: terminated by RS */
|
|
#define RS_ETC_DC2 0010000 /* Receive status: terminated by DC2 */
|
|
#define RS_ETC_CR 0004000 /* Receive status: terminated by CR */
|
|
#define RS_ETC_EOT 0000000 /* Receive status: terminated by EOT */
|
|
#define RS_CHAR_COUNT 0003777 /* Receive status: character count */
|
|
|
|
#define WR_NO_ENQACK 0020000 /* Write: no ENQ/ACK this xfer */
|
|
#define WR_ADD_CRLF 0010000 /* Write: add CR/LF if not '_' */
|
|
#define WR_PARTIAL 0004000 /* Write: write is partial */
|
|
#define WR_LENGTH 0003777 /* Write: write length in bytes */
|
|
|
|
#define RT_END_ON_CR 0000200 /* Receive type: end xfer on CR */
|
|
#define RT_END_ON_RS 0000100 /* Receive type: end xfer on RS */
|
|
#define RT_END_ON_EOT 0000040 /* Receive type: end xfer on EOT */
|
|
#define RT_END_ON_DC2 0000020 /* Receive type: end xfer on DC2 */
|
|
#define RT_END_ON_CNT 0000010 /* Receive type: end xfer on count */
|
|
#define RT_END_ON_CHAR 0000004 /* Receive type: end xfer on character */
|
|
#define RT_ENAB_EDIT 0000002 /* Receive type: enable input editing */
|
|
#define RT_ENAB_ECHO 0000001 /* Receive type: enable input echoing */
|
|
|
|
#define FC_FORCE_XON 0000002 /* Flow control: force XON */
|
|
#define FC_XONXOFF 0000001 /* Flow control: enable XON/XOFF */
|
|
|
|
#define CL_GUARD 0000040 /* Connect line: guard tone off or on */
|
|
#define CL_STANDARD 0000020 /* Connect line: standard 212 or V.22 */
|
|
#define CL_BITS 0000010 /* Connect line: bits 10 or 9 */
|
|
#define CL_MODE 0000004 /* Connect line: mode originate or answer */
|
|
#define CL_DIAL 0000002 /* Connect line: dial manual or automatic */
|
|
#define CL_SPEED 0000001 /* Connect line: speed low or high */
|
|
|
|
#define DL_AUTO_ANSWER 0000001 /* Disconnect line: auto-answer enable or disable */
|
|
|
|
#define LB_SPEED 0000004 /* Loopback test: speed low or high */
|
|
#define LB_MODE 0000002 /* Loopback test: mode analog or digital */
|
|
#define LB_TEST 0000001 /* Loopback test: test disable or enable */
|
|
|
|
#define GS_NO_SYSMDM 0000200 /* Get status: systems modem present or absent */
|
|
#define GS_SYSMDM_TO 0000100 /* Get status: systems modem OK or timed out */
|
|
#define GS_NO_MODEM 0000040 /* Get status: modem present or absent */
|
|
#define GS_SPEED 0000020 /* Get status: speed low or high */
|
|
#define GS_LINE 0000001 /* Get status: line disconnected or connected */
|
|
|
|
|
|
/* Bit fields (name, starting bit, bit width) */
|
|
|
|
BITFIELD (CMD_OPCODE, 8, 8) /* Command: opcode */
|
|
BITFIELD (CMD_KEY, 0, 8) /* Command: key */
|
|
|
|
BITFIELD (SK_BPC, 14, 2) /* Set key: bits per character */
|
|
BITFIELD (SK_MODEM, 13, 1) /* Set key: hardwired or modem */
|
|
BITFIELD (SK_BRG, 12, 1) /* Set key: baud rate generator 0/1 */
|
|
BITFIELD (SK_STOPBITS, 10, 2) /* Set key: stop bits */
|
|
BITFIELD (SK_PARITY, 8, 2) /* Set key: parity select */
|
|
BITFIELD (SK_ENQACK, 7, 1) /* Set key: disable or enable ENQ/ACK */
|
|
BITFIELD (SK_BAUDRATE, 3, 4) /* Set key: port baud rate */
|
|
BITFIELD (SK_PORT, 0, 3) /* Set key: port number */
|
|
|
|
BITFIELD (FL_ALERT, 11, 1) /* Port flags: alert for terminate recv buffer */
|
|
BITFIELD (FL_XOFF, 10, 1) /* Port flags: XOFF stopped transmission */
|
|
BITFIELD (FL_BREAK, 9, 1) /* Port flags: UI / break detected */
|
|
BITFIELD (FL_HAVEBUF, 8, 1) /* Port flags: UI / read buffer available */
|
|
BITFIELD (FL_WANTBUF, 7, 1) /* Port flags: UI / write buffer available */
|
|
BITFIELD (FL_RDOVFLOW, 6, 1) /* Port flags: read buffers overflowed */
|
|
BITFIELD (FL_RDFILL, 5, 1) /* Port flags: read buffer is filling */
|
|
BITFIELD (FL_RDEMPT, 4, 1) /* Port flags: read buffer is emptying */
|
|
BITFIELD (FL_WRFILL, 3, 1) /* Port flags: write buffer is filling */
|
|
BITFIELD (FL_WREMPT, 2, 1) /* Port flags: write buffer is emptying */
|
|
BITFIELD (FL_WAITACK, 1, 1) /* Port flags: ENQ sent, waiting for ACK */
|
|
BITFIELD (FL_DO_ENQACK, 0, 1) /* Port flags: do ENQ/ACK handshake */
|
|
|
|
#define SK_BRG_1 SK_BRG
|
|
#define SK_BRG_0 0
|
|
|
|
#define FL_RDFLAGS (FL_RDEMPT | FL_RDFILL | FL_RDOVFLOW)
|
|
#define FL_WRFLAGS (FL_WREMPT | FL_WRFILL)
|
|
#define FL_UI_PENDING (FL_WANTBUF | FL_HAVEBUF | FL_BREAK)
|
|
|
|
#define ACK_LIMIT 1000 /* poll timeout for ACK response */
|
|
#define ENQ_LIMIT 80 /* output chars before ENQ */
|
|
|
|
|
|
/* Packed field values */
|
|
|
|
#define SK_BPC_5 (0 << SK_BPC_V)
|
|
#define SK_BPC_6 (1 << SK_BPC_V)
|
|
#define SK_BPC_7 (2 << SK_BPC_V)
|
|
#define SK_BPC_8 (3 << SK_BPC_V)
|
|
|
|
#define SK_STOP_1 (1 << SK_STOPBITS_V)
|
|
#define SK_STOP_15 (2 << SK_STOPBITS_V)
|
|
#define SK_STOP_2 (3 << SK_STOPBITS_V)
|
|
|
|
#define SK_BAUD_NOCHG (0 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_50 (1 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_75 (2 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_110 (3 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_1345 (4 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_150 (5 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_300 (6 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_1200 (7 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_1800 (8 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_2400 (9 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_4800 (10 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_9600 (11 << SK_BAUDRATE_V)
|
|
#define SK_BAUD_19200 (12 << SK_BAUDRATE_V)
|
|
|
|
|
|
/* Default values */
|
|
|
|
#define SK_PWRUP_0 (SK_BPC_8 | SK_BRG_0 | SK_STOP_1 | SK_BAUD_9600)
|
|
#define SK_PWRUP_1 (SK_BPC_8 | SK_BRG_1 | SK_STOP_1 | SK_BAUD_9600)
|
|
|
|
#define RT_PWRUP (RT_END_ON_CR | RT_END_ON_CHAR | RT_ENAB_EDIT | RT_ENAB_ECHO)
|
|
|
|
|
|
/* Command helpers */
|
|
|
|
#define GET_OPCODE(w) (((w) & CMD_OPCODE) >> CMD_OPCODE_V)
|
|
#define GET_KEY(w) (((w) & CMD_KEY) >> CMD_KEY_V)
|
|
#define GET_BPC(w) (((w) & SK_BPC) >> SK_BPC_V)
|
|
#define GET_BAUDRATE(w) (((w) & SK_BAUDRATE) >> SK_BAUDRATE_V)
|
|
#define GET_PORT(w) (((w) & SK_PORT) >> SK_PORT_V)
|
|
#define GET_UIREASON(w) (((w) & UI_REASON) >> UI_REASON_V)
|
|
#define GET_UIPORT(w) (((w) & UI_PORT) >> UI_PORT_V)
|
|
|
|
|
|
/* Multiplexer controller state variables */
|
|
|
|
typedef enum { idle, cmd, param, exec } STATE;
|
|
|
|
STATE mpx_state = idle; /* controller state */
|
|
|
|
uint16 mpx_ibuf = 0; /* status/data in */
|
|
uint16 mpx_obuf = 0; /* command/data out */
|
|
|
|
uint32 mpx_cmd = 0; /* current command */
|
|
uint32 mpx_param = 0; /* current parameter */
|
|
uint32 mpx_port = 0; /* current port number for R/W */
|
|
uint32 mpx_portkey = 0; /* current port's key */
|
|
int32 mpx_iolen = 0; /* length of current I/O xfer */
|
|
|
|
t_bool mpx_uien = FALSE; /* unsolicited interrupts enabled */
|
|
uint32 mpx_uicode = 0; /* unsolicited interrupt reason and port */
|
|
|
|
struct {
|
|
FLIP_FLOP control; /* control flip-flop */
|
|
FLIP_FLOP flag; /* flag flip-flop */
|
|
FLIP_FLOP flagbuf; /* flag buffer flip-flop */
|
|
} mpx = { CLEAR, CLEAR, CLEAR };
|
|
|
|
/* Multiplexer per-line state variables */
|
|
|
|
uint8 mpx_key [MPX_PORTS]; /* port keys */
|
|
uint16 mpx_config [MPX_PORTS]; /* port configuration */
|
|
uint16 mpx_rcvtype [MPX_PORTS]; /* receive type */
|
|
uint16 mpx_charcnt [MPX_PORTS]; /* character count */
|
|
uint16 mpx_flowcntl [MPX_PORTS]; /* flow control */
|
|
uint8 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */
|
|
uint16 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */
|
|
uint16 mpx_flags [MPX_PORTS]; /* line state flags */
|
|
|
|
/* Multiplexer buffer selectors */
|
|
|
|
typedef enum { ioread, iowrite } IO_OPER; /* I/O operation */
|
|
typedef enum { get, put } BUF_SELECT; /* buffer selector */
|
|
|
|
static const char *const io_op [] = { "read", /* operation names */
|
|
"write" };
|
|
|
|
static const uint32 buf_size [] = { RD_BUF_SIZE, /* buffer sizes */
|
|
WR_BUF_SIZE };
|
|
|
|
static uint32 emptying_flags [2]; /* buffer emptying flags [IO_OPER] */
|
|
static uint32 filling_flags [2]; /* buffer filling flags [IO_OPER] */
|
|
|
|
|
|
/* Multiplexer per-line buffer variables */
|
|
|
|
typedef uint16 BUF_INDEX [MPX_PORTS] [2]; /* buffer index (read and write) */
|
|
|
|
BUF_INDEX mpx_put; /* read/write buffer add index */
|
|
BUF_INDEX mpx_sep; /* read/write buffer separator index */
|
|
BUF_INDEX mpx_get; /* read/write buffer remove index */
|
|
|
|
uint8 mpx_rbuf [MPX_PORTS] [RD_BUF_SIZE]; /* read buffer */
|
|
uint8 mpx_wbuf [MPX_PORTS] [WR_BUF_SIZE]; /* write buffer */
|
|
|
|
|
|
/* Multiplexer local routines */
|
|
|
|
static t_bool exec_command (void);
|
|
static void poll_connection (void);
|
|
static void controller_reset (void);
|
|
static uint32 service_time (uint16 control_word);
|
|
static int32 key_to_port (uint32 key);
|
|
|
|
static void buf_init (IO_OPER rw, uint32 port);
|
|
static uint8 buf_get (IO_OPER rw, uint32 port);
|
|
static void buf_put (IO_OPER rw, uint32 port, uint8 ch);
|
|
static void buf_remove (IO_OPER rw, uint32 port);
|
|
static void buf_term (IO_OPER rw, uint32 port, uint8 header);
|
|
static void buf_free (IO_OPER rw, uint32 port);
|
|
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which);
|
|
static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which);
|
|
static uint32 buf_avail (IO_OPER rw, uint32 port);
|
|
|
|
|
|
/* Multiplexer global routines */
|
|
|
|
IOHANDLER mpx_io;
|
|
|
|
t_stat mpx_line_svc (UNIT *uptr);
|
|
t_stat mpx_cntl_svc (UNIT *uptr);
|
|
t_stat mpx_poll_svc (UNIT *uptr);
|
|
t_stat mpx_reset (DEVICE *dptr);
|
|
t_stat mpx_attach (UNIT *uptr, char *cptr);
|
|
t_stat mpx_detach (UNIT *uptr);
|
|
t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|
t_stat mpx_set_frev (UNIT *uptr, int32 val, char *cptr, void *desc);
|
|
t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|
|
|
|
|
/* MPX data structures.
|
|
|
|
mpx_order MPX line connection order table
|
|
mpx_ldsc MPX terminal multiplexer line descriptors
|
|
mpx_desc MPX terminal multiplexer device descriptor
|
|
mpx_dib MPX device information block
|
|
mpx_unit MPX unit list
|
|
mpx_reg MPX register list
|
|
mpx_mod MPX modifier list
|
|
mpx_deb MPX debug list
|
|
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.
|
|
|
|
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
|
|
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
|
|
ten millisecond period.
|
|
|
|
The controller and poll units are hidden by disabling them, so as to present
|
|
a logical picture of the multiplexer to the user.
|
|
*/
|
|
|
|
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 */
|
|
|
|
DIB mpx_dib = { &mpx_io, MPX };
|
|
|
|
UNIT mpx_unit [] = {
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 0 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 1 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 2 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 3 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 4 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 5 */
|
|
{ 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 */
|
|
};
|
|
|
|
REG mpx_reg [] = {
|
|
{ DRDATA (STATE, mpx_state, 3) },
|
|
{ ORDATA (IBUF, mpx_ibuf, 16), REG_FIT },
|
|
{ ORDATA (OBUF, mpx_obuf, 16), REG_FIT },
|
|
|
|
{ ORDATA (CMD, mpx_cmd, 8) },
|
|
{ ORDATA (PARAM, mpx_param, 16) },
|
|
|
|
{ DRDATA (PORT, mpx_port, 8), PV_LEFT },
|
|
{ DRDATA (PORTKEY, mpx_portkey, 8), PV_LEFT },
|
|
{ DRDATA (IOLEN, mpx_iolen, 16), PV_LEFT },
|
|
|
|
{ FLDATA (UIEN, mpx_uien, 0) },
|
|
{ GRDATA (UIPORT, mpx_uicode, 10, 3, 0) },
|
|
{ GRDATA (UICODE, mpx_uicode, 10, 3, UI_REASON_V) },
|
|
|
|
{ BRDATA (KEYS, mpx_key, 10, 8, MPX_PORTS) },
|
|
{ BRDATA (PCONFIG, mpx_config, 8, 16, MPX_PORTS) },
|
|
{ BRDATA (RCVTYPE, mpx_rcvtype, 8, 16, MPX_PORTS) },
|
|
{ BRDATA (CHARCNT, mpx_charcnt, 8, 16, MPX_PORTS) },
|
|
{ BRDATA (FLOWCNTL, mpx_flowcntl, 8, 16, MPX_PORTS) },
|
|
|
|
{ BRDATA (ENQCNTR, mpx_enq_cntr, 10, 7, MPX_PORTS) },
|
|
{ BRDATA (ACKWAIT, mpx_ack_wait, 10, 10, MPX_PORTS) },
|
|
{ BRDATA (PFLAGS, mpx_flags, 2, 12, MPX_PORTS) },
|
|
|
|
{ BRDATA (RBUF, mpx_rbuf, 8, 8, MPX_PORTS * RD_BUF_SIZE) },
|
|
{ BRDATA (WBUF, mpx_wbuf, 8, 8, MPX_PORTS * WR_BUF_SIZE) },
|
|
|
|
{ BRDATA (GET, mpx_get, 10, 10, MPX_PORTS * 2) },
|
|
{ BRDATA (SEP, mpx_sep, 10, 10, MPX_PORTS * 2) },
|
|
{ BRDATA (PUT, mpx_put, 10, 10, MPX_PORTS * 2) },
|
|
|
|
{ FLDATA (CTL, mpx.control, 0) },
|
|
{ FLDATA (FLG, mpx.flag, 0) },
|
|
{ FLDATA (FBF, mpx.flagbuf, 0) },
|
|
{ ORDATA (SC, mpx_dib.select_code, 6), REG_HRO },
|
|
{ ORDATA (DEVNO, mpx_dib.select_code, 6), REG_HRO },
|
|
|
|
{ BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
MTAB mpx_mod [] = {
|
|
{ UNIT_FASTTIME, UNIT_FASTTIME, "fast timing", "FASTTIME", NULL, NULL, NULL },
|
|
{ UNIT_FASTTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL },
|
|
|
|
{ UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL },
|
|
{ UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL },
|
|
|
|
{ MTAB_XTD | MTAB_VDV, 0, "REV", NULL, &mpx_set_frev, &mpx_show_frev, NULL },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mpx_desc },
|
|
|
|
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mpx_desc },
|
|
|
|
{ MTAB_XTD | MTAB_VDV, 0, "", NULL, NULL, &mpx_status, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &mpx_dev },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mpx_dev },
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
DEBTAB mpx_deb [] = {
|
|
{ "CMDS", DEB_CMDS },
|
|
{ "CPU", DEB_CPU },
|
|
{ "BUF", DEB_BUF },
|
|
{ "XFER", DEB_XFER },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
DEVICE mpx_dev = {
|
|
"MPX", /* device name */
|
|
mpx_unit, /* unit array */
|
|
mpx_reg, /* register array */
|
|
mpx_mod, /* modifier array */
|
|
MPX_PORTS + MPX_CNTLS, /* number of units */
|
|
10, /* address radix */
|
|
31, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
8, /* data width */
|
|
&tmxr_ex, /* examine routine */
|
|
&tmxr_dep, /* deposit routine */
|
|
&mpx_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
&mpx_attach, /* attach routine */
|
|
&mpx_detach, /* detach routine */
|
|
&mpx_dib, /* device information block */
|
|
DEV_DEBUG | DEV_DISABLE, /* device flags */
|
|
0, /* debug control flags */
|
|
mpx_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL }; /* logical device name */
|
|
|
|
|
|
/* I/O signal handler.
|
|
|
|
Commands are sent to the card via an OTA/B. Issuing an STC SC,C causes the
|
|
mux to accept the word (STC causes a NMI on the card). If the command uses
|
|
one word, command execution will commence, and the flag will set on
|
|
completion. If the command uses two words, the flag will be set, indicating
|
|
that the second word should be output via an OTA/B. Command execution will
|
|
commence upon receipt, and the flag will set on completion.
|
|
|
|
When the flag sets for command completion, status or data may be read from
|
|
the card via an LIA/B. If additional status or data words are expected, the
|
|
flag will set when they are available.
|
|
|
|
A command consists of an opcode in the high byte, and a port key or command
|
|
parameter in the low byte. Undefined commands are treated as NOPs.
|
|
|
|
The card firmware executes commands as part of a twelve-event round-robin
|
|
scheduling poll. The card NMI service routine simply sets a flag that is
|
|
interrogated during polling. The poll sequence is advanced after each
|
|
command. This implies that successive commands incur a delay of at least one
|
|
poll-loop's execution time. On an otherwise quiescent card, this delay is
|
|
approximately 460 Z80 instructions, or about 950 usec. The average command
|
|
initiation time is half of that, or roughly 425 usec.
|
|
|
|
If a detected command requires a second word, the card sits in a tight loop,
|
|
waiting for the OTx that indicates that the parameter is available. Command
|
|
initiation from parameter receipt is about 25 usec.
|
|
|
|
For reads and writes to card buffers, the on-board DMA controller is used.
|
|
The CPU uses DCPC to handle the transfer, but the data transfer time is
|
|
limited by the Z80 DMA, which can process a word in about 1.25 usec.
|
|
|
|
For most cards, the hardware POPIO signal sets the flag buffer and flag
|
|
flip-flops, while CRS clears the control flip-flop. For this card, the
|
|
control and flags are cleared together by CRS, and POPIO is not used.
|
|
|
|
Implementation notes:
|
|
|
|
1. "Enable unsolicited input" is the only command that does not set the
|
|
device flag upon completion. Therefore, the CPU has no way of knowing
|
|
when the command has completed. Because the command in the input latch
|
|
is recorded in the NMI handler, but actual execution only begins when the
|
|
scheduler polls for the command indication, it is possible for another
|
|
command to be sent to the card before the "Enable unsolicited input"
|
|
command is recognized. In this case, the second command overwrites the
|
|
first and is executed by the scheduler poll. Under simulation, this
|
|
condition occurs when the OTx and STC processors are entered with
|
|
mpx_state = cmd.
|
|
|
|
2. The "Fast binary read" command inhibits all other commands until the card
|
|
is reset.
|
|
*/
|
|
|
|
uint32 mpx_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data)
|
|
{
|
|
static const char *output_state [] = { "Command", "Command override", "Parameter", "Data" };
|
|
static const char *input_state [] = { "Status", "Invalid status", "Parameter", "Data" };
|
|
const char *hold_or_clear = (signal_set & ioCLF ? ",C" : "");
|
|
int32 delay;
|
|
IOSIGNAL signal;
|
|
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */
|
|
|
|
while (working_set) {
|
|
signal = IONEXT (working_set); /* isolate next signal */
|
|
|
|
switch (signal) { /* dispatch I/O signal */
|
|
|
|
case ioCLF: /* clear flag flip-flop */
|
|
mpx.flag = mpx.flagbuf = CLEAR; /* clear flag and flag buffer */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [CLF] Flag cleared\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioSTF: /* set flag flip-flop */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [STF] Flag set\n", sim_deb);
|
|
/* fall into ENF */
|
|
|
|
case ioENF: /* enable flag */
|
|
mpx.flag = mpx.flagbuf = SET; /* set flag and flag buffer */
|
|
break;
|
|
|
|
|
|
case ioSFC: /* skip if flag is clear */
|
|
setstdSKF (mpx);
|
|
break;
|
|
|
|
|
|
case ioSFS: /* skip if flag is set */
|
|
setstdSKF (mpx);
|
|
break;
|
|
|
|
|
|
case ioIOI: /* I/O data input */
|
|
stat_data = IORETURN (SCPE_OK, mpx_ibuf); /* return info */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fprintf (sim_deb, ">>MPX cpu: [LIx%s] %s = %06o\n",
|
|
hold_or_clear, input_state [mpx_state], mpx_ibuf);
|
|
|
|
if (mpx_state == exec) /* if this is input data word */
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* continue transmission */
|
|
break;
|
|
|
|
|
|
case ioIOO: /* I/O data output */
|
|
mpx_obuf = IODATA (stat_data); /* save word */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fprintf (sim_deb, ">>MPX cpu: [OTx%s] %s = %06o\n",
|
|
hold_or_clear, output_state [mpx_state], mpx_obuf);
|
|
|
|
if (mpx_state == param) { /* if this is parameter word */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* do command now */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [OTx%s] Command %03o parameter %06o scheduled, "
|
|
"time = %d\n", hold_or_clear, mpx_cmd, mpx_obuf, CMD_DELAY);
|
|
}
|
|
|
|
else if (mpx_state == exec) /* else if this is output data word */
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* then do transmission */
|
|
break;
|
|
|
|
|
|
case ioCRS: /* control reset */
|
|
controller_reset (); /* reset firmware to power-on defaults */
|
|
mpx_obuf = 0; /* clear output buffer */
|
|
|
|
mpx.control = CLEAR; /* clear control */
|
|
mpx.flagbuf = CLEAR; /* clear flag buffer */
|
|
mpx.flag = CLEAR; /* clear flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [CRS] Controller reset\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioCLC: /* clear control flip-flop */
|
|
mpx.control = CLEAR; /* clear control */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [CLC%s] Control cleared\n", hold_or_clear);
|
|
break;
|
|
|
|
|
|
case ioSTC: /* set control flip-flop */
|
|
mpx.control = SET; /* set control */
|
|
|
|
if (mpx_cmd == CMD_BINARY_READ) /* executing fast binary read? */
|
|
break; /* further command execution inhibited */
|
|
|
|
mpx_cmd = GET_OPCODE (mpx_obuf); /* get command opcode */
|
|
mpx_portkey = GET_KEY (mpx_obuf); /* get port key */
|
|
|
|
if (mpx_state == cmd) /* already scheduled? */
|
|
sim_cancel (&mpx_cntl); /* cancel to get full delay */
|
|
|
|
mpx_state = cmd; /* set command state */
|
|
|
|
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */
|
|
delay = PARAM_DELAY; /* specify parameter wait */
|
|
else /* one-word command */
|
|
delay = CMD_DELAY; /* specify command wait */
|
|
|
|
sim_activate (&mpx_cntl, delay); /* schedule command */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [STC%s] Command %03o key %d scheduled, "
|
|
"time = %d\n", hold_or_clear, mpx_cmd, mpx_portkey, delay);
|
|
break;
|
|
|
|
|
|
case ioEDT: /* end data transfer */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fputs (">>MPX cpu: [EDT] DCPC transfer ended\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioSIR: /* set interrupt request */
|
|
setstdPRL (mpx); /* set standard PRL signal */
|
|
setstdIRQ (mpx); /* set standard IRQ signal */
|
|
setstdSRQ (mpx); /* set standard SRQ signal */
|
|
break;
|
|
|
|
|
|
case ioIAK: /* interrupt acknowledge */
|
|
mpx.flagbuf = CLEAR; /* clear flag buffer */
|
|
break;
|
|
|
|
|
|
default: /* all other signals */
|
|
break; /* are ignored */
|
|
}
|
|
|
|
working_set = working_set & ~signal; /* remove current signal from set */
|
|
}
|
|
|
|
return stat_data;
|
|
}
|
|
|
|
|
|
/* Command executor.
|
|
|
|
We are called by the controller service routine to process one- and two-word
|
|
commands. For two-word commands, the parameter word is present in mpx_param.
|
|
The return value indicates whether the card flag should be set upon
|
|
completion.
|
|
|
|
Most commands execute and complete directly. The read and write commands,
|
|
however, transition to the execution state to simulate the DMA transfer, and
|
|
the "Download executable" command does the same to receive the download from
|
|
the CPU.
|
|
|
|
Several commands were added for the B firmware revision, and the various
|
|
revisions of the RTE drivers sent some commands that were never implemented
|
|
in the mux firmware. The command protocol treated unknown commands as NOPs,
|
|
meaning that the command (and parameter, if it was a two-word command) was
|
|
absorbed and the card flag was set as though the command completed normally.
|
|
This allowed interoperability between firmware and driver revisions.
|
|
|
|
Commands that refer to ports do so indirectly by passing a port key, rather
|
|
than a port number. The key-to-port translation is established by the "Set
|
|
port key" command. If a key is not found in the table, the command is not
|
|
executed, and the status return is ST_BAD_KEY, which in hex is "BAD0".
|
|
|
|
Implementation notes:
|
|
|
|
1. The "Reset to power-on defaults" command causes the firmware to disable
|
|
interrupts and jump to the power-on initialization routine, exactly as
|
|
though the Z80 had received a hardware reset.
|
|
|
|
2. The "Abort DMA transfer" command works because STC causes NMI, so the
|
|
command is executed even in the middle of a DMA transfer. The OTx of the
|
|
command will be sent to the buffer if a "Write data to buffer" command is
|
|
in progress, but the STC will cause this routine to be called, which will
|
|
cancel the buffer and return the controller to the idle state. Note that
|
|
this command might be sent with no transfer in progress, in which case
|
|
nothing is done.
|
|
|
|
3. In response to an "Enable unsolicited interrupts" command, the controller
|
|
service is scheduled to check for a pending UI. If one is found, the
|
|
first UI status word is placed in the input buffer, and an interrupt is
|
|
generated by setting the flag. This causes entry to the driver, which
|
|
issues an "Acknowledge" command to obtain the second status word.
|
|
|
|
It is possible, however, for the interrupt to be ignored. For example,
|
|
the driver may be waiting for a "write buffer available" UI when it is
|
|
called to begin a write to a different port. If the flag is set by
|
|
the UI after RTE has been entered, the interrupt will be held off, and
|
|
the STC sc,C instruction that begins the command sequence will clear the
|
|
flag, removing the interrupt entirely. In this case, the controller will
|
|
reissue the UI when the next "Enable unsolicited interrupts" command is
|
|
sent.
|
|
|
|
Note that the firmware reissues the same UI, rather than recomputing UIs
|
|
and potentially selecting a different one of higher priority.
|
|
|
|
4. The "Fast binary read" command apparently was intended to facilitate
|
|
booting from a 264x tape drive, although no boot loader ROM for the
|
|
multiplexer was ever released. It sends the fast binary read escape
|
|
sequence (ESC e) to the terminal and then packs each pair of characters
|
|
received into a word and sends it to the CPU, accompanied by the device
|
|
flag.
|
|
|
|
The multiplexer firmware disables interrupts and then manipulates the SIO
|
|
for port 0 directly. Significantly, it does no interpretation of the
|
|
incoming data and sits in an endless I/O loop, so the only way to exit
|
|
the command is to reset the card with a CRS (front panel PRESET or CLC 0
|
|
instruction execution). Sending a command will not work; although the
|
|
NMI will interrupt the fast binary read, the NMI handler simply sets a
|
|
flag that is tested by the scheduler poll. Because the processor is in
|
|
an endless loop, control never returns to the scheduler, so the command
|
|
is never seen.
|
|
|
|
5. The "Terminate active receive buffer" behavior is a bit tricky. If the
|
|
read buffer has characters, the buffer is terminated as though a
|
|
"terminate on count" condition occurred. If the buffer is empty,
|
|
however, a "terminate on count = 1" condition is established. When a
|
|
character is received, the buffer is terminated, and the buffer
|
|
termination count is reset to 254.
|
|
*/
|
|
|
|
static t_bool exec_command (void)
|
|
{
|
|
int32 port;
|
|
uint32 svc_time;
|
|
t_bool set_flag = TRUE; /* flag is normally set on completion */
|
|
STATE next_state = idle; /* command normally executes to completion */
|
|
|
|
mpx_ibuf = ST_OK; /* return status is normally OK */
|
|
|
|
switch (mpx_cmd) {
|
|
|
|
case CMD_NOP: /* no operation */
|
|
break; /* just ignore */
|
|
|
|
|
|
case CMD_RESET: /* reset firmware */
|
|
controller_reset (); /* reset program variables */
|
|
mpx_ibuf = ST_TEST_OK; /* return self-test OK code */
|
|
break;
|
|
|
|
|
|
case CMD_ENABLE_UI:
|
|
mpx_uien = TRUE; /* enable unsolicited interrupts */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* and schedule controller for UI check */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Controller status check scheduled, "
|
|
"time = %d\n", CMD_DELAY);
|
|
|
|
set_flag = FALSE; /* do not set the flag at completion */
|
|
break;
|
|
|
|
|
|
case CMD_DISABLE:
|
|
switch (mpx_portkey) {
|
|
case SUBCMD_UI:
|
|
mpx_uien = FALSE; /* disable unsolicited interrupts */
|
|
break;
|
|
|
|
case SUBCMD_DMA:
|
|
if (mpx_flags [mpx_port] & FL_WRFILL) /* write buffer xfer in progress? */
|
|
buf_cancel (iowrite, mpx_port, put); /* cancel it */
|
|
else if (mpx_flags [mpx_port] & FL_RDEMPT) /* read buffer xfer in progress? */
|
|
buf_cancel (ioread, mpx_port, get); /* cancel it */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_ACK: /* acknowledge unsolicited interrupt */
|
|
switch (mpx_uicode & UI_REASON) {
|
|
|
|
case UI_WRBUF_AVAIL: /* write buffer notification */
|
|
mpx_flags [mpx_port] &= ~FL_WANTBUF; /* clear flag */
|
|
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer available */
|
|
break;
|
|
|
|
case UI_RDBUF_AVAIL: /* read buffer notification */
|
|
mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */
|
|
|
|
mpx_ibuf = buf_get (ioread, mpx_port) << 8 | /* get header value and position */
|
|
buf_len (ioread, mpx_port, get); /* and include buffer length */
|
|
|
|
if (mpx_flags [mpx_port] & FL_RDOVFLOW) { /* did a buffer overflow? */
|
|
mpx_ibuf = mpx_ibuf | RS_OVERFLOW; /* report it */
|
|
mpx_flags [mpx_port] &= ~FL_RDOVFLOW; /* clear overflow flag */
|
|
}
|
|
break;
|
|
|
|
case UI_BRK_RECD: /* break received */
|
|
mpx_flags [mpx_port] &= ~FL_BREAK; /* clear flag */
|
|
mpx_ibuf = 0; /* 2nd word is zero */
|
|
break;
|
|
}
|
|
|
|
mpx_uicode = 0; /* clear notification code */
|
|
break;
|
|
|
|
|
|
case CMD_CANCEL: /* cancel first read buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
buf_cancel (ioread, port, get); /* cancel get buffer */
|
|
|
|
if ((buf_avail (ioread, port) == 1) && /* one buffer remaining? */
|
|
!(mpx_flags [port] & FL_RDFILL)) /* and not filling it? */
|
|
mpx_flags [port] |= FL_HAVEBUF; /* indicate buffer availability */
|
|
break;
|
|
|
|
|
|
case CMD_CANCEL_ALL: /* cancel all read buffers */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
buf_init (ioread, port); /* reinitialize read buffers */
|
|
break;
|
|
|
|
|
|
case CMD_BINARY_READ: /* fast binary read */
|
|
for (port = 0; port < MPX_PORTS; port++)
|
|
sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */
|
|
|
|
mpx_flags [0] = 0; /* clear port 0 state flags */
|
|
mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */
|
|
mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */
|
|
|
|
tmxr_putc_ln (&mpx_ldsc [0], ESC); /* send fast binary read */
|
|
tmxr_putc_ln (&mpx_ldsc [0], 'e'); /* escape sequence to port 0 */
|
|
tmxr_poll_tx (&mpx_desc); /* flush output */
|
|
|
|
next_state = exec; /* set execution state */
|
|
break;
|
|
|
|
|
|
case CMD_REQ_WRITE: /* request write buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
if (buf_avail (iowrite, port) > 0) /* is a buffer available? */
|
|
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer limit */
|
|
|
|
else {
|
|
mpx_ibuf = 0; /* report none available */
|
|
mpx_flags [port] |= FL_WANTBUF; /* set buffer request */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_WRITE: /* write to buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) { /* port defined? */
|
|
mpx_port = port; /* save port number */
|
|
mpx_iolen = mpx_param & WR_LENGTH; /* save request length */
|
|
next_state = exec; /* set execution state */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_SET_KEY: /* set port key and configuration */
|
|
port = GET_PORT (mpx_param); /* get target port number */
|
|
mpx_key [port] = (uint8) mpx_portkey; /* set port key */
|
|
mpx_config [port] = mpx_param; /* set port configuration word */
|
|
|
|
svc_time = service_time (mpx_param); /* get service time for baud rate */
|
|
|
|
if (svc_time) /* want to change? */
|
|
mpx_unit [port].wait = svc_time; /* set service time */
|
|
|
|
mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */
|
|
break;
|
|
|
|
|
|
case CMD_SET_RCV: /* set receive type */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_rcvtype [port] = mpx_param; /* save port receive type */
|
|
break;
|
|
|
|
|
|
case CMD_SET_COUNT: /* set character count */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_charcnt [port] = mpx_param; /* save port character count */
|
|
break;
|
|
|
|
|
|
case CMD_SET_FLOW: /* set flow control */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_flowcntl [port] = mpx_param & FC_XONXOFF; /* save port flow control */
|
|
|
|
if (mpx_param & FC_FORCE_XON) /* force XON? */
|
|
mpx_flags [port] &= ~FL_XOFF; /* resume transmission if suspended */
|
|
break;
|
|
|
|
|
|
case CMD_READ: /* read from buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) { /* port defined? */
|
|
mpx_port = port; /* save port number */
|
|
mpx_iolen = mpx_param; /* save request length */
|
|
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* schedule the transfer */
|
|
next_state = exec; /* set execution state */
|
|
set_flag = FALSE; /* no flag until word ready */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_DL_EXEC: /* Download executable */
|
|
mpx_iolen = mpx_param; /* save request length */
|
|
next_state = exec; /* set execution state */
|
|
break;
|
|
|
|
|
|
case CMD_CN_LINE: /* connect modem line */
|
|
case CMD_DC_LINE: /* disconnect modem line */
|
|
case CMD_LOOPBACK: /* enable/disable modem loopback */
|
|
mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */
|
|
break;
|
|
|
|
|
|
case CMD_GET_STATUS: /* get modem status */
|
|
mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */
|
|
break;
|
|
|
|
|
|
case CMD_TERM_BUF: /* terminate active receive buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
if (buf_len (ioread, port, put) > 0) { /* any chars in buffer? */
|
|
buf_term (ioread, port, 0); /* terminate buffer and set header */
|
|
|
|
if (buf_avail (ioread, port) == 1) /* first read buffer? */
|
|
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */
|
|
}
|
|
|
|
else { /* buffer is empty */
|
|
mpx_charcnt [port] = 1; /* set to terminate on one char */
|
|
mpx_flags [port] |= FL_ALERT; /* set alert flag */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_VCP_PUT: /* VCP put byte */
|
|
case CMD_VCP_PUT_BUF: /* VCP put buffer */
|
|
case CMD_VCP_GET: /* VCP get byte */
|
|
case CMD_VCP_GET_BUF: /* VCP get buffer */
|
|
case CMD_VCP_EXIT: /* Exit VCP mode */
|
|
case CMD_VCP_ENTER: /* Enter VCP mode */
|
|
|
|
default: /* unknown command */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Unknown command %03o ignored\n", mpx_cmd);
|
|
}
|
|
|
|
mpx_state = next_state;
|
|
return set_flag;
|
|
}
|
|
|
|
|
|
/* Multiplexer controller service.
|
|
|
|
The controller service handles commands and data transfers to and from the
|
|
CPU. The delay in scheduling the controller service represents the firmware
|
|
command or data execution time. The controller may be in one of four states
|
|
upon entry: idle, first word of command received (cmd), command parameter
|
|
received (param), or data transfer (exec).
|
|
|
|
Entry in the command state causes execution of one-word commands and
|
|
solicitation of command parameters for two-word commands, which are executed
|
|
when entering in the parameter state.
|
|
|
|
Entry in the data transfer state moves one word between the CPU and a read or
|
|
write buffer. For writes, the write buffer is filled with words from the
|
|
CPU. Once the indicated number of words have been transferred, the
|
|
appropriate line service is scheduled to send the characters. For reads,
|
|
characters are unloaded from the read buffer to the CPU; an odd-length
|
|
transfer is padded with a blank. A read of fewer characters than are present
|
|
in the buffer will return the remaining characters when the next read is
|
|
performed.
|
|
|
|
Each read or write is terminated by the CPU sending one additional word (the
|
|
RTE drivers send -1). The command completes when this word is acknowledged
|
|
by the card setting the device flag. For zero-length writes, this additional
|
|
word will be the only word sent.
|
|
|
|
Data transfer is also used by the "Download executable" command to absorb the
|
|
downloaded program. The firmware jumps to location 5100 hex in the
|
|
downloaded program upon completion of reception. It is the responsibility of
|
|
the program to return to the multiplexer firmware and to return to the CPU
|
|
whatever status is appropriate when it is done. Under simulation, we simply
|
|
"sink" the program and return status compatible with the multiplexer
|
|
diagnostic program to simulate a passing test.
|
|
|
|
Entry in the idle state checks for unsolicited interrupts. UIs are sent to
|
|
the host when the controller is idle, UIs have been enabled, and a UI
|
|
condition exists. If a UI is not acknowledged, it will remain pending and
|
|
will be reissued the next time the controller is idle and UIs have been
|
|
enabled.
|
|
|
|
UI conditions are kept in the per-port flags. The UI conditions are write
|
|
buffer available, read buffer available, break received, modem line
|
|
connected, and modem line disconnected. The latter two conditions are not
|
|
implemented in this simulation. If a break condition occurs at the same time
|
|
as a read buffer completion, the break has priority; the buffer UI will occur
|
|
after the break UI is acknowledged.
|
|
|
|
The firmware checks for UI condition flags as part of the scheduler polling
|
|
loop. Under simulation, though, UIs can occur only in two places: the point
|
|
of origin (e.g., termination of a read buffer), or the "Enable unsolicited
|
|
input" command executor. UIs will be generated at the point of origin only
|
|
if the simulator is idle. If the simulator is not idle, it is assumed that
|
|
UIs have been disabled to execute the current command and will be reenabled
|
|
when the command sequence is complete.
|
|
|
|
When the multiplexer is reset, and before the port keys are set, all ports
|
|
enter "echoplex" mode. In this mode, characters received are echoed back as
|
|
a functional test. Each port terminates buffers on CR reception. We detect
|
|
this condition, cancel the buffer, and discard the buffer termination UI.
|
|
|
|
Implementation notes:
|
|
|
|
1. The firmware transfers the full amount requested by the CPU, even if the
|
|
transfer is longer than the buffer. Also, zero-length transfers program
|
|
the card DMA chip to transfer 0 bytes; this results in a transfer of 217
|
|
bytes, per the Zilog databook. Under simulation, writes beyond the
|
|
buffer are accepted from the CPU but discarded, and reads beyond the
|
|
buffer return blanks.
|
|
|
|
2. We should never return from this routine in the "cmd" state, so debugging
|
|
will report "internal error!" if we do.
|
|
*/
|
|
|
|
t_stat mpx_cntl_svc (UNIT *uptr)
|
|
{
|
|
uint8 ch;
|
|
uint32 i;
|
|
t_bool add_crlf;
|
|
t_bool set_flag = TRUE;
|
|
STATE last_state = mpx_state;
|
|
|
|
static const char *cmd_state [] = { "complete", "internal error!", "waiting for parameter", "executing" };
|
|
|
|
|
|
switch (mpx_state) { /* dispatch on current state */
|
|
|
|
case idle: /* controller idle */
|
|
set_flag = FALSE; /* assume no UI */
|
|
|
|
if (mpx_uicode) { /* unacknowledged UI? */
|
|
if (mpx_uien == TRUE) { /* interrupts enabled? */
|
|
mpx_port = GET_UIPORT (mpx_uicode); /* get port number */
|
|
mpx_portkey = mpx_key [mpx_port]; /* get port key */
|
|
mpx_ibuf = mpx_uicode & UI_REASON | mpx_portkey; /* report UI reason and port key */
|
|
set_flag = TRUE; /* reissue host interrupt */
|
|
mpx_uien = FALSE; /* disable UI */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt reissued, "
|
|
"reason = %d\n", mpx_port, mpx_portkey, GET_UIREASON (mpx_uicode));
|
|
}
|
|
}
|
|
|
|
else { /* no unacknowledged UI */
|
|
for (i = 0; i < MPX_PORTS; i++) { /* check all ports for UIs */
|
|
if (mpx_flags [i] & FL_UI_PENDING) { /* pending UI? */
|
|
mpx_portkey = mpx_key [i]; /* get port key */
|
|
|
|
if (mpx_portkey == KEY_DEFAULT) { /* key defined? */
|
|
if (mpx_flags [i] & FL_HAVEBUF) /* no, is this read buffer avail? */
|
|
buf_cancel (ioread, i, get); /* cancel buffer */
|
|
|
|
mpx_flags [i] &= ~FL_UI_PENDING; /* cancel pending UI */
|
|
}
|
|
|
|
else if (mpx_uien == TRUE) { /* interrupts enabled? */
|
|
if ((mpx_flags [i] & FL_WANTBUF) && /* port wants a write buffer? */
|
|
(buf_avail (iowrite, i) > 0)) /* and one is available? */
|
|
mpx_uicode = UI_WRBUF_AVAIL; /* set UI reason */
|
|
|
|
else if (mpx_flags [i] & FL_BREAK) /* received a line BREAK? */
|
|
mpx_uicode = UI_BRK_RECD; /* set UI reason */
|
|
|
|
else if (mpx_flags [i] & FL_HAVEBUF) /* have a read buffer ready? */
|
|
mpx_uicode = UI_RDBUF_AVAIL; /* set UI reason */
|
|
|
|
if (mpx_uicode) { /* UI to send? */
|
|
mpx_port = i; /* set port number for Acknowledge */
|
|
mpx_ibuf = mpx_uicode | mpx_portkey; /* merge UI reason and port key */
|
|
mpx_uicode = mpx_uicode | mpx_port; /* save UI reason and port */
|
|
set_flag = TRUE; /* interrupt host */
|
|
mpx_uien = FALSE; /* disable UI */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt generated, "
|
|
"reason = %d\n", i, mpx_portkey, GET_UIREASON (mpx_uicode));
|
|
|
|
break; /* quit after first UI */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case cmd: /* command state */
|
|
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */
|
|
mpx_state = param; /* look for parameter before executing */
|
|
else
|
|
set_flag = exec_command (); /* execute one-word command */
|
|
break;
|
|
|
|
|
|
case param: /* parameter get state */
|
|
mpx_param = mpx_obuf; /* save parameter */
|
|
set_flag = exec_command (); /* execute two-word command */
|
|
break;
|
|
|
|
|
|
case exec: /* execution state */
|
|
switch (mpx_cmd) {
|
|
|
|
case CMD_BINARY_READ: /* fast binary read */
|
|
mpx_flags [0] &= ~FL_HAVEBUF; /* data word was picked up by CPU */
|
|
set_flag = FALSE; /* suppress device flag */
|
|
break;
|
|
|
|
|
|
case CMD_WRITE: /* transfer data to buffer */
|
|
if (mpx_iolen <= 0) { /* last (or only) entry? */
|
|
mpx_state = idle; /* idle controller */
|
|
|
|
if (mpx_iolen < 0) /* tie-off for buffer complete? */
|
|
break; /* we're done */
|
|
}
|
|
|
|
add_crlf = ((mpx_param & /* CRLF should be added */
|
|
(WR_ADD_CRLF | WR_PARTIAL)) == WR_ADD_CRLF);
|
|
|
|
for (i = 0; i < 2; i++) /* output one or two chars */
|
|
if (mpx_iolen > 0) { /* more to do? */
|
|
if (i) /* high or low byte? */
|
|
ch = (uint8) (mpx_obuf & 0377); /* low byte */
|
|
else
|
|
ch = mpx_obuf >> 8; /* high byte */
|
|
|
|
if ((mpx_iolen == 1) && /* final char? */
|
|
(ch == '_') && add_crlf) { /* underscore and asking for CRLF? */
|
|
|
|
add_crlf = FALSE; /* suppress CRLF */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character '_' "
|
|
"suppressed CR/LF\n", mpx_port);
|
|
}
|
|
|
|
else if (buf_len (iowrite, mpx_port, put) < WR_BUF_LIMIT)
|
|
buf_put (iowrite, mpx_port, ch); /* add char to buffer if space avail */
|
|
|
|
mpx_iolen = mpx_iolen - 1; /* drop remaining count */
|
|
}
|
|
|
|
if (mpx_iolen == 0) { /* buffer done? */
|
|
if (add_crlf) { /* want CRLF? */
|
|
buf_put (iowrite, mpx_port, CR); /* add CR to buffer */
|
|
buf_put (iowrite, mpx_port, LF); /* add LF to buffer */
|
|
}
|
|
|
|
buf_term (iowrite, mpx_port, mpx_param >> 8); /* terminate buffer */
|
|
mpx_iolen = -1; /* mark as done */
|
|
}
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS) &&
|
|
(sim_is_active (&mpx_unit [mpx_port]) == 0))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d service scheduled, "
|
|
"time = %d\n", mpx_port, mpx_unit [mpx_port].wait);
|
|
|
|
sim_activate (&mpx_unit [mpx_port], /* start line service */
|
|
mpx_unit [mpx_port].wait);
|
|
break;
|
|
|
|
|
|
case CMD_READ: /* transfer data from buffer */
|
|
if (mpx_iolen < 0) { /* input complete? */
|
|
if (mpx_obuf == 0177777) { /* "tie-off" word received? */
|
|
if (buf_len (ioread, mpx_port, get) == 0) { /* buffer now empty? */
|
|
buf_free (ioread, mpx_port); /* free buffer */
|
|
|
|
if (buf_avail (ioread, mpx_port) == 1) /* another buffer available? */
|
|
mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate availability */
|
|
}
|
|
|
|
mpx_state = idle; /* idle controller */
|
|
}
|
|
|
|
else
|
|
set_flag = FALSE; /* ignore word */
|
|
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) /* input one or two chars */
|
|
if (mpx_iolen > 0) { /* more to transfer? */
|
|
if (buf_len (ioread, mpx_port, get) > 0) /* more chars available? */
|
|
ch = buf_get (ioread, mpx_port); /* get char from buffer */
|
|
else /* buffer exhausted */
|
|
ch = ' '; /* pad with blank */
|
|
|
|
if (i) /* high or low byte? */
|
|
mpx_ibuf = mpx_ibuf | ch; /* low byte */
|
|
else
|
|
mpx_ibuf = ch << 8; /* high byte */
|
|
|
|
mpx_iolen = mpx_iolen - 1; /* drop count */
|
|
}
|
|
|
|
else /* odd number of chars */
|
|
mpx_ibuf = mpx_ibuf | ' '; /* pad last with blank */
|
|
|
|
if (mpx_iolen == 0) /* end of host xfer? */
|
|
mpx_iolen = -1; /* mark as done */
|
|
|
|
break;
|
|
|
|
|
|
case CMD_DL_EXEC: /* sink data from host */
|
|
if (mpx_iolen <= 0) { /* final entry? */
|
|
mpx_state = idle; /* idle controller */
|
|
mpx_ibuf = ST_DIAG_OK; /* return diag passed status */
|
|
}
|
|
|
|
else {
|
|
if (mpx_iolen > 0) /* more from host? */
|
|
mpx_iolen = mpx_iolen - 2; /* sink two bytes */
|
|
|
|
if (mpx_iolen <= 0) /* finished download? */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* schedule completion */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Download completion scheduled, "
|
|
"time = %d\n", CMD_DELAY);
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
default: /* no other entries allowed */
|
|
return SCPE_IERR; /* simulator error! */
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS) && /* debug print? */
|
|
(last_state != mpx_state)) { /* and state change? */
|
|
fprintf (sim_deb, ">>MPX cmds: Command %03o ", mpx_cmd);
|
|
|
|
if ((mpx_cmd & CMD_TWO_WORDS) && (mpx_state != param))
|
|
fprintf (sim_deb, "parameter %06o ", mpx_param);
|
|
|
|
fputs (cmd_state [mpx_state], sim_deb);
|
|
fputc ('\n', sim_deb);
|
|
}
|
|
|
|
if (set_flag) {
|
|
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: Flag set\n", sim_deb);
|
|
}
|
|
|
|
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
|
|
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.
|
|
|
|
Each read and write buffer begins with a reserved header byte that stores
|
|
per-buffer information, such as whether handshaking should be suppressed
|
|
during output, or the specific cause of termination for input. Buffer
|
|
termination sets the header byte with the appropriate flags.
|
|
|
|
For output, a character counter is maintained and is incremented if ENQ/ACK
|
|
handshaking is enabled for the current port and request. If the counter
|
|
limit is reached, an ENQ is sent, and a flag is set to suspend transmission
|
|
until an ACK is received. If the last character of the buffer is sent, the
|
|
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
|
|
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
|
|
flag is set, and transmission is suspended until a corresponding XON is
|
|
received. If the character is an ACK and is in response to a previously sent
|
|
ENQ, it is discarded, and transmission is reenabled.
|
|
|
|
If editing is enabled, a BS will delete the last character in the read
|
|
buffer, and a DEL will delete the entire buffer. Otherwise, buffer
|
|
termination conditions are checked (end on character, end on count, or
|
|
buffer full), and if observed, the read buffer is terminated, and a read
|
|
buffer available UI condition is signalled.
|
|
|
|
Implementation notes:
|
|
|
|
1. The firmware echoes an entered BS before checking the buffer count to see
|
|
if there are any characters to delete. Under simulation, we only echo if
|
|
the buffer is not empty.
|
|
|
|
2. The "Fast binary read" command inhibits the normal transmit and receive
|
|
processing. Instead, a pair of characters are sought on line 0 to fill
|
|
the input buffer. When they are received, the device flag is set. The
|
|
CPU will do a LIx sc,C to retrieve the data and reset the flag.
|
|
*/
|
|
|
|
t_stat mpx_line_svc (UNIT *uptr)
|
|
{
|
|
const int32 port = uptr - mpx_unit; /* port number */
|
|
const uint16 rt = mpx_rcvtype [port]; /* receive type for port */
|
|
const uint32 data_bits = 5 + GET_BPC (mpx_config [port]); /* number of data bits */
|
|
const uint32 data_mask = (1 << data_bits) - 1; /* mask for data bits */
|
|
const t_bool fast_timing = (uptr->flags & UNIT_FASTTIME) != 0; /* port is set for fast timing */
|
|
const t_bool fast_binary_read = (mpx_cmd == CMD_BINARY_READ); /* fast binary read in progress */
|
|
|
|
uint8 ch;
|
|
int32 chx;
|
|
uint16 read_length;
|
|
t_stat status = SCPE_OK;
|
|
t_bool recv_loop = !fast_binary_read; /* bypass if fast binary read */
|
|
t_bool xmit_loop = !(fast_binary_read || /* bypass if fast read or output suspended */
|
|
(mpx_flags [port] & (FL_WAITACK | FL_XOFF)));
|
|
|
|
|
|
/* Transmission service */
|
|
|
|
while (xmit_loop && (buf_len (iowrite, port, get) > 0)) { /* character available to output? */
|
|
if ((mpx_flags [port] & FL_WREMPT) == 0) { /* has buffer started emptying? */
|
|
chx = buf_get (iowrite, port) << 8; /* get header value and position */
|
|
|
|
if (fast_timing || (chx & WR_NO_ENQACK) || /* do we want handshake? */
|
|
!(mpx_config [port] & SK_ENQACK)) /* and configured for handshake? */
|
|
mpx_flags [port] &= ~FL_DO_ENQACK; /* no, so clear flag */
|
|
else
|
|
mpx_flags [port] |= FL_DO_ENQACK; /* yes, so set flag */
|
|
|
|
continue; /* "continue" for zero-length write */
|
|
}
|
|
|
|
if (mpx_flags [port] & FL_DO_ENQACK) /* do handshake for this buffer? */
|
|
mpx_enq_cntr [port] = mpx_enq_cntr [port] + 1; /* bump character counter */
|
|
|
|
if (mpx_enq_cntr [port] > ENQ_LIMIT) { /* ready for ENQ? */
|
|
mpx_enq_cntr [port] = 0; /* clear ENQ counter */
|
|
mpx_ack_wait [port] = 0; /* clear ACK wait timer */
|
|
|
|
mpx_flags [port] |= FL_WAITACK; /* set wait for ACK */
|
|
ch = ENQ;
|
|
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit ENQ */
|
|
xmit_loop = FALSE; /* stop further transmission */
|
|
}
|
|
|
|
else { /* not ready for ENQ */
|
|
ch = buf_get (iowrite, port) & data_mask; /* get char and mask to bit width */
|
|
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */
|
|
xmit_loop = (status == SCPE_OK) && fast_timing; /* continue transmission? */
|
|
}
|
|
|
|
if ((status == SCPE_OK) && /* transmitted OK? */
|
|
DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d character %s transmitted\n",
|
|
port, fmt_char (ch));
|
|
|
|
else
|
|
xmit_loop = FALSE;
|
|
|
|
if (buf_len (iowrite, port, get) == 0) { /* buffer complete? */
|
|
buf_free (iowrite, port); /* free buffer */
|
|
|
|
if (mpx_state == idle) /* controller idle? */
|
|
mpx_cntl_svc (&mpx_cntl); /* check for UI */
|
|
}
|
|
}
|
|
|
|
|
|
/* Reception service */
|
|
|
|
while (recv_loop && /* OK to process? */
|
|
(chx = tmxr_getc_ln (&mpx_ldsc [port]))) { /* and new char available? */
|
|
|
|
if (chx & SCPE_BREAK) { /* break detected? */
|
|
mpx_flags [port] |= FL_BREAK; /* set break status */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fputs (">>MPX xfer: Break detected\n", sim_deb);
|
|
|
|
if (mpx_state == idle) /* controller idle? */
|
|
mpx_cntl_svc (&mpx_cntl); /* check for UI */
|
|
|
|
continue; /* discard NUL that accompanied BREAK */
|
|
}
|
|
|
|
ch = chx & data_mask; /* mask to bits per char */
|
|
|
|
if ((ch == XOFF) && /* XOFF? */
|
|
(mpx_flowcntl [port] & FC_XONXOFF)) { /* and handshaking enabled? */
|
|
mpx_flags [port] |= FL_XOFF; /* suspend transmission */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d character XOFF "
|
|
"suspends transmission\n", port);
|
|
|
|
recv_loop = fast_timing; /* set to loop if fast mode */
|
|
continue;
|
|
}
|
|
|
|
else if ((ch == XON) && /* XON? */
|
|
(mpx_flags [port] & FL_XOFF)) { /* and currently suspended? */
|
|
mpx_flags [port] &= ~FL_XOFF; /* resume transmission */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d character XON "
|
|
"resumes transmission\n", port);
|
|
|
|
recv_loop = fast_timing; /* set to loop if fast mode */
|
|
continue;
|
|
}
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d character %s received\n",
|
|
port, fmt_char (ch));
|
|
|
|
if ((ch == ACK) && (mpx_flags [port] & FL_WAITACK)) { /* ACK and waiting for it? */
|
|
mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* clear wait flag */
|
|
recv_loop = FALSE; /* absorb character */
|
|
}
|
|
|
|
else if ((buf_avail (ioread, port) == 0) && /* no free buffer available for char? */
|
|
!(mpx_flags [port] & FL_RDFILL)) { /* and not filling last buffer? */
|
|
mpx_flags [port] |= FL_RDOVFLOW; /* set buffer overflow flag */
|
|
recv_loop = fast_timing; /* continue loop if fast mode */
|
|
}
|
|
|
|
else { /* buffer is available */
|
|
if (rt & RT_ENAB_EDIT) /* editing enabled? */
|
|
if (ch == BS) { /* backspace? */
|
|
if (buf_len (ioread, port, put) > 0) /* at least one character in buffer? */
|
|
buf_remove (ioread, port); /* remove last char */
|
|
|
|
if (rt & RT_ENAB_ECHO) { /* echo enabled? */
|
|
tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */
|
|
|
|
if (fast_timing) { /* fast timing mode? */
|
|
tmxr_putc_ln (&mpx_ldsc [port], ' '); /* echo space */
|
|
tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
else if (ch == DEL) { /* delete line? */
|
|
buf_cancel (ioread, port, put); /* cancel put buffer */
|
|
|
|
if (rt & RT_ENAB_ECHO) { /* echo enabled? */
|
|
if (fast_timing) /* fast timing mode? */
|
|
tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* echo backslash */
|
|
|
|
tmxr_putc_ln (&mpx_ldsc [port], CR); /* echo CR */
|
|
tmxr_putc_ln (&mpx_ldsc [port], LF); /* and LF */
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (uptr->flags & UNIT_CAPSLOCK) /* caps lock mode? */
|
|
ch = toupper (ch); /* convert to upper case if lower */
|
|
|
|
if (rt & RT_ENAB_ECHO) /* echo enabled? */
|
|
tmxr_putc_ln (&mpx_ldsc [port], ch); /* echo the char */
|
|
|
|
if (rt & RT_END_ON_CHAR) { /* end on character? */
|
|
recv_loop = FALSE; /* assume termination */
|
|
|
|
if ((ch == CR) && (rt & RT_END_ON_CR)) {
|
|
if (rt & RT_ENAB_ECHO) /* echo enabled? */
|
|
tmxr_putc_ln (&mpx_ldsc [port], LF); /* send LF */
|
|
mpx_param = RS_ETC_CR; /* set termination condition */
|
|
}
|
|
|
|
else if ((ch == RS) && (rt & RT_END_ON_RS))
|
|
mpx_param = RS_ETC_RS; /* set termination condition */
|
|
|
|
else if ((ch == EOT) && (rt & RT_END_ON_EOT))
|
|
mpx_param = RS_ETC_EOT; /* set termination condition */
|
|
|
|
else if ((ch == DC2) && (rt & RT_END_ON_DC2))
|
|
mpx_param = RS_ETC_DC2; /* set termination condition */
|
|
|
|
else
|
|
recv_loop = TRUE; /* no termination */
|
|
}
|
|
|
|
if (recv_loop) /* no termination condition? */
|
|
buf_put (ioread, port, ch); /* put character in buffer */
|
|
|
|
read_length = buf_len (ioread, port, put); /* get current buffer length */
|
|
|
|
if ((rt & RT_END_ON_CNT) && /* end on count */
|
|
(read_length == mpx_charcnt [port])) { /* and count reached? */
|
|
recv_loop = FALSE; /* set termination */
|
|
mpx_param = 0; /* no extra termination info */
|
|
|
|
if (mpx_flags [port] & FL_ALERT) { /* was this alert for term rcv buffer? */
|
|
mpx_flags [port] &= ~FL_ALERT; /* clear alert flag */
|
|
mpx_charcnt [port] = RD_BUF_LIMIT; /* reset character count */
|
|
}
|
|
}
|
|
|
|
else if (read_length == RD_BUF_LIMIT) { /* buffer now full? */
|
|
recv_loop = FALSE; /* set termination */
|
|
mpx_param = mpx_param | RS_PARTIAL; /* and partial buffer flag */
|
|
}
|
|
|
|
if (recv_loop) /* no termination condition? */
|
|
recv_loop = fast_timing; /* set to loop if fast mode */
|
|
|
|
else { /* termination occurred */
|
|
if (DEBUG_PRI (mpx_dev, DEB_XFER)) {
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d read terminated on ", port);
|
|
|
|
if (mpx_param & RS_PARTIAL)
|
|
fputs ("buffer full\n", sim_deb);
|
|
else if (rt & RT_END_ON_CHAR)
|
|
fprintf (sim_deb, "character %s\n", fmt_char (ch));
|
|
else
|
|
fprintf (sim_deb, "count = %d\n", mpx_charcnt [port]);
|
|
}
|
|
|
|
if (buf_len (ioread, port, put) == 0) { /* zero-length read? */
|
|
buf_put (ioread, port, 0); /* dummy put to reserve header */
|
|
buf_remove (ioread, port); /* back out dummy char leaving header */
|
|
}
|
|
|
|
buf_term (ioread, port, mpx_param >> 8); /* terminate buffer and set header */
|
|
|
|
if (buf_avail (ioread, port) == 1) /* first read buffer? */
|
|
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */
|
|
|
|
if (mpx_state == idle) /* controller idle? */
|
|
mpx_cntl_svc (&mpx_cntl); /* check for UI */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Housekeeping */
|
|
|
|
if (fast_binary_read) { /* fast binary read in progress? */
|
|
if (port == 0) { /* on port 0? */
|
|
chx = tmxr_getc_ln (&mpx_ldsc [0]); /* see if a character is ready */
|
|
|
|
if (chx && !(mpx_flags [0] & FL_HAVEBUF)) { /* character ready and buffer empty? */
|
|
if (mpx_flags [0] & FL_WANTBUF) { /* second character? */
|
|
mpx_ibuf = mpx_ibuf | (chx & DMASK8); /* merge it into word */
|
|
mpx_flags [0] |= FL_HAVEBUF; /* mark buffer as ready */
|
|
|
|
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: Flag and SRQ set\n", sim_deb);
|
|
}
|
|
|
|
else /* first character */
|
|
mpx_ibuf = (chx & DMASK8) << 8; /* put in top half of word */
|
|
|
|
mpx_flags [0] ^= FL_WANTBUF; /* toggle byte flag */
|
|
}
|
|
|
|
sim_activate (uptr, uptr->wait); /* reschedule service for fast response */
|
|
}
|
|
}
|
|
|
|
else { /* normal service */
|
|
tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */
|
|
|
|
if ((buf_avail (iowrite, port) < 2) && /* more to transmit? */
|
|
!(mpx_flags [port] & (FL_WAITACK | FL_XOFF)) || /* and transmission not suspended */
|
|
tmxr_rqln (&mpx_ldsc [port])) /* or more to receive? */
|
|
sim_activate (uptr, uptr->wait); /* reschedule service */
|
|
else
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d service stopped\n", port);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* 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.
|
|
|
|
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
|
|
sent to avoid stalls. Lines are also checked for available characters, and
|
|
the corresponding line I/O service routine is scheduled if needed.
|
|
*/
|
|
|
|
t_stat mpx_poll_svc (UNIT *uptr)
|
|
{
|
|
uint32 i;
|
|
t_stat status = SCPE_OK;
|
|
|
|
poll_connection (); /* check for new connection */
|
|
|
|
tmxr_poll_rx (&mpx_desc); /* poll for input */
|
|
|
|
for (i = 0; i < MPX_PORTS; i++) { /* check lines */
|
|
if (mpx_flags [i] & FL_WAITACK) { /* waiting for ACK? */
|
|
mpx_ack_wait [i] = mpx_ack_wait [i] + 1; /* increment ACK wait timer */
|
|
|
|
if (mpx_ack_wait [i] > ACK_LIMIT) { /* has wait timed out? */
|
|
mpx_ack_wait [i] = 0; /* reset counter */
|
|
status = tmxr_putc_ln (&mpx_ldsc [i], ENQ); /* send ENQ again */
|
|
tmxr_poll_tx (&mpx_desc); /* transmit it */
|
|
|
|
if ((status == SCPE_OK) && /* transmitted OK? */
|
|
DEBUG_PRI (mpx_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>MPX xfer: Port %d character ENQ retransmitted\n", i);
|
|
}
|
|
}
|
|
|
|
if (tmxr_rqln (&mpx_ldsc [i])) /* chars available? */
|
|
sim_activate (&mpx_unit [i], mpx_unit [i].wait); /* activate I/O service */
|
|
}
|
|
|
|
if (uptr->wait == POLL_FIRST) /* first poll? */
|
|
uptr->wait = sync_poll (INITIAL); /* initial synchronization */
|
|
else /* not first */
|
|
uptr->wait = sync_poll (SERVICE); /* continue synchronization */
|
|
|
|
sim_activate (uptr, uptr->wait); /* continue polling */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Simulator reset routine.
|
|
|
|
The hardware CRS signal generates a reset signal to the Z80 and its
|
|
peripherals. This causes execution of the power up initialization code.
|
|
|
|
The CRS signal also has these hardware effects:
|
|
- clears control
|
|
- clears flag
|
|
- clears flag buffer
|
|
- clears backplane ready
|
|
- clears the output buffer register
|
|
|
|
Implementation notes:
|
|
|
|
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.
|
|
|
|
3. We must set the "emptying_flags" and "filling_flags" values here, because
|
|
they cannot be initialized statically, even though the values are
|
|
constant.
|
|
*/
|
|
|
|
t_stat mpx_reset (DEVICE *dptr)
|
|
{
|
|
if (sim_switches & SWMASK ('P')) { /* power-on reset? */
|
|
emptying_flags [ioread] = FL_RDEMPT; /* initialize buffer flags constants */
|
|
emptying_flags [iowrite] = FL_WREMPT;
|
|
filling_flags [ioread] = FL_RDFILL;
|
|
filling_flags [iowrite] = FL_WRFILL;
|
|
}
|
|
|
|
IOPRESET (&mpx_dib); /* PRESET device (does not use PON) */
|
|
|
|
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 */
|
|
mpx_poll.wait = POLL_FIRST; /* set up poll */
|
|
sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Attach the multiplexer or a line.
|
|
|
|
We are called by the ATTACH MPX <port> command to attach the multiplexer to
|
|
the listening port indicated by <port> and by ATTACH MPX<n> <ser> to attach
|
|
line <n> to serial port <ser>. 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
*/
|
|
|
|
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 (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 */
|
|
|
|
if (status == SCPE_OK) {
|
|
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.
|
|
|
|
We are called by the DETACH MPX command to detach the listening port and all
|
|
Telnet sessions and by the DETACH MPX<n> to detach a serial port from line
|
|
<n>. 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:
|
|
|
|
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.
|
|
*/
|
|
|
|
t_stat mpx_detach (UNIT *uptr)
|
|
{
|
|
uint32 ln;
|
|
t_stat status;
|
|
t_bool mux_free = TRUE;
|
|
|
|
if (uptr == &mpx_cntl) /* detaching controller directly? */
|
|
return SCPE_NOATT; /* disallow */
|
|
|
|
if (sim_unit_ref == ref_dev || uptr == &mpx_poll) /* device detach or detach all request? */
|
|
status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */
|
|
|
|
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 */
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Show multiplexer status */
|
|
|
|
t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, void *desc)
|
|
{
|
|
if (mpx_poll.flags & UNIT_ATT) /* attached to socket? */
|
|
fprintf (st, "attached to port %s, ", mpx_poll.filename);
|
|
else
|
|
fprintf (st, "not attached, ");
|
|
|
|
tmxr_show_summ (st, uptr, val, desc); /* report connection count */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Set firmware revision.
|
|
|
|
Currently, we support only revision C, so the MTAB entry does not have an
|
|
"mstring" entry. When we add revision D support, an "mstring" entry of "REV"
|
|
will enable changing the firmware revision.
|
|
*/
|
|
|
|
t_stat mpx_set_frev (UNIT *uptr, int32 val, char *cptr, void *desc)
|
|
{
|
|
if ((cptr == NULL) || /* no parameter? */
|
|
(*cptr < 'C') || (*cptr > 'D') || /* or not C or D? */
|
|
(*(cptr + 1) != '\0')) /* or not just one character? */
|
|
return SCPE_ARG; /* bad argument */
|
|
|
|
else {
|
|
if (*cptr == 'C') /* setting revision C? */
|
|
mpx_dev.flags = mpx_dev.flags & ~DEV_REV_D; /* clear 'D' flag */
|
|
else if (*cptr == 'D') /* setting revision D? */
|
|
mpx_dev.flags = mpx_dev.flags | DEV_REV_D; /* set 'D' flag */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
}
|
|
|
|
|
|
/* Show firmware revision */
|
|
|
|
t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc)
|
|
{
|
|
if (mpx_dev.flags & DEV_REV_D)
|
|
fputs ("12792D", st);
|
|
else
|
|
fputs ("12792C", st);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Local routines */
|
|
|
|
|
|
/* Poll for new connections */
|
|
|
|
static void poll_connection (void)
|
|
{
|
|
int32 new_line;
|
|
|
|
new_line = tmxr_poll_conn (&mpx_desc); /* check for new connection */
|
|
|
|
if (new_line >= 0) /* new connection established? */
|
|
mpx_ldsc [new_line].rcve = 1; /* enable line to receive */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Controller reset.
|
|
|
|
This is the card microprocessor reset, not the simulator reset routine. It
|
|
simulates a power-on restart of the Z80 firmware. When it is called from the
|
|
simulator reset routine, that routine will take care of setting the card
|
|
flip-flops appropriately.
|
|
*/
|
|
|
|
static void controller_reset (void)
|
|
{
|
|
uint32 i;
|
|
|
|
mpx_state = idle; /* idle state */
|
|
|
|
mpx_cmd = 0; /* clear command */
|
|
mpx_param = 0; /* clear parameter */
|
|
mpx_uien = FALSE; /* disable interrupts */
|
|
|
|
for (i = 0; i < MPX_PORTS; i++) { /* clear per-line variables */
|
|
buf_init (iowrite, i); /* initialize write buffers */
|
|
buf_init (ioread, i); /* initialize read buffers */
|
|
|
|
mpx_key [i] = KEY_DEFAULT; /* clear port key to default */
|
|
|
|
if (i == 0) /* default port configurations */
|
|
mpx_config [0] = SK_PWRUP_0; /* port 0 is separate from 1-7 */
|
|
else
|
|
mpx_config [i] = SK_PWRUP_1 | i;
|
|
|
|
mpx_rcvtype [i] = RT_PWRUP; /* power on config for echoplex */
|
|
mpx_charcnt [i] = 0; /* default character count */
|
|
mpx_flowcntl [i] = 0; /* default flow control */
|
|
mpx_flags [i] = 0; /* clear state flags */
|
|
mpx_enq_cntr [i] = 0; /* clear ENQ counter */
|
|
mpx_ack_wait [i] = 0; /* clear ACK wait timer */
|
|
mpx_unit [i].wait = service_time (mpx_config [i]); /* set terminal I/O time */
|
|
|
|
sim_cancel (&mpx_unit [i]); /* cancel line I/O */
|
|
}
|
|
|
|
sim_cancel (&mpx_cntl); /* cancel controller */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Calculate service time from baud rate.
|
|
|
|
Service times are based on 1580 instructions per millisecond, which is the
|
|
1000 E-Series execution speed. Baud rate 0 means "don't change" and is
|
|
handled by the "Set port key" command executor.
|
|
|
|
Baud rate settings of 13-15 are marked as "reserved" in the user manual, but
|
|
the firmware defines these as 38400, 9600, and 9600 baud, respectively.
|
|
*/
|
|
|
|
static uint32 service_time (uint16 control_word)
|
|
{
|
|
/* Baud Rates 0- 7 : --, 50, 75, 110, 134.5, 150, 300, 1200, */
|
|
/* Baud Rates 8-15 : 1800, 2400, 4800, 9600, 19200, 38400, 9600, 9600 */
|
|
static const int32 ticks [] = { 0, 316000, 210667, 143636, 117472, 105333, 52667, 13167,
|
|
8778, 6583, 3292, 1646, 823, 411, 1646, 1646 };
|
|
|
|
return ticks [GET_BAUDRATE (control_word)]; /* return service time for indicated rate */
|
|
}
|
|
|
|
|
|
/* Translate port key to port number.
|
|
|
|
Port keys are scanned in reverse port order, so if more than one port has the
|
|
same port key, commands specifying that key will affect the highest numbered
|
|
port.
|
|
|
|
If a port key is the reserved value 255, then the port key has not been set.
|
|
In this case, set the input buffer to 0xBAD0 and return -1 to indicate
|
|
failure.
|
|
*/
|
|
|
|
static int32 key_to_port (uint32 key)
|
|
{
|
|
int32 i;
|
|
|
|
for (i = MPX_PORTS - 1; i >= 0; i--) /* scan in reverse order */
|
|
if (mpx_key [i] == key) /* key found? */
|
|
return i; /* return port number */
|
|
|
|
mpx_ibuf = ST_BAD_KEY; /* key not found: set status */
|
|
return -1; /* return failure code */
|
|
}
|
|
|
|
|
|
/* Buffer manipulation routines.
|
|
|
|
The 12792 hardware provides 16K bytes of RAM to the microprocessor. From
|
|
this pool, the firmware allocates per-port read/write buffers and state
|
|
variables, global variables, and the system stack. Allocations are static
|
|
and differ between firmware revisions.
|
|
|
|
The A/B/C revisions allocate two 254-byte read buffers and two 254-byte write
|
|
buffers per port. Assuming an idle condition, the first write to a port
|
|
transfers characters to the first write buffer. When the transfer completes,
|
|
the SIO begins transmitting. During transmission, a second write can be
|
|
initiated, which transfers characters to the second write buffer. If a third
|
|
write is attempted before the first buffer has been released, it will be
|
|
denied until the SIO completes transmission; then, if enabled, an unsolicited
|
|
interrupt will occur to announce buffer availability. The "active" (filling)
|
|
buffer alternates between the two.
|
|
|
|
At idle, characters received will fill the first read buffer. When the read
|
|
completes according to the previously set termination criteria, an
|
|
unsolicited interrupt will occur (if enabled) to announce buffer
|
|
availability. If more characters are received before the first buffer has
|
|
been transferred to the CPU, they will fill the second buffer. If that read
|
|
also completes, additional characters will be discarded until the first
|
|
buffer has been emptied. The "active" (emptying) buffer alternates between
|
|
the two.
|
|
|
|
With this configuration, two one-character writes or reads will allocate both
|
|
available buffers, even though each was essentially empty.
|
|
|
|
The D revision allocates one 1024-byte FIFO read buffer and one 892-byte
|
|
write buffer per port. As with the A/B/C revisions, the first write to a
|
|
port transfers characters to the write buffer, and serial transmission begins
|
|
when the write completes. However, the write buffer is not a FIFO, so the
|
|
host is not permitted another write request until the entire buffer has been
|
|
transmitted.
|
|
|
|
The read buffer is a FIFO. Characters received are placed into the FIFO as a
|
|
stream. Unlike the A/B/C revisions, character editing and termination
|
|
conditions are not evaluated until the buffer is read. Therefore, a full
|
|
1024 characters may be received before additional characters would be
|
|
discarded.
|
|
|
|
When the first character is received, an unsolicited interrupt occurs (if
|
|
enabled) to announce data reception. A host read may then be initiated. The
|
|
write buffer is used temporarily to process characters from the read buffer.
|
|
Characters are copied from the read to the write buffer while editing as
|
|
directed by the configuration accompanying the read request (e.g., deleting
|
|
the character preceding a BS, stripping CR/LF, etc.). When the termination
|
|
condition is found, the read command completes. Incoming characters may be
|
|
added to the FIFO while this is occurring.
|
|
|
|
In summary, the revision differences in buffer handling are:
|
|
|
|
Revisions A/B/C:
|
|
- two 254-byte receive buffers
|
|
- a buffer is "full" when the terminator character or count is received
|
|
- termination type must be established before the corresponding read
|
|
- data is echoed as it is received
|
|
|
|
Revision D:
|
|
- one 1024-byte receive buffer
|
|
- buffer is "full" only when 1024 characters are received
|
|
- the concept of a buffer terminator does not apply, as the data is not
|
|
examined until a read is requested and characters are retrieved from the
|
|
FIFO.
|
|
- data is not echoed until it is read
|
|
|
|
To implement the C revision behavior, while preserving the option of reusing
|
|
the buffer handlers for future D revision support, the dual 254-byte buffers
|
|
are implemented as a single 514-byte circular FIFO with capacity limited to
|
|
254 bytes per buffer. This reserves space for a CR and LF and for a header
|
|
byte in each buffer. The header byte preserves per-buffer state information.
|
|
|
|
In this implementation, the buffer "put" index points at the next free
|
|
location, and the buffer "get" index points at the next character to
|
|
retrieve. In addition to "put" and "get" indexes, a third "separator" index
|
|
is maintained to divide the FIFO into two areas corresponding to the two
|
|
buffers, and a "buffer filling" flag is maintained for each FIFO that is set
|
|
by the fill (put) routine and cleared by the terminate buffer routine.
|
|
|
|
Graphically, the implementation is as follows for buffer "B[]", get "G", put
|
|
"P", and separator "S" indexes:
|
|
|
|
1. Initialize: 2. Fill first buffer:
|
|
G = S = P = 0 B[P] = char; Incr (P)
|
|
|
|
|------------------------------| |---------|--------------------|
|
|
G G P -->
|
|
S S
|
|
P
|
|
|
|
3. Terminate first buffer: 4. Fill second buffer:
|
|
if S == G then S = P else nop B[P] = char; Incr (P)
|
|
|
|
|------------|-----------------| |------------|------|----------|
|
|
G /---> S G S P -->
|
|
* ----/ P
|
|
|
|
5. Terminate second buffer: 6. Empty first buffer:
|
|
if S == G then S = P else nop char = B[G]; Incr (G)
|
|
|
|
|------------|------------|----| |----|-------|------------|----|
|
|
G S P G --> S P
|
|
|
|
7. First buffer is empty: 8. Free first buffer:
|
|
G == S if !filling then S = P else nop
|
|
|
|
|------------|------------|----| |------------|------------|----|
|
|
G P G /---> S
|
|
S * ----/ P
|
|
|
|
9. Empty second buffer: 10. Second buffer empty:
|
|
char = B[G]; Incr (G) G == S
|
|
|
|
|----------------|--------|----| |-------------------------|----|
|
|
G --> S G
|
|
P S
|
|
P
|
|
11. Free second buffer:
|
|
if !filling then S = P else nop
|
|
|
|
|-------------------------|----|
|
|
G
|
|
S
|
|
P
|
|
|
|
We also provide the following utility routines:
|
|
|
|
- Remove Character: Decr (P)
|
|
|
|
- Cancel Buffer: if S == G then P = G else G = S
|
|
|
|
- Buffer Length: if S < G then return S + BUFSIZE - G else return S - G
|
|
|
|
- Buffers Available: if G == P then return 2 else if G != S != P then return
|
|
0 else return 1
|
|
|
|
The "buffer filling" flag is necessary for the "free" routine to decide
|
|
whether to advance the separator index. If the first buffer is to be freed,
|
|
then G == S and S != P. If the second buffer is already filled, then S = P.
|
|
However, if the buffer is still filling, then S must remain at G. This
|
|
cannot be determined from G, S, and P alone.
|
|
|
|
A "buffer emptying" flag is also employed to record whether the per-buffer
|
|
header has been obtained. This allows the buffer length to exclude the
|
|
header and reflect only the characters present.
|
|
*/
|
|
|
|
|
|
/* Increment a buffer index with wraparound */
|
|
|
|
static uint16 buf_incr (BUF_INDEX index, uint32 port, IO_OPER rw, int increment)
|
|
{
|
|
index [port] [rw] =
|
|
(index [port] [rw] + buf_size [rw] + increment) % buf_size [rw];
|
|
|
|
return index [port] [rw];
|
|
}
|
|
|
|
|
|
/* Initialize the buffer.
|
|
|
|
Initialization sets the three indexes to zero and clears the buffer state
|
|
flags.
|
|
*/
|
|
|
|
static void buf_init (IO_OPER rw, uint32 port)
|
|
{
|
|
mpx_get [port] [rw] = 0; /* clear indexes */
|
|
mpx_sep [port] [rw] = 0;
|
|
mpx_put [port] [rw] = 0;
|
|
|
|
if (rw == ioread)
|
|
mpx_flags [mpx_port] &= ~(FL_RDFLAGS); /* clear read buffer flags */
|
|
else
|
|
mpx_flags [mpx_port] &= ~(FL_WRFLAGS); /* clear write buffer flags */
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get a character from the buffer.
|
|
|
|
The character indicated by the "get" index is retrieved from the buffer, and
|
|
the index is incremented with wraparound. If the buffer is now empty, the
|
|
"buffer emptying" flag is cleared. Otherwise, it is set to indicate that
|
|
characters have been removed from the buffer.
|
|
*/
|
|
|
|
static uint8 buf_get (IO_OPER rw, uint32 port)
|
|
{
|
|
uint8 ch;
|
|
uint32 index = mpx_get [port] [rw]; /* current get index */
|
|
|
|
if (rw == ioread)
|
|
ch = mpx_rbuf [port] [index]; /* get char from read buffer */
|
|
else
|
|
ch = mpx_wbuf [port] [index]; /* get char from write buffer */
|
|
|
|
buf_incr (mpx_get, port, rw, +1); /* increment circular get index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
if (mpx_flags [port] & emptying_flags [rw])
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s get from %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
else
|
|
fprintf (sim_deb, ">>MPX buf: Port %d header %03o get from %s buffer "
|
|
"[%d]\n", port, ch, io_op [rw], index);
|
|
|
|
if (mpx_get [port] [rw] == mpx_sep [port] [rw]) /* buffer now empty? */
|
|
mpx_flags [port] &= ~emptying_flags [rw]; /* clear "buffer emptying" flag */
|
|
else
|
|
mpx_flags [port] |= emptying_flags [rw]; /* set "buffer emptying" flag */
|
|
|
|
return ch;
|
|
}
|
|
|
|
|
|
/* Put a character to the buffer.
|
|
|
|
The character is written to the buffer in the slot indicated by the "put"
|
|
index, and the index is incremented with wraparound. The first character put
|
|
to a new buffer reserves space for the header and sets the "buffer filling"
|
|
flag.
|
|
*/
|
|
|
|
static void buf_put (IO_OPER rw, uint32 port, uint8 ch)
|
|
{
|
|
uint32 index;
|
|
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) { /* first put to this buffer? */
|
|
mpx_flags [port] |= filling_flags [rw]; /* set buffer filling flag */
|
|
index = mpx_put [port] [rw]; /* get current put index */
|
|
buf_incr (mpx_put, port, rw, +1); /* reserve space for header */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d reserved header "
|
|
"for %s buffer [%d]\n", port, io_op [rw], index);
|
|
}
|
|
|
|
index = mpx_put [port] [rw]; /* get current put index */
|
|
|
|
if (rw == ioread)
|
|
mpx_rbuf [port] [index] = ch; /* put char in read buffer */
|
|
else
|
|
mpx_wbuf [port] [index] = ch; /* put char in write buffer */
|
|
|
|
buf_incr (mpx_put, port, rw, +1); /* increment circular put index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s put to %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Remove the last character put to the buffer.
|
|
|
|
The most-recent character put to the buffer is removed by decrementing the
|
|
"put" index with wraparound.
|
|
*/
|
|
|
|
static void buf_remove (IO_OPER rw, uint32 port)
|
|
{
|
|
uint8 ch;
|
|
uint32 index;
|
|
|
|
index = buf_incr (mpx_put, port, rw, -1); /* decrement circular put index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF)) {
|
|
if (rw == ioread)
|
|
ch = mpx_rbuf [port] [index]; /* pick up char from read buffer */
|
|
else
|
|
ch = mpx_wbuf [port] [index]; /* pick up char from write buffer */
|
|
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s removed from %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/* Terminate the buffer.
|
|
|
|
The buffer is marked to indicate that filling is complete and that the next
|
|
"put" operation should begin a new buffer. The header value is stored in
|
|
first byte of buffer, which is reserved, and the "buffer filling" flag is
|
|
cleared.
|
|
*/
|
|
|
|
static void buf_term (IO_OPER rw, uint32 port, uint8 header)
|
|
{
|
|
uint32 index = mpx_sep [port] [rw]; /* separator index */
|
|
|
|
if (rw == ioread)
|
|
mpx_rbuf [port] [index] = header; /* put header in read buffer */
|
|
else
|
|
mpx_wbuf [port] [index] = header; /* put header in write buffer */
|
|
|
|
mpx_flags [port] = mpx_flags [port] & ~filling_flags [rw]; /* clear filling flag */
|
|
|
|
if (mpx_get [port] [rw] == index) /* reached separator? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move sep to end of next buffer */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d header %03o terminated %s buffer\n",
|
|
port, header, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Free the buffer.
|
|
|
|
The buffer is marked to indicate that it is available for reuse, and the
|
|
"buffer emptying" flag is reset.
|
|
*/
|
|
|
|
static void buf_free (IO_OPER rw, uint32 port)
|
|
{
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling next buffer? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */
|
|
/* else it will be moved when terminated */
|
|
mpx_flags [port] = mpx_flags [port] & ~emptying_flags [rw]; /* clear emptying flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d released %s buffer\n", port, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Cancel the selected buffer.
|
|
|
|
The selected buffer is marked to indicate that it is empty. Either the "put"
|
|
buffer or the "get" buffer may be selected.
|
|
*/
|
|
|
|
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which)
|
|
{
|
|
if (which == put) { /* cancel put buffer? */
|
|
mpx_put [port] [rw] = mpx_sep [port] [rw]; /* move put back to separator */
|
|
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */
|
|
}
|
|
|
|
else { /* cancel get buffer */
|
|
if (mpx_sep [port] [rw] == mpx_get [port] [rw]) { /* filling first buffer? */
|
|
mpx_put [port] [rw] = mpx_get [port] [rw]; /* cancel first buffer */
|
|
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */
|
|
}
|
|
|
|
else { /* not filling first buffer */
|
|
mpx_get [port] [rw] = mpx_sep [port] [rw]; /* cancel first buffer */
|
|
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling second buffer? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */
|
|
}
|
|
|
|
mpx_flags [port] &= ~emptying_flags [rw]; /* clear emptying flag */
|
|
}
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d cancelled %s buffer\n", port, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get the buffer length.
|
|
|
|
The current length of the selected buffer (put or get) is returned. For ease
|
|
of use, the returned length does NOT include the header byte, i.e., it
|
|
reflects only the characters contained in the buffer.
|
|
|
|
If the put buffer is selected, and the buffer is filling, or the get buffer
|
|
is selected, and the buffer is not emptying, then subtract one from the
|
|
length for the allocated header.
|
|
*/
|
|
|
|
static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which)
|
|
{
|
|
int32 length;
|
|
|
|
if (which == put)
|
|
length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */
|
|
((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */
|
|
|
|
else {
|
|
length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */
|
|
|
|
if (length && !(mpx_flags [port] & emptying_flags [rw])) /* not empty and not yet emptying? */
|
|
length = length - 1; /* account for allocated header */
|
|
}
|
|
|
|
if (length < 0) /* is length negative? */
|
|
return length + buf_size [rw]; /* account for wraparound */
|
|
else
|
|
return length;
|
|
}
|
|
|
|
|
|
/* Return the number of free buffers available.
|
|
|
|
Either 0, 1, or 2 free buffers will be available. A buffer is available if
|
|
it contains no characters (including the header byte).
|
|
*/
|
|
|
|
static uint32 buf_avail (IO_OPER rw, uint32 port)
|
|
{
|
|
if (mpx_get [port] [rw] == mpx_put [port] [rw]) /* get and put indexes equal? */
|
|
return 2; /* all buffers are free */
|
|
|
|
else if ((mpx_get [port] [rw] != mpx_sep [port] [rw]) && /* get, separator, and put */
|
|
(mpx_sep [port] [rw] != mpx_put [port] [rw])) /* all different? */
|
|
return 0; /* no buffers are free */
|
|
|
|
else
|
|
return 1; /* one buffer free */
|
|
}
|