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

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

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

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

/*
 *  This is a skeleton SDTI driver implementation.  It can be used for
 *  developing SDTI compliant drivers for use with the sl module for the SS7
 *  Level 2 (LINK) state machine.
 */

#define SDT_DESCRIP   "SS7/SDT: SS7 SDT (SIGNALLING DATA TERMINAL) STREAMS DRIVER."
#define SDT_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SDT_DEVICES   "Supports all SDTI capable devices."
#define SDT_CONTACT   "Brian F. G. Bidulock <bidulock@openss7.org>"
#define SDT_BANNER    SDT_DESCRIP   "\n" \
                      SDT_COPYRIGHT "\n" \
                      SDT_DEVICES   "\n" \
                      SDT_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(SDT_CONTACT);
MODULE_DESCRIPTION(SDT_DESCRIP);
MODULE_SUPPORTED_DEVICES(SDT_DEVICES);
#endif

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

/*
 * ============================================
 *
 *  STREAMS stuff.
 *
 * ============================================
 */

#define SDT_DEVICE       "sdt"
#define SDT_MIN_PDU      3
#define SDT_MAX_PDU      277
#define SDT_MOD_NUMBER   0x1111
#define SDT_W_HIWATER    1
#define SDT_W_LOWATER    0
#define SDT_R_HIWATER    1
#define SDT_R_LOWATER    0

typedef enum retval
{
    DONE,
    RETRY,
    ERR
} retval_t;

static struct module_info sdt_r_minfo =
{
    SDT_MOD_NUMBER,     /* Module ID number             */
    "sdt",              /* Module name                  */
    SDT_MIN_PDU,        /* Min packet size accepted     */
    SDT_MAX_PDU,        /* Max packet size accepted     */
    SDT_R_HIWATER,      /* Hi water mark                */
    SDT_R_LOWATER       /* Lo water mark                */
};

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

static struct qinit sdt_rinit =
{
    sdt_rput,     /* qi_putp   */ /* Read put (message from below)  */
    sdt_rsrv,     /* qi_srvp   */ /* Read queue service             */
    sdt_open,     /* qi_qopen  */ /* Each open                      */
    sdt_close,    /* qi_qclose */ /* Last close                     */
    NULL,         /* qi_qadmin */ /* Admin (not used)               */
    &sdt_r_minfo, /* qi_minfo  */ /* Information                    */
    NULL          /* qi_mstat  */ /* Statistics                     */
};

static struct module_info sdt_w_minfo =
{
    SDT_MOD_NUMBER,     /* Module ID number             */
    "sdt",              /* Module name                  */
    SDT_MIN_PDU,        /* Min packet size accepted     */
    SDT_MAX_PDU,        /* Max packet size accepted     */
    SDT_W_HIWATER,      /* Hi water mark                */
    SDT_W_LOWATER       /* Lo water mark                */
};

static void sdt_wput(queue_t *, mblk_t *);

static struct qinit sdt_rinit =
{
    sdt_wput,     /* qi_putp   */ /* Write put (message from above) */
    NULL,         /* qi_srvp   */ /* Write queue service            */
    NULL,         /* qi_qopen  */ /* Each open                      */
    NULL,         /* qi_qclose */ /* Last close                     */
    NULL,         /* qi_qadmin */ /* Admin (not used)               */
    &sdt_r_minfo, /* qi_minfo  */ /* Information                    */
    NULL          /* qi_mstat  */ /* Statistics                     */
};

#ifdef MODULE
static
#endif
struct streamtab sdt_info =
{
    sdt_rinit,  /* Upper read  queue                */
    sdt_winit,  /* Upper write queue                */
    NULL,       /* Lower read  queue (not a mux)    */
    NULL        /* Lower write queue (not a mux)    */
};

typedef struct {
    queue_t         *rq;        /* read  queue              */
    queue_t         *wq;        /* write queue              */
    sdt_ulong       state;      /* interface state          */
    sdt_ulong       flags;      /* flags for whatever       */
    sdt_config_t    config;     /* configuration parameters */
    sdt_statem_t    statem;     /* state machine variables  */
    sdt_device_t    dev;        /* device structure         */
} sdt_t;

#define SDT_N_MINOR 16 /* for now */
sdt_t sdts[SDT_N_MINOR];

static int sdt_initialized = 0;

void sdt_init(void)
{
    if ( sdt_initialized ) return;
    printk(KERN_INFO SDT_BANNER);   /* console splash */
    sdt_initialized = 1;
    /*
     *  Do some groovy initializations...
     */
};

void sdt_terminate(void)
{
    if ( !sdt_initialized ) return;
    sdt_initialized = 0;
    /*
     *  Do some cleanup
     */
};

