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

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

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

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

/*
 *  This is an SL (Signalling Link) kernel module which provides all of the
 *  capabilities of the SLI.  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 SDTI can push this module to form
 *      an SLI 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 <ss7/sli.h>
#include <ss7/sli_ioctl.h>

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

#define SL_DESCRIP      "SS7/SLI: (Signalling Link Interface) STREAMS DRIVER."
#define SL_COPYRIGHT    "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SL_DEVICES      "Supports OpenSS7 SL drivers."
#define SL_CONTACT      "Brian Bidulock <bidulock@openss7.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_DEVICE(SL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

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

#define DEBUG_LEVEL sl_debug

#define SL_CMAJOR 0
#define SL_NMINOR 255

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

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

static void sl_rput (queue_t *, mblk_t *);
static void sl_rsrv (queue_t *);
static int  sl_open (queue_t *, dev_t *, int, int, cred_t *);
static int  sl_close(queue_t *, int, cred_t *);

static struct qinit sl_rinit =
{
    sl_rput,        /* Read put (msg from below)    */
    sl_rsrv,        /* Read queue service           */
    sl_open,        /* Each open                    */
    sl_close,       /* Last close                   */
    NULL,           /* Admin (not used)             */
    &sl_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

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

static struct qinit sl_winit =
{
    sl_wput,        /* Write put (msg from above)   */
    sl_wsrv,        /* Write queue service          */
    NULL,           /* Each open                    */
    NULL,           /* Last close                   */
    NULL,           /* Admin (not used)             */
    &sl_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

#ifndef LIS_REGISTERED
static
#endif
struct streamtab sl_info =
{
    &sl_rinit,      /* Upper read  queue            */
    &sl_winit,      /* Upper write queue            */
    NULL,           /* Lower read  queue            */
    NULL            /* Lower write queue            */
};

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

static int
sl_iocgoptions(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sl->option, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
sl_iocsoptions(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(arg, &sl->option, _IOC_SIZE(cmd));
    return(0);
}

static int
sl_iocgconfig(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sl->config, arg, _IOC_SIZE(cmd));
    return(0);
}

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

static int
sl_iocgstatem(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sl->statem, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
sl_iocsstatsp(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}

static int
sl_iocgstatsp(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}

static int
sl_ioccstats(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    bzero(&sl->stats, _IOC_SIZE(cmd));
    return(0);
}

static int
sl_iocgstats(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sl->stats, arg, _IOC_SIZE(cmd));
    return(0);
}

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

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

static int
sl_iocgnotify(sl_t *sl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sl->notify, arg, _IOC_SIZE(cmd));
    return(0);
}

static int (*sl_ioc_lmiops [])(sl_t *, int, void *) =
{
    sl_iocgoptions, /* SL_IOCGOPTIONS */
    sl_iocsoptions, /* SL_IOCSOPTIONS */
    sl_iocgconfig,  /* SL_IOCGCONFIG  */
    sl_iocsconfig,  /* SL_IOCSCONFIG  */
    NULL,           /* SL_IOCTCONFIG  */
    NULL,           /* SL_IOCCCONFIG  */
    sl_iocgstatem,  /* SL_IOCGSTATEM  */
    NULL,           /* SL_IOCCMRESET  */
    sl_iocgstatsp,  /* SL_IOCGSTATSP  */
    sl_iocsstatsp,  /* SL_IOCSSTATSP  */
    sl_iocgstats,   /* SL_IOCGSTATS   */
    sl_ioccstats,   /* SL_IOCCSTATS   */
    sl_iocgnotify,  /* SL_IOCGNOTIFY  */
    sl_iocsnotify,  /* SL_IOCSNOTIFY  */
    sl_ioccnotify   /* SL_IOCCNOTIFY  */
};

/*
 *  =======================================================================
 *
 *  SL->LK Serivce Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static inline void sl_uprim_link(sl_t *sl, int type, int prim, mblk_t *mp)
{
    mblk_t *md = mp;
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        mp->b_wptr = mp->b_rptr;
        *((sl_long *)mp->b_rptr)++ = prim;
        md = mp->b_cont;
    }
    adjmsg(md, (sl->option.popt&SS7_POPT_XSN)?6:3); /* trim L2 header */
    putnext(sl->rq, mp);
}

