On 9/10/26 14:10, Eelco Chaudron wrote:
This patch adds support for offload providers to hook into PMD thread
lifecycle events, specifically thread initialization, reinitialization,
and termination.  The new pmd_thread_lifecycle() callback in
dpif_offload_class allows a provider to maintain per-PMD-thread context
across reloads and to release resources when a thread exits.


Thanks Eelco. Code operation looks good, a few minor comments on it.

It will need an Ack from Gaetan to confirm that it meets his requirements.

Signed-off-by: Eelco Chaudron <[email protected]>
---
  lib/dpif-netdev.c             |  25 ++++++++
  lib/dpif-offload-dummy.c      |  22 +++++++
  lib/dpif-offload-provider.h   |  14 +++++
  lib/dpif-offload.c            | 108 ++++++++++++++++++++++++++++++++++
  lib/dpif-offload.h            |   8 +++
  tests/pmd.at                  |  25 ++++++++
  utilities/checkpatch_dict.txt |   3 +
  7 files changed, 205 insertions(+)

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 4151ea056..b15552934 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -331,6 +331,9 @@ struct dp_netdev {
      uint64_t last_reconfigure_seq;
      struct ovsthread_once once_set_config;
+ /* When a reconfigure is requested, forcefully reload all PMDs. */
+    bool force_pmd_reload;
+
      /* Cpu mask for pin of pmd threads. */
      char *pmd_cmask;
@@ -341,6 +344,7 @@ struct dp_netdev { struct conntrack *conntrack;
      struct pmd_auto_lb pmd_alb;
+    bool offload_enabled;
/* Bonds. */
      struct ovs_mutex bond_mutex; /* Protects updates of 'tx_bonds'. */
@@ -4061,6 +4065,14 @@ dpif_netdev_set_config(struct dpif *dpif, const struct 
smap *other_config)
          log_all_pmd_sleeps(dp);
      }
+ if (!dp->offload_enabled) {
+        dp->offload_enabled = dpif_offload_enabled();
+        if (dp->offload_enabled) {
+            dp->force_pmd_reload = true;
+            dp_netdev_request_reconfigure(dp);
+        }
+    }
+

You could simplify a little,

    if (!dp->offload_enabled && dpif_offload_enabled()) {
        dp->offload_enabled = true;
        dp->force_pmd_reload = true;
        dp_netdev_request_reconfigure(dp);
    }

      return 0;
  }
@@ -5769,6 +5781,14 @@ reconfigure_datapath(struct dp_netdev *dp)
          ovs_mutex_unlock(&pmd->port_mutex);
      }
+ /* Do we need to forcefully reload all threads? */
+    if (dp->force_pmd_reload) {
+        CMAP_FOR_EACH (pmd, node, &dp->poll_threads) {
+            pmd->need_reload = true;
+        }
+        dp->force_pmd_reload = false;
+    }
+
      /* Reload affected pmd threads. */
      reload_affected_pmds(dp);
@@ -6054,6 +6074,7 @@ pmd_thread_main(void *f_)
  {
      struct dp_netdev_pmd_thread *pmd = f_;
      struct pmd_perf_stats *s = &pmd->perf_stats;
+    struct dpif_offload_pmd_ctx *offload_ctx = NULL;
      unsigned int lc = 0;
      struct polled_queue *poll_list;
      bool wait_for_reload = false;
@@ -6087,6 +6108,9 @@ reload:
          dpdk_attached = dpdk_attach_thread(pmd->core_id);
      }
