/* @(#)sc.c       1.1 94/10/31 Copyr 1987 Sun Micro */
#include "sc.h"
#if NSC > 0

#ifndef lint
static char sccsid[] = "@(#)sc.c       1.1 94/10/31 Copyr 1987 Sun Micro";
#endif	lint

/*#define SCSI_DEBUG			/* Turn on debugging code */
#define REL4				/* Enable release 4.00 mods */

/*
 * Generic scsi routines.
 */
#ifndef REL4
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dk.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/map.h"
#include "../h/vmmac.h"
#include "../h/ioctl.h"
#include "../h/uio.h"
#include "../h/kernel.h"
#include "../h/dkbad.h"
#include "../h/mman.h"

#include "../machine/pte.h"
#include "../machine/psl.h"
#include "../machine/mmu.h"
#include "../machine/cpu.h"
#include "../machine/scb.h"
#include "../vm/seg.h"
#include "../machine/seg_kmem.h"

#include "../sun/dklabel.h"
#include "../sun/dkio.h"

#include "../sundev/mbvar.h"
#include "../sundev/sireg.h"
#include "../sundev/screg.h"
#include "../sundev/scsi.h"

#else REL4
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/dk.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/user.h>
#include <sys/map.h>
#include <sys/vmmac.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/dkbad.h>
#include <sys/mman.h>

#include <machine/pte.h>
#include <machine/psl.h>
#include <machine/mmu.h>
#include <machine/cpu.h>
#include <machine/scb.h>
#include <vm/seg.h>
#include <machine/seg_kmem.h>

#include <sun/dklabel.h>
#include <sun/dkio.h>

#include <sundev/mbvar.h>
#include <sundev/sireg.h>
#include <sundev/screg.h>
#include <sundev/scsi.h>
#endif REL4

#define SCNUM(sc)	(sc - scctlrs)

int     scwatch();
int	scprobe(), scslave(), scattach(), scgo(), scdone(), scpoll();
int	scustart(), scstart(), sc_cmd(), sc_getstatus(), sc_cmdwait();
int	sc_off(), sc_reset(), sc_dmacnt(), sc_deque();

extern	int nsc, scsi_host_id, scsi_reset_delay;
extern	struct scsi_ctlr scctlrs[];		/* per controller structs */
extern	struct mb_ctlr *scinfo[];
extern	struct mb_device *sdinfo[];

struct	mb_driver scdriver = {
	scprobe, scslave, scattach, scgo, scdone, scpoll,
	sizeof (struct scsi_ha_reg), "sd", sdinfo, "sc", scinfo, MDR_BIODMA,
};

/* routines available to devices for mainbus scsi host adaptor */
struct scsi_ctlr_subr scsubr = {
	scustart, scstart, scdone, sc_cmd, sc_getstatus, sc_cmdwait,
	sc_off, sc_reset, sc_dmacnt, scgo, sc_deque,
};

extern int scsi_debug;
extern int scsi_disre_enable;
extern int scsi_ntype;
extern struct scsi_unit_subr scsi_unit_subr[];

#ifdef	SCSI_DEBUG
#define DEBUG_DELAY(cnt) \
	if (scsi_debug)  DELAY(cnt)

/* Handy debugging 0, 1, and 2 argument printfs */
#define DPRINTF(str) \
	if (scsi_debug > 1) printf(str)
#define DPRINTF1(str, arg1) \
	if (scsi_debug > 1) printf(str,arg1)
#define DPRINTF2(str, arg1, arg2) \
	if (scsi_debug > 1) printf(str,arg1,arg2)

/* Handy error reporting 0, 1, and 2 argument printfs */
#define EPRINTF(str) \
	if (scsi_debug) printf(str)
#define EPRINTF1(str, arg1) \
	if (scsi_debug) printf(str,arg1)
#define EPRINTF2(str, arg1, arg2) \
	if (scsi_debug) printf(str,arg1,arg2)

#else SCSI_DEBUG
#define DEBUG_DELAY(cnt)
#define DPRINTF(str) 
#define DPRINTF1(str, arg2) 
#define DPRINTF2(str, arg1, arg2) 
#define EPRINTF(str) 
#define EPRINTF1(str, arg2) 
#define EPRINTF2(str, arg1, arg2) 
#endif SCSI_DEBUG


/*
 * scsi CDB op code to command length decode table.
 */
