/* Copyright (c) 1994-2000 Begemot Computer Associates.
 * Copyright (c) 2000-2002 by Hartmut Brandt
 *	All rights reserved.
 *
 * Redistribution of this software and documentation 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 or documentation 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 and documentation developed by the
 * Hartmut Brandt.
 *
 * 4. The name of the author or its contributors may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE AUTHOR FOKUS
 * AND ITS 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 AUTHOR OR ITS 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.
 *
 * $Id: dev_qna.c 501 2002-09-04 13:03:28Z hbb $
 *
 * Author: Harti Brandt <brandt@fokus.gmd.de>
 *
 * DEQNA Ethernet adapter.
 */
#include "proc.h"
#include "qna_regs.h"
#include "eppdefs.h"

RCSID("$Id: dev_qna.c 501 2002-09-04 13:03:28Z hbb $")

/* #define DEBUG */

#ifdef DEBUG
#define D(ARG)	do {						\
	printf("%s,%d: ", __func, __LINE__);			\
	printf ARG;						\
	printf("\n");						\
    } while (0)
#else
#define D(ARG)
#endif


#define PROMSIZE	4096
#define FIFOSIZE	8192	/* must be > PROMSIZE + sizeof(u_short) */
#define MAXBUF		2048	/* must be power of 2 */

enum {
	FIFO_MASK	= 0xffff0000U,
	FIFO_LEN_MASK	= 0x0000ffffU,
	FIFO_RECEIVE	= 0x00010000U,
	FIFO_XSTATUS	= 0x00020000U,
	FIFO_RSTATUS	= 0x00030000U,
};
typedef u_int		FifoC;
typedef struct QNA	QNA;

struct QNA {
	u_int	csr_base;	/* register block base address */
	int	irq;		/* interrupt request level */
	u_short	vector;		/* interrupt vector */

	u_short	hw[8];		/* default hardware address + checksum */
	u_char	prom[PROMSIZE];	/* boot prom, only low byte used */
	char	*promname;	/* prom filename */

	pid_t	epp;		/* ethernet process */
	int	sock;		/* socket to epp */
	eppcomm_t *comm;		/* shared communications area */

	u_short	csr;		/* qna csr */
	int	LED;		/* 3 LED's n bits 1...3 */
	u_int	rbdl;		/* receive list */
	u_int	xbdl;		/* transmit list */
	int	interrupt;	/* interrupt pending */
	int	xmtbusy;	/* true if waiting for transmit status to come back */

	u_int	fifo_wp;	/* next free word in fifo */
	u_char	fifo[FIFOSIZE];	/* the fifo */

	u_int	timeout;	/* timeout value from setup packet */
	int	timer;		/* registered sanity timer */
	int	tcount;		/* count number of succesive timeouts */
	int	trun;		/* sanity timer running */
	int	tenable;	/* sanity timer enabled */
};

static int	qnac_showfifo(iodev_t *, int, char **);
static int	qnac_reset(iodev_t *, int, char **);
static int	qnac_info(iodev_t *, int, char **);
static int	qnac_timer(iodev_t *, int, char **);

iocmd_t qna_cmds[] = {
	{ "?",		"[command ...]",	dev_help	},
	{ "help",	"[command ...]",	dev_help	},
	{ "info",	"",			qnac_info	},
	{ "fifo", 	"[-f]",			qnac_showfifo 	},
	{ "reset",	"",			qnac_reset	},
	{ "timer",	"[on|off]",		qnac_timer	},
	{ NULL,		NULL,			NULL }
};

/*
 * mapping sanity timeout period -> msecs
 */
static long	ttab[8] = {
	250,		/* 000 -> 1/4 sec */
	1000,		/* 001 -> 1 sec */
	4000,		/* 010 -> 4 secs */
	16000,		/* 011 -> 16 secs */
	60000,		/* 100 -> 1 min */
	240000,		/* 101 -> 4 mins */
	960000,		/* 110 -> 16 mins */
	3840000,	/* 111 -> 64 mins */
};

static void	qna_ctrl_create(iodev_t *, int, char *[]);
static void	qna_ctrl_complete(iodev_t *);
static void	qna_dev_create(iodev_t *, int, char *[]);
static void	qna_reset(iodev_t *);
static u_short	qna_fetch(iodev_t *, u_int);
static void	qna_store(iodev_t *, u_int, mmode_t, u_short);
static u_short	qna_vector(iodev_t *);
static void	qna_dma(iodev_t *);
static void	qna_async(iodev_t *, void *);
static void	qna_info(iodev_t *);

ioops_t qna_ops = {
	qna_ctrl_create,	/* ctrl_create */
	qna_dev_create,		/* dev_create */
	qna_ctrl_complete,	/* ctrl_complete */
	qna_reset,		/* reset */
	qna_fetch,		/* fetch */
	qna_store,		/* store */
	qna_vector,		/* vector */
	qna_dma,		/* dma */
	qna_async,		/* async */
	qna_info,		/* info */
	0,			/* flush */
	qna_cmds		/* cmds */
};

