/* pdp11_hk.c - RK611/RK06/RK07 disk controller

   Copyright (c) 1993-2005, Robert M Supnik

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

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

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PUHKOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK 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 Robert M Supnik shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   hk           RK611/RK06/RK07 disk

   17-Nov-05    RMS     Removed unused variable
   13-Nov-05    RMS     Fixed overlapped seek interaction with NOP, DCLR, PACK
   16-Aug-05    RMS     Fixed C++ declaration and cast problems
   07-Jul-05    RMS     Removed extraneous externs
   18-Mar-05    RMS     Added attached test to detach routine
   03-Oct-04    RMS     Revised Unibus interface
                RMS     Fixed state of output ready for M+
   26-Mar-04    RMS     Fixed warnings with -std=c99
   25-Jan-04    RMS     Revised for device debug support
   04-Jan-04    RMS     Changed sim_fsize calling sequence
   29-Dec-03    RMS     Added 18b Qbus support
   25-Apr-03    RMS     Revised for extended file support

   This is a somewhat abstracted implementation of the RK611, more closely
   modelled on third party clones than DEC's own implementation.  In particular,
   the drive-to-controller serial communications system is simulated only at
   a level equal to the Emulex SC21.

   The RK611 functions only in 18b Unibus systems with I/O maps.  The Emulex
   SC02/C was a Qbus work-alike with a unique extension to 22b addressing.  It
   was only supported in Ultrix-11 and other third party software.

   This module includes ideas from a previous implementation by Fred Van Kempen.
*/

#if defined (VM_PDP10)                                  /* PDP10 version */
#error "RK611 is not supported on the PDP-10!"

#elif defined (VM_VAX)                                  /* VAX version */
#include "vax_defs.h"

#else                                                   /* PDP-11 version */
#include "pdp11_defs.h"
extern int32 cpu_opt;
#endif

extern uint16 *M;

#define HK_NUMDR        8                               /* #drives */
#define HK_NUMCY6       411                             /* cyl/drive */
#define HK_NUMCY7       815                             /* cyl/drive */
#define HK_NUMSF        3                               /* tracks/cyl */
#define HK_NUMSC        22                              /* sectors/track */
#define HK_NUMWD        256                             /* words/sector */
#define RK06_SIZE       (HK_NUMCY6*HK_NUMSF*HK_NUMSC*HK_NUMWD)
#define RK07_SIZE       (HK_NUMCY7*HK_NUMSF*HK_NUMSC*HK_NUMWD)
#define HK_SIZE(x)      (((x)->flags & UNIT_DTYPE)? RK07_SIZE: RK06_SIZE)
#define HK_CYL(x)       (((x)->flags & UNIT_DTYPE)? HK_NUMCY7: HK_NUMCY6)
#define HK_MAXFR        (1 << 16)

/* Flags in the unit flags word */

#define UNIT_V_WLK      (UNIT_V_UF + 0)                 /* write locked */
#define UNIT_V_DTYPE    (UNIT_V_UF + 1)                 /* disk type */
#define UNIT_V_AUTO     (UNIT_V_UF + 2)                 /* autosize */
#define UNIT_V_DUMMY    (UNIT_V_UF + 3)                 /* dummy flag */
#define UNIT_WLK        (1 << UNIT_V_WLK)
#define UNIT_DTYPE      (1 << UNIT_V_DTYPE)
#define  UNIT_RK06      (0 << UNIT_V_DTYPE)
#define  UNIT_RK07      (1 << UNIT_V_DTYPE)
#define UNIT_AUTO       (1 << UNIT_V_AUTO)
#define UNIT_DUMMY      (1 << UNIT_V_DUMMY)
#define UNIT_WPRT       (UNIT_WLK | UNIT_RO)            /* write prot */

/* Parameters in the unit descriptor */

#define CYL             u3                              /* current cylinder */
#define FNC             u4                              /* function */

/* HKCS1 - 177440 - control/status 1 */

#define CS1_GO          CSR_GO                          /* go */
#define CS1_V_FNC       1                               /* function pos */
#define CS1_M_FNC       017                             /* function mask */
#define CS1_FNC         (CS1_M_FNC << CS1_V_FNC)
#define  FNC_NOP        000                             /* no operation */
#define  FNC_PACK       001                             /* pack acknowledge */
#define  FNC_DCLR       002                             /* drive clear */
#define  FNC_UNLOAD     003                             /* unload */
#define  FNC_START      004                             /* start */
#define  FNC_RECAL      005                             /* recalibrate */
#define  FNC_OFFSET     006                             /* offset */
#define  FNC_SEEK       007                             /* seek */
#define FNC_XFER        010
#define  FNC_READ       010                             /* read */
#define  FNC_WRITE      011                             /* write */
#define  FNC_WRITEH     013                             /* write w/ headers */
#define  FNC_READH      012                             /* read w/ headers */
#define  FNC_WCHK       014                             /* write check */
#define FNC_2ND         020                             /* 2nd state flag */
#define CS1_SPA         0000040                         /* spare */
#define CS1_IE          CSR_IE                          /* int enable */
#define CS1_DONE        CSR_DONE                        /* ready */
#define CS1_V_UAE       8                               /* Unibus addr ext */
#define CS1_M_UAE       03
#define CS1_UAE         (CS1_M_UAE << CS1_V_UAE)
#define CS1_DT          0002000                         /* drive type */
#define CS1_CTO         0004000                         /* ctrl timeout NI */
#define CS1_FMT         0010000                         /* 16b/18b NI */
#define CS1_PAR         0020000                         /* par err NI */
#define CS1_DI          0040000                         /* drive intr */
#define CS1_ERR         0100000                         /* error */
#define CS1_CCLR        0100000                         /* ctrl clear */
#define CS1_RW          (CS1_DT|CS1_UAE|CS1_IE|CS1_SPA|CS1_FNC)
#define GET_FNC(x)      (((x) >> CS1_V_FNC) & CS1_M_FNC)
#define GET_UAE(x)      (((x) >> CS1_V_UAE) & CS1_M_UAE)
#define PUT_UAE(x,n)    (((x) & ~ CS1_UAE) | (((n) << CS1_V_UAE) & CS1_UAE))

/* HKWC - 177442 - word count */

/* HKBA - 177444 - base address */

#define BA_MBZ          0000001                         /* must be zero */

/* HKDA - 177446 - sector/track */

#define DA_V_SC         0                               /* sector pos */
#define DA_M_SC         037                             /* sector mask */
#define DA_V_SF         8                               /* track pos */
#define DA_M_SF         007                             /* track mask */
#define DA_MBZ          0174340
#define GET_SC(x)       (((x) >> DA_V_SC) & DA_M_SC)
#define GET_SF(x)       (((x) >> DA_V_SF) & DA_M_SF)

/* HKCS2 - 177450 - control/status 2 */

