/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	@(#)if_dmv.c	7.7 (Berkeley) 10/22/88
 */

/*
 * DMV-11 Driver
 *
 * Qbus Sync DDCMP interface - DMV operated in full duplex, point to point mode
 *
 * Written by Bob Kridle of Mt Xinu
 * starting from if_dmc.c version 6.12 dated 4/23/86
 */

#include "dmv.h"
#if NDMV > 0

#include "param.h"
#include "systm.h"
#include "mbuf.h"
#include "buf.h"
#include "ioctl.h"		/* must precede tty.h */
#include "tty.h"
#include "protosw.h"
#include "socket.h"
#include "syslog.h"
#include "vmmac.h"
#include "errno.h"
#include "time.h"
#include "kernel.h"

#include "../net/if.h"
#include "../net/netisr.h"
#include "../net/route.h"

#ifdef	INET
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/in_var.h"
#include "../netinet/ip.h"
#endif

#include "../vax/cpu.h"
#include "../vax/mtpr.h"
#include "../vax/pte.h"
#include "../vaxuba/ubareg.h"
#include "../vaxuba/ubavar.h"
#include "if_uba.h"
#include "if_dmv.h"

int	dmv_timeout = 8;		/* timeout value */

/*
 * Driver information for auto-configuration stuff.
 */
int	dmvprobe(), dmvattach(), dmvinit(), dmvioctl();
int	dmvoutput(), dmvreset(), dmvtimeout();
struct	uba_device *dmvinfo[NDMV];
u_short	dmvstd[] = { 0 };
struct	uba_driver dmvdriver =
	{ dmvprobe, 0, dmvattach, 0, dmvstd, "dmv", dmvinfo };

/*
 * Don't really know how many buffers/commands can be queued to a DMV-11.
 * Manual doesn't say... Perhaps we can look at a DEC driver some day.
 * These numbers ame from DMC/DMR driver.
 */
#define NRCV 5
#define NXMT 3 
#define NCMDS	(NRCV+NXMT+4)	/* size of command queue */

#ifdef DEBUG
#define printd(f)   if (sc->sc_if.if_flags & IFF_DEBUG) \
	printf("DMVDEBUG: dmv%d: ", unit), printf(f)
#else
#define	printd(f)	/* nil */
#endif

/* error reporting intervals */

#define	DMV_RPRTE	 1
#define	DMV_RPTTE        1
#define	DMV_RPSTE	 1
#define DMV_RPNXM        1
#define DMV_RPMODD       1
#define DMV_RPQOVF	 1
#define DMV_RPCXRL	 1

/* number of errors to accept before trying a reset */
#define DMV_RPUNKNOWN	 10

struct  dmv_command {
	u_char	qp_mask;	/* Which registers to set up */
#define	QP_TRIB		0x01
#define	QP_SEL4		0x02
#define	QP_SEL6		0x04
#define	QP_SEL10	0x08
	u_char	qp_cmd;
	u_char	qp_tributary;
	u_short	qp_sel4;
	u_short	qp_sel6;
	u_short	qp_sel10;
	struct	dmv_command *qp_next;	/* next command on queue */
};

#define	qp_lowbufaddr	qp_

struct dmvbufs {
	int	ubinfo;		/* from uballoc */
	short	cc;		/* buffer size */
	short	flags;		/* access control */
};

#define	DBUF_OURS	0	/* buffer is available */
#define	DBUF_DMVS	1	/* buffer claimed by somebody */
#define	DBUF_XMIT	4	/* transmit buffer */
#define	DBUF_RCV	8	/* receive buffer */


/*
 * DMV software status per interface.
 *
 * Each interface is referenced by a network interface structure,
 * sc_if, which the routing code uses to locate the interface.
 * This structure contains the output queue for the interface, its address, ...
 * We also have, for each interface, a  set of 7 UBA interface structures
 * for each, which
 * contain information about the UNIBUS resources held by the interface:
 * map registers, buffered data paths, etc.  Information is cached in this
 * structure for use by the if_uba.c routines in running the interface
 * efficiently.
 */
struct dmv_softc {
	struct	ifnet sc_if;		/* network-visible interface */
	short	sc_oused;		/* output buffers currently in use */
	short	sc_iused;		/* input buffers given to DMV */
	short	sc_flag;		/* flags */
	short	sc_ipl;			/* interrupt priority */
	int	sc_ubinfo;		/* UBA mapping info for base table */
	int	sc_errors[8];		/* error counters */
#define	sc_rte	sc_errors[0]		/* receive threshhold error */
#define	sc_xte	sc_errors[1]		/* xmit threshhold error */
#define	sc_ste	sc_errors[2]		/* select threshhold error */
#define	sc_nxm	sc_errors[3]		/* non-existant memory */
#define	sc_modd	sc_errors[4]		/* modem disconnect */
#define	sc_qovf	sc_errors[5]		/* command/response queue overflow */
#define	sc_cxrl	sc_errors[6]		/* carrier loss */
#define sc_unknown sc_errors[7]		/* other errors - look in DMV manual */
	struct	dmvbufs sc_rbufs[NRCV];	/* receive buffer info */
	struct	dmvbufs sc_xbufs[NXMT];	/* transmit buffer info */
	struct	ifubinfo sc_ifuba;	/* UNIBUS resources */
	struct	ifrw sc_ifr[NRCV];	/* UNIBUS receive buffer maps */
	struct	ifxmt sc_ifw[NXMT];	/* UNIBUS receive buffer maps */
	/* command queue stuff */
	struct	dmv_command sc_cmdbuf[NCMDS];
	struct	dmv_command *sc_qhead;	/* head of command queue */
	struct	dmv_command *sc_qtail;	/* tail of command queue */
	struct	dmv_command *sc_qactive;	/* command in progress */
	struct	dmv_command *sc_qfreeh;	/* head of list of free cmd buffers */
	struct	dmv_command *sc_qfreet;	/* tail of list of free cmd buffers */
	/* end command queue stuff */
} dmv_softc[NDMV];

/* flags */
#define DMV_RESTART	0x01		/* software restart in progress */
#define DMV_ONLINE	0x02		/* device managed to transmit */
#define DMV_RUNNING	0x04		/* device initialized */


/* queue manipulation macros */
#define	QUEUE_AT_HEAD(qp, head, tail) \
	(qp)->qp_next = (head); \
	(head) = (qp); \
	if ((tail) == (struct dmv_command *) 0) \
		(tail) = (head) 

#define QUEUE_AT_TAIL(qp, head, tail) \
	if ((tail)) \
		(tail)->qp_next = (qp); \
	else \
		(head) = (qp); \
	(qp)->qp_next = (struct dmv_command *) 0; \
	(tail) = (qp)

#define DEQUEUE(head, tail) \
	(head) = (head)->qp_next;\
	if ((head) == (struct dmv_command *) 0)\
		(tail) = (head)

dmvprobe(reg, ui)
	caddr_t reg;
	struct uba_device *ui;
{
	register int br, cvec;
	register struct dmvdevice *addr = (struct dmvdevice *)reg;
	register int i;

#ifdef lint
	br = 0; cvec = br; br = cvec;
	dmvrint(0); dmvxint(0);
#endif
	addr->bsel1 = DMV_MCLR;
	for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--)
		;
	if ((addr->bsel1 & DMV_RUN) == 0) {
		printf("dmvprobe: can't start device\n" );
		return (0);
	}
	if ((addr->bsel4 != 033) || (addr->bsel6 != 0305))
	{
		printf("dmvprobe: device init failed, bsel4=%o, bsel6=%o\n",
			addr->bsel4, addr->bsel6);
		return (0);
	}
	(void) spl6();
	addr->bsel0 = DMV_RQI|DMV_IEI|DMV_IEO;
	DELAY(1000000);
	addr->bsel1 = DMV_MCLR;
	for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--)
		;
	dmv_softc[ui->ui_unit].sc_ipl = br = qbgetpri();
	return (sizeof(struct dmvdevice));
}

