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

 @(#) $Id: sli.c,v 0.7.2.5 2001/02/03 05:48:51 brian Test $

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

 Copyright (C) 1997-2001  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: 2001/02/03 05:48:51 $ by $Author: brian $

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

static char const ident[] = "$Id: sli.c,v 0.7.2.5 2001/02/03 05:48:51 brian Test $";

#define SL_DESCRIP   "SS7/LINK: SS7 LEVEL 2 (LINK) STREAMS MODULE."
#define SL_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SL_DEVICES   "Supports all SDTI compliant devices."
#define SL_CONTACT   "Brian F. G. Bidulock <bidulock@openswitch.org>"
#define SL_BANNER    SL_DESCRIP   "\n" \
                     SL_COPYRIGHT "\n" \
                     SL_DEVICES   "\n" \
                     SL_CONTACT   "\n"
            
#ifdef MODULE
MODULE_AUTHOR(SL_CONTACT);
MODULE_DESCRIPTION(SL_DESCRIP);
MODULE_SUPPORTED_DEVICES(SL_DEVICES);
#endif

#ifdef SL_DEBUG
int sl_debug = SL_DEBUG;
#else
int sl_debug = 2;
#endif

/*
 *  Primary data structure for module and module list.
 */

/*
 * ---------------------------------------------------------
 *
 *  SS7 Level 2 (LINK) state machine datastructures, macros,
 *  constants, and frame formats.
 *
 * ---------------------------------------------------------
 */

/* FIXME: These have to be in bytes for STREAMS */
#define TB_Q_MAX_SIZE 127   /* max # messages in transmission   buffer */
#define RT_Q_MAX_SIZE 127   /* max # messages in retransmission buffer */
#define RB_Q_MAX_SIZE 127   /* max # messages in receive        buffer */

enum {
    LSSU_SIO    = 0x0,  /*                      */
    LSSU_SIN    = 0x1,  /* normal               */
    LSSU_SIE    = 0x2,  /* emergency            */
    LSSU_SIOS   = 0x3,  /* out of service       */
    LSSU_SIPO   = 0x4,  /* processor outage     */
    LSSU_SIB    = 0x5   /* busy                 */
};

struct sni {
    unsigned bib:1 __attribute__ ((packed));
    unsigned bsn:7 __attribute__ ((packed));
    unsigned fib:1 __attribute__ ((packed));
    unsigned fsn:7 __attribute__ ((packed));
} __attribute__ ((packed));

struct tx_sni {
    struct sni      N       /* normal       */  __attribute__ ((packed));
    struct sni      F       /* first in RTB */  __attribute__ ((packed));
    struct sni      L       /* last  in RTB */  __attribute__ ((packed));
    struct sni      C       /* RTB clear    */  __attribute__ ((packed));
    struct sni      X       /* expected     */  __attribute__ ((packed));
    struct sni      R       /* received     */  __attribute__ ((packed));
    unsigned char   sio     /* SIO for LSSU */  __attribute__ ((packed));
    int             len     /* len for Tx   */  __attribute__ ((packed));
} __attribute__ ((packed));

struct rx_sni {
    struct sni      X       /* expected     */  __attribute__ ((packed));
    struct sni      F       /*              */  __attribute__ ((packed));
    struct sni      R       /* received     */  __attribute__ ((packed));
    struct sni      T       /* transmitted  */  __attribute__ ((packed));
    unsigned char   sio     /* SIO for LSSU */  __attribute__ ((packed));
    int             len     /* len for Rx   */  __attribute__ ((packed));
} __attribute__ ((packed));

struct l2_header {
    unsigned bib:1 __attribute__ ((packed));
    unsigned bsn:7 __attribute__ ((packed));
    unsigned fib:1 __attribute__ ((packed));
    unsigned fsn:7 __attribute__ ((packed));
    unsigned li0:2 __attribute__ ((packed));
    unsigned  li:6 __attribute__ ((packed));
} __attribute__ ((packed));

#define SN_OUTSIDE(lower,middle,upper) \
    (   ( lower < upper ) \
        ? ( ( middle < lower ) || ( middle > upper ) ) \
        : ( ( middle < lower ) || ( middle > upper ) ) \
        )

typedef struct sl_sm {
    int t1_id;
    int t2_id;
    int t3_id;
    int t4_id;
    int t5_id;
    int t6_id;
    int t7_id;

    int   LSC_state;    /*   LSC state variable */

        int Local_Processor_Outage;     /* local  processor is out      */
        int Remote_Processor_Outage;    /* remote processor is out      */
        int Emergency;                  /* link emergency condition     */

    int   IAC_state;    /*   IAC state variable */

        int t4;
        int Cp;                         /* count of proving periods     */
        int Further_Proving;            /* further proving required     */

    int   TXC_state;    /*   TXC state variable */

        struct tx_sni tx;               /* transmit SN's and IB's       */
        int MSU_Inhibited;              /* are we inhibiting MSUs?      */
        int LSSU_Available;             /* is an LSSU avail for trans?  */
        int RTB_Full;                   /* is the retrans buffer full?  */
        int Clear_RTB;                  /* is the RTB to be cleared?    */
        int SIB_Received;               /* was an SIB received?         */
        int MSU_Incomplete;             /* are we still sending?        */

    int    RC_state;    /*    RC state variable */

        struct rx_sni rx;               /* receive  SN's and IB's       */
        int rtr;                        /* retransmission indicator     */
        int unb;                        /* unreasonable BSN received    */
        int unf;                        /* unreasonable FSN received    */
        int MSU_FISU_Accepted;          /* proving complete             */
        int Abnormal_BSNR;              /* received abnormal BSN        */
        int Abnormal_FIBR;              /* received abnormal FSN        */
        int Congestion_Discard;         /* discarding under Rx congest  */
        int Congestion_Accept;          /* accepting  under Rx congest  */
        int L2_Congestion_Detect;       /* congestion detected by L2    */
        int L3_Congestion_Detect;       /* congestion detected by L3    */

    int    CC_state;    /*    CC state variable */

        int cong_level;                 /* congestion level             */

    int DAEDT_state;    /* DAEDT state variable */
    int DAEDR_state;    /* DAEDR state variable */

        int Octet_Counting_Mode;        /* receivers free-wheeling      */

    int  AERM_state;    /*  AERM state variable */

        int Ca;                         /*                              */
        int Ti;                         /*                              */
        int Tin;                        /*                              */
        int Tie;                        /*                              */

    int SUERM_state;    /* SUERM state variable */

        int Cs;                         /*                              */
        int Ns;                         /*                              */
        int T;                          /*                              */

} sl_sm_t;

enum {  LSC_STATE_Power_On,
        LSC_STATE_Out_of_Service,
        LSC_STATE_Initial_Alignment,
        LSC_STATE_Aligned_Ready,
        LSC_STATE_Aligned_Not_Ready,
        LSC_STATE_In_Service,
        LSC_STATE_Processor_Outage  };

enum {  IAC_STATE_Idle,
        IAC_STATE_Not_Aligned,
        IAC_STATE_Aligned,
        IAC_STATE_Proving           }; 

enum {  TXC_STATE_Power_On,
        TXC_STATE_Active            }; 

enum {  RC_STATE_Power_On,
        RC_STATE_Active             }; 

enum {  CC_STATE_Normal,
        CC_STATE_Congested          }; 

enum {  DAEDT_STATE_Power_On,
        DAEDT_STATE_Active,
        DAEDT_STATE_Sleeping        }; 

enum {  DAEDR_STATE_Power_On,
        DAEDR_STATE_Active          }; 

enum {  AERM_STATE_Idle,
        AERM_STATE_Active           }; 

enum {  SUERM_STATE_Idle,
        SUERM_STATE_Active          };

/*
 * ---------------------------------------------------------
 *
 *  Congiguration data for state machines.  This config info
 *  can be changed with IOCTLs.
 *
 * ---------------------------------------------------------
 */

typedef struct sl_conf {
    unsigned int    pvar;       /* protocol variant */
    unsigned int    txc_method; /* TXC method */
    unsigned int    t1;         /* timer t1  timeout duration (ticks) */
    unsigned int    t2;         /* timer t2  timeout duration (ticks) */
    unsigned int    t2l;        /* timer t2l timeout duration (ticks) */
    unsigned int    t2h;        /* timer t2h timeout duration (ticks) */
    unsigned int    t3;         /* timer t3  timeout duration (ticks) */
    unsigned int    t4n;        /* timer t4n timeout duration (ticks) */
    unsigned int    t4e;        /* timer t4e timeout duration (ticks) */
    unsigned int    t5;         /* timer t5  timeout duration (ticks) */
    unsigned int    t6;         /* timer t6  timeout duration (ticks) */
    unsigned int    t7;         /* timer t7  timeout duration (ticks) */
    unsigned int    rb_abate;   /* receive buffer congestion abatement     (#msgs) */
    unsigned int    rb_accept;  /* receive buffer congestion onset accept  (#msgs) */
    unsigned int    rb_discard; /* receive buffer congestion onset discard (#msgs) */
} sl_conf_t;

enum {
    PVAR_ITUT,  /* ITU-T protocol variant */
    PVAR_ANSI,  /* ANSI  protocol variant */
    PVAR_ETSI,  /* ETSI  protocol variant */
    PVAR_MAX = PVAR_ETSI
};

enum {
    TXCM_BASIC, /* Basic transmission control */
    TXCM_PCR,   /* Preventative Cyclic Retransmission transmission control */
    TXCM_MAX = TXCM_PCR
};

static struct sl_conf sl_conf_default = {
    PVAR_ITUT, TXCM_BASIC, 4000,   500,  500,  7500, 100, 820, 60, 12, 600, 100,  0,  1,  2 };

static struct sl_conf sl_conf_minimum = {
    0,         0,          4000,   500,  500,  7500, 100, 750, 40,  8, 300,  50,  0,  0,  0 };

static struct sl_conf sl_conf_maximum = {
    PVAR_MAX,  TXCM_MAX,   5000, 15000, 7500, 15000, 200, 950, 60, 12, 600, 200, -1, -1, -1 };

typedef enum {
    ID_NONE    = 0,
    ID_BUFWAIT = 1,
    ID_TIMEOUT = 2
} sl_idtype;

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

struct sl
{
    queue_t     *rq;
    sl_ulong    state;
    sl_ulong    flags;
    int         wb_id;      /* bufcall id for write service */
    int         rb_id;      /* bufcall id for read  service */
    int         backlog;    /* backlog of messages in receive buffer */
    sl_sm_t     sm;
    sl_conf_t   config;
};

#define SL_N_MINOR 16    /* for now */
struct sl sl_sl[SL_N_MINOR];

typedef enum retval
{
    DONE,
    RETRY,
    ERR
} retval_t;

static struct module_info sl_minfo =
{
    0xeeee, /* FIXME */ /* Module ID number             */
    "sl",               /* Module name                  */
    SL_MINDATA,         /* Min packet size accepted     */
    SL_MAXDATA,         /* Max packet size accepted     */
    SL_MAXDATA,         /* Hi water mark                */
    0                   /* Lo water mark                */
};

static int sl_open(queue_t *, dev_t *, int, int, cred_t *);
static int sl_close(queue_t *, int, cred_t *);

static void sl_rput(queue_t *, mblk_t *);
static void sl_rsrv(queue_t *);

static void sl_wput(queue_t *, mblk_t *);
static void sl_wsrv(queue_t *);

struct module_stat sl_rstat =
{
    NULL,       /* ms_xptr  */  /* Private statistics           */
    0,          /* ms_xsize */  /* Size of private statistics   */
    0,          /* ms_flags */  /* Boolean stats                */
    0,          /* ms_pcnt  */  /* count of calls to put        */
    0,          /* ms_scnt  */  /* count of calls to srv        */
    0,          /* ms_ocnt  */  /* count of calls to open       */
    0,          /* ms_ccnt  */  /* count of calls to close      */
    0           /* ms_acnt  */  /* count of calls to admin      */
};

static struct qinit sl_rinit =
{
    sl_rput,    /* qi_putp  */  /* Read put (message from below)    */
    sl_rsrv,    /* qi_srvp  */  /* Read queue service               */
    sl_open,    /* qi_open  */  /* Each open                        */
    sl_close,   /* qi_close */  /* Lase close                       */
    NULL,       /* qi_admin */  /* Admin (not used)                 */
    &sl_minfo,  /* qi_info  */  /* Information                      */
    &sl_rstat   /* qi_mstat */  /* Statistics                       */
};

struct module_stat sl_wstat =
{
    NULL,       /* ms_xptr  */  /* Private statistics           */
    0,          /* ms_xsize */  /* Size of private statistics   */
    0,          /* ms_flags */  /* Boolean stats                */
    0,          /* ms_pcnt  */  /* count of calls to put        */
    0,          /* ms_scnt  */  /* count of calls to srv        */
    0,          /* ms_ocnt  */  /* count of calls to open       */
    0,          /* ms_ccnt  */  /* count of calls to close      */
    0           /* ms_acnt  */  /* count of calls to admin      */
};

static struct qinit sl_winit =
{
    sl_wput,    /* qi_putp  */  /* Read put (message from below)    */
    sl_wsrv,    /* qi_srvp  */  /* Read queue service               */
    NULL,       /* qi_open  */  /* Each open                        */
    NULL,       /* qi_close */  /* Lase close                       */
    NULL,       /* qi_admin */  /* Admin (not used)                 */
    &sl_minfo,  /* qi_info  */  /* Information                      */
    &sl_wstat   /* qi_mstat */  /* Statistics                       */
};

#ifdef MODULE
static
#endif
struct streamtab sl_info =
{
    sl_rinit,   /* Upper read queue                 */
    sl_winit,   /* Upper write queue                */
    NULL,       /* Lower read queue  (not a mux)    */
    NULL        /* Lower write queue (not a mux)    */
};

void sl_init(void)
{
    printk(KERN_INFO SL_BANNER); /* console splash */
    
    /* Do some groovy initializations... */
};

void sl_terminate(void)
{
    /* Do some cleanup. */
};

static int sl_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
    dev_t i;

    if (sflag == DEVOPEN)
        return EINVAL;    /* not a device */
    if (sflag == CLONEOPEN) {
        if ( WR(q)->q_next == NULL ) /* not a device */
            return EINVAL;
        for ( i=1; i<SL_N_MINOR; i++ )
            if (sl_sl[i].rq == NULL)
                break;
    } else {
        if ( (i = getminor(*devp)) >= SL_N_MINOR)
            return ENXIO;
    }
    *devp = makedevice(getmajor(*devp), i);

    if (q->q_ptr != NULL)
        return 0;         /* already open  */

    sl_sl[i].rq = q;
    sl_sl[i].state = SL_UNATTACHED;     /* FIXME: Do we really need a Style 1 and a Style 2
                                           signalling link module? */
    sl_sl[i].bufwait = 0;
    sl_sl[i].idtype  = ID_NONE;
    sl_sl[i].flags   = 0;

    /* sl_sl[i].config  = sl_config_default; */ /* default configuration */
    memcpy(&sl_sl[i].config, &sl_config_default, sizeof(sl_config_default));

    /* do some state machine initializations here */
    
    /*  Initialize timers */
    /*  Initialize buffers (queues) */

    q->q_ptr = WR(q)->q_ptr = &sl_sl[i];

#ifdef MODULE
    MOD_INC_USE_COUNT;
#endif
    return 0;
}

