/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * This is a simple minded implementation of rp06 disk and controller.
 * The problem is that I don't have access to a real rp06, never had
 * access and in fact never saw such a disk. The only information I got is a 
 * 1975 pdp11 peripherals handbook that describes the rp04 and the
 * RSX11+ DB-driver.
 * The intention was to emulate just enough to use it for sysgen-ing
 * RSX11+ BL46.
 *
 * Note:
 *	You may need to increase your data segment limit if you
 *	get SIGSEGV's (one RP06 has around 176Mbyte).
 *
 * Thoughts:
 *	There are different ways to implement the shadowing. Now we
 *	have per block shadowing. We could also do per window shadowing.
 *	In this case we would need to copy the base window to the shadow
 *	window on the first write to the window. Both of these variants
 *	could be combined with checking, whether data is really going to
 *	be changed on the creation of the shadow block/track. If not we could 
 *	ignore the write.
 */

# include "proc.h"

RCSID("$Id: dev_rp.c 475 2001-06-19 07:42:01Z hbb $")


# define RPS	8	/* number of RP06 drives */
# define RPSECS	22	/* sectors per track */
# define RPTRKS	19	/* tracks */
# define RPNCYL	815	/* cylinders */

# define RPBLKS	(RPSECS*RPTRKS*RPNCYL)
# define RPSIZE	(512*RPBLKS)

/*
 * These numbers are configurable. WINCYL may be 5 or 163.
 */
# define WINCYL	5	/* cylinders per window */
# define NWIN	16	/* number of windows */


/* size of one window in bytes */
# define WINBLKS (WINCYL * RPSECS * RPTRKS)
# define WINSIZE (WINBLKS * 512)


/* # define DEBUG */
# define DM(FMT)
# define DDMA(FMT)

# ifdef HAVE_MS_SYNC
#  define MSYNC(A,S)	(msync(A, S, MS_SYNC))
# else
#  define MSYNC(A,S)	(msync(A, S))
# endif

enum {
	/*
	 * CSR1 bits
	 */
	CS1_Sc	= 0100000,	/* special condition */
	CS1_Tre	= 0040000,	/* transfer error */
	CS1_Mcpe= 0020000,	/* mass i/o control bus paritiy error */
	CS1_Dva	= 0004000,	/* drive available */
	CS1_Psel= 0002000,	/* port select */
	CS1_A17	= 0001000,	/* A17 */
	CS1_A16	= 0000400,	/* A16 */
	CS1_Ash = 8,		/* a16/17 shift */
	CS1_Rdy	= 0000200,	/* ready */
	CS1_Ie	= 0000100,	/* interrupt enable */
	CS1_Func= 0000076,	/* function mask */
	CS1_Go	= 0000001,	/* go */

	F_Nop	= 2*0000,	/* nop */
	F_Unl	= 2*0001,	/* unload/standby */
	F_Seek	= 2*0002,	/* seek */
	F_Recal	= 2*0003,	/* recalibrate */
	F_Dclr	= 2*0004,	/* drive clear */
	F_Relse	= 2*0005,	/* release (dual port op) */
	F_Off	= 2*0006,	/* offset */
	F_Cent	= 2*0007,	/* return to centerline */
	F_Pres	= 2*0010,	/* read-in-preset */
	F_Pack	= 2*0011,	/* pack acknowledge */
	F_Srch	= 2*0014,	/* search */
	F_Wchkd	= 2*0024,	/* write check data */
	F_WchkH	= 2*0025,	/* write check header & data */
	F_Write	= 2*0030,	/* write data */
	F_WritH	= 2*0031,	/* write header and data */
	F_Read	= 2*0034,	/* read data */
	F_ReadH	= 2*0035,	/* read header and data */

	/*
	 * desired address
	 */
	DA_TAM	= 017400,	/* track address mask */
	DA_SAM	= 000037,	/* sector address mask */
	DA_TAS	= 8,		/* track address shift */
	DA_SAS	= 0,		/* sector address shift */

	/*
	 * CSR2
	 */
	CS2_Dlt	= 0100000,	/* data late */
	CS2_Wce	= 0040000,	/* write check error */
	CS2_Pe	= 0020000,	/* parity error */
	CS2_Ned	= 0010000,	/* X nonexistent drive */
	CS2_Nem	= 0004000,	/* X nonexistent memory */
	CS2_Pge	= 0002000,	/* program error */
	CS2_Mxf	= 0001000,	/* missed transfer */
	CS2_Mdpe= 0000400,	/* mass data bus parity error */
	CS2_Or	= 0000200,	/* output ready */
	CS2_Ir	= 0000100,	/* input ready */
	CS2_Clr	= 0000040,	/* controller clear */
	CS2_Pat	= 0000020,	/* parity test */
	CS2_Bai	= 0000010,	/* X unibus address increment inhibit */
	CS2_U	= 0000007,	/* X unit select */

	/*
	 * drive status
	 */
	DS_ATA	= 0100000,	/* X attention active */
	DS_Err	= 0040000,	/* X error */
	DS_Pip	= 0020000,	/* positioning in progress */
	DS_Mol	= 0010000,	/* X medium online */
	DS_Wrl	= 0004000,	/* X write lock */
	DS_Lst	= 0002000,	/* last sector transferred */
	DS_Pgm	= 0001000,	/* programmable */
	DS_Dpr	= 0000400,	/* X drive present */
	DS_Dry	= 0000200,	/* X drive ready */
	DS_Vv	= 0000100,	/* X volume valid */
	DS_De1	= 0000040,	/* difference equals one */
	DS_Dl64	= 0000020,	/* difference less than 64 */
	DS_Grv	= 0000010,	/* go reverse */
	DS_Digb	= 0000004,	/* drive to inner guard band */
	DS_DF20	= 0000002,	/* drive forward 20 inch/sec */
	DS_DF5	= 0000001,	/* drive forward 5 inch/sec */

	/*
	 * err 1
	 */
	E1_Dck	= 0100000,	/* data check */
	E1_Uns	= 0040000,	/* unsave */
	E1_Opi	= 0020000,	/* op incomplete */
	E1_Dte	= 0010000,	/* drive timing error */
	E1_Wle	= 0004000,	/* X write lock error */
	E1_Iae	= 0002000,	/* X invalid address error */
	E1_Aoe	= 0001000,	/* X address overflow error */
	E1_Hcrc	= 0000400,	/* header crc error */
	E1_Hce	= 0000200,	/* header compare error */
	E1_Ech	= 0000100,	/* ecc hard error */
	E1_Wcf	= 0000040,	/* write clock fail */
	E1_Fer	= 0000020,	/* format error */
	E1_Par	= 0000010,	/* parity error */
	E1_Rmr	= 0000004,	/* register modification refused */
	E1_Ilr	= 0000002,	/* illegal register */
	E1_Ilf	= 0000001,	/* X illegal function */