static inline void sl_uprim(sl_t *sl, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(sl_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sl_long *)mp->b_wptr)++ = prim;
        putnext(sl->rq, mp);
    }
}

static inline void sl_uprim_arg(sl_t *sl, int type, int prim, int arg)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(2*sizeof(sl_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sl_long *)mp->b_wptr)++ = prim;
        *((sl_long *)mp->b_wptr)++ = arg;
        putnext(sl->rq, mp);
    }
}

static inline void sl_uprim_2arg(sl_t *sl, int type, int prim, int arg, int arg2)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(3*sizeof(sl_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sl_long *)mp->b_wptr)++ = prim;
        *((sl_long *)mp->b_wptr)++ = arg;
        *((sl_long *)mp->b_wptr)++ = arg2;
        putnext(sl->rq, mp);
    }
}

static inline void sl_uprim_3arg(sl_t *sl, int type, int prim, int arg, int arg2, int arg3)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(4*sizeof(sl_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sl_long *)mp->b_wptr)++ = prim;
        *((sl_long *)mp->b_wptr)++ = arg;
        *((sl_long *)mp->b_wptr)++ = arg2;
        *((sl_long *)mp->b_wptr)++ = arg3;
        putnext(sl->rq, mp);
    }
}

static inline void sl_l3_pdu(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl_uprim_link(sl, M_PCPROTO, SL_PDU_IND, mp);
}
static inline void sl_l3_link_congested(sl_t *sl, int cong, int disc) {
    DTRACE;
    sl_uprim_3arg(sl, M_PCPROTO, SL_LINK_CONGESTED_IND,
            jiffies, cong, disc);
}
static inline void sl_l3_link_congestion_ceased(sl_t *sl, int cong, int disc) {
    DTRACE;
    sl_uprim_3arg(sl, M_PCPROTO, SL_LINK_CONGESTION_CEASED_IND,
            jiffies, cong, disc);
}
static inline void sl_l3_retrieved_message(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl_uprim_link(sl, M_PROTO, SL_RETRIEVED_MESSAGE_IND, mp);
}
static inline void sl_l3_retrieval_complete(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_RETRIEVAL_COMPLETE_IND);
}
static inline void sl_l3_rb_cleared(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_RB_CLEARED_IND);
}
static inline void sl_l3_bsnt(sl_t *sl, int bsnt) {
    DTRACE;
    sl_uprim_arg(sl, M_PCPROTO, SL_BSNT_IND, bsnt);
}
static inline void sl_l3_in_service(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_IN_SERVICE_IND);
}
static inline void sl_l3_out_of_service(sl_t *sl, int reason) {
    DTRACE;
    sl_uprim_arg(sl, M_PCPROTO, SL_OUT_OF_SERVICE_IND, reason);
}
static inline void sl_l3_remote_processor_outage(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_REMOTE_PROCESSOR_OUTAGE_IND);
}
static inline void sl_l3_remote_processor_recovered(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_REMOTE_PROCESSOR_RECOVERED_IND);
}
static inline void sl_l3_rtb_cleared(sl_t *sl) {
    DTRACE;
    sl_uprim(sl, M_PCPROTO, SL_RTB_CLEARED_IND);
}

static sl_ucalls_t sl_mod_ucalls =
{
    sl_l3_pdu,                         /* sl_pdu            */
    sl_l3_link_congested,              /* sl_congested      */
    sl_l3_link_congestion_ceased,      /* sl_no_cong        */
    sl_l3_retrieved_message,           /* sl_rtrv_msg       */
    sl_l3_retrieval_complete,          /* sl_rtrv_comp      */
    sl_l3_rb_cleared,                  /* sl_rb_cleared     */
    sl_l3_bsnt,                        /* sl_bsnt           */
    sl_l3_in_service,                  /* sl_in_service     */
    sl_l3_out_of_service,              /* sl_out_of_service */
    sl_l3_remote_processor_outage,     /* sl_rpoutage       */
    sl_l3_remote_processor_recovered,  /* sl_rprecovered    */
    sl_l3_rtb_cleared                  /* sl_rtb_cleared    */
};

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

