[BACK]Return to vs.c CVS log [TXT][DIR] Up to [local] / sys / arch / mvme88k / dev

File: [local] / sys / arch / mvme88k / dev / vs.c (download)

Revision 1.1, Tue Mar 4 16:04:38 2008 UTC (16 years, 2 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: vs.c,v 1.64 2006/12/01 19:36:09 miod Exp $	*/

/*
 * Copyright (c) 2004, Miodrag Vallat.
 * Copyright (c) 1999 Steve Murphree, Jr.
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Van Jacobson of Lawrence Berkeley Laboratory.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * MVME328S SCSI adaptor driver
 */

/* This card lives in D16 space */
#define	__BUS_SPACE_RESTRICT_D16__

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/dkstat.h>
#include <sys/buf.h>
#include <sys/malloc.h>

#include <uvm/uvm_extern.h>

#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>

#include <machine/autoconf.h>
#include <machine/cmmu.h>
#include <machine/cpu.h>

#include <mvme88k/dev/vsreg.h>
#include <mvme88k/dev/vsvar.h>
#include <mvme88k/dev/vme.h>

int	vsmatch(struct device *, void *, void *);
void	vsattach(struct device *, struct device *, void *);
int	vs_scsicmd(struct scsi_xfer *);

struct scsi_adapter vs_scsiswitch = {
	vs_scsicmd,
	minphys,
	0,			/* no lun support */
	0,			/* no lun support */
};

struct scsi_device vs_scsidev = {
	NULL,		/* use default error handler */
	NULL,		/* do not have a start function */
	NULL,		/* have no async handler */
	NULL,		/* Use default done routine */
};

struct cfattach vs_ca = {
	sizeof(struct vs_softc), vsmatch, vsattach,
};

struct cfdriver vs_cd = {
	NULL, "vs", DV_DULL,
};

int	do_vspoll(struct vs_softc *, struct scsi_xfer *, int);
void	thaw_queue(struct vs_softc *, int);
void	thaw_all_queues(struct vs_softc *);
M328_SG	vs_alloc_scatter_gather(void);
M328_SG	vs_build_memory_structure(struct vs_softc *, struct scsi_xfer *,
	    bus_addr_t);
void	vs_chksense(struct scsi_xfer *);
void	vs_dealloc_scatter_gather(M328_SG);
int	vs_eintr(void *);
bus_addr_t vs_getcqe(struct vs_softc *);
bus_addr_t vs_getiopb(struct vs_softc *);
int	vs_initialize(struct vs_softc *);
int	vs_intr(struct vs_softc *);
void	vs_link_sg_element(sg_list_element_t *, vaddr_t, int);
void	vs_link_sg_list(sg_list_element_t *, vaddr_t, int);
int	vs_nintr(void *);
int	vs_poll(struct vs_softc *, struct vs_cb *);
void	vs_print_addr(struct vs_softc *, struct scsi_xfer *);
struct vs_cb *vs_find_queue(struct scsi_link *, struct vs_softc *);
void	vs_reset(struct vs_softc *, int);
void	vs_resync(struct vs_softc *);
void	vs_scsidone(struct vs_softc *, struct vs_cb *);

static __inline__ void vs_free(struct vs_cb *);
static __inline__ void vs_clear_return_info(struct vs_softc *);
static __inline__ paddr_t kvtop(vaddr_t);

int
vsmatch(struct device *device, void *cf, void *args)
{
	struct confargs *ca = args;
	bus_space_tag_t iot = ca->ca_iot;
	bus_space_handle_t ioh;
	int rc;

	if (bus_space_map(iot, ca->ca_paddr, S_SHORTIO, 0, &ioh) != 0)
		return 0;
	rc = badaddr((vaddr_t)bus_space_vaddr(iot, ioh), 1);
	bus_space_unmap(iot, ioh, S_SHORTIO);

	return rc == 0;
}

void
vsattach(struct device *parent, struct device *self, void *args)
{
	struct vs_softc *sc = (struct vs_softc *)self;
	struct confargs *ca = args;
	struct scsi_link *sc_link;
	struct scsibus_attach_args saa;
	int evec, bus;
	int tmp;

	/* get the next available vector for the error interrupt */
	evec = vme_findvec(ca->ca_vec);

	if (ca->ca_vec < 0 || evec < 0) {
		printf(": no more interrupts!\n");
		return;
	}
	if (ca->ca_ipl < 0)
		ca->ca_ipl = IPL_BIO;

	printf(" vec 0x%x: ", evec);

	sc->sc_paddr = ca->ca_paddr;
	sc->sc_iot = ca->ca_iot;
	if (bus_space_map(sc->sc_iot, sc->sc_paddr, S_SHORTIO, 0,
	    &sc->sc_ioh) != 0) {
		printf("can't map registers!\n");
		return;
	}

	sc->sc_ipl = ca->ca_ipl;
	sc->sc_nvec = ca->ca_vec;
	sc->sc_evec = evec;

	if (vs_initialize(sc))
		return;

	sc->sc_ih_n.ih_fn = vs_nintr;
	sc->sc_ih_n.ih_arg = sc;
	sc->sc_ih_n.ih_wantframe = 0;
	sc->sc_ih_n.ih_ipl = ca->ca_ipl;

	sc->sc_ih_e.ih_fn = vs_eintr;
	sc->sc_ih_e.ih_arg = sc;
	sc->sc_ih_e.ih_wantframe = 0;
	sc->sc_ih_e.ih_ipl = ca->ca_ipl;

	vmeintr_establish(sc->sc_nvec, &sc->sc_ih_n, self->dv_xname);
	snprintf(sc->sc_intrname_e, sizeof sc->sc_intrname_e,
	    "%s_err", self->dv_xname);
	vmeintr_establish(sc->sc_evec, &sc->sc_ih_e, sc->sc_intrname_e);

	printf("SCSI ID");

	for (bus = 0; bus < 2; bus++) {
		if (sc->sc_id[bus] < 0)
			continue;

		sc_link = &sc->sc_link[bus];
		sc_link->adapter = &vs_scsiswitch;
		sc_link->adapter_buswidth = 8;
		sc_link->adapter_softc = sc;
		sc_link->adapter_target = sc->sc_id[bus];
		sc_link->device = &vs_scsidev;
#if 0
		sc_link->luns = 1;
#endif
		sc_link->openings = NUM_IOPB / 8;
		if (bus != 0)
			sc_link->flags = SDEV_2NDBUS;

		printf("%c%d", bus == 0 ? ' ' : '/', sc->sc_id[bus]);
	}

	printf("\n");

	/*
	 * Attach all scsi units on us, watching for boot device
	 * (see device_register).
	 */
	tmp = bootpart;
	if (sc->sc_paddr != bootaddr)
		bootpart = -1;		/* invalid flag to device_register */

	for (bus = 0; bus < 2; bus++) {
		if (sc->sc_id[bus] < 0)
			continue;

		bzero(&saa, sizeof(saa));
		saa.saa_sc_link = &sc->sc_link[bus];

		bootbus = bus;
		config_found(self, &saa, scsiprint);
	}

	bootpart = tmp;		    /* restore old values */
	bootbus = 0;
}

void
vs_print_addr(struct vs_softc *sc, struct scsi_xfer *xs)
{
	if (xs == NULL)
		printf("%s: ", sc->sc_dev.dv_xname);
	else {
		sc_print_addr(xs->sc_link);

		/* print bus number too if appropriate */
		if (sc->sc_id[1] >= 0)
			printf("(bus %d) ",
			    !!(xs->sc_link->flags & SDEV_2NDBUS));
	}
}

int
do_vspoll(struct vs_softc *sc, struct scsi_xfer *xs, int canreset)
{
	int to;
	int crsw, bus;

	if (xs != NULL) {
		bus = !!(xs->sc_link->flags & SDEV_2NDBUS);
		to = xs->timeout;
		if (to == 0)
			to = 2000;
	} else {
		bus = -1;
		to = 2000;
	}

	while (((crsw = CRSW) & (M_CRSW_CRBV | M_CRSW_CC)) == 0) {
		if (to-- <= 0) {
			vs_print_addr(sc, xs);
			printf("command timeout, crsw 0x%x\n", crsw);

			if (canreset) {
				vs_reset(sc, bus);
				vs_resync(sc);
			}
			return 1;
		}
		delay(1000);
	}
	return 0;
}

int
vs_poll(struct vs_softc *sc, struct vs_cb *cb)
{
	struct scsi_xfer *xs;
	int s;
	int rc;

	xs = cb->cb_xs;
	rc = do_vspoll(sc, xs, 1);

	s = splbio();
	if (rc != 0) {
		xs->error = XS_SELTIMEOUT;
		xs->status = -1;
		xs->flags |= ITSDONE;
#if 0
		scsi_done(xs);
#endif
		vs_free(cb);
	} else
		vs_scsidone(sc, cb);
	splx(s);

	if (CRSW & M_CRSW_ER)
		CRB_CLR_ER;
	CRB_CLR_DONE;

	vs_clear_return_info(sc);
	return (COMPLETE);
}

void
thaw_queue(struct vs_softc *sc, int target)
{
	THAW(target);

	/* loop until thawed */
	while (THAW_REG & M_THAW_TWQE)
		;
}

void
thaw_all_queues(struct vs_softc *sc)
{
	int i;

	for (i = 1; i < NUM_WQ; i++)
		thaw_queue(sc, i);
}

void
vs_scsidone(struct vs_softc *sc, struct vs_cb *cb)
{
	struct scsi_xfer *xs = cb->cb_xs;
	u_int32_t len;
	int error;

	len = vs_read(4, sh_RET_IOPB + IOPB_LENGTH);
	xs->resid = xs->datalen - len;

	error = vs_read(2, sh_RET_IOPB + IOPB_STATUS);
	if ((error & 0xff) == SCSI_SELECTION_TO) {
		xs->error = XS_SELTIMEOUT;
		xs->status = -1;
	} else
		xs->status = error >> 8;

	while (xs->status == SCSI_CHECK) {
		vs_chksense(xs);
	}

	xs->flags |= ITSDONE;
	thaw_queue(sc, cb->cb_q);

	scsi_done(xs);

	vs_free(cb);
}

int
vs_scsicmd(struct scsi_xfer *xs)
{
	struct scsi_link *slp = xs->sc_link;
	struct vs_softc *sc = slp->adapter_softc;
	int flags, option;
	unsigned int iopb_len;
	bus_addr_t cqep, iopb;
	struct vs_cb *cb;
	u_int queue;
	int s;

	flags = xs->flags;
	if (flags & SCSI_POLL) {
		cb = &sc->sc_cb[0];
		cqep = sh_MCE;
		iopb = sh_MCE_IOPB;

#ifdef VS_DEBUG
		if (mce_read(2, CQE_QECR) & M_QECR_GO)
			printf("%s: master command queue busy\n",
			    sc->sc_dev.dv_xname);
#endif
		/* Wait until we can use the command queue entry. */
		while (mce_read(2, CQE_QECR) & M_QECR_GO)
			;
#ifdef VS_DEBUG
		if (cb->cb_xs != NULL) {
			printf("%s: master command not idle\n",
			    sc->sc_dev.dv_xname);
			return (TRY_AGAIN_LATER);
		}
#endif
		s = splbio();
	} else {
		s = splbio();
		cb = vs_find_queue(slp, sc);
		if (cb == NULL) {
			splx(s);
#ifdef VS_DEBUG
			printf("%s: no free queues\n", sc->sc_dev.dv_xname);
#endif
			return (TRY_AGAIN_LATER);
		}
		cqep = vs_getcqe(sc);
		if (cqep == 0) {
			splx(s);
			return (TRY_AGAIN_LATER);
		}
		iopb = vs_getiopb(sc);
	}

	queue = cb->cb_q;

	vs_bzero(iopb, IOPB_LONG_SIZE);

	/*
	 * We should only provide the iopb len if the controller is not
	 * able to compute it from the SCSI command group.
	 * Note that it has no knowledge of group 2.
	 */
	switch ((xs->cmd->opcode) >> 5) {
	case 0:
	case 1:
	case 5:
		iopb_len = 0;
		break;
	default:
		iopb_len = IOPB_SHORT_SIZE + xs->cmdlen;
		break;
	}

	bus_space_write_region_1(sc->sc_iot, sc->sc_ioh, iopb + IOPB_SCSI_DATA,
	    (u_int8_t *)xs->cmd, xs->cmdlen);

	vs_write(2, iopb + IOPB_CMD, IOPB_PASSTHROUGH);
	vs_write(2, iopb + IOPB_UNIT,
	  IOPB_UNIT_VALUE(!!(slp->flags & SDEV_2NDBUS), slp->target, slp->lun));
	vs_write(1, iopb + IOPB_NVCT, sc->sc_nvec);
	vs_write(1, iopb + IOPB_EVCT, sc->sc_evec);

	/*
	 * Since the 88k doesn't support cache snooping, we have
	 * to flush the cache for a write and flush with inval for
	 * a read, prior to starting the IO.
	 */
	dma_cachectl(pmap_kernel(), (vaddr_t)xs->data, xs->datalen,
	    flags & SCSI_DATA_IN ? DMA_CACHE_SYNC_INVAL : DMA_CACHE_SYNC);
	
	option = 0;
	if (flags & SCSI_DATA_OUT)
		option |= M_OPT_DIR;

	if (flags & SCSI_POLL) {
		vs_write(2, iopb + IOPB_OPTION, option);
		vs_write(2, iopb + IOPB_LEVEL, 0);
	} else {
		vs_write(2, iopb + IOPB_OPTION, option | M_OPT_IE);
		vs_write(2, iopb + IOPB_LEVEL, sc->sc_ipl);
	}
	vs_write(2, iopb + IOPB_ADDR, ADDR_MOD);

	vs_write(2, cqep + CQE_IOPB_ADDR, iopb);
	vs_write(1, cqep + CQE_IOPB_LENGTH, iopb_len);
	vs_write(1, cqep + CQE_WORK_QUEUE, queue);

	cb->cb_xs = xs;
	splx(s);

	if (xs->datalen != 0)
		cb->cb_sg = vs_build_memory_structure(sc, xs, iopb);
	else
		cb->cb_sg = NULL;

	vs_write(4, cqep + CQE_CTAG, (u_int32_t)cb);

	if (crb_read(2, CRB_CRSW) & M_CRSW_AQ)
		vs_write(2, cqep + CQE_QECR, M_QECR_AA | M_QECR_GO);
	else
		vs_write(2, cqep + CQE_QECR, M_QECR_GO);

	if (flags & SCSI_POLL) {
		/* poll for the command to complete */
		return vs_poll(sc, cb);
	}

	return (SUCCESSFULLY_QUEUED);
}

void
vs_chksense(struct scsi_xfer *xs)
{
	int s;
	struct scsi_link *slp = xs->sc_link;
	struct vs_softc *sc = slp->adapter_softc;
	struct scsi_sense *ss;

	/* ack and clear the error */
	if (CRSW & M_CRSW_ER)
		CRB_CLR_ER;
	CRB_CLR_DONE;
	xs->status = 0;

	/* Wait until we can use the command queue entry. */
	while (mce_read(2, CQE_QECR) & M_QECR_GO)
		;

	vs_bzero(sh_MCE_IOPB, IOPB_LONG_SIZE);
	/* This is a command, so point to it */
	ss = (void *)(bus_space_vaddr(sc->sc_iot, sc->sc_ioh) +
	    sh_MCE_IOPB + IOPB_SCSI_DATA);
	ss->opcode = REQUEST_SENSE;
	ss->byte2 = slp->lun << 5;
	ss->length = sizeof(struct scsi_sense_data);

	mce_iopb_write(2, IOPB_CMD, IOPB_PASSTHROUGH);
	mce_iopb_write(2, IOPB_OPTION, 0);
	mce_iopb_write(1, IOPB_NVCT, sc->sc_nvec);
	mce_iopb_write(1, IOPB_EVCT, sc->sc_evec);
	mce_iopb_write(2, IOPB_LEVEL, 0 /* sc->sc_ipl */);
	mce_iopb_write(2, IOPB_ADDR, ADDR_MOD);
	mce_iopb_write(4, IOPB_BUFF, kvtop((vaddr_t)&xs->sense));
	mce_iopb_write(4, IOPB_LENGTH, sizeof(struct scsi_sense_data));
	mce_iopb_write(2, IOPB_UNIT,
	  IOPB_UNIT_VALUE(!!(slp->flags & SDEV_2NDBUS), slp->target, slp->lun));

	vs_bzero(sh_MCE, CQE_SIZE);
	mce_write(2, CQE_IOPB_ADDR, sh_MCE_IOPB);
	mce_write(1, CQE_IOPB_LENGTH, 0);
	mce_write(1, CQE_WORK_QUEUE, 0);
	mce_write(2, CQE_QECR, M_QECR_GO);

	/* poll for the command to complete */
	s = splbio();
	do_vspoll(sc, xs, 1);
	xs->status = vs_read(2, sh_RET_IOPB + IOPB_STATUS) >> 8;
	splx(s);
}

bus_addr_t
vs_getcqe(struct vs_softc *sc)
{
	bus_addr_t cqep;
	int qhdp;

	qhdp = mcsb_read(2, MCSB_QHDP);
	cqep = sh_CQE(qhdp);

	if (vs_read(2, cqep + CQE_QECR) & M_QECR_GO) {
		/* should never happen */
		return 0;
	}

	if (++qhdp == NUM_CQE)
		qhdp = 0;
	mcsb_write(2, MCSB_QHDP, qhdp);

	vs_bzero(cqep, CQE_SIZE);
	return cqep;
}

bus_addr_t
vs_getiopb(struct vs_softc *sc)
{
	bus_addr_t iopb;
	int qhdp;

	/*
	 * Since we are always invoked after vs_getcqe(), qhdp has already
	 * been incremented...
	 */
	qhdp = mcsb_read(2, MCSB_QHDP);
	if (--qhdp < 0)
		qhdp = NUM_CQE - 1;

	iopb = sh_IOPB(qhdp);
	return iopb;
}

int
vs_initialize(struct vs_softc *sc)
{
	int i, msr, dbid;

	for (i = 0; i < NUM_WQ; i++)
		sc->sc_cb[i].cb_q = i;

	/*
	 * Reset the board, and wait for it to get ready.
	 * The reset signal is applied for 70 usec, and the board status
	 * is not tested until 100 usec after the reset signal has been
	 * cleared, per the manual (MVME328/D1) pages 4-6 and 4-9.
	 */

	mcsb_write(2, MCSB_MCR, M_MCR_RES | M_MCR_SFEN);
	delay(70);
	mcsb_write(2, MCSB_MCR, M_MCR_SFEN);

	delay(100);
	i = 0;
	for (;;) {
		msr = mcsb_read(2, MCSB_MSR);
		if ((msr & (M_MSR_BOK | M_MSR_CNA)) == M_MSR_BOK)
			break;
		if (++i > 5000) {
			printf("board reset failed, status %x\n", msr);
			return 1;
		}
		delay(1000);
	}

	/* initialize channels id */
	sc->sc_id[0] = csb_read(1, CSB_PID);
	sc->sc_id[1] = -1;
	switch (dbid = csb_read(1, CSB_DBID)) {
	case DBID_SCSI2:
	case DBID_SCSI:
#if 0
		printf("daughter board, ");
#endif
		sc->sc_id[1] = csb_read(1, CSB_SID);
		break;
	case DBID_PRINTER:
		printf("printer port, ");
		break;
	case DBID_NONE:
		break;
	default:
		printf("unknown daughterboard id %x, ", dbid);
		break;
	}

	CRB_CLR_DONE;
	mcsb_write(2, MCSB_QHDP, 0);

	vs_bzero(sh_CIB, CIB_SIZE);
	cib_write(2, CIB_NCQE, NUM_CQE);
	cib_write(2, CIB_BURST, 0);
	cib_write(2, CIB_NVECT, (sc->sc_ipl << 8) | sc->sc_nvec);
	cib_write(2, CIB_EVECT, (sc->sc_ipl << 8) | sc->sc_evec);
	cib_write(2, CIB_PID, 0x08);	/* XXX default */
	cib_write(2, CIB_SID, 0x08);
	cib_write(2, CIB_CRBO, sh_CRB);
	cib_write(4, CIB_SELECT, SELECTION_TIMEOUT);
	cib_write(4, CIB_WQTIMO, 4);
	cib_write(4, CIB_VMETIMO, 0 /* VME_BUS_TIMEOUT */);
	cib_write(2, CIB_ERR_FLGS, M_ERRFLGS_RIN | M_ERRFLGS_RSE);
	cib_write(2, CIB_SBRIV, (sc->sc_ipl << 8) | sc->sc_evec);
	cib_write(1, CIB_SOF0, 0x15);
	cib_write(1, CIB_SRATE0, 100 / 4);
	cib_write(1, CIB_SOF1, 0);
	cib_write(1, CIB_SRATE1, 0);

	vs_bzero(sh_MCE_IOPB, IOPB_LONG_SIZE);
	mce_iopb_write(2, IOPB_CMD, CNTR_INIT);
	mce_iopb_write(2, IOPB_OPTION, 0);
	mce_iopb_write(1, IOPB_NVCT, sc->sc_nvec);
	mce_iopb_write(1, IOPB_EVCT, sc->sc_evec);
	mce_iopb_write(2, IOPB_LEVEL, 0 /* sc->sc_ipl */);
	mce_iopb_write(2, IOPB_ADDR, SHIO_MOD);
	mce_iopb_write(4, IOPB_BUFF, sh_CIB);
	mce_iopb_write(4, IOPB_LENGTH, CIB_SIZE);

	vs_bzero(sh_MCE, CQE_SIZE);
	mce_write(2, CQE_IOPB_ADDR, sh_MCE_IOPB);
	mce_write(1, CQE_IOPB_LENGTH, 0);
	mce_write(1, CQE_WORK_QUEUE, 0);
	mce_write(2, CQE_QECR, M_QECR_GO);

	/* poll for the command to complete */
	do_vspoll(sc, NULL, 1);

	/* initialize work queues */
	for (i = 1; i < NUM_WQ; i++) {
		/* Wait until we can use the command queue entry. */
		while (mce_read(2, CQE_QECR) & M_QECR_GO)
			;

		vs_bzero(sh_MCE_IOPB, IOPB_LONG_SIZE);
		mce_iopb_write(2, WQCF_CMD, CNTR_INIT_WORKQ);
		mce_iopb_write(2, WQCF_OPTION, 0);
		mce_iopb_write(1, WQCF_NVCT, sc->sc_nvec);
		mce_iopb_write(1, WQCF_EVCT, sc->sc_evec);
		mce_iopb_write(2, WQCF_ILVL, 0 /* sc->sc_ipl */);
		mce_iopb_write(2, WQCF_WORKQ, i);
		mce_iopb_write(2, WQCF_WOPT, M_WOPT_FE | M_WOPT_IWQ);
		mce_iopb_write(2, WQCF_SLOTS, JAGUAR_MAX_Q_SIZ);
		mce_iopb_write(4, WQCF_CMDTO, 4);	/* 1 second */

		vs_bzero(sh_MCE, CQE_SIZE);
		mce_write(2, CQE_IOPB_ADDR, sh_MCE_IOPB);
		mce_write(1, CQE_IOPB_LENGTH, 0);
		mce_write(1, CQE_WORK_QUEUE, 0);
		mce_write(2, CQE_QECR, M_QECR_GO);

		/* poll for the command to complete */
		do_vspoll(sc, NULL, 1);
		if (CRSW & M_CRSW_ER) {
			printf("work queue %d initialization error 0x%x\n",
			    i, vs_read(2, sh_RET_IOPB + IOPB_STATUS));
			return 1;
		}
		CRB_CLR_DONE;
	}

	/* start queue mode */
	mcsb_write(2, MCSB_MCR, mcsb_read(2, MCSB_MCR) | M_MCR_SQM);

	/* reset all SCSI buses */
	vs_reset(sc, -1);
	/* sync all devices */
	vs_resync(sc);

	return 0;
}

void
vs_resync(struct vs_softc *sc)
{
	int bus, target;

	for (bus = 0; bus < 2; bus++) {
		if (sc->sc_id[bus] < 0)
			break;

		for (target = 0; target < 8; target++) {
			if (target == sc->sc_id[bus])
				continue;

			/* Wait until we can use the command queue entry. */
			while (mce_read(2, CQE_QECR) & M_QECR_GO)
				;

			vs_bzero(sh_MCE_IOPB, IOPB_SHORT_SIZE);
			mce_iopb_write(2, DRCF_CMD, CNTR_DEV_REINIT);
			mce_iopb_write(2, DRCF_OPTION, 0); /* prefer polling */
			mce_iopb_write(1, DRCF_NVCT, sc->sc_nvec);
			mce_iopb_write(1, DRCF_EVCT, sc->sc_evec);
			mce_iopb_write(2, DRCF_ILVL, 0);
			mce_iopb_write(2, DRCF_UNIT,
			    IOPB_UNIT_VALUE(bus, target, 0));

			vs_bzero(sh_MCE, CQE_SIZE);
			mce_write(2, CQE_IOPB_ADDR, sh_MCE_IOPB);
			mce_write(1, CQE_IOPB_LENGTH, 0);
			mce_write(1, CQE_WORK_QUEUE, 0);
			mce_write(2, CQE_QECR, M_QECR_GO);

			/* poll for the command to complete */
			do_vspoll(sc, NULL, 0);
			if (CRSW & M_CRSW_ER)
				CRB_CLR_ER;
			CRB_CLR_DONE;
		}
	}
}

void
vs_reset(struct vs_softc *sc, int bus)
{
	int b, s;

	s = splbio();

	for (b = 0; b < 2; b++) {
		if (bus >= 0 && b != bus)
			continue;

		/* Wait until we can use the command queue entry. */
		while (mce_read(2, CQE_QECR) & M_QECR_GO)
			;

		vs_bzero(sh_MCE_IOPB, IOPB_SHORT_SIZE);
		mce_iopb_write(2, SRCF_CMD, IOPB_RESET);
		mce_iopb_write(2, SRCF_OPTION, 0);	/* prefer polling */
		mce_iopb_write(1, SRCF_NVCT, sc->sc_nvec);
		mce_iopb_write(1, SRCF_EVCT, sc->sc_evec);
		mce_iopb_write(2, SRCF_ILVL, 0);
		mce_iopb_write(2, SRCF_BUSID, b << 15);

		vs_bzero(sh_MCE, CQE_SIZE);
		mce_write(2, CQE_IOPB_ADDR, sh_MCE_IOPB);
		mce_write(1, CQE_IOPB_LENGTH, 0);
		mce_write(1, CQE_WORK_QUEUE, 0);
		mce_write(2, CQE_QECR, M_QECR_GO);

		/* poll for the command to complete */
		for (;;) {
			do_vspoll(sc, NULL, 0);
			/* ack & clear scsi error condition cause by reset */
			if (CRSW & M_CRSW_ER) {
				CRB_CLR_DONE;
				vs_write(2, sh_RET_IOPB + IOPB_STATUS, 0);
				break;
			}
			CRB_CLR_DONE;
		}
	}

	thaw_all_queues(sc);

	splx(s);
}

/* free a cb; invoked at splbio */
static __inline__ void
vs_free(struct vs_cb *cb)
{
	if (cb->cb_sg != NULL) {
		vs_dealloc_scatter_gather(cb->cb_sg);
		cb->cb_sg = NULL;
	}
	cb->cb_xs = NULL;
}

/* normal interrupt routine */
int
vs_nintr(void *vsc)
{
	struct vs_softc *sc = (struct vs_softc *)vsc;
	struct vs_cb *cb;
	int s;

	if ((CRSW & CONTROLLER_ERROR) == CONTROLLER_ERROR)
		return vs_eintr(sc);

	/* Got a valid interrupt on this device */
	s = splbio();
	cb = (struct vs_cb *)crb_read(4, CRB_CTAG);

	/*
	 * If this is a controller error, there won't be a cb
	 * pointer in the CTAG field.  Bad things happen if you try
	 * to point to address 0.  But then, we should have caught
	 * the controller error above.
	 */
	if (cb != NULL)
		vs_scsidone(sc, cb);

	/* ack the interrupt */
	if (CRSW & M_CRSW_ER)
		CRB_CLR_ER;
	CRB_CLR_DONE;

	vs_clear_return_info(sc);
	splx(s);

	return 1;
}

/* error interrupts */
int
vs_eintr(void *vsc)
{
	struct vs_softc *sc = (struct vs_softc *)vsc;
	struct vs_cb *cb;
	struct scsi_xfer *xs;
	int crsw, ecode;
	int s;

	/* Got a valid interrupt on this device */
	s = splbio();

	crsw = vs_read(2, sh_CEVSB + CEVSB_CRSW);
	ecode = vs_read(1, sh_CEVSB + CEVSB_ERROR);
	cb = (struct vs_cb *)crb_read(4, CRB_CTAG);
	xs = cb != NULL ? cb->cb_xs : NULL;

	vs_print_addr(sc, xs);

	if (crsw & M_CRSW_RST) {
		printf("bus reset\n");
	} else {
		switch (ecode) {
		case CEVSB_ERR_TYPE:
			printf("IOPB type error\n");
			break;
		case CEVSB_ERR_TO:
			printf("timeout\n");
			break;
		case CEVSB_ERR_TR:
			printf("reconnect error\n");
			break;
		case CEVSB_ERR_OF:
			printf("overflow\n");
			break;
		case CEVSB_ERR_BD:
			printf("bad direction\n");
			break;
		case CEVSB_ERR_NR:
			printf("non-recoverable error\n");
			break;
		case CEVSB_ERR_PANIC:
			printf("board panic\n");
			break;
		default:
			printf("unexpected error %x\n", ecode);
			break;
		}
	}

	if (xs != NULL) {
		xs->error = XS_SELTIMEOUT;
		xs->status = -1;
		xs->flags |= ITSDONE;
		scsi_done(xs);
	}

	if (CRSW & M_CRSW_ER)
		CRB_CLR_ER;
	CRB_CLR_DONE;

	thaw_all_queues(sc);
	vs_clear_return_info(sc);
	splx(s);

	return 1;
}

static void
vs_clear_return_info(struct vs_softc *sc)
{
	vs_bzero(sh_RET_IOPB, CRB_SIZE + IOPB_LONG_SIZE);
}

/*
 * Choose the first available work queue (invoked at splbio).
 * We used a simple round-robin mechanism which is faster than rescanning
 * from the beginning if we have more than one target on the bus.
 */
struct vs_cb *
vs_find_queue(struct scsi_link *sl, struct vs_softc *sc)
{
	struct vs_cb *cb;
	static u_int last = 0;
	u_int q;

	q = last;
	for (;;) {
		if (++q == NUM_WQ)
			q = 1;
		if (q == last)
			break;

		if ((cb = &sc->sc_cb[q])->cb_xs == NULL) {
			last = q;
			return (cb);
		}
	}

	return (NULL);
}

/*
 * Useful functions for scatter/gather list
 */

M328_SG
vs_alloc_scatter_gather(void)
{
	M328_SG sg;

	MALLOC(sg, M328_SG, sizeof(struct m328_sg), M_DEVBUF, M_WAITOK);
	bzero(sg, sizeof(struct m328_sg));

	return (sg);
}

void
vs_dealloc_scatter_gather(M328_SG sg)
{
	int i;

	if (sg->level > 0) {
		for (i = 0; sg->down[i] && i < MAX_SG_ELEMENTS; i++) {
			vs_dealloc_scatter_gather(sg->down[i]);
		}
	}
	FREE(sg, M_DEVBUF);
}

void
vs_link_sg_element(sg_list_element_t *element, vaddr_t phys_add, int len)
{
	element->count.bytes = len;
	element->addrlo = phys_add;
	element->addrhi = phys_add >> 16;
	element->link = 0; /* FALSE */
	element->transfer_type = NORMAL_TYPE;
	element->memory_type = LONG_TRANSFER;
	element->address_modifier = ADRM_EXT_S_D;
}

void
vs_link_sg_list(sg_list_element_t *list, vaddr_t phys_add, int elements)
{
	list->count.scatter.gather = elements;
	list->addrlo = phys_add;
	list->addrhi = phys_add >> 16;
	list->link = 1;	   /* TRUE */
	list->transfer_type = NORMAL_TYPE;
	list->memory_type = LONG_TRANSFER;
	list->address_modifier = ADRM_EXT_S_D;
}

M328_SG
vs_build_memory_structure(struct vs_softc *sc, struct scsi_xfer *xs,
     bus_addr_t iopb)
{
	M328_SG sg;
	vaddr_t starting_point_virt, starting_point_phys, point_virt,
	point1_phys, point2_phys, virt;
	unsigned int len;
	int level;

	sg = NULL;	/* Hopefully we need no scatter/gather list */

	/*
	 * We have the following things:
	 *	virt			va of the virtual memory block
	 *	len			length of the virtual memory block
	 *	starting_point_virt	va of the physical memory block
	 *	starting_point_phys	pa of the physical memory block
	 *	point_virt		va of the virtual memory
	 *				    we are checking at the moment
	 *	point1_phys		pa of the physical memory
	 *				    we are checking at the moment
	 *	point2_phys		pa of another physical memory
	 *				    we are checking at the moment
	 */

	level = 0;
	virt = starting_point_virt = (vaddr_t)xs->data;
	point1_phys = starting_point_phys = kvtop((vaddr_t)xs->data);
	len = xs->datalen;

	/*
	 * Check if we need scatter/gather
	 */
	if (trunc_page(virt + len - 1) != trunc_page(virt)) {
		for (point_virt = round_page(starting_point_virt + 1);
		    /* if we do already scatter/gather we have to stay in the loop and jump */
		    point_virt < virt + len || sg != NULL;
		    point_virt += PAGE_SIZE) {		   /* out later */

			point2_phys = kvtop(point_virt);

			if ((point2_phys != trunc_page(point1_phys) + PAGE_SIZE) ||		   /* physical memory is not contiguous */
			    (point_virt - starting_point_virt >= MAX_SG_BLOCK_SIZE && sg)) {   /* we only can access (1<<16)-1 bytes in scatter/gather_mode */
				if (point_virt - starting_point_virt >= MAX_SG_BLOCK_SIZE) {	       /* We were walking too far for one scatter/gather block ... */
					point_virt = trunc_page(starting_point_virt+MAX_SG_BLOCK_SIZE-1);    /* So go back to the beginning of the last matching page */
					/* and generate the physical address of
					 * this location for the next time. */
					point2_phys = kvtop(point_virt);
				}

				if (sg == NULL)
					sg = vs_alloc_scatter_gather();

#if 1 /* broken firmware */
				if (sg->elements >= MAX_SG_ELEMENTS) {
					vs_dealloc_scatter_gather(sg);
					printf("%s: scatter/gather list too large\n",
					    sc->sc_dev.dv_xname);
					return (NULL);
				}
#else /* if the firmware will ever get fixed */
				while (sg->elements >= MAX_SG_ELEMENTS) {
					if (!sg->up) { /* If the list full in this layer ? */
						sg->up = vs_alloc_scatter_gather();
						sg->up->level = sg->level+1;
						sg->up->down[0] = sg;
						sg->up->elements = 1;
					}
					/* link this full list also in physical memory */
					vs_link_sg_list(&(sg->up->list[sg->up->elements-1]),
							kvtop((vaddr_t)sg->list),
							sg->elements);
					sg = sg->up;	  /* Climb up */
				}
				while (sg->level) {  /* As long as we are not a the base level */
					int i;

					i = sg->elements;
					/* We need a new element */
					sg->down[i] = vs_alloc_scatter_gather();
					sg->down[i]->level = sg->level - 1;
					sg->down[i]->up = sg;
					sg->elements++;
					sg = sg->down[i]; /* Climb down */
				}
#endif /* 1 */
				if (point_virt < virt + len) {
					/* linking element */
					vs_link_sg_element(&(sg->list[sg->elements]),
							   starting_point_phys,
							   point_virt - starting_point_virt);
					sg->elements++;
				} else {
					/* linking last element */
					vs_link_sg_element(&(sg->list[sg->elements]),
							   starting_point_phys,
							   virt + len - starting_point_virt);
					sg->elements++;
					break;			       /* We have now collected all blocks */
				}
				starting_point_virt = point_virt;
				starting_point_phys = point2_phys;
			}
			point1_phys = point2_phys;
		}
	}

	/*
	 * Climb up along the right side of the tree until we reach the top.
	 */

	if (sg != NULL) {
		while (sg->up) {
			/* link this list also in physical memory */
			vs_link_sg_list(&(sg->up->list[sg->up->elements-1]),
					kvtop((vaddr_t)sg->list),
					sg->elements);
			sg = sg->up;		       /* Climb up */
		}

		vs_write(2, iopb + IOPB_OPTION,
		    vs_read(2, iopb + IOPB_OPTION) | M_OPT_SG);
		vs_write(2, iopb + IOPB_ADDR,
		    vs_read(2, iopb + IOPB_ADDR) | M_ADR_SG_LINK);
		vs_write(4, iopb + IOPB_BUFF, kvtop((vaddr_t)sg->list));
		vs_write(4, iopb + IOPB_LENGTH, sg->elements);
		vs_write(4, iopb + IOPB_SGTTL, len);
	} else {
		/* no scatter/gather necessary */
		vs_write(4, iopb + IOPB_BUFF, starting_point_phys);
		vs_write(4, iopb + IOPB_LENGTH, len);
	}
	return sg;
}

static paddr_t
kvtop(vaddr_t va)
{
	paddr_t pa;

	pmap_extract(pmap_kernel(), va, &pa);
	/* XXX check for failure */
	return pa;
}