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

 @(#) $Id: ss7if_sm.c,v 0.7.2.3 2000/10/09 02:40:40 brian Exp $

 -----------------------------------------------------------------------------

 Copyright (C) 1997, 1998, 1999, 2000  Brian Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 675 Mass
 Ave, Cambridge, MA 02139, USA.

 Last Modified $Date: 2000/10/09 02:40:40 $ by $Author: brian $

 -----------------------------------------------------------------------------

 $Log: ss7if_sm.c,v $
 Revision 0.7.2.3  2000/10/09 02:40:40  brian
 Pre-release 1.

 Revision 0.7.2.2  2000/09/21 05:05:07  brian
 Changed typo and removed redundant file.

 Revision 0.7.2.1  2000/09/11 23:35:27  brian
 Changed to work with new header files.

 Revision 0.7  2000/09/07 13:26:22  brian
 Initial import of this file into this directory.

 Revision 0.7.2.1  2000/09/07 12:49:16  brian
 Got ss7link interface support for drivers compiling.

 Revision 0.7  2000/09/07 11:12:13  brian
 Initial import of OpenSS7.

 Revision 1.2  2000/09/07 10:51:51  brian
 Got these files going.

 Revision 1.1.1.1  2000/09/05 11:00:20  brian
 Initial import of new OpenSS7 stack for Linux.

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

#include <linux/config.h>
#if 1 /* defined(CONFIG_SS7)||defined(CONFIG_SS7_MODULE) */

static char const ident[] = "$Id: ss7if_sm.c,v 0.7.2.3 2000/10/09 02:40:40 brian Exp $";

#define __NO_VERSION__
#include <linux/version.h>
#include <linux/module.h>
#include <linux/modversions.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/if_arp.h>
#include <linux/skbuff.h>
#include <linux/notifier.h>
#include <linux/netdevice.h>
#include <net/sock.h>

#include "../../net/ss7/af_ss7.h"
#include "../../net/ss7/mtp.h"
#include "../../net/ss7/mtp_route.h"
#include "../../net/ss7/mtp_sm.h"
#include "../../include/linux/ss7link.h"
#include "../../include/linux/ss7_timers.h"

#include "ss7if.h"
#include <net/pkt_sched.h>

/*
 * Some support functions.
 */

#define prep_for_fisu \
    if ( !(p->tsu_skb = skb_clone(p->fisu_skb, GFP_ATOMIC)) ) return
#define prep_for_lssu \
    if ( !(p->tsu_skb = skb_clone(p->lssu_skb, GFP_ATOMIC)) ) return
#define prep_for_msu  \
    if ( !(p->tsu_skb = skb_clone(p-> msu_skb, GFP_ATOMIC)) ) return

static __inline void send_fisu(struct ss7_iface *p)
{
    unsigned char* data = p->tsu_skb->data;
    data[0] = (p->tx.N.bsn|p->tx.N.bib);
    data[1] = (p->tx.N.fsn|p->tx.N.fib);
    data[2] = 0x00;
    p->if_xmit(p->tsu_skb,p->dev);
    return;
}

static __inline void send_lssu(struct ss7_iface *p)
{
    unsigned char* data = p->tsu_skb->data;
    data[0] = (p->tx.N.bsn|p->tx.N.bib);
    data[1] = (p->tx.N.fsn|p->tx.N.fib);
    data[2] = 0x01;
    data[3] = (p->tx.sio);
    p->if_xmit(p->tsu_skb,p->dev);
    return;
}

static __inline void send_msu(struct ss7_iface *p)
{
    unsigned char* data = p->tsu_skb->data;
    unsigned int len = (unsigned int)(p->tsu_skb->len - 3);
    data[0] = (p->tx.N.bsn|p->tx.N.bib);
    data[1] = (p->tx.N.fsn|p->tx.N.fib);
    data[2] = (len<64?len:63);
    p->if_xmit(p->tsu_skb,p->dev);
    return;
}

/*
 *  Receive Buffer Operations.
 */

#define RB_INIT \
    /* initialized by system */

#define RB_CLEAR \
    /* can't, there gone... */

#define RB_DISCARD \
    kfree_skb(p->rsu_skb)

#define RB_WRITE \
    Signal(L3_PDU); \
    RB_DISCARD;

/*
 *  Transmit Buffer Operations.
 */

#define TB_CLEAR \
    skb_queue_purge(&p->xmit_q)

#define TB_INIT \
    skb_queue_head_init(&p->xmit_q)

#define TB_START \
    p->retrans_cycle = 0; \
    p->msu_skb = skb_peek(&p->xmit_q)

#define TB_READ \
    skb_queue_tail(&p->rexm_q,skb_dequeue(&p->xmit_q))

#define TB_EMPTY \
        skb_queue_empty(&p->xmit_q)

/*
 *  Retransmit Buffer Operations.
 */

#define RTB_START \
    p->msu_skb = skb_peek(&p->rexm_q); \
    if (!skb_queue_empty(&p->rexm_q)) \
        p->retrans_cycle = 1

#define RTB_CYCLE \
    (p->retrans_cycle)

#define RTB_AGAIN \
    if ( p->msu_skb == (p->msu_skb = p->msu_skb->next) ) \
        p->retrans_cycle = 0

#define RTB_EMPTY \
    skb_queue_empty(&p->rexm_q)

#define RTB_CLEAR \
    skb_queue_purge(&p->rexm_q)

#define RTB_ACK \
    if (!skb_queue_empty(&p->rexm_q)) kfree_skb(skb_dequeue(&p->rexm_q))

#define RTB_READ \
    p->tsu_skb = skb_dequeue(&p->rexm_q)

#define RTP_REWRITE \
    skb_queue_head(&p->rexm_q,p->tsu_skb);

/*
 *  Macro magic for debugging without killing readability.
 */

#define Mark(flag) p-> ## flag = 1
#define Cancel(flag) p-> ## flag = 0
#define Set(flag) p-> ## flag
#define var(var) p-> ## var

#define State(module,state) module ## _STATE_ ## state
#define CurrentState(module) p-> ## module ## _state

#ifdef DEBUG_PRINT_STATE
#define NewState(module,state) \
    { \
        printk(KERN_INFO "SS7/LINK: " __FUNCTION__ " " #module " = " #state "\n"); \
        CurrentState(module) = State(module,state); \
    }
#else
#define NewState(module,state) CurrentState(module) = State(module,state);
#endif

#define Event(primitive) \
    static __inline void ss7if_ ## primitive ## (struct ss7_iface *p)

#ifdef DEBUG_PRINT_ASP
#define Signal(action) \
    { \
        print(KERN_INFO "SS7LINK: " __FUNCTION__ " -> " #action "\n"); \
        ss7if_ ## action ## (p); \
    }
#else
#define Signal(action) ss7if_ ## action ## (p)
#endif

#define SetTimer(timer,value) ss7if_timer_set_duration(p,TIMER_ ## timer, TIMER_ ## value);

#ifdef DEBUG_PRINT_TIMERS
#define Stop(timer) \
    { \
        print(KERN_INFO "SS7LINK: " __FUNCTION__ " -> " #timer " Stop\n"); \
        ss7if_timer_stop(p, TIMER_ ## timer); \
    }
#define Start(timer,timeout) \
    { \
        print(KERN_INFO "SS7LINK: " __FUNCTION__ " -> " #timer " Start (" #timeout ")\n"); \
        ss7if_timer_start(p, TIMER_ ## timer, ss7if_ ## timeout); \
    }
#else
#define Stop(timer) ss7if_timer_stop(p, TIMER_ ## timer);
#define Start(timer,timeout) ss7if_timer_start(p, TIMER_ ## timer, ss7if_ ## timeout);
#endif

/*
 *  ------------------------------------------------------------------------
 *
 *  L3 Primitives: to L3
 *
 *  ------------------------------------------------------------------------
 */

static void make_primitive(struct ss7_iface *p, unsigned char cmd,
        unsigned char *ptr, unsigned int size);

Event(L3_Link_Congested)
{
    unsigned char c = var(cong_level);
    make_primitive(p,SS7_L3_LINK_CONGESTED,&c,sizeof(c));
}

Event(L3_Link_Congestion_Ceased)
{
    unsigned char c = var(cong_level);
    make_primitive(p,SS7_L3_LINK_CONGESTION_CEASED,&c,sizeof(c));
}

Event(L3_BSNT)
{
    unsigned char c = var(rx.T.bsn)|var(rx.T.bib);
    make_primitive(p,SS7_L3_BSNT,&c,sizeof(c));
}

Event(L3_In_Service)
{
    make_primitive(p,SS7_L3_IN_SERVICE,NULL,0);
}

Event(L3_Out_Of_Service)
{
    make_primitive(p,SS7_L3_OUT_OF_SERVICE,NULL,0);
}

Event(L3_RB_Cleared)
{
    make_primitive(p,SS7_L3_RB_CLEARED,NULL,0);
}

Event(L3_RTB_Cleared)
{
    make_primitive(p,SS7_L3_RTB_CLEARED,NULL,0);
}

Event(L3_Remote_Processor_Outage)
{
    make_primitive(p,SS7_L3_REMOTE_PROCESSOR_OUTAGE,NULL,0);
}

Event(L3_Remote_Processor_Recovered)
{
    make_primitive(p,SS7_L3_REMOTE_PROCESSOR_RECOVERED,NULL,0);
}

Event(L3_Retreival_Complete)
{
    make_primitive(p,SS7_L3_RETRIEVAL_COMPLETE,NULL,0);
}

Event(L3_Retreived_Message)
{
    make_primitive(p,SS7_L3_RETRIEVED_MESSAGES,var(tsu_skb->data),var(tsu_skb->len));
}

Event(L3_PDU)
{
    make_primitive(p,SS7_L3_PDU,var(rsu_skb->data),var(rsu_skb->len));
}

/*
 *  ------------------------------------------------------------------------
 *
 *  State Machines
 *
 *      The order of the state machines primitives below may seem somewhat
 *      disorganized at first glance; however, they have been ordered by
 *      dependency because they are all __inline functions.  You see, the L2 state
 *      machine does not require multiple threading because there is never a
 *      requirement to invoke the individual state machines concurrently.  This
 *      works out good for the driver, because a primitive action expands __inline
 *      to the necessary procedure, while the source still take the appearance
 *      of the SDLs in the SS7 specification.
 *
 *  ------------------------------------------------------------------------
 */

/*
 *  DAEDR_Start is responsible for setting up the reception of the first frame
 *  and starting the receivers.
 *
 *  The rest of the DAEDR state machine is embedded in the RxISR.
 */
Event(DAEDR_Start)
{
   /*
    *   Note that the receivers (DAEDR) are not started for SS7 on
    *   power-up.  It is only after L2 receives the LSC_Start() command
    *   from L3 that the receivers are started.  Once started, however, the
    *   receivers are never stopped or reset.  In contrast, the
    *   transmitters (DAEDT) are started initially at power-on and are
    *   never restarted.  Transmitters are supposed to send SIOS on
    *   power-on.
    */

   /*
    *   This setup is done by the driver, however, the receivers start up
    *   at the same time as the transmitters.  Therefore, we track Octet
    *   Counting Mode while the receivers are idle and cannot reset it when
    *   they go active or we will get out of sync with the driver.
    */
#if 0
    Cancel(Octet_Counting_Mode);
#endif
    NewState(DAEDR,Active);
}

Event(SUERM_Stop)
{
    NewState(SUERM,Idle);
}

Event(CC_Normal)
{
    if ( CurrentState(CC) == State(CC,Congested) )
    {
        Stop(T5);
        NewState(CC,Normal);
    }
}

Event(RC_Stop)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    Signal(CC_Normal);
    NewState(RC,Power_On);
}

Event(IAC_Stop)
{
    Stop(T2);
    Cancel(Emergency);
    NewState(IAC,Idle);
}

Event(DAEDT_Transmitter_Wakeup)
{
    if ( CurrentState(DAEDT) != State(DAEDT,Sleeping) )
        return;
    NewState(DAEDT,Active);
}

Event(TXC_Send_SIOS)
{
    Stop(T7);
    Stop(T6);
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIOS;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(LSC_Link_Failure)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Initial_Alignment):
            Signal(L3_Out_Of_Service);
            Signal(IAC_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Local_Processor_Outage);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(RC_Stop);
            Signal(SUERM_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(TXC_Send_SIOS);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,In_Service):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
            
    }
}

Event(TXC_T6_Timeout)
{
    Signal(LSC_Link_Failure);
    Cancel(SIB_Received);
    Stop(T7);
}

Event(TXC_T7_Timeout)
{
    Signal(LSC_Link_Failure);
    Stop(T6);
}

Event(TXC_Send_SIB)
{
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIB;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_SIPO)
{
    Stop(T7);
    Stop(T6);
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIPO;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_SIO)
{
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIO;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_SIN)
{
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIN;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_SIE)
{
    Mark(LSSU_Available);
    var(tx.sio) = LSSU_SIE;
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_MSU)
{
    if ( !RTB_EMPTY )
    {
        Start(T7, TXC_T7_Timeout);
    }
    Cancel(MSU_Inhibited);
    Cancel(LSSU_Available);
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_Send_FISU)
{
    Stop(T7);
    Mark(MSU_Inhibited);
    Cancel(LSSU_Available);
    Stop(T6);
    Signal(DAEDT_Transmitter_Wakeup);
}

Event(TXC_FSNX_Value)
{
    var(tx.X.fsn) = var(rx.X.fsn);
}

Event(TXC_NACK_To_Be_Sent)
{
    var(tx.N.bib) = var(tx.N.bib)?0x00:0x80;
}

Event(LSC_RTB_Cleared)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Processor_Outage):
            Cancel(Remote_Processor_Outage);
            if ( Set(Local_Processor_Outage) ) return;
            Signal(L3_Remote_Processor_Recovered);
            Signal(L3_RTB_Cleared);
            Signal(TXC_Send_MSU);
            NewState(LSC,In_Service);
            return;
    }
}

Event(TXC_BSNR_and_BIBR)
{
    var(tx.R.bsn) = var(rx.R.bsn);
    var(tx.R.bib) = var(rx.R.bib);
    if ( Set(Clear_RTB) )
    {
       /*   Erase all MSUs in RTB */
        RTB_CLEAR;
        var(tx.F.fsn) = (var(tx.R.bsn)+1)&0x7f;
        var(tx.L.fsn) = var(tx.R.bsn);
        var(tx.N.fsn) = var(tx.R.bsn);
        var(tx.N.fib) = var(tx.R.bib);
        Signal(LSC_RTB_Cleared);
        Cancel(Clear_RTB);
        Cancel(RTB_Full);
        return;
    }
    if ( var(tx.F.fsn) != ((var(tx.R.bsn)+1)&0x7f) )
    {
        /*
         *  If we are here, we have received an ACK for FSN = var(tx.R.bsn)
         *
         *  Remote end not busy anymore if it is acknowledging frames which
         *  have been sent.  The distant end withholds acknoledgements when it
         *  is busy.
         */
        if ( Set(SIB_Received) )
        {
            Cancel(SIB_Received);
            Stop(T6);
        }
        /* 
         *  BSNR is an ACK for all outstanding messages up to BSNR and is
         *  either an ACK or a NACK for BSNR depending upon sense of BIBR.
         * 
         *  Mark as acknowledged all frames with FSN less than BSNR but don't
         *  go past end of RTB.
         */
        do {
            RTB_ACK;
            var(tx.F.fsn) = (var(tx.F.fsn)+1)&0x7f;
        } while ( var(tx.F.fsn) != ((var(tx.R.bsn)+1)&0x7f) );
        Signal(DAEDT_Transmitter_Wakeup);
        /* 
         *   Did we empty the RTB: if we did stop T7, otherwise restart T7
         */
//      if ( var(tx.L.fsn) == var(rx.F.fsn) ) {  /* RTB empty */
        if ( RTB_EMPTY ) {  /* RTB empty */
            Stop(T7);
        } else {
            /*
             *   I don't know that this is quite right... SS7 timers normally
             *   restart if they are started while they are running.  This
             *   should be fixed up a little better...
             */
//          Stop(T7);
            Start(T7, TXC_T7_Timeout);
        }
        /* 
         *   The RTB can't be full anymore...
         */
        Cancel(RTB_Full);
    }
    /* 
     *   If we NACK'ed a frame (inversion on bibr), set for retransmission...
     */
    if ( var(tx.N.fib) != var(tx.R.bib) )
    {
        /*
         *  If we are here, we have received a NACK for FSN = var(tx.R.bsn)
         *  and are starting a retransmission cycle.
         */
        var(tx.N.fib) = var(tx.R.bib);
        var(tx.N.fsn) = (var(tx.F.fsn)-1)&0x7f;
        RTB_START;
        Signal(DAEDT_Transmitter_Wakeup);
        return;
    }
    return;
}

Event(TXC_SIB_Received)
{
    if ( Set(LSSU_Available) )
        if ( var(tx.sio) != LSSU_SIB ) return;
    if ( RTB_EMPTY ) return;
    if ( !Set(SIB_Received) )
    {
        Start(T6, TXC_T6_Timeout);
        Mark(SIB_Received);
    }
    Start(T7, TXC_T7_Timeout);
}

Event(TXC_Clear_RTB)
{
    Mark(Clear_RTB);
}

Event(TXC_Clear_TB)
{
    TB_CLEAR;
}

Event(DAEDT_FISU)
{
    send_fisu(p);
    NewState(DAEDT,Sleeping);
}

Event(DAEDT_LSSU)
{
    send_lssu(p);
    if ( Set(LSSU_Available) )
        NewState(DAEDT,Sleeping); /* this is not a SIB */
}

Event(DAEDT_MSU)
{
    send_msu(p);
}

Event(RC_FSNT_Value)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    var(rx.T.fsn) = var(tx.N.fsn);
}

