/*
 *
 * ===================================
 * 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.
 *
 *	@(#) $Id: sigpvc_if.c,v 1.2 1998/10/31 20:06:56 phk Exp $
 *
 */

/*
 * PVC-only Signalling Manager
 * ---------------------------
 *
 * External interfaces to SigPVC manager.  Includes support for 
 * running as a loadable kernel module.
 *
 */

#ifndef ATM_SIGPVC_MODULE
#include "opt_atm.h"
#endif

#include <netatm/kern_include.h>

#include <netatm/sigpvc/sigpvc.h>
#include <netatm/sigpvc/sigpvc_var.h>

#ifndef lint
__RCSID("@(#) $Id: sigpvc_if.c,v 1.2 1998/10/31 20:06:56 phk Exp $");
#endif


/*
 * Global variables
 */
struct sp_info	sigpvc_vcpool = {
	"sigpvc vcc pool",		/* si_name */
	sizeof(struct sigpvc_vccb),	/* si_blksiz */
	10,				/* si_blkcnt */
	50				/* si_maxallow */
};

/*
 * Local functions
 */
static int	sigpvc_start __P((void));
static int	sigpvc_stop __P((void));
static int	sigpvc_attach __P((struct sigmgr *, struct atm_pif *));
static int	sigpvc_detach __P((struct atm_pif *));
static int	sigpvc_setup __P((Atm_connvc *, int *));
static int	sigpvc_release __P((struct vccb *, int *));
static int	sigpvc_free __P((struct vccb *));
static int	sigpvc_ioctl __P((int, caddr_t, caddr_t));

/*
 * Local variables
 */
static int	sigpvc_registered = 0;
static struct sigmgr	sigpvc_mgr = {
	NULL,
	ATM_SIG_PVC,
	NULL,
	sigpvc_attach,
	sigpvc_detach,
	sigpvc_setup,
	NULL,
	NULL,
	sigpvc_release,
	sigpvc_free,
	sigpvc_ioctl
};

static struct attr_cause	sigpvc_cause = {
	T_ATM_PRESENT,
	{
		T_ATM_ITU_CODING,
		T_ATM_LOC_USER,
		T_ATM_CAUSE_UNSPECIFIED_NORMAL,
		{0, 0, 0, 0}
	}
};


/*
 * Initialize sigpvc processing
 * 
 * This will be called during module loading.  We'll just register
 * the sigpvc protocol descriptor and wait for a SigPVC ATM interface 
 * to come online.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	startup was successful 
 *	errno	startup failed - reason indicated
 *
 */
static int
sigpvc_start()
{
	int	err = 0;

	/*
	 * Verify software version
	 */
	if (atm_version != ATM_VERSION) {
		log(LOG_ERR, "version mismatch: sigpvc=%d.%d kernel=%d.%d\n",
			ATM_VERS_MAJ(ATM_VERSION), ATM_VERS_MIN(ATM_VERSION),
			ATM_VERS_MAJ(atm_version), ATM_VERS_MIN(atm_version));
		return (EINVAL);
	}

	/*
	 * Register ourselves with system
	 */
	err = atm_sigmgr_register(&sigpvc_mgr);
	if (err == 0)
		sigpvc_registered = 1;

	return (err);
}


/*
 * Halt sigpvc processing 
 * 
 * This should be called just prior to unloading the module from
 * memory.  All sigpvc interfaces must be deregistered before the
 * protocol can be shutdown.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	shutdown was successful 
 *	errno	shutdown failed - reason indicated
 *
 */
static int
sigpvc_stop()
{
	int	err = 0;
	int	s = splnet();

	/*
	 * Is protocol even setup?
	 */
	if (sigpvc_registered) {
		
		/*
		 * Any protocol instances still registered??
		 */
		if (sigpvc_mgr.sm_prinst) {

			/* Yes, can't stop now */
			err = EBUSY;
			goto done;
		}

		/*
		 * De-register from system
		 */
		err = atm_sigmgr_deregister(&sigpvc_mgr);
		sigpvc_registered = 0;

		/*
		 * Free up our vccb storage pool
		 */
		atm_release_pool(&sigpvc_vcpool);
	} else
		err = ENXIO;

done:
	(void) splx(s);
	return (err);
}