#ifndef SDT_PROBE
#define SDT_PROBE(i) 1  /* This must probe for device i */
#endif
#ifndef SDT_OPEN
#define SDT_OPEN(i)  0  /* This must open device i */
#endif

static int sdt_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
    dev_t i;
    queue_t *rq;
    int ret;

    if (sflag == MODOPEN)
        return EINVAL;                  /* not a module */
    if (q->q_ptr != NULL)
        return 0;                       /* already open */
    if (slfag == CLONEOPEN)
    {
        if ( WR(q)->q_next != NULL)
            return EINVAL;              /* not a module */
        for ( i = 0; i < SDT_N_MINOR; i++ )
            if ( sdts[i].rq == NULL && SDT_PROBE(i) )
                break;
        if ( i >= SDT_N_MINOR )
            return ENXIO;
        *devp = makedevice(getmajor(*devp), i);
    }
    else
    {
        if ( (i = getminor(*devp)) >= SDT_N_MINOR)
            return ENXIO;
    }
#ifdef MODULE
    MOD_INC_USE_COUNT;
#endif
    /*
     *  Whether we have a specific device or have selected one
     *  with a clone operation, we have to go about acquiring
     *  the io region, IRQ and stuff like that.  This should be
     *  the same as the Linux Kernel driver's open routine.
     */
    if ( (ret = SDT_OPEN(i)) != 0 )
        return ret;

    sdts[i].rq = q;
    sdts[i].wq = WR(q);
    sdts[i].state = SDT_DISABLED;    /* for Style 1 PPA */
    sdts[i].flags = 0;
    sdts[i].sl = NULL;
    q->q_ptr = WR(q)->q_ptr = &sdts[i];
    return 0;
}

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

    assert(sdt != NULL);
    assert(sdt->rq == q);

    sdt->rq = NULL;
    sdt->wq = NULL;
    sdt->state = SDT_UNATTACHED;
    sdt->flags = 0;
    sdt->sl = NULL;
    q->q_ptr = WR(q)->q_ptr = NULL;
#ifdef MODULE
    MOD_DEC_USE_COUNT;
#endif
    return 0;
}

/*
 * ------------------------------------
 *
 *  IOCTLS
 *
 * ------------------------------------
 */

static inline int sdt_ioc_nak(sdt_t *sdt, mblk_t *mp)
{
    mp->b_datap->db_type = M_IOCNAK;
    qreply(sdt->wq, mp);
    return DONE;
}

static inline int sdt_ioc_ack(std_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    mp->b_datap->db_type = M_IOCACK;
    iocp->ioc_error = 0;
    iocp->ioc_rval  = 0;
    qreply(sdt->wq, mp);
    return DONE;
}


/*
 *  This is a dummy ioctl service routine...
 */
static retval_t sdt_ioc_control_X(sdt_t *sdt, struct ioblk *iocp, mblk_t *mp)
{
    (void)iocp;
    mp->b_datap->db_type = M_IOCNAK;
    qreply(sdt->wq, mp);
    return ERR;
}

/*
 *  SDT_IOCRESET: Do a master reset on device.
 */
static retval_t sdt_iocreset(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCRESET);
}

/*
 *  SDT_IOCSIFMODE: Set interface mode.
 */
static retval_t sdt_iocsifmode(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    int psw;
    mblk_t *md;
    struct sdt_ifmode_ioctl_t *arg;

    assert(iocp->ioc_cmd == SDT_IOCSIFMODE);
    if (mp->b_cont == NULL || mp->b_cont->b_datap->db_type != M_DATA)
        return sdt_ioc_nak(sdt, mp);
    if (iocp->ioc_count != sizeof(sdt_ifmode_ioctl_t))
        return sdt_ioc_nak(sdt, mp);
    md = mp->b_cont;
    assert(md->b_wptr - md->d_rptr >= sizeof(sdt_ifmode_ioctl_t));
    arg = (sdt_ifmode_ioctl_t *)md->b_rptr;

    if ( sdt->dev.ifmode != arg->ifmode ) {
        SPLSTR(psw);
        sdt->dev.ifmode = arg->ifmode;
        /*
         *  Actually changed the mode of the interface.
         */
        SPLX(psw);
    }
    sdt_ioc_ack(sdt, iocp, mp);
}

/*
 *  SDT_IOCGIFMODE: Get interface mode.
 */