#define SS7_RTB_BURST_MAX 10
Event(TXC_Retreival_Request_and_FSNC)
{
    start_bh_atomic();
    {
        int burst = SS7_RTB_BURST_MAX+1;     /* number of messages in burst */

        while (!TB_EMPTY )
            TB_READ;
        while (!RTB_EMPTY) {
            if (burst--) goto more_later;  /* give NET_BH time to run. */
            RTB_READ;
            Signal(L3_Retreived_Message);
            if (netdev_dropping) {
                if (net_ratelimit())
                    printk(KERN_CRIT "SS7/LINK: network dropping %s retreived frames: burst of %d\n",
                            p->dev->name, SS7_RTB_BURST_MAX);
                goto more_later;
            }
        }
        var(tx.F.fsn) = (var(tx.C.fsn)+1)&0x7f;
        var(tx.L.fsn) = var(tx.C.fsn);
        Cancel(RTB_Full);
        Signal(L3_Retreival_Complete);
    }
    end_bh_atomic();
    return;

more_later:
    ss7_sm_dispatch(&p->rx_taskq,ss7if_TXC_Retreival_Request_and_FSNC);
    end_bh_atomic();
    return;
}
#undef SS7_RTB_BURST_MAX

Event(TXC_Transmission_Request)
{
    if ( Set(LSSU_Available) )
    {
        prep_for_lssu;
        if ( var(tx.sio) == LSSU_SIB )
            Cancel(LSSU_Available);  /* Only send one SIB, repeat others */
        var(tx.N.fsn) = var(tx.L.fsn);
        var(tx.N.bib) = var(tx.N.bib);
        var(tx.N.bsn) = (var(tx.X.fsn) - 1)&0x7f;
        var(tx.N.fib) = var(tx.N.fib);
        Signal(DAEDT_LSSU);
        return;
    }
    if ( Set(MSU_Inhibited) )
    {
        prep_for_fisu;
        var(tx.N.fsn) = var(tx.L.fsn);
        var(tx.N.bib) = var(tx.N.bib);
        var(tx.N.bsn) = (var(tx.X.fsn) - 1)&0x7f;
        var(tx.N.fib) = var(tx.N.fib);
        Signal(DAEDT_FISU);
        return;
    }
    if ( RTB_CYCLE )     /* Retransmission cycle */
    {
        prep_for_msu;
        RTB_AGAIN;
        var(tx.N.fsn) = (var(tx.N.fsn) + 1)&0x7f;
        var(tx.N.bib) = var(tx.N.bib);
        var(tx.N.bsn) = (var(tx.X.fsn) - 1)&0x7f;
        var(tx.N.fib) = var(tx.N.fib);
        Signal(DAEDT_MSU);
        return;
    }
    if ( TB_EMPTY ||        /* Transmission buffer empty */
         Set(RTB_Full) )    /* Retransmission buffer full */
    {
        prep_for_fisu;
        var(tx.N.fsn) = var(tx.L.fsn);
        var(tx.N.bib) = var(tx.N.bib);
        var(tx.N.bsn) = (var(tx.X.fsn) - 1)&0x7f;
        var(tx.N.fib) = var(tx.N.fib);
        Signal(DAEDT_FISU);
        return;
    }
    else
    {
        TB_START;
        prep_for_msu;
        var(tx.L.fsn) = (var(tx.L.fsn) + 1)&0x7f;
        var(tx.N.fsn) = var(tx.L.fsn);
//      if ( var(tx.L.fsn) == var(tx.F.fsn) ) /* RTB empty */
        if ( RTB_EMPTY )
            Start(T7, TXC_T7_Timeout);
        TB_READ;
        Signal(RC_FSNT_Value);
        if ( var(tx.L.fsn) == ((var(tx.F.fsn) - 2)&0x7f) ) /* RTB full */
            Mark(RTB_Full);
        var(tx.N.bib) = var(tx.N.bib);
        var(tx.N.bsn) = (var(tx.X.fsn) - 1)&0x7f;
        var(tx.N.fib) = var(tx.N.fib);
        Signal(DAEDT_MSU);
    }
}