static void	qna_new_csr(iodev_t *dev, u_short ocsr);
static int	qna_empty_fifo(iodev_t *dev);
static void	qna_handle_epp(iodev_t *dev, epp_t *c);
static u_short	qna_store_frame(iodev_t *dev, u_char *frame, u_int len);
static void	qna_store_xstatus(iodev_t *dev, u_char *status, u_int len);
static void	qna_store_rstatus(iodev_t *dev, u_char *status, u_int len);
static int	qna_store_status(iodev_t *dev, u_char *status, u_int len, u_int *bdl, char);
static void	qna_irq(iodev_t *dev, u_short bit);
static int	qna_mem(iodev_t *dev, void *buf, u_int bytes, u_int mem, dmadir_t direction);
static int	qna_transmit(iodev_t *dev);
static int	qna_fetch_frame(iodev_t *dev, u_char *xbuf);
static void	write_epp(QNA *q, u_int type, void *buf, u_int len);
static void	qna_timeout(void *);
static void	qna_reset_timer(iodev_t *dev);

static char	*pled(int);

/*
 * create DEQNA
 * arguments are:
 *	csr base address
 *	interrupt request level
 *	default hardware address
 *	[optional checksum]
 *	ROM file
 */
static void
qna_ctrl_create(iodev_t *dev, int argc, char *argv[])
{
	QNA *q;
	int fd;
	char *fn;

	if (argc != 4 && argc != 5)
		conf_panic("qna: need 4 or 5 arguments for controller");

	q = xalloc(sizeof(QNA));
	(void)memset(q, 0, sizeof(QNA));

	q->csr_base = parse_csr(*argv++, "qna");
	argc--;
	q->irq = parse_irq(*argv++, "qna");
	argc--;
	q->vector = 0774;
	q->LED = 016;
	q->timeout = 5;			/* 4 minutes */
	q->tenable = 1;			/* enabled */

	if (sscanf(*argv, "%hx:%hx:%hx:%hx:%hx:%hx", q->hw+0, q->hw+1,
	    q->hw+2, q->hw+3, q->hw+4, q->hw+5) != 6)
		conf_panic("qna: can't parse ethernet address '%s'", *argv);
	argv++;
	argc--;

	if (argc > 1) {
		if (sscanf(*argv, "%hx:%hx", q->hw+6, q->hw+7) != 2)
			conf_panic("qna: can't parse ethernet address checksum '%s'", *argv);
		argv++;
		argc--;
	}
	
	if ((fd = lib_open(*argv, &fn)) < 0)
		conf_panic("qna: %s: %s", *argv, strerror(errno));
	if (read(fd, q->prom, PROMSIZE) <= 0)
		conf_panic("qna: prom read error: %s", strerror(errno));
	close(fd);

	q->promname = fn;
	dev->data = q;
}

/*
 * create external ethernet process
 * arguments are:
 *	the external program
 *	arguments to this program
 * XXX should add a timeout
 */
static void
qna_dev_create(iodev_t *dev, int argc, char *argv[])
{
	QNA 	*q = dev->data;
	fd_set	iset;
	epp_t	c;
	struct timeval tv;
	int	ret;
	int	fd;

	if(argc < 1)
		conf_panic("qna: need epp name");

	q->comm = (eppcomm_t *)mmap_shared_parent(sizeof(eppcomm_t), &fd);
	q->sock = register_handler(argv[0], argv, dev, NULL, fd, &q->epp);

	FD_ZERO(&iset);
	FD_SET(q->sock, &iset);

	tv.tv_sec = 10;
	tv.tv_usec = 0;

	while((ret = select(q->sock + 1, &iset, NULL, NULL, &tv)) < 0)
		if(errno != EINTR)
			conf_panic("qna: error on initialisation: %s", strerror(errno));
	if(ret == 0)
		conf_panic("qna: timeout on initialisation");

	if(read(q->sock, &c, sizeof(c)) != sizeof(c))
		conf_panic("qna: can't read socket: %s", strerror(errno));
	if(c.type != EPP_INIT)
		conf_panic("qna: EPP_INIT expected, got %04x", c.type);

	if((pid_t)c.reset_count != q->epp)
		conf_panic("qna: wrong process");

	close(fd);

	/*
	 * create disabled timer
	 */
	q->timer = register_timer(0, qna_timeout, dev);
}


/*
 * all creation done.
 * If we had a device line, plug in registers
 */
static void
qna_ctrl_complete(iodev_t *dev)
{
	QNA *q = dev->data;

	if(q->epp == 0)
		return;

	proc.iopage[IOP(q->csr_base +   0)] = dev;
	proc.iopage[IOP(q->csr_base +   2)] = dev;
	proc.iopage[IOP(q->csr_base +   4)] = dev;
	proc.iopage[IOP(q->csr_base +   6)] = dev;
	proc.iopage[IOP(q->csr_base + 010)] = dev;
	proc.iopage[IOP(q->csr_base + 012)] = dev;
	proc.iopage[IOP(q->csr_base + 014)] = dev;
	proc.iopage[IOP(q->csr_base + 016)] = dev;
}


