sfc: keep alive neighbour entries while a TC encap action is using them
authorEdward Cree <ecree.xilinx@gmail.com>
Wed, 21 Jun 2023 12:15:04 +0000 (13:15 +0100)
committerJakub Kicinski <kuba@kernel.org>
Fri, 23 Jun 2023 02:54:14 +0000 (19:54 -0700)
When processing counter updates, if any action set using the newly
 incremented counter includes an encap action, prod the corresponding
 neighbouring entry to indicate to the neighbour cache that the entry
 is still in use and passing traffic.

Signed-off-by: Edward Cree <ecree.xilinx@gmail.com>
Link: https://lore.kernel.org/r/20230621121504.17004-1-edward.cree@amd.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/sfc/tc.c
drivers/net/ethernet/sfc/tc.h
drivers/net/ethernet/sfc/tc_counters.c
drivers/net/ethernet/sfc/tc_counters.h

index 77acdb60381e3e805747095a48927013e992469c..15ebd39739224fdb7d87df83048c8a8b11775f1f 100644 (file)
@@ -110,8 +110,13 @@ static void efx_tc_free_action_set(struct efx_nic *efx,
                 */
                list_del(&act->list);
        }
-       if (act->count)
+       if (act->count) {
+               spin_lock_bh(&act->count->cnt->lock);
+               if (!list_empty(&act->count_user))
+                       list_del(&act->count_user);
+               spin_unlock_bh(&act->count->cnt->lock);
                efx_tc_flower_put_counter_index(efx, act->count);
+       }
        if (act->encap_md) {
                list_del(&act->encap_user);
                efx_tc_flower_release_encap_md(efx, act->encap_md);
@@ -796,6 +801,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
                                        goto release;
                                }
                                act->count = ctr;
