/* hp3000_ds.c: HP 3000 30229B Cartridge Disc Interface simulator

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

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

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

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

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

   DS           HP 30229B Cartridge Disc Interface

   26-Mar-23    JDB     Cleaned up unused macro
   06-Sep-22    JDB     Renamed CNTLR_INTRF to IOP_INTERFACE; updated DIB declaration
   26-Oct-21    JDB     Renamed (DL_)DEB_xxx trace flags to TRACE_xxx
                        Changed DS address radix from octal to decimal
   01-Apr-21    JDB     Removed REG_FIT from register definitions (obsolete)
   09-Dec-19    JDB     Replaced debugging macros with tracing macros
   14-Mar-19    JDB     ATTACH -N now creates a full-size disc image
   27-Dec-18    JDB     Revised fall through comments to comply with gcc 7
   05-Sep-17    JDB     Changed REG_A (permit any symbolic override) to REG_X
   12-Sep-16    JDB     Changed DIB register macro usage from SRDATA to DIB_REG
   09-Jun-16    JDB     Added casts for ptrdiff_t to int32 values
   08-Jun-16    JDB     Corrected %d format to %u for unsigned values
   16-May-16    JDB     Fixed interrupt mask setting
   13-May-16    JDB     Modified for revised SCP API function parameter types
   24-Mar-16    JDB     Changed the buffer element type from uint16 to DL_BUFFER
   21-Mar-16    JDB     Changed uint16 types to HP_WORD
   21-Jul-15    JDB     First release version
   15-Jun-15    JDB     Passes the cartridge disc diagnostic (D419A)
   15-Feb-15    JDB     Created

   References:
     - Model 7905A Cartridge Disc Subsystem Installation and Service Manual
         (30129-90003, May 1976)
     - Stand-Alone HP 30129A (7905A) Disc Cartridge Diagnostic
         (30129-90007, February 1976)
     - HP 3000 Series III Engineering Diagrams Set
         (30000-90141, April 1980)
     - 13037 Disc Controller Technical Information Package
         (13037-90902, August 1980)
     - 7925D Disc Drive Service Manual
         (07925-90913, April 1984)


   The HP 30129A Cartridge Disc Subsystem connects the 7905A, 7906A, 7920A, and
   7925A disc drives to the HP 3000.  The subsystem consists of a 30229B
   Cartridge Disc Interface, a 13037D Multiple-Access Disc Controller ("MAC"),
   and from one to eight MAC drives.  The subsystem uses the Selector Channel to
   achieve a 937.5 KB/second transfer rate to the CPU.

   The disc controller connects from one to eight HP 7905 (15 MB), 7906 (20 MB),
   7920 (50 MB), or 7925 (120 MB) disc drives to interfaces installed in from
   one to eight CPUs.  The drives use a common command set and present data to
   the controller synchronously at a 468.75 kiloword per second (2.133
   microseconds per word) data rate.

   The disc interface is used to connect the HP 3000 CPU to the 13037's device
   controller.  While the controller supports multiple-CPU systems, the HP 3000
   does not use this capability.

   This module simulates a 30229B interface connected to a 13037D controller;
   the controller simulation is provided by the hp_disclib module.  From one to
   eight drives may be connected, and drive types may be freely intermixed.  A
   unit that is enabled but not attached appears to be a connected drive that
   does not have a disc pack in place.  A unit that is disabled appears to be
   disconnected.  An extra unit for the use of the disc controller library is
   also allocated.

   In hardware, the controller runs continuously in one of three states: in the
   Poll Loop (idle state), in the Command Wait Loop (wait state), or in command
   execution (busy state).  In simulation, the controller is run only when a
   command is executing or when a transition into or out of the two loops might
   occur.  Internally, the controller handles these transitions:

     - when a command other than End terminates (busy => wait)
     - when the End command terminates (busy => idle)
     - when a command timeout occurs (wait => idle)
     - when a parameter timeout occurs (busy => idle)
     - when a seek completes (if idle, and interrupts are enabled: idle => wait)

   The interface must call the controller library to handle these transitions:

     - when a command is received from the CPU (idle or wait => busy)
     - when interrupts are enabled (if idle and drive Attention, idle => wait)

   In addition, each transition to the wait state must check for a pending
   command, and each transition to the idle state must check for both a pending
   command and a drive with Attention status asserted.

   While the controller is in the busy state, command execution is broken up
   into a series of phases.  Phase transitions are scheduled on the drive units
   for commands that access the drives and on the controller unit otherwise.
   The interface unit service routine must call the disc controller to inform it
   of these events.


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

   Control Word Format (CIO):

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

   Where:

     M = master reset
     R = reset interrupts
     T = test mode

   Test mode inhibits the flag bus signals.  This allows the diagnostic to
   exercise the interface without causing the disc controller to react.


   Control Word Format (SIO Control):

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

   Where:

     W = set the Wait flip-flop

   The command opcode is the disc controller command to execute.  If the command
   takes or returns parameters, an SIO Write or Read must follow the Control
   order to supply or receive them.


   Status Word Format (TIO and SIO Status):

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | T | I |termination status | -   -   -   - |  unit number  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = SIO OK
     T = test mode is enabled (also DIO OK)
     I = interrupt request

   The termination status and unit number report the success or failure of the
   last disc controller command.  Also, note that the test mode flip-flop output
   is reported as DIO OK.  This means that RIO and WIO are inhibited unless test
   mode is set.


   Output Data Word Format (WIO and SIO Write):

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


   Input Data Word Format (RIO and SIO Read):

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


   Disc read or write commands may transfer up to 4K words with a single SIO
   Read or Write order.  Chained orders are necessary if longer transfers are
   required.

   The interface allows the channel to retry a failed transfer without CPU
   intervention.  The controller asserts the DVEND signal for transfer errors
   that it considers retryable (e.g., a disc read error).  A channel program can
   detect this condition via a Conditional Jump order, which will succeed for
   each retryable failure until the retry count expires.

   Unusually among HP 3000 interfaces, this device reacts to the PFWARN signal.
   A pending power failure will abort the current disc transfer and channel
   program, so that the operating system will know to retry the transfer once
   power has been restored.


   Implementation notes:

    1. As only a single interface connected to the disc controller is supported,
       the interface select address jumpers are not simulated.  Instead, the
       interface behaves as though it is always selected and does not process
       the SELIF and DSCIF functions from the controller.

    2. In hardware, jumper W1 selects whether the interface should assert the
       CLEAR signal to the disc controller when the interface is preset.  This
       jumper is needed in a multiple-interface system so that only one
       interface clears the controller.  The simulation does check the state of
       jumper W1, but as only a single interface is supported, the jumper
       position is hard-coded as ENABLED rather than being configurable via the
       user interface.

    3. Several of the hardware flip-flops that directly drive flag signals to
       the controller are modeled in simulation by setting and clearing the
       corresponding bits in the flags word itself.

    4. The simulation provides REALTIME and FASTTIME options.  FASTTIME settings
       may be altered via the register interface.  Performing a power-on reset
       (RESET -P) will restore the original FASTTIME values.

    5. This simulation provides diagnostic override settings to allow complete
       testing coverage via the offline disc diagnostic.  See the comments in
       the disc controller library for details of this capability.
*/



