/*
 *  DMC-11 device driver
 *
 *  NOTE:
 *	This driver uses the old, in-address-space abuffers.
 *	Since those buffers no longer exist, this driver would
 *	need to be converted to use its own, local buffers
 *	before it could be used.
 */

/*
 *	SCCS id	@(#)dmc.c	2.1 (Berkeley)	8/5/83
 */

#include "param.h"
#include <sys/conf.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/buf.h>
#include <sys/proc.h>

#define CLOSE           /* include ioctl(NETCLOSE,,) */

#define DMCPRI  PZERO-1
#define NDMC    1

#define BUFSIZ  256     /* size of buffers */
#define NRBUFS  (int)(BSIZE/BUFSIZ-1) /* # of extra receive buffer headers */
#define NTBUFS  1       /* number of transmit buffer headers */
        /*
         * note: dmc only allows 7 receive, 7 transmit buffers to be queued,
         * thus NRBUFS and NTBUFS must be <= 7.
         * On an 11/70 the Unibus map must be allocated; if mapalloc() is
         * used, only one buffer can have the map.  NTBUFS might as
         * well be 1 so the next (only) write outstanding is the one
         * with the map allocated.
         */
#define LOOPCNT 10      /* max time waiting for ~RDYI */
#define MAXERRS 7       /* number of DCK or TIMEOUTs before fail */

struct device {
        char    inctrl;
        char    mstrctrl;
        char    outctrl;
        char    bsel3;
        unsigned bufad;
        unsigned port1;
};

struct device *dmcaddr[] = {
        (struct device *)0160200,
        (struct device *)0160210
};


struct buf dmctbufs[NDMC][NTBUFS];
struct buf dmcrbufs[NDMC][NRBUFS];

struct dmcinfo {
        struct buf *bp; /* system receive buffer to relse */
        caddr_t addr;           /* saved addr for dmcitrans */
        unsigned cnt;           /* saved xmem + count for dmcitrans */
} dmcinfo[NDMC];

struct buf dmcutab[NDMC];

#define MCLR    0100    /* mstrctrl -- master clear */
#define RDYI    0200    /* inctrl -- port avail */
#define RQI     0040    /* inctrl -- request input transfer */
#define IEI     0100    /* inctrl -- interrupt enable, input */
#define RDYO    0200    /* outctrl -- output transfer ready */
#define IEO     0100    /* outctrl -- output interrupt enable */
#define TRBIT   0004    /* inctrl, outctrl -- mask for t/r flag */
#define TFLAG   0000    /* inctrl, outctrl -- flag indicates transmission */
#define RFLAG   0004    /* inctrl, outctrl -- flag indicates reception */
#define TTYPE   0003    /* inctrl, outctrl -- mask for transfer type */
#define BACC    0000    /* inctrl, outctrl -- buf addr, char count */
#define CTRL    0001    /* inctrl, outctrl -- control in/out */
#define INVAL   0002    /* inctrl -- invalid (to shut down gracefully) */
#define BASEI   0003    /* inctrl -- base in */

#define FDUP    00000   /* port1 (control in) -- full duplex operation */
#define HDUP    02000   /* port1 (control in) -- half duplex operation */

        /* control out error states: */
#define FATAL   01630   /* fatal errors */
#define DCK     00001   /* data check */
#define TIMEOUT 00002   /* no response, 21 sec. */
#define ORUN    00004   /* overun error (waiting for recv buffer) */
#define MAINT   00010   /* maintenance mesg recd */
#define DLOST   00020   /* data lost (recv buffer too small) */
#define DISCON  00100   /* DSR transition */
#define RESTART 00200   /* DDCMP start received */
#define NXM     00400   /* non-existent memory */
#define PROCERR 01000   /* procedure error */

