/* hp3000_ms.c: HP 3000 30215A Magnetic Tape Controller Interface simulator

   Copyright (c) 2016-2021, J. David Bryan

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
   AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of the author shall not be used
   in advertising or otherwise to promote the sale, use or other dealings in
   this Software without prior written authorization from the author.

   MS           HP 30215A Magnetic Tape Controller Interface

   01-Apr-21    JDB     Removed REG_FIT from register definitions (obsolete)
   09-Dec-19    JDB     Replaced debugging macros with tracing macros
   27-Dec-18    JDB     Revised fall through comments to comply with gcc 7
   05-Sep-17    JDB     Changed REG_A (permit any symbolic override) to REG_X
   12-Sep-16    JDB     Changed DIB register macro usage from SRDATA to DIB_REG
   09-Jun-16    JDB     Added casts for ptrdiff_t to int32 values
   08-Jun-16    JDB     Corrected %d format to %u for unsigned values
   16-May-16    JDB     Fixed interrupt mask setting
   13-May-16    JDB     Modified for revised SCP API function parameter types
   24-Mar-16    JDB     Changed the buffer element type from uint8 to TL_BUFFER
   21-Mar-16    JDB     Changed uint16 types to HP_WORD
   10-Nov-15    JDB     First release version
   26-Oct-14    JDB     Passes the magnetic tape diagnostic (D433A)
   10-Feb-13    JDB     Created

   References:
     - 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Maintenance Manual
         (30115-90001, June 1976)
     - 30115A Nine-Track (NRZI-PE) Magnetic Tape Subsystem Microprogram Listing
         (30115-90005, January 1974)
     - Stand-Alone HP 30115A (7970B/E) Magnetic Tape (NRZI-PE) Diagnostic
         (30115-90014, May 1976)


   The HP 30115A Magnetic Tape Subsystem connects the 7970B/E 1/2-inch magnetic
   tape drives to the HP 3000.  The subsystem consists of a 30215A two-card tape
   controller processor and controller interface, and from one to four HP 7970B
   800-bpi NRZI or HP 7970E 1600-bpi PE drives.  The two drive types can be
   mixed on a single controller.  The subsystem uses the Multiplexer Channel to
   achieve a 36 KB/second (NRZI) or 72 KB/second (PE) transfer rate to the CPU.

   This module simulates the controller interface.  The controller processor
   simulation is provided by the HP magnetic tape controller simulator library
   (hp_tapelib).  Rather than simulating the signal interaction specific to
   these two cards, the HP tape library simulates an abstract controller having
   an electrical interface modelled on the HP 13037 disc controller.  The CPU
   interface and tape controller interact via 16-bit data, flag, and function
   buses.  Commands, status, and data are exchanged across the data bus, with
   the flag bus providing indications of the state of the interface and the
   function bus indicating what actions the interface must take in response to
   command processing by the controller.  By specifying the controller type as
   an HP 30215, the abstract controller adopts the personality of the HP 3000
   tape controller.

   While the interface and controller are idle, a drive unit that changes from
   Not Ready to Ready status will cause an interrupt.  This occurs when an
   offline drive is put online (e.g., after mounting a tape) and when a
   rewinding drive completes the action and is repositioned at the load point.

   An interrupt also occurs if an error terminates the current command.  The
   cause of the interrupt is encoded in the status word.  All error codes are
   cleared to the No Error state whenever a new SIO program is started.

   A new command may be rejected for one of several reasons:

     - the unit is not ready for any command requiring tape motion
     - the tape has no write ring and a write command is issued
     - an illegal command opcode is issued
     - illegal bits are set in the control word
     - a command is issued while the controller is busy
     - a TOGGLEOUTXFER signal asserts without a write data command in process
     - a TOGGLEINXFER signal asserts without a read data command in process
     - a PCONTSTB signal asserts with the input or output transfer flip-flops set

   Examples of the last three rejection reasons are:

     - a Write File Mark control order is followed by a write channel order
     - a Write Record control order is followed by a read channel order
     - a write channel order is followed by a Write Record control order


   The tape interface responds to direct and programmed I/O instructions, as
   follows:

   Control Word Format (CIO):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | -   -   -   -   -   -   -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     M = programmed master clear
     R = reset interrupts


   Control Word Format (SIO Control):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   - |  word 1
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   - | unit  | 0   0   0   0 | command code  |  word 2
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Unit:

     00 = select unit 0
     01 = select unit 1
     10 = select unit 2
     11 = select unit 3

   Command code:

     00 = Select Unit
     04 = Write Record
     05 = Write Gap
     06 = Read Record
     07 = Forward Space Record
     10 = Rewind
     11 = Rewind and Reset
     12 = Backspace Record
     13 = Backspace File
     14 = Write Record with Zero Parity
     15 = Write File Mark
     16 = Read Record with CRCC
     17 = Forward Space File

   Control word 1 is not used.

   The unit field is used only with the Select Unit command.  Bits 8-11 must be
   zero, or a Command Reject error will occur.  Command codes 01-03 are reserved
   and will cause a Command Reject error if specified.  Codes 14 and 16 are used
   for diagnostics only.


   Status Word Format (TIO and SIO Status):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | B | I | unit  | E | P | R | L | D | W | M | err code  | T |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = SIO OK
     B = byte count is odd
     I = interrupt requested
     E = end of tape
     P = write protected
     R = drive ready
     L = load point
     D = density 800/1600 (0/1)
     W = write status (last operation was a write of any kind)
     M = tape mark
     T = 9-track drive/7-track drive (0/1)

   Unit:

     00 = reporting unit 0
     01 = reporting unit 1
     10 = reporting unit 2
     11 = reporting unit 3

   Error code:

     000 = unit interrupt
     001 = transfer error
     010 = command reject error
     011 = tape runaway error
     100 = timing error
     101 = tape error
     110 = (reserved)
     111 = no error

   A unit interrupt occurs when a drive goes online or when a rewind operation
   completes.  A transfer error occurs when the channel asserts XFERERROR to
   abort a transfer for a parity error or memory address out of bounds.  These
   two errors are generated by the interface and not by the HP tape library.

   A timing error occurs when a read overrun or write underrun occurs.  A tape
   error occurs when a tape parity, CRC error, or multi-track error occurs.
   Only these two errors may occur in the same transfer, with timing error
   having priority.  The other errors only occur independently.


   Output Data Word Format (SIO Write):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  data buffer register value                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+


   Input Data Word Format (SIO Read):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  data buffer register value                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+


   The interface does not respond to WIO or RIO instructions.

   Tape read or write commands may transfer up to 4K words with a single SIO
   Read or Write order.  Chained orders are necessary if longer transfers are
   required.  However, if a chained read completes with a record shorter than
   the transfer length, a Command Reject will occur.


   Implementation notes:

    1. In hardware, each tape drive has four buttons numbered 0 to 3 that select
       the unit number to which the drive responds, plus an OFF button that
       inhibits drive selection (effectively disconnecting the drive from the
       controller).  Pressing a numbered button changes the unit number without
       altering the tape position or condition.

       In simulation, the tape unit number corresponds to the simulation unit
       number.  For example, simulation unit MS0 responds when the controller
       addresses tape unit 0.  The correspondence between tape and simulation
       unit numbers cannot be changed.  Therefore, changing a unit's number is
       accomplished by detaching the current tape image from the first
       simulation unit and attaching it to the second unit.  Note, however, that
       this resets the tape position to the load point, so it is not exactly
       equivalent.

    2. Per page 2-15 of the maintenance manual, during the idle state when no
       SIO program is active, the interface continuously selects one unit after
       another to look for a change from Not Ready to Ready status.  Therefore,
       the tape unit selected bits will be seen to change continuously.  In
       simulation, a change of status is noted when the change occurs, e.g.,
       when the SET <unit> ONLINE command is entered, so scanning is not
       necessary.  A program that continuously requests status will not see the
       unit select bits changing as in hardware.
*/