#include "hp3000_defs.h"
#include "hp3000_io.h"
#include "hp_disclib.h"



/* Program constants */

#define DRIVE_COUNT         (DL_MAXDRIVE + 1)           /* number of disc drive units */
#define UNIT_COUNT          (DRIVE_COUNT + DL_AUXUNITS) /* total number of units */

#define ds_cntlr            ds_unit [DL_MAXDRIVE + 1]   /* controller unit alias */

#define OVERRIDE_COUNT      50                          /* count of diagnostic override entries */

#define PRESET_ENABLE       TRUE                        /* Preset Jumper (W1) is enabled */

#define UNUSED_COMMANDS     (BUSY | DSCIF | SELIF | IFPRF | STDFL | FREE)   /* unused disc interface commands */


/* Control word.

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

#define CN_MR               0100000u            /* (M) master reset */
#define CN_RIN              0040000u            /* (R) reset interrupt */
#define CN_TEST             0020000u            /* (T) test mode */

#define CN_WAIT             0000001u            /* (W) wait for data */

#define CN_OPCODE_MASK      0017400u            /* command word opcode mask */

#define CN_OPCODE_SHIFT     8                   /* controller opcode alignment shift */

#define CN_OPCODE(c)        ((CNTLR_OPCODE) (((c) & CN_OPCODE_MASK) >> CN_OPCODE_SHIFT))

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

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


/* Status word.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | S | T | I |termination status | -   -   -   - |  unit number  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define ST_SIO_OK           0100000u            /* (S) SIO OK to use */
#define ST_TEST             0040000u            /* (T) test mode enabled */
#define ST_INTREQ           0020000u            /* (I) interrupt requested */
#define ST_STATUS_MASK      0017400u            /* encoded termination status mask */
#define ST_UNIT_MASK        0000017u            /* unit number mask */

#define ST_MASK             ~(ST_SIO_OK | ST_TEST | ST_INTREQ)

#define ST_STATUS_SHIFT     8                   /* termination status alignment shift */
#define ST_UNIT_SHIFT       0                   /* unit number alignment shift */

#define ST_TO_UNIT(s)       (((s) & ST_UNIT_MASK) >> ST_UNIT_SHIFT)
#define ST_TO_STATUS(s)     (CNTLR_STATUS) (((s) & ST_STATUS_MASK) >> ST_STATUS_SHIFT)


static const BITSET_NAME status_names [] = {    /* Status word names */
    "SIO OK",                                   /*   bit  0 */
    "test mode",                                /*   bit  1 */
    "interrupt"                                 /*   bit  2 */
    };

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


/* Disc controller library data structures */

#define DS_SEEK_ONE         uS (25)             /* track-to-track seek time */
#define DS_SEEK_FULL        uS (250)            /* full-stroke seek time */
#define DS_SECTOR_FULL      uS (50)             /* full sector rotation time */
#define DS_DATA_XFER        uS (1)              /* data transfer response time */
#define DS_ISG              uS (25)             /* intersector gap rotation time */
#define DS_OVERHEAD         uS (25)             /* controller execution overhead */

static DELAY_PROPS fast_times =                 /* FASTTIME delays */
    { DELAY_INIT (DS_SEEK_ONE,    DS_SEEK_FULL,
                  DS_SECTOR_FULL, DS_DATA_XFER,
                  DS_ISG,         DS_OVERHEAD) } ;

static DIAG_ENTRY overrides [OVERRIDE_COUNT] = { /* diagnostic overrides array */
    { DL_OVEND }
    };


/* Interface state */

static FLIP_FLOP sio_busy       = CLEAR;        /* SIO busy flip-flop */
static FLIP_FLOP device_sr      = CLEAR;        /* device service request flip-flop */
static FLIP_FLOP input_xfer     = CLEAR;        /* input transfer flip-flop */
static FLIP_FLOP output_xfer    = CLEAR;        /* output transfer flip-flop */
static FLIP_FLOP interrupt_mask = SET;          /* interrupt mask flip-flop */
static FLIP_FLOP jump_met       = CLEAR;        /* jump met flip-flop */
static FLIP_FLOP device_end     = CLEAR;        /* device end flip-flop */
static FLIP_FLOP data_overrun   = CLEAR;        /* data overrun flip-flop */
static FLIP_FLOP end_of_data    = CLEAR;        /* end of data flip-flop */
static FLIP_FLOP test_mode      = CLEAR;        /* test mode flip-flop */
static FLIP_FLOP data_wait      = CLEAR;        /* wait flip-flop */