Event(RC_Start)
{
    if ( CurrentState(RC) == State(RC,Active) ) return;
    var(rx.X.fsn) = 0x00;
    var(rx.X.fib) = 0x80;
    var(rx.F.fsn) = 0x00;
    var(rx.T.fsn) = 0x7f;
    var(rtr) = 0;
    Cancel(MSU_FISU_Accepted);
    Cancel(Abnormal_BSNR);
    Cancel(Abnormal_FIBR);
    Cancel(Congestion_Discard);
    Cancel(Congestion_Accept);
    RB_INIT;
    Signal(DAEDR_Start);
    NewState(RC,Active);
}

Event(RC_Reject_MSU_FISU)
{
    if ( CurrentState(RC) != State(RC,Active)) return;
    Cancel(MSU_FISU_Accepted);
}

Event(RC_Accept_MSU_FISU)
{
    if ( CurrentState(RC) != State(RC,Active)) return;
    Mark(MSU_FISU_Accepted);
}

Event(RC_Align_FSNX)
{
    if ( CurrentState(RC) != State(RC,Active)) return;
    Signal(TXC_FSNX_Value);
}

Event(RC_Clear_RB)
{
    if ( CurrentState(RC) != State(RC,Active)) return;
    RB_CLEAR;
    Signal(L3_RB_Cleared);
}

