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

/*
 * RK 05.
 *
 * Arguments:
 *	ctrl csr_base vector irq sync
 *	  disk-no file
 *	end
 *
 * sync is the interval between msync()s in milli-seconds.
 */

# include "proc.h"

RCSID("$Id: dev_rk.c 454 2000-11-24 10:34:24Z hbb $")

# define D(X) /* X */
/*
# define STOP() proc.bp = proc.reg[7]
*/
# define STOP()

# define RKS	8			/* discs per controller */
# define RKSIZE	(203 * 2 * 12 * 512)	/* RK05 in bytes */

/*
 * drive status bits
 */
enum {
	RKDS_ID		= 0160000,	/* which driver interrupted */
	RKDS_DPL	= 0010000,	/* drive power low */
	RKDS_RK05	= 0004000,	/* always set */
	RKDS_DRU	= 0002000,	/* drive unsafe */
	RKDS_SIN	= 0001000,	/* seek incomplete */
	RKDS_SOK	= 0000400,	/* sector couter ok */
	RKDS_DRY	= 0000200,	/* drive ready */
	RKDS_RDY	= 0000100,	/* R/W/S ready */
	RKDS_WPS	= 0000040,	/* write protect status */
	RKDS_MATCH	= 0000020,	/* SA == SC */
	RKDS_SC		= 0000017,	/* sector counter */
};
enum {
	RKER_DE		= 0100000,	/* drive error */
	RKER_OVR	= 0040000,	/* disk overrun */
	RKER_WLO	= 0020000,	/* write lock */
	RKER_SKE	= 0010000,	/* seek error */
	RKER_PGM	= 0004000,	/* format not for READ/WRITE */
	RKER_NXM	= 0002000,	/* non existent memory */
	RKER_DLT	= 0001000,	/* data late */
	RKER_TE		= 0000400,	/* timing error */
	RKER_NXD	= 0000200,	/* non existant disk */
	RKER_NXC	= 0000100,	/* non existant cylinder */
	RKER_NXS	= 0000040,	/* non existant sector */
	RKER_CSE	= 0000002,	/* checksum error */
	RKER_WCE	= 0000001,	/* write check error */

	RKER_HARD	= 0177740,	/* hard errors */
	RKER_SOFT	= 0000003,	/* soft errors */
};
enum {
	RKCS_ER		= 0100000,	/* error */
	RKCS_HE		= 0040000,	/* hard error */
	RKCS_SCP	= 0020000,	/* search complete */
	RKCS_II		= 0004000,	/* increment inhibit */
	RKCS_FMT	= 0002000,	/* format */
	RKCS_SSE 	= 0000400,	/* stop on soft error */
	RKCS_RDY	= 0000200,	/* controller ready */
	RKCS_IDE	= 0000100,	/* interrupt enable */
	RKCS_BAE	= 0000060,	/* bus address extension */
	RKCS_FUNC	= 0000016,	/* function */
	RKCS_GO		= 0000001,	/* go */
};

typedef struct RK RK;

struct RK {
	u_int	csr_base;	/* CSR base address */
	u_short	vector;		/* interrupt vector */
	u_short	irq_level;	/* interrupt level */
	int	sync_rate;	/* msync interval */

	u_int	ba;		/* bus address */
	u_short da;		/* disk address drive select */
	u_short wc;		/* word count */
	u_short er;		/* error register */
	u_int	inter;		/* drive interrupts */
	u_short csr;		/* CSR: II, FMT, RDY, IDE */
	u_short func;		/* current function */
	int	last_int;

	char	*fn[RKS];	/* file names */
	char	*mm[RKS];	/* mapped files */
	u_int	tr[RKS];	/* current track */
	u_int	sc[RKS];	/* current sector */
	u_int	wl[RKS];	/* software write lock */
	u_short ds[RKS];	/* drive status */
};

void	rk_ctrl_create(iodev_t *, int, char **);
void	rk_dev_create(iodev_t *, int, char **);
void	rk_ctrl_complete(iodev_t *);

void	rk_reset(iodev_t *);
u_short	rk_fetch(iodev_t *, u_int);
void	rk_store(iodev_t *, u_int, mmode_t, u_short);
u_short	rk_vector(iodev_t *);
void	rk_dma(iodev_t *);
void	rk_info(iodev_t *);
void	rk_flush(iodev_t *);

static int rkc_info(iodev_t *, int, char **);
static int rkc_load(iodev_t *, int, char **);
static int rkc_unload(iodev_t *, int, char **);
static int rkc_wlock(iodev_t *, int, char **);
static int rkc_wunlock(iodev_t *, int, char **);

