/* Copyright 1998 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
*    C.LLC - C source for bottom layer of NetBEUI
*
*    05-02-93      INH Original
*    22-08-95 1.57     No Tx sliding window
*    18-12-95 	       Some Network.C functions moved here
*/

/* Standard includes */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* RISCOS includes */

#include "kernel.h"
#include "os.h"
#include "swis.h"


/* Our includes */

#include "stdtypes.h"
#include "mbuf.h"
#include "sys/dcistructs.h"
#include "buflib.h"
#include "llc.h"
#include "stats.h"
#include "lanman.h"  /* For LM_pw value */

#include "LanMan_MH.h"

#ifdef DEBUG
  #ifdef TML
    #include "debug:tml.h"
  #endif
#endif

/* Private definitions ******************** */

typedef enum
{
  NO_CONNECT = 0,
  AWAIT_CONNECT,
  CONNECTED,
  AWAIT_DISCONNECT,
  FAILED
}
  LKSTATE;

/* ----------- */

typedef struct
{
  HCONN     hconn;
  LKSTATE   Status;
  NETADDR   farAddr;  /* Address of other end */
  BYTE      SAP;      /* Service access point */

  BUFQUEUE  TxPendingQ;
  int  Tx_Ns;          /* No of Tx packets sent */
  int  Tx_Outstanding; /* No of un-acked Tx packets */

  struct Timer T1;     /* Timer for acks from far end */
  int    T1Retries;
  bool   RcvReady;

  LLC_RCV_PROC RcvFunction;
  void *       RcvPrivate;
  int          Rx_Nr;    /* No. of packets received */

  struct Timer T2;     /* Timer for sending acks back */
}
  LLC_CONN;

/* Magic numbers ***************************************************** */

#define T2_VALUE        10
#define T1_VALUE       100
#define T1_RETRIES      20
#define CONNECT_TIME   100
#define CONNECT_TRIES    5
#define DISCONN_TIME   100
#define DISCONN_TRIES    3


/* IEEE 802.2 control bytes ************* */

#define UFORMAT_UA    0x63
#define UFORMAT_FRMR  0x87
#define UFORMAT_SABME 0x6F
#define UFORMAT_DISC  0x43
#define UFORMAT_POLL  0x10
#define UFORMAT_INFO  0x03

#define SFORMAT_RRDY  0x01
#define SFORMAT_RNRDY 0x05
#define SFORMAT_REJ   0x09

/* -------------- */

#define SAVE_INT_STATE   \
              int intstate = _kernel_irqs_disabled(); _kernel_irqs_off();

#define RESTORE_INT_STATE \
              if ( !intstate ) _kernel_irqs_on();

/* Callback routines ************************************************* */

static bool CallbackSet = false;

int LLC_CallbackFn_handler(_kernel_swi_regs *r, void *pw)
{
  (void) r;
  (void) pw;
  CallbackSet = false;
  LLC_BackgroundProcess();
  return 1;
}

/* --------------------- */

static void SetCallback()
{
  if (!CallbackSet)
  {
    CallbackSet = true;
    _swi(OS_AddCallBack, _IN(0)|_IN(1), (int)LLC_CallbackFn, LM_pw);
  }
}

/* Timer library ***************************************************** */

#define TickerV        0x1C
extern void TickerFn ( void );

volatile int         LLC_TickerTime;
static bool          TimerNeedsCheck;
static struct Timer *ActiveTimers;
static bool          TickerClaimed=false;

/* ------------------ */

/* Inserts a given timer at the head of the active timer list */

static void TimerIns ( struct Timer *pT )
{
  pT->status = TMR_ACTIVE;

  pT->prev = NULL;
  pT->next = ActiveTimers;
  if ( ActiveTimers != NULL ) ActiveTimers->prev = pT;
  ActiveTimers = pT;
}

/* ------------------ */

/* Deletes a given timer from the active timer list */

static void TimerDel ( struct Timer *pT )
{
  pT->status = TMR_INACTIVE;

  if ( pT->prev == NULL)     /* Start of list */
    ActiveTimers = pT->next;
  else
    pT->prev->next = pT->next;

  if ( pT->next != NULL )
    pT->next->prev = pT->prev;
}

/* -------------------------------- */

/* This may be called to give a running timer a new timeout,
   or to activate an inactive one. We have to lock out interrupts
   because TickerFn *and the callback* might happen halfway through,
   and fiddle with the active list.
*/

void TimerStart ( struct Timer *pT, int timeout )
{
  SAVE_INT_STATE;
  pT->expiretime = LLC_TickerTime + timeout;
  if ( pT->status != TMR_ACTIVE )
    TimerIns(pT);

  RESTORE_INT_STATE;
}

/* ------------------------------ */

/* Again, an interrupt might happen when this is in progress */

void TimerClear ( struct Timer *pT )
{
  SAVE_INT_STATE;
  if ( pT->status == TMR_ACTIVE )
    TimerDel(pT);

  RESTORE_INT_STATE;
}

/* ------------------------------ */

/* Used to initialise a timer structure. Do not use if timer is
   running.
*/

void TimerInit ( struct Timer *pT, pfnExpire pfE, void *pdata )
{
  pT->status = TMR_INACTIVE;
  pT->prev = NULL;
  pT->next = NULL;
  pT->expirefn = pfE;
  pT->prvdata = pdata;
}

/* ------------------------------------------- */

