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

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

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

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

/*
 *  This is a SDL (Signalling Data Terminal) loadable module which provides
 *  all of the capabilities of the SDLI while allowing device drivers to
 *  provide a few missing functions by registering with the module to provide
 *  a complete SDL driver.  This relieves much of the LiS STREAMS burden from
 *  the driver implementation and permits the driver to be more portable.  It
 *  also allows SS7 signalling link compliant state machines in the SDT and SL
 *  autopush modules 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 <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 "../lmi/lm.h"
#include "../devi/dev.h"
#include "../sdli/sdl.h"

#define SDL_DESCRIP   "SS7/SDLI: (Signalling Data Link Interface) STREAMS DRIVER."
#define SDL_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SDL_DEVICES   "Supports OpenSS7 SDL drivers."
#define SDL_CONTACT   "Brian Bidulock <bidulock@openss7.org>"
#define SDL_BANNER    SDL_DESCRIP   "\n" \
                      SDL_COPYRIGHT "\n" \
                      SDL_DEVICES   "\n" \
                      SDL_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(SDL_CONTACT);
MODULE_DESCRIPTION(SDL_DESCRIP);
MODULE_SUPPORTED_DEVICE(SDL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef SDL_DEBUG
static int sdl_debug = SL_DEBUG;
#else
static int sdl_debug = 2;
#endif

#define DEBUG_LEVEL sdl_debug

#define SDL_CMAJOR 0
#define SDL_NMINOR 255

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

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

static void sdl_rput (queue_t *, mblk_t *);
static void sdl_rsrv (queue_t *);
static int  sdl_open (queue_t *, dev_t *, int, int, cred_t *);
static int  sdl_close(queue_t *, int, cred_t *);

static struct qinit sdl_rinit =
{
    sdl_rput,       /* Read put (msg from below)    */
    sdl_rsrv,       /* Read queue service           */
    sdl_open,       /* Each open                    */
    sdl_close,      /* Last close                   */
    NULL,           /* Admin (not used)             */
    &sdl_minfo,     /* Information                  */
    NULL            /* Statistics                   */
};

static void sdl_wput (queue_t *, mblk_t *);
static void sdl_wsrv (queue_t *);

static struct qinit sdl_winit =
{
    sdl_wput,       /* Write put (msg from above)   */
    sdl_wsrv,       /* Write queue service          */
    NULL,           /* Each open                    */
    NULL,           /* Last close                   */
    NULL,           /* Admin (not used)             */
    &sdl_minfo,     /* Information                  */
    NULL            /* Statistics                   */
};

#ifndef LIS_REGISTERED
static
#endif
struct streamtab sdl_info =
{
    &sdl_rinit,     /* Upper read  queue            */
    &sdl_winit,     /* Upper write queue            */
    NULL,           /* Lower read  queue            */
    NULL            /* Lower write queue            */
};

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

static int
sdl_iocgoptions(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdl->option, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
sdl_iocsoptions(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(arg, &sdl->option, _IOC_SIZE(cmd));
    return(0);
}

static int
sdl_iocgconfig(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdl->config, arg, _IOC_SIZE(cmd));
    return(0);
}

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

static int
sdl_iocgstatem(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdl->statem, arg, _IOC_SIZE(cmd));
    return(0);
}

static int
sdl_iocsstatsp(sdl_t *sdl, int cmd, void *arg)
{
    (void)sdl;
    (void)cmd;
    (void)arg;
    DTRACE;
    return EOPNOTSUPP;
}

static int
sdl_iocgstatsp(sdl_t *sdl, int cmd, void *arg)
{
    (void)sdl;
    (void)cmd;
    (void)arg;
    DTRACE;
    return EOPNOTSUPP;
}

static int
sdl_ioccstats(sdl_t *sdl, int cmd, void *arg)
{
    (void)arg;
    DTRACE;
    bzero(&sdl->stats, _IOC_SIZE(cmd));
    return(0);
}

static int
sdl_iocgstats(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdl->stats, arg, _IOC_SIZE(cmd));
    return(0);
}

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

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