#include "hp3000_defs.h"
#include "hp3000_io.h"
#include "hp_tapelib.h"



/* Program constants */

#define DRIVE_COUNT         (TL_MAXDRIVE + 1)           /* the number of tape drive units */
#define UNIT_COUNT          (DRIVE_COUNT + TL_AUXUNITS) /* the total number of units */

#define cntlr_unit          ms_unit [TL_CNTLR_UNIT]     /* the controller unit alias */

#define UNUSED_COMMANDS     (STCFL | STDFL)             /* unused tape interface commands */


/* Debug flags (interface-specific) */

#define DEB_IOB             TL_DEB_IOB                  /* trace I/O bus signals and data words */
#define DEB_SERV            TL_DEB_SERV                 /* trace unit service scheduling calls */
#define DEB_CSRW            (1u << TL_DEB_V_UF + 0)     /* trace control, status, read, and write actions */


/* Control word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | M | R | -   -   -   -   -   -   -   -   -   -   -   -   -   - |  DIO
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   - |  PIO word 1
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   - | unit  | 0   0   0   0 | command code  |  PIO word 2
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CN_MR               0100000u            /* (M) master reset */
#define CN_RIN              0040000u            /* (R) reset interrupt */
#define CN_UNIT_MASK        0001400u            /* unit number mask */
#define CN_RSVD_MASK        0000360u            /* reserved mask */
#define CN_CMD_MASK         0000017u            /* command code mask */