static HP_WORD        status_word   = 0;        /* status register */
static HP_WORD        buffer_word   = 0;        /* data buffer register */
static HP_WORD        retry_counter = 0;        /* retry counter */
static CNTLR_FLAG_SET flags         = NO_FLAGS; /* disc controller interface flag set */

static DL_BUFFER      buffer [DL_BUFSIZE];      /* command/status/sector buffer */

DEVICE ds_dev;                                  /* incomplete device structure */

static CNTLR_VARS mac_cntlr =                   /* MAC controller */
    { CNTLR_INIT (MAC, ds_dev, buffer, overrides, fast_times) };


/* Interface local SCP support routines */

static IOP_INTERFACE ds_interface;
static t_stat        ds_service     (UNIT   *uptr);
static t_stat        ds_reset       (DEVICE *dptr);
static t_stat        ds_boot        (int32  unit_number, DEVICE *dptr);
static t_stat        ds_attach      (UNIT   *uptr, char *cptr);
static t_stat        ds_detach      (UNIT   *uptr);
static t_stat        ds_load_unload (UNIT   *uptr, int32 value, char *cptr, void *desc);


/* Interface local utility routines */

static void master_reset          (void);
static void deny_sio_busy         (void);
static void clear_interface_logic (DIB  *dibptr);
static void call_controller       (UNIT *uptr);


/* Interface SCP data structures */


/* Device information block */

static DIB ds_dib = {
    &ds_interface,                              /* the device's IOP interface function pointer */
    NULL,                                       /* the execution initialization function pointer */
    4,                                          /* the device number */
    SRNO_UNUSED,                                /* the service request number */
    4,                                          /* the interrupt priority */
    INTMASK_E,                                  /* the interrupt mask */
    0,                                          /* the card index */
    CLEAR,                                      /* the interrupt request flip-flop */
    CLEAR,                                      /* the interrupt active flip-flop */
    FALSE                                       /* TRUE if service has been requested */
    };

/* Unit list */

#define UNIT_FLAGS          (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD)

static UNIT ds_unit [UNIT_COUNT] = {
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 0 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 1 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 2 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 3 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 4 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 5 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 6 */
    { UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) },   /* drive unit 7 */
    { UDATA (&ds_service, UNIT_DIS,               0)          }    /* controller unit */
    };

/* Register list */

static REG ds_reg [] = {
/*    Macro   Name    Location         Width  Offset       Flags       */
/*    ------  ------  ---------------  -----  ------  ---------------- */
    { FLDATA (SIOBSY, sio_busy,                  0)                    },
    { FLDATA (DEVSR,  device_sr,                 0)                    },
    { FLDATA (INXFR,  input_xfer,                0)                    },
    { FLDATA (OUTXFR, output_xfer,               0)                    },
    { FLDATA (INTMSK, interrupt_mask,            0)                    },
    { FLDATA (JMPMET, jump_met,                  0)                    },
    { FLDATA (DEVEND, device_end,                0)                    },

    { FLDATA (DATOVR, data_overrun,              0)                    },
    { FLDATA (ENDDAT, end_of_data,               0)                    },
    { FLDATA (TEST,   test_mode,                 0)                    },
    { FLDATA (WAIT,   data_wait,                 0)                    },

    { FLDATA (CLEAR,  flags,                     0)                    },
    { FLDATA (CMRDY,  flags,                     1)                    },
    { FLDATA (DTRDY,  flags,                     2)                    },
    { FLDATA (EOD,    flags,                     3)                    },
    { FLDATA (INTOK,  flags,                     4)                    },
    { FLDATA (OVRUN,  flags,                     5)                    },
    { FLDATA (XFRNG,  flags,                     6)                    },

    { ORDATA (BUFFER, buffer_word,      16),          REG_X | PV_RZRO },
    { ORDATA (STATUS, status_word,      16),                  PV_RZRO },
    { DRDATA (RETRY,  retry_counter,     4),                  PV_LEFT },

    { SVDATA (DIAG,   overrides)                                      },

      DIB_REGS (ds_dib),

      DL_REGS (mac_cntlr, ds_unit, UNIT_COUNT, buffer, fast_times),

    { NULL }
    };

/* Modifier list */

