/*****************************************************************************
 * Copyright (c) 1988-2000 Digi International Inc., All Rights Reserved
 * 
 * This  software  contains  proprietary  and  confidential   information   of
 * Digi International  Inc.   By  accepting  transfer  of this copy, Recipient
 * agrees to retain this software in  confidence,  to  prevent  disclosure  to
 * others,  and  to make no  use of this software other than that for which it
 * was delivered.  This is an unpublished copyright work  Digi   International
 * Inc.    Except   as   permitted   by  federal  law,  17 USC 117, copying is
 * strictly prohibited.
 * 
 * Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc.
 * Confidential  when   combined   with   the   aggregated  modules  for  this
 * product) OBJECT CODE ONLY SOURCE MATERIALS
 * 
 * (C) COPYRIGHT Digi International Inc. 2000
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************/

/******************************************************************
 *
 *  SpiderX25 Protocol Streams Component
 *
 *  Copyright 1991 Spider Systems Limited
 *
 *  LAPB.C
 *
 *    LAPB Top-level STREAMS interface.
 *
 ******************************************************************/

/*
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/sys/lapb/0/s.lapb.c
 *	@(#)lapb.c	1.74
 *
 *	Last delta created	08:55:55 11/11/92
 *	This file extracted	22:30:13 11/19/92
 *
 */



#define STATIC
#define BUFP_T 	bufp_t
#define DBDIAG(MP, BYTE)    (MP)->b_datap->db_diag=BYTE;

#include "types.h"
#include "errno.h"
#include "stream.h"
#include "param.h"
#include "strlog.h"
#include "system.h"
#include "ll_proto.h"
#include "ll_control.h"
#include "wan_proto.h"
#include "timer.h"
#include "x25trace.h"
#include "lapb.h"

extern void putq();
extern int qenable();
extern mblk_t *getq(), *allocb(), *dupmsg(), *copymsg();

/* Routine to time stamp trace messages */
extern unsigned long curtime();


extern void set_loc_busy(), clear_loc_busy();


#define MAIN_PDU_SZ 7   /* Maximum main PDU buffer size (for extended FRMR) */
#define UAUX_PDU_SZ 2   /* Auxiliary PDU buffer size (for simple U cmd/rsp) */

/* Default tuning table from 'space.c' */
extern lapbtune_t   lapb_tdflt;

/* Counts from 'space.c' */
extern uint         lapb_ndevs;
extern uint         lapb_nwans;
extern uint         lapb_rqhwm;
extern uint         lapb_rqlwm;

/* Control structures from 'space.c' */
extern unchar       lapb_dtab[];
extern lapb_t       lapb_wtab[];

/* Stock buffer pointers */
mblk_t *lapb_upmp = 0 ;
mblk_t *lapb_cnmp = 0 ;
mblk_t *lapb_dnmp = 0 ;
mblk_t *lapb_dimp = 0 ;
mblk_t *lapb_udmp = 0 ;

/* stats macro */
#define LAPBGMONITOR(i) { \
if(lapbgmonarray[i]<0XFFFFFFFF) \
	lapbgmonarray[i]++; \
}

/* Bufcall control */
static nostock;

/*
 * Required Functional Prototypes
 */
static void flushstock();
static void flushrx();
static void lapb_trace();
int getupmsg(lapb_t *);
int getstock(lapb_t *, mblk_t *, int);

STATIC int lapbopen(), lapbclose(), lapbwput(), lapbrput(),
	   lapbwsrv(), lapbrsrv();

STATIC struct module_info uprinfo = { LAPB_STID, "lapb UR", 0, INFPSZ, 0x2000, 0x800 };
STATIC struct module_info upwinfo = { LAPB_STID, "lapb UW", 0, INFPSZ, 0x2000, 0x800 };
STATIC struct module_info dnrinfo = { LAPB_STID, "lapb DR", 0, INFPSZ, 0x2000, 0x800 };
STATIC struct module_info dnwinfo = { LAPB_STID, "lapb DW", 0, INFPSZ, 0, 0 };

/*
STATIC struct module_info dnrinfo = { LAPB_STID, "lapb DR", 0, INFPSZ, 1, 0 };
STATIC struct module_info dnwinfo = { LAPB_STID, "lapb DW", 0, INFPSZ, 0, 0 };

*/
STATIC struct qinit uprinit =
    { 0, lapbrsrv, lapbopen, lapbclose, 0, &uprinfo, 0 };

STATIC struct qinit upwinit =
    { lapbwput, 0, 0, 0, 0, &upwinfo, 0 };

STATIC struct qinit dnrinit =
    { lapbrput, 0, 0, 0, 0, &dnrinfo, 0 };

STATIC struct qinit dnwinit =
    { 0, lapbwsrv, 0, 0, 0, &dnwinfo, 0 };

struct streamtab lapbinfo = { &uprinit, &upwinit, &dnrinit, &dnwinit };

/* Link level message headers */
union ll_un {
    uint32          ll_type;
    struct ll_reg   reg;
    struct ll_msg   msg;
    struct ll_msgc  msgc;
};



STATIC int lapbopen(q, dev, flag, sflag)
queue_t *q;
{
    unsigned char *cp;

    if (sflag == CLONEOPEN)
    {   /* Opened as clone */
	for (dev = 0; dev< (int) lapb_ndevs && lapb_dtab[dev]; dev++);
    }
    else
	dev = minor(dev);

	printf("Lapb Open") ;

    if (dev >= (int) lapb_ndevs)
	{
		printf("Lapb Open Fail") ;
		return OPENFAIL;
	}

    if (!*(cp = &lapb_dtab[dev]))
    {
	q->q_ptr = (caddr_t)cp;
	*cp = 1;
    }

	printf("Lapb Open Dev = %x", dev) ;
    return dev;
}

STATIC lapbclose(q, flags)
queue_t *q;
int	flags;
{
    lapb_t *lp;

	printf("Lapb close") ;
    close_lapb_tracestrm(q);
#ifndef	XBUS_CLOSE
    
    if (lp = (lapb_t *)WR(q)->q_ptr)
    {   /* Write side has control block: active connection */
	lapb_stop(lp);
	bzero((BUFP_T)lp, (int)sizeof(lapb_t));
	WR(q)->q_ptr = (caddr_t)0;
    }

    /* Mark device as closed */
    *(unchar *)q->q_ptr = 0;
    q->q_ptr = (caddr_t)0;

    /* Flush stock messages */
    flushstock();
#endif

	return 1;
}