static inline void sl_dprim(sl_t *sl, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(sl_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((sl_long *)mp->b_wptr)++ = prim;
        putnext(sl->wq, mp);
    }
}

static void sdt_daedt_xmit_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        *((sl_long *)mp->b_rptr) = SDT_DAEDT_TRANSMISSION_REQ;
        mp->b_datap->db_type = M_PROTO;
    }
    putnext(sl->wq, mp);
}
static void sdt_daedt_start_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_DAEDT_START_REQ);
}
static void sdt_daedr_start_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_DAEDR_START_REQ);
}
static void sdt_aerm_start_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_AERM_START_REQ);
}
static void sdt_aerm_stop_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_AERM_STOP_REQ);
}
static void sdt_aerm_set_ti_to_tin_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_AERM_SET_TI_TO_TIN_REQ);
}
static void sdt_aerm_set_ti_to_tie_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_AERM_SET_TI_TO_TIE_REQ);
}
static void sdt_suerm_start_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_SUERM_START_REQ);
}
static void sdt_suerm_stop_req(sl_t *sl) {
    DTRACE;
    sl_dprim(sl, M_PCPROTO, SDT_SUERM_STOP_REQ);
}

static sl_mcalls_t sl_mod_dcalls =
{
    sdt_daedt_xmit_req,              /* daedt_xmit   */
    sdt_daedt_start_req,             /* daedt_start  */
    sdt_daedr_start_req,             /* daedr_start  */
    sdt_aerm_start_req,              /* aerm_start   */
    sdt_aerm_stop_req,               /* aerm_stop    */
    sdt_aerm_set_ti_to_tin_req,      /* aerm_set_tin */
    sdt_aerm_set_ti_to_tie_req,      /* aerm_set_tie */
    sdt_suerm_start_req,             /* suerm_start  */
    sdt_suerm_stop_req               /* suerm_stop   */
};

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

static void sdt_daedt_xmit(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl->driver->dcalls->daedt_xmit(sl->device, mp);
}
static void sdt_daedt_start(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->daedt_start(sl->device);
}
static void sdt_daedr_start(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->daedr_start(sl->device);
}
static void sdt_aerm_start(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->aerm_start(sl->device);
}
static void sdt_aerm_stop(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->aerm_stop(sl->device);
}
static void sdt_aerm_set_ti_to_tin(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->aerm_set_tin(sl->device);
}
static void sdt_aerm_set_ti_to_tie(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->aerm_set_tie(sl->device);
}
static void sdt_suerm_start(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->suerm_start(sl->device);
}
static void sdt_suerm_stop(sl_t *sl) {
    DTRACE;
    sl->driver->dcalls->suerm_stop(sl->device);
}

static sl_mcalls_t sl_drv_dcalls =
{
    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_start,                 /* suerm_start  */
    sdt_suerm_stop                   /* uerm_stop    */
};

/*
 *  =======================================================================
 *
 *  PROTOCOL STATE MACHINES
 *
 *  =======================================================================
 */

#include "sl_sm.h"

/*
 *  =======================================================================
 *
 *  SL<-LK Serivce Calls (Driver)
 *
 *  =======================================================================
 */