#define CN_CMD_RDR          0000006u            /* Read Record command */

#define CN_UNIT_SHIFT       8                   /* unit number alignment shift */
#define CN_CMD_SHIFT        0                   /* command code alignment shift */

#define CN_UNIT(c)          (((c) & CN_UNIT_MASK) >> CN_UNIT_SHIFT)
#define CN_CMD(c)           (((c) & CN_CMD_MASK)  >> CN_CMD_SHIFT)

static const BITSET_NAME control_names [] = {   /* Control word names */
    "master reset",                             /*   bit  0 */
    "reset interrupt"                           /*   bit  1 */
    };

static const BITSET_FORMAT control_format =     /* names, offset, direction, alternates, bar */
    { FMT_INIT (control_names, 14, msb_first, no_alt, no_bar) };


/* Status word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | B | I | unit  | E | P | R | L | D | W | M | err code  | T |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+


   Implementation notes:

    1. The status bits for the encoded error field are complemented from their
       actual values.  This allows the tape library to use an all-zeros value to
       represent No Error, which is consistent with the values used by other
       controllers.  The encoded error field bits must be complemented before
       reporting the controller status.
 */

#define ST_SIO_OK           0100000u            /* (S) SIO OK to use */
/*      ST_ODD_COUNT        0040000u */         /* (B) byte count is odd (supplied by hp_tapelib) */
#define ST_INTREQ           0020000u            /* (I) interrupt requested */
#define ST_UNIT_MASK        0014000u            /* unit selected mask */
/*      ST_EOT              0002000u */         /* (E) end of tape (supplied by hp_tapelib) */
/*      ST_PROTECTED        0001000u */         /* (P) write protected (supplied by hp_tapelib) */
/*      ST_READY            0000400u */         /* (R) unit ready (supplied by hp_tapelib) */
/*      ST_LOAD_POINT       0000200u */         /* (L) load point (supplied by hp_tapelib) */
/*      ST_DENSITY_1600     0000100u */         /* (D) 1600 bpi density (supplied by hp_tapelib) */
/*      ST_WRITE_STATUS     0000040u */         /* (W) write status (supplied by hp_tapelib) */
/*      ST_TAPE_MARK        0000020u */         /* (M) tape mark (supplied by hp_tapelib) */
#define ST_ERROR_MASK       0000016u            /* encoded error field mask */
/*      ST_7_TRACK          0000001u */         /* (T) 7-track unit (always off) */

#define ST_UNIT_SHIFT       11                  /* unit number alignment shift */
#define ST_ERROR_SHIFT      1                   /* encoded error alignment shift */

#define ST_UNIT(n)          ((n) << ST_UNIT_SHIFT & ST_UNIT_MASK)

#define ST_TO_UNIT(s)       (((s) & ST_UNIT_MASK)  >> ST_UNIT_SHIFT)
#define ST_TO_ERROR(s)      (((s) & ST_ERROR_MASK) >> ST_ERROR_SHIFT)


/* Error codes (complements of the values returned) */

#define ST_UNITIRQ          0000016u            /* unit interrupt */
#define ST_XFER             0000014u            /* transfer error */
/*      ST_REJECT           0000012u */         /* command reject (supplied by hp_tapelib) */
/*      ST_RUNAWAY          0000010u */         /* tape runaway (supplied by hp_tapelib) */
/*      ST_TIMING           0000006u */         /* timing error (supplied by hp_tapelib) */
/*      ST_PARITY           0000004u */         /* tape error (supplied by hp_tapelib) */
/*      ST_RESERVED         0000002u */         /* (reserved) */
/*      ST_NOERROR          0000000u */         /* no error */

static const BITSET_NAME status_names [] = {    /* Status word names */
    "SIO OK",                                   /*   bit  0 */
    "odd count",                                /*   bit  1 */
    "interrupt",                                /*   bit  2 */
    NULL,                                       /*   bit  3 */
    NULL,                                       /*   bit  4 */
    "end of tape",                              /*   bit  5 */
    "protected",                                /*   bit  6 */
    "ready",                                    /*   bit  7 */
    "load point",                               /*   bit  8 */
    "1600 bpi",                                 /*   bit  9 */
    "writing",                                  /*   bit 10 */
    "tape mark",                                /*   bit 11 */
    NULL,                                       /*   bit 12 */
    NULL,                                       /*   bit 13 */
    NULL,                                       /*   bit 14 */
    "7 track"                                   /*   bit 15 */
    };

