/*
 *
 * ===================================
 * HARP  |  Host ATM Research Platform
 * ===================================
 *
 *
 * This Host ATM Research Platform ("HARP") file (the "Software") is
 * made available by Network Computing Services, Inc. ("NetworkCS")
 * "AS IS".  NetworkCS does not provide maintenance, improvements or
 * support of any kind.
 *
 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
 * In no event shall NetworkCS be responsible for any damages, including
 * but not limited to consequential damages, arising from or relating to
 * any use of the Software or related support.
 *
 * Copyright 1994-1998 Network Computing Services, Inc.
 *
 * Copies of this Software may be made, however, the above copyright
 * notice must be reproduced on all copies.
 *
 *	@(#) $FreeBSD: src/sys/netatm/atm_cm.c,v 1.6 1999/08/28 00:48:34 peter Exp $
 *
 */

/*
 * Core ATM Services
 * -----------------
 *
 * ATM Connection Manager
 *
 */

#include <netatm/kern_include.h>

#ifndef lint
__RCSID("@(#) $FreeBSD: src/sys/netatm/atm_cm.c,v 1.6 1999/08/28 00:48:34 peter Exp $");
#endif


/*
 * Global variables
 */
struct atm_cm_stat	atm_cm_stat = {0};

/*
 * Local functions
 */
static void		atm_cm_cpcs_upper __P((int, void *, int, int));
static void		atm_cm_saal_upper __P((int, void *, int, int));
static void		atm_cm_sscop_upper __P((int, void *, int, int));
static Atm_connvc *	atm_cm_share_llc __P((Atm_attributes *));
static void		atm_cm_closeconn __P((Atm_connection *,
				struct t_atm_cause *));
static void		atm_cm_closevc __P((Atm_connvc *));
static void		atm_cm_timeout __P((struct atm_time *));
static KTimeout_ret	atm_cm_procinq __P((void *));
static void		atm_cm_incall __P((Atm_connvc *));
static int		atm_cm_accept __P((Atm_connvc *, Atm_connection *));

/*
 * Local variables
 */
static Queue_t		atm_connection_queue = {NULL};
static Queue_t		atm_incoming_queue = {NULL};
static int		atm_incoming_qlen = 0;
static Atm_connection	*atm_listen_queue = NULL;
static struct attr_cause	atm_cause_tmpl = 
	{T_ATM_PRESENT, {T_ATM_ITU_CODING, T_ATM_LOC_USER, 0, {0, 0, 0, 0}}};

/*
 * Stack commands, indexed by API
 */
static struct {
	int		init;
	int		term;
} atm_stackcmds[] = {
	{CPCS_INIT, CPCS_TERM},		/* CMAPI_CPCS */
	{SSCF_UNI_INIT, SSCF_UNI_TERM},	/* CMAPI_SAAL */
	{SSCOP_INIT, SSCOP_TERM},	/* CMAPI_SSCOP */
};


static struct sp_info          atm_connection_pool = {
	"atm connection pool",		/* si_name */
	sizeof(Atm_connection),		/* si_blksiz */
	10,				/* si_blkcnt */
	100				/* si_maxallow */
};
static struct sp_info          atm_connvc_pool = {
	"atm connection vcc pool",	/* si_name */
	sizeof(Atm_connvc),		/* si_blksiz */
	10,				/* si_blkcnt */
	100				/* si_maxallow */
};


/*
 * Initiate Outgoing ATM Call
 * 
 * Called by an endpoint service to create a new Connection Manager API
 * instance and to initiate an outbound ATM connection.  The endpoint
 * provided token will be used in all further CM -> endpoint function
 * calls, and the returned connection block pointer must be used in all
 * subsequent endpoint -> CM function calls.
 *
 * If the return indicates that the connection setup has been immediately
 * successful (typically only for PVCs and shared SVCs), then the connection
 * is ready for data transmission.
 *
 * If the return indicates that the connection setup is still in progress,
 * then the endpoint must wait for notification from the Connection Manager
 * indicating the final status of the call setup.  If the call setup completes
 * successfully, then a "call connected" notification will be sent to the
 * endpoint by the Connection Manager.  If the call setup fails, then the
 * endpoint will receive a "call cleared" notification.
 *
 * All connection instances must be freed with an atm_cm_release() call.
 *
 * Arguments:
 *	epp	pointer to endpoint definition structure
 *	token	endpoint's connection instance token
 *	ap	pointer to requested connection attributes
 *	copp	pointer to location to return allocated connection block
 *
 * Returns:
 *	0	connection has been successfully established
 *	EINPROGRESS	connection establishment is in progress
 *	errno	connection failed - reason indicated
 *
 */