	/*
	 * attention summary
	 */
	AS_ATA	= 0000377,	/* attention active */

	/*
	 * lock-ahead register 
	 */
	LA_SC	= 0003700,	/* sector counter */
	LA_EXT	= 0000060,	/* encoded extension field */

	/*
	 * drive type
	 */
	DT_MOH	= 0020000,	/* moving head */
	DT_RP04	= 0000020,	/* rp04 */
	DT_RP05	= 0000021,	/* rp05 */
	DT_RP06	= 0000022,	/* rp06 */
	DT_RP07	= 0000042,	/* rp07 */

	/*
	 * offset register 
	 */
	OF_Scg	= 0100000,	/* sign change */
	OF_Fmt	= 0010000,	/* format bit */
	OF_Eci	= 0004000,	/* error correction code inhibit */
	OF_Hci	= 0002000,	/* header compare inhibit */
	OF_Ofs	= 0000377,	/* offset information */

	/*
	 * desired cyl
 	 */
	DC_Mask	= 0001777,

	/*
	 * error 2
	 */
	E2_Acu	= 0100000,	/* ac unsafe */
	E2_Plu	= 0020000,	/* plo unsafe */
	E2_30vu	= 0010000,	/* 30 Volts unsafe */
	E2_Ixe	= 0004000,	/* index error */
	E2_Nhs	= 0002000,	/* no head selection */
	E2_Mhs	= 0001000,	/* multiple head select */
	E2_Wru	= 0000400,	/* write ready unsafe */
	E2_Fen	= 0000200,	/* failsafe enabled */
	E2_Tuf	= 0000100,	/* transitions unsafe */
	E2_Tdf	= 0000040,	/* transitions detector failure */
	E2_Mse	= 0000020,	/* motor sequence error */
	E2_Csu	= 0000010,	/* current switch unsafe */
	E2_Wsu	= 0000004,	/* write select unsafe */
	E2_Csf	= 0000002,	/* current sink failure */
	E2_Wcu	= 0000001,	/* write current unsafe */

	/*
	 * error register 3
	 */
	E3_Ocyl	= 0100000,	/* off cylinder */
	E3_Ski	= 0040000,	/* seek incomplete */
	E3_Dcl	= 0000100,	/* dc low */
	E3_Acl	= 0000040,	/* ac low */
	E3_Uwr	= 0000010,	/* any unsafe except r/w */
	E3_Vuf	= 0000002,	/* velocity unsafe */
	E3_Psu	= 0000001,	/* pack speed unsafe */
};

# define SMAP_SIZE	(RPBLKS / 8)
# define SMAP_IDX(B)	(B / 8)
# define SMAP_BIT(B)	(1 << (B % 8))

typedef struct RP RP;
typedef struct RPU RPU;

struct RP {
	u_int	csr_base;
	u_short	vector;
	u_short	irq_level;
	int	sync_rate;

	/* controller registers */
	int	ien;		/* RPCS1	IEN */
	int	rdy;		/* RPCS1	RDY */
	int	sel;		/* RPCS2	UNIT */
	int	ba;		/* RPBA<1:15>, RPCS1<8:9>, RPBAE<0:5> */
	u_short	wc;		/* RPWC */
	int	cs2;		/* RPCS2	NED NEM MXF BAI */
	int	cs1;		/* RPCS1	TRE SC */
	int	as;		/* RPAS		*/

	struct RPU {
		/* drive registers */
		u_int	cs1;	/* RPCS1	function code */
		u_int	dta;	/* RPDA		desired track address */
		u_int	dsa;	/* RPDA		desired sector address */
		u_int	dstat;	/* RPDS		ATA, ERR, LST, VV */
		u_int	er1;	/* RPER1	WLE, IAE, AOE, FER, ILF */
		u_int	sn;	/* RPSN 	*/
		u_int	of;	/* RPOF		FMT22 */
		u_int	ccyl;	/* RPCC		current cylinder */
		u_int	dcyl;	/* RPDC		desired cylinder */

		/* file status */
		u_int	wl;	/* write lock */

		u_char	*smap;

		struct RPFILE {
			char   *fn;	/* file name */
			int	fd;	/* file descriptor */
			int	iss;	/* this is the shadow */

			/* windows */
			struct RPWIN {
				char   *mm;	/* mapped cylinder */
				u_int	wno;	/* window number in file */
			}	win[NWIN];
		}	f, s;
	}	u[RPS];

	u_long	map_hits;	/* no of times the track was mapped */
	u_long	map_unmap;	/* no of times we had to unmap */
	u_long	map_calls;	/* call to maptrack */
};

void	rp_ctrl_create(iodev_t *, int, char **);
void	rp_dev_create(iodev_t *, int, char **);
void	rp_ctrl_complete(iodev_t *);

void	rp_reset(iodev_t *);
u_short	rp_fetch(iodev_t *, u_int);
void	rp_store(iodev_t *, u_int, mmode_t, u_short);
u_short	rp_vector(iodev_t *);
void	rp_dma(iodev_t *);
void	rp_info(iodev_t *);
void	rp_flush(iodev_t *);

static int rpc_info(iodev_t *dev, int argc, char **argv);
static int rpc_load(iodev_t *dev, int argc, char **argv);
static int rpc_unload(iodev_t *dev, int argc, char **argv);
static int rpc_wlock(iodev_t *dev, int argc, char **argv);
static int rpc_wunlock(iodev_t *dev, int argc, char **argv);
static int rpc_debug(iodev_t *dev, int argc, char **argv);

iocmd_t rp_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			rpc_info },
	{ "load",	"drive-no file",	rpc_load },
	{ "unload",	"drive-no",		rpc_unload },
	{ "wlock",	"drive-no",		rpc_wlock },
	{ "wunlock",	"drive-no",		rpc_wunlock },
	{ "debug",	"[on|off]",		rpc_debug },
	{ NULL,		NULL,			NULL }
};