static retval_t sdt_iocgifmode(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    mblk_t *md;
    sdt_ifmode_ioctl_t *arg;

    assert(iocp->ioc_cmd == SDT_IOCSIFMODE);
    if (mp->b_cont == NULL || mp->b_cont->b_datap_db_type != M_DATA)
        return sdt_ioc_nak(sdt, mp);
    if (iocp->ioc_count != sizeof(sdt_ifmode_ioctl_t))
        return sdt_ioc_nak(sdt, mp);

    if ( (md = allocb(sizeof(sdt_ifmode_ioctl_t)) == NULL ) )
        return RETRY;
    md = mp->b_cont;
    assert(md->b_wptr - md->d_rptr >= sizeof(sdt_ifmode_ioctl_t));
    arg = (sdt_ifmode_ioctl_t *)md->b_rptr;

    if ( sdt->dev.ifmode != arg->ifmode ) {
        SPLSTR(psw);
        sdt->dev.ifmode = arg->ifmode;
        /*
         *  Actually changed the mode of the interface.
         */
        SPLX(psw);
    }
    sdt_ioc_ack(sdt, iocp, mp);
}

    



}

/*
 *  SDT_IOCSIFCLOCK: Set interface clock.
 */
static retval_t sdt_iocsifclock(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCSIFCLOCK);
}

/*
 *  SDT_IOCGIFCLOCK: Get interface clock.
 */
static retval_t sdt_iocgifclock(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCGIFCLOCK);
}

/*
 *  SDT_IOCSIFLEAD: Set interface leads.
 */
static retval_t sdt_iocsiflead(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCSIFLEAD);
}

/*
 *  SDT_IOCGIFLEAD: Get interface leads.
 */
static retval_t sdt_iocgiflead(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCGIFLEAD);
}

/*
 *  SDT_IOCCBRKTXLINE: Break Tx line.
 */
static retval_t sdt_ioccbrktxline(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCCBRKTXLINE);
}

/*
 *  SDT_IOCCCONTXLINE: Connect Tx line.
 */
static retval_t sdt_iocccontxline(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCCCONTXLINE);
}

/*
 *  SDT_IOCCIFSTATS: Clear interface statistics.
 */
static retval_t sdt_ioccifstats(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCCIFSTATS);
}

/*
 *  SDT_IOCSIFSTATSP: Set statistics aggregation period.
 */
static retval_t sdt_iocsifstatsp(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCSIFSTATSP);
}

/*
 *  SDT_IOCGIFSTATS: Get interface statistics.
 */
static retval_t sdt_iocgifstats(sdt_t *sdt, struct iocblk *iocp, mblk_t *mp)
{
    assert(iocp->ioc_cmd == SDT_IOCGIFSTATS);
}


typedef retval_t (*sdt_ioc_ops_t[])(sdt_t *, struct ioblk *, mblk_t *);

static sdt_ioc_ops_t sdt_ioc_ops = {
    sdt_iocreset,       /* SDT_IOCRESET      */
    sdt_iocsifmode,     /* SDT_IOCSIFMODE    */
    sdt_iocgifmode,     /* SDT_IOCGIFMODE    */
    sdt_iocsifclock,    /* SDT_IOCSIFCLOCK   */
    sdt_iocgifclock,    /* SDT_IOCGIFCLOCK   */
    sdt_iocsiflead,     /* SDT_IOCSIFLEAD    */
    sdt_iocgiflead,     /* SDT_IOCGIFLEAD    */
    sdt_ioccbrktxline,  /* SDT_IOCCBRKTXLINE */
    sdt_iocccontxline,  /* SDT_IOCCCONTXLINE */
    sdt_ioccifstats,    /* SDT_IOCCIFSTATS   */
    sdt_iocsifstatsp,   /* SDT_IOCSIFSTATSP  */
    sdt_iocgifstats     /* SDT_IOCGIFSTATS   */
};

/*
 *  IOCTLs to perform implementation-specific functions on the device.  We
 *  probably want to define a few generic IOCTLs here and then pass the rest
 *  transparently on to the actual device code.
 *
 *  Some IOCTLs which are fairly generic are those that control V.35 modem
 *  controls.  Also, clocking modes on both V.35 and E1/T1/J1 connections.
 */
static inline retval_t sdt_m_ioctl(queue_t *q, mblk_t *mp)
{
    int iocmd;
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    struct iocblk *iocp = (struct iocblk *)mp->b_rptr;

    assert(sdt  != NULL);
    assert(mp   != NULL);
    assert(iocp != NULL);
    assert(mp->b_datap->db_type == M_IOCTL);

    iocmd = ((iocp->ioc_cmd) & 0xffff) - ('t' << 8);

    if ( (iocmd & 0xff00 == 0) && iocmd >= 0 && iocmd < sizeof(sdt_ioc_ops)/sizeof((*)()) )
        return sdt_ioc_ops[iocmd](sdt, iocp, mp);
    /*
     *  Do final upstream M_IOCNAK on unrecognized ioctls.
     */
    mp->b_datap->db_type = M_IOCNAK;
    qreply(sdt->wq, mp);
    return ERR;
}