static u_char cdb_size[] = {
	CDB_GROUP0,		/* Group 0, 6 byte cdb */
	CDB_GROUP1,		/* Group 1, 10 byte cdb */
	CDB_GROUP2,		/* Group 2, ? byte cdb */
	CDB_GROUP3,		/* Group 3, ? byte cdb */
	CDB_GROUP4,		/* Group 4, ? byte cdb */
	CDB_GROUP5,		/* Group 5, ? byte cdb */
	CDB_GROUP6,		/* Group 6, ? byte cdb */
	CDB_GROUP7		/* Group 7, ? byte cdb */
};


/*
 * This routine unlocks this driver when I/O
 * has ceased, and a call to scstart is
 * needed to start I/O up again.  Refer to
 * xd.c and xy.c for similar routines upon
 * which this routine is based
 */
static
scwatch(c)
	register struct scsi_ctlr *c;
{
	register struct scsi_unit *un;
	un = c->c_un;

	/*
	 * Find a valid un to call s?start with to for next command
	 * to be queued in case DVMA ran out.  Try the pending que
	 * then the disconnect que.  Otherwise, driver will hang
	 * if DVMA runs out.  The reason for this kludge is that
	 * scstart should really be passed c instead of un, but it
	 * would break lots of 3rd party S/W if this were fixed.
	 */
	if (un == NULL) {
		un = (struct scsi_unit *)c->c_tab.b_actf;
		if (un == NULL)
			un = (struct scsi_unit *)c->c_disqtab.b_actf;
	}
	scstart(un);
	timeout(scwatch, (caddr_t)c, 10*hz);
}

/*
 * Determine existence of SCSI host adapter.
 * Returns 0 for failure, size of sc data structure if ok.
 */
scprobe(reg, ctlr)
	register struct scsi_ha_reg *reg;
	register int ctlr;
{
	register struct scsi_ctlr *c;

	/* probe for different scsi host adaptor interfaces */
	c = &scctlrs[ctlr];
	if (peekc((char *)&reg->dma_count) == -1) {
		return (0);
	}
	/* validate cntlr by write/read/cmp with a data pattern */
	reg->dma_count = 0x6789;
	if (reg->dma_count != 0x6789) {
		return (0);
	}
	EPRINTF2("sc%d:  scprobe: reg= 0x%x", ctlr, (u_int)reg);
	EPRINTF1(",  c= 0x%x\n", (u_int)c );
        
	/*
         * Allocate a page for being able to
         * flush the last bit of a data transfer.
         */
        c->c_kment = rmalloc(kernelmap, (long)mmu_btopr(MMU_PAGESIZE));
        if (c->c_kment == 0) {
                printf("si%d: out of kernelmap for last DMA page\n", ctlr);
                return (0);
        }

	/* init controller information */
	c->c_flags = SCSI_PRESENT;
	c->c_reg = (int)reg;
	c->c_ss = &scsubr;
	c->c_name = "sc";
	c->c_tab.b_actf = NULL;
	c->c_tab.b_active = 0;
	sc_reset(c, NO_MSG);
	c->c_un = NULL;
	timeout(scwatch, (caddr_t)c, 10*hz);
	return (sizeof (struct scsi_ha_reg));
}


/*
 * See if a slave exists.
 * Since it may exist but be powered off, we always say yes.
 */
/*ARGSUSED*/
scslave(md, reg)
	struct mb_device *md;
	register struct scsi_ha_reg *reg;
{
	register struct scsi_unit *un;
	register int type;
	/* DPRINTF("scslave:\n"); */

#ifdef	lint
	reg = reg;				/* use it or lose it */
	reg = (struct scsi_ha_reg *)scsi_debug;
	reg = (struct scsi_ha_reg *)scsi_disre_enable;
#endif	lint

	/*
	 * This kludge allows autoconfig to print out "sd" for
	 * disks and "st" for tapes.  The problem is that there
	 * is only one md_driver for scsi devices.
	 */
	type = TYPE(md->md_flags);
	if (type >= scsi_ntype) {
		panic("scslave:  unknown type in md_flags\n");
	}

	/* link unit to its controller */
	un = (struct scsi_unit *)(*scsi_unit_subr[type].ss_unit_ptr)(md);
	if (un == 0) {
		panic("scslave:  md_flags scsi type not configured\n");
	}
	un->un_c = &scctlrs[md->md_ctlr];
	md->md_driver->mdr_dname = scsi_unit_subr[type].ss_devname;
	return (1);
}


/*
 * Attach device (boot time).
 */
