On Thu, Jun 5, 2025 at 11:42 PM Nitin Saxena <nsax...@marvell.com> wrote:
>
> This patch also adds feature arc fast path APIs as well along with
> documentation
>
> Signed-off-by: Nitin Saxena <nsax...@marvell.com>

Acked-by: Jerin Jacob <jer...@marvell.com>


> ---
>  doc/guides/prog_guide/graph_lib.rst      | 180 ++++++
>  lib/graph/graph_feature_arc.c            | 717 ++++++++++++++++++++++-
>  lib/graph/meson.build                    |   2 +-
>  lib/graph/rte_graph_feature_arc.h        | 152 ++++-
>  lib/graph/rte_graph_feature_arc_worker.h | 321 +++++++++-
>  5 files changed, 1353 insertions(+), 19 deletions(-)
>
> diff --git a/doc/guides/prog_guide/graph_lib.rst 
> b/doc/guides/prog_guide/graph_lib.rst
> index 695156aad8..618fdf50ba 100644
> --- a/doc/guides/prog_guide/graph_lib.rst
> +++ b/doc/guides/prog_guide/graph_lib.rst
> @@ -453,6 +453,8 @@ provides application to overload default node path by 
> providing hook
>  points(like netfilter) to insert out-of-tree or another protocol nodes in
>  packet path.
>
> +.. _Control_Data_Plane_Synchronization:
> +
>  Control/Data plane synchronization
>  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>  Feature arc does not stop worker cores for any runtime control plane updates.
> @@ -683,6 +685,11 @@ which might have allocated during feature enable.
>  notifier_cb() is called, at runtime, for every enable/disable of ``[feature,
>  index]`` from control thread.
>
> +If RCU is provided to enable/disable APIs, notifier_cb() is called after
> +``rte_rcu_qsbr_synchronize()``. Application also needs to call
> +``rte_rcu_qsbr_quiescent()`` in worker thread (preferably after every
> +``rte_graph_walk()`` iteration)
> +
>  override_index_cb()
>  ....................
>  A feature arc is :ref:`registered<Feature_Arc_Registration>` to operate on
> @@ -714,6 +721,179 @@ sub-system. If not called, feature arc has no impact on 
> application.
>     ``rte_graph_create()``. If not called, feature arc is a ``NOP`` to
>     application.
>
> +Runtime feature enable/disable
> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +A feature can be enabled or disabled at runtime from control thread using
> +``rte_graph_feature_enable()`` and ``rte_graph_feature_disable()`` APIs
> +respectively.
> +
> +.. code-block:: c
> +
> +    struct rte_rcu_qsbr *rcu_qsbr = app_get_rcu_qsbr();
> +    rte_graph_feature_arc_t _arc;
> +    uint16_t app_cookie;
> +
> +    if (rte_graph_feature_arc_lookup_by_name("Arc1", &_arc) < 0) {
> +        RTE_LOG(ERR, GRAPH, "Arc1 not found\n");
> +        return -ENOENT;
> +    }
> +    app_cookie = 100; /* Specific to ['Feature-1`, `port-0`]*/
> +
> +    /* Enable feature */
> +    rte_graph_feature_enable(_arc, 0 /* port-0 */,
> +                             "Feature-1" /* Name of the node feature */,
> +                             app_cookie, rcu_qsbr);
> +
> +    /* Disable feature */
> +    rte_graph_feature_disable(_arc, 0 /* port-0 */,
> +                              "Feature-1" /* Name of the node feature*/,
> +                              rcu_qsbr);
> +
> +.. note::
> +
> +   RCU argument is optional argument to enable/disable APIs. See
> +   :ref:`control/data plane
> +   synchronization<Control_Data_Plane_Synchronization>` and
> +   :ref:`notifier_cb<Feature_Notifier_Cb>` for more details on when RCU is
> +   needed.
> +
> +Fast path traversal rules
> +^^^^^^^^^^^^^^^^^^^^^^^^^
> +``Start node``
> +**************
> +If feature arc is :ref:`initialized<Feature_Arc_Initialization>`,
> +``start_node_feature_process_fn()`` will be called by ``rte_graph_walk()``
> +instead of node's original ``process()``. This function should allow packets 
> to
> +enter arc path whenever any feature is enabled at runtime
> +
> +.. code-block:: c
> +
> +    static int nodeA_init(const struct rte_graph *graph, struct rte_node 
> *node)
> +    {
> +        rte_graph_feature_arc_t _arc;
> +
> +        if (rte_graph_feature_arc_lookup_by_name("Arc1", &_arc) < 0) {
> +            RTE_LOG(ERR, GRAPH, "Arc1 not found\n");
> +            return -ENOENT;
> +        }
> +
> +        /* Save arc in node context */
> +        node->ctx = _arc;
> +        return 0;
> +    }
> +
> +    int nodeA_process_inline(struct rte_graph *graph, struct rte_node *node,
> +                             void **objs, uint16_t nb_objs,
> +                             struct rte_graph_feature_arc *arc,
> +                             const int do_arc_processing)
> +    {
> +        for(uint16_t i = 0; i < nb_objs; i++) {
> +            struct rte_mbuf *mbuf = objs[i];
> +            rte_edge_t edge_to_child = 0; /* By default to Node-B */
> +
> +            if (do_arc_processing) {
> +                struct rte_graph_feature_arc_mbuf_dynfields *dyn =
> +                    rte_graph_feature_arc_mbuf_dynfields_get(mbuf, 
> arc->mbuf_dyn_offset);
> +
> +                if (rte_graph_feature_data_first_feature_get(mbuf, 
> mbuf->port,
> +                                                             
> &dyn->feature_data,
> +                                                             &edge_to_child) 
> < 0) {
> +
> +                    /* Some feature is enabled, edge_to_child is overloaded*/
> +                }
> +            }
> +            /* enqueue as usual */
> +            rte_node_enqueue_x1(graph, node, mbuf, edge_to_child);
> +       }
> +    }
> +
> +    int nodeA_feature_process_fn(struct rte_graph *graph, struct rte_node 
> *node,
> +                                 void **objs, uint16_t nb_objs)
> +    {
> +        struct rte_graph_feature_arc *arc = 
> rte_graph_feature_arc_get(node->ctx);
> +
> +        if (unlikely(rte_graph_feature_arc_has_any_feature(arc)))
> +            return nodeA_process_inline(graph, node, objs, nb_objs, arc, 1 
> /* do arc processing */);
> +        else
> +            return nodeA_process_inline(graph, node, objs, nb_objs, NULL, 0 
> /* skip arc processing */);
> +    }
> +
> +``Feature nodes``
> +*****************
> +Following code-snippet explains fast path traversal rule for ``Feature-1``
> +:ref:`feature node<Feature_Nodes>` shown in :ref:`figure<Figure_Arc_2>`.
> +
> +.. code-block:: c
> +
> +    static int Feature1_node_init(const struct rte_graph *graph, struct 
> rte_node *node)
> +    {
> +        rte_graph_feature_arc_t _arc;
> +
> +        if (rte_graph_feature_arc_lookup_by_name("Arc1", &_arc) < 0) {
> +            RTE_LOG(ERR, GRAPH, "Arc1 not found\n");
> +            return -ENOENT;
> +        }
> +
> +        /* Save arc in node context */
> +        node->ctx = _arc;
> +        return 0;
> +    }
> +
> +    int feature1_process_inline(struct rte_graph *graph, struct rte_node 
> *node,
> +                                void **objs, uint16_t nb_objs,
> +                                struct rte_graph_feature_arc *arc)
> +    {
> +        for(uint16_t i = 0; i < nb_objs; i++) {
> +            struct rte_mbuf *mbuf = objs[i];
> +            rte_edge_t edge_to_child = 0; /* By default to Node-B */
> +
> +            struct rte_graph_feature_arc_mbuf_dynfields *dyn =
> +                    rte_graph_feature_arc_mbuf_dynfields_get(mbuf, 
> arc->mbuf_dyn_offset);
> +
> +            /* Get feature app cookie for mbuf */
> +            uint16_t app_cookie = 
> rte_graph_feature_data_app_cookie_get(mbuf, &dyn->feature_data);
> +
> +            if (feature_local_lookup(app_cookie) {
> +
> +                /* Packets is relevant to this feature. Move packet from arc 
> path */
> +                edge_to_child = X;
> +
> +            } else {
> +
> +                /* Packet not relevant to this feature. Send this packet to
> +                 * next enabled feature
> +                 */
> +                 rte_graph_feature_data_next_feature_get(mbuf, 
> &dyn->feature_data,
> +                                                         &edge_to_child);
> +            }
> +
> +            /* enqueue as usual */
> +            rte_node_enqueue_x1(graph, node, mbuf, edge_to_child);
> +       }
> +    }
> +
> +    int feature1_process_fn(struct rte_graph *graph, struct rte_node *node,
> +                           void **objs, uint16_t nb_objs)
> +    {
> +        struct rte_graph_feature_arc *arc = 
> rte_graph_feature_arc_get(node->ctx);
> +
> +        return feature1_process_inline(graph, node, objs, nb_objs, arc);
> +    }
> +
> +``End feature node``
> +********************
> +An end feature node is a feature node through which packets exits feature arc
> +path. It should not use any feature arc fast path APIs.
> +
> +Feature arc destroy
> +^^^^^^^^^^^^^^^^^^^
> +``rte_graph_feature_arc_destroy()`` can be used to free a arc object.
> +
> +Feature arc cleanup
> +^^^^^^^^^^^^^^^^^^^
> +``rte_graph_feature_arc_cleanup()`` can be used to free all resources
> +associated with feature arc module.
> +
>  Inbuilt Nodes
>  -------------
>
> diff --git a/lib/graph/graph_feature_arc.c b/lib/graph/graph_feature_arc.c
> index 568363c404..c7641ea619 100644
> --- a/lib/graph/graph_feature_arc.c
> +++ b/lib/graph/graph_feature_arc.c
> @@ -17,6 +17,11 @@
>
>  #define NUM_EXTRA_FEATURE_DATA   (2)
>
> +#define graph_uint_cast(f)             ((unsigned int)(f))
> +
> +#define fdata_fix_get(arc, feat, index)        \
> +                       RTE_GRAPH_FEATURE_TO_FEATURE_DATA(arc, feat, index)
> +
>  #define feat_dbg graph_dbg
>
>  #define FEAT_COND_ERR(cond, ...)                                           \
> @@ -59,6 +64,135 @@ static STAILQ_HEAD(, rte_graph_feature_arc_register) 
> feature_arc_list =
>  static STAILQ_HEAD(, rte_graph_feature_register) feature_list =
>                                         STAILQ_HEAD_INITIALIZER(feature_list);
>
> + /*
> +  * feature data index is not fixed for given [feature, index], although it 
> can
> +  * be, which is calculated as follows (fdata_fix_get())
> +  *
> +  * fdata = (arc->max_features * feature ) + index;
> +  *
> +  * But feature data index should not be fixed for any index. i.e
> +  * on any index, feature data can be placed. A slow path array is
> +  * maintained and within a feature range [start, end] it is checked where
> +  * feature_data_index is already placed.
> +  *
> +  * If is_release == false. feature_data_index is searched in a feature 
> range.
> +  * If found, index is returned. If not found, then reserve and return.
> +  *
> +  * If is_release == true, then feature_data_index is released for further
> +  * usage
> +  */
> +static rte_graph_feature_data_t
> +fdata_dyn_reserve_or_rel(struct rte_graph_feature_arc *arc, 
> rte_graph_feature_t f,
> +                        uint32_t index, bool is_release,
> +                        bool fdata_provided, rte_graph_feature_data_t fd)
> +{
> +       rte_graph_feature_data_t start, end, fdata;
> +       rte_graph_feature_t next_feat;
> +
> +       if (fdata_provided)
> +               fdata = fd;
> +       else
> +               fdata = fdata_fix_get(arc, f, index);
> +
> +       next_feat = f + 1;
> +       /* Find in a given feature range, feature data is stored or not */
> +       for (start = fdata_fix_get(arc, f, 0),
> +            end = fdata_fix_get(arc, next_feat, 0);
> +            start < end;
> +            start++) {
> +               if (arc->feature_data_by_index[start] == fdata) {
> +                       if (is_release)
> +                               arc->feature_data_by_index[start] = 
> RTE_GRAPH_FEATURE_DATA_INVALID;
> +
> +                       return start;
> +               }
> +       }
> +
> +       if (is_release)
> +               return RTE_GRAPH_FEATURE_DATA_INVALID;
> +
> +       /* If not found, then reserve valid one */
> +       for (start = fdata_fix_get(arc, f, 0),
> +            end = fdata_fix_get(arc, next_feat, 0);
> +            start < end;
> +            start++) {
> +               if (arc->feature_data_by_index[start] == 
> RTE_GRAPH_FEATURE_DATA_INVALID) {
> +                       arc->feature_data_by_index[start] = fdata;
> +                       return start;
> +               }
> +       }
> +
> +       return RTE_GRAPH_FEATURE_DATA_INVALID;
> +}
> +
> +static rte_graph_feature_data_t
> +fdata_reserve(struct rte_graph_feature_arc *arc,
> +             rte_graph_feature_t feature,
> +             uint32_t index)
> +{
> +       return fdata_dyn_reserve_or_rel(arc, feature + 1, index, false, 
> false, 0);
> +}
> +
> +static rte_graph_feature_data_t
> +fdata_release(struct rte_graph_feature_arc *arc,
> +             rte_graph_feature_t feature,
> +             uint32_t index)
> +{
> +       return fdata_dyn_reserve_or_rel(arc, feature + 1, index, true, false, 
> 0);
> +}
> +
> +static rte_graph_feature_data_t
> +first_fdata_reserve(struct rte_graph_feature_arc *arc,
> +                   uint32_t index)
> +{
> +       return fdata_dyn_reserve_or_rel(arc, 0, index, false, false, 0);
> +}
> +
> +static rte_graph_feature_data_t
> +first_fdata_release(struct rte_graph_feature_arc *arc,
> +                   uint32_t index)
> +{
> +       return fdata_dyn_reserve_or_rel(arc, 0, index, true, false, 0);
> +}
> +
> +static rte_graph_feature_data_t
> +extra_fdata_reserve(struct rte_graph_feature_arc *arc,
> +                   rte_graph_feature_t feature,
> +                   uint32_t index)
> +{
> +       rte_graph_feature_data_t fdata, fdata2;
> +       rte_graph_feature_t f;
> +
> +       f = arc->num_added_features + NUM_EXTRA_FEATURE_DATA - 1;
> +
> +       fdata = fdata_dyn_reserve_or_rel(arc, f, index,
> +                                        false, true, fdata_fix_get(arc, 
> feature + 1, index));
> +
> +       /* we do not have enough space in as
> +        * extra fdata accommodates indexes for all features
> +        * Needed (feature * index) space but has only (index) number of 
> space.
> +        * So dynamic allocation can fail.  When fail use static allocation
> +        */
> +       if (fdata == RTE_GRAPH_FEATURE_DATA_INVALID) {
> +               fdata = fdata_fix_get(arc, feature + 1, index);
> +               fdata2 = fdata_fix_get(arc, f, index);
> +               arc->feature_data_by_index[fdata2] = fdata;
> +       }
> +       return fdata;
> +}
> +
> +static rte_graph_feature_data_t
> +extra_fdata_release(struct rte_graph_feature_arc *arc,
> +                   rte_graph_feature_t feature,
> +                   uint32_t index)
> +{
> +       rte_graph_feature_t f;
> +
> +       f = arc->num_added_features + NUM_EXTRA_FEATURE_DATA - 1;
> +       return fdata_dyn_reserve_or_rel(arc, f, index,
> +                                       true, true, fdata_fix_get(arc, 
> feature + 1, index));
> +}
> +
>  /* feature registration validate */
>  static int
>  feature_registration_validate(struct rte_graph_feature_register *feat_entry,
> @@ -339,7 +473,10 @@ graph_first_feature_data_ptr_get(struct 
> rte_graph_feature_arc *arc,
>  static int
>  feature_arc_data_reset(struct rte_graph_feature_arc *arc)
>  {
> +       rte_graph_feature_data_t first_fdata;
> +       struct rte_graph_feature_data *fdata;
>         rte_graph_feature_data_t *f = NULL;
> +       rte_graph_feature_t iter;
>         uint16_t index;
>
>         arc->runtime_enabled_features = 0;
> @@ -349,6 +486,15 @@ feature_arc_data_reset(struct rte_graph_feature_arc *arc)
>                 *f = RTE_GRAPH_FEATURE_DATA_INVALID;
>         }
>
> +       for (iter = 0; iter < arc->max_features + NUM_EXTRA_FEATURE_DATA; 
> iter++) {
> +               first_fdata = fdata_fix_get(arc, iter, 0);
> +               for (index = 0; index < arc->max_indexes; index++) {
> +                       fdata = rte_graph_feature_data_get(arc, first_fdata + 
> index);
> +                       fdata->next_feature_data = 
> RTE_GRAPH_FEATURE_DATA_INVALID;
> +                       fdata->app_cookie = UINT16_MAX;
> +                       fdata->next_edge = RTE_EDGE_ID_INVALID;
> +               }
> +       }
>         return 0;
>  }
>
> @@ -370,7 +516,6 @@ nodeinfo_lkup_by_name(struct rte_graph_feature_arc *arc, 
> const char *feat_name,
>                 *slot = UINT32_MAX;
>
>         STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> -               RTE_VERIFY(finfo->feature_arc == arc);
>                 if (!strncmp(finfo->feature_name, feat_name, 
> strlen(finfo->feature_name))) {
>                         if (ffinfo)
>                                 *ffinfo = finfo;
> @@ -398,7 +543,6 @@ nodeinfo_add_lookup(struct rte_graph_feature_arc *arc, 
> const char *feat_node_nam
>                 *slot = 0;
>
>         STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> -               RTE_VERIFY(finfo->feature_arc == arc);
>                 if (!strncmp(finfo->feature_name, feat_node_name, 
> strlen(finfo->feature_name))) {
>                         if (ffinfo)
>                                 *ffinfo = finfo;
> @@ -432,7 +576,7 @@ nodeinfo_lkup_by_index(struct rte_graph_feature_arc *arc, 
> uint32_t feature_index
>                 /* Check sanity */
>                 if (do_sanity_check)
>                         if (finfo->finfo_index != index)
> -                               RTE_VERIFY(0);
> +                               return -1;
>                 if (index == feature_index) {
>                         *ppfinfo = finfo;
>                         return 0;
> @@ -477,6 +621,102 @@ get_existing_edge(const char *arc_name, rte_node_t 
> parent_node,
>         return -1;
>  }
>
> +
> +/* prepare feature arc after addition of all features */
> +static int
> +prepare_feature_arc_before_first_enable(struct rte_graph_feature_arc *arc)
> +{
> +       struct rte_graph_feature_node_list *lfinfo = NULL;
> +       struct rte_graph_feature_node_list *finfo = NULL;
> +       char name[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
> +       uint32_t findex = 0, iter;
> +       uint16_t num_fdata;
> +       rte_edge_t edge;
> +       size_t sz = 0;
> +
> +       STAILQ_FOREACH(lfinfo, &arc->all_features, next_feature) {
> +               lfinfo->finfo_index = findex;
> +               findex++;
> +       }
> +       if (!findex) {
> +               graph_err("No feature added to arc: %s", 
> arc->feature_arc_name);
> +               return -1;
> +       }
> +       arc->num_added_features = findex;
> +       num_fdata = arc->num_added_features + NUM_EXTRA_FEATURE_DATA;
> +
> +       sz = num_fdata * arc->max_indexes * sizeof(rte_graph_feature_data_t);
> +
> +       snprintf(name, sizeof(name), "%s-fdata", arc->feature_arc_name);
> +
> +       arc->feature_data_by_index = rte_malloc(name, sz, 0);
> +       if (!arc->feature_data_by_index) {
> +               graph_err("fdata/index rte_malloc failed for %s", name);
> +               return -1;
> +       }
> +
> +       for (iter = 0; iter < (num_fdata * arc->max_indexes); iter++)
> +               arc->feature_data_by_index[iter] = 
> RTE_GRAPH_FEATURE_DATA_INVALID;
> +
> +       /* Grab finfo corresponding to end_feature */
> +       nodeinfo_lkup_by_index(arc, arc->num_added_features - 1, &lfinfo, 0);
> +
> +       /* lfinfo should be the info corresponding to end_feature
> +        * Add edge from all features to end feature node to have exception 
> path
> +        * in fast path from all feature nodes to end feature node during 
> enable/disable
> +        */
> +       if (lfinfo->feature_node_id != arc->end_feature.feature_node_id) {
> +               graph_err("end_feature node mismatch [found-%s: exp-%s]",
> +                         rte_node_id_to_name(lfinfo->feature_node_id),
> +                         
> rte_node_id_to_name(arc->end_feature.feature_node_id));
> +               goto free_fdata_by_index;
> +       }
> +
> +       STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> +               if (get_existing_edge(arc->feature_arc_name, 
> arc->start_node->id,
> +                                     finfo->feature_node_id, &edge)) {
> +                       graph_err("No edge found from %s to %s",
> +                                 rte_node_id_to_name(arc->start_node->id),
> +                                 
> rte_node_id_to_name(finfo->feature_node_id));
> +                       goto free_fdata_by_index;
> +               }
> +               finfo->edge_to_this_feature = edge;
> +
> +               if (finfo == lfinfo)
> +                       continue;
> +
> +               if (get_existing_edge(arc->feature_arc_name, 
> finfo->feature_node_id,
> +                                     lfinfo->feature_node_id, &edge)) {
> +                       graph_err("No edge found from %s to %s",
> +                                 rte_node_id_to_name(finfo->feature_node_id),
> +                                 
> rte_node_id_to_name(lfinfo->feature_node_id));
> +                       goto free_fdata_by_index;
> +               }
> +               finfo->edge_to_last_feature = edge;
> +       }
> +       /**
> +        * Enable end_feature in control bitmask
> +        * (arc->feature_bit_mask_by_index) but not in fast path bitmask
> +        * arc->fp_feature_enable_bitmask. This is due to:
> +        * 1. Application may not explicitly enabling end_feature node
> +        * 2. However it should be enabled internally so that when a feature 
> is
> +        *    disabled (say on an interface), next_edge of data should be
> +        *    updated to end_feature node hence packet can exit arc.
> +        * 3. We do not want to set bit for end_feature in fast path bitmask 
> as
> +        *    it will void the purpose of fast path APIs
> +        *    rte_graph_feature_arc_is_any_feature_enabled(). Since enabling
> +        *    end_feature would make these APIs to always return "true"
> +        */
> +       for (iter = 0; iter < arc->max_indexes; iter++)
> +               arc->feature_bit_mask_by_index[iter] |= (1 << 
> lfinfo->finfo_index);
> +
> +       return 0;
> +
> +free_fdata_by_index:
> +       rte_free(arc->feature_data_by_index);
> +       return -1;
> +}
> +
>  /* feature arc sanity */
>  static int
>  feature_arc_sanity(rte_graph_feature_arc_t _arc)
> @@ -586,6 +826,241 @@ feature_arc_main_init(rte_graph_feature_arc_main_t 
> **pfl, uint32_t max_feature_a
>         return 0;
>  }
>
> +static int
> +feature_enable_disable_validate(rte_graph_feature_arc_t _arc, uint32_t index,
> +                               const char *feature_name,
> +                               int is_enable_disable, bool emit_logs)
> +{
> +       struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> +       struct rte_graph_feature_node_list *finfo = NULL;
> +       uint32_t slot, last_end_feature;
> +
> +       if (!arc)
> +               return -EINVAL;
> +
> +       /* validate _arc */
> +       if (arc->feature_arc_main != __rte_graph_feature_arc_main) {
> +               FEAT_COND_ERR(emit_logs, "invalid feature arc: 0x%x", _arc);
> +               return -EINVAL;
> +       }
> +
> +       /* validate index */
> +       if (index >= arc->max_indexes) {
> +               FEAT_COND_ERR(emit_logs, "%s: Invalid provided index: %u >= 
> %u configured",
> +                             arc->feature_arc_name, index, arc->max_indexes);
> +               return -1;
> +       }
> +
> +       /* validate feature_name is already added or not  */
> +       if (nodeinfo_lkup_by_name(arc, feature_name, &finfo, &slot)) {
> +               FEAT_COND_ERR(emit_logs, "%s: No feature %s added",
> +                             arc->feature_arc_name, feature_name);
> +               return -EINVAL;
> +       }
> +
> +       if (!finfo) {
> +               FEAT_COND_ERR(emit_logs, "%s: No feature: %s found to 
> enable/disable",
> +                             arc->feature_arc_name, feature_name);
> +               return -EINVAL;
> +       }
> +
> +       /* slot should be in valid range */
> +       if (slot >= arc->num_added_features) {
> +               FEAT_COND_ERR(emit_logs, "%s/%s: Invalid free slot %u(max=%u) 
> for feature",
> +                             arc->feature_arc_name, feature_name, slot, 
> arc->num_added_features);
> +               return -EINVAL;
> +       }
> +
> +       /* slot should be in range of 0 - 63 */
> +       if (slot > (GRAPH_FEATURE_MAX_NUM_PER_ARC - 1)) {
> +               FEAT_COND_ERR(emit_logs, "%s/%s: Invalid slot: %u", 
> arc->feature_arc_name,
> +                             feature_name, slot);
> +               return -EINVAL;
> +       }
> +
> +       last_end_feature = rte_fls_u64(arc->feature_bit_mask_by_index[index]);
> +       if (!last_end_feature) {
> +               FEAT_COND_ERR(emit_logs, "%s: End feature not enabled", 
> arc->feature_arc_name);
> +               return -EINVAL;
> +       }
> +
> +       /* if enabled feature is not end feature node and already enabled */
> +       if (is_enable_disable &&
> +           (arc->feature_bit_mask_by_index[index] & RTE_BIT64(slot)) &&
> +           (slot != (last_end_feature - 1))) {
> +               FEAT_COND_ERR(emit_logs, "%s: %s already enabled on index: 
> %u",
> +                             arc->feature_arc_name, feature_name, index);
> +               return -1;
> +       }
> +
> +       if (!is_enable_disable && !arc->runtime_enabled_features) {
> +               FEAT_COND_ERR(emit_logs, "%s: No feature enabled to disable",
> +                             arc->feature_arc_name);
> +               return -1;
> +       }
> +
> +       if (!is_enable_disable && !(arc->feature_bit_mask_by_index[index] & 
> RTE_BIT64(slot))) {
> +               FEAT_COND_ERR(emit_logs, "%s: %s not enabled in bitmask for 
> index: %u",
> +                             arc->feature_arc_name, feature_name, index);
> +               return -1;
> +       }
> +
> +       /* If no feature has been enabled, avoid extra sanity checks */
> +       if (!arc->runtime_enabled_features)
> +               return 0;
> +
> +       if (finfo->finfo_index != slot) {
> +               FEAT_COND_ERR(emit_logs,
> +                             "%s/%s: lookup slot mismatch for finfo idx: %u 
> and lookup slot: %u",
> +                             arc->feature_arc_name, feature_name, 
> finfo->finfo_index, slot);
> +               return -1;
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +refill_fastpath_data(struct rte_graph_feature_arc *arc, uint32_t feature_bit,
> +                    uint16_t index /* array index */, int is_enable_disable)
> +{
> +       struct rte_graph_feature_data *gfd = NULL, *prev_gfd = NULL, *fdptr = 
> NULL;
> +       struct rte_graph_feature_node_list *finfo = NULL, *prev_finfo = NULL;
> +       RTE_ATOMIC(rte_graph_feature_data_t) * first_fdata = NULL;
> +       uint32_t fi = 0, prev_fi = 0, next_fi = 0, cfi = 0;
> +       uint64_t bitmask = 0, prev_bitmask, next_bitmask;
> +       rte_graph_feature_data_t *__first_fd = NULL;
> +       rte_edge_t edge = RTE_EDGE_ID_INVALID;
> +       rte_graph_feature_data_t fdata, _fd;
> +       bool update_first_feature = false;
> +
> +       if (is_enable_disable)
> +               bitmask = RTE_BIT64(feature_bit);
> +
> +       /* set bit from (feature_bit + 1) to 64th bit */
> +       next_bitmask = UINT64_MAX << (feature_bit + 1);
> +
> +       /* set bits from 0 to (feature_bit - 1) */
> +       prev_bitmask = ((UINT64_MAX & ~next_bitmask) & 
> ~(RTE_BIT64(feature_bit)));
> +
> +       next_bitmask &= arc->feature_bit_mask_by_index[index];
> +       prev_bitmask &= arc->feature_bit_mask_by_index[index];
> +
> +       /* Set next bit set in next_bitmask */
> +       if (rte_bsf64_safe(next_bitmask, &next_fi))
> +               bitmask |= RTE_BIT64(next_fi);
> +
> +       /* Set prev bit set in prev_bitmask*/
> +       prev_fi = rte_fls_u64(prev_bitmask);
> +       if (prev_fi)
> +               bitmask |= RTE_BIT64(prev_fi - 1);
> +
> +       /* for each feature set for index, set fast path data */
> +       prev_gfd = NULL;
> +       while (rte_bsf64_safe(bitmask, &fi)) {
> +               _fd = fdata_reserve(arc, fi, index);
> +               gfd = rte_graph_feature_data_get(arc, _fd);
> +
> +               if (nodeinfo_lkup_by_index(arc, fi, &finfo, 1) < 0) {
> +                       graph_err("[%s/index:%2u,cookie:%u]: No finfo found 
> for index: %u",
> +                                 arc->feature_arc_name, index, 
> gfd->app_cookie, fi);
> +                       return -1;
> +               }
> +
> +               /* Reset next edge to point to last feature node so that 
> packet
> +                * can exit from arc
> +                */
> +               rte_atomic_store_explicit(&gfd->next_edge,
> +                                         finfo->edge_to_last_feature,
> +                                         rte_memory_order_relaxed);
> +
> +               /* If previous feature_index was valid in last loop */
> +               if (prev_gfd != NULL) {
> +                       /*
> +                        * Get edge of previous feature node connecting
> +                        * to this feature node
> +                        */
> +                       if (nodeinfo_lkup_by_index(arc, prev_fi, &prev_finfo, 
> 1) < 0) {
> +                               graph_err("[%s/index:%2u,cookie:%u]: No 
> prev_finfo found idx: %u",
> +                                         arc->feature_arc_name, index, 
> gfd->app_cookie, prev_fi);
> +                               return -1;
> +                       }
> +
> +                       if (!get_existing_edge(arc->feature_arc_name,
> +                                             prev_finfo->feature_node_id,
> +                                             finfo->feature_node_id, &edge)) 
> {
> +                               feat_dbg("\t[%s/index:%2u,cookie:%u]: 
> (%u->%u)%s[%u] = %s",
> +                                        arc->feature_arc_name, index,
> +                                        gfd->app_cookie, prev_fi, fi,
> +                                        
> rte_node_id_to_name(prev_finfo->feature_node_id),
> +                                        edge, 
> rte_node_id_to_name(finfo->feature_node_id));
> +
> +                               
> rte_atomic_store_explicit(&prev_gfd->next_edge,
> +                                                         edge,
> +                                                         
> rte_memory_order_relaxed);
> +
> +                               
> rte_atomic_store_explicit(&prev_gfd->next_feature_data, _fd,
> +                                                         
> rte_memory_order_relaxed);
> +                       } else {
> +                               /* Should not fail */
> +                               graph_err("[%s/index:%2u,cookie:%u]: No edge 
> found from %s to %s",
> +                                         arc->feature_arc_name, index, 
> gfd->app_cookie,
> +                                         
> rte_node_id_to_name(prev_finfo->feature_node_id),
> +                                         
> rte_node_id_to_name(finfo->feature_node_id));
> +                               return -1;
> +                       }
> +               }
> +               /* On first feature
> +                * 1. Update fdata with next_edge from start_node to feature 
> node
> +                * 2. Update first enabled feature in its index array
> +                */
> +               if (rte_bsf64_safe(arc->feature_bit_mask_by_index[index], 
> &cfi)) {
> +                       update_first_feature = (cfi == fi) ? true : false;
> +
> +                       if (update_first_feature) {
> +                               feat_dbg("\t[%s/index:%2u,cookie:%u]: 
> (->%u)%s[%u]=%s",
> +                                        arc->feature_arc_name, index,
> +                                        gfd->app_cookie, fi,
> +                                        arc->start_node->name, 
> finfo->edge_to_this_feature,
> +                                        
> rte_node_id_to_name(finfo->feature_node_id));
> +
> +                               /* Reserve feature data @0th index for first 
> feature */
> +                               fdata = first_fdata_reserve(arc, index);
> +                               fdptr = rte_graph_feature_data_get(arc, 
> fdata);
> +
> +                               /* add next edge into feature data
> +                                * First set feature data then first feature 
> memory
> +                                */
> +                               rte_atomic_store_explicit(&fdptr->next_edge,
> +                                                         
> finfo->edge_to_this_feature,
> +                                                         
> rte_memory_order_relaxed);
> +
> +                               
> rte_atomic_store_explicit(&fdptr->next_feature_data,
> +                                                         _fd,
> +                                                         
> rte_memory_order_relaxed);
> +
> +                               __first_fd = 
> graph_first_feature_data_ptr_get(arc, index);
> +                               first_fdata = 
> (RTE_ATOMIC(rte_graph_feature_data_t) *)__first_fd;
> +
> +                               /* Save reserved feature data @fp_index */
> +                               rte_atomic_store_explicit(first_fdata, fdata,
> +                                                         
> rte_memory_order_relaxed);
> +                       }
> +               }
> +               prev_fi = fi;
> +               prev_gfd = gfd;
> +               /* Clear current feature index */
> +               bitmask &= ~RTE_BIT64(fi);
> +       }
> +       /* If all features are disabled on index, except end feature
> +        * then release 0th index
> +        */
> +       if (!is_enable_disable &&
> +           (rte_popcount64(arc->feature_bit_mask_by_index[index]) == 1))
> +               first_fdata_release(arc, index);
> +
> +       return 0;
> +}
> +
>  /* feature arc initialization, public API */
>  RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_arc_init, 25.07);
>  int
> @@ -1128,6 +1603,199 @@ rte_graph_feature_lookup(rte_graph_feature_arc_t 
> _arc, const char *feature_name,
>         return -1;
>  }
>
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_enable, 25.07);
> +int
> +rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index,
> +                        const char *feature_name, uint16_t app_cookie,
> +                        struct rte_rcu_qsbr *qsbr)
> +{
> +       struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> +       struct rte_graph_feature_node_list *finfo = NULL;
> +       struct rte_graph_feature_data *gfd = NULL;
> +       uint64_t bitmask;
> +       uint32_t slot;
> +
> +       if (!arc) {
> +               graph_err("Invalid feature arc: 0x%x", _arc);
> +               return -1;
> +       }
> +
> +       feat_dbg("%s: Enabling feature: %s for index: %u",
> +                arc->feature_arc_name, feature_name, index);
> +
> +       if ((!arc->runtime_enabled_features &&
> +           (prepare_feature_arc_before_first_enable(arc) < 0)))
> +               return -1;
> +
> +       if (feature_enable_disable_validate(_arc, index, feature_name, 1 /* 
> enable */, true))
> +               return -1;
> +
> +       /** This should not fail as validate() has passed */
> +       if (nodeinfo_lkup_by_name(arc, feature_name, &finfo, &slot))
> +               return -1;
> +
> +       gfd = rte_graph_feature_data_get(arc, fdata_reserve(arc, slot, 
> index));
> +
> +       /* Set current app_cookie */
> +       rte_atomic_store_explicit(&gfd->app_cookie, app_cookie, 
> rte_memory_order_relaxed);
> +
> +       /* Set bitmask in control path bitmask */
> +       rte_bit_relaxed_set64(graph_uint_cast(slot), 
> &arc->feature_bit_mask_by_index[index]);
> +
> +       if (refill_fastpath_data(arc, slot, index, 1 /* enable */) < 0)
> +               return -1;
> +
> +       /* On very first feature enable instance */
> +       if (!finfo->ref_count) {
> +               /* If first time feature getting enabled
> +                */
> +               bitmask = 
> rte_atomic_load_explicit(&arc->fp_feature_enable_bitmask,
> +                                                  rte_memory_order_relaxed);
> +
> +               bitmask |= RTE_BIT64(slot);
> +
> +               rte_atomic_store_explicit(&arc->fp_feature_enable_bitmask,
> +                                         bitmask, rte_memory_order_relaxed);
> +       }
> +
> +       /* Slow path updates */
> +       arc->runtime_enabled_features++;
> +
> +       /* Increase feature node info reference count */
> +       finfo->ref_count++;
> +
> +       /* Release extra fdata, if reserved before */
> +       extra_fdata_release(arc, slot, index);
> +
> +       if (qsbr)
> +               rte_rcu_qsbr_synchronize(qsbr, RTE_QSBR_THRID_INVALID);
> +
> +       if (finfo->notifier_cb)
> +               finfo->notifier_cb(arc->feature_arc_name, finfo->feature_name,
> +                                  finfo->feature_node_id, index,
> +                                  true /* enable */, gfd->app_cookie);
> +
> +       return 0;
> +}
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_disable, 25.07);
> +int
> +rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index, 
> const char *feature_name,
> +                         struct rte_rcu_qsbr *qsbr)
> +{
> +       struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> +       struct rte_graph_feature_data *gfd = NULL, *extra_gfd = NULL;
> +       struct rte_graph_feature_node_list *finfo = NULL;
> +       rte_graph_feature_data_t extra_fdata;
> +       uint32_t slot, last_end_feature;
> +       uint64_t bitmask;
> +
> +       if (!arc) {
> +               graph_err("Invalid feature arc: 0x%x", _arc);
> +               return -1;
> +       }
> +       feat_dbg("%s: Disable feature: %s for index: %u",
> +                arc->feature_arc_name, feature_name, index);
> +
> +       if (feature_enable_disable_validate(_arc, index, feature_name, 0, 
> true))
> +               return -1;
> +
> +       if (nodeinfo_lkup_by_name(arc, feature_name, &finfo, &slot))
> +               return -1;
> +
> +       last_end_feature = rte_fls_u64(arc->feature_bit_mask_by_index[index]);
> +       if (last_end_feature != arc->num_added_features) {
> +               graph_err("%s/%s: No end feature enabled",
> +                         arc->feature_arc_name, feature_name);
> +               return -1;
> +       }
> +
> +       /* If feature is not last feature, unset in control plane bitmask */
> +       last_end_feature = arc->num_added_features - 1;
> +       if (slot != last_end_feature)
> +               rte_bit_relaxed_clear64(graph_uint_cast(slot),
> +                                       
> &arc->feature_bit_mask_by_index[index]);
> +
> +       /* we have allocated one extra feature data space. Get extra feature 
> data
> +        * No need to reserve instead use fixed  extra data for an index
> +        */
> +       extra_fdata = extra_fdata_reserve(arc, slot, index);
> +       extra_gfd = rte_graph_feature_data_get(arc, extra_fdata);
> +
> +       gfd = rte_graph_feature_data_get(arc, fdata_reserve(arc, slot, 
> index));
> +
> +       /*
> +        * Packets may have reached to feature node which is getting disabled.
> +        * We want to steer those packets to last feature node so that they 
> can
> +        * exit arc
> +        * - First, reset next_edge of extra feature data to point to 
> last_feature_node
> +        * - Secondly, reset next_feature_data of current feature getting 
> disabled to extra
> +        *   feature data
> +        */
> +       rte_atomic_store_explicit(&extra_gfd->next_edge, 
> finfo->edge_to_last_feature,
> +                                 rte_memory_order_relaxed);
> +       rte_atomic_store_explicit(&extra_gfd->next_feature_data, 
> RTE_GRAPH_FEATURE_DATA_INVALID,
> +                                 rte_memory_order_relaxed);
> +       rte_atomic_store_explicit(&gfd->next_feature_data, extra_fdata,
> +                                 rte_memory_order_relaxed);
> +       rte_atomic_store_explicit(&gfd->next_edge, 
> finfo->edge_to_last_feature,
> +                                 rte_memory_order_relaxed);
> +
> +       /* Now we can unwire fast path*/
> +       if (refill_fastpath_data(arc, slot, index, 0 /* disable */) < 0)
> +               return -1;
> +
> +       finfo->ref_count--;
> +
> +       /* When last feature is disabled */
> +       if (!finfo->ref_count) {
> +               /* If no feature enabled, reset feature in u64 fast path 
> bitmask */
> +               bitmask = 
> rte_atomic_load_explicit(&arc->fp_feature_enable_bitmask,
> +                                                  rte_memory_order_relaxed);
> +               bitmask &= ~(RTE_BIT64(slot));
> +               rte_atomic_store_explicit(&arc->fp_feature_enable_bitmask, 
> bitmask,
> +                                         rte_memory_order_relaxed);
> +       }
> +
> +       if (qsbr)
> +               rte_rcu_qsbr_synchronize(qsbr, RTE_QSBR_THRID_INVALID);
> +
> +       /* Call notifier cb with valid app_cookie */
> +       if (finfo->notifier_cb)
> +               finfo->notifier_cb(arc->feature_arc_name, finfo->feature_name,
> +                                  finfo->feature_node_id, index,
> +                                  false /* disable */, gfd->app_cookie);
> +
> +       /*
> +        * 1. Do not reset gfd for now as feature node might be in execution
> +        *
> +        * 2. We also don't call fdata_release() as that may return same
> +        * feature_data for other index for case like:
> +        *
> +        * feature_enable(arc, index-0, feature_name, cookie1);
> +        * feature_enable(arc, index-1, feature_name, cookie2);
> +        *
> +        * Second call can return same fdata which we avoided releasing here.
> +        * In order to make above case work, application has to mandatory use
> +        * RCU mechanism. For now fdata is not released until arc_destroy
> +        *
> +        * Only exception is
> +        * for(i=0; i< 100; i++) {
> +        *   feature_enable(arc, index-0, feature_name, cookie1);
> +        *   feature_disable(arc, index-0, feature_name, cookie1);
> +        * }
> +        * where RCU should be used but this is not valid use-case from 
> control plane.
> +        * If it is valid use-case then provide RCU argument
> +        */
> +
> +       /* Reset app_cookie later after calling notifier_cb */
> +       rte_atomic_store_explicit(&gfd->app_cookie, UINT16_MAX, 
> rte_memory_order_relaxed);
> +
> +       arc->runtime_enabled_features--;
> +
> +       return 0;
> +}
> +
>  RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_arc_destroy, 25.07);
>  int
>  rte_graph_feature_arc_destroy(rte_graph_feature_arc_t _arc)
> @@ -1135,6 +1803,8 @@ rte_graph_feature_arc_destroy(rte_graph_feature_arc_t 
> _arc)
>         struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
>         rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
>         struct rte_graph_feature_node_list *node_info = NULL;
> +       struct rte_graph_feature_data *fdptr = NULL;
> +       rte_graph_feature_data_t fdata;
>         int iter;
>
>         if (!arc) {
> @@ -1153,11 +1823,28 @@ rte_graph_feature_arc_destroy(rte_graph_feature_arc_t 
> _arc)
>                                     RTE_BIT64(node_info->finfo_index)))
>                                         continue;
>
> -                               node_info->notifier_cb(arc->feature_arc_name,
> -                                                      
> node_info->feature_name,
> -                                                      
> node_info->feature_node_id,
> -                                                      iter, false /* disable 
> */,
> -                                                      UINT16_MAX /* invalid 
> cookie */);
> +                               /* fdata_reserve would return already 
> allocated
> +                                * fdata for [finfo_index, iter]
> +                                */
> +                               fdata = fdata_reserve(arc, 
> node_info->finfo_index, iter);
> +                               if (fdata != RTE_GRAPH_FEATURE_DATA_INVALID) {
> +                                       fdptr = 
> rte_graph_feature_data_get(arc, fdata);
> +                                       
> node_info->notifier_cb(arc->feature_arc_name,
> +                                                              
> node_info->feature_name,
> +                                                              
> node_info->feature_node_id,
> +                                                              iter, false /* 
> disable */,
> +                                                              
> fdptr->app_cookie);
> +                               } else {
> +                                       
> node_info->notifier_cb(arc->feature_arc_name,
> +                                                              
> node_info->feature_name,
> +                                                              
> node_info->feature_node_id,
> +                                                              iter, false /* 
> disable */,
> +                                                              UINT16_MAX /* 
> invalid cookie */);
> +                               }
> +                               /* fdata_release() is not used yet, use it 
> for sake
> +                                * of function unused warnings
> +                                */
> +                               fdata = fdata_release(arc, 
> node_info->finfo_index, iter);
>                         }
>                 }
>                 rte_free(node_info);
> @@ -1237,6 +1924,20 @@ rte_graph_feature_arc_lookup_by_name(const char 
> *arc_name, rte_graph_feature_arc
>         return -1;
>  }
>
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_arc_num_enabled_features, 
> 25.07);
> +uint32_t
> +rte_graph_feature_arc_num_enabled_features(rte_graph_feature_arc_t _arc)
> +{
> +       struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> +
> +       if (!arc) {
> +               graph_err("Invalid feature arc: 0x%x", _arc);
> +               return 0;
> +       }
> +
> +       return arc->runtime_enabled_features;
> +}
> +
>  RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_graph_feature_arc_num_features, 25.07);
>  uint32_t
>  rte_graph_feature_arc_num_features(rte_graph_feature_arc_t _arc)
> diff --git a/lib/graph/meson.build b/lib/graph/meson.build
> index 6a6d570290..d48d49122d 100644
> --- a/lib/graph/meson.build
> +++ b/lib/graph/meson.build
> @@ -27,4 +27,4 @@ indirect_headers += files(
>          'rte_graph_worker_common.h',
>  )
>
> -deps += ['eal', 'pcapng', 'mempool', 'ring']
> +deps += ['eal', 'pcapng', 'mempool', 'ring', 'rcu']
> diff --git a/lib/graph/rte_graph_feature_arc.h 
> b/lib/graph/rte_graph_feature_arc.h
> index 49392f2e05..14f24be831 100644
> --- a/lib/graph/rte_graph_feature_arc.h
> +++ b/lib/graph/rte_graph_feature_arc.h
> @@ -18,6 +18,7 @@
>  #include <rte_compat.h>
>  #include <rte_debug.h>
>  #include <rte_graph.h>
> +#include <rte_rcu_qsbr.h>
>
>  #ifdef __cplusplus
>  extern "C" {
> @@ -49,7 +50,7 @@ extern "C" {
>   * plane. Protocols enabled on one interface may not be enabled on another
>   * interface.
>   *
> - * When more than one protocols are present at a networking layer (say IPv4,
> + * When more than one protocols are present in a networking layer (say IPv4,
>   * IP tables, IPsec etc), it becomes imperative to steer packets (in 
> dataplane)
>   * across each protocol processing in a defined sequential order. In ingress
>   * direction, stack decides to perform IPsec decryption first before IP
> @@ -92,7 +93,9 @@ extern "C" {
>   * A feature arc in a graph is represented via *start_node* and
>   * *end_feature_node*.  Feature nodes are added between start_node and
>   * end_feature_node. Packets enter feature arc path via start_node while they
> - * exit from end_feature_node.
> + * exit from end_feature_node. Packets steering from start_node to feature
> + * nodes are controlled in control plane via rte_graph_feature_enable(),
> + * rte_graph_feature_disable().
>   *
>   * This library facilitates rte graph based applications to implement stack
>   * functionalities described above by providing "edge" to the next enabled
> @@ -101,7 +104,7 @@ extern "C" {
>   * In order to use feature-arc APIs, applications needs to do following in
>   * control plane:
>   * - Create feature arc object using RTE_GRAPH_FEATURE_ARC_REGISTER()
> - * - New feature nodes (In-built/Out-of-tree) can be added to an arc via
> + * - New feature nodes (In-built or out-of-tree) can be added to an arc via
>   *   RTE_GRAPH_FEATURE_REGISTER(). RTE_GRAPH_FEATURE_REGISTER() has
>   *   rte_graph_feature_register::runs_after and
>   *   rte_graph_feature_register::runs_before to specify protocol
> @@ -109,6 +112,8 @@ extern "C" {
>   * - Before calling rte_graph_create(), rte_graph_feature_arc_init() API must
>   *   be called. If rte_graph_feature_arc_init() is not called by application,
>   *   feature arc library has no affect.
> + * - Features can be enabled/disabled on any index at runtime via
> + *   rte_graph_feature_enable(), rte_graph_feature_disable().
>   * - Feature arc can be destroyed via rte_graph_feature_arc_destroy()
>   *
>   * If a given feature likes to control number of indexes (which is higher 
> than
> @@ -119,10 +124,66 @@ extern "C" {
>   * maximum value returned by any of the feature is used for
>   * rte_graph_feature_arc_create()
>   *
> + * Before enabling a feature, control plane might allocate certain resources
> + * (like VRF table for IP lookup or IPsec SA for inbound policy etc). A
> + * reference of allocated resource can be passed from control plane to
> + * dataplane via *app_cookie* argument in @ref rte_graph_feature_enable(). A
> + * corresponding dataplane API @ref rte_graph_feature_data_app_cookie_get() 
> can
> + * be used to retrieve same cookie in fast path.
> + *
> + * When a feature is disabled, resources allocated during feature enable can 
> be
> + * safely released via registering a callback in
> + * rte_graph_feature_register::notifier_cb(). See fast path synchronization
> + * section below for more details.
> + *
> + * If current feature node is not consuming packet, it might want to send it 
> to
> + * next enabled feature. Depending upon current node is a:
> + * - start_node (via @ref rte_graph_feature_data_first_feature_get())
> + * - feature nodes added between start_node and end_node (via @ref
> + *   rte_graph_feature_data_next_feature_get())
> + * - end_feature_node (must not call any feature arc steering APIs) as from
> + *   this node packet exits feature arc
> + *
> + * Above APIs deals with fast path object: feature_data (struct
> + * rte_graph_feature_data), which is unique for every index per feature with 
> in
> + * a feature arc. It holds three data fields: next node edge, next enabled
> + * feature data and app_cookie.
> + *
> + * rte_mbuf carries [feature_data] into feature arc specific mbuf dynamic
> + * field. See @ref rte_graph_feature_arc_mbuf_dynfields and @ref
> + * rte_graph_feature_arc_mbuf_dynfields_get() for more details.
> + *
> + * Fast path synchronization
> + * -------------------------
> + * Any feature enable/disable in control plane does not require stopping of
> + * worker cores. rte_graph_feature_enable()/rte_graph_feature_disable() APIs
> + * are almost thread-safe avoiding any RCU usage. Only condition when race
> + * condition could occur is when application is trying to enable/disable
> + * feature very fast for [feature, index] combination. In that case,
> + * application should use rte_graph_feature_enable(),
> + * rte_graph_feature_disable() APIs with RCU argument
> + *
> + * RCU synchronization may also be required when application needs to free
> + * resources (using rte_graph_feature_register::notifier_cb()) which it may 
> have
> + * allocated during feature enable. Resources can be freed only when no 
> worker
> + * core is not acting on it.
> + *
> + * If RCU argument to rte_graph_feature_enable(), rte_graph_feature_disable()
> + * is non-NULL, as part of APIs:
> + *  - rte_rcu_qsbr_synchronize() is called to synchronize all worker cores
> + *  - If set, rte_graph_feature_register::notifier_cb() is called in which
> + *  application can safely release resources associated with [feature, index]
> + *
> + * It is application responsibility to pass valid RCU argument to APIs. It is
> + * recommended that application calls rte_rcu_qsbr_quiescent() after every
> + * iteration of rte_graph_walk()
> + *
>   * Constraints
>   * -----------
>   *  - rte_graph_feature_arc_init(), rte_graph_feature_arc_create() and
>   *  rte_graph_feature_add() must be called before rte_graph_create().
> + *  - rte_graph_feature_enable(), rte_graph_feature_disable() should be 
> called
> + *  after rte_graph_create()
>   *  - Not more than 63 features can be added to a feature arc. There is no
>   *  limit to number of feature arcs i.e. number of
>   *  RTE_GRAPH_FEATURE_ARC_REGISTER()
> @@ -349,7 +410,7 @@ int rte_graph_feature_arc_create(struct 
> rte_graph_feature_arc_register *reg,
>   * Get feature arc object with name
>   *
>   * @param arc_name
> - *   Feature arc name provided to successful @ref 
> rte_graph_feature_arc_create
> + *   Feature arc name provided to successful @ref 
> rte_graph_feature_arc_create()
>   * @param[out] _arc
>   *   Feature arc object returned. Valid only when API returns SUCCESS
>   *
> @@ -369,6 +430,9 @@ int rte_graph_feature_arc_lookup_by_name(const char 
> *arc_name, rte_graph_feature
>   * Pointer to struct rte_graph_feature_register
>   *
>   * <I> Must be called before rte_graph_create() </I>
> + * <I> rte_graph_feature_add() is not allowed after call to
> + * rte_graph_feature_enable() so all features must be added before they can 
> be
> + * enabled </I>
>   * <I> When called by application, then feature_node_id should be 
> appropriately set as
>   *     freg->feature_node_id = freg->feature_node->id;
>   * </I>
> @@ -380,14 +444,71 @@ int rte_graph_feature_arc_lookup_by_name(const char 
> *arc_name, rte_graph_feature
>  __rte_experimental
>  int rte_graph_feature_add(struct rte_graph_feature_register *feat_reg);
>
> +/**
> + * Enable feature within a feature arc
> + *
> + * Must be called after @b rte_graph_create().
> + *
> + * @param _arc
> + *   Feature arc object returned by @ref rte_graph_feature_arc_create() or 
> @ref
> + *   rte_graph_feature_arc_lookup_by_name()
> + * @param index
> + *   Application specific index. Can be corresponding to 
> interface_id/port_id etc
> + * @param feature_name
> + *   Name of the node which is already added via @ref rte_graph_feature_add()
> + * @param app_cookie
> + *   Application specific data which is retrieved in fast path
> + * @param qsbr
> + *   RCU QSBR object.  After enabling feature, API calls
> + *   rte_rcu_qsbr_synchronize() followed by call to struct
> + *   rte_graph_feature_register::notifier_cb(), if it is set, to notify 
> feature
> + *   caller This object can be passed NULL as well if no RCU synchronization 
> is
> + *   required
> + *
> + * @return
> + *  0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index, 
> const
> +                            char *feature_name, uint16_t app_cookie,
> +                            struct rte_rcu_qsbr *qsbr);
> +
> +/**
> + * Disable already enabled feature within a feature arc
> + *
> + * Must be called after @b rte_graph_create(). API is *NOT* Thread-safe
> + *
> + * @param _arc
> + *   Feature arc object returned by @ref rte_graph_feature_arc_create() or 
> @ref
> + *   rte_graph_feature_arc_lookup_by_name()
> + * @param index
> + *   Application specific index. Can be corresponding to 
> interface_id/port_id etc
> + * @param feature_name
> + *   Name of the node which is already added via @ref rte_graph_feature_add()
> + * @param qsbr
> + *   RCU QSBR object.  After disabling feature, API calls
> + *   rte_rcu_qsbr_synchronize() followed by call to struct
> + *   RTE_GRAPH_FEATURE_ARC_REGISTER::notifier_cb(), if it is set, to notify 
> feature
> + *   caller. This object can be passed NULL as well if no RCU 
> synchronization is
> + *   required
> + *
> + * @return
> + *  0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index,
> +                             const char *feature_name, struct rte_rcu_qsbr 
> *qsbr);
> +
>  /**
>   * Get rte_graph_feature_t object from feature name
>   *
>   * @param arc
> - *   Feature arc object returned by @ref rte_graph_feature_arc_create or @ref
> - *   rte_graph_feature_arc_lookup_by_name
> + *   Feature arc object returned by @ref rte_graph_feature_arc_create() or 
> @ref
> + *   rte_graph_feature_arc_lookup_by_name()
>   * @param feature_name
> - *   Feature name provided to @ref rte_graph_feature_add
> + *   Feature name provided to @ref rte_graph_feature_add()
>   * @param[out] feature
>   *   Feature object
>   *
> @@ -403,8 +524,8 @@ int rte_graph_feature_lookup(rte_graph_feature_arc_t arc, 
> const char *feature_na
>   * Delete feature_arc object
>   *
>   * @param _arc
> - *   Feature arc object returned by @ref rte_graph_feature_arc_create or @ref
> - *   rte_graph_feature_arc_lookup_by_name
> + *   Feature arc object returned by @ref rte_graph_feature_arc_create() or 
> @ref
> + *   rte_graph_feature_arc_lookup_by_name()
>   *
>   * @return
>   *  0: Success
> @@ -435,6 +556,19 @@ int rte_graph_feature_arc_cleanup(void);
>  __rte_experimental
>  uint32_t rte_graph_feature_arc_num_features(rte_graph_feature_arc_t _arc);
>
> +/**
> + * Slow path API to know how many features are currently enabled within a
> + * feature arc across all indexes. If a single feature is enabled on all 
> interfaces,
> + * this API would return "number_of_interfaces" as count (but not "1")
> + *
> + * @param _arc
> + *  Feature arc object
> + *
> + * @return: Number of enabled features across all indexes
> + */
> +__rte_experimental
> +uint32_t rte_graph_feature_arc_num_enabled_features(rte_graph_feature_arc_t 
> _arc);
> +
>  /**
>   * Slow path API to get feature node name from rte_graph_feature_t object
>   *
> diff --git a/lib/graph/rte_graph_feature_arc_worker.h 
> b/lib/graph/rte_graph_feature_arc_worker.h
> index d086a0c0c1..9719e9255a 100644
> --- a/lib/graph/rte_graph_feature_arc_worker.h
> +++ b/lib/graph/rte_graph_feature_arc_worker.h
> @@ -21,6 +21,7 @@
>   *
>   * Fast path Graph feature arc API
>   */
> +
>  #ifdef __cplusplus
>  extern "C" {
>  #endif
> @@ -34,6 +35,7 @@ struct rte_graph_feature_node_list {
>         /** Next feature */
>         STAILQ_ENTRY(rte_graph_feature_node_list) next_feature;
>
> +       /** Name of the feature */
>         char feature_name[RTE_GRAPH_FEATURE_ARC_NAMELEN];
>
>         /** node id representing feature */
> @@ -161,6 +163,45 @@ struct __rte_cache_aligned rte_graph_feature_arc {
>          */
>         int mbuf_dyn_offset;
>
> +       /** Fast path arc data starts */
> +       /*
> +        * Arc specific fast path data
> +        * It accommodates:
> +        *
> +        *      1. first enabled feature data for every index 
> (rte_graph_feature_data_t or fdata)
> +        *      
> +--------------------------------------------------------------+ <- 
> cache_aligned
> +        *      |  0th Index    | 1st Index   |  ... | max_index - 1          
>  |
> +        *      
> +--------------------------------------------------------------+
> +        *      |  Startfdata0  | Startfdata1 |  ... | 
> Startfdata(max_index-1) |
> +        *      
> +--------------------------------------------------------------+
> +        *
> +        *      2. struct rte_graph_feature_data per index per feature
> +        *      +----------------------------------------+ ^ <- Start 
> (Reserved, cache aligned)
> +        *      |  struct rte_graph_feature_data[Index0] | |
> +        *      +----------------------------------------+ | feature_size
> +        *      |  struct rte_graph_feature_data[Index1] | |
> +        *      +----------------------------------------+ ^ <- Feature-0 
> (cache_aligned)
> +        *      |  struct rte_graph_feature_data[Index0] | |
> +        *      +----------------------------------------+ | feature_size
> +        *      |  struct rte_graph_feature_data[Index1] | |
> +        *      +----------------------------------------+ v <- Feature-1 
> (cache aligned)
> +        *      |  struct rte_graph_feature_data[Index0] | ^
> +        *      +----------------------------------------+ | feature_size
> +        *      |  struct rte_graph_feature_data[Index1] | |
> +        *      +----------------------------------------+ v
> +        *      |         ...            ....            |
> +        *      |         ...            ....            |
> +        *      |         ...            ....            |
> +        *      +----------------------------------------+ v <- Feature 
> Index-1 (cache aligned)
> +        *      |  struct rte_graph_feature_data[Index0] | ^
> +        *      +----------------------------------------+ | feature_size
> +        *      |  struct rte_graph_feature_data[Index1] | |
> +        *      +----------------------------------------+ v <- Extra 
> (Reserved, cache aligned)
> +        *      |  struct rte_graph_feature_data[Index0] | ^
> +        *      +----------------------------------------+ | feature_size
> +        *      |  struct rte_graph_feature_data[Index1] | |
> +        *      +----------------------------------------+ v
> +        */
>         RTE_MARKER8 fp_arc_data;
>  };
>
> @@ -195,13 +236,15 @@ typedef struct rte_feature_arc_main {
>   *  It holds
>   *  - edge to reach to next feature node
>   *  - next_feature_data corresponding to next enabled feature
> + *  - app_cookie set by application in rte_graph_feature_enable()
>   */
>  struct rte_graph_feature_data {
>         /** edge from this feature node to next enabled feature node */
>         RTE_ATOMIC(rte_edge_t) next_edge;
>
>         /**
> -        * app_cookie
> +        * app_cookie set by application in rte_graph_feature_enable() for
> +        * current feature data
>          */
>         RTE_ATOMIC(uint16_t) app_cookie;
>
> @@ -218,6 +261,18 @@ struct rte_graph_feature_arc_mbuf_dynfields {
>  /** Name of dynamic mbuf field offset registered in 
> rte_graph_feature_arc_init() */
>  #define RTE_GRAPH_FEATURE_ARC_DYNFIELD_NAME    
> "__rte_graph_feature_arc_mbuf_dynfield"
>
> +/** log2(sizeof (struct rte_graph_feature_data)) */
> +#define RTE_GRAPH_FEATURE_DATA_SIZE_LOG2       3
> +
> +/** Number of struct rte_graph_feature_data per feature*/
> +#define RTE_GRAPH_FEATURE_DATA_NUM_PER_FEATURE(arc)                          
>   \
> +       (arc->feature_size >> RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)
> +
> +/** Get rte_graph_feature_data_t from rte_graph_feature_t */
> +#define RTE_GRAPH_FEATURE_TO_FEATURE_DATA(arc, feature, index)               
>   \
> +               ((rte_graph_feature_data_t)                                   
>   \
> +                ((RTE_GRAPH_FEATURE_DATA_NUM_PER_FEATURE(arc) * (feature)) + 
> (index)))
> +
>  /**
>   * @internal macro
>   */
> @@ -273,6 +328,23 @@ rte_graph_feature_is_valid(rte_graph_feature_t feature)
>         return (feature != RTE_GRAPH_FEATURE_INVALID);
>  }
>
> +/**
> + * API to know if feature data is valid or not
> + *
> + * @param feature_data
> + *  rte_graph_feature_data_t
> + *
> + * @return
> + *  1: If feature data is valid
> + *  0: If feature data is invalid
> + */
> +__rte_experimental
> +static __rte_always_inline int
> +rte_graph_feature_data_is_valid(rte_graph_feature_data_t feature_data)
> +{
> +       return (feature_data != RTE_GRAPH_FEATURE_DATA_INVALID);
> +}
> +
>  /**
>   * Get pointer to feature arc object from rte_graph_feature_arc_t
>   *
> @@ -299,6 +371,253 @@ rte_graph_feature_arc_get(rte_graph_feature_arc_t arc)
>                 NULL : (struct rte_graph_feature_arc *)fa;
>  }
>
> +/**
> + * Get rte_graph_feature_t from feature arc object without any checks
> + *
> + * @param arc
> + *  feature arc
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *   Pointer to feature data object
> + */
> +__rte_experimental
> +static __rte_always_inline struct rte_graph_feature_data*
> +__rte_graph_feature_data_get(struct rte_graph_feature_arc *arc,
> +                            rte_graph_feature_data_t fdata)
> +{
> +       return ((struct rte_graph_feature_data *) ((uint8_t *)arc + 
> arc->fp_feature_data_offset +
> +                                                  (fdata << 
> RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +}
> +
> +/**
> + * Get next edge from feature data pointer, without any check
> + *
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *  next edge
> + */
> +__rte_experimental
> +static __rte_always_inline rte_edge_t
> +__rte_graph_feature_data_edge_get(struct rte_graph_feature_data *fdata)
> +{
> +       return rte_atomic_load_explicit(&fdata->next_edge, 
> rte_memory_order_relaxed);
> +}
> +
> +/**
> + * Get app_cookie from feature data pointer, without any check
> + *
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *  app_cookie set by caller in rte_graph_feature_enable() API
> + */
> +__rte_experimental
> +static __rte_always_inline uint16_t
> +__rte_graph_feature_data_app_cookie_get(struct rte_graph_feature_data *fdata)
> +{
> +       return rte_atomic_load_explicit(&fdata->app_cookie, 
> rte_memory_order_relaxed);
> +}
> +
> +/**
> + * Get next_enabled_feature_data from pointer to feature data, without any 
> check
> + *
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *  next enabled feature data from this feature data
> + */
> +__rte_experimental
> +static __rte_always_inline rte_graph_feature_data_t
> +__rte_graph_feature_data_next_feature_get(struct rte_graph_feature_data 
> *fdata)
> +{
> +       return rte_atomic_load_explicit(&fdata->next_feature_data, 
> rte_memory_order_relaxed);
> +}
> +
> +
> +/**
> + * Get app_cookie from feature data object with checks
> + *
> + * @param arc
> + *  feature arc
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *  app_cookie set by caller in rte_graph_feature_enable() API
> + */
> +__rte_experimental
> +static __rte_always_inline uint16_t
> +rte_graph_feature_data_app_cookie_get(struct rte_graph_feature_arc *arc,
> +                                     rte_graph_feature_data_t fdata)
> +{
> +       struct rte_graph_feature_data *fdata_obj = ((struct 
> rte_graph_feature_data *)
> +                                                   ((uint8_t *)arc + 
> arc->fp_feature_data_offset +
> +                                                   (fdata << 
> RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +
> +
> +       return rte_atomic_load_explicit(&fdata_obj->app_cookie, 
> rte_memory_order_relaxed);
> +}
> +
> +/**
> + * Get next_enabled_feature_data from current feature data object with checks
> + *
> + * @param arc
> + *  feature arc
> + * @param fdata
> + *  Pointer to feature data object
> + * @param[out] next_edge
> + *  next_edge from current feature to next enabled feature
> + *
> + * @return
> + *  1: if next feature enabled on index
> + *  0: if no feature is enabled on index
> + */
> +__rte_experimental
> +static __rte_always_inline int
> +rte_graph_feature_data_next_feature_get(struct rte_graph_feature_arc *arc,
> +                                       rte_graph_feature_data_t *fdata,
> +                                       rte_edge_t *next_edge)
> +{
> +       struct rte_graph_feature_data *fdptr = ((struct 
> rte_graph_feature_data *)
> +                                               ((uint8_t *)arc + 
> arc->fp_feature_data_offset +
> +                                               ((*fdata) << 
> RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +       *fdata = rte_atomic_load_explicit(&fdptr->next_feature_data, 
> rte_memory_order_relaxed);
> +       *next_edge = rte_atomic_load_explicit(&fdptr->next_edge, 
> rte_memory_order_relaxed);
> +
> +
> +       return ((*fdata) != RTE_GRAPH_FEATURE_DATA_INVALID);
> +}
> +
> +/**
> + * Get struct rte_graph_feature_data from rte_graph_feature_dat_t
> + *
> + * @param arc
> + *   feature arc
> + * @param fdata
> + *  feature data object
> + *
> + * @return
> + *   NULL: On Failure
> + *   Non-NULL pointer on Success
> + */
> +__rte_experimental
> +static __rte_always_inline struct rte_graph_feature_data*
> +rte_graph_feature_data_get(struct rte_graph_feature_arc *arc,
> +                          rte_graph_feature_data_t fdata)
> +{
> +       if (fdata != RTE_GRAPH_FEATURE_DATA_INVALID)
> +               return ((struct rte_graph_feature_data *)
> +                       ((uint8_t *)arc + arc->fp_feature_data_offset +
> +                       (fdata << RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +       else
> +               return NULL;
> +}
> +
> +/**
> + * Get feature data corresponding to first enabled feature on index
> + * @param arc
> + *   feature arc
> + * @param index
> + *   Interface index
> + * @param[out] fdata
> + *  feature data object
> + * @param[out] edge
> + *  rte_edge object
> + *
> + * @return
> + *  1: if any feature enabled on index, return corresponding valid feature 
> data
> + *  0: if no feature is enabled on index
> + */
> +__rte_experimental
> +static __rte_always_inline int
> +rte_graph_feature_data_first_feature_get(struct rte_graph_feature_arc *arc,
> +                                        uint32_t index,
> +                                        rte_graph_feature_data_t *fdata,
> +                                        rte_edge_t *edge)
> +{
> +       struct rte_graph_feature_data *fdata_obj = NULL;
> +       rte_graph_feature_data_t *fd;
> +
> +       fd = (rte_graph_feature_data_t *)((uint8_t *)arc + 
> arc->fp_first_feature_offset +
> +                                         (sizeof(rte_graph_feature_data_t) * 
> index));
> +
> +       if ((*fd) != RTE_GRAPH_FEATURE_DATA_INVALID) {
> +               fdata_obj = ((struct rte_graph_feature_data *)
> +                            ((uint8_t *)arc + arc->fp_feature_data_offset +
> +                            ((*fd) << RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +
> +               *edge = rte_atomic_load_explicit(&fdata_obj->next_edge,
> +                                                rte_memory_order_relaxed);
> +
> +               *fdata = 
> rte_atomic_load_explicit(&fdata_obj->next_feature_data,
> +                                                 rte_memory_order_relaxed);
> +               return 1;
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * Fast path API to check if any feature enabled on a feature arc
> + * Typically from arc->start_node process function
> + *
> + * @param arc
> + *   Feature arc object
> + *
> + * @return
> + *  0: If no feature enabled
> + *  Non-Zero: Bitmask of features enabled.
> + *
> + */
> +__rte_experimental
> +static __rte_always_inline uint64_t
> +rte_graph_feature_arc_is_any_feature_enabled(struct rte_graph_feature_arc 
> *arc)
> +{
> +       if (unlikely(arc == NULL))
> +               return 0;
> +
> +       return (rte_atomic_load_explicit(&arc->fp_feature_enable_bitmask,
> +                                        rte_memory_order_relaxed));
> +}
> +
> +/**
> + * Prefetch feature arc fast path cache line
> + *
> + * @param arc
> + *   RTE_GRAPH feature arc object
> + */
> +__rte_experimental
> +static __rte_always_inline void
> +rte_graph_feature_arc_prefetch(struct rte_graph_feature_arc *arc)
> +{
> +       rte_prefetch0((void *)arc->fast_path_variables);
> +}
> +
> +/**
> + * Prefetch feature data related fast path cache line
> + *
> + * @param arc
> + *   RTE_GRAPH feature arc object
> + * @param fdata
> + *   Pointer to feature data object
> + */
> +__rte_experimental
> +static __rte_always_inline void
> +rte_graph_feature_arc_feature_data_prefetch(struct rte_graph_feature_arc 
> *arc,
> +                                           rte_graph_feature_data_t fdata)
> +{
> +       struct rte_graph_feature_data *fdata_obj = ((struct 
> rte_graph_feature_data *)
> +                                                   ((uint8_t *)arc + 
> arc->fp_feature_data_offset +
> +                                                   (fdata << 
> RTE_GRAPH_FEATURE_DATA_SIZE_LOG2)));
> +       rte_prefetch0((void *)fdata_obj);
> +}
> +
>  #ifdef __cplusplus
>  }
>  #endif
> --
> 2.43.0
>

Reply via email to