int
atm_cm_connect(epp, token, ap, copp)
	Atm_endpoint	*epp;
	void		*token;
	Atm_attributes	*ap;
	Atm_connection	**copp;
{
	Atm_connection	*cop;
	Atm_connvc	*cvp;
	struct atm_pif	*pip;
	struct sigmgr	*smp;
	struct stack_list	sl;
	void		(*upf)__P((int, void *, int, int));
	int		s, sli, err, err2;

	*copp = NULL;
	cvp = NULL;

	/*
	 * Get a connection block
	 */
	cop = (Atm_connection *)atm_allocate(&atm_connection_pool);
	if (cop == NULL)
		return (ENOMEM);

	/*
	 * Initialize connection info
	 */
	cop->co_endpt = epp;
	cop->co_toku = token;

	/*
	 * Initialize stack list index
	 */
	sli = 0;

	/*
	 * Validate and extract useful attribute information
	 */

	/*
	 * Must specify a network interface (validated below)
	 */
	if (ap->nif == NULL) {
		err = EINVAL;
		goto done;
	}

	/*
	 * Check out Data API
	 */
	switch (ap->api) {

	case CMAPI_CPCS:
		upf = atm_cm_cpcs_upper;
		break;

	case CMAPI_SAAL:
		sl.sl_sap[sli++] = SAP_SSCF_UNI;
		sl.sl_sap[sli++] = SAP_SSCOP;
		upf = atm_cm_saal_upper;
		break;

	case CMAPI_SSCOP:
		sl.sl_sap[sli++] = SAP_SSCOP;
		upf = atm_cm_sscop_upper;
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * AAL Attributes
	 */
	if (ap->aal.tag != T_ATM_PRESENT) {
		err = EINVAL;
		goto done;
	}

	switch (ap->aal.type) {

	case ATM_AAL5:
		sl.sl_sap[sli++] = SAP_CPCS_AAL5;
		sl.sl_sap[sli++] = SAP_SAR_AAL5;
		sl.sl_sap[sli++] = SAP_ATM;
		break;

	case ATM_AAL3_4:
		sl.sl_sap[sli++] = SAP_CPCS_AAL3_4;
		sl.sl_sap[sli++] = SAP_SAR_AAL3_4;
		sl.sl_sap[sli++] = SAP_ATM;
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Broadband Bearer Attributes
	 */
	if (ap->bearer.tag != T_ATM_PRESENT) {
		err = EINVAL;
		goto done;
	}

	switch (ap->bearer.v.connection_configuration) {

	case T_ATM_1_TO_1:
		cop->co_flags |= COF_P2P;
		break;

	case T_ATM_1_TO_MANY:
		/* Not supported */
		cop->co_flags |= COF_P2MP;
		err = EINVAL;
		goto done;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Logical Link Control Attributes
	 */
	if (ap->llc.tag == T_ATM_PRESENT) {
		if ((ap->blli.tag_l2 != T_ATM_PRESENT) ||
		    (ap->blli.v.layer_2_protocol.ID_type != T_ATM_SIMPLE_ID) ||
		    (ap->blli.v.layer_2_protocol.ID.simple_ID != 
				T_ATM_BLLI2_I8802) ||
		    (ap->llc.v.llc_len < T_ATM_LLC_MIN_LEN) ||
		    (ap->llc.v.llc_len > T_ATM_LLC_MAX_LEN)) {
			err = EINVAL;
			goto done;
		}
		cop->co_mpx = ATM_ENC_LLC;
		cop->co_llc = ap->llc;
	} else
		cop->co_mpx = ATM_ENC_NULL;

	/*
	 * Called Party Attributes
	 */
	if (ap->called.tag != T_ATM_PRESENT) {
		err = EINVAL;
		goto done;
	}

	if ((ap->called.addr.address_format == T_ATM_ABSENT) ||
	    (ap->called.addr.address_length == 0)) {
		err = EINVAL;
		goto done;
	}

	/*
	 * Calling Party Attributes
	 */
	if (ap->calling.tag != T_ATM_ABSENT) {
		err = EINVAL;
		goto done;
	}

	/*
	 * Quality of Service Attributes
	 */
	if (ap->qos.tag != T_ATM_PRESENT) {
		err = EINVAL;
		goto done;
	}

	/*
	 * Terminate stack list 
	 */
	sl.sl_sap[sli] = 0;

	s = splnet();

	/*
	 * Let multiplexors decide whether we need a new VCC
	 */
	switch (cop->co_mpx) {

	case ATM_ENC_NULL:
		/*
		 * All of these connections require a new VCC
		 */
		break;

	case ATM_ENC_LLC:
		/*
		 * See if we can share an existing LLC connection
		 */
		cvp = atm_cm_share_llc(ap);
		if (cvp == NULL)
			break;

		/*
		 * We've got a connection to share
		 */
		cop->co_connvc = cvp;
		if (cvp->cvc_state == CVCS_ACTIVE) {
			cop->co_state = COS_ACTIVE;
			err = 0;
		} else {
			cop->co_state = COS_OUTCONN;
			err = EINPROGRESS;
		}
		LINK2TAIL(cop, Atm_connection, cvp->cvc_conn->co_mxh, co_next);
		cop->co_mxh = cvp->cvc_conn->co_mxh;
		*copp = cop;

		(void) splx(s);
		return (err);

	default:
		panic("atm_cm_connect: unknown mpx");
	}

	/*
	 * If we get here, it means we need to create 
	 * a new VCC for this connection
	 */

	/*
	 * Validate that network interface is registered and that 
	 * a signalling manager is attached
	 */
	for (pip = atm_interface_head; pip != NULL; pip = pip->pif_next) {
		struct atm_nif	*nip;
		for (nip = pip->pif_nif; nip; nip = nip->nif_pnext) {
			if (nip == ap->nif)
				break;
		}
		if (nip)
			break;
	}
	if (pip == NULL) {
		err = ENXIO;
		goto donex;
	}

	if ((smp = pip->pif_sigmgr) == NULL) {
		err = ENXIO;
		goto donex;
	}

	/*
	 * Get a connection VCC block
	 */
	cvp = (Atm_connvc *)atm_allocate(&atm_connvc_pool);
	if (cvp == NULL) {
		err = ENOMEM;
		goto donex;
	}

	/*
	 * Save VCC attributes
	 */
	cvp->cvc_attr = *ap;
	cvp->cvc_flags |= CVCF_CALLER;

	/*
	 * Link the control blocks
	 */
	cop->co_connvc = cvp;
	cvp->cvc_conn = cop;
	cvp->cvc_sigmgr = smp;

	/*
	 * Create a service stack
	 */
	err = atm_create_stack(cvp, &sl, upf);
	if (err) {
		cvp->cvc_state = CVCS_CLEAR;
		atm_cm_closevc(cvp);
		goto donex;
	}

	/*
	 * Let the signalling manager handle the VCC creation
	 */
	cvp->cvc_state = CVCS_SETUP;
	switch ((*smp->sm_setup)(cvp, &err)) {

	case CALL_CONNECTED:
		/*
		 * Connection is fully setup - initialize the stack
		 */
		cvp->cvc_state = CVCS_INIT;
		STACK_CALL(atm_stackcmds[ap->api].init, cvp->cvc_lower,
			cvp->cvc_tokl, cvp, ap->api_init, 0, err2);
		if (err2)
			panic("atm_cm_connect: init");

		if (cvp->cvc_flags & CVCF_ABORTING) {
			/*
			 * Someone on the stack bailed out...schedule the 
			 * VCC and stack termination
			 */
			atm_cm_closevc(cvp);
			err = EFAULT;
		} else {
			/*
			 * Everything looks fine from here
			 */
			cvp->cvc_state = CVCS_ACTIVE;
			cop->co_state = COS_ACTIVE;
		}
		break;

	case CALL_FAILED:
		/*
		 * Terminate stack and clean up before we leave
		 */
		cvp->cvc_state = CVCS_CLEAR;
		atm_cm_closevc(cvp);
		break;

	case CALL_PROCEEDING:
		/*
		 * We'll just wait for final call status
		 */
		cop->co_state = COS_OUTCONN;
		err = EINPROGRESS;
		break;

	default:
		panic("atm_cm_connect: setup");
	}

donex:
	(void) splx(s);

done:
	if (err && err != EINPROGRESS) {
		/*
		 * Undo any partial setup stuff
		 */
		if (cop)
			atm_free((caddr_t)cop);
	} else {
		/*
		 * Finish connection setup
		 */
		s = splnet();
		cvp->cvc_flags |= CVCF_CONNQ;
		ENQUEUE(cvp, Atm_connvc, cvc_q, atm_connection_queue);
		LINK2TAIL(cop, Atm_connection, cop->co_mxh, co_next);
		(void) splx(s);
		*copp = cop;
	}
	return (err);
}


/*
 * Listen for Incoming ATM Calls
 * 
 * Called by an endpoint service in order to indicate its willingness to
 * accept certain incoming calls.  The types of calls which the endpoint
 * is prepared to accept are specified in the Atm_attributes parameter.
 *
 * For each call which meets the criteria specified by the endpoint, the
 * endpoint service will receive an incoming call notification via the
 * endpoint's ep_incoming() function.
 *
 * To cancel the listening connection, the endpoint user should invoke 
 * atm_cm_release().
 *
 * Arguments:
 *	epp	pointer to endpoint definition structure
 *	token	endpoint's listen instance token
 *	ap	pointer to listening connection attributes
 *	copp	pointer to location to return allocated connection block
 *
 * Returns:
 *	0	listening connection installed
 *	errno	listen failed - reason indicated
 *
 */
int
atm_cm_listen(epp, token, ap, copp)
	Atm_endpoint	*epp;
	void		*token;
	Atm_attributes	*ap;
	Atm_connection	**copp;
{
	Atm_connection	*cop;
	int		s, err = 0;

	*copp = NULL;

	/*
	 * Get a connection block
	 */
	cop = (Atm_connection *)atm_allocate(&atm_connection_pool);
	if (cop == NULL)
		return (ENOMEM);

	/*
	 * Initialize connection info
	 */
	cop->co_endpt = epp;
	cop->co_toku = token;
	cop->co_mxh = cop;

	/*
	 * Validate and extract useful attribute information
	 */

	/*
	 * Check out Data API
	 */
	switch (ap->api) {

	case CMAPI_CPCS:
	case CMAPI_SAAL:
	case CMAPI_SSCOP:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * AAL Attributes
	 */
	switch (ap->aal.tag) {

	case T_ATM_PRESENT:

		switch (ap->aal.type) {

		case ATM_AAL5:
		case ATM_AAL3_4:
			break;

		default:
			err = EINVAL;
			goto done;
		}
		break;

	case T_ATM_ABSENT:
	case T_ATM_ANY:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Broadband High Layer Information Attributes
	 */
	switch (ap->bhli.tag) {

	case T_ATM_PRESENT:
	case T_ATM_ABSENT:
	case T_ATM_ANY:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Broadband Low Layer Information Attributes
	 */
	switch (ap->blli.tag_l2) {

	case T_ATM_PRESENT:
	case T_ATM_ABSENT:
	case T_ATM_ANY:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	switch (ap->blli.tag_l3) {

	case T_ATM_PRESENT:
	case T_ATM_ABSENT:
	case T_ATM_ANY:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Logical Link Control Attributes
	 */
	switch (ap->llc.tag) {

	case T_ATM_PRESENT:
		if ((ap->blli.tag_l2 != T_ATM_PRESENT) ||
		    (ap->blli.v.layer_2_protocol.ID_type != T_ATM_SIMPLE_ID) ||
		    (ap->blli.v.layer_2_protocol.ID.simple_ID != 
				T_ATM_BLLI2_I8802) ||
		    (ap->llc.v.llc_len < T_ATM_LLC_MIN_LEN) ||
		    (ap->llc.v.llc_len > T_ATM_LLC_MAX_LEN)) {
			err = EINVAL;
			goto done;
		}
		cop->co_mpx = ATM_ENC_LLC;
		cop->co_llc = ap->llc;
		break;

	case T_ATM_ABSENT:
	case T_ATM_ANY:
		cop->co_mpx = ATM_ENC_NULL;
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Called Party Attributes
	 */
	switch (ap->called.tag) {

	case T_ATM_PRESENT:
		switch (ap->called.addr.address_format) {

		case T_ATM_ABSENT:
			ap->called.tag = T_ATM_ABSENT;
			break;

		case T_ATM_PVC_ADDR:
			err = EINVAL;
			goto done;
		}
		break;

	case T_ATM_ABSENT:
	case T_ATM_ANY:
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Get an attribute block and save listening attributes
	 */
	cop->co_lattr = (Atm_attributes *)atm_allocate(&atm_attributes_pool);
	if (cop->co_lattr == NULL) {
		err = ENOMEM;
		goto done;
	}
	*cop->co_lattr = *ap;

	/*
	 * Now try to register the listening connection
	 */
	s = splnet();
	if (atm_cm_match(cop->co_lattr, NULL) != NULL) {
		/*
		 * Can't have matching listeners
		 */
		err = EADDRINUSE;
		goto donex;
	}
	cop->co_state = COS_LISTEN;
	LINK2TAIL(cop, Atm_connection, atm_listen_queue, co_next);

donex:
	(void) splx(s);

done:
	if (err) {
		/*
		 * Undo any partial setup stuff
		 */
		if (cop) {
			if (cop->co_lattr)
				atm_free((caddr_t)cop->co_lattr);
			atm_free((caddr_t)cop);
		}
	} else {
		/*
		 * Finish connection setup
		 */
		*copp = cop;
	}
	return (err);
}


/*
 * Add to LLC Connection
 * 
 * Called by an endpoint service to create a new Connection Manager API
 * instance to be associated with an LLC-multiplexed connection instance
 * which has been previously created.  The endpoint provided token will
 * be used in all further CM -> endpoint function calls, and the returned
 * connection block pointer must be used in all subsequent endpoint -> CM
 * function calls.
 *
 * If the return indicates that the connection setup has been immediately
 * successful, then the connection is ready for data transmission.
 *
 * If the return indicates that the connection setup is still in progress,
 * then the endpoint must wait for notification from the Connection Manager
 * indicating the final status of the call setup.  If the call setup completes
 * successfully, then a "call connected" notification will be sent to the
 * endpoint by the Connection Manager.  If the call setup fails, then the
 * endpoint will receive a "call cleared" notification.
 *
 * All connection instances must be freed with an atm_cm_release() call.
 *
 * Arguments:
 *	epp	pointer to endpoint definition structure
 *	token	endpoint's connection instance token
 *	llc	pointer to llc attributes for new connection
 *	ecop	pointer to existing connection block
 *	copp	pointer to location to return allocated connection block
 *
 * Returns:
 *	0	connection has been successfully established
 *	EINPROGRESS	connection establishment is in progress
 *	errno	addllc failed - reason indicated
 *
 */
int
atm_cm_addllc(epp, token, llc, ecop, copp)
	Atm_endpoint	*epp;
	void		*token;
	struct attr_llc	*llc;
	Atm_connection	*ecop;
	Atm_connection	**copp;
{
	Atm_connection	*cop, *cop2;
	Atm_connvc	*cvp;
	int		s, err;

	*copp = NULL;

	/*
	 * Check out requested LLC attributes
	 */
	if ((llc->tag != T_ATM_PRESENT) ||
	    ((llc->v.flags & T_ATM_LLC_SHARING) == 0) ||
	    (llc->v.llc_len < T_ATM_LLC_MIN_LEN) ||
	    (llc->v.llc_len > T_ATM_LLC_MAX_LEN))
		return (EINVAL);

	/*
	 * Get a connection block
	 */
	cop = (Atm_connection *)atm_allocate(&atm_connection_pool);
	if (cop == NULL)
		return (ENOMEM);

	/*
	 * Initialize connection info
	 */
	cop->co_endpt = epp;
	cop->co_toku = token;
	cop->co_llc = *llc;

	s = splnet();

	/*
	 * Ensure that supplied connection is really valid
	 */
	cop2 = NULL;
	for (cvp = Q_HEAD(atm_connection_queue, Atm_connvc); cvp;
			cvp = Q_NEXT(cvp, Atm_connvc, cvc_q)) {
		for (cop2 = cvp->cvc_conn; cop2; cop2 = cop2->co_next) {
			if (ecop == cop2)
				break;
		}
		if (cop2)
			break;
	}
	if (cop2 == NULL) {
		err = ENOENT;
		goto done;
	}

	switch (ecop->co_state) {

	case COS_OUTCONN:
	case COS_INACCEPT:
		err = EINPROGRESS;
		break;

	case COS_ACTIVE:
		err = 0;
		break;

	default:
		err = EINVAL;
		goto done;
	}

	/*
	 * Connection must be LLC multiplexed and shared
	 */
	if ((ecop->co_mpx != ATM_ENC_LLC) ||
	    ((ecop->co_llc.v.flags & T_ATM_LLC_SHARING) == 0)) {
		err = EINVAL;
		goto done;
	}

	/*
	 * This new LLC header must be unique for this VCC
	 */
	cop2 = ecop->co_mxh;
	while (cop2) {
		int	i = MIN(llc->v.llc_len, cop2->co_llc.v.llc_len);

		if (KM_CMP(llc->v.llc_info, cop2->co_llc.v.llc_info, i) == 0) {
			err = EINVAL;
			goto done;
		}

		cop2 = cop2->co_next;
	}

	/*
	 * Everything seems to check out
	 */
	cop->co_flags = ecop->co_flags;
	cop->co_state = ecop->co_state;
	cop->co_mpx = ecop->co_mpx;
	cop->co_connvc = ecop->co_connvc;

	LINK2TAIL(cop, Atm_connection, ecop->co_mxh, co_next);
	cop->co_mxh = ecop->co_mxh;

done:
	(void) splx(s);

	if (err && err != EINPROGRESS) {
		/*
		 * Undo any partial setup stuff
		 */
		if (cop)
			atm_free((caddr_t)cop);
	} else {
		/*
		 * Pass new connection back to caller
		 */
		*copp = cop;
	}
	return (err);
}


/*
 * XXX
 * 
 * Arguments:
 *	cop	pointer to connection block
 *	id	identifier for party to be added
 *	addr	address of party to be added
 *
 * Returns:
 *	0	addparty successful
 *	errno	addparty failed - reason indicated
 *
 */
int
atm_cm_addparty(cop, id, addr)
	Atm_connection	*cop;
	int		id;
	struct t_atm_sap	*addr;
{
	return (0);
}


/*
 * XXX
 * 
 * Arguments:
 *	cop	pointer to connection block
 *	id	identifier for party to be added
 *	cause	pointer to cause of drop
 *
 * Returns:
 *	0	dropparty successful
 *	errno	dropparty failed - reason indicated
 *
 */
int
atm_cm_dropparty(cop, id, cause)
	Atm_connection	*cop;
	int		id;
	struct t_atm_cause	*cause;
{
	return (0);
}


/*
 * Release Connection Resources
 * 
 * Called by the endpoint service in order to terminate an ATM connection 
 * and to release all system resources for the connection.  This function
 * must be called for every allocated connection instance and must only 
 * be called by the connection's owner.
 *
 * Arguments:
 *	cop	pointer to connection block
 *	cause	pointer to cause of release
 *
 * Returns:
 *	0	release successful
 *	errno	release failed - reason indicated
 *
 */
int
atm_cm_release(cop, cause)
	Atm_connection	*cop;
	struct t_atm_cause	*cause;
{
	Atm_connvc	*cvp;
	int		s;

	s = splnet();

	/*
	 * First, a quick state validation check
	 */
	switch (cop->co_state) {

	case COS_OUTCONN:
	case COS_LISTEN:
	case COS_INACCEPT:
	case COS_ACTIVE:
	case COS_CLEAR:
		/*
		 * Break link to user
		 */
		cop->co_toku = NULL;
		break;

	case COS_INCONN:
		(void) splx(s);
		return (EFAULT);

	default:
		panic("atm_cm_release: bogus conn state");
	}

	/*
	 * Check out the VCC state too
	 */
	if ((cvp = cop->co_connvc) != NULL) {

		switch (cvp->cvc_state) {

		case CVCS_SETUP:
		case CVCS_INIT:
		case CVCS_ACCEPT:
		case CVCS_ACTIVE:
			break;

		case CVCS_INCOMING:
			(void) splx(s);
			return (EFAULT);

		case CVCS_CLEAR:
			(void) splx(s);
			return (EALREADY);

		default:
			panic("atm_cm_release: bogus connvc state");
		}

		/*
		 * If we're the only connection, terminate the VCC
		 */
		if ((cop->co_mxh == cop) && (cop->co_next == NULL)) {
			cvp->cvc_attr.cause.tag = T_ATM_PRESENT;
			cvp->cvc_attr.cause.v = *cause;
			atm_cm_closevc(cvp);
		}
	}

	/*
	 * Now get rid of the connection
	 */
	atm_cm_closeconn(cop, cause);

	return (0);
}


/*
 * Abort an ATM Connection VCC
 * 
 * This function allows any non-owner kernel entity to request an 
 * immediate termination of an ATM VCC.  This will normally be called
 * when encountering a catastrophic error condition that cannot be 
 * resolved via the available stack protocols.  The connection manager 
 * will schedule the connection's termination, including notifying the 
 * connection owner of the termination.  
 *
 * This function should only be called by a stack entity instance.  After 
 * calling the function, the caller should set a protocol state which just 
 * waits for a <sap>_TERM stack command to be delivered.
 *
 * Arguments:
 *	cvp	pointer to connection VCC block
 *	cause	pointer to cause of abort
 *
 * Returns:
 *	0	abort successful
 *	errno	abort failed - reason indicated
 *
 */
int
atm_cm_abort(cvp, cause)
	Atm_connvc	*cvp;
	struct t_atm_cause	*cause;
{
	ATM_DEBUG2("atm_cm_abort: cvp=%p cause=%d\n",
		cvp, cause->cause_value);

	/*
	 * Note that we're aborting
	 */
	cvp->cvc_flags |= CVCF_ABORTING;

	switch (cvp->cvc_state) {

	case CVCS_INIT:
		/*
		 * In-line code will handle this
		 */
		cvp->cvc_attr.cause.tag = T_ATM_PRESENT;
		cvp->cvc_attr.cause.v = *cause;
		break;

	case CVCS_SETUP:
	case CVCS_ACCEPT:
	case CVCS_ACTIVE:
		/*
		 * Schedule connection termination, since we want
		 * to avoid any sequencing interactions
		 */
		cvp->cvc_attr.cause.tag = T_ATM_PRESENT;
		cvp->cvc_attr.cause.v = *cause;
		CVC_TIMER(cvp, 0);
		break;

	case CVCS_REJECT:
	case CVCS_RELEASE:
	case CVCS_CLEAR:
	case CVCS_TERM:
		/*
		 * Ignore abort, as we're already terminating
		 */
		break;

	default:
		log(LOG_ERR,
			"atm_cm_abort: invalid state: cvp=%p, state=%d\n",
			cvp, cvp->cvc_state);
	}
	return (0);
}


/*
 * Incoming ATM Call Received
 * 
 * Called by a signalling manager to indicate that a new call request has
 * been received.  This function will allocate and initialize the connection
 * manager control blocks and queue this call request.  The call request 
 * processing function, atm_cm_procinq(), will be scheduled to perform the
 * call processing.
 *
 * Arguments:
 *	vcp	pointer to incoming call's VCC control block
 *	ap	pointer to incoming call's attributes
 *
 * Returns:
 *	0	call queuing successful
 *	errno	call queuing failed - reason indicated
 *
 */
int
atm_cm_incoming(vcp, ap)
	struct vccb	*vcp;
	Atm_attributes	*ap;
{
	Atm_connvc	*cvp;
	int		s, err;


	/*
	 * Do some minimal attribute validation
	 */

	/*
	 * Must specify a network interface
	 */
	if (ap->nif == NULL)
		return (EINVAL);

	/*
	 * AAL Attributes
	 */
	if ((ap->aal.tag != T_ATM_PRESENT) ||
	    ((ap->aal.type != ATM_AAL5) &&
	     (ap->aal.type != ATM_AAL3_4)))
		return (EINVAL);

	/*
	 * Traffic Descriptor Attributes
	 */
	if ((ap->traffic.tag != T_ATM_PRESENT) &&
	    (ap->traffic.tag != T_ATM_ABSENT))
		return (EINVAL);

	/*
	 * Broadband Bearer Attributes
	 */
	if ((ap->bearer.tag != T_ATM_PRESENT) ||
	    ((ap->bearer.v.connection_configuration != T_ATM_1_TO_1) &&
	     (ap->bearer.v.connection_configuration != T_ATM_1_TO_MANY)))
		return (EINVAL);

	/*
	 * Broadband High Layer Attributes
	 */
	if ((ap->bhli.tag != T_ATM_PRESENT) &&
	    (ap->bhli.tag != T_ATM_ABSENT))
		return (EINVAL);

	/*
	 * Broadband Low Layer Attributes
	 */
	if ((ap->blli.tag_l2 != T_ATM_PRESENT) &&
	    (ap->blli.tag_l2 != T_ATM_ABSENT))
		return (EINVAL);
	if ((ap->blli.tag_l3 != T_ATM_PRESENT) &&
	    (ap->blli.tag_l3 != T_ATM_ABSENT))
		return (EINVAL);

	/*
	 * Logical Link Control Attributes
	 */
	if (ap->llc.tag == T_ATM_PRESENT)
		return (EINVAL);
	ap->llc.tag = T_ATM_ANY;

	/*
	 * Called Party Attributes
	 */
	if ((ap->called.tag != T_ATM_PRESENT) ||
	    (ap->called.addr.address_format == T_ATM_ABSENT))
		return (EINVAL);
	if (ap->called.tag == T_ATM_ABSENT) {
		ap->called.addr.address_format = T_ATM_ABSENT;
		ap->called.addr.address_length = 0;
		ap->called.subaddr.address_format = T_ATM_ABSENT;
		ap->called.subaddr.address_length = 0;
	}

	/*
	 * Calling Party Attributes
	 */
	if ((ap->calling.tag != T_ATM_PRESENT) &&
	    (ap->calling.tag != T_ATM_ABSENT))
		return (EINVAL);
	if (ap->calling.tag == T_ATM_ABSENT) {
		ap->calling.addr.address_format = T_ATM_ABSENT;
		ap->calling.addr.address_length = 0;
		ap->calling.subaddr.address_format = T_ATM_ABSENT;
		ap->calling.subaddr.address_length = 0;
	}

	/*
	 * Quality of Service Attributes
	 */
	if (ap->qos.tag != T_ATM_PRESENT)
		return (EINVAL);

	/*
	 * Transit Network Attributes
	 */
	if ((ap->transit.tag != T_ATM_PRESENT) &&
	    (ap->transit.tag != T_ATM_ABSENT))
		return (EINVAL);

	/*
	 * Cause Attributes
	 */
	if ((ap->cause.tag != T_ATM_PRESENT) &&
	    (ap->cause.tag != T_ATM_ABSENT))
		return (EINVAL);

	/*
	 * Get a connection VCC block
	 */
	cvp = (Atm_connvc *)atm_allocate(&atm_connvc_pool);
	if (cvp == NULL) {
		err = ENOMEM;
		goto fail;
	}

	/*
	 * Initialize the control block
	 */
	cvp->cvc_vcc = vcp;
	cvp->cvc_sigmgr = vcp->vc_pif->pif_sigmgr;
	cvp->cvc_attr = *ap;
	cvp->cvc_state = CVCS_INCOMING;

	/*
	 * Control queue length
	 */
	s = splnet();
	if (atm_incoming_qlen >= ATM_CALLQ_MAX) {
		(void) splx(s);
		err = EBUSY;
		goto fail;
	}

	/*
	 * Queue request and schedule call processing function
	 */
	cvp->cvc_flags |= CVCF_INCOMQ;
	ENQUEUE(cvp, Atm_connvc, cvc_q, atm_incoming_queue);
	if (atm_incoming_qlen++ == 0) {
		timeout(atm_cm_procinq, (void *)0, 0);
	}

	/*
	 * Link for signalling manager
	 */
	vcp->vc_connvc = cvp;

	(void) splx(s);

	return (0);

fail:
	/*
	 * Free any resources
	 */
	if (cvp)
		atm_free((caddr_t)cvp);
	return (err);
}


/*
 * VCC Connected Notification
 * 
 * This function is called by a signalling manager as notification that a
 * VCC call setup has been successful.
 *
 * Arguments:
 *	cvp	pointer to connection VCC block
 *
 * Returns:
 *	none
 *
 */
void
atm_cm_connected(cvp)
	Atm_connvc	*cvp;
{
	Atm_connection	*cop, *cop2;
	KBuffer		*m;
	int		s, err;

	s = splnet();

	/*
	 * Validate connection vcc
	 */
	switch (cvp->cvc_state) {

	case CVCS_SETUP:
		/*
		 * Initialize the stack
		 */
		cvp->cvc_state = CVCS_INIT;
		STACK_CALL(atm_stackcmds[cvp->cvc_attr.api].init,
				cvp->cvc_lower, cvp->cvc_tokl,
				cvp, cvp->cvc_attr.api_init, 0, err);
		if (err)
			panic("atm_cm_connected: init");

		if (cvp->cvc_flags & CVCF_ABORTING) {
			/*
			 * Someone on the stack bailed out...notify all of the
			 * connections and schedule the VCC termination
			 */
			cop = cvp->cvc_conn;
			while (cop) {
				cop2 = cop->co_next;
				atm_cm_closeconn(cop, &cvp->cvc_attr.cause.v);
				cop = cop2;
			}
			atm_cm_closevc(cvp);
			(void) splx(s);
			return;
		}
		break;

	case CVCS_ACCEPT:
		/*
		 * Stack already initialized
		 */
		break;

	default:
		panic("atm_cm_connected: connvc state");
	}

	/*
	 * VCC is ready for action
	 */
	cvp->cvc_state = CVCS_ACTIVE;

	/*
	 * Notify all connections that the call has completed
	 */
	cop = cvp->cvc_conn;
	while (cop) {
		cop2 = cop->co_next;

		switch (cop->co_state) {

		case COS_OUTCONN:
		case COS_INACCEPT:
			cop->co_state = COS_ACTIVE;
			(*cop->co_endpt->ep_connected)(cop->co_toku);
			break;

		case COS_ACTIVE:
			/*
			 * May get here if an ep_connected() call (from
			 * above) results in an atm_cm_addllc() call for 
			 * the just connected connection.
			 */
			break;

		default:
			panic("atm_cm_connected: connection state");
		}

		cop = cop2;
	}

	(void) splx(s);

	/*
	 * Input any queued packets
	 */
	while ((m = cvp->cvc_rcvq) != NULL) {
		cvp->cvc_rcvq = KB_QNEXT(m);
		cvp->cvc_rcvqlen--;
		KB_QNEXT(m) = NULL;

		/*
		 * Currently only supported for CPCS API
		 */
		atm_cm_cpcs_upper(CPCS_UNITDATA_SIG, cvp, (int)m, 0);
	}

	return;
}


/*
 * VCC Cleared Notification
 * 
 * This function is called by a signalling manager as notification that a
 * VCC call has been cleared.  The cause information describing the reason
 * for the call clearing will be contained in the connection VCC attributes.
 *
 * Arguments:
 *	cvp	pointer to connection VCC block
 *
 * Returns:
 *	none
 *
 */
void
atm_cm_cleared(cvp)
	Atm_connvc	*cvp;
{
	Atm_connection	*cop, *cop2;
	int		s;

#ifdef DIAGNOSTIC
	if ((cvp->cvc_state == CVCS_FREE) ||
	    (cvp->cvc_state >= CVCS_CLEAR))
		panic("atm_cm_cleared");
#endif

	cvp->cvc_state = CVCS_CLEAR;

	s = splnet();

	/*
	 * Terminate all connections
	 */
	cop = cvp->cvc_conn;
	while (cop) {
		cop2 = cop->co_next;
		atm_cm_closeconn(cop, &cvp->cvc_attr.cause.v);
		cop = cop2;
	}

	/*
	 * Clean up connection VCC
	 */
	atm_cm_closevc(cvp);

	(void) splx(s);

	return;
}


/*
 * Process Incoming Call Queue
 * 
 * This function is scheduled by atm_cm_incoming() in order to process
 * all the entries on the incoming call queue.
 *
 * Arguments:
 *	arg	argument passed on timeout() call
 *
 * Returns:
 *	none
 *
 */
static KTimeout_ret
atm_cm_procinq(arg)
	void	*arg;
{
	Atm_connvc	*cvp;
	int		cnt = 0, s;

	/*
	 * Only process incoming calls up to our quota
	 */
	while (cnt++ < ATM_CALLQ_MAX) {

		s = splnet();

		/*
		 * Get next awaiting call
		 */
		cvp = Q_HEAD(atm_incoming_queue, Atm_connvc);
		if (cvp == NULL) {
			(void) splx(s);
			break;
		}
		DEQUEUE(cvp, Atm_connvc, cvc_q, atm_incoming_queue);
		atm_incoming_qlen--;
		cvp->cvc_flags &= ~CVCF_INCOMQ;

		/*
		 * Handle the call
		 */
		atm_cm_incall(cvp);

		(void) splx(s);
	}

	/*
	 * If we've expended our quota, reschedule ourselves
	 */
	if (cnt >= ATM_CALLQ_MAX)
		timeout(atm_cm_procinq, (void *)0, 0);
}


/*
 * Process Incoming Call
 * 
 * This function will search through the listening queue and try to find
 * matching endpoint(s) for the incoming call.  If we find any, we will
 * notify the endpoint service(s) of the incoming call and will then
 * notify the signalling manager to progress the call to an active status.
 * 
 * If there are no listeners for the call, the signalling manager will be
 * notified of a call rejection.
 *
 * Called at splnet.
 *
 * Arguments:
 *	cvp	pointer to connection VCC for incoming call
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_incall(cvp)
	Atm_connvc	*cvp;
{
	Atm_connection	*cop, *lcop, *hcop;
	Atm_attributes	attr;
	int		err;

	hcop = NULL;
	lcop = NULL;
	cop = NULL;
	attr = cvp->cvc_attr;

	/*
	 * Look for matching listeners
	 */
	while ((lcop = atm_cm_match(&attr, lcop)) != NULL) {

		if (cop == NULL) {
			/*
			 * Need a new connection block
			 */
			cop = (Atm_connection *)
				atm_allocate(&atm_connection_pool);
			if (cop == NULL) {
				cvp->cvc_attr.cause = atm_cause_tmpl;
				cvp->cvc_attr.cause.v.cause_value =
						T_ATM_CAUSE_TEMPORARY_FAILURE;
				goto fail;
			}
		}

		/*
		 * Initialize connection from listener and incoming call
		 */
		cop->co_mxh = NULL;
		cop->co_state = COS_INCONN;
		cop->co_mpx = lcop->co_mpx;
		cop->co_endpt = lcop->co_endpt;
		cop->co_llc = lcop->co_llc;

		switch (attr.bearer.v.connection_configuration) {

		case T_ATM_1_TO_1:
			cop->co_flags |= COF_P2P;
			break;

		case T_ATM_1_TO_MANY:
			/* Not supported */
			cop->co_flags |= COF_P2MP;
			cvp->cvc_attr.cause = atm_cause_tmpl;
			cvp->cvc_attr.cause.v.cause_value =
				T_ATM_CAUSE_BEARER_CAPABILITY_NOT_IMPLEMENTED;
			goto fail;
		}

		/*
		 * Notify endpoint of incoming call
		 */
		err = (*cop->co_endpt->ep_incoming)
			(lcop->co_toku, cop, &cvp->cvc_attr, &cop->co_toku);

		if (err == 0) {

			/*
			 * Endpoint has accepted the call
			 *
			 * Setup call attributes
			 */
			if (hcop == NULL) {
				cvp->cvc_attr.api = lcop->co_lattr->api;
				cvp->cvc_attr.api_init =
					lcop->co_lattr->api_init;
				cvp->cvc_attr.llc = lcop->co_lattr->llc;
			}
			cvp->cvc_attr.headin = MAX(cvp->cvc_attr.headin,
				lcop->co_lattr->headin);

			/*
			 * Setup connection info and queueing
			 */
			cop->co_state = COS_INACCEPT;
			cop->co_connvc = cvp;
			LINK2TAIL(cop, Atm_connection, hcop, co_next);
			cop->co_mxh = hcop;

			/*
			 * Need a new connection block next time around
			 */
			cop = NULL;

		} else {
			/*
			 * Endpoint refuses call
			 */
			goto fail;
		}
	}

	/*
	 * We're done looking for listeners
	 */
	if (hcop) {
		/*
		 * Someone actually wants the call, so notify
		 * the signalling manager to continue
		 */
		cvp->cvc_flags |= CVCF_CONNQ;
		ENQUEUE(cvp, Atm_connvc, cvc_q, atm_connection_queue);
		if (atm_cm_accept(cvp, hcop))
			goto fail;

	} else {
		/*
		 * Nobody around to take the call
		 */
		cvp->cvc_attr.cause = atm_cause_tmpl;
		cvp->cvc_attr.cause.v.cause_value =
				T_ATM_CAUSE_INCOMPATIBLE_DESTINATION;
		goto fail;
	}

	/*
	 * Clean up loose ends
	 */
	if (cop)
		atm_free((caddr_t)cop);

	/*
	 * Call has been accepted
	 */
	return;

fail:
	/*
	 * Call failed - notify any endpoints of the call failure
	 */

	/*
	 * Clean up loose ends
	 */
	if (cop)
		atm_free((caddr_t)cop);

	if (cvp->cvc_attr.cause.tag != T_ATM_PRESENT) {
		cvp->cvc_attr.cause = atm_cause_tmpl;
		cvp->cvc_attr.cause.v.cause_value =
				T_ATM_CAUSE_UNSPECIFIED_NORMAL;
	}
	cop = hcop;
	while (cop) {
		Atm_connection	*cop2 = cop->co_next;
		atm_cm_closeconn(cop, &cvp->cvc_attr.cause.v);
		cop = cop2;
	}

	/*
	 * Tell the signalling manager to reject the call
	 */
	atm_cm_closevc(cvp);

	return;
}


/*
 * Accept an Incoming ATM Call
 * 
 * Some endpoint service(s) wants to accept an incoming call, so the
 * signalling manager will be notified to attempt to progress the call
 * to an active status.
 *
 * If the signalling manager indicates that connection activation has 
 * been immediately successful, then all of the endpoints will be notified
 * that the connection is ready for data transmission.
 * 
 * If the return indicates that connection activation is still in progress,
 * then the endpoints must wait for notification from the Connection Manager
 * indicating the final status of the call setup.  If the call setup completes
 * successfully, then a "call connected" notification will be sent to the
 * endpoints by the Connection Manager.  If the call setup fails, then the
 * endpoints will receive a "call cleared" notification.
 *
 * Called at splnet.
 *
 * Arguments:
 *	cvp	pointer to connection VCC for incoming call
 *	cop	pointer to head of accepted connections
 *
 * Returns:
 *	0	connection has been successfully activated
 *	errno	accept failed - reason indicated
 *
 */
static int
atm_cm_accept(cvp, cop)
	Atm_connvc	*cvp;
	Atm_connection	*cop;
{
	struct stack_list	sl;
	void		(*upf)__P((int, void *, int, int));
	int		sli, err, err2;


	/*
	 * Link vcc to connections
	 */
	cvp->cvc_conn = cop;

	/*
	 * Initialize stack list index
	 */
	sli = 0;

	/*
	 * Check out Data API
	 */
	switch (cvp->cvc_attr.api) {

	case CMAPI_CPCS:
		upf = atm_cm_cpcs_upper;
		break;

	case CMAPI_SAAL:
		sl.sl_sap[sli++] = SAP_SSCF_UNI;
		sl.sl_sap[sli++] = SAP_SSCOP;
		upf = atm_cm_saal_upper;
		break;

	case CMAPI_SSCOP:
		sl.sl_sap[sli++] = SAP_SSCOP;
		upf = atm_cm_sscop_upper;
		break;

	default:
		upf = NULL;
	}

	/*
	 * AAL Attributes
	 */
	switch (cvp->cvc_attr.aal.type) {

	case ATM_AAL5:
		sl.sl_sap[sli++] = SAP_CPCS_AAL5;
		sl.sl_sap[sli++] = SAP_SAR_AAL5;
		sl.sl_sap[sli++] = SAP_ATM;
		break;

	case ATM_AAL3_4:
		sl.sl_sap[sli++] = SAP_CPCS_AAL3_4;
		sl.sl_sap[sli++] = SAP_SAR_AAL3_4;
		sl.sl_sap[sli++] = SAP_ATM;
		break;
	}

	/*
	 * Terminate stack list 
	 */
	sl.sl_sap[sli] = 0;

	/*
	 * Create a service stack
	 */
	err = atm_create_stack(cvp, &sl, upf);
	if (err) {
		goto done;
	}

	/*
	 * Let the signalling manager finish the VCC activation
	 */
	switch ((*cvp->cvc_sigmgr->sm_accept)(cvp->cvc_vcc, &err)) {

	case CALL_PROCEEDING:
		/*
		 * Note that we're not finished yet
		 */
		err = EINPROGRESS;
		/* FALLTHRU */

	case CALL_CONNECTED:
		/*
		 * Initialize the stack now, even if the call isn't totally
		 * active yet.  We want to avoid the delay between getting
		 * the "call connected" event and actually notifying the 
		 * adapter to accept cells on the new VCC - if the delay is 
		 * too long, then we end up dropping the first pdus sent by 
		 * the caller.
		 */
		cvp->cvc_state = CVCS_INIT;
		STACK_CALL(atm_stackcmds[cvp->cvc_attr.api].init,
				cvp->cvc_lower, cvp->cvc_tokl, cvp,
				cvp->cvc_attr.api_init, 0, err2);
		if (err2)
			panic("atm_cm_accept: init");

		if (cvp->cvc_flags & CVCF_ABORTING) {
			/*
			 * Someone on the stack bailed out...schedule the 
			 * VCC and stack termination
			 */
			err = ECONNABORTED;
		} else {
			/*
			 * Everything looks fine from here
			 */
			if (err)
				cvp->cvc_state = CVCS_ACCEPT;
			else
				cvp->cvc_state = CVCS_ACTIVE;
		}
		break;

	case CALL_FAILED:
		/*
		 * Terminate stack and clean up before we leave
		 */
		cvp->cvc_state = CVCS_CLEAR;
		break;

	default:
		panic("atm_cm_accept: accept");
	}

done:
	if (err == 0) {
		/*
		 * Call has been connected, notify endpoints
		 */
		while (cop) {
			Atm_connection	*cop2 = cop->co_next;

			cop->co_state = COS_ACTIVE;
			(*cop->co_endpt->ep_connected)(cop->co_toku);
			cop = cop2;
		}

	} else if (err == EINPROGRESS) {
		/*
		 * Call is still in progress, endpoint must wait
		 */
		err = 0;

	} else {
		/*
		 * Let caller know we failed
		 */
		cvp->cvc_attr.cause = atm_cause_tmpl;
		cvp->cvc_attr.cause.v.cause_value =
				T_ATM_CAUSE_UNSPECIFIED_RESOURCE_UNAVAILABLE;
	}

	return (err);
}


/*
 * Match Attributes on Listening Queue
 * 
 * This function will attempt to match the supplied connection attributes
 * with one of the registered attributes in the listening queue.  The pcop
 * argument may be supplied in order to allow multiple listeners to share 
 * an incoming call (if supported by the listeners).
 *
 * Called at splnet.
 *
 * Arguments:
 *	ap	pointer to attributes to be matched
 *	pcop	pointer to the previously matched connection
 *
 * Returns:
 *	addr	connection with which a match was found
 *	0	no match found
 *
 */
Atm_connection *
atm_cm_match(ap, pcop)
	Atm_attributes	*ap;
	Atm_connection	*pcop;
{
	Atm_connection	*cop;
	Atm_attributes	*lap;


	/*
	 * If we've already matched a listener...
	 */
	if (pcop) {
		/*
		 * Make sure already matched listener supports sharing
		 */
		if ((pcop->co_mpx != ATM_ENC_LLC) ||
		    ((pcop->co_llc.v.flags & T_ATM_LLC_SHARING) == 0))
			return (NULL);

		/*
		 * Position ourselves after the matched entry
		 */
		for (cop = atm_listen_queue; cop; cop = cop->co_next) {
			if (cop == pcop) {
				cop = pcop->co_next;
				break;
			}
		}
	} else {
		/*
		 * Start search at top of listening queue
		 */
		cop = atm_listen_queue;
	}

	/*
	 * Search through listening queue
	 */
	for (; cop; cop = cop->co_next) {

		lap = cop->co_lattr;

		/*
		 * If we're trying to share, check that this entry allows it
		 */
		if (pcop) {
			if ((cop->co_mpx != ATM_ENC_LLC) ||
			    ((cop->co_llc.v.flags & T_ATM_LLC_SHARING) == 0))
				continue;
		}

		/*
		 * ALL "matchable" attributes must match
		 */

		/*
		 * BHLI
		 */
		if (lap->bhli.tag == T_ATM_ABSENT) {
			if (ap->bhli.tag == T_ATM_PRESENT)
				continue;
		} else if (lap->bhli.tag == T_ATM_PRESENT) {
			if (ap->bhli.tag == T_ATM_ABSENT)
				continue;
			if (ap->bhli.tag == T_ATM_PRESENT)
				if (KM_CMP(&lap->bhli.v, &ap->bhli.v, 
						sizeof(struct t_atm_bhli)))
					continue;
		}

		/*
		 * BLLI Layer 2
		 */
		if (lap->blli.tag_l2 == T_ATM_ABSENT) {
			if (ap->blli.tag_l2 == T_ATM_PRESENT)
				continue;
		} else if (lap->blli.tag_l2 == T_ATM_PRESENT) {
			if (ap->blli.tag_l2 == T_ATM_ABSENT)
				continue;
			if (ap->blli.tag_l2 == T_ATM_PRESENT) {
				if (KM_CMP(&lap->blli.v.layer_2_protocol.ID,
					   &ap->blli.v.layer_2_protocol.ID, 
					   sizeof(
					      ap->blli.v.layer_2_protocol.ID)))
					continue;
			}
		}

		/*
		 * BLLI Layer 3
		 */
		if (lap->blli.tag_l3 == T_ATM_ABSENT) {
			if (ap->blli.tag_l3 == T_ATM_PRESENT)
				continue;
		} else if (lap->blli.tag_l3 == T_ATM_PRESENT) {
			if (ap->blli.tag_l3 == T_ATM_ABSENT)
				continue;
			if (ap->blli.tag_l3 == T_ATM_PRESENT) {
				if (KM_CMP(&lap->blli.v.layer_3_protocol.ID,
					   &ap->blli.v.layer_3_protocol.ID, 
					   sizeof(
					      ap->blli.v.layer_3_protocol.ID)))
					continue;
			}
		}

		/*
		 * LLC
		 */
		if (lap->llc.tag == T_ATM_ABSENT) {
			if (ap->llc.tag == T_ATM_PRESENT)
				continue;
		} else if (lap->llc.tag == T_ATM_PRESENT) {
			if (ap->llc.tag == T_ATM_ABSENT)
				continue;
			if (ap->llc.tag == T_ATM_PRESENT) {
				int	i = MIN(lap->llc.v.llc_len,
							ap->llc.v.llc_len);

				if (KM_CMP(lap->llc.v.llc_info,
							ap->llc.v.llc_info, i))
					continue;
			}
		}

		/*
		 * AAL
		 */
		if (lap->aal.tag == T_ATM_ABSENT) {
			if (ap->aal.tag == T_ATM_PRESENT)
				continue;
		} else if (lap->aal.tag == T_ATM_PRESENT) {
			if (ap->aal.tag == T_ATM_ABSENT)
				continue;
			if (ap->aal.tag == T_ATM_PRESENT) {
				if (lap->aal.type != ap->aal.type)
					continue;
				if (lap->aal.type == ATM_AAL5) {
					if (lap->aal.v.aal5.SSCS_type !=
						    ap->aal.v.aal5.SSCS_type)
						continue;
				} else {
					if (lap->aal.v.aal4.SSCS_type !=
						    ap->aal.v.aal4.SSCS_type)
						continue;
				}
			}
		}

		/*
		 * Called Party
		 */
		if (lap->called.tag == T_ATM_ABSENT) {
			if (ap->called.tag == T_ATM_PRESENT)
				continue;
		} else if (lap->called.tag == T_ATM_PRESENT) {
			if (ap->called.tag == T_ATM_ABSENT)
				continue;
			if (ap->called.tag == T_ATM_PRESENT) {
				if ((!ATM_ADDR_EQUAL(&lap->called.addr,
						&ap->called.addr)) ||
				    (!ATM_ADDR_EQUAL(&lap->called.subaddr,
						&ap->called.subaddr)))
					continue;
			}
		}

		/*
		 * Found a full match - return it
		 */
		break;
	}

	return (cop);
}


/*
 * Find Shareable LLC VCC
 * 
 * Given a endpoint-supplied connection attribute using LLC multiplexing,
 * this function will attempt to locate an existing connection which meets
 * the requirements of the supplied attributes.
 *
 * Called at splnet.
 *
 * Arguments:
 *	ap	pointer to requested attributes
 *
 * Returns:
 *	addr	shareable LLC connection VCC
 *	0	no shareable VCC available
 *
 */
static Atm_connvc *
atm_cm_share_llc(ap)
	Atm_attributes	*ap;
{
	Atm_connection	*cop;
	Atm_connvc	*cvp;

	/*
	 * Is requestor willing to share?
	 */
	if ((ap->llc.v.flags & T_ATM_LLC_SHARING) == 0)
		return (NULL);

	/*
	 * Try to find a shareable connection
	 */
	for (cvp = Q_HEAD(atm_connection_queue, Atm_connvc); cvp;
			cvp = Q_NEXT(cvp, Atm_connvc, cvc_q)) {

		/*
		 * Dont use terminating connections
		 */
		switch (cvp->cvc_state) {

		case CVCS_SETUP:
		case CVCS_ACCEPT:
		case CVCS_ACTIVE:
			break;

		default:
			continue;
		}

		/*
		 * Is connection LLC and shareable?
		 */
		if ((cvp->cvc_attr.llc.tag != T_ATM_PRESENT) ||
		    ((cvp->cvc_attr.llc.v.flags & T_ATM_LLC_SHARING) == 0))
			continue;

		/*
		 * Match requested attributes with existing connection
		 */
		if (ap->nif != cvp->cvc_attr.nif)
			continue;

		if ((ap->api != cvp->cvc_attr.api) ||
		    (ap->api_init != cvp->cvc_attr.api_init))
			continue;

		/*
		 * Remote Party
		 */
		if (cvp->cvc_flags & CVCF_CALLER) {
			if ((!ATM_ADDR_EQUAL(&ap->called.addr,
					&cvp->cvc_attr.called.addr)) ||
			    (!ATM_ADDR_EQUAL(&ap->called.subaddr,
					&cvp->cvc_attr.called.subaddr)))
				continue;
		} else {
			if (cvp->cvc_attr.calling.tag != T_ATM_PRESENT)
				continue;
			if ((!ATM_ADDR_EQUAL(&ap->called.addr,
					&cvp->cvc_attr.calling.addr)) ||
			    (!ATM_ADDR_EQUAL(&ap->called.subaddr,
					&cvp->cvc_attr.calling.subaddr)))
				continue;
		}

		/*
		 * AAL
		 */
		if (ap->aal.type == ATM_AAL5) {
			struct t_atm_aal5	*ap5, *cv5;

			ap5 = &ap->aal.v.aal5;
			cv5 = &cvp->cvc_attr.aal.v.aal5;

			if ((cvp->cvc_attr.aal.type != ATM_AAL5) ||
			    (ap5->SSCS_type != cv5->SSCS_type))
				continue;

			if (cvp->cvc_flags & CVCF_CALLER) {
				if (ap5->forward_max_SDU_size >
						cv5->forward_max_SDU_size)
					continue;
			} else {
				if (ap5->forward_max_SDU_size >
						cv5->backward_max_SDU_size)
					continue;
			}
		} else {
			struct t_atm_aal4	*ap4, *cv4;

			ap4 = &ap->aal.v.aal4;
			cv4 = &cvp->cvc_attr.aal.v.aal4;

			if ((cvp->cvc_attr.aal.type != ATM_AAL3_4) ||
			    (ap4->SSCS_type != cv4->SSCS_type))
				continue;

			if (cvp->cvc_flags & CVCF_CALLER) {
				if (ap4->forward_max_SDU_size >
						cv4->forward_max_SDU_size)
					continue;
			} else {
				if (ap4->forward_max_SDU_size >
						cv4->backward_max_SDU_size)
					continue;
			}
		}

		/*
		 * Traffic Descriptor
		 */
		if ((ap->traffic.tag != T_ATM_PRESENT) ||
		    (cvp->cvc_attr.traffic.tag != T_ATM_PRESENT) ||
		    (ap->traffic.v.best_effort != T_YES) ||
		    (cvp->cvc_attr.traffic.v.best_effort != T_YES))
			continue;

		/*
		 * Broadband Bearer
		 */
		if (ap->bearer.v.connection_configuration !=
				cvp->cvc_attr.bearer.v.connection_configuration)
			continue;

		/*
		 * QOS
		 */
		if (cvp->cvc_flags & CVCF_CALLER) {
			if ((ap->qos.v.forward.qos_class !=
				cvp->cvc_attr.qos.v.forward.qos_class) ||
			    (ap->qos.v.backward.qos_class !=
				cvp->cvc_attr.qos.v.backward.qos_class))
				continue;
		} else {
			if ((ap->qos.v.forward.qos_class !=
				cvp->cvc_attr.qos.v.backward.qos_class) ||
			    (ap->qos.v.backward.qos_class !=
				cvp->cvc_attr.qos.v.forward.qos_class))
				continue;
		}

		/*
		 * The new LLC header must also be unique for this VCC
		 */
		for (cop = cvp->cvc_conn; cop; cop = cop->co_next) {
			int	i = MIN(ap->llc.v.llc_len,
					cop->co_llc.v.llc_len);

			if (KM_CMP(ap->llc.v.llc_info, 
				   cop->co_llc.v.llc_info, i) == 0)
				break;
		}

		/*
		 * If no header overlaps, then we're done
		 */
		if (cop == NULL)
			break;
	}

	return (cvp);
}


/*
 * Close Connection
 * 
 * This function will terminate a connection, including notifying the
 * user, if necessary, and freeing up control block memory.  The caller
 * is responsible for managing the connection VCC.
 *
 * Called at splnet.
 *
 * Arguments:
 *	cop	pointer to connection block
 *	cause	pointer to cause of close
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_closeconn(cop, cause)
	Atm_connection	*cop;
	struct t_atm_cause	*cause;
{

	/*
	 * Decide whether user needs notification
	 */
	switch (cop->co_state) {

	case COS_OUTCONN:
	case COS_LISTEN:
	case COS_INCONN:
	case COS_INACCEPT:
	case COS_ACTIVE:
		/*
		 * Yup, let 'em know connection is gone
		 */
		if (cop->co_toku)
			(*cop->co_endpt->ep_cleared)(cop->co_toku, cause);
		break;

	case COS_CLEAR:
		/*
		 * Nope,they should know already
		 */
		break;

	default:
		panic("atm_cm_closeconn: bogus state");
	}

	/*
	 * Unlink connection from its queues
	 */
	switch (cop->co_state) {

	case COS_LISTEN:
		atm_free((caddr_t)cop->co_lattr);
		UNLINK(cop, Atm_connection, atm_listen_queue, co_next);
		break;

	default:
		/*
		 * Remove connection from multiplexor queue
		 */
		if (cop->co_mxh != cop) {
			/*
			 * Connection is down the chain, just unlink it
			 */
			UNLINK(cop, Atm_connection, cop->co_mxh, co_next);

		} else if (cop->co_next != NULL) {
			/*
			 * Connection is at the head of a non-singleton chain,
			 * so unlink and reset the chain head
			 */
			Atm_connection	*t, *nhd;

			t = nhd = cop->co_next;
			while (t) {
				t->co_mxh = nhd;
				t = t->co_next;
			}
			if (nhd->co_connvc)
				nhd->co_connvc->cvc_conn = nhd;
		}
	}

	/*
	 * Free the connection block
	 */
	cop->co_state = COS_FREE;
	atm_free((caddr_t)cop);

	return;
}


/*
 * Close Connection VCC
 * 
 * This function will terminate a connection VCC, including releasing the
 * the call to the signalling manager, terminating the VCC protocol stack,
 * and freeing up control block memory.
 *
 * Called at splnet.
 *
 * Arguments:
 *	cvp	pointer to connection VCC block
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_closevc(cvp)
	Atm_connvc	*cvp;
{
	int	err;

	/*
	 * Break links with the connection block
	 */
	cvp->cvc_conn = NULL;

	/*
	 * Cancel any running timer
	 */
	CVC_CANCEL(cvp);

	/*
	 * Free queued packets
	 */
	while (cvp->cvc_rcvq) {
		KBuffer		*m;

		m = cvp->cvc_rcvq;
		cvp->cvc_rcvq = KB_QNEXT(m);
		KB_QNEXT(m) = NULL;
		KB_FREEALL(m);
	}

	/*
	 * Unlink from any queues
	 */
	if (cvp->cvc_flags & CVCF_INCOMQ) {
		DEQUEUE(cvp, Atm_connvc, cvc_q, atm_incoming_queue);
		atm_incoming_qlen--;
		cvp->cvc_flags &=  ~CVCF_INCOMQ;

	} else if (cvp->cvc_flags & CVCF_CONNQ) {
		DEQUEUE(cvp, Atm_connvc, cvc_q, atm_connection_queue);
		cvp->cvc_flags &=  ~CVCF_CONNQ;
	}

	/*
	 * Release the signalling call
	 */
	switch (cvp->cvc_state) {

	case CVCS_SETUP:
	case CVCS_INIT:
	case CVCS_ACCEPT:
	case CVCS_ACTIVE:
	case CVCS_RELEASE:
		if (cvp->cvc_vcc) {
			cvp->cvc_state = CVCS_RELEASE;
			switch ((*cvp->cvc_sigmgr->sm_release)
				(cvp->cvc_vcc, &err)) {

			case CALL_CLEARED:
				/*
				 * Looks good so far...
				 */
				break;

			case CALL_PROCEEDING:
				/*
				 * We'll have to wait for the call to clear
				 */
				return;

			case CALL_FAILED:
				/*
				 * If there's a memory shortage, retry later.
				 * Otherwise who knows what's going on....
				 */
				if ((err == ENOMEM) || (err == ENOBUFS)) {
					CVC_TIMER(cvp, 1 * ATM_HZ);
					return;
				}
				log(LOG_ERR,
					"atm_cm_closevc: release %d\n", err);
				break;
			}
		}
		break;

	case CVCS_INCOMING:
	case CVCS_REJECT:
		if (cvp->cvc_vcc) {
			cvp->cvc_state = CVCS_REJECT;
			switch ((*cvp->cvc_sigmgr->sm_reject)
				(cvp->cvc_vcc, &err)) {

			case CALL_CLEARED:
				/*
				 * Looks good so far...
				 */
				break;

			case CALL_FAILED:
				/*
				 * If there's a memory shortage, retry later.
				 * Otherwise who knows what's going on....
				 */
				if ((err == ENOMEM) || (err == ENOBUFS)) {
					CVC_TIMER(cvp, 1 * ATM_HZ);
					return;
				}
				log(LOG_ERR,
					"atm_cm_closevc: reject %d\n", err);
				break;
			}
		}
		break;

	case CVCS_CLEAR:
	case CVCS_TERM:
		/*
		 * No need for anything here
		 */
		break;

	default:
		panic("atm_cm_closevc: bogus state");
	}

	/*
	 * Now terminate the stack
	 */
	if (cvp->cvc_tokl) {
		cvp->cvc_state = CVCS_TERM;

		/*
		 * Wait until stack is unwound before terminating
		 */
		if ((cvp->cvc_downcnt > 0) || (cvp->cvc_upcnt > 0)) {
			CVC_TIMER(cvp, 0);
			return;
		}

		STACK_CALL(atm_stackcmds[cvp->cvc_attr.api].term,
			cvp->cvc_lower, cvp->cvc_tokl, cvp, 0, 0, err);

		cvp->cvc_tokl = NULL;
	}

	/*
	 * Let signalling manager finish up
	 */
	cvp->cvc_state = CVCS_FREE;
	if (cvp->cvc_vcc) {
		(void) (*cvp->cvc_sigmgr->sm_free)(cvp->cvc_vcc);
	}

	/*
	 * Finally, free our own control blocks
	 */
	atm_free((caddr_t)cvp);

	return;
}


/*
 * Process a Connection VCC timeout
 * 
 * Called when a previously scheduled cvc control block timer expires.  
 * Processing will be based on the current cvc state.
 *
 * Called at splnet.
 *
 * Arguments:
 *	tip	pointer to cvc timer control block
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_timeout(tip)
	struct atm_time	*tip;
{
	Atm_connection	*cop, *cop2;
	Atm_connvc	*cvp;

	/*
	 * Back-off to cvc control block
	 */
	cvp = (Atm_connvc *)
			((caddr_t)tip - (int)(&((Atm_connvc *)0)->cvc_time));

	/*
	 * Process timeout based on protocol state
	 */
	switch (cvp->cvc_state) {

	case CVCS_SETUP:
	case CVCS_ACCEPT:
	case CVCS_ACTIVE:
		/*
		 * Handle VCC abort
		 */
		if ((cvp->cvc_flags & CVCF_ABORTING) == 0)
			goto logerr;

		/*
		 * Terminate all connections
		 */
		cop = cvp->cvc_conn;
		while (cop) {
			cop2 = cop->co_next;
			atm_cm_closeconn(cop, &cvp->cvc_attr.cause.v);
			cop = cop2;
		}

		/*
		 * Terminate VCC
		 */
		atm_cm_closevc(cvp);

		break;

	case CVCS_REJECT:
	case CVCS_RELEASE:
	case CVCS_TERM:
		/*
		 * Retry failed operation
		 */
		atm_cm_closevc(cvp);
		break;

	default:
logerr:
		log(LOG_ERR,
			"atm_cm_timeout: invalid state: cvp=%p, state=%d\n",
			cvp, cvp->cvc_state);
	}
}


/*
 * CPCS User Control Commands
 * 
 * This function is called by an endpoint user to pass a control command
 * across a CPCS data API.  Mostly we just send these down the stack.
 *
 * Arguments:
 *	cmd	stack command code
 *	cop	pointer to connection block
 *	arg	argument
 *
 * Returns:
 *	0	command output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_cpcs_ctl(cmd, cop, arg)
	int		cmd;
	Atm_connection	*cop;
	void		*arg;
{
	Atm_connvc	*cvp;
	int		err = 0;

	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_CPCS) {
		err = EFAULT;
		goto done;
	}

	switch (cmd) {

	default:
		err = EINVAL;
	}

done:
	return (err);
}


/*
 * CPCS Data Output
 * 
 * This function is called by an endpoint user to output a data packet
 * across a CPCS data API.   After we've validated the connection state, the
 * packet will be encapsulated (if necessary) and sent down the data stack.
 *
 * Arguments:
 *	cop	pointer to connection block
 *	m	pointer to packet buffer chain to be output
 *
 * Returns:
 *	0	packet output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_cpcs_data(cop, m)
	Atm_connection	*cop;
	KBuffer		*m;
{
	Atm_connvc	*cvp;
	struct attr_llc	*llcp;
	int		err, space;
	void		*bp;


	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_CPCS) {
		err = EFAULT;
		goto done;
	}

	/*
	 * Add any packet encapsulation
	 */
	switch (cop->co_mpx) {

	case ATM_ENC_NULL:
		/*
		 * None needed...
		 */
		break;

	case ATM_ENC_LLC:
		/*
		 * Need to add an LLC header
		 */
		llcp = &cop->co_llc;

		/*
		 * See if there's room to add LLC header to front of packet.
		 */
		KB_HEADROOM(m, space);
		if (space < llcp->v.llc_len) {
			KBuffer		*n;

			/*
			 * We have to allocate another buffer and tack it
			 * onto the front of the packet
			 */
			KB_ALLOCPKT(n, llcp->v.llc_len, KB_F_NOWAIT,
					KB_T_HEADER);
			if (n == 0) {
				err = ENOMEM;
				goto done;
			}
			KB_TAILALIGN(n, llcp->v.llc_len);
			KB_LINKHEAD(n, m);
			m = n;
		} else {
			/*
			 * Header fits, just adjust buffer controls
			 */
			KB_HEADADJ(m, llcp->v.llc_len);
		}

		/*
		 * Add the LLC header
		 */
		KB_DATASTART(m, bp, void *);
		KM_COPY(llcp->v.llc_info, bp, llcp->v.llc_len);
		KB_PLENADJ(m, llcp->v.llc_len);
		break;

	default:
		panic("atm_cm_cpcs_data: mpx");
	}

	/*
	 * Finally, we can send the packet on its way
	 */
	STACK_CALL(CPCS_UNITDATA_INV, cvp->cvc_lower, cvp->cvc_tokl, 
		cvp, (int)m, 0, err);

done:
	return (err);
}


/*
 * Process CPCS Stack Commands
 * 
 * This is the top of the CPCS API data stack.  All upward stack commands 
 * for the CPCS data API will be received and processed here.
 *
 * Arguments:
 *	cmd	stack command code
 *	tok	session token (pointer to connection VCC control block)
 *	arg1	argument 1
 *	arg2	argument 2
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_cpcs_upper(cmd, tok, arg1, arg2)
	int		cmd;
	void		*tok;
	int		arg1;
	int		arg2;
{
	Atm_connection	*cop;
	Atm_connvc	*cvp = tok;
	KBuffer		*m;
	void		*bp;
	int		s;

	switch (cmd) {

	case CPCS_UNITDATA_SIG:
		/*
		 * Input data packet
		 */
		m = (KBuffer *)arg1;

		if (cvp->cvc_state != CVCS_ACTIVE) {
			if (cvp->cvc_state == CVCS_ACCEPT) {
				KBuffer	*n;

				/*
				 * Queue up any packets received before sigmgr
				 * notifies us of incoming call completion
				 */
				if (cvp->cvc_rcvqlen >= CVC_RCVQ_MAX) {
					KB_FREEALL(m);
					atm_cm_stat.cms_rcvconnvc++;
					return;
				}
				KB_QNEXT(m) = NULL;
				if (cvp->cvc_rcvq == NULL) {
					cvp->cvc_rcvq = m;
				} else {
					for (n = cvp->cvc_rcvq; 
					     KB_QNEXT(n) != NULL; 
					     n = KB_QNEXT(n))
						;
					KB_QNEXT(n) = m;
				}
				cvp->cvc_rcvqlen++;
				return;
			} else {
				KB_FREEALL(m);
				atm_cm_stat.cms_rcvconnvc++;
				return;
			}
		}

		/*
		 * Locate packet's connection
		 */
		cop = cvp->cvc_conn;
		switch (cop->co_mpx) {

		case ATM_ENC_NULL:
			/*
			 * We're already there...
			 */
			break;

		case ATM_ENC_LLC:
			/*
			 * Find connection with matching LLC header
			 */
			if (KB_LEN(m) < T_ATM_LLC_MAX_LEN) {
				KB_PULLUP(m, T_ATM_LLC_MAX_LEN, m);
				if (m == 0) {
					atm_cm_stat.cms_llcdrop++;
					return;
				}
			}
			KB_DATASTART(m, bp, void *);

			s = splnet();

			while (cop) {
				if (KM_CMP(bp, cop->co_llc.v.llc_info,
						cop->co_llc.v.llc_len) == 0)
					break;
				cop = cop->co_next;
			}

			(void) splx(s);

			if (cop == NULL) {
				/*
				 * No connected user for this LLC
				 */
				KB_FREEALL(m);
				atm_cm_stat.cms_llcid++;
				return;
			}

			/*
			 * Strip off the LLC header
			 */
			KB_HEADADJ(m, -cop->co_llc.v.llc_len);
			KB_PLENADJ(m, -cop->co_llc.v.llc_len);
			break;

		default:
			panic("atm_cm_cpcs_upper: mpx");
		}

		/*
		 * We've found our connection, so hand the packet off
		 */
		if (cop->co_state != COS_ACTIVE) {
			KB_FREEALL(m);
			atm_cm_stat.cms_rcvconn++;
			return;
		}
		(*cop->co_endpt->ep_cpcs_data)(cop->co_toku, m);
		break;

	case CPCS_UABORT_SIG:
	case CPCS_PABORT_SIG:
		/*
		 * We don't support these (yet), so just fall thru...
		 */

	default:
		log(LOG_ERR, "atm_cm_cpcs_upper: unknown cmd 0x%x\n", cmd);
	}
}


/*
 * SAAL User Control Commands
 * 
 * This function is called by an endpoint user to pass a control command
 * across a SAAL data API.  Mostly we just send these down the stack.
 *
 * Arguments:
 *	cmd	stack command code
 *	cop	pointer to connection block
 *	arg	argument
 *
 * Returns:
 *	0	command output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_saal_ctl(cmd, cop, arg)
	int		cmd;
	Atm_connection	*cop;
	void		*arg;
{
	Atm_connvc	*cvp;
	int		err = 0;

	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_SAAL) {
		err = EFAULT;
		goto done;
	}

	switch (cmd) {

	case SSCF_UNI_ESTABLISH_REQ:
	case SSCF_UNI_RELEASE_REQ:
		/*
		 * Pass command down the stack
		 */
		STACK_CALL(cmd, cvp->cvc_lower, cvp->cvc_tokl, cvp, 
			(int)arg, 0, err);
		break;

	default:
		err = EINVAL;
	}

done:
	return (err);
}


/*
 * SAAL Data Output
 * 
 * This function is called by an endpoint user to output a data packet
 * across a SAAL data API.   After we've validated the connection state,
 * the packet will be sent down the data stack.
 *
 * Arguments:
 *	cop	pointer to connection block
 *	m	pointer to packet buffer chain to be output
 *
 * Returns:
 *	0	packet output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_saal_data(cop, m)
	Atm_connection	*cop;
	KBuffer		*m;
{
	Atm_connvc	*cvp;
	int		err;


	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_SAAL) {
		err = EFAULT;
		goto done;
	}

	/*
	 * Finally, we can send the packet on its way
	 */
	STACK_CALL(SSCF_UNI_DATA_REQ, cvp->cvc_lower, cvp->cvc_tokl, 
		cvp, (int)m, 0, err);

done:
	return (err);
}


