/*
 * Copyright (c) 2017 Intel Corporation, 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 "fi_verbs.h"

static inline int vrb_dgram_av_is_addr_valid(struct vrb_dgram_av *av,
						const void *addr)
{
	const struct ofi_ib_ud_ep_name *check_name = addr;
	return (check_name->lid > 0);
}

static inline int
vrb_dgram_verify_av_flags(struct util_av *av, uint64_t flags)
{
	if ((av->flags & FI_EVENT) && !av->eq) {
		VERBS_WARN(FI_LOG_AV, "No EQ bound to AV\n");
		return -FI_ENOEQ;
	}

	if (flags & ~(FI_MORE)) {
		VERBS_WARN(FI_LOG_AV, "Unsupported flags\n");
		return -FI_ENOEQ;
	}

	return FI_SUCCESS;
}

static int
vrb_dgram_av_insert_addr(struct vrb_dgram_av *av, const void *addr,
			    fi_addr_t *fi_addr, void *context)
{
	int ret;
	struct vrb_dgram_av_entry *av_entry;
	struct vrb_domain *domain =
		container_of(av->util_av.domain, struct vrb_domain, util_domain);

	struct ibv_ah_attr ah_attr = {
		.is_global = 0,
		.dlid = ((struct ofi_ib_ud_ep_name *)addr)->lid,
		.sl = ((struct ofi_ib_ud_ep_name *)addr)->sl,
		.src_path_bits = 0,
		.port_num = 1,
	};

	if (((struct ofi_ib_ud_ep_name *)addr)->gid.global.interface_id) {
		ah_attr.is_global = 1;
		ah_attr.grh.hop_limit = 64;
		ah_attr.grh.dgid = ((struct ofi_ib_ud_ep_name *)addr)->gid;
		ah_attr.grh.sgid_index = vrb_gl_data.gid_idx;
	} else if (OFI_UNLIKELY(!vrb_dgram_av_is_addr_valid(av, addr))) {
		ret = -FI_EADDRNOTAVAIL;
		VERBS_WARN(FI_LOG_AV, "Invalid address\n");
		goto fn1;
	}

	av_entry = calloc(1, sizeof(*av_entry));
	if (OFI_UNLIKELY(!av_entry)) {
		ret = -FI_ENOMEM;
		VERBS_WARN(FI_LOG_AV, "Unable to allocate memory for AV entry\n");
		goto fn1;
	}

	av_entry->ah = ibv_create_ah(domain->pd, &ah_attr);
	if (OFI_UNLIKELY(!av_entry->ah)) {
		ret = -errno;
		VERBS_WARN(FI_LOG_AV,
			   "Unable to create Address Handle, errno - %d\n", errno);
		goto fn2;
	}
	av_entry->addr = *(struct ofi_ib_ud_ep_name *)addr;
	dlist_insert_tail(&av_entry->list_entry, &av->av_entry_list);

	if (fi_addr)
		*fi_addr = (fi_addr_t)(uintptr_t)av_entry;
	return 0;
fn2:
	free(av_entry);
fn1:
	if (fi_addr)
		*fi_addr = FI_ADDR_NOTAVAIL;
	return ret;
}

static int vrb_dgram_av_insert(struct fid_av *av_fid, const void *addr,
				  size_t count, fi_addr_t *fi_addr,
				  uint64_t flags, void *context)
{
	int ret, success_cnt = 0;
	size_t i;
	struct vrb_dgram_av *av =
		 container_of(av_fid, struct vrb_dgram_av, util_av.av_fid);

	ret = vrb_dgram_verify_av_flags(&av->util_av, flags);
	if (ret)
		return ret;

	VERBS_DBG(FI_LOG_AV, "Inserting %"PRIu64" addresses\n", count);
	for (i = 0; i < count; i++) {
		ret = vrb_dgram_av_insert_addr(
				av, (struct ofi_ib_ud_ep_name *)addr + i,
				fi_addr ? &fi_addr[i] : NULL, context);
		if (!ret)
			success_cnt++;
	}

	VERBS_DBG(FI_LOG_AV,
		  "%d addresses were inserted successfully\n", success_cnt);
	return success_cnt;
}

static inline void
vrb_dgram_av_remove_addr(struct vrb_dgram_av_entry *av_entry)
{
	int ret = ibv_destroy_ah(av_entry->ah);
	if (ret)
		VERBS_WARN(FI_LOG_AV,
			   "AH Destroying failed with status - %d\n",
			   ret);
	dlist_remove(&av_entry->list_entry);
	free(av_entry);
}

static int vrb_dgram_av_remove(struct fid_av *av_fid, fi_addr_t *fi_addr,
				  size_t count, uint64_t flags)
{
	int i, ret;
	struct vrb_dgram_av *av =
		container_of(av_fid, struct vrb_dgram_av, util_av.av_fid);

	ret = vrb_dgram_verify_av_flags(&av->util_av, flags);
	if (ret)
		return ret;

	for (i = count - 1; i >= 0; i--) {
		struct vrb_dgram_av_entry *av_entry =
			(struct vrb_dgram_av_entry *) (uintptr_t) fi_addr[i];
		vrb_dgram_av_remove_addr(av_entry);
	}
	return FI_SUCCESS;
}

static inline
int vrb_dgram_av_lookup(struct fid_av *av_fid, fi_addr_t fi_addr,
			   void *addr, size_t *addrlen)
{
	struct vrb_dgram_av_entry *av_entry;

	av_entry = vrb_dgram_av_lookup_av_entry(fi_addr);
	if (!av_entry)
		return -FI_ENOENT;

	memcpy(addr, &av_entry->addr, MIN(*addrlen, sizeof(av_entry->addr)));
	*addrlen = sizeof(av_entry->addr);
	return FI_SUCCESS;
}

static inline const char *
vrb_dgram_av_straddr(struct fid_av *av, const void *addr, char *buf, size_t *len)
{
	return ofi_straddr(buf, len, FI_ADDR_IB_UD, addr);
}

static int vrb_dgram_av_close(struct fid *av_fid)
{
	struct vrb_dgram_av_entry *av_entry;
	struct vrb_dgram_av *av =
		container_of(av_fid, struct vrb_dgram_av, util_av.av_fid.fid);
	int ret = ofi_av_close_lightweight(&av->util_av);
	if (ret)
		return ret;

	while (!dlist_empty(&av->av_entry_list)) {
		av_entry = container_of(av->av_entry_list.next,
					struct vrb_dgram_av_entry,
					list_entry);
		vrb_dgram_av_remove_addr(av_entry);
	}

	free(av);
	return FI_SUCCESS;
}

static struct fi_ops vrb_dgram_fi_ops = {
	.size		= sizeof(vrb_dgram_fi_ops),
	.close		= vrb_dgram_av_close,
	.bind		= ofi_av_bind,
	.control	= fi_no_control,
	.ops_open	= fi_no_ops_open,
};

static struct fi_ops_av vrb_dgram_av_ops = {
	.size		= sizeof(vrb_dgram_av_ops),
	.insert		= vrb_dgram_av_insert,
	.insertsvc	= fi_no_av_insertsvc,
	.insertsym	= fi_no_av_insertsym,
	.remove		= vrb_dgram_av_remove,
	.lookup		= vrb_dgram_av_lookup,
	.straddr	= vrb_dgram_av_straddr,
};

int vrb_dgram_av_open(struct fid_domain *domain_fid, struct fi_av_attr *attr,
			 struct fid_av **av_fid, void *context)
{
	struct vrb_domain *domain =
		container_of(domain_fid, struct vrb_domain,
			     util_domain.domain_fid);
	struct vrb_dgram_av *av;
	int ret;

	av = calloc(1, sizeof(*av));
	if (!av)
		return -FI_ENOMEM;

	if (attr->type == FI_AV_UNSPEC)
		attr->type = FI_AV_MAP;

	ret = ofi_av_init_lightweight(&domain->util_domain, attr,
				      &av->util_av, context);
	if (ret)
		goto err_av_init;
	dlist_init(&av->av_entry_list);

	av->util_av.av_fid.fid.ops = &vrb_dgram_fi_ops;
	av->util_av.av_fid.ops = &vrb_dgram_av_ops;
	*av_fid = &av->util_av.av_fid;

	return FI_SUCCESS;
err_av_init:
	free(av);
	return ret;
}
