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

/*
 * RLV12 + RL02
 *
 * Arguments:
 *	ctrl csr_base vector irq sync
 *	  disk-no file
 * 	end
 *
 * sync is the interval between msync()s in milliseconds
 */

# include "proc.h"

RCSID("$Id: dev_rl.c 511 2006-10-27 12:04:38Z brandt_h $")

# define D(X)
# define D1(X)
# define DDMA(X)
# define DM(X)	/* printf X */


# define RLS	4			/* disks per controller */

/* Two surfaces with 40 256 byte sectors each, 512 tracks */
# define RLSECS	40			/* sectors per track */
# define RLTRKS	2			/* tracks per cylinder */
# define RLCYL	512			/* cylinders per disk */
# define RLBYTES 256			/* bytes per sector */

# define RLSIZE	(RLBYTES*RLCYL*RLTRKS*RLSECS)


/*
 * These numbers are configurable. WINCYL may any power of two
 * from 1 to 512.
 */
# define WINCYL	16			/* cylinders per window */
# define NWIN	16			/* windows to map */

/* this is one window */
# define WINSIZE (WINCYL * RLSECS * RLTRKS * RLBYTES)

/*
 * rl02 drive status bits
 */
enum {
	Load_state	= 00,
	Spin_up		= 01,
	Brush_cycle	= 02,
	Load_heads	= 03,
	Seek_track	= 04,
	Lock_on		= 05,
	Unload_heads	= 06,
	Spin_down	= 07,
	Brush_home	= 010,
	Heads_out	= 020,
	Cover_open	= 040,
	Head_select	= 0100,
	Drive_rl02	= 0200,
	Select_error	= 0400,
	Volume_check	= 01000,
	WGate_error	= 02000,
	Spin_error	= 04000,
	Seek_time_out	= 010000,
	Write_lock	= 020000,
	Current_error	= 040000,
	Write_error	= 0100000,
	DriveStat_err	= 0157400,
};

enum {
	CS_CMD		= 0000016,	/* command bits */
	  CSCMD_NOP	= 000,
	  CSCMD_WCHK	= 002,
	  CSCMD_GSTAT	= 004,
	  CSCMD_SEEK	= 006,
	  CSCMD_RDHDR	= 010,
	  CSCMD_WRITE	= 012,
	  CSCMD_READ	= 014,
	  CSCMD_RRAW	= 016,
	CS_EBA		= 0000060,	/* extended buffer address */
	CS_IEN		= 0000100,	/* interrupt enable */
	CS_CRDY		= 0000200,	/* controller ready */
	CS_GO		= 0000200,	/* go */
	CS_CERRMSK	= 0036000,	/* controller error mask */
	  CSERR_OPI	= 0002000,	/* operation incomplete */
	  CSERR_CRC	= 0004000,	/* CRC or write check error */
	  CSERR_HCRC	= 0006000,	/* header CRC */
	  CSERR_DLT	= 0010000,	/* data late */
	  CSERR_HNF	= 0012000,	/* header not found */
	  CSERR_NXM	= 0020000,	/* non-existant memory */
	  CSERR_MPE	= 0022000,	/* memory-parity */
	CS_DRE		= 0040000,	/* drive error */
	CS_ERR		= 0100000,	/* error */
	CS_ERRMSK	= 0076000,	/* error mask */
};

typedef struct RL RL;
typedef struct RLU RLU;


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

	u_int	command;	/* CSR 1:3 			*/
	u_int	ien;		/* CSR 6 			*/
	u_int	crdy;		/* CSR 7			*/
	u_int	sel;		/* CSR 8:9 			*/
	u_int	err;		/* CSR 10:15			*/
	u_int	ba;		/* BA 0:15, CSR 4:5, BAE 0:5	*/

	u_int	da;		/* multi-interpretable DA	*/
	u_int	buffer[256];	/* internal buffer		*/
	u_int	wc;		/* mpr during r/w		*/
	u_int	*mpr;		/* mpr pointer			*/

	struct RLU {
		u_int	op;		/* if set - cover open		*/
		u_int	pp;		/* present position (cylinder)	*/
		u_int	ph;		/* present head			*/
		u_int	wl;		/* write locked			*/
		u_int	st;		/* drive status			*/
		char   *fn;		/* file name 			*/
		int	fd;		/* open file descriptor */
		struct RLWIN {
			char   *mm;	/* mapped files	*/
			u_int	wno;	/* window number  */
		}	win[NWIN];
	}	disks[RLS];

	int	load_timer;
	int	timer_running;

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

