2690 lines
126 KiB
C
2690 lines
126 KiB
C
/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator
|
|
|
|
Copyright (c) 2008-2012, J. David Bryan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of the author shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from the author.
|
|
|
|
MPX 12792C 8-channel multiplexer card
|
|
|
|
10-Feb-12 JDB Deprecated DEVNO in favor of SC
|
|
28-Mar-11 JDB Tidied up signal handling
|
|
26-Oct-10 JDB Changed I/O signal handler for revised signal model
|
|
25-Nov-08 JDB Revised for new multiplexer library SHOW routines
|
|
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 in blocks.
|
|
|
|
2. ENQ/ACK handshaking is done locally without involving the client.
|
|
|
|
3. BS and DEL respond visually more like prior RTE terminal drivers.
|
|
|
|
HP did not offer a functional diagnostic for the 12792. Instead, a Z80
|
|
program that tested the operation of the hardware was downloaded to the card,
|
|
and a "go/no-go" status was returned to indicate the hardware condition.
|
|
Because this is a functional simulation of the multiplexer and not a Z80
|
|
emulation, the diagnostic cannot be used to test the implementation.
|
|
|
|
Implementation notes:
|
|
|
|
1. The 12792 had two baud-rate generators that were assigned to lines by the
|
|
wiring configuration in the I/O cable connector hood. Two of the four
|
|
CTC counters were used to implement the BRGs for all eight lines. Only
|
|
subsets of the configurable rates were allowed for lines connected to the
|
|
same BRG, and assigning mutually incompatible rates caused corruption of
|
|
the rates on lines assigned earlier. Under simulation, any baud rate may
|
|
be assigned to any line without interaction, and assignments of lines to
|
|
BRGs is not implemented.
|
|
|
|
2. Revisions B and C added support for the 37214A Systems Modem subsystem
|
|
and the RTE-A Virtual Control Panel (VCP). Under simulation, the modem
|
|
commands return status codes indicating that no modems are present, and
|
|
the VCP commands are not implemented.
|
|
*/
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "hp2100_defs.h"
|
|
#include "sim_sock.h"
|
|
#include "sim_tmxr.h"
|
|
|
|
|
|
/* Bitfield constructor.
|
|
|
|
Given a bitfield starting bit number and width in bits, declare two
|
|
constants: one for the starting bit number, and one for the positioned field
|
|
mask. That is, given a definition such as:
|
|
|
|
BITFIELD(SMALLFIELD,5,2)
|
|
|
|
...this macro produces:
|
|
|
|
static const uint32 SMALLFIELD_V = 5;
|
|
static const uint32 SMALLFIELD = ((1 << (2)) - 1) << (5);
|
|
|
|
The latter reduces to 3 << 5, or 0x00000060.
|
|
|
|
Note: C requires constant expressions in initializers for objects with static
|
|
storage duration, so initializing a static object with a BITFIELD value is
|
|
illegal (a "static const" object is not a constant!).
|
|
*/
|
|
|
|
#define BITFIELD(NAME,STARTBIT,BITWIDTH) \
|
|
static const uint32 NAME ## _V = STARTBIT; \
|
|
static const uint32 NAME = ((1 << (BITWIDTH)) - 1) << (STARTBIT);
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define MPX_DATE_CODE 2416 /* date code for C firmware */
|
|
|
|
#define RD_BUF_SIZE 514 /* read buffer size */
|
|
#define WR_BUF_SIZE 514 /* write buffer size */
|
|
|
|
#define RD_BUF_LIMIT 254 /* read buffer limit */
|
|
#define WR_BUF_LIMIT 254 /* write buffer limit */
|
|
|
|
#define KEY_DEFAULT 255 /* default port key */
|
|
|
|
|
|
/* Service times:
|
|
|
|
DATA_DELAY = 1.25 us (Z80 DMA data word transfer time)
|
|
PARAM_DELAY = 25 us (STC to STF for first word of two-word command)
|
|
CMD_DELAY = 400 us (STC to STF for one or two-word command execution)
|
|
*/
|
|
|
|
#define DATA_DELAY 2 /* data transfer time */
|
|
#define PARAM_DELAY 40 /* parameter request time */
|
|
#define CMD_DELAY 630 /* command completion time */
|
|
|
|
|
|
/* Unit references */
|
|
|
|
#define MPX_PORTS 8 /* number of visible units */
|
|
#define MPX_CNTLS 2 /* number of control units */
|
|
|
|
#define mpx_cntl (mpx_unit [MPX_PORTS + 0]) /* controller unit */
|
|
#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* 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 */
|
|
|
|
struct {
|
|
FLIP_FLOP control; /* control flip-flop */
|
|
FLIP_FLOP flag; /* flag flip-flop */
|
|
FLIP_FLOP flagbuf; /* flag buffer flip-flop */
|
|
} mpx = { CLEAR, CLEAR, CLEAR };
|
|
|
|
/* Multiplexer per-line state variables */
|
|
|
|
uint8 mpx_key [MPX_PORTS]; /* port keys */
|
|
uint16 mpx_config [MPX_PORTS]; /* port configuration */
|
|
uint16 mpx_rcvtype [MPX_PORTS]; /* receive type */
|
|
uint16 mpx_charcnt [MPX_PORTS]; /* character count */
|
|
uint16 mpx_flowcntl [MPX_PORTS]; /* flow control */
|
|
uint8 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */
|
|
uint16 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */
|
|
uint16 mpx_flags [MPX_PORTS]; /* line state flags */
|
|
|
|
/* Multiplexer buffer selectors */
|
|
|
|
typedef enum { ioread, iowrite } IO_OPER; /* I/O operation */
|
|
typedef enum { get, put } BUF_SELECT; /* buffer selector */
|
|
|
|
static const char *const io_op [] = { "read", /* operation names */
|
|
"write" };
|
|
|
|
static const uint32 buf_size [] = { RD_BUF_SIZE, /* buffer sizes */
|
|
WR_BUF_SIZE };
|
|
|
|
static uint32 emptying_flags [2]; /* buffer emptying flags [IO_OPER] */
|
|
static uint32 filling_flags [2]; /* buffer filling flags [IO_OPER] */
|
|
|
|
|
|
/* Multiplexer per-line buffer variables */
|
|
|
|
typedef uint16 BUF_INDEX [MPX_PORTS] [2]; /* buffer index (read and write) */
|
|
|
|
BUF_INDEX mpx_put; /* read/write buffer add index */
|
|
BUF_INDEX mpx_sep; /* read/write buffer separator index */
|
|
BUF_INDEX mpx_get; /* read/write buffer remove index */
|
|
|
|
uint8 mpx_rbuf [MPX_PORTS] [RD_BUF_SIZE]; /* read buffer */
|
|
uint8 mpx_wbuf [MPX_PORTS] [WR_BUF_SIZE]; /* write buffer */
|
|
|
|
|
|
/* Multiplexer local routines */
|
|
|
|
static t_bool exec_command (void);
|
|
static void poll_connection (void);
|
|
static void controller_reset (void);
|
|
static uint32 service_time (uint16 control_word);
|
|
static int32 key_to_port (uint32 key);
|
|
|
|
static void buf_init (IO_OPER rw, uint32 port);
|
|
static uint8 buf_get (IO_OPER rw, uint32 port);
|
|
static void buf_put (IO_OPER rw, uint32 port, uint8 ch);
|
|
static void buf_remove (IO_OPER rw, uint32 port);
|
|
static void buf_term (IO_OPER rw, uint32 port, uint8 header);
|
|
static void buf_free (IO_OPER rw, uint32 port);
|
|
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which);
|
|
static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which);
|
|
static uint32 buf_avail (IO_OPER rw, uint32 port);
|
|
|
|
|
|
/* Multiplexer global routines */
|
|
|
|
IOHANDLER mpx_io;
|
|
|
|
t_stat mpx_line_svc (UNIT *uptr);
|
|
t_stat mpx_cntl_svc (UNIT *uptr);
|
|
t_stat mpx_poll_svc (UNIT *uptr);
|
|
t_stat mpx_reset (DEVICE *dptr);
|
|
t_stat mpx_attach (UNIT *uptr, char *cptr);
|
|
t_stat mpx_detach (UNIT *uptr);
|
|
t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|
t_stat mpx_set_frev (UNIT *uptr, int32 val, char *cptr, void *desc);
|
|
t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|
|
|
|
|
/* MPX data structures.
|
|
|
|
mpx_order MPX line connection order table
|
|
mpx_ldsc MPX terminal multiplexer line descriptors
|
|
mpx_desc MPX terminal multiplexer device descriptor
|
|
mpx_dib MPX device information block
|
|
mpx_unit MPX unit list
|
|
mpx_reg MPX register list
|
|
mpx_mod MPX modifier list
|
|
mpx_deb MPX debug list
|
|
mpx_dev MPX device descriptor
|
|
|
|
The first eight units correspond to the eight multiplexer line ports. These
|
|
handle character I/O via the 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.
|
|
*/
|
|
|
|
DEVICE mpx_dev;
|
|
|
|
int32 mpx_order [MPX_PORTS] = { -1 }; /* connection order */
|
|
TMLN mpx_ldsc [MPX_PORTS] = { { 0 } }; /* line descriptors */
|
|
TMXR mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order }; /* device descriptor */
|
|
|
|
DIB mpx_dib = { &mpx_io, MPX };
|
|
|
|
UNIT mpx_unit [] = {
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 0 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 1 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 2 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 3 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 4 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 5 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 6 */
|
|
{ UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 7 */
|
|
{ UDATA (&mpx_cntl_svc, UNIT_DIS, 0) }, /* controller unit */
|
|
{ UDATA (&mpx_poll_svc, UNIT_ATTABLE | UNIT_DIS, POLL_FIRST) } /* 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 (SC, mpx_dib.select_code, 6), REG_HRO },
|
|
{ ORDATA (DEVNO, mpx_dib.select_code, 6), REG_HRO },
|
|
|
|
{ BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
MTAB mpx_mod [] = {
|
|
{ UNIT_FASTTIME, UNIT_FASTTIME, "fast timing", "FASTTIME", NULL, NULL, NULL },
|
|
{ UNIT_FASTTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL },
|
|
|
|
{ UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL },
|
|
{ UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL },
|
|
|
|
{ MTAB_XTD | MTAB_VDV, 0, "REV", NULL, &mpx_set_frev, &mpx_show_frev, NULL },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mpx_desc },
|
|
|
|
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mpx_desc },
|
|
|
|
{ MTAB_XTD | MTAB_VDV, 0, "", NULL, NULL, &mpx_status, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mpx_desc },
|
|
{ MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &mpx_dev },
|
|
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mpx_dev },
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
DEBTAB mpx_deb [] = {
|
|
{ "CMDS", DEB_CMDS },
|
|
{ "CPU", DEB_CPU },
|
|
{ "BUF", DEB_BUF },
|
|
{ "XFER", DEB_XFER },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
DEVICE mpx_dev = {
|
|
"MPX", /* device name */
|
|
mpx_unit, /* unit array */
|
|
mpx_reg, /* register array */
|
|
mpx_mod, /* modifier array */
|
|
MPX_PORTS + MPX_CNTLS, /* number of units */
|
|
10, /* address radix */
|
|
31, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
8, /* data width */
|
|
&tmxr_ex, /* examine routine */
|
|
&tmxr_dep, /* deposit routine */
|
|
&mpx_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
&mpx_attach, /* attach routine */
|
|
&mpx_detach, /* detach routine */
|
|
&mpx_dib, /* device information block */
|
|
DEV_DEBUG | DEV_DISABLE, /* device flags */
|
|
0, /* debug control flags */
|
|
mpx_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL }; /* logical device name */
|
|
|
|
|
|
/* I/O signal handler.
|
|
|
|
Commands are sent to the card via an OTA/B. Issuing an STC SC,C causes the
|
|
mux to accept the word (STC causes a NMI on the card). If the command uses
|
|
one word, command execution will commence, and the flag will set on
|
|
completion. If the command uses two words, the flag will be set, indicating
|
|
that the second word should be output via an OTA/B. Command execution will
|
|
commence upon receipt, and the flag will set on completion.
|
|
|
|
When the flag sets for command completion, status or data may be read from
|
|
the card via an LIA/B. If additional status or data words are expected, the
|
|
flag will set when they are available.
|
|
|
|
A command consists of an opcode in the high byte, and a port key or command
|
|
parameter in the low byte. Undefined commands are treated as NOPs.
|
|
|
|
The card firmware executes commands as part of a twelve-event round-robin
|
|
scheduling poll. The card NMI service routine simply sets a flag that is
|
|
interrogated during polling. The poll sequence is advanced after each
|
|
command. This implies that successive commands incur a delay of at least one
|
|
poll-loop's execution time. On an otherwise quiescent card, this delay is
|
|
approximately 460 Z80 instructions, or about 950 usec. The average command
|
|
initiation time is half of that, or roughly 425 usec.
|
|
|
|
If a detected command requires a second word, the card sits in a tight loop,
|
|
waiting for the OTx that indicates that the parameter is available. Command
|
|
initiation from parameter receipt is about 25 usec.
|
|
|
|
For reads and writes to card buffers, the on-board DMA controller is used.
|
|
The CPU uses DCPC to handle the transfer, but the data transfer time is
|
|
limited by the Z80 DMA, which can process a word in about 1.25 usec.
|
|
|
|
For most cards, the hardware POPIO signal sets the flag buffer and flag
|
|
flip-flops, while CRS clears the control flip-flop. For this card, the
|
|
control and flags are cleared together by CRS, and POPIO is not used.
|
|
|
|
Implementation notes:
|
|
|
|
1. "Enable unsolicited input" is the only command that does not set the
|
|
device flag upon completion. Therefore, the CPU has no way of knowing
|
|
when the command has completed. Because the command in the input latch
|
|
is recorded in the NMI handler, but actual execution only begins when the
|
|
scheduler polls for the command indication, it is possible for another
|
|
command to be sent to the card before the "Enable unsolicited input"
|
|
command is recognized. In this case, the second command overwrites the
|
|
first and is executed by the scheduler poll. Under simulation, this
|
|
condition occurs when the OTx and STC processors are entered with
|
|
mpx_state = cmd.
|
|
|
|
2. The "Fast binary read" command inhibits all other commands until the card
|
|
is reset.
|
|
*/
|
|
|
|
uint32 mpx_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data)
|
|
{
|
|
static const char *output_state [] = { "Command", "Command override", "Parameter", "Data" };
|
|
static const char *input_state [] = { "Status", "Invalid status", "Parameter", "Data" };
|
|
const char *hold_or_clear = (signal_set & ioCLF ? ",C" : "");
|
|
int32 delay;
|
|
IOSIGNAL signal;
|
|
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */
|
|
|
|
while (working_set) {
|
|
signal = IONEXT (working_set); /* isolate next signal */
|
|
|
|
switch (signal) { /* dispatch I/O signal */
|
|
|
|
case ioCLF: /* clear flag flip-flop */
|
|
mpx.flag = mpx.flagbuf = CLEAR; /* clear flag and flag buffer */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [CLF] Flag cleared\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioSTF: /* set flag flip-flop */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [STF] Flag set\n", sim_deb);
|
|
/* fall into ENF */
|
|
|
|
case ioENF: /* enable flag */
|
|
mpx.flag = mpx.flagbuf = SET; /* set flag and flag buffer */
|
|
break;
|
|
|
|
|
|
case ioSFC: /* skip if flag is clear */
|
|
setstdSKF (mpx);
|
|
break;
|
|
|
|
|
|
case ioSFS: /* skip if flag is set */
|
|
setstdSKF (mpx);
|
|
break;
|
|
|
|
|
|
case ioIOI: /* I/O data input */
|
|
stat_data = IORETURN (SCPE_OK, mpx_ibuf); /* return info */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fprintf (sim_deb, ">>MPX cpu: [LIx%s] %s = %06o\n",
|
|
hold_or_clear, input_state [mpx_state], mpx_ibuf);
|
|
|
|
if (mpx_state == exec) /* if this is input data word */
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* continue transmission */
|
|
break;
|
|
|
|
|
|
case ioIOO: /* I/O data output */
|
|
mpx_obuf = IODATA (stat_data); /* save word */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fprintf (sim_deb, ">>MPX cpu: [OTx%s] %s = %06o\n",
|
|
hold_or_clear, output_state [mpx_state], mpx_obuf);
|
|
|
|
if (mpx_state == param) { /* if this is parameter word */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* do command now */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [OTx%s] Command %03o parameter %06o scheduled, "
|
|
"time = %d\n", hold_or_clear, mpx_cmd, mpx_obuf, CMD_DELAY);
|
|
}
|
|
|
|
else if (mpx_state == exec) /* else if this is output data word */
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* then do transmission */
|
|
break;
|
|
|
|
|
|
case ioCRS: /* control reset */
|
|
controller_reset (); /* reset firmware to power-on defaults */
|
|
mpx_obuf = 0; /* clear output buffer */
|
|
|
|
mpx.control = CLEAR; /* clear control */
|
|
mpx.flagbuf = CLEAR; /* clear flag buffer */
|
|
mpx.flag = CLEAR; /* clear flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: [CRS] Controller reset\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioCLC: /* clear control flip-flop */
|
|
mpx.control = CLEAR; /* clear control */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [CLC%s] Control cleared\n", hold_or_clear);
|
|
break;
|
|
|
|
|
|
case ioSTC: /* set control flip-flop */
|
|
mpx.control = SET; /* set control */
|
|
|
|
if (mpx_cmd == CMD_BINARY_READ) /* executing fast binary read? */
|
|
break; /* further command execution inhibited */
|
|
|
|
mpx_cmd = GET_OPCODE (mpx_obuf); /* get command opcode */
|
|
mpx_portkey = GET_KEY (mpx_obuf); /* get port key */
|
|
|
|
if (mpx_state == cmd) /* already scheduled? */
|
|
sim_cancel (&mpx_cntl); /* cancel to get full delay */
|
|
|
|
mpx_state = cmd; /* set command state */
|
|
|
|
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */
|
|
delay = PARAM_DELAY; /* specify parameter wait */
|
|
else /* one-word command */
|
|
delay = CMD_DELAY; /* specify command wait */
|
|
|
|
sim_activate (&mpx_cntl, delay); /* schedule command */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: [STC%s] Command %03o key %d scheduled, "
|
|
"time = %d\n", hold_or_clear, mpx_cmd, mpx_portkey, delay);
|
|
break;
|
|
|
|
|
|
case ioEDT: /* end data transfer */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CPU))
|
|
fputs (">>MPX cpu: [EDT] DCPC transfer ended\n", sim_deb);
|
|
break;
|
|
|
|
|
|
case ioSIR: /* set interrupt request */
|
|
setstdPRL (mpx); /* set standard PRL signal */
|
|
setstdIRQ (mpx); /* set standard IRQ signal */
|
|
setstdSRQ (mpx); /* set standard SRQ signal */
|
|
break;
|
|
|
|
|
|
case ioIAK: /* interrupt acknowledge */
|
|
mpx.flagbuf = CLEAR; /* clear flag buffer */
|
|
break;
|
|
|
|
|
|
default: /* all other signals */
|
|
break; /* are ignored */
|
|
}
|
|
|
|
working_set = working_set & ~signal; /* remove current signal from set */
|
|
}
|
|
|
|
return stat_data;
|
|
}
|
|
|
|
|
|
/* Command executor.
|
|
|
|
We are called by the controller service routine to process one- and two-word
|
|
commands. For two-word commands, the parameter word is present in mpx_param.
|
|
The return value indicates whether the card flag should be set upon
|
|
completion.
|
|
|
|
Most commands execute and complete directly. The read and write commands,
|
|
however, transition to the execution state to simulate the DMA transfer, and
|
|
the "Download executable" command does the same to receive the download from
|
|
the CPU.
|
|
|
|
Several commands were added for the B firmware revision, and the various
|
|
revisions of the RTE drivers sent some commands that were never implemented
|
|
in the mux firmware. The command protocol treated unknown commands as NOPs,
|
|
meaning that the command (and parameter, if it was a two-word command) was
|
|
absorbed and the card flag was set as though the command completed normally.
|
|
This allowed interoperability between firmware and driver revisions.
|
|
|
|
Commands that refer to ports do so indirectly by passing a port key, rather
|
|
than a port number. The key-to-port translation is established by the "Set
|
|
port key" command. If a key is not found in the table, the command is not
|
|
executed, and the status return is ST_BAD_KEY, which in hex is "BAD0".
|
|
|
|
Implementation notes:
|
|
|
|
1. The "Reset to power-on defaults" command causes the firmware to disable
|
|
interrupts and jump to the power-on initialization routine, exactly as
|
|
though the Z80 had received a hardware reset.
|
|
|
|
2. The "Abort DMA transfer" command works because STC causes NMI, so the
|
|
command is executed even in the middle of a DMA transfer. The OTx of the
|
|
command will be sent to the buffer if a "Write data to buffer" command is
|
|
in progress, but the STC will cause this routine to be called, which will
|
|
cancel the buffer and return the controller to the idle state. Note that
|
|
this command might be sent with no transfer in progress, in which case
|
|
nothing is done.
|
|
|
|
3. In response to an "Enable unsolicited interrupts" command, the controller
|
|
service is scheduled to check for a pending UI. If one is found, the
|
|
first UI status word is placed in the input buffer, and an interrupt is
|
|
generated by setting the flag. This causes entry to the driver, which
|
|
issues an "Acknowledge" command to obtain the second status word.
|
|
|
|
It is possible, however, for the interrupt to be ignored. For example,
|
|
the driver may be waiting for a "write buffer available" UI when it is
|
|
called to begin a write to a different port. If the flag is set by
|
|
the UI after RTE has been entered, the interrupt will be held off, and
|
|
the STC sc,C instruction that begins the command sequence will clear the
|
|
flag, removing the interrupt entirely. In this case, the controller will
|
|
reissue the UI when the next "Enable unsolicited interrupts" command is
|
|
sent.
|
|
|
|
Note that the firmware reissues the same UI, rather than recomputing UIs
|
|
and potentially selecting a different one of higher priority.
|
|
|
|
4. The "Fast binary read" command apparently was intended to facilitate
|
|
booting from a 264x tape drive, although no boot loader ROM for the
|
|
multiplexer was ever released. It sends the fast binary read escape
|
|
sequence (ESC e) to the terminal and then packs each pair of characters
|
|
received into a word and sends it to the CPU, accompanied by the device
|
|
flag.
|
|
|
|
The multiplexer firmware disables interrupts and then manipulates the SIO
|
|
for port 0 directly. Significantly, it does no interpretation of the
|
|
incoming data and sits in an endless I/O loop, so the only way to exit
|
|
the command is to reset the card with a CRS (front panel PRESET or CLC 0
|
|
instruction execution). Sending a command will not work; although the
|
|
NMI will interrupt the fast binary read, the NMI handler simply sets a
|
|
flag that is tested by the scheduler poll. Because the processor is in
|
|
an endless loop, control never returns to the scheduler, so the command
|
|
is never seen.
|
|
|
|
5. The "Terminate active receive buffer" behavior is a bit tricky. If the
|
|
read buffer has characters, the buffer is terminated as though a
|
|
"terminate on count" condition occurred. If the buffer is empty,
|
|
however, a "terminate on count = 1" condition is established. When a
|
|
character is received, the buffer is terminated, and the buffer
|
|
termination count is reset to 254.
|
|
*/
|
|
|
|
static t_bool exec_command (void)
|
|
{
|
|
int32 port;
|
|
uint32 svc_time;
|
|
t_bool set_flag = TRUE; /* flag is normally set on completion */
|
|
STATE next_state = idle; /* command normally executes to completion */
|
|
|
|
mpx_ibuf = ST_OK; /* return status is normally OK */
|
|
|
|
switch (mpx_cmd) {
|
|
|
|
case CMD_NOP: /* no operation */
|
|
break; /* just ignore */
|
|
|
|
|
|
case CMD_RESET: /* reset firmware */
|
|
controller_reset (); /* reset program variables */
|
|
mpx_ibuf = ST_TEST_OK; /* return self-test OK code */
|
|
break;
|
|
|
|
|
|
case CMD_ENABLE_UI:
|
|
mpx_uien = TRUE; /* enable unsolicited interrupts */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* and schedule controller for UI check */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Controller status check scheduled, "
|
|
"time = %d\n", CMD_DELAY);
|
|
|
|
set_flag = FALSE; /* do not set the flag at completion */
|
|
break;
|
|
|
|
|
|
case CMD_DISABLE:
|
|
switch (mpx_portkey) {
|
|
case SUBCMD_UI:
|
|
mpx_uien = FALSE; /* disable unsolicited interrupts */
|
|
break;
|
|
|
|
case SUBCMD_DMA:
|
|
if (mpx_flags [mpx_port] & FL_WRFILL) /* write buffer xfer in progress? */
|
|
buf_cancel (iowrite, mpx_port, put); /* cancel it */
|
|
else if (mpx_flags [mpx_port] & FL_RDEMPT) /* read buffer xfer in progress? */
|
|
buf_cancel (ioread, mpx_port, get); /* cancel it */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_ACK: /* acknowledge unsolicited interrupt */
|
|
switch (mpx_uicode & UI_REASON) {
|
|
|
|
case UI_WRBUF_AVAIL: /* write buffer notification */
|
|
mpx_flags [mpx_port] &= ~FL_WANTBUF; /* clear flag */
|
|
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer available */
|
|
break;
|
|
|
|
case UI_RDBUF_AVAIL: /* read buffer notification */
|
|
mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */
|
|
|
|
mpx_ibuf = buf_get (ioread, mpx_port) << 8 | /* get header value and position */
|
|
buf_len (ioread, mpx_port, get); /* and include buffer length */
|
|
|
|
if (mpx_flags [mpx_port] & FL_RDOVFLOW) { /* did a buffer overflow? */
|
|
mpx_ibuf = mpx_ibuf | RS_OVERFLOW; /* report it */
|
|
mpx_flags [mpx_port] &= ~FL_RDOVFLOW; /* clear overflow flag */
|
|
}
|
|
break;
|
|
|
|
case UI_BRK_RECD: /* break received */
|
|
mpx_flags [mpx_port] &= ~FL_BREAK; /* clear flag */
|
|
mpx_ibuf = 0; /* 2nd word is zero */
|
|
break;
|
|
}
|
|
|
|
mpx_uicode = 0; /* clear notification code */
|
|
break;
|
|
|
|
|
|
case CMD_CANCEL: /* cancel first read buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
buf_cancel (ioread, port, get); /* cancel get buffer */
|
|
|
|
if ((buf_avail (ioread, port) == 1) && /* one buffer remaining? */
|
|
!(mpx_flags [port] & FL_RDFILL)) /* and not filling it? */
|
|
mpx_flags [port] |= FL_HAVEBUF; /* indicate buffer availability */
|
|
break;
|
|
|
|
|
|
case CMD_CANCEL_ALL: /* cancel all read buffers */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
buf_init (ioread, port); /* reinitialize read buffers */
|
|
break;
|
|
|
|
|
|
case CMD_BINARY_READ: /* fast binary read */
|
|
for (port = 0; port < MPX_PORTS; port++)
|
|
sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */
|
|
|
|
mpx_flags [0] = 0; /* clear port 0 state flags */
|
|
mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */
|
|
mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */
|
|
|
|
tmxr_putc_ln (&mpx_ldsc [0], ESC); /* send fast binary read */
|
|
tmxr_putc_ln (&mpx_ldsc [0], 'e'); /* escape sequence to port 0 */
|
|
tmxr_poll_tx (&mpx_desc); /* flush output */
|
|
|
|
next_state = exec; /* set execution state */
|
|
break;
|
|
|
|
|
|
case CMD_REQ_WRITE: /* request write buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
if (buf_avail (iowrite, port) > 0) /* is a buffer available? */
|
|
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer limit */
|
|
|
|
else {
|
|
mpx_ibuf = 0; /* report none available */
|
|
mpx_flags [port] |= FL_WANTBUF; /* set buffer request */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_WRITE: /* write to buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) { /* port defined? */
|
|
mpx_port = port; /* save port number */
|
|
mpx_iolen = mpx_param & WR_LENGTH; /* save request length */
|
|
next_state = exec; /* set execution state */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_SET_KEY: /* set port key and configuration */
|
|
port = GET_PORT (mpx_param); /* get target port number */
|
|
mpx_key [port] = (uint8) mpx_portkey; /* set port key */
|
|
mpx_config [port] = mpx_param; /* set port configuration word */
|
|
|
|
svc_time = service_time (mpx_param); /* get service time for baud rate */
|
|
|
|
if (svc_time) /* want to change? */
|
|
mpx_unit [port].wait = svc_time; /* set service time */
|
|
|
|
mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */
|
|
break;
|
|
|
|
|
|
case CMD_SET_RCV: /* set receive type */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_rcvtype [port] = mpx_param; /* save port receive type */
|
|
break;
|
|
|
|
|
|
case CMD_SET_COUNT: /* set character count */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_charcnt [port] = mpx_param; /* save port character count */
|
|
break;
|
|
|
|
|
|
case CMD_SET_FLOW: /* set flow control */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
mpx_flowcntl [port] = mpx_param & FC_XONXOFF; /* save port flow control */
|
|
|
|
if (mpx_param & FC_FORCE_XON) /* force XON? */
|
|
mpx_flags [port] &= ~FL_XOFF; /* resume transmission if suspended */
|
|
break;
|
|
|
|
|
|
case CMD_READ: /* read from buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) { /* port defined? */
|
|
mpx_port = port; /* save port number */
|
|
mpx_iolen = mpx_param; /* save request length */
|
|
|
|
sim_activate (&mpx_cntl, DATA_DELAY); /* schedule the transfer */
|
|
next_state = exec; /* set execution state */
|
|
set_flag = FALSE; /* no flag until word ready */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_DL_EXEC: /* Download executable */
|
|
mpx_iolen = mpx_param; /* save request length */
|
|
next_state = exec; /* set execution state */
|
|
break;
|
|
|
|
|
|
case CMD_CN_LINE: /* connect modem line */
|
|
case CMD_DC_LINE: /* disconnect modem line */
|
|
case CMD_LOOPBACK: /* enable/disable modem loopback */
|
|
mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */
|
|
break;
|
|
|
|
|
|
case CMD_GET_STATUS: /* get modem status */
|
|
mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */
|
|
break;
|
|
|
|
|
|
case CMD_TERM_BUF: /* terminate active receive buffer */
|
|
port = key_to_port (mpx_portkey); /* get port */
|
|
|
|
if (port >= 0) /* port defined? */
|
|
if (buf_len (ioread, port, put) > 0) { /* any chars in buffer? */
|
|
buf_term (ioread, port, 0); /* terminate buffer and set header */
|
|
|
|
if (buf_avail (ioread, port) == 1) /* first read buffer? */
|
|
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */
|
|
}
|
|
|
|
else { /* buffer is empty */
|
|
mpx_charcnt [port] = 1; /* set to terminate on one char */
|
|
mpx_flags [port] |= FL_ALERT; /* set alert flag */
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_VCP_PUT: /* VCP put byte */
|
|
case CMD_VCP_PUT_BUF: /* VCP put buffer */
|
|
case CMD_VCP_GET: /* VCP get byte */
|
|
case CMD_VCP_GET_BUF: /* VCP get buffer */
|
|
case CMD_VCP_EXIT: /* Exit VCP mode */
|
|
case CMD_VCP_ENTER: /* Enter VCP mode */
|
|
|
|
default: /* unknown command */
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Unknown command %03o ignored\n", mpx_cmd);
|
|
}
|
|
|
|
mpx_state = next_state;
|
|
return set_flag;
|
|
}
|
|
|
|
|
|
/* Multiplexer controller service.
|
|
|
|
The controller service handles commands and data transfers to and from the
|
|
CPU. The delay in scheduling the controller service represents the firmware
|
|
command or data execution time. The controller may be in one of four states
|
|
upon entry: idle, first word of command received (cmd), command parameter
|
|
received (param), or data transfer (exec).
|
|
|
|
Entry in the command state causes execution of one-word commands and
|
|
solicitation of command parameters for two-word commands, which are executed
|
|
when entering in the parameter state.
|
|
|
|
Entry in the data transfer state moves one word between the CPU and a read or
|
|
write buffer. For writes, the write buffer is filled with words from the
|
|
CPU. Once the indicated number of words have been transferred, the
|
|
appropriate line service is scheduled to send the characters. For reads,
|
|
characters are unloaded from the read buffer to the CPU; an odd-length
|
|
transfer is padded with a blank. A read of fewer characters than are present
|
|
in the buffer will return the remaining characters when the next read is
|
|
performed.
|
|
|
|
Each read or write is terminated by the CPU sending one additional word (the
|
|
RTE drivers send -1). The command completes when this word is acknowledged
|
|
by the card setting the device flag. For zero-length writes, this additional
|
|
word will be the only word sent.
|
|
|
|
Data transfer is also used by the "Download executable" command to absorb the
|
|
downloaded program. The firmware jumps to location 5100 hex in the
|
|
downloaded program upon completion of reception. It is the responsibility of
|
|
the program to return to the multiplexer firmware and to return to the CPU
|
|
whatever status is appropriate when it is done. Under simulation, we simply
|
|
"sink" the program and return status compatible with the multiplexer
|
|
diagnostic program to simulate a passing test.
|
|
|
|
Entry in the idle state checks for unsolicited interrupts. UIs are sent to
|
|
the host when the controller is idle, UIs have been enabled, and a UI
|
|
condition exists. If a UI is not acknowledged, it will remain pending and
|
|
will be reissued the next time the controller is idle and UIs have been
|
|
enabled.
|
|
|
|
UI conditions are kept in the per-port flags. The UI conditions are write
|
|
buffer available, read buffer available, break received, modem line
|
|
connected, and modem line disconnected. The latter two conditions are not
|
|
implemented in this simulation. If a break condition occurs at the same time
|
|
as a read buffer completion, the break has priority; the buffer UI will occur
|
|
after the break UI is acknowledged.
|
|
|
|
The firmware checks for UI condition flags as part of the scheduler polling
|
|
loop. Under simulation, though, UIs can occur only in two places: the point
|
|
of origin (e.g., termination of a read buffer), or the "Enable unsolicited
|
|
input" command executor. UIs will be generated at the point of origin only
|
|
if the simulator is idle. If the simulator is not idle, it is assumed that
|
|
UIs have been disabled to execute the current command and will be reenabled
|
|
when the command sequence is complete.
|
|
|
|
When the multiplexer is reset, and before the port keys are set, all ports
|
|
enter "echoplex" mode. In this mode, characters received are echoed back as
|
|
a functional test. Each port terminates buffers on CR reception. We detect
|
|
this condition, cancel the buffer, and discard the buffer termination UI.
|
|
|
|
Implementation notes:
|
|
|
|
1. The firmware transfers the full amount requested by the CPU, even if the
|
|
transfer is longer than the buffer. Also, zero-length transfers program
|
|
the card DMA chip to transfer 0 bytes; this results in a transfer of 217
|
|
bytes, per the Zilog databook. Under simulation, writes beyond the
|
|
buffer are accepted from the CPU but discarded, and reads beyond the
|
|
buffer return blanks.
|
|
|
|
2. We should never return from this routine in the "cmd" state, so debugging
|
|
will report "internal error!" if we do.
|
|
*/
|
|
|
|
t_stat mpx_cntl_svc (UNIT *uptr)
|
|
{
|
|
uint8 ch;
|
|
uint32 i;
|
|
t_bool add_crlf;
|
|
t_bool set_flag = TRUE;
|
|
STATE last_state = mpx_state;
|
|
|
|
static const char *cmd_state [] = { "complete", "internal error!", "waiting for parameter", "executing" };
|
|
|
|
|
|
switch (mpx_state) { /* dispatch on current state */
|
|
|
|
case idle: /* controller idle */
|
|
set_flag = FALSE; /* assume no UI */
|
|
|
|
if (mpx_uicode) { /* unacknowledged UI? */
|
|
if (mpx_uien == TRUE) { /* interrupts enabled? */
|
|
mpx_port = GET_UIPORT (mpx_uicode); /* get port number */
|
|
mpx_portkey = mpx_key [mpx_port]; /* get port key */
|
|
mpx_ibuf = mpx_uicode & UI_REASON | mpx_portkey; /* report UI reason and port key */
|
|
set_flag = TRUE; /* reissue host interrupt */
|
|
mpx_uien = FALSE; /* disable UI */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt reissued, "
|
|
"reason = %d\n", mpx_port, mpx_portkey, GET_UIREASON (mpx_uicode));
|
|
}
|
|
}
|
|
|
|
else { /* no unacknowledged UI */
|
|
for (i = 0; i < MPX_PORTS; i++) { /* check all ports for UIs */
|
|
if (mpx_flags [i] & FL_UI_PENDING) { /* pending UI? */
|
|
mpx_portkey = mpx_key [i]; /* get port key */
|
|
|
|
if (mpx_portkey == KEY_DEFAULT) { /* key defined? */
|
|
if (mpx_flags [i] & FL_HAVEBUF) /* no, is this read buffer avail? */
|
|
buf_cancel (ioread, i, get); /* cancel buffer */
|
|
|
|
mpx_flags [i] &= ~FL_UI_PENDING; /* cancel pending UI */
|
|
}
|
|
|
|
else if (mpx_uien == TRUE) { /* interrupts enabled? */
|
|
if ((mpx_flags [i] & FL_WANTBUF) && /* port wants a write buffer? */
|
|
(buf_avail (iowrite, i) > 0)) /* and one is available? */
|
|
mpx_uicode = UI_WRBUF_AVAIL; /* set UI reason */
|
|
|
|
else if (mpx_flags [i] & FL_BREAK) /* received a line BREAK? */
|
|
mpx_uicode = UI_BRK_RECD; /* set UI reason */
|
|
|
|
else if (mpx_flags [i] & FL_HAVEBUF) /* have a read buffer ready? */
|
|
mpx_uicode = UI_RDBUF_AVAIL; /* set UI reason */
|
|
|
|
if (mpx_uicode) { /* UI to send? */
|
|
mpx_port = i; /* set port number for Acknowledge */
|
|
mpx_ibuf = mpx_uicode | mpx_portkey; /* merge UI reason and port key */
|
|
mpx_uicode = mpx_uicode | mpx_port; /* save UI reason and port */
|
|
set_flag = TRUE; /* interrupt host */
|
|
mpx_uien = FALSE; /* disable UI */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt generated, "
|
|
"reason = %d\n", i, mpx_portkey, GET_UIREASON (mpx_uicode));
|
|
|
|
break; /* quit after first UI */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case cmd: /* command state */
|
|
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */
|
|
mpx_state = param; /* look for parameter before executing */
|
|
else
|
|
set_flag = exec_command (); /* execute one-word command */
|
|
break;
|
|
|
|
|
|
case param: /* parameter get state */
|
|
mpx_param = mpx_obuf; /* save parameter */
|
|
set_flag = exec_command (); /* execute two-word command */
|
|
break;
|
|
|
|
|
|
case exec: /* execution state */
|
|
switch (mpx_cmd) {
|
|
|
|
case CMD_BINARY_READ: /* fast binary read */
|
|
mpx_flags [0] &= ~FL_HAVEBUF; /* data word was picked up by CPU */
|
|
set_flag = FALSE; /* suppress device flag */
|
|
break;
|
|
|
|
|
|
case CMD_WRITE: /* transfer data to buffer */
|
|
if (mpx_iolen <= 0) { /* last (or only) entry? */
|
|
mpx_state = idle; /* idle controller */
|
|
|
|
if (mpx_iolen < 0) /* tie-off for buffer complete? */
|
|
break; /* we're done */
|
|
}
|
|
|
|
add_crlf = ((mpx_param & /* CRLF should be added */
|
|
(WR_ADD_CRLF | WR_PARTIAL)) == WR_ADD_CRLF);
|
|
|
|
for (i = 0; i < 2; i++) /* output one or two chars */
|
|
if (mpx_iolen > 0) { /* more to do? */
|
|
if (i) /* high or low byte? */
|
|
ch = (uint8) (mpx_obuf & 0377); /* low byte */
|
|
else
|
|
ch = mpx_obuf >> 8; /* high byte */
|
|
|
|
if ((mpx_iolen == 1) && /* final char? */
|
|
(ch == '_') && add_crlf) { /* underscore and asking for CRLF? */
|
|
|
|
add_crlf = FALSE; /* suppress CRLF */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character '_' "
|
|
"suppressed CR/LF\n", mpx_port);
|
|
}
|
|
|
|
else if (buf_len (iowrite, mpx_port, put) < WR_BUF_LIMIT)
|
|
buf_put (iowrite, mpx_port, ch); /* add char to buffer if space avail */
|
|
|
|
mpx_iolen = mpx_iolen - 1; /* drop remaining count */
|
|
}
|
|
|
|
if (mpx_iolen == 0) { /* buffer done? */
|
|
if (add_crlf) { /* want CRLF? */
|
|
buf_put (iowrite, mpx_port, CR); /* add CR to buffer */
|
|
buf_put (iowrite, mpx_port, LF); /* add LF to buffer */
|
|
}
|
|
|
|
buf_term (iowrite, mpx_port, mpx_param >> 8); /* terminate buffer */
|
|
mpx_iolen = -1; /* mark as done */
|
|
}
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS) &&
|
|
(sim_is_active (&mpx_unit [mpx_port]) == 0))
|
|
fprintf (sim_deb, ">>MPX cmds: Port %d service scheduled, "
|
|
"time = %d\n", mpx_port, mpx_unit [mpx_port].wait);
|
|
|
|
sim_activate (&mpx_unit [mpx_port], /* start line service */
|
|
mpx_unit [mpx_port].wait);
|
|
break;
|
|
|
|
|
|
case CMD_READ: /* transfer data from buffer */
|
|
if (mpx_iolen < 0) { /* input complete? */
|
|
if (mpx_obuf == 0177777) { /* "tie-off" word received? */
|
|
if (buf_len (ioread, mpx_port, get) == 0) { /* buffer now empty? */
|
|
buf_free (ioread, mpx_port); /* free buffer */
|
|
|
|
if (buf_avail (ioread, mpx_port) == 1) /* another buffer available? */
|
|
mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate availability */
|
|
}
|
|
|
|
mpx_state = idle; /* idle controller */
|
|
}
|
|
|
|
else
|
|
set_flag = FALSE; /* ignore word */
|
|
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) /* input one or two chars */
|
|
if (mpx_iolen > 0) { /* more to transfer? */
|
|
if (buf_len (ioread, mpx_port, get) > 0) /* more chars available? */
|
|
ch = buf_get (ioread, mpx_port); /* get char from buffer */
|
|
else /* buffer exhausted */
|
|
ch = ' '; /* pad with blank */
|
|
|
|
if (i) /* high or low byte? */
|
|
mpx_ibuf = mpx_ibuf | ch; /* low byte */
|
|
else
|
|
mpx_ibuf = ch << 8; /* high byte */
|
|
|
|
mpx_iolen = mpx_iolen - 1; /* drop count */
|
|
}
|
|
|
|
else /* odd number of chars */
|
|
mpx_ibuf = mpx_ibuf | ' '; /* pad last with blank */
|
|
|
|
if (mpx_iolen == 0) /* end of host xfer? */
|
|
mpx_iolen = -1; /* mark as done */
|
|
|
|
break;
|
|
|
|
|
|
case CMD_DL_EXEC: /* sink data from host */
|
|
if (mpx_iolen <= 0) { /* final entry? */
|
|
mpx_state = idle; /* idle controller */
|
|
mpx_ibuf = ST_DIAG_OK; /* return diag passed status */
|
|
}
|
|
|
|
else {
|
|
if (mpx_iolen > 0) /* more from host? */
|
|
mpx_iolen = mpx_iolen - 2; /* sink two bytes */
|
|
|
|
if (mpx_iolen <= 0) /* finished download? */
|
|
sim_activate (&mpx_cntl, CMD_DELAY); /* schedule completion */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fprintf (sim_deb, ">>MPX cmds: Download completion scheduled, "
|
|
"time = %d\n", CMD_DELAY);
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
default: /* no other entries allowed */
|
|
return SCPE_IERR; /* simulator error! */
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS) && /* debug print? */
|
|
(last_state != mpx_state)) { /* and state change? */
|
|
fprintf (sim_deb, ">>MPX cmds: Command %03o ", mpx_cmd);
|
|
|
|
if ((mpx_cmd & CMD_TWO_WORDS) && (mpx_state != param))
|
|
fprintf (sim_deb, "parameter %06o ", mpx_param);
|
|
|
|
fputs (cmd_state [mpx_state], sim_deb);
|
|
fputc ('\n', sim_deb);
|
|
}
|
|
|
|
if (set_flag) {
|
|
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_CMDS))
|
|
fputs (">>MPX cmds: Flag set\n", sim_deb);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Multiplexer line service.
|
|
|
|
The line service routine is used to transmit and receive characters. It is
|
|
started when a buffer is ready for output or when the 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, 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')) { /* power-on reset? */
|
|
emptying_flags [ioread] = FL_RDEMPT; /* initialize buffer flags constants */
|
|
emptying_flags [iowrite] = FL_WREMPT;
|
|
filling_flags [ioread] = FL_RDFILL;
|
|
filling_flags [iowrite] = FL_WRFILL;
|
|
}
|
|
|
|
IOPRESET (&mpx_dib); /* PRESET device (does not use PON) */
|
|
|
|
mpx_ibuf = 0; /* clear input buffer */
|
|
|
|
if (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 to inhibit its display. Attaching to a disabled
|
|
unit is not allowed, so we first enable the unit, then attach it, then
|
|
disable it again. Attachment is reported by the "mpx_status" routine below.
|
|
|
|
The 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 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 millisecond, which is the
|
|
1000 E-Series execution speed. Baud rate 0 means "don't change" and is
|
|
handled by the "Set port key" command executor.
|
|
|
|
Baud rate settings of 13-15 are marked as "reserved" in the user manual, but
|
|
the firmware defines these as 38400, 9600, and 9600 baud, respectively.
|
|
*/
|
|
|
|
static uint32 service_time (uint16 control_word)
|
|
{
|
|
/* Baud Rates 0- 7 : --, 50, 75, 110, 134.5, 150, 300, 1200, */
|
|
/* Baud Rates 8-15 : 1800, 2400, 4800, 9600, 19200, 38400, 9600, 9600 */
|
|
static const int32 ticks [] = { 0, 316000, 210667, 143636, 117472, 105333, 52667, 13167,
|
|
8778, 6583, 3292, 1646, 823, 411, 1646, 1646 };
|
|
|
|
return ticks [GET_BAUDRATE (control_word)]; /* return service time for indicated rate */
|
|
}
|
|
|
|
|
|
/* Translate port key to port number.
|
|
|
|
Port keys are scanned in reverse port order, so if more than one port has the
|
|
same port key, commands specifying that key will affect the highest numbered
|
|
port.
|
|
|
|
If a port key is the reserved value 255, then the port key has not been set.
|
|
In this case, set the input buffer to 0xBAD0 and return -1 to indicate
|
|
failure.
|
|
*/
|
|
|
|
static int32 key_to_port (uint32 key)
|
|
{
|
|
int32 i;
|
|
|
|
for (i = MPX_PORTS - 1; i >= 0; i--) /* scan in reverse order */
|
|
if (mpx_key [i] == key) /* key found? */
|
|
return i; /* return port number */
|
|
|
|
mpx_ibuf = ST_BAD_KEY; /* key not found: set status */
|
|
return -1; /* return failure code */
|
|
}
|
|
|
|
|
|
/* Buffer manipulation routines.
|
|
|
|
The 12792 hardware provides 16K bytes of RAM to the microprocessor. From
|
|
this pool, the firmware allocates per-port read/write buffers and state
|
|
variables, global variables, and the system stack. Allocations are static
|
|
and differ between firmware revisions.
|
|
|
|
The A/B/C revisions allocate two 254-byte read buffers and two 254-byte write
|
|
buffers per port. Assuming an idle condition, the first write to a port
|
|
transfers characters to the first write buffer. When the transfer completes,
|
|
the SIO begins transmitting. During transmission, a second write can be
|
|
initiated, which transfers characters to the second write buffer. If a third
|
|
write is attempted before the first buffer has been released, it will be
|
|
denied until the SIO completes transmission; then, if enabled, an unsolicited
|
|
interrupt will occur to announce buffer availability. The "active" (filling)
|
|
buffer alternates between the two.
|
|
|
|
At idle, characters received will fill the first read buffer. When the read
|
|
completes according to the previously set termination criteria, an
|
|
unsolicited interrupt will occur (if enabled) to announce buffer
|
|
availability. If more characters are received before the first buffer has
|
|
been transferred to the CPU, they will fill the second buffer. If that read
|
|
also completes, additional characters will be discarded until the first
|
|
buffer has been emptied. The "active" (emptying) buffer alternates between
|
|
the two.
|
|
|
|
With this configuration, two one-character writes or reads will allocate both
|
|
available buffers, even though each was essentially empty.
|
|
|
|
The D revision allocates one 1024-byte FIFO read buffer and one 892-byte
|
|
write buffer per port. As with the A/B/C revisions, the first write to a
|
|
port transfers characters to the write buffer, and serial transmission begins
|
|
when the write completes. However, the write buffer is not a FIFO, so the
|
|
host is not permitted another write request until the entire buffer has been
|
|
transmitted.
|
|
|
|
The read buffer is a FIFO. Characters received are placed into the FIFO as a
|
|
stream. Unlike the A/B/C revisions, character editing and termination
|
|
conditions are not evaluated until the buffer is read. Therefore, a full
|
|
1024 characters may be received before additional characters would be
|
|
discarded.
|
|
|
|
When the first character is received, an unsolicited interrupt occurs (if
|
|
enabled) to announce data reception. A host read may then be initiated. The
|
|
write buffer is used temporarily to process characters from the read buffer.
|
|
Characters are copied from the read to the write buffer while editing as
|
|
directed by the configuration accompanying the read request (e.g., deleting
|
|
the character preceding a BS, stripping CR/LF, etc.). When the termination
|
|
condition is found, the read command completes. Incoming characters may be
|
|
added to the FIFO while this is occurring.
|
|
|
|
In summary, the revision differences in buffer handling are:
|
|
|
|
Revisions A/B/C:
|
|
- two 254-byte receive buffers
|
|
- a buffer is "full" when the terminator character or count is received
|
|
- termination type must be established before the corresponding read
|
|
- data is echoed as it is received
|
|
|
|
Revision D:
|
|
- one 1024-byte receive buffer
|
|
- buffer is "full" only when 1024 characters are received
|
|
- the concept of a buffer terminator does not apply, as the data is not
|
|
examined until a read is requested and characters are retrieved from the
|
|
FIFO.
|
|
- data is not echoed until it is read
|
|
|
|
To implement the C revision behavior, while preserving the option of reusing
|
|
the buffer handlers for future D revision support, the dual 254-byte buffers
|
|
are implemented as a single 514-byte circular FIFO with capacity limited to
|
|
254 bytes per buffer. This reserves space for a CR and LF and for a header
|
|
byte in each buffer. The header byte preserves per-buffer state information.
|
|
|
|
In this implementation, the buffer "put" index points at the next free
|
|
location, and the buffer "get" index points at the next character to
|
|
retrieve. In addition to "put" and "get" indexes, a third "separator" index
|
|
is maintained to divide the FIFO into two areas corresponding to the two
|
|
buffers, and a "buffer filling" flag is maintained for each FIFO that is set
|
|
by the fill (put) routine and cleared by the terminate buffer routine.
|
|
|
|
Graphically, the implementation is as follows for buffer "B[]", get "G", put
|
|
"P", and separator "S" indexes:
|
|
|
|
1. Initialize: 2. Fill first buffer:
|
|
G = S = P = 0 B[P] = char; Incr (P)
|
|
|
|
|------------------------------| |---------|--------------------|
|
|
G G P -->
|
|
S S
|
|
P
|
|
|
|
3. Terminate first buffer: 4. Fill second buffer:
|
|
if S == G then S = P else nop B[P] = char; Incr (P)
|
|
|
|
|------------|-----------------| |------------|------|----------|
|
|
G /---> S G S P -->
|
|
* ----/ P
|
|
|
|
5. Terminate second buffer: 6. Empty first buffer:
|
|
if S == G then S = P else nop char = B[G]; Incr (G)
|
|
|
|
|------------|------------|----| |----|-------|------------|----|
|
|
G S P G --> S P
|
|
|
|
7. First buffer is empty: 8. Free first buffer:
|
|
G == S if !filling then S = P else nop
|
|
|
|
|------------|------------|----| |------------|------------|----|
|
|
G P G /---> S
|
|
S * ----/ P
|
|
|
|
9. Empty second buffer: 10. Second buffer empty:
|
|
char = B[G]; Incr (G) G == S
|
|
|
|
|----------------|--------|----| |-------------------------|----|
|
|
G --> S G
|
|
P S
|
|
P
|
|
11. Free second buffer:
|
|
if !filling then S = P else nop
|
|
|
|
|-------------------------|----|
|
|
G
|
|
S
|
|
P
|
|
|
|
We also provide the following utility routines:
|
|
|
|
- Remove Character: Decr (P)
|
|
|
|
- Cancel Buffer: if S == G then P = G else G = S
|
|
|
|
- Buffer Length: if S < G then return S + BUFSIZE - G else return S - G
|
|
|
|
- Buffers Available: if G == P then return 2 else if G != S != P then return
|
|
0 else return 1
|
|
|
|
The "buffer filling" flag is necessary for the "free" routine to decide
|
|
whether to advance the separator index. If the first buffer is to be freed,
|
|
then G == S and S != P. If the second buffer is already filled, then S = P.
|
|
However, if the buffer is still filling, then S must remain at G. This
|
|
cannot be determined from G, S, and P alone.
|
|
|
|
A "buffer emptying" flag is also employed to record whether the per-buffer
|
|
header has been obtained. This allows the buffer length to exclude the
|
|
header and reflect only the characters present.
|
|
*/
|
|
|
|
|
|
/* Increment a buffer index with wraparound */
|
|
|
|
static uint16 buf_incr (BUF_INDEX index, uint32 port, IO_OPER rw, int increment)
|
|
{
|
|
index [port] [rw] =
|
|
(index [port] [rw] + buf_size [rw] + increment) % buf_size [rw];
|
|
|
|
return index [port] [rw];
|
|
}
|
|
|
|
|
|
/* Initialize the buffer.
|
|
|
|
Initialization sets the three indexes to zero and clears the buffer state
|
|
flags.
|
|
*/
|
|
|
|
static void buf_init (IO_OPER rw, uint32 port)
|
|
{
|
|
mpx_get [port] [rw] = 0; /* clear indexes */
|
|
mpx_sep [port] [rw] = 0;
|
|
mpx_put [port] [rw] = 0;
|
|
|
|
if (rw == ioread)
|
|
mpx_flags [mpx_port] &= ~(FL_RDFLAGS); /* clear read buffer flags */
|
|
else
|
|
mpx_flags [mpx_port] &= ~(FL_WRFLAGS); /* clear write buffer flags */
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get a character from the buffer.
|
|
|
|
The character indicated by the "get" index is retrieved from the buffer, and
|
|
the index is incremented with wraparound. If the buffer is now empty, the
|
|
"buffer emptying" flag is cleared. Otherwise, it is set to indicate that
|
|
characters have been removed from the buffer.
|
|
*/
|
|
|
|
static uint8 buf_get (IO_OPER rw, uint32 port)
|
|
{
|
|
uint8 ch;
|
|
uint32 index = mpx_get [port] [rw]; /* current get index */
|
|
|
|
if (rw == ioread)
|
|
ch = mpx_rbuf [port] [index]; /* get char from read buffer */
|
|
else
|
|
ch = mpx_wbuf [port] [index]; /* get char from write buffer */
|
|
|
|
buf_incr (mpx_get, port, rw, +1); /* increment circular get index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
if (mpx_flags [port] & emptying_flags [rw])
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s get from %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
else
|
|
fprintf (sim_deb, ">>MPX buf: Port %d header %03o get from %s buffer "
|
|
"[%d]\n", port, ch, io_op [rw], index);
|
|
|
|
if (mpx_get [port] [rw] == mpx_sep [port] [rw]) /* buffer now empty? */
|
|
mpx_flags [port] &= ~emptying_flags [rw]; /* clear "buffer emptying" flag */
|
|
else
|
|
mpx_flags [port] |= emptying_flags [rw]; /* set "buffer emptying" flag */
|
|
|
|
return ch;
|
|
}
|
|
|
|
|
|
/* Put a character to the buffer.
|
|
|
|
The character is written to the buffer in the slot indicated by the "put"
|
|
index, and the index is incremented with wraparound. The first character put
|
|
to a new buffer reserves space for the header and sets the "buffer filling"
|
|
flag.
|
|
*/
|
|
|
|
static void buf_put (IO_OPER rw, uint32 port, uint8 ch)
|
|
{
|
|
uint32 index;
|
|
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) { /* first put to this buffer? */
|
|
mpx_flags [port] |= filling_flags [rw]; /* set buffer filling flag */
|
|
index = mpx_put [port] [rw]; /* get current put index */
|
|
buf_incr (mpx_put, port, rw, +1); /* reserve space for header */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d reserved header "
|
|
"for %s buffer [%d]\n", port, io_op [rw], index);
|
|
}
|
|
|
|
index = mpx_put [port] [rw]; /* get current put index */
|
|
|
|
if (rw == ioread)
|
|
mpx_rbuf [port] [index] = ch; /* put char in read buffer */
|
|
else
|
|
mpx_wbuf [port] [index] = ch; /* put char in write buffer */
|
|
|
|
buf_incr (mpx_put, port, rw, +1); /* increment circular put index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s put to %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Remove the last character put to the buffer.
|
|
|
|
The most-recent character put to the buffer is removed by decrementing the
|
|
"put" index with wraparound.
|
|
*/
|
|
|
|
static void buf_remove (IO_OPER rw, uint32 port)
|
|
{
|
|
uint8 ch;
|
|
uint32 index;
|
|
|
|
index = buf_incr (mpx_put, port, rw, -1); /* decrement circular put index */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF)) {
|
|
if (rw == ioread)
|
|
ch = mpx_rbuf [port] [index]; /* pick up char from read buffer */
|
|
else
|
|
ch = mpx_wbuf [port] [index]; /* pick up char from write buffer */
|
|
|
|
fprintf (sim_deb, ">>MPX buf: Port %d character %s removed from %s buffer "
|
|
"[%d]\n", port, fmt_char (ch), io_op [rw], index);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/* Terminate the buffer.
|
|
|
|
The buffer is marked to indicate that filling is complete and that the next
|
|
"put" operation should begin a new buffer. The header value is stored in
|
|
first byte of buffer, which is reserved, and the "buffer filling" flag is
|
|
cleared.
|
|
*/
|
|
|
|
static void buf_term (IO_OPER rw, uint32 port, uint8 header)
|
|
{
|
|
uint32 index = mpx_sep [port] [rw]; /* separator index */
|
|
|
|
if (rw == ioread)
|
|
mpx_rbuf [port] [index] = header; /* put header in read buffer */
|
|
else
|
|
mpx_wbuf [port] [index] = header; /* put header in write buffer */
|
|
|
|
mpx_flags [port] = mpx_flags [port] & ~filling_flags [rw]; /* clear filling flag */
|
|
|
|
if (mpx_get [port] [rw] == index) /* reached separator? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move sep to end of next buffer */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d header %03o terminated %s buffer\n",
|
|
port, header, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Free the buffer.
|
|
|
|
The buffer is marked to indicate that it is available for reuse, and the
|
|
"buffer emptying" flag is reset.
|
|
*/
|
|
|
|
static void buf_free (IO_OPER rw, uint32 port)
|
|
{
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling next buffer? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */
|
|
/* else it will be moved when terminated */
|
|
mpx_flags [port] = mpx_flags [port] & ~emptying_flags [rw]; /* clear emptying flag */
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d released %s buffer\n", port, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Cancel the selected buffer.
|
|
|
|
The selected buffer is marked to indicate that it is empty. Either the "put"
|
|
buffer or the "get" buffer may be selected.
|
|
*/
|
|
|
|
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which)
|
|
{
|
|
if (which == put) { /* cancel put buffer? */
|
|
mpx_put [port] [rw] = mpx_sep [port] [rw]; /* move put back to separator */
|
|
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */
|
|
}
|
|
|
|
else { /* cancel get buffer */
|
|
if (mpx_sep [port] [rw] == mpx_get [port] [rw]) { /* filling first buffer? */
|
|
mpx_put [port] [rw] = mpx_get [port] [rw]; /* cancel first buffer */
|
|
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */
|
|
}
|
|
|
|
else { /* not filling first buffer */
|
|
mpx_get [port] [rw] = mpx_sep [port] [rw]; /* cancel first buffer */
|
|
|
|
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling second buffer? */
|
|
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */
|
|
}
|
|
|
|
mpx_flags [port] &= ~emptying_flags [rw]; /* clear emptying flag */
|
|
}
|
|
|
|
if (DEBUG_PRI (mpx_dev, DEB_BUF))
|
|
fprintf (sim_deb, ">>MPX buf: Port %d cancelled %s buffer\n", port, io_op [rw]);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get the buffer length.
|
|
|
|
The current length of the selected buffer (put or get) is returned. For ease
|
|
of use, the returned length does NOT include the header byte, i.e., it
|
|
reflects only the characters contained in the buffer.
|
|
|
|
If the put buffer is selected, and the buffer is filling, or the get buffer
|
|
is selected, and the buffer is not emptying, then subtract one from the
|
|
length for the allocated header.
|
|
*/
|
|
|
|
static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which)
|
|
{
|
|
int32 length;
|
|
|
|
if (which == put)
|
|
length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */
|
|
((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */
|
|
|
|
else {
|
|
length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */
|
|
|
|
if (length && !(mpx_flags [port] & emptying_flags [rw])) /* not empty and not yet emptying? */
|
|
length = length - 1; /* account for allocated header */
|
|
}
|
|
|
|
if (length < 0) /* is length negative? */
|
|
return length + buf_size [rw]; /* account for wraparound */
|
|
else
|
|
return length;
|
|
}
|
|
|
|
|
|
/* Return the number of free buffers available.
|
|
|
|
Either 0, 1, or 2 free buffers will be available. A buffer is available if
|
|
it contains no characters (including the header byte).
|
|
*/
|
|
|
|
static uint32 buf_avail (IO_OPER rw, uint32 port)
|
|
{
|
|
if (mpx_get [port] [rw] == mpx_put [port] [rw]) /* get and put indexes equal? */
|
|
return 2; /* all buffers are free */
|
|
|
|
else if ((mpx_get [port] [rw] != mpx_sep [port] [rw]) && /* get, separator, and put */
|
|
(mpx_sep [port] [rw] != mpx_put [port] [rw])) /* all different? */
|
|
return 0; /* no buffers are free */
|
|
|
|
else
|
|
return 1; /* one buffer free */
|
|
}
|