OpenSS7
SS7 for the
Common Man

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified:

Home Overview Status News Documentation Resources About
   
 Overview
 Status
 News
 Documentation
 Resources
 About

   
Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/x400p-ss7/ch_x400p.c


File /code/strss7/drivers/x400p-ss7/ch_x400p.c



#ident "@(#) $RCSfile: ch_x400p.c,v $ $Name:  $($Revision: 0.8.2.5 $) $Date: 2003/04/06 08:53:40 $"

static char const ident[] =
    "$RCSfile: ch_x400p.c,v $ $Name:  $($Revision: 0.8.2.5 $) $Date: 2003/04/06 08:53:40 $";

#include <linux/config.h>
#include <linux/version.h>
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#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/sdli.h>
#include <ss7/sdli_ioctl.h>
#include <ss7/chi.h>
#include <ss7/chi_ioctl.h>

#include "../debug.h"
#include "../priv.h"
#include "../lock.h"
#include "../queue.h"
#include "../allocb.h"

#define CH_DESCRIP	"X400P-SS7 CHANNEL (CH) STREAMS MODULE."
#define CH_COPYRIGHT	"Copyright (c) 1997-2002 OpenSS7 Corporation.  All Rights Reserved."
#define CH_DEVICE	"Part of the OpenSS7 Stack for LiS STREAMS."
#define CH_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define CH_LICENSE	"GPL"
#define CH_BANNER	CH_DESCRIP	"\n" \
			CH_COPYRIGHT	"\n" \
			CH_DEVICE	"\n" \
			CH_CONTACT

MODULE_AUTHOR(CH_CONTACT);
MODULE_DESCRIPTION(CH_DESCRIP);
MODULE_SUPPORTED_DEVICE(CH_DEVICE);
#ifdef MODULE_LICENSE
MODULE_LICENSE(CH_LICENSE);
#endif

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

#define CH_MOD_ID   CH_IOC_MAGIC
#define CH_MOD_NAME "ch"

static struct module_info ch_minfo = {
	mi_idnum:CH_MOD_ID,			/* Module ID number */
	mi_idname:CH_MOD_NAME,			/* Module ID name */
	mi_minpsz:1,				/* Min packet size accepted */
	mi_maxpsz:INFPSZ,			/* Max packet size accepted */
	mi_hiwat:1,				/* Hi water mark */
	mi_lowat:0,				/* Lo water mark */
};

static int ch_open(queue_t *, dev_t *, int, int, cred_t *);
static int ch_close(queue_t *, int, cred_t *);

static struct qinit ch_rinit = {
	qi_putp:ss7_oput,			/* Read put (message from below) */
	qi_qopen:ch_open,			/* Each open */
	qi_qclose:ch_close,			/* Last close */
	qi_minfo:&ch_minfo,			/* Information */
};

static struct qinit ch_winit = {
	qi_putp:ss7_iput,			/* Write put (message from above) */
	qi_minfo:&ch_minfo,			/* Information */
};

static struct streamtab ch_info = {
	st_rdinit:&ch_rinit,			/* Upper read queue */
	st_wrinit:&ch_winit,			/* Upper write queue */
};

/*
 *  =========================================================================
 *
 *  Private Data Structures
 *
 *  =========================================================================
 */

/* channel structure */
typedef struct ch {
	STR_DECLARATION (struct ch);		/* stream declaration */
	struct ch_config config;		/* configuration */
	struct ch_stats statsp;			/* stats periods */
	struct ch_stats stats;			/* statistics */
	struct ch_notify notify;		/* notifications */
	uchar add_ptr[32];			/* attached address */
	size_t add_len;				/* attached address length */
	uchar rem_ptr[32];			/* remote address */
	size_t rem_len;				/* remote address length */
} ch_t;
#define CH_PRIV(__q) ((struct ch *)(__q)->q_ptr)

struct ch_config ch_default = {
	block_size:64,				/* 64 bits per block */
	encoding:CH_ENCODING_NONE,		/* sample block encoding */
	sample_size:8,				/* sample size */
	rate:8000,				/* clock rate */
	tx_channels:1,				/* tx channels */
	rx_channels:1,				/* tx channels */
	opt_flags:CH_PARM_OPT_CLRCH,		/* option flags */
};

static struct ch *ch_opens = NULL;

static struct ch *ch_alloc_priv(queue_t *, struct ch **, dev_t *, cred_t *);
static struct ch *ch_get(struct ch *);
static void ch_put(struct ch *);
static void ch_free_priv(queue_t *);

/*
 *  =========================================================================
 *
 *  PRIMITIVES
 *
 *  =========================================================================
 */
/*
 *  -------------------------------------------------------------------------
 *
 *  Primitives sent upstream
 *
 *  -------------------------------------------------------------------------
 */

/*
 *  M_ERROR
 *  -----------------------------------
 */
