/*
 * Copyright (c) 2006-2016 Chelsio, 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
 * OpenIB.org 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.
 */
#include <config.h>

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

#include "libcxgb4.h"
#include "cxgb4-abi.h"

bool is_64b_cqe;

#define MASKED(x) (void *)((unsigned long)(x) & c4iw_page_mask)

int c4iw_query_device(struct ibv_context *context,
		      const struct ibv_query_device_ex_input *input,
		      struct ibv_device_attr_ex *attr, size_t attr_size)
{
	struct ib_uverbs_ex_query_device_resp resp;
	size_t resp_size = sizeof(resp);
	uint64_t raw_fw_ver;
	u8 major, minor, sub_minor, build;
	int ret;

	ret = ibv_cmd_query_device_any(context, input, attr, attr_size, &resp,
				       &resp_size);
	if (ret)
		return ret;

	raw_fw_ver = resp.base.fw_ver;
	major = (raw_fw_ver >> 24) & 0xff;
	minor = (raw_fw_ver >> 16) & 0xff;
	sub_minor = (raw_fw_ver >> 8) & 0xff;
	build = raw_fw_ver & 0xff;

	snprintf(attr->orig_attr.fw_ver, sizeof(attr->orig_attr.fw_ver),
		 "%d.%d.%d.%d", major, minor, sub_minor, build);

	return 0;
}

int c4iw_query_port(struct ibv_context *context, uint8_t port,
		    struct ibv_port_attr *attr)
{
	struct ibv_query_port cmd;

	return ibv_cmd_query_port(context, port, attr, &cmd, sizeof cmd);
}

struct ibv_pd *c4iw_alloc_pd(struct ibv_context *context)
{
	struct ibv_alloc_pd cmd;
	struct uc4iw_alloc_pd_resp resp;
	struct c4iw_pd *pd;

	pd = malloc(sizeof *pd);
	if (!pd)
		return NULL;

	if (ibv_cmd_alloc_pd(context, &pd->ibv_pd, &cmd, sizeof cmd,
			     &resp.ibv_resp, sizeof resp)) {
		free(pd);
		return NULL;
	}

	return &pd->ibv_pd;
}

int c4iw_free_pd(struct ibv_pd *pd)
{
	int ret;

	ret = ibv_cmd_dealloc_pd(pd);
	if (ret)
		return ret;

	free(pd);
	return 0;
}

struct ibv_mr *c4iw_reg_mr(struct ibv_pd *pd, void *addr, size_t length,
			   uint64_t hca_va, int access)
{
	struct c4iw_mr *mhp;
	struct ibv_reg_mr cmd;
	struct ib_uverbs_reg_mr_resp resp;
	struct c4iw_dev *dev = to_c4iw_dev(pd->context->device);

	PDBG("%s addr %p length %ld hca_va %p\n", __func__, addr, length,
	     hca_va);

	mhp = malloc(sizeof *mhp);
	if (!mhp)
		return NULL;

	if (ibv_cmd_reg_mr(pd, addr, length, hca_va,
			   access, &mhp->vmr, &cmd, sizeof(cmd),
			   &resp, sizeof resp)) {
		free(mhp);
		return NULL;
	}

	mhp->va_fbo = hca_va;
	mhp->len = length;

	PDBG("%s stag 0x%x va_fbo 0x%" PRIx64 " len %d\n",
	     __func__, mhp->vmr.ibv_mr.rkey, mhp->va_fbo, mhp->len);

	pthread_spin_lock(&dev->lock);
	dev->mmid2ptr[c4iw_mmid(mhp->vmr.ibv_mr.lkey)] = mhp;
	pthread_spin_unlock(&dev->lock);
	INC_STAT(mr);
	return &mhp->vmr.ibv_mr;
}