static MTAB ds_mod [] = {

    DL_MODS (mac_cntlr, ds_load_unload, OVERRIDE_COUNT),

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

/* Debugging trace list */

static DEBTAB ds_deb [] = {
    { "CMD",   TRACE_CMD   },                   /* controller commands */
    { "INCO",  TRACE_INCO  },                   /* controller command initiations and completions */
    { "CSRW",  TRACE_CSRW  },                   /* interface control, status, read, and write actions */
    { "STATE", TRACE_STATE },                   /* controller execution state changes */
    { "SERV",  TRACE_SERV  },                   /* controller unit service scheduling calls */
    { "XFER",  TRACE_XFER  },                   /* controller data reads and writes */
    { "IOBUS", TRACE_IOBUS },                   /* interface and controller I/O bus signals and data words */
    { NULL,    0           }
    };

/* Device descriptor */

DEVICE ds_dev = {
    "DS",                                       /* device name */
    ds_unit,                                    /* unit array */
    ds_reg,                                     /* register array */
    ds_mod,                                     /* modifier array */
    UNIT_COUNT,                                 /* number of units */
    10,                                         /* address radix */
    27,                                         /* address width = 128 MB */
    1,                                          /* address increment */
    8,                                          /* data radix */
    16,                                         /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &ds_reset,                                  /* reset routine */
    &ds_boot,                                   /* boot routine */
    &ds_attach,                                 /* attach routine */
    &ds_detach,                                 /* detach routine */
    &ds_dib,                                    /* device information block */
    DEV_DEBUG | DEV_DISABLE,                    /* device flags */
    0,                                          /* debug control flags */
    ds_deb,                                     /* debug flag name table */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* Interface local SCP support routines */



/* Disc controller interface.

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

   MAC disc controller commands take from 0 to 2 parameters and return from 0 to
   7 status words.  All communication with the disc controller is via programmed
   I/O.  Direct I/O is used only to communicate with the interface.

   Commands consist of a Control I/O order optionally followed by a one- or
   two-word Write order to supply the parameters.  Commands that return status
   consist of a Control order followed by a Read order to send the status.
   Controller command opcodes are carried in the IOAW of a programmed I/O
   Control order.  The IOCW is not used, except for bit 15, which is clocked
   into the WAIT flip-flop.  This bit must be set for commands that return
   parameters (Request Status, Request Sector Address, Request Syndrome, and
   Request Disc Address) to hold off the controller until the channel has
   executed a Read I/O order.  Setting WAIT asserts DTRDY unconditionally; the
   controller will not send a word to the CPU until DTRDY denies, indicating
   that the interface data buffer is empty and ready to receive the word.


   Implementation notes:

    1. In hardware, the disc controller executes a status command, such as
       Request Status, by first asserting IFGTC to clear the command from the
       interface and then asserting IFIN to tell the interface that the (first)
       status word is ready for pickup.  Both IFGTC and IFIN assert CHANSR to
       the channel; the first completes the Control I/O order, and the second
       completes the TOGGLEINXFER phase of the Read I/O order.  Simulating this
       sequential assertion requires two calls to the controller.  The second
       call is placed the TOGGLEINXFER handler, although in hardware this signal
       has no effect on the controller state.

    2. In hardware, the PREADSTB and PWRITESTB signals each toggle the Data
       Ready flip-flop, rather than explicitly clearing and setting it,
       respectively.  The simulation maintains this action.

    3. In hardware, three serially connected End of Data flip-flops are
       employed.  The first presets on EOT, the second clocks on the leading
       edge of TOGGLEXFER, and the third clocks when the Data Ready flip-flop
       clears.  The output of the third drives the EOD line to the disc
       controller.  For a read, DTRDY denies when the trailing edge of PREADSTB
       clocks the third flip-flop.  For this to work, the implied relationship
       is EOT asserts before the leading edge of TOGGLEINXFER, which asserts
       before the trailing edge of PREADSTB.

       In simulation, PREADSTB is processed before EOT, which is processed
       before TOGGLEINXFER, which is the order of the leading edges of the
       hardware signals.  To simulate clocking the Data Ready flip-flop on the
       trailing edge, the action is performed in the TOGGLEINXFER handler
       instead of the PREADSTB handler.

    4. In hardware, the Device SR 1 flip-flop is cleared by assertion of the
       PCONTSTB or PWRITESTB signals, and the the Device SR 2 flip-flop is
       cleared by assertion of CHANSO without DEVEND or by the clear output of
       the SIO busy flip-flop.  Also, DEVEND forces CHANSR assertion.  In
       simulation, a unified device_sr flip-flop is employed that is cleared if
       CHANSO is asserted or SIO Busy is clear.

    5. When TOGGLESIOOK clears the sio_busy flip-flop, the controller must be
       called to poll the drives for attention.  Consider an SIO program that
       does a Seek on drive 0, followed by a Read on drive 1, followed by an
       End.  If the seek completes during the read, the Drive Attention
       interrupt won't occur after the End unless the drive is polled from the
       TOGGLESIOOK handler, as INTOK isn't asserted until the channel program
       ends.

    6. Receipt of a DRESETINT signal clears the interrupt request and active
       flip-flops but does not cancel a request pending but not yet serviced by
       the IOP.  However, when the IOP does service the request by asserting
       INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel
       the request.
*/

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

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

if (inbound_signals & CHANSO || sio_busy == CLEAR)      /* if a PIO signal is asserted or SIO is inactive */
    device_sr = CLEAR;                                  /*   then clear the device SR flip-flop */

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

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

        case SETINT:
        case DSETINT:
            dibptr->interrupt_request = SET;            /* request an interrupt */

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


        case DRESETINT:
            dibptr->interrupt_active = CLEAR;           /* reset the interrupt active flip-flop */
            break;


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

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


        case DCONTSTB:
            tprintf (ds_dev, TRACE_CSRW, "Control is %s\n",
                     fmt_bitset (inbound_value, control_format));

            if (inbound_value & CN_MR)                  /* if the master reset bit is set */
                master_reset ();                        /*   then reset the interface */

            if (inbound_value & CN_RIN)                 /* if the reset interrupt bit is set */
                dibptr->interrupt_request = CLEAR;      /*   then clear the interrupt request */

            test_mode = D_FF (inbound_value & CN_TEST); /* set the test mode flip-flop from the test bit */
            break;


        case PSTATSTB:
        case DSTATSTB:
            outbound_value = status_word;               /* get the controller status */

            if (sio_busy == CLEAR && sel_is_idle)       /* if the interface and channel are inactive */
                outbound_value |= ST_SIO_OK;            /*   then add the SIO OK status bit */

            if (test_mode == SET)                       /* if test mode is enabled */
                outbound_value |= ST_TEST;              /*   then add the DIO OK status bit */

            if (dibptr->interrupt_request == SET)       /* if an interrupt request is pending */
                outbound_value |= ST_INTREQ;            /*   then add the IRQ status bit */

            tprintf (ds_dev, TRACE_CSRW, "Status is %s%s | unit %u\n",
                     fmt_bitset (outbound_value, status_format),
                     dl_status_name (ST_TO_STATUS (outbound_value)),
                     ST_TO_UNIT (outbound_value));
            break;


        case DREADSTB:
            outbound_value = buffer_word;               /* return the data buffer register value */

            tprintf (ds_dev, TRACE_CSRW, "Buffer value %06o returned\n",
                     outbound_value);
            break;


        case DWRITESTB:
            tprintf (ds_dev, TRACE_CSRW, "Buffer value %06o set\n",
                     inbound_value);

            buffer_word = inbound_value;                /* set the data buffer register value */
            break;


        case DSTARTIO:
            tprintf (ds_dev, TRACE_CSRW, "Channel program started\n");

            sio_busy = SET;                             /* set the SIO busy flip-flop */
            flags &= ~INTOK;                            /*   and clear the interrupt OK flag */

            sel_assert_REQ (dibptr);                    /* request the channel */
            break;


        case TOGGLESIOOK:
            TOGGLE (sio_busy);                          /* set or clear the SIO busy flip-flop */

            if (sio_busy == CLEAR) {                    /* if the flip-flop was cleared */
                deny_sio_busy ();                       /*   then reset the associated devices */

                tprintf (ds_dev, TRACE_CSRW, "Channel program ended\n");

                call_controller (NULL);                 /* check for drive attention held off by INTOK denied */
                }
            break;


        case TOGGLEINXFER:
            TOGGLE (input_xfer);                        /* set or clear the input transfer flip-flop */

            if (input_xfer == SET)                      /* if the transfer is starting */
                call_controller (NULL);                 /*   then let the controller know to output the first word */

            else if (end_of_data == SET)                /* otherwise if EOT is asserted */
                flags |= EOD;                           /*   then PREADSTB has cleared DTRDY */
            break;


        case TOGGLEOUTXFER:
            TOGGLE (output_xfer);                       /* set or clear the output transfer flip-flop */

            if (output_xfer == SET)                     /* if the transfer is starting */
                device_sr = SET;                        /*   then request the first word from the channel */
            break;


        case PCMD1:
            data_wait = D_FF (inbound_value & CN_WAIT); /* set the wait flip-flop from the supplied value */

            if (data_wait == SET)                       /* if the wait flip-flip is set */
              flags |= DTRDY;                           /*   then the data ready flag is forced true */

            device_sr = SET;                            /* request the second control word */

            tprintf (ds_dev, TRACE_CSRW, "Control is %s wait\n",
                     (data_wait == SET ? "set" : "clear"));
            break;


        case PCONTSTB:
            tprintf (ds_dev, TRACE_CSRW, "Control is %06o (%s)\n",
                     inbound_value, dl_opcode_name (MAC, CN_OPCODE (inbound_value)));

            buffer_word = inbound_value;                /* store the command in the data buffer register */
            flags |= CMRDY;                             /*   and set the command ready flag */

            call_controller (NULL);                     /* tell the controller to start the command */
            break;


        case PREADSTB:
            outbound_value = buffer_word;               /* return the data buffer register value */
            flags ^= DTRDY;                             /*   and toggle (clear) the data ready flag */

            call_controller (NULL);                     /* tell the controller that the buffer is empty */
            break;


        case PWRITESTB:
            buffer_word = inbound_value;                /* save the word to write */
            flags ^= DTRDY;                             /*   and toggle (set) the data ready flag */

            if (inbound_signals & TOGGLEOUTXFER)        /* EOT asserted with TOGGLEOUTXFER */
                end_of_data = SET;                      /*   sets the End of Data flip-flop */

            call_controller (NULL);                     /* tell the controller that the buffer is full */
            break;


        case EOT:
            if (inbound_signals & TOGGLEINXFER)         /* EOT asserted with TOGGLEINXFER */
                end_of_data = SET;                      /*   sets the End of Data flip-flop */
            break;


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

                outbound_signals |= INTACK;             /* acknowledge the interrupt */
                outbound_value = dibptr->device_number; /*   and return our device number */
                }
            else                                        /* otherwise the request has been reset */
                outbound_signals |= INTPOLLOUT;         /*   so let the IOP know to cancel it */
            break;


        case XFERERROR:
        case PFWARN:
            tprintf (ds_dev, TRACE_CSRW, "Channel program aborted\n");

            flags |= XFRNG;                             /* set the transfer error flag */
            clear_interface_logic (dibptr);             /*   and clear the interface to abort the transfer */
            break;


        case SETJMP:
            if (jump_met == SET)                        /* if the jump met flip-flop is set */
                outbound_signals |= JMPMET;             /*   then assert the JMPMET signal */

            jump_met = CLEAR;                           /* reset the flip-flop */
            break;


        case CHANSO:
            if (device_end == SET) {                        /* if the device end flip-flop is set */
                outbound_signals |= DEVEND | CHANSR;        /*   then assert DEVEND and CHANSR to the channel */

                device_end = D_FF (input_xfer | output_xfer);   /* clear device end if the transfer has stopped */
                }

            else if (device_sr == SET || test_mode == SET)  /* if the interface requests service */
                outbound_signals |= CHANSR;                 /*   then assert CHANSR to the channel */

            outbound_signals |= CHANACK;                    /* assert CHANACK to acknowledge the signal */
            break;


        case READNEXTWD:                                /* not used by this interface */
        case ACKSR:                                     /* not used by this interface */
        case DEVNODB:                                   /* not used by this interface */
        case TOGGLESR:                                  /* not used by this interface */
            break;
        }

    IOCLEARSIG (working_set, signal);                   /* remove the current signal from the set */
    }

