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

 @(#) $Id: sdt.c,v 0.7.2.26 2001/02/03 05:48:47 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:47 $ by $Author: brian $

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

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

/*
 *  This is an SDT (Signalling Data Terminal) kernel module which provides all
 *  of the capabilities of the SDTI.  It can be used by drivers in two
 *  fashions:
 *
 *  1)  Drivers can provide a set of missing functions by registering with the
 *      module using sdt_register_driver.  This relieves much of the STREAMS
 *      burden from the driver implementation and permits the driver to be
 *      more portable across the Linux, STREAMS, and BSD implementation of the
 *      OpenSS7 stack.
 *
 *  2)  Drivers which implement the OpenSS7 SDLI can push this module to form
 *      an SDTI driver implementation.
 *
 *  Both fashions permit SS7 Level 2 compliant state machines to be tested
 *  once, but used by many drivers.
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/modversions.h>
#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>

#include "../debug.h"
#include "../bufq.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>
#include <ss7/devi.h>
#include <ss7/devi_ioctl.h>
#include <ss7/sdli.h>
#include <ss7/sdli_ioctl.h>
#include <ss7/sdti.h>
#include <ss7/sdti_ioctl.h>

#include "../lmi/lm.h"
#include "../devi/dev.h"
#include "../sdli/sdl.h"
#include "../sdti/sdt.h"

#define SDT_DESCRIP   "SS7/SDT: (Signalling Data Terminal) STREAMS DRIVER."
#define SDT_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SDT_DEVICES   "Supports OpenSS7 SDT drivers."
#define SDT_CONTACT   "Brian Bidulock <bidulock@openss7.org>"
#define SDT_BANNER    SDT_DESCRIP   "\n" \
                      SDT_COPYRIGHT "\n" \
                      SDT_DEVICES   "\n" \
                      SDT_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(SDT_CONTACT);
MODULE_DESCRIPTION(SDT_DESCRIP);
MODULE_SUPPORTED_DEVICE(SDT_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef SDT_DEBUG
static int sdt_debug = SDT_DEBUG;
#else
static int sdt_debug = 2;
#endif

#define DEBUG_LEVEL sdt_debug

#define SDT_CMAJOR 0
#define SDT_NMINOR 255

/*
 *  =======================================================================
 *
 *  STREAMS Definitions
 *
 *  =======================================================================
 */

static struct module_info sdt_minfo =
{
    0,              /* Module ID number             */
    "sdt",          /* Module name                  */
    4,              /* Min packet size accepted     */
    272,            /* Max packet size accepted     */
    8*272,          /* Hi water mark                */
    1*272           /* Lo water mark                */
};

static void sdt_rput (queue_t *, mblk_t *);
static void sdt_rsrv (queue_t *);
static int  sdt_open (queue_t *, dev_t *, int, int, cred_t *);
static int  sdt_close(queue_t *, int, cred_t *);

static struct qinit sdt_rinit =
{
    sdt_rput,   /* Read put (message from below)    */
    sdt_rsrv,   /* Read queue service               */
    sdt_open,   /* Each open                        */
    sdt_close,  /* Last close                       */
    NULL,       /* Admin (not used)                 */
    &sdt_minfo, /* Information                      */
    NULL        /* Statistics                       */
};

static void sdt_wput (queue_t *, mblk_t *);
static void sdt_wsrv (queue_t *);

static struct qinit sdt_winit =
{
    sdt_wput,   /* Write put (message from above)   */
    sdt_wsrv,   /* Write queue service              */
    NULL,       /* Each open                        */
    NULL,       /* Last close                       */
    NULL,       /* Admin (not used)                 */
    &sdt_minfo, /* Information                      */
    NULL        /* Statistics                       */
};

#ifndef LIS_REGISTERED
static
#endif
struct streamtab sdt_info =
{
    &sdt_rinit, /* Upper read  queue */
    &sdt_winit, /* Upper write queue */
    NULL,       /* Lower read  queue */
    NULL        /* Lower write queue */
};

/*
 *  =======================================================================
 *
 *  LMI PROTOCOL CONFIGURATION IOCTLs
 *
 *  =======================================================================
 */

static int sdt_iocgoptions(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdt->option, arg, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocsoptions(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(arg, &sdt->option, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocgconfig(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdt->config, arg, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocsconfig(sdt_t *sdt, int cmd, void *arg)
{
    signed long int *src, *dst, *end;
    DTRACE;
    dst = (signed long int *)&sdt->config;
    end = (signed long int *)((&sdt->config)+1);
    src = arg;
    while ( dst < end ) {
        if ( *src != -1 ) *dst = *src;
        dst++; src++;
    }
    return(0);
}

static int sdt_iocgstatem(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdt->statem, arg, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocsstatsp(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}

static int sdt_iocgstatsp(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}

static int sdt_ioccstats(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    bzero(&sdt->stats, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocgstats(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdt->stats, arg, _IOC_SIZE(cmd));
    return(0);
}

static int sdt_iocsnotify(sdt_t *sdt, int cmd, void *arg)
{
    caddr_t dst, src, end;
    DTRACE;
    src = arg;
    dst = (caddr_t)&sdt->notify;
    end = (caddr_t)((&sdt->notify)+1);
    while ( dst < end ) *dst++ |= *src++;
    return(0);
}

static int sdt_ioccnotify(sdt_t *sdt, int cmd, void *arg)
{
    caddr_t dst, src, end;
    DTRACE;
    src = arg;
    dst = (caddr_t)&sdt->notify;
    end = (caddr_t)((&sdt->notify)+1);
    while ( dst < end ) *dst++ &= ~*src++;
    return(0);
}

static int sdt_iocgnotify(sdt_t *sdt, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdt->notify, arg, _IOC_SIZE(cmd));
    return(0);
}

static int (*sdt_ioc_lmiops [])(sdt_t *, int, void *) =
{
    sdt_iocgoptions, /* SDT_IOCGOPTIONS */
    sdt_iocsoptions, /* SDT_IOCSOPTIONS */
    sdt_iocgconfig,  /* SDT_IOCGCONFIG  */
    sdt_iocsconfig,  /* SDT_IOCSCONFIG  */
    NULL,            /* SDT_IOCTCONFIG  */
    NULL,            /* SDT_IOCCCONFIG  */
    sdt_iocgstatem,  /* SDT_IOCGSTATEM  */
    NULL,            /* SDT_IOCCMRESET  */
    sdt_iocgstatsp,  /* SDT_IOCGSTATSP  */
    sdt_iocsstatsp,  /* SDT_IOCSSTATSP  */
    sdt_iocgstats,   /* SDT_IOCGSTATS   */
    sdt_ioccstats,   /* SDT_IOCCSTATS   */
    sdt_iocgnotify,  /* SDT_IOCGNOTIFY  */
    sdt_iocsnotify,  /* SDT_IOCSNOTIFY  */
    sdt_ioccnotify   /* SDT_IOCCNOTIFY  */
};

/*
 *  ==============================================================
 *
 *  SDT->SL Service Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  ==============================================================
 */

static inline void sdt_uprim(sdt_t *sdt, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(sdt_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sdt_long *)mp->b_wptr)++ = prim;
        putnext(sdt->rq, mp);
    }
}

static inline void sdt_rc_signal_unit(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        *((int *)mp->b_rptr) = SDT_RC_SIGNAL_UNIT_IND;
    }
    putnext(sdt->rq, mp);
}
static inline void sdt_rc_congestion_accept(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PCPROTO, SDT_RC_CONGESTION_ACCEPT_IND);
}
static inline void sdt_rc_congestion_discard(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PCPROTO, SDT_RC_CONGESTION_DISCARD_IND);
}
static inline void sdt_rc_no_congestion(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PCPROTO, SDT_RC_NO_CONGESTION_IND);
}
static inline void sdt_iac_correct_su(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PROTO, SDT_IAC_CORRECT_SU_IND);
}
static inline void sdt_iac_abort_proving(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PCPROTO, SDT_IAC_ABORT_PROVING_IND);
}
static inline void sdt_lsc_link_failure(sdt_t *sdt) {
    DTRACE;
    sdt_uprim(sdt, M_PCPROTO, SDT_LSC_LINK_FAILURE_IND);
}