/* TimerCheck()

   This is called from BackgroundProcess(), either during a callback
   or while we're waiting in one of our routines. It does a re-check
   of all the active timers and calls the expiry functions of all
   those that have expired.

   The timer expiry functions are allowed to do what they want - they
   may add/delete timers from the active list, and (unlike receive
   packet processing) may call BackgroundProcess(), which makes
   TimerCheck() reentrant. In either event, when an expiry function
   has been called, we must restart the check from the top of the
   ActiveTimers list.
*/

static void TimerCheck(void)
{
  struct Timer *pT;

  if ( !TimerNeedsCheck )
    return;

restart:
  for ( pT = ActiveTimers; pT != NULL; pT = pT->next )
    if ( (LLC_TickerTime - pT->expiretime) >= 0 )
    {
      TimerDel(pT);
      pT->expirefn ( pT->prvdata );
      goto restart;
    }

  TimerNeedsCheck = false;
}

/* Ticker event handler  ------- */

/* This merely updates the time, and sees if any of our active
   timers have expired. If they have, it organises a callback.

   The 'TimerCallbackSet' flag is used (i) to stop the callback
   being set more than once, and (ii) to stop TickerFn_handler
   interrupting anything until the callback is complete.
*/

int TickerFn_handler ( _kernel_swi_regs *R, void *pw )
{
  struct Timer *pT;

  (void) pw;
  (void) R;

  LLC_TickerTime++;

  if (!TimerNeedsCheck)
  {
    for ( pT = ActiveTimers; pT != NULL; pT = pT->next )
    {
      if ( (LLC_TickerTime - pT->expiretime) >= 0 )
      {
        TimerNeedsCheck = true;
        SetCallback();
        break;
      }
    }
  }
  return 1;
}

/* -------------------------------- */


static void TimerSetup ( void )
{
  ActiveTimers = NULL;
  LLC_TickerTime = 0;
  _swi(OS_Claim, _INR(0,2), TickerV, (int)TickerFn, LM_pw);
  TickerClaimed = true;
}

/* -------------------------------- */

static void TimerShutdown ( void )
{
  ActiveTimers = NULL;
  if (TickerClaimed)
  {
    TickerClaimed = false;
    _swi(OS_Release, _INR(0,2), TickerV, (int)TickerFn, LM_pw);
  }
}

/* DCI4 driver interface functions *********************************** */

NETADDR         LLC_MachineAddress;
static BUFQUEUE LLC_RxRawQueue;

static int      Net_DriverSWIbase;
static int      Net_DriverUnitNo;
static bool	Net_Attached=false;
static unsigned Net_DriverFlags;
static NETADDR  Net_MultiAddr;
static const NETADDR *Net_MultiPtr;


/* -------------------------------- */

int LLC_GetMTU ( void )  /* Returns LLC layer's maximum packet size */
{
  /* We can transmit the maximum packet size offered by the driver,
     less 4 for the LLC header */

  _kernel_swi_regs R;

  R.r[0] = 0;
  R.r[1] = Net_DriverUnitNo;

  _kernel_swi ( Net_DriverSWIbase+DCI4GetNetworkMTU, &R, &R );
  return R.r[2]-4;
}

/* -------------------------------- */

/* TxSend will send a packet out onto the network
   straight away. It is called with a destination address and
   a pointer to an mbuf chain. The length of data will
   be worked out from the chain.
*/

static bool TxSend ( NETADDR *dest, BUFCHAIN pB, bool DeleteAfterUse )
{
  int bytecount=ChainLen(pB);

  debug1("    TxSend called for %d bytes to ",bytecount);
  debug3("%02x:%02x:%02x:",dest->b[0],dest->b[1],dest->b[2]);
  debug3("%02x:%02x:%02x\n",dest->b[3],dest->b[4],dest->b[5]);

  if ( dest == NULL || bytecount == 0 || bytecount > 1500 )
  {
    if ( DeleteAfterUse )
    {
      FreeChain(pB);
    }
    debug0("    Ditching this packet\n");
    return false;
  }

  STAT(STA_NETTX_PKTS);

  if ( DeleteAfterUse )
  {
    pB->m_list = NULL;
    _swi(Net_DriverSWIbase+DCI4Transmit,
                _INR(0,5),
                0,                /* Flags */
                Net_DriverUnitNo,
                bytecount,        /* FrameType */
                (int) pB,
                (int) dest,
                NULL /* Src Addr */ );
  }
  else
  {
    struct mbuf *pBsave = pB->m_list;
    pB->m_list = NULL;     /* Save m_list value */

    _swi(Net_DriverSWIbase+DCI4Transmit,
                _INR(0,5),
                TX_PROTOSDATA, /* Flags */
                Net_DriverUnitNo,
                bytecount,      /* FrameType */
                (int) pB,
                (int) dest,
                NULL /* Src Addr */ );

     pB->m_list = pBsave;  /* Restore m_list after use */
  }
  debug0("    Finished TxSend\n");
  return true;
}


/* Receive handler --------- */

/* This module maintains a 'raw' receive queue, which is
   filled up with Rx data at interrupt time. This is then
   processed with LLC_RcvProcess() at a safe later time
   (either during a callback or when calling BackgroundProcess()
   manually).
*/

int ReceiveFn_handler ( _kernel_swi_regs *R, void *pw )
{
  struct mbuf *pData = (struct mbuf *) R->r[1];
  struct mbuf *pDnext;

  (void) pw;

  STAT(STA_NETRX_EVTS);

  /* Add all data to RxRawQueue in approximate order. Note that
     QueueAdd corrupts m_list; we must save it first */

  while ( pData != NULL )
  {
    STAT(STA_NETRX_PKTS);
    pDnext = pData->m_list;
    QueueAdd ( &LLC_RxRawQueue, pData );
    pData = pDnext;
  }

  SetCallback();

  return 1;
}

