+/* ik.c 1.1 86/11/29 */
+
+#include "ik.h"
+#if NIK > 0
+/*
+ * PS300/IKON DR-11W Device Driver.
+ */
+#include "param.h"
+#include "buf.h"
+#include "cmap.h"
+#include "conf.h"
+#include "dir.h"
+#include "dkstat.h"
+#include "map.h"
+#include "systm.h"
+#include "user.h"
+#include "vmmac.h"
+#include "proc.h"
+#include "uio.h"
+#include "kernel.h"
+
+#include "../tahoe/mtpr.h"
+#include "../tahoe/pte.h"
+
+#include "../tahoevba/vbavar.h"
+#include "../tahoevba/ikreg.h"
+#include "../tahoevba/psreg.h"
+#include "../tahoevba/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; /* 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;
+
+ if (badaddr(reg, 2))
+ return (0);
+ ik = (struct ikdevice *)reg;
+ ik->ik_vec = --vi->ui_hd->vh_lastiv;
+ /*
+ * 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;
+ if (!psreset(ik, IKCSR_IENA) || br == 0)
+ 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;
+ return (dioread(ik) == PS_RESET);
+}
+
+ikattach(vi)
+ struct vba_device *vi;
+{
+ register struct ikdevice *ik;
+
+ ikinfo[vi->ui_unit] = vi;
+ ik = (struct ikdevice *)vi->ui_addr;
+ ik->ik_vec = IKVEC_BASE + vi->ui_unit; /* interrupt vector */
+ ik->ik_mod = IKMOD_STD; /* address modifier */
+ 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 != -1 && sc->is_uid != u.u_uid)
+ return (EBUSY);
+ if (sc->is_uid == -1) {
+ sc->is_buf = (caddr_t)wmemall(vmemall, PS_MAXDMA);
+ if (sc->is_buf == 0)
+ return (ENOMEM);
+ sc->is_timeout = 0;
+ timeout(iktimer, 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, unit);
+ wmemfree(sc->is_buf, PS_MAXDMA);
+ sc->is_buf = 0;
+ 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;
+ if (sc->is_buf) {
+ wmemfree(sc->is_buf, PS_MAXDMA);
+ sc->is_buf = 0;
+ }
+ untimeout(iktimer, unit);
+}
+
+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],
+ 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(cp, P1DC);
+ if (!error)
+ error = copyout((caddr_t)&ap[1], iov->iov_base,
+ 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;
+
+ 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);
+ biowait(bp);
+ if (bp->b_flags&B_WANTED)
+ wakeup((caddr_t)bp);
+ bp->b_flags &= B_ERROR;
+ return (geterror(bp));
+}
+
+/*
+ * 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;
+ register struct psalist *ap;
+ 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 (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, bp->b_command))
+ goto bad;
+ if (bp->b_command < PS_DETACH) {
+ if (sc->is_error = diowrite(ik, bp->b_bcount))
+ goto bad;
+ if (sc->is_error = diowrite(ik, 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, 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) { \
+ int v; \
+\
+ 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;
+ u_short i, 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_ERROR, "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, unit, hz);
+}
+
+/*
+ * Handshake read from DR300.
+ */
+dioread(ik)
+ register struct ikdevice *ik;
+{
+ register int timeout;
+ u_short data;
+
+ for (timeout = ikdiotimo; timeout > 0; timeout--)
+ 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 timeout;
+ 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 (timeout = ikdiotimo; timeout > 0; timeout--) {
+ 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, sc->is_buf, 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