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

 @(#) sl.c,v 0.7.8.1 2001/12/11 13:15:40 brian Exp

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

 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 13:15:40 by brian

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

#ident "@(#) sl.c,v 0.7.8.1 2001/12/11 13:15:40 brian Exp"

static char const ident[] = "SS7AlphaRelease(0.7.8.1) 2001/12/11 13:15:40";

/*
 *  This is an SL (Signalling Link) kernel module which provides all of the
 *  capabilities of the SLI.  It can be used by drivers in two fashions:
 *
 *  1)  Drivers can provide a set of missing functions by registering with the
 *	module using sdt_register_driver.  This relieves much of the STREAMS
 *	burden from the driver implementation and permits the driver to be
 *	more portable across the Linux, STREAMS, and BSD implementation of the
 *	OpenSS7 stack.
 *
 *  2)  Drivers which implement the OpenSS7 SDTI can push this module to form
 *	an SLI driver implementation.
 *
 *  Both fashions permit SS7 Level 2 compliant state machines to be tested
 *  once, but used by many drivers.
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/modversions.h>
#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>

#include "../debug.h"
#include "../bufq.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>
#include <ss7/devi.h>
#include <ss7/devi_ioctl.h>
#include <ss7/sdli.h>
#include <ss7/sdli_ioctl.h>
#include <ss7/sdti.h>
#include <ss7/sdti_ioctl.h>
#include <ss7/sli.h>
#include <ss7/sli_ioctl.h>

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

#define SL_DESCRIP	"SS7/SLI: (Signalling Link Interface) STREAMS DRIVER."
#define SL_COPYRIGHT	"Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define SL_DEVICES	"Supports OpenSS7 SL drivers."
#define SL_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define SL_BANNER	SL_DESCRIP	"\n" \
			SL_COPYRIGHT	"\n" \
			SL_DEVICES	"\n" \
			SL_CONTACT	"\n"

#ifdef MODULE
MODULE_AUTHOR(SL_CONTACT);
MODULE_DESCRIPTION(SL_DESCRIP);
MODULE_SUPPORTED_DEVICE(SL_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef SL_DEBUG
static int sl_debug = SL_DEBUG;
#else
static int sl_debug = 2;
#endif

#define DEBUG_LEVEL sl_debug

#define SL_CMAJOR 0
#define SL_NMINOR 255

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

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

static int  sl_open (queue_t *, dev_t *, int, int, cred_t *);
static int  sl_close(queue_t *, int, cred_t *);

static void sl_rput (queue_t *, mblk_t *);
static void sl_rsrv (queue_t *);

static struct qinit sl_rinit =
{
	sl_rput,	/* Read put (msg from below)	*/
	sl_rsrv,	/* Read queue service		*/
	sl_open,	/* Each open			*/
	sl_close,	/* Last close			*/
	NULL,		/* Admin (not used)		*/
	&sl_minfo,	/* Information			*/
	NULL		/* Statistics			*/
};

static void sl_wput (queue_t *, mblk_t *);
static void sl_wsrv (queue_t *);

static struct qinit sl_winit =
{
	sl_wput,	/* Write put (msg from above)	*/
	sl_wsrv,	/* Write queue service		*/
	NULL,		/* Each open			*/
	NULL,		/* Last close			*/
	NULL,		/* Admin (not used)		*/
	&sl_minfo,	/* Information			*/
	NULL		/* Statistics			*/
};

#ifndef LIS_REGISTERED
static
#endif
struct streamtab sl_info =
{
	&sl_rinit,	/* Upper read  queue		*/
	&sl_winit,	/* Upper write queue		*/
	NULL,		/* Lower read  queue		*/
	NULL		/* Lower write queue		*/
};

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

static int
sl_iocgoptions(sl_t *sl, int cmd, void *arg)
{
	bcopy(&sl->option, arg, _IOC_SIZE(cmd));
	return(0);
}

static int
sl_iocsoptions(sl_t *sl, int cmd, void *arg)
{
	bcopy(arg, &sl->option, _IOC_SIZE(cmd));
	if ( sl->option.popt & SS7_POPT_XSN )
	{
		sl->statem.sn_mask = 0x7fff;
		sl->statem.ib_mask = 0x8000;
	}
	else
	{
		sl->statem.sn_mask = 0x7f;
		sl->statem.ib_mask = 0x80;
	}
	return(0);
}

static int
sl_iocgconfig(sl_t *sl, int cmd, void *arg)
{
	bcopy(&sl->config, arg, _IOC_SIZE(cmd));
	return(0);
}

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

