/* hp_tapelib.c: HP magnetic tape controller simulator library

   Copyright (c) 2013-2023, J. David Bryan
   Copyright (c) 2004-2011, Robert M. Supnik

   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
   AUTHORS 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 names of the authors 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 authors.

   26-Mar-23    JDB     Cleaned up unused macros
   26-Oct-21    JDB     Renamed TL_DEB_xxx trace flags to TRACE_xxx
   02-Apr-21    JDB     Changed u3-u6 synonyms to title case to avoid conflicts
   09-Dec-19    JDB     Replaced debugging macros with tracing macros
   27-Dec-18    JDB     Revised fall through comments to comply with gcc 7
   22-Apr-17    JDB     Corrected status_name array element count
   10-Oct-16    JDB     Moved "hp3000_defs.h" inclusion from "hp_tapelib.h"
   01-Jul-16    JDB     Changed tl_attach to reset the event delay times pointer
   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     TAPELIB_PROPERTIES.action is now a pointer-to-constant
   13-May-16    JDB     Modified for revised SCP API function parameter types
   03-May-16    JDB     Changed clear/attach/on/offline trace from INCO to CMD
   24-Mar-16    JDB     Changed the buffer element type from uint8 to TL_BUFFER
   21-Mar-16    JDB     Changed uint16 types to HP_WORD
   20-Nov-15    JDB     First release version
   24-Mar-13    JDB     Created tape controller common library from MS simulator

   References:
     - 13181B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual
         (13181-90901, November 1982)
     - 13183B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual
         (13183-90901, November 1983)
     - 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)
     - Standard ECMA-12 for Data Interchange on 9-Track Magnetic Tape
         (June 1970)


   This library provides the common functions required by the device controllers
   for the HP 7970B and 7970E tape drives.  It implements the command sets of
   the 13181 and 13183 controllers for the HP 1000, and the 30215 controller for
   the HP 3000.

   The library is an adaptation of the code originally written by Bob Supnik for
   the HP2100 MS simulator.  MS simulates an HP 13181 or 13183 controller
   interface for an HP 1000 computer.  To create the library, the functions of
   the controller were separated from the functions of the interface.  This
   allows the library to work with other CPUs, such as the 30215 interface for
   the HP 3000, that use substantially different communication protocols.  The
   library functions implement the controller command set for the drive units.
   The interface functions handle the transfer of commands and data to and from
   the CPU.

   In contrast with the 13037 MAC disc controller, HP had no standard tape
   controller that was shared between computer families.  Instead, there were
   several similar but incompatible controller implementations.  Command sets,
   command encoding, status encoding, status responses, and controller hardware
   design varied from implementation to implementation, with each tied tightly
   to the specific machine for which it was created.

   Therefore, to provide a "universal" tape controller that may be shared
   between the 1000 and 3000 simulators, this library implements an abstract
   controller modeled on the interface design of the 13037.  A tape interface
   interacts with the controller over 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.

   A device interface simulator interacts with the tape controller simulator via
   the "tl_controller" routine, which simulates the signal interconnections
   between the interface and controller.  Utility routines are also provided to
   attach and detach tape image files from drive units, set units offline or
   online, set drive model and protection status, and select the controller
   timing mode (real or fast).  The controller routine is called by the
   interface whenever the state of the flag bus changes, either due to a request
   from the CPU or a service event, and the controller responds by potentially
   changing the data and function buses.  The interface merely responds to those
   changes without requiring any other knowledge of the internal state of the
   controller.

   The interface initiates controller action by placing data on the data bus and
   then changing the state of the flag bus, and the controller responds by
   asserting one or more functions on the function bus.  For example, the
   interface starts a "Write Record" tape command by placing the command on the
   data bus and asserting CMRDY (Command Ready) and CMXEQ (Command Execute) on
   the flag bus.  The controller responds by decoding the command, initiating
   processing, and then placing the IFGTC (Interface Get Command) and RQSRV
   (Request Service) functions on the function bus.  The interface responds by
   clearing the transfer flags in preparation for the data transfer and
   requesting service from the DMA channel.  Once the controller determines that
   the tape is moving and is positioned correctly for the write, an event
   service entry calls the controller with the DTRDY (Data Ready) flag and the
   first word of data to write.  The controller responds by saving the word to
   the record buffer and asserting the IFOUT (Interface Out) and RQSRV
   functions.  The interface responds by clearing the DTRDY flag and requesting
   the next word from the DMA channel.

   Hardware wait loops in the abstract controller that wait for tape motion are
   simulated by timed events.  The tl_controller routine activates the selected
   unit and returns to the caller until the expected external event occurs.  In
   the Write Record example, the controller will wait for the simulated tape to
   start and position the record for writing and then will wait for each data
   word to be supplied by the interface.

   The controller supports realistic and optimized (fast) timing modes.
   Realistic timing attempts to model the actual controller and tape unit motion
   delays inherent in tape operations.  For example, in REALTIME mode, reads of
   longer records take more time than reads of shorter records, rewinds take
   times proportional to the distance from the load point, and an erase gap is
   written before the first record after the load point.  In FASTTIME mode, all
   timings are reduced to be "just long enough" to satisfy software
   requirements, and movement across erase gaps takes no additional time.

   Typically, a total operation time consists of a controller overhead delay, a
   tape transport start time, an optional gap traverse time, a data record
   traverse time, and a transport stop time.  These times are modeled by the
   service activation times for the various command phases.

   A controller instance is represented by a CNTLR_VARS structure, which
   maintains the controller's internal state.  Each 13181/3 and 30215 interface
   has a single controller instance that controls up to four drive units,
   whereas an HP-IB interface will have one controller instance per drive unit.
   The minor differences in controller action between the two are handled
   internally.

   The 1000/3000 interface simulators must declare one unit for each tape drive
   to be controlled by the library, plus one additional unit for the controller
   itself.  For an HP-IB controller, only one unit is required.

   The controller maintains five values in each drive's unit structure:

     u3 (Prop)   -- the current drive properties
     u4 (Status) -- the current drive status
     u5 (Opcode) -- the current drive operation in progress
     u6 (Phase)  -- the current drive operation phase
     pos         -- the current byte offset into the tape image file

   These and other definitions are in the file hp_tapelib.h, which must be
   included in the interface simulator.

   The drive status field contains only a subset of the status maintained by
   drives in hardware.  Specifically, the Write Protected, Write Status, and
   1600-bpi Density bits are stored in the status field.  The other bits (End of
   Tape, Unit Busy, Unit Ready, Load Point, Rewinding, and Unit Offline) are set
   dynamically whenever status is requested.

   Per-drive opcode and phase values allow rewinds to be overlapped with
   operations on other drives.  For example, a Rewind issued to unit 0 may be
   followed by a Read Record issued to unit 1.  When the rewind completes on
   unit 0, its opcode and phase values will let the controller set the
   appropriate completion status without disturbing the values currently in use
   by unit 1.

   The simulation defines these command phases:

     Idle     -- waiting for the next command to be issued
     Wait     -- waiting for the channel data transfer
     Error    -- waiting to interrupt for a command abort
     Start    -- waiting for the drive to come up to speed after starting
     Traverse -- waiting for the drive to traverse an erase gap
     Data     -- waiting for the drive to traverse a data record
     Stop     -- waiting for the drive to slow to a stop

   A value represents the current state of the unit.  If a unit is active, the
   phase will end when the unit is serviced.

   During reads and forward/backward spacing commands, each data record may be
   optionally preceded in the direction of motion by an erase gap.  If no gap is
   present, the associated traversal phase is skipped, and service proceeds
   directly from the start phase to the data phase.

   In-progress operations may be cancelled by issuing a controller clear.  In
   hardware, this causes tape motion to cease immediately, except for rewind
   operations, which continue to the load point.  Under simulation, a write-in-
   progress is terminated with the data received so far written as a truncated
   record.  Additionally, for a NRZI drive, the record is marked as "bad" due to
   the lack of a CRC character.  A read- or spacing-in-progress is abandoned if
   the clear occurs during the controller overhead or transport start period or
   is terminated with partial movement if it occurs during the gap traverse
   period.  A read- or spacing-in-progress is completed normally if the clear
   occurs during the data record traverse, as the SIMH magnetic tape format has
   no provision for recovery when positioned within a record.

   In addition to the controller structure(s), an interface declares a data
   buffer to be used for record transfers.  The buffer is an array containing
   TL_BUFSIZE 8-bit elements; the address of the buffer is stored in the
   controller state structure.  The controller maintains the current index into
   the buffer, as well as the length of valid data stored there.  Only one
   buffer is needed per interface, regardless of the number of controllers or
   units handled, as a single interface cannot perform data transfers on one
   drive concurrently with a command directed to another drive.

   An interface is also responsible for declaring a structure of type
   DELAY_PROPS that contains the timing values for the controller when in
   FASTTIME mode.  The values are event counts for these actions:

     - the time from rewind initiation to controller idle
     - the travel time per inch during rewinding
     - the time from BOT detection to load point search completion
     - the time starting from the BOT marker to the data block
     - the time starting from the IR gap to the data block
     - the travel time from one data byte to the next
     - the controller execution overhead time from command to first motion

   These values are typically exposed via the interface's register set and so
   may be altered by the user.  A macro, DELAY_INIT, is provided to initialize
   the structure.

   In addition, certain actions are enabled only when the controller is in
   realistic or optimized timing mode.  Specifically, when in REALTIME mode, the
   controller will:

     - stop at a proportional position if it is cleared during a gap traversal
     - calculate and add the CRCC and LRCC to the record buffer for 13181 reads

   In FASTTIME mode, the controller will:

     - omit writing the gap normally required at the beginning of the tape
     - omit writing the gap for the "Write Gap and File Mark" command
     - omit the gap traversal time for reads and spacings

   The controller library provides a macro, TL_MODS, that initializes MTAB
   entries.  A macro, CNTLR_INIT, is provided to initialize the controller
   structure from the following parameters:

     - the type of the controller
     - the simulation DEVICE structure on which the controller operates
     - the data buffer array
     - the structure containing the FASTTIME values

   A macro, TL_REGS, is also provided that initializes a set of REG structures
   to provide a user interface to the controller structure.

   In hardware, the 3000 controller detects a tape unit offline-to-online
   transition and responds by requesting a CPU interrupt.  In simulation, SET
   <dev> OFFLINE and SET <dev> ONLINE commands represent these actions.  The
   controller must be notified by calling the tl_onoffline routine in response
   to changes in a tape drive's ONLINE switch.

   Finally, the controller library provides extensive tracing of its internal
   operations via debug logging.  Six debug flags are declared for use by the
   interface simulator.  When enabled, these report controller actions at
   various levels of detail.

   The simulator tape support library (sim_tape) routines are used to implement
   the low-level tape image file handling.  Fatal errors from these routines are
   reported to the simulation console and cause a simulation stop.  If
   simulation is resumed, the controller command in progress is aborted with an
   uncorrectable data error indication.  Typically, the program executing under
   simulation reacts to this by retrying the operation.


   Implementation notes:

    1. The library does not simulate the optional OFF-0-1-2-3 Unit Select
       buttons of the 7970-series drives.  Instead, each drive adopts the unit
       address corresponding to the simulator UNIT number associated with the
       drive.  For example, unit MS2 responds to unit address 2.

       A set of bits to represent the Unit Select switch setting has been
       allocated in the Prop field of the UNIT structure, but it is currently
       unused.  This feature could be added by reading the unit number from
       there, rather than deriving it from the position in the device's UNIT
       array, and adding SET <unit> UNIT=OFF|0|1|2|3 and SHOW <unit> UNIT
       commands and associated validation and print routines.  However, the
       operational benefit of this, compared to the current approach, is nil, as
       the only utility derived would be a somewhat cleaner set of test commands
       for the magnetic tape diagnostic.

       The current unit select diagnostic approach moves the scratch tape
       attachment from drive to drive in lieu of changing the Unit Select switch
       of a single drive.  This passes both the 1000 and 3000 diagnostics,
       although reattaching does reset the tape position to the load point.
       Fortunately, continuance of tape position isn't tested.

    2. The library does not properly simulate the HP 30215 tape controller's
       Write_Record_without_Parity (WRZ) and Read_Record_with_CRCC (RDC)
       commands, which are used only with NRZI 7970B drives.  In hardware, WRZ
       forces the parity of data bytes written to the tape to zero, allowing the
       creation of records containing dropouts, bad parity, and spacing gaps,
       and RDC returns two extra bytes containing the CRCC and its parity bit
       read from the tape.

       Because the tape image format contains only the data content of records,
       under simulation WRZ writes all received bytes to a data record, except
       that a parity error is indicated if a byte has even parity, and zero
       bytes (dropouts) are omitted.  RDC is simulated by returning the
       calculated CRC of the record.

       It would be possible to simulate these functions correctly if the tape
       image format is extended to allow private data.  For example, the WRZ
       command would write a private data record containing all bytes, and RDC
       would read the record and interpret the data accordingly.  Reading a
       standard record with RDC would return a calculated CRCC.  However, the
       operational benefit of this is nil, as only the diagnostic uses these
       commands, and then only to verify that the CRCC, LRCC, and dropout
       detections are working properly.  These functions are irrelevant in a
       simulation environment.

    3. In hardware, the transport stop time will be eliminated if another
       command of the proper type is received in time.  If a new command is in
       the same command group (read forward, read reverse, or write forward),
       and it follows within 103 microseconds of the previous command, then tape
       motion will continue.  Otherwise, it will stop and restart.

       In simulation, all tape commands complete the stop phase before the next
       command is started.

    4. The SAVE command does not save the "wait" and "pos" fields of the UNIT
       structure automatically.  To ensure that they are saved, they are
       referenced by read-only registers.
*/



#include "hp3000_defs.h"                        /* this must reflect the machine used */
#include "hp_tapelib.h"



/* Program constants */

#define NO_EVENT            -1                  /* do not schedule an event */

#define NO_ACTION           (CNTLR_IFN_IBUS) (NO_FUNCTIONS | NO_DATA)
#define SCP_STATUS(w)       (CNTLR_IFN_IBUS) (SCPE | (w))


/* Controller unit pointer */

#define CNTLR_UPTR          (cvptr->device->units + TL_CNTLR_UNIT)


/* Unit flags accessor */

#define GET_MODEL(f)        (DRIVE_TYPE) ((f) >> UNIT_MODEL_SHIFT & UNIT_MODEL_MASK)


/* Per-unit property flags and accessors.

   The property value (Prop) contains several fields that describe the drive and
   its currently mounted tape reel:

                 15| 14  13  12| 11  10  9 | 8   7   6 | 5   4   3 | 2   1   0
           +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     [...]   - |   model   | unit num  | reel  |     property array index      |
           +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The property array index is the element number of the drive_props array that
   describes the drive associated with the unit number.  The reel size value is
   one of the REEL_SIZE enumeration constants that reflects the specified length
   of the tape reel mounted to the unit.

   The unit number and model are not currently used but are reserved for future
   expansion.


   Implementation notes:

    1. Unit attention could be kept in the property value, rather than as a flag
       in the bitmap controller field "unit_attention".  However, keeping the
       attention flags together means that they can be checked very quickly,
       rather than having to isolate and check a bit in each UNIT structure.
*/

#define PROP_INDEX_WIDTH    8                   /* tape drive property array index */
#define PROP_REEL_WIDTH     2                   /* mounted tape reel size */

#define PROP_INDEX_SHIFT    0                   /* bits 7-0 */
#define PROP_REEL_SHIFT     8                   /* bits 9-8 */

#define PROP_INDEX_MASK     ((1u << PROP_INDEX_WIDTH) - 1 << PROP_INDEX_SHIFT)
#define PROP_REEL_MASK      ((1u << PROP_REEL_WIDTH)  - 1 << PROP_REEL_SHIFT)

#define PROP_INDEX(u)       (((u)->Prop & PROP_INDEX_MASK) >> PROP_INDEX_SHIFT)
#define PROP_REEL(u)        (((u)->Prop & PROP_REEL_MASK)  >> PROP_REEL_SHIFT)

#define PROP_REEL_UNLIM     (Reel_Unlimited << PROP_REEL_SHIFT)
#define PROP_REEL_600       (Reel_600_Foot  << PROP_REEL_SHIFT)
#define PROP_REEL_1200      (Reel_1200_Foot << PROP_REEL_SHIFT)
#define PROP_REEL_2400      (Reel_2400_Foot << PROP_REEL_SHIFT)


/* Magnetic tape reel sizes */

typedef enum {
    Reel_Unlimited = 0,
    Reel_600_Foot,
    Reel_1200_Foot,
    Reel_2400_Foot
    } REEL_SIZE;