/*
 * Interface exists: make available by filling in network interface
 * record.  System will initialize the interface when it is ready
 * to accept packets.
 */
dmvattach(ui)
	register struct uba_device *ui;
{
	register struct dmv_softc *sc = &dmv_softc[ui->ui_unit];

	sc->sc_if.if_unit = ui->ui_unit;
	sc->sc_if.if_name = "dmv";
	sc->sc_if.if_mtu = DMVMTU;
	sc->sc_if.if_init = dmvinit;
	sc->sc_if.if_output = dmvoutput;
	sc->sc_if.if_ioctl = dmvioctl;
	sc->sc_if.if_reset = dmvreset;
	sc->sc_if.if_watchdog = dmvtimeout;
	sc->sc_if.if_flags = IFF_POINTOPOINT;
	sc->sc_ifuba.iff_flags = UBA_CANTWAIT;

	if_attach(&sc->sc_if);
}

/*
 * Reset of interface after UNIBUS reset.
 * If interface is on specified UBA, reset its state.
 */
dmvreset(unit, uban)
	int unit, uban;
{
	register struct uba_device *ui;
	register struct dmv_softc *sc = &dmv_softc[unit];

	if (unit >= NDMV || (ui = dmvinfo[unit]) == 0 || ui->ui_alive == 0 ||
	    ui->ui_ubanum != uban)
		return;
	printf(" dmv%d", unit);
	sc->sc_flag = 0;
	sc->sc_if.if_flags &= ~IFF_RUNNING;
	dmvinit(unit);
}

