/* hp2100_mpx.c: HP 12792C 8-Channel Asynchronous Multiplexer simulator Copyright (c) 2008-2021, 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-Apr-21 JDB Removed REG_FIT from register definitions (obsolete) 07-Feb-21 JDB Changed buffer register macros from BRDATA to CRDATA 31-Dec-20 JDB Added CNTLR_OPCODE, map_opcode declarations Changed uint16 uses to uint32 or HP_WORD 26-Dec-20 JDB Added LOCALACK/REMOTEACK unit option Moved REALTIME/FASTTIME to device option FASTTIME now uses fast controller times Added FASTTIME variables to the register list Poll for ACK after ENQ is now adaptive 09-Dec-20 JDB Replaced "io_assert" with "io_assert_ENF" 02-Dec-20 JDB RESET no longer presets the interface 28-Mar-19 JDB Revised for serial port support Moved SET DISCONNECT command from device to unit 18-Mar-19 JDB Reordered SCP includes 23-Jan-19 JDB Removed DEV_MUX to avoid TMXR debug flags 10-Jul-18 JDB Revised I/O model 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, June 1993) - 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. A device mode specifies whether the multiplexer operates in real-time or optimized ("fast") timing mode. In the former, character send and receive events and microprocessor actions occur at approximately the same rate (in machine instructions) as in hardware. The latter mode increases the rate to the maximum values consistent with correct operation in RTE. Two additional improvements are implemented when the device is in fast timing mode: 1. Buffered characters are transferred in blocks. 2. BS and DEL respond visually more like prior RTE terminal drivers. HP terminals required ENQ/ACK handshaking to operate reliably at speeds above 2400 baud. In simulation, a unit mode specifies whether ENQ/ACK handshaking is passed through to the terminal or is done locally without involving the client. 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. The interface responds to I/O instructions as follows. 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 | port key | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | command parameter | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 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. 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 commands implemented by firmware revision are: Rev Opc Param Operation --- --- ----- ------------------------------ ABC 100 - No operation ABC 101 - Reset to power-on defaults ABC 102 - Enable unsolicited input ABC 103 1 Disable unsolicited interrupts ABC 103 2 Abort DMA transfer ABC 104 - Acknowledge ABC 105 key Cancel first receive buffer ABC 106 key Cancel all received buffers ABC 107 - Fast binary read -BC 140 chr VCP put byte -BC 141 - VCP put buffer -BC 142 - VCP get byte -BC 143 - VCP get buffer -BC 144 - Exit VCP mode -BC 157 - Enter VCP mode Rev Cmd Value Operation --- --- ----- ------------------------------ ABC 300 - No operation ABC 301 key Request write buffer ABC 302 key Write data to buffer ABC 303 key Set port key ABC 304 key Set receive type ABC 305 key Set character count ABC 306 key Set flow control ABC 307 key Read data from buffer ABC 310 - Download executable -BC 311 key Connect line -BC 312 key Disconnect line -BC 315 key Get modem/port status -BC 316 key Enable/disable modem loopback -BC 320 key Terminate active receive buffer Output Data Word formats (OTA and OTB): 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 0 0 0 | 0 0 0 0 0 0 0 0 | opcode 100 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ No Operation. This command does nothing other than set the flag. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 0 0 1 | 0 0 0 0 0 0 0 0 | opcode 101 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Reset. This command reboots the card by branching to the power-on code, just as if a hardware microprocessor reset had been done. All mux port parameters are defaulted to their power up values. The status return value is 100000, which indicates to the A/L-Series that the card self-test is OK. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 0 1 0 | 0 0 0 0 0 0 0 0 | opcode 102 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Enable Unsolicited Interrupts. This command enables certain multiplexer conditions to generate an interrupt. Note that this command will NOT set the device flag to acknowledge the command. Instead, the flag will set when the card has an unsolicited input available for the host. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 0 1 1 | 0 0 0 0 0 0 0 1 | opcode 103.1 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Disable Unsolicited Interrupts. This command inhibits multiplexer conditions from generating an interrupt. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 0 1 1 | 0 0 0 0 0 0 1 0 | opcode 103.2 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Abort DMA Transfer. This command sends a RESET command (hex C3) to the DMA chip, which forcibly terminates the transfer. For a write transfer, the partial buffer obtained is discarded (never sent over the serial line). For a read transfer, the remainder of the read buffer is discarded. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 1 0 0 | 0 0 0 0 0 0 0 0 | opcode 104 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Acknowledge. This command acknowledges an unsolicited interrupt from the card. In response, the card supplies the second status word and sets the flag. An Acknowledge must be the only command sent in response to the first status word of an unsolicited interrupt. The mux will discard commands until an Acknowledge is seen. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 1 0 1 | port key | opcode 105 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Cancel First Receive Buffer. This command clears the first receive buffer associated with the specified port key, e.g., in response to a timeout, Control-D, etc. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 1 1 0 | port key | opcode 106 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Cancel All Receive Buffers. This command clears both pending receive buffers associated with the specified port key, e.g., in response to a timeout, Control-D, etc. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 0 0 0 | 1 1 1 | 0 0 0 0 0 0 0 0 | opcode 107 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Fast Binary Read. The card sets the flag and then sends an "ESC e" to port 0. Each pair of characters presented on port 0 are sent to the host, and the flag is set. The card waits until the last pair was retrieved via an LIA/B SC before sending the next pair. The card remains in this loop until it receives a hardware reset via CRS assertion. No status word is returned. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 0 0 0 | output character | opcode 140 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ VCP Put Byte. The character supplied is output to port 0, and the flag is set. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 0 0 1 | 0 0 0 0 0 0 0 0 | opcode 141 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ VCP Put Buffer. Because the VCP Put Byte command actually outputs the character, rather than buffering it, this command does nothing other than set the flag. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 0 1 0 | 0 0 0 0 0 0 0 0 | opcode 142 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ VCP Get Byte. A character is read from port 0, echoed, and returned in the lower byte of the status return value. The flag is set. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 0 1 1 | 0 0 0 0 0 0 0 0 | opcode 143 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ VCP Get Buffer. The value 80, representing the buffer length, is returned in the lower byte of the status return value, and the flag is set. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 1 0 0 | 0 0 0 0 0 0 0 0 | opcode 144 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Exit VCP Mode. The card leaves VCP mode (in which all other ports are frozen) and sets the flag. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 | 1 0 0 | 1 0 1 | 0 0 0 0 0 0 0 0 | opcode 145 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Enter VCP Mode. The card enters VCP mode (in which all other ports are frozen) and sets the flag. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 0 0 0 | port key | opcode 301 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | requested buffer size in bytes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Request Write Buffer. If a write buffer is available, the status return value will be the number of bytes in the buffer (fixed at 254). If a buffer is not available, the status return value will be zero, and an unsolicited input will occur if enabled when a write buffer of at least the requested size becomes available. Note that the requested size need not include space for a terminating CR/LF. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 0 1 0 | port key | opcode 302 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - | E | C | P | write length | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: E = disable ENQ/ACK for this write only C = add CR/LF if the last character is not '_' P = write is a partial transfer (no CR/LF at end) Write Data to Buffer. The write length specifies the number of bytes to transfer into the write buffer. If the C bit is 1, the P bit is 0, and the final character is an underscore, then the underscore is not transferred. Otherwise, a CR/LF is appended to the buffer. If the P bit is 1, then this transfer represents only part of the output line, so the underscore and CR/LF check is not done. A write length of zero is legal and will result in only a CR/LF (if enabled). After the second command word is sent, the device flag will be set when the card is ready for the first data word (two packed bytes). Data is output to the card via an OTA/B SC,C. The device flag will set when the next word may be sent. To conclude the transfer, one more word is sent to the card with an OTA/B SC,C. The value is ignored. The device flag will set to acknowledge the termination. No status word is returned. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 0 0 0 | port key | opcode 303 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | bits | M | G | stop | par | E | baud rate | port num | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: M = hardwired/modem (0/1) port G = port is connected to baud-rate generator 0/1 E = disable/enable (0/1) ENQ/ACK handshake Bits per character: 00 = 5 bits per character 01 = 7 bits per character 10 = 6 bits per character 11 = 8 bits per character 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: 0 = no change 1 = 50 baud 2 = 75 baud 3 = 110 baud 4 = 134.5 baud 5 = 150 baud 6 = 300 baud 7 = 1200 baud 8 = 1800 baud 9 = 2400 baud 10 = 4800 baud 11 = 9600 baud 12 = 19200 baud 13 = (reserved) 14 = (reserved) 15 = (reserved) Set Port Key. If the port is configured as a modem port, the systems modem on port 7 is initialized (once) to no editing, no echo, and terminate on CR, and a poll is started that outputs a '?' and CR once per second. The systems modem responds with status information for each of the modem ports. No check is made for duplicate port keys. The baud rate generator specified in the command is programmed to the rate specified. This will affect all other ports on the same generator (as set by the wiring in the connector hood). If the common ports are not programmed to the same rate, then the last port set will determine the rate for all. The status return value is zero for revision A firmware and the date code of the firmware for revision B and C firmware. Revision C returns 2416 decimal (ERS says "hex," but the firmware shows decimal). 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 1 0 0 | port key | opcode 304 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - | 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 Set Receive Type. Sets the conditions for the next data read. The N and K bits are mutually exclusive. The firmware actually only checks the K bit. If it is not set, then N (end on count) is assumed. The power-on configuration for each port is bits 7, 2, 1, and 0 on (echo, edit, and end on CR). The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 1 0 1 | port key | opcode 305 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | character count in bytes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Set Character Count. Specifies the length of reads that end on a character count. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 1 1 0 | port key | opcode 306 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - | F | X | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: F = force an XON if currently XOFF X = enable XON/XOFF handshaking Set Flow Control. Note that an XON received before an XOFF is not interpreted as a handshake character. A XON received after an XOFF is stripped and causes transmission to resume. The status return value is zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 0 | 1 1 1 | port key | opcode 307 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | read length in bytes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Read Data From Buffer. After the second command word is sent, the device flag will be set when the first data word (two packed bytes) is ready. Data is input from the card via an LIA SC,C. The device flag will set when the next word is available. To conclude the transfer, a word is sent to the card with an OTA/B SC,C. The value is ignored. The device flag will set to acknowledge the termination. No status word is returned. Odd-byte transfers have the low byte of the last word set to a blank. A read of less than the number of characters in the buffer will return the remaining characters when the next read is performed. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 1 | 0 0 0 | port key | opcode 310 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | size of download in bytes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Download Executable. Each word of the program to be downloaded is sent with an OTA/B SC,C and is acknowledged by the device flag. All card operations are suspended while download is in progress. At completion, the program is executed. No status word is returned. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 1 | 0 0 1 | port key | opcode 311 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - | G | M | B | D | I | S | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: G = guard tone off/on (0/1) M = mode is 212/V.22 (0/1) B = 10 bits/9 bits (0/1) D = originate/answer (0/1) I = manual/automatic (0/1) dial S = low/high (0/1) speed Connect Line. The status return value is 140000 if no modem is installed and 000000 otherwise. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 1 | 0 1 0 | port key | opcode 312 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - | A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: A = enable/disable auto-answer (0/1) Disconnect Line. The status return value is 140000 if no modem is installed and 000000 otherwise. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 1 | 1 0 1 | port key | opcode 315 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - - | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Get Modem/Port Status. The status return value has the modem status in the lower byte and a zero in the upper byte. If the systems modem card cage is not present, the return status value is 000200B. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 0 1 | 1 1 0 | port key | opcode 316 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - | S | T | E | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: S = low/high (0/1) speed T = analog/remote digital (0/1) E = disable/enable (0/1) loop test Enable/Disable Modem Loopback. The status return value is 140000 if no modem is installed and 000000 otherwise. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 | 0 1 0 | 0 0 0 | port key | opcode 320 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - - | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Terminate Active Receive Buffer. The active buffer is terminated immediately with the characters that have already been received. If this is the first buffer, a "Read buffer available" unsolicited input will be posted. A subsequent read request will return the characters. If the active buffer is empty, the port is configured temporarily to terminate after one character is received (i.e., the equivalent of a Set Character Count command is done). Upon receipt of the character, the port character length is reset to 254. The status return value is zero. 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. If bit 15 of the status word is set, the card has experienced a power fail reset. 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 content of the second status word depends on the reason supplied in the first word. Input Data Word format (LIA, LIB, MIA, and MIB): 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - | reason | port key | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | buffer size in bytes | code 001 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - - | code 002 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - - | code 003 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - - | code 004 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - | P | F | ETC | count of characters received | code 005 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Reason: 001 = Write buffer available 002 = Modem line connected 003 = Modem line disconnected 004 = Break received 005 = Read buffer available Where: P = parity error or buffer overflow occurred F = buffer full before end of text character seen End of text character seen: 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. If the systems modem card cage is present, the Get Modem/Port Status command returns modem status. Input Data Word format (LIA, LIB, MIA, and MIB): 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 (0/1) speed C = line disconnected/connected (0/1) At power up, as a result of a PRESET (CRS), or as a result of a programmed Reset, the card resets the microprocessor, which programs its peripherals to their default values, returns a status word of 100000B, and sets the flag. The RTE driver interprets bits 15-14 = 10 to indicate that the card has been reset. On power up, all port keys are defaulted to the reserved value 255. If a command is sent that uses a port key that has not been defined (either due to a programming error or a card reset without a subsequent Set Key command), the card returns a status word of 135320B (hex BAD0) and sets the flag. 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 #include "sim_defs.h" #include "sim_tmxr.h" #include "hp2100_defs.h" #include "hp2100_io.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 expansions 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) - 1u << (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 limits */ #define MPX_PORTS 8 /* number of visible units */ #define MPX_CNTLS 2 /* number of control units */ #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 */ /* Program constants */ #define MPX_DATE_CODE 2416 /* date code for C firmware */ #define KEY_DEFAULT 255 /* default port key */ /* Character constants */ #define NUL '\000' #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 /* Common per-port multiplexer state variables */ #define rate_index u3 /* index into baud rate timing array */ /* Device flags */ #define DEV_REV_D BIT (DEV_V_UF + 0) /* firmware revision D (not implemented) */ #define DEV_REALTIME BIT (DEV_V_UF + 1) /* timing mode is realistic */ /* Unit flags */ #define UNIT_LOCALACK BIT (UNIT_V_UF + 0) /* ENQ/ACK mode is local */ #define UNIT_CAPSLOCK BIT (UNIT_V_UF + 1) /* caps lock is down */ /* Unit references */ #define mpx_cntl mpx_unit [MPX_PORTS + 0] /* controller unit */ #define mpx_poll mpx_unit [MPX_PORTS + 1] /* polling unit */ /* Unit activation reasons. The simulation models the execution delays of the on-board microprocessor as timed events that are scheduled by controller unit command phase transitions. When a line or controller unit is scheduled, the activation reason determines the length of the delay before service entry. The delays are stored in an array indexed by the activation reason. Separate delays are specified for realistic and optimized timing. The array contains these elements: Send -- the time to transmit a character via the UART Receive -- the time to receive a character via the UART Backplane -- the time from DMA output to SRQ for a data word Parameter -- the time from STC to STF for the first word of a two-word command Command -- the time from STC to STF for one or two-word command execution Status -- the time from the Enable Interrupts command to the status check The sending and receiving times for realistic mode are calculated from the configured baud rate at each activation, so they do not appear in the delay array below. */ typedef enum { Send, /* character transmission */ Receive, /* character reception */ Backplane, /* backplane data transfer */ Parameter, /* parameter reception */ Command, /* command execution */ Status /* status check */ } ACTIVATION; typedef enum { /* the device timing modes */ Fast_Time, /* optimized timing */ Real_Time /* realistic timing */ } DEVICE_MODE; static int32 fast_times [6] = { /* initial fast timing delays, indexed by ACTIVATION */ /* Send Receive Backplane Parameter Command Status */ /* -------- -------- --------- --------- -------- ------- */ uS (200), uS (200), uS (1.25), uS (10), uS (50), uS (20) }; static int32 times [2] [6] = { /* timing delays, indexed by DEVICE_MODES and ACTIVATION */ /* Send Receive Backplane Parameter Command Status */ /* -------- -------- --------- --------- -------- ------- */ { uS (200), uS (200), uS (1.25), uS (10), uS (50), uS (20) }, /* Fast_Time timing */ { 0, 0, uS (1.25), uS (25), uS (400), uS (50) } /* Real_Time timing */ }; /* Multiplexer baud rate service times. Rates correspond to the baud rate code specified in the Set Port Key command. Baud rate setting 0 means "don't change" and is handled by the command executor. Baud rate settings 13-15 are marked as "reserved" in the user manual, but the firmware defines these as 38400, 9600, and 9600 baud, respectively. */ static const int32 rates [] = { /* (unused) 50 75 110 134.5 150 300 1200 */ /* 1800 2400 4800 9600 19200 38400 9600 9600 */ /* ---------- ---------- ---------- ---------- ----------- ----------- ----------- ---------- */ 0, mS (200.0), mS (133.3), mS (90.91), mS (74.39), mS (66.67), mS (33.34), mS (8.334), mS (5.556), mS (4.166), mS (2.084), mS (1.042), mS (0.521), mS (0.260), mS (1.042), mS (1.042) }; /* Microprocessor controller opcodes */ typedef enum { No_Operation, /* (0100) No operation */ Reset_Firmware, /* (0101) Reset firmware to power-on defaults */ Enable_UI, /* (0102) Enable unsolicited interrupts */ Abort_DMA_Transfer, /* (0103.2) Abort DMA Transfer */ Acknowledge, /* (0104) Acknowledge */ Cancel_First, /* (0105) Cancel first receive buffer */ Cancel_All, /* (0106) Cancel all received buffers */ Fast_Binary_Read, /* (0107) Fast binary read */ Disable_UI, /* (0103.1) Disable interrupts */ VCP_Put_Byte, /* (0140) VCP put byte */ VCP_Put_Buffer, /* (0141) VCP put buffer */ VCP_Get_Byte, /* (0142) VCP get byte */ VCP_Get_Buffer, /* (0143) VCP get buffer */ VCP_Exit_Mode, /* (0144) Exit VCP mode */ VCP_Enter_Mode, /* (0157) Enter VCP mode */ Request_Write_Buffer, /* (0301) Request write buffer */ Write_Data, /* (0302) Write data to buffer */ Set_Port_Key, /* (0303) Set port key */ Set_Receive_Type, /* (0304) Set receive type */ Set_Character_Count, /* (0305) Set character count */ Set_Flow_Control, /* (0306) Set flow control */ Read_Data, /* (0307) Read data from buffer */ Download_Executable, /* (0310) Download executable */ Connect_Line, /* (0311) Connect line */ Disconnect_Line, /* (0312) Disconnect line */ Get_Status, /* (0315) Get modem/port status */ Set_Modem_Loopback, /* (0316) Enable/disable modem loopback */ Terminate_Receive_Buffer, /* (0320) Terminate active receive buffer */ } CNTLR_OPCODE; static const char *const fmt_command [] = { /* command names, indexed by CNTLR_OPCODE */ "No Operation", /* 100 */ "Reset Firmware", /* 101 */ "Enable Unsolicited Interrupts", /* 102 */ "Abort DMA Transfer", /* 103 subopcode 2 */ "Acknowledge", /* 104 */ "Cancel First Receive Buffer", /* 105 */ "Cancel All Received Buffers", /* 106 */ "Fast Binary Read", /* 107 */ "Disable Unsolicited Interrupts", /* 103 subopcode 1 */ "VCP Put Byte", /* 140 */ "VCP Put Buffer", /* 141 */ "VCP Get Byte", /* 142 */ "VCP Get Buffer", /* 143 */ "Exit VCP Mode", /* 144 */ "Enter VCP Mode", /* 157 */ "Request Write Buffer", /* 301 */ "Write Data to Buffer", /* 302 */ "Set Port Key", /* 303 */ "Set Receive Type", /* 304 */ "Set Character Count", /* 305 */ "Set Flow Control", /* 306 */ "Read Data from Buffer", /* 307 */ "Download Executable", /* 310 */ "Connect Line", /* 311 */ "Disconnect Line", /* 312 */ "Get Modem/Port Status", /* 315 */ "Set Modem Loopback", /* 316 */ "Terminate Receive Buffer" /* 320 */ }; /* Multiplexer command field encoders */ #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 interrupts */ #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 */ /* One-word subcommand codes */ #define SUBCMD_UI 1 /* Disable unsolicited interrupts */ #define SUBCMD_DMA 2 /* Abort DMA transfer */ /* 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 */ #define CMD_HAS_PARAM(w) ((CN_OPCODE (w) & 0200u) != 0) /* 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) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - | E | C | P | write length | 302 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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 */ static const BITSET_NAME write_names [] = { /* Write Data to Buffer parameter */ "disable ENQ/ACK", /* bit 13 */ "add CR/LF", /* bit 12 */ "partial write" /* bit 11 */ }; static const BITSET_FORMAT write_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (write_names, 11, msb_first, no_alt, no_bar) }; /* Set port key (303) command parameter. 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 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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_STOPBITS(w) FIELD_TO (11, 10, w) #define GET_PARITY(w) FIELD_TO ( 9, 8, 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) static const BITSET_NAME set_key_names [] = { /* Set Port Key parameter */ "\1modem port\0hardwired port", /* bit 13 */ "\1BRG 1\0BRG 0", /* bit 12 */ NULL, /* bit 11 */ NULL, /* bit 10 */ NULL, /* bit 9 */ NULL, /* bit 8 */ "\1enable ENQ/ACK\0disable ENQ/ACK" /* bit 7 */ }; static const BITSET_FORMAT set_key_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (set_key_names, 7, msb_first, has_alt, append_bar) }; static const uint32 bits_per_char [4] = { /* bits per character, indexed by SK_BPC encoding */ 5, 7, 6, 8 }; static const char *stop_bits [4] = { /* stop bits, indexed by SK_STOPBITS encoding */ "(reserved)", "1 stop bit", "1.5 stop bits", "2 stop bits" }; static const char *parity [4] = { /* parity, indexed by SK_PARITY encoding */ "no", "odd", "no", "even" }; static const char *baud [16] = { /* baud rate, indexed by SK_BAUDRATE encoding */ "no change", "50 baud", "75 baud", "110 baud", "134.5 baud", "150 baud", "300 baud", "1200 baud", "1800 baud", "2400 baud", "4800 baud", "9600 baud", "19200 baud", "(reserved)", "(reserved)", "(reserved)" }; /* Set receive type (304) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - | C | R | T | D | N | K | E | H | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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) static const BITSET_NAME set_receive_names [] = { /* Set Receive Type parameter */ "CR", /* bit 7 */ "RS", /* bit 6 */ "EOT", /* bit 5 */ "DC2", /* bit 4 */ "count", /* bit 3 */ "character", /* bit 2 */ "enable editing", /* bit 1 */ "enable echo" /* bit 0 */ }; static const BITSET_FORMAT set_receive_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (set_receive_names, 0, msb_first, no_alt, no_bar) }; /* Set flow control (306) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - | F | X | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define FC_FORCE_XON BIT (1) /* Flow control: force XON */ #define FC_XONXOFF BIT (0) /* Flow control: enable XON/XOFF */ static const BITSET_NAME set_flow_names [] = { /* Set Flow Control parameter */ "force XON if XOFF", /* bit 1 */ "enable XON/XOFF handshake" /* bit 0 */ }; static const BITSET_FORMAT set_flow_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (set_flow_names, 0, msb_first, no_alt, no_bar) }; /* Connect line (311) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - | G | M | B | D | I | S | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - - - | A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define DL_AUTO_ANSWER BIT (0) /* Disconnect line: auto-answer enable or disable */ /* Enable/disable modem loopback (316) command parameter. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - - - - - - | S | T | E | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - | reason | port key | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | additional parameter | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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) static const char *const ui_names [] = { /* unsolicited interrupt reason names */ NULL, /* 0 */ "Write Buffer Available", /* 1 */ "Modem Line Connected", /* 2 */ "Modem Line Disconnected", /* 3 */ "Break Received", /* 4 */ "Read Buffer Available" /* 5 */ }; /* 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 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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 */ #define GET_ETC(w) FIELD_TO (12, 11, w) #define GET_READCOUNT(w) FIELD_TO (10, 0, w) #define RS_NO_ETC D16_UMAX /* a value indicating that no ETC condition exists */ static const BITSET_NAME buffer_names [] = { /* Read Buffer Available status */ "parity error", /* bit 14 */ "buffer full" /* bit 13 */ }; static const BITSET_FORMAT buffer_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (buffer_names, 13, msb_first, no_alt, append_bar) }; static const char *end_of_text [4] = { /* end of text character, indexed by RS_ETC encoding */ "EOT", "CR", "DC2", "RS" }; /* Get modem/port status (315) values. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | - - - - - - - - | M | T | P | - - - | S | C | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #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 */ /* Per-port internal state 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 states */ typedef enum { /* controller execution states */ Idle_State, /* idle */ Command_State, /* waiting for a command word */ Parameter_State, /* waiting for a parameter word */ Execution_State /* executing a command */ } CNTLR_STATE; static const char *state_name [] = { /* controller state names, indexed by CNTLR_STATE */ "Idle", "Command", "Parameter", "Execution" }; static HP_WORD mpx_ibuf = 0; /* status/data in */ static HP_WORD mpx_obuf = 0; /* command/data out */ static CNTLR_STATE mpx_state = Idle_State; /* current controller state */ static CNTLR_OPCODE mpx_cmd = No_Operation; /* 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_has_param = FALSE; /* current command has a parameter */ static t_bool mpx_uien = FALSE; /* unsolicited interrupts enabled */ static uint32 mpx_uicode = 0; /* unsolicited interrupt reason and port */ typedef struct { FLIP_FLOP control; /* control flip-flop */ FLIP_FLOP flag; /* flag flip-flop */ FLIP_FLOP flag_buffer; /* flag buffer flip-flop */ } CARD_STATE; static CARD_STATE mpx; /* per-card state */ /* Multiplexer per-line state */ static uint32 mpx_key [MPX_PORTS]; /* port keys */ static uint32 mpx_config [MPX_PORTS]; /* port configuration */ static uint32 mpx_rcvtype [MPX_PORTS]; /* receive type */ static uint32 mpx_charcnt [MPX_PORTS]; /* current character count */ static uint32 mpx_termcnt [MPX_PORTS]; /* termination character count */ static uint32 mpx_flowcntl [MPX_PORTS]; /* flow control */ static uint32 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */ static uint32 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */ static uint32 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 uint32 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 uint32 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 IO_INTERFACE mpx_interface; static t_stat set_mode (UNIT *uptr, int32 val, char *cptr, void *desc); static t_stat set_revision (UNIT *uptr, int32 val, char *cptr, void *desc); static t_stat show_mode (FILE *st, UNIT *uptr, int32 value, void *desc); static t_stat show_revision (FILE *st, UNIT *uptr, int32 value, void *desc); static t_stat show_status (FILE *st, UNIT *uptr, int32 value, void *desc); static t_stat mpx_reset (DEVICE *dptr); static t_stat mpx_attach (UNIT *uptr, char *cptr); static t_stat mpx_detach (UNIT *uptr); /* Multiplexer local utility routines */ static t_stat cntl_service (UNIT *uptr); static t_stat line_service (UNIT *uptr); static t_stat poll_service (UNIT *uptr); static int32 activate_unit (UNIT *uptr, ACTIVATION reason); static t_bool exec_command (void); static void poll_connection (void); static void controller_reset (void); static int32 key_to_port (uint32 key); static CNTLR_OPCODE map_opcode (uint32 command); static void buf_init (IO_OPER rw, uint32 port); static uint8 buf_get (IO_OPER rw, uint32 port); static void buf_put (IO_OPER rw, uint32 port, uint8 ch); static void buf_remove (IO_OPER rw, uint32 port); static void buf_term (IO_OPER rw, uint32 port, uint8 header); static void buf_free (IO_OPER rw, uint32 port); static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which); static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which); static uint32 buf_avail (IO_OPER rw, uint32 port); /* Multiplexer SCP data structures */ DEVICE mpx_dev; /* incomplete device structure */ /* Terminal multiplexer library descriptors */ static int32 mpx_order [MPX_PORTS] = { /* line connection order */ -1 /* use the default order */ }; 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 descriptor array */ mpx_order, /* line connection order */ &mpx_dev /* multiplexer device */ }; /* Device information block */ static DIB mpx_dib = { &mpx_interface, /* the device's I/O interface function pointer */ NULL, /* the power state function pointer */ MPX, /* the device's select code (02-77) */ 0, /* the card index */ "12792C 8-Channel Asynchronous Multiplexer", /* the card description */ NULL /* the ROM description */ }; /* 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_LOCALACK, 0) }, /* terminal I/O line 0 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 1 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 2 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 3 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 4 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 5 */ { UDATA (&line_service, UNIT_LOCALACK, 0) }, /* terminal I/O line 6 */ { UDATA (&line_service, UNIT_LOCALACK, 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_X }, { ORDATA (OBUF, mpx_obuf, 16), REG_X }, { ORDATA (CMD, mpx_cmd, 8) }, { ORDATA (PARAM, mpx_param, 16) }, { FLDATA (HASPARM, mpx_has_param, 0) }, { 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, 8) }, { 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) }, { CRDATA (RBUF, mpx_rbuf, 8, 8, MPX_PORTS * RD_BUF_SIZE), REG_A }, { CRDATA (WBUF, mpx_wbuf, 8, 8, MPX_PORTS * WR_BUF_SIZE), REG_A }, { CRDATA (GET, mpx_get, 10, 10, MPX_PORTS * 2) }, { CRDATA (SEP, mpx_sep, 10, 10, MPX_PORTS * 2) }, { CRDATA (PUT, mpx_put, 10, 10, MPX_PORTS * 2) }, { FLDATA (CTL, mpx.control, 0) }, { FLDATA (FLG, mpx.flag, 0) }, { FLDATA (FBF, mpx.flag_buffer, 0) }, { DRDATA (TTIME, times [Fast_Time] [Send], 24), PV_LEFT }, { DRDATA (RTIME, times [Fast_Time] [Receive], 24), PV_LEFT }, { DRDATA (DTIME, times [Fast_Time] [Backplane], 24), PV_LEFT }, { DRDATA (PTIME, times [Fast_Time] [Parameter], 24), PV_LEFT }, { DRDATA (CTIME, times [Fast_Time] [Command], 24), PV_LEFT }, { DRDATA (STIME, times [Fast_Time] [Status], 24), PV_LEFT }, { BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO }, DIB_REGS (mpx_dib), { NULL } }; /* Modifier list */ static MTAB mpx_mod [] = { /* Mask Value Match Value Print String Match String Validation Display Descriptor */ /* ------------- ------------- ------------------ ------------ ---------- ------- ---------- */ { UNIT_LOCALACK, UNIT_LOCALACK, "local ENQ/ACK", "LOCALACK", NULL, NULL, NULL }, { UNIT_LOCALACK, 0, "remote ENQ/ACK", "REMOTEACK", NULL, NULL, NULL }, { UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, { UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, /* 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_XUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, (void *) &mpx_desc }, { MTAB_XDV, 0, "REV", NULL, &set_revision, &show_revision, NULL }, { MTAB_XDV, Fast_Time, NULL, "FASTTIME", &set_mode, NULL, (void *) &mpx_dev }, { MTAB_XDV, Real_Time, NULL, "REALTIME", &set_mode, NULL, (void *) &mpx_dev }, { MTAB_XDV, 0, "MODES", NULL, NULL, &show_mode, (void *) &mpx_dev }, { 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 | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, (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 [] = { { "CMD", TRACE_CMD }, /* trace interface or controller commands */ { "INCO", TRACE_INCO, }, /* trace interface or controller command initiations and completions */ { "CSRW", TRACE_CSRW }, /* trace interface control, status, read, and write actions */ { "STATE", TRACE_STATE }, /* trace state changes */ { "SERV", TRACE_SERV }, /* trace unit service scheduling calls and entries */ { "PSERV", TRACE_PSERV }, /* trace periodic unit service scheduling calls and entries */ { "XFER", TRACE_XFER }, /* trace data receptions and transmissions */ { "BUF", TRACE_FIFO }, /* trace FIFO data buffer reads and writes */ { "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */ { 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_DISABLE | DEV_DEBUG, /* 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*/ NULL /* 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 = Command_State. 2. The "Fast binary read" command inhibits all other commands until the card is reset. */ static SIGNALS_VALUE mpx_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) { static const char * const input_state [] = { "Status", "Invalid status", "Parameter", "Data" }; static const char * const output_state [] = { "Command", "Command override", "Parameter", "Data" }; ACTIVATION reason; INBOUND_SIGNAL signal; INBOUND_SET working_set = inbound_signals; SIGNALS_VALUE outbound = { ioNONE, 0 }; t_bool irq_enabled = FALSE; while (working_set) { /* while signals remain */ signal = IONEXTSIG (working_set); /* isolate the next signal */ switch (signal) { /* dispatch the I/O signal */ case ioCLF: /* Clear Flag flip-flop */ mpx.flag_buffer = CLEAR; /* reset the flag buffer */ mpx.flag = CLEAR; /* and flag flip-flops */ break; case ioSTF: /* Set Flag flip-flop */ mpx.flag_buffer = SET; /* set the flag buffer flip-flop */ break; case ioENF: /* Enable Flag */ if (mpx.flag_buffer == SET) /* if the flag buffer flip-flop is set */ mpx.flag = SET; /* then set the flag flip-flop */ break; case ioSFC: /* Skip if Flag is Clear */ if (mpx.flag == CLEAR) /* if the flag flip-flop is clear */ outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */ break; case ioSFS: /* Skip if Flag is Set */ if (mpx.flag == SET) /* if the flag flip-flop is set */ outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */ break; case ioIOI: /* I/O data input */ outbound.value = mpx_ibuf; /* return info */ tprintf (mpx_dev, TRACE_CSRW, "%s is %06o\n", input_state [mpx_state], mpx_ibuf); if (mpx_state == Execution_State) /* if this is input data word */ activate_unit (&mpx_cntl, Backplane); /* then continue transmission */ break; case ioIOO: /* I/O data output */ mpx_obuf = inbound_value; /* save word */ tprintf (mpx_dev, TRACE_CSRW, "%s is %06o\n", output_state [mpx_state], mpx_obuf); if (mpx_state == Parameter_State) /* if this is a parameter word */ activate_unit (&mpx_cntl, Command); /* then schedule the command now */ else if (mpx_state == Execution_State) /* else if this is output data word */ activate_unit (&mpx_cntl, Backplane); /* 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.flag_buffer = CLEAR; /* clear flag buffer */ mpx.flag = CLEAR; /* clear flag */ break; case ioCLC: /* Clear Control flip-flop */ mpx.control = CLEAR; /* clear control */ break; case ioSTC: /* Set Control flip-flop */ mpx.control = SET; /* set control */ if (mpx_cmd == Fast_Binary_Read) /* if currently executing a Fast Binary Read */ break; /* then further command execution is inhibited */ mpx_cmd = map_opcode (mpx_obuf); /* get command opcode */ mpx_portkey = CN_KEY (mpx_obuf); /* get port key */ mpx_has_param = CMD_HAS_PARAM (mpx_obuf); /* set TRUE if the command has a parameter */ if (mpx_state == Command_State) /* already scheduled? */ sim_cancel (&mpx_cntl); /* cancel to get full delay */ else { /* otherwise */ tprintf (mpx_dev, TRACE_STATE, "Controller transitioned from %s state to %s state\n", state_name [mpx_state], state_name [Command_State]); mpx_state = Command_State; /* set the command state */ } if (mpx_has_param) /* if the command needs a parameter */ reason = Parameter; /* then specify the parameter delay */ else /* otherwise */ reason = Command; /* specify the command delay */ tprintf (mpx_dev, TRACE_INCO, "%s command key %d initiated\n", fmt_command [mpx_cmd], mpx_portkey); activate_unit (&mpx_cntl, reason); /* schedule command or parameter */ break; case ioEDT: /* end data transfer */ tprintf (mpx_dev, TRACE_INCO, "DCPC transfer completed\n"); break; case ioSIR: /* Set Interrupt Request */ if (mpx.control & mpx.flag) /* if the control and flag flip-flops are set */ outbound.signals |= cnVALID; /* then deny PRL */ else /* otherwise */ outbound.signals |= cnPRL | cnVALID; /* conditionally assert PRL */ if (mpx.control & mpx.flag & mpx.flag_buffer) /* if the control, flag, and flag buffer flip-flops are set */ outbound.signals |= cnIRQ; /* then conditionally assert IRQ */ if (mpx.flag == SET) /* if the flag flip-flop is set */ outbound.signals |= ioSRQ; /* then assert SRQ */ break; case ioIAK: /* Interrupt Acknowledge */ mpx.flag_buffer = CLEAR; /* clear the flag buffer flip-flop */ break; case ioIEN: /* Interrupt Enable */ irq_enabled = TRUE; /* permit IRQ to be asserted */ break; case ioPRH: /* Priority High */ if (irq_enabled && outbound.signals & cnIRQ) /* if IRQ is enabled and conditionally asserted */ outbound.signals |= ioIRQ | ioFLG; /* then assert IRQ and FLG */ if (not irq_enabled || outbound.signals & cnPRL) /* if IRQ is disabled or PRL is conditionally asserted */ outbound.signals |= ioPRL; /* then assert it unconditionally */ break; case ioPON: /* not used by this interface */ case ioPOPIO: /* not used by this interface */ break; } IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ } /* and continue until all signals are processed */ return outbound; /* return the outbound signals and value */ } /* 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_State, first word of command received (Command_State), command parameter received (Parameter_State), or data transfer (Execution_State). 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 for terminal read and write commands 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 "Fast Binary Read" and "Download Executable" commands. During a Fast Binary Read, port 0 is polled for input, and each pair of bytes is packed into a 16-bit data word for retrieval by the CPU. The CPU responds to the resulting data flag by executing an LIA or LIB instruction to obtain the word. The process then repeats until the multiplexer is reset by asserting CRS to the interface. In hardware, the Download Executable command transfers a Z80 machine program from the CPU to the on-board microprocessor. 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. While the Fast Binary Read command is executing, this service routine is entered repeatedly. On each entry, the "have a buffer" and "want a buffer" flags for port 0 respectively indicate the transfer state, as follows: 00 = waiting for the first byte of the word 01 = waiting for the second byte of the word 11 = complete word has been retrieved 3. 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) { static const char *trace_formats [2] [4] = { /* trace formats, indexed by parameter and state */ { "%s command%.0u status %06o completed\n", /* one-word commands */ "%s command%.0u invalid state\n", "%s command%.0u waiting for parameter\n", "%s command%.0u executing\n" }, { "%s command parameter %06o status %06o completed\n", /* two-word commands */ "%s command invalid state\n", "%s command waiting for parameter\n", "%s command parameter %06o executing\n" } }; int32 byte; uint8 ch; uint32 i; t_bool add_crlf; t_bool set_flag = TRUE; CNTLR_STATE last_state = mpx_state; tprintf (mpx_dev, TRACE_SERV, "Controller service entered in the %s state\n", state_name [mpx_state]); switch (mpx_state) { /* dispatch on current state */ case Idle_State: /* controller idle */ set_flag = FALSE; /* assume no UI */ if (mpx_uicode) { /* unacknowledged UI? */ if (mpx_uien) { /* interrupts enabled? */ mpx_port = GET_UIPORT (mpx_uicode); /* get port number */ mpx_portkey = mpx_key [mpx_port]; /* get port key */ mpx_ibuf = (HP_WORD) (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, TRACE_CMD, "Port %d key %d %s unsolicited interrupt reissued\n", mpx_port, mpx_portkey, ui_names [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) { /* 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 = (HP_WORD) (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, TRACE_CMD, "Port %d key %d %s unsolicited interrupt generated\n", mpx_port, mpx_portkey, ui_names [GET_UIREASON (mpx_uicode)]); break; /* quit after first UI */ } } } break; case Command_State: /* command state */ mpx_param = 0; /* clear the parameter for single-word commands */ if (mpx_has_param) /* if the command needs a parameter */ mpx_state = Parameter_State; /* then look for it before executing */ else /* otherwise */ set_flag = exec_command (); /* the command is ready to execute */ break; case Parameter_State: /* parameter get state */ mpx_param = mpx_obuf; /* save parameter */ set_flag = exec_command (); /* execute two-word command */ break; case Execution_State: /* execution state */ switch (mpx_cmd) { case Fast_Binary_Read: /* fast binary read */ set_flag = FALSE; /* suppress setting the device flag */ if (mpx_flags [0] & FL_HAVEBUF) /* if the data word has been retrieved */ mpx_flags [0] = 0; /* then reset the state to wait for the first byte */ else { /* otherwise we're waiting for a byte (or two) */ tmxr_poll_rx (&mpx_desc); /* so poll for input */ while (tmxr_rqln (&mpx_ldsc [0]) > 0) { /* if bytes are available */ byte = tmxr_getc_ln (&mpx_ldsc [0]); /* then get a byte */ if (mpx_flags [0] & FL_WANTBUF) { /* if the first byte has been stored */ mpx_ibuf = mpx_ibuf | LOWER_BYTE (byte); /* then merge the second byte */ mpx_flags [0] |= FL_HAVEBUF; /* mark the buffer as ready for pickup */ set_flag = TRUE; /* and set the device flag */ } else { /* otherwise this is the first byte */ mpx_ibuf = TO_WORD (byte, 0); /* so put it in the top half of the word */ mpx_flags [0] |= FL_WANTBUF; /* and wait for the second byte to arrive */ } } } if (not set_flag) /* if the data word is not complete */ activate_unit (uptr, Receive); /* then reschedule our service */ break; case Write_Data: /* transfer data to buffer */ if (mpx_iolen <= 0) { /* last (or only) entry? */ mpx_state = Idle_State; /* 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 = LOWER_BYTE (mpx_obuf); /* low byte */ else ch = UPPER_BYTE (mpx_obuf); /* high byte */ if ((mpx_iolen == 1) && /* final char? */ (ch == '_') && add_crlf) { /* underscore and asking for CRLF? */ add_crlf = FALSE; /* suppress CRLF */ tprintf (mpx_dev, TRACE_XFER, "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, UPPER_BYTE (mpx_param)); /* terminate buffer */ mpx_iolen = -1; /* mark as done */ } activate_unit (&mpx_unit [mpx_port], Send); /* start line service */ break; case Read_Data: /* 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? */ not (mpx_flags [mpx_port] & FL_RDFILL)) /* and not filling it? */ mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate buffer availability */ } mpx_ibuf = ST_OK; /* clear the status word */ mpx_state = Idle_State; /* and idle the 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 = TO_WORD (ch, 0); /* 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 Download_Executable: /* sink data from host */ if (mpx_iolen <= 0) { /* final entry? */ mpx_state = Idle_State; /* 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? */ activate_unit (&mpx_cntl, Command); /* schedule completion */ } break; default: /* no other entries allowed */ return SCPE_IERR; /* simulator error! */ } break; } if (last_state != mpx_state) { /* if a state change occurred */ tprintf (mpx_dev, TRACE_INCO, trace_formats [mpx_has_param] [mpx_state], fmt_command [mpx_cmd], (mpx_has_param ? mpx_param : 0), mpx_ibuf); tprintf (mpx_dev, TRACE_STATE, "Controller transitioned from %s state to %s state\n", state_name [last_state], state_name [mpx_state]); } if (set_flag) { mpx.flag_buffer = SET; /* set the flag buffer */ io_assert_ENF (&mpx_dib); /* and flag flip-flops */ tprintf (mpx_dev, TRACE_CSRW, "Device flag set\n"); } return SCPE_OK; } /* Multiplexer line service. The line service routine runs only when there are characters to transmit or receive. It is scheduled either at a realistic rate corresponding to the programmed baud rate of the port to be serviced, or at a somewhat faster optimized rate. It is entered when a port buffer is ready for output, when the poll routine determines that there are characters ready for input, or while waiting for an ACK to complete an ENQ/ACK handshake. It is stopped when there are no more characters to output or input. When a line is quiescent, this routine does not run. "Fast timing" mode enables two 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, 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 terminal multiplexer library is called to retrieve the waiting character from the line buffer. If a received character is not available, then the port is checked to see if an ACK is expected in reply to an earlier ENQ. If so, then the last wait time is doubled, and the service is rescheduled. However, if the new wait time is longer than the current poll time, service rescheduling is abandoned in favor of the normal poll for received characters. 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. 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. 3. 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 transmit 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 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. 4. 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. 5. 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 " " in response to receiving a , 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. 6. Because ENQ/ACK handshaking is handled entirely on the multiplexer card with no OS involvement, LOCALACK processing under simulation 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 uint32 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 = (1u << data_bits) - 1; /* mask for data bits */ uint32 buffer_count, write_count; int32 chx; uint8 ch = 0; uint32 termination = RS_NO_ETC; /* initialize to "no end of text character seen" */ t_bool xmit_loop = not (mpx_flags [port] & (FL_WAITACK | FL_XOFF)); /* bypass if output is suspended */ t_stat status = SCPE_OK; tprintf (mpx_dev, TRACE_SERV, "Port %d service entered\n", port); /* Transmission service */ write_count = buf_len (iowrite, port, get); /* get the output buffer length */ if (mpx_ldsc [port].xmte == 0) /* if the transmit buffer is full */ tprintf (mpx_dev, TRACE_XFER, "Port %d transmission stalled for full buffer\n", port); else while (xmit_loop && write_count > 0) { /* otherwise loop while characters are available to output */ if (not (mpx_flags [port] & FL_WREMPT)) { /* if the buffer has not started emptying */ chx = TO_WORD (buf_get (iowrite, port), 0); /* then get the header value and position it */ if (uptr->flags & UNIT_LOCALACK /* if configured for local ENQ/ACK */ || chx & WR_NO_ENQACK /* or ENQ/ACK is suppressed for this write */ || not (mpx_config [port] & SK_ENQACK)) /* or the port is not configured for ENQ/ACK */ mpx_flags [port] &= ~FL_DO_ENQACK; /* then clear the operation flag */ else /* otherwise */ mpx_flags [port] |= FL_DO_ENQACK; /* set the flag to do ENQ/ACK */ continue; /* continue with the first output character */ } else if (mpx_enq_cntr [port] >= ENQ_LIMIT) { /* otherwise if the terminal is ready for an ENQ */ ch = ENQ; /* then send one */ status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* to the multiplexer port */ if (status == SCPE_OK || status == SCPE_LOST) { /* if transmission succeeded or was 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 the "waiting for ACK" flag */ uptr->wait = activate_unit (uptr, Receive); /* schedule the ACK reception */ } xmit_loop = FALSE; /* stop further transmission until ACK is received */ } else { /* otherwise */ ch = buf_get (iowrite, port) & data_mask; /* get the next character and mask to the bit width */ status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */ if (status == SCPE_OK || status == SCPE_LOST) { /* if transmission succeeded or was ignored */ write_count = write_count - 1; /* then count the character */ if (mpx_flags [port] & FL_DO_ENQACK) /* if ENQ/ACK handshaking is enabled */ mpx_enq_cntr [port] += 1; /* then bump the character counter */ xmit_loop = (not (mpx_dev.flags & DEV_REALTIME) /* continue transmission if enabled */ && mpx_ldsc [port].xmte != 0); /* and buffer space is available */ } else /* otherwise transmission failed */ xmit_loop = FALSE; /* so give up on further transfers */ } if (status == SCPE_OK) /* if transmission succeeded */ tprintf (mpx_dev, TRACE_XFER, "Port %d character %s transmitted\n", port, fmt_char (ch)); else { /* otherwise report the failure */ tprintf (mpx_dev, TRACE_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) { /* if the buffer is now empty */ buf_free (iowrite, port); /* then free it */ write_count = buf_len (iowrite, port, get); /* get the next output buffer length */ if (mpx_state == Idle_State) /* if the controller is idle */ cntl_service (&mpx_cntl); /* then check for an unsolicited interrupt */ } } /* 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 (TRUE) { /* loop while there are characters to read */ chx = tmxr_getc_ln (&mpx_ldsc [port]); /* get a new character */ if (chx == 0) /* if no character is present */ if (mpx_flags [port] & FL_WAITACK && ch != ENQ) { /* then if the port has been waiting for an ACK */ tmxr_poll_rx (&mpx_desc); /* then poll the port to see if it has arrived early */ chx = tmxr_getc_ln (&mpx_ldsc [port]); /* try to pick up the ACK */ if (chx == 0) { /* if an ACK is expected but has not arrived */ uptr->wait = uptr->wait * 2; /* then double the wait time for the next check */ if (uptr->wait < mpx_poll.wait) { /* if the new wait is shorter than the standard poll wait */ sim_activate (uptr, uptr->wait); /* then reschedule the line service */ tprintf (mpx_dev, TRACE_SERV, "Port %d delay %d service rescheduled for ACK\n", port, uptr->wait); } else /* otherwise */ uptr->wait = 0; /* wait for the standard poll service */ break; /* quit the reception loop */ } } else /* otherwise no ACK is expected */ break; /* so quit the reception loop */ if (chx & SCPE_BREAK) { /* if a BREAK was detected */ mpx_flags [port] |= FL_BREAK; /* then set break status */ tprintf (mpx_dev, TRACE_XFER, "Break detected\n"); if (mpx_state == Idle_State) /* if the controller is idle */ cntl_service (&mpx_cntl); /* then generate the Break Received interrupt */ continue; /* discard the NUL that accompanied the BREAK */ } ch = (uint8) (chx & data_mask); /* mask the character to the data bits */ if (ch == XOFF /* if the character is an XOFF */ && mpx_flowcntl [port] & FC_XONXOFF) { /* and XON/XOFF handshaking is enabled */ mpx_flags [port] |= FL_XOFF; /* then suspend transmission */ tprintf (mpx_dev, TRACE_XFER, "Port %d character XOFF suspends transmission\n", port); continue; /* continue with the next character */ } else if (ch == XON /* otherwise if the character is an XON */ && mpx_flags [port] & FL_XOFF) { /* and transmission is currently suspended */ mpx_flags [port] &= ~FL_XOFF; /* then resume sending */ tprintf (mpx_dev, TRACE_XFER, "Port %d character XON resumes transmission\n", port); continue; /* continue with the next character */ } tprintf (mpx_dev, TRACE_XFER, "Port %d character %s received\n", port, fmt_char (ch)); if (ch == ACK && mpx_flags [port] & FL_WAITACK) { /* if it's an ACK and the port is waiting for it */ mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* then clear the wait flag */ uptr->wait = 0; /* and the event timer */ break; /* end reception for this cycle */ } else if (buffer_count == 0 /* otherwise if there are no free buffers available */ && not (mpx_flags [port] & FL_RDFILL)) /* and the last buffer is not filling */ mpx_flags [port] |= FL_RDOVFLOW; /* then set the buffer overflow flag */ else { /* otherwise a buffer is available */ if (rt & RT_ENAB_EDIT) /* if editing is enabled */ if (ch == BS) { /* then if the character is a backspace */ if (buf_len (ioread, port, put) > 0) /* then if the buffer is not empty */ buf_remove (ioread, port); /* then remove the last character */ if (rt & RT_ENAB_ECHO) { /* if echo is enabled */ tmxr_putc_ln (&mpx_ldsc [port], BS); /* then echo the BS */ if (not (mpx_dev.flags & DEV_REALTIME)) /* if the mode is not real-time */ tmxr_linemsg (&mpx_ldsc [port], " \b"); /* then also echo a space and and a BS */ } continue; /* continue with the next character */ } else if (ch == DEL) { /* otherwise if the character is a DEL */ buf_cancel (ioread, port, put); /* then cancel the current buffer */ if (rt & RT_ENAB_ECHO) { /* if echo is enabled */ if (not (mpx_dev.flags & DEV_REALTIME)) /* then if the mode is not real-time */ tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* then echo a backslash */ tmxr_linemsg (&mpx_ldsc [port], "\r\n"); /* echo a CR and a LF */ } continue; /* continue with the next character */ } if (uptr->flags & UNIT_CAPSLOCK) /* if the CAPS LOCK key is down */ ch = (uint8) toupper (ch); /* then convert lower to upper case */ if (rt & RT_ENAB_ECHO) /* if echo is enabled */ tmxr_putc_ln (&mpx_ldsc [port], ch); /* then echo the character */ if (rt & RT_END_ON_CHAR) /* if end-on-character is enabled */ if (ch == CR && rt & RT_END_ON_CR) { /* then if the end-on-CR condition is satisfied */ if (rt & RT_ENAB_ECHO) /* then if echo is enabled */ tmxr_linemsg (&mpx_ldsc [port], "\n"); /* then send a LF */ termination = RS_ETC_CR; /* set the termination condition */ } else if (ch == RS && rt & RT_END_ON_RS) /* otherwise if the end-on-RS condition is satisfied */ termination = RS_ETC_RS; /* then set the termination condition */ else if (ch == EOT && rt & RT_END_ON_EOT) /* otherwise if the end-on-EOT condition is satisfied */ termination = RS_ETC_EOT; /* then set the termination condition */ else if (ch == DC2 && rt & RT_END_ON_DC2) /* otherwise if the end-on-DC2 condition is satisfied */ termination = RS_ETC_DC2; /* then set the termination condition */ if (termination == RS_NO_ETC) { /* if no termination condition was seen */ buf_put (ioread, port, ch); /* then add the character to the buffer */ mpx_charcnt [port]++; /* and count it */ } if (rt & RT_END_ON_CNT /* if end-on-count is enabled */ && mpx_charcnt [port] == mpx_termcnt [port]) { /* and the termination count has been reached */ termination = 0; /* then set the termination condition */ mpx_charcnt [port] = 0; /* and clear the current character count */ if (mpx_flags [port] & FL_ALERT) { /* if the buffer was terminated by explicit command */ mpx_flags [port] &= ~FL_ALERT; /* then clear the alert flag */ mpx_termcnt [port] = RD_BUF_LIMIT; /* and reset the termination character count */ } } else if (buf_len (ioread, port, put) == RD_BUF_LIMIT) /* otherwise if the buffer is now full */ termination |= RS_PARTIAL; /* then set the partial-buffer flag */ if (termination == RS_NO_ETC) /* if there is no termination condition */ if (mpx_dev.flags & DEV_REALTIME /* then if the mode is real-time */ || buffer_count <= 1) /* or we're filling the last buffer */ break; /* then end reception for this cycle */ else /* otherwise */ continue; /* continue reception */ else { /* otherwise a termination condition exists */ if (termination & RS_PARTIAL) /* so report if a full buffer caused termination */ tprintf (mpx_dev, TRACE_XFER, "Port %d read terminated on buffer full\n", port); else if (rt & RT_END_ON_CHAR) /* or if the end-of-text character was seen */ tprintf (mpx_dev, TRACE_XFER, "Port %d read terminated on character %s\n", port, fmt_char (ch)); else /* or if the maximum count was received */ tprintf (mpx_dev, TRACE_XFER, "Port %d read terminated on count %d\n", port, mpx_termcnt [port]); if (buf_len (ioread, port, put) == 0) { /* if termination occurred with the no characters stored */ buf_put (ioread, port, 0); /* then do a dummy put to reserve the header */ buf_remove (ioread, port); /* and then back out the character */ } buf_term (ioread, port, UPPER_BYTE (termination)); /* terminate the buffer and set the header */ if (buf_avail (ioread, port) == 1) /* if operating on the first read buffer */ mpx_flags [port] |= FL_HAVEBUF; /* then indicate availability of the remaining buffer */ if (mpx_state == Idle_State) /* if the controller is idle */ cntl_service (&mpx_cntl); /* then generate the Read Buffer Available interrupt */ break; /* quit to allow the buffer to be transferred */ } } } /* continue reception while conditions permit */ /* Housekeeping */ tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */ if (write_count > 0 /* if there are more characters to transmit */ && not (mpx_flags [port] & (FL_WAITACK | FL_XOFF))) /* and transmission is not suspended */ activate_unit (uptr, Send); /* then reschedule the service */ else if (tmxr_rqln (&mpx_ldsc [port])) /* otherwise if there are more characters to receive */ activate_unit (uptr, Receive); /* then reschedule the service */ else if (uptr->wait == 0) /* otherwise if not waiting for an ACK */ tprintf (mpx_dev, TRACE_SERV, "Port %d service stopped\n", port); return status; /* return the service 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. If a Fast Binary Read command is executing, the controller is dedicated to port 0 until it is reset, and input polling is handled in the controller service. Otherwise, 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; tprintf (mpx_dev, TRACE_PSERV, "Poll delay %d service entered\n", uptr->wait); poll_connection (); /* check for new connections */ if (mpx_cmd != Fast_Binary_Read) { /* if a fast binary read is not in progress */ tmxr_poll_rx (&mpx_desc); /* then poll for input */ for (i = 0; i < MPX_PORTS; i++) { /* check lines */ if (mpx_flags [i] & FL_WAITACK) { /* if the port is waiting for an ACK */ mpx_ack_wait [i] = mpx_ack_wait [i] + 1; /* then increment the ACK wait timer */ if (mpx_ack_wait [i] > ACK_LIMIT) { /* if the wait has timed out */ status = tmxr_putc_ln (&mpx_ldsc [i], ENQ); /* then send the ENQ again */ if (status == SCPE_OK) { /* if the character was accepted */ tmxr_poll_tx (&mpx_desc); /* then transmit it now */ mpx_ack_wait [i] = 0; /* and reset the timer */ tprintf (mpx_dev, TRACE_XFER, "Port %d character ENQ retransmitted\n", i); } } } if (tmxr_rqln (&mpx_ldsc [i])) /* chars available? */ activate_unit (&mpx_unit [i], Receive); /* activate I/O service */ } } if (uptr->wait == POLL_FIRST) /* first poll? */ uptr->wait = hp_sync_poll (INITIAL); /* initial synchronization */ else /* not first */ uptr->wait = hp_sync_poll (SERVICE); /* continue synchronization */ sim_activate (uptr, uptr->wait); /* continue polling */ tprintf (mpx_dev, TRACE_PSERV, "Poll delay %d service rescheduled\n", uptr->wait); 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 If a power-on reset (RESET -P) is being done, the original FASTTIME settings are restored, in case they have been changed by the user. 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; memcpy (times [Fast_Time], fast_times, sizeof fast_times); /* reset the initial fast event delays */ controller_reset (); /* reset the firmware to the power-on defaults */ } mpx_ibuf = 0; /* clear input buffer */ if (mpx_poll.flags & UNIT_ATT) { /* network attached? */ mpx_poll.wait = POLL_FIRST; /* set up poll */ sim_activate_abs (&mpx_poll, mpx_poll.wait); /* restart poll immediately */ } else sim_cancel (&mpx_poll); /* else stop poll */ return SCPE_OK; } /* Attach the multiplexer or a multiplexer line. We are called by the ATTACH MPX command to attach the multiplexer to the network listening port indicated by and by ATTACH MPX to attach line to serial port . For the former, it is logically 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 image 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. 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 the listening port was attached when the SAVE was done. If the user attempts to attach unit 8 (controller) or 9 directly, the command will be rejected by SCP because the units are disabled. 2. If the poll unit was attached during a SAVE, it will be enabled as part of RESTORE processing. We always unilaterally disable this unit to ensure that it remains hidden. 3. ATTACH MPX will pass a pointer unit 0. This is because SCP treats ATTACH MPX as equivalent to ATTACH MPX0. The "tmxr_attach_unit" routine differentiates these cases by examining the "sim_ref_type" global to see if a device was referenced and, if so, applying the request to the poll unit. */ static t_stat mpx_attach (UNIT *uptr, char *cptr) { t_stat status; status = tmxr_attach_unit (&mpx_desc, &mpx_poll, uptr, cptr); /* attach the line */ if (status == SCPE_OK) { /* if the attach succeeded */ mpx_poll.wait = POLL_FIRST; /* then set up the poll */ sim_activate (&mpx_poll, mpx_poll.wait); /* and start it */ tprintf (mpx_dev, TRACE_PSERV, "Poll delay %d service scheduled\n", mpx_poll.wait); mpx_poll.flags |= UNIT_DIS; /* ensure that the poll unit remains disabled */ } return status; /* return the result of the attach */ } /* Detach the multiplexer or a multiplexer line. We are called by the DETACH MPX command to detach the listening port and all Telnet sessions and by the DETACH MPX to detach a serial port from line . 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. Detaching the listening port will disconnect all active Telnet connections but will not disturb any lines connected to serial ports. Therefore, we look for newly freed lines and clean them up. If all lines are now free, then we terminate the poll. 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. A RESTORE command will call us for unit 9 if it is attached. In this case, the terminal channels will have already been rescheduled as appropriate, so canceling them is skipped. 3. Because DETACH MPX will pass unit 0, the "tmxr_detach_unit" routine examines the "sim_ref_type" global to see if a device was referenced and, if so, applies the request to the poll unit. The routine will reject an attempt to detach unit 8 (controller) or unit 9 (poll) directly. */ static t_stat mpx_detach (UNIT *uptr) { t_stat status; uint32 line; status = tmxr_detach_unit (&mpx_desc, &mpx_poll, uptr); /* detach the line */ if (status == SCPE_OK) { /* if the detach succeeded */ for (line = 0; line < MPX_PORTS; line++) /* then look for Telnet connections */ if (tmxr_line_free (&mpx_ldsc [line])) { /* if the line is now free */ mpx_ldsc [line].rcve = 0; /* then disable reception */ sim_cancel (&mpx_unit [line]); /* and cancel any scheduled I/O */ } if (tmxr_mux_free (&mpx_desc)) { /* if all lines are now free */ sim_cancel (&mpx_poll); /* then stop polling */ tprintf (mpx_dev, TRACE_PSERV, "Poll service stopped\n"); } } return status; /* return the status of the detach */ } /* Set the device modes. The device flag implied by the DEVICE_MODE "value" passed to the routine is set or cleared in the device specified by the "desc" parameter. The unit and character pointers are not used. */ static t_stat set_mode (UNIT *uptr, int32 value, char *cptr, void *desc) { DEVICE * const dptr = (DEVICE *) desc; /* a pointer to the device */ switch ((DEVICE_MODE) value) { /* dispatch based on the mode to set */ case Fast_Time: /* entering optimized timing mode */ dptr->flags &= ~DEV_REALTIME; /* clears the real-time flag */ break; case Real_Time: /* entering realistic timing mode */ dptr->flags |= DEV_REALTIME; /* sets the real-time flag */ break; } return SCPE_OK; } /* Set the firmware revision. Currently, we support only revision C, so the MTAB entry does not have an "mstring" entry. If we add revision D support, an "mstring" entry of "REV" will enable changing the firmware revision. */ static t_stat set_revision (UNIT *uptr, int32 value, char *cptr, void *desc) { if (cptr == NULL /* if there is no parameter */ || *cptr < 'C' || *cptr > 'D' /* or it is not C or D */ || *(cptr + 1) != NUL) /* or it is not a single character */ return SCPE_ARG; /* then the argument is invalid */ else { /* otherwise */ 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 the device modes. The output stream and device pointer are passed in the "st" and "desc" parameters, respectively. The value and unit pointer are not used. */ static t_stat show_mode (FILE *st, UNIT *uptr, int32 value, void *desc) { const DEVICE * const dptr = (const DEVICE *) desc; /* a pointer to the device */ if (dptr->flags & DEV_REALTIME) /* if the real-time flag is set */ fputs ("realistic timing", st); /* then report that we are using realistic timing */ else /* otherwise */ fputs ("fast timing", st); /* report that we are using optimized timing */ return SCPE_OK; } /* Show firmware revision */ static t_stat show_revision (FILE *st, UNIT *uptr, int32 value, 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 value, 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, value, desc); /* report connection count */ return SCPE_OK; } /* Multiplexer local utility routines */ /* Activate a port or controller unit. The specified unit is activated to receive or send a character or run the microprocessor controller. The reason for the activation is specified by the "reason" parameter. If the multiplexer is in real-time mode, and a port unit is being activated to send or receive a character, the delay time will be zero. This is an indication that the previously initialized baud rate index should be used to look up the corresponding character delay used to schedule the event. In fast-time mode, the delay time is used as specified. If tracing is enabled, the activation is logged to the debug file. The activation delay used is returned as the value of the function. Implementation notes: 1. Testing for FASTTIME vs. REALTIME and using explicit array indices is faster than calculating the first subscript logically. */ static int32 activate_unit (UNIT *uptr, ACTIVATION reason) { int32 delay; if (mpx_dev.flags & DEV_REALTIME) /* if realistic timing is being used */ delay = times [Real_Time] [reason]; /* then get the indicated real-time event delay */ else /* otherwise */ delay = times [Fast_Time] [reason]; /* get the indicated optimized event delay */ if (delay == 0) /* if the delay is zero */ delay = rates [uptr->rate_index]; /* then schedule the realistic data time */ if (uptr == &mpx_cntl) tprintf (mpx_dev, TRACE_SERV, "Controller delay %d service scheduled\n", delay); else tprintf (mpx_dev, TRACE_SERV, "Port %d delay %d service scheduled\n", (int) (uptr - mpx_unit), delay); sim_activate (uptr, delay); /* activate the unit */ return delay; /* and return the activation delay */ } /* 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 new_rate; t_bool set_flag = TRUE; /* flag is normally set on completion */ CNTLR_STATE next_state = Idle_State; /* command normally executes to completion */ tprintf (mpx_dev, TRACE_CMD, "%s command key %d executing\n", fmt_command [mpx_cmd], mpx_portkey); mpx_ibuf = ST_OK; /* return status is normally OK */ switch (mpx_cmd) { case No_Operation: /* no operation */ break; /* just ignore */ case Reset_Firmware: /* reset firmware */ controller_reset (); /* reset program variables */ mpx_ibuf = ST_TEST_OK; /* return self-test OK code */ break; case Enable_UI: mpx_uien = TRUE; /* enable unsolicited interrupts */ activate_unit (&mpx_cntl, Status); /* and schedule controller for UI check */ set_flag = FALSE; /* do not set the flag at completion */ break; case Disable_UI: mpx_uien = FALSE; /* disable unsolicited interrupts */ break; case Abort_DMA_Transfer: 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; case Acknowledge: /* 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 */ tprintf (mpx_dev, TRACE_CMD, "Write buffer size %d\n", mpx_ibuf); break; case UI_RDBUF_AVAIL: /* read buffer notification */ mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */ mpx_ibuf = TO_WORD (buf_get (ioread, mpx_port), /* 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 */ } tprintf (mpx_dev, TRACE_CMD, "Read buffer %s%s termination | size %d\n", fmt_bitset (mpx_ibuf, buffer_format), end_of_text [GET_ETC (mpx_ibuf)], GET_READCOUNT (mpx_ibuf)); 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 Cancel_First: /* 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 (not (mpx_flags [port] & FL_RDFILL)) /* otherwise if the other buffer is not filling */ mpx_flags [port] |= FL_HAVEBUF; /* then indicate buffer availability */ } break; case 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 Fast_Binary_Read: /* fast binary read */ for (port = 0; port < MPX_PORTS; port++) sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */ mpx_flags [0] = 0; /* clear port 0 state flags */ mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */ mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */ tmxr_putc_ln (&mpx_ldsc [0], ESC); /* send the fast binary read */ tmxr_putc_ln (&mpx_ldsc [0], 'e'); /* escape sequence to port 0 */ tmxr_poll_tx (&mpx_desc); /* and flush the output */ next_state = Execution_State; /* set execution state */ activate_unit (&mpx_cntl, Status); /* schedule the transfer */ break; case Request_Write_Buffer: /* 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 */ } tprintf (mpx_dev, TRACE_CMD, "Request size %d\n", mpx_param); break; case Write_Data: /* 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 = Execution_State; /* set execution state */ } tprintf (mpx_dev, TRACE_CMD, "Write length %d | %s\n", mpx_iolen, fmt_bitset (mpx_param, write_format)); break; case Set_Port_Key: /* set port key and configuration */ port = GET_PORT (mpx_param); /* get target port number */ mpx_key [port] = mpx_portkey; /* set port key */ mpx_config [port] = mpx_param; /* set port configuration word */ new_rate = GET_BAUDRATE (mpx_param); /* get the new baud rate code */ if (new_rate != 0) /* if the rate is to be changed */ mpx_unit [port].rate_index = new_rate; /* then set the index to the rate code */ mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */ tprintf (mpx_dev, TRACE_CMD, "Configuration is %s%u bits | %s | %s parity | %s\n", fmt_bitset (mpx_param, set_key_format), bits_per_char [GET_BPC (mpx_param)], stop_bits [GET_STOPBITS (mpx_param)], parity [GET_PARITY (mpx_param)], baud [GET_BAUDRATE (mpx_param)]); break; case Set_Receive_Type: /* set receive type */ port = key_to_port (mpx_portkey); /* get port */ if (port >= 0) /* port defined? */ mpx_rcvtype [port] = mpx_param; /* save port receive type */ tprintf (mpx_dev, TRACE_CMD, "End on %s\n", fmt_bitset (mpx_param, set_receive_format)); break; case Set_Character_Count: /* set character count */ port = key_to_port (mpx_portkey); /* get port */ if (port >= 0) { /* port defined? */ mpx_termcnt [port] = mpx_param; /* save port termination character count */ mpx_charcnt [port] = 0; /* and clear the current character count */ } tprintf (mpx_dev, TRACE_CMD, "Termination count is %d\n", mpx_param); break; case Set_Flow_Control: /* 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 */ } tprintf (mpx_dev, TRACE_CMD, "Flow control is %s\n", fmt_bitset (mpx_param, set_flow_format)); break; case Read_Data: /* read from buffer */ tprintf (mpx_dev, TRACE_CMD, "Read length %d\n", mpx_param); 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 */ activate_unit (&mpx_cntl, Backplane); /* schedule the transfer */ next_state = Execution_State; /* set execution state */ set_flag = FALSE; /* no flag until word ready */ } break; case Download_Executable: /* Download executable */ mpx_iolen = mpx_param; /* save request length */ next_state = Execution_State; /* set execution state */ tprintf (mpx_dev, TRACE_CMD, "Download count is %d\n", mpx_iolen); break; case Connect_Line: /* connect modem line */ case Disconnect_Line: /* disconnect modem line */ case Set_Modem_Loopback: /* enable/disable modem loopback */ mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */ break; case Get_Status: /* get modem status */ mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */ break; case Terminate_Receive_Buffer: /* 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 VCP_Put_Byte: /* VCP put byte */ case VCP_Put_Buffer: /* VCP put buffer */ case VCP_Get_Byte: /* VCP get byte */ case VCP_Get_Buffer: /* VCP get buffer */ case VCP_Exit_Mode: /* Exit VCP mode */ case VCP_Enter_Mode: /* Enter VCP mode */ break; } 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_State; /* idle state */ mpx_cmd = No_Operation; /* clear command */ mpx_param = 0; /* clear parameter */ mpx_has_param = FALSE; /* reset parameter flag */ 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 [i] = SK_PWRUP_0; /* port 0 is separate from 1-7 */ else mpx_config [i] = SK_PWRUP_1 | i; mpx_unit [i].rate_index = GET_BAUDRATE (mpx_config [i]); /* set the baud rate index */ 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 */ sim_cancel (&mpx_unit [i]); /* cancel line I/O */ } sim_cancel (&mpx_cntl); /* cancel controller */ return; } /* 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 */ } /* Map the command opcode. The opcode in the high byte of the supplied "command" parameter is mapped to a controller opcode and returned to the caller. If the command opcode is not defined, the No_Operation code is returned. The command opcodes to which the microprocessor controller responds are disjoint. This makes it awkward to obtain the command names for tracing, as well as requiring a series of compare-and-jumps to decode the commands. Therefore, it is desirable to remap the command opcodes into a contiguous series of controller opcodes. The controller allows 48 one-word (100-series) commands and 48 two-word (300-series) commands, which would require a 96-way translation table if implemented directly. This would be cumbersome. Instead, we use a 16-way table for the relatively contiguous two-word command set and rely on direct mapping for the remaining contiguous subsets of the one-word command set. The only difficulty is the "Disable Unsolicited Interrupts" and "Abort DMA Transfer" commands, which share command opcode 103 and are differentiated by the key values (1 and 2, respectively). These must be decoded with explicit tests. However, we take advantage of the fact that the microprocessor firmware only checks for the key value 1 and assumes that any other value is a request to abort DMA, and that the "Disable Unsolicited Interrupts" command is issued at every entry into the RTE multiplexer driver and so is very frequently seen. Implementation notes: 1. The resulting map implementation, which looks complex, actually reduces to quite efficient machine code. 2. The VCP commands are not actually implemented in the 12792 firmware (they are reserved for the 12040 A/L-Series multiplexer), so we decode them last, as that path should never be called. */ static CNTLR_OPCODE map_opcode (uint32 command) { static const CNTLR_OPCODE remap [] = { /* translation table for opcodes 301-320 */ Request_Write_Buffer, /* 301 = Request write buffer */ Write_Data, /* 302 = Write data to buffer */ Set_Port_Key, /* 303 = Set port key */ Set_Receive_Type, /* 304 = Set receive type */ Set_Character_Count, /* 305 = Set character count */ Set_Flow_Control, /* 306 = Set flow control */ Read_Data, /* 307 = Read data from buffer */ Download_Executable, /* 310 = Download executable */ Connect_Line, /* 311 = Connect line */ Disconnect_Line, /* 312 = Disconnect line */ No_Operation, /* 313 = (undefined) */ No_Operation, /* 314 = (undefined) */ Get_Status, /* 315 = Get modem/port status */ Set_Modem_Loopback, /* 316 = Enable/disable modem loopback */ No_Operation, /* 317 = (undefined) */ Terminate_Receive_Buffer /* 320 = Terminate active receive buffer */ }; CNTLR_OPCODE opcode; const uint32 cmd_code = CN_OPCODE (command); if (command == TO_WORD (CMD_DISABLE, SUBCMD_UI)) /* if this is the "Disable UI" code */ opcode = Disable_UI; /* then translate it explicitly */ else if (cmd_code >= CMD_NOP && cmd_code <= CMD_BINARY_READ) /* otherwise if code is 100-107 */ opcode = (CNTLR_OPCODE) (cmd_code - CMD_NOP) + No_Operation; /* then translate it directly */ else if (cmd_code >= CMD_REQ_WRITE && cmd_code <= CMD_TERM_BUF) /* otherwise if code is 301-320 */ opcode = remap [cmd_code - CMD_REQ_WRITE]; /* then translate it via the lookup table */ else if (cmd_code >= CMD_VCP_PUT && cmd_code <= CMD_VCP_EXIT) /* otherwise if code is 140-144 */ opcode = (CNTLR_OPCODE) (cmd_code - CMD_VCP_PUT) + VCP_Put_Byte; /* then translate it directly */ else if (cmd_code == CMD_VCP_ENTER) /* otherwise if this is the "Enter VCP" code */ opcode = VCP_Enter_Mode; /* then translate it explicitly */ else /* otherwise the code is unknown */ opcode = No_Operation; /* so ignore it */ return opcode; /* return the translated opcode */ } /* 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 not 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 not 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 uint32 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, TRACE_FIFO, "Port %d character %s get from %s buffer [%d]\n", port, fmt_char (ch), io_op [rw], index); else tprintf (mpx_dev, TRACE_FIFO, "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 (not (mpx_flags [port] & filling_flags [rw])) { /* 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, TRACE_FIFO, "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, TRACE_FIFO, "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, TRACE_FIFO, "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, TRACE_FIFO, "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 (not (mpx_flags [port] & filling_flags [rw])) /* 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, TRACE_FIFO, "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 (not (mpx_flags [port] & filling_flags [rw])) /* 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, TRACE_FIFO, "Port %d cancelled %s buffer\n", port, io_op [rw]); return; } /* Get the buffer length. The current length of the selected buffer (put or get) is returned. For ease of use, the returned length does NOT include the header byte, i.e., it reflects only the characters contained in the buffer. If the put buffer is selected, and the buffer is filling, or the get buffer is selected, and the buffer is not emptying, then subtract one from the length for the allocated header. */ static uint32 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which) { int32 length; if (which == put) length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */ ((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */ else { length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */ if (length && not (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 */ }