/*
 * Copyright (c) 2001-2018 Mellanox Technologies, Ltd. 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 HAVE_CONFIG_H
#include "config.h"
#endif

#if defined(DEFINED_DIRECT_VERBS)

#include "vma/util/valgrind.h"
#include "vma/util/utils.h"
#include "vma/ib/mlx5/ib_mlx5.h"


int vma_ib_mlx5_get_qp(struct ibv_qp *qp, vma_ib_mlx5_qp_t *mlx5_qp, uint32_t flags)
{
	int ret = 0;
	struct mlx5dv_obj obj;
	struct mlx5dv_qp dqp;
	enum ibv_qp_attr_mask attr_mask = IBV_QP_CAP;
	struct ibv_qp_attr tmp_ibv_qp_attr;
	struct ibv_qp_init_attr tmp_ibv_qp_init_attr;

	memset(&obj, 0, sizeof(obj));
	memset(&dqp, 0, sizeof(dqp));

	obj.qp.in = qp;
	obj.qp.out = &dqp;
	ret = vma_ib_mlx5dv_init_obj(&obj, MLX5DV_OBJ_QP);
	if (ret != 0) {
		goto out;
	}

	mlx5_qp->qp           = qp;
	mlx5_qp->qpn          = qp->qp_num;
	mlx5_qp->flags        = flags;
	mlx5_qp->sq.dbrec     = &dqp.dbrec[MLX5_SND_DBR];
	mlx5_qp->sq.buf       = dqp.sq.buf;
	mlx5_qp->sq.wqe_cnt   = dqp.sq.wqe_cnt;
	mlx5_qp->sq.stride    = dqp.sq.stride;
	mlx5_qp->rq.dbrec     = &dqp.dbrec[MLX5_RCV_DBR];
	mlx5_qp->rq.buf       = dqp.rq.buf;
	mlx5_qp->rq.wqe_cnt   = dqp.rq.wqe_cnt;
	mlx5_qp->rq.stride    = dqp.rq.stride;
	mlx5_qp->rq.wqe_shift = ilog_2(dqp.rq.stride);
	mlx5_qp->rq.head      = 0;
	mlx5_qp->rq.tail      = 0;
	mlx5_qp->bf.reg       = dqp.bf.reg;
	mlx5_qp->bf.size      = dqp.bf.size;
	mlx5_qp->bf.offset    = 0;

	ret = ibv_query_qp(qp, &tmp_ibv_qp_attr, attr_mask, &tmp_ibv_qp_init_attr);
	if (ret != 0) {
		goto out;
	}

	VALGRIND_MAKE_MEM_DEFINED(&tmp_ibv_qp_attr, sizeof(tmp_ibv_qp_attr));
	mlx5_qp->cap.max_send_wr = tmp_ibv_qp_attr.cap.max_send_wr;
	mlx5_qp->cap.max_recv_wr = tmp_ibv_qp_attr.cap.max_recv_wr;
	mlx5_qp->cap.max_send_sge = tmp_ibv_qp_attr.cap.max_send_sge;
	mlx5_qp->cap.max_recv_sge = tmp_ibv_qp_attr.cap.max_recv_sge;
	mlx5_qp->cap.max_inline_data = tmp_ibv_qp_attr.cap.max_inline_data;

out:
    return ret;
}

int vma_ib_mlx5_get_cq(struct ibv_cq *cq, vma_ib_mlx5_cq_t *mlx5_cq)
{
	int ret = 0;
	struct mlx5dv_obj obj;
	struct mlx5dv_cq dcq;

	/* Initialization of cq can be done once to protect
	 * internal data from corruption.
	 * cq field is used to detect one time initialization
	 * For example: this function can be called when QP is moved
	 * from ERROR state to RESET so cq_ci or cq_sn should not be
	 * updated
	 */
	if (mlx5_cq == NULL || mlx5_cq->cq == cq) {
		return 0;
	}

	memset(&obj, 0, sizeof(obj));
	memset(&dcq, 0, sizeof(dcq));

	obj.cq.in = cq;
	obj.cq.out = &dcq;
	ret = vma_ib_mlx5dv_init_obj(&obj, MLX5DV_OBJ_CQ);
	if (ret != 0) {
		return ret;
	}

	mlx5_cq->cq           = cq;
	mlx5_cq->cq_num       = dcq.cqn;
	mlx5_cq->cq_ci        = 0;
	mlx5_cq->cq_sn        = 0;
	mlx5_cq->cqe_count    = dcq.cqe_cnt;
	mlx5_cq->cqe_size     = dcq.cqe_size;
	mlx5_cq->cqe_size_log = ilog_2(dcq.cqe_size);
	mlx5_cq->dbrec        = dcq.dbrec;
	mlx5_cq->uar          = dcq.cq_uar;

	/* Move buffer forward for 128b CQE, so we would get pointer to the 2nd
	 * 64b when polling.
	 */
	mlx5_cq->cq_buf       = (uint8_t *)dcq.buf + dcq.cqe_size - sizeof(struct mlx5_cqe64);

    return 0;
}