/*
 * Attach a SigPVC-controlled interface
 * 
 * Each ATM physical interface must be attached with the signalling manager for
 * the interface's signalling protocol (via the atm_sigmgr_attach function).  
 * This function will handle the attachment for SigPVC-controlled interfaces.
 * A new sigpvc protocol instance will be created and then we'll just sit
 * around waiting for connection requests.
 *
 * Function must be called at splnet.
 *
 * Arguments:
 *	smp	pointer to sigpvc signalling manager control block
 *	pip	pointer to atm physical interface control block
 *
 * Returns:
 *	0 	attach successful 
 *	errno	attach failed - reason indicated
 *
 */
static int
sigpvc_attach(smp, pip)
	struct sigmgr	*smp;
	struct atm_pif	*pip;
{
	int	err = 0;
	struct sigpvc	*pvp = NULL;

	/*
	 * Allocate sigpvc protocol instance control block
	 */
	pvp = (struct sigpvc *)
		KM_ALLOC(sizeof(struct sigpvc), M_DEVBUF, M_NOWAIT);
	if (pvp == NULL) {
		err = ENOMEM;
		goto done;
	}
	KM_ZERO(pvp, sizeof(struct sigpvc));

	/*
	 * Link instance into manager's chain
	 */
	LINK2TAIL((struct siginst *)pvp, struct siginst, 
		smp->sm_prinst, si_next);

	/*
	 * Finally, set state and link in interface
	 */
	pvp->pv_pif = pip;
	pvp->pv_state = SIGPVC_ACTIVE;
	pip->pif_sigmgr = smp;
	pip->pif_siginst = (struct siginst *)pvp;

done:
	/*
	 * Reset our work if attach fails
	 */
	if (err) {
		pip->pif_sigmgr = NULL;
		pip->pif_siginst = NULL;
		if (pvp) {
			UNLINK((struct siginst *)pvp, struct siginst, 
				smp->sm_prinst, si_next);
			KM_FREE(pvp, sizeof(struct sigpvc), M_DEVBUF);
		}
	}

	return (err);
}


/*
 * Detach a SigPVC-controlled interface
 * 
 * Each ATM physical interface may be detached from its signalling manager 
 * (via the atm_sigmgr_detach function).  This function will handle the 
 * detachment for all SigPVC-controlled interfaces.  All circuits will be 
 * immediately terminated.
 *
 * Function must be called at splnet.
 *
 * Arguments:
 *	pip	pointer to atm physical interface control block
 *
 * Returns:
 *	0 	detach successful 
 *	errno	detach failed - reason indicated
 *
 */
static int
sigpvc_detach(pip)
	struct atm_pif	*pip;
{
	struct sigpvc	*pvp;
	struct vccb	*vcp, *vnext;

	/*
	 * Get SigPVC protocol instance
	 */
	pvp = (struct sigpvc *)pip->pif_siginst;

	/*
	 * Terminate all of our VCCs
	 */
	for (vcp = Q_HEAD(pvp->pv_vccq, struct vccb); vcp; vcp = vnext){
		u_char	oustate;

		vnext = Q_NEXT(vcp, struct vccb, vc_sigelem);

		/*
		 * Close VCC and notify owner
		 */
		oustate = vcp->vc_ustate;
		sigpvc_close_vcc(vcp);
		if (oustate == VCCU_OPEN) {
			vcp->vc_connvc->cvc_attr.cause = sigpvc_cause;
			atm_cm_cleared(vcp->vc_connvc);
		}
	}