STATIC lapbwput(q, mp)
queue_t *q;
mblk_t  *mp;
{
    register lapb_t *lp = (lapb_t *)q->q_ptr; /* NULL if not registered */
    uint n = 0;

#ifdef DEBUG
	printf("Lapb Ioctl = %x",mp->b_datap->db_type) ;
#endif

    DBDIAG(mp, 0x60)
    switch (mp->b_datap->db_type)
    {
    case M_IOCTL:
	{
	    struct iocblk FAR *iocp = (struct iocblk FAR *)mp->b_rptr;
	    mblk_t *bp;
	    int command;
	    unchar error = EINVAL;
#if 0
		printf("iocp->ioc_cmd = %x",iocp->ioc_cmd) ;
#endif
	    switch (command = (int)iocp->ioc_cmd)
	    {
	    case I_LINK:
	    case I_UNLINK:
		{
		    struct linkblk FAR *linkp =
				(struct linkblk FAR *) mp->b_cont->b_rptr;
		    queue_t *downq = linkp->l_qbot;

		    if (command == I_LINK)
		    {   /* LINK action */

			/* Error if no free structure left */
			error = EAGAIN;

			/* Find free structure */
			for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
			{
			    if (!lp->downq)
			    {   /* Free structure */

				/* Interconnect with lower stream head */
				lp->downq = downq;
				lp->index = linkp->l_index;
				downq->q_ptr = RD(downq)->q_ptr = (caddr_t)lp;
				noenable(RD(downq));

				/* Initialise tuning values */
				bcopy((BUFP_T)&lapb_tdflt,
				      (BUFP_T)&lp->tunetab,
				      (int)sizeof(lapbtune_t));

				/* Initialise timers */
				init_timers(&lp->thead, lp->tmtab, NTIMERS,
					    qenable, (caddr_t)downq);

				/* No error */
				error = 0;

				/* Leave search loop */
				break;
			    }
			}
		    }
		    else
		    {   /* UNLINK action */
			if (lp = (lapb_t *)downq->q_ptr)
			{   /* Was successfully linked */

			    /* Close connection */
			    lapb_stop(lp);

			    /* Empty private queue */
			    flushrx(lp);

			    /* Disconnect dependent UP stream, if any */
			    if (lp->upq)
				WR(lp->upq)->q_ptr = (caddr_t)0;

			    /* Disconnect DOWN stream */
			    downq->q_ptr = RD(downq)->q_ptr = (caddr_t)0;

			    /* Clear block */
			    bzero((BUFP_T)lp, (int)sizeof(lapb_t));
			}

			/* No error */
			error = 0;
		    }
		}

		/* No data back from I_[UN]LINK */
		iocp->ioc_count = 0;
		break;

	    case L_SETSNID:

		printf("LAP L_SETSNID") ;
		if ((bp = mp->b_cont) &&
			((bp->b_wptr - bp->b_rptr) >= sizeof(struct ll_snioc)))
		{
		    struct ll_snioc FAR *pp =
			    (struct ll_snioc FAR *)bp->b_rptr;

			printf("LAP L_SETSNID 1") ;
#if ENDIAN_SWAP
		    SWAP_THIS(pp, sizeof(struct ll_snioc)) ;
#endif
		    /* Sanity checks */
		    if (pp->lli_type != LI_SNID ||
			pp->lli_index == 0 ||
			pp->lli_snid == 0)
			break;

			printf("LAP L_SETSNID 2") ;
		    /* Error if not found */
		    error = ENODEV;

		    /* Find downward stream */
		    for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
		    {   /* Scan structures */

			if (lp->index == (int) pp->lli_index)
			{   /* Found: set SNID */
			    mblk_t *wmp;
			    struct wan_sid FAR *wp;

			    lp->snid = pp->lli_snid;

				printf("LAP L_SETSNID = %x",lp->snid) ;
			    /*  Send SETSNID command to the WAN  */
			    if ((wmp = allocb(sizeof(struct wan_sid), BPRI_HI)))
			    {
				wmp->b_datap->db_type = M_PROTO;
				wp = (struct wan_sid FAR *)wmp->b_wptr;
				wp->wan_type = WAN_SID;
				wp->wan_snid = lp->snid;
				wmp->b_wptr += sizeof(struct wan_sid);
#if ENDIAN_SWAP
				SWAP32(wp->wan_snid) ;
#endif
				putnext(lp->downq, wmp);
				error = 0;
			    }
			    else
				strlog(LAPB_STID,0,0,SL_ERROR,"LAPB: No buffer for WAN_SETSNID!");
			    break;
			}
		    }

			printf("LAP LSET_SNID complete") ;
		}
		else
		{
			printf("L LSET_SNID Size is wrong") ;

		}
		break;

	    case L_GETSNID:
		if ((bp = mp->b_cont) &&
			((bp->b_wptr - bp->b_rptr) >= sizeof(struct ll_snioc)))
		{
		    struct ll_snioc FAR *pp =
			    (struct ll_snioc FAR *)bp->b_rptr;

		    /* Sanity check */
		    if (pp->lli_type != LI_SNID)
			break;

		    if (lp)
		    {   /* Registered */
			pp->lli_snid = lp->snid;
			pp->lli_index = lp->index;
#if ENDIAN_SWAP
			SWAP_THIS(pp, sizeof(struct ll_snioc)) ;
#endif
			error = 0;
		    }
		    else
			error = ENODEV;
		}
		break;

	    case L_GETTUNE:  /*  interrogate lp->tunetab  */
	    case L_SETTUNE:  /*  (re-)initialise lp->tunetab  */

		printf("Got a L_SETTUNE") ;

		if ((bp = mp->b_cont) &&
			((bp->b_wptr - bp->b_rptr) >= sizeof(struct lapb_tnioc)))
		{
		    struct lapb_tnioc FAR *pp =
			    (struct lapb_tnioc FAR *)bp->b_rptr;

#if ENDIAN_SWAP
		    SWAP_THIS(pp, sizeof(struct lapb_tnioc)) ;
#endif
		    /* Sanity check */
		    if (pp->lli_type!=LI_LAPBTUNE)
			{
					printf("LI_LAPBTUNE type is = %x",pp->lli_type) ;
					break;
			}

		    /* Error if no down stream found */
		    error = ENODEV;

		    /* Find appropriate stream(s) */
		    for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
		    {
			if (lp->downq && (pp->lli_snid == lp->snid ||
				command == L_SETTUNE && pp->lli_snid == (uint32)'*'))
			{   /* Match: transfer in appropriate direction */
			    if (iocp->ioc_cmd == L_SETTUNE)
				bcopy((BUFP_T)&pp->lapb_tune,
				      (BUFP_T)&lp->tunetab,
				      (int)sizeof(lapbtune_t));
			    else
				bcopy((BUFP_T)&lp->tunetab,
				      (BUFP_T)&pp->lapb_tune,
				      (int)sizeof(lapbtune_t));

			    /* No error */
			    error = 0;
			}
		    }
#if ENDIAN_SWAP
		    SWAP_THIS(pp, sizeof(struct lapb_tnioc)) ;
#endif
			printf("Error = %x", error) ;
		}
		else
		{
			printf("Size is invalid = %x",bp->b_wptr - bp->b_rptr) ;
			printf("Size of lapb_tnioc = %x",sizeof(struct lapb_tnioc)) ;
		}
		break;

	    case L_GETSTATS:
	    case L_ZEROSTATS:
		if ((bp = mp->b_cont) && (int) (bp->b_wptr - bp->b_rptr) >=
			(int) (command == L_GETSTATS ? sizeof(struct lapb_stioc)
					       : sizeof(struct ll_hdioc)))
		{
		    lliun_t FAR *pp = (lliun_t FAR *)bp->b_rptr;

#if ENDIAN_SWAP
		    if (command == L_GETSTATS)
			SWAP_THIS(pp, sizeof(struct lapb_stioc)) ;
		    else
			SWAP_THIS(pp, sizeof(struct ll_hdioc)) ;
#endif
		    error = ENODEV;
		    for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
		    {   /* Scan for SNID */
			if (lp->downq &&
			    (pp->ll_hd.lli_snid == lp->snid ||
				command == L_ZEROSTATS &&
					pp->ll_hd.lli_snid == (uint32)'*'))
			{
			    if (command == L_GETSTATS)
			    {	
				/* stats, then state */
				bcopy((BUFP_T)&lp->statstab,
				      (BUFP_T)&pp->lapb_st.lli_stats,
				      (int)sizeof(lapbstats_t));
				pp->lapb_st.state = lp->state;
#if ENDIAN_SWAP
				SWAP_THIS(pp, sizeof(struct lapb_stioc)) ;
#endif
			    }
			    else
			    {
				bzero((BUFP_T)&lp->statstab,
				      (int)sizeof(lapbstats_t));
			    }
			    error = 0;
			}
		    }
		}
		break;

	    case L_TRACEON:
	    case L_TRACEOFF:
	    {

		struct trc_regioc  FAR *reg;
		int snid_found, snid_busy;

		reg = (struct trc_regioc  FAR *)mp->b_cont->b_rptr;

#if ENDIAN_SWAP
		SWAP_THIS(reg, sizeof(struct trc_regioc)) ;
#endif
		/*
		    Check the privilege
		*/
		if ( iocp->ioc_uid != 0 )
		{
		    error = EACCES;
		    break;
		}

		if ( iocp->ioc_count != sizeof(struct trc_regioc) )
		{
		    error = EINVAL;
		    break;
		}

		snid_found = snid_busy = 0;
	    
		if ( command == L_TRACEOFF )
		{
		    snid_found = close_lapb_tracestrm(RD(q));

		    if ( snid_found )
		    {
			reg->all_snworks = snid_found;
			error  = 0;
		    }
		    else
		    {
			error = ENODEV;
		    }
		}
		else
		{
		    if ( reg->all_snworks )
		    {
			/*
			    All subnetworks to trace level specified
			*/
			for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
		        {
			    if ( lp->trace_active == 0 )
			    {
				lp->trace_active = (reg->level) ? reg->level : 1;
				lp->trace_queue  = RD(q);
				reg->active[snid_found++] = lp->snid;
			    }
			    else
			    	snid_busy++;
			}
		    }
		    else
		    {
			/*
			    Subnetwork and trace level specified.
			*/
			for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
		        {
			    if ( lp->snid  == reg->snid )
			    {
				if ( lp->trace_active == 0 )
				{
				    lp->trace_active = (reg->level) ? reg->level : 1;
				    lp->trace_queue  = RD(q);
				    reg->active[snid_found++] = lp->snid;
				}
				else
				    snid_busy++;

				break;
			    }
			}
		    }

		    if ( snid_found )
		    {
			if ( reg->all_snworks )
			{
		    	    reg->all_snworks      = snid_found;
			    reg->active[snid_found] = '\0';
			}
#if ENDIAN_SWAP
			SWAP_THIS(reg, sizeof(struct trc_regioc)) ;
#endif
			error = 0;
		    }
		    else
		    {
			if ( snid_busy )
			   error = EEXIST;
			else
			   error = ENODEV;
		    }
		}
	    }
	    break;

	    case RFC_1381_ADMN:
	    {
		rfc_1381_admn_t *admn ;

		if ((!(bp = mp->b_cont)) || (bp->b_wptr - bp->b_rptr != sizeof(rfc_1381_admn_t))) {
			error = ENOMEM ;
			break ;
		}

		admn = (rfc_1381_admn_t *) bp->b_rptr ;

#if ENDIAN_SWAP
		SWAP32(admn->lapbAdmnIndex) ;
#endif
		error = ENODEV;
		for (n = lapb_nwans, lp = lapb_wtab; n--; lp++) {
			/* Scan for SNID */
			if (lp->downq && (admn->lapbAdmnIndex == lp->snid)) {
				admn->lapbAdmnStationType = ((lp->myaddr == A_ADDRESS) ? 1 : 2) ;
				admn->lapbAdmnControlField = (lp->extended ? 2 : 1) ;
				admn->lapbAdmnTransmitN1FrameSize = admn->lapbAdmnReceiveN1FrameSize = lp->tunetab.max_I_len * 8 ;
				admn->lapbAdmnTransmitKWindowsSize = lp->tunetab.tx_window ;
				admn->lapbAdmnReceiveKWindowsSize = lp->tunetab.notack_max ;

				/* SNMP expects time to be expressed in msec */
				admn->lapbAdmnN2RxmitCount = lp->tunetab.N2 * 100 ;
				admn->lapbAdmnT1AckTimer = lp->tunetab.T1 * 100 ;
				admn->lapbAdmnT2AckDelayTimer = lp->tunetab.ack_delay * 100 ;
				admn->lapbAdmnT3DisconnectTimer = admn->lapbAdmnT4IdleTimer = lp->tunetab.Tidle * 100 ;

				admn->lapbAdmnActionInitiate = 1 ;
				admn->lapbAdmnActionRecvDM = 1 ;
				error = 0 ;
#if ENDIAN_SWAP
				SWAP_THIS(admn, sizeof(rfc_1381_admn_t)) ;
#endif
				break ;
			}
		}
		break;
	    }

	    case RFC_1381_FLOW:
	    {
		rfc_1381_flow_t *flow ;

		if ((!(bp = mp->b_cont)) || (((bp->b_wptr - bp->b_rptr) != sizeof(rfc_1381_flow_t)))) {
			error = ENOMEM ;
			break ;
		}

		flow = (rfc_1381_flow_t *) bp->b_rptr ;

#if ENDIAN_SWAP
		SWAP32(flow->lapbFlowIndex) ;
#endif
		error = ENODEV;
		for (n = lapb_nwans, lp = lapb_wtab; n--; lp++) {
			/* Scan for SNID */
			if (lp->downq && (flow->lapbFlowIndex == lp->snid)) {
				flow->lapbFlowStateChanges = 0 ;
				flow->lapbFlowChangeReason = 13 ;
				/* Must change this */
				flow->lapbFlowCurrentMode = lp->state ;
				flow->lapbFlowBusyDefers = 0 ;
				flow->lapbFlowRejOutPkts = lp->statstab.lapbmonarray[REJ_tx_cmd] + lp->statstab.lapbmonarray[REJ_tx_rsp] ;
				flow->lapbFlowRejInPkts = lp->statstab.lapbmonarray[REJ_rx_cmd] + lp->statstab.lapbmonarray[REJ_rx_rsp] ;
				flow->lapbFlowT1Timeouts = 
				flow->lapbFlowFrmrSent = 
				flow->lapbFlowFrmrReceived = 
				flow->lapbFlowXidReceived = 0 ;
				error = 0 ;
#if ENDIAN_SWAP
				SWAP_THIS(flow, sizeof(rfc_1381_flow_t)) ;
#endif
				break ;
			}
		}
		break;
	    }

	    default:
		break;
	    }

	    mp->b_datap->db_type = error ? M_IOCNAK : M_IOCACK;
	    iocp->ioc_error = error;
#ifdef DEBUG
		if(error)
			printf("Lapb Nak: Error = %x",error) ;
		else
			printf("Lapb Ioctl Success") ;
#endif
	    DBDIAG(mp, 0x61)
	    qreply(q, mp);
	}
	return 1;

    case M_FLUSH:
	if (*mp->b_rptr & FLUSHW)
	{
	    *mp->b_rptr &= ~FLUSHW;

	    /* Flush write queue */
	    flushq(q, FLUSHALL);
	}

	if (*mp->b_rptr & FLUSHR)
	{
	    /* Flush read queue */
	    flushq(RD(q), FLUSHALL);

	    /* Send back up read side */
	    qreply(q, mp);
	    return 1;
	}
	break;

    case M_PROTO:
    case M_PCPROTO:
	{
	    register union ll_un FAR *pp = (union ll_un FAR *)mp->b_rptr;

	    if (pp->ll_type == LL_REG)
	    {
		int extended = 0, myaddr = 0, mode = LAPB_MODE;
		uint8 regstatus = LS_EXHAUSTED;

		switch (((struct ll_reg FAR *)pp)->ll_class)
		{
		case LC_LAPBDTE:
		    myaddr = A_ADDRESS;
		    break;

		case LC_LAPBXDTE:
		    extended = 1;
		    myaddr = A_ADDRESS;
		    break;

		case LC_LAPBDCE:
		    myaddr = B_ADDRESS;
		    break;

		case LC_LAPBXDCE:
		    extended = 1;
		    myaddr = B_ADDRESS;
		    break;

		case LC_LAPDTE:
		    myaddr = A_ADDRESS;
		    mode = LAP_MODE;
		    break;

		case LC_LAPDCE:
		    myaddr = B_ADDRESS;
		    mode = LAP_MODE;
		    break;

		default:
		    break;
		}

		/* Sanity checks */
		if (!myaddr || !pp->reg.ll_snid)
		    break;

		if (!lp)
		{   /* Not active */

		    /* Find network */
		    for (lp = lapb_wtab; (lp->snid != pp->reg.ll_snid) &&
					(n < lapb_nwans); lp++, n++);

		    if (n<=lapb_nwans && lp->myaddr == 0 &&
				lapb_init(lp, extended, myaddr, mode))
		    {   /* Found && not in use && initialised OK */
			mblk_t *wmp;
			struct wan_reg FAR *wp;

			/* Send registration to WAN */
			if (!(wmp = allocb(sizeof(struct wan_reg), BPRI_HI)))
			{
			    strlog(LAPB_STID,0,0,SL_ERROR,"LAPB: No buffer for registration");
			    regstatus = LS_FAILED;
			}
			else
			{
			    wmp->b_datap->db_type = M_PROTO;
			    DBDIAG(wmp, 0x62)
			    wp = (struct wan_reg FAR *)wmp->b_wptr;
			    wp->wan_type = WAN_REG;
			    wp->wan_snid = lp->snid;

#if	ENDIAN_SWAP
			SWAP32(wp->wan_snid) ;
#endif
			    wmp->b_wptr += sizeof(struct wan_reg);

			    /* Interconnect with upper queue pair */
			    lp->upq = RD(q);
			    q->q_ptr = (caddr_t)lp; /* Write side only */
#ifdef NO_WAN_CONTROL
			    /* We can trigger start-up now */
			    lp->state = START;
#else
			    /*
				Initialise into a OFF state. No action
				on link until WAN in data transfer state.
			    */
			    init_mode(lp,OFF);
#endif
			    putnext(lp->downq, wmp);

			    regstatus = LS_SUCCESS;
			    qenable(lp->downq);
			}
		    }
		}
		else
		{   /* Active stream */
		    if (pp->reg.ll_snid == lp->snid &&
			extended == lp->extended &&
			myaddr == lp->myaddr)
			regstatus = LS_SUCCESS;
		}

		pp->reg.ll_regstatus = regstatus;
		DBDIAG(mp, 0x61)
		qreply(q, mp);
		return 1;
	    }


	    /* Not LL_REG: no use if not registered */
	    if (!lp) break;

	    /* Switch on non-LL_REG type */
	    switch (pp->ll_type)
	    {
	    case LL_DAT:
		{
		    /* Detach info part from incoming message */
		    register mblk_t *bp = mp->b_cont;
		    mp->b_cont = (mblk_t *)0;
		    DBDIAG(bp, 0x63)

		    /* Attach info part to ack/dat queue */
		    if (lp->ackq)
			lp->adqtail->b_next = bp;
		    else
			lp->ackq = bp;
		    lp->adqtail = bp;

		    /* Start up 'dat' queue if it was empty */
		    if (!lp->datq)
		    {   /* Data queue was empty */
			lp->datq = bp;
			if (!lp->txstopped)
			{
			    qenable(lp->downq);
			}
		    }
		    /* Run into 'break' to discard protocol header ... */
		}
		break;

	    case LL_CTL:
		switch (pp->msg.ll_command)
		{
		case LC_PRGBRA:
		    lp->discard++;
		    break;

		case LC_PRGKET:
		    lp->discard--;
		    break;

		case LC_CONNECT:
		    mp->b_datap->db_type = M_PCPROTO;
		    pp->msgc.ll_yourhandle = pp->msgc.ll_myhandle;
#ifdef NO_WAN_CONTROL
		    if (lp->connected )
#else
		    /*
			Check suitable status of link  and WAN
		    */
		    if (lp->connected ||
		        lp->WAN_state == WAN_CONN ||
			lp->WAN_state == WAN_DISC)
#endif
		    {
			pp->msg.ll_command = LC_CONNAK;
			pp->msg.ll_status = LS_EXHAUSTED;
			printf("LC_CONNAK 1") ; 
		    }
		    else
		    {
		    	lp->connected = 2;
			lp->nethandle = pp->msgc.ll_myhandle;
			lp->netcid = (ushort) pp->msgc.ll_connID;
#ifdef NO_WAN_CONTROL
			lapb_CONNECT(lp);
#else
			lapb_CONNECT(lp,&pp->msgc);
#endif
			pp->msgc.ll_command = LC_CONOK;
			pp->msgc.ll_myhandle = (uint3264)lp;
		    }
		    DBDIAG(mp, 0x61)
		    qreply(q, mp);
		    return 1;

		case LC_CONOK:
		    lp->nethandle = pp->msgc.ll_myhandle;
		    lp->netcid = (ushort) pp->msgc.ll_connID;
		    /* Run into default ... */

		default:
		    DBDIAG(mp, 0x64)
		    putq(lp->downq, mp);
		    return 1;
		}
		break;

	    default:
		/* Shouldnt happen ... */
		break;
	    }
	}
	break;

    default:
	/* Shouldn't happen ... */
	break;
    }

    if (mp)
    {   /* Message has not been disposed of */
	if (q->q_ptr)
	{   /* Active stream: message has been dealt with */
	    freemsg(mp);
	}
	else
	{   /* Admin stream: message is invalid */
	    mp->b_datap->db_type = M_ERROR;
	    mp->b_rptr = mp->b_wptr = mp->b_datap->db_base;
	    *mp->b_wptr++ = EINVAL;
	    qreply(q, mp);
	}
    }

	return 1;
}


