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

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

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

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

#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/lki.h>
#include <ss7/lki_ioctl.h>

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

#define LK_DESCRIP      "SS7/LKI: (Link Interface) STREAMS DRIVER."
#define LK_COPYRIGHT    "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define LK_DEVICES      "Supports OpenSS7 LK driver."
#define LK_CONTACT      "Brian Bidulock <bidulock@openss7.org>"
#define LK_BANNER       LK_DESCRIP      "\n" \
                        LK_COPYRIGHT    "\n" \
                        LK_DEVICES      "\n" \
                        LK_CONTACT      "\n"

#ifdef MODULE
MODULE_AUTHORS(LK_CONTACT);
MODULE_DESCRIPTION(LK_DESCRIP);
MODULE_SUPPORTED_DEVICE(LK_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef LK_DEBUG
static int lk_debug = LK_DEBUG;
#else
static int lk_debug = 2;
#endif

#define DEBUG_LEVEL lk_debug

#define LK_CMAJOR 0
#define LK_NMINOR 255

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

static struct module_info lk_minfo =
{
    0,              /* Module ID number             */
    "lk",           /* Module name                  */
    4,              /* 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 int  lk_open (queue_t *, dev_t *, int, int, cred_t *);
static int  lk_close(queue_t *, int, cred_t *);

static struct qinit lk_rinit =
{
    lk_rput,        /* Read put (msg from below)    */
    lk_rsrv,        /* Read queue service           */
    lk_open,        /* Each open                    */
    lk_close,       /* 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                   */
};

#ifndef LIS_REGISTERED
static
#endif
struct streamtab lk_info =
{
    &lk_rinit,      /* Upper read  queue            */
    &lk_winit,      /* Upper write queue            */
    NULL,           /* Lower read  queue            */
    NULL            /* Lower write queue            */
};

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

static int
lk_iocgoptions(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(&lk->option, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
lk_iocsoptions(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(arg, &lk->option, _IOC_SIZE(cmd));
    return(0);
}

static int
lk_iocgconfig(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(&lk->config, arg, _IOC_SIZE(cmd));
    return(0);
}

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

static int
lk_iocgstatem(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(&lk->statem, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
lk_iocsstatsp(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    (void)arg;
    return EOPNOTSUPP;
}

static int
lk_ioccstats(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(&lk->stats, arg, _IOC_SIZE(cmd));
    return(0);
}

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

static int
lk_ioccnotify(lk_t *lk, int cmd, void *arg)
{
    caddt_t dsc, src, end;
    DTRACE;
    src = arg;
    dst = (caddr_t)&lk->notify;
    end = (caddr_t)((&lk->notify)+1);
    while ( dst < end ) *dst++ &= ~*src++;
    return(0);
}

static int
lk_iocgnotify(lk_t *lk, int cmd, void *arg)
{
    DTRACE;
    bcopy(&lk->notify, arg, _IOC_SIZE(cmd));
    return(0);
}

static int (*lk_ioc_lmiops [])(lk_t *, int, void *) =
{
    lk_iocgoptions, /* LK_IOCGOPTIONS   */
    lk_iocsoptions, /* LK_IOCSOPTIONS   */
    lk_iocgconfig,  /* LK_IOCGCONFIG    */
    lk_iocsconfig,  /* LK_IOCSCONFIG    */
    NULL,           /* LK_IOCTCONFIG    */
    NULL,           /* LK_IOCCCONFIG    */
    lk_iocgstatem,  /* LK_IOCGSTATEM    */
    NULL,           /* LK_IOCMRESET     */
    lk_iocgstatsp,  /* LK_IOCGSTATSP    */
    lk_iocsstatsp,  /* LK_IOCSSTATSP    */
    lk_ioccstats,   /* LK_IOCCSTATS     */
    lk_iocgnotify,  /* LK_IOCGNOTIFY    */
    lk_iocsnotify,  /* LK_IOCSNOTIFY    */
    lk_ioccnotify   /* LK_IOCCNOTIFY    */
};

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

static inline void lk_uprim_link(lk_t *lk, 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;
        *((lk_long *)mp->b_rptr)++ = prim;
        md = mp->b_cont;
    }
    putnext(lk->rq, mp);
}

static inline void lk_uprim(lk_t *lk, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(lk_long), BRPI_HI)) )  {
        mp->b_datap->db_type = type;
        *((lk_long *)mp->b_wptr)++ = prim;
        putnext(lk->rq, mp);
    }
}

static inline void lk_uprim_arg(lk_t *lk, int type, int prim, int arg)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(2*sizeof(lk_long), BRPI_HI)) ) {
        mp->datap->db_type = type;
        *((lk_long *)mp->b_wptr)++ = prim;
        *((lk_long *)mp->b_wptr)++ = arg;
        putnext(lk->rq, mp);
    }
}

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

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