Event(RC_Retreive_BSNT)
{
    var(l3_bsnt) = var(rx.T.bsn) = (var(rx.X.fsn) - 1)&0x7F;
    Signal(L3_BSNT);
}

Event(CC_T5_Timeout)
{
    if ( CurrentState(CC) == State(CC,Congested) )
    {
        Signal(TXC_Send_SIB);
        Start(T5, CC_T5_Timeout);
    }
}

Event(CC_Busy)
{
    if ( ! CurrentState(CC) )
    {
        Signal(TXC_Send_SIB);
        Start(T5, CC_T5_Timeout);
        NewState(CC,Congested);
    }
}

Event(CC_Stop)
{
    if ( CurrentState(CC) == State(CC,Congested) )
    {
        Stop(T5);
        NewState(CC,Normal);
    }
}

Event(RC_Congestion_Discard)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    Mark(Congestion_Discard);
    Signal(CC_Busy);
}

Event(RC_Congestion_Accept)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    Mark(Congestion_Accept);
    Signal(CC_Busy);
}

Event(RC_No_Congestion)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    Cancel(Congestion_Discard);
    Cancel(Congestion_Accept);
    Signal(CC_Normal);
    Signal(TXC_FSNX_Value);
    if ( var(rtr) == 1 )
    {
        Signal(TXC_NACK_To_Be_Sent);
        var(rx.X.fib) = var(rx.X.fib)?0x00:0x80;
    }
}

Event(LSC_Congestion_Discard)
{
    Signal(RC_Congestion_Discard);
    Mark(L3_Congestion_Detect);
}

Event(LSC_Congestion_Accept)
{
    Signal(RC_Congestion_Accept);
    Mark(L3_Congestion_Detect);
}

Event(LSC_No_Congestion)
{
    Signal(RC_No_Congestion);
    Cancel(L3_Congestion_Detect);
}


/*
 *  ------------------------------------------------------------------------
 *
 *  These congestion functions are implementation dependent.  We should define
 *  a congestion onset level and set Congestion_Accept at that point.  We
 *  should also define a congestion panic level and set Congestion_Discard at
 *  that point.  Implemented levels are as follows:
 *
 *  Congestion_Accept   Onset = RB 2/3 full,  Abatement = RB 1/2 full
 *  Congestion_Discard  Onset = RB 3/4 full,  Abatement = RB 1/2 full
 *
 *  Out concept of receive buffer occupancy is a little vague because we are
 *  forwarding packets immediately to the packet scheduler.  Perhaps a better
 *  indication is how many tasks are backed up in the state machine wiatq.  We
 *  can use the same calculations but simplify them by setting the maximum
 *  wait queue depth to 4.  Then we can set congestion levels as follows:
 *
 *  Congestion_Accept   Onset = 2 tasks, Abatement = 1 task
 *  Congestion_Discard  Onset = 3 tasks, Abatement = 1 tasks
 *
 *  Nevertheless, we better watch the packet scheduler backlog queue to make
 *  sure that we are not overloading it.  Unfortunately the backlog queue is
 *  static, but the netdev_dropping is not.  We can check to see if the packet
 *  scheduler is dropping for discard congestion.  I will do that where I
 *  transmit the frame to L3.
 *
 *  ------------------------------------------------------------------------
 */

Event(RB_Congestion_Function)
{
    if ( !Set(L3_Congestion_Detect) ) {
        if ( Set(L2_Congestion_Detect) ) {
            if ( skb_queue_len(&var(read_q)) <= 1 && !netdev_dropping ) {
                Signal(RC_No_Congestion);
                Cancel(L2_Congestion_Detect);
            }
        }
        else {
            if ( skb_queue_len(&var(read_q)) > 1) {
                Signal(RC_Congestion_Accept);
                Mark(L2_Congestion_Detect);
            }
        }
        if ( skb_queue_len(&var(read_q)) > 2 || netdev_dropping ) {
            Signal(RC_Congestion_Discard);
            Mark(L2_Congestion_Detect);
        }
    }
}

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

Event(LSC_SIO)
/*
 *  SIOs do not need to be delivered to the LSC if the LSC is in initial
 *  alignment (IA) or out of service (OOS) or off (OFF).  No SUs are
 *  delivered to the LSC when the LSC is in the off (OFF) state.
 */
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Aligned_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(RC_Stop);
            Signal(SUERM_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,In_Service):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(LSC_Alignment_Not_Possible)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Initial_Alignment):
            Signal(L3_Out_Of_Service);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Local_Processor_Outage);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(IAC_T3_Timeout)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Aligned):
            Signal(LSC_Alignment_Not_Possible);
            Cancel(Emergency);
            return;
    }
}

Event(AERM_Start)
{
    var(Ca) = 0;
    NewState(AERM,Active);
}

Event(AERM_Set_Ti_to_Tin)
{
    var(Ti) = var(Tin);
}

Event(AERM_Set_Ti_to_Tie)
{
    var(Ti) = var(Tie);
}

Event(AERM_Stop)
{
    if ( CurrentState(AERM) != State(AERM,Active) ) return;
    var(Ti) = var(Tin);
    NewState(AERM,Idle);
}

Event(IAC_SIO)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Not_Aligned):
            Stop(T2);
            if ( Set(Emergency) )
            {
                var(t4) = TIMER_T4EV;
                SetTimer(T4, T4EV);
                Signal(TXC_Send_SIE);
            }
            else
            {
                var(t4) = TIMER_T4NV;
                SetTimer(T4, T4NV);
                Signal(TXC_Send_SIN);
            }
            Start(T3, IAC_T3_Timeout);
            NewState(IAC,Aligned);
            return;
        case State(IAC,Proving):
            Stop(T4);
            Signal(AERM_Stop);
            Start(T3, IAC_T3_Timeout);
            NewState(IAC,Aligned);
            return;
    }
}

Event(IAC_SIOS)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Proving):
            Stop(T4);
            Signal(LSC_Alignment_Not_Possible);
            Signal(AERM_Stop);
            Cancel(Emergency);
            NewState(IAC,Idle);
            return;
    }
}