/*
 * ------------------------------------
 *
 *  Service Primitives (M_PROTO and M_PCPROTO)
 *
 * ------------------------------------
 */

static retval_t
sdt_r_error_ack(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp,
        int errno, int reason)
{
    req->error_ack.sdt_error_primitive  = req->sdt_primitive;
    req->error_ack.sdt_primitive        = SDT_ERROR_ACK;
    req->error_ack.sdt_state            = sdt->state;
    req->error_ack.sdt_errno            = errno;
    req->error_ack.sdt_reason           = reason;
    req->error_ack.sdt_mod_state        = 0;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_error_ack_t);
    qreply(sdt->wq, mp);
    return ERR;
}

static retval_t
sdt_r_ok_ack(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    req->ok_ack.sdt_correct_primitive   = req->sdt_primitive;
    req->ok_ack.sdt_primitive           = SDT_OK_ACK;
    req->ok_ack.sdt_state               = sdt->state;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_ok_ack_t);
    qreply(sdt->wq, mp);
    return DONE;
}

static inline retval_t
sdt_r_enable_con(std_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    req->enable_con.sdt_primitive   = SDT_ENABLE_CON;
    req->enable_con.sdt_state       = sdt->state;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_enable_con_t);
    qreply(sdt->wq, mp);
    return DONE;
}

static inline retval_t
sdt_r_disable_con(std_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    req->disable_con.sdt_primitive   = SDT_ENABLE_CON;
    req->disable_con.sdt_state       = sdt->state;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_disable_con_t);
    qreply(sdt->wq, mp);
    return DONE;
}

/*
 *  SDT_INFO_REQ:  This primitive is used by local management to determine
 *  several characteristics about the device.  Of primary interest to the
 *  stack manager is the type of device (whether it matches configuration) and
 *  perhaps the settings of state machine parameters.  Perhaps it is of more
 *  use for reporting this information than anything else.  We should also
 *  return statistics, but I will define those later.
 *
 *  Bit rate is useful for local management for calculating the settings of
 *  other rate-related parameters in the stack.  For Style 2 devices it is
 *  only valid if the link is attached.
 */
static retval_t sdt_p_info_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    req->info_ack.sdt_primitive = SDT_INFO_ACK;
    req->info_ack.sdt_state     = sdt->state;
    req->info_ack.sdt_max_sdu   = 277;              /* except HSL */
    req->info_ack.sdt_mim_sdu   = 3;                /* except HSL */
    req->info_ack.sdt_ppa_style = SDT_STYLE1;       /* for example */
    req->info_ack.sdt_bitrate   = 56000;            /* for example */
    req->info_ack.sdt_m         = sdt->config.M;
    req->info_ack.sdt_tin       = sdt->config.Tin;
    req->info_ack.sdt_tie       = sdt->config.Tie;
    req->info_ack.sdt_t         = sdt->config.T;
    req->info_ack.sdt_d         = sdt->config.D;
    req->info_ack.sdt_n         = sdt->config.N;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_info_ack_t);
    qreply(sdt->wq, mp);
    return DONE;
}

/*
 *  SDT_CONFIG_REQ:  This primitive is used by local management to configure
 *  the device before enabling it.  This should be done for Style 2 devices
 *  after attaching the device.
 *  device is attached, but for a Style 2 device should only be done after
 */
static retval_t sdt_p_config_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->state != DISABLED )
        return sdt_r_error_ack(sdt, req, mp, SDT_OUTSTATE, 0);
    sdt->config.M   = req->config_req.sdt_m;
    sdt->config.Tin = req->config_req.sdt_tin;
    sdt->config.Tie = req->config_req.sdt_tie;
    sdt->config.T   = req->config_req.sdt_t;
    sdt->config.D   = req->config_req.sdt_d;
    sdt->config.N   = req->config_req.sdt_n;
    req->sdt_primitive = SDT_CONFIG_ACK;
    mp->b_wptr = mp->b_rptr + sizeof(sdt_config_ack_t);
    qreply(sdt->wq, mp);
    return DONE;
}

/*
 *  SDT_ATTACH_REQ:  This only applies to Style 2 devices, where the SDT needs
 *  to be attached to a SDL (Signalling Data Link) before it is usable.  These
 *  devices have their own specific needs and will be addressed in another
 *  example.
 */