int vma_ib_mlx5_post_recv(vma_ib_mlx5_qp_t *mlx5_qp,
		struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr)
{
	struct mlx5_wqe_data_seg *scat;
	int err = 0;
	int nreq;
	int ind;
	int i, j;

	ind = mlx5_qp->rq.head & (mlx5_qp->rq.wqe_cnt - 1);
	*bad_wr = NULL;

	for (nreq = 0; wr; ++nreq, wr = wr->next) {
		if (unlikely((int)mlx5_qp->rq.head - (int)mlx5_qp->rq.tail + nreq >= (int)mlx5_qp->cap.max_recv_wr)) {
			errno = ENOMEM;
			err = -errno;
			*bad_wr = wr;
			goto out;
		}

		if (unlikely(wr->num_sge > (int)mlx5_qp->cap.max_recv_sge)) {
			errno = EINVAL;
			err = -errno;
			*bad_wr = wr;
			goto out;
		}

		scat = (struct mlx5_wqe_data_seg *)((uint8_t *)mlx5_qp->rq.buf + (ind << mlx5_qp->rq.wqe_shift));

		for (i = 0, j = 0; i < wr->num_sge; ++i) {
			if (unlikely(!wr->sg_list[i].length)) continue;

			scat[j].byte_count = htonl(wr->sg_list[i].length);
			scat[j].lkey       = htonl(wr->sg_list[i].lkey);
			scat[j].addr       = htonll(wr->sg_list[i].addr);
			j++;
		}

		if (j < (int)mlx5_qp->cap.max_recv_sge) {
			scat[j].byte_count = 0;
			scat[j].lkey       = htonl(MLX5_INVALID_LKEY);
			scat[j].addr       = 0;
		}

		ind = (ind + 1) & (mlx5_qp->rq.wqe_cnt - 1);
	}

out:
	if (likely(nreq)) {
		mlx5_qp->rq.head += nreq;

		/*
		 * Make sure that descriptors are written before
		 * doorbell record.
		 */
		wmb();

		/*
		 * For Raw Packet QP, avoid updating the doorbell record
		 * as long as the QP isn't in RTR state, to avoid receiving
		 * packets in illegal states.
		 * This is only for Raw Packet QPs since they are represented
		 * differently in the hardware.
		 */
		if (likely(!((mlx5_qp->qp->qp_type == IBV_QPT_RAW_PACKET ||
				mlx5_qp->flags & VMA_IB_MLX5_QP_FLAGS_USE_UNDERLAY) &&
				mlx5_qp->qp->state < IBV_QPS_RTR)))
			*mlx5_qp->rq.dbrec = htonl(mlx5_qp->rq.head & 0xffff);
	}

	return err;
}

#endif /* DEFINED_DIRECT_VERBS */