int c4iw_dereg_mr(struct verbs_mr *vmr)
{
	int ret;
	struct c4iw_dev *dev = to_c4iw_dev(vmr->ibv_mr.pd->context->device);

	ret = ibv_cmd_dereg_mr(vmr);
	if (ret)
		return ret;

	pthread_spin_lock(&dev->lock);
	dev->mmid2ptr[c4iw_mmid(vmr->ibv_mr.lkey)] = NULL;
	pthread_spin_unlock(&dev->lock);

	free(to_c4iw_mr(vmr));

	return 0;
}

struct ibv_cq *c4iw_create_cq(struct ibv_context *context, int cqe,
			      struct ibv_comp_channel *channel, int comp_vector)
{
	struct uc4iw_create_cq cmd = {};
	struct uc4iw_create_cq_resp resp;
	struct c4iw_cq *chp;
	struct c4iw_dev *dev = to_c4iw_dev(context->device);
	int ret;

	if (!cqe || cqe > T4_MAX_CQ_DEPTH) {
		errno = EINVAL;
		return NULL;
	}

	chp = calloc(1, sizeof *chp);
	if (!chp) {
		return NULL;
	}

	resp.flags = 0;
	cmd.flags = C4IW_64B_CQE;

	ret = ibv_cmd_create_cq(context, cqe, channel, comp_vector,
				&chp->ibv_cq, &cmd.ibv_cmd, sizeof(cmd),
				&resp.ibv_resp, sizeof resp);
	if (ret)
		goto err1;

	if (resp.flags & C4IW_64B_CQE)
		is_64b_cqe = true;

	pthread_spin_init(&chp->lock, PTHREAD_PROCESS_PRIVATE);
#ifdef STALL_DETECTION
	gettimeofday(&chp->time, NULL);
#endif
	chp->rhp = dev;
	chp->cq.qid_mask = resp.qid_mask;
	chp->cq.cqid = resp.cqid;
	chp->cq.size = resp.size;
	chp->cq.memsize = resp.memsize;
	chp->cq.gen = 1;
	chp->cq.queue = mmap(NULL, chp->cq.memsize, PROT_READ|PROT_WRITE,
			     MAP_SHARED, context->cmd_fd, resp.key);
	if (chp->cq.queue == MAP_FAILED)
		goto err2;

	chp->cq.qp_errp =
	&((struct t4_status_page *)
			Q_ENTRY(chp->cq.queue, chp->cq.size))->qp_err;

	chp->cq.ugts = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
			   context->cmd_fd, resp.gts_key);
	if (chp->cq.ugts == MAP_FAILED)
		goto err3;

	if (dev_is_t4(chp->rhp))
		chp->cq.ugts += 1;
	else
		chp->cq.ugts += 5;
	chp->cq.sw_queue = calloc(chp->cq.size, CQE_SIZE(chp->cq.queue));
	if (!chp->cq.sw_queue)
		goto err4;

	PDBG("%s cqid 0x%x key %" PRIx64 " va %p memsize %lu gts_key %"
	       PRIx64 " va %p qid_mask 0x%x\n",
	       __func__, chp->cq.cqid, resp.key, chp->cq.queue,
	       chp->cq.memsize, resp.gts_key, chp->cq.ugts, chp->cq.qid_mask);

	pthread_spin_lock(&dev->lock);
	dev->cqid2ptr[chp->cq.cqid] = chp;
	pthread_spin_unlock(&dev->lock);
	INC_STAT(cq);
	return &chp->ibv_cq;
err4:
	munmap(MASKED(chp->cq.ugts), c4iw_page_size);
err3:
	munmap(chp->cq.queue, chp->cq.memsize);
err2:
	(void)ibv_cmd_destroy_cq(&chp->ibv_cq);
err1:
	free(chp);
	return NULL;
}

int c4iw_destroy_cq(struct ibv_cq *ibcq)
{
	int ret;
	struct c4iw_cq *chp = to_c4iw_cq(ibcq);
	struct c4iw_dev *dev = to_c4iw_dev(ibcq->context->device);

	chp->cq.error = 1;
	ret = ibv_cmd_destroy_cq(ibcq);
	if (ret) {
		return ret;
	}
	munmap(MASKED(chp->cq.ugts), c4iw_page_size);
	munmap(chp->cq.queue, chp->cq.memsize);

	pthread_spin_lock(&dev->lock);
	dev->cqid2ptr[chp->cq.cqid] = NULL;
	pthread_spin_unlock(&dev->lock);

	free(chp->cq.sw_queue);
	free(chp);
	return 0;
}