static int m_error(queue_t *q, struct ch *ch, int error)
{
	mblk_t *mp;
	int hangup = 0;
	if (error < 0)
		error = -error;
	switch (error) {
	case EBUSY:
	case ENOBUFS:
	case ENOMEM:
	case EAGAIN:
		return (-error);
	case EPIPE:
	case ENETDOWN:
	case EHOSTUNREACH:
		hangup = 1;
	}
	if ((mp = ss7_allocb(q, 2, BPRI_MED))) {
		if (hangup) {
			mp->b_datap->db_type = M_HANGUP;
			ch->i_state = CHS_UNUSABLE;
			printd(("%s: %p: <- M_HANGUP\n", CH_MOD_NAME, ch));
			putnext(ch->oq, mp);
			return (-error);
		} else {
			mp->b_datap->db_type = M_ERROR;
			*(mp->b_wptr)++ = error < 0 ? -error : error;
			*(mp->b_wptr)++ = error < 0 ? -error : error;
			ch->i_state = CHS_UNUSABLE;
			printd(("%s: %p: <- M_ERROR\n", CH_MOD_NAME, ch));
			putnext(ch->oq, mp);
			return (QR_DONE);
		}
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_INFO_ACK
 *  -----------------------------------
 *  Indicates to the channel user information concerning the channel provider and the
 *  attached channel (if any).
 */
static inline int ch_info_ack(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	struct CH_info_ack *p;
	struct CH_parms_circuit *o;
	size_t pad_len = (ch->add_len + (sizeof(ulong) - 1)) & ~(sizeof(ulong) - 1);
	if ((mp = ss7_allocb(q, sizeof(*p) + pad_len + sizeof(*o), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_INFO_ACK;
		p->ch_addr_length = ch->add_len;
		p->ch_addr_offset = ch->add_len ? sizeof(*p) : 0;
		p->ch_parm_length = sizeof(*o);
		p->ch_parm_offset = sizeof(*p) + pad_len;
		p->ch_prov_flags = 0;
		p->ch_style = 0x1;
		p->ch_version = CH_VERSION_1_0;
		if (ch->add_len) {
			bcopy(ch->add_ptr, mp->b_wptr, ch->add_len);
			mp->b_wptr += ch->add_len;
		}
		o = ((typeof(o)) mp->b_wptr)++;
		o->cp_type = CH_PARMS_CIRCUIT;
		o->cp_block_size = ch->config.block_size;
		o->cp_encoding = ch->config.encoding;
		o->cp_sample_size = ch->config.sample_size;
		o->cp_rate = ch->config.rate;
		o->cp_tx_channels = ch->config.tx_channels;
		o->cp_rx_channels = ch->config.rx_channels;
		o->cp_opt_flags = ch->config.opt_flags;
		printd(("%s: %p: <- CH_INFO_ACK\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_OPTMGMT_ACK
 *  -----------------------------------
 */
static inline int ch_optmgmt_ack(queue_t *q, struct ch *ch, uchar *opt_ptr, size_t opt_len, ulong flags)
{
	mblk_t *mp;
	struct CH_optmgmt_ack *p;
	if ((mp = ss7_allocb(q, sizeof(*p) + opt_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_OPTMGMT_ACK;
		p->ch_opt_length = opt_len;
		p->ch_opt_offset = opt_len ? sizeof(*p) : 0;
		p->ch_mgmt_flags = flags;
		if (opt_len) {
			bcopy(opt_ptr, mp->b_wptr, opt_len);
			mp->b_wptr += opt_len;
		}
		printd(("%s: %p: <- CH_OPTMGMT_ACK\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_OK_ACK
 *  -----------------------------------
 *  Indicates to the channel user that the last operation requiring
 *  acknowledgement was completed successfully.  (There are only two
 *  operations requiring acknowledgement: CH_ATTACH_REQ and CH_DETACH_REQ.)
 */
static inline int ch_ok_ack(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	struct CH_ok_ack *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_OK_ACK;
		switch (ch->i_state) {
		case CHS_WACK_AREQ:
			p->ch_correct_prim = CH_ATTACH_REQ;
			p->ch_state = ch->i_state = CHS_ATTACHED;
			break;
		case CHS_WACK_UREQ:
			p->ch_correct_prim = CH_DETACH_REQ;
			p->ch_state = ch->i_state = CHS_DETACHED;
			break;
		case CHS_UNUSABLE:
			p->ch_correct_prim = CH_DETACH_REQ;
			p->ch_state = ch->i_state = CHS_UNUSABLE;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_OK_ACK\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_ERROR_ACK
 *  -----------------------------------
 *  Indicates to the channel user that the last operation requiring
 *  acknowledgement or confirmation did not complete and was unsuccessful
 *  (suffered a non-fatal error).  There are six operations requiring
 *  acknowledgement or confirmation.  In addition, this error is returned when
 *  unrecognized primitives are sent.
 */
static inline int ch_error_ack(queue_t *q, struct ch *ch, ulong prim, long error)
{
	mblk_t *mp;
	struct CH_error_ack *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_ERROR_ACK;
		p->ch_error_type = error > 0 ? error : CHSYSERR;
		p->ch_unix_error = error < 0 ? -error : 0;
		p->ch_error_primitive = prim;
		switch (ch->i_state) {
		case CHS_WACK_AREQ:
			if (prim == CH_ATTACH_REQ)
				p->ch_state = ch->i_state = CHS_DETACHED;
			break;
		case CHS_WACK_UREQ:
			if (prim == CH_DETACH_REQ)
				p->ch_state = ch->i_state = CHS_ATTACHED;
			break;
		case CHS_WACK_EREQ:
		case CHS_WCON_EREQ:
			if (prim == CH_ENABLE_REQ)
				p->ch_state = ch->i_state = CHS_ATTACHED;
			break;
		case CHS_WACK_RREQ:
		case CHS_WCON_RREQ:
			if (prim == CH_DISABLE_REQ)
				p->ch_state = ch->i_state = CHS_ENABLED;
			break;
		case CHS_WACK_CREQ:
		case CHS_WCON_CREQ:
			if (prim == CH_CONNECT_REQ) {
				if (ch->flags & CHF_BOTH_DIR)
					p->ch_state = ch->i_state = CHS_CONNECTED;
				else
					p->ch_state = ch->i_state = CHS_ENABLED;
			}
			break;
		case CHS_WACK_DREQ:
		case CHS_WCON_DREQ:
			if (prim == CH_DISCONNECT_REQ)
				p->ch_state = ch->i_state = CHS_CONNECTED;
			break;
		default:
			/* default is don't change state */
			p->ch_state = ch->i_state;
			break;
		}
		printd(("%s: %p: <- CH_ERROR_ACK\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_ENABLE_CON
 *  -----------------------------------
 *  Confirms to the channel user that the attached channel was enabled as
 *  requested.
 */
static inline int ch_enable_con(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	struct CH_enable_con *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_ENABLE_CON;
		switch (ch->i_state) {
		case CHS_WACK_EREQ:
		case CHS_WCON_EREQ:
			ch->i_state = CHS_ENABLED;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_ENABLE_CON\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_CONNECT_CON
 *  -----------------------------------
 *  Confirms to the channel user that the enabled or connected channel was
 *  connected in the requested direction.
 */
static inline int ch_connect_con(queue_t *q, struct ch *ch, ulong flags)
{
	mblk_t *mp;
	struct CH_connect_con *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_CONNECT_CON;
		p->ch_conn_flags = flags;
		switch (ch->i_state) {
		case CHS_WCON_CREQ:
			ch->flags |= (flags & CHF_BOTH_DIR);
			ch->i_state = CHS_CONNECTED;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_CONNECT_CON\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_DATA_IND
 *  -----------------------------------
 *  Indicates to the channel user data which was received on the channel.
 *  This is the non-preferred way of sending data to the channel user.  We
 *  should normally just send M_DATA blocks.
 */
static inline int ch_data_ind(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	struct CH_data_ind *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_DATA_IND;
		printd(("%s: %p: <- CH_DATA_IND\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_DISCONNECT_IND
 *  -----------------------------------
 *  Indicates to the channel user that an autonomous disconnection occured,
 *  the directions of the disconnection and the cause of the disconnection.
 *  This is normally used to indicate that both directions were disconnected
 *  due to hardware or transmission system failure (loss of carrier or carrier
 *  alarms).  This indication is rather optional, as carrier alarms are also
 *  indicated to management streams for the underlying devices.
 */
static inline int ch_disconnect_ind(queue_t *q, struct ch *ch, ulong flags)
{
	mblk_t *mp;
	struct CH_disconnect_ind *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_DISCONNECT_IND;
		p->ch_conn_flags = flags;
		p->ch_cause = 0;	/* FIXME */
		switch (ch->i_state) {
		case CHS_CONNECTED:
		case CHS_WACK_CREQ:
		case CHS_WCON_CREQ:
			ch->flags &= ~CHF_BOTH_DIR;
			ch->i_state = CHS_ENABLED;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_DISCONNECT_IND\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_DISCONNECT_CON
 *  -----------------------------------
 *  Confirms to the channel user that the requested directions were
 *  disconnected as requested.
 */
static inline int ch_disconnect_con(queue_t *q, struct ch *ch, ulong flags)
{
	mblk_t *mp;
	struct CH_disconnect_con *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_DISCONNECT_CON;
		p->ch_conn_flags = flags;
		switch (ch->i_state) {
		case CHS_WCON_DREQ:
			ch->flags &= ~(flags & CHF_BOTH_DIR);
			if (ch->flags & CHF_BOTH_DIR)
				ch->i_state = CHS_CONNECTED;
			else
				ch->i_state = CHS_ENABLED;
			break;
		case CHS_UNUSABLE:
			ch->i_state = CHS_UNUSABLE;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_DISCONNECT_CON\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_DISABLE_IND
 *  -----------------------------------
 *  Indicates to the channel user that an autonomous disabling of the channel
 *  has occured.  This is normally used to indicate a fatal error on the
 *  underlying stream, but is optional.  The channel user should detach and
 *  reattach the stream.
 */
static inline int ch_disable_ind(queue_t *q, struct ch *ch, long cause)
{
	mblk_t *mp;
	struct CH_disable_ind *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_DISABLE_IND;
		p->ch_cause = cause;
		ch->i_state = CHS_UNUSABLE;
		printd(("%s: %p: <- CH_DISABLE_IND\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  CH_DISABLE_CON
 *  -----------------------------------
 */
static inline int ch_disable_con(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	struct CH_disable_con *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->ch_primitive = CH_DISABLE_CON;
		switch (ch->i_state) {
		case CHS_WACK_RREQ:
		case CHS_WCON_RREQ:
			ch->i_state = CHS_ATTACHED;
			ch->flags &= ~CHF_BOTH_DIR;
			break;
		case CHS_UNUSABLE:
			ch->i_state = CHS_UNUSABLE;
			ch->flags &= ~CHF_BOTH_DIR;
			break;
		default:
			swerr();
			freemsg(mp);
			return (-EFAULT);
		}
		printd(("%s: %p: <- CH_DISABLE_CON\n", CH_MOD_NAME, ch));
		putnext(ch->oq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  Primitive sent downstream
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  LMI_INFO_REQ
 *  -----------------------------------
 */
static inline int lmi_info_req(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	lmi_info_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_INFO_REQ;
		printd(("%s: %p: LMI_INFO_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  LMI_ATTACH_REQ
 *  -----------------------------------
 *  Requests that the provider attach the requesting stream to the specified
 *  PPA.  This is only valid for STYLE 2 devices.
 */
static inline int lmi_attach_req(queue_t *q, struct ch *ch, uchar *ppa_ptr, size_t ppa_len)
{
	mblk_t *mp;
	lmi_attach_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p) + ppa_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_ATTACH_REQ;
		if (ppa_len) {
			bcopy(ppa_ptr, mp->b_wptr, ppa_len);
			mp->b_wptr += ppa_len;
		}
		ch->i_state = CHS_WACK_AREQ;
		printd(("%s: %p: LMI_ATTACH_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  LMI_DETACH_REQ
 *  -----------------------------------
 *  Requests that the provider detach the requesting stream from the attached
 *  PPA.  This is only valid for STYLE 2 devices.
 */
static inline int lmi_detach_req(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	lmi_detach_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_DETACH_REQ;
		ch->i_state = CHS_WACK_UREQ;
		printd(("%s: %p: LMI_DETACH_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  LMI_ENABLE_REQ
 *  -----------------------------------
 *  Requests that the provider enable the attached stream to the specified
 *  remote address (if required).  Typically we do not have remote addresses
 *  for channels.
 */
static inline int lmi_enable_req(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	lmi_enable_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p) + ch->rem_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_ENABLE_REQ;
		if (ch->rem_len) {
			bcopy(ch->rem_ptr, mp->b_wptr, ch->rem_len);
			mp->b_wptr += ch->rem_len;
		}
		ch->i_state = CHS_WCON_EREQ;
		printd(("%s: %p: LMI_ENABLE_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  LMI_DISABLE_REQ
 *  -----------------------------------
 *  Requests that the provider disable the attached stream for the enabled
 *  remote address.
 */
static inline int lmi_disable_req(queue_t *q, struct ch *ch)
{
	mblk_t *mp;
	lmi_disable_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_DISABLE_REQ;
		ch->i_state = CHS_WCON_RREQ;
		printd(("%s: %p: LMI_DISABLE_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  LMI_OPTMGMT_REQ
 *  -----------------------------------
 *  Requests that the provider get, set or negotiate the specified options.
 */
static inline int lmi_optmgmt_req(queue_t *q, struct ch *ch, uchar *opt_ptr, size_t opt_len, ulong flags)
{
	mblk_t *mp;
	lmi_optmgmt_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p) + opt_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->lmi_primitive = LMI_OPTMGMT_REQ;
		p->lmi_opt_length = opt_len;
		p->lmi_opt_offset = opt_len ? sizeof(*p) : 0;
		p->lmi_mgmt_flags = flags;
		if (opt_len) {
			bcopy(opt_ptr, mp->b_wptr, opt_len);
			mp->b_wptr += opt_len;
		}
		printd(("%s: %p: LMI_OPTMGMT_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  SDL_BITS_FOR_TRANSMISSION_REQ
 *  -----------------------------------
 *  Requests transmission of the attached M_DATA blocks.  This is a
 *  non-preferred method for sending data to the lower level and is not used
 *  by this module.
 */
static inline int sdl_bits_for_transmission_req(queue_t *q, struct ch *ch, mblk_t *dp)
{
	mblk_t *mp;
	sdl_bits_for_transmission_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->sdl_primitive = SDL_BITS_FOR_TRANSMISSION_REQ;
		mp->b_cont = dp;
		printd(("%s: %p: SDL_BITS_FOR_TRANSMISSION_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  SDL_CONNECT_REQ
 *  -----------------------------------
 *  Requests that the provider connect the stream in the specified directions.
 */
static inline int sdl_connect_req(queue_t *q, struct ch *ch, ulong flags)
{
	mblk_t *mp;
	sdl_connect_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->sdl_primitive = SDL_CONNECT_REQ;
		p->sdl_flags = flags;
		ch->i_state = CHS_WCON_CREQ;
		printd(("%s: %p: SDL_CONNECT_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  SDL_DISCONNECT_REQ
 *  -----------------------------------
 */
static inline int sdl_disconnect_req(queue_t *q, struct ch *ch, ulong flags)
{
	mblk_t *mp;
	sdl_disconnect_req_t *p;
	if ((mp = ss7_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->sdl_primitive = SDL_DISCONNECT_REQ;
		p->sdl_flags = flags;
		ch->i_state = CHS_WCON_DREQ;
		printd(("%s: %p: SDL_DISCONNECT_REQ ->\n", CH_MOD_NAME, ch));
		putnext(ch->iq, mp);
		return (QR_DONE);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  Primitives from below
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  LMI_INFO_ACK
 *  -----------------------------------
 */
static int lmi_info_ack(queue_t *q, mblk_t *mp)
{
	/* discard */
	return (QR_DONE);
}

/*
 *  LMI_OK_ACK
 *  -----------------------------------
 */
static int lmi_ok_ack(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	return ch_ok_ack(q, ch);
}

/*
 *  LMI_ERROR_ACK
 *  -----------------------------------
 */
static int lmi_error_ack(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	long prim, error = -EFAULT;
	lmi_error_ack_t *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	switch (p->lmi_reason) {
	case LMI_BADADDRTYPE:
	case LMI_BADADDRESS:
	case LMI_BADPPA:
		error = CHBADADDR;
		break;
	case LMI_BADPRIM:
		error = CHBADPRIM;
		break;
	case LMI_NOTSUPP:
		error = CHNOTSUPP;
		break;
	case LMI_OUTSTATE:
		error = CHOUTSTATE;
		break;
	case LMI_SYSERR:
		error = -p->lmi_errno;
		break;
	}
	switch (ch->i_state) {
	case CHS_WACK_AREQ:
		prim = CH_ATTACH_REQ;
		break;
	case CHS_WACK_UREQ:
		prim = CH_DETACH_REQ;
		break;
	case CHS_WACK_EREQ:
	case CHS_WCON_EREQ:
		prim = CH_ENABLE_REQ;
		break;
	case CHS_WACK_RREQ:
	case CHS_WCON_RREQ:
		prim = CH_DISABLE_REQ;
		break;
	case CHS_WACK_CREQ:
	case CHS_WCON_CREQ:
		prim = CH_CONNECT_REQ;
		break;
	case CHS_WACK_DREQ:
	case CHS_WCON_DREQ:
		prim = CH_DISCONNECT_REQ;
		break;
	default:
		/* not expecting primitive */
		swerr();
		return (-EFAULT);
	}
	return ch_error_ack(q, ch, prim, error);
      emsgsize:
	swerr();
	return (-EFAULT);
}

/*
 *  LMI_ENABLE_CON
 *  -----------------------------------
 */
static int lmi_enable_con(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	return ch_enable_con(q, ch);
}

/*
 *  LMI_DISABLE_CON
 *  -----------------------------------
 */
static int lmi_disable_con(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	return ch_disable_con(q, ch);
}

/*
 *  LMI_OPTMGMT_ACK
 *  -----------------------------------
 */
static int lmi_optmgmt_ack(queue_t *q, mblk_t *mp)
{
	/* not expecting these */
	swerr();
	return (-EFAULT);
}

/*
 *  LMI_ERROR_IND
 *  -----------------------------------
 *  TODO: We need either an event indication or an error indication which
 *  indicates when a Red Alarm condition occurs so that we can notify of
 *  hardware failure.
 */
static int lmi_error_ind(queue_t *q, mblk_t *mp)
{
	/* not expecting these */
	swerr();
	return (-EFAULT);
}

/*
 *  LMI_STATS_IND
 *  -----------------------------------
 */
static int lmi_stats_ind(queue_t *q, mblk_t *mp)
{
	/* not expecting these */
	swerr();
	return (-EFAULT);
}

/*
 *  LMI_EVENT_IND
 *  -----------------------------------
 *  TODO: We need either an event indication or an error indication which
 *  indicates when a Red Alarm condition occurs so that we can notify of
 *  hardware failure.
 */
static int lmi_event_ind(queue_t *q, mblk_t *mp)
{
	/* not expecting these */
	swerr();
	return (-EFAULT);
}

/*
 *  M_DATA
 *  -----------------------------------
 */
static int ch_read(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	if (!mp || !msgdsize(mp))
		goto eproto;
	switch (ch->i_state) {
	case CHS_UNUSABLE:
	case CHS_ENABLED:
		goto discard;
	case CHS_WACK_CREQ:
	case CHS_WCON_CREQ:
	case CHS_WACK_DREQ:
	case CHS_WCON_DREQ:
	case CHS_CONNECTED:
		if (!(ch->flags & CHF_RX_DIR))
			goto discard;
		putnext(ch->oq, mp);
		return (QR_ABSORBED);
	}
	goto eproto;
      eproto:
	swerr();
	return (-EPROTO);
      discard:
	rare();
	return (QR_DONE);
}

/*
 *  SDL_RECEIVED_BITS_IND
 *  -----------------------------------
 */
static int sdl_received_bits_ind(queue_t *q, mblk_t *mp)
{
	sdl_received_bits_ind_t *p = (typeof(p)) mp->b_rptr;
	int err;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if ((err = ch_read(q, mp->b_cont)) == QR_ABSORBED)
		return (QR_TRIMMED);
	return (err);
      emsgsize:
	swerr();
	return (-EFAULT);
}

/*
 *  SDL_DISCONNECT_IND
 *  -----------------------------------
 */
static int sdl_disconnect_ind(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	sdl_disconnect_ind_t *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if (ch->i_state != CHS_CONNECTED && ch->i_state != CHS_WACK_CREQ && ch->i_state != CHS_WCON_CREQ)
		goto outstate;
	return ch_disconnect_ind(q, ch, CHF_BOTH_DIR);
      emsgsize:
	swerr();
	return (-EFAULT);
      outstate:
	swerr();
	return (-EPROTO);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  Primitives from above
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  M_DATA
 *  -----------------------------------
 *  Requests that the channel provider place the data contained in the M_DATA
 *  block onto the transmit stream of the channel.  This is the preferred
 *  method of sending data to the channel.
 */
static int ch_write(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	if (!mp || !msgdsize(mp))
		goto eproto;
	switch (ch->i_state) {
	case CHS_UNUSABLE:
	case CHS_ENABLED:
		goto discard;
	case CHS_WACK_CREQ:
	case CHS_WCON_CREQ:
	case CHS_WACK_DREQ:
	case CHS_WCON_DREQ:
	case CHS_CONNECTED:
		if (!(ch->flags & CHF_TX_DIR))
			goto discard;
		putnext(ch->iq, mp);
		return (QR_ABSORBED);
	}
	goto eproto;
      eproto:
	return m_error(q, ch, -EPROTO);
      discard:
	rare();
	return (QR_DONE);
}

/*
 *  CH_INFO_REQ
 *  -----------------------------------
 *  Requests that the channel provider return information about the provider
 *  and the attached channel (if any).
 */
static int ch_info_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_info_req *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if (ch->i_state == CHS_UNUSABLE)
		goto outstate;
	return ch_info_ack(q, ch);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_OPTMGMT_REQ
 *  -----------------------------------
 *  Requests that the channel provider set, get or negotiate provider or
 *  attached channel options.
 */
static int ch_optmgmt_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_optmgmt_req *p = (typeof(p)) mp->b_rptr;
	union CH_parms *o;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if (mp->b_wptr > mp->b_rptr + p->ch_opt_offset + p->ch_opt_length)
		goto emsgsize;
	switch (ch->i_state) {
	case CHS_DETACHED:
	case CHS_UNUSABLE:
	case CHS_WACK_AREQ:
	case CHS_WACK_UREQ:
		break;
	default:
		goto outstate;
	}
	if (p->ch_opt_length) {
		if (p->ch_opt_length < sizeof(o->cp_type))
			goto badopt;
		o = (typeof(o)) (mp->b_rptr + p->ch_opt_offset);
		switch (o->cp_type) {
		case CH_PARMS_CIRCUIT:
			if (p->ch_opt_length < sizeof(o->circuit))
				goto badparm;
			switch (p->ch_mgmt_flags) {
			case 0:
			case CH_NEGOTIATE:
				/* negotiate current */
			case CH_SET:
				/* set current */
				ch->config.block_size = o->circuit.cp_block_size;
				ch->config.encoding = o->circuit.cp_encoding;
				ch->config.sample_size = o->circuit.cp_sample_size;
				ch->config.rate = o->circuit.cp_rate;
				ch->config.tx_channels = o->circuit.cp_tx_channels;
				ch->config.rx_channels = o->circuit.cp_rx_channels;
				ch->config.opt_flags = o->circuit.cp_opt_flags;
				return ch_optmgmt_ack(q, ch, (uchar *) o, sizeof(o->circuit), p->ch_mgmt_flags);
			case CH_DEFAULT:
				/* set default */
				ch_default.block_size = o->circuit.cp_block_size;
				ch_default.encoding = o->circuit.cp_encoding;
				ch_default.sample_size = o->circuit.cp_sample_size;
				ch_default.rate = o->circuit.cp_rate;
				ch_default.tx_channels = o->circuit.cp_tx_channels;
				ch_default.rx_channels = o->circuit.cp_rx_channels;
				ch_default.opt_flags = o->circuit.cp_opt_flags;
				return ch_optmgmt_ack(q, ch, (uchar *) o, sizeof(o->circuit), p->ch_mgmt_flags);
			}
		}
		goto badparmtype;
	} else {
		union CH_parms parms;
		o = &parms;
		/* no options */
		switch (p->ch_mgmt_flags) {
		case CH_GET:
			/* get current */
			o->cp_type = CH_PARMS_CIRCUIT;
			o->circuit.cp_block_size = ch->config.block_size;
			o->circuit.cp_encoding = ch->config.encoding;
			o->circuit.cp_sample_size = ch->config.sample_size;
			o->circuit.cp_rate = ch->config.rate;
			o->circuit.cp_tx_channels = ch->config.tx_channels;
			o->circuit.cp_rx_channels = ch->config.rx_channels;
			o->circuit.cp_opt_flags = ch->config.opt_flags;
			return ch_optmgmt_ack(q, ch, (uchar *) o, sizeof(o->circuit), p->ch_mgmt_flags);
		case 0:
		case CH_DEFAULT:
			/* get default */
			o->cp_type = CH_PARMS_CIRCUIT;
			o->circuit.cp_block_size = ch_default.block_size;
			o->circuit.cp_encoding = ch_default.encoding;
			o->circuit.cp_sample_size = ch_default.sample_size;
			o->circuit.cp_rate = ch_default.rate;
			o->circuit.cp_tx_channels = ch_default.tx_channels;
			o->circuit.cp_rx_channels = ch_default.rx_channels;
			o->circuit.cp_opt_flags = ch_default.opt_flags;
			return ch_optmgmt_ack(q, ch, (uchar *) o, sizeof(o->circuit), p->ch_mgmt_flags);
		}
	}
	goto badflag;
      badflag:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADFLAG);
      badparmtype:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADPARMTYPE);
      badparm:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADPARM);
      badopt:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADOPT);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_ATTACH_REQ
 *  -----------------------------------
 *  Requests that the channel provider attached the requesting stream to the
 *  specified channel (specified in the channel address).
 */
static int ch_attach_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_attach_req *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if (mp->b_wptr > mp->b_rptr + p->ch_addr_offset + p->ch_addr_length)
		goto emsgsize;
	if (ch->i_state != CHS_DETACHED)
		goto outstate;
	return lmi_attach_req(q, ch, mp->b_rptr + p->ch_addr_offset, p->ch_addr_length);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_ENABLE_REQ
 *  -----------------------------------
 *  Requests that the channel provider enable the attached channel.
 */
static int ch_enable_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_enable_req *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	if (ch->i_state != CHS_ATTACHED)
		goto outstate;
	return lmi_enable_req(q, ch);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_CONNECT_REQ
 *  -----------------------------------
 *  Requests that the channel provider connect the specified direction on the
 *  attached and enabled channel.
 */
static int ch_connect_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_connect_req *p = (typeof(p)) mp->b_rptr;
	int err;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	switch (ch->i_state) {
	case CHS_ENABLED:
	case CHS_CONNECTED:
		if (!(p->ch_conn_flags & CHF_BOTH_DIR))
			goto badflag;
		if (((ch->flags & p->ch_conn_flags) & CHF_BOTH_DIR))
			goto badflag;
		if ((err = sdl_connect_req(q, ch, p->ch_conn_flags & CHF_BOTH_DIR)))
			return (err);
		/* fall through */
	case CHS_WCON_CREQ:
		/* automatic confirmation */
		return ch_connect_con(q, ch, p->ch_conn_flags & CHF_BOTH_DIR);
	}
	goto outstate;
      badflag:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADFLAG);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_DATA_REQ
 *  -----------------------------------
 *  Requests that the provider send data on the connected channel.
 */
static int ch_data_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_data_req *p = (typeof(p)) mp->b_rptr;
	int err;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto eproto;
	if ((err = ch_write(q, mp->b_cont)) == QR_ABSORBED)
		return (QR_TRIMMED);
	return (err);
      eproto:
	return m_error(q, ch, -EPROTO);
}

/*
 *  CH_DISCONNECT_REQ
 *  -----------------------------------
 *  Requests that the provider disconnect the specified direction for the
 *  attached, enabled and connected channel.
 */
static int ch_disconnect_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_disconnect_req *p = (typeof(p)) mp->b_rptr;
	int err;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	switch (ch->i_state) {
	case CHS_CONNECTED:
		if (!((ch->flags & p->ch_conn_flags) & CHF_BOTH_DIR))
			goto badflag;
		if ((err = sdl_disconnect_req(q, ch, p->ch_conn_flags & CHF_BOTH_DIR)))
			return (err);
		/* fall through */
	case CHS_WCON_DREQ:
	case CHS_UNUSABLE:
		/* automatic confirmation */
		return ch_disconnect_con(q, ch, p->ch_conn_flags & CHF_BOTH_DIR);
	}
	goto outstate;
      badflag:
	return ch_error_ack(q, ch, p->ch_primitive, CHBADFLAG);
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_DISABLE_REQ
 *  -----------------------------------
 *  Requests that the provider disable the attached and enabled channel.
 */
static int ch_disable_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_disable_req *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	switch (ch->i_state) {
	case CHS_ENABLED:
		return lmi_disable_req(q, ch);
	case CHS_UNUSABLE:
		return ch_disable_con(q, ch);
	}
	goto outstate;
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  CH_DETACH_REQ
 *  -----------------------------------
 *  Requests that the provider detach the attached channel.
 */
static int ch_detach_req(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	struct CH_detach_req *p = (typeof(p)) mp->b_rptr;
	if (mp->b_wptr > mp->b_rptr + sizeof(*p))
		goto emsgsize;
	switch (ch->i_state) {
	case CHS_ATTACHED:
		return lmi_detach_req(q, ch);
	case CHS_UNUSABLE:
		return ch_ok_ack(q, ch);
	}
	goto outstate;
      outstate:
	return ch_error_ack(q, ch, p->ch_primitive, CHOUTSTATE);
      emsgsize:
	return ch_error_ack(q, ch, p->ch_primitive, -EMSGSIZE);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  IO CONTROLS
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  CH_IOCGCONFIG
 *  -----------------------------------
 */
static int ch_iocgconfig(struct ch *ch, struct ch_config *p)
{
	*p = ch->config;
	return (0);
}

/*
 *  CH_IOCSCONFIG
 *  -----------------------------------
 */
static int ch_ioctconfig(struct ch *, struct ch_config *);
static int ch_iocsconfig(struct ch *ch, struct ch_config *p)
{
	int err;
	if ((err = ch_ioctconfig(ch, p)))
		return (err);
	ch->config = *p;
	return (0);
}

/*
 *  CH_IOCTCONFIG
 *  -----------------------------------
 */
static int ch_ioctconfig(struct ch *ch, struct ch_config *p)
{
	if (p->block_size <= 0 || p->block_size & 0x7)
		goto einval;
	switch (p->encoding) {
	case CH_ENCODING_NONE:
	case CH_ENCODING_G711_PCM_A:
	case CH_ENCODING_G711_PCM_U:
	case CH_ENCODING_S8:
	case CH_ENCODING_U8:
		break;
	default:
		goto einval;
	}
	if (p->sample_size <= 0 || p->sample_size > 8)
		goto einval;
	if (p->rate != 8000)
		goto einval;
	if (p->tx_channels != 1)
		goto einval;
	if (p->rx_channels != 1)
		goto einval;
	return (0);
      einval:
	return (-EINVAL);
}

/*
 *  CH_IOCCCONFIG
 *  -----------------------------------
 */
static int ch_ioccconfig(struct ch *ch, struct ch_config *p)
{
	*p = ch->config = ch_default;
	return (0);
}

/*
 *  CH_IOCGSTATEM
 *  -----------------------------------
 */
static int ch_iocgstatem(struct ch *ch, struct ch_statem *p)
{
	p->state = ch->i_state;
	p->flags = ch->flags;
	return (0);
}

/*
 *  CH_IOCCMRESET
 *  -----------------------------------
 */
static int ch_ioccmreset(struct ch *ch, struct ch_statem *p)
{
	ch->i_state = p->state;
	ch->flags = p->flags;
	return (0);
}

/*
 *  CH_IOCGSTATSP
 *  -----------------------------------
 */
static int ch_iocgstatsp(struct ch *ch, struct ch_stats *p)
{
	*p = ch->statsp;
	return (0);
}

/*
 *  CH_IOCSSTATSP
 *  -----------------------------------
 */
static int ch_iocsstatsp(struct ch *ch, struct ch_stats *p)
{
	ch->statsp = *p;
	return (0);
}

/*
 *  CH_IOCGSTATS
 *  -----------------------------------
 */
static int ch_iocgstats(struct ch *ch, struct ch_stats *p)
{
	*p = ch->stats;
	return (0);
}

/*
 *  CH_IOCCSTATS
 *  -----------------------------------
 */
static int ch_ioccstats(struct ch *ch, struct ch_stats *p)
{
	bzero(&ch->stats, sizeof(ch->stats));
	return (0);
}

/*
 *  CH_IOCGNOTIFY
 *  -----------------------------------
 */
static int ch_iocgnotify(struct ch *ch, struct ch_notify *p)
{
	*p = ch->notify;
	return (0);
}

/*
 *  CH_IOCSNOTIFY
 *  -----------------------------------
 */
static int ch_iocsnotify(struct ch *ch, struct ch_notify *p)
{
	ch->notify.events |= p->events;
	return (0);
}

/*
 *  CH_IOCCNOTIFY
 *  -----------------------------------
 */
static int ch_ioccnotify(struct ch *ch, struct ch_notify *p)
{
	ch->notify.events &= ~p->events;
	return (0);
}

/*
 *  SDL_IOCGCONFIG
 *  -----------------------------------
 */
static int sdl_iocgconfig_req(queue_t *q)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCGCONFIG;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_config_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_config_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bzero(dp->b_wptr, sizeof(sdl_config_t));
			dp->b_wptr += sizeof(sdl_config_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
static int sdl_iocgconfig_ack(struct ch *ch, sdl_config_t * p)
{
	/* ignore */
	switch (ch->i_state) {
	case CHS_UNINIT:
		switch (p->iftype) {
		case SDL_TYPE_DS0:
			ch->config.sample_size = 8;
			ch->config.rate = 8000;
			break;
		case SDL_TYPE_DS0A:
			ch->config.sample_size = 7;
			ch->config.rate = 8000;
			break;
		default:
			goto einval;
		}
		switch (p->ifgtype) {
		case SDL_GTYPE_E1:
			ch->config.encoding = CH_ENCODING_G711_PCM_A;
			break;
		case SDL_GTYPE_T1:
			ch->config.encoding = CH_ENCODING_G711_PCM_U;
			break;
		default:
			goto einval;
		}
		switch (p->ifmode) {
		case SDL_MODE_NONE:
		case SDL_MODE_PEER:
			break;
		default:
			goto einval;
		}
		switch (p->ifgmode) {
		case SDL_GMODE_NONE:
			break;
		default:
			goto einval;
		}
		switch (p->ifcoding) {
		case SDL_CODING_AMI:
		case SDL_CODING_HDB3:
		case SDL_CODING_B8ZS:
			break;
		default:
			goto einval;
		}
		switch (p->ifframing) {
		case SDL_FRAMING_CCS:
		case SDL_FRAMING_CAS:
		case SDL_FRAMING_SF:
		case SDL_FRAMING_ESF:
			break;
		default:
			goto einval;
		}
		if (p->ifalarms & SDL_ALARM_RED) {
		}
		ch->i_state = CHS_DETACHED;
		return (QR_DONE);
	default:
		swerr();
		return (-EFAULT);
	}
      einval:
	swerr();
	ch->i_state = CHS_UNUSABLE;
	return m_error(ch->oq, ch, -EFAULT);
}
static int sdl_iocgconfig_nak(struct ch *ch, sdl_config_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  SDL_IOCSSTATSP
 *  -----------------------------------
 */
#if 0
static int sdl_iocsstatsp_req(queue_t *q, sdl_stats_t * p)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCSSTATSP;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_stats_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_stats_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bcopy(p, dp->b_wptr, sizeof(sdl_stats_t));
			dp->b_wptr += sizeof(sdl_stats_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
#endif
static int sdl_iocsstatsp_ack(struct ch *ch, sdl_stats_t * p)
{
	/* ignore */
	return (QR_DONE);
}
static int sdl_iocsstatsp_nak(struct ch *ch, sdl_stats_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  SDL_IOCGSTATS
 *  -----------------------------------
 */
#if 0
static int sdl_iocgstats_req(queue_t *q)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCGSTATS;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_stats_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_stats_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bzero(dp->b_wptr, sizeof(sdl_stats_t));
			dp->b_wptr += sizeof(sdl_stats_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
#endif
static int sdl_iocgstats_ack(struct ch *ch, sdl_stats_t * p)
{
	/* ignore */
	return (QR_DONE);
}
static int sdl_iocgstats_nak(struct ch *ch, sdl_stats_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  SDL_IOCCSTATS
 *  -----------------------------------
 */
#if 0
static int sdl_ioccstats_req(queue_t *q)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCCSTATS;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_stats_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_stats_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bzero(dp->b_wptr, sizeof(sdl_stats_t));
			dp->b_wptr += sizeof(sdl_stats_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
#endif
static int sdl_ioccstats_ack(struct ch *ch, sdl_stats_t * p)
{
	/* ignore */
	return (QR_DONE);
}
static int sdl_ioccstats_nak(struct ch *ch, sdl_stats_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  SDL_IOCSNOTIFY
 *  -----------------------------------
 */
#if 0
static int sdl_iocsnotify_req(queue_t *q, sdl_notify_t * p)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCSNOTIFY;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_notify_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_notify_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bcopy(p, dp->b_wptr, sizeof(sdl_notify_t));
			dp->b_wptr += sizeof(sdl_notify_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
#endif
static int sdl_iocsnotify_ack(struct ch *ch, sdl_notify_t * p)
{
	/* ignore */
	return (QR_DONE);
}
static int sdl_iocsnotify_nak(struct ch *ch, sdl_notify_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  SDL_IOCCNOTIFY
 *  -----------------------------------
 */
#if 0
static int sdl_ioccnotify_req(queue_t *q, sdl_notify_t * p)
{
	struct ch *ch = CH_PRIV(q);
	mblk_t *mp, *dp;
	struct iocblk *iocp;
	if ((mp = ss7_allocb(q, sizeof(*iocp), BPRI_MED))) {
		mp->b_datap->db_type = M_IOCTL;
		bzero(mp->b_wptr, sizeof(*iocp));
		iocp = ((typeof(iocp)) mp->b_wptr)++;
		iocp->ioc_cmd = SDL_IOCCNOTIFY;
		iocp->ioc_id = (uint) ch;
		iocp->ioc_count = sizeof(sdl_notify_t);
		iocp->ioc_error = 0;
		iocp->ioc_rval = 0;
		if ((dp = ss7_allocb(q, sizeof(sdl_notify_t), BPRI_HI))) {
			dp->b_datap->db_type = M_DATA;
			bcopy(p, dp->b_wptr, sizeof(sdl_notify_t));
			dp->b_wptr += sizeof(sdl_notify_t);
			mp->b_cont = dp;
			putnext(ch->iq, mp);
			return (QR_DONE);
		}
		freemsg(mp);
	}
	rare();
	return (-ENOBUFS);
}
#endif
static int sdl_ioccnotify_ack(struct ch *ch, sdl_notify_t * p)
{
	/* ignore */
	return (QR_DONE);
}
static int sdl_ioccnotify_nak(struct ch *ch, sdl_notify_t * p)
{
	swerr();
	return m_error(ch->oq, ch, -EFAULT);
}

/*
 *  =========================================================================
 *
 *  STREAMS Message Handling
 *
 *  =========================================================================
 *
 *  M_IOCTL Handling
 *
 *  -------------------------------------------------------------------------
 */
static int ch_w_ioctl(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	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 type = _IOC_TYPE(cmd), nr = _IOC_NR(cmd), size = _IOC_SIZE(cmd);
	int ret = 0;
	switch (type) {
	case CH_IOC_MAGIC:
	{
		/* These are CH IOCTLs. */
		if (count < size || !arg) {
			ret = -EINVAL;
			break;
		}
		switch (nr) {
		case _IOC_NR(CH_IOCGCONFIG):
			ret = ch_iocgconfig(ch, arg);
			break;
		case _IOC_NR(CH_IOCSCONFIG):
			ret = ch_iocsconfig(ch, arg);
			break;
		case _IOC_NR(CH_IOCTCONFIG):
			ret = ch_ioctconfig(ch, arg);
			break;
		case _IOC_NR(CH_IOCCCONFIG):
			ret = ch_ioccconfig(ch, arg);
			break;
		case _IOC_NR(CH_IOCGSTATEM):
			ret = ch_iocgstatem(ch, arg);
			break;
		case _IOC_NR(CH_IOCCMRESET):
			ret = ch_ioccmreset(ch, arg);
			break;
		case _IOC_NR(CH_IOCGSTATSP):
			ret = ch_iocgstatsp(ch, arg);
			break;
		case _IOC_NR(CH_IOCSSTATSP):
			ret = ch_iocsstatsp(ch, arg);
			break;
		case _IOC_NR(CH_IOCGSTATS):
			ret = ch_iocgstats(ch, arg);
			break;
		case _IOC_NR(CH_IOCCSTATS):
			ret = ch_ioccstats(ch, arg);
			break;
		case _IOC_NR(CH_IOCGNOTIFY):
			ret = ch_iocgnotify(ch, arg);
			break;
		case _IOC_NR(CH_IOCSNOTIFY):
			ret = ch_iocsnotify(ch, arg);
			break;
		case _IOC_NR(CH_IOCCNOTIFY):
			ret = ch_ioccnotify(ch, arg);
			break;
		default:
			ptrace(("%s: ERROR: Unsupported CH ioctl %d\n", CH_MOD_NAME, nr));
			ret = -EOPNOTSUPP;
			break;
		}
		break;
	}
	default:
		/* pass down what we don't understand */
		return (QR_PASSALONG);
	}
	if (ret > 0) {
		return (ret);
	} else 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 (QR_ABSORBED);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_IOCACK, M_IOCNAK Handling
 *
 *  -------------------------------------------------------------------------
 */
static int ch_r_iocack(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	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 type = _IOC_TYPE(cmd), nr = _IOC_NR(cmd), size = _IOC_SIZE(cmd);
	if (iocp->ioc_id != (uint) ch)
		/* we didn't send it */
		return (QR_PASSALONG);
	switch (type) {
	case SDL_IOC_MAGIC:
		if (count < size || !arg)
			goto efault;
		switch (nr) {
		case _IOC_NR(SDL_IOCGCONFIG):
			return sdl_iocgconfig_ack(ch, arg);
		case _IOC_NR(SDL_IOCSSTATSP):
			return sdl_iocsstatsp_ack(ch, arg);
		case _IOC_NR(SDL_IOCGSTATS):
			return sdl_iocgstats_ack(ch, arg);
		case _IOC_NR(SDL_IOCCSTATS):
			return sdl_ioccstats_ack(ch, arg);
		case _IOC_NR(SDL_IOCSNOTIFY):
			return sdl_iocsnotify_ack(ch, arg);
		case _IOC_NR(SDL_IOCCNOTIFY):
			return sdl_ioccnotify_ack(ch, arg);
		case _IOC_NR(SDL_IOCSCONFIG):
		case _IOC_NR(SDL_IOCTCONFIG):
		case _IOC_NR(SDL_IOCCCONFIG):
		case _IOC_NR(SDL_IOCGSTATEM):
		case _IOC_NR(SDL_IOCCMRESET):
		case _IOC_NR(SDL_IOCGSTATSP):
		case _IOC_NR(SDL_IOCGNOTIFY):
			break;
		}
		goto efault;
	}
      efault:
	swerr();
	return (-EFAULT);
}
static int ch_r_iocnak(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	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 type = _IOC_TYPE(cmd), nr = _IOC_NR(cmd), size = _IOC_SIZE(cmd);
	if (iocp->ioc_id != (uint) ch)
		/* we didn't send it */
		return (QR_PASSALONG);
	switch (type) {
	case SDL_IOC_MAGIC:
		if (count < size || !arg)
			goto efault;
		switch (nr) {
		case _IOC_NR(SDL_IOCGCONFIG):
			return sdl_iocgconfig_nak(ch, arg);
		case _IOC_NR(SDL_IOCSSTATSP):
			return sdl_iocsstatsp_nak(ch, arg);
		case _IOC_NR(SDL_IOCGSTATS):
			return sdl_iocgstats_nak(ch, arg);
		case _IOC_NR(SDL_IOCCSTATS):
			return sdl_ioccstats_nak(ch, arg);
		case _IOC_NR(SDL_IOCSNOTIFY):
			return sdl_iocsnotify_nak(ch, arg);
		case _IOC_NR(SDL_IOCCNOTIFY):
			return sdl_ioccnotify_nak(ch, arg);
		case _IOC_NR(SDL_IOCSCONFIG):
		case _IOC_NR(SDL_IOCTCONFIG):
		case _IOC_NR(SDL_IOCCCONFIG):
		case _IOC_NR(SDL_IOCGSTATEM):
		case _IOC_NR(SDL_IOCCMRESET):
		case _IOC_NR(SDL_IOCGSTATSP):
		case _IOC_NR(SDL_IOCGNOTIFY):
			break;
		}
		goto efault;
	}
      efault:
	swerr();
	return (-EFAULT);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_PROTO, M_PCPROTO Handling
 *
 *  -------------------------------------------------------------------------
 */
/*
 *  Primitives from MG to CH.
 *  -----------------------------------
 */
static int ch_w_proto(queue_t *q, mblk_t *mp)
{
	int rtn;
	struct ch *ch = CH_PRIV(q);
	ulong prim;
	(void) ch;
	switch ((prim = *(ulong *) mp->b_rptr)) {
	case CH_INFO_REQ:
		printd(("%s: %p: -> CH_INFO_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_info_req(q, mp);
		break;
	case CH_OPTMGMT_REQ:
		printd(("%s: %p: -> CH_OPTMGMT_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_optmgmt_req(q, mp);
		break;
	case CH_ATTACH_REQ:
		printd(("%s: %p: -> CH_ATTACH_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_attach_req(q, mp);
		break;
	case CH_ENABLE_REQ:
		printd(("%s: %p: -> CH_ENABLE_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_enable_req(q, mp);
		break;
	case CH_CONNECT_REQ:
		printd(("%s: %p: -> CH_CONNECT_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_connect_req(q, mp);
		break;
	case CH_DATA_REQ:
		printd(("%s: %p: -> CH_DATA_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_data_req(q, mp);
		break;
	case CH_DISCONNECT_REQ:
		printd(("%s: %p: -> CH_DISCONNECT_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_disconnect_req(q, mp);
		break;
	case CH_DISABLE_REQ:
		printd(("%s: %p: -> CH_DISABLE_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_disable_req(q, mp);
		break;
	case CH_DETACH_REQ:
		printd(("%s: %p: -> CH_DETACH_REQ\n", CH_MOD_NAME, ch));
		rtn = ch_detach_req(q, mp);
		break;
	default:
		printd(("%s: %p: -> CH_????\n", CH_MOD_NAME, ch));
		rtn = ch_error_ack(q, ch, prim, CHNOTSUPP);
		break;
	}
	return (rtn);
}

/*
 *  Primitives from SDL to CH.
 *  -----------------------------------
 */
static int ch_r_proto(queue_t *q, mblk_t *mp)
{
	int rtn;
	struct ch *ch = CH_PRIV(q);
	(void) ch;
	switch (*((ulong *) mp->b_rptr)) {
	case LMI_INFO_ACK:
		printd(("%s: %p: LMI_INFO_ACK <-\n", CH_MOD_NAME, ch));
		rtn = lmi_info_ack(q, mp);
		break;
	case LMI_OK_ACK:
		printd(("%s: %p: LMI_OK_ACK <-\n", CH_MOD_NAME, ch));
		rtn = lmi_ok_ack(q, mp);
		break;
	case LMI_ERROR_ACK:
		printd(("%s: %p: LMI_ERROR_ACK <-\n", CH_MOD_NAME, ch));
		rtn = lmi_error_ack(q, mp);
		break;
	case LMI_ENABLE_CON:
		printd(("%s: %p: LMI_ENABLE_CON <-\n", CH_MOD_NAME, ch));
		rtn = lmi_enable_con(q, mp);
		break;
	case LMI_DISABLE_CON:
		printd(("%s: %p: LMI_DISABLE_CON <-\n", CH_MOD_NAME, ch));
		rtn = lmi_disable_con(q, mp);
		break;
	case LMI_OPTMGMT_ACK:
		printd(("%s: %p: LMI_OPTMGMT_ACK <-\n", CH_MOD_NAME, ch));
		rtn = lmi_optmgmt_ack(q, mp);
		break;
	case LMI_ERROR_IND:
		printd(("%s: %p: LMI_ERROR_IND <-\n", CH_MOD_NAME, ch));
		rtn = lmi_error_ind(q, mp);
		break;
	case LMI_STATS_IND:
		printd(("%s: %p: LMI_STATS_IND <-\n", CH_MOD_NAME, ch));
		rtn = lmi_stats_ind(q, mp);
		break;
	case LMI_EVENT_IND:
		printd(("%s: %p: LMI_EVENT_IND <-\n", CH_MOD_NAME, ch));
		rtn = lmi_event_ind(q, mp);
		break;
	case SDL_RECEIVED_BITS_IND:
		printd(("%s: %p: SDL_RECEIVED_BITS_IND <-\n", CH_MOD_NAME, ch));
		rtn = sdl_received_bits_ind(q, mp);
		break;
	case SDL_DISCONNECT_IND:
		printd(("%s: %p: SDL_DISCONNECT_IND <-\n", CH_MOD_NAME, ch));
		rtn = sdl_disconnect_ind(q, mp);
		break;
	default:
		printd(("%s: %p: ???? %lu <-\n", CH_MOD_NAME, ch, *(ulong *) mp->b_rptr));
		rtn = -EFAULT;
		break;
	}
	return (rtn);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_DATA Handling
 *
 *  -------------------------------------------------------------------------
 */
static int ch_w_data(queue_t *q, mblk_t *mp)
{
	/* data from above */
	printd(("%s: %p: -> M_DATA\n", CH_MOD_NAME, CH_PRIV(q)));
	return ch_write(q, mp);
}
static int ch_r_data(queue_t *q, mblk_t *mp)
{
	/* data from below */
	printd(("%s: %p: M_DATA <-\n", CH_MOD_NAME, CH_PRIV(q)));
	return ch_read(q, mp);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_ERROR, M_HANGUP Handling
 *
 *  -------------------------------------------------------------------------
 */
static int ch_r_error(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	int err;
	switch (ch->i_state) {
	default:
		/* warn user before we error out the stream */
		if ((err = ch_disable_ind(q, ch, -mp->b_rptr[0])))
			return (err);
		/* fall through */
	case CHS_UNUSABLE:
		return (QR_PASSALONG);
	}
}
static int ch_r_hangup(queue_t *q, mblk_t *mp)
{
	struct ch *ch = CH_PRIV(q);
	int err;
	switch (ch->i_state) {
	default:
		/* warn user before we error out the stream */
		if ((err = ch_disable_ind(q, ch, -EPIPE)))
			return (err);
		/* fall through */
	case CHS_UNUSABLE:
		return (QR_PASSALONG);
	}
}

/*
 *  =========================================================================
 *
 *  PUT and SRV
 *
 *  =========================================================================
 */
static inline int ch_w_prim(queue_t *q, mblk_t *mp)
{
	/* Fast Path */
	if (mp->b_datap->db_type == M_DATA)
		return ch_w_data(q, mp);
	switch (mp->b_datap->db_type) {
	case M_DATA:
		return ch_w_data(q, mp);
	case M_PROTO:
	case M_PCPROTO:
		return ch_w_proto(q, mp);
	case M_FLUSH:
		return ss7_w_flush(q, mp);
	case M_IOCTL:
		return ch_w_ioctl(q, mp);
	}
	return (QR_PASSALONG);
}
static inline int ch_r_prim(queue_t *q, mblk_t *mp)
{
	/* Fast Path */
	if (mp->b_datap->db_type == M_DATA)
		return ch_r_data(q, mp);
	switch (mp->b_datap->db_type) {
	case M_DATA:
		return ch_r_data(q, mp);
	case M_PROTO:
	case M_PCPROTO:
		return ch_r_proto(q, mp);
	case M_FLUSH:
		return ss7_r_flush(q, mp);
	case M_ERROR:
		return ch_r_error(q, mp);
	case M_HANGUP:
		return ch_r_hangup(q, mp);
	case M_IOCACK:
		return ch_r_iocack(q, mp);
	case M_IOCNAK:
		return ch_r_iocnak(q, mp);
	}
	return (QR_PASSALONG);
}

/*
 *  =========================================================================
 *
 *  OPEN and CLOSE
 *
 *  =========================================================================
 */
/*
 *  OPEN
 *  -------------------------------------------------------------------------
 */
static int ch_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
{
	MOD_INC_USE_COUNT;	/* keep module from unloading */
	if (q->q_ptr != NULL) {
		MOD_DEC_USE_COUNT;
		return (0);	/* already open */
	}
	if (sflag == MODOPEN || WR(q)->q_next != NULL) {
		int cmajor = getmajor(*devp);
		int cminor = getminor(*devp);
		struct ch *ch;
		for (ch = ch_opens; ch; ch = ch->next) {
			if (ch->u.dev.cmajor == cmajor && ch->u.dev.cminor == cminor) {
				MOD_DEC_USE_COUNT;
				return (ENXIO);
			}
		}
		if (!ch_alloc_priv(q, &ch_opens, devp, crp)) {
			MOD_DEC_USE_COUNT;
			return (ENOMEM);
		}
		/* generate immediate information request */
		sdl_iocgconfig_req(q);
		return (0);
	}
	MOD_DEC_USE_COUNT;
	return (EIO);
}

/*
 *  CLOSE
 *  -------------------------------------------------------------------------
 */
static int ch_close(queue_t *q, int flag, cred_t *crp)
{
	(void) flag;
	(void) crp;
	ch_free_priv(q);
	MOD_DEC_USE_COUNT;
	return (0);
}

/*
 *  =========================================================================
 *
 *  Private Structure allocation, deallocation and cache
 *
 *  =========================================================================
 */
static kmem_cache_t *ch_priv_cachep = NULL;
static int ch_init_caches(void)
{
	if (!ch_priv_cachep &&
	    !(ch_priv_cachep =
	      kmem_cache_create("ch_priv_cachep", sizeof(struct ch), 0, SLAB_HWCACHE_ALIGN, NULL, NULL))) {
		cmn_err(CE_PANIC, "%s: did not allocate ch_priv_cachep", CH_MOD_NAME);
		return (-ENOMEM);
	} else
		printd(("%s: initialized ch private structure cache\n", CH_MOD_NAME));
	return (0);
}
static void ch_term_caches(void)
{
	if (ch_priv_cachep) {
		if (kmem_cache_destroy(ch_priv_cachep))
			cmn_err(CE_WARN, "%s: did not destroy ch_priv_cachep", __FUNCTION__);
		else
			printd(("%s: destroyed ch_priv_cachep\n", CH_MOD_NAME));
	}
	return;
}

/*
 *  CH allocation and deallocation
 *  -------------------------------------------------------------------------
 */
static struct ch *ch_alloc_priv(queue_t *q, struct ch **chp, dev_t *devp, cred_t *crp)
{
	struct ch *ch;
	if ((ch = kmem_cache_alloc(ch_priv_cachep, SLAB_ATOMIC))) {
		printd(("%s: %p: allocated ch private structure\n", CH_MOD_NAME, ch));
		bzero(ch, sizeof(*ch));
		ch_get(ch);	/* first get */
		ch->u.dev.cmajor = getmajor(*devp);
		ch->u.dev.cminor = getminor(*devp);
		ch->cred = *crp;
		ch->o_prim = &ch_r_prim;
		ch->i_prim = &ch_w_prim;
		ch->o_wakeup = NULL;
		ch->i_wakeup = NULL;
		(ch->oq = RD(q))->q_ptr = ch_get(ch);
		(ch->iq = WR(q))->q_ptr = ch_get(ch);
		lis_spin_lock_init(&ch->qlock, "ch-queue-lock");
		ch->i_state = CHS_UNINIT;	/* unitialized */
		ch->i_version = 1;
		ch->i_style = 0;
		lis_spin_lock_init(&ch->lock, "ch-priv-lock");
		if ((ch->next = *chp))
			ch->next->prev = &ch->next;
		ch->prev = chp;
		*chp = ch_get(ch);
		printd(("%s: %p: linked ch private structure\n", CH_MOD_NAME, ch));
		ch->config = ch_default;
	} else
		ptrace(("%s: ERROR: Could not allocate ch private structure\n", CH_MOD_NAME));
	return (ch);
}
static void ch_free_priv(queue_t *q)
{
	struct ch *ch = CH_PRIV(q);
	int flags = 0;
	ensure(ch, return);
	lis_spin_lock_irqsave(&ch->lock, &flags);
	{
		ss7_unbufcall((str_t *) ch);
		if ((*ch->prev = ch->next))
			ch->next->prev = ch->prev;
		ch->next = NULL;
		ch->prev = &ch->next;
		ch_put(ch);
		ch->oq->q_ptr = NULL;
		flushq(ch->oq, FLUSHALL);
		ch->oq = NULL;
		ch_put(ch);
		ch->iq->q_ptr = NULL;
		flushq(ch->iq, FLUSHALL);
		ch->iq = NULL;
		ch_put(ch);
	}
	lis_spin_unlock_irqrestore(&ch->lock, &flags);
	ch_put(ch);		/* final put */
	return;
}
static struct ch *ch_get(struct ch *ch)
{
	atomic_inc(&ch->refcnt);
	return (ch);
}
static void ch_put(struct ch *ch)
{
	if (atomic_dec_and_test(&ch->refcnt)) {
		kmem_cache_free(ch_priv_cachep, ch);
		printd(("%s: %p: freed ch private structure\n", CH_MOD_NAME, ch));
	}
}

/*
 *  =========================================================================
 *
 *  LiS Module Initialization (For unregistered driver.)
 *
 *  =========================================================================
 */
static int ch_initialized = 0;
static void ch_init(void)
{
	unless(ch_initialized > 0, return);
	cmn_err(CE_NOTE, CH_BANNER);	/* console splash */
	if ((ch_initialized = ch_init_caches())) {
		cmn_err(CE_PANIC, "%s: ERROR: could not allocate caches", CH_MOD_NAME);
	} else if ((ch_initialized = lis_register_strmod(&ch_info, CH_MOD_NAME)) < 0) {
		cmn_err(CE_WARN, "%s: could not register module", CH_MOD_NAME);
		ch_term_caches();
	}
	return;
}
static void ch_terminate(void)
{
	ensure(ch_initialized > 0, return);
	if ((ch_initialized = lis_unregister_strmod(&ch_info)) < 0) {
		cmn_err(CE_PANIC, "%s: could not unregister module", CH_MOD_NAME);
	} else {
		ch_term_caches();
	}
	return;
}

/*
 *  =========================================================================
 *
 *  Kernel Module Initialization
 *
 *  =========================================================================
 */
int init_module(void)
{
	ch_init();
	if (ch_initialized < 0)
		return ch_initialized;
	return (0);
}
void cleanup_module(void)
{
	ch_terminate();
	return;
}


Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/x400p-ss7/ch_x400p.c

OpenSS7
SS7 for the
Common Man
Home Overview Status News Documentation Resources About

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified: