#ident "$Id: frsrx.c,v 1.9 1997/10/21 20:48:11 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) Functions return u_short instead of
                                        u_int
                                    (b) FRSRx_LkUp_Frame returns actual state 
    10-16-92 2.00D    H&J (DJ)        Add FRS_RCV_NOTIFY option.
             Other    H&J           See H&J's bug reports

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

    Revision 1.8  1997/01/16 22:08:00  gary
    Removed FRAME_STAGE counter from code.

    Revision 1.7  1996/11/06 16:11:11  gary
    FRF.9 was not renegotiating when the circuit made a transition from
    inactive to active due to LMI activity.

    Revision 1.6  1996/10/22 21:18:58  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.5  1996/06/21 16:36:37  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.4  1996/04/26 19:57:43  gary
    First rev of changes: Frame Relay on PRI card.
    Bug fix: sequence number on segmented frames was wrong.

    Revision 1.3  1995/04/10 16:32:06  garyp
    remove strm.h

 * Revision 1.11  1994/07/27  20:27:41  stavrosm
 * Release 2.2
 *
 * Revision 1.10  1994/02/25  20:16:15  guest
 * See bug report SA-103.
 *
 * Revision 1.9  1993/11/10  16:44:29  stavrosm
 * Release 2.1
 *
 * Revision 1.8  93/11/09  16:03:22  gregb
 * cleanup
 * 
    $Revision: 1.9 $
  
..............................................................................

    DESCRIPTION:

    FRSReceive:
    ----------

    This module is the H&J Frame Relay Stack Receiver Queue processor.

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

  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:

    FRSFR_RcvData        --- Frame Relay Receive API call
    FRSFR_RcvDataWait    --- Frame Relay Receive API call for blocking I/O
    FRSLAPD_RcvData      --- Raw Frame Relay Receive API call
    FRSLAPD_RcvDataWait  --- Raw Frame Relay Receive API call for blocking I/O
    FRSReceive           --- Frame Relay receive process entry point
    frsRxState           --- Dispatches event processing in current state.
    frsRx_LkDwn_LkUp     --- Receive State/event processing routine.
    frsRx_LkDwn_Frame    --- Receive State/event processing routine.
    frsRx_LkUp_LkDwn     --- Receive State/event processing routine.
    frsRx_LkUp_Frame     --- Receive State/event processing routine.

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


 /****************************************************************************
 * 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: frsrx.c,v 1.9 1997/10/21 20:48:11 gary Exp $I
 ****************************************************************************/


#define FRSRX_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 "ll_proto.h"
#include "ll_control.h"
#include "timer.h"
#include "x25trace.h"
#include "fr_cont.h"
#include "fr.h"
#include "frs.h"
#include "lapb.h"
#include "cnt_arry.h"

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

    PROCEDURE NAME:    FRSFR_RcvData

    DESCRIPTION:

    This function accepts a CCB handle (obtained via an open call) and an
    address of a pointer to a user buffer. After the read operation is complete,
    the pointer will point to a received chain of buffers. If the read
    operation encounters any errors, the return value will not be FRS_SUCCESS,
    instead be one of the listed error codes.

    PARAMETERS:

    u_short --- PLL handle.
    USRBUF ** --- Address of a pointer to a user buffer.
                  Will contain a pointer upon successful return.

    RETURNS:

    Returns the result of the read operation.
    API return codes are declared in frs.h.
    
    NOTES:
*****************************************************************************/

u_short
FRSFR_RcvData(hCCB, pUsrbuf)
u_long hCCB;
USRBUF **pUsrbuf;
{
    u_short rcode;
    CCB *pCCB;

    *pUsrbuf = BNULL;
    if(!m_FRS_CHECK) return FRS_DISABLED;

    /* Do a receive on this channel...
     */
    rcode = FRSLAPD_RcvData( hCCB, pUsrbuf);

/*StartUpDebug*/
/*	printf("FRSFR_RcvData rcode=%d %x",rcode,*pUsrbuf ) ; */

    /* If received any data for this channel...
     */
    if (rcode == FRS_SUCCESS && *pUsrbuf != BNULL)
    {
        pCCB = frsCCBtable[hCCB];

        /* Strip frame header...
         */

        /** Always use NON-ENCAPSULATED UnMake for switched PLLs
        *** and PLLs Opened without Encapsulation...
        **/

#if FRS_DCE_SUPPORT
        if ( (pCCB->fwd_node != 0) || (pCCB->encap == (NPMULTIPROTOCOL) 0) )
#else
        if (pCCB->encap == (NPMULTIPROTOCOL) 0)
#endif
	{
            rcode = FRSUnMakeFrame(pCCB,pUsrbuf);
	}
        else {
            rcode = pCCB->plcb->fUnMakeFrame(pCCB, pUsrbuf);
	}
/*	printf("FRSFR_RcvData 2 rcode=%d %x",rcode,*pUsrbuf ) ; */

    }
    else
    {
        if ( rcode == FRS_SUCCESS )
            rcode = FRS_QUEUE_EMPTY;
    }
    if ((rcode & FRS_RCODE_MASK) != FRS_COMPLETE_FRAME) {
        *pUsrbuf = BNULL;
    }

    return (rcode);
}

/**/
#if USE_BLOCKED_IO /* -------------------------------------------------------*/

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

    PROCEDURE NAME:    FRSFR_RcvDataWait

    DESCRIPTION:

    This function is similar to the FRSFR_RcvData() function, except
    that if there is no data in the CCB's read queue, the fReadBlock function
    for the PLL will be invoked to block the user process until any data is
    available on this channel. This function accepts a CCB handle (obtained
    via an open call) and an address of a pointer to a user buffer. After the
    read operation is complete, the pointer will point to a received chain of 
    buffers. If the read operation encounters any errors, the return value will
    not be FRS_SUCCESS, instead be one of the listed error codes.

    PARAMETERS:

    u_short --- PLL handle.
    USRBUF ** --- Address of a pointer to a user buffer
                  Will contain a pointer upon successful return.

    RETURNS:

    Returns the result of the read operation.
    API return codes are declared in frs.h.
    
    NOTES:
*****************************************************************************/

u_short
FRSFR_RcvDataWait(hCCB,pUsrbuf)
u_long hCCB;
USRBUF **pUsrbuf;
{

    u_short rcode;
    CCB *pCCB;

    if(!m_FRS_CHECK) return FRS_DISABLED;

    *pUsrbuf = BNULL;

    /* Fetch CCB...
     */
    pCCB = frsCCBtable[hCCB];

    /* Check if blocking allowed...
     */
    if (!(pCCB->fReadUnblock) || !(pCCB->fReadBlock))
        return (FRS_NO_BLKER);

    /* Scan for new data...
     */
    do 
    {
        rcode = FRSFR_RcvData(hCCB,pUsrbuf);

        /* If no data yet AND no errors, block and keep process blocked until
         * new data arrives...
         */
        while (!(m_FRS_IS_CCB_UP(pCCB)) || (rcode == FRS_QUEUE_EMPTY))
        {
            /* If any buffer returned from FRSFR_RcvData(), free it.
             */
            if ( *pUsrbuf != BNULL )
              {
                m_MMGR_FREE_BUFR(*pUsrbuf);
                *pUsrbuf = BNULL;
              }

            m_FRS_MARK_CCB_RCV_BLOCKED(pCCB);
            (pCCB->fReadBlock)(pCCB->uarg);      /* See you later */

            /* We get here when this process runs after being rescheduled.
             * Make sure we indeed have data, else go blocking again...
             */
            rcode = FRSFR_RcvData(hCCB,pUsrbuf);
        }
    }
    while ( rcode == FRS_INCOMPLETE_FRAME )
        ;/* VOID */

    return (rcode);
}

#endif /* USE_BLOCKED_IO  ------------------------------------------------- */

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

    PROCEDURE NAME:    FRSLAPD_RcvData

    DESCRIPTION:

    This function is similar to FRSFR_RcvData() except that it does not strip
    the frame header as it passes data to the user process.

    PARAMETERS:

    u_short --- PLL handle
    USRBUF ** - Address of a pointer to a user buffer
                Will contain a pointer upon successful return.

    RETURNS:

    Returns the result of the read operation.
    API return codes are declared in frs.h.

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

