/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator

   Copyright (c) 2008, 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

   25-Nov-08    JDB     Revised for new multiplexer library SHOW routines
   14-Nov-08    JDB     Cleaned up VC++ size mismatch warnings for zero assignments
   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 to a
   user-specified port.

   The single interface card contained a Z80 CPU, DMA controller, CTC, four
   two-channel SIO UARTs, 16K of RAM, 8K of ROM, and I/O backplane latches and
   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 via Telnet in blocks.

     2. ENQ/ACK handshaking is done locally without involving the Telnet 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])      /* Telnet 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 */

FLIP_FLOP mpx_control = CLEAR;                          /* control flip-flop */
FLIP_FLOP mpx_flag    = CLEAR;                          /* flag flip-flop */
FLIP_FLOP mpx_flagbuf = CLEAR;                          /* flag buffer flip-flop */


/* 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 */

uint32 mpx_io (uint32 select_code, IOSIG signal, uint32 data);

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 line descriptors
   mpx_desc    MPX multiplexer 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 Telnet library.  A ninth unit acts as the card
   controller, executing commands and transferring data to and from the I/O
   buffers.  A tenth unit is responsible for polling for connections and socket
   I/O.  It also holds the master socket.

   The character I/O service routines run only when there are characters to read
   or write.  They operate at the approximate baud rates of the terminals (in
   CPU instructions per second) in order to be compatible with the OS drivers.
   The controller service routine runs only when a command is executing or a
   data transfer to or from the CPU is in progress.  The Telnet poll must run
   continuously, but it may operate much more slowly, as the only requirement is
   that it must not present a perceptible lag to human input.  To be compatible
   with CPU idling, it is co-scheduled with the master poll timer, which uses a
   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.  However, we cannot attach
   to a disabled unit, so the poll unit is enabled prior to attaching and
   disabled thereafter.
*/

int32 mpx_order [MPX_PORTS] = { -1 };                       /* connection order */
TMLN  mpx_ldsc [MPX_PORTS] = { { 0 } };                     /* line descriptors */
TMXR  mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order };  /* device descriptor */

DIB mpx_dib = { MPX, &mpx_io };

DEVICE mpx_dev;

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) }  /* Telnet 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 (DEVNO, mpx_dib.devno, 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, "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_NET | 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.

    3. The card does not respond to POPIO.  However, as PRESET asserts POPIO and
       CRS together, we fall into the latter from the former.
*/

uint32 mpx_io (uint32 select_code, IOSIG signal, uint32 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 > ioCLF ? ",C" : "");
const IOSIG base_signal = IOBASE (signal);              /* derive base signal */
int32 delay;

switch (base_signal) {                                  /* dispatch base 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 */
        data = 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], data);

        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 */
        if (DEBUG_PRI (mpx_dev, DEB_CPU))
            fprintf (sim_deb, ">>MPX cpu:  [OTx%s] %s = %06o\n",
                              hold_or_clear, output_state [mpx_state], data);

        mpx_obuf = data;                                /* save word */

        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 ioPOPIO:                                       /* power-on preset to I/O */
                                                        /* fall into CRS handler */

    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 (select_code, mpx);                   /* set standard PRL signal */
        setstdIRQ (select_code, mpx);                   /* set standard IRQ signal */
        setstdSRQ (select_code, mpx);                   /* set standard SRQ signal */
        break;


    case ioIAK:                                         /* interrupt acknowledge */
        mpx_flagbuf = CLEAR;                            /* clear flag buffer */
        break;


    default:                                            /* all other signals */
        break;                                          /*   are ignored */
    }


if (signal > ioCLF)                                     /* multiple signals? */
    mpx_io (select_code, ioCLF, 0);                     /* issue CLF */
else if (signal > ioSIR)                                /* signal affected interrupt status? */
    mpx_io (select_code, ioSIR, 0);                     /* set interrupt request */

return 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.devno, 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 Telnet poll routine
   determines that there are characters ready for input, and it is stopped when
   there are no more characters to output or input.  When a line is quiescent,
   this routine does not run.  Service times are selected to approximate the
   baud rate setting of the multiplexer port.

   "Fast timing" mode enables three optimizations.  First, buffered characters
   are transferred via Telnet in blocks, rather than a character at a time; this
   reduces network traffic and decreases simulator overhead (there is only one
   service routine entry per block, rather than one per character).  Second,
   ENQ/ACK handshaking is done locally, without involving the Telnet client.
   Third, when editing and echo is enabled, entering BS echoes a backspace, a
   space, and a backspace, and entering DEL echoes a backslash, a carriage
   return, and a line feed, providing better compatibility with prior RTE
   terminal drivers.

   Each read and write buffer begins with a reserved header byte that stores
   per-buffer information, such as whether handshaking should be suppressed
   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 Telnet buffer.  If a BREAK was
   received, break status is set, and the character is discarded (the current
   multiplexer library implementation always returns a NUL with a BREAK
   indication).  If the character is an XOFF, and XON/XOFF pacing is enabled, a
   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 uint32 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.devno, 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;
}


