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

 @(#) sdt2.c,v SS7AlphaRelease(0.7) 2001/12/11 07:51:42

 -----------------------------------------------------------------------------

 Copyright (C) 2001  OpenSS7 Corporation <http://www.openss7.com>

 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.

 -----------------------------------------------------------------------------

 U.S. GOVERNMENT RESTRICTED RIGHTS.  If you are licensing this Software on
 behalf of the U.S. Government ("Government"), the following provisions apply
 to you.  If the Software is supplied by the Department of Defense ("DoD"), it
 is classified as "Commercial Computer Software" under paragraph 252.227-7014
 of the DoD Supplement to the Federal Acquisition Regulations ("DFARS") (or any
 successor regulations) and the Government is acquiring only the license rights
 granted herein (the license rights customarily provided to non-Government
 users).  If the Software is supplied to any unit or agency of the Government
 other than DoD, it is classified as "Restricted Computer Software" and the
 Government's rights in the Software are defined in paragraph 52.227-19 of the
 Federal Acquisition Regulations ("FAR") (or any success regulations) or, in
 the cases of NASA, in paragraph 18.52.227-86 of the NASA Supplement to the FAR
 (or any successor regulations).

 -----------------------------------------------------------------------------

 Last Modified 2001/12/11 07:51:42 by brian

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

#ident "@(#) sdt2.c,v SS7AlphaRelease(0.7) 2001/12/11 07:51:42"

static char const ident[] = "sdt2.c,v SS7AlphaRelease(0.7) 2001/12/11 07:51:42";

#include <linux/config.h>
#include <linux/version.h>
#include <linux/modversion.h>
#include <linux/module.h>

#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>
#include <sys/dki.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 "../debug.h"
#include "../bufq.h"

#define SDT_DESCRIP	"SIGNALLING DATA TERMINAL (SDT) STREAMS MODULE"
#define SDT_COPYRIGHT	"Copyright (c) 2001 Openss7 Corp.  All Rights Reserved."
#define SDT_DEVICE	"Part of the OpenSS7 Stack for LiS STREAMS"
#define SDT_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define SDT_BANNER	SDT_DESCRIP	"\n" \
			SDT_COPYRIGHT	"\n" \
			SDT_DEVICE	"\n" \
			SDT_CONTACT	"\n"

MODULE_AUTHOR(SDT_CONTACT);
MODULE_DESCRIPTION(SDT_DESCRIP);
MODULE_SUPPORTED_DEVICE(SDT_DEVICE);

#ifndef INT
#define INT void
#endif

typedef void (*bufcall_fnc_t)(long);

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

static struct module_info sdt_minfo =
{
	0,		/* Module ID number		*/
	"sdt",		/* Module name			*/
	0,		/* Min packet size		*/
	INFPSZ,		/* Max packet size		*/
	0,		/* Hi water mark		*/
	0		/* 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 INT sdt_rput (queue_t *, mblk_t *);
static INT sdt_wput (queue_t *, mblk_t *);

static struct qinit sdt_rinit =
{
	sdt_rput,	/* Read put (msg from below)	*/
	NULL,		/* Read queue service		*/
	sdt_open,	/* Each oepn			*/
	sdt_close,	/* Lase close			*/
	NULL,		/* Admin (not used)		*/
	&sdt_minfo,	/* Information			*/
	NULL		/* Statistics			*/
};

static struct qinit sdt_winit =
{
	sdt_wput,	/* Write put (msg from below)	*/
	NULL,		/* Write queues service		*/
	NULL,		/* Each open			*/
	NULL,		/* Last close			*/
	&sdt_minfo,	/* Information			*/
	NULL		/* Statistics			*/
};

static struct streamtab sdt_info =
{
	&sdt_rinit,	/* Upper read  queue		*/
	&sdt_winit,	/* Upper write queue		*/
	NULL,		/* Lower read  queue		*/
	NULL		/* Lower write queue		*/
};

/*
 *  =========================================================================
 *
 *  SDT Private Structure
 *
 *  =========================================================================
 */
typedef struct sdt
{
	queue_t		*rq;		/* read  queue			*/
	queue_t		*wq;		/* write queue			*/
	uint		i_state;	/* interface state		*/

	lmi_option_t	option;		/* protocol options		*/
	sdt_statem_t	statem;		/* state variables		*/
	sdt_config_t	config;		/* configuration		*/
	sdt_stats_t	stats;		/* statistics			*/
	sdt_stats_t	stamp;		/* timestamps			*/
}
sdt_t;

/*  
 *  =========================================================================
 *
 *  OUTPUT Events
 *
 *  =========================================================================
 */
/*
 *  -------------------------------------------------------------------------
 *
 *  SDT Provider -> SDT User Primitives
 *
 *  -------------------------------------------------------------------------
 */

/*
 *  SDT_RC_SIGNAL_UNIT_IND
 *  -----------------------------------
 */
static int
sdt_rc_signal_unit_ind(sdt_t *s, mblk_t *dp)
{
	mblk_t *mp;
	sdt_rc_signal_unit_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_RC_SIGNAL_UNIT_IND;
		mp->b_cont = dp;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_RC_CONGESTION_ACCEPT_IND
 *  -----------------------------------
 */
static int
sdt_rc_congestion_accept_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_rc_congestion_accept_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_RC_CONGESTION_ACCEPT_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_RC_CONGESTION_DISCARD_IND
 *  -----------------------------------
 */
static int
sdt_rc_congestion_discard_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_rc_congestion_discard_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_RC_CONGESTION_DISCARD_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_RC_NO_CONGESTION_IND
 *  -----------------------------------
 */
static int
sdt_rc_no_congestion_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_rc_no_congestion_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_RC_NO_CONGESTION_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_IAC_CORRECT_SU_IND
 *  -----------------------------------
 */
static int
sdt_iac_correct_su_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_iac_correct_su_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_IAC_CORRECT_SU_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_IAC_ABORT_PROVING_IND
 *  -----------------------------------
 */
static int
sdt_iac_abort_proving_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_iac_abort_proving_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_IAC_ABORT_PROVING_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_LSC_LINK_FAILURE_IND
 *  -----------------------------------
 */
static int
sdt_lsc_link_failure_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_lsc_link_failure_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_LSC_LINK_FAILURE_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDT_TXC_TRANSMISSION_REQUEST_IND
 *  -----------------------------------
 */
static int
sdt_txc_transmission_request_ind(sdt_t *s)
{
	mblk_t *mp;
	sdt_txc_transmission_request_ind_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdt_primitive    = SDT_TXC_TRANSMISSION_REQUEST_IND;
		putnext(s->rq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  SDL User --> SDL Provider Primitives
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  SDL_DAEDT_TRANSMISSION_REQ
 *  -----------------------------------
 */
static int
sdl_daedt_transmission_req(sdt_t *s, mblk_t *dp)
{
	mblk_t *mp;
	sdl_daedt_transmission_req_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdl_primitive    = SDL_DAEDT_TRANSMISSION_REQ;
		mp->b_cont = dp;
		putnext(s->wq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDL_DAEDT_START_REQ
 *  -----------------------------------
 */
static int
sdl_daedt_start_req(sdt_t *s)
{
	mblk_t *mp;
	sdl_daedt_start_req_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdl_primitive    = SDL_DAEDT_START_REQ;
		putnext(s->wq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  SDL_DAEDR_START_REQ
 *  -----------------------------------
 */
static int
sdl_daedr_start_req(sdt_t *s)
{
	mblk_t *mp;
	sdl_daedr_start_req_t *p;
	if ( (mp = allocb(sizeof(*p), BPRI_MED)) )
	{
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p))mp->b_wptr)++;
		p->sdl_primitive    = SDL_DAEDR_START_REQ;
		putnext(s->wq, mp);
		return(0);
	} rare(); return(-ENOBUFS);
}
/*
 *  =========================================================================
 *
 *  SDT State Machine
 *
 *  =========================================================================
 */
static void
sdt_aerm_su_in_error(sdt_t *s)
{
	if ( s->statem.aerm_state == SDT_STATE_MONITORING )
	{
		s->statem.Ca++;
		if ( s->statem.Ca == s->statem.Ti )
		{
			s->statem.aborted_proving = 1;
			sdt_iac_abort_proving_ind(s);
			s->statem.aerm_state = SDT_STATE_IDLE;
		}
	}
}
static void
sdt_aerm_correct_su(sdt_t *s)
{
	if ( s->statem.aerm_state == SDT_STATE_IDLE )
	{
		if ( s->statem.aborted_proving )
		{
			sdt_iac_correct_su_ind(s);
			s->statem.aborted_proving = 0;
		}
	}
}
static void
sdt_suerm_start(sdt_t *s)
{
	s->statem.Cs = 0;
	s->statem.Ns = 0;
	s->statem.suerm_state = SDT_STATE_IN_SERVICE;
}
static void
sdt_suerm_stop(sdt_t *s)
{
	s->statem.suerm_state = SDT_STATE_IDLE;
}
static void
sdt_suerm_su_in_error(sdt_t *s) /* RxISR */
{
	if ( s->statem.suerm_state == SDT_STATE_IN_SERVICE )
	{
		s->statem.Cs++;
		if ( s->statem.Cs >= s->config.T )
		{
			sdt_lsc_link_failure_ind(s);
			s->statem.suerm_state = SDT_STATE_IDLE;
			return;
		}
		s->statem.Ns++;
		if ( s->statem.Ns >= s->config.D )
		{
			s->statem.Ns = 0;
			if ( s->statem.Cs )
				s->statem.Cs--;
		}
	}
}
static void
sdt_eim_su_in_error(sdt_t *s) /* RxISR */
{
	if ( s->statem.eim_state == SDT_STATE_MONITORING )
		s->statem.interval_error = 1;
}
static void
sdt_suerm_correct_su(sdt_t *s) /* RxISR */
{
	if ( s->statem.suerm_state == SDT_STATE_IN_SERVICE )
	{
		s->statem.Ns++;
		if ( s->statem.Ns >= s->config.D )
		{
			s->statem.Ns = 0;
			if ( s->statem.Cs )
				s->statem.Cs--;
		}
	}
}
static void
sdt_eim_correct_su(sdt_t *s) /* RxISR */
{
	if ( s->statem.eim_state == SDT_STATE_MONITORING )
		s->statem.su_received = 1;
}
static void sdt_t8_timeout(sdt_t *s);
static void
sdt_timer_stop_t8(sdt_t *s)
{
	if ( s->timers.t8 ) untimeout(s->timers.t8);
}
static void
sdt_timer_start_t8(sdt_t *s)
{
	sdt_timer_stop_t8(s);
	s->timers.t8 = timeout(
			(timo_fcn_t *)sdt_t8_timeout,
			(caddr_t)s, s->config.t8);
}
static void
sdt_t8_timeout(sdt_t *s)
{
	if ( s->statem.eim_state == SDT_STATE_MONITORING )
	{
		sdt_timer_start_t8(s);
#if 0   /* probably just limit compress count */
		if ( s->option.popt & SS7_POPT_PING )
		{
			int psw;            /* force frame once per t8 */
			SPLSTR(psw);
			s->device->ifflags &= ~SDT_IF_SU_REPEAT;
			SPLX(psw);
		}
#endif
		if (s->statem.su_received)
		{
			s->statem.su_received = 0;
			if ( !s->statem.interval_error )
			{
				if ( (s->statem.Ce -= s->config.De) < 0 )
					s->statem.Ce = 0;
				return;
			}
		}
		s->statem.Ce += s->config.Ue;
		s->statem.interval_error = 0;
		if ( s->statem.Ce > s->config.Te )
		{
			sdt_lsc_link_failure_ind(s); /*-upstream-*/
			s->statem.eim_state = SDT_STATE_IDLE;
		}
	}
}
static void
sdt_eim_start(sdt_t *s)
{
	s->statem.Ce = 0;
	s->statem.interval_error = 0;
	s->statem.su_received = 0;
	sdt_timer_start_t8(s);
	s->statem.eim_state = SDT_STATE_MONITORING;
}
static void
sdt_eim_stop(sdt_t *s)
{
	s->statem.eim_state = SDT_STATE_IDLE;
	sdt_timer_stop_t8(s);
}
static int
sdt_daedr_correct_su(sdt_t *s, int count)
{
	int i;
	for ( i=0; i<count; i++)
	{
		sdt_eim_correct_su(s);
		sdt_suerm_correct_su(s);
		sdt_aerm_correct_su(s);
	}
	return(0);
}
static int
sdt_daedr_su_in_error(sdt_t *s)
{
	sdt_suerm_su_in_error(s);
	sdt_aerm_su_in_error(s);
	return(0);
}
static int
sdt_daedr_received_bits(sdt_t *s, mblk_t *mp)
{
	sdt_rc_signal_unit_ind(s, mp);   /*-upstream-*/
	sdt_daedr_correct_su(s, 1);
	return(1);
}
static int
sdt_daedt_transmission_request(sdt_t *s)
{
	sdt_txc_transmission_request_ind(s); /*-upstream-*/
	return(0);
}
static int
sdt_aerm_set_ti_to_tin(sdt_t *s)
{
	if ( s->statem.aerm_state == SDT_STATE_IDLE )
		s->statem.Ti = s->config.Tin;
	return(0);
}
static int
sdt_aerm_set_ti_to_tie(sdt_t *s)
{
	if ( s->statem.aerm_state == SDT_STATE_IDLE )
		s->statem.Ti = s->config.Tie;
	return(0);
}
static int
sdt_aerm_start(sdt_t *s)
{
	s->statem.Ca = 0;
	s->statem.aborted_proving = 0;
	s->statem.aerm_state = SDT_STATE_MONITORING;
	return(0);
}
static int
sdt_aerm_stop(sdt_t *s)
{
	s->statem.aerm_state = SDT_STATE_IDLE;
	s->statem.Ti = s->config.Tin;
	return(0);
}
static int
sdt_daedt_start(sdt_t *s)
{
	if ( s->statem.daedt_state == SDT_STATE_IDLE )
	{
		sdl_daedt_start_req(s);
		s->statem.daedt_state = SDT_STATE_IN_SERVICE;
	}
	return(0);
}
static int
sdt_daedr_start(sdt_t *s)
{
	if ( s->statem.daedr_state == SDT_STATE_IDLE )
	{
		sdl_daedr_start_req(s);
		s->statem.daedr_state = SDT_STATE_IN_SERVICE;
	}
	return(0);
}
static int
sdt_daedt_transmission(sdt_t *s, mblk_t *mp)
{
	if ( s->statem.daedt_state != SDT_STATE_IDLE )
	{
		putnext(s->wq, mp);
		return(1);
	}
	return(0);
}
static int
sdt_suerm_eim_start(sdt_t *s)
{
	if ( s->option.popt & SS7_POPT_HSL )
		sdt_eim_start(s);
	else	sdt_suerm_start(s);
	return(0);
}
static int
sdt_suerm_eim_stop(sdt_t *s)
{
	sdt_eim_stop(s);
	sdt_suerm_stop(s);
	return(0);
}

/*
 *  =========================================================================
 *
 *  INPUT Events
 *
 *  =========================================================================
 */
/*
 *  -------------------------------------------------------------------------
 *
 *  SDT User -> SDT Provider Primtives
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  LMI_INFO_REQ
 *  -----------------------------------
 */
static int
lmi_info_req(sdt_t *s, mblk_t *mp)
{
	(void)mp;
	return lmi_info_ack(s);
}
/*
 *  LMI_ATTACH_REQ
 *  -----------------------------------
 */
static int
lmi_attach_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	lmi_attach_req_t *p = (lmi_attach_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
	if ( s->i_state == LMI_UNATTACHED ) { s->i_state == LMI_ATTACH_PENDING;
		lmi_error_ack(s, LMI_ATTACH_REQ, LMI_NOTSUPP);
	} seldom(); err = LMI_OUTSTATE;	    break;  /* would place interface out of state   */
	} seldom(); err = LMI_PROTOSHORT;   break;  /* M_PROTO block too short		    */
	} while(0); seldom(); return lmi_error_ack(s, LMI_ATTACH_REQ, err);
}
/*
 *  LMI_DETACH_REQ
 *  -----------------------------------
 */
static int
lmi_detach_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	lmi_detach_req_t *p = (lmi_detach_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
	if ( s->i_state == LMI_DISABLE ) { s->i_state = LMI_DETACH_PENDING;
		lmi_error_ack(s, LMI_DETACH_REQ, LMI_NOTSUPP);
	} seldom(); err = LMI_OUTSTATE;	    break;  /* would place interface out of state   */
	} seldom(); err = LMI_PROTOSHORT;   break;  /* M_PROTO block too short		    */
	} while(0); seldom(); return lmi_error_ack(s, LMI_DETACH_REQ, err);
/*
 *  LMI_ENABLE_REQ
 *  -----------------------------------
 */
static int
lmi_enable_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	lmi_enable_req_t *p = (lmi_enable_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
	if ( s->i_state == LMI_DISABLED ) { s->i_state = LMI_ENABLE_PENDING;
		return lmi_enable_con(s);
	} seldom(); err = LMI_OUTSTATE;	    break;  /* would place interface out of state   */
	} seldom(); err = LMI_PROTOSHORT;   break;  /* M_PROTO block too short		    */
	} while(0); seldom(); return lmi_error_ack(s, LMI_ENABLE_REQ, err);
}
/*
 *  LMI_DISABLE_REQ
 *  -----------------------------------
 */
