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

   Copyright (c) 1993-2004, 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

   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.
*/

#include "pdp11_defs.h"

#define HK_RDX		8
#define HK_WID		16
extern int32 cpu_18b, cpu_ubm;

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|CS1_GO)
#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 int32 int_vec[IPL_HLVL][32];
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_stopioe = 1;					/* stop on error */
int32 hk_cwait = 5;					/* command time */
int32 hk_swait = 10;					/* seek time */
int32 hk_rwait = 10;					/* rotate time */
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, HK_RDX, 16, 0) },
	{ GRDATA (HKWC, hkwc, HK_RDX, 16, 0) },
	{ GRDATA (HKBA, hkba, HK_RDX, 16, 0) },
	{ GRDATA (HKDA, hkda, HK_RDX, 16, 0) },
	{ GRDATA (HKCS2, hkcs2, HK_RDX, 16, 0) },
	{ BRDATA (HKDS, hkds, HK_RDX, 16, HK_NUMDR) },
	{ BRDATA (HKER, hker, HK_RDX, 16, HK_NUMDR) },
	{ BRDATA (HKDB, hkdb, HK_RDX, 16, 3) },
	{ GRDATA (HKDC, hkdc, HK_RDX, 16, 0) },
	{ GRDATA (HKOF, hkof, HK_RDX, 8, 0) },
	{ GRDATA (HKMR, hkmr, HK_RDX, 16, 0) },
	{ GRDATA (HKMR2, hkmr2, HK_RDX, 16, 0), REG_RO },
	{ GRDATA (HKMR3, hkmr3, HK_RDX, 16, 0), REG_RO },
	{ GRDATA (HKSPR, hkspr, HK_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 (STIME, hk_swait, 24), REG_NZ + PV_LEFT },
	{ DRDATA (RTIME, hk_rwait, 24), REG_NZ + PV_LEFT },
	{ URDATA (FNC, hk_unit[0].FNC, HK_RDX, 5, 0,
		  HK_NUMDR, REG_HRO) },
	{ URDATA (CYL, hk_unit[0].CYL, HK_RDX, 10, 0,
		  HK_NUMDR, REG_HRO) },
	{ BRDATA (OFFSET, hk_off, HK_RDX, 16, HK_NUMDR), REG_HRO },
	{ BRDATA (CYLDIF, hk_dif, HK_RDX, 16, HK_NUMDR), REG_HRO },
	{ URDATA (CAPAC, hk_unit[0].capac, 10, T_ADDR_W, 0,
		  HK_NUMDR, PV_LEFT | REG_HRO) },
	{ FLDATA (STOP_IOE, hk_stopioe, 0) },
	{ GRDATA (DEVADDR, hk_dib.ba, HK_RDX, 32, 0), REG_HRO },
	{ GRDATA (DEVVEC, hk_dib.vec, HK_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, HK_RDX, 24, 1, HK_RDX, HK_WID,
	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 | CS2_OR;
	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_OR;			/* 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 (hkcs1 & CS1_GO) {				/* go? */
	    if (hkcs1 & CS1_ERR) hkcs1 = hkcs1 & ~CS1_GO;
	    else 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 | CS2_OR;
	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 - unit not busy, function 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: 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) == 0)) {	/* need attached? */
	hk_cmderr (ER_UNS, drv);			/* unsafe */
	return;  }
if (fnc_rdy[fnc] && sim_is_active (uptr)) {		/* need inactive? */
	hkcs1 = (hkcs1 | CS1_DONE) & ~CS1_GO;		/* ignore if busy */
	return;  }
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_DONE;				/* clear done */
hkds[drv] = hkds[drv] & ~DS_ATA;			/* clear attention */
uptr->FNC = fnc;					/* save function */
switch (fnc) {						/* case on function */

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

case FNC_NOP:						/* no operation */
case FNC_DCLR:						/* drive clear */
case FNC_START:						/* start spindle */
case FNC_UNLOAD:					/* unload */
case FNC_PACK:						/* pack acknowledge */
	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 */
	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->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, drv, fnc, err;
int32 wc, awc, da;
uint32 ba;
uint16 comp;

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

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

case FNC_DCLR:						/* drive clear */
	hker[drv] = 0;					/* clear errors */
	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;

case FNC_UNLOAD:					/* unload */
	hk_detach (uptr);				/* detach unit */
case FNC_START:						/* start spindle */
case FNC_NOP:						/* select */
	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_swait * 10);		/* 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]);			/* |cyl diff| */
	    if (t == 0) t = 1;				/* min time */
	    uptr->CYL = dc;				/* save cyl */		
	    sim_activate (uptr, hk_swait * 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 */
	    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, MAP)) {	/* 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, MAP)) {	/* 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], MAP)) {
		    wc = 0;				/* NXM, no xfr */
		    hkcs2 = hkcs2 | CS2_NEM;  }  }	/* set nxm err */
	    else {					/* normal */
		if (t = Map_WriteW (ba, wc << 1, hkxb, MAP)) {	/* 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, MAP)) {	/* 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 */
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 */
hkcs1 = hkcs1 & ~CS1_GO;				/* clear go */
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 | CS2_OR;				/* 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 = calloc (HK_MAXFR, sizeof (unsigned int16));
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 = 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 */

if ((p = sim_fsize (uptr->fileref)) == 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 (int16))) {
	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)
{
int32 drv;

drv = 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 & CS1_M_FNC) >= FNC_XFER)	/* data transfer? */
	    hkcs1 = hkcs1 | CS1_DONE | CS1_ERR;  }	/* set done, err */
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);
}

/* 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;
}