struct ibv_srq *c4iw_create_srq(struct ibv_pd *pd,
				struct ibv_srq_init_attr *attr)
{
	struct c4iw_dev *dev = to_c4iw_dev(pd->context->device);
	struct uc4iw_create_srq_resp resp;
	unsigned long segment_offset;
	struct ibv_create_srq cmd;
	struct c4iw_srq *srq;
	void *dbva;
	int ret;

	PDBG("%s enter\n", __func__);
	srq = calloc(1, sizeof(*srq));
	if (!srq)
		goto err;

	ret = ibv_cmd_create_srq(pd, &srq->ibv_srq, attr, &cmd,
			sizeof(cmd), &resp.ibv_resp, sizeof(resp));
	if (ret)
		goto err_free_srq_mem;

	PDBG("%s srq id 0x%x srq key %" PRIx64 " srq db/gts key %" PRIx64
			" qid_mask 0x%x\n", __func__,
			resp.srqid, resp.srq_key, resp.srq_db_gts_key,
			resp.qid_mask);

	srq->rhp = dev;
	srq->wq.qid = resp.srqid;
	srq->wq.size = resp.srq_size;
	srq->wq.memsize = resp.srq_memsize;
	srq->wq.rqt_abs_idx = resp.rqt_abs_idx;
	srq->flags = resp.flags;
	pthread_spin_init(&srq->lock, PTHREAD_PROCESS_PRIVATE);

	dbva = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
			pd->context->cmd_fd, resp.srq_db_gts_key);
	if (dbva == MAP_FAILED)
		goto err_destroy_srq;
	srq->wq.udb = dbva;

	segment_offset = 128 * (srq->wq.qid & resp.qid_mask);
	if (segment_offset < c4iw_page_size) {
		srq->wq.udb += segment_offset / 4;
		srq->wq.wc_reg_available = 1;
	} else
		srq->wq.bar2_qid = srq->wq.qid & resp.qid_mask;
	srq->wq.udb += 2;

	srq->wq.queue = mmap(NULL, srq->wq.memsize,
			PROT_WRITE, MAP_SHARED,
			pd->context->cmd_fd, resp.srq_key);
	if (srq->wq.queue == MAP_FAILED)
		goto err_unmap_udb;

	srq->wq.sw_rq = calloc(srq->wq.size, sizeof(struct t4_swrqe));
	if (!srq->wq.sw_rq)
		goto err_unmap_queue;
	srq->wq.pending_wrs =
		calloc(srq->wq.size, sizeof(*srq->wq.pending_wrs));
	if (!srq->wq.pending_wrs)
		goto err_free_sw_rq;

	pthread_spin_lock(&dev->lock);
	list_add_tail(&dev->srq_list, &srq->list);
	pthread_spin_unlock(&dev->lock);

	PDBG("%s srq dbva %p srq qva %p srq depth %u srq memsize %lu\n",
			__func__, srq->wq.udb, srq->wq.queue,
			srq->wq.size, srq->wq.memsize);

	INC_STAT(srq);
	return &srq->ibv_srq;
err_free_sw_rq:
	free(srq->wq.sw_rq);
err_unmap_queue:
	munmap((void *)srq->wq.queue, srq->wq.memsize);
err_unmap_udb:
	munmap(MASKED(srq->wq.udb), c4iw_page_size);
err_destroy_srq:
	(void)ibv_cmd_destroy_srq(&srq->ibv_srq);
err_free_srq_mem:
	free(srq);
err:

	return NULL;
}

