/*
 * Copyright (c) 2002-2003, Network Appliance, Inc. All rights reserved.
 *
 * This Software is licensed under one of the following licenses:
 *
 * 1) under the terms of the "Common Public License 1.0" a copy of which is
 *    available from the Open Source Initiative, see
 *    http://www.opensource.org/licenses/cpl.php.
 *
 * 2) under the terms of the "The BSD License" a copy of which is
 *    available from the Open Source Initiative, see
 *    http://www.opensource.org/licenses/bsd-license.php.
 *
 * 3) under the terms of the "GNU General Public License (GPL) Version 2" a
 *    copy of which is available from the Open Source Initiative, see
 *    http://www.opensource.org/licenses/gpl-license.php.
 *
 * Licensee has the right to choose one of the above licenses.
 *
 * Redistributions of source code must retain the above copyright
 * notice and one of the license notices.
 *
 * Redistributions in binary form must reproduce both the above copyright
 * notice, one of the license notices in the documentation
 * and/or other materials provided with the distribution.
 */

/**********************************************************************
 *
 * MODULE: dapl_evd_connection_callback.c
 *
 * PURPOSE: implements connection callbacks
 *
 * Description: Accepts asynchronous callbacks from the Communications Manager
 *              for EVDs that have been specified as the connection_evd.
 *
 * $Id:$
 **********************************************************************/

#include "dapl.h"
#include "dapl_evd_util.h"
#include "dapl_ep_util.h"
#include "dapl_timer_util.h"

/*
 * dapl_evd_connection_callback
 *
 * Connection callback function for ACTIVE connection requests; callbacks
 * generated by the Connection Manager in response to issuing a
 * connect call.
 *
 * Input:
 * 	ib_cm_handle,
 * 	ib_cm_event
 *	private_data_ptr
 * 	context (evd)
 *	cr_pp
 *
 * Output:
 * 	None
 *
 */