static sdt_ucalls_t sdt_mod_ucalls =
{
    sdt_rc_signal_unit,             /* rc_signal_unit        */
    sdt_rc_congestion_accept,       /* rc_congestion_accept  */
    sdt_rc_congestion_discard,      /* rc_congestion_discard */
    sdt_rc_no_congestion,           /* rc_no_congestion      */
    sdt_iac_correct_su,             /* iac_correct_su        */
    sdt_iac_abort_proving,          /* iac_abort_proving     */
    sdt_lsc_link_failure            /* lsc_link_failure      */
};

/*
 *  ==============================================================
 *
 *  SDT->SDL Service Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  ==============================================================
 */

static inline void
sdt_dprim(sdt_t *sdt, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(sdt_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sdt_long *)mp->b_wptr)++ = prim;
        putnext(sdt->wq, mp);
    }
}

static void sdl_daedt_xmit_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        *((int *)mp->b_rptr) = SDL_DAEDT_TRANSMISSION_REQ;
        mp->b_datap->db_type = M_PROTO;
    }
    putnext(sdt->wq, mp);
}

static void sdl_daedt_start_req(sdt_t *sdt) {
    DTRACE;
    sdt_dprim(sdt, M_PCPROTO, SDL_DAEDT_START_REQ);
}

static void sdl_daedr_start_req(sdt_t *sdt) {
    DTRACE;
    sdt_dprim(sdt, M_PCPROTO, SDL_DAEDR_START_REQ);
}

