/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */

/* $Header:op.c 12.0$ */
/* $ACIS:op.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/ca_atr/RCS/op.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char    *rcsid = "$Header:op.c 12.0$";
#endif

/* $Header:op.c 12.0$ */
/* $ACIS:op.c 12.0$ */

#include "op.h"
#if NOP > 0
#include	"../h/param.h"
#include	"../h/vm.h"
#include	"../h/buf.h"
#include	"../h/time.h"
#include	"../h/proc.h"
#include	"../h/errno.h"
#include	"../machine/pte.h"
#include	"../machine/io.h"
#include	"../machineio/ioccvar.h"
#include	"../h/kernel.h"
#include	"../h/systm.h"
#include	"../h/cmap.h"
#include	"../h/dk.h"
#include	"../h/uio.h"
#include	"../h/ioctl.h"
#include 	"../h/file.h"
#include	"../ca_atr/pcif.h"
#include	"../pc_code/dio.h"
#include	"../machine/mmu.h"
#include	"../machine/debug.h"
#include 	"../machine/dkio.h"



caddr_t         real_buf_addr();

struct pc_dpl   op_dpl[NOP];

#define	OP_SPL()	_spl3()	/* CPU level 3 */

#define UNIT_SHIFT	5
#define NPART	(1<<UNIT_SHIFT)
#define PART_MASK	(NPART-1)

#define	OPUNIT(dev)	(minor(dev) >> UNIT_SHIFT)
#define OPPART(dev)	(minor(dev) & PART_MASK)

#define	BSIZE	DEV_BSIZE
#define OPBPS	DEV_BSIZE

#define PAGESIZE 2048
#define OPTIMEOUT	30	/* number of seconds til interrupt known lost */
#define	OPFORMREQ	0x0b	/* magic flgs for opformat */
 /* internal command flags */
#define	B_CTRL		0x80000000	/* ctrl request */
#define	B_CLOSE		0x81000000	/* close request */
#define	B_STATUS	0x82000000	/* get status request */
#define	B_OPEN		0x83000000	/* open request */
#define	B_RESET		0x84000000	/* reset request */

unsigned        opbusy;

#define b_cylin	b_resid

struct op_softc {
	int             timer;	/* timer for watchdog */
	u_short         type[NOP];
}               ops;


struct buf      ropbuf[NOP];	/* buffers for raw I/O */
struct buf      oputab[NOP];	/* per drive buffers */

struct iocc_ctlr *opminfo[NOPC];
struct iocc_device *opdinfo[NOP];

int 
opprobe(), opslave(), opattach(), opdgo(), opint();
int             opwstart, 
opwatch(), opstrategy();

int        opminphys();
int        opsuspend();

caddr_t         opstd[] = {
			   0
};

struct iocc_driver opcdriver = {
    opprobe, opslave, opattach, opdgo, opstd, "op", opdinfo, "opc", opminfo,
				opint, 0, 0, DRIVER_SUSPEND, opsuspend
};

char           *bioserrmsg();

#define OPIRQ	2

/*
 * we have to handle the problem that both the block and character
 * device may be open, and only the final close should actually do
 * anything to the device. We do this by assuming that the block
 * and character device MAJOR number are different (which they normally
 * would be).
 */
int op_open_flag[NOP * NPART];	/* bits to indicate if open */
int op_open_how[NOP * NPART];	/* bits to indicate how open */
int op_size[NOP * NPART];	/* size (in blocks) */
int op_pc_cb;			/* pc control block address */

opprobe()
{

	op_pc_cb = get_pc_cb(OPENT);

	if (op_pc_cb == 0)
		return (PROBE_BAD);

	pcvec_map[OPIRQ] = 1;

	op_dpl[0].op_code = DISKOP_RESET;
	op_dpl[0].drive = 0;

	if (pc_req(CB_OPREQ, &op_dpl[0], OPENT) < 0)
		return (PROBE_BAD);

	PROBE_DELAY(20000);

	return (PROBE_OK);

}


