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

/*
 * ATM Forum UNI Support
 * ---------------------
 *
 * Service Specific Connection Oriented Protocol (SSCOP)
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/netatm/uni/sscop.c,v 1.18 2005/01/07 01:45:37 imp Exp $");

#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <net/if.h>
#include <netatm/port.h>
#include <netatm/queue.h>
#include <netatm/atm.h>
#include <netatm/atm_sys.h>
#include <netatm/atm_sap.h>
#include <netatm/atm_cm.h>
#include <netatm/atm_if.h>
#include <netatm/atm_vc.h>
#include <netatm/atm_stack.h>
#include <netatm/atm_pcb.h>
#include <netatm/atm_var.h>

#include <netatm/uni/sscop.h>
#include <netatm/uni/sscop_misc.h>
#include <netatm/uni/sscop_pdu.h>
#include <netatm/uni/sscop_var.h>

#include <vm/uma.h>

/*
 * Global variables
 */
int	sscop_vccnt = 0;

struct sscop		*sscop_head = NULL;

struct sscop_stat	sscop_stat = {0};

struct atm_time		sscop_timer = {0, 0};

uma_zone_t	sscop_zone;

/*
 * Local functions
 */
static int	sscop_inst(struct stack_defn **, Atm_connvc *);


/*
 * Local variables
 */
static struct stack_defn	sscop_service = {
	NULL,
	SAP_SSCOP,
	0,
	sscop_inst,
	sscop_lower,
	sscop_upper,
	0
};

static struct t_atm_cause       sscop_cause = {
	T_ATM_ITU_CODING,
	T_ATM_LOC_USER,
	T_ATM_CAUSE_TEMPORARY_FAILURE,
	{0, 0, 0, 0}
};

static u_char	sscop_maa_log[MAA_ERROR_COUNT] = {
	1,	/* A */
	1,	/* B */
	1,	/* C */
	1,	/* D */
	1,	/* E */
	1,	/* F */
	1,	/* G */
	1,	/* H */
	1,	/* I */
	1,	/* J */
	1,	/* K */
	1,	/* L */
	1,	/* M */
	0,	/* N */
	0,	/* O */
	0,	/* P */
	1,	/* Q */
	1,	/* R */
	1,	/* S */
	1,	/* T */
	1,	/* U */
	0,	/* V */
	0,	/* W */
	0,	/* X */
	1	/* INVAL */
};


/*
 * Initialize SSCOP processing
 * 
 * This will be called during module loading.  We will register our stack
 * service and wait for someone to talk to us.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	initialization was successful 
 *	errno	initialization failed - reason indicated
 *
 */
int
sscop_start()
{
	int	err = 0;

	sscop_zone = uma_zcreate("sscop", sizeof(struct sscop), NULL, NULL,
	    NULL, NULL, UMA_ALIGN_PTR, 0);
	if (sscop_zone == NULL)
		panic("sscop_start: uma_zcreate");

	/*
	 * Register stack service
	 */
	if ((err = atm_stack_register(&sscop_service)) != 0)
		goto done;

	/*
	 * Start up timer
	 */
	atm_timeout(&sscop_timer, ATM_HZ/SSCOP_HZ, sscop_timeout);

done:
	return (err);
}


/*
 * Terminate SSCOP processing 
 * 
 * This will be called just prior to unloading the module from memory.  All 
 * signalling instances should have been terminated by now, so we just free
 * up all of our resources.
 *
 * Called at splnet.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	termination was successful 
 *	errno	termination failed - reason indicated
 *
 */
int
sscop_stop()
{
	int	err = 0;

	/*
	 * Any connections still exist??  If so, we can't bail just yet.
	 */
	if (sscop_vccnt)
		return (EBUSY);

	/*
	 * Stop our timer
	 */
	(void)atm_untimeout(&sscop_timer);

	/*
	 * Deregister the stack service
	 */
	(void)atm_stack_deregister(&sscop_service);
	uma_zdestroy(sscop_zone);
	return (err);
}


/*
 * SSCOP Stack Instantiation 
 * 
 * Called at splnet.
 *
 * Arguments:
 *	ssp	pointer to array of stack definition pointers for connection
 *		ssp[0] points to upper layer's stack service definition
 *		ssp[1] points to this layer's stack service definition
 *		ssp[2] points to lower layer's stack service definition
 *	cvp	pointer to connection vcc for this stack
 *
 * Returns:
 *	0 	instantiation successful
 *	errno	instantiation failed - reason indicated
 *
 */