Event(LSC_SIOS)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Aligned_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(RC_Stop);
            Signal(SUERM_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Stop(T1);
            Signal(L3_Out_Of_Service);
            Signal(TXC_Send_SIOS);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,In_Service):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(LSC_FISU_MSU_Received)
{
    switch (CurrentState(LSC))
    {
        case State(LSC,Aligned_Ready):
            Signal(L3_In_Service);
            Stop(T1);
            Signal(TXC_Send_MSU);
            NewState(LSC,In_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Signal(L3_In_Service);
            Stop(T1);
            NewState(LSC,Processor_Outage);
            return;
        case State(LSC,Processor_Outage):
           /*
            *   A modification has been placed here to avoid continual
            *   indication to L3 on every FISU received until the system can
            *   respond to the outage situation.  A single indication is
            *   sufficient.  We have SU compression so it shouldn't be a
            *   problem ;)
            */
#ifndef HAVE_SU_COMPRESSION
            if ( Set(Remote_Processor_Outage) )
            {
                Cancel(Remote_Processor_Outage);
#endif
                Signal(L3_Remote_Processor_Recovered);
#ifndef HAVE_SU_COMPRESSION
            }
#endif
            return;
    }
}

Event(LSC_SIPO)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Aligned_Ready):
            Stop(T1);
            Signal(L3_Remote_Processor_Outage);
            Mark(Remote_Processor_Outage);
            NewState(LSC,Processor_Outage);
            return;
        case State(LSC,Aligned_Not_Ready):
            Signal(L3_Remote_Processor_Outage);
            Mark(Remote_Processor_Outage);
            Stop(T1);
            NewState(LSC,Processor_Outage);
            return;
        case State(LSC,In_Service):
            Signal(TXC_Send_FISU);
            Signal(L3_Remote_Processor_Outage);
            Mark(Remote_Processor_Outage);
            Signal(RC_Align_FSNX);
            Signal(CC_Stop);
            NewState(LSC,Processor_Outage);
            return;
        case State(LSC,Processor_Outage):
           /*
            *   A modification from the SDLs has been placed here to keep the
            *   L2 state machine from delivering an indication to L3 for every
            *   SIPO received.  This should allow the system time to respond
            *   to the condition before overflowing the service primitive
            *   stack.  A single indication should be sufficient.  We have SU
            *   compression so it shouldn't be a problem ;)
            */
#ifndef HAVE_SU_COMPRESSION
            if ( !Set(Remote_Processor_Outage) )
            {
                Mark(Remote_Processor_Outage);
#endif
                Signal(L3_Remote_Processor_Outage);
#ifndef HAVE_SU_COMPRESSION
            }
#endif
            return;
    }
}

Event(IAC_T2_Timeout)
{
    Signal(LSC_Alignment_Not_Possible);
    Cancel(Emergency);
    NewState(IAC,Idle);
    return;
}

Event(LSC_T1_Timeout)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Aligned_Ready):
            Signal(L3_Out_Of_Service);
            Signal(RC_Stop);
            Signal(SUERM_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Signal(L3_Out_Of_Service);
            Signal(TXC_Send_SIOS);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(SUERM_Start)
{
    var(Cs) = 0;
    var(Ns) = 0;
    var(T) = 64;
    NewState(SUERM,Active);
}

Event(LSC_Alignment_Complete)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Initial_Alignment):
            Signal(SUERM_Start);
            Start(T1, LSC_T1_Timeout);
            if ( Set(Local_Processor_Outage) )
            {
                Signal(TXC_Send_SIPO);
                Signal(RC_Reject_MSU_FISU);
                NewState(LSC,Aligned_Not_Ready);
                return;
            }
            else
            {
                Signal(TXC_Send_FISU);
                Signal(RC_Accept_MSU_FISU);
                NewState(LSC,Aligned_Ready);
                return;
            }
    }
}

Event(IAC_T4_Timeout)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Proving):
            if ( Set(Further_Proving) )
            {
                Signal(AERM_Start);
                Cancel(Further_Proving);
                Start(T4, IAC_T4_Timeout);
                return;
            }
            else
            {
                Signal(LSC_Alignment_Complete);
                Signal(AERM_Stop);
                Cancel(Emergency);
                NewState(IAC,Idle);
                return;
            }
    }
}

Event(LSC_SIN)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,In_Service):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(IAC_SIN)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Not_Aligned):
            Stop(T2);
            if ( Set(Emergency) )
            {
                var(t4) = TIMER_T4EV;
                SetTimer(T4, T4EV);
                Signal(TXC_Send_SIE);
            }
            else
            {
                var(t4) = TIMER_T4NV;
                SetTimer(T4, T4NV);
                Signal(TXC_Send_SIN);
            }
            Start(T3, IAC_T3_Timeout);
            NewState(IAC,Aligned);
            return;
        case State(IAC,Aligned):
            Stop(T3);
            if ( var(t4) == TIMER_T4EV )
                Signal(AERM_Set_Ti_to_Tie);
            Signal(AERM_Start);
            Start(T4, IAC_T4_Timeout);
            var(Cp) = 0;
            Cancel(Further_Proving);
            NewState(IAC,Proving);
            return;
    }
}

Event(LSC_SIE)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,In_Service):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(L3_Out_Of_Service);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(IAC_SIE)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Not_Aligned):
            Stop(T2);
            if ( Set(Emergency) )
            {
                var(t4) = TIMER_T4EV;
                SetTimer(T4, T4EV);
                Signal(TXC_Send_SIE);
            }
            else
            {
                var(t4) = TIMER_T4EV;
                SetTimer(T4, T4NV);
                Signal(TXC_Send_SIN);
            }
            Start(T3, IAC_T3_Timeout);
            NewState(IAC,Aligned);
            return;
        case State(IAC,Aligned):
            var(t4) = TIMER_T4EV;
            SetTimer(T4, T4EV);
            Stop(T3);
            Signal(AERM_Set_Ti_to_Tie);
            Signal(AERM_Start);
            Start(T4, IAC_T4_Timeout);
            var(Cp) = 0;
            Cancel(Further_Proving);
            NewState(IAC,Proving);
            return;
        case State(IAC,Proving):
            if ( var(t4) == TIMER_T4EV ) return;
            Stop(T4);
            var(t4) = TIMER_T4EV;
            SetTimer(T4, T4EV);
            Signal(AERM_Stop);
            Signal(AERM_Set_Ti_to_Tie);
            Signal(AERM_Start);
            Cancel(Further_Proving);
            Start(T4, IAC_T4_Timeout);
            return;
    }
}

#define SS7_L2_UNB = 1;
#define SS7_L2_UNF = 1;

Event(RC_Signal_Unit)
{
    if ( CurrentState(RC) != State(RC,Active) ) return;
    if ( ((var(rx.len))==1)||((var(rx.len))==2) ) {
        switch ( var(rx.sio) ) {
            case LSSU_SIO:  { Signal(LSC_SIO);  Signal(IAC_SIO);  break; }
            case LSSU_SIN:  { Signal(LSC_SIN);  Signal(IAC_SIN);  break; }
            case LSSU_SIE:  { Signal(LSC_SIE);  Signal(IAC_SIE);  break; }
            case LSSU_SIOS: { Signal(LSC_SIOS); Signal(IAC_SIOS); break; }
            case LSSU_SIPO: { Signal(LSC_SIPO);                   break; }
            case LSSU_SIB:  { Signal(TXC_SIB_Received);           break; }
        }
        return;
    }
    if ( SN_OUTSIDE(((var(rx.F.fsn)-1)&0x7f), (var(rx.R.bsn)), var(rx.T.fsn)) ) {
        RB_DISCARD;
        if ( Set(Abnormal_BSNR) ) {
            Signal(LSC_Link_Failure);
            NewState(RC,Power_On);
            return;
        } else {
            Mark(Abnormal_BSNR);
            var(unb) = 0;
            return;
        }
    }
    if ( Set(Abnormal_BSNR) ) {
        if ( var(unb) == 1 ) {
            Cancel(Abnormal_BSNR);
        } else {
            var(unb) = 1;
            RB_DISCARD;
            return;
        }
    }
    if ( var(rx.R.fib) == var(rx.X.fib) ) {
        if ( Set(Abnormal_FIBR) ) {
            if ( var(unf) == 1 ) {
                Cancel(Abnormal_FIBR);
            } else {
                var(unf) = 1;
                RB_DISCARD;
                return;
            }
        }
        Signal(LSC_FISU_MSU_Received);
        Signal(TXC_BSNR_and_BIBR);
        var(rx.F.fsn) = (var(rx.R.bsn)+1)&0x7f;
        if ( !Set(MSU_FISU_Accepted) ) {
            RB_DISCARD;
            return;
        }
        Signal(RB_Congestion_Function);
        if ( Set(Congestion_Discard) ) {
            if ( var(rx.len) > 2 ) {
                var(rtr) = 1;
            }
            RB_DISCARD;
            Signal(CC_Busy);    /* not really required anymore */
            return;
        }
        if ( (var(rx.R.fsn) == var(rx.X.fsn)) && (var(rx.len) > 2) ) {
            var(rx.X.fsn) = (var(rx.X.fsn)+1)&0x7f;
            var(rtr) = 0;
            if ( Set(Congestion_Accept) ) {
                RB_WRITE;
                Signal(CC_Busy);    /* not really required anymore */
            } else {
                RB_WRITE;
                Signal(TXC_FSNX_Value); }
            return;
        }
        if ( (var(rx.R.fsn) == ((var(rx.X.fsn)-1)&0x7f)) ) {
            RB_DISCARD;
            return;
        } else {
            if ( Set(Congestion_Accept) ) {
                var(rtr) = 1;
                Signal(CC_Busy);    /* not really required anymore */
                RB_DISCARD;
                return;
            } else {
                Signal(TXC_NACK_To_Be_Sent);
                var(rtr) = 1;
                var(rx.X.fib) = var(rx.X.fib)?0x00:0x80;
                RB_DISCARD;
                return;
            }
        }
    } else {
        RB_DISCARD;
        if ( Set(Abnormal_FIBR) ) {
            Signal(LSC_Link_Failure);
            return;
        }
        if ( var(rtr) == 1 ) {
            Signal(TXC_BSNR_and_BIBR);
            var(rx.F.fsn) = (var(rx.R.bsn)+1)&0x7f;
            return;
        }
        Mark(Abnormal_FIBR);
        var(unf) = 0;
        return;
    }
}

