mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
11 KiB
443 lines
11 KiB
/* |
|
* Copyright(c) 2016 Intel Corporation. |
|
* |
|
* This file is provided under a dual BSD/GPLv2 license. When using or |
|
* redistributing this file, you may do so under either license. |
|
* |
|
* GPL LICENSE SUMMARY |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of version 2 of the GNU General Public License as |
|
* published by the Free Software Foundation. |
|
* |
|
* This program is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* General Public License for more details. |
|
* |
|
* BSD LICENSE |
|
* |
|
* 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. |
|
* - Neither the name of Intel Corporation nor the names of its |
|
* contributors may be used to endorse or promote products derived |
|
* from this software without specific prior written permission. |
|
* |
|
* 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 |
|
* OWNER 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 <linux/slab.h> |
|
#include <linux/sched.h> |
|
#include <linux/rculist.h> |
|
#include <rdma/rdma_vt.h> |
|
#include <rdma/rdmavt_qp.h> |
|
|
|
#include "mcast.h" |
|
|
|
/** |
|
* rvt_driver_mcast_init - init resources for multicast |
|
* @rdi: rvt dev struct |
|
* |
|
* This is per device that registers with rdmavt |
|
*/ |
|
void rvt_driver_mcast_init(struct rvt_dev_info *rdi) |
|
{ |
|
/* |
|
* Anything that needs setup for multicast on a per driver or per rdi |
|
* basis should be done in here. |
|
*/ |
|
spin_lock_init(&rdi->n_mcast_grps_lock); |
|
} |
|
|
|
/** |
|
* rvt_mcast_qp_alloc - alloc a struct to link a QP to mcast GID struct |
|
* @qp: the QP to link |
|
*/ |
|
static struct rvt_mcast_qp *rvt_mcast_qp_alloc(struct rvt_qp *qp) |
|
{ |
|
struct rvt_mcast_qp *mqp; |
|
|
|
mqp = kmalloc(sizeof(*mqp), GFP_KERNEL); |
|
if (!mqp) |
|
goto bail; |
|
|
|
mqp->qp = qp; |
|
rvt_get_qp(qp); |
|
|
|
bail: |
|
return mqp; |
|
} |
|
|
|
static void rvt_mcast_qp_free(struct rvt_mcast_qp *mqp) |
|
{ |
|
struct rvt_qp *qp = mqp->qp; |
|
|
|
/* Notify hfi1_destroy_qp() if it is waiting. */ |
|
rvt_put_qp(qp); |
|
|
|
kfree(mqp); |
|
} |
|
|
|
/** |
|
* rvt_mcast_alloc - allocate the multicast GID structure |
|
* @mgid: the multicast GID |
|
* @lid: the muilticast LID (host order) |
|
* |
|
* A list of QPs will be attached to this structure. |
|
*/ |
|
static struct rvt_mcast *rvt_mcast_alloc(union ib_gid *mgid, u16 lid) |
|
{ |
|
struct rvt_mcast *mcast; |
|
|
|
mcast = kzalloc(sizeof(*mcast), GFP_KERNEL); |
|
if (!mcast) |
|
goto bail; |
|
|
|
mcast->mcast_addr.mgid = *mgid; |
|
mcast->mcast_addr.lid = lid; |
|
|
|
INIT_LIST_HEAD(&mcast->qp_list); |
|
init_waitqueue_head(&mcast->wait); |
|
atomic_set(&mcast->refcount, 0); |
|
|
|
bail: |
|
return mcast; |
|
} |
|
|
|
static void rvt_mcast_free(struct rvt_mcast *mcast) |
|
{ |
|
struct rvt_mcast_qp *p, *tmp; |
|
|
|
list_for_each_entry_safe(p, tmp, &mcast->qp_list, list) |
|
rvt_mcast_qp_free(p); |
|
|
|
kfree(mcast); |
|
} |
|
|
|
/** |
|
* rvt_mcast_find - search the global table for the given multicast GID/LID |
|
* NOTE: It is valid to have 1 MLID with multiple MGIDs. It is not valid |
|
* to have 1 MGID with multiple MLIDs. |
|
* @ibp: the IB port structure |
|
* @mgid: the multicast GID to search for |
|
* @lid: the multicast LID portion of the multicast address (host order) |
|
* |
|
* The caller is responsible for decrementing the reference count if found. |
|
* |
|
* Return: NULL if not found. |
|
*/ |
|
struct rvt_mcast *rvt_mcast_find(struct rvt_ibport *ibp, union ib_gid *mgid, |
|
u16 lid) |
|
{ |
|
struct rb_node *n; |
|
unsigned long flags; |
|
struct rvt_mcast *found = NULL; |
|
|
|
spin_lock_irqsave(&ibp->lock, flags); |
|
n = ibp->mcast_tree.rb_node; |
|
while (n) { |
|
int ret; |
|
struct rvt_mcast *mcast; |
|
|
|
mcast = rb_entry(n, struct rvt_mcast, rb_node); |
|
|
|
ret = memcmp(mgid->raw, mcast->mcast_addr.mgid.raw, |
|
sizeof(*mgid)); |
|
if (ret < 0) { |
|
n = n->rb_left; |
|
} else if (ret > 0) { |
|
n = n->rb_right; |
|
} else { |
|
/* MGID/MLID must match */ |
|
if (mcast->mcast_addr.lid == lid) { |
|
atomic_inc(&mcast->refcount); |
|
found = mcast; |
|
} |
|
break; |
|
} |
|
} |
|
spin_unlock_irqrestore(&ibp->lock, flags); |
|
return found; |
|
} |
|
EXPORT_SYMBOL(rvt_mcast_find); |
|
|
|
/* |
|
* rvt_mcast_add - insert mcast GID into table and attach QP struct |
|
* @mcast: the mcast GID table |
|
* @mqp: the QP to attach |
|
* |
|
* Return: zero if both were added. Return EEXIST if the GID was already in |
|
* the table but the QP was added. Return ESRCH if the QP was already |
|
* attached and neither structure was added. Return EINVAL if the MGID was |
|
* found, but the MLID did NOT match. |
|
*/ |
|
static int rvt_mcast_add(struct rvt_dev_info *rdi, struct rvt_ibport *ibp, |
|
struct rvt_mcast *mcast, struct rvt_mcast_qp *mqp) |
|
{ |
|
struct rb_node **n = &ibp->mcast_tree.rb_node; |
|
struct rb_node *pn = NULL; |
|
int ret; |
|
|
|
spin_lock_irq(&ibp->lock); |
|
|
|
while (*n) { |
|
struct rvt_mcast *tmcast; |
|
struct rvt_mcast_qp *p; |
|
|
|
pn = *n; |
|
tmcast = rb_entry(pn, struct rvt_mcast, rb_node); |
|
|
|
ret = memcmp(mcast->mcast_addr.mgid.raw, |
|
tmcast->mcast_addr.mgid.raw, |
|
sizeof(mcast->mcast_addr.mgid)); |
|
if (ret < 0) { |
|
n = &pn->rb_left; |
|
continue; |
|
} |
|
if (ret > 0) { |
|
n = &pn->rb_right; |
|
continue; |
|
} |
|
|
|
if (tmcast->mcast_addr.lid != mcast->mcast_addr.lid) { |
|
ret = EINVAL; |
|
goto bail; |
|
} |
|
|
|
/* Search the QP list to see if this is already there. */ |
|
list_for_each_entry_rcu(p, &tmcast->qp_list, list) { |
|
if (p->qp == mqp->qp) { |
|
ret = ESRCH; |
|
goto bail; |
|
} |
|
} |
|
if (tmcast->n_attached == |
|
rdi->dparms.props.max_mcast_qp_attach) { |
|
ret = ENOMEM; |
|
goto bail; |
|
} |
|
|
|
tmcast->n_attached++; |
|
|
|
list_add_tail_rcu(&mqp->list, &tmcast->qp_list); |
|
ret = EEXIST; |
|
goto bail; |
|
} |
|
|
|
spin_lock(&rdi->n_mcast_grps_lock); |
|
if (rdi->n_mcast_grps_allocated == rdi->dparms.props.max_mcast_grp) { |
|
spin_unlock(&rdi->n_mcast_grps_lock); |
|
ret = ENOMEM; |
|
goto bail; |
|
} |
|
|
|
rdi->n_mcast_grps_allocated++; |
|
spin_unlock(&rdi->n_mcast_grps_lock); |
|
|
|
mcast->n_attached++; |
|
|
|
list_add_tail_rcu(&mqp->list, &mcast->qp_list); |
|
|
|
atomic_inc(&mcast->refcount); |
|
rb_link_node(&mcast->rb_node, pn, n); |
|
rb_insert_color(&mcast->rb_node, &ibp->mcast_tree); |
|
|
|
ret = 0; |
|
|
|
bail: |
|
spin_unlock_irq(&ibp->lock); |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* rvt_attach_mcast - attach a qp to a multicast group |
|
* @ibqp: Infiniband qp |
|
* @gid: multicast guid |
|
* @lid: multicast lid |
|
* |
|
* Return: 0 on success |
|
*/ |
|
int rvt_attach_mcast(struct ib_qp *ibqp, union ib_gid *gid, u16 lid) |
|
{ |
|
struct rvt_qp *qp = ibqp_to_rvtqp(ibqp); |
|
struct rvt_dev_info *rdi = ib_to_rvt(ibqp->device); |
|
struct rvt_ibport *ibp = rdi->ports[qp->port_num - 1]; |
|
struct rvt_mcast *mcast; |
|
struct rvt_mcast_qp *mqp; |
|
int ret = -ENOMEM; |
|
|
|
if (ibqp->qp_num <= 1 || qp->state == IB_QPS_RESET) |
|
return -EINVAL; |
|
|
|
/* |
|
* Allocate data structures since its better to do this outside of |
|
* spin locks and it will most likely be needed. |
|
*/ |
|
mcast = rvt_mcast_alloc(gid, lid); |
|
if (!mcast) |
|
return -ENOMEM; |
|
|
|
mqp = rvt_mcast_qp_alloc(qp); |
|
if (!mqp) |
|
goto bail_mcast; |
|
|
|
switch (rvt_mcast_add(rdi, ibp, mcast, mqp)) { |
|
case ESRCH: |
|
/* Neither was used: OK to attach the same QP twice. */ |
|
ret = 0; |
|
goto bail_mqp; |
|
case EEXIST: /* The mcast wasn't used */ |
|
ret = 0; |
|
goto bail_mcast; |
|
case ENOMEM: |
|
/* Exceeded the maximum number of mcast groups. */ |
|
ret = -ENOMEM; |
|
goto bail_mqp; |
|
case EINVAL: |
|
/* Invalid MGID/MLID pair */ |
|
ret = -EINVAL; |
|
goto bail_mqp; |
|
default: |
|
break; |
|
} |
|
|
|
return 0; |
|
|
|
bail_mqp: |
|
rvt_mcast_qp_free(mqp); |
|
|
|
bail_mcast: |
|
rvt_mcast_free(mcast); |
|
|
|
return ret; |
|
} |
|
|
|
/** |
|
* rvt_detach_mcast - remove a qp from a multicast group |
|
* @ibqp: Infiniband qp |
|
* @gid: multicast guid |
|
* @lid: multicast lid |
|
* |
|
* Return: 0 on success |
|
*/ |
|
int rvt_detach_mcast(struct ib_qp *ibqp, union ib_gid *gid, u16 lid) |
|
{ |
|
struct rvt_qp *qp = ibqp_to_rvtqp(ibqp); |
|
struct rvt_dev_info *rdi = ib_to_rvt(ibqp->device); |
|
struct rvt_ibport *ibp = rdi->ports[qp->port_num - 1]; |
|
struct rvt_mcast *mcast = NULL; |
|
struct rvt_mcast_qp *p, *tmp, *delp = NULL; |
|
struct rb_node *n; |
|
int last = 0; |
|
int ret = 0; |
|
|
|
if (ibqp->qp_num <= 1) |
|
return -EINVAL; |
|
|
|
spin_lock_irq(&ibp->lock); |
|
|
|
/* Find the GID in the mcast table. */ |
|
n = ibp->mcast_tree.rb_node; |
|
while (1) { |
|
if (!n) { |
|
spin_unlock_irq(&ibp->lock); |
|
return -EINVAL; |
|
} |
|
|
|
mcast = rb_entry(n, struct rvt_mcast, rb_node); |
|
ret = memcmp(gid->raw, mcast->mcast_addr.mgid.raw, |
|
sizeof(*gid)); |
|
if (ret < 0) { |
|
n = n->rb_left; |
|
} else if (ret > 0) { |
|
n = n->rb_right; |
|
} else { |
|
/* MGID/MLID must match */ |
|
if (mcast->mcast_addr.lid != lid) { |
|
spin_unlock_irq(&ibp->lock); |
|
return -EINVAL; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/* Search the QP list. */ |
|
list_for_each_entry_safe(p, tmp, &mcast->qp_list, list) { |
|
if (p->qp != qp) |
|
continue; |
|
/* |
|
* We found it, so remove it, but don't poison the forward |
|
* link until we are sure there are no list walkers. |
|
*/ |
|
list_del_rcu(&p->list); |
|
mcast->n_attached--; |
|
delp = p; |
|
|
|
/* If this was the last attached QP, remove the GID too. */ |
|
if (list_empty(&mcast->qp_list)) { |
|
rb_erase(&mcast->rb_node, &ibp->mcast_tree); |
|
last = 1; |
|
} |
|
break; |
|
} |
|
|
|
spin_unlock_irq(&ibp->lock); |
|
/* QP not attached */ |
|
if (!delp) |
|
return -EINVAL; |
|
|
|
/* |
|
* Wait for any list walkers to finish before freeing the |
|
* list element. |
|
*/ |
|
wait_event(mcast->wait, atomic_read(&mcast->refcount) <= 1); |
|
rvt_mcast_qp_free(delp); |
|
|
|
if (last) { |
|
atomic_dec(&mcast->refcount); |
|
wait_event(mcast->wait, !atomic_read(&mcast->refcount)); |
|
rvt_mcast_free(mcast); |
|
spin_lock_irq(&rdi->n_mcast_grps_lock); |
|
rdi->n_mcast_grps_allocated--; |
|
spin_unlock_irq(&rdi->n_mcast_grps_lock); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* rvt_mcast_tree_empty - determine if any qps are attached to any mcast group |
|
* @rdi: rvt dev struct |
|
* |
|
* Return: in use count |
|
*/ |
|
int rvt_mcast_tree_empty(struct rvt_dev_info *rdi) |
|
{ |
|
int i; |
|
int in_use = 0; |
|
|
|
for (i = 0; i < rdi->dparms.nports; i++) |
|
if (rdi->ports[i]->mcast_tree.rb_node) |
|
in_use++; |
|
return in_use; |
|
}
|
|
|