* [RFC PATCH 0/3] add feature arc in rte_graph
@ 2024-09-07 7:31 Nitin Saxena
2024-09-07 7:31 ` [RFC PATCH 1/3] graph: add feature arc support Nitin Saxena
` (4 more replies)
0 siblings, 5 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-09-07 7:31 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
Feature arc represents an ordered list of features/protocols at a given
networking layer. It is a high level abstraction to connect various
rte_graph nodes, as feature nodes, and allow packets steering across
these nodes in a generic manner.
Features (or feature nodes) are nodes which handles partial or complete
handling of a protocol in fast path. Like ipv4-rewrite node, which adds
rewrite data to an outgoing IPv4 packet.
However in above example, outgoing interface(say "eth0") may have
outbound IPsec policy enabled, hence packets must be steered from
ipv4-rewrite node to ipsec-outbound-policy node for outbound IPsec
policy lookup. On the other hand, packets routed to another interface
(eth1) will not be sent to ipsec-outbound-policy node as IPsec feature
is disabled on eth1. Feature-arc allows rte_graph applications to manage
such constraints easily
Feature arc abstraction allows rte_graph based application to
1. Seamlessly steer packets across feature nodes based on wheter feature
is enabled or disabled on an interface. Features enabled on one
interface may not be enabled on another interface with in a same feature
arc.
2. Allow enabling/disabling of features on an interface at runtime,
so that if a feature is disabled, packets associated with that interface
won't be steered to corresponding feature node.
3. Provides mechanism to hook custom/user-defined nodes to a feature
node and allow packet steering from feature node to custom node without
changing former's fast path function
4. Allow expressing features in a particular sequential order so that
packets are steered in an ordered way across nodes in fast path. For
eg: if IPsec and IPv4 features are enabled on an ingress interface,
packets must be sent to IPsec inbound policy node first and then to ipv4
lookup node.
This patch series adds feature arc library in rte_graph and also adds
"ipv4-output" feature arc handling in "ipv4-rewrite" node.
Nitin Saxena (3):
graph: add feature arc support
graph: add feature arc option in graph create
graph: add IPv4 output feature arc
lib/graph/graph.c | 1 +
lib/graph/graph_feature_arc.c | 959 +++++++++++++++++++++++
lib/graph/graph_populate.c | 7 +-
lib/graph/graph_private.h | 3 +
lib/graph/meson.build | 2 +
lib/graph/node.c | 2 +
lib/graph/rte_graph.h | 3 +
lib/graph/rte_graph_feature_arc.h | 373 +++++++++
lib/graph/rte_graph_feature_arc_worker.h | 548 +++++++++++++
lib/graph/version.map | 17 +
lib/node/ip4_rewrite.c | 476 ++++++++---
lib/node/ip4_rewrite_priv.h | 9 +-
lib/node/node_private.h | 19 +-
lib/node/rte_node_ip4_api.h | 3 +
14 files changed, 2325 insertions(+), 97 deletions(-)
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH 1/3] graph: add feature arc support
2024-09-07 7:31 [RFC PATCH 0/3] add feature arc in rte_graph Nitin Saxena
@ 2024-09-07 7:31 ` Nitin Saxena
2024-09-11 4:41 ` Kiran Kumar Kokkilagadda
2024-09-07 7:31 ` [RFC PATCH 2/3] graph: add feature arc option in graph create Nitin Saxena
` (3 subsequent siblings)
4 siblings, 1 reply; 56+ messages in thread
From: Nitin Saxena @ 2024-09-07 7:31 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
add feature arc to allow dynamic steering of packets across graph nodes
based on protocol features enabled on incoming or outgoing interface
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
lib/graph/graph_feature_arc.c | 959 +++++++++++++++++++++++
lib/graph/meson.build | 2 +
lib/graph/rte_graph_feature_arc.h | 373 +++++++++
lib/graph/rte_graph_feature_arc_worker.h | 548 +++++++++++++
lib/graph/version.map | 17 +
5 files changed, 1899 insertions(+)
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
diff --git a/lib/graph/graph_feature_arc.c b/lib/graph/graph_feature_arc.c
new file mode 100644
index 0000000000..3b05bac137
--- /dev/null
+++ b/lib/graph/graph_feature_arc.c
@@ -0,0 +1,959 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#include "graph_private.h"
+#include <rte_graph_feature_arc_worker.h>
+#include <rte_malloc.h>
+
+#define __RTE_GRAPH_FEATURE_ARC_MAX 32
+
+#define ARC_PASSIVE_LIST(arc) (arc->active_feature_list ^ 0x1)
+
+#define rte_graph_uint_cast(x) ((unsigned int)x)
+#define feat_dbg graph_err
+
+rte_graph_feature_arc_main_t *__feature_arc_main;
+
+/* Make sure fast path cache line is compact */
+_Static_assert((offsetof(struct rte_graph_feature_arc, slow_path_variables)
+ - offsetof(struct rte_graph_feature_arc, fast_path_variables))
+ <= RTE_CACHE_LINE_SIZE);
+
+
+static int
+feature_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
+ struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ const char *name;
+
+ if (!feat_name)
+ return -1;
+
+ if (slot)
+ *slot = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ RTE_VERIFY(finfo->feature_arc == arc);
+ name = rte_node_id_to_name(finfo->feature_node->id);
+ if (!strncmp(name, feat_name, RTE_GRAPH_NAMESIZE)) {
+ if (ffinfo)
+ *ffinfo = finfo;
+ return 0;
+ }
+ if (slot)
+ (*slot)++;
+ }
+ return -1;
+}
+
+static int
+feature_arc_node_info_lookup(struct rte_graph_feature_arc *arc, uint32_t feature_index,
+ struct rte_graph_feature_node_list **ppfinfo)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ if (!ppfinfo)
+ return -1;
+
+ *ppfinfo = NULL;
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ if (index == feature_index) {
+ if (finfo->node_index == feature_index)
+ return -1;
+ *ppfinfo = finfo;
+ }
+ index++;
+ }
+ if (feature_index && (index >= feature_index))
+ return -1;
+
+ return 0;
+}
+
+static void
+prepare_feature_arc(struct rte_graph_feature_arc *arc)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ finfo->node_index = index;
+ index++;
+ }
+}
+
+static int
+feature_arc_lookup(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 = __feature_arc_main;
+ uint32_t iter;
+
+ if (!__feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ if (arc == (rte_graph_feature_arc_get(dm->feature_arcs[iter])))
+ return 0;
+ }
+ return -1;
+}
+
+static int
+get_existing_edge(const char *arc_name, struct rte_node_register *parent_node,
+ struct rte_node_register *child_node, rte_edge_t *_edge)
+{
+ char **next_edges = NULL;
+ uint32_t count, i;
+
+ RTE_SET_USED(arc_name);
+
+ count = rte_node_edge_get(parent_node->id, NULL);
+ next_edges = malloc(count);
+
+ if (!next_edges)
+ return -1;
+
+ count = rte_node_edge_get(parent_node->id, next_edges);
+ for (i = 0; i < count; i++) {
+ if (strstr(child_node->name, next_edges[i])) {
+ feat_dbg("%s: Edge exists [%s[%u]: \"%s\"]", arc_name,
+ parent_node->name, i, child_node->name);
+ if (_edge)
+ *_edge = (rte_edge_t)i;
+
+ free(next_edges);
+ return 0;
+ }
+ }
+ free(next_edges);
+
+ return -1;
+}
+
+static int
+connect_graph_nodes(struct rte_node_register *parent_node, struct rte_node_register *child_node,
+ rte_edge_t *_edge, char *arc_name)
+{
+ const char *next_node = NULL;
+ rte_edge_t edge;
+
+ if (!get_existing_edge(arc_name, parent_node, child_node, &edge)) {
+ feat_dbg("%s: add_feature: Edge reused [%s[%u]: \"%s\"]", arc_name,
+ parent_node->name, edge, child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+ }
+
+ /* Node to be added */
+ next_node = child_node->name;
+
+ edge = rte_node_edge_update(parent_node->id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+ if (edge == RTE_EDGE_ID_INVALID) {
+ graph_err("edge invalid");
+ return -1;
+ }
+ edge = rte_node_edge_count(parent_node->id) - 1;
+
+ feat_dbg("%s: add_feature: edge added [%s[%u]: \"%s\"]", arc_name, parent_node->name, edge,
+ child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+}
+
+static int
+feature_arc_init(rte_graph_feature_arc_main_t **pfl, uint32_t max_feature_arcs)
+{
+ rte_graph_feature_arc_main_t *pm = NULL;
+ uint32_t i;
+ size_t sz;
+
+ if (!pfl)
+ return -1;
+
+ sz = sizeof(rte_graph_feature_arc_main_t) +
+ (sizeof(pm->feature_arcs[0]) * max_feature_arcs);
+
+ pm = malloc(sz);
+ if (!pm)
+ return -1;
+
+ memset(pm, 0, sz);
+
+ for (i = 0; i < max_feature_arcs; i++)
+ pm->feature_arcs[i] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ pm->max_feature_arcs = max_feature_arcs;
+
+ *pfl = pm;
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_init(int max_feature_arcs)
+{
+ if (!max_feature_arcs)
+ return -1;
+
+ if (__feature_arc_main)
+ return -1;
+
+ return feature_arc_init(&__feature_arc_main, max_feature_arcs);
+}
+
+static void
+feature_arc_list_reset(struct rte_graph_feature_arc *arc, uint32_t list_index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+ uint32_t i, j;
+
+ list = arc->feature_list[list_index];
+ feat = arc->features[list_index];
+
+ /*Initialize variables*/
+ memset(feat, 0, arc->feature_size);
+ memset(list, 0, arc->feature_list_size);
+
+ /* Initialize feature and feature_data */
+ for (i = 0; i < arc->max_features; i++) {
+ feat = __rte_graph_feature_get(arc, i, list_index);
+ feat->this_feature_index = i;
+
+ for (j = 0; j < arc->max_indexes; j++) {
+ fdata = rte_graph_feature_data_get(arc, feat, j);
+ fdata->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ fdata->next_edge = UINT16_MAX;
+ fdata->user_data = UINT32_MAX;
+ }
+ }
+
+ for (i = 0; i < arc->max_indexes; i++)
+ list->first_enabled_feature_by_index[i] = RTE_GRAPH_FEATURE_INVALID;
+}
+
+static int
+feature_arc_list_init(struct rte_graph_feature_arc *arc, const char *flist_name,
+ rte_graph_feature_list_t **pplist,
+ struct rte_graph_feature **ppfeature, uint32_t list_index)
+{
+ char fname[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ size_t list_size, feat_size, fdata_size;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+
+ list_size = sizeof(list->first_enabled_feature_by_index[0]) * arc->max_indexes;
+
+ list = rte_malloc(flist_name, list_size, RTE_CACHE_LINE_SIZE);
+ if (!list)
+ return -ENOMEM;
+
+ fdata_size = arc->max_indexes * sizeof(rte_graph_feature_data_t);
+
+ /* Let one feature capture complete cache lines */
+ feat_size = RTE_ALIGN_CEIL(sizeof(struct rte_graph_feature) + fdata_size,
+ RTE_CACHE_LINE_SIZE);
+
+ snprintf(fname, sizeof(fname), "%s-%s", arc->feature_arc_name, "feat");
+
+ feat = rte_malloc(fname, feat_size * arc->max_features, RTE_CACHE_LINE_SIZE);
+ if (!feat) {
+ rte_free(list);
+ return -ENOMEM;
+ }
+ arc->feature_size = feat_size;
+ arc->feature_data_size = fdata_size;
+ arc->feature_list_size = list_size;
+
+ /* Initialize list */
+ list->indexed_by_features = feat;
+ *pplist = list;
+ *ppfeature = feat;
+
+ feature_arc_list_reset(arc, list_index);
+
+ return 0;
+}
+
+static void
+feature_arc_list_destroy(rte_graph_feature_list_t *list)
+{
+ rte_free(list->indexed_by_features);
+ rte_free(list);
+}
+
+int
+rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node, rte_graph_feature_arc_t *_arc)
+{
+ char name[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ rte_graph_feature_arc_main_t *dfm = NULL;
+ struct rte_graph_feature_arc *arc = NULL;
+ struct rte_graph_feature_data *gfd = NULL;
+ struct rte_graph_feature *df = NULL;
+ uint32_t iter, j, arc_index;
+ size_t sz;
+
+ if (!_arc)
+ return -1;
+
+ if (max_features < 2)
+ return -1;
+
+ if (!start_node)
+ return -1;
+
+ if (!feature_arc_name)
+ return -1;
+
+ if (max_features > RTE_GRAPH_FEATURE_MAX_PER_ARC) {
+ graph_err("Invalid max features: %u", max_features);
+ return -1;
+ }
+
+ /*
+ * Application hasn't called rte_graph_feature_arc_init(). Initialize with
+ * default values
+ */
+ if (!__feature_arc_main) {
+ if (rte_graph_feature_arc_init((int)__RTE_GRAPH_FEATURE_ARC_MAX) < 0) {
+ graph_err("rte_graph_feature_arc_init() failed");
+ return -1;
+ }
+ }
+
+ dfm = __feature_arc_main;
+
+ /* threshold check */
+ if (dfm->num_feature_arcs > (dfm->max_feature_arcs - 1)) {
+ graph_err("max threshold for num_feature_arcs: %d reached",
+ dfm->max_feature_arcs - 1);
+ return -1;
+ }
+ /* Find the free slot for feature arc */
+ for (iter = 0; iter < dfm->max_feature_arcs; iter++) {
+ if (dfm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ break;
+ }
+ arc_index = iter;
+
+ if (arc_index >= dfm->max_feature_arcs) {
+ graph_err("No free slot found for num_feature_arc");
+ return -1;
+ }
+
+ /* This should not happen */
+ RTE_VERIFY(dfm->feature_arcs[arc_index] == RTE_GRAPH_FEATURE_ARC_INITIALIZER);
+
+ /* size of feature arc + feature_bit_mask_by_index */
+ sz = sizeof(*arc) + (sizeof(uint64_t) * max_indexes);
+
+ arc = rte_malloc(feature_arc_name, sz, RTE_CACHE_LINE_SIZE);
+
+ if (!arc) {
+ graph_err("malloc failed for feature_arc_create()");
+ return -1;
+ }
+
+ memset(arc, 0, sz);
+
+ /* Initialize rte_graph port group fixed variables */
+ STAILQ_INIT(&arc->all_features);
+ strncpy(arc->feature_arc_name, feature_arc_name, RTE_GRAPH_FEATURE_ARC_NAMELEN - 1);
+ arc->feature_arc_main = (void *)dfm;
+ arc->start_node = start_node;
+ arc->max_features = max_features;
+ arc->max_indexes = max_indexes;
+
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist0");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[0], &arc->features[0], 0) < 0) {
+ rte_free(arc);
+ graph_err("feature_arc_list_init(0) failed");
+ return -1;
+ }
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist1");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[1], &arc->features[1], 1) < 0) {
+ feature_arc_list_destroy(arc->feature_list[0]);
+ graph_err("feature_arc_list_init(1) failed");
+ return -1;
+ }
+
+ for (iter = 0; iter < arc->max_features; iter++) {
+ df = rte_graph_feature_get(arc, iter);
+ for (j = 0; j < arc->max_indexes; j++) {
+ gfd = rte_graph_feature_data_get(arc, df, j);
+ gfd->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ }
+ }
+ arc->feature_arc_index = arc_index;
+ dfm->feature_arcs[arc->feature_arc_index] = (rte_graph_feature_arc_t)arc;
+ dfm->num_feature_arcs++;
+
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+
+ return 0;
+}
+
+int
+rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *after_feature, const char *before_feature)
+{
+ struct rte_graph_feature_node_list *after_finfo = NULL, *before_finfo = NULL;
+ struct rte_graph_feature_node_list *temp = NULL, *finfo = NULL;
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ uint32_t slot, add_flag;
+ rte_edge_t edge = -1;
+
+ RTE_VERIFY(arc->feature_arc_main == __feature_arc_main);
+
+ if (feature_arc_lookup(_arc)) {
+ graph_err("invalid feature arc: 0x%016" PRIx64, (uint64_t)_arc);
+ return -1;
+ }
+
+ if (arc->runtime_enabled_features) {
+ graph_err("adding features after enabling any one of them is not supported");
+ return -1;
+ }
+
+ if ((after_feature != NULL) && (before_feature != NULL) &&
+ (after_feature == before_feature)) {
+ graph_err("after_feature and before_feature are same '%s:%s]", after_feature,
+ before_feature);
+ return -1;
+ }
+
+ if (!feature_node) {
+ graph_err("feature_node: %p invalid", feature_node);
+ return -1;
+ }
+
+ arc = rte_graph_feature_arc_get(_arc);
+
+ if (feature_node->id == RTE_NODE_ID_INVALID) {
+ graph_err("Invalid node: %s", feature_node->name);
+ return -1;
+ }
+
+ if (!feature_lookup(arc, feature_node->name, &finfo, &slot)) {
+ graph_err("%s feature already added", feature_node->name);
+ return -1;
+ }
+
+ if (slot >= RTE_GRAPH_FEATURE_MAX_PER_ARC) {
+ graph_err("Max slot %u reached for feature addition", slot);
+ return -1;
+ }
+
+ if (strstr(feature_node->name, arc->start_node->name)) {
+ graph_err("Feature %s cannot point to itself: %s", feature_node->name,
+ arc->start_node->name);
+ return -1;
+ }
+
+ if (connect_graph_nodes(arc->start_node, feature_node, &edge, arc->feature_arc_name)) {
+ graph_err("unable to connect %s -> %s", arc->start_node->name, feature_node->name);
+ return -1;
+ }
+
+ finfo = malloc(sizeof(*finfo));
+ if (!finfo)
+ return -1;
+
+ memset(finfo, 0, sizeof(*finfo));
+
+ finfo->feature_arc = (void *)arc;
+ finfo->feature_node = feature_node;
+ finfo->edge_to_this_feature = edge;
+
+ /* Check for before and after constraints */
+ if (before_feature) {
+ /* before_feature sanity */
+ if (feature_lookup(arc, before_feature, &before_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid before feature name: %s", before_feature);
+
+ if (!before_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "before_feature %s does not exist", before_feature);
+
+ /*
+ * Starting from 0 to before_feature, continue connecting edges
+ */
+ add_flag = 1;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ /*
+ * As soon as we see before_feature. stop adding edges
+ */
+ if (!strncmp(temp->feature_node->name, before_feature,
+ RTE_GRAPH_NAMESIZE))
+ if (!connect_graph_nodes(finfo->feature_node, temp->feature_node,
+ &edge, arc->feature_arc_name))
+ add_flag = 0;
+
+ if (add_flag)
+ connect_graph_nodes(temp->feature_node, finfo->feature_node, NULL,
+ arc->feature_arc_name);
+ }
+ }
+
+ if (after_feature) {
+ if (feature_lookup(arc, after_feature, &after_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid after feature_name %s", after_feature);
+
+ if (!after_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "after_feature %s does not exist", after_feature);
+
+ /* Starting from after_feature to end continue connecting edges */
+ add_flag = 0;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ /* We have already seen after_feature now */
+ if (add_flag)
+ /* Add all features as next node to current feature*/
+ connect_graph_nodes(finfo->feature_node, temp->feature_node, NULL,
+ arc->feature_arc_name);
+
+ /* as soon as we see after_feature. start adding edges
+ * from next iteration
+ */
+ if (!strncmp(temp->feature_node->name, after_feature, RTE_GRAPH_NAMESIZE))
+ /* connect after_feature to this feature */
+ if (!connect_graph_nodes(temp->feature_node, finfo->feature_node,
+ &edge, arc->feature_arc_name))
+ add_flag = 1;
+ }
+
+ /* add feature next to after_feature */
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo, finfo, next_feature);
+ } else {
+ if (before_finfo) {
+ after_finfo = NULL;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (before_finfo == temp) {
+ if (after_finfo)
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo,
+ finfo, next_feature);
+ else
+ STAILQ_INSERT_HEAD(&arc->all_features, finfo,
+ next_feature);
+
+ return 0;
+ }
+ after_finfo = temp;
+ }
+ } else {
+ STAILQ_INSERT_TAIL(&arc->all_features, finfo, next_feature);
+ }
+ }
+
+ return 0;
+
+finfo_free:
+ free(finfo);
+
+ return -1;
+}
+
+int
+rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char *feature_name,
+ rte_graph_feature_t *feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot;
+
+ if (!feature_lookup(arc, feature_name, &finfo, &slot)) {
+ *feat = (rte_graph_feature_t) slot;
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index, const char *feature_name,
+ int is_enable_disable)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ struct rte_graph_feature *gf = NULL;
+ uint32_t slot;
+
+ /* validate _arc */
+ if (arc->feature_arc_main != __feature_arc_main) {
+ graph_err("invalid feature arc: 0x%016" PRIx64, (uint64_t)_arc);
+ return -EINVAL;
+ }
+
+ /* validate index */
+ if (index >= arc->max_indexes) {
+ graph_err("%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 (feature_lookup(arc, feature_name, &finfo, &slot)) {
+ graph_err("%s: No feature %s added", arc->feature_arc_name, feature_name);
+ return -EINVAL;
+ }
+
+ if (!finfo) {
+ graph_err("%s: No feature: %s found", arc->feature_arc_name, feature_name);
+ return -EINVAL;
+ }
+
+ /* slot should be in valid range */
+ if (slot >= arc->max_features) {
+ graph_err("%s/%s: Invalid free slot %u(max=%u) for feature", arc->feature_arc_name,
+ feature_name, slot, arc->max_features);
+ return -EINVAL;
+ }
+
+ /* slot should be in range of 0 - 63 */
+ if (slot > (RTE_GRAPH_FEATURE_MAX_PER_ARC - 1)) {
+ graph_err("%s/%s: Invalid slot: %u", arc->feature_arc_name,
+ feature_name, slot);
+ return -EINVAL;
+ }
+
+ if (finfo->node_index != slot) {
+ graph_err("%s/%s: feature lookup slot mismatch with finfo index: %u and lookup slot: %u",
+ arc->feature_arc_name, feature_name, finfo->node_index, slot);
+ return -1;
+ }
+
+ /* Get feature from active list */
+ gf = __rte_graph_feature_get(arc, slot, ARC_PASSIVE_LIST(arc));
+ if (gf->this_feature_index != slot) {
+ graph_err("%s: %s received feature_index: %u does not match with saved feature_index: %u",
+ arc->feature_arc_name, feature_name, slot, gf->this_feature_index);
+ return -1;
+ }
+
+ if (is_enable_disable && (arc->feature_bit_mask_by_index[index] &
+ RTE_BIT64(slot))) {
+ graph_err("%s: %s already enabled on index: %u",
+ arc->feature_arc_name, feature_name, index);
+ return -1;
+ }
+
+ if (!is_enable_disable && !arc->runtime_enabled_features) {
+ graph_err("%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))) {
+ graph_err("%s: %s not enabled in bitmask for index: %u",
+ arc->feature_arc_name, feature_name, index);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+copy_fastpath_user_data(struct rte_graph_feature_arc *arc, uint16_t dest_list_index,
+ uint16_t src_list_index)
+{
+ rte_graph_feature_data_t *sgfd = NULL, *dgfd = NULL;
+ struct rte_graph_feature *sgf = NULL, *dgf = NULL;
+ uint32_t i, j;
+
+ for (i = 0; i < arc->max_features; i++) {
+ sgf = __rte_graph_feature_get(arc, i, src_list_index);
+ dgf = __rte_graph_feature_get(arc, i, dest_list_index);
+ for (j = 0; j < arc->max_indexes; j++) {
+ sgfd = rte_graph_feature_data_get(arc, sgf, j);
+ dgfd = rte_graph_feature_data_get(arc, dgf, j);
+ dgfd->user_data = sgfd->user_data;
+ }
+ }
+}
+
+static void
+refill_feature_fastpath_data(struct rte_graph_feature_arc *arc, uint16_t list_index)
+{
+ struct rte_graph_feature_node_list *finfo = NULL, *prev_finfo = NULL;
+ struct rte_graph_feature_data *gfd = NULL, *prev_gfd = NULL;
+ struct rte_graph_feature *gf = NULL, *prev_gf = NULL;
+ rte_graph_feature_list_t *flist = NULL;
+ uint32_t fi, di, prev_fi;
+ uint64_t bitmask;
+ rte_edge_t edge;
+
+ flist = arc->feature_list[list_index];
+
+ for (di = 0; di < arc->max_indexes; di++) {
+ bitmask = arc->feature_bit_mask_by_index[di];
+ prev_fi = RTE_GRAPH_FEATURE_INVALID;
+ /* for each feature set for index, set fast path data */
+ while (rte_bsf64_safe(bitmask, &fi)) {
+ gf = __rte_graph_feature_get(arc, fi, list_index);
+ gfd = rte_graph_feature_data_get(arc, gf, di);
+ feature_arc_node_info_lookup(arc, fi, &finfo);
+
+ /* If previous feature_index was valid in last loop */
+ if (prev_fi != RTE_GRAPH_FEATURE_INVALID) {
+ prev_gf = __rte_graph_feature_get(arc, prev_fi, list_index);
+ prev_gfd = rte_graph_feature_data_get(arc, prev_gf, di);
+ /*
+ * Get edge of previous feature node connecting to this feature node
+ */
+ feature_arc_node_info_lookup(arc, prev_fi, &prev_finfo);
+ if (!get_existing_edge(arc->feature_arc_name,
+ prev_finfo->feature_node,
+ finfo->feature_node, &edge)) {
+ feat_dbg("[%s/%s(%2u)/idx:%2u]: %s[%u] = %s",
+ arc->feature_arc_name,
+ prev_finfo->feature_node->name, prev_fi, di,
+ prev_finfo->feature_node->name,
+ edge, finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /*
+ * Fill current feature as next enabled
+ * feature to previous one
+ */
+ prev_gfd->next_enabled_feature = fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* On first feature edge of the node to be added */
+ if (fi == rte_bsf64(arc->feature_bit_mask_by_index[di])) {
+ if (!get_existing_edge(arc->feature_arc_name, arc->start_node,
+ finfo->feature_node,
+ &edge)) {
+ feat_dbg("[%s/%s/%2u/idx:%2u]: 1st feat %s[%u] = %s",
+ arc->feature_arc_name,
+ arc->start_node->name, fi, di,
+ arc->start_node->name, edge,
+ finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /* Set first feature set array for index*/
+ flist->first_enabled_feature_by_index[di] = fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* Clear current feature index */
+ bitmask &= ~RTE_BIT64(fi);
+ }
+ }
+}
+
+int
+rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index, const
+ char *feature_name, int32_t user_data)
+{
+ 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;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint64_t fp_bitmask;
+ uint32_t slot;
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 1))
+ return -1;
+
+ /** This should not fail as validate() has passed */
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ RTE_VERIFY(0);
+
+ if (!arc->runtime_enabled_features)
+ prepare_feature_arc(arc);
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ feat_dbg("%s/%s: Enabling feature on list: %u for index: %u at feature slot %u",
+ arc->feature_arc_name, feature_name, passive_list, index, slot);
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Set current user-data */
+ gfd->user_data = user_data;
+
+ /* Set bitmask in control path bitmask */
+ rte_bit_relaxed_set64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+ refill_feature_fastpath_data(arc, passive_list);
+
+ /* Set fast path enable bitmask */
+ fp_bitmask = __atomic_load_n(&arc->feature_enable_bitmask[passive_list], __ATOMIC_RELAXED);
+ fp_bitmask |= RTE_BIT64(slot);
+ __atomic_store(&arc->feature_enable_bitmask[passive_list], &fp_bitmask, __ATOMIC_RELAXED);
+
+ /* Slow path updates */
+ arc->runtime_enabled_features++;
+
+ /* Increase feature node info reference count */
+ finfo->ref_count++;
+
+ /* Store release semantics for active_list update */
+ __atomic_store(&arc->active_feature_list, &passive_list, __ATOMIC_RELEASE);
+
+ return 0;
+}
+
+int
+rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index, const char *feature_name)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_data *gfd = NULL;
+ struct rte_graph_feature_node_list *finfo = NULL;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint32_t slot;
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 0))
+ return -1;
+
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ return -1;
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ feat_dbg("%s/%s: Disabling feature for index: %u at feature slot %u", arc->feature_arc_name,
+ feature_name, index, slot);
+
+ rte_bit_relaxed_clear64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+
+ /* Set fast path enable bitmask */
+ arc->feature_enable_bitmask[passive_list] &= ~(RTE_BIT64(slot));
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Reset current user-data */
+ gfd->user_data = ~0;
+
+ refill_feature_fastpath_data(arc, passive_list);
+
+ finfo->ref_count--;
+ arc->runtime_enabled_features--;
+
+ /* Store release semantics for active_list update */
+ __atomic_store(&arc->active_feature_list, &passive_list, __ATOMIC_RELEASE);
+
+ return 0;
+}
+
+int
+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 = __feature_arc_main;
+ struct rte_graph_feature_node_list *node_info = NULL;
+
+ while (!STAILQ_EMPTY(&arc->all_features)) {
+ node_info = STAILQ_FIRST(&arc->all_features);
+ STAILQ_REMOVE_HEAD(&arc->all_features, next_feature);
+ free(node_info);
+ }
+ feature_arc_list_destroy(arc->feature_list[0]);
+ feature_arc_list_destroy(arc->feature_list[1]);
+ rte_free(arc->features[0]);
+ rte_free(arc->features[1]);
+
+ dm->feature_arcs[arc->feature_arc_index] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ rte_free(arc);
+ return 0;
+}
+
+int
+rte_graph_feature_arc_cleanup(void)
+{
+ rte_graph_feature_arc_main_t *dm = __feature_arc_main;
+ uint32_t iter;
+
+ if (!__feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ rte_graph_feature_arc_destroy((rte_graph_feature_arc_t)dm->feature_arcs[iter]);
+ }
+ free(dm);
+
+ __feature_arc_main = NULL;
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc)
+{
+ rte_graph_feature_arc_main_t *dm = __feature_arc_main;
+ struct rte_graph_feature_arc *arc = NULL;
+ uint32_t iter;
+
+ if (!__feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ arc = rte_graph_feature_arc_get(dm->feature_arcs[iter]);
+
+ if (strstr(arc_name, arc->feature_arc_name)) {
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+int
+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);
+
+ return arc->runtime_enabled_features;
+}
+
+
diff --git a/lib/graph/meson.build b/lib/graph/meson.build
index 0cb15442ab..d916176fb7 100644
--- a/lib/graph/meson.build
+++ b/lib/graph/meson.build
@@ -14,11 +14,13 @@ sources = files(
'graph_debug.c',
'graph_stats.c',
'graph_populate.c',
+ 'graph_feature_arc.c',
'graph_pcap.c',
'rte_graph_worker.c',
'rte_graph_model_mcore_dispatch.c',
)
headers = files('rte_graph.h', 'rte_graph_worker.h')
+headers += files('rte_graph_feature_arc.h', 'rte_graph_feature_arc_worker.h')
indirect_headers += files(
'rte_graph_model_mcore_dispatch.h',
'rte_graph_model_rtc.h',
diff --git a/lib/graph/rte_graph_feature_arc.h b/lib/graph/rte_graph_feature_arc.h
new file mode 100644
index 0000000000..e3bf4eb73d
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc.h
@@ -0,0 +1,373 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_H_
+#define _RTE_GRAPH_FEATURE_ARC_H_
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_compat.h>
+#include <rte_debug.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc.h
+ *
+ * Define APIs and structures/variables with respect to feature arc
+ *
+ * - Feature arc(s)
+ * - Feature(s)
+ *
+ * A feature arc represents an ordered list of features/protocol-nodes at a
+ * given networking layer. Feature arc provides a high level abstraction to
+ * connect various *rte_graph* nodes, designated as *feature nodes*, and
+ * allowing steering of packets across these feature nodes fast path processing
+ * in a generic manner. In a typical network stack, often a protocol or feature
+ * must be first enabled on a given interface, before any packet is steered
+ * towards it for feature processing. For eg: incoming IPv4 packets are sent to
+ * routing sub-system only after a valid IPv4 address is assigned to the
+ * received interface. In other words, often packets needs to be steered across
+ * features not based on the packet content but based on whether a feature is
+ * enable or disable on a given incoming/outgoing interface. Feature arc
+ * provides mechanism to enable/disable feature(s) on each interface at runtime
+ * and allow seamless packet steering across runtime enabled feature nodes in
+ * fast path.
+ *
+ * Feature arc also provides a way to steer packets from standard nodes to
+ * custom/user-defined *feature nodes* without any change in standard node's
+ * fast path functions
+ *
+ * On a given interface multiple feature(s) might be enabled in a particular
+ * feature arc. For instance, both "ipv4-output" and "IPsec policy output"
+ * features may be enabled on "eth0" interface in "L3-output" feature arc.
+ * Similarly, "ipv6-output" and "ipsec-output" may be enabled on "eth1"
+ * interface in same "L3-output" feature arc.
+ *
+ * When multiple features are present in a given feature arc, its imperative
+ * to allow each feature processing in a particular sequential order. For
+ * instance, in "L3-input" feature arc it may be required to run "IPsec
+ * input" feature first, for packet decryption, before "ip-lookup". So a
+ * sequential order must be maintained among features present in a feature arc.
+ *
+ * Features are enabled/disabled multiple times at runtime to some or all
+ * available interfaces present in the system. Features can be enabled/disabled
+ * even after @b rte_graph_create() is called. Enable/disabling features on one
+ * interface is independent of other interface.
+ *
+ * A given feature might consume packet (if it's configured to consume) or may
+ * forward it to next enabled feature. For instance, "IPsec input" feature may
+ * consume/drop all packets with "Protect" policy action while all packets with
+ * policy action as "Bypass" may be forwarded to next enabled feature (with in
+ * same feature arc)
+ *
+ * This library facilitates rte graph based applications to steer packets in
+ * fast path to different feature nodes with-in a feature arc and support all
+ * functionalities described above
+ *
+ * In order to use feature-arc APIs, applications needs to do following in
+ * control path:
+ * - Initialize feature arc library via rte_graph_feature_arc_init()
+ * - Create feature arc via rte_graph_feature_arc_create()
+ * - *Before calling rte_graph_create()*, features must be added to feature-arc
+ * via rte_graph_feature_add(). rte_graph_feature_add() allows adding
+ * features in a sequential order with "runs_after" and "runs_before"
+ * constraints.
+ * - Post rte_graph_create(), features can be enabled/disabled at runtime on
+ * any interface via rte_graph_feature_enable()/rte_graph_feature_disable()
+ * - Feature arc can be destroyed via rte_graph_feature_arc_destroy()
+ *
+ * In fast path, APIs are provided to steer packets towards feature path from
+ * - start_node (provided as an argument to rte_graph_feature_arc_create())
+ * - feature nodes (which are added via rte_graph_feature_add())
+ *
+ * For typical steering of packets across feature nodes, application required
+ * to know "rte_edges" which are saved in feature data object. Feature data
+ * object is unique for every interface per feature with in a feature arc.
+ *
+ * When steering packets from start_node to feature node:
+ * - rte_graph_feature_arc_first_feature_get() provides first enabled feature.
+ * - Next rte_edge from start_node to first enabled feature can be obtained via
+ * rte_graph_feature_arc_feature_set()
+ *
+ * rte_mbuf can carry [current feature, index] from start_node of an arc to other
+ * feature nodes
+ *
+ * In feature node, application can get 32-bit user_data
+ * via_rte_graph_feature_user_data_get() which is provided in
+ * rte_graph_feature_enable(). User data can hold feature specific cookie like
+ * IPsec policy database index (if more than one are supported)
+ *
+ * If feature node is not consuming packet, next enabled feature and next
+ * rte_edge can be obtained via rte_graph_feature_arc_next_feature_get()
+ *
+ * It is application responsibility to ensure that at-least *last feature*(or sink
+ * feature) must be enabled from where packet can exit feature-arc path, if
+ * *NO* intermediate feature is consuming the packet and it has reached till
+ * the end of feature arc path
+ *
+ * Synchronization among cores
+ * ---------------------------
+ * Subsequent calls to rte_graph_feature_enable() is allowed while worker cores
+ * are processing in rte_graph_walk() loop. However, for
+ * rte_graph_feature_disable() application must use RCU based synchronization
+ */
+
+/**< Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_ARC_INITIALIZER ((rte_graph_feature_arc_t)UINT64_MAX)
+
+/** Max number of features supported in a given feature arc */
+#define RTE_GRAPH_FEATURE_MAX_PER_ARC 64
+
+/** Length of feature arc name */
+#define RTE_GRAPH_FEATURE_ARC_NAMELEN RTE_NODE_NAMESIZE
+
+/** @internal */
+#define rte_graph_feature_cast(x) ((rte_graph_feature_t)x)
+
+/**< Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_INVALID rte_graph_feature_cast(UINT8_MAX)
+
+/** rte_graph feature arc object */
+typedef uint64_t rte_graph_feature_arc_t;
+
+/** rte_graph feature object */
+typedef uint8_t rte_graph_feature_t;
+
+/** runtime active feature list index with in feature arc*/
+typedef uint8_t rte_graph_feature_rt_list_t;
+
+/** per feature arc monotonically increasing counter to synchronize fast path APIs */
+typedef uint16_t rte_graph_feature_counter_t;
+
+/**
+ * Initialize feature arc subsystem
+ *
+ * @param max_feature_arcs
+ * Maximum number of feature arcs required to be supported
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_init(int max_feature_arcs);
+
+/**
+ * Create a feature arc
+ *
+ * @param feature_arc_name
+ * Feature arc name with max length of @ref RTE_GRAPH_FEATURE_ARC_NAMELEN
+ * @param max_features
+ * Maximum number of features to be supported in this feature arc
+ * @param max_indexes
+ * Maximum number of interfaces/ports/indexes to be supported
+ * @param start_node
+ * Base node where this feature arc's features are checked in fast path
+ * @param[out] _arc
+ * Feature arc object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node,
+ rte_graph_feature_arc_t *_arc);
+
+/**
+ * Get feature arc object with name
+ *
+ * @param arc_name
+ * Feature arc name provided to successful @ref rte_graph_feature_arc_create
+ * @param[out] _arc
+ * Feature arc object returned
+ *
+ * @return
+ * 0: Success
+ * <0: Failure.
+ */
+__rte_experimental
+int rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc);
+
+/**
+ * Add a feature to already created feature arc. For instance
+ *
+ * 1. Add first feature node: "ipv4-input" to input arc
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-input", NULL, NULL);
+ *
+ * 2. Add "ipsec-input" feature node after "ipv4-input" node
+ * rte_graph_feature_add(ipv4_input_arc, "ipsec-input", "ipv4-input", NULL);
+ *
+ * 3. Add "ipv4-pre-classify-input" node before "ipv4-input" node
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-pre-classify-input"", NULL, "ipv4-input");
+ *
+ * 4. Add "acl-classify-input" node after ipv4-input but before ipsec-input
+ * rte_graph_feature_add(ipv4_input_arc, "acl-classify-input", "ipv4-input", "ipsec-input");
+ *
+ * @param _arc
+ * Feature arc handle returned from @ref rte_graph_feature_arc_create()
+ * @param feature_node
+ * Graph node representing feature. On success, feature_node is next_node of
+ * feature_arc->start_node
+ * @param runs_after
+ * Add this feature_node after already added "runs_after". Creates
+ * start_node -> runs_after -> this_feature sequence
+ * @param runs_before
+ * Add this feature_node before already added "runs_before". Creates
+ * start_node -> this_feature -> runs_before sequence
+ *
+ * <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>
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *runs_after, const char *runs_before);
+
+/**
+ * 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 user_data
+ * Application specific data which is retrieved in fast path
+ *
+ * @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,
+ int32_t user_data);
+
+/**
+ * Validate whether subsequent enable/disable feature would succeed or not.
+ * API is 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 is_enable_disable
+ * If 1, validate whether subsequent @ref rte_graph_feature_enable would pass or not
+ * If 0, validate whether subsequent @ref rte_graph_feature_disable would pass or not
+ *
+ * @return
+ * 0: Subsequent enable/disable API would pass
+ * <0: Subsequent enable/disable API would not pass
+ */
+__rte_experimental
+int rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index,
+ const char *feature_name, int is_enable_disable);
+
+/**
+ * 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
+ *
+ * @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);
+
+/**
+ * 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
+ * @param feature_name
+ * Feature name provided to @ref rte_graph_feature_add
+ * @param[out] feature
+ * Feature object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char *feature_name,
+ rte_graph_feature_t *feature);
+
+/**
+ * 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
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_destroy(rte_graph_feature_arc_t _arc);
+
+/**
+ * Cleanup all feature arcs
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_cleanup(void);
+
+/**
+ * Slow path API to know how many features are currently enabled within a featur-arc
+ *
+ * @param _arc
+ * Feature arc object
+ *
+ * @return: Number of enabled features
+ */
+__rte_experimental
+int rte_graph_feature_arc_num_enabled_features(rte_graph_feature_arc_t _arc);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/graph/rte_graph_feature_arc_worker.h b/lib/graph/rte_graph_feature_arc_worker.h
new file mode 100644
index 0000000000..6019d74853
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc_worker.h
@@ -0,0 +1,548 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+#define _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+
+#include <stddef.h>
+#include <rte_graph_feature_arc.h>
+#include <rte_bitops.h>
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc_worker.h
+ *
+ * Defines fast path structure
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @internal
+ *
+ * Slow path feature node info list
+ */
+struct rte_graph_feature_node_list {
+ /** Next feature */
+ STAILQ_ENTRY(rte_graph_feature_node_list) next_feature;
+
+ /** node representing feature */
+ struct rte_node_register *feature_node;
+
+ /** How many indexes/interfaces using this feature */
+ int32_t ref_count;
+
+ /* node_index in list (after feature_enable())*/
+ uint32_t node_index;
+
+ /** Back pointer to feature arc */
+ void *feature_arc;
+
+ /** rte_edge_t to this feature node from feature_arc->start_node */
+ rte_edge_t edge_to_this_feature;
+};
+
+/**
+ * Fast path holding rte_edge_t and next enabled feature for an feature
+ */
+typedef struct __rte_packed rte_graph_feature_data {
+ /* next node to which current mbuf should go*/
+ rte_edge_t next_edge;
+
+ /* next enabled feature on this arc for current index */
+ union {
+ uint16_t reserved;
+ struct {
+ rte_graph_feature_t next_enabled_feature;
+ };
+ };
+
+ /* user_data */
+ int32_t user_data;
+} rte_graph_feature_data_t;
+
+/**
+ * Fast path feature structure. Holds re_graph_feature_data_t per index
+ */
+struct __rte_cache_aligned rte_graph_feature {
+ uint16_t this_feature_index;
+
+ /* Array of size arc->feature_data_size
+ * [data-index-0][data-index-1]...
+ * Each index of size: sizeof(rte_graph_feature_data_t)
+ */
+ uint8_t feature_data_by_index[];
+};
+
+/**
+ * fast path cache aligned feature list holding all features
+ * There are two feature lists: active, passive
+ *
+ * Fast APIs works on active list while control plane updates passive list
+ * A atomic update to arc->active_feature_list is done to switch between active
+ * and passive
+ */
+typedef struct __rte_cache_aligned rte_graph_feature_list {
+ /**
+ * fast path array holding per_feature data.
+ * Duplicate entry as feature-arc also hold this pointer
+ * arc->features[]
+ *
+ *<-------------feature-0 ---------><CEIL><---------feature-1 -------------->...
+ *[index-0][index-1]...[max_index-1] [index-0][index-1] ...[max_index-1]...
+ */
+ struct rte_graph_feature *indexed_by_features;
+ /*
+ * fast path array holding first enabled feature per index
+ * (Required in start_node. In non start_node, mbuf can hold next enabled
+ * feature)
+ */
+ rte_graph_feature_t first_enabled_feature_by_index[];
+} rte_graph_feature_list_t;
+
+/**
+ * rte_graph feature arc object
+ *
+ * A feature-arc can only hold RTE_GRAPH_FEATURE_MAX_PER_ARC features but no
+ * limit to interface index
+ *
+ * Representing a feature arc holding all features which are enabled/disabled
+ * on any interfaces
+ */
+struct __rte_cache_aligned rte_graph_feature_arc {
+ /* First 64B is fast path variables */
+ RTE_MARKER fast_path_variables;
+
+ /** runtime active feature list */
+ rte_graph_feature_rt_list_t active_feature_list;
+
+ /* Actual Size of feature_list0 */
+ uint16_t feature_list_size;
+
+ /**
+ * Size each feature in fastpath.
+ * sizeof(arc->active_list->indexed_by_feature[0])
+ */
+ uint16_t feature_size;
+
+ /* Size of arc->max_index * sizeof(rte_graph_feature_data_t) */
+ uint16_t feature_data_size;
+
+ /**
+ * Fast path bitmask indicating if a feature is enabled or not Number
+ * of bits: RTE_GRAPH_FEATURE_MAX_PER_ARC
+ */
+ uint64_t feature_enable_bitmask[2];
+ rte_graph_feature_list_t *feature_list[2];
+ struct rte_graph_feature *features[2];
+
+ /** index in feature_arc_main */
+ uint16_t feature_arc_index;
+
+ uint16_t reserved[3];
+
+ /** Slow path variables follows*/
+ RTE_MARKER slow_path_variables;
+
+ /** feature arc name */
+ char feature_arc_name[RTE_GRAPH_FEATURE_ARC_NAMELEN];
+
+ /** All feature lists */
+ STAILQ_HEAD(, rte_graph_feature_node_list) all_features;
+
+ uint32_t runtime_enabled_features;
+
+ /** Back pointer to feature_arc_main */
+ void *feature_arc_main;
+
+ /* start_node */
+ struct rte_node_register *start_node;
+
+ /* maximum number of features supported by this arc */
+ uint32_t max_features;
+
+ /* maximum number of index supported by this arc */
+ uint32_t max_indexes;
+
+ /* Slow path bit mask per feature per index */
+ uint64_t feature_bit_mask_by_index[];
+};
+
+/** Feature arc main */
+typedef struct feature_arc_main {
+ /** number of feature arcs created by application */
+ uint32_t num_feature_arcs;
+
+ /** max features arcs allowed */
+ uint32_t max_feature_arcs;
+
+ /** feature arcs */
+ rte_graph_feature_arc_t feature_arcs[];
+} rte_graph_feature_arc_main_t;
+
+/** @internal Get feature arc pointer from object */
+#define rte_graph_feature_arc_get(arc) ((struct rte_graph_feature_arc *)arc)
+
+extern rte_graph_feature_arc_main_t *__feature_arc_main;
+
+/**
+ * API to know if feature is valid or not
+ */
+
+static __rte_always_inline int
+rte_graph_feature_is_valid(rte_graph_feature_t feature)
+{
+ return (feature != RTE_GRAPH_FEATURE_INVALID);
+}
+
+/**
+ * Get rte_graph_feature object with no checks
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ * @param feature_list
+ * active feature list retrieved from rte_graph_feature_arc_has_any_feature()
+ * or rte_graph_feature_arc_has_feature()
+ *
+ * @return
+ * Internal feature object.
+ */
+static __rte_always_inline struct rte_graph_feature *
+__rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature,
+ const rte_graph_feature_rt_list_t feature_list)
+{
+ return ((struct rte_graph_feature *)((uint8_t *)(arc->features[feature_list] +
+ (feature * arc->feature_size))));
+}
+
+/**
+ * Get rte_graph_feature object for a given interface/index from feature arc
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ *
+ * @return
+ * Internal feature object.
+ */
+static __rte_always_inline struct rte_graph_feature *
+rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature)
+{
+ RTE_VERIFY(feature < arc->max_features);
+
+ if (likely(rte_graph_feature_is_valid(feature)))
+ return __rte_graph_feature_get(arc, feature, arc->active_feature_list);
+
+ return NULL;
+}
+
+static __rte_always_inline rte_graph_feature_data_t *
+__rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ RTE_SET_USED(arc);
+ return ((rte_graph_feature_data_t *)(feature->feature_data_by_index +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Get rte_graph feature data object for a index in feature
+ *
+ * @param arc
+ * feature arc
+ * @param feature
+ * Pointer to feature object
+ * @param index
+ * Index of feature maintained in slow path linked list
+ *
+ * @return
+ * Valid feature data
+ */
+static __rte_always_inline rte_graph_feature_data_t *
+rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ if (likely(index < arc->max_indexes))
+ return __rte_graph_feature_data_get(arc, feature, index);
+
+ RTE_VERIFY(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
+ * @param[out] plist
+ * Pointer to runtime active feature list which needs to be provided to other
+ * fast path APIs
+ *
+ * @return
+ * 0: If no feature enabled
+ * Non-Zero: Bitmask of features enabled. plist is valid
+ *
+ */
+static __rte_always_inline uint64_t
+rte_graph_feature_arc_has_any_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_rt_list_t *plist)
+{
+ *plist = __atomic_load_n(&arc->active_feature_list, __ATOMIC_RELAXED);
+
+ return (__atomic_load_n(arc->feature_enable_bitmask + (uint8_t)*plist,
+ __ATOMIC_RELAXED));
+}
+
+/**
+ * Fast path API to check if provided feature is enabled on any interface/index
+ * or not
+ *
+ * @param arc
+ * Feature arc object
+ * @param feature
+ * Input rte_graph_feature_t that needs to be checked
+ * @param[out] plist
+ * Returns active list to caller which needs to be provided to other fast path
+ * APIs
+ *
+ * @return
+ * 1: If feature is enabled in arc
+ * 0: If feature is not enabled in arc
+ */
+static __rte_always_inline int
+rte_graph_feature_arc_has_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_t feature,
+ rte_graph_feature_rt_list_t *plist)
+{
+ uint64_t bitmask = RTE_BIT64(feature);
+
+ *plist = __atomic_load_n(&arc->active_feature_list, __ATOMIC_RELAXED);
+
+ return (bitmask & __atomic_load_n(arc->feature_enable_bitmask + (uint8_t)*plist,
+ __ATOMIC_RELAXED));
+}
+
+/**
+ * Prefetch feature arc fast path cache line
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ */
+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 related fast path cache line
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object
+ */
+static __rte_always_inline void
+rte_graph_feature_arc_feature_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature)
+{
+ /* feature cache line */
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)__rte_graph_feature_get(arc, feature, list));
+}
+
+/**
+ * Prefetch feature data upfront. Perform sanity
+ *
+ * @param _arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object returned from @ref
+ * rte_graph_feature_arc_first_feature_get()
+ * @param index
+ * Interface/index
+ */
+static __rte_always_inline void
+rte_graph_feature_arc_data_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature, uint32_t index)
+{
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)((uint8_t *)arc->features[list] +
+ offsetof(struct rte_graph_feature, feature_data_by_index) +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Fast path API to get first enabled feature on interface index
+ * Typically required in arc->start_node so that from returned feature,
+ * feature-data can be retrieved to steer packets
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * rte_graph_feature_arc_has_feature()
+ * @param index
+ * Interface Index
+ * @param[out] feature
+ * Pointer to rte_graph_feature_t.
+ *
+ * @return
+ * 0. Success. feature field is valid
+ * 1. Failure. feature field is invalid
+ *
+ */
+static __rte_always_inline int
+rte_graph_feature_arc_first_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+
+ *feature = feature_list->first_enabled_feature_by_index[index];
+
+ return rte_graph_feature_is_valid(*feature);
+}
+
+/**
+ * Fast path API to get next enabled feature on interface index with provided
+ * input feature
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * @param index
+ * Interface Index
+ * @param[in][out] feature
+ * Pointer to rte_graph_feature_t. Input feature set to next enabled feature
+ * after success return
+ * @param[out] next_edge
+ * Edge from current feature to next feature. Valid only if next feature is valid
+ *
+ * @return
+ * 0. Success. next enabled feature is valid.
+ * 1. Failure. next enabled feature is invalid
+ */
+static __rte_always_inline int
+rte_graph_feature_arc_next_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature,
+ rte_edge_t *next_edge)
+{
+ rte_graph_feature_data_t *feature_data = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(*feature))) {
+ f = __rte_graph_feature_get(arc, *feature, list);
+ feature_data = rte_graph_feature_data_get(arc, f, index);
+ *feature = feature_data->next_enabled_feature;
+ *next_edge = feature_data->next_edge;
+ return (*feature == RTE_GRAPH_FEATURE_INVALID);
+ }
+
+ return 1;
+}
+
+/**
+ * Set fields with respect to first enabled feature in an arc and return edge
+ * Typically returned feature and interface index must be saved in rte_mbuf
+ * structure to pass this information to next feature node
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param index
+ * Index (of interface)
+ * @param[out] gf
+ * Pointer to rte_graph_feature_t. Valid if API returns Success
+ * @param[out] edge
+ * Edge to steer packet from arc->start_node to first enabled feature. Valid
+ * only if API returns Success
+ *
+ * @return
+ * 0: If valid feature is set by API
+ * 1: If valid feature is NOT set by API
+ */
+static __rte_always_inline rte_graph_feature_t
+rte_graph_feature_arc_feature_set(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *gf,
+ rte_edge_t *edge)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+ struct rte_graph_feature_data *feature_data = NULL;
+ struct rte_graph_feature *feature = NULL;
+ rte_graph_feature_t f;
+
+ /* reset */
+ *gf = RTE_GRAPH_FEATURE_INVALID;
+ f = feature_list->first_enabled_feature_by_index[index];
+
+ if (unlikely(rte_graph_feature_is_valid(f))) {
+ feature = __rte_graph_feature_get(arc, f, list);
+ feature_data = rte_graph_feature_data_get(arc, feature, index);
+ *gf = f;
+ *edge = feature_data->next_edge;
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * Get user data corresponding to current feature set by application in
+ * rte_graph_feature_enable()
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Feature index
+ * @param index
+ * Interface index
+ *
+ * @return
+ * UINT32_MAX: Failure
+ * Valid user data: Success
+ */
+static __rte_always_inline uint32_t
+rte_graph_feature_user_data_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature,
+ uint32_t index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(feature))) {
+ f = __rte_graph_feature_get(arc, feature, list);
+ fdata = rte_graph_feature_data_get(arc, f, index);
+ return fdata->user_data;
+ }
+
+ return UINT32_MAX;
+}
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/graph/version.map b/lib/graph/version.map
index 2c83425ddc..82b2469fba 100644
--- a/lib/graph/version.map
+++ b/lib/graph/version.map
@@ -52,3 +52,20 @@ DPDK_25 {
local: *;
};
+
+EXPERIMENTAL {
+ global:
+
+ # added in 24.11
+ rte_graph_feature_arc_init;
+ rte_graph_feature_arc_create;
+ rte_graph_feature_arc_lookup_by_name;
+ rte_graph_feature_add;
+ rte_graph_feature_enable;
+ rte_graph_feature_validate;
+ rte_graph_feature_disable;
+ rte_graph_feature_lookup;
+ rte_graph_feature_arc_destroy;
+ rte_graph_feature_arc_cleanup;
+ rte_graph_feature_arc_num_enabled_features;
+};
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH 2/3] graph: add feature arc option in graph create
2024-09-07 7:31 [RFC PATCH 0/3] add feature arc in rte_graph Nitin Saxena
2024-09-07 7:31 ` [RFC PATCH 1/3] graph: add feature arc support Nitin Saxena
@ 2024-09-07 7:31 ` Nitin Saxena
2024-09-07 7:31 ` [RFC PATCH 3/3] graph: add IPv4 output feature arc Nitin Saxena
` (2 subsequent siblings)
4 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-09-07 7:31 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena, Pavan Nikhilesh
Added option in graph create to call feature-specific process node
functions. This removes extra overhead for checking feature arc status
in nodes where application is not using feature arc processing
Signed-off-by: Pavan Nikhilesh <pbhagavatula@marvell.com>
---
lib/graph/graph.c | 1 +
lib/graph/graph_populate.c | 7 ++++++-
lib/graph/graph_private.h | 3 +++
lib/graph/node.c | 2 ++
lib/graph/rte_graph.h | 3 +++
5 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/lib/graph/graph.c b/lib/graph/graph.c
index d5b8c9f918..b0ad3a83ae 100644
--- a/lib/graph/graph.c
+++ b/lib/graph/graph.c
@@ -455,6 +455,7 @@ rte_graph_create(const char *name, struct rte_graph_param *prm)
graph->parent_id = RTE_GRAPH_ID_INVALID;
graph->lcore_id = RTE_MAX_LCORE;
graph->num_pkt_to_capture = prm->num_pkt_to_capture;
+ graph->feature_arc_enabled = prm->feature_arc_enable;
if (prm->pcap_filename)
rte_strscpy(graph->pcap_filename, prm->pcap_filename, RTE_GRAPH_PCAP_FILE_SZ);
diff --git a/lib/graph/graph_populate.c b/lib/graph/graph_populate.c
index ed596a7711..2892f3cce2 100644
--- a/lib/graph/graph_populate.c
+++ b/lib/graph/graph_populate.c
@@ -79,8 +79,13 @@ graph_nodes_populate(struct graph *_graph)
if (graph_pcap_is_enable()) {
node->process = graph_pcap_dispatch;
node->original_process = graph_node->node->process;
- } else
+ if (_graph->feature_arc_enabled)
+ node->original_process = graph_node->node->feat_arc_proc;
+ } else {
node->process = graph_node->node->process;
+ if (_graph->feature_arc_enabled)
+ node->process = graph_node->node->feat_arc_proc;
+ }
memcpy(node->name, graph_node->node->name, RTE_GRAPH_NAMESIZE);
pid = graph_node->node->parent_id;
if (pid != RTE_NODE_ID_INVALID) { /* Cloned node */
diff --git a/lib/graph/graph_private.h b/lib/graph/graph_private.h
index d557d55f2d..58ba0abeff 100644
--- a/lib/graph/graph_private.h
+++ b/lib/graph/graph_private.h
@@ -56,6 +56,7 @@ struct node {
unsigned int lcore_id;
/**< Node runs on the Lcore ID used for mcore dispatch model. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Allocated identifier for the node. */
@@ -126,6 +127,8 @@ struct graph {
/**< Number of packets to be captured per core. */
char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
/**< pcap file name/path. */
+ uint8_t feature_arc_enabled;
+ /**< Graph feature arc. */
STAILQ_HEAD(gnode_list, graph_node) node_list;
/**< Nodes in a graph. */
};
diff --git a/lib/graph/node.c b/lib/graph/node.c
index 99a9622779..d8fd273543 100644
--- a/lib/graph/node.c
+++ b/lib/graph/node.c
@@ -90,6 +90,7 @@ __rte_node_register(const struct rte_node_register *reg)
goto free;
node->flags = reg->flags;
node->process = reg->process;
+ node->feat_arc_proc = reg->feat_arc_proc;
node->init = reg->init;
node->fini = reg->fini;
node->nb_edges = reg->nb_edges;
@@ -137,6 +138,7 @@ node_clone(struct node *node, const char *name)
/* Clone the source node */
reg->flags = node->flags;
reg->process = node->process;
+ reg->feat_arc_proc = node->feat_arc_proc;
reg->init = node->init;
reg->fini = node->fini;
reg->nb_edges = node->nb_edges;
diff --git a/lib/graph/rte_graph.h b/lib/graph/rte_graph.h
index ecfec2068a..ebbdbbea48 100644
--- a/lib/graph/rte_graph.h
+++ b/lib/graph/rte_graph.h
@@ -163,6 +163,8 @@ struct rte_graph_param {
uint64_t num_pkt_to_capture; /**< Number of packets to capture. */
char *pcap_filename; /**< Filename in which packets to be captured.*/
+ bool feature_arc_enable; /**< Enable Graph feature arc. */
+
union {
struct {
uint64_t rsvd; /**< Reserved for rtc model. */
@@ -470,6 +472,7 @@ struct rte_node_register {
uint64_t flags; /**< Node configuration flag. */
#define RTE_NODE_SOURCE_F (1ULL << 0) /**< Node type is source. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Node Identifier. */
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH 3/3] graph: add IPv4 output feature arc
2024-09-07 7:31 [RFC PATCH 0/3] add feature arc in rte_graph Nitin Saxena
2024-09-07 7:31 ` [RFC PATCH 1/3] graph: add feature arc support Nitin Saxena
2024-09-07 7:31 ` [RFC PATCH 2/3] graph: add feature arc option in graph create Nitin Saxena
@ 2024-09-07 7:31 ` Nitin Saxena
2024-10-08 8:04 ` [RFC PATCH 0/3] add feature arc in rte_graph David Marchand
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
4 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-09-07 7:31 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
add ipv4-output feature arc in ipv4-rewrite node to allow
custom/standard nodes(like outbound IPsec policy node) in outgoing
forwarding path
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
lib/node/ip4_rewrite.c | 476 +++++++++++++++++++++++++++++-------
lib/node/ip4_rewrite_priv.h | 9 +-
lib/node/node_private.h | 19 +-
lib/node/rte_node_ip4_api.h | 3 +
4 files changed, 411 insertions(+), 96 deletions(-)
diff --git a/lib/node/ip4_rewrite.c b/lib/node/ip4_rewrite.c
index 34a920df5e..916f180a3d 100644
--- a/lib/node/ip4_rewrite.c
+++ b/lib/node/ip4_rewrite.c
@@ -15,39 +15,156 @@
#include "ip4_rewrite_priv.h"
#include "node_private.h"
+#define ALL_PKT_MASK 0xf
+
struct ip4_rewrite_node_ctx {
+ rte_graph_feature_arc_t output_feature_arc;
/* Dynamic offset to mbuf priv1 */
int mbuf_priv1_off;
/* Cached next index */
uint16_t next_index;
+ uint16_t last_tx;
};
+typedef struct rewrite_priv_vars {
+ union {
+ struct {
+ rte_xmm_t xmm1;
+ };
+ struct __rte_packed {
+ uint16_t next0;
+ uint16_t next1;
+ uint16_t next2;
+ uint16_t next3;
+ uint16_t last_tx_interface;
+ uint16_t last_if_feature;
+ uint16_t actual_feat_mask;
+ uint16_t speculative_feat_mask;
+ };
+ };
+} rewrite_priv_vars_t;
+
static struct ip4_rewrite_node_main *ip4_rewrite_nm;
#define IP4_REWRITE_NODE_LAST_NEXT(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->next_index)
+#define IP4_REWRITE_NODE_LAST_TX(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->last_tx)
+
#define IP4_REWRITE_NODE_PRIV1_OFF(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->mbuf_priv1_off)
-static uint16_t
-ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
- void **objs, uint16_t nb_objs)
+#define IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->output_feature_arc)
+
+static __rte_always_inline void
+prefetch_mbuf_and_dynfield(struct rte_mbuf *mbuf)
{
+ /* prefetch first cache line required for accessing buf_addr */
+ rte_prefetch0((void *)mbuf);
+}
+
+static __rte_always_inline void
+check_output_feature_x4(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t flist,
+ rewrite_priv_vars_t *pvar, struct node_mbuf_priv1 *priv0,
+ struct node_mbuf_priv1 *priv1, struct node_mbuf_priv1 *priv2,
+ struct node_mbuf_priv1 *priv3)
+{
+ uint32_t mask = 0;
+ uint16_t xor = 0;
+
+ /*
+ * interface edge's start from 1 and not from 0 as "pkt_drop"
+ * is next node at 0th index
+ */
+ priv0->if_index = pvar->next0 - 1;
+ priv1->if_index = pvar->next1 - 1;
+ priv2->if_index = pvar->next2 - 1;
+ priv3->if_index = pvar->next3 - 1;
+
+ /* Find out if all packets are sent to last_tx_interface */
+ xor = pvar->last_tx_interface ^ priv0->if_index;
+ xor += priv0->if_index ^ priv1->if_index;
+ xor += priv1->if_index ^ priv2->if_index;
+ xor += priv2->if_index ^ priv3->if_index;
+
+ if (likely(!xor)) {
+ /* copy last interface feature and feature mask */
+ priv0->current_feature = priv1->current_feature =
+ priv2->current_feature = priv3->current_feature =
+ pvar->last_if_feature;
+ pvar->actual_feat_mask = pvar->speculative_feat_mask;
+ } else {
+ /* create a mask for index which does not have feature
+ * Also override next edge and if feature enabled, get feature
+ */
+ mask = rte_graph_feature_arc_feature_set(arc, flist, priv0->if_index,
+ &priv0->current_feature,
+ &pvar->next0);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv1->if_index,
+ &priv1->current_feature,
+ &pvar->next1)) << 1);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv2->if_index,
+ &priv2->current_feature,
+ &pvar->next2)) << 2);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv3->if_index,
+ &priv3->current_feature,
+ &pvar->next3)) << 3);
+
+ /*
+ * add last tx and last feature regardless even if feature is
+ * valid or not
+ */
+ pvar->last_tx_interface = priv3->if_index;
+ pvar->last_if_feature = priv3->current_feature;
+ /* Set 0xf if invalid feature to last packet, else 0 */
+ pvar->speculative_feat_mask = (priv3->current_feature ==
+ RTE_GRAPH_FEATURE_INVALID) ? ALL_PKT_MASK : 0x0;
+ pvar->actual_feat_mask = mask;
+ }
+}
+
+static __rte_always_inline uint16_t
+__ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ struct rte_graph_feature_arc *out_feature_arc,
+ void **objs, uint16_t nb_objs,
+ const int dyn, const int check_enabled_features,
+ const rte_graph_feature_rt_list_t flist)
+{
+ struct node_mbuf_priv1 *priv0 = NULL, *priv1 = NULL, *priv2 = NULL, *priv3 = NULL;
struct rte_mbuf *mbuf0, *mbuf1, *mbuf2, *mbuf3, **pkts;
struct ip4_rewrite_nh_header *nh = ip4_rewrite_nm->nh;
- const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
- uint16_t next0, next1, next2, next3, next_index;
- struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
uint16_t n_left_from, held = 0, last_spec = 0;
+ struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
+ rewrite_priv_vars_t pvar;
+ int64_t fd0, fd1, fd2, fd3;
+ rte_edge_t fix_spec = 0;
void *d0, *d1, *d2, *d3;
void **to_next, **from;
+ uint16_t next_index;
rte_xmm_t priv01;
rte_xmm_t priv23;
int i;
- /* Speculative next as last next */
+ RTE_SET_USED(fd0);
+ RTE_SET_USED(fd1);
+ RTE_SET_USED(fd2);
+ RTE_SET_USED(fd3);
+
+ /* Initialize speculative variables.*/
+
+ /* Last interface */
+ pvar.last_tx_interface = IP4_REWRITE_NODE_LAST_TX(node->ctx);
+ /*last next from node ctx*/
next_index = IP4_REWRITE_NODE_LAST_NEXT(node->ctx);
+ pvar.speculative_feat_mask = ALL_PKT_MASK;
+ pvar.actual_feat_mask = 0;
+
rte_prefetch0(nh);
pkts = (struct rte_mbuf **)objs;
@@ -55,20 +172,47 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
n_left_from = nb_objs;
for (i = 0; i < 4 && i < n_left_from; i++)
- rte_prefetch0(pkts[i]);
+ prefetch_mbuf_and_dynfield(pkts[i]);
/* Get stream for the speculated next node */
to_next = rte_node_next_stream_get(graph, node, next_index, nb_objs);
+
+ /* prefetch speculative feature and corresponding data */
+ if (check_enabled_features) {
+ /*
+ * Get first feature enabled, if any, on last_tx_interface
+ */
+ if (unlikely(!rte_graph_feature_arc_first_feature_get(out_feature_arc,
+ flist,
+ pvar.last_tx_interface,
+ (rte_graph_feature_t *)
+ &pvar.last_if_feature))) {
+ /* prefetch feature cache line */
+ rte_graph_feature_arc_feature_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature);
+
+ /* prefetch feature data cache line */
+ rte_graph_feature_arc_data_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature,
+ pvar.last_tx_interface);
+ /*
+ * Set speculativa_feat mask to indicate, all 4 packets
+ * going to feature path
+ */
+ pvar.speculative_feat_mask = 0;
+ }
+ }
+
/* Update Ethernet header of pkts */
while (n_left_from >= 4) {
if (likely(n_left_from > 7)) {
/* Prefetch only next-mbuf struct and priv area.
* Data need not be prefetched as we only write.
*/
- rte_prefetch0(pkts[4]);
- rte_prefetch0(pkts[5]);
- rte_prefetch0(pkts[6]);
- rte_prefetch0(pkts[7]);
+ prefetch_mbuf_and_dynfield(pkts[4]);
+ prefetch_mbuf_and_dynfield(pkts[5]);
+ prefetch_mbuf_and_dynfield(pkts[6]);
+ prefetch_mbuf_and_dynfield(pkts[7]);
}
mbuf0 = pkts[0];
@@ -78,66 +222,138 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 4;
n_left_from -= 4;
+
+ /* Copy mbuf private data into private variables */
priv01.u64[0] = node_mbuf_priv1(mbuf0, dyn)->u;
priv01.u64[1] = node_mbuf_priv1(mbuf1, dyn)->u;
priv23.u64[0] = node_mbuf_priv1(mbuf2, dyn)->u;
priv23.u64[1] = node_mbuf_priv1(mbuf3, dyn)->u;
- /* Increment checksum by one. */
- priv01.u32[1] += rte_cpu_to_be_16(0x0100);
- priv01.u32[3] += rte_cpu_to_be_16(0x0100);
- priv23.u32[1] += rte_cpu_to_be_16(0x0100);
- priv23.u32[3] += rte_cpu_to_be_16(0x0100);
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
- nh[priv01.u16[0]].rewrite_len);
-
- next0 = nh[priv01.u16[0]].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- ip0->time_to_live = priv01.u16[1] - 1;
- ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
- d1 = rte_pktmbuf_mtod(mbuf1, void *);
- rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
- nh[priv01.u16[4]].rewrite_len);
-
- next1 = nh[priv01.u16[4]].tx_node;
- ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
- sizeof(struct rte_ether_hdr));
- ip1->time_to_live = priv01.u16[5] - 1;
- ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
- d2 = rte_pktmbuf_mtod(mbuf2, void *);
- rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
- nh[priv23.u16[0]].rewrite_len);
- next2 = nh[priv23.u16[0]].tx_node;
- ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
- sizeof(struct rte_ether_hdr));
- ip2->time_to_live = priv23.u16[1] - 1;
- ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
- d3 = rte_pktmbuf_mtod(mbuf3, void *);
- rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
- nh[priv23.u16[4]].rewrite_len);
-
- next3 = nh[priv23.u16[4]].tx_node;
- ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
- sizeof(struct rte_ether_hdr));
- ip3->time_to_live = priv23.u16[5] - 1;
- ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ /* Copy next edge from next hop */
+ pvar.next0 = nh[priv01.u16[0]].tx_node;
+ pvar.next1 = nh[priv01.u16[4]].tx_node;
+ pvar.next2 = nh[priv23.u16[0]].tx_node;
+ pvar.next3 = nh[priv23.u16[4]].tx_node;
+
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ priv1 = node_mbuf_priv1(mbuf1, dyn);
+ priv2 = node_mbuf_priv1(mbuf2, dyn);
+ priv3 = node_mbuf_priv1(mbuf3, dyn);
+
+ /* If feature is enabled, override next edge for each mbuf
+ * and set node_mbuf_priv data appropriately
+ */
+ check_output_feature_x4(out_feature_arc, flist,
+ &pvar, priv0, priv1, priv2, priv3);
+
+ /* check_output_feature_x4() returns bit mask which indicates
+ * which packet is not following feature path, hence normal processing
+ * has to happen on them
+ */
+ if (unlikely(pvar.actual_feat_mask)) {
+ if (pvar.actual_feat_mask & 0x1) {
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x2) {
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+ }
+ if (pvar.actual_feat_mask & 0x4) {
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x8) {
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
+ }
+ } else {
+ /* Case when no feature is enabled */
+
+ /* Increment checksum by one. */
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
/* Enqueue four to next node */
- rte_edge_t fix_spec =
- ((next_index == next0) && (next0 == next1) &&
- (next1 == next2) && (next2 == next3));
+ fix_spec = next_index ^ pvar.next0;
+ fix_spec += next_index ^ pvar.next1;
+ fix_spec += next_index ^ pvar.next2;
+ fix_spec += next_index ^ pvar.next3;
- if (unlikely(fix_spec == 0)) {
+ if (unlikely(fix_spec != 0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -146,56 +362,56 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
last_spec = 0;
/* next0 */
- if (next_index == next0) {
+ if (next_index == pvar.next0) {
to_next[0] = from[0];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next0,
+ rte_node_enqueue_x1(graph, node, pvar.next0,
from[0]);
}
/* next1 */
- if (next_index == next1) {
+ if (next_index == pvar.next1) {
to_next[0] = from[1];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next1,
+ rte_node_enqueue_x1(graph, node, pvar.next1,
from[1]);
}
/* next2 */
- if (next_index == next2) {
+ if (next_index == pvar.next2) {
to_next[0] = from[2];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next2,
+ rte_node_enqueue_x1(graph, node, pvar.next2,
from[2]);
}
/* next3 */
- if (next_index == next3) {
+ if (next_index == pvar.next3) {
to_next[0] = from[3];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next3,
+ rte_node_enqueue_x1(graph, node, pvar.next3,
from[3]);
}
from += 4;
/* Change speculation if last two are same */
- if ((next_index != next3) && (next2 == next3)) {
+ if ((next_index != pvar.next3) && (pvar.next2 == pvar.next3)) {
/* Put the current speculated node */
rte_node_next_stream_put(graph, node,
next_index, held);
held = 0;
/* Get next speculated stream */
- next_index = next3;
+ next_index = pvar.next3;
to_next = rte_node_next_stream_get(
graph, node, next_index, nb_objs);
}
@@ -212,20 +428,41 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 1;
n_left_from -= 1;
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
- nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
-
- next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
- rte_cpu_to_be_16(0x0100);
- chksum += chksum >= 0xffff;
- ip0->hdr_checksum = chksum;
- ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
-
- if (unlikely(next_index ^ next0)) {
+ pvar.next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ if (pvar.next0 != (pvar.last_tx_interface + 1)) {
+ priv0->if_index = pvar.next0 - 1;
+ rte_graph_feature_arc_feature_set(out_feature_arc, flist,
+ priv0->if_index,
+ &priv0->current_feature,
+ &pvar.next0);
+ pvar.last_tx_interface = priv0->if_index;
+ pvar.last_if_feature = priv0->current_feature;
+ } else {
+ /* current mbuf index is same as last_tx_interface */
+ priv0->if_index = pvar.last_tx_interface;
+ priv0->current_feature = pvar.last_if_feature;
+ }
+ }
+ /* Do the needful if either feature arc is disabled OR
+ * Invalid feature is present
+ */
+ if (!check_enabled_features ||
+ (priv0->current_feature == RTE_GRAPH_FEATURE_INVALID)) {
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
+ nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
+ rte_cpu_to_be_16(0x0100);
+ chksum += chksum >= 0xffff;
+ ip0->hdr_checksum = chksum;
+ ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
+ }
+ if (unlikely(next_index ^ pvar.next0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -233,13 +470,15 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
held += last_spec;
last_spec = 0;
- rte_node_enqueue_x1(graph, node, next0, from[0]);
+ rte_node_enqueue_x1(graph, node, pvar.next0, from[0]);
from += 1;
} else {
last_spec += 1;
}
}
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = pvar.last_tx_interface;
+
/* !!! Home run !!! */
if (likely(last_spec == nb_objs)) {
rte_node_next_stream_move(graph, node, next_index);
@@ -255,22 +494,78 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
return nb_objs;
}
+static uint16_t
+ip4_rewrite_feature_node_process(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(IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx));
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+ rte_graph_feature_rt_list_t flist;
+
+ /* If any feature is enabled on this arc */
+ if (unlikely(rte_graph_feature_arc_has_any_feature(arc, &flist))) {
+ if (flist)
+ return __ip4_rewrite_node_process(graph, node, arc, objs, nb_objs,
+ dyn,
+ 1 /* check features */,
+ (rte_graph_feature_rt_list_t)1);
+ else
+ return __ip4_rewrite_node_process(graph, node, arc, objs, nb_objs,
+ dyn,
+ 1 /* check features */,
+ (rte_graph_feature_rt_list_t)0);
+ } else {
+ return __ip4_rewrite_node_process(graph, node, arc, objs, nb_objs, dyn,
+ 0/* don't check features*/,
+ 0/* don't care */);
+ }
+ return 0;
+}
+
+static uint16_t
+ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+
+ return __ip4_rewrite_node_process(graph, node, NULL, objs, nb_objs, dyn,
+ 0/* don't check features*/,
+ 0/* don't care */);
+}
+
static int
ip4_rewrite_node_init(const struct rte_graph *graph, struct rte_node *node)
{
+ rte_graph_feature_arc_t feature_arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
static bool init_once;
RTE_SET_USED(graph);
RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_node_ctx) > RTE_NODE_CTX_SZ);
+ RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_nh_header) != RTE_CACHE_LINE_MIN_SIZE);
if (!init_once) {
node_mbuf_priv1_dynfield_offset = rte_mbuf_dynfield_register(
&node_mbuf_priv1_dynfield_desc);
if (node_mbuf_priv1_dynfield_offset < 0)
return -rte_errno;
- init_once = true;
+
+ /* Create ipv4-output feature arc, if not created
+ */
+ if (rte_graph_feature_arc_lookup_by_name(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ NULL) < 0) {
+ if (rte_graph_feature_arc_create(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ RTE_GRAPH_FEATURE_MAX_PER_ARC,
+ RTE_MAX_ETHPORTS,
+ ip4_rewrite_node_get(), &feature_arc)) {
+ return -rte_errno;
+ }
+ init_once = true;
+ }
}
IP4_REWRITE_NODE_PRIV1_OFF(node->ctx) = node_mbuf_priv1_dynfield_offset;
+ IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx) = feature_arc;
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = UINT16_MAX;
node_dbg("ip4_rewrite", "Initialized ip4_rewrite node initialized");
@@ -329,6 +624,7 @@ rte_node_ip4_rewrite_add(uint16_t next_hop, uint8_t *rewrite_data,
static struct rte_node_register ip4_rewrite_node = {
.process = ip4_rewrite_node_process,
+ .feat_arc_proc = ip4_rewrite_feature_node_process,
.name = "ip4_rewrite",
/* Default edge i.e '0' is pkt drop */
.nb_edges = 1,
diff --git a/lib/node/ip4_rewrite_priv.h b/lib/node/ip4_rewrite_priv.h
index 5105ec1d29..27ccc67489 100644
--- a/lib/node/ip4_rewrite_priv.h
+++ b/lib/node/ip4_rewrite_priv.h
@@ -5,6 +5,7 @@
#define __INCLUDE_IP4_REWRITE_PRIV_H__
#include <rte_common.h>
+#include <rte_graph_feature_arc.h>
#define RTE_GRAPH_IP4_REWRITE_MAX_NH 64
#define RTE_GRAPH_IP4_REWRITE_MAX_LEN 56
@@ -15,11 +16,9 @@
* Ipv4 rewrite next hop header data structure. Used to store port specific
* rewrite data.
*/
-struct ip4_rewrite_nh_header {
- uint16_t rewrite_len; /**< Header rewrite length. */
+struct __rte_cache_min_aligned ip4_rewrite_nh_header {
uint16_t tx_node; /**< Tx node next index identifier. */
- uint16_t enabled; /**< NH enable flag */
- uint16_t rsvd;
+ uint16_t rewrite_len; /**< Header rewrite length. */
union {
struct {
struct rte_ether_addr dst;
@@ -30,6 +29,8 @@ struct ip4_rewrite_nh_header {
uint8_t rewrite_data[RTE_GRAPH_IP4_REWRITE_MAX_LEN];
/**< Generic rewrite data */
};
+ /* used in control path */
+ uint8_t enabled; /**< NH enable flag */
};
/**
diff --git a/lib/node/node_private.h b/lib/node/node_private.h
index 1de7306792..7c26686948 100644
--- a/lib/node/node_private.h
+++ b/lib/node/node_private.h
@@ -12,6 +12,9 @@
#include <rte_mbuf.h>
#include <rte_mbuf_dyn.h>
+#include <rte_graph_worker_common.h>
+#include <rte_graph_feature_arc_worker.h>
+
extern int rte_node_logtype;
#define RTE_LOGTYPE_NODE rte_node_logtype
@@ -29,13 +32,25 @@ extern int rte_node_logtype;
*/
struct node_mbuf_priv1 {
union {
- /* IP4/IP6 rewrite */
+ /**
+ * IP4/IP6 rewrite
+ * only used to pass lookup data from
+ * ip4-lookup to ip4-rewrite
+ */
struct {
uint16_t nh;
uint16_t ttl;
uint32_t cksum;
};
-
+ /**
+ * Feature arc data
+ */
+ struct {
+ /** interface index */
+ uint16_t if_index;
+ /** feature that current mbuf holds */
+ rte_graph_feature_t current_feature;
+ };
uint64_t u;
};
};
diff --git a/lib/node/rte_node_ip4_api.h b/lib/node/rte_node_ip4_api.h
index 24f8ec843a..0de06f7fc7 100644
--- a/lib/node/rte_node_ip4_api.h
+++ b/lib/node/rte_node_ip4_api.h
@@ -23,6 +23,7 @@ extern "C" {
#include <rte_compat.h>
#include <rte_graph.h>
+#include <rte_graph_feature_arc_worker.h>
/**
* IP4 lookup next nodes.
@@ -67,6 +68,8 @@ struct rte_node_ip4_reassembly_cfg {
/**< Node identifier to configure. */
};
+#define RTE_IP4_OUTPUT_FEATURE_ARC_NAME "ipv4-output"
+
/**
* Add ipv4 route to lookup table.
*
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* RE: [RFC PATCH 1/3] graph: add feature arc support
2024-09-07 7:31 ` [RFC PATCH 1/3] graph: add feature arc support Nitin Saxena
@ 2024-09-11 4:41 ` Kiran Kumar Kokkilagadda
2024-10-10 4:42 ` Nitin Saxena
0 siblings, 1 reply; 56+ messages in thread
From: Kiran Kumar Kokkilagadda @ 2024-09-11 4:41 UTC (permalink / raw)
To: Nitin Saxena, Jerin Jacob, Nithin Kumar Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
> -----Original Message-----
> From: Nitin Saxena <nsaxena@marvell.com>
> Sent: Saturday, September 7, 2024 1:01 PM
> To: Jerin Jacob <jerinj@marvell.com>; Kiran Kumar Kokkilagadda
> <kirankumark@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>; Zhirun Yan <yanzhirun_163@163.com>
> Cc: dev@dpdk.org; Nitin Saxena <nsaxena16@gmail.com>
> Subject: [RFC PATCH 1/3] graph: add feature arc support
>
> add feature arc to allow dynamic steering of packets across graph nodes
> based on protocol features enabled on incoming or outgoing interface
>
> Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
> ---
> lib/graph/graph_feature_arc.c | 959 +++++++++++++++++++++++
> lib/graph/meson.build | 2 +
> lib/graph/rte_graph_feature_arc.h | 373 +++++++++
> lib/graph/rte_graph_feature_arc_worker.h | 548 +++++++++++++
> lib/graph/version.map | 17 +
> 5 files changed, 1899 insertions(+)
> create mode 100644 lib/graph/graph_feature_arc.c
> create mode 100644 lib/graph/rte_graph_feature_arc.h
> create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
>
> diff --git a/lib/graph/graph_feature_arc.c b/lib/graph/graph_feature_arc.c
> new file mode 100644
> index 0000000000..3b05bac137
> --- /dev/null
> +++ b/lib/graph/graph_feature_arc.c
> @@ -0,0 +1,959 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2024 Marvell International Ltd.
> + */
> +
> +#include "graph_private.h"
> +#include <rte_graph_feature_arc_worker.h>
> +#include <rte_malloc.h>
> +
> +#define __RTE_GRAPH_FEATURE_ARC_MAX 32
> +
> +#define ARC_PASSIVE_LIST(arc) (arc->active_feature_list ^ 0x1)
> +
> +#define rte_graph_uint_cast(x) ((unsigned int)x)
> +#define feat_dbg graph_err
> +
> +rte_graph_feature_arc_main_t *__feature_arc_main;
> +
> +/* Make sure fast path cache line is compact */
> +_Static_assert((offsetof(struct rte_graph_feature_arc, slow_path_variables)
> + - offsetof(struct rte_graph_feature_arc, fast_path_variables))
> + <= RTE_CACHE_LINE_SIZE);
> +
> +
> +static int
> +feature_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
> + struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
> +{
> + struct rte_graph_feature_node_list *finfo = NULL;
> + const char *name;
> +
> + if (!feat_name)
> + return -1;
> +
> + if (slot)
> + *slot = 0;
> +
> + STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> + RTE_VERIFY(finfo->feature_arc == arc);
> + name = rte_node_id_to_name(finfo->feature_node->id);
> + if (!strncmp(name, feat_name, RTE_GRAPH_NAMESIZE)) {
> + if (ffinfo)
> + *ffinfo = finfo;
> + return 0;
> + }
> + if (slot)
> + (*slot)++;
> + }
> + return -1;
> +}
> +
> +static int
> +feature_arc_node_info_lookup(struct rte_graph_feature_arc *arc, uint32_t
> feature_index,
> + struct rte_graph_feature_node_list **ppfinfo)
> +{
> + struct rte_graph_feature_node_list *finfo = NULL;
> + uint32_t index = 0;
> +
> + if (!ppfinfo)
> + return -1;
> +
> + *ppfinfo = NULL;
> + STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> + if (index == feature_index) {
> + if (finfo->node_index == feature_index)
> + return -1;
> + *ppfinfo = finfo;
> + }
> + index++;
> + }
> + if (feature_index && (index >= feature_index))
> + return -1;
> +
> + return 0;
> +}
> +
> +static void
> +prepare_feature_arc(struct rte_graph_feature_arc *arc)
> +{
> + struct rte_graph_feature_node_list *finfo = NULL;
> + uint32_t index = 0;
> +
> + STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
> + finfo->node_index = index;
> + index++;
> + }
> +}
> +
> +static int
> +feature_arc_lookup(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 = __feature_arc_main;
> + uint32_t iter;
> +
> + if (!__feature_arc_main)
> + return -1;
> +
> + for (iter = 0; iter < dm->max_feature_arcs; iter++) {
> + if (dm->feature_arcs[iter] ==
> RTE_GRAPH_FEATURE_ARC_INITIALIZER)
> + continue;
> +
> + if (arc == (rte_graph_feature_arc_get(dm-
> >feature_arcs[iter])))
> + return 0;
> + }
> + return -1;
> +}
> +
> +static int
> +get_existing_edge(const char *arc_name, struct rte_node_register
> *parent_node,
> + struct rte_node_register *child_node, rte_edge_t *_edge)
> +{
> + char **next_edges = NULL;
> + uint32_t count, i;
> +
> + RTE_SET_USED(arc_name);
> +
> + count = rte_node_edge_get(parent_node->id, NULL);
> + next_edges = malloc(count);
> +
> + if (!next_edges)
> + return -1;
> +
> + count = rte_node_edge_get(parent_node->id, next_edges);
> + for (i = 0; i < count; i++) {
> + if (strstr(child_node->name, next_edges[i])) {
> + feat_dbg("%s: Edge exists [%s[%u]: \"%s\"]",
> arc_name,
> + parent_node->name, i, child_node->name);
> + if (_edge)
> + *_edge = (rte_edge_t)i;
> +
> + free(next_edges);
> + return 0;
> + }
> + }
> + free(next_edges);
> +
> + return -1;
> +}
> +
> +static int
> +connect_graph_nodes(struct rte_node_register *parent_node, struct
> rte_node_register *child_node,
> + rte_edge_t *_edge, char *arc_name)
> +{
> + const char *next_node = NULL;
> + rte_edge_t edge;
> +
> + if (!get_existing_edge(arc_name, parent_node, child_node, &edge)) {
> + feat_dbg("%s: add_feature: Edge reused [%s[%u]: \"%s\"]",
> arc_name,
> + parent_node->name, edge, child_node->name);
> +
> + if (_edge)
> + *_edge = edge;
> +
> + return 0;
> + }
> +
> + /* Node to be added */
> + next_node = child_node->name;
> +
> + edge = rte_node_edge_update(parent_node->id,
> RTE_EDGE_ID_INVALID, &next_node, 1);
> +
> + if (edge == RTE_EDGE_ID_INVALID) {
> + graph_err("edge invalid");
> + return -1;
> + }
> + edge = rte_node_edge_count(parent_node->id) - 1;
> +
> + feat_dbg("%s: add_feature: edge added [%s[%u]: \"%s\"]", arc_name,
> parent_node->name, edge,
> + child_node->name);
> +
> + if (_edge)
> + *_edge = edge;
> +
> + return 0;
> +}
> +
> +static int
> +feature_arc_init(rte_graph_feature_arc_main_t **pfl, uint32_t
> max_feature_arcs)
> +{
> + rte_graph_feature_arc_main_t *pm = NULL;
> + uint32_t i;
> + size_t sz;
> +
> + if (!pfl)
> + return -1;
> +
> + sz = sizeof(rte_graph_feature_arc_main_t) +
> + (sizeof(pm->feature_arcs[0]) * max_feature_arcs);
> +
> + pm = malloc(sz);
> + if (!pm)
> + return -1;
> +
> + memset(pm, 0, sz);
> +
> + for (i = 0; i < max_feature_arcs; i++)
> + pm->feature_arcs[i] =
> RTE_GRAPH_FEATURE_ARC_INITIALIZER;
> +
> + pm->max_feature_arcs = max_feature_arcs;
> +
> + *pfl = pm;
> +
> + return 0;
> +}
> +
> +int
> +rte_graph_feature_arc_init(int max_feature_arcs)
> +{
> + if (!max_feature_arcs)
> + return -1;
> +
> + if (__feature_arc_main)
> + return -1;
> +
> + return feature_arc_init(&__feature_arc_main, max_feature_arcs);
> +}
> +
> +static void
> +feature_arc_list_reset(struct rte_graph_feature_arc *arc, uint32_t list_index)
> +{
> + rte_graph_feature_data_t *fdata = NULL;
> + rte_graph_feature_list_t *list = NULL;
> + struct rte_graph_feature *feat = NULL;
> + uint32_t i, j;
> +
> + list = arc->feature_list[list_index];
> + feat = arc->features[list_index];
> +
> + /*Initialize variables*/
> + memset(feat, 0, arc->feature_size);
> + memset(list, 0, arc->feature_list_size);
> +
> + /* Initialize feature and feature_data */
> + for (i = 0; i < arc->max_features; i++) {
> + feat = __rte_graph_feature_get(arc, i, list_index);
> + feat->this_feature_index = i;
> +
> + for (j = 0; j < arc->max_indexes; j++) {
> + fdata = rte_graph_feature_data_get(arc, feat, j);
> + fdata->next_enabled_feature =
> RTE_GRAPH_FEATURE_INVALID;
> + fdata->next_edge = UINT16_MAX;
> + fdata->user_data = UINT32_MAX;
> + }
> + }
> +
> + for (i = 0; i < arc->max_indexes; i++)
> + list->first_enabled_feature_by_index[i] =
> RTE_GRAPH_FEATURE_INVALID;
> +}
> +
> +static int
> +feature_arc_list_init(struct rte_graph_feature_arc *arc, const char
> *flist_name,
> + rte_graph_feature_list_t **pplist,
> + struct rte_graph_feature **ppfeature, uint32_t
> list_index)
> +{
> + char fname[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
> + size_t list_size, feat_size, fdata_size;
> + rte_graph_feature_list_t *list = NULL;
> + struct rte_graph_feature *feat = NULL;
> +
> + list_size = sizeof(list->first_enabled_feature_by_index[0]) * arc-
> >max_indexes;
> +
> + list = rte_malloc(flist_name, list_size, RTE_CACHE_LINE_SIZE);
> + if (!list)
> + return -ENOMEM;
> +
> + fdata_size = arc->max_indexes * sizeof(rte_graph_feature_data_t);
> +
> + /* Let one feature capture complete cache lines */
> + feat_size = RTE_ALIGN_CEIL(sizeof(struct rte_graph_feature) +
> fdata_size,
> + RTE_CACHE_LINE_SIZE);
> +
> + snprintf(fname, sizeof(fname), "%s-%s", arc->feature_arc_name,
> "feat");
> +
> + feat = rte_malloc(fname, feat_size * arc->max_features,
> RTE_CACHE_LINE_SIZE);
> + if (!feat) {
> + rte_free(list);
> + return -ENOMEM;
> + }
> + arc->feature_size = feat_size;
> + arc->feature_data_size = fdata_size;
> + arc->feature_list_size = list_size;
> +
> + /* Initialize list */
> + list->indexed_by_features = feat;
> + *pplist = list;
> + *ppfeature = feat;
> +
> + feature_arc_list_reset(arc, list_index);
> +
> + return 0;
> +}
> +
> +static void
> +feature_arc_list_destroy(rte_graph_feature_list_t *list)
> +{
> + rte_free(list->indexed_by_features);
Do you need to free individual rte_graph_feature here, that is allocated in arc_list_init?
> + rte_free(list);
> +}
> +
> +int
> +rte_graph_feature_arc_create(const char *feature_arc_name, int
> max_features, int max_indexes,
> + struct rte_node_register *start_node,
> rte_graph_feature_arc_t *_arc)
> +{
> + char name[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
> + rte_graph_feature_arc_main_t *dfm = NULL;
> + struct rte_graph_feature_arc *arc = NULL;
> + struct rte_graph_feature_data *gfd = NULL;
> + struct rte_graph_feature *df = NULL;
> + uint32_t iter, j, arc_index;
> + size_t sz;
> +
> + if (!_arc)
> + return -1;
> +
> + if (max_features < 2)
> + return -1;
> +
> + if (!start_node)
> + return -1;
> +
> + if (!feature_arc_name)
> + return -1;
> +
> + if (max_features > RTE_GRAPH_FEATURE_MAX_PER_ARC) {
> + graph_err("Invalid max features: %u", max_features);
> + return -1;
> + }
> +
> + /*
> + * Application hasn't called rte_graph_feature_arc_init(). Initialize with
> + * default values
> + */
> + if (!__feature_arc_main) {
> + if
> (rte_graph_feature_arc_init((int)__RTE_GRAPH_FEATURE_ARC_MAX) < 0) {
> + graph_err("rte_graph_feature_arc_init() failed");
> + return -1;
> + }
> + }
> +
> + dfm = __feature_arc_main;
> +
> + /* threshold check */
> + if (dfm->num_feature_arcs > (dfm->max_feature_arcs - 1)) {
> + graph_err("max threshold for num_feature_arcs: %d
> reached",
> + dfm->max_feature_arcs - 1);
> + return -1;
> + }
> + /* Find the free slot for feature arc */
> + for (iter = 0; iter < dfm->max_feature_arcs; iter++) {
> + if (dfm->feature_arcs[iter] ==
> RTE_GRAPH_FEATURE_ARC_INITIALIZER)
> + break;
> + }
> + arc_index = iter;
> +
> + if (arc_index >= dfm->max_feature_arcs) {
> + graph_err("No free slot found for num_feature_arc");
> + return -1;
> + }
> +
> + /* This should not happen */
> + RTE_VERIFY(dfm->feature_arcs[arc_index] ==
> RTE_GRAPH_FEATURE_ARC_INITIALIZER);
> +
> + /* size of feature arc + feature_bit_mask_by_index */
> + sz = sizeof(*arc) + (sizeof(uint64_t) * max_indexes);
> +
> + arc = rte_malloc(feature_arc_name, sz, RTE_CACHE_LINE_SIZE);
> +
> + if (!arc) {
> + graph_err("malloc failed for feature_arc_create()");
> + return -1;
> + }
> +
> + memset(arc, 0, sz);
> +
> + /* Initialize rte_graph port group fixed variables */
> + STAILQ_INIT(&arc->all_features);
> + strncpy(arc->feature_arc_name, feature_arc_name,
> RTE_GRAPH_FEATURE_ARC_NAMELEN - 1);
> + arc->feature_arc_main = (void *)dfm;
> + arc->start_node = start_node;
> + arc->max_features = max_features;
> + arc->max_indexes = max_indexes;
> +
> + snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist0");
> +
> + if (feature_arc_list_init(arc, name, &arc->feature_list[0], &arc-
> >features[0], 0) < 0) {
> + rte_free(arc);
> + graph_err("feature_arc_list_init(0) failed");
> + return -1;
> + }
> + snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist1");
> +
> + if (feature_arc_list_init(arc, name, &arc->feature_list[1], &arc-
> >features[1], 1) < 0) {
> + feature_arc_list_destroy(arc->feature_list[0]);
> + graph_err("feature_arc_list_init(1) failed");
> + return -1;
> + }
> +
> + for (iter = 0; iter < arc->max_features; iter++) {
> + df = rte_graph_feature_get(arc, iter);
> + for (j = 0; j < arc->max_indexes; j++) {
> + gfd = rte_graph_feature_data_get(arc, df, j);
> + gfd->next_enabled_feature =
> RTE_GRAPH_FEATURE_INVALID;
> + }
> + }
> + arc->feature_arc_index = arc_index;
> + dfm->feature_arcs[arc->feature_arc_index] =
> (rte_graph_feature_arc_t)arc;
> + dfm->num_feature_arcs++;
> +
> + if (_arc)
> + *_arc = (rte_graph_feature_arc_t)arc;
> +
> + return 0;
> +}
> +
> +int
> +rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct
> rte_node_register *feature_node,
> + const char *after_feature, const char *before_feature)
> +{
> + struct rte_graph_feature_node_list *after_finfo = NULL, *before_finfo
> = NULL;
> + struct rte_graph_feature_node_list *temp = NULL, *finfo = NULL;
> + struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> + uint32_t slot, add_flag;
> + rte_edge_t edge = -1;
> +
> + RTE_VERIFY(arc->feature_arc_main == __feature_arc_main);
> +
> + if (feature_arc_lookup(_arc)) {
> + graph_err("invalid feature arc: 0x%016" PRIx64,
> (uint64_t)_arc);
> + return -1;
> + }
> +
> + if (arc->runtime_enabled_features) {
> + graph_err("adding features after enabling any one of them is
> not supported");
> + return -1;
> + }
> +
> + if ((after_feature != NULL) && (before_feature != NULL) &&
> + (after_feature == before_feature)) {
> + graph_err("after_feature and before_feature are same
> '%s:%s]", after_feature,
> + before_feature);
> + return -1;
> + }
> +
> + if (!feature_node) {
> + graph_err("feature_node: %p invalid", feature_node);
> + return -1;
> + }
> +
> + arc = rte_graph_feature_arc_get(_arc);
> +
> + if (feature_node->id == RTE_NODE_ID_INVALID) {
> + graph_err("Invalid node: %s", feature_node->name);
> + return -1;
> + }
> +
> + if (!feature_lookup(arc, feature_node->name, &finfo, &slot)) {
> + graph_err("%s feature already added", feature_node->name);
> + return -1;
> + }
> +
> + if (slot >= RTE_GRAPH_FEATURE_MAX_PER_ARC) {
> + graph_err("Max slot %u reached for feature addition", slot);
> + return -1;
> + }
> +
> + if (strstr(feature_node->name, arc->start_node->name)) {
> + graph_err("Feature %s cannot point to itself: %s",
> feature_node->name,
> + arc->start_node->name);
> + return -1;
> + }
> +
> + if (connect_graph_nodes(arc->start_node, feature_node, &edge, arc-
> >feature_arc_name)) {
> + graph_err("unable to connect %s -> %s", arc->start_node-
> >name, feature_node->name);
> + return -1;
> + }
> +
> + finfo = malloc(sizeof(*finfo));
> + if (!finfo)
> + return -1;
> +
> + memset(finfo, 0, sizeof(*finfo));
> +
> + finfo->feature_arc = (void *)arc;
> + finfo->feature_node = feature_node;
> + finfo->edge_to_this_feature = edge;
> +
> + /* Check for before and after constraints */
> + if (before_feature) {
> + /* before_feature sanity */
> + if (feature_lookup(arc, before_feature, &before_finfo, NULL))
> + SET_ERR_JMP(EINVAL, finfo_free,
> + "Invalid before feature name: %s",
> before_feature);
> +
> + if (!before_finfo)
> + SET_ERR_JMP(EINVAL, finfo_free,
> + "before_feature %s does not exist",
> before_feature);
> +
> + /*
> + * Starting from 0 to before_feature, continue connecting
> edges
> + */
> + add_flag = 1;
> + STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
> + /*
> + * As soon as we see before_feature. stop adding
> edges
> + */
> + if (!strncmp(temp->feature_node->name,
> before_feature,
> + RTE_GRAPH_NAMESIZE))
> + if (!connect_graph_nodes(finfo-
> >feature_node, temp->feature_node,
> + &edge, arc-
> >feature_arc_name))
> + add_flag = 0;
> +
> + if (add_flag)
> + connect_graph_nodes(temp->feature_node,
> finfo->feature_node, NULL,
> + arc->feature_arc_name);
> + }
> + }
> +
> + if (after_feature) {
> + if (feature_lookup(arc, after_feature, &after_finfo, NULL))
> + SET_ERR_JMP(EINVAL, finfo_free,
> + "Invalid after feature_name %s",
> after_feature);
> +
> + if (!after_finfo)
> + SET_ERR_JMP(EINVAL, finfo_free,
> + "after_feature %s does not exist",
> after_feature);
> +
> + /* Starting from after_feature to end continue connecting
> edges */
> + add_flag = 0;
> + STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
> + /* We have already seen after_feature now */
> + if (add_flag)
> + /* Add all features as next node to current
> feature*/
> + connect_graph_nodes(finfo->feature_node,
> temp->feature_node, NULL,
> + arc->feature_arc_name);
> +
> + /* as soon as we see after_feature. start adding edges
> + * from next iteration
> + */
> + if (!strncmp(temp->feature_node->name,
> after_feature, RTE_GRAPH_NAMESIZE))
> + /* connect after_feature to this feature */
> + if (!connect_graph_nodes(temp-
> >feature_node, finfo->feature_node,
> + &edge, arc-
> >feature_arc_name))
> + add_flag = 1;
> + }
> +
> + /* add feature next to after_feature */
> + STAILQ_INSERT_AFTER(&arc->all_features, after_finfo, finfo,
> next_feature);
> + } else {
> + if (before_finfo) {
> + after_finfo = NULL;
> + STAILQ_FOREACH(temp, &arc->all_features,
> next_feature) {
> + if (before_finfo == temp) {
> + if (after_finfo)
> + STAILQ_INSERT_AFTER(&arc-
> >all_features, after_finfo,
> + finfo,
> next_feature);
> + else
> + STAILQ_INSERT_HEAD(&arc-
> >all_features, finfo,
> +
> next_feature);
> +
> + return 0;
> + }
> + after_finfo = temp;
> + }
> + } else {
> + STAILQ_INSERT_TAIL(&arc->all_features, finfo,
> next_feature);
> + }
> + }
> +
> + return 0;
> +
> +finfo_free:
> + free(finfo);
> +
> + return -1;
> +}
> +
> +int
> +rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char
> *feature_name,
> + rte_graph_feature_t *feat)
> +{
> + struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> + struct rte_graph_feature_node_list *finfo = NULL;
> + uint32_t slot;
> +
> + if (!feature_lookup(arc, feature_name, &finfo, &slot)) {
> + *feat = (rte_graph_feature_t) slot;
> + return 0;
> + }
> +
> + return -1;
> +}
> +
> +int
> +rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index,
> const char *feature_name,
> + int is_enable_disable)
> +{
> + struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> + struct rte_graph_feature_node_list *finfo = NULL;
> + struct rte_graph_feature *gf = NULL;
> + uint32_t slot;
> +
> + /* validate _arc */
> + if (arc->feature_arc_main != __feature_arc_main) {
> + graph_err("invalid feature arc: 0x%016" PRIx64,
> (uint64_t)_arc);
> + return -EINVAL;
> + }
> +
> + /* validate index */
> + if (index >= arc->max_indexes) {
> + graph_err("%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 (feature_lookup(arc, feature_name, &finfo, &slot)) {
> + graph_err("%s: No feature %s added", arc-
> >feature_arc_name, feature_name);
> + return -EINVAL;
> + }
> +
> + if (!finfo) {
> + graph_err("%s: No feature: %s found", arc-
> >feature_arc_name, feature_name);
> + return -EINVAL;
> + }
> +
> + /* slot should be in valid range */
> + if (slot >= arc->max_features) {
> + graph_err("%s/%s: Invalid free slot %u(max=%u) for feature",
> arc->feature_arc_name,
> + feature_name, slot, arc->max_features);
> + return -EINVAL;
> + }
> +
> + /* slot should be in range of 0 - 63 */
> + if (slot > (RTE_GRAPH_FEATURE_MAX_PER_ARC - 1)) {
> + graph_err("%s/%s: Invalid slot: %u", arc->feature_arc_name,
> + feature_name, slot);
> + return -EINVAL;
> + }
> +
> + if (finfo->node_index != slot) {
> + graph_err("%s/%s: feature lookup slot mismatch with finfo
> index: %u and lookup slot: %u",
> + arc->feature_arc_name, feature_name, finfo-
> >node_index, slot);
> + return -1;
> + }
> +
> + /* Get feature from active list */
> + gf = __rte_graph_feature_get(arc, slot, ARC_PASSIVE_LIST(arc));
> + if (gf->this_feature_index != slot) {
> + graph_err("%s: %s received feature_index: %u does not match
> with saved feature_index: %u",
> + arc->feature_arc_name, feature_name, slot, gf-
> >this_feature_index);
> + return -1;
> + }
> +
> + if (is_enable_disable && (arc->feature_bit_mask_by_index[index] &
> + RTE_BIT64(slot))) {
> + graph_err("%s: %s already enabled on index: %u",
> + arc->feature_arc_name, feature_name, index);
> + return -1;
> + }
> +
> + if (!is_enable_disable && !arc->runtime_enabled_features) {
> + graph_err("%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))) {
> + graph_err("%s: %s not enabled in bitmask for index: %u",
> + arc->feature_arc_name, feature_name, index);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static void
> +copy_fastpath_user_data(struct rte_graph_feature_arc *arc, uint16_t
> dest_list_index,
> + uint16_t src_list_index)
> +{
> + rte_graph_feature_data_t *sgfd = NULL, *dgfd = NULL;
> + struct rte_graph_feature *sgf = NULL, *dgf = NULL;
> + uint32_t i, j;
> +
> + for (i = 0; i < arc->max_features; i++) {
> + sgf = __rte_graph_feature_get(arc, i, src_list_index);
> + dgf = __rte_graph_feature_get(arc, i, dest_list_index);
> + for (j = 0; j < arc->max_indexes; j++) {
> + sgfd = rte_graph_feature_data_get(arc, sgf, j);
> + dgfd = rte_graph_feature_data_get(arc, dgf, j);
> + dgfd->user_data = sgfd->user_data;
> + }
> + }
> +}
> +
> +static void
> +refill_feature_fastpath_data(struct rte_graph_feature_arc *arc, uint16_t
> list_index)
> +{
> + struct rte_graph_feature_node_list *finfo = NULL, *prev_finfo = NULL;
> + struct rte_graph_feature_data *gfd = NULL, *prev_gfd = NULL;
> + struct rte_graph_feature *gf = NULL, *prev_gf = NULL;
> + rte_graph_feature_list_t *flist = NULL;
> + uint32_t fi, di, prev_fi;
> + uint64_t bitmask;
> + rte_edge_t edge;
> +
> + flist = arc->feature_list[list_index];
> +
> + for (di = 0; di < arc->max_indexes; di++) {
> + bitmask = arc->feature_bit_mask_by_index[di];
> + prev_fi = RTE_GRAPH_FEATURE_INVALID;
> + /* for each feature set for index, set fast path data */
> + while (rte_bsf64_safe(bitmask, &fi)) {
> + gf = __rte_graph_feature_get(arc, fi, list_index);
> + gfd = rte_graph_feature_data_get(arc, gf, di);
> + feature_arc_node_info_lookup(arc, fi, &finfo);
> +
> + /* If previous feature_index was valid in last loop */
> + if (prev_fi != RTE_GRAPH_FEATURE_INVALID) {
> + prev_gf = __rte_graph_feature_get(arc,
> prev_fi, list_index);
> + prev_gfd = rte_graph_feature_data_get(arc,
> prev_gf, di);
> + /*
> + * Get edge of previous feature node
> connecting to this feature node
> + */
> + feature_arc_node_info_lookup(arc, prev_fi,
> &prev_finfo);
> + if (!get_existing_edge(arc->feature_arc_name,
> + prev_finfo->feature_node,
> + finfo->feature_node,
> &edge)) {
> + feat_dbg("[%s/%s(%2u)/idx:%2u]:
> %s[%u] = %s",
> + arc->feature_arc_name,
> + prev_finfo->feature_node-
> >name, prev_fi, di,
> + prev_finfo->feature_node-
> >name,
> + edge, finfo->feature_node-
> >name);
> + /* Copy feature index for next
> iteration*/
> + gfd->next_edge = edge;
> + prev_fi = fi;
> + /*
> + * Fill current feature as next enabled
> + * feature to previous one
> + */
> + prev_gfd->next_enabled_feature = fi;
> + } else {
> + /* Should not fail */
> + RTE_VERIFY(0);
> + }
> + }
> + /* On first feature edge of the node to be added */
> + if (fi == rte_bsf64(arc-
> >feature_bit_mask_by_index[di])) {
> + if (!get_existing_edge(arc->feature_arc_name,
> arc->start_node,
> + finfo->feature_node,
> + &edge)) {
> + feat_dbg("[%s/%s/%2u/idx:%2u]: 1st
> feat %s[%u] = %s",
> + arc->feature_arc_name,
> + arc->start_node->name, fi, di,
> + arc->start_node->name,
> edge,
> + finfo->feature_node->name);
> + /* Copy feature index for next
> iteration*/
> + gfd->next_edge = edge;
> + prev_fi = fi;
> + /* Set first feature set array for
> index*/
> + flist-
> >first_enabled_feature_by_index[di] = fi;
> + } else {
> + /* Should not fail */
> + RTE_VERIFY(0);
> + }
> + }
> + /* Clear current feature index */
> + bitmask &= ~RTE_BIT64(fi);
> + }
> + }
> +}
> +
> +int
> +rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index,
> const
> + char *feature_name, int32_t user_data)
> +{
> + 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;
> + rte_graph_feature_rt_list_t passive_list;
> + struct rte_graph_feature *gf = NULL;
> + uint64_t fp_bitmask;
> + uint32_t slot;
> +
> + if (rte_graph_feature_validate(_arc, index, feature_name, 1))
> + return -1;
> +
> + /** This should not fail as validate() has passed */
> + if (feature_lookup(arc, feature_name, &finfo, &slot))
> + RTE_VERIFY(0);
> +
> + if (!arc->runtime_enabled_features)
> + prepare_feature_arc(arc);
> +
> + passive_list = ARC_PASSIVE_LIST(arc);
> +
> + gf = __rte_graph_feature_get(arc, slot, passive_list);
> + gfd = rte_graph_feature_data_get(arc, gf, index);
> +
> + feat_dbg("%s/%s: Enabling feature on list: %u for index: %u at feature
> slot %u",
> + arc->feature_arc_name, feature_name, passive_list, index,
> slot);
> +
> + /* Reset feature list */
> + feature_arc_list_reset(arc, passive_list);
> +
> + /* Copy user-data */
> + copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
> +
> + /* Set current user-data */
> + gfd->user_data = user_data;
> +
> + /* Set bitmask in control path bitmask */
> + rte_bit_relaxed_set64(rte_graph_uint_cast(slot), &arc-
> >feature_bit_mask_by_index[index]);
> + refill_feature_fastpath_data(arc, passive_list);
> +
> + /* Set fast path enable bitmask */
> + fp_bitmask = __atomic_load_n(&arc-
> >feature_enable_bitmask[passive_list], __ATOMIC_RELAXED);
> + fp_bitmask |= RTE_BIT64(slot);
> + __atomic_store(&arc->feature_enable_bitmask[passive_list],
> &fp_bitmask, __ATOMIC_RELAXED);
> +
> + /* Slow path updates */
> + arc->runtime_enabled_features++;
> +
> + /* Increase feature node info reference count */
> + finfo->ref_count++;
> +
> + /* Store release semantics for active_list update */
> + __atomic_store(&arc->active_feature_list, &passive_list,
> __ATOMIC_RELEASE);
> +
> + return 0;
> +}
> +
> +int
> +rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index,
> const char *feature_name)
> +{
> + struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
> + struct rte_graph_feature_data *gfd = NULL;
> + struct rte_graph_feature_node_list *finfo = NULL;
> + rte_graph_feature_rt_list_t passive_list;
> + struct rte_graph_feature *gf = NULL;
> + uint32_t slot;
> +
> + if (rte_graph_feature_validate(_arc, index, feature_name, 0))
> + return -1;
> +
> + if (feature_lookup(arc, feature_name, &finfo, &slot))
> + return -1;
> +
> + passive_list = ARC_PASSIVE_LIST(arc);
> +
> + gf = __rte_graph_feature_get(arc, slot, passive_list);
> + gfd = rte_graph_feature_data_get(arc, gf, index);
> +
> + feat_dbg("%s/%s: Disabling feature for index: %u at feature slot %u",
> arc->feature_arc_name,
> + feature_name, index, slot);
> +
> + rte_bit_relaxed_clear64(rte_graph_uint_cast(slot), &arc-
> >feature_bit_mask_by_index[index]);
> +
> + /* Set fast path enable bitmask */
> + arc->feature_enable_bitmask[passive_list] &= ~(RTE_BIT64(slot));
> +
> + /* Reset feature list */
> + feature_arc_list_reset(arc, passive_list);
> +
> + /* Copy user-data */
> + copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
> +
> + /* Reset current user-data */
> + gfd->user_data = ~0;
> +
> + refill_feature_fastpath_data(arc, passive_list);
> +
> + finfo->ref_count--;
> + arc->runtime_enabled_features--;
> +
> + /* Store release semantics for active_list update */
> + __atomic_store(&arc->active_feature_list, &passive_list,
> __ATOMIC_RELEASE);
> +
> + return 0;
> +}
> +
> +int
> +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 = __feature_arc_main;
> + struct rte_graph_feature_node_list *node_info = NULL;
> +
> + while (!STAILQ_EMPTY(&arc->all_features)) {
> + node_info = STAILQ_FIRST(&arc->all_features);
> + STAILQ_REMOVE_HEAD(&arc->all_features, next_feature);
> + free(node_info);
> + }
> + feature_arc_list_destroy(arc->feature_list[0]);
> + feature_arc_list_destroy(arc->feature_list[1]);
> + rte_free(arc->features[0]);
> + rte_free(arc->features[1]);
> +
> + dm->feature_arcs[arc->feature_arc_index] =
> RTE_GRAPH_FEATURE_ARC_INITIALIZER;
> +
> + rte_free(arc);
> + return 0;
> +}
> +
> +int
> +rte_graph_feature_arc_cleanup(void)
> +{
> + rte_graph_feature_arc_main_t *dm = __feature_arc_main;
> + uint32_t iter;
> +
> + if (!__feature_arc_main)
> + return -1;
> +
> + for (iter = 0; iter < dm->max_feature_arcs; iter++) {
> + if (dm->feature_arcs[iter] ==
> RTE_GRAPH_FEATURE_ARC_INITIALIZER)
> + continue;
> +
> + rte_graph_feature_arc_destroy((rte_graph_feature_arc_t)dm-
> >feature_arcs[iter]);
> + }
> + free(dm);
> +
> + __feature_arc_main = NULL;
> +
> + return 0;
> +}
> +
> +int
> +rte_graph_feature_arc_lookup_by_name(const char *arc_name,
> rte_graph_feature_arc_t *_arc)
> +{
> + rte_graph_feature_arc_main_t *dm = __feature_arc_main;
> + struct rte_graph_feature_arc *arc = NULL;
> + uint32_t iter;
> +
> + if (!__feature_arc_main)
> + return -1;
> +
> + for (iter = 0; iter < dm->max_feature_arcs; iter++) {
> + if (dm->feature_arcs[iter] ==
> RTE_GRAPH_FEATURE_ARC_INITIALIZER)
> + continue;
> +
> + arc = rte_graph_feature_arc_get(dm->feature_arcs[iter]);
> +
> + if (strstr(arc_name, arc->feature_arc_name)) {
> + if (_arc)
> + *_arc = (rte_graph_feature_arc_t)arc;
> + return 0;
> + }
> + }
> +
> + return -1;
> +}
> +
> +int
> +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);
> +
> + return arc->runtime_enabled_features;
> +}
> +
> +
> diff --git a/lib/graph/meson.build b/lib/graph/meson.build
> index 0cb15442ab..d916176fb7 100644
> --- a/lib/graph/meson.build
> +++ b/lib/graph/meson.build
> @@ -14,11 +14,13 @@ sources = files(
> 'graph_debug.c',
> 'graph_stats.c',
> 'graph_populate.c',
> + 'graph_feature_arc.c',
> 'graph_pcap.c',
> 'rte_graph_worker.c',
> 'rte_graph_model_mcore_dispatch.c',
> )
> headers = files('rte_graph.h', 'rte_graph_worker.h')
> +headers += files('rte_graph_feature_arc.h', 'rte_graph_feature_arc_worker.h')
> indirect_headers += files(
> 'rte_graph_model_mcore_dispatch.h',
> 'rte_graph_model_rtc.h',
> diff --git a/lib/graph/rte_graph_feature_arc.h
> b/lib/graph/rte_graph_feature_arc.h
> new file mode 100644
> index 0000000000..e3bf4eb73d
> --- /dev/null
> +++ b/lib/graph/rte_graph_feature_arc.h
> @@ -0,0 +1,373 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2024 Marvell International Ltd.
> + */
> +
> +#ifndef _RTE_GRAPH_FEATURE_ARC_H_
> +#define _RTE_GRAPH_FEATURE_ARC_H_
> +
> +#include <assert.h>
> +#include <errno.h>
> +#include <signal.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <rte_common.h>
> +#include <rte_compat.h>
> +#include <rte_debug.h>
> +#include <rte_graph.h>
> +#include <rte_graph_worker.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/**
> + * @file
> + *
> + * rte_graph_feature_arc.h
> + *
> + * Define APIs and structures/variables with respect to feature arc
> + *
> + * - Feature arc(s)
> + * - Feature(s)
> + *
> + * A feature arc represents an ordered list of features/protocol-nodes at a
> + * given networking layer. Feature arc provides a high level abstraction to
> + * connect various *rte_graph* nodes, designated as *feature nodes*, and
> + * allowing steering of packets across these feature nodes fast path
> processing
> + * in a generic manner. In a typical network stack, often a protocol or feature
> + * must be first enabled on a given interface, before any packet is steered
> + * towards it for feature processing. For eg: incoming IPv4 packets are sent to
> + * routing sub-system only after a valid IPv4 address is assigned to the
> + * received interface. In other words, often packets needs to be steered across
> + * features not based on the packet content but based on whether a feature is
> + * enable or disable on a given incoming/outgoing interface. Feature arc
> + * provides mechanism to enable/disable feature(s) on each interface at
> runtime
> + * and allow seamless packet steering across runtime enabled feature nodes
> in
> + * fast path.
> + *
> + * Feature arc also provides a way to steer packets from standard nodes to
> + * custom/user-defined *feature nodes* without any change in standard
> node's
> + * fast path functions
> + *
> + * On a given interface multiple feature(s) might be enabled in a particular
> + * feature arc. For instance, both "ipv4-output" and "IPsec policy output"
> + * features may be enabled on "eth0" interface in "L3-output" feature arc.
> + * Similarly, "ipv6-output" and "ipsec-output" may be enabled on "eth1"
> + * interface in same "L3-output" feature arc.
> + *
> + * When multiple features are present in a given feature arc, its imperative
> + * to allow each feature processing in a particular sequential order. For
> + * instance, in "L3-input" feature arc it may be required to run "IPsec
> + * input" feature first, for packet decryption, before "ip-lookup". So a
> + * sequential order must be maintained among features present in a feature
> arc.
> + *
> + * Features are enabled/disabled multiple times at runtime to some or all
> + * available interfaces present in the system. Features can be
> enabled/disabled
> + * even after @b rte_graph_create() is called. Enable/disabling features on
> one
> + * interface is independent of other interface.
> + *
> + * A given feature might consume packet (if it's configured to consume) or
> may
> + * forward it to next enabled feature. For instance, "IPsec input" feature may
> + * consume/drop all packets with "Protect" policy action while all packets with
> + * policy action as "Bypass" may be forwarded to next enabled feature (with
> in
> + * same feature arc)
> + *
> + * This library facilitates rte graph based applications to steer packets in
> + * fast path to different feature nodes with-in a feature arc and support all
> + * functionalities described above
> + *
> + * In order to use feature-arc APIs, applications needs to do following in
> + * control path:
> + * - Initialize feature arc library via rte_graph_feature_arc_init()
> + * - Create feature arc via rte_graph_feature_arc_create()
> + * - *Before calling rte_graph_create()*, features must be added to feature-
> arc
> + * via rte_graph_feature_add(). rte_graph_feature_add() allows adding
> + * features in a sequential order with "runs_after" and "runs_before"
> + * constraints.
> + * - Post rte_graph_create(), features can be enabled/disabled at runtime on
> + * any interface via rte_graph_feature_enable()/rte_graph_feature_disable()
> + * - Feature arc can be destroyed via rte_graph_feature_arc_destroy()
> + *
> + * In fast path, APIs are provided to steer packets towards feature path from
> + * - start_node (provided as an argument to rte_graph_feature_arc_create())
> + * - feature nodes (which are added via rte_graph_feature_add())
> + *
> + * For typical steering of packets across feature nodes, application required
> + * to know "rte_edges" which are saved in feature data object. Feature data
> + * object is unique for every interface per feature with in a feature arc.
> + *
> + * When steering packets from start_node to feature node:
> + * - rte_graph_feature_arc_first_feature_get() provides first enabled feature.
> + * - Next rte_edge from start_node to first enabled feature can be obtained
> via
> + * rte_graph_feature_arc_feature_set()
> + *
> + * rte_mbuf can carry [current feature, index] from start_node of an arc to
> other
> + * feature nodes
> + *
> + * In feature node, application can get 32-bit user_data
> + * via_rte_graph_feature_user_data_get() which is provided in
> + * rte_graph_feature_enable(). User data can hold feature specific cookie like
> + * IPsec policy database index (if more than one are supported)
> + *
> + * If feature node is not consuming packet, next enabled feature and next
> + * rte_edge can be obtained via rte_graph_feature_arc_next_feature_get()
> + *
> + * It is application responsibility to ensure that at-least *last feature*(or sink
> + * feature) must be enabled from where packet can exit feature-arc path, if
> + * *NO* intermediate feature is consuming the packet and it has reached till
> + * the end of feature arc path
> + *
> + * Synchronization among cores
> + * ---------------------------
> + * Subsequent calls to rte_graph_feature_enable() is allowed while worker
> cores
> + * are processing in rte_graph_walk() loop. However, for
> + * rte_graph_feature_disable() application must use RCU based
> synchronization
> + */
> +
> +/**< Initializer value for rte_graph_feature_arc_t */
> +#define RTE_GRAPH_FEATURE_ARC_INITIALIZER
> ((rte_graph_feature_arc_t)UINT64_MAX)
> +
> +/** Max number of features supported in a given feature arc */
> +#define RTE_GRAPH_FEATURE_MAX_PER_ARC 64
> +
> +/** Length of feature arc name */
> +#define RTE_GRAPH_FEATURE_ARC_NAMELEN RTE_NODE_NAMESIZE
> +
> +/** @internal */
> +#define rte_graph_feature_cast(x) ((rte_graph_feature_t)x)
> +
> +/**< Initializer value for rte_graph_feature_arc_t */
> +#define RTE_GRAPH_FEATURE_INVALID
> rte_graph_feature_cast(UINT8_MAX)
> +
> +/** rte_graph feature arc object */
> +typedef uint64_t rte_graph_feature_arc_t;
> +
> +/** rte_graph feature object */
> +typedef uint8_t rte_graph_feature_t;
> +
> +/** runtime active feature list index with in feature arc*/
> +typedef uint8_t rte_graph_feature_rt_list_t;
> +
> +/** per feature arc monotonically increasing counter to synchronize fast path
> APIs */
> +typedef uint16_t rte_graph_feature_counter_t;
> +
> +/**
> + * Initialize feature arc subsystem
> + *
> + * @param max_feature_arcs
> + * Maximum number of feature arcs required to be supported
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_init(int max_feature_arcs);
> +
> +/**
> + * Create a feature arc
> + *
> + * @param feature_arc_name
> + * Feature arc name with max length of @ref
> RTE_GRAPH_FEATURE_ARC_NAMELEN
> + * @param max_features
> + * Maximum number of features to be supported in this feature arc
> + * @param max_indexes
> + * Maximum number of interfaces/ports/indexes to be supported
> + * @param start_node
> + * Base node where this feature arc's features are checked in fast path
> + * @param[out] _arc
> + * Feature arc object
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_create(const char *feature_arc_name, int
> max_features, int max_indexes,
> + struct rte_node_register *start_node,
> + rte_graph_feature_arc_t *_arc);
> +
> +/**
> + * Get feature arc object with name
> + *
> + * @param arc_name
> + * Feature arc name provided to successful @ref
> rte_graph_feature_arc_create
> + * @param[out] _arc
> + * Feature arc object returned
> + *
> + * @return
> + * 0: Success
> + * <0: Failure.
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_lookup_by_name(const char *arc_name,
> rte_graph_feature_arc_t *_arc);
> +
> +/**
> + * Add a feature to already created feature arc. For instance
> + *
> + * 1. Add first feature node: "ipv4-input" to input arc
> + * rte_graph_feature_add(ipv4_input_arc, "ipv4-input", NULL, NULL);
> + *
> + * 2. Add "ipsec-input" feature node after "ipv4-input" node
> + * rte_graph_feature_add(ipv4_input_arc, "ipsec-input", "ipv4-input",
> NULL);
> + *
> + * 3. Add "ipv4-pre-classify-input" node before "ipv4-input" node
> + * rte_graph_feature_add(ipv4_input_arc, "ipv4-pre-classify-input"", NULL,
> "ipv4-input");
> + *
> + * 4. Add "acl-classify-input" node after ipv4-input but before ipsec-input
> + * rte_graph_feature_add(ipv4_input_arc, "acl-classify-input", "ipv4-input",
> "ipsec-input");
> + *
> + * @param _arc
> + * Feature arc handle returned from @ref rte_graph_feature_arc_create()
> + * @param feature_node
> + * Graph node representing feature. On success, feature_node is next_node
> of
> + * feature_arc->start_node
> + * @param runs_after
> + * Add this feature_node after already added "runs_after". Creates
> + * start_node -> runs_after -> this_feature sequence
> + * @param runs_before
> + * Add this feature_node before already added "runs_before". Creates
> + * start_node -> this_feature -> runs_before sequence
> + *
> + * <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>
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct
> rte_node_register *feature_node,
> + const char *runs_after, const char *runs_before);
> +
> +/**
> + * 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 user_data
> + * Application specific data which is retrieved in fast path
> + *
> + * @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,
> + int32_t user_data);
> +
> +/**
> + * Validate whether subsequent enable/disable feature would succeed or not.
> + * API is 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 is_enable_disable
> + * If 1, validate whether subsequent @ref rte_graph_feature_enable would
> pass or not
> + * If 0, validate whether subsequent @ref rte_graph_feature_disable would
> pass or not
> + *
> + * @return
> + * 0: Subsequent enable/disable API would pass
> + * <0: Subsequent enable/disable API would not pass
> + */
> +__rte_experimental
> +int rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index,
> + const char *feature_name, int is_enable_disable);
> +
> +/**
> + * 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
> + *
> + * @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);
> +
> +/**
> + * 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
> + * @param feature_name
> + * Feature name provided to @ref rte_graph_feature_add
> + * @param[out] feature
> + * Feature object
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char
> *feature_name,
> + rte_graph_feature_t *feature);
> +
> +/**
> + * 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
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_destroy(rte_graph_feature_arc_t _arc);
> +
> +/**
> + * Cleanup all feature arcs
> + *
> + * @return
> + * 0: Success
> + * <0: Failure
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_cleanup(void);
> +
> +/**
> + * Slow path API to know how many features are currently enabled within a
> featur-arc
> + *
> + * @param _arc
> + * Feature arc object
> + *
> + * @return: Number of enabled features
> + */
> +__rte_experimental
> +int rte_graph_feature_arc_num_enabled_features(rte_graph_feature_arc_t
> _arc);
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif
> diff --git a/lib/graph/rte_graph_feature_arc_worker.h
> b/lib/graph/rte_graph_feature_arc_worker.h
> new file mode 100644
> index 0000000000..6019d74853
> --- /dev/null
> +++ b/lib/graph/rte_graph_feature_arc_worker.h
> @@ -0,0 +1,548 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(C) 2024 Marvell International Ltd.
> + */
> +
> +#ifndef _RTE_GRAPH_FEATURE_ARC_WORKER_H_
> +#define _RTE_GRAPH_FEATURE_ARC_WORKER_H_
> +
> +#include <stddef.h>
> +#include <rte_graph_feature_arc.h>
> +#include <rte_bitops.h>
> +
> +/**
> + * @file
> + *
> + * rte_graph_feature_arc_worker.h
> + *
> + * Defines fast path structure
> + */
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/** @internal
> + *
> + * Slow path feature node info list
> + */
> +struct rte_graph_feature_node_list {
> + /** Next feature */
> + STAILQ_ENTRY(rte_graph_feature_node_list) next_feature;
> +
> + /** node representing feature */
> + struct rte_node_register *feature_node;
> +
> + /** How many indexes/interfaces using this feature */
> + int32_t ref_count;
> +
> + /* node_index in list (after feature_enable())*/
> + uint32_t node_index;
> +
> + /** Back pointer to feature arc */
> + void *feature_arc;
> +
> + /** rte_edge_t to this feature node from feature_arc->start_node */
> + rte_edge_t edge_to_this_feature;
> +};
> +
> +/**
> + * Fast path holding rte_edge_t and next enabled feature for an feature
> + */
> +typedef struct __rte_packed rte_graph_feature_data {
> + /* next node to which current mbuf should go*/
> + rte_edge_t next_edge;
> +
> + /* next enabled feature on this arc for current index */
> + union {
> + uint16_t reserved;
> + struct {
> + rte_graph_feature_t next_enabled_feature;
> + };
> + };
> +
> + /* user_data */
> + int32_t user_data;
> +} rte_graph_feature_data_t;
> +
> +/**
> + * Fast path feature structure. Holds re_graph_feature_data_t per index
> + */
> +struct __rte_cache_aligned rte_graph_feature {
> + uint16_t this_feature_index;
> +
> + /* Array of size arc->feature_data_size
> + * [data-index-0][data-index-1]...
> + * Each index of size: sizeof(rte_graph_feature_data_t)
> + */
> + uint8_t feature_data_by_index[];
> +};
> +
> +/**
> + * fast path cache aligned feature list holding all features
> + * There are two feature lists: active, passive
> + *
> + * Fast APIs works on active list while control plane updates passive list
> + * A atomic update to arc->active_feature_list is done to switch between
> active
> + * and passive
> + */
> +typedef struct __rte_cache_aligned rte_graph_feature_list {
> + /**
> + * fast path array holding per_feature data.
> + * Duplicate entry as feature-arc also hold this pointer
> + * arc->features[]
> + *
> + *<-------------feature-0 ---------><CEIL><---------feature-1 --------------
> >...
> + *[index-0][index-1]...[max_index-1] [index-0][index-1]
> ...[max_index-1]...
> + */
> + struct rte_graph_feature *indexed_by_features;
> + /*
> + * fast path array holding first enabled feature per index
> + * (Required in start_node. In non start_node, mbuf can hold next
> enabled
> + * feature)
> + */
> + rte_graph_feature_t first_enabled_feature_by_index[];
> +} rte_graph_feature_list_t;
> +
> +/**
> + * rte_graph feature arc object
> + *
> + * A feature-arc can only hold RTE_GRAPH_FEATURE_MAX_PER_ARC features
> but no
> + * limit to interface index
> + *
> + * Representing a feature arc holding all features which are enabled/disabled
> + * on any interfaces
> + */
> +struct __rte_cache_aligned rte_graph_feature_arc {
> + /* First 64B is fast path variables */
> + RTE_MARKER fast_path_variables;
> +
> + /** runtime active feature list */
> + rte_graph_feature_rt_list_t active_feature_list;
> +
> + /* Actual Size of feature_list0 */
> + uint16_t feature_list_size;
> +
> + /**
> + * Size each feature in fastpath.
> + * sizeof(arc->active_list->indexed_by_feature[0])
> + */
> + uint16_t feature_size;
> +
> + /* Size of arc->max_index * sizeof(rte_graph_feature_data_t) */
> + uint16_t feature_data_size;
> +
> + /**
> + * Fast path bitmask indicating if a feature is enabled or not Number
> + * of bits: RTE_GRAPH_FEATURE_MAX_PER_ARC
> + */
> + uint64_t feature_enable_bitmask[2];
> + rte_graph_feature_list_t *feature_list[2];
> + struct rte_graph_feature *features[2];
> +
> + /** index in feature_arc_main */
> + uint16_t feature_arc_index;
> +
> + uint16_t reserved[3];
> +
> + /** Slow path variables follows*/
> + RTE_MARKER slow_path_variables;
> +
> + /** feature arc name */
> + char feature_arc_name[RTE_GRAPH_FEATURE_ARC_NAMELEN];
> +
> + /** All feature lists */
> + STAILQ_HEAD(, rte_graph_feature_node_list) all_features;
> +
> + uint32_t runtime_enabled_features;
> +
> + /** Back pointer to feature_arc_main */
> + void *feature_arc_main;
> +
> + /* start_node */
> + struct rte_node_register *start_node;
> +
> + /* maximum number of features supported by this arc */
> + uint32_t max_features;
> +
> + /* maximum number of index supported by this arc */
> + uint32_t max_indexes;
> +
> + /* Slow path bit mask per feature per index */
> + uint64_t feature_bit_mask_by_index[];
> +};
> +
> +/** Feature arc main */
> +typedef struct feature_arc_main {
> + /** number of feature arcs created by application */
> + uint32_t num_feature_arcs;
> +
> + /** max features arcs allowed */
> + uint32_t max_feature_arcs;
> +
> + /** feature arcs */
> + rte_graph_feature_arc_t feature_arcs[];
> +} rte_graph_feature_arc_main_t;
> +
> +/** @internal Get feature arc pointer from object */
> +#define rte_graph_feature_arc_get(arc) ((struct rte_graph_feature_arc *)arc)
> +
> +extern rte_graph_feature_arc_main_t *__feature_arc_main;
> +
> +/**
> + * API to know if feature is valid or not
> + */
> +
> +static __rte_always_inline int
> +rte_graph_feature_is_valid(rte_graph_feature_t feature)
> +{
> + return (feature != RTE_GRAPH_FEATURE_INVALID);
> +}
> +
> +/**
> + * Get rte_graph_feature object with no checks
> + *
> + * @param arc
> + * Feature arc pointer
> + * @param feature
> + * Feature index
> + * @param feature_list
> + * active feature list retrieved from
> rte_graph_feature_arc_has_any_feature()
> + * or rte_graph_feature_arc_has_feature()
> + *
> + * @return
> + * Internal feature object.
> + */
> +static __rte_always_inline struct rte_graph_feature *
> +__rte_graph_feature_get(struct rte_graph_feature_arc *arc,
> rte_graph_feature_t feature,
> + const rte_graph_feature_rt_list_t feature_list)
> +{
> + return ((struct rte_graph_feature *)((uint8_t *)(arc-
> >features[feature_list] +
> + (feature * arc->feature_size))));
> +}
> +
> +/**
> + * Get rte_graph_feature object for a given interface/index from feature arc
> + *
> + * @param arc
> + * Feature arc pointer
> + * @param feature
> + * Feature index
> + *
> + * @return
> + * Internal feature object.
> + */
> +static __rte_always_inline struct rte_graph_feature *
> +rte_graph_feature_get(struct rte_graph_feature_arc *arc,
> rte_graph_feature_t feature)
> +{
> + RTE_VERIFY(feature < arc->max_features);
> +
> + if (likely(rte_graph_feature_is_valid(feature)))
> + return __rte_graph_feature_get(arc, feature, arc-
> >active_feature_list);
> +
> + return NULL;
> +}
> +
> +static __rte_always_inline rte_graph_feature_data_t *
> +__rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct
> rte_graph_feature *feature,
> + uint8_t index)
> +{
> + RTE_SET_USED(arc);
> + return ((rte_graph_feature_data_t *)(feature->feature_data_by_index
> +
> + (index *
> sizeof(rte_graph_feature_data_t))));
> +}
> +
> +/**
> + * Get rte_graph feature data object for a index in feature
> + *
> + * @param arc
> + * feature arc
> + * @param feature
> + * Pointer to feature object
> + * @param index
> + * Index of feature maintained in slow path linked list
> + *
> + * @return
> + * Valid feature data
> + */
> +static __rte_always_inline rte_graph_feature_data_t *
> +rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct
> rte_graph_feature *feature,
> + uint8_t index)
> +{
> + if (likely(index < arc->max_indexes))
> + return __rte_graph_feature_data_get(arc, feature, index);
> +
> + RTE_VERIFY(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
> + * @param[out] plist
> + * Pointer to runtime active feature list which needs to be provided to other
> + * fast path APIs
> + *
> + * @return
> + * 0: If no feature enabled
> + * Non-Zero: Bitmask of features enabled. plist is valid
> + *
> + */
> +static __rte_always_inline uint64_t
> +rte_graph_feature_arc_has_any_feature(struct rte_graph_feature_arc *arc,
> + rte_graph_feature_rt_list_t *plist)
> +{
> + *plist = __atomic_load_n(&arc->active_feature_list,
> __ATOMIC_RELAXED);
> +
> + return (__atomic_load_n(arc->feature_enable_bitmask +
> (uint8_t)*plist,
> + __ATOMIC_RELAXED));
> +}
> +
> +/**
> + * Fast path API to check if provided feature is enabled on any interface/index
> + * or not
> + *
> + * @param arc
> + * Feature arc object
> + * @param feature
> + * Input rte_graph_feature_t that needs to be checked
> + * @param[out] plist
> + * Returns active list to caller which needs to be provided to other fast path
> + * APIs
> + *
> + * @return
> + * 1: If feature is enabled in arc
> + * 0: If feature is not enabled in arc
> + */
> +static __rte_always_inline int
> +rte_graph_feature_arc_has_feature(struct rte_graph_feature_arc *arc,
> + rte_graph_feature_t feature,
> + rte_graph_feature_rt_list_t *plist)
> +{
> + uint64_t bitmask = RTE_BIT64(feature);
> +
> + *plist = __atomic_load_n(&arc->active_feature_list,
> __ATOMIC_RELAXED);
> +
> + return (bitmask & __atomic_load_n(arc->feature_enable_bitmask +
> (uint8_t)*plist,
> + __ATOMIC_RELAXED));
> +}
> +
> +/**
> + * Prefetch feature arc fast path cache line
> + *
> + * @param arc
> + * RTE_GRAPH feature arc object
> + */
> +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 related fast path cache line
> + *
> + * @param arc
> + * RTE_GRAPH feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> rte_graph_feature_arc_has_any_feature();
> + * @param feature
> + * Pointer to feature object
> + */
> +static __rte_always_inline void
> +rte_graph_feature_arc_feature_prefetch(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + rte_graph_feature_t feature)
> +{
> + /* feature cache line */
> + if (likely(rte_graph_feature_is_valid(feature)))
> + rte_prefetch0((void *)__rte_graph_feature_get(arc, feature,
> list));
> +}
> +
> +/**
> + * Prefetch feature data upfront. Perform sanity
> + *
> + * @param _arc
> + * RTE_GRAPH feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> rte_graph_feature_arc_has_any_feature();
> + * @param feature
> + * Pointer to feature object returned from @ref
> + * rte_graph_feature_arc_first_feature_get()
> + * @param index
> + * Interface/index
> + */
> +static __rte_always_inline void
> +rte_graph_feature_arc_data_prefetch(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + rte_graph_feature_t feature, uint32_t index)
> +{
> + if (likely(rte_graph_feature_is_valid(feature)))
> + rte_prefetch0((void *)((uint8_t *)arc->features[list] +
> + offsetof(struct rte_graph_feature,
> feature_data_by_index) +
> + (index * sizeof(rte_graph_feature_data_t))));
> +}
> +
> +/**
> + * Fast path API to get first enabled feature on interface index
> + * Typically required in arc->start_node so that from returned feature,
> + * feature-data can be retrieved to steer packets
> + *
> + * @param arc
> + * Feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> + * rte_graph_feature_arc_has_any_feature() or
> + * rte_graph_feature_arc_has_feature()
> + * @param index
> + * Interface Index
> + * @param[out] feature
> + * Pointer to rte_graph_feature_t.
> + *
> + * @return
> + * 0. Success. feature field is valid
> + * 1. Failure. feature field is invalid
> + *
> + */
> +static __rte_always_inline int
> +rte_graph_feature_arc_first_feature_get(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + uint32_t index,
> + rte_graph_feature_t *feature)
> +{
> + struct rte_graph_feature_list *feature_list = arc->feature_list[list];
> +
> + *feature = feature_list->first_enabled_feature_by_index[index];
> +
> + return rte_graph_feature_is_valid(*feature);
> +}
> +
> +/**
> + * Fast path API to get next enabled feature on interface index with provided
> + * input feature
> + *
> + * @param arc
> + * Feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> + * rte_graph_feature_arc_has_any_feature() or
> + * @param index
> + * Interface Index
> + * @param[in][out] feature
> + * Pointer to rte_graph_feature_t. Input feature set to next enabled feature
> + * after success return
> + * @param[out] next_edge
> + * Edge from current feature to next feature. Valid only if next feature is
> valid
> + *
> + * @return
> + * 0. Success. next enabled feature is valid.
> + * 1. Failure. next enabled feature is invalid
> + */
> +static __rte_always_inline int
> +rte_graph_feature_arc_next_feature_get(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + uint32_t index,
> + rte_graph_feature_t *feature,
> + rte_edge_t *next_edge)
> +{
> + rte_graph_feature_data_t *feature_data = NULL;
> + struct rte_graph_feature *f = NULL;
> +
> + if (likely(rte_graph_feature_is_valid(*feature))) {
> + f = __rte_graph_feature_get(arc, *feature, list);
> + feature_data = rte_graph_feature_data_get(arc, f, index);
> + *feature = feature_data->next_enabled_feature;
> + *next_edge = feature_data->next_edge;
> + return (*feature == RTE_GRAPH_FEATURE_INVALID);
> + }
> +
> + return 1;
> +}
> +
> +/**
> + * Set fields with respect to first enabled feature in an arc and return edge
> + * Typically returned feature and interface index must be saved in rte_mbuf
> + * structure to pass this information to next feature node
> + *
> + * @param arc
> + * Feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> rte_graph_feature_arc_has_any_feature();
> + * @param index
> + * Index (of interface)
> + * @param[out] gf
> + * Pointer to rte_graph_feature_t. Valid if API returns Success
> + * @param[out] edge
> + * Edge to steer packet from arc->start_node to first enabled feature. Valid
> + * only if API returns Success
> + *
> + * @return
> + * 0: If valid feature is set by API
> + * 1: If valid feature is NOT set by API
> + */
> +static __rte_always_inline rte_graph_feature_t
> +rte_graph_feature_arc_feature_set(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + uint32_t index,
> + rte_graph_feature_t *gf,
> + rte_edge_t *edge)
> +{
> + struct rte_graph_feature_list *feature_list = arc->feature_list[list];
> + struct rte_graph_feature_data *feature_data = NULL;
> + struct rte_graph_feature *feature = NULL;
> + rte_graph_feature_t f;
> +
> + /* reset */
> + *gf = RTE_GRAPH_FEATURE_INVALID;
> + f = feature_list->first_enabled_feature_by_index[index];
> +
> + if (unlikely(rte_graph_feature_is_valid(f))) {
> + feature = __rte_graph_feature_get(arc, f, list);
> + feature_data = rte_graph_feature_data_get(arc, feature,
> index);
> + *gf = f;
> + *edge = feature_data->next_edge;
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +/**
> + * Get user data corresponding to current feature set by application in
> + * rte_graph_feature_enable()
> + *
> + * @param arc
> + * Feature arc object
> + * @param list
> + * Pointer to runtime active feature list from
> rte_graph_feature_arc_has_any_feature();
> + * @param feature
> + * Feature index
> + * @param index
> + * Interface index
> + *
> + * @return
> + * UINT32_MAX: Failure
> + * Valid user data: Success
> + */
> +static __rte_always_inline uint32_t
> +rte_graph_feature_user_data_get(struct rte_graph_feature_arc *arc,
> + const rte_graph_feature_rt_list_t list,
> + rte_graph_feature_t feature,
> + uint32_t index)
> +{
> + rte_graph_feature_data_t *fdata = NULL;
> + struct rte_graph_feature *f = NULL;
> +
> + if (likely(rte_graph_feature_is_valid(feature))) {
> + f = __rte_graph_feature_get(arc, feature, list);
> + fdata = rte_graph_feature_data_get(arc, f, index);
> + return fdata->user_data;
> + }
> +
> + return UINT32_MAX;
> +}
> +#ifdef __cplusplus
> +}
> +#endif
> +#endif
> diff --git a/lib/graph/version.map b/lib/graph/version.map
> index 2c83425ddc..82b2469fba 100644
> --- a/lib/graph/version.map
> +++ b/lib/graph/version.map
> @@ -52,3 +52,20 @@ DPDK_25 {
>
> local: *;
> };
> +
> +EXPERIMENTAL {
> + global:
> +
> + # added in 24.11
> + rte_graph_feature_arc_init;
> + rte_graph_feature_arc_create;
> + rte_graph_feature_arc_lookup_by_name;
> + rte_graph_feature_add;
> + rte_graph_feature_enable;
> + rte_graph_feature_validate;
> + rte_graph_feature_disable;
> + rte_graph_feature_lookup;
> + rte_graph_feature_arc_destroy;
> + rte_graph_feature_arc_cleanup;
> + rte_graph_feature_arc_num_enabled_features;
> +};
> --
> 2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [RFC PATCH 0/3] add feature arc in rte_graph
2024-09-07 7:31 [RFC PATCH 0/3] add feature arc in rte_graph Nitin Saxena
` (2 preceding siblings ...)
2024-09-07 7:31 ` [RFC PATCH 3/3] graph: add IPv4 output feature arc Nitin Saxena
@ 2024-10-08 8:04 ` David Marchand
2024-10-08 14:26 ` [EXTERNAL] " Nitin Saxena
2024-10-14 11:11 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
4 siblings, 2 replies; 56+ messages in thread
From: David Marchand @ 2024-10-08 8:04 UTC (permalink / raw)
To: Nitin Saxena
Cc: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan, dev,
Nitin Saxena, Robin Jarry, Christophe Fontaine
Hi graph guys,
On Sat, Sep 7, 2024 at 9:31 AM Nitin Saxena <nsaxena@marvell.com> wrote:
>
> Feature arc represents an ordered list of features/protocols at a given
> networking layer. It is a high level abstraction to connect various
> rte_graph nodes, as feature nodes, and allow packets steering across
> these nodes in a generic manner.
>
> Features (or feature nodes) are nodes which handles partial or complete
> handling of a protocol in fast path. Like ipv4-rewrite node, which adds
> rewrite data to an outgoing IPv4 packet.
>
> However in above example, outgoing interface(say "eth0") may have
> outbound IPsec policy enabled, hence packets must be steered from
> ipv4-rewrite node to ipsec-outbound-policy node for outbound IPsec
> policy lookup. On the other hand, packets routed to another interface
> (eth1) will not be sent to ipsec-outbound-policy node as IPsec feature
> is disabled on eth1. Feature-arc allows rte_graph applications to manage
> such constraints easily
>
> Feature arc abstraction allows rte_graph based application to
>
> 1. Seamlessly steer packets across feature nodes based on wheter feature
> is enabled or disabled on an interface. Features enabled on one
> interface may not be enabled on another interface with in a same feature
> arc.
>
> 2. Allow enabling/disabling of features on an interface at runtime,
> so that if a feature is disabled, packets associated with that interface
> won't be steered to corresponding feature node.
>
> 3. Provides mechanism to hook custom/user-defined nodes to a feature
> node and allow packet steering from feature node to custom node without
> changing former's fast path function
>
> 4. Allow expressing features in a particular sequential order so that
> packets are steered in an ordered way across nodes in fast path. For
> eg: if IPsec and IPv4 features are enabled on an ingress interface,
> packets must be sent to IPsec inbound policy node first and then to ipv4
> lookup node.
>
> This patch series adds feature arc library in rte_graph and also adds
> "ipv4-output" feature arc handling in "ipv4-rewrite" node.
>
> Nitin Saxena (3):
> graph: add feature arc support
> graph: add feature arc option in graph create
> graph: add IPv4 output feature arc
>
> lib/graph/graph.c | 1 +
> lib/graph/graph_feature_arc.c | 959 +++++++++++++++++++++++
> lib/graph/graph_populate.c | 7 +-
> lib/graph/graph_private.h | 3 +
> lib/graph/meson.build | 2 +
> lib/graph/node.c | 2 +
> lib/graph/rte_graph.h | 3 +
> lib/graph/rte_graph_feature_arc.h | 373 +++++++++
> lib/graph/rte_graph_feature_arc_worker.h | 548 +++++++++++++
> lib/graph/version.map | 17 +
> lib/node/ip4_rewrite.c | 476 ++++++++---
> lib/node/ip4_rewrite_priv.h | 9 +-
> lib/node/node_private.h | 19 +-
> lib/node/rte_node_ip4_api.h | 3 +
> 14 files changed, 2325 insertions(+), 97 deletions(-)
> create mode 100644 lib/graph/graph_feature_arc.c
> create mode 100644 lib/graph/rte_graph_feature_arc.h
> create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
I see no non-RFC series following this original submission.
It will slip to next release unless there is an objection.
Btw, I suggest copying Robin (and Christophe) for graph related changes.
--
David Marchand
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 0/5] add feature arc in rte_graph
2024-09-07 7:31 [RFC PATCH 0/3] add feature arc in rte_graph Nitin Saxena
` (3 preceding siblings ...)
2024-10-08 8:04 ` [RFC PATCH 0/3] add feature arc in rte_graph David Marchand
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 1/5] graph: add feature arc support Nitin Saxena
` (5 more replies)
4 siblings, 6 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
Feature arc represents an ordered list of features/protocols at a given
networking layer. It is a high level abstraction to connect various
rte_graph nodes, as feature nodes, and allow packets steering across
these nodes in a generic manner.
Features (or feature nodes) are nodes which handles partial or complete
handling of a protocol in fast path. Like ipv4-rewrite node, which adds
rewrite data to an outgoing IPv4 packet.
However in above example, outgoing interface(say "eth0") may have
outbound IPsec policy enabled, hence packets must be steered from
ipv4-rewrite node to ipsec-outbound-policy node for outbound IPsec
policy lookup. On the other hand, packets routed to another interface
(eth1) will not be sent to ipsec-outbound-policy node as IPsec feature
is disabled on eth1. Feature-arc allows rte_graph applications to manage
such constraints easily
Feature arc abstraction allows rte_graph based application to
1. Seamlessly steer packets across feature nodes based on whether
feature is enabled or disabled on an interface. Features enabled on one
interface may not be enabled on another interface with in a same feature
arc.
2. Allow enabling/disabling of features on an interface at runtime,
so that if a feature is disabled, packets associated with that interface
won't be steered to corresponding feature node.
3. Provides mechanism to hook custom/user-defined nodes to a feature
node and allow packet steering from feature node to custom node without
changing former's fast path function
4. Allow expressing features in a particular sequential order so that
packets are steered in an ordered way across nodes in fast path. For
eg: if IPsec and IPv4 features are enabled on an ingress interface,
packets must be sent to IPsec inbound policy node first and then to ipv4
lookup node.
This patch series adds feature arc library in rte_graph and also adds
"ipv4-output" feature arc handling in "ipv4-rewrite" node.
Changes in v2:
- Added unit tests for feature arc
- Fixed issues found in testing
- Added new public APIs rte_graph_feature_arc_feature_to_node(),
rte_graph_feature_arc_feature_to_name(),
rte_graph_feature_arc_num_features()
- Added programming guide for feature arc
- Added release notes for feature arc
Nitin Saxena (5):
graph: add feature arc support
graph: add feature arc option in graph create
graph: add IPv4 output feature arc
test/graph_feature_arc: add functional tests
docs: add programming guide for feature arc
app/test/meson.build | 1 +
app/test/test_graph_feature_arc.c | 1415 +++++++++++++++++++
doc/guides/prog_guide/graph_lib.rst | 289 ++++
doc/guides/prog_guide/img/feature_arc-1.jpg | Bin 0 -> 48984 bytes
doc/guides/prog_guide/img/feature_arc-2.jpg | Bin 0 -> 113287 bytes
doc/guides/prog_guide/img/feature_arc-3.jpg | Bin 0 -> 93408 bytes
doc/guides/rel_notes/release_24_11.rst | 11 +-
lib/graph/graph.c | 1 +
lib/graph/graph_feature_arc.c | 1223 ++++++++++++++++
lib/graph/graph_populate.c | 7 +-
lib/graph/graph_private.h | 3 +
lib/graph/meson.build | 2 +
lib/graph/node.c | 2 +
lib/graph/rte_graph.h | 3 +
lib/graph/rte_graph_feature_arc.h | 429 ++++++
lib/graph/rte_graph_feature_arc_worker.h | 672 +++++++++
lib/graph/version.map | 20 +
lib/node/ip4_rewrite.c | 476 +++++--
lib/node/ip4_rewrite_priv.h | 15 +-
lib/node/node_private.h | 20 +-
lib/node/rte_node_ip4_api.h | 3 +
21 files changed, 4493 insertions(+), 99 deletions(-)
create mode 100644 app/test/test_graph_feature_arc.c
create mode 100644 doc/guides/prog_guide/img/feature_arc-1.jpg
create mode 100644 doc/guides/prog_guide/img/feature_arc-2.jpg
create mode 100644 doc/guides/prog_guide/img/feature_arc-3.jpg
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 1/5] graph: add feature arc support
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 2/5] graph: add feature arc option in graph create Nitin Saxena
` (4 subsequent siblings)
5 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
add feature arc to allow dynamic steering of packets across graph nodes
based on protocol features enabled on incoming or outgoing interface
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
doc/guides/rel_notes/release_24_11.rst | 11 +-
lib/graph/graph_feature_arc.c | 1223 ++++++++++++++++++++++
lib/graph/meson.build | 2 +
lib/graph/rte_graph_feature_arc.h | 429 ++++++++
lib/graph/rte_graph_feature_arc_worker.h | 672 ++++++++++++
lib/graph/version.map | 20 +
6 files changed, 2356 insertions(+), 1 deletion(-)
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst
index 0ff70d9057..24852aa8e0 100644
--- a/doc/guides/rel_notes/release_24_11.rst
+++ b/doc/guides/rel_notes/release_24_11.rst
@@ -23,7 +23,6 @@ DPDK Release 24.11
New Features
------------
-
.. This section should contain new features added in this release.
Sample format:
@@ -55,6 +54,16 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added feature arc abstraction in graph library.**
+
+ Feature arc abstraction helps ``rte_graph`` based applications to steer
+ packets across different node path(s) based on the features (or protocols)
+ enabled on interfaces. Different feature node paths can be enabled/disabled
+ at runtime on some or on all interfaces. This abstraction also help
+ applications to hook their ``custom nodes`` in standard DPDK node paths
+ without any code changes in the later.
+
+ * Added ``ip4-output`` feature arc processing in ``ip4_rewrite`` node.
Removed Items
-------------
diff --git a/lib/graph/graph_feature_arc.c b/lib/graph/graph_feature_arc.c
new file mode 100644
index 0000000000..ff99f7b26a
--- /dev/null
+++ b/lib/graph/graph_feature_arc.c
@@ -0,0 +1,1223 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#include "graph_private.h"
+#include <rte_graph_feature_arc_worker.h>
+#include <rte_malloc.h>
+
+#define ARC_PASSIVE_LIST(arc) (arc->active_feature_list ^ 0x1)
+
+#define rte_graph_uint_cast(x) ((unsigned int)x)
+#define feat_dbg graph_dbg
+
+static rte_graph_feature_arc_main_t *__rte_graph_feature_arc_main;
+
+/* Make sure fast path cache line is compact */
+_Static_assert((offsetof(struct rte_graph_feature_arc, slow_path_variables)
+ - offsetof(struct rte_graph_feature_arc, fast_path_variables))
+ <= RTE_CACHE_LINE_SIZE,
+ "Fast path feature arc variables exceed cache line size");
+
+#define connect_graph_nodes(node1, node2, edge, arc_name) \
+ __connect_graph_nodes(node1, node2, edge, arc_name, __LINE__)
+
+#define FEAT_COND_ERR(cond, fmt, ...) \
+ do { \
+ if (cond) \
+ graph_err(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+/*
+ * lookup feature name and get control path node_list as well as feature index
+ * at which it is inserted
+ */
+static int
+feature_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
+ struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ const char *name;
+ uint32_t fi = 0;
+
+ if (!feat_name)
+ return -1;
+
+ if (slot)
+ *slot = UINT32_MAX;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ RTE_VERIFY(finfo->feature_arc == arc);
+ name = rte_node_id_to_name(finfo->feature_node->id);
+ if (!strncmp(name, feat_name, strlen(name))) {
+ if (ffinfo)
+ *ffinfo = finfo;
+ if (slot)
+ *slot = fi;
+ return 0;
+ }
+ fi++;
+ }
+ return -1;
+}
+
+/* Lookup used only during rte_graph_feature_add() */
+static int
+feature_add_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
+ struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ const char *name;
+ uint32_t fi = 0;
+
+ if (!feat_name)
+ return -1;
+
+ if (slot)
+ *slot = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ RTE_VERIFY(finfo->feature_arc == arc);
+ name = rte_node_id_to_name(finfo->feature_node->id);
+ if (!strncmp(name, feat_name, strlen(name))) {
+ if (ffinfo)
+ *ffinfo = finfo;
+ if (slot)
+ *slot = fi;
+ return 0;
+ }
+ /* Update slot where new feature can be added */
+ if (slot)
+ *slot = fi;
+ fi++;
+ }
+
+ return -1;
+}
+
+/* Get control path node info from provided input feature_index */
+static int
+feature_arc_node_info_lookup(struct rte_graph_feature_arc *arc, uint32_t feature_index,
+ struct rte_graph_feature_node_list **ppfinfo,
+ const int do_sanity_check)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ if (!ppfinfo)
+ return -1;
+
+ *ppfinfo = NULL;
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ /* Check sanity */
+ if (do_sanity_check)
+ if (finfo->node_index != index)
+ RTE_VERIFY(0);
+ if (index == feature_index) {
+ *ppfinfo = finfo;
+ return 0;
+ }
+ index++;
+ }
+ return -1;
+}
+
+/* prepare feature arc after addition of all features */
+static void
+prepare_feature_arc_before_first_enable(struct rte_graph_feature_arc *arc)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ arc->active_feature_list = 0;
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ finfo->node_index = index;
+ feat_dbg("\t%s prepare: %s added to list at index: %u", arc->feature_arc_name,
+ finfo->feature_node->name, index);
+ index++;
+ }
+}
+
+/* feature arc lookup in array */
+static int
+feature_arc_lookup(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;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ if (arc == (rte_graph_feature_arc_get(dm->feature_arcs[iter])))
+ return 0;
+ }
+ return -1;
+}
+
+/* Check valid values for known fields in arc to make sure arc is sane */
+static int check_feature_arc_sanity(rte_graph_feature_arc_t _arc, int iter)
+{
+#ifdef FEATURE_ARC_DEBUG
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+
+ RTE_VERIFY(arc->feature_arc_main == __rte_graph_feature_arc_main);
+ RTE_VERIFY(arc->feature_arc_index == iter);
+
+ RTE_VERIFY(arc->feature_list[0]->indexed_by_features = arc->features[0]);
+ RTE_VERIFY(arc->feature_list[1]->indexed_by_features = arc->features[1]);
+
+ RTE_VERIFY(arc->active_feature_list < 2);
+#else
+ RTE_SET_USED(_arc);
+ RTE_SET_USED(iter);
+#endif
+ return 0;
+}
+
+/* Perform sanity on all arc if any corruption occurred */
+static int do_sanity_all_arcs(void)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ uint32_t iter;
+
+ if (!dm)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ if (check_feature_arc_sanity(dm->feature_arcs[iter], iter))
+ return -1;
+ }
+ return 0;
+}
+
+/* get existing edge from parent_node -> child_node */
+static int
+get_existing_edge(const char *arc_name, struct rte_node_register *parent_node,
+ struct rte_node_register *child_node, rte_edge_t *_edge)
+{
+ char **next_edges = NULL;
+ uint32_t i, count = 0;
+
+ RTE_SET_USED(arc_name);
+
+ count = rte_node_edge_get(parent_node->id, NULL);
+
+ if (!count)
+ return -1;
+
+ next_edges = malloc(count);
+
+ if (!next_edges)
+ return -1;
+
+ count = rte_node_edge_get(parent_node->id, next_edges);
+ for (i = 0; i < count; i++) {
+ if (strstr(child_node->name, next_edges[i])) {
+ if (_edge)
+ *_edge = (rte_edge_t)i;
+
+ free(next_edges);
+ return 0;
+ }
+ }
+ free(next_edges);
+
+ return -1;
+}
+
+/* create or retrieve already existing edge from parent_node -> child_node */
+static int
+__connect_graph_nodes(struct rte_node_register *parent_node, struct rte_node_register *child_node,
+ rte_edge_t *_edge, char *arc_name, int lineno)
+{
+ const char *next_node = NULL;
+ rte_edge_t edge;
+
+ if (!get_existing_edge(arc_name, parent_node, child_node, &edge)) {
+ feat_dbg("\t%s/%d: %s[%u]: \"%s\", edge reused", arc_name, lineno,
+ parent_node->name, edge, child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+ }
+
+ /* Node to be added */
+ next_node = child_node->name;
+
+ edge = rte_node_edge_update(parent_node->id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+ if (edge == RTE_EDGE_ID_INVALID) {
+ graph_err("edge invalid");
+ return -1;
+ }
+ edge = rte_node_edge_count(parent_node->id) - 1;
+
+ feat_dbg("\t%s/%d: %s[%u]: \"%s\", new edge added", arc_name, lineno, parent_node->name,
+ edge, child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+}
+
+/* feature arc initialization */
+static int
+feature_arc_main_init(rte_graph_feature_arc_main_t **pfl, uint32_t max_feature_arcs)
+{
+ rte_graph_feature_arc_main_t *pm = NULL;
+ uint32_t i;
+ size_t sz;
+
+ if (!pfl)
+ return -1;
+
+ sz = sizeof(rte_graph_feature_arc_main_t) +
+ (sizeof(pm->feature_arcs[0]) * max_feature_arcs);
+
+ pm = rte_malloc("rte_graph_feature_arc_main", sz, 0);
+ if (!pm)
+ return -1;
+
+ memset(pm, 0, sz);
+
+ for (i = 0; i < max_feature_arcs; i++)
+ pm->feature_arcs[i] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ pm->max_feature_arcs = max_feature_arcs;
+
+ *pfl = pm;
+
+ return 0;
+}
+
+/* feature arc initialization, public API */
+int
+rte_graph_feature_arc_init(int max_feature_arcs)
+{
+ if (!max_feature_arcs)
+ return -1;
+
+ if (__rte_graph_feature_arc_main)
+ return -1;
+
+ return feature_arc_main_init(&__rte_graph_feature_arc_main, max_feature_arcs);
+}
+
+/* reset feature list before switching to passive list */
+static void
+feature_arc_list_reset(struct rte_graph_feature_arc *arc, uint32_t list_index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+ uint32_t i, j;
+
+ list = arc->feature_list[list_index];
+ feat = arc->features[list_index];
+
+ /*Initialize variables*/
+ memset(feat, 0, arc->feature_size * arc->max_features);
+ memset(list, 0, arc->feature_list_size);
+
+ /* Initialize feature and feature_data */
+ for (i = 0; i < arc->max_features; i++) {
+ feat = __rte_graph_feature_get(arc, i, list_index);
+ feat->this_feature_index = i;
+
+ for (j = 0; j < arc->max_indexes; j++) {
+ fdata = rte_graph_feature_data_get(arc, feat, j);
+ fdata->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ fdata->next_edge = UINT16_MAX;
+ fdata->user_data = UINT32_MAX;
+ }
+ }
+
+ for (i = 0; i < arc->max_indexes; i++)
+ list->first_enabled_feature_by_index[i] = RTE_GRAPH_FEATURE_INVALID;
+}
+
+static int
+feature_arc_list_init(struct rte_graph_feature_arc *arc, const char *flist_name,
+ rte_graph_feature_list_t **pplist,
+ struct rte_graph_feature **ppfeature, uint32_t list_index)
+{
+ char fname[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ size_t list_size, feat_size, fdata_size;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+
+ list_size = sizeof(struct rte_graph_feature_list) +
+ (sizeof(list->first_enabled_feature_by_index[0]) * arc->max_indexes);
+
+ list_size = RTE_ALIGN_CEIL(list_size, RTE_CACHE_LINE_SIZE);
+
+ list = rte_malloc(flist_name, list_size, RTE_CACHE_LINE_SIZE);
+ if (!list)
+ return -ENOMEM;
+
+ memset(list, 0, list_size);
+ fdata_size = arc->max_indexes * sizeof(rte_graph_feature_data_t);
+
+ /* Let one feature and its associated data per index capture complete
+ * cache lines
+ */
+ feat_size = RTE_ALIGN_CEIL(sizeof(struct rte_graph_feature) + fdata_size,
+ RTE_CACHE_LINE_SIZE);
+
+ snprintf(fname, sizeof(fname), "%s-%s", arc->feature_arc_name, "feat");
+
+ feat = rte_malloc(fname, feat_size * arc->max_features, RTE_CACHE_LINE_SIZE);
+ if (!feat) {
+ rte_free(list);
+ return -ENOMEM;
+ }
+ arc->feature_size = feat_size;
+ arc->feature_data_size = fdata_size;
+ arc->feature_list_size = list_size;
+
+ /* Initialize list */
+ list->indexed_by_features = feat;
+ *pplist = list;
+ *ppfeature = feat;
+
+ feature_arc_list_reset(arc, list_index);
+
+ return 0;
+}
+
+/* free resources allocated in feature_arc_list_init() */
+static void
+feature_arc_list_destroy(struct rte_graph_feature_arc *arc, int list_index)
+{
+ rte_graph_feature_list_t *list = NULL;
+
+ list = arc->feature_list[list_index];
+
+ rte_free(list->indexed_by_features);
+
+ arc->features[list_index] = NULL;
+
+ rte_free(list);
+
+ arc->feature_list[list_index] = NULL;
+}
+
+int
+rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node, rte_graph_feature_arc_t *_arc)
+{
+ char name[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ struct rte_graph_feature_data *gfd = NULL;
+ rte_graph_feature_arc_main_t *dfm = NULL;
+ struct rte_graph_feature_arc *arc = NULL;
+ struct rte_graph_feature *df = NULL;
+ uint32_t iter, j, arc_index;
+ size_t sz;
+
+ if (!_arc)
+ SET_ERR_JMP(EINVAL, err, "%s: Invalid _arc", feature_arc_name);
+
+ if (max_features < 2)
+ SET_ERR_JMP(EINVAL, err, "%s: max_features must be greater than 1",
+ feature_arc_name);
+
+ if (!start_node)
+ SET_ERR_JMP(EINVAL, err, "%s: start_node cannot be NULL",
+ feature_arc_name);
+
+ if (!feature_arc_name)
+ SET_ERR_JMP(EINVAL, err, "%s: feature_arc name cannot be NULL",
+ feature_arc_name);
+
+ if (max_features > RTE_GRAPH_FEATURE_MAX_PER_ARC)
+ SET_ERR_JMP(EAGAIN, err, "%s: number of features cannot be greater than 64",
+ feature_arc_name);
+
+ /*
+ * Application hasn't called rte_graph_feature_arc_init(). Initialize with
+ * default values
+ */
+ if (!__rte_graph_feature_arc_main) {
+ if (rte_graph_feature_arc_init((int)RTE_GRAPH_FEATURE_ARC_MAX) < 0) {
+ graph_err("rte_graph_feature_arc_init() failed");
+ return -1;
+ }
+ }
+
+ /* If name is not unique */
+ if (!rte_graph_feature_arc_lookup_by_name(feature_arc_name, NULL))
+ SET_ERR_JMP(EINVAL, err, "%s: feature arc name already exists",
+ feature_arc_name);
+
+ dfm = __rte_graph_feature_arc_main;
+
+ /* threshold check */
+ if (dfm->num_feature_arcs > (dfm->max_feature_arcs - 1))
+ SET_ERR_JMP(EAGAIN, err, "%s: max number (%u) of feature arcs reached",
+ feature_arc_name, dfm->max_feature_arcs);
+
+ /* Find the free slot for feature arc */
+ for (iter = 0; iter < dfm->max_feature_arcs; iter++) {
+ if (dfm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ break;
+ }
+ arc_index = iter;
+
+ if (arc_index >= dfm->max_feature_arcs) {
+ graph_err("No free slot found for num_feature_arc");
+ return -1;
+ }
+
+ /* This should not happen */
+ RTE_VERIFY(dfm->feature_arcs[arc_index] == RTE_GRAPH_FEATURE_ARC_INITIALIZER);
+
+ /* size of feature arc + feature_bit_mask_by_index */
+ sz = RTE_ALIGN_CEIL(sizeof(*arc) + (sizeof(uint64_t) * max_indexes), RTE_CACHE_LINE_SIZE);
+
+ arc = rte_malloc(feature_arc_name, sz, RTE_CACHE_LINE_SIZE);
+
+ if (!arc) {
+ graph_err("malloc failed for feature_arc_create()");
+ return -1;
+ }
+
+ memset(arc, 0, sz);
+
+ /* Initialize rte_graph port group fixed variables */
+ STAILQ_INIT(&arc->all_features);
+ strncpy(arc->feature_arc_name, feature_arc_name, RTE_GRAPH_FEATURE_ARC_NAMELEN - 1);
+ arc->feature_arc_main = (void *)dfm;
+ arc->start_node = start_node;
+ arc->max_features = max_features;
+ arc->max_indexes = max_indexes;
+ arc->feature_arc_index = arc_index;
+
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist0");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[0], &arc->features[0], 0) < 0) {
+ rte_free(arc);
+ graph_err("feature_arc_list_init(0) failed");
+ return -1;
+ }
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist1");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[1], &arc->features[1], 1) < 0) {
+ feature_arc_list_destroy(arc, 0);
+ rte_free(arc);
+ graph_err("feature_arc_list_init(1) failed");
+ return -1;
+ }
+
+ for (iter = 0; iter < arc->max_features; iter++) {
+ df = rte_graph_feature_get(arc, iter);
+ for (j = 0; j < arc->max_indexes; j++) {
+ gfd = rte_graph_feature_data_get(arc, df, j);
+ gfd->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ }
+ }
+ dfm->feature_arcs[arc->feature_arc_index] = (rte_graph_feature_arc_t)arc;
+ dfm->num_feature_arcs++;
+
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+
+ do_sanity_all_arcs();
+
+ feat_dbg("Feature arc %s[%p] created with max_features: %u and indexes: %u",
+ feature_arc_name, (void *)arc, max_features, max_indexes);
+ return 0;
+
+err:
+ return -rte_errno;
+}
+
+int
+rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *_runs_after, const char *runs_before)
+{
+ struct rte_graph_feature_node_list *after_finfo = NULL, *before_finfo = NULL;
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *temp = NULL, *finfo = NULL;
+ char feature_name[3*RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ const char *runs_after = NULL;
+ uint32_t num_feature = 0;
+ uint32_t slot, add_flag;
+ rte_edge_t edge = -1;
+
+ /* sanity */
+ if (arc->feature_arc_main != __rte_graph_feature_arc_main) {
+ graph_err("feature arc not created: 0x%016" PRIx64, (uint64_t)_arc);
+ return -1;
+ }
+
+ if (feature_arc_lookup(_arc)) {
+ graph_err("invalid feature arc: 0x%016" PRIx64, (uint64_t)_arc);
+ return -1;
+ }
+
+ if (arc->runtime_enabled_features) {
+ graph_err("adding features after enabling any one of them is not supported");
+ return -1;
+ }
+
+ if ((_runs_after != NULL) && (runs_before != NULL) &&
+ (_runs_after == runs_before)) {
+ graph_err("runs_after and runs_before are same '%s:%s]", _runs_after,
+ runs_before);
+ return -1;
+ }
+
+ if (!feature_node) {
+ graph_err("feature_node: %p invalid", feature_node);
+ return -1;
+ }
+
+ arc = rte_graph_feature_arc_get(_arc);
+
+ if (feature_node->id == RTE_NODE_ID_INVALID) {
+ graph_err("Invalid node: %s", feature_node->name);
+ return -1;
+ }
+
+ if (!feature_add_lookup(arc, feature_node->name, &finfo, &slot)) {
+ graph_err("%s feature already added", feature_node->name);
+ return -1;
+ }
+
+ if (slot >= arc->max_features) {
+ graph_err("%s: Max features %u added to feature arc",
+ arc->feature_arc_name, slot);
+ return -1;
+ }
+
+ if (strstr(feature_node->name, arc->start_node->name)) {
+ graph_err("Feature %s cannot point to itself: %s", feature_node->name,
+ arc->start_node->name);
+ return -1;
+ }
+
+ feat_dbg("%s: adding feature node: %s at feature index: %u", arc->feature_arc_name,
+ feature_node->name, slot);
+
+ if (connect_graph_nodes(arc->start_node, feature_node, &edge, arc->feature_arc_name)) {
+ graph_err("unable to connect %s -> %s", arc->start_node->name, feature_node->name);
+ return -1;
+ }
+
+ snprintf(feature_name, sizeof(feature_name), "%s-%s-finfo",
+ arc->feature_arc_name, feature_node->name);
+
+ finfo = rte_malloc(feature_name, sizeof(*finfo), 0);
+ if (!finfo) {
+ graph_err("%s/%s: rte_malloc failed", arc->feature_arc_name, feature_node->name);
+ return -1;
+ }
+
+ memset(finfo, 0, sizeof(*finfo));
+
+ finfo->feature_arc = (void *)arc;
+ finfo->feature_node = feature_node;
+ finfo->edge_to_this_feature = edge;
+ arc->runtime_enabled_features = 0;
+
+ /*
+ * if no constraints given and provided feature is not the first feature,
+ * explicitly set "runs_after" as last_feature. Handles the case:
+ *
+ * add(f1, NULL, NULL);
+ * add(f2, NULL, NULL);
+ */
+ num_feature = rte_graph_feature_arc_num_features(_arc);
+ if (!_runs_after && !runs_before && num_feature)
+ runs_after = rte_graph_feature_arc_feature_to_name(_arc, num_feature - 1);
+ else
+ runs_after = _runs_after;
+
+ /* Check for before and after constraints */
+ if (runs_before) {
+ /* runs_before sanity */
+ if (feature_lookup(arc, runs_before, &before_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid before feature name: %s", runs_before);
+
+ if (!before_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "runs_before %s does not exist", runs_before);
+
+ /*
+ * Starting from 0 to runs_before, continue connecting edges
+ */
+ add_flag = 1;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (!add_flag)
+ /* Nodes after seeing "runs_before", finfo connects to temp*/
+ connect_graph_nodes(finfo->feature_node, temp->feature_node,
+ NULL, arc->feature_arc_name);
+ /*
+ * As soon as we see runs_before. stop adding edges
+ */
+ if (!strncmp(temp->feature_node->name, runs_before,
+ RTE_GRAPH_NAMESIZE)) {
+ if (!connect_graph_nodes(finfo->feature_node, temp->feature_node,
+ &edge, arc->feature_arc_name))
+ add_flag = 0;
+ }
+
+ if (add_flag)
+ /* Nodes before seeing "run_before" are connected to finfo */
+ connect_graph_nodes(temp->feature_node, finfo->feature_node, NULL,
+ arc->feature_arc_name);
+ }
+ }
+
+ if (runs_after) {
+ if (feature_lookup(arc, runs_after, &after_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid after feature_name %s", runs_after);
+
+ if (!after_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "runs_after %s does not exist", runs_after);
+
+ /* Starting from runs_after to end continue connecting edges */
+ add_flag = 0;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (add_flag)
+ /* We have already seen runs_after now */
+ /* Add all features as next node to current feature*/
+ connect_graph_nodes(finfo->feature_node, temp->feature_node, NULL,
+ arc->feature_arc_name);
+ else
+ /* Connect initial nodes to newly added node*/
+ connect_graph_nodes(temp->feature_node, finfo->feature_node, NULL,
+ arc->feature_arc_name);
+
+ /* as soon as we see runs_after. start adding edges
+ * from next iteration
+ */
+ if (!strncmp(temp->feature_node->name, runs_after, RTE_GRAPH_NAMESIZE))
+ add_flag = 1;
+ }
+
+ /* add feature next to runs_after */
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo, finfo, next_feature);
+ } else {
+ if (before_finfo) {
+ /* add finfo before "before_finfo" element in the list */
+ after_finfo = NULL;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (before_finfo == temp) {
+ if (after_finfo)
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo,
+ finfo, next_feature);
+ else
+ STAILQ_INSERT_HEAD(&arc->all_features, finfo,
+ next_feature);
+
+ return 0;
+ }
+ after_finfo = temp;
+ }
+ } else {
+ /* Very first feature just needs to be added to list */
+ STAILQ_INSERT_TAIL(&arc->all_features, finfo, next_feature);
+ }
+ }
+
+ return 0;
+
+finfo_free:
+ rte_free(finfo);
+
+ return -1;
+}
+
+int
+rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char *feature_name,
+ rte_graph_feature_t *feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot;
+
+ if (!feature_lookup(arc, feature_name, &finfo, &slot)) {
+ *feat = (rte_graph_feature_t) slot;
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+rte_graph_feature_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;
+ struct rte_graph_feature *gf = NULL;
+ uint32_t slot;
+
+ /* validate _arc */
+ if (arc->feature_arc_main != __rte_graph_feature_arc_main) {
+ FEAT_COND_ERR(emit_logs, "invalid feature arc: 0x%016" PRIx64, (uint64_t)_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 (feature_lookup(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",
+ arc->feature_arc_name, feature_name);
+ return -EINVAL;
+ }
+
+ /* slot should be in valid range */
+ if (slot >= arc->max_features) {
+ FEAT_COND_ERR(emit_logs, "%s/%s: Invalid free slot %u(max=%u) for feature",
+ arc->feature_arc_name, feature_name, slot, arc->max_features);
+ return -EINVAL;
+ }
+
+ /* slot should be in range of 0 - 63 */
+ if (slot > (RTE_GRAPH_FEATURE_MAX_PER_ARC - 1)) {
+ FEAT_COND_ERR(emit_logs, "%s/%s: Invalid slot: %u", arc->feature_arc_name,
+ feature_name, slot);
+ return -EINVAL;
+ }
+
+ if (finfo->node_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->node_index, slot);
+ return -1;
+ }
+
+ /* Get feature from active list */
+ gf = __rte_graph_feature_get(arc, slot, ARC_PASSIVE_LIST(arc));
+ if (gf->this_feature_index != slot) {
+ FEAT_COND_ERR(emit_logs,
+ "%s: %s rcvd feature_idx: %u does not match with saved: %u",
+ arc->feature_arc_name, feature_name, slot, gf->this_feature_index);
+ return -1;
+ }
+
+ if (is_enable_disable && (arc->feature_bit_mask_by_index[index] &
+ RTE_BIT64(slot))) {
+ 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;
+ }
+
+ return 0;
+}
+
+/*
+ * Before switch to passive list, user_data needs to be copied from active list to passive list
+ */
+static void
+copy_fastpath_user_data(struct rte_graph_feature_arc *arc, uint16_t dest_list_index,
+ uint16_t src_list_index)
+{
+ rte_graph_feature_data_t *sgfd = NULL, *dgfd = NULL;
+ struct rte_graph_feature *sgf = NULL, *dgf = NULL;
+ uint32_t i, j;
+
+ for (i = 0; i < arc->max_features; i++) {
+ sgf = __rte_graph_feature_get(arc, i, src_list_index);
+ dgf = __rte_graph_feature_get(arc, i, dest_list_index);
+ for (j = 0; j < arc->max_indexes; j++) {
+ sgfd = rte_graph_feature_data_get(arc, sgf, j);
+ dgfd = rte_graph_feature_data_get(arc, dgf, j);
+ dgfd->user_data = sgfd->user_data;
+ }
+ }
+}
+/*
+ * Fill fast path information like
+ * - next_edge
+ * - next_enabled_feature
+ */
+static void
+refill_feature_fastpath_data(struct rte_graph_feature_arc *arc, uint16_t list_index)
+{
+ struct rte_graph_feature_node_list *finfo = NULL, *prev_finfo = NULL;
+ struct rte_graph_feature_data *gfd = NULL, *prev_gfd = NULL;
+ uint32_t fi = UINT32_MAX, di = UINT32_MAX, prev_fi = UINT32_MAX;
+ struct rte_graph_feature *gf = NULL, *prev_gf = NULL;
+ rte_graph_feature_list_t *flist = NULL;
+ rte_edge_t edge = UINT16_MAX;
+ uint64_t bitmask = 0;
+
+ flist = arc->feature_list[list_index];
+
+ for (di = 0; di < arc->max_indexes; di++) {
+ bitmask = arc->feature_bit_mask_by_index[di];
+ prev_fi = RTE_GRAPH_FEATURE_INVALID;
+ /* for each feature set for index, set fast path data */
+ while (rte_bsf64_safe(bitmask, &fi)) {
+ gf = __rte_graph_feature_get(arc, fi, list_index);
+ gfd = rte_graph_feature_data_get(arc, gf, di);
+ RTE_VERIFY(!feature_arc_node_info_lookup(arc, fi, &finfo, 1));
+
+ /* If previous feature_index was valid in last loop */
+ if (prev_fi != RTE_GRAPH_FEATURE_INVALID) {
+ prev_gf = __rte_graph_feature_get(arc, prev_fi, list_index);
+ prev_gfd = rte_graph_feature_data_get(arc, prev_gf, di);
+ /*
+ * Get edge of previous feature node connecting
+ * to this feature node
+ */
+ RTE_VERIFY(!feature_arc_node_info_lookup(arc, prev_fi,
+ &prev_finfo, 1));
+ if (!get_existing_edge(arc->feature_arc_name,
+ prev_finfo->feature_node,
+ finfo->feature_node, &edge)) {
+ feat_dbg("\t[%s/%u/di:%2u,cookie:%u]: (%u->%u)%s[%u] = %s",
+ arc->feature_arc_name, list_index, di,
+ prev_gfd->user_data, prev_fi, fi,
+ prev_finfo->feature_node->name,
+ edge, finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /*
+ * Fill current feature as next enabled
+ * feature to previous one
+ */
+ prev_gfd->next_enabled_feature = fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* On first feature edge of the node to be added */
+ if (fi == rte_bsf64(arc->feature_bit_mask_by_index[di])) {
+ if (!get_existing_edge(arc->feature_arc_name, arc->start_node,
+ finfo->feature_node,
+ &edge)) {
+ feat_dbg("\t[%s/%u/di:%2u,cookie:%u]: (->%u)%s[%u]=%s",
+ arc->feature_arc_name, list_index, di,
+ gfd->user_data, fi,
+ arc->start_node->name, edge,
+ finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /* Set first feature set array for index*/
+ flist->first_enabled_feature_by_index[di] =
+ (rte_graph_feature_t)fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* Clear current feature index */
+ bitmask &= ~RTE_BIT64(fi);
+ }
+ }
+}
+
+int
+rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index, const
+ char *feature_name, int32_t user_data)
+{
+ 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;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint64_t bitmask;
+ uint32_t slot;
+
+ 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);
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 1, true))
+ return -1;
+
+ /** This should not fail as validate() has passed */
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ RTE_VERIFY(0);
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ feat_dbg("\t%s/%s: index: %u, passive list: %u, feature index: %u",
+ arc->feature_arc_name, feature_name, index, passive_list, slot);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Set current user-data */
+ gfd->user_data = user_data;
+
+ /* Set bitmask in control path bitmask */
+ rte_bit_relaxed_set64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+ refill_feature_fastpath_data(arc, passive_list);
+
+ /* If first time feature getting enabled */
+ bitmask = rte_atomic_load_explicit(&arc->feature_enable_bitmask[arc->active_feature_list],
+ rte_memory_order_relaxed);
+
+ /* On very first feature enable instance */
+ if (!finfo->ref_count)
+ bitmask |= RTE_BIT64(slot);
+
+ rte_atomic_store_explicit(&arc->feature_enable_bitmask[passive_list],
+ bitmask, rte_memory_order_relaxed);
+
+ /* Slow path updates */
+ arc->runtime_enabled_features++;
+
+ /* Increase feature node info reference count */
+ finfo->ref_count++;
+
+ /* Store release semantics for active_list update */
+ rte_atomic_store_explicit(&arc->active_feature_list, passive_list,
+ rte_memory_order_release);
+
+ feat_dbg("%s/%s: After enable, switched active feature list to %u",
+ arc->feature_arc_name, feature_name, arc->active_feature_list);
+
+ return 0;
+}
+
+int
+rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index, const char *feature_name)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_data *gfd = NULL;
+ struct rte_graph_feature_node_list *finfo = NULL;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint64_t bitmask;
+ uint32_t slot;
+
+ feat_dbg("%s: Disable feature: %s for index: %u",
+ arc->feature_arc_name, feature_name, index);
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 0, true))
+ return -1;
+
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ return -1;
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ feat_dbg("\t%s/%s: index: %u, passive list: %u, feature index: %u",
+ arc->feature_arc_name, feature_name, index, passive_list, slot);
+
+ rte_bit_relaxed_clear64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Reset current user-data */
+ gfd->user_data = ~0;
+
+ refill_feature_fastpath_data(arc, passive_list);
+
+ finfo->ref_count--;
+ arc->runtime_enabled_features--;
+
+ /* If no feature enabled, reset feature in u64 fast path bitmask */
+ bitmask = rte_atomic_load_explicit(&arc->feature_enable_bitmask[arc->active_feature_list],
+ rte_memory_order_relaxed);
+
+ /* When last feature is disabled */
+ if (!finfo->ref_count)
+ bitmask &= ~(RTE_BIT64(slot));
+
+ rte_atomic_store_explicit(&arc->feature_enable_bitmask[passive_list], bitmask,
+ rte_memory_order_relaxed);
+
+ /* Store release semantics for active_list update */
+ rte_atomic_store_explicit(&arc->active_feature_list, passive_list,
+ rte_memory_order_release);
+
+ feat_dbg("%s/%s: After disable, switched active feature list to %u",
+ arc->feature_arc_name, feature_name, arc->active_feature_list);
+
+ return 0;
+}
+
+int
+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;
+
+ while (!STAILQ_EMPTY(&arc->all_features)) {
+ node_info = STAILQ_FIRST(&arc->all_features);
+ STAILQ_REMOVE_HEAD(&arc->all_features, next_feature);
+ rte_free(node_info);
+ }
+ feature_arc_list_destroy(arc, 0);
+ feature_arc_list_destroy(arc, 1);
+
+ dm->feature_arcs[arc->feature_arc_index] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ rte_free(arc);
+
+ do_sanity_all_arcs();
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_cleanup(void)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ rte_graph_feature_arc_destroy((rte_graph_feature_arc_t)dm->feature_arcs[iter]);
+ }
+ rte_free(dm);
+
+ __rte_graph_feature_arc_main = NULL;
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ struct rte_graph_feature_arc *arc = NULL;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ if (_arc)
+ *_arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ arc = rte_graph_feature_arc_get(dm->feature_arcs[iter]);
+
+ if ((strstr(arc->feature_arc_name, arc_name)) &&
+ (strlen(arc->feature_arc_name) == strlen(arc_name))) {
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+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);
+
+ return arc->runtime_enabled_features;
+}
+
+uint32_t
+rte_graph_feature_arc_num_features(rte_graph_feature_arc_t _arc)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t count = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature)
+ count++;
+
+ return count;
+}
+
+char *
+rte_graph_feature_arc_feature_to_name(rte_graph_feature_arc_t _arc, rte_graph_feature_t feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot = feat;
+
+ if (feat >= rte_graph_feature_arc_num_features(_arc)) {
+ graph_err("%s: feature %u does not exist", arc->feature_arc_name, feat);
+ return NULL;
+ }
+ if (!feature_arc_node_info_lookup(arc, slot, &finfo, 0/* ignore sanity*/))
+ return finfo->feature_node->name;
+
+ return NULL;
+}
+
+struct rte_node_register *
+rte_graph_feature_arc_feature_to_node(rte_graph_feature_arc_t _arc, rte_graph_feature_t feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot = feat;
+
+ if (feat >= rte_graph_feature_arc_num_features(_arc)) {
+ graph_err("%s: feature %u does not exist", arc->feature_arc_name, feat);
+ return NULL;
+ }
+ if (!feature_arc_node_info_lookup(arc, slot, &finfo, 0/* ignore sanity*/))
+ return finfo->feature_node;
+
+ return NULL;
+
+}
diff --git a/lib/graph/meson.build b/lib/graph/meson.build
index 0cb15442ab..d916176fb7 100644
--- a/lib/graph/meson.build
+++ b/lib/graph/meson.build
@@ -14,11 +14,13 @@ sources = files(
'graph_debug.c',
'graph_stats.c',
'graph_populate.c',
+ 'graph_feature_arc.c',
'graph_pcap.c',
'rte_graph_worker.c',
'rte_graph_model_mcore_dispatch.c',
)
headers = files('rte_graph.h', 'rte_graph_worker.h')
+headers += files('rte_graph_feature_arc.h', 'rte_graph_feature_arc_worker.h')
indirect_headers += files(
'rte_graph_model_mcore_dispatch.h',
'rte_graph_model_rtc.h',
diff --git a/lib/graph/rte_graph_feature_arc.h b/lib/graph/rte_graph_feature_arc.h
new file mode 100644
index 0000000000..d1c52bb3fb
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc.h
@@ -0,0 +1,429 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_H_
+#define _RTE_GRAPH_FEATURE_ARC_H_
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_compat.h>
+#include <rte_debug.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc.h
+ *
+ * Define APIs and structures/variables with respect to feature arc
+ *
+ * - Feature arc(s)
+ * - Feature(s)
+ *
+ * A feature arc represents an ordered list of features/protocol-nodes at a
+ * given networking layer. Feature arc provides a high level abstraction to
+ * connect various *rte_graph* nodes, designated as *feature nodes*, and
+ * allowing steering of packets across these feature nodes fast path processing
+ * in a generic manner. In a typical network stack, often a protocol or feature
+ * must be first enabled on a given interface, before any packet is steered
+ * towards it for feature processing. For eg: incoming IPv4 packets are sent to
+ * routing sub-system only after a valid IPv4 address is assigned to the
+ * received interface. In other words, often packets needs to be steered across
+ * features not based on the packet content but based on whether a feature is
+ * enable or disable on a given incoming/outgoing interface. Feature arc
+ * provides mechanism to enable/disable feature(s) on each interface at runtime
+ * and allow seamless packet steering across runtime enabled feature nodes in
+ * fast path.
+ *
+ * Feature arc also provides a way to steer packets from standard nodes to
+ * custom/user-defined *feature nodes* without any change in standard node's
+ * fast path functions
+ *
+ * On a given interface multiple feature(s) might be enabled in a particular
+ * feature arc. For instance, both "ipv4-output" and "IPsec policy output"
+ * features may be enabled on "eth0" interface in "L3-output" feature arc.
+ * Similarly, "ipv6-output" and "ipsec-output" may be enabled on "eth1"
+ * interface in same "L3-output" feature arc.
+ *
+ * When multiple features are present in a given feature arc, its imperative
+ * to allow each feature processing in a particular sequential order. For
+ * instance, in "L3-input" feature arc it may be required to run "IPsec
+ * input" feature first, for packet decryption, before "ip-lookup". So a
+ * sequential order must be maintained among features present in a feature arc.
+ *
+ * Features are enabled/disabled multiple times at runtime to some or all
+ * available interfaces present in the system. Enable/disabling features on one
+ * interface is independent of other interface.
+ *
+ * A given feature might consume packet (if it's configured to consume) or may
+ * forward it to next enabled feature. For instance, "IPsec input" feature may
+ * consume/drop all packets with "Protect" policy action while all packets with
+ * policy action as "Bypass" may be forwarded to next enabled feature (with in
+ * same feature arc)
+ *
+ * This library facilitates rte graph based applications to steer packets in
+ * fast path to different feature nodes with-in a feature arc and support all
+ * functionalities described above
+ *
+ * In order to use feature-arc APIs, applications needs to do following in
+ * control path:
+ * - Initialize feature arc library via rte_graph_feature_arc_init()
+ * - Create feature arc via rte_graph_feature_arc_create()
+ * - *Before calling rte_graph_create()*, features must be added to feature-arc
+ * via rte_graph_feature_add(). rte_graph_feature_add() allows adding
+ * features in a sequential order with "runs_after" and "runs_before"
+ * constraints.
+ * - Post rte_graph_create(), features can be enabled/disabled at runtime on
+ * any interface via rte_graph_feature_enable()/rte_graph_feature_disable()
+ * - Feature arc can be destroyed via rte_graph_feature_arc_destroy()
+ *
+ * In fast path, APIs are provided to steer packets towards feature path from
+ * - start_node (provided as an argument to rte_graph_feature_arc_create())
+ * - feature nodes (which are added via rte_graph_feature_add())
+ *
+ * For typical steering of packets across feature nodes, application required
+ * to know "rte_edges" which are saved in feature data object. Feature data
+ * object is unique for every interface per feature with in a feature arc.
+ *
+ * When steering packets from start_node to feature node:
+ * - rte_graph_feature_arc_first_feature_get() provides first enabled feature.
+ * - Next rte_edge from start_node to first enabled feature can be obtained via
+ * rte_graph_feature_arc_feature_set()
+ *
+ * rte_mbuf can carry [current feature, index] from start_node of an arc to other
+ * feature nodes
+ *
+ * In feature node, application can get 32-bit user_data
+ * via_rte_graph_feature_user_data_get() which is provided in
+ * rte_graph_feature_enable(). User data can hold feature specific cookie like
+ * IPsec policy database index (if more than one are supported)
+ *
+ * If feature node is not consuming packet, next enabled feature and next
+ * rte_edge can be obtained via rte_graph_feature_arc_next_feature_get()
+ *
+ * It is application responsibility to ensure that at-least *last feature*(or sink
+ * feature) must be enabled from where packet can exit feature-arc path, if
+ * *NO* intermediate feature is consuming the packet and it has reached till
+ * the end of feature arc path
+ *
+ * It is recommended that all features *MUST* be added to feature arc before calling
+ * `rte_graph_create()`. Addition of features after `rte_graph_create()` may
+ * not work functionally. Although,rte_graph_feature_enable()/rte_graph_feature_disable()
+ * should be called after `rte_graph_create()` in control plane.
+ *
+ * Synchronization among cores
+ * ---------------------------
+ * Subsequent calls to rte_graph_feature_enable() is allowed while worker cores
+ * are processing in rte_graph_walk() loop. However, for
+ * rte_graph_feature_disable() application must use RCU based synchronization
+ */
+
+/** Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_ARC_INITIALIZER ((rte_graph_feature_arc_t)UINT64_MAX)
+
+/** Max number of feature arcs which can be created */
+#define RTE_GRAPH_FEATURE_ARC_MAX 64
+
+/** Max number of features supported in a given feature arc */
+#define RTE_GRAPH_FEATURE_MAX_PER_ARC 64
+
+/** Length of feature arc name */
+#define RTE_GRAPH_FEATURE_ARC_NAMELEN RTE_NODE_NAMESIZE
+
+/** @internal */
+#define rte_graph_feature_cast(x) ((rte_graph_feature_t)x)
+
+/**< Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_INVALID rte_graph_feature_cast(UINT8_MAX)
+
+/** rte_graph feature arc object */
+typedef uint64_t rte_graph_feature_arc_t;
+
+/** rte_graph feature object */
+typedef uint8_t rte_graph_feature_t;
+
+/** runtime active feature list index with in feature arc*/
+typedef uint16_t rte_graph_feature_rt_list_t;
+
+/** per feature arc monotonically increasing counter to synchronize fast path APIs */
+typedef uint16_t rte_graph_feature_counter_t;
+
+/**
+ * Initialize feature arc subsystem
+ *
+ * @param max_feature_arcs
+ * Maximum number of feature arcs required to be supported
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_init(int max_feature_arcs);
+
+/**
+ * Create a feature arc
+ *
+ * @param feature_arc_name
+ * Feature arc name with max length of @ref RTE_GRAPH_FEATURE_ARC_NAMELEN
+ * @param max_features
+ * Maximum number of features to be supported in this feature arc
+ * @param max_indexes
+ * Maximum number of interfaces/ports/indexes to be supported
+ * @param start_node
+ * Base node where this feature arc's features are checked in fast path
+ * @param[out] _arc
+ * Feature arc object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node,
+ rte_graph_feature_arc_t *_arc);
+
+/**
+ * Get feature arc object with name
+ *
+ * @param arc_name
+ * 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
+ *
+ * @return
+ * 0: Success
+ * <0: Failure.
+ */
+__rte_experimental
+int rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc);
+
+/**
+ * Add a feature to already created feature arc. For instance
+ *
+ * 1. Add first feature node: "ipv4-input" to input arc
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-input", NULL, NULL);
+ *
+ * 2. Add "ipsec-input" feature node after "ipv4-input" feature
+ * rte_graph_feature_add(ipv4_input_arc, "ipsec-input", "ipv4-input", NULL);
+ *
+ * 3. Add "ipv4-pre-classify-input" node before "ipv4-input" feature
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-pre-classify-input"", NULL, "ipv4-input");
+ *
+ * 4. Add "acl-classify-input" node after ipv4-input but before ipsec-input
+ * rte_graph_feature_add(ipv4_input_arc, "acl-classify-input", "ipv4-input", "ipsec-input");
+ *
+ * @param _arc
+ * Feature arc handle returned from @ref rte_graph_feature_arc_create()
+ * @param feature_node
+ * Graph node representing feature. On success, feature_node is next_node of
+ * feature_arc->start_node
+ * @param runs_after
+ * Add this feature_node after already added "runs_after". Creates
+ * start_node -> runs_after -> this_feature sequence
+ * @param runs_before
+ * Add this feature_node before already added "runs_before". Creates
+ * start_node -> this_feature -> runs_before sequence
+ *
+ * <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>
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *runs_after, const char *runs_before);
+
+/**
+ * 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 user_data
+ * Application specific data which is retrieved in fast path
+ *
+ * @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,
+ int32_t user_data);
+
+/**
+ * Validate whether subsequent enable/disable feature would succeed or not.
+ * API is 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 is_enable_disable
+ * If 1, validate whether subsequent @ref rte_graph_feature_enable would pass or not
+ * If 0, validate whether subsequent @ref rte_graph_feature_disable would pass or not
+ * @param emit_logs
+ * If passed true, emit error logs when failure is returned
+ * If passed false, do not emit error logs when failure is returned
+ *
+ * @return
+ * 0: Subsequent enable/disable API would pass
+ * <0: Subsequent enable/disable API would not pass
+ */
+__rte_experimental
+int rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index,
+ const char *feature_name, int is_enable_disable, bool emit_logs);
+
+/**
+ * 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
+ *
+ * @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);
+
+/**
+ * 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
+ * @param feature_name
+ * Feature name provided to @ref rte_graph_feature_add
+ * @param[out] feature
+ * Feature object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_lookup(rte_graph_feature_arc_t arc, const char *feature_name,
+ rte_graph_feature_t *feature);
+
+/**
+ * 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
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_destroy(rte_graph_feature_arc_t _arc);
+
+/**
+ * Cleanup all feature arcs
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_cleanup(void);
+
+/**
+ * Slow path API to know how many features are added (NOT enabled) within a
+ * feature arc
+ *
+ * @param _arc
+ * Feature arc object
+ *
+ * @return: Number of added features to arc
+ */
+__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
+ *
+ * @param _arc
+ * Feature arc object
+ * @param feature
+ * Feature object
+ *
+ * @return: Name of the feature node
+ */
+__rte_experimental
+char *rte_graph_feature_arc_feature_to_name(rte_graph_feature_arc_t _arc,
+ rte_graph_feature_t feature);
+
+/**
+ * Slow path API to get corresponding struct rte_node_register * from
+ * rte_graph_feature_t
+ *
+ * @param _arc
+ * Feature arc object
+ * @param feature
+ * Feature object
+ *
+ * @return: struct rte_node_register * of feature node on SUCCESS else NULL
+ */
+__rte_experimental
+struct rte_node_register *
+rte_graph_feature_arc_feature_to_node(rte_graph_feature_arc_t _arc,
+ rte_graph_feature_t feature);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/graph/rte_graph_feature_arc_worker.h b/lib/graph/rte_graph_feature_arc_worker.h
new file mode 100644
index 0000000000..5c76cb1151
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc_worker.h
@@ -0,0 +1,672 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+#define _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+
+#include <stddef.h>
+#include <rte_graph_feature_arc.h>
+#include <rte_bitops.h>
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc_worker.h
+ *
+ * Defines fast path structure
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @internal
+ *
+ * Slow path feature node info list
+ */
+struct rte_graph_feature_node_list {
+ /** Next feature */
+ STAILQ_ENTRY(rte_graph_feature_node_list) next_feature;
+
+ /** node representing feature */
+ struct rte_node_register *feature_node;
+
+ /** How many indexes/interfaces using this feature */
+ int32_t ref_count;
+
+ /* node_index in list (after feature_enable())*/
+ uint32_t node_index;
+
+ /** Back pointer to feature arc */
+ void *feature_arc;
+
+ /** rte_edge_t to this feature node from feature_arc->start_node */
+ rte_edge_t edge_to_this_feature;
+};
+
+/**
+ * Feature data object:
+ *
+ * Feature data stores information to steer packets for:
+ * - a feature with in feature arc
+ * - Index i.e. Port/Interface index
+ *
+ * Each feature data object holds
+ * - User data of current feature retrieved via rte_graph_feature_user_data_get()
+ * - next_edge is used in two conditions when packet to be steered from
+ * -- start_node to first enabled feature on an interface index
+ * -- current feature node to next enabled feature on an interface index
+ * - next_enabled_feature on interface index, if current feature is not
+ * consuming packet
+ *
+ * While user_data corresponds to current enabled feature node, while
+ * next_edge and next_enabled_feature corresponds to be next enabled feature
+ * node on an interface index
+ *
+ * First enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_first_feature_get() if arc's start_node is trying to
+ * steer packet from start_node to first enabled feature on interface index
+ *
+ * Next enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_next_feature_get() if current node is not arc's
+ * start_node. Input to rte_graph_feature_next_feature_get() is current
+ * enabled feature and interface index
+ */
+typedef struct __rte_packed rte_graph_feature_data {
+ /** edge from current node to next enabled feature */
+ rte_edge_t next_edge;
+
+ union {
+ uint16_t reserved;
+ struct {
+ /** next enabled feature on index from current feature */
+ rte_graph_feature_t next_enabled_feature;
+ };
+ };
+
+ /** user_data set by application in rte_graph_feature_enable() for
+ * - current feature
+ * - interface index
+ */
+ int32_t user_data;
+} rte_graph_feature_data_t;
+
+/**
+ * Feature object
+ *
+ * Feature object holds feature data object for every index/interface within
+ * feature
+ *
+ * Within a given arc and interface index, first feature object can be
+ * retrieved in arc's start_node via:
+ * - rte_graph_feature_arc_first_feature_get()
+ *
+ * Feature data information can be retrieved for first feature in start node via
+ * - rte_graph_feature_arc_feature_set()
+ *
+ * Next enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_arc_next_feature_get()
+ *
+ * Typically application stores rte_graph_feature_t object in rte_mbuf.
+ * rte_graph_feature_t can be translated to (struct rte_graph_feature *) via
+ * rte_graph_feature_get() in fast path. Further if needed, feature data for an
+ * index within a feature can be retrieved via rte_graph_feature_data_get()
+ */
+struct __rte_cache_aligned rte_graph_feature {
+ /** feature index or rte_graph_feature_t */
+ uint16_t this_feature_index;
+
+ /*
+ * Array of size arc->feature_data_size
+ *
+ * <----------------- Feature -------------------------->
+ * [data-index-0][data-index-1]...[data-index-max_index-1]
+ *
+ * sizeof(feature_data_by_index[0] == sizeof(rte_graph_feature_data_t)
+ *
+ */
+ uint8_t feature_data_by_index[];
+};
+
+/**
+ * Feature list object
+ *
+ * Feature list is required to decouple fast path APIs with control path APIs.
+ *
+ * There are two feature lists: active, passive
+ *
+ * While fast path APIs always work on active list, control plane updates
+ * passive lists and atomically switch passive list to active list to make it
+ * available to fast path APIs
+ *
+ * Each feature node in start of it's fast path function, must grab active list from
+ * arc via
+ * - rte_graph_feature_arc_has_any_feature() or
+ * rte_graph_feature_arc_has_feature()
+ *
+ * Retrieved list must be provided to other feature arc fast path APIs so that
+ * any control plane changes of active list should not impact current node
+ * execution iteration. Active list change would be reflected to current node
+ * in next iteration
+ *
+ * With active/passive list mechanism and integrating RCU APIs in graph worker
+ * loop, application can update features at runtime without stopping fast path
+ * cores. A RCU synchronization is required when a feature needs to be disabled via
+ * rte_graph_feature_disable(). On enabling a feature, RCU synchronization may
+ * not be required
+ *
+ */
+typedef struct __rte_cache_aligned rte_graph_feature_list {
+ /**
+ * fast path array holding per_feature data.
+ * Duplicate entry as feature-arc also hold this pointer
+ * arc->features[]
+ *
+ *<-------------feature-0 ---------><---------feature-1 -------------->...
+ *[index-0][index-1]...[max_index-1]<-ALIGN->[index-0][index-1] ...[max_index-1]...
+ */
+ struct rte_graph_feature *indexed_by_features;
+ /*
+ * fast path array holding first enabled feature per index
+ * (Required in start_node. In non start_node, mbuf can hold next enabled
+ * feature)
+ */
+ rte_graph_feature_t first_enabled_feature_by_index[];
+} rte_graph_feature_list_t;
+
+/**
+ * rte_graph Feature arc object
+ *
+ * Feature arc object holds control plane and fast path information for all
+ * features and all interface index information for steering packets across
+ * feature nodes
+ *
+ * Within a feature arc, only RTE_GRAPH_FEATURE_MAX_PER_ARC features can be
+ * added. If more features needs to be added, another feature arc can be
+ * created
+ *
+ * Application gets rte_graph_feature_arc_t object via
+ * - rte_graph_feature_arc_create() OR
+ * - rte_graph_feature_arc_lookup_by_name()
+ *
+ * In fast path, rte_graph_feature_arc_t can be translated to (struct
+ * rte_graph_feature_arc *) via rte_graph_feature_arc_get(). Later is needed to
+ * add as an input argument to all fast path feature arc APIs
+ */
+struct __rte_cache_aligned rte_graph_feature_arc {
+ /* First 64B is fast path variables */
+ RTE_MARKER fast_path_variables;
+
+ /** runtime active feature list */
+ rte_graph_feature_rt_list_t active_feature_list;
+
+ /** Actual Size of feature_list object */
+ uint16_t feature_list_size;
+
+ /**
+ * Size each feature in fastpath.
+ * Required to navigate from feature to another feature in fast path
+ */
+ uint16_t feature_size;
+
+ /**
+ * Size of all feature data for an index
+ * Required to navigate through various feature data within a feature
+ * in fast path
+ */
+ uint16_t feature_data_size;
+
+ /**
+ * Quick fast path bitmask indicating if any feature enabled or not on
+ * any of the indexes. Helps in optimally process packets for the case
+ * when features are added but not enabled
+ *
+ * Separate for active and passive list
+ */
+ uint64_t feature_enable_bitmask[2];
+
+ /**
+ * Pointer to both active and passive feature list object
+ */
+ rte_graph_feature_list_t *feature_list[2];
+
+ /**
+ * Feature objects for each list
+ */
+ struct rte_graph_feature *features[2];
+
+ /** index in feature_arc_main */
+ uint16_t feature_arc_index;
+
+ uint16_t reserved[3];
+
+ /** Slow path variables follows*/
+ RTE_MARKER slow_path_variables;
+
+ /** feature arc name */
+ char feature_arc_name[RTE_GRAPH_FEATURE_ARC_NAMELEN];
+
+ /** All feature lists */
+ STAILQ_HEAD(, rte_graph_feature_node_list) all_features;
+
+ /** control plane counter to track enabled features */
+ uint32_t runtime_enabled_features;
+
+ /** Back pointer to feature_arc_main */
+ void *feature_arc_main;
+
+ /** Arc's start/base node */
+ struct rte_node_register *start_node;
+
+ /** maximum number of features supported by this arc */
+ uint32_t max_features;
+
+ /** maximum number of index supported by this arc */
+ uint32_t max_indexes;
+
+ /** Slow path bit mask per feature per index */
+ uint64_t feature_bit_mask_by_index[];
+};
+
+/**
+ * Feature arc main object
+ *
+ * Holds all feature arcs created by application
+ *
+ * RTE_GRAPH_FEATURE_ARC_MAX number of feature arcs can be created by
+ * application via rte_graph_feature_arc_create()
+ */
+typedef struct feature_arc_main {
+ /** number of feature arcs created by application */
+ uint32_t num_feature_arcs;
+
+ /** max features arcs allowed */
+ uint32_t max_feature_arcs;
+
+ /** feature arcs */
+ rte_graph_feature_arc_t feature_arcs[];
+} rte_graph_feature_arc_main_t;
+
+/** @internal Get feature arc pointer from object */
+#define rte_graph_feature_arc_get(arc) ((struct rte_graph_feature_arc *)arc)
+
+extern rte_graph_feature_arc_main_t *__feature_arc_main;
+
+/**
+ * API to know if feature is valid or not
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_is_valid(rte_graph_feature_t feature)
+{
+ return (feature != RTE_GRAPH_FEATURE_INVALID);
+}
+
+/**
+ * Get rte_graph_feature object with no checks
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ * @param feature_list
+ * active feature list retrieved from rte_graph_feature_arc_has_any_feature()
+ * or rte_graph_feature_arc_has_feature()
+ *
+ * @return
+ * Internal feature object.
+ */
+__rte_experimental
+static __rte_always_inline struct rte_graph_feature *
+__rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature,
+ const rte_graph_feature_rt_list_t feature_list)
+{
+ return ((struct rte_graph_feature *)(((uint8_t *)arc->features[feature_list]) +
+ (feature * arc->feature_size)));
+}
+
+/**
+ * Get rte_graph_feature object for a given interface/index from feature arc
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ *
+ * @return
+ * Internal feature object.
+ */
+__rte_experimental
+static __rte_always_inline struct rte_graph_feature *
+rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature)
+{
+ if (unlikely(feature >= arc->max_features))
+ RTE_VERIFY(0);
+
+ if (likely(rte_graph_feature_is_valid(feature)))
+ return __rte_graph_feature_get(arc, feature, arc->active_feature_list);
+
+ return NULL;
+}
+
+__rte_experimental
+static __rte_always_inline rte_graph_feature_data_t *
+__rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ RTE_SET_USED(arc);
+ return ((rte_graph_feature_data_t *)(((uint8_t *)feature->feature_data_by_index) +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Get rte_graph feature data object for a index in feature
+ *
+ * @param arc
+ * feature arc
+ * @param feature
+ * Pointer to feature object
+ * @param index
+ * Index of feature maintained in slow path linked list
+ *
+ * @return
+ * Valid feature data
+ */
+__rte_experimental
+static __rte_always_inline rte_graph_feature_data_t *
+rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ if (likely(index < arc->max_indexes))
+ return __rte_graph_feature_data_get(arc, feature, index);
+
+ RTE_VERIFY(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
+ * @param[out] plist
+ * Pointer to runtime active feature list which needs to be provided to other
+ * fast path APIs
+ *
+ * @return
+ * 0: If no feature enabled
+ * Non-Zero: Bitmask of features enabled. plist is valid
+ *
+ */
+__rte_experimental
+static __rte_always_inline uint64_t
+rte_graph_feature_arc_has_any_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_rt_list_t *plist)
+{
+ *plist = rte_atomic_load_explicit(&arc->active_feature_list, rte_memory_order_relaxed);
+
+ return (rte_atomic_load_explicit(arc->feature_enable_bitmask + (uint8_t)*plist,
+ rte_memory_order_relaxed));
+}
+
+/**
+ * Fast path API to check if provided feature is enabled on any interface/index
+ * or not
+ *
+ * @param arc
+ * Feature arc object
+ * @param feature
+ * Input rte_graph_feature_t that needs to be checked
+ * @param[out] plist
+ * Returns active list to caller which needs to be provided to other fast path
+ * APIs
+ *
+ * @return
+ * 1: If input [feature] is enabled in arc
+ * 0: If input [feature] is not enabled in arc
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_has_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_t feature,
+ rte_graph_feature_rt_list_t *plist)
+{
+ uint64_t bitmask = RTE_BIT64(feature);
+
+ *plist = rte_atomic_load_explicit(&arc->active_feature_list, rte_memory_order_relaxed);
+
+ return (bitmask & rte_atomic_load_explicit(arc->feature_enable_bitmask + (uint8_t)*plist,
+ 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 related fast path cache line
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object
+ */
+__rte_experimental
+static __rte_always_inline void
+rte_graph_feature_arc_feature_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature)
+{
+ /* feature cache line */
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)__rte_graph_feature_get(arc, feature, list));
+}
+
+/**
+ * Prefetch feature data upfront. Perform sanity
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object returned from @ref
+ * rte_graph_feature_arc_first_feature_get()
+ * @param index
+ * Interface/index
+ */
+__rte_experimental
+static __rte_always_inline void
+rte_graph_feature_arc_data_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature, uint32_t index)
+{
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)((uint8_t *)arc->features[list] +
+ offsetof(struct rte_graph_feature, feature_data_by_index) +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Fast path API to get first enabled feature on interface index
+ * Typically required in arc->start_node so that from returned feature,
+ * feature-data can be retrieved to steer packets
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * rte_graph_feature_arc_has_feature()
+ * @param index
+ * Interface Index
+ * @param[out] feature
+ * Pointer to rte_graph_feature_t.
+ *
+ * @return
+ * 1. Success. If first feature field is enabled and returned [feature] is valid
+ * 0. Failure. If first feature field is disabled in arc
+ *
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_first_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+
+ *feature = feature_list->first_enabled_feature_by_index[index];
+
+ return rte_graph_feature_is_valid(*feature);
+}
+
+/**
+ * Fast path API to get next enabled feature on interface index with provided
+ * input feature
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * @param index
+ * Interface Index
+ * @param[out] feature
+ * Pointer to rte_graph_feature_t. API sets next enabled feature on [index]
+ * from provided input feature. Valid only if API returns Success
+ * @param[out] next_edge
+ * Edge from current feature to next feature. Valid only if next feature is valid
+ *
+ * @return
+ * 1. Success. first feature field is enabled/valid
+ * 0. Failure. first feature field is disabled/invalid
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_next_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature,
+ rte_edge_t *next_edge)
+{
+ rte_graph_feature_data_t *feature_data = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(*feature))) {
+ f = __rte_graph_feature_get(arc, *feature, list);
+ feature_data = rte_graph_feature_data_get(arc, f, index);
+ *feature = feature_data->next_enabled_feature;
+ *next_edge = feature_data->next_edge;
+ return rte_graph_feature_is_valid(*feature);
+ }
+
+ return 0;
+}
+
+/**
+ * Set fields with respect to first enabled feature in an arc and return edge
+ * Typically returned feature and interface index must be saved in rte_mbuf
+ * structure to pass this information to next feature node
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param index
+ * Index (of interface)
+ * @param[out] gf
+ * Pointer to rte_graph_feature_t. Valid if API returns Success
+ * @param[out] edge
+ * Edge to steer packet from arc->start_node to first enabled feature. Valid
+ * only if API returns Success
+ *
+ * @return
+ * 0: If valid feature is enabled and set by API in *gf
+ * 1: If valid feature is NOT enabled
+ */
+__rte_experimental
+static __rte_always_inline rte_graph_feature_t
+rte_graph_feature_arc_feature_set(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *gf,
+ rte_edge_t *edge)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+ struct rte_graph_feature_data *feature_data = NULL;
+ struct rte_graph_feature *feature = NULL;
+ rte_graph_feature_t f;
+
+ f = feature_list->first_enabled_feature_by_index[index];
+
+ if (unlikely(rte_graph_feature_is_valid(f))) {
+ feature = __rte_graph_feature_get(arc, f, list);
+ feature_data = rte_graph_feature_data_get(arc, feature, index);
+ *gf = f;
+ *edge = feature_data->next_edge;
+ return 0;
+ }
+
+ return 1;
+}
+
+__rte_experimental
+static __rte_always_inline int32_t
+__rte_graph_feature_user_data_get(rte_graph_feature_data_t *fdata)
+{
+ return fdata->user_data;
+}
+
+/**
+ * Get user data corresponding to current feature set by application in
+ * rte_graph_feature_enable()
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Feature index
+ * @param index
+ * Interface index
+ *
+ * @return
+ * -1: Failure
+ * Valid user data: Success
+ */
+__rte_experimental
+static __rte_always_inline int32_t
+rte_graph_feature_user_data_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature,
+ uint32_t index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(feature))) {
+ f = __rte_graph_feature_get(arc, feature, list);
+ fdata = rte_graph_feature_data_get(arc, f, index);
+ return __rte_graph_feature_user_data_get(fdata);
+ }
+
+ return -1;
+}
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/graph/version.map b/lib/graph/version.map
index 2c83425ddc..3b7f475afd 100644
--- a/lib/graph/version.map
+++ b/lib/graph/version.map
@@ -52,3 +52,23 @@ DPDK_25 {
local: *;
};
+
+EXPERIMENTAL {
+ global:
+
+ # added in 24.11
+ rte_graph_feature_arc_init;
+ rte_graph_feature_arc_create;
+ rte_graph_feature_arc_lookup_by_name;
+ rte_graph_feature_add;
+ rte_graph_feature_enable;
+ rte_graph_feature_validate;
+ rte_graph_feature_disable;
+ rte_graph_feature_lookup;
+ rte_graph_feature_arc_destroy;
+ rte_graph_feature_arc_cleanup;
+ rte_graph_feature_arc_num_enabled_features;
+ rte_graph_feature_arc_num_features;
+ rte_graph_feature_arc_feature_to_name;
+ rte_graph_feature_arc_feature_to_node;
+};
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 2/5] graph: add feature arc option in graph create
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 1/5] graph: add feature arc support Nitin Saxena
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 3/5] graph: add IPv4 output feature arc Nitin Saxena
` (3 subsequent siblings)
5 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena, Pavan Nikhilesh
Added option in graph create to call feature-specific process node
functions. This removes extra overhead for checking feature arc status
in nodes where application is not using feature arc processing
Signed-off-by: Pavan Nikhilesh <pbhagavatula@marvell.com>
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
lib/graph/graph.c | 1 +
lib/graph/graph_populate.c | 7 ++++++-
lib/graph/graph_private.h | 3 +++
lib/graph/node.c | 2 ++
lib/graph/rte_graph.h | 3 +++
5 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/lib/graph/graph.c b/lib/graph/graph.c
index d5b8c9f918..b0ad3a83ae 100644
--- a/lib/graph/graph.c
+++ b/lib/graph/graph.c
@@ -455,6 +455,7 @@ rte_graph_create(const char *name, struct rte_graph_param *prm)
graph->parent_id = RTE_GRAPH_ID_INVALID;
graph->lcore_id = RTE_MAX_LCORE;
graph->num_pkt_to_capture = prm->num_pkt_to_capture;
+ graph->feature_arc_enabled = prm->feature_arc_enable;
if (prm->pcap_filename)
rte_strscpy(graph->pcap_filename, prm->pcap_filename, RTE_GRAPH_PCAP_FILE_SZ);
diff --git a/lib/graph/graph_populate.c b/lib/graph/graph_populate.c
index ed596a7711..5d8aa7b903 100644
--- a/lib/graph/graph_populate.c
+++ b/lib/graph/graph_populate.c
@@ -79,8 +79,13 @@ graph_nodes_populate(struct graph *_graph)
if (graph_pcap_is_enable()) {
node->process = graph_pcap_dispatch;
node->original_process = graph_node->node->process;
- } else
+ if (_graph->feature_arc_enabled && graph_node->node->feat_arc_proc)
+ node->original_process = graph_node->node->feat_arc_proc;
+ } else {
node->process = graph_node->node->process;
+ if (_graph->feature_arc_enabled && graph_node->node->feat_arc_proc)
+ node->process = graph_node->node->feat_arc_proc;
+ }
memcpy(node->name, graph_node->node->name, RTE_GRAPH_NAMESIZE);
pid = graph_node->node->parent_id;
if (pid != RTE_NODE_ID_INVALID) { /* Cloned node */
diff --git a/lib/graph/graph_private.h b/lib/graph/graph_private.h
index d557d55f2d..58ba0abeff 100644
--- a/lib/graph/graph_private.h
+++ b/lib/graph/graph_private.h
@@ -56,6 +56,7 @@ struct node {
unsigned int lcore_id;
/**< Node runs on the Lcore ID used for mcore dispatch model. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Allocated identifier for the node. */
@@ -126,6 +127,8 @@ struct graph {
/**< Number of packets to be captured per core. */
char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
/**< pcap file name/path. */
+ uint8_t feature_arc_enabled;
+ /**< Graph feature arc. */
STAILQ_HEAD(gnode_list, graph_node) node_list;
/**< Nodes in a graph. */
};
diff --git a/lib/graph/node.c b/lib/graph/node.c
index 99a9622779..d8fd273543 100644
--- a/lib/graph/node.c
+++ b/lib/graph/node.c
@@ -90,6 +90,7 @@ __rte_node_register(const struct rte_node_register *reg)
goto free;
node->flags = reg->flags;
node->process = reg->process;
+ node->feat_arc_proc = reg->feat_arc_proc;
node->init = reg->init;
node->fini = reg->fini;
node->nb_edges = reg->nb_edges;
@@ -137,6 +138,7 @@ node_clone(struct node *node, const char *name)
/* Clone the source node */
reg->flags = node->flags;
reg->process = node->process;
+ reg->feat_arc_proc = node->feat_arc_proc;
reg->init = node->init;
reg->fini = node->fini;
reg->nb_edges = node->nb_edges;
diff --git a/lib/graph/rte_graph.h b/lib/graph/rte_graph.h
index ecfec2068a..ebbdbbea48 100644
--- a/lib/graph/rte_graph.h
+++ b/lib/graph/rte_graph.h
@@ -163,6 +163,8 @@ struct rte_graph_param {
uint64_t num_pkt_to_capture; /**< Number of packets to capture. */
char *pcap_filename; /**< Filename in which packets to be captured.*/
+ bool feature_arc_enable; /**< Enable Graph feature arc. */
+
union {
struct {
uint64_t rsvd; /**< Reserved for rtc model. */
@@ -470,6 +472,7 @@ struct rte_node_register {
uint64_t flags; /**< Node configuration flag. */
#define RTE_NODE_SOURCE_F (1ULL << 0) /**< Node type is source. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Node Identifier. */
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 3/5] graph: add IPv4 output feature arc
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 1/5] graph: add feature arc support Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 2/5] graph: add feature arc option in graph create Nitin Saxena
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 4/5] test/graph_feature_arc: add functional tests Nitin Saxena
` (2 subsequent siblings)
5 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
add ipv4-output feature arc in ipv4-rewrite node to allow
custom/standard nodes(like outbound IPsec policy node) in outgoing
forwarding path
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
lib/node/ip4_rewrite.c | 476 +++++++++++++++++++++++++++++-------
lib/node/ip4_rewrite_priv.h | 15 +-
lib/node/node_private.h | 20 +-
lib/node/rte_node_ip4_api.h | 3 +
4 files changed, 417 insertions(+), 97 deletions(-)
diff --git a/lib/node/ip4_rewrite.c b/lib/node/ip4_rewrite.c
index 34a920df5e..5a160335f2 100644
--- a/lib/node/ip4_rewrite.c
+++ b/lib/node/ip4_rewrite.c
@@ -15,39 +15,156 @@
#include "ip4_rewrite_priv.h"
#include "node_private.h"
+#define ALL_PKT_MASK 0xf
+
struct ip4_rewrite_node_ctx {
+ rte_graph_feature_arc_t output_feature_arc;
/* Dynamic offset to mbuf priv1 */
int mbuf_priv1_off;
/* Cached next index */
uint16_t next_index;
+ uint16_t last_tx;
};
+typedef struct rewrite_priv_vars {
+ union {
+ struct {
+ rte_xmm_t xmm1;
+ };
+ struct __rte_packed {
+ uint16_t next0;
+ uint16_t next1;
+ uint16_t next2;
+ uint16_t next3;
+ uint16_t last_tx_interface;
+ uint16_t last_if_feature;
+ uint16_t actual_feat_mask;
+ uint16_t speculative_feat_mask;
+ };
+ };
+} rewrite_priv_vars_t;
+
static struct ip4_rewrite_node_main *ip4_rewrite_nm;
#define IP4_REWRITE_NODE_LAST_NEXT(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->next_index)
+#define IP4_REWRITE_NODE_LAST_TX(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->last_tx)
+
#define IP4_REWRITE_NODE_PRIV1_OFF(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->mbuf_priv1_off)
-static uint16_t
-ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
- void **objs, uint16_t nb_objs)
+#define IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->output_feature_arc)
+
+static __rte_always_inline void
+prefetch_mbuf_and_dynfield(struct rte_mbuf *mbuf)
{
+ /* prefetch first cache line required for accessing buf_addr */
+ rte_prefetch0((void *)mbuf);
+}
+
+static __rte_always_inline void
+check_output_feature_x4(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t flist,
+ rewrite_priv_vars_t *pvar, struct node_mbuf_priv1 *priv0,
+ struct node_mbuf_priv1 *priv1, struct node_mbuf_priv1 *priv2,
+ struct node_mbuf_priv1 *priv3)
+{
+ uint32_t mask = 0;
+ uint16_t xor = 0;
+
+ /*
+ * interface edge's start from 1 and not from 0 as "pkt_drop"
+ * is next node at 0th index
+ */
+ priv0->if_index = pvar->next0 - 1;
+ priv1->if_index = pvar->next1 - 1;
+ priv2->if_index = pvar->next2 - 1;
+ priv3->if_index = pvar->next3 - 1;
+
+ /* Find out if all packets are sent to last_tx_interface */
+ xor = pvar->last_tx_interface ^ priv0->if_index;
+ xor += priv0->if_index ^ priv1->if_index;
+ xor += priv1->if_index ^ priv2->if_index;
+ xor += priv2->if_index ^ priv3->if_index;
+
+ if (likely(!xor)) {
+ /* copy last interface feature and feature mask */
+ priv0->current_feature = priv1->current_feature =
+ priv2->current_feature = priv3->current_feature =
+ pvar->last_if_feature;
+ pvar->actual_feat_mask = pvar->speculative_feat_mask;
+ } else {
+ /* create a mask for index which does not have feature
+ * Also override next edge and if feature enabled, get feature
+ */
+ mask = rte_graph_feature_arc_feature_set(arc, flist, priv0->if_index,
+ &priv0->current_feature,
+ &pvar->next0);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv1->if_index,
+ &priv1->current_feature,
+ &pvar->next1)) << 1);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv2->if_index,
+ &priv2->current_feature,
+ &pvar->next2)) << 2);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv3->if_index,
+ &priv3->current_feature,
+ &pvar->next3)) << 3);
+
+ /*
+ * add last tx and last feature regardless even if feature is
+ * valid or not
+ */
+ pvar->last_tx_interface = priv3->if_index;
+ pvar->last_if_feature = priv3->current_feature;
+ /* Set 0xf if invalid feature to last packet, else 0 */
+ pvar->speculative_feat_mask = (priv3->current_feature ==
+ RTE_GRAPH_FEATURE_INVALID) ? ALL_PKT_MASK : 0x0;
+ pvar->actual_feat_mask = mask;
+ }
+}
+
+static __rte_always_inline uint16_t
+__ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs,
+ const int dyn, const int check_enabled_features,
+ struct rte_graph_feature_arc *out_feature_arc,
+ const rte_graph_feature_rt_list_t flist)
+{
+ struct node_mbuf_priv1 *priv0 = NULL, *priv1 = NULL, *priv2 = NULL, *priv3 = NULL;
struct rte_mbuf *mbuf0, *mbuf1, *mbuf2, *mbuf3, **pkts;
struct ip4_rewrite_nh_header *nh = ip4_rewrite_nm->nh;
- const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
- uint16_t next0, next1, next2, next3, next_index;
- struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
uint16_t n_left_from, held = 0, last_spec = 0;
+ struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
+ rewrite_priv_vars_t pvar;
+ int64_t fd0, fd1, fd2, fd3;
+ rte_edge_t fix_spec = 0;
void *d0, *d1, *d2, *d3;
void **to_next, **from;
+ uint16_t next_index;
rte_xmm_t priv01;
rte_xmm_t priv23;
int i;
- /* Speculative next as last next */
+ RTE_SET_USED(fd0);
+ RTE_SET_USED(fd1);
+ RTE_SET_USED(fd2);
+ RTE_SET_USED(fd3);
+
+ /* Initialize speculative variables.*/
+
+ /* Last interface */
+ pvar.last_tx_interface = IP4_REWRITE_NODE_LAST_TX(node->ctx);
+ /*last next from node ctx*/
next_index = IP4_REWRITE_NODE_LAST_NEXT(node->ctx);
+ pvar.speculative_feat_mask = ALL_PKT_MASK;
+ pvar.actual_feat_mask = 0;
+
rte_prefetch0(nh);
pkts = (struct rte_mbuf **)objs;
@@ -55,20 +172,47 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
n_left_from = nb_objs;
for (i = 0; i < 4 && i < n_left_from; i++)
- rte_prefetch0(pkts[i]);
+ prefetch_mbuf_and_dynfield(pkts[i]);
/* Get stream for the speculated next node */
to_next = rte_node_next_stream_get(graph, node, next_index, nb_objs);
+
+ /* prefetch speculative feature and corresponding data */
+ if (check_enabled_features) {
+ /*
+ * Get first feature enabled, if any, on last_tx_interface
+ */
+ if (unlikely(rte_graph_feature_arc_first_feature_get(out_feature_arc,
+ flist,
+ pvar.last_tx_interface,
+ (rte_graph_feature_t *)
+ &pvar.last_if_feature))) {
+ /* prefetch feature cache line */
+ rte_graph_feature_arc_feature_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature);
+
+ /* prefetch feature data cache line */
+ rte_graph_feature_arc_data_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature,
+ pvar.last_tx_interface);
+ /*
+ * Set speculativa_feat mask to indicate, all 4 packets
+ * going to feature path
+ */
+ pvar.speculative_feat_mask = 0;
+ }
+ }
+
/* Update Ethernet header of pkts */
while (n_left_from >= 4) {
if (likely(n_left_from > 7)) {
/* Prefetch only next-mbuf struct and priv area.
* Data need not be prefetched as we only write.
*/
- rte_prefetch0(pkts[4]);
- rte_prefetch0(pkts[5]);
- rte_prefetch0(pkts[6]);
- rte_prefetch0(pkts[7]);
+ prefetch_mbuf_and_dynfield(pkts[4]);
+ prefetch_mbuf_and_dynfield(pkts[5]);
+ prefetch_mbuf_and_dynfield(pkts[6]);
+ prefetch_mbuf_and_dynfield(pkts[7]);
}
mbuf0 = pkts[0];
@@ -78,66 +222,138 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 4;
n_left_from -= 4;
+
+ /* Copy mbuf private data into private variables */
priv01.u64[0] = node_mbuf_priv1(mbuf0, dyn)->u;
priv01.u64[1] = node_mbuf_priv1(mbuf1, dyn)->u;
priv23.u64[0] = node_mbuf_priv1(mbuf2, dyn)->u;
priv23.u64[1] = node_mbuf_priv1(mbuf3, dyn)->u;
- /* Increment checksum by one. */
- priv01.u32[1] += rte_cpu_to_be_16(0x0100);
- priv01.u32[3] += rte_cpu_to_be_16(0x0100);
- priv23.u32[1] += rte_cpu_to_be_16(0x0100);
- priv23.u32[3] += rte_cpu_to_be_16(0x0100);
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
- nh[priv01.u16[0]].rewrite_len);
-
- next0 = nh[priv01.u16[0]].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- ip0->time_to_live = priv01.u16[1] - 1;
- ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
- d1 = rte_pktmbuf_mtod(mbuf1, void *);
- rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
- nh[priv01.u16[4]].rewrite_len);
-
- next1 = nh[priv01.u16[4]].tx_node;
- ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
- sizeof(struct rte_ether_hdr));
- ip1->time_to_live = priv01.u16[5] - 1;
- ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
- d2 = rte_pktmbuf_mtod(mbuf2, void *);
- rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
- nh[priv23.u16[0]].rewrite_len);
- next2 = nh[priv23.u16[0]].tx_node;
- ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
- sizeof(struct rte_ether_hdr));
- ip2->time_to_live = priv23.u16[1] - 1;
- ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
- d3 = rte_pktmbuf_mtod(mbuf3, void *);
- rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
- nh[priv23.u16[4]].rewrite_len);
-
- next3 = nh[priv23.u16[4]].tx_node;
- ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
- sizeof(struct rte_ether_hdr));
- ip3->time_to_live = priv23.u16[5] - 1;
- ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ /* Copy next edge from next hop */
+ pvar.next0 = nh[priv01.u16[0]].tx_node;
+ pvar.next1 = nh[priv01.u16[4]].tx_node;
+ pvar.next2 = nh[priv23.u16[0]].tx_node;
+ pvar.next3 = nh[priv23.u16[4]].tx_node;
+
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ priv1 = node_mbuf_priv1(mbuf1, dyn);
+ priv2 = node_mbuf_priv1(mbuf2, dyn);
+ priv3 = node_mbuf_priv1(mbuf3, dyn);
+
+ /* If feature is enabled, override next edge for each mbuf
+ * and set node_mbuf_priv data appropriately
+ */
+ check_output_feature_x4(out_feature_arc, flist,
+ &pvar, priv0, priv1, priv2, priv3);
+
+ /* check_output_feature_x4() returns bit mask which indicates
+ * which packet is not following feature path, hence normal processing
+ * has to happen on them
+ */
+ if (unlikely(pvar.actual_feat_mask)) {
+ if (pvar.actual_feat_mask & 0x1) {
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x2) {
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+ }
+ if (pvar.actual_feat_mask & 0x4) {
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x8) {
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
+ }
+ } else {
+ /* Case when no feature is enabled */
+
+ /* Increment checksum by one. */
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
/* Enqueue four to next node */
- rte_edge_t fix_spec =
- ((next_index == next0) && (next0 == next1) &&
- (next1 == next2) && (next2 == next3));
+ fix_spec = next_index ^ pvar.next0;
+ fix_spec += next_index ^ pvar.next1;
+ fix_spec += next_index ^ pvar.next2;
+ fix_spec += next_index ^ pvar.next3;
- if (unlikely(fix_spec == 0)) {
+ if (unlikely(fix_spec != 0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -146,56 +362,56 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
last_spec = 0;
/* next0 */
- if (next_index == next0) {
+ if (next_index == pvar.next0) {
to_next[0] = from[0];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next0,
+ rte_node_enqueue_x1(graph, node, pvar.next0,
from[0]);
}
/* next1 */
- if (next_index == next1) {
+ if (next_index == pvar.next1) {
to_next[0] = from[1];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next1,
+ rte_node_enqueue_x1(graph, node, pvar.next1,
from[1]);
}
/* next2 */
- if (next_index == next2) {
+ if (next_index == pvar.next2) {
to_next[0] = from[2];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next2,
+ rte_node_enqueue_x1(graph, node, pvar.next2,
from[2]);
}
/* next3 */
- if (next_index == next3) {
+ if (next_index == pvar.next3) {
to_next[0] = from[3];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next3,
+ rte_node_enqueue_x1(graph, node, pvar.next3,
from[3]);
}
from += 4;
/* Change speculation if last two are same */
- if ((next_index != next3) && (next2 == next3)) {
+ if ((next_index != pvar.next3) && (pvar.next2 == pvar.next3)) {
/* Put the current speculated node */
rte_node_next_stream_put(graph, node,
next_index, held);
held = 0;
/* Get next speculated stream */
- next_index = next3;
+ next_index = pvar.next3;
to_next = rte_node_next_stream_get(
graph, node, next_index, nb_objs);
}
@@ -212,20 +428,41 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 1;
n_left_from -= 1;
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
- nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
-
- next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
- rte_cpu_to_be_16(0x0100);
- chksum += chksum >= 0xffff;
- ip0->hdr_checksum = chksum;
- ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
-
- if (unlikely(next_index ^ next0)) {
+ pvar.next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ if (pvar.next0 != (pvar.last_tx_interface + 1)) {
+ priv0->if_index = pvar.next0 - 1;
+ rte_graph_feature_arc_feature_set(out_feature_arc, flist,
+ priv0->if_index,
+ &priv0->current_feature,
+ &pvar.next0);
+ pvar.last_tx_interface = priv0->if_index;
+ pvar.last_if_feature = priv0->current_feature;
+ } else {
+ /* current mbuf index is same as last_tx_interface */
+ priv0->if_index = pvar.last_tx_interface;
+ priv0->current_feature = pvar.last_if_feature;
+ }
+ }
+ /* Do the needful if either feature arc is disabled OR
+ * Invalid feature is present
+ */
+ if (!check_enabled_features ||
+ (priv0->current_feature == RTE_GRAPH_FEATURE_INVALID)) {
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
+ nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
+ rte_cpu_to_be_16(0x0100);
+ chksum += chksum >= 0xffff;
+ ip0->hdr_checksum = chksum;
+ ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
+ }
+ if (unlikely(next_index ^ pvar.next0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -233,13 +470,15 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
held += last_spec;
last_spec = 0;
- rte_node_enqueue_x1(graph, node, next0, from[0]);
+ rte_node_enqueue_x1(graph, node, pvar.next0, from[0]);
from += 1;
} else {
last_spec += 1;
}
}
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = pvar.last_tx_interface;
+
/* !!! Home run !!! */
if (likely(last_spec == nb_objs)) {
rte_node_next_stream_move(graph, node, next_index);
@@ -255,22 +494,78 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
return nb_objs;
}
+static uint16_t
+ip4_rewrite_feature_node_process(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(IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx));
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+ rte_graph_feature_rt_list_t flist;
+
+ /* If any feature is enabled on this arc */
+ if (unlikely(rte_graph_feature_arc_has_any_feature(arc, &flist))) {
+ if (flist)
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs,
+ dyn,
+ 1 /* check features */, arc,
+ (rte_graph_feature_rt_list_t)1);
+ else
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs,
+ dyn,
+ 1 /* check features */, arc,
+ (rte_graph_feature_rt_list_t)0);
+ } else {
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs, dyn,
+ 0/* don't check features*/, NULL,
+ 0/* don't care */);
+ }
+ return 0;
+}
+
+static uint16_t
+ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs, dyn,
+ 0/* don't check features*/, NULL,
+ 0/* don't care */);
+}
+
static int
ip4_rewrite_node_init(const struct rte_graph *graph, struct rte_node *node)
{
+ rte_graph_feature_arc_t feature_arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
static bool init_once;
RTE_SET_USED(graph);
RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_node_ctx) > RTE_NODE_CTX_SZ);
+ RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_nh_header) != RTE_CACHE_LINE_MIN_SIZE);
if (!init_once) {
node_mbuf_priv1_dynfield_offset = rte_mbuf_dynfield_register(
&node_mbuf_priv1_dynfield_desc);
if (node_mbuf_priv1_dynfield_offset < 0)
return -rte_errno;
- init_once = true;
+
+ /* Create ipv4-output feature arc, if not created
+ */
+ if (rte_graph_feature_arc_lookup_by_name(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ NULL) < 0) {
+ if (rte_graph_feature_arc_create(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ RTE_GRAPH_FEATURE_MAX_PER_ARC,
+ RTE_MAX_ETHPORTS,
+ ip4_rewrite_node_get(), &feature_arc)) {
+ return -rte_errno;
+ }
+ init_once = true;
+ }
}
IP4_REWRITE_NODE_PRIV1_OFF(node->ctx) = node_mbuf_priv1_dynfield_offset;
+ IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx) = feature_arc;
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = UINT16_MAX;
node_dbg("ip4_rewrite", "Initialized ip4_rewrite node initialized");
@@ -329,6 +624,7 @@ rte_node_ip4_rewrite_add(uint16_t next_hop, uint8_t *rewrite_data,
static struct rte_node_register ip4_rewrite_node = {
.process = ip4_rewrite_node_process,
+ .feat_arc_proc = ip4_rewrite_feature_node_process,
.name = "ip4_rewrite",
/* Default edge i.e '0' is pkt drop */
.nb_edges = 1,
diff --git a/lib/node/ip4_rewrite_priv.h b/lib/node/ip4_rewrite_priv.h
index 5105ec1d29..52f39601bd 100644
--- a/lib/node/ip4_rewrite_priv.h
+++ b/lib/node/ip4_rewrite_priv.h
@@ -5,9 +5,11 @@
#define __INCLUDE_IP4_REWRITE_PRIV_H__
#include <rte_common.h>
+#include <rte_graph_feature_arc.h>
#define RTE_GRAPH_IP4_REWRITE_MAX_NH 64
-#define RTE_GRAPH_IP4_REWRITE_MAX_LEN 56
+#define RTE_GRAPH_IP4_REWRITE_MAX_LEN (sizeof(struct rte_ether_hdr) + \
+ (2 * sizeof(struct rte_vlan_hdr)))
/**
* @internal
@@ -15,11 +17,9 @@
* Ipv4 rewrite next hop header data structure. Used to store port specific
* rewrite data.
*/
-struct ip4_rewrite_nh_header {
- uint16_t rewrite_len; /**< Header rewrite length. */
+struct __rte_cache_aligned ip4_rewrite_nh_header {
uint16_t tx_node; /**< Tx node next index identifier. */
- uint16_t enabled; /**< NH enable flag */
- uint16_t rsvd;
+ uint16_t rewrite_len; /**< Header rewrite length. */
union {
struct {
struct rte_ether_addr dst;
@@ -30,8 +30,13 @@ struct ip4_rewrite_nh_header {
uint8_t rewrite_data[RTE_GRAPH_IP4_REWRITE_MAX_LEN];
/**< Generic rewrite data */
};
+ /* used in control path */
+ uint8_t enabled; /**< NH enable flag */
};
+_Static_assert(sizeof(struct ip4_rewrite_nh_header) <= (size_t)RTE_CACHE_LINE_SIZE,
+ "ip4_rewrite_nh_header size must be less or equal to cache line");
+
/**
* @internal
*
diff --git a/lib/node/node_private.h b/lib/node/node_private.h
index 1de7306792..25db04a9a6 100644
--- a/lib/node/node_private.h
+++ b/lib/node/node_private.h
@@ -12,6 +12,9 @@
#include <rte_mbuf.h>
#include <rte_mbuf_dyn.h>
+#include <rte_graph_worker_common.h>
+#include <rte_graph_feature_arc_worker.h>
+
extern int rte_node_logtype;
#define RTE_LOGTYPE_NODE rte_node_logtype
@@ -29,15 +32,28 @@ extern int rte_node_logtype;
*/
struct node_mbuf_priv1 {
union {
- /* IP4/IP6 rewrite */
+ /**
+ * IP4/IP6 rewrite
+ * only used to pass lookup data from
+ * ip4-lookup to ip4-rewrite
+ */
struct {
uint16_t nh;
uint16_t ttl;
uint32_t cksum;
};
-
uint64_t u;
};
+ /**
+ * Feature arc data
+ */
+ struct {
+ /** interface index */
+ uint16_t if_index;
+ /** feature that current mbuf holds */
+ rte_graph_feature_t current_feature;
+ uint8_t rsvd;
+ };
};
static const struct rte_mbuf_dynfield node_mbuf_priv1_dynfield_desc = {
diff --git a/lib/node/rte_node_ip4_api.h b/lib/node/rte_node_ip4_api.h
index 24f8ec843a..0de06f7fc7 100644
--- a/lib/node/rte_node_ip4_api.h
+++ b/lib/node/rte_node_ip4_api.h
@@ -23,6 +23,7 @@ extern "C" {
#include <rte_compat.h>
#include <rte_graph.h>
+#include <rte_graph_feature_arc_worker.h>
/**
* IP4 lookup next nodes.
@@ -67,6 +68,8 @@ struct rte_node_ip4_reassembly_cfg {
/**< Node identifier to configure. */
};
+#define RTE_IP4_OUTPUT_FEATURE_ARC_NAME "ipv4-output"
+
/**
* Add ipv4 route to lookup table.
*
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 4/5] test/graph_feature_arc: add functional tests
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
` (2 preceding siblings ...)
2024-10-08 13:30 ` [RFC PATCH v2 3/5] graph: add IPv4 output feature arc Nitin Saxena
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-08 13:30 ` [RFC PATCH v2 5/5] docs: add programming guide for feature arc Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
5 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
Added functional unit test case for verifying feature arc control plane
and fast path APIs
How to run:
$ echo "graph_feature_arc_autotest" | ./bin/dpdk-test
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
app/test/meson.build | 1 +
app/test/test_graph_feature_arc.c | 1415 +++++++++++++++++++++++++++++
2 files changed, 1416 insertions(+)
create mode 100644 app/test/test_graph_feature_arc.c
diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..740fa1bfb4 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -90,6 +90,7 @@ source_file_deps = {
'test_func_reentrancy.c': ['hash', 'lpm'],
'test_graph.c': ['graph'],
'test_graph_perf.c': ['graph'],
+ 'test_graph_feature_arc.c': ['graph'],
'test_hash.c': ['net', 'hash'],
'test_hash_functions.c': ['hash'],
'test_hash_multiwriter.c': ['hash'],
diff --git a/app/test/test_graph_feature_arc.c b/app/test/test_graph_feature_arc.c
new file mode 100644
index 0000000000..e185fcd393
--- /dev/null
+++ b/app/test/test_graph_feature_arc.c
@@ -0,0 +1,1415 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#include "test.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdalign.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_errno.h>
+
+#ifndef RTE_EXEC_ENV_WINDOWS
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
+#include <rte_random.h>
+#include <rte_graph_feature_arc.h>
+#include <rte_graph_feature_arc_worker.h>
+
+#define MBUFF_SIZE 512
+#define TEST_ARC1_NAME "arc1"
+#define TEST_ARC2_NAME "arc2"
+#define MAX_INDEXES 10
+#define MAX_FEATURES 5
+
+#define SOURCE1 "test_node_arc_source1"
+#define INPUT_STATIC "test_node_arc_input_static"
+#define OUTPUT_STATIC "test_node_arc_output_static"
+#define PKT_FREE_STATIC "test_node_arc_pkt_free_static"
+#define ARC1_FEATURE1 "test_node_arc1_feature1"
+#define ARC1_FEATURE2 "test_node_arc1_feature2"
+#define ARC2_FEATURE1 "test_node_arc2_feature1"
+#define ARC2_FEATURE2 "test_node_arc2_feature2"
+#define ARC2_FEATURE3 "test_node_arc2_feature3"
+#define DUMMY1_STATIC "test_node_arc_dummy1_static"
+#define DUMMY2_STATIC "test_node_arc_dummy2_static"
+
+/* (Node index, Node Name, feature user data base */
+#define FOREACH_TEST_NODE_ARC { \
+ R(0, SOURCE1, 64) \
+ R(1, INPUT_STATIC, 128) \
+ R(2, OUTPUT_STATIC, 256) \
+ R(3, PKT_FREE_STATIC, 512) \
+ R(4, ARC1_FEATURE1, 1024) \
+ R(5, ARC1_FEATURE2, 2048) \
+ R(6, ARC2_FEATURE1, 4096) \
+ R(7, ARC2_FEATURE2, 8192) \
+ R(8, ARC2_FEATURE3, 16384) \
+ R(9, DUMMY1_STATIC, 32768) \
+ R(10, DUMMY2_STATIC, 65536) \
+ }
+
+/**
+ * ARC1: Feature arc on ingress interface
+ * ARC2: Feature arc on egress interface
+ * XX_static: Static nodes
+ * XX_featureX: Feature X on arc
+ *
+ * -----> ARC1_FEATURE1
+ * | | |
+ * | | v
+ * | | ARC1_FEATURE2
+ * | | |
+ * | v v
+ * SOURCE1 ->-----> INPUT_STATIC --> OUTPUT_STATIC -----> PKT_FREE_STATIC
+ * | | | ^ ^ ^
+ * | | | | | |
+ * | | --> ARC2_FEATURE1 | |
+ * | | ^ ^ | |
+ * | | | | | |
+ * | ----------c-> ARC2_FEATURE2 |
+ * | | ^ |
+ * | | | |
+ * ----------> ARC2_FEATURE3 -------
+ */
+const char *node_names_feature_arc[] = {
+ SOURCE1, INPUT_STATIC, OUTPUT_STATIC, PKT_FREE_STATIC,
+ ARC1_FEATURE1, ARC1_FEATURE2, ARC2_FEATURE1, ARC2_FEATURE2, ARC2_FEATURE3,
+ DUMMY1_STATIC, DUMMY2_STATIC
+};
+
+#define MAX_NODES RTE_DIM(node_names_feature_arc)
+
+/* Function declaraions */
+static uint16_t
+source1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+input_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+input_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+output_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+output_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+pkt_free_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+pkt_free_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+dummy1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+dummy2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature3_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature3_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static int
+common_node_init(const struct rte_graph *graph, struct rte_node *node);
+
+typedef struct test_node_priv {
+ /* index fom 0 - MAX_NODES -1 */
+ uint8_t node_index;
+
+ /* feature */
+ rte_graph_feature_t feature;
+
+ /* rte_graph node id */
+ uint32_t node_id;
+
+ rte_graph_feature_arc_t arc;
+} test_node_priv_t;
+
+typedef struct {
+ rte_graph_feature_t feature;
+ uint16_t egress_interface;
+ uint16_t ingress_interface;
+} graph_dynfield_t;
+
+static int graph_dynfield_offset = -1;
+static rte_graph_feature_arc_t arcs[RTE_GRAPH_FEATURE_ARC_MAX + 128];
+static struct rte_mbuf mbuf[MAX_NODES + 1][MBUFF_SIZE];
+static void *mbuf_p[MAX_NODES + 1][MBUFF_SIZE];
+static rte_graph_t graph_id = RTE_GRAPH_ID_INVALID;
+
+const char *node_patterns_feature_arc[] = {
+ "test_node_arc*"
+};
+
+static inline graph_dynfield_t *
+graph_field(struct rte_mbuf *mbuf)
+{
+ return RTE_MBUF_DYNFIELD(mbuf, graph_dynfield_offset, graph_dynfield_t *);
+}
+
+static int32_t
+compute_unique_user_data(const char *parent, const char *child, uint32_t interface_index)
+{
+ uint32_t user_data = interface_index;
+
+ RTE_SET_USED(parent);
+#define R(idx, node, node_cookie) { \
+ if (!strcmp(child, node)) { \
+ user_data += node_cookie; \
+ } \
+ }
+
+ FOREACH_TEST_NODE_ARC
+#undef R
+
+ return user_data;
+}
+
+static int
+get_edge(struct rte_node_register *parent_node,
+ struct rte_node_register *child_node, rte_edge_t *_edge)
+{
+ char **next_edges = NULL;
+ uint32_t count, i;
+
+ count = rte_node_edge_get(parent_node->id, NULL);
+
+ if (!count)
+ return -1;
+
+ next_edges = malloc(count);
+
+ if (!next_edges)
+ return -1;
+
+ count = rte_node_edge_get(parent_node->id, next_edges);
+ for (i = 0; i < count; i++) {
+ if (strstr(child_node->name, next_edges[i])) {
+ if (_edge)
+ *_edge = (rte_edge_t)i;
+
+ free(next_edges);
+ return 0;
+ }
+ }
+ free(next_edges);
+
+ return -1;
+}
+
+int
+common_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+ test_node_priv_t *priv = (test_node_priv_t *)node->ctx;
+
+ RTE_SET_USED(graph);
+
+ priv->node_id = node->id;
+ priv->feature = RTE_GRAPH_FEATURE_INVALID;
+ priv->arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+#define R(idx, _name, user_data) { \
+ if (!strcmp(node->name, _name)) { \
+ priv->node_index = idx; \
+ } \
+ }
+ FOREACH_TEST_NODE_ARC
+#undef R
+
+ return 0;
+}
+
+uint16_t
+source1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register source1 = {
+ .name = SOURCE1,
+ .process = source1_fn,
+ .flags = RTE_NODE_SOURCE_F,
+ .nb_edges = 3,
+ .init = common_node_init,
+ .next_nodes = {INPUT_STATIC, DUMMY1_STATIC, DUMMY2_STATIC},
+};
+RTE_NODE_REGISTER(source1);
+
+uint16_t
+input_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+input_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register input = {
+ .name = INPUT_STATIC,
+ .process = input_fn,
+ .feat_arc_proc = input_fa_fn,
+ .nb_edges = 2,
+ .init = common_node_init,
+ .next_nodes = {OUTPUT_STATIC, DUMMY1_STATIC},
+};
+RTE_NODE_REGISTER(input);
+
+uint16_t
+output_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+output_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register output = {
+ .name = OUTPUT_STATIC,
+ .process = output_fn,
+ .feat_arc_proc = output_fa_fn,
+ .nb_edges = 3,
+ .init = common_node_init,
+ .next_nodes = {DUMMY1_STATIC, PKT_FREE_STATIC, DUMMY2_STATIC},
+};
+RTE_NODE_REGISTER(output);
+
+uint16_t
+pkt_free_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+pkt_free_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register pkt_free = {
+ .name = PKT_FREE_STATIC,
+ .process = pkt_free_fn,
+ .feat_arc_proc = pkt_free_fa_fn,
+ .nb_edges = 1,
+ .init = common_node_init,
+ .next_nodes = {DUMMY1_STATIC},
+};
+RTE_NODE_REGISTER(pkt_free);
+
+uint16_t
+dummy1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register dummy1 = {
+ .name = DUMMY1_STATIC,
+ .process = dummy1_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(dummy1);
+
+uint16_t
+dummy2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register dummy2 = {
+ .name = DUMMY2_STATIC,
+ .process = dummy2_fn,
+ .nb_edges = 5,
+ .init = common_node_init,
+ .next_nodes = { ARC1_FEATURE1, ARC1_FEATURE2, ARC2_FEATURE1,
+ ARC2_FEATURE2, ARC2_FEATURE3},
+};
+RTE_NODE_REGISTER(dummy2);
+
+uint16_t
+arc1_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc1_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc1_feature1 = {
+ .name = ARC1_FEATURE1,
+ .process = arc1_feature1_fn,
+ .feat_arc_proc = arc1_feature1_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc1_feature1);
+
+uint16_t
+arc1_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc1_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc1_feature2 = {
+ .name = ARC1_FEATURE2,
+ .process = arc1_feature2_fn,
+ .feat_arc_proc = arc1_feature2_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc1_feature2);
+
+uint16_t
+arc2_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature1 = {
+ .name = ARC2_FEATURE1,
+ .process = arc2_feature1_fn,
+ .feat_arc_proc = arc2_feature1_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature1);
+
+uint16_t
+arc2_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature2 = {
+ .name = ARC2_FEATURE2,
+ .process = arc2_feature2_fn,
+ .feat_arc_proc = arc2_feature2_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature2);
+
+uint16_t
+arc2_feature3_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature3_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature3 = {
+ .name = ARC2_FEATURE3,
+ .process = arc2_feature3_fn,
+ .feat_arc_proc = arc2_feature3_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature3);
+
+static int
+create_graph(void)
+{
+ struct rte_graph_param gconf = {
+ .socket_id = SOCKET_ID_ANY,
+ .nb_node_patterns = 1,
+ .node_patterns = node_patterns_feature_arc,
+ };
+
+ graph_id = rte_graph_create("worker0", &gconf);
+ if (graph_id == RTE_GRAPH_ID_INVALID) {
+ printf("Graph creation failed with error = %d\n", rte_errno);
+ return TEST_FAILED;
+ }
+
+ return TEST_SUCCESS;
+}
+
+static int
+__test_create_feature_arc(rte_graph_feature_arc_t *arcs, int max_arcs)
+{
+ rte_graph_feature_arc_t arc;
+ const char *sample_arc_name = "sample_arc";
+ char arc_name[256];
+ int n_arcs;
+
+ /* Create max number of feature arcs first */
+ for (n_arcs = 0; n_arcs < max_arcs; n_arcs++) {
+ snprintf(arc_name, sizeof(arc_name), "%s-%u", sample_arc_name, n_arcs);
+ if (rte_graph_feature_arc_create(arc_name, MAX_FEATURES,
+ MAX_INDEXES, &dummy1, &arcs[n_arcs])) {
+ printf("Feature arc creation failed for %u\n", n_arcs);
+ return TEST_FAILED;
+ }
+ }
+ /* Verify feature arc created more than max_arcs must fail */
+ if (!rte_graph_feature_arc_create("negative_test_create_arc", MAX_FEATURES,
+ MAX_INDEXES, &dummy2, &arc)) {
+ printf("Feature arc creation success for more than max configured: %u\n", n_arcs);
+ return TEST_FAILED;
+ }
+ /* Make sure lookup passes for all feature arcs */
+ for (n_arcs = 0; n_arcs < max_arcs; n_arcs++) {
+ snprintf(arc_name, sizeof(arc_name), "%s-%u", sample_arc_name, n_arcs);
+ arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+ if (!rte_graph_feature_arc_lookup_by_name(arc_name, &arc)) {
+ if (arc != arcs[n_arcs]) {
+ printf("%s: Feature arc lookup mismatch for arc [%p, exp: %p]\n",
+ arc_name, (void *)arc, (void *)arcs[n_arcs]);
+ return TEST_FAILED;
+ }
+ } else {
+ printf("Feature arc lookup %s failed after creation\n", arc_name);
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_create(void)
+{
+ int ret = 0, i;
+
+ /* Create arcs with RTE_GRAPH_FEATURE_ARC_MAX */
+ ret = __test_create_feature_arc(arcs, RTE_GRAPH_FEATURE_ARC_MAX);
+ if (ret) {
+ printf("Feature arc creation test failed for RTE_GRAPH_FEATURE_ARC_MAX arcs\n");
+ return TEST_FAILED;
+ }
+ /* destroy all arcs via cleanup API*/
+ ret = rte_graph_feature_arc_cleanup();
+ if (ret) {
+ printf("Feature arc cleanup failed\n");
+ return TEST_FAILED;
+ }
+
+#define NUM_FEAT_ARCS 128
+ /* create 128 dummy feature arcs */
+ ret = rte_graph_feature_arc_init(NUM_FEAT_ARCS);
+ if (ret) {
+ printf("Feature arc init failed for NUM_FEAT_ARCS");
+ return TEST_FAILED;
+ }
+ ret = __test_create_feature_arc(arcs, NUM_FEAT_ARCS);
+ if (ret) {
+ printf("Feature arc creation test failed for NUM_FEAT_ARCS\n");
+ return TEST_FAILED;
+ }
+ /* destroy all of them*/
+ for (i = 0; i < NUM_FEAT_ARCS; i++) {
+ if (rte_graph_feature_arc_destroy(arcs[i])) {
+ printf("Feature arc destroy failed for %u\n", i);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_cleanup();
+
+ /* Create two arcs as per test plan */
+ /* First arc start/source node is node: SOURCE1 */
+ if (rte_graph_feature_arc_create(TEST_ARC1_NAME, MAX_FEATURES,
+ MAX_INDEXES, &source1, &arcs[0])) {
+ printf("Feature arc creation failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ /* Duplicate name should fail */
+ if (!rte_graph_feature_arc_create(TEST_ARC1_NAME, MAX_FEATURES,
+ MAX_INDEXES, &source1, &arcs[1])) {
+ printf("Duplicate feature arc %s creation is not caught\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /* Second arc start/source node is node: OUTPUT_STATIC */
+ if (rte_graph_feature_arc_create(TEST_ARC2_NAME, MAX_FEATURES,
+ MAX_INDEXES, &output, &arcs[1])) {
+ printf("Feature arc creation failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_features_add(void)
+{
+ rte_graph_feature_t temp;
+
+ /* First feature to SOURCE1 start node -> ARC1_FEATURE1 */
+ if (rte_graph_feature_add(arcs[0], &arc1_feature1, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Second feature to SOURCE1 -> ARC1_FEATURE2 */
+ if (rte_graph_feature_add(arcs[0], &arc1_feature2, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ /* adding statically connected INPUT_STATIC as a last feature */
+ if (rte_graph_feature_add(arcs[0], &input, ARC1_FEATURE2, NULL)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC1_NAME, INPUT_STATIC, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ /* First feature to OUTPUT_STATIC start node -> ARC2_FEATURE3 */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature3, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Second feature to OUTPUT_STATIC -> ARC2_FEATURE1 and before feature to
+ * ARC2_FEATURE3
+ */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature1, NULL, ARC2_FEATURE3)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, ARC2_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Add PKT_FREE node as last feature, next to arc2_feature3 */
+ if (rte_graph_feature_add(arcs[1], &pkt_free, ARC2_FEATURE3, NULL)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Adding feature ARC2_FEATURE2 between ARC2_FEATURE1 and ARC2_FEATURE3. */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature2, ARC2_FEATURE1, ARC2_FEATURE3)) {
+ printf("%s: Feature add failed for adding feature %s between [%s - %s]\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, ARC2_FEATURE1, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Now check feature sequencing is correct for both ARCS */
+
+ /* arc1_featur1 must be first feature to arcs[0] */
+ if (!strstr(ARC1_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ rte_graph_feature_cast(0)))) {
+ printf("%s: %s is not the first feature instead %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[0], rte_graph_feature_cast(0)));
+ return TEST_FAILED;
+ }
+
+ /* arc1_feature2 must be second feature to arcs[0] */
+ if (!strstr(ARC1_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the second feature instead %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[0], rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* Make sure INPUT_STATIC is the last feature in arcs[0] */
+ temp = rte_graph_feature_arc_num_features(arcs[0]);
+ if (!strstr(INPUT_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ temp - rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the last feature instead %s\n",
+ TEST_ARC1_NAME, INPUT_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ temp - rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_featur1 must be first feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(0)))) {
+ printf("%s: %s is not the first feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(0)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_feature2 must be second feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the second feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_feature3 must be third feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE3,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(2)))) {
+ printf("%s: %s is not the third feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(2)));
+ return TEST_FAILED;
+ }
+
+ /* Make sure PKT_FREE is the last feature in arcs[1] */
+ temp = rte_graph_feature_arc_num_features(arcs[1]);
+ if (!strstr(PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ temp - rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the last feature instead %s\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ temp - rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ if (get_edge(&arc2_feature1, &pkt_free, NULL)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1, PKT_FREE_STATIC);
+ return TEST_FAILED;
+ }
+
+ return create_graph();
+}
+
+static int
+test_graph_feature_arc_first_feature_enable(void)
+{
+ uint32_t n_indexes, n_features, count = 0;
+ rte_graph_feature_rt_list_t feature_list, temp = 0;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ char *feature_name = NULL;
+ int32_t user_data;
+ rte_edge_t edge = ~0;
+
+ arc = rte_graph_feature_arc_get(arcs[0]);
+
+ if (rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should not have any feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_num_enabled_features(arcs[0])) {
+ printf("%s: Feature arc should not have any_feature() enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /*
+ * On interface 0, enable feature 0,
+ * On interface 1, enable feature 1 and so on so forth
+ *
+ * later verify first feature on every interface index is unique
+ * and check [rte_edge, user_data] retrieved via fast path APIs
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ n_features = n_indexes % 3 /* 3 features added to arc1 */;
+ feature_name = rte_graph_feature_arc_feature_to_name(arcs[0], n_features);
+ user_data = compute_unique_user_data(arc->start_node->name, feature_name,
+ n_indexes);
+ if (rte_graph_feature_validate(arcs[0], n_indexes, feature_name, 1, true)) {
+ printf("%s: Feature validate failed for %s on index %u\n",
+ TEST_ARC1_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* negative test case. enable feature on invalid index */
+ if (!n_indexes && !rte_graph_feature_enable(arcs[0], MAX_INDEXES, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature %s should not be enabled on invalid index\n",
+ TEST_ARC1_NAME, feature_name);
+ return TEST_FAILED;
+ }
+ if (rte_graph_feature_enable(arcs[0], n_indexes, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC1_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* has any feature should be valid */
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ if (temp == feature_list) {
+ printf("%s: Activer feature list not switched from %u -> %u\n",
+ TEST_ARC1_NAME, temp, feature_list);
+ return TEST_FAILED;
+ }
+ temp = feature_list;
+ if ((count + 1) != rte_graph_feature_arc_num_enabled_features(arcs[0])) {
+ printf("%s: Number of enabled mismatches [found: %u, exp: %u]\n",
+ TEST_ARC1_NAME,
+ rte_graph_feature_arc_num_enabled_features(arcs[0]),
+ count + 1);
+ return TEST_FAILED;
+ }
+ count++;
+ }
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /* Negative test case */
+ user_data = compute_unique_user_data(arc->start_node->name, ARC2_FEATURE1, 1);
+ if (!rte_graph_feature_enable(arcs[0], 1 /* index */, ARC2_FEATURE1, user_data)) {
+ printf("%s: Invalid feature %s is enabled on index 1\n",
+ TEST_ARC1_NAME, ARC2_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Duplicate enable */
+ if (!rte_graph_feature_enable(arcs[0], 1 /* index */, ARC1_FEATURE2, user_data)) {
+ printf("%s: Duplicate feature %s shouldn't be enabled again on index 1\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: %u\n",
+ TEST_ARC1_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ /* Get first feature data and ensure edge and user_data are correct */
+ fdata = rte_graph_feature_data_get(arc, rte_graph_feature_get(arc, feature),
+ n_indexes);
+ parent = arc->start_node;
+ if (0 == (n_indexes % 3))
+ child = &arc1_feature1;
+ else if (1 == (n_indexes % 3))
+ child = &arc1_feature2;
+ else
+ child = &input;
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC1_NAME, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for first feature on index %u [%u, exp: %u]\n",
+ TEST_ARC1_NAME, n_indexes, fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != compute_unique_user_data(parent->name, child->name,
+ n_indexes)) {
+ printf("%s: First feature user data mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC1_NAME, n_indexes, fdata->user_data,
+ compute_unique_user_data(parent->name, child->name, n_indexes));
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+verify_feature_sequencing(struct rte_graph_feature_arc *arc)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+ rte_edge_t edge = ~0;
+ int32_t user_data;
+
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: feature_list can't be obtained\n",
+ arc->feature_arc_name);
+ return TEST_FAILED;
+ }
+ /* Verify next features on interface 0 and interface 1*/
+ for (n_indexes = 0; n_indexes < 2; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: 0\n",
+ arc->feature_arc_name);
+ return TEST_FAILED;
+ }
+ parent = arc->start_node;
+ child = rte_graph_feature_arc_feature_to_node(arcs[1], feature);
+ /* until fast path API reaches last feature i.e pkt_free */
+ while (child != &pkt_free) {
+ fdata = rte_graph_feature_data_get(arc,
+ rte_graph_feature_get(arc, feature),
+ n_indexes);
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ arc->feature_arc_name, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ user_data = compute_unique_user_data(parent->name, child->name, n_indexes);
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for %s->%s on index %u [%u, exp: %u]\n",
+ arc->feature_arc_name, parent->name, child->name, n_indexes,
+ fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != user_data) {
+ printf("%s: Udata mismatch for %s->%s on index %u [%u, exp: %u]\n",
+ arc->feature_arc_name, parent->name, child->name, n_indexes,
+ fdata->user_data, user_data);
+ return TEST_FAILED;
+ }
+
+ feature = fdata->next_enabled_feature;
+
+ parent = child;
+ child = rte_graph_feature_arc_feature_to_node(arcs[1],
+ fdata->next_enabled_feature);
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_next_feature_enable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature_arc *arc;
+ uint32_t n_indexes, n_features;
+ rte_graph_feature_t feature;
+ char *feature_name = NULL;
+ rte_edge_t edge = ~0;
+ int32_t user_data;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ if (rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should not have any feature enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_num_enabled_features(arcs[1])) {
+ printf("%s: Feature arc should not have any_feature() enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ /*
+ * On interface 0, enable feature 2, skip feature 1 for later
+ * On interface 1, enable feature 3
+ * On interface 2, enable pkt_free feature
+ * On interface 3, continue as interface 0
+ *
+ * later enable next feature sequence for interface 0 from feature2 -> pkt_free
+ * later enable next feature sequence for interface 1 from feature3 -> pkt_free
+ *
+ * also later enable feature-1 and see first feature changes for all indexes
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ n_features = (n_indexes % 3) + 1; /* feature2 to pkt_free are 3 features */
+ feature_name = rte_graph_feature_arc_feature_to_name(arcs[1], n_features);
+ user_data = compute_unique_user_data(arc->start_node->name, feature_name,
+ n_indexes);
+ if (rte_graph_feature_enable(arcs[1], n_indexes, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC2_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* has any feature should be valid */
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ }
+ /* Retrieve latest feature_list */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ /* verify first feature */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ /* Get first feature data and ensure edge and user_data are correct */
+ fdata = rte_graph_feature_data_get(arc, rte_graph_feature_get(arc, feature),
+ n_indexes);
+ parent = arc->start_node;
+ if (0 == (n_indexes % 3))
+ child = &arc2_feature2;
+ else if (1 == (n_indexes % 3))
+ child = &arc2_feature3;
+ else
+ child = &pkt_free;
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC2_NAME, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for first feature on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != compute_unique_user_data(parent->name, child->name,
+ n_indexes)) {
+ printf("%s: First feature user data mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, fdata->user_data,
+ compute_unique_user_data(parent->name, child->name, n_indexes));
+ return TEST_FAILED;
+ }
+ }
+ /* add next_features now
+ * On interface 0, enable feature-3 and pkt_free
+ * On interface 1, enable pkt_free
+ * Skip interface 2
+ * On interface 3, same as interface 0
+ * On interface 4, same as interface 1
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (0 == (n_indexes % 3)) {
+ if (rte_graph_feature_enable(arcs[1], n_indexes, ARC2_FEATURE3,
+ compute_unique_user_data(ARC2_FEATURE2,
+ ARC2_FEATURE3,
+ n_indexes))) {
+ printf("%s: Feature enable failed for %s -> (%s) on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, ARC2_FEATURE3, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ /* pkt_free on interface-0, 1, 3, 4 and so on */
+ if ((0 == (n_indexes % 3)) || (1 == (n_indexes % 3))) {
+ if (rte_graph_feature_enable(arcs[1], n_indexes, PKT_FREE_STATIC,
+ compute_unique_user_data(ARC2_FEATURE3,
+ PKT_FREE_STATIC,
+ n_indexes))) {
+ printf("%s: Feature enable failed %s -> (%s) on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, PKT_FREE_STATIC, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ }
+
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ /* Enable feature-1 on all interfaces and check first feature changes */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ user_data = compute_unique_user_data(arc->start_node->name, ARC2_FEATURE1,
+ n_indexes);
+ if (rte_graph_feature_enable(arcs[1], n_indexes, ARC2_FEATURE1,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC2_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: None first feature enabled on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (feature != rte_graph_feature_cast(0)) {
+ printf("%s: First feature mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, feature, rte_graph_feature_cast(0));
+ return TEST_FAILED;
+ }
+ }
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_first_feature_disable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ /* Disable feature-1 on all interfaces and check first feature changes */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE1)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: First feature get failed on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (feature == rte_graph_feature_cast(0)) {
+ printf("%s: First feature not disabled on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, feature, rte_graph_feature_cast(1));
+ return TEST_FAILED;
+ }
+ if (!strncmp(ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ strlen(ARC2_FEATURE1))) {
+ printf("%s: First feature mismatch on index %u [%s, exp: %s]\n",
+ TEST_ARC2_NAME, n_indexes,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ ARC2_FEATURE2);
+ return TEST_FAILED;
+ }
+ }
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_next_feature_disable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ /*
+ * On interface 0, disable feature 2, keep feature3 and pkt_free enabled
+ * On interface 1, skip interface 1 where feature3 and pkt_free are enabled
+ * skip interface 2 as only pkt_free is enabled
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!(n_indexes % 3)) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE2)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+ }
+
+ /**
+ * Disable feature 3 on all interface 0 and 1 and check first feature
+ * is pkt_free on all indexes
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if ((0 == (n_indexes % 3)) || (1 == (n_indexes % 3))) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE3)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ }
+ /* Make sure pkt_free is first feature for all indexes */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: First feature get failed on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (strncmp(PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ strlen(PKT_FREE_STATIC))) {
+ printf("%s: %s is not first feature found on index %u [%s, exp: %s]\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, n_indexes,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ PKT_FREE_STATIC);
+ return TEST_FAILED;
+ }
+ }
+
+ /* Disable PKT_FREE_STATIC from all indexes with no feature enabled on any interface */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, PKT_FREE_STATIC)) {
+ printf("%s: Feat disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ /* Make sure no feature is enabled now on any interface */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: Index: %u should not have first feature enabled\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_destroy(void)
+{
+ rte_graph_feature_arc_t arc;
+
+ if (rte_graph_feature_arc_lookup_by_name(TEST_ARC1_NAME, &arc)) {
+ printf("Feature arc lookup failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (arc != arcs[0]) {
+ printf("Feature arc lookup mismatch for %s [%p, exp: %p]\n",
+ TEST_ARC1_NAME, (void *)arc, (void *)arcs[0]);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_destroy(arc)) {
+ printf("Feature arc destroy failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_lookup_by_name(TEST_ARC2_NAME, &arc)) {
+ printf("Feature arc lookup success after destroy for %s\n", TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+
+ if (arc != arcs[1]) {
+ printf("Feature arc lookup mismatch for %s [%p, exp: %p]\n",
+ TEST_ARC2_NAME, (void *)arc, (void *)arcs[1]);
+ return TEST_FAILED;
+ }
+ if (rte_graph_feature_arc_destroy(arc)) {
+ printf("Feature arc destroy failed for %s\n", TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+graph_feature_arc_setup(void)
+{
+ unsigned long i, j;
+
+ static const struct rte_mbuf_dynfield graph_dynfield_desc = {
+ .name = "test_graph_dynfield",
+ .size = sizeof(graph_dynfield_t),
+ .align = alignof(graph_dynfield_t),
+ };
+
+ graph_dynfield_offset =
+ rte_mbuf_dynfield_register(&graph_dynfield_desc);
+ if (graph_dynfield_offset < 0) {
+ printf("Cannot register mbuf field\n");
+ return TEST_FAILED;
+ }
+
+ for (i = 0; i <= MAX_NODES; i++) {
+ for (j = 0; j < MBUFF_SIZE; j++)
+ mbuf_p[i][j] = &mbuf[i][j];
+ }
+
+ return TEST_SUCCESS;
+
+}
+
+static void
+graph_feature_arc_teardown(void)
+{
+ if (graph_id != RTE_GRAPH_ID_INVALID)
+ rte_graph_destroy(graph_id);
+
+ rte_graph_feature_arc_cleanup();
+}
+
+static struct unit_test_suite graph_feature_arc_testsuite = {
+ .suite_name = "Graph Feature arc library test suite",
+ .setup = graph_feature_arc_setup,
+ .teardown = graph_feature_arc_teardown,
+ .unit_test_cases = {
+ TEST_CASE(test_graph_feature_arc_create),
+ TEST_CASE(test_graph_feature_arc_features_add),
+ TEST_CASE(test_graph_feature_arc_first_feature_enable),
+ TEST_CASE(test_graph_feature_arc_next_feature_enable),
+ TEST_CASE(test_graph_feature_arc_first_feature_disable),
+ TEST_CASE(test_graph_feature_arc_next_feature_disable),
+ TEST_CASE(test_graph_feature_arc_destroy),
+ TEST_CASES_END(), /**< NULL terminate unit test array */
+ },
+};
+
+static int
+graph_feature_arc_autotest_fn(void)
+{
+ return unit_test_suite_runner(&graph_feature_arc_testsuite);
+}
+
+REGISTER_FAST_TEST(graph_feature_arc_autotest, true, true, graph_feature_arc_autotest_fn);
+#endif /* !RTE_EXEC_ENV_WINDOWS */
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [RFC PATCH v2 5/5] docs: add programming guide for feature arc
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
` (3 preceding siblings ...)
2024-10-08 13:30 ` [RFC PATCH v2 4/5] test/graph_feature_arc: add functional tests Nitin Saxena
@ 2024-10-08 13:30 ` Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
5 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan
Cc: dev, Nitin Saxena
Updated graph library guide with feature arc
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
doc/guides/prog_guide/graph_lib.rst | 289 ++++++++++++++++++++
doc/guides/prog_guide/img/feature_arc-1.jpg | Bin 0 -> 48984 bytes
doc/guides/prog_guide/img/feature_arc-2.jpg | Bin 0 -> 113287 bytes
doc/guides/prog_guide/img/feature_arc-3.jpg | Bin 0 -> 93408 bytes
4 files changed, 289 insertions(+)
create mode 100644 doc/guides/prog_guide/img/feature_arc-1.jpg
create mode 100644 doc/guides/prog_guide/img/feature_arc-2.jpg
create mode 100644 doc/guides/prog_guide/img/feature_arc-3.jpg
diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index ad09bdfe26..45c7695c80 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -547,3 +547,292 @@ on success packet is enqueued to ``udp4_input`` node.
Hash lookup is performed in ``udp4_input`` node with registered destination port
and destination port in UDP packet , on success packet is handed to ``udp_user_node``.
+
+Feature Arc
+-----------
+`Feature arc` represents an ordered list of `protocols/features` at a given
+networking layer. It is a high level abstraction to connect various `feature`
+nodes in `rte_graph` instance and allows seamless packets steering based on the
+sequence of enabled features at runtime on each interface.
+
+`Features` (or feature nodes) are nodes which handle partial or complete
+protocol processing in a given direction. For instance, `ipv4-rewrite` and
+`IPv4 IPsec encryption` are outbound features while `ipv4-lookup` and `IPv4
+IPsec decryption` are inbound features. Further, `ipv4-rewrite` and `IPv4
+IPsec encryption` can collectively represent a `feature arc` towards egress
+direction with ordering constraints that `IPv4 IPsec encryption` must be
+performed before `ipv4-rewrite`. Similarly, `IPv4 IPsec decryption` and
+`ipv4-lookup` can represent a `feature arc` in an ingress direction. Both of
+these `feature arc` can co-exist at an IPv4 layer in egress and ingress
+direction respectively.
+
+A `feature` can be represented by a single node or collection of multiple nodes
+performing feature processing collectively.
+
+.. figure:: img/feature_arc-1.jpg
+ :alt: feature-arc-1
+ :width: 350px
+ :align: center
+
+ Feature Arc overview
+
+Each `feature arc` is associated with a `Start` node from which all features in
+a feature arc are connected. A `start` node itself is not a `feature` node but
+it is where `first enabled feature` is checked in fast path. In above figure,
+`Node-A` represents a `start node`. There may be a `Sink` node as well which
+is child node for every feature in an arc. 'Sink` node is responsible of
+consuming those packets which are not consumed by intermediate enabled features
+between `start` and `sink` node. `Sink` node, if present, is the last enabled
+feature in a feature arc. A `feature` node statically connected to `start` node
+must also be added via feature arc API, `rte_graph_feature_add()``. Here `Node-B`
+acts as a `sink` node which is statically linked to `Node A`. `Feature` nodes
+are connected via `rte_graph_feature_add()` which takes care of connecting
+all `feature` nodes with each other and start node.
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 8
+ :caption: Node-B statically linked to Node-A
+
+ static struct rte_node_register node_A_node = {
+ .process = node_A_process_func,
+ ...
+ ...
+ .name = "Node-A",
+ .next_nodes =
+ {
+ [0] = "Node-B",
+ },
+ .nb_edges = 1,
+ };
+
+When multiple features are enabled on an interface, it may be required to steer
+packets across `features` in a given order. For instance, if `Feature 1` and
+`Feature 2` both are enabled on an interface ``X``, it may be required to send
+packets to `Feature-1` before `Feature-2`. Such ordering constrainsts
+can be easily expressed with `feature arc`. In this case, `Feature 1` is called as
+``First Feature`` and `Feature 2` is called as ``Next Feature`` to `Feature 1`.
+
+.. figure:: img/feature_arc-2.jpg
+ :alt: feature-arc-2
+ :width: 600px
+ :align: center
+
+ First and Next features and their ordering
+
+In similar manner, even application specific ``custom features`` can be hooked
+to standard nodes. It is to be noted that this `custom feature` hooking to
+`feature arc` aware node does not require any code changes.
+
+It may be obvious by now that `features` enabled on one interface does not
+affect packets on other interfaces. In above example, if no feature is
+enabled on an interface ``X``, packets destined to interface ``X`` would be
+directly sent to `Node-B` from `Node-A`.
+
+.. figure:: img/feature_arc-3.jpg
+ :alt: feature-arc-3
+ :width: 450px
+ :align: center
+
+ Feature-2 consumed/non-consumed packet path
+
+When a `Feature-X` node receives packets via feature arc, it may decide whether
+to ``consume packet`` or send to `next enabled feature`. A node can consume
+packet by freeing it, sending it on wire or enqueuing it to hardware queue. If a
+packet is not consumed by a `Feature-X` node, it may send to `next enabled
+feature` on an interface. In above figure, `Feature-2` nodes are represented to
+consume packets. Classic example for a node performing consume and non-consume
+operation on packets would be IPsec policy node where all packets with
+``protect`` actions are consumed while remanining packets with ``bypass``
+actions are sent to next enabled feature.
+
+In fast path feature node may require to lookup local data structures for each
+interface. For example, retrieving policy database per interface for IPsec
+processing. ``rte_graph_feature_enable`` API allows to set application
+specific cookie per feature per interface. `Feature data` object maintains this
+cookie in fast path for each interface.
+
+`Feature arc design` allows to enable subsequent features in a control plane
+without stopping workers which are accessing feature arc's fast path APIs in
+``rte_graph_walk()`` context. However for disabling features require RCU like
+scheme for synchronization.
+
+Programming model
+~~~~~~~~~~~~~~~~~
+Feature Arc Objects
+^^^^^^^^^^^^^^^^^^^
+Control plane and fast path APIs deals with following objects:
+
+Feature arc
+***********
+``rte_graph_feature_arc_t`` is a handle to feature arc which is created via
+``rte_graph_feature_arc_create()``. It is a `uint64_t` size object which can be
+saved in feature node's context. This object can be translated to fast path
+feature arc object ``struct rte_graph_feature_arc`` which is an input
+argument to all fast path APIs. Control plane APIs majorly takes
+`rte_graph_feature_arc_t` object as an input.
+
+Feature List
+************
+Each feature arc holds two feature lists: `active` and `passive`. While worker
+cores uses `active` list, control plane APIs uses `passive` list for
+enabling/disabling a feature on any interface with in a arc. After successful
+feature enable/disable, ``rte_graph_feature_enable()``/
+``rte_graph_feature_disable()`` atomically switches passive list to active list
+and vice-versa. Most of the fast path APIs takes active list as an argument
+(``rte_graph_feature_rt_list_t``), which feature node can obtain in start of
+it's `process_func()` via ``rte_graph_feature_arc_has_any_feature()`` (in `start`
+node) or ``rte_graph_feature_arc_has_feature()`` (in next feature nodes).
+
+Each feature list holds RTE_GRAPH_MAX_FEATURES number of features and
+associated feature data for every interface index
+
+Feature
+********
+Feature is a data structure which holds `feature data` object for every
+interface. It is represented via ``rte_graph_feature_t`` which is a `uint8_t`
+size object. Fast path internal structure ``struct rte_graph_feature`` can be
+obtained from ``rte_graph_feature_t`` via ``rte_graph_feature_get()`` API.
+
+In `start` node `rte_graph_feature_arc_first_feature_get()` can be used to get
+first enabled `rte_graph_feature_t` object for an interface. `rte_edge` from
+`start` node to first enabled feature is provided by
+``rte_graph_feature_arc_feature_set()`` API.
+
+In `feature nodes`, next enabled feature is obtained by providing current feature
+as an input to ``rte_graph_feature_arc_next_feature_get()`` API.
+
+Feature data
+************
+Feature data object is maintained per feature per interface which holds
+following information in fast path
+
+- ``rte_edge_t`` to send packet to next enabled feature
+- ``Next enabled feature`` on current interface
+- ``User_data`` per feature per interface set by application via `rte_graph_feature_enable()`
+
+Enabling Feature Arc processing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+By default, feature arc processing is disabled in `rte_graph_create()`. To
+enable feature arc processing in fast path, `rte_graph_create()` API should be
+invoked with `feature_arc_enable` flag set as `true`
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 3
+ :caption: Enabling feature are processing in rte_graph_create()
+
+ struct rte_graph_param graph_conf;
+
+ graph_conf.feature_arc_enable = true;
+ struct rte_graph *graph = rte_graph_create("graph_name", &graph_conf);
+
+Further as an optimization technique, `rte_graph_walk()` would call newly added
+``feat_arc_proc()`` node callback function (if non-NULL) instead of
+``preocess_func``
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 3
+ :caption: Feature arc specific node callback function
+
+ static struct rte_node_register ip4_rewrite_node = {
+ .process = ip4_rewrite_node_process,
+ .feat_arc_proc = ip4_rewrite_feature_node_process,
+ .name = "ip4_rewrite",
+ ...
+ ...
+ };
+
+If `feat_arc_proc` is not provided in node registration, `process_func` would
+be called by `rte_graph_walk()`
+
+Sample Usage
+^^^^^^^^^^^^
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 29,38,49, 53,68,69,74
+ :caption: Feature arc sample usage
+
+ #define MAX_FEATURES 10
+ #define MAX_INDEXES 5
+
+ static uint16_t
+ feature2_feature_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* features may be enabled */
+ }
+ static uint16_t
+ feature2_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* Feature arc is disabled in rte_graph_create() */
+ }
+
+ static uint16_t
+ feature2_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* Feature arc may be enabled or disabled as this process_func() would
+ * be called for the case when feature arc is enabled in rte_graph_create()
+ * and also the case when it is disabled
+ */
+ }
+
+ static struct rte_node_register feature2_node = {
+ .process = feature2_node_process,
+ .feat_arc_proc = feature2_feature_node_process,
+ .name = "feature2",
+ .init = feature2_init_func,
+ ...
+ ...
+ };
+
+ static struct rte_node_register feature1_node = {
+ .process = feature1_node_process,
+ .feat_arc_proc = NULL,
+ .name = "feature1",
+ ...
+ ...
+ };
+
+ int worker_cb(void *_em)
+ {
+ rte_graph_feature_arc_t arc;
+ uint32_t user_data;
+
+ rte_graph_feature_arc_lookup_by_name("sample_arc", &arc);
+ user_data = 0x1234;
+
+ /* enable feature2 on interface index 4 */
+ rte_graph_feature_enable(arc, 4 /* interface index */, "feature2", user_data);
+
+ while(1) {
+ rte_graph_walk);
+ }
+ }
+
+ int main(void)
+ {
+ struct rte_graph_param graph_conf;
+ rte_graph_feature_arc_t arc;
+
+ if (rte_graph_feature_arc_create("sample_arc", MAX_FEATURES, MAX_INDEXES, &arc))
+ return -1;
+
+ rte_graph_feature_add(arc, "feature1", NULL, NULL);
+ rte_graph_feature_add(arc, "feature2", "feature1" /* add feature2 after feature 1*/, NULL);
+
+ /* create graph*/
+ ...
+ ...
+ graph_conf.feature_arc_enable = true;
+
+ struct rte_graph *graph = rte_graph_create("sample_graph", &graph_conf);
+
+ rte_eal_mp_remote_launch(worker_cb, arg, CALL_MAIN);
+ }
+
+
+
diff --git a/doc/guides/prog_guide/img/feature_arc-1.jpg b/doc/guides/prog_guide/img/feature_arc-1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7103286b2e75c49b5f7dc77b2f8b0c61d8c5302d
GIT binary patch
literal 48984
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
bQsDS?UG901$3M@V{$64D|NG;pKa>9pi3Yx6
literal 0
HcmV?d00001
diff --git a/doc/guides/prog_guide/img/feature_arc-2.jpg b/doc/guides/prog_guide/img/feature_arc-2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1a3e3dd0a62028bae7090edc6f536a73910b58da
GIT binary patch
literal 113287
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%0A&(of@<u3Kum!PcL48@V}bek+Wpy;Qtlp8-3#m
zb(IJ>7URsuLt+Ayh~R@z_r+O_gO{0|P-<;{2yV{OQ(MIHv}m`R7x659XW#J}RJ_Yu
zZb}~GKS+wFnm^XF4<%|GAV;;i;m-l@C7U@d`&}C#H!#$_F#Gy_TpdpU(qFTI`Fo+*
z`CQ$5-7T>S-*3{d1r*{TF=Zjeup}K9<c#?v;>9zN&>B!$zJ3VilC7xl^7~VlHEh;k
zT*Z|dR8N%6=eia<Gi9Pqu+hLfM~8tH;FbV7umE-jI-n&cg1P<2(KawF)|#ABvbn&R
zikE)cm@K3A%CheKvI75-VN*R+(b?jh!Ro2_=_iCsEAN5NxTdU6`K(*R?lK$a`U2<p
zr}5>am5}Bc_}7^htUh}Q^Uh4)Ll3&^^q5a<ug*Zv!HI|(Y&+YmQa4%BdzSaW_YCwp
zNK@z&Os$LO7?J4buJ&r7Z+<vXynZC;@rd%YnntG$Ql9B!X(y7qF%oA^#$s4WQYB#{
zsOp$RxPC6_CWg3}NYW~U8`LIr39GsfHm}dl2B{9rIjG%h@~-ofcWX3D)OlRFga1;O
z8uTlf@2Q@6>Ya|qDm3D$Fc9nC?yzr&TAq}X$8c&wL$@9Epv8DCg!e~ft|MJ3qh7$5
zDpCGXp$RY0wJu@4P-&Vyj1eGDFt<Q1|DA*Tp2m~;f5J_w2>pbjv?UDJ`m!<Mv^OPe
zdR6evxvGznS%RkfdHG)z$=j+1Y;|x1aEb%ZW#ZVnAxv<C(~>ig(;0}`#a?`5`tF#f
z3T{`}wN13fxHe1X?S!|yoMT;<-s){Jo>9Y#4vH%|2L5~Gw*+1im*Uzo_+WcFoO~V*
zCDupgx;K!)>XT8}7}@gO%Bz*a3(v#tHbzWFgJS2b@@=O_w1p>Dr&@FzgM5qaU3qx}
zh(oebbGN5?%mx_Z^*H<4{lrTZnu~O2zg@dSzgpZ*@wqcO;3RcOyrxy<8K^@mOvqK8
zHC+pRARqA;!nTpRd6<UIIOJTkotZQCr1jZ-*brfS<9%XUwS2}r6x)pau>*+?C?mMz
zp<6M8<(^~uI6oxs50#9Dy!8zA)`j;koL|lUtq^*)XRBP=La%UC*s0B^_S?w^%1hP;
z0+%xKaZEzbZ89cW0@1p-d5(c8&a2az4`lkVUo7#jE*GWz2<Ny&xAx2sbc8)nBYMBa
zxFY6wZ2{D0_ya%zzZ=WbCP?lcX^~jc;n&4q*OV_{$O~!C$bP0cB-_&;1IIYWIqEX&
z1xYM!sY`#Nj{onnKg><?Zw7qu8t>qSIJOHNxnew#c4N8fbw%HtmGsZ6T}bIQip#f$
zybm|^w?7BOd?J~Rm_H<}))cun`Ng<2erYt0J!JiSH<gBUX-{K>Ziwst&2#@vESOFp
z@aG8!ey7=_(B<_he$I|CVayb%(TwMLLqpJG?R<^+DA2{!c2*afro1)}K!t5vgQ$d~
zkJ2<MA>9JNxf{YFquVMn6)ItT8<}E|43`In;gp49pbpCBkKg^#T)*b>T_z9@A#ova
z^c_eZ7`jVal;|{xjU$6+y8`hcEP`FZMWxzEnmO6w+12<lhAf$<oHEMY@0J(hclg6a
zge!;+ou(Ez=QvM?x+(d>#<9vgV^2wA*k7h-7fad(_h?I==ie`U8{~6Pd()^wT<Zi!
zal*N$$n5j$h#K^^gb%m_!e|SFbk%1D@4m8Ulf!vVh4jDLWE}F_^!ij?-AmIZ#?N;w
zhKFqT69#b}v$xf<R`bi|-J5O_Sw5npYgr7t17(XYo@;~E0#dMwcVC#4<EyG@WjG4h
z6iy3tmI||^x}bdIVI4x;Mld*5Q<j)RKE@lL*vPb6<JXMqzPSGQVKeKM<sSWU+mW%V
z&)|Sgn@NN|9Y7z1v=JqfK4a-Sz_a;=Q!0=+9YSqygKGthYPUkg3E3aT{(Pb!q9QN$
ziulX!U$hUbvR5nE;-CyFyTY)OUiI(UgusIuf@w@Qss_dPJ4cAf8zH*|zdO<^Dlazp
z*~r&p&*rn9*bKz$du*|2@9|$~GKO%^hC#;}Ll2bUxEE6Jo`zU$&9N4i;-W%V-wSnL
zvreD3CgUf+DfKAx5a$(=A+jlNl&I;ZHg_owD;IIzZ!t~@u!nG)Oa}xn5r(Gnc@hwE
zQO;#T`6t?*kDjQq+j%+ZF?|>3P|UseOBNKQ(^;Xuzl7<TL-7J32M)mBXWKYWwT^oA
z2ODjSc4`LW9?x>_<kctne_C%Wb~yQ1Q+18*20n9%c?wa}7DqV7E=g_c7gHi@TeA9C
zixDek$$847E~YJuSm`TSelYgzrzyM96F-GJx(|N%{5b=4gXes4nVn~#IJGd&Ns`|6
zDZko=P@An_{q57Da5+U!O8zAGDQbI5b3_s4mMXhU`|N@nlEk@t@M`)D<Tm`{qyxd;
zV&qipI*Uvc`8wg)P!)aWsj)#*_EwBX@^;JId8UuuyjKml!a>@W4X1A{ET_XnH`=ZN
z+$R?BTa)C=urM}OAo|dbs1`A0l=lj6w3KnKZt|0{m6$BweQGS<o|w1%H$@}KoovG0
zO}oaa9kLjlni5Ycx&^Pr6ce)T0gNp{?nL&APuzCJ81_+Jyx3(>%LdBDq4fQWczXYn
z>m)y5{p8s!u-GHaLA2=fW`M~U6LZkGVb-F;B|AN<riraBIhKYwVZSL^^TX7FANMb`
z>wY()8P)VY@q=+51bhH|@w^EZ4~PK~1x2NJs}>cU)`Oj4!$?=joo(5S-4oUMSIwo(
zA6R0CTGOq<>7(J26zc<tA!`zbV@djyJy!{!N?|;GKL86dN2_nUJZ}tl{<VJb%dVw!
z<!LRJNyT6Hi)PPT*zI{ALIJm3VZ>+++>2teizClKM<I5+J@RvGxZTdnEjo1h<@Oty
z3s>shw=Z>d1*;s!?xMFJ%BV~P%)*tvdG`MLHmBLr#4_VV--mEP0#`C4TWFS0k;mu=
z?V06n09!dXvtHwB%~Sk26a_H|oRD&neG-)PjlD}TH070<xRsiu$UA8|?3R^lDO@OP
zzw-;64;cSs1uO0rK*!ZVIbdZE39SRNE5EAe?~Em|m>J#8lwo$bE=V)@^94V4%)etU
zY+ReZr5T7}4*ug2#<c|d45={@9W$!4=`1qM7I%tB|6yQRxx`29W9yajPl5#);UQe>
zO+4w9o59~3%Upw>H+UDjwsB9Qy?UZQ<v7(%r09DLb7=$|rQYFwqS#%-_pK@#bg&`Q
z)51$Rp9#<-vSFJLF|`P8(?Hdl64Qe9;FMAH^2pzW9}PztQ(5`#SG}GK+eQvX2Rb;s
zppyQP2Bn0);czJewtJ@KatL1I2YS2iUCRxhuqM`g$vD^2fszNkcK4sCD%gS*6;?EH
zU3Wg;d`=y%mGM7(%zyVa|GV!pzYu_o6p5=>=Z6%oC_5jp9>aDc_%!{t-EpBD&Zj>`
zd?Zf$E+)X5{tEu^qy=p?n0?qVHGkC{EHVGy0)02?^5Y5#z8=-NQSmy@w!RI_(kB$b
zd@)9c0MTKZMRe-P4##S`R$jKF?^HLFm{f88MbkjG(o2byA$H92M~@#VoZKM=09eXI
z3QTH`6C-W4FA}s@Qpa&?F>>~Tv^3Y%Y9iZKP5jqfG)rt#Y!@TLgekMvY4;g6-5cD4
zO>7efGW2}PVXqEXGYqbuPKXuUE1`PCMHNm5qB69BHWMfgLE7N@he_YzFi3SP;yqco
z8P#JiaVkQ5H=8yHJU2_{&M&K7BZGe-oryX-Th4$@r?vAdWy{)aFr;?e<0)92XY~9?
z$dbQQ<A(dKc$vSkx30w}a;NINcboiZ)s$}YHm9Q1Cd`A9hU8!SPtp(?HtZscA2?oJ
zbi75k&}yST=DN|iq|!#B_A~7b-Ae3%&FNR|>y(F1tss87tDynGOXj+HGp$o#e$><a
zPQnex;`t9BsK%t2zb?rwH&vIQKMup7@<X{E54Cnut&QNv#~S&&*X6t6`_VF&S0)jG
zp{|3LI+=!Rf;vhEQU_N~TeJnqizp`g_%LC2sE1A91-D?ku^@q*1f3hvtIVlM9Uc6S
zJFa}Yfp$c%Rmy;Q;H*H6E72TBR|<~S7F*Xl{Dl0&#@^8pV*I0nYV^ysd0o)23+0$<
z48aUMci6Q^xVJd7I@gosL1RD^&b~a}0>Q0I%=>(DV>rZngqwK28f~Ln`QzjS{th?o
z1j;knw>CpYGP@)wkbz`V{>gF|nnQ6%u0tJwzATp=Hf?n{sU(Ji7t%%!kEQe4o}FI!
zuV7s=D`4R2-}!~+6SjGP-Xv&wl4A3b`UIAI{lmA=#m>x&Z*GE}Y?2`~rAq?GO^wh5
z42>q|$xWD-kRUdPmC%q>o8X?_8jXdHJ(;M;`}k<o$#T`;9`2bxGu_Pq{tjiTF1XHV
zCfsVt)X^vdg2eFrZB@Dp+b>vjUEnn~{+b5T6VuJ_xps?=A^En4-;*Hpuy+EAp+$m-
zI-or*Bx`Mw!L>y1_%J0{*S%3VtxAmhyFUbPF~Vw!zbg&L8@sS|an9bh=RNno_I|SX
zje4c?1Q~%~ZZUVQgn2JvI=$I81I}g5-5ZCC7SGz*@H|L-V2NjLeC%{xGg_?Ge`=9h
zlg)W)o4%_Grqr1NLv&8xgSs?1xbkc|=l{HUvXnJ@AzL(6?I`69b@3bBPi#YMvuLYP
zxaksjmIg1M&~k1`K7N58Q1h1RN-2duASURkq7Y6`z1Ic{UHgmdu9`2{{xrB<5zxTd
z)b;Jog{g+EL&8ZHrgm)`LeT3#@S}5y?*W|?Eh`}MQ8kfeq^W+=pGALLe<IW8+ufg7
z<`*_K{1Jk`BIZ^fsvI6BU>tW}qb_KMhTvO6aNb=N-aP{8H8b;@ykvozMod%0_E1fa
z_%46SXvIw470XC|X@l{2h#(mYJ>Mp2ttl)9SP_=HFWe#AA#T_UR5u&Ai0+9vh$p^s
zeEp7sz%mNu)5L>FQvg%NM%K_;M}=UQb?9m_PKVKbk-be0kkU`jd4$xh;<SQ=QvXWv
z+H<4t$BI&)!=)!XcS54uE)&69rxXNOjR!^d1ehnfcU$Yk`XS?omt`Rjh#8G9hb|rl
zRCv?He#^**Se38To!qIZPgr^L+8j022dB!lfpx(3gyRZ|+A?VF+L?MR^Cmn8f8*)2
z_1+64E!YR_tnpWdv_oqsKxUM^_9WZ_hlp7?Pgsms*;PA%SdRUXvC%j+9C~9c^-<HM
z+NkZOP=mrC2qLC@p7aeEN5iDL5d;(OBycUbn_){nOl>nqe3}v@44#y7|3xw^bbOw-
zT+b5Du~D4s71LF&|IIs;j9*!`NcttUi@e}gPcha|K#FS`+>|_xqYtO<Z8ney5d@Wd
zy~wdEfPA^|cWM(+wcAOl=ggTC<?;y)xbL}mEtg(@p~q`oj~^^79=+5T5Azt}4^lOv
zWo?e@(&N9Fw~QuEhx^ILYjd=XXqx7&YhN0vQ{F=>&zpVMXg03x(|8tt^M3Em>mqyS
zXteby4iQGFJb}qH<Q@!*riLRCjMGMW3nO{*eO*gPYLqngX=97z-ip&#)0Yw%5bYpe
zktiDB0A_p}glLbdB>4gg#6x^T_b?+#bke_hmw$uIHPUh)@6G4-V)G2d@~gJdT}Tmo
z_e<SqWzGWr#x2{D+P9{@;<!ceB+M6;uAgDwFf5g11*vYMFfi;AB$j>IISzHJPQ4Q^
zFYd7G{6T?thkW?+DA?yF_#FHZkp^~xmr7Ov$1czTzC`=!10RA{N<L5yze{u-C*ANY
zg*@fS^$m3_Rz(KbyXs;2n!YOBG2F>dGC0VLJ`E3JbOqNQA`y<K@>nu>w8fIl0O))M
z7JMshk{-r-*YSNClQp)bcgP^}n^^TjMa^D*cK=gMDdbc)bg{*uE>ytn@Q`RSHIY7k
z>;9$ssXBSKS-TCM->=_3%(?R|o~z~w-LenXx~3>>Xp!{~lpS!itRe8e+Ezi(&Il=W
zHH`eu82$NMHA+Cq+f~GLis%t&my&K*-o+;wu5%-%`v=_g3`F~t7?AJzdCms5e=rY3
zA&Okir(SN#m@}WqG)@4$>D368lXw{{(ykF`-r`GWFd<NUm;9XAa@%{{kW{zzTGz*0
zP&SfI!^Bk%@?7U8Zb*IMo7**g5tp8CY7D+o*|pU{%rsY;3&SE{-UrHn?$RQUYHI=f
zGT>S0jx3QYpG`tVdwgnk@7bsR`Tp@W@{9eV?)N`6r`J|*Y>|YPwAgS9=@Q78Kb|tT
zs<T$zXJr4J+}U<W4JuGHz5Ecw8~8lH!|8pTc-l>H7KXWv-X6{lES_lSKg}lz5>UyB
z)j+l_F)U(P$gw*|3P{P5{ZQ?ey1}e`Cz1HmSlqlkGf<G0*qI`<!c$FBTSBp+lI@v*
zl+Uxj7qkh*SY-TtcLZyj8cf^2me`h}`6FXmf8t4VL_r8Uvzn?&%8lFg^**vK;y4|{
zFc$K+MHUKb4cP_*7K>~x>`Rbsq|7!221mL)Zi3FWRFI+Bzb<*ttQs{0__S6{OhW0%
z{U}DV5d3Zv5fww`C+Uo|JPq47@~vE75UQ!0s1$Ipejf5V@lNk8BL|yRsShLcpGNh$
zKo;kgQ8UGz!s^r6*6Q#k`8W#B{$?TJ#j#gjsM-*N`ISUes@;13QIN{Q=uP2A!taR|
zxFGME#dRG{zjxR{mgAzdiW2@aP{t^F#@XuwUj_E6@gMF&9A?(HOT7r~UFa@JS>)i;
zagPLJ;zjCS*F{t+zv7dxn&7`2{UiGwXuZ$&mk0|V@uxh?rjzAY>rB<R10e2!)Wgh!
zzx&!tg}d*ao0S=yJ9ybhFcwF#wOD)x^c~g<=e;KXSR9-vR|<14!RyCwsS{I8DK>$r
zj8b)E1wk$Xm-a&RMQv;dMca?3!3(L=)H~WYM8)14utJM&YCUdyM6n-(t^(L3gagy!
z`E_;qIF)xT-gp+l+mp#L`Sxyy>S6B=3H>22J-5YDAL}>7-weq8z625v4~qwah!$N4
zp4#X(!c;@iX258Dzx9}ZO2y)v2l^+YUqxP((@>umi~lW6^9%CYp6>Lm99h~$hmb)2
z{>4RGEdH1R0^fzstUVrm+_|dCcCquoQ&M_+hF_0C;P&iIk;wQxV{}ZLPBxk0<B2XT
z(u@!o??IL%kJ&3sK5ZDEyWL2h8uyS#TNu{5#||)DN@h`2cwyD~`SSB4t14*T#69{|
zba$u@UaEap82-G9Fj?G-VRFx+OLzQ4y8Z<~5caz6)f2tmI~R%6sx?hcW%_;Y*D$LP
zoY!6XjFOW~g`jv3R0)-JCo&{UK(G`xjl=W^A{j}FfZ)@7EW&wd$na4Xw)?mR<JlJH
zS%FJkmf>zl7thnG(uiGo=6X+ULxPLy2u&D1umMnbWD<gvwFWoj*cH=FRv|X@el)mT
zF>5T(WM^wd+26l20y(iwoWTkHYt?0;z-s#whDzRp;M2Eo8#K8O!-{dvJ~{)rBI;x(
zt^tc()4`Hyxqx%mDihH^D2S&)HAZUgQDw|W#@YGdtK=t=x9^K59QRm~*?@>V(#`k-
zQ_lPKlcM80S>J;yvf;*x;Z`)r^!PP;*XvIPf*yeA*LagjhReusMAh0hlt7k$jp88L
z5Ph*^X`+5lnQI$oOUroOpsWdPZJV90m}<4S>dPMR3~5-)dM1DGreN9D`XBc*(2rd*
z!?E8YlgspM8=5fJ?l664>`${)GbIYC{W8O>a4RCqFo^vo=wilNBicDHoI){(p+GGe
zWM1IWi6}{Z<nO!%ej63*`A5dJ?wyCWGz@jO!SvQMPP^^;228Sajb>^pH?MfAfe{+D
z6~;t?%$Mq0Pj3IdZ%-cG1IF<>$#tfBMx&ZHaNCN@y2QsH1H_{H3#ZYwA${dzGPe5C
zUz;8yxAqio4_}Hy+$*6(ZaBhWFQ$gAXK;~W4$-+ivQ1*3nBp#bcEI0%Nr!fQL8OuB
z*>dljWIbkDt~TVIY?eklC#Oo{Wj>=U%^OdXD3+cKXf4ZwV5kG7S|TP&)AzZu4il`Q
zd$#cJa5Vba1t8m^*`zDj@pZywT3)pR6>P@4=eBDu=<&}KARE84iw&OMawh8GoG@L`
z_<o}C@?j5)^>oh|qEn(ow-E3)oA8fxI5fNmo0?sn_j~8?D0SBAriFobidET<7N!$<
zSXuWX9mbPsk>z(&OjL^}@zU-+LgwqX8JZtF<9}KOw3LkZ5<fZcyt8dGVYpRu6uzH!
zC`;Lw#RU3;&?#++budVi%E{-%XaNYp74{<WLKddP=MjZ~p*|t$dq$jWYsptnI*lZw
zq0S%)PpK4sO>9Uam!jShiUlaM0cr~SDFGLS*y5*wiEC3o@$NxIl5Y?Sdux_LRBfWw
zvg$JGRPwCLqQmWaJ$EE*;)I!R76zbyliHNVmWZ#_X){L*%YRze{ZUO0=vThdAvCT7
zlTnQB4cN6^e)0Vyi0i)o|50&<VR{0DX$Tpmm2M3Tu7$s+QWx#b&Oia@9d7Nc{kW0_
zSnO}-k<2MoTMuLsCB8kym%v*su$gqStYbGBMwKm%jBT8@7#=bI3O&$hszOi619E4e
z<VtN}M28qz4LaM>FVUOC`mXotufqA2*8F4*uJ;iKQYuHjOY@BKOfbE><0235_?=v@
z$bWc}d!ITn?9nkQ%F0gDK)XmT4^w@2p}Gwtj#@ydoJs(xZA=om=_3Kh-%x5Fo<3jR
zf0=YDJ293QFLO*w7G#vp3;#znSZJ2m5=iERw1>ngRNi19&XecYto;I3`Pp0_?XyVX
z-D}|zw=6H;W0Q(Zgi-_Ghd?I&R7ZDIHf*lUt>(P!lYI^ym0{h+zWcx%nXSbi&daJO
zg4zN5q>7!_fUI8Ebeo2!lEte#r1q(_85{8!Z;h%qcb9fR$$zoG!8JaoA23UKP`0J<
zUnkzL|8ASf8s2QvYn78liA$0b``<7Oi{$%!WdyzwRUTRJ-YS3aV$E-%K2zLleA?e{
zzQXU%460&+u*-5)&ML9%;oE$hnJdKcEL2@~qLYv|gH^$7K-Og2Gh_zI0++B$3(J2n
zT6bN0sMMAB!Hiv3D|8@BO?tD@$UORiA_M(Jfn2BwdTYj+ICt<Fm??8V?)ojvN_gB@
z>LxI>bw8+vR?T-iS!HeQ2{JPBFqq?JNsG*hGU@7RfuB~`IL^LqImDaSB>-8XR|KSL
zJU6xGYJbYv2{P!1wm|OpDh{s8iFe&%nzLWmA1Z0HY!shdG~JyVd<;`uV(zRkx+*VQ
zcqRU=)AiDchrh;Gk~1e&#<Pq{=6}PdZ72~-xymp=@wSBbr!nNq=xyj0v_5F`41`@w
zUkkEP^F9MveBW*kan`h`fXh)T-rTw5;d>-w;A0sqe$Hsh8wD9v9-?}B;<+#ClzMCI
zReX3EtIuKZuM(|AEZcdQ@hyrY_gR-;GPQU#<!9=R=L$?QEV)%0;&geuFs@>SVXr1q
z_+>t=`m&Y@9u}R2X5``wEbW_1Si#I19YX!bZ89?n5<yQkmK9PHJ@uy&xt52Wy^OAJ
z965y?)<ACWz%%tj<(Iu@FgWnmjATkK+;kf5AG9pEX}PmoK~fVcsT=J^`HL%2fA2yM
zPOwJGpwv!FG0srH8N=(E*JUoS8x1#pDqNB0=AeagZ3w&hbY@!Y`Uc|%<|Y57{hxA@
z1+L?m)<B-|yGGsv$A|mY6>Rb<OWy|NbJhPIL2G;-iSd`-&CVF`v2o8|7(2AfiFs$?
zvm`%*>BVri32lbSjMUzc|HH;guO2n_^Hz>Q9|_y36ayEf-8P^48hlDaKy<q!espdw
zS#+KhAjwkw5{C&6{<JX|I=;!1r$Vt>2c25(*|EMJ2CN^%BI7?A+0C4RuA0BCKayAz
z55CCI#dmSG)oQg)0>Wmhf5TvUtyG24{o$Wx-%NYUyHn0<E8M>+e4389Fc3V1C?EBn
z-?Ln9a7O9bQARbK@FAzzQCer4PN-~nbFRzbGG4m3RSMQG*(zL&UA@lu<KiO|9tmr|
zU&|6RJu_rceXhrAeD^6)sX5{~3b{`S94RG#Z+UvSajjEi2U)Y3@=c?G=KRR8h>iRH
z4`mKdiKe$d^LMlSgVwg4a`o}@Gc~<$OiBiY(^W=TMwG>d%>NEBTMQAsEFj>wQ~4^N
z&5Q@`TR>NS&m7Z7k&K|?cn`@d3Ds{WB%3=uB~eA2Lt@ABc`b7X%EU=*58Dh44ie|J
z;3`MbTiO;g|F}B*Zdm25qJqc%hpO-HW9~<qXCSlxS;qUXK&vvbzzWe|4Yw2J>XdII
znU7y&|K_2rA^9EqF0poOIfYi{y}0*ICH=R7^Yo6Mh;v&t1xordb4DvBCAdULt?Cp_
z?(D#ekZW2SZYyLq(zOP_aLGlvWXE|zZf0Tg-h>>h^F-w@jiMaAjc;c3`u9Fx`wZHU
z29>A?nJwO_Rhl1WDQA3Sl6L*}x!8M(uwQTJ8@aXa5p|-KcLjie!#k5(6?L-m527k6
zE4;)w^nC=G6{~a3i;SE*mchcs&>PSi=h$+REZ%)H$+#oy^`JX$_<~*Ev#ef&>qxV<
zH+x3cExq{2`>{WdnytMZ{!OX#Kh6=W44!^#9|w+8pLIJtzEe-6m8lnUH_I|+F=Ouq
zHAZFFVXmw&af>`Dz4(|bOaRCx^eUXDSFE3b*p>T+d9nz08V?`!;k=fR{b@9xLi}Zs
zS@y<k5Awn_7mZI#;9A}Y?_!`x2-2=zH+Xb^4i-=4$-IYnDOe}#uJj_za^?AYIzRuP
z_=<9usT|R9$!bhpeOt{}yVMRJj()DP-vjNhwCh%1h7G=fT~|i2lJv?*+F^Is53;@f
z+=6(28G46@x;s)Wqwi6&ipIq_Ft?xl>sjT%toh#!ClB)$#uk)aE77Z(u}`9?f}Uxm
zQ)zOJaN4t!k*6{2SBs}=wPZTg4HI{3UBiZh@ZPLUWvw!19=Mjxo6*i6=_O}pjplY<
zt><W<XBrF%7I7+qXbvl&Xc0?RNiwZ3EVENgn7{skr{36}<#iC~Nc7<c>Xo@wx%r-#
z&j=Rr2p0n@l6`|P7a=5R6nH+ncB=8f9CdUa!rz(8H`H=f_m^fegYJg9aEue~qwpML
zyh678>eYNp&+Q64dk?jA`=k5eAfYJhyf>Zyu8hEHj^FKc*GUMwm^^Efkfii*e1$jI
ztinCQzrYT3YlvObPr{1z@$KgZ=Pcs~fEz@&{ch{@wJ7)_Z@SFo3Ra2KorS9jo)3P0
zEJL_JWNC63f7L?vbO<%a=X@6VwJ+!qZ4B#R^3Ee9!@L2nKc;Rox+s&cy-JbYDc6sl
z#kE;oc=O`LO?pTD7mM`dtG2*^@eo_Ls>1Ia<&jB5ZS9|zgl|c2G6TN9cvhcA@48W-
zc#XNh!uMY*;{Uxe{=fGwOi9nr-2~2soZLfnXlu&?f+i5PM^h~tFJ|F3zH4@R39*i6
zpa>%lxe#X+pz%ZpAj?y@Z|6vA72WOG&3xVM7q{Ni_45+^pF&4g!YJ&S4g<uqITRo{
zy&YPzaS&i{*uj<<oNvFLd1vt};d`CPyw-=v0w0r(9JzS<0qtdhQ>L!O;~eO_CYAZz
zM-pK}aJ9X8_;4p2vD9J^^v00;6;j1k>1FkN2Rc+<#YtZKaYp&7OI|!VRcCnQUyBYj
z>rlos(17$mWJf0|oj<9wtx9BuQX<+IXcUg35GeM%CgGxCS<|n*pQRlp)JWRggNS>`
zM+^@@Xa=o;;P@jCZ)Wpuocq%ijT=MX6^$+HQ+F#fA?`(^#|99*EF7cI>F7FUrtE6)
zc4;A9jQ@svansItGBV@-gK5WY9cp)V=j1l=uyH6OTE$oh{E*nz94MEdHL>WN98%(4
z{FKRPdR0<W-Uk!;;h|2ocYA2yfdJ8~hA4w~`vG`$8H~$OARI$XwEIVgmG;>bagVtO
zs^;W7S~Tp$bncI4{Mp6kY>ZRIkMvDnZftY&4ji>EIB|!R1_|*Y*eR}v+22E6Rg8Ic
z-FVhoChKJ>_w_wBZKI(&R|;u<xLlcy7IN}5tSg6^fPL*;-9$upm7R3><h4!&R4>>-
z#I9#4OWg1K8D8_`m(h2~Pzb)EGgR;32_+s01A@mNW(<S|v&CI-%RIPeD^4NlZgFro
zq`XPN4Bq*%^dTscpHF3vg&Td0v6z+aZP-aSG|$Ymsh_j`IcHbjI#;=NW%+|r?&V=d
za`kV6nA(G!-K!5B`9rh~j*c9;D4i4i&8U2gK#MmKz04cy0k<INbO+lvEL8;0N}cG;
z)DCMHzLU1%@UAt)@C!CN=w7`BvIW$()lT%`27n;3*Z>xG<p->+Cs_6EL4yU4y2RUn
zS!0v?g>?@u6cj6zUcJX2(Jm2w<Q2a<2)h+6a6fVK#;wa`A=0JFe-2mBs#z}iVGUZ$
zXP_QUFGq%n!YdP-X7|$&Rwe5_#erL;m5-PP9}QjJ+ZOZ+H9gUU8xnW7|DG5CI(o!~
zT9qNDE;Y7F+cL}9xq|z*xjrt~x;NU9zV_X~o%rW3BPcu)GAAV7dgv#VMVkW_-K&xL
zs`JE}aAI#i?2&le>6a`0qcuLh?=mY{GIfMDMoW~tbm4hy#<lpB`^{bugSw25Jo69<
zSW>ng)nAu~X=OL|C(3Sw7@3XUF9wHdNXxmq?GBB~fbGM6jv1an$V2wjz?`!ut@>Lx
zKY4b2uh-&=(GD@^;vRB%_Zn$9Bc<YxSmR|dQiEZ+F~{Xju%aWsS=o8Rzz`3Vy9*5d
zs9R?aw!6(g{;O2F$8>?TpZL&r+i7s<+3d7U^9Ci63oagt`iF`W)Uf^dunF6V0`zNR
zqbNnr5QIA}>C#*POV7~HG7dEQ)_mEhS7m-xeX480ou^U8{bFAp>HjNNY#Tw)ht9Ab
z9yFg8RGfia5FD;(-Y#zt{}zdcyVv5x`+?M8w)qc3h1+W4&(1)DCf7QWqTm)nYZH50
zl`k;6SETsRc7jLpz1Yn*CW5_~4qja)2;m#^*3I<Kq1`grO2s{(`FB_q91Mi;c0yj~
z@DK_(7@iQmr??hiMeLfV&OZ?GnG;pFFx2NJwIlMM>T}(r`Y#GyU;^B#7{=Mfd;A?8
z$P)d&=;ooC1xG!H^u)6?Tl)6h^KSN1M4{bo1n2CabH!!`e){fs5uYc$@qDZ3)7c<1
ziFbeJtJH7T={<-gk72mOdRfmvR4^WrV8@ge;&E)WC1&>3<d>nDVn)nfPsHEjCzRP2
z`6pGB1~R*>5frXoNWsR`<Jsi*C7n};j~m$IF6F55IXP)w&<{dJx0sfMea{iCCSc=s
zz5K!FH<V=2F)e!jqNz4F`0!fV$9nw^FTVU#JN_=_ySnY#`m*GIc~y{oUJJ6zUUEXm
z8FFC;=GcAMppJa)&z~{fwIJH`reF3$qK_~tRP<LBC0r6bb!Kne52#&e{7)Q}MK-Kv
zx#hSacBe&UNmg5)6Nnz|wwQ*9no8+Ok;9&YgKys0yYwxFCNP8b7_`wQ1~(hk=7PaC
ztXKA|Z`$fJKQ&349|+sJ*ZrxbTJ70Q%VP@^WNDY1Ji2Amz35GFekI;#N4Ky%IH~fw
z4?8EBxO%?t`_rg~3t>U(g?<$HnsyFwv=<he-d!a^6AKasLam9^6`zGo9=}1R&dPm|
z`u5}8eBV%DZF@cr!{WNYGUDmiD%m@3GVYPjZZkXES*1svJn$Jrvu;aa@<1c(xqwoC
zmW8T<TgQc;-_}jaexy*%t|8WvCiI^pr&oMNf-UUyMKs$W=SYTtJEvL<+|X&+PK(vT
zn5cM~Ly$jo#6<YC_ffluJEpv+W03JYdH6f)+*W|S+Ly^NA!y&AfNE206MPFsE%SS3
zJ6kHRHbJy+;PQ1eq-0n2(uAzUD_DLG!w6RVrS!9uI~@<$XxS(3QFo}@cBUF^QvT@V
zbc2~qByt^smO&ldA8S$Ykys4(_jJS!Emx)Fj)_~0tva5bFzDGN?x%4Wtmz=aZxwg5
z#V)d}?2#Zi8*g6M5)gTskne$-FC1SB-O$-g9IqPw$v1RO%PfASO$hErXy`%QP?mLA
z*x8xXv;EPB_Cz>+aQt5X_UcHEA;+a0_tJuY`=HTt6OlMJ(pA`16(Plw6SkfOuyeNl
z<dnS9*0*;0=!bp#o`sBgcdFGu1>Amy4*@MAjC3o0-*~bd^QFZx*TdBDj;q?yxteMi
zlxC<t&7E%r<jh>oJvtiVhSOFgiG3y3)(afQ?23-Ic;o*17P@hWx;Pnjq>il^D%u#=
z1+spAWcT7X3n<Xf0!)a+{{eH=p7cr8r0Emo;x5g(2g`Oxtc^>GiLLT++*)2uekAzJ
z9wwR|!uWrz<wPdoG*`!Yn4Uaw2BQCqriSg_uA3$)pu2v}&zdQWys8#sdoON4l|B*u
z_?~WG<rt-kD{x9NnU0uPDB-RtOb;(w3R_<CI}|oUE?>`Avip4=%ivj>ml&?S@r`b3
znNP}L&1H77IJWkye7W3{)|y6LaitCs*oyhjYfJH`TT_3GbvS+>S_kC2dLoJ!Z%-Yn
zPAgpf^3$NbOe~+8|Bm<tgYzm!`6m?UqXXlpJ1q}~o+xY{gwamO@eKtneY7jc?g~zL
zlKNE4FC!Uy(r6YQ<R<f1o11v4o>8H5_44<uQR8iW#P%T3J_J0JBXJ5QG`1S8TG2do
zZ*`(4m>xa`#7}S7{H}BE_p=Zr(%{)*b)KX7q@u`bFeMwn0^Q6N?WwD|E%pF=t^#eY
zpiY3;vc6tzYSnnqFm=_ju{MX&fSBp`vJ*F^yTSKDKWsk3neS>Rs<vao+};NLsOcyl
z*o}pmtM!$gLRGs4($@X+&p^zX&GKo_{!qM`^YFhAOaJe}=SN@)@M}g$PM0_+3w15F
z(i<H1RqPQswTk6deCzO`U@N6i-M3W5EX%Vsb+Y=e6M*e)f^_Y&4S(LWIGFW9c_@jG
zw@nI%2Z|rih#zpH2%c@1!e(ao*TGaRG^MZ~hEnOic;mmGI$c?=yKO7&`{z53#Ey`J
zRg5me0R@NzMv(<!se@P@p}(=(3b5mu*Yz2So#tKEOwoNOTBX<zUC5lLk>LS!f@S-a
zqC_$I_F`kY(+Y@uRB&5c!mi=2Ez=*wu+n=&_H3!B+|!kletGFol$G_|Ud=BG&L2pL
zjhCUo71sP>AQP)9fC<Mq_pNYZ-4)#J9I^2Q+*B}^tHTdSynHhsc993!?F;myWC|m~
zm~a?VJ18rtH!AB`{pCwdtp2$Ig_4I!TraplAnuaS>^EsZFF@PI(1_K>dKraoO*1*$
zF~m!|)ZfVMHUV5>gGf78ZeV@S92yepB-QMV>9-Pp6)%mxe)#Y6GU|Vqr~QZDTb7s6
zNh-$K3G%eC$Il}vw5Fu@=nKv#bln$fplY678kfuu3!ip8g!^H*$P$)t34;DYU099D
zm$&f?Q&deMCvkk@sziCBky!O-<l{9O$uDGDk_v`Uk(`SL()zP|b7WHF>H^~$ur8m^
zM-NbHe>vA=xWNn0Q-<YSBPL*i9oB(e$n&np!fEs~f3_95UvXWviWRbYxAzSz@ouj>
zHAtmGpDX;|@^<p^P!?CR65;PoTA7uNoBH#7CYyQdp9bRbupdv;-x74WSI8HMj}EtR
zco{Y(poknoAt$?~RiI)tbptO4c{+JF7TY)eYFuaDCf871{0ES*FJI(^8JZ9eqvdPu
z+<NEC$Lg+IFKkTV)Ub+bwCrS`n*t9l5@Citv#70Ud<ghFtnGNsoRCwjI14y;Pea=7
z*nKrsKOH93ZF8-idNX2&I?`r-|A*Gpa`{*k#~+<2VZl(FZh=Gr|I{x@#w$5Nf;(6N
zX<6|^*u$HaIpNb%%Sa@rt@~jZA3+ku8{cB#VG?+|zs6)#kcMv0XS9*N-*gPM^O@8M
zi9ywDsY}I;_h?_9ct(s#y4I;>uqu*qY~5g4Ua|LhUZLZLt03^pE$ov+5ptPrR!eQ8
z>mh(xiVNGxW-by6v`R!C!O~eh@^4U3SN?q@QPcUdeKwAbe%&5KSSdp0xlRQ?&UDDJ
zYpp21bv+O2bkYCUajH?pmCK-iP`Q62RNvtGfD+C-L4i_qWuS|5DuH;SYV6V#7OaHB
zT;wjqJ@tYN310rSKlIWWSKJMb?tr{*f%3+ci}48<CMxdGA39EmLT`lCU|;_H+fLnT
z77yB^G$pu|u`!s*l0{sChr9Z#xfV9=NRAdbp3VUYw<5=f6%^K1mX^nB=kG3BeTm)}
zgLzeWT`=Mr`ARX)eNIn1=qDalch_|HZB!;RR)6Vx68uVLNQG_2=H|FRzX}_@-3EsB
z?+u1P1l!#&RzGiOB$z(in5tH|G5D^5G!#R-8m^@pzkiMh?M6kPf#e!KS;BTrO(;=H
zS66wx#qY+YekUKv-u7R$<{1vGKMWZI+k{AP0;)%F-NUcKUZHF*@u#a=l~zWv$Bk8E
zt5+uZk<l`dwM!rFQE-ZX36|-ph#@$k>Da&7U~a$xzj&9}8^fvR?t6v2Y+iqrf6MC&
z!?hdy%!;GhLcaSl8zgC<%86)W(3mPMr#x?Q_p7RPeY$^yf8oz?hIk4M<`uut%TzI$
z(kt!LkL&}9?l|q-K)eQmA5gam0SmakIbyr`?ZI$2&!>zNp((2Cv}s(Qc%pv1;TO0w
zAMyLGg#sZPUymY&bgtd2NnRaToeHd;@Y}3o2rRUd)Eo?Y%RwWjOUqGSURju)j)dI#
z52oaQWorJD&zYfM(hW+*u?NX$=VC8CQj6el$sDX+gG^psZm#<fto)L?0*D9HmeC}^
zY(iOgaQXt8kkF$o2Z&bKR=t^9S3<bBc&!0By}p~sbP4^7xKJHE=?a(WQUkZb%h&t;
z&4zakRpsepddB<|?wLo<8%d%*2ZQ4fT!^L2#k%ROBk>_hUnDOPtsj%#OxP(<t}rP+
zEU0gsjiPj*#5Q=jcfjpVZea<L4+Rk2=<~1>TtI%aa~dAX_R~7Q;e_Mdm4OhoTQbLT
zpO1^u-78)9X6@&6gWO*<OGLxEQ~KHxi$#zXp_Nd)YG9OYb;)P>-7@l(;a%rn{JY_{
z;P-0kes&DnwJT0%pzt;q^xOL+5)86TX}Ji9_d~G%&PwrKdbOwKvt^+XaUB19^RM?Z
zFNytx!mZ!ctbrkyP!R%n_)&v^C-T5#FKxb{R^f*BuhkZ@9t6P?(VmbYivx3#9032*
zzFK+in$ET!FUNN&+s#BYx)W#~=Zrq;h2}#`kDfdIv6>u||E6soL!ReHY!`R=SxhX5
zQ2Jh9!cB|Q&Oql;+q%n4AngtJ9NyZ={Vkhs3Hv=Svx-R<3=iVRd{489n>cb+GHO<v
zGj%+0dLV^pzar1Q_g+5iqyAn3rS}b^w_K%TU@skcI^?DV#b+TBWq}+9{SXRpK)8*W
z-TR3W!|K$m3>SaMDi1mXwK=HlV=4TNne2l`z-bvJ2zVP3oFlcx+`z1<{dLoXkFArV
zG23t6FR|;2$N6aA^Ihq_^Ta?Q+8e@zS2c}A)g)$xxh*CQv+@23!ILz#3UcXMiV>Yy
zQ+;Q0`(jnYwiX3}V*o-cV3~JX0skdW4gE=!LAuJR>aomw=Opd|27#TE>`Ak2jH;m{
zQUr)`B+7T_)H=tJ1&4^#p3WA8wnDi~9g--Sd;G6eQHCUwe5R+?AM=ms^c6Zw$FCI<
z*qPw6D;Hj`Nbfp<hx$bdm)JN4-nu{hC~aV4|A6#XP^<0D$9&0F%34xjq6-^{T1clX
zZ%^A*V4aB7{l8dy�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
literal 0
HcmV?d00001
diff --git a/doc/guides/prog_guide/img/feature_arc-3.jpg b/doc/guides/prog_guide/img/feature_arc-3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..36b754bbd4072dbf558fce3db69aea436d34757e
GIT binary patch
literal 93408
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
NzYt1cf1LmSO#nJ?14IA-
literal 0
HcmV?d00001
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* RE: [EXTERNAL] Re: [RFC PATCH 0/3] add feature arc in rte_graph
2024-10-08 8:04 ` [RFC PATCH 0/3] add feature arc in rte_graph David Marchand
@ 2024-10-08 14:26 ` Nitin Saxena
2024-10-14 11:11 ` Nitin Saxena
1 sibling, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-08 14:26 UTC (permalink / raw)
To: David Marchand
Cc: Jerin Jacob, Kiran Kumar Kokkilagadda, Nithin Kumar Dabilpuram,
Zhirun Yan, dev, Nitin Saxena, Robin Jarry, Christophe Fontaine
Hi David,
I just sent v2 version for this patch series.
Will add Robin and Christophe from next version onwards
Thanks,
Nitin
> -----Original Message-----
> From: David Marchand <david.marchand@redhat.com>
> Sent: Tuesday, October 8, 2024 1:34 PM
> To: Nitin Saxena <nsaxena@marvell.com>
> Cc: Jerin Jacob <jerinj@marvell.com>; Kiran Kumar Kokkilagadda
> <kirankumark@marvell.com>; Nithin Kumar Dabilpuram
> <ndabilpuram@marvell.com>; Zhirun Yan <yanzhirun_163@163.com>;
> dev@dpdk.org; Nitin Saxena <nsaxena16@gmail.com>; Robin Jarry
> <rjarry@redhat.com>; Christophe Fontaine <cfontain@redhat.com>
> Subject: [EXTERNAL] Re: [RFC PATCH 0/3] add feature arc in rte_graph
>
> Hi graph guys, On Sat, Sep 7, 2024 at 9: 31 AM Nitin Saxena
> <nsaxena@ marvell. com> wrote: > > Feature arc represents an ordered list of
> features/protocols at a given > networking layer. It is a high level abstraction
> to connect
> Hi graph guys,
>
> On Sat, Sep 7, 2024 at 9:31 AM Nitin Saxena <nsaxena@marvell.com> wrote:
> >
> > Feature arc represents an ordered list of features/protocols at a
> > given networking layer. It is a high level abstraction to connect
> > various rte_graph nodes, as feature nodes, and allow packets steering
> > across these nodes in a generic manner.
> >
> > Features (or feature nodes) are nodes which handles partial or
> > complete handling of a protocol in fast path. Like ipv4-rewrite node,
> > which adds rewrite data to an outgoing IPv4 packet.
> >
> > However in above example, outgoing interface(say "eth0") may have
> > outbound IPsec policy enabled, hence packets must be steered from
> > ipv4-rewrite node to ipsec-outbound-policy node for outbound IPsec
> > policy lookup. On the other hand, packets routed to another interface
> > (eth1) will not be sent to ipsec-outbound-policy node as IPsec feature
> > is disabled on eth1. Feature-arc allows rte_graph applications to
> > manage such constraints easily
> >
> > Feature arc abstraction allows rte_graph based application to
> >
> > 1. Seamlessly steer packets across feature nodes based on wheter
> > feature is enabled or disabled on an interface. Features enabled on
> > one interface may not be enabled on another interface with in a same
> > feature arc.
> >
> > 2. Allow enabling/disabling of features on an interface at runtime, so
> > that if a feature is disabled, packets associated with that interface
> > won't be steered to corresponding feature node.
> >
> > 3. Provides mechanism to hook custom/user-defined nodes to a feature
> > node and allow packet steering from feature node to custom node
> > without changing former's fast path function
> >
> > 4. Allow expressing features in a particular sequential order so that
> > packets are steered in an ordered way across nodes in fast path. For
> > eg: if IPsec and IPv4 features are enabled on an ingress interface,
> > packets must be sent to IPsec inbound policy node first and then to
> > ipv4 lookup node.
> >
> > This patch series adds feature arc library in rte_graph and also adds
> > "ipv4-output" feature arc handling in "ipv4-rewrite" node.
> >
> > Nitin Saxena (3):
> > graph: add feature arc support
> > graph: add feature arc option in graph create
> > graph: add IPv4 output feature arc
> >
> > lib/graph/graph.c | 1 +
> > lib/graph/graph_feature_arc.c | 959 +++++++++++++++++++++++
> > lib/graph/graph_populate.c | 7 +-
> > lib/graph/graph_private.h | 3 +
> > lib/graph/meson.build | 2 +
> > lib/graph/node.c | 2 +
> > lib/graph/rte_graph.h | 3 +
> > lib/graph/rte_graph_feature_arc.h | 373 +++++++++
> > lib/graph/rte_graph_feature_arc_worker.h | 548 +++++++++++++
> > lib/graph/version.map | 17 +
> > lib/node/ip4_rewrite.c | 476 ++++++++---
> > lib/node/ip4_rewrite_priv.h | 9 +-
> > lib/node/node_private.h | 19 +-
> > lib/node/rte_node_ip4_api.h | 3 +
> > 14 files changed, 2325 insertions(+), 97 deletions(-) create mode
> > 100644 lib/graph/graph_feature_arc.c create mode 100644
> > lib/graph/rte_graph_feature_arc.h create mode 100644
> > lib/graph/rte_graph_feature_arc_worker.h
>
> I see no non-RFC series following this original submission.
> It will slip to next release unless there is an objection.
>
> Btw, I suggest copying Robin (and Christophe) for graph related changes.
>
>
> --
> David Marchand
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 0/5] add feature arc in rte_graph
2024-10-08 13:30 ` [RFC PATCH v2 0/5] " Nitin Saxena
` (4 preceding siblings ...)
2024-10-08 13:30 ` [RFC PATCH v2 5/5] docs: add programming guide for feature arc Nitin Saxena
@ 2024-10-09 13:29 ` Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 1/5] graph: add feature arc support Nitin Saxena
` (7 more replies)
5 siblings, 8 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:29 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena
Feature arc represents an ordered list of features/protocols at a given
networking layer. It is a high level abstraction to connect various
rte_graph nodes, as feature nodes, and allow packets steering across
these nodes in a generic manner.
Features (or feature nodes) are nodes which handles partial or complete
handling of a protocol in fast path. Like ipv4-rewrite node, which adds
rewrite data to an outgoing IPv4 packet.
However in above example, outgoing interface(say "eth0") may have
outbound IPsec policy enabled, hence packets must be steered from
ipv4-rewrite node to ipsec-outbound-policy node for outbound IPsec
policy lookup. On the other hand, packets routed to another interface
(eth1) will not be sent to ipsec-outbound-policy node as IPsec feature
is disabled on eth1. Feature-arc allows rte_graph applications to manage
such constraints easily
Feature arc abstraction allows rte_graph based application to
1. Seamlessly steer packets across feature nodes based on whether
feature is enabled or disabled on an interface. Features enabled on one
interface may not be enabled on another interface with in a same feature
arc.
2. Allow enabling/disabling of features on an interface at runtime,
so that if a feature is disabled, packets associated with that interface
won't be steered to corresponding feature node.
3. Provides mechanism to hook custom/user-defined nodes to a feature
node and allow packet steering from feature node to custom node without
changing former's fast path function
4. Allow expressing features in a particular sequential order so that
packets are steered in an ordered way across nodes in fast path. For
eg: if IPsec and IPv4 features are enabled on an ingress interface,
packets must be sent to IPsec inbound policy node first and then to ipv4
lookup node.
This patch series adds feature arc library in rte_graph and also adds
"ipv4-output" feature arc handling in "ipv4-rewrite" node.
Changes in v3:
- rte_graph_feature_arc_t typedef from uint64_t to uintptr_t to fix
compilation on 32-bit machine
- Updated images in .png format
- Added ABI change section in release notes
- Fixed DPDK CI failures
Changes in v2:
- Added unit tests for feature arc
- Fixed issues found in testing
- Added new public APIs rte_graph_feature_arc_feature_to_node(),
rte_graph_feature_arc_feature_to_name(),
rte_graph_feature_arc_num_features()
- Added programming guide for feature arc
- Added release notes for feature arc
Nitin Saxena (5):
graph: add feature arc support
graph: add feature arc option in graph create
graph: add IPv4 output feature arc
test/graph_feature_arc: add functional tests
docs: add programming guide for feature arc
app/test/meson.build | 1 +
app/test/test_graph_feature_arc.c | 1410 +++++++++++++++++++
doc/guides/prog_guide/graph_lib.rst | 288 ++++
doc/guides/prog_guide/img/feature_arc-1.png | Bin 0 -> 61532 bytes
doc/guides/prog_guide/img/feature_arc-2.png | Bin 0 -> 155806 bytes
doc/guides/prog_guide/img/feature_arc-3.png | Bin 0 -> 143697 bytes
doc/guides/rel_notes/release_24_11.rst | 13 +
lib/graph/graph.c | 1 +
lib/graph/graph_feature_arc.c | 1223 ++++++++++++++++
lib/graph/graph_populate.c | 7 +-
lib/graph/graph_private.h | 3 +
lib/graph/meson.build | 2 +
lib/graph/node.c | 2 +
lib/graph/rte_graph.h | 3 +
lib/graph/rte_graph_feature_arc.h | 431 ++++++
lib/graph/rte_graph_feature_arc_worker.h | 674 +++++++++
lib/graph/version.map | 20 +
lib/node/ip4_rewrite.c | 476 +++++--
lib/node/ip4_rewrite_priv.h | 15 +-
lib/node/node_private.h | 20 +-
lib/node/rte_node_ip4_api.h | 3 +
21 files changed, 4494 insertions(+), 98 deletions(-)
create mode 100644 app/test/test_graph_feature_arc.c
create mode 100644 doc/guides/prog_guide/img/feature_arc-1.png
create mode 100644 doc/guides/prog_guide/img/feature_arc-2.png
create mode 100644 doc/guides/prog_guide/img/feature_arc-3.png
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 1/5] graph: add feature arc support
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
@ 2024-10-09 13:29 ` Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 2/5] graph: add feature arc option in graph create Nitin Saxena
` (6 subsequent siblings)
7 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:29 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena
add feature arc to allow dynamic steering of packets across graph nodes
based on protocol features enabled on incoming or outgoing interface
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
doc/guides/rel_notes/release_24_11.rst | 10 +
lib/graph/graph_feature_arc.c | 1223 ++++++++++++++++++++++
lib/graph/meson.build | 2 +
lib/graph/rte_graph_feature_arc.h | 431 ++++++++
lib/graph/rte_graph_feature_arc_worker.h | 674 ++++++++++++
lib/graph/version.map | 20 +
6 files changed, 2360 insertions(+)
create mode 100644 lib/graph/graph_feature_arc.c
create mode 100644 lib/graph/rte_graph_feature_arc.h
create mode 100644 lib/graph/rte_graph_feature_arc_worker.h
diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst
index e0a9aa55a1..d6d64518e0 100644
--- a/doc/guides/rel_notes/release_24_11.rst
+++ b/doc/guides/rel_notes/release_24_11.rst
@@ -67,6 +67,16 @@ New Features
The new statistics are useful for debugging and profiling.
+* **Added feature arc abstraction in graph library.**
+
+ Feature arc abstraction helps ``rte_graph`` based applications to steer
+ packets across different node path(s) based on the features (or protocols)
+ enabled on interfaces. Different feature node paths can be enabled/disabled
+ at runtime on some or on all interfaces. This abstraction also help
+ applications to hook their ``custom nodes`` in standard DPDK node paths
+ without any code changes in the later.
+
+ * Added ``ip4-output`` feature arc processing in ``ip4_rewrite`` node.
Removed Items
-------------
diff --git a/lib/graph/graph_feature_arc.c b/lib/graph/graph_feature_arc.c
new file mode 100644
index 0000000000..ff99f7b26a
--- /dev/null
+++ b/lib/graph/graph_feature_arc.c
@@ -0,0 +1,1223 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#include "graph_private.h"
+#include <rte_graph_feature_arc_worker.h>
+#include <rte_malloc.h>
+
+#define ARC_PASSIVE_LIST(arc) (arc->active_feature_list ^ 0x1)
+
+#define rte_graph_uint_cast(x) ((unsigned int)x)
+#define feat_dbg graph_dbg
+
+static rte_graph_feature_arc_main_t *__rte_graph_feature_arc_main;
+
+/* Make sure fast path cache line is compact */
+_Static_assert((offsetof(struct rte_graph_feature_arc, slow_path_variables)
+ - offsetof(struct rte_graph_feature_arc, fast_path_variables))
+ <= RTE_CACHE_LINE_SIZE,
+ "Fast path feature arc variables exceed cache line size");
+
+#define connect_graph_nodes(node1, node2, edge, arc_name) \
+ __connect_graph_nodes(node1, node2, edge, arc_name, __LINE__)
+
+#define FEAT_COND_ERR(cond, fmt, ...) \
+ do { \
+ if (cond) \
+ graph_err(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+/*
+ * lookup feature name and get control path node_list as well as feature index
+ * at which it is inserted
+ */
+static int
+feature_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
+ struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ const char *name;
+ uint32_t fi = 0;
+
+ if (!feat_name)
+ return -1;
+
+ if (slot)
+ *slot = UINT32_MAX;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ RTE_VERIFY(finfo->feature_arc == arc);
+ name = rte_node_id_to_name(finfo->feature_node->id);
+ if (!strncmp(name, feat_name, strlen(name))) {
+ if (ffinfo)
+ *ffinfo = finfo;
+ if (slot)
+ *slot = fi;
+ return 0;
+ }
+ fi++;
+ }
+ return -1;
+}
+
+/* Lookup used only during rte_graph_feature_add() */
+static int
+feature_add_lookup(struct rte_graph_feature_arc *arc, const char *feat_name,
+ struct rte_graph_feature_node_list **ffinfo, uint32_t *slot)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ const char *name;
+ uint32_t fi = 0;
+
+ if (!feat_name)
+ return -1;
+
+ if (slot)
+ *slot = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ RTE_VERIFY(finfo->feature_arc == arc);
+ name = rte_node_id_to_name(finfo->feature_node->id);
+ if (!strncmp(name, feat_name, strlen(name))) {
+ if (ffinfo)
+ *ffinfo = finfo;
+ if (slot)
+ *slot = fi;
+ return 0;
+ }
+ /* Update slot where new feature can be added */
+ if (slot)
+ *slot = fi;
+ fi++;
+ }
+
+ return -1;
+}
+
+/* Get control path node info from provided input feature_index */
+static int
+feature_arc_node_info_lookup(struct rte_graph_feature_arc *arc, uint32_t feature_index,
+ struct rte_graph_feature_node_list **ppfinfo,
+ const int do_sanity_check)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ if (!ppfinfo)
+ return -1;
+
+ *ppfinfo = NULL;
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ /* Check sanity */
+ if (do_sanity_check)
+ if (finfo->node_index != index)
+ RTE_VERIFY(0);
+ if (index == feature_index) {
+ *ppfinfo = finfo;
+ return 0;
+ }
+ index++;
+ }
+ return -1;
+}
+
+/* prepare feature arc after addition of all features */
+static void
+prepare_feature_arc_before_first_enable(struct rte_graph_feature_arc *arc)
+{
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t index = 0;
+
+ arc->active_feature_list = 0;
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature) {
+ finfo->node_index = index;
+ feat_dbg("\t%s prepare: %s added to list at index: %u", arc->feature_arc_name,
+ finfo->feature_node->name, index);
+ index++;
+ }
+}
+
+/* feature arc lookup in array */
+static int
+feature_arc_lookup(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;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ if (arc == (rte_graph_feature_arc_get(dm->feature_arcs[iter])))
+ return 0;
+ }
+ return -1;
+}
+
+/* Check valid values for known fields in arc to make sure arc is sane */
+static int check_feature_arc_sanity(rte_graph_feature_arc_t _arc, int iter)
+{
+#ifdef FEATURE_ARC_DEBUG
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+
+ RTE_VERIFY(arc->feature_arc_main == __rte_graph_feature_arc_main);
+ RTE_VERIFY(arc->feature_arc_index == iter);
+
+ RTE_VERIFY(arc->feature_list[0]->indexed_by_features = arc->features[0]);
+ RTE_VERIFY(arc->feature_list[1]->indexed_by_features = arc->features[1]);
+
+ RTE_VERIFY(arc->active_feature_list < 2);
+#else
+ RTE_SET_USED(_arc);
+ RTE_SET_USED(iter);
+#endif
+ return 0;
+}
+
+/* Perform sanity on all arc if any corruption occurred */
+static int do_sanity_all_arcs(void)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ uint32_t iter;
+
+ if (!dm)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ if (check_feature_arc_sanity(dm->feature_arcs[iter], iter))
+ return -1;
+ }
+ return 0;
+}
+
+/* get existing edge from parent_node -> child_node */
+static int
+get_existing_edge(const char *arc_name, struct rte_node_register *parent_node,
+ struct rte_node_register *child_node, rte_edge_t *_edge)
+{
+ char **next_edges = NULL;
+ uint32_t i, count = 0;
+
+ RTE_SET_USED(arc_name);
+
+ count = rte_node_edge_get(parent_node->id, NULL);
+
+ if (!count)
+ return -1;
+
+ next_edges = malloc(count);
+
+ if (!next_edges)
+ return -1;
+
+ count = rte_node_edge_get(parent_node->id, next_edges);
+ for (i = 0; i < count; i++) {
+ if (strstr(child_node->name, next_edges[i])) {
+ if (_edge)
+ *_edge = (rte_edge_t)i;
+
+ free(next_edges);
+ return 0;
+ }
+ }
+ free(next_edges);
+
+ return -1;
+}
+
+/* create or retrieve already existing edge from parent_node -> child_node */
+static int
+__connect_graph_nodes(struct rte_node_register *parent_node, struct rte_node_register *child_node,
+ rte_edge_t *_edge, char *arc_name, int lineno)
+{
+ const char *next_node = NULL;
+ rte_edge_t edge;
+
+ if (!get_existing_edge(arc_name, parent_node, child_node, &edge)) {
+ feat_dbg("\t%s/%d: %s[%u]: \"%s\", edge reused", arc_name, lineno,
+ parent_node->name, edge, child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+ }
+
+ /* Node to be added */
+ next_node = child_node->name;
+
+ edge = rte_node_edge_update(parent_node->id, RTE_EDGE_ID_INVALID, &next_node, 1);
+
+ if (edge == RTE_EDGE_ID_INVALID) {
+ graph_err("edge invalid");
+ return -1;
+ }
+ edge = rte_node_edge_count(parent_node->id) - 1;
+
+ feat_dbg("\t%s/%d: %s[%u]: \"%s\", new edge added", arc_name, lineno, parent_node->name,
+ edge, child_node->name);
+
+ if (_edge)
+ *_edge = edge;
+
+ return 0;
+}
+
+/* feature arc initialization */
+static int
+feature_arc_main_init(rte_graph_feature_arc_main_t **pfl, uint32_t max_feature_arcs)
+{
+ rte_graph_feature_arc_main_t *pm = NULL;
+ uint32_t i;
+ size_t sz;
+
+ if (!pfl)
+ return -1;
+
+ sz = sizeof(rte_graph_feature_arc_main_t) +
+ (sizeof(pm->feature_arcs[0]) * max_feature_arcs);
+
+ pm = rte_malloc("rte_graph_feature_arc_main", sz, 0);
+ if (!pm)
+ return -1;
+
+ memset(pm, 0, sz);
+
+ for (i = 0; i < max_feature_arcs; i++)
+ pm->feature_arcs[i] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ pm->max_feature_arcs = max_feature_arcs;
+
+ *pfl = pm;
+
+ return 0;
+}
+
+/* feature arc initialization, public API */
+int
+rte_graph_feature_arc_init(int max_feature_arcs)
+{
+ if (!max_feature_arcs)
+ return -1;
+
+ if (__rte_graph_feature_arc_main)
+ return -1;
+
+ return feature_arc_main_init(&__rte_graph_feature_arc_main, max_feature_arcs);
+}
+
+/* reset feature list before switching to passive list */
+static void
+feature_arc_list_reset(struct rte_graph_feature_arc *arc, uint32_t list_index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+ uint32_t i, j;
+
+ list = arc->feature_list[list_index];
+ feat = arc->features[list_index];
+
+ /*Initialize variables*/
+ memset(feat, 0, arc->feature_size * arc->max_features);
+ memset(list, 0, arc->feature_list_size);
+
+ /* Initialize feature and feature_data */
+ for (i = 0; i < arc->max_features; i++) {
+ feat = __rte_graph_feature_get(arc, i, list_index);
+ feat->this_feature_index = i;
+
+ for (j = 0; j < arc->max_indexes; j++) {
+ fdata = rte_graph_feature_data_get(arc, feat, j);
+ fdata->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ fdata->next_edge = UINT16_MAX;
+ fdata->user_data = UINT32_MAX;
+ }
+ }
+
+ for (i = 0; i < arc->max_indexes; i++)
+ list->first_enabled_feature_by_index[i] = RTE_GRAPH_FEATURE_INVALID;
+}
+
+static int
+feature_arc_list_init(struct rte_graph_feature_arc *arc, const char *flist_name,
+ rte_graph_feature_list_t **pplist,
+ struct rte_graph_feature **ppfeature, uint32_t list_index)
+{
+ char fname[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ size_t list_size, feat_size, fdata_size;
+ rte_graph_feature_list_t *list = NULL;
+ struct rte_graph_feature *feat = NULL;
+
+ list_size = sizeof(struct rte_graph_feature_list) +
+ (sizeof(list->first_enabled_feature_by_index[0]) * arc->max_indexes);
+
+ list_size = RTE_ALIGN_CEIL(list_size, RTE_CACHE_LINE_SIZE);
+
+ list = rte_malloc(flist_name, list_size, RTE_CACHE_LINE_SIZE);
+ if (!list)
+ return -ENOMEM;
+
+ memset(list, 0, list_size);
+ fdata_size = arc->max_indexes * sizeof(rte_graph_feature_data_t);
+
+ /* Let one feature and its associated data per index capture complete
+ * cache lines
+ */
+ feat_size = RTE_ALIGN_CEIL(sizeof(struct rte_graph_feature) + fdata_size,
+ RTE_CACHE_LINE_SIZE);
+
+ snprintf(fname, sizeof(fname), "%s-%s", arc->feature_arc_name, "feat");
+
+ feat = rte_malloc(fname, feat_size * arc->max_features, RTE_CACHE_LINE_SIZE);
+ if (!feat) {
+ rte_free(list);
+ return -ENOMEM;
+ }
+ arc->feature_size = feat_size;
+ arc->feature_data_size = fdata_size;
+ arc->feature_list_size = list_size;
+
+ /* Initialize list */
+ list->indexed_by_features = feat;
+ *pplist = list;
+ *ppfeature = feat;
+
+ feature_arc_list_reset(arc, list_index);
+
+ return 0;
+}
+
+/* free resources allocated in feature_arc_list_init() */
+static void
+feature_arc_list_destroy(struct rte_graph_feature_arc *arc, int list_index)
+{
+ rte_graph_feature_list_t *list = NULL;
+
+ list = arc->feature_list[list_index];
+
+ rte_free(list->indexed_by_features);
+
+ arc->features[list_index] = NULL;
+
+ rte_free(list);
+
+ arc->feature_list[list_index] = NULL;
+}
+
+int
+rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node, rte_graph_feature_arc_t *_arc)
+{
+ char name[2 * RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ struct rte_graph_feature_data *gfd = NULL;
+ rte_graph_feature_arc_main_t *dfm = NULL;
+ struct rte_graph_feature_arc *arc = NULL;
+ struct rte_graph_feature *df = NULL;
+ uint32_t iter, j, arc_index;
+ size_t sz;
+
+ if (!_arc)
+ SET_ERR_JMP(EINVAL, err, "%s: Invalid _arc", feature_arc_name);
+
+ if (max_features < 2)
+ SET_ERR_JMP(EINVAL, err, "%s: max_features must be greater than 1",
+ feature_arc_name);
+
+ if (!start_node)
+ SET_ERR_JMP(EINVAL, err, "%s: start_node cannot be NULL",
+ feature_arc_name);
+
+ if (!feature_arc_name)
+ SET_ERR_JMP(EINVAL, err, "%s: feature_arc name cannot be NULL",
+ feature_arc_name);
+
+ if (max_features > RTE_GRAPH_FEATURE_MAX_PER_ARC)
+ SET_ERR_JMP(EAGAIN, err, "%s: number of features cannot be greater than 64",
+ feature_arc_name);
+
+ /*
+ * Application hasn't called rte_graph_feature_arc_init(). Initialize with
+ * default values
+ */
+ if (!__rte_graph_feature_arc_main) {
+ if (rte_graph_feature_arc_init((int)RTE_GRAPH_FEATURE_ARC_MAX) < 0) {
+ graph_err("rte_graph_feature_arc_init() failed");
+ return -1;
+ }
+ }
+
+ /* If name is not unique */
+ if (!rte_graph_feature_arc_lookup_by_name(feature_arc_name, NULL))
+ SET_ERR_JMP(EINVAL, err, "%s: feature arc name already exists",
+ feature_arc_name);
+
+ dfm = __rte_graph_feature_arc_main;
+
+ /* threshold check */
+ if (dfm->num_feature_arcs > (dfm->max_feature_arcs - 1))
+ SET_ERR_JMP(EAGAIN, err, "%s: max number (%u) of feature arcs reached",
+ feature_arc_name, dfm->max_feature_arcs);
+
+ /* Find the free slot for feature arc */
+ for (iter = 0; iter < dfm->max_feature_arcs; iter++) {
+ if (dfm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ break;
+ }
+ arc_index = iter;
+
+ if (arc_index >= dfm->max_feature_arcs) {
+ graph_err("No free slot found for num_feature_arc");
+ return -1;
+ }
+
+ /* This should not happen */
+ RTE_VERIFY(dfm->feature_arcs[arc_index] == RTE_GRAPH_FEATURE_ARC_INITIALIZER);
+
+ /* size of feature arc + feature_bit_mask_by_index */
+ sz = RTE_ALIGN_CEIL(sizeof(*arc) + (sizeof(uint64_t) * max_indexes), RTE_CACHE_LINE_SIZE);
+
+ arc = rte_malloc(feature_arc_name, sz, RTE_CACHE_LINE_SIZE);
+
+ if (!arc) {
+ graph_err("malloc failed for feature_arc_create()");
+ return -1;
+ }
+
+ memset(arc, 0, sz);
+
+ /* Initialize rte_graph port group fixed variables */
+ STAILQ_INIT(&arc->all_features);
+ strncpy(arc->feature_arc_name, feature_arc_name, RTE_GRAPH_FEATURE_ARC_NAMELEN - 1);
+ arc->feature_arc_main = (void *)dfm;
+ arc->start_node = start_node;
+ arc->max_features = max_features;
+ arc->max_indexes = max_indexes;
+ arc->feature_arc_index = arc_index;
+
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist0");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[0], &arc->features[0], 0) < 0) {
+ rte_free(arc);
+ graph_err("feature_arc_list_init(0) failed");
+ return -1;
+ }
+ snprintf(name, sizeof(name), "%s-%s", feature_arc_name, "flist1");
+
+ if (feature_arc_list_init(arc, name, &arc->feature_list[1], &arc->features[1], 1) < 0) {
+ feature_arc_list_destroy(arc, 0);
+ rte_free(arc);
+ graph_err("feature_arc_list_init(1) failed");
+ return -1;
+ }
+
+ for (iter = 0; iter < arc->max_features; iter++) {
+ df = rte_graph_feature_get(arc, iter);
+ for (j = 0; j < arc->max_indexes; j++) {
+ gfd = rte_graph_feature_data_get(arc, df, j);
+ gfd->next_enabled_feature = RTE_GRAPH_FEATURE_INVALID;
+ }
+ }
+ dfm->feature_arcs[arc->feature_arc_index] = (rte_graph_feature_arc_t)arc;
+ dfm->num_feature_arcs++;
+
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+
+ do_sanity_all_arcs();
+
+ feat_dbg("Feature arc %s[%p] created with max_features: %u and indexes: %u",
+ feature_arc_name, (void *)arc, max_features, max_indexes);
+ return 0;
+
+err:
+ return -rte_errno;
+}
+
+int
+rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *_runs_after, const char *runs_before)
+{
+ struct rte_graph_feature_node_list *after_finfo = NULL, *before_finfo = NULL;
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *temp = NULL, *finfo = NULL;
+ char feature_name[3*RTE_GRAPH_FEATURE_ARC_NAMELEN];
+ const char *runs_after = NULL;
+ uint32_t num_feature = 0;
+ uint32_t slot, add_flag;
+ rte_edge_t edge = -1;
+
+ /* sanity */
+ if (arc->feature_arc_main != __rte_graph_feature_arc_main) {
+ graph_err("feature arc not created: 0x%016" PRIx64, (uint64_t)_arc);
+ return -1;
+ }
+
+ if (feature_arc_lookup(_arc)) {
+ graph_err("invalid feature arc: 0x%016" PRIx64, (uint64_t)_arc);
+ return -1;
+ }
+
+ if (arc->runtime_enabled_features) {
+ graph_err("adding features after enabling any one of them is not supported");
+ return -1;
+ }
+
+ if ((_runs_after != NULL) && (runs_before != NULL) &&
+ (_runs_after == runs_before)) {
+ graph_err("runs_after and runs_before are same '%s:%s]", _runs_after,
+ runs_before);
+ return -1;
+ }
+
+ if (!feature_node) {
+ graph_err("feature_node: %p invalid", feature_node);
+ return -1;
+ }
+
+ arc = rte_graph_feature_arc_get(_arc);
+
+ if (feature_node->id == RTE_NODE_ID_INVALID) {
+ graph_err("Invalid node: %s", feature_node->name);
+ return -1;
+ }
+
+ if (!feature_add_lookup(arc, feature_node->name, &finfo, &slot)) {
+ graph_err("%s feature already added", feature_node->name);
+ return -1;
+ }
+
+ if (slot >= arc->max_features) {
+ graph_err("%s: Max features %u added to feature arc",
+ arc->feature_arc_name, slot);
+ return -1;
+ }
+
+ if (strstr(feature_node->name, arc->start_node->name)) {
+ graph_err("Feature %s cannot point to itself: %s", feature_node->name,
+ arc->start_node->name);
+ return -1;
+ }
+
+ feat_dbg("%s: adding feature node: %s at feature index: %u", arc->feature_arc_name,
+ feature_node->name, slot);
+
+ if (connect_graph_nodes(arc->start_node, feature_node, &edge, arc->feature_arc_name)) {
+ graph_err("unable to connect %s -> %s", arc->start_node->name, feature_node->name);
+ return -1;
+ }
+
+ snprintf(feature_name, sizeof(feature_name), "%s-%s-finfo",
+ arc->feature_arc_name, feature_node->name);
+
+ finfo = rte_malloc(feature_name, sizeof(*finfo), 0);
+ if (!finfo) {
+ graph_err("%s/%s: rte_malloc failed", arc->feature_arc_name, feature_node->name);
+ return -1;
+ }
+
+ memset(finfo, 0, sizeof(*finfo));
+
+ finfo->feature_arc = (void *)arc;
+ finfo->feature_node = feature_node;
+ finfo->edge_to_this_feature = edge;
+ arc->runtime_enabled_features = 0;
+
+ /*
+ * if no constraints given and provided feature is not the first feature,
+ * explicitly set "runs_after" as last_feature. Handles the case:
+ *
+ * add(f1, NULL, NULL);
+ * add(f2, NULL, NULL);
+ */
+ num_feature = rte_graph_feature_arc_num_features(_arc);
+ if (!_runs_after && !runs_before && num_feature)
+ runs_after = rte_graph_feature_arc_feature_to_name(_arc, num_feature - 1);
+ else
+ runs_after = _runs_after;
+
+ /* Check for before and after constraints */
+ if (runs_before) {
+ /* runs_before sanity */
+ if (feature_lookup(arc, runs_before, &before_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid before feature name: %s", runs_before);
+
+ if (!before_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "runs_before %s does not exist", runs_before);
+
+ /*
+ * Starting from 0 to runs_before, continue connecting edges
+ */
+ add_flag = 1;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (!add_flag)
+ /* Nodes after seeing "runs_before", finfo connects to temp*/
+ connect_graph_nodes(finfo->feature_node, temp->feature_node,
+ NULL, arc->feature_arc_name);
+ /*
+ * As soon as we see runs_before. stop adding edges
+ */
+ if (!strncmp(temp->feature_node->name, runs_before,
+ RTE_GRAPH_NAMESIZE)) {
+ if (!connect_graph_nodes(finfo->feature_node, temp->feature_node,
+ &edge, arc->feature_arc_name))
+ add_flag = 0;
+ }
+
+ if (add_flag)
+ /* Nodes before seeing "run_before" are connected to finfo */
+ connect_graph_nodes(temp->feature_node, finfo->feature_node, NULL,
+ arc->feature_arc_name);
+ }
+ }
+
+ if (runs_after) {
+ if (feature_lookup(arc, runs_after, &after_finfo, NULL))
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "Invalid after feature_name %s", runs_after);
+
+ if (!after_finfo)
+ SET_ERR_JMP(EINVAL, finfo_free,
+ "runs_after %s does not exist", runs_after);
+
+ /* Starting from runs_after to end continue connecting edges */
+ add_flag = 0;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (add_flag)
+ /* We have already seen runs_after now */
+ /* Add all features as next node to current feature*/
+ connect_graph_nodes(finfo->feature_node, temp->feature_node, NULL,
+ arc->feature_arc_name);
+ else
+ /* Connect initial nodes to newly added node*/
+ connect_graph_nodes(temp->feature_node, finfo->feature_node, NULL,
+ arc->feature_arc_name);
+
+ /* as soon as we see runs_after. start adding edges
+ * from next iteration
+ */
+ if (!strncmp(temp->feature_node->name, runs_after, RTE_GRAPH_NAMESIZE))
+ add_flag = 1;
+ }
+
+ /* add feature next to runs_after */
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo, finfo, next_feature);
+ } else {
+ if (before_finfo) {
+ /* add finfo before "before_finfo" element in the list */
+ after_finfo = NULL;
+ STAILQ_FOREACH(temp, &arc->all_features, next_feature) {
+ if (before_finfo == temp) {
+ if (after_finfo)
+ STAILQ_INSERT_AFTER(&arc->all_features, after_finfo,
+ finfo, next_feature);
+ else
+ STAILQ_INSERT_HEAD(&arc->all_features, finfo,
+ next_feature);
+
+ return 0;
+ }
+ after_finfo = temp;
+ }
+ } else {
+ /* Very first feature just needs to be added to list */
+ STAILQ_INSERT_TAIL(&arc->all_features, finfo, next_feature);
+ }
+ }
+
+ return 0;
+
+finfo_free:
+ rte_free(finfo);
+
+ return -1;
+}
+
+int
+rte_graph_feature_lookup(rte_graph_feature_arc_t _arc, const char *feature_name,
+ rte_graph_feature_t *feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot;
+
+ if (!feature_lookup(arc, feature_name, &finfo, &slot)) {
+ *feat = (rte_graph_feature_t) slot;
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+rte_graph_feature_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;
+ struct rte_graph_feature *gf = NULL;
+ uint32_t slot;
+
+ /* validate _arc */
+ if (arc->feature_arc_main != __rte_graph_feature_arc_main) {
+ FEAT_COND_ERR(emit_logs, "invalid feature arc: 0x%016" PRIx64, (uint64_t)_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 (feature_lookup(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",
+ arc->feature_arc_name, feature_name);
+ return -EINVAL;
+ }
+
+ /* slot should be in valid range */
+ if (slot >= arc->max_features) {
+ FEAT_COND_ERR(emit_logs, "%s/%s: Invalid free slot %u(max=%u) for feature",
+ arc->feature_arc_name, feature_name, slot, arc->max_features);
+ return -EINVAL;
+ }
+
+ /* slot should be in range of 0 - 63 */
+ if (slot > (RTE_GRAPH_FEATURE_MAX_PER_ARC - 1)) {
+ FEAT_COND_ERR(emit_logs, "%s/%s: Invalid slot: %u", arc->feature_arc_name,
+ feature_name, slot);
+ return -EINVAL;
+ }
+
+ if (finfo->node_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->node_index, slot);
+ return -1;
+ }
+
+ /* Get feature from active list */
+ gf = __rte_graph_feature_get(arc, slot, ARC_PASSIVE_LIST(arc));
+ if (gf->this_feature_index != slot) {
+ FEAT_COND_ERR(emit_logs,
+ "%s: %s rcvd feature_idx: %u does not match with saved: %u",
+ arc->feature_arc_name, feature_name, slot, gf->this_feature_index);
+ return -1;
+ }
+
+ if (is_enable_disable && (arc->feature_bit_mask_by_index[index] &
+ RTE_BIT64(slot))) {
+ 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;
+ }
+
+ return 0;
+}
+
+/*
+ * Before switch to passive list, user_data needs to be copied from active list to passive list
+ */
+static void
+copy_fastpath_user_data(struct rte_graph_feature_arc *arc, uint16_t dest_list_index,
+ uint16_t src_list_index)
+{
+ rte_graph_feature_data_t *sgfd = NULL, *dgfd = NULL;
+ struct rte_graph_feature *sgf = NULL, *dgf = NULL;
+ uint32_t i, j;
+
+ for (i = 0; i < arc->max_features; i++) {
+ sgf = __rte_graph_feature_get(arc, i, src_list_index);
+ dgf = __rte_graph_feature_get(arc, i, dest_list_index);
+ for (j = 0; j < arc->max_indexes; j++) {
+ sgfd = rte_graph_feature_data_get(arc, sgf, j);
+ dgfd = rte_graph_feature_data_get(arc, dgf, j);
+ dgfd->user_data = sgfd->user_data;
+ }
+ }
+}
+/*
+ * Fill fast path information like
+ * - next_edge
+ * - next_enabled_feature
+ */
+static void
+refill_feature_fastpath_data(struct rte_graph_feature_arc *arc, uint16_t list_index)
+{
+ struct rte_graph_feature_node_list *finfo = NULL, *prev_finfo = NULL;
+ struct rte_graph_feature_data *gfd = NULL, *prev_gfd = NULL;
+ uint32_t fi = UINT32_MAX, di = UINT32_MAX, prev_fi = UINT32_MAX;
+ struct rte_graph_feature *gf = NULL, *prev_gf = NULL;
+ rte_graph_feature_list_t *flist = NULL;
+ rte_edge_t edge = UINT16_MAX;
+ uint64_t bitmask = 0;
+
+ flist = arc->feature_list[list_index];
+
+ for (di = 0; di < arc->max_indexes; di++) {
+ bitmask = arc->feature_bit_mask_by_index[di];
+ prev_fi = RTE_GRAPH_FEATURE_INVALID;
+ /* for each feature set for index, set fast path data */
+ while (rte_bsf64_safe(bitmask, &fi)) {
+ gf = __rte_graph_feature_get(arc, fi, list_index);
+ gfd = rte_graph_feature_data_get(arc, gf, di);
+ RTE_VERIFY(!feature_arc_node_info_lookup(arc, fi, &finfo, 1));
+
+ /* If previous feature_index was valid in last loop */
+ if (prev_fi != RTE_GRAPH_FEATURE_INVALID) {
+ prev_gf = __rte_graph_feature_get(arc, prev_fi, list_index);
+ prev_gfd = rte_graph_feature_data_get(arc, prev_gf, di);
+ /*
+ * Get edge of previous feature node connecting
+ * to this feature node
+ */
+ RTE_VERIFY(!feature_arc_node_info_lookup(arc, prev_fi,
+ &prev_finfo, 1));
+ if (!get_existing_edge(arc->feature_arc_name,
+ prev_finfo->feature_node,
+ finfo->feature_node, &edge)) {
+ feat_dbg("\t[%s/%u/di:%2u,cookie:%u]: (%u->%u)%s[%u] = %s",
+ arc->feature_arc_name, list_index, di,
+ prev_gfd->user_data, prev_fi, fi,
+ prev_finfo->feature_node->name,
+ edge, finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /*
+ * Fill current feature as next enabled
+ * feature to previous one
+ */
+ prev_gfd->next_enabled_feature = fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* On first feature edge of the node to be added */
+ if (fi == rte_bsf64(arc->feature_bit_mask_by_index[di])) {
+ if (!get_existing_edge(arc->feature_arc_name, arc->start_node,
+ finfo->feature_node,
+ &edge)) {
+ feat_dbg("\t[%s/%u/di:%2u,cookie:%u]: (->%u)%s[%u]=%s",
+ arc->feature_arc_name, list_index, di,
+ gfd->user_data, fi,
+ arc->start_node->name, edge,
+ finfo->feature_node->name);
+ /* Copy feature index for next iteration*/
+ gfd->next_edge = edge;
+ prev_fi = fi;
+ /* Set first feature set array for index*/
+ flist->first_enabled_feature_by_index[di] =
+ (rte_graph_feature_t)fi;
+ } else {
+ /* Should not fail */
+ RTE_VERIFY(0);
+ }
+ }
+ /* Clear current feature index */
+ bitmask &= ~RTE_BIT64(fi);
+ }
+ }
+}
+
+int
+rte_graph_feature_enable(rte_graph_feature_arc_t _arc, uint32_t index, const
+ char *feature_name, int32_t user_data)
+{
+ 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;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint64_t bitmask;
+ uint32_t slot;
+
+ 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);
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 1, true))
+ return -1;
+
+ /** This should not fail as validate() has passed */
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ RTE_VERIFY(0);
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ feat_dbg("\t%s/%s: index: %u, passive list: %u, feature index: %u",
+ arc->feature_arc_name, feature_name, index, passive_list, slot);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Set current user-data */
+ gfd->user_data = user_data;
+
+ /* Set bitmask in control path bitmask */
+ rte_bit_relaxed_set64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+ refill_feature_fastpath_data(arc, passive_list);
+
+ /* If first time feature getting enabled */
+ bitmask = rte_atomic_load_explicit(&arc->feature_enable_bitmask[arc->active_feature_list],
+ rte_memory_order_relaxed);
+
+ /* On very first feature enable instance */
+ if (!finfo->ref_count)
+ bitmask |= RTE_BIT64(slot);
+
+ rte_atomic_store_explicit(&arc->feature_enable_bitmask[passive_list],
+ bitmask, rte_memory_order_relaxed);
+
+ /* Slow path updates */
+ arc->runtime_enabled_features++;
+
+ /* Increase feature node info reference count */
+ finfo->ref_count++;
+
+ /* Store release semantics for active_list update */
+ rte_atomic_store_explicit(&arc->active_feature_list, passive_list,
+ rte_memory_order_release);
+
+ feat_dbg("%s/%s: After enable, switched active feature list to %u",
+ arc->feature_arc_name, feature_name, arc->active_feature_list);
+
+ return 0;
+}
+
+int
+rte_graph_feature_disable(rte_graph_feature_arc_t _arc, uint32_t index, const char *feature_name)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_data *gfd = NULL;
+ struct rte_graph_feature_node_list *finfo = NULL;
+ rte_graph_feature_rt_list_t passive_list;
+ struct rte_graph_feature *gf = NULL;
+ uint64_t bitmask;
+ uint32_t slot;
+
+ feat_dbg("%s: Disable feature: %s for index: %u",
+ arc->feature_arc_name, feature_name, index);
+
+ if (rte_graph_feature_validate(_arc, index, feature_name, 0, true))
+ return -1;
+
+ if (feature_lookup(arc, feature_name, &finfo, &slot))
+ return -1;
+
+ passive_list = ARC_PASSIVE_LIST(arc);
+
+ gf = __rte_graph_feature_get(arc, slot, passive_list);
+ gfd = rte_graph_feature_data_get(arc, gf, index);
+
+ feat_dbg("\t%s/%s: index: %u, passive list: %u, feature index: %u",
+ arc->feature_arc_name, feature_name, index, passive_list, slot);
+
+ rte_bit_relaxed_clear64(rte_graph_uint_cast(slot), &arc->feature_bit_mask_by_index[index]);
+
+ /* Reset feature list */
+ feature_arc_list_reset(arc, passive_list);
+
+ /* Copy user-data */
+ copy_fastpath_user_data(arc, passive_list, arc->active_feature_list);
+
+ /* Reset current user-data */
+ gfd->user_data = ~0;
+
+ refill_feature_fastpath_data(arc, passive_list);
+
+ finfo->ref_count--;
+ arc->runtime_enabled_features--;
+
+ /* If no feature enabled, reset feature in u64 fast path bitmask */
+ bitmask = rte_atomic_load_explicit(&arc->feature_enable_bitmask[arc->active_feature_list],
+ rte_memory_order_relaxed);
+
+ /* When last feature is disabled */
+ if (!finfo->ref_count)
+ bitmask &= ~(RTE_BIT64(slot));
+
+ rte_atomic_store_explicit(&arc->feature_enable_bitmask[passive_list], bitmask,
+ rte_memory_order_relaxed);
+
+ /* Store release semantics for active_list update */
+ rte_atomic_store_explicit(&arc->active_feature_list, passive_list,
+ rte_memory_order_release);
+
+ feat_dbg("%s/%s: After disable, switched active feature list to %u",
+ arc->feature_arc_name, feature_name, arc->active_feature_list);
+
+ return 0;
+}
+
+int
+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;
+
+ while (!STAILQ_EMPTY(&arc->all_features)) {
+ node_info = STAILQ_FIRST(&arc->all_features);
+ STAILQ_REMOVE_HEAD(&arc->all_features, next_feature);
+ rte_free(node_info);
+ }
+ feature_arc_list_destroy(arc, 0);
+ feature_arc_list_destroy(arc, 1);
+
+ dm->feature_arcs[arc->feature_arc_index] = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ rte_free(arc);
+
+ do_sanity_all_arcs();
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_cleanup(void)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ rte_graph_feature_arc_destroy((rte_graph_feature_arc_t)dm->feature_arcs[iter]);
+ }
+ rte_free(dm);
+
+ __rte_graph_feature_arc_main = NULL;
+
+ return 0;
+}
+
+int
+rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc)
+{
+ rte_graph_feature_arc_main_t *dm = __rte_graph_feature_arc_main;
+ struct rte_graph_feature_arc *arc = NULL;
+ uint32_t iter;
+
+ if (!__rte_graph_feature_arc_main)
+ return -1;
+
+ if (_arc)
+ *_arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+ for (iter = 0; iter < dm->max_feature_arcs; iter++) {
+ if (dm->feature_arcs[iter] == RTE_GRAPH_FEATURE_ARC_INITIALIZER)
+ continue;
+
+ arc = rte_graph_feature_arc_get(dm->feature_arcs[iter]);
+
+ if ((strstr(arc->feature_arc_name, arc_name)) &&
+ (strlen(arc->feature_arc_name) == strlen(arc_name))) {
+ if (_arc)
+ *_arc = (rte_graph_feature_arc_t)arc;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+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);
+
+ return arc->runtime_enabled_features;
+}
+
+uint32_t
+rte_graph_feature_arc_num_features(rte_graph_feature_arc_t _arc)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t count = 0;
+
+ STAILQ_FOREACH(finfo, &arc->all_features, next_feature)
+ count++;
+
+ return count;
+}
+
+char *
+rte_graph_feature_arc_feature_to_name(rte_graph_feature_arc_t _arc, rte_graph_feature_t feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot = feat;
+
+ if (feat >= rte_graph_feature_arc_num_features(_arc)) {
+ graph_err("%s: feature %u does not exist", arc->feature_arc_name, feat);
+ return NULL;
+ }
+ if (!feature_arc_node_info_lookup(arc, slot, &finfo, 0/* ignore sanity*/))
+ return finfo->feature_node->name;
+
+ return NULL;
+}
+
+struct rte_node_register *
+rte_graph_feature_arc_feature_to_node(rte_graph_feature_arc_t _arc, rte_graph_feature_t feat)
+{
+ struct rte_graph_feature_arc *arc = rte_graph_feature_arc_get(_arc);
+ struct rte_graph_feature_node_list *finfo = NULL;
+ uint32_t slot = feat;
+
+ if (feat >= rte_graph_feature_arc_num_features(_arc)) {
+ graph_err("%s: feature %u does not exist", arc->feature_arc_name, feat);
+ return NULL;
+ }
+ if (!feature_arc_node_info_lookup(arc, slot, &finfo, 0/* ignore sanity*/))
+ return finfo->feature_node;
+
+ return NULL;
+
+}
diff --git a/lib/graph/meson.build b/lib/graph/meson.build
index 0cb15442ab..d916176fb7 100644
--- a/lib/graph/meson.build
+++ b/lib/graph/meson.build
@@ -14,11 +14,13 @@ sources = files(
'graph_debug.c',
'graph_stats.c',
'graph_populate.c',
+ 'graph_feature_arc.c',
'graph_pcap.c',
'rte_graph_worker.c',
'rte_graph_model_mcore_dispatch.c',
)
headers = files('rte_graph.h', 'rte_graph_worker.h')
+headers += files('rte_graph_feature_arc.h', 'rte_graph_feature_arc_worker.h')
indirect_headers += files(
'rte_graph_model_mcore_dispatch.h',
'rte_graph_model_rtc.h',
diff --git a/lib/graph/rte_graph_feature_arc.h b/lib/graph/rte_graph_feature_arc.h
new file mode 100644
index 0000000000..1615f8e1c8
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc.h
@@ -0,0 +1,431 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_H_
+#define _RTE_GRAPH_FEATURE_ARC_H_
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_compat.h>
+#include <rte_debug.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc.h
+ *
+ * Define APIs and structures/variables with respect to feature arc
+ *
+ * - Feature arc(s)
+ * - Feature(s)
+ *
+ * A feature arc represents an ordered list of features/protocol-nodes at a
+ * given networking layer. Feature arc provides a high level abstraction to
+ * connect various *rte_graph* nodes, designated as *feature nodes*, and
+ * allowing steering of packets across these feature nodes fast path processing
+ * in a generic manner. In a typical network stack, often a protocol or feature
+ * must be first enabled on a given interface, before any packet is steered
+ * towards it for feature processing. For eg: incoming IPv4 packets are sent to
+ * routing sub-system only after a valid IPv4 address is assigned to the
+ * received interface. In other words, often packets needs to be steered across
+ * features not based on the packet content but based on whether a feature is
+ * enable or disable on a given incoming/outgoing interface. Feature arc
+ * provides mechanism to enable/disable feature(s) on each interface at runtime
+ * and allow seamless packet steering across runtime enabled feature nodes in
+ * fast path.
+ *
+ * Feature arc also provides a way to steer packets from standard nodes to
+ * custom/user-defined *feature nodes* without any change in standard node's
+ * fast path functions
+ *
+ * On a given interface multiple feature(s) might be enabled in a particular
+ * feature arc. For instance, both "ipv4-output" and "IPsec policy output"
+ * features may be enabled on "eth0" interface in "L3-output" feature arc.
+ * Similarly, "ipv6-output" and "ipsec-output" may be enabled on "eth1"
+ * interface in same "L3-output" feature arc.
+ *
+ * When multiple features are present in a given feature arc, its imperative
+ * to allow each feature processing in a particular sequential order. For
+ * instance, in "L3-input" feature arc it may be required to run "IPsec
+ * input" feature first, for packet decryption, before "ip-lookup". So a
+ * sequential order must be maintained among features present in a feature arc.
+ *
+ * Features are enabled/disabled multiple times at runtime to some or all
+ * available interfaces present in the system. Enable/disabling features on one
+ * interface is independent of other interface.
+ *
+ * A given feature might consume packet (if it's configured to consume) or may
+ * forward it to next enabled feature. For instance, "IPsec input" feature may
+ * consume/drop all packets with "Protect" policy action while all packets with
+ * policy action as "Bypass" may be forwarded to next enabled feature (with in
+ * same feature arc)
+ *
+ * This library facilitates rte graph based applications to steer packets in
+ * fast path to different feature nodes with-in a feature arc and support all
+ * functionalities described above
+ *
+ * In order to use feature-arc APIs, applications needs to do following in
+ * control path:
+ * - Initialize feature arc library via rte_graph_feature_arc_init()
+ * - Create feature arc via rte_graph_feature_arc_create()
+ * - *Before calling rte_graph_create()*, features must be added to feature-arc
+ * via rte_graph_feature_add(). rte_graph_feature_add() allows adding
+ * features in a sequential order with "runs_after" and "runs_before"
+ * constraints.
+ * - Post rte_graph_create(), features can be enabled/disabled at runtime on
+ * any interface via rte_graph_feature_enable()/rte_graph_feature_disable()
+ * - Feature arc can be destroyed via rte_graph_feature_arc_destroy()
+ *
+ * In fast path, APIs are provided to steer packets towards feature path from
+ * - start_node (provided as an argument to rte_graph_feature_arc_create())
+ * - feature nodes (which are added via rte_graph_feature_add())
+ *
+ * For typical steering of packets across feature nodes, application required
+ * to know "rte_edges" which are saved in feature data object. Feature data
+ * object is unique for every interface per feature with in a feature arc.
+ *
+ * When steering packets from start_node to feature node:
+ * - rte_graph_feature_arc_first_feature_get() provides first enabled feature.
+ * - Next rte_edge from start_node to first enabled feature can be obtained via
+ * rte_graph_feature_arc_feature_set()
+ *
+ * rte_mbuf can carry [current feature, interface index] from start_node of an
+ * arc to other feature nodes
+ *
+ * At the time of feature enable(rte_graph_feature_enable), application can set
+ * 32-bit unique user_data specific to feature per interface. In fast path
+ * user_data can be retrieved via rte_graph_feature_user_data_get(). User data
+ * can hold application specific cookie like IPsec policy database index, FIB
+ * table index etc.
+ *
+ * If feature node is not consuming packet, next enabled feature and next
+ * rte_edge can be obtained via rte_graph_feature_arc_next_feature_get()
+ *
+ * It is application responsibility to ensure that at-least *last feature*(or
+ * sink feature) must be enabled from where packet can exit feature-arc path,
+ * if *NO* intermediate feature is consuming the packet and it has reached till
+ * the end of feature arc path
+ *
+ * It is recommended that all features *MUST* be added to feature arc before
+ * calling `rte_graph_create()`. Addition of features after
+ * `rte_graph_create()` may not work functionally.
+ * Although,rte_graph_feature_enable()/rte_graph_feature_disable() should be
+ * called after `rte_graph_create()` in control plane.
+ *
+ * Synchronization among cores
+ * ---------------------------
+ * Subsequent calls to rte_graph_feature_enable() is allowed while worker cores
+ * are processing in rte_graph_walk() loop. However, for
+ * rte_graph_feature_disable() application must use RCU based synchronization
+ */
+
+/** Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_ARC_INITIALIZER ((rte_graph_feature_arc_t)UINT64_MAX)
+
+/** Max number of feature arcs which can be created */
+#define RTE_GRAPH_FEATURE_ARC_MAX 64
+
+/** Max number of features supported in a given feature arc */
+#define RTE_GRAPH_FEATURE_MAX_PER_ARC 64
+
+/** Length of feature arc name */
+#define RTE_GRAPH_FEATURE_ARC_NAMELEN RTE_NODE_NAMESIZE
+
+/** @internal */
+#define rte_graph_feature_cast(x) ((rte_graph_feature_t)x)
+
+/**< Initializer value for rte_graph_feature_arc_t */
+#define RTE_GRAPH_FEATURE_INVALID rte_graph_feature_cast(UINT8_MAX)
+
+/** rte_graph feature arc object */
+typedef uintptr_t rte_graph_feature_arc_t;
+
+/** rte_graph feature object */
+typedef uint8_t rte_graph_feature_t;
+
+/** runtime active feature list index with in feature arc*/
+typedef uint16_t rte_graph_feature_rt_list_t;
+
+/** per feature arc monotonically increasing counter to synchronize fast path APIs */
+typedef uint16_t rte_graph_feature_counter_t;
+
+/**
+ * Initialize feature arc subsystem
+ *
+ * @param max_feature_arcs
+ * Maximum number of feature arcs required to be supported
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_init(int max_feature_arcs);
+
+/**
+ * Create a feature arc
+ *
+ * @param feature_arc_name
+ * Feature arc name with max length of @ref RTE_GRAPH_FEATURE_ARC_NAMELEN
+ * @param max_features
+ * Maximum number of features to be supported in this feature arc
+ * @param max_indexes
+ * Maximum number of interfaces/ports/indexes to be supported
+ * @param start_node
+ * Base node where this feature arc's features are checked in fast path
+ * @param[out] _arc
+ * Feature arc object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_create(const char *feature_arc_name, int max_features, int max_indexes,
+ struct rte_node_register *start_node,
+ rte_graph_feature_arc_t *_arc);
+
+/**
+ * Get feature arc object with name
+ *
+ * @param arc_name
+ * 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
+ *
+ * @return
+ * 0: Success
+ * <0: Failure.
+ */
+__rte_experimental
+int rte_graph_feature_arc_lookup_by_name(const char *arc_name, rte_graph_feature_arc_t *_arc);
+
+/**
+ * Add a feature to already created feature arc. For instance
+ *
+ * 1. Add first feature node: "ipv4-input" to input arc
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-input", NULL, NULL);
+ *
+ * 2. Add "ipsec-input" feature node after "ipv4-input" feature
+ * rte_graph_feature_add(ipv4_input_arc, "ipsec-input", "ipv4-input", NULL);
+ *
+ * 3. Add "ipv4-pre-classify-input" node before "ipv4-input" feature
+ * rte_graph_feature_add(ipv4_input_arc, "ipv4-pre-classify-input"", NULL, "ipv4-input");
+ *
+ * 4. Add "acl-classify-input" node after ipv4-input but before ipsec-input
+ * rte_graph_feature_add(ipv4_input_arc, "acl-classify-input", "ipv4-input", "ipsec-input");
+ *
+ * @param _arc
+ * Feature arc handle returned from @ref rte_graph_feature_arc_create()
+ * @param feature_node
+ * Graph node representing feature. On success, feature_node is next_node of
+ * feature_arc->start_node
+ * @param runs_after
+ * Add this feature_node after already added "runs_after". Creates
+ * start_node -> runs_after -> this_feature sequence
+ * @param runs_before
+ * Add this feature_node before already added "runs_before". Creates
+ * start_node -> this_feature -> runs_before sequence
+ *
+ * <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>
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_add(rte_graph_feature_arc_t _arc, struct rte_node_register *feature_node,
+ const char *runs_after, const char *runs_before);
+
+/**
+ * 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 user_data
+ * Application specific data which is retrieved in fast path
+ *
+ * @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,
+ int32_t user_data);
+
+/**
+ * Validate whether subsequent enable/disable feature would succeed or not.
+ * API is 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 is_enable_disable
+ * If 1, validate whether subsequent @ref rte_graph_feature_enable would pass or not
+ * If 0, validate whether subsequent @ref rte_graph_feature_disable would pass or not
+ * @param emit_logs
+ * If passed true, emit error logs when failure is returned
+ * If passed false, do not emit error logs when failure is returned
+ *
+ * @return
+ * 0: Subsequent enable/disable API would pass
+ * <0: Subsequent enable/disable API would not pass
+ */
+__rte_experimental
+int rte_graph_feature_validate(rte_graph_feature_arc_t _arc, uint32_t index,
+ const char *feature_name, int is_enable_disable, bool emit_logs);
+
+/**
+ * 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
+ *
+ * @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);
+
+/**
+ * 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
+ * @param feature_name
+ * Feature name provided to @ref rte_graph_feature_add
+ * @param[out] feature
+ * Feature object
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_lookup(rte_graph_feature_arc_t arc, const char *feature_name,
+ rte_graph_feature_t *feature);
+
+/**
+ * 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
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_destroy(rte_graph_feature_arc_t _arc);
+
+/**
+ * Cleanup all feature arcs
+ *
+ * @return
+ * 0: Success
+ * <0: Failure
+ */
+__rte_experimental
+int rte_graph_feature_arc_cleanup(void);
+
+/**
+ * Slow path API to know how many features are added (NOT enabled) within a
+ * feature arc
+ *
+ * @param _arc
+ * Feature arc object
+ *
+ * @return: Number of added features to arc
+ */
+__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
+ *
+ * @param _arc
+ * Feature arc object
+ * @param feature
+ * Feature object
+ *
+ * @return: Name of the feature node
+ */
+__rte_experimental
+char *rte_graph_feature_arc_feature_to_name(rte_graph_feature_arc_t _arc,
+ rte_graph_feature_t feature);
+
+/**
+ * Slow path API to get corresponding struct rte_node_register * from
+ * rte_graph_feature_t
+ *
+ * @param _arc
+ * Feature arc object
+ * @param feature
+ * Feature object
+ *
+ * @return: struct rte_node_register * of feature node on SUCCESS else NULL
+ */
+__rte_experimental
+struct rte_node_register *
+rte_graph_feature_arc_feature_to_node(rte_graph_feature_arc_t _arc,
+ rte_graph_feature_t feature);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/graph/rte_graph_feature_arc_worker.h b/lib/graph/rte_graph_feature_arc_worker.h
new file mode 100644
index 0000000000..ca12b3ffd0
--- /dev/null
+++ b/lib/graph/rte_graph_feature_arc_worker.h
@@ -0,0 +1,674 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#ifndef _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+#define _RTE_GRAPH_FEATURE_ARC_WORKER_H_
+
+#include <stddef.h>
+#include <rte_graph_feature_arc.h>
+#include <rte_bitops.h>
+
+/**
+ * @file
+ *
+ * rte_graph_feature_arc_worker.h
+ *
+ * Defines fast path structure
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @internal
+ *
+ * Slow path feature node info list
+ */
+struct rte_graph_feature_node_list {
+ /** Next feature */
+ STAILQ_ENTRY(rte_graph_feature_node_list) next_feature;
+
+ /** node representing feature */
+ struct rte_node_register *feature_node;
+
+ /** How many indexes/interfaces using this feature */
+ int32_t ref_count;
+
+ /* node_index in list (after feature_enable())*/
+ uint32_t node_index;
+
+ /** Back pointer to feature arc */
+ void *feature_arc;
+
+ /** rte_edge_t to this feature node from feature_arc->start_node */
+ rte_edge_t edge_to_this_feature;
+};
+
+/**
+ * Feature data object:
+ *
+ * Feature data stores information to steer packets for:
+ * - a feature with in feature arc
+ * - Index i.e. Port/Interface index
+ *
+ * Each feature data object holds
+ * - User data of current feature retrieved via rte_graph_feature_user_data_get()
+ * - next_edge is used in two conditions when packet to be steered from
+ * -- start_node to first enabled feature on an interface index
+ * -- current feature node to next enabled feature on an interface index
+ * - next_enabled_feature on interface index, if current feature is not
+ * consuming packet
+ *
+ * While user_data corresponds to current enabled feature node however
+ * next_edge and next_enabled_feature corresponds to next enabled feature
+ * node on an interface index
+ *
+ * First enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_first_feature_get() if arc's start_node is trying to
+ * steer packet from start_node to first enabled feature on interface index
+ *
+ * Next enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_next_feature_get() if current node is not arc's
+ * start_node. Input to rte_graph_feature_next_feature_get() is current
+ * enabled feature and interface index
+ */
+typedef struct __rte_packed rte_graph_feature_data {
+ /** edge from current node to next enabled feature */
+ rte_edge_t next_edge;
+
+ union {
+ uint16_t reserved;
+ struct {
+ /** next enabled feature on index from current feature */
+ rte_graph_feature_t next_enabled_feature;
+ };
+ };
+
+ /** user_data set by application in rte_graph_feature_enable() for
+ * - current feature
+ * - interface index
+ */
+ int32_t user_data;
+} rte_graph_feature_data_t;
+
+/**
+ * Feature object
+ *
+ * Feature object holds feature data object for every index/interface within
+ * feature
+ *
+ * Within a given arc and interface index, first feature object can be
+ * retrieved in arc's start_node via:
+ * - rte_graph_feature_arc_first_feature_get()
+ *
+ * Feature data information can be retrieved for first feature in start node via
+ * - rte_graph_feature_arc_feature_set()
+ *
+ * Next enabled feature on interface index can be retrieved via:
+ * - rte_graph_feature_arc_next_feature_get()
+ *
+ * Typically application stores rte_graph_feature_t object in rte_mbuf.
+ * rte_graph_feature_t can be translated to (struct rte_graph_feature *) via
+ * rte_graph_feature_get() in fast path. Further if needed, feature data for an
+ * index within a feature can be retrieved via rte_graph_feature_data_get()
+ */
+struct __rte_cache_aligned rte_graph_feature {
+ /** feature index or rte_graph_feature_t */
+ uint16_t this_feature_index;
+
+ /*
+ * Array of size arc->feature_data_size
+ *
+ * <----------------- Feature -------------------------->
+ * [data-index-0][data-index-1]...[data-index-max_index-1]
+ *
+ * sizeof(feature_data_by_index[0] == sizeof(rte_graph_feature_data_t)
+ *
+ */
+ uint8_t feature_data_by_index[];
+};
+
+/**
+ * Feature list object
+ *
+ * Feature list is required to decouple fast path APIs with control path APIs.
+ *
+ * There are two feature lists: active, passive
+ * Passive list is duplicate of active list in terms of memory.
+ *
+ * While fast path APIs always work on active list but control plane work on
+ * passive list. When control plane needs to enable/disable any feature, it
+ * populates passive list afresh and atomically switch passive list to active
+ * list to make it available for fast path APIs
+ *
+ * Each feature node in start of its fast path function, must grab active list from
+ * arc via
+ * - rte_graph_feature_arc_has_any_feature() or
+ * rte_graph_feature_arc_has_feature()
+ *
+ * Retrieved list must be provided to other feature arc fast path APIs so that
+ * any control plane changes of active list should not impact current node
+ * execution iteration. Active list change would be reflected to current node
+ * in next iteration
+ *
+ * With active/passive lists and RCU mechanism in graph worker
+ * loop, application can update features at runtime without stopping fast path
+ * cores. A RCU synchronization is required when a feature needs to be
+ * disabled via rte_graph_feature_disable(). On enabling a feature, RCU
+ * synchronization may not be required
+ *
+ */
+typedef struct __rte_cache_aligned rte_graph_feature_list {
+ /**
+ * fast path array holding per_feature data.
+ * Duplicate entry as feature-arc also hold this pointer
+ * arc->features[]
+ *
+ *<-------------feature-0 ---------><---------feature-1 -------------->...
+ *[index-0][index-1]...[max_index-1]<-ALIGN->[index-0][index-1] ...[max_index-1]...
+ */
+ struct rte_graph_feature *indexed_by_features;
+ /*
+ * fast path array holding first enabled feature per index
+ * (Required in start_node. In non start_node, mbuf can hold next enabled
+ * feature)
+ */
+ rte_graph_feature_t first_enabled_feature_by_index[];
+} rte_graph_feature_list_t;
+
+/**
+ * rte_graph Feature arc object
+ *
+ * Feature arc object holds control plane and fast path information for all
+ * features and all interface index information for steering packets across
+ * feature nodes
+ *
+ * Within a feature arc, only RTE_GRAPH_FEATURE_MAX_PER_ARC features can be
+ * added. If more features needs to be added, another feature arc can be
+ * created
+ *
+ * Application gets rte_graph_feature_arc_t object via
+ * - rte_graph_feature_arc_create() OR
+ * - rte_graph_feature_arc_lookup_by_name()
+ *
+ * In fast path, rte_graph_feature_arc_t can be translated to (struct
+ * rte_graph_feature_arc *) via rte_graph_feature_arc_get(). Later is needed to
+ * add as an input argument to all fast path feature arc APIs
+ */
+struct __rte_cache_aligned rte_graph_feature_arc {
+ /* First 64B is fast path variables */
+ RTE_MARKER fast_path_variables;
+
+ /** runtime active feature list */
+ rte_graph_feature_rt_list_t active_feature_list;
+
+ /** Actual Size of feature_list object */
+ uint16_t feature_list_size;
+
+ /**
+ * Size each feature in fastpath.
+ * Required to navigate from feature to another feature in fast path
+ */
+ uint16_t feature_size;
+
+ /**
+ * Size of all feature data for an index
+ * Required to navigate through various feature data within a feature
+ * in fast path
+ */
+ uint16_t feature_data_size;
+
+ /**
+ * Quick fast path bitmask indicating if any feature enabled or not on
+ * any of the indexes. Helps in optimally process packets for the case
+ * when features are added but not enabled
+ *
+ * Separate for active and passive list
+ */
+ uint64_t feature_enable_bitmask[2];
+
+ /**
+ * Pointer to both active and passive feature list object
+ */
+ rte_graph_feature_list_t *feature_list[2];
+
+ /**
+ * Feature objects for each list
+ */
+ struct rte_graph_feature *features[2];
+
+ /** index in feature_arc_main */
+ uint16_t feature_arc_index;
+
+ uint16_t reserved[3];
+
+ /** Slow path variables follows*/
+ RTE_MARKER slow_path_variables;
+
+ /** feature arc name */
+ char feature_arc_name[RTE_GRAPH_FEATURE_ARC_NAMELEN];
+
+ /** All feature lists */
+ STAILQ_HEAD(, rte_graph_feature_node_list) all_features;
+
+ /** control plane counter to track enabled features */
+ uint32_t runtime_enabled_features;
+
+ /** Back pointer to feature_arc_main */
+ void *feature_arc_main;
+
+ /** Arc's start/base node */
+ struct rte_node_register *start_node;
+
+ /** maximum number of features supported by this arc */
+ uint32_t max_features;
+
+ /** maximum number of index supported by this arc */
+ uint32_t max_indexes;
+
+ /** Slow path bit mask per feature per index */
+ uint64_t feature_bit_mask_by_index[];
+};
+
+/**
+ * Feature arc main object
+ *
+ * Holds all feature arcs created by application
+ *
+ * RTE_GRAPH_FEATURE_ARC_MAX number of feature arcs can be created by
+ * application via rte_graph_feature_arc_create()
+ */
+typedef struct feature_arc_main {
+ /** number of feature arcs created by application */
+ uint32_t num_feature_arcs;
+
+ /** max features arcs allowed */
+ uint32_t max_feature_arcs;
+
+ /** feature arcs */
+ rte_graph_feature_arc_t feature_arcs[];
+} rte_graph_feature_arc_main_t;
+
+/** @internal Get feature arc pointer from object */
+#define rte_graph_feature_arc_get(arc) ((struct rte_graph_feature_arc *)arc)
+
+extern rte_graph_feature_arc_main_t *__feature_arc_main;
+
+/**
+ * API to know if feature is valid or not
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_is_valid(rte_graph_feature_t feature)
+{
+ return (feature != RTE_GRAPH_FEATURE_INVALID);
+}
+
+/**
+ * Get rte_graph_feature object with no checks
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ * @param feature_list
+ * active feature list retrieved from rte_graph_feature_arc_has_any_feature()
+ * or rte_graph_feature_arc_has_feature()
+ *
+ * @return
+ * Internal feature object.
+ */
+__rte_experimental
+static __rte_always_inline struct rte_graph_feature *
+__rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature,
+ const rte_graph_feature_rt_list_t feature_list)
+{
+ return ((struct rte_graph_feature *)(((uint8_t *)arc->features[feature_list]) +
+ (feature * arc->feature_size)));
+}
+
+/**
+ * Get rte_graph_feature object for a given interface/index from feature arc
+ *
+ * @param arc
+ * Feature arc pointer
+ * @param feature
+ * Feature index
+ *
+ * @return
+ * Internal feature object.
+ */
+__rte_experimental
+static __rte_always_inline struct rte_graph_feature *
+rte_graph_feature_get(struct rte_graph_feature_arc *arc, rte_graph_feature_t feature)
+{
+ if (unlikely(feature >= arc->max_features))
+ RTE_VERIFY(0);
+
+ if (likely(rte_graph_feature_is_valid(feature)))
+ return __rte_graph_feature_get(arc, feature, arc->active_feature_list);
+
+ return NULL;
+}
+
+__rte_experimental
+static __rte_always_inline rte_graph_feature_data_t *
+__rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ RTE_SET_USED(arc);
+ return ((rte_graph_feature_data_t *)(((uint8_t *)feature->feature_data_by_index) +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Get rte_graph feature data object for a index in feature
+ *
+ * @param arc
+ * feature arc
+ * @param feature
+ * Pointer to feature object
+ * @param index
+ * Index of feature maintained in slow path linked list
+ *
+ * @return
+ * Valid feature data
+ */
+__rte_experimental
+static __rte_always_inline rte_graph_feature_data_t *
+rte_graph_feature_data_get(struct rte_graph_feature_arc *arc, struct rte_graph_feature *feature,
+ uint8_t index)
+{
+ if (likely(index < arc->max_indexes))
+ return __rte_graph_feature_data_get(arc, feature, index);
+
+ RTE_VERIFY(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
+ * @param[out] plist
+ * Pointer to runtime active feature list which needs to be provided to other
+ * fast path APIs
+ *
+ * @return
+ * 0: If no feature enabled
+ * Non-Zero: Bitmask of features enabled. plist is valid
+ *
+ */
+__rte_experimental
+static __rte_always_inline uint64_t
+rte_graph_feature_arc_has_any_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_rt_list_t *plist)
+{
+ *plist = rte_atomic_load_explicit(&arc->active_feature_list, rte_memory_order_relaxed);
+
+ return (rte_atomic_load_explicit(arc->feature_enable_bitmask + (uint8_t)*plist,
+ rte_memory_order_relaxed));
+}
+
+/**
+ * Fast path API to check if provided feature is enabled on any interface/index
+ * or not
+ *
+ * @param arc
+ * Feature arc object
+ * @param feature
+ * Input rte_graph_feature_t that needs to be checked
+ * @param[out] plist
+ * Returns active list to caller which needs to be provided to other fast path
+ * APIs
+ *
+ * @return
+ * 1: If input [feature] is enabled in arc
+ * 0: If input [feature] is not enabled in arc
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_has_feature(struct rte_graph_feature_arc *arc,
+ rte_graph_feature_t feature,
+ rte_graph_feature_rt_list_t *plist)
+{
+ uint64_t bitmask = RTE_BIT64(feature);
+
+ *plist = rte_atomic_load_explicit(&arc->active_feature_list, rte_memory_order_relaxed);
+
+ return (bitmask & rte_atomic_load_explicit(arc->feature_enable_bitmask + (uint8_t)*plist,
+ 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 related fast path cache line
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object
+ */
+__rte_experimental
+static __rte_always_inline void
+rte_graph_feature_arc_feature_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature)
+{
+ /* feature cache line */
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)__rte_graph_feature_get(arc, feature, list));
+}
+
+/**
+ * Prefetch feature data upfront. Perform sanity
+ *
+ * @param arc
+ * RTE_GRAPH feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Pointer to feature object returned from @ref
+ * rte_graph_feature_arc_first_feature_get()
+ * @param index
+ * Interface/index
+ */
+__rte_experimental
+static __rte_always_inline void
+rte_graph_feature_arc_data_prefetch(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature, uint32_t index)
+{
+ if (likely(rte_graph_feature_is_valid(feature)))
+ rte_prefetch0((void *)((uint8_t *)arc->features[list] +
+ offsetof(struct rte_graph_feature, feature_data_by_index) +
+ (index * sizeof(rte_graph_feature_data_t))));
+}
+
+/**
+ * Fast path API to get first enabled feature on interface index
+ * Typically required in arc->start_node so that from returned feature,
+ * feature-data can be retrieved to steer packets
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * rte_graph_feature_arc_has_feature()
+ * @param index
+ * Interface Index
+ * @param[out] feature
+ * Pointer to rte_graph_feature_t.
+ *
+ * @return
+ * 1. Success. If first feature field is enabled and returned [feature] is valid
+ * 0. Failure. If first feature field is disabled in arc
+ *
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_first_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+
+ *feature = feature_list->first_enabled_feature_by_index[index];
+
+ return rte_graph_feature_is_valid(*feature);
+}
+
+/**
+ * Fast path API to get next enabled feature on interface index with provided
+ * input feature
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from
+ * rte_graph_feature_arc_has_any_feature() or
+ * @param index
+ * Interface Index
+ * @param[out] feature
+ * Pointer to rte_graph_feature_t. API sets next enabled feature on [index]
+ * from provided input feature. Valid only if API returns Success
+ * @param[out] next_edge
+ * Edge from current feature to next feature. Valid only if next feature is valid
+ *
+ * @return
+ * 1. Success. first feature field is enabled/valid
+ * 0. Failure. first feature field is disabled/invalid
+ */
+__rte_experimental
+static __rte_always_inline int
+rte_graph_feature_arc_next_feature_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *feature,
+ rte_edge_t *next_edge)
+{
+ rte_graph_feature_data_t *feature_data = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(*feature))) {
+ f = __rte_graph_feature_get(arc, *feature, list);
+ feature_data = rte_graph_feature_data_get(arc, f, index);
+ *feature = feature_data->next_enabled_feature;
+ *next_edge = feature_data->next_edge;
+ return rte_graph_feature_is_valid(*feature);
+ }
+
+ return 0;
+}
+
+/**
+ * Set fields with respect to first enabled feature in an arc and return edge
+ * Typically returned feature and interface index must be saved in rte_mbuf
+ * structure to pass this information to next feature node
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param index
+ * Index (of interface)
+ * @param[out] gf
+ * Pointer to rte_graph_feature_t. Valid if API returns Success
+ * @param[out] edge
+ * Edge to steer packet from arc->start_node to first enabled feature. Valid
+ * only if API returns Success
+ *
+ * @return
+ * 0: If valid feature is enabled and set by API in *gf
+ * 1: If valid feature is NOT enabled
+ */
+__rte_experimental
+static __rte_always_inline rte_graph_feature_t
+rte_graph_feature_arc_feature_set(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ uint32_t index,
+ rte_graph_feature_t *gf,
+ rte_edge_t *edge)
+{
+ struct rte_graph_feature_list *feature_list = arc->feature_list[list];
+ struct rte_graph_feature_data *feature_data = NULL;
+ struct rte_graph_feature *feature = NULL;
+ rte_graph_feature_t f;
+
+ f = feature_list->first_enabled_feature_by_index[index];
+
+ if (unlikely(rte_graph_feature_is_valid(f))) {
+ feature = __rte_graph_feature_get(arc, f, list);
+ feature_data = rte_graph_feature_data_get(arc, feature, index);
+ *gf = f;
+ *edge = feature_data->next_edge;
+ return 0;
+ }
+
+ return 1;
+}
+
+__rte_experimental
+static __rte_always_inline int32_t
+__rte_graph_feature_user_data_get(rte_graph_feature_data_t *fdata)
+{
+ return fdata->user_data;
+}
+
+/**
+ * Get user data corresponding to current feature set by application in
+ * rte_graph_feature_enable()
+ *
+ * @param arc
+ * Feature arc object
+ * @param list
+ * Pointer to runtime active feature list from rte_graph_feature_arc_has_any_feature();
+ * @param feature
+ * Feature index
+ * @param index
+ * Interface index
+ *
+ * @return
+ * -1: Failure
+ * Valid user data: Success
+ */
+__rte_experimental
+static __rte_always_inline int32_t
+rte_graph_feature_user_data_get(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t list,
+ rte_graph_feature_t feature,
+ uint32_t index)
+{
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature *f = NULL;
+
+ if (likely(rte_graph_feature_is_valid(feature))) {
+ f = __rte_graph_feature_get(arc, feature, list);
+ fdata = rte_graph_feature_data_get(arc, f, index);
+ return __rte_graph_feature_user_data_get(fdata);
+ }
+
+ return -1;
+}
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/graph/version.map b/lib/graph/version.map
index 2c83425ddc..3b7f475afd 100644
--- a/lib/graph/version.map
+++ b/lib/graph/version.map
@@ -52,3 +52,23 @@ DPDK_25 {
local: *;
};
+
+EXPERIMENTAL {
+ global:
+
+ # added in 24.11
+ rte_graph_feature_arc_init;
+ rte_graph_feature_arc_create;
+ rte_graph_feature_arc_lookup_by_name;
+ rte_graph_feature_add;
+ rte_graph_feature_enable;
+ rte_graph_feature_validate;
+ rte_graph_feature_disable;
+ rte_graph_feature_lookup;
+ rte_graph_feature_arc_destroy;
+ rte_graph_feature_arc_cleanup;
+ rte_graph_feature_arc_num_enabled_features;
+ rte_graph_feature_arc_num_features;
+ rte_graph_feature_arc_feature_to_name;
+ rte_graph_feature_arc_feature_to_node;
+};
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 2/5] graph: add feature arc option in graph create
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 1/5] graph: add feature arc support Nitin Saxena
@ 2024-10-09 13:29 ` Nitin Saxena
2024-10-09 13:30 ` [PATCH v3 3/5] graph: add IPv4 output feature arc Nitin Saxena
` (5 subsequent siblings)
7 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:29 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena, Pavan Nikhilesh
Added option in graph create to call feature-specific process node
functions. This removes extra overhead for checking feature arc status
in nodes where application is not using feature arc processing
Signed-off-by: Pavan Nikhilesh <pbhagavatula@marvell.com>
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
doc/guides/rel_notes/release_24_11.rst | 3 +++
lib/graph/graph.c | 1 +
lib/graph/graph_populate.c | 7 ++++++-
lib/graph/graph_private.h | 3 +++
lib/graph/node.c | 2 ++
lib/graph/rte_graph.h | 3 +++
6 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst
index d6d64518e0..5195badf54 100644
--- a/doc/guides/rel_notes/release_24_11.rst
+++ b/doc/guides/rel_notes/release_24_11.rst
@@ -122,6 +122,9 @@ ABI Changes
Also, make sure to start the actual text at the margin.
=======================================================
+* graph: Added new `feature_arc_enable` parameter in `struct rte_graph_param` to
+ allow `rte_graph_walk()` call `feat_arc_proc` node callback function, if it
+ is provided in node registration
Known Issues
------------
diff --git a/lib/graph/graph.c b/lib/graph/graph.c
index d5b8c9f918..b0ad3a83ae 100644
--- a/lib/graph/graph.c
+++ b/lib/graph/graph.c
@@ -455,6 +455,7 @@ rte_graph_create(const char *name, struct rte_graph_param *prm)
graph->parent_id = RTE_GRAPH_ID_INVALID;
graph->lcore_id = RTE_MAX_LCORE;
graph->num_pkt_to_capture = prm->num_pkt_to_capture;
+ graph->feature_arc_enabled = prm->feature_arc_enable;
if (prm->pcap_filename)
rte_strscpy(graph->pcap_filename, prm->pcap_filename, RTE_GRAPH_PCAP_FILE_SZ);
diff --git a/lib/graph/graph_populate.c b/lib/graph/graph_populate.c
index ed596a7711..5d8aa7b903 100644
--- a/lib/graph/graph_populate.c
+++ b/lib/graph/graph_populate.c
@@ -79,8 +79,13 @@ graph_nodes_populate(struct graph *_graph)
if (graph_pcap_is_enable()) {
node->process = graph_pcap_dispatch;
node->original_process = graph_node->node->process;
- } else
+ if (_graph->feature_arc_enabled && graph_node->node->feat_arc_proc)
+ node->original_process = graph_node->node->feat_arc_proc;
+ } else {
node->process = graph_node->node->process;
+ if (_graph->feature_arc_enabled && graph_node->node->feat_arc_proc)
+ node->process = graph_node->node->feat_arc_proc;
+ }
memcpy(node->name, graph_node->node->name, RTE_GRAPH_NAMESIZE);
pid = graph_node->node->parent_id;
if (pid != RTE_NODE_ID_INVALID) { /* Cloned node */
diff --git a/lib/graph/graph_private.h b/lib/graph/graph_private.h
index d557d55f2d..58ba0abeff 100644
--- a/lib/graph/graph_private.h
+++ b/lib/graph/graph_private.h
@@ -56,6 +56,7 @@ struct node {
unsigned int lcore_id;
/**< Node runs on the Lcore ID used for mcore dispatch model. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Allocated identifier for the node. */
@@ -126,6 +127,8 @@ struct graph {
/**< Number of packets to be captured per core. */
char pcap_filename[RTE_GRAPH_PCAP_FILE_SZ];
/**< pcap file name/path. */
+ uint8_t feature_arc_enabled;
+ /**< Graph feature arc. */
STAILQ_HEAD(gnode_list, graph_node) node_list;
/**< Nodes in a graph. */
};
diff --git a/lib/graph/node.c b/lib/graph/node.c
index 99a9622779..d8fd273543 100644
--- a/lib/graph/node.c
+++ b/lib/graph/node.c
@@ -90,6 +90,7 @@ __rte_node_register(const struct rte_node_register *reg)
goto free;
node->flags = reg->flags;
node->process = reg->process;
+ node->feat_arc_proc = reg->feat_arc_proc;
node->init = reg->init;
node->fini = reg->fini;
node->nb_edges = reg->nb_edges;
@@ -137,6 +138,7 @@ node_clone(struct node *node, const char *name)
/* Clone the source node */
reg->flags = node->flags;
reg->process = node->process;
+ reg->feat_arc_proc = node->feat_arc_proc;
reg->init = node->init;
reg->fini = node->fini;
reg->nb_edges = node->nb_edges;
diff --git a/lib/graph/rte_graph.h b/lib/graph/rte_graph.h
index ecfec2068a..1a3bd7e1ba 100644
--- a/lib/graph/rte_graph.h
+++ b/lib/graph/rte_graph.h
@@ -172,6 +172,8 @@ struct rte_graph_param {
uint32_t mp_capacity; /**< Capacity of memory pool for dispatch model. */
} dispatch;
};
+
+ bool feature_arc_enable; /**< Enable Graph feature arc. */
};
/**
@@ -470,6 +472,7 @@ struct rte_node_register {
uint64_t flags; /**< Node configuration flag. */
#define RTE_NODE_SOURCE_F (1ULL << 0) /**< Node type is source. */
rte_node_process_t process; /**< Node process function. */
+ rte_node_process_t feat_arc_proc; /**< Node feature-arch process function. */
rte_node_init_t init; /**< Node init function. */
rte_node_fini_t fini; /**< Node fini function. */
rte_node_t id; /**< Node Identifier. */
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 3/5] graph: add IPv4 output feature arc
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 1/5] graph: add feature arc support Nitin Saxena
2024-10-09 13:29 ` [PATCH v3 2/5] graph: add feature arc option in graph create Nitin Saxena
@ 2024-10-09 13:30 ` Nitin Saxena
2024-10-09 13:30 ` [PATCH v3 4/5] test/graph_feature_arc: add functional tests Nitin Saxena
` (4 subsequent siblings)
7 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena
add ipv4-output feature arc in ipv4-rewrite node to allow
custom/standard nodes(like outbound IPsec policy node) in outgoing
forwarding path
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
lib/node/ip4_rewrite.c | 476 +++++++++++++++++++++++++++++-------
lib/node/ip4_rewrite_priv.h | 15 +-
lib/node/node_private.h | 20 +-
lib/node/rte_node_ip4_api.h | 3 +
4 files changed, 417 insertions(+), 97 deletions(-)
diff --git a/lib/node/ip4_rewrite.c b/lib/node/ip4_rewrite.c
index 34a920df5e..5a160335f2 100644
--- a/lib/node/ip4_rewrite.c
+++ b/lib/node/ip4_rewrite.c
@@ -15,39 +15,156 @@
#include "ip4_rewrite_priv.h"
#include "node_private.h"
+#define ALL_PKT_MASK 0xf
+
struct ip4_rewrite_node_ctx {
+ rte_graph_feature_arc_t output_feature_arc;
/* Dynamic offset to mbuf priv1 */
int mbuf_priv1_off;
/* Cached next index */
uint16_t next_index;
+ uint16_t last_tx;
};
+typedef struct rewrite_priv_vars {
+ union {
+ struct {
+ rte_xmm_t xmm1;
+ };
+ struct __rte_packed {
+ uint16_t next0;
+ uint16_t next1;
+ uint16_t next2;
+ uint16_t next3;
+ uint16_t last_tx_interface;
+ uint16_t last_if_feature;
+ uint16_t actual_feat_mask;
+ uint16_t speculative_feat_mask;
+ };
+ };
+} rewrite_priv_vars_t;
+
static struct ip4_rewrite_node_main *ip4_rewrite_nm;
#define IP4_REWRITE_NODE_LAST_NEXT(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->next_index)
+#define IP4_REWRITE_NODE_LAST_TX(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->last_tx)
+
#define IP4_REWRITE_NODE_PRIV1_OFF(ctx) \
(((struct ip4_rewrite_node_ctx *)ctx)->mbuf_priv1_off)
-static uint16_t
-ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
- void **objs, uint16_t nb_objs)
+#define IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(ctx) \
+ (((struct ip4_rewrite_node_ctx *)ctx)->output_feature_arc)
+
+static __rte_always_inline void
+prefetch_mbuf_and_dynfield(struct rte_mbuf *mbuf)
{
+ /* prefetch first cache line required for accessing buf_addr */
+ rte_prefetch0((void *)mbuf);
+}
+
+static __rte_always_inline void
+check_output_feature_x4(struct rte_graph_feature_arc *arc,
+ const rte_graph_feature_rt_list_t flist,
+ rewrite_priv_vars_t *pvar, struct node_mbuf_priv1 *priv0,
+ struct node_mbuf_priv1 *priv1, struct node_mbuf_priv1 *priv2,
+ struct node_mbuf_priv1 *priv3)
+{
+ uint32_t mask = 0;
+ uint16_t xor = 0;
+
+ /*
+ * interface edge's start from 1 and not from 0 as "pkt_drop"
+ * is next node at 0th index
+ */
+ priv0->if_index = pvar->next0 - 1;
+ priv1->if_index = pvar->next1 - 1;
+ priv2->if_index = pvar->next2 - 1;
+ priv3->if_index = pvar->next3 - 1;
+
+ /* Find out if all packets are sent to last_tx_interface */
+ xor = pvar->last_tx_interface ^ priv0->if_index;
+ xor += priv0->if_index ^ priv1->if_index;
+ xor += priv1->if_index ^ priv2->if_index;
+ xor += priv2->if_index ^ priv3->if_index;
+
+ if (likely(!xor)) {
+ /* copy last interface feature and feature mask */
+ priv0->current_feature = priv1->current_feature =
+ priv2->current_feature = priv3->current_feature =
+ pvar->last_if_feature;
+ pvar->actual_feat_mask = pvar->speculative_feat_mask;
+ } else {
+ /* create a mask for index which does not have feature
+ * Also override next edge and if feature enabled, get feature
+ */
+ mask = rte_graph_feature_arc_feature_set(arc, flist, priv0->if_index,
+ &priv0->current_feature,
+ &pvar->next0);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv1->if_index,
+ &priv1->current_feature,
+ &pvar->next1)) << 1);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv2->if_index,
+ &priv2->current_feature,
+ &pvar->next2)) << 2);
+
+ mask |= ((rte_graph_feature_arc_feature_set(arc, flist, priv3->if_index,
+ &priv3->current_feature,
+ &pvar->next3)) << 3);
+
+ /*
+ * add last tx and last feature regardless even if feature is
+ * valid or not
+ */
+ pvar->last_tx_interface = priv3->if_index;
+ pvar->last_if_feature = priv3->current_feature;
+ /* Set 0xf if invalid feature to last packet, else 0 */
+ pvar->speculative_feat_mask = (priv3->current_feature ==
+ RTE_GRAPH_FEATURE_INVALID) ? ALL_PKT_MASK : 0x0;
+ pvar->actual_feat_mask = mask;
+ }
+}
+
+static __rte_always_inline uint16_t
+__ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs,
+ const int dyn, const int check_enabled_features,
+ struct rte_graph_feature_arc *out_feature_arc,
+ const rte_graph_feature_rt_list_t flist)
+{
+ struct node_mbuf_priv1 *priv0 = NULL, *priv1 = NULL, *priv2 = NULL, *priv3 = NULL;
struct rte_mbuf *mbuf0, *mbuf1, *mbuf2, *mbuf3, **pkts;
struct ip4_rewrite_nh_header *nh = ip4_rewrite_nm->nh;
- const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
- uint16_t next0, next1, next2, next3, next_index;
- struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
uint16_t n_left_from, held = 0, last_spec = 0;
+ struct rte_ipv4_hdr *ip0, *ip1, *ip2, *ip3;
+ rewrite_priv_vars_t pvar;
+ int64_t fd0, fd1, fd2, fd3;
+ rte_edge_t fix_spec = 0;
void *d0, *d1, *d2, *d3;
void **to_next, **from;
+ uint16_t next_index;
rte_xmm_t priv01;
rte_xmm_t priv23;
int i;
- /* Speculative next as last next */
+ RTE_SET_USED(fd0);
+ RTE_SET_USED(fd1);
+ RTE_SET_USED(fd2);
+ RTE_SET_USED(fd3);
+
+ /* Initialize speculative variables.*/
+
+ /* Last interface */
+ pvar.last_tx_interface = IP4_REWRITE_NODE_LAST_TX(node->ctx);
+ /*last next from node ctx*/
next_index = IP4_REWRITE_NODE_LAST_NEXT(node->ctx);
+ pvar.speculative_feat_mask = ALL_PKT_MASK;
+ pvar.actual_feat_mask = 0;
+
rte_prefetch0(nh);
pkts = (struct rte_mbuf **)objs;
@@ -55,20 +172,47 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
n_left_from = nb_objs;
for (i = 0; i < 4 && i < n_left_from; i++)
- rte_prefetch0(pkts[i]);
+ prefetch_mbuf_and_dynfield(pkts[i]);
/* Get stream for the speculated next node */
to_next = rte_node_next_stream_get(graph, node, next_index, nb_objs);
+
+ /* prefetch speculative feature and corresponding data */
+ if (check_enabled_features) {
+ /*
+ * Get first feature enabled, if any, on last_tx_interface
+ */
+ if (unlikely(rte_graph_feature_arc_first_feature_get(out_feature_arc,
+ flist,
+ pvar.last_tx_interface,
+ (rte_graph_feature_t *)
+ &pvar.last_if_feature))) {
+ /* prefetch feature cache line */
+ rte_graph_feature_arc_feature_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature);
+
+ /* prefetch feature data cache line */
+ rte_graph_feature_arc_data_prefetch(out_feature_arc, flist,
+ pvar.last_if_feature,
+ pvar.last_tx_interface);
+ /*
+ * Set speculativa_feat mask to indicate, all 4 packets
+ * going to feature path
+ */
+ pvar.speculative_feat_mask = 0;
+ }
+ }
+
/* Update Ethernet header of pkts */
while (n_left_from >= 4) {
if (likely(n_left_from > 7)) {
/* Prefetch only next-mbuf struct and priv area.
* Data need not be prefetched as we only write.
*/
- rte_prefetch0(pkts[4]);
- rte_prefetch0(pkts[5]);
- rte_prefetch0(pkts[6]);
- rte_prefetch0(pkts[7]);
+ prefetch_mbuf_and_dynfield(pkts[4]);
+ prefetch_mbuf_and_dynfield(pkts[5]);
+ prefetch_mbuf_and_dynfield(pkts[6]);
+ prefetch_mbuf_and_dynfield(pkts[7]);
}
mbuf0 = pkts[0];
@@ -78,66 +222,138 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 4;
n_left_from -= 4;
+
+ /* Copy mbuf private data into private variables */
priv01.u64[0] = node_mbuf_priv1(mbuf0, dyn)->u;
priv01.u64[1] = node_mbuf_priv1(mbuf1, dyn)->u;
priv23.u64[0] = node_mbuf_priv1(mbuf2, dyn)->u;
priv23.u64[1] = node_mbuf_priv1(mbuf3, dyn)->u;
- /* Increment checksum by one. */
- priv01.u32[1] += rte_cpu_to_be_16(0x0100);
- priv01.u32[3] += rte_cpu_to_be_16(0x0100);
- priv23.u32[1] += rte_cpu_to_be_16(0x0100);
- priv23.u32[3] += rte_cpu_to_be_16(0x0100);
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
- nh[priv01.u16[0]].rewrite_len);
-
- next0 = nh[priv01.u16[0]].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- ip0->time_to_live = priv01.u16[1] - 1;
- ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
- d1 = rte_pktmbuf_mtod(mbuf1, void *);
- rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
- nh[priv01.u16[4]].rewrite_len);
-
- next1 = nh[priv01.u16[4]].tx_node;
- ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
- sizeof(struct rte_ether_hdr));
- ip1->time_to_live = priv01.u16[5] - 1;
- ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
- d2 = rte_pktmbuf_mtod(mbuf2, void *);
- rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
- nh[priv23.u16[0]].rewrite_len);
- next2 = nh[priv23.u16[0]].tx_node;
- ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
- sizeof(struct rte_ether_hdr));
- ip2->time_to_live = priv23.u16[1] - 1;
- ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
-
- /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
- d3 = rte_pktmbuf_mtod(mbuf3, void *);
- rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
- nh[priv23.u16[4]].rewrite_len);
-
- next3 = nh[priv23.u16[4]].tx_node;
- ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
- sizeof(struct rte_ether_hdr));
- ip3->time_to_live = priv23.u16[5] - 1;
- ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ /* Copy next edge from next hop */
+ pvar.next0 = nh[priv01.u16[0]].tx_node;
+ pvar.next1 = nh[priv01.u16[4]].tx_node;
+ pvar.next2 = nh[priv23.u16[0]].tx_node;
+ pvar.next3 = nh[priv23.u16[4]].tx_node;
+
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ priv1 = node_mbuf_priv1(mbuf1, dyn);
+ priv2 = node_mbuf_priv1(mbuf2, dyn);
+ priv3 = node_mbuf_priv1(mbuf3, dyn);
+
+ /* If feature is enabled, override next edge for each mbuf
+ * and set node_mbuf_priv data appropriately
+ */
+ check_output_feature_x4(out_feature_arc, flist,
+ &pvar, priv0, priv1, priv2, priv3);
+
+ /* check_output_feature_x4() returns bit mask which indicates
+ * which packet is not following feature path, hence normal processing
+ * has to happen on them
+ */
+ if (unlikely(pvar.actual_feat_mask)) {
+ if (pvar.actual_feat_mask & 0x1) {
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x2) {
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+ }
+ if (pvar.actual_feat_mask & 0x4) {
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+ }
+ if (pvar.actual_feat_mask & 0x8) {
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
+ }
+ } else {
+ /* Case when no feature is enabled */
+
+ /* Increment checksum by one. */
+ priv01.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv01.u32[3] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[1] += rte_cpu_to_be_16(0x0100);
+ priv23.u32[3] += rte_cpu_to_be_16(0x0100);
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf0 */
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[priv01.u16[0]].rewrite_data,
+ nh[priv01.u16[0]].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ ip0->time_to_live = priv01.u16[1] - 1;
+ ip0->hdr_checksum = priv01.u16[2] + priv01.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf1 */
+ d1 = rte_pktmbuf_mtod(mbuf1, void *);
+ rte_memcpy(d1, nh[priv01.u16[4]].rewrite_data,
+ nh[priv01.u16[4]].rewrite_len);
+
+ ip1 = (struct rte_ipv4_hdr *)((uint8_t *)d1 +
+ sizeof(struct rte_ether_hdr));
+ ip1->time_to_live = priv01.u16[5] - 1;
+ ip1->hdr_checksum = priv01.u16[6] + priv01.u16[7];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf2 */
+ d2 = rte_pktmbuf_mtod(mbuf2, void *);
+ rte_memcpy(d2, nh[priv23.u16[0]].rewrite_data,
+ nh[priv23.u16[0]].rewrite_len);
+ ip2 = (struct rte_ipv4_hdr *)((uint8_t *)d2 +
+ sizeof(struct rte_ether_hdr));
+ ip2->time_to_live = priv23.u16[1] - 1;
+ ip2->hdr_checksum = priv23.u16[2] + priv23.u16[3];
+
+ /* Update ttl,cksum rewrite ethernet hdr on mbuf3 */
+ d3 = rte_pktmbuf_mtod(mbuf3, void *);
+ rte_memcpy(d3, nh[priv23.u16[4]].rewrite_data,
+ nh[priv23.u16[4]].rewrite_len);
+
+ ip3 = (struct rte_ipv4_hdr *)((uint8_t *)d3 +
+ sizeof(struct rte_ether_hdr));
+ ip3->time_to_live = priv23.u16[5] - 1;
+ ip3->hdr_checksum = priv23.u16[6] + priv23.u16[7];
+ }
/* Enqueue four to next node */
- rte_edge_t fix_spec =
- ((next_index == next0) && (next0 == next1) &&
- (next1 == next2) && (next2 == next3));
+ fix_spec = next_index ^ pvar.next0;
+ fix_spec += next_index ^ pvar.next1;
+ fix_spec += next_index ^ pvar.next2;
+ fix_spec += next_index ^ pvar.next3;
- if (unlikely(fix_spec == 0)) {
+ if (unlikely(fix_spec != 0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -146,56 +362,56 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
last_spec = 0;
/* next0 */
- if (next_index == next0) {
+ if (next_index == pvar.next0) {
to_next[0] = from[0];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next0,
+ rte_node_enqueue_x1(graph, node, pvar.next0,
from[0]);
}
/* next1 */
- if (next_index == next1) {
+ if (next_index == pvar.next1) {
to_next[0] = from[1];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next1,
+ rte_node_enqueue_x1(graph, node, pvar.next1,
from[1]);
}
/* next2 */
- if (next_index == next2) {
+ if (next_index == pvar.next2) {
to_next[0] = from[2];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next2,
+ rte_node_enqueue_x1(graph, node, pvar.next2,
from[2]);
}
/* next3 */
- if (next_index == next3) {
+ if (next_index == pvar.next3) {
to_next[0] = from[3];
to_next++;
held++;
} else {
- rte_node_enqueue_x1(graph, node, next3,
+ rte_node_enqueue_x1(graph, node, pvar.next3,
from[3]);
}
from += 4;
/* Change speculation if last two are same */
- if ((next_index != next3) && (next2 == next3)) {
+ if ((next_index != pvar.next3) && (pvar.next2 == pvar.next3)) {
/* Put the current speculated node */
rte_node_next_stream_put(graph, node,
next_index, held);
held = 0;
/* Get next speculated stream */
- next_index = next3;
+ next_index = pvar.next3;
to_next = rte_node_next_stream_get(
graph, node, next_index, nb_objs);
}
@@ -212,20 +428,41 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
pkts += 1;
n_left_from -= 1;
- d0 = rte_pktmbuf_mtod(mbuf0, void *);
- rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
- nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
-
- next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
- ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
- sizeof(struct rte_ether_hdr));
- chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
- rte_cpu_to_be_16(0x0100);
- chksum += chksum >= 0xffff;
- ip0->hdr_checksum = chksum;
- ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
-
- if (unlikely(next_index ^ next0)) {
+ pvar.next0 = nh[node_mbuf_priv1(mbuf0, dyn)->nh].tx_node;
+ if (check_enabled_features) {
+ priv0 = node_mbuf_priv1(mbuf0, dyn);
+ if (pvar.next0 != (pvar.last_tx_interface + 1)) {
+ priv0->if_index = pvar.next0 - 1;
+ rte_graph_feature_arc_feature_set(out_feature_arc, flist,
+ priv0->if_index,
+ &priv0->current_feature,
+ &pvar.next0);
+ pvar.last_tx_interface = priv0->if_index;
+ pvar.last_if_feature = priv0->current_feature;
+ } else {
+ /* current mbuf index is same as last_tx_interface */
+ priv0->if_index = pvar.last_tx_interface;
+ priv0->current_feature = pvar.last_if_feature;
+ }
+ }
+ /* Do the needful if either feature arc is disabled OR
+ * Invalid feature is present
+ */
+ if (!check_enabled_features ||
+ (priv0->current_feature == RTE_GRAPH_FEATURE_INVALID)) {
+ d0 = rte_pktmbuf_mtod(mbuf0, void *);
+ rte_memcpy(d0, nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_data,
+ nh[node_mbuf_priv1(mbuf0, dyn)->nh].rewrite_len);
+
+ ip0 = (struct rte_ipv4_hdr *)((uint8_t *)d0 +
+ sizeof(struct rte_ether_hdr));
+ chksum = node_mbuf_priv1(mbuf0, dyn)->cksum +
+ rte_cpu_to_be_16(0x0100);
+ chksum += chksum >= 0xffff;
+ ip0->hdr_checksum = chksum;
+ ip0->time_to_live = node_mbuf_priv1(mbuf0, dyn)->ttl - 1;
+ }
+ if (unlikely(next_index ^ pvar.next0)) {
/* Copy things successfully speculated till now */
rte_memcpy(to_next, from, last_spec * sizeof(from[0]));
from += last_spec;
@@ -233,13 +470,15 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
held += last_spec;
last_spec = 0;
- rte_node_enqueue_x1(graph, node, next0, from[0]);
+ rte_node_enqueue_x1(graph, node, pvar.next0, from[0]);
from += 1;
} else {
last_spec += 1;
}
}
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = pvar.last_tx_interface;
+
/* !!! Home run !!! */
if (likely(last_spec == nb_objs)) {
rte_node_next_stream_move(graph, node, next_index);
@@ -255,22 +494,78 @@ ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
return nb_objs;
}
+static uint16_t
+ip4_rewrite_feature_node_process(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(IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx));
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+ rte_graph_feature_rt_list_t flist;
+
+ /* If any feature is enabled on this arc */
+ if (unlikely(rte_graph_feature_arc_has_any_feature(arc, &flist))) {
+ if (flist)
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs,
+ dyn,
+ 1 /* check features */, arc,
+ (rte_graph_feature_rt_list_t)1);
+ else
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs,
+ dyn,
+ 1 /* check features */, arc,
+ (rte_graph_feature_rt_list_t)0);
+ } else {
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs, dyn,
+ 0/* don't check features*/, NULL,
+ 0/* don't care */);
+ }
+ return 0;
+}
+
+static uint16_t
+ip4_rewrite_node_process(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ const int dyn = IP4_REWRITE_NODE_PRIV1_OFF(node->ctx);
+
+ return __ip4_rewrite_node_process(graph, node, objs, nb_objs, dyn,
+ 0/* don't check features*/, NULL,
+ 0/* don't care */);
+}
+
static int
ip4_rewrite_node_init(const struct rte_graph *graph, struct rte_node *node)
{
+ rte_graph_feature_arc_t feature_arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
static bool init_once;
RTE_SET_USED(graph);
RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_node_ctx) > RTE_NODE_CTX_SZ);
+ RTE_BUILD_BUG_ON(sizeof(struct ip4_rewrite_nh_header) != RTE_CACHE_LINE_MIN_SIZE);
if (!init_once) {
node_mbuf_priv1_dynfield_offset = rte_mbuf_dynfield_register(
&node_mbuf_priv1_dynfield_desc);
if (node_mbuf_priv1_dynfield_offset < 0)
return -rte_errno;
- init_once = true;
+
+ /* Create ipv4-output feature arc, if not created
+ */
+ if (rte_graph_feature_arc_lookup_by_name(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ NULL) < 0) {
+ if (rte_graph_feature_arc_create(RTE_IP4_OUTPUT_FEATURE_ARC_NAME,
+ RTE_GRAPH_FEATURE_MAX_PER_ARC,
+ RTE_MAX_ETHPORTS,
+ ip4_rewrite_node_get(), &feature_arc)) {
+ return -rte_errno;
+ }
+ init_once = true;
+ }
}
IP4_REWRITE_NODE_PRIV1_OFF(node->ctx) = node_mbuf_priv1_dynfield_offset;
+ IP4_REWRITE_NODE_OUTPUT_FEATURE_ARC(node->ctx) = feature_arc;
+ IP4_REWRITE_NODE_LAST_TX(node->ctx) = UINT16_MAX;
node_dbg("ip4_rewrite", "Initialized ip4_rewrite node initialized");
@@ -329,6 +624,7 @@ rte_node_ip4_rewrite_add(uint16_t next_hop, uint8_t *rewrite_data,
static struct rte_node_register ip4_rewrite_node = {
.process = ip4_rewrite_node_process,
+ .feat_arc_proc = ip4_rewrite_feature_node_process,
.name = "ip4_rewrite",
/* Default edge i.e '0' is pkt drop */
.nb_edges = 1,
diff --git a/lib/node/ip4_rewrite_priv.h b/lib/node/ip4_rewrite_priv.h
index 5105ec1d29..52f39601bd 100644
--- a/lib/node/ip4_rewrite_priv.h
+++ b/lib/node/ip4_rewrite_priv.h
@@ -5,9 +5,11 @@
#define __INCLUDE_IP4_REWRITE_PRIV_H__
#include <rte_common.h>
+#include <rte_graph_feature_arc.h>
#define RTE_GRAPH_IP4_REWRITE_MAX_NH 64
-#define RTE_GRAPH_IP4_REWRITE_MAX_LEN 56
+#define RTE_GRAPH_IP4_REWRITE_MAX_LEN (sizeof(struct rte_ether_hdr) + \
+ (2 * sizeof(struct rte_vlan_hdr)))
/**
* @internal
@@ -15,11 +17,9 @@
* Ipv4 rewrite next hop header data structure. Used to store port specific
* rewrite data.
*/
-struct ip4_rewrite_nh_header {
- uint16_t rewrite_len; /**< Header rewrite length. */
+struct __rte_cache_aligned ip4_rewrite_nh_header {
uint16_t tx_node; /**< Tx node next index identifier. */
- uint16_t enabled; /**< NH enable flag */
- uint16_t rsvd;
+ uint16_t rewrite_len; /**< Header rewrite length. */
union {
struct {
struct rte_ether_addr dst;
@@ -30,8 +30,13 @@ struct ip4_rewrite_nh_header {
uint8_t rewrite_data[RTE_GRAPH_IP4_REWRITE_MAX_LEN];
/**< Generic rewrite data */
};
+ /* used in control path */
+ uint8_t enabled; /**< NH enable flag */
};
+_Static_assert(sizeof(struct ip4_rewrite_nh_header) <= (size_t)RTE_CACHE_LINE_SIZE,
+ "ip4_rewrite_nh_header size must be less or equal to cache line");
+
/**
* @internal
*
diff --git a/lib/node/node_private.h b/lib/node/node_private.h
index 1de7306792..25db04a9a6 100644
--- a/lib/node/node_private.h
+++ b/lib/node/node_private.h
@@ -12,6 +12,9 @@
#include <rte_mbuf.h>
#include <rte_mbuf_dyn.h>
+#include <rte_graph_worker_common.h>
+#include <rte_graph_feature_arc_worker.h>
+
extern int rte_node_logtype;
#define RTE_LOGTYPE_NODE rte_node_logtype
@@ -29,15 +32,28 @@ extern int rte_node_logtype;
*/
struct node_mbuf_priv1 {
union {
- /* IP4/IP6 rewrite */
+ /**
+ * IP4/IP6 rewrite
+ * only used to pass lookup data from
+ * ip4-lookup to ip4-rewrite
+ */
struct {
uint16_t nh;
uint16_t ttl;
uint32_t cksum;
};
-
uint64_t u;
};
+ /**
+ * Feature arc data
+ */
+ struct {
+ /** interface index */
+ uint16_t if_index;
+ /** feature that current mbuf holds */
+ rte_graph_feature_t current_feature;
+ uint8_t rsvd;
+ };
};
static const struct rte_mbuf_dynfield node_mbuf_priv1_dynfield_desc = {
diff --git a/lib/node/rte_node_ip4_api.h b/lib/node/rte_node_ip4_api.h
index 24f8ec843a..0de06f7fc7 100644
--- a/lib/node/rte_node_ip4_api.h
+++ b/lib/node/rte_node_ip4_api.h
@@ -23,6 +23,7 @@ extern "C" {
#include <rte_compat.h>
#include <rte_graph.h>
+#include <rte_graph_feature_arc_worker.h>
/**
* IP4 lookup next nodes.
@@ -67,6 +68,8 @@ struct rte_node_ip4_reassembly_cfg {
/**< Node identifier to configure. */
};
+#define RTE_IP4_OUTPUT_FEATURE_ARC_NAME "ipv4-output"
+
/**
* Add ipv4 route to lookup table.
*
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 4/5] test/graph_feature_arc: add functional tests
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
` (2 preceding siblings ...)
2024-10-09 13:30 ` [PATCH v3 3/5] graph: add IPv4 output feature arc Nitin Saxena
@ 2024-10-09 13:30 ` Nitin Saxena
2024-10-09 13:30 ` [PATCH v3 5/5] docs: add programming guide for feature arc Nitin Saxena
` (3 subsequent siblings)
7 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena
Added functional unit test case for verifying feature arc control plane
and fast path APIs
How to run:
$ echo "graph_feature_arc_autotest" | ./bin/dpdk-test
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
app/test/meson.build | 1 +
app/test/test_graph_feature_arc.c | 1410 +++++++++++++++++++++++++++++
2 files changed, 1411 insertions(+)
create mode 100644 app/test/test_graph_feature_arc.c
diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..740fa1bfb4 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -90,6 +90,7 @@ source_file_deps = {
'test_func_reentrancy.c': ['hash', 'lpm'],
'test_graph.c': ['graph'],
'test_graph_perf.c': ['graph'],
+ 'test_graph_feature_arc.c': ['graph'],
'test_hash.c': ['net', 'hash'],
'test_hash_functions.c': ['hash'],
'test_hash_multiwriter.c': ['hash'],
diff --git a/app/test/test_graph_feature_arc.c b/app/test/test_graph_feature_arc.c
new file mode 100644
index 0000000000..8ea7a7d3eb
--- /dev/null
+++ b/app/test/test_graph_feature_arc.c
@@ -0,0 +1,1410 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell International Ltd.
+ */
+
+#include "test.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdalign.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_errno.h>
+
+#ifndef RTE_EXEC_ENV_WINDOWS
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
+#include <rte_random.h>
+#include <rte_graph_feature_arc.h>
+#include <rte_graph_feature_arc_worker.h>
+
+#define MBUFF_SIZE 512
+#define TEST_ARC1_NAME "arc1"
+#define TEST_ARC2_NAME "arc2"
+#define MAX_INDEXES 10
+#define MAX_FEATURES 5
+
+#define SOURCE1 "test_node_arc_source1"
+#define INPUT_STATIC "test_node_arc_input_static"
+#define OUTPUT_STATIC "test_node_arc_output_static"
+#define PKT_FREE_STATIC "test_node_arc_pkt_free_static"
+#define ARC1_FEATURE1 "test_node_arc1_feature1"
+#define ARC1_FEATURE2 "test_node_arc1_feature2"
+#define ARC2_FEATURE1 "test_node_arc2_feature1"
+#define ARC2_FEATURE2 "test_node_arc2_feature2"
+#define ARC2_FEATURE3 "test_node_arc2_feature3"
+#define DUMMY1_STATIC "test_node_arc_dummy1_static"
+#define DUMMY2_STATIC "test_node_arc_dummy2_static"
+
+/* (Node index, Node Name, feature user data base */
+#define FOREACH_TEST_NODE_ARC { \
+ R(0, SOURCE1, 64) \
+ R(1, INPUT_STATIC, 128) \
+ R(2, OUTPUT_STATIC, 256) \
+ R(3, PKT_FREE_STATIC, 512) \
+ R(4, ARC1_FEATURE1, 1024) \
+ R(5, ARC1_FEATURE2, 2048) \
+ R(6, ARC2_FEATURE1, 4096) \
+ R(7, ARC2_FEATURE2, 8192) \
+ R(8, ARC2_FEATURE3, 16384) \
+ R(9, DUMMY1_STATIC, 32768) \
+ R(10, DUMMY2_STATIC, 65536) \
+ }
+
+/**
+ * ARC1: Feature arc on ingress interface
+ * ARC2: Feature arc on egress interface
+ * XX_static: Static nodes
+ * XX_featureX: Feature X on arc
+ *
+ * -----> ARC1_FEATURE1
+ * | | |
+ * | | v
+ * | | ARC1_FEATURE2
+ * | | |
+ * | v v
+ * SOURCE1 ->-----> INPUT_STATIC --> OUTPUT_STATIC -----> PKT_FREE_STATIC
+ * | | | ^ ^ ^
+ * | | | | | |
+ * | | --> ARC2_FEATURE1 | |
+ * | | ^ ^ | |
+ * | | | | | |
+ * | ----------c-> ARC2_FEATURE2 |
+ * | | ^ |
+ * | | | |
+ * ----------> ARC2_FEATURE3 -------
+ */
+const char *node_names_feature_arc[] = {
+ SOURCE1, INPUT_STATIC, OUTPUT_STATIC, PKT_FREE_STATIC,
+ ARC1_FEATURE1, ARC1_FEATURE2, ARC2_FEATURE1, ARC2_FEATURE2, ARC2_FEATURE3,
+ DUMMY1_STATIC, DUMMY2_STATIC
+};
+
+#define MAX_NODES RTE_DIM(node_names_feature_arc)
+
+/* Function declarations */
+static uint16_t
+source1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+input_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+input_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+output_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+output_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+pkt_free_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+pkt_free_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+dummy1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+dummy2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc1_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature3_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static uint16_t
+arc2_feature3_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs);
+static int
+common_node_init(const struct rte_graph *graph, struct rte_node *node);
+
+typedef struct test_node_priv {
+ /* index from 0 - MAX_NODES -1 */
+ uint8_t node_index;
+
+ /* feature */
+ rte_graph_feature_t feature;
+
+ /* rte_graph node id */
+ uint32_t node_id;
+
+ rte_graph_feature_arc_t arc;
+} test_node_priv_t;
+
+typedef struct {
+ rte_graph_feature_t feature;
+ uint16_t egress_interface;
+ uint16_t ingress_interface;
+} graph_dynfield_t;
+
+static int graph_dynfield_offset = -1;
+static rte_graph_feature_arc_t arcs[RTE_GRAPH_FEATURE_ARC_MAX + 128];
+static struct rte_mbuf mbuf[MAX_NODES + 1][MBUFF_SIZE];
+static void *mbuf_p[MAX_NODES + 1][MBUFF_SIZE];
+static rte_graph_t graph_id = RTE_GRAPH_ID_INVALID;
+
+const char *node_patterns_feature_arc[] = {
+ "test_node_arc*"
+};
+
+static int32_t
+compute_unique_user_data(const char *parent, const char *child, uint32_t interface_index)
+{
+ uint32_t user_data = interface_index;
+
+ RTE_SET_USED(parent);
+#define R(idx, node, node_cookie) { \
+ if (!strcmp(child, node)) { \
+ user_data += node_cookie; \
+ } \
+ }
+
+ FOREACH_TEST_NODE_ARC
+#undef R
+
+ return user_data;
+}
+
+static int
+get_edge(struct rte_node_register *parent_node,
+ struct rte_node_register *child_node, rte_edge_t *_edge)
+{
+ char **next_edges = NULL;
+ uint32_t count, i;
+
+ count = rte_node_edge_get(parent_node->id, NULL);
+
+ if (!count)
+ return -1;
+
+ next_edges = malloc(count);
+
+ if (!next_edges)
+ return -1;
+
+ count = rte_node_edge_get(parent_node->id, next_edges);
+ for (i = 0; i < count; i++) {
+ if (strstr(child_node->name, next_edges[i])) {
+ if (_edge)
+ *_edge = (rte_edge_t)i;
+
+ free(next_edges);
+ return 0;
+ }
+ }
+ free(next_edges);
+
+ return -1;
+}
+
+int
+common_node_init(const struct rte_graph *graph, struct rte_node *node)
+{
+ test_node_priv_t *priv = (test_node_priv_t *)node->ctx;
+
+ RTE_SET_USED(graph);
+
+ priv->node_id = node->id;
+ priv->feature = RTE_GRAPH_FEATURE_INVALID;
+ priv->arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+
+#define R(idx, _name, user_data) { \
+ if (!strcmp(node->name, _name)) { \
+ priv->node_index = idx; \
+ } \
+ }
+ FOREACH_TEST_NODE_ARC
+#undef R
+
+ return 0;
+}
+
+uint16_t
+source1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register source1 = {
+ .name = SOURCE1,
+ .process = source1_fn,
+ .flags = RTE_NODE_SOURCE_F,
+ .nb_edges = 3,
+ .init = common_node_init,
+ .next_nodes = {INPUT_STATIC, DUMMY1_STATIC, DUMMY2_STATIC},
+};
+RTE_NODE_REGISTER(source1);
+
+uint16_t
+input_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+input_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register input = {
+ .name = INPUT_STATIC,
+ .process = input_fn,
+ .feat_arc_proc = input_fa_fn,
+ .nb_edges = 2,
+ .init = common_node_init,
+ .next_nodes = {OUTPUT_STATIC, DUMMY1_STATIC},
+};
+RTE_NODE_REGISTER(input);
+
+uint16_t
+output_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+output_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register output = {
+ .name = OUTPUT_STATIC,
+ .process = output_fn,
+ .feat_arc_proc = output_fa_fn,
+ .nb_edges = 3,
+ .init = common_node_init,
+ .next_nodes = {DUMMY1_STATIC, PKT_FREE_STATIC, DUMMY2_STATIC},
+};
+RTE_NODE_REGISTER(output);
+
+uint16_t
+pkt_free_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+uint16_t
+pkt_free_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register pkt_free = {
+ .name = PKT_FREE_STATIC,
+ .process = pkt_free_fn,
+ .feat_arc_proc = pkt_free_fa_fn,
+ .nb_edges = 1,
+ .init = common_node_init,
+ .next_nodes = {DUMMY1_STATIC},
+};
+RTE_NODE_REGISTER(pkt_free);
+
+uint16_t
+dummy1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register dummy1 = {
+ .name = DUMMY1_STATIC,
+ .process = dummy1_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(dummy1);
+
+uint16_t
+dummy2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+ return 0;
+}
+
+static struct rte_node_register dummy2 = {
+ .name = DUMMY2_STATIC,
+ .process = dummy2_fn,
+ .nb_edges = 5,
+ .init = common_node_init,
+ .next_nodes = { ARC1_FEATURE1, ARC1_FEATURE2, ARC2_FEATURE1,
+ ARC2_FEATURE2, ARC2_FEATURE3},
+};
+RTE_NODE_REGISTER(dummy2);
+
+uint16_t
+arc1_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc1_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc1_feature1 = {
+ .name = ARC1_FEATURE1,
+ .process = arc1_feature1_fn,
+ .feat_arc_proc = arc1_feature1_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc1_feature1);
+
+uint16_t
+arc1_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc1_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc1_feature2 = {
+ .name = ARC1_FEATURE2,
+ .process = arc1_feature2_fn,
+ .feat_arc_proc = arc1_feature2_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc1_feature2);
+
+uint16_t
+arc2_feature1_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature1_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature1 = {
+ .name = ARC2_FEATURE1,
+ .process = arc2_feature1_fn,
+ .feat_arc_proc = arc2_feature1_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature1);
+
+uint16_t
+arc2_feature2_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature2_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature2 = {
+ .name = ARC2_FEATURE2,
+ .process = arc2_feature2_fn,
+ .feat_arc_proc = arc2_feature2_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature2);
+
+uint16_t
+arc2_feature3_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+uint16_t
+arc2_feature3_fa_fn(struct rte_graph *graph, struct rte_node *node,
+ void **objs, uint16_t nb_objs)
+{
+ RTE_SET_USED(graph);
+ RTE_SET_USED(node);
+ RTE_SET_USED(objs);
+ RTE_SET_USED(nb_objs);
+
+ return 0;
+}
+
+static struct rte_node_register arc2_feature3 = {
+ .name = ARC2_FEATURE3,
+ .process = arc2_feature3_fn,
+ .feat_arc_proc = arc2_feature3_fa_fn,
+ .nb_edges = 0,
+ .init = common_node_init,
+};
+RTE_NODE_REGISTER(arc2_feature3);
+
+static int
+create_graph(void)
+{
+ struct rte_graph_param gconf = {
+ .socket_id = SOCKET_ID_ANY,
+ .nb_node_patterns = 1,
+ .node_patterns = node_patterns_feature_arc,
+ };
+
+ graph_id = rte_graph_create("worker0", &gconf);
+ if (graph_id == RTE_GRAPH_ID_INVALID) {
+ printf("Graph creation failed with error = %d\n", rte_errno);
+ return TEST_FAILED;
+ }
+
+ return TEST_SUCCESS;
+}
+
+static int
+__test_create_feature_arc(rte_graph_feature_arc_t *arcs, int max_arcs)
+{
+ rte_graph_feature_arc_t arc;
+ const char *sample_arc_name = "sample_arc";
+ char arc_name[256];
+ int n_arcs;
+
+ /* Create max number of feature arcs first */
+ for (n_arcs = 0; n_arcs < max_arcs; n_arcs++) {
+ snprintf(arc_name, sizeof(arc_name), "%s-%u", sample_arc_name, n_arcs);
+ if (rte_graph_feature_arc_create(arc_name, MAX_FEATURES,
+ MAX_INDEXES, &dummy1, &arcs[n_arcs])) {
+ printf("Feature arc creation failed for %u\n", n_arcs);
+ return TEST_FAILED;
+ }
+ }
+ /* Verify feature arc created more than max_arcs must fail */
+ if (!rte_graph_feature_arc_create("negative_test_create_arc", MAX_FEATURES,
+ MAX_INDEXES, &dummy2, &arc)) {
+ printf("Feature arc creation success for more than max configured: %u\n", n_arcs);
+ return TEST_FAILED;
+ }
+ /* Make sure lookup passes for all feature arcs */
+ for (n_arcs = 0; n_arcs < max_arcs; n_arcs++) {
+ snprintf(arc_name, sizeof(arc_name), "%s-%u", sample_arc_name, n_arcs);
+ arc = RTE_GRAPH_FEATURE_ARC_INITIALIZER;
+ if (!rte_graph_feature_arc_lookup_by_name(arc_name, &arc)) {
+ if (arc != arcs[n_arcs]) {
+ printf("%s: Feature arc lookup mismatch for arc [%p, exp: %p]\n",
+ arc_name, (void *)arc, (void *)arcs[n_arcs]);
+ return TEST_FAILED;
+ }
+ } else {
+ printf("Feature arc lookup %s failed after creation\n", arc_name);
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_create(void)
+{
+ int ret = 0, i;
+
+ /* Create arcs with RTE_GRAPH_FEATURE_ARC_MAX */
+ ret = __test_create_feature_arc(arcs, RTE_GRAPH_FEATURE_ARC_MAX);
+ if (ret) {
+ printf("Feature arc creation test failed for RTE_GRAPH_FEATURE_ARC_MAX arcs\n");
+ return TEST_FAILED;
+ }
+ /* destroy all arcs via cleanup API*/
+ ret = rte_graph_feature_arc_cleanup();
+ if (ret) {
+ printf("Feature arc cleanup failed\n");
+ return TEST_FAILED;
+ }
+
+#define NUM_FEAT_ARCS 128
+ /* create 128 dummy feature arcs */
+ ret = rte_graph_feature_arc_init(NUM_FEAT_ARCS);
+ if (ret) {
+ printf("Feature arc init failed for NUM_FEAT_ARCS");
+ return TEST_FAILED;
+ }
+ ret = __test_create_feature_arc(arcs, NUM_FEAT_ARCS);
+ if (ret) {
+ printf("Feature arc creation test failed for NUM_FEAT_ARCS\n");
+ return TEST_FAILED;
+ }
+ /* destroy all of them*/
+ for (i = 0; i < NUM_FEAT_ARCS; i++) {
+ if (rte_graph_feature_arc_destroy(arcs[i])) {
+ printf("Feature arc destroy failed for %u\n", i);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_cleanup();
+
+ /* Create two arcs as per test plan */
+ /* First arc start/source node is node: SOURCE1 */
+ if (rte_graph_feature_arc_create(TEST_ARC1_NAME, MAX_FEATURES,
+ MAX_INDEXES, &source1, &arcs[0])) {
+ printf("Feature arc creation failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ /* Duplicate name should fail */
+ if (!rte_graph_feature_arc_create(TEST_ARC1_NAME, MAX_FEATURES,
+ MAX_INDEXES, &source1, &arcs[1])) {
+ printf("Duplicate feature arc %s creation is not caught\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /* Second arc start/source node is node: OUTPUT_STATIC */
+ if (rte_graph_feature_arc_create(TEST_ARC2_NAME, MAX_FEATURES,
+ MAX_INDEXES, &output, &arcs[1])) {
+ printf("Feature arc creation failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_features_add(void)
+{
+ rte_graph_feature_t temp;
+
+ /* First feature to SOURCE1 start node -> ARC1_FEATURE1 */
+ if (rte_graph_feature_add(arcs[0], &arc1_feature1, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Second feature to SOURCE1 -> ARC1_FEATURE2 */
+ if (rte_graph_feature_add(arcs[0], &arc1_feature2, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ /* adding statically connected INPUT_STATIC as a last feature */
+ if (rte_graph_feature_add(arcs[0], &input, ARC1_FEATURE2, NULL)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC1_NAME, INPUT_STATIC, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ /* First feature to OUTPUT_STATIC start node -> ARC2_FEATURE3 */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature3, NULL, NULL)) {
+ printf("%s: Feature add failed for adding feature %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Second feature to OUTPUT_STATIC -> ARC2_FEATURE1 and before feature to
+ * ARC2_FEATURE3
+ */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature1, NULL, ARC2_FEATURE3)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, ARC2_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Add PKT_FREE node as last feature, next to arc2_feature3 */
+ if (rte_graph_feature_add(arcs[1], &pkt_free, ARC2_FEATURE3, NULL)) {
+ printf("%s: Feature add failed for adding feature %s after %s\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Adding feature ARC2_FEATURE2 between ARC2_FEATURE1 and ARC2_FEATURE3. */
+ if (rte_graph_feature_add(arcs[1], &arc2_feature2, ARC2_FEATURE1, ARC2_FEATURE3)) {
+ printf("%s: Feature add failed for adding feature %s between [%s - %s]\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, ARC2_FEATURE1, ARC2_FEATURE3);
+ return TEST_FAILED;
+ }
+ /* Now check feature sequencing is correct for both ARCS */
+
+ /* arc1_featur1 must be first feature to arcs[0] */
+ if (!strstr(ARC1_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ rte_graph_feature_cast(0)))) {
+ printf("%s: %s is not the first feature instead %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[0], rte_graph_feature_cast(0)));
+ return TEST_FAILED;
+ }
+
+ /* arc1_feature2 must be second feature to arcs[0] */
+ if (!strstr(ARC1_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the second feature instead %s\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[0], rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* Make sure INPUT_STATIC is the last feature in arcs[0] */
+ temp = rte_graph_feature_arc_num_features(arcs[0]);
+ if (!strstr(INPUT_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ temp - rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the last feature instead %s\n",
+ TEST_ARC1_NAME, INPUT_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[0],
+ temp - rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_featur1 must be first feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(0)))) {
+ printf("%s: %s is not the first feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(0)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_feature2 must be second feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the second feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ /* arc2_feature3 must be third feature to arcs[1] */
+ if (!strstr(ARC2_FEATURE3,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ rte_graph_feature_cast(2)))) {
+ printf("%s: %s is not the third feature instead %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3,
+ rte_graph_feature_arc_feature_to_name(arcs[1], rte_graph_feature_cast(2)));
+ return TEST_FAILED;
+ }
+
+ /* Make sure PKT_FREE is the last feature in arcs[1] */
+ temp = rte_graph_feature_arc_num_features(arcs[1]);
+ if (!strstr(PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ temp - rte_graph_feature_cast(1)))) {
+ printf("%s: %s is not the last feature instead %s\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1],
+ temp - rte_graph_feature_cast(1)));
+ return TEST_FAILED;
+ }
+
+ if (get_edge(&arc2_feature1, &pkt_free, NULL)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1, PKT_FREE_STATIC);
+ return TEST_FAILED;
+ }
+
+ return create_graph();
+}
+
+static int
+test_graph_feature_arc_first_feature_enable(void)
+{
+ uint32_t n_indexes, n_features, count = 0;
+ rte_graph_feature_rt_list_t feature_list, temp = 0;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ char *feature_name = NULL;
+ int32_t user_data;
+ rte_edge_t edge = ~0;
+
+ arc = rte_graph_feature_arc_get(arcs[0]);
+
+ if (rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should not have any feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_num_enabled_features(arcs[0])) {
+ printf("%s: Feature arc should not have any_feature() enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /*
+ * On interface 0, enable feature 0,
+ * On interface 1, enable feature 1 and so on so forth
+ *
+ * later verify first feature on every interface index is unique
+ * and check [rte_edge, user_data] retrieved via fast path APIs
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ n_features = n_indexes % 3 /* 3 features added to arc1 */;
+ feature_name = rte_graph_feature_arc_feature_to_name(arcs[0], n_features);
+ user_data = compute_unique_user_data(arc->start_node->name, feature_name,
+ n_indexes);
+ if (rte_graph_feature_validate(arcs[0], n_indexes, feature_name, 1, true)) {
+ printf("%s: Feature validate failed for %s on index %u\n",
+ TEST_ARC1_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* negative test case. enable feature on invalid index */
+ if (!n_indexes && !rte_graph_feature_enable(arcs[0], MAX_INDEXES, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature %s should not be enabled on invalid index\n",
+ TEST_ARC1_NAME, feature_name);
+ return TEST_FAILED;
+ }
+ if (rte_graph_feature_enable(arcs[0], n_indexes, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC1_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* has any feature should be valid */
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ if (temp == feature_list) {
+ printf("%s: Activer feature list not switched from %u -> %u\n",
+ TEST_ARC1_NAME, temp, feature_list);
+ return TEST_FAILED;
+ }
+ temp = feature_list;
+ if ((count + 1) != rte_graph_feature_arc_num_enabled_features(arcs[0])) {
+ printf("%s: Number of enabled mismatches [found: %u, exp: %u]\n",
+ TEST_ARC1_NAME,
+ rte_graph_feature_arc_num_enabled_features(arcs[0]),
+ count + 1);
+ return TEST_FAILED;
+ }
+ count++;
+ }
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+ /* Negative test case */
+ user_data = compute_unique_user_data(arc->start_node->name, ARC2_FEATURE1, 1);
+ if (!rte_graph_feature_enable(arcs[0], 1 /* index */, ARC2_FEATURE1, user_data)) {
+ printf("%s: Invalid feature %s is enabled on index 1\n",
+ TEST_ARC1_NAME, ARC2_FEATURE1);
+ return TEST_FAILED;
+ }
+ /* Duplicate enable */
+ if (!rte_graph_feature_enable(arcs[0], 1 /* index */, ARC1_FEATURE2, user_data)) {
+ printf("%s: Duplicate feature %s shouldn't be enabled again on index 1\n",
+ TEST_ARC1_NAME, ARC1_FEATURE2);
+ return TEST_FAILED;
+ }
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: %u\n",
+ TEST_ARC1_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ /* Get first feature data and ensure edge and user_data are correct */
+ fdata = rte_graph_feature_data_get(arc, rte_graph_feature_get(arc, feature),
+ n_indexes);
+ parent = arc->start_node;
+ if (0 == (n_indexes % 3))
+ child = &arc1_feature1;
+ else if (1 == (n_indexes % 3))
+ child = &arc1_feature2;
+ else
+ child = &input;
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC1_NAME, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for first feature on index %u [%u, exp: %u]\n",
+ TEST_ARC1_NAME, n_indexes, fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != compute_unique_user_data(parent->name, child->name,
+ n_indexes)) {
+ printf("%s: First feature user data mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC1_NAME, n_indexes, fdata->user_data,
+ compute_unique_user_data(parent->name, child->name, n_indexes));
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+verify_feature_sequencing(struct rte_graph_feature_arc *arc)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+ rte_edge_t edge = ~0;
+ int32_t user_data;
+
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: feature_list can't be obtained\n",
+ arc->feature_arc_name);
+ return TEST_FAILED;
+ }
+ /* Verify next features on interface 0 and interface 1*/
+ for (n_indexes = 0; n_indexes < 2; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: 0\n",
+ arc->feature_arc_name);
+ return TEST_FAILED;
+ }
+ parent = arc->start_node;
+ child = rte_graph_feature_arc_feature_to_node(arcs[1], feature);
+ /* until fast path API reaches last feature i.e pkt_free */
+ while (child != &pkt_free) {
+ fdata = rte_graph_feature_data_get(arc,
+ rte_graph_feature_get(arc, feature),
+ n_indexes);
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ arc->feature_arc_name, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ user_data = compute_unique_user_data(parent->name, child->name, n_indexes);
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for %s->%s on index %u [%u, exp: %u]\n",
+ arc->feature_arc_name, parent->name, child->name, n_indexes,
+ fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != user_data) {
+ printf("%s: Udata mismatch for %s->%s on index %u [%u, exp: %u]\n",
+ arc->feature_arc_name, parent->name, child->name, n_indexes,
+ fdata->user_data, user_data);
+ return TEST_FAILED;
+ }
+
+ feature = fdata->next_enabled_feature;
+
+ parent = child;
+ child = rte_graph_feature_arc_feature_to_node(arcs[1],
+ fdata->next_enabled_feature);
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_next_feature_enable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_node_register *parent, *child;
+ rte_graph_feature_data_t *fdata = NULL;
+ struct rte_graph_feature_arc *arc;
+ uint32_t n_indexes, n_features;
+ rte_graph_feature_t feature;
+ char *feature_name = NULL;
+ rte_edge_t edge = ~0;
+ int32_t user_data;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ if (rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should not have any feature enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_num_enabled_features(arcs[1])) {
+ printf("%s: Feature arc should not have any_feature() enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ /*
+ * On interface 0, enable feature 2, skip feature 1 for later
+ * On interface 1, enable feature 3
+ * On interface 2, enable pkt_free feature
+ * On interface 3, continue as interface 0
+ *
+ * later enable next feature sequence for interface 0 from feature2 -> pkt_free
+ * later enable next feature sequence for interface 1 from feature3 -> pkt_free
+ *
+ * also later enable feature-1 and see first feature changes for all indexes
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ n_features = (n_indexes % 3) + 1; /* feature2 to pkt_free are 3 features */
+ feature_name = rte_graph_feature_arc_feature_to_name(arcs[1], n_features);
+ user_data = compute_unique_user_data(arc->start_node->name, feature_name,
+ n_indexes);
+ if (rte_graph_feature_enable(arcs[1], n_indexes, feature_name,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC2_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ /* has any feature should be valid */
+ if (!rte_graph_feature_arc_has_any_feature(arc, &feature_list)) {
+ printf("%s: Feature arc should have any_feature enabled by now\n",
+ TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ }
+ /* Retrieve latest feature_list */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ /* verify first feature */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: No first feature enabled on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ /* Get first feature data and ensure edge and user_data are correct */
+ fdata = rte_graph_feature_data_get(arc, rte_graph_feature_get(arc, feature),
+ n_indexes);
+ parent = arc->start_node;
+ if (0 == (n_indexes % 3))
+ child = &arc2_feature2;
+ else if (1 == (n_indexes % 3))
+ child = &arc2_feature3;
+ else
+ child = &pkt_free;
+
+ if (get_edge(parent, child, &edge)) {
+ printf("%s: Edge not found between %s and %s\n",
+ TEST_ARC2_NAME, parent->name, child->name);
+ return TEST_FAILED;
+ }
+ if (fdata->next_edge != edge) {
+ printf("%s: Edge mismatch for first feature on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, fdata->next_edge, edge);
+ return TEST_FAILED;
+ }
+ if (fdata->user_data != compute_unique_user_data(parent->name, child->name,
+ n_indexes)) {
+ printf("%s: First feature user data mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, fdata->user_data,
+ compute_unique_user_data(parent->name, child->name, n_indexes));
+ return TEST_FAILED;
+ }
+ }
+ /* add next_features now
+ * On interface 0, enable feature-3 and pkt_free
+ * On interface 1, enable pkt_free
+ * Skip interface 2
+ * On interface 3, same as interface 0
+ * On interface 4, same as interface 1
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (0 == (n_indexes % 3)) {
+ if (rte_graph_feature_enable(arcs[1], n_indexes, ARC2_FEATURE3,
+ compute_unique_user_data(ARC2_FEATURE2,
+ ARC2_FEATURE3,
+ n_indexes))) {
+ printf("%s: Feature enable failed for %s -> (%s) on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, ARC2_FEATURE3, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ /* pkt_free on interface-0, 1, 3, 4 and so on */
+ if ((0 == (n_indexes % 3)) || (1 == (n_indexes % 3))) {
+ if (rte_graph_feature_enable(arcs[1], n_indexes, PKT_FREE_STATIC,
+ compute_unique_user_data(ARC2_FEATURE3,
+ PKT_FREE_STATIC,
+ n_indexes))) {
+ printf("%s: Feature enable failed %s -> (%s) on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, PKT_FREE_STATIC, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ }
+
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ /* Enable feature-1 on all interfaces and check first feature changes */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ user_data = compute_unique_user_data(arc->start_node->name, ARC2_FEATURE1,
+ n_indexes);
+ if (rte_graph_feature_enable(arcs[1], n_indexes, ARC2_FEATURE1,
+ (int32_t)user_data)) {
+ printf("%s: Feature enable failed for %s on index %u\n",
+ TEST_ARC2_NAME, feature_name, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: None first feature enabled on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (feature != rte_graph_feature_cast(0)) {
+ printf("%s: First feature mismatch on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, feature, rte_graph_feature_cast(0));
+ return TEST_FAILED;
+ }
+ }
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_first_feature_disable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ /* Disable feature-1 on all interfaces and check first feature changes */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE1)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE1, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: First feature get failed on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (feature == rte_graph_feature_cast(0)) {
+ printf("%s: First feature not disabled on index %u [%u, exp: %u]\n",
+ TEST_ARC2_NAME, n_indexes, feature, rte_graph_feature_cast(1));
+ return TEST_FAILED;
+ }
+ if (!strncmp(ARC2_FEATURE1,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ strlen(ARC2_FEATURE1))) {
+ printf("%s: First feature mismatch on index %u [%s, exp: %s]\n",
+ TEST_ARC2_NAME, n_indexes,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ ARC2_FEATURE2);
+ return TEST_FAILED;
+ }
+ }
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_next_feature_disable(void)
+{
+ rte_graph_feature_rt_list_t feature_list;
+ struct rte_graph_feature_arc *arc;
+ rte_graph_feature_t feature;
+ uint32_t n_indexes;
+
+ arc = rte_graph_feature_arc_get(arcs[1]);
+
+ /*
+ * On interface 0, disable feature 2, keep feature3 and pkt_free enabled
+ * On interface 1, skip interface 1 where feature3 and pkt_free are enabled
+ * skip interface 2 as only pkt_free is enabled
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!(n_indexes % 3)) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE2)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE2, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+
+ if (verify_feature_sequencing(arc) == TEST_FAILED)
+ return TEST_FAILED;
+ }
+
+ /**
+ * Disable feature 3 on all interface 0 and 1 and check first feature
+ * is pkt_free on all indexes
+ */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if ((0 == (n_indexes % 3)) || (1 == (n_indexes % 3))) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, ARC2_FEATURE3)) {
+ printf("%s: Feature disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, ARC2_FEATURE3, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ }
+ /* Make sure pkt_free is first feature for all indexes */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (!rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: First feature get failed on index: %u\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ if (strncmp(PKT_FREE_STATIC,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ strlen(PKT_FREE_STATIC))) {
+ printf("%s: %s is not first feature found on index %u [%s, exp: %s]\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, n_indexes,
+ rte_graph_feature_arc_feature_to_name(arcs[1], feature),
+ PKT_FREE_STATIC);
+ return TEST_FAILED;
+ }
+ }
+
+ /* Disable PKT_FREE_STATIC from all indexes with no feature enabled on any interface */
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_disable(arcs[1], n_indexes, PKT_FREE_STATIC)) {
+ printf("%s: Feat disable failed for %s on index %u\n",
+ TEST_ARC2_NAME, PKT_FREE_STATIC, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ /* Make sure no feature is enabled now on any interface */
+ rte_graph_feature_arc_has_any_feature(arc, &feature_list);
+ for (n_indexes = 0; n_indexes < MAX_INDEXES; n_indexes++) {
+ if (rte_graph_feature_arc_first_feature_get(arc, feature_list, n_indexes,
+ &feature)) {
+ printf("%s: Index: %u should not have first feature enabled\n",
+ TEST_ARC2_NAME, n_indexes);
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_graph_feature_arc_destroy(void)
+{
+ rte_graph_feature_arc_t arc;
+
+ if (rte_graph_feature_arc_lookup_by_name(TEST_ARC1_NAME, &arc)) {
+ printf("Feature arc lookup failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (arc != arcs[0]) {
+ printf("Feature arc lookup mismatch for %s [%p, exp: %p]\n",
+ TEST_ARC1_NAME, (void *)arc, (void *)arcs[0]);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_destroy(arc)) {
+ printf("Feature arc destroy failed for %s\n", TEST_ARC1_NAME);
+ return TEST_FAILED;
+ }
+
+ if (rte_graph_feature_arc_lookup_by_name(TEST_ARC2_NAME, &arc)) {
+ printf("Feature arc lookup success after destroy for %s\n", TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+
+ if (arc != arcs[1]) {
+ printf("Feature arc lookup mismatch for %s [%p, exp: %p]\n",
+ TEST_ARC2_NAME, (void *)arc, (void *)arcs[1]);
+ return TEST_FAILED;
+ }
+ if (rte_graph_feature_arc_destroy(arc)) {
+ printf("Feature arc destroy failed for %s\n", TEST_ARC2_NAME);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+graph_feature_arc_setup(void)
+{
+ unsigned long i, j;
+
+ static const struct rte_mbuf_dynfield graph_dynfield_desc = {
+ .name = "test_graph_dynfield",
+ .size = sizeof(graph_dynfield_t),
+ .align = alignof(graph_dynfield_t),
+ };
+
+ graph_dynfield_offset =
+ rte_mbuf_dynfield_register(&graph_dynfield_desc);
+ if (graph_dynfield_offset < 0) {
+ printf("Cannot register mbuf field\n");
+ return TEST_FAILED;
+ }
+ RTE_SET_USED(graph_dynfield_offset);
+
+ for (i = 0; i <= MAX_NODES; i++) {
+ for (j = 0; j < MBUFF_SIZE; j++)
+ mbuf_p[i][j] = &mbuf[i][j];
+ }
+
+ return TEST_SUCCESS;
+
+}
+
+static void
+graph_feature_arc_teardown(void)
+{
+ if (graph_id != RTE_GRAPH_ID_INVALID)
+ rte_graph_destroy(graph_id);
+
+ rte_graph_feature_arc_cleanup();
+}
+
+static struct unit_test_suite graph_feature_arc_testsuite = {
+ .suite_name = "Graph Feature arc library test suite",
+ .setup = graph_feature_arc_setup,
+ .teardown = graph_feature_arc_teardown,
+ .unit_test_cases = {
+ TEST_CASE(test_graph_feature_arc_create),
+ TEST_CASE(test_graph_feature_arc_features_add),
+ TEST_CASE(test_graph_feature_arc_first_feature_enable),
+ TEST_CASE(test_graph_feature_arc_next_feature_enable),
+ TEST_CASE(test_graph_feature_arc_first_feature_disable),
+ TEST_CASE(test_graph_feature_arc_next_feature_disable),
+ TEST_CASE(test_graph_feature_arc_destroy),
+ TEST_CASES_END(), /**< NULL terminate unit test array */
+ },
+};
+
+static int
+graph_feature_arc_autotest_fn(void)
+{
+ return unit_test_suite_runner(&graph_feature_arc_testsuite);
+}
+
+REGISTER_FAST_TEST(graph_feature_arc_autotest, true, true, graph_feature_arc_autotest_fn);
+#endif /* !RTE_EXEC_ENV_WINDOWS */
--
2.43.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v3 5/5] docs: add programming guide for feature arc
2024-10-09 13:29 ` [PATCH v3 0/5] add feature arc in rte_graph Nitin Saxena
` (3 preceding siblings ...)
2024-10-09 13:30 ` [PATCH v3 4/5] test/graph_feature_arc: add functional tests Nitin Saxena
@ 2024-10-09 13:30 ` Nitin Saxena
2024-10-09 14:21 ` [PATCH v3 0/5] add feature arc in rte_graph Christophe Fontaine
` (2 subsequent siblings)
7 siblings, 0 replies; 56+ messages in thread
From: Nitin Saxena @ 2024-10-09 13:30 UTC (permalink / raw)
To: Jerin Jacob, Kiran Kumar K, Nithin Dabilpuram, Zhirun Yan,
Robin Jarry, Christophe Fontaine
Cc: dev, Nitin Saxena
Updated graph library guide with feature arc
Signed-off-by: Nitin Saxena <nsaxena@marvell.com>
---
doc/guides/prog_guide/graph_lib.rst | 288 ++++++++++++++++++++
doc/guides/prog_guide/img/feature_arc-1.png | Bin 0 -> 61532 bytes
doc/guides/prog_guide/img/feature_arc-2.png | Bin 0 -> 155806 bytes
doc/guides/prog_guide/img/feature_arc-3.png | Bin 0 -> 143697 bytes
4 files changed, 288 insertions(+)
create mode 100644 doc/guides/prog_guide/img/feature_arc-1.png
create mode 100644 doc/guides/prog_guide/img/feature_arc-2.png
create mode 100644 doc/guides/prog_guide/img/feature_arc-3.png
diff --git a/doc/guides/prog_guide/graph_lib.rst b/doc/guides/prog_guide/graph_lib.rst
index ad09bdfe26..fc231ace99 100644
--- a/doc/guides/prog_guide/graph_lib.rst
+++ b/doc/guides/prog_guide/graph_lib.rst
@@ -547,3 +547,291 @@ on success packet is enqueued to ``udp4_input`` node.
Hash lookup is performed in ``udp4_input`` node with registered destination port
and destination port in UDP packet , on success packet is handed to ``udp_user_node``.
+
+Feature Arc
+-----------
+`Feature arc` represents an ordered list of `protocols/features` at a given
+networking layer. It is a high level abstraction to connect various `feature`
+nodes in `rte_graph` instance and allows seamless packets steering based on the
+sequence of enabled features at runtime on each interface.
+
+`Features` (or feature nodes) are nodes which handle partial or complete
+protocol processing in a given direction. For instance, `ipv4-rewrite` and
+`IPv4 IPsec encryption` are outbound features while `ipv4-lookup` and `IPv4
+IPsec decryption` are inbound features. Further, `ipv4-rewrite` and `IPv4
+IPsec encryption` can collectively represent a `feature arc` towards egress
+direction with ordering constraints that `IPv4 IPsec encryption` must be
+performed before `ipv4-rewrite`. Similarly, `IPv4 IPsec decryption` and
+`ipv4-lookup` can represent a `feature arc` in an ingress direction. Both of
+these `feature arc` can co-exist at an IPv4 layer in egress and ingress
+direction respectively.
+
+A `feature` can be represented by a single node or collection of multiple nodes
+performing feature processing collectively.
+
+.. figure:: img/feature_arc-1.png
+ :alt: feature-arc-1
+ :width: 350px
+ :align: center
+
+ Feature Arc overview
+
+Each `feature arc` is associated with a `Start` node from which all features in
+a feature arc are connected. A `start` node itself is not a `feature` node but
+it is where `first enabled feature` is checked in fast path. In above figure,
+`Node-A` represents a `start node`. There may be a `Sink` node as well which
+is child node for every feature in an arc. 'Sink` node is responsible of
+consuming those packets which are not consumed by intermediate enabled features
+between `start` and `sink` node. `Sink` node, if present, is the last enabled
+feature in a feature arc. A `feature` node statically connected to `start` node
+must also be added via feature arc API, `rte_graph_feature_add()``. Here `Node-B`
+acts as a `sink` node which is statically linked to `Node A`. `Feature` nodes
+are connected via `rte_graph_feature_add()` which takes care of connecting
+all `feature` nodes with each other and start node.
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 8
+ :caption: Node-B statically linked to Node-A
+
+ static struct rte_node_register node_A_node = {
+ .process = node_A_process_func,
+ ...
+ ...
+ .name = "Node-A",
+ .next_nodes =
+ {
+ [0] = "Node-B",
+ },
+ .nb_edges = 1,
+ };
+
+When multiple features are enabled on an interface, it may be required to steer
+packets across `features` in a given order. For instance, if `Feature 1` and
+`Feature 2` both are enabled on an interface ``X``, it may be required to send
+packets to `Feature-1` before `Feature-2`. Such ordering constraints can be
+easily expressed with `feature arc`. In this case, `Feature 1` is called as
+``First Feature`` and `Feature 2` is called as ``Next Feature`` to `Feature 1`.
+
+.. figure:: img/feature_arc-2.png
+ :alt: feature-arc-2
+ :width: 600px
+ :align: center
+
+ First and Next features and their ordering
+
+In similar manner, even application specific ``custom features`` can be hooked
+to standard nodes. It is to be noted that this `custom feature` hooking to
+`feature arc` aware node does not require any code changes.
+
+It may be obvious by now that `features` enabled on one interface does not
+affect packets on other interfaces. In above example, if no feature is
+enabled on an interface ``X``, packets destined to interface ``X`` would be
+directly sent to `Node-B` from `Node-A`.
+
+.. figure:: img/feature_arc-3.png
+ :alt: feature-arc-3
+ :width: 550px
+ :align: center
+
+ Feature-2 consumed/non-consumed packet path
+
+When a `Feature-X` node receives packets via feature arc, it may decide whether
+to ``consume packet`` or send to `next enabled feature`. A node can consume
+packet by freeing it, sending it on wire or enqueuing it to hardware queue. If a
+packet is not consumed by a `Feature-X` node, it may send to `next enabled
+feature` on an interface. In above figure, `Feature-2` nodes are represented to
+consume packets. Classic example for a node performing consume and non-consume
+operation on packets would be IPsec policy node where all packets with
+``protect`` actions are consumed while remaining packets with ``bypass``
+actions are sent to next enabled feature.
+
+In fast path feature node may require to lookup local data structures for each
+interface. For example, retrieving policy database per interface for IPsec
+processing. ``rte_graph_feature_enable`` API allows to set application
+specific cookie per feature per interface. `Feature data` object maintains this
+cookie in fast path for each interface.
+
+`Feature arc design` allows to enable subsequent features in a control plane
+without stopping workers which are accessing feature arc's fast path APIs in
+``rte_graph_walk()`` context. However for disabling features require RCU like
+scheme for synchronization.
+
+Programming model
+~~~~~~~~~~~~~~~~~
+Feature Arc Objects
+^^^^^^^^^^^^^^^^^^^
+Control plane and fast path APIs deals with following objects:
+
+Feature arc
+***********
+``rte_graph_feature_arc_t`` is a handle to feature arc which is created via
+``rte_graph_feature_arc_create()``. It is a `uint64_t` size object which can be
+saved in feature node's context. This object can be translated to fast path
+feature arc object ``struct rte_graph_feature_arc`` which is an input
+argument to all fast path APIs. Control plane APIs majorly takes
+`rte_graph_feature_arc_t` object as an input.
+
+Feature List
+************
+Each feature arc holds two feature lists: `active` and `passive`. While worker
+cores uses `active` list, control plane APIs uses `passive` list for
+enabling/disabling a feature on any interface with in a arc. After successful
+feature enable/disable, ``rte_graph_feature_enable()``/
+``rte_graph_feature_disable()`` atomically switches passive list to active list
+and vice-versa. Most of the fast path APIs takes active list as an argument
+(``rte_graph_feature_rt_list_t``), which feature node can obtain in start of
+it's `process_func()` via ``rte_graph_feature_arc_has_any_feature()`` (in `start`
+node) or ``rte_graph_feature_arc_has_feature()`` (in next feature nodes).
+
+Each feature list holds RTE_GRAPH_MAX_FEATURES number of features and
+associated feature data for every interface index
+
+Feature
+********
+Feature is a data structure which holds `feature data` object for every
+interface. It is represented via ``rte_graph_feature_t`` which is a `uint8_t`
+size object. Fast path internal structure ``struct rte_graph_feature`` can be
+obtained from ``rte_graph_feature_t`` via ``rte_graph_feature_get()`` API.
+
+In `start` node `rte_graph_feature_arc_first_feature_get()` can be used to get
+first enabled `rte_graph_feature_t` object for an interface. `rte_edge` from
+`start` node to first enabled feature is provided by
+``rte_graph_feature_arc_feature_set()`` API.
+
+In `feature nodes`, next enabled feature is obtained by providing current feature
+as an input to ``rte_graph_feature_arc_next_feature_get()`` API.
+
+Feature data
+************
+Feature data object is maintained per feature per interface which holds
+following information in fast path
+
+- ``rte_edge_t`` to send packet to next enabled feature
+- ``Next enabled feature`` on current interface
+- ``User_data`` per feature per interface set by application via `rte_graph_feature_enable()`
+
+Enabling Feature Arc processing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+By default, feature arc processing is disabled in `rte_graph_create()`. To
+enable feature arc processing in fast path, `rte_graph_create()` API should be
+invoked with `feature_arc_enable` flag set as `true`
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 3
+ :caption: Enabling feature are processing in rte_graph_create()
+
+ struct rte_graph_param graph_conf;
+
+ graph_conf.feature_arc_enable = true;
+ struct rte_graph *graph = rte_graph_create("graph_name", &graph_conf);
+
+Further as an optimization technique, `rte_graph_walk()` would call newly added
+``feat_arc_proc()`` node callback function (if non-NULL) instead of
+``preocess_func``
+
+.. code-block:: bash
+ :linenos:
+ :emphasize-lines: 3
+ :caption: Feature arc specific node callback function
+
+ static struct rte_node_register ip4_rewrite_node = {
+ .process = ip4_rewrite_node_process,
+ .feat_arc_proc = ip4_rewrite_feature_node_process,
+ .name = "ip4_rewrite",
+ ...
+ ...
+ };
+
+If `feat_arc_proc` is not provided in node registration, `process_func` would
+be called by `rte_graph_walk()`
+
+Sample Usage
+^^^^^^^^^^^^
+.. code-block:: bash
+ :linenos:
+ :caption: Feature arc sample usage
+
+ #define MAX_FEATURES 10
+ #define MAX_INDEXES 5
+
+ static uint16_t
+ feature2_feature_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* features may be enabled */
+ }
+ static uint16_t
+ feature2_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* Feature arc is disabled in rte_graph_create() */
+ }
+
+ static uint16_t
+ feature2_node_process (struct rte_graph *graph, struct
+ rte_node *node, void **objs, uint16_t nb_objs)
+ {
+ /* Feature arc may be enabled or disabled as this process_func() would
+ * be called for the case when feature arc is enabled in rte_graph_create()
+ * and also the case when it is disabled
+ */
+ }
+
+ static struct rte_node_register feature2_node = {
+ .process = feature2_node_process,
+ .feat_arc_proc = feature2_feature_node_process,
+ .name = "feature2",
+ .init = feature2_init_func,
+ ...
+ ...
+ };
+
+ static struct rte_node_register feature1_node = {
+ .process = feature1_node_process,
+ .feat_arc_proc = NULL,
+ .name = "feature1",
+ ...
+ ...
+ };
+
+ int worker_cb(void *_em)
+ {
+ rte_graph_feature_arc_t arc;
+ uint32_t user_data;
+
+ rte_graph_feature_arc_lookup_by_name("sample_arc", &arc);
+
+ /* From control thread context (like CLII):
+ * Enable feature 2 on interface index 4
+ */
+ if (rte_lcore_id() == rte_get_main_lcore) {
+ user_data = 0x1234;
+ rte_graph_feature_enable(arc, 4 /* interface index */, "feature2", user_data);
+ } else {
+ while(1)
+ rte_graph_walk);
+ }
+ }
+
+ int main(void)
+ {
+ struct rte_graph_param graph_conf;
+ rte_graph_feature_arc_t arc;
+
+ if (rte_graph_feature_arc_create("sample_arc", MAX_FEATURES, MAX_INDEXES, &arc))
+ return -1;
+
+ rte_graph_feature_add(arc, "feature1", NULL, NULL);
+ rte_graph_feature_add(arc, "feature2", "feature1" /* add feature2 after feature 1*/, NULL);
+
+ /* create graph*/
+ ...
+ ...
+ graph_conf.feature_arc_enable = true;
+
+ struct rte_graph *graph = rte_graph_create("sample_graph", &graph_conf);
+
+ rte_eal_mp_remote_launch(worker_cb, arg, CALL_MAIN);
+ }
diff --git a/doc/guides/prog_guide/img/feature_arc-1.png b/doc/guides/prog_guide/img/feature_arc-1.png
new file mode 100644
index 0000000000000000000000000000000000000000..d518000f42753ea7a1a71668c2735c48fc75c3ed
GIT binary patch
literal 61532
zcmbq*WmH^2w`Fh-1W0gqx5nMwlK{a&@L<6m(r9pZ3mSo-!QI^xn#R3xcbyCQ-di(o
z*8G|uG~K=Kt*Tpf>eSxn?9*WyYVug<#OTkSJ;PE|kkNYf3_<nTGx*P_$iO!e@CnDj
zKR9PCd8ucmBcxlv2SiIrRmo@1Dq=8hO^|@kXrC1HoS!{=Mfdmzr>I4H`0Uxik)n*`
zYY(H{ER<C3zf&P)%`4T0gUwULr}U~nU&C!F;9*c2GQp#N|El|}VvIF0;&Xr!3KKjt
znT#Zp8(ak#s~w(^dEMB*MN}^>i|58HKCNHH;<$OVikwdHF4OEhqxxdz0Y?gzD(La0
zMI-$`?`Yu@>HqU>*t5d0|6D;WVNLyXVNfjU|Hq|78{cyL;`=#NlJy-L-`snjy>VK9
z^T4ZFxa~SoYixXFJIi;~`f$^_G(n3yDt1w_#-ZD0(Aau^J=j#^eqiM6JNw&?MrXZM
zX!m+y@`C*8v}$ysxn;VCXH<-4vPeB4Up@xec$j>u`7GIGXF>J;{^nqbB?u>8I2RPh
zd4GGNe&sky4sG#mY`WQRJl?1@=k^{UO%t%rizF1(A}0kYc%ALjxw|xK3%Tvn+Rauc
zWH{98rPtb0MP=~CU1O0b><PIRyo=h_`k`dbq;vnSzQ~1%>0MJQKI7}L+lpTQBZsEw
ziwe@@UN2pW+4l2tLtBcQ_WN?<U5Z}tY8tP1ji4AQLo`8e)G9-e88!~RkCC08Q@1il
zYvNwi*&K4$#tiy;XDNY^&CG5Orm=BSA@}+~`uc(&%udj4SNCxK5?=4)Z0RopL&{c{
z-9K>?GN}D$8tL)RZn@^GZBlIK>NGbvQaMa}qmodNMHvkbHktXW?b-PMlrg+yev?Su
z9bR;0nn{8H1D~{eF5iC27BJ4_AMsypBpE(?PZJ$G1eSC3!t#n*XL-xHiVvrgymfwF
z8)}|HjpQeB?DuVp&oyoLR<r-!`}oh{x3#`Fns#?oDq43b>j8w@+mq4bqh5+Z5{DKh
z4z)Du7%XoUuZ6$b?|$1-y=2irzgzxLfMt-g!Iqw}b2eRWvT?mEwBohWfGag;3$3Ip
zk@UL|bq{S=p+ev_-N0lrtWnwYHH(IRa$6nXUcP?CYQVUA{ZU7RRov#hL<1RZ{CZ=`
zx>8Wo>zp%<yvaXd_0FThb>}V9>w-_4(+j%i^!^3ZL2F)&s`Xze{BJd<e(5tL7<)(}
zW0F$-dbdbhIGFhS&SW6GF7Lf$eaUOR`T{4c`Z_n$`tp9e<Fbr{*b|}ilq&D%aaH?d
zY?fl&dLxAA;hFbqtb%r327+f>!A{%G%Ik@agKKMO#cKuqJ8K0@Bx_wM{QYP=VzH~1
zzO22Yv+TWWw_5iV9w}EG_fe1|vE<HUUF;oG;c4=kh&@yBX^QiRyH$Y^EYi_d`<6-4
z#ctR1)YWds)k{NI(s<ihw`<Q?k8AH)uP-GnRIkd-E)_&3<|A;a#%wh`JLq$~_076c
zb$TRgejhaN>RRuGwLSPHj%F>C!Vi`G<p@mJJ*44=n$LC`r^YZcS5r>{!l^$MA}Ypu
zN-nc@bI_V{gEHq}#G<!NobY0j-B!TwX8N#&EtK6QOKZxVC)%WYAgN_#X@EP|JA+NN
zlr`?++VAwJccaeUc43Gg;maz-yKw%3nAe)L=2jJa(3E2A3H5B6uCZt>Y5~3Bvzx)+
z4hh}KpZ$aseULipN)r?-6FQOjc`4>Hz1qfXqCgQWzJX96Tbyiy6&~4DSXh?i5BW$-
z@OzUH=GJ07vMv5@#ADZ-h92kTmqEv}eNL@}+5cg;4~x~r={w5}YU9ZJDEk=u*!y_<
z1p6dZ{Q)}ZV|Nf1+&+OHa}q(j=7;2K^ja4@C9Bs4(8|vt4dEeL#ph>KXoUQJ2D+=Q
z^Lu+z2f4GUY?!gH%uGMo;=r|<c8WFUA;VRe55Fv}kS}G$J5-t!K4?m?5W$`L8yRi1
zNS5PIp1gk4)1+g}dm}+#ym40TvyOf=)`?x%|NIsEJK9sRg8mUVS#;8z3$Yhc3ibgL
ziJd+-d$pAw{U3;~r%mbeHCNK(N5lo<R*pZ=t%<*}84Qkch&!1*ORn<9PT-+PvkF@7
zO5q!DjA(HhkgY^1#VExt#VdtCo5^_~*pMS}h@nGi@s5`~`keTj{G9rn&YZE-F*Qy^
zB_tWc`+jn;W4eGrg?MucHvGZb0`<T&$E}lGk5Qkm$;lK0iMBr(IVssHoMT##YOGoQ
z;!8)+XItOfeg>0XV86lX7lA&Nj=vs6Fxr}6P5fY)ABzS!%!8qu1xJl@uLV1=vK;o=
z7U`Svn@(z+pxCBog-j%p7vWeW{C$@Xck26?AVLjZwTEdLyp)u>9+x!XR#lEG%f%N?
zGhYOqRt66QgFnadf_EZzB6p&8qIY6$mKwzUz&DXMQ8&>yqv-y$W#wl5xRyn5c|88@
z0U*7X-Lt<i^-x`mr2|POoEPsdg3-R{_-MFlFP>O9P@CsYmFg2r{lW&@Cd^}vv0&Ap
zdM)bL_|%h%y0H#lZSqw-m+4Ga7Cu=|G1p3FHQa7$A#BCpx`w~eZHB;^M#m~Pu21O?
zxh^z&yHB50S1G!I(GIRm8%tMfA|#f41e2q1;t9Tjx?kKL$=BPzwwETrxuc0UGI*Pp
zXkhT|N9JNpO6FgO*I&oQ)YW{<tEx0d`_kI(+I-vZ+I>6jI((hy)peFU+nm8Lgi$oA
z`j;tdR+M<lKhq&=Jw>^gNXe4Poe^wuHCA8s)7ZooY)i9(kE1O+eB|Bo7iV3ur=esz
z$rx$Oq&y3gj^b_>0~uHFHMY}9Yo{JfTBd<JHaQ7oUcSw+XBwUd+wbW)lBsbPsGmEu
zX1yz#V`p9VRX?)G*F_A#iP!lGGN<xB-;MJu8{xqd%3G_xAOC`%Lcm5(@YjI}HYcc{
z=KrA~lhZe%DsH5Y415!M^W`S&Cj4fn;b5WdBp{3#Hj*(Yg-5Fs&?6d-KK=3pWRxW?
zUb1Y<KVY~5zgr>SDUXTGW*ULb{o3e97%01++!=BCFg%DKZ(Y=$;Jhx+sw25*XXg)J
z@5j~+`GTF|)kIs{tCLwVMA(*PcCu!^yyAoRO$J(c^Qz_EkjGW{OR2IO*GBK<Zi0@k
z)3IFXf5(lq^#Eegj_gv*E}pvB3s-tH)0nYgmPG_p)m(U;&WyNP_zS_sHZG@oJNLAk
z126U_Fl_6Hfjp(!24XR5mKbFAa<kNef1iqxnSTKuccIihJj=SWus``nD21nh3Nq)&
zz=+e=o6Nf_MRnSyG{)B57_56_kH%YB^p}Er6QQgLwcZ9^?+qJg$xF+8OK@2A*P5IK
zJRuhtug7n84ifjgcr;2qbKBZbkVCrulu&f@(gwxO0Xsyl#%`8Afk8d_d6rPo&CZHB
zBry&nqlOm=S&iiCC#kr5MwRBULTQ`x=TcwG3u0yhK5Nr#Jjh8t7=}4I+ohPWRy8Vy
zAr=<2hPg=&sk2Uqt*dL8f{W~R1U5suLDa_s?jlSN4d&eg!yDHnGX1reVG2@6yY?=n
zrJn3f6ZpSqFU`}MAQ1KMpM3Z3W{9W`ywCXzx=AT>FkZ2P);ssYz0NC^XRh}~d)O7D
zx4lDS3jDFqsA%~mti$0?XUR{;q@4__f98r4`Z;PgZ^*@~5#5ATwGX|ogCE7nCJ_-Y
zg$+Cx3B`U+OuGXqi`jx$OI5;yw(Yo%b~-i~R4r8Ed2`ZjeDRsaLWH@wX0TY}KT!>u
z@n1Gi;3<B`*H2eDr%yG9n-;4y-o*U^k8ZJ0_}RtNoHd=@*NYW!!KE?HqB;;^9L01_
zf;ZEiy0CCT*e-kc`F*n?7GRhJGZ$^(Ez8*Q7DJg21Amydj4-57``I+oeI$~q<Wa=a
z?SmVYnct+G%v|uU>PBbO&O<3JI6t6?$a`cT8W?KT7oF5kww+w{<;V01lPu&)#MyS1
zB8P^w>fn!kt!?!8>?Mcpc(cGvKIk^$*Drd|2kjGZn_F7CG}Fjy^9Y<5)cEPq9;DLm
zDTS^c+b^<{mFm$nMW2kXVCrJaaub6-GOj-Gi@gr!Xs%Fk<L|I4D`Vqwdx07$S~>dH
zZ?w_h!Bs^6`z~huqN8_}?gBRqK&=_MAib|P9kIF6G!}{M97bB~Rtu8HScEhTs?a6d
zQ*J^%A_nB_Dj$Sir0m9MFl@ASPB446T{@)C`=Gfu1U-y}QQ<2*uBr#irfg%2P7U?1
zP;<K<M{ccEEa#rRQqbvO+S?8mtOeF&<M*uqd_EcGW-{_r<Y-!r*tLyu4|wNLq_{q+
z-v*ZpEmk06Bsp+An5m9flnsg%k)BYVO#Y=m#1Z?~W(w7J+<UchVtuV?_8@Ljj_niQ
zN218xNZ=i&NQ^JSsQI>pr^Aw`pY611rRSgp-#Z0^wv(jnO9{;1thEHK+%v?nUk_Rz
z#Ohqo`{t(8D6v5IaZwc{y9@iKZu#O`1VSz`Cu%b`p~VFP3I{$e%~mWM&@`^rwlmk^
z1T)KoU2{j(PkwH3SO?tat#8tsh-k&3C+y$Ak%k4%bime=&{CSbzq>J?SW|S8zVotf
znd4<|Ec%_fSX!IO=agNwJn)`Y>T6ry)!wxXD?J$Ivr-UtJnPVkBn}s3hGq5H=7L99
zIB?%#bD?B@{8l2QXot*joq4puFAr&q7wa!wg*fP#iYB(0h{rOhpnvvmWOi?VRb9Jr
zs(#Gg)eT8v=qkuQDDJ1;%Jf*XhB-<v<cBgW@kkwf5(Y}{4_-X$UaN^K<%p(O|ErwD
zME0(Czvx@S?6#wnhU2i#FQdK0e2{4~>1rCjS;^`BrrVh6DEqV|Dph@4L447020zOE
zv#ST<FQw{?D1I}Cf+NVxfpL9~Gl%tHSgre#pqqMS1nhp{W}jzXW74MN#Ur(geSO>n
zGNC*?C7LXh9egzsLQ78}`cOmebdAT8g{6s1!{4)|K7=j;5nVt2(495vd-7^{cp5z!
zf&P&KD&~l=sHTo?j8g>`cJb0>MfCu{%`9AywDwThGC!fc4+9JnUPM@3TZA++827+d
z<Jw~e0NO`<yY`Pva(Jqxzb(_F0r-_OA5K}*HFLrnp|iB^5oG$nxD<rZyDK}dfuwA>
z6=J)Y8eUW4Y1f#?8)-8|XjF^e{)(TSsd3~(e$Xx~x;@GMwsxsUAxx5hDaVZ@hVrm0
zY;f;v$OeP0Px00E4=gnaKCxC>Yy~`G_u4tP;;N(2t3=&D;Pyjo{Fu70vhoSRXMTS6
zEoZOvmeY(wpE@Gyub%kr)9qAGH}?I6?-U1C-I#$l%1y|HD8`W*F~p=^JI6Yh%#knh
zIGeQA{jL*1J^DKt4r-I-(qG5jSN=qe$WaBhD~KOn36}X9e-oMi(@@=1zpFZc_sF*6
zqYY?ODg2MaTsHr3sY*Ww%aNOhwf8k}9{<VB(ayF%7+X+ztLXX2w-qX54<~{=le2xc
z`$1UzU7lp{m8TQlBXSgh?R`E=C$2{FwIZVTU8|X}C#J3_DWyCafP_)-?rhtd*aqgh
z%fZm1qW6KblI74-xNNf7dhN%@>W|$1_dXU`PH>Ix{wzF}!%4&XHOGFB55pB7JMr6Y
zmTzmWP+4~s(@`^U%4wj^d_;~cu>Fhw(uupKbR-jI$~Uzv;S97w#gz*8MKK!X3oJrf
z2UdOd)`xrfL$XH;lTq$aoCL^72eIjwL8X)gAM8kXz7;}DxpVmzlm~{Cq<X$#OV`UU
z%R?V%QEOP68{TV93{I>O&@0~uAW4mG#nH$I92A)EHMnYh)sWN{JU(2$bdxl!bol`#
zh>kEt0!}4IMMCSDI>-D!KlJ%a;6fyHnpTbGmu>5~2qQBweAo>Q&p$$b&>S?KJSXuN
z?4s}|&0`g;^KZLzas}UR5dG3R6S@LOP~btqIuw@88*p|=gDsy}T=IT;wR6ZZiPbgg
z_6%a&u7gGuL`#fDrHaR(ifQ9Q#r_jwphi^n2dZnzNf?IrEz;AF{pTX}VLevLH?&dp
zuL9;YMtaxQ2MbHWh@kq4AGiFJmt?}Op!nnM+bgHj&GDtiWtW46dDfGOL5?f-gg3t%
zo3jj(9zI(8I`4d;mIv&~vR~6$$m8(q>GnKJNv^BxGIhMelCI@yWczzs`!(<PnFg8#
zR@1PnQX}_%z^w)5c;I5&>ZOaB@|pLytBc>!2BQ;jkFJGak*K%}^d%Ud^}eev;|~Zn
zUuC-wj=<;YJp;)5O7>sCzTzF7RMMx)j5&Be{>8L;Z)i}PmI=CIvkZLf%Wvt#`v_|o
zAqV!SMEfkA*KEGQbx6pZE3t6%C|XmC>3VHaeNrSP=U|Qr2_aw|V))!NAE1M_Q@tlq
zrGA7oXPwyMmtw0`7Nh9;C>`fpmO1?Z^OIgpOJ*}BPyXBC_*L0ttI}L5hpwdd!}>s-
zWh-Df1$6QS)^nFfx#IA|FgNlg+aO>)=LleKM6nhnlhcl?jB0wH*_0EEQl<3511xiV
zr(9|$xs-&W`;z7*O^_okk6(c$YLpr;p`+Du&B$l#jR!5tCy}$JKXyMS&ijte=U%uS
zERek;h%c07F^D6ZM+O!+DB;(H%jOu>)<Tv1bjrhew^AQb;5PvhBTQtPiyk8BqJi~p
z<q+S6gWUyHzlXbvC4TrYB>m)UvJ>&EjSR!YF~+S|9{amoO8PanTtwtLlBjI(%P)Tx
z4cmh{5TO-?Nbzrq4D?QXqyu(ccd4<@h1}@G5u>H6-nsFL+F}zi%~=kMY@OTo11o~-
zU#XgYzBkJN1S#;ZX~1>gC0PU=2aqiTBii4u-M7I=a-16*jZTu;%to_C+Z~YO$zuzV
zB3^l$F<sJ!h1;+s@&n(?DOSlX1mL_&ZTB;jMXy{Th6U|^V-{lO*;()!tlc3Svs>{^
z`qk`x5fbe>cj;(d`F^QqV|d7sXI2K43aVVAFj+d^SYVPbw}B1r5p);Xv^4{kp@{(C
z2#h6-Mt8_qYsF=)=zh{T1U0Opf<CX8yfl-!0~QJC_TXV`sJRozM+`YZYYN5@w9LWb
z_<Vzel0t4O4HpVO+F-+COf|11W6KSPC31^%jISJ9RS1D2=@Qr7dZxxsKRp1uB)a!K
zt?}1}Y&JDUoTIS6oODbY$(6|prirgzV#(my+b5`Tjkb<glIZVh7c<RERsQVlIq>VT
zHzgZAh9+sf!-_$6(1teyIwd&gvi=*L37J^T$boSet3&eiC|yj|nqQ0$7iWKmt>AyI
z#HVmEc)>hJCP>UqBqXQ6Bbx48iKNUUOiyF3m_nc*4;)kprB=|1Q<$-TI^_4Q2S^f#
zgvRdD*-xClUBZ?+il@_RT|>YsoTSLvuvGx2X)SCrCzVQPL!AuKxJ1TL56{YszF0sq
zIr*1v8ng1^E_#b}C48i&Q<Ncmx~!DD0BT(EPb1L$GAJV$MigIQnn`KwPvNGnTYST9
zcofjh_&Qy84U8j&8&ju%=VxBkNN8YGkop8%;JV*)#&f8V2pY3<q+jQw6IZAD)29)K
z10E>75}!cI@UZ2M?@~$NJW~}oYkB<}#|-zUD@-?hljvJ36eS|$H@TTr-QI|hG%f`u
zOQJBVQuy9-J%)U7BUw|59NhNeoz}~+cthA(4AlUq*9HCbfs?SG<jLu=BuspgS@K>T
zEIamdMD3fHx)A@$YK{=66v4tQyP5Y5uYa46;j7_!Io43;T6ea#VlR(LD<MfRMC_XO
z*E8!l)FuallsYcmsX30~!kr2ma;hLRaXofbH$6wx$$mT?VG<Qg<Uq&cE?$$BKY4Ek
zL5AOt(V3NFFp1>!2Izki8y2v}z4DjP5kig7Dc9hX@<)}?5%OLr=nWBl6;B~-idb5v
zJ!^}-1H?qRen*cG6^=F;GD^h|nC9-ruWsH$itI=dhC9w%?#}fs@&KdB=-0B|_Frty
z6-WFsDOavc4jg!--=chYO9W(Ey>DPljsdK8vo)f$%BhxNX4nf-*nTbt+38nD$&@JP
zsdPGZ1w-toOHot&wS4ev%=o!bdWaW35J>MAtW-(wi6$X9U-z5xRTm~v03K^Os%qb#
zzJ&BDwZhcdw{wg|ImfpqDr(+Vj-kA|m$AOQar(;DS30HfX1|r=PcU}JWWBN_&ns-h
zV*`oI{arW9)L4@%0B)w<UA+vKNNJZ-B@SIfs|4NquliP*1Gr&DJy~ILr>vFyd~n*6
z>_(78?y*+_nCmtLy;=kT$4GU2h40Pt0bBr&#E16%2JKB((3JeXl#Q}?<NHDg_`T-3
z??-l2j#5m=qcuoDG?#`6B<1GTC&|AO=^jB(Osuu__IQ=0LMl6xv8-C^6}XD4>pdso
zRyP`tc%m}%TMXZCE(hZ@I*|@}tdGdTGebpj^5>i4j)wJh63!kd4q!@=Ry2!k6(+${
z$sESn<+vXPCrAd92LUGV!siKyd4{PdYFBLxOJil6%+9UbKH~xqM2Vx{>_V=+m#RY(
z*K(!mzv0yPm4ljZj|Ztt8i0VVpvjDd<!WgAd(ID0ctv(`Ea(JizP&1GLcCO!s?5sR
z*nW|ffTIhdeL)moBpv!(@iEeXm0sm7x?y|C3G^Yiy1URR1uX*HX$k_$bHr9~B}Lza
zHjSKi@*{kvrIVgG?MhG&3PKC4d8oL|0AZ0M1AxrdK<kIxX5$7BY={wnsEKIkHC)`I
zFeyI*Ib6Id&@#lR@v<{5M#YKGjc{CkRNW7YrMSswF~=#>v|eF(zUL^~+)1eBE?bCe
zwSxk?+b3F(Xw=7-bNAZs(^5HUYgaQX&Cbe@JVWry;LG(C;{bQc7K!AZ$P5X+N3i&X
z%ECHb)Kcv`ob%CH6g(D*MH)AW(K1{!d|5a3%fzC~o~gUgK>2K?YAkQ3zk+L1b#-c!
zZ5OHo*t|RzBau7P^ht${*QbR~>qRRwH1VfiIVBc!2Y~F%C{j7w<@h3Be~JW_w|)l(
z3y<x|Cb^&YRV;Pity*orF{(IFx5%iZx`(=ZSlMd#s%5QcDPmWGoTm()%uFh?i&hoL
z<k;v!n()50tlF+|e!xfpfpr_UG}BiI%8;Yql(ESuK2X5RT8H~*3b+Ouq>1})dRrbW
zG`9;cS=`N%<c-MvV4dfGk7ND>c3Xf30rZN=YrtOWonHm+{33c=fF?7m8Z&|4dbw5h
zX9a=Z0m{&LuQKc+a&gvf;7ndG$X*JeK5R}n4jlfyHR)4m1r1M{_+jG?<b|@ASnS>Q
zW>lZ%QsEEKIGK<ursAaDotzK}f8=y0i-;RRxmDxiaPiJvyPG8}tOsiSgN$Zh2ryV=
ztWa2<QM}yFbN3ECA7S^x$z5>2NQgFU=r15B66g+pGPu}CoNCY=kSSR<JTdZvK<iAp
z$c>hXZm;gk{pBYg$DO15Fu0hz)36L_jiQ8_W<JA^>JnG;xoR$F_wp7=D-gdIppAL%
zJBOMOl4Up3=s@vrDv0PmT6;M|GpMW^E<-j(La=B|mtT^D+7`uejDE2-+j~_gO|G-F
z@|*wG6q?O|cG!&eOz-VBd{$t^=$p4M-XX3zQ<DJwL|*9Ru28~@#)iPnYJMsaXxZI-
z6>8&hsIXlv2@#ox)dc>4bp+r*@dzL`F9-OV?|XJk(r8g%G^_&$<ZkNG@#I1?m7bSt
zJN3UM=a%_10dkw09xH1Cs0hsV4=494c5A7?y9>gGt!`@77@rn#+gZQ&IltI64<oML
z<{}c{kSUCuGZy}M^FGI$P4k#$W_8eN+L_11xP}mVP(dkk-sPEf9a^we&ovrcpxDa=
z?I-|yCNn<nG1PumO=azz#)IdI+AUzytSDl+o(?)y58t1>Zpf@(D2d0F65Qk8ewGvO
z*`VAlNx)?)4&03=Fv}=s6P8BYwYyQo3+iWaIX|keA>wen-q83swY%Qdd}yRCjL{u{
za})!x7z|aH*+z8=o>DZ^L@%UXu6lTAk*S-p(j)1K#ROKD*M35PR`r|MNqUuLXP@q+
zB3{SH$TM-tFLLQkgAx*)eglo7Uy|6^aq#v&ScbZL$I=}*>GqWJeXA=#GrVnr^6J#e
z9Pl~%=#+;0sTEswMCzm4Q`GgaL07XVU55C&`PF>gtgQ^RJLRy}+rWX`K3!IetO;^n
zNvFUFaHJ+^K3f0OEVfWcSMxfuv&;{Pt2i78ofo(WN9^jF^^o_d1$J>x2`g3*t(LpE
zLr<>)*fPz5)v?|2$`0EQYtJ04oIdq%?S43)xMxVTeAzVHHO<tz`%Z2})kcgyGS><1
zMQI${+w)Wgxv5@7juyQ5qHSKAzEh&AiI?<<RjU8J3{W7SQLq(6L?J-J8yP7}Yb_-+
zsmdX0Erqb?C!T(l(`0zH%c@l}dqSS0lTIn=*2tH>T2&intNqr(%dc|7BK*(Uh>=?H
zPJObenV4GYQLcd=rEK~~$gdrI+SbeM;<7;ZCnrGbmX};4C5sN2MBP=6@Ka}A6F~}P
z8Ae_cW&}18$xJgPk&AUeI5hTUxP^Dkdyhi2Zl;cC4yCK|7!Km`aQa<SMfILt!Hku0
z)z~=V-r+@}*dlnXI$cfc+1x7a=-UA3Q$pM<BvNs*gCq0Ntf+~tmHzdR!YQv)W3}E<
zrgAy^R;R(L*3!N8L@9yIH5QxgHrD99UiaU14jOQUl9Csc#vvpUiE4#_S@rTc^ShGN
z-9`Vlq0z^a2nW}JdrH)Sz7b|_E<>aGMPdGQBB97?(AT@OG#W*5e9yux@11^R(~fO*
z0ek-R>~EOnac5?X{Ff$Nd=~nh9(FHBv_@~EG$-@*zPQr^u18Z)c-tn0gN8G%gx3KK
zwW*;0RhEZRLUVIu{L6{8my?lgfJvh{P@yV41tlsYz85l;tX)<`Jnb9mSJuZnMJZ%y
z^xCrQmtG=;Of1aH=C9Lt>(f5jRj7bW-0{pLzg%|x7-dCc5mb8;2Pn9`s0KMY&0fl~
zP6T~&oor+wvsMb?QpMpEw>}=UGPm?#fq#pE`tdhpYq<Fbtmm<R$Xd;#$xuNh|9Og7
zP!^;j=V|M7F{`3F@HeN3Qm-dH*H@}+@G|;6TU8}sWPO~>KZ>?h4zmb%wT*Wlp`VM%
z#A$6JeMC30Ma4tzW6q}82@f~oH(`(SfEq@jsXSS^KDgG}jy%}S4|3=GNJtbBsgO{c
zkv%Vp=|QrqC#sHjPBD01-o*CO$Xe;ZSLZcaY5x2sx4zTdD;Wj#lG(~a&g9Ci$jy}%
zuU}FVzfaE!|EVO*NPxCS#UDuH>AS)>T6b)xIX}ciD&SFTjl=6CcaEs#YWjC8J=i{{
z9ZcrdNfB(^A+K>S96i^F+t2tsJzZDOr;phoOpwH1v#-AAlnP~cG?I)gw8;(;wZ!ZD
z>6uU12*1dG5ZuE<b-oM5YZ`c(tRsex*6@#q6Ysl`ZT^X;V}vM)J_vZ8m3aCCKT4rM
z@x>eqe;ic()X&QAo1iRaK23R_F(=q#IN?%2p0ZffL6I>!`>`g++3ujU9||TIx+R7u
zAeD{521-`cI9JEF51P5GlY3+{&LaP;BPq4OdZQ!_IA7}aqegnLXwLg0L7t6UOn$4@
zO++uB2|B(3Yg4OEZ_kjMVa^8lOi$0ACy+wUKa*3`?9o;IJ^!7N6CYRVNnGOt_bR9_
zy{^tEqU}O09Hg2d7G-$yxCP>W{Rbd;5H$Zd5FinVoTlb^GNcIF7r;re$n?&Ll5yOH
zE5gDXhvfCuWFfVA$S2Wrp|!s5a9@85rBs}Sos^{cTU{LL6UUW5SSE%i=lk;{O%4An
zOj>0EQOJD@_hZvz*zgI_#OYZ}c{COmLNs~>X!-*98xb`-d{jo|`q=BGYAr2O0R4t?
zH_L>GYU$_CUtc%yHwKv+k;l^E7xnd3ANQ#H7uqil$tH!dyVgB-lSCsGZrn6yf5boE
zZHxf?GtK)Hw}h$<O@O>dGGjr8KEC-9`5PIaDAGr``13#bD;zYODwyz}N!aURRYiGD
zin^2MzCnYRg+=xREHke**T#Zsz8L#m(Os%Od5m~p;L4>ih8$Gh59~iQc*p8YYQoyz
zoB0}O)m!`np(&=lc_ce>Pm`NurjY)eOo$eccA)ZFjO?GKR<(BmfSo7`NH=3dnK#r(
zc_yqs1$g_>xM@|F0DSpeaq=Y+#sJzRpD>9SKp<Y&WOCX?7--fhYzkWp{P{sT_&AbS
zQku>Q#B^Q<@{fNq7dP1d3wfW+o<BEFOJDGyVcB>Jl*;!ff_}ZLW-eirX@N7%#G(Wq
z?F-*A7AeWwTa&Bf^&#&6z6?w*>K#JNpORQc%`kM^_Kz)`t^}n~4A8wS4sOwIxY%rG
zeKJj_OcmwHXfojfkAB=-xsfLZ6Y}2-CfDAxTM0j9w8jo2<1+2h)6g;ibO#JE_7^S5
zSa{+_GXKquaOt~Nx+9=$PwR#znXG^ZNCbSoR|E>|w0z6Lq5%ec*rRtLjz_OD8bu^+
z@)V-&h&7);yk2y7NEOK9`;c7)e&j*KPt@^H8laBOq)+3-4QEZFANzTxn%gsKUO3qQ
z#Br5?t{*wBlU~|!?m^_MU+=V1pO)*BSv|<N$|fbPdGXaZ?H*P~zEpsh9QQDp8e!uV
z0V)0jj@E1DOVvl*ffFwPTnz>iF@0~Yv>Rvt#RuXI&wx9WfIvZuff1;Bn;yNtM7KVE
z^<Vq3U9#zI`VP%6--eT#zEbn|Nw0c}CNKag;1GkfR*B6<xWaBoiQa!bBrd--ync;+
z((y}{f^coh5g(8EDk>oUaO{_P)wfojhrKy)vrwe^NO=Kb1#(QHF9+htKL99_DzHu>
zFa`Yz5I#@-V^L1_k`fOfoVb>tE|$yrwBpNQjUcdLqq}0KTvSKbR;Dny3ttH^_=O1P
zeNudSl`|4hY*~xNILc$&YtJizp||+n6s)WM0<O-xW?S!KBFT(m1wuSG2ssB4#0$VZ
zj4Umd0Ho(fcsdGK66_`rAI*Q>`y3VT+h9aeEDy4}BVQfR%YGD3h)U96L<;6j(;~Dv
z;}#H&wL2g__7yuPS)-J|ZGU@N9)iCzH*z8TFDBSsZ-^XXo`M4ityV7|`Ebyhl9Nn&
z+x6MbB>tDWI(r^L>tp>9IYJ-_aN7pESRqMA5gT1bq5xz_^8c)+l&-e`ja0-#!|P&;
z?4toyDWgHq-!sBU1H$jo?y_-tCONQd_p`*in+p{2qbCPepFo;KZw}<9Ot9FxWY5wE
znM{*~NGt)a?zoc3RW>MlrjICAyF7nHFCq?x{u>n<u2gzkPZUX^+I)ja6g!YVxOo)Q
zeBzq|9()8zECuG;z&|`)IODqA2k2Y=v)I{GCIyN_Tuw5&2$+s~TTbx@;G>Zc62N8M
z_gySKtW|PDimpnYQ*dSeV>Kf39g29<HFg;;`xA7kfxCIgzaFJgnNb3O&v<Bq9pr`Z
zqlm>ULkoet!5|Q{eRYe7_cUv4xLWCnNEojas<;1k5?i9WK!uOiFEXQqfVDgWk7g9J
z82$bD*X;iGYKZ;GRMZp3CJ>`ZMXA-r&dhYBRacvy`>uDTFsft9HNoeU0je!!8s#s&
zIUWYjDFjJx3vi#hF~l`WPmB@2k`Z#*PAysv5h(MO$S$&X;kyQ=VHZ3)=}9(M=>ib9
z<MCb!p2jDyRzL+&NT{^E;<onX>3JQ*zlH;w_rqQX5;Zb}dLI`dOv;baItO;|gnyGz
zKm$=ssC>*C0a}s?(C%Z_NM9|zvK`83#z|<6kVA00xfEXr2nwOMx|07$W@Wrhp`u>B
zQ{kM`>@RC#uP+$s?y-|0qmF#IM3_CLZ7|bPgR#7P{bc)Q*|#PCb=O3~4sciX$E3Nb
z8rAd=iWTuU9|yz_Je8tz&MK;w``*)PFF7BMwsfwGL;gDX8QI%8XIyawJPi{kUMf3|
z-W=F}6E%jymZmtZMLGWZ*n*Ggw;tO6E&X;?=H@3h^V^Q4_-$FyQ$!%hqtAJw;C<e(
zd?;ozE+o-~59h(KIBtzZ;BB=2WnmS#|NUBU+esAcb`iqw=J)*Y%ERn1A5f{~AdCOY
z!gVQUC($FFh^Xdp83MAvWm}Mu(eKoLv8?+pm8Z;Ml;bsC75u2HdVQ0wBg}cRN!B@y
z&zBhXG+U@)0U{i#d>Q->$v{}lxRgD15gOanbjj=Fmtn+8-rko4&U+54-6HddRu;^0
z5V^QmaL=)w`5k6>2y9RssSlJt3KXHO{;ewMB8v9kjYpw%G6rs{)`G2u4uW=TB9;hA
z?FvBxc5B68*l+bEPqtN`=ocO%3~K3&tw5CU5KrP?GgM54{T72$Y}~s?CR)uFSeAi!
zkDJ8edB#)f+u;95eXm!Kz+Qr3R~05P{Tpl<X>R~gBH^C!?~A?pTu1GljJ3a4C+nNz
z`R&z}1fo6lCmY>+gaBw^c@)1sYE%W?vg6v0MEN)BLA|y6ziJB<D)KgZYk{B@lJT(U
zP?~Tr=D2{5fV;bF?;R13TjXQ7bN9Zf+OHX(MRU$4<SAWI4DFrsbRIstdrWwhb;V58
z&e@86YxcSb-!^Dm7=MF~&&*iHW|V1uA-+}Ek363of=R{<c~iW7`$66SU=C>yXn{NG
zYlsbzwx}v184hjJZO)K!7b;iL6jYJgz);M%iv{nPJQ-9))UhQuU*me}><QekyzYp!
zJO7hme#fi*xvOpht=tc?RWaiCmo%3jSKddLX$>5aiFj~ceni(UQi%%)#!$>@yy?sF
zzbEqHbssw}dRr9Ne0m1K(|Vo19?^F9<8@)>?ZtMniaJw<<7%(sa-Qr^%mdWd-@c)~
zy1peMg*B`(aeuZ}T`7s(E_>WE+n4^QGn*k_A<76x>n*Mi-3cHCnJ#w>6cM`AJo&nk
zchgOmWYg!lcQdf1u9C94Vdu{0MHHzEKl&n@K}09z_3`Lu>O&u~<4v05kq~!ArJ&d#
zc)f6`V2q4|pN5TYg01;ti}l*}4*reSK5u?_8P@RM4spF=B88|7#1|?Li^XCo$Q*rs
z{dGBN^~5L;h1{p@_T9ruB7yUp8sxOJ6*?f0e9#6)XxDLOC~G|md2y3DKwtm4E0Hzn
zm}bAM^`6*=*RRC?W^e3nXkbXQL?b<KX|HXV7*OH7;L#u^_-n&%dEuq<q`{H=e`s*N
zF}{$(edQuQXk7mJkBx@${q;`SQn58yK$7|JE_vRtZHx{}>ktqvcK?bPe-u+)_?jV$
zwn$=QDLMPN?#xpHQtNjM=zZq)15*;9*!8X>hHxvh#BU+u=9RwK&%HQJ3P>OX^YD(o
zRnkhl`5^~iN`^?g(f>>8M6aG4oh2OHvUUd_c*0>jK|e-F&ipMZm7#ZZe#4fNC!Xby
z^y_B?+O3WvJDOLH5=(+Mixq(?>7f20aR9Z}Kcc`?%=ozA&Ny?)#v{+dtzZJ<SRmM)
z5hA?MTQ=*y=v?bVe90~XXrn*<_+&Af#^&zUIJrA(1Owz7swdLgqm5qa>rI9T6<DCx
z759lhwEu~gwJNpe8{Czv?$pKZK)b1+h+y;G&jkex>V3QKA9T-woM8eO8xsT{ekSa;
z%}By$dzeIM7QMSV>H3(vtcfNndkwO?P!tt2vl-*;vDvH%UFT)_0s)Tly{4iqu%n!+
zz3ukg^GdWn0WVn;LsUnsfkYOYh94%)&%vz`3(oMRSAubc<E3#HK4Ym|S|``<a$!G@
zUEi$rf6GpEXuX-bs6R%4A8(d-r^#**k4*4(hH?w8ILC0lvTkdG(>kAV$~|cp0&?4G
zn?6Gxg)7$Iu!zpPR^_tC?64siun2qa@koabLRz1xropC%`<spHnO+2{j;aISx5`P1
zTlALl#ToB{?G7S1T0!wJ(N_MH=B*66<I+Iy01DnurG^b|To8hrFu4hGzcrH+=!x69
zbc(2maqIngM!k{yS%We<@&QSYQf`EO!Hqhg#=HDnre8ba)4rrU@Avr@IFD=wuWIM%
zu8^bS5*SLH_<j<3vV6)p5#^jptm?gU7bO7Xgr^=$5srH^)m;8yJShcgjg}8cC%z#e
zDaHsC-wh<hUZ;<hH&=L8Y2;6l=)P)}*gcRh$<N!Ujdq`lk6yo8>cp0w)iUWgHcG2p
z?<Vn5X%Io)FZO_PS?<q$fgpU<Cp{0`#g=Jr#M;GZ#%h}7n&0w?#x`-UIuUMxI&Tmv
zg0x=RzQ?BEnyY6GVo2UaA?~g*u==_jb4Q&qMn&mSBO+Z18eBsEQk|VZUgb_CtekI}
z)ig*+iEjLD@>9)`Fa;KA#sx}X#cGe-?Ks)IpraUl<BG2R)Gl<x*P&Ka<bgSIp+a5h
zE(B}Ha)3OvfO>sC2H8w17;k><b-`Yn^S2Ak*-6KMQi-$e*8At9BGNOQb8Tmyy_RYR
z;`id|j^KBoj{Sj9)84asQA!7oY(v;w<r`(Ff1B?fASJ|rR$0ajdAnwx?m_rZWiyT5
zJIzOI*CqmZ*{rAHNy522{C`V0ozxe-hf;<CCrs#*2T&pRO4pq!jn2dT81ZCNF(|5j
zbnLHj<LV>R0=K;?mK~Eu)r@(oWcl;1{d3D8`Z0|F4T(Q~m&>7v6#NKSTTbiRKci+0
z*TytDxmHpv?GB+GW*9hQrXAj!+-G+eiy3>S!d@y?Z8Nywi`e0Ip$dGk)k0-3lzghw
z(+`ju7g5DH*8{ZEnSDBijX;R6-}}CAg0dJct;2f(6M;Bp0Rm=w<3W)|Xx3b=O2gfL
zv%^Ndj8aNb6B@!2jUA;o=Wm90>S{(<Zrm*Ir8bR?s9F|ikxj{}dr(}~f{sUK##H{R
zdS@zRUiu|xi7COC&z~iZE1pMB&VF~#HGEdV*7u?`YG-<PsBi8w=g!qqsOGTR2eLV5
z(CHp~aGiK8zO7{|LucOnIp_p&%$H(xIJphh#qtMGB324BvoO+wSKGbnIB;x5r+96q
zg3R?@#TOvev*c4?Ti?rZc?<7!*@>GbXV}QtU5EE2<_GUSxn&eg!(Ns;xjJf>Go&Ib
z8BP)l_jQto)+6xfhk@qa6OpUn{#<W9hlQpwNBQl0dFybR&2Lf0YUHRQjN=x0I4Mbt
zbOaD<W?1bWo#h#cdBb_u<$#4HO;#Ka_y&9va1(eFbQ65D(U4QxcJdhpKZ=qK!0dkI
zuP@NrB~1JN_O)Lu4C(O{%Kb_&$_<x`j4PHvkn0tImO-s7(4)lXYg$4Mbjo(0B~f{k
zk<u7Rn+t#@qFj)=I~uXPZ*~^dGL|K2wEgSd%zrg;Cy~#5J_)C4tFSVidX?3{Ul1uS
zML%fY$GkZZ>}uN&mf5WSjfDAi2bZ{{j@;pwx^Pi{wW#=~AunHp)qGn??X-5@zds#E
z^{CDBqdp4HBEpUD!1e|TZ`!ZC=-vhooMmI6ik8+|_%$sxEE9ruOmnb%ZahbO#q14H
z^74Dvl4eE>g)(4&M~Lf<v_XSG<g8wIas6Fq<vfmj)8uBe3*=_VE8x}N5fTz_U;EuM
z&sldjE5ey*az*_)dp`SGvh$wLZXt66dh~%?iB7o)O754a85g|kSr+qlRv&=YCw}ls
z+I#^hsBf-%9uEASEHCUgIs-}Y?F@`oh#w0@CWm)&eB}{|_nd*Mk40y`Z6ZCqx|X_;
zx`n#^y8Alp(wkL+y3xADx`VohireK!39v$ez0(3`{IQycPNRYq7!5@GhS9}Vi-JWz
ziM-)?CHcj?zp=vct|MJIJIRk!0OFAiaXU0!Sy=g+u>pI`KVOtRiP`|rOK#58^>Ey5
z@lu1?-qozh2#ML{*DXXO<~qS$byiER&bgA={+L+p^ku@g8k1+<_=;;rk#F(YfZFPy
zcf`P)7pF6?o&6)TgaE}Hf?0ds@JM~7Vz?4f58sv`AsYIex{sVKbjmlQ)Gah`&|?PI
zOgX_Kp=mYM$ZLx(p+D?3X@@>F*=QVkY7Wki*CzE=%x6^jLB1Z1$8U-?=QA<rZ!Yd}
zmQ%8hfvO-A&P&Im_-^jgir#`ZO9-Xd>a32Q6}ZPaJEbWYB#YFr+8sM|Odx<D^Hfr^
zqD-=0Cu=MiObJDRAVE+dXpkKs5j%9&>Do0-W1s^$i^i|>uZyY6sVl2%1>zpI2FBlN
zu4L!WM8hOZOSDr)iop5veYUZ79c6z3?O>d~eRH?W-|fF0HP#rBd>V07;WaM|U?B|_
z-c|<rk}+p?FRI4(<nd}i^)k$&zflpn-<O)g0SPL41R?_b_A#5OubQ1>Wv8eP)~-ou
zg$_ZZ=0PR$;~$t{yL;EjzrC}SRhpLSPCf2^9{w1b4k+j?>Lq8gwRQ?ij0*o!nxUof
zLGRCnJWy$6<XKkhJ9K_UX%l#1phqvRvcXtBvQoI5MzdEFIvU5uSn8Q!dj)MWS57~X
z;lmIoo}1dJ8S{Lf<MkZ1Q8;UHPJ^E1Wh{xeXzQ4(tNvy(jG?FE6?iN%o=u*@=MWsH
z@_XFxiuV`(^2rbCJ}%XI%+my;Dz~T~$!-sy;U@ClC<AqBr-rQtj@DZ?>p<1a01}(f
zKz-IhO4W5tG7q^o6cqveW#;8Ez2N>|GPU|XJMc-{)l$<ZKLPbjju8^r!@;|#p|ekh
z->eWW5H64|P%h9eFfO71DI?jQQRu;dF0EVz3&ay4$|Z>b6kYuuyw5S?C~bxKu?G$=
zO@g1&7P4=IvimsGls{~{xgGZrunX|Pob$V+Se~FM*G6OiyJ{#q@%THBP7(qBy>z4(
z6(kMafrh(H9|}yvZVOPQR#I}0^Q3~1cO-K{D%ed4lE+LV=yz8&pvOCWD3rl{GSh$O
zKQMg+?-*8S6>^3_J~J+RtrAk2r9;PuVqbZELDu0z+q|T$^4&?3cf<kRJ1)MKx}`<o
zUf01#fR;b1HjaaCEC`Y{1k<^kEv7x|er{g{lSiOy7NFU<5*-}fjF&sooRpNkYi9nV
zdRCZX%$-AIHe=w#_hAjyIjRZD#F1EJfQXLx_1)qcfe~k*P~12pS*J9ys)1(tSA(}d
zOVWN{tO%*Db<o%L1I1q2BYV@vBm06fBMt7q0sA)-G@WK~x=yHC`~AW>DmC+ZPD{{y
zj+=&=LDh%>P<y}cS_e{TeMePd$*VBCl+{;$Y1F*nhm}PSvIA2=)|j($v6{d=lg4SW
z^T0ck)@kwR)uy(i+no*Jp`+YE(x-|;_beSb1PB3y2tooOgX9Dgqf&L{=v7$~ZAk`l
z%*cKJfI&dqdP2Y&Yd2dPiY7n)b@zMb_dl2BVFuG5icHg8A`@ycvhBsE&_3}rWWNF*
z?s$co%GdImVgHsaT}>-5`@x}_1n@u(Tf>)V`s|V4@!7UTT23o@y&~PXki=hqp*Ann
zFoVD8^mFz1lS(0<F@+6sw5qs^qB0J|<ZGZJqQXd2U!anWB1%d@YTXyT`FDk^-uBya
zT&nzhcJ-#_B#*9l&o(K*-UA115^Ge*XjsC8%muFNYFV(orFo@rHSJS#c@|J<7@`<E
z@M=cl{Qgb!?k?V;w<s>NOc|S_x-Le=WC0xQ@EPmO`=*y8FJWfx>yx-^Kyidb@gCc|
z+jla#g`jmfzxyl8tIO4xVTxFSZz^%UAy_<jWf`?r>_F4p*!p>0>sJ_kTZI|@-NDqO
zs)>f+ok{A$^_a(+#@;7Sb=Kud2PE`JE~{T*;eh^3+N^mvM(I?xmz^>TUq_S=ge}rG
zFe}6ciU%IpjHBqs#`;REl1HgDI+o2|v}BXJ@i*$02D+J_B+#1?eDWRN>~?eXv6{=P
z*c`A*+jnfg^4qnW>4hcfC$X}L@6)}?)wJ$7bG{phS)GJOs|nf?t3j5boRJeKfFnXW
zXgMV;7u6x)0Q89Nk$lsy&cPw|JGYKv%=Yxf_)UJysz#o0KoNqkaW54W!iMkuSQO?(
z@0-K+WS&I+UOof8ben*|`Wm?3R)Njzr5yVh+?qyL+SX34aHrCR{bkJA;UqPUt0$wA
zzR!2<-OhcVo9B?qlIH<y$f&CLjC=v0$^AX<`~$a0C&QtrNwW<L<GhEVKdP^!1L7gq
z!SR2`b5F7+DwO&03e)GP9==ci?do5*kMn=|YsG+p=YKe%R>xI2XVJVn4FJ=Gk&}%E
zA;%QAlc6Qi>$D?*<f=skTNbP1%v1O;ln|!IS0!m)$H@2ccs8zBx1)Ho)7eTth|L%t
z;5QjJ+LJjOoZm28^kvIjpUuc2q24&Tj#lxQdF?htvzQ_d&8dPzn5M3nr?W===3RZ_
z3KWpK9Q=)JsXmz(?bb){!lTW&4spx|m%jbJYtdSEj8JhJUbY~e5+q4~BxVMa5RxT+
zoo+xx`924mo1%dOl_>j#POapZ7gH5T{TvyHwp_;nLpY@*1JTlQ6~++X*VazvA-``Z
z7IFk_-fnsv360G2BKOnj@x?l4#@w~ESTqdZy=W0dO&|ECJYf(?k)wqOe>1kTnc}N;
zD>H~x7#0LZx#|`5HoH0BI|hpM(usZ@!O1H*clE`AIRYg&mRs`mC4z+9<7@V2`iFtg
zkJcGb&CGS@VFUSL`ZIB<Gd!^t9go_$8sFr&hYnI5&kTX^|6f(ApF}Q^FndE+yN*`s
z3@Kg%VO1ysq7=!U1FJF%;a0BJXSrz63Rb_0-bG}PP=%cXy%HTNmZ!HGjrTpRp%ESG
z%^}~l>qFn~=~<mLDFl3f_fH1X&l{+xnp%>fwfy-7vXr)Z%GksAOQdJS5}D;3dtzTM
zY{$nj(^~9DDgNfmd%Tp^R{~`O`OZXnl!Wdr8yKz%mt&KLvPiFo?qgiYmr+EMym?i<
z`B_Km)BG6Lizvfg?J_pRZ$NS9(0TcD89kCaFWb^1#cBR*q>{j$pRh<+fnLOh1;CVT
zD(P^f@}2KecgO@NUeX86k5S-TwJwQfVz0fp>8;V2F*JTSE`HrVL5)*)aPDm0u!nT+
z(6A&}FkC_~G_{h+NfD<k<eEt`^OkWh#v8u9tx4~iNNzffarpK<*+EtA-;+<|b?jbb
zi>PyDv$Ko#BQJVwF*211&jTG*8MA{g4u$+~r$)UCq+ak{#*yAsG#;Di*BMQR6n|X)
z@`7nKZETjYnF5Ycm3H4{dq$L2RdsfO>1yYjMMl%O%^wwLrLWGX$2?FyTH}p@7Zwgd
zEW$9ob^h>kyGyS^G$?)6>EG&+{rN^|qF17FWrR#Zg3VdMQma#CWAGg;DiVc?DPeNi
z0Qjl-cGBY^-M9`-Ezx{03OV)~K73%eo>+gK?XpkbbbyPR)!NUZANN^fsX>a})t`8#
z&K)hnI2S%hC-t*Z0=uaLUS%QP^vvvd+j+~m?I^Zz#@b9Yc@>+gM~$Y$fU6h|R%BX@
zznO;hg!$`iYS&o=w-i1ztJ_KjH@<$7bHTa-#gvnQ1dEJQU*V>X0xJU1gJS`=tb8V|
zTvQS^7W)(q3b79!;rMz`h77A-9yleuFyR^yl<VU)cYWoR#5a*Q2Vzq69A3hQ^6Yhw
zxdJrohVIR>Id-ZrWDxrS#V3CS;#fyX1V2FRSGidYQOTd?^+Q|v^UJ$dBOfab7nBM*
z$@?ly5dEU?n_(2~-rLX>#Pb^g_9mTXiztmwlHTACFWpRaz>HQ!7_h?{(+NWkw-;ZA
z!6C*M<nz+7KwmcfmN|D49AuSOB>_R+pMRP#Zk4uf%+8;D^5N%Ge({==g1O9Owoc5d
z>)JI!8|{RJVn5U(`P4@UaTMi(_@=5#k<WunPtFiK%E6`9oh`z<AdQ3-W4!!;Dq8X&
z*NDLu`{H``K&z5;JYZESdAwzTu>(Am;O6kV5e7#}5u{vvE}2XzUV4@H$sdGZq5P)6
z-n%WCs`W?-J)37<e!BYdd^WaxX8wfp9wn~;0!iu)(B-YKu%6~~{B>J^GTU`i{n6N=
zl+Fxol-zG~%y!x)Vm_GK_O@a92Y!9ci$M>1;3ps)d)3oVBfRg^e`)3SDhQ6ysOf-6
zD8(rh#L1K&vtUTSXLUe^amVOHNt{xZB#&#f1!P5Bd)ckUUBXFqm}JwDRdr)6h=BE>
z<p`+Y-IPNN>r;UA$jCOreITv8NbzGE`&{y#JB+$NOCw!jF6?c(t=y;qRV%52@UtW!
zf7>51P1nW)gpFQo{Vh(3qwiY^rSJtx%|kw*3ib4ru~1TlAr^*pWko7z{~j`K<Soxy
z5(h>WYY_el+;$m#+T*ZB54aKg(9$nOAIPX4f8wGm%R_Op4CNu#d1ub9;W&4DA!Tt7
zt3~?pf!KGcaXjs8W0F_p9I@H*!0>R4nXxH|acg#u`>{(oojkivA-WjfsHI8q!Lib5
zlF#J$NtXmvnI_WXy06fwa<Kc&)|y@M+J*M5$?jAYU{F40BTyCXpIUHM!E384I+r%a
zd^3N7+_?W}xk0f*k8{niFj3&4oa}9Sa_$hI{FKSU!4-XMwNg3VR-<xmzaezrt9SZM
z3{HH+bc=83P$sVqpMzwQhMzxBa;_%z*aky^q9ml5SX6G{D~zCWToP5$<Nk6rHm<Ne
zMwIGe;o&Q*@2aYxM{I&0E^v$V(=Cq@a3_S=LfMFv0UGI${)A5Xen6S~D1M~{e+9+W
zCFmERt>&gL2&(}bE<;Klwg1)m{wzZktce+@1u`?+0s=5<*9STOi>I#)h^qU(exw_u
zyE_F&Lb^MoYv@kt5)dirZlpmG9b`~Cl<pJ(Nok}T1mS;1fA9OjPcrwOdvc$(*ItXv
z{J9JkDX;D3NucwUt``7bSHzsYc2|L#fUJC=!Vvc7-0*1n>Z9$BP8}ch3_4vXdtVX_
zb~{lRA_oE_D<C(##=r7j*aAi{+s)VYmPV-vT1w`y`#=6i(+dwazr;Z%U^JqlI|m@Y
zVfQ;>r|ICgqP4|J3k>9j*AIdZlCH05ghLC6c}?UI$_GkpvXzMDQ<V>>XP64xm738Y
zKlb|V1?sYxd5f`^8t*kAaEQkp<J{?#)>r?2P{KPnje3$F?UMO!M!}U?;%1>Pt;6y`
z;P%g$)uk`bR|nXJ+r4B>^z=T-+@H^gvdsW+bZDW7>`iOVE^B2}<opc3j`WiE>McEK
zBS=y34hRz6TYIgM@|fnG=ACugUhf{xhOTV?_*@MXM6}IKTSb&=FcC9yfnJ0B#;4Vx
z)C@O^8)$`~Zoh1}+sCzUC2U!__O>~-_tL+suCRHNRGIqNe@R?4=*;nG@fpzDnBo?+
z`<3yEqUwEdNX&)3#a!w%d=6w^O7<`=5^bb=yASia^QnH#2wrae!I$j+?rLgKEQ|C&
zvzr@FAdUsb;dF40C@QMiG*5xPJYrHS7ibPT|J4{~um&Ox(5^{|!|KD!fi5Dl0Z+-3
zZ{_zQxeY;TD~%CcfN@3~(8pDaT`1)UaYo+VZ&(&u{7DZ;&I><$cl*O?+6TF*4$+=Z
zto96hN?d<SpZl?5z`y71Pe&o@Tt1Ld%#cUa(I~ePN6KXnck8$>Y>A{BGXV7!!xCn{
zfA5O2zD^xKhyYoMlwDZO2$@AF$5>*ZChUXu(sN1u90CNj_*vBg`rlR>VRV|p7w~)*
zO`DwZKMu%Y#71V}%<u**Kd+02XW%XIt8yxUVh2x3j^EF>G&#)5UpalDd*Z`><;=Nl
zKK)k3CY?ma1{eA#I+>hzIqbgeFeFF9p&sJ?gZUF)<o=qLWa?v=ndn}@w&TrwD_r+i
z-7mtC%YEhqN}?IQo3>jkR8nm=(sHJfD?N6(;AYYwc<WdP^{f1~@-^bCq3*{%)h0M!
z0PbSxnM6NY68a_j%=6I#QG_PNX3$Sbc|z3!HE^aR{%0N5|8_<ESqQYV8cOI%7k2d%
zeF~!{FodVBW^hKIpQ7KW`{B*Z?#FxSJ!9(k%ViGu-V$e5{09SvR}Y#iaH&PK2hB}+
zneho2Z57)9n1X*eAHMtk1f}F<<9yAWWqzY@8&Y3+5jr(pl0WEYxYHEt&UkIaj&<Cw
z|H_vKbqV$nXOfCfdL5f~HouqLEL8jR1zu-nBg3J3CX3%VM+9?d$(N%}6)R^XJzmX4
zM9`{xyB)W7a}lW{wh>{~^q20z`!Ve~tT&Ks3O3nS$XPgJ)S&N^rH&h~8>;D1ump>r
zet20Sjq)kJNJw&D`Vzj_;{WEFDza8@HrqcCV$UA{UB21O_mJI{g)=6m4!AaaeIoqr
z@m9%XDPhGVAy1ywv5*lzuS)VVmqud};)ZtEc5kI>C{98_9KIc-G^5=$n?y!!^Lo5d
zf9?oveeJ`Sz(7>p7KhS@3qX8fG^8huf6``5nUt+<&7#2_nzxoC=I^c?0DVw05q$Oi
z_j!qWCCzM2($3VeQ5RAX=8T9GNjZ=&!dvVEYCt(#=#wa-22JBHu~M2<^*L`s4d}%w
zC_2!cUP_07#P?>iBSQ>2(iYK>H};Vn`uEQOjm$Y>QZ8RhQmRavlwZ|!&tkRPO@dBt
z?aV0_iah0I=*CuycEG)2p-Z&MO#EQlkcX38O&^IxN}2A*1dYOk+o)kPSDRhCXp~`5
z;F%K8<IHCH(?Dk3I66HY%f?`pG5aOq7`5ZW>F#4$?~SxeY8xJ;PW1Y6C(35B9vZP@
z$iEqVJ^c7mb*iArd-(e2!r71NeoNDSgB6pV&xW-}STyzVr!UNk8U8H#FXwv8M6~^l
zk$Ew{+4Kg@9s-JpO%lyaaUdufwn>VlZQ)^@D2BgW`*Me<qwy|k>$|_9tGYxU`_C37
z?WOP8+?U$5_IK(Zpe;gsp>Ht)EYNpIUNyo@^y?0?zBnk>5G7HBJ7DNUC46uvTP^8~
zTvI|LnLnpxiX8gfVYEH@IX+Oxez<_*{Ev>wvR9p`M+-)GSF$*F6w)Qi*x~mtAzwrp
zY@)XwQt(jwi^nM<;)Mb1N!z-hQ$N2Erbu!l0dbcJnDqrYOD4SsD9RhQ?7&OqB*-9f
z?J}pO*DWh8W3=wr+z&$UO`sqe6nN`eVpY}mOSl~aH<-GC$7+Jm=_c<_Y{gy^Rs@em
zWsAu~X=QlA>17-?Ww}V`h`<8~yw<~Zw^hKgp3Lzh2z+NgCUNCQo^l(MHU`oqS}fv6
z%FoEzEOUwGJ(l$stZ}`55cuVyG!|ek`7Z8s9cs4EZaMugj`1lW{o;b5FTaB#A8#r8
zr63OmlOUc}1^SP45}}6S7-oF$UHn^2J3>PogIR5S*u`xkZC=a<x3qDjD2S6$j0nwT
zaO(b`?gQ1}Q)XRf<L)&bw<l^xnctj)`Xy<83*O#Qm0(df1o90!Fqt5Nj`hb*acHmf
zi{3a4@S$I+2{HseY_Ywn#?}@nAQ2M8?qx5jMgCb!CH4aq4>#<_&^a>>U+Rwio6+<P
zV{DwgvtG)cEH=#It6P;$_yi8|`qvt(O57VGI0M#qnX>UwR}KPH>O(NU%Ch*gAtgQ{
zl;w_(%6=ZE(TEM%#wS#pQp1{-*xdRreoYVUk)5pg<DSj#xmla`Gq2o2af58%TQ}9$
z-<Ff5q$7WIPaud^K0$usuSrtucz}-b)+>|KYkJx+MkSpz{E;!&n5dFi;lRPk8|dmY
zNeh(7VN%yj?t;V7FI%IbTE_Pm^Y*z0k~ayYd)R$1uFe}~<D^%}k6dL}+>Aes;d(_L
z)Wf{{oe^EaTn4cM(Exf++D7C$E|@s&#SdbmA5lhw?dr$M^j>r|M&w|kVRLIhh06W@
zp!x&TJRh17ub-dK7!91@8{Mx`w{rB}TF=4!X?XBTh`t3?sBBro70?#XvCv@=MeNR(
z;x~;Sp@-*3*ufSlB(^ez*stE~Qh%;C`DBx)XK4fA42J>V_pl>XqF%_N|9yy;Y5x2=
zP)Xze8Dj66OCjuD5IFkmPwUI9)MNGEd1_aBPxfq%<A+t#Ef``P19mpDhrNH%ltsb<
z`ZIvAS3(L~!670ro<2=^`uF?t!AM*;`RcF@PQU#g5~*(fNm6Tw5sW<8lWE6|zVWLr
zS?u|CnnFU43+;<szfQse`_1Iv722sy0)qb}35Zxe<)^tb<!Q%$3HzMt5Mw9{NneMK
zQi*7F?8T6Id{?{w0KKIb4x%RD%phrC!P-9F6>4?#mnX?Jf{S68z8D|DSqtCe^@!Uc
zG-OohZNL3B^i}{TkKfDp{FLeYnxRwY@n^?Ofz)b$iUPx3OFL1>*=H`AM~fRsH7+yK
z`Pwou?BLedf)VLYv!AymA!+y);#5EMIl-bHD@PVDd@LE-&tNh>jW@Of#NgJ<$a2|j
z<Mzj<yH$vcC6d?8)A*+%-vt9OGGk4AHL|gE?mr5M)dMPMB(sR$z%!q(HUmdjtCVHs
zZyNzoVj1#k{MjW|mMW3C_hasG`?~SQm;defwaxKREoSow{|HE^=#MDEnM;dV)0I)<
zWAw#6D^+083wR`indlF-=;JGCUP8u~w4^Lq;!dijdX4mS{J`9#M5_Y1@4Cf7T!Y!i
zHQ8cD9I}+ka;yqv*6SSqFnKt4RN6=9;-@O;p2FC_GpNti1&;y*LtF{(3%|$J8$eei
z%%zw2$FX{Ux8;54?pw%6!DFspyCBul8EST;{Ru-}5la=~_-$M~dq?9{^+w-$9ZfPL
z!3)GWBLL3gS`9x#36GDvv<$HdKhk~<&7$(<k6WDk?|ThZNjOX)YG`o-TAoFy>J|kb
z_}R<8h|5i~&~frgq(Mq~K*LsHHy*C4%iTj>Dx;RLcE$L|1oo29&4zX>7LBiYtxaH`
z#C!B2hn=Lfrv3L|0iUX3a7~QRLT$ojft$?a`PpNs>*FpTWE|>hi_{pSK0%5q_Cl{V
zvs^Bzy)~(;;4^RRs$1z7_+pg5e|#qByWg_lm?I*g+Z!5{3_t4?bP^W7HLS@7sI#e3
z#lXQR86WuEa@P#(SIdtp)N*}4V?@SLZ3ds1vlT1Qw=Q18u@G0!0d9>!aIbqrwAs8~
z1+Hh@R+`FqU1>i>)ygMVvC1)UC2oT(KxOt+4qQ;;haLX>vym>_v8r--R4?a6{EQh=
zxdP)+JJd|JrSw{3^WCn({73X3c6f;gX97mP9?AnaO`MZW+D@`elpVK*)lVh9O%s4g
z$s%z(b62s%q97Es_e2qEc8;xMFGIETPrJP{+*v%GROOZa(zEBBacbsTrt1$5*Me5A
z_K(#|5+<^pTJU~&3(qYcJvvZQm@usxeI>h6XgMly=zzStlZ;#cUzJ&7ui%3Z30rLX
zx)yoP-^ca>Sb12(xs4EA!GmOlomQB=eBq<Xb}K@-%iKJHxJAZH663A!3~nOJO@6YT
z63TkS#vG%}K_QvU(&bIAo5(w^-OCIQO)Hr`A0L-1o8vNa?9Qq&`U;_+$oX@XuBBc^
zvTG%d8i{>4N5S+02=^EnDQzKjK6&Cm{9WdKSa8VORM_<CxU`}_m)mXW@j^Y+w2roZ
zD@~YeJFSx9gQL6}_TyG^f|rgh3?&cVB|bj{%#I_;-o5~;1KrAUz23U&ow?abJGx|p
zF*znO3L-P9g*V`URQd2@Cg0{bNpR9ey+%IpQ{rkuf`g6^R3g-;u8Uv#%bz4VsPegQ
z2%2YD5!I=g^7unmB9c}E6O_(TQjP%JzW*LdlakIy?Aa91na}Lcu}t{Vbf~A<*MFq#
zR;?b}@$;;i*dNummmuAf#anXVl+a5Vhw9Bly?U3i8bn`K3C9ZWcEFgCG+)a@?4H_r
z3ziF0a$|h9$cZV`^cA*OrzOX#QmB-&%v<BK_%_J6EQfr*h3fl@OAg$~OBmwNiiDWk
zesXdpamOv@e3nDS#eM8yV^F67JGXwwdh`VupS&spqgg=I98yQ5#;R7w`l_ZK@7`0o
z0{t+s2i@@k{pQcjvmbwUZZ!_WQl7ja(x!;a_|?9DgryXulL1ZuPOE-B#o!Ob)E$~S
zyApnpmsjC_d_FwM$4`S^Lnq6J=1$6u*+aCDbs}7;VA=j=sifhrm?iSuPTUO}UK?8>
zU4<i!Re_U3d8IDS!5s+WSQU9^a?E%#txiA2N5oMj5WB)@(|UB6;Sn7uY<tK_A$fMJ
zP1d>`mb^csMM&p^WEz~1hTRhXx_XdS+OPv?3DV^e6LlhU7`0nR9o91d!m&R#kpQ<8
z5Ob`Pf=q(!qZ}hbEijD@GCuJ(Ug!q-(=PS*K9Mp<%6E?3WnZb{#T{$WX974fA1UW^
zF|B+^a6qnQ2ilrVqp)61c?=yhfWwXZ{vv2>KQ#||JmTAV!KCu^JxKewa$32}*r_8u
zyG>ENxtjOiTM-XM^_uxKmsqleIYZlb2X3O#oV#<2ryucc@gI9fs#k`DZd<?2<*{u`
zNmclcIfgencJMvh{2>MvHCr#bGnhGwWJAyvT>+C^RGYPDFk>i+Z1rYlTfLdGqbVAU
z3#KbBppFG@nl!4qn=c;GSX|>ZRqhj54S2~~v)Q#y68e2*0_kb1e{)Ab30>)2LT~YA
zL;CsTSmZj1kw5{WJPQa3fYq5H`deHgpJfO$me6qE*}oqe|N9{v&0|_Z?42*wS6DD}
z6+iUI+?3Ejpa7mBHl8*Bit1X?+K6O3fwXBOkVG@l_N`G&PW3XY_;li<Q1<}tA$9*E
zZT$gDm&1tW&da`I)g~g)+Lx2^$`}9J7wGB_W?A+n)?R~~hYIO9s>fbH77iO|xWvCH
zP;oM)B{cx#r`Gc(D`YzeOVwSLLqqItl4!nGSffKPN2|R32j!c5Sksgy(takxZAAcd
z<r)nR9J~sSD4<QGRwYo;)vO<2v;H=9_{+o92k=ivkN>^G``;^K_pWYFA6dPmcCyD5
zn7&`SE5ZD+%A@%>I4Q%t%%S!dtxCYZ-YWgqW@bcOB&!2)`wiJ@(kg)Pe;TgepO2`q
z$8jB+Uesb6)BuQ%fdO2NPKCaerOP5L{V)4>T^s?Mr9tz)zlU-GQsa+)ZNSW0@1qOD
zX;05`e*Dhi$^6-m)gFdxlO|{VSwAzP4Mx>^n$ENhW~B+<0B#CR0DV0~^mO{(<hS!A
z7(?uY^v@WLd?D0qWmE;mk9#1o!u=P!qNK{3mJN?B>q|nj(6GDvaXF>8SQkOGnYM?*
z()AgewvP)yd@6c~5oT_IIAvin`V4(&h{Pl|H*Iu$x^1#O%bJZS2$BPmVdN&4&CdE7
zip~hF4;GzX&7Hf+A%nI<cH@K0Hx_u$?(hlez_1NBVuBW?Mf2PADSz#ZS}%#AO9NUw
zVpq<#fD`HVeP_0+?Dl%A=&RwH98eA!UQU+uzl^<UKOqEqAZl@>e5}Hs+f_pEnNGt5
zfsz{6l!JQmZ;AmNkWflTN0)Fc<o`s)X<?{P7;u8Q6h`;A>2;7f=&4>3y-*5M%bo~|
zW9KPRx%0X6=JM+nA3P{s21mzvz3G%~HDr%B%ugm(dGWutV%zJ?5%kK3Z^r!ZiOpW7
z441KV&7meUGf5o&;xHuZ=hLm!1j0W_C>Du0lpj&CNFeu#4opFy{#ys=Xq+}6dtF}s
zyb_*0#cDK1sES-}1C}gSdmg{Z(ziizCIGLccE#YMTO`u0^(LoxD#S;A<^ZT)!sI&g
zzoi-n3G5@2iB^=7c*3meCtVDx4GvnFm}T>FO~Bs59kl;hdlR#UX|4Q5_Oc_v%))1}
zdcvk0pw<=2-4ix}_q3^3$i^lg9r;^UQp+nxaeYVp&j4mg?t8h5M<bS_c(&`&%Ak~7
zx$pj3pWalb-0|t@rOggiO`oqtH*WW5AeJ>EG20Oq1|ZK#Pzbbn9dyy;AlDhQr>hu)
zzV7>P(JxHeM!@|2gjA}x=19)X7*UR$XB$%k>E<|~7j2hIL*LF7X4*c4Th?XSqJgEv
z9QE4bIfFtR$zvOh`zxm3d++SS+V7sUOF<lGWcGIdB2FrPNc_EmIt@#>CpU-3hm`oD
zou?R2hj>+n_7SU4<$_=1iPThC1f9lZ0lx#$Zvh)oj8s_sL2zit?W}e5v&5J<GIp!G
zP8W|l26fp0wdgu*+5ycJ{+;h_`Pb*v%Q23`OF&xZ1W5ddc94PdxM1fln%7(E_9)`I
zr%iE$aufY6r+!U3ptp<G@%Ceh1HmdCiAL^=sEs1ZyNu^RQpc&CD4QGV-2BYSb+WJM
zIU~^Z#h_v8B?tkMsZQU&HWlxF!#&>}D3ObKSyTBRCe@>7FEV&dS<e=e-S|M>MObYe
ziry#Yw_r4di?h2L@QAmsg-_}kF5oxMNZ+IVG<wqxnQm|~MxcqI45%U<2NKO{cPqZz
zi*FiEpOm8?0C|i2Xc!ywe>220bG~EeW)1E4?K`|uN8#ZJy6vXq>g{sq&Hh_Kx5i2W
z)4kKb#O)q4!-Ycx#@&8%AjVYjxTeMqM%0TtQYgr_NBL8<r*__|SK0XFOu^FPqSiY1
zg679i{!o@^jWCR@aKA?_E2mSp-(-`|h<+q=&xp!VYrCm~u2FL7kIeS7wm;2rp~oB9
zo4MoO)4tEz!^Dyvu134)0*ugVs6%9<$CYA=gzJBs+9yWZcAS(4Ug7HL(D4T=kD+58
z?raM8M)ag6Q7o$(9|}33*GBLHM>6pwLq<_l%77pLU7}G|z<M81zP`GXm;2~B#cCxb
z?bM1uGj~h)1KpxdoU*1~i0m!3iS6BJ3yL~^oG-PhsdF(5U?s&>dXN0zW8Yx0h<u5q
zK|cu}+Gx-Gt6u(W5H&X47RR9F_I|z#gCy#^*2q@GIn`H$qqX>(CF~#>y?@(;hL8xg
ztKWO2|3kaA^X|q0U4HY!7s<9u>Kdl;l)lFk9d#E$79r>QdQy_ke*LB>NPskGmQo3{
zlzeEJ<S8$AKAi7rFkl|uu#Xqd5cDv#+9dDg+;*-*E^*ghAB)T7J*znr_Y7U~{(c4Y
zyGEL4=|+b><CGd6nmY+pHj)B}&?_{WV5wFoAevLZYOh87B9(@s!RT*RV{g*dzod)K
zUGMW$tV62tq<dgL>j#szCqB?mJ^|(2>7^F^FuU3?pVO{T?Y0`V8QvXLBZ%eg6y|sN
zCQ5GOLbH#yA}T0HyF#G~eg5xX@5^-IBpN`5$3LFqeVgg!{4Qq`y4IUmnVN`poj8)8
zdyY_bdxSZ`>1`C+6^&b>C`$QtW6;8-9$f`KGf}G&fKPZf@TMs5&>15Iw#IXD^d$m6
z#~a@88In_b;;vMu9E95r^ryzR2o9@OR&pLAN6h4ZcBK9881*(VYAC)<qMufPCTPU`
zIlsc$(Gqr{Ta`%ZM3T+7J=!Gi(FIWkj#u@;^at4WF~cYVFPr%mybH)+!mt(|D^7P#
zyvEHSTz!<|-;*;I*-W%^Uf~Y7eJ}QB$4E!<w_g5P$?G^B6)SN8$~<b`u0}w4QilbJ
zLSzA3Y#y1O>34ywqSW2GjVPsv`G~koKrP*^>HQs3M8wRQiA`IwB;I=mH!|y(EmR%5
zWj5P*PQxneB-GM~-LgJOyTOd{5R*E4jkm?IJknPT)*;d;zBDaQmPcQUjqqF31+{;0
zceVo=bjyo}0la#%j}C2v#%%olrn=@TFs02H^}omku(eN1ecbWgorl}0Z6_B(rIA*!
z`(Tp}=oc{=A&Q4xhRcWPF>JhPYayyxWhvc*8uLi=_%P`cVsMNSqCz6|nNu_-qsv}K
z=LmJ~G2%FG&mpb;sR$j;;jfg5Q>+ns^%<qHdWiZ3xg|}_e?Q3rmj9sGA?Y~W6V`ZD
z&Jh;G48|D_5<~m4=%}Uc3LD`f7eZ|_efW#Cuy7uSxI+A;`!#K6p;E&db9)KMI+TLm
zXR(XFap~0BKi-?BZZP$-V1j-|Q3@`-<T6|3&E}o3UAfby#S2id0d|=jxA5^=$COOD
zuxJbK{bux%Dc8h8{O`4WWIykU#pn*IIKChGH=ZFYqbGeG6_PRW31;Q8T}0o=E1s?D
zXYUtUy_?JBrRvY}yP4X3QzMgOHpf8xp;L7G$<?vY<VnB$sw9g^eeAy&jwC_qMAI+c
zBE0pK=fQK#)jwx&Srcxg`PvHX&T@e$X@*#!*_%5a7t)MZ&MSD8$*uHp*L>*#^r*i5
zJ{*Vkw+J{WBW2VAmGbVDTYk<#_s1O-*9bZST2L@nBP>?iax0a6wo}rc!YeQn9rY@^
zE<dOVjDMMlZWSV!F#5jtvb}Qg{5BHz?Z@rAk#?F+$1=jmHMN>>XOBaWyL#rX6@@x9
zYg{&{&zaw$&ujek+L=j;3+w4Dqq7AUq;tZ^-<J-><V5U;IuVQ_i6WP%jg-?qg=VH{
zc8+5N66JeZcM$H*rQ?agL*?!tHZ{{q!LHDQ*XNkrql$0)5h7QP6zP`9o~v{20fMF-
ztQup<XJ$rAT&{z^L1v2(mmy)}Y8CfyWRXsVLEOA`9nR<eyoy`hAwlOe<@AV`pQ3Z$
zR#8lxGAN5M)xb|HKS8sSBgM&OYYDbgl+Kpu!1Sgo_$ZykaxF3T0kL)1>hYFhn&#=%
zkALwS$uBpi0&=p-Uv0W*1)*;zHYiG#8z{XuM{z%t&2boJVFJR0MO~eiA6dtlY=z3k
z1MBrP6ao&S4VJw9rg9ZK?8J?Tq+Tgzg)!ka8|{?WG7QN=Zu5}`$al0qq)3D6SW-}K
z7f=1dkqLw$-)hYv)9ZTT^bhN)t8%XGs`Z9_)IsgDd%=L@93!eaSu^`#6OTrUEOmrM
zy>cJ-wO!EBUC8A!*+oKdvRsrB>WGJ_xtX`zwZf1A4~GVG6HuDQ$;Jt+RZDZp^|I9R
z+BFQNUX7IE{jEm9sAGI)Bt76&Bkozcun+?kc>X(K4zcd=Amg9op%5$V6hAd@JInDG
z-=jNT(+KpiVpV>Sjy`;viGO?Z;k*huJWim9{GF78>ce{nhZH?cG~+qJk#2b+PK_Y-
z*+PqY_3Q~>Mk|jo593T$z^>!}dVOUV;PX7qf@wKk0&S3rfG?9xIg|EHYQZW;+M4r%
zOFf&8L7!ssSD(7N<_@cytDE>=&tY4tb=-bv${UWLqQmNP;O?ftd=}`5gg;yKw{1BZ
z4vZJ1PcPTmT9k~eJ0JNla+>U|UHc6dxa4+S0zGY=_XpWhW}1x3gz#98<DPgv4bxY|
z33ihf!p9XA7VZ54JA7_C8eY{stRK1CZJ}Y~g4oRm=d=>>#+7DBYSHNnk*c~ty>N;+
z@D)lZixTo&jZ0x14&}<yvHDfJyx|vcjO87S{HAfiE`S=Vx9kN98Vn*hwa=wlRr}sM
zlH$Nij2~rI8g07j41^OjUbr>)pvoi7D?Ao8>>W{Th#5{<UdHYxgQdTI{1T_6WbaoV
z`kuSz&L0|h-@UZaX4~&~*Hli}adEM25}DLep4eQ!R>;NH6~+ce*>^xuj#iw7uy`hu
zhvAcs8Kx4Ds3^4P9ahdO!-xdB<-bMv#Mdrv@>paWL?uEqS*2u7OD7>Vo5MaV5)+7X
z7F<@z`%#>xCG>hs$@eN_W}YT3@E%z@yxQB-4N715u`zDpnee)^9Z2sHVcm+E-v*`N
zo)$#1bg8(@EA(iE-fBO1iPC6`jq7k2z)C|FHDi`2@jjxLp0eAo%&Y~!xmQre1hQs_
zokp7UvCfhcYl7HCThHtE-Kvu!_IzUHuwT%8*R|fe0s+4Pb2MYfZO#_wiDfrWfcr#(
z!=z1p#js)z3J@A0W*Df2YYEjRIbfMe1}xvgwc=dF(-%j^^7WrO{eb&cSLKl50>Ft2
z2pXfT3McFB;MayC`AmV?AUbc%lF6!^;z9#JIcAQdc2y!QLwTk*f2k<|Gw78O!n6>%
zB0R;meYGd$<G&CXa~(mVQROuP`5Aw`UHeUnFog*tv-*S<ZfGJP!4>FvELIsQs+Q%z
z2Ud!gpyM+a<hjg&uQxL@Rc`HDdzYW{X((4Fk{keB>su$+Ww_-I$JPPJ<4h2zN&Ojv
zX}EXiCH!^mq)q1&nBqOO?LeT<p`)fImpe-d&$r-V5-zl+_4J@o`}l=;6a4XSNdXdz
zkk^b>c_Ck2jQI;yDll(OXps+uVML>I^COx)Zu`wXy@E8#Vd6v}#aJf_+e=RbeKY#i
z)^JMK=h{k3{GfH|iQhmL`3PDQ3|~4GbphY32pPSLMWbRszxY94mDGQ;FC!g5R_0IO
z;WF?)u`T2mw+MRuNq`ov&#Us?m+%7y%KzpHH39i+2h6Z5kM<3F-PQ;9$$Wesz7ix1
z++-?wAECDoz6yPmSVS|Q!O2FeM-`n4TT50j7(Tc44jFrDWmBvTFVMeSMF(rD^`)O(
zM8K>!+DM*+n)A1JPVM+@3go{Qv}anA2sdD8p9UirU+GWCuFV(vqu)$4WfAzXs&Xy;
z6eB7jL`#*HBTLkHZ{uH7*MjyV(LC6c0Nw4A?2{jhOQUAU)cF!ByaLXgZU7st$rE(Y
zoaMh!Ng<=DiS!+}1X(DLkqdguhK7zsYUCw3Ln#T#k#<YErIUH-KEdmOVzNbA=HVrL
zScydHdmbyR)hx}1Cdc)MGY^1>sGtwgW9v;_N2Hs++PTsS1^Q<aZIgge__0tz61%T@
zhN{of0aL%+jA#jIv9g-DDJiNm_(=^BC$jaj5qe+JN5QA{>JmgCPpBkfDDKgm)Fxg?
z(uQ}X{xh(rcFp&eY7A+~qE5({_N{#QzaxLQwoh!1g0XlqV<~{u=eULQBXxV@p3cUF
z2%BpcZ&cE;j^Du9S@XRD_U3a=?;CAVUUqfA3|B#UMNNxd{-?$-vTz*8#M82`o^Oq6
zXZ4YoTb-mc_B<}coSkF#SZN`j3wbX5W3s$7D*7D{hu`et$qRcOGeU%1q920wm;L1M
zBt>1q-n4U4uFhs@-zk9YbI%E&;IajO?xRycy40#uf&GzVJSybT*!c_p>51<hKMmC>
zpY5}Z&r(gN?Jz8a%$Fa||0H28UR@&;E?&z5+lM{f=XZt6?Pnsbi<c?T9-+2fdb7nN
z$o#sA<kKk3e^^uxdIooqq)kxX?%TBtM&B_xA(g2k0wyRxly7|bChY61?as4k+F6Dk
z`X}abSb$Jx7@XPzyTxChSg!n6WnQPatwht`d5elY387|Ca9jzjP?qVM6Dw2+7x6uu
zkoXo58L)u3Cxf6qT0qI~QaqCy@~v%m^$M7@q-<V&`XoX*2UFxjdGj>Amm%;acZph7
z(b>umdfgZAslI>Zmm@^nGt)i0T-26dX&Mn@Xf_-y%5J?pFl@vFa?&b!9hjgVNPR9w
zF7BCY|9@6gU_aCONE`V{y$QY<T2_*{+vIxfWqkI-rShkTL%1u4SF9<(PeDK2mMeQE
zIv|OV`IPBBRH(%{<X^oL0^GpuptXfZDlq>h$2jRvSk!d&Yr^iBTNCdUPCthHtb6gk
z)SPYRiiO$+fb}tjYCaAMn=^+^^54%dF81ZN5*;vP-{iiLkNh{Wg2UB!vcE@ev5C~w
zB?Wg1_|;?J;<ZLgS9y2Ts$Fz90klBwX!<>JE}PzB7dczD_-Crym(&uyTWk`45kQOq
z%6|X}oG!SK)iwb*%-F(&ZI${Ie@cky+Qz5Q7%e~E;R%iHxiNm+kWQzCzP{5jf*!4+
z3s=vG$9c#|`cYgh8X-GjjQtM|y8UXz<TF&)M>n^+P6jLxa2C;9Zbi%+`gj@WjZAJI
z=>LJ?VHvbmsN{BBgiQG!7CiB18<B5XyAys2SP4V~^Jg_ReqYxD{t9C5{)1uTq{!n@
zz8)vp$QnxYPw_M?Sz8~ad@T`JU9y9&W$5+pO|BW?&+C-@22NLXt`JLZ5=Oz|U2}nt
zu05(dUP}Hub2qf2UfmtZ5{R~@q4HplDi{9KdX(A%uYeRkb<uMCMK&Q6%As+rRK4>P
zn|0E1NqOxxBZ>c)HpRKqchl8TA-OWe))e9iYmqM$DETr;EK3)54=FQU)Uv7{?Ks$t
z{Aidg09zt>0j{d<2_jTs2C@p8Q|w8^<XeQeK-w|lz-xyj>_1LL_GFiA3Um`{qzzT3
z*c1ug!NO$@O6{Hg(CFJ<H9Jzlr|y{<jD&y%YRNjh=uhwVxJ=%52DUhPQ+Q`pL^2{&
zf=&Cc*XQsnV#&vGhYntnXlUmVVh5`e+V;{eqwMi(^47wK5|sq+2@7+LT7Fp3j0o*`
zM}CQXVJvFQ3s;B1H)74<)X;r2@uQEYrvf`s992Ns)}BNhj3qO0_Iem0)R=15|I1xB
zqjIl{l*u;z(ZySf`%o(i`HrScdg*BwJ>j7h(IflP|GJ&OZ9mOz_n#%J-}rT~McAIO
z@~-*K(+~4Uk-N<?+>@`Kj$MVCYh&`+wdy=W1<#MPrOIHnmiFg@n5^-SMown5SuUhO
z4Hl9>`Joaqq0i}*iU$LfJUB9ekrn$>^-jf16d&nr(hP5AA;q|UjZaI-UqC(*797FK
zt^7)~NSkBqu&(sjhP0pguP8;#q&yC%1`J2uz67#?vyBzAirbGPA$5HH78PZ6oehf7
zuLoJYbRA~_z7c97=C*@-u;BhUyUc<QOYp7??n}RUu@*nNxtrba6&+DzEGgjmgW)qV
z^llee1VpB6BYMYg%%(Dy-2`UVGS1MFkqYxpDXGR@$5+t~vdP|44Y?5432+14$=pzP
zZTR!EVG=Ld4dTEc`WQit2`nNcRE8Wkgw7G1PL+ti*b3X}*W_oltRb8Vj;8SE`q)XC
zldpXD2<26|4jHC0GuH~+hrf0bu-`tqs%0xE!tV(9GqQ2JhY((x13yub3G_T{RWp(Q
zuLl{pBxQhFX?au8#7X%V(067Vm`IVeYCCo!QuxYkS@JW!4~-uma7b*{gh<gF9vA76
z_0ZGnyaFP;Ls=gV^8;M|h@EVnkO+kRblL3q-z%`eD<=5Wb1}21*(hZCcmq)}pqG<W
zmneZJNx_r848M;9Djz*PxIX`#2YasZJ2`VBzy5d*l<UZ*;i&4S0T@9PS0Iy};3ub8
zyY=+otFah~gMk*_&Emm0Sn!}A`8{y~yJ4AaK7)YgM388&Lk@z-i}i0`QVy~ltwc+t
zjD~9!0i&1SKIJpG#JkPSfBtxM-ow`^g6~hElec%rqXS+KC6q=oLT<wFc{LED)QV9N
zYm4DiyPCtAHeKx4eM-a@U5J|@rL-YIb4k1t`xWlj&_Tk*<WOymdh*S4n^;uxrTG4>
zyGOSTGibW<0oY=3en9#10`tZzuyA$##+Z&b+iwoBhv*dq{Zv{G?50o8uSZ{dIW&lA
z^hmyD!h~MHoF%oBy!)r%{!$xth*qrgvukt{#6xxpGy$yW!I&5vWcT|MH%-er>h#^-
zus%Xe{jcMjuC#ueiG*Tw^8s|pRXRy41Ht@Gc@HvK<=Y|z8DvV9I2e@Czdbs>puG9<
zEYXkAOqFN=>$aCy;>p!S1}r%Dc_FJto^5@y!@^Qb?@&$aUJL$y^X2#{_Qv*8$r;24
znMZ(!t&lzUnA<c$vQaN}E7!?WHhAf&1xwoqV4dPJK0t3?*#N+)Yp@@E-NuN39dX7Z
zkhlU}0erT)XNeX{y(lPS9DRNe%{C(`&|4#dn+NhBWz~9e4^Zl!TUVLxj~@VdF>p#$
zgdc*Z9f>qg4ay4~#dYlieNGrTrrqCi7>8yQJwWU+h9LKZmY^$p*%I&y2b>*5wi*+@
zH74iItn(TAUN&SUS3oif<e>0ubMHvN1c}hUM080vSlb{I7Hdm3k@ZyzAIP>F#LfQV
z5nJ!(T<k1YDO0(xh2OTh-I^g*2PT*U<qvSRJhH_-aA*aL5C&>yOad9KY4ZV_a}Ki5
zQ-R4vJ_*K5ZoWDDSL$Z{uwcaZGJ+%`gSPdY=?_tzkLw}>@heIZ2+u$V>Qh-e{RbFi
z?8Zx>V8VQL=v>$$d;ia}0uzwl$+Z{0>f6teyk-7g6D9?gOCVuF@+9{Zh+g0uqSI*`
z=7FUU1Q|W{0pgEEaV8^0o%N!92u=5+W84GZ!eg2|t#kD<Cgr_|5bgngL<q*X)9J>B
zO&AP6le7OV3-By8Lv$ZQG2|gK>$$w#1nry1gP@d3V3dG?nPgusqD3?iUwqbRpUbFW
zpHI_zf9)QMP^(Kp0pkUB#lQKqG=XFyl%0vM$<dFOK>e8h-LA0^A?OxU$Eo_nz|B;R
zk#_=X$0*jmV^ei@TMgXCAXOLHO$0V8pr{I*6@EcZg`Er~Nng?jVzvy6gtGVl{d=N~
zLTwg1YGTzat<}gTL|2-DW=8nmroD6yx|&|h;sCQYel~rsw`@4T0MG!)p29~ga~ofr
zAk?93S-`0<eyqlhVnr4>4h!H^X777%Hy{c=h-=oax6VsoJ|JSi3#xw!2};W05c97C
zv>T9EMI?4nIM+%h9I+n`^2t@ExXB7j=n;3_TzPz&1myeJ(Wx<Ux8~B-IrCaCJ6Nz#
z$6aWV_!Ium)rG>8mvmf1%Zt5Z)QBg^Af6QJ=AY>HE6n3wyMZM!DxUOdo^qkgP{NFh
zwv^Iff&<IgusIkFYWBXs?kbappI%qW2mQr=2Nex5+w6MU=+_|5XY521n$FcA2xg3+
z!+xZRzo=<Zc%fPt$V>J<#Nvq&^wPd&mbvdL7v!{M1rXI0G^#`lqA^)4%a`+qUkyQj
z5D<b~g!QJ)B==<LjQNmPE;x76#H6h-LwPa5czyyNmE^%V=``%W35Iei&OlG6UC}f7
zPWmpcpV%XpZ@ta+V$RkhK<s94WK~^48^a-Qg-@rCDgd23rJK#JbU~~uPU)kqDB{zR
z5Sx5pF1->_VG#i#IdQml?%y-%6#(N>x*dM4`mevs!Y{9Cj&b}J_FmoWlX(*DnnM;C
zVlP=IRUII1WL=J!P?f1<uhOwHp+Vwhz&Bt(LLGXNl;I?$m*lOUV+sbFLeedlT4HdF
z7kt4w*jLK8jaWN)1K2wX^r2_$8j<Ug+Qg+ys#%79S~)E?Gs)p&&z@N!CVa9Scn#2R
zJzGjf1wGZ2UDL75Ha2X=z-F*RO33OnvnG$2acP+Uj-1HuLBTr1etjPF!M)7<b#^IG
z+Af=h@xTAH&9K0~dw45_c(7<OgoOhLk1C5-ExLSywD}jbUd|cbzDKA$AbdcH!P}V4
z%hfaHB_L?;z=#WY{tPl$L~s#YO2AJ!70#)IQb1Kai$(^wuSWOf!+aq(EO#ET&B&(3
zSE((h64j{jP?vaKC;l6hb@q5=usaPv5f9I-9G3rN%nsf$fDYH7iP1<Ude^9ybxF;y
z>vyn3bw!H-Lr7#XKgy_NMoph@hAY*g2x>&8IF7#y?$cN0(qgx0zg={@m81vl8UJ_Z
zdMNu}^M0n26og*u=iD1%;n2ZDWbmb!e{oeQC)r9wz$OsTzw!~sAR7i9-UYeoY>D6t
zlm^f$uJ=oDrf<H<L>r(uW&juQ#A8+6F4mFqXSJoHo;tAY`5L~B*c9>p8L7SWn)c;B
zPlP}*AuWO&Ru9qt@Klgl?(KNYFd<xZX?v!lsA|E1uO*}}X@wM%VE{!y7nJUZy-V8&
zDSAEh>p5a>QX_uJsiBeIAb^z7VJTA;BG>k|_Ys1`pj{z_LE0xlkoFuz+iMYP#bDtx
zi22{I4{I`b`&#`8wKsp;kxY%*u8P<{{x*SABhT*Tqo2zYSmOxa7X4~N!Cg?ClZ<ek
z8Td~%yU0}|qdHZ`_fSyOJ|r2Zu+>QOeVTxjUcQp~iV+9sCS$OSyJ<2jC+&tPk9Wqe
zM{~+C6GDSNA#}2a7qo|k9;TUz3kFx1O`kk)7&u9DLmTQw^%!VJ?#Sm(|FaZ%y<7Tg
z5_rx;mLtk0Xlfvzoo0ZDd}pf;VnXCcxh!XIBKHnq7N%{at|lfHMcS2tKTb(Na54Jk
zC2D}{rA->E&ls0}C8TBRp)Afee8zKDPZz$xqUu)wZAo^$JyW}zMNC~?#OOSAat?73
zW0J(c(1T0^HcP7rq!6>BZ|;a8a#sFV^BcsxTko^DK%W6qMAjN{ekQ3Bm6~@c0w)5;
zIZwmcp^{sXEoa8aMn}<SFAKCqy}nm*a{U5Z+5ie78S3gJ`>An<6sXEXJ>oct-+iZ_
znOdeO&?T=-SQ~a9dTl?$d%azjjIgpn)Bvt;7@DU7)<nNeSG3Cg%|YgYM36F=X5~8{
zXx1Y!j9JA_GTj&qN!aNT^aQZMrUKo&4A9%kQE?>0{u(2@E-H&b!pdd6iRH$pn~0>o
zmrMS9`QOT$SvF8`^QCqr=b5XBA~jbMMIVAha)@C4^l@oW_EUcVh(Te|)$_aox8o<9
zjzAd>7mrq)UCeLqQPIBB)Ek7G>qcRFNMg#u?EBn@q!`2cK$Y+A#VVNtSq%w6DX$aC
z0N<VdS@V}5!-_dg1GLcxTQ9vkn|@Lswr|mbimPW#CR~QF3=lC7gd?NBGz6v!%B6@B
zFHpsoBf-dgcm!Df1Hdbe4V#QN<9Mxas5pi;O%wV=EkY4jF3Jr4gm=tBT9bWQCW&wP
zsb*{ahXS($Qld9mZY*!I-B}&7oS7Xm-#e_fu1;&*Ry$^XX+8#+^C}<<tw;YpFPD3h
zByjW<-F9!b_5=ua4+0%ZyUT;c+d?3Qf;iz3u~ABE*C=0m3Xo%k7KR*%?o792Q+OBw
z&!;y8wh&&vpwDI|u$0?3&JUZmqY1ra>L$JJKG<z6@RF<-wQ;Jo-&7A$B!5qtiusl@
zMO(x2M#8tP;0e6G;6$B9^t!3p>*R`Ry?kqTAU1t>2ve785VHo7w&no-*$Zcd4ZBz)
z-6zi+(Pn*x8=>!7Q_Ygr$GX|j&5~WjhOCi~Vp3x#VfIDabc4X$a}VY{=!meRR@Mii
zVuRqAW>U-I<~I%q4FiR97vSa)jWFbJ_nf$8P`u2$yS&Iou!AgOLBN=@fGU}f@JUj$
zu;3gMzj?nfy~|rQQ{3+KrG@fhqMIZNmZio?nic;mGv(BSgKNvDn*AD0FLNqpI!(6_
z2JHUcvaz);XeZ^*=-u&4`J?~;f%b%nY@K@W+Q7cDkYMGztTr1=p!C{V-RdV7O4y!F
zX$`Hi#A_%_+Vmx%9x{TJ`yO#qXjt;AzLujNp<9^HO2*TGP~n2rPMAL?d81xAmcsyK
zXgUU*i)9Yn%HNP>XY$hD!;BYf%}4jO%}1MC;tKd1D+^9sp-cq4O_t^TB(R5YqelBi
zzsBgstj0>13k}Dk!gqihQ*qyRImk6tIlxx*yz|fmtWYE%3NW}ilCROLjJ$?-gigk2
z+i`%a7YENyL<5IPEHZ1ndHwTT96Ekkcb9p^xNPo+-?ugS#!>f6(O5LrXy=G*b;d_V
zSWps%(P2EzSesNv`lI_s=Df~EI=pT5DW^+U3blki8(H)2nQTQts!IW<8oH4&%iuUA
zq(YoYNRh6oyZEk<@axRdeKas+%v-a+?+gMx2Z?t={OJ&4wcm<@A;Ww)fTE~9CR}VH
zo#c#8d6f7XH(2ok{h)_q>>FOc-eb`Q-N2@gPo*mdmH2e0o%OO?f-Zrnc`JQ>gR6am
zB)AfygBT{lQ{_o2H#0_<^8SF`pHcuwJWX~$n5)C_6BJ%D`yNax0P0m%W$j*^+7M|5
zc3xIm*u^`>H%Z0R8=E4b_BNuvdT!SaMVw&T>v%aeZO8c#a~SCT@ZY8fW@KqV(s=Ds
zkRdVv1XFU{HhI=jfZ2C@1-Op31CuNQ2;xeaeWH(`iXm~UDsOvx<Bqw1Z`brj_W*7|
zqKD#AERlsHy@R;PqHQ@ZOxFy?o)KDK3+^;thas1PoeJ0+TSZ}bl5XIrZq?3vCDSL;
z6QMj=u79I_uHxCIh@pa-yY!|g(8T0(I|o!nwx>DlGdGjNqHa%u)bGCPgXqUhO{*CW
zyZy$0-t~95b24S?adI1)?}vs$5O(TJib*NBR6-xQ=opweK&;4?gDxL0Wx3MWp7oGX
zCEdPpuaxk@WmxecV`XaG$#UE8Gfn@}*`fGNVd(zoFaOsZf&0#n1j&~~oUA28cb9h6
zv)hN_6<~$0FaujI_ZZ^MO_^ix>F^{gK=T^_Z1#v=g_DPp6CxLlmkI!~9&qd+LOya2
zz#$5`ITxHB+Z!tvzJu1~6nHBsi24S&*{6{k6Xwj4ovQx1`EC8MPMchKrrO`b7+BCz
zO9Vl#*1@V@ikOs_EjBHvu1>~E9K?xJwAKGBr~mW4$!FDrwtLA_m-bM!ws83J*0PS~
zXu#WO>U*rA5W!WdJ}nn!2XP&_(T)#h-CkQ)^Ur|biLJ5p1&20(1Hh?HK7OuA<;0|v
z2ssT1V5?OCw}gc*m$#*4A3)%Q5j*_D*F}RN2Hf<bDT|k`v^E&MG-t59PI8(@qj@4y
zp`Is=&0d-fSU_nh?#jMIHJir<OXa=U=QJb*i~%Aro>o9dSTUMjos-ktNoQOEKa7~c
z06`aL=4tUe6hPJDG=LF1I|d;uP>=4I$!G?$(yQ+I^(lW5*=Twpz4%myw`$Kqz4Unx
z)?X)hU<S78|Ac{g2COMG5)#u$;KO2*Z&q^y4m)NNpN`${Wi8h@PGyrxYA_yK)(i<d
z2-hONm!Tzm(YqxhTaA<Wgyb!+zmMIegsd_Qu}5)o`f(To+koIjd{rV9zfe#poO^V8
zs`JL3<_$O0M2KE>zvgkoA%|*0E5Vy2JPf@zE+Ku=(=c7r-b6aK;LCmNP!MM^*Mhkv
zHzU@ZCuQpwjxHstQx)q+%L1NguoQeKu><^`+KnsXe`Q06hIKN?14T#4&7i!_W;(Fp
zq6@(n(#Nm-mjYwv*kqMu!Lf<<p}>JG;F(G~uRpNCeTofYRAwq{GM==o4<@2^z>o}e
zec@tbW8JerE;iN7^W^^5K+|ik{*4)8<<ll<NZYYuu9yeyMl3;E3J8W?0jMZX$_x>Z
zgR@?x<L-~_WaM@JCogau-GE)9)0jA=f+}CwdvLPnbKKvfPHOY9rw}_uLV2uVzH&Qy
z^<a;0yjY*)W7h@c&8LMRY6cT0a<(<l`>lX^If#8d)CEAZ17AmBlc@rJTV-a&tzqsz
z_seH}99lELZ9FTT3mZyvH;0K5+Mh4x&x>ds0cah0PCVw~iP_1wCKs})eRhM?SY~kH
zvA?z5iIYY?D&QsAxH6=CLKO$pr^S<szH5)}7^uM5^Q89bB8!17CgERs25?KCG5Tm3
zj;Ndcz)gbJuVXk!7?C;wj4mbUwR$E8iJFtfas^|+m3rVIK%ZXV=yF>MnE7nQM=MW8
zb`s{SOu@Rhf)Z2YE!SG8D$aR?-(*%Wrc_`(Fm_&d#SkD*m|eYcN<=!*dkSMOG$_pb
z?8v+Oy+a|8G13-sAC(^PY*}ceBVd^kDOY}`PBgH83vzKMM@-3AwpmDUV#k#x%24nH
z-k!H-#p0p@T9^3b^ud=0xS^`RV6q)7lLw_b@NbVF_Nb_(?9#n=WX1+v<8J@sY)1!n
z`?+MxJObWrX>0LT9BNE|8xyZ%r#b{QM*qTOTHFkbTDW1VSfqYh@AncBaU(K1KHAK1
z=#oi?dC=J&1JHNFYL_J>0Z0W7#glG~RJ8(ace9~XrbpSjpE`kp4zn=aZr8wR3g(u#
zH;F2om6gu=EQtk;i4CmX^Yxcmc9ZHE!64mi*VUWo1%j*<wj5?b9uH?0Lo((3*FHTO
zA3V|!a=VT87Y~e^+y3ltip={v7BTfnTO&3u%Yrl>P4X?k<KZK1mHc^)$sZ45m37vm
z7?<sjee3UTf3l?TRq4r(29%?z#R!sP5;)*gG2A!79P<JT(IjqDb~M$}$b9Cw&EfL1
z-VZqt>|jvO-tp4KfzDBPhabW6JOQqGXv++-fGZyv&WOXQ2~>qCe@olAS0e6k*>Si4
zWZ`*_C+@BP&YUdh;PE&~GU!oe^Md_txM7KoLf~4Wun{|>N}$BE!+2tB4Bw+bBTx&D
zdC85CH3hWb<8gjo<7PcPI!Ws1+02|^#-Wk0Jx^iKsvoyS0T0I&zgh5hYW6mpy`mHi
zT)-Xer39Mn2_n~F!u_ZtsueqMVe55e(cxN6l|Ep`)(NuOBG2frG<|@yvQo3}A#24@
z7Ut)uAO3sv;H=o>vM@p@%5>gEfBWDcAr6YzPY}fg&51!#qg1BI0#SAd{xrBK@Yh%k
z16ya&6(zTQ+~Ym(u|bQl3j0CCY3W0qZjr-%*nR`^!JrNUey8OK<yWAXAaHZFI>e`u
zg*)Z2eG))dGuNlCgE$-OMSw{Lk1i0@V~oWScndvjWIAhQ;^orU9)14tNu3&wB5#}^
z9gH_S3zzP8xm^x@t}R&jK8yJwFdoHG`LCBt!PN4>;_riBzS%%!;L#ohd=2R6Vhmwe
z(FBLVUWoqz=%xL&Oa$7syOe8Fj867xlK=13c)reNIvA+jRo~om4@;>8!vB$X?dQK1
z)$T;jVp4@|ZTCBZ{Z@?hV9K0`;L0Xbdz?XMmQH-;=6#`(ml&4;ECtj=#}s;ZN8#3_
z5?QRTf5K68Pavv<WMI4Fj6H#xmb7vkH%Bg>r=i%|pDjs=Y|C7qj&1j1$*?nG5+Tih
zcP02(ea4`6dWTMzbGa0>%fn&PYUC)<|9v^;UTF6PNWsL&SNj(&Id#KABhX6J(~K_<
zzxk#{O}b_d$nG!GJy){*^ZVRt@$Q-W#nl2Zx$V!Ao*e@AG&8)$2qcA*H5b#=2LYkR
z_Je@_b>(cAsK0@&AJBM5_Vi0u^@?(MPt>gHxuCpc50y4x^bI?Wwoe*w(`@Nt*yDAc
zt$dv+<1dn#1GtI#bg2`W9v20oR%6*bitJ~W{v~x`8TAe5t!uBf2hQJS9>-=xQ3{8e
z%?3)pYn{~ZuzHsQWE0;LB~cj*zDiRqHtff!;Bb<;YyqIO9cTUF?85~UTd)VDUIF5M
zh3-nSv5|l4B<lKE$zu+697Wo=DY_btY;&qYQODq5vFo2PbXN^5$*VMD8Rm{NLM`&5
z0#Zvj*!8Awm90+OtLhyvQ|Q<^*9mz%NC!<(5dP>4$F(hc9Q9{8$La<fQq(Q4c~1Qn
zBk1RD6gLO*!+RDSe&78Sq0amdfq8(1#ct^;mtLvS8M)KhTd^L=0m4XLAk*Kn7FmV6
z{&F*1v3MF<6B_Xw?!BdX7cvl0F5KRaXR8CQv^lke8(9zr!N^*<EB-)`51j<=i&X|?
zK}K3=Op0Gind^xeTIZ-upSMESC#lA4$zE-5Y+uA2L~@2rpj2=G&;`>5FzJQ>R<Cv1
zhmy<joI*Yy3P&7Cwq!-<h5JBXBJz)nNQ)_peKvUl^Q*eUchujZH9+U|WI~C~vpAR*
z*(Tsjsn)S?z57RnR-Ix>1&|TYm>QWU0k&d2^xAJb^Htz22Y?wO#*Mu(LOeHr?zd~&
zYO6Ba{2vtUse)E^Eb(4pHYsSy1ODbpAC6NikjFt3f}AX`pgV!`7Jf{S-&}{#g<h3h
z+c@9Lh@5_PCT2xriUods?}kWFb$hC0d$8a@!z@mh_Q9kjc2vXUYdUkMA7S(%Po@2^
z6e9J;>B;*E+HMDj;9k?56oBDgLp}$6&}MxJbO_pYQ1*`1m-c_4GMeoondTwt8lq^e
z5usB5qpC+%+GWCJpMpjxcK{eXPSy?5?D9N7Dkd%66$29YNZIf{g<dl(V-56<ElvXT
zQx9gxueC!euHB8$*2SegPXE+dpb=t4LFwVw7uE0#I0+RLk;~N68#Na;D0ek_uE{QL
zwd$;oFb7t!rD<~*DfT_LYW)@-Q9ELlx%dCV$ydKGcB!7)1N~aptS-7d$FSFBkkkU}
z>38~8e;R7opMNg6kmtc7ib(AS_T(mBfyduG`K~rXb|x!`-<Tyq1*j#k%_dj+0MEao
zialoQf$U>&iJ1gq928>i@1FyE38$OcD29aM+tg-0TiBj!Dv#9?BY}BsU7w%~>`0Px
ziy_{2tReEqufxSIHqU}|AV0Cw%Y=nsD}i?pAr(Fxd_Mga?lXW0uRmSMLTs`iYZ)Y`
zkcWhP2w86-`YSjg>iW0TN!NfN6{I&hV%V{U0x2hL_+GQde)?f%=SOKX=_DQ$0
zz+rde>Gn^M5_1|%-wifFL0YxDPl@TotB2$scuPic(|cbmLt1VYfmKPTfDte%JjtYO
z`9H$mGAgU6YZpcl1W`c*>244Zq`SMjk#3M~kdj6^Zb~`@K{^HL6p)gVmX>ZfYopJ5
zp7)GzjPI}9d%5<iJ=dI9Tx96K(bk?{=o=1r2tNFUETVC|xYtMKhe2=Do5DeE-I%>F
z!o;<=u}X)8(_Kc_Y#Dg^kej9Pl$GfyF@1m6%vGIrwogSQe(V}e{$ifNWjM;6p>#%@
zMIeNwfBBJi$M?Z9(UL?1^c=tS9(h<(3gr`e20xi->G|kE|F;{S^SiF|k~YoErUAA-
z*dJD@<T!lnT!leO9n^S!-y=#iuM{6(i4j56KN{3xLA@UFf0fRXmGNZ+c-q8r%fU1l
zWzuljZP3{?se1`>ag3@Mj3RxU6u0JcJT<U5<C%ONrz=@z#(LlEF!&`4;LOd1X`8e)
z8|A(_DejGw{TA52n;&d13lSx9#cGi?zjVKJZ(Nv;`)QaC|B&>9s*vfUnepp4yvCJB
zXXoGifPNIyM+ZXO6zcoW29qfV5YRpyfdI^9v{t|Mv;Mi9_ohdb0kD;6@w07I$B?=n
zsw+`1PyT`du25hz1{P{H!_EWlk-*rzVMWv+8^VvH*nVr2L1`0F=|Lzdl7aV{va}zN
zH;R>#fe%PS%$}d6f$H)55BP0<e*&XlK^2x2iT)_@up`=ScS(5|u_-oH-st=9`$}%j
z;5u)y7D?LCZxxOe?Ci=>Yvc}=c3TM7(1w;^Srz5i^w+Ko-Ogx2LQphK`>L=k+m{24
znrS}IDycA>e@Y~ll3#J`+T8CEK5A#^+5WUyX3&$e=Fj#tcg=7zm=m6P#`CXwK2RO1
zv7G~634v&LG$wuv{|6Q>$VNq=er{YT_4|I|XqD-Xmc{qNEKSOZ$5@htuimORM(HV4
zgy7#wk(mXA03TG+&iygGUf<#uPcYvLApZthIdo^T!bp;);2n3*B*%akrmQfQZ@kKT
zoz;PuYg*O(s&n`*{i~3qRl4?dqdr&uZYE%D93kE!xVW6Er#*0<ibN7mxnuy2<y7Pw
zu}DHpCR`S4G-q{QkhFK@M~damDqFx;&|1SU2d2{2w$>8^Jb31_X|CFw(EIf`rOn*@
z{C-Ax<ic^Zp7o3UDG9kIxTm9m3pz3@Ury+N)boZF18$cSu!y%OX=FuGE=GSSR5#H*
z(x<0tn)*5o2j)FOV<|8Rw{MjKCGch*U?$u-(bGQ0qs68{kvm1FYO;a@zEHpt>KXgZ
z!VP;E^G9;dhCxwbk5LTie43xg&XV&FpDRFQXCC1Y-;>gTmr&0t&!Cix=&Ko(25X$x
zB-AA2?jQ&YFiqJuMA7r}BR3Jz1Gn4OSp)44PkmTG$v#~HYqX0=R>U1rgL3pkd=kcg
zWu*7q>>Ff2%Zz(lr1nw;<Gv%N!>G1UEEZVO<`#C`TRGDOAT#|w7jY>NeX$2!bVhba
z9;U!6-ybQQ_Svo+VgQm=MR?<r*F%;iD~!oMo!15g$5TwedutM!@oABjQg=W??pH5J
zD-ODef{jmi?M${ViF(>66MO!|$oGE>Ad1^-xy|sMkt_ou0HB`gLitdXg!t9G(gMsT
z%w?XO-=dw3iKNIBW&6+2^G<hI$l&($7h%jpC|h)8l+k!+5S9`N(vnDnb4LgLjE>gI
zTxY!_&8Ph4H<!TgliBV@Xu8Th-0N_V;r#lP-`@&7b_xuB8-*-fz;?4HMXSBI4_N8%
zc3m5M?LJBqRw`gn8jh_3{^WF59lU7@*mg}znb&0~kv0v@!k$Aj#ua<(nX3TZA8$gZ
z);FN(xH^*cp<mbg0$0#ut=^v5yUW_$XxicYV$04U2*3I7CBWXX+|JTF4u=~LnP4k&
z9(CUs;tVdqVomiKo6hbxhOVK3+n~ZTEkLkLCf?|%tdZF`D=3YnL`b1`LazGO_?V>i
z>OvYarllHpWOIt8Xz+a{7<HxJwH{r6nbYgg?vIx~BNzYsrp?WLYu(1-!2iY2DNf3V
z{`qb41;Pu>Kan%1Ian(l2!rYU6h>)hhTh8Q%B_wqmp6^MmotIeBT)pI{Rasp?JI?5
zd!8%%RN_5V8($m}@Xvu?0X~gRmBep7yC)Ly3mTiwcFh!`;aG7E$F@jmhoHK;{0*=Q
z4NiiLv>ILL*Pry0fQ#}QEbY&ol8sBUZa)6*Px?2o$vm%=$+HtEg|ZEROJ9-hp=FZ9
zMVY(1CXB)J%NOg5okIcPo7<Jc1>b1p7fP_DXK4B94}D)v;wZpIyF`zr-#7d=C=O`)
zDaxq1((?iMdr%;tV8ueDMxu2>{G*R|>`~WmDa_7c&r0XLJuVsIR3q^C<sM<tSZB1l
znN^4IgYt_I0y2@*r>KHX;y*9~O+}>pmw!wXM^Pm3ByG8zcQHIDhYcs2SVoV|_ZIr2
zu$K~LbI{db(FUWAIV_!2w4a*wTh6Y#qhH;VQvS(Na_gMsk7o0;dhl5H$Z2+DX3sia
z^UFHSUK~=GTlt+O9^BW_%h<PN#}EgKXEIL-BkW#K#F)D)(;eo_Lae1n3A8YxSX4qD
zY%g|vBvn5lgw^Twj7U${dT?>T3l&ak`%k|S8#Yuw>CwxCD6|c|pc(){0Yx{7Uc;aU
zd?!uzpi@lKH^|{}p4HC5+YFv6rJYW;U?q#e8gf+5El=wABeVE|5T9QKY`72&bdP`(
z(u~fCc6&KBe5H7FPJH&`@CbZhbiut*a7^tky9v~YNFzwNyw2Xf4OQ7>Ewt<MZfxk!
z;?@P4UxOD@gk5ALWXJndnP+;-wG-t@zyl?4La>(K`r+k7h4QxyH=dUzK^1aFT~RTx
z$;n5uM^VSo2+MT^0DvQs-A-tSLFWUY8jMY<2gFLa(XTIg?S4rIF2(D-)NbLaw$e*q
zdI|Rocz%*iV4rv1pFW%{SF;5Xb{sc@W%5r^yAi8!(kME=(t(V_;!@A89cvt4bO!No
zZ`z5xzGrk3c?d@FVHk9{xH*B=&u?~Uk?v{I=v4-~zi66URjH}^WcYB!0@T(lGC~2@
z^L=DqCH#zVlHk>CDL7Z|2WMj@(5kzfAPOBllK2UzoCB5QRuIVhxrI#J3)okD5L~(c
zjtVv*mVnLqe|3JMCV!YHOy!Puv^AkWHq>~nC8bPEEgPYct^Er|c+>i4pMz-kR?CIx
zXE&4VMn`>mUw=Xyt;g?##Xdebq=7T!xZM79-qj7W?hA&bXV#n`NnNp=nI!)HF-lyH
zI@ZfrP{yrHbB(C$qE>Aw3$w6tLlH86hBT(T`{Bo~0~h6^VI;~o;$rAqf^R`)$=2uS
z8`}<pZx5y5(C49Lc^*QohbdG}&#Lmw`zJm*u>6_F0z}+|;#{QXQHzo63oOGp#-L)r
z`2K-4V4Z4STv2d?Yx2XmaXG+RkDn7fF&kU^-8e5AkO_HU&UyVBORaj*tx+sv5$9gn
zT2(w=XxoBur8GyrHv+<)??lmFr)qgD2*6s2Wivde^7?dd<us>gnZ4x+OPTuBQPvN)
zEkRCOp9`a`&ylsh)0B-eIXnr#uN#SFqBVBCMJG1j&$d4{eA35DUG7oFy7)tvAl%CW
z21}e80=p}Bmc0Ep53Ci>oJUVT@029Yky!cOkiVU$c|wK1l}$A7thR@T!_hkY^g=&p
zIj2r+8mETk8lR)QyJnbal4wc9A`Jf=Q4J;1YSVpE_aG_f@_88b?A%_<_XSQTzkYLe
zn)pLC&V$6m_xP(d4)dXM<@3#>5sFI42QLBzlIkG)-sUpWp!IL@2>kdy3nVPGTrj3~
ztArAjn3A%VpGaewzInUw0%>8G@-%)=r@7w#dgrd&%~8Ggolo|~<6m_s2vMoi`K3V+
z_AB8}lV5#IC2hbml`Ldrqfa<8@VU%nXs1HOWy8@lH8voho$ZO_SP_{eHy&H>i~Z>Q
z7khjZ{5Qu|Z^LbGkR;lt%10l`znoEs=+Nj+9~-UO`ab{bg|eh3mGdS2ytk51$R=(x
zA;(0=Gc<{@wFQ^H?>k?<?GohCTyr=(+0<t%NaZ&QenT{fRNRRQ-j2O?eHw&0N{8#`
zTx+)wBNp{}_qlvzF22cg_8;X*rV*o{{Q2sTElWH75sE@vWjFVRRgVh6#TY$pG=Xty
zbdW!>g-ibnQbw)}Nrc`E=|?zJlc?QWfYD*1!9jWl>gbuDYG1(>2a*U?FJoek9tn!X
z(Nt3X7&mfg7CT@A3=skkK6zO5?BQz*<7yT`mlN&IdkT@L(;xMS<l@C^2jflnn~5xS
z;FCDARL;u9m2MS;IrQeMyR4VXP&3(Q1W$VBqv(Y+h}K!1CuuoxudK;D0}g&`v~D)|
zWxZ5alYTDp@C{A~!pUh@#%bQL_;cZPWP^)AN1wW<9+C5pb;x*aUoo_G^a8{9qvc%5
zqC?iKf&dWR^+)1gmpZlN1RvUgDzvZNjhJE-deUbmsfX>|)E1(=o;)#-=9X-(dwkPq
z?roHc->~+B_IRRGoZA(ETAqNQJXNF_ylN67qs!EzXz~`Dll*xl<3huM4Gg%5-F!AP
zExcz^oAzBwy&qO=`_xO(E{xu`|3DmiFIy?9COwAb`|--@kxvSFxyHDcm4b$PGv`7p
zy?B83^q>By=*RBghN!bp($tN!amM1uM;{QIyQuZz(KzDf{h|`lt7!1K{usDbZ1xbE
z_r=xsBo?%~H4!oSsC-un7e~#L=j?Lux<}vh<oey^Jq*HPa)$Y4D1l<k*3}5(`iQ+~
zSvD%yBK?|eJMZN7^9*?U`w(pmhlWv4GEA(>DWy|dxr@qTlBX$APXSi>0$_ob(yJyA
zHL06|QPWB|giC?3IJ3yq2kBYsBaIy<Ss<FU<shSc@bS9yS@)p%iuDw^wp$*JR<z(j
z-{USKg6r1!VP*1ZK@lyNjf}xc-p}4F?I<rBUlXfXOioT&tRYFY(a~~J$o^JZr+UI+
z$4i;!n!AL7y_ef>+ffOu)RaJ=@BviKfwlUGVW;t?^w_9Vw__*Wm}#H7K0+DeDz!ly
zHpS!=#hVYJ7a&qJ=^f1@X#<McK0zebp9v%|Gl;C=@8=4wup4~RB^PR3Dq@*VVT6Q6
zV&k>#8ppkmNEjHZiqjV^K^j~34Bk+_Fr3jJ<idjPr%>-On1?6u+NpSml1t2!%D_xn
zUD&K+=_{3BY}Y65g^ONR7;*DAm>5suD8h&}VwQ3Oibmt8M^)&A)PB$DE0vGfq>4Q(
zQ+&Pdfa^;ryWtRE6+gefY7AZpH6g#<Pt3YM)z0}w*HKhW{X3Z^Dy8hHOWT(b<|XWJ
zfm!#^UOH8b?OW2yf)w>SH_q;qy^-ubQlm$PR9r1rrr`scw>nygyhmCBFv`|fDUM7g
zufZ`-!3$iZuQfu*Wd%^U%#Eh-(5Sed=Vb>mMwRvi#>@(9oHiv*@uDs!Q1*O}qBiW5
zC(7MWJLzBip)cWlrI34e@qxF^v_|5_iCGF4jU?hS<@Vcq76dGGQJ9?W587>BZl4?O
zYC()a){DLdF$6WI@ZzO6av)W9{kd^HflgmfPrFh@EW+cbE3k&Yy(E0I%+|I~M_*Qm
zkrhrDrh)kfXx%e-mX*0s&nr|oP<;8&9k84NM~-5YBU8;g{5l&5n#d~akaE12qq{hF
zZXP?HVr2vb2MQXHa)WbZ<mI=|?q!%>9mZ-t(SpgKH>{CJ9JC3cS&-3nvs^o~c->@Z
zyc0<KWnr$a>Dt=VVivwpY?icgLdGr;XBx%a&g*A|@hM{9#@4&=GKc8YIBOP~idG>s
zIHS~j8Sm+70A+sU0i=~IozRjcV*Qp-v0uES_3V*yM<{VzkKdR2?H?R!k4nU82=0jU
zxmI-X?Cr*85nmzgfyXZ<Qh3dxVqTR7?D_|VV?4cX{)S!HdL!rletXls=iSbH^9=q&
zJ)*O(0yNmOdb##W<VMgU6o~`DvmZtGm{>ND;A15~VQbeF&{0qv9%563DFHmP+q0sT
zx+f1T+p;^EfB6Lq``%Xh6H4+l1rc6-e$8LzkJ`hwF-7%q`3ce(K(i1~YN3&*7xXsr
zFpEaNx@uK_w=L<^ENBo$1Xc&A)H{%+&9d2a2*a3Piy<4R55t>BGhP|w(DZtbd4BuW
zB}K86oNt~>Gj9}6EgOn8B4fLS5I2|I!9lxP-{2jSCXUb%q4>xYlr?n!Ts`6w@c*0+
zf<flFU8iYa!y)^6`jh_-sjv!U6laa`VCRd?j8gwc?nj~@B|3o48)_(K12(B+UJ6w#
zgc^)bi_R}*wUpYZG!f34`(uclh$V0Pia+Rvglq4_ibmEiL_NQk4s+zaYg9h?;D=7Z
z4fk!jSI-|X{(4vD?|ZA?cW$DZPc!OJPfsno(R~+(At>b{cfx7R`u#;t7GjuCW@FMO
zP=>@>aaTMz>3ytv0K8Qzzn5ELP149&JQ7ip8l$4zifjMwWZcczl<nvcxa{}l8WRI2
zS`$dnm5gE^O;`P~F;Rf={k)Ym_uW7?uze(@d||KnE!M&0Ccae^+pd3CK9C#>+I>{H
zMV+#>;OqQJ$nQ8a^Q=iy+=z4zlv#r?-86vI3k`4CArn)-2=Mw?|15bem!`8YT_>5}
zBV|#!uIG?IUJKtI(5pQn`(*sgX<8%KV(9d@f8qsMh&l{SYho5;Br23+$V~@Z<b{-?
zalkVcQqxhsSk2INu=UFOl~W`sed?GX2#ToWEYN80`vK{7yK{I#5=le=U2W45KS`6o
z3Ekm#c~JWTr<F-ogGeaQ)-~qlOdwaKG;05Y%IH{yW5M^j{4cv@u}2`ViKh(tj_P}j
zJ{x=cpjKnIpI@nQe7e8d?X>9o{+0a^<HC7{(gXVAIzh4A-ua>0;1f>iOwZE<9E<jP
z-%~T-kV=3R*t_e|(`)1Cnl`@2RZlv_T5i)=Q~F+90v(i<AV8Rc5`N@@`HJ+qsbNhh
zP!fM#9j>>7e(=G4S4^%FOwaT7M8bD3k&9=uLg3d3+OMQMZ{-N$A2T)lB%>wL?j-ng
zQ#Dt#B|ld!Rdaiz``bGtDlDkPGhOOA_T~7NDYxq6lgERu4?-)}liE+Ze5yNMo(?3H
z_nNhNFLJzPAO=S7=KH&G4pW<IAv;U>o)3rZ=TGrgsD@dkRybj3@%cUWir0W&js3%S
zMPl=sDv<mkg!coP7#zy0dtv!~D2Q`HFpm7ttAj1ZMcpGxic>FHO?p|Rkg*TG*SNFU
z{AlsGcD%`Ahzd@N!A=vw=5J<y{Ln2C);-dh+tSJFkD04zyk2xWB|0ZNv(+M|ueHIY
z$RwP#=^*|x8u*SQ&ov5el3K|xOaxgf&KKP9H?L$u_F7OwIBuUli**7yzS;da%7J16
zn-hudH%wz90!iEr;8uP8Mc%=6Bs`dfBpUcYrD6gt26-8j>GQX@>Ibt)(xhyq6*sec
z3r|ynkq`VUl#K3g-MCH@P;w(Ia^my&u*}MzI605)Oij~=gs6Ksk?MN-YS@CWMc;Uv
zw|5mk1$-jrXrKTVk4}ya-=w+qFCO_miUUE5gAcx1x`(!V-!p)#yrk0KBww8F14Bqh
zDsRkxUtMDt2upFE!Z8hG@N!p&&mVWjwS*Ib{H4B*O1Vg()jaU#87OlPBnNQgyJ5~&
zsrFz!3`P<<`u$5>wv*?HM1t<B9WY_tMi+dX{Cjv8_|<>he91rb=2a!nkXMzMxsdjM
z@>7B2fJAlI%KgATw<ox)kFNzC5c(o>@HvA*r`}9B?_|45c9Md%CYu&X2pQg+UlnLE
z)gNqveNC-*bh=O!hX=@NC{QIT1_;_?HI=zKvFaYH#4Au60_CY}1{=B9a8T<OhQhqq
zEhw31eV5Gp>Ly9h4`uSEYwh&^%mkywMnH2FDhAL$NYvM!syFe)YK6AW?ct!DMFbhr
zQ^>g$W$EZKSEIaY2zLY?^SuD)5m*6SZU%`SmVYOH9$1Eq(|aL8Cp%ReWp=SqkSSFD
z3_TPtl@-N#jC-%fVSo)}N@cq1cghp!0yHU|NcjI(h7&EvN-Bxjm%M3w`@8dQYn%S8
zSQIXfaw0Xag+KW;0ufsB2V@8jfD!(t$bPB&0JC2>;Rp@D0rNOOyN|^I?js1(*Ue_%
z%Hhb|Qq%Vi)lQ0N?4*+XDzPV3(SPcwwbu7HmUjeem|7YDQzbs1<d?&xm8Dh2UKX(@
z^>G4JRFFH|FK2OJdyfs^4@&s_%`pIDIij#13{oSGdXz#T5CH?A%vd6aTCOj|;z?8*
zzd1xFo3G0p#8HN%gyB}I_t-`Pr15kM2<115N(I*pd*8;aEPfg0i-U}R&5-R4maUR)
zK(>;p8(~m7c+HqrCHJ%STtmVkJi5?hkdONwAqPq@Du&sES7(xOa<*2kr-xE;N}t4>
z9lg@X0?zh<9-G135kUB5EP?Vvc9dg-8d9W@5U9*2%^e_-K#8!AqZosXmOh5d!I>;1
zj=&s-bfwlN!e(?b#fH>}huu`B5JpJ4;Elfk8W{cHg&O$52$#<x5TDPB<7^PXlEJck
z5JdPVm=w0HsAd9Ffmtg8-=#}uu+T&ne69z+WJhKyXExk*z-t+lhE~IbiWYEOk2gCd
zt&vGAAj-%KV+p(2=qQVZGOh8#)lPX>gVT;-jY)q}CW{57^~}i*U`W^p?6QFRGg9bS
z-s+=X$>*D#c+z0x>z5%UM{HP-a1asDMzp3Jq6|c11f9B{*Ss$dF9hA`zI#ccQC#t_
zS&Y6nS5p~IW%F*B&Y-tsuu#hSW^XUR5c)oy+ZBd$wO?Ayk<OD}_P18^T8>biT_#yd
zM_GvvDDm8+lLT@=`SobBPo`Kjqa~l$K}SD7k--hInXsGs8lu&DL!(<lEIZG<?#7>Z
z9lK&WeemZzm4Qz2tJ61o)(nuSb?7Y0vFgvuLwVmqgch#1>?IS3<_nIN$Hs}#JANHg
zzA_4i_;VXgb8Fcou`Gf=SiWJ>7-F&teJxfkk(qwu9aZ+P1Q=y+_$rn|4o^>-fidGE
zKng%0jG-bR;5CRqM-@mIf6QKsrILAB_jZKz(evI{cx<+hw~B)~u18=PRET8A%b*JW
zt$1beq2Q6_$5+pUMgg8>NzMZRAe+vA1)%2kIjgpJYz#asQA`8nU;vINBBYdArBjP`
zUGPpaN=7146a=?N1swxGF39S&K3=F0VR^oIAK=0s@ZDZH7U|Z?C3B?DeM0XHZIkfl
zxGd{)2x7IjiPU0U&H2EW4gSaM_ka&#j%-Je1FohN*#rwB07TFZTz2v#$0)OwNJCKY
zl41eW;VoMg7`ol4=lN{WD6phNy2FB-o#KU$Ek&PuTLlB7f$$Us4Jdjr<+V6Rkzc#M
zjG6AWIe!?w@__&L>WkCnD5p#s1D)>I2<JBw0G7aQf3eKEI%@TeZl$`pIsNL-Hz0}v
z+zoL4)#yWbt_Q{-Ylv^SDrO6cF<|4Y_;ECvQPhuaMg_Ya6j81IS^U;!2L~*)0QQFI
z_k)^|&){f-yffBXApl@#UswWNu2Mpu!IKgcJUuc{Z23NoX7ma>lmG-X)L63`w58nh
z)tG(#z~owk1Xz#IK>N@LmW_iEe(;Oyo(&5)U`fB24HIs7bQ3Jj9ZO-xza}ajDg}k#
z@TGy^lKwmslpwzDkP`x~<cf$ZPhq6qm*6-EQG>lIPy$%JK418LK=;#I0@>QQ5|msy
zx6r!J`>YNJb$Dq{SU_PG8$j!9!Dt4VC7}TzY5X@J8OXl~ke-|nz!(TVfAi5CmiUEr
zVGfduv+bMXC3#ZgTcbQ%>rS7(Lfv?0?{XSu(Z!%zsG>OUw*17Un=H_n1_X}zK*7+!
z*E~BCA`XCXQS`2co(d=#{KfziHHr~hm#g%Qb2$b#9xTIqOA#cjEBup~33TO8Z!cMZ
zDacCgf)9!h44}uh{tK4tK&D+|o4~g7GrzY-@2!yqr-cx&nQ&Cwn+|qRQk7>g*QAtH
zZ%Vbbr3VXr#ke{!x(;AcAUt_b;YiLBDZ~OyO#vG}p3c`s1OAC0gC_uxf;&F=Ae2Tf
z7qG`V;_L29CD70V1;qq1{6-O^(AUrx7wo3mfL%UR{sReE=@@2Ec|H#lw*BLTJi4$T
zEDOO|{x|Qo=Nj41Xea`s&p;e#@Mbqpw7l80QZ6PNq_6;vCpIc_&o&+y#;2)eggsc@
z!0ZF#hv`Q|8u1cSo=zGRiIbeS77Vi<3Q5#}21o$83SC*07eZLRc&R}QjK=nRc~l?w
zJg4e5dBuzlx{V#3Cp9#n1mri3f$2p&g^)xPSa$Enfrl>cyTT|}AciT?t2CMHyL`Lj
zyNrreylya*56xs#UwaTr9w;M^^(kk)<8jUc?=tc}CeQYsCM`@Y5y)jy1GnA0m(PS8
z01t}EJh8~GwS^=F2db{aOyh|qulIKB^k%9+ew7}Q5-4A$l2>1Al^SZzOG{%eekz^t
z8as+(HHFK;XU4(7B<m0QEm9vk33&{3$S2}2v>1s4P5te$9z`-?e|UQc1rp~Trse^f
z($uIjkC~ln($sQ))bYR2nuPxwtyus*jF6LL3cDSl34mHEK*cgv`R8FmANS47GT)h|
zo@`G)_o9Gi-1_C?9_4<uPiY5E?)z5`wwDJE2OLAk9TH^#y>`4ZJA}D%VD%{XT!$Do
z@0K$Fy*N7b;%Bn9Agn1FGfrrlJAqEdSjd)9{$jzkTsZ>`Yn+kBj7KM3_Y~Wm>mS^e
zc`&dF*Y&hneBOPX0D9#tsNxxh=0j3zg6Q41FFu$^LvRaDIC3tZ^J8^)pQS^k)AtQ>
zehpRDB%HO^)ef6IKQXAqGD4KaClejHb?K7yw5bOVqY8BkYCxGt510dCUBv_Ti6OwF
zz6Zjvsa^QAE7ME4y8Dzo{B2F|S6}5L`yOrY%R_@5+XucFx!NCg<=lmI(9MlAW-6M}
z(9NBcK4RC?DEwHQWmsK}eRNOJAwpHisXnLVrFKn(CRj&h5UR0|co8*Kt^12~%nZm#
z1R6)_HoB&SW&<=@{Muojy<}&*gWUQfzb<p#M;K%z%kk@*eVnc;(bBY7CzyEv<JP16
zEi<;f<tA+55|~B-rHDX3TMxj@Lb?3LX0#UZ%D$aP?j@(3#6*S5oE8q0MNlrY)-W22
zx>aEy1qTc<)dysx_?-Q|H^%B|JPgEAbF@9aWpqn)QUFO9<GI(Uu2!biTM7nQ9xQuB
zKPitME|NR!{?W%v!Z2}jb`5oi^OADA5fN<Ja~Gvq<3yB(Ef=uHz9uXpSzk~aa*aU0
zKi9aoF_|>CheYCz4rcvLsZy2D13ws_QNy_%ID#Q?J0fo8>Cp~drxb=On-c5)MYnTw
z3o=zwD#Wj(*<!9POky1lQj!2F0%h~%Q4EE2l2SZq6Bk-Z3HYvUvCDzRjAuFw-rhw=
zn`7GGlu6HDwCr9LJ?ux2mG0HNE{g-WR0e7>C;QP34~L27$zA}g>|O0V;LfCfVl4Ro
zg~xv;Tr~>eF(m)zxJT;6d6oZX!z?3F?UUWXr}hFrL-boqO|hYdf-tZF2u!oFDdpU2
z<OE7f{?Q2z7?(bdG?}2_FgXNTPkkmSCYl6*?{etFPALaVwl$(_p88@Y|JEu7EM|0m
zAl}Ii!26*z<cbh2f`9Tqw1~8stfmx=(>q#(<^QEc=nn0u==~!`+_?HyZ;s251Os4a
zA5;qT`U(Ck01KdCDvVvfFiO$;=rIo<6xoS@)<-hkz0U307p|NyvCg17;P!mdYzi+a
zk>0%F==zH~Ca1HA4X2Nok=b%;%$fbwK$pX2;m?C%K2;g@M(KV`r+G)a|6i8JP{r}}
zC$Vggg*kDcV}#@U9!GVQ!CGTfrYhA=fR%IAqECes;f}46=mlSr@^~~TQmQX+^_p(R
zJx42Y(*cNKGM@)w86PGNZaV<_-v(HThy$I0p=`?}5ET^tVT80!jaU7DF>w~HRW1?w
z()E836Dt!CE2|E26fK*5-ga-gKY0>ww)tSCu=v?j_<d5mqU~Bb1@Ha;;@~_PtA_pK
z;M{$Z0?FAjy-!kPGMR*T8*hE;vwV2#8w6H1Ez0Ujo$j5x4lY4FFR{WqveCZz6VBD?
z28Z_kC+R|8%`MBGl#Sp7Si!8`TLoK%ScO_`HcZ4U6<?`Kr{ir(-FogNsOQVZ$@85D
zE>wev00BaR9J17d>yn`|{T}esh-ni>>^~vk_TLi$7@<367B*fG^ZI|&vQ`+eJ<0Tz
z!-UlP0j;WE^jNCroIQ!j5spOJ`Qoz{rFPK4Z?ur*wfa?3c7*?ji$!BqcCQ$B307C8
z%5tybllC6s#>ki82%ifJ%_ifDb01T7ZV-E+f?(sJQZ53CLf2IOZS!^dGLaz-@RkD@
zdjc1q{h+3si>(8I8kPiwvRt2Uh)N$7oH`M+Bm2YkY#Q#}{Nuw>9^SFx02=>4Y&h7<
zi49@P9*Yl?v11tx^D<pww$@bq>gsZYz5!`RUc8+44&IywmcpEi31a!FGxlHb`T?nc
z$?vw?$_=&ujb>?pbHx*Dl$nm23e;_s%Z?WN$Loku2;G53-<Xy(><2hpCOJ)5h>Lq}
zj+;sfK(P6`(zqA>bp=N&KZtoep&fzUZ7dzbyN7NpwXii?9gedXXdTC}g;^>Aok^Eo
zr*p0cfP2yFkAPx)b+&)-OE@4G7<Y>mkETYmb1(5DNcT;5mM6WIW(Cr34Tjj$&yuNM
zXf{1f=jwwGAdIgt>h8NbKQsY4s+FFnwm$NJhT|^)_WUbseBaW2@ka2?w`iENVC6-M
zyB62}B0oP#24bl|d>%Iimj^9oYUO(LWBKw)&&b64c)c&BE}!nKc9{<50wycu-(@Nj
zMUR;hDvg5iBt(gxvwoKT;6s?Mu)U<;Z>b71mbXtiu+p9nJy~}<<vj&oir{`AUqMQz
z{wenV<ycB70aN<z7<D~j6`09EA4WsaZ_kmF^a3EMU}$iAbA2#arVC+HdmEg$j7O}>
zai+0fyF~D?StlR0?&)pJ=L9=#{Om;P{bXYs2TsIT)4|mKCy}_)i!gc${nZnLY~${8
z3QI|lf5`)~(vh<&g&J)G{11EEII_ed@ltFIQm5EX{Jae^r`V(Jb^9B&S1e-NmvWfm
za|0G&6DV3qjzo+AlxS`Y)($M#mX)Cb0+7W}HgfBegH;&qcCgjPZw#pPxiXjCD`{Nb
zY1vLLJA6u?X5OaMy=mT_x%Ip=9VO=iF?X0tt7GUe(20*=0U95Q1ydqglR#7m4PE|%
zk6T@?(4nB5`blPu%*nNwB9M}awFl2gk?)Vl#Of#TfD@BEMj(G9zIcvC3b1~c?sBBF
z(rB6Bv<>Q&&fS*@d-@~FWrz~`<-C)}6$K8bcgpsa3y>3|I0LLkv{+FniyazAiOVUp
z%A>7UB@npaM(N#ZTx6wz<d24q70s#DY!W<scw}8)d@gXl^#}M6g8xcl2wFMYv~EA2
zpq6il)B?Jd3>j;W(8i_Da=H#!uOu4=;iK;Cvjl8x8|UX+E58+e^dUMtbkT@-;q~sp
zwBKl>=gBrQ14X6#q3M^>p{+nbtoHGMp7oi+$y=rk!gBcjo73<;KBosu9U|1(YtUKz
za*izR`Fz<_|GoT=K}0IaO?w4^f{kW)c(dNPIuaV5E7O>DPqE<}ja1SChX5gVs{otL
z?fV5x&}#>Phi01F1?q@K%u)=*YRL~=!8Ml2BpG7=cbG2zJYCJ*YRG#=q0+L&X_#+)
zv5j}W`jx0H4^m=C`=?2QW*D$9jWw}{g`US2p#PB?(xf+sF5jT%l7z$}BYy_AF@c?B
z%{yv)21wv46r7fKx)1JnA)~Lqs7a*E1ov?|U-U%jdfw06`;`ifCn01ubI0m^C}IJm
z@AIrL4aDNND<rX#IxJnmFAmWEUP}PgStTB}z|Ti4wdhD=@g_nk?#5{K&ol4_V0r$P
zYX`y!sgQ625E@80^b$b^Dot;3_@J4$Wo%r7DBu4)1fIn<3ciQdPkJ_s`TcbzXo;PI
zWCswMBqcwMYaJ)%jLHV89psY#`A2)rW)pFh%+6E(1$u4Q?WJ`{96||7goELcFubSe
z;kHCdGiO_fufFlr-KUL<vu~IT%-zyo<uD+AfyylUK_lDvgRJGOZz;Y(H8|8sIsmv2
zLdpv{AgVNb{t%C6Q6O#F`Gv(%jt-vv4<$;d63aN@)rK{>V!MtM(DR^d#XsY%djWnS
zbOmlXcb&fMUDy;<-WhrU<}me(1BxhQDH;k!=dSt6%$DIH*0>R&cuEE>Mt)@wt^tb<
z@UnNkpLi!}x?b)Lq!w(;b5A$gDv?0f{s|6%zA-=TFgs4%8HH4Y3Wk7Ypf27ETZnC6
z35ZB{%^M1f76C04SuAdXB;ybATwW^@Z!MrUI_~^jK^vn~yo#e=vGW9<zvyXM;1Gj)
z*URv4^@KhS?A6{GiUMK-c#1zOukn0rq5k3jXK5T+Fuf6)YLZG~$0J5nxa)i1|NJeb
z;f#QD?|ow)Pz0$Ag(fYK9OK_jR?{5_C>{Li5M{PIo+!+4O+<nef6#%_3)Jmc838Xs
zaA+5~2(Q*j73uCgB{B$o3%X5S`)^(kxX|(n%*vvf?<sN>xMY5>BL__TpK-f^RiGIp
zNtwU>m(Y8VeYCXXN*ac~rd&K>6A0;t_;7wjUj)CBumz<%Xo0VY_PJoc*z{RP&x;vL
z>g5ZAW;mAW-~749cxI&2Q$3Il{5v;IAcG9;9Uyk&Ie+XL$s9N2vp=T?#MnWb2e1CU
zA3Gc}IHeko+&yZJ2zjNW2v-j!p^?M**D=yB9{o?rq}G4bb6e}6O9w(yb|U{)bSR<-
za@^2QM;dpdCI4eDF|gIq4FB~eRDdNr@Ue2a;%k%s6TADvW^YBI*(ASnk_Bx)`^{l3
zC6K68Ja-<7-o*;Z!vwnhr`@kt)qDM+s=JRvp&e@S{B9+ZD1v|yT5Ra|dK3@uEt|}5
z(jTDQZ9E}~$54J9JnjaE>;Ys;l^W(0>Vex9r$hYsE@&AI{mwrE{7CJdq7Zu{3^>z*
zV<a$A_QLBl<ChXN7ynV{d*1sPjY5WN#^-nZ=lb|<u;QM9!Dix9hJF+Z|8LyLK8m=J
zD+;M>vFQMBVPbusTK2!&Nn+yPo?vbtd#&DsoG_M%q^`K61AS%wXT3^@3Dh<{FW)j1
zDFU)y7|>A{xPtw+>5hIN6pnJEZyart4V{oA<OTo61+Ehx82>6rRpZfit8UYJ`f_7?
z0d)oN3&1#q_OO)R{6YmCu~fJabV$JJ-tyi_Nd>O_6AOz)acVw%PVt2P*^t>g=)wBG
zjLG~JxVb&40x~HeP8OGtS_2IFZjs7Fp@EA%`#Z7}4-Y3>GV?_^M2fUdpE0|Sc!1-5
zuZ;qlb0G(;zg_SBHcn536gt|o(NF#NeF5tinwM4ESNfxygER1_bZ0^j6wdDTL+dDT
zh2rljb0DIX+6k76aohw}$Ukj<o<#nMx_fh+zwgyYP)L33?%^U4zirNbhQQh7c_-LJ
z4u~jVU(nb~%Ez++;}pX#>ivxWeoji})y?l#dPFV?<a>&^ECzno(?hoI&_w+8K?aI|
z&y7>$O1;dIsk06U{w_1}JGqtk{Eoco>*ZAtgyc>ZrijIBoJZXaHT~aLorY%VPR^aN
zbq3F?lSl&n-{ase1LeT>A1=N2SbGFCz03<s=8*m#cSybHf8Tg1fL;na988js&WQCp
zAUJ6yb2r-m@!)wHGI~|5?JbJz2l(GaBym`u)>%Q89;_gsX8?UKD-k19xys8t87MHJ
z!=Rt>p$}>qq`n8vRFF~zhf{v(GVO4a)ObT)$SAOuMq&8(S8&I^`)dw#+=^KM1vIcj
zbs_Z^@T>&S-w{7}XcVtzGfx<urO9kJIPsMgx#jQU=s+|9dc3L>bn)9TV?14IflI9w
zG%I3O(8+=%$?>;`N(zd!<F*o0b>vSl967q~jxr<?WDsz4q#GyZf8urle!kbD$oDii
zSuOLer*9%R${<x3aJt@=+<Iwc(LOHIEiBSW?I5;FKrtM6eQ>t|-XUu;%E4L6D|J#d
zOAi!*cmRiHy#?OC@y7y@w>?!#9`bUs<q<Z`p_*Nc{@>L|QV((#q1^70uxJukt7%ER
zCA$hGqgE!y(%t_X3phm*i4|`~3{$R_ey}W}QwCa1<GfxcPYWreo^xAp;{*rzXM4#~
zm%l%UzV<JdKOOI#nI>dM0%)fcl7&CD!RYjfa6u2Jvyy1<Q;9JwVCwyEUJf9lXx=RQ
zn`q_t*`XQDdoc^X!_)Ts*a1J*H#9-qHA5p6VmT+?13a_)5_cN?t?o|6=q5mrHKs}F
zWup*E+yJW?%9KD;1<s4uRb%p*cpw?`%401|A@q;c&!;hh&;Pw`FQfv;iPOtwBA!SC
ztB?6S0Q{2`|2+)AxS=Q&#;HpwChgdH!I}Ehc|V`T<ex^x8NGZ4iJ1W<9B6w9y#z>r
zA!eJo3TeWOWKya53Q7LGTi!Yh=od&NBhX-z!K~xx`X6kBV(zG|qf<PIN7ApbNPRU4
zeMmtwkm!mS%QU53B6546!va{#s7-`kO!uj0;S~MR&O?=scgc*Q!(1A}2t=?@yZg}B
zlR~McH`PSRz<;|6@5)d`_vF?2wtd2EHjDen%t8OXyRRQu5%^%gsgs(dZohmU2ps2%
z9LWW}vT1(4^4(EWCe;zOSUFn#d%&W_=0S_c9{OpVdNK>p4t}9pv(ec(aCgjtWHj_L
z-3d0MFXJ*_k;v3+Xkfqt#tGD+P%<#s^i$K6gUsH?*L?a0qf<@SslQ(Q{cA4{Sn}GR
zzsUA5i;~HNfCxooT=WApKHyFR8l6V01b_0bbaYi|4>TY3#m|F+7jXVjgT8lV`rgp+
z?@p7=)Y>J1Qjz++PK^w3P$C1B(0J&^7!ZI6-N;v>Y+a?ghthLx8eJR?>Di@$KR_<<
zP*G|%fG^T5h*Bw0)CJ}uFM*i{-DEt&9iAUl6q3SNxB4YlXE+ghF{#vdX=|OgjZcEF
z4R1M~;tvCa^x*y?hkX-=Ek6I>8P!D2qe15II4a!!SuM?GJ4=0tHIP)X+;w*m1_khh
zRIS=XVC4-o%xI-z3`JM_N^bBbd-Okl)Y1A_sH2hF1Vni-IUCQ89W1BS9G0qW(nkZo
zI+WR*BMiZ>2OPGaFY?&8aM-2YcDb^{w*0oBfdH!+++Gggpz@Q~)qeLuSE?P9%mNZg
zgmD?J$ML|sT%xltA<ZzRa1pZ@+|!wFo#|6C{kF!ML>(QQ3Gvtlboh0|b>wx_|F%ah
zTOUH?d!;z>Nt-H8-%SwlF+TApVE&%hAMes9AhsOlkFZ*)WU?CD$GWogGjg?^vbML?
zwncboYf67;ms4b@!L=&wyvLZ+Xo}9^BJRsd)icM{9|c%_MZ@$7%3btvaa5(Tag^n8
zarxHK&-LfYsRqsjqB4&w@cHc;@oWD`pLZ|muC<=A>vdmhJnc8)E&Z_b#&e3H=EL<H
z-zmnE56c|vZ+UJT&gC4vuqG`Gqp-RomtE7<#o#BgCUGY5CJ82qPW|py2|38yJXpEh
zYfS?7-#xjKaVt0X;Bx`r&QvG5M3KlwN9#iC-1U=~gJ5`+gYEj|YUxeRYDBjlo8f&$
zzdHQW&8K9n^}KHhIf&A*fR9S(VW<1@)vazxRy|?a7KY6)pI<G%ZhrAC`Yg8mJ8-~F
zJ@_1;hfMZkJDHCvx@E+2z<obG@<_toy#oCpv$%S-y{oq8e8;1Ix;eNv<%0}M``aaH
zywy5Uea=<VY4}Nm$%nxDduv<oL&{e0)>-CO8T;h8b2AyjF3~R8F4ZpGF5}&D>zxCi
zvS%tTGoS4PB5oIMSB7@yWSlwGzs)c*Fxp3MFuVGs>KzQXkU`_W(vh^__~;&))&2h(
zqc&&XtNW)or+B9Xr<s^HcZ&(cBl#Q$KzlS_hn=>T)pB%d`lCyy358T|r!%bY+{P0m
zki`&icMexN)Y-W4jw8~LXHjRdW{GC0W*KMMC$F6lG9L0^x$*r0mU+-7?f@X~AV5<R
zUu)))2ZU2ADZNJA{`tBlbEf)2A8F3@Awh?W=VPmZ8`^1+PGY-Av;4E-v+}d*v-*?X
zr-&Izo)`0Ej_ZRaS;%+oJpj-?<eR<Qn_(4YPr%^H9wIl`e%9c=>iMRWM&*~K+v@|h
zUwrF$9AZPQI}DDuy2C;2e|UIrZg<ijCS#~hobo@wQWTtA)D=93(?@t9>Hk1CA(xui
zULs)So*J@0IytpeG;y2mv-igc7|9ex4_p<E5z+45liXr35NUNrE+M8wQyg_&;OkTw
z`~;ifog3qQJ@>kG;qZzmPuN-Y_!I1K&vCb2xuEa^--U#3ix;PN$2~YWI03zP7LQ`8
zs#vPuwA~+Xl+SMXB4uLq)vCmL@aN_l+uSEa_)gO@CH3Yw)5pb%ZLtyXG&_y<f223p
zCgw`*4RQHvjMIAEQrUf5xSW0~Tu3|sZN8hmKZfxDY}E?f%XXAaKSWgA04?1-Sf@5+
z(eE_Wh<mSvD0|&v?8%n9S<0MxIlO>tRkh6w8{G^tcp8oqCBGMSbk+JQa9`7<j?PhE
z7<1Apne{{7_rBqXdrU%E5?wJ7R5<D;J_}PEyVkvD`0PGuKU<8r5g&o4iG#ngSOC8%
zZCS64ymvTx4&#=E3!SHGo#Ml4>Ws4nk3F7+<%XSgY3?&gw%8~A#`C?IN$;;qe`TNj
zv)T&lZyWv?Zevxh3wo^Xea7RU%w|MPORHTH6}H8xg6U}z$y6vx&91tg;Of0>pKPZy
z!5XC=6R|ECM^(h@dHUuf^b+m40<iqC0{N12l(iD^RQA`1Y1x&mRz<PA?+XoItK-6}
z2WAV7zR5NIPyo*s7j4gHcf)fG%ES5k_uvpv&+WIP!Z0HyLnK4J(Z_-8%IHG+mjMr(
z#A1b_?Bn`V{<C)%z?Et&v%bAZxj<#^;<$7ji;7V#i9xrLZ~<6K;Bs2#T7pE}#U8h<
z>q)btkXgc2lJUATCD5{Zvi{m?R`<1Gxb0+PZOa!TNyoS!o#9wxnfKsAY#zm1{QP3}
zVGnhHy49B>?@02eA6Dv34U>Q<HB8m|g1cp(yMsMyFnqhO&evr{qU3SH_Hzstty-I9
z<`<e4KaVWxU;H`!JuN(^xA=6+F%#zkNP$($CfF9LHC4AW@!tF>6?JsJI<sFs=6m~H
zVBIbYyW?<uxPU*-`($TkeUp|>HeQ4I9^3)S`8<nmlanI7ZjB0{XUx~fY{hnpOS=#h
zmZdk$Vuz4ogg3ZsK3b?F%<WVCWQu>fzq33JkOV)1!s`#ExdikM2H6)1x#<=xnqFMd
zP_VL2-oNQnE#}B9F^hemoG8gGli<&jA%~nE`JS4JS%@>C`953-;=w12*kkK1`LvJ4
zlckGS2H%~f;}X?g2Mg`p^kX}b#uJe*H=a34ZYBl~gfsRZGugJgIsf_9c0~7(_;!E(
za<8$H8RP;&VAdtrAH!VL98PvJFHaj!05t*^(D36Z&>brQGN9XBvZmnn-A!3o*f9;w
zJ?Uf8pp53tAN9)l%))T*G$<ER@=@9C)aE>o-ri``Id$)-scpd2Cvo9r0uR0z&n9iN
zNz5hcA+D$b+5Yi-RQ_Bi)B}Pqrh^VC5efn403N`V=N+2>sEy_0pzNPkFJfp8^w@UU
zwaN0F=(TGdYDi2RYetY+GJ6+v@-vz}xK_Px`jCh$FFqf-D;*RJ5651QM8YzjS>i1W
zxs{IUP?i{t^N-Cla=x$G`N?@n2k4ImBFx%waP^WcOhBB@MhVnL9c}v>Y@1sv8Vr83
zIFE;2cfCQ+@+Yr<YKzULJ9i=QeEImmt+vb(<lB>}df0o?NVt>bf5)OZ1U&T4h{6v2
z>AY)y^Qm83iz;OC`xn0XN!P>cxdZmh-gpyqr?HiCgCLfc=+iJsFZ<c>vYjZx*FO`U
zjrO0?E+i<@V*l2_qSssDniy_9Yj@7zlh7=eOXDzLk*SY42D8e-WCtU!+ZsK#Uw>aJ
zJ)xXk1TS<$F-Yb!$v4v1?opl@*xgtNM8w`?5U-5{xYJ2H(44*O7T2z+t7JA5+P*!N
zVwPyQ*IR6q>8jnZagbz!ezYAgA(PPi`TSe_GxR3Myh<*$xgN<ADc`-39C0QYxajf_
z&9V3f5>@AY1m}}MP>B6trs4J?3ypj-yj0YbMP3(OMauHg8nPjJ`3ABWkSCV*bu|qj
zMb9BIu^N4o?fGGs!RQTMXtU>OgG^v@1OR4fO~xe0so7|8cXdvHosz!x{H9Wd&7-C_
zF&UpJD0p=AO0RrABARc3ro<>=_=oIAy$SZUdZr;qhtfeJt3@up$Isz9xR}gf<ZfGO
z(Z6OxoF%`j8;{5tDTJW)X*pbX9OHt1rE|HSAZTnc$)hR%K1}hCVb{)O*F|%G&E>%+
zIU&yFyV&P>T*<@q+OXrus(S`bNG~UqjJqGB@JqU}E5Mzl^?HTsx$PqykE`pOH0Z$(
znvLRdhBQ&Qx%6i8tlJ!YG%Ih)KIVX1d1%<hvcvyY;CMIAyop3QI#^SP+M#kld2dnd
z@W&U|av6daxNJ;sKIK;jLv)pc;XN!wQg-NB!vv!X>*bmO^Oic9spMT^1EIavDbc&{
zE@r8<=h3;^th}583TsJfWk%djJ@X_3uW=0fwvA$yguEHz)%QwuBomS03^NMZWtBW{
z4yuwO*_Z0(`My-SB{BULMharAZ&^YgT(bb0CZ&fD6hGMN38fE{Q@D7jiP`PYhiHD6
zp4(n;&U06CS!d;&Ka~>eov%r+-Z!hcBFS@|cUhjdj+rNrXXl#_vS=3TZfw0cH2iu0
z6CQ=wk190`IDwbX#`7%(VXVc1-`=O#ezgxJQ;9JiKo_c~sGncD;7RS};hp;Er?rEE
z(aR;svc$@p#h_tj=Cysg<(M8wpK-3_qSkX+XCW8sI&<QcFQ7@;i$2~^P;=}t_s4A{
zGRC&4smpA&ST$;YKXE6e54ky#aC`lDJ3)e3r}9POwWh<%mrf{f6>=)DeDJj&uhr*Y
zN3<?c2_GspR|;b)6h>NYTDhP*P9C6fN-f~?zSKgIf){y?GOzLL(VFZU1=>Xuxw1vZ
zJcnQ=$||d@;Y5>WDkUoa3N{{?rH?P;_*(l^Jj|AdStP}JGMNr)NKO;i+&`$^S~pwE
zXK?#m)m7_yM`2KSz@cr4q+7t49N#+!jH}kD5vX3(*LM*=C5gYJy}8O%o&M?kTDL^$
zCij->Np#ZhJj-d#Ov%i0V`e61{XRC73olALG4Y7NIZpXX{rMqxFcPI}%+332cnr^}
zP3#_`5;*8lt-8-UXB}C`W;|oY$rR}RS)$Hp*66qPdSgvuLErtu963B3@rzL0F>+tP
z0yqe9T&||ba|==<U_-P;%4&+!Ny?9H4o+ob9a@Czu{k=IY3tjKqN1;HUS0<4;rFm-
z<L1VDTDs}OluB4XIU+^8(I)xaRaL!+TK`-ANc<xsCX*m1quk-RgT$Q;S;M1~wHvJh
zc({&7iUzQ?!OZp9w>AluFUrG)d={`vNdmLQMa{~U^I)~7y0x~4M;EtEQTF~k!uL-E
z29d8M*Oq$fj?4DQQ-}!ckTaaz#=9Gh<9Ca+yWRDYj1!Nnqpx~C1OCH1O-j6J+vQ+b
z9kVKBsKLds@6Ga0xzAlxCFe2?Or!lLGF=jOqH<gIa3LlKKUl5-Xu=Zc=L|(^sI)j>
zs(KCJ8@+vx7N&>k%OIIh?vS+Q;bZwndGlyvY(t$#V0Y1`3N7NOUNmJu;2kI5O(nJA
z+l$j->Syt>bt|ppC9O9*c2SF!{IBDq++%TKm*d!$S*_-5Y`W(^0MxHm=HehmkdfDY
z-h~oZvFVH=>G^mX<+M|!?1yj_G8rlPpysI=e$X5FLv2I+XHf~3H7lYqQr7Pzoz^^}
z$Wz^6oK}B!>)D<Q)%gX+btt$Rk;_QV&#iY%f7dr5>n)BK@G4MYdn4Z`=k=g7EG|CK
z{yqpolgZU($Lbx|f4mcf*Y#7WFCHIve*OX%o)P?ZvmrEGrQVWNXYw)*{-Okc4D%PB
zK`k2w=)9H%2Cbhqr|RSA-qDU{=g?NZ4QBc!gw)xxa*?>D!#Ekf_gycY$DR3l2zmnT
zdL_()%VWFHqtry2${hXrBy>Ge@P!cd?MAJ9lykxstKEo@_xT><*d7}6`w`iwRImf9
zWS$TKin2mmoYUl3WE@Kf$1jVq$54BbD}@$68|^ZSUG2P+3>t1a&%53|B8k6szOF;y
zTaK^zRqzss>74=rEbwa)R!&nGko71AG~Wz^WG}zbYUM<1c$z?kwvlS(q@o#G9nD%%
z2y$Ny>WY6Kq31M&@f{%u)T;NKvDWJ(6c<koBy_<&B2oFHV!dXmx?2`Oq-v}<`~;8P
zFb(`4#I&Jx7F-TTg~iI{WzQccd{Pz1Oux=xdMkv4se_r`ESK;}ceq}?$9q{{e-s}K
zJzVyOkW|X%Pj%S&3j>$mv0sq{ez#k*alt77c!@&kB!;{NSz_4v`MTrg?-3(flW$_j
zUG1HgE59o?mpa2YhCvf6jHH$@u?VjTWFL`EQ-#=Iv`3{NY_5k1KlXKb&_(m;S=`aI
zSulMZi%l2NxUxK|GNW8_ZX$!Hw9WKI1((AL^=bkfT(n(ivISTWE6g$!VmH0m#Nghi
zP9fI3X(`U<IfQ@z4^J1Hq}5G>T4XARCNKA<dp9`&SA70Z#x>`40<kW!$?Qa3V?LM9
zmD|^fnH)1zt06lnOUtNRj_+(O>8$#HQnH2%f#Pv4Ke%{IlaS^^l=X?o>8EZ>=)rjK
zKrQ{CppK7Br^^Dc_OT0eI*eoWGH$}Os@KYsbK$&hI<bnCR=Lyd8#&zV1ztOuCG<^f
zMG<f(<}6?SC{i9WqZpLUc0z|EmT6~`1uCR2)h`&wncJ|@NCFkqXvd37jM8#3&$Fsn
zf^7P5I>>h}FAK!t$n#tdc#H=<_gc$oRf-GIFUgC!Y1I?}zEXXCI8)wtVsO6MlRm)t
zHUPCy-}jpJdL+#N(9WY$v@>Icdg<T<k^^mEh);1>u=AOZch!TEd@UcpM-!r^!_>P$
zRb87$9O5<k`ql4&=d-1$ie<~amR4<9ybkgwxXdbpFkY-U49d6ynVNx7fMWVVlyMet
zd<$}5?4~hy;lBo_E(7~kwdRCbuk#TghWs5kikD+m7Gsp^ntJ;qUm~Fd2|~$|(OMm@
zaBhCSQa%=`;k%<Dmj=lTVv2<PC(lGObE>S*nfnWS@JW?9XF-JfQki1aVV(${9EH{4
zhlJ`@#?PTCEJup1r1Q%~0V#Ns)Dutg0M^rC$4|q%(<KEB-)i;K(Yeg}yh7crrU8%O
zRC}J?yUXbq+^AkNz`<RiC0bZw5$(;+O1pUWVZ(vpE~K6I4pGjZ%(v05l+}DQES~*E
zcQ<EP@nS92uGCjr7-=jm55(PCVx2P&+J^+DedU{(2jkG=R10joU?B}qOl0PHa9iUS
z=Pq{wJOIgJN83wrHR0eeR4Q#?v2k(fcsA!!2`Y;eo)K-RLP$Zy?b0X3K<I<xG#Q{5
zp7mHvQHBn=xQAmWLDz~wMd&(_Aj^zHF!N^PFvVCd-ddP2X<)QXIJmUJ)-1+Y`(T#;
zbkDWyp8JrV$oyr-gw0Hy%m^S<qyc8<M(@q$CDr9y`8lqMt-OKgd~o!o^d?$xx$dSx
zr_Or5*YYpPuCEVC0xL`Kf@NGPWk*NP2U1>dxPJ5x?j;|YX4upG)8#MY7{A?xHvb-N
zG+A&U=BCcrXWM1<M(QGWK(6mr5C1g#u<2UEOn4)`MK;E!W}8#aFj+?P)%4!1rP4h(
zv~c5o7FloiVn^Xb$(dWTofw&W(3_VqpRzjavH}3IViKdiTr0}z|7q;X<Du@_zPlnt
zNZchPVyt1t7P3`jn4ua>w#a&qwX(}nq*B%yS;j6#W^7rrQ^F9+sA-a=ER($~S;q37
zao^ALzR%}<|9IywpO3Sj>vyi}e6R1hF7I}6+i8zAHH{&C=+2as*5Jxb+2_yI9+V$R
z_KKV9+$X?4H20Z}ywF-GD5mf`1R{N`s@7ew=o!hvx%(askQa>nQ#zPaJVa9_mo){?
z>~7v(j&0*&b~fADAD|M&OuIr^ytKiA`7-8Plded{d@0`?Zn=rgJ3$qa=zTvVyt*=%
zK(?nL#SyWh9*qR@PCyP<$OwcdnDNX00cIad7ov+WT~a&h?^RqN;x^6U(FKbl=^YHa
zRicmEoL3+weDk|MG2CJPkUCL4(C3nr)p+;2O;=|sWhv`T@%ryA=hf*n4!2*wvduSQ
z?F4G0U!$i9o*`@LvO0qx-p1GUp5Wq}0X5b?r^Hg=6`DhOE6){9&Gzz=zvgMm+F{sl
z>HyO33WVnw$%Z_)?tWE15Q(+LAOEY(YDk&3jGOcP9NtjLOE|=GT1hmW0Ccjt+9$z>
zCQ5ATQp#x&t8LAl7ohy9grEFG%)rLRz%KfjwvMo0XOf8ToAT+tuY`8((5)RYkrKmX
zsk8G+)!M*zqnU7GhpTSR*38d5?$_0ud+FSh3<Sb=P7a&&CiC3w*MF6JP*!@%c92ho
z8{1TMYcN*XZa2y;)O`G<vpn%DCc!}0ofJ(YfJk?3=6G-y@pnv!ynvIwo+4);MdLuA
z)(>|o)9f?E%EX@fzW|DC`ZNTaqlkZr#J2SLCw-aZ_BfN5GB*9$v)!2sS6nU5CYx&<
zp%E_n#-2tXkA4+zD((x5^~58FHx6i#oYX?fMR1|yX?;I{F35s@CuNg0?;8V)<K%uJ
zzWOcJ+1#Y*oA}pgg3o%^hF{IZDk-!xx8*X-q}DUfOkqG~xVUMt*=O=#I$*Y5GLOz8
zi%)0HE5bK$x+QN}8=ht+{eVkn0vVCGOHfBHh28B-8j<MVvBCZErPe#!v0`P4*WcPF
zeD*S#v9Cmdm4!tVPZ$a16{eUutRoz6MP5MXa9%EV4*q)=DpVGhDWz)?BD?A`{o!(?
z_Q&W9-lnXky8@A5EP0xe5js%40h$S3`8}<4%pu_UQ25xr(c3O+;Xl4gw519UR9>jR
z&5NUepm#lr(TN{)F50o9N0z|}XWdU8ub%zts3nh8zY<K#X=)h|x?o40{%BdIu}Go%
z0_Cfwmrej-Mf+^=DO=IdS9-ZUg2l!)uOXjdT3}bYOEJ8{>M2M668bIdDz&I@NitlL
zM@Ns4agvn&8t{ts=OE?HV49Lnfetnl9NgDjF1u>-REi1+H+_ga)TH&QYw%~TXRs<c
zELNVj11thq^_N=XmBzA^xq<yhPWe`#gDracRD(txIo=##;YrOwQZ|!T`(m3`p8x7-
zWpg$ivA3Oi`(u}jfZ(R^HNS=;Nma~&^}X<|j5Ddh3?H10X<2xB|ELdz&b{Bvp=D>L
zwN19=#iNK3nu!|R-&GUA`<df&w@^Xx6gy2K5azWlAZJ-t1VZHY&F0s&uLw{w<*>)4
zhjE-&K|tK%wa1nfcB2#D9u;?fDs=MV6Lk*UKDdGopq4s^#-+lfml+c{)5Gsh{sn9R
ziN7eyl9)I@9RFJhE;Tk4sk=*Tvks7*u#W9V=SZ-oD@asbtX&a$cN_N+Hk3}ocDih2
zal9QoU%s7h^ee5wvBhaCG1=2|_giD|`?ZWlh#5tkAxYP(aXiD2VXRp_W*m=EM1Cj%
zR)r8{<;xru#)@-ragl6VH~~paddCVwE}cTTYC;j#HDMkCQot0u2bK?nBppvB0F`<o
z@??@$8UmM!uM|z1j&i^|am6)zhIhI8k1#xwr`m@&+n$~fhN@??V(=Q9+RI5UNn1zf
zc34Tb5V#vzwQa7apvXk@FFH0O%Inej?W$R{FI4ThLA>8P>{&98ZF9!2k}nWAUiC42
zKyzrG)?k{c#qimbW#Y^*_AkoSZo}PBB(-r<&GAZPg(MDx?L02LOIuyXvlyJYP7$XG
zZu>=~o+QUTUMa>QbBg1fQW_0w;57xVj}L?n?I3U`AZ59fwf?GJLxI9MJY<n^a_*c!
z;<lcbt<r3$>kW$ISrMNpcS8^`&6K;xDfJRoT_aHOEXuXPxo4D6DGY{j0QsH(*Vy<>
zn4VG7QQg}hSNx@5e}kW{JP}aj2=(zF&=Ohk4kI;r2gDq&rtQdlwB=H>$qgSfSz0AD
z4I=$(*@cR5jvn#s-qFwN4IucWMq#kjr<lRfGoAdg%jBiV(lDO}N4%5#qf|)4w@9)g
z4X@z|$(#qhpO%YIMXn8&fZcfAqpk9feAL5#o;(DF&{UA4e_5V{ir??vZWIt91Gp^c
zl9GId-A;{U!NW~=RvhNb?~4X^ZD=E&fBf!mK&EN(ms<YHdUJ(m+<^2e!~B~wNZ(}M
z=zyR3R92QOXZsD`GGRK<Dmv)0ck$5BONUsl_o*VIEW*7w-LCxR>r#%6kf!u>D@Pw1
z$mVw0mh_g+&eA{ZcXwoa84rQ#eHp~%{5BmWq$b~@AB}Rvw@<KE_DeBjsx~>YF~k?Z
zzRb6(owZjsgapx5n4A2-=r@AWu-kuPFlSC$mLlFwL@3Bo^3~iE<WS`TiP`cl9_HCY
z`GV?D&Nh$5bUP)O?F~x40+0K=T>_ioXv;06@i_w70Cp!)ivLntK{|o4&|+6&vt9b}
zohZoz2l8xe3xICeAI&|ZpDjGEfIZxy3?c@kJi`oFry5IJmruR6OaB>PeXZWUwlHNh
z2#NEW5qEb|3+pmkvJym*?WDSWxhfUCu%0-N4C;F;2<%<)@ou@M^JV^-cWpQV7Jv!;
z$)R!wF&HiP=NoWFv#=kCJ)Ni9mI}F=18XWqpvvc)n+6)jqv0=W(=(&o{2V7j`?Y%q
zosWm6V|40bV-C#*D*GG*TC{qev0S<~)vj@k)s|quvy%`PT=5%QJR}|~mP%zhNxtz^
zVeV85WmkXNTP+~|OK3t0iU3#BP{-g`=ABa-m62jiTL~Vh3BO*_qJVYW!Tq+&pyp{I
zAVqRs={|?wDsRYAdZa1&u6S)NA5k;{C9d&pG74i=Ae?;<^Q3s+SuYMyApv*56F(*1
zdmDkW`F@M}?G;`@;JVBRYi55Eq)$#MPrIK4Yt<5C;dPk8H~eH)>9s4~t9C)JtT!z7
zjwWcKO9gZ(T_iozLfM}@5Z@ulJV>J4@RvqlT9#+J_{;C!C`-p02K00I-@V-D?8F94
zq@j`X3L5Wk;`6Kv(*1l96vpgGr!tTd-O{5~n9YTTMInl~3>GMua=lzxO#<1PP#BPS
ztPuhkm{lgzA5~}tg?7YbzavPK=*W7VZLMp0QnVt&S|RR_D~g5EbTjdw6s2sqFV-uN
zImM@7iVuK3&CDri|C!b?#uimNmkLR&MVNLbZG5OiVTEN{K7^}YMkIT6BP4u+r?Wg3
zRdt4NQSR~UhzD_GzMyoyKx*ES+vs!*_98U)n@b_kEF*F_A-i(g`J`&2n83GW*y`Ny
z(+vc(KhgsyJc#(XkV+VcuTo)Clk#<@Dn~au2dfh94Kvgod?6uW8WcMGAzTd+L2eDK
zL}P7#xqy3!@1SGq0UT!Oqzd%HHUA$f0oa>Mrvenyii-J&7+xi*r%S<W61;3&b>{V!
zcSl3%PcT*$zK05e{?%#N`nQ7t(Ia{D9V`c<2;g9@Mf7*rCBL!0BWakRkS3m3`_7cT
zYAOM9*P+A7+9A((#gn0=PMAORdxu=pbSZBrtBaxKbR2h7pN3A<*R&}JDm2M7(xHI%
zS?Vqt65t@-fA+D)uZq5SXqR5DG}tBBvuRe?QLHdj;H-kfm54M+zIVUdKO3g#FwF16
zF5b~L5qOsvePncp_a*GrEhMU(&Jjfre25G%TIOY}<SKK6mhWUHASu?~E%RW#xT{xK
zRW8F=f$bUP4n&CeWpT`dq~vJYl1qyC&y>|v7uGKMG$(J<etELLvvo}(dzA?ej`hP@
zOpnZA=X{9$a&X5dh9{H22Gfp1A2_AlF)0A)H;27TQwiX?Gzx!^ITsUPk%K!+DR0wk
zXRi{Q2D>O<X{ks{tL1x(mF>0B!qI5vl<&kRAaRPTlSL*xD8f!;^Vma=V;7{BWR@8n
zeN8R^3Z7Y-9P3tPo^E3B8Uzzi0-W4g&9-;3k&*)YPd(u2F7*<E(b9U%YQwZ-dyasD
z3CF?J<`9`oj>E{<6=7u>o#LTC_1JT-m<N(?m;=0xeyS2YNS*?IE*9OaL{Ex%)zkQZ
z2t0N;3&y4c#z{2t7y0g#_NB_xxbr+{unYAX#`Lq?QHhe$4z7WMSGT$LOwbO!{Ah5{
ztYS|h3m-V+AKZ^*Q*T421+S4__vomw0nc>Ov%?Czk7Rtj_tUdr_G7w#7p8+-K8ST+
z3}B;$1L_4<kQ0%>dEaeRVCCo6y+pJqBXAWIL{*O|g5Gt-*@u)5&3-EJc{aUg)&LUF
zgz4>2gL4nzRsQo<-Aka*X_?25F}&ERKEDXztI>(!FgC#Fn=p^QT2{|Vaj$fBOl1~w
zdz^~lWx_hn8BcnzV>lCdE_7MEOe5LEiagZOj0j>*gvb2Spz<MM&44=**%j3@FZLiL
zslOp43ukw#&w>Dmc+#rKT)e@fw<*4WP4B)MHhgXml(PQ^l<IR%1t{s|?b#jxyorNm
zSw38BN8)~do9dnUeOO>pb&_ai{?_Fh@exmb9zd2zcLJP!hm#Co;N`5-sXb^-0^np1
zN~~+^trB}OqTCDRplnyzb0eO+7s{h*&0qZZDwtor4$=?-hYuKa^#qt2c%N7}Gi<H%
z=G=zw_S5Cq`+NWgoZY<5VS^OOPYR$e44;62!jIkVfGJ`y7$Qi}EkAC?gkN=|c)D9;
zc5JYul0aZ~4uV8mihB*OPQ1eK#+}k6o^q!RIA?@0+Lt?8nSkKRkYr~)Cu;f6vx6zS
zgz5dD;dZt<FlO(TZ4192gCCqTj8qnLp2z{GcLmRdm%zVato4cPoSI*<&NS8+IYAAU
z52mGQkZ)h>eB4@d?t`(Ey7~DHKZxqM$05qsQ1A6!%)Hd<2z7O5;@-{-pi4{#?0NV<
zpGG5b$bqkQrMgDGsL`t(t)Teb`l8VjXn(k}q;&L#F=(3yHxZ5Dt??X6F4DX7Z*2GG
z|HO6|9&%gPc(m>9<~yt_0lp3bVGJe|K04R->_y0=j4$<~6r05GuWoc*kDnHIuv>yg
z=%()`Kl4!GNuMafRHUheXUV?j{WqkZ@t=_T!ktFyF5Rz!Ii3WC$A>B6b6@LFDxnic
z(eEXm7z^bdF46Gd{KRu1NU~0T9Qa`{3j=SE>BRY=+P`IXlt+a?cIkL0P|AWZ`<KI_
z^~irZEI>BFPC2GTHlLY6;1ZIP34l_PB7Iu+V_u~UKFNXeJI@7mg}NOht>m$WQ^T|Z
z;9((Ne8g2?*9OMLvhnnVGE&w3Q}NX!Csln}UB#qnve?F+6N91;#1!gA5CwtUs(<Bx
zH%CZ+CPiXN<tos2PgCTHDTPgQhX+00uAU-1k?S}wnuNm6%F5pkgj9;-L5$V<TfZ)B
zK4a1%62hZ{DNdEUt$zTLhXbJ%$w%#R&H8g*nJtt(_q_xaW#e6GIYVnm*`)7mJEedc
zIjuZO1TsWIMc>aPL7jg=*MyJiBQ<p>SJ5f0;KrL6mm(Cs&n>h&_#H1O&3DeBPnnkQ
zWoT}DY7r}4e(ddPpn+`@fX<~Zu{$T*C@ejXB+E(o!pp;oTlZE<Pr?O>>jk;o%u`C^
zT@SBZYI(X?@&p+^bV6M0LEL6(e8Ax17fF2Yz_{(;!y|%<%niIU799j;0noya)pu(V
z_p^U|VhEA|l7T{(eD>xPIx1s`ogI64lk%~ITN!#oAwFQ%ZTrN1OC~7&>H=_9tBQDy
z6xXl)xXH;jNnOF(9wfTouyyBz9PRGM-nOe4I_+m2r5QuL{Euh7ojFpLz^(vbmHzof
zNauD0@kZrCs4mFmZp=J?&3_Ds3ozaCkxn5!=UOInEjng(OXF`J>mWrQ<R;jugW4=(
z5~FyCpd}qMT~R;;8T~qPUVgPTFPWnNfL<`t<blIgs)_A4L24w^M-elw-5lD;RQa=e
zeIL+kRzqrlr$JZ2qoQj8X2tfL;yvc1A|ro6#4<Y{4ol#YXgKmKcDx~W+`41C^2}x8
za6<qpf~f*|_OW&=(=xO}t$OVg?X<o$ra@ovJu9&^el`ca#GhU_a3V*H?}&`5&ol1~
z?Rmkn>e%t?;^QGBTR}6OBH@S@=Blxr<~y8^8K5<Im*LMlSGawCl?WXJjhJ^@y&-&-
zb&t6(d!eq#p{#LMF#b)*ctz>$#wSkaP4Va9;Qsfhm?;{%senzBf5zGPm|ZYj>;0G>
zlTCvzd04%JLzF7K-d8VmT%4o-PZ6z7wFTZ1-fNUseB^)gaSWL9emcS)zg6x?=TZp+
zgp1Mb7B#jPHQigyiBD!#5Vo3rMKV=ON{;WJUXjAy79WRqn5QweTy^zf+MTf?g&+k+
z+*RgOx<nO`dOv@4ijZC^H1a6!cP5!t2KoV{lekApg8tv6q?o-a$p6EVQVDO{_v~|R
z{2oi{in}p{KKivjUExF+oIm~LHQQAaJk;KR)fmQ=e?KxcKYhrT`*XXV_;@ke!CNJK
zC&Yal3I@W`Wvht1dAeoEn^vLEgh1VBUm62nplh|)%VlV*(m=92R@13!0+DV>E#jj-
zzvxrf=jSFFYOx$;PUn$n8v3CtA1(vKq-XjJ7qh*keF)S6^`NDEt2(!Wld2)9M39iG
z!~V0ZrA@8bGS>`nL{(3{<hxN%JrVf)b3#b?l2-Sc^e8E$g|Ij*E1+=k(Gl~-;*iS#
zwi64z#1wg5<$IWJJ?-7^9IKy(yh-#9Z4qCHky*{=FwvKyN_1LlBewrU7Y{Uxh0`24
z2d<77Hgj*>m|!F>!kZN~Mi8TgaM&p)o38Z)rY{a?84iN<@r}t8yW-LipWQ9jRiI8R
z{il|RHmi8z%A1m6Sef9%-k+@V-Xt+{LyVr*lbDIE?x5u5vvk|OmJn{jVrTZm-Q-2<
z=75bIXCY>jx^rNNj&UM7rJtH>Et-@uSf<Zz@B948m4C;aM6wnMG#vg)d@4h8eEiXV
z44}S)D&fIse#=G_|K@Cp!Nn`@CFuHn@fnipc4}N{9+^7o@vt?+KGJl^LG1rnBxY|s
zL-zQ*gtF#MBhQ_`DnFj}9Q^Tt7~082bSW7#Zb59eRL>Bt@#hJERC0BAFd#_=`aOQ8
z9!^fanP9-Gj}0JQAaM!y>KDV~F2-GsGmbNjL&c%vtmAf*A!kdLZ)%vf8U4o!GW$He
zdmr;eG+*zY@2vID4nen-)DIR0&n@PyI&!od_AJZ0Yg(W3nt<_Zp;o&<+>!kT_d|VW
z_Z!cNCbn0Gn^EJfb4t^<j{D8`$eO(rSiIgbfv1k3^>Nqap5IxGI6E^Fr71UlEs$0?
zL*!?CG?&#uMU8Az`BCGHvMuXVDfCIdrQo3KM!T9bL%8|w-;+KQ%M(76%g#FALm10;
zhfJ?CBqe+*S}H~>b_wI%rT@KUI<Y62GdTQbDj}p5FrW*6FOHUJUvIh5RCg;EuN+iy
za%J!||ETtjVFrT{<HBN_n34Tk_=`2&eP9CqX#=+`MMw3?GMiu_6tUcz9+2=``Ej*+
z8JFp{ivQ?lG9X0q8DjdE0Bx!=xGG4AH0k$@7$6Y)zgIY%-k14lBVl<@4iorOA-6;`
zhfOlh7ilx*lvY7j$rpfx<@VOvyC`UxUIb#L5b2$9=8=y$ElgL!uSxu#e-89TE^oc~
zi*>GfbCPO_`EsY#3&0OB2LZ^c>N`Fbp{e*?A9OX5_&pPCM4tfPb&yR~3Y9!N8M+ik
zi}Nt8ksV6uPwdC_KkHA~PS{TLAFc?m$Y!paFM(;c-7|~?J_P2EodtmO%-?C2RHmf}
p{>NgE-}{4Q-@X4wp{=~Z-F;h55Us2a?>;c=!Szipm0qxq_%EcAi=hAj
literal 0
HcmV?d00001
diff --git a/doc/guides/prog_guide/img/feature_arc-2.png b/doc/guides/prog_guide/img/feature_arc-2.png
new file mode 100644
index 0000000000000000000000000000000000000000..604a67229b29a9c8ace59d28a7324e02c2bc4a89
GIT binary patch
literal 155806
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