static int
lmi_disable_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	lmi_disable_req_t *p = (lmi_disable_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
	if ( s->i_state == LMI_ENABLED || s->i_state == LMI_ENABLE_PENDING ) {
		if ( (err = sdt_stop(s)) )
			return(err);
		s->i_state = LMI_DISABLE_PENDING;
		return lmi_disable_con(s);
	} seldom(); err = LMI_OUTSTATE;	    break;  /* would place interface out of state   */
	} seldom(); err = LMI_PROTOSHORT;   break;  /* M_PROTO block too short		    */
	} while(0); seldom(); return lmi_error_ack(s, LMI_DISABLE_REQ, err);
}
/*
 *  LMI_OPTMGMT_REQ	
 *  -----------------------------------
 */
static int
lmi_optmgmt_req	(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	lmi_optmgmt_ack_t *p = (lmi_optmgmt_ack_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		lmi_error_ack(s, LMI_OPTMGMT_REQ, LMI_NOSUPP);
	} seldom(); err = LMI_PROTOSHORT;   break;  /* M_PROTO block too short		    */
	} while(0); seldom(); return lmi_error_ack(s, LMI_OPTMGMT_REQ, err);
}
/*
 *  SDT_DAEDT_TRANSMISSION_REQ
 *  -----------------------------------
 */