+ dpif_offload_pmd_thread_reload(pmd->dp->full_name, pmd->core_id,
+                                   pmd->numa_id, &offload_ctx);
+
      /* List port/core affinity */
      for (i = 0; i < poll_cnt; i++) {
         VLOG_DBG("Core %d processing port \'%s\' with queue-id %d\n",
@@ -6246,6 +6270,7 @@ reload:
          goto reload;
      }
+ dpif_offload_pmd_thread_exit(offload_ctx);
      pmd_free_static_tx_qid(pmd);
      dfc_cache_uninit(&pmd->flow_cache);
      free(poll_list);
diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c
index 878276a94..99f2b3733 100644
--- a/lib/dpif-offload-dummy.c
+++ b/lib/dpif-offload-dummy.c
@@ -1146,6 +1146,27 @@ dummy_netdev_hw_offload_run(struct netdev *netdev)
      }
  }
+static void
+dummy_pmd_thread_lifecycle(const struct dpif_offload *dpif_offload, bool exit,
+                           unsigned core_id, int numa_id, void **ctx)
+{
+    /* Only do this for the 'dummy' class, not for 'dummy_x'. */
+    if (strcmp(dpif_offload_type(dpif_offload), "dummy")) {
+        *ctx = NULL;

I don't think it will happen, but if it's somehow the wrong offload, I don't think we should do anything with ctx.

+        return;
+    }
+
+    VLOG_DBG(
+        "pmd_thread_lifecycle; exit=%s, core=%u, numa=%d, ctx=%p",
+        exit ? "true" : "false", core_id, numa_id, *ctx);
+
+    if (exit) {
+        free(*ctx);
+    } else {
+        *ctx = *ctx ? *ctx : xstrdup("DUMMY_OFFLOAD_WORK");
+    }
+}
+
  #define DEFINE_DPIF_DUMMY_CLASS(NAME, TYPE_STR)                             \
      struct dpif_offload_class NAME = {                                      \
          .type = TYPE_STR,                                                   \
@@ -1166,6 +1187,7 @@ dummy_netdev_hw_offload_run(struct netdev *netdev)
          .netdev_flow_del = dummy_flow_del,                                  \
          .netdev_flow_stats = dummy_flow_stats,                              \
          .register_flow_unreference_cb = dummy_register_flow_unreference_cb, \
+        .pmd_thread_lifecycle = dummy_pmd_thread_lifecycle                  \
  }
DEFINE_DPIF_DUMMY_CLASS(dpif_offload_dummy_class, "dummy");
diff --git a/lib/dpif-offload-provider.h b/lib/dpif-offload-provider.h
index 444b13138..0b098f6c1 100644
--- a/lib/dpif-offload-provider.h
+++ b/lib/dpif-offload-provider.h
@@ -318,6 +318,20 @@ struct dpif_offload_class {
       * to netdev_flow_put() is no longer held by the offload provider. */
      void (*register_flow_unreference_cb)(const struct dpif_offload *,
                                           dpif_offload_flow_unreference_cb *);
+
+    /* The API below is specific to PMD (userspace) thread lifecycle handling.
+     *
+     * The lifecycle hook may be invoked multiple times for the same PMD
+     * thread.  For example, when the thread is reinitialized, this function
+     * will be called again and the previous 'ctx' value will be passed back
+     * in.  It is the provider's responsibility to decide whether it should
+     * be reused, replaced, or cleaned up before storing the new 'ctx' value.
+     *
+     * When the PMD thread is terminating, this API is called with
+     * 'exit == true'.  At that point, the provider must release any resources
+     * associated with the previously returned 'ctx'. */
+    void (*pmd_thread_lifecycle)(const struct dpif_offload *, bool exit,
+                                 unsigned core_id, int numa_id, void **ctx);

Not sure about the naming. "lifecycle" is not a verb and I don't feel it describes much. Then again, I can't think of a good suggestion.

Do we need the offload as a param, when the function will be called per offload ?

  };
extern struct dpif_offload_class dpif_offload_dummy_class;
diff --git a/lib/dpif-offload.c b/lib/dpif-offload.c
index 04dabc42c..9b0083200 100644
--- a/lib/dpif-offload.c
+++ b/lib/dpif-offload.c
@@ -54,6 +54,7 @@ static const struct dpif_offload_class 
*base_dpif_offload_classes[] = {
      &dpif_offload_dummy_x_class,
  };