int c4iw_modify_srq(struct ibv_srq *ibsrq, struct ibv_srq_attr *attr,
		    int attr_mask)
{
	struct c4iw_srq *srq = to_c4iw_srq(ibsrq);
	struct ibv_modify_srq cmd;
	int ret;

	/* XXX no support for this yet */
	if (attr_mask & IBV_SRQ_MAX_WR)
		return EINVAL;

	ret = ibv_cmd_modify_srq(ibsrq, attr, attr_mask, &cmd, sizeof(cmd));
	if (!ret) {
		if (attr_mask & IBV_SRQ_LIMIT) {
			srq->armed = 1;
			srq->srq_limit = attr->srq_limit;
		}
	}
	return ret;
}

int c4iw_destroy_srq(struct ibv_srq *ibsrq)
{
	int ret;
	struct c4iw_srq *srq = to_c4iw_srq(ibsrq);

	PDBG("%s enter qp %p\n", __func__, ibsrq);

	ret = ibv_cmd_destroy_srq(ibsrq);
	if (ret)
		return ret;

	pthread_spin_lock(&srq->rhp->lock);
	list_del(&srq->list);
	pthread_spin_unlock(&srq->rhp->lock);

	munmap(MASKED(srq->wq.udb), c4iw_page_size);
	munmap(srq->wq.queue, srq->wq.memsize);

	free(srq->wq.pending_wrs);
	free(srq->wq.sw_rq);
	free(srq);
	return 0;

}

int c4iw_query_srq(struct ibv_srq *ibsrq, struct ibv_srq_attr *attr)
{
	struct ibv_query_srq cmd;

	return ibv_cmd_query_srq(ibsrq, attr, &cmd, sizeof(cmd));
}

static struct ibv_qp *create_qp_v0(struct ibv_pd *pd,
				   struct ibv_qp_init_attr *attr)
{
	struct ibv_create_qp cmd;
	struct uc4iw_create_qp_v0_resp resp;
	struct c4iw_qp *qhp;
	struct c4iw_dev *dev = to_c4iw_dev(pd->context->device);
	int ret;
	void *dbva;

	PDBG("%s enter qp\n", __func__);
	qhp = calloc(1, sizeof *qhp);
	if (!qhp)
		goto err1;

	ret = ibv_cmd_create_qp(pd, &qhp->ibv_qp, attr, &cmd,
				sizeof cmd, &resp.ibv_resp, sizeof resp);
	if (ret)
		goto err2;

	PDBG("%s sqid 0x%x sq key %" PRIx64 " sq db/gts key %" PRIx64
	       " rqid 0x%x rq key %" PRIx64 " rq db/gts key %" PRIx64
	       " qid_mask 0x%x\n",
		__func__,
		resp.sqid, resp.sq_key, resp.sq_db_gts_key,
		resp.rqid, resp.rq_key, resp.rq_db_gts_key, resp.qid_mask);

	qhp->wq.qid_mask = resp.qid_mask;
	qhp->rhp = dev;
	qhp->wq.sq.qid = resp.sqid;
	qhp->wq.sq.size = resp.sq_size;
	qhp->wq.sq.memsize = resp.sq_memsize;
	qhp->wq.sq.flags = 0;
	qhp->wq.rq.msn = 1;
	qhp->wq.rq.qid = resp.rqid;
	qhp->wq.rq.size = resp.rq_size;
	qhp->wq.rq.memsize = resp.rq_memsize;
	pthread_spin_init(&qhp->lock, PTHREAD_PROCESS_PRIVATE);