static int
sl_iocgstatem(sl_t *sl, int cmd, void *arg)
{
	bcopy(&sl->statem, arg, _IOC_SIZE(cmd));
	return(0);
}

static int
sl_iocsstatsp(sl_t *sl, int cmd, void *arg)
{
	(void)arg;
	return EOPNOTSUPP;
}

static int
sl_iocgstatsp(sl_t *sl, int cmd, void *arg)
{
	(void)arg;
	return EOPNOTSUPP;
}

static int
sl_ioccstats(sl_t *sl, int cmd, void *arg)
{
	(void)arg;
	bzero(&sl->stats, _IOC_SIZE(cmd));
	return(0);
}

static int
sl_iocgstats(sl_t *sl, int cmd, void *arg)
{
	bcopy(&sl->stats, arg, _IOC_SIZE(cmd));
	return(0);
}

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

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

static int
sl_iocgnotify(sl_t *sl, int cmd, void *arg)
{
	bcopy(&sl->notify, arg, _IOC_SIZE(cmd));
	return(0);
}

static int (*sl_ioc_lmiops [])(sl_t *, int, void *) =
{
	sl_iocgoptions,	/* SL_IOCGOPTIONS	*/
	sl_iocsoptions,	/* SL_IOCSOPTIONS	*/
	sl_iocgconfig,	/* SL_IOCGCONFIG	*/
	sl_iocsconfig,	/* SL_IOCSCONFIG	*/
	NULL,		/* SL_IOCTCONFIG	*/
	NULL,		/* SL_IOCCCONFIG	*/
	sl_iocgstatem,	/* SL_IOCGSTATEM	*/
	NULL,		/* SL_IOCCMRESET	*/
	sl_iocgstatsp,	/* SL_IOCGSTATSP	*/
	sl_iocsstatsp,	/* SL_IOCSSTATSP	*/
	sl_iocgstats,	/* SL_IOCGSTATS		*/
	sl_ioccstats,	/* SL_IOCCSTATS		*/
	sl_iocgnotify,	/* SL_IOCGNOTIFY	*/
	sl_iocsnotify,	/* SL_IOCSNOTIFY	*/
	sl_ioccnotify	/* SL_IOCCNOTIFY	*/
};

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

static inline void sl_uprim_link(sl_t *sl, int type, int prim, mblk_t *mp)
{
	mblk_t *md = mp;
	if ( mp->b_datap->db_type != M_DATA )
	{
		mp->b_wptr = mp->b_rptr;
		*((sl_long *)mp->b_rptr)++ = prim;
		md = mp->b_cont;
	}
	adjmsg(md, (sl->option.popt&SS7_POPT_XSN)?6:3); /* trim L2 header */
	putnext(sl->rq, mp);
}

static inline void sl_uprim(sl_t *sl, int type, int prim)
{
	mblk_t *mp;
	if ( (mp = allocb(sizeof(sl_long), BPRI_HI)) )
	{
		mp->b_datap->db_type = type;
		*((sl_long *)mp->b_wptr)++ = prim;
		putnext(sl->rq, mp);
	}
}

static inline void sl_uprim_arg(sl_t *sl, int type, int prim, int arg)
{
	mblk_t *mp;
	if ( (mp = allocb(2*sizeof(sl_long), BPRI_HI)) )
	{
		mp->b_datap->db_type = type;
		*((sl_long *)mp->b_wptr)++ = prim;
		*((sl_long *)mp->b_wptr)++ = arg;
		putnext(sl->rq, mp);
	}
}

static inline void sl_uprim_2arg(sl_t *sl, int type, int prim, int arg, int arg2)
{
	mblk_t *mp;
	if ( (mp = allocb(3*sizeof(sl_long), BPRI_HI)) )
	{
		mp->b_datap->db_type = type;
		*((sl_long *)mp->b_wptr)++ = prim;
		*((sl_long *)mp->b_wptr)++ = arg;
		*((sl_long *)mp->b_wptr)++ = arg2;
		putnext(sl->rq, mp);
	}
}

static inline void sl_uprim_3arg(sl_t *sl, int type, int prim, int arg, int arg2, int arg3)
{
	mblk_t *mp;
	if ( (mp = allocb(4*sizeof(sl_long), BPRI_HI)) )
	{
		mp->b_datap->db_type = type;
		*((sl_long *)mp->b_wptr)++ = prim;
		*((sl_long *)mp->b_wptr)++ = arg;
		*((sl_long *)mp->b_wptr)++ = arg2;
		*((sl_long *)mp->b_wptr)++ = arg3;
		putnext(sl->rq, mp);
	}
}

