1337 lines
63 KiB
C
1337 lines
63 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
|
|
|
|
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 (1 << 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 0100000 /* (M) master reset */
|
|
#define CN_RIN 0040000 /* (R) reset interrupt */
|
|
#define CN_UNIT_MASK 0001400 /* unit number mask */
|
|
#define CN_RSVD_MASK 0000360 /* reserved mask */
|
|
#define CN_CMD_MASK 0000017 /* command code mask */
|
|
|
|
#define CN_CMD_RDR 0000006 /* 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 0100000 /* (S) SIO OK to use */
|
|
/* ST_ODD_COUNT 0040000 */ /* (B) byte count is odd (supplied by hp_tapelib) */
|
|
#define ST_INTREQ 0020000 /* (I) interrupt requested */
|
|
#define ST_UNIT_MASK 0014000 /* unit selected mask */
|
|
/* ST_EOT 0002000 */ /* (E) end of tape (supplied by hp_tapelib) */
|
|
/* ST_PROTECTED 0001000 */ /* (P) write protected (supplied by hp_tapelib) */
|
|
/* ST_READY 0000400 */ /* (R) unit ready (supplied by hp_tapelib) */
|
|
/* ST_LOAD_POINT 0000200 */ /* (L) load point (supplied by hp_tapelib) */
|
|
/* ST_DENSITY_1600 0000100 */ /* (D) 1600 bpi density (supplied by hp_tapelib) */
|
|
/* ST_WRITE_STATUS 0000040 */ /* (W) write status (supplied by hp_tapelib) */
|
|
/* ST_TAPE_MARK 0000020 */ /* (M) tape mark (supplied by hp_tapelib) */
|
|
#define ST_ERROR_MASK 0000016 /* encoded error field mask */
|
|
/* ST_7_TRACK 0000001 */ /* (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 0000016 /* unit interrupt */
|
|
#define ST_XFER 0000014 /* transfer error */
|
|
/* ST_REJECT 0000012 */ /* command reject (supplied by hp_tapelib) */
|
|
/* ST_RUNAWAY 0000010 */ /* tape runaway (supplied by hp_tapelib) */
|
|
/* ST_TIMING 0000006 */ /* timing error (supplied by hp_tapelib) */
|
|
/* ST_PARITY 0000004 */ /* tape error (supplied by hp_tapelib) */
|
|
/* ST_RESERVED 0000002 */ /* (reserved) */
|
|
/* ST_NOERROR 0000000 */ /* 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 = CLEAR; /* 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 uint16 buffer_word = 0; /* data buffer word */
|
|
static uint16 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 uint8 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, char *cptr);
|
|
static t_stat ms_onoffline (UNIT *uptr, int32 value, 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) },
|
|
{ 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, uint16 inbound_value)
|
|
{
|
|
CNTLR_OPCODE opcode;
|
|
INBOUND_SIGNAL signal;
|
|
INBOUND_SET working_set = inbound_signals;
|
|
uint16 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:
|
|
interrupt_mask = /* set the mask flip-flop */
|
|
D_FF (dibptr->interrupt_mask & inbound_value); /* from the mask bit and 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 %d\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 = (uint16) Invalid_Opcode; /* then reject the command */
|
|
else /* otherwise */
|
|
buffer_word = (uint16) 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 (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)
|
|
{
|
|
t_stat status;
|
|
|
|
master_reset (); /* perform a master reset */
|
|
|
|
status = tl_reset (&ms_cntlr); /* reset the controller */
|
|
|
|
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 */
|
|
}
|
|
|
|
return status; /* return the result of the reset */
|
|
}
|
|
|
|
|
|
/* 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, 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, 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, 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 */
|
|
}
|