/**
* Copyright (C) Mellanox Technologies Ltd. 2001-2018.  ALL RIGHTS RESERVED.
*
* See file LICENSE for terms.
*/
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#ifdef HAVE_INFINIBAND_MLX5_HW_H

#include "ib_mlx5_hw.h"

#include <ucs/debug/log.h>
#include <ucs/debug/assert.h>
#include <ucs/arch/bitops.h>
#include <uct/ib/base/ib_verbs.h>
#include <infiniband/mlx5_hw.h>
#include <string.h>

/* Since this file intended to emulate DV using legacy mlx5_hw.h definitions
 * we include DV declarations. */
#define UCT_IB_MLX5_H_
#include <uct/ib/mlx5/dv/ib_mlx5_dv.h>

static void UCS_F_MAYBE_UNUSED uct_ib_mlx5_obj_error(const char *obj_name)
{
    ucs_error("Failed to get mlx5 %s information. Please make sure the installed "
              "libmlx5 version matches the one UCX was compiled with (%s)",
              obj_name, UCT_IB_LIBMLX5_VER);
}

#if !HAVE_DECL_MLX5DV_INIT_OBJ
ucs_status_t uct_ib_mlx5_get_qp_info(struct ibv_qp *qp, uct_ib_mlx5dv_qp_t *qp_info)
{
#if HAVE_DECL_IBV_MLX5_EXP_GET_QP_INFO
    struct ibv_mlx5_qp_info ibv_qp_info;
    int ret;

    ret = ibv_mlx5_exp_get_qp_info(qp, &ibv_qp_info);
    if (ret != 0) {
        uct_ib_mlx5_obj_error("qp");
        return UCS_ERR_NO_DEVICE;
    }

    qp_info->dv.dbrec      = ibv_qp_info.dbrec;
    qp_info->dv.sq.buf     = ibv_qp_info.sq.buf;
    qp_info->dv.sq.wqe_cnt = ibv_qp_info.sq.wqe_cnt;
    qp_info->dv.sq.stride  = ibv_qp_info.sq.stride;
    qp_info->dv.rq.buf     = ibv_qp_info.rq.buf;
    qp_info->dv.rq.wqe_cnt = ibv_qp_info.rq.wqe_cnt;
    qp_info->dv.rq.stride  = ibv_qp_info.rq.stride;
    qp_info->dv.bf.reg     = ibv_qp_info.bf.reg;
    qp_info->dv.bf.size    = ibv_qp_info.bf.size;
#else
    struct mlx5_qp *mqp = ucs_container_of(qp, struct mlx5_qp, verbs_qp.qp);

    if ((mqp->sq.cur_post != 0) || (mqp->rq.head != 0)) {
        ucs_warn("cur_post=%d head=%d need_lock=%d", mqp->sq.cur_post,
                 mqp->rq.head, mqp->bf->need_lock);
        return UCS_ERR_NO_DEVICE;
    }

    qp_info->dv.qpn        = qp->qp_num;
    qp_info->dv.dbrec      = mqp->db;
    qp_info->dv.sq.buf     = mqp->buf.buf + mqp->sq.offset;
    qp_info->dv.sq.wqe_cnt = mqp->sq.wqe_cnt;
    qp_info->dv.sq.stride  = 1 << mqp->sq.wqe_shift;
    qp_info->dv.rq.buf     = mqp->buf.buf + mqp->rq.offset;
    qp_info->dv.rq.wqe_cnt = mqp->rq.wqe_cnt;
    qp_info->dv.rq.stride  = 1 << mqp->rq.wqe_shift;
    qp_info->dv.bf.reg     = mqp->bf->reg;

    if (mqp->bf->uuarn > 0) {
        qp_info->dv.bf.size = mqp->bf->buf_size;
    } else {
        qp_info->dv.bf.size = 0; /* No BF */
    }
#endif
    return UCS_OK;
}

ucs_status_t uct_ib_mlx5_get_srq_info(struct ibv_srq *srq,
                                      uct_ib_mlx5dv_srq_t *srq_info)
{
#if HAVE_DECL_IBV_MLX5_EXP_GET_SRQ_INFO
    struct ibv_mlx5_srq_info ibv_srq_info;
    int ret;

    ret = ibv_mlx5_exp_get_srq_info(srq, &ibv_srq_info);
    if (ret != 0) {
        uct_ib_mlx5_obj_error("srq");
        return UCS_ERR_NO_DEVICE;
    }

    srq_info->dv.buf    = ibv_srq_info.buf;
    srq_info->dv.dbrec  = ibv_srq_info.dbrec;
    srq_info->dv.stride = ibv_srq_info.stride;
    srq_info->dv.head   = ibv_srq_info.head;
    srq_info->dv.tail   = ibv_srq_info.tail;
#else
    struct mlx5_srq *msrq;

    if (srq->handle == LEGACY_XRC_SRQ_HANDLE) {
        srq = (struct ibv_srq *)(((struct ibv_srq_legacy *)srq)->ibv_srq);
    }

    msrq = ucs_container_of(srq, struct mlx5_srq, vsrq.srq);

    if (msrq->counter != 0) {
        ucs_error("SRQ counter is not 0 (%d)", msrq->counter);
        return UCS_ERR_NO_DEVICE;
    }

    srq_info->dv.buf    = msrq->buf.buf;
    srq_info->dv.dbrec  = msrq->db;
    srq_info->dv.stride = 1 << msrq->wqe_shift;
    srq_info->dv.head   = msrq->head;
    srq_info->dv.tail   = msrq->tail;
#endif
    return UCS_OK;
}