/* ---------------------- */

static Dib *FindDCIDriver ( char *name, int unit )
{
  _kernel_swi_regs R;
  ChDib *pCD, *pCD2;
  Dib      *pD;

  /* Get list of network drivers -------------- */

  R.r[0] = 0;
  R.r[1] = Service_EnumerateNetworkDrivers;

  _kernel_swi ( OS_ServiceCall, &R, &R );

  /* Search for correct one --------- */

  pCD = (ChDib *)R.r[0];
  pD  = NULL;

  while ( pCD != NULL )
  {
    if ( unit == pCD->chd_dib->dib_unit &&
         strcmp ( name, (char *) pCD->chd_dib->dib_name ) == 0 )
    {
      pD = pCD->chd_dib;
    }

    /* Free 'chaindib' back to RMA */
    pCD2 = pCD->chd_next;
    free ( pCD );
    pCD = pCD2;
  }

  return pD;
}


/* DCI4 initialisation ----------- */

#if DCIVERSION < 404
# error "Multicast filtering requires DCI v4.04 or later (TCPIPLibs-5_01 or later)"
#endif
static void DetachDriver(void);


err_t LLC_AttachDriver( char *name, const NETADDR *Multi_Addr )
{
  _kernel_swi_regs R;
  Dib      *pD;
  char dname[20];
  int filter_flags = FILTER_NO_UNSAFE | FILTER_CLAIM;

  if ( name == NULL ||
       sscanf( name, "%[^0123456789]%d", dname, &Net_DriverUnitNo) != 2 )
    return EDRIVERNAME;

  debug2("  Using driver '%s' unit #%d\n", dname, Net_DriverUnitNo );

  pD = FindDCIDriver ( dname, Net_DriverUnitNo );
  if ( pD == NULL )
  {
    debug0("    Error - unable to match driver name\n");
    return EDRIVERNAME;
  }

  if ( pD->dib_swibase==0 )
  {
    debug0("    Error - unable to get driver SWIbase\n");
    return EDRIVERVER;
  }

  Net_DriverSWIbase = (pD->dib_swibase | os_X);

  debug1("  Driver SWI base %Xh\n", Net_DriverSWIbase & ~os_X);

  /* Get driver interface version: must be major version 4 */

  R.r[0] = 0;
  _kernel_swi ( Net_DriverSWIbase+DCI4Version, &R, &R );

  debug1("  Driver version %d\n", R.r[1]);

  if ( R.r[1] < 400 || R.r[1] > 499 )
    return EDRIVERVER;

  /* Check flags */

  R.r[0] = 0;
  R.r[1] = Net_DriverUnitNo;
  _kernel_swi ( Net_DriverSWIbase+DCI4Inquire, &R, &R );

  Net_DriverFlags = R.r[2];
  debug1("  Driver flags: %Xh\n", R.r[2] );
  if ( (R.r[2] & (INQ_MULTICAST | INQ_HWADDRVALID) )
              != (INQ_MULTICAST | INQ_HWADDRVALID) )
    return EDRIVERTYPE;

  /* Save address */

  memcpy ( LLC_MachineAddress.b, pD->dib_address, 6 );

  /* Can the driver perform h/w multicast filtering? */

  if (Net_DriverFlags & INQ_FILTERMCAST) {
    filter_flags |= FILTER_SPECIFIC_MCAST;
  }
  else {
    filter_flags |= FILTER_ALL_MCAST;
  }

  /* Now request ISO packets */

  R.r[0] = filter_flags; /* Flags */
  R.r[1] = Net_DriverUnitNo;
  R.r[2] = 0;
  SET_FRAMELEVEL(R.r[2], FRMLVL_IEEE);      /* Need 802.3 frames */
  R.r[3] = ADDRLVL_MULTICAST;
  R.r[4] = ERRLVL_NO_ERRORS;
  R.r[5] = LM_pw;            /* Protocol handle (our PW) */
  R.r[6] = (int) ReceiveFn;        /* Receive handler function */

  if ( _kernel_swi ( Net_DriverSWIbase + DCI4Filter, &R, &R ) != NULL ) {
    debug1("    Filter fail: %s\n", _kernel_last_oserror()->errmess);
    return EPACKETTYPE;
  }

  Net_Attached = true;

  if (Net_DriverFlags & INQ_FILTERMCAST) {
    if (Multi_Addr) {
      Net_MultiAddr = *Multi_Addr;
      Net_MultiPtr = &Net_MultiAddr;
      R.r[4] = R.r[3] = (int) Net_MultiPtr;
      R.r[0] = MULTICAST_ADDR_REQ | MULTICAST_SPECIFIC_ADDR;
    }
    else {
      Net_MultiPtr = NULL;
      R.r[0] = MULTICAST_ADDR_REQ | MULTICAST_ALL_ADDR;
      R.r[4] = R.r[3] = 0;
    }
    R.r[1] = Net_DriverUnitNo;
    R.r[2] = 0;
    SET_FRAMELEVEL(R.r[2], FRMLVL_IEEE);      /* Need 802.3 frames */
    R.r[5] = LM_pw;
    R.r[6] = (int) ReceiveFn;
    if ( _kernel_swi ( Net_DriverSWIbase + DCI4MulticastRequest, &R, &R) != NULL ) {
      DetachDriver();
      Net_Attached = false;
      return EPACKETTYPE;
    }
  }

  return OK;
}

/* --------------------------------- */