static inline void sl_l3_pdu(sl_t *sl, mblk_t *mp)
{
	sl_uprim_link(sl, M_PCPROTO, SL_PDU_IND, mp);
}
static inline void sl_l3_link_congested(sl_t *sl, int cong, int disc)
{
	sl_uprim_3arg(sl, M_PCPROTO, SL_LINK_CONGESTED_IND,
			jiffies, cong, disc);
}
static inline void sl_l3_link_congestion_ceased(sl_t *sl, int cong, int disc)
{
	sl_uprim_3arg(sl, M_PCPROTO, SL_LINK_CONGESTION_CEASED_IND,
			jiffies, cong, disc);
}
static inline void sl_l3_retrieved_message(sl_t *sl, mblk_t *mp)
{
	sl_uprim_link(sl, M_PROTO, SL_RETRIEVED_MESSAGE_IND, mp);
}
static inline void sl_l3_retrieval_complete(sl_t *sl)
{
	sl_uprim(sl, M_PCPROTO, SL_RETRIEVAL_COMPLETE_IND);
}
static inline void sl_l3_rb_cleared(sl_t *sl)
{
	sl_uprim(sl, M_PCPROTO, SL_RB_CLEARED_IND);
}
static inline void sl_l3_bsnt(sl_t *sl, int bsnt)
{
	sl_uprim_arg(sl, M_PCPROTO, SL_BSNT_IND, bsnt);
}
static inline void sl_l3_in_service(sl_t *sl)
{
	sl_uprim(sl, M_PCPROTO, SL_IN_SERVICE_IND);
}
static inline void sl_l3_out_of_service(sl_t *sl, int reason)
{
	sl_uprim_2arg(sl, M_PCPROTO, SL_OUT_OF_SERVICE_IND,
			jiffies, reason);
}
static inline void sl_l3_remote_processor_outage(sl_t *sl)
{
	sl_uprim_arg(sl, M_PCPROTO, SL_REMOTE_PROCESSOR_OUTAGE_IND,
			jiffies);
}
static inline void sl_l3_remote_processor_recovered(sl_t *sl)
{
	sl_uprim_arg(sl, M_PCPROTO, SL_REMOTE_PROCESSOR_RECOVERED_IND,
			jiffies);
}
static inline void sl_l3_rtb_cleared(sl_t *sl)
{
	sl_uprim(sl, M_PCPROTO, SL_RTB_CLEARED_IND);
}

static sl_ucalls_t sl_mod_ucalls =
{
	sl_l3_pdu,				/* sl_pdu		*/
	sl_l3_link_congested,			/* sl_congested		*/
	sl_l3_link_congestion_ceased,		/* sl_no_cong		*/
	sl_l3_retrieved_message,		/* sl_rtrv_msg		*/
	sl_l3_retrieval_complete,		/* sl_rtrv_comp		*/
	sl_l3_rb_cleared,			/* sl_rb_cleared	*/
	sl_l3_bsnt,				/* sl_bsnt		*/
	sl_l3_in_service,			/* sl_in_service	*/
	sl_l3_out_of_service,			/* sl_out_of_service	*/
	sl_l3_remote_processor_outage,		/* sl_rpoutage		*/
	sl_l3_remote_processor_recovered,	/* sl_rprecovered	*/
	sl_l3_rtb_cleared			/* sl_rtb_cleared	*/
};