static const BITSET_FORMAT status_format =      /* names, offset, direction, alternates, bar */
    { FMT_INIT (status_names, 0, msb_first, no_alt, append_bar) };


static const char *const error_names [] = {     /* error status code names */
    "unit interrupt",                           /*   code 0 */
    "transfer error",                           /*   code 1 */
    "command reject",                           /*   code 2 */
    "tape runaway",                             /*   code 3 */
    "timing error",                             /*   code 4 */
    "tape error",                               /*   code 5 */
    "reserved",                                 /*   code 6 */
    "no error"                                  /*   code 7 */
    };


/* Interface command code to controller opcode translation table */

static const CNTLR_OPCODE to_opcode [] = {      /* opcode translation table (fully decoded) */
    Select_Unit_0,                              /*   000 SEL = Select Unit */
    Invalid_Opcode,                             /*   001 --- = invalid */
    Invalid_Opcode,                             /*   002 --- = invalid */
    Invalid_Opcode,                             /*   003 --- = invalid */
    Write_Record,                               /*   004 WRR = Write Record */
    Write_Gap,                                  /*   005 GAP = Write Gap */
    Read_Record,                                /*   006 RDR = Read Record */
    Forward_Space_Record,                       /*   007 FSR = Forward Space Record */
    Rewind,                                     /*   010 REW = Rewind */
    Rewind_Offline,                             /*   011 RST = Rewind and Reset */
    Backspace_Record,                           /*   012 BSR = Backspace Record */
    Backspace_File,                             /*   013 BSF = Backspace File */
    Write_Record_without_Parity,                /*   014 WRZ = Write Record with Zero Parity */
    Write_File_Mark,                            /*   015 WFM = Write File Mark */
    Read_Record_with_CRCC,                      /*   016 RDC = Read Record with CRCC */
    Forward_Space_File                          /*   017 FSF = Forward Space File */
    };


/* Tape controller library data structures */

#define MS_REW_START        uS (10)             /* fast rewind start time */
#define MS_REW_RATE         uS (1)              /* fast rewind time per inch of travel */
#define MS_REW_STOP         uS (10)             /* fast rewind stop time */
#define MS_START            uS (10)             /* fast BOT/interrecord start delay time */
#define MS_DATA             uS (1)              /* fast per-byte data transfer time */
#define MS_OVERHEAD         uS (10)             /* fast controller overhead time */

static DELAY_PROPS fast_times =                 /* FASTTIME delays */
    { DELAY_INIT (MS_REW_START, MS_REW_RATE,
                  MS_REW_STOP,  MS_START,
                  MS_START,     MS_DATA,
                  MS_OVERHEAD)  };


/* Interface state */

static FLIP_FLOP sio_busy       = CLEAR;                /* SIO busy flip-flop */
static FLIP_FLOP channel_sr     = CLEAR;                /* channel service request flip-flop */
static FLIP_FLOP device_sr      = CLEAR;                /* device service request flip-flop */
static FLIP_FLOP input_xfer     = CLEAR;                /* input transfer flip-flop */
static FLIP_FLOP output_xfer    = CLEAR;                /* output transfer flip-flop */
static FLIP_FLOP interrupt_mask = SET;                  /* interrupt mask flip-flop */
static FLIP_FLOP unit_interrupt = CLEAR;                /* unit ready flip-flop */
static FLIP_FLOP device_end     = CLEAR;                /* device end flip-flop */
static FLIP_FLOP xfer_error     = CLEAR;                /* transfer error flip-flop */

static HP_WORD        buffer_word    = 0;               /* data buffer word */
static HP_WORD        attention_unit = 0;               /* number of the unit requesting attention */
static CNTLR_CLASS    command_class  = Class_Invalid;   /* current command classification */
static CNTLR_FLAG_SET flags          = INTOK;           /* tape controller interface flag set */

static TL_BUFFER buffer [TL_BUFSIZE];                   /* the tape record buffer */

DEVICE ms_dev;                                          /* incomplete device structure */

static CNTLR_VARS ms_cntlr =                            /* the tape controller */
    { CNTLR_INIT (HP_30215, ms_dev, buffer, fast_times) };


/* Interface local SCP support routines */