static void DetachDriver()
{
  _kernel_swi_regs R;

  if ( Net_Attached )
  {
    int filter_flags = FILTER_NO_UNSAFE | FILTER_RELEASE;

    /* Was the driver performing h/w multicast filtering? Release claims! */

    if (Net_DriverFlags & INQ_FILTERMCAST) {
      R.r[0] = MULTICAST_ADDR_REL | (Net_MultiPtr ? MULTICAST_SPECIFIC_ADDR : MULTICAST_ALL_ADDR);
      R.r[1] = Net_DriverUnitNo;
      R.r[2] = 0;
      SET_FRAMELEVEL(R.r[2], FRMLVL_IEEE);      /* Need 802.3 frames */
      R.r[3] = (int) Net_MultiPtr;
      R.r[4] = (int) Net_MultiPtr;
      R.r[5] = LM_pw;
      R.r[6] = (int) ReceiveFn;
      debug0("    Releasing driver multicast filter\n");
      (void) _kernel_swi ( Net_DriverSWIbase + DCI4MulticastRequest, &R, &R);
      filter_flags |= FILTER_SPECIFIC_MCAST;
    }
    else {
      filter_flags |= FILTER_ALL_MCAST;
    }

    debug0("    Releasing driver claim\n");
    Net_Attached = false;
    R.r[0] = filter_flags; /* Flags */
    R.r[1] = Net_DriverUnitNo;
    R.r[2] = 0;
    SET_FRAMELEVEL(R.r[2], FRMLVL_IEEE);      /* Need 802.3 frames */
    R.r[3] = ADDRLVL_MULTICAST;
    R.r[4] = ERRLVL_NO_ERRORS;
    R.r[5] = LM_pw;            /* Protocol handle (our PW) */
    R.r[6] = (int) ReceiveFn;        /* Receive handler function */

    _kernel_swi ( Net_DriverSWIbase + DCI4Filter, &R, &R );
  }
}


/* Receive processing ************************************************ */


/* Subroutines ------------------------------------------ */

static bool Match ( NETADDR *pa, NETADDR *pb )
{
  int i;
  for ( i=5; i>=0; i-- )
    if ( pa->b[i] != pb->b[i] )
      return false;

  return true;
}

/* ----------------------------- */

/* LinkFail():

   Used to free everything on the queues & stop
   timers (etc), when a link is going down.
*/

static void LinkFail ( LLC_CONN *pC )
{
  QueueFlush ( &(pC->TxPendingQ) );

  /* Stop timers */
  TimerClear( &pC->T1 );
  TimerClear( &pC->T2 );
  pC->Status = FAILED;
}


/* Unnumbered-Information (UI) processing ---------------------------- */


/* Connections for Unnumbered-information frames */
#define MAX_UI_CONN 6
static BYTE        UI_SAPtbl      [MAX_UI_CONN];
static UI_RCV_PROC UI_RcvFunction [MAX_UI_CONN];
static void *      UI_PrivateWord [MAX_UI_CONN];



static void DispatchUI ( NETADDR *srcaddr, int SAP, BUFCHAIN pB )
{
  int i;

  SAP &= 0xFE;

  for ( i=0; i<MAX_UI_CONN; i++ )
   if ( UI_SAPtbl[i] == SAP && UI_RcvFunction[i] != NULL )
   {
     UI_RcvFunction[i] ( srcaddr, pB, UI_PrivateWord[i] );
     return;
   }

  /* No takers for packet */
  FreeChain(pB);
}

/* ------------------- */

err_t UI_SetReceiver ( int SAP, UI_RCV_PROC RcvFn, void *private )
{
  int i;

  SAP &= 0xFE;

  /* Replace handler for existing Service Access Point? */

  for ( i=0; i<MAX_UI_CONN; i++ )
    if ( UI_SAPtbl[i] == SAP )
    {
      UI_RcvFunction[i] = RcvFn;
      UI_PrivateWord[i] = private;
      return OK;
    }

  /* Add new handler ? */

  for ( i=0; i<MAX_UI_CONN; i++ )
    if ( UI_SAPtbl[i] == 0 )
    {
      UI_SAPtbl[i] = SAP;
      UI_RcvFunction[i] = RcvFn;
      UI_PrivateWord[i] = private;
      return OK;
    }

  return ENOHANDLES;
}


/* ------------------------------------------------- */

err_t UI_SendData ( NETADDR *dst, int SAP, BUFCHAIN pB )
{
  BYTE LLCHdr[3];

  if ( pB == NULL )
    return EBADPARAM;

  LLCHdr[0] = SAP;
  LLCHdr[1] = (SAP & 0xFE);
  LLCHdr[2] = UFORMAT_INFO;    /* Un-numbered information frame */
  pB = AddChain ( pB, LLCHdr, 3 );

  if ( pB == NULL )
    return EOUTOFMEM;

  TxSend ( dst, pB, true );
  return OK;
}


/* LLC link processing ------------------------------------------ */

/* Connections for 802.2 logical layer links */
#define MAX_LLC_CONN 6
static LLC_CONN LLC_Connections[MAX_LLC_CONN];

/* ------------------------------ */

/* GetConnect():

   Returns a connection for a given source address and
     SSAP, or NULL if no connection exists.
*/

static LLC_CONN *GetConnect ( NETADDR *src, int SSAP )
{
  int i;
  LLC_CONN *pC = LLC_Connections;

  SSAP &= 0xFE;

  for (i=0; i<MAX_LLC_CONN; i++)
  {
    if ( (pC->Status != NO_CONNECT) &&
         Match(src, &(pC->farAddr) ) &&
         ( SSAP == pC->SAP )
       )
      return pC;
    pC++;
  }

  return NULL;
}