ioops_t rp_ops = {
	rp_ctrl_create,		/* ctrl_create */
	rp_dev_create,		/* dev_create */
	rp_ctrl_complete,	/* ctrl_complete */
	rp_reset,		/* reset */
	rp_fetch,		/* fetch */
	rp_store,		/* store */
	rp_vector,		/* vector */
	rp_dma,			/* dma */
	0,			/* async */
	rp_info,		/* info */
	rp_flush,		/* flush */
	rp_cmds,		/* cmds */
};

enum {
	RP_CS1	= 000,	/* csr 1 */
	RP_WC	= 002,	/* word count */
	RP_BA	= 004,	/* bus address */
	RP_DA	= 006,	/* disk address */
	RP_CS2	= 010,
	RP_DS	= 012,	/* drive status */
	RP_ER1	= 014,	/* error 1 */
	RP_AS	= 016,	/* attention summary */
	RP_LA	= 020,	/* look-ahead */
	RP_DB	= 022,	/* data buffer */
	RP_MR	= 024,	/* maintenance */
	RP_DT	= 026,	/* drive type */
	RP_SN	= 030,	/* serial number */
	RP_OF	= 032,	/* offset */
	RP_DC	= 034,	/* desired cylinder */
	RP_CC	= 036,	/* current cylinder */
	RP_ER2	= 040,	/* error 2 */
	RP_ER3	= 042,	/* error 3 */
	RP_EC1	= 044,	/* ecc 1 */
	RP_EC2	= 046,	/* ecc 2 */
	RP_BAE	= 050,	/* bus address extension */
	RP_CS3	= 052,	/* csr 3 */
};

# ifdef DEBUG
char *reg_names[] = {
	"RP_CS1",	"RP_WC",	"RP_BA",	"RP_DA",
	"RP_CS2",	"RP_DS",	"RP_ER1",	"RP_AS",
	"RP_LA",	"RP_DB",	"RP_MR",	"RP_DT",
	"RP_SN",	"RP_OF",	"RP_DC",	"RP_CC",
	"RP_ER2",	"RP_ER3",	"RP_EC1",	"RP_EC2",
	"RP_BAE",	"RP_CS3"
};
# endif

static void	load(iodev_t *, int, char *, char *, int);
static void	unload(RP *, int);
static void 	rpsync(void *);
static void	dofunc(iodev_t *);
static void	wcsr1(iodev_t *, u_short);
static int 	drive_exists(RP *d);
static int	check_parms(RP *d);

static void	cclr(iodev_t *);
static void	dclr(RP *, int);

int	rpdebug;

/*
 * initialise controller 
 * 	CSR-base
 *	vector
 *	irq level
 */
void	
rp_ctrl_create(iodev_t *dev, int argc, char **argv)
{
	RP	*d;
	int	i;

	if(argc != 4)
		conf_panic("rp: missing args in controller configuration");

	dev->data = d = xalloc(sizeof(RP));
	(void)memset(dev->data, 0, sizeof(RP));

	d->csr_base = parse_csr(argv[0], "rp");
	d->vector = parse_vec(argv[1], "rp");
	d->irq_level = parse_irq(argv[2], "rp");
	d->sync_rate = strtol(argv[3], 0, 10);

	for(i = 0; i < RPS; i++) {
		d->u[i].s.fd = -1;
		d->u[i].f.fd = -1;
		d->u[i].s.iss = 1;
	}

	proc.iopage[IOP(d->csr_base + RP_CS1)] = dev;
	proc.iopage[IOP(d->csr_base + RP_WC )] = dev;
	proc.iopage[IOP(d->csr_base + RP_BA )] = dev;
	proc.iopage[IOP(d->csr_base + RP_DA )] = dev;
	proc.iopage[IOP(d->csr_base + RP_CS2)] = dev;
	proc.iopage[IOP(d->csr_base + RP_DS )] = dev;
	proc.iopage[IOP(d->csr_base + RP_ER1)] = dev;
	proc.iopage[IOP(d->csr_base + RP_AS )] = dev;
	proc.iopage[IOP(d->csr_base + RP_LA )] = dev;
	proc.iopage[IOP(d->csr_base + RP_DB )] = dev;
	proc.iopage[IOP(d->csr_base + RP_MR )] = dev;
	proc.iopage[IOP(d->csr_base + RP_DT )] = dev;
	proc.iopage[IOP(d->csr_base + RP_SN )] = dev;
	proc.iopage[IOP(d->csr_base + RP_OF )] = dev;
	proc.iopage[IOP(d->csr_base + RP_DC )] = dev;
	proc.iopage[IOP(d->csr_base + RP_CC )] = dev;
	proc.iopage[IOP(d->csr_base + RP_ER2)] = dev;
	proc.iopage[IOP(d->csr_base + RP_ER3)] = dev;
	proc.iopage[IOP(d->csr_base + RP_EC1)] = dev;
	proc.iopage[IOP(d->csr_base + RP_EC2)] = dev;
	proc.iopage[IOP(d->csr_base + RP_BAE)] = dev;
	proc.iopage[IOP(d->csr_base + RP_CS3)] = dev;

	cclr(dev);
}

/*
 * create drive
 *	drive-no file s-file serial number
 */
void	
rp_dev_create(iodev_t *dev, int argc, char **argv)
{
	RP *d = (RP *)dev->data;
	int i;
	char *fn, *sfn;

	if(argc != 3 && argc != 4)
		conf_panic("rp: bad args in device description");

	i = (int)strtol(*argv++, 0, 0);
	if(i > RPS)
		conf_panic("rp: controller supports up to 8 disks only");

	if(d->u[i].f.fd >= 0)
		unload(d, i);

	fn = *argv++;
	sfn = (argc == 3) ? NULL : *argv++;

	load(dev, i, fn, sfn, 1);

	d->u[i].sn = strtol(*argv++, 0, 0);
}

void	
rp_ctrl_complete(iodev_t *dev)
{
	RP *d = (RP *)dev->data;

	if(d->sync_rate > 0)
		register_timer(d->sync_rate, rpsync, d);
}



static void
rpsync(void *v)
{
	RP *d = v;
	int i;
	struct RPWIN *w;

	for(i = 0; i < RPS; i++)
		if(d->u[i].s.fd >= 0) {
			for(w = d->u[i].s.win; w < &d->u[i].s.win[NWIN]; w++)
				if(w->mm)
					MSYNC(w->mm, WINSIZE);
			MSYNC(d->u[i].smap, SMAP_SIZE);
		} else {
			for(w = d->u[i].f.win; w < &d->u[i].f.win[NWIN]; w++)
				if(w->mm)
					MSYNC(w->mm, WINSIZE);
		}
}