static inline void sl_pdu(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl_lsc_pdu(sl, mp);
}
static inline void sl_emergency(sl_t *sl) {
    DTRACE;
    sl_lsc_emergency(sl);
}
static inline void sl_emergency_ceases(sl_t *sl) {
    DTRACE;
    sl_lsc_emergency_ceases(sl);
}
static inline void sl_start(sl_t *sl) {
    DTRACE;
    sl_lsc_start(sl);
}
static inline void sl_stop(sl_t *sl) {
    DTRACE;
    sl_lsc_stop(sl);
}
static inline void sl_retrieve_bsnt(sl_t *sl) {
    DTRACE;
    sl_lsc_retrieve_bsnt(sl);
}
static inline void sl_retrieval_request_and_fsnc(sl_t *sl, int fsnc) {
    DTRACE;
    sl_lsc_retrieval_request_and_fsnc(sl, fsnc);
}
static inline void sl_resume(sl_t *sl) {
    DTRACE;
    sl_lsc_resume(sl);
}
static inline void sl_clear_buffers(sl_t *sl) {
    DTRACE;
    sl_lsc_clear_buffers(sl);
}
static inline void sl_local_processor_outage(sl_t *sl) {
    DTRACE;
    sl_lsc_local_processor_outage(sl);
}
static inline void sl_congestion_discard(sl_t *sl) {
    DTRACE;
    sl_lsc_congestion_discard(sl);
}
static inline void sl_congestion_accept(sl_t *sl) {
    DTRACE;
    sl_lsc_congestion_accept(sl);
}
static inline void sl_no_congestion(sl_t *sl) {
    DTRACE;
    sl_lsc_no_congestion(sl);
}
static inline void sl_power_on(sl_t *sl) {
    DTRACE;
    sl_lsc_power_on(sl);
}

#if 0
static sl_dcalls_t sl_drv_ops =
{
    sl_pdu,                         /* sl_pdu           */
    sl_emergency,                   /* sl_emerg         */
    sl_emergency_ceases,            /* sl_normal        */
    sl_start,                       /* sl_start         */
    sl_stop,                        /* sl_stop          */
    sl_retrieve_bsnt,               /* sl_bsnt          */
    sl_retrieval_request_and_fsnc,  /* sl_fsnc          */
    sl_resume,                      /* sl_resume        */
    sl_clear_buffers,               /* sl_clear_bufs    */
    sl_local_processor_outage,      /* sl_lpo           */
    sl_congestion_discard,          /* sl_cg_accept     */
    sl_congestion_accept,           /* sl_cg_discard    */
    sl_no_congestion,               /* sl_no_cong       */
    sl_power_on                     /* sl_power_on      */
};
#endif

/*
 *  =======================================================================
 *
 *  SL<-LK Serivce Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static void sl_pdu_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl_pdu(sl, mp);
}
static void sl_emergency_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_emergency(sl);
}
static void sl_emergency_ceases_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_emergency_ceases(sl);
}
static void sl_start_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_start(sl);
}
static void sl_stop_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_stop(sl);
}
static void sl_retrieve_bsnt_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_retrieve_bsnt(sl);
}
static void sl_retrieval_request_and_fsnc_req(sl_t *sl, mblk_t *mp) {
    int fsnc = ((sl_ulong *)mp->b_rptr)[1];
    DTRACE;
    freemsg(mp);
    sl_retrieval_request_and_fsnc(sl, fsnc);
}
static void sl_resume_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_resume(sl);
}
static void sl_clear_buffers_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_clear_buffers(sl);
}
static void sl_local_processor_outage_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_local_processor_outage(sl);
}
static void sl_congestion_discard_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_congestion_discard(sl);
}
static void sl_congestion_accept_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_congestion_accept(sl);
}
static void sl_no_congestion_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_no_congestion(sl);
}
static void sl_power_on_req(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_power_on(sl);
}

static void (*sl_slsl_ops[])(sl_t *, mblk_t *) =
{
    sl_pdu_req,                        /* SL_PDU_REQ                        */
    sl_emergency_req,                  /* SL_EMERGENCY_REQ                  */
    sl_emergency_ceases_req,           /* SL_EMERGENCY_CEASES_REQ           */
    sl_start_req,                      /* SL_START_REQ                      */
    sl_stop_req,                       /* SL_STOP_REQ                       */
    sl_retrieve_bsnt_req,              /* SL_RETRIEVE_BSNT_REQ              */
    sl_retrieval_request_and_fsnc_req, /* SL_RETRIEVAL_REQUEST_AND_FSNC_REQ */
    sl_resume_req,                     /* SL_RESUME_REQ                     */
    sl_clear_buffers_req,              /* SL_CLEAR_BUFFERS_REQ              */
    sl_local_processor_outage_req,     /* SL_LOCAL_PROCESSOR_OUTAGE_REQ     */
    sl_congestion_discard_req,         /* SL_CONGESTION_DISCARD_REQ         */
    sl_congestion_accept_req,          /* SL_CONGESTION_ACCEPT_REQ          */
    sl_no_congestion_req,              /* SL_NO_CONGESTION_REQ              */
    sl_power_on_req                    /* SL_POWER_ON_REQ                   */
};

