/*
* Copyright (c) 2015-2016 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.
*/

#ifdef _WIN32

#include "netdir.h"

#include "netdir_ov.h"
#include "netdir_err.h"
#include "netdir_iface.h"

#include "ofi.h"
#include "ofi_util.h"

static int ofi_nd_mr_close(struct fid *fid);

static struct fi_ops ofi_nd_fi_ops = {
	.size = sizeof(ofi_nd_fi_ops),
	.close = ofi_nd_mr_close,
	.bind = fi_no_bind,
	.control = fi_no_control,
	.ops_open = fi_no_ops_open,
};

static struct fid ofi_nd_fid = {
	.fclass = FI_CLASS_MR,
	.context = NULL,
	.ops = &ofi_nd_fi_ops
};

typedef struct ofi_nd_mr_ov {
	nd_event_base	base;
	struct nd_eq	*eq;
	fid_t		fid;
	void		*context;
	LONG		cnt;
} ofi_nd_mr_ov;

static void ofi_nd_mr_ov_free(struct nd_event_base* base);
static void ofi_nd_mr_ov_event(struct nd_event_base* base, DWORD bytes);
static void ofi_nd_mr_ov_err(struct nd_event_base* base, DWORD bytes, DWORD err);

OFI_ND_NB_BUF_TYPED(nd_mr, struct nd_mr);
OFI_ND_NB_BUF_IMP(nd_mr);

OFI_ND_NB_BUF_TYPED(mr_ov, ofi_nd_mr_ov);
OFI_ND_NB_BUF_IMP(mr_ov);

static inline void ofi_nd_mr_fini_handler()
{
	ND_REGISTER_FINI(ND_BUF_FINIPTR(nd_mr));
	ND_REGISTER_FINI(ND_BUF_FINIPTR(mr_ov));
}

int ofi_nd_mr_reg(struct fid *fid, const void *buf, size_t len,
		  uint64_t access, uint64_t offset, uint64_t requested_key,
		  uint64_t flags, struct fid_mr **pmr, void *context)
{
	OFI_UNUSED(requested_key);

	assert(fid->fclass == FI_CLASS_DOMAIN);
	assert(!offset);

	HRESULT hr;

	ofi_nd_mr_fini_handler();

	if (fid->fclass != FI_CLASS_DOMAIN)
		return -FI_EINVAL;
	if (offset)
		return -FI_EINVAL;
	if (flags)
		return -FI_EINVAL;

	struct nd_domain *domain = container_of(fid, struct nd_domain, fid.fid);

	assert(domain->adapter);
	assert(domain->adapter_file);

	struct nd_mr *mr = ofi_nd_buf_alloc_nd_mr();
	if (!mr)
		return -FI_ENOMEM;

	struct nd_mr def = {
		.fid = {
			.fid = ofi_nd_fid
		}
	};

	*mr = def;

	hr = domain->adapter->lpVtbl->CreateMemoryRegion(
		domain->adapter, &IID_IND2MemoryRegion, domain->adapter_file,
		(void**)&mr->mr);

	if (FAILED(hr))
		goto fn_fail;

	ULONG ind2flag = 0;

	if (access & FI_REMOTE_READ)
		ind2flag |= ND_MR_FLAG_ALLOW_REMOTE_READ;
	if (access & FI_REMOTE_WRITE)
		ind2flag |= ND_MR_FLAG_ALLOW_REMOTE_WRITE;
	if ((access & FI_WRITE) || (access & FI_RECV))
		ind2flag |= ND_MR_FLAG_ALLOW_LOCAL_WRITE;

	/* there is bug in mlx4 module: it always generates
	   IO completion (even for cases when hEvent value
	   of OVERLAPPED structure is initialized). To
	   workaround this we have to use dynamically allocated
	   ov */
	ofi_nd_mr_ov *ov = ND_BUF_ALLOC(mr_ov);
	if (!ov) {
		hr = ND_NO_MEMORY;
		goto fn_fail;
	}
	memset(ov, 0, sizeof(*ov));

	ofi_nd_mr_ov ovdef = {
		.base = {
			.free = ofi_nd_mr_ov_free,
			.event_cb = ofi_nd_mr_ov_event,
			.err_cb = ofi_nd_mr_ov_err
		},
		.eq = domain->eq,
		.fid = &mr->fid.fid,
		.context = context
	};

	*ov = ovdef;
	if (!(domain->eq_flags & FI_MR_COMPLETE)) {
		ov->cnt = 2;
		ov->base.ov.hEvent = CreateEvent(0, TRUE, FALSE, NULL);
	}

	hr = mr->mr->lpVtbl->Register(mr->mr, buf, len, ind2flag, &ov->base.ov);
	if (FAILED(hr)) {
		ofi_nd_mr_ov_free(&ov->base);
		goto fn_fail;
	}

	if (!(domain->eq_flags & FI_MR_COMPLETE)) {
		/* sync memory registration */
		hr = mr->mr->lpVtbl->GetOverlappedResult(mr->mr, &ov->base.ov, TRUE);
		if (!InterlockedDecrement(&ov->cnt))
			ofi_nd_mr_ov_free(&ov->base);
		if (FAILED(hr))
			goto fn_fail;
		mr->fid.key = mr->mr->lpVtbl->GetRemoteToken(mr->mr);
		mr->fid.mem_desc = (void *)(uintptr_t)mr->mr->lpVtbl->GetLocalToken(mr->mr);
	}
	else {
		/* async memory registration */
		hr = mr->mr->lpVtbl->Register(
			mr->mr, buf, len, ind2flag, &ov->base.ov);
		if (FAILED(hr)) {
			ofi_nd_mr_ov_free(&ov->base);
			goto fn_fail;
		}
	}

	*pmr = &mr->fid;

	return FI_SUCCESS;

fn_fail:
	ofi_nd_mr_close(&mr->fid.fid);
	return H2F(hr);
}