Event(LSC_Stop)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Initial_Alignment):
            Signal(IAC_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Local_Processor_Outage);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Ready):
            Stop(T1);
            Signal(RC_Stop);
            Signal(SUERM_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Aligned_Not_Ready):
            Stop(T1);
            Signal(TXC_Send_SIOS);
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,In_Service):
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            NewState(LSC,Out_of_Service);
            return;
        case State(LSC,Processor_Outage):
            Signal(SUERM_Stop);
            Signal(RC_Stop);
            Signal(TXC_Send_SIOS);
            Cancel(Emergency);
            Cancel(Local_Processor_Outage);
            Cancel(Remote_Processor_Outage);
            NewState(LSC,Out_of_Service);
            return;
    }
}

Event(LSC_Clear_RTB)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Processor_Outage):
            Cancel(Local_Processor_Outage);
            Signal(TXC_Send_FISU);
            Signal(TXC_Clear_RTB);
            return;
    }
}

Event(IAC_Correct_SU)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Proving):
            if ( Set(Further_Proving) )
            {
                Stop(T4);
                Signal(AERM_Start);
                Cancel(Further_Proving);
                Start(T4, IAC_T4_Timeout);
                return;
            }
            return;
    }
}

Event(IAC_Abort_Proving)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Proving):
            var(Cp)++;
            if ( var(Cp) == 5 )
            {
                Signal(LSC_Alignment_Not_Possible);
                Stop(T4);
                Signal(AERM_Stop);
                Cancel(Emergency);
                NewState(IAC,Idle);
                return;
            }
            Mark(Further_Proving);
            return;
    }
}

Event(AERM_SU_In_Error)
{
    if ( CurrentState(AERM) == State(AERM,Active) )
    {
        var(Ca)++;
        if ( var(Ca) == var(Ti) )
        {
            Signal(IAC_Abort_Proving);
            NewState(AERM,Idle);
        }
    }
}

Event(SUERM_SU_In_Error)
{
    if ( CurrentState(SUERM) == State(SUERM,Active) )
    {
        var(Cs)++;
        if ( var(Cs) == var(T) )
        {
            Signal(LSC_Link_Failure);
            NewState(SUERM,Idle);
            return;
        }
        if ( var(Cs) )
        {
            var(Ns)++;
            if ( var(Ns) == 256 )
            {
                var(Cs)--;
                var(Ns) = 0;
            }
        }
    }
}

Event(SUERM_Correct_SU)
{
    if ( CurrentState(SUERM) == State(SUERM,Active) )
    {
        if ( var(Cs) )
        {
            var(Ns)++;
            if ( var(Ns) == 256 )
            {
                var(Cs)--;
                var(Ns) = 0;
            }
        }
    }
}

/*
 *  When we are in octet counting mode we have to generate an error to the
 *  SUERM/AERM every 16 octets from the line.  At 64,000 bps, this is every
 *  2ms.  Unfortunately, kernel timers are only accurate to 10ms.  Therefore,
 *  the SS7 device (which has a finer timing source) will have to generate the
 *  pulses to us after being told to start octet counting.
 */
Event(DAEDR_16_Octets)
{
    start_bh_atomic();
    {
        if (p->DAEDR_state)
            while (var(Octets_Counted)) {
                var(Octets_Counted)--;
                if ( Set(Octet_Counting_Mode) ) {
                    Signal(SUERM_SU_In_Error);
                    Signal(AERM_SU_In_Error);
                }
            }
        else
            var(Octets_Counted) = 0;
    }
    end_bh_atomic();
}

/*
 *  Even if though the DAEDR may not be active, yet, we still have to track
 *  the state of Octet Counting or we will get out of step with the driver.
 *  Linux drivers are not so nice as to halt the receive while running the
 *  transmit; i.e., there is no half-open command.
 */
Event(DAEDR_Loss_of_Sync)
{
    Mark(Octet_Counting_Mode);
}

Event(DAEDR_Error_Frame)
{
    start_bh_atomic();
    {
        if ( p->DAEDR_state && !Set(Octet_Counting_Mode) ) {
            Signal(SUERM_SU_In_Error);
            Signal(AERM_SU_In_Error);
        }
    }
    end_bh_atomic();
}

Event(DAEDR_Compressed_Frame)
{
    start_bh_atomic();
    {
        if (p->DAEDR_state)
            while (var(SUs_Repeated)) {
                var(SUs_Repeated)--;
                Signal(IAC_Correct_SU);
                Signal(SUERM_Correct_SU);
            }
        else
            var(SUs_Repeated) = 0;
    }
    end_bh_atomic();
}

Event(DAEDR_Received_Frame)
{
    struct sk_buff *skb;
    start_bh_atomic();
    {
        while ((skb = skb_dequeue(&var(read_q)))) {
            if (p->DAEDR_state)
            {
#if 0           
                unsigned int len = skb->len;

                /*
                 *  Note: the driver must check both of these
                 *        before we get here.
                 */
                if ( (len<3 || len>SS7_MTU) ||
                    ((len<64+3?len:63+3) != (var(rx.len) = skb->data[2]&0x3f)))
                {
                    kfree_skb(skb);
                    return Signal(DAEDR_Error_Frame);        
                }
                else
#endif
                {
                    unsigned char* data = skb->data;
                    var(rsu_skb)  = skb;
                    var(rx.R.bsn) = data[0] & 0x7f;
                    var(rx.R.bib) = data[0] & 0x80;
                    var(rx.R.fsn) = data[1] & 0x7f;
                    var(rx.R.fib) = data[1] & 0x80;
                    var(rx.len)   = data[2] & 0x3f;
                    var(rx.sio)   = data[3] & 0x07;
                    Cancel(Octet_Counting_Mode);
                    Signal(RC_Signal_Unit);
                    Signal(IAC_Correct_SU);
                    Signal(SUERM_Correct_SU);
                }
            }
            else {
                Cancel(Octet_Counting_Mode);
                kfree_skb(skb);
            }
        }
    }
    end_bh_atomic();
}

