/*
 * RX11 floppy disk driver
 * Modified 2/1/78, Brad Blasing, to emulate DMA operations
 *
 * Modified 15 DEC 77 by SSB,DWD,BEB, University of Minnesota 
 * for RX11 floppy drive.
 * Modified 6/16/78, Brad Blasing, to provide better error recovery
 *  (we don't loop at spl5 waiting for INIT to finish any more).
 * Modified 10/19/79, Scott Bertilson, to run with slow floppy interfaces.
 * Modified 4/5/80, Scott Bertilson, added CP/M interleaving to table.
 * Modified 2/1/81, Scott Bertilson, to run on Version 7.
 * Modified at Tek to "sort of" run under 2.9bsd; 5/83; dgc
 */

#include "rx.h"
#if	NRX > 0
#include "param.h"
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/rxreg.h>
extern	struct rxdevice *RXADDR;

/* #define DEBUG	1 */
#define	DMA	1
/* #define RXADDR	((struct rxdevice *)0177170)	/* MAE - 3/29/83 */
#define NRXTYP	4
#define MAXRETRY  5
#define TTIME	60	/* Timeout time in HZ */
#define RRATE	6	/* Recall rate for rxreset */
#define RESETMAX 10	/* Max. num. of reset recalls before timeout */
			/* RESETMAX*RRATE/60 = time in second */

#define RXWAIT	while((rxaddr->rxcs & (TR | DONE)) == 0) ;
#define KISA	0172340
#define KISD	0172300

struct	rxtype {
	int	secsize;		/* size (bytes) one sector */
	int	secpertrk;		/* sectors/track */
	int	secperblk;		/* sectors/unix block */
	int	numsec;			/* total sectors on device */
	int	numblks;		/* number of blocks on device */
	int	secincr;		/* increment to get next sector of block */
	int	intrlv;			/* interleaving factor */
	int	skew;			/* sector skew across tracks */
	int	trkoffset;		/* offset num of 1st sec */
} rxtypes[NRXTYP] {
		128, 26, 4, 77*26, 500, 2, 13, 6, 0,	/* our "standard" format */
		128, 26, 4, 77*26, 500, 1, 26, 0, 0,	/* IBM format */
		128, 26, 4, 76*26, 494, 2, 13, 6, 1,	/* Terak or RT11 format */
		128, 26, 4, 76*26, 494, 6, 13, 0, 2	/* CP/M format */
};

struct	rxstat {
	int	fminor;			/* present request device number */
	struct	rxtype *ftype;		/* present request floppy type */
	int	bytect;			/* remaining bytes (neg) */
	int	sector;			/* absolute sector (0..numsec-1) */
	int	toutact;		/* timeout active */
	int	reqnum;			/* floppy request number for timeout */
	caddr_t	coreaddr;		/* current core address for transfer */
#ifdef DMA
	char	*coreblk;		/* block no. to put in seg. register */
#endif
} rxstat;

struct	buf	rxtab;

rxstrategy(abp)
struct buf *abp;
{
	register struct buf *bp;
	extern int rxtimeout();

#ifdef DEBUG
	if(minor(abp->b_dev) == 127) {
		rxdebug();
		iodone(abp);
		spl0();
		return;
	}
#endif
	bp = abp;
	/*
	 * test for valid request
	 */
	if(rxok(bp) == 0) {
		bp->b_flags =| B_ERROR;
		iodone(bp);
		return;
	}
	/*
	 * link buffer into device queue
	 */
	bp->av_forw = NULL;
	spl5();
	if(rxtab.b_actf == NULL)
		rxtab.b_actf = bp;
	else
		rxtab.b_actl->av_forw = bp;
	rxtab.b_actl = bp;
	/*
	 * start rxtimeout if inactive
	 */
	if(rxstat.toutact == 0) {
		rxstat.toutact++;
		timeout(rxtimeout, (caddr_t)0, TTIME);
	}
	/*
	 * start device if there is no current request
	 */
	if(rxtab.b_active == NULL)
		rxstart();
	spl0();
}

rxstart()
{
	register struct buf *bp;
	register struct rxdevice *rxaddr;
	register int dminor;

	rxaddr = RXADDR;
	/*
	 * if there is no request in queue...return
	 */
loop:	if((bp = rxtab.b_actf) == NULL)
		return;
	/*
	 * check if drive ready
	 */
	dminor = (minor(bp->b_dev) & 1) << 4;
	rxaddr->rxcs = dminor | RDSTAT | GO;
	RXWAIT
	if((rxaddr->rxdb & DR) == 0) {
		printf("rx%d: Floppy not ready\n", minor(bp->b_dev));
		rxabtbuf();
		goto loop;
	}
	/*
	 * set active request flag
	 */
	rxtab.b_active++;
	rxsetup(bp);
	rxregs(bp);
}