/* ---------------------- */

/* RcvProcessNr():

   This is called whenever we get a Nr value acknowledgement from the other
   end, in an I-frame or S-frame. When the right ack number comes along, we
   take the corresponding packet out of the Tx Pending queue.
*/

static void RcvProcessNr ( LLC_CONN *pC, int Nr )
{
  int n_pkts_acked;

  /* The Nr for a packet is one more than the Ns used to
     send it (= the Ns of the next packet to be sent)  */

  n_pkts_acked = pC->Tx_Outstanding - ((pC->Tx_Ns - Nr) & 0x7F);

  while ( n_pkts_acked-- > 0 )
  {
    BUFCHAIN pB = QueueGet ( &(pC->TxPendingQ) );
    pC->Tx_Outstanding--;
    FreeChain ( pB );
    STAT(STA_LLCRX_PKTACKS);
  }

  /* Has this emptied the Tx pending queue? */

  if ( QueuePeek( &(pC->TxPendingQ) ) == NULL )
  {
    TimerClear(&pC->T1);    /* If so, stop timer */
    pC->T1Retries = T1_RETRIES;
  }
}

/* --------------------- */

/* CheckResend re-sends the first packet in the TX Pending queue for
   a channel. If nothing is present, all will be well.
*/

static void CheckResend ( LLC_CONN *pC )
{
  BUFCHAIN pB;
  BYTE LLCHdr[4];

  pB = QueuePeek ( &(pC->TxPendingQ) );
  if ( pB == NULL )
    return;

  STAT(STA_LLCRX_RESENDS);

  pB = GetData( pB, LLCHdr, 4 ); /* If error, not much we can do */

  /* Recreate LLC header */

  LLCHdr[0] = pC->SAP;  /* Restate the obvious */
  LLCHdr[1] = pC->SAP;  /* Make it a 'command' pkt */
  /* Retain original Ns value */
  LLCHdr[3] = (pC->Rx_Nr << 1);

  pB = AddChain ( pB, LLCHdr, 4 );
  if ( pB == NULL )
    STAT(STA_SERIOUS_BARF);
  else
  {
    TimerStart( &pC->T1, T1_VALUE );      /* Start timer for ack */
    TxSend ( &(pC->farAddr), pB, false );
  }
}

/* ---------------------- */

/* Sends an ack for all the packets received so far */

static void SendAck ( LLC_CONN *pC, bool Final )
{
  BUFCHAIN pB;
  BYTE LLCHdr[4];

  /* Generate short packet */

  LLCHdr[0] = pC->SAP;          /* Destination Access Point */
  LLCHdr[1] = pC->SAP   | 1 ;   /* Send it as 'response' */
  LLCHdr[2] = SFORMAT_RRDY;     /* Receive ready */
  LLCHdr[3] = (pC->Rx_Nr << 1) | (Final ? 1 : 0); /* Set 'Final' bit */

    /* Put it into data chain */

  pB = AddChain ( NULL, LLCHdr, 4 );
  if ( pB==NULL ) /* Run out of buffers */
    return;

  TimerClear(&pC->T2);  /* Clear ack timer */

  TxSend ( &(pC->farAddr), pB, true );
}

/* Send Poll command (request for acks on received packets) */

static void SendPoll ( LLC_CONN *pC )
{
  BUFCHAIN pB;
  BYTE LLCHdr[4];

  LLCHdr[0] = pC->SAP;    /* Destination Service Access Point */
  LLCHdr[1] = pC->SAP;    /* Send it as 'command' */
  LLCHdr[2] = SFORMAT_RRDY;       /* Receive ready */
  LLCHdr[3] = (pC->Rx_Nr << 1) | 1; /* Send 'Poll' bit */

  TimerStart( &pC->T1, T1_VALUE );      /* Start timer for ack */

  /* Put it into data chain */

  pB = AddChain ( NULL, LLCHdr, 4 );
  if ( pB==NULL ) /* Run out of buffers */
    return;

  TxSend ( &(pC->farAddr), pB, true );
}

/* --------------------- */

static void SendReject ( LLC_CONN *pC )
{
  BUFCHAIN pB;
  BYTE LLCHdr[4];

  LLCHdr[0] = pC->SAP;
  LLCHdr[1] = pC->SAP | 1; /* Use as response */
  LLCHdr[2] = SFORMAT_REJ;        /* REJ value */
  LLCHdr[3] = (pC->Rx_Nr << 1); /* Don't Set Final bit 15/9/95*/

  pB = AddChain ( NULL, LLCHdr, 4 );
  if ( pB != NULL )
    TxSend ( &(pC->farAddr), pB, true );
}

/* ---------------------- */

static void SendUformat ( LLC_CONN *pC, int Cmd )
{
  BUFCHAIN pB;
  BYTE LLCHdr[3];

  LLCHdr[0] = pC->SAP;
  LLCHdr[1] = pC->SAP;
  LLCHdr[2] = Cmd;
  pB = AddChain ( NULL, LLCHdr, 3 );
  if ( pB != NULL )
    TxSend ( &(pC->farAddr), pB, true );
}

/* Receive processing for S, I and U-format frames ---------------------- */


/* Processing for received I-format (information) and S-format frames. */

