/* hp2100_ds.c: HP 13037D/13175D disc controller/interface simulator

   Copyright (c) 2004-2012, Robert M. Supnik
   Copyright (c) 2012-2018  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
   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.

   DS           13037D/13175D disc controller/interface

   13-Jun-18    JDB     Revised I/O model
   07-May-18    JDB     Removed "dl_clear_controller" status return
   27-Feb-18    JDB     Added the BMDL
   21-Feb-18    JDB     ATTACH -N now creates a full-size disc image
   11-Jul-17    JDB     Renamed "ibl_copy" to "cpu_ibl"
   15-Mar-17    JDB     Trace flags are now global
                        Changed DEBUG_PRI calls to tprintfs
   10-Mar-17    JDB     Added IOBUS to the debug table
   09-Mar-17    JDB     Deprecated LOCKED/WRITEENABLED for PROTECT/UNPROTECT
   13-May-16    JDB     Modified for revised SCP API function parameter types
   04-Mar-16    JDB     Name changed to "hp2100_disclib" until HP 3000 integration
   30-Dec-14    JDB     Added S-register parameters to ibl_copy
   24-Dec-14    JDB     Use T_ADDR_FMT with t_addr values for 64-bit compatibility
   18-Mar-13    JDB     Fixed poll_drives definition to match declaration
   24-Oct-12    JDB     Changed CNTLR_OPCODE to title case to avoid name clash
   29-Mar-12    JDB     Rewritten to use the MAC/ICD disc controller library
                        ioIOO now notifies controller service of parameter output
   14-Feb-12    JDB     Corrected SRQ generation and FIFO under/overrun detection
                        Corrected Clear command to conform to the hardware
                        Fixed Request Status to return Unit Unavailable if illegal
                        Seek and Cold Load Read now Seek Check if seek in progress
                        Remodeled command wait for seek completion
   10-Feb-12    JDB     Deprecated DEVNO in favor of SC
   21-Jun-11    JDB     Corrected status returns for disabled drive, auto-seek
                        beyond drive limits, Request Sector Address and Wakeup
                        with invalid or offline unit
                        Address verification reenabled if auto-seek during
                        Read Without Verify
   28-Mar-11    JDB     Tidied up signal handling
   26-Oct-10    JDB     Changed I/O signal handler for revised signal model
   26-Jun-08    JDB     Rewrote device I/O to model backplane signals
   31-Dec-07    JDB     Corrected and verified ioCRS action
   20-Dec-07    JDB     Corrected DPTR register definition from FLDATA to DRDATA
   28-Dec-06    JDB     Added ioCRS state to I/O decoders
   03-Aug-06    JDB     Fixed REQUEST STATUS command to clear status-1
                        Removed redundant attached test in "ds_detach"
   18-Mar-05    RMS     Added attached test to detach routine
   01-Mar-05    JDB     Added SET UNLOAD/LOAD

   References:
     - 13037 Disc Controller Technical Information Package
         (13037-90902, August 1980)
     - 7925D Disc Drive Service Manual
         (07925-90913, April 1984)
     - HP 12992 Loader ROMs Installation Manual
         (12992-90001, April 1986)
     - DVR32 RTE Moving Head Driver source
         (92084-18711, Revision 5000)


   The 13037D multiple-access (MAC) disc controller supports from one to eight
   HP 7905 (15 MB), 7906 (20MB), 7920 (50 MB), and 7925 (120 MB) disc drives
   accessed by one to eight CPUs.  The controller hardware consists of a 16-bit
   microprogrammed processor constructed from 74S181 bit slices operating at 5
   MHz, a device controller providing the interconnections to the drives and CPU
   interfaces, and an error correction controller that enables the correction of
   up to 32-bit error bursts.  1024 words of 24-bit firmware are stored in ROM.

   The 13175D disc interface is used to connect the HP 1000 CPU to the 13037
   device controller.  In a multiple-CPU system, one interface is strapped to
   reset the controller when the CPU's front panel PRESET button is pressed.

   This module simulates a 13037D connected to a single 13175D interface.  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.

   This simulator is an adaptation of the code originally written by Bob Supnik.
   The functions of the controller have been separated from the functions of the
   interface, with the former placed into a separate disc controller library.
   This allows the library to support other CPU interfaces, such as the 12821A
   HP-IB disc interface, 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 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.


   Implementation notes:

    1. Although the 13175D has a 16-word FIFO, the "full" level is set at 5
       entries in hardware to avoid a long DCPC preemption time at the start of
       a disc write as the FIFO fills.
*/



#include "hp2100_defs.h"
#include "hp2100_io.h"
#include "hp2100_disclib.h"



/* Program constants */

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

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

#define FIFO_SIZE       16                              /* FIFO depth */

#define FIFO_EMPTY      (ds.fifo_count == 0)            /* FIFO empty test */
#define FIFO_STOP       (ds.fifo_count >= 5)            /* FIFO stop filling test */
#define FIFO_FULL       (ds.fifo_count == FIFO_SIZE)    /* FIFO full test */

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


/* Per-card state variables */

typedef struct {
    FLIP_FLOP    control;                       /* control flip-flop */
    FLIP_FLOP    flag;                          /* flag flip-flop */
    FLIP_FLOP    flag_buffer;                   /* flag buffer flip-flop */
    FLIP_FLOP    srq;                           /* SRQ flip-flop */
    FLIP_FLOP    edt;                           /* EDT flip-flop */
    FLIP_FLOP    cmfol;                         /* command follows flip-flop */
    FLIP_FLOP    cmrdy;                         /* command ready flip-flop */
    uint16       fifo [FIFO_SIZE];              /* FIFO buffer */
    uint32       fifo_count;                    /* FIFO occupancy counter */
    REG          *fifo_reg;                     /* FIFO register pointer */
    } CARD_STATE;


/* MAC disc state variables */

static UNIT ds_unit [DS_UNITS];                         /* unit array */

static CARD_STATE ds;                                   /* card state */

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

static CNTLR_VARS mac_cntlr =                           /* MAC controller */
    { CNTLR_INIT (MAC, buffer, &ds_cntlr) };



/* Interface local SCP support routines */

static INTERFACE ds_interface;


/* Interface local SCP support routines */

static t_stat ds_service_drive      (UNIT   *uptr);
static t_stat ds_service_controller (UNIT   *uptr);
static t_stat ds_service_timer      (UNIT   *uptr);
static t_stat ds_reset              (DEVICE *dptr);
static t_stat ds_attach             (UNIT   *uptr,  CONST char *cptr);
static t_stat ds_detach             (UNIT   *uptr);
static t_stat ds_boot               (int32  unitno, DEVICE *dptr);
static t_stat ds_load_unload        (UNIT *uptr, int32 value, CONST char *cptr, void *desc);


/* MAC disc local utility routines */

static void   start_command     (void);
static void   poll_interface    (void);
static void   poll_drives       (void);
static void   fifo_load         (uint16 data);
static uint16 fifo_unload       (void);
static void   fifo_clear        (void);
static t_stat activate_unit     (UNIT *uptr);


/* Interface SCP data structures */


/* Device information block */

static DIB ds_dib = {
    &ds_interface,                              /* the device's I/O interface function pointer */
    DS,                                         /* the device's select code (02-77) */
    0,                                          /* the card index */
    "13175D Disc Controller Interface",         /* the card description */
    "12992B 7905/7906/7920/7925 Disc Loader"    /* the ROM description */
    };


/* Units list */

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

static UNIT ds_unit [] = {
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 0 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 1 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 2 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 3 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 4 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 5 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 6 */
    { UDATA (&ds_service_drive,      UNIT_FLAGS | MODEL_7905, D7905_WORDS) },   /* drive unit 7 */
    { UDATA (&ds_service_controller, UNIT_DIS,                0)           },   /* controller unit */
    { UDATA (&ds_service_timer,      UNIT_DIS,                0)           }    /* timer unit */
    };


/* Register list */