#define OPEN    001
#define CLOSING 002     /* closing gracefully */
#define STATE   003
#define ERROR   004     /* fatal line error */
#define TOLD    010     /* console message already printed */
#define R_WAITING 0100  /* read waiting */
#define W_WAITING 0200  /* write waiting */
#define TR_WAITING 0400 /* itrans waiting */
#define WAITING   0700

#define EXTSHFT 14

#ifndef hiint
#define hiint(x) (int)(x>>16)
#define loint(x) (int)(x&177777)
#endif


#define tbufq   b_forw          /* empty transmit buffer headers */
#define outbufq b_back          /* filled transmit buffers */
#define rbufq   av_forw         /* empty receive buffers */
#define inbufq  av_back         /* filled receive buffers */

/* redeclarations for dmcutab: */

#define xcnt    b_resid         /* saved port1 (xmem+char cnt) */
#define state   b_flags


int     dmcbase[NDMC][64];

dmcopen(dev)
dev_t dev;
{
        register int unit;
        register struct buf *dp, *bp, *hp;
        long base;
        if ((unit=minor(dev)) >= NDMC || unit < 0) {
                u.u_error = ENXIO;
                return;
        }
        dp = &dmcutab[unit];
        if (dp->state & OPEN)
                return;

        dp->state = 0;

        dp->rbufq = bp = getiblk();
        dmcinfo[unit].bp = bp;
        bp->b_dev = dev;
        dmcinit(unit);

        dp->tbufq = dmctbufs[unit];
        for (hp = dp->tbufq; hp < &dmctbufs[unit][NTBUFS-1]; hp++)
                hp->tbufq = hp + 1;
        hp->tbufq = 0;
        dp->outbufq = 0;
        dp->state = OPEN;
}

dmcclose(dev)
dev_t dev;
{
        register int unit;
        register struct buf *dp;

        unit = minor(dev);
        dp = &dmcutab[unit];
        dmcaddr[unit]->outctrl &= ~IEO;
        brelse(dmcinfo[unit].bp);
        dp->state = 0;
}

dmcread(dev)
dev_t dev;
{
        register struct buf *dp;
        register struct buf *bp, *pbp;
        register int unit;

        unit=minor(dev);
        dp = &dmcutab[unit];

        if ((!(dp->state & OPEN))       /* last read after NETCLOSE */
            || (u.u_count < bp->b_bcount)) {
                u.u_error = EINVAL;
                return;
        }

        for (;;) {
                (void) _spl5();
                if ((bp = dp->inbufq) != 0)
                        break;
                if (dp->state & ERROR) {
                        u.u_error = EIO;
                        dp->state &= ~(ERROR|TOLD);
                        dmcinit(unit);
			(void) _spl0();
                        return;
                }
                dp->state |= R_WAITING;
                sleep((caddr_t)dp, DMCPRI);
        }
        (void) _spl0();

        if (bp->b_bcount > 0) {
                iomove(mapin(bp), bp->b_bcount, B_READ);
                mapout(bp);
        }
        dp->inbufq = bp->inbufq;
        for (pbp=dp; pbp->rbufq; pbp=pbp->rbufq)
                ;
        pbp->rbufq = bp;
        bp->rbufq = 0;
        dmcitrans(unit,BACC | RFLAG, bp->b_un.b_addr, bp->xcnt);
}