scattach(md)
	register struct mb_device *md;
{
	register struct mb_ctlr *mc = md->md_mc;
	register struct scsi_ctlr *c = &scctlrs[md->md_ctlr];
	register int type = TYPE(md->md_flags);
	register struct scsi_ha_reg *reg;

	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("scattach:\n"); */
	if (type >= scsi_ntype) {
		panic("scattach:  unknown type in md_flags\n");
	}
	(*scsi_unit_subr[type].ss_attach)(md);

	/*
	 * Initialize interrupt register
	 */
	if (mc->mc_intr) {
		/* set up for vectored interrupts - we will pass ctlr ptr */
		reg->intvec = mc->mc_intr->v_vec;
		*(mc->mc_intr->v_vptr) = (int)c;
	} else {
		/* use auto-vectoring */
		reg->intvec = AUTOBASE + mc->mc_intpri;
	}
}


/* 
 * corresponding to un is an md.  if this md has SCSI commands
 * queued up (md->md_utab.b_actf != NULL) and md is currently
 * not active (md->md_utab.b_active == 0), this routine queues the
 * un on its queue of devices (c->c_tab) for which to run commands
 * Note that md is active if its un is already queued up on c->c_tab,
 * or ts un is on the scsi_ctlr's disconnect queue c->c_disqtab.
 */
scustart(un)
	register struct scsi_unit *un;
{
	register struct scsi_ctlr *c = un->un_c;
	register struct mb_ctlr *mc = un->un_mc;
	register struct mb_device *md = un->un_md;
	int	s;

	/* DPRINTF("scustart:\n"); */
	s = splr(pritospl(mc->mc_intpri));
	if ((md->md_utab.b_actf != NULL) && (md->md_utab.b_active == 0)) {

		if (c->c_tab.b_actf == NULL)
			c->c_tab.b_actf = (struct buf *)un;
		else
			((struct scsi_unit *)(c->c_tab.b_actl))->un_forw = un;

		un->un_forw = NULL;
		c->c_tab.b_actl = (struct buf *)un;
		md->md_utab.b_active = 1;

	}
	(void)splx(s);
}


/*
 * Set up a scsi operation.
 */
scstart(un)
	register struct scsi_unit *un;
{
	register struct scsi_ctlr *c;
	register struct mb_device *md;
	register struct buf *bp;
	int s;

	/* return immediately if passed NULL un */
	if (un == NULL)
		return;

	/* DPRINTF("scstart:\n"); */

	/* return immediately, if the ctlr is already actively
         * running a SCSI command
         */
	s = splr(pritospl(3));
	c = un->un_c;
        if (c->c_tab.b_active != 0) {
		(void)splx(s);
                return;
	}

        /* if there are currently no SCSI devices queued to run
         * a command, then simply return.  otherwise, obtain the
         * next un for which a command should be run.
         */
        if ((un=(struct scsi_unit *)(c->c_tab.b_actf)) == NULL) {
		(void)splx(s);
                return;
	}
	md = un->un_md;
	c->c_tab.b_active = 1;

	/* 
         * put bp into the active position
         */
        bp = md->md_utab.b_actf;
        md->md_utab.b_forw = bp;

	/* only happens when called by intr */
	if (bp == NULL) {
		c->c_tab.b_active = 0;
		(void) splx(s);
		return;
	}

        /* special commands which are initiated by
         * the high-level driver, are run using
         * its special buffer, un->un_sbuf.
         * in most cases, main bus set-up has
         * already been done, so scgo can be called
         * for on-line formatting, we need to
         * call mbsetup.
         */
	if ((*un->un_ss->ss_start)(bp, un)) {
		un->un_c->c_un = un;
		c->c_tab.b_active = 1;

		if (bp == &un->un_sbuf &&
                    ((un->un_flags & SC_UNF_DVMA) == 0) &&
		    ((un->un_flags & SC_UNF_SPECIAL_DVMA) == 0)) {
			scgo(md);
			(void) splx(s);
			return;
		} else {
			(void)mbugo(md);
			(void) splx(s);
			return;
		}
	} else {
		scdone(md);
		(void) splx(s);
	}
}


/*
 * Start up a transfer
 * Called via mbgo after buffer is in memory
 */
