#ident "$Id: frsutil.c,v 1.13 1997/10/21 20:48:12 gary Exp $"
/*****************************************************************************
 * Copyright (c) 1988-95 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. 1995
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************/

/*****************************************************************************
 *                                                                           *
 * (c) COPYRIGHT 1991, 1992 HARRIS & JEFFRIES, INC.                          *
 *                          888 WASHINGTON STREET                            *
 *                          SUITE 130                                        *
 *                          DEDHAM, MA  02026                                *
 * ALL RIGHTS RESERVED                                                       *
 *                                                                           *
 * This software is the property of Harris & Jeffries, Inc. and is furnished *
 * under license by Harris & Jeffries, Inc. and this software may be used    *
 * only in accordance with the terms of said license.  This copyright notice *
 * may not be removed, modified or obliterated without the prior written     *
 * permission of Harris & Jeffries, Inc.                                     *
 *                                                                           *
 * This software may not be copied, transmitted, provided to or otherwise    *
 * made available to any other person, company, corporation or other entiry  *
 * except as specified in the terms of said license.                         *
 *                                                                           *
 * No right, title, ownership or other interest in the software is hereby    *
 * granted or transferred.                                                   *
 *                                                                           *
 * The information contained herein is subject to change without notice and  *
 * should not be construed as a commitment by Harris & Jeffries, Inc.        *
 *                                                                           *
 *****************************************************************************

  MODULE NAME:


    @@@@@@  @@@@@    @@@@   @    @   @@@@@     @    @                @@@@
    @       @    @  @       @    @     @       @    @               @    @
    @@@@@   @    @   @@@@   @    @     @       @    @               @
    @       @@@@@        @  @    @     @       @    @        @@@    @
    @       @   @   @    @  @    @     @       @    @        @@@    @    @
    @       @    @   @@@@    @@@@      @       @    @@@@@@   @@@     @@@@


  ORIGINAL AUTHOR:  David Jeffries
  SUBJECT MATTER 
      SPECIALIST:   Deepak Shahane

  REVISION HISTORY:

  Date     Version  Name          Description
  -------- -------  ------------  --------------------------------------------
  01-16-92 1.10A    H&J (DJ)      Original
  06-08-92 1.20A    H&J (DS)      (a) Ctrl structs are now allocated using
				  MMGR_SPL_ALLOC and MMGR_SPL_FREE macros
	   Other    H&J           See H&J's bug reports

  $Log: frsutil.c,v $
  Revision 1.13  1997/10/21 20:48:12  gary
  Modifications made to accomodate the SOH (Streams-on-host) folks.

  Revision 1.12  1997/09/23 16:38:14  gary
  The Frame Relay and X.25 API's consist of 32-bit fields only.  This is
  to simplify structure alignment considerations, and make endian issues
  less cumbersome.

  In addition, the Frame Relay API now uses SNID's instead of links, thereby
  making it more compatible with the X.25 and ISDN API's.

  These changes span a large number of files.

  Revision 1.11  1996/10/22 21:19:02  gary
  A bit of rearranging was required in order to move the FRF9_Stats_t
  structure over to fr_cont.h. (needed by host drivers)

  Revision 1.10  1996/10/17 14:46:10  gary
  Activated FRF.9 code.

  Revision 1.9  1996/06/21 16:36:40  gary
  Next stage of FRF.9 implementation complete.  The appropriate functions
  have been inserted into the transmit and receive path, but are not
  active yet.  Development is being put on hold until a more pressing
  issue (flow control) is dealt with.

  Revision 1.8  1996/06/19 16:33:25  gary
  General cleanup and modifications so that the source files may be
  compiled using makefile v2.x.  References to the compiler flag
  "JUPITER" were replaced with "PCI".  #include <xxx> lines were
  replaced with #includ "xxx".

  Revision 1.7  1996/05/21 19:28:52  gary
  Removed obsolete debugging code from file.

  Revision 1.6  1996/04/26 19:57:48  gary
  First rev of changes: Frame Relay on PRI card.
  Bug fix: sequence number on segmented frames was wrong.

  Revision 1.5  1995/09/22 10:18:51  garyp
  typo in a few variable names.  corrected.

 * Revision 1.10  1994/07/27  20:27:41  stavrosm
 * Release 2.2
 *
 * Revision 1.9  1994/07/06  20:14:20  saula
 * Bug Report #SA-940706:
 * 1. Efficiency improvements using timers for Traffic Control
 *    (CIR & Excess Burst enforcement)
 * 2. Improvement in congestion control for fallback:
 *    Excess bits get zapped temporarily during BECN / congestion.
 *
 * Revision 1.8  1994/03/16  21:50:28  deepak
 *  Changed usage of "pLCB->my_cfg" reflecting change in LCB struct.
 *
 * Revision 1.7  1993/11/10  16:44:36  stavrosm
 * Release 2.1
 *
 * Revision 1.6  93/11/10  16:26:04  stavrosm
 * Cleanup to make lint happy
 * 
 * Revision 1.5  93/11/02  11:13:45  stavrosm
 *  
..............................................................................

  DESCRIPTION:

  This module contains utility functions used by the H&J Frame Relay Stack.

..............................................................................

  REF DOCUMENTS:

  (a) U.S Sprint Frame Relay Service Interface Specification Doc #: 5136.02
  (b) Frame Relay Interface Specification, Stratacom Inc., Doc #: 001-207460
  (c) Frame Relay Stack Functional Specification Rel 1.2 Harris & Jeffries Inc.
  (d) Frame Relay Stack External Specification Rel 1.2 Harris & Jeffries Inc.
  (e) MIB for Frame Relay DTEs - RFC Draft from Internet Engr Task Force (IETF).
  (f) Multiprotocol Interconnect over Frame Relay Networks - T. Bradley, C.Brown
      Wellfleet Communications, Inc. A. Malis BBN Communications Oct 14, 1991.

..............................................................................

  SPECIAL COMPILE/LINK INSTRUCTIONS:

	see notes/instructions in frs.h

..............................................................................

  FUNCTIONS INCLUDED in this module:

FRSAllocEvent      --- Allocates an event buffer.
FRSFreeEvent       --- Frees an event buffer.
FRSDequeueHead     --- Dequeue a queued event from the top of the specified 
			 queue.
FRSQTDequeue       --- Dequeue a queued event from the top of the queue within a
			 queue ctrl blk.
FRSEnqueueTail     --- Enqueue an event into the specified queue.
FRSQTEnqueue       --- Enqueue an event into the queue within a queue ctrl blk.
FRSQT_Peek         --- Return a pointer to the first item in a task queue 
			 without dequeueing.
FRSQT_Flush        --- Flush all items from a queue.
FRSUnlinkCCB       --- Terminate all activity associated with a CCB and free up
			 all resources used by the CCB.
FRSCreateCCB       --- Creates a CCB using configured parameters.
FRSFetchCCBHandle  --- Fetches the handle of ccb given a pointer to the ccb.
FRSLocateCCB       --- Locates the CCB for a link ctrl blk and a frame buffer.
FRSLocateCCBbyDLCI --- Locates the CCB for a link ctrl blk and a dlci.
FRSUnMakeFrame     --- Strips the raw frame-relay frame of its frame header.
FRSMakeFrame       --- Prepends a frame relay header onto the payload data.
FRSGetFrameDLCI    --- Returns the dlci of the frame relay frame.
FRS_CMPTimer       --- Falls the rate forward or sets up for a fall forward
			 upon receipt of more BECNs.
FRSFallback        --- Causes the current injection rate to fall back by 1/8th.
FRS_BECNReceived   --- Invoked whenever a frame relay frame with the BECN bit 
			 set is received.
FRSCheckCMP        --- Invoked by the user interface when it modifies the BcFwd
			 or bc_cur.
FRSLogErr          --- Invoked whenever a snapshot of an error condition needs
			 to be preserved.
FRSUpdateStats     --- Updates relevant statistics.
FRSFrameSize       --- Counts the size of the payload data in user frame.

............................................................................
*/


 /****************************************************************************
 * 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 of Digi International  *
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly*
 * prohibited.                                                              *
 * Copyright 1994, Digi International Inc.                                  *
 * All rights reserved.                                                     *
 *       $Id: frsutil.c,v 1.13 1997/10/21 20:48:12 gary Exp $I
 ****************************************************************************/