	dbva = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
		    pd->context->cmd_fd, resp.sq_db_gts_key);
	if (dbva == MAP_FAILED)
		goto err3;

	qhp->wq.sq.udb = dbva;
	qhp->wq.sq.queue = mmap(NULL, qhp->wq.sq.memsize,
			    PROT_WRITE, MAP_SHARED,
			    pd->context->cmd_fd, resp.sq_key);
	if (qhp->wq.sq.queue == MAP_FAILED)
		goto err4;

	dbva = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
		    pd->context->cmd_fd, resp.rq_db_gts_key);
	if (dbva == MAP_FAILED)
		goto err5;
	qhp->wq.rq.udb = dbva;
	qhp->wq.rq.queue = mmap(NULL, qhp->wq.rq.memsize,
			    PROT_WRITE, MAP_SHARED,
			    pd->context->cmd_fd, resp.rq_key);
	if (qhp->wq.rq.queue == MAP_FAILED)
		goto err6;

	qhp->wq.sq.sw_sq = calloc(qhp->wq.sq.size, sizeof (struct t4_swsqe));
	if (!qhp->wq.sq.sw_sq)
		goto err7;

	qhp->wq.rq.sw_rq = calloc(qhp->wq.rq.size, sizeof(struct t4_swrqe));
	if (!qhp->wq.rq.sw_rq)
		goto err8;

	PDBG("%s sq dbva %p sq qva %p sq depth %u sq memsize %lu "
	       " rq dbva %p rq qva %p rq depth %u rq memsize %lu\n",
	     __func__,
	     qhp->wq.sq.udb, qhp->wq.sq.queue,
	     qhp->wq.sq.size, qhp->wq.sq.memsize,
	     qhp->wq.rq.udb, qhp->wq.rq.queue,
	     qhp->wq.rq.size, qhp->wq.rq.memsize);

	qhp->sq_sig_all = attr->sq_sig_all;

	pthread_spin_lock(&dev->lock);
	dev->qpid2ptr[qhp->wq.sq.qid] = qhp;
	pthread_spin_unlock(&dev->lock);
	INC_STAT(qp);
	return &qhp->ibv_qp;
err8:
	free(qhp->wq.sq.sw_sq);
err7:
	munmap((void *)qhp->wq.rq.queue, qhp->wq.rq.memsize);
err6:
	munmap(MASKED(qhp->wq.rq.udb), c4iw_page_size);
err5:
	munmap((void *)qhp->wq.sq.queue, qhp->wq.sq.memsize);
err4:
	munmap(MASKED(qhp->wq.sq.udb), c4iw_page_size);
err3:
	(void)ibv_cmd_destroy_qp(&qhp->ibv_qp);
err2:
	free(qhp);
err1:
	return NULL;
}

static struct ibv_qp *create_qp(struct ibv_pd *pd,
				struct ibv_qp_init_attr *attr)
{
	struct ibv_create_qp cmd;
	struct uc4iw_create_qp_resp resp;
	struct c4iw_qp *qhp;
	struct c4iw_dev *dev = to_c4iw_dev(pd->context->device);
	struct c4iw_context *ctx = to_c4iw_context(pd->context);
	int ret;
	void *dbva;

	PDBG("%s enter qp\n", __func__);
	qhp = calloc(1, sizeof *qhp);
	if (!qhp)
		goto err1;

	ret = ibv_cmd_create_qp(pd, &qhp->ibv_qp, attr, &cmd,
				sizeof cmd, &resp.ibv_resp, sizeof resp);
	if (ret)
		goto err2;

	PDBG("%s sqid 0x%x sq key %" PRIx64 " sq db/gts key %" PRIx64
	       " rqid 0x%x rq key %" PRIx64 " rq db/gts key %" PRIx64
	       " qid_mask 0x%x\n",
		__func__,
		resp.sqid, resp.sq_key, resp.sq_db_gts_key,
		resp.rqid, resp.rq_key, resp.rq_db_gts_key, resp.qid_mask);

	qhp->wq.qid_mask = resp.qid_mask;
	qhp->rhp = dev;
	qhp->wq.sq.qid = resp.sqid;
	qhp->wq.sq.size = resp.sq_size;
	qhp->wq.sq.memsize = resp.sq_memsize;
	qhp->wq.sq.flags = resp.flags & C4IW_QPF_ONCHIP ? T4_SQ_ONCHIP : 0;
	if (resp.flags & C4IW_QPF_WRITE_W_IMM)
		qhp->wq.sq.flags |= T4_SQ_WRITE_W_IMM;
	qhp->wq.sq.flush_cidx = -1;
	qhp->wq.rq.msn = 1;
	qhp->srq = to_c4iw_srq(attr->srq);
	if (!attr->srq) {
		qhp->wq.rq.qid = resp.rqid;
		qhp->wq.rq.size = resp.rq_size;
		qhp->wq.rq.memsize = resp.rq_memsize;
	}
	if (ma_wr && resp.sq_memsize < (resp.sq_size + 1) *
	    sizeof *qhp->wq.sq.queue + 16*sizeof(__be64) ) {
		ma_wr = 0;
		fprintf(stderr, "libcxgb4 warning - downlevel iw_cxgb4 driver. "
			"MA workaround disabled.\n");
	}
	pthread_spin_init(&qhp->lock, PTHREAD_PROCESS_PRIVATE);