+                               INIT_LIST_HEAD(&act->count_user);
                        }
 
                        if (!efx_tc_flower_action_order_ok(act, EFX_TC_AO_DELIVER)) {
@@ -1083,6 +1089,7 @@ static int efx_tc_flower_replace(struct efx_nic *efx,
                                goto release;
                        }
                        act->count = ctr;
+                       INIT_LIST_HEAD(&act->count_user);
                }
 
                switch (fa->id) {
@@ -1120,6 +1127,17 @@ static int efx_tc_flower_replace(struct efx_nic *efx,
                                list_add_tail(&act->encap_user, &encap->users);
                                act->dest_mport = encap->dest_mport;
                                act->deliver = 1;
+                               if (act->count && !WARN_ON(!act->count->cnt)) {
+                                       /* This counter is used by an encap
+                                        * action, which needs a reference back
+                                        * so it can prod neighbouring whenever
+                                        * traffic is seen.
+                                        */
+                                       spin_lock_bh(&act->count->cnt->lock);
+                                       list_add_tail(&act->count_user,
+                                                     &act->count->cnt->users);
+                                       spin_unlock_bh(&act->count->cnt->lock);
+                               }
                                rc = efx_mae_alloc_action_set(efx, act);
                                if (rc) {
                                        NL_SET_ERR_MSG_MOD(extack, "Failed to write action set to hw (encap)");
index 607429f8bb289ab486276be283b0c94b481267be..1549c3df43bbe4744515f8a45e8632ddd8c32416 100644 (file)
@@ -38,6 +38,7 @@ struct efx_tc_action_set {
        struct efx_tc_encap_action *encap_md; /* entry in tc_encap_ht table */
        struct list_head encap_user; /* entry on encap_md->users list */
        struct efx_tc_action_set_list *user; /* Only populated if encap_md */
+       struct list_head count_user; /* entry on counter->users list, if encap */
        u32 dest_mport;
        u32 fw_id; /* index of this entry in firmware actions table */
        struct list_head list;
index d1a91d54c6bb4cdc1e8d558dd90e25fbee6bf20a..979f49058a0c0d89d88abe7d0e19c1732d00bb1b 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include "tc_counters.h"
+#include "tc_encap_actions.h"
 #include "mae_counter_format.h"
 #include "mae.h"
 #include "rx_common.h"
@@ -31,6 +32,15 @@ static void efx_tc_counter_free(void *ptr, void *__unused)
 {
        struct efx_tc_counter *cnt = ptr;
 
+       WARN_ON(!list_empty(&cnt->users));
+       /* We'd like to synchronize_rcu() here, but unfortunately we aren't
+        * removing the element from the hashtable (it's not clear that's a
+        * safe thing to do in an rhashtable_free_and_destroy free_fn), so
+        * threads could still be obtaining new pointers to *cnt if they can
+        * race against this function at all.
+        */
+       flush_work(&cnt->work);
+       EFX_WARN_ON_PARANOID(spin_is_locked(&cnt->lock));
        kfree(cnt);
 }
 
@@ -74,6 +84,49 @@ void efx_tc_fini_counters(struct efx_nic *efx)
        rhashtable_free_and_destroy(&efx->tc->counter_ht, efx_tc_counter_free, NULL);
 }
 
+static void efx_tc_counter_work(struct work_struct *work)
+{
+       struct efx_tc_counter *cnt = container_of(work, struct efx_tc_counter, work);
+       struct efx_tc_encap_action *encap;
+       struct efx_tc_action_set *act;
+       unsigned long touched;
+       struct neighbour *n;
+
+       spin_lock_bh(&cnt->lock);
+       touched = READ_ONCE(cnt->touched);
+
+       list_for_each_entry(act, &cnt->users, count_user) {
+               encap = act->encap_md;
+               if (!encap)
+                       continue;
+               if (!encap->neigh) /* can't happen */
+                       continue;
+               if (time_after_eq(encap->neigh->used, touched))
+                       continue;
+               encap->neigh->used = touched;
+               /* We have passed traffic using this ARP entry, so
+                * indicate to the ARP cache that it's still active
+                */
+               if (encap->neigh->dst_ip)
+                       n = neigh_lookup(&arp_tbl, &encap->neigh->dst_ip,
+                                        encap->neigh->egdev);
+               else
+#if IS_ENABLED(CONFIG_IPV6)
+                       n = neigh_lookup(ipv6_stub->nd_tbl,
+                                        &encap->neigh->dst_ip6,
+                                        encap->neigh->egdev);
+#else
+                       n = NULL;
+#endif
+               if (!n)
+                       continue;
+
+               neigh_event_send(n, NULL);
+               neigh_release(n);
+       }
+       spin_unlock_bh(&cnt->lock);
+}
+
 /* Counter allocation */
 
 static struct efx_tc_counter *efx_tc_flower_allocate_counter(struct efx_nic *efx,
@@ -87,12 +140,14 @@ static struct efx_tc_counter *efx_tc_flower_allocate_counter(struct efx_nic *efx
                return ERR_PTR(-ENOMEM);
 
        spin_lock_init(&cnt->lock);
+       INIT_WORK(&cnt->work, efx_tc_counter_work);
        cnt->touched = jiffies;
        cnt->type = type;
 
        rc = efx_mae_allocate_counter(efx, cnt);
        if (rc)
                goto fail1;
+       INIT_LIST_HEAD(&cnt->users);
        rc = rhashtable_insert_fast(&efx->tc->counter_ht, &cnt->linkage,
                                    efx_tc_counter_ht_params);
        if (rc)
@@ -126,6 +181,7 @@ static void efx_tc_flower_release_counter(struct efx_nic *efx,
                netif_warn(efx, hw, efx->net_dev,
                           "Failed to free MAE counter %u, rc %d\n",
                           cnt->fw_id, rc);
+       WARN_ON(!list_empty(&cnt->users));
        /* This doesn't protect counter updates coming in arbitrarily long
         * after we deleted the counter.  The RCU just ensures that we won't
         * free the counter while another thread has a pointer to it.
@@ -133,6 +189,7 @@ static void efx_tc_flower_release_counter(struct efx_nic *efx,
         * is handled by the generation count.
         */
        synchronize_rcu();
+       flush_work(&cnt->work);
        EFX_WARN_ON_PARANOID(spin_is_locked(&cnt->lock));
        kfree(cnt);
 }
@@ -302,6 +359,7 @@ static void efx_tc_counter_update(struct efx_nic *efx,
                cnt->touched = jiffies;
        }
        spin_unlock_bh(&cnt->lock);
+       schedule_work(&cnt->work);
 out:
        rcu_read_unlock();
 }
index 8fc7c4bbb29ca9032fdf2bdfd4a8a63c165067cd..41e57f34b763a2e47101067445a42ec732a18833 100644 (file)
@@ -32,6 +32,9 @@ struct efx_tc_counter {
        u64 old_packets, old_bytes; /* Values last time passed to userspace */
        /* jiffies of the last time we saw packets increase */
        unsigned long touched;
+       struct work_struct work; /* For notifying encap actions */
+       /* owners of corresponding count actions */
+       struct list_head users;
 };
 
 struct efx_tc_counter_index {