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


/* This file contains the routines used to interact with the
 * non-transport features of the IMP.
 * It will eventually deal with flow control (RFNM counting), Dead
 * Host information, IMP going down, and telling the IMP about errors
 * we notice in messages or leaders.
 */



#include	<types.h>
#include	<sys.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	"arpa.h"
#include	"arconst.h"



/* Error and Info messages */

static char ardsc[] = "Discarding msg type %d/%d %d/%d from %d/%d\n";

void	arprep(/* iob, msg_type, opcode, length */);


/* Synchronize with IMP. Called whenever we think the IMP may be down (i.e.,
 * when an IO operation returns an error).
 * (Note: the gateway's Ready line is dropped when MOS is started (by the
 * RESET instruction), and is raised by the driver's first I/O operation.
 *
 * The stategy used to resynchronize with the imp is simplistic, but should
 * work even with a crufty imp implementation.  The steps are:
 *
 *	0. Setup receive side to discard all msgs and to check NOPs
 *	   for the correct leader format and host address.
 *	   Declare the network down.
 *	1. Drop host ready line to let imp know there is a problem.
 *	   Set state to SDWN.
 *	2. Wait two seconds so even an imp which polls the ready line
 *	   will notice it dropped.
 *	3. Raise host ready line.
 *	4. Wait two seconds for the same reason.
 *	5. Queue four NOPs for output.
 *	   Set state to SNOP.
 *	   Declare the network up to enable input queue refilling.
 *	6. Wait for output queue to become empty.
 *	7. Set state to SUP.
 *
 * Steps 6 and 7 are performed by ar_iout().
 * If the imp did not really come up, then the first IO operations will return
 * an error, and the whole process will start over at step zero.
 * Note: the same IO handlers are used during sync as during normal operation.
 */
arsync(netp)
reg	net	*netp;
{
    reg	iorb	*iob;
    reg arneti	*aip;
    void arraise();

    aip = mkarni(netp->n_pernet);
    iflog(L_INFU)
      dolog("IMP down on net %d intfc %s/%d\n", mksnet(netp));
    netp->n_flg &= ~N_UP;
    rfnm_clear(&aip->a_rfnm);
    aip->a_state = SDWN;
    aip->a_scmi = 0;
    aip->a_scmo = 0;
    aip->a_idwnc++;

    dmadr(netp->n_idevp);
    aip->a_state = SRDYL;

    stime(arraise, netp, RLTICS, ARSPRI);
}


/* Raise host ready line.  Steps 3 and 4 of resync. */
arraise(netp)
reg	net	*netp;
{
    reg	iorb	*iob;
    reg arneti	*aip;
    void	sndnops();

    dmarr(netp->n_idevp);
    aip = mkarni(netp->n_pernet);
    aip->a_state = SRDYR;

    stime(sndnops, netp, RLTICS, ARSPRI);
}


/* Send NOP messages to the IMP.
 * Step 5 of resync. */
sndnops(netp)
reg	net	*netp;
{
    reg	iorb	*iob;
    reg	arpkt	*pkt;
    void	netistart();
    arneti	*aip;
    int		i;

    aip = mkarni(netp->n_pernet);
    netp->n_flg |= N_UP;
    if ((netp->n_flg & N_IWT) == 0) {
	netp->n_flg |= N_IWT;
	addtsk(syshnd, GWISPRI, netistart, netp);
    }
    for (i=0 ; i<NNOP ; i++) {
	if ((iob = mkiorb(getbuf())) == NULL)
	  bughalt("Can't get ARPAnet NOP pkt");
	pkt = mkar((((byte *) iob) + pktoff) - sizeof(arpkt));
	iob->i_addr = ((byte *) pkt);

	arprep(iob, MT_NOP, ARCLEN);
	pkt->ar_sblk = 0;		/* want zero padding words */
	sndcntl(iob, netp);
    }
    aip->a_state = SNOP;
}


/* Send a control message to the imp.
 * Works even if the network is not up. */
