/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator | |
Copyright (c) 2008-2013, 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-Jan-13 MP Added DEV_MUX and additional DEVICE field values | |
28-Dec-12 JDB Allow direct attach to the poll unit only when restoring | |
10-Feb-12 JDB Deprecated DEVNO in favor of SC | |
Removed DEV_NET to allow restoration of listening port | |
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_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 | DEV_MUX, /* device flags */ | |
0, /* debug control flags */ | |
mpx_deb, /* debug flag name table */ | |
NULL, /* memory size change routine */ | |
NULL, /* logical device name */ | |
NULL, /* help routine */ | |
NULL, /* help attach routine*/ | |
(void *) &mpx_desc /* help context */ | |
}; | |
/* 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 int32 port = uptr - mpx_unit; /* port number */ | |
const uint16 rt = mpx_rcvtype [port]; /* receive type for port */ | |
const uint32 data_bits = 5 + GET_BPC (mpx_config [port]); /* number of data bits */ | |
const uint32 data_mask = (1 << data_bits) - 1; /* mask for data bits */ | |
const t_bool fast_timing = (uptr->flags & UNIT_FASTTIME) != 0; /* port is set for fast timing */ | |
const t_bool fast_binary_read = (mpx_cmd == CMD_BINARY_READ); /* fast binary read in progress */ | |
uint8 ch; | |
int32 chx; | |
uint16 read_length; | |
t_stat status = SCPE_OK; | |
t_bool recv_loop = !fast_binary_read; /* bypass if fast binary read */ | |
t_bool xmit_loop = !(fast_binary_read || /* bypass if fast read or output suspended */ | |
(mpx_flags [port] & (FL_WAITACK | FL_XOFF))); | |
/* Transmission service */ | |
while (xmit_loop && (buf_len (iowrite, port, get) > 0)) { /* character available to output? */ | |
if ((mpx_flags [port] & FL_WREMPT) == 0) { /* has buffer started emptying? */ | |
chx = buf_get (iowrite, port) << 8; /* get header value and position */ | |
if (fast_timing || (chx & WR_NO_ENQACK) || /* do we want handshake? */ | |
!(mpx_config [port] & SK_ENQACK)) /* and configured for handshake? */ | |
mpx_flags [port] &= ~FL_DO_ENQACK; /* no, so clear flag */ | |
else | |
mpx_flags [port] |= FL_DO_ENQACK; /* yes, so set flag */ | |
continue; /* "continue" for zero-length write */ | |
} | |
if (mpx_flags [port] & FL_DO_ENQACK) /* do handshake for this buffer? */ | |
mpx_enq_cntr [port] = mpx_enq_cntr [port] + 1; /* bump character counter */ | |
if (mpx_enq_cntr [port] > ENQ_LIMIT) { /* ready for ENQ? */ | |
mpx_enq_cntr [port] = 0; /* clear ENQ counter */ | |
mpx_ack_wait [port] = 0; /* clear ACK wait timer */ | |
mpx_flags [port] |= FL_WAITACK; /* set wait for ACK */ | |
ch = ENQ; | |
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit ENQ */ | |
xmit_loop = FALSE; /* stop further transmission */ | |
} | |
else { /* not ready for ENQ */ | |
ch = buf_get (iowrite, port) & data_mask; /* get char and mask to bit width */ | |
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */ | |
xmit_loop = (status == SCPE_OK) && fast_timing; /* continue transmission? */ | |
} | |
if ((status == SCPE_OK) && /* transmitted OK? */ | |
DEBUG_PRI (mpx_dev, DEB_XFER)) | |
fprintf (sim_deb, ">>MPX xfer: Port %d character %s transmitted\n", | |
port, fmt_char (ch)); | |
else | |
xmit_loop = FALSE; | |
if (buf_len (iowrite, port, get) == 0) { /* buffer complete? */ | |
buf_free (iowrite, port); /* free buffer */ | |
if (mpx_state == idle) /* controller idle? */ | |
mpx_cntl_svc (&mpx_cntl); /* check for UI */ | |
} | |
} | |
/* Reception service */ | |
while (recv_loop && /* OK to process? */ | |
(chx = tmxr_getc_ln (&mpx_ldsc [port]))) { /* and new char available? */ | |
if (chx & SCPE_BREAK) { /* break detected? */ | |
mpx_flags [port] |= FL_BREAK; /* set break status */ | |
if (DEBUG_PRI (mpx_dev, DEB_XFER)) | |
fputs (">>MPX xfer: Break detected\n", sim_deb); | |
if (mpx_state == idle) /* controller idle? */ | |
mpx_cntl_svc (&mpx_cntl); /* check for UI */ | |
continue; /* discard NUL that accompanied BREAK */ | |
} | |
ch = chx & data_mask; /* mask to bits per char */ | |
if ((ch == XOFF) && /* XOFF? */ | |
(mpx_flowcntl [port] & FC_XONXOFF)) { /* and handshaking enabled? */ | |
mpx_flags [port] |= FL_XOFF; /* suspend transmission */ | |
if (DEBUG_PRI (mpx_dev, DEB_XFER)) | |
fprintf (sim_deb, ">>MPX xfer: Port %d character XOFF " | |
"suspends transmission\n", port); | |
recv_loop = fast_timing; /* set to loop if fast mode */ | |
continue; | |
} | |
else if ((ch == XON) && /* XON? */ | |
(mpx_flags [port] & FL_XOFF)) { /* and currently suspended? */ | |
mpx_flags [port] &= ~FL_XOFF; /* resume transmission */ | |
if (DEBUG_PRI (mpx_dev, DEB_XFER)) | |
fprintf (sim_deb, ">>MPX xfer: Port %d character XON " | |
"resumes transmission\n", port); | |
recv_loop = fast_timing; /* set to loop if fast mode */ | |
continue; | |
} | |
if (DEBUG_PRI (mpx_dev, DEB_XFER)) | |
fprintf (sim_deb, ">>MPX xfer: Port %d character %s received\n", | |
port, fmt_char (ch)); | |
if ((ch == ACK) && (mpx_flags [port] & FL_WAITACK)) { /* ACK and waiting for it? */ | |
mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* clear wait flag */ | |
recv_loop = FALSE; /* absorb character */ | |
} | |
else if ((buf_avail (ioread, port) == 0) && /* no free buffer available for char? */ | |
!(mpx_flags [port] & FL_RDFILL)) { /* and not filling last buffer? */ | |
mpx_flags [port] |= FL_RDOVFLOW; /* set buffer overflow flag */ | |
recv_loop = fast_timing; /* continue loop if fast mode */ | |
} | |
else { /* buffer is available */ | |
if (rt & RT_ENAB_EDIT) /* editing enabled? */ | |
if (ch == BS) { /* backspace? */ | |
if (buf_len (ioread, port, put) > 0) /* at least one character in buffer? */ | |
buf_remove (ioread, port); /* remove last char */ | |
if (rt & RT_ENAB_ECHO) { /* echo enabled? */ | |
tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ | |
if (fast_timing) { /* fast timing mode? */ | |
tmxr_putc_ln (&mpx_ldsc [port], ' '); /* echo space */ | |
tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ | |
} | |
} | |
continue; | |
} | |
else if (ch == DEL) { /* delete line? */ | |
buf_cancel (ioread, port, put); /* cancel put buffer */ | |
if (rt & RT_ENAB_ECHO) { /* echo enabled? */ | |
if (fast_timing) /* fast timing mode? */ | |
tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* echo backslash */ | |
tmxr_putc_ln (&mpx_ldsc [port], CR); /* echo CR */ | |
tmxr_putc_ln (&mpx_ldsc [port], LF); /* and LF */ | |
} | |
continue; | |
} | |
if (uptr->flags & UNIT_CAPSLOCK) /* caps lock mode? */ | |
ch = toupper (ch); /* convert to upper case if lower */ | |
if (rt & RT_ENAB_ECHO) /* echo enabled? */ | |
tmxr_putc_ln (&mpx_ldsc [port], ch); /* echo the char */ | |
if (rt & RT_END_ON_CHAR) { /* end on character? */ | |
recv_loop = FALSE; /* assume termination */ | |
if ((ch == CR) && (rt & RT_END_ON_CR)) { | |
if (rt & RT_ENAB_ECHO) /* echo enabled? */ | |
tmxr_putc_ln (&mpx_ldsc [port], LF); /* send LF */ | |
mpx_param = RS_ETC_CR; /* set termination condition */ | |
} | |
else if ((ch == RS) && (rt & RT_END_ON_RS)) | |
mpx_param = RS_ETC_RS; /* set termination condition */ | |
else if ((ch == EOT) && (rt & RT_END_ON_EOT)) | |
mpx_param = RS_ETC_EOT; /* set termination condition */ | |
else if ((ch == DC2) && (rt & RT_END_ON_DC2)) | |
mpx_param = RS_ETC_DC2; /* set termination condition */ | |
else | |
recv_loop = TRUE; /* no termination */ | |
} | |
if (recv_loop) /* no termination condition? */ | |
buf_put (ioread, port, ch); /* put character in buffer */ | |
read_length = buf_len (ioread, port, put); /* get current buffer length */ | |
if ((rt & RT_END_ON_CNT) && /* end on count */ | |
(read_length == mpx_charcnt [port])) { /* and count reached? */ | |
recv_loop = FALSE; /* set termination */ | |
mpx_param = 0; /* no extra termination info */ | |
if (mpx_flags [port] & FL_ALERT) { /* was this alert for term rcv buffer? */ | |
mpx_flags [port] &= ~FL_ALERT; /* clear alert flag */ | |
mpx_charcnt [port] = RD_BUF_LIMIT; /* reset character count */ | |
} | |
} | |
else if (read_length == RD_BUF_LIMIT) { /* buffer now full? */ | |
recv_loop = FALSE; /* set termination */ | |
mpx_param = mpx_param | RS_PARTIAL; /* and partial buffer flag */ | |
} | |
if (recv_loop) /* no termination condition? */ | |
recv_loop = fast_timing; /* set to loop if fast mode */ | |
else { /* termination occurred */ | |
if (DEBUG_PRI (mpx_dev, DEB_XFER)) { | |
fprintf (sim_deb, ">>MPX xfer: Port %d read terminated on ", port); | |
if (mpx_param & RS_PARTIAL) | |
fputs ("buffer full\n", sim_deb); | |
else if (rt & RT_END_ON_CHAR) | |
fprintf (sim_deb, "character %s\n", fmt_char (ch)); | |
else | |
fprintf (sim_deb, "count = %d\n", mpx_charcnt [port]); | |
} | |
if (buf_len (ioread, port, put) == 0) { /* zero-length read? */ | |
buf_put (ioread, port, 0); /* dummy put to reserve header */ | |
buf_remove (ioread, port); /* back out dummy char leaving header */ | |
} | |
buf_term (ioread, port, mpx_param >> 8); /* terminate buffer and set header */ | |
if (buf_avail (ioread, port) == 1) /* first read buffer? */ | |
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ | |
if (mpx_state == idle) /* controller idle? */ | |
mpx_cntl_svc (&mpx_cntl); /* check for UI */ | |
} | |
} | |
} | |
/* Housekeeping */ | |
if (fast_binary_read) { /* fast binary read in progress? */ | |
if (port == 0) { /* on port 0? */ | |
chx = tmxr_getc_ln (&mpx_ldsc [0]); /* see if a character is ready */ | |
if (chx && !(mpx_flags [0] & FL_HAVEBUF)) { /* character ready and buffer empty? */ | |
if (mpx_flags [0] & FL_WANTBUF) { /* second character? */ | |
mpx_ibuf = mpx_ibuf | (chx & DMASK8); /* merge it into word */ | |
mpx_flags [0] |= FL_HAVEBUF; /* mark buffer as ready */ | |
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ | |
if (DEBUG_PRI (mpx_dev, DEB_CMDS)) | |
fputs (">>MPX cmds: Flag and SRQ set\n", sim_deb); | |
} | |
else /* first character */ | |
mpx_ibuf = (chx & DMASK8) << 8; /* put in top half of word */ | |
mpx_flags [0] ^= FL_WANTBUF; /* toggle byte flag */ | |
} | |
sim_activate (uptr, uptr->wait); /* reschedule service for fast response */ | |
} | |
} | |
else { /* normal service */ | |
tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */ | |
if ((buf_avail (iowrite, port) < 2) && /* more to transmit? */ | |
!(mpx_flags [port] & (FL_WAITACK | FL_XOFF)) || /* and transmission not suspended */ | |
tmxr_rqln (&mpx_ldsc [port])) /* or more to receive? */ | |
sim_activate (uptr, uptr->wait); /* reschedule service */ | |
else | |
if (DEBUG_PRI (mpx_dev, DEB_CMDS)) | |
fprintf (sim_deb, ">>MPX cmds: Port %d service stopped\n", port); | |
} | |
return SCPE_OK; | |
} | |
/* 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. | |
A direct attach to the poll unit is only allowed when restoring a previously | |
saved session. | |
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? */ | |
&& (uptr != &mpx_poll || !(sim_switches & SIM_SW_REST))) /* and not restoring the poll unit? */ | |
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 */ | |
} |