void
dapl_evd_connection_callback(IN dp_ib_cm_handle_t ib_cm_handle,
			     IN const ib_cm_events_t ib_cm_event,
			     IN const void *private_data_ptr,
			     IN const void *context)
{
	DAPL_EP *ep_ptr;
	DAPL_EVD *evd_ptr;
	DAPL_PRIVATE *prd_ptr;
	DAT_EVENT_NUMBER dat_event_num;
	DAT_RETURN dat_status;
	int private_data_size;

	dapl_dbg_log(DAPL_DBG_TYPE_CM | DAPL_DBG_TYPE_CALLBACK,
		     "--> dapl_evd_connection_callback: ctxt: %p event: %x cm_handle %p\n",
		     context, ib_cm_event, (void *)ib_cm_handle);

	/*
	 * Determine the type of handle passed back to us in the context
	 * and sort out key parameters.
	 */
	if (context == NULL
	    || ((DAPL_HEADER *) context)->magic != DAPL_MAGIC_EP) {
		return;
	}

	/*
	 * Active side of the connection, context is an EP and
	 * PSP is irrelevant.
	 */
	ep_ptr = (DAPL_EP *) context;
	evd_ptr = (DAPL_EVD *) ep_ptr->param.connect_evd_handle;
	DAPL_CNTR(evd_ptr, DCNT_EVD_CONN_CALLBACK);

	prd_ptr = (DAPL_PRIVATE *) private_data_ptr;
	private_data_size = 0;
	/*
	 * All operations effect the EP, so lock it once and unlock
	 * when necessary
	 */
	dapl_os_lock(&ep_ptr->header.lock);

	/*
	 * If a connection timer has been set up on this EP, cancel it now
	 */
	if (ep_ptr->cxn_timer != NULL) {
		dapls_timer_cancel(ep_ptr->cxn_timer);
		dapl_os_free(ep_ptr->cxn_timer, sizeof(DAPL_OS_TIMER));
		ep_ptr->cxn_timer = NULL;
	}

	/* Obtain the event number from the provider layer */
	dat_event_num = dapls_ib_get_dat_event(ib_cm_event, DAT_FALSE);

	switch (dat_event_num) {
	case DAT_CONNECTION_EVENT_ESTABLISHED:
		{
			/* If we don't have an EP at this point we are very screwed
			 * up
			 */
			if (ep_ptr->param.ep_state !=
			    DAT_EP_STATE_ACTIVE_CONNECTION_PENDING) {
				/* If someone pulled the plug on the connection, just
				 * exit
				 */
				dapl_os_unlock(&ep_ptr->header.lock);
				dat_status = DAT_SUCCESS;
				break;
			}
			ep_ptr->param.ep_state = DAT_EP_STATE_CONNECTED;
			ep_ptr->cm_handle = ib_cm_handle;
			if (prd_ptr == NULL) {
				private_data_size = 0;
			} else {
				private_data_size =
				    dapls_ib_private_data_size(prd_ptr,
							       DAPL_PDATA_CONN_REP,
							       ep_ptr->header.
							       owner_ia->
							       hca_ptr);
			}

			if (private_data_size > 0) {
				/* copy in the private data */
				dapl_os_memcpy(ep_ptr->private.private_data,
					       prd_ptr->private_data,
					       DAPL_MIN(private_data_size,
							DAPL_MAX_PRIVATE_DATA_SIZE));
			}
			dapl_os_unlock(&ep_ptr->header.lock);

			break;
		}
	case DAT_CONNECTION_EVENT_PEER_REJECTED:
		{
			/* peer reject may include private data */
			if (prd_ptr != NULL)
				private_data_size =
				    dapls_ib_private_data_size(prd_ptr,
							       DAPL_PDATA_CONN_REJ,
							       ep_ptr->header.
							       owner_ia->
							       hca_ptr);

			if (private_data_size > 0)
				dapl_os_memcpy(ep_ptr->private.private_data,
					       prd_ptr->private_data,
					       DAPL_MIN(private_data_size,
							DAPL_MAX_PRIVATE_DATA_SIZE));

			dapl_dbg_log(DAPL_DBG_TYPE_CM | DAPL_DBG_TYPE_CALLBACK,
				     "dapl_evd_connection_callback PEER REJ pd=%p sz=%d\n",
				     prd_ptr, private_data_size);
		}
	case DAT_CONNECTION_EVENT_DISCONNECTED:
	case DAT_CONNECTION_EVENT_UNREACHABLE:
	case DAT_CONNECTION_EVENT_NON_PEER_REJECTED:
		{
			ep_ptr->param.ep_state = DAT_EP_STATE_DISCONNECTED;
			dapls_ib_disconnect_clean(ep_ptr, DAT_TRUE,
						  ib_cm_event);
			dapl_os_unlock(&ep_ptr->header.lock);

			break;
		}
	case DAT_CONNECTION_EVENT_BROKEN:
	case DAT_CONNECTION_EVENT_TIMED_OUT:
		{
			ep_ptr->param.ep_state = DAT_EP_STATE_DISCONNECTED;
			dapls_ib_disconnect_clean(ep_ptr, DAT_FALSE,
						  ib_cm_event);
			dapl_os_unlock(&ep_ptr->header.lock);

			break;
		}
	case DAT_CONNECTION_REQUEST_EVENT:
	default:
		{
			dapl_os_unlock(&ep_ptr->header.lock);
			evd_ptr = NULL;

			dapl_os_assert(0);	/* shouldn't happen */
			break;
		}
	}

	/*
	 * Post the event
	 * If the EP has been freed, the evd_ptr will be NULL
	 */
	if (evd_ptr != NULL) {
		dat_status = dapls_evd_post_connection_event(evd_ptr, dat_event_num, (DAT_HANDLE) ep_ptr, private_data_size,	/* CONNECTED or REJECT */
							     ep_ptr->private.
							     private_data);

		if (dat_status != DAT_SUCCESS &&
		    dat_event_num == DAT_CONNECTION_EVENT_ESTABLISHED) {
			/* We can't tell the user we are connected, something
			 * is wrong locally. Just kill the connection and
			 * reset the state to DISCONNECTED as we don't
			 * expect a callback on an ABRUPT disconnect.
			 */
			dapls_ib_disconnect(ep_ptr, DAT_CLOSE_ABRUPT_FLAG);
			dapl_os_lock(&ep_ptr->header.lock);
			ep_ptr->param.ep_state = DAT_EP_STATE_DISCONNECTED;
			dapl_os_unlock(&ep_ptr->header.lock);
		}
	}

	dapl_dbg_log(DAPL_DBG_TYPE_CM | DAPL_DBG_TYPE_CALLBACK,
		     "dapl_evd_connection_callback () returns\n");

}

/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 8
 * End:
 */