u_short
FRSLAPD_RcvData(hCCB,pUsrbuf)
u_long hCCB;
USRBUF **pUsrbuf;
{
    CCB *pCCB;
    LCB *pLCB;
    u_short rcode = FRS_BAD_CCB_HANDLE;

    if(!m_FRS_CHECK) return FRS_DISABLED;

    /***************************************************************
     * Validate handle...
     **************************************************************/

    *pUsrbuf = BNULL;

    if ((hCCB < gGlobalCfg.frsTotalPLLs) && 
        (pCCB = frsCCBtable[hCCB]) != CCBNULL)
    {
        /* If any received frames in Read_Queue...
         */
        rcode = FRS_SUCCESS;
        pLCB = pCCB->plcb;

        if ( pCCB->Read_Queue.Q.qtop)
        {
            *pUsrbuf = FRSQTDequeue(&pCCB->Read_Queue);
        }
        else
        {     
            /* Rcv Q is empty. Verify link state...
             */
            if (pLCB->State != FRS_LKUP)
                rcode = FRS_LINK_DOWN;
            else if (!(m_FRS_IS_CCB_UP( pCCB)))
                rcode = FRS_PLL_DOWN;
        }
    }

    return (rcode);
}

/**/
#if USE_BLOCKED_IO /* ------------------------------------------------------ */

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

    PROCEDURE NAME:    FRSLAPD_RcvDataWait

    DESCRIPTION:

    This function is similar to FRSFR_RcvDataWait() except that it does not
    strip the frame header as it passes data to the user process.

    PARAMETERS:

    u_short --- PLL handle
    USRBUF ** - Address of a pointer to a user buffer
                Will contain a pointer upon successful return.

    RETURNS:

    Returns the result of the read operation.
    API return codes are declared in frs.h.

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

u_short
FRSLAPD_RcvDataWait(hCCB, pUsrbuf)
u_long hCCB;
USRBUF **pUsrbuf;
{
    u_short rcode;
    CCB *pCCB;

    if(!m_FRS_CHECK) return FRS_DISABLED;

    *pUsrbuf = BNULL;

    /* scan for new data...
     */
    rcode = FRSLAPD_RcvData(hCCB,pUsrbuf);

    /* if no frame yet and no errors, block and keep process blocked until
     * new frame arrives...
     */
    while (rcode == FRS_SUCCESS && *pUsrbuf == BNULL)
    {
        /* fetch ccb...
         */
        pCCB = frsCCBtable[hCCB];

        /* Block this process only if blocking and unblocking functions have 
         * been pre-provided...
         */
        if ((pCCB->fReadUnblock) && (pCCB->fReadBlock))
        {
            m_FRS_MARK_CCB_RCV_BLOCKED(pCCB);
            (pCCB->fReadBlock)(pCCB->uarg);      /* see you later */

            /* we get here when this process runs after being rescheduled.
             * make sure we indeed have frame, else go blocking again...
             */
            rcode = FRSLAPD_RcvData(hCCB,pUsrbuf);
            continue;
        }
        else
            rcode = FRS_NO_BLKER;
    }

    return(rcode);
}
#endif /* USE_BLOCKED_IO --------------------------------------------------- */

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

    PROCEDURE NAME:    FRSReceive

    DESCRIPTION:

    This procedure, constituting the top-level of an FRS receive process, waits
    for events to occur. It invokes the target-system function os_queue_wait()
    to block until new events occur and expects the target system to
    reschedule this process until that time. As new events occur, the frs
    receiver is cycled and appropriate state routines are invoked.

    Events are generated by the low-level frs driver as and when link states
    change and/or data is received from the network.

    PARAMETERS:

    u_int --- task -instance identifying the link being serviced

    RETURNS:  Nothing (See NOTES)

    NOTES:

    Never return from this function.
    Caller is advised that should this function return:

    (a) Something truly wierd has happened possibly with the stack, and,
    (b) To restart the frs receiver, call this function again after appropriate
        error diagnostics and attendant cleanup.

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