	/*
	 * If there are no vcc's queued, then get rid of the protocol 
	 * instance.  
	 */
	if (Q_HEAD(pvp->pv_vccq, struct vccb) == NULL) {
		struct sigmgr	*smp = pip->pif_sigmgr;

		pip->pif_sigmgr = NULL;
		pip->pif_siginst = NULL;
		UNLINK((struct siginst *)pvp, struct siginst, smp->sm_prinst, 
				si_next);
		KM_FREE(pvp, sizeof(struct sigpvc), M_DEVBUF);
	} else {

		/*
		 * Otherwise, set new state indicating detach in progress.
		 * The protocol instance will be freed during sigpvc_free 
		 * processing for the last queued vcc.
		 */
		pvp->pv_state = SIGPVC_DETACH;
	}

	return (0);
}


/*
 * Open a SigPVC ATM Connection
 * 
 * All service user requests to open a VC connection (via atm_open_connection)
 * over an ATM interface attached to the SigPVC signalling manager are handled 
 * here.  Only PVC requests are allowed.
 *
 * Function will be called at splnet.
 *
 * Arguments:
 *	cvp	pointer to CM's connection VCC
 *	errp	location to store an error code if CALL_FAILED is returned
 *
 * Returns:
 *	CALL_PROCEEDING	- connection establishment is in progress
 *	CALL_FAILED	- connection establishment failed
 *	CALL_CONNECTED	- connection has been successfully established
 *
 */
static int
sigpvc_setup(cvp, errp)
	Atm_connvc	*cvp;
	int		*errp;
{
	struct sigpvc	*pvp =
		(struct sigpvc *)cvp->cvc_attr.nif->nif_pif->pif_siginst;
	int	ret;

	/*
	 * See what signalling has to say
	 */
	switch (pvp->pv_state) {

	case SIGPVC_ACTIVE:
		break;

	default:
		*errp = ENXIO;
		ret = CALL_FAILED;
		goto done;
	}
	
	/*
	 * Open requested type of connection
	 */
	switch (cvp->cvc_attr.called.addr.address_format) {

	case T_ATM_PVC_ADDR:
		/*
		 * Create a PVC
		 */
		ret = sigpvc_create_pvc(pvp, cvp, errp);
		break;

	default:
		*errp = EPROTONOSUPPORT;
		ret = CALL_FAILED;
	}

done:
	return (ret);
}


/*
 * Close a SigPVC ATM Connection
 * 
 * All service user requests to terminate a previously open VC connection 
 * (via the atm_close_connection function), which is running over an interface 
 * attached to the SigPVC signalling manager, are handled here.
 *
 * Function will be called at splnet.
 * 
 * Arguments:
 *	vcp	pointer to connection's VC control block
 *	errp	location to store an error code if CALL_FAILED is returned
 *
 * Returns:
 *	CALL_PROCEEDING	- connection termination is in progress
 *	CALL_FAILED	- connection termination failed
 *	CALL_CLEARED	- connection has been successfully terminated
 *
 */
static int
sigpvc_release(vcp, errp)
	struct vccb	*vcp;
	int		*errp;
{

	/*
	 * Make sure VCC is open
	 */
	if ((vcp->vc_sstate == VCCS_NULL) || (vcp->vc_sstate == VCCS_FREE) ||
	    (vcp->vc_ustate == VCCU_NULL) || (vcp->vc_ustate == VCCU_CLOSED)) {
		*errp = EALREADY;
		return (CALL_FAILED);
	}

	/*
	 * Not much else to do except close the vccb
	 */
	sigpvc_close_vcc(vcp);

	return (CALL_CLEARED);
}


/*
 * Free SigPVC ATM Connection Resources
 * 
 * All service user requests to free the resources of a closed VCC connection
 * (via the atm_free_connection function), which is running over an interface 
 * attached to the SigPVC signalling manager, are handled here.
 *
 * Function will be called at splnet.
 * 
 * Arguments:
 *	vcp	pointer to connection's VCC control block
 *
 * Returns:
 *	0 	connection free was successful 
 *	errno	connection free failed - reason indicated
 *
 */
static int
sigpvc_free(vcp)
	struct vccb	*vcp;
{
	struct atm_pif	*pip = vcp->vc_pif;
	struct sigpvc	*pvp = (struct sigpvc *)pip->pif_siginst;