static int sector;

void	rl_ctrl_create(iodev_t *, int, char **);
void	rl_dev_create(iodev_t *, int, char **);
void	rl_ctrl_complete(iodev_t *);

void	rl_reset(iodev_t *);
u_short	rl_fetch(iodev_t *, u_int);
void	rl_store(iodev_t *, u_int, mmode_t, u_short);
u_short	rl_vector(iodev_t *);
void	rl_dma(iodev_t *);
void	rl_async(iodev_t *);
void	rl_info(iodev_t *);
void	rl_command(iodev_t *, int, char *[]);
void	rl_flush(iodev_t *);


static void dofunc(iodev_t *);
static void rlsync(void *);
static void unload(RL *, int, int);
static void load(RL *, int, char *, int);
static u_short crc(u_short, u_short);
static void load_func(void *);

static int rlc_info(iodev_t *dev, int argc, char **argv);
static int rlc_load(iodev_t *dev, int argc, char **argv);
static int rlc_unload(iodev_t *dev, int argc, char **argv);
static int rlc_wlock(iodev_t *dev, int argc, char **argv);
static int rlc_wunlock(iodev_t *dev, int argc, char **argv);
static int rlc_open(iodev_t *dev, int argc, char **argv);

iocmd_t rl_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			rlc_info },
	{ "load",	"drive-no file", 	rlc_load },
	{ "unload",	"drive-no",		rlc_unload },
	{ "wlock",	"drive-no",		rlc_wlock },
	{ "wunlock",	"drive-no",		rlc_wunlock },
	{ "open",	"drive-no [o|c]",	rlc_open },
	{ NULL,		NULL,			NULL }
};


ioops_t rl_ops = {
	rl_ctrl_create,		/* ctrl_create		*/
	rl_dev_create,		/* dev_create		*/
	rl_ctrl_complete,	/* ctrl_complete	*/
	rl_reset,		/* reset		*/
	rl_fetch,		/* fetch		*/
	rl_store,		/* store		*/
	rl_vector,		/* vector		*/
	rl_dma,			/* dma			*/
	0,			/* async 		*/
	rl_info,		/* info			*/
	rl_flush,		/* flush		*/
	rl_cmds,		/* cmds */
};

enum {
	RL_CSR		= 000,
	RL_BA		= 002,
	RL_DA		= 004,
	RL_MP		= 006,
	RL_BAE		= 010,
};

/*
 * initialise controler
 * args:
 *	CSR-base
 *	vector
 *	interrupt request level
 */
void
rl_ctrl_create(iodev_t *dev, int argc, char **argv)
{
	RL	*d;
	int	i;

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

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

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

	proc.iopage[IOP(d->csr_base + RL_CSR)] = dev;
	proc.iopage[IOP(d->csr_base + RL_BA )] = dev;
	proc.iopage[IOP(d->csr_base + RL_DA )] = dev;
	proc.iopage[IOP(d->csr_base + RL_MP )] = dev;
	proc.iopage[IOP(d->csr_base + RL_BAE)] = dev;

	for(i = 0; i < RLS; i++) {
		d->disks[i].st = Load_state | Drive_rl02 | Brush_home;
		d->disks[i].fd = -1;
	}

	/* create a disabled timer */
	d->load_timer = register_timer(0, load_func, d);
	d->timer_running = 0;
}


/*
 * create drive
 * args:
 *	drive-no
 *	file
 *	[shadow]
 */
void
rl_dev_create(iodev_t *dev, int argc, char **argv)
{
	RL *d = (RL *)dev->data;
	int	i;

	if(argc != 2)
		conf_panic("rl: bad args int device description");

	i = (int)strtol(argv[0], 0, 0);
	if(i > 4)
		conf_panic("rl: controller supports up to 4 disks only");

	if(d->disks[i].fd >= 0)
		unload(d, i, 1);
	load(d, i, argv[1], 1);
}


/*
 * load new disk in drive i
 * should we simulate the spin up, brushes ... ?
 * set write lock, if file is read-only
 */