/*
 * Process SAAL Stack Commands
 * 
 * This is the top of the SAAL API data stack.  All upward stack commands 
 * for the SAAL data API will be received and processed here.
 *
 * Arguments:
 *	cmd	stack command code
 *	tok	session token (pointer to connection VCC control block)
 *	arg1	argument 1
 *	arg2	argument 2
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_saal_upper(cmd, tok, arg1, arg2)
	int		cmd;
	void		*tok;
	int		arg1;
	int		arg2;
{
	Atm_connection	*cop;
	Atm_connvc	*cvp = tok;


	switch (cmd) {

	case SSCF_UNI_ESTABLISH_IND:
	case SSCF_UNI_ESTABLISH_CNF:
	case SSCF_UNI_RELEASE_IND:
	case SSCF_UNI_RELEASE_CNF:
		/*
		 * Control commands
		 */
		cop = cvp->cvc_conn;
		if (cvp->cvc_state != CVCS_ACTIVE)
			break;
		if (cop->co_state != COS_ACTIVE)
			break;

		(*cop->co_endpt->ep_saal_ctl)(cmd, cop->co_toku, (void *)arg1);
		break;

	case SSCF_UNI_DATA_IND:
		/*
		 * User data
		 */
		cop = cvp->cvc_conn;
		if (cvp->cvc_state != CVCS_ACTIVE) {
			atm_cm_stat.cms_rcvconnvc++;
			KB_FREEALL((KBuffer *)arg1);
			break;
		}
		if (cop->co_state != COS_ACTIVE) {
			atm_cm_stat.cms_rcvconn++;
			KB_FREEALL((KBuffer *)arg1);
			break;
		}

		(*cop->co_endpt->ep_saal_data)(cop->co_toku, (KBuffer *)arg1);
		break;

	case SSCF_UNI_UNITDATA_IND:
		/*
		 * Not supported
		 */
		KB_FREEALL((KBuffer *)arg1);

		/* FALLTHRU */

	default:
		log(LOG_ERR, "atm_cm_saal_upper: unknown cmd 0x%x\n", cmd);
	}
}


