mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2025-01-14 18:36:49 +07:00
69911416d8
Commit a9c8ba5884
("IPoIB: Fix usage of uninitialized multicast
objects") added a new flag MCAST_JOIN_STARTED, but was not very strict
in how it was used. We didn't always initialize the completion struct
before we set the flag, and we didn't always call complete on the
completion struct from all paths that complete it. And when we did
complete it, sometimes we continued to touch the mcast entry after
the completion, opening us up to possible use after free issues.
This made it less than totally effective, and certainly made its use
confusing. And in the flush function we would use the presence of this
flag to signal that we should wait on the completion struct, but we never
cleared this flag, ever.
In order to make things clearer and aid in resolving the rtnl deadlock
bug I've been chasing, I cleaned this up a bit.
1) Remove the MCAST_JOIN_STARTED flag entirely
2) Change MCAST_FLAG_BUSY so it now only means a join is in-flight
3) Test mcast->mc directly to see if we have completed
ib_sa_join_multicast (using IS_ERR_OR_NULL)
4) Make sure that before setting MCAST_FLAG_BUSY we always initialize
the mcast->done completion struct
5) Make sure that before calling complete(&mcast->done), we always clear
the MCAST_FLAG_BUSY bit
6) Take the mcast_mutex before we call ib_sa_multicast_join and also
take the mutex in our join callback. This forces
ib_sa_multicast_join to return and set mcast->mc before we process
the callback. This way, our callback can safely clear mcast->mc
if there is an error on the join and we will do the right thing as
a result in mcast_dev_flush.
7) Because we need the mutex to synchronize mcast->mc, we can no
longer call mcast_sendonly_join directly from mcast_send and
instead must add sendonly join processing to the mcast_join_task
8) Make MCAST_RUN mean that we have a working mcast subsystem, not that
we have a running task. We know when we need to reschedule our
join task thread and don't need a flag to tell us.
9) Add a helper for rescheduling the join task thread
A number of different races are resolved with these changes. These
races existed with the old MCAST_FLAG_BUSY usage, the
MCAST_JOIN_STARTED flag was an attempt to address them, and while it
helped, a determined effort could still trip things up.
One race looks something like this:
Thread 1 Thread 2
ib_sa_join_multicast (as part of running restart mcast task)
alloc member
call callback
ifconfig ib0 down
wait_for_completion
callback call completes
wait_for_completion in
mcast_dev_flush completes
mcast->mc is PTR_ERR_OR_NULL
so we skip ib_sa_leave_multicast
return from callback
return from ib_sa_join_multicast
set mcast->mc = return from ib_sa_multicast
We now have a permanently unbalanced join/leave issue that trips up the
refcounting in core/multicast.c
Another like this:
Thread 1 Thread 2 Thread 3
ib_sa_multicast_join
ifconfig ib0 down
priv->broadcast = NULL
join_complete
wait_for_completion
mcast->mc is not yet set, so don't clear
return from ib_sa_join_multicast and set mcast->mc
complete
return -EAGAIN (making mcast->mc invalid)
call ib_sa_multicast_leave
on invalid mcast->mc, hang
forever
By holding the mutex around ib_sa_multicast_join and taking the mutex
early in the callback, we force mcast->mc to be valid at the time we
run the callback. This allows us to clear mcast->mc if there is an
error and the join is going to fail. We do this before we complete
the mcast. In this way, mcast_dev_flush always sees consistent state
in regards to mcast->mc membership at the time that the
wait_for_completion() returns.
Signed-off-by: Doug Ledford <dledford@redhat.com>
1080 lines
29 KiB
C
1080 lines
29 KiB
C
/*
|
|
* Copyright (c) 2004, 2005 Topspin Communications. All rights reserved.
|
|
* Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
|
|
* Copyright (c) 2004 Voltaire, 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/skbuff.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/in.h>
|
|
#include <linux/igmp.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <net/dst.h>
|
|
|
|
#include "ipoib.h"
|
|
|
|
#ifdef CONFIG_INFINIBAND_IPOIB_DEBUG
|
|
static int mcast_debug_level;
|
|
|
|
module_param(mcast_debug_level, int, 0644);
|
|
MODULE_PARM_DESC(mcast_debug_level,
|
|
"Enable multicast debug tracing if > 0");
|
|
#endif
|
|
|
|
static DEFINE_MUTEX(mcast_mutex);
|
|
|
|
struct ipoib_mcast_iter {
|
|
struct net_device *dev;
|
|
union ib_gid mgid;
|
|
unsigned long created;
|
|
unsigned int queuelen;
|
|
unsigned int complete;
|
|
unsigned int send_only;
|
|
};
|
|
|
|
/*
|
|
* This should be called with the mcast_mutex held
|
|
*/
|
|
static void __ipoib_mcast_schedule_join_thread(struct ipoib_dev_priv *priv,
|
|
struct ipoib_mcast *mcast,
|
|
bool delay)
|
|
{
|
|
if (!test_bit(IPOIB_MCAST_RUN, &priv->flags))
|
|
return;
|
|
|
|
/*
|
|
* We will be scheduling *something*, so cancel whatever is
|
|
* currently scheduled first
|
|
*/
|
|
cancel_delayed_work(&priv->mcast_task);
|
|
if (mcast && delay) {
|
|
/*
|
|
* We had a failure and want to schedule a retry later
|
|
*/
|
|
mcast->backoff *= 2;
|
|
if (mcast->backoff > IPOIB_MAX_BACKOFF_SECONDS)
|
|
mcast->backoff = IPOIB_MAX_BACKOFF_SECONDS;
|
|
mcast->delay_until = jiffies + (mcast->backoff * HZ);
|
|
/*
|
|
* Mark this mcast for its delay, but restart the
|
|
* task immediately. The join task will make sure to
|
|
* clear out all entries without delays, and then
|
|
* schedule itself to run again when the earliest
|
|
* delay expires
|
|
*/
|
|
queue_delayed_work(priv->wq, &priv->mcast_task, 0);
|
|
} else if (delay) {
|
|
/*
|
|
* Special case of retrying after a failure to
|
|
* allocate the broadcast multicast group, wait
|
|
* 1 second and try again
|
|
*/
|
|
queue_delayed_work(priv->wq, &priv->mcast_task, HZ);
|
|
} else
|
|
queue_delayed_work(priv->wq, &priv->mcast_task, 0);
|
|
}
|
|
|
|
static void ipoib_mcast_free(struct ipoib_mcast *mcast)
|
|
{
|
|
struct net_device *dev = mcast->dev;
|
|
int tx_dropped = 0;
|
|
|
|
ipoib_dbg_mcast(netdev_priv(dev), "deleting multicast group %pI6\n",
|
|
mcast->mcmember.mgid.raw);
|
|
|
|
/* remove all neigh connected to this mcast */
|
|
ipoib_del_neighs_by_gid(dev, mcast->mcmember.mgid.raw);
|
|
|
|
if (mcast->ah)
|
|
ipoib_put_ah(mcast->ah);
|
|
|
|
while (!skb_queue_empty(&mcast->pkt_queue)) {
|
|
++tx_dropped;
|
|
dev_kfree_skb_any(skb_dequeue(&mcast->pkt_queue));
|
|
}
|
|
|
|
netif_tx_lock_bh(dev);
|
|
dev->stats.tx_dropped += tx_dropped;
|
|
netif_tx_unlock_bh(dev);
|
|
|
|
kfree(mcast);
|
|
}
|
|
|
|
static struct ipoib_mcast *ipoib_mcast_alloc(struct net_device *dev,
|
|
int can_sleep)
|
|
{
|
|
struct ipoib_mcast *mcast;
|
|
|
|
mcast = kzalloc(sizeof *mcast, can_sleep ? GFP_KERNEL : GFP_ATOMIC);
|
|
if (!mcast)
|
|
return NULL;
|
|
|
|
mcast->dev = dev;
|
|
mcast->created = jiffies;
|
|
mcast->delay_until = jiffies;
|
|
mcast->backoff = 1;
|
|
|
|
INIT_LIST_HEAD(&mcast->list);
|
|
INIT_LIST_HEAD(&mcast->neigh_list);
|
|
skb_queue_head_init(&mcast->pkt_queue);
|
|
|
|
return mcast;
|
|
}
|
|
|
|
static struct ipoib_mcast *__ipoib_mcast_find(struct net_device *dev, void *mgid)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct rb_node *n = priv->multicast_tree.rb_node;
|
|
|
|
while (n) {
|
|
struct ipoib_mcast *mcast;
|
|
int ret;
|
|
|
|
mcast = rb_entry(n, struct ipoib_mcast, rb_node);
|
|
|
|
ret = memcmp(mgid, mcast->mcmember.mgid.raw,
|
|
sizeof (union ib_gid));
|
|
if (ret < 0)
|
|
n = n->rb_left;
|
|
else if (ret > 0)
|
|
n = n->rb_right;
|
|
else
|
|
return mcast;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int __ipoib_mcast_add(struct net_device *dev, struct ipoib_mcast *mcast)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct rb_node **n = &priv->multicast_tree.rb_node, *pn = NULL;
|
|
|
|
while (*n) {
|
|
struct ipoib_mcast *tmcast;
|
|
int ret;
|
|
|
|
pn = *n;
|
|
tmcast = rb_entry(pn, struct ipoib_mcast, rb_node);
|
|
|
|
ret = memcmp(mcast->mcmember.mgid.raw, tmcast->mcmember.mgid.raw,
|
|
sizeof (union ib_gid));
|
|
if (ret < 0)
|
|
n = &pn->rb_left;
|
|
else if (ret > 0)
|
|
n = &pn->rb_right;
|
|
else
|
|
return -EEXIST;
|
|
}
|
|
|
|
rb_link_node(&mcast->rb_node, pn, n);
|
|
rb_insert_color(&mcast->rb_node, &priv->multicast_tree);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipoib_mcast_join_finish(struct ipoib_mcast *mcast,
|
|
struct ib_sa_mcmember_rec *mcmember)
|
|
{
|
|
struct net_device *dev = mcast->dev;
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct ipoib_ah *ah;
|
|
int ret;
|
|
int set_qkey = 0;
|
|
|
|
mcast->mcmember = *mcmember;
|
|
|
|
/* Set the multicast MTU and cached Q_Key before we attach if it's
|
|
* the broadcast group.
|
|
*/
|
|
if (!memcmp(mcast->mcmember.mgid.raw, priv->dev->broadcast + 4,
|
|
sizeof (union ib_gid))) {
|
|
spin_lock_irq(&priv->lock);
|
|
if (!priv->broadcast) {
|
|
spin_unlock_irq(&priv->lock);
|
|
return -EAGAIN;
|
|
}
|
|
priv->mcast_mtu = IPOIB_UD_MTU(ib_mtu_enum_to_int(priv->broadcast->mcmember.mtu));
|
|
priv->qkey = be32_to_cpu(priv->broadcast->mcmember.qkey);
|
|
spin_unlock_irq(&priv->lock);
|
|
priv->tx_wr.wr.ud.remote_qkey = priv->qkey;
|
|
set_qkey = 1;
|
|
}
|
|
|
|
if (!test_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags)) {
|
|
if (test_and_set_bit(IPOIB_MCAST_FLAG_ATTACHED, &mcast->flags)) {
|
|
ipoib_warn(priv, "multicast group %pI6 already attached\n",
|
|
mcast->mcmember.mgid.raw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
ret = ipoib_mcast_attach(dev, be16_to_cpu(mcast->mcmember.mlid),
|
|
&mcast->mcmember.mgid, set_qkey);
|
|
if (ret < 0) {
|
|
ipoib_warn(priv, "couldn't attach QP to multicast group %pI6\n",
|
|
mcast->mcmember.mgid.raw);
|
|
|
|
clear_bit(IPOIB_MCAST_FLAG_ATTACHED, &mcast->flags);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
{
|
|
struct ib_ah_attr av = {
|
|
.dlid = be16_to_cpu(mcast->mcmember.mlid),
|
|
.port_num = priv->port,
|
|
.sl = mcast->mcmember.sl,
|
|
.ah_flags = IB_AH_GRH,
|
|
.static_rate = mcast->mcmember.rate,
|
|
.grh = {
|
|
.flow_label = be32_to_cpu(mcast->mcmember.flow_label),
|
|
.hop_limit = mcast->mcmember.hop_limit,
|
|
.sgid_index = 0,
|
|
.traffic_class = mcast->mcmember.traffic_class
|
|
}
|
|
};
|
|
av.grh.dgid = mcast->mcmember.mgid;
|
|
|
|
ah = ipoib_create_ah(dev, priv->pd, &av);
|
|
if (IS_ERR(ah)) {
|
|
ipoib_warn(priv, "ib_address_create failed %ld\n",
|
|
-PTR_ERR(ah));
|
|
/* use original error */
|
|
return PTR_ERR(ah);
|
|
} else {
|
|
spin_lock_irq(&priv->lock);
|
|
mcast->ah = ah;
|
|
spin_unlock_irq(&priv->lock);
|
|
|
|
ipoib_dbg_mcast(priv, "MGID %pI6 AV %p, LID 0x%04x, SL %d\n",
|
|
mcast->mcmember.mgid.raw,
|
|
mcast->ah->ah,
|
|
be16_to_cpu(mcast->mcmember.mlid),
|
|
mcast->mcmember.sl);
|
|
}
|
|
}
|
|
|
|
/* actually send any queued packets */
|
|
netif_tx_lock_bh(dev);
|
|
while (!skb_queue_empty(&mcast->pkt_queue)) {
|
|
struct sk_buff *skb = skb_dequeue(&mcast->pkt_queue);
|
|
|
|
netif_tx_unlock_bh(dev);
|
|
|
|
skb->dev = dev;
|
|
if (dev_queue_xmit(skb))
|
|
ipoib_warn(priv, "dev_queue_xmit failed to requeue packet\n");
|
|
|
|
netif_tx_lock_bh(dev);
|
|
}
|
|
netif_tx_unlock_bh(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ipoib_mcast_sendonly_join_complete(int status,
|
|
struct ib_sa_multicast *multicast)
|
|
{
|
|
struct ipoib_mcast *mcast = multicast->context;
|
|
struct net_device *dev = mcast->dev;
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
|
|
/*
|
|
* We have to take the mutex to force mcast_sendonly_join to
|
|
* return from ib_sa_multicast_join and set mcast->mc to a
|
|
* valid value. Otherwise we were racing with ourselves in
|
|
* that we might fail here, but get a valid return from
|
|
* ib_sa_multicast_join after we had cleared mcast->mc here,
|
|
* resulting in mis-matched joins and leaves and a deadlock
|
|
*/
|
|
mutex_lock(&mcast_mutex);
|
|
|
|
/* We trap for port events ourselves. */
|
|
if (status == -ENETRESET) {
|
|
status = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (!status)
|
|
status = ipoib_mcast_join_finish(mcast, &multicast->rec);
|
|
|
|
if (status) {
|
|
if (mcast->logcount++ < 20)
|
|
ipoib_dbg_mcast(netdev_priv(dev), "sendonly multicast "
|
|
"join failed for %pI6, status %d\n",
|
|
mcast->mcmember.mgid.raw, status);
|
|
|
|
/* Flush out any queued packets */
|
|
netif_tx_lock_bh(dev);
|
|
while (!skb_queue_empty(&mcast->pkt_queue)) {
|
|
++dev->stats.tx_dropped;
|
|
dev_kfree_skb_any(skb_dequeue(&mcast->pkt_queue));
|
|
}
|
|
netif_tx_unlock_bh(dev);
|
|
__ipoib_mcast_schedule_join_thread(priv, mcast, 1);
|
|
} else {
|
|
mcast->backoff = 1;
|
|
mcast->delay_until = jiffies;
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 0);
|
|
}
|
|
out:
|
|
clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
if (status)
|
|
mcast->mc = NULL;
|
|
complete(&mcast->done);
|
|
mutex_unlock(&mcast_mutex);
|
|
return status;
|
|
}
|
|
|
|
static int ipoib_mcast_sendonly_join(struct ipoib_mcast *mcast)
|
|
{
|
|
struct net_device *dev = mcast->dev;
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct ib_sa_mcmember_rec rec = {
|
|
#if 0 /* Some SMs don't support send-only yet */
|
|
.join_state = 4
|
|
#else
|
|
.join_state = 1
|
|
#endif
|
|
};
|
|
int ret = 0;
|
|
|
|
if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) {
|
|
ipoib_dbg_mcast(priv, "device shutting down, no sendonly "
|
|
"multicast joins\n");
|
|
clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
complete(&mcast->done);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rec.mgid = mcast->mcmember.mgid;
|
|
rec.port_gid = priv->local_gid;
|
|
rec.pkey = cpu_to_be16(priv->pkey);
|
|
|
|
mutex_lock(&mcast_mutex);
|
|
mcast->mc = ib_sa_join_multicast(&ipoib_sa_client, priv->ca,
|
|
priv->port, &rec,
|
|
IB_SA_MCMEMBER_REC_MGID |
|
|
IB_SA_MCMEMBER_REC_PORT_GID |
|
|
IB_SA_MCMEMBER_REC_PKEY |
|
|
IB_SA_MCMEMBER_REC_JOIN_STATE,
|
|
GFP_ATOMIC,
|
|
ipoib_mcast_sendonly_join_complete,
|
|
mcast);
|
|
if (IS_ERR(mcast->mc)) {
|
|
ret = PTR_ERR(mcast->mc);
|
|
clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
ipoib_warn(priv, "ib_sa_join_multicast for sendonly join "
|
|
"failed (ret = %d)\n", ret);
|
|
complete(&mcast->done);
|
|
} else {
|
|
ipoib_dbg_mcast(priv, "no multicast record for %pI6, starting "
|
|
"sendonly join\n", mcast->mcmember.mgid.raw);
|
|
}
|
|
mutex_unlock(&mcast_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ipoib_mcast_carrier_on_task(struct work_struct *work)
|
|
{
|
|
struct ipoib_dev_priv *priv = container_of(work, struct ipoib_dev_priv,
|
|
carrier_on_task);
|
|
struct ib_port_attr attr;
|
|
|
|
if (ib_query_port(priv->ca, priv->port, &attr) ||
|
|
attr.state != IB_PORT_ACTIVE) {
|
|
ipoib_dbg(priv, "Keeping carrier off until IB port is active\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Take rtnl_lock to avoid racing with ipoib_stop() and
|
|
* turning the carrier back on while a device is being
|
|
* removed. However, ipoib_stop() will attempt to flush
|
|
* the workqueue while holding the rtnl lock, so loop
|
|
* on trylock until either we get the lock or we see
|
|
* FLAG_OPER_UP go away as that signals that we are bailing
|
|
* and can safely ignore the carrier on work.
|
|
*/
|
|
while (!rtnl_trylock()) {
|
|
if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags))
|
|
return;
|
|
else
|
|
msleep(20);
|
|
}
|
|
if (!ipoib_cm_admin_enabled(priv->dev))
|
|
dev_set_mtu(priv->dev, min(priv->mcast_mtu, priv->admin_mtu));
|
|
netif_carrier_on(priv->dev);
|
|
rtnl_unlock();
|
|
}
|
|
|
|
static int ipoib_mcast_join_complete(int status,
|
|
struct ib_sa_multicast *multicast)
|
|
{
|
|
struct ipoib_mcast *mcast = multicast->context;
|
|
struct net_device *dev = mcast->dev;
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
|
|
ipoib_dbg_mcast(priv, "join completion for %pI6 (status %d)\n",
|
|
mcast->mcmember.mgid.raw, status);
|
|
|
|
/*
|
|
* We have to take the mutex to force mcast_join to
|
|
* return from ib_sa_multicast_join and set mcast->mc to a
|
|
* valid value. Otherwise we were racing with ourselves in
|
|
* that we might fail here, but get a valid return from
|
|
* ib_sa_multicast_join after we had cleared mcast->mc here,
|
|
* resulting in mis-matched joins and leaves and a deadlock
|
|
*/
|
|
mutex_lock(&mcast_mutex);
|
|
|
|
/* We trap for port events ourselves. */
|
|
if (status == -ENETRESET) {
|
|
status = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (!status)
|
|
status = ipoib_mcast_join_finish(mcast, &multicast->rec);
|
|
|
|
if (!status) {
|
|
mcast->backoff = 1;
|
|
mcast->delay_until = jiffies;
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 0);
|
|
|
|
/*
|
|
* Defer carrier on work to priv->wq to avoid a
|
|
* deadlock on rtnl_lock here.
|
|
*/
|
|
if (mcast == priv->broadcast)
|
|
queue_work(priv->wq, &priv->carrier_on_task);
|
|
} else {
|
|
if (mcast->logcount++ < 20) {
|
|
if (status == -ETIMEDOUT || status == -EAGAIN) {
|
|
ipoib_dbg_mcast(priv, "multicast join failed for %pI6, status %d\n",
|
|
mcast->mcmember.mgid.raw, status);
|
|
} else {
|
|
ipoib_warn(priv, "multicast join failed for %pI6, status %d\n",
|
|
mcast->mcmember.mgid.raw, status);
|
|
}
|
|
}
|
|
|
|
/* Requeue this join task with a backoff delay */
|
|
__ipoib_mcast_schedule_join_thread(priv, mcast, 1);
|
|
}
|
|
out:
|
|
clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
if (status)
|
|
mcast->mc = NULL;
|
|
complete(&mcast->done);
|
|
mutex_unlock(&mcast_mutex);
|
|
return status;
|
|
}
|
|
|
|
static void ipoib_mcast_join(struct net_device *dev, struct ipoib_mcast *mcast,
|
|
int create)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct ib_sa_mcmember_rec rec = {
|
|
.join_state = 1
|
|
};
|
|
ib_sa_comp_mask comp_mask;
|
|
int ret = 0;
|
|
|
|
ipoib_dbg_mcast(priv, "joining MGID %pI6\n", mcast->mcmember.mgid.raw);
|
|
|
|
rec.mgid = mcast->mcmember.mgid;
|
|
rec.port_gid = priv->local_gid;
|
|
rec.pkey = cpu_to_be16(priv->pkey);
|
|
|
|
comp_mask =
|
|
IB_SA_MCMEMBER_REC_MGID |
|
|
IB_SA_MCMEMBER_REC_PORT_GID |
|
|
IB_SA_MCMEMBER_REC_PKEY |
|
|
IB_SA_MCMEMBER_REC_JOIN_STATE;
|
|
|
|
if (create) {
|
|
comp_mask |=
|
|
IB_SA_MCMEMBER_REC_QKEY |
|
|
IB_SA_MCMEMBER_REC_MTU_SELECTOR |
|
|
IB_SA_MCMEMBER_REC_MTU |
|
|
IB_SA_MCMEMBER_REC_TRAFFIC_CLASS |
|
|
IB_SA_MCMEMBER_REC_RATE_SELECTOR |
|
|
IB_SA_MCMEMBER_REC_RATE |
|
|
IB_SA_MCMEMBER_REC_SL |
|
|
IB_SA_MCMEMBER_REC_FLOW_LABEL |
|
|
IB_SA_MCMEMBER_REC_HOP_LIMIT;
|
|
|
|
rec.qkey = priv->broadcast->mcmember.qkey;
|
|
rec.mtu_selector = IB_SA_EQ;
|
|
rec.mtu = priv->broadcast->mcmember.mtu;
|
|
rec.traffic_class = priv->broadcast->mcmember.traffic_class;
|
|
rec.rate_selector = IB_SA_EQ;
|
|
rec.rate = priv->broadcast->mcmember.rate;
|
|
rec.sl = priv->broadcast->mcmember.sl;
|
|
rec.flow_label = priv->broadcast->mcmember.flow_label;
|
|
rec.hop_limit = priv->broadcast->mcmember.hop_limit;
|
|
}
|
|
|
|
mutex_lock(&mcast_mutex);
|
|
mcast->mc = ib_sa_join_multicast(&ipoib_sa_client, priv->ca, priv->port,
|
|
&rec, comp_mask, GFP_KERNEL,
|
|
ipoib_mcast_join_complete, mcast);
|
|
if (IS_ERR(mcast->mc)) {
|
|
clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
ret = PTR_ERR(mcast->mc);
|
|
ipoib_warn(priv, "ib_sa_join_multicast failed, status %d\n", ret);
|
|
__ipoib_mcast_schedule_join_thread(priv, mcast, 1);
|
|
complete(&mcast->done);
|
|
}
|
|
mutex_unlock(&mcast_mutex);
|
|
}
|
|
|
|
void ipoib_mcast_join_task(struct work_struct *work)
|
|
{
|
|
struct ipoib_dev_priv *priv =
|
|
container_of(work, struct ipoib_dev_priv, mcast_task.work);
|
|
struct net_device *dev = priv->dev;
|
|
struct ib_port_attr port_attr;
|
|
unsigned long delay_until = 0;
|
|
struct ipoib_mcast *mcast = NULL;
|
|
int create = 1;
|
|
|
|
if (!test_bit(IPOIB_MCAST_RUN, &priv->flags))
|
|
return;
|
|
|
|
if (ib_query_port(priv->ca, priv->port, &port_attr) ||
|
|
port_attr.state != IB_PORT_ACTIVE) {
|
|
ipoib_dbg(priv, "port state is not ACTIVE (state = %d) suspending join task\n",
|
|
port_attr.state);
|
|
return;
|
|
}
|
|
priv->local_lid = port_attr.lid;
|
|
|
|
if (ib_query_gid(priv->ca, priv->port, 0, &priv->local_gid))
|
|
ipoib_warn(priv, "ib_query_gid() failed\n");
|
|
else
|
|
memcpy(priv->dev->dev_addr + 4, priv->local_gid.raw, sizeof (union ib_gid));
|
|
|
|
/*
|
|
* We have to hold the mutex to keep from racing with the join
|
|
* completion threads on setting flags on mcasts, and we have
|
|
* to hold the priv->lock because dev_flush will remove entries
|
|
* out from underneath us, so at a minimum we need the lock
|
|
* through the time that we do the for_each loop of the mcast
|
|
* list or else dev_flush can make us oops.
|
|
*/
|
|
mutex_lock(&mcast_mutex);
|
|
spin_lock_irq(&priv->lock);
|
|
if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags))
|
|
goto out;
|
|
|
|
if (!priv->broadcast) {
|
|
struct ipoib_mcast *broadcast;
|
|
|
|
broadcast = ipoib_mcast_alloc(dev, 0);
|
|
if (!broadcast) {
|
|
ipoib_warn(priv, "failed to allocate broadcast group\n");
|
|
/*
|
|
* Restart us after a 1 second delay to retry
|
|
* creating our broadcast group and attaching to
|
|
* it. Until this succeeds, this ipoib dev is
|
|
* completely stalled (multicast wise).
|
|
*/
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 1);
|
|
goto out;
|
|
}
|
|
|
|
memcpy(broadcast->mcmember.mgid.raw, priv->dev->broadcast + 4,
|
|
sizeof (union ib_gid));
|
|
priv->broadcast = broadcast;
|
|
|
|
__ipoib_mcast_add(dev, priv->broadcast);
|
|
}
|
|
|
|
if (!test_bit(IPOIB_MCAST_FLAG_ATTACHED, &priv->broadcast->flags)) {
|
|
if (IS_ERR_OR_NULL(priv->broadcast->mc) &&
|
|
!test_bit(IPOIB_MCAST_FLAG_BUSY, &priv->broadcast->flags)) {
|
|
mcast = priv->broadcast;
|
|
create = 0;
|
|
if (mcast->backoff > 1 &&
|
|
time_before(jiffies, mcast->delay_until)) {
|
|
delay_until = mcast->delay_until;
|
|
mcast = NULL;
|
|
}
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* We'll never get here until the broadcast group is both allocated
|
|
* and attached
|
|
*/
|
|
list_for_each_entry(mcast, &priv->multicast_list, list) {
|
|
if (IS_ERR_OR_NULL(mcast->mc) &&
|
|
!test_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags) &&
|
|
!test_bit(IPOIB_MCAST_FLAG_ATTACHED, &mcast->flags)) {
|
|
if (mcast->backoff == 1 ||
|
|
time_after_eq(jiffies, mcast->delay_until))
|
|
/* Found the next unjoined group */
|
|
break;
|
|
else if (!delay_until ||
|
|
time_before(mcast->delay_until, delay_until))
|
|
delay_until = mcast->delay_until;
|
|
}
|
|
}
|
|
|
|
if (&mcast->list == &priv->multicast_list) {
|
|
/*
|
|
* All done, unless we have delayed work from
|
|
* backoff retransmissions, but we will get
|
|
* restarted when the time is right, so we are
|
|
* done for now
|
|
*/
|
|
mcast = NULL;
|
|
ipoib_dbg_mcast(priv, "successfully joined all "
|
|
"multicast groups\n");
|
|
}
|
|
|
|
out:
|
|
if (mcast) {
|
|
init_completion(&mcast->done);
|
|
set_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags);
|
|
}
|
|
spin_unlock_irq(&priv->lock);
|
|
mutex_unlock(&mcast_mutex);
|
|
if (mcast) {
|
|
if (test_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags))
|
|
ipoib_mcast_sendonly_join(mcast);
|
|
else
|
|
ipoib_mcast_join(dev, mcast, create);
|
|
}
|
|
if (delay_until)
|
|
queue_delayed_work(priv->wq, &priv->mcast_task,
|
|
delay_until - jiffies);
|
|
}
|
|
|
|
int ipoib_mcast_start_thread(struct net_device *dev)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
|
|
ipoib_dbg_mcast(priv, "starting multicast thread\n");
|
|
|
|
mutex_lock(&mcast_mutex);
|
|
set_bit(IPOIB_MCAST_RUN, &priv->flags);
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 0);
|
|
mutex_unlock(&mcast_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ipoib_mcast_stop_thread(struct net_device *dev)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
|
|
ipoib_dbg_mcast(priv, "stopping multicast thread\n");
|
|
|
|
mutex_lock(&mcast_mutex);
|
|
clear_bit(IPOIB_MCAST_RUN, &priv->flags);
|
|
cancel_delayed_work(&priv->mcast_task);
|
|
mutex_unlock(&mcast_mutex);
|
|
|
|
flush_workqueue(priv->wq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipoib_mcast_leave(struct net_device *dev, struct ipoib_mcast *mcast)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
int ret = 0;
|
|
|
|
if (test_and_clear_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags))
|
|
ipoib_warn(priv, "ipoib_mcast_leave on an in-flight join\n");
|
|
|
|
if (!IS_ERR_OR_NULL(mcast->mc))
|
|
ib_sa_free_multicast(mcast->mc);
|
|
else
|
|
ipoib_dbg(priv, "ipoib_mcast_leave with mcast->mc invalid\n");
|
|
|
|
if (test_and_clear_bit(IPOIB_MCAST_FLAG_ATTACHED, &mcast->flags)) {
|
|
ipoib_dbg_mcast(priv, "leaving MGID %pI6\n",
|
|
mcast->mcmember.mgid.raw);
|
|
|
|
/* Remove ourselves from the multicast group */
|
|
ret = ib_detach_mcast(priv->qp, &mcast->mcmember.mgid,
|
|
be16_to_cpu(mcast->mcmember.mlid));
|
|
if (ret)
|
|
ipoib_warn(priv, "ib_detach_mcast failed (result = %d)\n", ret);
|
|
} else if (!test_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags))
|
|
ipoib_dbg(priv, "leaving with no mcmember but not a "
|
|
"SENDONLY join\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ipoib_mcast_send(struct net_device *dev, u8 *daddr, struct sk_buff *skb)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
struct ipoib_mcast *mcast;
|
|
unsigned long flags;
|
|
void *mgid = daddr + 4;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) ||
|
|
!priv->broadcast ||
|
|
!test_bit(IPOIB_MCAST_FLAG_ATTACHED, &priv->broadcast->flags)) {
|
|
++dev->stats.tx_dropped;
|
|
dev_kfree_skb_any(skb);
|
|
goto unlock;
|
|
}
|
|
|
|
mcast = __ipoib_mcast_find(dev, mgid);
|
|
if (!mcast) {
|
|
/* Let's create a new send only group now */
|
|
ipoib_dbg_mcast(priv, "setting up send only multicast group for %pI6\n",
|
|
mgid);
|
|
|
|
mcast = ipoib_mcast_alloc(dev, 0);
|
|
if (!mcast) {
|
|
ipoib_warn(priv, "unable to allocate memory for "
|
|
"multicast structure\n");
|
|
++dev->stats.tx_dropped;
|
|
dev_kfree_skb_any(skb);
|
|
goto out;
|
|
}
|
|
|
|
set_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags);
|
|
memcpy(mcast->mcmember.mgid.raw, mgid, sizeof (union ib_gid));
|
|
__ipoib_mcast_add(dev, mcast);
|
|
list_add_tail(&mcast->list, &priv->multicast_list);
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 0);
|
|
}
|
|
|
|
if (!mcast->ah) {
|
|
if (skb_queue_len(&mcast->pkt_queue) < IPOIB_MAX_MCAST_QUEUE)
|
|
skb_queue_tail(&mcast->pkt_queue, skb);
|
|
else {
|
|
++dev->stats.tx_dropped;
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
/*
|
|
* If lookup completes between here and out:, don't
|
|
* want to send packet twice.
|
|
*/
|
|
mcast = NULL;
|
|
}
|
|
|
|
out:
|
|
if (mcast && mcast->ah) {
|
|
struct ipoib_neigh *neigh;
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
neigh = ipoib_neigh_get(dev, daddr);
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (!neigh) {
|
|
neigh = ipoib_neigh_alloc(daddr, dev);
|
|
if (neigh) {
|
|
kref_get(&mcast->ah->ref);
|
|
neigh->ah = mcast->ah;
|
|
list_add_tail(&neigh->list, &mcast->neigh_list);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
ipoib_send(dev, skb, mcast->ah, IB_MULTICAST_QPN);
|
|
if (neigh)
|
|
ipoib_neigh_put(neigh);
|
|
return;
|
|
}
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
void ipoib_mcast_dev_flush(struct net_device *dev)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(dev);
|
|
LIST_HEAD(remove_list);
|
|
struct ipoib_mcast *mcast, *tmcast;
|
|
unsigned long flags;
|
|
|
|
ipoib_dbg_mcast(priv, "flushing multicast list\n");
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
list_for_each_entry_safe(mcast, tmcast, &priv->multicast_list, list) {
|
|
list_del(&mcast->list);
|
|
rb_erase(&mcast->rb_node, &priv->multicast_tree);
|
|
list_add_tail(&mcast->list, &remove_list);
|
|
}
|
|
|
|
if (priv->broadcast) {
|
|
rb_erase(&priv->broadcast->rb_node, &priv->multicast_tree);
|
|
list_add_tail(&priv->broadcast->list, &remove_list);
|
|
priv->broadcast = NULL;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
/*
|
|
* make sure the in-flight joins have finished before we attempt
|
|
* to leave
|
|
*/
|
|
list_for_each_entry_safe(mcast, tmcast, &remove_list, list)
|
|
if (test_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags))
|
|
wait_for_completion(&mcast->done);
|
|
|
|
list_for_each_entry_safe(mcast, tmcast, &remove_list, list) {
|
|
ipoib_mcast_leave(dev, mcast);
|
|
ipoib_mcast_free(mcast);
|
|
}
|
|
}
|
|
|
|
static int ipoib_mcast_addr_is_valid(const u8 *addr, const u8 *broadcast)
|
|
{
|
|
/* reserved QPN, prefix, scope */
|
|
if (memcmp(addr, broadcast, 6))
|
|
return 0;
|
|
/* signature lower, pkey */
|
|
if (memcmp(addr + 7, broadcast + 7, 3))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
void ipoib_mcast_restart_task(struct work_struct *work)
|
|
{
|
|
struct ipoib_dev_priv *priv =
|
|
container_of(work, struct ipoib_dev_priv, restart_task);
|
|
struct net_device *dev = priv->dev;
|
|
struct netdev_hw_addr *ha;
|
|
struct ipoib_mcast *mcast, *tmcast;
|
|
LIST_HEAD(remove_list);
|
|
unsigned long flags;
|
|
struct ib_sa_mcmember_rec rec;
|
|
|
|
if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags))
|
|
/*
|
|
* shortcut...on shutdown flush is called next, just
|
|
* let it do all the work
|
|
*/
|
|
return;
|
|
|
|
ipoib_dbg_mcast(priv, "restarting multicast task\n");
|
|
|
|
local_irq_save(flags);
|
|
netif_addr_lock(dev);
|
|
spin_lock(&priv->lock);
|
|
|
|
/*
|
|
* Unfortunately, the networking core only gives us a list of all of
|
|
* the multicast hardware addresses. We need to figure out which ones
|
|
* are new and which ones have been removed
|
|
*/
|
|
|
|
/* Clear out the found flag */
|
|
list_for_each_entry(mcast, &priv->multicast_list, list)
|
|
clear_bit(IPOIB_MCAST_FLAG_FOUND, &mcast->flags);
|
|
|
|
/* Mark all of the entries that are found or don't exist */
|
|
netdev_for_each_mc_addr(ha, dev) {
|
|
union ib_gid mgid;
|
|
|
|
if (!ipoib_mcast_addr_is_valid(ha->addr, dev->broadcast))
|
|
continue;
|
|
|
|
memcpy(mgid.raw, ha->addr + 4, sizeof mgid);
|
|
|
|
mcast = __ipoib_mcast_find(dev, &mgid);
|
|
if (!mcast || test_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags)) {
|
|
struct ipoib_mcast *nmcast;
|
|
|
|
/* ignore group which is directly joined by userspace */
|
|
if (test_bit(IPOIB_FLAG_UMCAST, &priv->flags) &&
|
|
!ib_sa_get_mcmember_rec(priv->ca, priv->port, &mgid, &rec)) {
|
|
ipoib_dbg_mcast(priv, "ignoring multicast entry for mgid %pI6\n",
|
|
mgid.raw);
|
|
continue;
|
|
}
|
|
|
|
/* Not found or send-only group, let's add a new entry */
|
|
ipoib_dbg_mcast(priv, "adding multicast entry for mgid %pI6\n",
|
|
mgid.raw);
|
|
|
|
nmcast = ipoib_mcast_alloc(dev, 0);
|
|
if (!nmcast) {
|
|
ipoib_warn(priv, "unable to allocate memory for multicast structure\n");
|
|
continue;
|
|
}
|
|
|
|
set_bit(IPOIB_MCAST_FLAG_FOUND, &nmcast->flags);
|
|
|
|
nmcast->mcmember.mgid = mgid;
|
|
|
|
if (mcast) {
|
|
/* Destroy the send only entry */
|
|
list_move_tail(&mcast->list, &remove_list);
|
|
|
|
rb_replace_node(&mcast->rb_node,
|
|
&nmcast->rb_node,
|
|
&priv->multicast_tree);
|
|
} else
|
|
__ipoib_mcast_add(dev, nmcast);
|
|
|
|
list_add_tail(&nmcast->list, &priv->multicast_list);
|
|
}
|
|
|
|
if (mcast)
|
|
set_bit(IPOIB_MCAST_FLAG_FOUND, &mcast->flags);
|
|
}
|
|
|
|
/* Remove all of the entries don't exist anymore */
|
|
list_for_each_entry_safe(mcast, tmcast, &priv->multicast_list, list) {
|
|
if (!test_bit(IPOIB_MCAST_FLAG_FOUND, &mcast->flags) &&
|
|
!test_bit(IPOIB_MCAST_FLAG_SENDONLY, &mcast->flags)) {
|
|
ipoib_dbg_mcast(priv, "deleting multicast group %pI6\n",
|
|
mcast->mcmember.mgid.raw);
|
|
|
|
rb_erase(&mcast->rb_node, &priv->multicast_tree);
|
|
|
|
/* Move to the remove list */
|
|
list_move_tail(&mcast->list, &remove_list);
|
|
}
|
|
}
|
|
|
|
spin_unlock(&priv->lock);
|
|
netif_addr_unlock(dev);
|
|
local_irq_restore(flags);
|
|
|
|
/*
|
|
* make sure the in-flight joins have finished before we attempt
|
|
* to leave
|
|
*/
|
|
list_for_each_entry_safe(mcast, tmcast, &remove_list, list)
|
|
if (test_bit(IPOIB_MCAST_FLAG_BUSY, &mcast->flags))
|
|
wait_for_completion(&mcast->done);
|
|
|
|
list_for_each_entry_safe(mcast, tmcast, &remove_list, list) {
|
|
ipoib_mcast_leave(mcast->dev, mcast);
|
|
ipoib_mcast_free(mcast);
|
|
}
|
|
|
|
/*
|
|
* Double check that we are still up
|
|
*/
|
|
if (test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) {
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
__ipoib_mcast_schedule_join_thread(priv, NULL, 0);
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_INFINIBAND_IPOIB_DEBUG
|
|
|
|
struct ipoib_mcast_iter *ipoib_mcast_iter_init(struct net_device *dev)
|
|
{
|
|
struct ipoib_mcast_iter *iter;
|
|
|
|
iter = kmalloc(sizeof *iter, GFP_KERNEL);
|
|
if (!iter)
|
|
return NULL;
|
|
|
|
iter->dev = dev;
|
|
memset(iter->mgid.raw, 0, 16);
|
|
|
|
if (ipoib_mcast_iter_next(iter)) {
|
|
kfree(iter);
|
|
return NULL;
|
|
}
|
|
|
|
return iter;
|
|
}
|
|
|
|
int ipoib_mcast_iter_next(struct ipoib_mcast_iter *iter)
|
|
{
|
|
struct ipoib_dev_priv *priv = netdev_priv(iter->dev);
|
|
struct rb_node *n;
|
|
struct ipoib_mcast *mcast;
|
|
int ret = 1;
|
|
|
|
spin_lock_irq(&priv->lock);
|
|
|
|
n = rb_first(&priv->multicast_tree);
|
|
|
|
while (n) {
|
|
mcast = rb_entry(n, struct ipoib_mcast, rb_node);
|
|
|
|
if (memcmp(iter->mgid.raw, mcast->mcmember.mgid.raw,
|
|
sizeof (union ib_gid)) < 0) {
|
|
iter->mgid = mcast->mcmember.mgid;
|
|
iter->created = mcast->created;
|
|
iter->queuelen = skb_queue_len(&mcast->pkt_queue);
|
|
iter->complete = !!mcast->ah;
|
|
iter->send_only = !!(mcast->flags & (1 << IPOIB_MCAST_FLAG_SENDONLY));
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
n = rb_next(n);
|
|
}
|
|
|
|
spin_unlock_irq(&priv->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ipoib_mcast_iter_read(struct ipoib_mcast_iter *iter,
|
|
union ib_gid *mgid,
|
|
unsigned long *created,
|
|
unsigned int *queuelen,
|
|
unsigned int *complete,
|
|
unsigned int *send_only)
|
|
{
|
|
*mgid = iter->mgid;
|
|
*created = iter->created;
|
|
*queuelen = iter->queuelen;
|
|
*complete = iter->complete;
|
|
*send_only = iter->send_only;
|
|
}
|
|
|
|
#endif /* CONFIG_INFINIBAND_IPOIB_DEBUG */
|