static struct sdt_mcalls sdt_mod_dcalls =
{
    sdl_daedt_xmit_req,     /* daedt_xmit   */
    sdl_daedt_start_req,    /* daedt_start  */
    sdl_daedr_start_req     /* daedr_start  */
};

/*
 *  ==============================================================
 *
 *  SDT->SDL Service Calls (Driver)
 *
 *  ==============================================================
 */

static void sdl_daedt_xmit(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    sdt->driver->dcalls->daedt_xmit(sdt->device, mp);
}
static void sdl_daedt_start(sdt_t *sdt) {
    DTRACE;
    sdt->driver->dcalls->daedt_start(sdt->device);
}
static void sdl_daedr_start(sdt_t *sdt) {
    DTRACE;
    sdt->driver->dcalls->daedr_start(sdt->device);
}

static struct sdt_mcalls sdt_drv_dcalls =
{
    sdl_daedt_xmit,         /* daedt_xmit   */
    sdl_daedt_start,        /* daedt_start  */
    sdl_daedr_start         /* daedr_start  */
};

/*
 *  =========================================================================
 *
 *  PROTOCOL STATE MACHINE FUNCTIONS
 *
 *  =========================================================================
 */

static inline void
sdt_aerm_su_in_error(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.aerm_state == SDT_STATE_MONITORING ) {
        sdt->statem.Ca++;
        if ( sdt->statem.Ca >= sdt->statem.Ti ) {
            sdt->statem.aborted_proving = 1;
            sdt->ucalls->iac_abort_proving(sdt); /*-upstream-*/
            sdt->statem.aerm_state = SDT_STATE_IDLE;
        }
    }
}

static inline void
sdt_suerm_start(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.Cs = 0;
    sdt->statem.Ns = 0;
    sdt->statem.suerm_state = SDT_STATE_IN_SERVICE;
}

static inline void
sdt_suerm_stop(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.suerm_state = SDT_STATE_IDLE;
}

static inline void
sdt_suerm_su_in_error(sdt_t *sdt) /* RxISR */
{
    DTRACE;
    if ( sdt->statem.suerm_state == SDT_STATE_IN_SERVICE ) {
        sdt->statem.Cs++;
        if ( sdt->statem.Cs >= sdt->config.T ) {
            sdt->ucalls->lsc_link_failure(sdt); /*-upstream-*/
            sdt->statem.suerm_state = SDT_STATE_IDLE;
            return;
        }
        if ( sdt->statem.Cs ) {
            sdt->statem.Ns++;
            if ( sdt->statem.Ns >= sdt->config.D ) {
                sdt->statem.Cs--;
                sdt->statem.Ns = 0;
            }
        }
    }
}

static inline void
sdt_eim_su_in_error(sdt_t *sdt) /* RxISR */
{
    DTRACE;
    if ( sdt->statem.eim_state == SDT_STATE_MONITORING ) {
        sdt->statem.interval_error = 1;
    }
}

static inline void
sdt_suerm_correct_su(sdt_t *sdt) /* RxISR */
{
    DTRACE;
    if ( sdt->statem.suerm_state == SDT_STATE_IN_SERVICE ) {
        if ( sdt->statem.Cs ) {
            sdt->statem.Ns++;
            if ( sdt->statem.Ns >= sdt->config.D ) {
                sdt->statem.Cs--;
                sdt->statem.Ns = 0;
            }
        }
    }
}

static inline void
sdt_eim_correct_su(sdt_t *sdt) /* RxISR */
{
    DTRACE;
    if ( sdt->statem.eim_state == SDT_STATE_MONITORING ) {
        sdt->statem.su_received = 1;
    }
}