static void
load(RL *d, int i, char *fn, int isconf)
{
	typedef void (*pfunc)(char *, ...);
	struct stat statb;
	pfunc ef = isconf ? (pfunc)conf_panic : (pfunc)printf;
	RLU *disk = &d->disks[i];
	int w;

	disk->wl = (access(fn, W_OK) != 0);
	disk->fd = open(fn, disk->wl ? O_RDONLY : (O_RDWR | O_CREAT), 0666);
	if(disk->fd < 0) {
		(*ef)("rl%d: can't open %s: %s", i, fn, strerror(errno));
		return;
	}

	if(fstat(disk->fd, &statb)) {
		(*ef)("rl%d: can't stat %s: %s", i, fn, strerror(errno));
		(void)close(disk->fd);
		disk->fd = -1;
		return;
	}

	if(statb.st_size < RLSIZE) {
		if(disk->wl) {
			(*ef)("rl%d: can't expand %s to required size\n", i, fn);
			(void)close(disk->fd);
			disk->fd = -1;
			return;
		}
		lseek(disk->fd, RLSIZE - 1, 0);
		write(disk->fd, "\0", 1);
	} else if(statb.st_size > RLSIZE) {
		if(disk->wl) {
			(*ef)("rl%d: can't truncate %s to required size\n", i, fn);
			(void)close(disk->fd);
			disk->fd = -1;
			return;
		}
		ftruncate(disk->fd, RLSIZE);
	}

	for(w = 0; w < NWIN; w++)
		disk->win[w].mm = NULL;

	disk->fn = xalloc((strlen(fn) + 1) * sizeof(char));
	strcpy(disk->fn, fn);

	disk->pp = 0;
	disk->ph = 0;
	disk->op = 0;

	/*
	 * If we are in the config phase - just load the drive and spin it
	 * up very quickly
	 */
	if(isconf) {
		disk->st = Volume_check | Brush_home
			 | Lock_on | Heads_out | Drive_rl02;
		return;
	}
	disk->st = Volume_check | Brush_home | Spin_up | Drive_rl02;
	if(!d->timer_running) {
		d->timer_running = 1;
		reset_timer(10, d->load_timer);
	}
}



