/*
 * Copyright (c) 2013-2018 Intel Corporation. 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.
 */

#ifndef _FI_PSM_VERSION_H_
#define _FI_PSM_VERSION_H_

#if HAVE_PSM2_SRC
#include "psm2/psm2.h"
#include "psm2/psm2_mq.h"
#include "psm2/psm2_am.h"
#ifdef VALGRIND_MAKE_MEM_DEFINED
#undef VALGRIND_MAKE_MEM_DEFINED
#endif
#define PSM_IS_TEST /* keep plain malloc/realloc/calloc/memalign/free/strdup */
#include "psm2/psm_mq_internal.h"
#else
#include <psm2.h>
#include <psm2_mq.h>
#include <psm2_am.h>
#endif

#define PSMX2_PROV_NAME		"psm2"
#define PSMX2_DOMAIN_NAME	"psm2"
#define PSMX2_FABRIC_NAME	"psm2"

#define PSMX2_DEFAULT_UUID	"00FF00FF-0000-0000-0000-00FF00FF00FF"
#define PROVIDER_INI		PSM2_INI

#if HAVE_PSM2_SRC

#ifndef PSMX2_USE_REQ_CONTEXT
#define PSMX2_USE_REQ_CONTEXT	1
#endif

#define PSMX2_STATUS_TYPE	struct psm2_mq_req
#define PSMX2_STATUS_DECL(s)	struct psm2_mq_req *s
#define PSMX2_STATUS_INIT(s)
#define PSMX2_STATUS_SAVE(s,t)	do { t = s; } while (0)
#define PSMX2_STATUS_ERROR(s)	((s)->error_code)
#define PSMX2_STATUS_TAG(s)	((s)->tag)
#define PSMX2_STATUS_RCVLEN(s)	((s)->recv_msglen)
#define PSMX2_STATUS_SNDLEN(s)	((s)->send_msglen)
#define PSMX2_STATUS_PEER(s)	((s)->peer)
#define PSMX2_STATUS_CONTEXT(s)	((s)->context)

#define PSMX2_POLL_COMPLETION(trx_ctxt, status, err) \
	do { \
		(err) = psm2_mq_ipeek_dequeue((trx_ctxt)->psm2_mq, &(status)); \
	} while (0)

#define PSMX2_FREE_COMPLETION(trx_ctxt, status) \
	psm2_mq_req_free((trx_ctxt)->psm2_mq, status)

#else /* !HAVE_PSM2_SRC */

#ifdef PSMX2_USE_REQ_CONTEXT
#undef PSMX2_USE_REQ_CONTEXT
#endif
#define PSMX2_USE_REQ_CONTEXT	0

#define PSMX2_STATUS_TYPE	psm2_mq_status2_t
#define PSMX2_STATUS_DECL(s)	psm2_mq_status2_t s##_priv, *s
#define PSMX2_STATUS_INIT(s)	do { s = &s##_priv; } while (0)
#define PSMX2_STATUS_SAVE(s,t)	do { *(t) = *(s); } while (0)
#define PSMX2_STATUS_ERROR(s)	((s)->error_code)
#define PSMX2_STATUS_TAG(s)	((s)->msg_tag)
#define PSMX2_STATUS_RCVLEN(s)	((s)->nbytes)
#define PSMX2_STATUS_SNDLEN(s)	((s)->msg_length)
#define PSMX2_STATUS_PEER(s)	((s)->msg_peer)
#define PSMX2_STATUS_CONTEXT(s)	((s)->context)

/*
 * psm2_mq_test2 is called immediately after psm2_mq_ipeek with a lock held to
 * prevent psm2_mq_ipeek from returning the same request multiple times under
 * different threads.
 */
#define PSMX2_POLL_COMPLETION(trx_ctxt, status, err) \
	do { \
		if (psmx2_trylock(&(trx_ctxt)->poll_lock, 2)) { \
			(err) = PSM2_MQ_NO_COMPLETIONS; \
		} else { \
			psm2_mq_req_t psm2_req; \
			(err) = psm2_mq_ipeek((trx_ctxt)->psm2_mq, &psm2_req, NULL); \
			if ((err) == PSM2_OK) \
				psm2_mq_test2(&psm2_req, (status)); \
			psmx2_unlock(&(trx_ctxt)->poll_lock, 2); \
		} \
	} while(0)

#define PSMX2_FREE_COMPLETION(trx_ctxt, status)

#endif /* HAVE_PSM2_SRC */

/*
 * Provide backward compatibility for older PSM2 libraries that lack the
 * psm2_am_register_handlers_2 function.
 */

#if !HAVE_PSM2_AM_REGISTER_HANDLERS_2

#define PSMX2_MAX_AM_HANDLERS 1000

typedef int (*psm2_am_handler_2_fn_t) (
			psm2_am_token_t token,
			psm2_amarg_t *args, int nargs,
			void *src, uint32_t len, void *hctx);

