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

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

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

 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 2001/12/11 13:15:35 by brian

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

static char const ident[] = "sdl.c,v 0.7.8.1 2001/12/11 13:15:35 brian Exp";

/*
 *  This is a SDL (Signalling Data Terminal) loadable module which provides
 *  all of the capabilities of the SDLI while allowing device drivers to
 *  provide a few missing functions by registering with the module to provide
 *  a complete SDL driver.  This relieves much of the LiS STREAMS burden from
 *  the driver implementation and permits the driver to be more portable.  It
 *  also allows SS7 signalling link compliant state machines in the SDT and SL
 *  autopush modules to be tested once, but used by many drivers.
 */

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


#include "../debug.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>
#include <ss7/devi.h>
#include <ss7/devi_ioctl.h>
#include <ss7/sdli.h>
#include <ss7/sdli_ioctl.h>

#include "../lmi/lm.h"
#include "../devi/dev.h"
#include "../sdli/sdl.h"

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

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

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

#define DEBUG_LEVEL sdl_debug

#define SDL_CMAJOR 0
#define SDL_NMINOR 255

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

static void sdl_dev_tx_start(sdl_t *sdl)
{
	ensure( sdl && sdl->driver, return );
	if ( sdl->driver)
		sdl->driver->dcalls->tx_start(sdl->device);
}

static void sdl_dev_rx_start(sdl_t *sdl)
{
	ensure( sdl && sdl->driver, return );
	if ( sdl->driver)
		sdl->driver->dcalls->rx_start(sdl->device);
}

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

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

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

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

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

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

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

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

static void sdl_daedr_recvd_frame(sdl_t *sdl, mblk_t *mp)
{
	if ( sdl->statem.daedr_state == SDL_STATE_IN_SERVICE )
	{
		/*
		 *  Here we check for length and length indicator problems.
		 *  The message block should be an M_DATA block.
		 */
		int len = mp->b_wptr - mp->b_rptr;
		if ( sdl->option.popt & SS7_POPT_XSN )
		{
//			ptrace(("message len=%d, li=%d, m=%ld\n", len,
//						ntohs(((sdl_ushort *)mp->b_rptr)[2]&0x01ff),
//						sdl->config.m));
			if ( 6 <= len && len <= sdl->config.m + 6 )
			{
				int li = ntohs(((sdl_ushort *)mp->b_rptr)[2]) & 0x01ff;
				len -= 6;
				if ( len == li || ( li == 0x1ff && len > li ) )
				{
					sdl->ucalls->daedr_received_bits(sdl, mp);
					return;
				}
			}
		}
		else
		{
//			ptrace(("message len=%d, li=%d, m=%ld\n", len,
//						(int)(((sdl_uchar *)mp->b_rptr)[2]&0x3f),
//						sdl->config.m));
			if ( 3 <= len && len <= sdl->config.m + 3 )
			{
				int li = ((sdl_uchar  *)mp->b_rptr)[2] & 0x3f;
				len -= 3;
				if ( len == li || ( li == 0x3f && len > li ) )
				{
					sdl->ucalls->daedr_received_bits(sdl, mp);
					return;
				}
			}
		}
		assert(mp->b_datap->db_ref);
		freemsg(mp);
		sdl->ucalls->daedr_su_in_error(sdl);
		return;
	}
	assert(mp->b_datap->db_ref);
	freemsg(mp);
}

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

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

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

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

static void sdl_daedt_tx_request(sdl_t *sdl)
{
	if ( sdl->statem.daedt_state == SDL_STATE_IN_SERVICE )
		sdl->ucalls->daedt_tx_request(sdl);
}

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

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

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

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

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

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

static void sdl_daedt_xmit_req(sdl_t *sdl, mblk_t *mp)
{
	sdl_daedt_bits_for_trans(sdl, mp);
}
static void sdl_daedt_start_req(sdl_t *sdl, mblk_t *mp)
{
	assert(mp->b_datap->db_ref);
	freemsg(mp);
	sdl_daedt_start(sdl);
}
static void sdl_daedr_start_req(sdl_t *sdl, mblk_t *mp)
{
	assert(mp->b_datap->db_ref);
	freemsg(mp);
	sdl_daedr_start(sdl);
}

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

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

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

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

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

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

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

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