static int
sscop_inst(ssp, cvp)
	struct stack_defn	**ssp;
	Atm_connvc		*cvp;
{
	struct stack_defn	*sdp_up = ssp[0],
				*sdp_me = ssp[1],
				*sdp_low = ssp[2];
	struct sscop	*sop;
	int		err;

	ATM_DEBUG2("sscop_inst: ssp=%p, cvp=%p\n", ssp, cvp);

	/*
	 * Validate lower SAP
	 */
	if ((sdp_low->sd_sap & SAP_CLASS_MASK) != SAP_CPCS)
		return (EINVAL);

	/*
	 * Allocate our control block
	 */
	sop = uma_zalloc(sscop_zone, M_WAITOK);
	if (sop == NULL)
		return (ENOMEM);

	sop->so_state = SOS_INST;
	sop->so_connvc = cvp;
	sop->so_toku = sdp_up->sd_toku;
	sop->so_upper = sdp_up->sd_upper;

	/*
	 * Store my token into service definition
	 */
	sdp_me->sd_toku = sop;

	/*
	 * Update and save input buffer headroom
	 */
	HEADIN(cvp, sizeof(struct pdu_hdr), 0);
	/* sop->so_headin = cvp->cvc_attr.headin; */

	/*
	 * Pass instantiation down the stack
	 */
	err = sdp_low->sd_inst(ssp + 1, cvp);
	if (err) {
		/*
		 * Lower layer instantiation failed, free our resources
		 */
		uma_zfree(sscop_zone, sop);
		return (err);
	}

	/*
	 * Link in connection block
	 */
	LINK2TAIL(sop, struct sscop, sscop_head, so_next);
	sscop_vccnt++;
	sscop_stat.sos_connects++;

	/*
	 * Save and update output buffer headroom
	 */
	sop->so_headout = cvp->cvc_attr.headout;
	HEADOUT(cvp, sizeof(struct pdu_hdr), 0);

	/*
	 * Save lower layer's interface info
	 */
	sop->so_lower = sdp_low->sd_lower;
	sop->so_tokl = sdp_low->sd_toku;

	/*
	 * Initialize version (until INIT received)
	 */
	sop->so_vers = SSCOP_VERS_Q2110;

	return (0);
}


/*
 * Report Management Error
 * 
 * Called to report an error to the layer management entity.
 *
 * Arguments:
 *	sop	pointer to sscop control block
 *	code	error code
 *
 * Returns:
 *	none
 *
 */
void
sscop_maa_error(sop, code)
	struct sscop	*sop;
	int		code;
{
	int		i;

	/*
	 * Validate error code
	 */
	if ((code < MAA_ERROR_MIN) ||
	    (code > MAA_ERROR_MAX))
		code = MAA_ERROR_INVAL;
	i = code - MAA_ERROR_MIN;

	/*
	 * Bump statistics counters
	 */
	sscop_stat.sos_maa_error[i]++;

	/*
	 * Log error message
	 */
	if (sscop_maa_log[i] != 0) {
		struct vccb	*vcp = sop->so_connvc->cvc_vcc;
		struct atm_pif	*pip = vcp->vc_pif;

		log(LOG_ERR,
			"sscop_maa_error: intf=%s%d vpi=%d vci=%d code=%c state=%d\n",
			pip->pif_name, pip->pif_unit,
			vcp->vc_vpi, vcp->vc_vci, code, sop->so_state);
	}
}


/*
 * Abort an SSCOP connection
 * 
 * Called when an unrecoverable or "should never happen" error occurs.
 * We log a message, send an END PDU to our peer and request the signalling
 * manager to abort the connection.
 *
 * Arguments:
 *	sop	pointer to sscop control block
 *	msg	pointer to error message
 *
 * Returns:
 *	none
 *
 */
void
sscop_abort(sop, msg)
	struct sscop	*sop;
	char		*msg;
{
	Atm_connvc	*cvp = sop->so_connvc;

	/*
	 * Log and count error
	 */
	log(LOG_ERR, "%s", msg);
	sscop_stat.sos_aborts++;

	/*
	 * Send an END PDU as a courtesy to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Set termination state
	 */
	sop->so_state = SOS_TERM;

	/*
	 * Flush all of our queues
	 */
	sscop_xmit_drain(sop);
	sscop_rcvr_drain(sop);

	/*
	 * Tell Connection Manager to abort this connection
	 */
	(void) atm_cm_abort(cvp, &sscop_cause);
}