/*
 *  =======================================================================
 *
 *  SL->SDT Serivce Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static inline void sl_dprim(sl_t *sl, int type, int prim)
{
	mblk_t *mp;
	if ( (mp = allocb(sizeof(sl_long), BPRI_HI)) )
	{
		mp->b_datap->db_type = type;
		*((sl_long *)mp->b_wptr)++ = prim;
		putnext(sl->wq, mp);
	}
}

static void sdt_daedt_xmit_req(sl_t *sl, mblk_t *mp)
{
	if ( mp->b_datap->db_type != M_DATA )
	{
		*((sl_long *)mp->b_rptr) = SDT_DAEDT_TRANSMISSION_REQ;
		mp->b_datap->db_type = M_PROTO;
	}
	putnext(sl->wq, mp);
}
static void sdt_daedt_start_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_DAEDT_START_REQ);
}
static void sdt_daedr_start_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_DAEDR_START_REQ);
}
static void sdt_aerm_start_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_AERM_START_REQ);
}
static void sdt_aerm_stop_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_AERM_STOP_REQ);
}
static void sdt_aerm_set_ti_to_tin_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_AERM_SET_TI_TO_TIN_REQ);
}
static void sdt_aerm_set_ti_to_tie_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_AERM_SET_TI_TO_TIE_REQ);
}
static void sdt_suerm_start_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_SUERM_START_REQ);
}
static void sdt_suerm_stop_req(sl_t *sl)
{
	sl_dprim(sl, M_PCPROTO, SDT_SUERM_STOP_REQ);
}
static sl_mcalls_t sl_mod_dcalls =
{
	sdt_daedt_xmit_req,		/* daedt_xmit	*/
	sdt_daedt_start_req,		/* daedt_start	*/
	sdt_daedr_start_req,		/* daedr_start	*/
	sdt_aerm_start_req,		/* aerm_start	*/
	sdt_aerm_stop_req,		/* aerm_stop	*/
	sdt_aerm_set_ti_to_tin_req,	/* aerm_set_tin	*/
	sdt_aerm_set_ti_to_tie_req,	/* aerm_set_tie	*/
	sdt_suerm_start_req,		/* suerm_start	*/
	sdt_suerm_stop_req		/* suerm_stop	*/
};

/*
 *  =======================================================================
 *
 *  SL->SDT Serivce Calls (Driver)
 *
 *  =======================================================================
 */

static void sdt_daedt_xmit(sl_t *sl, mblk_t *mp)
{
	sl->driver->dcalls->daedt_xmit(sl->device, mp);
}
static void sdt_daedt_start(sl_t *sl)
{
	sl->driver->dcalls->daedt_start(sl->device);
}
static void sdt_daedr_start(sl_t *sl)
{
	sl->driver->dcalls->daedr_start(sl->device);
}
static void sdt_aerm_start(sl_t *sl) {
    sl->driver->dcalls->aerm_start(sl->device);
}
static void sdt_aerm_stop(sl_t *sl) {
    sl->driver->dcalls->aerm_stop(sl->device);
}
static void sdt_aerm_set_ti_to_tin(sl_t *sl)
{
	sl->driver->dcalls->aerm_set_tin(sl->device);
}
static void sdt_aerm_set_ti_to_tie(sl_t *sl)
{
	sl->driver->dcalls->aerm_set_tie(sl->device);
}
static void sdt_suerm_start(sl_t *sl)
{
	sl->driver->dcalls->suerm_start(sl->device);
}
static void sdt_suerm_stop(sl_t *sl)
{
	sl->driver->dcalls->suerm_stop(sl->device);
}

static sl_mcalls_t sl_drv_dcalls =
{
	sdt_daedt_xmit,		/* daedt_xmit	*/
	sdt_daedt_start,	/* daedt_start	*/
	sdt_daedr_start,	/* daedr_start	*/
	sdt_aerm_start,		/* aerm_start	*/
	sdt_aerm_stop,		/* aerm_stop	*/
	sdt_aerm_set_ti_to_tin,	/* aerm_set_tin	*/
	sdt_aerm_set_ti_to_tie,	/* aerm_set_tie	*/
	sdt_suerm_start,	/* suerm_start	*/
	sdt_suerm_stop		/* uerm_stop	*/
};

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

#include "sl_sm.h"

/*
 *  =======================================================================
 *
 *  SL<-LK Serivce Calls (Driver)
 *
 *  =======================================================================
 */

static inline void sl_pdu(sl_t *sl, mblk_t *mp)
{
	sl_lsc_pdu(sl, mp);
}
static inline void sl_emergency(sl_t *sl)
{
	sl_lsc_emergency(sl);
}
static inline void sl_emergency_ceases(sl_t *sl)
{
	sl_lsc_emergency_ceases(sl);
}
static inline void sl_start(sl_t *sl)
{
	sl_lsc_start(sl);
}
static inline void sl_stop(sl_t *sl)
{
	sl_lsc_stop(sl);
}
static inline void sl_retrieve_bsnt(sl_t *sl)
{
	sl_lsc_retrieve_bsnt(sl);
}
static inline void sl_retrieval_request_and_fsnc(sl_t *sl, int fsnc)
{
	sl_lsc_retrieval_request_and_fsnc(sl, fsnc);
}
static inline void sl_resume(sl_t *sl)
{
	sl_lsc_resume(sl);
}
static inline void sl_clear_buffers(sl_t *sl)
{
	sl_lsc_clear_buffers(sl);
}
static inline void sl_clear_rtb(sl_t *sl)
{
	sl_lsc_clear_rtb(sl);
}
static inline void sl_local_processor_outage(sl_t *sl)
{
	sl_lsc_local_processor_outage(sl);
}
static inline void sl_congestion_discard(sl_t *sl)
{
	sl_lsc_congestion_discard(sl);
}
static inline void sl_congestion_accept(sl_t *sl)
{
	sl_lsc_congestion_accept(sl);
}
static inline void sl_no_congestion(sl_t *sl)
{
	sl_lsc_no_congestion(sl);
}
static inline void sl_power_on(sl_t *sl)
{
	sl_lsc_power_on(sl);
}