static ucs_status_t uct_ib_mlx5_get_cq(struct ibv_cq *cq, uct_ib_mlx5dv_cq_t *mlx5_cq)
{
#if HAVE_DECL_IBV_MLX5_EXP_GET_CQ_INFO
    struct ibv_mlx5_cq_info ibv_cq_info;
    int ret;

    ret = ibv_mlx5_exp_get_cq_info(cq, &ibv_cq_info);
    if (ret != 0) {
        uct_ib_mlx5_obj_error("cq");
        return UCS_ERR_NO_DEVICE;
    }

    mlx5_cq->dv.buf      = ibv_cq_info.buf;
    mlx5_cq->dv.cqe_cnt  = ibv_cq_info.cqe_cnt;
    mlx5_cq->dv.cqn      = ibv_cq_info.cqn;
    mlx5_cq->dv.cqe_size = ibv_cq_info.cqe_size;
#else
    struct mlx5_cq *mcq = ucs_container_of(cq, struct mlx5_cq, ibv_cq);
    int ret;

    if (mcq->cons_index != 0) {
        ucs_error("CQ consumer index is not 0 (%d)", mcq->cons_index);
        return UCS_ERR_NO_DEVICE;
    }

    mlx5_cq->dv.buf      = mcq->active_buf->buf;
    mlx5_cq->dv.cqe_cnt  = mcq->ibv_cq.cqe + 1;
    mlx5_cq->dv.cqn      = mcq->cqn;
    mlx5_cq->dv.cqe_size = mcq->cqe_sz;
#endif
    return UCS_OK;
}

ucs_status_t uct_ib_mlx5dv_init_obj(uct_ib_mlx5dv_t *obj, uint64_t obj_type)
{
    ucs_status_t ret = UCS_OK;

    if (obj_type & MLX5DV_OBJ_QP) {
        ret = uct_ib_mlx5_get_qp_info(obj->dv.qp.in,
                ucs_container_of(obj->dv.qp.out, uct_ib_mlx5dv_qp_t, dv));
    }

    if (!ret && (obj_type & MLX5DV_OBJ_CQ)) {
        ret = uct_ib_mlx5_get_cq(obj->dv.cq.in,
                ucs_container_of(obj->dv.cq.out, uct_ib_mlx5dv_cq_t, dv));
    }

    if (!ret && (obj_type & MLX5DV_OBJ_SRQ)) {
        ret = uct_ib_mlx5_get_srq_info(obj->dv.srq.in,
                ucs_container_of(obj->dv.srq.out, uct_ib_mlx5dv_srq_t, dv));
    }

#ifdef HAVE_IBV_EXP_DM
    if (!ret && (obj_type & MLX5DV_OBJ_DM)) {
        ret = uct_ib_mlx5_get_dm_info(obj->dv_dm.in, obj->dv_dm.out);
    }
#endif

    return ret;
}
#endif

void uct_ib_mlx5_update_cq_ci(struct ibv_cq *cq, unsigned cq_ci)
{
#if HAVE_DECL_IBV_MLX5_EXP_UPDATE_CQ_CI
    ibv_mlx5_exp_update_cq_ci(cq, cq_ci);
#else
    struct mlx5_cq *mcq = ucs_container_of(cq, struct mlx5_cq, ibv_cq);
    mcq->cons_index = cq_ci;
#endif
}

unsigned uct_ib_mlx5_get_cq_ci(struct ibv_cq *cq)
{
    struct mlx5_cq *mcq = ucs_container_of(cq, struct mlx5_cq, ibv_cq);
    return mcq->cons_index;
}

#if !HAVE_DECL_MLX5DV_OBJ_AH
void uct_ib_mlx5_get_av(struct ibv_ah *ah, struct mlx5_wqe_av *av)
{
    memcpy(av, &ucs_container_of(ah, struct mlx5_ah, ibv_ah)->av, sizeof(*av));
}
#endif

struct ibv_qp *uct_dv_get_cmd_qp(struct ibv_srq *srq)
{
#ifdef HAVE_STRUCT_MLX5_SRQ_CMD_QP
    struct mlx5_srq *msrq;

    if (srq->handle == LEGACY_XRC_SRQ_HANDLE) {
        srq = (struct ibv_srq *)(((struct ibv_srq_legacy *)srq)->ibv_srq);
    }

    msrq = ucs_container_of(srq, struct mlx5_srq, vsrq.srq);
    if (msrq->counter != 0) {
        ucs_error("SRQ counter is not 0 (%d)", msrq->counter);
        return NULL;
    }

    return &msrq->cmd_qp->verbs_qp.qp;
#else
    return NULL;
#endif
}

struct mlx5_uar_data {
    enum { __DUMMY }            map_type;
    void                        *regs;
};

void *uct_dv_get_info_uar0(void *uar)
{
#if HAVE_DECL_MLX5DV_INIT_OBJ
    struct mlx5_uar_data *muar = uar;
    return muar[0].regs;
#else
    return NULL;
#endif
}

#endif