scgo(md)
	register struct mb_device *md;
{
	register struct mb_ctlr *mc;
	register struct scsi_ctlr *c;
	register struct scsi_unit *un;
	register struct buf *bp;
	register int unit;
	register int err;
	int s;
	int type;


	/* DPRINTF("scgo:\n"); */
	s = splr(pritospl(3));
	if (md == NULL) {
		(void) splx(s);
                panic("scgo:  queueing error1\n");
        }

	mc = md->md_mc;
	c = &scctlrs[mc->mc_ctlr];

	type = TYPE(md->md_flags);
	un = (struct scsi_unit *)
	     (*scsi_unit_subr[type].ss_unit_ptr)(md);

	bp = md->md_utab.b_forw;
	if (bp == NULL) {
		EPRINTF("scgo: bp is NULL\n");
		(void) splx(s);
		return;
	}

	un->un_baddr = MBI_ADDR(md->md_mbinfo);
	c->c_tab.b_active |= 0x1;

	/*
	 * Diddle stats if necessary.
	 */
	if ((unit = un->un_md->md_dk) >= 0) {
		dk_busy |= 1<<unit;
		dk_xfer[unit]++;
		if (bp->b_flags & B_READ)
			dk_read[unit]++;
		dk_wds[unit] += bp->b_bcount >> 6;
	}

	/*
	 * Make the command block and fire it up in interrupt mode.
	 * If it fails right off the bat, call the interrupt routine
	 * to handle the failure.
	 */
	(*un->un_ss->ss_mkcdb)(un);
	if ((err=sc_cmd(c, un, 1)) != OK) {

	/* note that in the non-polled case (here) that
	 * SCSI_FAIL is not a possible return value from
	 * sc_cmd.
	 */
		if (err == FAIL) {
			EPRINTF("scgo:  sc_cmd FAILED\n");
			(*un->un_ss->ss_intr)(c, 0, SE_RETRYABLE);

		} else if (err == HARD_FAIL) {
			EPRINTF("scgo:  sc_cmd hard FAIL\n");
			(*un->un_ss->ss_intr)(c, 0, SE_FATAL);
			sc_off(c, un);
		}
	}
	(void) splx(s);
}


/*
 * Handle a polling SCSI bus interrupt.
 */
scpoll()
{
	register struct scsi_ctlr *c;
	register struct scsi_ha_reg *reg;
	register int serviced = 0;

	/* DPRINTF("scpoll:\n"); */
	for (c = scctlrs; c < &scctlrs[nsc]; c++) {
		if ((c->c_flags & SCSI_PRESENT) == 0)
			continue;
		reg = (struct scsi_ha_reg *)(c->c_reg);
		if ((reg->icr & (ICR_INTERRUPT_REQUEST | ICR_BUS_ERROR)) == 0) {
			continue;
		}
		serviced = 1;
		scintr(c);
	}
	return (serviced);
}


/*
 * Clean up queues, free resources, and start next I/O
 * after I/O finishes.  Called by mbdone after moving
 * read data from Mainbus
 */
scdone(md)
	register struct mb_device *md;
{
	register struct mb_ctlr *mc = md->md_mc;
        register struct scsi_ctlr *c = &scctlrs[mc->mc_ctlr];
        register struct scsi_unit *un;
        register struct buf *bp = md->md_utab.b_forw;
	int type;

	/* DPRINTF("scdone:\n"); */

	/* more reliable than un = c->c_un; */
	type = TYPE(md->md_flags);
	un = (struct scsi_unit *)
		(*scsi_unit_subr[type].ss_unit_ptr)(md);
        /* for on-line formatting case, call mbrelse to release
         * DVMA.
         */

	/* advance mb_device queue */
	md->md_utab.b_actf = bp->av_forw;

        /* we are done, so clear buf in active position of
         * md's queue. then call iodone to complete i/o
         */
        md->md_utab.b_forw = NULL;

        /* just got done with i/o so mark ctlr inactive
         * then advance to next md on the ctlr.
         */
        c->c_tab.b_active = 0;
        c->c_tab.b_actf = (struct buf *)(un->un_forw);

	/* advancing the ctlr's queue has removed md from
         * the queue.  if there are any more i/o for this
         * md, scustart will queue up md again. at tail.
         * first, need to mark md as inactive (not on queue)
         */
        md->md_utab.b_active = 0;
        scustart(un);

	iodone(bp);

        /* check if the ctlr's queue is NULL, and if so,
         * idle the controller, otherwesie,
		 * start up the next command on the scsi_ctlr.
         */
        if (c->c_tab.b_actf == NULL) {
                sc_idle(c);
		return;
	}

	scstart(un);
}


/*
 * Pass a command to the SCSI bus.
 * OK returned if successful.  Otherwise,
 * FAIL returned for recoverable error;
 * HARD_FAIL returned for non-recoverable error; and
 * SCSI_FAIL returned for SCSI bus timing failure.
 */