static void sdt_t8_timeout(sdt_t *sdt);

static inline void
sdt_timer_stop_t8(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->timers.t8 ) untimeout(sdt->timers.t8);
}

static inline void
sdt_timer_start_t8(sdt_t *sdt)
{
    DTRACE;
    sdt_timer_stop_t8(sdt);
    sdt->timers.t8 = timeout(
            (timo_fcn_t *)sdt_t8_timeout, (caddr_t)sdt, sdt->config.t8);
}


static void
sdt_t8_timeout(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.eim_state == SDT_STATE_MONITORING ) {
        sdt_timer_start_t8(sdt);
#if 0   /* probably just limit compress count */
        if ( sdt->option.popt & SS7_POPT_PING )
        {
            int psw;            /* force frame once per t8 */
            SPLSTR(psw);
            sdt->device->ifflags &= ~SDT_IF_SU_REPEAT;
            SPLX(psw);
        }
#endif
        if (sdt->statem.su_received) {
            sdt->statem.su_received = 0;
            if ( !sdt->statem.interval_error ) {
                if ( (sdt->statem.Ce -= sdt->config.De) < 0 )
                    sdt->statem.Ce = 0;
                return;
            }
        }
        sdt->statem.Ce += sdt->config.Ue;
        sdt->statem.interval_error = 0;
        if ( sdt->statem.Ce > sdt->config.Te ) {
            sdt->ucalls->lsc_link_failure(sdt); /*-upstream-*/
            sdt->statem.eim_state = SDT_STATE_IDLE;
        }
    }
}

static inline void
sdt_eim_start(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.Ce = 0;
    sdt->statem.interval_error = 0;
    sdt->statem.su_received = 0;
    sdt_timer_start_t8(sdt);
    sdt->statem.eim_state = SDT_STATE_MONITORING;
}

static inline void
sdt_eim_stop(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.eim_state = SDT_STATE_IDLE;
    sdt_timer_stop_t8(sdt);
}

static inline void
sdt_daedr_correct_su(sdt_t *sdt, int count)
{
    DTRACE;
    while ( count-- ) {
        if ( sdt->statem.aborted_proving ) {
            sdt->ucalls->iac_correct_su(sdt);
            sdt->statem.aborted_proving = 0;
        }
        sdt_eim_correct_su(sdt);
        sdt_suerm_correct_su(sdt);
    }
}

static inline void
sdt_daedr_su_in_error(sdt_t *sdt)
{
    DTRACE;
    sdt_suerm_su_in_error(sdt);
    sdt_aerm_su_in_error(sdt);
}

static inline void
sdt_daedr_received_bits(sdt_t *sdt, mblk_t *mp)
{
    DTRACE;
    sdt->ucalls->rc_signal_unit(sdt, mp);   /*-upstream-*/
    sdt_daedr_correct_su(sdt, 1);
}

/*
 *  ==============================================================
 *
 *  SL->SDT Service Calls (Driver)
 *
 *  ==============================================================
 */

static void
sdt_daedt_xmit(sdt_t *sdt, mblk_t *mp)
{
    DTRACE;
    if ( sdt->statem.daedt_state != SDT_STATE_IDLE ) {
        *((sdt_long *)mp->b_rptr) = SDL_DAEDT_TRANSMISSION_REQ;
        sdt->dcalls->daedt_xmit(sdt, mp);
    }
    freemsg(mp);

}

static void
sdt_daedr_start(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.daedr_state == SDT_STATE_IDLE ) {
        sdt->statem.daedr_state = SDT_STATE_IN_SERVICE;
        sdt->dcalls->daedr_start(sdt);
    }
}

static void
sdt_daedt_start(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.daedt_state == SDT_STATE_IDLE ) {
        sdt->statem.daedt_state = SDT_STATE_IN_SERVICE;
        sdt->dcalls->daedt_start(sdt);
    }
}

static void
sdt_aerm_start(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.Ca = 0;
    sdt->statem.aerm_state = SDT_STATE_MONITORING;
}

static void
sdt_aerm_stop(sdt_t *sdt)
{
    DTRACE;
    sdt->statem.aerm_state = SDT_STATE_IDLE;
    sdt->statem.Ti = sdt->config.Tin;
}

static void
sdt_aerm_set_ti_to_tin(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.aerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Ti = sdt->config.Tin;
    }
}

static void
sdt_aerm_set_ti_to_tie(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->statem.aerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Ti = sdt->config.Tie;
    }
}