static void
load_func(void *a)
{
	RL *d = (RL *)a;
	int flag = 0;
	RLU *disk;

	for(disk = d->disks; disk < &d->disks[RLS]; disk++) {
		switch(disk->st & 07) {
		  case Load_state:
			break;

		  case Spin_up:
			printf("rl%d: starting brush cycle\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Brush_cycle;
			flag = 1;
			break;

		  case Brush_cycle:
			printf("rl%d: loading heads\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Load_heads;
			disk->st &= ~Brush_home;
			flag = 1;
			break;

		  case Load_heads:
			printf("rl%d: seeking\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Seek_track | Heads_out;
			disk->st |= Brush_home;
			flag = 1;
			break;

		  case Seek_track:
			printf("rl%d: lock on\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Lock_on;
			flag = 1;
			break;

		  case Lock_on:
			break;

		  case Unload_heads:
			printf("rl%d: spinning down\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Spin_down;
			flag = 1;
			break;

		  case Spin_down:
			printf("rl%d: load state\n", disk - d->disks);
			disk->st = (disk->st & ~07) | Load_state;
			flag = 1;
			break;
		}
	}
	if(flag && !d->timer_running) {
		d->timer_running = 1;
		reset_timer(10, d->load_timer);
	} else if(!flag && d->timer_running) {
		d->timer_running = 0;
		reset_timer(0, d->load_timer);
	}
}

/*
 * unload disk from drive i
 */
static void
unload(RL *d, int i, int isconf)
{
	RLU *disk = &d->disks[i];
	int w;

	if(disk->fd < 0)
		return;
	disk->pp = 0;
	disk->ph = 0;
	free(disk->fn);
	disk->fn = NULL;
	disk->op = 0;


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

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

	if(isconf) {
		disk->st = Load_state | Drive_rl02 | Brush_home;
		return;
	}

	disk->st = Unload_heads | Drive_rl02 | Brush_home;
	if(!d->timer_running) {
		d->timer_running = 1;
		reset_timer(10, d->load_timer);
	}
}


void
rl_ctrl_complete(iodev_t *dev)
{
	RL *d = (RL *)dev->data;

	register_timer(d->sync_rate, rlsync, d);
}


static void
rlsync(void *v)
{
	RL *d = v;
	int i, w;

	for(i = 0; i < RLS; i++) {
		for(w = 0; w < NWIN; w++)
			if(d->disks[i].win[w].mm)
# ifdef HAVE_MS_SYNC
				msync(d->disks[i].win[w].mm, WINSIZE, MS_SYNC);
# else
				msync(d->disks[i].win[w].mm, WINSIZE);
# endif
	}
}

void
rl_flush(iodev_t *dev)
{
	rlsync(dev->data);
}

void
rl_reset(iodev_t *dev)
{
	RL *d = (RL *)dev->data;

	d->command	= 0;
	d->ien		= 0;
	d->sel		= 0;
	d->err		= 0;
	d->ba		= 0;
	d->crdy 	= CS_CRDY;

	d->da		= 0;
	d->wc 		= 0;
	memset((caddr_t)d->buffer, 0, sizeof(d->buffer));
	d->mpr 		= &d->wc;
}

/*
 * fetch io-register
 */
u_short
rl_fetch(iodev_t *dev, u_int a)
{
	RL *d = (RL *)dev->data;
	u_short v;

	switch(a - d->csr_base) {

	case RL_CSR:
		v = ((d->disks[d->sel].fd >= 0) ? 1 : 0) /* DRDY */
		  | d->command			/* FUNC */
		  | ((d->ba >> 12) & 060)	/* BA 16:17 */
		  | d->ien			/* IEN */
		  | d->crdy			/* CRDY */
		  | (d->sel << 8)		/* DSEL */
		  | d->err;			/* ERROR */
		break;

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

	case RL_DA:
		v = d->da;
		break;

	case RL_MP:
		v = *d->mpr;
		switch(d->command) {

		case 010:	/* read header */
			if(d->mpr < &d->buffer[2])
				d->mpr++;
			break;

		case 04:	/* get status */
			break;
		}
		break;

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

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

	D1(printf("rl: %06o: fetch(%02o)=%06o\n", proc.reg[7], a-d->csr_base, v);)

	return v;
}

/*
 * store in io-regs
 */
void
rl_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	RL *d = (RL *)dev->data;

	D1(printf("rl: %06o: store(%02o)=%06o (%d)\n", proc.reg[7], a-d->csr_base, v, mode);)

	switch(a - d->csr_base) {

	case RL_CSR:
		if(mode == M_Word || mode == M_High) {
			d->sel = (v >> 8) & 3;
		}
		if(mode == M_Word || mode == M_Low) {
			d->ba = (d->ba & ~0600000) | ((v & CS_EBA) << 12);
			if(!(v & CS_GO)) {	/* GO */
				d->crdy = 0;
				d->err = 0;
			}
			if(!d->ien && (v & CS_IEN) && d->crdy)
				IRQ(dev, d->irq_level);
			if(!(d->ien = v & CS_IEN))
				dev->reqpri = 0;
			d->command = v & CS_CMD;

			if(!d->crdy) {
				/* go bit was set and droppped ready */
				d->err &= CS_DRE;
				if(d->disks[d->sel].fd < 0)
					d->err |= CSERR_OPI;
				if(!d->err)
					dofunc(dev);
				if(d->err & CS_ERRMSK) {
					d->err |= CS_ERR;
					d->crdy = CS_CRDY;
				}
				if(d->ien && d->crdy)
					IRQ(dev, d->irq_level);
			}
		}
		break;

	case RL_BA:		/* has 22 bit! */
		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 RL_DA:
		SETWORD(d->da, mode, v);
		break;

	case RL_MP:
		d->mpr = &d->wc;
		SETWORD(*d->mpr, mode, v);
		break;

	case RL_BAE:
		if(mode == M_High)
			break;
		v &= 077;
		d->ba = (d->ba & 0177777) | (v << 16);
		break;

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

# if 0
D(
	if(d->err) {
		printf("rl: error %o\n", d->err);
		proc.halt++;
	}
)
# endif
}

/*
 * interrupt ack.
 */
u_short
rl_vector(iodev_t *dev)
{
	RL 	*d = (RL *)dev->data;

	dev->reqpri = 0;

	return d->vector;
}

static struct RLWIN *
maptrack(RL *rl, RLU *disk, u_int off)
{
	struct RLWIN *win;
	u_int wno = off / WINSIZE;

	/*
	 * Check whether this cylinder is mapped
	 */
	rl->map_calls++;
	for(win = disk->win; win < &disk->win[NWIN]; win++)
		if(win->mm != NULL && win->wno == wno) {
			DM(("maptrack %u - already mapped; slot %u\n", wno,
			    win - disk->win));
			rl->map_hits++;
			return win;
		}

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

	if(win == &disk->win[NWIN]) {
		/*
		 * Nothing empty. Unmap the oldest one.
		 */
		win--;
# ifdef HAVE_MS_SYNC
		msync(win->mm, WINSIZE, MS_SYNC);
# else
		msync(win->mm, WINSIZE);
# endif
		munmap(win->mm, WINSIZE);
		win->mm = NULL;
		DM(("maptrack %u - unmapping slot %u/%u\n", wno,
		    win - disk->win, win->wno));
		rl->map_unmap++;
	}

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

	return win;
}


/*
 * dma
 *
 * currently assumes that the disk is not unloaded after initiating
 * the command and before doing dma
 */
void
rl_dma(iodev_t *dev)
{
	RL 	*d = (RL *)dev->data;
	int	sec;		/* sector on track */
	u_int	off;		/* starting byte */
	u_int	coff;		/* starting byte in window */
	u_int	bytes;		/* bytes to transfer */
	u_int	abytes;		/* bytes actualy transfered */
	u_int	fill;		/* bytes to zero-fill in last sector */
	int	err;		/* possible OPI condition */
	int	flg;		/* compare flag */
	RLU *disk = &d->disks[d->sel];
	struct RLWIN *win;


	/*
	 * set ready and request interrupt
	 */
	d->crdy = CS_CRDY;
	dev->reqpri = 0;
	if(d->ien)
		IRQ(dev, d->irq_level);

 	sec = (d->command == 016) ? (sector % RLSECS) : (d->da & 077);

	/*
	 * check that sector is legal
	 */
	if(sec >= RLSECS) {
		d->err |= CSERR_OPI|CS_ERR;
		return;
	}

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

	/*
	 * RL can't do implicite seeks
	 */
	err = 0;
	if(RLBYTES * sec + bytes > RLSECS * RLBYTES) {
		err = CSERR_HNF|CS_ERR;
		bytes = (RLSECS - sec) * RLBYTES;
	}

	off = RLBYTES * (sec + RLSECS * (disk->ph + RLTRKS * disk->pp));
	coff = off % WINSIZE;
	win = maptrack(d, disk, off);

	switch(d->command) {

	case CSCMD_WCHK:	/* write check */
		DDMA(printf("rl%d: %06o DMA WCHK mem=%08o bytes=%d\n",
			d->sel, proc.reg[7], d->ba, bytes));
		abytes = dma(win->mm + coff, bytes, d->ba, CmpMem, &flg);
		d->da = (d->da & ~077) | ((d->da + (abytes + 255)/256) & 077);
		if(flg)
			err = CSERR_CRC|CS_ERR;
		break;

	case CSCMD_WRITE:	/* write data */
		DDMA(printf("rl%d: %06o DMA WRITE mem=%08o bytes=%d\n",
			d->sel, proc.reg[7], d->ba, bytes));
		abytes = dma(win->mm + coff, bytes, d->ba, ReadMem, NULL);
		d->da = (d->da & ~077) | ((d->da + (abytes + 255)/256) & 077);
		/*
		 * must zero-fill partial sector
		 */
		if(abytes == bytes && (fill = 256 - bytes % 256) != 0 && fill != 256)
			(void)memset(win->mm + off + abytes, 0, fill);
		break;

	case CSCMD_READ:	/* read data */
	case CSCMD_RRAW:	/* read data without check */
		DDMA(printf("rl%d: %06o DMA READ mem=%08o bytes=%d\n",
			d->sel, proc.reg[7], d->ba, bytes));
		abytes = dma(win->mm + coff, bytes, d->ba, WriteMem, NULL);
		d->da = (d->da & ~077) | ((d->da + (abytes + 255)/256) & 077);
		break;

	default:
		panic("bad switch: %s, %d", __FILE__, __LINE__);
	}

	if(abytes < bytes)
		d->err |= CSERR_NXM|CS_ERR;
	else
		d->err |= err;		/* may be OPI */

	d->ba += abytes;

	/*
	 * Age windows
	 */
	if(win != disk->win) {
		struct RLWIN tmp = *win;
		while(--win >= disk->win)
			win[1] = win[0];
		disk->win[0] = tmp;
	}
}

/*
 * doing
 */
static void
dofunc(iodev_t *dev)
{
	RL *d = (RL *)dev->data;
	RLU *disk = &d->disks[d->sel];

	D(printf("dofunc: %o %o\n", d->command, d->da));

	if(disk->st & Volume_check) {
		d->err |= CS_DRE;
		if(d->command != CSCMD_GSTAT) {
			d->crdy = CS_CRDY;
			return;
		}
	}

	if(d->command != CSCMD_NOP && d->command != CSCMD_GSTAT && disk->fd < 0) {
		d->err |= CSERR_OPI;
		return;
	}

	switch(d->command) {

	case CSCMD_NOP:		/* maintenance (nop) */
		d->crdy = CS_CRDY;
		d->err &= ~CS_DRE;	/* drive error */
		break;

	case CSCMD_GSTAT:	/* get status */
		if((d->da & 03) != 03) {
			d->err |= CSERR_OPI;
		} else {
			d->err &= ~CS_DRE;
			if(d->da & 010) {
				/* clear error bits */
				if(disk->fd >= 0)
					disk->st = Brush_home | Lock_on
						| Heads_out | Drive_rl02;
				else
					disk->st = Load_state | Drive_rl02
						| Brush_home;
			}

			d->buffer[0] = disk->st
				     | (disk->wl << 13)
				     | (disk->op << 5)
				     | (disk->ph << 6);
			d->mpr = d->buffer;
		}
		d->crdy = CS_CRDY;
		break;

	case CSCMD_SEEK: {	/* seek */
		int diff = d->da >> 7;
		int np = disk->pp + ((d->da & 04) ? (diff) : (-diff));

		if(np < 0) {
			disk->pp = 0;	/* XXX how about errors ? */
			disk->ph = 0;
		} else if(np >= 512) {
			disk->pp = 511;	/* XXX how about errors ? */
			disk->ph = 0;
		} else {
			disk->pp = np;
			disk->ph = (d->da & 020) ? 1 : 0;
		} 
		d->crdy = CS_CRDY;
		}
		break;

	case CSCMD_RDHDR:	/* read header */
		d->buffer[0] = (disk->pp << 7) 
			     | (disk->ph << 6)
			     | (sector++ % 40);
		d->buffer[1] = 0;
		d->buffer[2] = crc(d->buffer[0], 0);
		d->mpr = d->buffer;
		d->crdy = CS_CRDY;
		break;

	case CSCMD_WRITE:	/* write data */
		if(disk->wl) {
			d->err |= CS_DRE;
			break;
		}
		/* durchfall */

	case CSCMD_READ:	/* read data */
	case CSCMD_WCHK:	/* write check */
		D(printf("r/w: %3ho [%d,%d,%d,%d]\n",
			d->command, (d->da >> 7) & 0777,
			(d->da >> 6) & 1, d->da & 077,
			0400000 - ((u_int)(u_short)d->wc << 1));)
		if(((d->da >> 7) & 0777) != disk->pp ||
     		   ((d->da >> 6) & 0001) != disk->ph ||
		   ((d->da >> 0) & 0077) >= 40) {
			d->err |= CSERR_HNF;
		} else
			IRQ(dev, DMAPRI);
		break;

	case CSCMD_RRAW:	/* read data without header check */
		IRQ(dev, DMAPRI);
		break;
	}
}

/*
 * This could probably be optimized
 */
static u_short
xcrc(u_short w, u_short old)
{
	int cnt = 020;

	while(cnt--) {
		if((old + (w & 1)) & 1) {
			w >>= 1;
			old >>= 1;
			old = (old & ~0120001) | (old ^ 0120001);
		} else {
			w >>= 1;
			old >>= 1;
		}
	}
	return old;
}

static u_short
crc(u_short w1, u_short w2)
{
	return xcrc(w2, xcrc(w1, 0));
}

void
rl_info(iodev_t *dev)
{
	RL *d = (RL *)dev->data;
	RLU *disk;

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

	printf("UNIT\tPP\tPH\tWL\tOP\tST\tFile\n");

	for(disk = d->disks; disk < &d->disks[RLS]; disk++) {
		if(disk->fd < 0)
			continue;
		printf("%u\t%u\t%u\t%u\t%u\t%06o\t%s\n", disk - d->disks,
			disk->pp, disk->ph, disk->wl, disk->op, disk->st,
			disk->fn);
	}
}


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


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


/*
 * load new disk on drive
 */
static int
rlc_load(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RL	*rl =dev->data;

	if(argc != 2)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rl, argv[0])) < 0)
		return 0;
	if(rl->disks[drive].fd >= 0) {
		printf("rl%d: already loaded and spinning\n", drive);
		return 0;
	}
	load(rl, drive, argv[1], 0);
	if(rl->disks[drive].fd >= 0)
		printf("rl%d: loaded\n", drive);
	return 0;
}

/*
 * unload disk from drive
 */
static int
rlc_unload(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RL	*rl =dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rl, argv[0])) < 0)
		return 0;
	if(rl->disks[drive].fd < 0) {
		printf("rl%d: no disk loaded\n", drive);
		return 0;
	}
	unload(rl, drive, 0);
	if(rl->disks[drive].fd < 0)
		printf("rl%d: unloaded\n", drive);
	return 0;
}