/*
 * Initialization of interface; reinitialize UNIBUS usage.
 */
dmvinit(unit)
	int unit;
{
	register struct dmv_softc *sc = &dmv_softc[unit];
	register struct uba_device *ui = dmvinfo[unit];
	register struct dmvdevice *addr;
	register struct ifnet *ifp = &sc->sc_if;
	register struct ifrw *ifrw;
	register struct ifxmt *ifxp;
	register struct dmvbufs *rp;
	register struct dmv_command *qp;
	struct ifaddr *ifa;
	int base;
	int s;

	addr = (struct dmvdevice *)ui->ui_addr;

	/*
	 * Check to see that an address has been set
	 * (both local and destination for an address family).
	 */
	for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next)
		if (ifa->ifa_addr.sa_family && ifa->ifa_dstaddr.sa_family)
			break;
	if (ifa == (struct ifaddr *) 0)
		return;

	if ((addr->bsel1&DMV_RUN) == 0) {
		log(LOG_CRIT, "dmvinit: dmv%d not running\n", unit);
		ifp->if_flags &= ~IFF_UP;
		return;
	}
	printd(("dmvinit\n"));
	/* initialize UNIBUS resources */
	sc->sc_iused = sc->sc_oused = 0;
	if ((ifp->if_flags & IFF_RUNNING) == 0) {
		if (if_ubaminit(
			&sc->sc_ifuba,
			ui->ui_ubanum,
		    	sizeof(struct dmv_header),
			(int)btoc(DMVMTU),
			sc->sc_ifr,
			NRCV,
			sc->sc_ifw,
			NXMT
	      	) == 0) {
			log(LOG_CRIT, "dmvinit: dmv%d can't allocate uba resources\n", unit);
			ifp->if_flags &= ~IFF_UP;
			return;
		}
		ifp->if_flags |= IFF_RUNNING;
	}
	/*
	 * Limit packets enqueued until we see if we're on the air.
	 */
	ifp->if_snd.ifq_maxlen = 3;


	/* initialize buffer pool */
	/* receives */
	ifrw = &sc->sc_ifr[0];
	for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) {
		rp->ubinfo = UBAI_ADDR(ifrw->ifrw_info);
		rp->cc = DMVMTU + sizeof (struct dmv_header);
		rp->flags = DBUF_OURS|DBUF_RCV;
		ifrw++; 
	}
	/* transmits */
	ifxp = &sc->sc_ifw[0];
	for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++) {
		rp->ubinfo = UBAI_ADDR(ifxp->ifw_info);
		rp->cc = 0;
		rp->flags = DBUF_OURS|DBUF_XMIT;
		ifxp++; 
	}

	/* set up command queues */
	sc->sc_qfreeh = sc->sc_qfreet
		 = sc->sc_qhead = sc->sc_qtail = sc->sc_qactive =
		(struct dmv_command *)0;
	/* set up free command buffer list */
	for (qp = &sc->sc_cmdbuf[0]; qp < &sc->sc_cmdbuf[NCMDS]; qp++) {
		QUEUE_AT_HEAD(qp, sc->sc_qfreeh, sc->sc_qfreet);
	}
	if(sc->sc_flag & DMV_RUNNING)
		dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_REQHS,0);
	else
		dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_ESTTRIB,0);
	dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_REQSUS,0);
	sc->sc_flag |= (DMV_RESTART|DMV_RUNNING);
	sc->sc_flag &= ~DMV_ONLINE;
	addr->bsel0 |= DMV_IEO;
}

/*
 * Start output on interface.  Get another datagram
 * to send from the interface queue and map it to
 * the interface before starting output.
 *
 * Must be called at spl 5
 */