void
rp_flush(iodev_t *dev)
{
	rpsync(dev->data);
}

/*
 * load new disk in drive i
 * set write lock, if file is read-only
 */
static void
load(iodev_t *dev, int i, char *fn, char *sfn, int isconf)
{
	typedef void (*pfunc)(char *, ...);
	struct stat statb;
	pfunc ef = isconf ? (pfunc)conf_panic : (pfunc)printf;
	RP *d = (RP *)dev->data;
	struct RPU *rp = &d->u[i];
	int w;

	if(sfn == NULL) {
		/*
		 * No shadow file: try to open file R/W
		 */
		if((rp->f.fd = open(fn, O_RDWR, 0)) < 0) {
			rp->wl = 2;
			if((rp->f.fd = open(fn, O_RDONLY, 0)) < 0) {
				(*ef)("rp%d: can't open %s: %s", i, fn,
				    strerror(errno));
				goto err;
			}
		}

		/*
		 * Check for correct size.
		 */
		if(fstat(rp->f.fd, &statb)) {
			(*ef)("rp%d: can't stat %s: %s", i, fn, strerror(errno));
			goto err;
		}
		if(statb.st_size != RPSIZE) {
			(*ef)("rp%d: %s has wrong expand size (%d)",
			    i, fn, RPSIZE);
			goto err;
		}

		rp->f.fn = strcpy(xalloc(strlen(fn) + 1), fn);
		rp->s.fd = -1;
		rp->s.fn = NULL;
	} else {
		/*
		 * Shadow file: open base file RO
		 */
		if((rp->f.fd = open(fn, O_RDONLY, 0)) < 0) {
			(*ef)("rp%d: can't open %s: %s", i, fn,
			    strerror(errno));
			goto err;
		}
		/*
		 * The base file must have the correct size.
		 */
		if(fstat(rp->f.fd, &statb)) {
			(*ef)("rp%d: can't stat %s: %s", i, fn,
			    strerror(errno));
			goto err;
		}
		if(statb.st_size != RPSIZE) {
			(*ef)("rp%d: %s has the wrong size (%d)", i, fn,
			    RPSIZE);
			goto err;
		}

		/*
		 * Try to open shadow file R/W
		 */
		if((rp->s.fd = open(sfn, O_RDWR, 0)) < 0) {
			rp->wl = 2;
			if((rp->s.fd = open(sfn, O_RDONLY, 0)) < 0) {
				(*ef)("rp%d: can't open %s: %s", i, sfn,
				    strerror(errno));
				goto err;
			}
		}

		/*
		 * Must have correct size
		 */
		if(fstat(rp->s.fd, &statb)) {
			(*ef)("rp%d: can't stat %s: %s", i, sfn, strerror(errno));
			goto err;
		}

		if(statb.st_size != RPSIZE + SMAP_SIZE) {
			(*ef)("rp%d: %s has the wrong size (%d)", i, sfn,
			    RPSIZE + SMAP_SIZE);
			goto err;
		}
		rp->smap = mmap(0, SMAP_SIZE,
		    PROT_READ | (rp->wl ? 0 : PROT_WRITE),
		    MAP_FILE | MAP_SHARED, rp->s.fd, RPSIZE);
		if(rp->smap == MAP_FAILED) {
			(*ef)("rp%d: can't mmap smap: %s", i, strerror(errno));
			goto err;
		}

		rp->f.fn = strcpy(xalloc(strlen(fn) + 1), fn);
		rp->s.fn = strcpy(xalloc(strlen(sfn) + 1), sfn);
	}

	for(w = 0; w < NWIN; w++) {
		rp->f.win[w].mm = NULL;
		rp->s.win[w].mm = NULL;
	}

	/*
	 * power up cycle
  	 */
	rp->ccyl = 0;		/* RPCC */
	rp->dstat &= ~DS_Vv;

	dclr(d, i);			/* really necessary? */

	rp->dstat |= DS_ATA;
	d->as |= (1 << i);
	d->cs1 |= CS1_Sc;

	/* interrupt on ATA transition */
	IRQ(dev, d->irq_level);	
	return;

  err:
	if(rp->f.fd >= 0) {
		(void)close(rp->f.fd);
		rp->f.fd = -1;
	}
	if(rp->s.fd >= 0) {
		(void)close(rp->s.fd);
		rp->s.fd = -1;
	}
	return;
}

static void
free_file(struct RPFILE *f)
{
	int w;

	free(f->fn);
	f->fn = NULL;

	(void)close(f->fd);
	f->fd = -1;

	for(w = 0; w < NWIN; w++) {
		munmap(f->win[w].mm, WINSIZE);
		f->win[w].mm = NULL;
	}
}

/*
 * unload disk from drive i
 */
static void
unload(RP *d, int i)
{
	struct RPU *rp = &d->u[i];

	if(rp->f.fd < 0)
		return;
	free_file(&rp->f);
	if(rp->s.fd >= 0) {
		munmap(rp->smap, SMAP_SIZE);
		free_file(&rp->s);
	}

	rp->wl = 0;

	dclr(d, i);
}


void	
rp_reset(iodev_t *dev)
{
	cclr(dev);
}


/*
 * fetch a register
 */