/*
 * write protect drive
 */
static int
rlc_wlock(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RL	*rl =dev->data;
	RLU *disk;
	struct RLWIN *w;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rl, argv[0])) < 0)
		return 0;
	disk = &rl->disks[drive];
	if(disk->fd < 0) {
		disk->wl = 1;
		printf("rl%d write locked but not loaded\n", drive);
		return 0;
	}
	if(disk->wl) {
		printf("rl%d: already write locked\n", drive);
		return 0;
	}

	for(w = disk->win; w < &disk->win[NWIN]; w++)
		if(w->mm != NULL) {
			if(mprotect(w->mm, WINSIZE, PROT_READ)) {
				printf("rl%d: %s\n", drive, strerror(errno));
				return 0;
			}
		}
	disk->wl = 1;
	printf("rl%d write locked\n", drive);
	return 0;
}


/*
 * unlock write protection on drive
 */
static int
rlc_wunlock(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RL	*rl =dev->data;
	RLU *disk;
	struct RLWIN *w;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rl, argv[0])) < 0)
		return 0;
	disk = &rl->disks[drive];

	if(disk->fd < 0) {
		disk->wl = 0;
		printf("rl%d write enabled but not loaded\n", drive);
		return 0;
	}
	if(!disk->wl) {
		printf("rl%d: not write locked\n", drive);
		return 0;
	}

	if(access(disk->fn, W_OK)) {
		printf("rl%d: file is not writeable: %s\n", drive, disk->fn);
		return 0;
	}

	for(w = disk->win; w < &disk->win[NWIN]; w++)
		if(w->mm != NULL) {
			if(mprotect(w->mm, WINSIZE, PROT_READ | PROT_WRITE)) {
				printf("rl%d: %s\n", drive, strerror(errno));
				return 0;
			}
		}

	disk->wl = 0;
	printf("rl%d: write enabled\n", drive);
	return 0;
}

/*
 * open/close cover on unloaded drives
 */
static int
rlc_open(iodev_t *dev, int argc, char **argv)
{
	int	drive;
	RL	*rl =dev->data;
	RLU *disk;

	if(argc != 1 && argc != 2)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rl, argv[0])) < 0)
		return 0;
	disk = &rl->disks[drive];

	if((disk->st & 07) != Load_state) {
		printf("rl%d: loaded, can't open/close\n", drive);
		return 0;
	}
	if(argc == 1) {
		disk->op = !disk->op;
		printf("rl%d: %s\n", drive, disk->op ? "opened" : "close");
		return 0;
	} else if(strcmp(argv[1], "o") == 0) {
		if(disk->op) {
			printf("rl%d: already open\n", drive);
			return 0;
		}
		disk->op = 1;
		printf("rl%d: opened\n", drive);
		return 0;
	} else if(strcmp(argv[1], "c") == 0) {
		if(!disk->op) {
			printf("rl%d: already closed\n", drive);
			return 0;
		}
		disk->op = 0;
		printf("rl%d: closed\n", drive);
		return 0;
	}
	return 1;
}


/*
 * 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("rl: DMA request pending - try again\n");
		return 1;
	}
	return 0;
}

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

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

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