/* Unit command phases.

   Command phases indicate the state of the unit.  If an event is currently
   scheduled, the phase indicates the unit state as it will be on entry to the
   event service routine.  For example, upon event expiration, a scheduled unit
   in the start phase will have started, will be up to speed, and will have
   passed through the interrecord gap and be ready to transfer data.  The start
   phase activation delay represents the time required for these actions to
   occur.
*/

typedef enum {
    Idle_Phase = 0,                             /* waiting for the next command to be issued */
    Wait_Phase,                                 /* waiting for the channel data transfer */
    Start_Phase,                                /* waiting for the drive to come up to speed after starting */
    Traverse_Phase,                             /* waiting for the drive to traverse an erase gap */
    Data_Phase,                                 /* waiting for the drive to traverse a data record */
    Stop_Phase,                                 /* waiting for the drive to slow to a stop */
    Error_Phase                                 /* waiting to interrupt for a command abort */
    } CNTLR_PHASE;


/* Drive properties table.

   The drive properties table is used to validate drive type and density changes
   within the subset of drives supported by a given controller.  It also
   contains the erase gap size, which is controller-specific.
*/

typedef struct {
    CNTLR_TYPE  controller;                     /* the controller model */
    DRIVE_TYPE  drive;                          /* a supported tape drive model */
    uint32      density;                        /* a supported tape drive density code */
    uint32      bpi;                            /* the recording density in bits per inch */
    uint32      gap_size;                       /* the erase gap size in tenths of an inch */
    } DRIVE_PROPS;

static const DRIVE_PROPS drive_props [] = {
/*                                               gap  */
/*     cntrlr      drive    density code   bpi   size */
/*    ---------  ---------  ------------  -----  ---- */
    { HP_13181,  HP_7970B,  MT_DENS_800,   800,   48  },
    { HP_13183,  HP_7970E,  MT_DENS_1600, 1600,   30  },
    { HP_30215,  HP_7970B,  MT_DENS_800,   800,   38  },
    { HP_30215,  HP_7970E,  MT_DENS_1600, 1600,   38  },
    { HP_IB,     HP_7974,   MT_DENS_800,   800,    0  },
    { HP_IB,     HP_7974,   MT_DENS_1600, 1600,    0  }
    };

#define PROPS_COUNT         (sizeof drive_props / sizeof drive_props [0])


/* Delay properties table.

   To support the realistic timing mode, the delay properties table contains
   timing specifications for the supported tape drives.  Table entries
   correspond one-for-one with drive properties table entries.

   The times represent the delays for mechanical and electronic operations.
   Delay values are stored in event tick counts; macros are used to convert from
   times to ticks.

   The rewind start time is the delay from rewind command initiation until the
   controller is released.  The rewind stop time is the time to stop the reverse
   motion plus the time to search forward to the load point.

   The controller overhead values are estimates; they do not appear to be
   documented for any of the controllers.
*/

static const DELAY_PROPS real_times [] = {
/*     rewind    rewind     rewind       BOT         IR      per-byte     cntlr   */
/*     start    time/inch    stop       time        time     data xfer   overhead */
/*    --------  ---------  --------  -----------  ---------  ----------  -------- */
    { uS (556), mS (6.25), S (2.25), mS (102.22), mS (7.88), uS (27.78), uS (  5) },   /* HP_13181,  HP_7970B,  MT_DENS_800, */
    { uS (556), mS (6.25), S (2.25), mS (160.00), mS (8.67), uS (13.89), uS (  5) },   /* HP_13183,  HP_7970E,  MT_DENS_1600 */
    { mS (2.2), mS (6.25), S (2.25), mS (  9.73), mS (9.73), uS (27.78), uS ( 20) },   /* HP_30215,  HP_7970B,  MT_DENS_800  */
    { mS (2.2), mS (6.25), S (2.25), mS ( 12.24), mS (9.73), uS (13.89), uS ( 20) },   /* HP_30215,  HP_7970E,  MT_DENS_1600 */
    { mS (0.0), mS (0.00), S (0.00), mS ( 00.00), mS (0.00), uS (00.00), uS ( 00) },   /* HP_IB,     HP_7974,   MT_DENS_800  */
    { mS (0.0), mS (0.00), S (0.00), mS ( 00.00), mS (0.00), uS (00.00), uS ( 00) }    /* HP_IB,     HP_7974,   MT_DENS_1600 */
    };


/* Command properties table.

   The validity of each command for the controller type is checked against the
   command properties table when it is prepared for execution.  The table also
   includes the class of the command, whether the drive must be ready before
   execution is permitted, and whether the command requires a data transfer
   phase.
 */

typedef struct {
    CNTLR_CLASS  class;                         /* command class */
    t_bool       valid [CNTLR_COUNT];           /* command validity, indexed by CNTLR_TYPE */
    t_bool       ready;                         /* command requires a ready drive */
    t_bool       transfer;                      /* command requires a transfer of data */
    } COMMAND_PROPERTIES;

#define T               TRUE
#define F               FALSE

static const COMMAND_PROPERTIES cmd_props [] = {        /* command properties, in CNTLR_OPCODE order */
/*       opcode            valid for      drive  data  */
/*        class         181 183 215 HPIB  ready  xfer  */
/*    -------------     --- --- --- ----  -----  ----- */
    { Class_Control,   { T,  T,  T,  T },   F,     F   },   /* 00 = Select_Unit_0 */
    { Class_Control,   { T,  T,  T,  T },   F,     F   },   /* 01 = Select_Unit_1 */
    { Class_Control,   { T,  T,  T,  T },   F,     F   },   /* 02 = Select_Unit_2 */
    { Class_Control,   { T,  T,  T,  T },   F,     F   },   /* 03 = Select_Unit_3 */
    { Class_Control,   { T,  T,  F,  F },   F,     F   },   /* 04 = Clear_Controller */
    { Class_Read,      { T,  T,  T,  T },   T,     T   },   /* 05 = Read_Record */
    { Class_Read,      { F,  F,  T,  F },   T,     T   },   /* 06 = Read_Record_with_CRCC */
    { Class_Read,      { T,  T,  F,  F },   T,     T   },   /* 07 = Read_Record_Backward */
    { Class_Read,      { T,  T,  F,  F },   T,     T   },   /* 08 = Read_File_Forward */
    { Class_Write,     { T,  T,  T,  T },   T,     T   },   /* 09 = Write_Record */
    { Class_Write,     { F,  F,  T,  F },   T,     T   },   /* 10 = Write_Record_without_Parity */
    { Class_Write,     { T,  T,  T,  T },   T,     F   },   /* 11 = Write_File_Mark */
    { Class_Write,     { T,  T,  T,  T },   T,     F   },   /* 12 = Write_Gap */
    { Class_Write,     { T,  T,  F,  F },   T,     F   },   /* 13 = Write_Gap_and_File_Mark */
    { Class_Control,   { T,  T,  T,  T },   T,     F   },   /* 14 = Forward_Space_Record */
    { Class_Control,   { T,  T,  T,  T },   T,     F   },   /* 15 = Forward_Space_File */
    { Class_Control,   { T,  T,  T,  T },   T,     F   },   /* 16 = Backspace_Record */
    { Class_Control,   { T,  T,  T,  T },   T,     F   },   /* 17 = Backspace_File */
    { Class_Rewind,    { T,  T,  T,  T },   T,     F   },   /* 18 = Rewind */
    { Class_Rewind,    { T,  T,  T,  T },   T,     F   },   /* 19 = Rewind_Offline */
    { Class_Invalid,   { F,  F,  F,  F },   F,     F   }    /* 20 = Invalid_Opcode */
    };


/* Status mapping table.

   The various HP tape controllers report tape status conditions in different
   locations in the status word.  To accommodate this, a lookup table is used to
   map specific conditions to the controller-specific bit(s).  An enumeration is
   used to provide symbolic table index constants.

   A given controller may not report all status conditions.  For conditions not
   reported, the table entries are zero, so that ORing the values into the
   status word has no effect.


   Implementation notes:

    1. Unit Selected status for units 0-3 must have values 0-3, so that the
       selected unit field of the controller state structure may be used
       directly as an index into the table.

    2. Single Track Error cannot occur under simulation.  In hardware, this
       error occurs on phase-encoded drives when a data error is detected and
       corrected.

    3. Multiple Track Error status for PE drives is the same as Parity Error
       status for NRZI drives.  Both are reported as Data_Error status.

    4. The status bits for the 30215 controller's Data_Error, Timing_Error,
       Tape_Runaway, and Command_Rejected errors are complemented from their
       actual values.  This allows an all-zeros value to represent No Error,
       which is consistent with the values used by the other controllers.  The
       30215 simulator complements these encoded error bits before reporting the
       controller status.

    5. Of the 30215 controller's encoded error status values, only Data_Error
       (value 4) and Timing_Error (value 6) can occur in the same transfer.  The
       controller hardware uses a priority encoder to give Timing_Error reporting
       priority.  Because of the values employed, we can simply OR the two
       errors as they occur, and Timing_Error will result.
*/

typedef enum {
    Unit_0_Selected = 0,
    Unit_1_Selected = 1,
    Unit_2_Selected = 2,
    Unit_3_Selected = 3,
    Command_Rejected,
    Data_Error,
    Density_1600,
    End_of_File,
    End_of_Tape,
    Interface_Busy,
    Load_Point,
    Odd_Length,
    Protected,
    Rewinding,
    Tape_Runaway,
    Timing_Error,
    Unit_Busy,
    Unit_Offline,
    Unit_Ready,
    Write_Status
    } STATUS_CONDITION;

static const uint32 status_bits [] [CNTLR_COUNT] = {    /* indexed by STATUS_CONDITION and CNTLR_TYPE */

/*    HP_13181   HP_13183   HP_30215   */
/*    --------   --------   --------   */
    { 0000000,   0000000,   0000000    },       /* Unit_0_Selected  */
    { 0000000,   0020000,   0004000    },       /* Unit_1_Selected  */
    { 0000000,   0040000,   0010000    },       /* Unit_2_Selected  */
    { 0000000,   0060000,   0014000    },       /* Unit_3_Selected  */
    { 0000010,   0000010,   0000012    },       /* Command_Rejected */
    { 0000002,   0000002,   0000004    },       /* Data_Error       */
    { 0000000,   0100000,   0000100    },       /* Density_1600     */
    { 0000200,   0000200,   0000020    },       /* End_of_File      */
    { 0000040,   0000040,   0002000    },       /* End_of_Tape      */
    { 0000400,   0000400,   0000000    },       /* Interface_Busy   */
    { 0000100,   0000100,   0000200    },       /* Load_Point       */
    { 0004000,   0004000,   0040000    },       /* Odd_Length       */
    { 0000004,   0000004,   0001000    },       /* Protected        */
    { 0002000,   0002000,   0000000    },       /* Rewinding        */
    { 0000000,   0000000,   0000010    },       /* Tape_Runaway     */
    { 0000020,   0000020,   0000006    },       /* Timing_Error     */
    { 0001000,   0001000,   0000000    },       /* Unit_Busy        */
    { 0000001,   0000001,   0000000    },       /* Unit_Offline     */
    { 0000000,   0000000,   0000400    },       /* Unit_Ready       */
    { 0000000,   0000000,   0000040    }        /* Write_Status     */
    };

/* Controller status flags */

#define CST_UNITSEL     (status_bits [cvptr->unit_selected] [cvptr->type])
#define CST_REJECT      (status_bits [Command_Rejected]     [cvptr->type])
#define CST_RUNAWAY     (status_bits [Tape_Runaway]         [cvptr->type])
#define CST_DATAERR     (status_bits [Data_Error]           [cvptr->type])
#define CST_EOF         (status_bits [End_of_File]          [cvptr->type])
#define CST_TIMERR      (status_bits [Timing_Error]         [cvptr->type])
#define CST_IFBUSY      (status_bits [Interface_Busy]       [cvptr->type])
#define CST_ODDLEN      (status_bits [Odd_Length]           [cvptr->type])

/* Unit status flags */

#define UST_WRPROT      (status_bits [Protected]            [cvptr->type])
#define UST_WRSTAT      (status_bits [Write_Status]         [cvptr->type])
#define UST_DEN1600     (status_bits [Density_1600]         [cvptr->type])

/* Dynamic status flags */

#define DST_EOT         (status_bits [End_of_Tape]          [cvptr->type])
#define DST_UNITBUSY    (status_bits [Unit_Busy]            [cvptr->type])
#define DST_UNITRDY     (status_bits [Unit_Ready]           [cvptr->type])
#define DST_LOADPT      (status_bits [Load_Point]           [cvptr->type])
#define DST_REWIND      (status_bits [Rewinding]            [cvptr->type])
#define DST_UNITLOCL    (status_bits [Unit_Offline]         [cvptr->type])


/* Controller operation names */

static const BITSET_NAME flag_names [] = {      /* controller flag names, in CNTLR_FLAG order */
    "CMRDY",                                    /*   000001 */
    "CMXEQ",                                    /*   000002 */
    "DTRDY",                                    /*   000004 */
    "EOD",                                      /*   000010 */
    "INTOK",                                    /*   000020 */
    "OVRUN",                                    /*   000040 */
    "XFRNG"                                     /*   000100 */
    };

static const BITSET_FORMAT flag_format =        /* names, offset, direction, alternates, bar */
    { FMT_INIT (flag_names, 0, lsb_first, no_alt, no_bar) };


static const BITSET_NAME function_names [] = {  /* interface function names, in CNTLR_IFN order */
    "IFIN",                                     /*   000000200000 */
    "IFOUT",                                    /*   000000400000 */
    "IFGTC",                                    /*   000001000000 */
    "SCPE",                                     /*   000002000000 */
    "RQSRV",                                    /*   000004000000 */
    "DVEND",                                    /*   000010000000 */
    "STCFL",                                    /*   000020000000 */
    "STDFL",                                    /*   000040000000 */
    "STINT",                                    /*   000100000000 */
    "DATTN"                                     /*   000200000000 */
    };

static const BITSET_FORMAT function_format =    /* names, offset, direction, alternates, bar */
    { FMT_INIT (function_names, 16, lsb_first, no_alt, no_bar) };


static const char *opcode_names [] = {           /* command opcode names, in CNTLR_OPCODE order */
    "Select Unit 0",
    "Select Unit 1",
    "Select Unit 2",
    "Select Unit 3",
    "Clear Controller",
    "Read Record",
    "Read Record with CRCC",
    "Read Record Backward",
    "Read File Forward",
    "Write Record",
    "Write Record without Parity",
    "Write File Mark",
    "Write Gap",
    "Write Gap and File Mark",
    "Forward Space Record",
    "Forward Space File",
    "Backspace Record",
    "Backspace File",
    "Rewind",
    "Rewind Offline",
    "Invalid Command"
    };


static const char *phase_names [] = {            /* unit state names, in CNTLR_PHASE order */
    "idle",
    "wait",
    "start",
    "traverse",
    "data",
    "stop",
    "error"
    };


static const char *state_names [] = {           /* controller state names, in CNTLR_STATE order */
    "idle",
    "busy",
    "end",
    "error"
    };


static const char *const unit_names [] = {      /* unit names, in unit number order */
    "Unit 0",
    "Unit 1",
    "Unit 2",
    "Unit 3",
    "Controller unit"
    };


/* Simulator tape support library call properties table.

   The support library (sim_tape) call properties table is used to determine
   whether the call may have read or written an erase gap in the tape image
   file and whether the call returned a valid data length value.  These are used
   to interpret the tape positional change as a result of the call to separate
   the gap length from the data record length.  The table also contains strings
   describing the call actions that are used when tracing library calls.
*/

typedef enum {
    lib_space_fwd,                              /* call to sim_tape_sprecf */
    lib_space_rev,                              /* call to sim_tape_sprecr */
    lib_read_fwd,                               /* call to sim_tape_rdrecf */
    lib_read_rev,                               /* call to sim_tape_rdrecr */
    lib_write,                                  /* call to sim_tape_wrrecf */
    lib_write_gap,                              /* call to sim_tape_wrgap  */
    lib_write_tmk,                              /* call to sim_tape_wrtmk  */
    lib_rewind                                  /* call to sim_tape_rewind */
    } TAPELIB_CALL;


typedef struct {
    t_bool      gap_is_valid;                   /* call may involve an erase gap */
    t_bool      data_is_valid;                  /* call may involve a data record */
    const char  *action;                        /* string describing the call action */
    } TAPELIB_PROPERTIES;


static const TAPELIB_PROPERTIES lib_props [] = {    /* indexed by TAPELIB_CALL */
/*     gap   data                     */
/*    valid  valid        action      */
/*    -----  -----  ----------------- */
    {   T,     T,   "forward space"   },        /* lib_space_fwd */
    {   T,     T,   "backspace"       },        /* lib_space_rev */
    {   T,     T,   "read"            },        /* lib_read_fwd  */
    {   T,     T,   "reverse read"    },        /* lib_read_rev  */
    {   F,     T,   "write"           },        /* lib_write     */
    {   T,     F,   "write gap"       },        /* lib_write_gap */
    {   F,     F,   "write tape mark" },        /* lib_write_tmk */
    {   T,     F,   "rewind"          },        /* lib_rewind    */
    };