dmvstart(dev)
	dev_t dev;
{
	int unit = minor(dev);
	register struct dmv_softc *sc = &dmv_softc[unit];
	struct mbuf *m;
	register struct dmvbufs *rp;
	register int n;

	/*
	 * Dequeue up to NXMT requests and map them to the UNIBUS.
	 * If no more requests, or no dmv buffers available, just return.
	 */
	printd(("dmvstart\n"));
	n = 0;
	for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++ ) {
		/* find an available buffer */
		if ((rp->flags & DBUF_DMVS) == 0) {
			IF_DEQUEUE(&sc->sc_if.if_snd, m);
			if (m == 0)
				return;
			/* mark it dmvs */
			rp->flags |= (DBUF_DMVS);
			/*
			 * Have request mapped to UNIBUS for transmission
			 * and start the output.
			 */
			rp->cc = if_ubaput(&sc->sc_ifuba, &sc->sc_ifw[n], m);
			if (++sc->sc_oused == 1)
				sc->sc_if.if_timer = dmv_timeout;
			dmvload(
				sc,
				DMV_BACCX,
				QP_TRIB|QP_SEL4|QP_SEL6|QP_SEL10,
				1,
				rp->ubinfo,
				(rp->ubinfo>>16)&0x3f,
				rp->cc
			);
		}
		n++;
	}
}

/*
 * Utility routine to load the DMV device registers.
 */
dmvload(sc, cmd, mask, tributary, sel4, sel6, sel10)
	register struct dmv_softc *sc;
	u_char cmd, tributary, mask;
	u_short sel4, sel6, sel10;
{
	register struct dmvdevice *addr;
	register int unit, sps;
	register struct dmv_command *qp;

	unit = sc - dmv_softc;
	printd(("dmvload: cmd=%x mask=%x trib=%x sel4=%x sel6=%x sel10=%x\n",
		(unsigned) cmd,
		(unsigned) mask,
		(unsigned) tributary,
		(unsigned) sel4,
		(unsigned) sel6,
		(unsigned) sel10
	));
	addr = (struct dmvdevice *)dmvinfo[unit]->ui_addr;
	sps = spl5();

	/* grab a command buffer from the free list */
	if ((qp = sc->sc_qfreeh) == (struct dmv_command *)0)
		panic("dmv command queue overflow");
	DEQUEUE(sc->sc_qfreeh, sc->sc_qfreet);

	/* fill in requested info */
	qp->qp_cmd = cmd;
	qp->qp_mask = mask;
	qp->qp_tributary = tributary;
	qp->qp_sel4 = sel4;
	qp->qp_sel6 = sel6;
	qp->qp_sel10 = sel10;
	
	if (sc->sc_qactive) {	/* command in progress */
		if (cmd == DMV_BACCR) {  /* supply read buffers first */
			QUEUE_AT_HEAD(qp, sc->sc_qhead, sc->sc_qtail);
		} else {
			QUEUE_AT_TAIL(qp, sc->sc_qhead, sc->sc_qtail);
		}
	} else {	/* command port free */
		sc->sc_qactive = qp;
		addr->bsel0 = (DMV_RQI|DMV_IEI|DMV_IEO);
	}
	splx(sps);
}
/*
 * DMV interface input interrupt.
 * Ready to accept another command,
 * pull one off the command queue.
 */
dmvrint(unit)
	int unit;
{
	register struct dmv_softc *sc;
	register struct dmvdevice *addr;
	register struct dmv_command *qp;
	register int n;

	addr = (struct dmvdevice *)dmvinfo[unit]->ui_addr;
	sc = &dmv_softc[unit];
	splx(sc->sc_ipl);
	printd(("dmvrint\n"));
	if ((qp = sc->sc_qactive) == (struct dmv_command *) 0) {
		log(LOG_WARNING, "dmvrint: dmv%d no command\n", unit);
		return;
	}
	while (addr->bsel2&DMV_RDI) {
		if(qp->qp_mask&QP_SEL4)
			addr->wsel4 = qp->qp_sel4;
		if(qp->qp_mask&QP_SEL6)
			addr->wsel6 = qp->qp_sel6;
		if(qp->qp_mask&QP_SEL10) {
			addr->wsel10 = qp->qp_sel10;
			qp->qp_cmd |= DMV_22BIT;
		}
		if(qp->qp_mask&QP_TRIB)
			addr->wsel2 = qp->qp_cmd|(qp->qp_tributary << 8);
		else
			addr->bsel2 = qp->qp_cmd;
		QUEUE_AT_HEAD(qp, sc->sc_qfreeh, sc->sc_qfreet);
		if ((sc->sc_qactive = sc->sc_qhead) == (struct dmv_command *)0)
			break;
		qp = sc->sc_qactive;
		DEQUEUE(sc->sc_qhead, sc->sc_qtail);
		if (addr->bsel2&DMV_RDO)
				break;
	}
	if (!sc->sc_qactive) {
		if(addr->bsel2&DMV_RDI) {
			/* clear RQI prior to last command per DMV manual */
			addr->bsel0 &= ~DMV_RQI;
			addr->wsel6 = DMV_NOP;
			addr->bsel2 = DMV_CNTRLI;
		}
		addr->bsel0 = DMV_IEO;
	}
	else /* RDO set or DMV still holding CSR */
		addr->bsel0 = (DMV_RQI|DMV_IEI|DMV_IEO);

}