	/*
	 * Make sure VCC has been closed
	 */
	if ((vcp->vc_ustate != VCCU_CLOSED) || (vcp->vc_sstate != VCCS_FREE))
		return (EEXIST);

	/*
	 * Remove vccb from protocol queue
	 */
	DEQUEUE(vcp, struct vccb, vc_sigelem, pvp->pv_vccq);

	/*
	 * Free vccb storage
	 */
	vcp->vc_ustate = VCCU_NULL;
	vcp->vc_sstate = VCCS_NULL;
	atm_free((caddr_t)vcp);

	/*
	 * If we're detaching and this was the last vcc queued,
	 * get rid of the protocol instance
	 */
	if ((pvp->pv_state == SIGPVC_DETACH) && 
	    (Q_HEAD(pvp->pv_vccq, struct vccb) == NULL)) {
		struct sigmgr	*smp = pip->pif_sigmgr;

		pip->pif_sigmgr = NULL;
		pip->pif_siginst = NULL;
		UNLINK((struct siginst *)pvp, struct siginst, smp->sm_prinst, 
				si_next);
		KM_FREE(pvp, sizeof(struct sigpvc), M_DEVBUF);
	}

	return (0);
}


/*
 * Process Signalling Manager PF_ATM ioctls
 * 
 * Function will be called at splnet.
 *
 * Arguments:
 *	code	PF_ATM sub-operation code
 *	data	pointer to code specific parameter data area
 *	arg1	pointer to code specific argument
 *
 * Returns:
 *	0 	request procesed
 *	errno	error processing request - reason indicated
 *
 */
static int
sigpvc_ioctl(code, data, arg1)
	int		code;
	caddr_t		data;
	caddr_t		arg1;
{
	struct atmdelreq	*adp;
	struct atminfreq	*aip;
	struct air_vcc_rsp	avr;
	struct sigpvc	*pvp;
	struct vccb	*vcp;
	Atm_connection	*cop;
	caddr_t		cp;
	u_int	vpi, vci;
	int	i, space, err = 0;


	switch (code) {

	case AIOCS_DEL_PVC:
		/*
		 * Delete a PVC
		 */
		adp = (struct atmdelreq *)data;
		pvp = (struct sigpvc *)arg1;

		/*
		 * Find requested VCC
		 */
		vpi = adp->adr_pvc_vpi;
		vci = adp->adr_pvc_vci;
		for (vcp = Q_HEAD(pvp->pv_vccq, struct vccb); vcp; 
				vcp = Q_NEXT(vcp, struct vccb, vc_sigelem)) {
			if ((vcp->vc_vpi == vpi) && (vcp->vc_vci == vci))
				break;
		}
		if (vcp == NULL)
			return (ENOENT);

		/*
		 * Schedule VCC termination
		 */
		err = atm_cm_abort(vcp->vc_connvc, &sigpvc_cause.v);
		break;

	case AIOCS_DEL_SVC:
		/*
		 * Delete a SVC
		 */
		err = ENOENT;
		break;

	case AIOCS_INF_VCC:
		/*
		 * Get VCC information
		 */
		aip = (struct atminfreq *)data;
		pvp = (struct sigpvc *)arg1;

		cp = aip->air_buf_addr;
		space = aip->air_buf_len;

		/*
		 * Get info for all VCCs on interface
		 */
		for (vcp = Q_HEAD(pvp->pv_vccq, struct vccb); vcp; 
				vcp = Q_NEXT(vcp, struct vccb, vc_sigelem)) {
			/*
			 * Make sure there's room in user buffer
			 */
			if (space < sizeof(avr)) {
				err = ENOSPC;
				break;
			}

			/*
			 * Fill in info to be returned
			 */
			(void) sprintf(avr.avp_intf, "%s%d", 
				pvp->pv_pif->pif_name, pvp->pv_pif->pif_unit);
			avr.avp_vpi = vcp->vc_vpi;
			avr.avp_vci = vcp->vc_vci;
			avr.avp_type = vcp->vc_type;
			avr.avp_sig_proto = ATM_SIG_PVC;
			avr.avp_aal = vcp->vc_connvc->cvc_attr.aal.type;
			cop = vcp->vc_connvc->cvc_conn;
			if  (cop)
				avr.avp_encaps = cop->co_mpx;
			else
				avr.avp_encaps = 0;
			KM_ZERO(avr.avp_owners, sizeof(avr.avp_owners));
			for (i = 0; cop && i < sizeof(avr.avp_owners);
					cop = cop->co_next,
					i += T_ATM_APP_NAME_LEN+1) {
				strncpy(&avr.avp_owners[i],
					cop->co_endpt->ep_getname(cop->co_toku),
					T_ATM_APP_NAME_LEN);
			}
			avr.avp_state = vcp->vc_sstate;
			avr.avp_daddr.address_format = T_ATM_ABSENT;
			avr.avp_dsubaddr.address_format = T_ATM_ABSENT;
			avr.avp_ipdus = vcp->vc_ipdus;
			avr.avp_opdus = vcp->vc_opdus;
			avr.avp_ibytes = vcp->vc_ibytes;
			avr.avp_obytes = vcp->vc_obytes;
			avr.avp_ierrors = vcp->vc_ierrors;
			avr.avp_oerrors = vcp->vc_oerrors;
			avr.avp_tstamp = vcp->vc_tstamp;

			/*
			 * Copy data to user buffer and update buffer info
			 */
			if (err = copyout((caddr_t)&avr, cp, sizeof(avr)))
				break;
			cp += sizeof(avr);
			space -= sizeof(avr);
		}

		/*
		 * Update buffer pointer/count
		 */
		aip->air_buf_addr = cp;
		aip->air_buf_len = space;
		break;

	case AIOCS_INF_ARP:
		/*
		 * Get ARP table information
		 */
		/* We don't maintain any ARP information */
		break;

	default:
		err = EOPNOTSUPP;
	}

	return (err);
}