tprintf (ds_dev, TRACE_IOBUS, "Returned data %06o with signals %s\n",
         outbound_value, fmt_bitset (outbound_signals, outbound_format));

return IORETURN (outbound_signals, outbound_value);     /* return the outbound signals and value */
}


/* Service a controller or drive unit.

   The service routine is called to execute scheduled controller command phases
   for the specified unit.  The actions to be taken depend on the current state
   of the controller and the drive unit.

   This routine is entered for three general reasons:

    1. A disc unit is ready to execute the next command phase.

    2. The controller unit is ready to execute the next command phase.

    3. The controller unit has timed out while waiting for a new command.

   Generally, the controller library handles all of the disc operations.  All
   that is necessary is to notify the controller, which will process the next
   phase of command execution.  Because the controller can overlap operations,
   in particular scheduling seeks on several drive units simultaneously, each
   drive unit carries its own current operation code and execution phase.  The
   controller uses these to determine what to do next.
*/

static t_stat ds_service (UNIT *uptr)
{
tprintf (ds_dev, TRACE_SERV, (uptr == &ds_cntlr
                                 ? "Controller unit service entered\n"
                                 : "Unit %d service entered\n"),
         (int32) (uptr - &ds_unit [0]));

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

if (device_sr == SET)                                   /* if the interface requests service */
    sel_assert_CHANSR (&ds_dib);                        /*   then assert CHANSR to the channel */

return SCPE_OK;
}