u_short	
rp_fetch(iodev_t *dev, u_int a)
{
	RP *d = (RP *)dev->data;
	u_short v = 0;

	switch(a - d->csr_base) {

	case RP_CS1:
		v |= ((d->u[d->sel].f.fd >= 0) ? CS1_Dva : 0)
		   | ((d->ba >> (16 - CS1_Ash)) & (CS1_A17 | CS1_A16))
		   | CS1_Rdy
		   | (d->ien ? CS1_Ie : 0)
		   | (d->u[d->sel].cs1)
		   | d->cs1;
		break;

	case RP_WC:
		v = d->wc;
		break;

	case RP_BA:
		v = d->ba;
		break;

	case RP_DA:
		v = (d->u[d->sel].dta << DA_TAS) | (d->u[d->sel].dsa << DA_SAS);
		break;

	case RP_CS2:
		v = d->cs2 | d->sel;
		break;

	case RP_DS:
		v = d->u[d->sel].dstat
		  | (d->u[d->sel].wl ? (DS_Wrl) : 0)
		  | ((d->u[d->sel].f.fd >= 0) ? (DS_Mol | DS_Dpr | DS_Dry) : 0);
		break;

	case RP_ER1:
		v = d->u[d->sel].er1;
		break;

	case RP_AS:
		v = d->as;
		break;

	case RP_LA:
		v = 0;		/* not implemented */
		break;

	case RP_DB:
		v = 0;		/* not implemented */
		break;

	case RP_MR:
		v = 0;		/* not implemented */
		break;

	case RP_DT:
		v = DT_MOH | DT_RP06;
		break;

	case RP_SN:
		v = d->u[d->sel].sn;
		break;

	case RP_OF:
		v = d->u[d->sel].of;
		break;

	case RP_DC:
		v = d->u[d->sel].dcyl;
		break;

	case RP_CC:
		v = d->u[d->sel].ccyl;
		break;

	case RP_ER2:
		v = 0;
		break;

	case RP_ER3:
		v = 0;
		break;

	case RP_EC1:
		v = 0;		/* not implemented */
		break;

	case RP_EC2:
		v = 0;		/* not implemented */
		break;

	case RP_BAE:
		v = d->ba >> 16;
		break;

	case RP_CS3:
		v = 0;		/* not implemented */
		break;

	default:
		warn("rp_fetch(%o)", a);
		Trap4(020);
	}
# ifdef DEBUG
	printf("rp_fetch(%s)=%06o\n", reg_names[(a - d->csr_base) >> 1], v);
# endif
	return v;
}

void	
rp_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	RP *d = (RP *)dev->data;
	int i;

# ifdef DEBUG
	printf("rp_store(%s,%o)=%06o\n", reg_names[(a - d->csr_base) >> 1], mode, v);
# endif

	switch(a - d->csr_base) {

	case RP_CS1:
		if(mode == M_Word || mode == M_High) {
			d->ba = (d->ba & ~0x30000)
			      | ((v << (16 - CS1_Ash)) & 0x30000);
			/*
			 * the RSX driver seems to expect that a write into
			 * TRE causes the controller errors to reset (NED & NEM)
			 */
			if(v & CS1_Tre) {
				d->cs2 &= ~(CS2_Ned | CS2_Nem | CS2_Mxf);
				d->cs1 &= ~(CS1_Tre | CS1_Sc);
				d->cs1 |= (d->as ? CS1_Sc : 0);
			}
		}
		if(mode == M_Word || mode == M_Low)
			wcsr1(dev, v);
		break;

	case RP_WC:
		SETWORD(d->wc, mode, v);
		break;

	case RP_BA:
		v &= ~1;
		if(mode == M_Word || mode == M_Low)
			d->ba = (d->ba & ~0377) | (v & 0377);
		if(mode == M_Word || mode == M_High)
			d->ba = (d->ba & ~0177400) | (v & 0177400);
		break;

	case RP_DA:
		if(!drive_exists(d))
			break;
		if(mode == M_Word || mode == M_High)
			d->u[d->sel].dta = (v & DA_TAM) >> DA_TAS;
		if(mode == M_Word || mode == M_Low)
			d->u[d->sel].dsa = (v & DA_SAM) >> DA_SAS;
		break;

	case RP_CS2:
		if(mode == M_Word || mode == M_Low) {
			d->sel = v & CS2_U;
			d->cs2 = (d->cs2 & ~CS2_Bai) | (v & CS2_Bai);
			if(v & CS2_Clr)
				cclr(dev);
		}
		break;

	case RP_DS:		/* read only */
		break;

	case RP_ER1:
		if(!drive_exists(d))
			break;
		d->u[d->sel].er1 = 0;
		break;

	case RP_AS:
		if(mode == M_Word || mode == M_Low) {
			for(i = 0; i < RPS; i++)
				if(v & (1 << i))
					d->u[i].dstat &= ~DS_ATA;
			d->cs1 &= ~CS1_Sc;
			if((d->as &= ~v) != 0)
				d->cs1 |= CS1_Sc;
		}
		break;

	case RP_LA:		/* read only */
		break;

	case RP_DB:		/* not implemented */
		break;

	case RP_MR:		/* not inplemented */
		break;

	case RP_DT:		/* read only */
		break;

	case RP_SN:		/* read only */
		break;

	case RP_OF:		/* partially implemented */
		if(!drive_exists(d))
			break;
		if(mode == M_Word || mode == M_High)
			d->u[d->sel].of = v & OF_Fmt;
		break;

	case RP_DC:
		if(!drive_exists(d))
			break;
		v &= DC_Mask;
		SETWORD(d->u[d->sel].dcyl, mode, v);
		break;

	case RP_CC:		/* read only */
		break;

	case RP_ER2:		/* not implemented */
		if(!drive_exists(d))
			break;
		break;

	case RP_ER3:		/* not implemented */
		if(!drive_exists(d))
			break;
		break;

	case RP_EC1:		/* not implemented */
		break;

	case RP_EC2:		/* not implemented */
		break;

	case RP_BAE:
		if(mode == M_Word || mode == M_Low) {
			v &= 077;
			d->ba = (d->ba & 0177777) | (v << 16);
		}
		break;

	case RP_CS3:		/* not implemented */
		break;

	default:
		warn("rp_store(%o)", a);
		Trap4(020);
	}
}

/*
 * try to write into drive register
 */
static int
drive_exists(RP *d)
{
	if(d->u[d->sel].f.fd < 0) {
		/*
		 * if selected drive does not exist - error
		 */
		d->cs2 |= CS2_Ned;
		d->cs1 |= CS1_Tre | CS1_Sc;
		d->rdy = CS1_Rdy;
		return 0;
	}
	return 1;
}

/*
 * write into csr1 low byte
 */
