/*
 * Copyright (c) 2012-2016 VMware, Inc.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of EITHER the GNU General Public License
 * version 2 as published by the Free Software Foundation or the BSD
 * 2-Clause License. This program is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License version 2 for more details at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program available in the file COPYING in the main
 * directory of this source tree.
 *
 * The BSD 2-Clause License
 *
 *     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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "pvrdma.h"

static void pvrdma_free_context(struct ibv_context *ibctx);

/*
 * VMware PVRDMA vendor id and PCI device id.
 */
#define PCI_VENDOR_ID_VMWARE		0x15AD
#define PCI_DEVICE_ID_VMWARE_PVRDMA	0x0820

static const struct verbs_context_ops pvrdma_ctx_ops = {
	.free_context = pvrdma_free_context,
	.query_device_ex = pvrdma_query_device,
	.query_port = pvrdma_query_port,
	.alloc_pd = pvrdma_alloc_pd,
	.dealloc_pd = pvrdma_free_pd,

	.reg_mr = pvrdma_reg_mr,
	.dereg_mr = pvrdma_dereg_mr,
	.create_cq = pvrdma_create_cq,
	.poll_cq = pvrdma_poll_cq,
	.req_notify_cq = pvrdma_req_notify_cq,
	.destroy_cq = pvrdma_destroy_cq,

	.create_qp = pvrdma_create_qp,
	.query_qp = pvrdma_query_qp,
	.modify_qp = pvrdma_modify_qp,
	.destroy_qp = pvrdma_destroy_qp,

	.create_srq = pvrdma_create_srq,
	.modify_srq = pvrdma_modify_srq,
	.query_srq = pvrdma_query_srq,
	.destroy_srq = pvrdma_destroy_srq,
	.post_srq_recv = pvrdma_post_srq_recv,

	.post_send = pvrdma_post_send,
	.post_recv = pvrdma_post_recv,
	.create_ah = pvrdma_create_ah,
	.destroy_ah = pvrdma_destroy_ah,
};

int pvrdma_alloc_buf(struct pvrdma_buf *buf, size_t size, int page_size)
{
	int ret;

	buf->length = align(size, page_size);
	buf->buf = mmap(NULL, buf->length, PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (buf->buf == MAP_FAILED)
		return errno;

	ret = ibv_dontfork_range(buf->buf, size);
	if (ret)
		munmap(buf->buf, buf->length);

	return ret;
}

void pvrdma_free_buf(struct pvrdma_buf *buf)
{
	ibv_dofork_range(buf->buf, buf->length);
	munmap(buf->buf, buf->length);
}

static int pvrdma_init_context_shared(struct pvrdma_context *context,
				      struct ibv_device *ibdev,
				      int cmd_fd)
{
	struct ibv_get_context cmd;
	struct user_pvrdma_alloc_ucontext_resp resp;

	context->ibv_ctx.context.cmd_fd = cmd_fd;
	if (ibv_cmd_get_context(&context->ibv_ctx, &cmd, sizeof(cmd),
				&resp.ibv_resp, sizeof(resp)))
		return errno;

	context->qp_tbl = calloc(resp.qp_tab_size & 0xFFFF,
				 sizeof(struct pvrdma_qp *));
	if (!context->qp_tbl)
		return -ENOMEM;

	context->uar = mmap(NULL, to_vdev(ibdev)->page_size, PROT_WRITE,
			    MAP_SHARED, cmd_fd, 0);
	if (context->uar == MAP_FAILED) {
		free(context->qp_tbl);
		return errno;
	}

	pthread_spin_init(&context->uar_lock, PTHREAD_PROCESS_PRIVATE);

	verbs_set_ops(&context->ibv_ctx, &pvrdma_ctx_ops);

	return 0;
}

static void pvrdma_free_context_shared(struct pvrdma_context *context,
				       struct pvrdma_device *dev)
{
	munmap(context->uar, dev->page_size);
	free(context->qp_tbl);
}

static struct verbs_context *pvrdma_alloc_context(struct ibv_device *ibdev,
						  int cmd_fd,
						  void *private_data)
{
	struct pvrdma_context *context;

	context = verbs_init_and_alloc_context(ibdev, cmd_fd, context, ibv_ctx,
					       RDMA_DRIVER_VMW_PVRDMA);
	if (!context)
		return NULL;

	if (pvrdma_init_context_shared(context, ibdev, cmd_fd)) {
		verbs_uninit_context(&context->ibv_ctx);
		free(context);
		return NULL;
	}

	return &context->ibv_ctx;
}

static void pvrdma_free_context(struct ibv_context *ibctx)
{
	struct pvrdma_context *context = to_vctx(ibctx);

	pvrdma_free_context_shared(context, to_vdev(ibctx->device));
	verbs_uninit_context(&context->ibv_ctx);
	free(context);
}

static void pvrdma_uninit_device(struct verbs_device *verbs_device)
{
	struct pvrdma_device *dev = to_vdev(&verbs_device->device);

	free(dev);
}

static struct verbs_device *
pvrdma_device_alloc(struct verbs_sysfs_dev *sysfs_dev)
{
	struct pvrdma_device *dev;

	dev = calloc(1, sizeof(*dev));
	if (!dev)
		return NULL;

	dev->abi_version = sysfs_dev->abi_ver;
	dev->page_size   = sysconf(_SC_PAGESIZE);

	return &dev->ibv_dev;
}

static const struct verbs_match_ent hca_table[] = {
	VERBS_DRIVER_ID(RDMA_DRIVER_VMW_PVRDMA),
	VERBS_PCI_MATCH(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_PVRDMA,
			NULL),
	{}
};

static const struct verbs_device_ops pvrdma_dev_ops = {
	.name = "pvrdma",
	.match_min_abi_version = PVRDMA_UVERBS_ABI_VERSION,
	.match_max_abi_version = PVRDMA_UVERBS_ABI_VERSION,
	.match_table = hca_table,
	.alloc_device = pvrdma_device_alloc,
	.uninit_device = pvrdma_uninit_device,
	.alloc_context = pvrdma_alloc_context,
};
PROVIDER_DRIVER(vmw_pvrdma, pvrdma_dev_ops);