int ofi_nd_mr_regv(struct fid *fid, const struct iovec *iov,
		   size_t count, uint64_t access,
		   uint64_t offset, uint64_t requested_key,
		   uint64_t flags, struct fid_mr **mr, void *context)
{
	OFI_UNUSED(fid);
	OFI_UNUSED(iov);
	OFI_UNUSED(count);
	OFI_UNUSED(access);
	OFI_UNUSED(offset);
	OFI_UNUSED(requested_key);
	OFI_UNUSED(flags);
	OFI_UNUSED(fid);
	OFI_UNUSED(mr);
	OFI_UNUSED(context);

	/* This functionality wasn't implemented due to impossibility
	 * to do it by means of ND services. To avoid problems in future,
	 * just to not implement it until no support from ND */

	ofi_nd_mr_fini_handler();
	assert(0);
	return FI_SUCCESS;
}

int ofi_nd_mr_regattr(struct fid *fid, const struct fi_mr_attr *attr,
		      uint64_t flags, struct fid_mr **mr)
{
	OFI_UNUSED(fid);
	OFI_UNUSED(attr);
	OFI_UNUSED(flags);
	OFI_UNUSED(mr);

	ofi_nd_mr_fini_handler();
	assert(0);
	return FI_SUCCESS;
}

static int ofi_nd_mr_close(struct fid *fid)
{
	ND_LOG_DEBUG(FI_LOG_MR, "closing mr\n");
	assert(fid->fclass == FI_CLASS_MR);
	if (fid->fclass != FI_CLASS_MR)
		return -FI_EINVAL;

	struct nd_mr *mr = container_of(fid, struct nd_mr, fid.fid);

	if (mr->mr)
		mr->mr->lpVtbl->Release(mr->mr);
	if (mr->wnd)
		mr->wnd->lpVtbl->Release(mr->wnd);

	ofi_nd_buf_free_nd_mr(mr);

	return FI_SUCCESS;
}

static void ofi_nd_mr_ov_free(struct nd_event_base* base)
{
	ofi_nd_mr_ov *ov = container_of(base, ofi_nd_mr_ov, base);
	if (ov->base.ov.hEvent && ov->base.ov.hEvent != INVALID_HANDLE_VALUE)
		CloseHandle(ov->base.ov.hEvent);

	ofi_nd_buf_free_mr_ov(ov);
}

static void ofi_nd_mr_ov_event(struct nd_event_base* base, DWORD bytes)
{
	OFI_UNUSED(bytes);

	HRESULT hr;

	ofi_nd_mr_ov *ov = container_of(base, ofi_nd_mr_ov, base);

	if (ov->cnt) { /* this is sync mr reg operation */
		if (!InterlockedDecrement(&ov->cnt))
			ofi_nd_mr_ov_free(&ov->base);
		return;
	}

	assert(ov->eq);
	assert(ov->fid);
	assert(ov->fid->fclass == FI_CLASS_MR);

	struct nd_mr *mr = container_of(ov->fid, struct nd_mr, fid.fid);
	assert(mr->mr);
	mr->fid.key = mr->mr->lpVtbl->GetRemoteToken(mr->mr);
	mr->fid.mem_desc = (void *)(uintptr_t)mr->mr->lpVtbl->GetLocalToken(mr->mr);

	struct fi_eq_entry entry = {.fid = ov->fid, .context = ov->context};
	ofi_nd_mr_ov_free(base);

	struct nd_eq_event *err;
	struct nd_eq_event *ev = ofi_nd_buf_alloc_nd_eq_event();
	if (!ev) {
		hr = ND_NO_MEMORY;
		goto fn_fail;
	}
	memset(ev, 0, sizeof(*ev));
	ev->eq_event = FI_MR_COMPLETE;
	ev->operation = entry;
	ofi_nd_eq_push(ov->eq, ev);
	return;

fn_fail:
	err = ofi_nd_buf_alloc_nd_eq_event();
	if (!err) {
		ND_LOG_WARN(FI_LOG_EP_CTRL,
			   "failed to allocate error event\n");
		return;
	}
	memset(err, 0, sizeof(*err));
	err->error.err = -H2F(hr);
	err->error.prov_errno = (int)hr;
	err->error.fid = ov->fid;
	ofi_nd_eq_push_err(ov->eq, err);
}

static void ofi_nd_mr_ov_err(struct nd_event_base* base, DWORD bytes,
			     DWORD error)
{
	OFI_UNUSED(bytes);

	ofi_nd_mr_ov *ov = container_of(base, ofi_nd_mr_ov, base);

	assert(ov->eq);
	assert(ov->fid);
	assert(ov->fid->fclass == FI_CLASS_MR);

	struct nd_mr *mr = container_of(ov->fid, struct nd_mr, fid.fid);
	assert(mr->mr);

	struct fi_eq_err_entry entry = {
		.fid = ov->fid,
		.context = ov->context,
		.err = H2F(error),
		.prov_errno = error
	};

	ofi_nd_mr_ov_free(base);

	struct nd_eq_event *err = ofi_nd_buf_alloc_nd_eq_event();
	if (!err) {
		ND_LOG_WARN(FI_LOG_EP_CTRL,
			   "failed to allocate error event\n");
		return;
	}
	memset(err, 0, sizeof(*err));
	err->error = entry;
	ofi_nd_eq_push_err(ov->eq, err);
}


#endif /* _WIN32 */