#ifdef ATM_SIGPVC_MODULE
/*
 *******************************************************************
 *
 * Loadable Module Support
 *
 *******************************************************************
 */
static int	sigpvc_doload __P((void));
static int	sigpvc_dounload __P((void));

/*
 * Generic module load processing
 * 
 * This function is called by an OS-specific function when this
 * module is being loaded.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	load was successful 
 *	errno	load failed - reason indicated
 *
 */
static int
sigpvc_doload()
{
	int	err = 0;

	/*
	 * Start us up
	 */
	err = sigpvc_start();
	if (err)
		/* Problems, clean up */
		(void)sigpvc_stop();

	return (err);
}


/*
 * Generic module unload processing
 * 
 * This function is called by an OS-specific function when this
 * module is being unloaded.
 *
 * Arguments:
 *	none
 *
 * Returns:
 *	0 	unload was successful 
 *	errno	unload failed - reason indicated
 *
 */
static int
sigpvc_dounload()
{
	int	err = 0;

	/*
	 * OK, try to clean up our mess
	 */
	err = sigpvc_stop();

	return (err);
}


#ifdef sun
/*
 * Loadable driver description
 */
struct vdldrv sigpvc_drv = {
	VDMAGIC_PSEUDO,	/* Pseudo Driver */
	"sigpvc_mod",	/* name */
	NULL,		/* dev_ops */
	NULL,		/* bdevsw */
	NULL,		/* cdevsw */
	0,		/* blockmajor */
	0		/* charmajor */
};


/*
 * Loadable module support entry point
 * 
 * This is the routine called by the vd driver for all loadable module
 * functions for this pseudo driver.  This routine name must be specified
 * on the modload(1) command.  This routine will be called whenever the
 * modload(1), modunload(1) or modstat(1) commands are issued for this
 * module.
 *
 * Arguments:
 *	cmd	vd command code
 *	vdp	pointer to vd driver's structure
 *	vdi	pointer to command-specific vdioctl_* structure
 *	vds	pointer to status structure (VDSTAT only)
 *
 * Returns:
 *	0 	command was successful 
 *	errno	command failed - reason indicated
 *
 */