static lk_ucalls_t lk_mod_ucalls =
{
    lk_ls_pdu,                          /* lk_pdu               */




};

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

static inline void lk_dprim(lk_t *lk, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(lk_long), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((lk_long *)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;
    DTRACE;
    if ( (mp = allocb(1*sizeof(lk_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((lk_ulong *)mp->b_wptr)++ = prim;
        *((lk_ulong *)mp->b_wptr)++ = arg;
        putnext(lk->wq, mp);
    }
}

static void sl_pdu_req(lk_t *lk, mblk_t *mp) {
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        mp->b_datap->db_type = M_PROTO;
        mp->b_wptr = mp->b_rptr;
        *((lk_long *)mp->b_wptr)++ = SL_PDU_REQ;
    }
    putnext(lk->wq, mp);
}
static void sl_emerg_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_EMERGENCY_REQ);
}
static void sl_normal_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_EMERGENCY_CEASES_REQ);
}
static void sl_start_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_START_REQ);
}
static void sl_stop_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_STOP_REQ);
}
static void sl_bsnt_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_RETRIEVE_BSNT_REQ);
}
static void sl_fsnc_req(lk_t *lk, int fsnc) {
    DTRACE;
    lk_dprim_arg(lk, M_PCPROTO, SL_RETRIEVAL_REQUEST_AND_FSNC_REQ, fsnc);
}
static void sl_resume_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_RESUME_REQ);
}
static void sl_clear_bufs_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_CLEAR_BUFFERS_REQ);
}
static void sl_lpo_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_LOCAL_PROCESSOR_OUTAGE_REQ);
}
static void sl_cg_accept_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_CONGESTION_DISCARD_REQ);
}
static void sl_cg_discard_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_CONGESTION_ACCEPT_REQ);
}
static void sl_no_cong_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_NO_CONGESTION_REQ);
}
static void sl_power_on_req(lk_t *lk) {
    DTRACE;
    lk_dprim(lk, M_PCPROTO, SL_POWER_ON_REQ);
}

static lk_mcalls_t lk_mod_dcalls =
{
    sl_pdu,                     /* sl_pdu           */
    sl_emerg,                   /* sl_emerg         */
    sl_normal,                  /* sl_normal        */
    sl_start,                   /* sl_start         */
    sl_stop,                    /* sl_stop          */
    sl_bsnt,                    /* sl_bsnt          */
    sl_fsnc,                    /* sl_fsnc          */
    sl_resume,                  /* sl_resume        */
    sl_clear_bufs,              /* sl_clear_bufs    */
    sl_lpo,                     /* sl_lpo           */
    sl_cg_accept,               /* sl_cg_accept     */
    sl_cg_discard,              /* sl_cg_discard    */
    sl_no_cong,                 /* sl_no_cong       */
    sl_power_on                 /* sl_power_on      */
};

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

static void sl_pdu(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_pdu(lk->device, mp);
}

static void sl_emerg(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_emerg(lk->device, mp);
}

static void sl_normal(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_normal(lk->device, mp);
}

static void sl_start(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_start(lk->device, mp);
}

static void sl_stop(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_stop(lk->device, mp);
}

static void sl_bsnt(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_bsnt(lk->device, mp);
}

static void sl_fsnc(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_fsnc(lk->device, mp);
}

static void sl_resume(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_resume(lk->device, mp);
}

static void sl_clear_bufs(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_clear_bufs(lk->device, mp);
}