static void
wcsr1(iodev_t *dev, u_short v)
{
	RP *d = (RP *)dev->data;

	if(drive_exists(d)) {
		d->u[d->sel].cs1 = v & CS1_Func;

		if(v & CS1_Go) {
			/*
			 * go bit
			 */
			d->rdy = 0;
			if(d->u[d->sel].dstat & DS_Err) {
				/*
				 * drive exists but has error
				 * only drive clear is accepted in this state
				 */
				if(d->u[d->sel].cs1 == F_Dclr) {
					dclr(d, d->sel);
					d->rdy = CS1_Rdy;
					goto setrdy;
				} else {
					d->cs2 |= CS2_Mxf;
					d->cs1 |= CS1_Tre | CS1_Sc;
					d->rdy = CS1_Rdy;
					goto setrdy;
				}
			} else {
				/*
			 	 * go set - clear some status
			 	 */
				d->cs2 &= ~(CS2_Ned | CS2_Nem | CS2_Mxf);

				if(d->u[d->sel].cs1 >= 051) {
					/*
					 * go and data transfer - clear status
					 */
					d->cs1 &= ~CS1_Tre;
					d->u[d->sel].dstat &= ~DS_ATA;
				}
			}
		}
	}

	/*
	 * if IE is toggled on and no go - interrupt
	 */
	if(!d->ien && (v & CS1_Ie) && d->rdy)
		IRQ(dev, d->irq_level);
	/*
	 * Interrupt also for the 2.11 autoconf if IE & RDY are
	 * written.
	 */
	if((v & (CS1_Ie | CS1_Rdy | CS1_Go)) == (CS1_Ie | CS1_Rdy) && d->rdy)
		IRQ(dev, d->irq_level);

	/*
	 * if IE is switched off - clear pending ints
	 */
	if(!(d->ien = v & CS1_Ie))
		dev->reqpri = 0;

	/*
	 * not ready, have go - do function else done
	 */
	if(!d->rdy)
		dofunc(dev);
	else
		return;

setrdy:
	if(d->rdy) {
		/*
		 * completion - set attention and request interrupt
		 */
		d->cs1 |= CS1_Sc;
		d->u[d->sel].dstat |= DS_ATA;
		d->as |= (1 << d->sel);
		if(d->ien)
			IRQ(dev, d->irq_level);
	}
}

/*
 * return interrupt vector and clear interrupt pending flag
 */
u_short	
rp_vector(iodev_t *dev)
{
# ifdef DEBUG
	printf("rp_vector -> %o\n", ((RP *)dev->data)->vector);
# endif
	dev->reqpri = 0;
	return ((RP *)dev->data)->vector;
}

/*
 * recompute present position from sector number
 */
static void
recomp_pos(RP *d, int blkno)
{
	d->u[d->sel].dsa = blkno % RPSECS;
	blkno /= RPSECS;
	d->u[d->sel].dta = blkno % RPTRKS;
	blkno /= RPTRKS;
	d->u[d->sel].ccyl = blkno;
	d->u[d->sel].dcyl = blkno;
}



static struct RPWIN *
mapfile(RP *d, struct RPFILE *f, u_int blkno, u_int prot)
{
	struct RPWIN *win;
	u_int wno = blkno / WINBLKS;

	/*
	 * Check whether this window is mapped
	 */
	d->map_calls++;
	for(win = f->win; win < &f->win[NWIN]; win++)
		if(win->mm != NULL && win->wno == wno) {
			DM(("mapfile(%u,%d) - already mapped; slot %u\n",
			    wno, f->iss, win - f->win));
			d->map_hits++;
			goto age;
		}

	/*
	 * Not mapped. Find an empty entry.
	 */
	for(win = f->win; win < &f->win[NWIN]; win++)
		if(win->mm == NULL)
			break;

	if(win == &f->win[NWIN]) {
		/*
		 * Nothing empty. Unmap the oldest one.
		 */
		win--;
		MSYNC(win->mm, WINSIZE);
		munmap(win->mm, WINSIZE);
		win->mm = NULL;
		DM(("mapfile(%u,%d) - unmapping slot %u(%u)\n",
		    wno, f->iss, win - f->win, win->wno));
		d->map_unmap++;
	}

	/*
	 * Now map the track
	 */
	DM(("mapfile(%u,%d) - mapping slot %u\n",
	    wno, f->iss, win - f->win));
	win->mm = mmap(0, WINSIZE, PROT_READ | prot,
	    MAP_FILE | MAP_SHARED, f->fd, WINSIZE * wno);
	if(win->mm == MAP_FAILED)
		panic("rl: can't mmap %s: %s", f->fn, strerror(errno));
	win->wno = wno;

  age:
	/*
	 * Age windows
	 */
	if(win != f->win) {
		struct RPWIN tmp = *win;
		while(--win >= f->win)
			win[1] = win[0];
		f->win[0] = tmp;
	}
	return f->win;
}


/*
 * Map the window of the given file at the offset and return a pointer
 * to that window.
 */
static struct RPWIN *
mapcyl(RP *d, RPU *rpu, u_int blkno, int wrt)
{
	if(wrt) {
		if(rpu->s.fd >= 0)
			return mapfile(d, &rpu->s, blkno, PROT_WRITE);
		else
			return mapfile(d, &rpu->f, blkno, PROT_WRITE);
	} else {
		if(rpu->s.fd >= 0) {
			if(rpu->smap[SMAP_IDX(blkno)] & SMAP_BIT(blkno))
				return mapfile(d, &rpu->s, blkno,
				    (rpu->wl==2)?0:PROT_WRITE);
			else
				return mapfile(d, &rpu->f, blkno, 0);
		} else
			return mapfile(d, &rpu->f, blkno, (rpu->wl==2)?0:PROT_WRITE);
	}
}

/*
 * do DMA
 */