	dbva = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
		    pd->context->cmd_fd, resp.sq_db_gts_key);
	if (dbva == MAP_FAILED)
		goto err3;
	qhp->wq.sq.udb = dbva;
	if (!dev_is_t4(qhp->rhp)) {
		unsigned long segment_offset = 128 * (qhp->wq.sq.qid &
						      qhp->wq.qid_mask);

		if (segment_offset < c4iw_page_size) {
			qhp->wq.sq.udb += segment_offset / 4;
			qhp->wq.sq.wc_reg_available = 1;
		} else
			qhp->wq.sq.bar2_qid = qhp->wq.sq.qid & qhp->wq.qid_mask;
		qhp->wq.sq.udb += 2;
	}

	qhp->wq.sq.queue = mmap(NULL, qhp->wq.sq.memsize,
			    PROT_WRITE, MAP_SHARED,
			    pd->context->cmd_fd, resp.sq_key);
	if (qhp->wq.sq.queue == MAP_FAILED)
		goto err4;

	if (!attr->srq) {
		dbva = mmap(NULL, c4iw_page_size, PROT_WRITE, MAP_SHARED,
				pd->context->cmd_fd, resp.rq_db_gts_key);
		if (dbva == MAP_FAILED)
			goto err5;
		qhp->wq.rq.udb = dbva;
		if (!dev_is_t4(qhp->rhp)) {
			unsigned long segment_offset = 128 * (qhp->wq.rq.qid &
					qhp->wq.qid_mask);

			if (segment_offset < c4iw_page_size) {
				qhp->wq.rq.udb += segment_offset / 4;
				qhp->wq.rq.wc_reg_available = 1;
			} else
				qhp->wq.rq.bar2_qid =
					qhp->wq.rq.qid & qhp->wq.qid_mask;
			qhp->wq.rq.udb += 2;
		}
		qhp->wq.rq.queue = mmap(NULL, qhp->wq.rq.memsize,
				PROT_WRITE, MAP_SHARED,
				pd->context->cmd_fd, resp.rq_key);
		if (qhp->wq.rq.queue == MAP_FAILED)
			goto err6;
	}

	qhp->wq.sq.sw_sq = calloc(qhp->wq.sq.size, sizeof (struct t4_swsqe));
	if (!qhp->wq.sq.sw_sq)
		goto err7;

	if (!attr->srq) {
		qhp->wq.rq.sw_rq =
			calloc(qhp->wq.rq.size, sizeof(struct t4_swrqe));
		if (!qhp->wq.rq.sw_rq)
			goto err8;
	}

	if (t4_sq_onchip(&qhp->wq)) {
		qhp->wq.sq.ma_sync = mmap(NULL, c4iw_page_size, PROT_WRITE,
					  MAP_SHARED, pd->context->cmd_fd,
					  resp.ma_sync_key);
		if (qhp->wq.sq.ma_sync == MAP_FAILED)
			goto err9;
		qhp->wq.sq.ma_sync += (A_PCIE_MA_SYNC & (c4iw_page_size - 1));
	}

	if (ctx->status_page_size) {
		qhp->wq.db_offp = &ctx->status_page->db_off;
	} else if (!attr->srq) {
		qhp->wq.db_offp = 
			&qhp->wq.rq.queue[qhp->wq.rq.size].status.db_off;
	}

	if (!attr->srq)
		qhp->wq.qp_errp =
			&qhp->wq.rq.queue[qhp->wq.rq.size].status.qp_err;
	else {
		qhp->wq.qp_errp =
			&qhp->wq.sq.queue[qhp->wq.sq.size].status.qp_err;
		qhp->wq.srqidxp =
			&qhp->wq.sq.queue[qhp->wq.sq.size].status.srqidx;
	}

	PDBG("%s sq dbva %p sq qva %p sq depth %u sq memsize %lu "
	       " rq dbva %p rq qva %p rq depth %u rq memsize %lu\n",
	     __func__,
	     qhp->wq.sq.udb, qhp->wq.sq.queue,
	     qhp->wq.sq.size, qhp->wq.sq.memsize,
	     qhp->wq.rq.udb, qhp->wq.rq.queue,
	     qhp->wq.rq.size, qhp->wq.rq.memsize);

	qhp->sq_sig_all = attr->sq_sig_all;

	pthread_spin_lock(&dev->lock);
	dev->qpid2ptr[qhp->wq.sq.qid] = qhp;
	pthread_spin_unlock(&dev->lock);
	INC_STAT(qp);
	return &qhp->ibv_qp;