static int
sdt_daedt_transmission_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_daedt_transmission_req_t *p = (sdt_daedt_transmission_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		if ( !sdt_daedt_transmission(s, mp->b_cont) )
			mp->b_cont = NULL;
		return(0);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_DAEDT_TRANSMISSION_REQ, err);
}
/*
 *  SDT_DAEDT_START_REQ
 *  -----------------------------------
 */
static int
sdt_daedt_start_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_daedt_start_req_t *p = (sdt_daedt_start_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedt_start(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_DAEDT_START_REQ, err);
}
/*
 *  SDT_DAEDR_START_REQ
 *  -----------------------------------
 */
static int
sdt_daedr_start_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_daedr_start_req_t *p = (sdt_daedr_start_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedr_start(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_DAEDR_START_REQ, err);
}
/*
 *  SDT_AERM_START_REQ
 *  -----------------------------------
 */
static int
sdt_aerm_start_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_aerm_start_req_t *p = (sdt_aerm_start_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_aerm_start(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_AERM_START_REQ, err);
}
/*
 *  SDT_AERM_STOP_REQ
 *  -----------------------------------
 */
static int
sdt_aerm_stop_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_aerm_stop_req_t *p = (sdt_aerm_stop_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_aerm_stop(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_AERM_STOP_REQ, err);
}
/*
 *  SDT_AERM_SET_TI_TO_TIN_REQ
 *  -----------------------------------
 */