Event(LSC_Clear_Buffers)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Out_of_Service):
            Signal(L3_RTB_Cleared);
            return;
        case State(LSC,Initial_Alignment):
            Signal(L3_RTB_Cleared);
            Cancel(Local_Processor_Outage);
            return;
        case State(LSC,Aligned_Not_Ready):
            Signal(L3_RTB_Cleared);
            Cancel(Local_Processor_Outage);
            Signal(TXC_Send_FISU);
            NewState(LSC,Aligned_Ready);
            return;
        case State(LSC,Processor_Outage):
            Cancel(Local_Processor_Outage);
            Signal(RC_Clear_RB);
            Signal(RC_Accept_MSU_FISU);
            Signal(TXC_Send_FISU);
            Signal(TXC_Clear_TB);
            Signal(TXC_Clear_RTB);
            return;
    }
}

Event(LSC_Resume)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Out_of_Service):
            Cancel(Local_Processor_Outage);
            return;
        case State(LSC,Initial_Alignment):
            Cancel(Local_Processor_Outage);
            return;
        case State(LSC,Aligned_Ready):
            return;
        case State(LSC,Aligned_Not_Ready):
            Cancel(Local_Processor_Outage);
            Signal(TXC_Send_FISU);
            NewState(LSC,Aligned_Ready);
            return;
        case State(LSC,Processor_Outage):
            Cancel(Local_Processor_Outage);
            Signal(RC_Accept_MSU_FISU);
            if ( Set(Remote_Processor_Outage) )
            {
                Signal(TXC_Send_FISU);
                Signal(L3_Remote_Processor_Outage);
                return;
            }
            Signal(TXC_Send_MSU);
            NewState(LSC,In_Service);
            return;
    }
}

Event(LSC_Local_Processor_Outage)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Out_of_Service):
        case State(LSC,Initial_Alignment):
            Mark(Local_Processor_Outage);
            return;
        case State(LSC,Aligned_Ready):
            Mark(Local_Processor_Outage);
            Signal(TXC_Send_SIPO);
            Signal(RC_Reject_MSU_FISU);
            NewState(LSC,Aligned_Not_Ready);
            return;
        case State(LSC,In_Service):
            Signal(TXC_Send_SIPO);
            Signal(RC_Reject_MSU_FISU);
            Mark(Local_Processor_Outage);
            Signal(RC_Align_FSNX);
            Signal(CC_Stop);
            NewState(LSC,Processor_Outage);
            return;
        case State(LSC,Processor_Outage):
            Mark(Local_Processor_Outage);
            Signal(TXC_Send_SIPO);
            return;
    }
}

Event(IAC_Emergency)
{
    switch ( CurrentState(IAC) )
    {
        case State(IAC,Proving):
            Signal(TXC_Send_SIE);
            Stop(T4);
            var(t4) = TIMER_T4EV;
            SetTimer(T4, T4EV);
            Signal(AERM_Stop);
            Signal(AERM_Set_Ti_to_Tie);
            Signal(AERM_Start);
            Cancel(Further_Proving);
            Start(T4, IAC_T4_Timeout);
            return;
        case State(IAC,Aligned):
            Signal(TXC_Send_SIE);
            var(t4) = TIMER_T4EV;
            SetTimer(T4, T4EV);
            Start(T4, IAC_T4_Timeout);
            return;
        default:
            Mark(Emergency);
    }
}

Event(LSC_Emergency)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Out_of_Service):
            Mark(Emergency);
            return;
        case State(LSC,Initial_Alignment):
            Mark(Emergency);
            Signal(IAC_Emergency);
            return;
    }
}

Event(LSC_Emergency_Ceases)
{
    switch ( CurrentState(LSC) )
    {
        case State(LSC,Out_of_Service):
            Cancel(Emergency);
            return;
    }
}

Event(IAC_Start)
{
    Signal(TXC_Send_SIO);
    Start(T2, IAC_T2_Timeout);
    NewState(IAC,Not_Aligned);
}

Event(DAEDT_Start)
/*
 *  DAEDT_Start is responsible for preparing for transmission of the first
 *  frame and starting the transmitters.  The transmitters should be allowed
 *  to idle flags until a call to TXC_Transmission_Request is invoked.
 *
 *  The rest of the DAEDT state machine is embedded in the TxISR.
 */
{
   /*
    *   Only do this once, if we are already running, don't start up again
    *   unless we want to do a reset?  This should be done at the ioctl()
    *   level if we want to reset.
    *
    *   Note that the transmitters are initialized on power-on.  This means
    *   that when the device driver is opened the transmitters will be set
    *   up to send SIOS.  The receivers will only be set up when LSC_Start()
    *   has been called by L3.
    */
    
    if ( CurrentState(DAEDT) == State(DAEDT,Active) ) return;
    
   /*
    *   This should set up for the next frame (which is an SIOS).
    */
    
    Signal(TXC_Transmission_Request);
    NewState(DAEDT,Active);
    return;
}

Event(DAEDT_Transmission_Request)
{
    if ( CurrentState(DAEDT) == State(DAEDT,Active) )
        Signal(TXC_Transmission_Request);
}

Event(TXC_Start)
{
   /*
    *   The transmitters and transmission control are only started once for
    *   SS7, at power-on.  The transmitters stay operational until the
    *   device is physically powered down (or in our case, the device is
    *   closed and the driver is removed).  Transmitters stay blindly
    *   sending SIOS when the LSC is stopped.
    */
    
    if ( CurrentState(TXC) == State(TXC,Active) ) return;
    
    Cancel(RTB_Full);
    Cancel(SIB_Received);
    Cancel(Clear_RTB);
    var(tx.sio) = LSSU_SIOS;
    Mark(LSSU_Available);
    Cancel(MSU_Inhibited);
    var(tx.L.fsn) = var(tx.N.fsn) = 0x7f;
    var(tx.X.fsn) = 0x00;
    var(tx.N.fib) = var(tx.N.bib) = 0x80;
    var(tx.F.fsn) = 0x00;
    TB_INIT;
    Signal(DAEDT_Start);
    NewState(TXC,Active);
}

Event(LSC_Start)
{
    if ( CurrentState(LSC) != State(LSC,Out_of_Service) ) return;
    
    Signal(RC_Start);
    Signal(TXC_Start);  /*
                    *   This is rather redundant: transmission control and
                    *   the transmitters should have been started on
                    *   power-on.
                    */
    if ( Set(Emergency) ) Signal(IAC_Emergency);
    Signal(IAC_Start);
    NewState(LSC,Initial_Alignment);
}

Event(LSC_Retreive_BSNT)
{
    Signal(RC_Retreive_BSNT);
}

Event(LSC_Retreival_Request_and_FSNC)
{
    Signal(TXC_Retreival_Request_and_FSNC);
}

Event(LSC_Power_On)
{
   /*
    *   This power-on sequence should only be performed once, regardless of
    *   how many times the device driver is opened or closed.  This
    *   initializes the transmitters to send SIOS and should never be
    *   changed hence.
    */
    
    if ( CurrentState(LSC) != State(LSC,Power_On) ) return;
    
    var(Tie) = AERM_TIE_VALUE;
    var(Tin) = AERM_TIN_VALUE;
    Signal(TXC_Start);
    Signal(TXC_Send_SIOS);
    Signal(AERM_Set_Ti_to_Tin);
    Cancel(Local_Processor_Outage);
    Cancel(Emergency);
    NewState(LSC,Out_of_Service);
}

/*
 *  ------------------------------------------------------------------------
 *
 *  L2 Primitives: to L3
 *
 *  ------------------------------------------------------------------------
 */