static int
sdl_iocgnotify(sdl_t *sdl, int cmd, void *arg)
{
    DTRACE;
    bcopy(&sdl->notify, arg, _IOC_SIZE(cmd));
    return(0);
}

static int (*sdl_ioc_lmiops [])(sdl_t *, int, void *) =
{
    sdl_iocgoptions, /* SDL_IOCGOPTIONS */
    sdl_iocsoptions, /* SDL_IOCSOPTIONS */
    sdl_iocgconfig,  /* SDL_IOCGCONFIG  */
    sdl_iocsconfig,  /* SDL_IOCSCONFIG  */
    NULL,            /* SDL_IOCTCONFIG  */
    NULL,            /* SDL_IOCCCONFIG  */
    sdl_iocgstatem,  /* SDL_IOCGSTATEM  */
    NULL,            /* SDL_IOCCMRESET  */
    sdl_iocgstatsp,  /* SDL_IOCGSTATSP  */
    sdl_iocsstatsp,  /* SDL_IOCSSTATSP  */
    sdl_iocgstats,   /* SDL_IOCGSTATS   */
    sdl_ioccstats,   /* SDL_IOCCSTATS   */
    sdl_iocgnotify,  /* SDL_IOCGNOTIFY  */
    sdl_iocsnotify,  /* SDL_IOCSNOTIFY  */
    sdl_ioccnotify   /* SDL_IOCCNOTIFY  */
};

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

static inline void
sdl_daedr_received_bits_ind(sdl_t *sdl, mblk_t *mp) { /* called by driver */
    DTRACE;
    putq(sdl->rq, mp);
}

static inline void
sdl_daedr_correct_su_ind(sdl_t *sdl, int count) { /* called by driver */
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(SDL_DAEDR_CORRECT_SU_IND_SIZE, BPRI_MED)) ) {
        mp->b_datap->db_type = M_PCPROTO;
        *((int *)mp->b_wptr)++ = SDL_DAEDR_CORRECT_SU_IND;
        *((int *)mp->b_wptr)++ = count;
        putq(sdl->rq, mp);
    }
}

static inline void
sdl_daedr_su_in_error_ind(sdl_t *sdl) { /* called by driver */
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(SDL_DAEDR_SU_IN_ERROR_IND_SIZE, BPRI_MED)) ) {
        mp->b_datap->db_type = M_PCPROTO;
        *((int *)mp->b_wptr)++ = SDL_DAEDR_SU_IN_ERROR_IND;
        putq(sdl->rq, mp);
    }
}

static struct sdl_ucalls sdl_mod_ucalls =
{
    sdl_daedr_received_bits_ind,    /* daedr_received_bits  */
    sdl_daedr_correct_su_ind,       /* daedr_correct_su     */
    sdl_daedr_su_in_error_ind       /* daedr_su_in_error    */
};

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

static void sdl_dev_xmit(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    ASSERT(sdl->driver);
    if ( sdl->driver)
        sdl->driver->dcalls->xmit(sdl->device, mp);
}

static void sdl_dev_tx_start(sdl_t *sdl)
{
    DTRACE;
    ASSERT(sdl->driver);
    if ( sdl->driver)
        sdl->driver->dcalls->tx_start(sdl->device);
}

static void sdl_dev_rx_start(sdl_t *sdl)
{
    DTRACE;
    ASSERT(sdl->driver);
    if ( sdl->driver)
        sdl->driver->dcalls->rx_start(sdl->device);
}

static struct sdl_mcalls sdl_drv_dcalls =
{
    sdl_dev_xmit,       /* xmit     */
    sdl_dev_tx_start,   /* tx_start */
    sdl_dev_rx_start    /* rx_start */
};

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

static inline void sdl_dprim(sdl_t *sdl, int type, int prim)
{
    mblk_t *mp;
    DTRACE;
    if ( (mp = allocb(sizeof(sdl_ulong), BPRI_HI)) ) {
        mp->b_datap->db_type = type;
        *((int *)mp->b_wptr)++ = prim;
        putnext(sdl->wq, mp); /* FIXME */
    }
}

