/* Copyright 1984 by the Massachusetts Institute of Technology */


/* This file contains a bunch of utility routines that perform most of
 * the network processing for vanilla network interfaces. (See comments
 * in net.h and gate.c).
 * By convention, all network routines store a pointer to the net
 * associated with an iorb in the i_usr2 word. On output, a retransmission
 * count is stored in i_usr1 and a datum indicating the last host
 * sent to (for optimal flow control) is in i_usr3.
 */


#include	<types.h>
#include	<sys.h>
#include	"const.h"
#include	"param.h"
#include	"defs.h"
#include	"macs.h"
#include	"net.h"
#include	"ext.h"

/* Error/notification messages */

static	char ntiper[] =	"Ip err nt %d int %s/%d\n";
static	char ntidisc[] = "Ip ovfl nt %d int %s/%d, disc\n";
static	char ntdwni[] =	"Net dwn for ip rstrt nt %d int %s/%d\n";
static	char ntipqnb[] = "Ip q len %d no ip buf nt %d int %s/%d\n";
static	char ntdwnd[] =	"Nt dwn, disc pkt to hst %d nt %d int %s/%d\n";
static	char ntrtov[] =	"Rtrns ovfl to hst %d nt %d int %s/%d\n";
static	char ntdwn[] =	"Nt dwn to hst %d nt %d int %s/%d\n";
static	char ntoodp[] =	"Op ovfl to hst %d nt %d int %s/%d\n";



/* Standard network reset routine for startup. Sets up default
 * signal handlers for input and output and clears status info.
 */

netinit(netp)
reg	net	*netp;

{	netp->n_flg |= N_UP;
	netp->n_irtn = netino;
	netp->n_ortn = netouto;

	netistart(netp);
}


/* Read causes the per-network input processing (which generally
 * results in some forwarder being called). In any case, when done,
 * new reads are queued.
 */

netin(iob)
reg	iorb	*iob;

{	reg	net	*netp;
		void	netistart();
		void	netxin();

	netp = mknet(iob->i_usr2);
	netp->n_ipc--;		  
	netp->n_pkti++;
	netp->n_byti += iob->i_bxfr;

	if ((iob->i_stat & I_ERR) != 0) {
		iflog(L_INFU)
			dolog(ntiper, mksnet(netp));
		netp->n_ioin++;
		freebuf(iob);
		}
	  else {
		if ((numq(&netp->n_ipq) >= bufshr) && (nbfree <= buflow)) {
			iflog(L_INFC)
				dolog(ntidisc, mksnet(netp));
			netp->n_ovfin++;
			freebuf(iob);
			}
		  else {
			if (tsteq(&netp->n_ipq))
				addtsk(syshnd, GWIPPRI, netxin, netp);
			addq(&netp->n_ipq, iob);
			}
		}

	if ((netp->n_flg & N_IWT) == 0) {
		netp->n_flg |= N_IWT;
		addtsk(syshnd, GWISPRI, netistart, netp);
		}
}


/* Packet handler; loops over waiting packet list while nothing
 * more important is happening.
 */

netxin(netp)
reg	net	*netp;

{	while (tstneq(&netp->n_ipq)) {
/*		if (*gwmsgq != NULL) {
			addtsk(syshnd, GWIPPRI, netxin, netp);
			return;
			} 	There should probably be some other mechanism
				put here to handle this. */
		(*netp->n_in)(rmq(&netp->n_ipq));
		}
}


/* Restart input for a given net. Basically, all you have to do is
 * queue up packets until the input queue limit is reached.
 * Note that you also have to create the packet buffers and set
 * up the iorbs.
 */

netistart(netp)
reg	net	*netp;