iocmd_t rk_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			rkc_info },
	{ "load",	"drive-no file",	rkc_load },
	{ "unload",	"drive-no",		rkc_unload },
	{ "wlock",	"drive-no",		rkc_wlock },
	{ "wunlock",	"drive-no",		rkc_wunlock },
	{ NULL,		NULL,			NULL }
};

ioops_t rk_ops = {
	rk_ctrl_create,		/* ctrl_create */
	rk_dev_create,		/* dev_create */
	rk_ctrl_complete,	/* ctrl_complete */
	rk_reset,		/* reset */
	rk_fetch,		/* fetch */
	rk_store,		/* store */
	rk_vector,		/* vector */
	rk_dma,			/* dma */
	0,			/* async */
	rk_info,		/* info */
	rk_flush,		/* flush */
	rk_cmds,		/* cmds */
};

enum {
	RK_DS	= 0,		/* drive status */
	RK_ER	= 2,		/* error */
	RK_CSR	= 4,		/* CSR */
	RK_WC	= 6,		/* word count */
	RK_BA	= 010,		/* bus address */
	RK_DA	= 012,		/* disk address */
	RK_DB	= 014,		/* data buffer */
	RK_OOPS	= 016,
};

static void rksync(void *);
static void load(RK *, int, char *, int);
static void unload(RK *, int);
static void dofunc(iodev_t *);

void
rk_ctrl_create(iodev_t *dev, int argc, char **argv)
{
	RK *d;
	int i;

	if(argc != 4)
		conf_panic("rk: bad number of args to controller config");

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

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

	proc.iopage[IOP(d->csr_base + RK_DS )] = dev;
	proc.iopage[IOP(d->csr_base + RK_ER )] = dev;
	proc.iopage[IOP(d->csr_base + RK_CSR)] = dev;
	proc.iopage[IOP(d->csr_base + RK_WC )] = dev;
	proc.iopage[IOP(d->csr_base + RK_BA )] = dev;
	proc.iopage[IOP(d->csr_base + RK_DA )] = dev;
	proc.iopage[IOP(d->csr_base + RK_DB )] = dev;
	proc.iopage[IOP(d->csr_base + RK_OOPS)] = dev;

	d->ba = 0;
	d->da = 0;
	d->wc = 0;
	d->er = 0;

	for(i = 0; i < RKS; i++) {
		d->fn[i] = NULL;
		d->mm[i] = NULL;
		d->tr[i] = 0;
		d->sc[i] = 0;
		d->ds[i] = RKDS_RK05|RKDS_SOK;
		d->wl[i] = 0;
	}
}

void
rk_dev_create(iodev_t *dev, int argc, char **argv)
{
	RK *d = (RK *)dev->data;
	int i;

	if(argc != 2)
		conf_panic("rk: bad number of args to device description");

	i = (int)strtol(argv[0], 0, 0);
	if(i >= RKS)
		conf_panic("rk: supports only up to %d disks", RKS);

	if(d->mm[i])
		unload(d, i);
	load(d, i, argv[1], 1);
}