STATIC int lapbrput(q, mp)
queue_t *q;
mblk_t  *mp;
{   /* This is the lower (read) put procedure */
    register lapb_t *lp = (lapb_t *)q->q_ptr;
	mblk_t *bp ;

    switch (mp->b_datap->db_type)
    {
    case M_FLUSH:
	if (*mp->b_rptr & FLUSHR)
	{
	    *mp->b_rptr &= ~FLUSHR;

	    /* Flush public Rx queue */
	    flushq(q, FLUSHALL);

	    /* Flush private Rx queue */
	    if (lp) flushrx(lp);
	}

	if (*mp->b_rptr & FLUSHW)
	{
	    /* Flush write queue */
	    flushq(WR(q), FLUSHALL);

	    /* Flush stock messages */
	    flushstock();

	    /* Send back down write side */
	    qreply(q, mp);
	    return 1;
	}
	break;

#ifdef NO_WAN_CONTROL
    case M_PCPROTO:
#endif
    case M_PROTO:

	if (lp && lp->state>START)
	{   /* Running OK */
	    DBDIAG(mp, 0x65)

	    if (lp->trace_active) lapb_trace(lp,mp->b_cont,1);

	    /* Check number of messages held */
	    if (lp->rxqcnt >= (int) lapb_rqhwm)
	    {   /* Put message on public queue, thus blocking flow */
		DBDIAG(mp, 0x66)
		putq(q, mp);
		return 1;
	    }

	    /* Otherwise put on private queue */
	    if (lp->rxqcnt++ == 0)
	    {   /* First one: start queue and wake up state machine */
		lp->rxqueue = lp->rxqtail = mp;
		qenable(lp->downq);
		return 1;
	    }

	    /* Not first: add to tail */
	    lp->rxqtail->b_next = mp;
	    lp->rxqtail = mp;

	    return 1;
	}
	break;

#ifndef NO_WAN_CONTROL
    case M_PCPROTO:
    {
    	/*
	    These primitives deal with the state of the WAN interface
	    as perceived by lapb. 
	*/
	if (lp)
	{
	    /*
		Put on WAN message queue to be dealt with in the
		service procedure.
	    */
	    if (lp->WAN_qcnt++ == 0)
	    {
		/*
		    First one: start queue and wake up state machine
		*/
		lp->WAN_queue = lp->WAN_qtail = mp;
		qenable(lp->downq);
		return 1;
	    }

	    /*
		Not first so add to tail
	    */
	    lp->WAN_qtail->b_next = mp;
	    lp->WAN_qtail = mp;

	    return 1;
	}

    }
    break;
#endif

	case M_DATA:
	
	if (lp && lp->state>START)
	{   /* Running OK */
		struct wan_msg *pp ;
		DBDIAG(mp, 0x65)

		if (!(bp = allocb(sizeof(struct wan_msg), BPRI_HI)))
		{
			freemsg(mp) ;			
			return (0) ;
		}

		pp = (struct wan_msg *) bp->b_wptr ;
		bp->b_wptr += sizeof(struct wan_msg) ;
		bp->b_datap->db_type = M_PROTO ;
		pp->wan_type    = WAN_DAT ;
		pp->wan_command = WC_RX ;
		bp->b_cont = mp ;

	    if(lp->trace_active) 
	    		lapb_trace(lp,mp,1) ;

	    /* Check number of messages held */
	    if (lp->rxqcnt >= (int) lapb_rqhwm)
	    {   /* Put message on public queue, thus blocking flow */
			DBDIAG(bp, 0x66)
			putq(q, bp);
			return 1;
	    }

	    /* Otherwise put on private queue */
	    if (lp->rxqcnt++ == 0)
	    {   /* First one: start queue and wake up state machine */
			lp->rxqueue = lp->rxqtail = bp;
			qenable(lp->downq);
			return 1;
	    }

	    /* Not first: add to tail */
	    lp->rxqtail->b_next = bp;
	    lp->rxqtail = bp;

	    return 1;

	}

	break ;
		
    default:
	/* Shouldn't happen ... */
    break;
    }

    /* Free message which has not been queued */
    freemsg(mp);

	return 1;
}