rxintr()
{
	register struct buf *bp;
	register struct rxtype *rxt;
	register struct rxdevice *rxaddr;

	rxaddr = RXADDR;
	/*
	 * if there is no active request, false alarm.
	 */
	if(rxtab.b_active == NULL) 
		return;
	rxtab.b_active = NULL;
	/*
	 * pointer to the buffer
	 */
	bp = rxtab.b_actf;
	/*
	 * pointer to a data structure describing
	 *  the type of device (i.e. interleaving)
	 */
	rxt = rxstat.ftype;
	/*
	 * check error bit
	 */
	if(rxaddr->rxcs & ERROR) {
		/*
		 * send read error register command
		 */
		rxaddr->rxcs = RDERR | GO;
		RXWAIT
#ifndef	UCB_DEVERR
		deverror(bp, rxaddr->rxcs, rxaddr->rxdb);
#else
		harderr(bp, "rx");
		printf("cs=%b, es=%b\n", rxaddr->rxcs, RXCS_BITS,
			rxaddr->rxdb, RXES_BITS);
#endif
		/*
		 * make MAXRETRY retries on an error
		 */
		if(++rxtab.b_errcnt <= MAXRETRY) {
			rxreset(0);
			return;
		}
		/*
		 * return an i/o error
		 */
		bp->b_flags =| B_ERROR;
	} else {
		/*
		 * if we just read a sector, we need to
		 *  empty the device buffer
		 */
		if(bp->b_flags & B_READ) 
			rxempty(bp);
		/*
		 * see if there is more data to read for
		 * this request.
		 */
		rxstat.bytect =+ rxt->secsize;
		rxstat.sector++;
		if(rxstat.bytect < 0 && rxstat.sector < rxt->numsec) {
			rxtab.b_active++;
			rxregs(bp);
			return;
		}
	}
	rxtab.b_errcnt = 0;
	/*
	 * unlink block from queue
	 */
	rxtab.b_actf = bp->av_forw;
	iodone(bp);
	/*
	 * start i/o on next buffer in queue
	 */
	rxstart();
}

rxreset(flag)
{
	register struct rxdevice *rxaddr;

	rxaddr = RXADDR;
	/*
	 * Check to see if this is a call from rxintr or
	 * a recall from timeout.
	 */
	if(flag) {
		if(rxaddr->rxcs & DONE) {
			rxtab.b_active = 0;
			rxstart();
		} else
			if(flag > RESETMAX) {
				printf("rx%d: Reset timeout\n", minor(rxtab.b_actf->b_dev));
				rxabtbuf();
				rxstart();
			} else {
				timeout(rxreset, (caddr_t)flag+1, RRATE);
				/*
				 * Keep rxtimeout from timing out.
				 */
				rxstat.reqnum++;
			}
	} else {
		rxaddr->rxcs = INIT;
		rxtab.b_active++;
		rxstat.reqnum++;
		timeout(rxreset, (caddr_t)1, 1);
	}
}

rxregs(abp)
struct buf *abp;
{
	register struct buf *bp;
	register struct rxtype *rxt;
	register struct rxdevice *rxaddr;
	int	dminor, cursec, curtrk;

	rxaddr = RXADDR;
	/*
	 * set device bit into proper position for command
	 */
	dminor = rxstat.fminor << 4;
	bp = abp;
	rxt = rxstat.ftype;
	/*
	 * increment interrupt request number
	 */
	rxstat.reqnum++;
	/*
	 * if command is read, initiate the command
	 */
	if(bp->b_flags & B_READ){
		RXWAIT
		rxaddr->rxcs = dminor | IENABLE | GO | READ;
	} else {
		/*
		 * if command is write, fill the device buffer,
		 *   then initiate the write
		 */
		rxfill(bp);
		RXWAIT
		rxaddr->rxcs = dminor | IENABLE | GO | WRITE;
	}
	/*
	 * set track number
	 */
	curtrk = rxstat.sector / rxt->secpertrk;
	/*
	 * set sector number
	 */
	dminor = rxstat.sector % rxt->secpertrk;
	cursec = (dminor % rxt->intrlv) * rxt->secincr +
		(dminor / rxt->intrlv);
	/*
	 * add skew to sector
	 */
	cursec = (cursec + curtrk * rxt->skew)
		% rxt->secpertrk;
	/*
	 * massage registers
	 */
	RXWAIT
	rxaddr->rxdb = cursec + 1;
	RXWAIT
	rxaddr->rxdb = curtrk + rxt->trkoffset;
}