/*
 * DMV interface output interrupt.
 * A transfer may have completed, check for errors.
 * If it was a read, notify appropriate protocol.
 * If it was a write, pull the next one off the queue.
 */
dmvxint(unit)
	int unit;
{
	register struct dmv_softc *sc;
	register struct ifnet *ifp;
	struct uba_device *ui = dmvinfo[unit];
	struct dmvdevice *addr;
	struct mbuf *m;
	struct ifqueue *inq;
	int sel2, sel3, sel4, sel6, sel10, pkaddr, len, s;
	register struct ifrw *ifrw;
	register struct dmvbufs *rp;
	register struct ifxmt *ifxp;
	struct dmv_header *dh;
	int off, resid;

	addr = (struct dmvdevice *)ui->ui_addr;
	sc = &dmv_softc[unit];
	splx(sc->sc_ipl);
	ifp = &sc->sc_if;

	while (addr->bsel2 & DMV_RDO) {

		sel2 = addr->bsel2;
		sel3 = addr->bsel3;
		sel4 = addr->wsel4;		/* release port */
		sel6 = addr->wsel6;
		if(sel2 & DMV_22BIT)
			sel10 = addr->wsel10;
		addr->bsel2 &= ~DMV_RDO;
		pkaddr =  sel4 | ((sel6 & 0x3f) << 16);
		printd(("dmvxint: sel2=%x sel4=%x sel6=%x sel10=%x pkaddr=%x\n",
			(unsigned) sel2,
			(unsigned) sel4,
			(unsigned) sel6,
			(unsigned) sel10,
			(unsigned) pkaddr
		));
		if((sc->sc_flag & DMV_RUNNING)==0) {
				log(LOG_WARNING, "dmvxint: dmv%d xint while down\n", unit);
				return;
		}
		switch (sel2 & 07) {
		case DMV_BDRUS:
			/*
			 * A read has completed.  
			 * Pass packet to type specific
			 * higher-level input routine.
			 */
			ifp->if_ipackets++;
			/* find location in dmvuba struct */
			ifrw= &sc->sc_ifr[0];
			for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) {
				if(rp->ubinfo == pkaddr)
					break;
				ifrw++;
			}
			if (rp >= &sc->sc_rbufs[NRCV])
				panic("dmv rcv");
			if ((rp->flags & DBUF_DMVS) == 0)
				log(LOG_WARNING, "dmvxint: dmv%d done unalloc rbuf\n", unit);

			len = (sel10&0x3fff) - sizeof (struct dmv_header);
			if (len < 0 || len > DMVMTU) {
				ifp->if_ierrors++;
				log(LOG_ERR, "dmvxint: dmv%d bad rcv pkt addr 0x%x len 0x%x\n",
				    unit, pkaddr, len);
				goto setup;
			}
			/*
			 * Deal with trailer protocol: if type is trailer
			 * get true type from first 16-bit word past data.
			 * Remember that type was trailer by setting off.
			 */
			dh = (struct dmv_header *)ifrw->ifrw_addr;
			dh->dmv_type = ntohs((u_short)dh->dmv_type);
#define dmvdataaddr(dh, off, type)	((type)(((caddr_t)((dh)+1)+(off))))
			if (dh->dmv_type >= DMV_TRAILER &&
			    dh->dmv_type < DMV_TRAILER+DMV_NTRAILER) {
				off = (dh->dmv_type - DMV_TRAILER) * 512;
				if (off >= DMVMTU)
					goto setup;		/* sanity */
				dh->dmv_type = ntohs(*dmvdataaddr(dh, off, u_short *));
				resid = ntohs(*(dmvdataaddr(dh, off+2, u_short *)));
				if (off + resid > len)
					goto setup;		/* sanity */
				len = off + resid;
			} else
				off = 0;
			if (len == 0)
				goto setup;

			/*
			 * Pull packet off interface.  Off is nonzero if
			 * packet has trailing header; dmv_get will then
			 * force this header information to be at the front,
			 * but we still have to drop the type and length
			 * which are at the front of any trailer data.
			 */
			m = if_ubaget(&sc->sc_ifuba, ifrw, len, off, ifp);
			if (m == 0)
				goto setup;
			if (off) {
				ifp = *(mtod(m, struct ifnet **));
				m->m_off += 2 * sizeof (u_short);
				m->m_len -= 2 * sizeof (u_short);
				*(mtod(m, struct ifnet **)) = ifp;
			}
			switch (dh->dmv_type) {
#ifdef INET
			case DMV_IPTYPE:
				schednetisr(NETISR_IP);
				inq = &ipintrq;
				break;
#endif
			default:
				m_freem(m);
				goto setup;
			}

			s = splimp();
			if (IF_QFULL(inq)) {
				IF_DROP(inq);
				m_freem(m);
			} else
				IF_ENQUEUE(inq, m);
			splx(s);
	setup:
			/* is this needed? */
			rp->ubinfo = UBAI_ADDR(ifrw->ifrw_info);
			dmvload(
				sc,
				DMV_BACCR,
				QP_SEL4|QP_SEL6|QP_SEL10,
				0,
				(u_short) rp->ubinfo,
				(rp->ubinfo>>16)&0x3f,
				rp->cc
			);
			break;
		case DMV_BDXSA:
			/*
			 * A write has completed, start another
			 * transfer if there is more data to send.
			 */
			ifp->if_opackets++;
			/* find associated dmvbuf structure */
			ifxp = &sc->sc_ifw[0];
			for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++) {
				if(rp->ubinfo == pkaddr)
					break;
				ifxp++;
			}
			if (rp >= &sc->sc_xbufs[NXMT]) {
				log(LOG_ERR, "dmv%d: bad packet address 0x%x\n",
				    unit, pkaddr);
				break;
			}
			if ((rp->flags & DBUF_DMVS) == 0)
				log(LOG_ERR, "dmvxint: dmv%d unallocated packet 0x%x\n",
				    unit, pkaddr);
			/* mark buffer free */
			if (ifxp->ifw_xtofree) {
				(void)m_freem(ifxp->ifw_xtofree);
				ifxp->ifw_xtofree = 0;
			}
			rp->flags &= ~DBUF_DMVS;
			if (--sc->sc_oused == 0)
				sc->sc_if.if_timer = 0;
			else
				sc->sc_if.if_timer = dmv_timeout;
			if ((sc->sc_flag & DMV_ONLINE) == 0) {
				extern int ifqmaxlen;

				/*
				 * We're on the air.
				 * Open the queue to the usual value.
				 */
				sc->sc_flag |= DMV_ONLINE;
				ifp->if_snd.ifq_maxlen = ifqmaxlen;
			}
			break;

		case DMV_CNTRLO:
			/* ACCUMULATE STATISTICS */
			switch(sel6&DMV_EEC) {
			case DMV_ORUN:
				if(sc->sc_flag & DMV_RESTART) {
					load_rec_bufs(sc);
					sc->sc_flag &= ~DMV_RESTART;
					log(LOG_INFO,
					    "dmv%d: far end on-line\n", unit);
				} else {
					log(LOG_WARNING,
					    "dmv%d: far end restart\n", unit);
					goto restart;
				}
				break;
			case DMV_RTE:
				ifp->if_ierrors++;
				if ((sc->sc_rte++ % DMV_RPRTE) == 0)
					log(LOG_WARNING,
				    "dmv%d: receive threshold error\n",
					    unit);
				break;
			case DMV_TTE:
				ifp->if_oerrors++;
				if ((sc->sc_xte++ % DMV_RPTTE) == 0)
					log(LOG_WARNING,
				    "dmv%d: transmit threshold error\n",
					    unit);
				break;
			case DMV_STE:
				if ((sc->sc_ste++ % DMV_RPSTE) == 0)
					log(LOG_WARNING,
				    "dmv%d: select threshold error\n",
					    unit);
				break;
			case DMV_NXM:
				if ((sc->sc_nxm++ % DMV_RPNXM) == 0)
					log(LOG_WARNING,
				    "dmv%d: nonexistent memory error\n",
					    unit);
				break;
			case DMV_MODD:
				if ((sc->sc_modd++ % DMV_RPMODD) == 0) {
					log(LOG_WARNING,
				    "dmv%d: modem disconnected error\n",
					    unit);
					goto restart;
				}
				break;
			case DMV_CXRL:
				if ((sc->sc_cxrl++ % DMV_RPCXRL) == 0)
					log(LOG_WARNING,
				    "dmv%d: carrier loss error\n",
					    unit);
				break;
			case DMV_QOVF:
				log(LOG_WARNING,
				    "dmv%d: response queue overflow\n",
				    unit);
				sc->sc_qovf++;
				goto restart;

			default:
				log(LOG_WARNING,
				    "dmv%d: unknown error %o\n",
				    unit, sel6&DMV_EEC);
				if ((sc->sc_unknown++ % DMV_RPUNKNOWN) == 0)
					goto restart;
				break;
			}
			break;

		case DMV_BDRUNUS:
		case DMV_BDXSN:
		case DMV_BDXNS:
			log(LOG_INFO,
			   "dmv%d: buffer disp for halted trib %o\n",
			   unit, sel2&0x7
		        );
			break;

		case DMV_MDEFO:
			if((sel6&0x1f) == 020) {
				log(LOG_INFO,
			   		"dmv%d: buffer return complete sel3=%x\n",
			   		unit, sel3);
			} else {
				log(LOG_INFO,
			   	"dmv%d: info resp sel3=%x sel4=%x sel6=%x\n",
			   	unit, sel3, sel4, sel6
		        	);
			}
			break;
			
		default:
			log(LOG_WARNING, "dmv%d: bad control %o\n",
			   unit, sel2&0x7
		        );
			break;
		}
	}
	dmvstart(unit);
	return;