int
sigpvc_mod(cmd, vdp, vdi, vds)
	int		cmd;
	struct vddrv	*vdp;
	caddr_t		vdi;
	struct vdstat	*vds;
{
	int	err = 0;

	switch (cmd) {

	case VDLOAD:
		/*
		 * Module Load
		 *
		 * We dont support any user configuration
		 */
		err = sigpvc_doload();
		if (err == 0)
			/* Let vd driver know about us */
			vdp->vdd_vdtab = (struct vdlinkage *)&sigpvc_drv;
		break;

	case VDUNLOAD:
		/*
		 * Module Unload
		 */
		err = sigpvc_dounload();
		break;

	case VDSTAT:
		/*
		 * Module Status
		 */

		/* Not much to say at the moment */

		break;

	default:
		log(LOG_ERR, "sigpvc_mod: Unknown vd command 0x%x\n", cmd);
		err = EINVAL;
	}

	return (err);
}
#endif	/* sun */

#ifdef __FreeBSD__

#include <sys/exec.h>
#include <sys/sysent.h>
#include <sys/lkm.h>

/*
 * Loadable miscellaneous module description
 */
MOD_MISC(sigpvc);


/*
 * Loadable module support "load" entry point
 * 
 * This is the routine called by the lkm driver whenever the
 * modload(1) command is issued for this module.
 *
 * Arguments:
 *	lkmtp	pointer to lkm drivers's structure
 *	cmd	lkm command code
 *
 * Returns:
 *	0 	command was successful 
 *	errno	command failed - reason indicated
 *
 */
static int
sigpvc_load(lkmtp, cmd)
	struct lkm_table	*lkmtp;
	int		cmd;
{
	return(sigpvc_doload());
}


/*
 * Loadable module support "unload" entry point
 * 
 * This is the routine called by the lkm driver whenever the
 * modunload(1) command is issued for this module.
 *
 * Arguments:
 *	lkmtp	pointer to lkm drivers's structure
 *	cmd	lkm command code
 *
 * Returns:
 *	0 	command was successful 
 *	errno	command failed - reason indicated
 *
 */
static int
sigpvc_unload(lkmtp, cmd)
	struct lkm_table	*lkmtp;
	int		cmd;
{
	return(sigpvc_dounload());
}


/*
 * Loadable module support entry point
 * 
 * This is the routine called by the lkm driver for all loadable module
 * functions for this driver.  This routine name must be specified
 * on the modload(1) command.  This routine will be called whenever the
 * modload(1), modunload(1) or modstat(1) commands are issued for this
 * module.
 *
 * Arguments:
 *	lkmtp	pointer to lkm drivers's structure
 *	cmd	lkm command code
 *	ver	lkm version
 *
 * Returns:
 *	0 	command was successful 
 *	errno	command failed - reason indicated
 *
 */
int
sigpvc_mod(lkmtp, cmd, ver)
	struct lkm_table	*lkmtp;
	int		cmd;
	int		ver;
{
	MOD_DISPATCH(sigpvc, lkmtp, cmd, ver,
		sigpvc_load, sigpvc_unload, lkm_nullcmd);
}
#endif	/* __FreeBSD__ */

#else	/* !ATM_SIGPVC_MODULE */

/*
 *******************************************************************
 *
 * Kernel Compiled Module Support
 *
 *******************************************************************
 */
static void	sigpvc_doload __P((void *));

SYSINIT(atmsigpvc, SI_SUB_PROTO_END, SI_ORDER_ANY, sigpvc_doload, NULL)

/*
 * Kernel initialization
 * 
 * Arguments:
 *	arg	Not used
 *
 * Returns:
 *	none
 *
 */
static void
sigpvc_doload(void *arg)
{
	int	err = 0;

	/*
	 * Start us up
	 */
	err = sigpvc_start();
	if (err) {
		/* Problems, clean up */
		(void)sigpvc_stop();

		log(LOG_ERR, "ATM SIGPVC unable to initialize (%d)!!\n", err);
	}
	return;
}
#endif	/* ATM_SIGPVC_MODULE */

