4e8e6ee380
Fix a crash if the driver has to wait for a QP reference to be dropped when destroying the QP. Signed-off-by: Ethan Burns <eaburns@iol.unh.edu> Acked-by: Tom Tucker <tom@opengridcomputing.com> Signed-off-by: Roland Dreier <rolandd@cisco.com>
1008 lines
24 KiB
C
1008 lines
24 KiB
C
/*
|
|
* Copyright (c) 2004 Topspin Communications. All rights reserved.
|
|
* Copyright (c) 2005 Cisco Systems. All rights reserved.
|
|
* Copyright (c) 2005 Mellanox Technologies. All rights reserved.
|
|
* Copyright (c) 2004 Voltaire, Inc. All rights reserved.
|
|
* Copyright (c) 2005 Open Grid Computing, 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 <linux/delay.h>
|
|
|
|
#include "c2.h"
|
|
#include "c2_vq.h"
|
|
#include "c2_status.h"
|
|
|
|
#define C2_MAX_ORD_PER_QP 128
|
|
#define C2_MAX_IRD_PER_QP 128
|
|
|
|
#define C2_HINT_MAKE(q_index, hint_count) (((q_index) << 16) | hint_count)
|
|
#define C2_HINT_GET_INDEX(hint) (((hint) & 0x7FFF0000) >> 16)
|
|
#define C2_HINT_GET_COUNT(hint) ((hint) & 0x0000FFFF)
|
|
|
|
#define NO_SUPPORT -1
|
|
static const u8 c2_opcode[] = {
|
|
[IB_WR_SEND] = C2_WR_TYPE_SEND,
|
|
[IB_WR_SEND_WITH_IMM] = NO_SUPPORT,
|
|
[IB_WR_RDMA_WRITE] = C2_WR_TYPE_RDMA_WRITE,
|
|
[IB_WR_RDMA_WRITE_WITH_IMM] = NO_SUPPORT,
|
|
[IB_WR_RDMA_READ] = C2_WR_TYPE_RDMA_READ,
|
|
[IB_WR_ATOMIC_CMP_AND_SWP] = NO_SUPPORT,
|
|
[IB_WR_ATOMIC_FETCH_AND_ADD] = NO_SUPPORT,
|
|
};
|
|
|
|
static int to_c2_state(enum ib_qp_state ib_state)
|
|
{
|
|
switch (ib_state) {
|
|
case IB_QPS_RESET:
|
|
return C2_QP_STATE_IDLE;
|
|
case IB_QPS_RTS:
|
|
return C2_QP_STATE_RTS;
|
|
case IB_QPS_SQD:
|
|
return C2_QP_STATE_CLOSING;
|
|
case IB_QPS_SQE:
|
|
return C2_QP_STATE_CLOSING;
|
|
case IB_QPS_ERR:
|
|
return C2_QP_STATE_ERROR;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int to_ib_state(enum c2_qp_state c2_state)
|
|
{
|
|
switch (c2_state) {
|
|
case C2_QP_STATE_IDLE:
|
|
return IB_QPS_RESET;
|
|
case C2_QP_STATE_CONNECTING:
|
|
return IB_QPS_RTR;
|
|
case C2_QP_STATE_RTS:
|
|
return IB_QPS_RTS;
|
|
case C2_QP_STATE_CLOSING:
|
|
return IB_QPS_SQD;
|
|
case C2_QP_STATE_ERROR:
|
|
return IB_QPS_ERR;
|
|
case C2_QP_STATE_TERMINATE:
|
|
return IB_QPS_SQE;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static const char *to_ib_state_str(int ib_state)
|
|
{
|
|
static const char *state_str[] = {
|
|
"IB_QPS_RESET",
|
|
"IB_QPS_INIT",
|
|
"IB_QPS_RTR",
|
|
"IB_QPS_RTS",
|
|
"IB_QPS_SQD",
|
|
"IB_QPS_SQE",
|
|
"IB_QPS_ERR"
|
|
};
|
|
if (ib_state < IB_QPS_RESET ||
|
|
ib_state > IB_QPS_ERR)
|
|
return "<invalid IB QP state>";
|
|
|
|
ib_state -= IB_QPS_RESET;
|
|
return state_str[ib_state];
|
|
}
|
|
|
|
void c2_set_qp_state(struct c2_qp *qp, int c2_state)
|
|
{
|
|
int new_state = to_ib_state(c2_state);
|
|
|
|
pr_debug("%s: qp[%p] state modify %s --> %s\n",
|
|
__FUNCTION__,
|
|
qp,
|
|
to_ib_state_str(qp->state),
|
|
to_ib_state_str(new_state));
|
|
qp->state = new_state;
|
|
}
|
|
|
|
#define C2_QP_NO_ATTR_CHANGE 0xFFFFFFFF
|
|
|
|
int c2_qp_modify(struct c2_dev *c2dev, struct c2_qp *qp,
|
|
struct ib_qp_attr *attr, int attr_mask)
|
|
{
|
|
struct c2wr_qp_modify_req wr;
|
|
struct c2wr_qp_modify_rep *reply;
|
|
struct c2_vq_req *vq_req;
|
|
unsigned long flags;
|
|
u8 next_state;
|
|
int err;
|
|
|
|
pr_debug("%s:%d qp=%p, %s --> %s\n",
|
|
__FUNCTION__, __LINE__,
|
|
qp,
|
|
to_ib_state_str(qp->state),
|
|
to_ib_state_str(attr->qp_state));
|
|
|
|
vq_req = vq_req_alloc(c2dev);
|
|
if (!vq_req)
|
|
return -ENOMEM;
|
|
|
|
c2_wr_set_id(&wr, CCWR_QP_MODIFY);
|
|
wr.hdr.context = (unsigned long) vq_req;
|
|
wr.rnic_handle = c2dev->adapter_handle;
|
|
wr.qp_handle = qp->adapter_handle;
|
|
wr.ord = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
wr.ird = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
wr.sq_depth = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
wr.rq_depth = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
|
|
if (attr_mask & IB_QP_STATE) {
|
|
/* Ensure the state is valid */
|
|
if (attr->qp_state < 0 || attr->qp_state > IB_QPS_ERR) {
|
|
err = -EINVAL;
|
|
goto bail0;
|
|
}
|
|
|
|
wr.next_qp_state = cpu_to_be32(to_c2_state(attr->qp_state));
|
|
|
|
if (attr->qp_state == IB_QPS_ERR) {
|
|
spin_lock_irqsave(&qp->lock, flags);
|
|
if (qp->cm_id && qp->state == IB_QPS_RTS) {
|
|
pr_debug("Generating CLOSE event for QP-->ERR, "
|
|
"qp=%p, cm_id=%p\n",qp,qp->cm_id);
|
|
/* Generate an CLOSE event */
|
|
vq_req->cm_id = qp->cm_id;
|
|
vq_req->event = IW_CM_EVENT_CLOSE;
|
|
}
|
|
spin_unlock_irqrestore(&qp->lock, flags);
|
|
}
|
|
next_state = attr->qp_state;
|
|
|
|
} else if (attr_mask & IB_QP_CUR_STATE) {
|
|
|
|
if (attr->cur_qp_state != IB_QPS_RTR &&
|
|
attr->cur_qp_state != IB_QPS_RTS &&
|
|
attr->cur_qp_state != IB_QPS_SQD &&
|
|
attr->cur_qp_state != IB_QPS_SQE) {
|
|
err = -EINVAL;
|
|
goto bail0;
|
|
} else
|
|
wr.next_qp_state =
|
|
cpu_to_be32(to_c2_state(attr->cur_qp_state));
|
|
|
|
next_state = attr->cur_qp_state;
|
|
|
|
} else {
|
|
err = 0;
|
|
goto bail0;
|
|
}
|
|
|
|
/* reference the request struct */
|
|
vq_req_get(c2dev, vq_req);
|
|
|
|
err = vq_send_wr(c2dev, (union c2wr *) & wr);
|
|
if (err) {
|
|
vq_req_put(c2dev, vq_req);
|
|
goto bail0;
|
|
}
|
|
|
|
err = vq_wait_for_reply(c2dev, vq_req);
|
|
if (err)
|
|
goto bail0;
|
|
|
|
reply = (struct c2wr_qp_modify_rep *) (unsigned long) vq_req->reply_msg;
|
|
if (!reply) {
|
|
err = -ENOMEM;
|
|
goto bail0;
|
|
}
|
|
|
|
err = c2_errno(reply);
|
|
if (!err)
|
|
qp->state = next_state;
|
|
#ifdef DEBUG
|
|
else
|
|
pr_debug("%s: c2_errno=%d\n", __FUNCTION__, err);
|
|
#endif
|
|
/*
|
|
* If we're going to error and generating the event here, then
|
|
* we need to remove the reference because there will be no
|
|
* close event generated by the adapter
|
|
*/
|
|
spin_lock_irqsave(&qp->lock, flags);
|
|
if (vq_req->event==IW_CM_EVENT_CLOSE && qp->cm_id) {
|
|
qp->cm_id->rem_ref(qp->cm_id);
|
|
qp->cm_id = NULL;
|
|
}
|
|
spin_unlock_irqrestore(&qp->lock, flags);
|
|
|
|
vq_repbuf_free(c2dev, reply);
|
|
bail0:
|
|
vq_req_free(c2dev, vq_req);
|
|
|
|
pr_debug("%s:%d qp=%p, cur_state=%s\n",
|
|
__FUNCTION__, __LINE__,
|
|
qp,
|
|
to_ib_state_str(qp->state));
|
|
return err;
|
|
}
|
|
|
|
int c2_qp_set_read_limits(struct c2_dev *c2dev, struct c2_qp *qp,
|
|
int ord, int ird)
|
|
{
|
|
struct c2wr_qp_modify_req wr;
|
|
struct c2wr_qp_modify_rep *reply;
|
|
struct c2_vq_req *vq_req;
|
|
int err;
|
|
|
|
vq_req = vq_req_alloc(c2dev);
|
|
if (!vq_req)
|
|
return -ENOMEM;
|
|
|
|
c2_wr_set_id(&wr, CCWR_QP_MODIFY);
|
|
wr.hdr.context = (unsigned long) vq_req;
|
|
wr.rnic_handle = c2dev->adapter_handle;
|
|
wr.qp_handle = qp->adapter_handle;
|
|
wr.ord = cpu_to_be32(ord);
|
|
wr.ird = cpu_to_be32(ird);
|
|
wr.sq_depth = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
wr.rq_depth = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
wr.next_qp_state = cpu_to_be32(C2_QP_NO_ATTR_CHANGE);
|
|
|
|
/* reference the request struct */
|
|
vq_req_get(c2dev, vq_req);
|
|
|
|
err = vq_send_wr(c2dev, (union c2wr *) & wr);
|
|
if (err) {
|
|
vq_req_put(c2dev, vq_req);
|
|
goto bail0;
|
|
}
|
|
|
|
err = vq_wait_for_reply(c2dev, vq_req);
|
|
if (err)
|
|
goto bail0;
|
|
|
|
reply = (struct c2wr_qp_modify_rep *) (unsigned long)
|
|
vq_req->reply_msg;
|
|
if (!reply) {
|
|
err = -ENOMEM;
|
|
goto bail0;
|
|
}
|
|
|
|
err = c2_errno(reply);
|
|
vq_repbuf_free(c2dev, reply);
|
|
bail0:
|
|
vq_req_free(c2dev, vq_req);
|
|
return err;
|
|
}
|
|
|
|
static int destroy_qp(struct c2_dev *c2dev, struct c2_qp *qp)
|
|
{
|
|
struct c2_vq_req *vq_req;
|
|
struct c2wr_qp_destroy_req wr;
|
|
struct c2wr_qp_destroy_rep *reply;
|
|
unsigned long flags;
|
|
int err;
|
|
|
|
/*
|
|
* Allocate a verb request message
|
|
*/
|
|
vq_req = vq_req_alloc(c2dev);
|
|
if (!vq_req) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* Initialize the WR
|
|
*/
|
|
c2_wr_set_id(&wr, CCWR_QP_DESTROY);
|
|
wr.hdr.context = (unsigned long) vq_req;
|
|
wr.rnic_handle = c2dev->adapter_handle;
|
|
wr.qp_handle = qp->adapter_handle;
|
|
|
|
/*
|
|
* reference the request struct. dereferenced in the int handler.
|
|
*/
|
|
vq_req_get(c2dev, vq_req);
|
|
|
|
spin_lock_irqsave(&qp->lock, flags);
|
|
if (qp->cm_id && qp->state == IB_QPS_RTS) {
|
|
pr_debug("destroy_qp: generating CLOSE event for QP-->ERR, "
|
|
"qp=%p, cm_id=%p\n",qp,qp->cm_id);
|
|
/* Generate an CLOSE event */
|
|
vq_req->qp = qp;
|
|
vq_req->cm_id = qp->cm_id;
|
|
vq_req->event = IW_CM_EVENT_CLOSE;
|
|
}
|
|
spin_unlock_irqrestore(&qp->lock, flags);
|
|
|
|
/*
|
|
* Send WR to adapter
|
|
*/
|
|
err = vq_send_wr(c2dev, (union c2wr *) & wr);
|
|
if (err) {
|
|
vq_req_put(c2dev, vq_req);
|
|
goto bail0;
|
|
}
|
|
|
|
/*
|
|
* Wait for reply from adapter
|
|
*/
|
|
err = vq_wait_for_reply(c2dev, vq_req);
|
|
if (err) {
|
|
goto bail0;
|
|
}
|
|
|
|
/*
|
|
* Process reply
|
|
*/
|
|
reply = (struct c2wr_qp_destroy_rep *) (unsigned long) (vq_req->reply_msg);
|
|
if (!reply) {
|
|
err = -ENOMEM;
|
|
goto bail0;
|
|
}
|
|
|
|
spin_lock_irqsave(&qp->lock, flags);
|
|
if (qp->cm_id) {
|
|
qp->cm_id->rem_ref(qp->cm_id);
|
|
qp->cm_id = NULL;
|
|
}
|
|
spin_unlock_irqrestore(&qp->lock, flags);
|
|
|
|
vq_repbuf_free(c2dev, reply);
|
|
bail0:
|
|
vq_req_free(c2dev, vq_req);
|
|
return err;
|
|
}
|
|
|
|
static int c2_alloc_qpn(struct c2_dev *c2dev, struct c2_qp *qp)
|
|
{
|
|
int ret;
|
|
|
|
do {
|
|
spin_lock_irq(&c2dev->qp_table.lock);
|
|
ret = idr_get_new_above(&c2dev->qp_table.idr, qp,
|
|
c2dev->qp_table.last++, &qp->qpn);
|
|
spin_unlock_irq(&c2dev->qp_table.lock);
|
|
} while ((ret == -EAGAIN) &&
|
|
idr_pre_get(&c2dev->qp_table.idr, GFP_KERNEL));
|
|
return ret;
|
|
}
|
|
|
|
static void c2_free_qpn(struct c2_dev *c2dev, int qpn)
|
|
{
|
|
spin_lock_irq(&c2dev->qp_table.lock);
|
|
idr_remove(&c2dev->qp_table.idr, qpn);
|
|
spin_unlock_irq(&c2dev->qp_table.lock);
|
|
}
|
|
|
|
struct c2_qp *c2_find_qpn(struct c2_dev *c2dev, int qpn)
|
|
{
|
|
unsigned long flags;
|
|
struct c2_qp *qp;
|
|
|
|
spin_lock_irqsave(&c2dev->qp_table.lock, flags);
|
|
qp = idr_find(&c2dev->qp_table.idr, qpn);
|
|
spin_unlock_irqrestore(&c2dev->qp_table.lock, flags);
|
|
return qp;
|
|
}
|
|
|
|
int c2_alloc_qp(struct c2_dev *c2dev,
|
|
struct c2_pd *pd,
|
|
struct ib_qp_init_attr *qp_attrs, struct c2_qp *qp)
|
|
{
|
|
struct c2wr_qp_create_req wr;
|
|
struct c2wr_qp_create_rep *reply;
|
|
struct c2_vq_req *vq_req;
|
|
struct c2_cq *send_cq = to_c2cq(qp_attrs->send_cq);
|
|
struct c2_cq *recv_cq = to_c2cq(qp_attrs->recv_cq);
|
|
unsigned long peer_pa;
|
|
u32 q_size, msg_size, mmap_size;
|
|
void __iomem *mmap;
|
|
int err;
|
|
|
|
err = c2_alloc_qpn(c2dev, qp);
|
|
if (err)
|
|
return err;
|
|
qp->ibqp.qp_num = qp->qpn;
|
|
qp->ibqp.qp_type = IB_QPT_RC;
|
|
|
|
/* Allocate the SQ and RQ shared pointers */
|
|
qp->sq_mq.shared = c2_alloc_mqsp(c2dev, c2dev->kern_mqsp_pool,
|
|
&qp->sq_mq.shared_dma, GFP_KERNEL);
|
|
if (!qp->sq_mq.shared) {
|
|
err = -ENOMEM;
|
|
goto bail0;
|
|
}
|
|
|
|
qp->rq_mq.shared = c2_alloc_mqsp(c2dev, c2dev->kern_mqsp_pool,
|
|
&qp->rq_mq.shared_dma, GFP_KERNEL);
|
|
if (!qp->rq_mq.shared) {
|
|
err = -ENOMEM;
|
|
goto bail1;
|
|
}
|
|
|
|
/* Allocate the verbs request */
|
|
vq_req = vq_req_alloc(c2dev);
|
|
if (vq_req == NULL) {
|
|
err = -ENOMEM;
|
|
goto bail2;
|
|
}
|
|
|
|
/* Initialize the work request */
|
|
memset(&wr, 0, sizeof(wr));
|
|
c2_wr_set_id(&wr, CCWR_QP_CREATE);
|
|
wr.hdr.context = (unsigned long) vq_req;
|
|
wr.rnic_handle = c2dev->adapter_handle;
|
|
wr.sq_cq_handle = send_cq->adapter_handle;
|
|
wr.rq_cq_handle = recv_cq->adapter_handle;
|
|
wr.sq_depth = cpu_to_be32(qp_attrs->cap.max_send_wr + 1);
|
|
wr.rq_depth = cpu_to_be32(qp_attrs->cap.max_recv_wr + 1);
|
|
wr.srq_handle = 0;
|
|
wr.flags = cpu_to_be32(QP_RDMA_READ | QP_RDMA_WRITE | QP_MW_BIND |
|
|
QP_ZERO_STAG | QP_RDMA_READ_RESPONSE);
|
|
wr.send_sgl_depth = cpu_to_be32(qp_attrs->cap.max_send_sge);
|
|
wr.recv_sgl_depth = cpu_to_be32(qp_attrs->cap.max_recv_sge);
|
|
wr.rdma_write_sgl_depth = cpu_to_be32(qp_attrs->cap.max_send_sge);
|
|
wr.shared_sq_ht = cpu_to_be64(qp->sq_mq.shared_dma);
|
|
wr.shared_rq_ht = cpu_to_be64(qp->rq_mq.shared_dma);
|
|
wr.ord = cpu_to_be32(C2_MAX_ORD_PER_QP);
|
|
wr.ird = cpu_to_be32(C2_MAX_IRD_PER_QP);
|
|
wr.pd_id = pd->pd_id;
|
|
wr.user_context = (unsigned long) qp;
|
|
|
|
vq_req_get(c2dev, vq_req);
|
|
|
|
/* Send the WR to the adapter */
|
|
err = vq_send_wr(c2dev, (union c2wr *) & wr);
|
|
if (err) {
|
|
vq_req_put(c2dev, vq_req);
|
|
goto bail3;
|
|
}
|
|
|
|
/* Wait for the verb reply */
|
|
err = vq_wait_for_reply(c2dev, vq_req);
|
|
if (err) {
|
|
goto bail3;
|
|
}
|
|
|
|
/* Process the reply */
|
|
reply = (struct c2wr_qp_create_rep *) (unsigned long) (vq_req->reply_msg);
|
|
if (!reply) {
|
|
err = -ENOMEM;
|
|
goto bail3;
|
|
}
|
|
|
|
if ((err = c2_wr_get_result(reply)) != 0) {
|
|
goto bail4;
|
|
}
|
|
|
|
/* Fill in the kernel QP struct */
|
|
atomic_set(&qp->refcount, 1);
|
|
qp->adapter_handle = reply->qp_handle;
|
|
qp->state = IB_QPS_RESET;
|
|
qp->send_sgl_depth = qp_attrs->cap.max_send_sge;
|
|
qp->rdma_write_sgl_depth = qp_attrs->cap.max_send_sge;
|
|
qp->recv_sgl_depth = qp_attrs->cap.max_recv_sge;
|
|
init_waitqueue_head(&qp->wait);
|
|
|
|
/* Initialize the SQ MQ */
|
|
q_size = be32_to_cpu(reply->sq_depth);
|
|
msg_size = be32_to_cpu(reply->sq_msg_size);
|
|
peer_pa = c2dev->pa + be32_to_cpu(reply->sq_mq_start);
|
|
mmap_size = PAGE_ALIGN(sizeof(struct c2_mq_shared) + msg_size * q_size);
|
|
mmap = ioremap_nocache(peer_pa, mmap_size);
|
|
if (!mmap) {
|
|
err = -ENOMEM;
|
|
goto bail5;
|
|
}
|
|
|
|
c2_mq_req_init(&qp->sq_mq,
|
|
be32_to_cpu(reply->sq_mq_index),
|
|
q_size,
|
|
msg_size,
|
|
mmap + sizeof(struct c2_mq_shared), /* pool start */
|
|
mmap, /* peer */
|
|
C2_MQ_ADAPTER_TARGET);
|
|
|
|
/* Initialize the RQ mq */
|
|
q_size = be32_to_cpu(reply->rq_depth);
|
|
msg_size = be32_to_cpu(reply->rq_msg_size);
|
|
peer_pa = c2dev->pa + be32_to_cpu(reply->rq_mq_start);
|
|
mmap_size = PAGE_ALIGN(sizeof(struct c2_mq_shared) + msg_size * q_size);
|
|
mmap = ioremap_nocache(peer_pa, mmap_size);
|
|
if (!mmap) {
|
|
err = -ENOMEM;
|
|
goto bail6;
|
|
}
|
|
|
|
c2_mq_req_init(&qp->rq_mq,
|
|
be32_to_cpu(reply->rq_mq_index),
|
|
q_size,
|
|
msg_size,
|
|
mmap + sizeof(struct c2_mq_shared), /* pool start */
|
|
mmap, /* peer */
|
|
C2_MQ_ADAPTER_TARGET);
|
|
|
|
vq_repbuf_free(c2dev, reply);
|
|
vq_req_free(c2dev, vq_req);
|
|
|
|
return 0;
|
|
|
|
bail6:
|
|
iounmap(qp->sq_mq.peer);
|
|
bail5:
|
|
destroy_qp(c2dev, qp);
|
|
bail4:
|
|
vq_repbuf_free(c2dev, reply);
|
|
bail3:
|
|
vq_req_free(c2dev, vq_req);
|
|
bail2:
|
|
c2_free_mqsp(qp->rq_mq.shared);
|
|
bail1:
|
|
c2_free_mqsp(qp->sq_mq.shared);
|
|
bail0:
|
|
c2_free_qpn(c2dev, qp->qpn);
|
|
return err;
|
|
}
|
|
|
|
static inline void c2_lock_cqs(struct c2_cq *send_cq, struct c2_cq *recv_cq)
|
|
{
|
|
if (send_cq == recv_cq)
|
|
spin_lock_irq(&send_cq->lock);
|
|
else if (send_cq > recv_cq) {
|
|
spin_lock_irq(&send_cq->lock);
|
|
spin_lock_nested(&recv_cq->lock, SINGLE_DEPTH_NESTING);
|
|
} else {
|
|
spin_lock_irq(&recv_cq->lock);
|
|
spin_lock_nested(&send_cq->lock, SINGLE_DEPTH_NESTING);
|
|
}
|
|
}
|
|
|
|
static inline void c2_unlock_cqs(struct c2_cq *send_cq, struct c2_cq *recv_cq)
|
|
{
|
|
if (send_cq == recv_cq)
|
|
spin_unlock_irq(&send_cq->lock);
|
|
else if (send_cq > recv_cq) {
|
|
spin_unlock(&recv_cq->lock);
|
|
spin_unlock_irq(&send_cq->lock);
|
|
} else {
|
|
spin_unlock(&send_cq->lock);
|
|
spin_unlock_irq(&recv_cq->lock);
|
|
}
|
|
}
|
|
|
|
void c2_free_qp(struct c2_dev *c2dev, struct c2_qp *qp)
|
|
{
|
|
struct c2_cq *send_cq;
|
|
struct c2_cq *recv_cq;
|
|
|
|
send_cq = to_c2cq(qp->ibqp.send_cq);
|
|
recv_cq = to_c2cq(qp->ibqp.recv_cq);
|
|
|
|
/*
|
|
* Lock CQs here, so that CQ polling code can do QP lookup
|
|
* without taking a lock.
|
|
*/
|
|
c2_lock_cqs(send_cq, recv_cq);
|
|
c2_free_qpn(c2dev, qp->qpn);
|
|
c2_unlock_cqs(send_cq, recv_cq);
|
|
|
|
/*
|
|
* Destory qp in the rnic...
|
|
*/
|
|
destroy_qp(c2dev, qp);
|
|
|
|
/*
|
|
* Mark any unreaped CQEs as null and void.
|
|
*/
|
|
c2_cq_clean(c2dev, qp, send_cq->cqn);
|
|
if (send_cq != recv_cq)
|
|
c2_cq_clean(c2dev, qp, recv_cq->cqn);
|
|
/*
|
|
* Unmap the MQs and return the shared pointers
|
|
* to the message pool.
|
|
*/
|
|
iounmap(qp->sq_mq.peer);
|
|
iounmap(qp->rq_mq.peer);
|
|
c2_free_mqsp(qp->sq_mq.shared);
|
|
c2_free_mqsp(qp->rq_mq.shared);
|
|
|
|
atomic_dec(&qp->refcount);
|
|
wait_event(qp->wait, !atomic_read(&qp->refcount));
|
|
}
|
|
|
|
/*
|
|
* Function: move_sgl
|
|
*
|
|
* Description:
|
|
* Move an SGL from the user's work request struct into a CCIL Work Request
|
|
* message, swapping to WR byte order and ensure the total length doesn't
|
|
* overflow.
|
|
*
|
|
* IN:
|
|
* dst - ptr to CCIL Work Request message SGL memory.
|
|
* src - ptr to the consumers SGL memory.
|
|
*
|
|
* OUT: none
|
|
*
|
|
* Return:
|
|
* CCIL status codes.
|
|
*/
|
|
static int
|
|
move_sgl(struct c2_data_addr * dst, struct ib_sge *src, int count, u32 * p_len,
|
|
u8 * actual_count)
|
|
{
|
|
u32 tot = 0; /* running total */
|
|
u8 acount = 0; /* running total non-0 len sge's */
|
|
|
|
while (count > 0) {
|
|
/*
|
|
* If the addition of this SGE causes the
|
|
* total SGL length to exceed 2^32-1, then
|
|
* fail-n-bail.
|
|
*
|
|
* If the current total plus the next element length
|
|
* wraps, then it will go negative and be less than the
|
|
* current total...
|
|
*/
|
|
if ((tot + src->length) < tot) {
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* Bug: 1456 (as well as 1498 & 1643)
|
|
* Skip over any sge's supplied with len=0
|
|
*/
|
|
if (src->length) {
|
|
tot += src->length;
|
|
dst->stag = cpu_to_be32(src->lkey);
|
|
dst->to = cpu_to_be64(src->addr);
|
|
dst->length = cpu_to_be32(src->length);
|
|
dst++;
|
|
acount++;
|
|
}
|
|
src++;
|
|
count--;
|
|
}
|
|
|
|
if (acount == 0) {
|
|
/*
|
|
* Bug: 1476 (as well as 1498, 1456 and 1643)
|
|
* Setup the SGL in the WR to make it easier for the RNIC.
|
|
* This way, the FW doesn't have to deal with special cases.
|
|
* Setting length=0 should be sufficient.
|
|
*/
|
|
dst->stag = 0;
|
|
dst->to = 0;
|
|
dst->length = 0;
|
|
}
|
|
|
|
*p_len = tot;
|
|
*actual_count = acount;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: c2_activity (private function)
|
|
*
|
|
* Description:
|
|
* Post an mq index to the host->adapter activity fifo.
|
|
*
|
|
* IN:
|
|
* c2dev - ptr to c2dev structure
|
|
* mq_index - mq index to post
|
|
* shared - value most recently written to shared
|
|
*
|
|
* OUT:
|
|
*
|
|
* Return:
|
|
* none
|
|
*/
|
|
static inline void c2_activity(struct c2_dev *c2dev, u32 mq_index, u16 shared)
|
|
{
|
|
/*
|
|
* First read the register to see if the FIFO is full, and if so,
|
|
* spin until it's not. This isn't perfect -- there is no
|
|
* synchronization among the clients of the register, but in
|
|
* practice it prevents multiple CPU from hammering the bus
|
|
* with PCI RETRY. Note that when this does happen, the card
|
|
* cannot get on the bus and the card and system hang in a
|
|
* deadlock -- thus the need for this code. [TOT]
|
|
*/
|
|
while (readl(c2dev->regs + PCI_BAR0_ADAPTER_HINT) & 0x80000000)
|
|
udelay(10);
|
|
|
|
__raw_writel(C2_HINT_MAKE(mq_index, shared),
|
|
c2dev->regs + PCI_BAR0_ADAPTER_HINT);
|
|
}
|
|
|
|
/*
|
|
* Function: qp_wr_post
|
|
*
|
|
* Description:
|
|
* This in-line function allocates a MQ msg, then moves the host-copy of
|
|
* the completed WR into msg. Then it posts the message.
|
|
*
|
|
* IN:
|
|
* q - ptr to user MQ.
|
|
* wr - ptr to host-copy of the WR.
|
|
* qp - ptr to user qp
|
|
* size - Number of bytes to post. Assumed to be divisible by 4.
|
|
*
|
|
* OUT: none
|
|
*
|
|
* Return:
|
|
* CCIL status codes.
|
|
*/
|
|
static int qp_wr_post(struct c2_mq *q, union c2wr * wr, struct c2_qp *qp, u32 size)
|
|
{
|
|
union c2wr *msg;
|
|
|
|
msg = c2_mq_alloc(q);
|
|
if (msg == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
#ifdef CCMSGMAGIC
|
|
((c2wr_hdr_t *) wr)->magic = cpu_to_be32(CCWR_MAGIC);
|
|
#endif
|
|
|
|
/*
|
|
* Since all header fields in the WR are the same as the
|
|
* CQE, set the following so the adapter need not.
|
|
*/
|
|
c2_wr_set_result(wr, CCERR_PENDING);
|
|
|
|
/*
|
|
* Copy the wr down to the adapter
|
|
*/
|
|
memcpy((void *) msg, (void *) wr, size);
|
|
|
|
c2_mq_produce(q);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int c2_post_send(struct ib_qp *ibqp, struct ib_send_wr *ib_wr,
|
|
struct ib_send_wr **bad_wr)
|
|
{
|
|
struct c2_dev *c2dev = to_c2dev(ibqp->device);
|
|
struct c2_qp *qp = to_c2qp(ibqp);
|
|
union c2wr wr;
|
|
unsigned long lock_flags;
|
|
int err = 0;
|
|
|
|
u32 flags;
|
|
u32 tot_len;
|
|
u8 actual_sge_count;
|
|
u32 msg_size;
|
|
|
|
if (qp->state > IB_QPS_RTS)
|
|
return -EINVAL;
|
|
|
|
while (ib_wr) {
|
|
|
|
flags = 0;
|
|
wr.sqwr.sq_hdr.user_hdr.hdr.context = ib_wr->wr_id;
|
|
if (ib_wr->send_flags & IB_SEND_SIGNALED) {
|
|
flags |= SQ_SIGNALED;
|
|
}
|
|
|
|
switch (ib_wr->opcode) {
|
|
case IB_WR_SEND:
|
|
if (ib_wr->send_flags & IB_SEND_SOLICITED) {
|
|
c2_wr_set_id(&wr, C2_WR_TYPE_SEND_SE);
|
|
msg_size = sizeof(struct c2wr_send_req);
|
|
} else {
|
|
c2_wr_set_id(&wr, C2_WR_TYPE_SEND);
|
|
msg_size = sizeof(struct c2wr_send_req);
|
|
}
|
|
|
|
wr.sqwr.send.remote_stag = 0;
|
|
msg_size += sizeof(struct c2_data_addr) * ib_wr->num_sge;
|
|
if (ib_wr->num_sge > qp->send_sgl_depth) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
if (ib_wr->send_flags & IB_SEND_FENCE) {
|
|
flags |= SQ_READ_FENCE;
|
|
}
|
|
err = move_sgl((struct c2_data_addr *) & (wr.sqwr.send.data),
|
|
ib_wr->sg_list,
|
|
ib_wr->num_sge,
|
|
&tot_len, &actual_sge_count);
|
|
wr.sqwr.send.sge_len = cpu_to_be32(tot_len);
|
|
c2_wr_set_sge_count(&wr, actual_sge_count);
|
|
break;
|
|
case IB_WR_RDMA_WRITE:
|
|
c2_wr_set_id(&wr, C2_WR_TYPE_RDMA_WRITE);
|
|
msg_size = sizeof(struct c2wr_rdma_write_req) +
|
|
(sizeof(struct c2_data_addr) * ib_wr->num_sge);
|
|
if (ib_wr->num_sge > qp->rdma_write_sgl_depth) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
if (ib_wr->send_flags & IB_SEND_FENCE) {
|
|
flags |= SQ_READ_FENCE;
|
|
}
|
|
wr.sqwr.rdma_write.remote_stag =
|
|
cpu_to_be32(ib_wr->wr.rdma.rkey);
|
|
wr.sqwr.rdma_write.remote_to =
|
|
cpu_to_be64(ib_wr->wr.rdma.remote_addr);
|
|
err = move_sgl((struct c2_data_addr *)
|
|
& (wr.sqwr.rdma_write.data),
|
|
ib_wr->sg_list,
|
|
ib_wr->num_sge,
|
|
&tot_len, &actual_sge_count);
|
|
wr.sqwr.rdma_write.sge_len = cpu_to_be32(tot_len);
|
|
c2_wr_set_sge_count(&wr, actual_sge_count);
|
|
break;
|
|
case IB_WR_RDMA_READ:
|
|
c2_wr_set_id(&wr, C2_WR_TYPE_RDMA_READ);
|
|
msg_size = sizeof(struct c2wr_rdma_read_req);
|
|
|
|
/* IWarp only suppots 1 sge for RDMA reads */
|
|
if (ib_wr->num_sge > 1) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Move the local and remote stag/to/len into the WR.
|
|
*/
|
|
wr.sqwr.rdma_read.local_stag =
|
|
cpu_to_be32(ib_wr->sg_list->lkey);
|
|
wr.sqwr.rdma_read.local_to =
|
|
cpu_to_be64(ib_wr->sg_list->addr);
|
|
wr.sqwr.rdma_read.remote_stag =
|
|
cpu_to_be32(ib_wr->wr.rdma.rkey);
|
|
wr.sqwr.rdma_read.remote_to =
|
|
cpu_to_be64(ib_wr->wr.rdma.remote_addr);
|
|
wr.sqwr.rdma_read.length =
|
|
cpu_to_be32(ib_wr->sg_list->length);
|
|
break;
|
|
default:
|
|
/* error */
|
|
msg_size = 0;
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If we had an error on the last wr build, then
|
|
* break out. Possible errors include bogus WR
|
|
* type, and a bogus SGL length...
|
|
*/
|
|
if (err) {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Store flags
|
|
*/
|
|
c2_wr_set_flags(&wr, flags);
|
|
|
|
/*
|
|
* Post the puppy!
|
|
*/
|
|
spin_lock_irqsave(&qp->lock, lock_flags);
|
|
err = qp_wr_post(&qp->sq_mq, &wr, qp, msg_size);
|
|
if (err) {
|
|
spin_unlock_irqrestore(&qp->lock, lock_flags);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Enqueue mq index to activity FIFO.
|
|
*/
|
|
c2_activity(c2dev, qp->sq_mq.index, qp->sq_mq.hint_count);
|
|
spin_unlock_irqrestore(&qp->lock, lock_flags);
|
|
|
|
ib_wr = ib_wr->next;
|
|
}
|
|
|
|
if (err)
|
|
*bad_wr = ib_wr;
|
|
return err;
|
|
}
|
|
|
|
int c2_post_receive(struct ib_qp *ibqp, struct ib_recv_wr *ib_wr,
|
|
struct ib_recv_wr **bad_wr)
|
|
{
|
|
struct c2_dev *c2dev = to_c2dev(ibqp->device);
|
|
struct c2_qp *qp = to_c2qp(ibqp);
|
|
union c2wr wr;
|
|
unsigned long lock_flags;
|
|
int err = 0;
|
|
|
|
if (qp->state > IB_QPS_RTS)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Try and post each work request
|
|
*/
|
|
while (ib_wr) {
|
|
u32 tot_len;
|
|
u8 actual_sge_count;
|
|
|
|
if (ib_wr->num_sge > qp->recv_sgl_depth) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Create local host-copy of the WR
|
|
*/
|
|
wr.rqwr.rq_hdr.user_hdr.hdr.context = ib_wr->wr_id;
|
|
c2_wr_set_id(&wr, CCWR_RECV);
|
|
c2_wr_set_flags(&wr, 0);
|
|
|
|
/* sge_count is limited to eight bits. */
|
|
BUG_ON(ib_wr->num_sge >= 256);
|
|
err = move_sgl((struct c2_data_addr *) & (wr.rqwr.data),
|
|
ib_wr->sg_list,
|
|
ib_wr->num_sge, &tot_len, &actual_sge_count);
|
|
c2_wr_set_sge_count(&wr, actual_sge_count);
|
|
|
|
/*
|
|
* If we had an error on the last wr build, then
|
|
* break out. Possible errors include bogus WR
|
|
* type, and a bogus SGL length...
|
|
*/
|
|
if (err) {
|
|
break;
|
|
}
|
|
|
|
spin_lock_irqsave(&qp->lock, lock_flags);
|
|
err = qp_wr_post(&qp->rq_mq, &wr, qp, qp->rq_mq.msg_size);
|
|
if (err) {
|
|
spin_unlock_irqrestore(&qp->lock, lock_flags);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Enqueue mq index to activity FIFO
|
|
*/
|
|
c2_activity(c2dev, qp->rq_mq.index, qp->rq_mq.hint_count);
|
|
spin_unlock_irqrestore(&qp->lock, lock_flags);
|
|
|
|
ib_wr = ib_wr->next;
|
|
}
|
|
|
|
if (err)
|
|
*bad_wr = ib_wr;
|
|
return err;
|
|
}
|
|
|
|
void __devinit c2_init_qp_table(struct c2_dev *c2dev)
|
|
{
|
|
spin_lock_init(&c2dev->qp_table.lock);
|
|
idr_init(&c2dev->qp_table.idr);
|
|
}
|
|
|
|
void __devexit c2_cleanup_qp_table(struct c2_dev *c2dev)
|
|
{
|
|
idr_destroy(&c2dev->qp_table.idr);
|
|
}
|