static void
sdt_suerm_eim_start(sdt_t *sdt)
{
    DTRACE;
    if ( sdt->option.popt & SS7_POPT_HSL )  sdt_eim_start(sdt);
    else                                    sdt_suerm_start(sdt);
}

static void
sdt_suerm_eim_stop(sdt_t *sdt)
{
    DTRACE;
    sdt_eim_stop(sdt);
    sdt_suerm_stop(sdt);
}

static sdt_dcalls_t sdt_drv_ops =
{
    sdt_daedt_xmit,             /* daedt_xmit   */
    sdt_daedt_start,            /* daedt_start  */
    sdt_daedr_start,            /* daedr_start  */
    sdt_aerm_start,             /* aerm_start   */
    sdt_aerm_stop,              /* aerm_stop    */
    sdt_aerm_set_ti_to_tin,     /* aerm_set_tin */
    sdt_aerm_set_ti_to_tie,     /* aerm_set_tie */
    sdt_suerm_eim_start,        /* suerm_start  */
    sdt_suerm_eim_stop          /* suerm_stop   */
};

/*
 *  ==============================================================
 *
 *  SL->SDT Service Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  ==============================================================
 */

static void sdt_daedt_xmit_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    sdt_daedt_xmit(sdt, mp);
}
static void sdt_daedt_start_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_daedt_start(sdt);
}
static void sdt_daedr_start_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_daedr_start(sdt);
}
static void sdt_aerm_start_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_aerm_start(sdt);
}
static void sdt_aerm_stop_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_aerm_stop(sdt);
}
static void sdt_aerm_set_ti_to_tin_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_aerm_set_ti_to_tin(sdt);
}
static void sdt_aerm_set_ti_to_tie_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_aerm_set_ti_to_tie(sdt);
}
static void sdt_suerm_eim_start_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_suerm_eim_start(sdt);
}
static void sdt_suerm_eim_stop_req(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sdt_suerm_eim_stop(sdt);
}
static void (*sdt_sl_ops[])(sdt_t *, mblk_t *) =
{
    sdt_daedt_xmit_req,         /* SDT_DAEDT_TRANSMISSION_REQ */
    sdt_daedt_start_req,        /* SDT_DAEDT_START_REQ        */
    sdt_daedr_start_req,        /* SDT_DAEDR_START_REQ        */
    sdt_aerm_start_req,         /* SDT_AERM_START_REQ         */
    sdt_aerm_stop_req,          /* SDT_AERM_STOP_REQ          */
    sdt_aerm_set_ti_to_tin_req, /* SDT_AERM_SET_TI_TO_TIN_REQ */
    sdt_aerm_set_ti_to_tie_req, /* SDT_AERM_SET_TI_TO_TIE_REQ */
    sdt_suerm_eim_start_req,    /* SDT_SUERM_START_REQ        */
    sdt_suerm_eim_stop_req      /* SDT_SUERM_STOP_REQ         */
};

/*
 *  ==============================================================
 *
 *  SDL->SDT Service Calls (Driver)  (WARNING: called at IRQ)
 *
 *  ==============================================================
 */

static void sdl_daedr_received_bits(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    sdt_daedr_received_bits(sdl->module, mp);
}

static void sdl_daedr_correct_su(sdl_t *sdl, int count)
{
    DTRACE;
    sdt_daedr_correct_su(sdl->module, count);
}

static void sdl_daedr_su_in_error(sdl_t *sdl)
{
    DTRACE;
    sdt_daedr_su_in_error(sdl->module);
}

/* driver calls */

static sdl_ucalls_t sdt_drv_ucalls =
{
    sdl_daedr_received_bits, /* daedr_received_bits */
    sdl_daedr_correct_su,    /* daedr_correct_su    */
    sdl_daedr_su_in_error    /* daedr_su_in_error   */
};

/*
 *  ==============================================================
 *
 *  SDL->SDT Service Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  ==============================================================
 */

static void sdl_daedr_received_bits_ind(sdt_t *sdt, mblk_t *mp)
{
    DTRACE;
    sdt_daedr_received_bits(sdt, mp);
}
static void sdl_daedr_correct_su_ind(sdt_t *sdt, mblk_t *mp)
{
    int count = ((sdl_daedr_correct_su_ind_t *)mp->b_rptr)->sdl_count;
    DTRACE;
    freemsg(mp);
    sdt_daedr_correct_su(sdt, count);
}
static void sdl_daedr_su_in_error_ind(sdt_t *sdt, mblk_t *mp)
{
    DTRACE;
    sdt_daedr_su_in_error(sdt);
}