sc_cmd(c, un, intr)
	register struct scsi_ctlr *c;
	register struct scsi_unit *un;
	register int intr;
{
	register u_char *cp;
	register int i, errct = 0;
	register u_short icr_mode;
	register u_char size;
	register struct scsi_ha_reg *reg;

	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("sc_cmd:\n"); */
	do {
		/* make sure scsi bus is not continuously busy */
		for (i = SC_WAIT_COUNT; i > 0; i--) {
		        if (!(reg->icr & ICR_BUSY))
		                break;
		        DELAY(10);
		}
		if (i == 0) {
			printf("sc%d:  SCSI bus continuously busy\n",
				SCNUM(c));
			sc_reset(c, PRINT_MSG);
			sc_idle(c);
			return (FAIL);
		}

		/* select target and wait for response */
		/*+++wmb (by mjacob) for bug #1004639 */

		reg->icr = 0; /* Make sure SECONDBYTE flag is clear */

		/* ---wmb	*/
		reg->data = (1 << un->un_target) | HOST_ADDR;
		reg->icr = ICR_SELECT;

		/* target never responded to selection */
		if (sc_wait(c, SC_SHORT_WAIT, ICR_BUSY) != OK) {
			reg->data = 0;
			reg->icr = 0;
			return (HARD_FAIL);
		}
		/*
		 * may need to map between the CPU's kernel context address
		 * and the device's DVMA bus address
		 */
		SET_DMA_ADDR(reg, un->un_dma_addr);
		reg->dma_count = ~un->un_dma_count; /* hardware is funny */
		icr_mode = ICR_DMA_ENABLE;
		if (intr) {
			icr_mode |= ICR_INTERRUPT_ENABLE;
			un->un_wantint = 1;
		}

		/* Allow either byte or word transfers.
		 * Note, we trust that DMA count is even.
		 * This seems safe enough since sd & st
		 * worry about this for us.
		 */
		if  ( !(un->un_dma_addr & 0x1)) {
			icr_mode |= ICR_WORD_MODE;
		} 
#ifdef		SCSI_DEBUG
		else {
			DPRINTF("sc_cmd:  odd byte DMA address\n");
		}
#endif		SCSI_DEBUG
		reg->icr = icr_mode;

		/*
		 * Determine size of the cdb.  Since we're smart, we
		 * look at group code and guess.  If we don't
		 * recognize the group id, we use the specified
		 * cdb length.  If both are zero, use max. size
		 * of data structure.
		 */
		cp = (u_char *) &un->un_cdb;
		if ((size = cdb_size[CDB_GROUPID(un->un_cdb.cmd)]) == 0  &&
		    (size = un->un_cmd_len) == 0) {
			size = sizeof (struct scsi_cdb);
			printf("sc%d:  invalid cdb size, using size= %d\n",
				SCNUM(c), size);
		}

#ifdef		SCSI_DEBUG
		if (scsi_debug > 2) {
			printf("sc%d:  sc_cmd: target %d command=",
			    SCNUM(c), un->un_target);
			for (i = 0; i < size; i++) {
				printf("  %x", *cp++);
			}
			printf("\n");
			cp = (u_char *) &un->un_cdb;	/* restore cp */
		}
#endif		SCSI_DEBUG

		for (i = 0; i < size; i++) {
			if (sc_putbyte(c, ICR_COMMAND, *cp++) != OK) {
				errct++;
				break;
			}
		}

		if (i == size) {
			/* If not in polled I/O mode, we're done */
			if (intr != 0)
				return (OK);

			/*
			 * If in polled I/O mode, wait for cmd completion
			 * and get the result status, too.
			 */
			if ((sc_cmdwait(c) == OK)  &&
			    (sc_getstatus(c->c_un) == OK)) {
				/* DPRINTF("sc_cmd: cmd ok\n"); */
				return (OK);
			} else {
				EPRINTF("sc_cmd: cmd error\n");
				return (FAIL);
			}
		}

	} while (errct < 5);
	printf("sc%d:  sc_cmd: unrecoverable errors\n", SCNUM(c));
	return (HARD_FAIL);
}


/*
 * Handle a scsi bus interrupt.
 */