static REG ds_reg [] = {
/*    Macro   Name    Location                  Radix   Width     Offset      Depth            Flags       */
/*    ------  ------  ------------------------  ----- ----------  ------  -------------  ----------------- */
    { FLDATA (CMFOL,  ds.cmfol,                                      0)                                    },
    { FLDATA (CMRDY,  ds.cmrdy,                                      0)                                    },
    { DRDATA (FCNT,   ds.fifo_count,                     5)                                                },
    { BRDATA (FIFO,   ds.fifo,                    8,    16,               FIFO_SIZE),    REG_CIRC          },
    { ORDATA (FREG,   ds.fifo_reg,                      32),                             REG_HRO           },

    { ORDATA (CNTYPE, mac_cntlr.type,                    2),                             REG_HRO           },
    { ORDATA (STATE,  mac_cntlr.state,                   2)                                                },
    { ORDATA (OPCODE, mac_cntlr.opcode,                  6)                                                },
    { ORDATA (STATUS, mac_cntlr.status,                  6)                                                },
    { FLDATA (EOC,    mac_cntlr.eoc,                                 0)                                    },
    { FLDATA (EOD,    mac_cntlr.eod,                                 0)                                    },
    { ORDATA (SPDU,   mac_cntlr.spd_unit,               16)                                                },
    { ORDATA (FLMASK, mac_cntlr.file_mask,               4)                                                },
    { ORDATA (RETRY,  mac_cntlr.retry,                   4),                             REG_HRO           },
    { ORDATA (CYL,    mac_cntlr.cylinder,               16)                                                },
    { ORDATA (HEAD,   mac_cntlr.head,                    6)                                                },
    { ORDATA (SECTOR, mac_cntlr.sector,                  8)                                                },
    { ORDATA (VFYCNT, mac_cntlr.verify_count,           16)                                                },
    { ORDATA (LASPOL, mac_cntlr.poll_unit,               3)                                                },
    { HRDATA (BUFPTR, mac_cntlr.buffer,                 32),                             REG_HRO           },
    { BRDATA (BUFFER, buffer,                     8,    16,               DL_BUFSIZE)                      },
    { DRDATA (INDEX,  mac_cntlr.index,                   8)                                                },
    { DRDATA (LENGTH, mac_cntlr.length,                  8)                                                },
    { HRDATA (AUXPTR, mac_cntlr.aux,                    32),                             REG_HRO           },
    { DRDATA (STIME,  mac_cntlr.seek_time,              24),                             PV_LEFT | REG_NZ  },
    { DRDATA (ITIME,  mac_cntlr.sector_time,            24),                             PV_LEFT | REG_NZ  },
    { DRDATA (CTIME,  mac_cntlr.cmd_time,               24),                             PV_LEFT | REG_NZ  },
    { DRDATA (DTIME,  mac_cntlr.data_time,              24),                             PV_LEFT | REG_NZ  },
    { DRDATA (WTIME,  mac_cntlr.wait_time,              31),                             PV_LEFT | REG_NZ  },

    { FLDATA (CTL,    ds.control,                                    0)                                    },
    { FLDATA (FLG,    ds.flag,                                       0)                                    },
    { FLDATA (FBF,    ds.flag_buffer,                                0)                                    },
    { FLDATA (SRQ,    ds.srq,                                        0)                                    },
    { FLDATA (EDT,    ds.edt,                                        0)                                    },

    { URDATA (UCYL,   ds_unit[0].CYL,            10,    10,          0,   DS_UNITS,      PV_LEFT)          },
    { URDATA (UOP,    ds_unit[0].OP,              8,     6,          0,   DS_UNITS,      PV_RZRO)          },
    { URDATA (USTAT,  ds_unit[0].STAT,            2,     8,          0,   DS_UNITS,      PV_RZRO)          },
    { URDATA (UPHASE, ds_unit[0].PHASE,           8,     3,          0,   DS_UNITS,      PV_RZRO)          },
    { URDATA (UPOS,   ds_unit[0].pos,             8,  T_ADDR_W,      0,   DS_UNITS,      PV_LEFT)          },
    { URDATA (UWAIT,  ds_unit[0].wait,            8,    32,          0,   DS_UNITS,      PV_LEFT)          },

      DIB_REGS (ds_dib),

    { NULL }
    };


/* Modifier list.

   For the drive models, the modifiers provide this SHOW behavior:

    - when detached and autosized, prints "autosize"
    - when detached and not autosized, prints the model number
    - when attached, prints the model number (regardless of autosizing)


   Implementation notes:

    1. The validation routine does not allow the model number or autosizing
       option to be changed when the unit is attached.  Therefore, specifying
       UNIT_ATT in the mask field has no adverse effect.

    2. The modifier DEVNO is deprecated in favor of SC but is retained for
       compatibility.
*/

static MTAB ds_mod [] = {
/*    Mask Value    Match Value   Print String       Match String     Validation        Display  Descriptor */
/*    ------------  ------------  -----------------  ---------------  ----------------  -------  ---------- */
    { UNIT_UNLOAD,  UNIT_UNLOAD,  "heads unloaded",  "UNLOADED",      &ds_load_unload,  NULL,    NULL       },
    { UNIT_UNLOAD,  0,            "heads loaded",    "LOADED",        &ds_load_unload,  NULL,    NULL       },

    { UNIT_WLK,     UNIT_WLK,     "protected",       "PROTECT",       NULL,             NULL,    NULL       },
    { UNIT_WLK,     0,            "unprotected",     "UNPROTECT",     NULL,             NULL,    NULL       },

    { UNIT_WLK,     UNIT_WLK,     NULL,              "LOCKED",        NULL,             NULL,    NULL       },
    { UNIT_WLK,     0,            NULL,              "WRITEENABLED",  NULL,             NULL,    NULL       },

    { UNIT_FMT,     UNIT_FMT,     "format enabled",  "FORMAT",        NULL,             NULL,    NULL       },
    { UNIT_FMT,     0,            "format disabled", "NOFORMAT",      NULL,             NULL,    NULL       },

/*                                                              Print       Match                                 */
/*    Mask Value                         Match Value            String      String      Validation     Disp  Desc */
/*    ---------------------------------  ---------------------  ----------  ----------  -------------  ----  ---- */
    { UNIT_AUTO | UNIT_ATT,              UNIT_AUTO,             "autosize", "AUTOSIZE", &dl_set_model, NULL, NULL },
    { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7905,            "7905",     "7905",     &dl_set_model, NULL, NULL },
    { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7906,            "7906",     "7906",     &dl_set_model, NULL, NULL },
    { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7920,            "7920",     "7920",     &dl_set_model, NULL, NULL },
    { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7925,            "7925",     "7925",     &dl_set_model, NULL, NULL },
    { UNIT_ATT | UNIT_MODEL,             UNIT_ATT | MODEL_7905, "7905",     NULL,       NULL,          NULL, NULL },
    { UNIT_ATT | UNIT_MODEL,             UNIT_ATT | MODEL_7906, "7906",     NULL,       NULL,          NULL, NULL },
    { UNIT_ATT | UNIT_MODEL,             UNIT_ATT | MODEL_7920, "7920",     NULL,       NULL,          NULL, NULL },
    { UNIT_ATT | UNIT_MODEL,             UNIT_ATT | MODEL_7925, "7925",     NULL,       NULL,          NULL, NULL },

/*    Entry Flags          Value  Print String  Match String  Validation    Display        Descriptor       */
/*    -------------------  -----  ------------  ------------  ------------  -------------  ---------------- */
    { MTAB_XDV,              1u,  "SC",         "SC",         &hp_set_dib,  &hp_show_dib,  (void *) &ds_dib },
    { MTAB_XDV | MTAB_NMO,  ~1u,  "DEVNO",      "DEVNO",      &hp_set_dib,  &hp_show_dib,  (void *) &ds_dib },
    { 0 }
    };


/* Debugging trace list */

static DEBTAB ds_deb [] = {
    { "RWSC",  DEB_RWSC    },
    { "CMDS",  DEB_CMDS    },
    { "CPU",   DEB_CPU     },
    { "BUF",   DEB_BUF     },
    { "SERV",  DEB_SERV    },
    { "IOBUS", TRACE_IOBUS },
    { NULL,    0           }
    };


/* Device descriptor */