static void (*sdt_sdl_ops[])(sdt_t *, mblk_t *) =
{
    sdl_daedr_received_bits_ind,    /* SDL_DAEDR_RECEIVED_BITS_IND  */
    sdl_daedr_correct_su_ind,       /* SDL_DAEDR_CORRECT_SU_IND     */
    sdl_daedr_su_in_error_ind       /* SDL_DAEDR_SU_IN_ERROR_IND    */
};

/*
 *  =======================================================================
 *
 *  M_IOCTL handling
 *
 *  =======================================================================
 */

static int
sdt_do_ioctl(lmi_t *sdt, int cmd, void *arg)
{
    lmi_driver_t *drv;
    int nr = _IOC_NR(cmd);

    DTRACE;
    if ( _IOC_TYPE(cmd) == SDT_IOC_MAGIC )
        if ( SDT_IOC_FIRST <= nr && nr <= SDT_IOC_LAST )
            if ( sdt_ioc_lmiops[nr] )
                return sdt_ioc_lmiops[nr]((sdt_t *)sdt, cmd, arg);

    if ( (drv = sdt->driver) )
        return drv->ops.lmi.ioctl(sdt, cmd, arg);

    return -ENXIO;
}

#ifndef abs
#define abs(x) ((x)<0 ? -(x):(x))
#endif

static inline int
sdt_m_ioctl(queue_t *q, mblk_t *mp)
{
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
    void *arg = mp->b_cont?mp->b_cont->b_rptr:NULL;
    int cmd = iocp->ioc_cmd, size = iocp->ioc_count;
    int ret = -EINVAL;

    DTRACE;
    if ( size >= _IOC_SIZE(cmd) ) {
        ret = sdt_do_ioctl((lmi_t *)sdt, cmd, arg);
    }
    if ( abs(ret) == ENXIO ) {
        if ( q->q_next ) {
            putnext(q, mp);
            return(0);
        }
    }
    if ( ret == 0 ) {
        mp->b_datap->db_type = M_IOCACK;
        iocp->ioc_error = 0;
        iocp->ioc_rval  = 0;
    } else {
        mp->b_datap->db_type = M_IOCNAK;
        iocp->ioc_error = abs(ret);
        iocp->ioc_rval  = -1;
    }
    qreply(q, mp);
    return(0);
}



/*
 *  =======================================================================
 *
 *  M_PROTO, M_PCPROTO handling
 *
 *  =======================================================================
 */

static inline int
sdt_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    int prim = *((sdt_long *)mp->b_rptr);

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SDT_DSTR_FIRST <= prim && prim <= SDT_DSTR_LAST ) {
        (*sdt_sl_ops[prim - SDT_DSTR_FIRST])(sdt, mp);
        return(0);
    }
    if ( q->q_next) {
        putnext(q, mp);
        return (0);
    }
    if ( LMI_DSTR_FIRST <= prim && prim <= LMI_DSTR_LAST ) {
        err = (*lmi_lmi_ops[prim - LMI_DSTR_FIRST])((lmi_t *)sdt, mp);
    }
    return err;
}

static inline int
sdl_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    int prim = *((sdt_long *)mp->b_rptr);

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SDL_USTR_FIRST <= prim && prim <= SDL_USTR_LAST ) {
        (*sdt_sdl_ops[SDL_USTR_FIRST - prim])(sdt, mp);
        return(0);
    }
    if ( q->q_next ) {
        putnext(q, mp);
        return (0);
    }
    return err;
}

/*
 *  =======================================================================
 *
 *  M_DATA handling
 *
 *  =======================================================================
 */

static inline int
sdt_m_data(queue_t *q, mblk_t *mp)
{
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    DTRACE;
    sdt->dcalls->daedt_xmit(sdt, mp);
    return(0);
}

static inline int
sdl_m_data(queue_t *q, mblk_t *mp)
{
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    DTRACE;
    sdt->ucalls->rc_signal_unit(sdt, mp);
    return(0);
}

/*
 *  --------------------------------------------
 *
 *  STREAMS QUEUE PUT and QUEUE SERVICE routines
 *
 *  --------------------------------------------
 */

/*
 *  We only want M_DATA on the queue so that the driver can read from the
 *  queue and backenable to the Signalling Link write queue.  When can attempt
 *  to deliver to the driver in the service routine for push devices but it
 *  is better to take the pull approach.  None of the protocol messages take
 *  any time here anyway.
 */