scintr(c)
	register struct scsi_ctlr *c;
{
	register struct scsi_unit *un = c->c_un;
	register struct scsi_ha_reg *reg = (struct scsi_ha_reg *)(c->c_reg);
	register int resid;
	int offset;
	u_char *mapaddr;
        u_int pv;

	/* DPRINTF("scintr:\n"); */
	if (un->un_wantint == 0) {
		if (reg->icr & ICR_BUS_ERROR) {
                        printf("sc%d:  scsi bus error (unwanted interrupt)\n",
				SCNUM(c));
		} else {
			printf("sc%d:  spurious interrupt\n", SCNUM(c));
		}
		DEBUG_DELAY(100000000);
		sc_reset(c, PRINT_MSG);
		sc_idle(c);
		return;
	}
	un->un_wantint = 0;
	resid = ~reg->dma_count & 0xffff;
	if (reg->icr & ICR_BUS_ERROR) {
		printf("sc%d:  SCSI bus error, icr= %x  resid= %d\n",
		    	SCNUM(c), (u_short)(reg->icr), resid);
		DEBUG_DELAY(100000000);
		sc_reset(c, PRINT_MSG);
		sc_idle(c);
		return;
	}
	if (reg->icr & ICR_ODD_LENGTH) {
		if (un->un_flags & SC_UNF_RECV_DATA) {
			offset = un->un_baddr + un->un_dma_count - resid;
			if (MBI_MR(offset) < dvmasize) { 
				DVMA[offset] = reg->data;
			}
			else {
#ifdef  sun3x
			   pv = btop (VME24D16_BASE + (offset & VME24D16_MASK));
#else   sun3x
			   pv = PGT_VME_D16 | btop(VME24_BASE | (offset & VME24_MASK));
#endif  sun3x
		           mapaddr = (u_char *) ((u_long) kmxtob(c->c_kment) |
                          	    (u_long) MBI_OFFSET(offset));
		           segkmem_mapin(&kseg, 
				(addr_t) (((int)mapaddr) & PAGEMASK),
                		(u_int) mmu_ptob(1), PROT_READ | PROT_WRITE, pv, 0);
			   *mapaddr = reg->data;
        		   segkmem_mapout(&kseg, 
				(addr_t)(((int)mapaddr) & PAGEMASK), 
				(u_int) mmu_ptob(1));
			}
			resid--;
		} else if (un->un_dma_count != 0) {
			resid++;
		} else {
			printf("sc%d:  odd length without xfer\n",
				SCNUM(c));
		}
		resid &= 0xffff;	/* overflow/underflow protection */
	}
	if (sc_getstatus(c->c_un) == OK) {
		/* DPRINTF("scintr: no error\n"); */
		(*c->c_un->un_ss->ss_intr)(c, resid, SE_NO_ERROR);
	} else {
		EPRINTF("scintr: error\n");
		(*c->c_un->un_ss->ss_intr)(c, resid, SE_RETRYABLE);
	}
}


/*
 * Bring a unit offline.
 */
/*ARGSUSED*/
sc_off(c, un)
	register struct scsi_ctlr *c;
	register struct scsi_unit *un;
{
	register struct mb_device *md = un->un_md;

	/*
	 * Be warned, if you take the root device offline,
	 * the system is going to have a heartattack !
	 * Note, if unit is already offline, don't bother
	 * to print error message.
	 */
	if (un->un_present) {
		printf("sc%d:  %s%d, unit offline\n", SCNUM(c),
			scsi_unit_subr[md->md_flags].ss_devname,
			SCUNIT(un->un_unit));
		un->un_present = 0;
	}
}


/*
 * Wait for a condition to be (de)asserted on the scsi bus.
 * Returns OK if successful.  Otherwise, returns
 * FAIL.
 */
static
sc_wait(c, wait_count, cond)
	register struct scsi_ctlr *c;
	register int wait_count;
{
	register int i, icr;
	register struct scsi_ha_reg *reg;
	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("sc_wait:\n"); */

	for (i = 0; i < wait_count; i++) {
		icr = reg->icr;
		if ((icr & cond) == cond) {
			return (OK);
		}
		if (icr & ICR_BUS_ERROR) {
			break;
		}
		DELAY(10);
	}
	/* DPRINTF("sc_wait: failed\n"); */
	return (FAIL);
}


/*
 * Wait for a scsi dma request to complete.
 * Called by top level drivers in order to poll for
 * command completion.
 */
sc_cmdwait(c)
	register struct scsi_ctlr *c;
{
	/* DPRINTF("sc_cmdwait:\n"); */
	if (sc_wait(c, SC_WAIT_COUNT, ICR_INTERRUPT_REQUEST) != OK) {
		printf("sc%d:  sc_cmdwait: no interrupt request\n",
			SCNUM(c));
		sc_reset(c, PRINT_MSG);
		return (SCSI_FAIL);
	} else {
		return (OK);
	}
}


/*
 * Put data byte onto the scsi bus.
 * Returns OK if successful, FAIL otherwise.
 */