#define CS2_V_UNIT      0                               /* unit pos */
#define CS2_M_UNIT      07                              /* unit mask */
#define CS2_UNIT        (CS2_M_UNIT << CS2_V_UNIT)
#define CS2_RLS         0000010                         /* release NI */
#define CS2_UAI         0000020                         /* addr inhibit */
#define CS2_CLR         0000040                         /* controller clear */
#define CS2_IR          0000100                         /* input ready */
#define CS2_OR          0000200                         /* output ready */
#define CS2_UFE         0000400                         /* unit field err NI */
#define CS2_MDS         0001000                         /* multidrive sel NI */
#define CS2_PGE         0002000                         /* program err */
#define CS2_NEM         0004000                         /* nx mem err */
#define CS2_NED         0010000                         /* nx drive err */
#define CS2_PE          0020000                         /* parity err NI */
#define CS2_WCE         0040000                         /* write check err */
#define CS2_DLT         0100000                         /* data late NI */
#define CS2_MBZ         (CS2_CLR)
#define CS2_RW          0000037
#define CS2_ERR         (CS2_UFE | CS2_MDS | CS2_PGE | CS2_NEM | \
						 CS2_NED | CS2_PE | CS2_WCE | CS2_DLT )
#define GET_UNIT(x)     (((x) >> CS2_V_UNIT) & CS2_M_UNIT)

/* HKDS - 177452 - drive status ^ = calculated dynamically */

#define DS_DRA          0000001                         /* ^drive avail */
#define DS_OF           0000004                         /* ^offset mode */
#define DS_ACLO         0000010                         /* ^AC LO NI */
#define DS_SPLS         0000020                         /* ^speed loss NI */
#define DS_DOT          0000040                         /* ^off track NI */
#define DS_VV           0000100                         /* volume valid */
#define DS_RDY          0000200                         /* ^drive ready */
#define DS_DT           0000400                         /* ^drive type */
#define DS_WRL          0004000                         /* ^write locked */
#define DS_PIP          0020000                         /* pos in progress */
#define DS_ATA          0040000                         /* attention active */
#define DS_VLD          0100000                         /* ^status valid */
#define DS_MBZ          0013002

/* HKER - 177454 - error status */

#define ER_ILF          0000001                         /* illegal func */
#define ER_SKI          0000002                         /* seek incomp */
#define ER_NXF          0000004                         /* non-exec func */
#define ER_PAR          0000010                         /* parity err */
#define ER_FER          0000020                         /* format err NI */
#define ER_DTY          0000040                         /* drive type err */
#define ER_ECH          0000100                         /* ECC hard err NI */
#define ER_BSE          0000200                         /* bad sector err NI */
#define ER_HCR          0000400                         /* hdr CRC err NI */
#define ER_AOE          0001000                         /* addr ovflo err */
#define ER_IAE          0002000                         /* invalid addr err */
#define ER_WLE          0004000                         /* write lock err */
#define ER_DTE          0010000                         /* drive time err NI */
#define ER_OPI          0020000                         /* op incomplete */
#define ER_UNS          0040000                         /* drive unsafe */
#define ER_DCK          0100000                         /* data check NI */

/* HKAS - 177456 - attention summary/offset */

#define AS_U0           0000400                         /* unit 0 flag */
#define AS_OF           0000277                         /* offset mask */

/* HKDC - 177460 - desired cylinder */

#define DC_V_CY         0                               /* cylinder pos */
#define DC_M_CY         0001777                         /* cylinder mask */
#define DC_MBZ          0176000
#define GET_CY(x)       (((x) >> DC_V_CY) & DC_M_CY)
#define GET_DA(c,fs)    ((((GET_CY (c) * HK_NUMSF) + \
                        GET_SF (fs)) * HK_NUMSC) + GET_SC (fs))

/* Spare - 177462 - read/write */

#define XM_KMASK        0177700                         /* Qbus XM key mask */
#define XM_KEY          0022000                         /* Qbus XM "key" */
#define XM_MMASK        0000077                         /* Qbus XM mask */
#define SC02C           (!UNIBUS && ((hkspr & XM_KMASK) == XM_KEY))

/* HKDB - 177464 - read/write */

/* HKMR - 177466 - maintenance register 1 */

#define MR_V_MS         0                               /* message select */
#define MR_M_MS         03
#define MR_MS           (MR_M_MS << MR_V_MS)
#define GET_MS(x)       (((x) >> MR_V_MS) & MR_M_MS)
#define MR_PAR          0000020                         /* force even parity */
#define MR_DMD          0000040                         /* diagnostic mode */
#define MR_RW           0001777

/* HKEC1 - 177470 - ECC status 1 - always reads as 0 */
/* HKEC2 - 177472 - ECC status 2 - always reads as 0 */

/* HKMR2 - 177474 - maintenance register 2 */

#define AX_V_UNIT       0                               /* unit #, all msgs */
#define AX_PAR          0100000                         /* parity, all msgs */

#define A0_DRA          0000040                         /* drive avail */
#define A0_VV           0000100                         /* vol valid */
#define A0_RDY          0000200                         /* drive ready */
#define A0_DT           0000400                         /* drive type */
#define A0_FMT          0001000                         /* format NI */ 
#define A0_OF           0002000                         /* offset mode */
#define A0_WRL          0004000                         /* write lock */
#define A0_SPO          0010000                         /* spindle on */
#define A0_PIP          0020000                         /* pos in prog */
#define A0_ATA          0040000                         /* attention */

#define A1_SRV          0000020                         /* servo */
#define A1_HHM          0000040                         /* heads home */
#define A1_BHM          0000100                         /* brushes home */
#define A1_DOR          0000200                         /* door latched */
#define A1_CAR          0000400                         /* cartridge present */
#define A1_SPD          0001000                         /* speed ok */
#define A1_FWD          0002000                         /* seek fwd */
#define A1_REV          0004000                         /* seek rev */
#define A1_LDH          0010000                         /* loading heads NI */
#define A1_RTZ          0020000                         /* return to zero */
#define A1_UNL          0040000                         /* unloading heads */

#define A2_V_DIF        4                               /* cyl diff */
#define A2_M_DIF        0777

#define A3_V_SNO        3                               /* serial # */

/* HKMR3 - 177476 - maintenance register 3 */

#define B0_IAE          0000040                         /* invalid addr */
#define B0_ACLO         0000100                         /* AC LO NI */
#define B0_FLT          0000200                         /* fault */
#define B0_NXF          0000400                         /* non exec fnc */
#define B0_CDP          0001000                         /* msg parity err */
#define B0_SKI          0002000                         /* seek incomp */
#define B0_WLE          0004000                         /* write lock err */
#define B0_SLO          0010000                         /* speed low NI */
#define B0_OFT          0020000                         /* off track NI */
#define B0_UNS          0040000                         /* rw unsafe NI */

