- This release of the HP 3000 simulator adds the following device simulation: - 30209A Line Printer Controller with One 2607/13/17/18 Line Printer The simulation supports the use of custom VFU tape images, as well as the built-in HP-standard VFU tape. The simulated device name is "LP". The full set of configurable options is detailed in a new section of the HP 3000 Simulator User's Guide. In addition, the preconfigured MPE-V/R disc image has been updated to add the following features: - The MPE cold load command files attach the line printer to the "lp.txt" output file and specify the "-n" option to clear the file before use. - Preinstalled User-Defined Commands (UDCs) provide access to the COBOL 74 compiler with the MPE-V/E :COBOLII, :COBOLIIPREP, and :COBOLIIGO commands, and to the COBOL 85 compiler with :COBOLIIX, :COBOLIIXPREP, and :COBOLIIXGO. However, see the implementation note below. -------------------- Implementation Notes -------------------- - MPE requires a line printer, so it is recommended that the MPE startup simulator command file include an ATTACH LP <filename> command to load paper into the printer before cold loading. If the printer is not attached, it will appear to MPE to be out of paper. - The line printer terminates each print line with an HP-standard CR/LF pair. If the output file is to be retained as a text file on a Unix system, removal of the carriage returns, e.g., via the "dos2unix" utility, may be desirable. - The simulator currently does not provide the HP 32234A COBOL II firmware instructions, so programs generated by the COBOLII compiler will abort at run time with an "ILLEGAL INSTRUCTION" error. Programs generated by the COBOL compiler do not use these instructions and therefore are not affected. ---------- Bugs Fixed ---------- 1. PROBLEM: The effective address of a byte pointer with a negative index is calculated incorrectly. VERSION: Release 1 OBSERVATION: Defining a :WELCOME message in MPE appears to work, but when the next logon attempts to print the message, an infinite number of CRLFs are printed instead. CAUSE: The welcome message is stored in an extra data segment. The format for each message line is a line length stored in the lower byte of the word preceding the message string. The code defines BYTE POINTER NEXTLINE and points NEXTLINE to the first message character. The line length is set with NEXTLINE(-1) := IOCOUNT. This generates a LOAD <IOCOUNT> ; LDXN 1 ; STB <NEXTLINE>,I,X sequence. In the "cpu_ea" routine, the indexing adds the X register value (-1) to the byte pointer (NEXTLINE). This causes an overflow that is not masked to 16 bits. For a word access, this displacement is added to the base register and then masked to 16 bits, which gives the correct value. However, for byte accesses, the displacement is divided by 2 and then added, and the sum is masked. Dividing by 2 shifts the overflow bit into the MSB, causing the addition result to be off by 32K. The STB goes to the wrong location, the original zero in the length byte location is retained, and when the welcome message is printed, a zero-length line is printed, and the byte pointer is incremented by zero, so the null line is printed forever. RESOLUTION: Modify "cpu_ea" (hp3000_cpu.c) to mask indexed displacements to 16 bits after adding the X register value. STATUS: Fixed in Release 2. 2. PROBLEM: An SMSK instruction may clear the interrupt mask flip-flop of a device that specifies that it is should be "always enabled." VERSION: Release 1 OBSERVATION: If the TOS word is zero, an SMSK instruction will clear the interrupt mask flip-flop of a device whose mask jumper is set to "E" (always enabled). CAUSE: In response to a DSETMASK signal, device interfaces set their interrupt mask flip-flops by "anding" the incoming data word with the interrupt mask jumper setting. The jumper setting value for "always enabled" is %177777, which sets the mask flip-flop in all cases, except when the data word is zero. RESOLUTION: Modify hp3000_atc.c, hp3000_ds.c, and hp3000_ms.c to set their mask flip-flops unconditionally if the jumper setting is "E". STATUS: Fixed in Release 2. 3. PROBLEM: The "SET <dev> INTMASK=<n>" command sets the wrong bit in the device interface's interrupt mask jumper setting. VERSION: Release 1 OBSERVATION: The interrupt mask jumper on a device interface is set by specifying the mask bit number in a "SET <dev> INTMASK=<n>" command. This sets a bit in the device's interrupt mask jumper word corresponding to the bit number requested. However, the bit numbering is incorrect; setting the jumper for bit 15, for example, sets bit 0 of the jumper word. Therefore, the interface's mask flip-flop is not set as expected when an SMSK instruction is executed. CAUSE: The bit numbers were counted from the wrong end of the word. RESOLUTION: Modify "hp_set_dib" and "hp_show_dib" (hp3000_sys.c) to number the bits from the MSB instead of the LSB. STATUS: Fixed in Release 2. 4. PROBLEM: The Multiplexer Channel is not generating the ACKSR signal correctly. VERSION: Release 1 OBSERVATION: The line printer controller hangs when an SIO chained write is performed. The first programmed write completes normally, but the second does not start. The channel is waiting for a service request that does not occur. CAUSE: The service request from the last write of the first block transfer is being cleared by an ACKSR generated by the Multiplexer Channel when it performs the IOCW fetch in State A for the second write request. The channel should omit this ACKSR when the previous I/O order was a chained read or write. However, the simulator is testing the order just fetched (Write) instead of the order that has just completed (Write Chained). RESOLUTION: Modify "mpx_service" (hp3000_mpx.c) to test the correct I/O order in State A. STATUS: Fixed in Release 2.
1342 lines
64 KiB
C
1342 lines
64 KiB
C
/* hp3000_ms.c: HP 3000 30215A Magnetic Tape Controller Interface simulator
|
|
|
|
Copyright (c) 2016, 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.
|
|
|
|
MS HP 30215A Magnetic Tape Controller Interface
|
|
|
|
09-Jun-16 JDB Added casts for ptrdiff_t to int32 values
|
|
08-Jun-16 JDB Corrected %d format to %u for unsigned values
|
|
16-May-16 JDB Fixed interrupt mask setting
|
|
13-May-16 JDB Modified for revised SCP API function parameter types
|
|
24-Mar-16 JDB Changed the buffer element type from uint8 to TL_BUFFER
|
|
21-Mar-16 JDB Changed uint16 types to HP_WORD
|
|
10-Nov-15 JDB First release version
|
|
26-Oct-14 JDB Passes the magnetic tape diagnostic (D433A)
|
|
10-Feb-13 JDB Created
|
|
|
|
References:
|
|
- 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Maintenance Manual
|
|
(30115-90001, June 1976)
|
|
- 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Microprogram Listing
|
|
(30115-90005, January 1974)
|
|
- Stand-Alone HP 30115A (7970B/E) Magnetic Tape (NRZI-PE) Diagnostic
|
|
(30115-90014, May 1976)
|
|
|
|
|
|
The HP 30115A Magnetic Tape Subsystem connects the 7970B/E 1/2-inch magnetic
|
|
tape drives to the HP 3000. The subsystem consists of a 30215A two-card tape
|
|
controller processor and controller interface, and from one to four HP 7970B
|
|
800-bpi NRZI or HP 7970E 1600-bpi PE drives. The two drive types can be
|
|
mixed on a single controller. The subsystem uses the Multiplexer Channel to
|
|
achieve a 36 KB/second (NRZI) or 72 KB/second (PE) transfer rate to the CPU.
|
|
|
|
This module simulates the controller interface. The controller processor
|
|
simulation is provided by the HP magnetic tape controller simulator library
|
|
(hp_tapelib). Rather than simulating the signal interaction specific to
|
|
these two cards, the HP tape library simulates an abstract controller having
|
|
an electrical interface modelled on the HP 13037 disc controller. The CPU
|
|
interface and tape controller interact via 16-bit data, flag, and function
|
|
buses. Commands, status, and data are exchanged across the data bus, with
|
|
the flag bus providing indications of the state of the interface and the
|
|
function bus indicating what actions the interface must take in response to
|
|
command processing by the controller. By specifying the controller type as
|
|
an HP 30215, the abstract controller adopts the personality of the HP 3000
|
|
tape controller.
|
|
|
|
While the interface and controller are idle, a drive unit that changes from
|
|
Not Ready to Ready status will cause an interrupt. This occurs when an
|
|
offline drive is put online (e.g., after mounting a tape) and when a
|
|
rewinding drive completes the action and is repositioned at the load point.
|
|
|
|
An interrupt also occurs if an error terminates the current command. The
|
|
cause of the interrupt is encoded in the status word. All error codes are
|
|
cleared to the No Error state whenever a new SIO program is started.
|
|
|
|
A new command may be rejected for one of several reasons:
|
|
|
|
- the unit is not ready for any command requiring tape motion
|
|
- the tape has no write ring and a write command is issued
|
|
- an illegal command opcode is issued
|
|
- illegal bits are set in the control word
|
|
- a command is issued while the controller is busy
|
|
- a TOGGLEOUTXFER signal asserts without a write data command in process
|
|
- a TOGGLEINXFER signal asserts without a read data command in process
|
|
- a PCONTSTB signal asserts with the input or output transfer flip-flops set
|
|
|
|
Examples of the last three rejection reasons are:
|
|
|
|
- a Write File Mark control order is followed by a write channel order
|
|
- a Write Record control order is followed by a read channel order
|
|
- a write channel order is followed by a Write Record control order
|
|
|
|
|
|
The tape interface responds to direct and programmed I/O instructions, as
|
|
follows:
|
|
|
|
Control Word Format (CIO):
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| M | R | - - - - - - - - - - - - - - |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Where:
|
|
|
|
M = programmed master clear
|
|
R = reset interrupts
|
|
|
|
|
|
Control Word Format (SIO Control):
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - - - - - - - - - - - | word 1
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - | unit | 0 0 0 0 | command code | word 2
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Unit:
|
|
|
|
00 = select unit 0
|
|
01 = select unit 1
|
|
10 = select unit 2
|
|
11 = select unit 3
|
|
|
|
Command code:
|
|
|
|
00 = Select Unit
|
|
04 = Write Record
|
|
05 = Write Gap
|
|
06 = Read Record
|
|
07 = Forward Space Record
|
|
10 = Rewind
|
|
11 = Rewind and Reset
|
|
12 = Backspace Record
|
|
13 = Backspace File
|
|
14 = Write Record with Zero Parity
|
|
15 = Write File Mark
|
|
16 = Read Record with CRCC
|
|
17 = Forward Space File
|
|
|
|
Control word 1 is not used.
|
|
|
|
The unit field is used only with the Select Unit command. Bits 8-11 must be
|
|
zero, or a Command Reject error will occur. Command codes 01-03 are reserved
|
|
and will cause a Command Reject error if specified. Codes 14 and 16 are used
|
|
for diagnostics only.
|
|
|
|
|
|
Status Word Format (TIO and SIO Status):
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| S | B | I | unit | E | P | R | L | D | W | M | err code | T |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Where:
|
|
|
|
S = SIO OK
|
|
B = byte count is odd
|
|
I = interrupt requested
|
|
E = end of tape
|
|
P = write protected
|
|
R = drive ready
|
|
L = load point
|
|
D = density 800/1600 (0/1)
|
|
W = write status (last operation was a write of any kind)
|
|
M = tape mark
|
|
T = 9-track drive/7-track drive (0/1)
|
|
|
|
Unit:
|
|
|
|
00 = reporting unit 0
|
|
01 = reporting unit 1
|
|
10 = reporting unit 2
|
|
11 = reporting unit 3
|
|
|
|
Error code:
|
|
|
|
000 = unit interrupt
|
|
001 = transfer error
|
|
010 = command reject error
|
|
011 = tape runaway error
|
|
100 = timing error
|
|
101 = tape error
|
|
110 = (reserved)
|
|
111 = no error
|
|
|
|
A unit interrupt occurs when a drive goes online or when a rewind operation
|
|
completes. A transfer error occurs when the channel asserts XFERERROR to
|
|
abort a transfer for a parity error or memory address out of bounds. These
|
|
two errors are generated by the interface and not by the HP tape library.
|
|
|
|
A timing error occurs when a read overrun or write underrun occurs. A tape
|
|
error occurs when a tape parity, CRC error, or multi-track error occurs.
|
|
Only these two errors may occur in the same transfer, with timing error
|
|
having priority. The other errors only occur independently.
|
|
|
|
|
|
Output Data Word Format (SIO Write):
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| data buffer register value |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
Input Data Word Format (SIO Read):
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| data buffer register value |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
The interface does not respond to WIO or RIO instructions.
|
|
|
|
Tape read or write commands may transfer up to 4K words with a single SIO
|
|
Read or Write order. Chained orders are necessary if longer transfers are
|
|
required. However, if a chained read completes with a record shorter than
|
|
the transfer length, a Command Reject will occur.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. In hardware, each tape drive has four buttons numbered 0 to 3 that select
|
|
the unit number to which the drive responds, plus an OFF button that
|
|
inhibits drive selection (effectively disconnecting the drive from the
|
|
controller). Pressing a numbered button changes the unit number without
|
|
altering the tape position or condition.
|
|
|
|
In simulation, the tape unit number corresponds to the simulation unit
|
|
number. For example, simulation unit MS0 responds when the controller
|
|
addresses tape unit 0. The correspondence between tape and simulation
|
|
unit numbers cannot be changed. Therefore, changing a unit's number is
|
|
accomplished by detaching the current tape image from the first
|
|
simulation unit and attaching it to the second unit. Note, however, that
|
|
this resets the tape position to the load point, so it is not exactly
|
|
equivalent.
|
|
|
|
2. Per page 2-15 of the maintenance manual, during the idle state when no
|
|
SIO program is active, the interface continuously selects one unit after
|
|
another to look for a change from Not Ready to Ready status. Therefore,
|
|
the tape unit selected bits will be seen to change continuously. In
|
|
simulation, a change of status is noted when the change occurs, e.g.,
|
|
when the SET <unit> ONLINE command is entered, so scanning is not
|
|
necessary. A program that continuously requests status will not see the
|
|
unit select bits changing as in hardware.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp3000_defs.h"
|
|
#include "hp3000_io.h"
|
|
#include "hp_tapelib.h"
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define DRIVE_COUNT (TL_MAXDRIVE + 1) /* the number of tape drive units */
|
|
#define UNIT_COUNT (DRIVE_COUNT + TL_AUXUNITS) /* the total number of units */
|
|
|
|
#define cntlr_unit ms_unit [TL_CNTLR_UNIT] /* the controller unit alias */
|
|
|
|
#define UNUSED_COMMANDS (STCFL | STDFL) /* unused tape interface commands */
|
|
|
|
|
|
/* Debug flags (interface-specific) */
|
|
|
|
#define DEB_IOB TL_DEB_IOB /* trace I/O bus signals and data words */
|
|
#define DEB_SERV TL_DEB_SERV /* trace unit service scheduling calls */
|
|
#define DEB_CSRW (1u << TL_DEB_V_UF + 0) /* trace control, status, read, and write actions */
|
|
|
|
|
|
/* Control word.
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| M | R | - - - - - - - - - - - - - - | DIO
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - - - - - - - - - - - | PIO word 1
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - | unit | 0 0 0 0 | command code | PIO word 2
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
#define CN_MR 0100000u /* (M) master reset */
|
|
#define CN_RIN 0040000u /* (R) reset interrupt */
|
|
#define CN_UNIT_MASK 0001400u /* unit number mask */
|
|
#define CN_RSVD_MASK 0000360u /* reserved mask */
|
|
#define CN_CMD_MASK 0000017u /* command code mask */
|
|
|
|
#define CN_CMD_RDR 0000006u /* Read Record command */
|
|
|
|
#define CN_UNIT_SHIFT 8 /* unit number alignment shift */
|
|
#define CN_CMD_SHIFT 0 /* command code alignment shift */
|
|
|
|
#define CN_UNIT(c) (((c) & CN_UNIT_MASK) >> CN_UNIT_SHIFT)
|
|
#define CN_CMD(c) (((c) & CN_CMD_MASK) >> CN_CMD_SHIFT)
|
|
|
|
static const BITSET_NAME control_names [] = { /* Control word names */
|
|
"master reset", /* bit 0 */
|
|
"reset interrupt" /* bit 1 */
|
|
};
|
|
|
|
static const BITSET_FORMAT control_format = /* names, offset, direction, alternates, bar */
|
|
{ FMT_INIT (control_names, 14, msb_first, no_alt, no_bar) };
|
|
|
|
|
|
/* Status word.
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| S | B | I | unit | E | P | R | L | D | W | M | err code | T |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The status bits for the encoded error field are complemented from their
|
|
actual values. This allows the tape library to use an all-zeros value to
|
|
represent No Error, which is consistent with the values used by other
|
|
controllers. The encoded error field bits must be complemented before
|
|
reporting the controller status.
|
|
*/
|
|
|
|
#define ST_SIO_OK 0100000u /* (S) SIO OK to use */
|
|
/* ST_ODD_COUNT 0040000u */ /* (B) byte count is odd (supplied by hp_tapelib) */
|
|
#define ST_INTREQ 0020000u /* (I) interrupt requested */
|
|
#define ST_UNIT_MASK 0014000u /* unit selected mask */
|
|
/* ST_EOT 0002000u */ /* (E) end of tape (supplied by hp_tapelib) */
|
|
/* ST_PROTECTED 0001000u */ /* (P) write protected (supplied by hp_tapelib) */
|
|
/* ST_READY 0000400u */ /* (R) unit ready (supplied by hp_tapelib) */
|
|
/* ST_LOAD_POINT 0000200u */ /* (L) load point (supplied by hp_tapelib) */
|
|
/* ST_DENSITY_1600 0000100u */ /* (D) 1600 bpi density (supplied by hp_tapelib) */
|
|
/* ST_WRITE_STATUS 0000040u */ /* (W) write status (supplied by hp_tapelib) */
|
|
/* ST_TAPE_MARK 0000020u */ /* (M) tape mark (supplied by hp_tapelib) */
|
|
#define ST_ERROR_MASK 0000016u /* encoded error field mask */
|
|
/* ST_7_TRACK 0000001u */ /* (T) 7-track unit (always off) */
|
|
|
|
#define ST_UNIT_SHIFT 11 /* unit number alignment shift */
|
|
#define ST_ERROR_SHIFT 1 /* encoded error alignment shift */
|
|
|
|
#define ST_UNIT(n) ((n) << ST_UNIT_SHIFT & ST_UNIT_MASK)
|
|
|
|
#define ST_TO_UNIT(s) (((s) & ST_UNIT_MASK) >> ST_UNIT_SHIFT)
|
|
#define ST_TO_ERROR(s) (((s) & ST_ERROR_MASK) >> ST_ERROR_SHIFT)
|
|
|
|
|
|
/* Error codes (complements of the values returned) */
|
|
|
|
#define ST_UNITIRQ 0000016u /* unit interrupt */
|
|
#define ST_XFER 0000014u /* transfer error */
|
|
/* ST_REJECT 0000012u */ /* command reject (supplied by hp_tapelib) */
|
|
/* ST_RUNAWAY 0000010u */ /* tape runaway (supplied by hp_tapelib) */
|
|
/* ST_TIMING 0000006u */ /* timing error (supplied by hp_tapelib) */
|
|
/* ST_PARITY 0000004u */ /* tape error (supplied by hp_tapelib) */
|
|
/* ST_RESERVED 0000002u */ /* (reserved) */
|
|
/* ST_NOERROR 0000000u */ /* no error */
|
|
|
|
static const BITSET_NAME status_names [] = { /* Status word names */
|
|
"SIO OK", /* bit 0 */
|
|
"odd count", /* bit 1 */
|
|
"interrupt", /* bit 2 */
|
|
NULL, /* bit 3 */
|
|
NULL, /* bit 4 */
|
|
"end of tape", /* bit 5 */
|
|
"protected", /* bit 6 */
|
|
"ready", /* bit 7 */
|
|
"load point", /* bit 8 */
|
|
"1600 bpi", /* bit 9 */
|
|
"writing", /* bit 10 */
|
|
"tape mark", /* bit 11 */
|
|
NULL, /* bit 12 */
|
|
NULL, /* bit 13 */
|
|
NULL, /* bit 14 */
|
|
"7 track" /* bit 15 */
|
|
};
|
|
|
|
static const BITSET_FORMAT status_format = /* names, offset, direction, alternates, bar */
|
|
{ FMT_INIT (status_names, 0, msb_first, no_alt, append_bar) };
|
|
|
|
|
|
static const char *const error_names [] = { /* error status code names */
|
|
"unit interrupt", /* code 0 */
|
|
"transfer error", /* code 1 */
|
|
"command reject", /* code 2 */
|
|
"tape runaway", /* code 3 */
|
|
"timing error", /* code 4 */
|
|
"tape error", /* code 5 */
|
|
"reserved", /* code 6 */
|
|
"no error" /* code 7 */
|
|
};
|
|
|
|
|
|
/* Interface command code to controller opcode translation table */
|
|
|
|
static const CNTLR_OPCODE to_opcode [] = { /* opcode translation table (fully decoded) */
|
|
Select_Unit_0, /* 000 SEL = Select Unit */
|
|
Invalid_Opcode, /* 001 --- = invalid */
|
|
Invalid_Opcode, /* 002 --- = invalid */
|
|
Invalid_Opcode, /* 003 --- = invalid */
|
|
Write_Record, /* 004 WRR = Write Record */
|
|
Write_Gap, /* 005 GAP = Write Gap */
|
|
Read_Record, /* 006 RDR = Read Record */
|
|
Forward_Space_Record, /* 007 FSR = Forward Space Record */
|
|
Rewind, /* 010 REW = Rewind */
|
|
Rewind_Offline, /* 011 RST = Rewind and Reset */
|
|
Backspace_Record, /* 012 BSR = Backspace Record */
|
|
Backspace_File, /* 013 BSF = Backspace File */
|
|
Write_Record_without_Parity, /* 014 WRZ = Write Record with Zero Parity */
|
|
Write_File_Mark, /* 015 WFM = Write File Mark */
|
|
Read_Record_with_CRCC, /* 016 RDC = Read Record with CRCC */
|
|
Forward_Space_File /* 017 FSF = Forward Space File */
|
|
};
|
|
|
|
|
|
/* Tape controller library data structures */
|
|
|
|
#define MS_REW_START uS (10) /* fast rewind start time */
|
|
#define MS_REW_RATE uS (1) /* fast rewind time per inch of travel */
|
|
#define MS_REW_STOP uS (10) /* fast rewind stop time */
|
|
#define MS_START uS (10) /* fast BOT/interrecord start delay time */
|
|
#define MS_DATA uS (1) /* fast per-byte data transfer time */
|
|
#define MS_OVERHEAD uS (10) /* fast controller overhead time */
|
|
|
|
static DELAY_PROPS fast_times = /* FASTTIME delays */
|
|
{ DELAY_INIT (MS_REW_START, MS_REW_RATE,
|
|
MS_REW_STOP, MS_START,
|
|
MS_START, MS_DATA,
|
|
MS_OVERHEAD) };
|
|
|
|
|
|
/* Interface state */
|
|
|
|
static FLIP_FLOP sio_busy = CLEAR; /* SIO busy flip-flop */
|
|
static FLIP_FLOP channel_sr = CLEAR; /* channel service request flip-flop */
|
|
static FLIP_FLOP device_sr = CLEAR; /* device service request flip-flop */
|
|
static FLIP_FLOP input_xfer = CLEAR; /* input transfer flip-flop */
|
|
static FLIP_FLOP output_xfer = CLEAR; /* output transfer flip-flop */
|
|
static FLIP_FLOP interrupt_mask = SET; /* interrupt mask flip-flop */
|
|
static FLIP_FLOP unit_interrupt = CLEAR; /* unit ready flip-flop */
|
|
static FLIP_FLOP device_end = CLEAR; /* device end flip-flop */
|
|
static FLIP_FLOP xfer_error = CLEAR; /* transfer error flip-flop */
|
|
|
|
static HP_WORD buffer_word = 0; /* data buffer word */
|
|
static HP_WORD attention_unit = 0; /* number of the unit requesting attention */
|
|
static CNTLR_CLASS command_class = Class_Invalid; /* current command classification */
|
|
static CNTLR_FLAG_SET flags = INTOK; /* tape controller interface flag set */
|
|
|
|
static TL_BUFFER buffer [TL_BUFSIZE]; /* the tape record buffer */
|
|
|
|
DEVICE ms_dev; /* incomplete device structure */
|
|
|
|
static CNTLR_VARS ms_cntlr = /* the tape controller */
|
|
{ CNTLR_INIT (HP_30215, ms_dev, buffer, fast_times) };
|
|
|
|
|
|
/* Interface local SCP support routines */
|
|
|
|
static CNTLR_INTRF ms_interface;
|
|
static t_stat ms_service (UNIT *uptr);
|
|
static t_stat ms_reset (DEVICE *dptr);
|
|
static t_stat ms_boot (int32 unit_number, DEVICE *dptr);
|
|
static t_stat ms_attach (UNIT *uptr, CONST char *cptr);
|
|
static t_stat ms_onoffline (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
|
|
|
|
|
|
/* Interface local utility routines */
|
|
|
|
static void master_reset (void);
|
|
static void clear_interface_logic (void);
|
|
static t_stat call_controller (UNIT *uptr);
|
|
|
|
|
|
/* Interface SCP data structures */
|
|
|
|
|
|
/* Device information block */
|
|
|
|
static DIB ms_dib = {
|
|
&ms_interface, /* device interface */
|
|
6, /* device number */
|
|
3, /* service request number */
|
|
14, /* interrupt priority */
|
|
INTMASK_E /* interrupt mask */
|
|
};
|
|
|
|
/* Unit list */
|
|
|
|
#define UNIT_FLAGS (UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_OFFLINE)
|
|
|
|
static UNIT ms_unit [UNIT_COUNT] = {
|
|
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 0 */
|
|
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 1 */
|
|
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 2 */
|
|
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 3 */
|
|
{ UDATA (&ms_service, UNIT_DIS, 0) } /* controller unit */
|
|
};
|
|
|
|
/* Register list */
|
|
|
|
static REG ms_reg [] = {
|
|
/* Macro Name Location Width Offset Flags */
|
|
/* ------ ------ -------------- ----- ------ ------------------------- */
|
|
{ FLDATA (SIOBSY, sio_busy, 0) },
|
|
{ FLDATA (CHANSR, channel_sr, 0) },
|
|
{ FLDATA (DEVSR, device_sr, 0) },
|
|
{ FLDATA (INXFR, input_xfer, 0) },
|
|
{ FLDATA (OUTXFR, output_xfer, 0) },
|
|
{ FLDATA (INTMSK, interrupt_mask, 0) },
|
|
{ FLDATA (UINTRP, unit_interrupt, 0) },
|
|
{ FLDATA (DEVEND, device_end, 0) },
|
|
{ FLDATA (XFRERR, xfer_error, 0) },
|
|
{ ORDATA (BUFWRD, buffer_word, 16), REG_A | REG_FIT | PV_RZRO },
|
|
{ DRDATA (ATUNIT, attention_unit, 16), REG_FIT | PV_LEFT },
|
|
{ DRDATA (CLASS, command_class, 4), PV_LEFT },
|
|
{ YRDATA (FLAGS, flags, 8, PV_RZRO) },
|
|
{ SRDATA (DIB, ms_dib, REG_HRO) },
|
|
|
|
TL_REGS (ms_cntlr, ms_unit, DRIVE_COUNT, buffer, fast_times),
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
/* Modifier list */
|
|
|
|
static MTAB ms_mod [] = {
|
|
|
|
TL_MODS (ms_cntlr, TL_7970B | TL_7970E, TL_FIXED, ms_onoffline),
|
|
|
|
/* Entry Flags Value Print String Match String Validation Display Descriptor */
|
|
/* ----------- ----------- ------------ ------------ ----------- ------------ ---------------- */
|
|
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
|
|
{ MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
|
|
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
|
|
{ MTAB_XDV, VAL_SRNO, "SRNO", "SRNO", &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
|
|
{ 0 }
|
|
};
|
|
|
|
/* Debugging trace list */
|
|
|
|
static DEBTAB ms_deb [] = {
|
|
{ "CMD", TL_DEB_CMD }, /* controller commands */
|
|
{ "INCO", TL_DEB_INCO }, /* controller command initiations and completions */
|
|
{ "CSRW", DEB_CSRW }, /* interface control, status, read, and write actions */
|
|
{ "STATE", TL_DEB_STATE }, /* controller execution state changes */
|
|
{ "SERV", DEB_SERV }, /* controller unit service scheduling calls */
|
|
{ "XFER", TL_DEB_XFER }, /* controller data reads and writes */
|
|
{ "IOBUS", DEB_IOB }, /* interface and controller I/O bus signals and data words */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
/* Device descriptor */
|
|
|
|
DEVICE ms_dev = {
|
|
"MS", /* device name */
|
|
ms_unit, /* unit array */
|
|
ms_reg, /* register array */
|
|
ms_mod, /* modifier array */
|
|
UNIT_COUNT, /* number of units */
|
|
10, /* address radix */
|
|
32, /* address width = 4 GB */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
8, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&ms_reset, /* reset routine */
|
|
&ms_boot, /* boot routine */
|
|
&ms_attach, /* attach routine */
|
|
&tl_detach, /* detach routine */
|
|
&ms_dib, /* device information block pointer */
|
|
DEV_DISABLE | DEV_DEBUG, /* device flags */
|
|
0, /* debug control flags */
|
|
ms_deb, /* debug flag name array */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* Interface local SCP support routines */
|
|
|
|
|
|
|
|
/* Magnetic tape interface.
|
|
|
|
The interface is installed on the IOP and Multiplexer Channel buses and
|
|
receives direct and programmed I/O commands from the IOP and Multiplexer
|
|
Channel, respectively. In simulation, the asserted signals on the buses are
|
|
represented as bits in the inbound_signals set. Each signal is processed
|
|
sequentially in numerical order, and a set of similar outbound_signals is
|
|
assembled and returned to the caller, simulating assertion of the
|
|
corresponding backplane signals.
|
|
|
|
The DCONTSTB signal qualifies direct I/O control word bits 0 and 1 (master
|
|
reset and reset interrupt, respectively) only. The PCONTSTB signal does not
|
|
enable these functions. A master reset is identical to an IORESET signal
|
|
assertion; the current command is aborted, all drives are stopped (unless
|
|
rewinding), and the interface is cleared. The reset interrupt function
|
|
clears the Interrupt Request flip-flop; it does not affect the Interrupt
|
|
Active flip-flop.
|
|
|
|
Controller commands are executed by the PCONTSTB signal. Command opcodes are
|
|
carried in the IOAW of the control order. The IOCW is not used. Commands
|
|
that transfer data must be followed by the appropriate read or write I/O
|
|
order. The controller sets up the associated command during PCONTSTB
|
|
processing but does not actually initiate tape movement (i.e., does not begin
|
|
start phase processing) until the corresponding TOGGLEINXFER or TOGGLEOUTXFER
|
|
signal is asserted.
|
|
|
|
The DSTATSTB and PSTATSTB signals are tied together in hardware and therefore
|
|
perform identically. Both return the status of the currently selected tape
|
|
drive unit.
|
|
|
|
The DREADSTB and DWRITESTB signals are acknowledged but perform no other
|
|
function. DREADSTB returns all-zeros data.
|
|
|
|
A channel transfer error asserts XFERERROR, which sets the xfer_error
|
|
flip-flop. This causes the interface to assert a Transfer Error interrupt
|
|
until the flip-flop is cleared by a Programmed Master Clear. The controller
|
|
sees no error indication; it simply hangs while waiting for the next data
|
|
transfer, which does not occur because the channel transfer was aborted.
|
|
This condition persists until a PMC occurs, which performs a hardware restart
|
|
on the controller.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. A unit interrupt ORs in the unit interrupt status code, rather than
|
|
masking out any previous code. This works because the code is all ones,
|
|
which overrides any prior code.
|
|
|
|
Similarly, a transfer error ORs in its status code, which is all ones
|
|
except for the LSB. This would fail if a code already present had the
|
|
LSB set. The only codes which do are ST_REJECT and ST_TIMING, and
|
|
neither of these can be present when a transfer error occurs (a transfer
|
|
error can only occur in the data phase due to a bad memory bank number; a
|
|
timing error is set in the stop phase, after the transfer error has
|
|
aborted the command, and a reject error is set in the wait phase, before
|
|
the transfer is begun).
|
|
|
|
2. Command errors and units becoming ready cause interrupts. Once an
|
|
interrupt is asserted, the controller sits in a tight loop waiting for
|
|
the interrupt to be reset. When it is, the controller returns to the
|
|
idle loop and looks for the next command.
|
|
|
|
In simulation, when a command is issued with an interrupt in process, the
|
|
command is set up, the command ready flag is set, but the controller is
|
|
not notified. When DRESETINT is received, the controller will be
|
|
called to start the command, which provides the same semantics.
|
|
|
|
3. The maintenance manual states that DREADSTB and DWRITESTB are not used.
|
|
But the schematic shows that DREADSTB is decoded and will enable the DATA
|
|
IN lines when asserted. However, none of the output drivers on that
|
|
ground-true bus will be enabled. There are pullups on all bits except
|
|
6-13, which would be driven (if enabled) by the device number buffer. So
|
|
it appears that executing an RIO instruction will return zeros for bits
|
|
0-5 and 14-15, with bits 6-13 indeterminate.
|
|
|
|
4. The controller opcodes Select_Unit_0 through Select_Unit_3 are
|
|
contiguous, so the interface may derive these opcodes for the SEL command
|
|
by adding the unit number to the Select_Unit_0 value.
|
|
|
|
5. In hardware, the controller microcode checks the input and output
|
|
transfer flip-flops while waiting for a new command. If either are set,
|
|
a command reject is indicated. This occurs if a Read or Write order
|
|
precedes a Control order. It also occurs if chained Read order is
|
|
terminated with a Device End condition due to a record length shorter
|
|
than the transfer length.
|
|
|
|
In simulation, these conditions are tested separately. A premature Read
|
|
or Write order will be caught during TOGGLEINXFER or TOGGLEOUTXFER
|
|
processing, and a chained Read order after a Device End will be caught
|
|
during READNEXTWD processing when the device end flip-flop set. In both
|
|
cases, the controller is called to continue a command, but no command is
|
|
in process, so a reject occurs. Note that these conditions will no
|
|
longer exist when a Control order is received, so tests there are not
|
|
required.
|
|
|
|
6. In hardware, the EOT, READNEXTWD, and SETJMP signals are ignored, and the
|
|
JMPMET signal is asserted continuously when enabled by CHANSO.
|
|
*/
|
|
|
|
static SIGNALS_DATA ms_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
|
|
{
|
|
CNTLR_OPCODE opcode;
|
|
INBOUND_SIGNAL signal;
|
|
INBOUND_SET working_set = inbound_signals;
|
|
HP_WORD outbound_value = 0;
|
|
OUTBOUND_SET outbound_signals = NO_SIGNALS;
|
|
|
|
dprintf (ms_dev, DEB_IOB, "Received data %06o with signals %s\n",
|
|
inbound_value, fmt_bitset (inbound_signals, inbound_format));
|
|
|
|
while (working_set) {
|
|
signal = IONEXTSIG (working_set); /* isolate the next signal */
|
|
|
|
switch (signal) { /* dispatch an I/O signal */
|
|
|
|
case INTPOLLIN:
|
|
if (dibptr->interrupt_request) { /* if a request is pending */
|
|
dibptr->interrupt_request = CLEAR; /* then clear it */
|
|
dibptr->interrupt_active = SET; /* and mark it now active */
|
|
|
|
outbound_signals |= INTACK; /* acknowledge the interrupt */
|
|
outbound_value = dibptr->device_number; /* and return our device number */
|
|
}
|
|
|
|
else /* otherwise the request has been reset */
|
|
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */
|
|
break;
|
|
|
|
|
|
case SETINT:
|
|
case DSETINT:
|
|
dibptr->interrupt_request = SET; /* request an interrupt */
|
|
flags &= ~INTOK; /* and clear the interrupt OK flag */
|
|
|
|
if (interrupt_mask == SET) /* if the interrupt mask is satisfied */
|
|
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
|
|
break;
|
|
|
|
|
|
case DRESETINT:
|
|
dibptr->interrupt_active = CLEAR; /* reset the interrupt active */
|
|
unit_interrupt = CLEAR; /* and unit interrupt flip-flops */
|
|
|
|
if (dibptr->interrupt_request == CLEAR) { /* if there's no request pending */
|
|
if (sio_busy == CLEAR) /* then if an SIO program is not executing */
|
|
flags |= INTOK; /* then set the interrupt OK flag */
|
|
|
|
if (flags & (CMRDY | INTOK)) /* if a command is present or a poll is needed */
|
|
call_controller (NULL); /* then tell the controller */
|
|
|
|
if (device_sr) /* if the interface has requested service */
|
|
outbound_signals |= SRn; /* then assert SRn to the channel */
|
|
}
|
|
break;
|
|
|
|
|
|
case DSETMASK:
|
|
if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */
|
|
interrupt_mask = SET; /* then set the mask flip-flop */
|
|
else /* otherwise */
|
|
interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */
|
|
& inbound_value); /* is present in the mask value */
|
|
|
|
if (interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */
|
|
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
|
|
break;
|
|
|
|
|
|
case DCONTSTB:
|
|
dprintf (ms_dev, DEB_CSRW, "Control is %s\n",
|
|
fmt_bitset (inbound_value, control_format));
|
|
|
|
if (inbound_value & CN_MR) /* if the master reset bit is set */
|
|
master_reset (); /* then reset the interface */
|
|
|
|
if (inbound_value & CN_RIN) { /* if the reset interrupt bit is set */
|
|
dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */
|
|
|
|
if (dibptr->interrupt_active == CLEAR) { /* if an interrupt is not active */
|
|
unit_interrupt = CLEAR; /* then clear the unit interrupt flip-flop too */
|
|
|
|
if (sio_busy == CLEAR) /* if an SIO program is not executing */
|
|
flags |= INTOK; /* then set the interrupt OK flag */
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case PSTATSTB:
|
|
case DSTATSTB:
|
|
outbound_value = tl_status (&ms_cntlr); /* get the controller and unit status */
|
|
|
|
if (unit_interrupt) /* if a unit interrupt is pending */
|
|
outbound_value = /* then replace the selected unit */
|
|
outbound_value & ~ST_UNIT_MASK /* with the interrupting unit */
|
|
| ST_UNIT (attention_unit) /* and set the status code */
|
|
| ST_UNITIRQ;
|
|
|
|
else if (xfer_error) /* otherwise if a transfer error occurred */
|
|
outbound_value |= ST_XFER; /* then set the status bit */
|
|
|
|
outbound_value ^= ST_ERROR_MASK; /* complement the encoded error bits */
|
|
|
|
if (sio_busy == CLEAR) /* if the interface is inactive */
|
|
outbound_value |= ST_SIO_OK; /* then add the SIO OK status bit */
|
|
|
|
if (dibptr->interrupt_request) /* if an interrupt request is pending */
|
|
outbound_value |= ST_INTREQ; /* then set the status bit */
|
|
|
|
dprintf (ms_dev, DEB_CSRW, "Status is %s%s | unit %u\n",
|
|
fmt_bitset (outbound_value, status_format),
|
|
error_names [ST_TO_ERROR (outbound_value)],
|
|
ST_TO_UNIT (outbound_value));
|
|
break;
|
|
|
|
|
|
case DSTARTIO:
|
|
dprintf (ms_dev, DEB_CSRW, "Channel program started\n");
|
|
|
|
sio_busy = SET; /* set the SIO busy flip-flop */
|
|
flags &= ~INTOK; /* and clear the interrupt OK flag */
|
|
|
|
mpx_assert_REQ (dibptr); /* request the channel */
|
|
|
|
channel_sr = SET; /* set the service request flip-flop */
|
|
outbound_signals |= SRn; /* and assert a service request */
|
|
break;
|
|
|
|
|
|
case ACKSR:
|
|
device_sr = CLEAR; /* acknowledge the service request */
|
|
break;
|
|
|
|
|
|
case TOGGLESR:
|
|
TOGGLE (channel_sr); /* set or clear the channel service request flip-flop */
|
|
break;
|
|
|
|
|
|
case TOGGLESIOOK:
|
|
TOGGLE (sio_busy); /* set or clear the SIO busy flip-flop */
|
|
|
|
if (sio_busy == CLEAR) { /* if the flip-flop was cleared */
|
|
dprintf (ms_dev, DEB_CSRW, "Channel program ended\n");
|
|
|
|
if (dibptr->interrupt_request == CLEAR /* then if there's no interrupt request */
|
|
&& dibptr->interrupt_active == CLEAR) { /* active or pending */
|
|
flags |= INTOK; /* then set the interrupt OK flag */
|
|
|
|
call_controller (NULL); /* check for drive attention held off by INTOK denied */
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case TOGGLEINXFER:
|
|
TOGGLE (input_xfer); /* set or clear the input transfer flip-flop */
|
|
|
|
if (input_xfer == SET) { /* if the transfer is starting */
|
|
if (command_class == Class_Read) /* then if a read command is pending */
|
|
flags &= ~EOD; /* then clear the EOD flag to enable the data transfer */
|
|
|
|
call_controller (&cntlr_unit); /* let the controller know the channel has started */
|
|
}
|
|
|
|
else { /* otherwise the transfer is ending */
|
|
flags |= EOD; /* so set the end-of-data flag */
|
|
device_end = CLEAR; /* and clear any device end condition */
|
|
}
|
|
break;
|
|
|
|
|
|
case TOGGLEOUTXFER:
|
|
TOGGLE (output_xfer); /* set or clear the output transfer flip-flop */
|
|
|
|
if (output_xfer == SET) { /* if the transfer is starting */
|
|
if (command_class == Class_Write) /* then if a write command is pending */
|
|
flags &= ~EOD; /* then clear the EOD flag to enable the data transfer */
|
|
|
|
call_controller (&cntlr_unit); /* let the controller know the channel has started */
|
|
}
|
|
|
|
else /* otherwise the transfer is ending */
|
|
flags |= EOD; /* so set the end-of-data flag */
|
|
break;
|
|
|
|
|
|
case PCMD1:
|
|
device_sr = SET; /* request the second control word */
|
|
break;
|
|
|
|
|
|
case PCONTSTB:
|
|
opcode = to_opcode [CN_CMD (inbound_value)]; /* get the command code from the control word */
|
|
|
|
if (opcode == Select_Unit_0) /* if this is a select unit command */
|
|
opcode = opcode + CN_UNIT (inbound_value); /* then convert to a unit-specific opcode */
|
|
|
|
dprintf (ms_dev, DEB_CSRW, "Control is %06o (%s)\n",
|
|
inbound_value, tl_opcode_name (opcode));
|
|
|
|
if ((inbound_value & CN_RSVD_MASK) != 0) /* if the reserved bits aren't zero */
|
|
buffer_word = (HP_WORD) Invalid_Opcode; /* then reject the command */
|
|
else /* otherwise */
|
|
buffer_word = (HP_WORD) opcode; /* store the opcode in the data buffer register */
|
|
|
|
flags |= CMRDY | CMXEQ; /* set the command ready and execute flags */
|
|
|
|
if (dibptr->interrupt_request == CLEAR /* if no interrupt is pending */
|
|
&& dibptr->interrupt_active == CLEAR) { /* or active */
|
|
call_controller (NULL); /* then tell the controller to start the command */
|
|
|
|
unit_interrupt = CLEAR; /* clear the unit interrupt flip-flop */
|
|
}
|
|
break;
|
|
|
|
|
|
case READNEXTWD:
|
|
if (device_end == SET /* if the device end flip-flop is set */
|
|
&& (inbound_signals & TOGGLESR)) { /* and we're starting (not continuing) a transfer */
|
|
call_controller (&cntlr_unit); /* then let the controller know to reject */
|
|
|
|
device_end = CLEAR; /* clear the device end condition */
|
|
}
|
|
break;
|
|
|
|
|
|
case PREADSTB:
|
|
if (device_end) { /* if the transfer has been aborted */
|
|
outbound_value = dibptr->device_number * 4; /* then return the DRT address */
|
|
outbound_signals |= DEVEND; /* and indicate a device abort */
|
|
}
|
|
|
|
else { /* otherwise the transfer continues */
|
|
outbound_value = buffer_word; /* so return the data buffer register value */
|
|
flags &= ~DTRDY; /* and clear the data ready flag */
|
|
}
|
|
break;
|
|
|
|
|
|
case PWRITESTB:
|
|
buffer_word = inbound_value; /* save the word to write */
|
|
flags |= DTRDY; /* and set the data ready flag */
|
|
break;
|
|
|
|
|
|
case DEVNODB:
|
|
outbound_value = dibptr->device_number * 4; /* return the DRT address */
|
|
break;
|
|
|
|
|
|
case XFERERROR:
|
|
dprintf (ms_dev, DEB_CSRW, "Channel program aborted\n");
|
|
|
|
xfer_error = SET; /* set the transfer error flip-flop */
|
|
flags |= XFRNG; /* and controller flag */
|
|
|
|
call_controller (NULL); /* let the controller know of the abort */
|
|
|
|
clear_interface_logic (); /* clear the interface to abort the transfer */
|
|
|
|
dibptr->interrupt_request = SET; /* request an interrupt */
|
|
flags &= ~INTOK; /* and clear the interrupt OK flag */
|
|
|
|
if (interrupt_mask == SET) /* if the interrupt mask is satisfied */
|
|
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
|
|
break;
|
|
|
|
|
|
case CHANSO:
|
|
if (channel_sr | device_sr) /* if the interface has requested service */
|
|
outbound_signals |= SRn; /* then assert SRn to the channel */
|
|
|
|
outbound_signals |= JMPMET; /* JMPMET is tied active on this interface */
|
|
break;
|
|
|
|
|
|
case DREADSTB: /* not used by this interface */
|
|
case DWRITESTB: /* not used by this interface */
|
|
case EOT: /* not used by this interface */
|
|
case SETJMP: /* not used by this interface */
|
|
case PFWARN: /* not used by this interface */
|
|
break;
|
|
}
|
|
|
|
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
|
|
}
|
|
|
|
dprintf (ms_dev, DEB_IOB, "Returned data %06o with signals %s\n",
|
|
outbound_value, fmt_bitset (outbound_signals, outbound_format));
|
|
|
|
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */
|
|
}
|
|
|
|
|
|
/* Service the controller or a drive unit.
|
|
|
|
The service routine is called to execute scheduled controller command phases
|
|
for the specified unit. The actions to be taken depend on the current state
|
|
of the controller and the drive unit.
|
|
|
|
This routine is entered when a tape unit or the controller unit is ready to
|
|
execute the next command phase. Generally, the controller library handles
|
|
all of the tape operations. All that is necessary is to notify the
|
|
controller, which will process the next phase of command execution. Because
|
|
the controller can overlap operations, in particular scheduling rewinds on
|
|
several drive units simultaneously, each drive unit carries its own current
|
|
operation code and execution phase. The controller uses these to determine
|
|
what to do next.
|
|
*/
|
|
|
|
static t_stat ms_service (UNIT *uptr)
|
|
{
|
|
t_stat result;
|
|
|
|
dprintf (ms_dev, DEB_SERV, "%s service entered\n",
|
|
tl_unit_name ((int32) (uptr - ms_unit)));
|
|
|
|
result = call_controller (uptr); /* call the controller */
|
|
|
|
if (device_sr == SET) /* if the device has requested service */
|
|
mpx_assert_SRn (&ms_dib); /* then assert SR to the channel */
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Device reset routine.
|
|
|
|
This routine is called for a RESET, RESET MS, or BOOT MS command. It is the
|
|
simulation equivalent of the IORESET signal, which is asserted by the front
|
|
panel LOAD and DUMP switches.
|
|
|
|
For this interface, IORESET is identical to the Programmed Master Clear. In
|
|
addition, if a power-on reset (RESET -P) is done, the original FASTTIME
|
|
settings are restored.
|
|
*/
|
|
|
|
static t_stat ms_reset (DEVICE *dptr)
|
|
{
|
|
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
|
|
fast_times.rewind_start = MS_REW_START; /* then reset the rewind initiation time, */
|
|
fast_times.rewind_rate = MS_REW_RATE; /* the rewind time per inch, */
|
|
fast_times.bot_start = MS_START; /* the beginning-of-tape gap traverse time, */
|
|
fast_times.ir_start = MS_START; /* the interrecord traverse time, */
|
|
fast_times.data_xfer = MS_DATA; /* the per-byte data transfer time, */
|
|
fast_times.overhead = MS_OVERHEAD; /* and the controller execution overhead */
|
|
}
|
|
|
|
master_reset (); /* perform a master reset */
|
|
|
|
return tl_reset (&ms_cntlr); /* reset the controller and return the result */
|
|
}
|
|
|
|
|
|
/* Device boot routine.
|
|
|
|
This routine is called for the BOOT MS command to initiate the system cold
|
|
load procedure for the tape. It is the simulation equivalent to presetting
|
|
the System Switch Register to the appropriate control and device number bytes
|
|
and then pressing the ENABLE and LOAD front panel switches.
|
|
|
|
For this interface, the switch register is set to %0030nn, where "nn"
|
|
is the current tape interface device number, which defaults to 6. The
|
|
control byte is 06 (Read Record).
|
|
|
|
The cold load procedure always uses unit 0.
|
|
*/
|
|
|
|
static t_stat ms_boot (int32 unit_number, DEVICE *dptr)
|
|
{
|
|
if (unit_number != 0) /* if a unit other than 0 is specified */
|
|
return SCPE_ARG; /* then fail with an invalid argument error */
|
|
|
|
else { /* otherwise */
|
|
cpu_front_panel (TO_WORD (CN_CMD_RDR, /* set up the Read Record command */
|
|
ms_dib.device_number), /* from tape unit 0 */
|
|
Cold_Load);
|
|
|
|
return SCPE_OK; /* return to run the bootstrap */
|
|
}
|
|
}
|
|
|
|
|
|
/* Attach a tape image file to a drive unit.
|
|
|
|
The specified file is attached to the indicated drive unit. This is the
|
|
simulation equivalent of mounting a tape reel on the drive and pressing the
|
|
LOAD and ONLINE buttons. The transition from offline to online causes a Unit
|
|
Attention interrupt.
|
|
|
|
The controller library routine handles command validation and setting the
|
|
appropriate drive unit status. It will return an error code if the command
|
|
fails. Otherwise, it will return SCPE_INCOMP if the command must be
|
|
completed with a controller call or SCPE_OK if the command is complete. If
|
|
the controller is idle, a call will be needed to poll the drives for
|
|
attention; otherwise, the drives will be polled the next time the controller
|
|
becomes idle.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If we are called during a RESTORE command to reattach a file previously
|
|
attached when the simulation was SAVEd, the unit status will not be
|
|
changed by the controller, so the unit will not request attention.
|
|
*/
|
|
|
|
static t_stat ms_attach (UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat result;
|
|
|
|
result = tl_attach (&ms_cntlr, uptr, cptr); /* attach the drive */
|
|
|
|
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */
|
|
call_controller (NULL); /* then let it know to poll the drives */
|
|
return SCPE_OK; /* before returning with success */
|
|
}
|
|
|
|
else /* otherwise */
|
|
return result; /* return the status of the attach */
|
|
}
|
|
|
|
|
|
/* Set the drive online or offline.
|
|
|
|
The SET MSn OFFLINE command simulates pressing the RESET button, and the SET
|
|
MSn ONLINE command simulates pressing the ONLINE button. The transition from
|
|
offline to online causes a Unit Attention interrupt. The SET request fails
|
|
if there is no tape mounted on the drive, i.e., if the unit is not attached
|
|
to a tape image file.
|
|
|
|
The controller library routine handles command validation and setting the
|
|
appropriate drive unit status. It will return an error code if the command
|
|
fails. Otherwise, it will return SCPE_INCOMP if the command must be
|
|
completed with a controller call or SCPE_OK if the command is complete. If
|
|
the controller is idle, a call will be needed to poll the drives for
|
|
attention; otherwise, the drives will be polled the next time the controller
|
|
becomes idle.
|
|
*/
|
|
|
|
static t_stat ms_onoffline (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
const t_bool online = (value != UNIT_OFFLINE); /* TRUE if the drive is being put online */
|
|
t_stat result;
|
|
|
|
result = tl_onoffline (&ms_cntlr, uptr, online); /* set the drive online or offline */
|
|
|
|
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */
|
|
call_controller (NULL); /* then let it know to poll the drives */
|
|
return SCPE_OK; /* before returning with success */
|
|
}
|
|
|
|
else /* otherwise */
|
|
return result; /* return the status of the load or unload */
|
|
}
|
|
|
|
|
|
|
|
/* Interface local utility routines */
|
|
|
|
|
|
/* Master reset.
|
|
|
|
A master reset is generated either by an I/O Reset signal or a Programmed
|
|
Master Clear (CIO bit 0). It initializes the interface and the tape
|
|
controller to their respective idle states. Clearing the controller aborts
|
|
all commands in progress and stops all drive motion except for rewinding,
|
|
which completes normally.
|
|
*/
|
|
|
|
static void master_reset (void)
|
|
{
|
|
tl_clear (&ms_cntlr); /* clear the controller to stop the drives */
|
|
|
|
ms_dib.interrupt_request = CLEAR; /* clear any current */
|
|
ms_dib.interrupt_active = CLEAR; /* interrupt request */
|
|
|
|
interrupt_mask = SET; /* set the interrupt mask */
|
|
flags = INTOK; /* and the Interrupt OK flag */
|
|
|
|
xfer_error = CLEAR; /* clear the transfer error flip-flop */
|
|
|
|
clear_interface_logic (); /* clear the interface to abort the transfer */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Clear interface logic.
|
|
|
|
The clear interface logic signal is asserted during channel operation when
|
|
the controller is reset or requests an interrupt, the channel indicates a
|
|
transfer failure by asserting XFERERROR, or a master reset occurs. It clears
|
|
the SIO Busy, Channel and Device Service Request, Input Transfer, Output
|
|
Transfer, and Device End flip-flops.
|
|
*/
|
|
|
|
static void clear_interface_logic (void)
|
|
{
|
|
sio_busy = CLEAR; /* clear the SIO busy flip-flop */
|
|
channel_sr = CLEAR; /* and the channel service request flip-flop */
|
|
device_sr = CLEAR; /* and the device service request flip-flop */
|
|
input_xfer = CLEAR; /* and the input transfer flip-flop */
|
|
output_xfer = CLEAR; /* and the output transfer flip-flop */
|
|
device_end = CLEAR; /* and the device end flip-flop */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Call the tape controller.
|
|
|
|
The abstract tape controller connects to the CPU interface via 16-bit data,
|
|
flag, and function buses. The controller monitors the flag bus and reacts to
|
|
the interface changing the flag states by placing or accepting data on the
|
|
data bus and issuing commands to the interface via the function bus.
|
|
|
|
In simulation, a call to the tl_controller routine informs the controller of
|
|
a (potential) change in flag state. The current set of flags and data bus
|
|
value are supplied, and the controller returns a combined set of functions
|
|
and a data bus value.
|
|
|
|
The controller must be called any time there is a change in the state of the
|
|
interface or the drive units. Generally, the cases that require notification
|
|
are when the interface:
|
|
|
|
- has a new command to execute
|
|
- has detected the channel starting, ending, or aborting the transfer
|
|
- has a new data word available to send
|
|
- has obtained the last data word received
|
|
- has received a unit service event notification
|
|
- has detected the mounting of the tape reel on a drive
|
|
- has detected a drive being placed online or offline
|
|
- has detected the interrupt request being reset
|
|
|
|
The set of returned functions is processed sequentially, updating the
|
|
interface state as indicated. Some functions are not used by this interface,
|
|
so they are masked off before processing to improve performance.
|
|
|
|
Because the tape is a synchronous device, overrun or underrun can occur if
|
|
the interface is not ready when the controller must transfer data. There are
|
|
four conditions that lead to an overrun or underrun:
|
|
|
|
1. The controller is ready with a tape read word (IFIN), but the interface
|
|
buffer is full (DTRDY).
|
|
|
|
2. The controller needs a tape write word (IFOUT), but the interface buffer
|
|
is empty (~DTRDY).
|
|
|
|
3. The CPU attempts to read a word, but the interface buffer is empty
|
|
(~DTRDY).
|
|
|
|
4. The CPU attempts to write a word, but the interface buffer is full
|
|
(DTRDY).
|
|
|
|
The interface detects the first two conditions and sets the data overrun flag
|
|
if either occurs. The hardware design of the interface prevents the last two
|
|
conditions, as the interface will assert SRn only when the buffer is full
|
|
(read) or empty (write).
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. In hardware, data overrun and underrun are detected as each byte is moved
|
|
between the tape unit and the data buffer register. In simulation, OVRUN
|
|
will not be asserted when the controller is called with the full or empty
|
|
buffer; instead, it will be asserted for the next controller call.
|
|
Because the controller will be called for the tape stop phase, and
|
|
because OVRUN isn't checked until that point, this "late" assertion does
|
|
not affect overrun or underrun detection.
|
|
*/
|
|
|
|
static t_stat call_controller (UNIT *uptr)
|
|
{
|
|
CNTLR_IFN_IBUS result;
|
|
CNTLR_IFN_SET command_set;
|
|
CNTLR_IFN command;
|
|
t_stat status = SCPE_OK;
|
|
|
|
result = /* call the controller to start or continue a command */
|
|
tl_controller (&ms_cntlr, uptr, flags, (CNTLR_IBUS) buffer_word);
|
|
|
|
command_set = TLIFN (result) & ~UNUSED_COMMANDS; /* strip the commands we don't use as an efficiency */
|
|
|
|
while (command_set) { /* process the set of returned interface commands */
|
|
command = TLNEXTIFN (command_set); /* isolate the next command */
|
|
|
|
switch (command) { /* dispatch an interface command */
|
|
|
|
case IFIN: /* Interface In */
|
|
if (flags & DTRDY) /* if the buffer is still full */
|
|
flags |= OVRUN; /* then this input overruns it */
|
|
|
|
buffer_word = TLIBUS (result); /* store the data word in the buffer */
|
|
flags |= DTRDY; /* and set the data ready flag */
|
|
break;
|
|
|
|
|
|
case IFOUT: /* Interface Out */
|
|
if ((flags & DTRDY) == NO_FLAGS) /* if the buffer is empty */
|
|
flags |= OVRUN; /* then this output underruns it */
|
|
|
|
flags &= ~DTRDY; /* clear the data ready flag */
|
|
break;
|
|
|
|
|
|
case IFGTC: /* Interface Get Command */
|
|
flags = flags & INTOK | EOD; /* clear the interface transfer flags and set EOD */
|
|
|
|
command_class = (CNTLR_CLASS) TLIBUS (result); /* save the command classification */
|
|
break;
|
|
|
|
|
|
case RQSRV: /* Request Service */
|
|
device_sr = SET; /* set the device service request flip-flop */
|
|
break;
|
|
|
|
|
|
case DVEND: /* Device End */
|
|
device_end = SET; /* set the device end flip-flop */
|
|
break;
|
|
|
|
|
|
case DATTN: /* Drive Attention */
|
|
unit_interrupt = SET; /* set the unit interrupt flip-flop */
|
|
attention_unit = TLIBUS (result); /* and save the number of the requesting unit */
|
|
|
|
/* fall into the STINT case */
|
|
|
|
case STINT: /* Set Interrupt */
|
|
flags = NO_FLAGS; /* clear the interface transfer flags and INTOK */
|
|
|
|
clear_interface_logic (); /* clear the interface to abort the transfer */
|
|
|
|
ms_dib.interrupt_request = SET; /* set the interrupt request flip-flop */
|
|
|
|
if (interrupt_mask == SET) /* if the interrupt mask is satisfied */
|
|
iop_assert_INTREQ (&ms_dib); /* then assert the INTREQ signal */
|
|
break;
|
|
|
|
|
|
case SCPE: /* SCP Error Status */
|
|
status = TLIBUS (result); /* get the status code */
|
|
break;
|
|
|
|
case STDFL: /* not decoded by this interface */
|
|
case STCFL: /* not decoded by this interface */
|
|
break;
|
|
}
|
|
|
|
command_set &= ~command; /* remove the current command from the set */
|
|
} /* and continue with the remaining commands */
|
|
|
|
return status; /* return the result of the call */
|
|
}
|