opslave(iod)
	register struct iocc_device *iod;
{
	register short  unit = iod->iod_unit;
	register u_char eq_byte = (u_char) (equip & 0x00ff);

	if (unit < NOP)		/* there has to be drive 1 */
		return (1);	/* tell em it's there */

	return(0);
}


opattach(iod)
	register struct iocc_device *iod;
{
	static float    mspw =.0000016097;
	register struct buf *dp = &oputab[iod->iod_unit];

	if (opwstart == 0) {
		timeout(opwatch, (caddr_t) 0, hz);
		++opwstart;
	}
	dp->b_actf = NULL;
	dp->b_actl = NULL;
	dp->b_active = 0;
	ops.timer = 0;
	if (iod->iod_dk >= 0)
		dk_mspw[iod->iod_dk] = mspw;
	return (1);
}



opopen(dev, flags)
	dev_t           dev;
	int             flags;
{
	register int    unit = OPUNIT(dev);
	register struct iocc_device *ui;

	DEBUGF(opdebug & SHOW_OPEN, printf("opopen dev=%x entered\n",dev));

	if (unit >= NOP || (ui = opdinfo[unit]) == 0 || ui->iod_alive == 0)
		return (ENXIO);


	if (op_open_flag[minor(dev)] == 0) {
		if (opautodensity(dev, flags) < 0)
			return (EIO);	/* return error if no disk */
	}

	op_open_flag[minor(dev)] |= 1 << major(dev);	/* now open */
	op_open_how[minor(dev)] |= flags;		/* how open */

	DEBUGF(opdebug & SHOW_OPEN, printf("opopen exit\n"));
	return (0);
}


opautodensity(dev, flags)
	dev_t           dev;
	int             flags;
{
	register int    unit = OPUNIT(dev);
	struct pc_dpl   dpl;
	int             return_code;

	DEBUGF(opdebug & SHOW_OPEN, printf("opautodensity dev=%x entered\n",dev));

	bzero(&dpl, sizeof(struct pc_dpl));

	dpl.op_code = DISKOP_OPEN;	/* open the drive */
	dpl.drive = minor(dev);
	dpl.number = (flags & FWRITE) != 0;

	if (flags == OPFORMREQ)
		return (0);

	if ((return_code = opop(&dpl)) < 0) {
		printf("op%d: door open or hardware fault.\n", unit);
		return (-1);
	}
#ifdef notdef
	if ((return_code & OPWP) && (flags & FWRITE)) {
		printf("op%d: write protected\n", unit);
		return (-1);
	}
#endif
	DEBUGF(opdebug & SHOW_OPEN, printf("open done\n"));

	return (0);
}



opclose(dev, flag)
	dev_t           dev;
{
	register int    unit = OPUNIT(dev);
	struct pc_dpl   dpl;
	int             return_code;

	op_open_flag[minor(dev)] &= ~(1 << major(dev));	/* now closed */

	bzero(&dpl, sizeof(struct pc_dpl));

	dpl.op_code = DISKOP_CLOSE;	/* close the drive */
	dpl.drive = minor(dev);
	dpl.number = (flag & FWRITE) != 0;

	if (flag == OPFORMREQ)
		return (0);

	if (op_open_flag[minor(dev)] == 0) {	/* last close */
		op_open_how[minor(dev)] = 0;	/* now closed */
		if ((return_code = opop(&dpl)) < 0) {
			printf("op%d: door open or hardware fault.\n", unit);
			return (-1);
		}
	}
	DEBUGF(opdebug & SHOW_OPEN, printf("opclose: dev=0x%x\n", dev));
	return (0);
}





opread(dev, uio)
	dev_t           dev;
	struct uio     *uio;
{
	int             unit = OPUNIT(dev);
	DEBUGF(opdebug & SHOW_RDWR, printf("opread entered\n"));

	if ((uio->uio_offset < 0) || (unit >= NOP))
		return (ENXIO);