DEVICE ds_dev = {
    "DS",                                       /* device name */
    ds_unit,                                    /* unit array */
    ds_reg,                                     /* register array */
    ds_mod,                                     /* modifier array */
    DS_UNITS,                                   /* number of units */
    8,                                          /* 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_DISABLE | DEV_DEBUG,                    /* 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 interface.

   The 13175D disc interface data path consists of an input multiplexer/latch
   and a 16-word FIFO buffer.  The FIFO source may be either the CPU's I/O
   input bus or the controller's interface data bus.  The output of the FIFO may
   be enabled either to the CPU's I/O output bus or the interface data bus.

   The control path consists of the usual control, flag buffer, flag, and SRQ
   flip-flops, although flag and SRQ are decoupled to allow the full DCPC
   transfer rate through the FIFO (driving SRQ from the flag limits transfers to
   every other cycle).  SRQ is based on the FIFO level: if data or room in the
   FIFO is available, SRQ is set to initiate a transfer.  The flag is only used
   to signal an interrupt at the end of a command.

   One unusual aspect is that SFC and SFS test different things, rather than
   complementary states of the same thing.  SFC tests the controller busy state,
   and SFS tests the flag flip-flop.

   In addition, the card contains end-of-data-transfer, command-follows, and
   command-ready flip-flops.  EDT is set when the DCPC EDT signal is asserted
   and is used in conjunction with the FIFO level to assert the end-of-data
   signal to the controller.  The command-follows flip-flop is set by a CLC to
   indicate that the next data word output from the CPU is a disc command.  The
   command-ready flip-flop is set when a command is received to schedule an
   interface poll.


   Implementation notes:

    1. In hardware, SRQ is enabled only when the controller is reading or
       writing the disc (IFIN or IFOUT functions are asserted) and set when the
       FIFO is not empty (read) or not full (write).  In simulation, SRQ is set
       by the unit service read/write data phase transfers and cleared in the
       IOI and IOO signal handlers when the FIFO is empty (read) or full
       (write).

    2. The DCPC EDT signal cannot set the controller's end-of-data flag directly
       because a write EOD must occur only after the FIFO has been drained.

    3. Polling the interface or drives must be deferred to the end of I/O signal
       handling.  If they are performed in the IOO/STC handlers themselves, an
       associated CLF might clear the flag that was set by the poll.

    4. Executing a CLC sets the controller's end-of-data flag, which will abort
       a read or write data transfer in progress.  Parameter transfers are not
       affected.  If a command is received when a parameter is expected, the
       word is interpreted as data, even though the command-ready flip-flop is
       set.  The controller firmware only checks DTRDY for a parameter transfer,
       and DTRDY is asserted whenever the FIFO is not empty.

    5. The hardware Interface Function and Flag Buses are not implemented
       explicitly.  Instead, interface functions and signals are inferred by the
       interface from the current command operation and phase.
*/

static SIGNALS_VALUE ds_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
static const char * const output_state [] = { "Data", "Command" };
const char * const hold_or_clear = (inbound_signals & ioCLF ? ",C" : "");
INBOUND_SIGNAL signal;
INBOUND_SET    working_set = inbound_signals;
SIGNALS_VALUE  outbound    = { ioNONE, 0 };
t_bool         irq_enabled = FALSE;
t_bool         command_issued = FALSE;
t_bool         interrupt_enabled = FALSE;

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

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

        case ioCLF:                                     /* clear flag flip-flop */
            ds.flag_buffer = CLEAR;                     /* clear the flag buffer */
            ds.flag        = CLEAR;                     /*   and flag */

            tprintf (ds_dev, DEB_CMDS, "[CLF] Flag cleared\n");
            break;


        case ioSTF:                                     /* set flag flip-flop */
            ds.flag_buffer = SET;                       /* set the flag buffer */

            tprintf (ds_dev, DEB_CMDS, "[STF] Flag set\n");
            break;


        case ioENF:                                     /* enable flag */
            if (ds.flag_buffer == SET)
                ds.flag = SET;
            break;


        case ioSFC:                                     /* skip if flag is clear */
            if (mac_cntlr.state != cntlr_busy)          /* skip if the controller is not busy */
                outbound.signals |= ioSKF;
            break;


        case ioSFS:                                     /* skip if flag is set */
            if (ds.flag == SET)                         /* assert SKF if the flag is set */
                outbound.signals |= ioSKF;
            break;


        case ioIOI:                                         /* I/O data input */
            outbound.value = fifo_unload ();                /* unload the next word from the FIFO */

            tprintf (ds_dev, DEB_CPU, "[LIx%s] Data = %06o\n", hold_or_clear, outbound.value);

            if (FIFO_EMPTY) {                               /* is the FIFO now empty? */
                if (ds.srq == SET)
                    tprintf (ds_dev, DEB_CMDS, "[LIx%s] SRQ cleared\n", hold_or_clear);

                ds.srq = CLEAR;                             /* clear SRQ */

                if (ds_cntlr.PHASE == data_phase) {         /* is this an outbound parameter? */
                    ds_cntlr.wait = mac_cntlr.data_time;    /* activate the controller */
                    activate_unit (&ds_cntlr);              /*   to acknowledge the data */
                    }
                }
            break;


        case ioIOO:                                         /* I/O data output */
            tprintf (ds_dev, DEB_CPU, "[OTx%s] %s = %06o\n",
                     hold_or_clear, output_state [ds.cmfol], inbound_value);

            fifo_load ((uint16) inbound_value);             /* load the word into the FIFO */

            if (ds.cmfol == SET) {                          /* are we expecting a command? */
                ds.cmfol = CLEAR;                           /* clear the command follows flip-flop */
                ds.cmrdy = SET;                             /* set the command ready flip-flop */
                command_issued = TRUE;                      /*   and request an interface poll */
                }

            else {                                          /* not a command */
                if (ds_cntlr.PHASE == data_phase) {         /* is this an inbound parameter? */
                    ds_cntlr.wait = mac_cntlr.data_time;    /* activate the controller */
                    activate_unit (&ds_cntlr);              /*   to receive the data */
                    }

                if (FIFO_STOP) {                            /* is the FIFO now full enough? */
                    if (ds.srq == SET)
                        tprintf (ds_dev, DEB_CMDS, "[OTx%s] SRQ cleared\n", hold_or_clear);

                    ds.srq = CLEAR;                         /* clear SRQ to stop filling */
                    }
                }
            break;


        case ioPOPIO:                                   /* power-on preset to I/O */
            ds.flag_buffer = SET;                       /* set flag buffer */
            ds.cmrdy       = CLEAR;                     /* clear the command ready flip-flop */

            tprintf (ds_dev, DEB_CMDS, "[POPIO] Flag set\n");
            break;


        case ioCRS:                                         /* control reset */
            tprintf (ds_dev, DEB_CMDS, "[CRS] Master reset\n");

            ds.control = CLEAR;                             /* clear the control */
            ds.cmfol = CLEAR;                               /*   and command follows flip-flops */

            if (PRESET_ENABLE) {                            /* is preset enabled for this interface? */
                fifo_clear ();                              /* clear the FIFO */

                dl_clear_controller (&mac_cntlr, ds_unit,   /* do a hard clear of the controller */
                                     hard_clear);
                }
            break;


        case ioCLC:                                     /* clear control flip-flop */
            tprintf (ds_dev, DEB_CMDS, "[CLC%s] Control cleared\n", hold_or_clear);

            ds.control = CLEAR;                         /* clear the control */
            ds.edt = CLEAR;                             /*   and EDT flip-flops */
            ds.cmfol = SET;                             /* set the command follows flip-flop */
            mac_cntlr.eod = SET;                        /* set the controller's EOD flag */

            fifo_clear ();                              /* clear the FIFO */
            break;


        case ioSTC:                                     /* set control flip-flop */
            ds.control = SET;                           /* set the control flip-flop */

            interrupt_enabled = TRUE;                   /* check for drive attention */

            tprintf (ds_dev, DEB_CMDS, "[STC%s] Control set\n", hold_or_clear);
            break;


        case ioEDT:                                     /* end data transfer */
            ds.edt = SET;                               /* set the EDT flip-flop */

            tprintf (ds_dev, DEB_CPU, "[EDT] DCPC transfer ended\n");
            break;


        case ioSIR:                                     /* set interrupt request */
            if (ds.control & ds.flag)                   /* if control and flag are set */
                outbound.signals |= cnVALID;            /*   then deny PRL */
            else                                        /* otherwise */
                outbound.signals |= cnPRL | cnVALID;    /*   conditionally assert PRL */

            if (ds.control & ds.flag & ds.flag_buffer)  /* if control and flag and flag buffer are set */
                outbound.signals |= cnIRQ | cnVALID;    /*   then conditionally assert IRQ */

            if (ds.srq == SET)                          /* if the SRQ flip-flop is set */
                outbound.signals |= ioSRQ;              /*   then assert SRQ */
            break;


        case ioIAK:                                     /* interrupt acknowledge */
            ds.flag_buffer = CLEAR;
            break;


        case ioIEN:                                     /* interrupt enable */
            irq_enabled = TRUE;
            break;


        case ioPRH:                                         /* priority high */
            if (irq_enabled && outbound.signals & cnIRQ)    /* if IRQ is enabled and conditionally asserted */
                outbound.signals |= ioIRQ | ioFLG;          /*   then assert IRQ and FLG */

            if (!irq_enabled || outbound.signals & cnPRL)   /* if IRQ is disabled or PRL is conditionally asserted */
                outbound.signals |= ioPRL;                  /*   then assert it unconditionally */
            break;


        case ioPON:                                     /* not used by this interface */
            break;
        }

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


