blob: 9d429a9ce63ac839fa6530dfd1115883f2664741 [file] [log] [blame] [raw]
/* 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 */
}