/*
 * reset qna
 *	flush fifo
 *	invalidate both lists
 *	reset epp
 *	reset interrupt or DMA request
 */
static void
qna_reset(iodev_t *dev)
{
	QNA 	*q = dev->data;

	if(!q->epp)
		return;

	q->csr = QNA_XL | QNA_RL | QNA_OK | (q->csr & (QNA_IE | QNA_SR));

	q->timeout = 5;
	q->trun = 0;
	qna_reset_timer(dev);

	q->comm->reset_count++;
	q->comm->rcv_enable = 0;
	kill(q->epp, SIGHUP);

	q->interrupt = 0;
	q->xmtbusy = 0;

	q->fifo_wp = 0;

	dev->reqpri = 0;
}


/*
 * reset timer
 */
static void
qna_reset_timer(iodev_t *dev)
{
	QNA	*q = dev->data;

	if(q->trun && q->tenable)
		reset_timer(ttab[q->timeout], q->timer);
	else
		reset_timer(0, q->timer);

	q->tcount = 0;

#if 0
	if(q->trun && q->tenable)
		printf("qna: timer set to %ld\n", ttab[q->timeout]);
	else
		printf("qna: timer reset\n");
#endif
}

/*
 * this is called if the sanity timer expires 
 */
static void
qna_timeout(void *p)
{
	iodev_t	*dev = p;
	QNA	*q = dev->data;

	if(++q->tcount == 2) {
		q->timeout = 5;
		qna_reset_timer(dev);
	}
	proc_pwrfail(0);
}


/*
 * fetch register
 */
static u_short
qna_fetch(iodev_t *dev, u_int a)
{
	QNA *q = dev->data;
	u_short v = 0;

	switch(a - q->csr_base) {

	case 000:
	case 002:
	case 004:
	case 006:
	case 010:
	case 012:
		/*
		 * station address prom
 		 * If EL is set a checksum appears in the 1st two
		 * registers. I could no make out how it is computed
		 * so specify it in the config file if you need.
		 */
		if(q->csr & QNA_EL)
			v = q->hw[7 - ((a - q->csr_base) >> 1)] | 0177400;
		else
			v = q->hw[(a - q->csr_base) >> 1] | 0177400;
		break;

	case 014:
		v = q->vector;
		break;

	case 016:
		v = q->csr;
		break;

	default:
		warn("qna_fetch(%o)\n", a);
		Trap4(020);
	}

	return v;
}


/*
 * store into register
 */
static void
qna_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	QNA *q = dev->data;
	u_short ocsr;

	a -= q->csr_base;

	/*
	 * bdls can be updated only in word mode if not currently resetting
 	 */
	if((mode != M_Word || (q->csr & QNA_SR)) && a >= 004 && a <= 012)
		return;

	switch(a) {

	case 000:				/* read only */
	case 002:
		break;

	case 004:				/* receive bdl */
		q->rbdl = (q->rbdl & 0x3f0000) | (v & ~1);
		break;

	case 006:				/* receive bdl */
		q->rbdl = (q->rbdl & 0x00ffff) | (((u_int)v << 16) & 0x3f0000);
#ifdef DEBUG
		if(!(q->csr & QNA_RL))
			warn("qna: updating valid rbdl");
		printf("qna: new RBDL %08o, pc = %06o\n", q->rbdl, proc.reg[7]);
#endif
		q->csr &= ~QNA_RL;
		IRQ(dev, DMAPRI);
		break;

	case 010:				/* transmit bdl */
		q->xbdl = (q->xbdl & 0x3f0000) | (v & ~1);
		break;

	case 012:				/* transmit bdl */
		q->xbdl = (q->xbdl & 0x00ffff) | (((u_int)v << 16) & 0x3f0000);
#ifdef DEBUG
		if(!(q->csr & QNA_XL))
			warn("qna: updating valid xbdl");
		printf("qna: new XBDL %08o, pc = %06o\n", q->xbdl, proc.reg[7]);
#endif
		q->csr &= ~QNA_XL;
		IRQ(dev, DMAPRI);
		break;

	case 014:
		if((q->csr & QNA_SR) == 0) {
			v &= 0774;
			SETWORD(q->vector, mode, v);
		}
		break;

	case 016:
		if(q->csr & QNA_SR) {
			/*
			 * if reset only IE and SR can be written
			 */
			if(mode == M_Word || mode == M_Low) {
				q->csr &= ~(QNA_SR | QNA_IE);
				q->csr |= v & (QNA_SR | QNA_IE);
			}
		} else {
			ocsr = q->csr;
			if(mode == M_Word || mode == M_High) {
				if(v & QNA_RI)
					q->csr &= ~QNA_RI;
				q->csr &= ~(QNA_EL | QNA_IL | QNA_SE);
				q->csr |= v & (QNA_EL | QNA_IL | QNA_SE);
			}
			if(mode == M_Word || mode == M_Low) {
				if(v & QNA_XI)
					q->csr &= ~QNA_XI;
				q->csr &= ~(QNA_IE | QNA_BD | QNA_SR | QNA_RE);
				q->csr |= v & (QNA_IE | QNA_BD | QNA_SR | QNA_RE);
			}
			qna_new_csr(dev, ocsr);
		}
		break;

	default:
		warn("qna_store(%o)\n", a);
		Trap4(020);
	}
}