sndcntl(iob, netp)
reg	iorb	*iob;
reg	net	*netp;
{
    reg arneti *aip;

    aip = mkarni(netp->n_pernet);
    iob->i_link = NULL;
    iob->i_cmp = netp->n_ortn;
    iob->i_devp = netp->n_odevp;
    iob->i_usr2 = (word)netp;

    sio(iob);
    aip->a_cmo++;
    netp->n_opc++;
    if (aip->a_state <= SNOP)
      aip->a_scmo++;
}


/* Handler for all non-regular IMP messages.
 * For now, discard them. */
arspec(iob)
iorb	*iob;
{
    reg	net	*netp;
    reg	arneti	*aip;
    reg	arpkt	*pkt;

    netp = mknet(iob->i_usr2);
    aip = mkarni(netp->n_pernet);
    pkt = mkar(iob->i_addr);
    
    aip->a_cmi++;
    if (aip->a_state != SUP) aip->a_scmi++;
    switch (pkt->ar_mtype)  {
    case MT_RFNM:
	rfnm_dec(&aip->a_rfnm,
		 (pkt->ar_host << 8) | mkunsb(swab(pkt->ar_imp)));
	break;

    case MT_NOP:
	if ((pkt->ar_imp  != (aip->a_name)->i_word.i_wordl) ||
	    (pkt->ar_host != mkunsb((aip->a_name)->i_byte.i_byteu))) {
		bughalt("Arpanet addr IP no mtch");
	    }
	iflog(L_TRCO)
	  dolog(ardsc, pkt->ar_mtype,
		(pkt->ar_sblk & AR_STM),
		pkt->ar_link&0377,
		(pkt->ar_sblk & AR_SLM)>>AR_SLSH,
		mksara(pkt));
	break;

    case MT_DEAD:
    case MT_STAT:
	rfnm_clr_hst(&aip->a_rfnm,
		     (pkt->ar_host << 8) | mkunsb(swab(pkt->ar_imp)));
	iflog(L_ERRU)
	  dolog(ardsc, pkt->ar_mtype,
		(pkt->ar_sblk & AR_STM),
		pkt->ar_link&0377,
		(pkt->ar_sblk & AR_SLM)>>AR_SLSH,
		mksara(pkt));
	break;

    default:
	iflog(L_INFU)
	  dolog(ardsc, pkt->ar_mtype,
		(pkt->ar_sblk & AR_STM),
		pkt->ar_link&0377,
		(pkt->ar_sblk & AR_SLM)>>AR_SLSH,
		mksara(pkt));
	break;
    }
    freebuf(iob);
}

/*
 * RFNM table maintenance routines.
 */

/* Initialize the structures. */
rfnm_init(netp, rt)
net *netp;
reg rtbl *rt;
{
    rt->r_netp = netp;
    rt->r_blks = NULL;
    rt->r_free = NULL;
    rt->r_flag = 0;
}

/* Clear all the RFNM counters */
rfnm_clear(rt)
reg rtbl *rt;
{
    reg rbkt *rb;

    /* Just move everything to the free list */
    if (rt->r_free == NULL)
      rt->r_free = rt->r_blks;
    else {
	for (rb = rt->r_free; rb->r_link != NULL; rb = rb->r_link)
	  ;
	rb->r_link = rt->r_blks;
    }
    rt->r_blks = NULL;
}

/* Clear the RFNM count for a specific host. */
rfnm_clr_hst(rt, host)
rtbl *rt;
word host;
{
    reg rbkt *rb, **prb;

    for (prb = &rt->r_blks, rb = rt->r_blks; rb != NULL;
	 prb = &rb->r_link, rb = rb->r_link)
      if (rb->r_addr == host)
	break;
    if (rb == NULL)
      return;			/* what else can I do? */

    *prb = rb->r_link;
    rb->r_link = rt->r_free;
    rt->r_free = rb;
}
	
/* Increment the count for host.  If there is no bucket for host then
 * create one.  If this puts the count at ARMXO then queue a timeout routine
 * to scan the list and check for links that have been blocked for too
 * long. */