#if 0
static sl_dcalls_t sl_drv_ops =
{
	sl_pdu,				/* sl_pdu		*/
	sl_emergency,			/* sl_emerg		*/
	sl_emergency_ceases,		/* sl_normal		*/
	sl_start,			/* sl_start		*/
	sl_stop,			/* sl_stop		*/
	sl_retrieve_bsnt,		/* sl_bsnt		*/
	sl_retrieval_request_and_fsnc,	/* sl_fsnc		*/
	sl_resume,			/* sl_resume		*/
	sl_clear_buffers,		/* sl_clear_bufs	*/
	sl_clear_rtb,			/* sl_clear_rtb		*/
	sl_local_processor_outage,	/* sl_lpo		*/
	sl_congestion_discard,		/* sl_cg_accept		*/
	sl_congestion_accept,		/* sl_cg_discard	*/
	sl_no_congestion,		/* sl_no_cong		*/
	sl_power_on			/* sl_power_on		*/
};
#endif

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

static void sl_pdu_req(sl_t *sl, mblk_t *mp)
{
	sl_pdu(sl, mp);
}
static void sl_emergency_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_emergency(sl);
}
static void sl_emergency_ceases_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_emergency_ceases(sl);
}
static void sl_start_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_start(sl);
}
static void sl_stop_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_stop(sl);
}
static void sl_retrieve_bsnt_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_retrieve_bsnt(sl);
}
static void sl_retrieval_request_and_fsnc_req(sl_t *sl, mblk_t *mp)
{
	int fsnc = ((sl_ulong *)mp->b_rptr)[1];
	freemsg(mp);
	sl_retrieval_request_and_fsnc(sl, fsnc);
}
static void sl_resume_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_resume(sl);
}
static void sl_clear_buffers_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_clear_buffers(sl);
}
static void sl_clear_rtb_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_clear_rtb(sl);
}
static void sl_local_processor_outage_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_local_processor_outage(sl);
}
static void sl_congestion_discard_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_congestion_discard(sl);
}
static void sl_congestion_accept_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_congestion_accept(sl);
}
static void sl_no_congestion_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_no_congestion(sl);
}
static void sl_power_on_req(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_power_on(sl);
}

static void (*sl_lk_ops[])(sl_t *, mblk_t *) =
{
	sl_pdu_req,				/* SL_PDU_REQ				*/
	sl_emergency_req,			/* SL_EMERGENCY_REQ			*/
	sl_emergency_ceases_req,		/* SL_EMERGENCY_CEASES_REQ		*/
	sl_start_req,				/* SL_START_REQ				*/
	sl_stop_req,				/* SL_STOP_REQ				*/
	sl_retrieve_bsnt_req,			/* SL_RETRIEVE_BSNT_REQ			*/
	sl_retrieval_request_and_fsnc_req,	/* SL_RETRIEVAL_REQUEST_AND_FSNC_REQ	*/
	sl_resume_req,				/* SL_RESUME_REQ			*/
	sl_clear_buffers_req,			/* SL_CLEAR_BUFFERS_REQ			*/
	sl_clear_rtb_req,			/* SL_CLEAR_RTB_REQ			*/
	sl_local_processor_outage_req,		/* SL_LOCAL_PROCESSOR_OUTAGE_REQ	*/
	sl_congestion_discard_req,		/* SL_CONGESTION_DISCARD_REQ		*/
	sl_congestion_accept_req,		/* SL_CONGESTION_ACCEPT_REQ		*/
	sl_no_congestion_req,			/* SL_NO_CONGESTION_REQ			*/
	sl_power_on_req				/* SL_POWER_ON_REQ			*/
};

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