#define B1_SCE          0000020                         /* sector err NI */
#define B1_NWC          0000040                         /* no write curr NI */
#define B1_NWT          0000100                         /* no write trans NI */
#define B1_HFL          0000200                         /* head fault NI */
#define B1_MHS          0000400                         /* multiselect NI */
#define B1_IDX          0001000                         /* index err NI */
#define B1_TRI          0002000                         /* tribit err NI */
#define B1_SVE          0004000                         /* servo err NI */
#define B1_SKI          0010000                         /* seek no mot */
#define B1_LIM          0020000                         /* seek limit NI */
#define B1_SVU          0040000                         /* servo unsafe NI */

#define B2_V_CYL        4                               /* cylinder */

#define B3_V_SEC        4                               /* sector */
#define B3_V_DHA        9                               /* decoded head */

/* Read header */

#define RDH1_V_CYL      0                               /* cylinder */
#define RDH2_V_SEC      0                               /* sector */
#define RDH2_V_DHA      5                               /* decoded head */
#define RDH2_GOOD       0140000                         /* good sector flags */

extern int32 int_req[IPL_HLVL];
extern FILE *sim_deb;

uint16 *hkxb = NULL;                                    /* xfer buffer */
int32 hkcs1 = 0;                                        /* control/status 1 */
int32 hkwc = 0;                                         /* word count */
int32 hkba = 0;                                         /* bus address */
int32 hkda = 0;                                         /* track/sector */
int32 hkcs2 = 0;                                        /* control/status 2 */
int32 hkds[HK_NUMDR] = { 0 };                           /* drive status */
int32 hker[HK_NUMDR] = { 0 };                           /* error status */
int32 hkof = 0;                                         /* offset */
int32 hkmr = 0;                                         /* maint registers */
int32 hkmr2 = 0;
int32 hkmr3 = 0;
int32 hkdc = 0;                                         /* cylinder */
int32 hkspr = 0;                                        /* spare */
int32 hk_cwait = 5;                                     /* command time */
int32 hk_swait = 10;                                    /* seek time */
int32 hk_rwait = 10;                                    /* rotate time */
int32 hk_min2wait = 300;                                /* min time to 2nd int */
int16 hkdb[3] = { 0 };                                  /* data buffer silo */
int16 hk_off[HK_NUMDR] = { 0 };                         /* saved offset */
int16 hk_dif[HK_NUMDR] = { 0 };                         /* cylinder diff */
static int32 reg_in_drive[16] = {
 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

DEVICE hk_dev;
t_stat hk_rd (int32 *data, int32 PA, int32 access);
t_stat hk_wr (int32 data, int32 PA, int32 access);
t_stat hk_svc (UNIT *uptr);
t_stat hk_reset (DEVICE *dptr);
t_stat hk_boot (int32 unitno, DEVICE *dptr);
t_stat hk_attach (UNIT *uptr, char *cptr);
t_stat hk_detach (UNIT *uptr);
int32 hk_rdmr2 (int32 msg);
int32 hk_rdmr3 (int32 msg);
void update_hkcs (int32 flags, int32 drv);
void update_hkds (int32 drv);
void hk_cmderr (int32 err, int32 drv);
void hk_go (int32 drv);
t_stat hk_set_size (UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat hk_set_bad (UNIT *uptr, int32 val, char *cptr, void *desc);

extern t_stat pdp11_bad_block (UNIT *uptr, int32 sec, int32 wds);

/* HK data structures

   hk_dev       HK device descriptor
   hk_unit      HK unit list
   hk_reg       HK register list
   hk_mod       HK modifier list
*/

DIB hk_dib = {
    IOBA_HK, IOLN_HK, &hk_rd, &hk_wr,
    1, IVCL (HK), VEC_HK, { NULL }
    };

UNIT hk_unit[] = {
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) },
    { UDATA (&hk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_AUTO+
             UNIT_ROABLE+UNIT_RK06, RK06_SIZE) }
    };

REG hk_reg[] = {
    { GRDATA (HKCS1, hkcs1, DEV_RDX, 16, 0) },
    { GRDATA (HKWC, hkwc, DEV_RDX, 16, 0) },
    { GRDATA (HKBA, hkba, DEV_RDX, 16, 0) },
    { GRDATA (HKDA, hkda, DEV_RDX, 16, 0) },
    { GRDATA (HKCS2, hkcs2, DEV_RDX, 16, 0) },
    { BRDATA (HKDS, hkds, DEV_RDX, 16, HK_NUMDR) },
    { BRDATA (HKER, hker, DEV_RDX, 16, HK_NUMDR) },
    { BRDATA (HKDB, hkdb, DEV_RDX, 16, 3) },
    { GRDATA (HKDC, hkdc, DEV_RDX, 16, 0) },
    { GRDATA (HKOF, hkof, DEV_RDX, 8, 0) },
    { GRDATA (HKMR, hkmr, DEV_RDX, 16, 0) },
    { GRDATA (HKMR2, hkmr2, DEV_RDX, 16, 0), REG_RO },
    { GRDATA (HKMR3, hkmr3, DEV_RDX, 16, 0), REG_RO },
    { GRDATA (HKSPR, hkspr, DEV_RDX, 16, 0) },
    { FLDATA (INT, IREQ (HK), INT_V_HK) },
    { FLDATA (ERR, hkcs1, CSR_V_ERR) },
    { FLDATA (DONE, hkcs1, CSR_V_DONE) },
    { FLDATA (IE, hkcs1, CSR_V_IE) },
    { DRDATA (CTIME, hk_cwait, 24), REG_NZ + PV_LEFT },
    { DRDATA (STIME, hk_swait, 24), REG_NZ + PV_LEFT },
    { DRDATA (RTIME, hk_rwait, 24), REG_NZ + PV_LEFT },
    { DRDATA (MIN2TIME, hk_min2wait, 24), REG_NZ + PV_LEFT + REG_HRO },
    { URDATA (FNC, hk_unit[0].FNC, DEV_RDX, 5, 0,
              HK_NUMDR, REG_HRO) },
    { URDATA (CYL, hk_unit[0].CYL, DEV_RDX, 10, 0,
              HK_NUMDR, REG_HRO) },
    { BRDATA (OFFSET, hk_off, DEV_RDX, 16, HK_NUMDR), REG_HRO },
    { BRDATA (CYLDIF, hk_dif, DEV_RDX, 16, HK_NUMDR), REG_HRO },
    { URDATA (CAPAC, hk_unit[0].capac, 10, T_ADDR_W, 0,
              HK_NUMDR, PV_LEFT | REG_HRO) },
    { GRDATA (DEVADDR, hk_dib.ba, DEV_RDX, 32, 0), REG_HRO },
    { GRDATA (DEVVEC, hk_dib.vec, DEV_RDX, 16, 0), REG_HRO },
    { NULL }
    };