static void sdl_dev_xmit_req(sdl_t *sdl, mblk_t *mp) {
    DTRACE;
    if ( mp->b_datap->db_type != M_DATA ) {
        *((int *)mp->b_rptr) = 0; /* DEV_XMIT_REQ; FIXME */
        mp->b_datap->db_type = M_PROTO;
    }
    putnext(sdl->wq, mp); /* FIXME */
}

static void sdl_dev_tx_start_req(sdl_t *sdl) {
    DTRACE;
    sdl_dprim(sdl, M_PCPROTO, 1); /* DEV_TX_START_REQ); FIXME */
}

static void sdl_dev_rx_start_req(sdl_t *sdl) {
    DTRACE;
    sdl_dprim(sdl, M_PCPROTO, 2); /* DEV_RX_START_REQ); FIXME */
}

static struct sdl_mcalls sdl_mod_dcalls =
{
    sdl_dev_xmit_req,       /* xmit     */
    sdl_dev_tx_start_req,   /* tx_start */
    sdl_dev_rx_start_req    /* rx_start */
};

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

static void sdl_daedr_recvd_frame(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    if ( sdl->statem.daedr_state == SDL_STATE_IN_SERVICE )
        sdl->ucalls->daedr_received_bits(sdl, mp);
    else
        freemsg(mp);
}

static void sdl_daedr_compr_frame(sdl_t *sdl, int count)
{
    DTRACE;
    if ( sdl->statem.daedr_state == SDL_STATE_IN_SERVICE )
        sdl->ucalls->daedr_correct_su(sdl, count);
}

static void sdl_daedr_error_frame(sdl_t *sdl)
{
    DTRACE;
    if ( sdl->statem.daedr_state == SDL_STATE_IN_SERVICE )
        if ( !sdl->statem.octet_counting_mode )
            sdl->ucalls->daedr_su_in_error(sdl);
}

static void sdl_daedr_loss_of_sync(sdl_t *sdl)
{
    DTRACE;
    sdl->statem.octet_counting_mode = 1;
}

static void sdl_daedr_N_octets(sdl_t *sdl)
{
    DTRACE;
    if ( sdl->statem.daedr_state == SDL_STATE_IN_SERVICE )
        sdl->ucalls->daedr_su_in_error(sdl);
}

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

static void sdl_daedt_bits_for_trans(sdl_t *sdl, mblk_t *mp) {
    DTRACE;
    sdl->dcalls->xmit(sdl, mp);
}

static void sdl_daedt_start(sdl_t *sdl) {
    DTRACE;
    sdl->statem.daedt_state = SDL_STATE_IN_SERVICE;
    sdl->dcalls->tx_start(sdl);
}

static void sdl_daedr_start(sdl_t *sdl) {
    DTRACE;
    sdl->statem.daedr_state = SDL_STATE_IN_SERVICE;
    sdl->dcalls->rx_start(sdl);
}

static struct sdl_dcalls sdl_drv_ops =
{
    sdl_daedt_bits_for_trans,   /* daedt_xmit   */
    sdl_daedt_start,            /* daedt_start  */
    sdl_daedr_start             /* daedr_start  */
};

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

static void sdl_daedt_xmit_req(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    sdl_daedt_bits_for_trans(sdl, mp);
}
static void sdl_daedt_start_req(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    freemsg(mp);
    sdl_daedt_start(sdl);
}
static void sdl_daedr_start_req(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    freemsg(mp);
    sdl_daedr_start(sdl);
}

static void (*sdl_sdt_ops[])(sdl_t *, mblk_t *) = {
    sdl_daedt_xmit_req,         /* SDL_DAEDT_BITS_FOR_TRANS   */
    sdl_daedt_start_req,        /* SDL_DAEDT_START_REQ        */
    sdl_daedr_start_req         /* SDL_DAEDR_START_REQ        */
};

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