if (command_issued)                                     /* was a command received? */
    poll_interface ();                                  /* poll the interface for the next command */
else if (interrupt_enabled)                             /* were interrupts enabled? */
    poll_drives ();                                     /* poll the drives for Attention */

return outbound;                                        /* return the outbound signals and value */
}


/* Service the disc drive unit.

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

   Generally, the controller library service routine handles all of the disc
   operations except data transfer to and from the interface.  Read transfers
   are responsible for loading words from the sector buffer into the FIFO and
   enabling SRQ.  If the current sector transfer is complete, either due to EDT
   assertion or buffer exhaustion, the controller is moved to the end phase to
   complete or continue the read with the next sector.  In either case, the unit
   is rescheduled.  If the FIFO overflows, the read terminates with a data
   overrun error.

   Write transfers set the initial SRQ to request words from the CPU.  As each
   word arrives, it is unloaded from the FIFO into the sector buffer, and SRQ is
   enabled.  If the current sector transfer is complete, the controller is moved
   to the end phase.  If the FIFO underflows, the write terminates with a data
   overrun error.

   The synchronous nature of the disc drive requires that data be supplied or
   accepted continuously by the CPU.  DCPC generally assures that this occurs,
   and the FIFO allows for some latency before an overrun or underrun occurs.

   The other operation the interface must handle is seek completion.  The
   controller handles seek completion by setting Attention status in the drive's
   status word.  The interface is responsible for polling the drives if the
   controller is idle and interrupts are enabled.


   Implementation notes:

    1. Every command except Seek, Recalibrate, and End sets the flag when the
       command completes.  A command completes when the controller is no longer
       busy (it becomes idle for Seek, Recalibrate, and End, or it becomes
       waiting for all others).  Seek and Recalibrate may generate errors (e.g.,
       heads unloaded), in which case the flag must be set.  But in these cases,
       the controller state is waiting, not idle.

       However, it is insufficient simply to check that the controller has moved
       to the wait state, because a seek may complete while the controller is
       waiting for the next command.  For example, a Seek is started on unit 0,
       and the controller moves to the idle state.  But before the seek
       completes, another command is issued that attempts to access unit 1,
       which is not ready.  The command fails with a Status-2 error, and the
       controller moves to the wait state.  When the seek completes, the
       controller is waiting with error status.  We must determine whether the
       seek completed successfully or not, as we must interrupt in the latter
       case.

       Therefore, we determine seek completion by checking if the Attention
       status was set.  Attention sets only if the seek completes successfully.

       (Actually, Attention sets if a seek check occurs, but in that case, the
       command terminated before the seek ever started.  Also, a seek may
       complete while the controller is busy, waiting, or idle.)

    2. For debug printouts, we want to print the name of the command that has
       completed when the controller returns to the idle or wait state.
       Normally, we would use the controller's "opcode" field to identify the
       command that completed.  However, while waiting for Seek or Recalibrate
       completion, "opcode" may be set to another command if that command does
       not access this drive.  For example, it might be set to a Read of another
       unit, or a Request Status for this unit.  So we can't rely on "opcode" to
       report the correct name of the completed positioning command.

       However, we cannot rely on "uptr->OP" either, as that can be changed
       during the course of a command.  For example, Read Without Verify is
       changed to Read after a track crossing.

       Instead, we have to determine whether a seek is completing.  If it is,
       then we report "uptr->OP"; otherwise, we report "opcode".

    3. The initial write SRQ must set only at the transition from the start
       phase to the data phase.  If a write command begins with an auto-seek,
       the drive service will be entered twice in the start phase (the first
       entry performs the seek, and the second begins the write).  In hardware,
       SRQ does not assert until the write begins.

    4. The DCPC EDT signal cannot set the controller's end-of-data flag
       directly because a write EOD must only occur after the FIFO has been
       drained.
*/

static t_stat ds_service_drive (UNIT *uptr)
{
t_stat result;
t_bool seek_completion;
FLIP_FLOP entry_srq = ds.srq;                           /* get the SRQ state on entry */
CNTLR_PHASE entry_phase = (CNTLR_PHASE) uptr->PHASE;    /* get the operation phase on entry */
uint32 entry_status = uptr->STAT;                       /* get the drive status on entry */

result = dl_service_drive (&mac_cntlr, uptr);           /* service the drive */

if ((CNTLR_PHASE) uptr->PHASE == data_phase)            /* is the drive in the data phase? */
    switch ((CNTLR_OPCODE) uptr->OP) {                  /* dispatch the current operation */

        case Read:                                      /* read operations */
        case Read_Full_Sector:
        case Read_With_Offset:
        case Read_Without_Verify:
            if (mac_cntlr.length == 0 || ds.edt == SET) {   /* is the data phase complete? */
                mac_cntlr.eod = ds.edt;                     /* set EOD if DCPC is done */
                uptr->PHASE = end_phase;                    /* set the end phase */
                uptr->wait = mac_cntlr.cmd_time;            /*   and schedule the controller */
                }

            else if (FIFO_FULL)                             /* is the FIFO already full? */
                dl_end_command (&mac_cntlr, data_overrun);  /* terminate the command with an overrun */

            else {
                fifo_load (buffer [mac_cntlr.index++]);     /* load the next word into the FIFO */
                mac_cntlr.length--;                         /* count it */
                ds.srq = SET;                               /* ask DCPC to pick it up */
                io_assert (&ds_dev, ioa_SIR);               /*   and assert SRQ */
                uptr->wait = mac_cntlr.data_time;           /* schedule the next data transfer */
                }

            break;


        case Write:                                     /* write operations */
        case Write_Full_Sector:
        case Initialize:
            if (entry_phase == start_phase) {               /* is this the phase transition? */
                ds.srq = SET;                               /* start the DCPC transfer */
                io_assert (&ds_dev, ioa_SIR);               /*   and assert SRQ */
                }

            else if (FIFO_EMPTY)                            /* is the FIFO empty? */
                dl_end_command (&mac_cntlr, data_overrun);  /* terminate the command with an underrun */

            else {
                buffer [mac_cntlr.index++] = fifo_unload ();    /* unload the next word from the FIFO */
                mac_cntlr.length--;                             /* count it */

                if (ds.edt == SET && FIFO_EMPTY)                /* if DCPC is complete and the FIFO is empty */
                    mac_cntlr.eod = SET;                        /*   then set the end-of-data flag */

                if (mac_cntlr.length == 0 || mac_cntlr.eod == SET) {    /* is the data phase complete? */
                    uptr->PHASE = end_phase;                            /* set the end phase */
                    uptr->wait = mac_cntlr.cmd_time;                    /*   and schedule the controller */
                    }

                else {
                    if (ds.edt == CLEAR) {              /* if DCPC is still transferring */
                        ds.srq = SET;                   /*   then request the next word */
                        io_assert (&ds_dev, ioa_SIR);   /*     and assert SRQ */
                        }

                    uptr->wait = mac_cntlr.data_time;   /* schedule the next data transfer */
                    }
                }

            break;


        default:                                        /* we were entered with an invalid state */
            result = SCPE_IERR;                         /* return an internal (programming) error */
            break;
        }                                               /* end of data phase operation dispatch */


if (entry_srq != ds.srq)
    tprintf (ds_dev, DEB_CMDS, "SRQ %s\n", ds.srq == SET ? "set" : "cleared");

if (uptr->wait)                                             /* was service requested? */
    activate_unit (uptr);                                   /* schedule the next event */

seek_completion = ~entry_status & uptr->STAT & DL_S2ATN;    /* seek is complete when Attention sets */

if (mac_cntlr.state != cntlr_busy) {                            /* is the command complete? */
    if (mac_cntlr.state == cntlr_wait && !seek_completion) {    /* is it command but not seek completion? */
        ds.flag_buffer = SET;                                   /* set the data flag */
        io_assert (&ds_dev, ioa_ENF);                           /*   to interrupt the CPU */
        }

    poll_interface ();                                      /* poll the interface for the next command */
    poll_drives ();                                         /* poll the drives for Attention */
    }


if (result == SCPE_IERR)                                    /* did an internal error occur? */
    tprintf (ds_dev, DEB_RWSC, "Unit %d %s command %s phase service not handled\n",
             uptr - ds_unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP),
             dl_phase_name ((CNTLR_PHASE) uptr->PHASE));