MTAB hk_mod[] = {
    { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL },
    { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL },
    { UNIT_DUMMY, 0, NULL, "BADBLOCK", &hk_set_bad },
    { (UNIT_DTYPE+UNIT_ATT), UNIT_RK06 + UNIT_ATT,
      "RK06", NULL, NULL },
    { (UNIT_DTYPE+UNIT_ATT), UNIT_RK07 + UNIT_ATT,
      "RK07", NULL, NULL },
    { (UNIT_AUTO+UNIT_DTYPE+UNIT_ATT), UNIT_RK06,
      "RK06", NULL, NULL },
    { (UNIT_AUTO+UNIT_DTYPE+UNIT_ATT), UNIT_RK07,
      "RK07", NULL, NULL },
    { (UNIT_AUTO+UNIT_ATT), UNIT_AUTO, "autosize", NULL, NULL },
    { UNIT_AUTO, UNIT_AUTO, NULL, "AUTOSIZE", NULL },
    { (UNIT_AUTO+UNIT_DTYPE), UNIT_RK06,
      NULL, "RK06", &hk_set_size },
    { (UNIT_AUTO+UNIT_DTYPE), UNIT_RK07,
      NULL, "RK07", &hk_set_size }, 
    { MTAB_XTD|MTAB_VDV, 0040, "ADDRESS", "ADDRESS",
      &set_addr, &show_addr, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
      &set_vec, &show_vec, NULL },
    { 0 }
    };

DEVICE hk_dev = {
    "HK", hk_unit, hk_reg, hk_mod,
    HK_NUMDR, DEV_RDX, 24, 1, DEV_RDX, 16,
    NULL, NULL, &hk_reset,
    &hk_boot, &hk_attach, &hk_detach,
    &hk_dib, DEV_DISABLE | DEV_UBUS | DEV_Q18 | DEV_DEBUG
    };

/* I/O dispatch routines, I/O addresses 17777440 - 17777476 */

t_stat hk_rd (int32 *data, int32 PA, int32 access)
{
int32 drv, i, j;

drv = GET_UNIT (hkcs2);                                 /* get current unit */
j = (PA >> 1) & 017;                                    /* get reg offset */
if (reg_in_drive[j] && (hk_unit[drv].flags & UNIT_DIS)) {       /* nx disk */
    hkcs2 = hkcs2 | CS2_NED;                            /* set error flag */
    update_hkcs (0, drv);
    *data = 0;
    return SCPE_OK;
    }

update_hkcs (0, drv);                                   /* update status */
switch (j) {                                            /* decode PA<4:1> */

    case 000:                                           /* HKCS1 */
        *data = hkcs1;
        break;

    case 001:                                           /* HKWC */
        *data = hkwc;
        break;

    case 002:                                           /* HKBA */
        *data = hkba = hkba & ~BA_MBZ;
        break;

    case 003:                                           /* HKDA */
        *data = hkda = hkda & ~DA_MBZ;
        break;

    case 004:                                           /* HKCS2 */
        *data = hkcs2 = (hkcs2 & ~CS2_MBZ) | CS2_IR;
        break;

    case 005:                                           /* HKDS */
        *data = hkds[drv];
        break;

    case 006:                                           /* HKER */
        *data = hker[drv];
        break;

    case 007:                                           /* HKAS */
        *data = hkof;
        for (i = 0; i < HK_NUMDR; i++)
            if (hkds[i] & DS_ATA) *data = *data | (AS_U0 << i);
        break;

    case 010:                                           /* HKDC */
        *data = hkdc = hkdc & ~DC_MBZ;
        break;

    case 011:                                           /* spare */
        *data = hkspr;
        break;

    case 012:                                           /* HKDB */
        *data = hkdb[0];                                /* top of silo */
        hkdb[0] = hkdb[1];                              /* ripple silo */
        hkdb[1] = hkdb[2];
        hkdb[2] = 0;                                    /* just for READH */
        break;

    case 013:                                           /* HKMR */
        *data = hkmr;
        break;

    case 014:                                           /* HKEC1 */
    case 015:                                           /* HKEC2 */
        *data = 0;                                      /* no ECC */
        break;

    case 016:                                           /* HKMR2 */
        *data = hkmr2 = hk_rdmr2 (GET_MS (hkmr));
        break;

    case 017:                                           /* HKMR3 */
        *data = hkmr3 = hk_rdmr3 (GET_MS (hkmr));
        break;
        }

return SCPE_OK;
}

t_stat hk_wr (int32 data, int32 PA, int32 access)
{
int32 drv, i, j;
UNIT *uptr;

drv = GET_UNIT (hkcs2);                                 /* get current unit */
uptr = hk_dev.units + drv;                              /* get unit */
j = (PA >> 1) & 017;                                    /* get reg offset */
if ((hkcs1 & CS1_GO) &&                                 /* busy? */
    !(((j == 0) && (data & CS1_CCLR)) ||                /* not cclr or sclr? */
      ((j == 4) && (data & CS2_CLR)))) {
        hkcs2 = hkcs2 | CS2_PGE;                        /* prog error */
        update_hkcs (0, drv);
        return SCPE_OK;
        }

switch (j) {                                            /* decode PA<4:1> */

    case 000:                                           /* HKCS1 */
        if (data & CS1_CCLR) {                          /* controller reset? */
            hkcs1 = CS1_DONE;                           /* CS1 = done */
            hkcs2 = CS2_IR;                             /* CS2 = ready */
            hkmr = hkmr2 = hkmr3 = 0;                   /* maint = 0 */
            hkda = hkdc = 0;
            hkba = hkwc = 0;
            hkspr = hkof = 0;
            CLR_INT (HK);                               /* clr int */
            for (i = 0; i < HK_NUMDR; i++) {            /* stop data xfr */
                if (sim_is_active (&hk_unit[i]) &&
                    ((uptr->FNC & CS1_M_FNC) >= FNC_XFER))
                sim_cancel (&hk_unit[i]);
                }
            drv = 0;
            break;
            }
        if (data & CS1_IE) {                            /* setting IE? */
            if (data & CS1_DONE) SET_INT (HK);          /* write to DONE+IE? */
            }
        else CLR_INT (HK);                              /* no, clr intr */
        hkcs1 = (hkcs1 & ~CS1_RW) | (data & CS1_RW);    /* merge data */
        if (SC02C) hkspr = (hkspr & ~CS1_M_UAE) | GET_UAE (hkcs1);
        if ((data & CS1_GO) && !(hkcs1 & CS1_ERR))      /* go? */
            hk_go (drv);
        break;  

    case 001:                                           /* HKWC */
        hkwc = data;
        break;

    case 002:                                           /* HKBA */
        hkba = data & ~BA_MBZ;
        break;

    case 003:                                           /* HKDA */
        hkda = data & ~DA_MBZ;
        break;

    case 004:                                           /* HKCS2 */
        if (data & CS2_CLR) hk_reset (&hk_dev);         /* init? */
        else hkcs2 = (hkcs2 & ~CS2_RW) | (data & CS2_RW) | CS2_IR;
        drv = GET_UNIT (hkcs2);
        break;

    case 007:                                           /* HKAS */
        hkof = data & AS_OF;
        break;

    case 010:                                           /* HKDC */
        hkdc = data & ~DC_MBZ;
        break;

    case 011:                                           /* spare */
        hkspr = data;
        if (SC02C) hkcs1 = PUT_UAE (hkcs1, hkspr & 03); /* SC02C? upd UAE */
        break;

    case 012:                                           /* HKDB */
        hkdb[0] = data;
        break;

    case 013:                                           /* HKMR */
        hkmr = data & MR_RW;
        break;

    default:                                            /* all others RO */
        break;
        }                                               /* end switch */

update_hkcs (0, drv);                                   /* update status */
return SCPE_OK;
}

