/* Copyright 1986 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include	<notice.h>

/* ARPA network driver - contains interface modules for the
 * gateway process which actually talk to this particular
 * network. The Host-IMP protocol stuff is all in arnet.c.
 *
 * Notice that this code uses pieces of the default code in net.c.
 * Some changes to that code will kill this driver.
 * The driver uses netistart(), netostart() and netsend().
 * It has its own input and output completion handlers.
 *
 * Some run time checks seem like a waste, but they are there to catch
 * errors in the configuration file.
 *
 *
 * Special assumptions about 1822 device driver:
 *
 * 1. As well as setting I_ERR in the IORB, transmit and receive operation
 *	generate error codes (see MOSTBL-1.SML) depending on the type
 *	of error that occurred; i.e. overrun versus ready-line error.
 *	The exact error codes and their meanings are:
 *		OFLN: Imp Relay open - can be generated by either side,
 *			but must be by at least the input side.
 *		OVFL: Received packet too long.
 *		FLSH: Marks the subsequent fragments of a packet that overran.
 *
 * 2. The device supplies two routines accesses via the DCT that raise
 *	lower the host ready line. The pointers to these routines
 *	the DCT can be initialized by the device driver's receive
 *	side initialization routine, since an sio() is done to fill the
 *	input queue before the routines are used. The ready line
 *	operations do not cause IO completion signals.
 *	[pointers to these routines no longer in DCT.  now called directly.
 *	should be put back somettime.]
 */


#include	<types.h>
#include	<sys.h>
#include	<sysext.h>
#include	"../src/const.h"
#include	"../src/param.h"
#include	"../src/defs.h"
#include	"../src/macs.h"
#include	"../src/net.h"
#include	"../src/ext.h"
#include	"../in/in.h"
#include	"../in/inparam.h"
#include	"arpa.h"
#include	"arconst.h"



/* Log messages */

static char armtl[] =	"IMP len ovfl %d frm %d/%d nt %d int %s/%d\n";
static char armff[] =	"IMP frg ln %d flsh nt%do int %s/%d\n";
static char arlts[] =	"IMP msg trnc ln %d nt %d int %s/%d\n";
static char arblf[] =	"Bad ldr frmt %d/%d/%d/%d nt %d int %s/%d\n";
static char armts[] =	"Msg trnc ln %d clmd %d frm %d/%d nt %d int %s/%d\n";
static char arsst[] =	"Strng sbtyp %d reg msg frm %d/%d nt %d int %s/%d\n";
static char arul[] =	"Pkt unknwn lnk %d frm %d/%d nt %d int %s/%d\n";
static char artrc[] =	"ARPA TRC'd pkt frm %d/%d nt %d int %s/%d\n";
static char ariup[] =	"IMP up nt %d int %s/%d\n";
static char ariec[] =	"Ilg err cd frm IMP drvr";


/* Initialize IMP code. Called once by the main gateway. It
 * allocates the arpanet info structure and fills in fields
 * needed to synchonize with the IMP. The routine arsync
 * in arnet.c does all the work of getting the link up.
 *
 * This routine marks the network as being up in order to allow
 * netistart() to queue up some input packets.  Then arsync() is
 * called to initialize the link to the IMP.
 */
ar_init(netp)
reg	net	*netp;
{
    reg arneti	*aip;
    void	ar_iin();
    void	ar_iout();

    if (netp->n_type != ((unsb) T_ARPA))
      bughalt("ar_init, nt ARPA");

    if ((aip = mkarni(getmem(sizeof(arneti)))) == NULL)
      bughalt("Cnt get arneti strct");
    netp->n_pernet = (word)aip;

    netp->n_irtn = ar_iin;
    netp->n_ortn = ar_iout;
    aip->a_idwnc = 0;
    aip->a_cmi = 0;
    aip->a_cmo = 0;
    rfnm_init(netp, &aip->a_rfnm);

    aip->a_state = SUP;
    netp->n_flg = N_UP;
    netistart(netp);
    arsync(netp);
}


/* Per protocol initialization. Called after ar_init().
 * Merely stores the address since we can't check it until
 * the NOP's come in.
 */

ar_prinit(netp, prot, nm)
net	*netp;
unsw	prot;
word	nm;
{
    if (prot != P_IN)
      bughalt("Bd prt ARPA");

    ((arneti *)netp->n_pernet)->a_name = (inaddr *)nm;
}


/* This routine is called when we packet comes in from the 1822
 * interface. It just does simple preliminary processing and
 * starts tasks to handle everything else. A Device Offline error
 * that the IMP is probably down, so we go into resync mode.
 * After that runs, input buffers in the system will show up in
 * the restart handler.
 */