/*
 * SSCOP User Control Commands
 * 
 * This function is called by an endpoint user to pass a control command
 * across a SSCOP data API.  Mostly we just send these down the stack.
 *
 * Arguments:
 *	cmd	stack command code
 *	cop	pointer to connection block
 *	arg1	argument
 *	arg2	argument
 *
 * Returns:
 *	0	command output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_sscop_ctl(cmd, cop, arg1, arg2)
	int		cmd;
	Atm_connection	*cop;
	void		*arg1;
	void		*arg2;
{
	Atm_connvc	*cvp;
	int		err = 0;

	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_SSCOP) {
		err = EFAULT;
		goto done;
	}

	switch (cmd) {

	case SSCOP_ESTABLISH_REQ:
	case SSCOP_ESTABLISH_RSP:
	case SSCOP_RELEASE_REQ:
	case SSCOP_RESYNC_REQ:
	case SSCOP_RESYNC_RSP:
	case SSCOP_RECOVER_RSP:
	case SSCOP_RETRIEVE_REQ:
		/*
		 * Pass command down the stack
		 */
		STACK_CALL(cmd, cvp->cvc_lower, cvp->cvc_tokl, cvp, 
			(int)arg1, (int)arg2, err);
		break;

	default:
		err = EINVAL;
	}