dmcwrite(dev)
dev_t dev;
{
        register struct buf *bp, *dp, *pbp;
        register int unit;

        if (chkphys(BYTE))
                return;
        unit = minor(dev);
        if (u.u_count > BUFSIZ) {
                u.u_error = EINVAL;
                return;
        }
        dp = &dmcutab[unit];

        for (;;) {
                (void) _spl5();
                if (dp->state & ERROR) {
                        u.u_error = EIO;
                        /*
                         * If it is not necessary to report all errors
                         * on reads (presumably to one read process)
                         * the error could be cleared here.  However,
                         * dmcinit should not be called until the outbufq
                         * is empty, which is easiest to do by reading
                         * until a read returns -1, at which point the error
                         * is cleared.  Otherwise, do:
                         * dp->state &= ~(ERROR|TOLD);
                         * dmcinit(unit);
                         */
			(void) _spl0();
                        return;
                }
                if (bp = dp->tbufq)
                        break;
                dp->state |= W_WAITING;
                sleep((caddr_t)dp, DMCPRI);
        }
        (void) _spl0();
        physbuf(bp,dev,B_WRITE);
        u.u_procp->p_flag |= SLOCK;
        mapalloc(bp);
        dp->tbufq = bp->tbufq;
        for (pbp=dp; pbp->outbufq; pbp=pbp->outbufq)
                ;
        pbp->outbufq = bp;
        bp->outbufq = 0;

        dmcitrans(unit, BACC|TFLAG, bp->b_un.b_addr,
                ((unsigned)bp->b_xmem<<EXTSHFT)|bp->b_bcount);
        iowait(bp);
        u.u_count = 0;
        bp->b_flags = 0;
        u.u_procp->p_flag &= ~SLOCK;
        bp->tbufq = dp->tbufq;
        dp->tbufq = bp;
        if (dp->state & W_WAITING) {
                dp->state &= ~W_WAITING;
                wakeup((caddr_t)dp);
        }
}

#ifdef CLOSE
#define NETCLOSE (('n'<<8)|1)

dmcioctl(dev,cmd,addr,flag)
dev_t dev;
caddr_t addr;
{
        register unit;
        unit = minor(dev);
        if (cmd == NETCLOSE) {
                dmcutab[unit].state |= CLOSING | ERROR;
                dmcitrans(unit,INVAL,(caddr_t)0,0);
        }
        else u.u_error = ENOTTY;
}
#endif

dmcoint(unit)
{
        register struct device *dmc;
        register struct buf *bp, *dp, *pbp;
        int errflgs;

        dp = &dmcutab[unit];
        dmc = dmcaddr[unit];
        if ((dmc->outctrl & TTYPE) == BACC) {
                if ((dmc->outctrl & TRBIT) == RFLAG) {
                        bp = dp->rbufq;
#ifdef DIAG
                        if (bp == 0) {
                                printf("dmc rbufq missing\n");
                                goto error;
                        }
                        if (bp->b_un.b_addr != dmc->bufad) {
                                printf("dmc receive out of order\n");
                                goto error;
                        }
#endif
                        bp->b_bcount = dmc->port1;
                        for (pbp=dp; pbp->inbufq; pbp=pbp->inbufq)
                                ;
                        pbp->inbufq = bp;
                        bp->inbufq = 0;
                        dp->rbufq = bp->rbufq;
                        if (dp->state & R_WAITING) {
                                dp->state &= ~R_WAITING;
                                wakeup((caddr_t)dp);
                        }
                } else {
                        bp = dp->outbufq;
#ifdef DIAG
                        if  (bp == 0) {
                                printf("dmc outbufq missing\n");
                                goto error;
                        }
                        if (bp->b_un.b_addr != dmc->bufad) {
                                printf("dmc transmit out of order\n");
                                goto error;
                        }
#endif
                        dp->outbufq = bp->outbufq;
                        iodone(bp);
                }
                dp->state &= ~(ERROR | TOLD);
                dp->b_errcnt = 0;
        } else {
                errflgs = dmc->port1;
#ifdef CLOSE
                if ((errflgs & PROCERR) && (dp->state & CLOSING)){
                        /* shutting down gracefully */
                        dp->state &= ~(CLOSING|OPEN);
                        dmc->outctrl &= ~IEO;
                        goto error;
                }
#endif
                if (((errflgs & (FATAL|TIMEOUT)) || ((errflgs&DCK) &&
                    ++dp->b_errcnt>MAXERRS)) && ((dp->state & TOLD)==0)) {
                        printf("DMC/%d error=%o\n",unit,errflgs);
                        goto error;
                }
#ifdef DIAG
                else if (dp->state & TOLD == 0) {
                        printf("dmc nonfatal error %o\n",errflgs);
                        dp->state &= TOLD;
                }
#endif
        }
        dmc->outctrl &= ~RDYO;
        return;
error:
        dmc->outctrl &= ~RDYO;
        dp->state |= ERROR|TOLD;
        for (bp=dp->outbufq; bp; bp=bp->outbufq) {
                bp->b_flags = B_ERROR | B_DONE;
                wakeup((caddr_t)bp);
        }
        if (dp->state & WAITING) {
                dp->state &= ~WAITING;
                wakeup((caddr_t)dp);
        }
}