/*
 * new csr value, decide what to do
 * Old value is ocsr
 */
static void
qna_new_csr(iodev_t *dev, u_short ocsr)
{
	QNA *q = dev->data;
	FifoC type;

#ifdef DEBUG
	printf("qna: old csr = %06o, new csr = %06o\n", ocsr, q->csr);
#endif
	/*
	 * if reset is just switched on reset things
	 * if it was already on - do nothing
	 */
	if(q->csr & QNA_SR) {
		if(!(ocsr & QNA_SR))
			qna_reset(dev);
		return;
	}

	/*
	 * if IE was switched off drop pending interrupt requests
	 */
	if(!(q->csr & QNA_IE) && q->interrupt) {
		q->interrupt = 0;
		if(dev->reqpri != DMAPRI)
			dev->reqpri = 0;
	}

	/*
	 * if receiver state changes, inform epp
	 */
	if((ocsr ^ q->csr) & QNA_RE) {
		q->comm->rcv_enable = q->csr & QNA_RE;
		kill(q->epp, SIGUSR1);
	}


	/*
	 * if BD is switched on transmit prom and try a receive cycle
	 */
	if((q->csr & QNA_EL) && (q->csr & QNA_BD) && !(q->csr & QNA_RE) && !(ocsr & QNA_BD) && q->fifo_wp == 0) {
		/*
		 * receiver is disabled so we can copy into fifo
		 */
		assert(FIFOSIZE >= PROMSIZE + sizeof(FifoC));

		type = FIFO_RECEIVE + PROMSIZE;
		memcpy(&q->fifo[0], &type, sizeof(FifoC));
		q->fifo_wp += sizeof(FifoC);
		q->LED = 016;

		memcpy(&q->fifo[sizeof(FifoC)], q->prom, PROMSIZE);
		q->fifo_wp += PROMSIZE;

		IRQ(dev, DMAPRI);
	}
}

/*
 * return vector
 * can set reqpri to 0 because DMA is done before
 * any interrupt is processed
 */
static u_short
qna_vector(iodev_t *dev)
{
	dev->reqpri = 0;
	((QNA *)dev->data)->interrupt = 0;
	return ((QNA *)dev->data)->vector;
}



/*
 * here is the main focus
 * this function handles the various events
 */
static void
qna_dma(iodev_t *dev)
{
	QNA *q = dev->data;
	epp_t	c;
	int	ret;

	dev->reqpri = 0;
again:
	/*
	 * try to empty the fifo
	 */
	if(q->fifo_wp != 0 && qna_empty_fifo(dev))
		goto again;

	/*
	 * look if the epp has something to say
	 */
	if((ret = read(q->sock, &c, sizeof(c))) == sizeof(c)) {
		qna_handle_epp(dev, &c);
		goto again;
	}
	if(ret == 0 || (ret < 0 && errno != EAGAIN))
		panic("qna: socket read: %s", strerror(errno));

	/*
	 * now try to transmit
	 * use heuristic to check if status will fit
	 */
	if(!(q->csr & QNA_XL) && !q->xmtbusy && q->fifo_wp < FIFOSIZE - 10 && qna_transmit(dev))
		goto again;

	/*
	 * nothing more to do, leave
	 */
	if(dev->reqpri == 0 && q->interrupt)
		IRQ(dev, q->irq);
}


/*
 * called if transmit list is valid
 * try to transmit something, return true if did
 */