/* Device reset routine.

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

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

static t_stat ds_reset (DEVICE *dptr)
{
master_reset ();                                        /* perform a master reset */

if (sim_switches & SWMASK ('P')) {                      /* if this is a power-on reset */
    fast_times.seek_one        = DS_SEEK_ONE;           /*   then reset the track-to-track seek time, */
    fast_times.seek_full       = DS_SEEK_FULL;          /*     the full-stroke seek time, */
    fast_times.sector_full     = DS_SECTOR_FULL;        /*       the full-sector rotation time, */
    fast_times.data_xfer       = DS_DATA_XFER;          /*         the per-word data transfer time, */
    fast_times.intersector_gap = DS_ISG;                /*           the intersector gap time, */
    fast_times.overhead        = DS_OVERHEAD;           /*             and the controller execution overhead */
    }

return SCPE_OK;
}


/* Device boot routine.

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

   For this interface, the switch register is set to %0000nn, where "nn"
   is the current disc interface device number, which defaults to 4.  The
   control byte is 0 (Cold Load Read).

   The cold load procedure always uses unit 0.
*/

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

else {                                                  /* otherwise */
    cpu_front_panel (TO_WORD (Cold_Load_Read,           /*   set up the cold load */
                              ds_dib.device_number),    /*     from disc unit 0 */
                    Cold_Load);

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


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

   The specified file is attached to the indicated drive unit.  This is the
   simulation equivalent to inserting a disc pack into the drive and setting
   the RUN/STOP switch to RUN, which will load the heads and set the First
   Status and Attention bits in the drive status.

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

   If a new file is specified, the file is initialized to its capacity by
   writing a zero to the last byte in the file.


   Implementation notes:

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

    2. The C standard says, "A binary stream need not meaningfully support fseek
       calls with a whence value of SEEK_END," so instead we determine the
       offset from the start of the file to the last byte and seek there.
*/

static t_stat ds_attach (UNIT *uptr, char *cptr)
{
t_stat      result;
t_addr      offset;
const uint8 zero = 0;

result = dl_attach (&mac_cntlr, uptr, cptr);            /* attach the drive */

if (result == SCPE_INCOMP) {                            /* if the controller must be called before returning */
    call_controller (NULL);                             /*   then let it know to poll the drives */
    result = SCPE_OK;                                   /*     before returning with success */
    }

if (result == SCPE_OK && sim_switches & SWMASK ('N')) { /* if the attach was successful and this is a new image */
    offset = (t_addr)                                   /*   then determine the offset of */
      (uptr->capac * sizeof (int16) - sizeof zero);     /*     the last byte in a full-sized file */

    if (sim_fseek (uptr->fileref, offset, SEEK_SET) != 0    /* seek to the last byte */
      || fwrite (&zero, sizeof zero, 1, uptr->fileref) == 0 /*   and write a zero to fill */
      || fflush (uptr->fileref) != 0)                       /*     the file to its capacity */
        clearerr (uptr->fileref);                           /* clear and ignore any errors */
    }

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


/* Detach a disc image file from a drive unit.

   The specified file is detached from the indicated drive unit.  This is the
   simulation equivalent to setting the RUN/STOP switch to STOP and removing the
   disc pack from the drive.  Stopping the drive will unload the heads and set
   the Attention bit in the drive status.

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

static t_stat ds_detach (UNIT *uptr)
{
t_stat result;

result = dl_detach (&mac_cntlr, uptr);                  /* detach the drive */

if (result == SCPE_INCOMP) {                            /* if the controller must be called before returning */
    call_controller (NULL);                             /*   then let it know to poll the drives */
    return SCPE_OK;                                     /*     before returning with success */
    }

else                                                    /* otherwise */
    return result;                                      /*   return the status of the detach */
}


/* Load or unload the drive heads.

   The SET DSn UNLOADED command simulates setting the hardware RUN/STOP switch
   to STOP.  The heads are unloaded, and the drive is spun down.

   The SET DSn LOADED command simulates setting the switch to RUN.  The drive is
   spun up, and the heads are loaded.  Loading fails if there is no pack in the
   drive, i.e., if the unit is not attached to a disc image file.

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

static t_stat ds_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc)
{
const t_bool load = (value != UNIT_UNLOAD);             /* TRUE if the heads are loading */
t_stat result;

result = dl_load_unload (&mac_cntlr, uptr, load);       /* load or unload the heads */

if (result == SCPE_INCOMP) {                            /* if the controller must be called before returning */
    call_controller (NULL);                             /*   then let it know to poll the drives */
    return SCPE_OK;                                     /*     before returning with success */
    }

else                                                    /* otherwise */
    return result;                                      /*   return the status of the load or unload */
}