/* Simulator tape support library status values */

static const char *status_name [] = {           /* indexed by MTSE value */
    "succeeded",                                /*   MTSE_OK      */
    "terminated with tape mark seen",           /*   MTSE_TMK     */
    "failed with unit not attached",            /*   MTSE_UNATT   */
    "failed with I/O error",                    /*   MTSE_IOERR   */
    "failed with invalid record length",        /*   MTSE_INVRL   */
    "failed with invalid tape format",          /*   MTSE_FMT     */
    "terminated with beginning of tape seen",   /*   MTSE_BOT     */
    "terminated with end of medium seen",       /*   MTSE_EOM     */
    "succeeded with data error",                /*   MTSE_RECE    */
    "failed with no write ring",                /*   MTSE_WRP     */
    "failed with logical end of tape",          /*   MTSE_LEOT    */
    "failed with tape runaway"                  /*   MTSE_RUNAWAY */
    };


/* Tape library local controller routines */

static CNTLR_IFN_IBUS start_command    (CVPTR cvptr, CNTLR_FLAG_SET flags, CNTLR_OPCODE opcode);
static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data);
static CNTLR_IFN_IBUS end_command      (CVPTR cvptr, UNIT *uptr);
static CNTLR_IFN_IBUS poll_drives      (CVPTR cvptr);
static CNTLR_IFN_IBUS call_tapelib     (CVPTR cvptr, UNIT *uptr, TAPELIB_CALL lib_call, t_mtrlnt parameter);
static CNTLR_IFN_IBUS abort_command    (CVPTR cvptr, UNIT *uptr, t_stat status);
static void           reject_command   (CVPTR cvptr, UNIT *uptr);
static void           add_crcc_lrcc    (CVPTR cvptr, CNTLR_OPCODE opcode);

/* Tape library local utility routines */

static void   activate_unit  (CVPTR cvptr, UNIT *uptr);
static t_stat validate_drive (CVPTR cvptr, UNIT *uptr, DRIVE_TYPE new_drive, uint32 new_bpi);



/* Tape library global controller routines */


/* Tape controller interface.

   This routine simulates the hardware interconnection between the abstract tape
   controller and the CPU interface.  This routine is called whenever the flag
   state changes.  This would be when a new command is to be started, when a
   channel begins a read or write operation, when a channel program terminates,
   or when a channel program error occurs.  It must also be called when the unit
   service routine is entered.  The caller passes in the set of interface flags
   and the contents of the data buffer.  The routine returns a set of functions;
   this is accompanied by a data value in these cases:

     Function  Data Value
     --------  --------------------------------
      IFGTC    command classification code
      SCPE     SCPE status code
      IFIN     data record word
      IFOUT    data record CRCC and LRCC word
      DATTN    unit number requesting attention

   On entry, the flags and controller state are examined to determine if a new
   controller command should be initiated or the current command should be
   continued.  If this routine is being called as a result of an event service
   or by channel initialization, "uptr" will point at the unit being serviced.
   Otherwise, "uptr" will be NULL (for example, when starting a new controller
   command).

   On entry, if a 3000 channel error has occurred, then return with no event
   scheduled; the CPU interface will recover by clearing the controller.
   Otherwise, if this is an event service entry or channel initialization, then
   process the next step of the command.  Otherwise, if the CMRDY or CMXEQ flag
   is asserted, then validate or start a new command.  Finally, if the
   controller is idle, then poll the drives for attention.

   In all cases, return a combined function set and outbound data word to the
   caller.


   Implementation notes:

    1. To accommodate the HP 1000's 1318x controllers that separate command
       validation from command execution, the "start_command" routine is entered
       if either the CMRDY or CMXEQ flag is set, but the command will be started
       only if the latter is set.
*/

CNTLR_IFN_IBUS tl_controller (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data)
{
CNTLR_IFN_IBUS outbound;

tpprintf (cvptr->device, TRACE_IOBUS, "Controller (%s) received data %06o with flags %s\n",
          state_names [cvptr->state], data, fmt_bitset (flags, flag_format));

if (flags & XFRNG)                                      /* if a channel error has occurred */
    outbound = NO_ACTION;                               /*   then the controller hangs until it's cleared */

else if (uptr)                                              /* otherwise if an event is being serviced */
    outbound = continue_command (cvptr, uptr, flags, data); /*   then continue with command processing */

else if (flags & (CMRDY | CMXEQ))                                   /* otherwise if a new command is ready */
    outbound = start_command (cvptr, flags, (CNTLR_OPCODE) data);   /*   then validate or execute it */

else                                                    /* otherwise there's nothing to do */
    outbound = NO_ACTION;                               /*   except possibly poll for attention */

if (cvptr->state == Idle_State                          /* if the controller is idle */
  && cvptr->type == HP_30215                            /*   and it's the 3000 controller */
  && flags & INTOK)                                     /*     and interrupts are allowed */
    outbound = poll_drives (cvptr);                     /*       then poll the drives for attention */

tpprintf (cvptr->device, TRACE_IOBUS, "Controller (%s) returned data %06o with functions %s\n",
          state_names [cvptr->state], TLIBUS (outbound),
          fmt_bitset (TLIFN (outbound), function_format));

return outbound;
}


/* Set a unit online or offline.

   If the unit indicated by the "uptr" parameter is currently attached, it will
   be set online if the "online" parameter is true or offline if the parameter
   is false.  If the drive is not rewinding, and it was offline and is being set
   online, then the unit attention flag is set, and, if the controller is idle,
   the routine returns SCPE_INCOMP to indicate that the caller must then call
   the controller to poll for unit attention to complete the command.
   Otherwise, it returns SCPE_OK, and the drives will be polled automatically
   when the current command completes and the controller is idled.

   If the drive is rewinding when it is set online, attention is deferred until
   the rewind completes (because the unit is not ready until then).
*/

t_stat tl_onoffline (CVPTR cvptr, UNIT *uptr, t_bool online)
{
const int32 unit = (int32) (uptr - cvptr->device->units);   /* the unit number */
t_stat status = SCPE_OK;

if (uptr->flags & UNIT_ATT) {                           /* if the unit is attached */
    if (!(uptr->flags & UNIT_REWINDING)                 /*   then if the tape is not rewinding */
      && uptr->flags & UNIT_OFFLINE                     /*     and it is currently offline */
      && online) {                                      /*       and is going online */
        cvptr->unit_attention |= 1 << unit;             /*         then the unit needs attention */

        if (cvptr->state == Idle_State)                 /* if the controller is idle */
            status = SCPE_INCOMP;                       /*   then it must be called to poll the drives */
        }

    tpprintf (cvptr->device, TRACE_CMD, "Unit %d set %s\n",
              unit, (online ? "online" : "offline"));
    }

else                                                    /* otherwise the unit is detached and offline */
    status = SCPE_UNATT;                                /*   so it cannot be set online */

return status;
}


/* Return the current controller and unit status.

   This routine returns the combined formatted controller and unit status for
   the currently selected drive unit.

   In hardware, the selected tape unit presents its status information
   continuously to the controller.  The 7970B and 7970E drives provide these
   common status signals:

     - SL  = status online (selected * online)
     - SLP = status load point (selected * online * at-load-point)
     - SRW = status rewind (selected * online * rewinding)
     - SET = status end-of-tape (selected * online * EOT-during-motion-forward)
     - SR  = status ready (selected * online * ~loading * ~rewinding)
     - SFP = status file protect (selected * online * ~write-ring)
     - SW  = status write (selected * online * cmd-set-write * motion-forward)

   Unique status signals for the 7970B are:

     - S7  = status 7-track drive

   For the 7970E, they are:

     - SD2  = status density 200 bpi
     - SD5  = status density 556 bpi
     - SD8  = status density 800 bpi
     - SD16 = status density 1600 bpi
     - MTE  = multiple-track-error
     - STE  = single-track-error
     - TM   = tape-mark
     - IDB  = ID burst
     - EOB  = end-of-block

   In simulation, returned status is a combination of static controller status,
   static unit status, and dynamic unit status.  Static status is set during
   command execution and persists until the next command begins.  An example is
   a data error or write protected status.  Dynamic status is determined at the
   point of the status call.  An example is end-of-tape status.

   The bits representing the returned status are controller-specific, so macros
   are used to index into the status mapping table.


   Implementation notes:

    1. The HP 3000 controller presents the SR signal as "unit ready" status.
       The 1000 controllers present the complement of the SR signal as "unit
       busy" status and the complement of the SL signal as "unit local" status.
*/

HP_WORD tl_status (CVPTR cvptr)
{
UNIT *const uptr = cvptr->device->units + cvptr->unit_selected; /* a pointer to the selected unit */
uint32 status;

status = cvptr->status | CST_UNITSEL;                   /* merge the controller status and the selected unit number */

if (uptr->flags & UNIT_OFFLINE)                         /* if the unit is offline */
    status |= DST_UNITBUSY | DST_UNITLOCL;              /*   then set not-ready and not-online status */

else {                                                  /* otherwise the unit is online */
    status |= uptr->Status;                             /*   so add the unit status */

    if (uptr->flags & UNIT_REWINDING)                   /* if the tape is rewinding */
        status |= DST_REWIND | DST_UNITBUSY;            /*   then set rewind and busy status */

    else {                                              /* otherwise */
        status |= DST_UNITRDY;                          /*   the unit is ready */

        if (sim_tape_bot (uptr))                        /* if the tape is positioned at the beginning */
            status |= DST_LOADPT;                       /*   then set load-point status */

        else if (sim_tape_eot (uptr))                   /* otherwise if the tape positioned after EOT */
            status |= DST_EOT;                          /*   then set end-of-tape status */
        }
    }

return LOWER_WORD (status);                             /* return the 16-bit combined controller and unit status */
}


/* Reset the controller.

   This routine is called to perform a hard clear on the tape controller.  It is
   a harder clear than the clear performed by the "tl_clear" routine in
   response to a programmed master reset or clear.  This clear aborts any I/O in
   progress, including rewinds, and resets the controller and all units to the
   idle state.  It is typically called by the interface in response to a RESET
   command.

   In addition, if this is a power-on reset, it sets up the property entry index
   value in each unit.
*/

t_stat tl_reset (CVPTR cvptr)
{
uint32     unit;
UNIT       *uptr;
DRIVE_TYPE drive;

tl_clear (cvptr);                                           /* clear any in-progress writes */

if (sim_switches & SWMASK ('P'))                                /* if this is a power-on reset */
    for (unit = 0; unit < cvptr->device->numunits; unit++) {    /*   then set up the unit property indices */
        uptr = cvptr->device->units + unit;                     /* get a pointer to the unit */
        drive = GET_MODEL (uptr->flags);                        /*   and the current drive model ID */

        if (validate_drive (cvptr, uptr, drive, 0) != SCPE_OK)  /* if the drive's property index cannot be set */
            return SCPE_IERR;                                   /*   then return internal error status */
        }

for (unit = 0; unit < cvptr->device->numunits; unit++) {    /* reset all of the drives */
    uptr = cvptr->device->units + unit;                     /* get a pointer to the unit */

    sim_tape_reset (uptr);                                  /* reset the tape support library status */
    sim_cancel (uptr);                                      /*   and cancel any in-process operation */
    uptr->wait = NO_EVENT;                                  /*     and any scheduled operation */

    uptr->Phase = Idle_Phase;                               /* idle the unit */
    uptr->Opcode = Invalid_Opcode;                          /*   and clear the opcode */

    uptr->Status &= ~UST_WRSTAT;                            /* clear any write status */
    uptr->flags &= ~UNIT_REWINDING;                         /* the unit is no longer rewinding */
    }

return SCPE_OK;
}


/* Clear the controller.

   This routine performs a hardware clear on the controller.  It is equivalent
   to asserting the CLEAR signal to the 3000 controller, which restarts the
   controller microprogram, or executing the CLR command on the 1000
   controllers.  It clears any controller operation in progress and stops all
   tape motion, except that drives with rewinds in progress are allowed to
   complete the rewind.  For the 3000 controller only, unit 0 is selected.

   In simulation, if a clear is done before a write starts, the write is
   abandoned.  If it's done while a write is in progress, the record is
   truncated at the current point and marked as bad.

   In REALTIME mode only, if an in-progress read or spacing operation involves
   an erase gap (indicated by the position change being greater than the record
   length traversed), the simulated position is calculated.  If it is within the
   gap, the tape is repositioned and then aligned to a gap marker.  Otherwise,
   the position is unchanged, as repositioning within a record is not supported.

   In FASTTIME mode, calculating tape position from the remaining traversal time
   is unreliable, so it is not attempted.


   Implementation notes:

    1. The traverse phase is entered either when traversing an erase gap or when
       rewinding.  Once rewinding is excluded, a unit in the traverse phase must
       be moving over a gap.

    2. The REALTIME gap traversal position at the time of the controller clear
       is calculated by determining how much of the gap had been traversed and
       then adding or subtracting that from the initial position, depending on
       the direction of travel.  The gap traversal amount in bytes is the
       original gap length minus the amount not yet traversed, which is
       determined by dividing the remaining event delay time by the delay time
       per byte.  If the current (final) position is less than the initial
       position, then the tape was moving backward, so the traversal amount is
       subtracted from the initial position.  Otherwise, the tape is moving
       forward, and the amount is added.

    3. In hardware, a clear asynchronously resets the state machine (1000
       controllers) or restarts the microprogram (3000 controller).  Therefore,
       an aborted write will leave a bad record on the tape, as the normal write
       sequence will not complete -- an NRZI drive will not have the trailing
       CRCC and LRCC bytes, and a PE drive will not have the postamble and IRG.
       In simulation, the record is written with the "bad data" indicator.

    4. The only unit that may be in a non-idle phase without being active is the
       controller unit.
*/

void tl_clear (CVPTR cvptr)
{
uint32   unit;
int32    remaining_time;
UNIT     *uptr;
t_addr   reset_position, relative_position;
t_mtrlnt marker;

for (unit = 0; unit < cvptr->device->numunits; unit++) {    /* look for a write or gap traverse in progress */
    uptr = cvptr->device->units + unit;                     /* get a pointer to the unit */

    remaining_time = sim_activate_time (uptr);              /* get the remaining unit delay time, if any */

    if (remaining_time) {                                   /* if the unit is currently active */
        if (uptr->flags & UNIT_REWINDING)                   /*   then a clear does not affect a rewind in progress */
            tpprintf (cvptr->device, TRACE_INCO,
                      "Unit %u controller clear allowed %s to continue\n",
                      unit, opcode_names [uptr->Opcode]);

        else {                                              /* but all other commands are aborted */
            sim_cancel (uptr);                              /*   so cancel any scheduled event */

            if ((cvptr->device->flags & DEV_REALTIME)       /* if REALTIME mode is selected */
              && uptr->Phase == Traverse_Phase) {           /*   and a gap is being traversed */
                relative_position = cvptr->gaplen           /*     then calculate the relative progress */
                                      - remaining_time / cvptr->dlyptr->data_xfer;

                if (uptr->pos < cvptr->initial_position)                            /* if the motion is backward */
                    reset_position = cvptr->initial_position - relative_position;   /*   then move toward the BOT */
                else                                                                /* otherwise */
                    reset_position = cvptr->initial_position + relative_position;   /*   move toward the EOT */

                cvptr->gaplen -= (t_mtrlnt) relative_position;  /* reduce the gap length by the amount not traversed */

                while (cvptr->gaplen > sizeof (t_mtrlnt)) {     /* align the reset position to a gap marker */
                    if (sim_fseek (uptr->fileref,               /* seek to the reset position */
                                   reset_position, SEEK_SET)    /*   and if the seek succeeds */
                      || sim_fread (&marker, sizeof (t_mtrlnt), /*     then read the marker */
                                    1, uptr->fileref) == 0) {                   /* if either call fails */
                        cprintf ("%s simulator tape library I/O error: %s\n",   /*   then report the error */
                                 sim_name, strerror (errno));                   /*     to the console */

                        clearerr (uptr->fileref);                               /* clear the error and leave the file */
                        break;                                                  /*   at the original position */
                        }

                    else if (marker == MTR_GAP) {           /* otherwise if a gap marker was read */
                        uptr->pos = reset_position;         /*   then set the new position */
                        break;                              /*     and repositioning is complete */
                        }

                    else {                                  /* otherwise */
                        reset_position--;                   /*   back up a byte and try again */
                        cvptr->gaplen--;                    /*     until the gap is exhausted */
                        }
                    };

                tpprintf (cvptr->device, TRACE_INCO,
                          "Unit %u controller clear stopped tape motion at position %" T_ADDR_FMT "u\n",
                          unit, uptr->pos);
                }

            else                                            /* otherwise FASTTIME mode is selected */
                tpprintf (cvptr->device, TRACE_INCO,
                          "Unit %u controller clear aborted %s after partial completion\n",
                          unit, opcode_names [uptr->Opcode]);

            if (cmd_props [uptr->Opcode].class == Class_Write   /* if the last command was a write */
              && cmd_props [uptr->Opcode].transfer == TRUE      /*   that involves a data transfer */
              && uptr->Phase == Data_Phase)                     /*   that was not completed */
                cvptr->call_status = MTSE_RECE;                 /*     then the record will be bad */

            uptr->Phase = Stop_Phase;                           /* execute the stop phase of the command */
            continue_command (cvptr, uptr, NO_FLAGS, NO_DATA);  /*   to ensure a partial record is written */

            sim_tape_reset (uptr);                              /* reset the tape support library status */
            }
        }

    else if (uptr->Phase != Idle_Phase) {               /* otherwise if the controller unit is executing */
        uptr->Phase = Idle_Phase;                       /*   then idle it */

        if (uptr->Opcode != Clear_Controller)           /* report the abort only if this isn't a clear command */
            tpprintf (cvptr->device, TRACE_INCO,
                      "Unit %u controller clear aborted %s after partial completion\n",
                      unit, opcode_names [uptr->Opcode]);
        }
    }

cvptr->status = 0;                                      /* clear the controller status */
cvptr->state = Idle_State;                              /*   and idle it */
cvptr->unit_attention = 0;                              /* clear any pending unit attention */

if (cvptr->type == HP_30215)                            /* if this is the 3000 controller */
    cvptr->unit_selected = 0;                           /*   then a clear selects unit 0 */

tpprintf (cvptr->device, TRACE_CMD, "Controller cleared\n");

return;
}