static void LLC_IorSformat ( NETADDR *src, BYTE *LLCHdr, BUFCHAIN pB )
{
  LLC_CONN *pC;
  int SSAP = LLCHdr[1];
  int Nr   = (LLCHdr[3] >> 1) & 0x7F;

  pC = GetConnect ( src, SSAP );

  if ( pC == NULL || pC->Status != CONNECTED ) /* Not recognised: drop it */
  {
    STAT(STA_LLCRX_NOCONN);
    FreeChain(pB);
    return;
  }

  /* Remove packets from Tx Pending queue according to Nr */

  RcvProcessNr ( pC, Nr );

  /* Process received command or information */

  if ( LLCHdr[2] & 1 ) /* It's an S-format */
  {
    STAT(STA_LLCRX_SFORMAT);
    FreeChain(pB);

    switch ( LLCHdr[2] )
    {
      case SFORMAT_RRDY:  /* Receive ready */
        pC->RcvReady = true;
        break;

      case SFORMAT_RNRDY:  /* Receive not ready */
        pC->RcvReady = false;
        break;

      case SFORMAT_REJ:  /* Reject - resend first in Tx queue */
        debug0("Reject from other end\n");
        STAT(STA_LLCRX_REJECTS);
        CheckResend(pC);
        return;

      default:
        break;
    }
  }
  else /* It's an I-format (received data) */
  {
    int Ns   = (LLCHdr[2] >> 1) & 0x7F;

    STAT(STA_LLCRX_IFORMAT);

    if ( Ns == (pC->Rx_Nr & 0x7F) )  /* It's OK */
    {
      debug0(".");
      STAT ( STA_LLCRX_DATA );
      pC->Rx_Nr++;

      if ( pC->RcvFunction != NULL )
        pC->RcvFunction ( pC->hconn, pB, pC->RcvPrivate );
      else
        FreeChain(pB);

      /* Start ack timer, if not already running */

      if ( !TimerRunning(&pC->T2) )
        TimerStart ( &pC->T2, T2_VALUE );
    }
    else /* Send a REJ */
    {
      debug2("Rx Reject: we got %d, want %d", Ns, (pC->Rx_Nr & 0x7F) );
      STAT(STA_LLCRX_REJDATA);
      FreeChain(pB);
      SendReject (pC);
      return;
    }
  }

  /* Check for poll/final bit */

  if ( LLCHdr[3] & 1 )     /* Is Poll/Final bit set? */
  {
     if ( SSAP & 1 )   /* Response => it's a Final bit */
     {
       CheckResend(pC);
     }
     else              /* Command => it's a Poll bit */
       SendAck ( pC, true );
  }

}

/* ---------------- */


static void LLC_Uformat ( NETADDR *src, BYTE *LLCHdr )
{
   LLC_CONN *pC = GetConnect ( src, LLCHdr[1] /* SSAP */ );
   if ( pC == NULL )   /* Not recognised: drop it */
     return;

   switch ( LLCHdr[2] & 0xEF )
   {
     case UFORMAT_UA:
       if ( pC->Status == AWAIT_CONNECT )
       {
         STAT(STA_LLCRX_CONN);
         pC->Status = CONNECTED;
       }
       else if ( pC->Status == AWAIT_DISCONNECT )
       {
         STAT(STA_LLCRX_DISCON);
         pC->Status   = NO_CONNECT;
       }
       break;

     case UFORMAT_FRMR: /* Oh dear */
       debug0("Link failed - FRMR packet");
       STAT(STA_LLCRX_FRMR);
       LinkFail(pC);
       break;

     case UFORMAT_DISC:
       debug0("Link failed - other end sent DISC");
       STAT(STA_LLCRX_DISC);
       SendUformat ( pC, UFORMAT_UA | UFORMAT_POLL );
       LinkFail(pC);
       break;

     default: /* Dunno. Hope it'll go away */
       STAT(STA_LLCRX_UNKNOWNU);
       break;
   }
}

/* Timer expiry routines ----------------------------------- */

/* T1 is started whenever we send data to the other end.
   When all the data sent has been acknowledged, the timer
   is cleared. If it isn't, and T1 expires, we a 'poll' cmd
   is sent to the other end to check its receive status.
*/

static void T1ExpiryCallback ( void * data )
{
  LLC_CONN *pC = (LLC_CONN *)data;

  if ( pC->Status != CONNECTED ) /* Brief check for rogue timers! */
    return;

  STAT(STA_LLC_T1EXPIRE);

  if ( --(pC->T1Retries) > 0 )  /* Still has a chance! */
    SendPoll ( pC );            /* Will restart timer */
  else                          /* Oh dear! */
  {
    SendUformat ( pC, UFORMAT_DISC | UFORMAT_POLL );
    STAT(STA_LLC_T1FAIL);
    debug0("Link failed - T1 expired");
    LinkFail(pC);
  }
}

/* ---------------- */

/* T2 expires during the normal run of play when we've
   received data from the other end, but haven't acked
   it yet. This will send an acknowledge back.
*/

static void T2ExpiryCallback ( void * data )
{
  LLC_CONN *pC = (LLC_CONN *)data;

  debug2("\nT2 Expired, ack for %d, next send=%d\n", (pC->Rx_Nr) & 127,
             (pC->Tx_Ns) & 127);
  if ( pC->Status == CONNECTED ) /* Brief check for rogue timers! */
    SendAck(pC, false);
}





/* General receive processing ------------------------------- */

/* LLC_RcvProcess() ------------------------------ */

/* RxProcess is called to process a packets from the
   Rx input queue, and dispatch them to their various
   handlers or LLC connections. This is called from
   a 'safe' (non-interrupt, etc) point by the Network
   module, so we needn't worry about reentrancy issues.
*/