/* Initiate operation - go set, not previously set */

void hk_go (int32 drv)
{
int32 fnc, t;
UNIT *uptr;

static int32 fnc_nxf[16] = {
    0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0
    };
static int32 fnc_att[16] = {
    0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0
    };
static int32 fnc_rdy[16] = {
    0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0
    };
static int32 fnc_cyl[16] = {
    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0
    };

fnc = GET_FNC (hkcs1);
if (DEBUG_PRS (hk_dev)) fprintf (sim_deb,
    ">>HK%d go: fnc=%o, ds=%o, cyl=%o, da=%o, ba=%o, wc=%o\n",
    drv, fnc, hkds[drv], hkdc, hkda, hkba, hkwc);
uptr = hk_dev.units + drv;                              /* get unit */
if (fnc != FNC_NOP) hkmr = hkmr & ~MR_MS;               /* !nop, clr msg sel */
if (uptr->flags & UNIT_DIS) {                           /* nx unit? */
    hkcs2 = hkcs2 | CS2_NED;                            /* set error flag */
    update_hkcs (CS1_DONE, drv);                        /* done */
    return;
    }
if (((hkcs1 & CS1_DT) != 0) !=                          /* dtype mismatch? */
    ((uptr->flags & UNIT_DTYPE) != 0)) {
        hk_cmderr (ER_DTY, drv);                        /* type error */
        return;
        }
if (fnc_nxf[fnc] && ((hkds[drv] & DS_VV) == 0)) {       /* need vol valid? */
    hk_cmderr (ER_NXF, drv);                            /* non exec func */
    return;
    }
if (fnc_att[fnc] && !(uptr->flags & UNIT_ATT)) {        /* need attached? */
    hk_cmderr (ER_UNS, drv);                            /* unsafe */
    return;
    }
if (fnc_rdy[fnc] && sim_is_active (uptr)) return;       /* need inactive? */
if (fnc_cyl[fnc] &&                                     /* need valid cyl */
   ((GET_CY (hkdc) >= HK_CYL (uptr)) ||                 /* bad cylinder */
    (GET_SF (hkda) >= HK_NUMSF) ||                      /* bad surface */
    (GET_SC (hkda) >= HK_NUMSC))) {                     /* or bad sector? */
	hk_cmderr (ER_IAE, drv);                            /* illegal addr */
	return;
	}

hkcs1 = (hkcs1 | CS1_GO) & ~CS1_DONE;                   /* set go, clear done */
switch (fnc) {                                          /* case on function */

/* Instantaneous functions (unit may be busy, can't schedule thread) */

    case FNC_DCLR:                                      /* drive clear */
        hkds[drv] &= ~DS_ATA;                           /* clr ATA */        
        hker[drv] = 0;                                  /* clear errors */
    case FNC_NOP:                                       /* no operation */
        update_hkcs (CS1_DONE, drv);                    /* done */
        break;
    case FNC_PACK:                                      /* pack acknowledge */
        hkds[drv] = hkds[drv] | DS_VV;                  /* set volume valid */
        update_hkcs (CS1_DONE, drv);                    /* done */
        break;

/* "Fast functions" finish in less than 15 usec */

    case FNC_START:                                     /* start spindle */
    case FNC_UNLOAD:                                    /* unload */
        uptr->FNC = fnc;                                /* save function */
        sim_activate (uptr, hk_cwait);                  /* schedule */
        return;

/* Positioning functions provide two interrupts - an immediate interrupt
   on ctrl done and a second one (if ctrl ready) when the seek is complete */

    case FNC_OFFSET:                                    /* offset mode */
    case FNC_RECAL:                                     /* recalibrate */
    case FNC_SEEK:                                      /* seek */
        hkds[drv] = hkds[drv] | DS_PIP;                 /* set positioning */
        uptr->FNC = fnc;                                /* save function */
        sim_activate (uptr, hk_cwait);                  /* schedule */
        return;

/* Data transfer functions lock the controller for the duration */

    case FNC_WRITEH:                                    /* write headers */
    case FNC_WRITE:                                     /* write */
        hk_off[drv] = 0;                                /* clr offset */
    case FNC_WCHK:                                      /* write check */
    case FNC_READ:                                      /* read */
    case FNC_READH:                                     /* read headers */
        hk_dif[drv] = hkdc - uptr->CYL;                 /* cyl diff */
        t = abs (hk_dif[drv]);                          /* |cyl diff| */
        sim_activate (uptr, hk_rwait + (hk_swait * t)); /* Schedule */
        uptr->FNC = fnc;                                /* save function */
        uptr->CYL = hkdc;                               /* update cyl */
        return;

    default:
        hk_cmderr (ER_ILF, drv);                        /* not supported */
        break;
        }
return;
}

/* Service unit timeout

   Complete movement or data transfer command
   Unit must exist - can't remove an active unit
   Unit must be attached - detach cancels in progress operations
*/