static CNTLR_INTRF ms_interface;
static t_stat      ms_service   (UNIT   *uptr);
static t_stat      ms_reset     (DEVICE *dptr);
static t_stat      ms_boot      (int32 unit_number, DEVICE *dptr);
static t_stat      ms_attach    (UNIT  *uptr,       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_X | PV_RZRO },
    { DRDATA (ATUNIT, attention_unit,  16),                  PV_LEFT },
    { DRDATA (CLASS,  command_class,    4),                  PV_LEFT },
    { YRDATA (FLAGS,  flags,            8),                  PV_RZRO },

      DIB_REGS (ms_dib),

      TL_REGS (ms_cntlr, ms_unit, DRIVE_COUNT, buffer, fast_times),

    { NULL }
    };

/* Modifier list */

static MTAB ms_mod [] = {

    TL_MODS (ms_cntlr, TL_7970B | TL_7970E, TL_FIXED, ms_onoffline),

/*    Entry Flags  Value        Print String  Match String  Validation   Display       Descriptor       */
/*    -----------  -----------  ------------  ------------  -----------  ------------  ---------------- */
    { MTAB_XDV,    VAL_DEVNO,   "DEVNO",      "DEVNO",      &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
    { MTAB_XDV,    VAL_INTMASK, "INTMASK",    "INTMASK",    &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
    { MTAB_XDV,    VAL_INTPRI,  "INTPRI",     "INTPRI",     &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
    { MTAB_XDV,    VAL_SRNO,    "SRNO",       "SRNO",       &hp_set_dib, &hp_show_dib, (void *) &ms_dib },
    { 0 }
    };

/* Debugging trace list */

static DEBTAB ms_deb [] = {
    { "CMD",   TL_DEB_CMD   },                  /* controller commands */
    { "INCO",  TL_DEB_INCO  },                  /* controller command initiations and completions */
    { "CSRW",  DEB_CSRW     },                  /* interface control, status, read, and write actions */
    { "STATE", TL_DEB_STATE },                  /* controller execution state changes */
    { "SERV",  DEB_SERV     },                  /* controller unit service scheduling calls */
    { "XFER",  TL_DEB_XFER  },                  /* controller data reads and writes */
    { "IOBUS", DEB_IOB      },                  /* interface and controller I/O bus signals and data words */
    { NULL,    0            }
    };

/* Device descriptor */

DEVICE ms_dev = {
    "MS",                                       /* device name */
    ms_unit,                                    /* unit array */
    ms_reg,                                     /* register array */
    ms_mod,                                     /* modifier array */
    UNIT_COUNT,                                 /* number of units */
    10,                                         /* address radix */
    32,                                         /* address width = 4 GB */
    1,                                          /* address increment */
    8,                                          /* data radix */
    8,                                          /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &ms_reset,                                  /* reset routine */
    &ms_boot,                                   /* boot routine */
    &ms_attach,                                 /* attach routine */
    &tl_detach,                                 /* detach routine */
    &ms_dib,                                    /* device information block pointer */
    DEV_DISABLE | DEV_DEBUG,                    /* device flags */
    0,                                          /* debug control flags */
    ms_deb,                                     /* debug flag name array */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* Interface local SCP support routines */



/* Magnetic tape interface.

   The interface is installed on the IOP and Multiplexer Channel buses and
   receives direct and programmed I/O commands from the IOP and Multiplexer
   Channel, respectively.  In simulation, the asserted signals on the buses are
   represented as bits in the inbound_signals set.  Each signal is processed
   sequentially in numerical order, and a set of similar outbound_signals is
   assembled and returned to the caller, simulating assertion of the
   corresponding backplane signals.

   The DCONTSTB signal qualifies direct I/O control word bits 0 and 1 (master
   reset and reset interrupt, respectively) only.  The PCONTSTB signal does not
   enable these functions.  A master reset is identical to an IORESET signal
   assertion; the current command is aborted, all drives are stopped (unless
   rewinding), and the interface is cleared.  The reset interrupt function
   clears the Interrupt Request flip-flop; it does not affect the Interrupt
   Active flip-flop.

   Controller commands are executed by the PCONTSTB signal.  Command opcodes are
   carried in the IOAW of the control order.  The IOCW is not used.  Commands
   that transfer data must be followed by the appropriate read or write I/O
   order.  The controller sets up the associated command during PCONTSTB
   processing but does not actually initiate tape movement (i.e., does not begin
   start phase processing) until the corresponding TOGGLEINXFER or TOGGLEOUTXFER
   signal is asserted.

   The DSTATSTB and PSTATSTB signals are tied together in hardware and therefore
   perform identically.  Both return the status of the currently selected tape
   drive unit.

   The DREADSTB and DWRITESTB signals are acknowledged but perform no other
   function.  DREADSTB returns all-zeros data.

   A channel transfer error asserts XFERERROR, which sets the xfer_error
   flip-flop.  This causes the interface to assert a Transfer Error interrupt
   until the flip-flop is cleared by a Programmed Master Clear.  The controller
   sees no error indication; it simply hangs while waiting for the next data
   transfer, which does not occur because the channel transfer was aborted.
   This condition persists until a PMC occurs, which performs a hardware restart
   on the controller.


   Implementation notes:

    1. A unit interrupt ORs in the unit interrupt status code, rather than
       masking out any previous code.  This works because the code is all ones,
       which overrides any prior code.

       Similarly, a transfer error ORs in its status code, which is all ones
       except for the LSB.  This would fail if a code already present had the
       LSB set.  The only codes which do are ST_REJECT and ST_TIMING, and
       neither of these can be present when a transfer error occurs (a transfer
       error can only occur in the data phase due to a bad memory bank number; a
       timing error is set in the stop phase, after the transfer error has
       aborted the command, and a reject error is set in the wait phase, before
       the transfer is begun).

    2. Command errors and units becoming ready cause interrupts.  Once an
       interrupt is asserted, the controller sits in a tight loop waiting for
       the interrupt to be reset.  When it is, the controller returns to the
       idle loop and looks for the next command.

       In simulation, when a command is issued with an interrupt in process, the
       command is set up, the command ready flag is set, but the controller is
       not notified.  When DRESETINT is received, the controller will be
       called to start the command, which provides the same semantics.

    3. The maintenance manual states that DREADSTB and DWRITESTB are not used.
       But the schematic shows that DREADSTB is decoded and will enable the DATA
       IN lines when asserted.  However, none of the output drivers on that
       ground-true bus will be enabled.  There are pullups on all bits except
       6-13, which would be driven (if enabled) by the device number buffer.  So
       it appears that executing an RIO instruction will return zeros for bits
       0-5 and 14-15, with bits 6-13 indeterminate.

    4. The controller opcodes Select_Unit_0 through Select_Unit_3 are
       contiguous, so the interface may derive these opcodes for the SEL command
       by adding the unit number to the Select_Unit_0 value.

    5. In hardware, the controller microcode checks the input and output
       transfer flip-flops while waiting for a new command.  If either are set,
       a command reject is indicated.  This occurs if a Read or Write order
       precedes a Control order.  It also occurs if chained Read order is
       terminated with a Device End condition due to a record length shorter
       than the transfer length.

       In simulation, these conditions are tested separately.  A premature Read
       or Write order will be caught during TOGGLEINXFER or TOGGLEOUTXFER
       processing, and a chained Read order after a Device End will be caught
       during READNEXTWD processing when the device end flip-flop set.  In both
       cases, the controller is called to continue a command, but no command is
       in process, so a reject occurs.  Note that these conditions will no
       longer exist when a Control order is received, so tests there are not
       required.

    6. In hardware, the EOT, READNEXTWD, and SETJMP signals are ignored, and the
       JMPMET signal is asserted continuously when enabled by CHANSO.
*/

static SIGNALS_DATA ms_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
CNTLR_OPCODE   opcode;
INBOUND_SIGNAL signal;
INBOUND_SET    working_set      = inbound_signals;
HP_WORD        outbound_value   = 0;
OUTBOUND_SET   outbound_signals = NO_SIGNALS;

tprintf (ms_dev, DEB_IOB, "Received data %06o with signals %s\n",
         inbound_value, fmt_bitset (inbound_signals, inbound_format));

while (working_set) {
    signal = IONEXTSIG (working_set);                   /* isolate the next signal */

    switch (signal) {                                   /* dispatch an I/O signal */

        case INTPOLLIN:
            if (dibptr->interrupt_request) {            /* if a request is pending */
                dibptr->interrupt_request = CLEAR;      /*   then clear it */
                dibptr->interrupt_active  = SET;        /*     and mark it now active */

                outbound_signals |= INTACK;             /* acknowledge the interrupt */
                outbound_value = dibptr->device_number; /*   and return our device number */
                }

            else                                        /* otherwise the request has been reset */
                outbound_signals |= INTPOLLOUT;         /*   so let the IOP know to cancel it */
            break;


        case SETINT:
        case DSETINT:
            dibptr->interrupt_request = SET;            /* request an interrupt */
            flags &= ~INTOK;                            /*   and clear the interrupt OK flag */

            if (interrupt_mask == SET)                  /* if the interrupt mask is satisfied */
                outbound_signals |= INTREQ;             /*   then assert the INTREQ signal */
            break;


        case DRESETINT:
            dibptr->interrupt_active = CLEAR;           /* reset the interrupt active */
            unit_interrupt           = CLEAR;           /*   and unit interrupt flip-flops */

            if (dibptr->interrupt_request == CLEAR) {   /* if there's no request pending */
                if (sio_busy == CLEAR)                  /*   then if an SIO program is not executing */
                    flags |= INTOK;                     /*     then set the interrupt OK flag */

                if (flags & (CMRDY | INTOK))            /* if a command is present or a poll is needed */
                    call_controller (NULL);             /*   then tell the controller */

                if (device_sr)                          /* if the interface has requested service */
                    outbound_signals |= SRn;            /*   then assert SRn to the channel */
                }
            break;


        case DSETMASK:
            if (dibptr->interrupt_mask == INTMASK_E)            /* if the mask is always enabled */
                interrupt_mask = SET;                           /*   then set the mask flip-flop */
            else                                                /* otherwise */
                interrupt_mask = D_FF (dibptr->interrupt_mask   /*   set the mask flip-flop if the mask bit */
                                       & inbound_value);        /*     is present in the mask value */

            if (interrupt_mask && dibptr->interrupt_request)    /* if the mask is enabled and a request is pending */
                outbound_signals |= INTREQ;                     /*   then assert the INTREQ signal */
            break;


        case DCONTSTB:
            tprintf (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 */

            tprintf (ms_dev, DEB_CSRW, "Status is %s%s | unit %u\n",
                     fmt_bitset (outbound_value, status_format),
                     error_names [ST_TO_ERROR (outbound_value)],
                     ST_TO_UNIT (outbound_value));
            break;


        case DSTARTIO:
            tprintf (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 */
                tprintf (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 */

            tprintf (ms_dev, DEB_CSRW, "Control is %06o (%s)\n",
                     inbound_value, tl_opcode_name (opcode));

            if ((inbound_value & CN_RSVD_MASK) != 0)    /* if the reserved bits aren't zero */
                buffer_word = (HP_WORD) Invalid_Opcode; /*   then reject the command */
            else                                        /* otherwise */
                buffer_word = (HP_WORD) opcode;         /*   store the opcode in the data buffer register */

            flags |= CMRDY | CMXEQ;                     /* set the command ready and execute flags */

            if (dibptr->interrupt_request == CLEAR      /* if no interrupt is pending */
              && dibptr->interrupt_active == CLEAR) {   /*   or active */
                call_controller (NULL);                 /*     then tell the controller to start the command */

                unit_interrupt = CLEAR;                 /* clear the unit interrupt flip-flop */
                }
            break;


        case READNEXTWD:
            if (device_end == SET                       /* if the device end flip-flop is set */
              && (inbound_signals & TOGGLESR)) {        /*   and we're starting (not continuing) a transfer */
                call_controller (&cntlr_unit);          /*     then let the controller know to reject */

                device_end = CLEAR;                     /* clear the device end condition */
                }
            break;


        case PREADSTB:
            if (device_end) {                               /* if the transfer has been aborted */
                outbound_value = dibptr->device_number * 4; /*   then return the DRT address */
                outbound_signals |= DEVEND;                 /*     and indicate a device abort */
                }

            else {                                          /* otherwise the transfer continues */
                outbound_value = buffer_word;               /*   so return the data buffer register value */
                flags &= ~DTRDY;                            /*     and clear the data ready flag */
                }
            break;


        case PWRITESTB:
            buffer_word = inbound_value;                /* save the word to write */
            flags |= DTRDY;                             /*   and set the data ready flag */
            break;


        case DEVNODB:
            outbound_value = dibptr->device_number * 4; /* return the DRT address */
            break;


        case XFERERROR:
            tprintf (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 */
    }

tprintf (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;

tprintf (ms_dev, DEB_SERV, "%s service entered\n",
         tl_unit_name ((int32) (uptr - ms_unit)));

result = call_controller (uptr);                        /* call the controller */

if (device_sr == SET)                                   /* if the device has requested service */
    mpx_assert_SRn (&ms_dib);                           /*   then assert SR to the channel */

return result;
}


/* Device reset routine.

   This routine is called for a RESET, RESET MS, or BOOT MS command.  It is the
   simulation equivalent of the IORESET signal, which is asserted by the front
   panel LOAD and DUMP switches.

   For this interface, IORESET is identical to the Programmed Master Clear.  In
   addition, if a power-on reset (RESET -P) is done, the original FASTTIME
   settings are restored.
*/

static t_stat ms_reset (DEVICE *dptr)
{
if (sim_switches & SWMASK ('P')) {                      /* if this is a power-on reset */
    fast_times.rewind_start = MS_REW_START;             /*   then reset the rewind initiation time, */
    fast_times.rewind_rate  = MS_REW_RATE;              /*     the rewind time per inch, */
    fast_times.bot_start    = MS_START;                 /*       the beginning-of-tape gap traverse time, */
    fast_times.ir_start     = MS_START;                 /*         the interrecord traverse time, */
    fast_times.data_xfer    = MS_DATA;                  /*           the per-byte data transfer time, */
    fast_times.overhead     = MS_OVERHEAD;              /*             and the controller execution overhead */
    }

master_reset ();                                        /* perform a master reset */

return tl_reset (&ms_cntlr);                            /* reset the controller and return the result */
}


/* Device boot routine.

   This routine is called for the BOOT MS command to initiate the system cold
   load procedure for the tape.  It is the simulation equivalent to presetting
   the System Switch Register to the appropriate control and device number bytes
   and then pressing the ENABLE and LOAD front panel switches.

   For this interface, the switch register is set to %0030nn, where "nn"
   is the current tape interface device number, which defaults to 6.  The
   control byte is 06 (Read Record).

   The cold load procedure always uses unit 0.
*/

static t_stat ms_boot (int32 unit_number, DEVICE *dptr)
{
if (unit_number != 0)                                   /* if a unit other than 0 is specified */
    return SCPE_ARG;                                    /*   then fail with an invalid argument error */

else {                                                  /* otherwise */
    cpu_front_panel (TO_WORD (CN_CMD_RDR,               /*   set up the Read Record command */
                              ms_dib.device_number),    /*     from tape unit 0 */
                     Cold_Load);

    return SCPE_OK;                                     /* return to run the bootstrap */
    }
}


/* Attach a tape image file to a drive unit.

   The specified file is attached to the indicated drive unit.  This is the
   simulation equivalent of mounting a tape reel on the drive and pressing the
   LOAD and ONLINE buttons.  The transition from offline to online causes a Unit
   Attention interrupt.

   The controller library routine handles command validation and setting the
   appropriate drive unit status.  It will return an error code if the command
   fails.  Otherwise, it will return SCPE_INCOMP if the command must be
   completed with a controller call or SCPE_OK if the command is complete.  If
   the controller is idle, a call will be needed to poll the drives for
   attention; otherwise, the drives will be polled the next time the controller
   becomes idle.


   Implementation notes:

    1. If we are called during a RESTORE command to reattach a file previously
       attached when the simulation was SAVEd, the unit status will not be
       changed by the controller, so the unit will not request attention.
*/

static t_stat ms_attach (UNIT *uptr, 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, (CNTLR_IBUS) buffer_word);

command_set = TLIFN (result) & ~UNUSED_COMMANDS;        /* strip the commands we don't use as an efficiency */

while (command_set) {                                   /* process the set of returned interface commands */
    command = TLNEXTIFN (command_set);                  /* isolate the next command */

    switch (command) {                                  /* dispatch an interface command */

        case IFIN:                                      /* Interface In */
            if (flags & DTRDY)                          /* if the buffer is still full */
                flags |= OVRUN;                         /*   then this input overruns it */

            buffer_word = TLIBUS (result);              /* store the data word in the buffer */
            flags |= DTRDY;                             /*   and set the data ready flag */
            break;


        case IFOUT:                                     /* Interface Out */
            if ((flags & DTRDY) == NO_FLAGS)            /* if the buffer is empty */
                flags |= OVRUN;                         /*   then this output underruns it */

            flags &= ~DTRDY;                            /* clear the data ready flag */
            break;


        case IFGTC:                                         /* Interface Get Command */
            flags = flags & INTOK | EOD;                    /* clear the interface transfer flags and set EOD */

            command_class = (CNTLR_CLASS) TLIBUS (result);  /* save the command classification */
            break;


        case RQSRV:                                     /* Request Service */
            device_sr = SET;                            /* set the device service request flip-flop */
            break;


        case DVEND:                                     /* Device End */
            device_end = SET;                           /* set the device end flip-flop */
            break;


        case DATTN:                                     /* Drive Attention */
            unit_interrupt = SET;                       /* set the unit interrupt flip-flop */
            attention_unit = TLIBUS (result);           /*   and save the number of the requesting unit */

        /* fall through 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 */
}
