/* 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 | |
13-May-16 JDB Modified for revised SCP API function parameter types | |
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, CONST char *cptr); | |
static t_stat ms_onoffline (UNIT *uptr, int32 value, CONST char *cptr, void *desc); | |
/* Interface local utility routines */ | |
static void master_reset (void); | |
static void clear_interface_logic (void); | |
static t_stat call_controller (UNIT *uptr); | |
/* Interface SCP data structures */ | |
/* Device information block */ | |
static DIB ms_dib = { | |
&ms_interface, /* device interface */ | |
6, /* device number */ | |
3, /* service request number */ | |
14, /* interrupt priority */ | |
INTMASK_E /* interrupt mask */ | |
}; | |
/* Unit list */ | |
#define UNIT_FLAGS (UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_OFFLINE) | |
static UNIT ms_unit [UNIT_COUNT] = { | |
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 0 */ | |
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 1 */ | |
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 2 */ | |
{ UDATA (&ms_service, UNIT_FLAGS | UNIT_7970E, 0) }, /* drive unit 3 */ | |
{ UDATA (&ms_service, UNIT_DIS, 0) } /* controller unit */ | |
}; | |
/* Register list */ | |
static REG ms_reg [] = { | |
/* Macro Name Location Width Offset Flags */ | |
/* ------ ------ -------------- ----- ------ ------------------------- */ | |
{ FLDATA (SIOBSY, sio_busy, 0) }, | |
{ FLDATA (CHANSR, channel_sr, 0) }, | |
{ FLDATA (DEVSR, device_sr, 0) }, | |
{ FLDATA (INXFR, input_xfer, 0) }, | |
{ FLDATA (OUTXFR, output_xfer, 0) }, | |
{ FLDATA (INTMSK, interrupt_mask, 0) }, | |
{ FLDATA (UINTRP, unit_interrupt, 0) }, | |
{ FLDATA (DEVEND, device_end, 0) }, | |
{ FLDATA (XFRERR, xfer_error, 0) }, | |
{ ORDATA (BUFWRD, buffer_word, 16), REG_A | REG_FIT | PV_RZRO }, | |
{ DRDATA (ATUNIT, attention_unit, 16), REG_FIT | PV_LEFT }, | |
{ DRDATA (CLASS, command_class, 4), PV_LEFT }, | |
{ YRDATA (FLAGS, flags, 8) }, | |
{ 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, CONST char *cptr) | |
{ | |
t_stat result; | |
result = tl_attach (&ms_cntlr, uptr, cptr); /* attach the drive */ | |
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */ | |
call_controller (NULL); /* then let it know to poll the drives */ | |
return SCPE_OK; /* before returning with success */ | |
} | |
else /* otherwise */ | |
return result; /* return the status of the attach */ | |
} | |
/* Set the drive online or offline. | |
The SET MSn OFFLINE command simulates pressing the RESET button, and the SET | |
MSn ONLINE command simulates pressing the ONLINE button. The transition from | |
offline to online causes a Unit Attention interrupt. The SET request fails | |
if there is no tape mounted on the drive, i.e., if the unit is not attached | |
to a tape image file. | |
The controller library routine handles command validation and setting the | |
appropriate drive unit status. It will return an error code if the command | |
fails. Otherwise, it will return SCPE_INCOMP if the command must be | |
completed with a controller call or SCPE_OK if the command is complete. If | |
the controller is idle, a call will be needed to poll the drives for | |
attention; otherwise, the drives will be polled the next time the controller | |
becomes idle. | |
*/ | |
static t_stat ms_onoffline (UNIT *uptr, int32 value, CONST char *cptr, void *desc) | |
{ | |
const t_bool online = (value != UNIT_OFFLINE); /* TRUE if the drive is being put online */ | |
t_stat result; | |
result = tl_onoffline (&ms_cntlr, uptr, online); /* set the drive online or offline */ | |
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */ | |
call_controller (NULL); /* then let it know to poll the drives */ | |
return SCPE_OK; /* before returning with success */ | |
} | |
else /* otherwise */ | |
return result; /* return the status of the load or unload */ | |
} | |
/* Interface local utility routines */ | |
/* Master reset. | |
A master reset is generated either by an I/O Reset signal or a Programmed | |
Master Clear (CIO bit 0). It initializes the interface and the tape | |
controller to their respective idle states. Clearing the controller aborts | |
all commands in progress and stops all drive motion except for rewinding, | |
which completes normally. | |
*/ | |
static void master_reset (void) | |
{ | |
tl_clear (&ms_cntlr); /* clear the controller to stop the drives */ | |
ms_dib.interrupt_request = CLEAR; /* clear any current */ | |
ms_dib.interrupt_active = CLEAR; /* interrupt request */ | |
interrupt_mask = SET; /* set the interrupt mask */ | |
flags = INTOK; /* and the Interrupt OK flag */ | |
xfer_error = CLEAR; /* clear the transfer error flip-flop */ | |
clear_interface_logic (); /* clear the interface to abort the transfer */ | |
return; | |
} | |
/* Clear interface logic. | |
The clear interface logic signal is asserted during channel operation when | |
the controller is reset or requests an interrupt, the channel indicates a | |
transfer failure by asserting XFERERROR, or a master reset occurs. It clears | |
the SIO Busy, Channel and Device Service Request, Input Transfer, Output | |
Transfer, and Device End flip-flops. | |
*/ | |
static void clear_interface_logic (void) | |
{ | |
sio_busy = CLEAR; /* clear the SIO busy flip-flop */ | |
channel_sr = CLEAR; /* and the channel service request flip-flop */ | |
device_sr = CLEAR; /* and the device service request flip-flop */ | |
input_xfer = CLEAR; /* and the input transfer flip-flop */ | |
output_xfer = CLEAR; /* and the output transfer flip-flop */ | |
device_end = CLEAR; /* and the device end flip-flop */ | |
return; | |
} | |
/* Call the tape controller. | |
The abstract tape controller connects to the CPU interface via 16-bit data, | |
flag, and function buses. The controller monitors the flag bus and reacts to | |
the interface changing the flag states by placing or accepting data on the | |
data bus and issuing commands to the interface via the function bus. | |
In simulation, a call to the tl_controller routine informs the controller of | |
a (potential) change in flag state. The current set of flags and data bus | |
value are supplied, and the controller returns a combined set of functions | |
and a data bus value. | |
The controller must be called any time there is a change in the state of the | |
interface or the drive units. Generally, the cases that require notification | |
are when the interface: | |
- has a new command to execute | |
- has detected the channel starting, ending, or aborting the transfer | |
- has a new data word available to send | |
- has obtained the last data word received | |
- has received a unit service event notification | |
- has detected the mounting of the tape reel on a drive | |
- has detected a drive being placed online or offline | |
- has detected the interrupt request being reset | |
The set of returned functions is processed sequentially, updating the | |
interface state as indicated. Some functions are not used by this interface, | |
so they are masked off before processing to improve performance. | |
Because the tape is a synchronous device, overrun or underrun can occur if | |
the interface is not ready when the controller must transfer data. There are | |
four conditions that lead to an overrun or underrun: | |
1. The controller is ready with a tape read word (IFIN), but the interface | |
buffer is full (DTRDY). | |
2. The controller needs a tape write word (IFOUT), but the interface buffer | |
is empty (~DTRDY). | |
3. The CPU attempts to read a word, but the interface buffer is empty | |
(~DTRDY). | |
4. The CPU attempts to write a word, but the interface buffer is full | |
(DTRDY). | |
The interface detects the first two conditions and sets the data overrun flag | |
if either occurs. The hardware design of the interface prevents the last two | |
conditions, as the interface will assert SRn only when the buffer is full | |
(read) or empty (write). | |
Implementation notes: | |
1. In hardware, data overrun and underrun are detected as each byte is moved | |
between the tape unit and the data buffer register. In simulation, OVRUN | |
will not be asserted when the controller is called with the full or empty | |
buffer; instead, it will be asserted for the next controller call. | |
Because the controller will be called for the tape stop phase, and | |
because OVRUN isn't checked until that point, this "late" assertion does | |
not affect overrun or underrun detection. | |
*/ | |
static t_stat call_controller (UNIT *uptr) | |
{ | |
CNTLR_IFN_IBUS result; | |
CNTLR_IFN_SET command_set; | |
CNTLR_IFN command; | |
t_stat status = SCPE_OK; | |
result = /* call the controller to start or continue a command */ | |
tl_controller (&ms_cntlr, uptr, flags, 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 */ | |
} |