STATIC int lapbwsrv(q)
register queue_t *q;
{   /* Lower write queue service function */
    register mblk_t *mp;
    register lapb_t *lp = (lapb_t *)q->q_ptr;
    timer_t *tp;

    /* Check for NULL pointer */
    if ( lp == NULL )
    {
	flushq(q,FLUSHALL);
	return 1;
    }

#ifndef NO_WAN_CONTROL
    /*
	Firstly any control messages from the WAN. Note the call to
	getupmsg and not getstock.
    */
    while ( lp->WAN_qcnt && getupmsg(lp))
    {
	lp->WAN_qcnt--;
	mp = lp->WAN_queue;
	lp->WAN_queue = mp->b_next;
	mp->b_next = (mblk_t *)0;

	lapb_rxwctl(lp,mp);
    }
#endif

    /* Start if state is START */
    if (lp->state == START && getstock(lp, (mblk_t *)0, 0))
    {
	lapb_start(lp);
    }

    /* Deal with data input from WAN */

    while (lp->connected != 1)
    {  /* Not awaiting CONOK from network level */
	if (lp->rxqcnt)
	{   /* Take from head of private queue first */
	    lp->rxqcnt--;
	    mp = lp->rxqueue;
	    lp->rxqueue = mp->b_next;
	    mp->b_next = (mblk_t *)0;
	}
	else
	{   /* Try public queue instead: break if none left */
	    if (!(mp = getq(RD(q))))
		break;
	}

	DBDIAG(mp, 0x67)
	if (getstock(lp,
		     (mblk_t *)0,
		     lp->state <= SETUP))   /* This condition covers a  */
					    /* SABM(E) or UA which may  */
					    /* generate a CONNECT	*/
					    /* indication.		*/
	{
	    lapb_rxframe(lp, mp);
	    continue;
	}

	/* Put unused message back on private queue */
	DBDIAG(mp, 0x65)
	if (lp->rxqcnt++ == 0)
	{   /* First entry */
	    lp->rxqueue = lp->rxqtail = mp;
	}
	else
	{   /* Put back at queue head */
	    mp->b_next = lp->rxqueue;
	    lp->rxqueue = mp;
	}

	/* If private queue at LWM, fill from public queue */
	if (lp->rxqcnt <= (int) lapb_rqlwm)
	{
	    while (lp->rxqcnt < (int) lapb_rqhwm)
	    {
		if (mp = getq(RD(q)))
		{
		    lp->rxqcnt++;
		    lp->rxqtail->b_next = mp;
		    lp->rxqtail = mp;
		    continue;
		}

		/* Public queue now empty: leave loop */
		break;
	    }
	}

	/* Leave loop */
	break;
    }

    /* Deal with expired timers */
    while ((tp = lp->thead.th_expired) && getstock(lp, (mblk_t *)0, 0))
    {   /* Remove timer from expired list then call state M/C */
	set_timer(tp, (unsigned short) 0);
	lapb_expired(lp, tp->tm_id);
    }

    /* Deal with controls */
    while (mp = getq(q))
    {
	if (getstock(lp, (mblk_t *)0, 0))
	{
	    int command = ((struct ll_msg FAR *)mp->b_rptr)->ll_command;
	    freemsg(mp);

	    switch (command)
	    {
	    case LC_CONOK:
		lapb_CONOK(lp);
		continue;

	    case LC_CONNAK:
		lapb_CONNAK(lp);
		continue;

	    case LC_DISC:
		lapb_DISC(lp);
		continue;

	    case LC_RESET:
		lapb_RESET(lp);
		continue;

	    default:
		/* Shouldn't happen ... */
		continue;
	    }
	}

	DBDIAG(mp, 0x6D)
	putbq(q, mp);
	return 1;
    }

    /* Deal with data */
    if (lp->connected == 2)
    {
	while ((mp = lp->datq) && !lp->txstopped && getstock(lp, mp, 0))
	{   /* Move data queue pointer forward, leaving message on the ackq */
	    DBDIAG(mp, 0x6E)
	    lp->datq = mp->b_next;

	    /* Operate the state M/C */
	    lapb_wdata(lp);
	}
    }
    else
    {
	discard_data(lp);
    }

    /* If we needed an ack, and the 'next' queue will accept it, */
    /* and it hasn't already been piggy-backed, send it now. */
    if (lp->need_ack && getstock(lp, (mblk_t *)0, 0))
    {
	lapb_txack(lp);
    }

	return 1;
}