static void
sdt_wput(queue_t *q, mblk_t *mp)
{
    int err = EOPNOTSUPP;
    DTRACE;
    if ( q->q_count && mp->b_datap->db_type < QPCTL ) {
        putq(q, mp);
        return;
    }
    switch ( mp->b_datap->db_type ) {
        case M_DATA:
            if ( (err = sdt_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = sdt_m_proto(q, mp)) ) break;
            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);
                qreply(q, mp);
            } else break;
            return;
        case M_IOCTL:
            if ( (err = sdt_m_ioctl(q, mp)) ) break;
            return;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
        case EOPNOTSUPP:
            if ( q->q_next ) {
                putnext(q, mp);
                return;
            }
    }
    freemsg(mp);    /* don't even want to complain... */
    return;
}

static void
sdt_wsrv(queue_t *q)
{
    int err = EOPNOTSUPP;
    mblk_t *mp;

    DTRACE;
    while ( (mp = getq(q)) ) {
        if ( q->q_next && mp->b_datap->db_type < QPCTL && !canputnext(q) ) {
            putbq(q, mp);
            return;
        }
        switch ( mp->b_datap->db_type ) {
            case M_DATA:
                if ( (err = sdt_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = sdt_m_proto(q, mp)) ) break;
                continue;
        }
        switch ( err&0xffff ) {
            case EAGAIN:
                if ( mp->b_datap->db_type < QPCTL ) {
                    putbq(q, mp);
                    return;
                }
                break;
            case EOPNOTSUPP:
                if ( q->q_next ) {
                    putnext(q, mp);
                    return;
                }
        }
        freemsg(mp);    /* don't even want to complain... */
    }
}

static void
sdt_rput(queue_t *q, mblk_t *mp)
{
    int err = EOPNOTSUPP;
    DTRACE;
    if ( q->q_count && mp->b_datap->db_type < QPCTL ) {
        putq(q, mp);
        return;
    }
    switch ( mp->b_datap->db_type ) {
        case M_DATA:
            if ( (err = sdl_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = sdl_m_proto(q, mp)) ) break;
            return;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
        case EOPNOTSUPP:
            putnext(q, mp);
            return;
    }
    freemsg(mp);
}

static void
sdt_rsrv(queue_t *q)
{
    mblk_t *mp;
    int err = EOPNOTSUPP;

    DTRACE;
    while ( (mp = getq(q)) ) {
        if ( mp->b_datap->db_type < QPCTL && !canputnext(q) ) {
            putbq(q, mp);
            return;
        }
        switch ( mp->b_datap->db_type ) {
            case M_DATA:
                if ( (err = sdl_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = sdl_m_proto(q, mp)) ) break;
                continue;
        }
        switch ( err&0xffff ) {
            case EAGAIN:
                if ( mp->b_datap->db_type < QPCTL ) {
                    putbq(q, mp);
                    return;
                }
                break;
            case EOPNOTSUPP:
                putnext(q, mp);
                return;
        }
        freemsg(mp);
    }
}

/*
 *  =======================================================================
 *
 *  SL Driver Implemenation
 *
 *  =======================================================================
 */

static lmi_driver_t *sdt_drivers = NULL;

static struct lmi *
sdt_drv_attach(dev_t dev)
{
    lmi_t *sdt;

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( !(sdt = lmi_drv_attach(dev, sdt_drivers, sizeof(sdt_t))) )
        MOD_DEC_USE_COUNT;

    return sdt;
}

static int
sdt_drv_open(struct lmi *sdt)
{
    int err;
    DTRACE;
    if ( !sdt ) return ENXIO;
    if ( !(err = lmi_drv_open(sdt)) ) {
        sdt->device->module = sdt;
        sdt->device->ucalls = sdt->driver->ucalls;
        sdt->dcalls = &sdt_drv_dcalls;
    }
    return (err);
}

static int
sdt_drv_close(struct lmi *sdt)
{
    int err;
    sdt_t *p = (sdt_t *)sdt;

    DTRACE;
    if ( !(err = lmi_drv_close(sdt,
                    (sdt_ulong *)NULL,       SDT_MAX_TIMERIDS,
                    (sdt_ulong *)&p->bufids, SDT_MAX_BUFCALLS)) )
        MOD_DEC_USE_COUNT;
    return(err);
}

/* these are for registration with SL as a driver */