static int
qna_transmit(iodev_t *dev)
{
	QNA	*q = dev->data;
	u_char	xbuf[MAXBUF];
	int	xlen;
	int	LED;
	int	t;

	D(("+++"));
	if((xlen = qna_fetch_frame(dev, xbuf)) == 0)
		return 0;
	D(("xlen=%d", xlen));

	if(xlen < 0) {
		/*
		 * setup frame
		 */
#ifdef DEBUG
		printf("qna: SETUP %o\n", -xlen);
#endif
		xlen = -xlen;
		write_epp(q, EPP_SETUP, xbuf, xlen);
		q->xmtbusy = 1;

		if(xlen > 0200 && xlen <= 0400) {
			LED = q->LED & ~(1 << ((xlen >> 2) & 3));
			if(LED != q->LED) {
				printf("qna: LED = %s", pled(q->LED));
				printf(" -> %s\n", pled(LED));
				q->LED = LED;
			}

			/*
			 * sample sanity timer
			 */
			t = q->trun;
			q->trun = q->csr & QNA_SE;
			q->timeout = (xlen >> 4) & 7;
			if(t || q->trun)
				qna_reset_timer(dev);
		}
		return 1;

	} else switch(q->csr & (QNA_IL | QNA_EL)) {

	case 0:
		/*
		 * internal loopback
		 */
#ifdef DEBUG
		printf("qna: ILOOP %o\n", xlen);
#endif
		if(!(q->csr & QNA_RL)) {
			write_epp(q, EPP_ILOOP, xbuf, xlen);
			q->xmtbusy = 1;
		}
		break;


	case QNA_EL | QNA_IL:
		/*
		 * external loopback
		 */
#ifdef DEBUG
		printf("qna: ELOOP %o\n", xlen);
#endif
		write_epp(q, EPP_ELOOP, xbuf, xlen);
		q->xmtbusy = 1;
		break;


	case QNA_EL:
		/*
		 * extended internal loopback
		 */
#ifdef DEBUG
		printf("qna: EILOOP %o\n", xlen);
#endif
		write_epp(q, EPP_EILOOP, xbuf, xlen);
		q->xmtbusy = 1;
		break;


	case QNA_IL:
		/*
		 * normal transmission
		 */
#ifdef DEBUG
		printf("qna: TRANSMIT %o\n", xlen);
#endif
		write_epp(q, EPP_TRANSMIT, xbuf, xlen);
		q->xmtbusy = 1;
		break;

	default:
		panic("qna: gulp %d", __LINE__);
	}

	return 0;
}

/*
 * try to fetch next frame
 * return 0 if couldn't, > 0 if could, < 0 if could and setup frame
 */
static int
qna_fetch_frame(iodev_t *dev, u_char *xbuf)
{
	QNA	*q = dev->data;
	u_short	desc;
	u_int	faddr;
	u_short	blen;
	u_int	findex = 0;
	u_short	w;

chain:
	/*
	 * 1. write -1 to flag word, showing we are using this entry
	 */
	D(("step 1"));
	SETW(w, 0177777);
	if(!qna_mem(dev, &w, sizeof(u_short), q->xbdl, WriteMem))
		return 0;

	/*
	 * 2. read descriptor and check entry
	 */
	D(("step 2"));
	if(!qna_mem(dev, &desc, sizeof(u_short), q->xbdl + 2, ReadMem))
		return 0;
	desc = GETW(desc);
	D(("desc=%o", desc));
	if((desc & (BD_VALID | BD_CHAIN)) == 0) {
		q->csr |= QNA_XL;
		return 0;
	}
	faddr = (desc & 077) << 16;

	/*
	 * 3. read address
	 */
	D(("step 3"));
	if(!qna_mem(dev, &w, sizeof(u_short), q->xbdl + 4, ReadMem))
		return 0;
	faddr |= GETW(w);

	/*
	 * 4. process chaining
	 */
	D(("step 4"));
	if(desc & BD_CHAIN) {
		q->xbdl = faddr;
		goto chain;
	}

	/*
	 * 5. read and process length
	 */
	D(("step 5"));
	if(!qna_mem(dev, &blen, sizeof(u_short), q->xbdl + 6, ReadMem))
		return 0;
	blen = -(signed short)GETW(blen) << 1;
	if(desc & BD_HBYTE)
		blen--;
	if(desc & BD_LBYTE)
		blen--;
	faddr &= ~1;
	if(blen > MAXBUF)
		blen = MAXBUF;

	/*
	 * 6. read buffer
	 */
	D(("step 6"));
	if(desc & BD_HBYTE)
		faddr++;
	w = (blen < MAXBUF - findex) ? blen : (MAXBUF - findex);
	if(!qna_mem(dev, xbuf, w, faddr, ReadMem))
		return 0;
	findex += w;
	xbuf += w;
	

	/*
	 * 7. check for EOM
	 */
	D(("step 7"));
	if(desc & BD_EOM)
		return (desc & BD_SETUP) ? -(int)findex : (int)findex;

	/*
	 * 8. implicit chain
	 */
	D(("step 8"));
	SETW(w, 0177777);
	if(!qna_mem(dev, &w, sizeof(u_short), q->xbdl + 8, WriteMem))
		return 0;
	if(!qna_mem(dev, &w, sizeof(u_short), q->xbdl + 10, WriteMem))
		return 0;

	q->xbdl += 12;

	D(("chain"));
	goto chain;
}


/*
 * called if there is something waiting in the pipe
 * because this is called from the signal handler don't do anything
 * critical and only trigger DMA
 */
static void
qna_async(iodev_t *dev, void *argp UNUSED)
{
	IRQ(dev, DMAPRI);
}


/*
 * Try to DMA fifo.
 * Called from DMA if fifo is not empty and receive list not
 * invalid.
 */