+#define TOTAL_PROVIDERS ARRAY_SIZE(base_dpif_offload_classes)
  #define DEFAULT_PROVIDER_PRIORITY_LIST "tc,dpdk,dummy,dummy_x"
static char *dpif_offload_provider_priority_list = NULL;
@@ -1693,3 +1694,110 @@ dpif_offload_port_mgr_port_count(const struct 
dpif_offload *offload)
return cmap_count(&offload->ports->odp_port_to_port);
  }
+
+struct dpif_offload_pmd_ctx_node {
+    const struct dpif_offload *offload;
+    void *provider_ctx;
+};
+
+struct dpif_offload_pmd_ctx {
+    unsigned core_id;
+    int numa_id;
+    size_t n_nodes;
+    struct dpif_offload_pmd_ctx_node nodes[TOTAL_PROVIDERS];
+};
+
+void
+dpif_offload_pmd_thread_reload(const char *dpif_name, unsigned core_id,
+                               int numa_id, struct dpif_offload_pmd_ctx **ctx_)
+{
+    struct dpif_offload_pmd_ctx_node old_nodes[TOTAL_PROVIDERS];
+    struct dpif_offload_provider_collection *collection;
+    struct dpif_offload_pmd_ctx *ctx;
+    struct dpif_offload *offload;
+    size_t old_n_nodes = 0;
+
+    if (!dpif_offload_enabled()) {
+        ovs_assert(!*ctx_);
+        return;
+    }
+
+    ovs_mutex_lock(&dpif_offload_mutex);
+    collection = shash_find_data(&dpif_offload_providers, dpif_name);
+    ovs_mutex_unlock(&dpif_offload_mutex);
+
+    if (OVS_UNLIKELY(!collection)) {
+        ovs_assert(!*ctx_);
+        return;
+    }
+
+    if (!*ctx_) {
+        /* Would be nice if we have a numa specific xzalloc(). */

If you want to keep a note, then probably a FIXME or TODO (I didn't check Coding stds)

+        ctx = xzalloc(sizeof *ctx);
+        ctx->core_id = core_id;
+        ctx->numa_id = numa_id;
+        *ctx_ = ctx;
+    } else {
+        ctx = *ctx_;
+        old_n_nodes = ctx->n_nodes;
+
+        if (old_n_nodes) {

The guard is not really needed but you may want to keep for readability

+            memcpy(old_nodes, ctx->nodes, old_n_nodes * sizeof old_nodes[0]);
+        }
+
+        /* Reset active nodes array. */
+        memset(ctx->nodes, 0, sizeof ctx->nodes);
+        ctx->n_nodes = 0;
+    }
+
+    LIST_FOR_EACH (offload, dpif_list_node, &collection->list) {
+
+        ovs_assert(ctx->n_nodes < TOTAL_PROVIDERS);
+
+        if (!offload->class->pmd_thread_lifecycle) {
+            continue;
+        }
+
+        if (old_n_nodes) {

Similar to above, guard not strictly needed but you may want to keep anyway

+            /* If this is a reload, try to find previous context. */
+            for (size_t i = 0; i < old_n_nodes; i++) {
+                struct dpif_offload_pmd_ctx_node *node = &old_nodes[i];
+
+                if (offload == node->offload) {
+                    ctx->nodes[ctx->n_nodes].provider_ctx = node->provider_ctx;
+                    break;
+                }
+            }
+        }
+
+        offload->class->pmd_thread_lifecycle(
+            offload, false, core_id, numa_id,
+            &ctx->nodes[ctx->n_nodes].provider_ctx);
+
+        if (ctx->nodes[ctx->n_nodes].provider_ctx) {
+            ctx->nodes[ctx->n_nodes].offload = offload;
+            ctx->n_nodes++;
+        } else {
+            memset(&ctx->nodes[ctx->n_nodes], 0,
+                   sizeof ctx->nodes[ctx->n_nodes]);

This branch doesn't seem to be needed as provider_ctx is just checked and nothing will have written to offload field

+        }
+    }
+}
+
+void
+dpif_offload_pmd_thread_exit(struct dpif_offload_pmd_ctx *ctx)
+{
+    if (!ctx) {
+        return;
+    }
+
+    for (size_t i = 0; i < ctx->n_nodes; i++) {
+        struct dpif_offload_pmd_ctx_node *node = &ctx->nodes[i];
+
+        node->offload->class->pmd_thread_lifecycle(node->offload, true,
+                                                   ctx->core_id, ctx->numa_id,
+                                                   &node->provider_ctx);
+    }
+
+    free(ctx);
+}
diff --git a/lib/dpif-offload.h b/lib/dpif-offload.h
index bf7643320..b6d3c1f37 100644
--- a/lib/dpif-offload.h
+++ b/lib/dpif-offload.h
@@ -189,4 +189,12 @@ dpif_offload_datapath_flow_op_continue(struct 
dpif_offload_flow_cb_data *cb,
      }
  }
