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

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

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

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

/*
 *  This is an SLS (Signalling Link Set) loadable module which provides all of
 *  the capabilities of the SLSI.  It can be used by drivers in two fashions:
 *
 *  1)  Drivers can provide a set of missing functions by registering with the
 *      module using the ls_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)  Driver which implement the OpenSS7 SLI can be linked under this
 *      multiplexor to form an SLSI driver implementation.
 *
 *  Both fashions permit SS7 Level 2 and 3 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 <ss7/slsi.h>
#include <ss7/slsi_ioctl.h>

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

#define LS_DESCRIP     "SS7/SLS: (Signalling Link Set) STREAMS DRIVER."
#define LS_COPYRIGHT   "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define LS_DEVICES     "Supports OpenSS7 SLS drivers."
#define LS_CONTACT     "Brian Bidulock <bidulock@openss7.org>"
#define LS_BANNER      LS_DESCRIP     "\n" \
                       LS_COPYRIGHT   "\n" \
                       LS_DEVICES     "\n" \
                       LS_CONTACT     "\n"

#ifdef MODULE
MODULE_AUTHOR(LS_CONTACT);
MODULE_DESCRIPTION(LS_DESCRIP);
MODULE_SUPPORTED_DEVICE(LS_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef LS_DEBUG
static int ls_debug = LS_DEBUG;
#else
static int ls_debug = 2;
#endif

#define DEBUG_LEVEL ls_debug

#ifndef LS_CMAJOR
#define LS_CMAJOR 249
#endif
#define LS_NMINOR 255

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

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

static void ls_rput (queue_t *, mblk_t *);
static void ls_rsrv (queue_t *);
static int  ls_open (queue_t *, dev_t *, int, int, cred_t *);
static int  ls_close(queue_t *, int, cred_t *);

static struct qinit ls_rinit =
{
    ls_rput,        /* Read put (msg from below)    */
    ls_rsrv,        /* Read queue service           */
    ls_open,        /* Each open                    */
    ls_close,       /* Last close                   */
    NULL,           /* Admin (not used)             */
    &ls_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

static void ls_wput (queue_t *, mblk_t *);
static void ls_wsrv (queue_t *);