static void dev_daedr_recvd_frame(struct dev *dev, mblk_t *mp)
{
    DTRACE;
    /* FIXME: if (sdl->rq) build mp and place on sdl->rq */
    sdl_daedr_recvd_frame(dev->module, mp);
}
static void dev_daedr_compr_frame(struct dev *dev, int count)
{
    DTRACE;
    /* FIXME: if (sdl->rq) build mp and place on sdl->rq */
    sdl_daedr_compr_frame(dev->module, count);
}
static void dev_daedr_error_frame(struct dev *dev)
{
    DTRACE;
    /* FIXME: if (sdl->rq) build mp and place on sdl->rq */
    sdl_daedr_error_frame(dev->module);
}
static void dev_daedr_loss_of_sync(struct dev *dev)
{
    DTRACE;
    /* FIXME: if (sdl->rq) build mp and place on sdl->rq */
    sdl_daedr_loss_of_sync(dev->module);
}
static void dev_daedr_N_octets(struct dev *dev)
{
    DTRACE;
    /* FIXME: if (sdl->rq) build mp and place on sdl->rq */
    sdl_daedr_N_octets(dev->module);
}

static dev_ucalls_t sdl_dev_ucalls =
{
    dev_daedr_recvd_frame,  /* daedr_recvd_frame    */
    dev_daedr_compr_frame,  /* daedr_compr_frame    */
    dev_daedr_error_frame,  /* daedr_error_frame    */
    dev_daedr_loss_of_sync, /* daedr_loss_of_sync   */
    dev_daedr_N_octets,     /* daedr_N_octets       */
};

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

static void dev_daedr_recvd_frame_ind(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    sdl_daedr_recvd_frame(sdl, mp); /* FIXME */
}
static void dev_daedr_compr_frame_ind(sdl_t *sdl, mblk_t *mp)
{
    int count = ((int *)mp->b_rptr)[1];
    DTRACE;
    freemsg(mp); /* FIXME */
    sdl_daedr_compr_frame(sdl, count);
}
static void dev_daedr_error_frame_ind(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    freemsg(mp); /* FIXME */
    sdl_daedr_error_frame(sdl);
}
static void dev_daedr_loss_of_sync_ind(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    freemsg(mp); /* FIXME */
    sdl_daedr_loss_of_sync(sdl);
}
static void dev_daedr_N_octets_ind(sdl_t *sdl, mblk_t *mp)
{
    DTRACE;
    freemsg(mp); /* FIXME */
    sdl_daedr_N_octets(sdl);
}

