/* hp2100_mpx.c: HP 2100 12792C 8-Channel Asynchronous Multiplexer | |
Copyright (c) 2008-2017, J. David Bryan | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of the author shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from the author. | |
MPX 12792C 8-Channel Asynchronous Multiplexer | |
01-Nov-17 JDB Fixed serial output buffer overflow handling | |
26-Jul-17 JDB Changed BITFIELD macros to field constructors | |
22-Apr-17 JDB Corrected missing compound statements | |
15-Mar-17 JDB Trace flags are now global | |
Changed DEBUG_PRI calls to tprintfs | |
10-Mar-17 JDB Added IOBUS to the debug table | |
17_Jan_17 JDB Changed "hp_---sc" and "hp_---dev" to "hp_---_dib" | |
02-Aug-16 JDB Burst-fill only the first receive buffer in fast mode | |
28-Jul-16 JDB Fixed buffer ready check at read completion | |
Fixed terminate on character counts > 254 | |
13-May-16 JDB Modified for revised SCP API function parameter types | |
24-Dec-14 JDB Added casts for explicit downward conversions | |
10-Jan-13 MP Added DEV_MUX and additional DEVICE field values | |
28-Dec-12 JDB Allow direct attach to the poll unit only when restoring | |
10-Feb-12 JDB Deprecated DEVNO in favor of SC | |
Removed DEV_NET to allow restoration of listening port | |
28-Mar-11 JDB Tidied up signal handling | |
26-Oct-10 JDB Changed I/O signal handler for revised signal model | |
25-Nov-08 JDB Revised for new multiplexer library SHOW routines | |
14-Nov-08 JDB Cleaned up VC++ size mismatch warnings for zero assignments | |
03-Oct-08 JDB Fixed logic for ENQ/XOFF transmit wait | |
07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach | |
10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size | |
26-Jun-08 JDB Rewrote device I/O to model backplane signals | |
26-May-08 JDB Created MPX device | |
References: | |
- HP 12792B 8-Channel Asynchronous Multiplexer Subsystem Installation and Reference Manual | |
(12792-90020, July 1984) | |
- HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem User's Manual | |
(5955-8867, Jun e1993) | |
- HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem Configuration Guide | |
(5955-8868, June 1993) | |
- HP 1000 series 8-channel Multiplexer Firmware External Reference Specification | |
(October 19, 1982) | |
- HP 12792/12040 Multiplexer Firmware Source | |
(24999-18312, revision C) | |
- Zilog Components Data Book | |
(00-2034-04, 1985) | |
The 12792A/B/C/D was an eight-line asynchronous serial multiplexer that | |
connected terminals, modems, serial line printers, and "black box" devices | |
that used the RS-232 standard to the CPU. It used an on-board microprocessor | |
and provided input and output buffering to support block-mode reads from HP | |
264x and 262x terminals at speeds up to 19.2K baud. The card handled | |
character editing, echoing, ENQ/ACK handshaking, and read terminator | |
detection, substantially reducing the load on the CPU over the earlier 12920 | |
multiplexer. It was supported by HP under RTE-MIII, RTE-IVB, and RTE-6/VM. | |
Under simulation, it connects with HP terminal emulators via Telnet or serial | |
ports. | |
The single interface card contained a Z80 CPU, DMA controller, CTC, four | |
two-channel SIO UARTs, 16K of RAM, 8K of ROM, and I/O backplane latches and | |
control circuitry. The card executed a high-level command set, and data | |
transfer to and from the CPU was via the on-board DMA controller and the DCPC | |
in the CPU. | |
The 12792 for the M/E/F series and the 12040 multiplexer for the A/L series | |
differed only in backplane design. Early ROMs were card-specific, but later | |
ones were interchangeable; the code would determine whether it was executing | |
on an MEF card or an AL card. | |
Four major firmware revisions were made. These were labelled "A", "B", "C", | |
and "D". The A, B, and C revisions were interchangeable from the perspective | |
of the OS driver; the D was different and required an updated driver. | |
Specifically: | |
Op. Sys. Driver Part Number Rev | |
-------- ------ -------------------- --- | |
RTE-MIII DVM00 12792-16002 Rev.2032 A | |
RTE-IVB DVM00 12792-16002 Rev.5000 ABC | |
RTE-6/VM DVM00 12792-16002 Rev.5000 ABC | |
RTE-6/VM DV800 92084-15068 Rev.6000 D | |
RTE-A IDM00 92077-16754 Rev.5020 ABC | |
RTE-A ID800 92077-16887 Rev.6200 D | |
Revisions A-C have an upward-compatible command set that partitions each OS | |
request into several sub-commands. Each command is initiated by setting the | |
control flip-flop on the card, which causes a non-maskable interrupt (NMI) on | |
the card's Z80 processor. | |
The D-revision firmware uses a completely different command set. The | |
commands are slightly modified versions of the original EXEC calls (read, | |
write, and control) and are generally passed to the card directly for action. | |
This simulation supports the C revision. D-revision support may be added | |
later. | |
Twelve programmable baud rates are supported by the multiplexer. These | |
"realistic" rates are simulated by scheduling I/O service based on the | |
appropriate number of 1000 E-Series instructions for the rate selected. | |
The simulation provides both the "realistic timing" described above, as well | |
as an optimized "fast timing" option. Optimization makes three improvements: | |
1. Buffered characters are transferred in blocks. | |
2. ENQ/ACK handshaking is done locally without involving the client. | |
3. BS and DEL respond visually more like prior RTE terminal drivers. | |
HP did not offer a functional diagnostic for the 12792. Instead, a Z80 | |
program that tested the operation of the hardware was downloaded to the card, | |
and a "go/no-go" status was returned to indicate the hardware condition. | |
Because this is a functional simulation of the multiplexer and not a Z80 | |
emulation, the diagnostic cannot be used to test the implementation. | |
Implementation notes: | |
1. The 12792 had two baud-rate generators that were assigned to lines by the | |
wiring configuration in the I/O cable connector hood. Two of the four | |
CTC counters were used to implement the BRGs for all eight lines. Only | |
subsets of the configurable rates were allowed for lines connected to the | |
same BRG, and assigning mutually incompatible rates caused corruption of | |
the rates on lines assigned earlier. Under simulation, any baud rate may | |
be assigned to any line without interaction, and assignments of lines to | |
BRGs is not implemented. | |
2. Revisions B and C added support for the 37214A Systems Modem subsystem | |
and the RTE-A Virtual Control Panel (VCP). Under simulation, the modem | |
commands return status codes indicating that no modems are present, and | |
the VCP commands are not implemented. | |
*/ | |
#include <ctype.h> | |
#include "hp2100_defs.h" | |
#include "sim_tmxr.h" | |
/* Bitfield constructors. | |
Most of the control and status words used by the multiplexer are encoded into | |
fields of varying lengths. Traditionally, field accessors have been defined | |
as macro definitions of numeric values. For example, a flag in bit 15 and a | |
two-bit field occupying bits 12-11 would be defined as: | |
#define CHAR_ECHO 0100000u | |
#define CHAR_SIZE 0014000u | |
#define SIZE_A 0004000u | |
#define SIZE_B 0010000u | |
#define CHAR_SHIFT 11 | |
#define GET_SIZE(v) (((v) & CHAR_SIZE) >> CHAR_SHIFT) | |
A drawback is that mental conversion is necessary to determine the affected | |
bits for, e.g., CHAR_SIZE. It would be better if the bit numbers were | |
explicit. This is what the bitfield constructors attempt to do. | |
Four constructors are provided: | |
BIT(n) -- a value corresponding to bit number "n". | |
FIELD(h,l) -- a mask corresponding to bits "h" through "l" inclusive. | |
FIELD_TO(h,l,v) -- a value extracted from field "h" through "l" of word "v". | |
TO_FIELD(h,l,v) -- a value "v" aligned to a field in bits "h" through "l". | |
With these constructors, the above definitions would be rewritten as follows: | |
#define CHAR_ECHO BIT (15) | |
#define CHAR_SIZE FIELD (12, 11) | |
#define SIZE_A TO_FIELD (12, 11, 1) | |
#define SIZE_B TO_FIELD (12, 11, 2) | |
#define GET_SIZE(v) FIELD_TO (12, 11, v) | |
With optimization, the above macro expanstions reduce to the equivalent | |
numeric values. Hopefully, these will be easier to maintain than octal | |
literals. | |
*/ | |
#undef BIT /* undefine any prior sim_defs.h usage */ | |
#undef FIELD /* undefine any prior sim_defs.h usage */ | |
#undef FIELD_TO /* undefine any prior sim_defs.h usage */ | |
#undef TO_FIELD /* undefine any prior sim_defs.h usage */ | |
#define BIT(b) (1u << (b)) | |
#define FIELD(h,l) (BIT ((h) - (l) + 1) - 1 << (l)) | |
#define FIELD_TO(h,l,v) (((unsigned) (v) & FIELD (h, l)) >> (l)) | |
#define TO_FIELD(h,l,v) ((unsigned) (v) << (l) & FIELD (h, l)) | |
/* Program constants */ | |
#define MPX_DATE_CODE 2416 /* date code for C firmware */ | |
#define RD_BUF_SIZE 514 /* read buffer size */ | |
#define WR_BUF_SIZE 514 /* write buffer size */ | |
#define RD_BUF_LIMIT 254 /* read buffer limit */ | |
#define WR_BUF_LIMIT 254 /* write buffer limit */ | |
#define KEY_DEFAULT 255 /* default port key */ | |
/* Service times: | |
DATA_DELAY = Z80 DMA data word transfer time | |
PARAM_DELAY = STC to STF for first word of two-word command | |
CMD_DELAY = STC to STF for one or two-word command execution | |
*/ | |
#define DATA_DELAY uS (1.25) /* data transfer time */ | |
#define PARAM_DELAY uS (25) /* parameter request time */ | |
#define CMD_DELAY uS (400) /* command completion time */ | |
/* Unit references */ | |
#define MPX_PORTS 8 /* number of visible units */ | |
#define MPX_CNTLS 2 /* number of control units */ | |
#define mpx_cntl (mpx_unit [MPX_PORTS + 0]) /* controller unit */ | |
#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* polling unit */ | |
/* Character constants */ | |
#define EOT '\004' | |
#define ENQ '\005' | |
#define ACK '\006' | |
#define BS '\010' | |
#define LF '\012' | |
#define CR '\015' | |
#define DC1 '\021' | |
#define DC2 '\022' | |
#define DC3 '\023' | |
#define ESC '\033' | |
#define RS '\036' | |
#define DEL '\177' | |
#define XON DC1 | |
#define XOFF DC3 | |
/* Device flags */ | |
#define DEV_REV_D BIT (DEV_V_UF + 0) /* firmware revision D (not implemented) */ | |
/* Unit flags */ | |
#define UNIT_FASTTIME BIT (UNIT_V_UF + 0) /* fast timing mode */ | |
#define UNIT_CAPSLOCK BIT (UNIT_V_UF + 1) /* caps lock mode */ | |
/* Multiplexer commands for revisions A/B/C. | |
The CPU outputs commands to the interface with the OTA and OTB instructions. | |
Commands are either one or two words in length. The one-word format is: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 1 | command opcode | command parameter | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The two-word format is: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | command opcode | command value | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| command parameter | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Commands implemented by firmware revision: | |
Rev Cmd Param Operation Status Value(s) Returned | |
--- --- ----- ------------------------------- ------------------------ | |
ABC 100 - No operation 000000 | |
ABC 101 - Reset to power-on defaults 100000 | |
ABC 102 - Enable unsolicited input None, unless UI pending | |
ABC 103 1 Disable unsolicited interrupts 000000 | |
ABC 103 2 Abort DMA transfer 000000 | |
ABC 104 - Acknowledge Second word of UI status | |
ABC 105 key Cancel first receive buffer 000000 | |
ABC 106 key Cancel all received buffers 000000 | |
ABC 107 - Fast binary read (none) | |
-BC 140 chr VCP put byte 000000 | |
-BC 141 - VCP put buffer 000000 | |
-BC 142 - VCP get byte Character from port 0 | |
-BC 143 - VCP get buffer 000120 | |
-BC 144 - Exit VCP mode 000000 | |
-BC 157 - Enter VCP mode 000000 | |
Rev Cmd Value Operation Status Value(s) Returned | |
--- --- ----- ------------------------------- ---------------------------------- | |
ABC 300 - No operation 000000 | |
ABC 301 key Request write buffer 000000 or 000376 | |
ABC 302 key Write data to buffer (none) | |
ABC 303 key Set port key 000000 or date code of firmware | |
ABC 304 key Set receive type 000000 | |
ABC 305 key Set character count 000000 | |
ABC 306 key Set flow control 000000 | |
ABC 307 key Read data from buffer (none) | |
ABC 310 - Download executable (none) | |
-BC 311 key Connect line 000000 or 140000 if no modem | |
-BC 312 key Disconnect line 000000 or 140000 if no modem | |
-BC 315 key Get modem/port status modem status or 000200 if no modem | |
-BC 316 key Enable/disable modem loopback 000000 or 140000 if no modem | |
-BC 320 key Terminate active receive buffer 000000 | |
Simple parameter words for commands 301-320 are: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| unused | 300 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| requested buffer size in bytes | 301 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| character count in bytes | 305 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| read length in bytes | 307 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| size of download in bytes | 307 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| unused | 315 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| unused | 320 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The remaining commands have parameter words containing bit fields. These are | |
described below. | |
*/ | |
#define CN_OPCODE(w) FIELD_TO (15, 8, w) | |
#define CN_KEY(w) FIELD_TO ( 7, 0, w) | |
/* One-word command codes */ | |
#define CMD_NOP 0100u /* No operation */ | |
#define CMD_RESET 0101u /* Reset firmware to power-on defaults */ | |
#define CMD_ENABLE_UI 0102u /* Enable unsolicited input */ | |
#define CMD_DISABLE 0103u /* Disable interrupts / Abort DMA Transfer */ | |
#define CMD_ACK 0104u /* Acknowledge */ | |
#define CMD_CANCEL 0105u /* Cancel first receive buffer */ | |
#define CMD_CANCEL_ALL 0106u /* Cancel all received buffers */ | |
#define CMD_BINARY_READ 0107u /* Fast binary read */ | |
#define CMD_VCP_PUT 0140u /* VCP put byte */ | |
#define CMD_VCP_PUT_BUF 0141u /* VCP put buffer */ | |
#define CMD_VCP_GET 0142u /* VCP get byte */ | |
#define CMD_VCP_GET_BUF 0143u /* VCP get buffer */ | |
#define CMD_VCP_EXIT 0144u /* Exit VCP mode */ | |
#define CMD_VCP_ENTER 0157u /* Enter VCP mode */ | |
/* Two-word command codes */ | |
#define CMD_REQ_WRITE 0301u /* Request write buffer */ | |
#define CMD_WRITE 0302u /* Write data to buffer */ | |
#define CMD_SET_KEY 0303u /* Set port key */ | |
#define CMD_SET_RCV 0304u /* Set receive type */ | |
#define CMD_SET_COUNT 0305u /* Set character count */ | |
#define CMD_SET_FLOW 0306u /* Set flow control */ | |
#define CMD_READ 0307u /* Read data from buffer */ | |
#define CMD_DL_EXEC 0310u /* Download executable */ | |
#define CMD_CN_LINE 0311u /* Connect line */ | |
#define CMD_DC_LINE 0312u /* Disconnect line */ | |
#define CMD_GET_STATUS 0315u /* Get modem/port status */ | |
#define CMD_LOOPBACK 0316u /* Enable/disable modem loopback */ | |
#define CMD_TERM_BUF 0320u /* Terminate active receive buffer */ | |
/* Sub-command codes */ | |
#define SUBCMD_UI 1 /* Disable unsolicited interrupts */ | |
#define SUBCMD_DMA 2 /* Abort DMA transfer */ | |
#define CMD_TWO_WORDS 0200u /* two-word commands have the high bit set */ | |
/* Input status. | |
The CPU inputs status from the interface with the LIA, LIB, MIA, and MIB | |
instructions. The format is not encoded but is instead dependent on the | |
command executed. Commands that complete normally return 0. | |
*/ | |
#define ST_OK 0000000u /* Command OK */ | |
#define ST_DIAG_OK 0000015u /* Diagnostic passes */ | |
#define ST_VCP_SIZE 0000120u /* VCP buffer size = 80 chars */ | |
#define ST_NO_SYSMDM 0000200u /* No systems modem card */ | |
#define ST_TEST_OK 0100000u /* Self test OK */ | |
#define ST_NO_MODEM 0140000u /* No modem card on port */ | |
#define ST_BAD_KEY 0135320u /* Bad port key = 0xBAD0 */ | |
/* Write data to buffer (302). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - | E | C | P | write length | 302 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
E = disable ENQ/ACK for this write only | |
C = add CR/LF if last char not '_' | |
P = write is partial transfer (no CR/LF at end) | |
*/ | |
#define WR_NO_ENQACK BIT (13) /* Write: no ENQ/ACK this xfer */ | |
#define WR_ADD_CRLF BIT (12) /* Write: add CR/LF if not '_' */ | |
#define WR_PARTIAL BIT (11) /* Write: write is partial */ | |
#define WR_LENGTH(w) FIELD_TO (10, 0, w) /* Write: write length in bytes */ | |
/* Set port key (303). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| bits | M | G | stop | par | E | baud rate | port | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
M = hardwired or modem (0/1) | |
G = baud rate generator 0/1 | |
E = disable or enable ENQ/ACK (0/1) | |
Bits per Character: | |
00 = 5 bits | |
01 = 7 bits | |
10 = 6 bits | |
11 = 8 bits | |
Stop Bits: | |
00 = reserved | |
01 = 1 stop bit | |
10 = 1.5 stop bits | |
11 = 2 stop bits | |
Parity: | |
00 = no parity | |
01 = odd parity | |
10 = no parity | |
11 = even parity | |
Baud Rate: | |
0000 = no change | |
0001 = 50 baud | |
0010 = 75 baud | |
0011 = 110 baud | |
0100 = 134.5 baud | |
0101 = 150 baud | |
0110 = 300 baud | |
0111 = 1200 baud | |
1000 = 1800 baud | |
1001 = 2400 baud | |
1010 = 4800 baud | |
1011 = 9600 baud | |
1100 = 19200 baud | |
1101 = reserved | |
1110 = reserved | |
1111 = reserved | |
*/ | |
#define SK_BPC_MASK FIELD (15, 14) /* Set key: bits per character */ | |
#define SK_BPC_5 TO_FIELD (15, 14, 0) /* 5 bits per character */ | |
#define SK_BPC_7 TO_FIELD (15, 14, 1) /* 7 bits per character */ | |
#define SK_BPC_6 TO_FIELD (15, 14, 2) /* 6 bits per character */ | |
#define SK_BPC_8 TO_FIELD (15, 14, 3) /* 8 bits per character */ | |
#define SK_MODEM BIT (13) /* Set key: hardwired or modem */ | |
#define SK_BRG BIT (12) /* Set key: baud rate generator 0/1 */ | |
#define SK_STOPBITS_MASK FIELD (11, 10) /* Set key: stop bits */ | |
#define SK_STOP_1 TO_FIELD (11, 10, 1) /* 1 stop bit */ | |
#define SK_STOP_15 TO_FIELD (11, 10, 2) /* 1.5 stop bits */ | |
#define SK_STOP_2 TO_FIELD (11, 10, 3) /* 2 stop bits */ | |
#define SK_PARITY_MASK FIELD (9, 8) /* Set key: parity select */ | |
#define SK_PARITY_NONE TO_FIELD (9, 8, 0) /* no parity */ | |
#define SK_PARITY_ODD TO_FIELD (9, 8, 1) /* odd parity */ | |
#define SK_PARITY_EVEN TO_FIELD (9, 8, 3) /* even parity */ | |
#define SK_ENQACK BIT (7) /* Set key: disable or enable ENQ/ACK */ | |
#define SK_BAUDRATE_MASK FIELD (6, 3) /* Set key: port baud rate */ | |
#define SK_BAUD_NOCHG TO_FIELD (6, 3, 0) /* no change */ | |
#define SK_BAUD_50 TO_FIELD (6, 3, 1) /* 50 port baud rate */ | |
#define SK_BAUD_75 TO_FIELD (6, 3, 2) /* 75 port baud rate */ | |
#define SK_BAUD_110 TO_FIELD (6, 3, 3) /* 110 port baud rate */ | |
#define SK_BAUD_1345 TO_FIELD (6, 3, 4) /* 134.5 port baud rate */ | |
#define SK_BAUD_150 TO_FIELD (6, 3, 5) /* 150 port baud rate */ | |
#define SK_BAUD_300 TO_FIELD (6, 3, 6) /* 300 port baud rate */ | |
#define SK_BAUD_1200 TO_FIELD (6, 3, 7) /* 1200 port baud rate */ | |
#define SK_BAUD_1800 TO_FIELD (6, 3, 8) /* 1800 port baud rate */ | |
#define SK_BAUD_2400 TO_FIELD (6, 3, 9) /* 2400 port baud rate */ | |
#define SK_BAUD_4800 TO_FIELD (6, 3, 10) /* 4800 port baud rate */ | |
#define SK_BAUD_9600 TO_FIELD (6, 3, 11) /* 9600 port baud rate */ | |
#define SK_BAUD_19200 TO_FIELD (6, 3, 12) /* 19200 port baud rate */ | |
#define SK_PORT_MASK FIELD (2, 0) /* Set key: port number */ | |
#define GET_BPC(w) FIELD_TO (15, 14, w) | |
#define GET_BAUDRATE(w) FIELD_TO ( 6, 3, w) | |
#define GET_PORT(w) FIELD_TO ( 2, 0, w) | |
#define SK_BRG_1 SK_BRG | |
#define SK_BRG_0 0 | |
#define SK_PWRUP_0 (SK_BPC_8 | SK_BRG_0 | SK_STOP_1 | SK_BAUD_9600) | |
#define SK_PWRUP_1 (SK_BPC_8 | SK_BRG_1 | SK_STOP_1 | SK_BAUD_9600) | |
/* Set receive type (304). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - | C | R | T | D | N | K | E | H | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
C = end transfer on CR | |
R = end transfer on RS | |
T = end transfer on EOT | |
D = end transfer on DC2 | |
N = end transfer on count | |
K = end transfer on character | |
E = enable input editing (BS and DEL) | |
H = enable input echo | |
*/ | |
#define RT_END_ON_CR BIT (7) /* Receive type: end xfer on CR */ | |
#define RT_END_ON_RS BIT (6) /* Receive type: end xfer on RS */ | |
#define RT_END_ON_EOT BIT (5) /* Receive type: end xfer on EOT */ | |
#define RT_END_ON_DC2 BIT (4) /* Receive type: end xfer on DC2 */ | |
#define RT_END_ON_CNT BIT (3) /* Receive type: end xfer on count */ | |
#define RT_END_ON_CHAR BIT (2) /* Receive type: end xfer on character */ | |
#define RT_ENAB_EDIT BIT (1) /* Receive type: enable input editing */ | |
#define RT_ENAB_ECHO BIT (0) /* Receive type: enable input echoing */ | |
#define RT_PWRUP (RT_END_ON_CR | RT_END_ON_CHAR | RT_ENAB_EDIT | RT_ENAB_ECHO) | |
/* Set flow control (306). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - - - - - - - | F | X | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
F = force an XON if currently XOFF | |
X = enable XON/XOFF handshaking | |
*/ | |
#define FC_FORCE_XON BIT (1) /* Flow control: force XON */ | |
#define FC_XONXOFF BIT (0) /* Flow control: enable XON/XOFF */ | |
/* Connect line (311). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - - - | G | M | B | D | I | S | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
G = guard tone off/on (0/1) | |
M = 212/V.22 mode (0/1) | |
B = 10/9 bits (0/1) | |
D = originate/answer (0/1) | |
I = manual/automatic dial (0/1) | |
S = low/high speed (0/1) | |
*/ | |
#define CL_GUARD BIT (5) /* Connect line: guard tone off or on */ | |
#define CL_STANDARD BIT (4) /* Connect line: standard 212 or V.22 */ | |
#define CL_BITS BIT (3) /* Connect line: bits 10 or 9 */ | |
#define CL_MODE BIT (2) /* Connect line: mode originate or answer */ | |
#define CL_DIAL BIT (1) /* Connect line: dial manual or automatic */ | |
#define CL_SPEED BIT (0) /* Connect line: speed low or high */ | |
/* Disconnect line (312). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - - - - - - - - | A | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
A = enable/disable auto-answer (0/1) | |
*/ | |
#define DL_AUTO_ANSWER BIT (0) /* Disconnect line: auto-answer enable or disable */ | |
/* Enable/disable modem loopback (316). | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - - - - - - | S | T | E | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
S = low/high speed (0/1) | |
T = analog/remote digital (0/1) | |
E = disable/enable loop test (0/1) | |
*/ | |
#define LB_SPEED BIT (2) /* Loopback test: speed low or high */ | |
#define LB_MODE BIT (1) /* Loopback test: mode analog or digital */ | |
#define LB_TEST BIT (0) /* Loopback test: test disable or enable */ | |
/* Unsolicited interrupts. | |
Upon detecting certain conditions, and if enabled by command 102, the card | |
can send unsolicited inputs to the host. The card notifies the host that an | |
unsolicited input is available by presenting the first status word and | |
setting the flag. After sending the unsolicited input, the mux disables | |
unsolicited inputs to the host until they are enabled again. The host reads | |
the status with an LIA/B and acknowledges the unsolicited input with an | |
Acknowledge command. In response, the card outputs the second word of status | |
and sets the flag again. The host reads the second word with an LIA/B. | |
The format of the unsolicited input is: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - | reason | port key | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| additional parameter | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The unsolicited inputs by firmware revision are: | |
Rev Reason Description Additional Parameter | |
--- ------ ----------------------- --------------------- | |
ABC 001 Write buffer available Buffer size in bytes | |
-BC 002 Modem line connected 000000 | |
-BC 003 Modem line disconnected 000000 | |
ABC 004 Break received 000000 | |
ABC 005 Read buffer available Reception status | |
*/ | |
#define UI_REASON_MASK FIELD (13, 8) /* Unsolicited interrupt reason */ | |
#define UI_WRBUF_AVAIL TO_FIELD (13, 8, 1) /* Write buffer available */ | |
#define UI_LINE_CONN TO_FIELD (13, 8, 2) /* Modem line connected */ | |
#define UI_LINE_DISC TO_FIELD (13, 8, 3) /* Modem line disconnected */ | |
#define UI_BRK_RECD TO_FIELD (13, 8, 4) /* Break received */ | |
#define UI_RDBUF_AVAIL TO_FIELD (13, 8, 5) /* Read buffer available */ | |
#define UI_PORT_KEY_MASK FIELD (7, 0) /* Unsolicited interrupt port key */ | |
#define UI_REASON_SHIFT 8 /* Unsolicited interrupt reason alignment shift */ | |
#define GET_UIREASON(w) FIELD_TO (13, 8, w) | |
#define GET_UIPORT(w) FIELD_TO ( 7, 0, w) | |
/* Read buffer available reception status. | |
The reception status for Reason 005 is in this format: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - | P | F | ETC | count of characters received | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
P = parity error or buffer overflow occurred | |
F = buffer full before end of text character seen | |
End of Text Character: | |
00 = EOT | |
01 = CR | |
10 = DC2 | |
11 = RS | |
A parity error detected during reception sets the P and F bits and | |
immediately terminates the buffer, generating a "read buffer available" | |
interrupt. A buffer full condition (characters received with both read | |
buffers terminated) sets the P bit for the next interrupt return. Receiving | |
the 254th character will set the F bit and terminate the read buffer. | |
*/ | |
#define RS_OVERFLOW BIT (14) /* Reception status: buffer overflow occurred */ | |
#define RS_PARTIAL BIT (13) /* Reception status: buffer is partial */ | |
#define RS_ETC_RS TO_FIELD (12, 11, 3) /* Reception status: terminated by RS */ | |
#define RS_ETC_DC2 TO_FIELD (12, 11, 2) /* Reception status: terminated by DC2 */ | |
#define RS_ETC_CR TO_FIELD (12, 11, 1) /* Reception status: terminated by CR */ | |
#define RS_ETC_EOT TO_FIELD (12, 11, 0) /* Reception status: terminated by EOT */ | |
#define RS_CHAR_COUNT_MASK FIELD (10, 0) /* Reception status: character count mask */ | |
/* Get modem/port status (315). | |
The status return value has the modem status in the lower byte and a zero in | |
the upper byte, as follows: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - - - - - | M | T | P | - - - | S | C | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
M = systems modem present/absent (0/1) | |
T = systems modem OK/timed out (0/1) | |
P = modem present/absent (0/1) | |
S = low/high speed (0/1) | |
C = line disconnected/connected (0/1) | |
If the systems modem card cage is not present, the return status value is | |
000200B. | |
*/ | |
#define GS_NO_SYSMDM BIT (7) /* Get status: systems modem present or absent */ | |
#define GS_SYSMDM_TO BIT (6) /* Get status: systems modem OK or timed out */ | |
#define GS_NO_MODEM BIT (5) /* Get status: modem present or absent */ | |
#define GS_SPEED BIT (1) /* Get status: speed low or high */ | |
#define GS_LINE BIT (0) /* Get status: line disconnected or connected */ | |
/* Port flags. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| - - - - | A | X | B | H | W | O | F | E | f | e | K | D | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
A = a Terminate Receive Buffer command has reset the termination count | |
X = an incoming XOFF character has stopped the transmission | |
B = an incoming BREAK was detected | |
H = a read buffer is now available | |
W = a write buffer has been requested but is not available | |
O = a read buffer has overflowed | |
F = a read buffer is currently filling | |
E = a read buffer is currently emptying | |
f = a write buffer is currently filling | |
e = a write buffer is currently emptying | |
K = waiting for an ACK in response to ENQ | |
D = do an ENQ/ACK handshake after the output limit has been reached | |
*/ | |
#define FL_ALERT BIT (11) /* Port flags: alert for terminate recv buffer */ | |
#define FL_XOFF BIT (10) /* Port flags: XOFF stopped transmission */ | |
#define FL_BREAK BIT ( 9) /* Port flags: UI / break detected */ | |
#define FL_HAVEBUF BIT ( 8) /* Port flags: UI / read buffer available */ | |
#define FL_WANTBUF BIT ( 7) /* Port flags: UI / write buffer available */ | |
#define FL_RDOVFLOW BIT ( 6) /* Port flags: read buffers overflowed */ | |
#define FL_RDFILL BIT ( 5) /* Port flags: read buffer is filling */ | |
#define FL_RDEMPT BIT ( 4) /* Port flags: read buffer is emptying */ | |
#define FL_WRFILL BIT ( 3) /* Port flags: write buffer is filling */ | |
#define FL_WREMPT BIT ( 2) /* Port flags: write buffer is emptying */ | |
#define FL_WAITACK BIT ( 1) /* Port flags: ENQ sent, waiting for ACK */ | |
#define FL_DO_ENQACK BIT ( 0) /* Port flags: do ENQ/ACK handshake */ | |
#define FL_RDFLAGS (FL_RDEMPT | FL_RDFILL | FL_RDOVFLOW) | |
#define FL_WRFLAGS (FL_WREMPT | FL_WRFILL) | |
#define FL_UI_PENDING (FL_WANTBUF | FL_HAVEBUF | FL_BREAK) | |
#define ACK_LIMIT 1000 /* poll timeout for ACK response */ | |
#define ENQ_LIMIT 80 /* output chars before ENQ */ | |
/* Multiplexer interface state */ | |
typedef enum { /* controller execution states */ | |
idle, /* idle */ | |
cmd, /* waiting for a command word */ | |
param, /* waiting for a parameter word */ | |
exec /* executing a command */ | |
} STATE; | |
static STATE mpx_state = idle; /* current controller state */ | |
static uint16 mpx_ibuf = 0; /* status/data in */ | |
static uint16 mpx_obuf = 0; /* command/data out */ | |
static uint32 mpx_cmd = 0; /* current command */ | |
static uint32 mpx_param = 0; /* current parameter */ | |
static uint32 mpx_port = 0; /* current port number for R/W */ | |
static uint32 mpx_portkey = 0; /* current port's key */ | |
static int32 mpx_iolen = 0; /* length of current I/O xfer */ | |
static t_bool mpx_uien = FALSE; /* unsolicited interrupts enabled */ | |
static uint32 mpx_uicode = 0; /* unsolicited interrupt reason and port */ | |
static struct { | |
FLIP_FLOP control; /* control flip-flop */ | |
FLIP_FLOP flag; /* flag flip-flop */ | |
FLIP_FLOP flagbuf; /* flag buffer flip-flop */ | |
} mpx = { CLEAR, CLEAR, CLEAR }; | |
/* Multiplexer per-line state */ | |
static uint8 mpx_key [MPX_PORTS]; /* port keys */ | |
static uint16 mpx_config [MPX_PORTS]; /* port configuration */ | |
static uint16 mpx_rcvtype [MPX_PORTS]; /* receive type */ | |
static uint16 mpx_charcnt [MPX_PORTS]; /* current character count */ | |
static uint16 mpx_termcnt [MPX_PORTS]; /* termination character count */ | |
static uint16 mpx_flowcntl [MPX_PORTS]; /* flow control */ | |
static uint8 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */ | |
static uint16 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */ | |
static uint16 mpx_flags [MPX_PORTS]; /* line state flags */ | |
/* Multiplexer buffer selectors */ | |
typedef enum { /* I/O operations */ | |
ioread, | |
iowrite | |
} IO_OPER; | |
typedef enum { /* buffer selectors */ | |
get, | |
put | |
} BUF_SELECT; | |
static const char *const io_op [] = { /* operation names, indexed by IO_OPER */ | |
"read", | |
"write" | |
}; | |
static const uint16 buf_size [] = { /* buffer sizes, indexed by IO_OPER */ | |
RD_BUF_SIZE, | |
WR_BUF_SIZE | |
}; | |
static uint32 emptying_flags [2]; /* buffer emptying flags [IO_OPER] */ | |
static uint32 filling_flags [2]; /* buffer filling flags [IO_OPER] */ | |
/* Multiplexer per-line buffers */ | |
typedef uint16 BUF_INDEX [MPX_PORTS] [2]; /* buffer index (read and write) */ | |
static BUF_INDEX mpx_put; /* read/write buffer add index */ | |
static BUF_INDEX mpx_sep; /* read/write buffer separator index */ | |
static BUF_INDEX mpx_get; /* read/write buffer remove index */ | |
static uint8 mpx_rbuf [MPX_PORTS] [RD_BUF_SIZE]; /* read buffer */ | |
static uint8 mpx_wbuf [MPX_PORTS] [WR_BUF_SIZE]; /* write buffer */ | |
/* Multiplexer local SCP support routines */ | |
static IOHANDLER mpx_io; | |
static t_stat cntl_service (UNIT *uptr); | |
static t_stat line_service (UNIT *uptr); | |
static t_stat poll_service (UNIT *uptr); | |
static t_stat mpx_reset (DEVICE *dptr); | |
static t_stat mpx_attach (UNIT *uptr, CONST char *cptr); | |
static t_stat mpx_detach (UNIT *uptr); | |
static t_stat set_revision (UNIT *uptr, int32 val, CONST char *cptr, void *desc); | |
static t_stat show_revision (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
static t_stat show_status (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
/* Multiplexer local utility routines */ | |
static t_bool exec_command (void); | |
static void poll_connection (void); | |
static void controller_reset (void); | |
static uint32 service_time (uint16 control_word); | |
static int32 key_to_port (uint32 key); | |
static void buf_init (IO_OPER rw, uint32 port); | |
static uint8 buf_get (IO_OPER rw, uint32 port); | |
static void buf_put (IO_OPER rw, uint32 port, uint8 ch); | |
static void buf_remove (IO_OPER rw, uint32 port); | |
static void buf_term (IO_OPER rw, uint32 port, uint8 header); | |
static void buf_free (IO_OPER rw, uint32 port); | |
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which); | |
static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which); | |
static uint32 buf_avail (IO_OPER rw, uint32 port); | |
/* Multiplexer SCP data structures */ | |
/* Terminal multiplexer library structures */ | |
static int32 mpx_order [MPX_PORTS] = { /* line connection order */ | |
-1 | |
}; | |
static TMLN mpx_ldsc [MPX_PORTS] = { /* line descriptors */ | |
{ 0 } | |
}; | |
static TMXR mpx_desc = { /* multiplexer descriptor */ | |
MPX_PORTS, /* number of terminal lines */ | |
0, /* listening port (reserved) */ | |
0, /* master socket (reserved) */ | |
mpx_ldsc, /* line descriptors */ | |
mpx_order, /* line connection order */ | |
NULL /* multiplexer device (derived internally) */ | |
}; | |
/* Device information block */ | |
static DIB mpx_dib = { | |
&mpx_io, /* device interface */ | |
MPX, /* select code */ | |
0 /* card index */ | |
}; | |
/* Unit list. | |
The first eight units correspond to the eight multiplexer line ports. These | |
handle character I/O via the multiplexer library. A ninth unit acts as the | |
card controller, executing commands and transferring data to and from the I/O | |
buffers. A tenth unit is responsible for polling for connections and line | |
I/O. It also holds the master socket for Telnet connections. | |
The character I/O service routines run only when there are characters to read | |
or write. They operate at the approximate baud rates of the terminals (in | |
CPU instructions per second) in order to be compatible with the OS drivers. | |
The controller service routine runs only when a command is executing or a | |
data transfer to or from the CPU is in progress. The poll service must run | |
continuously, but it may operate much more slowly, as the only requirement is | |
that it must not present a perceptible lag to human input. To be compatible | |
with CPU idling, it is co-scheduled with the master poll timer, which uses a | |
ten millisecond period. | |
The controller and poll units are hidden by disabling them, so as to present | |
a logical picture of the multiplexer to the user. | |
*/ | |
#define POLL_FLAGS (UNIT_ATTABLE | UNIT_DIS) | |
static UNIT mpx_unit [] = { | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 0 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 1 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 2 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 3 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 4 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 5 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 6 */ | |
{ UDATA (&line_service, UNIT_FASTTIME, 0) }, /* terminal I/O line 7 */ | |
{ UDATA (&cntl_service, UNIT_DIS, 0) }, /* controller unit */ | |
{ UDATA (&poll_service, POLL_FLAGS, 0), POLL_FIRST } /* poll unit */ | |
}; | |
/* Register list */ | |
static REG mpx_reg [] = { | |
/* Macro Name Location Radix Width Offset Depth Flags */ | |
/* ------ -------- ------------------- ----- ----- --------------- ------------------------ --------------- */ | |
{ DRDATA (STATE, mpx_state, 3) }, | |
{ ORDATA (IBUF, mpx_ibuf, 16), REG_FIT | REG_X }, | |
{ ORDATA (OBUF, mpx_obuf, 16), REG_FIT | REG_X }, | |
{ ORDATA (CMD, mpx_cmd, 8) }, | |
{ ORDATA (PARAM, mpx_param, 16) }, | |
{ DRDATA (PORT, mpx_port, 8), PV_LEFT }, | |
{ DRDATA (PORTKEY, mpx_portkey, 8), PV_LEFT }, | |
{ DRDATA (IOLEN, mpx_iolen, 16), PV_LEFT }, | |
{ FLDATA (UIEN, mpx_uien, 0) }, | |
{ GRDATA (UIPORT, mpx_uicode, 10, 3, 0) }, | |
{ GRDATA (UICODE, mpx_uicode, 10, 3, UI_REASON_SHIFT) }, | |
{ BRDATA (KEYS, mpx_key, 10, 8, MPX_PORTS) }, | |
{ BRDATA (PCONFIG, mpx_config, 8, 16, MPX_PORTS) }, | |
{ BRDATA (RCVTYPE, mpx_rcvtype, 2, 16, MPX_PORTS) }, | |
{ BRDATA (CHARCNT, mpx_charcnt, 8, 16, MPX_PORTS) }, | |
{ BRDATA (TERMCNT, mpx_termcnt, 8, 16, MPX_PORTS) }, | |
{ BRDATA (FLOWCNTL, mpx_flowcntl, 8, 16, MPX_PORTS) }, | |
{ BRDATA (ENQCNTR, mpx_enq_cntr, 10, 7, MPX_PORTS) }, | |
{ BRDATA (ACKWAIT, mpx_ack_wait, 10, 10, MPX_PORTS) }, | |
{ BRDATA (PFLAGS, mpx_flags, 2, 12, MPX_PORTS) }, | |
{ BRDATA (RBUF, mpx_rbuf, 8, 8, MPX_PORTS * RD_BUF_SIZE), REG_A }, | |
{ BRDATA (WBUF, mpx_wbuf, 8, 8, MPX_PORTS * WR_BUF_SIZE), REG_A }, | |
{ BRDATA (GET, mpx_get, 10, 10, MPX_PORTS * 2) }, | |
{ BRDATA (SEP, mpx_sep, 10, 10, MPX_PORTS * 2) }, | |
{ BRDATA (PUT, mpx_put, 10, 10, MPX_PORTS * 2) }, | |
{ FLDATA (CTL, mpx.control, 0) }, | |
{ FLDATA (FLG, mpx.flag, 0) }, | |
{ FLDATA (FBF, mpx.flagbuf, 0) }, | |
{ ORDATA (SC, mpx_dib.select_code, 6), REG_HRO }, | |
{ ORDATA (DEVNO, mpx_dib.select_code, 6), REG_HRO }, | |
{ BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO }, | |
{ NULL } | |
}; | |
/* Modifier list */ | |
static MTAB mpx_mod [] = { | |
/* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
/* ------------- ------------- ------------------ ------------ ---------- ------- ---------- */ | |
{ UNIT_FASTTIME, UNIT_FASTTIME, "fast timing", "FASTTIME", NULL, NULL, NULL }, | |
{ UNIT_FASTTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, | |
{ UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, | |
{ UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, | |
/* Entry Flags Value Print String Match String Validation Display Descriptor */ | |
/* ------------------- ----- ------------ ------------- ----------------- ------------------ ------------------ */ | |
{ MTAB_XUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, (void *) &mpx_desc }, | |
{ MTAB_XUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, (void *) &mpx_desc }, | |
{ MTAB_XDV, 0, "REV", NULL, &set_revision, &show_revision, NULL }, | |
{ MTAB_XDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, (void *) &mpx_desc }, | |
{ MTAB_XDV, 0, "", NULL, NULL, &show_status, (void *) &mpx_desc }, | |
{ MTAB_XDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, (void *) &mpx_desc }, | |
{ MTAB_XDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, (void *) &mpx_desc }, | |
{ MTAB_XDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, (void *) &mpx_desc }, | |
{ MTAB_XDV, 1u, "SC", "SC", &hp_set_dib, &hp_show_dib, (void *) &mpx_dib }, | |
{ MTAB_XDV | MTAB_NMO, ~1u, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &mpx_dib }, | |
{ 0 } | |
}; | |
/* Debugging trace list */ | |
static DEBTAB mpx_deb [] = { | |
{ "CMDS", DEB_CMDS }, | |
{ "CPU", DEB_CPU }, | |
{ "BUF", DEB_BUF }, | |
{ "XFER", DEB_XFER }, | |
{ "IOBUS", TRACE_IOBUS }, /* interface I/O bus signals and data words */ | |
{ NULL, 0 } | |
}; | |
/* Device descriptor */ | |
DEVICE mpx_dev = { | |
"MPX", /* device name */ | |
mpx_unit, /* unit array */ | |
mpx_reg, /* register array */ | |
mpx_mod, /* modifier array */ | |
MPX_PORTS + MPX_CNTLS, /* number of units */ | |
10, /* address radix */ | |
31, /* address width */ | |
1, /* address increment */ | |
8, /* data radix */ | |
8, /* data width */ | |
&tmxr_ex, /* examine routine */ | |
&tmxr_dep, /* deposit routine */ | |
&mpx_reset, /* reset routine */ | |
NULL, /* boot routine */ | |
&mpx_attach, /* attach routine */ | |
&mpx_detach, /* detach routine */ | |
&mpx_dib, /* device information block */ | |
DEV_DEBUG | DEV_DISABLE | DEV_MUX, /* device flags */ | |
0, /* debug control flags */ | |
mpx_deb, /* debug flag name table */ | |
NULL, /* memory size change routine */ | |
NULL, /* logical device name */ | |
NULL, /* help routine */ | |
NULL, /* help attach routine*/ | |
(void *) &mpx_desc /* help context */ | |
}; | |
/* Interface local SCP support routines */ | |
/* Multiplexer interface. | |
Commands are sent to the card via an OTA/B. Issuing an STC SC,C causes the | |
mux to accept the word (STC causes a NMI on the card). If the command uses | |
one word, command execution will commence, and the flag will set on | |
completion. If the command uses two words, the flag will be set, indicating | |
that the second word should be output via an OTA/B. Command execution will | |
commence upon receipt, and the flag will set on completion. | |
When the flag sets for command completion, status or data may be read from | |
the card via an LIA/B. If additional status or data words are expected, the | |
flag will set when they are available. | |
A command consists of an opcode in the high byte, and a port key or command | |
parameter in the low byte. Undefined commands are treated as NOPs. | |
The card firmware executes commands as part of a twelve-event round-robin | |
scheduling poll. The card NMI service routine simply sets a flag that is | |
interrogated during polling. The poll sequence is advanced after each | |
command. This implies that successive commands incur a delay of at least one | |
poll-loop's execution time. On an otherwise quiescent card, this delay is | |
approximately 460 Z80 instructions, or about 950 usec. The average command | |
initiation time is half of that, or roughly 425 usec. | |
If a detected command requires a second word, the card sits in a tight loop, | |
waiting for the OTx that indicates that the parameter is available. Command | |
initiation from parameter receipt is about 25 usec. | |
For reads and writes to card buffers, the on-board DMA controller is used. | |
The CPU uses DCPC to handle the transfer, but the data transfer time is | |
limited by the Z80 DMA, which can process a word in about 1.25 usec. | |
For most cards, the hardware POPIO signal sets the flag buffer and flag | |
flip-flops, while CRS clears the control flip-flop. For this card, the | |
control and flags are cleared together by CRS, and POPIO is not used. | |
Implementation notes: | |
1. "Enable unsolicited input" is the only command that does not set the | |
device flag upon completion. Therefore, the CPU has no way of knowing | |
when the command has completed. Because the command in the input latch | |
is recorded in the NMI handler, but actual execution only begins when the | |
scheduler polls for the command indication, it is possible for another | |
command to be sent to the card before the "Enable unsolicited input" | |
command is recognized. In this case, the second command overwrites the | |
first and is executed by the scheduler poll. Under simulation, this | |
condition occurs when the OTx and STC processors are entered with | |
mpx_state = cmd. | |
2. The "Fast binary read" command inhibits all other commands until the card | |
is reset. | |
*/ | |
static uint32 mpx_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
{ | |
static const char *output_state [] = { "Command", "Command override", "Parameter", "Data" }; | |
static const char *input_state [] = { "Status", "Invalid status", "Parameter", "Data" }; | |
const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); | |
int32 delay; | |
IOSIGNAL signal; | |
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ | |
while (working_set) { | |
signal = IONEXT (working_set); /* isolate next signal */ | |
switch (signal) { /* dispatch I/O signal */ | |
case ioCLF: /* clear flag flip-flop */ | |
mpx.flag = mpx.flagbuf = CLEAR; /* clear flag and flag buffer */ | |
tprintf (mpx_dev, DEB_CMDS, "[CLF] Flag cleared\n"); | |
break; | |
case ioSTF: /* set flag flip-flop */ | |
tprintf (mpx_dev, DEB_CMDS, "[STF] Flag set\n"); | |
/* fall into ENF */ | |
case ioENF: /* enable flag */ | |
mpx.flag = mpx.flagbuf = SET; /* set flag and flag buffer */ | |
break; | |
case ioSFC: /* skip if flag is clear */ | |
setstdSKF (mpx); | |
break; | |
case ioSFS: /* skip if flag is set */ | |
setstdSKF (mpx); | |
break; | |
case ioIOI: /* I/O data input */ | |
stat_data = IORETURN (SCPE_OK, mpx_ibuf); /* return info */ | |
tprintf (mpx_dev, DEB_CPU, "[LIx%s] %s = %06o\n", | |
hold_or_clear, input_state [mpx_state], mpx_ibuf); | |
if (mpx_state == exec) /* if this is input data word */ | |
sim_activate (&mpx_cntl, DATA_DELAY); /* continue transmission */ | |
break; | |
case ioIOO: /* I/O data output */ | |
mpx_obuf = IODATA (stat_data); /* save word */ | |
tprintf (mpx_dev, DEB_CPU, "[OTx%s] %s = %06o\n", | |
hold_or_clear, output_state [mpx_state], mpx_obuf); | |
if (mpx_state == param) { /* if this is parameter word */ | |
sim_activate (&mpx_cntl, CMD_DELAY); /* do command now */ | |
tprintf (mpx_dev, DEB_CMDS, "[OTx%s] Command %03o parameter %06o scheduled, time = %d\n", | |
hold_or_clear, mpx_cmd, mpx_obuf, CMD_DELAY); | |
} | |
else if (mpx_state == exec) /* else if this is output data word */ | |
sim_activate (&mpx_cntl, DATA_DELAY); /* then do transmission */ | |
break; | |
case ioCRS: /* control reset */ | |
controller_reset (); /* reset firmware to power-on defaults */ | |
mpx_obuf = 0; /* clear output buffer */ | |
mpx.control = CLEAR; /* clear control */ | |
mpx.flagbuf = CLEAR; /* clear flag buffer */ | |
mpx.flag = CLEAR; /* clear flag */ | |
tprintf (mpx_dev, DEB_CMDS, "[CRS] Controller reset\n"); | |
break; | |
case ioCLC: /* clear control flip-flop */ | |
mpx.control = CLEAR; /* clear control */ | |
tprintf (mpx_dev, DEB_CMDS, "[CLC%s] Control cleared\n", hold_or_clear); | |
break; | |
case ioSTC: /* set control flip-flop */ | |
mpx.control = SET; /* set control */ | |
if (mpx_cmd == CMD_BINARY_READ) /* executing fast binary read? */ | |
break; /* further command execution inhibited */ | |
mpx_cmd = CN_OPCODE (mpx_obuf); /* get command opcode */ | |
mpx_portkey = CN_KEY (mpx_obuf); /* get port key */ | |
if (mpx_state == cmd) /* already scheduled? */ | |
sim_cancel (&mpx_cntl); /* cancel to get full delay */ | |
mpx_state = cmd; /* set command state */ | |
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ | |
delay = PARAM_DELAY; /* specify parameter wait */ | |
else /* one-word command */ | |
delay = CMD_DELAY; /* specify command wait */ | |
sim_activate (&mpx_cntl, delay); /* schedule command */ | |
tprintf (mpx_dev, DEB_CMDS, "[STC%s] Command %03o key %d scheduled, time = %d\n", | |
hold_or_clear, mpx_cmd, mpx_portkey, delay); | |
break; | |
case ioEDT: /* end data transfer */ | |
tprintf (mpx_dev, DEB_CPU, "[EDT] DCPC transfer ended\n"); | |
break; | |
case ioSIR: /* set interrupt request */ | |
setstdPRL (mpx); /* set standard PRL signal */ | |
setstdIRQ (mpx); /* set standard IRQ signal */ | |
setstdSRQ (mpx); /* set standard SRQ signal */ | |
break; | |
case ioIAK: /* interrupt acknowledge */ | |
mpx.flagbuf = CLEAR; /* clear flag buffer */ | |
break; | |
default: /* all other signals */ | |
break; /* are ignored */ | |
} | |
working_set = working_set & ~signal; /* remove current signal from set */ | |
} | |
return stat_data; | |
} | |
/* Multiplexer controller service. | |
The controller service handles commands and data transfers to and from the | |
CPU. The delay in scheduling the controller service represents the firmware | |
command or data execution time. The controller may be in one of four states | |
upon entry: idle, first word of command received (cmd), command parameter | |
received (param), or data transfer (exec). | |
Entry in the command state causes execution of one-word commands and | |
solicitation of command parameters for two-word commands, which are executed | |
when entering in the parameter state. | |
Entry in the data transfer state moves one word between the CPU and a read or | |
write buffer. For writes, the write buffer is filled with words from the | |
CPU. Once the indicated number of words have been transferred, the | |
appropriate line service is scheduled to send the characters. For reads, | |
characters are unloaded from the read buffer to the CPU; an odd-length | |
transfer is padded with a blank. A read of fewer characters than are present | |
in the buffer will return the remaining characters when the next read is | |
performed. | |
Each read or write is terminated by the CPU sending one additional word (the | |
RTE drivers send -1). The command completes when this word is acknowledged | |
by the card setting the device flag. For zero-length writes, this additional | |
word will be the only word sent. | |
Data transfer is also used by the "Download executable" command to absorb the | |
downloaded program. The firmware jumps to location 5100 hex in the | |
downloaded program upon completion of reception. It is the responsibility of | |
the program to return to the multiplexer firmware and to return to the CPU | |
whatever status is appropriate when it is done. Under simulation, we simply | |
"sink" the program and return status compatible with the multiplexer | |
diagnostic program to simulate a passing test. | |
Entry in the idle state checks for unsolicited interrupts. UIs are sent to | |
the host when the controller is idle, UIs have been enabled, and a UI | |
condition exists. If a UI is not acknowledged, it will remain pending and | |
will be reissued the next time the controller is idle and UIs have been | |
enabled. | |
UI conditions are kept in the per-port flags. The UI conditions are write | |
buffer available, read buffer available, break received, modem line | |
connected, and modem line disconnected. The latter two conditions are not | |
implemented in this simulation. If a break condition occurs at the same time | |
as a read buffer completion, the break has priority; the buffer UI will occur | |
after the break UI is acknowledged. | |
The firmware checks for UI condition flags as part of the scheduler polling | |
loop. Under simulation, though, UIs can occur only in two places: the point | |
of origin (e.g., termination of a read buffer), or the "Enable unsolicited | |
input" command executor. UIs will be generated at the point of origin only | |
if the simulator is idle. If the simulator is not idle, it is assumed that | |
UIs have been disabled to execute the current command and will be reenabled | |
when the command sequence is complete. | |
When the multiplexer is reset, and before the port keys are set, all ports | |
enter "echoplex" mode. In this mode, characters received are echoed back as | |
a functional test. Each port terminates buffers on CR reception. We detect | |
this condition, cancel the buffer, and discard the buffer termination UI. | |
Implementation notes: | |
1. The firmware transfers the full amount requested by the CPU, even if the | |
transfer is longer than the buffer. Also, zero-length transfers program | |
the card DMA chip to transfer 0 bytes; this results in a transfer of 217 | |
bytes, per the Zilog databook. Under simulation, writes beyond the | |
buffer are accepted from the CPU but discarded, and reads beyond the | |
buffer return blanks. | |
2. We should never return from this routine in the "cmd" state, so debugging | |
will report "internal error!" if we do. | |
*/ | |
static t_stat cntl_service (UNIT *uptr) | |
{ | |
uint8 ch; | |
uint32 i; | |
t_bool add_crlf; | |
t_bool set_flag = TRUE; | |
STATE last_state = mpx_state; | |
static const char *cmd_state [] = { "complete", "internal error!", "waiting for parameter", "executing" }; | |
switch (mpx_state) { /* dispatch on current state */ | |
case idle: /* controller idle */ | |
set_flag = FALSE; /* assume no UI */ | |
if (mpx_uicode) { /* unacknowledged UI? */ | |
if (mpx_uien == TRUE) { /* interrupts enabled? */ | |
mpx_port = GET_UIPORT (mpx_uicode); /* get port number */ | |
mpx_portkey = mpx_key [mpx_port]; /* get port key */ | |
mpx_ibuf = (uint16) (mpx_uicode & UI_REASON_MASK | mpx_portkey); /* report UI reason and port key */ | |
set_flag = TRUE; /* reissue host interrupt */ | |
mpx_uien = FALSE; /* disable UI */ | |
tprintf (mpx_dev, DEB_CMDS, "Port %d key %d unsolicited interrupt reissued, reason = %d\n", | |
mpx_port, mpx_portkey, GET_UIREASON (mpx_uicode)); | |
} | |
} | |
else { /* no unacknowledged UI */ | |
for (i = 0; i < MPX_PORTS; i++) { /* check all ports for UIs */ | |
if (mpx_flags [i] & FL_UI_PENDING) { /* pending UI? */ | |
mpx_portkey = mpx_key [i]; /* get port key */ | |
if (mpx_portkey == KEY_DEFAULT) { /* key defined? */ | |
if (mpx_flags [i] & FL_HAVEBUF) /* no, is this read buffer avail? */ | |
buf_cancel (ioread, i, get); /* cancel buffer */ | |
mpx_flags [i] &= ~FL_UI_PENDING; /* cancel pending UI */ | |
} | |
else if (mpx_uien == TRUE) { /* interrupts enabled? */ | |
if ((mpx_flags [i] & FL_WANTBUF) && /* port wants a write buffer? */ | |
(buf_avail (iowrite, i) > 0)) /* and one is available? */ | |
mpx_uicode = UI_WRBUF_AVAIL; /* set UI reason */ | |
else if (mpx_flags [i] & FL_BREAK) /* received a line BREAK? */ | |
mpx_uicode = UI_BRK_RECD; /* set UI reason */ | |
else if (mpx_flags [i] & FL_HAVEBUF) /* have a read buffer ready? */ | |
mpx_uicode = UI_RDBUF_AVAIL; /* set UI reason */ | |
if (mpx_uicode) { /* UI to send? */ | |
mpx_port = i; /* set port number for Acknowledge */ | |
mpx_ibuf = (uint16) (mpx_uicode | mpx_portkey); /* merge UI reason and port key */ | |
mpx_uicode = mpx_uicode | mpx_port; /* save UI reason and port */ | |
set_flag = TRUE; /* interrupt host */ | |
mpx_uien = FALSE; /* disable UI */ | |
tprintf (mpx_dev, DEB_CMDS, "Port %d key %d unsolicited interrupt generated, reason = %d\n", | |
i, mpx_portkey, GET_UIREASON (mpx_uicode)); | |
break; /* quit after first UI */ | |
} | |
} | |
} | |
} | |
} | |
break; | |
case cmd: /* command state */ | |
if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ | |
mpx_state = param; /* look for parameter before executing */ | |
else | |
set_flag = exec_command (); /* execute one-word command */ | |
break; | |
case param: /* parameter get state */ | |
mpx_param = mpx_obuf; /* save parameter */ | |
set_flag = exec_command (); /* execute two-word command */ | |
break; | |
case exec: /* execution state */ | |
switch (mpx_cmd) { | |
case CMD_BINARY_READ: /* fast binary read */ | |
mpx_flags [0] &= ~FL_HAVEBUF; /* data word was picked up by CPU */ | |
set_flag = FALSE; /* suppress device flag */ | |
break; | |
case CMD_WRITE: /* transfer data to buffer */ | |
if (mpx_iolen <= 0) { /* last (or only) entry? */ | |
mpx_state = idle; /* idle controller */ | |
if (mpx_iolen < 0) /* tie-off for buffer complete? */ | |
break; /* we're done */ | |
} | |
add_crlf = ((mpx_param & /* CRLF should be added */ | |
(WR_ADD_CRLF | WR_PARTIAL)) == WR_ADD_CRLF); | |
for (i = 0; i < 2; i++) /* output one or two chars */ | |
if (mpx_iolen > 0) { /* more to do? */ | |
if (i) /* high or low byte? */ | |
ch = (uint8) (mpx_obuf & 0377); /* low byte */ | |
else | |
ch = mpx_obuf >> 8; /* high byte */ | |
if ((mpx_iolen == 1) && /* final char? */ | |
(ch == '_') && add_crlf) { /* underscore and asking for CRLF? */ | |
add_crlf = FALSE; /* suppress CRLF */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d character '_' suppressed CR/LF\n", mpx_port); | |
} | |
else if (buf_len (iowrite, mpx_port, put) < WR_BUF_LIMIT) | |
buf_put (iowrite, mpx_port, ch); /* add char to buffer if space avail */ | |
mpx_iolen = mpx_iolen - 1; /* drop remaining count */ | |
} | |
if (mpx_iolen == 0) { /* buffer done? */ | |
if (add_crlf) { /* want CRLF? */ | |
buf_put (iowrite, mpx_port, CR); /* add CR to buffer */ | |
buf_put (iowrite, mpx_port, LF); /* add LF to buffer */ | |
} | |
buf_term (iowrite, mpx_port, (uint8) (mpx_param >> 8)); /* terminate buffer */ | |
mpx_iolen = -1; /* mark as done */ | |
} | |
if (sim_is_active (&mpx_unit [mpx_port]) == 0) | |
tprintf (mpx_dev, DEB_CMDS, "Port %d service scheduled, time = %d\n", | |
mpx_port, mpx_unit [mpx_port].wait); | |
sim_activate (&mpx_unit [mpx_port], /* start line service */ | |
mpx_unit [mpx_port].wait); | |
break; | |
case CMD_READ: /* transfer data from buffer */ | |
if (mpx_iolen < 0) { /* input complete? */ | |
if (mpx_obuf == 0177777) { /* "tie-off" word received? */ | |
if (buf_len (ioread, mpx_port, get) == 0) { /* buffer now empty? */ | |
buf_free (ioread, mpx_port); /* free buffer */ | |
if ((buf_avail (ioread, mpx_port) == 1) && /* one buffer remaining? */ | |
!(mpx_flags [mpx_port] & FL_RDFILL)) /* and not filling it? */ | |
mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate buffer availability */ | |
} | |
mpx_state = idle; /* idle controller */ | |
} | |
else | |
set_flag = FALSE; /* ignore word */ | |
break; | |
} | |
for (i = 0; i < 2; i++) /* input one or two chars */ | |
if (mpx_iolen > 0) { /* more to transfer? */ | |
if (buf_len (ioread, mpx_port, get) > 0) /* more chars available? */ | |
ch = buf_get (ioread, mpx_port); /* get char from buffer */ | |
else /* buffer exhausted */ | |
ch = ' '; /* pad with blank */ | |
if (i) /* high or low byte? */ | |
mpx_ibuf = mpx_ibuf | ch; /* low byte */ | |
else | |
mpx_ibuf = (uint16) (ch << 8); /* high byte */ | |
mpx_iolen = mpx_iolen - 1; /* drop count */ | |
} | |
else /* odd number of chars */ | |
mpx_ibuf = mpx_ibuf | ' '; /* pad last with blank */ | |
if (mpx_iolen == 0) /* end of host xfer? */ | |
mpx_iolen = -1; /* mark as done */ | |
break; | |
case CMD_DL_EXEC: /* sink data from host */ | |
if (mpx_iolen <= 0) { /* final entry? */ | |
mpx_state = idle; /* idle controller */ | |
mpx_ibuf = ST_DIAG_OK; /* return diag passed status */ | |
} | |
else { | |
if (mpx_iolen > 0) /* more from host? */ | |
mpx_iolen = mpx_iolen - 2; /* sink two bytes */ | |
if (mpx_iolen <= 0) /* finished download? */ | |
sim_activate (&mpx_cntl, CMD_DELAY); /* schedule completion */ | |
tprintf (mpx_dev, DEB_CMDS, "Download completion scheduled, time = %d\n", CMD_DELAY); | |
} | |
break; | |
default: /* no other entries allowed */ | |
return SCPE_IERR; /* simulator error! */ | |
} | |
break; | |
} | |
if (TRACING (mpx_dev, DEB_CMDS) /* debug print? */ | |
&& last_state != mpx_state) /* and state change? */ | |
if ((mpx_cmd & CMD_TWO_WORDS) && (mpx_state != param)) | |
tprintf (mpx_dev, DEB_CMDS, "Command %03o parameter %06o %s", | |
mpx_cmd, mpx_param, cmd_state [mpx_state]); | |
else | |
tprintf (mpx_dev, DEB_CMDS, "Command %03o %s", | |
mpx_cmd, cmd_state [mpx_state]); | |
if (set_flag) { | |
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ | |
tprintf (mpx_dev, DEB_CMDS, "Flag set\n"); | |
} | |
return SCPE_OK; | |
} | |
/* Multiplexer line service. | |
The line service routine is used to transmit and receive characters. It is | |
started when a buffer is ready for output or when the poll service routine | |
determines that there are characters ready for input, and it is stopped when | |
there are no more characters to output or input. When a line is quiescent, | |
this routine does not run. Service times are selected to approximate the | |
baud rate setting of the multiplexer port. | |
"Fast timing" mode enables three optimizations. First, buffered characters | |
are transferred in blocks, rather than a character at a time; this reduces | |
line traffic and decreases simulator overhead (there is only one service | |
routine entry per block, rather than one per character). Second, ENQ/ACK | |
handshaking is done locally, without involving the client. Third, when | |
editing and echo is enabled, entering BS echoes a backspace, a space, and a | |
backspace, and entering DEL echoes a backslash, a carriage return, and a line | |
feed, providing better compatibility with prior RTE terminal drivers. | |
Each read and write buffer begins with a reserved header byte that stores | |
per-buffer information, such as whether handshaking should be suppressed | |
during output, or the specific cause of termination for input. Buffer | |
termination sets the header byte with the appropriate flags. | |
For output, a character counter is maintained and is incremented if ENQ/ACK | |
handshaking is enabled for the current port and request. If the counter | |
limit is reached, an ENQ is sent, and a flag is set to suspend transmission | |
until an ACK is received. If the last character of the buffer is sent, the | |
write buffer is freed, and a UI check is made if the controller is idle, in | |
case a write buffer request is pending. | |
For input, the character is retrieved from the line buffer. If a BREAK was | |
received, break status is set, and the character is discarded (the current | |
multiplexer library implementation always returns a NUL with a BREAK | |
indication). If the character is an XOFF, and XON/XOFF pacing is enabled, a | |
flag is set, and transmission is suspended until a corresponding XON is | |
received. If the character is an ACK and is in response to a previously sent | |
ENQ, it is discarded, and transmission is reenabled. | |
If editing is enabled, a BS will delete the last character in the read | |
buffer, and a DEL will delete the entire buffer. Otherwise, buffer | |
termination conditions are checked (end on character, end on count, or | |
buffer full), and if observed, the read buffer is terminated, and a read | |
buffer available UI condition is signalled. | |
Implementation notes: | |
1. The firmware echoes an entered BS before checking the buffer count to see | |
if there are any characters to delete. Under simulation, we only echo if | |
the buffer is not empty. | |
2. The "Fast binary read" command inhibits the normal transmit and receive | |
processing. Instead, a pair of characters are sought on line 0 to fill | |
the input buffer. When they are received, the device flag is set. The | |
CPU will do a LIx sc,C to retrieve the data and reset the flag. | |
3. In fast timing mode, burst transfers are used only to fill the first of | |
the two receive buffers; the second is filled with one character per | |
service entry. This allows the CPU time to unload the first buffer | |
before the second fills up. Once the first buffer is freed, the routine | |
shifts back to burst mode to fill the remainder of the second buffer. | |
4. The terminal multiplexer library "tmxr_putc_ln" routine returns | |
SCPE_STALL if it is called when the transmit buffer is full. When the | |
last character is added to the buffer, the routine returns SCPE_OK but | |
also changes the "xmte" field of the terminal multiplexer line (TMLN) | |
structure from 1 to 0 to indicate that further calls will be rejected. | |
The "xmte" value is set back to 1 when the tranmit buffer empties. | |
This presents two approaches to handling buffer overflows: either call | |
"tmxr_putc_ln" unconditionally and test for SCPE_STALL on return, or call | |
"tmxr_putc_ln" only if "xmte" is 1. The former approach adds a new | |
character to the transmit buffer as soon as space is available, while the | |
latter adds a new character only when the buffer has completely emptied. | |
With either approach, transmission must be rescheduled after a delay to | |
allow the buffer to drain. | |
It would seem that the former approach is more attractive, as it would | |
allow the simulated I/O operation to complete more quickly. However, | |
there are two mitigating factors. First, the library attempts to write | |
the entire transmit buffer in one host system call, so there is usually | |
no time difference between freeing one buffer character and freeing the | |
entire buffer (barring host system buffer congestion). Second, the | |
routine increments a "character dropped" counter when returning | |
SCPE_STALL status. However, the characters actually would not be lost, | |
as the SCPE_STALL return would schedule retransmission when buffer space | |
is available, . This would lead to erroneous reporting in the SHOW | |
<unit> STATISTICS command. | |
Therefore, we adopt the latter approach and reschedule transmission if | |
the "xmte" field is 0. Note that the "tmxr_poll_tx" routine still must | |
be called in this case, as it is responsible for transmitting the buffer | |
contents and therefore freeing space in the buffer. | |
5. The "tmxr_putc_ln" library routine returns SCPE_LOST if the line is not | |
connected. We ignore this error so that an OS may output an | |
initialization "welcome" message even when the terminal is not connected. | |
This permits the simulation to continue while ignoring the output. | |
6. The serial transmit buffer provided by the terminal multiplexer library | |
is restricted to one character. Therefore, attempting to send several | |
characters in response to input, e.g., echoing "<BS> <space> <BS>" in | |
response to receiving a <BS>, will fail with SCPE_STALL. Calling | |
"tmxr_poll_tx" between characters will not clear the buffer if the line | |
speed has been set explicitly. | |
To avoid having to do our own buffering for echoed characters, we call | |
the "tmxr_linemsg" routine which loops internally until the characters | |
have been transmitted. This is ugly but is a consequence of the buffer | |
restriction imposed by the TMXR library. | |
7. Because ENQ/ACK handshaking is handled entirely on the multiplexer card | |
with no OS involvement, FASTTIME "local handling" consists simply of | |
omitting the handshake even if it is configured by the multiplexer. | |
*/ | |
static t_stat line_service (UNIT *uptr) | |
{ | |
const int32 port = uptr - mpx_unit; /* port number */ | |
const uint16 rt = mpx_rcvtype [port]; /* receive type for port */ | |
const uint32 data_bits = 5 + GET_BPC (mpx_config [port]); /* number of data bits */ | |
const uint32 data_mask = (1 << data_bits) - 1; /* mask for data bits */ | |
const t_bool fast_timing = (uptr->flags & UNIT_FASTTIME) != 0; /* port is set for fast timing */ | |
const t_bool fast_binary_read = (mpx_cmd == CMD_BINARY_READ); /* fast binary read in progress */ | |
uint8 ch; | |
int32 chx; | |
uint32 buffer_count, write_count; | |
t_stat status = SCPE_OK; | |
t_bool recv_loop = !fast_binary_read; /* bypass if fast binary read */ | |
t_bool xmit_loop = !(fast_binary_read /* bypass if fast read */ | |
|| mpx_flags [port] & (FL_WAITACK | FL_XOFF) /* or output suspended */ | |
|| mpx_ldsc [port].xmte == 0); /* or buffer full */ | |
tprintf (mpx_dev, DEB_CMDS, "Port %d service entered\n", port); | |
/* Transmission service */ | |
if (mpx_ldsc [port].xmte == 0) /* if the transmit buffer is full */ | |
tprintf (mpx_dev, DEB_XFER, "Port %d transmission stalled for full buffer\n", | |
port); | |
write_count = buf_len (iowrite, port, get); /* get the output buffer length */ | |
while (xmit_loop && write_count > 0) { /* character available to output? */ | |
if ((mpx_flags [port] & FL_WREMPT) == 0) { /* if the buffer has not started emptying */ | |
chx = buf_get (iowrite, port) << 8; /* then get the header value and position it */ | |
if (fast_timing || (chx & WR_NO_ENQACK) || /* do we want handshake? */ | |
!(mpx_config [port] & SK_ENQACK)) /* and configured for handshake? */ | |
mpx_flags [port] &= ~FL_DO_ENQACK; /* no, so clear flag */ | |
else | |
mpx_flags [port] |= FL_DO_ENQACK; /* yes, so set flag */ | |
continue; /* continue with the first output character */ | |
} | |
if (mpx_enq_cntr [port] >= ENQ_LIMIT) { /* ready for ENQ? */ | |
ch = ENQ; | |
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit ENQ */ | |
if (status == SCPE_OK || status == SCPE_LOST) { /* if transmission succeeded or is ignored */ | |
mpx_enq_cntr [port] = 0; /* then clear the ENQ counter */ | |
mpx_ack_wait [port] = 0; /* and the ACK wait timer */ | |
mpx_flags [port] |= FL_WAITACK; /* set wait for ACK */ | |
} | |
xmit_loop = FALSE; /* stop further transmission */ | |
} | |
else { /* not ready for ENQ */ | |
ch = buf_get (iowrite, port) & data_mask; /* get char and mask to bit width */ | |
status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */ | |
if (status == SCPE_OK || status == SCPE_LOST) { /* if transmission succeeded or is ignored */ | |
write_count = write_count - 1; /* then count the character */ | |
xmit_loop = (fast_timing /* continue transmission if enabled */ | |
&& mpx_ldsc [port].xmte != 0); /* and buffer space is available */ | |
if (mpx_flags [port] & FL_DO_ENQACK) /* if ENQ/ACK handshaking is enabled */ | |
mpx_enq_cntr [port] += 1; /* then bump the character counter */ | |
} | |
else /* otherwise transmission failed */ | |
xmit_loop = FALSE; /* so exit the loop */ | |
} | |
if (status == SCPE_OK) | |
tprintf (mpx_dev, DEB_XFER, "Port %d character %s transmitted\n", | |
port, fmt_char (ch)); | |
else { | |
tprintf (mpx_dev, DEB_XFER, "Port %d character %s transmission failed with status %d\n", | |
port, fmt_char (ch), status); | |
if (status == SCPE_LOST) /* if the line is not connected */ | |
status = SCPE_OK; /* then ignore the output */ | |
} | |
if (write_count == 0) { /* buffer complete? */ | |
buf_free (iowrite, port); /* free buffer */ | |
write_count = buf_len (iowrite, port, get); /* get the next output buffer length */ | |
if (mpx_state == idle) /* controller idle? */ | |
cntl_service (&mpx_cntl); /* check for UI */ | |
} | |
} | |
/* Reception service */ | |
buffer_count = buf_avail (ioread, port); /* get the number of available read buffers */ | |
if (mpx_flags [port] & FL_RDFILL) /* if filling the current buffer */ | |
buffer_count = buffer_count + 1; /* then include it in the count */ | |
while (recv_loop) { /* OK to process? */ | |
chx = tmxr_getc_ln (&mpx_ldsc [port]); /* get a new character */ | |
if (chx == 0) /* if there are no more characters available */ | |
break; /* then quit the reception loop */ | |
if (chx & SCPE_BREAK) { /* break detected? */ | |
mpx_flags [port] |= FL_BREAK; /* set break status */ | |
tprintf (mpx_dev, DEB_XFER, "Break detected\n"); | |
if (mpx_state == idle) /* controller idle? */ | |
cntl_service (&mpx_cntl); /* check for UI */ | |
continue; /* discard NUL that accompanied BREAK */ | |
} | |
ch = (uint8) (chx & data_mask); /* mask to bits per char */ | |
if ((ch == XOFF) && /* XOFF? */ | |
(mpx_flowcntl [port] & FC_XONXOFF)) { /* and handshaking enabled? */ | |
mpx_flags [port] |= FL_XOFF; /* suspend transmission */ | |
tprintf (mpx_dev, DEB_XFER, "Port %d character XOFF suspends transmission\n", port); | |
recv_loop = fast_timing; /* set to loop if fast mode */ | |
continue; | |
} | |
else if ((ch == XON) && /* XON? */ | |
(mpx_flags [port] & FL_XOFF)) { /* and currently suspended? */ | |
mpx_flags [port] &= ~FL_XOFF; /* resume transmission */ | |
tprintf (mpx_dev, DEB_XFER, "Port %d character XON resumes transmission\n", port); | |
recv_loop = fast_timing; /* set to loop if fast mode */ | |
continue; | |
} | |
tprintf (mpx_dev, DEB_XFER, "Port %d character %s received\n", | |
port, fmt_char (ch)); | |
if ((ch == ACK) && (mpx_flags [port] & FL_WAITACK)) { /* ACK and waiting for it? */ | |
mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* clear wait flag */ | |
recv_loop = FALSE; /* absorb character */ | |
} | |
else if (buffer_count == 0 && /* no free buffer available for char? */ | |
!(mpx_flags [port] & FL_RDFILL)) { /* and not filling last buffer? */ | |
mpx_flags [port] |= FL_RDOVFLOW; /* set buffer overflow flag */ | |
recv_loop = fast_timing; /* continue loop if fast mode */ | |
} | |
else { /* buffer is available */ | |
if (rt & RT_ENAB_EDIT) /* editing enabled? */ | |
if (ch == BS) { /* backspace? */ | |
if (buf_len (ioread, port, put) > 0) /* at least one character in buffer? */ | |
buf_remove (ioread, port); /* remove last char */ | |
if (rt & RT_ENAB_ECHO) { /* echo enabled? */ | |
tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ | |
if (fast_timing) /* fast timing mode? */ | |
tmxr_linemsg (&mpx_ldsc [port], " \b"); /* echo space and BS */ | |
} | |
continue; | |
} | |
else if (ch == DEL) { /* delete line? */ | |
buf_cancel (ioread, port, put); /* cancel put buffer */ | |
if (rt & RT_ENAB_ECHO) { /* echo enabled? */ | |
if (fast_timing) /* fast timing mode? */ | |
tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* echo backslash */ | |
tmxr_linemsg (&mpx_ldsc [port], "\r\n"); /* echo CR and LF */ | |
} | |
continue; | |
} | |
if (uptr->flags & UNIT_CAPSLOCK) /* caps lock mode? */ | |
ch = (uint8) toupper (ch); /* convert to upper case if lower */ | |
if (rt & RT_ENAB_ECHO) /* echo enabled? */ | |
tmxr_putc_ln (&mpx_ldsc [port], ch); /* echo the char */ | |
if (rt & RT_END_ON_CHAR) { /* end on character? */ | |
recv_loop = FALSE; /* assume termination */ | |
if ((ch == CR) && (rt & RT_END_ON_CR)) { | |
if (rt & RT_ENAB_ECHO) /* echo enabled? */ | |
tmxr_linemsg (&mpx_ldsc [port], "\n"); /* send LF */ | |
mpx_param = RS_ETC_CR; /* set termination condition */ | |
} | |
else if ((ch == RS) && (rt & RT_END_ON_RS)) | |
mpx_param = RS_ETC_RS; /* set termination condition */ | |
else if ((ch == EOT) && (rt & RT_END_ON_EOT)) | |
mpx_param = RS_ETC_EOT; /* set termination condition */ | |
else if ((ch == DC2) && (rt & RT_END_ON_DC2)) | |
mpx_param = RS_ETC_DC2; /* set termination condition */ | |
else | |
recv_loop = TRUE; /* no termination */ | |
} | |
if (recv_loop) { /* no termination condition? */ | |
buf_put (ioread, port, ch); /* put character in buffer */ | |
mpx_charcnt [port]++; /* and count it */ | |
} | |
if ((rt & RT_END_ON_CNT) && /* end on count */ | |
(mpx_charcnt [port] == mpx_termcnt [port])) { /* and termination count reached? */ | |
recv_loop = FALSE; /* set termination */ | |
mpx_param = 0; /* no extra termination info */ | |
mpx_charcnt [port] = 0; /* clear the current character count */ | |
if (mpx_flags [port] & FL_ALERT) { /* was this alert for term rcv buffer? */ | |
mpx_flags [port] &= ~FL_ALERT; /* clear alert flag */ | |
mpx_termcnt [port] = RD_BUF_LIMIT; /* reset termination character count */ | |
} | |
} | |
else if (buf_len (ioread, port, put) == RD_BUF_LIMIT) { /* buffer now full? */ | |
recv_loop = FALSE; /* set termination */ | |
mpx_param = mpx_param | RS_PARTIAL; /* and partial buffer flag */ | |
} | |
if (recv_loop) /* if there is no termination condition */ | |
if (buffer_count == 2) /* then if we're filling the first buffer */ | |
recv_loop = fast_timing; /* then set to loop if in fast mode */ | |
else /* otherwise we're filling the second */ | |
recv_loop = FALSE; /* so give the CPU a chance to read the first */ | |
else { /* otherwise a termination condition exists */ | |
if (mpx_param & RS_PARTIAL) | |
tprintf (mpx_dev, DEB_XFER, "Port %d read terminated on buffer full\n", | |
port); | |
else if (rt & RT_END_ON_CHAR) | |
tprintf (mpx_dev, DEB_XFER, "Port %d read terminated on character %s\n", | |
port, fmt_char (ch)); | |
else | |
tprintf (mpx_dev, DEB_XFER, "Port %d read terminated on count = %d\n", | |
port, mpx_termcnt [port]); | |
if (buf_len (ioread, port, put) == 0) { /* zero-length read? */ | |
buf_put (ioread, port, 0); /* dummy put to reserve header */ | |
buf_remove (ioread, port); /* back out dummy char leaving header */ | |
} | |
buf_term (ioread, port, (uint8) (mpx_param >> 8)); /* terminate buffer and set header */ | |
if (buf_avail (ioread, port) == 1) /* first read buffer? */ | |
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ | |
if (mpx_state == idle) /* controller idle? */ | |
cntl_service (&mpx_cntl); /* check for UI */ | |
} | |
} | |
} | |
/* Housekeeping */ | |
if (fast_binary_read) { /* fast binary read in progress? */ | |
if (port == 0) { /* on port 0? */ | |
chx = tmxr_getc_ln (&mpx_ldsc [0]); /* see if a character is ready */ | |
if (chx && !(mpx_flags [0] & FL_HAVEBUF)) { /* character ready and buffer empty? */ | |
if (mpx_flags [0] & FL_WANTBUF) { /* second character? */ | |
mpx_ibuf = mpx_ibuf | (chx & DMASK8); /* merge it into word */ | |
mpx_flags [0] |= FL_HAVEBUF; /* mark buffer as ready */ | |
mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ | |
tprintf (mpx_dev, DEB_CMDS, "Flag and SRQ set\n"); | |
} | |
else /* first character */ | |
mpx_ibuf = (uint16) ((chx & DMASK8) << 8); /* put in top half of word */ | |
mpx_flags [0] ^= FL_WANTBUF; /* toggle byte flag */ | |
} | |
sim_activate (uptr, uptr->wait); /* reschedule service for fast response */ | |
} | |
} | |
else { /* normal service */ | |
tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */ | |
if (write_count > 0 /* if there are more characters to transmit */ | |
&& !(mpx_flags [port] & (FL_WAITACK | FL_XOFF)) /* and transmission is not suspended */ | |
|| tmxr_rqln (&mpx_ldsc [port])) { /* or there are more characters to receive */ | |
sim_activate (uptr, uptr->wait); /* then reschedule the service */ | |
tprintf (mpx_dev, DEB_CMDS, "Port %d delay %d service rescheduled\n", port, uptr->wait); | |
} | |
else | |
tprintf (mpx_dev, DEB_CMDS, "Port %d service stopped\n", port); | |
} | |
return status; | |
} | |
/* Poll service. | |
This service routine is used to poll for connections and incoming characters. | |
It is started when the listening socket or a serial line is attached and is | |
stopped when the socket and all lines are detached. | |
Each line is then checked for a pending ENQ/ACK handshake. If one is | |
pending, the ACK counter is incremented, and if it times out, another ENQ is | |
sent to avoid stalls. Lines are also checked for available characters, and | |
the corresponding line I/O service routine is scheduled if needed. | |
*/ | |
static t_stat poll_service (UNIT *uptr) | |
{ | |
uint32 i; | |
t_stat status = SCPE_OK; | |
poll_connection (); /* check for new connection */ | |
tmxr_poll_rx (&mpx_desc); /* poll for input */ | |
for (i = 0; i < MPX_PORTS; i++) { /* check lines */ | |
if (mpx_flags [i] & FL_WAITACK) { /* waiting for ACK? */ | |
mpx_ack_wait [i] = mpx_ack_wait [i] + 1; /* increment ACK wait timer */ | |
if (mpx_ack_wait [i] > ACK_LIMIT) { /* has wait timed out? */ | |
mpx_ack_wait [i] = 0; /* reset counter */ | |
status = tmxr_putc_ln (&mpx_ldsc [i], ENQ); /* send ENQ again */ | |
tmxr_poll_tx (&mpx_desc); /* transmit it */ | |
if (status == SCPE_OK) /* transmitted OK? */ | |
tprintf (mpx_dev, DEB_XFER, "Port %d character ENQ retransmitted\n", i); | |
} | |
} | |
if (tmxr_rqln (&mpx_ldsc [i])) /* chars available? */ | |
sim_activate (&mpx_unit [i], mpx_unit [i].wait); /* activate I/O service */ | |
} | |
if (uptr->wait == POLL_FIRST) /* first poll? */ | |
uptr->wait = sync_poll (INITIAL); /* initial synchronization */ | |
else /* not first */ | |
uptr->wait = sync_poll (SERVICE); /* continue synchronization */ | |
sim_activate (uptr, uptr->wait); /* continue polling */ | |
return SCPE_OK; | |
} | |
/* Device reset routine. | |
The hardware CRS signal generates a reset signal to the Z80 and its | |
peripherals. This causes execution of the power up initialization code. | |
The CRS signal also has these hardware effects: | |
- clears control | |
- clears flag | |
- clears flag buffer | |
- clears backplane ready | |
- clears the output buffer register | |
Implementation notes: | |
1. Under simulation, we also clear the input buffer register, even though | |
the hardware doesn't. | |
2. We set up the first poll for connections to occur "immediately" upon | |
execution, so that clients will be connected before execution begins. | |
Otherwise, a fast program may access the multiplexer before the poll | |
service routine activates. | |
3. We must set the "emptying_flags" and "filling_flags" values here, because | |
they cannot be initialized statically, even though the values are | |
constant. | |
*/ | |
static t_stat mpx_reset (DEVICE *dptr) | |
{ | |
if (sim_switches & SWMASK ('P')) { /* power-on reset? */ | |
emptying_flags [ioread] = FL_RDEMPT; /* initialize buffer flags constants */ | |
emptying_flags [iowrite] = FL_WREMPT; | |
filling_flags [ioread] = FL_RDFILL; | |
filling_flags [iowrite] = FL_WRFILL; | |
} | |
IOPRESET (&mpx_dib); /* PRESET device (does not use PON) */ | |
mpx_ibuf = 0; /* clear input buffer */ | |
if (mpx_poll.flags & UNIT_ATT) { /* network attached? */ | |
mpx_poll.wait = POLL_FIRST; /* set up poll */ | |
sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ | |
} | |
else | |
sim_cancel (&mpx_poll); /* else stop poll */ | |
return SCPE_OK; | |
} | |
/* Attach the multiplexer to a Telnet port. | |
We are called by the ATTACH MPX <port> command to attach the multiplexer to | |
the listening port indicated by <port>. Logically, it is the multiplexer | |
device that is attached; however, SIMH only allows units to be attached. This | |
makes sense for devices such as tape drives, where the attached media is a | |
property of a specific drive. In our case, though, the listening port is a | |
property of the multiplexer card, not of any given line. As ATTACH MPX is | |
equivalent to ATTACH MPX0, the port would, by default, be attached to the | |
first line and be reported there in a SHOW MPX command. | |
To preserve the logical picture, we attach the listening port to the poll | |
unit (unit 9), which is normally disabled to inhibit its display. Serial | |
ports are attached to line units 0-7 normally. Attachment is reported by the | |
"show_status" routine below. | |
The connection poll service routine is synchronized with the other input | |
polling devices in the simulator to facilitate idling. | |
Implementation notes: | |
1. If we are being called as part of RESTORE processing, we may see a | |
request to attach the poll unit (unit 9). This will occur if unit 9 was | |
attached when the SAVE was done. In this case, the SIM_SW_REST flag will | |
be set in "sim_switches", and we will allow the call to succeed. | |
2. If the poll unit is attached, it will be enabled as part of RESTORE | |
processing. We always unilaterally disable this unit to ensure that it | |
remains hidden. | |
*/ | |
static t_stat mpx_attach (UNIT *uptr, CONST char *cptr) | |
{ | |
t_stat status = SCPE_OK; | |
if (uptr != mpx_unit /* not unit 0? */ | |
&& (uptr != &mpx_poll || !(sim_switches & SIM_SW_REST))) /* and not restoring the poll unit? */ | |
return SCPE_NOATT; /* can't attach */ | |
mpx_poll.flags = mpx_poll.flags & ~UNIT_DIS; /* enable unit */ | |
status = tmxr_attach (&mpx_desc, &mpx_poll, cptr); /* attach to socket */ | |
mpx_poll.flags = mpx_poll.flags | UNIT_DIS; /* disable unit */ | |
if (status == SCPE_OK) { | |
mpx_poll.wait = POLL_FIRST; /* set up poll */ | |
sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ | |
} | |
return status; | |
} | |
/* Detach the multiplexer. | |
We are called by the DETACH MPX command to detach the listening port and all | |
Telnet sessions. We will also be called by DETACH ALL, RESTORE, and during | |
simulator shutdown. For DETACH ALL and RESTORE, we must not fail the call, | |
or processing of other units will cease. | |
Implementation notes: | |
1. During simulator shutdown, we will be called for units 0-8 (detach_all in | |
scp.c calls the detach routines of all units that do NOT have | |
UNIT_ATTABLE), as well as for unit 9 if it is attached. | |
2. We cannot fail a direct DETACH MPX9 (poll unit), because we cannot tell | |
that case apart from a DETACH ALL (a RESTORE will have the SIM_SW_REST | |
flag set in "sim_switches"). | |
*/ | |
static t_stat mpx_detach (UNIT *uptr) | |
{ | |
t_stat status = SCPE_OK; | |
int32 i; | |
if ((uptr == mpx_unit) || (uptr == &mpx_poll)) { /* base unit or poll unit? */ | |
status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */ | |
for (i = 0; i < MPX_PORTS; i++) { | |
mpx_ldsc [i].rcve = 0; /* disable line reception */ | |
sim_cancel (&mpx_unit [i]); /* cancel any scheduled I/O */ | |
} | |
sim_cancel (&mpx_poll); /* stop poll */ | |
} | |
return status; | |
} | |
/* Set firmware revision. | |
Currently, we support only revision C, so the MTAB entry does not have an | |
"mstring" entry. When we add revision D support, an "mstring" entry of "REV" | |
will enable changing the firmware revision. | |
*/ | |
static t_stat set_revision (UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
if ((cptr == NULL) || /* no parameter? */ | |
(*cptr < 'C') || (*cptr > 'D') || /* or not C or D? */ | |
(*(cptr + 1) != '\0')) /* or not just one character? */ | |
return SCPE_ARG; /* bad argument */ | |
else { | |
if (*cptr == 'C') /* setting revision C? */ | |
mpx_dev.flags = mpx_dev.flags & ~DEV_REV_D; /* clear 'D' flag */ | |
else if (*cptr == 'D') /* setting revision D? */ | |
mpx_dev.flags = mpx_dev.flags | DEV_REV_D; /* set 'D' flag */ | |
return SCPE_OK; | |
} | |
} | |
/* Show firmware revision */ | |
static t_stat show_revision (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
if (mpx_dev.flags & DEV_REV_D) | |
fputs ("12792D", st); | |
else | |
fputs ("12792C", st); | |
return SCPE_OK; | |
} | |
/* Show multiplexer status */ | |
static t_stat show_status (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
if (mpx_poll.flags & UNIT_ATT) /* attached to socket? */ | |
fprintf (st, "attached to port %s, ", mpx_poll.filename); | |
else | |
fprintf (st, "not attached, "); | |
tmxr_show_summ (st, uptr, val, desc); /* report connection count */ | |
return SCPE_OK; | |
} | |
/* Multiplexer local utility routines */ | |
/* Command executor. | |
We are called by the controller service routine to process one- and two-word | |
commands. For two-word commands, the parameter word is present in mpx_param. | |
The return value indicates whether the card flag should be set upon | |
completion. | |
Most commands execute and complete directly. The read and write commands, | |
however, transition to the execution state to simulate the DMA transfer, and | |
the "Download executable" command does the same to receive the download from | |
the CPU. | |
Several commands were added for the B firmware revision, and the various | |
revisions of the RTE drivers sent some commands that were never implemented | |
in the mux firmware. The command protocol treated unknown commands as NOPs, | |
meaning that the command (and parameter, if it was a two-word command) was | |
absorbed and the card flag was set as though the command completed normally. | |
This allowed interoperability between firmware and driver revisions. | |
Commands that refer to ports do so indirectly by passing a port key, rather | |
than a port number. The key-to-port translation is established by the "Set | |
port key" command. If a key is not found in the table, the command is not | |
executed, and the status return is ST_BAD_KEY, which in hex is "BAD0". | |
Implementation notes: | |
1. The "Reset to power-on defaults" command causes the firmware to disable | |
interrupts and jump to the power-on initialization routine, exactly as | |
though the Z80 had received a hardware reset. | |
2. The "Abort DMA transfer" command works because STC causes NMI, so the | |
command is executed even in the middle of a DMA transfer. The OTx of the | |
command will be sent to the buffer if a "Write data to buffer" command is | |
in progress, but the STC will cause this routine to be called, which will | |
cancel the buffer and return the controller to the idle state. Note that | |
this command might be sent with no transfer in progress, in which case | |
nothing is done. | |
3. In response to an "Enable unsolicited interrupts" command, the controller | |
service is scheduled to check for a pending UI. If one is found, the | |
first UI status word is placed in the input buffer, and an interrupt is | |
generated by setting the flag. This causes entry to the driver, which | |
issues an "Acknowledge" command to obtain the second status word. | |
It is possible, however, for the interrupt to be ignored. For example, | |
the driver may be waiting for a "write buffer available" UI when it is | |
called to begin a write to a different port. If the flag is set by | |
the UI after RTE has been entered, the interrupt will be held off, and | |
the STC sc,C instruction that begins the command sequence will clear the | |
flag, removing the interrupt entirely. In this case, the controller will | |
reissue the UI when the next "Enable unsolicited interrupts" command is | |
sent. | |
Note that the firmware reissues the same UI, rather than recomputing UIs | |
and potentially selecting a different one of higher priority. | |
4. The "Fast binary read" command apparently was intended to facilitate | |
booting from a 264x tape drive, although no boot loader ROM for the | |
multiplexer was ever released. It sends the fast binary read escape | |
sequence (ESC e) to the terminal and then packs each pair of characters | |
received into a word and sends it to the CPU, accompanied by the device | |
flag. | |
The multiplexer firmware disables interrupts and then manipulates the SIO | |
for port 0 directly. Significantly, it does no interpretation of the | |
incoming data and sits in an endless I/O loop, so the only way to exit | |
the command is to reset the card with a CRS (front panel PRESET or CLC 0 | |
instruction execution). Sending a command will not work; although the | |
NMI will interrupt the fast binary read, the NMI handler simply sets a | |
flag that is tested by the scheduler poll. Because the processor is in | |
an endless loop, control never returns to the scheduler, so the command | |
is never seen. | |
5. The "Terminate active receive buffer" behavior is a bit tricky. If the | |
read buffer has characters, the buffer is terminated as though a | |
"terminate on count" condition occurred. If the buffer is empty, | |
however, a "terminate on count = 1" condition is established. When a | |
character is received, the buffer is terminated, and the buffer | |
termination count is reset to 254. | |
*/ | |
static t_bool exec_command (void) | |
{ | |
int32 port; | |
uint32 svc_time; | |
t_bool set_flag = TRUE; /* flag is normally set on completion */ | |
STATE next_state = idle; /* command normally executes to completion */ | |
mpx_ibuf = ST_OK; /* return status is normally OK */ | |
switch (mpx_cmd) { | |
case CMD_NOP: /* no operation */ | |
break; /* just ignore */ | |
case CMD_RESET: /* reset firmware */ | |
controller_reset (); /* reset program variables */ | |
mpx_ibuf = ST_TEST_OK; /* return self-test OK code */ | |
break; | |
case CMD_ENABLE_UI: | |
mpx_uien = TRUE; /* enable unsolicited interrupts */ | |
sim_activate (&mpx_cntl, CMD_DELAY); /* and schedule controller for UI check */ | |
tprintf (mpx_dev, DEB_CMDS, "Controller status check scheduled, time = %d\n", CMD_DELAY); | |
set_flag = FALSE; /* do not set the flag at completion */ | |
break; | |
case CMD_DISABLE: | |
switch (mpx_portkey) { | |
case SUBCMD_UI: | |
mpx_uien = FALSE; /* disable unsolicited interrupts */ | |
break; | |
case SUBCMD_DMA: | |
if (mpx_flags [mpx_port] & FL_WRFILL) /* write buffer xfer in progress? */ | |
buf_cancel (iowrite, mpx_port, put); /* cancel it */ | |
else if (mpx_flags [mpx_port] & FL_RDEMPT) /* read buffer xfer in progress? */ | |
buf_cancel (ioread, mpx_port, get); /* cancel it */ | |
break; | |
} | |
break; | |
case CMD_ACK: /* acknowledge unsolicited interrupt */ | |
switch (mpx_uicode & UI_REASON_MASK) { | |
case UI_WRBUF_AVAIL: /* write buffer notification */ | |
mpx_flags [mpx_port] &= ~FL_WANTBUF; /* clear flag */ | |
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer available */ | |
break; | |
case UI_RDBUF_AVAIL: /* read buffer notification */ | |
mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */ | |
mpx_ibuf = (uint16) (buf_get (ioread, mpx_port) << 8 | /* get header value and position */ | |
buf_len (ioread, mpx_port, get)); /* and include buffer length */ | |
if (mpx_flags [mpx_port] & FL_RDOVFLOW) { /* did a buffer overflow? */ | |
mpx_ibuf = mpx_ibuf | RS_OVERFLOW; /* report it */ | |
mpx_flags [mpx_port] &= ~FL_RDOVFLOW; /* clear overflow flag */ | |
} | |
break; | |
case UI_BRK_RECD: /* break received */ | |
mpx_flags [mpx_port] &= ~FL_BREAK; /* clear flag */ | |
mpx_ibuf = 0; /* 2nd word is zero */ | |
break; | |
} | |
mpx_uicode = 0; /* clear notification code */ | |
break; | |
case CMD_CANCEL: /* cancel first read buffer */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
buf_cancel (ioread, port, get); /* cancel get buffer */ | |
if (buf_avail (ioread, port) == 2) /* if all buffers are now clear */ | |
mpx_charcnt [port] = 0; /* then clear the current character count */ | |
else if (!(mpx_flags [port] & FL_RDFILL)) /* otherwise if the other buffer is not filling */ | |
mpx_flags [port] |= FL_HAVEBUF; /* then indicate buffer availability */ | |
} | |
break; | |
case CMD_CANCEL_ALL: /* cancel all read buffers */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
buf_init (ioread, port); /* reinitialize read buffers */ | |
mpx_charcnt [port] = 0; /* and clear the current character count */ | |
} | |
break; | |
case CMD_BINARY_READ: /* fast binary read */ | |
for (port = 0; port < MPX_PORTS; port++) | |
sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */ | |
mpx_flags [0] = 0; /* clear port 0 state flags */ | |
mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */ | |
mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */ | |
tmxr_linemsg (&mpx_ldsc [0], "\033e"); /* send the fast binary read escape sequence to port 0 */ | |
tmxr_poll_tx (&mpx_desc); /* and flush the output */ | |
next_state = exec; /* set execution state */ | |
break; | |
case CMD_REQ_WRITE: /* request write buffer */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) /* port defined? */ | |
if (buf_avail (iowrite, port) > 0) /* is a buffer available? */ | |
mpx_ibuf = WR_BUF_LIMIT; /* report write buffer limit */ | |
else { | |
mpx_ibuf = 0; /* report none available */ | |
mpx_flags [port] |= FL_WANTBUF; /* set buffer request */ | |
} | |
break; | |
case CMD_WRITE: /* write to buffer */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
mpx_port = port; /* save port number */ | |
mpx_iolen = WR_LENGTH (mpx_param); /* save request length */ | |
next_state = exec; /* set execution state */ | |
} | |
break; | |
case CMD_SET_KEY: /* set port key and configuration */ | |
port = GET_PORT (mpx_param); /* get target port number */ | |
mpx_key [port] = (uint8) mpx_portkey; /* set port key */ | |
mpx_config [port] = (uint16) mpx_param; /* set port configuration word */ | |
svc_time = service_time (mpx_config [port]); /* get service time for baud rate */ | |
if (svc_time) /* want to change? */ | |
mpx_unit [port].wait = svc_time; /* set service time */ | |
mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */ | |
break; | |
case CMD_SET_RCV: /* set receive type */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) /* port defined? */ | |
mpx_rcvtype [port] = (uint16) mpx_param; /* save port receive type */ | |
break; | |
case CMD_SET_COUNT: /* set character count */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
mpx_termcnt [port] = (uint16) mpx_param; /* save port termination character count */ | |
mpx_charcnt [port] = 0; /* and clear the current character count */ | |
} | |
break; | |
case CMD_SET_FLOW: /* set flow control */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
mpx_flowcntl [port] = mpx_param & FC_XONXOFF; /* save port flow control */ | |
if (mpx_param & FC_FORCE_XON) /* force XON? */ | |
mpx_flags [port] &= ~FL_XOFF; /* resume transmission if suspended */ | |
} | |
break; | |
case CMD_READ: /* read from buffer */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) { /* port defined? */ | |
mpx_port = port; /* save port number */ | |
mpx_iolen = mpx_param; /* save request length */ | |
sim_activate (&mpx_cntl, DATA_DELAY); /* schedule the transfer */ | |
next_state = exec; /* set execution state */ | |
set_flag = FALSE; /* no flag until word ready */ | |
} | |
break; | |
case CMD_DL_EXEC: /* Download executable */ | |
mpx_iolen = mpx_param; /* save request length */ | |
next_state = exec; /* set execution state */ | |
break; | |
case CMD_CN_LINE: /* connect modem line */ | |
case CMD_DC_LINE: /* disconnect modem line */ | |
case CMD_LOOPBACK: /* enable/disable modem loopback */ | |
mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */ | |
break; | |
case CMD_GET_STATUS: /* get modem status */ | |
mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */ | |
break; | |
case CMD_TERM_BUF: /* terminate active receive buffer */ | |
port = key_to_port (mpx_portkey); /* get port */ | |
if (port >= 0) /* port defined? */ | |
if (buf_len (ioread, port, put) > 0) { /* any chars in buffer? */ | |
buf_term (ioread, port, 0); /* terminate buffer and set header */ | |
mpx_charcnt [port] = 0; /* then clear the current character count */ | |
if (buf_avail (ioread, port) == 1) /* first read buffer? */ | |
mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ | |
} | |
else { /* buffer is empty */ | |
mpx_termcnt [port] = 1; /* set to terminate on one char */ | |
mpx_flags [port] |= FL_ALERT; /* set alert flag */ | |
} | |
break; | |
case CMD_VCP_PUT: /* VCP put byte */ | |
case CMD_VCP_PUT_BUF: /* VCP put buffer */ | |
case CMD_VCP_GET: /* VCP get byte */ | |
case CMD_VCP_GET_BUF: /* VCP get buffer */ | |
case CMD_VCP_EXIT: /* Exit VCP mode */ | |
case CMD_VCP_ENTER: /* Enter VCP mode */ | |
default: /* unknown command */ | |
tprintf (mpx_dev, DEB_CMDS, "Unknown command %03o ignored\n", mpx_cmd); | |
} | |
mpx_state = next_state; | |
return set_flag; | |
} | |
/* Poll for new connections */ | |
static void poll_connection (void) | |
{ | |
int32 new_line; | |
new_line = tmxr_poll_conn (&mpx_desc); /* check for new connection */ | |
if (new_line >= 0) /* new connection established? */ | |
mpx_ldsc [new_line].rcve = 1; /* enable line to receive */ | |
return; | |
} | |
/* Controller reset. | |
This is the card microprocessor reset, not the simulator reset routine. It | |
simulates a power-on restart of the Z80 firmware. When it is called from the | |
simulator reset routine, that routine will take care of setting the card | |
flip-flops appropriately. | |
*/ | |
static void controller_reset (void) | |
{ | |
uint32 i; | |
mpx_state = idle; /* idle state */ | |
mpx_cmd = 0; /* clear command */ | |
mpx_param = 0; /* clear parameter */ | |
mpx_uien = FALSE; /* disable interrupts */ | |
for (i = 0; i < MPX_PORTS; i++) { /* clear per-line variables */ | |
buf_init (iowrite, i); /* initialize write buffers */ | |
buf_init (ioread, i); /* initialize read buffers */ | |
mpx_key [i] = KEY_DEFAULT; /* clear port key to default */ | |
if (i == 0) /* default port configurations */ | |
mpx_config [0] = SK_PWRUP_0; /* port 0 is separate from 1-7 */ | |
else | |
mpx_config [i] = (uint16) (SK_PWRUP_1 | i); | |
mpx_rcvtype [i] = RT_PWRUP; /* power on config for echoplex */ | |
mpx_charcnt [i] = 0; /* clear character count */ | |
mpx_termcnt [i] = 0; /* default termination character count */ | |
mpx_flowcntl [i] = 0; /* default flow control */ | |
mpx_flags [i] = 0; /* clear state flags */ | |
mpx_enq_cntr [i] = 0; /* clear ENQ counter */ | |
mpx_ack_wait [i] = 0; /* clear ACK wait timer */ | |
mpx_unit [i].wait = service_time (mpx_config [i]); /* set terminal I/O time */ | |
sim_cancel (&mpx_unit [i]); /* cancel line I/O */ | |
} | |
sim_cancel (&mpx_cntl); /* cancel controller */ | |
return; | |
} | |
/* Calculate service time from baud rate. | |
Service times are based on 1580 instructions per millisecond, which is the | |
1000 E-Series execution speed. Baud rate 0 means "don't change" and is | |
handled by the "Set port key" command executor. | |
Baud rate settings of 13-15 are marked as "reserved" in the user manual, but | |
the firmware defines these as 38400, 9600, and 9600 baud, respectively. | |
*/ | |
static uint32 service_time (uint16 control_word) | |
{ | |
/* Baud Rates 0- 7 : --, 50, 75, 110, 134.5, 150, 300, 1200, */ | |
/* Baud Rates 8-15 : 1800, 2400, 4800, 9600, 19200, 38400, 9600, 9600 */ | |
static const int32 ticks [] = { 0, 316000, 210667, 143636, 117472, 105333, 52667, 13167, | |
8778, 6583, 3292, 1646, 823, 411, 1646, 1646 }; | |
return ticks [GET_BAUDRATE (control_word)]; /* return service time for indicated rate */ | |
} | |
/* Translate port key to port number. | |
Port keys are scanned in reverse port order, so if more than one port has the | |
same port key, commands specifying that key will affect the highest numbered | |
port. | |
If a port key is the reserved value 255, then the port key has not been set. | |
In this case, set the input buffer to 0xBAD0 and return -1 to indicate | |
failure. | |
*/ | |
static int32 key_to_port (uint32 key) | |
{ | |
int32 i; | |
for (i = MPX_PORTS - 1; i >= 0; i--) /* scan in reverse order */ | |
if (mpx_key [i] == key) /* key found? */ | |
return i; /* return port number */ | |
mpx_ibuf = ST_BAD_KEY; /* key not found: set status */ | |
return -1; /* return failure code */ | |
} | |
/* Buffer manipulation routines. | |
The 12792 hardware provides 16K bytes of RAM to the microprocessor. From | |
this pool, the firmware allocates per-port read/write buffers and state | |
variables, global variables, and the system stack. Allocations are static | |
and differ between firmware revisions. | |
The A/B/C revisions allocate two 254-byte read buffers and two 254-byte write | |
buffers per port. Assuming an idle condition, the first write to a port | |
transfers characters to the first write buffer. When the transfer completes, | |
the SIO begins transmitting. During transmission, a second write can be | |
initiated, which transfers characters to the second write buffer. If a third | |
write is attempted before the first buffer has been released, it will be | |
denied until the SIO completes transmission; then, if enabled, an unsolicited | |
interrupt will occur to announce buffer availability. The "active" (filling) | |
buffer alternates between the two. | |
At idle, characters received will fill the first read buffer. When the read | |
completes according to the previously set termination criteria, an | |
unsolicited interrupt will occur (if enabled) to announce buffer | |
availability. If more characters are received before the first buffer has | |
been transferred to the CPU, they will fill the second buffer. If that read | |
also completes, additional characters will be discarded until the first | |
buffer has been emptied. The "active" (emptying) buffer alternates between | |
the two. | |
With this configuration, two one-character writes or reads will allocate both | |
available buffers, even though each will be essentially empty. | |
The D revision allocates one 1024-byte FIFO read buffer and one 892-byte | |
write buffer per port. As with the A/B/C revisions, the first write to a | |
port transfers characters to the write buffer, and serial transmission begins | |
when the write completes. However, the write buffer is not a FIFO, so the | |
host is not permitted another write request until the entire buffer has been | |
transmitted. | |
The read buffer is a FIFO. Characters received are placed into the FIFO as a | |
stream. Unlike the A/B/C revisions, character editing and termination | |
conditions are not evaluated until the buffer is read. Therefore, a full | |
1024 characters may be received before additional characters would be | |
discarded. | |
When the first character is received, an unsolicited interrupt occurs (if | |
enabled) to announce data reception. A host read may then be initiated. The | |
write buffer is used temporarily to process characters from the read buffer. | |
Characters are copied from the read to the write buffer while editing as | |
directed by the configuration accompanying the read request (e.g., deleting | |
the character preceding a BS, stripping CR/LF, etc.). When the termination | |
condition is found, the read command completes. Incoming characters may be | |
added to the FIFO while this is occurring. | |
In summary, the revision differences in buffer handling are: | |
Revisions A/B/C: | |
- two 254-byte receive buffers | |
- a buffer is "full" when the terminator character or count is received | |
- termination type must be established before the corresponding read | |
- data is echoed as it is received | |
Revision D: | |
- one 1024-byte receive buffer | |
- buffer is "full" only when 1024 characters are received | |
- the concept of a buffer terminator does not apply, as the data is not | |
examined until a read is requested and characters are retrieved from the | |
FIFO. | |
- data is not echoed until it is read | |
To implement the C revision behavior, while preserving the option of reusing | |
the buffer handlers for future D revision support, the dual 254-byte buffers | |
are implemented as a single 514-byte circular FIFO with capacity limited to | |
254 bytes per buffer. This reserves space for a CR and LF and for a header | |
byte in each buffer. The header byte preserves per-buffer state information. | |
In this implementation, the buffer "put" index points at the next free | |
location, and the buffer "get" index points at the next character to | |
retrieve. In addition to "put" and "get" indexes, a third "separator" index | |
is maintained to divide the FIFO into two areas corresponding to the two | |
buffers, and a "buffer filling" flag is maintained for each FIFO that is set | |
by the fill (put) routine and cleared by the terminate buffer routine. | |
Graphically, the implementation is as follows for buffer "B[]", get "G", put | |
"P", and separator "S" indexes: | |
1. Initialize: 2. Fill first buffer: | |
G = S = P = 0 B[P] = char; Incr (P) | |
|------------------------------| |---------|--------------------| | |
G G P --> | |
S S | |
P | |
3. Terminate first buffer: 4. Fill second buffer: | |
if S == G then S = P else nop B[P] = char; Incr (P) | |
|------------|-----------------| |------------|------|----------| | |
G /---> S G S P --> | |
* ----/ P | |
5. Terminate second buffer: 6. Empty first buffer: | |
if S == G then S = P else nop char = B[G]; Incr (G) | |
|------------|------------|----| |----|-------|------------|----| | |
G S P G --> S P | |
7. First buffer is empty: 8. Free first buffer: | |
G == S if !filling then S = P else nop | |
|------------|------------|----| |------------|------------|----| | |
G P G /---> S | |
S * ----/ P | |
9. Empty second buffer: 10. Second buffer empty: | |
char = B[G]; Incr (G) G == S | |
|----------------|--------|----| |-------------------------|----| | |
G --> S G | |
P S | |
P | |
11. Free second buffer: | |
if !filling then S = P else nop | |
|-------------------------|----| | |
G | |
S | |
P | |
We also provide the following utility routines: | |
- Remove Character: Decr (P) | |
- Cancel Buffer: if S == G then P = G else G = S | |
- Buffer Length: if S < G then return S + BUFSIZE - G else return S - G | |
- Buffers Available: if G == P then return 2 else if G != S != P then return | |
0 else return 1 | |
The "buffer filling" flag is necessary for the "free" routine to decide | |
whether to advance the separator index. If the first buffer is to be freed, | |
then G == S and S != P. If the second buffer is already filled, then S = P. | |
However, if the buffer is still filling, then S must remain at G. This | |
cannot be determined from G, S, and P alone. | |
A "buffer emptying" flag is also employed to record whether the per-buffer | |
header has been obtained. This allows the buffer length to exclude the | |
header and reflect only the characters present. | |
*/ | |
/* Increment a buffer index with wraparound */ | |
static uint16 buf_incr (BUF_INDEX index, uint32 port, IO_OPER rw, int increment) | |
{ | |
index [port] [rw] = | |
(index [port] [rw] + buf_size [rw] + increment) % buf_size [rw]; | |
return index [port] [rw]; | |
} | |
/* Initialize the buffer. | |
Initialization sets the three indexes to zero and clears the buffer state | |
flags. | |
*/ | |
static void buf_init (IO_OPER rw, uint32 port) | |
{ | |
mpx_get [port] [rw] = 0; /* clear indexes */ | |
mpx_sep [port] [rw] = 0; | |
mpx_put [port] [rw] = 0; | |
if (rw == ioread) | |
mpx_flags [mpx_port] &= ~(FL_RDFLAGS); /* clear read buffer flags */ | |
else | |
mpx_flags [mpx_port] &= ~(FL_WRFLAGS); /* clear write buffer flags */ | |
return; | |
} | |
/* Get a character from the buffer. | |
The character indicated by the "get" index is retrieved from the buffer, and | |
the index is incremented with wraparound. If the buffer is now empty, the | |
"buffer emptying" flag is cleared. Otherwise, it is set to indicate that | |
characters have been removed from the buffer. | |
*/ | |
static uint8 buf_get (IO_OPER rw, uint32 port) | |
{ | |
uint8 ch; | |
uint32 index = mpx_get [port] [rw]; /* current get index */ | |
if (rw == ioread) | |
ch = mpx_rbuf [port] [index]; /* get char from read buffer */ | |
else | |
ch = mpx_wbuf [port] [index]; /* get char from write buffer */ | |
buf_incr (mpx_get, port, rw, +1); /* increment circular get index */ | |
if (mpx_flags [port] & emptying_flags [rw]) | |
tprintf (mpx_dev, DEB_BUF, "Port %d character %s get from %s buffer [%d]\n", | |
port, fmt_char (ch), io_op [rw], index); | |
else | |
tprintf (mpx_dev, DEB_BUF, "Port %d header %03o get from %s buffer [%d]\n", | |
port, ch, io_op [rw], index); | |
if (mpx_get [port] [rw] == mpx_sep [port] [rw]) /* buffer now empty? */ | |
mpx_flags [port] &= ~emptying_flags [rw]; /* clear "buffer emptying" flag */ | |
else | |
mpx_flags [port] |= emptying_flags [rw]; /* set "buffer emptying" flag */ | |
return ch; | |
} | |
/* Put a character to the buffer. | |
The character is written to the buffer in the slot indicated by the "put" | |
index, and the index is incremented with wraparound. The first character put | |
to a new buffer reserves space for the header and sets the "buffer filling" | |
flag. | |
*/ | |
static void buf_put (IO_OPER rw, uint32 port, uint8 ch) | |
{ | |
uint32 index; | |
if ((mpx_flags [port] & filling_flags [rw]) == 0) { /* first put to this buffer? */ | |
mpx_flags [port] |= filling_flags [rw]; /* set buffer filling flag */ | |
index = mpx_put [port] [rw]; /* get current put index */ | |
buf_incr (mpx_put, port, rw, +1); /* reserve space for header */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d reserved header for %s buffer [%d]\n", | |
port, io_op [rw], index); | |
} | |
index = mpx_put [port] [rw]; /* get current put index */ | |
if (rw == ioread) | |
mpx_rbuf [port] [index] = ch; /* put char in read buffer */ | |
else | |
mpx_wbuf [port] [index] = ch; /* put char in write buffer */ | |
buf_incr (mpx_put, port, rw, +1); /* increment circular put index */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d character %s put to %s buffer [%d]\n", | |
port, fmt_char (ch), io_op [rw], index); | |
return; | |
} | |
/* Remove the last character put to the buffer. | |
The most-recent character put to the buffer is removed by decrementing the | |
"put" index with wraparound. | |
*/ | |
static void buf_remove (IO_OPER rw, uint32 port) | |
{ | |
uint32 index; | |
index = buf_incr (mpx_put, port, rw, -1); /* decrement circular put index */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d character %s removed from %s buffer [%d]\n", | |
port, (rw == ioread ? fmt_char (mpx_rbuf [port] [index]) | |
: fmt_char (mpx_wbuf [port] [index])), | |
io_op [rw], index); | |
return; | |
} | |
/* Terminate the buffer. | |
The buffer is marked to indicate that filling is complete and that the next | |
"put" operation should begin a new buffer. The header value is stored in | |
first byte of buffer, which is reserved, and the "buffer filling" flag is | |
cleared. | |
*/ | |
static void buf_term (IO_OPER rw, uint32 port, uint8 header) | |
{ | |
uint32 index = mpx_sep [port] [rw]; /* separator index */ | |
if (rw == ioread) | |
mpx_rbuf [port] [index] = header; /* put header in read buffer */ | |
else | |
mpx_wbuf [port] [index] = header; /* put header in write buffer */ | |
mpx_flags [port] = mpx_flags [port] & ~filling_flags [rw]; /* clear filling flag */ | |
if (mpx_get [port] [rw] == index) /* reached separator? */ | |
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move sep to end of next buffer */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d header %03o terminated %s buffer\n", | |
port, header, io_op [rw]); | |
return; | |
} | |
/* Free the buffer. | |
The buffer is marked to indicate that it is available for reuse, and the | |
"buffer emptying" flag is reset. | |
*/ | |
static void buf_free (IO_OPER rw, uint32 port) | |
{ | |
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling next buffer? */ | |
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ | |
/* else it will be moved when terminated */ | |
mpx_flags [port] = mpx_flags [port] & ~emptying_flags [rw]; /* clear emptying flag */ | |
tprintf (mpx_dev, DEB_BUF, "Port %d released %s buffer\n", port, io_op [rw]); | |
return; | |
} | |
/* Cancel the selected buffer. | |
The selected buffer is marked to indicate that it is empty. Either the "put" | |
buffer or the "get" buffer may be selected. | |
*/ | |
static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which) | |
{ | |
if (which == put) { /* cancel put buffer? */ | |
mpx_put [port] [rw] = mpx_sep [port] [rw]; /* move put back to separator */ | |
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ | |
} | |
else { /* cancel get buffer */ | |
if (mpx_sep [port] [rw] == mpx_get [port] [rw]) { /* filling first buffer? */ | |
mpx_put [port] [rw] = mpx_get [port] [rw]; /* cancel first buffer */ | |
mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ | |
} | |
else { /* not filling first buffer */ | |
mpx_get [port] [rw] = mpx_sep [port] [rw]; /* cancel first buffer */ | |
if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling second buffer? */ | |
mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ | |
} | |
mpx_flags [port] &= ~emptying_flags [rw]; /* clear emptying flag */ | |
} | |
tprintf (mpx_dev, DEB_BUF, "Port %d cancelled %s buffer\n", port, io_op [rw]); | |
return; | |
} | |
/* Get the buffer length. | |
The current length of the selected buffer (put or get) is returned. For ease | |
of use, the returned length does NOT include the header byte, i.e., it | |
reflects only the characters contained in the buffer. | |
If the put buffer is selected, and the buffer is filling, or the get buffer | |
is selected, and the buffer is not emptying, then subtract one from the | |
length for the allocated header. | |
*/ | |
static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which) | |
{ | |
int16 length; | |
if (which == put) | |
length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */ | |
((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */ | |
else { | |
length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */ | |
if (length && !(mpx_flags [port] & emptying_flags [rw])) /* not empty and not yet emptying? */ | |
length = length - 1; /* account for allocated header */ | |
} | |
if (length < 0) /* is length negative? */ | |
return length + buf_size [rw]; /* account for wraparound */ | |
else | |
return length; | |
} | |
/* Return the number of free buffers available. | |
Either 0, 1, or 2 free buffers will be available. A buffer is available if | |
it contains no characters (including the header byte). | |
*/ | |
static uint32 buf_avail (IO_OPER rw, uint32 port) | |
{ | |
if (mpx_get [port] [rw] == mpx_put [port] [rw]) /* get and put indexes equal? */ | |
return 2; /* all buffers are free */ | |
else if ((mpx_get [port] [rw] != mpx_sep [port] [rw]) && /* get, separator, and put */ | |
(mpx_sep [port] [rw] != mpx_put [port] [rw])) /* all different? */ | |
return 0; /* no buffers are free */ | |
else | |
return 1; /* one buffer free */ | |
} |