static int
qna_empty_fifo(iodev_t *dev)
{
	QNA	*q = dev->data;
	FifoC	type;
	u_int	flen, rest;
	u_int	alen = 0;

	memcpy(&type, &q->fifo[0], sizeof(FifoC));

	flen = type & FIFO_LEN_MASK;
	type &= FIFO_MASK;

	switch(type) {


	case FIFO_RECEIVE:
		if(!(q->csr & QNA_RL)) {
			alen = qna_store_frame(dev, &q->fifo[sizeof(FifoC)], flen);
#ifdef DEBUG
			printf("qna: RECEIVE %d byte frame into fifo -> %d, rest %d\n", flen, alen, flen - alen);
#endif
		}
		break;


	case FIFO_XSTATUS:
		alen = flen;
		qna_store_xstatus(dev, &q->fifo[sizeof(FifoC)], flen);
		break;


	case FIFO_RSTATUS:
		alen = flen;
		qna_store_rstatus(dev, &q->fifo[sizeof(FifoC)], flen);
		break;

	default:
		panic("qna: bad fifo type %04x", type);
	}

	if(alen == 0)
		return 0;

	/*
	 * bytes in first entry: flen
	 * removed from first entry: alen
	 */
	if((rest = flen - alen) == 0) {
		/*
		 * entry totally removed
		 */
		if(flen + sizeof(FifoC) == q->fifo_wp) {
			/*
			 * there was only one entry
			 */
			q->fifo_wp = 0;
		} else {
			/*
			 * move rest down
			 */
			memcpy(&q->fifo[0], &q->fifo[flen + sizeof(FifoC)], q->fifo_wp - flen - sizeof(FifoC));
			q->fifo_wp -= flen + sizeof(FifoC);
		}
	} else {
		/*
		 * move rest of first entry and other entries down
		 */
		memcpy(&q->fifo[sizeof(FifoC)], &q->fifo[alen + sizeof(FifoC)], q->fifo_wp - alen - sizeof(FifoC));
		type |= rest;
		memcpy(&q->fifo[0], &type, sizeof(FifoC));
		q->fifo_wp -= alen;
	}

	return 1;
}

/*
 * try to DMA status
 */
static void
qna_store_xstatus(iodev_t *dev, u_char *status, u_int len)
{
	QNA *q = dev->data;

	if(!q->xmtbusy)
		panic("qna: xstatus but not xmtbusy");
	q->xmtbusy = 0;

	if(!(q->csr & QNA_XL))
		if(qna_store_status(dev, status, len, &q->xbdl, 'X'))
			qna_irq(dev, QNA_XI);
}

static void
qna_store_rstatus(iodev_t *dev, u_char *status, u_int len)
{
	QNA *q = dev->data;

	if(!(q->csr & QNA_RL))
		if(qna_store_status(dev, status, len, &q->rbdl, 'R'))
			qna_irq(dev, QNA_RI);
}

static int
qna_store_status(iodev_t *dev, u_char *status, u_int len UNUSED, u_int *bdl, char c UNUSED)
{
	u_short	v[2];

	memcpy(v, status, sizeof(v));
	SETW(v[0], v[0]);
	SETW(v[1], v[1]);

	if(!qna_mem(dev, v, 2 * sizeof(u_short), *bdl + 8, WriteMem))
		return 0;

	*bdl += 12;

#ifdef DEBUG
	printf("qna: %cSTATUS %06o %06o\n", c, v[0], v[1]);
#endif

	return 1;
}

/*
 * try to DMA frame from *frame to memory
 * return number of bytes actually written
 */
static u_short
qna_store_frame(iodev_t *dev, u_char *frame, u_int len)
{
	u_int	findex = 0;	/* byte index into frame */
	QNA	*q = dev->data;
	u_int	faddr;
	u_short	desc;
	u_short	blen;
	u_short	w;

chain:
	/*
	 * 1. write -1 to flag word showing QNA uses entry
	 */
	SETW(w, 0177777);
	if(!qna_mem(dev, &w, sizeof(u_short), q->rbdl, WriteMem))
		return findex;

	/*
	 * 2. read descriptor flags and check
	 */
	if(!qna_mem(dev, &desc, sizeof(u_short), q->rbdl + 2, ReadMem))
		return findex;
	desc = GETW(desc);
	faddr = (u_int)(desc & 077) << 16;
	if((desc & (BD_VALID | BD_CHAIN)) == 0) {
		q->csr |= QNA_RL;
		return findex;
	}

	/*
 	 * 3. read address
	 */
	if(!qna_mem(dev, &w, sizeof(u_short), q->rbdl + 4, ReadMem))
		return findex;
	faddr |= GETW(w);

	/*
	 * 4. process chaining
	 */
	if(desc & BD_CHAIN) {
		q->rbdl = faddr & ~1;
		goto chain;
	}

	/*
	 * 5. read length
	 *    "receive buffers must start and end on word boundaries"
	 */
	if(!qna_mem(dev, &blen, sizeof(u_short), q->rbdl + 6, ReadMem))
		return findex;
	blen = -(signed short)GETW(blen) << 1;
	faddr &= ~1;
	if(blen > MAXBUF)
		blen = MAXBUF;

	/*
	 * 6. write packet
	 */
	w = (blen < len - findex) ? blen : (len - findex);
	if(!qna_mem(dev, frame, w, faddr, WriteMem))
		return findex;
	findex += w;
	frame += w;

	/*
	 * 7. check if done
	 */
	if(findex == len) {
		blen -= w;
		faddr += w;
		if(((faddr & 1) && blen > 2) || (!(faddr & 1) && blen > 1)) {
			SETW(w, 0177777);
			qna_mem(dev, &w, sizeof(u_short), (faddr + 1) & ~1, WriteMem);
		}
		return findex;
	}

	/*
	 * 8. implicit chain
	 */
	SETW(w, 0177777);
	if(!qna_mem(dev, &w, sizeof(u_short), q->rbdl + 8, WriteMem))
		return findex;
	if(!qna_mem(dev, &w, sizeof(u_short), q->rbdl + 10, WriteMem))
		return findex;

	q->rbdl += 12;

	goto chain;
}