/* Tape library global utility routines */


/* Return the name of an opcode.

   A string representing the supplied controller opcode is returned to the
   caller.  If the opcode is invalid, an error string is returned.
*/

const char *tl_opcode_name (CNTLR_OPCODE opcode)
{
if (opcode < Invalid_Opcode)                            /* if the opcode is legal */
    return opcode_names [opcode];                       /*   then return the opcode name */
else                                                    /* otherwise */
    return opcode_names [Invalid_Opcode];               /*   return an error indication */
}


/* Return the name of a unit.

   A string representing the supplied unit is returned to the caller.  If the
   unit is invalid, an error string is returned.
*/

const char *tl_unit_name (int32 unit)
{
if (unit <= TL_CNTLR_UNIT)                              /* if the unit number is valid */
    return unit_names [unit];                           /*   then return the unit designator */
else                                                    /* otherwise */
    return "Unit invalid";                              /*   return an error indication */
}


/* Tape library global SCP support routines */


/* Attach a tape image file to a unit.

   The file specified by the supplied filename is attached to the indicated
   unit.  If the attach was successful, the drive is set online, and unit
   attention is set.

   If the controller is idle, the routine returns SCPE_INCOMP to indicate that
   the caller must then call the controller to poll for drive attention to
   complete the command.  Otherwise, it returns SCPE_OK, and the drives will be
   polled automatically when the current command completes and the controller is
   idled.


   Implementation notes:

    1. The support library LOCKED and WRITEENABLED modifiers are not used to
       specify a tape as read-only.  All HP drives use a write ring on the back
       of the tape reel, which must be dismounted to remove or add the ring.
       Therefore, changing read-only status results in a remount, leaving the
       tape at the load point.  LOCKED and WRITEENABLED do not provide these
       semantics.  Instead, we require the -R option to the ATTACH command to
       specify that a tape does not have a write ring.

    2. If we are called during a RESTORE command, the unit's flags are not
       changed to avoid upsetting the state that was SAVEd.

    3. The pointer to the appropriate event delay times is set in case we are
       being called during a RESTORE command (the assignment is redundant
       otherwise).
*/

t_stat tl_attach (CVPTR cvptr, UNIT *uptr, char *cptr)
{
const int32 unit = (int32) (uptr - cvptr->device->units);   /* the unit number */
t_stat result;

result = sim_tape_attach (uptr, cptr);                  /* attach the tape image file to the unit */

if (result == SCPE_OK                                   /* if the attach was successful */
  && (sim_switches & SIM_SW_REST) == 0) {               /*   and we are not being called during a RESTORE command */
    uptr->flags = uptr->flags & ~UNIT_OFFLINE;          /*     then set the unit online */

    if (uptr->flags & UNIT_RO)                          /* if the attached file is read-only */
        uptr->Status |= UST_WRPROT;                     /*   then set write-protected status */
    else                                                /* otherwise */
        uptr->Status &= ~UST_WRPROT;                    /*   clear the status */

    cvptr->unit_attention |= 1 << unit;                 /* drive attention sets on tape load */

    tpprintf (cvptr->device, TRACE_CMD, "Unit %d tape loaded and set online\n",
              unit);

    if (cvptr->state == Idle_State)                     /* if the controller is idle */
        result = SCPE_INCOMP;                           /*   then it must be called to poll the drives */
    }

if (cvptr->device->flags & DEV_REALTIME)                /* if realistic timing is selected */
    cvptr->dlyptr = &real_times [PROP_INDEX (uptr)];    /*   then get the real times pointer for this drive */
else                                                    /* otherwise optimized timing is selected */
    cvptr->dlyptr = cvptr->fastptr;                     /*   so use the fast times pointer */

return result;                                          /* return the result of the attach */
}


/* Detach a tape image file from a unit.

   The tape is unloaded from the drive, and the attached file, if any, is
   detached.  Unloading a tape leaves the drive offline.  A command in progress
   is allowed to continue to completion, unless it attempts to access the file.
   If it does, the command will abort and simulation will stop with a "Unit not
   attached" error message.
*/

t_stat tl_detach (UNIT *uptr)
{
uptr->flags &= ~UNIT_OFFLINE;                           /* set the unit offline */

return sim_tape_detach (uptr);                          /* detach the tape image file from the unit */
}


/* Set the controller timing mode.

   This validation routine is called to set the timing mode for the tape
   controller.  The "value" parameter is set to 1 to enable realistic timing and
   0 to enable optimized timing.  The "desc" parameter is a pointer to the
   controller.

   When realistic timing is specified, unit activation delays are a function of
   both the controller type and the tape drive model.  As some controllers,
   e.g., the HP 3000 controller, support several drive models, the delays must
   be determined dynamically when a command is started on a given unit.
   Therefore, this routine simply sets or clears the "real time" flag, which is
   tested in the "start_command" routine.
*/

t_stat tl_set_timing (UNIT *uptr, int32 value, char *cptr, void *desc)
{
DEVICE *const dptr = ((CVPTR) desc)->device;            /* a pointer to the controlling device */

if (value)                                              /* if realistic timing is requested */
    dptr->flags |= DEV_REALTIME;                        /*   then set the real time flag */
else                                                    /* otherwise */
    dptr->flags &= ~DEV_REALTIME;                       /*   clear it for fast timing */

return SCPE_OK;
}


/* Set the tape drive model.

   This validation routine is called to set the model of the tape drive
   associated with the specified unit.  The "value" parameter indicates the
   model ID.  Support for the drive model with the specified controller is
   verified before permitting the change.
*/

t_stat tl_set_model (UNIT *uptr, int32 value, char *cptr, void *desc)
{
const CVPTR cvptr = (CVPTR) desc;                       /* the controller state structure pointer */
const DRIVE_TYPE new_drive = GET_MODEL (value);         /* the new model ID */

return validate_drive (cvptr, uptr, new_drive, 0);      /* verify the model change and set the property index */
}


/* Set the tape drive density.

   This validation routine is called to set the density of the tape drive
   associated with the specified unit via a "SET <unit> DENSITY=<bpi>" command.
   The "cptr" parameter points to the <bpi> string, which must be a value
   supported by the controller and tape drive model.  Support for the new
   density setting is verified before permitting the change.
*/

t_stat tl_set_density (UNIT *uptr, int32 value, char *cptr, void *desc)
{
const CVPTR cvptr = (CVPTR) desc;                       /* the controller state structure pointer */
const DRIVE_TYPE model = GET_MODEL (uptr->flags);       /* the current drive model ID */
uint32 new_bpi;
t_stat status;

if (cptr == NULL || *cptr == '\0')                      /* if no density value is present */
    return SCPE_MISVAL;                                 /*   then report a missing value error */

new_bpi = (uint32) get_uint (cptr, 10, UINT_MAX, &status);  /* parse the supplied value */

if (status != SCPE_OK)                                      /* if a parsing failure occurred */
    return status;                                          /*   then report the problem */

else if (new_bpi == 0)                                      /* otherwise if the new density is zero */
    return SCPE_ARG;                                        /*   then disallow it */

else                                                        /* otherwise a numeric value is present */
    return validate_drive (cvptr, uptr, model, new_bpi);    /*   so validate the density change */
}


/* Set the tape drive reel capacity.

   This validation routine is called to set the tape reel capacity of the tape
   drive associated with the specified unit.  The "value" parameter indicates
   whether the capacity in megabytes (0) or the tape length in feet (1) was
   specified.

   If the capacity was specified with a "SET <unit> CAPACITY=<n>" command, the
   simulator tape support library routine "sim_tape_set_capac" is called to
   parse the size in megabytes and set the unit capacity value.  If the routine
   succeeded, any existing reel size is cleared.

   If the capacity was specified with a "SET <unit> REEL=<length>" command, the
   "cptr" parameter points to the <length> string, which must be one of the
   standard reel sizes (600, 1200, or 2400-foot).  If the reel size is 0, the
   capacity is reset to the unlimited size.  Otherwise, the reel size ID is set
   in the unit property value, and the unit capacity in bytes is calculated and
   set from the reel size and tape drive density.  If the reel size is not a
   valid value, the routine returns SCPE_ARG to indicate an invalid argument was
   supplied.
*/

t_stat tl_set_reelsize (UNIT *uptr, int32 value, char *cptr, void *desc)
{
const uint32 tape_bpi = drive_props [PROP_INDEX (uptr)].bpi;    /* the tape unit density */
int32  reel;
t_stat status;

if (value == 0) {                                           /* if the capacity is being specified directly */
    status = sim_tape_set_capac (uptr, value, cptr, desc);  /*   then set it using the supplied size in megabytes */

    if (status == SCPE_OK)                                  /* if the supplied capacity is OK */
        uptr->Prop = uptr->Prop & ~PROP_REEL_MASK           /*   then clear any prior reel size setting */
                       | PROP_REEL_UNLIM;
    return status;
    }

else if (cptr == NULL)                                  /* otherwise if no reel size is present */
    return SCPE_ARG;                                    /*   then return an invalid argument error */

else {                                                  /* otherwise a size is specified */
    reel = (int32) get_uint (cptr, 10, 2400, &status);  /*   so parse the tape length in feet */

    if (status != SCPE_OK)                              /* if the parse failed */
        return status;                                  /*   then return the failure status */

    else {                                              /* otherwise */
        switch (reel) {                                 /*   validate the reel size */

            case 0:                                         /* an unlimited-length reel */
                uptr->capac = 0;                            /*   so set the capacity  */
                uptr->Prop = uptr->Prop & ~PROP_REEL_MASK   /*     and reel property to "unlimited" */
                               | PROP_REEL_UNLIM;
                break;

            case 600:                                       /* a 600 foot reel */
                uptr->capac = 600 * 12 * tape_bpi;          /*   so set the capacity in bytes */
                uptr->Prop = uptr->Prop & ~PROP_REEL_MASK   /*     and set the reel property ID */
                               | PROP_REEL_600;
                break;

            case 1200:                                      /* a 1200 foot reel */
                uptr->capac = 1200 * 12 * tape_bpi;
                uptr->Prop = uptr->Prop & ~PROP_REEL_MASK
                               | PROP_REEL_1200;
                break;

            case 2400:                                      /* a 2400 foot reel */
                uptr->capac = 2400 * 12 * tape_bpi;
                uptr->Prop = uptr->Prop & ~PROP_REEL_MASK
                               | PROP_REEL_2400;
                break;

            default:                                        /* other length values */
                return SCPE_ARG;                            /*   are invalid */
            }

        return SCPE_OK;                                 /* a valid reel size is accepted */
        }
    }
}


/* Show the controller timing mode.

   This display routine is called to show the timing mode for the tape
   controller.  The "desc" parameter is a pointer to the controller.


   Implementation notes:

    1. The explicit use of "const CNTLR_VARS *" is necessary to declare a
       pointer to a constant structure.  Using "const CVPTR" declares a constant
       pointer instead.
*/

t_stat tl_show_timing (FILE *st, UNIT *uptr, int32 value, void *desc)
{
DEVICE *const dptr = ((const CNTLR_VARS *) desc)->device;   /* a pointer to the controlling device */

if (dptr->flags & DEV_REALTIME)                         /* if the real time flag is set */
    fputs ("realistic timing", st);                     /*   then we're using realistic timing */
else                                                    /* otherwise */
    fputs ("fast timing", st);                          /*   we're using optimized timing */

return SCPE_OK;
}


/* Show the tape drive density.

   This display routine is called to show the density in bits per inch of the
   tape drive associated with the specified unit.  The "uptr" parameter points
   to the unit to be queried.
*/

t_stat tl_show_density (FILE *st, UNIT *uptr, int32 value, void *desc)
{
fprintf (st, "%u bpi", drive_props [PROP_INDEX (uptr)].bpi);

return SCPE_OK;
}


/* Show the tape drive reel capacity.

   This display routine is called to show the tape reel capacity of the tape
   drive associated with the unit pointed to by the "uptr" parameter.  The
   "value" parameter indicates whether the reel size was requested explicitly
   via a "SHOW <unit> REEL" command (1), or whether the capacity was requested
   explicitly via a "SHOW <unit> CAPACITY" command or implicitly via "SHOW
   <dev>" or "SHOW <unit>" commands (0).  The "desc" parameter is a pointer to
   the controller.

   If the reel capacity is unlimited or is a specified size in megabytes, the
   simulator tape support library routine "sim_tape_show_capac" is called to
   report the unlimited size or the size in megabytes.  Otherwise, the specified
   length in feet is reported.

   The reel size format modifier is a "named SHOW only" entry, so the caller
   does not add a trailing newline after the routine returns.  Therefore, we
   have to add it here.


   Implementation notes:

    1. Reel size IDs for 600, 1200, and 2400 foot reels must be 1, 2, and 3,
       respectively, to provide multiplication by 2 ** <reel ID>.
*/

t_stat tl_show_reelsize (FILE *st, UNIT *uptr, int32 value, void *desc)
{
t_stat status = SCPE_OK;

if (PROP_REEL (uptr) == Reel_Unlimited)                     /* if the unit has unlimited or custom size */
    status = sim_tape_show_capac (st, uptr, value, desc);   /*   then display the capacity in bytes */

else                                                        /* otherwise */
    fprintf (st, "%4d foot reel", 300 << PROP_REEL (uptr)); /*   display the reel size in feet */

if (value == 1)                                             /* if we're called to display the reel size */
    fputc ('\n', st);                                       /*   then add a newline, as the caller will omit it */

return status;
}



/* Tape library local controller routines */