static
sc_putbyte(c, bits, data)
	register struct scsi_ctlr *c;
	register u_short bits;
	register u_char data;
{
	register int icr;
	register struct scsi_ha_reg *reg;
	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("sc_putbyte:\n"); */

	if (sc_wait(c, SC_WAIT_COUNT, ICR_REQUEST) != OK) {
		printf("sc%d:  sc_cmdwait: no REQ\n", SCNUM(c));
		return (FAIL);
	}
	icr = reg->icr;
	if ((icr & ICR_BITS) != bits) {
#ifdef		SCSI_DEBUG
		printf("sc_putbyte:  error\n");
		sc_pr_icr("icr is     ", icr);
		sc_pr_icr("waiting for", bits);
#endif		SCSI_DEBUG
		return (FAIL);
	}
#ifdef	SC_PARITY
	/* Enable parity on SCSI bus... for bug #1006663 KSAM */
	reg->icr = icr | ICR_PARITY_ENABLE;
#endif	SC_PARITY
	reg->cmd_stat = data;
	return (OK);
}


/*
 * Get data from the scsi bus.
 * Returns a single byte of data, -1 if unsuccessful.
 */
static
sc_getbyte(c, bits)
	register struct scsi_ctlr *c;
{
	register int icr;
	register struct scsi_ha_reg *reg;
	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("sc_getbyte:\n"); */

	if (sc_wait(c, SC_WAIT_COUNT, ICR_REQUEST) != OK) {
		printf("sc%d:  sc_cmdwait: no REQ\n", SCNUM(c));
		return (-1);
	}
	icr = reg->icr;
	if ((icr & ICR_BITS) != bits) {
		if (bits == ICR_STATUS) {
			return (-1);	/* no more status */
		}
#ifdef		SCSI_DEBUG
		printf("sc_getbyte:  error\n");
		sc_pr_icr("icr is     ", icr);
		sc_pr_icr("waiting for", bits);
#endif		SCSI_DEBUG
		return (-1);
	}
#ifdef  SCSI_DEBUG
	/* check parity error on SCSI bus... for bug #1006663 KSAM */
	/* for normal operation, just ignored due to some peripheral */
	/* that does NOT generate parity, resulting host parity ERRROR */
	if (icr & ICR_PARITY_ERROR) {           /* read only bit */
		EPRINTF1("sc%d:  parity error detected\n", SCNUM(c));
		/* Momentarily clear parity_enable bit, which will clear error */
		icr &= ~ICR_PARITY_ENABLE;	/* disable parity */
		reg->icr = icr;
		icr |= ICR_PARITY_ENABLE;	/* re-enable parity */
		reg->icr = icr;
	}
#endif  SCSI_DEBUG
	return (reg->cmd_stat);
}


/*
 * Routine to handle abort SCSI commands which have timed out.
 */
sc_deque(c, un)
	register struct scsi_ctlr *c;
	register struct scsi_unit *un;
{
	register u_int target;
	register u_int lun;
	int s;

	/* Lock out the rest of sc till we've finished the dirty work. */
	target = un->un_target;
	lun = un->un_lun;
#ifdef 	lint
	target = target;
	lun = lun;
#endif	lint

	s = splr(pritospl(un->un_mc->mc_intpri));
	un = c->c_un;			/* get current unit */

	/*
	 * If timeout didn't occur on current I/O request,
	 * we've got a problem.
	 */
	if (un->un_c->c_tab.b_active != 1 ) {
		EPRINTF("sc_deque:  entry not found\n");
		(void) splx(s);
		return (FAIL);
	}
	EPRINTF("sc_deque:  removing failed request\n");
	(*un->un_ss->ss_intr)(c, un->un_dma_count, SE_TIMEOUT);
	(void) splx(s);
	return (OK);
}


/*
 * Return residual count for dma.
 */
sc_dmacnt(c)
	register struct scsi_ctlr *c;
{
	register struct scsi_ha_reg *reg;
	reg = (struct scsi_ha_reg *)(c->c_reg);

	return ((u_short)(~reg->dma_count));
}


/*
 * Flush current I/O request after SCSI bus reset.
 * If no current I/O request, forget it.
 */
static
sc_idle(c)
	register struct scsi_ctlr *c;
{
	register struct scsi_unit *un;
	un = c->c_un;
	/* DPRINTF("sc_idle:\n"); */

	if (un->un_c->c_tab.b_active) {
		/*
		 * Since we reset the bus, we should inform
		 * the top level driver that it's dead.
		 */
		EPRINTF("sc_idle:  returning fatal error\n");
		(*c->c_un->un_ss->ss_intr)(c, 0, SE_RETRYABLE);
	}
}


/*
 * Reset SCSI control logic and bus.
 */
sc_reset(c, msg_enable)
	register struct scsi_ctlr *c;
	register int msg_enable;
{
	register struct scsi_ha_reg *reg;
	reg = (struct scsi_ha_reg *)(c->c_reg);

	/* DPRINTF("sc_reset:\n"); */
	if (msg_enable) {
		printf("sc%d:  resetting scsi bus\n", SCNUM(c));
		DEBUG_DELAY(100000000);
	}

	reg->icr = ICR_RESET;
	DELAY(1000);
	reg->icr = 0;

	/* give reset scsi devices time to recover (> 2 Sec) */
	DELAY(scsi_reset_delay);
}