static int
sdl_do_ioctl(lmi_t *sdl, int cmd, void *arg)
{
	int nr = _IOC_NR(cmd);
	lmi_driver_t *drv;
	if ( _IOC_TYPE(cmd) == SDL_IOC_MAGIC )
		if ( SDL_IOC_FIRST <= nr && nr <= SDL_IOC_LAST )
			if ( sdl_ioc_lmiops[nr] )
				return sdl_ioc_lmiops[nr]((sdl_t *)sdl, cmd, arg);
	if ( (drv = sdl->driver) )
		return drv->ops.lmi.ioctl(sdl, cmd, arg);
	rare();
	return -ENXIO;
}

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

static inline int
sdl_m_ioctl(queue_t *q, mblk_t *mp)
{
	sdl_t *sdl = (sdl_t *)q->q_ptr;
	struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
	void *arg = mp->b_cont?mp->b_cont->b_rptr:NULL;
	int cmd = iocp->ioc_cmd, size = iocp->ioc_count;
	int ret = -EINVAL;
	if ( size >= _IOC_SIZE(cmd) )
		ret = sdl_do_ioctl((lmi_t *)sdl, cmd, arg);
	if ( abs(ret) == ENXIO )
	{
		rare();
		if ( WR(q)->q_next )
		{
			rare();
			putnext(q, mp);
			return(0);
		}
	}
	if ( ret == 0 )
	{
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_error = 0;
		iocp->ioc_rval  = 0;
	}
	else
	{
		seldom();
		mp->b_datap->db_type = M_IOCNAK;
		iocp->ioc_error = abs(ret);
		iocp->ioc_rval  = -1;
	}
	qreply(q, mp);
	return(0);
}

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

static inline int
sdl_m_proto(queue_t *q, mblk_t *mp)
{
	int err = LMI_BADPRIM + EOPNOTSUPP;
	sdl_t *sdl = (sdl_t *)q->q_ptr;
	int prim = *((lmi_long *)mp->b_rptr);
//	ptrace(("primitive = %d\n", prim));
	if ( SDL_DSTR_FIRST <= prim && prim <= SDL_DSTR_LAST )
	{
//		ptrace(("sdl_sdt_ops offset = %d\n", (int)(prim - SDL_DSTR_FIRST)));
		(*sdl_sdt_ops[prim - SDL_DSTR_FIRST])(sdl, mp);
		return(0);
	}
	seldom();
	if ( WR(q)->q_next )
	{
		rare();
		putnext(q, mp);
		return (0);
	}
	if ( LMI_DSTR_FIRST <= prim && prim <= LMI_DSTR_LAST )
		if ( !(err = lmi_lmi_ops[prim - LMI_DSTR_FIRST]((lmi_t *)sdl, mp)) )
			return(0);
	ptrace(("Error return = %d\n", err));
	return err;
}

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

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

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

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

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

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

static void
sdl_wput(queue_t *q, mblk_t *mp)
{
	int err= EOPNOTSUPP;
	if ( q->q_count && mp->b_datap->db_type < QPCTL )
	{
		seldom();
		putq(q, mp);
		return;
	}
	switch ( mp->b_datap->db_type )
	{
		case M_DATA:
			if ( (err = sdl_m_data(q, mp)) ) {
				seldom();
				break;
			}
			return;
		case M_CTL:
		case M_PROTO:   /* should be sent band 1 */
		case M_PCPROTO:
			if ( (err = sdl_m_proto(q, mp)) ) {
				seldom();
				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 = sdl_m_ioctl(q, mp)) ) {
				seldom();
				break;
			}
			return;
	}
	switch ( err&0xffff )
	{
		case EAGAIN:
			seldom();
			putq(q, mp);
			return;
		case EOPNOTSUPP:
			rare();
			if ( q->q_next )
			{
				rare();
				putnext(q, mp);
				return;
			}
	}
	rare();
	assert(mp->b_datap->db_ref);
	freemsg(mp);    /* don't even want to complain... */
}

static void
sdl_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) )
		{
			seldom();
			putbq(q, mp);
			return;
		}
		switch ( mp->b_datap->db_type )
		{
			case M_DATA:
				if ( (err = sdl_m_data(q, mp)) ) {
					seldom();
					break;
				}
				continue;
			case M_CTL:
			case M_PROTO:
			case M_PCPROTO:
				if ( (err = sdl_m_proto(q, mp)) ) {
					seldom();
					break;
				}
				continue;
			default:
				err = EOPNOTSUPP;
		}
		switch ( err&0xffff )
		{
			case EAGAIN:
				seldom();
				if ( mp->b_datap->db_type < QPCTL )
				{
					putbq(q, mp);
					return;
				}
				rare();
				break;
			case EOPNOTSUPP:
				rare();
				if ( q->q_next )
				{
					rare();
					putnext(q, mp);
					return;
				}
		}
		rare();
		assert(mp->b_datap->db_ref);
		freemsg(mp);
	}
}