+/* PMD Thread helper functions. */
+struct dpif_offload_pmd_ctx;
+
+void dpif_offload_pmd_thread_reload(const char *dpif_name,
+                                    unsigned core_id, int numa_id,
+                                    struct dpif_offload_pmd_ctx **);

Is NUMA needed for something ? if in doubt better to keep

+void dpif_offload_pmd_thread_exit(struct dpif_offload_pmd_ctx *);
+
  #endif /* DPIF_OFFLOAD_H */
diff --git a/tests/pmd.at b/tests/pmd.at
index 66fccc1d5..e315bc3a7 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -1599,3 +1599,28 @@ 
recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=10.1.2.
OVS_VSWITCHD_STOP
  AT_CLEANUP
+
+AT_SETUP([PMD - offload thread lifecycle])
+OVS_VSWITCHD_START([], [], [], [DUMMY_NUMA],
+                   [-- set Open_vSwitch . other_config:hw-offload=true])
+
+AT_CHECK([ovs-appctl vlog/set dpif_offload_dummy:dbg])
+AT_CHECK([ovs-vsctl add-port br0 p0 -- set Interface p0 type=dummy-pmd])
+
+CHECK_CPU_DISCOVERED()
+CHECK_PMD_THREADS_CREATED()
+
+OVS_VSWITCHD_STOP
+
+LOG="$(sed -n 's/.*\(pmd_thread_lifecycle.*\)/\1/p' ovs-vswitchd.log)"
+CTX=$(echo "$LOG" | sed -n '2p' | sed -n 's/.*ctx=\(.*\)$/\1/p')
+
+AT_CHECK([echo "$LOG" | sed -n '1p' | sed 's/(nil)/0x0/g'], [0], [dnl
+pmd_thread_lifecycle; exit=false, core=0, numa=0, ctx=0x0
+])
+AT_CHECK([echo "$LOG" | sed -n '2p' \
+          | grep -q "exit=false, core=0, numa=0, ctx=$CTX"])
+AT_CHECK([echo "$LOG" | sed -n '$p' \
+          | grep -q "exit=true, core=0, numa=0, ctx=$CTX"])
+
+AT_CLEANUP
diff --git a/utilities/checkpatch_dict.txt b/utilities/checkpatch_dict.txt
index cb25a86ea..acdaf2c74 100644
--- a/utilities/checkpatch_dict.txt
+++ b/utilities/checkpatch_dict.txt
@@ -35,6 +35,7 @@ cpu
  cpus
  cstime
  csum
+ctx
  cutime
  cvlan
  datapath
@@ -45,6 +46,7 @@ decap
  decapsulation
  defrag
  defragment
+deinitialization
  deref
  dereference
  dest
@@ -233,6 +235,7 @@ rebased
  recirc
  recirculation
  recirculations
+reinitialization
  revalidate
  revalidation
  revalidator


_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to