static int
sdt_aerm_set_ti_to_tin_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_aerm_set_ti_to_tin_req_t *p = (sdt_aerm_set_ti_to_tin_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_aerm_set_ti_to_tin(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_AERM_SET_TI_TO_TIN_REQ, err);
}
/*
 *  SDT_AERM_SET_TI_TO_TIE_REQ
 *  -----------------------------------
 */
static int
sdt_aerm_set_ti_to_tie_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_aerm_set_ti_to_tie_req_t *p = (sdt_aerm_set_ti_to_tie_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_aerm_set_ti_to_tie(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_AERM_SET_TI_TO_TIE_REQ, err);
}
/*
 *  SDT_SUERM_START_REQ
 *  -----------------------------------
 */
static int
sdt_suerm_start_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_suerm_start_req_t *p = (sdt_suerm_start_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_suerm_eim_start(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_SUERM_START_REQ, err);
}
/*
 *  SDT_SUERM_STOP_REQ
 *  -----------------------------------
 */
static int
sdt_suerm_stop_req(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdt_suer_stop_req_t *p = (sdt_suer_stop_req_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_suerm_eim_stop(s);
	} seldom(); err = LMI_PROTOSHORT;   break;
	} while(0); seldom(); return lmi_error_ack(s, SDT_SUERM_STOP_REQ, err);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  SDL Provider -> SDL User Primtives
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  SDL_DAEDR_RECEIVED_BITS_IND
 *  -----------------------------------
 */