err9:
	if (!attr->srq)
		free(qhp->wq.rq.sw_rq);
err8:
	free(qhp->wq.sq.sw_sq);
err7:
	if (!attr->srq)
		munmap((void *)qhp->wq.rq.queue, qhp->wq.rq.memsize);
err6:
	if (!attr->srq)
		munmap(MASKED(qhp->wq.rq.udb), c4iw_page_size);
err5:
	munmap((void *)qhp->wq.sq.queue, qhp->wq.sq.memsize);
err4:
	munmap(MASKED(qhp->wq.sq.udb), c4iw_page_size);
err3:
	(void)ibv_cmd_destroy_qp(&qhp->ibv_qp);
err2:
	free(qhp);
err1:
	return NULL;
}

struct ibv_qp *c4iw_create_qp(struct ibv_pd *pd,
				     struct ibv_qp_init_attr *attr)
{
	struct c4iw_dev *dev = to_c4iw_dev(pd->context->device);

	if (dev->abi_version == 0)
		return create_qp_v0(pd, attr);
	return create_qp(pd, attr);
}

static void reset_qp(struct c4iw_qp *qhp)
{
	PDBG("%s enter qp %p\n", __func__, qhp);
	qhp->wq.sq.cidx = 0;
	qhp->wq.sq.wq_pidx = qhp->wq.sq.pidx = qhp->wq.sq.in_use = 0;
	qhp->wq.rq.cidx = qhp->wq.rq.pidx = qhp->wq.rq.in_use = 0;
	qhp->wq.sq.oldest_read = NULL;
	memset(qhp->wq.sq.queue, 0, qhp->wq.sq.memsize);
	if (t4_sq_onchip(&qhp->wq))
		mmio_flush_writes();
	memset(qhp->wq.rq.queue, 0, qhp->wq.rq.memsize);
}

int c4iw_modify_qp(struct ibv_qp *ibqp, struct ibv_qp_attr *attr,
		   int attr_mask)
{
	struct ibv_modify_qp cmd = {};
	struct c4iw_qp *qhp = to_c4iw_qp(ibqp);
	int ret;

	PDBG("%s enter qp %p new state %d\n", __func__, ibqp, attr_mask & IBV_QP_STATE ? attr->qp_state : -1);
	if (t4_wq_in_error(&qhp->wq))
		c4iw_flush_qp(qhp);
	pthread_spin_lock(&qhp->lock);
	ret = ibv_cmd_modify_qp(ibqp, attr, attr_mask, &cmd, sizeof cmd);
	if (!ret && (attr_mask & IBV_QP_STATE) && attr->qp_state == IBV_QPS_RESET)
		reset_qp(qhp);
	pthread_spin_unlock(&qhp->lock);
	return ret;
}

