static char rcsid[] = "$Header: cv.c,v 800.0 85/08/06 14:08:05 root Exp $";
static char sccsid[] = "%W% %Y% %Q% %G%";


#define SCCSID(s) /* s */
SCCSID(%W% %G% %U% %Y%)

/*
 * Corvus Disk System
 *
 * $Log:	cv.c,v $
 * Revision 800.0  85/08/06  14:08:05  root
 * Bumped for Release 800.00
 * 
 * Revision 8.0  85/07/17  12:58:45  root
 * Initial checkin (sas).
 * 
 * Revision 7.28  85/04/12  10:13:36  sbs
 * *** empty log message ***
 * 
 * Revision 7.27  85/02/27  22:39:55  sbs
 * Made two mods to cv_sizes.
 * 1. Cut the extent of "full disk" partitions (viz., 0, 1, and a) by
 *    590 sectors, this to avoid the controller's spare tracks area.
 * 2. Made partition 0 == partition 1, so that typical usages of #1 get
 *    the whole disk.
 * 
 * Revision 7.26  85/02/22  14:53:20  sbs
 * Rewrote ioctl to use cv scheme (instead of ddiag scheme).
 * 
 */

#include "../h/param.h"			/* system types and constants */
#include "../h/inode.h"			/* inode structure */
#include "../h/buf.h"			/* struct buf and flags */
#include "../h/systm.h"			/* system-wide variables */
#include "../h/dir.h"			/* directories */
#include "../h/file.h"			/* files */
#include "../h/user.h"			/* struct user and flags */
#include "../h/uio.h"			/* struct uio */
#include "../h/ioctl.h"			/* ioctl interface */
#include "../s32/cpu.h"			/* XXX */
#include "../white/cvreg.h"		/* device (CV) definitions */

/* BUG this is just a kludge */
#define btod(x)		(((x)+511)>>9)

typedef short cnt_t;

struct	buf	cvtab[NCV],
		cvrbuf[NCV],
		cvmbuf;
static	errflag[NCV],
	cvbusy[NCV];

/* starting block and size in blocks for all disk partitions */

/*
 * starting sector and size in sectors for all partitions.
 * Use the "Valid" scheme a la the rimfire drivers for compatibility.
 *
 * The Corvus partition scheme is more UNIX-like, having 8 partitions
 * organized as
 *	0	50	a: root
 *	50	2966	b: swap
 *	3016	21304	c: root
 *	24320	12160	d: user
 *	0	1000000	h: full disk
 */
struct cv_sizes cv_sizes[] = {
	0,	35840,				/* 0: full disk */
	0,	35840,				/* 1: full disk */
	0,	0,				/* 2: unused */
	0,	0,				/* 3: unused */
	0,	0,				/* 4: unused */
	0,	50,				/* 5: boot file */
	50,	7000,				/* 6: root */
	0,	0,				/* 7: unused */
	0,	0,				/* 8: unused */
	7050,	8000,				/* 9: swap */
	15050,	20790,				/* a: user portion */
	0,	0,				/* b: unused */
	0,	0,				/* c: unused */
	0,	0,				/* d: unused */
	0,	0,				/* e: unused */
	0,	0,				/* f: unused */
};

int	cv_vpage[NCV];	/* Virtual page that we can map into */
#ifdef SAS_PAGE
short saspage = 1;	/* debugging flag for page 0 access */
#endif SAS_PAGE

char *cv_errlist[] = {
	"header fault",
	"seek timeout",
	"seek fault",
	"seek error",
	"header CRC error",
	"rezero fault",
	"rezero timeout",
	"drive not online",
	"write fault",
	"???",
	"read data fault",
	"data CRC error",
	"sector locate error",
	"write protected",
	"illegal sector address",
	"illegal command op code",
	"drive not acknowledged",
	"acknowledge stuck active",
	"timeout",
	"fault",
	"CRC",
	"seek",
	"verification",
	"drive speed error",
	"drive illegal address error",
	"drive r/w fault error",
	"drive servo error",
	"drive quard band",
	"drive PLO error",
	"drive r/w unsafe",
	"???",
	"???",
};


/*
 * At initialization time we allocate some
 * virtual memory in which to map any pages
 * we are transferring.
 */
cvinit()
{
	register int i;

	for (i = 0; i < NCV; i++)
		cv_vpage[i] = kvalloc(2);
}

/*
 * Return the size of a logical disk in blocks.
 */
cvnblk(dev)
	dev_t dev;
{
	register i = minor(dev);
	register unit = cv_unit(i);

	if (unit < 0 || NCV <= unit)
		return(0);
	return(cv_sizes[cv_part(i)].sz_size);
}


/*
 * Receive a packet directly from the disk.
 * This routine is only called when we do an ioctl and we need to
 * look at the return information.  Called from cvint().
 */
unsigned char
cvrcv(unit, bp)
	register struct buf *bp;
{
	register cnt;
	register char *cp;
	char code;

	cnt = bp->b_rcvem;
	code = bp->b_code;
	cp = (char *)bp->b_un.b_addr;
	/*
	 * Map the virtual pages to our buffer so that when we
	 * copy in, we're really copying in.
	 */
	setpagemap(cv_vpage[unit], ((int)cp) >> pageshift);
	setpagemap(cv_vpage[unit] + 1, (((int)cp) >> pageshift) + 1);

	if (cnt)
		cvr(unit, cp, cnt, cnt);
	cp += cnt - 1;
	cnt = *cp++;	/* byte count of errors is here */
	switch (code) {
	case M_RPTVFY1:
	case M_RSTRPT1:
		cnt <<= 1;
		break;
	case D_VERIFY:
		cvr(unit, cp, cnt, cnt);
		bp->b_rcvem += cnt;	/* add in the variable len */
		break;
	}
	return 0;	/* no errors to report for now */
}


cvstrategy(bp)
	register struct buf *bp;
{
	register int i;
	int unit, part;

	i = minor(bp->b_dev);
	unit = cv_unit(i);
	if (unit < 0 || unit >= NCV) {
		printf("cvstrategy: bad unit, unit=%d\n", unit);
		goto bad;
	}
	if (bp == &cvmbuf) {
		bp->b_resid = 0;
		goto iocmd;
	}
	part = cv_part(i);
	if (bp->b_blkno < 0 || bp->b_blkno >= cv_sizes[part].sz_size) {
		prdev("cvstrategy: illegal blkno", bp->b_dev);
		printf("blkno, blkno=%d bcount=%d\n",
			bp->b_blkno, bp->b_bcount);
		goto bad;
	}
	i = bp->b_blkno + btod(bp->b_bcount);
	if (i >= cv_sizes[part].sz_size)	/* trim too long transfers */
		bp->b_bcount = dtob(cv_sizes[part].sz_size - bp->b_blkno);
	bp->b_resid = bp->b_blkno; /* arg to disksort..*/
#ifdef SAS_PAGE
	if (saspage && (int)bp->b_un.b_addr >> pageshift == 0) {
		printf("cvstrategy: page 0 access buf 0x%x\n", bp);
		cdebugger("cvstrategy");
	}
#endif SAS_PAGE
iocmd:	spl4();
	disksort(&cvtab[unit], bp);
	if (cvtab[unit].b_active == 0)
		cvstart(unit);
	spl0();
	return;
bad:
	bp->b_flags |= B_ERROR;
	iodone(bp);
	return;
}


/*
 * start
 *	called from strategy or interrupt to initiate next
 *	transfer. Can handle both i/o and diags.
 */