{	reg	iorb	*iob;

	netp->n_flg &= ~N_IWT;
	if ((netp->n_flg & N_UP) == 0) {
		iflog(L_ERRU)
			dolog(ntdwni, mksnet(netp));
		return;
		}

	while (netp->n_ipc < netp->n_maxip) {
		if ((iob = mkiorb(getbuf())) == NULL) {
			iflog(L_ERRU)
				dolog(ntipqnb, netp->n_ipc, mksnet(netp));
			if ((netp->n_flg & N_IRWT) == 0) {
			    netp->n_flg |= N_IRWT;
			    stime(netipro, netp, GWIRD, 400);
			    }
			return;
			}

		iob->i_devp = netp->n_idevp;
		iob->i_cmp = netp->n_irtn;
		iob->i_addr = (((byte *) iob) + pktoff);
		iob->i_breq = netp->n_max;
		iob->i_usr2 = (word)netp;

		if (netp->n_start != NULL)
			(*netp->n_start)(iob);
		  else {
			iob->i_addr -= netp->n_hsiz;
			iob->i_breq += netp->n_siz;
			}
		sio(iob);
		netp->n_ipc++;
		}
}


/* Restart input on a network after a wait after a new input buffer
 * could not be obtained. Note that buffers may have become free
 * in the interim and the network may already have been restarted
 * after some other input completed.
 */

netirstrt(netp)
reg	net	*netp;

{	void	netistart();

	netp->n_flg &= ~N_IRWT;
	if (netp->n_ipc == netp->n_maxip)
		return;
	if (nbfree == 0) {
		netp->n_flg |= N_IRWT;
		stime(netipro, netp, GWIRD, 400);
		return;
		}

	if ((netp->n_flg & N_IWT) == 0) {
		netp->n_flg |= N_IWT;
		addtsk(syshnd, GWISPRI, netistart, netp);
		}
}


/* Write completion causes the packet buffer to be freed if
 * there was no I/O error, and if packets remain for the network
 * netostart is queued for it.
 */

netout(iob)
reg	iorb	*iob;

{	reg	net	*netp;
		void	netostart();

	netp = mknet(iob->i_usr2);
	netp->n_opc--;
	netp->n_pkto++;
	netp->n_byto += iob->i_bxfr;

	if ((iob->i_stat & I_ERR) != 0) {
		netoerr(iob);
		return;
		}

	freebuf(iob);

	if (((netp->n_flg & N_OWT) != 0) || tsteq(&netp->n_opq))
		return;
	netp->n_flg |= N_OWT;
	addtsk(syshnd, GWOSPRI, netostart, netp);
}


/* An I/O error (which is assumed to mean the failure of the
 * hardware level ACK) means that the host was not ready for
 * the packet; if the packet has already been retransmitted
 * some maximum number of time it is dropped, otherwise it
 * will be requeued and the network's strategy routine will
 * retransmit it when it gets a chance.
 */

netoerr(iob)
reg	iorb	*iob;

{	reg	net	*netp;
		void	netostart();

	netp = mknet(iob->i_usr2);

	netp->n_ioout++;
	iob->i_usr1++;

	if (iob->i_usr1 > netp->n_maxr) {
		iflog(L_INFU)
			dolog(ntrtov, iob->i_usr3, mksnet(netp));
		netp->n_disc++;
		freebuf(iob);
		}
	  else {
		addhq(&netp->n_opq, iob);
		if ((netp->n_flg & N_OWT) == 0) {
			netp->n_flg |= N_OWT;
			addtsk(syshnd, GWOSPRI, netostart, netp);
			}
		}
}


/* Restart output on a network. All the work is done by the
 * per-network selection routine, which is responsible for
 * the queue management as well as access control, flow and
 * congestion control, etc.
 */

netostart(netp)
reg	net	*netp;

{	reg	iorb	*iob;

	netp->n_flg &= ~N_OWT;
	while (netp->n_opc < netp->n_maxop) {
		if ((iob = (*netp->n_get)(netp)) == NULL)
			return;
		if ((netp->n_flg & N_UP) == 0) {
			iflog(L_INFU)
				dolog(ntdwnd, iob->i_usr3, mksnet(netp));
			netp->n_disc++;
			freebuf(iob);
			return;
			}

		sio(iob);
		netp->n_opc++;
		}
}


/* Standard selection routine, which is responsible for
 * the queue management. The approach used here is suitable
 * for most local networks; it basically attempts to never
 * send two packets to the same person in a row.
 * If it can't find a packet for some other host then the code
 * uses a simple strategy. As long as the remote host is accepting
 * packets, it keeps taking them off the queue and sending them.
 * When it starts to get local flow control errors, it dallies the
 * network.
 * The exact working of the code are fairly complicated, and probably
 * broken, since it has to handle many special cases; e.g. if a packet
 * for some other host is added to the queue, it should be sent, but
 * if another one for this host is added it should not be.
 * The only complicated part to notice is that once a packet has accumulated
 * more than MXR errors (it is allowed a few, in case of real errors on
 * the network) each additional error causes the packet to be dallied.
 * The N_DLY flag is there to prevent the network being re-dallied
 * unnecessarily when extra packets for the same host are added to
 * the output queue.
 * The code is duplicated in the really strange fashion because
 * the optimizer was too stupid to produce good code given the
 * obvious code.
 */

iorb	*netget(netp)
reg	net	*netp;

{	reg	iorb	*iob, **iobp;

	if (tsteq(&netp->n_opq)) {
		netp->n_lsthst = NOHOST;
		return(NULL);
		}

	iobp = &(gethq(&netp->n_opq));
	if (netp->n_cmp != NULL)
		while ((iob = *iobp) != NULL) {
			if ((*netp->n_cmp)(iob->i_usr3, netp->n_lsthst)) {
				rmmq(&netp->n_opq, iobp, iob);
				netp->n_lsthst = iob->i_usr3;
				return(iob);
				}
			iobp = &iob->i_link;
			}
	  else
		while ((iob = *iobp) != NULL) {
			if (iob->i_usr3 != netp->n_lsthst) {
				rmmq(&netp->n_opq, iobp, iob);
				netp->n_lsthst = iob->i_usr3;
				return(iob);
				}
			iobp = &iob->i_link;
			}

	iob = gethq(&netp->n_opq);
	if (iob->i_usr1 > MXR) {
		if ((netp->n_flg & N_DLY) != 0)
			return(NULL);
		netp->n_flg |= N_DLY;
		stime(netdlyo, netp, XDLY, 400);
		return(NULL);
		}
	  else
		return(rmq(&netp->n_opq));
}


/* Restart output on a network after a dally; the network will be
 * restarted with the associativity with the last host cleared.
 * This will cause the first packet on the queue (the one that
 * was dallied) to be sent.
 * Note that if a packet was sent to another host in the interim,
 * this routine will be called unneccessarily, but that won't hurt.
 */

netorstrt(netp)
reg	net	*netp;

{	void	netostart();

	netp->n_flg &= ~N_DLY;
	netp->n_lsthst = NOHOST;
	if ((netp->n_flg & N_OWT) == 0) {
		netp->n_flg |= N_OWT;
		addtsk(syshnd, GWOSPRI, netostart, netp);
		}
}


/* This routine is responsible for adding packets to the O/P
 * queue. It returns the appropriate completion code. Note where
 * the packet is sent right away if the network is idle; this
 * is an attempt to maximize throughput by reducing network idle time.
 */

word	netsend(iob, netp)
reg	iorb	*iob;
reg	net	*netp;

{	reg	iorb	**iobp;
		char	*err;		/* Let optimizer collapse code */
		word	errc;
		void	netostart();

	if ((netp->n_flg & N_UP) == 0) {
		netp->n_disc++;
		err = ntdwn;
		errc = D_NTDN;
		iflog(L_INFU)
			dolog(err, iob->i_usr3, mksnet(netp));
		return(errc);
		}

	iob->i_link = NULL;
	iob->i_cmp = netp->n_ortn;
	iob->i_devp = netp->n_odevp;
	iob->i_usr2 = (word)netp;
	iob->i_usr1 = 0;

	if ((numq(&netp->n_opq) >= bufshr) && (nbfree <= buflow)) {
		netp->n_ovfout++;
		err = ntoodp;
		errc = D_OVFL;
		iflog(L_INFC)
			dolog(err, iob->i_usr3, mksnet(netp));
		return(errc);
		}

	addq(&netp->n_opq, iob);

	if (netp->n_opc == 0) {
		netostart(netp);
		return(D_OK);
		}
		
	if (((netp->n_flg & N_OWT) == 0) && (netp->n_opc < netp->n_maxop)) {
		netp->n_flg |= N_OWT;
		addtsk(syshnd, GWOSPRI, netostart, netp);
		}
	return(D_OK);
}