int c4iw_destroy_qp(struct ibv_qp *ibqp)
{
	int ret;
	struct c4iw_qp *qhp = to_c4iw_qp(ibqp);
	struct c4iw_dev *dev = to_c4iw_dev(ibqp->context->device);

	PDBG("%s enter qp %p\n", __func__, ibqp);
	c4iw_flush_qp(qhp);

	ret = ibv_cmd_destroy_qp(ibqp);
	if (ret) {
		return ret;
	}
	if (t4_sq_onchip(&qhp->wq)) {
		qhp->wq.sq.ma_sync -= (A_PCIE_MA_SYNC & (c4iw_page_size - 1));
		munmap((void *)qhp->wq.sq.ma_sync, c4iw_page_size);
	}
	munmap(MASKED(qhp->wq.sq.udb), c4iw_page_size);
	munmap(qhp->wq.sq.queue, qhp->wq.sq.memsize);
	if (!qhp->srq) {
		munmap(MASKED(qhp->wq.rq.udb), c4iw_page_size);
		munmap(qhp->wq.rq.queue, qhp->wq.rq.memsize);
	}

	pthread_spin_lock(&dev->lock);
	dev->qpid2ptr[qhp->wq.sq.qid] = NULL;
	pthread_spin_unlock(&dev->lock);

	if (!qhp->srq)
		free(qhp->wq.rq.sw_rq);
	free(qhp->wq.sq.sw_sq);
	free(qhp);
	return 0;
}

int c4iw_query_qp(struct ibv_qp *ibqp, struct ibv_qp_attr *attr,
		  int attr_mask, struct ibv_qp_init_attr *init_attr)
{
	struct ibv_query_qp cmd;
	struct c4iw_qp *qhp = to_c4iw_qp(ibqp);
	int ret;

	if (t4_wq_in_error(&qhp->wq))
		c4iw_flush_qp(qhp);
	pthread_spin_lock(&qhp->lock);
	ret = ibv_cmd_query_qp(ibqp, attr, attr_mask, init_attr, &cmd, sizeof cmd);
	pthread_spin_unlock(&qhp->lock);
	return ret;
}

int c4iw_attach_mcast(struct ibv_qp *ibqp, const union ibv_gid *gid,
		      uint16_t lid)
{
	struct c4iw_qp *qhp = to_c4iw_qp(ibqp);
	int ret;

	if (t4_wq_in_error(&qhp->wq))
		c4iw_flush_qp(qhp);
	pthread_spin_lock(&qhp->lock);
	ret = ibv_cmd_attach_mcast(ibqp, gid, lid);
	pthread_spin_unlock(&qhp->lock);
	return ret;
}

int c4iw_detach_mcast(struct ibv_qp *ibqp, const union ibv_gid *gid,
		      uint16_t lid)
{
	struct c4iw_qp *qhp = to_c4iw_qp(ibqp);
	int ret;

	if (t4_wq_in_error(&qhp->wq))
		c4iw_flush_qp(qhp);
	pthread_spin_lock(&qhp->lock);
	ret = ibv_cmd_detach_mcast(ibqp, gid, lid);
	pthread_spin_unlock(&qhp->lock);
	return ret;
}

void c4iw_async_event(struct ibv_context *context,
		      struct ibv_async_event *event)
{
	PDBG("%s type %d obj %p\n", __func__, event->event_type,
	event->element.cq);

	switch (event->event_type) {
	case IBV_EVENT_CQ_ERR:
		break;
	case IBV_EVENT_QP_FATAL:
	case IBV_EVENT_QP_REQ_ERR:
	case IBV_EVENT_QP_ACCESS_ERR:
	case IBV_EVENT_PATH_MIG_ERR: {
		struct c4iw_qp *qhp = to_c4iw_qp(event->element.qp);
		c4iw_flush_qp(qhp);
		break;
	}
	case IBV_EVENT_SQ_DRAINED:
	case IBV_EVENT_PATH_MIG:
	case IBV_EVENT_COMM_EST:
	case IBV_EVENT_QP_LAST_WQE_REACHED:
	default:
		break;
	}
}
