/*
 * Copyright (c) 2017 Cray Inc. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "gnix_util.h"
#include "gnix_smrn.h"

static struct gnix_smrn global_smrn;

int _gnix_smrn_init(void)
{
	int ret;

	ofi_spin_init(&global_smrn.lock);
	global_smrn.references = 0;
	dlist_init(&global_smrn.rq_head);

	ret = _gnix_notifier_init();

	return ret;
}

int _gnix_smrn_open(struct gnix_smrn **smrn)
{
	struct gnix_smrn *tmp = &global_smrn;
	int ret = FI_SUCCESS;

	ofi_spin_lock(&tmp->lock);
	if (tmp->references == 0)
		ret = _gnix_notifier_open(&tmp->notifier);

	if (!ret)
		tmp->references += 1;
	ofi_spin_unlock(&tmp->lock);

	if (!ret)
		*smrn = tmp;

	return ret;
}

int _gnix_smrn_close(struct gnix_smrn *smrn)
{
	int ret = FI_SUCCESS;

	ofi_spin_lock(&smrn->lock);
	if (smrn->references == 0)
		ret = -FI_EINVAL;

	if (smrn->references == 1)
		ret = _gnix_notifier_close(smrn->notifier);

	if (!ret)
		smrn->references -= 1;
	ofi_spin_unlock(&smrn->lock);

	return ret;
}

int _gnix_smrn_monitor(struct gnix_smrn *smrn,
	struct gnix_smrn_rq *rq,
	void *addr,
	uint64_t len,
	uint64_t cookie,
	struct gnix_smrn_context *context)
{
	int ret;

	if (!context || !rq || !smrn)
		return -FI_EINVAL;

	context->rq = rq;
	context->cookie = cookie;

	ret = _gnix_notifier_monitor(smrn->notifier, addr,
				len, (uint64_t) context);
	if (ret == FI_SUCCESS)
		GNIX_DEBUG(FI_LOG_FABRIC,
				"monitoring addr=%p len=%d cookie=%p "
				"context=%p rq=%p notifier=%p\n",
				addr, len, context->cookie,
				context, rq, smrn->notifier);
	return ret;
}

int _gnix_smrn_unmonitor(struct gnix_smrn *smrn,
	uint64_t cookie,
	struct gnix_smrn_context *context)
{
	if (!smrn)
		return -FI_EINVAL;

	if (cookie != context->cookie)
		return -FI_EINVAL;

	return _gnix_notifier_unmonitor(smrn->notifier, (uint64_t) context);
}

static void __gnix_smrn_read_events(struct gnix_smrn *smrn)
{
	int ret;
	struct gnix_smrn_context *context;
	struct gnix_smrn_rq *rq;
	int len = sizeof(uint64_t);

	do {
		ret = _gnix_notifier_get_event(smrn->notifier,
			(void *) &context, len);
		if (ret != len) {
			GNIX_DEBUG(FI_LOG_FABRIC,
				"no more events to be read\n");
			break;
		}

		GNIX_DEBUG(FI_LOG_FABRIC,
			"found event, context=%p rq=%p cookie=%lx\n",
			context, context->rq, context->cookie);

		rq = context->rq;
		ofi_spin_lock(&rq->lock);
		dlist_insert_tail(&context->entry, &rq->list);
		ofi_spin_unlock(&rq->lock);
	} while (ret == len);
}

int _gnix_smrn_get_event(struct gnix_smrn *smrn,
	struct gnix_smrn_rq *rq,
	struct gnix_smrn_context **context)
{
	int ret;

	if (!smrn || !context)
		return -FI_EINVAL;

	__gnix_smrn_read_events(smrn);

	ofi_spin_lock(&rq->lock);
	if (!dlist_empty(&rq->list)) {
		dlist_pop_front(&rq->list, struct gnix_smrn_context,
			*context, entry);
		ret = FI_SUCCESS;
	} else
		ret = -FI_EAGAIN;
	ofi_spin_unlock(&rq->lock);

	return ret;
}

