Skip to content

Commit

Permalink
IB/ipoib: Do not remove child devices from within the ndo_uninit
Browse files Browse the repository at this point in the history
Switching to priv_destructor and needs_free_netdev created a subtle
ordering problem in ipoib_remove_one.

Now that unregister_netdev frees the netdev and priv we must ensure that
the children are unregistered before trying to unregister the parent,
or child unregister will use after free.

The solution is to unregister the children, then parent, in the same batch
all while holding the rtnl_lock. This closes all the races where a new
child could have been added and ensures proper ordering.

Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
Signed-off-by: Leon Romanovsky <leonro@mellanox.com>
  • Loading branch information
Jason Gunthorpe committed Aug 3, 2018
1 parent ee190ab commit 25405d9
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
7 changes: 7 additions & 0 deletions drivers/infiniband/ulp/ipoib/ipoib.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ struct ipoib_dev_priv {

unsigned long flags;

/*
* This protects access to the child_intfs list.
* To READ from child_intfs the RTNL or vlan_rwsem read side must be
* held. To WRITE RTNL and the vlan_rwsem write side must be held (in
* that order) This lock exists because we have a few contexts where
* we need the child_intfs, but do not want to grab the RTNL.
*/
struct rw_semaphore vlan_rwsem;
struct mutex mcast_mutex;

Expand Down
28 changes: 17 additions & 11 deletions drivers/infiniband/ulp/ipoib/ipoib_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1939,18 +1939,15 @@ static int ipoib_ndo_init(struct net_device *ndev)

static void ipoib_ndo_uninit(struct net_device *dev)
{
struct ipoib_dev_priv *priv = ipoib_priv(dev), *cpriv, *tcpriv;
LIST_HEAD(head);
struct ipoib_dev_priv *priv = ipoib_priv(dev);

ASSERT_RTNL();

/* Delete any child interfaces first */
list_for_each_entry_safe(cpriv, tcpriv, &priv->child_intfs, list) {
/* Stop GC on child */
cancel_delayed_work_sync(&cpriv->neigh_reap_task);
unregister_netdevice_queue(cpriv->dev, &head);
}
unregister_netdevice_many(&head);
/*
* ipoib_remove_one guarantees the children are removed before the
* parent, and that is the only place where a parent can be removed.
*/
WARN_ON(!list_empty(&priv->child_intfs));

ipoib_neigh_hash_uninit(dev);

Expand Down Expand Up @@ -2466,16 +2463,25 @@ static void ipoib_add_one(struct ib_device *device)

static void ipoib_remove_one(struct ib_device *device, void *client_data)
{
struct ipoib_dev_priv *priv, *tmp;
struct ipoib_dev_priv *priv, *tmp, *cpriv, *tcpriv;
struct list_head *dev_list = client_data;

if (!dev_list)
return;

list_for_each_entry_safe(priv, tmp, dev_list, list) {
LIST_HEAD(head);
ipoib_parent_unregister_pre(priv->dev);

unregister_netdev(priv->dev);
rtnl_lock();

list_for_each_entry_safe(cpriv, tcpriv, &priv->child_intfs,
list)
unregister_netdevice_queue(cpriv->dev, &head);
unregister_netdevice_queue(priv->dev, &head);
unregister_netdevice_many(&head);

rtnl_unlock();
}

kfree(dev_list);
Expand Down
6 changes: 6 additions & 0 deletions drivers/infiniband/ulp/ipoib/ipoib_vlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ int __ipoib_vlan_add(struct ipoib_dev_priv *ppriv, struct ipoib_dev_priv *priv,

ASSERT_RTNL();

/*
* Racing with unregister of the parent must be prevented by the
* caller.
*/
WARN_ON(ppriv->dev->reg_state != NETREG_REGISTERED);

priv->parent = ppriv->dev;
priv->pkey = pkey;
priv->child_type = type;
Expand Down

0 comments on commit 25405d9

Please sign in to comment.