STATIC lapbrsrv(q)
register queue_t *q;
{   /* This is the upper (read) service procedure */
    register lapb_t *lp = (lapb_t *)(WR(q)->q_ptr);
    register mblk_t  *mp;
	register struct ll_msg FAR *pp ;

    while (mp = getq(q))
    {
    	if (canput(q->q_next))
	{
		pp = (struct ll_msg FAR *)mp->b_rptr;

		if(pp->ll_type == LL_DAT)
   				lp->i_frame_cnt-- ;
		
		putnext(q, mp);
	}
	else
	{
		putbq(q, mp);
		return 1 ;
	}
    }

	return 1 ;

}

void txlapb(lp, mp, sz)
lapb_t *lp;
register mblk_t *mp;
{
    register queue_t *nq = lp->downq->q_next;
    struct wan_msg FAR *pp = (struct wan_msg FAR *)mp->b_wptr;

    mp->b_wptr += sizeof(struct wan_msg);
    mp->b_datap->db_type = M_PROTO;
    pp->wan_type = WAN_DAT;
    pp->wan_command = WC_TX;

    mp->b_cont->b_wptr += sz;

    DBDIAG(mp, 0x6C)
    if ( lp->trace_active ) lapb_trace(lp,mp->b_cont,0);

    putnext(lp->downq, mp);

}