else if (seek_completion)                                           /* if a seek has completed */
    tprintf (ds_dev, DEB_RWSC, "Unit %d %s command completed\n",    /*   report the unit command */
             uptr - ds_unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP));

else if (mac_cntlr.state == cntlr_wait)                             /* if the controller has stopped */
    tprintf (ds_dev, DEB_RWSC, "Unit %d %s command completed\n",    /*   report the controller command */
             uptr - ds_unit, dl_opcode_name (MAC, mac_cntlr.opcode));

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


/* Service the controller unit.

   The controller service routine is called to execute scheduled controller
   commands that do not access drive units.  It is also called to obtain command
   parameters from the interface and to return command result values to the
   interface.

   Most controller commands are handled completely in the library's service
   routine, so we call that first.  Commands that neither accept nor supply
   parameters are complete when the library routine returns, so all we have to
   do is set the interface flag if required.

   For parameter transfers in the data phase, the interface is responsible for
   moving words between the sector buffer and the FIFO and setting the flag to
   notify the CPU.


   Implementation notes:

    1. In hardware, the Read With Offset command sets the data flag after the
       offset parameter has been read and the head positioner has been moved by
       the indicated amount.  The intent is to delay the DCPC start until the
       drive is ready to supply data from the disc.

       In simulation, the flag is set as soon as the parameter is received.
*/

static t_stat ds_service_controller (UNIT *uptr)
{
t_stat result;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP;

result = dl_service_controller (&mac_cntlr, uptr);      /* service the controller */

switch ((CNTLR_PHASE) uptr->PHASE) {                    /* dispatch the current phase */

    case start_phase:                                   /* most controller operations */
    case end_phase:                                     /*   start and end on the same phase */
        switch (opcode) {                               /* dispatch the current operation */

            case Request_Status:
            case Request_Sector_Address:
            case Address_Record:
            case Request_Syndrome:
            case Load_TIO_Register:
            case Request_Disc_Address:
            case End:
                break;                                  /* complete the operation without setting the flag */


            case Clear:
            case Set_File_Mask:
            case Wakeup:
                ds.flag_buffer = SET;                   /* complete the operation */
                io_assert (&ds_dev, ioa_ENF);           /*   and set the flag */
                break;


            default:                                    /* we were entered with an invalid state */
                result = SCPE_IERR;                     /* return an internal (programming) error */
                break;
            }                                           /* end of operation dispatch */
        break;                                          /* end of start and end phase handlers */


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

            case Seek:                                  /* operations that accept parameters */
            case Verify:
            case Address_Record:
            case Read_With_Offset:
            case Load_TIO_Register:
                buffer [mac_cntlr.index++] = fifo_unload ();    /* unload the next word from the FIFO */
                mac_cntlr.length--;                             /* count it */

                if (mac_cntlr.length) {                         /* are there more words to transfer? */
                    ds.flag_buffer = SET;                       /* set the flag */
                    io_assert (&ds_dev, ioa_ENF);               /*   to request the next one */
                    }

                else {                                          /* all parameters have been received */
                    uptr->PHASE = end_phase;                    /* set the end phase */

                    if (opcode == Read_With_Offset) {           /* a Read With Offset command sets the flag */
                        ds.flag_buffer = SET;                   /*   to indicate that offsetting */
                        io_assert (&ds_dev, ioa_ENF);           /*     is complete */
                        }

                    start_command ();                           /* the command is now ready to execute */
                    }
                break;


            case Request_Status:                        /* operations that supply parameters */
            case Request_Sector_Address:
            case Request_Syndrome:
            case Request_Disc_Address:
                if (mac_cntlr.length) {                         /* are there more words to return? */
                    fifo_load (buffer [mac_cntlr.index++]);     /* load the next word into the FIFO */
                    mac_cntlr.length--;                         /* count it */

                    ds.flag_buffer = SET;               /* set the data flag */
                    io_assert (&ds_dev, ioa_ENF);       /*   to request pickup by the CPU */
                    }

                else {                                  /* all parameters have been sent */
                    uptr->PHASE = end_phase;            /* set the end phase */
                    uptr->wait = mac_cntlr.cmd_time;    /* schedule the controller */
                    activate_unit (uptr);               /*   to complete the command */
                    }
                break;


            default:                                    /* we were entered with an invalid state */
                result = SCPE_IERR;                     /* return an internal (programming) error */
                break;
            }                                           /* end of operation dispatch */
        break;                                          /* end of data phase handlers */
    }                                                   /* end of phase dispatch */


if (result == SCPE_IERR)                                /* did an internal error occur? */
    tprintf (ds_dev, DEB_RWSC, "Controller %s command %s phase service not handled\n",
             dl_opcode_name (MAC, opcode), dl_phase_name ((CNTLR_PHASE) uptr->PHASE));


if (mac_cntlr.state != cntlr_busy) {                    /* has the controller stopped? */
    poll_interface ();                                  /* poll the interface for the next command */
    poll_drives ();                                     /* poll the drives for Attention status */

    tprintf (ds_dev, DEB_RWSC, "Controller %s command completed\n",
             dl_opcode_name (MAC, opcode));
    }

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


/* Service the command wait timer unit.

   The command wait timer service routine is called if the command wait timer
   expires.  The library is called to reset the file mask and idle the
   controller.  Then the interface is polled for a command and the drives are
   polled for Attention status.
*/

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

result = dl_service_timer (&mac_cntlr, uptr);           /* service the timer */

poll_interface ();                                      /* poll the interface for the next command */
poll_drives ();                                         /* poll the drives for Attention status */

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


/* Reset the simulator.

   In hardware, the PON signal clears the Interface Selected flip-flop,
   disconnecting the interface from the disc controller.  In simulation, the
   interface always remains connected to the controller, so no special action is
   needed.


   Implementation notes:

    1. During a power-on reset, a pointer to the FIFO simulation register is
       saved to allow access to the "qptr" field during FIFO loading and
       unloading.  This enables SCP to view the FIFO as a circular queue, so
       that the bottom word of the FIFO is always displayed as FIFO[0],
       regardless of where it is in the actual FIFO array.

    2. SRQ is denied because neither IFIN nor IFOUT is asserted when the
       interface is not selected.
*/

static t_stat ds_reset (DEVICE *dptr)
{
uint32 unit;

if (sim_switches & SWMASK ('P')) {                      /* is this a power-on reset? */
    ds.fifo_reg = find_reg ("FIFO", NULL, dptr);        /* find the FIFO register entry */

    if (ds.fifo_reg == NULL)                            /* if it cannot be found, */
        return SCPE_IERR;                               /*   report a programming error */

    else {                                              /* found it */
        ds.fifo_reg->qptr = 0;                          /*   so reset the FIFO bottom index */
        ds.fifo_count = 0;                              /*   and clear the FIFO */
        }

    for (unit = 0; unit < dptr->numunits; unit++) {     /* loop through all of the units */
        sim_cancel (dptr->units + unit);                /* cancel activation */
        dptr->units [unit].CYL = 0;                     /* reset the head position to cylinder 0 */
        dptr->units [unit].pos = 0;                     /* (irrelevant for the controller and timer) */
        }
    }

io_assert (dptr, ioa_POPIO);                            /* PRESET the device */
ds.srq = CLEAR;                                         /* clear SRQ */

return SCPE_OK;
}


