/*
 * Copyright (C) 2008-2013 Emulex.  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.
 *
 * 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 <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <pthread.h>

#include "ocrdma_main.h"
#include "ocrdma_abi.h"
#include <ccan/list.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static void ocrdma_free_context(struct ibv_context *ibctx);

#define PCI_VENDOR_ID_EMULEX		0x10DF
#define PCI_DEVICE_ID_EMULEX_GEN1	0xe220
#define PCI_DEVICE_ID_EMULEX_GEN2        0x720
#define PCI_DEVICE_ID_EMULEX_GEN2_VF     0x728

#define UCNA(v, d)                                                             \
	VERBS_PCI_MATCH(PCI_VENDOR_ID_##v, PCI_DEVICE_ID_EMULEX_##d, NULL)
static const struct verbs_match_ent ucna_table[] = {
	VERBS_DRIVER_ID(RDMA_DRIVER_OCRDMA),
	UCNA(EMULEX, GEN1),
	UCNA(EMULEX, GEN2),
	UCNA(EMULEX, GEN2_VF),
	{}
};

static const struct verbs_context_ops ocrdma_ctx_ops = {
	.query_device_ex = ocrdma_query_device,
	.query_port = ocrdma_query_port,
	.alloc_pd = ocrdma_alloc_pd,
	.dealloc_pd = ocrdma_free_pd,
	.reg_mr = ocrdma_reg_mr,
	.dereg_mr = ocrdma_dereg_mr,
	.create_cq = ocrdma_create_cq,
	.poll_cq = ocrdma_poll_cq,
	.req_notify_cq = ocrdma_arm_cq,
	.resize_cq = ocrdma_resize_cq,
	.destroy_cq = ocrdma_destroy_cq,

	.create_qp = ocrdma_create_qp,
	.query_qp = ocrdma_query_qp,
	.modify_qp = ocrdma_modify_qp,
	.destroy_qp = ocrdma_destroy_qp,
	.post_send = ocrdma_post_send,
	.post_recv = ocrdma_post_recv,
	.create_ah = ocrdma_create_ah,
	.destroy_ah = ocrdma_destroy_ah,

	.create_srq = ocrdma_create_srq,
	.modify_srq = ocrdma_modify_srq,
	.query_srq = ocrdma_query_srq,
	.destroy_srq = ocrdma_destroy_srq,
	.post_srq_recv = ocrdma_post_srq_recv,
	.attach_mcast = ocrdma_attach_mcast,
	.detach_mcast = ocrdma_detach_mcast,
	.free_context = ocrdma_free_context,
};

static void ocrdma_uninit_device(struct verbs_device *verbs_device)
{
	struct ocrdma_device *dev = get_ocrdma_dev(&verbs_device->device);

	free(dev);
}

/*
 * ocrdma_alloc_context
 */
static struct verbs_context *ocrdma_alloc_context(struct ibv_device *ibdev,
						  int cmd_fd,
						  void *private_data)
{
	struct ocrdma_devctx *ctx;
	struct uocrdma_get_context cmd;
	struct uocrdma_get_context_resp resp;

	ctx = verbs_init_and_alloc_context(ibdev, cmd_fd, ctx, ibv_ctx,
					   RDMA_DRIVER_OCRDMA);
	if (!ctx)
		return NULL;

	if (ibv_cmd_get_context(&ctx->ibv_ctx,
				(struct ibv_get_context *)&cmd, sizeof cmd,
				&resp.ibv_resp, sizeof(resp)))
		goto cmd_err;

	verbs_set_ops(&ctx->ibv_ctx, &ocrdma_ctx_ops);

	get_ocrdma_dev(ibdev)->id = resp.dev_id;
	get_ocrdma_dev(ibdev)->max_inline_data = resp.max_inline_data;
	get_ocrdma_dev(ibdev)->wqe_size = resp.wqe_size;
	get_ocrdma_dev(ibdev)->rqe_size = resp.rqe_size;
	memcpy(get_ocrdma_dev(ibdev)->fw_ver, resp.fw_ver, sizeof(resp.fw_ver));
	get_ocrdma_dev(ibdev)->dpp_wqe_size = resp.dpp_wqe_size;

	ctx->ah_tbl =
	    mmap(NULL, resp.ah_tbl_len, PROT_READ | PROT_WRITE, MAP_SHARED,
		 cmd_fd, resp.ah_tbl_page);

	if (ctx->ah_tbl == MAP_FAILED)
		goto cmd_err;
	ctx->ah_tbl_len = resp.ah_tbl_len;
	ocrdma_init_ahid_tbl(ctx);

	return &ctx->ibv_ctx;

cmd_err:
	ocrdma_err("%s: Failed to allocate context for device.\n", __func__);
	verbs_uninit_context(&ctx->ibv_ctx);
	free(ctx);
	return NULL;
}

/*
 * ocrdma_free_context
 */
static void ocrdma_free_context(struct ibv_context *ibctx)
{
	struct ocrdma_devctx *ctx = get_ocrdma_ctx(ibctx);

	if (ctx->ah_tbl)
		munmap((void *)ctx->ah_tbl, ctx->ah_tbl_len);

	verbs_uninit_context(&ctx->ibv_ctx);
	free(ctx);
}

static struct verbs_device *
ocrdma_device_alloc(struct verbs_sysfs_dev *sysfs_dev)
{
	struct ocrdma_device *dev;

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

	dev->qp_tbl = malloc(OCRDMA_MAX_QP * sizeof(struct ocrdma_qp *));
	if (!dev->qp_tbl)
		goto qp_err;
	bzero(dev->qp_tbl, OCRDMA_MAX_QP * sizeof(struct ocrdma_qp *));
	pthread_mutex_init(&dev->dev_lock, NULL);
	pthread_spin_init(&dev->flush_q_lock, PTHREAD_PROCESS_PRIVATE);
	return &dev->ibv_dev;
qp_err:
	free(dev);
	return NULL;
}

static const struct verbs_device_ops ocrdma_dev_ops = {
	.name = "ocrdma",
	.match_min_abi_version = OCRDMA_ABI_VERSION,
	.match_max_abi_version = OCRDMA_ABI_VERSION,
	.match_table = ucna_table,
	.alloc_device = ocrdma_device_alloc,
	.uninit_device = ocrdma_uninit_device,
	.alloc_context = ocrdma_alloc_context,
};
PROVIDER_DRIVER(ocrdma, ocrdma_dev_ops);