t_stat hk_svc (UNIT *uptr)
{
int32 i, t, dc, fnc, err;
int32 wc, awc, da;
uint32 drv, ba;
uint16 comp;

drv = (uint32) (uptr - hk_dev.units);                   /* get drv number */
fnc = uptr->FNC & CS1_M_FNC;                            /* get function */
switch (fnc) {                                          /* case on function */

/* Fast commands - start spindle only provides one interrupt
   because ATTACH implicitly spins up the drive */

    case FNC_UNLOAD:                                    /* unload */
        hk_detach (uptr);                               /* detach unit */
    case FNC_START:                                     /* start spindle */
        update_hkcs (CS1_DONE, drv);                    /* done */
        break;

/* Positioning commands provide two interrupts, an immediate controller done
   and a delayed drive interrupt */

    case FNC_OFFSET:                                    /* offset */
        if (uptr->FNC & FNC_2ND) {                      /* 2nd int? */
            hkds[drv] = (hkds[drv] & ~DS_PIP) | DS_ATA; /* upd sta */
            update_hkcs (CS1_DI, drv);                  /* drive intr */
            }
        else {
            uptr->FNC = uptr->FNC | FNC_2ND;            /* second state */
            hk_off[drv] = hkof & AS_OF;                 /* save offset */
            sim_activate (uptr, hk_min2wait);           /* wait for compl */
            update_hkcs (CS1_DONE, drv);                /* done */
            }           
        break;

    case FNC_RECAL:                                     /* recalibrate */
    case FNC_SEEK:                                      /* seek */
        if (uptr->FNC & FNC_2ND) {                      /* 2nd int? */
            hkds[drv] = (hkds[drv] & ~DS_PIP) | DS_ATA; /* upd sta */
            update_hkcs (CS1_DI, drv);                  /* drive intr */
            }
        else {
            uptr->FNC = uptr->FNC | FNC_2ND;            /* second state */
            hk_off[drv] = 0;                            /* clr offset */
            dc = (fnc == FNC_SEEK)? hkdc: 0;            /* get cyl */
            hk_dif[drv] = dc - uptr->CYL;               /* cyl diff */
            t = abs (hk_dif[drv]) * hk_swait;           /* |cyl diff| */
            if (t < hk_min2wait) t = hk_min2wait;       /* min time */
            uptr->CYL = dc;                             /* save cyl */          
            sim_activate (uptr, t);                     /* schedule */
            update_hkcs (CS1_DONE, drv);                /* done */
            }
        break;

/* Data transfer commands only generate one interrupt */

    case FNC_READH:
        hkdb[0] = uptr->CYL << RDH1_V_CYL;              /* first word */
        hkdb[1] = (GET_SC (hkda) << RDH2_V_SEC) |       /* second word */
            (1 << (GET_SF (hkda) + RDH2_V_DHA)) | RDH2_GOOD;
        hkdb[2] = hkdb[0] ^ hkdb[1];                    /* checksum */
        update_hkcs (CS1_DONE, drv);                    /* done */
        break;

    case FNC_WRITE:                                     /* write */
        if (uptr->flags & UNIT_WPRT) {                  /* write locked? */
            hk_cmderr (ER_WLE, drv);                    /* command error */
            return SCPE_OK;
            }
    case FNC_WCHK:                                      /* write check */
    case FNC_READ:                                      /* read */
        if (SC02C) ba = ((hkspr & XM_MMASK) << 16) | hkba;      /* 22b addr? */
        else ba = (GET_UAE (hkcs1) << 16) | hkba;       /* no, 18b addr */
        da = GET_DA (hkdc, hkda) * HK_NUMWD;            /* get disk addr */
        wc = 0200000 - hkwc;                            /* get true wc */

        if ((da + wc) > HK_SIZE (uptr)) {               /* disk overrun? */
            hker[drv] = hker[drv] | ER_AOE;             /* set err */
            hkds[drv] = hkds[drv] | DS_ATA;             /* set attn */
            wc = HK_SIZE (uptr) - da;                   /* trim xfer */
            if (da >= HK_SIZE (uptr)) {                 /* none left? */
                update_hkcs (CS1_DONE, drv);            /* then done */
                break;
                }
            }

        err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET);
        if (uptr->FNC == FNC_WRITE) {                   /* write? */
            if (hkcs2 & CS2_UAI) {                      /* no addr inc? */
                if (t = Map_ReadW (ba, 2, &comp)) {     /* get 1st wd */
                    wc = 0;                             /* NXM, no xfr */
                    hkcs2 = hkcs2 | CS2_NEM;            /* set nxm err */
                    }
                for (i = 0; i < wc; i++) hkxb[i] = comp;
                }
            else {                                      /* normal */
                if (t = Map_ReadW (ba, wc << 1, hkxb)) { /* get buf */
                    wc = wc - (t >> 1);                 /* NXM, adj wc */
                    hkcs2 = hkcs2 | CS2_NEM;            /* set nxm err */
                    }
                ba = ba + (wc << 1);                    /* adv ba */
                }
            awc = (wc + (HK_NUMWD - 1)) & ~(HK_NUMWD - 1);
            for (i = wc; i < awc; i++) hkxb[i] = 0;     /* fill buf */
            if (wc && !err) {                           /* write buf */
                fxwrite (hkxb, sizeof (uint16), wc, uptr->fileref);
                err = ferror (uptr->fileref);
                }
            }                                           /* end if wr */
        else if (uptr->FNC == FNC_READ) {               /* read? */
            i = fxread (hkxb, sizeof (uint16), wc, uptr->fileref);
            err = ferror (uptr->fileref);
            for ( ; i < wc; i++) hkxb[i] = 0;           /* fill buf */
            if (hkcs2 & CS2_UAI) {                      /* no addr inc? */
                if (t = Map_WriteW (ba, 2, &hkxb[wc - 1])) {
                    wc = 0;                             /* NXM, no xfr */
                    hkcs2 = hkcs2 | CS2_NEM;            /* set nxm err */
                    }
                }
            else {                                      /* normal */
                if (t = Map_WriteW (ba, wc << 1, hkxb)) {       /* put buf */
                    wc = wc - (t >> 1);                 /* NXM, adj wc */
                    hkcs2 = hkcs2 | CS2_NEM;            /* set nxm err */
                    }
                ba = ba + (wc << 1);                    /* adv ba */
                }
            }                                           /* end if read */
        else {                                          /* wchk */                  
            i = fxread (hkxb, sizeof (uint16), wc, uptr->fileref);
            err = ferror (uptr->fileref);
            for ( ; i < wc; i++) hkxb[i] = 0;           /* fill buf */
            awc = wc;
            for (wc = 0; wc < awc; wc++) {              /* loop thru buf */
                if (Map_ReadW (ba, 2, &comp)) {         /* read word */
                    hkcs2 = hkcs2 | CS2_NEM;            /* set error */
                    break;
                    }
                if (comp != hkxb[wc]) {                 /* compare wd */
                    hkcs2 = hkcs2 | CS2_WCE;            /* set error */
                    break;
                    }
                if ((hkcs2 & CS2_UAI) == 0) ba = ba + 2;
                }
            }                                           /* end else wchk */

        hkwc = (hkwc + wc) & 0177777;                   /* final word count */
        hkba = (ba & 0177777) & ~BA_MBZ;                /* lower 16b */
        hkcs1 = PUT_UAE (hkcs1, ba >> 16);              /* upper 2b */
        if (SC02C)                                      /* SC02C? upper 6b */
            hkspr = (hkspr & ~XM_MMASK) | ((ba >> 16) & XM_MMASK);
        da = da + wc + (HK_NUMWD - 1);
        da = da / HK_NUMWD;
        hkda = da % HK_NUMSC;
        da = da / HK_NUMSC;
        hkda = hkda | ((da % HK_NUMSF) << DA_V_SF);
        hkdc = da / HK_NUMSF;

        if (err != 0) {                                 /* error? */
            hk_cmderr (ER_PAR, drv);                    /* set drive error */
            perror ("HK I/O error");
            clearerr (uptr->fileref);
            return SCPE_IOERR;
            }

    case FNC_WRITEH:                                    /* write headers stub */
        update_hkcs (CS1_DONE, drv);                    /* set done */
        break;
        }                                               /* end case func */