static struct qinit ls_winit =
{
    ls_wput,        /* Write put (msg from above)   */
    ls_wsrv,        /* Write queue service          */
    NULL,           /* Each open                    */
    NULL,           /* Last close                   */
    NULL,           /* Admin (not used)             */
    &ls_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

static struct module_info lk_minfo =
{
    0,              /* Module ID number             */
    "ls-lower",     /* Module name                  */
    5,              /* Min packet size accepted     */
    272,            /* Max packet size accepted     */
    8*272,          /* Hi water mark                */
    1*272           /* Lo water mark                */
};

static void lk_rput(queue_t *, mblk_t *);
static void lk_rsrv(queue_t *);

static struct qinit lk_rinit =
{
    lk_rput,        /* Write put (msg from above)   */
    lk_rsrv, /*?*/  /* Write queue service          */
    NULL,           /* Each open                    */
    NULL,           /* Last close                   */
    NULL,           /* Admin (not used)             */
    &lk_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

static void lk_wput(queue_t *, mblk_t *);
static void lk_wsrv(queue_t *);

static struct qinit lk_winit =
{
    lk_wput,        /* Write put (msg from above)   */
    lk_wsrv,        /* Write queue service          */
    NULL,           /* Each open                    */
    NULL,           /* Last close                   */
    NULL,           /* Admin (not used)             */
    &lk_minfo,      /* Information                  */
    NULL            /* Statistics                   */
};

#ifdef MODULE
static
#endif
struct streamtab ls_info =
{
    &ls_rinit,      /* Upper read  queue            */
    &ls_winit,      /* Upper write queue            */
    &lk_rinit,      /* Lower read  queue            */
    &lk_winit       /* Lower write queue            */
};

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

static int ls_iocgoptions(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(&ls->option, arg, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocsoptions(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(arg, &ls->option, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocgconfig(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(&ls->config, arg, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocsconfig(ls_t *ls, int cmd, void *arg)
{
    signed long int *src, *dst, *end;
    DTRACE;
    dst = (signed long int *)&ls->config;
    end = (signed long int *)((&ls->config)+1);
    src = arg;
    while ( dst < end ) {
        if ( *src != -1 ) *dst = *src;
        dst++; src++;
    }
    return(0);
}
static int ls_iocgstatem(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(&ls->statem, arg, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocsstatsp(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}
static int ls_iocgstatsp(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}
static int ls_ioccstats(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    bzero(&ls->stats, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocgstats(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(&ls->stats, arg, _IOC_SIZE(cmd));
    return(0);
}
static int ls_iocsnotify(ls_t *ls, int cmd, void *arg)
{
    caddr_t dst, src, end;
    DTRACE;
    src = arg;
    dst = (caddr_t)&ls->notify;
    end = (caddr_t)((&ls->notify)+1);
    while ( dst < end ) *dst++ |= *src++;
    return(0);
}
static int ls_ioccnotify(ls_t *ls, int cmd, void *arg)
{
    caddr_t dst, src, end;
    DTRACE;
    src = arg;
    dst = (caddr_t)&ls->notify;
    end = (caddr_t)((&ls->notify)+1);
    while ( dst < end ) *dst++ &= ~*src++;
    return(0);
}
static int ls_iocgnotify(ls_t *ls, int cmd, void *arg)
{
    DTRACE;
    bcopy(&ls->notify, arg, _IOC_SIZE(cmd));
    return(0);
}

static int (*ls_ioc_lmiops [])(ls_t *, int, void *) =
{
    ls_iocgoptions, /* LS_IOCGOPTIONS */
    ls_iocsoptions, /* LS_IOCSOPTIONS */
    ls_iocgconfig,  /* LS_IOCGCONFIG  */
    ls_iocsconfig,  /* LS_IOCSCONFIG  */
    NULL,           /* LS_IOCTCONFIG  */
    NULL,           /* LS_IOCCCONFIG  */
    ls_iocgstatem,  /* LS_IOCGSTATEM  */
    NULL,           /* LS_IOCCMRESET  */
    ls_iocgstatsp,  /* LS_IOCGSTATSP  */
    ls_iocsstatsp,  /* LS_IOCSSTATSP  */
    ls_iocgstats,   /* LS_IOCGSTATS   */
    ls_ioccstats,   /* LS_IOCCSTATS   */
    ls_iocgnotify,  /* LS_IOCGNOTIFY  */
    ls_iocsnotify,  /* LS_IOCSNOTIFY  */
    ls_ioccnotify   /* LS_IOCCNOTIFY  */
};

/*
 *  =======================================================================
 *
 *  LS->MTP Service Primitives (M_CTL, M_PROTO, M_PCPROTO
 *
 *  =======================================================================
 */

static inline void ls_uprim_arg_link(ls_t *ls, int type, int prim, int arg, mblk_t *mp)
{
    mblk_t *md = mp;
    if ( mp->b_datap->db_type != M_DATA ) {
        mp->b_wptr = mp->b_rptr;
        *((ls_long *)mp->b_rptr)++ = prim;
        *((ls_long *)mp->b_rptr)++ = arg;
        md = mp->b_cont;
    }
    putq(ls->rq, mp);
}
static inline void ls_uprim(ls_t *ls, int type, int prim)
{
    mblk_t *mp;
    if ( (mp = allocb(1*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_long *)mp->b_wptr)++ = prim;
        putq(ls->rq, mp);
    }
}
static inline void ls_uprim_arg(ls_t *ls, int type, int prim, int arg)
{
    mblk_t *mp;
    if ( (mp = allocb(2*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_long *)mp->b_wptr)++ = prim;
        *((ls_long *)mp->b_wptr)++ = arg;
        putq(ls->rq, mp);
    }
}
static inline void ls_uprim_2arg(ls_t *ls, int type, int prim, int arg, int arg2)
{
    mblk_t *mp;
    if ( (mp = allocb(3*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_long *)mp->b_wptr)++ = prim;
        *((ls_long *)mp->b_wptr)++ = arg;
        *((ls_long *)mp->b_wptr)++ = arg2;
        putq(ls->rq, mp);
    }
}
static inline void ls_uprim_3arg(ls_t *ls, int type, int prim, int arg, int arg2, int arg3)
{
    mblk_t *mp;
    if ( (mp = allocb(4*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_long *)mp->b_wptr)++ = prim;
        *((ls_long *)mp->b_wptr)++ = arg;
        *((ls_long *)mp->b_wptr)++ = arg2;
        *((ls_long *)mp->b_wptr)++ = arg3;
        putq(ls->rq, mp);
    }
}

static void ls_alternative_routing_data_request(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_ALTERNATIVE_ROUTING_DATA_REQUEST_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_cancel_link_inhibited(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_CANCEL_LINK_INHIBITED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_changeover_executed(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_CHANGEOVER_EXECUTED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_changeover_not_required(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_CHANGEOVER_NOT_REQUIRED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_inhibit_denied(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_INHIBIT_DENIED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_link_in_service_at_level_2(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_LINK_IN_SERVICE_AT_LEVEL_2_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_link_inhibited(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_LINK_INHIBITED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_link_uninhibited(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_LINK_UNINHIBITED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_local_inhibit_request(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_LOCAL_INHIBIT_REQUEST_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_message_for_distribution(ls_t *ls, int slc, mblk_t *mp) {
    mp->b_datap->db_type = M_PROTO;
    *((ls_long *)mp->b_rptr) = LS_MESSAGE_FOR_DISTRIBUTION_IND;
    putq(ls->rq, mp);
}
static void ls_message_for_routing(ls_t *ls, int slc, mblk_t *mp) {
    mp->b_datap->db_type = M_PROTO;
    *((ls_long *)mp->b_rptr) = LS_MESSAGE_FOR_ROUTING_IND;
    putq(ls->rq, mp);
}
static void ls_procedure_terminated(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_PROCEDURE_TERMINATED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_remote_inhibit_request(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_REMOTE_INHIBIT_REQUEST_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_signalling_link_available(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_SIGNALLING_LINK_AVAILABLE_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_signalling_link_unavailable(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_SIGNALLING_LINK_UNAVAILABLE_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_slt_failed(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_SLT_FAILED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_sslt_failed(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_SSLT_FAILED_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_sslt_successful(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_SSLT_SUCCESSFUL_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_uninhibit_not_possible(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_UNINHIBIT_NOT_POSSIBLE_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_uninhibit_request(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_UNINHIBIT_REQUEST_IND, ls->config.lpc, ls->config.apc, slc);
}
static void ls_uninhibiting_not_possible(ls_t *ls, int slc) {
    ls_uprim_3arg(ls, M_PROTO, LS_UNINHIBITING_NOT_POSSIBLE_IND, ls->config.lpc, ls->config.apc, slc);
}

static ls_ucalls_t ls_mod_ucalls =
{
    ls_alternative_routing_data_request, /* ls_alternative_routing_data_request  */
    ls_cancel_link_inhibited,            /* ls_cancel_link_inhibited             */
    ls_changeover_executed,              /* ls_changeover_executed               */
    ls_changeover_not_required,          /* ls_changeover_not_required           */
    ls_inhibit_denied,                   /* ls_inhibit_denied                    */
    ls_link_in_service_at_level_2,       /* ls_link_in_service_at_level_2        */
    ls_link_inhibited,                   /* ls_link_inhibited                    */
    ls_link_uninhibited,                 /* ls_link_uninhibited                  */
    ls_local_inhibit_request,            /* ls_local_inhibit_request             */
    ls_message_for_distribution,         /* ls_message_for_distribution          */
    ls_message_for_routing,              /* ls_message_for_routing               */
    ls_procedure_terminated,             /* ls_procedure_terminated              */
    ls_remote_inhibit_request,           /* ls_remote_inhibit_request            */
    ls_signalling_link_available,        /* ls_signalling_link_available         */
    ls_signalling_link_unavailable,      /* ls_signalling_link_unavailable       */
    ls_slt_failed,                       /* ls_slt_failed                        */
    ls_sslt_failed,                      /* ls_sslt_failed                       */
    ls_sslt_successful,                  /* ls_sslt_successful                   */
    ls_uninhibit_not_possible,           /* ls_uninhibit_not_possible            */
    ls_uninhibit_request,                /* ls_uninhibit_request                 */
    ls_uninhibiting_not_possible         /* ls_uninhibiting_not_possible         */
};

static inline void mtp_hmdt_hmdc_message_for_distribution(lk_t *lk, mblk_t *mp) {
    ls_message_for_distribution(lk->module, lk->config.slc, mp);
}
static inline void mtp_hmrt_hmdc_message_for_routing(lk_t *lk, mblk_t *mp) {
    ls_message_for_routing(lk->module, lk->config.slc, mp);
}
static inline void lk_mgmt_sltc_slt_failed(lk_t *lk) {
    ls_slt_failed(lk->module, lk->config.slc);
}
static inline void lk_mgmt_sltc_sslt_failed(lk_t *lk) {
    ls_sslt_failed(lk->module, lk->config.slc);
}
static inline void lk_mgmt_sltc_sslt_successful(lk_t *lk) {
    ls_sslt_successful(lk->module, lk->config.slc);
}
static inline void lk_mgmt_tlac_inhibit_denied(lk_t *lk) {
    ls_inhibit_denied(lk->module, lk->config.slc);
}
static inline void lk_mgmt_tlac_link_inhibited(lk_t *lk) {
    ls_link_inhibited(lk->module, lk->config.slc);
}
static inline void lk_mgmt_tlac_link_uninhibited(lk_t *lk) {
    ls_link_uninhibited(lk->module, lk->config.slc);
}
static inline void lk_mgmt_tlac_uninhibit_not_possible(lk_t *lk) {
    ls_uninhibit_not_possible(lk->module, lk->config.slc);
}
static inline void lk_mgmt_tlac_uninhibiting_not_possible(lk_t *lk) {
    ls_uninhibiting_not_possible(lk->module, lk->config.slc);
}
static inline void lk_tsrc_lsda_alternative_routing_data_request(lk_t *lk) {
    ls_alternative_routing_data_request(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tcbc_alternative_routing_data_request(lk_t *lk) {
    ls_alternative_routing_data_request(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tcoc_alternative_routing_data_request(lk_t *lk) {
    ls_alternative_routing_data_request(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_cancel_link_inhibited(lk_t *lk) {
    ls_cancel_link_inhibited(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tcoc_changeover_executed(lk_t *lk) {
    ls_changeover_executed(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tcoc_changeover_not_required(lk_t *lk) {
    ls_changeover_not_required(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_link_inhibited(lk_t *lk) {
    ls_link_inhibited(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_link_in_service_at_level_2(lk_t *lk) {
    ls_link_in_service_at_level_2(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_local_inhibit_request(lk_t *lk) {
    ls_local_inhibit_request(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tcbc_procedure_terminated(lk_t *lk) {
    ls_procedure_terminated(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_remote_inhibit_request(lk_t *lk) {
    ls_remote_inhibit_request(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_signalling_link_available(lk_t *lk) {
    ls_signalling_link_available(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_signalling_link_unavailable(lk_t *lk) {
    ls_signalling_link_unavailable(lk->module, lk->config.slc);
}
static inline void lk_tsrc_tlac_uninhibit_request(lk_t *lk) {
    ls_uninhibit_request(lk->module, lk->config.slc);
}
static inline void lk_tprc_tlac_signalling_link_unavailable(lk_t *lk) {
    ls_signalling_link_unavailable(lk->module, lk->config.slc);
}

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

static inline void lk_dprim_link(lk_t *lk, int type, int prim, mblk_t *mp)
{
    if ( mp->b_datap->db_type != M_DATA ) {
        mp->b_datap->db_type = type;
        mp->b_wptr = mp->b_rptr;
        *((ls_long *)mp->b_wptr)++ = prim;
    }
    putnext(lk->wq, mp);
}
static inline void lk_dprim(lk_t *lk, int type, int prim)
{
    mblk_t *mp;
    if ( (mp = allocb(1*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_ulong *)mp->b_wptr)++ = prim;
        putnext(lk->wq, mp);
    }
}
static inline void lk_dprim_arg(lk_t *lk, int type, int prim, int arg)
{
    mblk_t *mp;
    if ( (mp = allocb(1*sizeof(ls_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((ls_ulong *)mp->b_wptr)++ = prim;
        *((ls_ulong *)mp->b_wptr)++ = arg;
        putnext(lk->wq, mp);
    }
}

static void sl_pdu_req(lk_t *lk, mblk_t *mp)
{
    lk_dprim_link(lk, M_PROTO, SL_PDU_REQ, mp);
}
static void sl_emergency_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_EMERGENCY_REQ);
}
static void sl_emergency_ceases_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_EMERGENCY_CEASES_REQ);
}
static void sl_start_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_START_REQ);
}
static void sl_stop_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_STOP_REQ);
}
static void sl_retrieve_bsnt_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_RETRIEVE_BSNT_REQ);
}
static void sl_retrieval_request_and_fsnc_req(lk_t *lk, int fsnc)
{
    lk_dprim_arg(lk, M_PCPROTO, SL_RETRIEVAL_REQUEST_AND_FSNC_REQ, fsnc);
}
static void sl_resume_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_RESUME_REQ);
}
static void sl_clear_buffers_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_CLEAR_BUFFERS_REQ);
}
static void sl_local_processor_outage_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_LOCAL_PROCESSOR_OUTAGE_REQ);
}
static void sl_congestion_accept_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_CONGESTION_ACCEPT_REQ);
}
static void sl_congestion_discard_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_CONGESTION_DISCARD_REQ);
}
static void sl_no_congestion_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_NO_CONGESTION_REQ);
}
static void sl_power_on_req(lk_t *lk)
{
    lk_dprim(lk, M_PCPROTO, SL_POWER_ON_REQ);
}

static lk_mcalls_t lk_mod_dcalls =
{
    sl_pdu_req,                         /* sl_pdu        */
    sl_emergency_req,                   /* sl_emerg      */
    sl_emergency_ceases_req,            /* sl_normal     */
    sl_start_req,                       /* sl_start      */
    sl_stop_req,                        /* sl_stop       */
    sl_retrieve_bsnt_req,               /* sl_bsnt       */
    sl_retrieval_request_and_fsnc_req,  /* sl_fsnc       */
    sl_resume_req,                      /* sl_resume     */
    sl_clear_buffers_req,               /* sl_clear_bufs */
    sl_local_processor_outage_req,      /* sl_lpo        */
    sl_congestion_accept_req,           /* sl_cg_accept  */
    sl_congestion_discard_req,          /* sl_cg_discard */
    sl_no_congestion_req,               /* sl_no_cong    */
    sl_power_on_req                     /* sl_power_on   */
};

/*
 *  =======================================================================
 *
 *  LK->SL Service Calls (Link Driver)
 *
 *  =======================================================================
 */

#if 0
static void sl_pdu(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_pdu(lk->device, mp);
}
static void sl_emergency(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_emerg(lk->device);
}
static void sl_emergency_ceases(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_normal(lk->device);
}
static void sl_start(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_start(lk->device);
}
static void sl_stop(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_stop(lk->device);
}
static void sl_retrieve_bsnt(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_bsnt(lk->device);
}
static void sl_retrieval_request_and_fsnc(lk_t *lk, int fsnc) {
    DTRACE;
    lk->driver->dcalls->sl_fsnc(lk->device, fsnc);
}
static void sl_resume(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_resume(lk->device);
}
static void sl_clear_buffers(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_clear_bufs(lk->device);
}
static void sl_local_processor_outage(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_lpo(lk->device);
}
static void sl_congestion_accept(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_cg_accept(lk->device);
}
static void sl_congestion_discard(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_cg_discard(lk->device);
}
static void sl_no_congestion(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_no_cong(lk->device);
}
static void sl_power_on(lk_t *lk) {
    DTRACE;
    lk->driver->dcalls->sl_power_on(lk->device);
}

static lk_mcalls_t lk_drv_dcalls =
{
    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_accept,           /* sl_cg_accept  */
    sl_congestion_discard,          /* sl_cg_discard */
    sl_no_congestion,               /* sl_no_cong    */
    sl_power_on                     /* sl_power_on   */
};
#endif

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

#define lk_timer_stop(tim) \
{ \
    if ( lk->timers.tim) \
        untimeout(lk->timers.tim); \
}

#define lk_timer_start(tim) \
{ \
    lk_timer_stop(tim); \
    lk->timers.tim= \
      timeout((timo_fcn_t *)lk_ ## tim ## _timeout,(caddr_t)lk,lk->config.tim); \
}

#define ls_timer_stop(tim) \
{ \
    if ( ls->timers.tim) \
        untimeout(ls->timers.tim); \
}

#define ls_timer_start(tim) \
{ \
    ls_timer_stop(tim); \
    ls->timers.tim= \
      timeout((timo_fcn_t *)ls_ ## tim ## _timeout,(caddr_t)ls,ls->config.tim); \
}

#include "sls_codec.c"
#include "sls_hmdc.h"   /* Signalling Message Handling (SMH) Message Discrimination (HMDC) */
#include "sls_hmdt.h"   /* Signalling Message Handling (SMH) Message Distribution (HMDT) */
#include "sls_hmcg.h"   /* Signalling Message Handling (SMH) Message Congestion Control (HMCG) */
#include "sls_hmrt.h"   /* Signalling Message Handling (SMH) Message Routing (HMRT) */
#include "sls_sltc.h"   /* Signalling Link Test Control (SLTC) */
#include "sls_tcbc.h"   /* Signalling Traffic Management (STM) Changeback Control (TCBC) */
#include "sls_tcoc.h"   /* Signalling Traffic Management (STM) Changeover Control (TCOC) */
#include "sls_tlac.h"   /* Signalling Traffic Management (STM) Link Activation Control (TLAC) */
#include "sls_llsc.h"   /* Signalling Link Management (SLM) Link Set Control (LLSC) */
#include "sls_lsac.h"   /* Signalling Link Management (SLM) Link Set Activation Control (LSAC) */
#include "sls_lsda.h"   /* Signalling Link Management (SLM) Signalling Data Link Activation (LSDA) */
#include "sls_lsla.h"   /* Signalling Link Management (SLM) Signalling Link Activation (LSLA) */
#include "sls_lsld.h"   /* Signalling Link Management (SLM) Signalling Link Deactivation (LSLD) */
#include "sls_lslr.h"   /* Signalling Link Management (SLM) Signalling Link Restoration (LSLR) */
#include "sls_lsta.h"   /* Signalling Link Management (SLM) Signalling Terminal Activation (LSTA) */

/* These two functions need to be implemented somehow */
static inline void lk_tcoc_l2_retrieval_not_possible(lk_t *lk);
static inline void lk_tcoc_l2_bsnt_not_retrievable(lk_t *lk);

/* I haven't found the hooks into the state machine for these ones yet... */
static inline void lk_rb_cleared(lk_t *lk)
{
    (void)lk;
}
static inline void lk_rtb_cleared(lk_t *lk)
{
    (void)lk;
}

/*
 *  =======================================================================
 *
 *  LS<-MTP Service Calls (Driver)
 *
 *  =======================================================================
 */

#if 0
static void mtp_signal_unit(ls_t *ls, int slc, mblk_t *mp) {
    ls_hmrt_hmdc_message_for_routing(ls, slc, mp);
}
static void mtp_stop(ls_t *ls) {
    ls_stop(ls);
}
static void mtp_start(ls_t *ls) {
    ls_start(ls);
}
static void mtp_restart(ls_t *ls) {
    ls_restart(ls);
}
static void mtp_inhibit(ls_t *ls) {
    ls_inhibit(ls);
}
static void mtp_uninhibit(ls_t *ls) {
    ls_unihibit(ls);
}
static void mtp_emergency(ls_t *ls) {
    ls_emergency(ls);
}
static void mtp_normal(ls_t *ls) {
    ls_normal(ls);
}

static ls_dcalls_t ls_drv_ops =
{
    mtp_signal_unit, /* ls_signal_unit */
    mtp_stop,        /* ls_stop        */
    mtp_start,       /* ls_start       */
    mtp_restart,     /* ls_restart     */
    mtp_inhibit,     /* ls_inhibit     */
    mtp_uninhibit,   /* ls_uninhibit   */
    mtp_emergency,   /* ls_emergency   */
    mtp_normal       /* ls_normal      */
};
#endif

/*
 *  =======================================================================
 *
 *  LS<-MTP Service Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static void ls_activate_link_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_activate_link_set_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_adjacent_sp_restarted_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_adjacent_sp_restarting_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_alternative_routing_data_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_deactivate_link_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_deactivate_link_set_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_emergency_ceases_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_emergency_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_inhibit_signalling_link_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_local_inhibit_allowed_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_local_inhibit_denied_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_local_processor_outage_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_local_processor_recovered_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_message_for_distribution_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_message_for_routing_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_remote_inhibit_allowed_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_remote_inhibit_denied_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_restart_begins_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_restart_ends_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_start_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_stop_restart_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_uninhibiting_not_possible_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_uninhibiting_possible_res(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}
static void ls_uninhibit_signalling_link_req(ls_t *ls, mblk_t *mp) {
    freemsg(mp);
}

static void (*ls_mtpl_ops[])(ls_t *, mblk_t *) =
{
    ls_activate_link_req,              /* LS_ACTIVATE_LINK_REQ               */
    ls_activate_link_set_req,          /* LS_ACTIVATE_LINK_SET_REQ           */
    ls_adjacent_sp_restarted_req,      /* LS_ADJACENT_SP_RESTARTED_REQ       */
    ls_adjacent_sp_restarting_req,     /* LS_ADJACENT_SP_RESTARTING_REQ      */
    ls_alternative_routing_data_res,   /* LS_ALTERNATIVE_ROUTING_DATA_RES    */
    ls_deactivate_link_req,            /* LS_DEACTIVATE_LINK_REQ             */
    ls_deactivate_link_set_req,        /* LS_DEACTIVATE_LINK_SET_REQ         */
    ls_emergency_ceases_req,           /* LS_EMERGENCY_CEASES_REQ            */
    ls_emergency_req,                  /* LS_EMERGENCY_REQ                   */
    ls_inhibit_signalling_link_req,    /* LS_INHIBIT_SIGNALLING_LINK_REQ     */
    ls_local_inhibit_allowed_res,      /* LS_LOCAL_INHIBIT_ALLOWED_RES       */
    ls_local_inhibit_denied_res,       /* LS_LOCAL_INHIBIT_DENIED_RES        */
    ls_local_processor_outage_req,     /* LS_LOCAL_PROCESSOR_OUTAGE_REQ      */
    ls_local_processor_recovered_req,  /* LS_LOCAL_PROCESSOR_RECOVERED_REQ   */
    ls_message_for_distribution_req,   /* LS_MESSAGE_FOR_DISTRIBUTION_REQ    */
    ls_message_for_routing_req,        /* LS_MESSAGE_FOR_ROUTING_REQ         */
    ls_remote_inhibit_allowed_res,     /* LS_REMOTE_INHIBIT_ALLOWED_RES      */
    ls_remote_inhibit_denied_res,      /* LS_REMOTE_INHIBIT_DENIED_RES       */
    ls_restart_begins_req,             /* LS_RESTART_BEGINS_REQ              */
    ls_restart_ends_req,               /* LS_RESTART_ENDS_REQ                */
    ls_start_req,                      /* LS_START_REQ                       */
    ls_stop_restart_req,               /* LS_STOP_RESTART_REQ                */
    ls_uninhibiting_not_possible_res,  /* LS_UNINHIBITING_NOT_POSSIBLE_RES   */
    ls_uninhibiting_possible_res,      /* LS_UNINHIBITING_POSSIBLE_RES       */
    ls_uninhibit_signalling_link_req   /* LS_UNINHIBIT_SIGNALLING_LINK_REQ   */
};

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

#if 0
static void sl_rx_pdu(sl_t *sl, mblk_t *mp) {
    DTRACE;
    lk_hmdc_l2_received_message(sl->module, mp);
}
static void sl_congested(sl_t *sl, int cong, int disc) {
    DTRACE;
    lk_tsrc_l2_congested(sl->module, cong, disc);
}
static void sl_no_cong(sl_t *sl, int cong, int disc) {
    DTRACE;
    lk_tsrc_l2_uncongested(sl->module, cong, disc);
}
static void sl_rtrv_msg(sl_t *sl, mblk_t *mp) {
    DTRACE;
    lk_tcoc_l2_retrieved_message(sl->module, mp);
}
static void sl_rtrv_comp(sl_t *sl) {
    DTRACE;
    lk_tcoc_l2_retrieval_complete(sl->module);
}
static void sl_rb_cleared(sl_t *sl) {
    DTRACE;
    lk_rb_cleared(sl->module);
}
static void sl_bsnt(sl_t *sl, int bsnt) {
    DTRACE;
    lk_tcoc_l2_bsnt(sl->module, bsnt);
}
static void sl_in_service(sl_t *sl) {
    DTRACE;
    lk_lsac_l2_in_service(sl->module);
}
static void sl_out_of_service(sl_t *sl, int reason) {
    DTRACE;
    (void)reason;
    lk_lsac_l2_out_of_service(sl->module);
}
static void sl_rpoutage(sl_t *sl) {
    DTRACE;
    lk_lsac_l2_remote_processor_outage(sl->module);
}
static void sl_rprecovered(sl_t *sl) {
    DTRACE;
    lk_lsac_l2_remote_processor_recovered(sl->module);
}
static void sl_rtb_cleared(sl_t *sl) {
    DTRACE;
    lk_rtb_cleared(sl->module);
}

static sl_ucalls_t lk_drv_ucalls =
{
    sl_rx_pdu,          /* sl_pdu               */
    sl_congested,       /* sl_congested         */
    sl_no_cong,         /* sl_no_cong           */
    sl_rtrv_msg,        /* sl_rtrv_msg          */
    sl_rtrv_comp,       /* sl_rtrv_comp         */
    sl_rb_cleared,      /* sl_rb_cleared        */
    sl_bsnt,            /* sl_bsnt              */
    sl_in_service,      /* sl_in_service        */
    sl_out_of_service,  /* sl_out_of_service    */
    sl_rpoutage,        /* sl_rpoutage          */
    sl_rprecovered,     /* sl_rprecovered       */
    sl_rtb_cleared      /* sl_rtb_cleared       */
};
#endif

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

static void sl_pdu_ind(lk_t *lk, mblk_t *mp) {
    lk_hmdc_l2_received_message(lk, mp);
}
static void sl_link_congested_ind(lk_t *lk, mblk_t *mp) {
    int cong = ((int *)mp->b_rptr)[1];
    int disc = ((int *)mp->b_rptr)[2];
    freemsg(mp);
    lk_tsrc_l2_congested(lk, cong, disc);
}
static void sl_link_congestion_ceased_ind(lk_t *lk, mblk_t *mp) {
    int cong = ((int *)mp->b_rptr)[1];
    int disc = ((int *)mp->b_rptr)[2];
    freemsg(mp);
    lk_tsrc_l2_uncongested(lk, cong, disc);
}
static void sl_retrieved_message_ind(lk_t *lk, mblk_t *mp) {
    lk_tcoc_l2_retrieved_message(lk, mp);
}
static void sl_retrieval_complete_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_tcoc_l2_retrieval_complete(lk);
}
static void sl_rb_cleared_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_rb_cleared(lk);
}
static void sl_bsnt_ind(lk_t *lk, mblk_t *mp) {
    int bsnt = ((int *)mp->b_rptr)[1];
    freemsg(mp);
    lk_tcoc_l2_bsnt(lk, bsnt);
}
static void sl_in_service_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_lsac_l2_in_service(lk);
}
static void sl_out_of_service_ind(lk_t *lk, mblk_t *mp) {
    int reason = ((int *)mp->b_rptr)[1];
    freemsg(mp);
    (void)reason;
    lk_lsac_l2_out_of_service(lk);
}
static void sl_remote_processor_outage_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_lsac_l2_remote_processor_outage(lk);
}
static void sl_remote_processor_recovered_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_lsac_l2_remote_processor_recovered(lk);
}
static void sl_rtb_cleared_ind(lk_t *lk, mblk_t *mp) {
    freemsg(mp);
    lk_rtb_cleared(lk);
}

static void (*lk_sl_ops[])(lk_t *, mblk_t *) =
{
    sl_pdu_ind,                         /* SL_PDU_IND                        */
    sl_link_congested_ind,              /* SL_LINK_CONGESTED_IND             */
    sl_link_congestion_ceased_ind,      /* SL_LINK_CONGESTION_CEASED_IND     */
    sl_retrieved_message_ind,           /* SL_RETRIEVED_MESSAGE_IND          */
    sl_retrieval_complete_ind,          /* SL_RETRIEVAL_COMPLETE_IND         */
    sl_rb_cleared_ind,                  /* SL_RB_CLEARED_IND                 */
    sl_bsnt_ind,                        /* SL_BSNT_IND                       */
    sl_in_service_ind,                  /* SL_IN_SERVICE_IND                 */
    sl_out_of_service_ind,              /* SL_OUT_OF_SERVICE_IND             */
    sl_remote_processor_outage_ind,     /* SL_REMOTE_PROCESSOR_OUTAGE_IND    */
    sl_remote_processor_recovered_ind,  /* SL_REMOTE_PROCESSOR_RECOVERED_IND */
    sl_rtb_cleared_ind,                 /* SL_RTB_CLEARED_IND                */
};

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

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

    DTRACE;
    if ( _IOC_TYPE(cmd) == LS_IOC_MAGIC )
        if ( LS_IOC_FIRST <= nr && nr <= LS_IOC_LAST )
            if ( ls_ioc_lmiops[nr] )
                return ls_ioc_lmiops[nr]((ls_t *)ls, cmd, arg);

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

    return -ENXIO;
}

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

static inline int
ls_m_ioctl(queue_t *q, mblk_t *mp)
{
    lk_t *lk, **ll;
    ls_t *ls = (ls_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;
    struct linkblk * lp = (struct linkblk *)arg;

    DTRACE;
    switch ( cmd ) {
        case I_LINK:
        case I_PLINK:
            if ( MINOR(ls->devnum) ) {
                ret = -EPERM;
                break;
            }
            if ( !(lk = kmalloc(sizeof(*lk), GFP_KERNEL)) ) {
                ret = -ENOMEM;
                break;
            }
            bzero(lk, sizeof(*lk));

            lk->next = ls->device; ls->device = lk;
            lk->module = ls;
            lk->ucalls = NULL;
            lk->dcalls = &lk_mod_dcalls;
            lk->device = NULL;
            lk->driver = NULL;
            lk->muxid = lp->l_index;
            lk->rq = RD(lp->l_qbot); lk->rq->q_ptr = lk;
            lk->wq = WR(lp->l_qbot); lk->wq->q_ptr = lk;

            ret = 0;
            break;
        case I_UNLINK:
        case I_PUNLINK:
            if ( MINOR(ls->devnum) ) {
                ret = -EPERM;
                break;
            }
            for ( ll = &ls->device; *ll && (*ll)->muxid != lp->l_index; ll = &(*ll)->next );
            if ( !(lk = *ll) ) {
                ret = -ENXIO;
                break;
            }
            *ll = lk->next;

            lk->rq->q_ptr = NULL;
            lk->wq->q_ptr = NULL;

            kfree(lk);

            ret = 0;
            break;
        default:
            if ( count >= _IOC_SIZE(cmd) ) {
                ret = ls_do_ioctl((lmi_t *)ls, cmd, arg);
            }
#if 0
            /*
             *  FIXME:
             *
             *  If the IOCTL is destined for the link instead of the linkset,
             *  we should pass the command down to the link.  The link can
             *  then attempt to deal with it.  We should use the muxid to
             *  determine which link to pass the command to.  The muxid should
             *  be the first element in arg.  Go through the lower mux list
             *  at ls->device.
             */
            if ( abs(ret) == ENXIO ) {
                if ( q->q_next ) {
                    putnext(q, mp);
                    return(0);
                }
            }
#endif
            break;
    }
    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
ls_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    ls_t *ls = (ls_t *)q->q_ptr;
    int prim = *((ls_long *)mp->b_rptr);

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

static inline int
lk_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    lk_t *lk = (lk_t *)q->q_ptr;
    int prim = *((ls_long *)mp->b_rptr);

    DTRACE;
    DPRINT(0, ("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( LS_USTR_FIRST <= prim && prim <= LS_USTR_LAST ) {
        freemsg(mp);
        (*lk_sl_ops[LS_USTR_FIRST - prim])(lk, mp);
         return(0);
    }
#if 0
    if ( q->q_next ) {
        putnext(q, mp);
        return(0);
    }
#endif
    return err;
}

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

static inline int
ls_m_data(queue_t *q, mblk_t *mp)
{
    ls_t *ls = (ls_t *)q->q_ptr;
    DTRACE;
    ls_hmrt_hmdc_message_for_routing(ls, mp);
    return(0);
}

static inline int
lk_m_data(queue_t *q, mblk_t *mp)
{
    lk_t *lk = (lk_t *)q->q_ptr;
    DTRACE;
    lk_hmdc_l2_received_message(lk, mp);
    return(0);
}

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

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

static void
ls_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 = ls_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = ls_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 = ls_m_ioctl(q, mp)) ) break;
            return;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
#if 0
        case EOPNOTSUPP:
            if ( q->q_next ) {
                putnext(q, mp);
                return;
            }
#endif
    }
    DTRACE;
    freemsg(mp);    /* don't even want to complain */
    return;
}

static void
ls_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 = ls_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = ls_m_proto(q, mp)) ) break;
                continue;
        }
        switch ( err&0xffff ) {
            case EAGAIN:
                if ( mp->b_datap->db_type < QPCTL ) {
                    putbq(q, mp);
                    return;
                }
                break;
#if 0
            case EOPNOTSUPP:
                if ( q->q_next ) {
                    putnext(q, mp);
                    return;
                }
#endif
        }
        DTRACE;
        freemsg(mp);
    }
}

static void
ls_rput(queue_t *q, mblk_t *mp)
{
    if ( mp->b_datap->db_type >= QPCTL || ( !q->q_count && canputnext(q) ))
        putnext(q, mp);
    else
        putq(q, mp);
}

static void
ls_rsrv(queue_t *q)
{
    mblk_t *mp;
    while ( (mp = getq(q)) ) {
        if ( !canputnext(q) ) {
            putbq(q, mp);
            return;
        }
        putnext(q, mp);
    }
}

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

static void
lk_wput(queue_t *q, mblk_t *mp)
{
    if ( mp->b_datap->db_type >= QPCTL || ( !q->q_count && canputnext(q) ))
        putnext(q, mp);
    else
        putq(q, mp);
}

static void
lk_wsrv(queue_t *q)
{
    mblk_t *mp;
    while ( (mp = getq(q)) ) {
        if ( !canputnext(q) ) {
            putbq(q, mp);
            return;
        }
        putnext(q, mp);
    }
}

static void
lk_rput(queue_t *q, mblk_t *mp)
{
    int err = EOPNOTSUPP;
    queue_t *upperq = ((lk_t *)q->q_ptr)->module->rq;

    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 = lk_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = lk_m_proto(q, mp)) ) break;
            return;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
        case EOPNOTSUPP:
            putq(upperq, mp);
            return;
    }
    DTRACE;
    freemsg(mp);
}

static void
lk_rsrv(queue_t *q)
{
    mblk_t *mp;
    int err = EOPNOTSUPP;
    queue_t *upperq = ((lk_t *)q->q_ptr)->module->rq;

    DTRACE;
    while ( (mp = getq(q)) ) {
        if ( mp->b_datap->db_type < QPCTL && !canput(upperq) ) {
            putbq(q, mp);
            return;
        }
        switch ( mp->b_datap->db_type ) {
            case M_DATA:
                if ( (err = lk_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = lk_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:
                putq(upperq, mp);
                return;
        }
        DTRACE;
        freemsg(mp);
    }
}

/*
 *  =======================================================================
 *
 *  SLS Driver Implementation
 *
 *  =======================================================================
 */

static lmi_driver_t *ls_drivers = NULL;

#if 0
static struct lmi *
ls_drv_attach(dev_t dev)
{
    lmi_t *ls;

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( !(ls = lmi_drv_attach(dev, ls_drivers, sizeof(ls_t))) )
        MOD_DEC_USE_COUNT;

    return ls;
}

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

static int
ls_drv_close(struct lmi *ls)
{
    int err;
    ls_t *p = (ls_t *)ls;

    DTRACE;
    if ( !(err = lmi_drv_close(ls,
                    (ls_ulong *)NULL,       LS_MAX_TIMERIDS,
                    (ls_ulong *)&p->bufids, LS_MAX_BUFCALLS)) )
        MOD_DEC_USE_COUNT;
    return(err);
}

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

static struct lmi_ops ls_lmi_ops =
{
    {
        ls_drv_attach,  /* dev.attach   */
        ls_drv_open,    /* dev.open     */
        ls_drv_close,   /* dev.close    */
    },
    {
        lmi_info,       /* lmi.info     */
        lmi_attach,     /* lmi.attach   */
        lmi_detach,     /* lmi.detach   */
        lmi_enable,     /* lmi.enable   */
        lmi_disable,    /* lmi.disable  */
        ls_do_ioctl     /* lmi.ioctl    */
    }
};
#endif

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

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

    if ( (err = lmi_open(q, devp, flag, sflag, crp, ls_drivers, sizeof(ls_t))) )
        return(err);
    ls = (ls_t *)q->q_ptr;
    ls->ucalls = &ls_mod_ucalls;
#if 0
    if ( WR(q)->q_next ) ls->dcalls = &ls_mod_dcalls;
    else                 ls->dcalls = &ls_drv_dcalls;
#endif
    return(0);
}

static int
ls_close(queue_t *q, int flag, cred_t *crp)
{
    int err;
    ls_t *ls = (ls_t *)q->q_ptr;

    DTRACE;
    err = lmi_close(q, flag, crp,
            (ls_ulong *)&ls->timers, LS_MAX_TIMERIDS,
            (ls_ulong *)&ls->bufids, LS_MAX_BUFCALLS);
    return(err);
}

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

#if 0
int
ls_register_driver(major_t cmajor, int nminor, char *name,
        lmi_ops_t *ops, ls_dcalls_t *dcalls)
{
    int ret;

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( (ret = lmi_register_driver(
                    &ls_drivers, cmajor, &ls_info, nminor, name,
                    ops, dcalls, &ls_drv_ucalls)) < 0 ) {
        MOD_DEC_USE_COUNT;
        return(ret);
    }
    return(ret);
}

int
ls_unregister_driver(major_t cmajor)
{
    int err = lmi_unregister_driver(&ls_drivers, cmajor);
    DTRACE;
    if ( !err )
        MOD_DEC_USE_COUNT;
    return (err);
}
#endif

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

static int ls_initialized = 0;

#ifndef LIS_REGISTERED
static inline void ls_init(void)
#else
__initfunc(void ls_init(void))
#endif
{
    if ( ls_initialized ) return;
    ls_initialized = 1;
    printk(KERN_INFO LS_BANNER);   /* console splash */
#ifndef LIS_REGISTERED
    if ( !(ls_minfo.mi_idnum = lis_register_strmod(&ls_info, ls_minfo.mi_idname)) ) {
        cmn_err(CE_NOTE, "ls: couldn't register module\n");
    }
#endif
#if 0
    if ( mtp_register_driver(LS_CMAJOR, LS_NMINOR, "sls",
                &ls_lmi_ops, &ls_drv_ops) ) {
        cmn_err(CE_NOTE, "sls: couldn't register as driver\n");
    }
#endif
};

#ifndef LIS_REGISTERED
static inline void ls_terminate(void)
#else
__initfunc(void ls_terminate(void))
#endif
{
    if ( !ls_initialized ) return;
    ls_initialized = 0;
#ifndef LIS_REGSITERED
    if (ls_minfo.mi_idnum)
        if ( (ls_minfo.mi_idnum = lis_unregister_strmod(&ls_info)) ) {
            cmn_err(CE_WARN, "ls: couldn't unregister module!\n");
        }
#endif
};

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

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

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

