blob: 87b8e60bf95a444fe67e498a9010d802dbb2b31e [file] [log] [blame] [raw]
/* hp3000_atc.c: HP 3000 30032B Asynchronous Terminal Controller simulator
Copyright (c) 2014-2017, J. David Bryan
Copyright (c) 2002-2012, Robert M Supnik
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
AUTHORS 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 names of the authors 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 authors.
ATCD,ATCC HP 30032B Asynchronous Terminal Controller
18-Dec-17 JDB Return event time instead of status from "activate_unit"
11-Dec-17 JDB Reschedule "line_service" if receive buffer has data
26-Oct-17 JDB Call "tmxr_poll_tx" if transmit buffer is full
05-Sep-17 JDB Changed REG_A (permit any symbolic override) to REG_X
16-Sep-16 JDB Fixed atcd_detach to skip channel cancel if SIM_SW_REST
12-Sep-16 JDB Changed DIB register macro usage from SRDATA to DIB_REG
20-Jul-16 JDB Corrected poll_unit "wait" field initializer.
26-Jun-16 JDB Removed tmxr_set_modem_control_passthru call in atcc_reset
09-Jun-16 JDB Added casts for ptrdiff_t to int32 values
16-May-16 JDB Fixed interrupt mask setting
13-May-16 JDB Modified for revised SCP API function parameter types
21-Mar-16 JDB Changed uint16 types to HP_WORD
26-Aug-15 JDB First release version
31-Jul-15 JDB Passes the terminal control diagnostic (D438A)
11-Aug-14 JDB Passes the terminal data diagnostic (D427A)
28-Jul-14 JDB Created from the HP 2100 12920A MUX device simulator
References:
- 30032B Asynchronous Terminal Controller Installation and Service Manual
(30032-90004, February 1977)
- Stand-Alone HP 30032B Terminal Data Interface Diagnostic
(30032-90011, October 1980)
- Stand-Alone HP 30061A Terminal Controller Interface Diagnostic
(30060-90004, February 1976)
- HP 3000 Series III Engineering Diagrams Set
(30000-90141, April 1980)
The HP 30032B Asynchronous Terminal Controller is a 16-channel terminal
multiplexer used with the HP 3000 CX through Series III systems. The ATC
connects from 1 to 16 serial terminals or modems to the HP 3000 at
programmable baud rates from 75 to 2400 bits per second. Character sizes are
also programmable from 5 to 12 bits in length, including the start and stop
bits. Each channel can be independently configured, including for separate
send and receive rates. The ATC is not buffered, so the CPU has to retrieve
each character from a given channel before the next character arrives. To
avoid saturating the CPU with interrupt requests, the ATC maintains an
internal "mini-interrupt" system that queues requests and holds additional
interrupts off until the CPU acknowledges the current request.
The HP 3000CX and Series I use a dedicated serial interface for the system
console, while user terminals are connected to the ATC. For the Series II
and III, the separate card is eliminated, and channel 0 of the ATC is
reserved for the console.
This module is an adaptation of the code originally written by Bob Supnik for
the HP2100 MUX simulator. The MUX device simulates an HP 12920A interface
for an HP 2100/1000 computer. The 12920A is an ATC with the HP 3000 I/O bus
connection replaced by an HP 2100 I/O bus connection. Programming and
operation of the two multiplexers are virtually identical.
The ATC consists of a Terminal Data Interface, which provides direct
connection for 16 serial terminals, and one or two optional Terminal Control
Interfaces, which provides control and status lines for Bell 103 and 202 data
sets, respectively. The ATC base product, order number 30032, consisted of
one TDI card. Option -001 added one TCI, and option -002 added two. A
second ATC subsystem could be added to support an additional 16 terminals or
modems.
This simulation provides one TDI and one optional TCI. Each of the channels
may be connected either to a Telnet session or a serial port on the host
machine. Channel 0 is connected to the simulation console, which initially
performs I/O to the controlling window but may be rerouted instead to a
Telnet session or serial port, if desired. Additional channel configuration
options select the input mode (upshifted or normal), output mode (8-bit,
7-bit, printable, or upshifted), and whether the HP-standard ENQ/ACK
handshaking is done by the external device or internally by the simulator.
A device mode specifies whether terminals or diagnostic loopback cables are
connected to the TDI and TCI. Enabling the diagnostic mode simulates the
installation of eight HP 30062-60003 diagnostic test (loopback) cables
between channels 0-1, 2-3, etc., as required by the multiplexer diagnostics.
In this mode, sending data on one channel automatically receives the same
data on the alternate channel. In addition, all Telnet and serial sessions
are disconnected, and the TDI is detached from the listening port. While in
diagnostic mode, the ATTACH command is not allowed. Enabling terminal mode
allows the TDI to be attached to accept incoming connections again.
Another device mode specifies whether the TDI operates in real-time or
optimized ("fast") timing mode. In the former, character send and receive
events occur at approximately the same rate (in machine instructions) as in
hardware. The latter mode increases the rate to the maximum value consistent
with correct operation in MPE.
Both the TDI and TCI are normally enabled, although the TCI will not be used
unless MPE is configured to use data sets on one or more channels. When so
configured, logging off will cause the channel to disconnect the Telnet
session or drop the Data Terminal Ready signal on the serial port. A channel
controlled by the TCI will be marked as "data set" in a unit listing;
channels not controlled will be marked as "direct".
The TDI and TCI may be disabled, if desired, although the TDI must be
detached from the listening port first. Disabling the TDI does not affect
the simulation console, as the CPU process clock will take over console
polling automatically.
The Terminal Data Interface provides 16 send channels, 16 receive channels,
and 5 auxiliary channels. The auxiliary channels are receive-only and do
not connect to external devices. Rather, they may be connected as a group to
one or more of the other channels. Their primary purpose is to diagnose
conditions (e.g., baud rate) on the connected channel(s).
In hardware, a recirculating memory stores seven 8-bit words of data,
parameters, and status for each of the 37 channels. A set of registers form
a "window" into the recirculating memory, and the memory makes one complete
pass every 69.44 microseconds. Serial transfer rates are determined by each
channel's parameter word, which specifies the number of recirculations that
occur for each bit sent or received.
In simulation, the memory is represented by separate buffer, parameter, and
status arrays. Recirculation is simulated by indexing through each of the
arrays in sequence.
The TDI responds only to direct I/O instructions, as follows:
TDI Control Word Format (CIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | channel number | - - - - - - - | E | A |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
M = master reset
R = reset interrupts
E = enable store of preceding data or parameter word to memory
A = acknowledge interrupt
TDI Status Word Format (TIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | D | I | - | C | R | L | B | - - - - - - - - |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = SIO OK (always 0)
D = direct read/write I/O OK
I = interrupt request
C = read/write completion flag
R = receive/send (0/1) character interrupt
L = character lost
B = break status
TDI Parameter Word Format (WIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 | R | I | E | D | char size | baud rate |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
R = receive/send (0/1) configuration
I = enable channel completion interrupt
E = enable echo (receive) or generate parity (send)
D = diagnose using the auxiliary channels
Character size:
The three least-significant bits of the sum of the data, parity, and stop
bits. For example, 7E1 is 1001, so 001 is coded.
Baud rate:
The value (14400 / device bit rate) - 1. For example, 2400 baud is 005.
TDI Output Data Word Format (WIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | - - | S | send data |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = sync bit
data = right-justified with leading ones
TDI Input Data Word Format (RIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| channel | P | receive data |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
P = computed parity
data = right-justified with leading ones
The Terminal Control Interface provides two serial control outputs and two
serial status inputs for each of 16 channels. The first TCI connects to the
Request to Send (CA) and Data Terminal Ready (CD) control lines and the Data
Carrier Detect (CF) and Data Set Ready (CC) status lines. Addressable
latches hold the control line values and assert them continuously to the 16
channels. In addition, a 16-word by 4-bit RAM holds the expected state for
each channel's status lines and the corresponding interrupt enable bits to
provide notification if those lines change.
The TCI responds only to direct I/O instructions, as follows:
TCI Control Word Format (CIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | S | U | channel | W | X | Q | T | Y | Z | C | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
M = master reset
R = reset interrupts
S = scan status
U = enable DCD/DSR state update
W = enable RTS change
X = enable DTR change
Q = new RTS state
T = new DTR state
Y = DCD interrupt enabled
Z = DSR interrupt enabled
C = expected DCD state
D = expected DSR state
TCI Status Word Format (TIO or RIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | I | 1 | channel | 0 | 0 | J | K | Y | Z | C | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
I = interrupt request
J = DCD interrupt present
K = DSR interrupt present
Y = DCD interrupt enabled
Z = DSR interrupt enabled
C = current DCD state
D = current DSR state
Implementation notes:
1. The UNIT_MODEM flag indicates that a channel is controlled by the TCI.
However, no modifier entry is provided, nor is one needed, as the flag is
set automatically when the TCI first initializes the channel. MPE
defines separate terminal subtype numbers for directly connected
terminals and modem-connected terminals, which are set at system
generation time.
2. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only
so that a debug trace will record the generation correctly.
*/
#include <ctype.h>
#include "hp3000_defs.h"
#include "hp3000_io.h"
#include "sim_tmxr.h"
/* Program limits */
#define TERM_COUNT 16 /* number of terminal channels */
#define AUX_COUNT 5 /* number of auxiliary channels */
#define POLL_COUNT 1 /* number of poll units */
#define RECV_CHAN_COUNT (TERM_COUNT + AUX_COUNT) /* number of receive channels */
#define SEND_CHAN_COUNT TERM_COUNT /* number of send channels */
#define UNIT_COUNT (TERM_COUNT + POLL_COUNT) /* number of units */
#define FIRST_TERM 0 /* first terminal index */
#define LAST_TERM (FIRST_TERM + TERM_COUNT - 1) /* last terminal index */
#define FIRST_AUX TERM_COUNT /* first auxiliary index */
#define LAST_AUX (FIRST_AUX + AUX_COUNT - 1) /* last auxiliary index */
/* Program constants */
#define FAST_IO_TIME 500 /* initial fast receive/send time in event ticks */
#define POLL_RATE 100 /* poll 100 times per second (unless synchronized) */
#define POLL_TIME mS (10) /* poll time is 10 milliseconds */
#define NUL '\000' /* null */
#define ENQ '\005' /* enquire */
#define ACK '\006' /* acknowledge */
#define ASCII_MASK 000177u /* 7-bit ASCII character set mask */
#define GEN_ACK (TMXR_VALID | SCPE_KFLAG | ACK) /* a generated ACK character */
#define SCAN_ALL (-1) /* scan all channels for completion */
/* Parity functions derived from the global lookup table */
#define RECV_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDR_PARITY)
#define SEND_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDS_PARITY)
/* Debug flags */
#define DEB_CSRW (1u << 0) /* trace command initiations and completions */
#define DEB_XFER (1u << 1) /* trace data receptions and transmissions */
#define DEB_IOB (1u << 2) /* trace I/O bus signals and data words */
#define DEB_SERV (1u << 3) /* trace channel service scheduling calls */
#define DEB_PSERV (1u << 4) /* trace poll service scheduling calls */
/* Common per-unit multiplexer channel state variables */
#define recv_time u3 /* realistic receive time in event ticks */
#define send_time u4 /* realistic send time in event ticks */
#define stop_bits u5 /* stop bits to be added to each character received */
/* Device flags */
#define DEV_DIAG_SHIFT (DEV_V_UF + 0) /* diagnostic loopback */
#define DEV_REALTIME_SHIFT (DEV_V_UF + 1) /* timing mode is realistic */
#define DEV_DIAG (1u << DEV_DIAG_SHIFT) /* diagnostic mode flag */
#define DEV_REALTIME (1u << DEV_REALTIME_SHIFT) /* realistic timing flag */
/* Unit flags */
#define UNIT_CAPSLOCK_SHIFT (TTUF_V_UF + 0) /* caps lock mode */
#define UNIT_LOCALACK_SHIFT (TTUF_V_UF + 1) /* local ACK mode */
#define UNIT_MODEM_SHIFT (TTUF_V_UF + 2) /* modem control */
#define UNIT_CAPSLOCK (1u << UNIT_CAPSLOCK_SHIFT) /* caps lock is down flag */
#define UNIT_LOCALACK (1u << UNIT_LOCALACK_SHIFT) /* ENQ/ACK mode is local flag */
#define UNIT_MODEM (1u << UNIT_MODEM_SHIFT) /* channel connects to a data set flag */
/* Unit references */
#define line_unit atcd_unit /* receive/send channel units */
#define poll_unit atcd_unit [LAST_TERM + 1] /* input polling unit */
/* Activation reasons */
typedef enum {
Receive,
Send,
Loop,
Stall
} ACTIVATOR;
/* TDI control word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | channel number | - - - - - - - | E | A |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define DCN_MR 0100000u /* (M) master reset */
#define DCN_IRQ_RESET 0040000u /* (R) interrupt request reset */
#define DCN_CHAN_MASK 0037000u /* channel number mask */
#define DCN_ENABLE 0000002u /* (E) enable store of preceding data or parameter word */
#define DCN_ACKN 0000001u /* (A) acknowledge interrupt */
#define DCN_CHAN_SHIFT 9 /* channel number alignment shift */
#define DCN_CHAN(c) (((c) & DCN_CHAN_MASK) >> DCN_CHAN_SHIFT)
static const BITSET_NAME tdi_control_names [] = { /* TDI control word names */
"master reset", /* bit 0 */
"reset interrupt", /* bit 1 */
NULL, /* bit 2 */
NULL, /* bit 3 */
NULL, /* bit 4 */
NULL, /* bit 5 */
NULL, /* bit 6 */
NULL, /* bit 7 */
NULL, /* bit 8 */
NULL, /* bit 9 */
NULL, /* bit 10 */
NULL, /* bit 11 */
NULL, /* bit 12 */
NULL, /* bit 13 */
"store word", /* bit 14 */
"acknowledge interrupt" /* bit 15 */
};
static const BITSET_FORMAT tdi_control_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tdi_control_names, 0, msb_first, no_alt, no_bar) };
/* TDI status word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - | D | I | - | C | R | L | B | - - - - - - - - |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define DST_DIO_OK 0040000u /* (D) direct I/O OK to use */
#define DST_IRQ 0020000u /* (I) interrupt requested */
#define DST_COMPLETE 0004000u /* (C) operation is complete and channel is ready to interrupt */
#define DST_SEND_IRQ 0002000u /* (R) interrupt request is for character sent */
#define DST_CHAR_LOST 0001000u /* (L) character was lost */
#define DST_BREAK 0000400u /* (B) break occurred */
#define DST_DIAGNOSE 0000000u /* status is from an auxiliary channel (not used on ATC) */
#define DST_CHAN(n) 0 /* position channel number for status (not used on ATC) */
static const BITSET_NAME tdi_status_names [] = { /* TDI status word names */
"DIO OK", /* bit 1 */
"interrupt", /* bit 2 */
NULL, /* bit 3 */
"complete", /* bit 4 */
"\1send\0receive", /* bit 5 */
"lost", /* bit 6 */
"break" /* bit 7 */
};
static const BITSET_FORMAT tdi_status_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tdi_status_names, 8, msb_first, has_alt, no_bar) };
/* TDI parameter word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 | R | I | E | D | char size | baud rate |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The baud rate is encoded as 14400 / device_bit_rate - 1, but the manual says
to round the result, so that, e.g., the 110 baud rate encoding of 129.91 is
rounded to 130. To reconstruct the rate without floating-point calculations,
the parameter print routine uses:
baud_rate = (2 * 14400 / (encoded_rate + 1) + 1) / 2
...which is equivalent to:
baud_rate = (int) (14400 / (encoded_rate + 1) + 0.5)
The multiplexer pads the received character data to the left with one-bits.
The PAD_BITS function generates the pad bits, assuming that the received
character transmission has one stop bit. This isn't always correct, e.g., a
Teleprinter uses two stop bits at 110 baud, but there's no way to reconstruct
the number of stop bits from the receive parameter word.
*/
#define DPI_IS_PARAM 0100000u /* value is a parameter (always set) */
#define DPI_IS_SEND 0040000u /* (R) value is a send parameter */
#define DPI_ENABLE_IRQ 0020000u /* (I) enable interrupt requests */
#define DPI_ENABLE_PARITY 0010000u /* (E) enable parity for send */
#define DPI_ENABLE_ECHO 0010000u /* (E) enable echo for receive */
#define DPI_DIAGNOSE 0004000u /* (D) connect to the auxiliary channels */
#define DPI_SIZE_MASK 0003400u /* character size mask */
#define DPI_RATE_MASK 0000377u /* baud rate mask */
#define DPI_CHAR_CONFIG (DPI_SIZE_MASK | DPI_RATE_MASK) /* character configuration data */
#define DPI_SIZE_SHIFT 8 /* character size alignment shift */
#define DPI_RATE_SHIFT 0 /* baud rate alignment shift */
#define DPI_CHAR_SIZE(p) (((p) & DPI_SIZE_MASK) >> DPI_SIZE_SHIFT)
#define DPI_BAUD_RATE(p) (((p) & DPI_RATE_MASK) >> DPI_RATE_SHIFT)
#define BAUD_RATE(p) ((28800 / (DPI_BAUD_RATE (p) + 1) + 1) / 2)
#define PAD_BITS(c) (~((1u << bits_per_char [DPI_CHAR_SIZE (c)] - 2) - 1))
static const uint32 bits_per_char [8] = { /* bits per character, indexed by DPI_CHAR_SIZE encoding */
9, 10, 11, 12, 5, 6, 7, 8
};
static const BITSET_NAME tdi_parameter_names [] = { /* TDI parameter word names */
"\1send\0receive", /* bit 1 */
"enable interrupt", /* bit 2 */
"enable parity/echo", /* bit 3 */
"diagnose" /* bit 4 */
};
static const BITSET_FORMAT tdi_parameter_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tdi_parameter_names, 11, msb_first, has_alt, append_bar) };
/* TDI output (send) data word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | - - | S | send data |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define DDS_IS_SEND 0040000u /* value is a send data word (always set) */
#define DDS_SYNC 0004000u /* (S) sync */
#define DDS_DATA_MASK 0003777u /* data value mask */
#define DDS_PARITY 0000200u /* data parity bit */
#define DDS_MARK (DDS_SYNC | DDS_DATA_MASK) /* all-mark character */
#define DDS_DATA(d) ((d) & DDS_DATA_MASK)
static const BITSET_NAME tdi_output_data_names [] = { /* TDI output data word names */
"send", /* bit 1 */
NULL, /* bit 2 */
NULL, /* bit 3 */
"sync" /* bit 4 */
};
static const BITSET_FORMAT tdi_output_data_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tdi_output_data_names, 11, msb_first, no_alt, append_bar) };
/* TDI input (receive) data word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| channel | P | receive data |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define DDR_CHAN_MASK 0174000u /* channel number mask */
#define DDR_PARITY 0002000u /* (P) computed parity bit */
#define DDR_DATA_MASK 0001777u /* data value mask */
#define DDR_CHAN_SHIFT 11 /* channel number alignment shift */
#define DDR_DATA_SHIFT 0 /* data alignment shift */
#define DDR_CHAN(n) ((n) << DDR_CHAN_SHIFT & DDR_CHAN_MASK)
#define DDR_DATA(d) ((d) << DDR_DATA_SHIFT & DDR_DATA_MASK)
#define DDR_TO_CHAN(w) (((w) & DDR_CHAN_MASK) >> DDR_CHAN_SHIFT)
#define DDR_TO_DATA(w) (((w) & DDR_DATA_MASK) >> DDR_DATA_SHIFT)
static const BITSET_NAME tdi_input_data_names [] = { /* TDI input data word names */
"\1odd parity\0even parity", /* bit 5 */
};
static const BITSET_FORMAT tdi_input_data_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tdi_input_data_names, 10, msb_first, has_alt, append_bar) };
/* TCI control word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | S | U | channel | W | X | Q | T | Y | Z | C | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define CCN_MR 0100000u /* (M) master reset */
#define CCN_IRQ_RESET 0040000u /* (R) interrupt request reset */
#define CCN_SCAN 0020000u /* (S) scan enable */
#define CCN_UPDATE 0010000u /* (U) update enable */
#define CCN_CHAN_MASK 0007400u /* channel number mask */
#define CCN_ECX_MASK 0000300u /* control output enable mask */
#define CCN_EC2 0000200u /* (W) C2 output enable */
#define CCN_EC1 0000100u /* (X) C1 output enable */
#define CCN_CX_MASK 0000060u /* output mask */
#define CCN_C2 0000040u /* (Q) C2 output [RTS] */
#define CCN_C1 0000020u /* (T) C1 output [DTR] */
#define CCN_STAT_MASK 0000017u /* status RAM mask */
#define CCN_ESX_MASK 0000014u /* status interrupt enable mask */
#define CCN_ES2 0000010u /* (Y) S2 interrupt enable */
#define CCN_ES1 0000004u /* (Z) S1 interrupt enable */
#define CCN_SX_MASK 0000003u /* status mask */
#define CCN_S2 0000002u /* (C) S2 status [DCD]*/
#define CCN_S1 0000001u /* (D) S1 status [DSR] */
#define CCN_CHAN_SHIFT 8 /* channel number alignment shift */
#define CCN_CX_SHIFT 4 /* control alignment shift */
#define CCN_ECX_SHIFT 2 /* control output enable alignment shift (to Cx) */
#define CCN_ESX_SHIFT 2 /* status interrupt enable alignment shift */
#define CCN_CHAN(c) (((c) & CCN_CHAN_MASK) >> CCN_CHAN_SHIFT)
#define CCN_ECX(c) (((c) & CCN_ECX_MASK) >> CCN_ECX_SHIFT)
#define CCN_CX(c) (((c) & CCN_CX_MASK) >> CCN_CX_SHIFT)
#define CCN_ESX(c) (((c) & CCN_ESX_MASK) >> CCN_ESX_SHIFT)
static const BITSET_NAME tci_control_names [] = { /* TCI control word names */
"master reset", /* bit 0 */
"reset interrupt", /* bit 1 */
"scan", /* bit 2 */
"update", /* bit 3 */
NULL, /* bit 4 */
NULL, /* bit 5 */
NULL, /* bit 6 */
NULL, /* bit 7 */
"EC2", /* bit 8 */
"EC1", /* bit 9 */
"\1C2\0~C2", /* bit 10 */
"\1C1\0~C1", /* bit 11 */
"ES2", /* bit 12 */
"ES1", /* bit 13 */
"\1S2\0~S2", /* bit 14 */
"\1S1\0~S1" /* bit 15 */
};
static const BITSET_FORMAT tci_control_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tci_control_names, 0, msb_first, has_alt, no_bar) };
/* TCI status word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - | 1 | I | 1 | channel | - | - | J | K | Y | Z | C | D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define CST_DIO_OK 0040000u /* direct I/O OK to use (always set) */
#define CST_IRQ 0020000u /* (I) interrupt request */
#define CST_ON 0010000u /* (always set) */
#define CST_CHAN_MASK 0007400u /* channel number mask */
#define CST_IX_MASK 0000060u /* status interrupt mask */
#define CST_I2 0000040u /* (J) S2 interrupt */
#define CST_I1 0000020u /* (K) S1 interrupt */
#define CST_ESX_MASK 0000014u /* status interrupt enable mask */
#define CST_ES2 0000010u /* (Y) S2 interrupt enable */
#define CST_ES1 0000004u /* (Z) S1 interrupt enable */
#define CST_SX_MASK 0000003u /* status mask */
#define CST_S2 0000002u /* (C) S2 status [DCD] */
#define CST_S1 0000001u /* (D) S1 status [DSR] */
#define CST_CHAN_SHIFT 8 /* channel number alignment shift */
#define CST_IX_SHIFT 4 /* status interrupt alignment shift */
#define CST_CHAN(n) ((n) << CST_CHAN_SHIFT & CST_CHAN_MASK)
#define CST_IX(i) ((i) << CST_IX_SHIFT & CST_IX_MASK)
static const BITSET_NAME tci_status_names [] = { /* TCI status word names */
"interrupt", /* bit 2 */
NULL, /* bit 3 */
NULL, /* bit 4 */
NULL, /* bit 5 */
NULL, /* bit 6 */
NULL, /* bit 7 */
NULL, /* bit 8 */
NULL, /* bit 9 */
"I2", /* bit 10 */
"I1", /* bit 11 */
"ES2", /* bit 12 */
"ES1", /* bit 13 */
"\1S2\0~S2", /* bit 14 */
"\1S1\0~S1" /* bit 15 */
};
static const BITSET_FORMAT tci_status_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tci_status_names, 0, msb_first, has_alt, no_bar) };
/* TCI #1 serial line bits */
#define RTS CCN_C2 /* TCI #1 C2 = Request to Send */
#define DTR CCN_C1 /* TCI #1 C1 = Data Terminal Ready */
#define DCD CCN_S2 /* TCI #1 S2 = Data Carrier Detect */
#define DSR CCN_S1 /* TCI #1 S1 = Data Set Ready */
static const BITSET_NAME tci_line_names [] = { /* TCI serial line status names */
"RTS", /* bit 10 */
"DTR", /* bit 11 */
NULL, /* bit 12 */
NULL, /* bit 13 */
"DCD", /* bit 14 */
"DSR" /* bit 15 */
};
static const BITSET_FORMAT tci_line_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (tci_line_names, 0, msb_first, no_alt, no_bar) };
/* ATC global state */
t_bool atc_is_polling = TRUE; /* TRUE if the ATC is polling for the simulation console */
/* TDI interface state */
static HP_WORD tdi_control_word = 0; /* control word */
static HP_WORD tdi_status_word = 0; /* status word */
static HP_WORD tdi_read_word = 0; /* read word */
static HP_WORD tdi_write_word = 0; /* write word */
static FLIP_FLOP tdi_interrupt_mask = SET; /* interrupt mask flip-flop */
static FLIP_FLOP tdi_data_flag = CLEAR; /* data flag */
static int32 fast_data_time = FAST_IO_TIME; /* fast receive/send time */
/* TDI per-channel state */
static HP_WORD recv_status [RECV_CHAN_COUNT]; /* receive status words */
static HP_WORD recv_param [RECV_CHAN_COUNT]; /* receive parameter words */
static HP_WORD recv_buffer [RECV_CHAN_COUNT]; /* receive character buffers */
static HP_WORD send_status [SEND_CHAN_COUNT]; /* send status words */
static HP_WORD send_param [SEND_CHAN_COUNT]; /* send parameter words */
static HP_WORD send_buffer [SEND_CHAN_COUNT]; /* send character buffers */
/* TCI interface state */
static HP_WORD tci_control_word = 0; /* control word */
static HP_WORD tci_status_word = 0; /* status word */
static uint32 tci_cntr = 0; /* channel counter */
static FLIP_FLOP tci_interrupt_mask = SET; /* interrupt mask flip-flop */
static FLIP_FLOP tci_scan = CLEAR; /* scanning enabled flip-flop */
/* TCI per-channel state */
static uint8 cntl_status [TERM_COUNT]; /* C2/C1/S2/S1 line status */
static uint8 cntl_param [TERM_COUNT]; /* ES2/ES1/S2/S1 parameter RAM */
/* ATC local SCP support routines */
static CNTLR_INTRF atcd_interface;
static CNTLR_INTRF atcc_interface;
static t_stat atc_set_endis (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat atc_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc);
static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, CONST void *desc);
static t_stat atcd_reset (DEVICE *dptr);
static t_stat atcc_reset (DEVICE *dptr);
static t_stat atcd_attach (UNIT *uptr, CONST char *cptr);
static t_stat atcd_detach (UNIT *uptr);
/* ATC local utility routines */
static void tdi_set_interrupt (void);
static void tdi_master_reset (void);
static void tci_master_reset (void);
static t_stat line_service (UNIT *uptr);
static t_stat poll_service (UNIT *uptr);
static int32 activate_unit (UNIT *uptr, ACTIVATOR reason);
static uint32 service_time (HP_WORD control, ACTIVATOR reason);
static void store (HP_WORD control, HP_WORD data);
static void receive (int32 channel, int32 data, t_bool loopback);
static void diagnose (HP_WORD control, int32 data);
static void scan_channels (int32 channel);
static HP_WORD scan_status (void);
/* ATC SCP data structures */
DEVICE atcd_dev; /* incomplete device structure */
DEVICE atcc_dev; /* incomplete device structure */
/* Terminal multiplexer library structures.
The ATC uses the connection line order feature to bypass channel 0, which is
dedicated to the system console. For convenience, the system console is
connected to the simulation console. As such, it calls the console I/O
routines instead of the terminal multiplexer routines.
User-defined line order is not supported.
*/
static int32 atcd_order [TERM_COUNT] = { /* line connection order */
1, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15 };
static TMLN atcd_ldsc [TERM_COUNT] = { /* line descriptors */
{ 0 }
};
static TMXR atcd_mdsc = { /* multiplexer descriptor */
TERM_COUNT, /* number of terminal lines */
0, /* listening port (reserved) */
0, /* master socket (reserved) */
atcd_ldsc, /* line descriptors */
atcd_order, /* line connection order */
NULL /* multiplexer device (derived internally) */
};
/* Device information blocks */
static DIB atcd_dib = {
&atcd_interface, /* device interface */
7, /* device number */
SRNO_UNUSED, /* service request number */
0, /* interrupt priority */
INTMASK_E /* interrupt mask */
};
static DIB atcc_dib = {
&atcc_interface, /* device interface */
8, /* device number */
SRNO_UNUSED, /* service request number */
8, /* interrupt priority */
INTMASK_E /* interrupt mask */
};
/* Unit lists.
The first sixteen TDI units correspond to the sixteen multiplexer main
send/receive channels. These handle character I/O via the Telnet library. A
seventeenth unit is responsible for polling for connections and socket I/O.
It also holds the master socket.
Channel 0 is reserved for the system console and is connected to the
simulation console. As such, it's not likely to be using an HP terminal
emulator, so the default is CAPSLOCK input mode and 7P output mode. The
remainder of the channels default to NOCAPSLOCK and 7B, as they're likely to
be connected to HP terminals or terminal emulators. All channels initially
omit the UNIT_MODEM flag to allow the MPE terminal subtype configuration to
determine which channels support data sets and which do not.
The TDI line service routine runs only when there are characters to read or
write. It is scheduled either at a realistic rate corresponding to the
programmed baud rate of the channel to be serviced, or at a somewhat faster
optimized rate. The multiplexer connection and input 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. It is coscheduled
with the process clock to permit idling. The poll unit is hidden by
disabling it, so as to present a logical picture of the multiplexer to the
user.
The TCI does not use any units, but a dummy one is defined to satisfy SCP
requirements.
Implementation notes:
1. There are no units corresponding to the auxiliary receive channels. This
is because reception isn't scheduled on these channels but instead occurs
concurrently with the main channel that is connected to the auxiliary
channels.
*/
static UNIT atcd_unit [UNIT_COUNT] = {
{ UDATA (&line_service, TT_MODE_7P | UNIT_LOCALACK | UNIT_CAPSLOCK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) },
{ UDATA (&poll_service, UNIT_ATTABLE | UNIT_DIS | UNIT_IDLE, 0), POLL_TIME } /* multiplexer poll unit */
};
static UNIT atcc_unit [] = { /* a dummy unit to satisfy SCP requirements */
{ UDATA (NULL, 0, 0) }
};
/* Register lists.
The internal state of the TDI and TCI are exposed to the user and to ensure
that SAVE and RESTORE pick up the values. The user may set FTIME explicitly
as needed to accommodate software that does not work with the default
setting.
Implementation notes:
1. The TCI control and status line register definitions use the VM-defined
FBDATA macro. This macro defines a flag that is replicated in the same
bit position in each element of an array.
*/
static REG atcd_reg [] = {
/* Macro Name Location Radix Width Offset Depth Flags */
/* ------ ------ ------------------- ----- ----- ------ ---------------- --------------- */
{ ORDATA (CNTL, tdi_control_word, 16), REG_FIT },
{ ORDATA (STAT, tdi_status_word, 16), REG_FIT },
{ ORDATA (READ, tdi_read_word, 16), REG_X | REG_FIT },
{ ORDATA (WRITE, tdi_write_word, 16), REG_X | REG_FIT },
{ FLDATA (FLAG, tdi_data_flag, 0) },
{ FLDATA (MASK, tdi_interrupt_mask, 0) },
{ DRDATA (FTIME, fast_data_time, 24), PV_LEFT },
{ BRDATA (RSTAT, recv_status, 8, 16, RECV_CHAN_COUNT) },
{ BRDATA (RPARM, recv_param, 8, 16, RECV_CHAN_COUNT) },
{ BRDATA (RBUFR, recv_buffer, 8, 16, RECV_CHAN_COUNT), REG_X },
{ BRDATA (SSTAT, send_status, 8, 16, SEND_CHAN_COUNT) },
{ BRDATA (SPARM, send_param, 8, 16, SEND_CHAN_COUNT) },
{ BRDATA (SBUFR, send_buffer, 8, 16, SEND_CHAN_COUNT), REG_X },
{ FLDATA (POLL, atc_is_polling, 0), REG_HRO },
DIB_REGS (atcd_dib),
{ NULL }
};
static REG atcc_reg [] = {
/* Macro Name Location Width Offset Depth Flags */
/* ------ ------ ------------------- ----- ------ ---------- ------- */
{ ORDATA (CNTL, tci_control_word, 16), REG_FIT },
{ ORDATA (STAT, tci_status_word, 16), REG_FIT },
{ DRDATA (CNTR, tci_cntr, 4) },
{ FLDATA (SCAN, tci_scan, 0) },
{ FLDATA (MASK, tci_interrupt_mask, 0) },
{ FBDATA (C2, cntl_status, 5, TERM_COUNT, PV_RZRO) },
{ FBDATA (C1, cntl_status, 4, TERM_COUNT, PV_RZRO) },
{ FBDATA (S2, cntl_status, 1, TERM_COUNT, PV_RZRO) },
{ FBDATA (S1, cntl_status, 0, TERM_COUNT, PV_RZRO) },
{ FBDATA (ES2, cntl_param, 3, TERM_COUNT, PV_RZRO) },
{ FBDATA (ES1, cntl_param, 2, TERM_COUNT, PV_RZRO) },
{ FBDATA (MS2, cntl_param, 1, TERM_COUNT, PV_RZRO) },
{ FBDATA (MS1, cntl_param, 0, TERM_COUNT, PV_RZRO) },
DIB_REGS (atcc_dib),
{ NULL }
};
/* Modifier lists */
typedef enum {
Fast_Time,
Real_Time,
Terminal,
Diagnostic
} DEVICE_MODES;
static MTAB atcd_mod [] = {
/* Mask Value Match Value Print String Match String Validation Display Descriptor */
/* ------------- ------------- ---------------- ------------ ---------- ------- ---------- */
{ UNIT_MODEM, UNIT_MODEM, "data set", NULL, NULL, NULL, NULL },
{ UNIT_MODEM, 0, "direct", NULL, NULL, NULL, NULL },
{ UNIT_LOCALACK, UNIT_LOCALACK, "local ENQ/ACK", "LOCALACK", NULL, NULL, NULL },
{ UNIT_LOCALACK, 0, "remote ENQ/ACK", "REMOTEACK", NULL, NULL, NULL },
{ UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL },
{ UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL },
{ TT_MODE, TT_MODE_UC, "UC output", "UC", NULL, NULL, NULL },
{ TT_MODE, TT_MODE_7B, "7b output", "7B", NULL, NULL, NULL },
{ TT_MODE, TT_MODE_7P, "7p output", "7P", NULL, NULL, NULL },
{ TT_MODE, TT_MODE_8B, "8b output", "8B", NULL, NULL, NULL },
/* Entry Flags Value Print String Match String Validation Display Descriptor */
/* -------------------- ----------- ------------- ------------ --------------- ---------------- ------------------- */
{ MTAB_XUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, (void *) &atcd_mdsc },
{ MTAB_XUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, (void *) &atcd_mdsc },
{ MTAB_XUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, (void *) &atcd_mdsc },
{ MTAB_XDV, Fast_Time, NULL, "FASTTIME", &atc_set_mode, NULL, (void *) &atcd_dev },
{ MTAB_XDV, Real_Time, NULL, "REALTIME", &atc_set_mode, NULL, (void *) &atcd_dev },
{ MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcd_dev },
{ MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcd_dev },
{ MTAB_XDV, 0, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcd_dev },
{ MTAB_XDV, 0, "", NULL, NULL, &atc_show_status, (void *) &atcd_mdsc },
{ MTAB_XDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc },
{ MTAB_XDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc },
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib },
{ MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib },
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib },
{ MTAB_XDV | MTAB_NMO, 1, NULL, "ENABLED", &atc_set_endis, NULL, NULL },
{ MTAB_XDV | MTAB_NMO, 0, NULL, "DISABLED", &atc_set_endis, NULL, NULL },
{ 0 }
};
static MTAB atcc_mod [] = {
/* Entry Flags Value Print String Match String Validation Display Descriptor */
/* ----------- ----------- ------------ ------------ ------------- -------------- ------------------ */
{ MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcc_dev },
{ MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcc_dev },
{ MTAB_XDV, 1, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcc_dev },
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib },
{ MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib },
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib },
{ 0 }
};
/* Debugging trace lists */
static DEBTAB atcd_deb [] = {
{ "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */
{ "SERV", DEB_SERV }, /* Channel unit service scheduling calls */
{ "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */
{ "XFER", DEB_XFER }, /* Data receptions and transmissions */
{ "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */
{ NULL, 0 }
};
static DEBTAB atcc_deb [] = {
{ "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */
{ "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */
{ "XFER", DEB_XFER }, /* Control and status line changes */
{ "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */
{ NULL, 0 }
};
/* Device descriptors.
Both devices may be disabled. However, we want to be able to disable the TDI
while it is polling for the simulation console, which the standard SCP
routine will not do (it refuses if any unit is active). So we define our own
DISABLED and ENABLED modifiers and a validation routine that sets or clears
the DEV_DIS flag and then calls atcd_reset. The reset routine cancels or
reenables the poll as indicated.
Implementation notes:
1. The ATCD device does not specify the DEV_DISABLE flag to avoid the
DISABLED and ENABLED modifiers from being listed twice for a SHOW ATCD
MODIFIERS command. SIMH 3.9 tested for user-defined ENABLED/DISABLED
modifiers and skipped the printing that results from specifying
DEV_DISABLE. SIMH 4.0 no longer does this, so we omit the flag to
suppress the duplicate printing (the flag is otherwise used only to
validate the SET DISABLED command).
*/
DEVICE atcd_dev = {
"ATCD", /* device name */
atcd_unit, /* unit array */
atcd_reg, /* register array */
atcd_mod, /* modifier array */
UNIT_COUNT, /* number of units */
10, /* address radix */
PA_WIDTH, /* address width */
1, /* address increment */
8, /* data radix */
DV_WIDTH, /* data width */
&tmxr_ex, /* examine routine */
&tmxr_dep, /* deposit routine */
&atcd_reset, /* reset routine */
NULL, /* boot routine */
&atcd_attach, /* attach routine */
&atcd_detach, /* detach routine */
&atcd_dib, /* device information block pointer */
DEV_DEBUG, /* device flags */
0, /* debug control flags */
atcd_deb, /* debug flag name array */
NULL, /* memory size change routine */
NULL }; /* logical device name */
DEVICE atcc_dev = {
"ATCC", /* device name */
atcc_unit, /* unit array */
atcc_reg, /* register array */
atcc_mod, /* modifier array */
1, /* number of units */
10, /* address radix */
PA_WIDTH, /* address width */
1, /* address increment */
8, /* data radix */
DV_WIDTH, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&atcc_reset, /* reset routine */
NULL, /* boot routine */
NULL, /* attach routine */
NULL, /* detach routine */
&atcc_dib, /* device information block pointer */
DEV_DEBUG | DEV_DISABLE, /* device flags */
0, /* debug control flags */
atcc_deb, /* debug flag name array */
NULL, /* memory size change routine */
NULL }; /* logical device name */
/* ATC local SCP support routines */
/* TDI interface.
The interface is installed on the IOP bus and receives direct I/O commands
from the IOP. In simulation, the asserted signals on the bus are represented
as bits in the inbound_signals set. Each signal is processed sequentially in
numerical order, and a set of similar outbound_signals is assembled and
returned to the caller, simulating assertion of the corresponding backplane
signals.
Before a channel can receive or send, it must be configured. The number of
the channel to configure is set via a CIO instruction, followed by parameters
for baud rate and character size via WIO instructions. Data to be sent is
passed to the interface via WIO, while received data is picked up with RIO
instructions.
When a channel has completed sending or receiving a character, it will set
its completion flag. If the TDI data flag is clear, indicating that all
prior interrupts have been serviced, a scan of the serviced channel is made
to see if the channel is enabled to interrupt. If it is, the TDI data flag
will be set, the channel flag will be cleared, and an interrupt will be
requested. When the interrupt is serviced and acknowledged, the flag will be
cleared, and the scan will continue to look for other channel flags.
The status word is set during the scan to reflect the interrupting channel
status. If status bit 3 (DST_COMPLETE) is clear, then status bits 5, 6, and
7 (DST_SEND_IRQ, DST_CHAR_LOST, and DST_BREAK) retain their values from the
prior send or receive interrupt.
Implementation notes:
1. In hardware, the DIO OK status bit (bit 1) is denied when a store to the
recirculating memory is pending and is reasserted once the designated
channel rotates into the window and the parameter or data is stored. The
duration of the denial varies from 0 to 69.44 microseconds, depending on
the location of the window in memory when DWRITESTB is asserted. In
simulation, DIO OK is always asserted.
2. Receipt of a DRESETINT signal clears the interrupt request and active
flip-flops but does not cancel a request pending but not yet serviced by
the IOP. However, when the IOP does service the request by asserting
INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel
the request.
*/
static SIGNALS_DATA atcd_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET working_set = inbound_signals;
HP_WORD outbound_value = 0;
OUTBOUND_SET outbound_signals = NO_SIGNALS;
dprintf (atcd_dev, DEB_IOB, "Received data %06o with signals %s\n",
inbound_value, fmt_bitset (inbound_signals, inbound_format));
while (working_set) { /* while signals remain */
signal = IONEXTSIG (working_set); /* isolate the next signal */
switch (signal) { /* dispatch the I/O signal */
case DCONTSTB:
dprintf (atcd_dev, DEB_CSRW, (inbound_value & DCN_ENABLE
? "Control is %s | channel %u\n"
: "Control is %s\n"),
fmt_bitset (inbound_value, tdi_control_format),
DCN_CHAN (inbound_value));
tdi_control_word = inbound_value; /* save the control word */
if (tdi_control_word & DCN_MR) /* if master reset is requested */
tdi_master_reset (); /* then perform an I/O reset */
if (tdi_control_word & DCN_IRQ_RESET) /* if reset interrupt is requested */
dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */
if (tdi_control_word & DCN_ENABLE) /* if output is enabled */
store (tdi_control_word, tdi_write_word); /* then store the parameter or data word */
if (tdi_control_word & DCN_ACKN) { /* if acknowledge interrupt is requested */
tdi_data_flag = CLEAR; /* then clear the data flag */
scan_channels (SCAN_ALL); /* scan all channels for a new interrupt request */
}
break;
case DSTATSTB:
tdi_status_word |= DST_DIO_OK; /* the interface is always ready for commands */
if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */
tdi_status_word |= DST_IRQ; /* in the status word */
else /* to indicate */
tdi_status_word &= ~DST_IRQ; /* whether or not a request is pending */
if (tdi_data_flag == SET) /* reflect the data flag value */
tdi_status_word |= DST_COMPLETE; /* in the status word */
else /* to indicate */
tdi_status_word &= ~DST_COMPLETE; /* whether or not a channel has completed */
outbound_value = tdi_status_word; /* return the status word */
dprintf (atcd_dev, DEB_CSRW, "Status is %s\n",
fmt_bitset (outbound_value, tdi_status_format));
break;
case DWRITESTB:
tdi_write_word = inbound_value; /* save the data or parameter word */
if (DPRINTING (atcd_dev, DEB_CSRW))
if (inbound_value & DPI_IS_PARAM)
hp_debug (&atcd_dev, DEB_CSRW, "Parameter is %s%u bits | %u baud\n",
fmt_bitset (inbound_value, tdi_parameter_format),
bits_per_char [DPI_CHAR_SIZE (inbound_value)],
BAUD_RATE (inbound_value));
else
hp_debug (&atcd_dev, DEB_CSRW, "Output data is %s%04o\n",
fmt_bitset (inbound_value, tdi_output_data_format),
DDS_DATA (inbound_value));
break;
case DREADSTB:
outbound_value = tdi_read_word; /* return the data word */
dprintf (atcd_dev, DEB_CSRW, "Input data is channel %u | %s%04o\n",
DDR_TO_CHAN (outbound_value),
fmt_bitset (outbound_value, tdi_input_data_format),
DDR_TO_DATA (outbound_value));
break;
case DSETINT:
dibptr->interrupt_request = SET; /* request an interrupt */
if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
break;
case DRESETINT:
dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */
break;
case INTPOLLIN:
if (dibptr->interrupt_request) { /* if a request is pending */
dibptr->interrupt_request = CLEAR; /* then clear it */
dibptr->interrupt_active = SET; /* and mark it now active */
outbound_signals |= INTACK; /* acknowledge the interrupt */
outbound_value = dibptr->device_number; /* and return our device number */
}
else /* otherwise the request has been reset */
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */
break;
case DSETMASK:
if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */
tdi_interrupt_mask = SET; /* then set the mask flip-flop */
else /* otherwise */
tdi_interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */
& inbound_value); /* is present in the mask value */
if (tdi_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */
outbound_signals |= INTREQ; /* then assert INTREQ */
break;
case DSTARTIO: /* not used by this interface */
case XFERERROR: /* not used by this interface */
case ACKSR: /* not used by this interface */
case TOGGLESR: /* not used by this interface */
case TOGGLESIOOK: /* not used by this interface */
case TOGGLEINXFER: /* not used by this interface */
case TOGGLEOUTXFER: /* not used by this interface */
case READNEXTWD: /* not used by this interface */
case PREADSTB: /* not used by this interface */
case PWRITESTB: /* not used by this interface */
case PCMD1: /* not used by this interface */
case PCONTSTB: /* not used by this interface */
case PSTATSTB: /* not used by this interface */
case DEVNODB: /* not used by this interface */
case SETINT: /* not used by this interface */
case EOT: /* not used by this interface */
case SETJMP: /* not used by this interface */
case CHANSO: /* not used by this interface */
case PFWARN: /* not used by this interface */
break;
}
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
}
dprintf (atcd_dev, DEB_IOB, "Returned data %06o with signals %s\n",
outbound_value, fmt_bitset (outbound_signals, outbound_format));
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */
}
/* TCI interface.
The interface is installed on the IOP bus and receives direct I/O commands
from the IOP. In simulation, the asserted signals on the bus are represented
as bits in the inbound_signals set. Each signal is processed sequentially in
numerical order, and a set of similar outbound_signals is assembled and
returned to the caller, simulating assertion of the corresponding backplane
signals. For this interface, a read order executes identically to a test
order, and a write order is ignored.
The control word contains three independent enables that affect the
interpretation of the rest of the word. Bit 3 (CCN_UPDATE) must be set to
enable storing bits 12-15 (CCN_ES2/1 and CCN_S2/1) into the state RAM. Bits
8 (CCN_EC2) and 9 (CCN_EC1) must be set to enable storing bits 10 (CCN_C2)
and 11 (CCN_C1), respectively, into the addressable latch. If none of these
enables are set, then only bits 0-2 are interpreted.
Implementation notes:
1. The cntl_status array contains the values for the serial device control
and status lines. The line bit positions in the array correspond to the
C2/C1 and S2/S1 positions in the control word.
2. A control word write directed to a given channel sets that channel's
UNIT_MODEM flag to indicate that the serial line status should be
updated at each input poll service.
3. The terminal multiplexer library will disconnect an associated Telnet
session if DTR is dropped.
4. Receipt of a DRESETINT signal clears the interrupt request and active
flip-flops but does not cancel a request pending but not yet serviced by
the IOP. However, when the IOP does service the request by asserting
INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel
the request.
*/
static SIGNALS_DATA atcc_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET working_set = inbound_signals;
HP_WORD outbound_value = 0;
OUTBOUND_SET outbound_signals = NO_SIGNALS;
int32 set_lines, clear_lines;
dprintf (atcc_dev, DEB_IOB, "Received data %06o with signals %s\n",
inbound_value, fmt_bitset (inbound_signals, inbound_format));
while (working_set) { /* while signals remain */
signal = IONEXTSIG (working_set); /* isolate the next signal */
switch (signal) { /* dispatch the I/O signal */
case DCONTSTB:
tci_cntr = CCN_CHAN (inbound_value); /* set the counter to the target channel */
dprintf (atcc_dev, DEB_CSRW, "Control is channel %u | %s\n",
tci_cntr, fmt_bitset (inbound_value, tci_control_format));
tci_control_word = inbound_value; /* save the control word */
line_unit [tci_cntr].flags |= UNIT_MODEM; /* set the modem control flag on this unit */
if (tci_control_word & CCN_MR) /* if master reset is requested */
tci_master_reset (); /* then perform an I/O reset */
if (tci_control_word & CCN_IRQ_RESET) /* if reset interrupt is requested */
dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */
cntl_status [tci_cntr] = cntl_status [tci_cntr] /* set the control lines */
& ~CCN_ECX (tci_control_word) /* that are enabled for output */
| CCN_CX_MASK /* to the control bits */
& CCN_ECX (tci_control_word) /* that are enabled */
& tci_control_word; /* in the control word */
dprintf (atcc_dev, DEB_XFER, "Channel %u line status is %s\n",
tci_cntr, fmt_bitset (cntl_status [tci_cntr], tci_line_format));
if (atcc_dev.flags & DEV_DIAG) { /* if the interface is in diagnostic mode */
cntl_status [tci_cntr ^ 1] = /* then loop the control lines */
cntl_status [tci_cntr ^ 1] & ~CCN_SX_MASK /* back to the alternate channel */
| CCN_CX (cntl_status [tci_cntr]); /* from the selected channel */
dprintf (atcc_dev, DEB_XFER, "Channel %u line status is %s\n",
tci_cntr ^ 1, fmt_bitset (cntl_status [tci_cntr ^ 1], tci_line_format));
}
else if (tci_control_word & CCN_ECX_MASK) { /* otherwise if either control line is enabled */
set_lines = 0; /* then prepare the multiplexer library to set */
clear_lines = 0; /* the modem status (either real or simulated) */
if (tci_control_word & CCN_EC2) /* if control line 2 is enabled for output */
if (RTS & cntl_status [tci_cntr]) /* then if the line is asserted */
set_lines |= TMXR_MDM_RTS; /* then set the RTS line up */
else /* otherwise */
clear_lines |= TMXR_MDM_RTS; /* set it down */
if (tci_control_word & CCN_EC1) /* if control line 1 is enabled for output */
if (DTR & cntl_status [tci_cntr]) /* then if the line is asserted */
set_lines |= TMXR_MDM_DTR; /* then set the DTR line up */
else { /* otherwise */
clear_lines |= TMXR_MDM_DTR; /* set it down */
if (cntl_status [tci_cntr] & DCD) /* setting DTR down will disconnect the channel */
dprintf (atcc_dev, DEB_CSRW, "Channel %u disconnected by DTR drop\n",
tci_cntr);
}
tmxr_set_get_modem_bits (&atcd_ldsc [tci_cntr], /* tell the multiplexer library */
set_lines, clear_lines, /* to set or clear the indicated lines */
NULL); /* and omit returning the current status */
}
if (tci_control_word & CCN_UPDATE) /* if the status output is enabled */
cntl_param [tci_cntr] = tci_control_word /* then store the status line enables and states */
& CCN_STAT_MASK; /* in the parameter RAM */
tci_scan = D_FF (tci_control_word & CCN_SCAN); /* set or clear the scan flip-flop as directed */
if (tci_scan) /* if scanning is enabled */
scan_status (); /* then look for channel status changes */
break;
case DREADSTB: /* RIO and TIO return the same value */
case DSTATSTB:
tci_status_word = CST_DIO_OK | CST_ON /* form the status word */
| CST_CHAN (tci_cntr)
| cntl_param [tci_cntr] & CST_ESX_MASK
| cntl_status [tci_cntr] & CST_SX_MASK
| scan_status ();
if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */
tci_status_word |= CST_IRQ; /* in the status word */
outbound_value = tci_status_word; /* return the status word */
dprintf (atcc_dev, DEB_CSRW, "Status is channel %u | %s\n",
tci_cntr, fmt_bitset (outbound_value, tci_status_format));
break;
case DSETINT:
dibptr->interrupt_request = SET; /* request an interrupt */
if (tci_interrupt_mask) /* if the interrupt mask is satisfied */
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
break;
case DRESETINT:
dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */
break;
case INTPOLLIN:
if (dibptr->interrupt_request) { /* if a request is pending */
dibptr->interrupt_request = CLEAR; /* then clear it */
dibptr->interrupt_active = SET; /* and mark it now active */
outbound_signals |= INTACK; /* acknowledge the interrupt */
outbound_value = dibptr->device_number; /* and return our device number */
}
else /* otherwise the request has been reset */
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */
break;
case DSETMASK:
if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */
tci_interrupt_mask = SET; /* then set the mask flip-flop */
else /* otherwise */
tci_interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */
& inbound_value); /* is present in the mask value */
if (tci_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */
outbound_signals |= INTREQ; /* then assert INTREQ */
break;
case DWRITESTB: /* not used by this interface */
case DSTARTIO: /* not used by this interface */
case XFERERROR: /* not used by this interface */
case ACKSR: /* not used by this interface */
case TOGGLESR: /* not used by this interface */
case TOGGLESIOOK: /* not used by this interface */
case TOGGLEINXFER: /* not used by this interface */
case TOGGLEOUTXFER: /* not used by this interface */
case READNEXTWD: /* not used by this interface */
case PREADSTB: /* not used by this interface */
case PWRITESTB: /* not used by this interface */
case PCMD1: /* not used by this interface */
case PCONTSTB: /* not used by this interface */
case PSTATSTB: /* not used by this interface */
case DEVNODB: /* not used by this interface */
case SETINT: /* not used by this interface */
case EOT: /* not used by this interface */
case SETJMP: /* not used by this interface */
case CHANSO: /* not used by this interface */
case PFWARN: /* not used by this interface */
break;
}
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
}
dprintf (atcc_dev, DEB_IOB, "Returned data %06o with signals %s\n",
outbound_value, fmt_bitset (outbound_signals, outbound_format));
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */
}
/* Enable or disable the TDI.
This validation routine is entered with "value" set to 1 for an ENABLE and 0
for a DISABLE, and "cptr" pointing to the next character after the keyword.
If the TDI is already enabled or disabled, respectively, the routine returns
with no further action. Otherwise, if "value" is 1, the device is enabled by
clearing the DEV_DIS flag, and the polling flag is set TRUE to indicate that
the TDI is polling for the simulation console. If "value" is 0, a check is
made to see if the TDI is listening for connections. If it is, the disable
request is rejected; the device must be detached first. Otherwise, the
device is disabled by setting the DEV_DIS flag, and the polling flag is set
FALSE to indicate that the TDI is no longer polling for the simulation
console (the PCLK device will take over when the polling flag is FALSE).
In either case, the device is reset, which will restart or cancel the poll,
as appropriate.
*/
static t_stat atc_set_endis (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if (value) /* if this is an ENABLE request */
if (atcd_dev.flags & DEV_DIS) { /* then if the device is disabled */
atcd_dev.flags &= ~DEV_DIS; /* then reenable it */
atc_is_polling = TRUE; /* and set the polling flag */
}
else /* otherwise the device is already enabled */
return SCPE_OK; /* so there's nothing to do */
else /* otherwise this is a DISABLE request */
if (atcd_dev.flags & DEV_DIS) /* so if the device is already disabled */
return SCPE_OK; /* so there's nothing to do */
else if (poll_unit.flags & UNIT_ATT) /* otherwise if the poll unit is still attached */
return SCPE_NOFNC; /* then report that the command failed */
else { /* otherwise */
atcd_dev.flags |= DEV_DIS; /* disable the device */
atc_is_polling = FALSE; /* and clear the polling flag */
}
return atcd_reset (&atcd_dev); /* reset the TDI and restart or cancel polling */
}
/* Set the device modes.
The device flag implied by the DEVICE_MODES "value" passed to the routine is
set or cleared in the device specified by the "desc" parameter. The unit and
character pointers are not used.
Implementation notes:
1. In hardware, terminals and modems must be disconnected from the ATC and
loopback cables installed between each pair or channels when the
diagnostic is run. In simulation, setting DIAG mode detaches any
existing listening port, so that Telnet sessions will not interfere with
the internal loopback connections from the send to the receive channels.
*/
static t_stat atc_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
DEVICE * const dptr = (DEVICE *) desc; /* a pointer to the device */
switch ((DEVICE_MODES) value) { /* dispatch based on the mode to set */
case Fast_Time: /* entering optimized timing mode */
dptr->flags &= ~DEV_REALTIME; /* so clear the real-time flag */
break;
case Real_Time: /* entering realistic timing mode */
dptr->flags |= DEV_REALTIME; /* so set the flag */
break;
case Terminal: /* entering terminal mode */
dptr->flags &= ~DEV_DIAG; /* so clear the diagnostic flag */
break;
case Diagnostic: /* entering the diagnostic mode */
dptr->flags |= DEV_DIAG; /* so set the flag */
if (dptr == &atcd_dev) /* if we're setting the TDI mode */
atcd_detach (&poll_unit); /* then detach any existing connections */
break;
}
return SCPE_OK;
}
/* Show the device modes.
The output stream and device pointer are passed in the "st" and "desc"
parameters, respectively. If "value" is 0, then all of the flags are checked
for the TDI. If "value" is 1, then only the diagnostic flag is checked for
the TCI. The unit pointer is not used.
*/
static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
const DEVICE * const dptr = (const DEVICE *) desc; /* a pointer to the device */
if (value == 0) /* if this is the TDI */
if (dptr->flags & DEV_REALTIME) /* then if the real-time flag is set */
fputs ("realistic timing, ", st); /* then report that we are using realistic timing */
else /* otherwise */
fputs ("fast timing, ", st); /* report that we are using optimized timing */
if (dptr->flags & DEV_DIAG) /* if the diagnostic flag is set */
fputs ("diagnostic mode", st); /* then report that we're in loopback mode */
else /* otherwise */
fputs ("terminal mode", st); /* we're in normal (terminal) mode */
return SCPE_OK;
}
/* Show the TDI device status.
The attachment condition and connection count are printed to the stream
specified by "st" as part of the ATCD device display. The "desc" parameter
is a pointer to the terminal multiplexer library descriptor; the unit pointer
and value parameters are not used.
*/
static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
if (poll_unit.flags & UNIT_ATT) /* if the poll unit is attached */
fprintf (st, "attached to port %s, ", /* then report it */
poll_unit.filename); /* with the listening port number */
else /* otherwise */
fprintf (st, "not attached, "); /* report the condition */
tmxr_show_summ (st, uptr, value, desc); /* also report the count of connections */
return SCPE_OK;
}
/* TDI device reset.
This routine is called for a RESET or RESET ATCD command. It is the
simulation equivalent of the IORESET signal, which is asserted by the front
panel LOAD and DUMP switches.
If a power-on reset (RESET -P) is being done, the poll timer is initialized.
In addition, the original FASTTIME setting is restored, in case it's been
changed by the user.
If the polling flag is set, then start or resynchronize the poll unit with
the process clock to enable idling. If the CPU process clock is calibrated,
then the poll event service is synchronized with the process clock service.
Otherwise, the service time is set up but is otherwise asynchronous with the
process clock.
If the polling flag is clear, then the poll is stopped, as it's not needed.
Implementation notes:
1. To synchronize events, the poll must be activated absolutely, as a
service event may already be scheduled, and normal activation will not
disturb an existing event.
*/
static t_stat atcd_reset (DEVICE *dptr)
{
tdi_master_reset (); /* perform a master reset */
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
sim_rtcn_init (poll_unit.wait, TMR_ATC); /* then initialize the poll timer */
fast_data_time = FAST_IO_TIME; /* restore the initial fast data time */
atcd_ldsc [0].xmte = 1; /* enable transmission on the system console port */
}
if (atc_is_polling) { /* if we're polling for the simulation console */
if (cpu_is_calibrated) /* then if the process clock is calibrated */
poll_unit.wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */
else /* otherwise */
poll_unit.wait = POLL_TIME; /* set up an independent poll time */
sim_activate_abs (&poll_unit, poll_unit.wait); /* restart the poll timer */
}
else /* otherwise */
sim_cancel (&poll_unit); /* cancel the poll */
return SCPE_OK;
}
/* TCI device reset.
This routine is called for a RESET or RESET ATCC command. It is the
simulation equivalent of the IORESET signal, which is asserted by the front
panel LOAD and DUMP switches.
If a power-on reset (RESET -P) is being done, then local modem control is
established by setting DTR on all channels. This is necessary so that
channels not controlled by the TCI will be able to connect (TCI-controlled
channels will have their DTR and RTS state set by the MPE TCI initialization
routine).
*/
static t_stat atcc_reset (DEVICE *dptr)
{
uint32 channel;
tci_master_reset (); /* perform a master reset */
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
for (channel = 0; channel < TERM_COUNT; channel++) /* then for each terminal channel */
tmxr_set_get_modem_bits (&atcd_ldsc [channel], /* set the DTR line on */
TMXR_MDM_DTR, /* to allow non-TCI channels to connect */
0, NULL);
}
return SCPE_OK;
}
/* Attach the TDI to a Telnet listening port.
This routine is called by the ATTACH ATCD <port> command to attach the TDI to
the listening port indicated by <port>. Logically, it is the ATCD 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 TDI card, not of any given serial line. As ATTACH ATCD is equivalent
to ATTACH ATCD0, the port would, by default, be attached to the first channel
and be reported there in a SHOW ATCD 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 "atc_show_status" routine.
A direct attach to the poll unit is allowed only when restoring a previously
saved session via the RESTORE command.
*/
static t_stat atcd_attach (UNIT *uptr, CONST char *cptr)
{
t_stat status;
if (atcd_dev.flags & DEV_DIAG) /* if the TDI is in diagnostic mode */
return SCPE_NOFNC; /* then the command is not allowed */
if (uptr != line_unit /* if we're not attaching unit 0 */
&& (uptr != &poll_unit || !(sim_switches & SIM_SW_REST))) /* and not we're not restoring the poll unit */
return SCPE_NOATT; /* then the unit specified is not attachable */
poll_unit.flags &= ~UNIT_DIS; /* enable the poll unit */
status = tmxr_attach (&atcd_mdsc, &poll_unit, cptr); /* and attach it to the specified listening port */
poll_unit.flags |= UNIT_DIS; /* and then disable it again */
return status;
}
/* Detach the TDI.
Normally, this routine is called by the DETACH ATCD command, which is
equivalent to DETACH ATCD0. However, it may be called with other units in
three cases.
A DETACH ALL command will call us for unit 16 (the poll unit) if it is
attached. A RESTORE command also will call us for unit 16 if it is attached.
In the latter case, the terminal channels will have already been rescheduled
as appropriate, so canceling them is skipped. Also, during simulator
shutdown, we will be called for units 0-15 (detach_all in scp.c calls the
detach routines of all units that do NOT have UNIT_ATTABLE), as well as for
unit 16 if it is attached. In all cases, it is imperative that we not reject
the request for unit 16; otherwise any remaining device detaches will not be
performed.
Implementation notes:
1. Detaching the multiplexer resets each line first, which flushes the
output buffer. This ensures that buffered data that has not been output
via a poll_service entry before the DETACH command was issued is written
before the line is disconnected.
*/
static t_stat atcd_detach (UNIT *uptr)
{
uint32 channel;
t_stat status = SCPE_OK;
if (uptr == line_unit || uptr == &poll_unit) { /* if we're detaching the base unit or poll unit */
status = tmxr_detach (&atcd_mdsc, &poll_unit); /* then detach the listening port */
if ((sim_switches & SIM_SW_REST) == 0) /* if this is not a RESTORE call */
for (channel = 0; channel < TERM_COUNT; channel++) { /* then for each terminal channel */
atcd_ldsc [channel].rcve = FALSE; /* disable reception */
sim_cancel (&line_unit [channel]); /* and cancel any transfer in progress */
}
}
return status;
}
/* ATC local utility routines */
/* Request a TDI interrupt.
The data flag and interrupt request flip-flops are set. If the interrupt
mask permits, the interrupt request is passed to the IOP.
*/
static void tdi_set_interrupt (void)
{
tdi_data_flag = SET; /* set the data flag */
atcd_dib.interrupt_request = SET; /* request an interrupt */
if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */
iop_assert_INTREQ (&atcd_dib); /* then assert the INTREQ signal to the IOP */
return;
}
/* TDI master reset.
A master reset is generated either by an IORESET signal or a programmed
master reset (CIO bit 0 set). It clears any pending or active interrupt,
sets the interrupt mask, clears the status word and data flag, and resets all
channels to their initial, unconfigured state.
Implementation notes:
1. In hardware, a master reset sets the Initialize flip-flop. This causes a
direct clear of the recirculating memory window registers, thereby
clearing each channel's buffer, parameter, and status values as they
pass through the window. The flip-flop is cleared when a control word is
sent with the master clear bit (CIO bit 0) cleared. A full recirculation
takes 69.44 microseconds, so the CPU must allow at least this time for
each channel to pass through the window to ensure that all memory
locations are reset. In simulation, the clear occurs "instantaneously."
*/
static void tdi_master_reset (void)
{
uint32 chan;
atcd_dib.interrupt_request = CLEAR; /* clear any current */
atcd_dib.interrupt_active = CLEAR; /* interrupt request */
tdi_interrupt_mask = SET; /* set the interrupt mask */
tdi_status_word = 0; /* clear the status word */
tdi_data_flag = CLEAR; /* and the data flag */
for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */
recv_buffer [chan] = 0; /* clear the receive data buffer */
recv_param [chan] = 0; /* and parameter */
recv_status [chan] = 0; /* and status */
send_buffer [chan] = 0; /* also clear the send data buffer */
send_param [chan] = 0; /* and parameter */
send_status [chan] = 0; /* and status */
sim_cancel (&line_unit [chan]); /* cancel any transfer in progress */
}
for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* for each auxiliary channel */
recv_buffer [chan] = 0; /* clear the receive data buffer */
recv_param [chan] = 0; /* and parameter */
recv_status [chan] = 0; /* and status */
}
return;
}
/* TCI master reset.
A master reset is generated either by an IORESET signal or a programmed
master reset (CIO bit 0 set). It clears any pending or active interrupt,
sets the interrupt mask, clears the control word and channel counter, and
resets all channels to their initial, unconfigured state.
Implementation notes:
1. In hardware, a master reset sets the Status Clear flip-flop. This causes
a direct clear of the Control Word Holding Register and enables writing
into each location of the addressable latches and state RAM. The
flip-flop is reset automatically when the channel counter rolls over.
This takes approximately 12 microseconds, so the CPU must allow at least
this time before sending new control information. In simulation, the
master reset occurs "instantaneously."
2. In hardware, the C2 and C1 control line outputs are cleared by a master
clear. In simulation, we also clear the S2 and S1 status line input
values. This is OK, because they will be reestablished at the next poll
service entry.
*/
static void tci_master_reset (void)
{
uint32 chan;
atcc_dib.interrupt_request = CLEAR; /* clear any current */
atcc_dib.interrupt_active = CLEAR; /* interrupt request */
tci_interrupt_mask = SET; /* set the interrupt mask */
tci_control_word = 0; /* clear the control word */
tci_cntr = 0; /* and the channel counter */
for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */
cntl_status [chan] = 0; /* clear all serial line values */
cntl_param [chan] = 0; /* and the parameter RAM */
}
return;
}
/* Multiplexer channel service.
The channel service routine runs only when there are characters to read or
write. It is scheduled either at a realistic rate corresponding to the
programmed baud rate of the channel to be serviced, or at a somewhat faster
optimized rate. It is entered when a channel buffer is ready for output,
when the poll routine determines that there are characters ready for input,
or while waiting for an ACK to complete an ENQ/ACK handshake.
On entry, the receive channel buffer is checked for a character. If one is
not already present, then the terminal multiplexer library is called to
retrieve the waiting character. If a valid character is now available, it is
processed. If the receive channel has its "diagnose" bit set, the character
is also passed to the auxiliary channels.
If a received character is not available, then the unit's "wait" field is
checked to see if an ACK is expected in reply to an earlier ENQ. If a wait
time is present, it is doubled, and the service is rescheduled. However, if
the new wait time is longer than the current poll time, service rescheduling
is abandoned in favor of the normal poll for received characters.
The send channel buffer is then checked for a character to output. If one is
present, then if it is an all-mark (sync) character, it is discarded, as the
receiver would never see it. Otherwise, if the TDI is in diagnostic mode,
then the character is looped back to the associated receive channel by
storing it in that channel's receive buffer and then recursively calling the
routine for that channel.
If the TDI is in terminal mode, then if the channel flag is set for local
ENQ/ACK handshaking, and the character is an ENQ, it is discarded, an ACK is
stored in the channel's receive buffer, and its reception is scheduled.
Otherwise, the character is processed and then transmitted either to the
simulation console (if output is to channel 0) or to the terminal multiplexer
library for output via Telnet or a serial port on the host machine. If the
channel has its "diagnose" bit set, the character is also passed to the
auxiliary channels. If an ENQ was transmitted, service is rescheduled to
wait for reception of the ACK.
If the data flag is clear, the indicated receive and send channels are
checked for completion flags. If either is set, an interrupt is requested.
If characters remain in the Telnet receive buffer, the service routine is
rescheduled to receive the next one. Otherwise, the routine goes idle until
the next character is output or the next poll determines that there are
characters to receive.
Implementation notes:
1. The "wait" fields of the channel units are not used (i.e., are set to
zero) except when channels are waiting for ACKs. Because we want to
retrieve the ACK as quickly as possible but also minimize the load on the
host system, we initially wait the normal reception time (fast or
realistic) and then double the wait each time it expires without
reception. Therefore, a non-zero "wait" value indicates that an ACK is
expected.
2. Receipt of any character cancels a pending ACK wait, even though it is
possible that the character is not an ACK (for example, if the character
was received but not processed before the ENQ was sent).
3. Calling "tmxr_getc_ln" for channel 0 is OK, as reception is disabled by
default and therefore will return 0.
4. The send channel buffer will always be non-zero if a character is present
(even a NUL) because the data word will have DDS_IS_SEND set.
The receive buffer will always be non-zero if a character is present
(even a NUL) because characters from the console will have SCPE_KFLAG
set, characters from the terminal multiplexer library will have
TMXR_VALID set, and characters looped back from sending will have
DDS_IS_SEND set.
5. Reception of a loopback character is performed immediately because the
reception occurs concurrently with transmission. Reception of a locally
generated ACK is scheduled with a one-character delay to reflect the
remote device transmission delay.
6. If storing an ACK locally overwrites a character already present but not
yet processed, then the receive routine will set the character lost flag.
7. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only
so that a debug trace will record the generation correctly.
8. The console library "sim_putchar_s" routine and the terminal multiplexer
library "tmxr_putc_ln" routine return SCPE_STALL if they are called when
the transmit buffer is full. When called to add the last character to
the buffer, the routines return SCPE_OK but also change the "xmte" field
of the terminal multiplexer line (TMLN) structure from 1 to 0 to indicate
that further calls will be rejected; the value is set back to 1 when the
transmit buffer empties.
Entry with the transmit buffer full causes the service to be rescheduled
to retry the write after a short delay. The "tmxr_poll_tx" routine must
be called in this case, as it is responsible for transmitting the buffer
contents and therefore freeing space in the buffer.
Both library "put" routines also return SCPE_LOST if the line has been
dropped on the remote end. We ignore that error here to allow the
simulation to continue while ignoring the output to a disconnected
terminal.
9. Characters written using "tmxr_putc_ln" are buffered and not transmitted
until the buffer is full, the character is an ENQ (and so we will be
waiting to receive an ACK), or the next input poll is performed. The
last case ensures that the buffer is flushed when output is complete, as
there is no indication from the CPU that the last character has been
sent. This does incur a delay of up to 10 milliseconds, but this is
imperceptible by the user. Buffering offers significantly better
throughput compared to transmitting each character as it is written.
10. The receive/send completion flag (buffer flag) will not set unless the
interrupt enable flag for that channel is also set. If enable is not
set, the completion indication will be lost.
*/
static t_stat line_service (UNIT *uptr)
{
const int32 channel = (int32) (uptr - line_unit); /* the channel number */
const int32 alt_channel = channel ^ 1; /* alternate channel number for diagnostic mode */
const t_bool loopback = (atcd_dev.flags & DEV_DIAG) != 0; /* TRUE if device is set for diagnostic mode */
int32 recv_data, send_data, char_data, cvtd_data;
t_stat result = SCPE_OK;
dprintf (atcd_dev, DEB_SERV, "Channel %d service entered\n",
channel);
/* Reception service */
recv_data = recv_buffer [channel]; /* get the current buffer character */
if (recv_data == 0) { /* if no character is present */
if (uptr->wait) /* then if the channel is waiting for an ACK */
tmxr_poll_rx (&atcd_mdsc); /* then poll the line to see if it has arrived */
recv_data = tmxr_getc_ln (&atcd_ldsc [channel]); /* see if there's now a character ready */
}
if (recv_data & ~DDR_DATA_MASK) { /* if we now have a valid character */
receive (channel, recv_data, loopback); /* then process the reception */
if (recv_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */
diagnose (recv_param [channel], recv_data); /* then route the data to the auxiliary channels */
uptr->wait = 0; /* clear any pending ACK wait */
}
else if (uptr->wait) { /* otherwise if an ACK is expected but has not arrived */
uptr->wait = uptr-> wait * 2; /* then double the wait time for the next check */
if (uptr->wait < poll_unit.wait) { /* if the wait is shorter than the standard poll wait */
sim_activate (uptr, uptr->wait); /* then reschedule the line service */
dprintf (atcd_dev, DEB_SERV, "Channel %d delay %d service rescheduled for ACK\n",
channel, uptr->wait);
}
}
/* Transmission service */
if (send_buffer [channel]) { /* if data is available to send */
send_data = DDS_DATA (send_buffer [channel]); /* then pick up the data and stop bits */
char_data = send_data & ASCII_MASK; /* and also the ASCII character value */
if (send_status [channel] & DST_COMPLETE) { /* if the last completion hasn't been acknowledged */
send_status [channel] |= DST_CHAR_LOST; /* then indicate an overrun condition */
dprintf (atcd_dev, DEB_CSRW, "Channel %d send data overrun\n",
channel);
}
if ((send_buffer [channel] & DDS_MARK) == DDS_MARK) { /* if it's an all-mark character */
send_buffer [channel] = 0; /* then the receiver won't see it */
if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */
send_status [channel] |= DST_COMPLETE; /* then set the completion flag */
dprintf (atcd_dev, DEB_XFER, (loopback
? "Channel %d sync character sent to channel %d\n"
: "Channel %d sync character sent\n"),
channel, alt_channel);
}
else if (loopback) { /* otherwise if the device is in loopback mode */
if (send_param [channel] & DPI_DIAGNOSE) /* then if a diagnosis is requested */
diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */
if ((send_buffer [channel] & DDR_DATA_MASK) == 0) /* if all bits are clear */
recv_buffer [alt_channel] = SCPE_BREAK; /* then it will be seen as a BREAK */
else /* otherwise a character will be received */
recv_buffer [alt_channel] = send_buffer [channel]; /* so store it in the buffer */
send_buffer [channel] = 0; /* clear the send buffer */
if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */
send_status [channel] |= DST_COMPLETE; /* then set the completion flag */
dprintf (atcd_dev, DEB_XFER, "Channel %d character %s sent to channel %d\n",
channel, fmt_char (char_data), alt_channel);
line_service (&line_unit [alt_channel]); /* receive the character on the alternate channel */
}
else if (char_data == ENQ && uptr->flags & UNIT_LOCALACK) { /* otherwise if it's an ENQ and local reply is enabled */
recv_buffer [channel] = GEN_ACK; /* then "receive" an ACK on the channel */
send_buffer [channel] = 0; /* discard the ENQ */
if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */
send_status [channel] |= DST_COMPLETE; /* then set the completion flag */
dprintf (atcd_dev, DEB_XFER, "Channel %d character ENQ absorbed internally\n",
channel);
activate_unit (uptr, Receive); /* schedule the reception */
}
else { /* otherwise it's a normal character */
cvtd_data = sim_tt_outcvt (LOWER_BYTE (send_data), /* so convert it as directed */
TT_GET_MODE (uptr->flags)); /* by the output mode flag */
if (cvtd_data >= 0 && atcd_ldsc [channel].xmte == 0) { /* if it's printable but the transmit buffer is full */
activate_unit (uptr, Stall); /* then retry the output a while later */
tmxr_poll_tx (&atcd_mdsc); /* transmit the line buffer */
dprintf (atcd_dev, DEB_XFER, "Channel %d character %s transmission stalled for full buffer\n",
channel, fmt_char (cvtd_data));
}
else { /* otherwise the character will be consumed */
if (cvtd_data >= 0) /* if the converted character is printable */
if (channel == 0) /* then if we are writing to channel 0 */
result = sim_putchar_s (cvtd_data); /* then output it to the simulation console */
else { /* otherwise */
result = tmxr_putc_ln (&atcd_ldsc [channel], /* output it to the multiplexer line */
cvtd_data);
if (char_data == ENQ /* if sending an ENQ */
|| atcd_ldsc [channel].xmte == 0) /* or the output buffer is full */
tmxr_poll_tx (&atcd_mdsc); /* then transmit the line buffer */
}
if (result == SCPE_OK || result == SCPE_LOST) { /* if the character is queued to transmit */
if (DPRINTING (atcd_dev, DEB_XFER))
if (result == SCPE_LOST)
hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by connection loss\n",
channel, fmt_char (char_data));
else if (cvtd_data >= 0)
hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s sent\n",
channel, fmt_char (cvtd_data));
else
hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by output filter\n",
channel, fmt_char (char_data));
if (send_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */
diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */
send_buffer [channel] = 0; /* clear the buffer */
if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */
send_status [channel] |= DST_COMPLETE; /* then set the completion flag */
if (cvtd_data == ENQ && result == SCPE_OK) /* if an ENQ was successfully sent */
uptr->wait = activate_unit (uptr, Receive); /* then schedule the ACK reception */
result = SCPE_OK; /* return OK in case the connection was lost */
}
}
}
}
if (tdi_data_flag == CLEAR) /* if an interrupt is not currently pending */
scan_channels (channel); /* then scan the channels for completion flags */
if (tmxr_rqln (&atcd_ldsc [channel])) /* if characters are still available on this channel */
activate_unit (uptr, Receive); /* then reschedule the line service */
return result; /* return the result of the service */
}
/* Multiplexer poll service.
The poll service routine is used to poll for Telnet connections and incoming
characters. It also polls the simulation console for channel 0 and flushes
the output buffers for all channels. Polling starts at simulator startup or
when the TDI is enabled and stops when it is disabled.
Implementation notes:
1. The poll service routine may be entered with the TCI either enabled or
disabled. It will not be entered if the TDI is disabled, as it may be
disabled only when it is detached from a listening port.
2. If a character is received on the simulation console, we must call the
channel 0 line service directly. This is necessary because the poll time
may be shorter than the channel service time, and as the console provides
no buffering, a second character received before the channel service had
been entered would be lost.
3. A channel that is waiting for an ACK to complete an ENQ/ACK handshake has
its unit "wait" field set non-zero. If the field value is greater than
the realistic reception time, then the line service is scheduled
immediately, as the channel has already waited the minimum time necessary
(the "wait" field value is doubled each time the line service is entered
before the ACK has been received). Otherwise, the service is scheduled
using the normal reception time.
*/
static t_stat poll_service (UNIT *uptr)
{
int32 chan, line_state;
t_stat status = SCPE_OK;
dprintf (atcd_dev, DEB_PSERV, "Poll delay %d service entered\n",
uptr->wait);
if ((atcc_dev.flags & DEV_DIS) == 0)
dprintf (atcc_dev, DEB_PSERV, "Poll delay %d service entered\n",
uptr->wait);
if ((atcd_dev.flags & DEV_DIAG) == 0) { /* if we're not in diagnostic mode */
chan = tmxr_poll_conn (&atcd_mdsc); /* then check for a new multiplexer connection */
if (chan != -1) { /* if a new connection was established */
atcd_ldsc [chan].rcve = TRUE; /* then enable the channel to receive */
dprintf (atcc_dev, DEB_XFER, "Channel %d connected\n",
chan);
}
}
tmxr_poll_tx (&atcd_mdsc); /* flush the multiplexer output buffers */
tmxr_poll_rx (&atcd_mdsc); /* and poll the multiplexer connections for input */
if ((atcc_dev.flags & (DEV_DIAG | DEV_DIS)) == 0) /* if we're not in diagnostic mode or are disabled */
for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* then scan the channels for line state changes */
if (line_unit [chan].flags & UNIT_MODEM) { /* if the channel is controlled by the TCI */
tmxr_set_get_modem_bits (&atcd_ldsc [chan], /* then get the current line state */
0, 0, &line_state);
if (line_state & TMXR_MDM_DCD) /* if DCD is set */
cntl_status [chan] |= DCD; /* then set the corresponding line flag */
else { /* otherwise DCD is clear */
if (cntl_status [chan] & DCD) /* and a disconnect occurred if DCD was previously set */
dprintf (atcc_dev, DEB_XFER, "Channel %d disconnect dropped DCD and DSR\n",
chan);
cntl_status [chan] &= ~DCD; /* clear the corresponding flag */
}
if (line_state & TMXR_MDM_DSR) /* if DSR is set */
cntl_status [chan] |= DSR; /* then set the corresponding line flag */
else /* otherwise */
cntl_status [chan] &= ~DSR; /* clear the flag */
}
status = sim_poll_kbd (); /* poll the simulation console keyboard for input */
if (status >= SCPE_KFLAG) { /* if a character was present */
recv_buffer [0] = (HP_WORD) status; /* then save it for processing */
status = SCPE_OK; /* and then clear the status */
line_service (&line_unit [0]); /* run the system console's I/O service */
}
for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* check each of the channels for available input */
if (tmxr_rqln (&atcd_ldsc [chan])) /* if characters are available on this channel */
if (line_unit [chan].wait > line_unit [chan].recv_time) { /* then if the channel is waiting for an ACK */
sim_cancel (&line_unit [chan]); /* then cancel any current wait */
activate_unit (&line_unit [chan], Loop); /* and activate the line service immediately */
}
else /* otherwise this is a normal input */
activate_unit (&line_unit [chan], Receive); /* so schedule with the normal receive timing */
if (cpu_is_calibrated) /* if the process clock is calibrated */
uptr->wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */
else /* otherwise */
uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_ATC); /* calibrate the poll timer independently */
sim_activate (uptr, uptr->wait); /* continue polling */
if (tci_scan) /* if scanning is active */
scan_status (); /* then check for line status changes */
return status; /* return the service status */
}
/* Activate a channel unit.
The specified unit is activated to receive or send a character. The reason
for the activation is specified by the "reason" parameter. If the TDI is in
real-time mode, the previously calculated service time is used to schedule
the event. Otherwise, the current value of the optimized timing delay is
used. If tracing is enabled, the activation is logged to the debug file.
Implementation notes:
1. The loopback time is the difference between the reception and
transmission times, as the latter event has already occurred when we are
called.
*/
static int32 activate_unit (UNIT *uptr, ACTIVATOR reason)
{
const int32 channel = (int32) (uptr - line_unit); /* the channel number */
int32 delay = 0;
if (atcd_dev.flags & (DEV_DIAG | DEV_REALTIME)) /* if either diagnostic or real-time mode is set */
switch (reason) { /* then dispatch the REALTIME activation */
case Receive: /* reception event */
delay = uptr->recv_time; /* schedule for the realistic reception time */
break;
case Send: /* transmission event */
delay = uptr->send_time; /* schedule for the realistic transmission time */
break;
case Loop: /* diagnostic loopback reception event */
delay = uptr->recv_time - uptr->send_time; /* schedule the additional reception overhead */
if (delay < 0) /* if the receive time is less than the send time */
delay = 0; /* then schedule the reception immediately */
break;
case Stall: /* transmission stall event */
delay = uptr->send_time / 10; /* reschedule the transmission after a delay */
break;
}
else /* otherwise, we are in optimized timing mode */
switch (reason) { /* so dispatch the FASTTIME activation */
case Receive: /* reception event */
case Send: /* transmission event */
delay = fast_data_time; /* use the optimized timing value */
break;
case Loop: /* diagnostic loopback reception event */
delay = 1; /* use a nominal delay */
break;
case Stall: /* transmission stall event */
delay = fast_data_time / 10; /* reschedule the transmission after a delay */
break;
}
dprintf (atcd_dev, DEB_SERV, "Channel %d delay %d service scheduled\n",
channel, delay);
sim_activate (uptr, delay); /* activate the unit */
return delay; /* and return the activation delay */
}
/* Calculate the service time.
The realistic channel service time in event ticks per character is calculated
from the encoded character size and baud rate in the supplied control word.
The time consists of the transfer time plus a small overhead, which is
different for receiving and sending.
The character size field in the control word is generated by this equation:
encoded_size = (bits_per_character - 1) AND 7
That is, the encoded character size is the value expressed by the three
least-significant bits of the count of the data and stop bits. Therefore,
the actual number of bits per character (including the start bit) is encoded
as:
Actual Encoded
------ -------
5 4
6 5
7 6
8 7
9 0
10 1
11 2
12 3
The baud rate field in the control word is generated by this equation:
14400
encoded_rate = --------- - 1
baud_rate
The transmission and overhead times are related to the recirculation of the
multiplexer's internal memory, which contains the data, parameters, and
status for each of the 16 send channels, 16 receive channels, and 5 auxiliary
channels. Data for a given channel can be accessed only once per
recirculation, which takes 69.44 microseconds (1/14400 of a second). The
encoded rate plus one gives the number of recirculations corresponding to a
single bit time; multiplying by the number of bits per character gives the
number of recirculations to send or receive an entire character.
All operations encounter two overhead delays. First, an average of one-half
of a recirculation must occur to align the memory with the channel of
interest. Second, a full recirculation is required after receiving or
sending is complete before an interrupt may be generated.
For receiving, there is an additional delay to right-justify the received
character in the data accumulator. The accumulator is a 12-bit shift
register, with received data bits are shifted from left to right. When the
final bit is entered, the register must be shifted additionally until the
first data bit is in the LSB (i.e., until the start bit is shifted out of the
register). One shift per recirculation is performed, and the number of
additional shifts required is 12 + 1 - the number of bits per character.
Justification begins immediately after the stop bit has been received, so the
full set of recirculations for that bit are skipped in lieu of justification.
Also, reception of the start bit is delayed by one-half of the bit time to
improve noise immunity.
Therefore, given R = encoded_rate + 1 and B = bits_per_character, the number
of memory recirculations required for sending is:
0.5 to align memory with the target channel (on average)
+ R * B to send the start, data, and stop bits
+ 1 to set the data flag to request an interrupt
For example, at 2400 baud (encoded rate 5), a 10-bit character size, and
69.44 microseconds per circulation, the service time would be:
34.7 usec to align
4166.7 usec to send the start, data, and stop bits
69.4 usec to set the data flag
===========
4270.8 usec from initiation to data flag
The number of memory recirculations required for receiving is:
0.5 to align memory with the target channel (on average)
+ R / 2 to receive the start bit
+ R * (B - 1) to receive the data and stop bits
+ (12 - B + 1) to right-justify the data
+ 1 to set the data flag to request an interrupt
Using the same example as above, the service time would be:
34.7 usec to align
208.3 usec to receive the start bit
3750.0 usec to receive the data and stop bits
208.3 usec to right-justify the data
69.4 usec to set the data flag
===========
4270.7 usec from initiation to data flag
Implementation notes:
1. The multiplexer uses an 8-bit field to set the baud rate. In practice,
only the common rates (110, 150, 300, 600, 1200, 2400) will be used.
Still, the real-time calculation must accommodate any valid setting, so a
lookup table is infeasible.
2. The receive calculation is simplified by noting that R / 2 + R * (B - 1)
is equivalent to R * B - R / 2, so the send calculation may be reused.
Note that the receive time may be less than the send time, typically when
the baud rate is low, so that the time to send the stop bits is longer
than the time to right-justify the reception. This means that the
"addition" of the receive overhead may actually be a subtraction.
*/
static uint32 service_time (HP_WORD control, ACTIVATOR reason)
{
const double recirc_time = 69.44; /* microseconds per memory recirculation */
const uint32 recirc_per_bit = DPI_BAUD_RATE (control) + 1; /* number of memory recirculations per bit */
const uint32 char_size = bits_per_char [DPI_CHAR_SIZE (control)]; /* number of bits per character */
double usec_per_char;
usec_per_char = recirc_time * /* calculate the overhead for sending */
(char_size * recirc_per_bit + 1.5);
if (reason == Receive) /* if we're receiving */
usec_per_char += recirc_time * /* then add the additional receiving overhead */
(12 - char_size + 1
- recirc_per_bit / 2.0);
return (uint32) (usec_per_char / USEC_PER_EVENT); /* return the service time for indicated rate */
}
/* Store a word in the recirculating memory.
A parameter or data word is stored in the recirculating memory for the
channel indicated by the associated field of the "control" parameter. If the
channel number is out of range, the store is ignored.
For receive and send parameters, the realistic service time is calculated and
stored in the unit for use when a receive or send event is scheduled. For
send data, parity is calculated and added if specified by the channel's
parameter, and the transmission event is scheduled. For a receive parameter,
the pad bits that would normally be added during right-justification after
reception are calculated and stored in the unit.
Implementation notes:
1. Service times are not calculated or set for auxiliary channels because
events are not scheduled on them (and so no units are allocated for
them).
2. Pad bits begin with the stop bit and continue until the character is
right-justified in the receive buffer. The calculation assumes one stop
bit, but there is no way of ascertaining the actual number of stop bits
from the parameter word.
*/
static void store (HP_WORD control, HP_WORD data)
{
const uint32 channel = DCN_CHAN (control); /* current channel number */
if (data & DDS_IS_SEND) /* if this is a send parameter or data */
if (channel > LAST_TERM) /* then report if the channel number is out of range */
dprintf (atcd_dev, DEB_CSRW, "Send channel %u invalid\n",
channel);
else if (data & DPI_IS_PARAM) { /* otherwise if this is a parameter store */
send_param [channel] = data; /* then save it */
line_unit [channel].send_time = /* and set the service time */
service_time (data, Send);
dprintf (atcd_dev, DEB_CSRW, "Channel %u send parameter %06o stored\n",
channel, data);
}
else { /* otherwise this is a data store */
if (send_param [channel] & DPI_ENABLE_PARITY) /* if parity is enabled */
data = data & ~DDS_PARITY /* then replace the parity bit */
| SEND_PARITY (data); /* with the calculated value */
send_buffer [channel] = data; /* store it in the buffer */
dprintf (atcd_dev, DEB_CSRW, "Channel %u send data %06o stored\n",
channel, data);
activate_unit (&line_unit [channel], Send); /* schedule the transmission event */
}
else /* otherwise this is a receive parameter */
if (channel >= RECV_CHAN_COUNT) /* report if the channel number is out of range */
dprintf (atcd_dev, DEB_CSRW, "Receive channel %u invalid\n",
channel);
else if (data & DPI_IS_PARAM) { /* otherwise if this is a parameter store */
recv_param [channel] = data; /* then save it */
if (channel <= LAST_TERM) { /* if this is a terminal channel */
line_unit [channel].recv_time = /* and not an auxiliary channel */
service_time (data, Receive); /* then set the service time */
line_unit [channel].stop_bits = /* set the stop bits mask for reception */
PAD_BITS (data);
}
dprintf (atcd_dev, DEB_CSRW, "Channel %u receive parameter %06o stored\n",
channel, data);
}
else /* otherwise a data store to a receive channel is invalid */
dprintf (atcd_dev, DEB_CSRW, "Channel %u receive output data word %06o invalid\n",
channel, data);
}
/* Process a character received from a channel.
This routine is called to process received data on a channel, typically when
a character exists in the channel's receive buffer, but also when a character
is received on an auxiliary channel. The "channel" parameter indicates the
channel on which reception occurred, "data" is the (full) character data
as received from the console or terminal multiplexer libraries, and
"loopback" is TRUE if the data should be looped back to the alternate channel
for diagnostic execution.
On entry, the bits required to pad the character are obtained. If a BREAK
was detected, then break status is set, and the character is set to NUL,
reflecting the all-space reception. Otherwise, if a character is already
present in the receive buffer, "character lost" status is set to indicate
that it will be overwritten.
If this is a loopback reception, and echo is enabled on the channel, the
character is sent back to the alternate channel. Otherwise, if this is a
main and not auxiliary channel reception, the character is upshifted if the
UNIT_CAPSLOCK flag is set. If echo is enabled, the character is written back
to the console or terminal multiplexer library line. Finally, the completion
flag is set if enabled.
Implementation notes:
1. The echo to a terminal multiplexer library line will return SCPE_LOST if
the line has been dropped on the remote end. We can ignore the error
here, as the line drop will be picked up when the next input poll is
performed.
In addition, the SCPE_STALL returned for a full output buffer is also
ignored, as there's no way of queuing echoed characters while waiting for
the buffer to empty.
*/
static void receive (int32 channel, int32 data, t_bool loopback)
{
int32 recv_data, char_data, char_echo, pad;
recv_data = data & DDR_DATA_MASK; /* mask to just the character data */
char_data = recv_data & ASCII_MASK; /* and to the equivalent ASCII character */
if (channel <= LAST_TERM) /* if this is a receive channel */
pad = line_unit [channel].stop_bits; /* then set the stop-bit padding from the unit */
else /* otherwise it's an auxiliary channel */
pad = PAD_BITS (recv_param [channel]); /* so calculate the padding */
if (data & SCPE_BREAK) { /* if a break was detected */
recv_buffer [channel] = NUL; /* then return a NUL character */
recv_status [channel] |= DST_BREAK; /* and set break reception status */
dprintf (atcd_dev, DEB_XFER, "Channel %d break detected\n",
channel);
}
else { /* otherwise a normal character was received */
if (recv_status [channel] & DST_COMPLETE) { /* if a character is already pending */
recv_status [channel] |= DST_CHAR_LOST; /* then the previous character will be lost */
dprintf (atcd_dev, DEB_CSRW, "Channel %d receive data overrun\n",
channel);
}
recv_buffer [channel] = recv_data | pad; /* save the character and padding in the buffer */
if (loopback) { /* if this channel has a loopback cable installed */
if (recv_param [channel] & DPI_ENABLE_ECHO) { /* and the channel has echo enabled */
recv_buffer [channel ^ 1] = data; /* then send the data back to the other channel */
activate_unit (&line_unit [channel ^ 1], Loop); /* schedule the reception */
dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echoed to channel %d\n",
channel, fmt_char (char_data), channel ^ 1);
}
}
else if (channel <= LAST_TERM) { /* otherwise if it's a receive channel */
if (line_unit [channel].flags & UNIT_CAPSLOCK) { /* then if caps lock is down */
recv_data = toupper (recv_data); /* then convert to upper case if lower */
recv_buffer [channel] = recv_data | pad; /* and replace the character in the buffer */
}
if (recv_param [channel] & DPI_ENABLE_ECHO) { /* if the channel has echo enabled */
char_echo = sim_tt_outcvt (recv_data, /* then convert the character per the output mode */
TT_GET_MODE (line_unit [channel].flags));
if (char_echo >= 0) { /* if the converted character is valid for the mode */
if (channel == 0) /* then if this is for channel 0 */
sim_putchar (char_echo); /* then write it back to the simulation console */
else { /* otherwise */
tmxr_putc_ln (&atcd_ldsc [channel], /* write it to the multiplexer output line */
char_echo);
if (atcd_ldsc [channel].xmte == 0) /* if the output buffer is full */
tmxr_poll_tx (&atcd_mdsc); /* then transmit the line buffer */
}
dprintf (atcd_dev, DEB_XFER, ("Channel %d character %s echoed\n"),
channel, fmt_char (char_echo));
}
else /* otherwise the echo character was discarded */
dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echo discarded by output filter\n",
channel, fmt_char (char_data));
}
}
}
if (recv_param [channel] & DPI_ENABLE_IRQ) /* if the channel is enabled to interrupt */
recv_status [channel] |= DST_COMPLETE; /* then set the completion flag */
dprintf (atcd_dev, DEB_XFER, "Channel %d character %s %s\n",
channel, fmt_char (char_data),
(data == GEN_ACK ? "generated internally" : "received"));
return;
}
/* Check for a character received on an auxiliary channel.
If a send or receive channel has its "diagnose" bit set, then this routine is
called to check if any of the auxiliary channels would receive the character
too. If one or more would, then the "receive" routine is called to store the
character in the appropriate buffer.
The diagnosis mode is typically used to speed-sense a receive channel.
In hardware, reception on a given channel is simultaneously received on the
five auxiliary channels, with each channel set for a different baud rate.
When a specific character (e.g., CR) is sent, only the channel with the
correct baud rate setting will receive the intended character. By
determining which channel received the correct data, the baud rate of the
sending terminal may be obtained.
In simulation, a main channel will receive a character regardless of the baud
rate configuration. Therefore, an auxiliary channel will receive the same
character only if it is configured for the same baud rate and character size.
*/
static void diagnose (HP_WORD control, int32 data)
{
const HP_WORD config = control & DPI_CHAR_CONFIG; /* main channel character size and baud rate */
int32 channel;
for (channel = FIRST_AUX; channel <= LAST_AUX; channel++) /* scan the auxiliary channels */
if ((recv_param [channel] & DPI_CHAR_CONFIG) == config) /* if the character configurations match */
receive (channel, data, FALSE); /* then receive the data on this channel */
return;
}
/* Scan the channels for a transfer completion interrupt.
If the multiplexer data flag is not set, this routine is called to scan the
channels for completion flags. If the "channel" parameter value is SCAN_ALL,
then all of the channels are checked. Otherwise, only the specified channel
is checked.
If a channel has its completion flag set, the multiplexer data and status
words are set for return to the CPU, the data flag is set, and an interrupt
is requested. The channel requesting the interrupt is contained in the
status word.
In hardware, the recirculating buffer consists of the sixteen receive
channels, then the sixteen send channels, and then the five auxiliary
channels. The completion flags are checked in this order during the
recirculation after a completion flag is set. If the scan has been inhibited
by the data flag, it will commence with the channel currently in the
recirculation window at the time the flag was cleared and then continue in
the order indicated.
In simulation, the scan is always initiated as though at the beginning of a
recirculation.
Implementation notes:
1. After a send completion, the data word contains all ones (stop bits).
*/
static void scan_channels (int32 channel)
{
int32 chan, first_chan, last_chan;
if (channel == SCAN_ALL) { /* if all channels are to be scanned */
first_chan = FIRST_TERM; /* then set the loop limits */
last_chan = LAST_TERM; /* to the full range of channels */
}
else /* otherwise scan just the channel indicated */
first_chan = last_chan = channel; /* plus the auxiliary channels if requested */
for (chan = first_chan; chan <= last_chan; chan++) { /* scan the receive channels */
if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */
tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */
| DDR_CHAN (chan) /* from the character, channel, and parity */
| RECV_PARITY (recv_buffer [chan]);
tdi_status_word = recv_status [chan] /* form the partial status word */
| DST_CHAN (chan);
recv_buffer [chan] = 0; /* clear the receive buffer */
recv_status [chan] = 0; /* and the channel status */
dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n",
chan);
tdi_set_interrupt (); /* set the data flag and request an interrupt */
return; /* and terminate scanning */
}
}
for (chan = first_chan; chan <= last_chan; chan++) { /* scan the send channels */
if (send_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */
tdi_read_word = DDR_DATA_MASK /* then form the input data word from the */
| DDR_CHAN (chan); /* data input buffer and the channel number */
tdi_status_word = send_status [chan] /* form the partial status word */
| DST_CHAN (chan)
| DST_SEND_IRQ;
send_status [chan] = 0; /* clear the channel status */
dprintf (atcd_dev, DEB_CSRW, "Channel %d send interrupt requested\n",
chan);
tdi_set_interrupt (); /* set the data flag and request an interrupt */
return; /* and terminate scanning */
}
}
if (channel == SCAN_ALL /* if we're scanning all channels */
|| send_param [channel] & DPI_DIAGNOSE /* or the indicated channel is diagnosing */
|| recv_param [channel] & DPI_DIAGNOSE) /* its transmission or reception */
for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* then scan the auxiliary channels */
if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */
tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */
| DDR_CHAN (chan) /* from the character, channel, and parity */
| RECV_PARITY (recv_buffer [chan]);
tdi_status_word = recv_status [chan] /* form the partial status word */
| DST_CHAN (chan)
| DST_DIAGNOSE;
recv_buffer [chan] = 0; /* clear the receive buffer */
recv_status [chan] = 0; /* and the channel status */
dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n",
chan);
tdi_set_interrupt (); /* set the data flag and request an interrupt */
return; /* and terminate scanning */
}
}
return; /* no channel has completed */
}
/* Check for a control interrupt.
If the scan flag is clear, then return the interrupt status bits for the
channel indicated by the current control counter value. Otherwise, scan all
of the control channels, starting with the current counter, to check for a
status mismatch. This occurs when either of the incoming status bits does
not match the stored status, and the corresponding mismatch detection is
enabled. If an enabled mismatch is found, request an interrupt from the CPU,
clear the scan flag, and return the interrupt status bits with the counter
pointing at the interrupting channel.
*/
static HP_WORD scan_status (void)
{
uint32 chan_count;
HP_WORD interrupts;
if (tci_scan) /* if the control interface is scanning */
chan_count = TERM_COUNT; /* then look at all of the channels */
else /* otherwise */
chan_count = 1; /* look at only the current channel */
while (chan_count > 0) { /* scan the control channels */
interrupts = CST_IX (CCN_ESX (cntl_param [tci_cntr]) /* check for an enabled status mismatch */
& (cntl_param [tci_cntr] ^ cntl_status [tci_cntr]));
if (tci_scan) { /* if the interface is scanning */
if (interrupts) { /* and a mismatch was found */
atcc_dib.interrupt_request = SET; /* then request an interrupt */
if (tci_interrupt_mask) /* if the interrupt mask is satisfied */
iop_assert_INTREQ (&atcc_dib); /* then assert the INTREQ signal */
tci_scan = CLEAR; /* stop the scan at the current channel */
dprintf (atcc_dev, DEB_CSRW, "Channel %u interrupt requested\n",
tci_cntr);
break;
}
tci_cntr = (tci_cntr + 1) % TERM_COUNT; /* set the counter to the next channel in sequence */
}
chan_count = chan_count - 1; /* drop the count of channels to check */
}
return interrupts; /* return the interrupt status bits */
}