restart:
	dmvrestart(unit);
}

load_rec_bufs(sc)
register struct dmv_softc *sc;
{
	register struct dmvbufs *rp;

	/* queue first NRCV buffers for DMV to fill */
	for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) {
		rp->flags |= DBUF_DMVS;
		dmvload(
			sc,
			DMV_BACCR,
			QP_TRIB|QP_SEL4|QP_SEL6|QP_SEL10,
			1,
			rp->ubinfo,
			(rp->ubinfo>>16)&0x3f,
			rp->cc
		);
		sc->sc_iused++;
	}
}

/*
 * DMV output routine.
 * Encapsulate a packet of type family for the dmv.
 * Use trailer local net encapsulation if enough data in first
 * packet leaves a multiple of 512 bytes of data in remainder.
 */
dmvoutput(ifp, m0, dst)
	register struct ifnet *ifp;
	register struct mbuf *m0;
	struct sockaddr *dst;
{
	int type, error, s;
	register struct mbuf *m = m0;
	register struct dmv_header *dh;
	register int off;

	if ((ifp->if_flags & IFF_UP) == 0) {
		error = ENETDOWN;
		goto bad;
	}

	switch (dst->sa_family) {
#ifdef	INET
	case AF_INET:
		off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len;
		if ((ifp->if_flags & IFF_NOTRAILERS) == 0)
		if (off > 0 && (off & 0x1ff) == 0 &&
		    m->m_off >= MMINOFF + 2 * sizeof (u_short)) {
			type = DMV_TRAILER + (off>>9);
			m->m_off -= 2 * sizeof (u_short);
			m->m_len += 2 * sizeof (u_short);
			*mtod(m, u_short *) = htons((u_short)DMV_IPTYPE);
			*(mtod(m, u_short *) + 1) = htons((u_short)m->m_len);
			goto gottrailertype;
		}
		type = DMV_IPTYPE;
		off = 0;
		goto gottype;
#endif

	case AF_UNSPEC:
		dh = (struct dmv_header *)dst->sa_data;
		type = dh->dmv_type;
		goto gottype;

	default:
		log(LOG_ERR, "dmvoutput, dmv%d can't handle af%d\n",
		    ifp->if_unit, dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	}

gottrailertype:
	/*
	 * Packet to be sent as a trailer; move first packet
	 * (control information) to end of chain.
	 */
	while (m->m_next)
		m = m->m_next;
	m->m_next = m0;
	m = m0->m_next;
	m0->m_next = 0;
	m0 = m;

gottype:
	/*
	 * Add local network header
	 * (there is space for a uba on a vax to step on)
	 */
	if (m->m_off > MMAXOFF ||
	    MMINOFF + sizeof(struct dmv_header) > m->m_off) {
		m = m_get(M_DONTWAIT, MT_HEADER);
		if (m == 0) {
			error = ENOBUFS;
			goto bad;
		}
		m->m_next = m0;
		m->m_off = MMINOFF;
		m->m_len = sizeof (struct dmv_header);
	} else {
		m->m_off -= sizeof (struct dmv_header);
		m->m_len += sizeof (struct dmv_header);
	}
	dh = mtod(m, struct dmv_header *);
	dh->dmv_type = htons((u_short)type);

	/*
	 * Queue message on interface, and start output if interface
	 * not yet active.
	 */
	s = splimp();
	if (IF_QFULL(&ifp->if_snd)) {
		IF_DROP(&ifp->if_snd);
		m_freem(m);
		splx(s);
		return (ENOBUFS);
	}
	IF_ENQUEUE(&ifp->if_snd, m);
	dmvstart(ifp->if_unit);
	splx(s);
	return (0);

bad:
	m_freem(m0);
	return (error);
}


/*
 * Process an ioctl request.
 */
/* ARGSUSED */
dmvioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	int s = splimp(), error = 0;
	struct mbuf *m;
	register struct dmv_softc *sc = &dmv_softc[ifp->if_unit];