	return (physio(opstrategy, &ropbuf[unit], dev, B_READ, opminphys, uio));
}


opwrite(dev, uio)
	dev_t           dev;
	struct uio     *uio;
{
	int             unit = OPUNIT(dev);

	DEBUGF(opdebug & SHOW_RDWR, printf("opwrite entered\n"));

	if ((uio->uio_offset < 0) || (unit >= NOP))
		return (ENXIO);

	return (physio(opstrategy, &ropbuf[unit], dev, B_WRITE, opminphys, uio));
}



opstrategy(bp)
	register struct buf *bp;
{

	register int    unit = OPUNIT(bp->b_dev);
	register struct iocc_device *iod;
	register struct buf *dp;
	register int    s;

	DEBUGF(opdebug & SHOW_RDWR, printf("opstrategy: bp=%x dev=%x blkno=%d addr=%x\n", bp, bp->b_dev, bp->b_blkno, bp->b_un.b_addr));

	iod = opdinfo[unit];

	if ((unit >= NOP) || (iod->iod_alive == 0) || (iod == 0) ||
	    (op_open_flag[minor(bp->b_dev)] == 0 && (bp->b_flags&B_CTRL) == 0)
	    || (bp->b_blkno < 0)) {
		bp->b_error = ENXIO;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}

	/* sort in order by unit number, then block */
	bp->b_cylin = bp->b_blkno  + (OPPART(bp->b_dev) << 24);

	s = OP_SPL();
	dp = &oputab[unit];
	disksort(dp, bp);

	if (dp->b_active == 0) {
		(void) opustart(iod);
		bp = &iod->iod_mi->ic_tab;
		if (bp->b_actf && bp->b_active == 0)
			(void) opstart(iod->iod_mi);
	}
	splx(s);
}



opustart(iod)
	register struct iocc_device *iod;
{
	register struct buf *dp;
	register struct iocc_ctlr *ic;

	dp = &oputab[iod->iod_unit];
	ic = iod->iod_mi;

	dp->b_forw = NULL;
	if (ic->ic_tab.b_actf == NULL)
		ic->ic_tab.b_actf = dp;
	else
		ic->ic_tab.b_actl->b_forw = dp;
	ic->ic_tab.b_actl = dp;
	dp->b_active++;

	return (0);
}


/*
 *	Controller Startup Routine:
 *
 *
 */