/* Start a new command.

   This routine is called to validate and optionally begin execution of a new
   command.  It is called when the controller is waiting for a command and the
   interface asserts CMRDY and/or CMXEQ to indicate that a new command is
   available.  It returns a set of action functions and a data word to the
   caller.  For a good command with CMXEQ asserted, it also sets up the next
   phase of operation on the controller and/or drive unit and schedules the
   unit(s) as appropriate.

   On entry, the command opcode is supplied in the "inbound_data" parameter and
   applies to the currently selected unit.  The current delay properties pointer
   is set up, depending on the timing mode, and the opcode is checked for
   validity.  If it fails the check, the command is rejected.  If it's OK and is
   a Unit Select command, it is executed immediately.  Otherwise, if the CMXEQ
   flag is set, command execution is scheduled.

   The routine asserts the IFGTC function to inform the interface that the
   command was executed.  If the command transfers data, the class of the
   command (read or write) is returned, so that the interface may ensure that
   the correct type of channel transfer is set up.  All other commands,
   including write commands that do not transfer data (e.g., Write File Mark),
   are classified as control commands to indicate that no channel transfer is
   required.

   A command rejection occurs on the 1000 controllers for these reasons:

     - the unit is not ready for any command requiring tape motion
     - the tape has no write ring and a write command is issued
     - a Select Unit command is issued while the controller is busy
     - a Backspace Record or Backspace File command is issued with the tape
       positioned at the load point

   A command rejection occurs on the 3000 controller for these 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

   An interface may force a command reject by passing the Invalid_Opcode value
   as the command to validate.


   Implementation notes:

    1. The 1000 controllers separate validity checking from execution.  The
       former occurs when an OTx instruction is issued; the latter occurs when
       an STC instruction is issued.  The flags that accompany instruction
       execution are CMRDY for OTx and CMXEQ for STC.

       The 3000 controller combines these two functions, and the flags
       accompanying the single call are CMRDY and CMXEQ.

    2. The Select_Unit_n command opcode values are contiguous, so that the
       unit selected may be set directly from the opcode value.

    3. The 3000 controller hardware executes a Select Unit command in about 10
       microcode instructions from PCONTSTB to drive select output and service
       request assertion, which is about 4.3 microseconds (the microinstruction
       time is 434 nanoseconds).  The 1000 controllers clock the Select Unit
       command directly into a register with the IOO signal; this effectively
       selects the unit "immediately."  In simulation, the Select Unit command
       completes without scheduling an event delay.

    4. For the 3000 controller, command rejection requires a delay between
       detection and interrupt assertion, which must be scheduled on the
       controller unit (all drive units may be busy, e.g., rewinding, so we
       can't schedule on them).

    5. All tape drives ignore a rewind command if the tape is positioned at the
       load point.  The 3000 controller will complete the command immediately,
       whereas the 1000 controllers still will delay for the rewind start time
       before completing.

    6. The "drive ready" condition exists if the unit is online and not
       rewinding.
*/

static CNTLR_IFN_IBUS start_command (CVPTR cvptr, CNTLR_FLAG_SET flags, CNTLR_OPCODE opcode)
{
UNIT *const uptr = cvptr->device->units + cvptr->unit_selected;   /* a pointer to the currently selected unit */
CNTLR_IFN_IBUS outbound;

if (cvptr->device->flags & DEV_REALTIME)                /* if realistic timing is selected */
    cvptr->dlyptr = &real_times [PROP_INDEX (uptr)];    /*   then get the real times pointer for this drive */
else                                                    /* otherwise optimized timing is selected */
    cvptr->dlyptr = cvptr->fastptr;                     /*   so use the fast times pointer */

if ((flags & CMRDY)                                     /* if command validity is to be checked */
  && (opcode >= Invalid_Opcode                          /*   and the opcode is invalid */
  || cvptr->type > LAST_CNTLR                           /*   or the controller type is undefined */
  || cmd_props [opcode].valid [cvptr->type] == FALSE    /*   or the opcode is not valid for this controller */
  || cmd_props [opcode].ready                           /*   or it requires a ready drive */
    && uptr->flags & (UNIT_OFFLINE | UNIT_REWINDING)    /*     but it's offline or rewinding */
  || cmd_props [opcode].class == Class_Write            /*   or this is a write command */
    && sim_tape_wrp (uptr)                              /*     but the drive is write-protected */
  || opcode != Clear_Controller                         /*   or it's not a controller clear */
    && cvptr->state != Idle_State)) {                   /*     and the controller is busy */
    CNTLR_UPTR->Opcode = opcode;                        /*   then save the rejected code */
    reject_command (cvptr, NULL);                       /*     and reject the command */
    outbound = NO_ACTION;                               /*       with no further action */
    }

else {                                                  /* otherwise the command is (assumed to be) OK */
    if (cmd_props [opcode].class == Class_Write)        /* if this is a write command */
        uptr->Status |= UST_WRSTAT;                     /*   then set write status */
    else                                                /* otherwise */
        uptr->Status &= ~UST_WRSTAT;                    /*   clear it */

    cvptr->status = 0;                                  /* clear the controller status for the new command */

    cvptr->call_status = MTSE_OK;                       /* clear the last library call status code */
    cvptr->index  = 0;                                  /* set up the */
    cvptr->length = 0;                                  /*   buffer access */
    cvptr->gaplen = 0;                                  /*     and clear the gap length */

    if (opcode >= Select_Unit_0 && opcode <= Select_Unit_3) {       /* if the opcode is a Select Unit command */
        cvptr->unit_selected = (uint32) (opcode - Select_Unit_0);   /*   then select the indicated unit */

        tpprintf (cvptr->device, TRACE_INCO, "%s completed\n",
                  opcode_names [opcode]);

        tpprintf (cvptr->device, TRACE_CMD, "%s succeeded\n",
                  opcode_names [opcode]);

        outbound = IFGTC | RQSRV | Class_Control;       /* indicate that a control command was executed */
        }

    else if (flags & CMXEQ) {                           /* otherwise if the command is to be executed */
        cvptr->state = Busy_State;                      /*   then set the controller */
        cvptr->status = CST_IFBUSY;                     /*     and the interface to the busy state */

        uptr->Opcode = opcode;                          /* save the opcode in the selected unit */

        if (cmd_props [opcode].transfer) {              /* if this command transfers data */
            CNTLR_UPTR->Phase = Wait_Phase;             /*   then set up the wait phase */
            CNTLR_UPTR->Opcode = opcode;                /*     and command opcode on the controller unit */

            outbound = IFGTC | RQSRV | cmd_props [opcode].class;    /* return the transfer class (read or write) */
            }

        else {                                          /* otherwise it's a control command */
            uptr->Phase = Start_Phase;                  /*   so set up the start phase */
            uptr->wait = cvptr->dlyptr->overhead;       /*     and the initial controller delay */

            if (cmd_props [opcode].ready)                       /* if the command accesses the drive */
                if (cmd_props [opcode].class != Class_Rewind)   /*   then if this is not a rewind command */
                    if (sim_tape_bot (uptr))                    /*      then if the tape is positioned at the BOT */
                        uptr->wait += cvptr->dlyptr->bot_start; /*        then the start delay is longer */
                    else                                        /*          than it would be if the position */
                        uptr->wait += cvptr->dlyptr->ir_start;  /*            is between two records */

                else if (! sim_tape_bot (uptr)                  /* otherwise if the rewind is not from the load point */
                  || cvptr->type != HP_30215)                   /*   or this is not the 3000 controller */
                    uptr->wait += cvptr->dlyptr->rewind_start;  /*     then add the rewind start delay */

            activate_unit (cvptr, uptr);                /* schedule the start phase */

            outbound = IFGTC | Class_Control;           /* indicate a control command is executing */
            }
        }

    else                                                /* otherwise */
        outbound = NO_ACTION;                           /*   command execution was not requested */
    }

return outbound;                                        /* return the functions and data word */
}


/* Continue the current command.

   This routine simulates continuing execution of the controller microcode or
   state machine for the current command.  It is called whenever the controller
   has had to wait for action from the CPU interface or the drive unit, and that
   action has now occurred.  Typically, this would be whenever the interface
   flag status changes, or a unit's event service has been entered.  It returns
   a set of action functions and a data word to the caller.  It also sets up the
   next phase of operation on the controller and/or drive unit and schedules the
   unit(s) as appropriate.

   On entry, the "uptr" parameter points to the unit whose event is to be
   serviced; this may be either the controller unit or a drive unit.  The
   "inbound_flags" and "inbound_data" parameters contain the CPU interface flags
   and data buffer values.

   While a unit is activated, the current phase indicates the reason for the
   activation, i.e., what the simulated drive is "doing" at the moment, as
   follows:

      Idle_Phase     -- Waiting for the next command to be issued (note that
                        this phase, which indicates that the unit is not
                        scheduled, is distinct from the controller Idle_State,
                        which indicates that the controller itself is idle).

      Wait_Phase     -- Waiting for the channel data transfer program to start.

      Start_Phase    -- Waiting for the drive to come up to speed after
                        starting.

      Traverse_Phase -- Waiting for the drive to traverse an erase gap preceding
                        a data record.

      Data_Phase     -- Waiting for the drive to traverse a data record or
                        accept or return the next data bytes to be transferred
                        to or from the tape.

      Stop_Phase     -- Waiting for the drive to slow to a stop.

      Error_Phase    -- Waiting to interrupt for a command abort.

   Depending on the current command opcode and phase, a number of actions may be
   taken:

      Idle_Phase     -- Attempting to continue execution of an idle unit causes
                        a command rejection error.  For the 3000, this implies a
                        TOGGLEINXFER or TOGGLEOUTXFER or READNEXTWD signal was
                        asserted with no command in progress.

      Wait_Phase     -- If the EOD flag is set, then reject the command.
                        Otherwise, the correct type of channel transfer has
                        started, so start the tape motion and set up the start
                        phase.

      Start_Phase    -- If a write data command is executing, request the first
                        data word from the interface.  A Write File Mark command
                        sets up the traverse phase to write an erase gap if the
                        tape is at BOT and the mode is REALTIME; otherwise, the
                        data phase is set up.  A read data command reads the
                        record from the tape image file and sets up the traverse
                        or data phases, depending on whether or not an erase gap
                        was present.  A file or record positioning command
                        spaces the file in the indicated direction and sets up
                        the traverse, start, or stop phases, depending on
                        whether a gap was present and the command continues or
                        is complete.  A Write Gap command sets up the traverse
                        phase to write the gap to the file.  A rewind command
                        completes immediately or sets up the stop phase if the
                        tape is positioned at BOT, or sets up the traverse phase
                        to rewind the file.

      Traverse_Phase -- A Write Gap or rewind command sets up the stop phase.  A
                        write, read, or spacing command sets up the data phase.
                        A gap traversal that ended with an error, e.g., runaway
                        or a tape mark, sets up the stop phase.

      Data_Phase     -- For read transfers, return the next word from the record
                        buffer, or for write transfers, store the next word into
                        the record buffer, and schedule the next data phase
                        transfer if the CPU has not indicated an end-of-data
                        condition.  If it has, or if the last word of the buffer
                        has been transmitted, schedule the stop phase.  For a
                        Write File Mark command, write the tape mark to the
                        file and set up the stop phase.

      Stop_Phase     -- A write data command writes the completed record to the
                        file.  A Rewind command sets unit attention and then
                        rewinds the image file.  In all cases, idle the unit and
                        controller and mark the command as complete.

      Error_Phase    -- A command abort has delayed until the tape motion has
                        stopped, so idle the unit and assert the STINT function
                        to request an interrupt.

   Phase assignments are validated on entry, and SCPE_IERR (Internal Error) is
   returned if entry is made with a phase that is not valid for a given
   operation.

   At the completion of the current phase, the next phase is scheduled, if
   required, before returning the appropriate function set and data word to the
   caller.  If the current phase is continuing, the service activation time will
   be set appropriately.

   The commands employ the various phases as follows:

       Command                       Wait    Start   Trav    Data    Stop
     +-----------------------------+-------+-------+-------+-------+-------+
     | Select_Unit_0               |   -   |   -   |   -   |   -   |   -   |
     | Select_Unit_1               |   -   |   -   |   -   |   -   |   -   |
     | Select_Unit_2               |   -   |   -   |   -   |   -   |   -   |
     | Select_Unit_3               |   -   |   -   |   -   |   -   |   -   |
     | Clear_Controller            |   -   |   C   |   -   |   -   |   N   |
     | Read_Record                 |   X   |   R   |   t   |   D   |   N   |
     | Read_Record_with_CRCC       |   X   |   R   |   t   |   D   |   N   |
     | Read_Record_Backward        |   X   |   R   |   t   |   D   |   N   |
     | Read_File_Forward           |   X   |   R   |   t   |   D   |   N   |
     | Write_Record                |   X   |   g   |   t   |   D   |   W   |
     | Write_Record_without_Parity |   X   |   g   |   t   |   D   |   W   |
     | Write_File_Mark             |   -   |   g   |   t   |   F   |   N   |
     | Write_Gap                   |   -   |   G   |   T   |   -   |   N   |
     | Write_Gap_and_File_Mark     |   -   |   G   |   T   |   F   |   N   |
     | Forward_Space_Record        |   -   |   S   |   t   |   d   |   N   |
     | Forward_Space_File          |   -   |   S   |   t   |   d   |   N   |
     | Backspace_Record            |   -   |   S   |   t   |   d   |   N   |
     | Backspace_File              |   -   |   S   |   t   |   d   |   N   |
     | Rewind                      |   -   |   N   |   B   |   -   |   E   |
     | Rewind_Offline              |   -   |   N   |   B   |   -   |   E   |
     +-----------------------------+-------+-------+-------+-------+-------+

     Key:

       B = traverse the tape while rewinding to BOT
       C = clear the controller
       D = traverse the record while transferring data via the record buffer
       d = traverse the record without transferring data
       E = rewind the tape file
       F = write a file mark to the tape file
       G = write a gap to the tape file
       g = write a gap to the tape file only if REALTIME and BOT
       N = no action other than setting up the next phase
       R = read a record from the tape file
       S = space over a record in the tape file
       T = traverse an erase gap
       t = traverse an erase gap only if present
       W = write a record to the tape file
       X = wait for a channel transfer to start
       - = skip the phase

   For the 3000 controller, a unit preparing to read or write data must wait for
   the channel transfer to start.  The wait phase is set up on the controller
   unit, and the operation pauses until a read or write channel order is issued.
   The EOD flag is used to ensure that the correct type of order is issued.  The
   interface sets the EOD flag as part of IFGTC processing and clears it if the
   type of channel order matches the type of tape command, e.g., a read order is
   issued for a Read Record command.  If the wrong type of order is issued,
   e.g., a write order for a Read Record command, EOD will be set on entry in
   the wait state; this will cause a command reject.

   Within the data phase of a write command, the interface sets EOD to indicate
   the end of the data record.  For a read command, the interface generally
   doesn't know the length of the next record.  If the programmed transfer
   length is shorter than the record length, the interface will set EOD to
   indicate that the remainder of the data record is not wanted.  If the record
   length is shorter than the transfer length, the controller will assert the
   DVEND function to indicate that the device has ended the transfer
   prematurely.


   Implementation notes:

    1. The Forward Space Record, Forward Space File, Backspace Record, and
       Backspace File commands schedule data phases to traverse the data record,
       but not data is transferred to the interface.

    2. The 7970 ignores a rewind command if the tape is positioned at the load
       point.  The 3000 controller completes such a command immediately; as the
       Unit Ready signal from the drive never denies, drive attention is never
       set, i.e., the rewind completes without the usual unit interrupt.  The
       1000 controllers set Interface Busy status for 0.56 millisecond,
       regardless of the tape position, and complete with the Command Flag set.

    3. The 30215 controller does not detect tape runaway when spacing in the
       reverse direction.  If the support library "space record reverse" routine
       returns MTSE_RUNAWAY status, the error status is cleared, but the buffer
       length is set to 0 to indicate that the spacing did not encompass a data
       record.  After traversing the gap, the spacing will be retried.

    4. Support library errors that occur after traversing an erase gap, e.g.,
       encountering a tape mark, schedule the traversal phase for the gap but
       set the controller into the error state to terminate the command after
       traversal.

    5. If the EOD flag is set by the interface before the entire data record is
       transferred, the stop phase delay for the interrecord gap is lengthened
       by an amount corresponding to the length of the data record remaining.

    6. Data record read error status (MTSE_RECE) is initially treated as
       successful to allow the transfer of the record data to the interface.
       After the stop phase has been processed, the error status is restored to
       abort the tape command.

    7. The Clear Controller command must be scheduled on the controller unit, as
       all four tape units may be executing rewinds in progress, which must be
       allowed to complete normally.

    8. A write of a null record is reported as a tape error.  This may occur if
       a Write_Record_without_Parity (WRZ) writes an all-zeros buffer (a byte
       with zero parity won't be seen, as it has no clocking information).

    9. The 13181 controller places the CRCC and LRCC into the interface output
       data register after completion of a read or write record command.  This
       behavior is undocumented but is used by the HP 1000 tape diagnostic.  To
       provide this functionality in simulation, the controller calculates the
       CRCC and LRCC from the current record buffer if it is configured as a
       13181 and is in REALTIME mode and returns the CRCC/LRCC in an outbound
       data word at the end of a read or write transfer.  The 13181 interface
       simulator must store this word in its output data register if IFOUT is
       asserted with EOD set; the IFIN handler stores the outbound value in the
       data register normally, so no special handling is needed for this case.
       This "extra" word is not accompanied by a channel service request, so it
       does not form part of the data record transferred.

   10. The 13181 and 13183 controllers check and report odd-length records while
       spacing.  The 30215 controller checks and reports only for reads.

   11. The 13181 and 13183 controllers terminate the Forward Space File command
       at the end of the first record past the end of tape marker.

   12. The 30215 controller always indicates an even number of bytes for a partial
       transfer, even if the record itself is an odd length.  Odd-length status
       for the other controllers always reflects the last record read or spaced
       over.

   13. Erase gaps must be written to the image file during the start phase so
       that a controller clear during the traverse phase can reposition into the
       gap appropriately.

   14. Data phase trace statements number data bytes or words transferred from 1
       to the record length, rather than from 0 to the length - 1.

   15. The data record buffer size is limited.  If an attempt is made to write a
       record that is larger than the buffer, the record is truncated at the
       buffer size and marked as "bad", and a Transfer Error interrupt occurs.

       Tape diagnostics usually attempt to write a single record that
       encompasses the entire tape reel by supplying data until "end of tape"
       status is seen.  Continuing to accept and discard data after the buffer
       size is exceeded isn't practicable, as the unit position isn't updated
       until the record is written, and therefore the caller would never see the
       EOT status that is used to terminate the write.

   16. The tape drive is a synchronous device, so overrun or underrun can occur
       if the interface is not ready when the controller must transfer data.  An
       overrun occurs when the controller is ready with a tape read word (IFIN),
       but the interface buffer is full (DTRDY).  An underrun occurs when the
       controller needs a tape write word (IFOUT), but the interface buffer is
       empty (~DTRDY).  These conditions are detected by the interface and
       communicated to the controller by the OVRUN flag.

   17. Unit attention is set at the completion of a Rewind or Rewind_Offline if
       the unit is online.  The status cannot be inferred from the command, as
       the user may have set the unit offline or online explicitly before the
       rewind completed.

   18. The "%.0u" print specification in the trace call absorbs the zero
       "length" value parameter without printing when the controller unit is
       specified.
*/