extern psm2_am_handler_fn_t psmx2_am_handlers[];
extern psm2_am_handler_2_fn_t psmx2_am_handlers_2[];
extern void *psmx2_am_handler_ctxts[];
extern int psmx2_am_handler_count;

static inline
psm2_error_t psm2_am_register_handlers_2(
			psm2_ep_t ep,
			const psm2_am_handler_2_fn_t * handlers,
			int num_handlers, void **hctx,
			int *handlers_idx)
{
	int i;
	int start = psmx2_am_handler_count;

	if (start + num_handlers > PSMX2_MAX_AM_HANDLERS)
		return PSM2_EP_NO_RESOURCES;

	psmx2_am_handler_count += num_handlers;

	for (i = 0; i < num_handlers; i++) {
		psmx2_am_handlers_2[start + i] = handlers[i];
		psmx2_am_handler_ctxts[start + i] = hctx[i];
	}

	return psm2_am_register_handlers(ep, psmx2_am_handlers + start,
					 num_handlers, handlers_idx);
}

#endif /* !HAVE_PSM2_AM_REGISTER_HANDLERS_2 */

/*
 * Use reserved space within psm2_mq_req for fi_context instead of
 * allocating from a internal queue.
 *
 * Only work when compiled with PSM2 source. Can be turned off by
 * passing "-DPSMX2_USE_REQ_CONTEXT=0" to the compiler.
 */

#if PSMX2_USE_REQ_CONTEXT

#define PSMX2_EP_DECL_OP_CONTEXT

#define PSMX2_EP_INIT_OP_CONTEXT(ep) \
	do { \
		FI_INFO(&psmx2_prov, FI_LOG_EP_CTRL, \
			"skip initialization of op context list.\n"); \
	} while (0)

#define PSMX2_EP_FINI_OP_CONTEXT(ep)

#define PSMX2_EP_GET_OP_CONTEXT(ep, ctx) \
	do { \
		(ctx) = NULL; \
	} while (0)

#define PSMX2_EP_PUT_OP_CONTEXT(ep, ctx)

#define PSMX2_REQ_GET_OP_CONTEXT(req, ctx) \
	do { \
		(ctx) = (req)->context = (req)->user_reserved; \
	} while (0)

#else /* !PSMX2_USE_REQ_CONTEXT */

struct psmx2_context {
        struct fi_context fi_context;
        struct slist_entry list_entry;
};

#define PSMX2_EP_DECL_OP_CONTEXT \
	struct slist	free_context_list; \
	fastlock_t	context_lock;

#define PSMX2_EP_INIT_OP_CONTEXT(ep) \
	do { \
		struct psmx2_context *item; \
		int i; \
		slist_init(&(ep)->free_context_list); \
		fastlock_init(&(ep)->context_lock); \
		for (i = 0; i < 64; i++) { \
			item = calloc(1, sizeof(*item)); \
			if (!item) { \
				FI_WARN(&psmx2_prov, FI_LOG_EP_CTRL, "out of memory.\n"); \
				break; \
			} \
			slist_insert_tail(&item->list_entry, &(ep)->free_context_list); \
		} \
	} while (0)

#define PSMX2_EP_FINI_OP_CONTEXT(ep) \
	do { \
		struct slist_entry *entry; \
		struct psmx2_context *item; \
		while (!slist_empty(&(ep)->free_context_list)) { \
			entry = slist_remove_head(&(ep)->free_context_list); \
			item = container_of(entry, struct psmx2_context, list_entry); \
			free(item); \
		} \
		fastlock_destroy(&(ep)->context_lock); \
	} while (0)

#define PSMX2_EP_GET_OP_CONTEXT(ep, ctx) \
	do { \
		struct psmx2_context *context; \
		psmx2_lock(&(ep)->context_lock, 2); \
		if (!slist_empty(&(ep)->free_context_list)) { \
			context = container_of(slist_remove_head(&(ep)->free_context_list), \
					       struct psmx2_context, list_entry); \
			psmx2_unlock(&(ep)->context_lock, 2); \
			(ctx) = &context->fi_context; \
			break; \
		} \
		psmx2_unlock(&(ep)->context_lock, 2); \
		context = malloc(sizeof(*context)); \
		if (!context) { \
			FI_WARN(&psmx2_prov, FI_LOG_EP_DATA, "out of memory.\n"); \
			return -FI_ENOMEM; \
		} \
		(ctx) = &context->fi_context; \
	} while (0)

#define PSMX2_EP_PUT_OP_CONTEXT(ep, ctx) \
	do { \
		struct psmx2_context *context; \
		context = container_of((ctx), struct psmx2_context, fi_context); \
		context->list_entry.next = NULL; \
		psmx2_lock(&(ep)->context_lock, 2); \
		slist_insert_tail(&context->list_entry, &(ep)->free_context_list); \
		psmx2_unlock(&(ep)->context_lock, 2); \
	} while (0)

#endif /* PSMX2_USE_REQ_CONTEXT */

#endif