static void LLC_RcvProcess( NETADDR *src, BUFCHAIN pB )
{
  BYTE LLChdr[4];

  STAT(STA_LLCRX_PKTS);

  /* Get 802.2 header */

  pB = GetData(pB, LLChdr, 3);
  if ( pB == NULL )
  {
    STAT(STA_LLCRX_TOOSHORT);
    return;
  }

  /* Do I or S-format */

  if ( (LLChdr[2] & 3) != 3 )  /* I-format or S-format */
  {
     pB = GetData(pB, &LLChdr[3], 1);  /* 4-byte header */
     if ( pB != NULL )
       LLC_IorSformat ( src, LLChdr, pB );
     else
       STAT(STA_LLCRX_TOOSHORT);
     return;
  }

  /* U-type */

  if ( (LLChdr[2] & 0xEF) == UFORMAT_INFO )  /* UI frame */
  {
    STAT(STA_LLCRX_UIDATA);
    DispatchUI ( src, LLChdr[0], pB );
    return;
  }

  /* Misc U-format packet */

  STAT(STA_LLCRX_UFORMAT);
  FreeChain(pB);
  LLC_Uformat ( src, LLChdr );
}

/* ----------------------------------- */

static void ReceiveProcess (BUFCHAIN pB)
{
  BUFCHAIN pB2;
  RxHdr *pRH;
  NETADDR srcaddr;
  int     len;

  pRH = mtod ( pB, RxHdr *);

  /* Extract source address & length  */
  memcpy ( srcaddr.b, pRH->rx_src_addr, 6 );

  len = pRH->rx_frame_type;
  debug1("Got pkt len %xh", len);

  /* Ignore our own Tx's */
  if ( Match ( &srcaddr, &LLC_MachineAddress ) )
  {
    STAT(STA_NETRX_OWNTX);
    debug0("Own transmission:");
    FreeChain(pB);
    return;
  }

  /* Chop off first mbuf from chain & free it*/

  pB2 = pB;
  pB = pB->m_next;
  FreeBuf (pB2);

  /* Chop off any pad bytes */

  for ( pB2 = pB; pB2 != NULL; pB2 = pB2->m_next )
  {
    if ( len < pB2->m_len )
      pB2->m_len = len;

    len -= pB2->m_len;
  }

  /* Process packet in LLC layer */
  LLC_RcvProcess( &srcaddr, pB);
}

/* LLC_BackgroundProcess() -------------------------- */

void LLC_BackgroundProcess()
{
  BUFCHAIN pB;

  /* Receive processing */

  while ( pB = QueueGet(&LLC_RxRawQueue), pB != NULL )
  {
    ReceiveProcess(pB);
  }

  /* Timer processing */

  TimerCheck();
}


/* LLC Public routines ============================================== */

err_t LLC_SendData ( HCONN hc, BUFCHAIN data )
{
  LLC_CONN *pC;
  BYTE LLCHdr[4];

  /* Validate handle -------------------- */

  if ( hc < 0 || hc >= MAX_LLC_CONN || data == NULL )
  {
    FreeChain(data);
    return EBADPARAM;
  }

  pC = LLC_Connections + hc;

  if ( pC->Status != CONNECTED )
  {
    FreeChain(data);

    if ( pC->Status == FAILED )
      return ELINKFAILED;
    else
      return ENOCONN;
  }

  STAT(STA_LLC_TXPACKETS);

  /* Check for other end receive ready ------- */

  /* If receive not ready, poll the other end. This will also
     set the T1 timer going, which will send a Poll regularly
     until the far end acknowledges, and automatically fail if
     it never responds.

     If the T1 timer is already going, we just wait for it
     to complete (successfully or otherwise).
  */

  while ( 1 )
  {
    LLC_BackgroundProcess();   /* Ensure we're up-to-date with Rx */

    if ( pC->Status != CONNECTED )
    {
      FreeChain(data);
      return ELINKFAILED;
    }

    if ( pC->RcvReady )    /* If receive is ready, we're OK */
      break;

    if ( !TimerRunning(&pC->T1) )
      SendPoll(pC);        /* Will start timer */
  }

  /* Prepare packet to send */

  LLCHdr[0] = pC->SAP;
  LLCHdr[1] = pC->SAP;       /* Send as 'Command' */
  LLCHdr[2] = (pC->Tx_Ns << 1);  /* I-format frame */
  LLCHdr[3] = (pC->Rx_Nr << 1);  /* No Poll bit */

  /* Check transmit window. For now, leave it at 2 pkts */

  if ( pC->Tx_Outstanding >= 1 )
  {
    LLCHdr[3] |= 1;
    pC->RcvReady = false;
  }

  data = AddChain ( data, LLCHdr, 4 );
  if ( data == NULL )
    return EOUTOFMEM;

  /* Put in pending queue */
  pC->Tx_Ns++;
  pC->Tx_Outstanding++;

  QueueAdd ( &(pC->TxPendingQ), data );

  /* Actually transmit data */

  TxSend ( &(pC->farAddr), data, false );

  /* (Re)start timer for other end to ack packet */

  TimerStart(&pC->T1, T1_VALUE);

  return OK;
}

/* ----------------------------------------- */