static void
sdl_rput(queue_t *q, mblk_t *mp)
{
	int err = EOPNOTSUPP;
	if ( q->q_count && mp->b_datap->db_type < QPCTL )
	{
		seldom();
		putq(q, mp);
		return;
	}
	switch ( mp->b_datap->db_type )
	{
		case M_DATA:
			if ( (err = dev_m_data(q, mp)) ) {
				seldom();
				break;
			}
			return;
		case M_CTL:
		case M_PROTO:
		case M_PCPROTO:
			if ( (err = dev_m_proto(q, mp)) ) {
				seldom();
				break;
			}
			return;
	}
	switch ( err&0xffff )
	{
		case EAGAIN:
			rare();
			putq(q, mp);
			return;
		case EOPNOTSUPP:
			rare();
			putnext(q, mp);
			return;
	}
	rare();
	assert(mp->b_datap->db_ref);
	freemsg(mp);
}

#define SDL_SUPQ_MAXLEN 20

static void
sdl_rsrv(queue_t *q)
{
	mblk_t *mp;
	int err= EOPNOTSUPP;
	while ( (mp = getq(q)) )
	{
		if ( mp->b_datap->db_type < QPCTL && !canputnext(q) )
		{
			seldom();
			putbq(q, mp);
			return;
		}
		switch ( mp->b_datap->db_type )
		{
			case M_DATA:
				if ( (err = dev_m_data(q, mp)) ) {
					seldom();
					break;
				}
				continue;
			case M_CTL:
			case M_PROTO:
			case M_PCPROTO:
				if ( (err = dev_m_proto(q, mp)) ) {
					seldom();
					break;
				}
				continue;
			default:
				err = EOPNOTSUPP;
		}
		switch ( err&0xffff )
		{
			case EAGAIN:
				seldom();
				if ( mp->b_datap->db_type < QPCTL )
				{
					putbq(q, mp);
					return;
				}
				rare();
				break;
			case EOPNOTSUPP:
				seldom();
				putnext(q, mp);
				return;
		}
		rare();
		assert(mp->b_datap->db_ref);
		freemsg(mp);
	}
}

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

static lmi_driver_t *sdl_drivers = NULL;

static struct lmi *
sdl_drv_attach(dev_t dev)
{
	lmi_t *sdl;
	MOD_INC_USE_COUNT;
	if ( !(sdl = lmi_drv_attach(dev, sdl_drivers, sizeof(sdl_t))) )
		MOD_DEC_USE_COUNT;
	return sdl;
}

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

static int
sdl_drv_close(struct lmi *sdl)
{
	int err;
	sdl_t *p = (sdl_t *)sdl;
	if ( !(err = lmi_drv_close(sdl, (sdl_ulong *)NULL,       SDL_MAX_TIMERIDS,
					(sdl_ulong *)&p->bufids, SDL_MAX_BUFCALLS)) )
		MOD_DEC_USE_COUNT;
	return(err);
}

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

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

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

static int
sdl_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
	int err;
	sdl_t *sdl;
	if ( q->q_ptr != NULL )
		return (0);
	if ( (err = lmi_open(q, devp, flag, sflag, crp, sdl_drivers, sizeof(sdl_t))) )
		return(err);
	sdl = (sdl_t *)q->q_ptr;
	sdl->ucalls = &sdl_mod_ucalls;
	if ( WR(q)->q_next )
		sdl->dcalls = &sdl_mod_dcalls;
	else	sdl->dcalls = &sdl_drv_dcalls;
	return(0);
}

static int
sdl_close(queue_t *q, int flag, cred_t *crp)
{
	int err;
	sdl_t *sdl = (sdl_t *)q->q_ptr;
	err = lmi_close(q, flag, crp,
			(sdl_ulong *)&sdl->timers, SDL_MAX_TIMERIDS,
			(sdl_ulong *)&sdl->bufids, SDL_MAX_BUFCALLS);
	return(err);
}

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


int
sdl_register_driver(major_t cmajor, int nminor, char *name,
		lmi_ops_t *ops, dev_dcalls_t *dcalls)
{
	int err;
	MOD_INC_USE_COUNT;
	err = lmi_register_driver(
			&sdl_drivers, cmajor, &sdl_info, nminor, name,
			ops, dcalls, &sdl_dev_ucalls );
	if ( err < 0 )
		MOD_DEC_USE_COUNT;
	return(err);
}

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

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

static int sdl_initialized = 0;

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

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

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

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

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