void	
rp_dma(iodev_t *dev)
{
	RP	*d = (RP *)dev->data;
	RPU	*u = &d->u[d->sel];
	u_int	coff, blkno;
	u_int	edisk;
	u_int	bytes;
	u_int	secs = 0;	/* sectors to transfer */
	u_int	cb;		/* bytes to transfer from current cylinder */
	u_int	ab;		/* actually transfered */
	struct RPWIN *cyl;

	blkno = RPSECS * (u->dta + u->ccyl * RPTRKS) + u->dsa;

	edisk = RPNCYL * RPTRKS * RPSECS;

	bytes = ((0200000 - (u_int)d->wc) << 1);

	/*
	 * check if disk address overflows end of disk
	 */
	switch(u->cs1) {
	  case F_WchkH:
	  case F_WritH:
	  case F_ReadH:
		secs = (bytes + 519)/520;
		if(blkno + secs >= edisk) {
			secs = edisk - blkno;
			bytes = secs * 520;
			u->er1 |= E1_Aoe;
		}
		break;
	case F_Wchkd:
	case F_Write:
	case F_Read:
		secs = (bytes + 511) / 512;
		if(blkno + secs >= edisk) {
			secs = edisk - blkno;
			bytes = secs * 512;
			u->er1 |= E1_Aoe;
		}
		break;
	}

	if(blkno + secs == edisk - 1)
		u->dstat |= DS_Lst;
	else
		u->dstat &= ~DS_Lst;

	switch(u->cs1) {

	  case F_Wchkd:
	  case F_WchkH:
		d->ba += bytes;
		d->wc += bytes >> 1;
		blkno += (bytes + 511)/512;
		recomp_pos(d, blkno);
		break;


	  case F_Read:
		while(bytes > 0) {
			cb = (bytes < 512) ? bytes : 512;
			coff = (blkno * 512) % WINSIZE;
			cyl = mapcyl(d, u, blkno, 0);
			DDMA(("WriteMem %p + %u, %u bytes\n", cyl->mm, coff, cb));
			ab = dma(cyl->mm + coff, cb, d->ba, WriteMem, NULL);
			d->ba += ab;
			d->wc += ab >> 1;
			blkno++;
			recomp_pos(d, blkno);
			if(ab != cb) {
				d->cs2 |= CS2_Nem;
				d->cs1 |= CS1_Tre;
				break;
			}
			bytes -= ab;
		}
		break;

	  case F_Write:
		while(bytes > 0) {
			cb = (bytes < 512) ? bytes : 512;
			coff = (blkno * 512) % WINSIZE;
			cyl = mapcyl(d, u, blkno, 1);
			DDMA(("ReadMem %p + %u, %u bytes\n", cyl->mm, coff, cb));
			ab = dma(cyl->mm + coff, cb, d->ba, ReadMem, NULL);
			d->ba += ab;
			d->wc += ab >> 1;
			if(u->s.fd >= 0 && ab > 0)
				u->smap[SMAP_IDX(blkno)] |= SMAP_BIT(blkno);
			blkno++;
			recomp_pos(d, blkno);
			if(ab != cb) {
				d->cs2 |= CS2_Nem;
				d->cs1 |= CS1_Tre;
				break;
			}
			bytes -= ab;
		}
		break;

	  case F_ReadH:
		recomp_pos(d, blkno);
		while(bytes >= 8) {
			u_short h[4];

			/* 
			 * generate 4 word header
			 */
			h[0] = u->ccyl | 010000;
			h[1] = (u->dta << DA_TAS) | (u->dsa << DA_SAS);
			h[2] = h[3] = 0;	/* probably ECC */
# if 0
			printf("R: %06o %06o %06o %06o\n", h[0], h[1], h[2], h[3]);
# endif
			DDMA(("WriteMem h, 8 bytes\n"));
			ab = dma((u_char *)h, 8, d->ba, WriteMem, NULL);
			d->wc += 4;
			d->ba += ab;
			bytes -= ab;
			if(ab < 8) {
				d->cs2 |= CS2_Nem;
				d->cs1 |= CS1_Tre;
				break;
			}

			if(bytes == 0)
				break;

			cb = (bytes < 512) ? bytes : 512;
			coff = (blkno * 512) % WINSIZE;
			cyl = mapcyl(d, u, blkno, 0);
			DDMA(("WriteMem %p + %u, %u bytes\n", cyl->mm, coff, cb));
			ab = dma(cyl->mm + coff, cb, d->ba, WriteMem, NULL);
			d->ba += ab;
			d->wc += ab >> 1;
			recomp_pos(d, ++blkno);
			if(ab < cb) {
				d->cs2 |= CS2_Nem;
				d->cs1 |= CS1_Tre;
				break;
			}

			bytes -= ab;
		}
		break;


	  case F_WritH:
		recomp_pos(d, blkno);
		while(bytes >= 8) {
			/* 
			 * skip 4 word header
			 */
			d->ba += 8;
			d->wc += 4;
			bytes -= 8;

			if(bytes == 0)
				break;

			cb = (bytes < 512) ? bytes : 512;
			coff = (blkno * 512) % WINSIZE;
			cyl = mapcyl(d, u, blkno, 1);
			DDMA(("ReadMem %p + %u, %u bytes\n", cyl->mm, coff, cb));
			ab = dma(cyl->mm + coff, cb, d->ba, ReadMem, NULL);
			d->ba += ab;
			d->wc += ab >> 1;
			if(u->s.fd >= 0 && ab > 0)
				u->smap[SMAP_IDX(blkno)] |= SMAP_BIT(blkno);
			recomp_pos(d, ++blkno);
			if(ab < cb) {
				d->cs2 |= CS2_Nem;
				d->cs1 |= CS1_Tre;
				break;
			}

			bytes -= ab;
		}
		break;

	  default:
		panic("rp_dma(%o)\n", u->cs1);
	}

	/*
	 * read and attention
	 */
	dev->reqpri = 0;

	d->rdy = CS1_Rdy | CS1_Sc;
	u->dstat |= DS_ATA;
	d->as |= (1 << d->sel);
	if(d->ien)
		IRQ(dev, d->irq_level);

}

/*
 * print status
 */
void	
rp_info(iodev_t *dev)
{
	RP *d = (RP *)dev->data;
	RPU *u;

	printf("RHV?? Controller\n");
	printf("CSR at %08o, vector %03o at level %d, %d RP06 disks\n", d->csr_base, 
			d->vector, d->irq_level, RPS);
	printf("MAP: calls=%lu hits=%lu unmap=%lu\n", d->map_calls,
	    d->map_hits, d->map_unmap);

	printf("U CS1  DTA  DSA     DS    ER1   SN     OF CCYL DCYL WL\n");

	for(u = d->u; u < &d->u[RPS]; u++) {
		if(u->f.fd < 0)
			continue;
		printf("%1d", u - d->u);
		printf(" %03o", u->cs1);
		printf("%5o", u->dta);
		printf("%5o", u->dsa);
		printf(" %06o", u->dstat);
		printf(" %06o", u->er1);
		printf(" %04x", u->sn);
		printf(" %06o", u->of);
		printf(" %4d", u->ccyl);
		printf(" %4d", u->dcyl);
		printf("  %d %s %s\n", u->wl, u->f.fn,
		    u->s.fd >= 0 ? u->s.fn : "");
	}
}

/*
 * controller clear
 */