static retval_t sdt_p_attach_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->state != SDT_UNATTACHED )
        return sdt_r_error_ack(sdt, req, mp, SDT_OUTSTATE, 0);
    return sdt_r_error_ack(sdt, req, mp, SDT_NOTSUPP, 0);
    sdt->state = SDT_DISABLED;
    return sdt_r_ok_ack(sdt, req, mp);
}

/*
 *  SDT_DETACH_REQ:  This only applies to Style 2 devices, where the SDT needs
 *  detached from an SDL (Signalling Data Link) before it is unloaded.  These
 *  devices have their own specific needs and will be addressed in another
 *  example.
 */
static retval_t sdt_p_detach_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->state != SDT_DISABLED )
        return sdt_r_error_ack(sdt, req, mp, SDT_OUTSTATE, 0);
    return sdt_r_error_ack(sdt, req, mp, SDT_NOTSUPP, 0);
    sdt->state = SDT_UNATTACHED;
    return sdt_r_ok_ack(sdt, req, mp);
}

/*
 *  SDT_ENABLE_REQ:  This actually enables the device.  The device is not
 *  usable until this request has been satisfied.  Some device may take some
 *  time to enable and there is an SDT_ENABLE_PENDING state to permit this.
 *  In this example, the device enables trivially.
 *
 *  With SS7, SDTs are never powered up and down within the protocol.  For
 *  that reason, there are several initialization options whcih accompany the
 *  enable request.
 */
static retval_t sdt_p_enable_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->state != SDT_DISABLED )
        return sdt_r_error_ack(sdt, req, mp, SDT_OUTSTATE, 0);
    sdt->state = SDT_ENABLE_PENDING;
    switch (req->enable_req.sdt_rx_init ) {
        default:
        case SDT_RX_OFF:
            /*
             *  Do not attach the receivers to the line,
             *  disconnect them if they are attached.  Place
             *  the DAEDR into the SDT_STATE_IDLE state.
             */
            break;
        case SDT_RX_DISABLE:
            /*
             *  Attach the receivers to the line and track
             *  synchronization with flags.  Leave the 
             */
            break;
        case SDT_RX_NONE:
            /*
             *  Leave the receivers in their current state,
             *  whether that be initialized or disposed from
             *  the last disable request.
             */
            break;
    }
    switch (req->enable_req.sdt_rx_init ) {
        default:
        case SDT_TX_OFF:
            /*
             *  Do not attach the transmitters to the line,
             *  disconnect them if they are attached.  Place
             *  the DAEDT in to the SDT_STATE_IDLE state.
             */
            break;
        case SDT_TX_IDLE_MARK:
            /*
             *  Start up the TxISRs and set up to idle
             *  flags.  Do not back-enable queues.
             */
            break;
        case SDT_TX_IDLE_FLAG:
            /*
             *  Start up the TxISRs and set up to idle
             *  flags.  Do not back-enable queues.
             */
            break;
        case SDT_TX_IDLE_FISU:
            /*
             *  Start up the TxISRs and set up to idle FISU.
             *  Do not back-enable the queues.
             */
            break;
        case SDT_TX_IDLE_SIOS:
            /*
             *  Start up the TxISRs and set up to idle SIOS.
             *  Do not back-enable queues.
             */
            break;
        case SDT_TX_NONE:
            /*
             *  Leave the transmitters in their current
             *  state, whether that be initialized or
             *  disposed from the last disable request.
             */
            break;
    }
    sdt->state = SDT_ENABLED;
    return sdt_r_enable_con(sdt, req, mp);
}

/*
 *  SDT_DISABLE_REQ:  This disables the device.  The device is not usable once
 *  it is disabled.  Some devices may take some time to disable and may have
 *  to deal with various conditions before the device can be disabled.  There
 *  is a SDT_DISABLE_PENDING state to permit this.
 *
 *  With SS7, SDTs are never powered down within the protocol.  For that
 *  reason, there are several disposition options which accompany the disable
 *  request.
 */