cvstart(unit)
{
	register struct buf *bp;
	register i;
	int dev;

loop:
	if ((bp = cvtab[unit].b_actf) == (struct buf *)NULL)
		return;
#ifdef SAS_PAGE
	if (saspage && (int)bp->b_un.b_addr >> pageshift == 0) {
		printf("cvstart: page 0 access buf 0x%x\n", bp);
		cdebugger("cvrw");
	}
#endif SAS_PAGE

	if (cvtab[unit].b_active == 0) {
		bp->b_resid = bp->b_bcount;
		cvtab[unit].b_active = 1;	/* active in sysIII is a char */
	}
	dev = minor(bp->b_dev);
	if (bp == &cvmbuf && bp->b_resid > 0) {
		if (cvsend(unit, bp) < 0) {
			errflag[unit] = 1;
			bp->b_flags |= B_ERROR;
			bp->b_resid = 0;
			goto next;
		}
		return;
	}
	if (bp->b_resid < DSIZE) {
next:		if (bp->b_resid != 0)
			printf("cvstart: blkno=%d resid=%d\n",
				bp->b_blkno, bp->b_resid);
		cvtab[unit].b_actf = bp->av_forw;
		cvtab[unit].b_active = 0;
		cvtab[unit].b_errcnt = 0;
		if (bp == &cvmbuf) {
			cvbusy[unit] = 0;
			wakeup(&cvmbuf);
		} else
			iodone(bp);
		goto loop;
	}
	i = bp->b_bcount - bp->b_resid;

	if (cvrw(unit,
	    bp->b_blkno + cv_sizes[cv_part(dev)].sz_offset + btod(i),
	    DSIZE, bp->b_flags&B_READ,
			bp->b_flags & B_PHYS ? (int)bp->b_un.b_addr + i
					     : vtop(bp->b_un.b_addr + i)) < 0) {
/* 7.25 toaster code 
		 bp->b_blkno + cv_sizes[cv_part(dev)].sz_offset + btod(i),
		 DSIZE, bp->b_flags&B_READ, bp->b_un.b_addr + i) < 0) {
*/
		bp->b_flags |= B_ERROR;
		goto next;
	}
	return;
}


/*
 * Read or write a data block to the disk
 *	called from start.
 */
cvrw(unit, blkno, n, flag, buf)
	daddr_t blkno;
	caddr_t buf;
{
	char cbuf[4];

#ifdef SAS_PAGE
	if (saspage && (int)buf >> pageshift == 0) {
		printf("cvrw: %s page 0\n", (flag == B_READ) ? "read"
			: "write");
		printf("cvrw: unit %x blkno 0x%x buf 0x%x\n",
			unit, blkno, buf);
		cdebugger("cvrw");
	}
#endif SAS_PAGE
	setpagemap(cv_vpage[unit], (int)buf >> pageshift);
	setpagemap(cv_vpage[unit]+1, ((int)buf >> pageshift) + 1);
	cvtab[unit].io_buf = (int)buf;
	cvtab[unit].io_count = n;
	cbuf[0] = flag == B_READ? N_R512: N_W512;
	cbuf[1] = cv_drive(unit) | (blkno >> 12 & 0xf0);
	cbuf[2] = blkno & 0xff;
	cbuf[3] = blkno >> 8 &0xff;
	if (cvop(unit, 4, cbuf) == 0) {
		if (flag == B_WRITE)
			cvw(unit, buf, n, DSIZE);
		return 0;
	}
bad:

/**
	printf("cvrw: %s error unit=%d blkno=%d n=%d buf=0x%x\n",
		flag==B_READ?"read":"write", unit, blkno, n, buf);
**/
	return -1;
}


/*
 * Interrupt service for cv disks.  cv's interrupt on a mpx'd slot which
 * is serviced by appleint(); this routine gets called iff the disk really
 * did interrupt.
 */
cvint(unit)
{
	register struct buf *bp;
	register struct cv_device *addr;
	unsigned char status;


	if (cvtab[unit].b_active == 0)
		return;

	if ((bp = cvtab[unit].b_actf) == (struct buf *)NULL)
		return;
	addr = cv_addr(unit);
	if ((addr->cv_stat&ST_CTOH) == 0) {
		/**
		printf("cvint: drive not ready\n");
		**/
		addr->cv_stat = ST_ENB; 		/* ints ON */
		return;
	}
	cvwait(addr);
	cvwait(addr);
	addr->cv_stat = 0; 		/* ints OFF */
	if (bp == &cvmbuf) {
		cvrcv(unit, bp);
		bp->b_resid -= cvtab[unit].io_count;
	} else {
		status = addr->cv_data;
		if (bp->b_flags&B_READ)
			cvr(unit, cvtab[unit].io_buf, cvtab[unit].io_count,
			    DSIZE);
		if ((status&(NS_FATAL|NS_VERIFY)) != 0) {
			prdev(bp->b_flags&B_READ? "read error": "write error",
			      bp->b_dev);
			printf("blkno=%d status=0x%x (%s)\n", bp->b_blkno,
				status, cv_errlist[status&0x1F]);
			cvtab[unit].b_active = 0;
			cvtab[unit].b_errcnt = 0;
			cvtab[unit].b_actf = bp->av_forw;
			bp->b_flags |= B_ERROR;
			iodone(bp);
		} else {
			if (status != 0) {
				prdev(bp->b_flags&B_READ?
				      "read soft error": "write soft error",
				      bp->b_dev);
				printf(" blkno=%d status=0x%x (%s)\n",
					bp->b_blkno, status,
					cv_errlist[status&0x1F]);
			}
	  	        bp->b_resid -= cvtab[unit].io_count;
		}
	}
	cvstart(unit);
}