void
rk_ctrl_complete(iodev_t *dev)
{
	RK *d = (RK *)dev->data;

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

/*
 * Load file fn as disk i.
 */
static void
load(RK *d, int i, char *fn, int isconf)
{
	typedef void (*pfunc)(char *, ...);
	struct stat statb;
	int fd, fl;
	pfunc ef = isconf ? (pfunc)conf_panic : (pfunc)printf;

	fl = O_RDWR | O_CREAT;
	if(access(fn, W_OK) != 0) {
		d->ds[i] |= RKDS_WPS;
		fl = O_RDONLY;
	}
	if((fd = open(fn, fl, 0666)) < 0) {
		(*ef)("rk%d: can't open %s: %s", i, fn, strerror(errno));
		return;
	}
	if(fstat(fd, &statb)) {
		(*ef)("rk%d: can't stat %s: %s", i, fn, strerror(errno));
		(void)close(fd);
		return;
	}
	if(statb.st_size < RKSIZE) {
		if(d->ds[i] & RKDS_WPS) {
			(*ef)("rk%d: can't expand %s to required size", i, fn);
			(void)close(fd);
			return;
		}
		lseek(fd, RKSIZE - 1, 0);
		write(fd, "\0", 1);
	} else if(statb.st_size > RKSIZE) {
		if(d->ds[i] & RKDS_WPS) {
			(*ef)("rk%d: can't truncate %s to required size", i,fn);
			(void)close(fd);
			return;
		}
		ftruncate(fd, RKSIZE);
	}

	d->mm[i] = mmap(0, RKSIZE,
		PROT_READ | ((d->ds[i] & RKDS_WPS) ? 0 : PROT_WRITE),
		MAP_FILE | MAP_SHARED, fd, 0);
	if((long)d->mm[i] == -1) {
		d->mm[i] = NULL;
		(*ef)("rk%d: can't mmap %s: %s", i, fn, strerror(errno));
		(void)close(fd);
		return;
	}

	(void)close(fd);

	d->fn[i] = xalloc((strlen(fn) + 1) * sizeof(char));
	(void)strcpy(d->fn[i], fn);

	d->sc[i] = 0;
	d->tr[i] = 0;
	d->ds[i] |= RKDS_DRY|RKDS_RDY;
	d->wl[i] = 0;
}

static void
unload(RK *d, int i)
{
	if(!d->mm[i])
		return;
	free(d->fn[i]);
	d->fn[i] = NULL;
	munmap(d->mm[i], RKSIZE);
	d->mm[i] = NULL;

	d->sc[i] = 0;
	d->tr[i] = 0;
	d->ds[i] = RKDS_RK05|RKDS_SOK;
	d->wl[i] = 0;
}


void
rk_reset(iodev_t *dev)
{
	RK *d = dev->data;

	d->er = 0;
	d->ba = 0;
	d->da = 0;
	d->wc = 0;
	d->inter = 0;
	d->csr = RKCS_RDY;
	d->func = 0;
	dev->reqpri = 0;
}

u_short
rk_fetch(iodev_t *dev, u_int a)
{
	RK *d = dev->data;
	u_short v = 0;

	D(printf("rk_fetch(%06o)\n", a));

	switch(a - d->csr_base) {

	  case RK_DS:
		v = d->ds[d->da >> 13];
		if(d->csr & RKCS_SCP)
			v |= (d->da >> 13) << 13;
		break;

	  case RK_ER:
		v = d->er;
		break;

	  case RK_CSR:
		v = d->csr | d->func
			| ((d->er != 0) ? RKCS_ER : 0)
			| ((d->er & RKER_HARD) ? RKCS_HE : 0);
		break;

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

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

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

	  case RK_DB:
		v = 0;
		break;

	  case RK_OOPS:
		printf("rk_fetch(OOPS)\n");
		break;

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

	return v;
}

void
rk_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	RK *d = (RK *)dev->data;

	D(printf("rk_store(%06o,%06o,%d)\n", a, v, mode));

	switch(a - d->csr_base) {

	  case RK_DS:		/* read only */
		break;

	  case RK_ER:		/* read only */
		break;

	  case RK_CSR:
		if(mode == M_Word || mode == M_High)
			d->csr = (d->csr & 0377) | (v & (RKCS_II|RKCS_FMT));
		if(mode == M_Word || mode == M_Low) {
			d->ba = (d->ba & ~0600000) | ((v & RKCS_BAE) << 12);
			d->func = v & 016;
			if(!(d->csr & RKCS_IDE) &&
			   (v & (RKCS_IDE|RKCS_GO|RKCS_RDY)) ==
					(RKCS_IDE|RKCS_RDY)) {
				/* no go, but IE toggled */
				d->csr |= RKCS_IDE|RKCS_RDY;
				IRQ(dev, d->irq_level);
				break;
			}
			d->csr = (d->csr & ~RKCS_IDE) | (v & RKCS_IDE);
			if(!(d->csr & RKCS_IDE)) {
				dev->reqpri = 0;
				d->inter = 0;
			}

			if(!(v & RKCS_GO)) {
				d->csr |= RKCS_RDY;
				break;
			}

			d->csr &= ~RKCS_RDY;
			dofunc(dev);
			if((d->csr & RKCS_RDY) && (d->csr & RKCS_IDE))
				IRQ(dev, d->irq_level);
		}
		break;

	  case RK_WC:
		if(!(mode & M_High))
			d->wc = (d->wc & ~0377) | (v & 0377);
		if(!(mode & M_Low))
			d->wc = (d->wc & ~0177400) | (v & 0177400);
		break;

	  case RK_BA:		/* 18 bit */
		v &= ~1;
		switch(mode) {

		  case M_Low:
			d->ba = (d->ba & ~0377) | (v & 0377);
			break;

		  case M_High:
			d->ba = (d->ba & ~0177400) | (v & 0177400);
			break;

		  case M_Word:
			d->ba = (d->ba & ~0177777) | (v & 0177777);
			break;
		}
		break;

	  case RK_DA:
		SETWORD(d->da, mode, v);
		break;

	  case RK_DB:
		break;

	  case RK_OOPS:
		printf("rk_store(OOPS)\n");
		break;

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

static void
dofunc(iodev_t *dev)
{
	RK *d = dev->data;
	u_int track, sect, surf, unit;

	if(d->func == 0) {	/* CONTROL RESET */
		/*
		 * Reset all controller state.
		 */
		rk_reset(dev);
		return;
	}

	d->er &= ~RKER_SOFT;
	if(d->er) {
		d->csr |= RKCS_RDY;
		d->inter |= 1;
		return;
	}
	unit = (d->da >> 13) & 7;
	if(d->mm[unit] == NULL) {
		d->er |= RKER_NXD;
		d->csr |= RKCS_RDY;
		d->inter |= 1;
		return;
	}

	track = (d->da >> 5) & 0377;
	sect = d->da & 017;
	surf = d->da & 020;

	switch(d->func) {

	  case 2:		/* WRITE */
		if(d->wl[unit] || (d->ds[unit] & RKDS_WPS)) {
			d->er |= RKER_WLO;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		if(track >= 203) {
			d->er |= RKER_NXC;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		if(sect >= 12) {
			d->er |= RKER_NXS;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		IRQ(dev, DMAPRI);
		break;
	  case 4:		/* READ */
		if(track >= 203) {
			d->er |= RKER_NXC;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		if(sect >= 12) {
			d->er |= RKER_NXS;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		IRQ(dev, DMAPRI);
		break;
	  case 6:		/* WRITE CHECK */
		break;
	  case 010:		/* SEEK */
		if(track >= 203) {
			d->er |= RKER_NXC;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		if(sect >= 12) {
			d->er |= RKER_NXS;
			d->csr |= RKCS_RDY;
			d->inter |= 1;
			return;
		}
		d->sc[unit] = sect;
		d->tr[unit] = track;
		d->csr |= RKCS_RDY;
		d->inter |= (2 << unit) | 1;
		break;

	  case 012:		/* READ CHECK */
		break;

	  case 014:		/* DRIVE RESET */
		d->wl[unit] = 0;	/* ??? */
		d->sc[unit] = 0;
		d->tr[unit] = 0;
		d->csr |= RKCS_RDY;
		d->inter |= (2 << unit) | 1;
		break;

	  case 016:		/* WRITE LOCK */
		d->wl[unit] = 1;
		d->csr |= RKCS_RDY;
		d->inter |= 1;
		break;

	  default:
		panic("bad func");
	}
}

/*
 * Interrupt ack. Return the vector and delete the highest priority
 * interrupt. If there are still interrupts pending, request a new one.
 */
u_short
rk_vector(iodev_t *dev)
{
	RK *d = (RK *)dev->data;
	u_int i;

	D(printf("rk_vector()\n"));

	dev->reqpri = 0;
	if(d->inter & 1) {
		d->inter &= ~1;
		d->csr &= ~RKCS_SCP;
		d->last_int = 0;
	} else {
		for(i = 0; i < RKS; i++)
			if(d->inter & (2 << i)) {
				d->inter &= ~(2 << i);
				break;
			}
		d->csr |= RKCS_SCP;
		d->last_int = i;
	}

	if(d->inter)
		IRQ(dev, d->irq_level);

	return d->vector;
}

void
rk_dma(iodev_t *dev)
{
	RK *d = dev->data;
	u_int bytes, off, abytes;
	u_int track, sect, unit, surf;
	u_int err;

	D(printf("rk_dma()\n"));

	/*
	 * set ready and request interrupt
	 */
	d->csr |= RKCS_RDY;
	d->inter |= 1;
	dev->reqpri = 0;
	if(d->csr & RKCS_IDE)
		IRQ(dev, d->irq_level);

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

	unit = (d->da >> 13) & 7;
	track = (d->da >> 5) & 0377;
	sect = d->da & 017;
	surf = ((d->da & 020) != 0);

	off = 512 * ((2 * track + surf) * 12 + sect);

	d->sc[unit] = sect;
	d->tr[unit] = track;

	/*
	 * Check whether the transfer hits the end of the disk
	 */
	err = 0;
	if(off + bytes > RKSIZE) {
		err = RKER_OVR;
		bytes = RKSIZE - off;
	}

	switch(d->func) {

	  case 2:	/* WRITE */
		abytes = dma(d->mm[unit] + off, bytes, d->ba, ReadMem, NULL);
		D(fprintf(stderr, "RK: u=%u a=%u/%u/%u <- ba=%u bytes=%u/%u\n",
			unit, track, surf, sect, d->ba, bytes, abytes));
		STOP();
		break;
	  case 4:	/* READ */
		abytes = dma(d->mm[unit] + off, bytes, d->ba, WriteMem, NULL);
		D(fprintf(stderr, "RK: u=%u a=%u/%u/%u -> ba=%u bytes=%u/%u\n",
			unit, track, surf, sect, d->ba, bytes, abytes));
		STOP();
		break;
	  default:
		panic("bad switch: %s, %d", __FILE__, __LINE__);
	}

	if(abytes < bytes)
		d->er |= RKER_NXM;
	else
		d->er |= err;
	d->wc += abytes >> 1;
	d->ba += abytes;
}

void
rk_info(iodev_t *dev)
{
	RK *d = dev->data;
	int i;

	printf("RK11 Controller with RK05 disks\n");
	printf("CSR at %08o, vector %03o at level %d, %d RK05 disks\n",
		d->csr_base, d->vector, d->irq_level, RKS);
	printf("UNIT\tTR\tSC\tWL\tDS\tFile\n");
	for(i = 0; i < RKS; i++)
		printf("%d\t%u\t%u\t%d\t%06o\t%s\n", i,
			d->tr[i], d->sc[i], d->wl[i],
			d->ds[i], d->fn[i] ? d->fn[i] : "");
}

static void
rksync(void *v)
{
	RK *d = v;
	int i;

	for(i = 0; i < RKS; i++)
		if(d->mm[i])
# ifdef HAVE_MS_SYNC
			msync(d->mm[i], RKSIZE, MS_SYNC);
# else
			msync(d->mm[i], RKSIZE);
# endif
}

void
rk_flush(iodev_t *dev)
{
	rksync(dev->data);
}


/*
 * Command interface
 */
static int chkdma(iodev_t *);
static int get_drive(RK *, char *);

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

static int
rkc_load(iodev_t *dev, int argc, char **argv)
{
	int drive;
	RK *rk = dev->data;

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

static int
rkc_unload(iodev_t *dev, int argc, char **argv)
{
	int drive;
	RK *rk = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rk, argv[0])) < 0)
		return 0;
	if(!rk->mm[drive]) {
		printf("rk%d: not loaded\n", drive);
		return 0;
	}
	unload(rk, drive);
	if(!rk->mm[drive])
		printf("rk%d: unloaded\n", drive);
	return 0;
}

static int
rkc_wlock(iodev_t *dev, int argc, char **argv)
{
	int drive;
	RK *rk = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rk, argv[0])) < 0)
		return 0;
	if(!rk->mm[drive]) {
		printf("rk%d: not loaded\n", drive);
		return 0;
	}
	if(rk->ds[drive] & RKDS_WPS) {
		printf("rk%d: already write locked\n", drive);
		return 0;
	}

	if(mprotect(rk->mm[drive], RKSIZE, PROT_READ)) {
		printf("rk%d: %s\n", drive, strerror(errno));
		return 0;
	}
	rk->ds[drive] |= RKDS_WPS;
	printf("rk%d: write locked\n", drive);
	return 0;
}

static int
rkc_wunlock(iodev_t *dev, int argc, char **argv)
{
	int drive;
	RK *rk = dev->data;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((drive = get_drive(rk, argv[0])) < 0)
		return 0;
	if(!rk->mm[drive]) {
		printf("rk%d: not loaded\n", drive);
		return 0;
	}
	if(!(rk->ds[drive] & RKDS_WPS)) {
		printf("rk%d: not write locked\n", drive);
		return 0;
	}

	if(access(rk->fn[drive], W_OK)) {
		printf("rk%d: file not writeable: %s", drive, rk->fn[drive]);
		return 0;
	}
	if(mprotect(rk->mm[drive], RKSIZE, PROT_READ|PROT_WRITE)) {
		printf("rk%d: %s\n", drive, strerror(errno));
		return 0;
	}
	rk->ds[drive] &= ~RKDS_WPS;
	printf("rk%d: write enabled\n", drive);
	return 0;
}

/*
 * Disable unloading while DMA is in request.
 */
static int
chkdma(iodev_t *dev)
{
	if(dev->reqpri == DMAPRI) {
		printf("rk: DMA request pending - try again\n");
		return 1;
	}
	return 0;
}

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

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

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