static retval_t sdt_p_disable_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->state != SDT_ENABLED )
        return sdt_r_error_ack(sdt, req, mp, SDT_OUTSTATE, 0);
    sdt->state = SDT_DISABLE_PENDING;
    switch ( req->disable_req.sdt_rx_disp ) {
        default:
        case SDT_RX_OFF:
            /*
             *  Turn off the RxISR altogether.  Discard any
             *  frame which have been received or partially
             *  received and place the DAEDR into the
             *  SDT_STATE_IDLE state.
             */
            break;
        case SDT_RX_DISABLE:
            /*
             *  Leave the RxISR running.  Discard current
             *  and future frames as they are received.
             *  Make no indications to uppper layer.  Leave
             *  the DAEDR in the SDT_STATE_IN_SERVICE state.
             */
            break;
    }
    switch ( req->disable_req.sdt_tx_disp ) {
        default:
        case SDT_TX_OFF:
            /*
             *  Turn off the TxISR and disable the
             *  transmitters altogether.  Discard any
             *  partially sent frames and place the DAEDT
             *  into the SDT_STATE_IDLE state.
             */
            break;
        case SDT_TX_IDLE_MARK:
            /*
             *  Turn off the TxISR but allow transmitters to
             *  idle mark.  Discard any partially sent
             *  frames and place the DAEDT into the
             *  SDT_STATE_IDLE state.  (Note that tx will
             *  have to be made to idle flags if restarted.)
             */
            break;
        case SDT_TX_IDLE_FLAG:
            /*
             *  Turn off the TxISR but allow transmitters to
             *  idle flags.  Discard any partially sent
             *  frames and place the DEADT into the
             *  SDT_STATE_IDLE state.
             */
            break;
        case SDT_TX_IDLE_FISU:
            /*
             *  Leave the TxISR running and make
             *  arrangements for it to idle FISUs after it
             *  has delivered the current frame.  Leave the
             *  DAEDT in the SDT_STATE_IN_SERVICE state, but
             *  don't back-enable queues.
             */
            break;
        case SDT_TX_IDLE_SIOS:
            /*
             *  Leave the TxISR running and make
             *  arrangements for it to idle SIOS after it
             *  has delivered the current frame.  Leave the
             *  DAEDT in the SDT_STATE_IN_SERVICE state, but
             *  don't back-enable queues.
             */
            break;
        case SDT_TX_NONE:
            /*
             *  Leave the TxISR running, leave the DAEDT in
             *  SDT_STATE_IN_SERVICE and idle FISUs/LSSUs as
             *  normal after current frame, but don't
             *  back-enable queues.
             */
            break;
    }
    sdt->state = SDT_DISABLED;
    return sdt_r_disable_con(sdt, req, mp);
}

/*
 *  SDT_DAEDR_START_REQ
 *  Inspected to Figure 11/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_daedr_start_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.daedr_state == SDT_STATE_IDLE ) {
        /*
         *  We must actually activate the receivers here.
         *  Care should be taken to discover the state that
         *  the receivers were left in before we received
         *  the start command.
         *
         *  We might have to do something regardless of the
         *  state of the DAEDR.
         */
        sdt->statem.daedr_state = SDT_STATE_IN_SERVICE;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_DAEDT_START_REQ
 *  Inspected to Figure 12/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_daedt_start_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.daedt_state == SDT_STATE_IDLE ) {
        /*
         *  We must actually activate the transmitters here.
         *  Care should be taken to discover the state that
         *  the receivers were left in before we received
         *  the start command.
         *
         *  We might have to do something regardless of the
         *  state of the DAEDT.
         */
        sdt->statem.daedt_state = SDT_STATE_IN_SERVICE;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_DAEDT_SIGNAL_UNIT_REQ
 *  Inspected to Figure 11/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 *  (Note: data should be sent with M_DATA for speed instead of with this
 *  request.)
 */
static retval_t sdt_p_daedt_signal_unit_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    mblk_t *md;
    if ( (md = unlinkb(mp)) ) putq(sdt->wq, md);
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_AERM_START_REQ
 *  Inspected to Figure 17/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
*/
static retval_t sdt_p_aerm_start_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.aerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Ca = 0;
        sdt->statem.aerm_state = SDT_STATE_MONITORING;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_AERM_STOP_REQ
 *  Inspected to Figure 17/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_aerm_stop_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.aerm_state == SDT_STATE_MONITORING ) {
        sdt->statem.Ti = sdt->config.Tin;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_AERM_SET_TI_TO_TIN_REQ
 *  Inspected to Figure 17/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_aerm_set_ti_to_tin_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.aerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Ti = sdt->config.Tin;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_AERM_SET_TI_TO_TIE_REQ
 *  Inspected to Figure 17/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_aerm_set_ti_to_tie_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.aerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Ti = sdt->config.Tie;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_SUERM_START_REQ
 *  Inspected to Figure 18/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_suerm_start_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.suerm_state == SDT_STATE_IDLE ) {
        sdt->statem.Cs = 0;
        sdt->statem.Ns = 0;
        sdt->statem.suerm_state = SDT_STATE_IN_SERVICE;
    }
    freemsg(mp);
    return DONE;
}

/*
 *  SDT_SUERM_STOP_REQ
 *  Inspected to Figure 18/Q.703 (03/93), Q.703 (07/96), T1.111.3-1992
 */