static int
sdl_daedr_received_bits_ind(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdl_daedr_received_bits_ind_t *p = (sdl_daedr_received_bits_ind_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedr_received_bits(s, mp->b_cont);
	} seldom(); err = -EMSGSIZE;	    break;
	} while(0); seldom(); return(err);
}
/*
 *  SDL_DAEDR_CORRECT_SU_IND
 *  -----------------------------------
 */
static int
sdl_daedr_correct_su_ind(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdl_daedr_correct_su_ind_t *p = (sdl_daedr_correct_su_ind_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedr_correct_su(s, p->sdl_count);
	} seldom(); err = -EMSGSIZE;	    break;
	} while(0); seldom(); return(err);
}
/*
 *  SDL_DAEDR_SU_IN_ERROR_IND
 *  -----------------------------------
 */
static int
sdl_daedr_su_in_error_ind(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdl_daedr_su_in_serror_ind_t *p = (sdl_daedr_su_in_serror_ind_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedr_su_in_error(s);
	} seldom(); err = -EMSGSIZE;	    break;
	} while(0); seldom(); return(err);
}
/*
 *  SDL_DAEDT_TRANSMISSION_REQUEST_IND
 *  -----------------------------------
 */
static int
sdl_daedt_transmission_request_ind(sdt_t *s, mblk_t *mp)
{
	int err;
	size_t mlen = mp->b_wptr - mp->b_rptr;
	sdl_daedt_transmission_request_ind_t *p = (sdl_daedt_transmission_request_ind_t *)mp->b_rptr;
	do {
	if ( mlen >= sizeof(*p) ) {
		return sdt_daedt_transmission_request(s);
	} seldom(); err = -EMSGSIZE;	    break;
	} while(0); seldom(); return(err);
}

