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

 @(#) sdl.c,v SS7preBetaRelease(0.8.2.3) 2002/04/13 19:27:43

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

 Copyright (c) 2001-2002  OpenSS7 Corporation <http://www.openss7.com>
 Copyright (c) 1997-2000  Brian F. G. 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.

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

 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).

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

 Commercial licensing and support of this software is available from OpenSS7
 Corporation at a fee.  See http://www.openss7.com/

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

 Last Modified 2002/04/13 19:27:43 by brian

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

#ident "@(#) sdl.c,v SS7preBetaRelease(0.8.2.3) 2002/04/13 19:27:43"

static char const ident[] =
	"sdl.c,v SS7preBetaRelease(0.8.2.3) 2002/04/13 19:27:43";

/* 
 *  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 "../lock.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-2002 OpenSS7 Corp.  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 int sdl_rput(queue_t *, mblk_t *);
static int 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 int sdl_wput(queue_t *, mblk_t *);
static int 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 int sdl_wput(queue_t * q, mblk_t * mp)
{
	lmi_t *lmi = LMI_PRIV(q);
	int err = EOPNOTSUPP;
	if (q->q_count && mp->b_datap->db_type < QPCTL)
	{
		seldom();
		putq(q, mp);
		return (0);
	}
	if (!lmi_trylock(lmi))
	{
		switch (mp->b_datap->db_type)
		{
			case M_DATA:
				if ((err = sdl_m_data(q, mp)))
				{
					seldom();
					break;
				}
				lmi_unlock(lmi);
				return (0);
			case M_CTL:
			case M_PROTO:	/* should be sent band 1 */
			case M_PCPROTO:
				if ((err = sdl_m_proto(q, mp)))
				{
					seldom();
					break;
				}
				lmi_unlock(lmi);
				return (0);
			case M_FLUSH:
				if (*mp->b_rptr & FLUSHW)
				{
					flushq(q, FLUSHALL);
					if (q->q_next)
					{
						putnext(q, mp);	/* flush all the way down */
						lmi_unlock(lmi);
						return (0);
					}
					*mp->b_rptr &= ~FLUSHW;
				}
				if (*mp->b_rptr & FLUSHR)
				{
					flushq(RD(q), FLUSHALL);
					qreply(q, mp);
				} else
					break;
				lmi_unlock(lmi);
				return (0);
			case M_IOCTL:
				if ((err = sdl_m_ioctl(q, mp)))
				{
					seldom();
					break;
				}
				lmi_unlock(lmi);
				return (0);
		}
		switch (err & 0xffff)
		{
			case EAGAIN:
				seldom();
				putq(q, mp);
				lmi_unlock(lmi);
				return (0);
			case EOPNOTSUPP:
				rare();
				if (q->q_next)
				{
					rare();
					putnext(q, mp);
					lmi_unlock(lmi);
					return (0);
				}
		}
	} else
	{
		rare();
		putq(q, mp);
		return (0);
	}
	rare();
	assert(mp->b_datap->db_ref);
	freemsg(mp);	/* don't even want to complain... */
	return (0);
}

static int sdl_wsrv(queue_t * q)
{
	lmi_t *lmi = LMI_PRIV(q);
	int err = EOPNOTSUPP;
	mblk_t *mp;
	if (!lmi_trylock(lmi))
	{
		while ((mp = getq(q)))
		{
			if (q->q_next && mp->b_datap->db_type < QPCTL && !canputnext(q))
			{
				seldom();
				putbq(q, mp);
				lmi_unlock(lmi);
				return (0);
			}
			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);
						lmi_unlock(lmi);
						return (0);
					}
					rare();
					break;
				case EOPNOTSUPP:
					rare();
					if (q->q_next)
					{
						rare();
						putnext(q, mp);
						lmi_unlock(lmi);
						return (0);
					}
			}
			rare();
			assert(mp->b_datap->db_ref);
			freemsg(mp);
		}
	} else
	{
		rare();
		qenable(q);
	}
	return (0);
}

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

#define SDL_SUPQ_MAXLEN 20

static int sdl_rsrv(queue_t * q)
{
	mblk_t *mp;
	lmi_t *lmi = LMI_PRIV(q);
	int err = EOPNOTSUPP;
	if (!lmi_trylock(lmi))
	{
		while ((mp = getq(q)))
		{
			if (mp->b_datap->db_type < QPCTL && !canputnext(q))
			{
				seldom();
				putbq(q, mp);
				lmi_unlock(lmi);
				return (0);
			}
			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);
						lmi_unlock(lmi);
						return (0);
					}
					rare();
					break;
				case EOPNOTSUPP:
					seldom();
					putnext(q, mp);
					lmi_unlock(lmi);
					return (0);
			}
			rare();
			assert(mp->b_datap->db_ref);
			freemsg(mp);
			lmi_unlock(lmi);
		}
	} else
	{
		rare();
		qenable(q);
	}
	return (0);
}

/* 
 *  =======================================================================
 *
 *  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