#if USE_TASKING /* -----------------------------------------------------*/

/** ------------------------------------------------------------------- **/
/** ---------------- Tasking version of FRSReceive -------------------- **/
/** ------------------------------------------------------------------- **/

void
FRSReceive(task_inst)
u_int task_inst;
{
    EVENT *pEvent;
    LCB *pLCB = frsLCBtable[task_inst];

    while ( TRUE )
        {
        /* Wait for events...
         * Processing thread breaks here and lets other tasks run.
         */
        os_queue_wait(&pLCB->RQTCB, &pEvent);
        pLCB->State = frsRxState( pLCB, pEvent );
        FRSFreeEvent(pEvent);
        }
}
#else /* --------------------------------------------------------------------*/

/** ------------------------------------------------------------------- **/
/** ---------------- Non-tasking version of FRSReceive ---------------- **/
/** ------------------------------------------------------------------- **/
void
FRSReceive(pLCB, pEvent)
LCB *pLCB;
EVENT *pEvent;
{
    pLCB->State = frsRxState( pLCB, pEvent );
    FRSFreeEvent(pEvent);
    return;
}

#endif /* USE_TASKING -----------------------------------------------------*/


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

    PROCEDURE NAME:    frsRxState

    DESCRIPTION:

    This is an internal procedure responsible for calling the appropriate
    receiver state function based on the current state in the context of a
    received event.

    PARAMETERS:

    pLCB - pointer to the LCB affected by the event.
    struct event pointer - pointer to the current receiver event.

    RETURNS:  the new state of the link (LCB).

    NOTES:

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

FRSstate
frsRxState(pLCB, pEvent)
LCB *pLCB;
EVENT *pEvent;
{
FRSstate next_state;

    /** This should never be the case
     **/
    if( pLCB == (LCB *) NULL )
        return( FRS_LKDWN );
    else
        /** Set the next state to the current state
         **/
        next_state = pLCB->State;

/*StartUpDebug*/
/*	printf("frsRxState next_state = %d",next_state) ; */

    /** What state are we in now ?
     **/
    switch( pLCB->State )
        {

        /** ================================================================ **/
        /** Link is down...                                                  **/
        /** ================================================================ **/
        case FRS_LKDWN:

            /** Which event did we get ?
             **/
            switch( pEvent->EventCode )
                {
                default:
                /** Link is down / link is down **/
                case FRS_LKDWN:
                    /* no state change */
                    break;

                /** Link is down / link went up **/
                case FRS_LKUP:
                    next_state = frsRx_LkDwn_LkUp(pLCB);
                    break;

                /** Link is down / frame was received **/
                case FRS_FRAME:
                    next_state = frsRx_LkDwn_Frame(pLCB, pEvent);
                    break;
                }
            break;

        /** ================================================================ **/
        /** Link is up...                                                    **/
        /** ================================================================ **/
        case FRS_LKUP:

            /** Which event did we get ?
             **/
            switch( pEvent->EventCode )
                {
                default:
                /** Link is up / link is up **/
                case FRS_LKUP:
                    /* no state change */
                    break;

                /** Link is up / link went down **/
                case FRS_LKDWN:
                    next_state = frsRx_LkUp_LkDwn(pLCB);
                    break;

                /** Link is up / frame was received **/
                case FRS_FRAME:
                    next_state = frsRx_LkUp_Frame(pLCB, pEvent);
                    break;
                }

            default: /* unknown event */
                /* no state change */
                break;
            }/* end state switch */

    return( next_state );
}
/**/
/**************************************************************************
 * This internal routine is called whenever the current state of the link
 * is FRS_LKDWN and the receiver event is FRS_LKUP
 *
 **************************************************************************/