/* Attach a drive unit.

   The specified file is attached to the indicated drive unit.  The library
   attach routine will load the heads.  This will set the First Status and
   Attention bits in the drive status, so we poll the drives to ensure that the
   CPU is notified that the drive is now online.

   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, the drive status will not be
       changed, so polling the drives will have no effect.

    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, CONST 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_OK) {                                /* if the attach was successful */
    poll_drives ();                                     /*   then poll the drives to notify the CPU */

    if (sim_switches & SWMASK ('N')) {                  /* if this is a new disc 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 result of the attach */
}


/* Detach a drive unit.

   The specified file is detached from the indicated drive unit.  The library
   detach routine will unload the heads.  This will set the Attention bit in the
   drive status, so we poll the drives to ensure that the CPU is notified that
   the drive is now offline.
*/

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

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

if (result == SCPE_OK)                                  /* was the detach successful? */
    poll_drives ();                                     /* poll the drives to notify the CPU */

return result;
}


/* MAC disc bootstrap loaders (BMDL and 12992B).

   The Basic Moving-Head Disc Loader (BMDL) consists of two programs.  The
   program starting at address x7700 loads absolute paper tapes into memory.
   The program starting at address x7750 loads a disc-resident bootstrap from
   the MAC disc drive into memory.  The S register specifies the head to use.

   For a 2100/14/15/16 CPU, entering a LOAD DS or BOOT DS command loads the BMDL
   into memory and executes the disc portion starting at x7750.  The bootstrap
   reads 2047 words from cylinder 0 sector 0 of the specified head into memory
   starting at location 2011 octal.  Loader execution ends with one of the
   following instructions:

     * HLT 11B    - the disc read failed.
     * JSB 2055,I - the disc read completed.

   The HP 1000 uses the 12992B boot loader ROM to bootstrap the disc.  The head
   number is obtained from bits 2-0 of the existing S-register value when the
   loader is executed.  Bits 5-3 of the existing S-register value are also
   retained and are available to the boot extension program.  The loader reads
   6144 words from cylinder 0 sector 0 of the specified head into memory
   starting at location 2011 octal.  Loader execution ends with one of the
   following instructions:

     * HLT 30     - the drive is not ready..
     * JSB 2055,I - the disc read succeeded.

   The loader automatically retries the operations for all disc errors other
   than a drive fault.


   Implementation notes:

    1. After the BMDL has been loaded into memory, the paper tape portion may be
       executed manually by setting the P register to the starting address
       (x7700).

    2. For compatibility with the "cpu_copy_loader" routine, the BMDL device I/O
       instructions address select codes 10 and 11.
*/

static const LOADER_ARRAY ds_loaders = {
    {                               /* HP 21xx Basic Moving-Head Disc Loader (BMDL-7905) */
      050,                          /*   loader starting index */
      076,                          /*   DMA index */
      034,                          /*   FWA index */
      { 0002401,                    /*   77700:  PTAPE CLA,RSS             Paper Tape start */
        0063722,                    /*   77701:        LDA 77722           */
        0107700,                    /*   77702:        CLC 0,C             */
        0002307,                    /*   77703:        CCE,INA,SZA,RSS     */
        0102077,                    /*   77704:        HLT 77              */
        0017735,                    /*   77705:        JSB 77735           */
        0007307,                    /*   77706:        CMB,CCE,INB,SZB,RSS */
        0027702,                    /*   77707:        JMP 77702           */
        0077733,                    /*   77710:        STB 77733           */
        0017735,                    /*   77711:        JSB 77735           */
        0017735,                    /*   77712:        JSB 77735           */
        0074000,                    /*   77713:        STB 0               */
        0077747,                    /*   77714:        STB 77747           */
        0047734,                    /*   77715:        ADB 77734           */
        0002140,                    /*   77716:        SEZ,CLE             */
        0102055,                    /*   77717:        HLT 55              */
        0017735,                    /*   77720:        JSB 77735           */
        0040001,                    /*   77721:        ADA 1               */
        0177747,                    /*   77722:        STB 77747,I         */
        0067747,                    /*   77723:        LDB 77747           */
        0006104,                    /*   77724:        CLE,INB             */
        0037733,                    /*   77725:        ISZ 77733           */
        0027714,                    /*   77726:        JMP 77714           */
        0017735,                    /*   77727:        JSB 77735           */
        0054000,                    /*   77730:        CPB 0               */
        0027701,                    /*   77731:        JMP 77701           */
        0102011,                    /*   77732:        HLT 11              */
        0000000,                    /*   77733:        NOP                 */
        0100100,                    /*   77734:        RRL 16              */
        0000000,                    /*   77735:        NOP                 */
        0006400,                    /*   77736:        CLB                 */
        0103710,                    /*   77737:        STC 10,C            */
        0102310,                    /*   77740:        SFS 10              */
        0027740,                    /*   77741:        JMP 77740           */
        0106410,                    /*   77742:        MIB 10              */
        0002240,                    /*   77743:        SEZ,CME             */
        0127735,                    /*   77744:        JMP 77735,I         */
        0005727,                    /*   77745:        BLF,BLF             */
        0027737,                    /*   77746:        JMP 77737           */
        0000000,                    /*   77747:        NOP                 */
        0067777,                    /*   77750: DISC   LDB 77777           */
        0174001,                    /*   77751:        STB 1,I             */
        0006004,                    /*   77752:        INB                 */
        0063732,                    /*   77753:        LDA 77732           */
        0170001,                    /*   77754:        STA 1,I             */
        0067776,                    /*   77755:        LDB 77776           */
        0106606,                    /*   77756:        OTB 6               */
        0106702,                    /*   77757:        CLC 2               */
        0102602,                    /*   77760:        OTA 2               */
        0102702,                    /*   77761:        STC 2               */
        0063751,                    /*   77762:        LDA 77751           */
        0102602,                    /*   77763:        OTA 2               */
        0102501,                    /*   77764:        LIA 1               */
        0001027,                    /*   77765:        ALS,ALF             */
        0013767,                    /*   77766:        AND 77767           */
        0000160,                    /*   77767:        CLE,ALS             */
        0106710,                    /*   77770:        CLC 10              */
        0103610,                    /*   77771:        OTA 10,C            */
        0103706,                    /*   77772:        STC 6,C             */
        0102310,                    /*   77773:        SFS 10              */
        0027773,                    /*   77774:        JMP 77773           */
        0117717,                    /*   77775:        JSB 77717,I         */
        0000010,                    /*   77776:        SLA                 */
        0002055 } },                /*   77777:        SEZ,SLA,INA,RSS     */

    {                               /* HP 1000 Loader ROM (12992B) */
      IBL_START,                    /*   loader starting index */
      IBL_DMA,                      /*   DMA index */
      IBL_FWA,                      /*   FWA index */
      { 0017727,                    /*   77700:  START JSB STAT      GET STATUS */
        0002021,                    /*   77701:        SSA,RSS       IS DRIVE READY ? */
        0027742,                    /*   77702:        JMP DMA       YES, SET UP DMA */
        0013714,                    /*   77703:        AND B20       NO, CHECK STATUS BITS */
        0002002,                    /*   77704:        SZA           IS DRIVE FAULTY OR HARD DOWN ? */
        0102030,                    /*   77705:        HLT 30B       YES, HALT 30B, "RUN" TO TRY AGAIN */
        0027700,                    /*   77706:        JMP START     NO, TRY AGAIN FOR DISC READY */
        0102011,                    /*   77707:  ADDR1 OCT 102011    */
        0102055,                    /*   77710:  ADDR2 OCT 102055    */
        0164000,                    /*   77711:  CNT   DEC -6144     */
        0000007,                    /*   77712:  D7    OCT 7         */
        0001400,                    /*   77713:  STCMD OCT 1400      */
        0000020,                    /*   77714:  B20   OCT 20        */
        0017400,                    /*   77715:  STMSK OCT 17400     */
        0000000,                    /*   77716:        NOP           */
        0000000,                    /*   77717:        NOP           */
        0000000,                    /*   77720:        NOP           */
        0000000,                    /*   77721:        NOP           */
        0000000,                    /*   77722:        NOP           */
        0000000,                    /*   77723:        NOP           */
        0000000,                    /*   77724:        NOP           */
        0000000,                    /*   77725:        NOP           */
        0000000,                    /*   77726:        NOP           */
        0000000,                    /*   77727:  STAT  NOP           STATUS CHECK SUBROUTINE */
        0107710,                    /*   77730:        CLC DC,C      SET STATUS COMMAND MODE */
        0063713,                    /*   77731:        LDA STCMD     GET STATUS COMMAND */
        0102610,                    /*   77732:        OTA DC        OUTPUT STATUS COMMAND */
        0102310,                    /*   77733:        SFS DC        WAIT FOR STATUS#1 WORD */
        0027733,                    /*   77734:        JMP *-1       */
        0107510,                    /*   77735:        LIB DC,C      B-REG = STATUS#1 WORD */
        0102310,                    /*   77736:        SFS DC        WAIT FOR STATUS#2 WORD */
        0027736,                    /*   77737:        JMP *-1       */
        0103510,                    /*   77740:        LIA DC,C      A-REG = STATUS#2 WORD */
        0127727,                    /*   77741:        JMP STAT,I    RETURN */
        0067776,                    /*   77742:  DMA   LDB DMACW     GET DMA CONTROL WORD */
        0106606,                    /*   77743:        OTB 6         OUTPUT DMA CONTROL WORD */
        0067707,                    /*   77744:        LDB ADDR1     GET MEMORY ADDRESS */
        0106702,                    /*   77745:        CLC 2         SET MEMORY ADDRESS INPUT MODE */
        0106602,                    /*   77746:        OTB 2         OUTPUT MEMORY ADDRESS TO DMA */
        0102702,                    /*   77747:        STC 2         SET WORD COUNT INPUT MODE */
        0067711,                    /*   77750:        LDB CNT       GET WORD COUNT */
        0106602,                    /*   77751:        OTB 2         OUTPUT WORD COUNT TO DMA */
        0106710,                    /*   77752:  CLDLD CLC DC        SET COMMAND INPUT MODE */
        0102501,                    /*   77753:        LIA 1         LOAD SWITCH */
        0106501,                    /*   77754:        LIB 1         REGISTER SETTINGS */
        0013712,                    /*   77755:        AND D7        ISOLATE HEAD NUMBER */
        0005750,                    /*   77756:        BLF,CLE,SLB   BIT 12=0? */
        0027762,                    /*   77757:        JMP *+3       NO,MANUAL BOOT */
        0002002,                    /*   77760:        SZA           YES,RPL BOOT. HEAD#=0? */
        0001000,                    /*   77761:        ALS           NO,HEAD#1, MAKE HEAD#=2 */
        0001720,                    /*   77762:        ALF,ALS       FORM COLD LOAD */
        0001000,                    /*   77763:        ALS           COMMAND WORD */
        0103706,                    /*   77764:        STC 6,C       ACTIVATE DMA */
        0103610,                    /*   77765:        OTA DC,C      OUTPUT COLD LOAD COMMAND */
        0102310,                    /*   77766:        SFS DC        IS COLD LOAD COMPLETED ? */
        0027766,                    /*   77767:        JMP *-1       NO, WAIT */
        0017727,                    /*   77770:        JSB STAT      YES, GET STATUS */
        0060001,                    /*   77771:        LDA 1         */
        0013715,                    /*   77772:        AND STMSK     A-REG = STATUS BITS OF STATUS#1 WD */
        0002002,                    /*   77773:        SZA           IS TRANSFER OK ? */
        0027700,                    /*   77774:        JMP START     NO,TRY AGAIN */
        0117710,                    /*   77775:  EXIT  JSB ADDR2,I   YES, EXEC LOADED PROGRAM @ 2055B */
        0000010,                    /*   77776:  DMACW ABS DC        */
        0170100 } }                 /*   77777:        ABS -START    */
    };