/*
 * Get status bytes from scsi bus.
 */
sc_getstatus(un)
	register struct scsi_unit *un;
{
	register struct scsi_ctlr *c = un->un_c;
	register u_char *cp = (u_char *)&un->un_scb;
	register int i;
	register int b;
	register short retval = OK;
	/* DPRINTF("sc_getstatus:\n"); */

	/* get all the status bytes */
	for (i = 0;;) {
		b = sc_getbyte(c, ICR_STATUS);
		if (b < 0) {
			break;
		}
		if (i < STATUS_LEN) {
			cp[i++] = b;
		}
	}
	/* If status anything but no error, report it. */
	if (cp[0] & SCB_STATUS_MASK)
		retval = FAIL;		/* cmd completed with error */

	/* get command complete message */
	b = sc_getbyte(c, ICR_MESSAGE_IN);
	if (b != SC_COMMAND_COMPLETE) {
#ifdef		SCSI_DEBUG
		if (scsi_debug) {
			printf("sc_getstatus:  Invalid SCSI message: %x\n", b);
			printf("  Status bytes= ");
			for (b = 0; b < i; b++) {
				printf(" %x", cp[b]);
			}
			printf("\n");
		}
#endif		SCSI_DEBUG
		return (FAIL);
	}

#ifdef	SCSI_DEBUG
	if (scsi_debug > 1) {
		printf("sc_getstatus:  status=");
		for (b = 0; b < i; b++) {
			printf(" %x", cp[b]);
		}
		printf("\n");
	}
#endif	SCSI_DEBUG
	return (retval);
}


#ifdef	SCSI_DEBUG
/*
 * Print out the scsi host adapter interface control register.
 */
static
sc_pr_icr(cp, i)
	register char *cp;
{
	printf("\t%s: %x ", cp, i);
	if (i & ICR_PARITY_ERROR)
		printf("Parity err ");
	if (i & ICR_BUS_ERROR)
		printf("Bus err ");
	if (i & ICR_ODD_LENGTH)
		printf("Odd len ");
	if (i & ICR_INTERRUPT_REQUEST)
		printf("Int req ");
	if (i & ICR_REQUEST) {
		printf("Req ");
		switch (i & ICR_BITS) {
		case 0:
			printf("Data out ");
			break;
		case ICR_INPUT_OUTPUT:
			printf("Data in ");
			break;
		case ICR_COMMAND_DATA:
			printf("Command ");
			break;
		case ICR_COMMAND_DATA | ICR_INPUT_OUTPUT:
			printf("Status ");
			break;
		case ICR_MESSAGE | ICR_COMMAND_DATA:
			printf("Msg out ");
			break;
		case ICR_MESSAGE | ICR_COMMAND_DATA | ICR_INPUT_OUTPUT:
			printf("Msg in ");
			break;
		default:
			printf("DCM: %x ", i & ICR_BITS);
			break;
		}
	}
	if (i & ICR_PARITY)
		printf("Parity ");
	if (i & ICR_BUSY)
		printf("Busy ");
	if (i & ICR_SELECT)
		printf("Sel ");
	if (i & ICR_RESET)
		printf("Reset ");
	if (i & ICR_PARITY_ENABLE)
		printf("Par ena ");
	if (i & ICR_WORD_MODE)
		printf("Word mode ");
	if (i & ICR_DMA_ENABLE)
		printf("Dma ena ");
	if (i & ICR_INTERRUPT_ENABLE)
		printf("Int ena ");
	printf("\n");
}


/*
 * Print out status completion block.
 */
static
sc_pr_scb(scb)
	register struct scsi_scb *scb;
{
	register u_char *cp;

	cp = (u_char *) scb;
	printf("scb: %x", cp[0]);
	if (scb->ext_st1) {
		printf(" %x", cp[1]);
		if (scb->ext_st2) {
			printf(" %x", cp[2]);
		}
	}
	if (scb->is) {
		printf(" intermediate status");
	}
	if (scb->busy) {
		printf(" busy");
	}
	if (scb->cm) {
		printf(" condition met");
	}
	if (scb->chk) {
		printf(" check");
	}
	if (scb->ext_st1 && scb->ha_er) {
		printf(" host adapter detected error");
	}
	printf("\n");
}
#endif	SCSI_DEBUG
#endif	NSC > 0