static retval_t sdt_p_suerm_stop_req(sdt_t *sdt, union SDT_primitives *req, mblk_t *mp)
{
    if ( sdt->statem.suerm_state == SDT_STATE_IN_SERVICE ) {
        sdt->statem.suerm_state = SDT_STATE_IDLE;
    }
    freemsg(mp);
    return DONE;
}


typedef retval_t (*sdt_prim_ops_t[])(sdt_t *, union SL_primitive *, mblk_t *);

static sdt_prim_ops_t sdt_prim_ops = {
    sdt_p_info_req,                 /* SDT_INFO_REQ               */
    sdt_p_config_req,               /* SDT_CONFIG_REQ             */
    sdt_p_attach_req,               /* SDT_ATTACH_REQ             */
    sdt_p_detach_req,               /* SDT_DETACH_REQ             */
    sdt_p_enable_req,               /* SDT_ENABLE_REQ             */
    sdt_p_disable_req,              /* SDT_DISABLE_REQ            */
    sdt_p_daedr_start_req,          /* SDT_DAEDR_START_REQ        */
    sdt_p_daedt_start_req,          /* SDT_DAEDT_START_REQ        */
    sdt_p_daedt_signal_unit_req,    /* SDT_DAEDT_SIGNAL_UNIT_REQ  */
    sdt_p_aerm_start_req,           /* SDT_AERM_START_REQ         */
    sdt_p_aerm_stop_req,            /* SDT_AERM_STOP_REQ          */
    sdt_p_aerm_set_ti_to_tin_req,   /* SDT_AERM_SET_TI_TO_TIN_REQ */
    sdt_p_aerm_set_ti_to_tie_req,   /* SDT_AERM_SET_TI_TO_TIE_REQ */
    sdt_p_suerm_start_req,          /* SDT_SUERM_START_REQ        */
    sdt_p_suerm_stop_req            /* SDT_SUERM_STOP_REQ         */
};

static inline retval_t sdt_m_proto(queue_t *q, mblk_t *mp)
{
    int prim;
    sdt_t *sdt = (sdt_t *)q->q_ptr;
    union SL_primitive *req = (union SL_primitive *)mp->b_rptr;

    assert(sdt  != NULL);
    assert(mp   != NULL);
    assert(req  != NULL);

    prim = req->sdt_primitive;

    if ( prim >= 0 && prim < sizeof(sdt_prim_ops)/sizeof((*)()) )
        return sdt_prim_ops[prim](sdt, req, mp);
    /*
     *  Don't recognize this primitive...
     */
    mp->b_datap->db_type = M_PCPROTO;
    req->sdt_primitive = SDT_ERROR_ACK;
    /* FIXME: add reason code, state, etc. */
    qreply(sdt->rq, mp);
    return ERR;
}

/*
 * ------------------------------------
 *
 *  WRITE QUEUE put and service routines.
 *
 * ------------------------------------
 */

typedef retval_t (*sdt_prim_ops_t[])(sdt_t *, mblk_t *);

static sdt_prim_ops_t sdt_prim_ops = {
    sdt_p_info,         /* SDT_INFO_REQ         */
    sdt_p_attach,       /* SDT_ATTACH_REQ       */
    sdt_p_enable,       /* SDT_ENABLE_REQ       */
    sdt_p_disable,      /* SDT_DISABLE_REQ      */
};

/*
 *  We have a message being put from above.  If it is a protocol command or
 *  control, perform the function immediately, otherwise, queue data for the
 *  driver to read.  We don't have a service routine, the driver will take from
 *  the queue when it wants to.  Reading from the queue will backenable the
 *  queue and request another packet from the signalling link module.
 */
static void sdt_wput(queue_t *q, mblk_t *mp)
{
    int type = mp->b_datap->db_type;

    if ( mp->b_datap->db_type == M_DATA ) {     /* fast path */
        putq(q, mp);
        return;
    }
    switch ( mp->b_datap->db_type )
    {
        case M_PROTO:
        case M_PCPROTO:
            sdt_m_proto(q, mp);
            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:
            sdt_m_ioctl(q, mp);
            return;
    }
    freemsg(mp);
    return;
}

/*
 * ------------------------------------
 *
 *  READ QUEUE put and service routines.
 *
 * ------------------------------------
 */

/*
 *  We have a message put from below by the driver.  It will call this put
 *  routine.  This routine should simply queue the message and forward enable
 *  the service routine with an explicit qenable().  This will schedule to
 *  queue's service routing (outside the driver ISR) as a bottom half and will
 *  run the queue after the ISR returns.
 *
 *  Careful! This routine is called inside a driver ISR!
 */