static void make_primitive(struct ss7_iface *p, unsigned char cmd,
        unsigned char *ptr, unsigned int size)
{
    struct sk_buff *skb;

    if ((skb = alloc_skb(size+1, GFP_KERNEL)) == NULL) return;

    memcpy(skb->data+1, ptr, size);

    skb->data[0] = cmd;
    skb->dev = p->dev;
    skb->protocol = AF_SS7;
    skb->pkt_type = ETH_P_SS7;
    skb->priority = TC_PRIO_CONTROL;
    skb->mac.raw = skb->data;

    netif_rx(skb);

    if (netdev_dropping) {
        Signal(RC_Congestion_Discard);
        Mark(L2_Congestion_Detect);
    }
}

/*
 *  ------------------------------------------------------------------------
 *
 *  L3 Primitives: from L3
 *
 *  ------------------------------------------------------------------------
 */

/*
*   This is our transmission congestion check.  We check the total buffer
*   occupancy and apply the necessary congestion control signal.
*
*   Level       Onset       Abatement
*   ----------- ----------- -----------
*   0           N/A         N/A
*   1           1/2         1/3
*   2           3/4         2/3
*   3           Full        Not Full
*
*   This should really be rearranged to follow the documentation and should
*   include discard status.  The threshold levels should be precalculated when
*   the buffers are allocated rather than here at run-time.  Also, the same
*   check must be performed whenever frames are acknowledged and removed from
*   the retransmit buffer in RC_Signal_Unit or TXC_BSNR_and_BIBR.
*/

#define Q_MAX TB_Q_MAX_SIZE + RTB_Q_MAX_SIZE

static __inline void check_congestion(struct ss7_iface *p)
{
    unsigned int occupancy =
        skb_queue_len(&p->xmit_q) +
        skb_queue_len(&p->rexm_q);

    switch (p->cong_level) {    /* is there a quicker way to do this? */
        case 0:
/* onset 1 */   if ( occupancy*2 >= Q_MAX*1 ) { p->cong_level = 1; ss7if_L3_Link_Congested(p); }
/* onset 2 */   if ( occupancy*4 >= Q_MAX*3 ) { p->cong_level = 2; ss7if_L3_Link_Congested(p); }
/* onset 3 */   if ( occupancy*1 >= Q_MAX*1 ) { p->cong_level = 3; ss7if_L3_Link_Congested(p); }
            break;
        case 1:
/* onset 2 */   if ( occupancy*4 >= Q_MAX*3 ) { p->cong_level = 2; ss7if_L3_Link_Congested(p); }
/* onset 3 */   if ( occupancy*1 >= Q_MAX*1 ) { p->cong_level = 3; ss7if_L3_Link_Congested(p); }
/* abate 1 */   if ( occupancy*3 <  Q_MAX*1 ) { p->cong_level = 0; ss7if_L3_Link_Congested(p); }
            break;
        case 2:
/* onset 3 */   if ( occupancy*1 >= Q_MAX*1 ) { p->cong_level = 3; ss7if_L3_Link_Congested(p); }
/* abate 2 */   if ( occupancy*3 <  Q_MAX*2 ) { p->cong_level = 1; ss7if_L3_Link_Congested(p); }
/* abate 1 */   if ( occupancy*3 <  Q_MAX*1 ) { p->cong_level = 0; ss7if_L3_Link_Congested(p); }
            break;
        case 3:
/* abate 3 */   if ( occupancy*1 <  Q_MAX*1 ) { p->cong_level = 2; ss7if_L3_Link_Congested(p); }
/* abate 2 */   if ( occupancy*3 <  Q_MAX*2 ) { p->cong_level = 1; ss7if_L3_Link_Congested(p); }
/* abate 1 */   if ( occupancy*3 <  Q_MAX*1 ) { p->cong_level = 0; ss7if_L3_Link_Congested(p); }
            break;
    }
}

static void (*cmdjump[])(struct ss7_iface *) =
{
    &ss7if_DAEDT_Transmitter_Wakeup,
    &ss7if_LSC_Emergency,
    &ss7if_LSC_Emergency_Ceases,
    &ss7if_LSC_Start,
    &ss7if_LSC_Stop,
    &ss7if_LSC_Retreive_BSNT,
    &ss7if_LSC_Retreival_Request_and_FSNC,
    &ss7if_LSC_Resume,
    &ss7if_LSC_Clear_Buffers,
    &ss7if_LSC_Local_Processor_Outage,
    &ss7if_LSC_Congestion_Discard,
    &ss7if_LSC_Congestion_Accept,
    &ss7if_LSC_No_Congestion
};

#define SS7_HDRLEN_COMMAND 1
#define SS7_HDRLEN_L2      3
#define SS7_HDRLEN_L3      7

Event(LSC_L3_Primitive)
{
    struct sk_buff *skb;
    start_bh_atomic();
    {
        if ((skb = skb_dequeue(&p->writ_q)))
        {
            unsigned char cmd = skb->data[0];

            if (cmd >= SS7_L2_LAST) {
                if (net_ratelimit())
                    printk(KERN_WARNING "SS7/LINK: %s received invalid command %d\n",
                            p->dev->name,(int)cmd);
                kfree_skb(skb);
                return;
            }
            if (cmd == SS7_L2_PDU ) {
                check_congestion(p);
            if ( (((skb->data[3])>>4)&0x3) < p->cong_level ) {
                    if (net_ratelimit())
                        printk(KERN_WARNING "SS7LINK: %s is discarding at congestion level %d\n",
                                p->dev->name,p->cong_level);
                    kfree_skb(skb);
                    return;
                }
                skb_queue_tail(&p->xmit_q,skb);
            }
            cmdjump[cmd](p);
        }
    }
    end_bh_atomic();
}

/*
 *  TRANSMIT INTERFACE
 */

int ss7if_output(struct sk_buff *skb, struct device *dev)
{
    struct ss7_iface *iface = dev->ss7_ptr;
    skb_queue_tail(&iface->writ_q,skb);
    ss7_sm_dispatch(&iface->tx_taskq,&ss7if_LSC_L3_Primitive);
    return 0;
}

/*
 *  RECEIVE INTERRUPT SERVICE ROUTINE INTERFACE
 */

void ss7if_rx(struct sk_buff *skb)
{
    struct ss7_iface *iface = skb->dev->ss7_ptr;
    skb_queue_tail(&iface->read_q,skb);
    ss7_sm_dispatch(&iface->rx_taskq,&ss7if_DAEDR_Received_Frame);
}
void ss7if_los(struct device *dev)
{
    struct ss7_iface *iface = dev->ss7_ptr;
    ss7_sm_dispatch(&iface->rx_taskq,&ss7if_DAEDR_Loss_of_Sync);
}
void ss7if_err(struct device *dev)
{
    struct ss7_iface *iface = dev->ss7_ptr;
    if (!iface->Octet_Counting_Mode)
        ss7_sm_dispatch(&iface->rx_taskq,&ss7if_DAEDR_Error_Frame);
}
void ss7if_oct(struct device *dev)
{
    struct ss7_iface *iface = dev->ss7_ptr;
    if (iface->Octet_Counting_Mode) {
        iface->Octets_Counted++;
        ss7_sm_dispatch(&iface->rx_taskq,&ss7if_DAEDR_16_Octets);
    }
}
void ss7if_rpt(struct device *dev)
{
    struct ss7_iface *iface = dev->ss7_ptr;
    iface->SUs_Repeated++;
    ss7_sm_dispatch(&iface->rx_taskq,&ss7if_DAEDR_Compressed_Frame);
}

#endif  /* defined(CONFIG_SS7)||defined(CONFIG_SS7_MODULE) */