/*
 *  =========================================================================
 *
 *  STREAMS Message Handling
 *
 *  =========================================================================
 */
/*
 *  -------------------------------------------------------------------------
 *
 *  M_IOCDATA Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_iocdata(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(4);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_IOCTL Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_ioctl(queue_t *q, mblk_t *mp)
{
	sdt_t *s = (sdt_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;
	struct linkblk *lp = (struct linkblk *)arg;
	caddr_t *uaddr = (caddr_t *)arg;
	int ret	    = 0;
	int type    = _IOC_TYPE	(cmd);
	int nr	    = _IOC_NR	(cmd);
	int size    = _IOC_SIZE	(cmd);
	int dir	    = _IOC_DIR	(cmd);
	switch ( type )
	{
		case __SID:
			switch ( cmd )
			{
				default:
					ptrace(("Unknown IOCTL %#08x\n",cmd));
				case I_STR:
				case I_LINK:
				case I_UNLINK:
				case I_PLINK:
				case I_PUNLINK:
					rare();
					(void)lp;
					ret = -EOPNOTSUPP;
					break;
			}
			ret = -EOPNOTSUPP;
			break;
		case SDT_IOC_MAGIC:
			if ( count == TRANSPARENT )
				ret = -EINVAL;
			else if ( count >= size )
			{
				switch ( cmd )
				{
					case SDT_IOCGOPTIONS:
					case SDT_IOCSOPTIONS:
					case SDT_IOCGCONFIG:
					case SDT_IOCSCONFIG:
					case SDT_IOCTCONFIG:
					case SDT_IOCCCONFIG:
					case SDT_IOCGSTATEM:
					case SDT_IOCCMRESET:
					case SDT_IOCSSTATSP:
					case SDT_IOCGSTATS:
					case SDT_IOCCSTATS:
					case SDT_IOCGNOTIFY:
					case SDT_IOCSNOTIFY:
					case SDT_IOCCNOTIFY:
					default:
						ret = -EOPNOTSUPP;
						break;
				}
			}
			else ret = -EINVAL;
			break;
		default:
			return(4);
	}
	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 = -ret;
		iocp->ioc_rval	= -1;
	}
	qreply(q, mp);
	return(1);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_PROTO, M_PCPROTO Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_proto(queue_t *q, mblk_t *mp)
{
	int rtn;
	sdt_t *s = (sdt_t *)q->q_ptr;
	ulong oldstate = s->i_state;
	if ( mp->b_wptr - mp->b_rptr >= sizeof(long) ) {
	switch ( *((long *)mp->b_rptr) )
	{
		case LMI_INFO_REQ:			rtn = lmi_info_req		 (s, mp); break;
		case LMI_ATTACH_REQ:			rtn = lmi_attach_req		 (s, mp); break;
		case LMI_DETACH_REQ:			rtn = lmi_detach_req		 (s, mp); break;
		case LMI_ENABLE_REQ:			rtn = lmi_enable_req		 (s, mp); break;
		case LMI_DISABLE_REQ:			rtn = lmi_disable_req		 (s, mp); break;
		case LMI_OPTMGMT_REQ:			rtn = lmi_optmgmt_req		 (s, mp); break;
		case SDT_DAEDT_TRANSMISSION_REQ:	rtn = sdt_daedt_transmission_req (s, mp); break;
		case SDT_DAEDT_START_REQ:		rtn = sdt_daedt_start_req	 (s, mp); break;
		case SDT_DAEDR_START_REQ:		rtn = sdt_daedr_start_req	 (s, mp); break;
		case SDT_AERM_START_REQ:		rtn = sdt_aerm_start_req	 (s, mp); break;
		case SDT_AERM_STOP_REQ:			rtn = sdt_aerm_stop_req		 (s, mp); break;
		case SDT_AERM_SET_TI_TO_TIN_REQ:	rtn = sdt_aerm_set_ti_to_tin_req (s, mp); break;
		case SDT_AERM_SET_TI_TO_TIE_REQ:	rtn = sdt_aerm_set_ti_to_tie_req (s, mp); break;
		case SDT_SUERM_START_REQ:		rtn = sdt_suerm_start_req	 (s, mp); break;
		case SDT_SUERM_STOP_REQ:		rtn = sdt_suerm_stop_req	 (s, mp); break;
		default:				rtn = -EOPNOTSUPP;			  break;
	}
	} else { rare(); rtn = -EMSGSIZE; }
	if ( rtn < 0 ) { seldom(); sl->i_state = oldstate; }
	return(rtn);
}
static int
sdt_r_proto(queue_t *q, mblk_t *mp)
{
	int rtn;
	sdt_t *s = (sdt_t *)q->q_ptr;
	if ( mp->b_wptr - mp->b_rptr >= sizeof(long) ) {
	switch ( *((long *)mp->b_rptr) )
	{
		case LMI_INFO_ACK:			rtn = lmi_info_ack			(s, mp); break;
		case LMI_OK_ACK:			rtn = lmi_ok_ack			(s, mp); break;
		case LMI_ERROR_ACK:			rtn = lmi_error_ack			(s, mp); break;
		case LMI_ENABLE_CON:			rtn = lmi_enable_con			(s, mp); break;
		case LMI_DISABLE_CON:			rtn = lmi_disable_con			(s, mp); break;
		case LMI_OPTMGMT_ACK:			rtn = lmi_optmgmt_ack			(s, mp); break;
		case LMI_ERROR_IND:			rtn = lmi_error_ind			(s, mp); break;
		case LMI_STATS_IND:			rtn = lmi_stats_ind			(s, mp); break;
		case LMI_EVENT_IND:			rtn = lmi_event_ind			(s, mp); break;
		case SDL_DAEDR_RECEIVED_BITS_IND:	rtn = sdl_daedr_received_bits_ind	(s, mp); break;
		case SDL_DAEDR_CORRECT_SU_IND:		rtn = sdl_daedr_correct_su_ind		(s, mp); break;
		case SDL_DAEDR_SU_IN_ERROR_IND:		rtn = sdl_daedr_su_in_error_ind		(s, mp); break;
		case SDL_DAEDT_TRANSMISSION_REQUEST_IND:rtn = sdl_daedt_transmission_request_ind(s, mp); break;
		default:				rtn = -EOPNOTSUPP;				 break;
	}
	} else { rare(); rtn = -EMSGSIZE; }
	if ( rtn < 0 ) { rare(); }
	return(rtn);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_DATA Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_data(queue_t *q, mblk_t *mp)
{
	sdt_t *s = (sdt_t *)q->q_ptr;
	return sdt_daedt_transmission(s, mp);
}
static int
sdt_r_data(queue_t *q, mblk_t *mp)
{
	sdt_t *s = (sdt_t *)q->q_ptr;
	return sdt_daedr_received_bits(s, mp);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_FLUSH Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_flush(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(3);
}
static int
sdt_r_flush(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(3);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_ERROR Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_r_error(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(4);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  M_HANGUP Handling
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_r_hangup(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(4);
}
/*
 *  -------------------------------------------------------------------------
 *
 *  Other messages (e.g. M_IOCACK)
 *
 *  -------------------------------------------------------------------------
 */