/*
 * do DMA for QNA.
 * returns TRUE if ok, FALSE on NXM
 */
static int
qna_mem(iodev_t *dev, void *buf, u_int bytes, u_int mem, dmadir_t direction)
{
	u_int	abytes;

	abytes = dma(buf, bytes, mem, direction, NULL);

	if(abytes < bytes) {
		((QNA *)dev->data)->csr |= QNA_XL | QNA_RL | QNA_NI;
		qna_irq(dev, QNA_XI);
		return 0;
	} else
		return 1;
}

/*
 * handle interrupt requstion on csr bit
 */
static void
qna_irq(iodev_t *dev, u_short bit)
{
	QNA *q = dev->data;

	if(!(q->csr & bit)) {
		q->csr |= bit;
		if(q->csr & QNA_IE) {
			q->interrupt = 1;
			if(dev->reqpri < q->irq)
				IRQ(dev, q->irq);
		}
	}
}

/*
 * Handle input from epp.
 * Called from DMA with the epp command
 */
static void
qna_handle_epp(iodev_t *dev, epp_t *c)
{
	QNA	*q = dev->data;
	char	buf[2048];
	u_int	fifo_space;
	u_int	fifo_need;
	FifoC	fc;

	if(c->len > 0 && read(q->sock, buf, c->len) != c->len)
		panic("qna: socket read error: %s", strerror(errno));

	if(c->reset_count != q->comm->reset_count)
		return;

	fifo_space = FIFOSIZE - q->fifo_wp - (q->xmtbusy ? (sizeof(FifoC) + 4) : 0);
	fifo_need = sizeof(FifoC) + c->len;

	switch(c->type) {


	case EPP_RECEIVE:		/* receive frame */
		if(fifo_need > fifo_space)
			return;
		fc = FIFO_RECEIVE + c->len;
		break;


	case EPP_RSTATUS:		/* receive status */
		if(fifo_need > fifo_space)
			return;
		if(c->len != 4)
			panic("qna: rstatus expected 4 instead of %d\n", c->len);
		fc = FIFO_RSTATUS + 4;
		break;


	case EPP_XSTATUS:		/* transmit status */
		fifo_space += sizeof(FifoC) + 4;
		if(fifo_need > fifo_space)
			return;
		if(c->len != 4)
			panic("qna: xstatus expected 4 instead of %d\n", c->len);
		fc = FIFO_XSTATUS + 4;
		break;

	default:
		panic("qna: bad epp command %04x\n", c->type);
	}

	memcpy(&q->fifo[q->fifo_wp], &fc, sizeof(FifoC));
	q->fifo_wp += sizeof(FifoC);
	if(c->len > 0) {
		memcpy(&q->fifo[q->fifo_wp], buf, c->len);
		q->fifo_wp += c->len;
	}
}

/*
 * write command to epp
 */
static void
write_epp(QNA *q, u_int type, void *buf, u_int len)
{
	struct iovec iov[2];
	epp_t	c;
	struct timeval tv;
	int	ret;

	c.type = type;
	c.len = len;
	c.reset_count = q->comm->reset_count;

	iov[0].iov_base = (caddr_t)&c;
	iov[0].iov_len = sizeof(c);
	iov[1].iov_base = (caddr_t)buf;
	iov[1].iov_len = len;
	if((ret = writev(q->sock, iov, 2)) < 0)
		panic("qna: socket write error: %s", strerror(errno));
	if((u_int)ret != sizeof(c) + len)
		panic("qna: bad socket write: %d %u", ret, sizeof(c) + len);

	/*
	 * heuristic:
	 *  the citizenship test in the QNA boot prom
	 *  does busy wait for transmission status and times
	 *  out after a short period. This fails in the emulator
	 *  because we have only one processor and should give it up
	 *  to give the epp a chance to process the frame. If not all
	 *  LEDs are off we assume that the boot prom is running and try
	 *  to give up our time slice trough the following code.
	 */
	if(q->LED) {
		tv.tv_sec = 0;
		tv.tv_usec = 1000;
		select(0, NULL, NULL, NULL, &tv);
	}
}

/*
 * print fancy information
 */