static void sl_lpo(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_lpo(lk->device, mp);
}

static void sl_cg_accept(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_cg_accept(lk->device, mp);
}

static void sl_cg_discard(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_cg_discard(lk->device, mp);
}

static void sl_no_cong(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_no_cong(lk->device, mp);
}

static void sl_power_on(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk->driver->dcalls->sl_power_on(lk->device, mp);
}

static lk_mcalls_t lk_drv_dcalls =
{
    sl_pdu,                     /* sl_pdu           */
    sl_emerg,                   /* sl_emerg         */
    sl_normal,                  /* sl_normal        */
    sl_start,                   /* sl_start         */
    sl_stop,                    /* sl_stop          */
    sl_bsnt,                    /* sl_bsnt          */
    sl_fsnc,                    /* sl_fsnc          */
    sl_resume,                  /* sl_resume        */
    sl_clear_bufs,              /* sl_clear_bufs    */
    sl_lpo,                     /* sl_lpo           */
    sl_cg_accept,               /* sl_cg_accept     */
    sl_cg_discard,              /* sl_cg_discard    */
    sl_no_cong,                 /* sl_no_cong       */
    sl_power_on                 /* sl_power_on      */
};

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

#include "lk_sm.h"

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

#if 0
static lk_dcalls_t lk_drv_ops =
{
    lk_pdu,                         /* lk_pdu               */
};
#endif

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

static void (*lk_ls_ops[])(lk_t *, mblk_t *) =
{
    lk_pdu_req,                     /* LK_PDU_REQ                           */
};

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

static sl_ucalls_t lk_drv_ucalls =
{
    sl_pdu,                         /* sl_pdu               */
};

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

static void sl_pdu_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    lk_pdu(lk, mp);
}
static void sl_link_congested_ind(lk_t *lk, mblk_t *mp) {
    sl_link_cong_ind_t *p = (sl_link_cong_ind_t *)mp->b_rptr;
    int cong = p->sl_cong_level;
    int disc = p->sl_disc_level;
    DTRACE;
    freemsg(mp);
    lk_link_congested(lk, cong, disc);
}
static void sl_link_congestion_ceased_ind(lk_t *lk, mblk_t *mp) {
    sl_link_cong_ceased_ind_t *p = (sl_link_cong_ceased_ind_t *)mp->b_rptr;
    int cong = p->sl_cong_level;
    int disc = p->sl_disc_level;
    DTRACE;
    freemsg(mp);
    lk_link_congestion_ceased(lk, cong, disc);
}
static void sl_retreived_message(lk_t *lk, mblk_t *mp) {
    mblk_t *md = mp->b_cont;
    DTRACE;
    mp->b_cont = NULL;
    freemsg(mp);
    lk_retrieved_message(lk, md);
}
static void sl_retrieval_complete_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    lk_retrieval_complete(lk);
}
static void sl_rb_cleared(lk_t *lk, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    lk_rb_cleared(lk);
}
static void sl_bsnt_ind(lk_t *lk, mblk_t *mp) {
    sl_bsnt_ind_t *p = (sl_bsnt_ind_t *)mp->b_rptr;
    int bsnt = p->sl_bsnt;
    DTRACE;
    freemsg(mp);
    lk_bsnt(lk, bsnt);
}
static void sl_in_service_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    lk_in_service(lk);
}
static void sl_out_of_service_ind(lk_t *lk, mblk_t *mp) {
    sl_out_of_service_t *p = (sl_out_of_service_t *)mp->b_rptr;
    int reason = p->sl_reason;
    DTRACE;
    freemsg(mp);
    lk_out_of_service(lk, reason);
}
static void sl_remote_processor_outage_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    lk_remote_processor_outage(lk);
}
static void sl_remote_processor_recovered_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    freemsg(mp);
    lk_remote_processor_recovered_ind(lk);
}
static void sl_rtb_cleared_ind(lk_t *lk, mblk_t *mp) {
    DTRACE;
    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
lk_do_ioctl(lmi_t, int cmd, void *arg)
{
    lmi_driver_t *drv;
    int nr = _IOC_NR(cmd);

    DTRACE;
    if ( _IOC_TYPE(cmd) == LK_IOC_MAGIC )
        if ( LK_IOC_FIRST <= nr && nr <= LK_IOC_LAST )
            if ( lk_ioc_lmiops[nr] )
                return lk_ioc_lmiops[nr]((lk_t *)lk, cmd, arg);

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

    return -ENXIO;
}

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

static inline int
lk_m_ioctl(queue_t *q, mblk_t *mp)
{
    lk_t *lk = (lk_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 = lk_do_ioctl((lmi_t *)lk, 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
lk_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    lk_t *lk = (lk_t *)q->q_ptr;
    int prim = *((lk_long *)mp->b_rptr);

    DTRACE;
    DRPINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( LK_DSTR_FIRST <= prim && prim <= LK_DSTR_LAST ) {
        (*lk_ls_ops[prim - LK_DSTR_FIRST])(lk, 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 *)lk, mp);
    }
    return err;
}

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

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SL_USTR_FIRST <= prim && prim <= SL_USTR_LAST ) {
        freemsg(mp);
        (*lk_sl_ops[LK_USTR_FIRST = prim])(lk, mp);
        return(0);
    }
    if ( q->q_next ) {
        putnext(q, mp);
        return(0);
    }
    return err;
}
/* 
 *  =========================================================================
 *
 *  M_DATA handling
 *
 *  =========================================================================
 */

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

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

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