static void sdt_rput(queue_t *q, mblk_t *mp)
{
    putq(q, mp);
    qenable(q);     /* maybe not necessary */
}

/*
 *  Read queue service routine: this will be called by STREAMS
 *  scheduler outside the driver as an immediate bottom-half.
 */
static void sdt_rsrv(queue_t *q)
{
    mblk_t *mp;

    while ( (mp = getq(q)) ) {
        if ( canputnext(q) )
            putnextq(q, mp);
        else {
            putbq(q, mp);
            return;
        }
    }
}


/*
 *-------------------------------------
 *
 *  State machine functions
 *
 *-------------------------------------
 */

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

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

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

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

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

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

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

static inline void sdt_suerm_su_in_error(sdt_t *sdt)
{
    if ( sdt->statem.suerm_state == SDT_STATE_IN_SERVICE ) {
        sdt->statem.Cs++;
        if ( sdt->statem.Cs >= sdt->config.T ) {
            sdt_lsc_link_failure(sdt, SDT_FAIL_SUERM); /**********/
            sdt->statem.suerm_state = SDT_STATE_IDLE;
            return;
        }
        if ( sdt->statem.Cs ) {
            sdt->statem.Ns++;
            if ( sdt->statem.Ns >= sdt->config.D ) {
                sdt->statem.Cs--;
                sdt->statem.Ns = 0;
            }
        }
    }
}

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

/*
 *  Note that the receivers (DAEDR) are not started for SS7 at power-on.  It
 *  is only after L2 receives the SDT_DAEDR_START primitive from the
 *  signalling link that the receivers are started.  Once started, however,
 *  the receivers are never stopped or reset.  In contrast, the transmitters
 *  (DAEDT) are started initially at power-on and are never restarted.
 *  Transmitters are supposed to send SIOS at power-on.
 */
static inline void sdt_daedr_start(sdt_t *sdt)
{
    if ( sdt->statem.daedr_state == SDT_STATE_IDLE ) {
        /*
         *  Perform the function which actually starts the
         *  receivers in the driver.
         */
        sdt->statem.daedr_state = SDT_STATE_IN_SERVICE;
    }
}

static inline void sdt_daedr_N_octets(sdt_t *sdt)
{
    if ( sdt->statem.daedr_state == SDT_STATE_IN_SERVICE ) {
        if ( sdt->statem.octet_counting_mode ) {
            sdt_suerm_su_in_error(sdt);
            sdt_aerm_su_in_error(sdt);
        }
    }
}

static inline void sdt_daedr_loss_of_sync(sdt_t *sdt)
{
    sdt->statem.octet_counting_mode = 1;
    /*
     *  Perform the function necessary to start octet
     *  indications.
     */
}

static inline void sdt_daedr_compressed_frame(sdt_t *sdt)
{
    if ( sdt->statem.daedr_state == SDT_STATE_IN_SERVICE ) {
        if ( sdt->statem.aborted_proving ) {
            sdt_iac_correct_su(sdt);    /***************/
            sdt->statem.aborted_proving = 0;
        }
        sdt_suerm_correct_su(sdt);
    }
}

static inline void sdt_daedr_received_frame(sdt_t *sl, mblk_t *mp)
{
    if ( sdt->statem.daedr_state == SDT_STATE_IN_SERVICE ) {
        if ( sdt->statem.aborted_proving ) {
            sdt_iac_correct_su(sdt);    /***************/
            sdt->statem.aborted_proving = 0;
        }
        sdt_suerm_correct_su(sdt);
        sdt_rc_signal_unit(sdt, mp);  /*****************/
    }

}

static inline void sdt_daedt_start(sdt_t *sdt)
{
    if ( sdt->statem.daedt_state == SDT_STATE_IDLE) {
        /*
         *  Perform the function which actually starts the
         *  receives in the driver.
         */
        sdt->statem.daedt_state = SDT_STATE_IN_SERVICE;
    }
}

static inline void sdt_daedt_message_for_transmission(sdt_t *sdt, mblk_t *mp)
{
    putq(sdt->wq, mp);
}

static inline void sdt_daedt_transmission_request(sdt_t *sdt)
{
    if ( sdt->statem.daedt_state == SDT_STATE_IN_SERVICE ) {
        if ( sdt->wq->q_count ) {
            mblk_t *md;
            if ( (md = getq(sdt->wq)) ) {
                /*
                 *  Set up for transmission.
                 */
            }
        } else switch ( sdt->statem.tx_state ) {
            case SDT_TX_SENDING_MSU:
            case SDT_TX_SENDING_FISU:
            case SDT_TX_SENDING_LSSU:
        }
    }
}