/* Interface local utility routines */



/* Master reset.

   A master reset is generated either by an IORESET signal or a Programmed
   Master Clear (CIO with bit 0 set).  It initializes the interface to its idle
   state.  In addition, if jumper W1 (PRESET_ENABLE) is set, it asserts the
   CLEAR flag to the disc controller to perform a hard clear.
*/

static void master_reset (void)
{
interrupt_mask = SET;                                   /* set the interrupt mask */

ds_dib.interrupt_request = CLEAR;                       /* clear any current */
ds_dib.interrupt_active  = CLEAR;                       /*   interrupt request */

sio_busy     = CLEAR;                                   /* clear the SIO busy */
input_xfer   = CLEAR;                                   /*   input transfer */
output_xfer  = CLEAR;                                   /*     and output transfer flip-flops */
data_overrun = CLEAR;                                   /* clear the data overrun */
end_of_data  = CLEAR;                                   /*   end of data */
test_mode    = CLEAR;                                   /*     and test mode flip-flops */

deny_sio_busy ();                                       /* clear the logic affected by SIO Busy */

flags &= ~XFRNG;                                        /* clear the transfer error flag */

status_word = 0;                                        /* clear the status register */

if (PRESET_ENABLE) {                                    /* if jumper W1 (preset) is set to "E" */
    flags |= CLEARF;                                    /*   then assert CLEAR */
    call_controller (NULL);                             /*     to the controller */
    flags &= ~CLEARF;                                   /*       to do a hard clear */
    }

return;
}


/* Deny SIO busy.

   The internal SIO Busy signal enables a number of logic devices on the
   interface associated with SIO channel transfers.  When SIO Busy is denied,
   those devices are set or cleared as appropriate in preparation for the next
   SIO program.
*/

static void deny_sio_busy (void)
{
device_sr = CLEAR;                                      /* clear the service request, */
jump_met  = CLEAR;                                      /*   jump met, */
data_wait = CLEAR;                                      /*     and wait flip-flops */

retry_counter = 0;                                      /* clear the retry counter */

flags = flags & ~(CMRDY | DTRDY) | INTOK | EOD;         /* clear CMRDY and DTRDY and set INTOK and EOD flags */

return;
}


/* Clear interface logic.

   The clear interface logic signal is asserted during channel operation either
   when the interface requests an interrupt or the channel indicates a transfer
   failure by asserting XFERERROR.  It clears the SIO Busy, Input Transfer, and
   Output Transfer flip-flops, pulses the REQ line to abort the channel program,
   and sends EOD to the disc controller to abort any in-progress data transfer.

   The signal is inhibited when an SIO program is not active.
*/

static void clear_interface_logic (DIB *dibptr)
{
if (sio_busy == SET) {                                  /* if a channel program is in progress */
    sio_busy    = CLEAR;                                /*   then clear the SIO busy */
    input_xfer  = CLEAR;                                /*     input transfer */
    output_xfer = CLEAR;                                /*       and output transfer flip-flops */

    end_of_data = SET;                                  /* set the end of data flip-flop */

    deny_sio_busy ();                                   /* deny the SIO Busy signal */

    sel_assert_REQ (dibptr);                            /* abort the channel program */
    }

return;
}


/* Call the disc controller.

   The 13037 disc controller connects to CPU interfaces via a 16-bit data bus, a
   6-bit flag bus, a 4-bit function bus, and five additional control signals.
   The controller continuously monitors the flag bus and reacts to the interface
   changing the flag states by placing or accepting data on the data bus and
   issuing commands to the interface via the function bus.  The controller
   supports up to eight CPU interfaces simultaneously, and provision is made to
   poll each interface in turn via select and disconnect functions.  An
   interface only responds if it is currently selected.

   In simulation, a call to the dl_controller routine informs the controller of
   a (potential) change in flag state.  The current set of flags and data bus
   value are supplied, and the controller returns a combined set of functions
   and a data bus value.

   The controller must be called any time there is a change in the state of the
   interface or the drive units.  Generally, the cases that require notification
   are when the interface:

     - has a new command to execute
     - has a new data word available to send
     - has obtained the last data word received
     - has received a unit service event notification
     - has detected insertion or removal of a disc pack from a drive
     - has detected loading or unloading of a drive's heads
     - wants to hard-clear the controller

   The set of returned functions is processed sequentially, updating the
   interface state as indicated.  Some functions are not used by this interface,
   so they are masked off before processing to improve performance.

   Disc commands may be "stacked" on the interface by asserting PCONTSTB to
   store the new command into the data buffer register while the controller is
   still busy with the previous command.  This will assert CMRDY (if not in test
   mode), but the controller will not react to this signal until it finishes the
   current command.  For example, a channel program containing the Wakeup and
   End commands will transmit the End before the Wakeup completes.  This occurs
   because Wakeup asserts IFGTC to clear the command (and thereby asserts CHANSR
   to allow the channel to continue) about 18 microseconds before the controller
   completes the command and returns to the wait loop.  In simulation, an
   explicit check is made when a command completes.  If a stacked command is
   seen, the controller is called again to start it.

   Because the disc is a synchronous device, overrun or underrun can occur if
   the interface is not ready when the controller must transfer data.  There are
   four conditions that lead to an overrun or underrun:

    1. The controller is ready with a disc read word (IFCLK * IFIN), but the
       interface buffer is full (DTRDY).

    2. The controller needs a disc write word (IFCLK * IFOUT), but the interface
       buffer is empty (~DTRDY).

    3. The CPU attempts to read a word, but the interface buffer is empty
       (~DTRDY).

    4. The CPU attempts to write a word, but the interface buffer is full
       (DTRDY).

   The hardware design of the interface prevents the last two conditions, as the
   interface will assert CHANSR only when the buffer is full (read) or empty
   (write).  The interface does detect the first two conditions and sets the
   data overrun flip-flop if either occurs.

   Implementation notes:

    1. In hardware, OVRUN will be asserted when the controller requests write
       data when the buffer is empty.  In simulation, OVRUN will not be asserted
       when the controller is called with the empty buffer; instead, it will be
       asserted for the next controller call.  Because the controller will be
       called for the intersector phase, and because OVRUN isn't checked until
       that point, this "late" assertion does not affect overrun detection.

    2. In hardware, the data ready flip-flop is toggled as a result of reading
       or writing a word from or to the controller.  We follow that practice
       here, rather than setting or clearing it, which would be more
       appropriate.

    3. The hardware interface decodes the DSCIF and SELIF functions to allow the
       controller to be shared by two or more CPUs.  In simulation, these
       functions are ignored, as the simulator supports only one CPU connected
       to the interface.
*/