void uplapb(lp, bp)
register lapb_t *lp;
mblk_t *bp;
{
    /* Get protocol header */
    register mblk_t *mp;
    register queue_t *upq;
    register struct ll_msg FAR *pp;
    /* Collect upward message from stock */
    if (!(mp = lapb_upmp))
    {   /* Ouch! */
    	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no up message");
	return;
    }
    lapb_upmp = (mblk_t *)0;

    DBDIAG(mp, 0x6E)

    if ( lp->upcmd == LC_CONCNF && lp->upstatus == LS_SUCCESS)
	strlog(LAPB_STID,(short)lp->snid,1,SL_TRACE,"LINK  Up  : '%x'",lp->snid);
    else if ( lp->upcmd == LC_DISC ||
             (lp->upcmd == LC_REPORT && lp->upstatus == LS_RST_FAILED))
	strlog(LAPB_STID,(short)lp->snid,1,SL_TRACE,"LINK  Dwn : '%x' Remote",(short)lp->snid);
    else if ( lp->upcmd == LC_RESET)
	strlog(LAPB_STID,(short)lp->snid,2,SL_TRACE,"LINK  Rst : '%x' Remote",(short)lp->snid);
	
    /* Fill message in */
    {
	pp = (struct ll_msg FAR *)mp->b_wptr;

	/* Fill in header */
	if (lp->upcmd == LC_CONNECT)
	{   /* Clear block */
	    bzero((BUFP_T)pp, (int)sizeof(struct ll_msgc));
	    mp->b_cont = bp;
	}
	pp->ll_type =
	    (pp->ll_command = lp->upcmd) == LC_DATA ? LL_DAT : LL_CTL;

	if(pp->ll_type == LL_DAT)
		lp->i_frame_cnt++ ;

	pp->ll_connID = lp->netcid;
	pp->ll_yourhandle = lp->nethandle;
	if (lp->upcmd == LC_CONNECT)
	    pp->ll_status = (uint3264)lp; /* Really ll_myhandle */
	else
	    pp->ll_status = lp->upstatus;

	mp->b_wptr += sizeof(struct ll_msg);
	mp->b_datap->db_type = M_PROTO;

	/* Add upward data if any */
	mp->b_cont = bp;
    }

    upq = lp->upq;
    if (canput(upq->q_next))
    {
		lp->busyflag = 0 ;
		/* Send message off */
		if (upq->q_first == (struct msgb *)0)
		{
			pp = (struct ll_msg FAR *)mp->b_rptr;		
	
			if(pp->ll_type == LL_DAT)
   				lp->i_frame_cnt-- ;
		
		    putnext(upq, mp);
		}
		else
	   		putq(upq, mp);
    }
    else
    {
	/*  Put it on the upper read service queue - go busy  */
	putq(upq, mp);
/*	lp->busyflag = 1 ;
	set_loc_busy(lp); 
*/
    }
}