	switch (cmd) {

	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;
		if ((ifp->if_flags & IFF_RUNNING) == 0)
			dmvinit(ifp->if_unit); 
		break;

	case SIOCSIFDSTADDR:
		if ((ifp->if_flags & IFF_RUNNING) == 0)
			dmvinit(ifp->if_unit); 
		break;
		
	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == 0 &&
		    sc->sc_flag & DMV_RUNNING)
			dmvdown(ifp->if_unit);
		else if (ifp->if_flags & IFF_UP &&
		    (sc->sc_flag & DMV_RUNNING) == 0)
			dmvrestart(ifp->if_unit);
		break;

	default:
		error = EINVAL;
	}
	splx(s);
	return (error);
}

/*
 * Restart after a fatal error.
 * Clear device and reinitialize.
 */
dmvrestart(unit)
	int unit;
{
	register struct dmvdevice *addr;
	register int i;

	dmvdown(unit);

	addr = (struct dmvdevice *)(dmvinfo[unit]->ui_addr);
	/*
	 * Let the DMV finish the MCLR.
	 */
	for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--)
		;
	if ((addr->bsel1 & DMV_RUN) == 0) {
		log(LOG_ERR, "dmvrestart: can't start device\n" );
		return (0);
	}
	if ((addr->bsel4 != 033) || (addr->bsel6 != 0305))
	{
		log(LOG_ERR, "dmv%d: device init failed, bsel4=%o, bsel6=%o\n",
			unit, addr->bsel4, addr->bsel6);
		return (0);
	}

	/* restart DMV */
	dmvinit(unit);
	dmv_softc[unit].sc_if.if_collisions++;	/* why not? */
}
	