if (DEBUG_PRS (hk_dev)) fprintf (sim_deb,
    ">>HK%d done: fnc=%o, cs1 = %o, cs2 = %o, ds=%o, cyl=%o, da=%o, ba=%o, wc=%o\n",
    drv, fnc, hkcs1, hkcs2, hkds[drv], hkdc, hkda, hkba, hkwc);
return SCPE_OK;
}

/* Controller status update

   Check for done transition
   Update drive status
   Update HKCS1
   Update interrupt request
*/

void update_hkcs (int32 flag, int32 drv)
{
int32 i;

update_hkds (drv);                                      /* upd drv status */
if (flag & CS1_DONE) hkcs1 = hkcs1 & ~CS1_GO;           /* clear go */
if (hkcs1 & CS1_IE) {                                   /* intr enable? */
    if (((flag & CS1_DONE) && ((hkcs1 & CS1_DONE) == 0)) ||
        ((flag & CS1_DI) && (hkcs1 & CS1_DONE)))        /* done 0->1 or DI? */
        SET_INT (HK);
    }
else CLR_INT (HK);
hkcs1 = (hkcs1 & (CS1_DT|CS1_UAE|CS1_DONE|CS1_IE|CS1_SPA|CS1_FNC|CS1_GO)) | flag;
for (i = 0; i < HK_NUMDR; i++) {                        /* if ATA, set DI */
    if (hkds[i] & DS_ATA) hkcs1 = hkcs1 | CS1_DI;
    }
if (hker[drv] | (hkcs1 & (CS1_PAR | CS1_CTO)) |         /* if err, set ERR */
    (hkcs2 & CS2_ERR)) hkcs1 = hkcs1 | CS1_ERR;
return;
}

/* Drive status update */

void update_hkds (int32 drv)
{
if (hk_unit[drv].flags & UNIT_DIS) {                    /* disabled? */
    hkds[drv] = hker[drv] = 0;                          /* all clear */
    return;
    }
hkds[drv] = (hkds[drv] & (DS_VV | DS_PIP | DS_ATA)) | DS_VLD | DS_DRA;
if (hk_unit[drv].flags & UNIT_ATT) {                    /* attached? */
    if (!sim_is_active (&hk_unit[drv]))                 /* not busy? */
        hkds[drv] = hkds[drv] | DS_RDY;                 /* set RDY */
    if (hker[drv]) hkds[drv] = hkds[drv] | DS_ATA;      /* err? set ATA */
    if (hk_off[drv]) hkds[drv] = hkds[drv] | DS_OF;     /* offset? set OF */
    if (hk_unit[drv].flags & UNIT_WPRT)                 /* write locked? */
        hkds[drv] = hkds[drv] | DS_WRL;                 /* set WRL */
    }
else {
    hkds[drv] = hkds[drv] & ~(DS_PIP | DS_VV);          /* no, clr PIP,VV */
    hker[drv] = 0;                                      /* no errors */
    }
if (hk_unit[drv].flags & UNIT_RK07) hkds[drv] = hkds[drv] | DS_DT;
return;
}

/* Set error and abort command */

void hk_cmderr (int32 err, int32 drv)
{
hker[drv] = hker[drv] | err;                            /* set error */
hkds[drv] = hkds[drv] | DS_ATA;                         /* set attn */
update_hkcs (CS1_DONE, drv);                            /* set done */
return;
}

/* Diagnostic registers

   It's unclear whether the drivers actually use these values, but the
   Emulex controller bothers to implement them, so we will too */

int32 hk_mrpar (int32 v)
{
int32 bit, wrk;

wrk = v & 077777;                                       /* par on 15b */
v = wrk | ((hkmr & MR_PAR)? 0: AX_PAR);                 /* even/odd */
while (wrk) {                                           /* while 1's */
    bit = wrk & (-wrk);                                 /* lowest 1 */
    wrk = wrk & ~bit;                                   /* clear */
    v = v ^ AX_PAR;                                     /* xor parity */
    }
return v;
}

int32 hk_rdmr2 (int32 msg)
{
int32 drv = GET_UNIT (hkcs2);
int32 v = drv << AX_V_UNIT;
UNIT *uptr = hk_dev.units + drv;
int32 fnc = uptr->FNC & CS1_M_FNC;

switch (msg) {

    case 0:                                             /* message A0 */
        v = v | ((hkds[drv] & DS_ATA)? A0_ATA: 0) |
                ((hkds[drv] & DS_PIP)? A0_PIP: 0) |
                ((uptr->flags & UNIT_WPRT)? A0_WRL: 0) |
                ((hk_off[drv])? A0_OF: 0) |
                ((uptr->flags & UNIT_RK07)? A0_DT: 0) |
                ((hkds[drv] & DS_VV)? A0_VV: 0) | A0_DRA;
        if (uptr->flags & UNIT_ATT) v = v | A0_SPO |
                (!sim_is_active (uptr)? A0_RDY: 0);
        break;

    case 1:                                             /* message A1 */
        if (uptr->flags & UNIT_ATT) {
            if (sim_is_active (uptr)) {
                if (fnc == FNC_UNLOAD) v = v | A1_UNL;
                else if (fnc == FNC_RECAL) v = v | A1_RTZ;
                else if (fnc == FNC_SEEK) {
                    if (hk_dif[drv] < 0) v = v | A1_REV;
                    if (hk_dif[drv] > 0) v = v | A1_FWD;
                    }
                }
            v = v | (A1_SPD|A1_CAR|A1_DOR|A1_HHM|A1_SRV);
            }
        else v = v | A1_HHM;
        break;

    case 2:                                             /* message A2 */
        if (hkds[drv] & DS_OF)
            v = v | ((hk_off[drv] & A2_M_DIF) << A2_V_DIF);
        else v = v | ((hk_dif[drv] & A2_M_DIF) << A2_V_DIF);
        break;

    case 3:                                             /* message A3 */
        v = v | ((012340 + v) << A3_V_SNO);
        break;
        }

return hk_mrpar (v);
}

int32 hk_rdmr3 (int32 msg)
{
int32 drv = GET_UNIT (hkcs2);
int32 v = msg & 03;

switch (msg) {

    case 0:                                             /* message B0 */
        v = v | ((hker[drv] & ER_WLE)? (B0_WLE | B0_FLT): 0) |
                ((hker[drv] & ER_SKI)? (B0_SKI | B0_FLT): 0) |
                ((hker[drv] & ER_NXF)? (B0_NXF | B0_FLT): 0) |
                ((hker[drv] & ER_IAE)? (B0_IAE | B0_FLT): 0);
        break;

    case 1:                                             /* message B1 */
        v = v | ((hker[drv] & ER_SKI)? B1_SKI: 0) |
                ((hker[drv] & ER_UNS)? B1_SVE: 0);
        break;

    case 2:                                             /* message B2 */
        v = v | (hk_unit[drv].CYL << B2_V_CYL);
        break;

    case 3:                                             /* message B3 */
        v = v | (GET_SC (hkda) << B3_V_SEC) |
                (1 << (GET_SF (hkda) + B3_V_DHA));
        break;
        }

return hk_mrpar (v);
}