/*
 * send a packet direct to the disk
 *	called from cvstart for ioctl buffers
 */
cvsend(unit, bp)
	register struct buf *bp;
{
	register cnt;

	cnt = bp->b_bcount;
		/* handshake in the first bytes .. */
	if (cvop(unit, cnt < 8? cnt: 8, bp->b_un.b_addr) < 0)
		goto bad;
	cnt -= 8;
	if (cnt > 0)
		cvw(unit, bp->b_un.b_addr + 8, cnt, cnt);
	cvtab[unit].io_count = bp->b_resid;	/* see cvint for use */
	return 0;
bad:
	/**
	printf("cvsend: error unit=%d b_bcount=%d %s %s\n",
		unit, bp->b_bcount,bp->b_un.b_addr[0],bp->b_un.b_addr[1]);
	**/
	return -1;
}


/*	Send the opcode to the disk
 *	called from cvrw to handshake the command across to the disk.
 */
cvop(unit, nb, buf)
	char	*buf;
{
	register char *ap;
	register struct cv_device *addr = cv_addr(unit);

	if (cvhtoc(addr) < 0)
		return -1;
	ap = buf;
	cvwait(addr);			/* pause for glitchs */
	for (; nb > 0; nb--, ap++) {	/* don't want prefetch */
		cvwait(addr);
		addr->cv_data = *ap;
	}
	addr->cv_stat = ST_ENB; /* ints enabled */
	return 0;
}


/*
 * copy data, byte by byte, into the disk data buffer.
 *	called from cvrw after the command is sent.
 *	The dbf loop speeds things up, but not enough
 *	to make a big difference (sigh).
 */
cvw(unit, buf, n, limit)
	char	*buf;
	register cnt_t  n;	/* d7 */
{
	register char *p;			/* a5 */
	register struct cv_device *addr = cv_addr(unit); /* a4 */
	register char *cp;	/* a3 */
	register i;	/* d6 */

	for (i=100; --i; )
		;
	cp = (char *)((cv_vpage[unit] << pageshift) + ((int)buf & pagemask));
	i = limit - n;
	p = &addr->cv_data;
	/*
	 * do *p = *cp++;		(copy data)
	 * while (--n != -1);
	 */
	--n;
	asm("cvw1:");
	asm("	movb	a3@+, a5@");
	asm("	dbf	d7, cvw1");
	;
	if (--i >= 0) {
		/*
		 * do *p = 0;		(zero out the remaining bytes)
		 * while (--i != -1);
		 */
		asm("cvw2:");
		asm("	clrb	a5@");
		asm("	dbf	d6, cvw2");
		;
	}
}


/*
 * read from the controller
 * called by cvint to suck back data on read&c. commands
 */
cvr(unit, buf, n, limit)
	char	*buf;
	register cnt_t n;	/* d7 */
{
	register struct cv_device *addr = cv_addr(unit);	/* a5 */
	register char *p;
	register char *cp;	/* a4, a3 */
	register i;	/* d6 */

	cp = (char *)((cv_vpage[unit] << pageshift) + ((int)buf & pagemask));
	p = &addr->cv_data;
	i = limit - n;

	--n;
	/*
	 * do *cp++ = *p;
	 * while (--n != -1);
	 */
	asm("cvr1:");
	asm("	movb	a4@, a3@+");
	asm("	dbf	d7, cvr1");
	;
	if (--i >= 0) {
		/*
		 * Complete the read command (STOOPID!)
		 * do n = *p;
		 * while (--i != -1);
		 */
		asm("cvr2:");
		asm("	movb	a4@, d7");
		asm("	dbf	d6, cvr2");
		;
	}
}


