4.4BSD/usr/src/sys/tahoe/vba/ik.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright (c) 1986 The Regents of the University of California.
 * All rights reserved.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 *
 *	@(#)ik.c	7.7 (Berkeley) 12/16/90
 */

#include "ik.h"
#if NIK > 0
/*
 * PS300/IKON DR-11W Device Driver.
 */
#include "sys/param.h"
#include "sys/buf.h"
#include "sys/cmap.h"
#include "sys/conf.h"
#include "sys/dkstat.h"
#include "sys/map.h"
#include "sys/systm.h"
#include "sys/user.h"
#include "sys/vmmac.h"
#include "sys/proc.h"
#include "sys/kernel.h"
#include "sys/syslog.h"

#include "../include/mtpr.h"
#include "../include/pte.h"

#include "../vba/vbavar.h"
#include "../vba/ikreg.h"
#include "../vba/psreg.h"
#include "../vba/psproto.h"

int	ikprobe(), ikattach(), iktimer();
struct	vba_device *ikinfo[NIK];
long	ikstd[] = { 0 };
struct	vba_driver ikdriver = { ikprobe, 0, ikattach, 0, ikstd, "ik", ikinfo };

#define splik()		spl4()
/*
 * Devices are organized in pairs with the odd valued
 * device being used for ``diagnostic'' purposes.  That
 * is diagnostic devices don't get auto-attach'd and
 * detach'd on open-close.
 */
#define IKUNIT(dev)	(minor(dev) >> 1)
#define IKDIAG(dev)	(minor(dev) & 01)	/* is a diagnostic unit */

struct	ik_softc {
	uid_t	is_uid;		/* uid of open processes */
	u_short is_timeout;	/* current timeout (seconds) */
	u_short is_error;	/* internal error codes */
	u_short is_flags;
#define IKF_ATTACHED	0x1	/* unit is attached (not used yet) */
	union {
		u_short w[2];
		u_long	l;
	} is_nameaddr;		/* address of last symbol lookup */
	caddr_t is_buf[PS_MAXDMA];/* i/o buffer XXX */
} ik_softc[NIK];

struct	buf iktab[NIK];		/* unit command queue headers */
struct	buf rikbuf[NIK];	/* buffers for read/write operations */
struct	buf cikbuf[NIK];	/* buffers for control operations */

/* buf overlay definitions */
#define b_command	b_resid

int	ikdiotimo = PS_DIOTIMO; /* dio polling timeout */
int	iktimeout = PS_TIMEOUT; /* attention/dma timeout (in hz) */

ikprobe(reg, vi)
	caddr_t reg;
	struct vba_device *vi;
{
	register int br, cvec;		/* r12, r11 */
	register struct ikdevice *ik;

#ifdef lint
	br = 0; cvec = br; br = cvec;
	ikintr(0);
#endif
	if (badaddr(reg, 2))
		return (0);
	ik = (struct ikdevice *)reg;
	ik->ik_vec = --vi->ui_hd->vh_lastiv;
	/*
	 * Use extended non-privileged address modifier
	 * to avoid address overlap with 24-bit devices.
	 */
	ik->ik_mod = 0xf1;			/* address modifier */
	/*
	 * Try and reset the PS300.  Since this
	 * won't work if it's powered off, we
	 * can't use sucess/failure to decide
	 * if the device is present.
	 */
	br = 0;
	(void) psreset(ik, IKCSR_IENA);
	if (br == 0)				/* XXX */
		br = 0x18, cvec = ik->ik_vec;	/* XXX */
	return (sizeof (struct ikdevice));
}

/*
 * Perform a ``hard'' reset.
 */
psreset(ik, iena)
	register struct ikdevice *ik;
{

	ik->ik_csr = IKCSR_MCLR|iena;
	DELAY(10000);
	ik->ik_csr = IKCSR_FNC3|iena;
	if (!iena)
		return (dioread(ik) == PS_RESET);
	return (1);
}

ikattach(vi)
	struct vba_device *vi;
{

	ik_softc[vi->ui_unit].is_uid = -1;
}

/*
 * Open a PS300 and attach.  We allow multiple
 * processes with the same uid to share a unit.
 */
/*ARGSUSED*/
ikopen(dev, flag)
	dev_t dev;
	int flag;
{
	register int unit = IKUNIT(dev);
	register struct ik_softc *sc;
	struct vba_device *vi;
	struct ikdevice *ik;
	int reset;

	if (unit >= NIK || (vi = ikinfo[unit]) == 0 || vi->ui_alive == 0)
		return (ENXIO);
	sc = &ik_softc[unit];
	if (sc->is_uid != (uid_t)-1 && sc->is_uid != u.u_uid)
		return (EBUSY);
	if (sc->is_uid == (uid_t)-1) {
		sc->is_timeout = 0;
		timeout(iktimer, (caddr_t)unit, hz);
		/*
		 * Perform PS300 attach for first process.
		 */
		if (!IKDIAG(dev)) {
			reset = 0;
		again:
			if (ikcommand(dev, PS_ATTACH, 1)) {
				/*
				 * If attach fails, perform a hard
				 * reset once, then retry the command.
				 */
				ik = (struct ikdevice *)ikinfo[unit]->ui_addr;
				if (!reset++ && psreset(ik, 0))
					goto again;
				untimeout(iktimer, (caddr_t)unit);
				return (EIO);
			}
		}
		sc->is_uid = u.u_uid;
	}
	return (0);
}

/*ARGSUSED*/
ikclose(dev, flag)
	dev_t dev;
	int flag;
{
	int unit = IKUNIT(dev);
	register struct ik_softc *sc = &ik_softc[unit];

	if (!IKDIAG(dev))
		(void) ikcommand(dev, PS_DETACH, 1);	/* auto detach */
	sc->is_uid = -1;
	untimeout(iktimer, (caddr_t)unit);
	return (0);
}

ikread(dev, uio)
	dev_t dev;
	struct uio *uio;
{

	return (ikrw(dev, uio, B_READ));
}

ikwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{

	return (ikrw(dev, uio, B_WRITE));
}

/*
 * Take read/write request and perform physical i/o
 * transaction with PS300.  This involves constructing
 * a physical i/o request vector based on the uio
 * vector, performing the dma, and, finally, moving
 * the data to it's final destination (because of CCI
 * VERSAbus bogosities).
 */
ikrw(dev, uio, rw)
	dev_t dev;
	register struct uio *uio;
	int rw;
{
	int error, unit = IKUNIT(dev), s, wrcmd;
	register struct buf *bp;
	register struct iovec *iov;
	register struct psalist *ap;
	struct ik_softc *sc = &ik_softc[unit];

	if (unit >= NIK)
		return (ENXIO);
	bp = &rikbuf[unit];
	error = 0, iov = uio->uio_iov, wrcmd = PS_WRPHY;
	for (; !error && uio->uio_iovcnt; iov++, uio->uio_iovcnt--) { 
		/*
		 * Hack way to set PS300 address w/o doing an lseek
		 * and specify write physical w/ refresh synchronization.
		 */
		if (iov->iov_len == 0) {
			if ((int)iov->iov_base&PSIO_SYNC)
				wrcmd = PS_WRPHY_SYNC;
			uio->uio_offset = (int)iov->iov_base & ~PSIO_SYNC;
			continue;
		}
		if (iov->iov_len > PS_MAXDMA) {
			sc->is_error = PSERROR_INVALBC, error = EINVAL;
			continue;
		}
		if ((int)uio->uio_offset&01) {
			sc->is_error = PSERROR_BADADDR, error = EINVAL;
			continue;
		}
		s = splbio();
		while (bp->b_flags&B_BUSY) {
			bp->b_flags |= B_WANTED;
			sleep((caddr_t)bp, PRIBIO+1);
		}
		splx(s);
		bp->b_flags = B_BUSY | rw;
		/*
		 * Construct address descriptor in buffer.
		 */
		ap = (struct psalist *)sc->is_buf;
		ap->nblocks = 1;
		/* work-around dr300 word swapping */
		ap->addr[0] = uio->uio_offset & 0xffff;
		ap->addr[1] = uio->uio_offset >> 16;
		ap->wc = (iov->iov_len + 1) >> 1;
		if (rw == B_WRITE) {
			error = copyin(iov->iov_base, (caddr_t)&ap[1],
			    (unsigned)iov->iov_len);
			if (!error)
				error = ikcommand(dev, wrcmd,
				    iov->iov_len + sizeof (*ap));
		} else {
			caddr_t cp;
			int len;

			error = ikcommand(dev, PS_RDPHY, sizeof (*ap));
			cp = (caddr_t)&ap[1], len = iov->iov_len;
			for (; len > 0; len -= NBPG, cp += NBPG)
				mtpr(P1DC, cp);
			if (!error)
				error = copyout((caddr_t)&ap[1], iov->iov_base,
				    (unsigned)iov->iov_len);
		}
		(void) splbio();
		if (bp->b_flags&B_WANTED)
			wakeup((caddr_t)bp);
		splx(s);
		uio->uio_resid -= iov->iov_len;
		uio->uio_offset += iov->iov_len;
		bp->b_flags &= ~(B_BUSY|B_WANTED);
	}
	return (error);
}

/*
 * Perform a PS300 command.
 */
ikcommand(dev, com, count)
	dev_t dev;
	int com, count;
{
	register struct buf *bp;
	register int s;
	int error;

	bp = &cikbuf[IKUNIT(dev)];
	s = splik();
	while (bp->b_flags&B_BUSY) {
		if (bp->b_flags&B_DONE)
			break;
		bp->b_flags |= B_WANTED;
		sleep((caddr_t)bp, PRIBIO);
	}
	bp->b_flags = B_BUSY|B_READ;
	splx(s);
	bp->b_dev = dev;
	bp->b_command = com;
	bp->b_bcount = count;
	ikstrategy(bp);
	error = biowait(bp);
	if (bp->b_flags&B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags &= B_ERROR;
	return (error);
}

/*
 * Physio strategy routine
 */
ikstrategy(bp)
	register struct buf *bp;
{
	register struct buf *dp;

	/*
	 * Put request at end of controller queue.
	 */
	dp = &iktab[IKUNIT(bp->b_dev)];
	bp->av_forw = NULL;
	(void) splik();
	if (dp->b_actf != NULL) {
		dp->b_actl->av_forw = bp;
		dp->b_actl = bp;
	} else
		dp->b_actf = dp->b_actl = bp;
	if (!dp->b_active)
		ikstart(dp);
	(void) spl0();
}

/*
 * Start the next command on the controller's queue.
 */
ikstart(dp)
	register struct buf *dp;
{
	register struct buf *bp;
	register struct ikdevice *ik;
	register struct ik_softc *sc;
	u_short bc, csr;
	u_int addr;
	int unit;

loop:
	/*
	 * Pull a request off the controller queue
	 */
	if ((bp = dp->b_actf) == NULL) {
		dp->b_active = 0;
		return;
	}
	/*
	 * Mark controller busy and process this request.
	 */
	dp->b_active = 1;
	unit = IKUNIT(bp->b_dev);
	sc = &ik_softc[unit];
	ik = (struct ikdevice *)ikinfo[unit]->ui_addr;
	switch ((int)bp->b_command) {

	case PS_ATTACH:		/* logical unit attach */
	case PS_DETACH:		/* logical unit detach */
	case PS_LOOKUP:		/* name lookup */
	case PS_RDPHY:		/* physical i/o read */
	case PS_WRPHY:		/* physical i/o write */
	case PS_WRPHY_SYNC:	/* physical i/o write w/ sync */
		/*
		 * Handshake command and, optionally,
		 * byte count and byte swap flag.
		 */
		if (sc->is_error = diowrite(ik, (u_short)bp->b_command))
			goto bad;
		if (bp->b_command < PS_DETACH) {
			if (sc->is_error = diowrite(ik, (u_short)bp->b_bcount))
				goto bad;
			if (sc->is_error = diowrite(ik, (u_short)0 /* !swab */))
				goto bad;
		}
		/*
		 * Set timeout and wait for an attention interrupt.
		 */
		sc->is_timeout = iktimeout;
		return;

	case PS_DMAOUT:		/* dma data host->PS300 */
		bc = bp->b_bcount;
		csr = IKCSR_CYCLE;
		break;

	case PS_DMAIN:		/* dma data PS300->host */
		bc = bp->b_bcount;
		csr = IKCSR_CYCLE|IKCSR_FNC1;
		break;

	default:
		log(LOG_ERR, "ik%d: bad cmd %x\n", unit, bp->b_command);
		sc->is_error = PSERROR_BADCMD;
		goto bad;
	}
	/* initiate dma transfer */
	addr = vtoph((struct proc *)0, (unsigned)sc->is_buf);
	ik->ik_bahi = addr >> 17;
	ik->ik_balo = (addr >> 1) & 0xffff;
	ik->ik_wc = ((bc + 1) >> 1) - 1;	/* round & convert */
	ik->ik_pulse = IKPULSE_RATTF|IKPULSE_RDMAF;
	sc->is_timeout = iktimeout;
	ik->ik_csr = IKCSR_IENA|IKCSR_GO|csr;
	return;
bad:
	bp->b_flags |= B_ERROR;
	dp->b_actf = bp->av_forw;		/* remove from queue */
	biodone(bp);
	goto loop;
}

#define FETCHWORD(i) { \
	v = dioread(ik); \
	if (v == -1) { \
		sc->is_error = PSERROR_NAMETIMO; \
		goto bad; \
	} \
	sc->is_nameaddr.w[i] = v; \
}

/*
 * Process a device interrupt.
 */
ikintr(ikon)
	int ikon;
{
	register struct ikdevice *ik;
	register struct buf *bp, *dp;
	struct ik_softc *sc;
	register u_short data;
	int v;

	/* should go by controller, but for now... */
	if (ikinfo[ikon] == 0)
		return;
	ik = (struct ikdevice *)ikinfo[ikon]->ui_addr;
	/*
	 * Discard all non-attention interrupts.  The
	 * interrupts we're throwing away should all be
	 * associated with DMA completion.
	 */
	data = ik->ik_data;
	if ((ik->ik_csr&(IKCSR_ATTF|IKCSR_STATC)) != IKCSR_ATTF) {
		ik->ik_pulse = IKPULSE_RATTF|IKPULSE_RDMAF|IKPULSE_SIENA;
		return;
	}
	/*
	 * Fetch attention code immediately.
	 */
	ik->ik_csr = IKCSR_RATTF|IKCSR_RDMAF|IKCSR_FNC1;
	ik->ik_pulse = IKPULSE_FNC2;
	/*
	 * Get device and block structures, and a pointer
	 * to the vba_device for the device.  We receive an
	 * unsolicited interrupt whenever the PS300 is power
	 * cycled (so ignore it in that case).
	 */
	dp = &iktab[ikon];
	if ((bp = dp->b_actf) == NULL) {
		if (PS_CODE(data) != PS_RESET)		/* power failure */
			log(LOG_WARNING, "ik%d: spurious interrupt, code %x\n",
			    ikon, data);
		goto enable;
	}
	sc = &ik_softc[IKUNIT(bp->b_dev)];
	sc->is_timeout = 0;			/* disable timer */
	switch (PS_CODE(data)) {

	case PS_LOOKUP:				/* name lookup */
		if (data == PS_LOOKUP) {	/* dma name */
			bp->b_command = PS_DMAOUT;
			goto opcont;
		}
		if (data == PS_DMAOK(PS_LOOKUP)) {
			/* reenable interrupt and wait for address */
			sc->is_timeout = iktimeout;
			goto enable;
		}
		/*
		 * Address should be present, extract it one
		 * word at a time from the PS300 (yech).
		 */
		if (data != PS_ADROK(PS_LOOKUP))
			goto bad;
		FETCHWORD(0);
		FETCHWORD(1);
		goto opdone;

	case PS_WRPHY_SYNC:			/* physical i/o write w/ sync */
		if (data == PS_WRPHY_SYNC) {	/* start dma transfer */
			bp->b_command = PS_DMAOUT;
			goto opcont;
		}
		if (data != PS_DMAOK(PS_WRPHY_SYNC))
			goto bad;
		goto opdone;

	case PS_WRPHY:				/* physical i/o write */
		if (data == PS_WRPHY) { /* start dma transfer */
			bp->b_command = PS_DMAOUT;
			goto opcont;
		}
		if (data != PS_DMAOK(PS_WRPHY))
			goto bad;
		goto opdone;

	case PS_ATTACH:				/* attach unit */
	case PS_DETACH:				/* detach unit */
	case PS_ABORT:				/* abort code from ps300 */
		if (data != bp->b_command)
			goto bad;
		goto opdone;

	case PS_RDPHY:				/* physical i/o read */
		if (data == PS_RDPHY) {		/* dma address list */
			bp->b_command = PS_DMAOUT;
			goto opcont;
		}
		if (data == PS_ADROK(PS_RDPHY)) {
			/* collect read byte count and start dma */
			bp->b_bcount = dioread(ik);
			if (bp->b_bcount == -1)
				goto bad;
			bp->b_command = PS_DMAIN;
			goto opcont;
		}
		if (data == PS_DMAOK(PS_RDPHY))
			goto opdone;
		goto bad;
	}
bad:
	sc->is_error = data;
	bp->b_flags |= B_ERROR;
opdone:
	dp->b_actf = bp->av_forw;		/* remove from queue */
	biodone(bp);
opcont:
	ikstart(dp);
enable:
	ik->ik_pulse = IKPULSE_SIENA;		/* explicitly reenable */
}

/*
 * Watchdog timer.
 */
iktimer(unit)
	int unit;
{
	register struct ik_softc *sc = &ik_softc[unit];

	if (sc->is_timeout && --sc->is_timeout == 0) {
		register struct buf *dp, *bp;
		int s;

		log(LOG_ERR, "ik%d: timeout\n", unit);
		s = splik();
		/* should abort current command */
		dp = &iktab[unit];
		if (bp = dp->b_actf) {
			sc->is_error = PSERROR_CMDTIMO;
			bp->b_flags |= B_ERROR;
			dp->b_actf = bp->av_forw;	/* remove from queue */
			biodone(bp);
			ikstart(dp);
		}
		splx(s);
	}
	timeout(iktimer, (caddr_t)unit, hz);
}

/*
 * Handshake read from DR300.
 */
dioread(ik)
	register struct ikdevice *ik;
{
	register int t;
	u_short data;

	for (t = ikdiotimo; t > 0; t--)
		if ((ik->ik_csr&(IKCSR_ATTF|IKCSR_STATC)) == IKCSR_ATTF) {
			data = ik->ik_data;
			ik->ik_csr = IKCSR_RATTF|IKCSR_RDMAF|IKCSR_FNC1;
			ik->ik_pulse = IKPULSE_FNC2;
			return (data);
		}
	return (-1);
}

/*
 * Handshake write to DR300. 
 *
 * Interrupts are enabled before completing the work
 * so the caller should either be at splik or be
 * prepared to take the interrupt immediately.
 */
diowrite(ik, v)
	register struct ikdevice *ik;
	u_short v;
{
	register int t;
	register u_short csr;

top:
	/*
	 * Deposit data and generate dr300 attention
	 */
	ik->ik_data = v;
	ik->ik_csr = IKCSR_RDMAF|IKCSR_RATTF;
	ik->ik_pulse = IKPULSE_FNC2;
	for (t = ikdiotimo; t > 0; t--) {
		csr = ik->ik_csr;
#define IKCSR_DONE	(IKCSR_STATA|IKCSR_STATC)
		if ((csr&IKCSR_DONE) == IKCSR_DONE) {
			/* 
			 * Done, complete handshake by notifying dr300.
			 */
			ik->ik_csr = IKCSR_IENA;	/* ~IKCSR_FNC1 */
			ik->ik_pulse = IKPULSE_FNC2;
			return (0);
		}
		/* beware of potential deadlock with dioread */
		if ((csr&(IKCSR_ATTF|IKCSR_STATC)) == IKCSR_ATTF)
			goto top;
	}
	ik->ik_csr = IKCSR_IENA;
	return (PSERROR_DIOTIMO);
}

/*ARGSUSED*/
ikioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	int error = 0, unit = IKUNIT(dev), s;
	register struct ik_softc *sc = &ik_softc[unit];

	switch (cmd) {

	case PSIOGETERROR:		/* get error code for last operation */
		*(int *)data = sc->is_error;
		break;

	case PSIOLOOKUP: {		/* PS300 name lookup */
		register struct pslookup *lp = (struct pslookup *)data;
		register struct buf *bp;

		if (lp->pl_len > PS_MAXNAMELEN)
			return (EINVAL);
		bp = &rikbuf[unit];
		s = splbio();
		while (bp->b_flags&B_BUSY) {
			bp->b_flags |= B_WANTED;
			sleep((caddr_t)bp, PRIBIO+1);
		}
		splx(s);
		bp->b_flags = B_BUSY | B_WRITE;
		error = copyin(lp->pl_name, (caddr_t)sc->is_buf,
		    (unsigned)lp->pl_len);
		if (error == 0) {
			if (lp->pl_len&1)
				sc->is_buf[lp->pl_len] = '\0';
			error = ikcommand(dev, PS_LOOKUP, lp->pl_len);
		}
		s = splbio();
		if (bp->b_flags&B_WANTED)
			wakeup((caddr_t)bp);
		splx(s);
		bp->b_flags &= ~(B_BUSY|B_WANTED);
		lp->pl_addr = sc->is_nameaddr.l;
		break;
	}
	default:
		return (ENOTTY);
	}
	return (error);
}
#endif