static struct lmi_ops sdt_lmi_ops = 
{
    {
        sdt_drv_attach, /* dev.attach   */
        sdt_drv_open,   /* dev.open     */
        sdt_drv_close   /* dev.close    */
    },
    {
        lmi_info,       /* lmi.info     */
        lmi_attach,     /* lmi.attach   */
        lmi_detach,     /* lmi.detach   */
        lmi_enable,     /* lmi.enable   */
        lmi_disable,    /* lmi.disable  */
        sdt_do_ioctl    /* lmi.ioctl    */
    }
};

/*
 *  =======================================================================
 *
 *  OPEN and CLOSE
 *
 *  =======================================================================
 */

static int
sdt_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
    int err;
    sdt_t *sdt;
    DTRACE;
    if ( q->q_ptr != NULL ) return (0);

    if ( (err = lmi_open(q, devp, flag, sflag, crp, sdt_drivers, sizeof(sdt_t))) )
        return(err);
    sdt = (sdt_t *)q->q_ptr;
    sdt->ucalls = &sdt_mod_ucalls;
    if ( WR(q)->q_next ) sdt->dcalls = &sdt_mod_dcalls;
    else                 sdt->dcalls = &sdt_drv_dcalls;
    return(0);
}

static int
sdt_close(queue_t *q, int flag, cred_t *crp)
{
    int err;
    sdt_t *sdt = (sdt_t *)q->q_ptr;

    DTRACE;
    err = lmi_close(q, flag, crp,
            (sdt_ulong *)&sdt->timers, SDT_MAX_TIMERIDS,
            (sdt_ulong *)&sdt->bufids, SDT_MAX_BUFCALLS);
    return(err);
}

/*
 *  =======================================================================
 *
 *  DRIVER Registration (driver registering with us)
 *
 *  =======================================================================
 */

int
sdt_register_driver(major_t cmajor, int nminor, char *name,
        lmi_ops_t *ops, sdl_dcalls_t *dcalls)
{
    int ret;

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( (ret = lmi_register_driver(
            &sdt_drivers, cmajor, &sdt_info, nminor, name,
            ops, dcalls, &sdt_drv_ucalls)) < 0 ) {
        MOD_DEC_USE_COUNT;
        return(ret);
    }
    return(ret);
}

int
sdt_unregister_driver(major_t cmajor)
{
    int err = lmi_unregister_driver(&sdt_drivers, cmajor);
    DTRACE;
    if ( !err )
        MOD_DEC_USE_COUNT;
    return (err);
}

/*
 *  =======================================================================
 *
 *  LiS Module Initialization
 *
 *  =======================================================================
 */

static int sdt_initialized = 0;

#ifndef LIS_REGISTERED
static inline void sdt_init(void)
#else
__initfunc(void sdt_init(void))
#endif
{
    DTRACE;
    if ( sdt_initialized ) return;
    sdt_initialized = 1;
    printk(KERN_INFO SDT_BANNER);   /* console splash */
#ifndef LIS_REGISTERED
    if ( !(sdt_minfo.mi_idnum = lis_register_strmod(&sdt_info, sdt_minfo.mi_idname)) ) {
        cmn_err(CE_NOTE, "sdt: couldn't register as module\n");
    }
#endif
#if 0
    if ( sl_register_driver(SDT_CMAJOR, SDT_NMINOR, "sdt",
                &sdt_lmi_ops, &sdt_drv_ops) ) {
        cmn_err(CE_NOTE, "sdt: couldn't register as driver\n");
    }
#else
    (void)sdt_lmi_ops;
    (void)sdt_drv_ops;
#endif
};

#ifndef LIS_REGISTERED
static inline void sdt_terminate(void)
#else
__initfunc(void sdt_terminate(void))
#endif
{
    DTRACE;
    if ( !sdt_initialized ) return;
    sdt_initialized = 0;
#ifndef LIS_REGISTERED
    if (sdt_minfo.mi_idnum)
        if ( (sdt_minfo.mi_idnum = lis_unregister_strmod(&sdt_info)) ) {
            cmn_err(CE_WARN, "sdt: couldn't unregister as module!\n");
        }
#endif
#if 0
    if ( sl_unregister_driver(SDT_CMAJOR) ) {
        cmn_err(CE_WARN, "sdt: couldn't unregister as driver!\n");
    }
#endif
};

/*
 *  =======================================================================
 *
 *  Kernel Module Initialization
 *
 *  =======================================================================
 */

#ifdef MODULE
int init_module(void)
{
    DTRACE;
    (void)sdt_debug;
    sdt_init();
    return(0);
}

void cleanup_module(void)
{
    DTRACE;
    sdt_terminate();
    return;
}
#endif