static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET inbound_flags, CNTLR_IBUS inbound_data)
{
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->Opcode;    /* the current command opcode */
const CNTLR_PHASE  phase  = (CNTLR_PHASE)  uptr->Phase;     /* the current command phase */
const t_bool       service_entry = (phase > Wait_Phase);    /* TRUE if entered via unit service */
int32              unit;
TL_BUFFER          data_byte;
t_mtrlnt           error_flag;
BYTE_SELECTOR      selector;
DRIVE_PROPS const  *pptr;
CNTLR_IFN_IBUS     outbound = NO_ACTION;
t_bool             complete = FALSE;

unit = (int32) (uptr - cvptr->device->units);           /* get the unit number */

tpprintf (cvptr->device, TRACE_STATE, "%s %s %s phase entered from %s\n",
          unit_names [unit], opcode_names [opcode], phase_names [phase],
          (service_entry ? "service" : "interface"));


switch (phase) {                                        /* dispatch the phase */

    case Idle_Phase:
        reject_command (cvptr, uptr);                   /* reject if no command is in progress */
        break;


    case Wait_Phase:
        if (inbound_flags & EOD)                        /* if the EOD flag is set */
            reject_command (cvptr, uptr);               /*   then the wrong channel order was issued */

        else {                                          /* otherwise the channel is ready to transfer */
            uptr->Opcode = Invalid_Opcode;              /*   so clear the controller command */
            uptr->Phase = Idle_Phase;                   /*     and idle the unit */

            unit = (int32) cvptr->unit_selected;        /* get the selected unit number */
            uptr = cvptr->device->units + unit;         /*   and unit pointer */

            uptr->Phase = Start_Phase;                  /* set up the start phase */
            uptr->wait = cvptr->dlyptr->overhead;       /*   and the initial controller delay */

            if (sim_tape_bot (uptr))                    /* if the tape is positioned at the BOT */
                uptr->wait += cvptr->dlyptr->bot_start; /*   then the start delay is longer */
            else                                        /*     than it would if the position */
                uptr->wait += cvptr->dlyptr->ir_start;  /*       is between records */
            }
        break;


    case Start_Phase:
        tpprintf (cvptr->device, TRACE_INCO, "Unit %d %s started at position %" T_ADDR_FMT "u\n",
                  unit, opcode_names [opcode], uptr->pos);

        pptr = &drive_props [PROP_INDEX (uptr)];        /* get the drive property pointer */

        cvptr->initial_position = uptr->pos;            /* save the initial tape position */

        switch (opcode) {                               /* dispatch the current operation */

            case Clear_Controller:
                tl_clear (cvptr);                       /* clear the controller */

                uptr->Phase = Stop_Phase;               /* schedule the completion */
                uptr->wait = cvptr->dlyptr->ir_start;   /*   after motion ceases */
                break;


            case Read_Record:
            case Read_Record_with_CRCC:
                outbound =                                      /* read the next record from the file */
                   call_tapelib (cvptr, uptr, lib_read_fwd, TL_MAXREC);

                if ((outbound & SCPE) == NO_FLAGS) {            /* if the read succeeded */
                    if (cvptr->length & 1)                      /*   then if the record length was odd */
                        cvptr->status |= CST_ODDLEN;            /*     then set the corresponding status */

                    if (cvptr->gaplen) {                        /* if a gap was present */
                        uptr->Phase = Traverse_Phase;           /*   then schedule the gap traversal phase */
                        uptr->wait =                            /*     and the traversal time */
                           cvptr->gaplen * cvptr->dlyptr->data_xfer;
                        }

                    else {                                      /* otherwise there was no gap */
                        uptr->Phase = Data_Phase;               /*   so proceed to the data phase */
                        uptr->wait =                            /*     with the data delay time */
                           2 * cvptr->dlyptr->data_xfer;
                        }

                    if (pptr->bpi <= 800                        /* if this is an NRZI drive */
                      && opcode == Read_Record_with_CRCC        /*   and the CRCC was requested */
                      || cvptr->device->flags & DEV_REALTIME    /* or the mode is REALTIME */
                      && cvptr->type == HP_13181)               /*   for the 13181 controller */
                        add_crcc_lrcc (cvptr, opcode);          /*     then add the CRCC/LRCC to the buffer */
                    }
                break;


            case Write_Record:
            case Write_Record_without_Parity:
                outbound = RQSRV;                               /* request the first data word from the channel */

            /* fall through into the Write_File_Mark case */

            case Write_File_Mark:
                if ((cvptr->device->flags & DEV_REALTIME) == 0  /* if fast timing is enabled */
                  || sim_tape_bot (uptr) == FALSE) {            /*   or the tape is not positioned at the BOT */
                    uptr->Phase = Data_Phase;                   /*     then proceed to the data phase */
                    uptr->wait = 2 * cvptr->dlyptr->data_xfer;  /*       with the data delay time */
                    break;
                    }                                           /* otherwise an initial gap is needed */

            /* fall through into the Write_Gap case */

            case Write_Gap:
                outbound |=                                     /* erase the gap */
                   call_tapelib (cvptr, uptr, lib_write_gap, pptr->gap_size);

                if ((outbound & SCPE) == NO_FLAGS) {            /* if the erase succeeded */
                    uptr->Phase = Traverse_Phase;               /*   then set up to traverse the it */

                    cvptr->gaplen = (pptr->gap_size * pptr->bpi) / 10;      /* set the length of the gap */
                    uptr->wait = cvptr->gaplen * cvptr->dlyptr->data_xfer;  /*   and the traversal time */
                    }
                break;


            case Forward_Space_Record:
            case Forward_Space_File:
                outbound =                                  /* space forward over the next record in the file */
                   call_tapelib (cvptr, uptr, lib_space_fwd, 0);

                if ((outbound & SCPE) == NO_FLAGS)          /* if the spacing succeeded */
                    if (cvptr->gaplen > 0) {                /*   then if a gap is present */
                        uptr->Phase = Traverse_Phase;       /*     then schedule the traversal phase */
                        uptr->wait =                        /*       for the gap */
                           cvptr->gaplen * cvptr->dlyptr->data_xfer;
                        }

                    else {                                  /* otherwise no gap intervenes */
                        uptr->Phase = Data_Phase;           /*   so schedule the data phase */
                        uptr->wait =                        /*     to space over the record */
                           cvptr->length * cvptr->dlyptr->data_xfer;
                        }
                break;


            case Backspace_Record:
            case Backspace_File:
                outbound =                                  /* space in reverse over the previous record in the file */
                   call_tapelib (cvptr, uptr, lib_space_rev, 0);

                if ((outbound & SCPE) == NO_FLAGS)          /* if the spacing succeeded */
                    if (cvptr->gaplen > 0) {                /*   then if a gap is present */
                        uptr->Phase = Traverse_Phase;       /*     then schedule the traversal phase */
                        uptr->wait =                        /*       for the gap */
                           cvptr->gaplen * cvptr->dlyptr->data_xfer;
                        }

                    else {                                  /* otherwise no gap intervenes */
                        uptr->Phase = Data_Phase;           /*   so schedule the data phase */
                        uptr->wait =                        /*     to space over the record */
                           cvptr->length * cvptr->dlyptr->data_xfer;
                        }
                break;


            case Rewind_Offline:
                uptr->flags |= UNIT_OFFLINE;            /* set the unit offline immediately */

            /* fall through into the Rewind case */

            case Rewind:
                outbound = end_command (cvptr, uptr);   /* release the controller */

                if (sim_tape_bot (uptr))                /* if the tape is positioned at the load point */
                    complete = TRUE;                    /*   then the command is complete */

                else {                                  /* otherwise the tape must be rewound */
                    uptr->flags |= UNIT_REWINDING;      /*   so set rewinding status */

                    uptr->Opcode = opcode;              /* restore the opcode cleared by the end routine */
                    uptr->Phase = Traverse_Phase;       /*   and proceed to the rewinding phase */

                    uptr->wait =                        /* base the traversal time on the current tape position */
                       (int32) ((uptr->pos * cvptr->dlyptr->rewind_rate) / pptr->bpi);
                    }
                break;


            case Select_Unit_0:                         /* the command is invalid for this state */
            case Select_Unit_1:                         /* the command is invalid for this state */
            case Select_Unit_2:                         /* the command is invalid for this state */
            case Select_Unit_3:                         /* the command is invalid for this state */
            case Read_Record_Backward:                  /* the command is invalid for this state */
            case Read_File_Forward:                     /* the command is invalid for this state */
            case Write_Gap_and_File_Mark:               /* the command is invalid for this state */
            case Invalid_Opcode:                        /* the command is invalid for this state */
                outbound =                              /* abort the command and stop the simulation */
                   abort_command (cvptr, uptr, SCPE_IERR);
                break;
            }                                           /* end of the start phase operation dispatch */

        break;                                          /* end of the start phase handlers */


    case Traverse_Phase:
        switch (opcode) {                               /* dispatch the current operation */

            case Read_Record:
            case Read_Record_with_CRCC:
            case Write_Record:
            case Write_Record_without_Parity:
            case Write_File_Mark:
            case Forward_Space_Record:
            case Forward_Space_File:
                uptr->Phase = Data_Phase;                   /* proceed to the data phase */
                uptr->wait = 2 * cvptr->dlyptr->data_xfer;  /*   for a data transfer as needed */
                break;


            case Write_Gap:
               uptr->Phase = Stop_Phase;                /* proceed to the completion phase */
               uptr->wait = cvptr->dlyptr->ir_start;    /*   after the interrecord stop delay */
               break;


            case Backspace_Record:
            case Backspace_File:
                if (cvptr->length == 0)                 /* if a tape runaway occurred but was ignored */
                    uptr->Phase = Start_Phase;          /*   then try the spacing operation again */
                else                                    /* otherwise */
                    uptr->Phase = Data_Phase;           /*   proceed to the data phase to skip the record */

                uptr->wait = 2 * cvptr->dlyptr->data_xfer;
                break;


            case Rewind:
            case Rewind_Offline:
                uptr->Phase = Stop_Phase;                   /* motion is complete, so stop the drive */
                uptr->wait = cvptr->dlyptr->rewind_stop;    /*   after the load point search finishes */
                break;


            case Select_Unit_0:                         /* the command is invalid for this state */
            case Select_Unit_1:                         /* the command is invalid for this state */
            case Select_Unit_2:                         /* the command is invalid for this state */
            case Select_Unit_3:                         /* the command is invalid for this state */
            case Clear_Controller:                      /* the command is invalid for this state */
            case Read_Record_Backward:                  /* the command is invalid for this state */
            case Read_File_Forward:                     /* the command is invalid for this state */
            case Write_Gap_and_File_Mark:               /* the command is invalid for this state */
            case Invalid_Opcode:                        /* the command is invalid for this state */
                outbound =                              /* abort the command and stop the simulation */
                   abort_command (cvptr, uptr, SCPE_IERR);
                break;
            }                                           /* end of the traverse phase operation dispatch */


            if (cvptr->state > Busy_State) {            /* if an error condition exists */
                uptr->Phase = Stop_Phase;               /*   then terminate the command */
                uptr->wait = cvptr->dlyptr->ir_start;   /*     after tape motion stops */
                }

        break;                                          /* end of the traverse phase handlers */


    case Data_Phase:
        switch (opcode) {                               /* dispatch the current operation */

            case Read_Record:
            case Read_Record_with_CRCC:
                if (cvptr->index == cvptr->length           /* if record data is exhausted */
                  || inbound_flags & EOD) {                 /*   or the channel transfer is complete */
                    uptr->Phase = Stop_Phase;               /*   then set up the stop phase processing */
                    uptr->wait = (cvptr->length - cvptr->index)
                                   * cvptr->dlyptr->data_xfer
                                   + cvptr->dlyptr->ir_start;

                    if ((inbound_flags & EOD) == NO_FLAGS)  /* if the record ended before than the channel finished */
                        cvptr->state = End_State;           /*   then note that the device ended the transfer */

                    if (cvptr->device->flags & DEV_REALTIME /* if the mode is REALTIME */
                      && cvptr->type == HP_13181)           /*   for the 13181 controller */
                        outbound =                          /*     then return the CRCC and LRCC */
                           IFIN | TO_WORD (cvptr->buffer [cvptr->length + 0],
                                           cvptr->buffer [cvptr->length + 1]);
                    }

                else {                                          /* otherwise there is more data to transfer */
                    if (cvptr->type == HP_IB) {                 /* if the controller uses HP-IB */
                        outbound =                              /*   then transfer one byte at a time */
                           cvptr->buffer [cvptr->index++];      /*     to the data register */

                        tpprintf (cvptr->device, TRACE_XFER, "Unit %d %s byte %u is %03o\n",
                                  unit, opcode_names [opcode],
                                  cvptr->index, outbound);

                        uptr->wait = cvptr->dlyptr->data_xfer;  /* schedule the next byte transfer */
                        }

                    else {                                      /* otherwise we transfer full words */
                        outbound =                              /* move data to the high byte */
                           TO_WORD (cvptr->buffer [cvptr->index++], 0);

                        if (cvptr->index < cvptr->length)       /* if there is more to transfer */
                            outbound |=                         /*   then merge in the low byte */
                              cvptr->buffer [cvptr->index++];

                        tpprintf (cvptr->device, TRACE_XFER, "Unit %d %s word %u is %06o\n",
                                  unit, opcode_names [opcode],
                                  (cvptr->index + 1) / 2, outbound);

                        uptr->wait = 2 * cvptr->dlyptr->data_xfer;  /* schedule the next word transfer */
                        }

                    outbound |= IFIN | RQSRV;               /* tell the interface that more data is available */
                    }
                break;


            case Write_Record:
            case Write_Record_without_Parity:
                if (cvptr->index == TL_MAXREC) {            /* if the buffer is full */
                    uptr->Phase = Stop_Phase;               /*   then set up the stop phase processing */
                    uptr->wait = cvptr->dlyptr->ir_start;   /*     after the motion stops */

                    cvptr->call_status = MTSE_RECE;         /* the record will be truncated, so mark it as bad */

                    outbound = IFOUT;                       /* tell the interface that we have the data */
                    }

                else {                                          /* otherwise the buffer still has space */
                    if (cvptr->type == HP_IB) {                 /* if the controller uses HP-IB */
                        cvptr->buffer [cvptr->index++] =        /*   then transfer the lower byte */
                           LOWER_BYTE (inbound_data);           /*     to the buffer */

                        cvptr->length = cvptr->length + 1;      /* update the buffer length */

                        uptr->wait = cvptr->dlyptr->data_xfer;  /* schedule the next byte transfer */

                        tpprintf (cvptr->device, TRACE_XFER, "Unit %d %s byte %u is %06o\n",
                                  unit, opcode_names [opcode],
                                  cvptr->index, inbound_data);
                        }

                    else {                                                      /* otherwise we transfer full words */
                        for (selector = upper; selector <= lower; selector++) { /*   so unpack the data bytes */
                            if (selector == upper)                              /* if the upper byte is selected */
                                data_byte = UPPER_BYTE (inbound_data);          /*   then get it */
                            else                                                /* otherwise */
                                data_byte = LOWER_BYTE (inbound_data);          /*   get the lower byte */

                            if (opcode == Write_Record_without_Parity           /* if writing without parity */
                              && drive_props [PROP_INDEX (uptr)].bpi <= 800) {  /*   and this is an NRZI drive */
                                if (odd_parity [data_byte])                     /*     then if the parity is wrong */
                                    cvptr->call_status = MTSE_RECE;             /*       then the CRCC will be wrong */

                                if (data_byte == 0)                     /* a zero byte without parity */
                                    continue;                           /*   will be read as a dropout, not data */
                                }                                       /*     which may generate an odd record length */

                            cvptr->buffer [cvptr->index++] = data_byte; /* transfer the byte */
                            cvptr->length = cvptr->length + 1;          /*  and update the buffer length */
                            }

                        uptr->wait = 2 * cvptr->dlyptr->data_xfer;      /* schedule the next word transfer */

                        tpprintf (cvptr->device, TRACE_XFER, "Unit %d %s word %u is %06o\n",
                                  unit, opcode_names [opcode],
                                  (cvptr->index + 1) / 2, inbound_data);
                        }

                    if (inbound_flags & EOD) {                  /* if the transfer is now complete */
                        uptr->Phase = Stop_Phase;               /*   then set up the stop phase processing */
                        uptr->wait = cvptr->dlyptr->ir_start;   /*     after the motion stops */

                        if (cvptr->device->flags & DEV_REALTIME /* if the mode is REALTIME */
                          && cvptr->type == HP_13181) {         /*   for the 13181 controller */
                            add_crcc_lrcc (cvptr, opcode);      /*     then add the CRCC/LRCC to the buffer */

                            outbound =                          /* return the CRCC and LRCC */
                               TO_WORD (cvptr->buffer [cvptr->length + 0],
                                        cvptr->buffer [cvptr->length + 1]);
                            }

                        outbound |= IFOUT;                      /* tell the interface that we have the data */
                        }

                    else                                        /* otherwise tell the interface that */
                        outbound = IFOUT | RQSRV;               /*   we have the data and need more */
                    }
                break;


            case Write_File_Mark:
            case Write_Gap_and_File_Mark:
                outbound =                                  /* write the file (tape) mark */
                   call_tapelib (cvptr, uptr, lib_write_tmk, 0);

                if ((outbound & SCPE) == NO_FLAGS) {        /* if the write succeeded */
                    cvptr->status |= CST_EOF;               /*   then set EOF status */
                    uptr->Phase = Stop_Phase;               /*     and set up the stop phase processing */
                    uptr->wait = cvptr->dlyptr->ir_start;   /*       after the motion stops */
                    }
                break;


            case Forward_Space_Record:
            case Backspace_Record:
                uptr->Phase = Stop_Phase;               /* set up the stop phase processing */
                uptr->wait = cvptr->dlyptr->ir_start;   /*   after the motion stops */
                break;


            case Forward_Space_File:
                if (sim_tape_eot (uptr)                     /* if the EOT was seen */
                  && (cvptr->type == HP_13181               /*   and this is */
                  || cvptr->type == HP_13183)) {            /*     an HP 1000 controller */
                    uptr->Phase = Stop_Phase;               /*       then the command ends */
                    uptr->wait = cvptr->dlyptr->ir_start;   /*         at this record */
                    break;                                  /*           after the motion stops */
                    }

            /* otherwise fall through into the Backspace_File case */

            case Backspace_File:
                uptr->Phase = Start_Phase;                  /* set up to space over the next record */
                uptr->wait = 2 * cvptr->dlyptr->ir_start;   /*   after spacing over the interrecord gap */
                break;


            case Select_Unit_0:                         /* the command is invalid for this state */
            case Select_Unit_1:                         /* the command is invalid for this state */
            case Select_Unit_2:                         /* the command is invalid for this state */
            case Select_Unit_3:                         /* the command is invalid for this state */
            case Clear_Controller:                      /* the command is invalid for this state */
            case Read_Record_Backward:                  /* the command is invalid for this state */
            case Read_File_Forward:                     /* the command is invalid for this state */
            case Write_Gap:                             /* the command is invalid for this state */
            case Rewind:                                /* the command is invalid for this state */
            case Rewind_Offline:                        /* the command is invalid for this state */
            case Invalid_Opcode:                        /* the command is invalid for this state */
                outbound =                              /* abort the command and stop the simulation */
                   abort_command (cvptr, uptr, SCPE_IERR);
                break;
            }                                           /* end of the data phase operation dispatch */

        break;                                          /* end of the data phase handlers */


    case Stop_Phase:
        switch (opcode) {                               /* dispatch the operation command */

            case Clear_Controller:                      /* no additional completion action is required */
                break;


            case Read_Record:
            case Read_Record_with_CRCC:
                if (inbound_flags & OVRUN) {            /* if a data overrun occurred */
                    cvptr->status |= CST_TIMERR;        /*   then indicate a timing error */
                    cvptr->state = Error_State;
                    }

                if (cvptr->type == HP_30215             /* if this is the 3000 controller */
                  && cvptr->index < cvptr->length)      /*   and a partial record was transferred */
                    cvptr->status &= ~CST_ODDLEN;       /*     then the byte count is always even */

                cvptr->length = cvptr->index;           /* set the length to report the amount actually transferred */
                break;


            case Write_Record:
            case Write_Record_without_Parity:
                if (cvptr->length > 0) {                    /* if there is data in the buffer */
                    if (cvptr->call_status == MTSE_RECE)    /*   then if the data is in error */
                        error_flag = MTR_ERF;               /*     then mark the record as bad */
                    else                                    /*   otherwise */
                        error_flag = 0;                     /*     mark the record as good */

                    outbound =                              /* write the record */
                       call_tapelib (cvptr, uptr, lib_write, error_flag);

                    if (inbound_flags & OVRUN) {            /* if a data underrun occurred */
                        cvptr->status |= CST_TIMERR;        /*   then indicate a timing error */
                        cvptr->state = Error_State;
                        }
                    }

                else {                                  /* otherwise the buffer is empty */
                    cvptr->status |= CST_DATAERR;       /*   which reports as a data error */
                    cvptr->state = Error_State;         /*     as it cannot be written */
                    }
                break;


            case Forward_Space_Record:
            case Forward_Space_File:
            case Backspace_Record:
            case Backspace_File:
                if (cvptr->type != HP_30215             /* if this is not the 3000 controller */
                  && cvptr->length & 1)                 /*   and the record length is odd */
                    cvptr->status |= CST_ODDLEN;        /*     then set the corresponding status */

            /* fall through into the Write_File_Mark case */

            case Write_File_Mark:
            case Write_Gap:
                if (cvptr->state == End_State)          /* a BOT or EOF indication */
                    cvptr->state = Busy_State;          /*   is the normal completion for these commands */
                break;                                  /* no additional completion action required */


            case Rewind:
            case Rewind_Offline:
                if ((uptr->flags & UNIT_OFFLINE) == 0)      /* if the unit is online */
                    cvptr->unit_attention |= 1u << unit;    /*   then attention sets on rewind completion */

                uptr->flags &= ~UNIT_REWINDING;         /* clear rewinding status */

                outbound =                              /* rewind the tape (which always succeeds) */
                   call_tapelib (cvptr, uptr, lib_rewind, 0);

                complete = TRUE;                        /* mark the command as complete */
                uptr->Phase = Idle_Phase;               /*   but skip the normal completion action */
                break;


            case Select_Unit_0:                         /* the command is invalid for this state */
            case Select_Unit_1:                         /* the command is invalid for this state */
            case Select_Unit_2:                         /* the command is invalid for this state */
            case Select_Unit_3:                         /* the command is invalid for this state */
            case Read_Record_Backward:                  /* the command is invalid for this state */
            case Read_File_Forward:                     /* the command is invalid for this state */
            case Write_Gap_and_File_Mark:               /* the command is invalid for this state */
            case Invalid_Opcode:                        /* the command is invalid for this state */
                outbound =                              /* abort the command and stop the simulation */
                   abort_command (cvptr, uptr, SCPE_IERR);
                break;
            }                                           /* end of the stop phase operation dispatch */


        if (cvptr->call_status == MTSE_RECE)            /* a bad data record */
            cvptr->state = Error_State;                 /*   is now treated as an error */

        if (uptr->Phase == Stop_Phase) {                /* if the stop completed normally */
            outbound |= end_command (cvptr, uptr);      /*   then terminate the command */
            complete = TRUE;                            /*     and mark it as complete */
            }

        break;                                          /* end of the stop phase handlers */


    case Error_Phase:
        outbound = end_command (cvptr, uptr);           /* end the command with an interrupt */
        break;

    }                                                   /* end of phase dispatching */


if (uptr->wait != NO_EVENT)                             /* if the unit has been scheduled */
    activate_unit (cvptr, uptr);                        /*   then activate it */

if (complete) {                                         /* if the command is complete */
    tpprintf (cvptr->device, TRACE_INCO,               /*   then report the final tape position */
              "Unit %d %s completed at position %" T_ADDR_FMT "u\n",
              unit, opcode_names [opcode], uptr->pos);

    tpprintf (cvptr->device, TRACE_CMD, (cvptr->length > 0
                                            ? "Unit %d %s of %u-byte record %s\n"
                                            : "Unit %d %s %.0u%s\n"),
              unit, opcode_names [opcode], cvptr->length,
              status_name [cvptr->call_status]);
    }

return outbound;                                        /* return the data word and function set */
}