rxok(abp)
struct buf *abp;
{
	register struct buf *bp;
	register int type;
	register int dminor;

	/*
	 * get sub-device number and type from dminor device number
	 */
	dminor = minor((bp = abp)->b_dev);
	type = dminor >> 3;
	/*
	 * check for valid type
	 *
	 * check for block number within range of device
	 */
	if(type >= NRXTYP ||
		bp->b_blkno >= (daddr_t)rxtypes[type].numblks)
		return(0);
#ifndef DMA
	if(bp->b_xmem != 0) {		/* No buffers outside kernel space */
		prdev("Buffer outside kernel space", bp->b_dev);
		return(0);
	}
#endif
	return(1);
}

rxsetup(abp)
struct buf *abp;
{
	register struct buf *bp;
	register int dminor;
	register struct rxtype *rxt;

	/*
	 * get dminor device number from buffer
	 */
	dminor = minor((bp = abp)->b_dev);
	/*
	 * get sub-device number from dminor device number
	 */
	rxt = rxstat.ftype = &rxtypes[dminor >> 3];
	/*
	 * make sure device number is only 0 or 1
	 */
	rxstat.fminor = dminor & 1;
	/*
	 * get byte count to read from buffer (negative number)
	 */
	rxstat.bytect = -bp->b_bcount;
	/*
	 * transform block number into the first
	 * sector to read on the floppy
	 */
	rxstat.sector = (int)bp->b_blkno * rxt->secperblk;
	/*
	 * set the core address to get or put bytes.
	 */
#ifndef DMA
	rxstat.coreaddr = bp->b_un.b_addr;
#endif
#ifdef DMA
	rxstat.coreaddr = (bp->b_un.b_addr & 077) + 0120000;
	rxstat.coreblk = ((bp->b_un.b_addr >> 6) & 01777) |
		((bp->b_xmem & 03) << 10);
#endif
}

#ifndef DMA
rxempty(abp)
struct buf *abp;
{
	register struct rxdevice *rxaddr;
	register int i;
	register char *cp;
	int wc;

	rxaddr = RXADDR;
	/*
	 * start empty buffer command
	 */
	RXWAIT
	rxaddr->rxcs = EMPTY | GO;
	spl1();
	/*
	 * get core address and byte count
	 */
	cp = rxstat.coreaddr;
	rxstat.coreaddr =+ 128;
	wc = ((rxstat.bytect <= -128)? 128 : -rxstat.bytect);
	/*
	 * move wc bytes from the device buffer
	 *   into the in core buffer
	 */
	for(i=wc; i>0; --i) {
		RXWAIT
		*cp++ = rxaddr->rxdb;
	}
	/*
	 * sluff excess bytes
	 */
	for(i=128-wc; i>0; --i) {
		RXWAIT
		cp = rxaddr->rxdb;
	}
	spl5();
}

rxfill(abp)
struct buf *abp;
{
	register struct rxdevice *rxaddr;
	register int i;
	register char *cp;
	int wc;

	rxaddr = RXADDR;
	/*
	 * initiate the fill buffer command
	 */
	RXWAIT
	rxaddr->rxcs = FILL | GO;
	spl1();
	/*
	 * get core address and byte count
	 */
	cp = rxstat.coreaddr;
	rxstat.coreaddr =+ 128;
	wc = ((rxstat.bytect <= -128)? 128 : -rxstat.bytect);
	/*
	 * move wc bytes from the in-core buffer to
	 *   the device buffer
	 */
	for(i=wc;  i>0; --i) {
		RXWAIT
		rxaddr->rxdb = *cp++;
	}
	/*
	 * sluff excess bytes
	 */
	for(i=128-wc; i>0; --i) {
		RXWAIT
		rxaddr->rxdb = 0;
	}
	spl5();
}
#endif

#ifdef DMA
	/*
	 * This copy of the fill and empty routines emulate a dma
	 * floppy controller.  It adds the feature of being able
	 * to write anywhere in physical memory, just like an rk
	 * disk.  To do this, we borrow a segmentation register
	 * to do the transfer.  While the segmentation register
	 * is pointing to the proper place, we need to run at spl7.
	 * This is harder on the system, so the non-dma driver should
	 * be used if you only intend to do buffer requests (i.e.
	 * no swapping or raw i/o).
	 */