FRSstate
frsRx_LkDwn_LkUp(pLCB)
LCB *pLCB;
{
    CCB *pCCB;

    /** Set link state to be 'up'.  
     *  This is necessary for the loop through the CCBs to work properly.
     *  Moved here in fixing bug SA-22, so that sys_link_up() functions
     *  which might cause frames to be transmit will work properly.
     */
    pLCB->State = FRS_LKUP;

    /* Inform target system about a link up situation...
     */
    if (  !m_FRS_IS_ANY_LMI_CFG( pLCB) && !m_FRS_IS_LINK_DCE( pLCB) )
        {
        sys_link_up( pLCB);
        pLCB->link_uptime = sys_get_sys_uptime();
        }

    /* Start LMI polling for DTEs...
     */
    if ((m_FRS_IS_ANY_LMI_CFG(pLCB)) && !(m_FRS_IS_LINK_DCE(pLCB)))
        {
        /* Send out the first StatEnq to the network...
         */
        FRSTMR_start(pLCB->lmi_ccb->common_ccb.plcb->LmiTimerId, 1000);
        }

    /* State transition from LkDwn to LkUp...
     * All FRSReceive* functions will now actually poll appropriate read
     * queues for data instead of returning FRS_LINK_DOWN.
     */

    /** Send trap for link down to up transition
     **/
    sys_trap_event( pLCB, CCBNULL, TRAP_RX_LINKUP );

    /* Now, for each CCB on the list, start outputting any queued tx frames.
     */
    for (pCCB = pLCB->ccbList; pCCB; pCCB = pCCB->next_ccb)
         {
         FRSInjectTimeout(pCCB->hCCB);
         }
 
    return ((FRSstate) FRS_LKUP);
}

/**/
/**************************************************************************
 * This internal routine is called whenever the current state of the link
 * is FRS_LKDWN and the receiver event is FRS_FRAME
 *
 **************************************************************************/

FRSstate
frsRx_LkDwn_Frame(pLCB, pEvent)
LCB *pLCB;
EVENT *pEvent;
{
    USRBUF *pUsrbuf;
    CCB *pCCB;

    pUsrbuf = (USRBUF *)pEvent->Info;

/*StartUpDebug*/
    printf("frsRx_LkDwn_Frame") ;
    /*
     * Buffer received during link down state.
     * Locate the CCB associated with this frame
     */
    if ((pCCB = FRSLocateCCB(pLCB, pUsrbuf)) != CCBNULL)
        {
        /*
         * If data received on LMI channel, process it ....
         */
        if (m_FRS_IS_LMI_CCB(pCCB))
            {
/*StartUpDebug*/
	printf("frsRx_LkDwn_Frame LMI") ;
            FRSLmiRcv((LMICCB *)pCCB, (USRBUF *)pEvent->Info);
            m_MMGR_FREE_BUFR(pUsrbuf);
            }
        else
	{
/*StartUpDebug*/
	printf("frsRx_LkDwn_Frame Non-LMI") ;
            /** Free the buffer
             **/
            m_MMGR_FREE_BUFR(pUsrbuf);
	}
        }
    else
        {
/*StartUpDebug*/
	printf("frsRx_LkDwn_Frame CCB Not Found") ;
        /*
         * Free the buffer and update statistics
         */
        m_MMGR_FREE_BUFR(pUsrbuf);
        pLCB->my_err_stats.UnkDlci++;
        }

    /*
     * Return the persistent state
     */
    return (FRSstate)pLCB->State;
}

/**/
/**************************************************************************
 * This internal routine is called whenever the current state of the link
 * is FRS_LKUP and the receiver event is FRS_LKDWN
 *
 **************************************************************************/

FRSstate
frsRx_LkUp_LkDwn(pLCB)
LCB *pLCB;
{

    /* Inform target system about a link down situation...
     */
    sys_link_down( pLCB);

    /* Stop LMI polling for DTEs...
     */
    if ((m_FRS_IS_ANY_LMI_CFG(pLCB)) && !(m_FRS_IS_LINK_DCE(pLCB)))
    {
        /* Stop LMI Timers...
         */
        FRSTMR_stop(pLCB->lmi_ccb->common_ccb.plcb->LmiTimerId);
        FRSTMR_stop(pLCB->LmiXcessChkTimerId);
    }

    /** Trap to indicate link transition from up to down...
     **/
    sys_trap_event( pLCB, CCBNULL, TRAP_RX_LINKDOWN );
    return ( (FRSstate) FRS_LKDWN );
}