/* Device reset */

t_stat hk_reset (DEVICE *dptr)
{
int32 i;
UNIT *uptr;

hkcs1 = CS1_DONE;                                       /* set done */
hkcs2 = CS2_IR;                                         /* clear state */
hkmr = hkmr2 = hkmr3 = 0;
hkda = hkdc = 0;
hkba = hkwc = 0;
hkof = hkspr = 0;
CLR_INT (HK);                                           /* clear intr req */
for (i = 0; i < HK_NUMDR; i++) {                        /* stop operations */
    uptr = hk_dev.units + i;
    sim_cancel (uptr);
    if (uptr->flags & UNIT_ATT) hkds[i] = hkds[i] & DS_VV;
    else hkds[i] = 0;
    uptr->CYL = uptr->FNC = 0;                          /* clear state */
    hk_dif[i] = 0;
    hk_off[i] = 0;
    hker[i] = 0;
    }                                                   /* clear errors */
if (hkxb == NULL) hkxb = (uint16 *) calloc (HK_MAXFR, sizeof (uint16));
if (hkxb == NULL) return SCPE_MEM;
return SCPE_OK;
}

/* Device attach */

t_stat hk_attach (UNIT *uptr, char *cptr)
{
uint32 drv, p;
t_stat r;

uptr->capac = HK_SIZE (uptr);
r = attach_unit (uptr, cptr);                           /* attach unit */
if (r != SCPE_OK) return r;                             /* error? */
drv = (uint32) (uptr - hk_dev.units);                   /* get drv number */
hkds[drv] = DS_ATA | DS_RDY | ((uptr->flags & UNIT_WPRT)? DS_WRL: 0);
hker[drv] = 0;                                          /* upd drv status */
hk_off[drv] = 0;
hk_dif[drv] = 0;
uptr->CYL = 0;
update_hkcs (CS1_DI, drv);                              /* upd ctlr status */

p = sim_fsize (uptr->fileref);                          /* get file size */
if (p == 0) {                                           /* new disk image? */
    if (uptr->flags & UNIT_RO) return SCPE_OK;
    return pdp11_bad_block (uptr, HK_NUMSC, HK_NUMWD);
    }
if ((uptr->flags & UNIT_AUTO) == 0) return SCPE_OK;     /* autosize? */
if (p > (RK06_SIZE * sizeof (uint16))) {
    uptr->flags = uptr->flags | UNIT_RK07;
    uptr->capac = RK07_SIZE;
    }
else {
    uptr->flags = uptr->flags & ~UNIT_RK07;
    uptr->capac = RK06_SIZE;
    }
return SCPE_OK;
}

/* Device detach */

t_stat hk_detach (UNIT *uptr)
{
uint32 drv;

if (!(uptr->flags & UNIT_ATT)) return SCPE_OK;          /* attached? */
drv = (uint32) (uptr - hk_dev.units);                   /* get drv number */
hkds[drv] = (hkds[drv] & ~(DS_RDY | DS_WRL | DS_VV | DS_OF)) | DS_ATA;
if (sim_is_active (uptr)) {                             /* unit active? */
    sim_cancel (uptr);                                  /* cancel operation */
    hker[drv] = hker[drv] | ER_OPI;                     /* set drive error */
    if ((uptr->FNC & FNC_2ND) == 0)                     /* expecting done? */
        update_hkcs (CS1_DONE, drv);                    /* set done */
    }
update_hkcs (CS1_DI, drv);                              /* request intr */
return detach_unit (uptr);
}

/* Set size command validation routine */

t_stat hk_set_size (UNIT *uptr, int32 val, char *cptr, void *desc)
{
if (uptr->flags & UNIT_ATT) return SCPE_ALATT;
uptr->capac = val? RK07_SIZE: RK06_SIZE;
return SCPE_OK;
}

/* Set bad block routine */

t_stat hk_set_bad (UNIT *uptr, int32 val, char *cptr, void *desc)
{
return pdp11_bad_block (uptr, HK_NUMSC, HK_NUMWD);
}

#if defined (VM_PDP11)

/* Device bootstrap - does not clear CSR when done */

#define BOOT_START      02000                           /* start */
#define BOOT_ENTRY      (BOOT_START + 002)              /* entry */
#define BOOT_UNIT       (BOOT_START + 010)              /* unit number */
#define BOOT_CSR        (BOOT_START + 014)              /* CSR */
#define BOOT_LEN        (sizeof (boot_rom) / sizeof (int16))

static const uint16 boot_rom[] = {
    0042115,                        /* "MD" */
    0012706, BOOT_START,            /* mov #boot_start, sp */
    0012700, 0000000,               /* mov #unit, r0 */
    0012701, 0177440,               /* mov #HKCS1, r1 */
    0012761, 0000040, 0000010,      /* mov #CS2_CLR, 10(r1) ; reset */
    0010061, 0000010,               /* mov r0, 10(r1)       ; set unit */
    0016102, 0000012,               /* mov 12(r1), r2       ; drv typ */
    0100375,                        /* bpl .-4              ; valid? */
    0042702, 0177377,               /* bic #177377, r2      ; clr rest */
    0006302,                        /* asl r2               ; move */
    0006302,                        /* asl r2 */
    0012703, 0000003,               /* mov #pack+go, r3 */
    0050203,                        /* bis r2, r3           ; merge type */
    0010311,                        /* mov r3, (r1);        ; pack ack */
    0105711,                        /* tstb (r1)            ; wait */
    0100376,                        /* bpl .-2 */
    0012761, 0177000, 0000002,      /* mov #-512.,2(r1)     ; set wc */
    0005061, 0000004,               /* clr 4(r1)            ; clr ba */
    0005061, 0000006,               /* clr 6(r1)            ; clr da */
    0005061, 0000020,               /* clr 20(r1)           ; clr cyl */
    0012703, 0000021,               /* mov #read+go, r3 */
    0050203,                        /* bis r2, r3           ; merge type */
    0010311,                        /* mov r3, (r1);        ; read */
    0105711,                        /* tstb (r1)            ; wait */
    0100376,                        /* bpl .-2 */
    0005002,                        /* clr R2 */
    0005003,                        /* clr R3 */
    0012704, BOOT_START+020,        /* mov #start+020, r4 */
    0005005,                        /* clr R5 */
    0005007                         /* clr PC */
    };

t_stat hk_boot (int32 unitno, DEVICE *dptr)
{
int32 i;
extern int32 saved_PC;

for (i = 0; i < BOOT_LEN; i++) M[(BOOT_START >> 1) + i] = boot_rom[i];
M[BOOT_UNIT >> 1] = unitno & CS2_M_UNIT;
M[BOOT_CSR >> 1] = hk_dib.ba & DMASK;
saved_PC = BOOT_ENTRY;
return SCPE_OK;
}

#else

t_stat hk_boot (int32 unitno, DEVICE *dptr)
{
return SCPE_NOFNC;
}

#endif