static void (*sdl_dev_ops[])(sdl_t *, mblk_t *) =
{
    dev_daedr_recvd_frame_ind,  /* DEV_DAEDR_RECEIVED_FRAME_IND   */
    dev_daedr_compr_frame_ind,  /* DEV_DAEDR_COMPRESSED_FRAME_IND */
    dev_daedr_error_frame_ind,  /* DEV_DAEDR_ERROR_FRAME_IND      */
    dev_daedr_loss_of_sync_ind, /* DEV_DAEDR_LOSS_OF_SYNC_IND     */
    dev_daedr_N_octets_ind      /* DEV_DAEDR_N_OCTETS_IND         */
};

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

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

    DTRACE;
    if ( _IOC_TYPE(cmd) == SDL_IOC_MAGIC )
        if ( SDL_IOC_FIRST <= nr && nr <= SDL_IOC_LAST )
            if ( sdl_ioc_lmiops[nr] )
                return sdl_ioc_lmiops[nr]((sdl_t *)sdl, cmd, arg);
    
    if ( (drv = sdl->driver) )
            return drv->ops.lmi.ioctl(sdl, cmd, arg);

    return -ENXIO;
}

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

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

    DTRACE;
    if ( size >= _IOC_SIZE(cmd) ) {
        ret = sdl_do_ioctl((lmi_t *)sdl, cmd, arg);
    }
    if ( abs(ret) == ENXIO ) {
        if ( WR(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
sdl_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
    sdl_t *sdl = (sdl_t *)q->q_ptr;
    int prim = *((lmi_long *)mp->b_rptr);

    DTRACE;
    DPRINT(0,("%s: [%s %d] primitive = %d\n", __FUNCTION__, __FILE__, __LINE__, prim));
    if ( SDL_DSTR_FIRST <= prim && prim <= SDL_DSTR_LAST ) {
        DPRINT(0,("%s: [%s %d] sdl_sdt_ops offset = %d\n",
                    __FUNCTION__, __FILE__, __LINE__, (int)(prim - SDL_DSTR_FIRST)));
        (*sdl_sdt_ops[prim - SDL_DSTR_FIRST])(sdl, mp);
        return(0);
    }
    if ( WR(q)->q_next ) {
        putnext(q, mp);
        return (0);
    }
    if ( LMI_DSTR_FIRST <= prim && prim <= LMI_DSTR_LAST ) {
        DTRACE;
        err = lmi_lmi_ops[prim - LMI_DSTR_FIRST]((lmi_t *)sdl, mp);
    }
    DPRINT(0,("%s: [%s %d] Error return = %d\n",
                __FUNCTION__, __FILE__, __LINE__, err));
    return err;
}

static inline int
dev_m_proto(queue_t *q, mblk_t *mp)
{
    int err = LMI_BADPRIM + EOPNOTSUPP;
//  sdl_t *sdl = (sdl_t *)q->q_ptr;
//  int prim = *((lmi_long *)mp->b_rptr);

    DTRACE;
//  FIXME: for device which want to send mblks...
//  if ( DEV_USTR_FIRST <= prim && prim <= DEV_USTR_LAST ) {
//      (*sdl_dev_ops[DEV_USTR_FIRST - prim])(sdl, mp);
//      return(0);
//  }
    if ( RD(q)->q_next ) {
        putnext(q, mp);
        return(0);
    }
    return err;
}

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

static inline int
sdl_m_data(queue_t *q, mblk_t *mp)
{
    sdl_t *sdl = (sdl_t *)q->q_ptr;
    DTRACE;
    sdl->dcalls->xmit(sdl, mp);
    return(0);
}

static inline int
dev_m_data(queue_t *q, mblk_t *mp)
{
//  sdl_t *sdl = (sdl_t *)q->q_ptr;
    DTRACE;
    putnext(q, mp);
//  sdl->ucalls->daedr_received_bits(sdl, mp);  /* loop */
    return(0);
}

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

static void
sdl_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 = sdl_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:   /* should be sent band 1 */
        case M_PCPROTO:
            if ( (err = sdl_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 = sdl_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... */
}

static void
sdl_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 = sdl_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = sdl_m_proto(q, mp)) ) break;
                continue;
            default:
                err = EOPNOTSUPP;
        }
        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
sdl_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 = dev_m_data(q, mp)) ) break;
            return;
        case M_CTL:
        case M_PROTO:
        case M_PCPROTO:
            if ( (err = dev_m_proto(q, mp)) ) break;
            return;
    }
    switch ( err&0xffff ) {
        case EAGAIN:
            putq(q, mp);
            return;
        case EOPNOTSUPP:
            putnext(q, mp);
            return;
    }
    freemsg(mp);
}

#define SDL_SUPQ_MAXLEN 20

static void
sdl_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 = dev_m_data(q, mp)) ) break;
                continue;
            case M_CTL:
            case M_PROTO:
            case M_PCPROTO:
                if ( (err = dev_m_proto(q, mp)) ) break;
                continue;
            default:
                err = EOPNOTSUPP;
        }
        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);
    }
}

/*
 *  =======================================================================
 *
 *  SDT Driver Implemenation
 *
 *  =======================================================================
 */

static lmi_driver_t *sdl_drivers = NULL;

static struct lmi *
sdl_drv_attach(dev_t dev)
{
    lmi_t *sdl;

    DTRACE;
    MOD_INC_USE_COUNT;
    if ( !(sdl = lmi_drv_attach(dev, sdl_drivers, sizeof(sdl_t))) )
        MOD_DEC_USE_COUNT;

    return sdl;
}

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