/*
 * Reset a device and mark down.
 * Flush output queue and drop queue limit.
 */
dmvdown(unit)
	int unit;
{
	struct dmv_softc *sc = &dmv_softc[unit];
	register struct ifxmt *ifxp;

	((struct dmvdevice *)(dmvinfo[unit]->ui_addr))->bsel1 = DMV_MCLR;
	sc->sc_flag &= ~(DMV_RUNNING | DMV_ONLINE);

	for (ifxp = sc->sc_ifw; ifxp < &sc->sc_ifw[NXMT]; ifxp++) {
		if (ifxp->ifw_xtofree) {
			(void) m_freem(ifxp->ifw_xtofree);
			ifxp->ifw_xtofree = 0;
		}
	}
	sc->sc_oused = 0;
	if_qflush(&sc->sc_if.if_snd);

	/*
	 * Limit packets enqueued until we're back on the air.
	 */
	sc->sc_if.if_snd.ifq_maxlen = 3;
}

/*
 * Watchdog timeout to see that transmitted packets don't
 * lose interrupts.  The device has to be online.
 */
dmvtimeout(unit)
	int unit;
{
	register struct dmv_softc *sc;
	struct dmvdevice *addr;

	sc = &dmv_softc[unit];
	if (sc->sc_flag & DMV_ONLINE) {
		addr = (struct dmvdevice *)(dmvinfo[unit]->ui_addr);
		log(LOG_ERR, "dmv%d: output timeout, bsel0=%b bsel2=%b\n",
		    unit, addr->bsel0 & 0xff, DMV0BITS,
		    addr->bsel2 & 0xff, DMV2BITS);
		dmvrestart(unit);
	}
}
#endif