static int sl_close(queue_t *q, int flag, cred_t *crp)
{
    struct sl *sl = (struct *sl)q->q_ptr;

    assert(sl != NULL);
    assert(sl->rq == q);

    if (sl->sm.t1_id) { untimeout(sl->sm.t1_id); }  /* stop timer t1 if running */
    if (sl->sm.t2_id) { untimeout(sl->sm.t2_id); }  /* stop timer t2 if running */
    if (sl->sm.t3_id) { untimeout(sl->sm.t3_id); }  /* stop timer t3 if running */
    if (sl->sm.t4_id) { untimeout(sl->sm.t4_id); }  /* stop timer t4 if running */
    if (sl->sm.t5_id) { untimeout(sl->sm.t5_id); }  /* stop timer t5 if running */
    if (sl->sm.t6_id) { untimeout(sl->sm.t6_id); }  /* stop timer t6 if running */
    if (sl->sm.t7_id) { untimeout(sl->sm.t7_id); }  /* stop timer t7 if running */

    if (sl->wb_id) { unbufcall(sl->wb_id); } /* cancel write buffer callout */
    if (sl->rb_id) { unbufcall(sl->rb_id); } /* cancel read  buffer callout */

    sl->state = SL_UNATTACHED;
    sl->rq = NULL;
    q->q->ptr = WR(q)->q_ptr = NULL;

#ifdef MODULE
    MOD_DEC_USE_COUNT;
#endif
    return 0;
}

