net/mlx5e: Fix race condition on nhe->n pointer in neigh update
[linux-2.6-microblaze.git] / drivers / net / ethernet / mellanox / mlx5 / core / en / rep / neigh.c
index 9062920..58e2703 100644 (file)
@@ -110,11 +110,25 @@ static void mlx5e_rep_neigh_stats_work(struct work_struct *work)
        rtnl_unlock();
 }
 
+struct neigh_update_work {
+       struct work_struct work;
+       struct neighbour *n;
+       struct mlx5e_neigh_hash_entry *nhe;
+};
+
+static void mlx5e_release_neigh_update_work(struct neigh_update_work *update_work)
+{
+       neigh_release(update_work->n);
+       mlx5e_rep_neigh_entry_release(update_work->nhe);
+       kfree(update_work);
+}
+
 static void mlx5e_rep_neigh_update(struct work_struct *work)
 {
-       struct mlx5e_neigh_hash_entry *nhe =
-               container_of(work, struct mlx5e_neigh_hash_entry, neigh_update_work);
-       struct neighbour *n = nhe->n;
+       struct neigh_update_work *update_work = container_of(work, struct neigh_update_work,
+                                                            work);
+       struct mlx5e_neigh_hash_entry *nhe = update_work->nhe;
+       struct neighbour *n = update_work->n;
        struct mlx5e_encap_entry *e;
        unsigned char ha[ETH_ALEN];
        struct mlx5e_priv *priv;
@@ -146,30 +160,42 @@ static void mlx5e_rep_neigh_update(struct work_struct *work)
                mlx5e_rep_update_flows(priv, e, neigh_connected, ha);
                mlx5e_encap_put(priv, e);
        }
-       mlx5e_rep_neigh_entry_release(nhe);
        rtnl_unlock();
-       neigh_release(n);
+       mlx5e_release_neigh_update_work(update_work);
 }
 
-static void mlx5e_rep_queue_neigh_update_work(struct mlx5e_priv *priv,
-                                             struct mlx5e_neigh_hash_entry *nhe,
-                                             struct neighbour *n)
+static struct neigh_update_work *mlx5e_alloc_neigh_update_work(struct mlx5e_priv *priv,
+                                                              struct neighbour *n)
 {
-       /* Take a reference to ensure the neighbour and mlx5 encap
-        * entry won't be destructed until we drop the reference in
-        * delayed work.
-        */
-       neigh_hold(n);
+       struct neigh_update_work *update_work;
+       struct mlx5e_neigh_hash_entry *nhe;
+       struct mlx5e_neigh m_neigh = {};
 
-       /* This assignment is valid as long as the the neigh reference
-        * is taken
-        */
-       nhe->n = n;
+       update_work = kzalloc(sizeof(*update_work), GFP_ATOMIC);
+       if (WARN_ON(!update_work))
+               return NULL;
 
-       if (!queue_work(priv->wq, &nhe->neigh_update_work)) {
-               mlx5e_rep_neigh_entry_release(nhe);
-               neigh_release(n);
+       m_neigh.dev = n->dev;
+       m_neigh.family = n->ops->family;
+       memcpy(&m_neigh.dst_ip, n->primary_key, n->tbl->key_len);
+
+       /* Obtain reference to nhe as last step in order not to release it in
+        * atomic context.
+        */
+       rcu_read_lock();
+       nhe = mlx5e_rep_neigh_entry_lookup(priv, &m_neigh);
+       rcu_read_unlock();
+       if (!nhe) {
+               kfree(update_work);
+               return NULL;
        }
+
+       INIT_WORK(&update_work->work, mlx5e_rep_neigh_update);
+       neigh_hold(n);
+       update_work->n = n;
+       update_work->nhe = nhe;
+
+       return update_work;
 }
 
 static int mlx5e_rep_netevent_event(struct notifier_block *nb,
@@ -181,7 +207,7 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
        struct net_device *netdev = rpriv->netdev;
        struct mlx5e_priv *priv = netdev_priv(netdev);
        struct mlx5e_neigh_hash_entry *nhe = NULL;
-       struct mlx5e_neigh m_neigh = {};
+       struct neigh_update_work *update_work;
        struct neigh_parms *p;
        struct neighbour *n;
        bool found = false;
@@ -196,17 +222,11 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 #endif
                        return NOTIFY_DONE;
 
-               m_neigh.dev = n->dev;
-               m_neigh.family = n->ops->family;
-               memcpy(&m_neigh.dst_ip, n->primary_key, n->tbl->key_len);
-
-               rcu_read_lock();
-               nhe = mlx5e_rep_neigh_entry_lookup(priv, &m_neigh);
-               rcu_read_unlock();
-               if (!nhe)
+               update_work = mlx5e_alloc_neigh_update_work(priv, n);
+               if (!update_work)
                        return NOTIFY_DONE;
 
-               mlx5e_rep_queue_neigh_update_work(priv, nhe, n);
+               queue_work(priv->wq, &update_work->work);
                break;
 
        case NETEVENT_DELAY_PROBE_TIME_UPDATE:
@@ -352,7 +372,6 @@ int mlx5e_rep_neigh_entry_create(struct mlx5e_priv *priv,
 
        (*nhe)->priv = priv;
        memcpy(&(*nhe)->m_neigh, &e->m_neigh, sizeof(e->m_neigh));
-       INIT_WORK(&(*nhe)->neigh_update_work, mlx5e_rep_neigh_update);
        spin_lock_init(&(*nhe)->encap_list_lock);
        INIT_LIST_HEAD(&(*nhe)->encap_list);
        refcount_set(&(*nhe)->refcnt, 1);