/* Telnet poll service.

   This service routine is used to poll for Telnet connections and incoming
   characters.  It starts when the socket is attached and stops when the socket
   is detached.

   Each line is then checked for a pending ENQ/ACK handshake.  If one is
   pending, the ACK counter is incremented, and if it times out, another ENQ is
   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 Telnet connections to occur "immediately"
       upon execution, so that clients will be connected before execution
       begins.  Otherwise, a fast program may access the multiplexer before the
       poll service routine activates.

    3. We must set the "emptying_flags" and "filling_flags" values here, because
       they cannot be initialized statically, even though the values are
       constant.
*/

t_stat mpx_reset (DEVICE *dptr)
{
if (sim_switches & SWMASK ('P')) {                      /* PON 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;
    }

mpx_io (mpx_dib.devno, ioPOPIO, 0);                     /* send POPIO signal */

mpx_ibuf = 0;                                           /* clear input buffer */

if (mpx_poll.flags & UNIT_ATT) {                        /* network attached? */
    mpx_poll.wait = POLL_FIRST;                         /* set up poll */
    sim_activate (&mpx_poll, mpx_poll.wait);            /* start Telnet poll immediately */
    }
else
    sim_cancel (&mpx_poll);                             /* else stop Telnet poll */

return SCPE_OK;
}


/* Attach the multiplexer to a Telnet port.

   We are called by the ATTACH MPX <port> command to attach the multiplexer to
   the listening port indicated by <port>.  Logically, it is the multiplexer
   device that is attached; however, SIMH only allows units to be attached.
   This makes sense for devices such as tape drives, where the attached media is
   a property of a specific drive.  In our case, though, the listening port is a
   property of the multiplexer card, not of any given serial line.  As ATTACH
   MPX is equivalent to ATTACH MPX0, the port would, by default, be attached to
   the first serial line and be reported there in a SHOW MPX command.

   To preserve the logical picture, we attach the port to the Telnet poll unit,
   which is normally disabled, inhibiting its display.  Attaching to a disabled
   unit is not allowed, so we first enable the unit, then attach it, then
   disable it again.  Attachment is reported by the "mpx_status" routine below.

   The Telnet poll service routine is synchronized with the other input polling
   devices in the simulator to facilitate idling.
*/

t_stat mpx_attach (UNIT *uptr, char *cptr)
{
t_stat status = SCPE_OK;

if (uptr != mpx_unit)                                   /* not unit 0? */
    return SCPE_NOATT;                                  /* can't attach */

mpx_poll.flags = mpx_poll.flags & ~UNIT_DIS;            /* enable unit */
status = tmxr_attach (&mpx_desc, &mpx_poll, cptr);      /* attach to socket */
mpx_poll.flags = mpx_poll.flags | UNIT_DIS;             /* disable unit */

if (status == SCPE_OK) {
    mpx_poll.wait = POLL_FIRST;                         /* set up poll */
    sim_activate (&mpx_poll, mpx_poll.wait);            /* start Telnet poll immediately */
    }
return status;
}


/* Detach the multiplexer.

   Normally, we are called by the DETACH MPX command, which is equivalent to
   DETACH MPX0.  However, we may be called with other units in two cases.

   A DETACH ALL command will call us for unit 9 (the poll unit) if it is
   attached.  Also, during simulator shutdown, we will be called for units 0-8
   (detach_all in scp.c calls the detach routines of all units that do NOT have
   UNIT_ATTABLE), as well as for unit 9 if it is attached.  In both cases, it is
   imperative that we return SCPE_OK, otherwise any remaining device detaches
   will not be performed.
*/

t_stat mpx_detach (UNIT *uptr)
{
t_stat status = SCPE_OK;
int32 i;

if ((uptr == mpx_unit) || (uptr == &mpx_poll)) {        /* base unit or poll unit? */
    status = tmxr_detach (&mpx_desc, &mpx_poll);        /* detach socket */

    for (i = 0; i < MPX_PORTS; i++) {
        mpx_ldsc [i].rcve = 0;                          /* disable line reception */
        sim_cancel (&mpx_unit [i]);                     /* cancel any scheduled I/O */
        }

    sim_cancel (&mpx_poll);                             /* stop Telnet 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 Telnet 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 second, 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 */
}