/**/
/**************************************************************************
 * This internal routine is called whenever the current state of the link
 * is FRS_LKUP and the receiver event is FRS_FRAME
 *
 **************************************************************************/

FRSstate
frsRx_LkUp_Frame(pLCB, pEvent)
LCB *pLCB;
EVENT *pEvent;
{
    USRBUF *pUsrbuf = (USRBUF *)pEvent->Info;
    CCB *pCCB;
    u_short rcode ;

/* StartUpDebug */
/* printf("frsRx_LkUp_Frame packet received") ; */

    /* Locate the CCB associated with this frame...
     */
    if ((pCCB = FRSLocateCCB(pLCB, pUsrbuf)) != CCBNULL)
    {

        /**************************************************************
         * A frame has arrived from the network for a specific channel.
         **************************************************************/

        /* If data rcvd on LMI channel...
         */
        if (m_FRS_IS_LMI_CCB(pCCB))
        {
            /* Process LMI message...
             */
            rcode = FRSLmiRcv((LMICCB *)pCCB, (USRBUF *)pEvent->Info);
	 
            m_MMGR_FREE_BUFR( pUsrbuf);
        }
        else
        {
            if (m_FRS_IS_CCB_UP(pCCB))
            {

#if FRS_DCE_SUPPORT
            if ( pCCB->pFwd != CCBNULL )
                {
                /** PLL is not local, forward frame to outbound PLL...
                 **/
                if (m_FRS_IS_STATS_ENBL(pLCB))
                    FRSUpdateStats(pCCB,pUsrbuf,FRS_DIRECTION_INBOUND);

                if ( sys_fwd_frame(pUsrbuf,pCCB,pCCB->pFwd) == TRUE )
                    {
                    FRSUnMakeFrame(pCCB,&pUsrbuf);
                    FRSMakeFrame(pCCB->pFwd,pUsrbuf,PID_NONE);
                    if (FRSLAPD_SendData(pCCB->pFwd->hCCB,pUsrbuf) 
                                       != FRS_SUCCESS)
                        m_MMGR_FREE_BUFR(pUsrbuf);
                    }
                }
            else
#endif

                {
                /* Enqueue the received frame into the CCB's read queue...
                 */
                if (m_FRS_IS_STATS_ENBL(pLCB))
                    FRSUpdateStats(pCCB,pUsrbuf,FRS_DIRECTION_INBOUND);

                FRSQTEnqueue(pUsrbuf,&(pCCB->Read_Queue));

#if USE_RECEIVE_NOTIFY /* --------------------------------------------*/

                /* Notify target system of frame arrival...
                 */
                sys_pll_rcvd_frame(pCCB);

#endif /* -------------------------------------------------------------*/

#if USE_BLOCKED_IO /* --------------------------------------------------------*/

                /* Was a user process waiting (blocked) for data...?
                 */
                if (m_FRS_IS_CCB_RCV_BLOCKED( pCCB))
                    {
                    /* Mark the CCB unblocked and call deblocking function...
                     */
                    m_FRS_MARK_CCB_RCV_UNBLOCKED(pCCB);
                    pCCB->fReadUnblock( pCCB->uarg);
                    }

#endif  /* USE_BLOCKED_IO --------------------------------------------------*/

                }
            }
            else
            {
                /* Buffer received during pll down state...
                 * Discard buffer.
                 */
                m_MMGR_FREE_BUFR(pUsrbuf);
            }
        }
    }
    else
    {
/*StartUpDebug*/
/*	printf("Unknown CCB destination for Packet") ; */
        m_MMGR_FREE_BUFR(pUsrbuf);  /* Free unknown packet */
        pLCB->my_err_stats.UnkDlci++;
    }

    /* Return our current state...
     * State may have been altered by LMI processing.
     */
    return ((FRSstate)pLCB->State);
}

