/*#define HOWFAR*/
#define INTSON		/* defined for an interrupting disk */

/*
 * Corvus Disk System
 */
#include "sys/param.h"
#include "sys/config.h"
#include "sys/mmu.h"
#include "sys/types.h"
#include "sys/sysmacros.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/utsname.h"
#include "sys/buf.h"
#include "sys/elog.h"
#include "sys/erec.h"
#include "sys/iobuf.h"
#include "sys/systm.h"
#include "sys/var.h"
#include "sys/altblk.h"
#include "sys/diskformat.h"
#include "setjmp.h"
#include "sys/cops.h"
#include "sys/pport.h"
#include "sys/d_profile.h"
#include "sys/cv.h"
#include "sys/swapsz.h"

#ifdef notdef		/* defined in d_profile.h */
#define logical(x)	(minor(x) & 7)		/* eight logicals per phys */
#define interleave(x)	(minor(x) & 0x8)	/* interleave bit for swapping */
#define physical(x)	((minor(x) & 0xF0) >> 4)/* 10 physical devs */
#endif
#define cv_addr(d)	(prodata[d].pd_da)
#define cvwait(a)	while(((a)->d_irb & ST_BUSY) == 0)

/*
 * the total space on the corvus h series is:
 * 306 cylinders are there but corvus reserves 2
 * 304 cylinders * 20 sectors per track * 6 heads =
 * 	36480
 *
 * The first 100 blocks are reserved for the boot program and
 * are inaccessible via unix.
 */
#define	MAXBOOT	100
struct cv_sizes {
	daddr_t	sz_offset;
	daddr_t	sz_size;
}	cv_sizes[] = {
	CVNSWAP+101,	32420,	/* a: root filesystem */
	101,	CVNSWAP,	/* b: swap area (3959 blocks) */
	0,	0,		/* c: unused */
	0,	0,		/* d: unused */
	0,	0,		/* e: unused */
	0,	0,		/* f: unused */
	0,	0,		/* g: unused */
	101,	1000000		/* h: filesystem using entire disk */
};

struct iostat cvstat[NPPDEVS];
struct iobuf cvtab = tabinit(CV2,cvstat);	/* active buffer header */
struct buf cvrbuf;

/*
 * cvopen - check for existence of controller
 */
cvopen(dev)
register dev;
{
	register punit;
	register struct device_d *devp;
	int cvint();
	extern char slot[];

	punit = physical(dev);
	if (punit) {		/* for expansion slot check slot number and type */
		if (!PPOK(punit) || (slot[PPSLOT(punit)] != PR0)) {
			u.u_error = ENXIO;
			return 1;
		}
	}
	devp = pro_da[punit];
	u.u_error = 0;

	if (iocheck(&devp->d_ifr)) {	/* board there ? */
		if (cv_addr(punit) != devp) {	/* not already setup */
			if (setppint((cv_addr(punit) = devp),cvint))
				goto fail;
			if (cvinit(&prodata[punit])) {
				freeppin(devp);
				goto fail;
			}
		}
	} else {
	fail:
		u.u_error = ENXIO;
		cv_addr(punit) = (struct device_d *)0;
		return 1;
	}
	return 0;
}

/*
 * cvinit - initialize drive first time
 */
cvinit(p)
register struct prodata *p;
{
	register struct device_d *devp = p->pd_da;
	register char irb;
	register char zero = 0;
	int pl;

	pl = spl6();
	if (devp == PPADDR) {
		devp->d_ddrb &= 0x5C;	/* port B bits: 0,1,5,7 to in, 2,3,4,6 to out */
		devp->d_pcr = 0x6B;	/* set controller CA2 pulse mode strobe */
		devp->d_ddra = zero;	/* set port A bits to input **/
		devp->d_irb |= CMD|DRW;	/* set command = false  set direction = in */
		devp->d_ddrb |= 0x7C;
		devp->d_irb &= ~DEN;	/* set enable = true */
	} else {
		devp->d_pcr = 0x6B;	/* set controller CA2 pulse mode strobe */
		devp->d_ddra = zero;	/* set port A bits to input **/
		devp->d_irb = CMD|DRW;	/* set command = false  set direction = in */
		devp->d_ddrb = 0x7C;
	}
#ifdef	INTSON
	devp->d_ier = FIRQ|FCA1;
	irb = devp->d_irb;
#ifdef lint
	pl = irb;
#endif lint
	p->pd_state = SCMD;
#endif	INTSON
	splx(pl);
	return 0;
}