dmciint(unit)
{
        register struct buf *dp;
        register struct device *dmc;
        register struct dmcinfo *infp;

        dp = &dmcutab[unit];
        dmc = dmcaddr[unit];
        infp = &dmcinfo[unit];

        if (dp->b_active) {
                dmc->bufad = infp->addr;
                dmc->port1 = infp->cnt;
                dmc->inctrl &= ~RQI;
                dp->b_active = 0;
                if (dp->state & TR_WAITING) {
                        dp->state &= ~TR_WAITING;
                        wakeup((caddr_t)dp);
                }
#ifdef DIAG
        } else {
                printf("bad DMC/%d input interrupt\n",unit);
                dp->state |= TOLD;
#endif
        }
}

dmcinit(unit)
{
        register struct buf *bp, *dp, *hp;
        long base;

        dp = &dmcutab[unit];
        dmcaddr[unit]->mstrctrl |= MCLR;
        dmcitrans(unit,BASEI, (caddr_t)dmcbase[unit], 0);
        dmcitrans(unit,CTRL, (caddr_t)0, FDUP);
        dmcaddr[unit]->outctrl |= IEO;

        bp = dmcinfo[unit].bp;
        base = (long)bp->b_un.b_addr + (((long)bp->b_xmem)<<16);
        bp->xcnt = ((unsigned)hiint(base)<<EXTSHFT) | BUFSIZ;
        dmcitrans(unit, BACC|RFLAG,  bp->b_un.b_addr, bp->xcnt);
        for (hp = &dmcrbufs[unit][0]; hp <= &dmcrbufs[unit][NRBUFS-1]; hp++) {
                bp->rbufq = hp;
                base += BUFSIZ;
                hp->b_un.b_addr = (caddr_t)loint(base);
                hp->b_xmem = (char)hiint(base);
                hp->xcnt = ((unsigned)hiint(base)<<EXTSHFT) | BUFSIZ;
                dmcitrans(unit,BACC | RFLAG, hp->b_un.b_addr, hp->xcnt);
                bp = hp;
        }
        bp->rbufq = 0;
        dp->b_errcnt = 0;
        dp->inbufq = 0;
}

dmcitrans(unit, ttype, addr, cnt)
caddr_t addr;
unsigned cnt;
{
        register struct buf *dp;
        register struct device *dmc;
        register int i;
        register struct dmcinfo *infp;

        dp = &dmcutab[unit];
        infp = &dmcinfo[unit];
        for (;;) {
                if (dp->state & ERROR)
                        return;
                (void) _spl5();
                if (!dp->b_active)
                        break;
                dp->state |= TR_WAITING;
                sleep((caddr_t)dp,DMCPRI);
        }
        (void) _spl0();
        infp->addr = addr;
        infp->cnt = cnt;
        dp->b_active++;

        /* start transfer */
        i = 0;
        dmc = dmcaddr[unit];
        while (dmc->inctrl & RDYI)
                if (++i > LOOPCNT) {
                        /*
                         * This shouldn't happen; if it does, it
                         * will cause a spurious initialization.
                         */
#ifdef DIAG
                        printf("dmc missed RDYI going off\n");
#endif
                        dp->state |= ERROR;
                        return;
                }
        dmc->inctrl |= RQI|ttype|IEI;
}