err_t LLC_OpenLink ( NETADDR *dst, int DSAP, LLC_RCV_PROC RcvFn,
            void *Private, HCONN *pHandleOut )
{
  int i;
  int timer;

  LLC_CONN *pC;

  STAT(STA_LLC_OPENREQ);

  DSAP &= 0xFE;
  pC = GetConnect ( dst, DSAP );
  if ( pC != NULL )
    return ELINKEXISTS;

  for ( i=0; i<MAX_LLC_CONN; i++ )
  {
    pC = &LLC_Connections[i];
    if ( pC->Status == NO_CONNECT )
      goto GotGap;
  }

  return ENOHANDLES;

GotGap:
  pC->hconn       = i;
  pC->farAddr     = *dst;
  pC->SAP         = DSAP;
  pC->RcvFunction = RcvFn;
  pC->RcvPrivate = Private;

  QueueFlush ( &(pC->TxPendingQ) ); /* Just in case! */

  pC->Tx_Ns = pC->Rx_Nr = 0;
  pC->Tx_Outstanding = 0;

  TimerClear(&pC->T1);
  TimerClear(&pC->T2);
  pC->RcvReady = false;

  /* Try 'connect' command to other end, with retries */

  for ( i=0; i<CONNECT_TRIES; i++ )
  {
    pC->Status   = AWAIT_CONNECT;
    SendUformat ( pC, UFORMAT_SABME | UFORMAT_POLL );

    timer = LLC_TickerTime;

    do
    {
      LLC_BackgroundProcess();
      if ( pC->Status == CONNECTED )
        goto LinkOpened;
    }
      while ( (LLC_TickerTime - timer) < CONNECT_TIME );
  }

  /* Get here if failed */

  pC->Status = NO_CONNECT;
  return ETIMEOUT;


LinkOpened:

  /* Else: see if the other end's ready - this will give it
     approx 5 seconds to respond */

  pC->T1Retries = T1_RETRIES;
  SendPoll ( pC );

  *pHandleOut = pC->hconn;
  return OK;
}

/* ------------------------ */

err_t LLC_CloseLink ( HCONN hc )
{
  int i, timer;

  LLC_CONN *pC;

  STAT(STA_LLC_CLOSEREQ);

  if ( hc < 0 || hc >= MAX_LLC_CONN )
    return EBADPARAM;

  pC = LLC_Connections + hc;

  if ( pC->Status == NO_CONNECT )
    return ENOCONN;

  /* Closing a failed link? */

  if ( pC->Status != CONNECTED )
    goto ShutIt;

  /* Any acks for us to send ? */

  if ( TimerRunning(&pC->T2) )
    SendAck ( pC, false );   /* Will clear T2 */

  /* Are we waiting for any acks - if so, hurry the process along! */

  if ( TimerRunning(&pC->T1) )
    SendPoll ( pC );

  /* Wait for any acks which are due to us */

  while ( TimerRunning(&pC->T1) )
  {
    LLC_BackgroundProcess();
    if ( pC->Status != CONNECTED ) /* Link failed */
      goto ShutIt;
  }

  /* Try 'disconnect' command to other end, with retries */

  for ( i=0; i<DISCONN_TRIES; i++ )
  {
    pC->Status   = AWAIT_DISCONNECT;
    SendUformat ( pC, UFORMAT_DISC | UFORMAT_POLL );

    timer = LLC_TickerTime;

    do
    {
      LLC_BackgroundProcess();
      if ( pC->Status != AWAIT_DISCONNECT )
        goto ShutIt;
    }
      while ( (LLC_TickerTime - timer) < DISCONN_TIME );

  }

  /* This link is now closed */

ShutIt:
  QueueFlush ( &(pC->TxPendingQ) );
  pC->Status = NO_CONNECT;
  return OK;
}

/* -------------------------------------- */

bool LLC_LinkOK (HCONN hc)
{
  if ( hc < 0 || hc >= MAX_LLC_CONN ||
       LLC_Connections[hc].Status != CONNECTED )
    return false;

  return true;
}



/* Initialise routines ===================================== */

bool LLC_Init ( void )
{
  int i;

  Net_Attached = false;
  debug1("LLC_Init: Just about to call TimerSetup() - LLC_TickerTime=%d\n",LLC_TickerTime);
  TimerSetup();
  debug1("LLC_Init: Just called TimerSetup()        - LLC_TickerTime=%d\n",LLC_TickerTime);

  LLC_RxRawQueue.Head = NULL;
  LLC_RxRawQueue.Last = NULL;

  for ( i=0; i < MAX_UI_CONN; i++ )
  {
    UI_RcvFunction[i] = NULL;
    UI_SAPtbl[i] = 0;
  }

  for ( i=0; i < MAX_LLC_CONN; i++ )
  {
    LLC_Connections[i].Status = NO_CONNECT;
    LLC_Connections[i].hconn = i;
    LLC_Connections[i].TxPendingQ.Head = NULL;
    LLC_Connections[i].TxPendingQ.Last = NULL;
    TimerInit ( &LLC_Connections[i].T1,
                      T1ExpiryCallback, &LLC_Connections[i] );
    TimerInit ( &LLC_Connections[i].T2,
                      T2ExpiryCallback, &LLC_Connections[i] );
  }
  debug1("LLC_Init: Just called TimerInit() several times - LLC_TickerTime=%d\n",LLC_TickerTime);
  return true;
}


/* -------------------------------------- */

void LLC_Shutdown ( void )
{
  int i;

  DetachDriver();
  TimerShutdown();

  QueueFlush ( &LLC_RxRawQueue );

  /* 25/3/96 - if our callback has been set, collect it now before
       we leave. All rx packets have been flushed & timers deleted,
       so it is safe to do this without fear of the consequences */
  CollectCallbacks();

  for ( i=0; i < MAX_LLC_CONN; i++ )
  {
    LLC_Connections[i].Status = NO_CONNECT;
    QueueFlush(&LLC_Connections[i].TxPendingQ);
  }
}