static void
qna_info(iodev_t *dev)
{
	QNA *q = dev->data;
	struct timeval tv;

	printf("emulated DEQNA\n");
	printf("  csr@%08o", q->csr_base);
	printf("  irq=%d", q->irq);
	printf("  vector=%03o", q->vector);
	printf("  %02x:%02x:%02x:%02x:%02x:%02x  %02x:%02x",
		q->hw[0], q->hw[1], q->hw[2], q->hw[3],
		q->hw[4], q->hw[5], q->hw[6], q->hw[7]);
	putchar('\n');

	printf("  epp=%u", (u_int)q->epp);
	printf("  prom=%s", q->promname);
	printf("  csr=%06o", q->csr);
	printf("  LED=%s", pled(q->LED));
	printf("  reset=%d", q->comm->reset_count);
	putchar('\n');

	printf("  interrupt=%d", q->interrupt);
	printf("  reqpri=%d", dev->reqpri);
	printf("  rbdl=%08o", q->rbdl);
	printf("  xbdl=%08o", q->xbdl);
	printf("  fifo=%d", q->fifo_wp);
	printf("  xmtbusy=%d", q->xmtbusy);
	putchar('\n');

	printf("  timer=%d", q->timer);
	printf("  timeout=%o", q->timeout);
	printf("  trun=%d", (q->trun != 0));
	printf("  tenable=%d", q->tenable);
	putchar('\n');

	kill(q->epp, SIGINFO);

	tv.tv_sec = 0;
	tv.tv_usec = 500000;
	select(0, 0, 0, 0, &tv);
}

static char *
pled(int LED)
{
	static char buf[4];

	buf[0] = ".o"[(LED & 010) != 0];
	buf[1] = ".o"[(LED & 004) != 0];
	buf[2] = ".o"[(LED & 002) != 0];
	buf[3] = '\0';

	return buf;
}


/*
 * command interface
 */
static int
qnac_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	qna_info(dev);
	return 0;
}

/*
 * pretty print fifo contents
 */
static int
qnac_showfifo(iodev_t *dev, int argc, char **argv)
{
	int	opt;
	int	full = 0;
	QNA	*q = dev->data;
	u_int	i;

	optind = 0;
	opterr = 0;
	while((opt = getopt(argc, argv, "f")) != EOF)
		switch(opt) {

		case 'f':
			full++;
			break;

		case '?':
		case ':':
			return 1;
		}
	argc -= optind;
	argv += optind;

	if(argc != 0)
		return 1;

	printf("qna fifo: %d. bytes\n", q->fifo_wp);
	if(q->fifo_wp > FIFOSIZE)
		panic("qna: fifo write pointer points behind fifo");

	i = 0;
	while(i < q->fifo_wp) {
		u_short	status[2];
		FifoC	type;
		u_char	*p;
		u_int	len;
		int	col;

		memcpy(&type, &q->fifo[i], sizeof(type));
		if((i += sizeof(type)) > q->fifo_wp)
			break;

		len = type & FIFO_LEN_MASK;
		type &= FIFO_MASK;

		if(i + len > q->fifo_wp) {
			i += len;
			break;
		}

		switch(type) {

		case FIFO_RECEIVE:
			printf("\tRECEIVE: %d. bytes\n", len);
			if(full) {
				for(p = &q->fifo[i], col = 0; p < &q->fifo[i+len]; p++, col++) {
					if(col % 16 == 0)
						putchar('\t');
					printf("%03o ", *p);
					if(col % 16 == 15)
						putchar('\n');
				}
				if(col % 16 != 0)
					putchar('\n');
			}
			break;

		case FIFO_XSTATUS:
			if(len != sizeof(status))
				panic("qna: FIFO_XSTATUS length = %d.", len);
			memcpy(status, &q->fifo[i], sizeof(status));
			printf("\tXSTATUS: %06o %06o\n", status[0], status[1]);
			break;

		case FIFO_RSTATUS:
			if(len != sizeof(status))
				panic("qna: FIFO_RSTATUS length = %d.", len);
			memcpy(status, &q->fifo[i], sizeof(status));
			printf("\tRSTATUS: %06o %06o\n", status[0], status[1]);
			break;

		default:
			panic("qna: bad fifo type %08x", type);
		}

		i += len;
	}
	if(i > q->fifo_wp)
		panic("qna: fifo structur broken: i = %d., fifo_wp = %d.", i, q->fifo_wp);
	return 0;
}

/*
 * reset qna
 */
static int
qnac_reset(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	(*dev->ioops->reset)(dev);
	return 0;
}

/*
 * enable/disable timer
 */
static int
qnac_timer(iodev_t *dev, int argc, char **argv)
{
	QNA	*q = dev->data;

	if(argc > 1)
		return 1;
	if(argc == 1) {
		if(strcmp(argv[0], "on") == 0)
			q->tenable = 1;
		else if(strcmp(argv[0], "off") == 0)
			q->tenable = 0;
		else
			return 1;
	}
	printf("timer %sabled\n", q->tenable ? "en" : "dis");

	return 0;
}