#define FRSUTIL_C

/*
 * Includes
 */
#define STATIC 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 "strlog.h"
#include "system.h"
#include "timer.h"
#include "x25trace.h"
#include "fr_cont.h"
#include "fr.h"
#include "frs.h"
#include "frsproto.h"
#include "cnt_arry.h"
extern fr_t fr_wtab[] ;

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSAllocEvent

    DESCRIPTION:

    Allocates an event buffer.

    PARAMETERS:        Pointer to the event buffer

    RETURNS:           Nothing

    NOTES:
*****************************************************************************/

EVENT *
FRSAllocEvent()
{
#ifdef	STATIC_MEMORY 
    return ((EVENT *)m_MMGR_EVENT_ALLOC());
#else
   return ((EVENT *)m_MMGR_SPL_ALLOC(sizeof(EVENT));
#endif

}

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSFreeEvent

    DESCRIPTION:

    Frees a event buffer.

    PARAMETERS:        Pointer to the event buffer

    RETURNS:           Nothing

    NOTES:
*****************************************************************************/

void
FRSFreeEvent(pEvent)
EVENT *pEvent;
{
#ifdef	STATIC_MEMORY 
    m_MMGR_EVENT_DEALLOC(pEvent);
#else
    m_MMGR_SPL_FREE(pEvent);
#endif
}

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSDequeueHead

    DESCRIPTION:

    Dequeue a queued event from the top of the specified queue.

    PARAMETERS:        Pointer to a queue.

    RETURNS:           NULL ptr if nothing is queued.
		 Ptr to event buffer if queue is not empty.

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

USRBUF *
FRSDequeueHead(my_queue)
queue   *my_queue;
{
    USRBUF *this_item;

    m_START_CRITICAL;
    if ((this_item = (USRBUF *)my_queue->qtop) != BNULL)
    {
#ifndef	STREAM_MESSAGES
	if ((my_queue->qtop = ((USRBUF *)this_item)->next) == BNULL)
		my_queue->qbot = BNULL;

	this_item->next = BNULL;

#else
	if ((my_queue->qtop = ((USRBUF *)this_item)->b_next) == BNULL)
		my_queue->qbot = BNULL;

	this_item->b_next = BNULL;

#endif
    }
    m_END_CRITICAL;

    return (this_item);
}

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSQTDequeue

    DESCRIPTION:

    Dequeue a queued event from the top of the queue within a queue ctrl blk.
    Adjust the Q-count appropriately.

    PARAMETERS:        Ptr to the Queue Ctrl Blk

    RETURNS:           NULL ptr if nothing is queued.
		 Ptr to event buffer if queue is not empty.

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

USRBUF *
FRSQTDequeue(my_QTCB)
QTCB *my_QTCB;
{
    USRBUF *this_item = BNULL;

    m_START_CRITICAL;
    if (my_QTCB->qcnt)
    {
	my_QTCB->qcnt--;
	this_item = FRSDequeueHead(&(my_QTCB->Q));
    }
    m_END_CRITICAL;

    return (this_item);
}

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSEnqueueTail

    DESCRIPTION:

    Enqueue an event into the specified queue.

    PARAMETERS:  Ptr to the event buffer
		 Ptr to the queue

    RETURNS:           Nothing

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

int
FRSEnqueueTail(this_item,my_queue)
USRBUF *this_item;
queue   *my_queue;
{
    USRBUF *last_item;
    int    item_count = 1;

    m_START_CRITICAL;

#ifndef	STREAM_MESSAGES 

   if ((last_item = (USRBUF *)my_queue->qbot) != BNULL)
    {
	last_item->next = this_item;
	last_item = this_item;
	while ( last_item->next != BNULL )
	{
	    last_item = last_item->next;
	    item_count++;
	}
	my_queue->qbot = last_item;
    }
    else
    {
	my_queue->qtop = last_item = this_item;
	while ( last_item->next != BNULL )
	{
	    last_item = last_item->next;
	    item_count++;
	}
	my_queue->qbot = last_item;
    }

#else

   if ((last_item = (USRBUF *)my_queue->qbot) != BNULL)
    {
	last_item->b_next = this_item;
	last_item = this_item;
	while ( last_item->b_next != BNULL )
	{
	    last_item = last_item->b_next ;
	    item_count++;
	}
	my_queue->qbot = last_item;
    }
    else
    {
	my_queue->qtop = last_item = this_item;
	while ( last_item->b_next != BNULL )
	{
	    last_item = last_item->b_next;
	    item_count++;
	}
	my_queue->qbot = last_item;
    }

#endif
    m_END_CRITICAL;
    return(item_count);
}

/****************************************************************************

    PROCEDURE NAME:    FRSEnqueueHead

    DESCRIPTION:

    Enqueue an event onto the head of a specified queue.

    PARAMETERS:  Ptr to the event buffer
		 Ptr to the queue

    RETURNS:           Nothing

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

int
FRSEnqueueHead(this_item,my_queue)
USRBUF *this_item;
queue   *my_queue;
{
    USRBUF *last_item;
    int    item_count = 1;

    m_START_CRITICAL;

    for( last_item = this_item; last_item->b_next != BNULL; )
    {
        last_item = last_item->b_next ;
        item_count++;
    }

    if ((last_item->b_next = (USRBUF *)my_queue->qtop) == BNULL)
    {
	my_queue->qbot = last_item ;
    }

    my_queue->qtop = this_item ;

    m_END_CRITICAL;
    return(item_count);
}

/**/
/****************************************************************************

    PROCEDURE NAME:    FRSQTEnqueue

    DESCRIPTION:

    Enqueue an event into the queue within a queue ctrl blk

    PARAMETERS:        Ptr to the usrbuf buffer
		 Ptr to the queue ctrl blk

    RETURNS:           Nothing

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

void
FRSQTEnqueue(this_item,my_QTCB)
USRBUF *this_item;
QTCB    *my_QTCB;
{
    m_START_CRITICAL;
    my_QTCB->qcnt += (ushort) FRSEnqueueTail(this_item,&(my_QTCB->Q));
    m_END_CRITICAL;
    return;

}

/****************************************************************************

    PROCEDURE NAME:    FRSQTEnqHead

    DESCRIPTION:

    Enqueue an event at the head of a queue within a queue ctrl blk

    PARAMETERS:        Ptr to the usrbuf buffer
		 Ptr to the queue ctrl blk

    RETURNS:           Nothing

    NOTES:

    Since interrupt service routines may potentially call this function, the
    allocation micro tasks within this function are bracketed with a 
    pair of START_CRITICAL/END_CRITICAL macros.
*****************************************************************************/

void
FRSQTEnqHead(this_item,my_QTCB)
USRBUF *this_item;
QTCB    *my_QTCB;
{
    m_START_CRITICAL;
    my_QTCB->qcnt += (ushort) FRSEnqueueHead(this_item,&(my_QTCB->Q));
    m_END_CRITICAL;
    return;

}

/**/
/*****************************************************************************

  PROCEDURE NAME:    FRSQT_Peek

  DESCRIPTION:

  Primitive to return a pointer to the first item in a task queue
  without dequeueing...

  PARAMETERS:

  QTCB --- a queue task control block

  RETURNS:         qtop from QTCB

  NOTES:
*****************************************************************************/

USRBUF *
FRSQT_Peek(qtcb)
QTCB qtcb;
{
    return (qtcb.Q.qtop);
}

/**/
/*****************************************************************************

  PROCEDURE NAME:    FRSQT_Flush

  DESCRIPTION:

  Primitive to flush all items from a queue

  PARAMETERS:

  QTCB --- pointer to a queue task control block

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void 
FRSQT_flush(pqtcb)
QTCB *pqtcb;
{
    USRBUF *pUsrbuf;

    for (; ((pUsrbuf = FRSQTDequeue(pqtcb)) != BNULL);)
	m_MMGR_FREE_BUFR( pUsrbuf);
    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSUnlinkCCB

  DESCRIPTION:

  This function will terminate all activity associated with a CCB including
  freeing up of all resources being used by the CCB.

  PARAMETERS:

    struct ccb * --- Pointer to the CCB being released

  RETURNS:           Nothing

  NOTES:
*****************************************************************************/

void
FRSUnlinkCCB( pCCB)
CCB *pCCB;
{

    u_long  h;
    CCB *pPrevCCB,*pCurrCCB;
printf("FRSUnlinkCCB: ");
    if (FRSFetchCCBHandle( pCCB, &h) != FRS_SUCCESS)
	return;

printf("GOT_CCB_HANDLE ");
    /* Flush all queues...
     */
    FRSQT_flush(&pCCB->Write_Queue);
    FRSQT_flush(&pCCB->Read_Queue);

    StopFRF9Session(pCCB) ;

    /* Drop ccb from lcb's ccblist...
     */
    for (pCurrCCB = pCCB->plcb->ccbList, pPrevCCB = CCBNULL; (pCurrCCB);)
    {
	if (pCurrCCB == pCCB)
	    break;
	pPrevCCB = pCurrCCB;
	pCurrCCB = pCurrCCB->next_ccb;
    }

    /* Startup Debug */
    if (!pCurrCCB) 
    {
	    printf("Could not find CCB in LCB list!\n");
    }
    else
    {
        if (pPrevCCB != CCBNULL)
	        pPrevCCB->next_ccb = pCurrCCB->next_ccb;
        else
	        pCCB->plcb->ccbList = pCurrCCB->next_ccb;
    }

    /* Cleanup rest of the stuff...
     */
    pCCB->plcb->num_plls--;
    frsCCBtable[h] = CCBNULL;
    pCCB->dlci = 0;

    /* Wait!  Before we delete the CCB,
     * we must destroy the timer entries
     */
    FRSTMR_destroy(pCCB->CMPTimerId);
    FRSTMR_destroy(pCCB->InjectionTimerId);

#ifdef	STATIC_MEMORY
    m_MMGR_CCB_DEALLOC(pCCB);
#else
    m_MMGR_SPL_FREE(pCCB);
#endif
    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSCreateCCB

  DESCRIPTION:

  Routine 'creates' a CCB using cfg'ed parameters. If specific parameters
  are not configured for this PLL on this link, default values are used.

  PARAMETERS:

  struct lcb * --- Pointer to a link ctrl blk
  u_short --- a number identifying a PLL
  u_short --- a flag indicating the source of this call (currently unused)

  RETURNS:

  struct ccb * --- Pointer to a CCB
  Or NULL

  NOTES:
*****************************************************************************/

CCB *
FRSCreateCCB(pLCB, pvc_num, src)
LCB *pLCB;
u_long pvc_num;
u_short src;                /* TRUE == from API, FALSE == from LMI */
{
  u_long h = 0;
  CCB *pCCB = CCBNULL;
  FRS_PLL_CFG *pPLLCfg;
  CCB *pPrevCCB, *pNextCCB;

#if FRS_DCE_SUPPORT
  LCB *pFwdLCB;
  CCB *pFwdCCB;
#endif

  /* First, verify if a CCB exists with the specified PVC number on the
   * specified link...
   */
  if ((pCCB = FRSLocateCCBbyDLCI( pLCB, pvc_num)) == CCBNULL)
    {
      /*  CCB does not exist. Create a CCB and assign values from PLL
       * config. If config does not exist, assign default values...
       *  
       * Look for room in the CCB Table...
       * Bail out if none exists.
       */
      while ( (h < gGlobalCfg.frsTotalPLLs) && frsCCBtable[h])
	h++;
      if (h == gGlobalCfg.frsTotalPLLs)
	return CCBNULL;

      /* Verify if link cfg allows for more channels on this link...
       */
      if (pLCB->num_plls >= pLCB->my_cfg->frs_link_mP1)
	return CCBNULL;
  
    /* We have room; allocate and init a CCB struct...
       */
#ifdef	STATIC_MEMORY
     
	if ((pCCB = (CCB *)m_MMGR_CCB_ALLOC()) != CCBNULL)

#else
	if ((pCCB = (CCB *)m_MMGR_SPL_ALLOC(sizeof (struct ccb))) != CCBNULL)

#endif
	{
	  m_MMGR_MEMSET(pCCB, 0, sizeof(CCB));
	  frsCCBtable[h] = pCCB;
	  pCCB->hCCB = h ;
	  pCCB->plcb = pLCB;
	  pCCB->dlci = pvc_num;

	  /** Insert CCB in list by dlci # **/

	  m_START_CRITICAL;
	  pPrevCCB = CCBNULL;
	  pNextCCB = pLCB->ccbList;
	  while ( (pNextCCB != CCBNULL) && (pNextCCB->dlci < pCCB->dlci) )
	    {
	      pPrevCCB = pNextCCB;
	      pNextCCB = pPrevCCB->next_ccb;
	    }
	  pCCB->next_ccb = pNextCCB;
	  if ( pPrevCCB == CCBNULL )
	    pLCB->ccbList = pCCB;
	  else
	    pPrevCCB->next_ccb = pCCB;
	  
	  pLCB->num_plls++;
	  m_END_CRITICAL;

/* FOR DTEs:
 *          
 *   If LMI is operational on this link, then final clearance about
 *   PLL status must come from LMI. PLL status is left to be decided
 *   by a FULL STATUS LMI message...
 *          
 * FOR DCEs:
 *          
 *   The CCB is not marked up and will be marked so only after we
 *   receive a STAT_ENQ from the DTE.
 */
	  if ((m_FRS_IS_ANY_LMI_CFG(pLCB)) || (m_FRS_IS_LINK_DCE(pLCB)))
	    m_FRS_RESET_CCB_UP(pCCB);
	  else if ( pLCB->State == FRS_LKUP )
	    {
	      m_FRS_SET_CCB_UP( pCCB);
	      pCCB->LastStatChgTime = sys_get_sys_uptime();
	      sys_sdt_ccb( pCCB);    
	    }
	  
	  /* Create timers...
	   */
	  pCCB->CMPTimerId = 
	    FRSTMR_create(h, FRS_CMPTimer , CMPTIMR);
	  
	  pCCB->InjectionTimerId = 
	    FRSTMR_create(h, FRSInjectTimeout, INJTIMR);
	  
	  /* Update time stamps...
	   */
	  pCCB->CreateTime = sys_get_sys_uptime();
	  pCCB->LastStatChgTime = sys_get_sys_uptime();
	  
	  /* Assign Injection Function...
	   */
	  if (m_FRS_IS_INJ_MGR_FORCED(pCCB->plcb))
	    pCCB->InjectionFunc = FRSInjectForce;
	  else
	    /** This injection function is used for
	     ** BUFFERED and STANDARD schemes.
	     **/
	    pCCB->InjectionFunc = FRSInjectNormal;
	  
	  /* Assign the config block, else use one with default values...
	   */
	  if ((FRSLocatePLLCfg(pLCB->hLCB, pvc_num, &pPLLCfg)) != FRS_SUCCESS)
	    {
	      /* Config does not exist, create one with default values...
	       */
	      pCCB->my_cosb.bc_min = FRS_PLL_CFG_DEFAULT_BCMIN / 8;
	      pCCB->my_cosb.Tc = FRS_PLL_CFG_DEFAULT_TC;
	      pCCB->my_cosb.CMP = FRS_PLL_CFG_DEFAULT_CMP;
	      pCCB->my_cosb.bc_cur =
		pCCB->my_cosb.bc_fwd =
		  FRS_PLL_CFG_DEFAULT_BCMAX / 8;
	      pCCB->my_cosb.be_fwd =
		FRS_PLL_CFG_DEFAULT_BE / 8;
	      pCCB->FRF9.RetryPeriod =
	      pCCB->FRF9.RetryCount =
	      pCCB->FRF9.ModesWanted = 0;
	      pCCB->FRF9.ContextMax = 0 ;
	    }
	  else
	    {
	      /* Config exists, use cfg'ed values...
	       */
	      pCCB->my_cosb.bc_min = pPLLCfg->frs_pll_BcMin / 8;
	      if (pPLLCfg->frs_pll_CIR &&
		  (1000L * pPLLCfg->frs_pll_BcMax) >= pPLLCfg->frs_pll_CIR)
	      	pCCB->my_cosb.Tc = (u_short)((1000L*pPLLCfg->frs_pll_BcMax) /
					   pPLLCfg->frs_pll_CIR);
	      else
		pCCB->my_cosb.Tc = FRS_PLL_CFG_DEFAULT_TC;
	      pCCB->my_cosb.CMP = (uint16) pPLLCfg->frs_pll_CMP;
	      pCCB->my_cosb.bc_cur =
		  pCCB->my_cosb.bc_fwd = pPLLCfg->frs_pll_BcMax / 8;
	      pCCB->my_cosb.be_fwd = pPLLCfg->frs_pll_Be / 8;
	      pCCB->FRF9.RetryPeriod =
		(uint8) pPLLCfg->frs_pll_FRF9RetryPeriod ;
	      pCCB->FRF9.RetryCount =
		(uint8) pPLLCfg->frs_pll_FRF9RetryCount ;
	      pCCB->FRF9.ModesWanted =
		(uint8) pPLLCfg->frs_pll_FRF9ModesWanted ;
	      pCCB->FRF9.ContextMax =
		(uint16) pPLLCfg->frs_pll_FRF9ContextMax ;
	      
#if FRS_DCE_SUPPORT
	      /* Check for switched PLL...
	       */
	      if ( pPLLCfg->frs_pll_fwd_node != 0 )
		{
		  /* This CCB is an endpoint in a switched PLL
		   * Set the remote address in the CCB...
		   */
		  pCCB->fwd_node = (u_char)pPLLCfg->frs_pll_fwd_node;
		  pCCB->fwd_link = (u_char)pPLLCfg->frs_pll_fwd_link;
		  pCCB->fwd_dlci = (u_char)pPLLCfg->frs_pll_fwd_dlci;
		  
		  /* And add remote node to CMI list...
		   */
		  FRSCmiInsertRemoteNode(pCCB->fwd_node);
		}
	      else
		{
		  if ( pPLLCfg->frs_pll_fwd_link != 0 )
		    {
		      /** This PLL is configured for local switching.
			If peer CCB exists, set pFwd links in both...
			**/
		      if((pFwdLCB = frsLCBtable[pPLLCfg->frs_pll_fwd_link-1])
			 != LCBNULL )
			{
			  if((pFwdCCB = FRSLocateCCBbyDLCI( pFwdLCB, 
							   pPLLCfg->frs_pll_fwd_dlci)) !=
			     CCBNULL)
			    {
			      pCCB->pFwd = pFwdCCB;
			      pFwdCCB->pFwd = pCCB;
			    }
			}
		    }/* end if */
		}/* end else */
#endif

	    }/* end else */

	  /* Initialize handy-dandy limit of bytes per Tc time period */
	  pCCB->my_cosb.be_bc_cur = (pCCB->my_cosb.bc_cur +
				     pCCB->my_cosb.be_fwd);

	  /* Setup FR_HDR...
	   */
	  mSetDLCI(pCCB);

	  /* Initialise Read_Write Qs...
	   */
	  FRSQT_flush(&pCCB->Write_Queue);
	  FRSQT_flush(&pCCB->Read_Queue);
	  
	  /* Initialise Congestion Status Block...
	   */
	  pCCB->my_cosb.State = COSB_NORMAL;

	}/* end if */
      else
	return pCCB;
    }

  return pCCB;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSFetchCCBHandle

  DESCRIPTION:

  Fetches the handle of ccb given a pointer to the ccb.

  PARAMETERS:        

  struct ccb * --- ptr to the CCB
  u_short * --- ptr to a handle (will hold handle upon successful return)

  RETURNS:

  FRS_SUCCESS indicating success or one of the FRS_* API codes...
   
  NOTES:
*****************************************************************************/

u_long
FRSFetchCCBHandle( pCCB, hCCB)
CCB *pCCB;
u_long *hCCB;
{
    u_long h = 0;

    if (pCCB != CCBNULL)
    {
	/* Scan the table for a match...
	 */
	while ( h < gGlobalCfg.frsTotalPLLs && (frsCCBtable[h] != pCCB)) 
	    h++;

	/* Checkpoint Charlie...
	 */
	if (h == gGlobalCfg.frsTotalPLLs)
	    return FRS_INVALID;
    }

    *hCCB = h;
    return FRS_SUCCESS;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSLocateCCB

  DESCRIPTION:

  Given a link ctrl blk and a frame buffer, this function returns the ccb
  relevant to this context.

  PARAMETER:

  struct lcb * --- Ptr to the link ctrl blk
  USRBUF * --- Ptr to the frame buffer

  RETURNS:

  struct ccb * --- Ptr to a CCB associated with the frame on the link.

  NOTES:
*****************************************************************************/

CCB *
FRSLocateCCB(my_link, pUsrbuf)
LCB *my_link;
USRBUF *pUsrbuf;
{
    char *pHdr;
    CCB *pCCB;
    u_short dlci;

    pHdr = m_MMGR_DATA(pUsrbuf, char *);  /* point to FR Header. */

    /* Verify if EA1 & EA0 bits are correct. */
    if (  (((*pHdr) & EA_MASK) != 0)      /* EA0 must be zero... */
	||(((*(pHdr+1)) & EA_MASK) == 0)  /* ... and EA1 must be 1... */
	)
      return CCBNULL;                     /* ... or else it's a protocol violation */

    pCCB = my_link->ccbList;
    dlci = FRSGetFrameDLCI(pHdr);

    while ( pCCB && ( pCCB->dlci != dlci ) )
	pCCB = pCCB->next_ccb;

    /* Check if its an LMI CCB...
     */
    if (pCCB == CCBNULL && dlci == my_link->lmi_dlci)
	pCCB = (struct ccb *)my_link->lmi_ccb;

    return(pCCB);
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSLocateCCBbyDLCI

  DESCRIPTION:

  Given a link ctrl blk and a dlci, this function returns a pointer to the
  ccb.

  PARAMETERS:

  struct lcb * --- Pointer to the link ctrl blk
  u_short         --- the dlci

  RETURNS:

  struct ccb * --- Pointer to the ccb managing this PLL.

  NOTES:
*****************************************************************************/

CCB *
FRSLocateCCBbyDLCI(my_link,dlci)
LCB *my_link;
u_long dlci;
{
    CCB *pCCB;

    pCCB = my_link->ccbList;

    while ( pCCB && ( pCCB->dlci != dlci ) )
	pCCB = pCCB->next_ccb;

    return(pCCB);
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSUnMakeFrame

  DESCRIPTION:

  The FRSUnMakeFrame function strips the raw frame-relay frame of its
  frame header and manipulates the buffer header fields such that buffer macros
  used to generate pointers to data within a buffer will yield pointers to the
  frame relay payload data.

  PARAMETERS:

  struct ccb * --- Pointer to the CCB
  USRBUF *  --- Pointer to a received raw frame relay buffer

  RETURNS:      Always returns FRS_SUCCESS for compatibility.

  NOTES:

    It is assumed that the 2-byte FCS has already been stripped by the
  low-level driver before the frame was sent upstack to the frame relay
  processes.
*****************************************************************************/

u_short
FRSUnMakeFrame(pCCB, pUsrbuf)
CCB *pCCB;
USRBUF **pUsrbuf;
{
    char   *payload = m_MMGR_DATA(*pUsrbuf, char *);

    /* Watch for the BECN bit here...
     * For LMI packets, the following test will always fail, as desired.
     */
    if (((FR_HDR *)payload)->dlciLSB & BECN_MASK)
	FRS_BECNReceived( pCCB);

    /* Watch for FECN bit...
     * We do not really care for FECN. Just log it and move on.
     */
    if (((FR_HDR *)payload)->dlciLSB & FECN_MASK)
	if (m_FRS_IS_STATS_ENBL(pCCB->plcb))
	{
	    gGlobalStats.global_gen_stats.RcvFECNs++;
	    pCCB->plcb->my_stats.RcvFECNs++;
	    pCCB->my_stats.RcvFECNs++;
	}

    /* Adjust the buffer...
     */
    m_MMGR_INC_OFF(*pUsrbuf, Q922_ADDR_LEN);

#ifndef	STREAM_MESSAGES
    m_MMGR_DEC_LEN(*pUsrbuf, Q922_ADDR_LEN);
#endif

    return (FRS_COMPLETE_FRAME | (PID_NONE << FRS_RC_PROT_SHIFT));
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSMakeFrame

  DESCRIPTION:

  The FRSMakeFrame function prepends a frame relay header onto the frame
  relay payload data contained within the buffer passed as an argument to
  this function. Due to the nature of frame relay and attendant congestion
  management, the frame relay header is updated just before it is passed onto
  the low-level driver for actual Xmission. (Injection mgmt may hold this
  buffer up to meet subscribed CIR requirements).

  PARAMETERS:

  struct ccb * --- Pointer to a channel ctrl blk
  USRBUF * --- Pointer to a buffer requiring frametising

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

u_short
FRSMakeFrame(pCCB, pUsrbuf, pid)
CCB *pCCB;
USRBUF *pUsrbuf;
u_short pid;
{
    struct fr_hdr *pFrame;

    if (pid != PID_NONE)
	return(ENCAP_UNK_PROT);

    m_MMGR_DEC_OFF(pUsrbuf, Q922_ADDR_LEN);

#ifndef STREAM_MESSAGES
    m_MMGR_INC_LEN(pUsrbuf, Q922_ADDR_LEN);
#endif

    pFrame = m_MMGR_DATA(pUsrbuf, struct fr_hdr *);

    *pFrame = pCCB->my_header;

    return FRS_SUCCESS;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSGetFrameDLCI

  DESCRIPTION:

  Given a frame buffer, this function returns the dlci of the frame relay
  frame.

  PARAMETERS:

  char * --- Ptr to start of the payload in the packet

  RETURNS:

  u_short --- the dlci.

  NOTES:
*****************************************************************************/

u_short
FRSGetFrameDLCI(pPayload)
char *pPayload;
{
    u_short dlci;

    dlci = ((((u_short)(pPayload[0])) << 2) & 0x3F0) + 
	   ((((u_short)(pPayload[1])) >> 4) & 0x0F);

    return(dlci);
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRS_CMPTimer

  DESCRIPTION:

  This routine is invoked at the time of expiry of a CMP period. The CMP
  period is relevant only in the context of congestion mgmt via the BECN bit.
  Based upon the current state of the congestion, this procedure will
  either fall the rate forward or setup for a fall forward upon receipt of
  more BECNs.

  This routine is also invoked whenever a change is made in the
  configuration affecting the quiescent injection rate.

  PARAMETERS:

  struct ccb * --- Pointer to a CCB

  RETURNS:         Nothing

  NOTES:

  This routine is not directly invoked. It is dispatched to by a target
  system timer. This timer is set whenever
  (a) a frame is rcvd with BECN set in the NORMAL state (i.e. the first BECN)
  (b) a CMP period expires and more than one BECNs are rcvd during that period.
*****************************************************************************/
void
FRS_CMPTimer(hCCB)
u_long hCCB;
{
  CCB *pCCB = frsCCBtable[hCCB];
  enum cosb_state CosbState = pCCB->my_cosb.State;
 
  switch (CosbState)
    {
    case COSB_WAIT:
      FRSTMR_start(pCCB->CMPTimerId, (u_short) (pCCB->my_cosb.CMP * (u_short) 1000));
      CosbState = COSB_NO_BECN;
      break;

    case COSB_1ST_BECN:
      CosbState = COSB_NO_BECN;
      /* Fall thru' to fall forward speed...
       */

    case COSB_NO_BECN:
      {
	register u_long NewBc;
	register u_int be_bc_max; /* max value of be + bc */

	be_bc_max = pCCB->my_cosb.be_fwd + pCCB->my_cosb.bc_fwd;  

	if (pCCB->my_cosb.bc_cur < pCCB->my_cosb.bc_fwd)
	  {
	    /* We've done a fallback of CIR bits...
	     * ... We can accelerate again.
	     */
	    NewBc = (pCCB->my_cosb.bc_cur + 
		     (pCCB->my_cosb.bc_fwd / 16)
		     );

	    if (NewBc > pCCB->my_cosb.bc_fwd)
	      NewBc = pCCB->my_cosb.bc_fwd; /* but not more than orig value */

	    pCCB->my_cosb.bc_cur =        /* New Bc count... */
	      pCCB->my_cosb.be_bc_cur =   /* ... no Excess rate yet */
		NewBc;

	    /* Tell user about it */
	    sys_throttle_ccb(pCCB,(u_char)FALSE);
	  }
	else if (pCCB->my_cosb.be_bc_cur < be_bc_max )
	  {
	    /* The CIR was already back to its max value,
	     * but the Excess rate is still in fallback.
	     * Slap back the Excess rate.
	     */
	    NewBc = (pCCB->my_cosb.be_bc_cur +
		     (pCCB->my_cosb.be_fwd / 2)
		     );

	    if (NewBc > be_bc_max)
	      NewBc = be_bc_max; /* but not more than orig value */

	    pCCB->my_cosb.be_bc_cur = NewBc;
	    
	    /* Tell user about it */
	    sys_throttle_ccb(pCCB,(u_char)FALSE);
	  }

	if (  (pCCB->my_cosb.bc_cur >= pCCB->my_cosb.bc_fwd)
	    &&(pCCB->my_cosb.be_bc_cur >= be_bc_max)
	    )            /* back to fwd rate? */
	  {
	    CosbState = COSB_NORMAL;
	    FRSTMR_stop(pCCB->CMPTimerId);

#if FRS_DCE_SUPPORT
	    /* Tell CMI...
	     */
	    m_FRS_RESET_CCB_FALLBACK(pCCB);
#endif
	  }
	else
	  /* stay in same state - further fallforwards are possible...
	   * Timer stays enabled.
	   */
	  FRSTMR_start(pCCB->CMPTimerId, (u_short) (pCCB->my_cosb.CMP * (u_short) 1000));
      }
      break;

    case COSB_NORMAL:
      /* This shouldn't really happen...
       */
      CosbState = COSB_NORMAL;
      FRSTMR_stop(pCCB->CMPTimerId);
      break;
    }

  pCCB->my_cosb.State = CosbState;
  return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSFallback

  DESCRIPTION:

  This procedure causes the current injection rate to fall back.

  Initially, if there is any Excess rate allowed, The Excess rate
  is zeroed.  Then, on subsequent calls, the CIR rate is cut by 1/8th
  until it reaches its configured minimum.

  This procedure is invoked when the criteria for BECN frequency has been met.

  PARAMETERS:

  struct ccb * --- Pointer to a CCB

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void
FRSFallback(pCCB)
CCB *pCCB;
{
  u_long NewBc;

  if (pCCB->my_cosb.be_bc_cur > pCCB->my_cosb.bc_cur)
    {
      /* There was an excess rate defined.
       * Kill it. 
       */
      pCCB->my_cosb.be_bc_cur = pCCB->my_cosb.bc_cur;   /* ZAP the Excess rate */
    }
  else
    {
      /* The Excess rate was already zero.  
       * Try reducing the normal (CIR) rate until
       * it reaches its minimum.
       */

      NewBc = (pCCB->my_cosb.bc_cur * 7)/8;      /* 7/8ths of old rate */

      if (NewBc < pCCB->my_cosb.bc_min)
	NewBc = pCCB->my_cosb.bc_min;    /* but not less than minimum */

      pCCB->my_cosb.bc_cur = 
	pCCB->my_cosb.be_bc_cur =   /* Remember to keep excess rate empty. */
	  NewBc;
    }

#if FRS_DCE_SUPPORT
  /*  Tell CMI about it...
   */
  m_FRS_SET_CCB_FALLBACK(pCCB);
#endif

  /*  Tell user about it...
   */
  sys_throttle_ccb(pCCB,(u_char)TRUE);
  return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRS_BECNReceived

  DESCRIPTION:

  Is invoked whenever a frame relay frame with the BECN bit set is received.

  PARAMETERS:

  struct ccb * --- Pointer to a CCB

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void
FRS_BECNReceived(pCCB)
CCB *pCCB;
{
    enum cosb_state CosbState = pCCB->my_cosb.State;

    /* Update Stats...
     */
    if (m_FRS_IS_STATS_ENBL(pCCB->plcb))
    {
	gGlobalStats.global_gen_stats.RcvBECNs++;
	pCCB->plcb->my_stats.RcvBECNs++;
	pCCB->my_stats.RcvBECNs++;
    }

    /* If congestion management enabled...
     */
    if(m_FRS_IS_BECN_XONXOFF(pCCB->plcb))
    {
	switch (CosbState)
	{
	case COSB_NORMAL:
	    FRSFallback(pCCB);   /* do a fallback */
	    FRSTMR_start(pCCB->CMPTimerId, (u_short) (pCCB->my_cosb.CMP * (u_short) 1000));
	    CosbState = COSB_1ST_BECN;
	    break;

	case COSB_NO_BECN:
	    FRSFallback(pCCB);

	    /** Falls through **/

	case COSB_1ST_BECN:
	case COSB_WAIT:
	    CosbState = COSB_WAIT;  /* prevent fall forward if CMT Tick */
	    break;                  /* CMP timer is ALREADY scheduled. */
	}

	pCCB->my_cosb.State = CosbState;
    }

    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSCheckCMP

  DESCRIPTION:

  This routine is invoked by the user interface when it modifies
  the BcFwd or bc_cur.  If we aren't currently doing a CMP timer,
  and if we are now in a fallback state, we should allow fallforwarding.

  PARAMETERS:

  struct ccb * --- Pointer to a CCB

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void 
FRSCheckCMP(pCCB)
CCB *pCCB;
{
    if(m_FRS_IS_BECN_XONXOFF(pCCB->plcb))
    {
	if (  (pCCB->my_cosb.State == COSB_NORMAL)
	    &&(  (pCCB->my_cosb.be_bc_cur < (pCCB->my_cosb.be_fwd + 
					     pCCB->my_cosb.bc_fwd))
	       ||(pCCB->my_cosb.bc_cur < pCCB->my_cosb.bc_fwd)
	       )
	    ) /* in fallback? */
	{
	    /* wait one CMP period before fall-forward */
	    pCCB->my_cosb.State = COSB_WAIT;  
	    FRS_CMPTimer(pCCB->hCCB);
	}
    }
    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSLogErr

  DESCRIPTION:

  This routine is invoked whenever a snapshot of an error condition needs
    to be preserved. The error frame is copied and cached in the CCB for
    later analysis.

  PARAMETERS:

  LCB *    --- Pointer to the LCB for the link with the bad frame.
    USRBUF * --- The error frame in question
    u_short  --- The error code discovered during processing.

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void 
FRSLogErr( pLCB, pFrame, Ecode)
LCB *pLCB;
USRBUF *pFrame;
u_short Ecode;
{
    ERR_STATS *pGlobal = &gGlobalStats.global_err_stats;
    ERR_STATS *pLink = &pLCB->my_err_stats;

    /* Update counters...
     */
    switch (Ecode)
    {
    case FRS_LNK_UNKERR:
	pGlobal->UnkError++;
	pLink->UnkError++;
	break;

    case FRS_LNK_RCVSHORT:
	pGlobal->RcvShort++;
	pLink->RcvShort++;
	break;

    case FRS_LNK_RCVLONG:
	pGlobal->RcvLong++;
	pLink->RcvLong++;
	break;

    case FRS_LNK_INVDLCI:
	pGlobal->InvDlci++;
	pLink->InvDlci++;
	break;

    case FRS_LNK_UNKDLCI:
	pGlobal->UnkDlci++;
	pLink->UnkDlci++;
	break;

    case FRS_LNK_LMIPROTOERR:
	pLCB->lmi_ccb->lmi_inv_msg_rcvd ++;
	break;

    case FRS_LNK_UNKNOWNIE:
	pLCB->lmi_ccb->lmi_unsupp_ie_msg ++;
	break;

    case FRS_LNK_SEQNUMERR :
	pLCB->lmi_ccb->lmi_lost_seq_num_sync_event ++;
	break;

    case FRS_LNK_UNKRPT:
	pLCB->lmi_ccb->lmi_unexpected_pvc_num ++;
	break;
    }

/*** Is this needed ???        
	pLCB->error_type = Ecode;  
***/

    /* Preserve last error info...
     */
    pLCB->last_err = Ecode;

    /* Preserve last error frame...
     */
    if (pLCB->err_data != BNULL)
	m_MMGR_FREE_BUFR( pLCB->err_data);
    pLCB->err_data = m_MMGR_DITTO_BUFR( pFrame);

    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSUpdateStats

  DESCRIPTION:

  This is a convenience function to update relevant statistics.

  PARAMETERS:

  CCB *    --- Pointer to a CCB
    USRBUF * --- The buffer being Xmitted for received
    u_short  --- A flag indicating Transmit(0) or Receive(1)

  RETURNS:         Nothing

  NOTES:
*****************************************************************************/

void
FRSUpdateStats( pCCB, pBfr, direction)
CCB *pCCB;
USRBUF *pBfr;
u_short direction;
{
    u_long nCount;
    LCB *pLCB = pCCB->plcb;

    do 
    {
	nCount = m_MMGR_DATA_LEN(pBfr);
	if (direction == FRS_DIRECTION_OUTBOUND)
	{
	    gGlobalStats.global_gen_stats.SendChars += nCount;
	    gGlobalStats.global_gen_stats.SendFrames++;
	    pLCB->my_stats.SendChars += nCount;
	    pLCB->my_stats.SendFrames++;
	    pCCB->my_stats.SendChars += nCount;
	    pCCB->my_stats.SendFrames++;
	}
	else
	{
	    gGlobalStats.global_gen_stats.RcvChars += nCount;
	    gGlobalStats.global_gen_stats.RcvFrames++;
	    pLCB->my_stats.RcvChars += nCount;
	    pLCB->my_stats.RcvFrames++;
	    pCCB->my_stats.RcvChars += nCount;
	    pCCB->my_stats.RcvFrames++;
	}
    }
#ifdef	STREAM_MESSAGES
    while ( (pBfr = pBfr->b_next) != BNULL )
	;/* VOID */
#else
    while ( (pBfr = pBfr->next) != BNULL )
	;/* VOID */
#endif
    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:    FRSFrameSize

  DESCRIPTION:

    This function is used by the MACRO m_MMGR_FRAME_LEN() to count the payload
    size of a user frame. The user's frame is comprised of 1 or more USRBUF
    structures. This function begins with the usrbuf pointer 'p' and begins
    accumulating the sizes of each subsequently linked usrbuf.

  PARAMETERS:
    USRBUF *pframe - pointer to a usrbuf structure

  RETURNS:
    The number of bytes in a frame.

  NOTES:
*****************************************************************************/

u_long
FRSFrameSize( pframe )
USRBUF *pframe;
{
    u_long count = 0;

    while( pframe )
    {
	count += m_MMGR_DATA_LEN(pframe);
#ifdef	STREAM_MESSAGES
	pframe = pframe->b_cont ;
#else   
	pframe = pframe->link ;
#endif

    }

    return( count );
}


/****************************************************************************

  PROCEDURE NAME:    FRSCountUsrbufs

  DESCRIPTION:

    This function is used to count the number of usrbufs chained together.
    It begins with the usrbuf pointer 'pChain' and counts each usrbuf in
    the linked list.

  PARAMETERS:
    USRBUF *pChain - pointer to head of usrbuf list.

  RETURNS:
    The number of usrbufs in the list.

  NOTES:
*****************************************************************************/

u_short
FRSCountUsrbufs( pChain )
USRBUF *pChain;
{
    u_short buf_count = 0;
    while (pChain)
    {
#ifdef	STREAM_MESSAGES
	pChain = pChain->b_next;
#else
	pChain = pChain->link ;
#endif
	buf_count++;
    }
    return (buf_count);
}

/****************************************************************************

  PROCEDURE NAME:    snid_to_pLCB

  DESCRIPTION:

    This function finds the LCB associated with a particular SNID.

  PARAMETERS:
    u_long snid - SNID in question

  RETURNS:
    Pointer to the matching LCB, NULL if snid is not found

  NOTES:
*****************************************************************************/

LCB *
snid_to_pLCB( snid )
u_long snid;
{
	register int n ;
	register fr_t *lp ;

	for(n = NLCBS, lp = fr_wtab; n--; lp++)
		if (lp->snid == snid) return (lp->pLCB) ;

	return ((LCB *) 0) ;
}

/****************************************************************************

  PROCEDURE NAME:    snid_to_hLCB

  DESCRIPTION:

    This function finds the LCB handle (aka "link number") associated
    with a particular SNID.

  PARAMETERS:
    u_long snid - SNID in question

  RETURNS:
    The link number, 0 if snid is not found

  NOTES:
*****************************************************************************/

u_long
snid_to_hLCB( snid )
u_long snid;
{
	register int n ;
	register fr_t *lp ;

	for(n = NLCBS, lp = fr_wtab; n--; lp++)
		if (lp->snid == snid) return ((u_long) (lp->pLCB - frsLCBtab + 1)) ;

	return (0) ;
}

/****************************************************************************

  PROCEDURE NAME:    pLCB_to_snid

  DESCRIPTION:

    This function returns the SNID associated with a particular LCB.

  PARAMETERS:
    LCB *pLCB - Pointer to LCB structure

  RETURNS:
    The snid, 0 otherwise

  NOTES:
*****************************************************************************/

u_long
pLCB_to_snid( pLCB )
LCB *pLCB;
{
	register int n ;
	register fr_t *lp ;

	for(n = NLCBS, lp = fr_wtab; n--; lp++)
		if (lp->pLCB == pLCB) return (lp->snid) ;

	return (0) ;
}