/*
 * Wait for controller to reverse the bus direction so we can send things.
 */
cvhtoc(a)
	register struct cv_device *a;
{
	register i;

#ifdef WHITE
	for (i = 100; i-- > 0;);
	i = 300000;
#else WHITE
	for (i = 20; i-- > 0;);
	i = 100000;
#endif WHITE
	do
		while (--i > 0 && a->cv_stat&ST_BUSY);
	while (i > 0 && a->cv_stat&ST_CTOH);
	if (i <= 0) {
                printf("cvhtoc: timeout stat=%x addr=%x\n", a->cv_stat&0xFF,a);

		return -1;
	}
	return 0;
}


cvread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return physio(cvstrategy, &cvrbuf[cv_unit(minor(dev))], dev, B_READ,
	    minphys, uio);
}

cvwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return physio(cvstrategy, &cvrbuf[cv_unit(minor(dev))], dev, B_WRITE,
	    minphys, uio);
}


/*
 * ioctl
 *	CVIOCSTATS just pulls out some driver-resident goodies about the
 *	device.  The other commands cause an actual disk event to occur.
 *	Each disk event is characterized by
 *		send command block
 *		receive result block
 *	The blocks are variable length and obscurely formatted.  Much of
 *	the work in this routine is munging and verifying the blocks.
 */