struct { int r[]; };

rxempty(abp)
struct buf *abp;
{
	register int i;
	register char *cp;
	register int wc;
	int a,d;
	struct rxdevice *rxaddr;

	rxaddr = RXADDR;
	/*
	 * start empty buffer command
	 */
	rxaddr->rxcs = EMPTY | GO;
	/*
	 * get core address and byte count
	 */
	cp = rxstat.coreaddr;
	wc = ((rxstat.bytect <= -128)? 128 : -rxstat.bytect);
	/*
	 * save and set segmentation register.
	 */
	a = KISA->r[5];
	d = KISD->r[5];
	spl7();
	KISA->r[5] = rxstat.coreblk;
	KISD->r[5] = 01006;
	/*
	 * move wc bytes from the device buffer
	 *   into the in core buffer
	 */
	for(i=wc; i>0; --i) {
		RXWAIT
		*cp++ = rxaddr->rxdb;
	}
	/*
	 * sluff excess bytes
	 */
	for(i=128-wc; i>0; --i) {
		RXWAIT
		cp = rxaddr->rxdb;
	}
	KISA->r[5] = a;
	KISD->r[5] = d;
	spl5();
	rxstat.coreblk =+ 2;
}

rxfill(abp)
struct buf *abp;
{
	register int i;
	register char *cp;
	register int wc;
	int a,d;
	struct rxdevice *rxaddr;

	rxaddr = RXADDR;
	/*
	 * initiate the fill buffer command
	 */
	rxaddr->rxcs = FILL | GO;
	/*
	 * get core address and byte count
	 */
	cp = rxstat.coreaddr;
	wc = ((rxstat.bytect <= -128)? 128 : -rxstat.bytect);
	/*
	 * save and set segmentation register.
	 */
	a = KISA->r[5];
	d = KISD->r[5];
	spl7();
	KISA->r[5] = rxstat.coreblk;
	KISD->r[5] = 01006;
	/*
	 * move wc bytes from the in-core buffer to
	 *   the device buffer
	 */
	for(i=wc;  i>0; --i) {
		RXWAIT
		rxaddr->rxdb = *cp++;
	}
	/*
	 * sluff excess bytes
	 */
	for(i=128-wc; i>0; --i) {
		RXWAIT
		rxaddr->rxdb = 0;
	}
	KISA->r[5] = a;
	KISD->r[5] = d;
	spl5();
	rxstat.coreblk =+ 2;
}
#endif

rxtimeout(dummy)
{
	static int prevreq;
	register struct buf *bp;

	bp = rxtab.b_actf;
	/*
	 * if the queue isn't empty and the current request number is the
	 * same as last time, abort the buffer and restart i/o.
	 */
	if(bp) {
		if(prevreq == rxstat.reqnum) {
			printf("rx%d: Floppy timeout\n", minor(bp->b_dev));
			rxabtbuf();
			rxstart();
		}
		prevreq = rxstat.reqnum;
		timeout(rxtimeout, (caddr_t)0, TTIME);
	} else {
		/*
		 * if queue is empty, just quit and rxstrategy will
		 * restart us.
		 */
		rxstat.toutact = 0;
	}
}

rxabtbuf()
{
	register struct buf *bp;

	/*
	 * abort the current buffer with an error and unlink it.
	 */
	bp = rxtab.b_actf;
	bp->b_flags =| B_ERROR;
	rxtab.b_actf = bp->av_forw;
	rxtab.b_errcnt = 0;
	rxtab.b_active = NULL;
	iodone(bp);
}

#ifdef DEBUG
rxdebug() {
	register struct buf *bp;

	spl5();
	printf("Debug:  &rxtab=%o, &rxstat=%o\n", &rxtab, &rxstat);
	printf(" rxstat:  fminor=%l, bytect=%l, sec=%l\n",
		rxstat.fminor, -rxstat.bytect, rxstat.sector);
	printf("   reqnum=%l\n", rxstat.reqnum);
	printf(" rxtab:  d_active=%l, buffers:\n", rxtab.b_active);
	for(bp=rxtab.b_actf; bp; bp=bp->av_forw)
		printf(" dev=%l/%l, blkno=%l, bcnt=%l, flags=%o.\n", major(bp->b_dev),
			minor(bp->b_dev), bp->b_blkno, -bp->b_bcount, bp->b_flags);
	putchar('\n');
}
#endif