/* End the current command.

   The command executing on the specified unit is ended, and the unit and
   controller are idled.  The set of functions appropriate to the controller
   state is returned to the caller.
*/

static CNTLR_IFN_IBUS end_command (CVPTR cvptr, UNIT *uptr)
{
static const CNTLR_IFN_SET end_functions [] = {         /* indexed by CNTLR_STATE */
    NO_FUNCTIONS,                                       /*   Idle_State  */
    RQSRV | STCFL,                                      /*   Busy_State  */
    DVEND | RQSRV | STCFL,                              /*   End_State   */
    STINT | STCFL                                       /*   Error_State */
    };
CNTLR_IFN_SET outbound;

outbound = end_functions [cvptr->state];                /* get the state-specific end functions */

uptr->Opcode = Invalid_Opcode;                          /* clear the command */
uptr->Phase = Idle_Phase;                               /*   and idle the unit */

cvptr->state = Idle_State;                              /* idle the controller */
cvptr->status &= ~CST_IFBUSY;                           /*   and the interface */

return (CNTLR_IFN_IBUS) outbound;                       /* return the function set */
}


/* Poll the tape drives for drive attention status.

   The controller's drive attention bitmap is checked to determine if any tape
   drive unit is requesting attention.  If so, then the units are scanned in
   ascending order to determine the first unit with a request pending.  That
   unit's flag is cleared, and a Set Drive Attention interface function is
   returned, along with the unit number on the interface data bus.  If no unit
   is requesting attention, the routine returns an empty function set.

   The HP 3000 controller sets drive attention when a Rewind command completes
   or a unit is set online from an offline condition.  The controller must poll
   the drives for attention requests when it is idle and interrupts are allowed
   by the CPU interface.

   HP 1000 controllers do not call this routine.  Rewind completion and
   offline-to-online transitions must be determined by requesting controller
   status.
*/

static CNTLR_IFN_IBUS poll_drives (CVPTR cvptr)
{
int32 unit;

tpprintf (cvptr->device, TRACE_INCO, "Controller polled drives for attention\n");

unit = 0;                                               /* scan the units in numerical order */

while (cvptr->unit_attention)                           /* loop through the attention bits */
    if (cvptr->unit_attention & 1 << unit) {            /*   looking for the first one set */
        cvptr->unit_attention &= ~(1 << unit);          /*     and then clear it */

        tpprintf (cvptr->device, TRACE_INCO, "Unit %u requested attention\n",
                  unit);

        cvptr->unit_selected = unit;                    /* select the unit requesting attention */

        return (CNTLR_IFN_IBUS) (DATTN | unit);         /* return drive attention and the unit number */
        }

    else                                                /* if this unit isn't requesting attention */
        unit = unit + 1;                                /*   then look at the next one */

return NO_ACTION;                                       /* no drives are requesting attention */
}


/* Call a simulator tape support library routine.

   The simulator tape support library routine specified by the "lib_call"
   parameter is called for the controller specified by "cvptr" and the unit
   specified by "uptr".  For the lib_read_fwd and lib_read_rev calls, the
   "parameter" value is the available buffer size in bytes.  For the lib_write
   call, the parameter is 0 to mark the record as good or MTR_ERF to mark it as
   bad.  For the lib_write_gap call, the parameter is the gap length in tenths
   of an inch.  The parameter value is ignored for the other calls.

   After calling the specified routine, the returned status is examined and
   translated into the appropriate controller status values.  Any status other
   than "call succeeded" or "call succeeded but with bad record data" asserts
   the SCPE function and returns the corresponding SCPE status code.
   Recoverable errors, e.g., encountering a tape mark, return SCPE_OK to the
   event service routine to allow simulation to continue.  Fatal errors, e.g., a
   corrupt tape image file, cause simulation stops.  If simulation is resumed by
   the user, the current tape command fails and sets tape error status.

   The caller must test the returned bus value for the SCPE function before
   continuing with the current phase processing.  If it is present, an abort has
   occurred, and the rest of the phase processing should be bypassed, as the
   phase and timing have been set appropriately by this routine.


   Implementation notes:

    1. If the parameter accompanying a lib_write call is MTR_ERF, and the write
       succeeded, then report a read-after-write failure by setting the call
       status explicitly to indicate a bad record.

    2. The simulator tape support library routines skip over erase gaps when
       reading or spacing.  To recover the gap size for use in calculating the
       motion delay, the difference between the initial and final tape
       positions, less the difference corresponding to the returned data record,
       determines the gap size.

    3. The position delta cannot be calculated by taking the absolute value of
       the difference because both position values are of type t_addr, which is
       unsigned and therefore yields an unsigned result.  Moreover, the
       positions cannot be cast to signed values because they may be either 32-
       or 64-bit values, depending on USE_ADDR64.

    4. The calculated "gap length" is actually the rewind distance for the
       Rewind and Rewind_Offline commands.

    5. When a command is started, the current record length is initialized to 0.
       Support library routines that return MTSE_OK or MTSE_RECE may (e.g.,
       sim_tape_rdrecf) or may not (e.g., sim_tape_wrtmk) set the record length.
       Therefore, when processing these two status returns, cvptr->length > 0
       indicates that the command has associated data, whereas cvptr->length = 0
       indicates that no data are present.

    6. The record length value returned by support library routines is not valid
       unless the call succeeds.  Therefore, we set the length to zero
       explicitly for all status returns other than MTSE_OK and MTSE_RECE.

    7. The CNTLR_IFN_IBUS returned value depends on the SCPE error codes fitting
       into the lower 16 bits.

    8. The 30215 controller does not detect tape runaway when spacing in the
       reverse direction.  If the support library "space record reverse" routine
       returns MTSE_RUNAWAY status, the error status is cleared, but the buffer
       length is set to 0 to indicate that the spacing did not encompass a data
       record.  After traversing the gap, the spacing will be retried.

    9. The MTSE_WRP status return shouldn't occur, as write-protection status is
       checked before initiating a write command.  However, a user might detach
       and then reattach a file with read-only status in the middle of the
       command, so we detect and handle this condition.

   10. In hardware, if a tape unit is taken offline while a transfer is in
       progress, the 1000 and 3000 controllers will hang.  They check for Unit
       Ready status at the start of the transfer but then wait in a loop for
       Read Clock or End of Block.  Neither signal will occur if the drive has
       been taken offline.

       In simulation, taking a unit offline with the SET <unit> OFFLINE command
       allows the current command to complete normally before Unit Ready is
       denied.  Taking a unit offline with the DETACH <unit> command causes a
       "Unit not attached" simulator stop.
*/