static int sl_rcong(queue_t *q)
{
    struct sl *sl = q->q_ptr;
    struct sl_sm *sm = &sl->sm;

    /*
     *  First, if L3 has explicitly set congestion, then we want to listen to whatever it
     *  says.  This is because there may be congestion past a multiplexer which we do not
     *  know about.  Only if we are on our own to determine congestion levels do we do the
     *  calculations.
     *
     *  This calculation has two levels of abatement and two levels of onset as illustrated
     *  below:
     *  
     *            |                  |                |
     *            |                  |Accept--------+ | Discard ----+
     *            |                  |              | |             |
     *  <--None-->|<-----------------+--------------+ |             |
     *            |<-----------------+----------------+-------------+
     *            |                  |                |
     *          Abate             Accept           Discard
     *                             Onset            Onset
     */
    if ( !sm->L3_Congestion_Detect ) {
        if ( sm->Congestion_Discard ) {
            if ( sl->backlog <= RB_Q_CONG_ACCEPT_ABATE ) {
                /* downgrade to no congestion */
                sm->L2_Congestion_Detect = 0;
                ss7if_RC_No_Congestion(sm);
            }
            if ( sl->backlog <= RB_Q_CONG_DISCARD_ABATE ) {
                /* congestion abated */;
                sm->L2_Congestion_Detect = 1;
                ss7if_RC_Congestion_Accept(sm);
            }
        }
        else
        if ( sm->Congestion_Accept  ) {
            if ( sl->backlog <= RB_Q_CONG_ACCEPT_ABATE ) {
                /* congestion abated */;
                sm->L2_Congestion_Detect = 0;
                ss7if_RC_No_Congestion(sm);
            }
            if ( sl->backlog >= RB_Q_CONG_DISCARD_ONSET ) {
                /* congestion onset */;
                sm->L2_Congestion_Detect = 1;
                ss7if_RC_Congestion_Discard(sm);
            }
        }
        else {
            if ( sl->backlog >= RB_Q_CONG_ACCEPT_ONSET ) {
                /* congestion onset */
                sm->L2_Congestion_Detect = 1;
                ss7if_RC_Congestion_Accept(sm);
            }
            if ( sl->backlog >= RB_Q_CONG_DISCARD_ONSET ) {
                /* congestion onset */
                sm->L2_Congestion_Detect = 1;
                ss7if_RC_Congestion_Discard(sm);
            }
        }
    }
    return 0;
}