cvstrategy(bp)
register struct buf *bp;
{
	register punit, lunit, bn;

	punit = physical(bp->b_dev);
	lunit = logical(bp->b_dev);
	bn = bp->b_blkno + cv_sizes[lunit].sz_offset;
	if (bp->b_blkno < 0 || bn <= MAXBOOT) {
#ifdef HOWFAR
		prdev("cvstrategy: illegal blkno", bp->b_dev);
		printf("blkno=%d bcount=%d\n", bp->b_blkno, bp->b_bcount);
#endif HOWFAR
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	cvstat[punit].io_ops++;
#ifdef INTSON
	bp->b_resid = bn;	/* resid for disksort */
	SPL6();
	disksort(&cvtab, bp);
#else INTSON
	bp->av_forw = (struct buf *)NULL; /* last of all bufs */
	if (cvtab.b_actf == NULL)
		cvtab.b_actf = bp;	/* empty - put on front */
	else
		cvtab.b_actl->av_forw = bp; /* else put at end */
	cvtab.b_actl = bp;
#endif INTSON
	if (cvtab.b_active == 0)
		cvstart();
#ifdef INTSON
	SPL0();
#else INTSON
	while (cvtab.b_active)
		cvint();
#endif INTSON
	return;
}

cvstart()
{
	register struct buf *bp;
	register lunit, offset, bn;
	register struct device_d *addr;

loop:
	if ((bp = cvtab.b_actf) == (struct buf *)NULL)
		return;
	if (cvtab.b_active == 0) {
		bp->b_resid = bp->b_bcount;
		cvtab.b_active = 1;
	}
	lunit = logical(bp->b_dev);
	blkacty |= (1<<CV2);
	offset = bp->b_bcount - bp->b_resid;
	bn = bp->b_blkno + btod(offset);	/* logical block number */
	if (bp->b_resid < BSIZE || bn >= cv_sizes[lunit].sz_size) {
	next:
#ifdef HOWFAR
		if (bp->b_resid != 0)
			printf("Unix cvstart: blkno=%d resid=%d bn=%d\n",
				bp->b_blkno, bp->b_resid, bn);
#endif HOWFAR
		blkacty &= ~(1<<CV2);
		cvtab.b_active = 0;
		if (cvtab.b_errcnt) {
			logberr(&cvtab, 0); /* errlog non-fatal errors */
			cvtab.b_errcnt = 0;
		}
		addr = cv_addr(physical(bp->b_dev));
		addr->d_ifr = addr->d_ifr;		/* reset intr */
		cvtab.b_actf = bp->av_forw;
		iodone(bp);
		goto loop;
	}
	if (cvrw(minor(bp->b_dev), bn + cv_sizes[lunit].sz_offset, BSIZE,
			bp->b_flags&B_READ, bp->b_un.b_addr + offset) < 0) {
		bp->b_flags |= B_ERROR;
		logberr(&cvtab, 1);	/* log fatal error */
		goto next;
	}
	return;
}

caddr_t cv_buf;
int cv_count;

cvrw(dev, blkno, n, flag, buff)
register dev;
register daddr_t blkno;
register n, flag;
register caddr_t buff;
{
	register punit;
	register struct device_d *addr;

	punit=physical(dev);
	cv_buf = buff;
	cv_count = n;
	if (cvop(punit, 4, flag==B_READ ? N_R512 : N_W512, (blkno>>16 & 0xf)+1,
		blkno & 0xff, blkno>>8 & 0xff) < 0)
		goto bad;

	if (flag == B_WRITE && cvw(punit, buff, n) < 0)
		goto bad;
	addr = cv_addr(punit);
	addr->d_ddra = 0x00;	/* data direction port A bits to input */
	addr->d_irb |= 0x08;	/* bidirectional driver to input */
	return 0;
bad:
#ifdef HOWFAR
	printf("cvrw: %s error unit=%d blkno=%d n=%d buf=0x%x\n",
		flag==B_READ?"read":"write", punit, blkno, n, buff);
#endif HOWFAR
	return -1;
}

/* VARARGS3 */
cvop(unit, na, a)
{
#ifdef INTSON
	register s;
#endif INTSON
	register int *ap;
	register struct device_d *addr = cv_addr(unit);

	addr->d_ddra = 0xff;	/* port A to output */
	addr->d_irb &= ~0x08;	/* bidirectional driver to output */

	ap = &a;
	if (na-- > 0) {
#ifdef INTSON
		s = spl7();
#endif INTSON
		cvwait(addr);
		addr->d_ira = *ap++;
#ifdef INTSON
		splx(s);
#endif INTSON
	}
	for (; na > 0; na--, ap++) {
		cvwait(addr);
		addr->d_ira = *ap;
	}
	return 0;
}

cvint(punit)
int punit;
{
	register struct device_d *addr;
	register struct buf *bp;
	register char status;

	(void) spl6();
	addr = cv_addr(punit);
	if (cvtab.b_active == 0) {
#ifdef HOWFAR
		printf("cvint: b_active == 0\n");
#endif HOWFAR
		if (addr == PPADDR)
			addr->d_ifr = addr->d_ifr;		/* reset intr */
		return;
	}
	if ((bp = cvtab.b_actf) == (struct buf *)NULL) {
#ifdef HOWFAR
		printf("cvint: b_actf == NULL\n");
#endif HOWFAR
		if (addr == PPADDR)
			addr->d_ifr = addr->d_ifr;		/* reset intr */
		return;
	}
	cvwait(addr);
	if (addr == PPADDR)
		addr->d_ifr = addr->d_ifr;		/* reset intr */
	if (status = addr->d_ira) {
	err:
		printf("%s error: /dev/c%d%c blkno=%d\n",
			bp->b_flags&B_READ?"read":"write", punit,
			'a'+logical(bp->b_dev), bp->b_blkno);
		cvlog(status);
		cv_count = 0;
		if (++cvtab.b_errcnt > NRETRY) {
			bp->b_flags |= B_ERROR;
			logberr(&cvtab, 1);	/* log fatal error */
			blkacty &= ~(1<<CV2);
			cvtab.b_errcnt = 0;
			cvtab.b_actf = bp->av_forw;
			cvtab.b_active = 0;
			iodone(bp);
		}
	} else if (bp->b_flags&B_READ)
		if (cvr(punit, (char *)cv_buf, cv_count) < 0)
			goto err;
	/*
	 * because a single buffer can take several io operations, 
	 * we leave it to cvstart() to figure out when it's done
	 */
	bp->b_resid -= cv_count;
	cvstart();
}

cvlog(status)
register status;
{
	register struct buf *bp;
	register struct device_d *addr;
	register bn, punit, lunit;
	struct deverreg cvreg[2];

	bp = cvtab.b_actf;
	punit = physical(bp->b_dev);
	lunit = logical(bp->b_dev);
	cvtab.io_stp = &cvstat[lunit];
	addr = cv_addr(punit);
	cvreg[0].draddr = (long)&(addr->d_ira);
	cvreg[0].drvalue = status;
	cvreg[0].drname = "cv status";
	cvreg[0].drbits = "Corvus disk status code";
	cvreg[1].draddr = (long)0;
	cvreg[1].drvalue = cv_count;
	cvreg[1].drname = "count";
	cvreg[1].drbits = "byte count of transfer";
	bn = bp->b_blkno + btod(bp->b_bcount - bp->b_resid) + cv_sizes[lunit].sz_offset;
	fmtberr(&cvtab,
		(unsigned)punit,
		(unsigned)0,		/* cylinder */
		(unsigned)0,		/* track */
		(unsigned)bn,		/* sector */
		(long)(sizeof(cvreg)/sizeof(cvreg[0])), /* regcnt */
		&cvreg[0],&cvreg[1]);
}

cvw(unit, buff, n)
register char *buff;
register n;
{
	register char *ira;
	register struct device_d *addr = cv_addr(unit);

	ira = &(addr->d_ira);
	cvwait(addr);
	for ( ; n > 0; n--) {
		if ((addr->d_irb & ST_HTOC) == 0)
			break;
		*ira = *buff++;
	}
	for (;;) {
		cvwait(addr);
		if ((addr->d_irb & ST_HTOC) == 0)
			break;
		*ira = 0;
	}
	cv_count -= n;
	if (n > 0) {
#ifdef HOWFAR
		printf("cvw: %d bytes short\n", n);
#endif HOWFAR
		cvlog(0);
		return -1;
	}
	return 0;
}

cvr(unit, buff, n)
register char *buff;
register n;
{
	register char *ira;
	register struct device_d *addr = cv_addr(unit);

	cvwait(addr);
	ira = &(addr->d_ira);
	for ( ; n > 0; n--) {
		if (addr->d_irb & ST_HTOC)
			break;
		*buff++ = *ira;
	}
	cv_count -= n;
	if (n > 0) {
#ifdef HOWFAR
		printf("cvr: %d bytes short\n", n);
#endif HOWFAR
		return -1;
	}
	for (;;) {
		cvwait(addr);
		if (addr->d_irb & ST_HTOC)
			break;
		n = *ira;
	}
	return 0;
}

/********
#ifdef	INTSON
#else	INTSON
/* wait for controller to host direction or timeout */ /*
cvctoh(a)
register struct device_d *a;
{
	register i;

	for (i = 20; i-- > 0;);
	i = 100000;
	do
		while (--i > 0 && ((a->d_irb&ST_BUSY) == 0));
	while (i > 0 && (a->d_irb & ST_HTOC));
	if (i <= 0) {
		printf("cvctoh: timeout\n");
		return -1;
	}
	return 0;
}
#endif	INTSON
********/

cvread(dev)
dev_t dev;
{
	physio(cvstrategy, &cvrbuf, dev, B_READ);
}

cvwrite(dev)
dev_t dev;
{
	physio(cvstrategy, &cvrbuf, dev, B_WRITE);
}

cvprint(dev, str)
char *str;
{
	printf("%s on cv drive %d, slice %d\n", str, (dev>>4)&0xF, dev&7);
}