done:
	return (err);
}


/*
 * SSCOP Data Output
 * 
 * This function is called by an endpoint user to output a data packet
 * across a SSCOP data API.   After we've validated the connection state,
 * the packet will be encapsulated and sent down the data stack.
 *
 * Arguments:
 *	cop	pointer to connection block
 *	m	pointer to packet buffer chain to be output
 *
 * Returns:
 *	0	packet output successful
 *	errno	output failed - reason indicated
 *
 */
int
atm_cm_sscop_data(cop, m)
	Atm_connection	*cop;
	KBuffer		*m;
{
	Atm_connvc	*cvp;
	int		err;


	/*
	 * Validate connection state
	 */
	if (cop->co_state != COS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	cvp = cop->co_connvc;
	if (cvp->cvc_state != CVCS_ACTIVE) {
		err = EFAULT;
		goto done;
	}

	if (cvp->cvc_attr.api != CMAPI_SSCOP) {
		err = EFAULT;
		goto done;
	}

	/*
	 * Finally, we can send the packet on its way
	 */
	STACK_CALL(SSCOP_DATA_REQ, cvp->cvc_lower, cvp->cvc_tokl, 
		cvp, (int)m, 0, err);

done:
	return (err);
}


/*
 * Process SSCOP Stack Commands
 * 
 * This is the top of the SSCOP API data stack.  All upward stack commands 
 * for the SSCOP data API will be received and processed here.
 *
 * Arguments:
 *	cmd	stack command code
 *	tok	session token (pointer to connection VCC control block)
 *	arg1	argument 1
 *	arg2	argument 2
 *
 * Returns:
 *	none
 *
 */
static void
atm_cm_sscop_upper(cmd, tok, arg1, arg2)
	int		cmd;
	void		*tok;
	int		arg1;
	int		arg2;
{
	Atm_connection	*cop;
	Atm_connvc	*cvp = tok;

	switch (cmd) {

	case SSCOP_ESTABLISH_IND:
	case SSCOP_ESTABLISH_CNF:
	case SSCOP_RELEASE_IND:
	case SSCOP_RESYNC_IND:
		/*
		 * Control commands
		 */
		cop = cvp->cvc_conn;
		if ((cvp->cvc_state != CVCS_ACTIVE) ||
		    (cop->co_state != COS_ACTIVE)) {
			KB_FREEALL((KBuffer *)arg1);
			break;
		}

		(*cop->co_endpt->ep_sscop_ctl)
			(cmd, cop->co_toku, (void *)arg1, (void *)arg2);
		break;

	case SSCOP_RELEASE_CNF:
	case SSCOP_RESYNC_CNF:
	case SSCOP_RECOVER_IND:
	case SSCOP_RETRIEVE_IND:
	case SSCOP_RETRIEVECMP_IND:
		/*
		 * Control commands
		 */
		cop = cvp->cvc_conn;
		if ((cvp->cvc_state != CVCS_ACTIVE) ||
		    (cop->co_state != COS_ACTIVE))
			break;

		(*cop->co_endpt->ep_sscop_ctl)
			(cmd, cop->co_toku, (void *)arg1, (void *)arg2);
		break;

	case SSCOP_DATA_IND:
		/*
		 * User data
		 */
		cop = cvp->cvc_conn;
		if (cvp->cvc_state != CVCS_ACTIVE) {
			atm_cm_stat.cms_rcvconnvc++;
			KB_FREEALL((KBuffer *)arg1);
			break;
		}
		if (cop->co_state != COS_ACTIVE) {
			atm_cm_stat.cms_rcvconn++;
			KB_FREEALL((KBuffer *)arg1);
			break;
		}

		(*cop->co_endpt->ep_sscop_data)
				(cop->co_toku, (KBuffer *)arg1, arg2);
		break;

	case SSCOP_UNITDATA_IND:
		/*
		 * Not supported
		 */
		KB_FREEALL((KBuffer *)arg1);

		/* FALLTHRU */

	default:
		log(LOG_ERR, "atm_cm_sscop_upper: unknown cmd 0x%x\n", cmd);
	}
}


/*
 * Register an ATM Endpoint Service
 * 
 * Every ATM endpoint service must register itself here before it can 
 * issue or receive any connection requests.
 *
 * Arguments:
 *	epp	pointer to endpoint definition structure
 *
 * Returns:
 *	0	registration successful
 *	errno	registration failed - reason indicated
 *
 */
int
atm_endpoint_register(epp)
	Atm_endpoint	*epp;
{
	int		s = splnet();

	/*
	 * See if we need to be initialized
	 */
	if (!atm_init)
		atm_initialize();

	/*
	 * Validate endpoint
	 */
	if (epp->ep_id > ENDPT_MAX) {
		(void) splx(s);
		return (EINVAL);
	}
	if (atm_endpoints[epp->ep_id] != NULL) {
		(void) splx(s);
		return (EEXIST);
	}

	/*
	 * Add endpoint to list
	 */
	atm_endpoints[epp->ep_id] = epp;

	(void) splx(s);
	return (0);
}


/*
 * De-register an ATM Endpoint Service
 * 
 * Each ATM endpoint service provider must de-register its registered 
 * endpoint(s) before terminating.  Specifically, loaded kernel modules
 * must de-register their services before unloading themselves.
 *
 * Arguments:
 *	epp	pointer to endpoint definition structure
 *
 * Returns:
 *	0	de-registration successful 
 *	errno	de-registration failed - reason indicated
 *
 */
int
atm_endpoint_deregister(epp)
	Atm_endpoint	*epp;
{
	int	s = splnet();

	/*
	 * Validate endpoint
	 */
	if (epp->ep_id > ENDPT_MAX) {
		(void) splx(s);
		return (EINVAL);
	}
	if (atm_endpoints[epp->ep_id] != epp) {
		(void) splx(s);
		return (ENOENT);
	}

	/*
	 * Remove endpoint from list
	 */
	atm_endpoints[epp->ep_id] = NULL;

	(void) splx(s);
	return (0);
}