/*
 *  =======================================================================
 *
 *  SL<-SDT Serivce Calls (Driver)  (WARNING: may be called at IRQ)
 *
 *  =======================================================================
 */

static void sdt_rc_signal_unit(sdt_t *sdt, mblk_t *mp) {
    DTRACE;
    sl_rc_signal_unit(sdt->module, mp);
}
static void sdt_rc_congestion_accept(sdt_t *sdt) {
    DTRACE;
    sl_rc_congestion_accept(sdt->module);
}
static void sdt_rc_congestion_discard(sdt_t *sdt) {
    DTRACE;
    sl_rc_congestion_discard(sdt->module);
}
static void sdt_rc_no_congestion(sdt_t *sdt) {
    DTRACE;
    sl_rc_no_congestion(sdt->module);
}
static void sdt_iac_correct_su(sdt_t *sdt) {
    DTRACE;
    sl_iac_correct_su(sdt->module);
}
static void sdt_iac_abort_proving(sdt_t *sdt) {
    DTRACE;
    sl_iac_abort_proving(sdt->module);
}
static void sdt_lsc_link_failure(sdt_t *sdt) {
    DTRACE;
    sl_lsc_link_failure(sdt->module);
}

static sdt_ucalls_t sl_drv_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      */
};

/*
 *  =======================================================================
 *
 *  SL<-SDT Serivce Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static void sdt_rc_signal_unit_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    sl_rc_signal_unit(sl, mp);
}
static void sdt_rc_congestion_accept_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_rc_congestion_accept(sl);
}
static void sdt_rc_congestion_discard_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_rc_congestion_discard(sl);
}
static void sdt_rc_no_congestion_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_rc_no_congestion(sl);
}
static void sdt_iac_correct_su_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_iac_correct_su(sl);
}
static void sdt_iac_abort_proving_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_iac_abort_proving(sl);
}
static void sdt_lsc_link_failure_ind(sl_t *sl, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    sl_lsc_link_failure(sl);
}

static void (*sl_sdt_ops[])(sl_t *, mblk_t *) =
{
    sdt_rc_signal_unit_ind,         /* SDT_RC_SIGNAL_UNIT_IND        */
    sdt_rc_congestion_accept_ind,   /* SDT_RC_CONGESTION_ACCEPT_IND  */
    sdt_rc_congestion_discard_ind,  /* SDT_RC_CONGESTION_DISCARD_IND */
    sdt_rc_no_congestion_ind,       /* SDT_RC_NO_CONGESTION_IND      */
    sdt_iac_correct_su_ind,         /* SDT_IAC_CORRECT_SU_IND        */
    sdt_iac_abort_proving_ind,      /* SDT_IAC_ABORT_PROVING_IND     */
    sdt_lsc_link_failure_ind        /* SDT_LSC_LINK_FAILURE_IND      */
};

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

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

    DTRACE;
    if ( _IOC_TYPE(cmd) == SL_IOC_MAGIC )
        if ( SL_IOC_FIRST <= nr && nr <= SL_IOC_LAST )
            if ( sl_ioc_lmiops[nr] )
                return sl_ioc_lmiops[nr]((sl_t *)sl, cmd, arg);

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

    return -ENXIO;
}

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

static inline int
sl_m_ioctl(queue_t *q, mblk_t *mp)
{
    sl_t *sl = (sl_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, count = iocp->ioc_count;
    int ret = -EINVAL;

    DTRACE;
    if ( count >= _IOC_SIZE(cmd) ) {
        ret = sl_do_ioctl((lmi_t *)sl, 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
sl_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    sl_t *sl = (sl_t *)q->q_ptr;
    int prim = *((sl_long *)mp->b_rptr);

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SL_DSTR_FIRST <= prim && prim <= SL_DSTR_LAST ) {
        (*sl_slsl_ops[prim - SL_DSTR_FIRST])(sl, 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 *)sl, mp);
    }
    return err;
}

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

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SDT_USTR_FIRST <= prim && prim <= SDT_USTR_LAST ) {
        freemsg(mp);
        (*sl_sdt_ops[SL_USTR_FIRST - prim])(sl, mp);
        return(0);
    }
    if ( q->q_next ) {
        putnext(q, mp);
        return (0);
    }
    return err;
}

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