cvioctl(dev, cmd, addr, flag)
	dev_t dev;
	register caddr_t addr;
{
	dev_t d = minor(dev);
	short unit = cv_unit(d);
	short drive = cv_drive(unit);
	short docopy = 0;		/* do copy out the result */
	static char cvbuf[600];
	static short cvisioctl[NCV];
	static short cvdiagmode[NCV];

#define A ((struct cv_specialcmd *)addr)
#define BP (&cvmbuf)
#define C ((struct cv_drivercmd *)addr)
	switch (cmd) {
	case CVIOCSTATS: {
		register struct cv_sizes *cvsize = &cv_sizes[cv_part(d)];

		C->result = 0;
		C->cvs_drive = drive;
		C->cvs_bno = cvsize->sz_offset;
		C->cvs_size = cvsize->sz_size;
		return 0;
	}
	case CVIOCPARK:		/* park heads */
	case CVIOCRFIRM:	/* read firmware block */
	case CVIOCWFIRM:	/* write firmware block */
	case CVIOCPREP:		/* go into diagnostic mode */
	case CVIOCRESET:	/* reset the drive */
	case CVIOCFORMAT:	/* format me */
	case CVIOCPBLK:		/* extract parameter block */
		break;
	default:
		return ENOTTY;
	}

	/*
	 * Protect cvmbuf here
	 */
	spl6();
	if (cvisioctl[unit]) {
		spl0();
		return EBUSY;		/* XXX */
	}
	cvisioctl[unit] = 1;
	spl0();

	/*
	 * Do generic formatting of the buffer
	 */
	bzero(BP, sizeof(*BP));	/* paranoid, really */
	BP->b_un.b_addr = (caddr_t)cvbuf;
	BP->b_dev = dev;
	BP->b_code = 0;

	/*
	 * Set up command-specific operations
	 */
	bzero(cvbuf, sizeof(cvbuf));
	switch (cmd) {
	/*
	 * Park the heads on a Rev-H disk (I don't check to make sure that
	 * it's H!)
	 */
	case CVIOCPARK:
		cvbuf[0] = 0x11;		/* diagnostic mode */
		cvbuf[1] = drive;		/* drive # */
		cvbuf[13] = 0xC3;		/* park head command */
		cvbuf[14] = 0xC3;		/* repeat */
		BP->b_bcount = 514;		/* command length */
		BP->b_rcvem = 1;		/* result length */
		break;

	/*
	 * Put the controller into diagnostic, or "prep," mode.  This
	 * means reading in a block of firmware and handing it to the
	 * controller; the controller will then start executing this block
	 */
	case CVIOCPREP:
		cvbuf[0] = 0x11;		/* diagnostic mode */
		cvbuf[1] = drive;		/* drive # */
		if (copyin(A->request_data, &cvbuf[2], 512)) {/* get prep blk */
			u.u_error = EFAULT;		/* oops! */
			goto errxit;
		}
		BP->b_bcount = 514;		/* command length */
		BP->b_rcvem = 1;		/* result length */
		cvdiagmode[unit] = 1;		/* mark as being diagnostic */
		break;

	/*
	 * Reset the controller from "prep" to normal mode.
	 */
	case CVIOCRESET:
		cvbuf[0] = 0;			/* reset command */
		BP->b_bcount = 1;		/* command length */
		BP->b_rcvem = 1;		/* result length */
		cvdiagmode[unit] = 0;		/* reset the software flag */
		break;

	/*
	 * Format the drive.  We load the buffer with the Format command
	 * along with a 512-byte format pattern.  Format completely
	 * obliterates the disk (including firmware and track tables.)
	 */
	case CVIOCFORMAT: {
		register n = 256;
		register char *ap = &cvbuf[1];

		cvbuf[0] = 1;			/* format command byte */
		while (--n >= 0) {
			*ap++ = 0xB6;	/* Corvus-recommended pattern */
			*ap++ = 0xD9;	/*	bits (who knows?) */
		}
		BP->b_bcount = 513;		/* command length */
		BP->b_rcvem = 1;		/* result length */
		break;
	}

	/*
	 * Write firmware to the drive
	 */
	case CVIOCWFIRM: {
		/*
		 * convert absolute sector number into <head:3, sector:5>
		 */
#define head_sec(x) (((x)>=20? (1<<5): 0) | ((x)%20))
		u_char hs;			/* head/sector pair */
		cvbuf[0] = 0x33;		/* write firmware cmd */
		if (copyin(A->request_data, &cvbuf[1], 513)) {
			u.u_error = EFAULT;
			goto errxit;
		}
		hs = cvbuf[1]; cvbuf[1] = head_sec(hs);
		BP->b_bcount = 514;
		BP->b_rcvem = 1;
		break;
	}

	/*
	 * Read firmware from the drive
	 */
	case CVIOCRFIRM: {
		u_char hs;			/* head/sector pair */

		cvbuf[0] = 0x32;		/* read firmware cmd */
		docopy = 1;			/* need to copy out result */
		if (copyin(A->request_data, &cvbuf[1], 1)) {
			u.u_error = EFAULT;
			goto errxit;
		}
		hs = cvbuf[1]; cvbuf[1] = head_sec(hs);
		BP->b_bcount = 2;
		BP->b_rcvem = 513;
		break;
#undef head_sec
	}


	/*
	 * Extract the parameter block from the drive.  Set docopy
	 * so that the block will be copied out to the user when done.
	 */
	case CVIOCPBLK:
		docopy = 1;
		cvbuf[0] = 0x10;		/* command byte */
		cvbuf[1] = drive;		/* drive # */
		BP->b_bcount = 2;		/* command length */
		BP->b_rcvem = 129;		/* result length */
		break;
	}

	/*
	 * Run the command
	 */
	cvbusy[unit]++;
	cvstrategy(BP);
	while (cvbusy[unit])
		sleep(BP, PZERO);
	/*
	 * If an error occurred, clean up and inform the user.
	 */
	if (errflag[unit]) {
		errflag[unit] = 0;
		if (u.u_error == 0)
			u.u_error = EIO;
	} else if (docopy && BP->b_rcvem > 0) {
		/* --- Now copy out the result record (if needed) */
		if (copyout(cvbuf, A->response_data, BP->b_rcvem) < 0)
			u.u_error = EFAULT;
	}
/* pull out the result, anyway */
	A->result = cvbuf[0];
errxit:
	cvisioctl[unit] = 0;	/* don't forget to open the barn door again! */
	return u.u_error;
}