opstart(ic)
	register struct iocc_ctlr *ic;
{
	register struct buf *bp, *dp;
	register struct iocc_device *iod;

loop:
	if ((dp = ic->ic_tab.b_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL) {
		ic->ic_tab.b_actf = dp->b_forw;
		goto loop;
	}
	ic->ic_tab.b_active++;	/* Mark controller active */

	iod = opdinfo[OPUNIT(bp->b_dev)];

	opdgo(bp);
	if (iod->iod_dk >= 0) {
		dk_busy |= 1 << iod->iod_dk;
		dk_seek[iod->iod_dk]++;
		dk_xfer[iod->iod_dk]++;
		dk_wds[iod->iod_dk] += bp->b_bcount >> 6;
	}
	return (0);
}


/* 
 * This function sends the command out via a cbcb call
 */

opdgo(bp)
	register struct buf *bp;
{
	int             i, unit = OPUNIT(bp->b_dev);
	u_short         head, sector, xfer_cnt, cnt;
	char           *vir_addr, *xfer_addr;

	if ((bp->b_bcount < 0) || (bp->b_bcount >= 0x10000)) {
		printf("op%d: bad bcount = %x\n", unit, bp->b_bcount);
		bp->b_error = ENXIO;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	while (opbusy) {
		delay(10);
		if (i++ > 100)
			panic("op: Non-sync request attempt.");
	}

	vir_addr = bp->b_un.b_addr;
	xfer_addr = real_buf_addr(bp, vir_addr);
	xfer_cnt = (u_short) bp->b_bcount;

	if (bp->b_flags & B_CTRL) {

		switch (bp->b_flags & 0xff000000) {

		case B_RESET:
			op_dpl[unit].op_code = DISKOP_RESET;
			break;

		case B_STATUS:
			op_dpl[unit].op_code = DISKOP_STATUS;
			break;

		case B_OPEN:
			op_dpl[unit].op_code = DISKOP_OPEN;
			break;

		case B_CLOSE:
			op_dpl[unit].op_code = DISKOP_CLOSE;
			break;

		default:
			printf("op%d: Invalid internal command\n", unit);
			return (-1);

		}

		op_dpl[unit].atr_addr[0] = (int) xfer_addr;
		op_dpl[unit].atr_cnt[0] = BSIZE;
		op_dpl[unit].atr_addr[1] = 0;
		op_dpl[unit].atr_cnt[1] = 0;
		goto doit;
	} else {
		if (bp->b_flags & B_READ)
			op_dpl[unit].op_code = DISKOP_READ;
		else
			op_dpl[unit].op_code = DISKOP_WRITE;
	}

	op_dpl[unit].atr_addr[0] = (int) xfer_addr;	/* set up 1st tcw */

	if ((cnt = (PAGESIZE - ((int) xfer_addr & 0x000007ff))) > xfer_cnt)
		op_dpl[unit].atr_cnt[0] = (u_short) xfer_cnt;
	else
		op_dpl[unit].atr_cnt[0] = (u_short) cnt;

	xfer_cnt -= op_dpl[unit].atr_cnt[0];	/* adjust the total count */
	vir_addr += op_dpl[unit].atr_cnt[0];	/* adjust the virtual addr */
	/* we're on a boundry now */
	for (i = 1; xfer_cnt > PAGESIZE; i++) {	/* while there's a count */
		xfer_addr = real_buf_addr(bp, vir_addr);	/* fill out tcw's */
		op_dpl[unit].atr_addr[i] = (int) xfer_addr;
		op_dpl[unit].atr_cnt[i] = PAGESIZE;
		vir_addr += PAGESIZE;
		xfer_cnt -= PAGESIZE;
	}

	if (xfer_cnt) {		/* if there is a partial page left, do it */
		xfer_addr = real_buf_addr(bp, vir_addr);
		op_dpl[unit].atr_addr[i] = (int) xfer_addr;
		op_dpl[unit].atr_cnt[i] = xfer_cnt;
		i++;
	}
	op_dpl[unit].atr_addr[i] = 0;	/* mark end of request */
	op_dpl[unit].atr_cnt[i] = 0;

	/* Then fill in the rest of the dpl and send it off */
doit:
	sector = (u_short) bp->b_blkno;		/* low-order block number */
	head = bp->b_blkno >> 16;		/* high-order block number */

	op_dpl[unit].drive = minor(bp->b_dev);	/* load up the dpl */
	op_dpl[unit].sector = (u_short) sector;
	op_dpl[unit].cyl = (u_short) bp->b_cylin;
	op_dpl[unit].head = (u_short) head;
	op_dpl[unit].number = (u_short) ((bp->b_bcount + BSIZE - 1) / BSIZE);

	opbusy++;

	if (pc_req(CB_OPREQ, &op_dpl[unit], OPENT) < 0) {
		printf("op%d: pc_req failed\n", unit);
		bp->b_error = EIO;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
}


opint(ctlr)
	int             ctlr;
{
	register struct buf *bp, *dp;
	register struct iocc_ctlr *ic;
	struct iocc_device *iod;
	struct pc_stat *op_stat;
	register int    unit, old_window;

	ic = opminfo[ctlr];

	if (ic == 0 || ic->ic_tab.b_active == 0) {
		return (1);
	}
	dp = ic->ic_tab.b_actf;
	if (dp->b_active == 0) {
		return (1);
	}
	bp = dp->b_actf;
	unit = OPUNIT(bp->b_dev);

	old_window = get_512_window();	/* Save the current window */
	op_stat = (struct pc_stat *) (set_512_window(op_pc_cb) + pcif_512_fw);
	op_stat += minor(bp->b_dev);


	if (op_stat->return_code & BIOS_ERROR) {
		printf("op%d: BIOS error code = %x<%s>\n", unit,
		       (char) (op_stat->return_code & 0x00ff),
		       bioserrmsg((char) (op_stat->return_code & 0x00ff)));
		bp->b_flags |= B_ERROR;
	}
	opbusy = 0;		/* clear the busy flag */

	/*
	 * Flag current request complete 
	 */
	if (ic->ic_tab.b_active) {	/* For when we do error recovery */
		ic->ic_tab.b_active = 0;
		ic->ic_tab.b_errcnt = 0;
		ic->ic_tab.b_actf = dp->b_forw;	/* rotate to next drive */
		dp->b_active = 0;
		dp->b_errcnt = 0;
		dp->b_actf = bp->av_forw;
		bp->b_resid = bp->b_bcount - op_stat->pad;	/* amount not xfer'ed */
		iodone(bp);

		/*
		 * If more work to do on this drive, restart it. 
		 */
		iod = opdinfo[unit];
		if (iod->iod_dk >= 0)
			dk_busy &= ~(1 << iod->iod_dk);
		if (dp->b_actf)
			opustart(iod);
	}
	opstart(ic);

	ops.timer = 0;		/* did not timeout */

	set_512_window(old_window);

	return (0);
}


/*
 * watchdog timer - checks to see if we've lost an interrupt
 * by having the timer click 30 times without being cleared
 * (by interrupt routine)
 */

opwatch(reg)
	caddr_t         reg;
{
	register struct op_softc *op = &ops;
	register struct iocc_ctlr *ic;
	register int    s = OP_SPL();
	register int    i;
	register struct buf *dp, *bp;
	struct pc_stat *op_stat;
	register int    old_window;

	for (i = 0; i < NOPC; ++i) {
		if ((ic = opminfo[i]) == 0 || ic->ic_alive == 0 || ic->ic_tab.b_active == 0) {
			op->timer = 0;
			continue;	/* not doing anything */
		}
		if (++op->timer > OPTIMEOUT) {
			if ((dp = ic->ic_tab.b_actf) == NULL || (bp = dp->b_actf) == NULL)
				continue;

			old_window = get_512_window();
			op_stat = (struct pc_stat *) (set_512_window(op_pc_cb) + pcif_512_fw);
			op_stat += OPUNIT(bp->b_dev);

			if (op_stat->return_code & (BIOS_ERROR | BIOS_RET_OK)) {
				set_512_window(old_window);
				op->timer = 0;
				printf("op%d: opint called from opwatch\n", OPUNIT(bp->b_dev));
				opint(i);
			} else {
				opbusy = 0;	/* clear the busy flag */
				op->timer = 0;
				set_512_window(old_window);
				printf("op%d: lost interrupt\n", OPUNIT(bp->b_dev));
				opstart(ic);	/* retry it */
			}
		}
	}
	timeout(opwatch, (caddr_t) 0, hz);
	splx(s);
}




/*
 * Interface for internally generated commands.
 */

opop(dpl)
	register struct pc_dpl *dpl;
{
	register ushort unit = dpl->drive;
	struct buf     *bp = geteblk(BSIZE);
	struct pc_stat *op_stat;
	register int    old_window, return_code;

	switch (dpl->op_code) {

	case DISKOP_RESET:
		bp->b_flags = B_RESET;
		break;

	case DISKOP_STATUS:
		bp->b_flags = B_STATUS;
		break;

	case DISKOP_OPEN:
		bp->b_flags = B_OPEN;
		break;

	case DISKOP_CLOSE:
		bp->b_flags = B_CLOSE;
		break;

	default:
		printf("op%d: Invalid opop command\n", unit);
		return (-1);
	}

	bp->b_dev = (dev_t) makedev(0,unit);
	bp->b_blkno = dpl->cyl;
	bp->b_bcount = dpl->number * BSIZE;

	opstrategy(bp);
	iowait(bp);

	old_window = get_512_window();	/* Save the current window */
	op_stat = (struct pc_stat *) (set_512_window(op_pc_cb) + pcif_512_fw);
	op_stat += unit;
	return_code = op_stat->return_code;
	op_size[unit] = op_stat->badblock;	/* size (in blocks) */
	set_512_window(old_window);

	if (bp->b_flags & B_ERROR)
		return_code = -1;

	brelse(bp);

	return (return_code);

}


/*
 * Control routine:
 * processes two kinds of requests:
 *
 *	(1) Format the diskette according to the specified data parameter.
 *	(2) Report the density of the diskette in the indicated drive
 *	    (since the density it automatically determined by the driver,
 *	     this is the only way to let an application program know the
 *	     density)
 *
 */


opioctl(dev, cmd, data, flag)
	dev_t           dev;
	caddr_t         data;
{
	int             unit = OPUNIT(dev);
	DEBUGF(opdebug & SHOW_OPEN, printf("opioctl entered\n");
	);

	switch (cmd) {

#ifdef OPIOC_GDENS
	case OPIOC_GDENS:	/* get density */

		*(int *) data = (int) ops.type[unit];
		return (0);
#endif

#ifdef OPIOC_RESET
	case OPIOC_RESET:	/* reset specified unit */

		return (opreset(unit));
#endif

	case DKIOCGPART:{
			register struct dkpart *dk = (struct dkpart *) data;

			dk->dk_size = op_size[minor(dev)];	/* size */
			dk->dk_start = 0;	/* start */
			dk->dk_blocksize = OPBPS;	/* device blocksize */
			dk->dk_ntrack = 1;	/* tracks/cylinder */
			dk->dk_nsector = 23;	/* sectors/track */
			dk->dk_ncyl = 0;	/* cylinders */
			strcpy(dk->dk_name, "worm");	/* copy name */
			return (0);
		}
	}
	return (ENOTTY);	/* sigh */
}


opreset(unit)
	int             unit;
{
	struct pc_dpl   dpl;

	bzero(&dpl, sizeof(struct pc_dpl));

	dpl.op_code = DISKOP_RESET;	/* reset the drive */
	dpl.drive = (u_short) unit;

	if (opop(&dpl) < 0)
		return (-1);

	return (0);
}



#define OPMAXPHYS	(1024 * 60)	/* 60K maximum transfer for hd & op */
opminphys(bp)
	struct buf     *bp;
{

	if (bp->b_bcount > OPMAXPHYS)
		bp->b_bcount = OPMAXPHYS;
}



/*
 * return partition size (for swap partitions)
 */

opsize(dev)
	dev_t           dev;
{
	register int    unit = OPUNIT(dev);
	register struct iocc_device *iod;

	if (unit >= NOP || (iod = opdinfo[unit]) == 0 || iod->iod_alive == 0)
		return (-1);

	return (5*1024*2);		/* pretend we've got 5meg */
}


/*
 * routine called when we suspend and when we return
 * all we have to do is pick up a new op_pc_cb when we
 * return. At some point we might want to stop the watch
 * routine but this isn't necessary now because timeouts
 * don't run while we are suspended.
 * we don't bother to close open files because DOS will do that for us.
 * we do reopen the devices that were open we we come back from the
 * suspension.
 */
opsuspend(iod, idr, how)
	struct iocc_device *iod;
	struct iocc_driver *idr;
{
	if (how == SUSPEND_DONE) {
		int i;
		op_pc_cb = get_pc_cb(OPENT);	/* get pc_cb addr */
		for (i=0; i<NOP * NPART; ++i) {
			if (op_open_flag[i])
				opautodensity(makedev(ffs(op_open_flag[i])-1,i), op_open_how[i]);
		}
	}
}
#endif NOP