/* Device boot routine.

   This routine is called directly by the BOOT DS and LOAD DS commands to copy
   the device bootstrap into the upper 64 words of the logical address space.
   It is also called indirectly by a BOOT CPU or LOAD CPU command when the
   specified HP 1000 loader ROM socket contains a 12992B ROM.

   When called in response to a BOOT DS or LOAD DS command, the "unitno"
   parameter indicates the unit number specified in the BOOT command or is zero
   for the LOAD command, and "dptr" points at the DS device structure.  The
   bootstrap supports loading only from unit 0, and the command will be rejected
   if another unit is specified (e.g., BOOT DS1).  Otherwise, depending on the
   current CPU model, the BMDL or 12992B loader ROM will be copied into memory
   and configured for the DS select code.  If the CPU is a 1000, the S register
   will be set as it would be by the front-panel microcode.

   When called for a BOOT/LOAD CPU command, the "unitno" parameter indicates the
   select code to be used for configuration, and "dptr" will be NULL.  As above,
   the BMDL or 12992B loader ROM will be copied into memory and configured for
   the specified select code.  The S register is assumed to be set correctly on
   entry and is not modified.

   In either case, if the CPU is a 21xx model, the paper tape portion of the
   BMDL will be automatically configured for the select code of the paper tape
   reader.

   For the 12992B boot loader ROM for the HP 1000, the S register is set as
   follows:

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | ROM # | 0   1 |      select code      | reserved  | 0 | head  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Bit 12 must be 1 for a manual boot.  Bits 5-3 are nominally zero but are
   reserved for the target operating system.  For example, RTE uses bit 5 to
   indicate whether a standard (0) or reconfiguration (1) boot is desired.


   Implementation notes:

    1. In hardware, the BMDL was hand-configured for the disc and paper tape
       reader select codes when it was installed on a given system.  Under
       simulation, the LOAD and BOOT commands automatically configure the BMDL
       to the current select codes of the PTR and DS devices.

    2. The HP 1000 Loader ROMs manual indicates that bits 2-0 select the head to
       use, implying that heads 0-7 are valid.  However, Table 5 has entries
       only for heads 0-3, and the boot loader code will malfunction if heads
       4-7 are specified.  The code masks the head number to three bits but
       forms the Cold Load Read command by shifting the head number six bits to
       the left.  As the head field in the command is only two bits wide,
       specifying heads 4-7 will result in bit 2 being shifted into the opcode
       field, resulting in a Recalibrate command.
*/

static t_stat ds_boot (int32 unitno, DEVICE *dptr)
{
static const HP_WORD ds_preserved   = 0000073u;             /* S-register bits 5-3 and 1-0 are preserved */
static const HP_WORD ds_manual_boot = 0010000u;             /* S-register bit 12 set for a manual boot */
uint32 start;

if (dptr == NULL)                                           /* if we are being called for a BOOT/LOAD CPU */
    start = cpu_copy_loader (ds_loaders, unitno,            /*   then copy the boot loader to memory */
                             IBL_S_NOCLEAR, IBL_S_NOSET);   /*     but do not alter the S register */

else if (unitno != 0)                                       /* otherwise a BOOT DS for a non-zero unit */
    return SCPE_NOFNC;                                      /*   is rejected as unsupported */

else                                                            /* otherwise this is a BOOT/LOAD DS */
    start = cpu_copy_loader (ds_loaders, ds_dib.select_code,    /*   so copy the boot loader to memory */
                             ds_preserved, ds_manual_boot);     /*     and configure the S register if 1000 CPU */

if (start == 0)                                         /* if the copy failed */
    return SCPE_NOFNC;                                  /*   then reject the command */
else                                                    /* otherwise */
    return SCPE_OK;                                     /*   the boot loader was successfully copied */
}



/* MAC disc global SCP routines */


/* Load or unload the drive heads.

   The SCP command SET DSn UNLOADED 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.

   The library handles command validation and setting the appropriate drive unit
   status.
*/

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

return dl_load_unload (&mac_cntlr, uptr, load);         /* load or unload the heads */
}



/* MAC disc local utility routines */