rfnm_inc(rt, host)
rtbl *rt;
word host;
{
    reg rbkt *rb;
    ext unsl systod;
    void rfnm_timeout();

    for (rb = rt->r_blks; rb != NULL; rb = rb->r_link)
      if (rb->r_addr == host)
	break;

    if (rb == NULL) {
	if ((rb = rt->r_free) != NULL)
	  rt->r_free = rb->r_link;
	else
	  rb = (rbkt *)getmem(sizeof(rbkt));
	rb->r_count = 0;
	rb->r_link = rt->r_blks;
	rt->r_blks = rb;
    }

    rb->r_addr = host;
    if (++rb->r_count >= ARMXO) {
	iflog(L_INFC)
	  dolog("ARPA RFNM blk hst %d/%d\n",
		(rb->r_addr >> 8) & 0xff, rb->r_addr & 0xff);
	rb->r_time = systod;
	if (!rt->r_flag) {
	    rt->r_flag = 1;
	    stime(rfnm_timeout, rt, ARTMO, ARSPRI);
	}
    }
}

/* Decrements the count.  If this unblocks a host, then restart
 * output.  If the cont is now zero, then remove that block to the
 * free list. */
rfnm_dec(rt, host)
rtbl *rt;
word host;
{
    reg rbkt *rb, **prb;
    void netostart();

    for (prb = &rt->r_blks, rb = rt->r_blks; rb != NULL;
	 prb = &rb->r_link, rb = rb->r_link)
      if (rb->r_addr == host)
	break;
    if (rb == NULL)
      return;			/* what else can I do? */

    if (rb->r_count-- == ARMXO) {
	if ((rt->r_netp->n_flg & N_OWT) == 0) {
	    rt->r_netp->n_flg |= N_OWT;
	    addtsk(syshnd, GWOSPRI, netostart, rt->r_netp);
	}
    }
    else if (rb->r_count == 0) {
	*prb = rb->r_link;
	rb->r_link = rt->r_free;
	rt->r_free = rb;
    }
}

/* Returns TRUE if host is RFNM blocked else returns FALSE. */
rfnm_blocked(rt, host)
rtbl *rt;
reg word host;
{
    reg rbkt *rb;

    for (rb = rt->r_blks; rb != NULL; rb = rb->r_link)
      if (rb->r_addr == host) {
	  if (rb->r_count >= ARMXO)
	    return (TRUE);
	  else
	    return (FALSE);
      }
    return (FALSE);
}

/* Scan the entries looking for links which have been blocked too
 * long.  When done, if any hosts were unblocked then restart output
 * and if any hosts still blocked, reschedule timeout routine.
 */
rfnm_timeout(rt)
reg rtbl *rt;
{
    int blk_cnt, unblkd;
    reg rbkt *rb, **prb;
    ext unsl systod;
    void netostart();

    blk_cnt = unblkd = 0;
    for (prb = &rt->r_blks, rb = rt->r_blks; rb != NULL;
	 prb = &rb->r_link, rb = rb->r_link)
      if (rb->r_count >= ARMXO) {
	  if (systod - rb->r_time >= ARTMO) {
	      iflog(L_ERRU)
		dolog("ARPA RFNM tmo hst %d/%d\n",
		      (rb->r_addr >> 8) & 0xff, rb->r_addr & 0xff);
	      *prb = rb->r_link;
	      rb->r_link = rt->r_free;
	      rt->r_free = rb;
	      unblkd++;
	  }
	  else
	    blk_cnt++;
      }

    if ((unblkd != 0) && ((rt->r_netp->n_flg & N_OWT) == 0)) {
	rt->r_netp->n_flg |= N_OWT;
	addtsk(syshnd, GWOSPRI, netostart, rt->r_netp);
    }

    if (blk_cnt != 0)
      stime(rfnm_timeout, rt, ARTMO, ARSPRI);
    else
      rt->r_flag = 0;
}