static void gotbuf(arg)
caddr_t arg;
{
    register lapb_t *lp = (lapb_t *)arg;
	printf("Lapb:BufCall Returned") ;
    nostock = 0;
    if (lp->downq)
	qenable(lp->downq);

	return;
}

static int nobuf(lp, sz)
lapb_t *lp;
{
	printf("Lapb: Bufcall") ;
    bufcall(sz, BPRI_MED, (PFI) gotbuf, (caddr_t)lp);
    nostock = 1;
    return 0;
}

/*
 * 'getstock' is called before cranking the state M/C to get blocks
 * for all STREAMS messages that might be needed in advance.
 *
 * For the upward (net) interface, just a short top-level protocol
 * header is normally needed.
 * The one exception to this is when a SABM(E) has been received
 * on an idle connection, and it will be required to send an
 * LC_CONNECT message, which needs a longer header.
 * This case is signalled by 'need_con' being set.
 *
 * Otherwise, if a correctly-received information frame is being
 * handled, the information blocks are detached from the incoming
 * WAN frame and transferred across to the data indication message.
 *
 * For downward (WAN) messages two blocks are normally required,
 * a protocol block and a block to hold the LAPB header.
 *
 * For data requests the main WAN message has a duplicate of the
 * information part attached to it.  The original message remains
 * in the acknowledgement queue until acknowledged by the remote end,
 * at which point it is freed.
 */