static int
sdt_w_other(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(4);
}
static int
sdt_r_other(queue_t *q, mblk_t *mp)
{
	(void)q; (void)mp;
	return(4);
}

/*
 *  =========================================================================
 *
 *  PUT and SRV
 *
 *  =========================================================================
 */
/*
 *  Write PUT
 */
static INT
sdt_wput(queue_t *q, mblk_t *mp)
{
	int rtn;
	ensure( q,  return((INT)(-EFAULT)) );
	ensuer( mp, return((INT)(-EFAULT)) );
	switch ( mp->b_datap->db_type )
	{
		case M_DATA:	rtn = sdt_w_data    (q, mp); break;
		case M_PROTO:
		case M_PCPROTO:	rtn = sdt_w_proto   (q, mp); break;
		case M_FLUSH:	rtn = sdt_w_flush   (q, mp); break;
		case M_IOCTL:	rtn = sdt_w_ioctl   (q, mp); break;
		case M_IOCDATA:	rtn = sdt_w_iocdata (q, mp); break;
		default:	rtn = sdt_w_other   (q, mp); break;
	}
	switch ( rtn )
	{
		case 0:	freemsg(mp);
		case 1:	break;
		case 2:	freeb(mp);
			break;
		case 3:
		case 4:	putnext(q, mp);
			break;
		default:
			ptrace(("Error = %d\n", rtn));
			freemsg(mp);
			return(INT)(rtn);
	}
	return(INT)(0);
}
/*
 *  Read PUT
 */