static void sdt_rc_signal_unit(sdt_t *sdt, mblk_t *mp)
{
	sl_rc_signal_unit(sdt->module, mp);
}
static void sdt_rc_congestion_accept(sdt_t *sdt)
{
	sl_rc_congestion_accept(sdt->module);
}
static void sdt_rc_congestion_discard(sdt_t *sdt)
{
	sl_rc_congestion_discard(sdt->module);
}
static void sdt_rc_no_congestion(sdt_t *sdt)
{
	sl_rc_no_congestion(sdt->module);
}
static void sdt_iac_correct_su(sdt_t *sdt)
{
	sl_iac_correct_su(sdt->module);
}
static void sdt_iac_abort_proving(sdt_t *sdt)
{
	sl_iac_abort_proving(sdt->module);
}
static void sdt_lsc_link_failure(sdt_t *sdt)
{
	sl_lsc_link_failure(sdt->module);
}
static sdt_ucalls_t sl_drv_ucalls =
{
	sdt_rc_signal_unit,		/* rc_signal_unit		*/
	sdt_rc_congestion_accept,	/* rc_congestion_accept		*/
	sdt_rc_congestion_discard,	/* rc_congestion_discard	*/
	sdt_rc_no_congestion,		/* rc_no_congestion		*/
	sdt_iac_correct_su,		/* iac_correct_su		*/
	sdt_iac_abort_proving,		/* iac_abort_proving		*/
	sdt_lsc_link_failure		/* lsc_link_failure		*/
};

/*
 *  =======================================================================
 *
 *  SL<-SDT Serivce Primitives (M_CTL, M_PROTO, M_PCPROTO)
 *
 *  =======================================================================
 */

static void sdt_rc_signal_unit_ind(sl_t *sl, mblk_t *mp)
{
	sl_rc_signal_unit(sl, mp);
}
static void sdt_rc_congestion_accept_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_rc_congestion_accept(sl);
}
static void sdt_rc_congestion_discard_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_rc_congestion_discard(sl);
}
static void sdt_rc_no_congestion_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_rc_no_congestion(sl);
}
static void sdt_iac_correct_su_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_iac_correct_su(sl);
}
static void sdt_iac_abort_proving_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_iac_abort_proving(sl);
}
static void sdt_lsc_link_failure_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_lsc_link_failure(sl);
}
static void sdt_txc_transmission_request_ind(sl_t *sl, mblk_t *mp)
{
	freemsg(mp);
	sl_txc_transmission_request(sl);
}

static void (*sl_sdt_ops[])(sl_t *, mblk_t *) =
{
	sdt_rc_signal_unit_ind,		/* SDT_RC_SIGNAL_UNIT_IND		*/
	sdt_rc_congestion_accept_ind,	/* SDT_RC_CONGESTION_ACCEPT_IND		*/
	sdt_rc_congestion_discard_ind,	/* SDT_RC_CONGESTION_DISCARD_IND	*/
	sdt_rc_no_congestion_ind,	/* SDT_RC_NO_CONGESTION_IND		*/
	sdt_iac_correct_su_ind,		/* SDT_IAC_CORRECT_SU_IND		*/
	sdt_iac_abort_proving_ind,	/* SDT_IAC_ABORT_PROVING_IND		*/
	sdt_lsc_link_failure_ind,	/* SDT_LSC_LINK_FAILURE_IND		*/
	sdt_txc_transmission_request_ind/* SDT_TXC_TRANSMISSION_REQUEST_IND	*/
};

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

static int
sl_do_ioctl(lmi_t *sl, int cmd, void *arg)
{
	lmi_driver_t *drv;
	int nr = _IOC_NR(cmd);
	if ( _IOC_TYPE(cmd) == SL_IOC_MAGIC )
		if ( SL_IOC_FIRST <= nr && nr <= SL_IOC_LAST )
			if ( sl_ioc_lmiops[nr] )
				return sl_ioc_lmiops[nr]((sl_t *)sl, cmd, arg);
	if ( (drv = sl->driver) )
		return drv->ops.lmi.ioctl(sl, cmd, arg);
	return -ENXIO;
}

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

static inline int
sl_m_ioctl(queue_t *q, mblk_t *mp)
{
	sl_t *sl = (sl_t *)q->q_ptr;
	struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
	void *arg = mp->b_cont?mp->b_cont->b_rptr:NULL;
	int cmd = iocp->ioc_cmd, count = iocp->ioc_count;
	int ret = -EINVAL;
	if ( count >= _IOC_SIZE(cmd) )
	{
		ret = sl_do_ioctl((lmi_t *)sl, cmd, arg);
	}
	if ( abs(ret) == ENXIO )
	{
		if ( q->q_next )
		{
			putnext(q, mp);
			return(0);
		}
	}
	if ( ret == 0 )
	{
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_error = 0;
		iocp->ioc_rval  = 0;
	}
	else
	{
		mp->b_datap->db_type = M_IOCNAK;
		iocp->ioc_error = abs(ret);
		iocp->ioc_rval  = -1;
	}
	qreply(q, mp);
	return(0);
}