static void
cclr(iodev_t *dev)
{
	int i;
	RP *d = (RP *)dev->data;

	dev->reqpri = 0;

	d->rdy = CS1_Rdy;	/* RPCS1 */
	d->ien = 0;
	d->ba = 0;		/* RPBA, RPBAE */
	d->cs2 = 0;		/* RPCS2 */
	d->sel = 0;
	d->cs1 = 0;
	d->as = 0;

	for(i = 0; i < RPS; i++)
		dclr(d, i);
}

/*
 * drive clear
 */
static void
dclr(RP *d, int u)
{
	d->u[u].cs1 = 0;	/* RPCS1 */
	d->u[u].dta = 0;	/* RPDA */
	d->u[u].dsa = 0;
	
	/* clear ERR and ATA 	   RPDS */
	d->u[u].dstat &= DS_Lst | DS_Vv;

	d->u[u].er1 = 0;

	/* dcyl, ccyl not touched */
}

/*
 * called if go bit set in csr1
 * do actual function
 */
static void
dofunc(iodev_t *dev)
{
	RP *d = (RP *)dev->data;

	switch(d->u[d->sel].cs1) {

	case F_Nop:
	case F_Relse:	/* should RELEASE set ATA ? */
	case F_Recal:
		d->rdy = CS1_Rdy;
		break;

	case F_Unl:
		dclr(d, d->sel);
		d->rdy = CS1_Rdy;
		d->u[d->sel].dstat &= ~DS_Vv;
		break;

	case F_Dclr:	/* should DCLR set ATA ? */
		dclr(d, d->sel);
		d->rdy = CS1_Rdy;
		break;

	case F_Pack:
	case F_Pres:
		d->u[d->sel].dstat |= DS_Vv;
		d->rdy = CS1_Rdy;
		break;

	case F_Srch:
	case F_Seek:
		if(!check_parms(d)) {
			d->u[d->sel].er1 |= E1_Iae;
			d->u[d->sel].dstat |= DS_Err;
			d->rdy = CS1_Rdy;
			break;
		}
		d->u[d->sel].ccyl = d->u[d->sel].dcyl;
		d->rdy = CS1_Rdy;
		break;

	case F_ReadH:
	case F_Read:
	case F_WritH:
	case F_Write:
	case F_Wchkd:
	case F_WchkH:
		if(!check_parms(d)) {
			d->u[d->sel].er1 |= E1_Iae;
			d->u[d->sel].dstat |= DS_Err;
			d->rdy = CS1_Rdy;
			break;
		}
		d->u[d->sel].ccyl = d->u[d->sel].dcyl;
		IRQ(dev, DMAPRI);
		break;

	default:
		warn("rp: function not implemented: %06o\n", d->u[d->sel].cs1);
		d->u[d->sel].er1 |= E1_Ilf;
		d->u[d->sel].dstat |= DS_Err;
		d->rdy = CS1_Rdy;
		break;
	}
}

/*
 * check if sector/track/cylinder are ok
 */
static int
check_parms(RP *d)
{
	return d->u[d->sel].dsa < RPSECS
	    && d->u[d->sel].dta < RPTRKS
	    && d->u[d->sel].dcyl < RPNCYL;
}



/*
 * command interface
 */
static int	chkdma(iodev_t *dev);
static int	get_drive(RP *rp, char *arg);


static int
rpc_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	rp_info(dev);
	return 0;
}


/*
 * load file on drive
 */
static int
rpc_load(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RP	*rp = dev->data;

	if(argc != 2 && argc != 3)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rp, argv[0])) < 0)
		return 0;

	if(rp->u[drive].f.fd >= 0) {
		printf("rp%d: already loaded and spinning\n", drive);
		return 0;
	}

	load(dev, drive, argv[1], argv[2], 0);
	if(rp->u[drive].f.fd >= 0)
		printf("rp%d: loaded\n", drive);
	return 0;
}

/*
 * unload disk from drive
 */
static int
rpc_unload(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RP	*rp = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rp, argv[0])) < 0)
		return 0;

	if(rp->u[drive].f.fd < 0) {
		printf("rp%d: no disk loaded\n", drive);
		return 0;
	}

	unload(rp, drive);
	if(rp->u[drive].f.fd < 0)
		printf("rp%d: unloaded\n", drive);
	return 0;
}


/*
 * write protect drive
 */
static int
rpc_wlock(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RP	*rp = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rp, argv[0])) < 0)
		return 0;

	if(rp->u[drive].f.fd < 0) {
		printf("rp%d: not loaded\n", drive);
		return 0;
	}

	if(rp->u[drive].wl) {
		printf("rp%d: already write locked\n", drive);
		return 0;
	}

	rp->u[drive].wl = 1;
	printf("rp%d write locked\n", drive);
	return 0;
}


/*
 * write enable drive
 */
static int
rpc_wunlock(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RP	*rp = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rp, argv[0])) < 0)
		return 0;

	if(rp->u[drive].f.fd < 0) {
		printf("rp%d: not loaded\n", drive);
		return 0;
	}

	if(rp->u[drive].wl == 0) {
		printf("rp%d: not write locked\n", drive);
		return 0;
	}
	if(rp->u[drive].wl == 2) {
		printf("rp%d: cannot unlock\n", drive);
		return 0;
	}

	rp->u[drive].wl = 0;
	printf("rp%d: write enabled\n", drive);
	return 0;
}

/*
 * toggle debugging
 */
static int
rpc_debug(iodev_t *dev UNUSED, int argc, char **argv)
{
	if(argc == 1) {
		if(strcmp(argv[0], "on") == 0)
			rpdebug = 1;
		else if(strcmp(argv[0], "off") == 0)
			rpdebug = 0;
		else
			return 1;
	} else if(argc > 1)
		return 1;

	printf("rp: debugging %s\n", rpdebug ? "on" : "off");
	return 0;
}

/*
 * I don't like to include too much error checking into the code
 * so we prevent the user from changing anything if a dma request is pending.
 * If we wouldn't someone could just unload a disk that is about to get
 * dma granted and the dma routine had to check if the disk is still there.
 */
static int
chkdma(iodev_t *dev)
{
	if(dev->reqpri == DMAPRI) {
		printf("rp: DMA request pending - try again\n");
		return 1;
	}
	return 0;
}

/*
 * parse a drive number
 */
static int
get_drive(RP *rp UNUSED, char *arg)
{
	long	drive;
	char	*end;

	drive = strtol(arg, &end, 0);

	if(*end || drive < 0 || drive >= RPS) {
		printf("rp: bad drive number '%s'\n", arg);
		return -1;
	}
	return drive;
}