static void call_controller (UNIT *uptr)
{
CNTLR_IFN_IBUS result;
CNTLR_IFN_SET  command_set;
CNTLR_IFN      command;
CNTLR_FLAG_SET flag_set;

if (data_overrun == SET && (flags & XFRNG) == NO_FLAGS) /* if an overrun occurred without a transfer error */
    flags |= OVRUN;                                     /*   then tell the controller */

if (test_mode == SET)                                   /* if in test mode */
    flag_set = flags & CLEARF;                          /*   then all flags except CLEAR are inhibited */
else                                                    /* otherwise */
    flag_set = flags;                                   /*   present the full set of flags to the controller */


do {                                                    /* call the controller potentially more than once */
    result =                                            /*   to start or continue a command */
       dl_controller (&mac_cntlr, uptr, flag_set, (CNTLR_IBUS) buffer_word);

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

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

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

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

                else {                                  /* otherwise the buffer is empty */
                    device_sr = D_FF (! end_of_data);   /*   so request the next word unless EOT */

                    if ((input_xfer == CLEAR            /* if not configured to read */
                      || output_xfer == SET)            /*   or configured to write */
                      && (flags & EOD) == NO_FLAGS)     /*     and the transfer is active */
                        flags |= XFRNG;                 /*       then set the transfer is no good */
                    }

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


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

                if (end_of_data == SET)                 /* if this is the last transfer */
                    flags |= EOD;                       /*   then tell the controller */

                else {                                  /* otherwise the transfer continues */
                    device_sr = SET;                    /*   so request the next word */

                    if ((output_xfer == CLEAR           /* if not configured to write */
                      || input_xfer == SET)             /*   or configured to read */
                      && (flags & EOD) == NO_FLAGS)     /*     and the transfer is active */
                        flags |= XFRNG;                 /*       then set the transfer is no good */
                    }

                flags ^= DTRDY;                         /* toggle (clear) the data ready flag */
                break;


            case IFGTC:                                     /* Interface Get Command */
                flags &= ~(CMRDY | DTRDY | EOD | OVRUN);    /* clear the interface transfer flags */

                end_of_data = CLEAR;                        /* clear the end-of-data */
                data_overrun = CLEAR;                       /*   and data-overrun flip-flops */

                device_sr = SET;                            /* request channel service */
                break;


            case RQSRV:                                 /* Request Service */
                flags &= ~(EOD | OVRUN);                /* clear the end of data and data overrun flags */

                end_of_data = CLEAR;                    /* clear the */
                data_overrun = CLEAR;                   /*   corresponding flip-flops */

                device_sr = SET;                        /* request channel service */
                break;


            case SRTRY:                                 /* Set Retry */
                retry_counter = DLIBUS (result);        /* store the data value into the retry counter */
                break;


            case DVEND:                                 /* Device End */
                device_end = SET;                       /* set the device end */
                jump_met = SET;                         /*   and the "jump met condition" flip-flops */

                if (retry_counter > 0) {                /* if retries remain */
                    retry_counter = retry_counter - 1;  /*   then decrement the retry counter */
                    break;                              /*     and try again */
                    }
                                                        /* otherwise, request an interrupt */
            /* fall through into the STINT case */

            case STINT:                                 /* Set Interrupt */
                flags &= ~XFRNG;                        /* clear the transfer error flag */

                clear_interface_logic (&ds_dib);        /* clear the interface to abort the transfer */

                ds_dib.interrupt_request = SET;         /* set the request flip-flop */

                if (interrupt_mask)                     /* if the interrupt mask is satisfied */
                    iop_assert_INTREQ (&ds_dib);        /*   then assert the INTREQ signal */
                break;


            case WRTIO:                                     /* Write TIO */
                status_word = DLIBUS (result) & ST_MASK;    /* save the value without the SPD bits for TIO */
                break;


            case DSCIF:                                 /* not used by this simulation */
            case SELIF:                                 /* not used by this simulation */
                break;

            case BUSY:                                  /* not decoded by this interface */
            case IFPRF:                                 /* not decoded by this interface */
            case FREE:                                  /* not decoded by this interface */
            case STDFL:                                 /* not decoded by this interface */
                break;
            }

        command_set &= ~command;                        /* remove the current command from the set */
        }                                               /*   and continue with the remaining commands */
    }
while (flags & CMRDY                                    /* call the controller again if a command is pending */
  && result & FREE                                      /*   and a prior command just completed */
  && test_mode == CLEAR);                               /*     and not in test mode, which inhibits CMRDY */

return;
}