static void sl_rput(queue_t *q, mblk_t *mp)
{
    struct sl *sl = q->q_ptr;
    struct sl_sm * sm = &sl->sm;

    if ( mp->b_datap->db_type >= QPCTL ) {
        putnext(q, mp);
        return;
    }
    if ( !q->q_count && canputnext(q) ) {
        /*
         *  If we have a backlog, don't putnext, put it on
         *  our service queue to preserve FIFO ordering with
         *  other messages.
         */
        putnext(q);
        return;
    }
    putq(q, mp);        /* put it on our queue */
    sl->backlog++;
    /*
     *  There are two reasons for being here: (1) we have an existing backlog which has not
     *  cleared yet.  (2) there is an upstream blockage.  In either case, we want to
     *  recalculate receive congestion and set the appropriate indications.
     *
     *  For STREAMS the queue occupancy is in terms of the number of bytes in the queue
     *  (not the number of messages as in skb_queues in the Linux Kernel implementation).
     *  Since receive buffer occupancy is better expressed in terms of messages (received
     *  processing delays are message based rather than byte based), we keep track of the
     *  number of messages backlogged.
     */
    sl_rcong(q);
    return;
}

static void sl_rsrv(queue_t *q)
{
    mblk_t *mp;
    struct sl *sl = q->q_ptr;
    struct sl_sm * sm = &sl->sm;

    while ( (mp = getq(q)) != NULL ) {
        if ( mp->b_datap->db_type >= QPCTL ) {
            putnext(q, mp);
            sl->backlog--;
            continue;
        }
        if ( canputnext(q) ) {
            putnext(q);
            sl->backlog--;
            continue;
        }
        putbq(q, mp); /* put it back on our queue */
        /*
         *  Ok, we didn't manage to clear our backlog.  That means we are still in receive
         *  congestion, but we can check whether we can downgrade the receive congestion
         *  from Congestion Discard to Congestion Accept.
         */
        sl_rcong(q);
        return;
    }
    /*
     *  If we make it here, we have cleared our backlog.  Because we were here
     *  at all indicates that we had receive congestion before this point.  We
     *  must indicate to the SS7 L2 state machines that congestion has abated.
     */
    if (!q->q_count)
        sl->backlog = 0;
    sl_rcong(q);
    return;
}