static CNTLR_IFN_IBUS call_tapelib (CVPTR cvptr, UNIT *uptr, TAPELIB_CALL lib_call, t_mtrlnt parameter)
{
t_bool         do_gap, do_data;
int32          unit;
uint32         gap_inches, gap_tenths;
CNTLR_IFN_IBUS result = (CNTLR_IFN_IBUS) NO_FUNCTIONS;  /* the expected case */

switch (lib_call) {                                     /* dispatch to the selected routine */

    case lib_space_fwd:                                 /* space record forward */
        cvptr->call_status = sim_tape_sprecf (uptr, &cvptr->length);
        break;

    case lib_space_rev:                                 /* space record reverse */
        cvptr->call_status = sim_tape_sprecr (uptr, &cvptr->length);
        break;

    case lib_read_fwd:                                  /* read record forward */
        cvptr->call_status = sim_tape_rdrecf (uptr, cvptr->buffer,
                                              &cvptr->length, parameter);
        break;

    case lib_read_rev:                                  /* read record reverse */
        cvptr->call_status = sim_tape_rdrecr (uptr, cvptr->buffer,
                                              &cvptr->length, parameter);
        break;

    case lib_write:                                     /* write record forward */
        cvptr->call_status = sim_tape_wrrecf (uptr, cvptr->buffer,
                                              parameter | cvptr->length);

        if (parameter && cvptr->call_status == MTSE_OK) /* if the record is bad and the write succeeded */
            cvptr->call_status = MTSE_RECE;             /*   then report a read-after-write failure */
        break;

    case lib_write_gap:                                 /* write erase gap */
        cvptr->call_status = sim_tape_wrgap (uptr, (uint32) parameter);
        break;

    case lib_write_tmk:                                 /* write tape mark */
        cvptr->call_status = sim_tape_wrtmk (uptr);
        break;

    case lib_rewind:                                    /* rewind tape */
        cvptr->call_status = sim_tape_rewind (uptr);
        break;
    }


if (cvptr->initial_position < uptr->pos)                                /* calculate the preliminary gap size */
    cvptr->gaplen = (t_mtrlnt) (uptr->pos - cvptr->initial_position);   /*   for either forward motion */
else                                                                    /*     or */
    cvptr->gaplen = (t_mtrlnt) (cvptr->initial_position - uptr->pos);   /*       for reverse motion */

switch (cvptr->call_status) {                           /* dispatch on the call status */

    case MTSE_RECE:                                     /* record data in error */
        cvptr->status |= CST_DATAERR;                   /* report as a data error */

    /* fall through into the MTSE_OK case */

    case MTSE_OK:                                       /* operation succeeded */
        if (cvptr->length > 0)                          /* if data is present */
            cvptr->gaplen -= (cvptr->length + 1 & ~1)   /*   then reduce the calculated gap length */
                             + 2 * sizeof (t_mtrlnt);   /*     by the rounded record length and marker sizes */
        break;


    case MTSE_TMK:                                      /* tape mark encountered */
        cvptr->gaplen -= sizeof (t_mtrlnt);             /* reduce the gap length by the metadata marker size */

    /* fall through into the MTSE_EOM case */

    case MTSE_EOM:                                      /* end of medium encountered */
        cvptr->status |= CST_EOF;                       /* set the EOF status */

        if (cvptr->type == HP_13181)                    /* the HP 1000 NRZI controller */
            cvptr->status |= CST_ODDLEN;                /*   also sets odd length status for a tape mark */

    /* fall through into the MTSE_BOT case */

    case MTSE_BOT:                                      /* beginning of tape encountered */
        cvptr->state = End_State;                       /* indicate a device end condition */

        if (cvptr->gaplen > 0) {                        /* if a gap is present */
            uptr->Phase = Traverse_Phase;               /*   then traverse it first */
            uptr->wait =                                /*     before stopping */
               cvptr->gaplen * cvptr->dlyptr->data_xfer;
            }

        else {                                          /* otherwise */
            uptr->Phase = Stop_Phase;                   /*   set up the stop phase processing */
            uptr->wait = cvptr->dlyptr->ir_start;       /*     after motion stops */
            }

        cvptr->length = 0;                              /* clear the record length */
        result = SCP_STATUS (SCPE_OK);                  /*   and indicate a recoverable error */
        break;


    case MTSE_RUNAWAY:                                  /* tape runaway */
        if (lib_call == lib_space_rev                   /* the HP 3000 controller does not recognize */
          && cvptr->type == HP_30215)                   /*   tape runaway during reverse motion */
            cvptr->call_status = MTSE_OK;               /*     so ignore it if it's encountered */

        else {                                          /* otherwise */
            cvptr->state = Error_State;                 /*   indicate a terminal error */
            cvptr->status |= CST_RUNAWAY;               /*     and report the cause */

            uptr->Phase = Traverse_Phase;               /* traverse the gap */
            uptr->wait =                                /*   before stopping */
               cvptr->gaplen * cvptr->dlyptr->data_xfer;

            result = SCP_STATUS (SCPE_OK);              /* indicate a recoverable error */
            }

        cvptr->length = 0;                              /* clear the record length */
        break;


    case MTSE_FMT:                                          /* illegal tape image format */
        result = abort_command (cvptr, uptr, SCPE_FMT);     /* abort the command and stop the simulation */
        break;


    case MTSE_UNATT:                                        /* unit unattached */
        tl_detach (uptr);                                   /* resyncronize the attachment status */
        result = abort_command (cvptr, uptr, SCPE_UNATT);   /* abort the command and stop the simulation */
        break;


    case MTSE_INVRL:                                        /* invalid record length */
        result = abort_command (cvptr, uptr, SCPE_MTRLNT);  /* abort the command and stop the simulation */
        break;


    case MTSE_IOERR:                                        /* host system I/O error */
        result = abort_command (cvptr, uptr, SCPE_IOERR);   /* abort the command and stop the simulation */
        break;


    case MTSE_WRP:                                          /* write protected */
        uptr->Status |= UST_WRPROT;                         /* resynchronize the write protection status */
        result = abort_command (cvptr, uptr, SCPE_NORO);    /* abort the command and stop the simulation */
        break;


    default:                                                /* unanticipated error */
        result = abort_command (cvptr, uptr, SCPE_IERR);    /* abort the command and stop the simulation */
        break;
    }                                                   /* end of the call status dispatching */


if (TRACINGP (cvptr->device, TRACE_INCO)) {             /* if tracing is enabled */
    unit = (int32) (uptr - cvptr->device->units);       /*   then get the unit number */

    do_data =                                           /* TRUE if the data record length is valid and present */
       lib_props [lib_call].data_is_valid && cvptr->length > 0;

    do_gap =                                            /* TRUE if the erase gap length is valid and present */
       lib_props [lib_call].gap_is_valid && cvptr->gaplen > 0;

    if (cvptr->gaplen > 0) {                            /* if a gap or rewind spacing exists */
        gap_inches =                                    /*   then calculate the movement in inches */
           cvptr->gaplen / drive_props [PROP_INDEX (uptr)].bpi;
        gap_tenths =                                    /*     and tenths of an inch */
           ((10 * cvptr->gaplen) / drive_props [PROP_INDEX (uptr)].bpi) % 10;
        }

    if (do_gap && do_data)                              /* if both gap and data are present */
        hp_trace (cvptr->device, TRACE_INCO,            /*   then report both objects */
                  "Unit %d %s call of %u.%u-inch erase gap and %u-word record %s\n",
                  unit, lib_props [lib_call].action,
                  gap_inches, gap_tenths,               /*     using the movement calculated above */
                  (cvptr->length + 1) / 2,
                  status_name [cvptr->call_status]);

    else if (do_data)                                   /* otherwise if data only are present */
        hp_trace (cvptr->device, TRACE_INCO,            /*   then report the record length */
                  "Unit %d %s call of %u-word record %s\n",
                  unit, lib_props [lib_call].action,
                  (cvptr->length + 1) / 2,
                  status_name [cvptr->call_status]);

    else if (do_gap)                                    /* otherwise if motion only is present */
        hp_trace (cvptr->device, TRACE_INCO,            /*   then report it */
                  "Unit %d %s call of %u.%u%s %s\n",
                  unit, lib_props [lib_call].action,
                  gap_inches, gap_tenths,
                  (lib_call == lib_rewind ? " inches" : "-inch erase gap"),
                  status_name [cvptr->call_status]);

    else                                                /* otherwise no data was involved */
        hp_trace (cvptr->device, TRACE_INCO,            /*   so just report the event */
                  "Unit %d %s call %s\n",
                  unit, lib_props [lib_call].action,
                  status_name [cvptr->call_status]);
    }

if ((cvptr->device->flags & DEV_REALTIME) == 0)         /* if optimized timing is selected */
    cvptr->gaplen = 0;                                  /*   then the gap traversal phase is omitted */

return result;                                          /* return the function set and data */
}


/* Abort the command.

   The current command is aborted due to a fatal error return from the simulator
   tape support library, e.g., if the tape image file format is corrupt.  As
   this routine is invoked when there is no sensible way of recovering, it sets
   the controller to the error state, sets the controller status to reflect an
   uncorrectable data error, schedules the error phase after a nominal delay,
   and returns the associated SCPE error code to the calling service event
   routine to stop the simulation.  If the user resumes, the error phase will be
   entered, and the program executing under simulation (typically, an operating
   system driver) will receive the data error.  The operation may be retried,
   leading to the same failure, but eventually the simulated program will give
   up and handle the fatal error in an appropriate fashion.
*/

static CNTLR_IFN_IBUS abort_command (CVPTR cvptr, UNIT *uptr, t_stat status)
{
cvptr->state = Error_State;                             /* indicate a fatal error */
cvptr->status |= CST_DATAERR;                           /*   and report the cause as a data error */

uptr->Phase = Error_Phase;                              /* terminate the command */
uptr->wait = cvptr->dlyptr->overhead;                   /*   after a nominal delay */

cvptr->length = 0;                                      /* clear the record length and stop the simulation */
return SCP_STATUS (status);                             /*   with the appropriate console message */
}


/* Reject the command.

   The command attempting to start (if "uptr" is NULL) is rejected, or the
   command currently executing (if "uptr" is not NULL) is aborted.  A busy unit
   is idled, Command Reject status is set, and, if the controller is the HP 3000
   controller, an interrupt request is scheduled after the tape stop delay
   expires.
*/

static void reject_command (CVPTR cvptr, UNIT *uptr)
{
if (uptr)                                               /* if a command is currently executing */
    uptr->Phase = Idle_Phase;                           /*   then idle the tape drive */
else                                                    /* otherwise a command is attempting to start */
    uptr = CNTLR_UPTR;                                  /*   so set up action on the controller unit */

tpprintf (cvptr->device, TRACE_CMD, "%s %s command rejected\n",
          unit_names [uptr - cvptr->device->units], opcode_names [uptr->Opcode]);

cvptr->status = CST_REJECT;                             /* set the Command Reject status */
cvptr->state = Error_State;

if (cvptr->type == HP_30215) {                          /* if this is the 3000 controller */
    uptr = CNTLR_UPTR;                                  /*   then schedule the delay on the controller unit */

    uptr->Phase = Error_Phase;                          /* set up the error phase */
    uptr->wait = cvptr->dlyptr->ir_start;               /*   and schedule the tape stop delay */

    activate_unit (cvptr, uptr);                        /* start the controller delay */
    }

return;
}


/* Add the calculated CRC and LRC characters to the tape record buffer.

   The cyclic redundancy check and longitudinal redundancy check characters
   specified by ANSI X3.22 and ECMA-12 are calculated for the data record
   currently in the buffer and then appended to the end of the buffer after
   padding to an even record length if necessary.

   The CRCC is generated by performing half-adds (XORs) of each record byte plus
   odd parity with an initially-zero accumulator, followed by a 9-bit circular
   right-shift.  For each add, if the LSB of the accumulator is 1, bits 2 to 5
   include a forced-carry (an XOR with the binary constant 000 011 100).  After
   all data bytes have been added, all bits except bits 3 and 5 are inverted by
   a half-add with the binary constant 111 010 111.

   The LRCC is generated by performing half-adds (XORs) of each record byte plus
   odd parity with an initially-zero accumulator, followed by a half-add of the
   calculated CRCC byte.

   If the routine is called on behalf of the Read_Record_with_CRCC command, the
   9-bit CRCC is split and appended to the buffer, and the record length is
   increased appropriately.  Otherwise, the CRCC and LRCC, without their MSBs,
   are stored in the buffer, and the record length is not altered.


   Implementation notes:

    1. The 9-bit circular right-shift is implemented by testing the LSB.  If
       it is zero, then a 16-bit logical right-shift is performed; this is
       equivalent, as the rotated-in MSB would be zero.  If the LSB is one, the
       same 16-bit logical right-shift is performed, but the rotated-in MSB is
       restored by ORing in a preshifted one-bit (this latter action is folded
       into the XOR required to complement the bits 5-2 and relies on the MSB
       being zero after shifting).
*/

static void add_crcc_lrcc (CVPTR cvptr, CNTLR_OPCODE opcode)
{
uint32  index;
HP_WORD byte, crc, lrc;

crc = 0;                                                /* initialize the CRC  */
lrc = 0;                                                /*   and LRC accumulators */

for (index = 0; index < cvptr->length; index++) {       /* for each byte in the data record */
    byte = odd_parity [cvptr->buffer [index]]           /*   merge the calculated odd parity bit */
             | cvptr->buffer [index];                   /*     to reconstruct the 9-bit tape data */

    crc = crc ^ byte;                                   /* add the byte to the accumulators */
    lrc = lrc ^ byte;                                   /*   without carries */

    if (crc & 1)                                        /* perform a 9-bit circular right shift */
        crc = crc >> 1 ^ 0474;                          /*   on the CRC accumulator */
    else                                                /*     while inverting bits 2 through 5 */
        crc = crc >> 1;                                 /*       if the resulting LSB is a one */

    tpprintf (cvptr->device, TRACE_XFER,
              "CRCC/LRCC index = %2d, buffer = %03o, byte = %06o, crc = %06o, lrc = %06o\n",
              index, cvptr->buffer [index], byte, crc, lrc);
    }

crc = crc ^ 0727;                                       /* invert all bits except bits 3 and 5 */
lrc = lrc ^ crc;                                        /* include the CRCC in the LRCC calculation */

index = cvptr->length;                                  /* get the current record length */

if (index & 1)                                          /* if the record length was odd */
    cvptr->buffer [index++] = 0;                        /*   then pad the buffer with a zero byte */

if (opcode == Read_Record_with_CRCC) {                  /* if the CRCC was requested with the record */
    cvptr->buffer [index++] = LOWER_BYTE (crc);         /*   then store the CRCC in the upper byte */
    cvptr->buffer [index++] = crc >> 1 & D8_SIGN;       /*     and the parity bit in the MSB of the lower byte */

    cvptr->length = index;                              /* count the CRCC and LRCC as part of the record */
    }

else {
    cvptr->buffer [index++] = LOWER_BYTE (crc);         /* store the CRCC and LRCC into the buffer */
    cvptr->buffer [index++] = LOWER_BYTE (lrc);         /*   without altering the valid length */
    }

return;
}



/* Tape library local utility routines */


/* Activate the unit.

   The specified unit is activated using the unit's "wait" time.  If tracing
   is enabled, the activation is logged to the debug file.
*/

static void activate_unit (CVPTR cvptr, UNIT *uptr)
{
tpprintf (cvptr->device, TRACE_STATE, "%s %s %s phase delay %d service scheduled\n",
          unit_names [uptr - cvptr->device->units],
          opcode_names [uptr->Opcode], phase_names [uptr->Phase],
          uptr->wait);

sim_activate (uptr, uptr->wait);                        /* activate the unit */
uptr->wait = NO_EVENT;                                  /*   and clear the activation time */

return;
}


/* Validate a drive model or density change.

   The tape drive model "new_drive" and density setting "new_bpi" of the unit
   specified by "uptr" are validated against the drives and densities supported
   by the controller type indicated by "cvptr".  If the controller supports the
   model at the density specified, the unit's property index field is set to
   reference the corresponding entry in the drive properties table, the
   simulator tape support library is notified of the density change, and the
   routine returns SCPE_OK to indicate success.  If the controller does not
   support the model, SCPE_ARG is returned to indicate failure.

   If "new_bpi" is zero, then validation does not consider the drive density,
   and the first matching property entry, which should specify the preferred
   default for selectable-density drives, is used.
*/

static t_stat validate_drive (CVPTR cvptr, UNIT *uptr, DRIVE_TYPE new_drive, uint32 new_bpi)
{
const CNTLR_TYPE ctype = cvptr->type;                   /* the controller type */
uint32 entry;

for (entry = 0; entry < PROPS_COUNT; entry++)           /* check each property table entry for a match */
    if (drive_props [entry].controller == ctype         /* if this is our controller */
      && drive_props [entry].drive == new_drive         /*   and the model is supported */
      && (new_bpi == 0                                  /*   and the density is not specified */
      || drive_props [entry].bpi == new_bpi)) {         /*     or the density is supported */
        uptr->Prop = uptr->Prop & ~PROP_INDEX_MASK      /*   then set the unit's property table index */
                       | entry << PROP_INDEX_SHIFT;

        sim_tape_set_dens (uptr,                        /* tell the tape library the density in use */
                           drive_props [entry].density,
                           NULL, NULL);

        if (drive_props [entry].bpi == 1600)            /* if the drive is a 1600-bpi unit */
            uptr->Status |= UST_DEN1600;                /*   then set the density bit in the status */
        else                                            /* otherwise */
            uptr->Status &= ~UST_DEN1600;               /*   clear it */

        return SCPE_OK;                                 /* allow the change */
        }

return SCPE_ARG;                                        /* if validation fails then reject the change */
}