void lk_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 = 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;
        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 = lk_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 lk_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 = 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:
                if ( q->q_next ) {
                    putnext(q, mp);
                    return;
                }
        }
        freemsg(mp);
    }
}

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

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

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

static lmi_driver_t *lk_drivers = NULL;

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

    if ( (err = lmi_open(q, devp, flag, sflag, crp, lk_drivers, sizeof(lk_t))) )
        return(err);
    lk = (lk_t *)q->q_ptr;
    lk->ucalls = &lk_mod_ucalls;
    if ( WR(q)->q_next ) lk->dcalls = &lk_mod_dcalls;
    else                 lk->dcalls = &lk_drv_dcalls;

    return(0);
}

static int
lk_close(queue_t *q, int flag, cred_t *crp)
{
    int err;
    lk_t *lk = (lk_t *)q->q_ptr;

    DTRACE;
    err = lmi_close(q, flag, crp,
            (lk_ulong *)&lk->timers, LK_MAX_TIMERIDS,
            (lk_ulong *)&lk->bufids, LK_MAX_BUFCALLS);
    return(err);
}

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

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

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( (ret = lmi_register_driver(
                    &lk_drivers, cmajor, &lk_info, nminor, name,
                    ops, dcalls, &lk_drv_calls)) < 0 ) {
        MOD_DEC_USE_COUNT;
        return(ret);
    }
    return(ret);
}

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

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

static in lk_initialized = 0;

#ifndef LIS_REGISTERED
static inline void lk_init(void)
#else
__initfunc(void lk_init(void))
#endif
{
    DTRACE;
    if ( lk_initialized ) return;
    lk_initialized = 1;
    printk(KERN_INFO LK_BANNER);    /* console splash */
#ifndef LIS_REGISTERED
    if ( !(lk_minfo.mi_idnum = lis_register_strmod(&lk_info, lk_minfo.mi_idname)) ) {
        cmd_err(CE_NOTE, "lk: couldn't register module\n");
    }
#endif
#if 0
    if ( ls_register_driver(LS_CMAJOR, LS_NMINOR, "lk",
                &lk_lmi_ops, &lk_drv_ops) ) {
        cmn_err(CE_NOTE, "lk: couldn't register driver\n");
    }
#endif
};

#ifndef LIS_REGISTER
static inline void lk_terminate(void)
#else
__initfunc(void lk_terminate(void))
#endif
{
    if ( !lk_initialized ) return;
    lk_initialized = 0;
#ifndef LIS_REGISTERED
    if ( lk_minfo.mi_idnum )
        if ( (lk_minfo.mi_idnum = lis_unregister_strmod(&lk_info)) ) {
            cmd_err(CE_WARN, "lk: couldn't unregister module!\n");
        }
#endif
};

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

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

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