static int
sdl_drv_close(struct lmi *sdl)
{
    int err;
    sdl_t *p = (sdl_t *)sdl;

    DTRACE;
    if ( !(err = lmi_drv_close(sdl,
                    (sdl_ulong *)NULL,       SDL_MAX_TIMERIDS,
                    (sdl_ulong *)&p->bufids, SDL_MAX_BUFCALLS)) )
        MOD_DEC_USE_COUNT;
    return(err);
}

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

static struct lmi_ops sdl_lmi_ops = 
{
    {
        sdl_drv_attach, /* dev.attach   */
        sdl_drv_open,   /* dev.open     */
        sdl_drv_close   /* dev.close    */
    },
    {
        lmi_info,       /* lmi.info     */
        lmi_attach,     /* lmi.attach   */
        lmi_detach,     /* lmi.detach   */
        lmi_enable,     /* lmi.enable   */
        lmi_disable,    /* lmi.disable  */
        sdl_do_ioctl    /* lmi.ioctl    */
    }
};

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

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

    if ( (err = lmi_open(q, devp, flag, sflag, crp, sdl_drivers, sizeof(sdl_t))) )
        return(err);
    sdl = (sdl_t *)q->q_ptr;
    sdl->ucalls = &sdl_mod_ucalls;
    if ( WR(q)->q_next ) sdl->dcalls = &sdl_mod_dcalls;
    else                 sdl->dcalls = &sdl_drv_dcalls;
    return(0);
}

static int
sdl_close(queue_t *q, int flag, cred_t *crp)
{
    int err;
    sdl_t *sdl = (sdl_t *)q->q_ptr;

    DTRACE;
    err = lmi_close(q, flag, crp,
            (sdl_ulong *)NULL,         SDL_MAX_TIMERIDS,
            (sdl_ulong *)&sdl->bufids, SDL_MAX_BUFCALLS);
    return(err);
}

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


int
sdl_register_driver(major_t cmajor, int nminor, char *name,
        lmi_ops_t *ops, dev_dcalls_t *dcalls)
{
    int err;

    DTRACE;
    MOD_INC_USE_COUNT;
    err = lmi_register_driver(
            &sdl_drivers, cmajor, &sdl_info, nminor, name,
            ops, dcalls, &sdl_dev_ucalls );
    if ( err < 0 )
        MOD_DEC_USE_COUNT;
    return(err);
}

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

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

static int sdl_initialized = 0;

#ifndef LIS_REGISTERED
static inline void sdl_init(void)
#else
__initfunc(void sdl_init(void))
#endif
{
    DTRACE;
    if ( sdl_initialized ) return;
    sdl_initialized = 1;
    printk(KERN_INFO SDL_BANNER);   /* console splash */
#if 0
#ifndef LIS_REGISTERED
    if ( !(sdl_minfo.mi_idnum = lis_register_strmod(&sdl_info, sdl_minfo.mi_idname)) ) {
        cmn_err(CE_NOTE, "sdl: couldn't register as module\n");
    }
#endif
    if ( sdt_register_driver(SDL_CMAJOR, SDL_NMINOR, "sdl",
                &sdl_lmi_ops, &sdl_drv_ops) ) {
        cmn_err(CE_NOTE, "sdl: couldn't register as driver\n");
    }
#else
    (void)sdl_lmi_ops;
    (void)sdl_drv_ops;
    (void)sdl_dev_ops;
#endif
};

#ifndef LIS_REGISTERED
static inline void sdl_terminate(void)
#else
__initfunc(void sdl_terminate(void))
#endif
{
    DTRACE;
    if ( !sdl_initialized ) return;
    sdl_initialized = 0;
#if 0
#ifndef LIS_REGSITERED
    if (sdl_minfo.mi_idnum)
        if ( (sdl_minfo.mi_idnum = lis_unregister_strmod(&sdl_info)) ) {
            cmn_err(CE_WARN, "sdl: couldn't unregister as module!\n");
        }
#endif
    if ( sdt_unregister_driver(SDL_CMAJOR) ) {
        cmn_err(CE_WARN, "sdl: couldn't unregister as driver!\n");
    }
#endif
};

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

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

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