static inline int sl_m_proto(queue_t *q, mblk_t *mp) {
    sl_t *sl = (sl_t *)q->q_ptr;
    union SL_primitives *req = (union SL_primitives *)mp->b_rptr;
    return sl_user_req(sl, mp, req->sl_primitive);
}

/*
 *  This is a message written to us by the SLS users (i.e., MTP L3, SLSI).
 */
static void sl_wput(queue_t *q, mblk_t *mp)
{
    switch (mp->b_datap->db_type)
    {
        case M_DATA:
            if ( sl_m_data(q, mp) == RETRY )
                break;
            return;
        case M_PROTO:
            if ( q->q_count || !canputnext(q) )
                break;
            if ( sl_user_req((sl_t *)q->q_ptr, mp) == RETRY )
                break;
            return;
        case M_PCPROTO:
            if ( sl_user_req((sl_t *)q->q_ptr, mp) == RETRY )
                freemsg(mp);
            return;
        case M_FLUSH:
            if ( *mp->b_rptr & FLUSHW ) {
                flushq(q, FLUSHALL);
                *mp->b_rptr &= ~FLUSHW;
            }
            if ( *mp->b_rptr & FLUSHR ) {
                flushq(RD(q), FLUSHALL);
                *mp->b_rptr &= ~FLUSHR;
            }
            else
                freemsg(mp);
            return;
        case M_IOCTL:
            if ( sl_m_ioctl((sl_t *)q->q_ptr, mp) == RETRY ) break;
            return;
        default:
            freemsg(mp);
            return;
    }
    putq(q, mp);
}

static void sl_wsrv(queue_t *q)
{
    mblk_t *mp;

    while ( (mp = getq(q)) ) {
        switch ( mp->b_datap->db_type )
        {
            case M_DATA:
                if ( sl_m_data(q, mp) == RETRY )
                    break;
                return;
            case M_PROTO:
            case M_PCPROTO:
        }
    }
}