int getstock(lp, infop, need_con)
lapb_t *lp;
mblk_t *infop;
{   /* Gets all the message blocks that might be needed by the state M/C */

    register mblk_t *bp;

    /* Avoid duplicate bufcalls */
    if (nostock)
	return 0;

    /* Check flow control */
    if (!canput(lp->downq->q_next))
	return 0;

    /* If connect, any current header is probably too short */
    if (need_con && lapb_upmp)
    {
	freemsg(lapb_upmp);
	lapb_upmp = (mblk_t *)0;
    }

    /* Make sure we have an up (network) message */
    if (!lapb_upmp)
    {
		int sz = need_con ? sizeof(struct ll_msgc) : sizeof(struct ll_msg);
		if (!(lapb_upmp = allocb(sz, BPRI_MED)))
		{
			printf("nobuf 1 sz = %x",sz) ;
		    return nobuf(lp, sz);
		}
    }

    /* Make sure we have a main WAN message M_PROTO block */
    if (!(bp = lapb_dnmp) &&
       (!(lapb_dnmp = bp = allocb(sizeof(struct wan_msg),BPRI_MED))))
	{
		printf("nobuf 2 sz = %x",sizeof(struct wan_msg)) ;
	    return nobuf(lp, sizeof(struct wan_msg));
	}

    /* Make sure main WAN message has a PDU block */
    if (!bp->b_cont && !(bp->b_cont = allocb(MAIN_PDU_SZ, BPRI_MED)))
	{
		printf("nobuf 3 sz =%x",sizeof(MAIN_PDU_SZ)) ;
		return nobuf(lp, sizeof(MAIN_PDU_SZ));
	}

    /* Make sure there is no OLD unused duplicate down information part */
    if (lapb_dimp)
    {   /* Free old (unused) information part of down message, if any */
	freemsg(lapb_dimp);
	lapb_dimp = (mblk_t *)0;
    }

    /* Get duplicate of new information part (if any) for MAC message */
    if (infop && !(lapb_dimp = dupmsg(infop)))
	{
		printf("nobuf 4 sz = %x",sizeof(mblk_t)) ;
		return nobuf(lp, sizeof(mblk_t));
	}

    /* Make sure we have an auxiliary U-format WAN message M_PROTO block */
    if (!(bp = lapb_udmp) && !(lapb_udmp = bp = allocb(sizeof(struct wan_msg), BPRI_MED)))
	{
		printf("nobuf 5 sz = %x",sizeof(struct wan_msg)) ; 
		return nobuf(lp, sizeof(struct wan_msg));
	}

    /* Now get the auxiliary WAN message's PDU part */
    if (!bp->b_cont && !(bp->b_cont = allocb(UAUX_PDU_SZ, BPRI_MED)))
	{
		printf("nobuf 6 sz = %x",UAUX_PDU_SZ) ;
		return nobuf(lp, UAUX_PDU_SZ);
	}

    /* Success */
    return 1;
}

#ifndef NO_WAN_CONTROL

/*
 * 'getupmsg' is called specifically to get a buffer for a upward
 *  network message when a WAN connection event has occurred. Unlike
 *  getstock no checking on flow control is performed.
 */
int getupmsg(lp)
lapb_t *lp;
{ 
    register mblk_t *bp;

    /* Avoid duplicate bufcalls */
    if (nostock)
	return 0;

    /* Any current header is probably too short */
    if (lapb_upmp)
    {
	freemsg(lapb_upmp);
	lapb_upmp = (mblk_t *)0;
    }

    /* Make sure we have the largest up (network) message */
    if (!lapb_upmp)
    {
	if (!(lapb_upmp = allocb(sizeof(struct ll_msgc), BPRI_MED)))
	    return nobuf(lp, sizeof(struct ll_msgc));
    }
    
    /* Success */
    return 1;
}
#endif

static void flushstock()
{
    /* Throw away any unused messages got by 'getstock' */
    static mblk_t **stocktab[] = {
	&lapb_upmp,
	&lapb_cnmp,
	&lapb_dnmp,
	&lapb_dimp,
	&lapb_udmp,
	(mblk_t **)0
    };

    mblk_t ***stockp = stocktab;
    mblk_t **mpp;

    while (mpp = *stockp)
    {
	if (*mpp)
	{
	    freemsg(*mpp);
	    *mpp = (mblk_t *)0;
	}
	stockp++;
    }

    /* Forget bufcall hold */
    nostock = 0;
}

static void flushrx(lp)
lapb_t *lp;
{
    while (lp->rxqcnt)
    {
	mblk_t *mp;

	lp->rxqcnt--;
	mp = lp->rxqueue;
	lp->rxqueue = mp->b_next;
	mp->b_next = (mblk_t *)0;
	freemsg(mp);
    }
}

/******************************************************************/
int close_lapb_tracestrm(q)
/******************************************************************/

/*
    Deactivates tracing on any subnet whose tracestream matches
    that which is closing down.
    
    Returns number of subnetworks processed.
*/

queue_t  *q;

{
    lapb_t *lp;
    int n;
    int snid_found = 0;
			
    for (n = lapb_nwans, lp = lapb_wtab; n--; lp++)
    {
	if( lp->trace_active != 0 )
	{
	    if ( lp->trace_queue == q )
	    {
		snid_found++;
		lp->trace_active = 0;
		lp->trace_queue  = 0;
	    }
	}
    }
    return(snid_found);
}
			
static void lapb_trace(lp,mp,received)
lapb_t *lp;
mblk_t *mp;
int received;
{
    register mblk_t *cmp;
    mblk_t *dmp;
    
    trc_types FAR *trc_msg;

    static unsigned int trace_seq = 0;

    trace_seq++;    
    
    if ( canput(lp->trace_queue->q_next) )
    {
	if ((cmp = allocb(sizeof(trc_types), BPRI_LO)))
	{
	    if (dmp = copymsg(mp))
	    {
	    	cmp->b_datap->db_type = M_PROTO;
	        cmp->b_wptr += sizeof(trc_types);

		trc_msg = (trc_types FAR *)cmp->b_rptr;
		
		trc_msg->trc_prim = TR_LAPB_DAT;
		trc_msg->trc_hdr.trc_seq  = trace_seq;
		trc_msg->trc_hdr.trc_snid = lp->snid;
		trc_msg->trc_hdr.trc_time = curtime();
		trc_msg->trc_hdr.trc_rcv  = received;
		trc_msg->trc_hdr.trc_mid  = LAPB_STID;

		cmp->b_cont = dmp;
		
#if ENDIAN_SWAP
		SWAP_THIS(trc_msg, sizeof(struct trc_ctl)) ;
#endif
	        putnext(lp->trace_queue,cmp);
	    }
	    else
	        freemsg(cmp);
	}
    }
}