/*
 *  =======================================================================
 *
 *  M_PROTO, M_PCPROTO handling
 *
 *  =======================================================================
 */

static inline int
sl_m_proto(queue_t *q, mblk_t *mp)
{
	int err = LMI_BADPRIM + EOPNOTSUPP;
	sl_t *sl = (sl_t *)q->q_ptr;
	int prim = *((sl_long *)mp->b_rptr);
	if ( SL_DSTR_FIRST <= prim && prim <= SL_DSTR_LAST )
	{
		(*sl_lk_ops[prim - SL_DSTR_FIRST])(sl, mp);
		return(0);
	}
	if ( q->q_next )
	{
		putnext(q, mp);
		return(0);
	}
	if ( LMI_DSTR_FIRST <= prim && prim <= LMI_DSTR_LAST )
	{
		err = (*lmi_lmi_ops[prim - LMI_DSTR_FIRST])((lmi_t *)sl, mp);
	}
	return err;
}

static inline int
sdt_m_proto(queue_t *q, mblk_t *mp)
{
	int err = LMI_BADPRIM + EOPNOTSUPP;
	sl_t *sl = (sl_t *)q->q_ptr;
	long prim = *((sl_long *)mp->b_rptr);
	if ( SDT_USTR_FIRST <= prim && prim <= SDT_USTR_LAST )
	{
		(*sl_sdt_ops[SDT_USTR_LAST - prim])(sl, mp);
		return(0);
	}
	if ( q->q_next )
	{
		putnext(q, mp);
		return (0);
	}
	return err;
}

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

static inline int
sl_m_data(queue_t *q, mblk_t *mp)
{
	sl_t *sl = (sl_t *)q->q_ptr;
	sl_lsc_pdu(sl, mp);
	return(0);
}

static inline int
sdt_m_data(queue_t *q, mblk_t *mp)
{
	sl_t *sl = (sl_t *)q->q_ptr;
	sdt_rc_signal_unit_ind(sl, mp);
	return(0);
}

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

void sl_wput(queue_t *q, mblk_t *mp)
{
	int err = EOPNOTSUPP;
	if ( q->q_count && mp->b_datap->db_type < QPCTL )
	{
		putq(q, mp);
		return;
	}
	switch ( mp->b_datap->db_type )
	{
		case M_DATA:
			if ( (err = sl_m_data(q, mp)) )
				break;
			return;
		case M_CTL:
		case M_PROTO:
		case M_PCPROTO:
			if ( (err = sl_m_proto(q, mp)) )
				break;
			return;
		case M_FLUSH:
			if ( *mp->b_rptr & FLUSHW )
			{
				flushq(q, FLUSHALL);
				if ( q->q_next )
				{
					putnext(q, mp); /* flush all the way down */
					return;
				}
				*mp->b_rptr &= ~FLUSHW;
			}
			if ( *mp->b_rptr & FLUSHR )
			{
				flushq(RD(q), FLUSHALL);
				qreply(q, mp);
			}
			else break;
			return;
		case M_IOCTL:
			if ( (err = sl_m_ioctl(q, mp)) )
				break;
			return;
	}
	switch ( err&0xffff )
	{
		case EAGAIN:
			putq(q, mp);
			return;
		case EOPNOTSUPP:
			if ( q->q_next )
			{
				putnext(q, mp);
				return;
			}
	}
	freemsg(mp);    /* don't even want to complain */
	return;
}

void sl_wsrv(queue_t *q)
{
	int err = EOPNOTSUPP;
	mblk_t *mp;
	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 = sl_m_data(q, mp)) )
					break;
				continue;
			case M_CTL:
			case M_PROTO:
			case M_PCPROTO:
				if ( (err = sl_m_proto(q, mp)) )
					break;
				continue;
		}
		switch ( err&0xffff )
		{
			case EAGAIN:
				if ( mp->b_datap->db_type < QPCTL )
				{
					putbq(q, mp);
					return;
				}
				break;
			case EOPNOTSUPP:
				if ( q->q_next )
				{
					putnext(q, mp);
					return;
				}
		}
		freemsg(mp);
	}
}