ar_iin(iob)
reg	iorb	*iob;
{
    reg	net	*netp;
    reg	arpkt	*pkt;
    unsw	errc;
    void	ar_xin();
    void	netistart();

    netp = mknet(iob->i_usr2);
    netp->n_ipc--;

    if ((errc = iob->i_stat & I_ERR) != 0) {
	pkt = mkar(iob->i_addr);

	switch(errc) {
	default:
	  bughalt(ariec);

      case ER_OFLN:
	  freebuf(iob);
	  if (mkarni(netp->n_pernet)->a_state == SUP)
	    arsync(netp);
	  return;

      case ER_OVFL:
	  netp->n_ioin++;
	  iflog(L_INFU)
	    dolog(armtl, bitoby(swab(pkt->ar_len)),
		  mksara(pkt), mksnet(netp));

      case ER_FLSH:
	  freebuf(iob);
	  iflog(L_INFC)
	    dolog(armff, iob->i_bxfr, mksnet(netp));
      }
    }
    else {
	if (tsteq(&netp->n_ipq))
	  addtsk(syshnd, ARIPPRI, ar_xin, 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. */
ar_xin(netp)
reg	net	*netp;
{
    while (tstneq(&netp->n_ipq)) {
	ar_in(rmq(&netp->n_ipq));
    }
}


/* This routine processes the packets that have been received.
 * It is actually several routines rolled into one.
 * The first routine tests for errors and records general statistics.
 * The second one checks the header and calls the right forwarder with
 * i_addr pointing at the inter-network header, and i_bxfr giving the
 * length of the inter-network packet. The code that checks the headers
 * uses knowledge about the placement of fields to compress out useless
 * checks that the optimizer does not catch.
 */
ar_in(iob)
reg	iorb	*iob;
{
    net	*netp;
    reg	arpkt	*pkt;
    reg	unsw	tmp;
    arneti	*aip;

    netp = mknet(iob->i_usr2);
    pkt = mkar(iob->i_addr);
    aip = mkarni(netp->n_pernet);

    if (iob->i_bxfr < ARCLEN) {
	iflog(L_ERRU)
	  dolog(arlts, iob->i_bxfr, mksnet(netp));
	dispkt(n_ioin);
    }

    if ((pkt->ar_frmt != AR_FMT) || (pkt->ar_net != 0) ||
	((pkt->ar_flags & (AR_UN1 | AR_HST | AR_ULRF)) != 0) ||
	((pkt->ar_htype & AR_UHT) != 0)) {
	    iflog(L_ERRU)
	      dolog(arblf, pkt->ar_frmt, pkt->ar_net, pkt->ar_flags,
		    pkt->ar_htype, mksnet(netp));
	    dispkt(n_disc);
	}

    if (pkt->ar_flags & AR_TRC)
      iflog(L_INFU)
	dolog(artrc, mksara(pkt), mksnet(netp));

    if (pkt->ar_mtype != MT_REG) {
	arspec(iob);
	return;
    }

    if (aip->a_state != SUP) {
	dispkt(n_disc);
    }

    tmp = (bitoby(swab(pkt->ar_len)) + sizeof(arpkt));
    if (iob->i_bxfr < tmp) {
	iflog(L_ERRU)
	  dolog(armts, iob->i_bxfr, tmp, mksara(pkt), mksnet(netp));
	dispkt(n_ioin);
    }

    netp->n_pkti++;
    netp->n_byti += iob->i_bxfr;

    tmp = (pkt->ar_sblk & AR_STM);
    if ((tmp != STR_NR) && (tmp != STR_UNC)) {
	iflog(L_ERRU)
	  dolog(arsst, tmp, mksara(pkt), mksnet(netp));
	dispkt(n_disc);
    }

    iob->i_addr += sizeof(arpkt);
    iob->i_bxfr -= sizeof(arpkt);

    if (mkunsb(pkt->ar_link) != ARL_INP) {
	iflog(L_INFC)
	  dolog(arul, mkunsb(pkt->ar_link), mksara(pkt), mksnet(netp));
	dispkt(n_disc);
    }
    
    inaddq(iob);
}


/* Routine that prepares a packet for output. Uses netsend to actually
 * queue the packet, etc. Assumes iob->i_addr and i_breq describe the
 * bytes occupied by the internetwork packet.
 */

word	ar_out(iob, netp, prot, nm)
reg	iorb	*iob;
	net	*netp;
	unsw	prot;
reg	inaddr	*nm;
{
    reg	arpkt	*pkt;
    unsw	len;

    if (prot != P_IN)
      bughalt("Nt IN prt ARPA Op");
    if (nm == ANYHOST)
      bughalt("No brdcst ARPA");

    pkt = mkar(iob->i_addr - sizeof(arpkt));
    iob->i_addr = (byte *)pkt;
    arprep(iob, MT_REG, iob->i_breq + sizeof(arpkt));

    if ((word)nm != NOHOST) {
	pkt->ar_host = nm->i_byte.i_byteu;
	pkt->ar_imp = swab(mkunsb(nm->i_byte.i_bytel));
	iob->i_usr3 = (nm->i_byte.i_byteu << 8) | nm->i_byte.i_bytel;
    }				/* IMP num only 8 bits in IP */
    else {
	pkt->ar_host = 0;
	pkt->ar_imp = 0;
	iob->i_usr3 = 0;
    }
    iob->i_usr5 = (word)systick;

    return(netsend(iob, netp));
}


/* Prepare a packet for output.
 * Note: iob->i_addr should point to the space for the arpanet header that
 * this routine will fill in.
 */
arprep(iob, mtype, len)
reg	iorb	*iob;
	unsb	mtype;		/* Message type */
	unsw	len;		/* Byte len of msg including arpa header */
{
    reg	arpkt	*pkt;

    pkt = mkar(iob->i_addr);

    pkt->ar_frmt = AR_FMT;
    pkt->ar_net = 0;
    pkt->ar_flags = 0;
    pkt->ar_mtype = mtype;
    pkt->ar_htype = HTR;
    pkt->ar_link = ARL_INP;
    pkt->ar_sblk = STR_NR;			/* should set sub-link (ID) */
    pkt->ar_len = swab((bytobi(len)));	/* convert to bits */

    iob->i_usr1 = 0;
    iob->i_usr3 = 0;
    iob->i_breq = roundup(len);
    if (iob->i_breq >= ARMAX)
      bughalt("Pkt too big ARPA");
}


/* Called when we get an output completion interrupt from the device driver.
 * An I/O error indicates that the IMP is down. In response we flush the
 * rest of the reusable output queue.
 * The packets already in the system output queue will trickle in, and be
 * thrown away, since arsync() sets the state to SDWN.
 * This also performs steps 6 and 7 of the IMP resynchronization procedure,
 * which includes any initialization that must be performed when the imp
 * comes back up (e.g., set flags, and restart input). It marks the net
 * up when the output queue drains (usually these wouuld just be NOP messages,
 * but due to complexity with the N_UP bit it could be a user packet) when
 * it's in 'coming up' state. Note that only I/O errors on the last operation
 * are significant as far as marking the net down, since the first few may
 * abort due to relay bounce.
 */
ar_iout(iob)
reg	iorb	*iob;
{
    reg	net	*netp;
    reg	arneti	*aip;
    void	netostart();

    netp = mknet(iob->i_usr2);
    aip = mkarni(netp->n_pernet);
    netp->n_opc--;

    if (iob->i_usr5 > TICKS/2)
      iflog(L_ERRC)
	dolog("ARPA pkt out time %d/%d sec\n", iob->i_usr5, TICKS);

    if (aip->a_state != SUP) {
	if ((aip->a_state == SNOP) && (netp->n_opc == 0)) {
	    aip->a_state = SUP;

	    if ((iob->i_stat & I_ERR) != 0) {
		if ((iob->i_stat & I_ERR) != ER_OFLN)
		  bughalt(ariec);
		freebuf(iob);
		arsync(netp);
		return;
	    }

	    iflog(L_INFU)
	      dolog(ariup, mksnet(netp));

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

	dispkt(n_pkto);
    }

    if ((iob->i_stat & I_ERR) != 0) {
	if ((iob->i_stat & I_ERR) != ER_OFLN)
	  bughalt(ariec);

	freebuf(iob);
	netp->n_disc++;
	while (tstneq(&netp->n_opq)) {
	    freebuf(rmq(&netp->n_opq));
	    netp->n_disc++;
	}
	arsync(netp);
	return;
    }

    netp->n_pkto++;
    netp->n_byto += iob->i_bxfr;
    freebuf(iob);

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


/* Select a packet from the output queue to send over the ARPAnet.
 * Packets that have been waiting for more than two seconds or are
 * RFNM blocked are just flushed to keep from over running the IMP. */
iorb *ar_get(netp)
reg net *netp;
{
    reg	arneti *aip;
    reg iorb *iob;

    aip = mkarni(netp->n_pernet);
    while (tstneq(&netp->n_opq)) {
	iob = rmq(&netp->n_opq);
	if (((systick - iob->i_usr5) >= 2 * TICKS) ||
	    rfnm_blocked(&aip->a_rfnm, iob->i_usr3)) {
		freebuf(iob);
	    }
	else {
	    rfnm_inc(&aip->a_rfnm, iob->i_usr3);
	    return (iob);
	}
    }
    return (NULL);
}