/* Start a command.

   The previously prepared command is executed by calling the corresponding
   library routine.  On entry, the controller's opcode field contains the
   command to start, and the buffer contains the command word in element 0 and
   the parameters required by the command, if any, beginning in element 1.

   If the command started, the returned pointer will point at the unit to
   activate (if that unit's "wait" field is non-zero).  If the returned pointer
   is NULL, the command failed to start, and the controller status has been set
   to indicate the reason.  The interface flag is set to notify the CPU of the
   failure.


   Implementation notes:

    1. If a command that accesses the drive is attempted on a drive currently
       seeking, the returned pointer will be valid, but the unit's "wait" time
       will be zero.  The unit must not be activated (as it already is active).
       When the seek completes, the command will be executed automatically.

       If a Seek or Cold Load Read command is attempted on a drive currently
       seeking, seek completion will occur normally, but Seek Check status will
       be set.

    2. For debug printouts, we want to print the name of the command (Seek or
       Recalibrate) in progress when a new command is started.  However, when
       the library routine returns, the unit operation and controller opcode
       have been changed to reflect the new command.  Therefore, we must record
       the operation in progress before calling the library.

       The problem is in determining which unit's operation code to record.  We
       cannot blindly use the unit field from the new command, as recorded in
       the controller, as preparation has ensured only that the target unit
       number is legal but not necessarily valid.  Therefore, we must validate
       the unit number before accessing the unit's operation code.

       If the unit number is invalid, the command will not start, but the
       compiler does not know this.  Therefore, we must ensure that the saved
       operation code is initialized, or a "variable used uninitialized" warning
       will occur.
*/

static void start_command (void)
{
int32 unit, time;
UNIT *uptr;
CNTLR_OPCODE drive_command;

unit = GET_S1UNIT (mac_cntlr.spd_unit);                 /* get the (prepared) unit from the command */

if (unit <= DL_MAXDRIVE)                                /* is the unit number valid? */
    drive_command = (CNTLR_OPCODE) ds_unit [unit].OP;   /* get the opcode from the unit that will be used */
else                                                    /* the unit is invalid, so the command will not start */
    drive_command = End;                                /*   but the compiler doesn't know this! */

uptr = dl_start_command (&mac_cntlr, ds_unit, DL_MAXDRIVE); /* ask the controller to start the command */

if (uptr) {                                             /* did the command start? */
    time = uptr->wait;                                  /* save the activation time */

    if (time)                                           /* was the unit scheduled? */
        activate_unit (uptr);                           /* activate it (and clear the "wait" field) */

    if (time == 0)                                  /* was the unit busy? */
        tprintf (ds_dev, DEB_RWSC, "Unit %d %s in progress\n",
                 uptr - ds_unit, dl_opcode_name (MAC, drive_command));

    if (uptr - ds_unit > DL_MAXDRIVE)
        tprintf (ds_dev, DEB_RWSC, "Controller %s command initiated\n",
                 dl_opcode_name (MAC, mac_cntlr.opcode));
    else
        tprintf (ds_dev, DEB_RWSC, "Unit %d position %" T_ADDR_FMT "d %s command initiated\n",
                 uptr - ds_unit, uptr->pos, dl_opcode_name (MAC, mac_cntlr.opcode));
    }

else {                                                  /* the command failed to start */
    ds.flag_buffer = SET;                               /*   so set the data flag */
    io_assert (&ds_dev, ioa_ENF);                       /*     to notify the CPU */
    }

return;
}


/* Poll the interface for a new command.

   If a new command is available, and the controller is not busy, prepare the
   command for execution.  If preparation succeeded, and the command needs
   parameters before executing, set the flag to request the first one from the
   CPU.  If no parameters are needed, the command is ready to execute.

   If preparation failed, set the flag to notify the CPU.  The controller
   status contains the reason for the failure.
*/

static void poll_interface (void)
{
if (ds.cmrdy == SET && mac_cntlr.state != cntlr_busy) {     /* are the interface and controller ready? */
    buffer [0] = fifo_unload ();                            /* unload the command into the buffer */

    if (dl_prepare_command (&mac_cntlr, ds_unit, DL_MAXDRIVE)) {    /* prepare the command; did it succeed? */
        if (mac_cntlr.length) {                                     /* does the command require parameters? */
            ds.flag_buffer = SET;                                   /* set the data flag */
            io_assert (&ds_dev, ioa_ENF);                           /*   to request the first one */
            }

        else                                                        /* if not waiting for parameters */
            start_command ();                                       /*   start the command */
        }

    else {                                              /* preparation failed */
        ds.flag_buffer = SET;                           /*   so set the data flag */
        io_assert (&ds_dev, ioa_ENF);                   /*     to notify the CPU */
        }

    ds.cmrdy = CLEAR;                                   /* flush the command from the interface */
    }

return;
}


/* Poll the drives for attention requests.

   If the controller is idle and interrupts are allowed, the drives are polled
   to see if any drive is requesting attention.  If one is found, the controller
   resets that drive's Attention status, saves the drive's unit number, sets
   Drive Attention status, and waits for a command from the CPU.  The interface
   sets the flag to notify the CPU.
*/

static void poll_drives (void)
{
if (mac_cntlr.state == cntlr_idle && ds.control == SET)         /* is the controller idle and interrupts are allowed? */
    if (dl_poll_drives (&mac_cntlr, ds_unit, DL_MAXDRIVE)) {    /* poll the drives; was Attention seen? */
        ds.flag_buffer = SET;                                   /* set the data flag */
        io_assert (&ds_dev, ioa_ENF);                           /*   to request an interrupt */
        }
return;
}


/* Load a word into the FIFO.

   A word is loaded into the next available location in the FIFO, and the FIFO
   occupancy count is incremented.  If the FIFO is full on entry, the load is
   ignored.


   Implementation notes:

    1. The FIFO is implemented as circular queue to take advantage of REG_CIRC
       EXAMINE semantics.  REG->qptr is the index of the first word currently in
       the FIFO.  By specifying REG_CIRC, examining FIFO[0-n] will always
       display the words in load order, regardless of the actual array index of
       the start of the list.  The number of words currently present in the FIFO
       is kept in fifo_count (0 = empty, 1-16 = number of words available).

       If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the
       index of the new word location.  Loading stores the word there and then
       increments fifo_count.

    2. Because the load and unload routines need access to qptr in the REG
       structure for the FIFO array, a pointer to the REG is stored in the
       fifo_reg variable during device reset.
*/

static void fifo_load (uint16 data)
{
uint32 index;

if (FIFO_FULL) {                                            /* is the FIFO already full? */
    tprintf (ds_dev, DEB_BUF, "Attempted load to full FIFO, data %06o\n", data);

    return;                                                 /* return with the load ignored */
    }

index = (ds.fifo_reg->qptr + ds.fifo_count) % FIFO_SIZE;    /* calculate the index of the next available location */

ds.fifo [index] = data;                                     /* store the word in the FIFO */
ds.fifo_count = ds.fifo_count + 1;                          /* increment the count of words stored */

tprintf (ds_dev, DEB_BUF, "Data %06o loaded into FIFO (%d)\n",
         data, ds.fifo_count);

return;
}


/* Unload a word from the FIFO.

   A word is unloaded from the first location in the FIFO, and the FIFO
   occupancy count is decremented.  If the FIFO is empty on entry, the unload
   returns dummy data.


   Implementation notes:

    1. If fifo_count > 0, REG->qptr is the index of the word to remove.  Removal
       gets the word and then increments qptr (mod FIFO_SIZE) and decrements
       fifo_count.
*/

static uint16 fifo_unload (void)
{
uint16 data;

if (FIFO_EMPTY) {                                           /* is the FIFO already empty? */
    tprintf (ds_dev, DEB_BUF, "Attempted unload from empty FIFO\n");
    return 0;                                               /* return with no data */
    }

data = ds.fifo [ds.fifo_reg->qptr];                         /* get the word from the FIFO */

ds.fifo_reg->qptr = (ds.fifo_reg->qptr + 1) % FIFO_SIZE;    /* update the FIFO queue pointer */
ds.fifo_count = ds.fifo_count - 1;                          /* decrement the count of words stored */

tprintf (ds_dev, DEB_BUF, "Data %06o unloaded from FIFO (%d)\n",
         data, ds.fifo_count);

return data;
}


/* Clear the FIFO.

   The FIFO is cleared by setting the occupancy counter to zero.
*/

static void fifo_clear (void)
{
ds.fifo_count = 0;                                      /* clear the FIFO */

tprintf (ds_dev, DEB_BUF, "FIFO cleared\n");

return;
}


/* Activate the unit.

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

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

if (uptr == &ds_cntlr)
    tprintf (ds_dev, DEB_SERV, "Controller delay %d service scheduled\n",
             uptr->wait);
else
    tprintf (ds_dev, DEB_SERV, "Unit %d delay %d service scheduled\n",
             uptr - ds_unit, uptr->wait);

result = sim_activate (uptr, uptr->wait);               /* activate the unit */
uptr->wait = 0;                                         /* reset the activation time */

return result;                                          /* return the activation status */
}