static void
sl_rput(queue_t *q, mblk_t *mp)
{
	int err = EOPNOTSUPP;
	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 = sdt_m_data(q, mp)) )
				break;
			return;
		case M_CTL:
		case M_PROTO:
		case M_PCPROTO:
			if ( (err = sdt_m_proto(q, mp)) )
				break;
			return;
	}
	switch ( err&0xffff )
	{
		case EAGAIN:
			putq(q, mp);
			return;
		case EOPNOTSUPP:
			putnext(q, mp);
			return;
	}
	freemsg(mp);
}

static void
sl_rsrv(queue_t *q)
{
	mblk_t *mp;
	int err = EOPNOTSUPP;
	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 = sdt_m_data(q, mp)) )
					break;
				continue;
			case M_CTL:
			case M_PROTO:
			case M_PCPROTO:
				if ( (err = sdt_m_proto(q, mp)) )
					break;
				continue;
		}
		switch ( err&0xffff )
		{
			case EAGAIN:
				if ( mp->b_datap->db_type < QPCTL )
				{
					putbq(q, mp);
					return;
				}
				break;
			case EOPNOTSUPP:
				putnext(q, mp);
				return;
		}
		freemsg(mp);
	}
}

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

static lmi_driver_t *sl_drivers = NULL;

static int
sl_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
	int err;
	sl_t *sl;
	if ( q->q_ptr != NULL )
		return (0);
	if ( (err = lmi_open(q, devp, flag, sflag, crp, sl_drivers, sizeof(sl_t))) )
		return(err);
	sl = (sl_t *)q->q_ptr;
	sl->ucalls = &sl_mod_ucalls;
	if ( WR(q)->q_next )
		sl->dcalls = &sl_mod_dcalls;
	else	sl->dcalls = &sl_drv_dcalls;
	bufq_init(&sl->tb);
	bufq_init(&sl->rtb);
	return(0);
}

static int
sl_close(queue_t *q, int flag, cred_t *crp)
{
	int err;
	sl_t *sl = (sl_t *)q->q_ptr;
//	bufq_purge(&sl->tb);    /* causes problems for some reason */
//	bufq_purge(&sl->rtb);   /* causes problems for some reason */
	err = lmi_close(q, flag, crp,
			(sl_ulong *)&sl->timers, SL_MAX_TIMERIDS,
			(sl_ulong *)&sl->bufids, SL_MAX_BUFCALLS);
	return(err);
}

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

int
sl_register_driver(major_t cmajor, int nminor, char *name,
		lmi_ops_t *ops, sdt_dcalls_t *dcalls)
{
	int ret;
	MOD_INC_USE_COUNT;
	if ( (ret = lmi_register_driver(
					&sl_drivers, cmajor, &sl_info, nminor, name,
					ops, dcalls, &sl_drv_ucalls)) < 0 )
	{
		MOD_DEC_USE_COUNT;
		return(ret);
	}
	return(ret);
}

int
sl_unregister_driver(major_t cmajor)
{
	int err = lmi_unregister_driver(&sl_drivers, cmajor);
	if ( !err )
		MOD_DEC_USE_COUNT;
	return (err);
}

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

static int sl_initialized = 0;

#ifndef LIS_REGISTERED
static inline void sl_init(void)
#else
__initfunc(void sl_init(void))
#endif
{
	if ( sl_initialized )
		return;
	sl_initialized = 1;
	printk(KERN_INFO SL_BANNER);   /* console splash */
#ifndef LIS_REGISTERED
	if ( !(sl_minfo.mi_idnum = lis_register_strmod(&sl_info, sl_minfo.mi_idname)) )
	{
		cmn_err(CE_NOTE, "sl: couldn't register module\n");
	}
#endif
#if 0
	if ( sls_register_driver(SL_CMAJOR, SL_NMINOR, "sl",
				&sl_lmi_ops, &sl_drv_ops) )
	{
		cmn_err(CE_NOTE: "sl: couldn't register as driver\n");
	}
#endif
};

#ifndef LIS_REGISTERED
static inline void sl_terminate(void)
#else
__initfunc(void sl_terminate(void))
#endif
{
	if ( !sl_initialized )
		return;
	sl_initialized = 0;
#ifndef LIS_REGSITERED
	if (sl_minfo.mi_idnum)
		if ( (sl_minfo.mi_idnum = lis_unregister_strmod(&sl_info)) )
		{
			cmn_err(CE_WARN, "sl: couldn't unregister module!\n");
		}
#endif
};

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

#ifdef MODULE
int init_module(void)
{
	(void)sl_debug;
	sl_init();
	return(0);
}

void cleanup_module(void)
{
	sl_terminate();
	return;
}
#endif