static inline int
sl_m_data(queue_t *q, mblk_t *mp)
{
    sl_t *sl = (sl_t *)q->q_ptr;
    DTRACE;
    sl_lsc_pdu(sl, mp);
    return(0);
}

static inline int
sdt_m_data(queue_t *q, mblk_t *mp)
{
    sl_t *sl = (sl_t *)q->q_ptr;
    DTRACE;
    sdt_rc_signal_unit_ind(sl, mp);
    return(0);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  STREAMS PUTQ and SRVQ routines
 *
 *  -------------------------------------------------------------------------
 */

void sl_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 = sl_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = sl_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 = sl_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;
}

void sl_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 = sl_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = sl_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);
    }
}

static void
sl_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 = 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;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
        case EOPNOTSUPP:
            putnext(q, mp);
            return;
    }
    freemsg(mp);
}

static void
sl_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 = 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:
                putnext(q, mp);
                return;
        }
        freemsg(mp);
    }
}

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

static lmi_driver_t *sl_drivers = NULL;

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

    if ( (err = lmi_open(q, devp, flag, sflag, crp, sl_drivers, sizeof(sl_t))) )
        return(err);
    sl = (sl_t *)q->q_ptr;
    sl->ucalls = &sl_mod_ucalls;
    if ( WR(q)->q_next ) sl->dcalls = &sl_mod_dcalls;
    else                 sl->dcalls = &sl_drv_dcalls;

    bufq_init(&sl->tb);
    bufq_init(&sl->rtb);
    return(0);
}

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

    DTRACE;
    err = lmi_close(q, flag, crp,
            (sl_ulong *)&sl->timers, SL_MAX_TIMERIDS,
            (sl_ulong *)&sl->bufids, SL_MAX_BUFCALLS);
    return(err);
}

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

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

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( (ret = lmi_register_driver(
            &sl_drivers, cmajor, &sl_info, nminor, name,
            ops, dcalls, &sl_drv_ucalls)) < 0 ) {
        MOD_DEC_USE_COUNT;
        return(ret);
    }
    return(ret);
}

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

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

static int sl_initialized = 0;

#ifndef LIS_REGISTERED
static inline void sl_init(void)
#else
__initfunc(void sl_init(void))
#endif
{
    DTRACE;
    if ( sl_initialized ) return;
    sl_initialized = 1;
    printk(KERN_INFO SL_BANNER);   /* console splash */
#ifndef LIS_REGISTERED
    if ( !(sl_minfo.mi_idnum = lis_register_strmod(&sl_info, sl_minfo.mi_idname)) ) {
        cmn_err(CE_NOTE, "sl: couldn't register module\n");
    }
#endif
#if 0
    if ( sls_register_driver(SL_CMAJOR, SL_NMINOR, "sl",
                &sl_lmi_ops, &sl_drv_ops) ) {
        cmn_err(CE_NOTE: "sl: couldn't register as driver\n");
    }
#endif
};

#ifndef LIS_REGISTERED
static inline void sl_terminate(void)
#else
__initfunc(void sl_terminate(void))
#endif
{
    if ( !sl_initialized ) return;
    sl_initialized = 0;
#ifndef LIS_REGSITERED
    if (sl_minfo.mi_idnum)
        if ( (sl_minfo.mi_idnum = lis_unregister_strmod(&sl_info)) ) {
            cmn_err(CE_WARN, "sl: couldn't unregister module!\n");
        }
#endif
};

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

#ifdef MODULE
int init_module(void)
{
    DTRACE;
    (void)sl_debug;
    sl_init();
    return(0);
}

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