static INT
sdt_rput(queue_t *q, mblk_t *mp)
{
	int rtn;
	ensure( q,  return((INT)(-EFAULT)) );
	ensuer( mp, return((INT)(-EFAULT)) );
	switch ( mp->b_datap->db_type )
	{
		case M_DATA:	rtn = sdt_r_data    (q, mp); break;
		case M_PROTO:
		case M_PCPROTO:	rtn = sdt_r_proto   (q, mp); break;
		case M_FLUSH:	rtn = sdt_r_flush   (q, mp); break;
		case M_ERROR:	rtn = sdt_r_error   (q, mp); break;
		case M_HANGUP:	rtn = sdt_r_hangup  (q, mp); break;
		default:	rtn = sdt_r_other   (q, mp); break;
	}
	switch ( rtn )
	{
		case 0:	freemsg(mp);
		case 1:	break;
		case 2:	freeb(mp);
			break;
		case 3:
		case 4:	putnext(q, mp);
			break;
		default:
			ptrace(("Error = %d\n", rtn));
			freemsg(mp);
			return(INT)(rtn);
	}
	return(INT)(0);
}
/*
 *  =========================================================================
 *
 *  Private Structure allocation, deallocation and cache
 *
 *  =========================================================================
 */
kmem_cache_t *sdt_cachep = NULL;

static void
sdt_init_caches(void)
{
	if ( !(sdt_cachep) )
	if ( !(sdt_cachep = kmem_find_general_cachep(sizeof(sdt_t))) )
	if ( !(sdt_cachep = kmem_cache_create("sdt_cachep", sizeof(sdt_t),
					0, SLAB_HWCACHE_ALIGN, NULL, NULL)) )
		panic(__FUNCTION__ ":Cannot alloc sdt_cachep\n");
	return;
}
static void
sdt_term_caches(void)
{
	/*
	 *  Don't attempt to shrink cache.
	 */
}
static sdt_t *
sdt_alloc_priv(queue_t *q)
{
	sdt_t *s;
	if ( (s = kmem_cache_alloc(st_cachep, SLAB_ATOMIC)) )
	{
		bzero(s, sizeof(*s));
		RD(q)->q_ptr = WR(q)->q_ptr = s;
		s->rq = RD(q);
		s->wq = WR(q);
		s->i_state = LMI_DISABLED;
	}
	return(s);
}
static void
sdt_free_priv(queue_t *q)
{
	sdt_t *s = (sdt_t *)q->q_ptr;
	untimeout(s->timer_t8);
	kmem_cache_free(sdt_cachep, s);
	return;
}

/*
 *  =========================================================================
 *
 *  OPEN and CLOSE
 *
 *  =========================================================================
 */
static int
sdt_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
	(void)crp;
	if ( q->q_ptr != NULL )	    /* already open */
		return(0);
	if ( sflag == MODOPEN || WR(q)->q_next != NULL )
	{
		fixme(("Check module we are being pushed over\n"));
		if ( !sdt_alloc_priv(q) )
			return ENOMEM;
		return(0);
	}
	return EIO;
}
static int
sdt_close(queue_t *q, int flag, cred_t *crp)
{
	(void)flag;
	(void)crp;
	sdt_free_priv(q);
	return(0);
}
/*
 *  =========================================================================
 *
 *  LiS Module Initialization
 *
 *  =========================================================================
 */
void
sdt_init(void)
{
	int modnum;
	unless( sdt_minfo.mi_idnum, return );
	cmn_err(CE_NOTE, SDT_BANNER);	/* console splash */
	sdt_init_caches();
	if ( !(modnum = lis_register_strmod(&sdt_info, sdt_minfo.mi_idnum)) )
	{
		sdt_minfo.mi_idnum = 0;
		cmn_err(CE_WARN, "sdt: couldn't register as module\n");
		return;
	}
	sdt_minfo.mi_idnum = modnum;
	return;
}
void
sdt_terminate(void)
{
	ensure( sdt_minfo.mi_idnum, return );
	if ( (sdt_minfo.mi_idnum = lis_register_strmod(&sdt_info)) )
		cmn_err(CE_WARN, "sdt: couldn't unregister module\n");
	sdt_term_caches();
	return;
}
/*
 *  =========================================================================
 *
 *  Kernel Module Initialization
 *
 *  =========================================================================
 */
int init_module(void)
{
	sdt_init();
	return(0);
}
void cleanup_module(void)
{
	sdt_terminate();
	return;
}
