Skip to contentSkip to conversation

Building a better rte_flow parser

PATCH Superseded Anatoly Burakov 6 participants 243 messages Aug 20 – Oct 20 Page 2 of 5

Subsystems (2)
lore
Share & tools
lore
#49 lore raw

PATCH v2 18/19 net/i40e: reimplement hash parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS.

The RSS parser was bypassing the other generic infrastructure, probably because it was very convoluted and did not map onto that model very well. It has now been made a first-class citizen.

The hash parser is multiple engines that share state, so each engine will attach to the shared refcounted RSS configuration state.

Additionally, this is the final engine in i40e, so remaining legacy flow infrastructure has been removed.

---
 drivers/net/intel/i40e/i40e_ethdev.c    |   34 +-
 drivers/net/intel/i40e/i40e_ethdev.h    |   34 +-
 drivers/net/intel/i40e/i40e_flow.c      |  289 +----
 drivers/net/intel/i40e/i40e_flow.h      |    3 +
 drivers/net/intel/i40e/i40e_flow_hash.c | 1441 +++++++++++++++++++++++
 drivers/net/intel/i40e/i40e_hash.c      | 1208 +------------------
 drivers/net/intel/i40e/i40e_hash.h      |   19 +-
 drivers/net/intel/i40e/meson.build      |    1 +
 8 files changed, 1549 insertions(+), 1480 deletions(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow_hash.c
drivers/net/intel/i40e/i40e_ethdev.c +21 -13
index 97211b5994..8c35a64780 100644
@@ -1027,6 +1027,27 @@ i40e_tunnel_state_detach(struct i40e_tunnel_state *state)
 	*state = (struct i40e_tunnel_state){0};
 }
 
+struct i40e_rss_state *
+i40e_rss_state_attach(struct rte_eth_dev_data *dev_data)
+{
+	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev_data->dev_private);
+	struct i40e_rss_state *state = &pf->rss_state;
+
+	if (state->refcnt == 0)
+		TAILQ_INIT(&state->list);
+
+	state->refcnt++;
+	return state;
+}
+
+void
+i40e_rss_state_detach(struct i40e_rss_state *state)
+{
+	if (--state->refcnt > 0)
+		return;
+
+	*state = (struct i40e_rss_state){0};
+}
 static void
 i40e_init_customized_info(struct i40e_pf *pf)
 {
@@ -1630,9 +1651,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	 */
 	i40e_add_tx_flow_control_drop_filter(pf);
 
-	/* initialize RSS rule list */
-	TAILQ_INIT(&pf->rss_config_list);
-
 	/* initialize Traffic Manager configuration */
 	i40e_tm_conf_init(dev);
 
@@ -1781,8 +1799,6 @@ i40e_dev_configure(struct rte_eth_dev *dev)
 		}
 	}
 
-	TAILQ_INIT(&pf->flow_list);
-
 	return 0;
 
 err_dcb:
@@ -2450,7 +2466,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
 	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
 	struct i40e_filter_control_settings settings;
-	struct rte_flow *p_flow;
 	uint32_t reg;
 	int i;
 	int ret;
@@ -2541,12 +2556,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 		i40e_msec_delay(500);
 	} while (retries++ < 5);
 
-	/* Remove all flows */
-	while ((p_flow = TAILQ_FIRST(&pf->flow_list))) {
-		TAILQ_REMOVE(&pf->flow_list, p_flow, node);
-		rte_free(p_flow);
-	}
-
 	/* release the fdir static allocated memory */
 	i40e_fdir_memory_cleanup(pf);
 
@@ -11216,7 +11225,6 @@ static void
 i40e_filter_restore(struct i40e_pf *pf)
 {
 	i40e_fdir_filter_restore(pf);
-	(void)i40e_hash_filter_restore(pf);
 }
 
 bool
drivers/net/intel/i40e/i40e_ethdev.h +17 -17
index 9d68d8fd0f..782b85e19c 100644
@@ -281,9 +281,6 @@ enum i40e_flxpld_layer_idx {
  */
 struct rte_flow {
 	struct ci_flow base;
-	TAILQ_ENTRY(rte_flow) node;
-	enum rte_filter_type filter_type;
-	void *rule;
 };
 
 /**
@@ -953,8 +950,6 @@ struct i40e_tunnel_filter_conf {
 	uint16_t vf_id;         /**< VF id, available when is_to_vf is 1. */
 };
 
-TAILQ_HEAD(i40e_flow_list, rte_flow);
-
 /* Struct to store Traffic Manager shaper profile. */
 struct i40e_tm_shaper_profile {
 	TAILQ_ENTRY(i40e_tm_shaper_profile) node;
@@ -1076,14 +1071,23 @@ struct i40e_rss_filter_data {
 	uint64_t reset_symmetric_pctypes;
 };
 
-/* RSS filter list structure */
-struct i40e_rss_filter {
-	TAILQ_ENTRY(i40e_rss_filter) next;
-	struct i40e_rte_flow_rss_conf rss_filter_info;
+/*
+ * Tracking entry for a registered RSS flow: points back to the flow that
+ * owns the rss_conf, plus the metadata recording which HW state it owns.
+ */
+struct i40e_rss_flow_node {
+	TAILQ_ENTRY(i40e_rss_flow_node) next;
+	struct ci_flow *flow;
 	struct i40e_rss_filter_data filter_data;
 };
 
-TAILQ_HEAD(i40e_rss_conf_list, i40e_rss_filter);
+TAILQ_HEAD(i40e_rss_node_list, i40e_rss_flow_node);
+
+/* RSS flow engine shared state, attached/detached by the RSS engines */
+struct i40e_rss_state {
+	struct i40e_rss_node_list list;
+	uint16_t refcnt;
+};
 
 struct i40e_vf_msg_cfg {
 	/* maximal VF message during a statistic period */
@@ -1160,13 +1164,12 @@ struct i40e_pf {
 
 	struct i40e_fdir_info fdir; /* flow director info */
 	struct i40e_tunnel_state tunnel_state; /* tunnel flow engine state */
-	struct i40e_rss_conf_list rss_config_list; /* RSS rule list */
+	struct i40e_rss_state rss_state; /* RSS flow engine state */
 	struct i40e_queue_regions queue_region; /* queue region info */
 	struct i40e_fc_conf fc_conf; /* Flow control conf */
 	bool floating_veb; /* The flag to use the floating VEB */
 	/* The floating enable flag for the specific VF */
 	bool floating_veb_list[I40E_MAX_VF];
-	struct i40e_flow_list flow_list;
 	/* flow engine configuration */
 	struct ci_flow_engine_conf flow_engine_conf;
 	bool mpls_replace_flag;  /* 1 - MPLS filter replace is done */
@@ -1305,11 +1308,6 @@ struct i40e_vf_representor {
 
 extern const struct rte_flow_ops i40e_flow_ops;
 
-struct i40e_filter_ctx {
-	struct i40e_rte_flow_rss_conf rss_conf;
-	enum rte_filter_type type;
-};
-
 int i40e_dev_switch_queues(struct i40e_pf *pf, bool on);
 int i40e_vsi_release(struct i40e_vsi *vsi);
 struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf,
@@ -1409,6 +1407,8 @@ int i40e_tunnel_filter_match_key_get(struct i40e_pf *pf,
 				 struct i40e_tunnel_filter_match_key *out);
 struct i40e_tunnel_state *i40e_tunnel_state_attach(struct rte_eth_dev_data *dev_data);
 void i40e_tunnel_state_detach(struct i40e_tunnel_state *state);
+struct i40e_rss_state *i40e_rss_state_attach(struct rte_eth_dev_data *dev_data);
+void i40e_rss_state_detach(struct i40e_rss_state *state);
 int i40e_tunnel_filter_program(struct i40e_pf *pf,
 			       struct i40e_tunnel_filter_conf *tunnel_filter,
 			       uint8_t add);
drivers/net/intel/i40e/i40e_flow.c +10 -279

347 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h +3 -0
index 28e342210c..31269d4d93 100644
@@ -23,5 +23,8 @@ extern const struct ci_flow_engine i40e_flow_engine_tunnel_nvgre;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_mpls;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_gtp;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_l4;
+extern const struct ci_flow_engine i40e_flow_engine_hash_pattern;
+extern const struct ci_flow_engine i40e_flow_engine_hash_vlan;
+extern const struct ci_flow_engine i40e_flow_engine_hash_empty;
 
 #endif /* _I40E_FLOW_H_ */
drivers/net/intel/i40e/i40e_flow_hash.c new file +1441 -0

1,441 lines not on this page · raw message

drivers/net/intel/i40e/i40e_hash.c +50 -1158

1,056 lines not on this page · raw message

drivers/net/intel/i40e/i40e_hash.h +6 -13
index 3bf30cdee5..c1ce372b6e 100644

Lines of this file are not shown. View the raw message.

drivers/net/intel/i40e/meson.build +1 -0
index 0db60c1e99..07162a93b3 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.52.0

Part of the diff is not shown here. View the raw message.

#50 lore raw

PATCH v2 19/19 net/i40e: advertise flow keep capability

In reply to Anatoly Burakov

The i40e driver's rte_flow implementation has always endeavored to keep flows across device restart, however it never advertised this capability. Advertise it explicitly.

---
 drivers/net/intel/i40e/i40e_ethdev.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/net/intel/i40e/i40e_ethdev.c +2 -1
index 8c35a64780..746fdd564e 100644
@@ -3662,7 +3662,8 @@ i40e_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->dev_capa =
 		RTE_ETH_DEV_CAPA_RUNTIME_RX_QUEUE_SETUP |
 		RTE_ETH_DEV_CAPA_RUNTIME_TX_QUEUE_SETUP;
-	dev_info->dev_capa &= ~RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+	/* rte_flow rules are kept across dev_stop/dev_start and replayed on start */
+	dev_info->dev_capa |= RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
 
 	dev_info->hash_key_size = (I40E_PFQF_HKEY_MAX_INDEX + 1) *
 						sizeof(uint32_t);
Signature · 1 line
2.52.0
#51 lore raw
In reply to Anatoly Burakov
Quoted 36 lines · Anatoly Burakov

Most rte_flow parsers in DPDK suffer from huge implementation complexity because even though 99% of what people use rte_flow parsers for is parsing protocol graphs, no parser is written explicitly as a graph. This patchset attempts to suggest a viable model to build rte_flow parsers as graphs, by offering a lightweight header only library to build rte_flow parsering graphs without too much boilerplate and complexity.

Most of the patchset is about Intel drivers, but they are meant as reimplementations as well as examples for the rest of the community to assess how to build parsers using this new infrastructure. I expect the first two patches will be of most interest to non-Intel reviewers, as they deal with building two reusable parser architecture pieces.

The first piece is a new flow graph helper in ethdev. Its purpose is deliberately narrow: it targets the protocol-graph part of rte_flow pattern parsing, where drivers walk packet headers and validate legal item sequences and parameters. That does not cover all possible rte_flow features, especially more exotic flow items, but it does cover a large and widely shared part of what existing drivers need to do. Or, to put it in other words, the only flow items this infrastructure *doesn't* cover is things that do not lend themselves well to be parsed as a graph of protocol headers (e.g. conntrack items). Everything else should be covered or cover-able. In practice, just about all drivers will benefit from graph parsing as all but one of them implement only the protocol stack parts, which are the ones targeted by the graph helper.

The second piece is a reusable flow engine framework for Intel Ethernet drivers. This is kept Intel-local because I do not feel it is even appropriate to define such a framework for all drivers to use in the first place. Even so, the intent is to establish a cleaner parser architecture with a defined interaction model, explicit memory ownership rules, locking, initialization sequence, implementations of rte_flow API entry points, flow replay and memory cleanup, and engine definitions that do not block secondary-process-safe usage. It is my hope that this would serve as a model for other drivers to follow, expand on, rework, and improve, so that maybe down the line we *might* have a common rte_flow infrastructure for drivers to use.

Quoted 9 lines

Most of the rest of the series is parser reimplementation, but that is mainly the vehicle for demonstrating and validating those two pieces. ixgbe and i40e are wired into the new common parsing path, and their existing parsers are migrated incrementally to the graph-based model. Besides reducing ad hoc parser code, this also makes validation more explicit and more consistent. In a few places that means invalid inputs that were previously ignored, deferred, or interpreted loosely are now rejected earlier and more strictly, without any increase in code complexity (in fact, with marked *decrease* of it!).

Recheck-request: rebase=next-net-intel, iol-compile-amd64-testing, iol-intel-Functional, iol-intel-Performance

Signature · 2 lines
Thanks,
Anatoly
#52 lore raw

v3 cover Building a better rte_flow parser

In reply to Anatoly Burakov

Most rte_flow parsers in DPDK suffer from huge implementation complexity because even though 99% of what people use rte_flow parsers for is parsing protocol graphs, no parser is written explicitly as a graph. This patchset attempts to suggest a viable model to build rte_flow parsers as graphs, by offering a lightweight header only library to build rte_flow parsering graphs without too much boilerplate and complexity.

Most of the patchset is about Intel drivers, but they are meant as reimplementations as well as examples for the rest of the community to assess how to build parsers using this new infrastructure. I expect the first two patches will be of most interest to non-Intel reviewers, as they deal with building two reusable parser architecture pieces.

The first piece is a new flow graph helper in ethdev. Its purpose is deliberately narrow: it targets the protocol-graph part of rte_flow pattern parsing, where drivers walk packet headers and validate legal item sequences and parameters. That does not cover all possible rte_flow features, especially more exotic flow items, but it does cover a large and widely shared part of what existing drivers need to do. Or, to put it in other words, the only flow items this infrastructure *doesn't* cover is things that do not lend themselves well to be parsed as a graph of protocol headers (e.g. conntrack items). Everything else should be covered or cover-able. In practice, just about all drivers will benefit from graph parsing as all but one of them implement only the protocol stack parts, which are the ones targeted by the graph helper.

The second piece is a reusable flow engine framework for Intel Ethernet drivers. This is kept Intel-local because I do not feel it is even appropriate to define such a framework for all drivers to use in the first place. Even so, the intent is to establish a cleaner parser architecture with a defined interaction model, explicit memory ownership rules, locking, initialization sequence, implementations of rte_flow API entry points, flow replay and memory cleanup, and engine definitions that do not block secondary-process-safe usage. It is my hope that this would serve as a model for other drivers to follow, expand on, rework, and improve, so that maybe down the line we *might* have a common rte_flow infrastructure for drivers to use.

Most of the rest of the series is parser reimplementation, but that is mainly the vehicle for demonstrating and validating those two pieces. ixgbe and i40e are wired into the new common parsing path, and their existing parsers are migrated incrementally to the graph-based model. Besides reducing ad hoc parser code, this also makes validation more explicit and more consistent. In a few places that means invalid inputs that were previously ignored, deferred, or interpreted loosely are now rejected earlier and more strictly, without any increase in code complexity (in fact, with marked *decrease* of it!).

v2 -> v3:
- Rebase on top of latest next-net-intel
- Cleanup pass on naming of engines and hash tables
- Added more logging to flow engine initializations
- Introduced a lightweight refcount to initialize shared resources
- Some small refactors in how ixgbe flow director engine keeps its state
- Fixed typos in documentation

v1 -> v2:
- Renamed rte_flow_graph to flow_graph
- Addressed some comments from Stephen's AI review
- Removed the rte_flow_conv hack on account of Stephen's upcoming patches making the API stable
- Split up "flow_install" into register and install steps, which enabled two new use cases: cleanup of memory without HW deregistration, and flow configuration replay after dev_start
- More aggressive refactoring of all engines on account of new pieces of infrastructure, which allows for reduced complexity of implementation
- Combined i40e tunnel engine patches into one patch
- Added i40e flow director refactoring to cleanly support the new API as well as the legacy PMD-specific API

RFC -> v1:
- Fixed a lot of bugs, concurrency issues, and init/uninit sequence problems in Intel common flow engine implementation
- Fixed a few copypaste errors in various engines
- Improved comment documentation and flow engine docs

Anatoly Burakov (19):
ethdev: add flow graph API net/intel/common: add flow engines infrastructure net/intel/common: add utility functions net/ixgbe: add support for common flow parsing net/ixgbe: reimplement ethertype parser net/ixgbe: reimplement syn parser net/ixgbe: reimplement L2 tunnel parser net/ixgbe: reimplement ntuple parser net/ixgbe: reimplement security parser net/ixgbe: reimplement FDIR parser net/ixgbe: reimplement hash parser net/ixgbe: advertise flow keep capability net/i40e: add support for common flow parsing net/i40e: reimplement ethertype parser net/i40e: refactor FDIR engine infrastructure net/i40e: reimplement FDIR parser net/i40e: reimplement tunnel parsers net/i40e: reimplement hash parser net/i40e: advertise flow keep capability

 doc/guides/prog_guide/ethdev/flow_graph.rst   |  749 +++
 doc/guides/prog_guide/ethdev/index.rst        |    1 +
 doc/guides/rel_notes/release_26_11.rst        |    5 +
 drivers/net/intel/common/flow_engine.h        | 1620 +++++++
 drivers/net/intel/common/flow_util.h          |  228 +
 drivers/net/intel/i40e/i40e_ethdev.c          | 1000 +---
 drivers/net/intel/i40e/i40e_ethdev.h          |  262 +-
 drivers/net/intel/i40e/i40e_fdir.c            |  983 ++--
 drivers/net/intel/i40e/i40e_flow.c            | 4185 +----------------
 drivers/net/intel/i40e/i40e_flow.h            |   30 +
 drivers/net/intel/i40e/i40e_flow_ethertype.c  |  351 ++
 drivers/net/intel/i40e/i40e_flow_fdir.c       | 2056 ++++++++
 drivers/net/intel/i40e/i40e_flow_hash.c       | 1441 ++++++
 drivers/net/intel/i40e/i40e_flow_tunnel.c     | 1590 +++++++
 drivers/net/intel/i40e/i40e_hash.c            | 1208 +----
 drivers/net/intel/i40e/i40e_hash.h            |   19 +-
 drivers/net/intel/i40e/meson.build            |    4 +
 drivers/net/intel/i40e/rte_pmd_i40e.c         |    4 +-
 drivers/net/intel/ixgbe/ixgbe_ethdev.c        |  993 +---
 drivers/net/intel/ixgbe/ixgbe_ethdev.h        |  235 +-
 drivers/net/intel/ixgbe/ixgbe_fdir.c          |  304 +-
 drivers/net/intel/ixgbe/ixgbe_flow.c          | 3217 +------------
 drivers/net/intel/ixgbe/ixgbe_flow.h          |   27 +
 .../net/intel/ixgbe/ixgbe_flow_ethertype.c    |  295 ++
 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c     | 1704 +++++++
 drivers/net/intel/ixgbe/ixgbe_flow_hash.c     |  212 +
 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c    |  317 ++
 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c   |  597 +++
 drivers/net/intel/ixgbe/ixgbe_flow_security.c |  536 +++
 drivers/net/intel/ixgbe/ixgbe_flow_syn.c      |  312 ++
 drivers/net/intel/ixgbe/ixgbe_ipsec.c         |  359 +-
 drivers/net/intel/ixgbe/ixgbe_ipsec.h         |   50 +-
 drivers/net/intel/ixgbe/ixgbe_pf.c            |   49 +-
 drivers/net/intel/ixgbe/ixgbe_rxtx.c          |   48 +-
 drivers/net/intel/ixgbe/meson.build           |    7 +
 lib/ethdev/flow_graph.h                       |  507 ++
 lib/ethdev/meson.build                        |    1 +
 37 files changed, 14143 insertions(+), 11363 deletions(-)
 create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst
 create mode 100644 drivers/net/intel/common/flow_engine.h
 create mode 100644 drivers/net/intel/common/flow_util.h
 create mode 100644 drivers/net/intel/i40e/i40e_flow.h
 create mode 100644 drivers/net/intel/i40e/i40e_flow_ethertype.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_fdir.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_hash.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_tunnel.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow.h
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_hash.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_security.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_syn.c
 create mode 100644 lib/ethdev/flow_graph.h
Signature · 1 line
2.52.0
#53 lore raw

PATCH v3 1/19 ethdev: add flow graph API

This commit adds a flow graph parsing API. This is a helper API intended to help ethdev drivers implement rte_flow parsers, as common usages map to graph traversal problem very well.

Features provided by the API:
- Flow graph, edge, and node definitions
- Graph traversal logic
- Declarative validation against common flow item types
- Per-node validation and state processing callbacks

---
 doc/guides/prog_guide/ethdev/flow_graph.rst | 749 ++++++++++++++++++++
 doc/guides/prog_guide/ethdev/index.rst      |   1 +
 doc/guides/rel_notes/release_26_11.rst      |   5 +
 lib/ethdev/flow_graph.h                     | 507 +++++++++++++
 lib/ethdev/meson.build                      |   1 +
 5 files changed, 1263 insertions(+)
 create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst
 create mode 100644 lib/ethdev/flow_graph.h
doc/guides/prog_guide/ethdev/flow_graph.rst new file +749 -0
new file mode 100644
index 00000000000..cc6b06ccb4a
@@ -0,0 +1,749 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Intel Corporation
+
+Flow Graph Parser
+=================
+
+Introduction
+------------
+
+The flow graph parser is a helper library for PMD drivers that implements ``rte_flow`` pattern matching.
+It lets a driver declare the protocol sequences it supports as a directed graph of nodes and edges.
+It then validates and extracts fields from an ``rte_flow_item`` pattern in a single traversal.
+
+The library is defined in ``flow_graph.h`` and is header-only.
+
+Scope and Limitations
+~~~~~~~~~~~~~~~~~~~~~
+
+Because the parser is graph-based, it is well suited for matching *protocol stacks*.
+These are sequences of protocol headers such as ``ETH / IPv4 / TCP``.
+Any pattern that can be expressed as "from protocol A, transitions to protocol B or C are allowed" fits naturally into the graph model.
+
+The library is **not** designed to cover every ``rte_flow`` item type.
+Items that do not represent a position in a protocol stack do not have a natural place in a protocol graph.
+This includes conntrack state, meter color, and other metadata items.
+Such items are best handled outside the graph, either before or after the graph parse call.
+
+Defining a Graph
+----------------
+
+A graph consists of three parts:
+
+1. An **enum** that assigns a numeric index to every node.
+2. A **node array** (``struct flow_graph_node[]``) indexed by that enum.
+3. An **edge array** (``struct flow_graph_edge[]``) also indexed by that enum, describing allowed transitions.
+
+These parts are bundled together in a ``struct flow_graph``.
+
+The running example used throughout this guide models the following protocol graph::
+
+   START -> ETH -> [VLAN] -> (IPv4 | IPv6) -> [(TCP | UDP | SCTP)] -> END
+
+Brackets ``[...]`` denote optional items.
+Parentheses ``(...)`` denote a required choice between alternatives.
+The key ideas are:
+
+* ``ETH`` is required after ``START``.
+* After ``ETH``, an optional ``VLAN`` may appear, but the pattern must then see an IP layer.
+* After an IP layer, an optional transport layer may appear; it may be TCP, UDP, or SCTP, after which the pattern reaches ``END``.
+
+Node Enum
+~~~~~~~~~
+
+Every node needs a stable index.
+The first node **must** be at index ``FLOW_GRAPH_NODE_FIRST`` (which is 0).
+This is the *start node*.
+It is used only as a traversal anchor and must not carry callbacks.
+
+.. code-block:: c
+
+   enum example_node_id {
+       EXAMPLE_NODE_START = FLOW_GRAPH_NODE_FIRST,
+       EXAMPLE_NODE_ETH,
+       EXAMPLE_NODE_VLAN,
+       EXAMPLE_NODE_IPV4,
+       EXAMPLE_NODE_IPV6,
+       EXAMPLE_NODE_TCP,
+       EXAMPLE_NODE_UDP,
+       EXAMPLE_NODE_SCTP,
+       EXAMPLE_NODE_END,
+       /* keep last */
+       EXAMPLE_NODE_MAX,
+   };
+
+Node Definitions
+~~~~~~~~~~~~~~~~
+
+Each node maps to one ``rte_flow_item_type``.
+It can also carry a *validate* callback, a *process* callback, and a set of *constraints*.
+The the ``END`` node can also have callbacks to perform end-of-match processing.
+
+A minimal skeleton (callbacks and constraints are added in later sections):
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+               /* Start node: no type, no callbacks */
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name  = "ETH",
+               .type  = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name  = "VLAN",
+               .type  = RTE_FLOW_ITEM_TYPE_VLAN,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name  = "IPV4",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name  = "IPV6",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV6,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name  = "TCP",
+               .type  = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name  = "UDP",
+               .type  = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name  = "SCTP",
+               .type  = RTE_FLOW_ITEM_TYPE_SCTP,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name  = "END",
+               .type  = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+   };
+
+Edge Definitions
+~~~~~~~~~~~~~~~~
+
+Edges express which nodes may follow the current one.
+Every edge list is terminated by the ``FLOW_GRAPH_NODE_EDGE_END`` sentinel.
+All non-``END`` nodes **must** have an edge list.
+The ``END`` node itself does not need one.
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           /* ... same nodes as above ... */
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+Reading the edges back:
+
+* From ``START``, the parser can only reach ``ETH``, which makes ``ETH`` required.
+* From ``ETH``, the parser can reach ``VLAN``, ``IPV4``, or ``IPV6``, which makes ``VLAN`` optional.
+* From ``IPV4`` or ``IPV6``, the parser can reach ``TCP``, ``UDP``, ``SCTP``, or ``END``, which makes the transport layer optional.
+
+Assembling the Graph
+~~~~~~~~~~~~~~~~~~~~
+
+With nodes and edges defined inline, assembling the graph is just a matter of
+combining the two arrays into a single compound literal:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START"
+           },
+           [EXAMPLE_NODE_ETH]   = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH
+           },
+           /* ... remaining nodes ... */
+           [EXAMPLE_NODE_END]   = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END
+               }
+           },
+           /* ... remaining edges ... */
+       },
+   };
+
+Callbacks
+---------
+
+The graph calls up to two callbacks on every visited node: *validate* and *process*.
+
+Both callbacks share the same return convention.
+On success they must return ``0``.
+On failure they must call ``rte_flow_error_set`` to record a descriptive error and return its result.
+
+Validate Callback
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*flow_graph_node_validate_fn)(
+       const void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **read-only** context pointer.
+The canonical intent is that it should check whether the item's spec, mask, and last values are acceptable for the driver.
+On failure it returns the result of ``rte_flow_error_set`` (see the return convention above).
+
+It is recommended to use this callback for **all checks that can reject a rule**.
+This includes unsupported mask bits, conflicting field combinations, hardware limitations, and other applicable criteria.
+
+Process Callback
+~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*flow_graph_node_process_fn)(
+       void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **mutable** context pointer.
+The canonical expectation is that it should extract the fields needed for hardware programming.
+It should then store extracted data in the driver's context structure.
+On the rare failure path it returns the result of ``rte_flow_error_set`` (see the return convention above).
+
+It is recommended to use this callback for the **happy path**.
+For example, it can copy addresses, ports, and protocol IDs into the driver context so they can be programmed later.
+
+Defining a Context Structure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The opaque ``ctx`` pointer passed to every callback is driver-defined.
+A typical context accumulates the parsed protocol fields:
+
+.. code-block:: c
+
+   struct example_parsed_flow {
+       /* L2 */
+       struct rte_ether_addr dst_mac;
+       bool has_vlan;
+       uint16_t vlan_tci;
+
+       /* L3 */
+       bool is_ipv6;
+       rte_be32_t ipv4_src;
+       rte_be32_t ipv4_dst;
+       uint8_t ipv6_src[16];
+       uint8_t ipv6_dst[16];
+
+       /* L4 */
+       enum rte_flow_item_type l4_proto;
+       rte_be16_t src_port;
+       rte_be16_t dst_port;
+   };
+
+These fields are meant to reflect the structure used by the driver to programming hardware with.
+
+Callback Example
+~~~~~~~~~~~~~~~~
+
+Below is a validate/process pair for the IPv4 node.
+The validate callback rejects unsupported mask bits.
+The process callback copies addresses into the context:
+
+.. code-block:: c
+
+   static int
+   example_validate_ipv4(const void *ctx __rte_unused,
+                         const struct rte_flow_item *item,
+                         struct rte_flow_error *error)
+   {
+       const struct rte_flow_item_ipv4 *mask = item->mask;
+
+       if (mask->hdr.version_ihl ||
+           mask->hdr.type_of_service ||
+           mask->hdr.total_length ||
+           mask->hdr.packet_id ||
+           mask->hdr.fragment_offset ||
+           mask->hdr.time_to_live ||
+           mask->hdr.next_proto_id ||
+           mask->hdr.hdr_checksum) {
+           return rte_flow_error_set(error, EINVAL,
+                   RTE_FLOW_ERROR_TYPE_ITEM, item,
+                   "Only src/dst addresses supported");
+       }
+       return 0;
+   }
+
+   static int
+   example_process_ipv4(void *ctx,
+                        const struct rte_flow_item *item,
+                        struct rte_flow_error *error __rte_unused)
+   {
+       struct example_parsed_flow *parsed = ctx;
+       const struct rte_flow_item_ipv4 *spec = item->spec;
+
+       parsed->is_ipv6 = false;
+       if (spec != NULL) {
+           parsed->ipv4_src = spec->hdr.src_addr;
+           parsed->ipv4_dst = spec->hdr.dst_addr;
+       }
+       return 0;
+   }
+
+Add the callbacks to the node definition:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name      = "IPV4",
+       .type      = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate  = example_validate_ipv4,
+       .process   = example_process_ipv4,
+   },
+
+Node Constraints
+----------------
+
+Many nodes share common requirements about which combination of ``spec``, ``mask``, and ``last`` pointers an item must carry.
+Instead of checking these in every validate callback, they can be declared via the ``constraints`` field.
+The field uses ``flow_graph_node_expect`` flags.
+
+Available constraint flags (may be ORed together):
+
+``FLOW_GRAPH_NODE_EXPECT_EMPTY``
+   The item must have ``spec == NULL``, ``mask == NULL``, and
+   ``last == NULL``.
+
+``FLOW_GRAPH_NODE_EXPECT_SPEC``
+   ``spec`` is required; ``mask`` and ``last`` must be NULL.
+
+``FLOW_GRAPH_NODE_EXPECT_MASK``
+   ``mask`` is required; ``spec`` and ``last`` must be NULL.
+
+``FLOW_GRAPH_NODE_EXPECT_SPEC_MASK``
+   Both ``spec`` and ``mask`` are required; ``last`` must be NULL.
+
+``FLOW_GRAPH_NODE_EXPECT_RANGE``
+   All three (``spec``, ``mask``, ``last``) are required.
+
+``FLOW_GRAPH_NODE_EXPECT_NOT_RANGE``
+   ``last`` must be NULL (``spec`` and ``mask`` are unconstrained).
+
+Multiple flags can be ORed together.
+The item is accepted if **any one** of the flagged constraints is satisfied.
+The purpose is to express the difference between "must" (i.e. accept ONLY this), and "may" (i.e. accept this OR that).
+
+For example, an IPv4 node that accepts either a mask-only item or a spec+mask item:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name        = "IPV4",
+       .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate    = example_validate_ipv4,
+       .process     = example_process_ipv4,
+       .constraints = FLOW_GRAPH_NODE_EXPECT_MASK |
+                      FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+   },
+
+An Ethernet node that may appear empty (no spec/mask) or with spec+mask:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_ETH] = {
+       .name        = "ETH",
+       .type        = RTE_FLOW_ITEM_TYPE_ETH,
+       .validate    = example_validate_eth,
+       .process     = example_process_eth,
+       .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY |
+                      FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+   },
+
+Constraints are checked **before** the validate callback is invoked.
+
+Ignoring Item Types
+~~~~~~~~~~~~~~~~~~~
+
+The ``ignore_nodes`` field on ``struct flow_graph`` is an optional complement to node constraints.
+When the pattern may contain item types that are irrelevant to the driver, list them in ``ignore_nodes``.
+For example, metadata items like ``RTE_FLOW_ITEM_TYPE_MARK`` do not represent a protocol header.
+The parser skips ignored items silently without advancing the current graph position:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       /* ... nodes and edges ... */
+       .ignore_nodes = (enum rte_flow_item_type[]){
+           RTE_FLOW_ITEM_TYPE_MARK,
+           RTE_FLOW_ITEM_TYPE_END,
+       },
+   };
+
+``RTE_FLOW_ITEM_TYPE_VOID`` is always ignored regardless of this list.
+Omit ``ignore_nodes`` entirely when no additional item types need to be skipped.
+
+Calling the Parser
+------------------
+
+``flow_graph_parse`` walks the pattern against the graph:
+
+.. code-block:: c
+
+   int
+   flow_graph_parse(const struct flow_graph *graph,
+                        const struct rte_flow_item *pattern,
+                        struct rte_flow_error *error,
+                        void *ctx);
+
+A typical call site looks like this:
+
+.. code-block:: c
+
+   struct example_parsed_flow parsed;
+   int ret;
+
+   memset(&parsed, 0, sizeof(parsed));
+
+   ret = flow_graph_parse(&example_graph, pattern, error, &parsed);
+   if (ret != 0)
+       return ret;
+
+   /* 'parsed' now contains the extracted protocol fields */
+
+The function returns success or failure, with ``error`` populated.
+
+Error conditions:
+
+* **Graph is NULL** — for example, when the graph pointer itself is not provided.
+* **Pattern is NULL**.
+* **Unsupported transition** — when an item type has no matching edge from the current node.
+    This is the primary way the graph rejects unsupported protocol sequences.
+* **Constraint failure** — when the spec, mask, and last combination does not satisfy the node's declared constraints.
+* **Validate callback failure** — when driver-specific validation rejects the item.
+* **Process callback failure** — when driver-specific extraction path fails.
+
+.. warning::
+
+    Malformed graph tables (for example invalid node indices, missing sentinels,
+    or otherwise inconsistent driver-defined graph structures) are considered to be a driver implementation bug.
+    Graphs are trusted by default: driver-owned graph structures are expected to be valid and are not fully validated.
+
+The traversal processes items in order, skipping ignored types.
+After the last non-``END`` item, the parser looks for an ``END`` node reachable from the current position.
+It then visits that node and runs its callbacks, if any.
+This means drivers can attach a process callback to the ``END`` node for post-traversal finalization.
+
+Putting It All Together
+-----------------------
+
+The complete graph definition with callbacks and constraints:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name        = "ETH",
+               .type        = RTE_FLOW_ITEM_TYPE_ETH,
+               .validate    = example_validate_eth,
+               .process     = example_process_eth,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name        = "VLAN",
+               .type        = RTE_FLOW_ITEM_TYPE_VLAN,
+               .process     = example_process_vlan,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name        = "IPV4",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+               .validate    = example_validate_ipv4,
+               .process     = example_process_ipv4,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_MASK
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name        = "IPV6",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV6,
+               .validate    = example_validate_ipv6,
+               .process     = example_process_ipv6,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_MASK
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name        = "TCP",
+               .type        = RTE_FLOW_ITEM_TYPE_TCP,
+               .validate    = example_validate_tcp,
+               .process     = example_process_tcp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_MASK
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name        = "UDP",
+               .type        = RTE_FLOW_ITEM_TYPE_UDP,
+               .validate    = example_validate_udp,
+               .process     = example_process_udp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_MASK
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name        = "SCTP",
+               .type        = RTE_FLOW_ITEM_TYPE_SCTP,
+               .process     = example_process_sctp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY
+                            | FLOW_GRAPH_NODE_EXPECT_MASK
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+
+Tunnel Graphs and Repeated Item Types
+-------------------------------------
+
+Tunneled patterns often repeat the same ``rte_flow_item_type`` in outer and inner headers.
+A simple representative example is a TCP-IPv4 over GTP-U pattern::
+
+   ETH -> IPV4 -> UDP -> GTPU -> IPV4 -> TCP
+
+The graph library supports this naturally.
+Multiple nodes may use the same ``type`` value, as long as they are distinct nodes in the graph and reached through different edges.
+
+For tunnel parsing, the recommended style is to model repeated protocol types as separate inner/outer nodes, for example ``OUTER_IPV4`` and ``INNER_IPV4``.
+This makes the graph intent explicit, keeps callback logic clear, and avoids unexpected graph paths due to loops.
+
+.. code-block:: c
+
+   enum tunnel_node_id {
+       TUNNEL_NODE_START = FLOW_GRAPH_NODE_FIRST,
+       TUNNEL_NODE_ETH,
+       TUNNEL_NODE_OUTER_IPV4,
+       TUNNEL_NODE_TCP,
+       TUNNEL_NODE_UDP,
+       TUNNEL_NODE_GTPU,
+       TUNNEL_NODE_INNER_IPV4,
+       TUNNEL_NODE_END,
+       TUNNEL_NODE_MAX,
+   };
+
+   const struct flow_graph tunnel_graph = {
+       .nodes = (struct flow_graph_node[]){
+           /* Minimal topology example: callbacks and constraints are omitted. */
+           [TUNNEL_NODE_START] = { .name = "START" },
+           [TUNNEL_NODE_ETH] = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .name = "OUTER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_UDP] = {
+               .name = "UDP",
+               .type = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .name = "GTPU",
+               .type = RTE_FLOW_ITEM_TYPE_GTPU,
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .name = "INNER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_TCP] = {
+               .name = "TCP",
+               .type = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [TUNNEL_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [TUNNEL_NODE_START] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_ETH] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_OUTER_IPV4,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_UDP,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_UDP] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_GTPU,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_INNER_IPV4,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_TCP,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_TCP] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+In other words, traversal follows graph edges (node-to-node), while node matching is done against each candidate node's ``rte_flow_item_type``.
+That combination allows repeated protocol layers to be represented cleanly with separate nodes for different parsing contexts.
+
+Although arbitrary loops are possible in the graph, tunnel protocol graphs are usually easier to reason about when repeated item types are split into explicit inner/outer nodes.
+It is not recommended to create loops in the graph, as these loops will be unbounded.
doc/guides/prog_guide/ethdev/index.rst +1 -0
index 392ced0a2ef..b41fd045bb3 100644
@@ -10,6 +10,7 @@ Ethernet Device Library
     ethdev
     switch_representation
     flow_offload
+    flow_graph
     traffic_metering_and_policing
     traffic_management
     qos_framework
doc/guides/rel_notes/release_26_11.rst +5 -0
index 0785f96dd81..37881aea4a2 100644
@@ -55,6 +55,11 @@ New Features
      Also, make sure to start the actual text at the margin.
      =======================================================
 
+* **Added internal ethdev flow graph parser helper API.**
+
+  Added ``flow_graph`` helper definitions in ``flow_graph.h``
+  for PMD drivers to build graph-based flow pattern parsers.
+
 * **Updated Intel iavf driver.**
 
   * Runtime Rx/Tx queue setup is now automatically disabled while a
lib/ethdev/flow_graph.h new file +507 -0

507 lines not on this page · raw message

lib/ethdev/meson.build +1 -0
index 8ba6c708a24..99ff3c990c1 100644
@@ -40,6 +40,7 @@ driver_sdk_headers += files(
         'ethdev_pci.h',
         'ethdev_vdev.h',
         'rte_flow_driver.h',
+        'flow_graph.h',
         'rte_mtr_driver.h',
         'rte_tm_driver.h',
 )
Signature · 1 line
2.52.0
#54 lore raw

Re: [PATCH v3 01/19] ethdev: add flow graph API

lib/ethdev/flow_graph.h line 50

Quoted 3 lines · On Wed, 16 Sep 2026 13:18:07 +0100 Anatoly Burakov <[email protected]>…
lib/ethdev/flow_graph.h new file +3 -0
+static inline const char *
+flow_graph_item_type_to_str(enum rte_flow_item_type type)
+{

None of this is critical path, it should not need to be static inline and include. Build it once as part of ethdev.

#55 lore raw

Re: [PATCH v3 01/19] ethdev: add flow graph API

Quoted 9 lines · Stephen Hemminger

lib/ethdev/flow_graph.h line 50

Quoted 3 lines · On Wed, 16 Sep 2026 13:18:07 +0100 Anatoly Burakov <[email protected]>…
lib/ethdev/flow_graph.h new file +3 -0
+static inline const char *
+flow_graph_item_type_to_str(enum rte_flow_item_type type)
+{

None of this is critical path, it should not need to be static inline and include. Build it once as part of ethdev.

There is nowhere to put it, this is a header library with no compilation units. The only place this could be moved to is the rte_flow implementation itself which I guess kind of works but will look like a less flexible version of rte_flow_conv.

Signature · 2 lines
Thanks,
Anatoly
#56 lore raw

Re: [PATCH v3 01/19] ethdev: add flow graph API

In reply to Anatoly Burakov

Hi Anatoly,

Quoted 21 lines · Anatoly Burakov

This commit adds a flow graph parsing API. This is a helper API intended to help ethdev drivers implement rte_flow parsers, as common usages map to graph traversal problem very well.

Features provided by the API:
- Flow graph, edge, and node definitions
- Graph traversal logic
- Declarative validation against common flow item types
- Per-node validation and state processing callbacks

---
  doc/guides/prog_guide/ethdev/flow_graph.rst | 749 ++++++++++++++++++++
  doc/guides/prog_guide/ethdev/index.rst      |   1 +
  doc/guides/rel_notes/release_26_11.rst      |   5 +
  lib/ethdev/flow_graph.h                     | 507 +++++++++++++
  lib/ethdev/meson.build                      |   1 +
  5 files changed, 1263 insertions(+)

create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst create mode 100644 lib/ethdev/flow_graph.h

<snip>

lib/ethdev/flow_graph.h line 136

Quoted 12 lines
lib/ethdev/flow_graph.h new file +12 -0
+/**
+ * Graph node definition.
+ *
+ * Node validity rules:
+ * - all nodes must define a name,
+ * - all non-END nodes must define an edge list,
+ * - start node must not define validation/processing callbacks.
+ */
+struct flow_graph_node {
+	const char *name;                    /**< Node name. */
+	enum rte_flow_item_type type;        /**< Flow item type to match. */
+	enum flow_graph_node_expect constraints; /**< Common validation constraints (ORed). */

since this field behaves as a bitmask, it's better to change the type to uint32_t

Quoted 4 lines
lib/ethdev/flow_graph.h new file +4 -0
+	flow_graph_node_validate_fn validate;  /**< Validation callback (NULL if unsupported). */
+	flow_graph_node_process_fn process;    /**< Processing callback (NULL if no extraction needed). */
+};
+

<snip>

Quoted 24 lines
lib/ethdev/flow_graph.h new file +24 -0
+static inline bool
+_flow_graph_node_is_valid(const struct flow_graph *graph,
+			   const struct flow_graph_node *node,
+			   struct rte_flow_error *error)
+{
+	size_t node_idx;
+
+	if (node == NULL) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				"Flow graph node pointer is NULL");
+		return false;
+	}
+
+	if (node->name == NULL) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+				"Flow graph node name is not defined");
+		return false;
+	}
+
+	node_idx = _flow_graph_get_node_index(graph, node);
+
+	/* first node can't have callbacks because there's no item */

lib/ethdev/flow_graph.h line 330

Quoted 10 lines
lib/ethdev/flow_graph.h new file +10 -0
+	if (node_idx == FLOW_GRAPH_NODE_FIRST &&
+			(node->validate != NULL || node->process != NULL)) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+				"Flow graph start node callbacks are not allowed");
+		return false;
+	}
+
+	/* all non-END nodes must have edges */
+	if (node->type != RTE_FLOW_ITEM_TYPE_END &&

this statement does not check edges for FLOW_GRAPH_NODE_FIRST node since it's type is 0 (thus RTE_FLOW_ITEM_TYPE_END)

Quoted 10 lines
lib/ethdev/flow_graph.h new file +10 -0
+			graph->edges[node_idx].next == NULL) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+				"Flow graph edge list is not defined for non-END node");
+		return false;
+	}
+
+	return true;
+}
+

<snip>

Quoted 9 lines
lib/ethdev/flow_graph.h new file +9 -0
+	/* Traverse pattern items */
+	for (item = pattern; item->type != RTE_FLOW_ITEM_TYPE_END; item++) {
+
+		/* Skip items in the graph's ignore list */
+		if (_flow_graph_node_is_ignored(graph, item->type)) {
+			FLOW_GRAPH_LOG(DEBUG, "ignored item %s",
+					flow_graph_item_type_to_str(item->type));
+			continue;
+		}

lib/ethdev/flow_graph.h line 470

Quoted 10 lines
lib/ethdev/flow_graph.h new file +10 -0
+
+		/* Find the next graph node for this item type */
+		cur_node = _flow_graph_find_next_node(graph, cur_node,
+				item->type, error);
+		if (cur_node == NULL) {
+			FLOW_GRAPH_LOG(DEBUG, "cannot traverse to item %s",
+					flow_graph_item_type_to_str(item->type));
+			return rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Pattern item not supported");

if _flow_graph_node_is_valid() fails this rewrites the reason why it failed

lib/ethdev/flow_graph.h line 483

Quoted 13 lines
lib/ethdev/flow_graph.h new file +13 -0
+		}
+		FLOW_GRAPH_LOG(DEBUG, "processing %s", cur_node->name);
+		/* Validate and process the current item at this node */
+		ret = _flow_graph_visit_node(cur_node, ctx, item, error);
+		if (ret != 0) {
+			/* error may be NULL */
+			if (error != NULL)
+				FLOW_GRAPH_LOG(DEBUG, "%s", error->message);
+			return ret;
+		}
+	}
+
+	/* Pattern items have ended but we still need to process the end */

why not have this tail processing of the RTE_FLOW_ITEM_TYPE_END item inside the loop?

Quoted 36 lines
lib/ethdev/flow_graph.h new file +24 -0
+	cur_node = _flow_graph_find_next_node(graph, cur_node, item->type, error);
+	if (cur_node == NULL) {
+		FLOW_GRAPH_LOG(DEBUG, "cannot traverse to item %s",
+				flow_graph_item_type_to_str(item->type));
+		return rte_flow_error_set(error, ENOTSUP,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Pattern item not supported");
+	}
+	ret = _flow_graph_visit_node(cur_node, ctx, item, error);
+	if (ret != 0) {
+		/* error may be NULL */
+		if (error != NULL)
+			FLOW_GRAPH_LOG(DEBUG, "%s", error->message);
+		return ret;
+	}
+
+	return 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FLOW_GRAPH_H_ */
lib/ethdev/meson.build +1 -0
index 8ba6c708a24..99ff3c990c1 100644
@@ -40,6 +40,7 @@ driver_sdk_headers += files(
          'ethdev_pci.h',
          'ethdev_vdev.h',
          'rte_flow_driver.h',
+        'flow_graph.h',
          'rte_mtr_driver.h',
          'rte_tm_driver.h',
  )
Signature · 2 lines
Regards,
Vladimir
#57 lore raw

PATCH v3 2/19 net/intel/common: add flow engines infrastructure

In reply to Anatoly Burakov

Current implementation of flow engines in various drivers have a few issues that need to be corrected.

For one, some of the are fundamentally incompatible with secondary processes, because the flow engine registration and creation will allocate structures in shared memory but use process-local pointers to point to flow engines and pattern tables.

For another, a lot of them are needlessly complicated and rely on a separation between patterns and parsing that is hard to reason about and
maintain: they do not define memory ownership model, they do not define the way in which we approach parameter and pattern parsing, and they occasionally do weird things like passing around pointers-to-void-pointers or even using pointers as integer values.

Another common problem is extremely convoluted internal tracking, flow installation, flow replay, and cleanup code. This infrastructure is usually done in an ad-hoc manner that has a lot of boilerplate.

These issues can be corrected, but because of how much code there is to the current infrastructure and how tightly coupled it is, it would be easier to just build new one from scratch, and gradually migrate all engines to use it. This patch is intended as a first step towards that goal, and defines both common data types to be used by all rte_flow parsers, as well as the interaction model that is to be followed by all drivers.

We define a set of structures that will represent:

- Defined rte_flow parsing interaction model and code flow (ops struct)
- Defined memory allocation and ownership model for all engines
- Scratch space format for all engines (variably allocated typed struct)
- Flow rule format for all engines (variably allocated typed struct)
- Engine definitions that are compatible with secondary process model
- Implementations of common rte_flow operations
- Various supporting infrastructure for parser customization, e.g. hooks
- Support for using custom allocation (e.g. for mempool-based alloc)
- Support for replaying all flows to restore HW state
- Support for removing all flows without modifying HW state

The design intent is heavily documented right inside the header and is to be considered authoritative design document for how to build rte_flow parsers for Intel Ethernet drivers going forward.

---
 drivers/net/intel/common/flow_engine.h | 1620 ++++++++++++++++++++++++
 1 file changed, 1620 insertions(+)
 create mode 100644 drivers/net/intel/common/flow_engine.h
drivers/net/intel/common/flow_engine.h new file +1620 -0
new file mode 100644
index 00000000000..0b3837cc010
@@ -0,0 +1,1620 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_FLOW_ENGINE_H_
+#define _COMMON_INTEL_FLOW_ENGINE_H_
+
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/queue.h>
+
+#include <rte_bitops.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+
+#include <ethdev_driver.h>
+#include <rte_flow.h>
+#include <flow_graph.h>
+#include <rte_tailq.h>
+#include <rte_rwlock.h>
+#include <rte_hexdump.h>
+
+#include "log.h"
+
+/*
+ * This is a common header for Intel Ethernet drivers' flow engine
+ * implementations. It defines the interfaces and data structures required to
+ * implement flow rule engines that can be plugged into the drivers' flow
+ * handling logic.
+ *
+ * Design considerations:
+ *
+ * 1. Ease of implementation
+ *
+ * The flow engine interface is designed to be as simple as possible with
+ * obvious defaults (i.e. not specifying something leads to behavior that
+ * would've been the most expected in context). The point is not to produce a
+ * monstrous driver-within-a-driver framework, but rather to make engine
+ * definitions follow semantic expectations of what the engine actually does.
+ *
+ * All the boilerplate (flow management, engine enablement tracking, etc.) is
+ * handled by the common flow infrastructure, so the engine implementation only
+ * needs to focus on the actual logic of parsing and installing/uninstalling
+ * flow rules, and defining each step of the process as it pertains to each flow
+ * engine.
+ *
+ * It is expected that drivers will use other utility functions from the common
+ * flow-related code where applicable (e.g. flow_util.h, flow_check.h, etc.).
+ *
+ * 2. Full secondary process compatibility
+ *
+ * In order to support rte_flow operations in secondary processes, we need to
+ * store which engines are enabled for particular driver instance, and resolve
+ * them at runtime. The engine index (its position in the engine list) is used as
+ * a bit position in a driver-specific 64-bit field of enabled engines. This
+ * way, the engine definitions can be stored in read-only memory, and referenced
+ * by both primary and secondary processes without issues.
+ *
+ * For this to remain safe, flow engine lists and engine definitions must be
+ * immutable for process lifetime (declare them as const).
+ *
+ * Note that this does not imply that all drivers are therefore able to support
+ * rte_flow-related operations in secondary processes - that is still up to each
+ * driver to implement. This just ensures that the flow engine framework does
+ * not prevent it.
+ *
+ * Engine callbacks must not access or retain an `struct rte_eth_dev *` pointer,
+ * as that object is process-local; use the process-independent
+ * `struct rte_eth_dev_data *` provided by the framework instead.
+ *
+ * The per-instance engine configuration is set up and torn down exclusively by
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()`. These functions
+ * should only be called at device setup/teardown by primary process.
+ *
+ * 3. Flow object lifecycle is framework-owned
+ *
+ * Engines are expected to treat framework-provided context and flow objects as
+ * storage they fill in, not storage they own. In other words, engine logic
+ * should focus on contents of flow data, while object lifetime is managed by
+ * the framework. Engines may still allocate auxiliary data, but only in places
+ * where the framework guarantees a matching teardown call, which will give the
+ * engine the opportunity to release said auxiliary data.
+ *
+ * 4. Pattern parsing: flow_graph and pattern_parse callback
+ *
+ * The flow engine framework is designed to work hand-in-hand with the
+ * `flow_graph` parsing infrastructure. Each engine may provide a pattern
+ * graph that is used to match the flow pattern, and extract relevant data
+ * into the engine context provided by the framework.
+ *
+ * Engines may also provide a `pattern_parse` callback that is invoked before
+ * the graph parser runs. This allows engines to handle pattern items that
+ * don't fit neatly into the graph model (e.g. FUZZY items that can appear at
+ * any position), as well as ignoring the graph parser entirely and implementing
+ * custom pattern parsing.
+ *
+ * There is no way to completely ignore pattern contents for the engine except
+ * for defining a noop `pattern_parse` callback. This is by design, as such case
+ * is considered rte_flow API misuse. By default, even for empty fallback case,
+ * a meaningful pattern (one that is not empty or ANY) will be treated as error.
+ *
+ * 5. Setup, teardown, and flow list lifecycle ordering
+ *
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()` are
+ * primary-process-only (see point 2), and the framework does not serialize
+ * them against concurrent flow operations or against each other - the driver
+ * must do so.
+ *
+ * The expected sequence of calls for a driver instance is:
+ *
+ * - `ci_flow_engine_conf_init()` should run from the driver's `dev_init` path.
+ *
+ * - At `dev_close`, `ci_flow_cleanup()` should be run first to drop all flows
+ *   and their internal tracking. Then, `ci_flow_engine_conf_reset()` can be run.
+ *
+ * - Devices may or may not advertise `RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP`,
+ *   i.e. support for keeping flow rules across a `dev_stop`/`dev_start`
+ *   cycle.
+ *
+ * - If the device does not advertise this capability, flows must be
+ *   flushed via `ci_flow_flush()` as the first step of `dev_stop()` (doing so
+ *   later may interfere with flow uninstall).
+ *
+ * - If rule replay is needed (i.e. flows were kept rather than flushed at
+ *   `dev_stop`), the driver should call `ci_flow_replay()` from `dev_start`
+ *   to re-install the kept flows to hardware as last step.
+ */
+
+/* forward declarations for flow engine data types */
+struct ci_flow_engine_ops;
+struct ci_flow_engine_ctx;
+struct ci_flow_engine;
+struct ci_flow_engine_ref;
+struct ci_flow_engine_list;
+struct ci_flow_engine_conf;
+struct ci_flow;
+
+/*
+ * Flow engine ops.
+ *
+ * Each flow engine must provide a set of operations to handle common
+ * operations, such as:
+ *
+ * - Initialize and clean up engine resources (engine_init/engine_uninit)
+ * - Allocate memory for flow rules (flow_alloc)
+ * - Process flow attributes and actions into engine-specific context (ctx_init)
+ * - Parse pattern into engine-specific context (pattern_parse)
+ * - Pattern graph to use when parsing flow patterns (see ethdev's `flow_graph`)
+ * - Any final post-parse checks or actions (ctx_finalize)
+ * - Build the actual flow rule structure from the parsed context (ctx_to_flow)
+ * - Track/untrack the flow rule in driver-internal state (flow_register/flow_unregister)
+ * - Install/remove the flow rule to/from hardware (flow_install/flow_uninstall)
+ * - Query data for the flow rule (flow_query)
+ *
+ * What follows is a description of the flow engine pipeline as it relates to
+ * the flow engine ops struct. Each stage has a setup step (run when a flow is
+ * created) and, where applicable, a teardown step (run when a flow is removed).
+ * Each operation is then a composition of stages: setup steps run in forward
+ * order, teardown steps in reverse. Optional callbacks are shown in [brackets].
+ *
+ * - Allocation stage
+ *     setup:    [flow_alloc]      teardown: [flow_free]
+ *   Provides engine-specific flow object allocation. Skipped on the validate path.
+ *
+ * - Parse stage
+ *     setup:    ctx_init -> [pattern_parse] -> [graph parser] ->
+ *               [ctx_finalize] -> [ctx_to_flow]
+ *     teardown: (none)
+ *   Interprets the request into the flow structure. Must NOT allocate.
+ *
+ * - Track stage
+ *     setup:    [flow_register]   teardown: [flow_unregister]
+ *   Updates driver-internal state. Permitted to allocate.
+ *
+ * - Apply stage
+ *     setup:    [flow_install]    teardown: [flow_uninstall]
+ *   Programs the hardware. Must NOT allocate.
+ *
+ * Typical operation sequences:
+ *
+ * - rte_flow_create:   Allocation -> Parse -> Track -> Apply   (setup)
+ * - rte_flow_validate: Parse
+ * - rte_flow_destroy:  Apply -> Track -> Allocation            (teardown)
+ * - rte_flow_flush:    Apply -> Track -> Allocation            (teardown, per flow)
+ * - ci_flow_replay:    Apply                                   (setup, per flow)
+ * - ci_flow_cleanup:   Track -> Allocation                     (teardown, per flow)
+ *
+ * 1) Engine availability and lifecycle
+ *
+ * The engine availability can be checked by the driver at init time. The exact
+ * mechanics of this is left up to each individual driver - it may be hardware
+ * capability bits, PHY type check, devargs, or any other criteria that makes
+ * sense in the context of driver/adapter. If the engine_init callback is not
+ * implemented, the engine is assumed to be always available.
+ *
+ * If `priv_size` is non-zero, the framework allocates a zeroed private block
+ * and passes it to engine_init/engine_uninit. Each allocated flow will also
+ * carry a pointer to this per-device private data in `flow->engine_priv`.
+ *
+ * 2) Input parsing
+ *
+ * `ctx_init` is mandatory and acts as the main gateway for processing actions and
+ * attributes into engine context.
+ *
+ * Patterns are matched either against the pattern graph provided by the
+ * `ci_flow_engine` structure, or by the `pattern_parse` callback, or both.
+ *
+ * Pattern handling mode is selected from graph/callback presence:
+ *
+ * - graph only: graph parser runs
+ * - callback only: pattern_parse runs
+ * - callback + graph: pattern_parse runs, then graph parser runs
+ * - neither graph nor callback: empty fallback matcher is used
+ *
+ * Empty fallback matcher accepts:
+ *
+ * - NULL pattern
+ * - empty pattern (start -> end)
+ * - ANY-only pattern (start -> any -> end)
+ *
+ * Any other patterns will be rejected by the empty path.
+ *
+ * In callback + graph mode, the callback does not remove items from the
+ * original pattern stream. The graph parser still sees the same pattern, so the
+ * graph should define its `ignore_nodes` list appropriately.
+ *
+ * 3) Rule finalization
+ *
+ * `ctx_finalize` is an optional final check for consistency between parsed
+ * action/attribute data and pattern-derived data, as well as a chance to perform
+ * any final post-parse operations.
+ *
+ * 4) Rule materialization
+ *
+ * `ctx_to_flow` translates parsed context into the concrete engine-specific
+ * flow rule representation. As a general guideline, flow rule should not
+ * contain anything that isn't useful for rule installation or querying. All
+ * of the temporary data should be stored in the flow building context, while
+ * the rule should only contain data that is pertinent to flow programming.
+ *
+ * 5) Rule lifecycle hooks:
+ *
+ * `flow_alloc`/`flow_free` are optional custom object lifecycle callbacks
+ * (Allocation stage). If `flow_alloc` is not provided (or returns NULL), the
+ * framework falls back to rte_zmalloc-based allocation. If `flow_alloc` callback
+ * implementation exists, `flow_free` implementation must exist as well.
+ *
+ * `flow_register`/`flow_unregister` are the optional Track stage callbacks.
+ * `flow_register` builds driver-internal state for the flow; `flow_unregister`
+ * tears it down. As with alloc/free, they must both be defined or both be NULL.
+ *
+ * `flow_install`/`flow_uninstall` are the optional Apply stage callbacks that
+ * program and unprogram the hardware. They must not allocate.
+ *
+ * The engines own only engine-specific fields. The common `ci_flow` fields
+ * (engine_idx, engine_priv, fallback_alloc, etc.) are owned and managed by the
+ * framework and are not to be modified.
+ *
+ * IMPORTANT: by the time the Track and Apply stages run, the engine has already
+ * accepted the flow through `ctx_init`/`ctx_finalize`/`ctx_to_flow`. A failure
+ * in `flow_register` or `flow_install` (e.g. hardware/software resource
+ * exhaustion) is treated as a hard failure: no other engines will be tried. It
+ * is therefore implied that if the flow parser has accepted the flow, it should
+ * in principle be trackable and installable - any checks intrinsic to the pattern
+ * itself that would have prevented that should have been done at the parse stage.
+ *
+ * 6) Memory ownership and allocation policy:
+ *
+ * There are exactly three places where an engine is permitted to do allocations:
+ *
+ * - engine init
+ * - flow alloc (Allocation stage)
+ * - flow register (Track stage)
+ *
+ * In engine initialization, the engine may allocate any engine-wide resources
+ * that will later be utilized by other callbacks (such as creating a memory
+ * pool from which `flow_alloc` will allocate flow objects).
+ *
+ * The flow allocation callback is an allocation-policy callback only. That is,
+ * a well-behaved implementation for this callback may allocate memory *for the
+ * flow itself* (such as getting an object from a mempool), and it may update
+ * allocator-associated state (such as tracking allocated objects), but it
+ * otherwise must not produce any artifacts to be used by the parsing stage.
+ * This is because for `rte_flow_validate()`, the `flow_alloc` callbacks are
+ * bypassed, so parse stage cannot rely on anything `flow_alloc` may have done.
+ *
+ * The flow register callback is where the transition from "parsing the flow" to
+ * "updating driver-internal state" happens, and is the only per-flow callback
+ * where a well-behaved implementation may allocate memory and store a pointer
+ * in the flow object or in any internal driver structure.
+ *
+ * The flow install callback must NOT allocate: it only programs the hardware.
+ *
+ * Parsing-related callbacks (`ctx_init`, `pattern_parse`, `ctx_finalize`,
+ * `ctx_to_flow`) must not allocate any memory, as the parsing control flow may
+ * fail at any of these steps without there being any teardown.
+ *
+ * 7) Flow query
+ *
+ * For querying data, the flow_query function is provided to query data for the
+ * flow rule. The engine may not provide this function, in which case any
+ * attempt to query the rule will result in failure.
+ *
+ * 8) Concurrency model
+ *
+ * The framework serializes access to each driver instance's flow state with a
+ * single rwlock. Create, destroy and flush take it exclusively (write);
+ * validate and query take it shared (read). Consequently the parse-phase
+ * callbacks (ctx_init, pattern_parse, ctx_finalize, ctx_to_flow) can run
+ * concurrently with each other on the validate path, and flow_query can run
+ * concurrently with other queries. These callbacks must treat shared engine
+ * state - including engine_priv - as read-only and must not mutate it. State
+ * mutation belongs in the write-locked callbacks (engine_init/engine_uninit,
+ * flow_alloc/flow_free, flow_register/flow_unregister,
+ * flow_install/flow_uninstall).
+ */
+struct ci_flow_engine_ops {
+	/* engine init callback - can be NULL */
+	int (*engine_init)(const struct ci_flow_engine *engine,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* engine uninit callback - can be NULL */
+	void (*engine_uninit)(const struct ci_flow_engine *engine,
+			void *priv);
+	/* allocation callback for flow rules - can be NULL */
+	struct ci_flow *(*flow_alloc)(const struct ci_flow_engine *engine,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* deallocation callback for flow rules - can be NULL */
+	void (*flow_free)(struct ci_flow *flow,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* initialize engine context from flow attr/actions - mandatory */
+	int (*ctx_init)(const struct rte_flow_action actions[],
+			const struct rte_flow_attr *attr,
+			struct ci_flow_engine_ctx *ctx,
+			struct rte_flow_error *error);
+	/* pattern parsing callback - can be NULL */
+	int (*pattern_parse)(struct ci_flow_engine_ctx *ctx,
+			const struct rte_flow_item pattern[],
+			struct rte_flow_error *error);
+	/* final pass before converting context to flow - can be NULL */
+	int (*ctx_finalize)(struct ci_flow_engine_ctx *ctx,
+			struct rte_flow_error *error);
+	/* initialize flow rule from parsed context - can be NULL */
+	int (*ctx_to_flow)(const struct ci_flow_engine_ctx *ctx,
+			struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* track a flow rule in driver-internal state - can be NULL */
+	int (*flow_register)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* untrack a flow rule from driver-internal state - can be NULL */
+	int (*flow_unregister)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* install a flow rule to hardware - can be NULL */
+	int (*flow_install)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* uninstall a flow rule from hardware - can be NULL */
+	int (*flow_uninstall)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* query flow - can be NULL */
+	int (*flow_query)(struct ci_flow *flow,
+			const struct rte_flow_action *action,
+			void *data,
+			struct rte_flow_error *error);
+};
+
+/*
+ * common definition for flow engine context.
+ * each engine will define its own context structure that
+ * *must* start with this base structure.
+ */
+struct ci_flow_engine_ctx {
+	/* ethernet device this context belongs to */
+	struct rte_eth_dev_data *dev_data;
+	/* original flow attributes, as passed by the caller */
+	const struct rte_flow_attr *attr;
+	/* original flow pattern, as passed by the caller */
+	const struct rte_flow_item *pattern;
+	/* original flow actions, as passed by the caller */
+	const struct rte_flow_action *actions;
+};
+
+/*
+ * Common definition for flow rule.
+ *
+ * For flow rules, there are three parts to consider:
+ *
+ * 1) Common data
+ * 2) Driver-specific data
+ * 3) Engine-specific data
+ *
+ * The common data is defined here as the `ci_flow` structure. It contains
+ * fields that are common to all flow rules, regardless of driver or engine.
+ * This includes a linked list node for managing flow rules in a list, a pointer
+ * to the device (driver instance) the flow belongs to, and the engine index
+ * that created the flow.
+ *
+ * With rte_flow API, each driver is meant to define its own rte_flow structure
+ * that contains driver-specific data. This structure must start with the
+ * `ci_flow` structure defined here, followed by driver-specific fields.
+ *
+ * Additionally, each *engine* may want to define its own flow rule structure
+ * that contains actual engine-specific data. This structure must start with the
+ * driver-wide `rte_flow` structure such that it contains everything before it,
+ * followed by engine-specific fields.
+ *
+ * IMPORTANT:
+ *
+ * All of these structures will be referred to by the same pointer and can be
+ * freely (and safely) cast between each other *as long as* each structure
+ * definition has the parent structure as its first member. E.g. the common flow
+ * struct is `ci_flow`, and the driver-specific `rte_flow` must be defined as
+ * follows:
+ *
+ * struct rte_flow {
+ *     struct ci_flow base;
+ *     ...any driver-specific fields...
+ * }
+ *
+ * If the engine needs to define its own flow structure, in turn it should be
+ * defined as follows:
+ *
+ * struct ixgbe_fdir_flow {
+ *     struct rte_flow base;
+ *     ...any engine-specific fields...
+ * }
+ *
+ * This ensures pointer conversion safety between all three types:
+ *
+ *     struct ci_flow *flow = ...;
+ *     struct rte_flow *rte_flow = (struct rte_flow *)flow;
+ *     struct engine_specific_flow *es_flow = (struct engine_specific_flow *)flow;
+ *
+ * The engine structure provides a `flow_size` field that indicates how much
+ * memory is required for a particular engine's flow structure. The driver must
+ * provide that value for each engine, as it will be used to size flow structure
+ * allocations. If the engine does not require any memory beyond the `rte_flow`
+ * structure, this value should be set to `sizeof(rte_flow)` for those engines,
+ * thus making it equal to sizeof(ci_flow) plus any driver-specific data.
+ *
+ * Engine references and engine_idx:
+ *
+ * The engine index is treated as the engine type discriminator and is stored in
+ * every flow. Runtime code then reconstructs a `ci_flow_engine_ref`
+ * (engine pointer + index) from the immutable engine list and this stored
+ * index. This gives two important guarantees:
+ *
+ * - The framework works only with immutable engine definitions/lists.
+ * - We avoid repeated pointer-to-index lookups after flow creation; rebuilding
+ *   a ref from index is direct and keeps call sites explicit about both values.
+ *
+ * This is particularly important for secondary-process compatibility where the
+ * index is the stable identity and pointer values are only meaningful in the
+ * context of the shared immutable engine list.
+ *
+ * Code that works with engine pointers and engine indices should be careful,
+ * and should treat engine references as the basic building block.
+ *
+ * Per-engine private data (engine_priv):
+ *
+ * Each flow will also carry a pointer to engine-private data, to allow for
+ * reaching engine-specific data, such as custom allocation structures to
+ * be used with flow_alloc/flow_free at allocation time.
+ *
+ * Fallback allocation flag:
+ *
+ * When `flow_alloc` is provided by the driver, but it returns NULL, the framework
+ * will fall back to rte_zmalloc()-based allocation. The `fallback_alloc` flag is
+ * then used to route subsequent deallocation to rte_free(). This implies that for
+ * an engine, `flow_alloc` returning NULL *is not considered to indicate an error*.
+ */
+struct ci_flow {
+	TAILQ_ENTRY(ci_flow) node;
+	/* device this flow belongs to */
+	struct rte_eth_dev_data *dev_data;
+	/* index of engine this flow was created by */
+	size_t engine_idx;
+	/* per-engine private data pointer, set by the framework at alloc time */
+	void *engine_priv;
+	/* set if engine allocator callback existed but fallback allocator was used */
+	bool fallback_alloc;
+};
+
+/* flow engine definition */
+struct ci_flow_engine {
+	/* engine name */
+	const char *name;
+	/* size of scratch space structure, can be 0 */
+	size_t ctx_size;
+	/* size of flow rule structure, must not be 0 */
+	size_t flow_size;
+	/* size of per-device engine private data, can be 0 */
+	size_t priv_size;
+	/* ops for this flow engine */
+	const struct ci_flow_engine_ops *ops;
+	/* pattern graph this engine supports - can be NULL */
+	const struct flow_graph *graph;
+};
+
+/* engine reference: immutable engine pointer paired with its list index */
+struct ci_flow_engine_ref {
+	const struct ci_flow_engine *engine;
+	size_t engine_idx;
+};
+
+#define CI_FLOW_ENGINE_MAX	64
+
+/* flow engine list definition */
+struct ci_flow_engine_list {
+	/* NULL-terminated immutable array of flow engine pointers */
+	const struct ci_flow_engine * const engines[CI_FLOW_ENGINE_MAX];
+};
+
+/* flow engine configuration - each device must have its own instance */
+struct ci_flow_engine_conf {
+	/* lock to protect config */
+	rte_rwlock_t config_lock;
+	/* list of flows created on this device */
+	TAILQ_HEAD(ci_flow_list, ci_flow) flows;
+	/* bitmask of enabled engines */
+	uint64_t enabled_engines;
+	/* back-reference to device structure */
+	struct rte_eth_dev_data *dev_data;
+	/* reference to the driver's engine list */
+	const struct ci_flow_engine_list *engines;
+	/* per-engine private data pointers, indexed by engine index */
+	void *engine_priv[CI_FLOW_ENGINE_MAX];
+};
+
+/* build engine ref from engine config and engine index - caller must hold config lock */
+static inline struct ci_flow_engine_ref
+ci_flow_engine_ref_from_idx(const struct ci_flow_engine_conf *engine_conf,
+		const size_t engine_idx)
+{
+	/* check if valid reference can be constructed at all */
+	if (engine_conf == NULL || engine_conf->engines == NULL ||
+			engine_idx >= CI_FLOW_ENGINE_MAX) {
+		return (struct ci_flow_engine_ref) {
+			.engine = NULL,
+			.engine_idx = CI_FLOW_ENGINE_MAX
+		};
+	}
+	/* the index itself is valid, but there may be no engine */
+	if (engine_conf->engines->engines[engine_idx] == NULL) {
+		return (struct ci_flow_engine_ref) {
+			.engine = NULL,
+			.engine_idx = CI_FLOW_ENGINE_MAX
+		};
+	}
+
+	return (struct ci_flow_engine_ref) {
+		.engine = engine_conf->engines->engines[engine_idx],
+		.engine_idx = engine_idx,
+	};
+}
+
+/* helper macro to iterate over list of engines as engine refs */
+#define CI_FLOW_ENGINE_LIST_FOREACH(engine_ref, engine_conf)                         \
+	for (size_t __ci_flow_engine_idx = 0;                                        \
+		__ci_flow_engine_idx < CI_FLOW_ENGINE_MAX &&                         \
+			(((engine_ref) = ci_flow_engine_ref_from_idx((engine_conf),  \
+		__ci_flow_engine_idx)).engine != NULL);                              \
+		__ci_flow_engine_idx++)
+
+/* basic checks for flow engine validity */
+static inline bool
+ci_flow_engine_is_valid(const struct ci_flow_engine *engine)
+{
+	/* is the pointer valid? */
+	if (engine == NULL)
+		return false;
+	/* does the engine have a name? */
+	if (engine->name == NULL)
+		return false;
+	/* does the engine have ops? */
+	if (engine->ops == NULL)
+		return false;
+	/* does the engine have mandatory ctx_init op? */
+	if (engine->ops->ctx_init == NULL)
+		return false;
+	/* flow size cannot be less than ci_flow */
+	if (engine->flow_size < sizeof(struct ci_flow))
+		return false;
+	/* alloc and free must both be defined or NULL */
+	if ((engine->ops->flow_alloc == NULL) != (engine->ops->flow_free == NULL))
+		return false;
+	/* register and unregister must both be defined or NULL */
+	if ((engine->ops->flow_register == NULL) != (engine->ops->flow_unregister == NULL))
+		return false;
+	/* engine looks valid */
+	return true;
+}
+
+/* helper to check whether an engine is enabled in the bitmask - caller must hold config lock */
+static inline bool
+ci_flow_engine_is_enabled(const struct ci_flow_engine_conf *conf,
+		const size_t engine_idx)
+{
+	return (conf->enabled_engines & RTE_BIT64(engine_idx)) != 0;
+}
+
+/* helper to enable an engine in the bitmask - caller must hold config lock */
+static inline void
+ci_flow_engine_set_enabled(struct ci_flow_engine_conf *conf, const size_t engine_idx,
+		bool enabled)
+{
+	if (enabled)
+		conf->enabled_engines |= RTE_BIT64(engine_idx);
+	else
+		conf->enabled_engines &= ~RTE_BIT64(engine_idx);
+}
+
+static inline struct ci_flow *
+ci_flow_alloc(const struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv = engine_conf->engine_priv[engine_ref.engine_idx];
+	struct ci_flow *flow = NULL;
+	bool fallback = false;
+
+	/* if engine has an allocator callback, try it first */
+	if (engine->ops->flow_alloc != NULL)
+		flow = engine->ops->flow_alloc(engine, engine_conf->dev_data, priv);
+	/* if allocator callback is not defined or failed, use default allocator */
+	if (flow == NULL) {
+		flow = (struct ci_flow *)rte_zmalloc(NULL, engine->flow_size, 0);
+
+		/* if callback exists and we're here, allocation has fallen back */
+		if (flow != NULL && engine->ops->flow_alloc != NULL)
+			fallback = true;
+	}
+	/* set the engine data to enable correct deallocation in case of failure */
+	if (flow != NULL) {
+		/* erase the common parts - the rest is left up to the engine */
+		memset(flow, 0, sizeof(struct ci_flow));
+		flow->fallback_alloc = fallback;
+		flow->engine_idx = engine_ref.engine_idx;
+		flow->dev_data = engine_conf->dev_data;
+		flow->engine_priv = priv;
+	}
+	return flow;
+}
+
+static inline void
+ci_flow_free(struct ci_flow_engine_ref engine_ref, struct ci_flow *flow)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_free != NULL && !flow->fallback_alloc)
+		engine->ops->flow_free(flow, flow->dev_data, flow->engine_priv);
+	else
+		rte_free(flow);
+}
+
+/* track a flow in driver-internal state (Track stage) - caller must hold config lock */
+static inline int
+ci_flow_register(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_register != NULL)
+		return engine->ops->flow_register(flow, error);
+
+	return 0;
+}
+
+/* untrack a flow from driver-internal state (Track stage) - caller must hold config lock */
+static inline int
+ci_flow_unregister(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_unregister != NULL)
+		return engine->ops->flow_unregister(flow, error);
+
+	return 0;
+}
+
+/* allocate per-device engine private data and call init - caller must hold config lock */
+static inline int
+ci_flow_engine_init(struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv = NULL;
+	int ret;
+
+	if (engine->priv_size > 0) {
+		priv = rte_zmalloc(engine->name, engine->priv_size, 0);
+		if (priv == NULL) {
+			ret = -ENOMEM;
+			goto err;
+		}
+	}
+
+	if (engine->ops->engine_init != NULL) {
+		ret = engine->ops->engine_init(engine, engine_conf->dev_data, priv);
+		if (ret != 0)
+			goto err;
+	}
+	engine_conf->engine_priv[engine_ref.engine_idx] = priv;
+	return 0;
+err:
+	rte_free(priv);
+	return ret;
+}
+
+/* call uninit and free per-device engine private data - caller must hold config lock */
+static inline void
+ci_flow_engine_uninit(struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv;
+
+	priv = engine_conf->engine_priv[engine_ref.engine_idx];
+
+	CI_DRV_LOG(DEBUG, "engine '%s': uninit", engine->name);
+	/* ignore uninit errors */
+	if (engine->ops->engine_uninit != NULL)
+		engine->ops->engine_uninit(engine, priv);
+
+	rte_free(priv);
+	engine_conf->engine_priv[engine_ref.engine_idx] = NULL;
+}
+
+/* disable all engines for a specific driver instance - caller must serialize teardown */
+static inline void
+ci_flow_engine_conf_reset(struct ci_flow_engine_conf *engine_conf)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *flow, *tmp;
+
+	/* free all flows - shouldn't have any at this point */
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct rte_flow_error unreg_error = { 0 };
+		int unreg_ret;
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		/* can't make it into the list if this was invalid, so no checks */
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+		/* untrack before free; ignore errors on this teardown path */
+		unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+		if (unreg_ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister flow: %s",
+					engine_ref.engine->name, unreg_error.message);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	CI_FLOW_ENGINE_LIST_FOREACH(engine_ref, engine_conf) {
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+		/* ignore errors */
+		ci_flow_engine_uninit(engine_conf, engine_ref);
+		ci_flow_engine_set_enabled(engine_conf,
+				engine_ref.engine_idx, false);
+	}
+
+	/* erase device pointer */
+	engine_conf->dev_data = NULL;
+	engine_conf->engines = NULL;
+}
+
+/* enable all engines for a specific driver instance - caller must serialize initialization */
+static inline int
+ci_flow_engine_conf_init(struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine_list *engine_list,
+		struct rte_eth_dev_data *dev_data)
+{
+	struct ci_flow_engine_ref engine_ref;
+
+	/* reject invalid configuration */
+	if (engine_conf == NULL || engine_list == NULL || dev_data == NULL)
+		return -1;
+
+	/* init the lock */
+	rte_rwlock_init(&engine_conf->config_lock);
+
+	/* store data in conf */
+	engine_conf->dev_data = dev_data;
+	engine_conf->engines = engine_list;
+
+	/* init the flow list */
+	TAILQ_INIT(&engine_conf->flows);
+
+	/* enable all engines */
+	CI_FLOW_ENGINE_LIST_FOREACH(engine_ref, engine_conf) {
+		/* skip invalid engines */
+		if (!ci_flow_engine_is_valid(engine_ref.engine)) {
+			CI_DRV_LOG(DEBUG, "engine[%zu]: invalid, skipping",
+					engine_ref.engine_idx);
+			continue;
+		}
+		int ret = ci_flow_engine_init(engine_conf, engine_ref);
+
+		if (ret != 0) {
+			if (ret == -ENOTSUP)
+				CI_DRV_LOG(DEBUG, "engine '%s': not supported, skipping",
+						engine_ref.engine->name);
+			else
+				CI_DRV_LOG(ERR, "engine '%s': init failed (%d: %s), skipping",
+						engine_ref.engine->name, ret, rte_strerror(-ret));
+			continue;
+		}
+
+		ci_flow_engine_set_enabled(engine_conf,
+				engine_ref.engine_idx, true);
+		CI_DRV_LOG(DEBUG, "engine '%s': enabled", engine_ref.engine->name);
+	}
+	return 0;
+}
+
+/* check whether a flow is valid for a specific engine configuration - caller must hold config lock */
+static inline bool
+ci_flow_is_valid(const struct ci_flow *flow,
+		const struct ci_flow_engine_conf *engine_conf)
+{
+	/* is the pointer valid? */
+	if (flow == NULL)
+		return false;
+	/* is the conf initialized? */
+	if (engine_conf == NULL || engine_conf->dev_data == NULL ||
+			engine_conf->engines == NULL)
+		return false;
+	/* does the flow belong to this device? */
+	if (flow->dev_data != engine_conf->dev_data)
+		return false;
+	/* can we find the engine that created this flow? */
+	if (flow->engine_idx >= CI_FLOW_ENGINE_MAX)
+		return false;
+	/* engine must be enabled */
+	if (!ci_flow_engine_is_enabled(engine_conf, flow->engine_idx))
+		return false;
+	/* flow looks valid */
+	return true;
+}
+
+/* default empty pattern graph definitions */
+enum ci_flow_empty_graph_node_id {
+	CI_FLOW_EMPTY_GRAPH_NODE_START = FLOW_GRAPH_NODE_FIRST,
+	CI_FLOW_EMPTY_GRAPH_NODE_ANY,
+	CI_FLOW_EMPTY_GRAPH_NODE_END,
+};
+
+static const struct flow_graph ci_flow_empty_graph = {
+	.nodes = (struct flow_graph_node []) {
+		[CI_FLOW_EMPTY_GRAPH_NODE_START] = {
+			.name = "START",
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_ANY] = {
+			.name = "ANY",
+			.type = RTE_FLOW_ITEM_TYPE_ANY,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY,
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_END] = {
+			.name = "END",
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	},
+	.edges = (struct flow_graph_edge []) {
+		[CI_FLOW_EMPTY_GRAPH_NODE_START] = {
+			.next = (size_t []) {
+				CI_FLOW_EMPTY_GRAPH_NODE_ANY,
+				CI_FLOW_EMPTY_GRAPH_NODE_END,
+				FLOW_GRAPH_NODE_EDGE_END,
+			},
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_ANY] = {
+			.next = (size_t []) {
+				CI_FLOW_EMPTY_GRAPH_NODE_END,
+				FLOW_GRAPH_NODE_EDGE_END,
+			},
+		},
+	}
+};
+
+/* concrete pattern-matching mode selected from graph/callback presence */
+enum ci_match_type {
+	CI_MATCH_EMPTY,    /* no graph, no callback */
+	CI_MATCH_CALLBACK, /* callback only */
+	CI_MATCH_GRAPH,    /* graph only */
+	CI_MATCH_ALL,      /* callback + graph */
+};
+
+/*
+ * helper to match a pattern against an engine's pattern graph and/or callback,
+ * depending on the match type. Caller must hold config lock.
+ */
+static inline int
+ci_flow_match(const struct ci_flow_engine *engine,
+		const struct rte_flow_item pattern[],
+		struct ci_flow_engine_ctx *ctx,
+		enum ci_match_type match_type,
+		struct rte_flow_error *error)
+{
+	switch (match_type) {
+	case CI_MATCH_EMPTY:
+		/* for empty matching, NULL pattern is not an error */
+		if (pattern != NULL) {
+			return flow_graph_parse(&ci_flow_empty_graph,
+					pattern, error, ctx);
+		}
+		return 0;
+
+	case CI_MATCH_CALLBACK:
+		/* for callback matching, pattern cannot be NULL */
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		return engine->ops->pattern_parse(ctx, pattern, error);
+
+	case CI_MATCH_GRAPH:
+		/* for graph matching, pattern cannot be NULL */
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		return flow_graph_parse(engine->graph, pattern, error, ctx);
+
+	case CI_MATCH_ALL:
+	{
+		int ret;
+
+		/* for callback + graph matching, pattern cannot be NULL */
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		ret = engine->ops->pattern_parse(ctx, pattern, error);
+		if (ret != 0)
+			return ret;
+		return flow_graph_parse(engine->graph, pattern, error, ctx);
+	}
+
+	default:
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+				"Invalid match type");
+	}
+}
+
+/* parse a flow using a specific engine - caller must hold config lock */
+static inline int
+ci_flow_parse(const struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine *engine,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	enum ci_match_type match_type;
+	struct ci_flow_engine_ctx *ctx;
+	int ret = 0;
+
+	/*
+	 * Determine the type of matching we are going to perform based on the
+	 * presence of pattern graph and pattern_parse callback. The logic is as
+	 * follows:
+	 *
+	 * - if graph but no callback, match against graph
+	 *
+	 * Expected default case: pattern matching is graph based, no special
+	 * handling for any pattern items.
+	 *
+	 * - if both graph and callback, match against callback + graph
+	 *
+	 * Preprocessor case, i.e. preprocess the pattern with the callback
+	 * before handling the matching to the graph engine. The assumption is
+	 * that the graph will be set up with a proper ignore list to skip over
+	 * nodes that weren't meant for the graph processing.
+	 *
+	 * - if no graph but callback, match against callback
+	 *
+	 * Fully custom pattern parsing case.
+	 *
+	 * - if no graph and no callback, match against empty graph
+	 *
+	 * "Pattern is not meaningful" case, for engines that do not care about
+	 * the pattern at all. A default matching behavior against empty
+	 * patterns is provided (i.e. allow NULL pattern, and allow END or ANY
+	 * -> END patterns). Note that this is not the same as ignoring pattern
+	 * entirely: the engine will still reject patterns that are not empty.
+	 */
+	match_type = engine->graph == NULL ?
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_EMPTY : CI_MATCH_CALLBACK) :
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_GRAPH : CI_MATCH_ALL);
+
+	CI_DRV_LOG(DEBUG, "engine '%s': parsing flow", engine->name);
+
+	/* allocate context */
+	ctx = (struct ci_flow_engine_ctx *)calloc(1,
+			RTE_MAX(engine->ctx_size, sizeof(struct ci_flow_engine_ctx)));
+	if (ctx == NULL) {
+		return rte_flow_error_set(error, ENOMEM,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Failed to allocate memory for rule engine context");
+	}
+	ctx->dev_data = engine_conf->dev_data;
+	ctx->attr = attr;
+	ctx->pattern = pattern;
+	ctx->actions = actions;
+	flow->dev_data = engine_conf->dev_data;
+
+	/* parse flow parameters */
+	ret = engine->ops->ctx_init(actions, attr, ctx, error);
+
+	/* context init failed - that means engine can't be used for this flow */
+	if (ret != 0)
+		goto free_ctx;
+
+	/* match the pattern */
+	ret = ci_flow_match(engine, pattern, ctx, match_type, error);
+
+	/* check if pattern didn't match */
+	if (ret != 0)
+		goto free_ctx;
+
+	/* final verification, if the operation is defined */
+	if (engine->ops->ctx_finalize != NULL)
+		ret = engine->ops->ctx_finalize(ctx, error);
+
+	/* finalization failed - mismatch between parsed data and context data */
+	if (ret != 0)
+		goto free_ctx;
+
+	/* if we need to build rules from context, do it */
+	if (engine->ops->ctx_to_flow != NULL) {
+		ret = engine->ops->ctx_to_flow(ctx, flow, error);
+
+		/* flow building failed - something wrong with context data */
+		if (ret != 0)
+			goto free_ctx;
+	}
+	/* success */
+	ret = 0;
+
+free_ctx:
+	free(ctx);
+	return ret;
+}
+
+/* install a flow using its appropriate engine - caller must hold config lock */
+static inline int
+ci_flow_install(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_install != NULL)
+		return engine->ops->flow_install(flow, error);
+
+	return 0;
+}
+
+/* uninstall a flow using its appropriate engine - caller must hold config lock */
+static inline int
+ci_flow_uninstall(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	/* uninstall the flow if required */
+	if (engine->ops->flow_uninstall != NULL)
+		return engine->ops->flow_uninstall(flow, error);
+
+	return 0;
+}
+
+/*
+ * The following functions are designed to be called from the context of
+ * rte_flow API implementations and are provided as default implementations.
+ *
+ * These default implementations take the config lock internally.
+ */
+
+/* default implementation of rte_flow_create using flow engines */
+static inline struct rte_flow *
+ci_flow_create(struct ci_flow_engine_conf *engine_conf,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *flow = NULL;
+	int ret;
+
+	if (attr == NULL || actions == NULL) {
+		CI_DRV_LOG(DEBUG, "attr or actions is NULL");
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ATTR, NULL,
+				"Attributes and actions cannot be NULL");
+		return NULL;
+	}
+
+	/* lock the config for writing */
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	/* find an engine that can handle this flow */
+	CI_FLOW_ENGINE_LIST_FOREACH(engine_ref, engine_conf) {
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+
+		flow = ci_flow_alloc(engine_conf, engine_ref);
+		if (flow == NULL) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to allocate flow",
+					engine_ref.engine->name);
+			rte_flow_error_set(error, ENOMEM,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to allocate memory for flow rule");
+			/* this is a serious error so don't continue */
+			goto unlock;
+		}
+
+		ret = ci_flow_parse(engine_conf, engine_ref.engine, attr, pattern,
+				actions, flow, error);
+
+		/* parsing failed - free the flow and try next engine */
+		if (ret != 0) {
+			ci_flow_free(engine_ref, flow);
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' rejected flow: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' rejected flow",
+						engine_ref.engine->name);
+			continue;
+		}
+
+		CI_DRV_LOG(DEBUG, "engine '%s' accepted flow, committing",
+				engine_ref.engine->name);
+
+		/* engine accepted the flow - Track then Apply are a hard commitment */
+		ret = ci_flow_register(engine_ref, flow, error);
+		if (ret != 0) {
+			/* track failed after parse accepted - do not try other engines */
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' register failed: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' register failed",
+						engine_ref.engine->name);
+			ci_flow_free(engine_ref, flow);
+			flow = NULL;
+			goto unlock;
+		}
+
+		ret = ci_flow_install(engine_ref, flow, error);
+		if (ret != 0) {
+			struct rte_flow_error unreg_error = { 0 };
+			int unreg_ret;
+
+			/* install failed after parse accepted - this is an
+			 * engine bug, do not try other engines
+			 */
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' install failed: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' install failed",
+						engine_ref.engine->name);
+			/* roll back the Track stage, preserving the install error */
+			unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+			if (unreg_ret != 0)
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister: %s",
+						engine_ref.engine->name, unreg_error.message);
+			ci_flow_free(engine_ref, flow);
+			flow = NULL;
+			goto unlock;
+		}
+
+		CI_DRV_LOG(DEBUG, "flow installed by engine '%s'",
+				engine_ref.engine->name);
+		/* success */
+		TAILQ_INSERT_TAIL(&engine_conf->flows, flow, node);
+		goto unlock;
+	}
+
+	/* no engine could handle this flow */
+	CI_DRV_LOG(DEBUG, "no engine accepted the flow");
+	flow = NULL;
+	rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"No flow engine could handle the requested flow");
+unlock:
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return (struct rte_flow *)flow;
+}
+
+/* default implementation of rte_flow_validate using flow engines */
+static inline int
+ci_flow_validate(struct ci_flow_engine_conf *engine_conf,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	int ret;
+
+	if (attr == NULL || actions == NULL) {
+		CI_DRV_LOG(DEBUG, "attr or actions is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ATTR, NULL,
+				"Attributes and actions cannot be NULL");
+	}
+
+	/* lock the config for reading */
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	/* find an engine that can handle this flow */
+	CI_FLOW_ENGINE_LIST_FOREACH(engine_ref, engine_conf) {
+		struct ci_flow *flow;
+
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+
+		/* use OS allocator as we're not keeping the flow */
+		flow = (struct ci_flow *)calloc(1, engine_ref.engine->flow_size);
+		if (flow == NULL) {
+			/* this is a serious error so don't continue */
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to allocate flow",
+					engine_ref.engine->name);
+			ret = rte_flow_error_set(error, ENOMEM,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to allocate memory for flow rule");
+			goto unlock;
+		}
+		/* set up the flow fields */
+		flow->fallback_alloc = false;
+		flow->engine_idx = engine_ref.engine_idx;
+		flow->dev_data = engine_conf->dev_data;
+		flow->engine_priv = engine_conf->engine_priv[engine_ref.engine_idx];
+
+		/* try to parse the flow with this engine */
+		ret = ci_flow_parse(engine_conf, engine_ref.engine, attr, pattern,
+				actions, flow, error);
+		free(flow);
+
+		if (ret == 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s' accepted flow",
+					engine_ref.engine->name);
+			goto unlock;
+		} else if (error != NULL) {
+			CI_DRV_LOG(DEBUG, "engine '%s' rejected flow: %s",
+					engine_ref.engine->name, error->message);
+		} else {
+			CI_DRV_LOG(DEBUG, "engine '%s' rejected flow",
+					engine_ref.engine->name);
+		}
+	}
+	/* no engine could handle this flow */
+	CI_DRV_LOG(DEBUG, "no engine accepted the flow");
+	ret = rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"No flow engine could handle the requested flow");
+unlock:
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+	return ret;
+}
+
+/* default implementation of rte_flow_destroy using flow engines. */
+static inline int
+ci_flow_destroy(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *rte_flow,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow = (struct ci_flow *)rte_flow;
+	struct ci_flow_engine_ref engine_ref;
+	struct rte_flow_error unreg_error = {0};
+	int unreg_ret, ret = 0;
+
+	if (rte_flow == NULL) {
+		CI_DRV_LOG(DEBUG, "flow handle is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Flow handle cannot be NULL");
+	}
+
+	/* lock the config for writing */
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	/* validate the flow */
+	if (!ci_flow_is_valid(flow, engine_conf)) {
+		CI_DRV_LOG(DEBUG, "invalid flow handle");
+		ret = rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Invalid flow handle");
+		goto unlock;
+	}
+	engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+	CI_DRV_LOG(DEBUG, "uninstalling flow (engine '%s')",
+			engine_ref.engine->name);
+
+	ret = ci_flow_uninstall(engine_ref, flow, error);
+
+	if (ret != 0) {
+		if (error != NULL)
+			CI_DRV_LOG(DEBUG, "uninstall failed: %s", error->message);
+		else
+			CI_DRV_LOG(DEBUG, "uninstall failed");
+		goto unlock;
+	}
+
+	/* untrack after HW removal; ignore errors on this teardown path */
+	unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+	if (unreg_ret != 0)
+		CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister flow: %s",
+				engine_ref.engine->name, unreg_error.message);
+
+	/* remove the flow from the list and free it */
+	TAILQ_REMOVE(&engine_conf->flows, flow, node);
+	ci_flow_free(engine_ref, flow);
+unlock:
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return ret;
+}
+
+/* default implementation of rte_flow_flush using flow engines */
+static inline int
+ci_flow_flush(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow, *tmp;
+
+	CI_DRV_LOG(DEBUG, "removing all flows");
+
+	/* lock the config for writing */
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	/* iterate over all flows and uninstall them */
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct ci_flow_engine_ref engine_ref;
+		struct rte_flow_error unreg_error = { 0 };
+		int uninstall_ret;
+		int unreg_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		uninstall_ret = ci_flow_uninstall(engine_ref, flow, error);
+
+		/* if uninstall failed, log but ignore failure */
+		if (uninstall_ret != 0) {
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to uninstall flow: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to uninstall flow",
+						engine_ref.engine->name);
+		}
+
+		/* untrack after HW removal; ignore errors on this teardown path, but log them */
+		unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+		if (unreg_ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to untrack flow: %s",
+					engine_ref.engine->name, unreg_error.message);
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+/* default implementation of rte_flow_query using flow engines */
+static inline int
+ci_flow_query(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *rte_flow,
+		const struct rte_flow_action *action,
+		void *data,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow = (struct ci_flow *)rte_flow;
+	struct ci_flow_engine_ref engine_ref;
+	int ret;
+
+	if (action == NULL || data == NULL) {
+		CI_DRV_LOG(DEBUG, "action or data is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Action or data cannot be NULL");
+	}
+
+	/* lock the config for reading */
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	/* validate the flow first */
+	if (!ci_flow_is_valid(flow, engine_conf)) {
+		CI_DRV_LOG(DEBUG, "invalid flow handle");
+		ret = rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Invalid flow handle");
+		goto unlock;
+	}
+	/* find the engine that created this flow */
+	engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+	/* query the flow if supported */
+	if (engine_ref.engine->ops->flow_query != NULL) {
+		ret = engine_ref.engine->ops->flow_query(flow, action, data, error);
+
+		if (ret != 0 && error != NULL)
+			CI_DRV_LOG(DEBUG, "engine '%s' query failed: %s",
+					engine_ref.engine->name, error->message);
+		else if (ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s' query failed",
+					engine_ref.engine->name);
+	} else {
+		CI_DRV_LOG(DEBUG, "engine '%s' does not support querying",
+				engine_ref.engine->name);
+		ret = rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"Flow engine does not support querying");
+	}
+unlock:
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+
+	return ret;
+}
+
+/* dump flow rule chunks using memdump - helper for ci_flow_dump */
+#define CI_FLOW_DUMP_CHUNK_BYTES 32
+
+static inline void
+ci_flow_dump_one(FILE *file, const char *driver, const char *engine,
+		const void *data, size_t data_len)
+{
+	const uint8_t *raw = (const uint8_t *)data;
+	const size_t nchunks =
+		(data_len + CI_FLOW_DUMP_CHUNK_BYTES - 1) /
+		CI_FLOW_DUMP_CHUNK_BYTES;
+	char title[64];
+	size_t ci;
+
+	fprintf(file, "FLOW DUMP: driver=%s engine=%s\n", driver, engine);
+	fprintf(file, "FLOW DUMP: DATA size=%zu chunks=%zu chunk_bytes=%d\n",
+		data_len, nchunks, CI_FLOW_DUMP_CHUNK_BYTES);
+
+	for (ci = 0; ci < nchunks; ci++) {
+		const size_t off = ci * CI_FLOW_DUMP_CHUNK_BYTES;
+		const size_t clen =
+			RTE_MIN((size_t)CI_FLOW_DUMP_CHUNK_BYTES,
+				data_len - off);
+		snprintf(title, sizeof(title), "FLOW DUMP: chunk %03zu/%03zu",
+			 ci + 1, nchunks);
+		rte_memdump(file, title, raw + off, clen);
+	}
+}
+
+/* default implementation of rte_flow_dev_dump using flow engines */
+static inline int
+ci_flow_dump(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *flow,
+		FILE *file,
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *cur;
+	bool found = false;
+	const char *driver_name =
+#ifdef RTE_COMPONENT_NAME
+			RTE_STR(RTE_COMPONENT_NAME);
+#else
+			"unknown";
+#endif
+
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	TAILQ_FOREACH(cur, &engine_conf->flows, node) {
+		const void *data;
+		size_t data_len;
+
+		if (flow != NULL && (struct rte_flow *)cur != flow)
+			continue;
+
+		/* is this flow valid? */
+		if (!ci_flow_is_valid(cur, engine_conf)) {
+			CI_DRV_LOG(DEBUG, "invalid flow handle: %p, skipping", cur);
+			continue;
+		}
+
+		found = true;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf, cur->engine_idx);
+
+		data = RTE_PTR_ADD(cur, sizeof(struct ci_flow));
+		data_len = engine_ref.engine->flow_size - sizeof(struct ci_flow);
+
+		/*
+		 * when data_len is 0 the dump loop would not access the data
+		 * pointer, but static analysis tools may flag this as a
+		 * potential NULL dereference, so skip dump when data_len is 0.
+		 */
+		if (data_len == 0) {
+			CI_DRV_LOG(DEBUG, "flow data length is 0, skipping dump");
+			continue;
+		}
+
+		ci_flow_dump_one(file, driver_name, engine_ref.engine->name,
+				data, data_len);
+	}
+
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+
+	if (flow != NULL && !found) {
+		return rte_flow_error_set(error, ENOENT,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"Flow not found");
+	}
+
+	return 0;
+}
+
+/*
+ * The following functions are designed to be called from the context of
+ * the driver lifecycle functions such as `dev_start`.
+ *
+ * These default implementations take the config lock internally.
+ */
+
+/* re-install all flows */
+static inline int
+ci_flow_replay(struct ci_flow_engine_conf *engine_conf)
+{
+	struct rte_flow_error error = {0};
+	struct ci_flow *flow;
+
+	CI_DRV_LOG(DEBUG, "replaying all flows");
+
+	/* lock the config for writing */
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	TAILQ_FOREACH(flow, &engine_conf->flows, node) {
+		struct ci_flow_engine_ref engine_ref;
+		int install_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		install_ret = ci_flow_install(engine_ref, flow, &error);
+
+		/* if install failed, log but ignore failure */
+		if (install_ret != 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to install flow: %s",
+					engine_ref.engine->name, error.message);
+		}
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+/* remove all flows without uninstalling */
+static inline int
+ci_flow_cleanup(struct ci_flow_engine_conf *engine_conf)
+{
+	struct rte_flow_error error = {0};
+	struct ci_flow *flow, *tmp;
+
+	CI_DRV_LOG(DEBUG, "cleaning up all flows");
+
+	/* lock the config for writing */
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct ci_flow_engine_ref engine_ref;
+		int unregister_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		/* untrack only; the hardware is not touched */
+		unregister_ret = ci_flow_unregister(engine_ref, flow, &error);
+
+		/* if untrack failed, log but ignore failure */
+		if (unregister_ret != 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to untrack flow: %s",
+					engine_ref.engine->name, error.message);
+		}
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+#endif /* _COMMON_INTEL_FLOW_ENGINE_H_ */
Signature · 1 line
2.52.0
#58 lore raw

Re: [PATCH v3 02/19] net/intel/common: add flow engines infrastructure

Quoted 94 lines · On Wed, 16 Sep 2026 13:18:08 +0100 Anatoly Burakov <[email protected]>…
drivers/net/intel/common/flow_engine.h new file +94 -0
+
+/*
+ * This is a common header for Intel Ethernet drivers' flow engine
+ * implementations. It defines the interfaces and data structures required to
+ * implement flow rule engines that can be plugged into the drivers' flow
+ * handling logic.
+ *
+ * Design considerations:
+ *
+ * 1. Ease of implementation
+ *
+ * The flow engine interface is designed to be as simple as possible with
+ * obvious defaults (i.e. not specifying something leads to behavior that
+ * would've been the most expected in context). The point is not to produce a
+ * monstrous driver-within-a-driver framework, but rather to make engine
+ * definitions follow semantic expectations of what the engine actually does.
+ *
+ * All the boilerplate (flow management, engine enablement tracking, etc.) is
+ * handled by the common flow infrastructure, so the engine implementation only
+ * needs to focus on the actual logic of parsing and installing/uninstalling
+ * flow rules, and defining each step of the process as it pertains to each flow
+ * engine.
+ *
+ * It is expected that drivers will use other utility functions from the common
+ * flow-related code where applicable (e.g. flow_util.h, flow_check.h, etc.).
+ *
+ * 2. Full secondary process compatibility
+ *
+ * In order to support rte_flow operations in secondary processes, we need to
+ * store which engines are enabled for particular driver instance, and resolve
+ * them at runtime. The engine index (its position in the engine list) is used as
+ * a bit position in a driver-specific 64-bit field of enabled engines. This
+ * way, the engine definitions can be stored in read-only memory, and referenced
+ * by both primary and secondary processes without issues.
+ *
+ * For this to remain safe, flow engine lists and engine definitions must be
+ * immutable for process lifetime (declare them as const).
+ *
+ * Note that this does not imply that all drivers are therefore able to support
+ * rte_flow-related operations in secondary processes - that is still up to each
+ * driver to implement. This just ensures that the flow engine framework does
+ * not prevent it.
+ *
+ * Engine callbacks must not access or retain an `struct rte_eth_dev *` pointer,
+ * as that object is process-local; use the process-independent
+ * `struct rte_eth_dev_data *` provided by the framework instead.
+ *
+ * The per-instance engine configuration is set up and torn down exclusively by
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()`. These functions
+ * should only be called at device setup/teardown by primary process.
+ *
+ * 3. Flow object lifecycle is framework-owned
+ *
+ * Engines are expected to treat framework-provided context and flow objects as
+ * storage they fill in, not storage they own. In other words, engine logic
+ * should focus on contents of flow data, while object lifetime is managed by
+ * the framework. Engines may still allocate auxiliary data, but only in places
+ * where the framework guarantees a matching teardown call, which will give the
+ * engine the opportunity to release said auxiliary data.
+ *
+ * 4. Pattern parsing: flow_graph and pattern_parse callback
+ *
+ * The flow engine framework is designed to work hand-in-hand with the
+ * `flow_graph` parsing infrastructure. Each engine may provide a pattern
+ * graph that is used to match the flow pattern, and extract relevant data
+ * into the engine context provided by the framework.
+ *
+ * Engines may also provide a `pattern_parse` callback that is invoked before
+ * the graph parser runs. This allows engines to handle pattern items that
+ * don't fit neatly into the graph model (e.g. FUZZY items that can appear at
+ * any position), as well as ignoring the graph parser entirely and implementing
+ * custom pattern parsing.
+ *
+ * There is no way to completely ignore pattern contents for the engine except
+ * for defining a noop `pattern_parse` callback. This is by design, as such case
+ * is considered rte_flow API misuse. By default, even for empty fallback case,
+ * a meaningful pattern (one that is not empty or ANY) will be treated as error.
+ *
+ * 5. Setup, teardown, and flow list lifecycle ordering
+ *
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()` are
+ * primary-process-only (see point 2), and the framework does not serialize
+ * them against concurrent flow operations or against each other - the driver
+ * must do so.
+ *
+ * The expected sequence of calls for a driver instance is:
+ *
+ * - `ci_flow_engine_conf_init()` should run from the driver's `dev_init` path.
+ *
+ * - At `dev_close`, `ci_flow_cleanup()` should be run first to drop all flows
+ *   and their internal tracking. Then, `ci_flow_engine_conf_reset()` can be run.
+ *
+ * - Devices may or may not advertise `RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP`,
+ *   i.e. support for keeping flow rules across a `dev_stop`/`dev_start`

drivers/net/intel/common/flow_engine.h line 140

Quoted 10 lines
drivers/net/intel/common/flow_engine.h new file +10 -0
+ *   cycle.
+ *
+ * - If the device does not advertise this capability, flows must be
+ *   flushed via `ci_flow_flush()` as the first step of `dev_stop()` (doing so
+ *   later may interfere with flow uninstall).
+ *
+ * - If rule replay is needed (i.e. flows were kept rather than flushed at
+ *   `dev_stop`), the driver should call `ci_flow_replay()` from `dev_start`
+ *   to re-install the kept flows to hardware as last step.
+ *

This is excess commenting, which is the kind of thing AI likes to generate unless you tell to get to the point. Even AI evaluating itself said:

Warning

Comments are far longer than the code needs. Roughly a third of flow_engine.h (587 of 1620 lines) and flow_graph.h (175 of 507) is comment text, and many comments narrate the obvious or restate the design document. Examples:

flow_graph.h, _flow_graph_node_is_expected():

/*
* In the interest of everyone debugging flow parsing code, we should
* provide the user with meaningful messages about exactly what failed,
* as no one likes non-descript "node constraints not met" errors with
* no clear indication of where this is even coming from. What follows
* is us building said meaningful error messages. It's a bit ugly, but
* it is for the greater good.
*/

flow_engine.h: the file header and the struct ci_flow_engine_ops comment together run about 290 lines of design essay. ci_flow_parse() repeats the same match-mode table that already appears above ci_flow_engine_ops.

ixgbe_flow_dev_dump() and i40e_flow_dev_dump() (patches 4 and 13) carry a 13-line comment explaining one if statement.

i40e_fdir_flow_install() and i40e_fdir_flow_register() (patch 16) open with paragraph-length comments before a single condition.

Short one-line comments are enough for straightforward code. Put the design description in flow_graph.rst (or a short block at the top of flow_engine.h) once, and drop the per-function restatements. Trivial comments such as "/* success */", "/* is the pointer valid? */" and "/* engine looks valid */" can go.

#59 lore raw

Re: [PATCH v3 02/19] net/intel/common: add flow engines infrastructure

Quoted 102 lines · Stephen Hemminger
Quoted 99 lines · On Wed, 16 Sep 2026 13:18:08 +0100 Anatoly Burakov <[email protected]>…
drivers/net/intel/common/flow_engine.h new file +99 -0
+
+/*
+ * This is a common header for Intel Ethernet drivers' flow engine
+ * implementations. It defines the interfaces and data structures required to
+ * implement flow rule engines that can be plugged into the drivers' flow
+ * handling logic.
+ *
+ * Design considerations:
+ *
+ * 1. Ease of implementation
+ *
+ * The flow engine interface is designed to be as simple as possible with
+ * obvious defaults (i.e. not specifying something leads to behavior that
+ * would've been the most expected in context). The point is not to produce a
+ * monstrous driver-within-a-driver framework, but rather to make engine
+ * definitions follow semantic expectations of what the engine actually does.
+ *
+ * All the boilerplate (flow management, engine enablement tracking, etc.) is
+ * handled by the common flow infrastructure, so the engine implementation only
+ * needs to focus on the actual logic of parsing and installing/uninstalling
+ * flow rules, and defining each step of the process as it pertains to each flow
+ * engine.
+ *
+ * It is expected that drivers will use other utility functions from the common
+ * flow-related code where applicable (e.g. flow_util.h, flow_check.h, etc.).
+ *
+ * 2. Full secondary process compatibility
+ *
+ * In order to support rte_flow operations in secondary processes, we need to
+ * store which engines are enabled for particular driver instance, and resolve
+ * them at runtime. The engine index (its position in the engine list) is used as
+ * a bit position in a driver-specific 64-bit field of enabled engines. This
+ * way, the engine definitions can be stored in read-only memory, and referenced
+ * by both primary and secondary processes without issues.
+ *
+ * For this to remain safe, flow engine lists and engine definitions must be
+ * immutable for process lifetime (declare them as const).
+ *
+ * Note that this does not imply that all drivers are therefore able to support
+ * rte_flow-related operations in secondary processes - that is still up to each
+ * driver to implement. This just ensures that the flow engine framework does
+ * not prevent it.
+ *
+ * Engine callbacks must not access or retain an `struct rte_eth_dev *` pointer,
+ * as that object is process-local; use the process-independent
+ * `struct rte_eth_dev_data *` provided by the framework instead.
+ *
+ * The per-instance engine configuration is set up and torn down exclusively by
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()`. These functions
+ * should only be called at device setup/teardown by primary process.
+ *
+ * 3. Flow object lifecycle is framework-owned
+ *
+ * Engines are expected to treat framework-provided context and flow objects as
+ * storage they fill in, not storage they own. In other words, engine logic
+ * should focus on contents of flow data, while object lifetime is managed by
+ * the framework. Engines may still allocate auxiliary data, but only in places
+ * where the framework guarantees a matching teardown call, which will give the
+ * engine the opportunity to release said auxiliary data.
+ *
+ * 4. Pattern parsing: flow_graph and pattern_parse callback
+ *
+ * The flow engine framework is designed to work hand-in-hand with the
+ * `flow_graph` parsing infrastructure. Each engine may provide a pattern
+ * graph that is used to match the flow pattern, and extract relevant data
+ * into the engine context provided by the framework.
+ *
+ * Engines may also provide a `pattern_parse` callback that is invoked before
+ * the graph parser runs. This allows engines to handle pattern items that
+ * don't fit neatly into the graph model (e.g. FUZZY items that can appear at
+ * any position), as well as ignoring the graph parser entirely and implementing
+ * custom pattern parsing.
+ *
+ * There is no way to completely ignore pattern contents for the engine except
+ * for defining a noop `pattern_parse` callback. This is by design, as such case
+ * is considered rte_flow API misuse. By default, even for empty fallback case,
+ * a meaningful pattern (one that is not empty or ANY) will be treated as error.
+ *
+ * 5. Setup, teardown, and flow list lifecycle ordering
+ *
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()` are
+ * primary-process-only (see point 2), and the framework does not serialize
+ * them against concurrent flow operations or against each other - the driver
+ * must do so.
+ *
+ * The expected sequence of calls for a driver instance is:
+ *
+ * - `ci_flow_engine_conf_init()` should run from the driver's `dev_init` path.
+ *
+ * - At `dev_close`, `ci_flow_cleanup()` should be run first to drop all flows
+ *   and their internal tracking. Then, `ci_flow_engine_conf_reset()` can be run.
+ *
+ * - Devices may or may not advertise `RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP`,
+ *   i.e. support for keeping flow rules across a `dev_stop`/`dev_start`
+ *   cycle.
+ *
+ * - If the device does not advertise this capability, flows must be
+ *   flushed via `ci_flow_flush()` as the first step of `dev_stop()` (doing so
+ *   later may interfere with flow uninstall).
Quoted 8 lines
Quoted 5 lines

+ *
+ * - If rule replay is needed (i.e. flows were kept rather than flushed at
+ * dev_stop), the driver should call ci_flow_replay() from dev_start
+ * to re-install the kept flows to hardware as last step.
+ *

This is excess commenting, which is the kind of thing AI likes to generate unless you tell to get to the point. Even AI evaluating itself said:

Such "excessive" commenting is intentional and it was not in fact written by AI, it was written by me :)

This is an Intel-internal header so this can't be in programmer's guide, but I do need to spell out the design intention and constraints, both to prevent future misuse, and to enable far easier and far more precise AI review (because now AI can read all of these comments and notice if the reviewed code does not follow the design intent). AI calls this a "design essay", and it's right: it *is* a design essay! That was the entire point!

Quoted 27 lines

Warning

Comments are far longer than the code needs. Roughly a third of flow_engine.h (587 of 1620 lines) and flow_graph.h (175 of 507) is comment text, and many comments narrate the obvious or restate the design document. Examples:

flow_graph.h, _flow_graph_node_is_expected():

/*
* In the interest of everyone debugging flow parsing code, we should
* provide the user with meaningful messages about exactly what failed,
* as no one likes non-descript "node constraints not met" errors with
* no clear indication of where this is even coming from. What follows
* is us building said meaningful error messages. It's a bit ugly, but
* it is for the greater good.
*/

flow_engine.h: the file header and the struct ci_flow_engine_ops comment together run about 290 lines of design essay. ci_flow_parse() repeats the same match-mode table that already appears above ci_flow_engine_ops.

ixgbe_flow_dev_dump() and i40e_flow_dev_dump() (patches 4 and 13) carry a 13-line comment explaining one if statement.

Quoted 8 lines

i40e_fdir_flow_install() and i40e_fdir_flow_register() (patch 16) open with paragraph-length comments before a single condition.

Short one-line comments are enough for straightforward code. Put the design description in flow_graph.rst (or a short block at the top of flow_engine.h) once, and drop the per-function restatements. Trivial comments such as "/* success */", "/* is the pointer valid? */" and "/* engine looks valid */" can go.

This can't go into flow_graph documentation because this is not ethdev header, this is common Intel code. There's no place in documentation for this, at least not that I can see.

I'll see if I can reduce the number of "obvious" comments and any possible duplication, but I would not look to reduce the overall comment style. This is intended to present a complex system, I *want* it to be well documented. The fact that our driver code is *not* this well documented is why it's often impossible to reason about it.

Signature · 2 lines
Thanks,
Anatoly
#60 lore raw

Re: [PATCH v3 02/19] net/intel/common: add flow engines infrastructure

In reply to Anatoly Burakov
Quoted 4 lines · Anatoly Burakov

Current implementation of flow engines in various drivers have a few issues that need to be corrected.

For one, some of the

seems like a missed word?

Quoted 42 lines

are fundamentally incompatible with secondary
processes, because the flow engine registration and creation will allocate structures in shared memory but use process-local pointers to point to flow engines and pattern tables.

For another, a lot of them are needlessly complicated and rely on a separation between patterns and parsing that is hard to reason about and
maintain: they do not define memory ownership model, they do not define the way in which we approach parameter and pattern parsing, and they occasionally do weird things like passing around pointers-to-void-pointers or even using pointers as integer values.

Another common problem is extremely convoluted internal tracking, flow installation, flow replay, and cleanup code. This infrastructure is usually done in an ad-hoc manner that has a lot of boilerplate.

These issues can be corrected, but because of how much code there is to the current infrastructure and how tightly coupled it is, it would be easier to just build new one from scratch, and gradually migrate all engines to use it. This patch is intended as a first step towards that goal, and defines both common data types to be used by all rte_flow parsers, as well as the interaction model that is to be followed by all drivers.

We define a set of structures that will represent:

- Defined rte_flow parsing interaction model and code flow (ops struct)
- Defined memory allocation and ownership model for all engines
- Scratch space format for all engines (variably allocated typed struct)
- Flow rule format for all engines (variably allocated typed struct)
- Engine definitions that are compatible with secondary process model
- Implementations of common rte_flow operations
- Various supporting infrastructure for parser customization, e.g. hooks
- Support for using custom allocation (e.g. for mempool-based alloc)
- Support for replaying all flows to restore HW state
- Support for removing all flows without modifying HW state

The design intent is heavily documented right inside the header and is to be considered authoritative design document for how to build rte_flow parsers for Intel Ethernet drivers going forward.

---

<snip>

drivers/net/intel/common/flow_engine.h line 780

Quoted 11 lines
drivers/net/intel/common/flow_engine.h new file +11 -0
+/* enable all engines for a specific driver instance - caller must serialize initialization */
+static inline int
+ci_flow_engine_conf_init(struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine_list *engine_list,
+		struct rte_eth_dev_data *dev_data)
+{
+	struct ci_flow_engine_ref engine_ref;
+
+	/* reject invalid configuration */
+	if (engine_conf == NULL || engine_list == NULL || dev_data == NULL)
+		return -1;

return -EINVAL?

Quoted 4 lines
drivers/net/intel/common/flow_engine.h new file +4 -0
+
+	/* init the lock */
+	rte_rwlock_init(&engine_conf->config_lock);
+

<snip>

Quoted 52 lines
drivers/net/intel/common/flow_engine.h new file +52 -0
+/* parse a flow using a specific engine - caller must hold config lock */
+static inline int
+ci_flow_parse(const struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine *engine,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	enum ci_match_type match_type;
+	struct ci_flow_engine_ctx *ctx;
+	int ret = 0;
+
+	/*
+	 * Determine the type of matching we are going to perform based on the
+	 * presence of pattern graph and pattern_parse callback. The logic is as
+	 * follows:
+	 *
+	 * - if graph but no callback, match against graph
+	 *
+	 * Expected default case: pattern matching is graph based, no special
+	 * handling for any pattern items.
+	 *
+	 * - if both graph and callback, match against callback + graph
+	 *
+	 * Preprocessor case, i.e. preprocess the pattern with the callback
+	 * before handling the matching to the graph engine. The assumption is
+	 * that the graph will be set up with a proper ignore list to skip over
+	 * nodes that weren't meant for the graph processing.
+	 *
+	 * - if no graph but callback, match against callback
+	 *
+	 * Fully custom pattern parsing case.
+	 *
+	 * - if no graph and no callback, match against empty graph
+	 *
+	 * "Pattern is not meaningful" case, for engines that do not care about
+	 * the pattern at all. A default matching behavior against empty
+	 * patterns is provided (i.e. allow NULL pattern, and allow END or ANY
+	 * -> END patterns). Note that this is not the same as ignoring pattern
+	 * entirely: the engine will still reject patterns that are not empty.
+	 */
+	match_type = engine->graph == NULL ?
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_EMPTY : CI_MATCH_CALLBACK) :
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_GRAPH : CI_MATCH_ALL);
+
+	CI_DRV_LOG(DEBUG, "engine '%s': parsing flow", engine->name);
+
+	/* allocate context */
+	ctx = (struct ci_flow_engine_ctx *)calloc(1,
+			RTE_MAX(engine->ctx_size, sizeof(struct ci_flow_engine_ctx)));

drivers/net/intel/common/flow_engine.h line 1013

Quoted 10 lines
drivers/net/intel/common/flow_engine.h new file +10 -0
+	if (ctx == NULL) {
+		return rte_flow_error_set(error, ENOMEM,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Failed to allocate memory for rule engine context");
+	}
+	ctx->dev_data = engine_conf->dev_data;
+	ctx->attr = attr;
+	ctx->pattern = pattern;
+	ctx->actions = actions;
+	flow->dev_data = engine_conf->dev_data;

it was set in ci_flow_alloc()

Quoted 3 lines
drivers/net/intel/common/flow_engine.h new file +3 -0
+
+	/* parse flow parameters */
+	ret = engine->ops->ctx_init(actions, attr, ctx, error);

<snip>

drivers/net/intel/common/flow_engine.h line 1197

Quoted 7 lines
drivers/net/intel/common/flow_engine.h new file +7 -0
+
+	/* no engine could handle this flow */
+	CI_DRV_LOG(DEBUG, "no engine accepted the flow");
+	flow = NULL;
+	rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"No flow engine could handle the requested flow");

This unconditionally rewrites error string, don't we want to keep the last reason why flow wasn't created? Same is applied for validate.

Quoted 6 lines
drivers/net/intel/common/flow_engine.h new file +6 -0
+unlock:
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return (struct rte_flow *)flow;
+}
+

<snip>

Signature · 2 lines
Regards,
Vladimir
#61 lore raw

Re: [PATCH v3 02/19] net/intel/common: add flow engines infrastructure

Quoted 7 lines · Vladimir
Quoted 4 lines · Anatoly Burakov

Current implementation of flow engines in various drivers have a few issues that need to be corrected.

For one, some of the

seems like a missed word?

Not missed, typoed - should've been "them" not "the" :)

Quoted 4 lines
Quoted 4 lines

are fundamentally incompatible with secondary
processes, because the flow engine registration and creation will allocate structures in shared memory but use process-local pointers to point to flow engines and pattern tables.

<snip>

Quoted 8 lines
Quoted 7 lines

+
+    /* no engine could handle this flow */
+    CI_DRV_LOG(DEBUG, "no engine accepted the flow"); +    flow = NULL;
+    rte_flow_error_set(error, ENOTSUP,
+            RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+            "No flow engine could handle the requested flow");

This unconditionally rewrites error string, don't we want to keep the last reason why flow wasn't created? Same is applied for validate.

No, if no engine accepted the flow that means there are no meaningful error messages to be had here. If the pattern matching failed but should've succeeded, there is no way for us to tell whether it was *supposed to* succeed or whether this pattern genuinely needs a different engine. So, if no engine accepted the flow, we have no idea whether it's because the flow is genuinely unsupported, or because there is a bug in engine implementation.

In contrast, if the engine has accepted the flow (i.e. if *parsing* succeeded), then a failure on register/install is a genuine error, in which case we do not try other engines, and output a meaningful error message.

Signature · 2 lines
Thanks,
Anatoly
#62 lore raw

Re: [PATCH v3 02/19] net/intel/common: add flow engines infrastructure

In reply to Medvedkin, Vladimir
Quoted 49 lines · Vladimir
Quoted 4 lines · Anatoly Burakov

Current implementation of flow engines in various drivers have a few issues that need to be corrected.

For one, some of the

seems like a missed word?

Quoted 42 lines

are fundamentally incompatible with secondary
processes, because the flow engine registration and creation will allocate structures in shared memory but use process-local pointers to point to flow engines and pattern tables.

For another, a lot of them are needlessly complicated and rely on a separation between patterns and parsing that is hard to reason about and
maintain: they do not define memory ownership model, they do not define the way in which we approach parameter and pattern parsing, and they occasionally do weird things like passing around pointers-to-void- pointers or even using pointers as integer values.

Another common problem is extremely convoluted internal tracking, flow installation, flow replay, and cleanup code. This infrastructure is usually done in an ad-hoc manner that has a lot of boilerplate.

These issues can be corrected, but because of how much code there is to the current infrastructure and how tightly coupled it is, it would be easier to just build new one from scratch, and gradually migrate all engines to use it. This patch is intended as a first step towards that goal, and defines both common data types to be used by all rte_flow parsers, as well as the interaction model that is to be followed by all drivers.

We define a set of structures that will represent:

- Defined rte_flow parsing interaction model and code flow (ops struct)
- Defined memory allocation and ownership model for all engines
- Scratch space format for all engines (variably allocated typed struct)
- Flow rule format for all engines (variably allocated typed struct)
- Engine definitions that are compatible with secondary process model
- Implementations of common rte_flow operations
- Various supporting infrastructure for parser customization, e.g. hooks
- Support for using custom allocation (e.g. for mempool-based alloc)
- Support for replaying all flows to restore HW state
- Support for removing all flows without modifying HW state

The design intent is heavily documented right inside the header and is to be considered authoritative design document for how to build rte_flow parsers for Intel Ethernet drivers going forward.

---

<snip>

Quoted 15 lines
Quoted 6 lines

+    /* allocate context */
+    ctx = (struct ci_flow_engine_ctx *)calloc(1, +            RTE_MAX(engine->ctx_size, sizeof(struct ci_flow_engine_ctx)));
+    if (ctx == NULL) {
+        return rte_flow_error_set(error, ENOMEM,

Quoted 8 lines

+                RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+                "Failed to allocate memory for rule engine context"); +    }
+    ctx->dev_data = engine_conf->dev_data;
+    ctx->attr = attr;
+    ctx->pattern = pattern;
+    ctx->actions = actions;
+    flow->dev_data = engine_conf->dev_data;

Quoted 1 line

it was set in ci_flow_alloc()

flow_alloc might not be called (e.g. for validate).

Signature · 2 lines
Thanks,
Anatoly
#63 lore raw

PATCH v3 3/19 net/intel/common: add utility functions

In reply to Anatoly Burakov

A lot of parsers will rely on doing the same things over and over, so create a header with utility functions to aid in writing parsers.

---
 drivers/net/intel/common/flow_util.h | 228 +++++++++++++++++++++++++++
 1 file changed, 228 insertions(+)
 create mode 100644 drivers/net/intel/common/flow_util.h
drivers/net/intel/common/flow_util.h new file +228 -0
new file mode 100644
index 00000000000..698a83a1785
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_FLOW_UTIL_H_
+#define _COMMON_INTEL_FLOW_UTIL_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <rte_debug.h>
+
+/*
+ * Utility functions primarily intended for flow parsers.
+ */
+
+/*
+ * Simplistic reference counter. Not thread-safe, not atomic.
+ */
+struct ci_refcount {
+	uint32_t count;
+};
+
+/**
+ * Take a reference. Returns true if this is the first reference.
+ *
+ * @param ref
+ *   Reference count to acquire.
+ * @return
+ *   true if this is the first reference, false otherwise.
+ */
+static inline bool
+ci_refcount_acquire(struct ci_refcount *ref)
+{
+#ifdef CI_DEBUG
+	if (ref->count == UINT32_MAX)
+		rte_panic("ci_refcount overflow\n");
+#endif
+	return ref->count++ == 0;
+}
+
+/**
+ * Drop a reference. Returns true if this was the last reference.
+ *
+ * @param ref
+ *   Reference count to release.
+ * @return
+ *   true if this was the last reference, false otherwise.
+ */
+static inline bool
+ci_refcount_release(struct ci_refcount *ref)
+{
+#ifdef CI_DEBUG
+	if (ref->count == 0)
+		rte_panic("ci_refcount underflow\n");
+#endif
+	return --ref->count == 0;
+}
+
+/**
+ * Check if memory region is filled with a specific byte value.
+ *
+ * @param ptr
+ *   Pointer to memory region.
+ * @param len
+ *   Length in bytes.
+ * @param val
+ *   Byte value to check (e.g. 0x00 or 0xFF).
+ * @return
+ *   true if all bytes equal val, false otherwise.
+ */
+static inline bool
+ci_is_all_byte(const void *ptr, size_t len, uint8_t val)
+{
+	const uint8_t *bytes = (const uint8_t *)ptr;
+	const uint32_t pattern32 = 0x01010101U * val;
+	size_t i = 0;
+
+	/* Process 4-byte chunks using memcpy */
+	for (; i + 4 <= len; i += 4) {
+		uint32_t chunk;
+		memcpy(&chunk, bytes + i, 4);
+		if (chunk != pattern32)
+			return false;
+	}
+
+	/* Process remaining bytes */
+	for (; i < len; i++) {
+		if (bytes[i] != val)
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * Check if bytes are all 0x00 OR all 0xFF.
+ *
+ * @param ptr
+ *   Pointer to memory region.
+ * @param len
+ *   Length in bytes.
+ * @return
+ *   true if all bytes are 0x00 OR all bytes are 0xFF, false otherwise.
+ */
+static inline bool
+ci_is_all_zero_or_masked(const void *ptr, size_t len)
+{
+	const uint8_t *bytes = (const uint8_t *)ptr;
+	uint8_t first_val;
+
+	/* zero length cannot be valid */
+	if (len == 0)
+		return false;
+
+	first_val = bytes[0];
+
+	if (first_val != 0x00 && first_val != 0xFF)
+		return false;
+
+	return ci_is_all_byte(ptr, len, first_val);
+}
+
+/**
+ * Check if a value has no bits outside the mask, and within the mask is
+ * either all-zero or all-one.
+ *
+ * This is intended for bitfields e.g. VLAN_TCI. For byte-aligned fields,
+ * use CI_FIELD_IS_ZERO_OR_MASKED below.
+ *
+ * @param value
+ *   Data value to check.
+ * @param mask
+ *   Mask to compare against.
+ * @return
+ *   true if (value & ~mask) == 0 AND (value & mask) is 0 or mask,
+ *   false otherwise.
+ */
+static inline bool
+ci_is_zero_or_masked(uint64_t value, uint64_t mask)
+{
+	uint64_t masked = value & mask;
+	uint64_t unmasked = value & ~mask;
+
+	return unmasked == 0 && (masked == 0 || masked == mask);
+}
+
+/**
+ * Check if a struct field is fully masked or unmasked.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_ZERO_OR_MASKED(field_ptr) \
+	ci_is_all_zero_or_masked((field_ptr), sizeof(*(field_ptr)))
+
+/**
+ * Check if a struct field is all 0x00.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_ZERO(field_ptr) \
+	ci_is_all_byte((field_ptr), sizeof(*(field_ptr)), 0x00)
+
+/**
+ * Check if a struct field is all 0xFF.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_MASKED(field_ptr) \
+	ci_is_all_byte((field_ptr), sizeof(*(field_ptr)), 0xFF)
+
+/**
+ * Convert 24-bit big-endian value to host byte order.
+ *
+ * Used to extract 24-bit big-endian values (e.g. VXLAN VNI).
+ *
+ * @param val
+ *   Pointer to 3-byte big-endian value.
+ * @return
+ *   Value in host byte order.
+ */
+static inline uint32_t
+ci_be24_to_cpu(const uint8_t val[3])
+{
+	return (val[0] << 16) | (val[1] << 8) | val[2];
+}
+
+/**
+ * Check if a character is a valid hexadecimal digit.
+ *
+ * @param c
+ *   Character to check.
+ * @return
+ *   true if c is in [0-9a-fA-F], false otherwise.
+ */
+static inline bool
+ci_is_hex_char(unsigned char c)
+{
+	return ((c >= '0' && c <= '9') ||
+		(c >= 'a' && c <= 'f') ||
+		(c >= 'A' && c <= 'F'));
+}
+
+/**
+ * Convert hex character to 4-bit value.
+ *
+ * @param c
+ *   Hex character ('0'-'9', 'a'-'f', 'A'-'F').
+ * @return
+ *   Value 0-15, or 0 if invalid.
+ */
+static inline unsigned char
+ci_hex_char_to_nibble(unsigned char c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	return 0;
+}
+
+#endif /* _INTEL_COMMON_FLOW_UTIL_H_ */
Signature · 1 line
2.52.0
#64 lore raw

Re: [PATCH v3 03/19] net/intel/common: add utility functions

Quoted 15 lines · On Wed, 16 Sep 2026 13:18:09 +0100 Anatoly Burakov <[email protected]>…
drivers/net/intel/common/flow_util.h new file +15 -0
+/*
+ * Simplistic reference counter. Not thread-safe, not atomic.
+ */
+struct ci_refcount {
+	uint32_t count;
+};
+
+/**
+ * Take a reference. Returns true if this is the first reference.
+ *
+ * @param ref
+ *   Reference count to acquire.
+ * @return
+ *   true if this is the first reference, false otherwise.
+ */

drivers/net/intel/common/flow_util.h line 42

Quoted 10 lines
drivers/net/intel/common/flow_util.h new file +10 -0
+static inline bool
+ci_refcount_acquire(struct ci_refcount *ref)
+{
+#ifdef CI_DEBUG
+	if (ref->count == UINT32_MAX)
+		rte_panic("ci_refcount overflow\n");
+#endif
+	return ref->count++ == 0;
+}
+

Would it help if EAL has a generic refcount?
I would make the refcount overflow/underflow still fatal, should never happen.

#65 lore raw

Re: [PATCH v3 03/19] net/intel/common: add utility functions

Quoted 23 lines · Stephen Hemminger
Quoted 20 lines · On Wed, 16 Sep 2026 13:18:09 +0100 Anatoly Burakov <[email protected]>…
drivers/net/intel/common/flow_util.h new file +20 -0
+/*
+ * Simplistic reference counter. Not thread-safe, not atomic.
+ */
+struct ci_refcount {
+	uint32_t count;
+};
+
+/**
+ * Take a reference. Returns true if this is the first reference.
+ *
+ * @param ref
+ *   Reference count to acquire.
+ * @return
+ *   true if this is the first reference, false otherwise.
+ */
+static inline bool
+ci_refcount_acquire(struct ci_refcount *ref)
+{
+#ifdef CI_DEBUG
+	if (ref->count == UINT32_MAX)
Quoted 8 lines

drivers/net/intel/common/flow_util.h line 42

Quoted 5 lines
drivers/net/intel/common/flow_util.h new file +5 -0
+		rte_panic("ci_refcount overflow\n");
+#endif
+	return ref->count++ == 0;
+}
+

Would it help if EAL has a generic refcount?
I would make the refcount overflow/underflow still fatal, should never happen.

In the next revision I changed the name to ci_usage_count_t which is a more descriptive name than a refcount (less connotations), and made it a direct uint32_t instead of a struct.

It would be nice to have this in EAL, but I'm not sure of the scope of such work and what sort of use cases should this cover. It can be promoted to EAL later if you think it's worth doing, and I'll gladly submit a patch for the next release and we can work both towards defining what sorts of use cases we want to cover and convert the codebase to use those, but I would defer it to next release.

Signature · 2 lines
Thanks,
Anatoly
#66 lore raw

Re: [PATCH v3 03/19] net/intel/common: add utility functions

In reply to Anatoly Burakov
Quoted 5 lines · Anatoly Burakov

A lot of parsers will rely on doing the same things over and over, so create a header with utility functions to aid in writing parsers.

---

<snip>

Quoted 13 lines
drivers/net/intel/common/flow_util.h new file +13 -0
+
+/**
+ * Check if a value has no bits outside the mask, and within the mask is
+ * either all-zero or all-one.
+ *
+ * This is intended for bitfields e.g. VLAN_TCI. For byte-aligned fields,
+ * use CI_FIELD_IS_ZERO_OR_MASKED below.
+ *
+ * @param value
+ *   Data value to check.
+ * @param mask
+ *   Mask to compare against.
+ * @return

drivers/net/intel/common/flow_util.h line 146

Quoted 10 lines
drivers/net/intel/common/flow_util.h new file +10 -0
+ *   true if (value & ~mask) == 0 AND (value & mask) is 0 or mask,
+ *   false otherwise.
+ */
+static inline bool
+ci_is_zero_or_masked(uint64_t value, uint64_t mask)
+{
+	uint64_t masked = value & mask;
+	uint64_t unmasked = value & ~mask;
+
+	return unmasked == 0 && (masked == 0 || masked == mask);

I think it can be done in a more simple way:

return value == 0 || value == mask

Quoted 2 lines
(quoted diff) +2 -0
+}
+

<snip>

Review

Quoted 2 lines
(quoted diff) +2 -0
+
+#endif /* _INTEL_COMMON_FLOW_UTIL_H_ */

"_COMMON_INTEL_FLOW_UTIL_H_" I guess?

Signature · 2 lines
Regards,
Vladimir
#67 lore raw

PATCH v3 4/19 net/ixgbe: add support for common flow parsing

In reply to Anatoly Burakov

Implement support for common flow parsing infrastructure in preparation for migration of flow engines. The following features are enabled:

- Conf init on dev_init
- Conf uninit on dev_close
- Flow cleanup on dev_close
- Flow replay on dev_start

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c | 22 ++++++++++-
 drivers/net/intel/ixgbe/ixgbe_ethdev.h |  5 +++
 drivers/net/intel/ixgbe/ixgbe_flow.c   | 54 +++++++++++++++++++++++++-
 drivers/net/intel/ixgbe/ixgbe_flow.h   | 12 ++++++
 4 files changed, 90 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow.h
drivers/net/intel/ixgbe/ixgbe_ethdev.c +20 -2
index fc3a02c7d98..48cf608aebb 100644
@@ -46,6 +46,7 @@
 #include "base/ixgbe_phy.h"
 #include "ixgbe_osdep.h"
 #include "ixgbe_regs.h"
+#include "ixgbe_flow.h"
 
 /*
  * High threshold controlling when to start sending XOFF frames. Must be at
@@ -1373,6 +1374,12 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	if (ret)
 		goto err_l2_tn_filter_init;
 
+	/* initialize flow engine configuration */
+	ret = ci_flow_engine_conf_init(&ad->flow_engine_conf,
+			&ixgbe_flow_engine_list, eth_dev->data);
+	if (ret)
+		goto err_flow_engine_conf_init;
+
 	/* initialize flow filter lists */
 	ixgbe_filterlist_init(eth_dev);
 
@@ -1384,6 +1391,8 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 
 	return 0;
 
+err_flow_engine_conf_init:
+	ixgbe_l2_tn_filter_uninit(eth_dev);
 err_l2_tn_filter_init:
 	ixgbe_fdir_filter_uninit(eth_dev);
 err_fdir_filter_init:
@@ -2949,6 +2958,9 @@ ixgbe_dev_start(struct rte_eth_dev *dev)
 	if (macsec_setting->offload_en)
 		ixgbe_dev_macsec_register_enable(dev, macsec_setting);
 
+	/* re-install kept flows to hardware */
+	ci_flow_replay(&adapter->flow_engine_conf);
+
 	return 0;
 
 error:
@@ -3128,8 +3140,8 @@ ixgbe_dev_set_link_down(struct rte_eth_dev *dev)
 static int
 ixgbe_dev_close(struct rte_eth_dev *dev)
 {
-	struct ixgbe_hw *hw =
-		IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_adapter *ad = dev->data->dev_private;
+	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(ad);
 	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
 	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
 	int retries = 0;
@@ -3196,6 +3208,12 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	rte_free(dev->security_ctx);
 	dev->security_ctx = NULL;
 
+	/* drop all flows */
+	ci_flow_cleanup(&ad->flow_engine_conf);
+
+	/* reset flow engines */
+	ci_flow_engine_conf_reset(&ad->flow_engine_conf);
+
 	return ret;
 }
 
drivers/net/intel/ixgbe/ixgbe_ethdev.h +5 -0
index fd3236fda24..fe3790b9f49 100644
@@ -22,6 +22,8 @@
 #include <bus_pci_driver.h>
 #include <rte_tm_driver.h>
 
+#include "../common/flow_engine.h"
+
 /* need update link, bit flag */
 #define IXGBE_FLAG_NEED_LINK_UPDATE (uint32_t)(1 << 0)
 #define IXGBE_FLAG_MAILBOX          (uint32_t)(1 << 1)
@@ -346,6 +348,7 @@ struct ixgbe_l2_tn_info {
 };
 
 struct rte_flow {
+	struct ci_flow flow;
 	enum rte_filter_type filter_type;
 	/* security flows are not rte_filter_type */
 	bool is_security;
@@ -492,6 +495,8 @@ struct ixgbe_adapter {
 	struct rte_timecounter      tx_tstamp_tc;
  	struct ixgbe_tm_conf        tm_conf;
 
+	struct ci_flow_engine_conf flow_engine_conf;
+
 	/* For RSS reta table update */
 	uint8_t rss_reta_updated;
 
drivers/net/intel/ixgbe/ixgbe_flow.c +53 -1

104 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h new file +12 -0
new file mode 100644
index 00000000000..5e68c9886c1
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _IXGBE_FLOW_H_
+#define _IXGBE_FLOW_H_
+
+#include "../common/flow_engine.h"
+
+extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
+
+#endif /*  _IXGBE_FLOW_H_ */
Signature · 1 line
2.52.0
Acked-by on v3 4/19
#68 lore raw

Re: [PATCH v3 04/19] net/ixgbe: add support for common flow parsing

Quoted 249 lines · Anatoly Burakov

Implement support for common flow parsing infrastructure in preparation for migration of flow engines. The following features are enabled:

- Conf init on dev_init
- Conf uninit on dev_close
- Flow cleanup on dev_close
- Flow replay on dev_start

---
  drivers/net/intel/ixgbe/ixgbe_ethdev.c | 22 ++++++++++-
  drivers/net/intel/ixgbe/ixgbe_ethdev.h |  5 +++
  drivers/net/intel/ixgbe/ixgbe_flow.c   | 54 +++++++++++++++++++++++++-
  drivers/net/intel/ixgbe/ixgbe_flow.h   | 12 ++++++
  4 files changed, 90 insertions(+), 3 deletions(-)

create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow.h

drivers/net/intel/ixgbe/ixgbe_ethdev.c +20 -2
index fc3a02c7d98..48cf608aebb 100644
@@ -46,6 +46,7 @@
  #include "base/ixgbe_phy.h"
  #include "ixgbe_osdep.h"
  #include "ixgbe_regs.h"
+#include "ixgbe_flow.h"
 
  /*
   * High threshold controlling when to start sending XOFF frames. Must be at
@@ -1373,6 +1374,12 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
  	if (ret)
  		goto err_l2_tn_filter_init;
 
+	/* initialize flow engine configuration */
+	ret = ci_flow_engine_conf_init(&ad->flow_engine_conf,
+			&ixgbe_flow_engine_list, eth_dev->data);
+	if (ret)
+		goto err_flow_engine_conf_init;
+
  	/* initialize flow filter lists */
  	ixgbe_filterlist_init(eth_dev);
 
@@ -1384,6 +1391,8 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 
  	return 0;
 
+err_flow_engine_conf_init:
+	ixgbe_l2_tn_filter_uninit(eth_dev);
  err_l2_tn_filter_init:
  	ixgbe_fdir_filter_uninit(eth_dev);
  err_fdir_filter_init:
@@ -2949,6 +2958,9 @@ ixgbe_dev_start(struct rte_eth_dev *dev)
  	if (macsec_setting->offload_en)
  		ixgbe_dev_macsec_register_enable(dev, macsec_setting);
 
+	/* re-install kept flows to hardware */
+	ci_flow_replay(&adapter->flow_engine_conf);
+
  	return 0;
 
  error:
@@ -3128,8 +3140,8 @@ ixgbe_dev_set_link_down(struct rte_eth_dev *dev)
  static int
  ixgbe_dev_close(struct rte_eth_dev *dev)
  {
-	struct ixgbe_hw *hw =
-		IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_adapter *ad = dev->data->dev_private;
+	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(ad);
  	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
  	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
  	int retries = 0;
@@ -3196,6 +3208,12 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
  	rte_free(dev->security_ctx);
  	dev->security_ctx = NULL;
 
+	/* drop all flows */
+	ci_flow_cleanup(&ad->flow_engine_conf);
+
+	/* reset flow engines */
+	ci_flow_engine_conf_reset(&ad->flow_engine_conf);
+
  	return ret;
  }
 
drivers/net/intel/ixgbe/ixgbe_ethdev.h +5 -0
index fd3236fda24..fe3790b9f49 100644
@@ -22,6 +22,8 @@
  #include <bus_pci_driver.h>
  #include <rte_tm_driver.h>
 
+#include "../common/flow_engine.h"
+
  /* need update link, bit flag */
  #define IXGBE_FLAG_NEED_LINK_UPDATE (uint32_t)(1 << 0)
  #define IXGBE_FLAG_MAILBOX          (uint32_t)(1 << 1)
@@ -346,6 +348,7 @@ struct ixgbe_l2_tn_info {
  };
 
  struct rte_flow {
+	struct ci_flow flow;
  	enum rte_filter_type filter_type;
  	/* security flows are not rte_filter_type */
  	bool is_security;
@@ -492,6 +495,8 @@ struct ixgbe_adapter {
  	struct rte_timecounter      tx_tstamp_tc;
   	struct ixgbe_tm_conf        tm_conf;
 
+	struct ci_flow_engine_conf flow_engine_conf;
+
  	/* For RSS reta table update */
  	uint8_t rss_reta_updated;
 
drivers/net/intel/ixgbe/ixgbe_flow.c +53 -1
index 80d9147acf4..6c2f7d591e4 100644
@@ -47,7 +47,8 @@
  #include "rte_pmd_ixgbe.h"
 
  #include "../common/flow_check.h"
-
+#include "../common/flow_engine.h"
+#include "ixgbe_flow.h"
 
  #define IXGBE_MIN_N_TUPLE_PRIO 1
  #define IXGBE_MAX_N_TUPLE_PRIO 7
@@ -93,6 +94,8 @@ struct ixgbe_flow_mem {
  	struct rte_flow *flow;
  };
 
+const struct ci_flow_engine_list ixgbe_flow_engine_list = {0};
+
  /**
   * Endless loop will never happen with below assumption
   * 1. there is at least one no-void item(END)
@@ -2832,6 +2835,13 @@ ixgbe_flow_create(struct rte_eth_dev *dev,
  	struct ixgbe_rss_conf_ele *rss_filter_ptr;
  	struct ixgbe_flow_mem *ixgbe_flow_mem_ptr;
 
+	/* try the new flow engine first */
+	flow = ci_flow_create(&adapter->flow_engine_conf, attr, pattern, actions, error);
+	if (flow != NULL)
+		return flow;
+
+	/* fall back to legacy flow engines */
+
  	flow = rte_zmalloc("ixgbe_rte_flow", sizeof(struct rte_flow), 0);
  	if (!flow) {
  		PMD_DRV_LOG(ERR, "failed to allocate memory");
@@ -3015,6 +3025,7 @@ ixgbe_flow_validate(struct rte_eth_dev *dev,
  		const struct rte_flow_action actions[],
  		struct rte_flow_error *error)
  {
+	struct ixgbe_adapter *ad = IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
  	struct rte_eth_ntuple_filter ntuple_filter;
  	struct rte_eth_ethertype_filter ethertype_filter;
  	struct rte_eth_syn_filter syn_filter;
@@ -3023,6 +3034,13 @@ ixgbe_flow_validate(struct rte_eth_dev *dev,
  	struct ixgbe_rte_flow_rss_conf rss_conf;
  	int ret;
 
+	/* try the new flow engine first */
+	ret = ci_flow_validate(&ad->flow_engine_conf, attr, pattern, actions, error);
+	if (ret == 0)
+		return ret;
+
+	/* fall back to legacy engines */
+
  	/**
  	 *  Special case for flow action type RTE_FLOW_ACTION_TYPE_SECURITY
  	 */
@@ -3094,6 +3112,13 @@ ixgbe_flow_destroy(struct rte_eth_dev *dev,
  	struct rte_eth_fdir_conf *fdir_conf = IXGBE_DEV_FDIR_CONF(dev);
  	struct ixgbe_rss_conf_ele *rss_filter_ptr;
 
+	/* try the new flow engine first */
+	ret = ci_flow_destroy(&adapter->flow_engine_conf, flow, error);
+	if (ret == 0)
+		return 0;
+
+	/* fall back to legacy engines */
+
  	/* Validate ownership before touching HW/SW state. */
  	TAILQ_FOREACH(flow_mem_base, &adapter->flow_list, entries) {
  		struct ixgbe_flow_mem *ixgbe_flow_mem_ptr =
@@ -3197,8 +3222,16 @@ static int
  ixgbe_flow_flush(struct rte_eth_dev *dev,
  		struct rte_flow_error *error)
  {
+	struct ixgbe_adapter *ad = IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
  	int ret = 0;
 
+	/* flush all flows from the new flow engine */
+	ret = ci_flow_flush(&ad->flow_engine_conf, error);
+	if (ret) {
+		PMD_DRV_LOG(ERR, "Failed to flush flow");
+		return ret;
+	}
+
  	ixgbe_clear_all_ntuple_filter(dev);
  	ixgbe_clear_all_ethertype_filter(dev);
  	ixgbe_clear_syn_filter(dev);
@@ -3318,6 +3351,25 @@ ixgbe_flow_dev_dump(struct rte_eth_dev *dev,
  	struct ixgbe_adapter *ad = IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
  	struct ixgbe_filter_ele_base *flow_mem_base;
  	bool found = false;
+	int ret;
+
+	/* try the new flow engine first */
+	ret = ci_flow_dump(&ad->flow_engine_conf, flow, file, error);
+
+	/*
+	 * There are multiple possible situations here:
+	 *
+	 * - User requested to dump all flows
+	 * - User requested to dump a specific flow
+	 *
+	 * For the first case, we keep going because legacy engines might still
+	 * have flows we want to dump.
+	 *
+	 * For the second case, we only stop if the flow we were asked to dump
+	 * was found in the new engines, otherwise we keep looking.
+	 */
+	if (flow != NULL && ret == 0)
+		return 0;
 
  	TAILQ_FOREACH(flow_mem_base, &ad->flow_list, entries) {
  		struct ixgbe_flow_mem *ixgbe_flow_mem_ptr =
drivers/net/intel/ixgbe/ixgbe_flow.h new file +12 -0
new file mode 100644
index 00000000000..5e68c9886c1
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _IXGBE_FLOW_H_
+#define _IXGBE_FLOW_H_
+
+#include "../common/flow_engine.h"
+
+extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
+
+#endif /*  _IXGBE_FLOW_H_ */
Signature · 2 lines
Regards,
Vladimir
#69 lore raw

PATCH v3 5/19 net/ixgbe: reimplement ethertype parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ethertype.

The old ethertype parser was accepting certain things that were later rejected by the actual ethertype installation code, in particular DROP action as well as dst MAC address filtering. This was removed from the graph parser.

The ethertype filter tracking table is used by the rte_flow ethertype engine, but it is also in use by other features, so the filter tracking is refactored to be properly shared between the engine and other features that write into the same table.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c        | 186 +++++------
 drivers/net/intel/ixgbe/ixgbe_ethdev.h        |  89 ++----
 drivers/net/intel/ixgbe/ixgbe_flow.c          | 219 +------------
 drivers/net/intel/ixgbe/ixgbe_flow.h          |   8 +
 .../net/intel/ixgbe/ixgbe_flow_ethertype.c    | 295 ++++++++++++++++++
 drivers/net/intel/ixgbe/ixgbe_pf.c            |  49 +--
 drivers/net/intel/ixgbe/meson.build           |   1 +
 7 files changed, 429 insertions(+), 418 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +71 -115
index 48cf608aebb..771d2c584ba 100644
@@ -3000,6 +3000,10 @@ ixgbe_dev_stop(struct rte_eth_dev *dev)
 	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
 		return -E_RTE_SECONDARY;
 
+	/* disable timestamping; the application must re-enable it after restart */
+	if (adapter->filter.timesync_installed)
+		ixgbe_timesync_disable(dev);
+
 	ixgbe_dev_wait_setup_link_complete(dev, 0);
 
 	/* disable interrupts */
@@ -6891,76 +6895,51 @@ ixgbe_add_del_ntuple_filter(struct ixgbe_adapter *adapter,
 	return 0;
 }
 
-int
-ixgbe_add_del_ethertype_filter(struct ixgbe_adapter *adapter,
-			struct rte_eth_ethertype_filter *filter,
-			bool add)
+void
+ixgbe_ethertype_filter_program(struct ixgbe_hw *hw, uint8_t idx,
+			uint32_t etqf, uint32_t etqs)
 {
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(adapter);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	uint32_t etqf = 0;
-	uint32_t etqs = 0;
-	int ret;
-	struct ixgbe_ethertype_filter ethertype_filter;
-
-	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM)
-		return -EINVAL;
-
-	if (filter->ether_type == RTE_ETHER_TYPE_IPV4 ||
-		filter->ether_type == RTE_ETHER_TYPE_IPV6) {
-		PMD_DRV_LOG(ERR, "unsupported ether_type(0x%04x) in"
-			" ethertype filter.", filter->ether_type);
-		return -EINVAL;
-	}
-
-	if (filter->flags & RTE_ETHTYPE_FLAGS_MAC) {
-		PMD_DRV_LOG(ERR, "mac compare is unsupported.");
-		return -EINVAL;
-	}
-	if (filter->flags & RTE_ETHTYPE_FLAGS_DROP) {
-		PMD_DRV_LOG(ERR, "drop option is unsupported.");
-		return -EINVAL;
-	}
-
-	ret = ixgbe_ethertype_filter_lookup(filter_info, filter->ether_type);
-	if (ret >= 0 && add) {
-		PMD_DRV_LOG(ERR, "ethertype (0x%04x) filter exists.",
-			    filter->ether_type);
-		return -EEXIST;
-	}
-	if (ret < 0 && !add) {
-		PMD_DRV_LOG(ERR, "ethertype (0x%04x) filter doesn't exist.",
-			    filter->ether_type);
-		return -ENOENT;
-	}
-
-	if (add) {
-		etqf = IXGBE_ETQF_FILTER_EN;
-		etqf |= (uint32_t)filter->ether_type;
-		etqs |= (uint32_t)((filter->queue <<
-				    IXGBE_ETQS_RX_QUEUE_SHIFT) &
-				    IXGBE_ETQS_RX_QUEUE);
-		etqs |= IXGBE_ETQS_QUEUE_EN;
-
-		ethertype_filter.ethertype = filter->ether_type;
-		ethertype_filter.etqf = etqf;
-		ethertype_filter.etqs = etqs;
-		ethertype_filter.conf = FALSE;
-		ret = ixgbe_ethertype_filter_insert(filter_info,
-						    &ethertype_filter);
-		if (ret < 0) {
-			PMD_DRV_LOG(ERR, "ethertype filters are full.");
-			return -ENOSPC;
-		}
-	} else {
-		ret = ixgbe_ethertype_filter_remove(filter_info, (uint8_t)ret);
-		if (ret < 0)
-			return -ENOSYS;
-	}
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(ret), etqf);
-	IXGBE_WRITE_REG(hw, IXGBE_ETQS(ret), etqs);
+	IXGBE_WRITE_REG(hw, IXGBE_ETQF(idx), etqf);
+	IXGBE_WRITE_REG(hw, IXGBE_ETQS(idx), etqs);
 	IXGBE_WRITE_FLUSH(hw);
+}
+
+int
+ixgbe_ethertype_table_add(struct ixgbe_ethertype_table *table,
+			uint16_t ethertype, uint32_t etqf, uint32_t etqs)
+{
+	int free_idx = -1;
+	int i;
+
+	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
+		if (table->mask & (1u << i)) {
+			if (table->entries[i].ethertype == ethertype)
+				return -EEXIST;
+		} else if (free_idx < 0) {
+			free_idx = i;
+		}
+	}
+	if (free_idx < 0)
+		return -ENOSPC;
+
+	table->mask |= 1u << free_idx;
+	table->entries[free_idx].ethertype = ethertype;
+	table->entries[free_idx].etqf = etqf;
+	table->entries[free_idx].etqs = etqs;
+
+	return free_idx;
+}
+
+int
+ixgbe_ethertype_table_del(struct ixgbe_ethertype_table *table, uint8_t idx)
+{
+	if (idx >= IXGBE_MAX_ETQF_FILTERS || !(table->mask & (1u << idx)))
+		return -ENOENT;
+
+	table->mask &= ~(1u << idx);
+	table->entries[idx].ethertype = 0;
+	table->entries[idx].etqf = 0;
+	table->entries[idx].etqs = 0;
 
 	return 0;
 }
@@ -7198,8 +7177,11 @@ static int
 ixgbe_timesync_enable(struct rte_eth_dev *dev)
 {
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
 	uint32_t tsync_ctl;
 	uint32_t tsauxc;
+	uint32_t etqf;
 	struct timespec ts;
 
 	memset(&ts, 0, sizeof(struct timespec));
@@ -7221,10 +7203,19 @@ ixgbe_timesync_enable(struct rte_eth_dev *dev)
 	ixgbe_start_timecounters(dev);
 
 	/* Enable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588),
-			(RTE_ETHER_TYPE_1588 |
-			 IXGBE_ETQF_FILTER_EN |
-			 IXGBE_ETQF_1588));
+	etqf = RTE_ETHER_TYPE_1588 | IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_1588;
+	if (!filter_info->timesync_installed) {
+		int idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table,
+				RTE_ETHER_TYPE_1588, etqf, 0);
+
+		if (idx < 0) {
+			PMD_DRV_LOG(ERR, "no free ETQF slot for 1588 timestamping");
+			return idx;
+		}
+		filter_info->timesync_idx = idx;
+		filter_info->timesync_installed = true;
+	}
+	ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, etqf, 0);
 
 	/* Enable timestamping of received PTP packets. */
 	tsync_ctl = IXGBE_READ_REG(hw, IXGBE_TSYNCRXCTL);
@@ -7248,6 +7239,8 @@ static int
 ixgbe_timesync_disable(struct rte_eth_dev *dev)
 {
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
 	uint32_t tsync_ctl;
 
 	/* Disable timestamping of transmitted PTP packets. */
@@ -7261,7 +7254,12 @@ ixgbe_timesync_disable(struct rte_eth_dev *dev)
 	IXGBE_WRITE_REG(hw, IXGBE_TSYNCRXCTL, tsync_ctl);
 
 	/* Disable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588), 0);
+	if (filter_info->timesync_installed) {
+		ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, 0, 0);
+		ixgbe_ethertype_table_del(&filter_info->ethertype_table,
+				filter_info->timesync_idx);
+		filter_info->timesync_installed = false;
+	}
 
 	/* Stop incrementing the System Time registers. */
 	IXGBE_WRITE_REG(hw, IXGBE_TIMINCA, 0);
@@ -8403,26 +8401,6 @@ ixgbe_ntuple_filter_restore(struct rte_eth_dev *dev)
 	}
 }
 
-/* restore ethernet type filter */
-static inline void
-ixgbe_ethertype_filter_restore(struct rte_eth_dev *dev)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-	int i;
-
-	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
-		if (filter_info->ethertype_mask & (1 << i)) {
-			IXGBE_WRITE_REG(hw, IXGBE_ETQF(i),
-					filter_info->ethertype_filters[i].etqf);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQS(i),
-					filter_info->ethertype_filters[i].etqs);
-			IXGBE_WRITE_FLUSH(hw);
-		}
-	}
-}
-
 /* restore SYN filter */
 static inline void
 ixgbe_syn_filter_restore(struct rte_eth_dev *dev)
@@ -8478,7 +8456,6 @@ static int
 ixgbe_filter_restore(struct rte_eth_dev *dev)
 {
 	ixgbe_ntuple_filter_restore(dev);
-	ixgbe_ethertype_filter_restore(dev);
 	ixgbe_syn_filter_restore(dev);
 	ixgbe_fdir_filter_restore(dev);
 	ixgbe_l2_tn_filter_restore(dev);
@@ -8517,27 +8494,6 @@ ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev)
 		ixgbe_remove_5tuple_filter(adapter, p_5tuple);
 }
 
-/* remove all the ether type filters */
-void
-ixgbe_clear_all_ethertype_filter(struct rte_eth_dev *dev)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-	int i;
-
-	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
-		if (filter_info->ethertype_mask & (1 << i) &&
-		    !filter_info->ethertype_filters[i].conf) {
-			(void)ixgbe_ethertype_filter_remove(filter_info,
-							    (uint8_t)i);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQF(i), 0);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQS(i), 0);
-			IXGBE_WRITE_FLUSH(hw);
-		}
-	}
-}
-
 /* remove the SYN filter */
 void
 ixgbe_clear_syn_filter(struct rte_eth_dev *dev)
drivers/net/intel/ixgbe/ixgbe_ethdev.h +22 -67

127 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.c +6 -213

295 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +8 -0
index 5e68c9886c1..d7694283a54 100644
@@ -5,8 +5,16 @@
 #ifndef _IXGBE_FLOW_H_
 #define _IXGBE_FLOW_H_
 
+#include "../common/flow_check.h"
 #include "../common/flow_engine.h"
 
+int
+ixgbe_flow_actions_check(const struct ci_flow_actions *actions,
+		const struct ci_flow_actions_check_param *param,
+		struct rte_flow_error *error);
+
 extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
 
+extern const struct ci_flow_engine ixgbe_ethertype_flow_engine;
+
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c new file +295 -0

295 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_pf.c +26 -23

76 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 0531d37acd3..f2857feab76 100644
@@ -26,6 +26,7 @@ sources += files(
         'ixgbe_ethdev.c',
         'ixgbe_fdir.c',
         'ixgbe_flow.c',
+        'ixgbe_flow_ethertype.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
#70 lore raw

Re: [PATCH v3 05/19] net/ixgbe: reimplement ethertype parser

Quoted 15 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ethertype.

The old ethertype parser was accepting certain things that were later rejected by the actual ethertype installation code, in particular DROP action as well as dst MAC address filtering. This was removed from the graph parser.

The ethertype filter tracking table is used by the rte_flow ethertype engine, but it is also in use by other features, so the filter tracking is refactored to be properly shared between the engine and other features that write into the same table.

---

<snip>

Quoted 14 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +3 -0
@@ -7198,8 +7177,11 @@ static int
  ixgbe_timesync_enable(struct rte_eth_dev *dev)
  {
  	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
  	uint32_t tsync_ctl;
  	uint32_t tsauxc;
+	uint32_t etqf;
  	struct timespec ts;
 
  	memset(&ts, 0, sizeof(struct timespec));
@@ -7221,10 +7203,19 @@ ixgbe_timesync_enable(struct rte_eth_dev *dev)
  	ixgbe_start_timecounters(dev);

drivers/net/intel/ixgbe/ixgbe_ethdev.c ixgbe_timesync_enable(struct rte_eth_dev *dev) · line 7209

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +4 -4
 
  	/* Enable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588),
-			(RTE_ETHER_TYPE_1588 |
-			 IXGBE_ETQF_FILTER_EN |
-			 IXGBE_ETQF_1588));
+	etqf = RTE_ETHER_TYPE_1588 | IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_1588;
+	if (!filter_info->timesync_installed) {
+		int idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table,
+				RTE_ETHER_TYPE_1588, etqf, 0);

if I understood this part correctly, from now Ethertype filters are no longer statically dedicated for predefined set of ethertypes (ref IXGBE_ETQF_FILTER_1588) and now are dynamically managed. It is probably worth to separate this to another commit, or at least mention about in commit message

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +9 -0
+
+		if (idx < 0) {
+			PMD_DRV_LOG(ERR, "no free ETQF slot for 1588 timestamping");
+			return idx;
+		}
+		filter_info->timesync_idx = idx;
+		filter_info->timesync_installed = true;
+	}
+	ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, etqf, 0);
 

<snip>

drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c line 200

Quoted 12 lines
drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c new file +12 -0
+
+static int
+ixgbe_flow_ethertype_register(struct ci_flow *flow, struct rte_flow_error *error)
+{
+	struct ixgbe_ethertype_flow *ethertype_flow = (struct ixgbe_ethertype_flow *)flow;
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(flow->dev_data->dev_private);
+	int idx;
+
+	idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table,
+			ethertype_flow->ether_type, ethertype_flow->etqf,
+			ethertype_flow->etqs);

nit: is it worth to check if idx is negative instead of handling only 2 error codes?

Quoted 15 lines
drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c new file +15 -0
+	if (idx == -EEXIST) {
+		return rte_flow_error_set(error, EEXIST,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Ethertype filter already exists");
+	}
+	if (idx == -ENOSPC) {
+		return rte_flow_error_set(error, ENOSPC,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Ethertype filters are full");
+	}
+	ethertype_flow->index = idx;
+
+	return 0;
+}
+

<snip>

Quoted 29 lines
drivers/net/intel/ixgbe/ixgbe_pf.c +7 -10
@@ -168,38 +180,29 @@ ixgbe_add_tx_flow_control_drop_filter(struct rte_eth_dev *eth_dev)
  	struct ixgbe_filter_info *filter_info =
  		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(eth_dev->data->dev_private);
  	uint16_t vf_num;
+	uint32_t etqf, etqs;
  	int i;
-	struct ixgbe_ethertype_filter ethertype_filter;
 
  	if (!hw->mac.ops.set_ethertype_anti_spoofing) {
  		PMD_DRV_LOG(INFO, "ether type anti-spoofing is not supported.");
  		return;
  	}
 
-	i = ixgbe_ethertype_filter_lookup(filter_info,
-					  IXGBE_ETHERTYPE_FLOW_CTRL);
-	if (i >= 0) {
-		PMD_DRV_LOG(ERR, "A ether type filter entity for flow control already exists!");
-		return;
-	}
+	etqf = IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_TX_ANTISPOOF |
+			IXGBE_ETHERTYPE_FLOW_CTRL;
+	etqs = 0;
+	if (!filter_info->antispoof_installed) {
+		int idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table,
+				IXGBE_ETHERTYPE_FLOW_CTRL, etqf, etqs);
 
-	ethertype_filter.ethertype = IXGBE_ETHERTYPE_FLOW_CTRL;
-	ethertype_filter.etqf = IXGBE_ETQF_FILTER_EN |
-				IXGBE_ETQF_TX_ANTISPOOF |

drivers/net/intel/ixgbe/ixgbe_pf.c ixgbe_add_tx_flow_control_drop_filter(struct rte_eth_dev *eth_dev) · line 199

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_pf.c +2 -8
-				IXGBE_ETHERTYPE_FLOW_CTRL;
-	ethertype_filter.etqs = 0;
-	ethertype_filter.conf = TRUE;
-	i = ixgbe_ethertype_filter_insert(filter_info,
-					  &ethertype_filter);
-	if (i < 0) {
-		PMD_DRV_LOG(ERR, "Cannot find an unused ether type filter entity for flow control.");
-		return;
+		if (idx < 0) {
+			PMD_DRV_LOG(ERR, "no free ETQF slot for Tx anti-spoof filter");

here and for ixgbe_timesync_enable(), what if ixgbe_ethertype_table_add() returns -EEXIST? It is better to add since ixgbe_ethertype_filter_lookup() was removed

Quoted 26 lines
drivers/net/intel/ixgbe/ixgbe_pf.c +5 -5
+			return;
+		}
+		filter_info->antispoof_idx = idx;
+		filter_info->antispoof_installed = true;
  	}
-
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(i),
-			(IXGBE_ETQF_FILTER_EN |
-			IXGBE_ETQF_TX_ANTISPOOF |
-			IXGBE_ETHERTYPE_FLOW_CTRL));
+	ixgbe_ethertype_filter_program(hw, filter_info->antispoof_idx, etqf, etqs);
 
  	vf_num = dev_num_vf(eth_dev);
  	for (i = 0; i < vf_num; i++)
drivers/net/intel/ixgbe/meson.build +1 -0
index 0531d37acd3..f2857feab76 100644
@@ -26,6 +26,7 @@ sources += files(
          'ixgbe_ethdev.c',
          'ixgbe_fdir.c',
          'ixgbe_flow.c',
+        'ixgbe_flow_ethertype.c',
          'ixgbe_ipsec.c',
          'ixgbe_pf.c',
          'ixgbe_rxtx.c',
Signature · 2 lines
Regards,
Vladimir
#71 lore raw

Re: [PATCH v3 05/19] net/ixgbe: reimplement ethertype parser

Quoted 10 lines · Vladimir
Quoted 8 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ethertype.

The old ethertype parser was accepting certain things that were later rejected by the actual ethertype installation code, in particular DROP action as well as dst MAC address filtering. This was removed from the graph parser.

Quoted 7 lines
Quoted 7 lines

The ethertype filter tracking table is used by the rte_flow ethertype engine, but it is also in use by other features, so the filter tracking is refactored to be properly shared between the engine and other features that write into the same table.

---

Hi Vladimir!

Quoted 23 lines

<snip>

Quoted 22 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +0 -0
@@ -7198,8 +7177,11 @@ static int
   ixgbe_timesync_enable(struct rte_eth_dev *dev)
   {

      struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data-

Quoted 1 line

dev_private);

+    struct ixgbe_filter_info *filter_info =
+        IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private); uint32_t tsync_ctl;
      uint32_t tsauxc;
+    uint32_t etqf;
      struct timespec ts;
      memset(&ts, 0, sizeof(struct timespec));

drivers/net/intel/ixgbe/ixgbe_ethdev.c +3 -4
@@ -7221,10 +7203,19 @@ ixgbe_timesync_enable(struct rte_eth_dev *dev)
       ixgbe_start_timecounters(dev);
       /* Enable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-    IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588),
-            (RTE_ETHER_TYPE_1588 |
-             IXGBE_ETQF_FILTER_EN |
-             IXGBE_ETQF_1588));
+    etqf = RTE_ETHER_TYPE_1588 | IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_1588;
+    if (!filter_info->timesync_installed) {
+        int idx = ixgbe_ethertype_table_add(&filter_info- 
Quoted 3 lines

Review ixgbe_timesync_enable(struct rte_eth_dev *dev) · line 7209

Quoted 2 lines
(quoted diff) +1 -0
 >ethertype_table,
+                RTE_ETHER_TYPE_1588, etqf, 0);

if I understood this part correctly, from now Ethertype filters are no longer statically dedicated for predefined set of ethertypes (ref IXGBE_ETQF_FILTER_1588) and now are dynamically managed. It is probably worth to separate this to another commit, or at least mention about in commit message

Yes, I'll separate it out.

Quoted 11 lines
Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +9 -0
+
+        if (idx < 0) {
+            PMD_DRV_LOG(ERR, "no free ETQF slot for 1588 timestamping");
+            return idx;
+        }
+        filter_info->timesync_idx = idx;
+        filter_info->timesync_installed = true;
+    }
+    ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, 
 etqf, 0);

<snip>

Quoted 16 lines
Quoted 6 lines
drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c new file +3 -0
+
+static int
+ixgbe_flow_ethertype_register(struct ci_flow *flow, struct 

rte_flow_error *error)
+{
+    struct ixgbe_ethertype_flow *ethertype_flow = (struct

Quoted 8 lines

ixgbe_ethertype_flow *)flow;
+    struct ixgbe_filter_info *filter_info =
+        IXGBE_DEV_PRIVATE_TO_FILTER_INFO(flow->dev_data->dev_private); +    int idx;
+
+    idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table, +            ethertype_flow->ether_type, ethertype_flow->etqf, +            ethertype_flow->etqs);

nit: is it worth to check if idx is negative instead of handling only 2 error codes?

Will fix.

Quoted 58 lines
Quoted 16 lines

+    if (idx == -EEXIST) {
+        return rte_flow_error_set(error, EEXIST, +                RTE_FLOW_ERROR_TYPE_HANDLE, NULL, +                "Ethertype filter already exists"); +    }
+    if (idx == -ENOSPC) {
+        return rte_flow_error_set(error, ENOSPC, +                RTE_FLOW_ERROR_TYPE_HANDLE, NULL, +                "Ethertype filters are full"); +    }
+    ethertype_flow->index = idx;

drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c new file +4 -0
+
+    return 0;
+}
+

<snip>

Quoted 25 lines
(quoted diff) +0 -0
@@ -168,38 +180,29 @@ ixgbe_add_tx_flow_control_drop_filter(struct 
 rte_eth_dev *eth_dev)

      struct ixgbe_filter_info *filter_info = IXGBE_DEV_PRIVATE_TO_FILTER_INFO(eth_dev->data->dev_private); uint16_t vf_num;
+    uint32_t etqf, etqs;
      int i;
-    struct ixgbe_ethertype_filter ethertype_filter;
      if (!hw->mac.ops.set_ethertype_anti_spoofing) { PMD_DRV_LOG(INFO, "ether type anti-spoofing is not supported."); return;
      }
-    i = ixgbe_ethertype_filter_lookup(filter_info,
-                      IXGBE_ETHERTYPE_FLOW_CTRL);
-    if (i >= 0) {
-        PMD_DRV_LOG(ERR, "A ether type filter entity for flow control already exists!");
-        return;
-    }
+    etqf = IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_TX_ANTISPOOF |
+            IXGBE_ETHERTYPE_FLOW_CTRL;
+    etqs = 0;
+    if (!filter_info->antispoof_installed) {
+        int idx = ixgbe_ethertype_table_add(&filter_info-

Quoted 1 line

ethertype_table,

Quoted 16 lines

+                IXGBE_ETHERTYPE_FLOW_CTRL, etqf, etqs); -    ethertype_filter.ethertype = IXGBE_ETHERTYPE_FLOW_CTRL; -    ethertype_filter.etqf = IXGBE_ETQF_FILTER_EN |
-                IXGBE_ETQF_TX_ANTISPOOF |
-                IXGBE_ETHERTYPE_FLOW_CTRL;
-    ethertype_filter.etqs = 0;
-    ethertype_filter.conf = TRUE;
-    i = ixgbe_ethertype_filter_insert(filter_info,
-                      &ethertype_filter);
-    if (i < 0) {
-        PMD_DRV_LOG(ERR, "Cannot find an unused ether type filter entity for flow control.");
-        return;
+        if (idx < 0) {
+            PMD_DRV_LOG(ERR, "no free ETQF slot for Tx anti-spoof filter");

Quoted 1 line

here and for ixgbe_timesync_enable(), what if ixgbe_ethertype_table_add() returns -EEXIST? It is better to add since ixgbe_ethertype_filter_lookup() was removed

This should not be possible. If the entry is already installed, antispoof_installed will be true. If it's false, then the only way in which we could have the same filter already installed is through rte_flow which would not produce such specific ETQF values.

Signature · 2 lines
Thanks,
Anatoly
#72 lore raw

PATCH v3 6/19 net/ixgbe: reimplement syn parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for SYN.

As a result of this migration, queue index validation has changed:

- queue is now validated at parse time against the configured number of Rx queues (nb_rx_queues), rather than at install time against the hardware maximum (IXGBE_MAX_RX_QUEUE_NUM)
- the per-function queue bound check in ixgbe_syn_filter_set() has been removed as it is no longer needed

The syn filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c   |  70 +----
 drivers/net/intel/ixgbe/ixgbe_ethdev.h   |   7 +-
 drivers/net/intel/ixgbe/ixgbe_flow.c     | 247 +-----------------
 drivers/net/intel/ixgbe/ixgbe_flow.h     |   1 +
 drivers/net/intel/ixgbe/ixgbe_flow_syn.c | 312 +++++++++++++++++++++++
 drivers/net/intel/ixgbe/meson.build      |   1 +
 6 files changed, 319 insertions(+), 319 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_syn.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +2 -68
index 771d2c584ba..00ba6f32fa9 100644
@@ -6517,43 +6517,11 @@ ixgbevf_set_default_mac_addr(struct rte_eth_dev *dev,
 	return 0;
 }
 
-int
-ixgbe_syn_filter_set(struct ixgbe_adapter *adapter,
-			struct rte_eth_syn_filter *filter,
-			bool add)
+void
+ixgbe_syn_filter_program(struct ixgbe_hw *hw, uint32_t synqf)
 {
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(adapter);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	uint32_t syn_info;
-	uint32_t synqf;
-
-	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM)
-		return -EINVAL;
-
-	syn_info = filter_info->syn_info;
-
-	if (add) {
-		if (syn_info & IXGBE_SYN_FILTER_ENABLE)
-			return -EINVAL;
-		synqf = (uint32_t)(((filter->queue << IXGBE_SYN_FILTER_QUEUE_SHIFT) &
-			IXGBE_SYN_FILTER_QUEUE) | IXGBE_SYN_FILTER_ENABLE);
-
-		if (filter->hig_pri)
-			synqf |= IXGBE_SYN_FILTER_SYNQFP;
-		else
-			synqf &= ~IXGBE_SYN_FILTER_SYNQFP;
-	} else {
-		synqf = IXGBE_READ_REG(hw, IXGBE_SYNQF);
-		if (!(syn_info & IXGBE_SYN_FILTER_ENABLE))
-			return -ENOENT;
-		synqf &= ~(IXGBE_SYN_FILTER_QUEUE | IXGBE_SYN_FILTER_ENABLE);
-	}
-
-	filter_info->syn_info = synqf;
 	IXGBE_WRITE_REG(hw, IXGBE_SYNQF, synqf);
 	IXGBE_WRITE_FLUSH(hw);
-	return 0;
 }
 
 
@@ -8401,23 +8369,6 @@ ixgbe_ntuple_filter_restore(struct rte_eth_dev *dev)
 	}
 }
 
-/* restore SYN filter */
-static inline void
-ixgbe_syn_filter_restore(struct rte_eth_dev *dev)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-	uint32_t synqf;
-
-	synqf = filter_info->syn_info;
-
-	if (synqf & IXGBE_SYN_FILTER_ENABLE) {
-		IXGBE_WRITE_REG(hw, IXGBE_SYNQF, synqf);
-		IXGBE_WRITE_FLUSH(hw);
-	}
-}
-
 /* restore L2 tunnel filter */
 static inline void
 ixgbe_l2_tn_filter_restore(struct rte_eth_dev *dev)
@@ -8456,7 +8407,6 @@ static int
 ixgbe_filter_restore(struct rte_eth_dev *dev)
 {
 	ixgbe_ntuple_filter_restore(dev);
-	ixgbe_syn_filter_restore(dev);
 	ixgbe_fdir_filter_restore(dev);
 	ixgbe_l2_tn_filter_restore(dev);
 	ixgbe_rss_filter_restore(dev);
@@ -8494,22 +8444,6 @@ ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev)
 		ixgbe_remove_5tuple_filter(adapter, p_5tuple);
 }
 
-/* remove the SYN filter */
-void
-ixgbe_clear_syn_filter(struct rte_eth_dev *dev)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-
-	if (filter_info->syn_info & IXGBE_SYN_FILTER_ENABLE) {
-		filter_info->syn_info = 0;
-
-		IXGBE_WRITE_REG(hw, IXGBE_SYNQF, 0);
-		IXGBE_WRITE_FLUSH(hw);
-	}
-}
-
 /* remove all the L2 tunnel filters */
 int
 ixgbe_clear_all_l2_tn_filter(struct rte_eth_dev *dev)
drivers/net/intel/ixgbe/ixgbe_ethdev.h +1 -6
index 61bbe610f33..ec216ec8c1f 100644
@@ -315,8 +315,6 @@ struct ixgbe_filter_info {
 	/* Bit mask for every used 5tuple filter */
 	uint32_t fivetuple_mask[IXGBE_5TUPLE_ARRAY_SIZE];
 	struct ixgbe_5tuple_filter_list fivetuple_list;
-	/* store the SYN filter info */
-	uint32_t syn_info;
 	/* store the rss filter info */
 	struct ixgbe_rte_flow_rss_conf rss_info;
 	/* shared EtherType (ETQF) slot table */
@@ -684,15 +682,13 @@ bool ixgbe_rss_update_sp(enum ixgbe_mac_type mac_type);
 int ixgbe_add_del_ntuple_filter(struct ixgbe_adapter *adapter,
 			struct rte_eth_ntuple_filter *filter,
 			bool add);
-int ixgbe_syn_filter_set(struct ixgbe_adapter *adapter,
-			struct rte_eth_syn_filter *filter,
-			bool add);
 
 void ixgbe_ethertype_filter_program(struct ixgbe_hw *hw, uint8_t idx,
 			uint32_t etqf, uint32_t etqs);
 int ixgbe_ethertype_table_add(struct ixgbe_ethertype_table *table,
 			uint16_t ethertype, uint32_t etqf, uint32_t etqs);
 int ixgbe_ethertype_table_del(struct ixgbe_ethertype_table *table, uint8_t idx);
+void ixgbe_syn_filter_program(struct ixgbe_hw *hw, uint32_t synqf);
 
 /**
  * l2 tunnel configuration.
@@ -765,7 +761,6 @@ int ixgbe_clear_all_fdir_filter(struct rte_eth_dev *dev);
 extern const struct rte_flow_ops ixgbe_flow_ops;
 
 void ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev);
-void ixgbe_clear_syn_filter(struct rte_eth_dev *dev);
 int ixgbe_clear_all_l2_tn_filter(struct rte_eth_dev *dev);
 
 int ixgbe_disable_sec_tx_path_generic(struct ixgbe_hw *hw);
drivers/net/intel/ixgbe/ixgbe_flow.c +2 -245

316 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +1 -0
index d7694283a54..453a23d3b63 100644
@@ -16,5 +16,6 @@ ixgbe_flow_actions_check(const struct ci_flow_actions *actions,
 extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
 
 extern const struct ci_flow_engine ixgbe_ethertype_flow_engine;
+extern const struct ci_flow_engine ixgbe_syn_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +312 -0

312 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index f2857feab76..1ef818fa67a 100644
@@ -27,6 +27,7 @@ sources += files(
         'ixgbe_fdir.c',
         'ixgbe_flow.c',
         'ixgbe_flow_ethertype.c',
+        'ixgbe_flow_syn.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
#73 lore raw

Re: [PATCH v3 06/19] net/ixgbe: reimplement syn parser

Quoted 16 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for SYN.

As a result of this migration, queue index validation has changed:

- queue is now validated at parse time against the configured number of Rx queues (nb_rx_queues), rather than at install time against the hardware maximum (IXGBE_MAX_RX_QUEUE_NUM)
- the per-function queue bound check in ixgbe_syn_filter_set() has been removed as it is no longer needed

The syn filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---

<snip>

Quoted 16 lines
drivers/net/intel/ixgbe/ixgbe_flow.c +0 -16
-
-	/* Support 2 priorities, the lowest or highest. */
-	if (!attr->priority) {
-		filter->hig_pri = 0;
-	} else if (attr->priority == (uint32_t)~0U) {
-		filter->hig_pri = 1;
-	} else {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
-			attr, "Priority can be 0 or 0xFFFFFFFF");
-		return -rte_errno;
-	}
-
-	return 0;
-}

<snip>

Quoted 9 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +9 -0
+
+	/* check priority */
+	if (attr->priority != 0 && attr->priority != (uint32_t)~0U) {
+		return rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+			attr, "Priority can be 0 or 0xFFFFFFFF");
+	}
+
+	/* parse requested actions */

drivers/net/intel/ixgbe/ixgbe_flow_syn.c line 201

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +10 -0
+	ret = ci_flow_check_actions(actions, &ap_param, &parsed_actions, error);
+	if (ret)
+		return ret;
+
+	q_act = parsed_actions.actions[0]->conf;
+
+	syn_ctx->syn.queue = q_act->index;
+
+	/* Support 2 priorities. rte_flow priority 0 is highest */
+	syn_ctx->syn.hig_pri = attr->priority == 0;

seems like inverting priority with the previous implementation

Quoted 3 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +3 -0
+
+	return 0;
+}

<snip>

Signature · 2 lines
Regards,
Vladimir
#74 lore raw

Re: [PATCH v3 06/19] net/ixgbe: reimplement syn parser

Quoted 50 lines · Vladimir
Quoted 17 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for SYN.

As a result of this migration, queue index validation has changed:

- queue is now validated at parse time against the configured number of Rx
   queues (nb_rx_queues), rather than at install time against the hardware maximum (IXGBE_MAX_RX_QUEUE_NUM)
- the per-function queue bound check in ixgbe_syn_filter_set() has been removed as it is no longer needed

The syn filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---

<snip>

Quoted 16 lines
drivers/net/intel/ixgbe/ixgbe_flow.c +0 -16
-
-    /* Support 2 priorities, the lowest or highest. */
-    if (!attr->priority) {
-        filter->hig_pri = 0;
-    } else if (attr->priority == (uint32_t)~0U) {
-        filter->hig_pri = 1;
-    } else {
-        memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-        rte_flow_error_set(error, EINVAL,
-            RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
-            attr, "Priority can be 0 or 0xFFFFFFFF");
-        return -rte_errno;
-    }
-
-    return 0;
-}

<snip>

Quoted 13 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +10 -0
+
+    /* check priority */
+    if (attr->priority != 0 && attr->priority != (uint32_t)~0U) {
+        return rte_flow_error_set(error, EINVAL,
+            RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+            attr, "Priority can be 0 or 0xFFFFFFFF");
+    }
+
+    /* parse requested actions */
+    ret = ci_flow_check_actions(actions, &ap_param, &parsed_actions, 

error);
+    if (ret)
+        return ret;

Quoted 8 lines

drivers/net/intel/ixgbe/ixgbe_flow_syn.c line 201

Quoted 7 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +7 -0
+
+    q_act = parsed_actions.actions[0]->conf;
+
+    syn_ctx->syn.queue = q_act->index;
+
+    /* Support 2 priorities. rte_flow priority 0 is highest */
+    syn_ctx->syn.hig_pri = attr->priority == 0;

seems like inverting priority with the previous implementation

Yes. I am not sure what to do about this, because the previous priority is technically wrong, and my current code is correct as far as rte_flow API semantics goes. Should this perhaps be a separate fix before the refactor? To be perfectly blunt, I don't think many people use syn flows so perhaps breaking backwards compatibility won't be a big issue.

Quoted 5 lines
Quoted 3 lines
drivers/net/intel/ixgbe/ixgbe_flow_syn.c new file +3 -0
+
+    return 0;
+}

<snip>

Signature · 2 lines
Thanks,
Anatoly
#75 lore raw

PATCH v3 7/19 net/ixgbe: reimplement L2 tunnel parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for L2 tunnel.

There are two L2-tag-related features in the driver: the global per-port one, and the one that can direct traffic to specific queues (the one the engine targets). The former is left completely untouched, while the latter one is migrated to use engine infrastructure and use internal tracking.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c     | 248 +---------------
 drivers/net/intel/ixgbe/ixgbe_ethdev.h     |  26 +-
 drivers/net/intel/ixgbe/ixgbe_flow.c       | 206 +------------
 drivers/net/intel/ixgbe/ixgbe_flow.h       |   1 +
 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c | 317 +++++++++++++++++++++
 drivers/net/intel/ixgbe/meson.build        |   1 +
 6 files changed, 326 insertions(+), 473 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +2 -246
index 00ba6f32fa9..3923a00c284 100644
@@ -149,7 +149,6 @@ static int eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev);
 static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev);
-static int ixgbe_l2_tn_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_ntuple_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_dev_configure(struct rte_eth_dev *dev);
 static int ixgbe_dev_start(struct rte_eth_dev *dev);
@@ -1392,7 +1391,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	return 0;
 
 err_flow_engine_conf_init:
-	ixgbe_l2_tn_filter_uninit(eth_dev);
 err_l2_tn_filter_init:
 	ixgbe_fdir_filter_uninit(eth_dev);
 err_fdir_filter_init:
@@ -1462,25 +1460,6 @@ static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev)
 	return 0;
 }
 
-static int ixgbe_l2_tn_filter_uninit(struct rte_eth_dev *eth_dev)
-{
-	struct ixgbe_l2_tn_info *l2_tn_info =
-		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(eth_dev->data->dev_private);
-	struct ixgbe_l2_tn_filter *l2_tn_filter;
-
-	rte_free(l2_tn_info->hash_map);
-	rte_hash_free(l2_tn_info->hash_handle);
-
-	while ((l2_tn_filter = TAILQ_FIRST(&l2_tn_info->l2_tn_list))) {
-		TAILQ_REMOVE(&l2_tn_info->l2_tn_list,
-			     l2_tn_filter,
-			     entries);
-		rte_free(l2_tn_filter);
-	}
-
-	return 0;
-}
-
 static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev)
 {
 	struct ixgbe_hw_fdir_info *fdir_info =
@@ -1526,34 +1505,7 @@ static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev)
 {
 	struct ixgbe_l2_tn_info *l2_tn_info =
 		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(eth_dev->data->dev_private);
-	char l2_tn_hash_name[RTE_HASH_NAMESIZE];
-	struct rte_hash_parameters l2_tn_hash_params = {
-		.name = l2_tn_hash_name,
-		.entries = IXGBE_MAX_L2_TN_FILTER_NUM,
-		.key_len = sizeof(struct ixgbe_l2_tn_key),
-		.hash_func = rte_hash_crc,
-		.hash_func_init_val = 0,
-		.socket_id = rte_socket_id(),
-	};
 
-	TAILQ_INIT(&l2_tn_info->l2_tn_list);
-	snprintf(l2_tn_hash_name, RTE_HASH_NAMESIZE,
-		 "l2_tn_%s", eth_dev->device->name);
-	l2_tn_info->hash_handle = rte_hash_create(&l2_tn_hash_params);
-	if (!l2_tn_info->hash_handle) {
-		PMD_INIT_LOG(ERR, "Failed to create L2 TN hash table!");
-		return -EINVAL;
-	}
-	l2_tn_info->hash_map = rte_zmalloc("ixgbe",
-				   sizeof(struct ixgbe_l2_tn_filter *) *
-				   IXGBE_MAX_L2_TN_FILTER_NUM,
-				   0);
-	if (!l2_tn_info->hash_map) {
-		PMD_INIT_LOG(ERR,
-			"Failed to allocate memory for L2 TN hash map!");
-		rte_hash_free(l2_tn_info->hash_handle);
-		return -ENOMEM;
-	}
 	l2_tn_info->e_tag_en = FALSE;
 	l2_tn_info->e_tag_fwd_en = FALSE;
 	l2_tn_info->e_tag_ether_type = RTE_ETHER_TYPE_ETAG;
@@ -3197,9 +3149,6 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	/* remove all the fdir filters & hash */
 	ixgbe_fdir_filter_uninit(dev);
 
-	/* remove all the L2 tunnel filters & hash */
-	ixgbe_l2_tn_filter_uninit(dev);
-
 	/* Remove all ntuple filters of the device */
 	ixgbe_ntuple_filter_uninit(dev);
 
@@ -7745,7 +7694,7 @@ ixgbe_e_tag_enable(struct ixgbe_hw *hw)
 	return 0;
 }
 
-static int
+int
 ixgbe_e_tag_filter_del(struct ixgbe_adapter *adapter,
 		       struct ixgbe_l2_tunnel_conf *l2_tunnel)
 {
@@ -7782,7 +7731,7 @@ ixgbe_e_tag_filter_del(struct ixgbe_adapter *adapter,
 	return ret;
 }
 
-static int
+int
 ixgbe_e_tag_filter_add(struct ixgbe_adapter *adapter,
 		       struct ixgbe_l2_tunnel_conf *l2_tunnel)
 {
@@ -7824,154 +7773,6 @@ ixgbe_e_tag_filter_add(struct ixgbe_adapter *adapter,
 	return -EINVAL;
 }
 
-static inline struct ixgbe_l2_tn_filter *
-ixgbe_l2_tn_filter_lookup(struct ixgbe_l2_tn_info *l2_tn_info,
-			  struct ixgbe_l2_tn_key *key)
-{
-	int ret;
-
-	ret = rte_hash_lookup(l2_tn_info->hash_handle, (const void *)key);
-	if (ret < 0)
-		return NULL;
-
-	return l2_tn_info->hash_map[ret];
-}
-
-static inline int
-ixgbe_insert_l2_tn_filter(struct ixgbe_l2_tn_info *l2_tn_info,
-			  struct ixgbe_l2_tn_filter *l2_tn_filter)
-{
-	int ret;
-
-	ret = rte_hash_add_key(l2_tn_info->hash_handle,
-			       &l2_tn_filter->key);
-
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "Failed to insert L2 tunnel filter"
-			    " to hash table %d!",
-			    ret);
-		return ret;
-	}
-
-	l2_tn_info->hash_map[ret] = l2_tn_filter;
-
-	TAILQ_INSERT_TAIL(&l2_tn_info->l2_tn_list, l2_tn_filter, entries);
-
-	return 0;
-}
-
-static inline int
-ixgbe_remove_l2_tn_filter(struct ixgbe_l2_tn_info *l2_tn_info,
-			  struct ixgbe_l2_tn_key *key)
-{
-	int ret;
-	struct ixgbe_l2_tn_filter *l2_tn_filter;
-
-	ret = rte_hash_del_key(l2_tn_info->hash_handle, key);
-
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "No such L2 tunnel filter to delete %d!",
-			    ret);
-		return ret;
-	}
-
-	l2_tn_filter = l2_tn_info->hash_map[ret];
-	l2_tn_info->hash_map[ret] = NULL;
-
-	TAILQ_REMOVE(&l2_tn_info->l2_tn_list, l2_tn_filter, entries);
-	rte_free(l2_tn_filter);
-
-	return 0;
-}
-
-/* Add l2 tunnel filter */
-int
-ixgbe_dev_l2_tunnel_filter_add(struct ixgbe_adapter *adapter,
-			       struct ixgbe_l2_tunnel_conf *l2_tunnel,
-			       bool restore)
-{
-	int ret;
-	struct ixgbe_l2_tn_info *l2_tn_info =
-		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(adapter);
-	struct ixgbe_l2_tn_key key;
-	struct ixgbe_l2_tn_filter *node;
-
-	if (!restore) {
-		key.l2_tn_type = l2_tunnel->l2_tunnel_type;
-		key.tn_id = l2_tunnel->tunnel_id;
-
-		node = ixgbe_l2_tn_filter_lookup(l2_tn_info, &key);
-
-		if (node) {
-			PMD_DRV_LOG(ERR,
-				    "The L2 tunnel filter already exists!");
-			return -EINVAL;
-		}
-
-		node = rte_zmalloc("ixgbe_l2_tn",
-				   sizeof(struct ixgbe_l2_tn_filter),
-				   0);
-		if (!node)
-			return -ENOMEM;
-
-		memcpy(&node->key,
-				 &key,
-				 sizeof(struct ixgbe_l2_tn_key));
-		node->pool = l2_tunnel->pool;
-		ret = ixgbe_insert_l2_tn_filter(l2_tn_info, node);
-		if (ret < 0) {
-			rte_free(node);
-			return ret;
-		}
-	}
-
-	switch (l2_tunnel->l2_tunnel_type) {
-	case RTE_ETH_L2_TUNNEL_TYPE_E_TAG:
-		ret = ixgbe_e_tag_filter_add(adapter, l2_tunnel);
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "Invalid tunnel type");
-		ret = -EINVAL;
-		break;
-	}
-
-	if ((!restore) && (ret < 0))
-		(void)ixgbe_remove_l2_tn_filter(l2_tn_info, &key);
-
-	return ret;
-}
-
-/* Delete l2 tunnel filter */
-int
-ixgbe_dev_l2_tunnel_filter_del(struct ixgbe_adapter *adapter,
-			       struct ixgbe_l2_tunnel_conf *l2_tunnel)
-{
-	int ret;
-	struct ixgbe_l2_tn_info *l2_tn_info =
-		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(adapter);
-	struct ixgbe_l2_tn_key key;
-
-	key.l2_tn_type = l2_tunnel->l2_tunnel_type;
-	key.tn_id = l2_tunnel->tunnel_id;
-	ret = ixgbe_remove_l2_tn_filter(l2_tn_info, &key);
-	if (ret < 0)
-		return ret;
-
-	switch (l2_tunnel->l2_tunnel_type) {
-	case RTE_ETH_L2_TUNNEL_TYPE_E_TAG:
-		ret = ixgbe_e_tag_filter_del(adapter, l2_tunnel);
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "Invalid tunnel type");
-		ret = -EINVAL;
-		break;
-	}
-
-	return ret;
-}
-
 static int
 ixgbe_e_tag_forwarding_en_dis(struct rte_eth_dev *dev, bool en)
 {
@@ -8369,26 +8170,6 @@ ixgbe_ntuple_filter_restore(struct rte_eth_dev *dev)
 	}
 }
 
-/* restore L2 tunnel filter */
-static inline void
-ixgbe_l2_tn_filter_restore(struct rte_eth_dev *dev)
-{
-	struct ixgbe_adapter *adapter =
-		IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct ixgbe_l2_tn_info *l2_tn_info =
-		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(adapter);
-	struct ixgbe_l2_tn_filter *node;
-	struct ixgbe_l2_tunnel_conf l2_tn_conf;
-
-	TAILQ_FOREACH(node, &l2_tn_info->l2_tn_list, entries) {
-		l2_tn_conf.l2_tunnel_type = node->key.l2_tn_type;
-		l2_tn_conf.tunnel_id      = node->key.tn_id;
-		l2_tn_conf.pool           = node->pool;
-		(void)ixgbe_dev_l2_tunnel_filter_add(adapter,
-						     &l2_tn_conf, TRUE);
-	}
-}
-
 /* restore rss filter */
 static inline void
 ixgbe_rss_filter_restore(struct rte_eth_dev *dev)
@@ -8408,7 +8189,6 @@ ixgbe_filter_restore(struct rte_eth_dev *dev)
 {
 	ixgbe_ntuple_filter_restore(dev);
 	ixgbe_fdir_filter_restore(dev);
-	ixgbe_l2_tn_filter_restore(dev);
 	ixgbe_rss_filter_restore(dev);
 
 	return 0;
@@ -8444,30 +8224,6 @@ ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev)
 		ixgbe_remove_5tuple_filter(adapter, p_5tuple);
 }
 
-/* remove all the L2 tunnel filters */
-int
-ixgbe_clear_all_l2_tn_filter(struct rte_eth_dev *dev)
-{
-	struct ixgbe_adapter *adapter =
-			IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct ixgbe_l2_tn_info *l2_tn_info =
-		IXGBE_DEV_PRIVATE_TO_L2_TN_INFO(dev->data->dev_private);
-	struct ixgbe_l2_tn_filter *l2_tn_filter;
-	struct ixgbe_l2_tunnel_conf l2_tn_conf;
-	int ret = 0;
-
-	while ((l2_tn_filter = TAILQ_FIRST(&l2_tn_info->l2_tn_list))) {
-		l2_tn_conf.l2_tunnel_type = l2_tn_filter->key.l2_tn_type;
-		l2_tn_conf.tunnel_id      = l2_tn_filter->key.tn_id;
-		l2_tn_conf.pool           = l2_tn_filter->pool;
-		ret = ixgbe_dev_l2_tunnel_filter_del(adapter, &l2_tn_conf);
-		if (ret < 0)
-			return ret;
-	}
-
-	return 0;
-}
-
 void
 ixgbe_dev_macsec_setting_save(struct rte_eth_dev *dev,
 				struct ixgbe_macsec_setting *macsec_setting)
drivers/net/intel/ixgbe/ixgbe_ethdev.h +4 -22
index ec216ec8c1f..b252fd39731 100644
@@ -327,23 +327,7 @@ struct ixgbe_filter_info {
 	uint8_t antispoof_idx;
 };
 
-struct ixgbe_l2_tn_key {
-	enum rte_eth_tunnel_type          l2_tn_type;
-	uint32_t                          tn_id;
-};
-
-struct ixgbe_l2_tn_filter {
-	TAILQ_ENTRY(ixgbe_l2_tn_filter)    entries;
-	struct ixgbe_l2_tn_key             key;
-	uint32_t                           pool;
-};
-
-TAILQ_HEAD(ixgbe_l2_tn_filter_list, ixgbe_l2_tn_filter);
-
 struct ixgbe_l2_tn_info {
-	struct ixgbe_l2_tn_filter_list      l2_tn_list;
-	struct ixgbe_l2_tn_filter         **hash_map;
-	struct rte_hash                    *hash_handle;
 	bool e_tag_en; /* e-tag enabled */
 	bool e_tag_fwd_en; /* e-tag based forwarding enabled */
 	uint16_t e_tag_ether_type; /* ether type for e-tag */
@@ -702,12 +686,11 @@ struct ixgbe_l2_tunnel_conf {
 };
 
 int
-ixgbe_dev_l2_tunnel_filter_add(struct ixgbe_adapter *adapter,
-			       struct ixgbe_l2_tunnel_conf *l2_tunnel,
-			       bool restore);
+ixgbe_e_tag_filter_add(struct ixgbe_adapter *adapter,
+		       struct ixgbe_l2_tunnel_conf *l2_tunnel);
 int
-ixgbe_dev_l2_tunnel_filter_del(struct ixgbe_adapter *adapter,
-			       struct ixgbe_l2_tunnel_conf *l2_tunnel);
+ixgbe_e_tag_filter_del(struct ixgbe_adapter *adapter,
+		       struct ixgbe_l2_tunnel_conf *l2_tunnel);
 void ixgbe_filterlist_init(struct rte_eth_dev *dev);
 void ixgbe_filterlist_flush(struct rte_eth_dev *dev);
 /*
@@ -761,7 +744,6 @@ int ixgbe_clear_all_fdir_filter(struct rte_eth_dev *dev);
 extern const struct rte_flow_ops ixgbe_flow_ops;
 
 void ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev);
-int ixgbe_clear_all_l2_tn_filter(struct rte_eth_dev *dev);
 
 int ixgbe_disable_sec_tx_path_generic(struct ixgbe_hw *hw);
 
drivers/net/intel/ixgbe/ixgbe_flow.c +1 -205

272 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +1 -0
index 453a23d3b63..ba0486b2c0e 100644
@@ -17,5 +17,6 @@ extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
 
 extern const struct ci_flow_engine ixgbe_ethertype_flow_engine;
 extern const struct ci_flow_engine ixgbe_syn_flow_engine;
+extern const struct ci_flow_engine ixgbe_l2_tunnel_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c new file +317 -0

317 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 1ef818fa67a..91ec2611543 100644
@@ -28,6 +28,7 @@ sources += files(
         'ixgbe_flow.c',
         'ixgbe_flow_ethertype.c',
         'ixgbe_flow_syn.c',
+        'ixgbe_flow_l2tun.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
Acked-by on v3 7/19
#76 lore raw

Re: [PATCH v3 07/19] net/ixgbe: reimplement L2 tunnel parser

one nit below, apart from it,

Quoted 32 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for L2 tunnel.

There are two L2-tag-related features in the driver: the global per-port one, and the one that can direct traffic to specific queues (the one the engine targets). The former is left completely untouched, while the latter one is migrated to use engine infrastructure and use internal tracking.

---
  drivers/net/intel/ixgbe/ixgbe_ethdev.c     | 248 +---------------
  drivers/net/intel/ixgbe/ixgbe_ethdev.h     |  26 +-
  drivers/net/intel/ixgbe/ixgbe_flow.c       | 206 +------------
  drivers/net/intel/ixgbe/ixgbe_flow.h       |   1 +
  drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c | 317 +++++++++++++++++++++
  drivers/net/intel/ixgbe/meson.build        |   1 +
  6 files changed, 326 insertions(+), 473 deletions(-)

create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c

drivers/net/intel/ixgbe/ixgbe_ethdev.c +0 -1
index 00ba6f32fa9..3923a00c284 100644
@@ -149,7 +149,6 @@ static int eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev);
  static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev);
  static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev);
  static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev);
-static int ixgbe_l2_tn_filter_uninit(struct rte_eth_dev *eth_dev);
  static int ixgbe_ntuple_filter_uninit(struct rte_eth_dev *eth_dev);
  static int ixgbe_dev_configure(struct rte_eth_dev *dev);
  static int ixgbe_dev_start(struct rte_eth_dev *dev);

drivers/net/intel/ixgbe/ixgbe_ethdev.c eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused) · line 1395

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +0 -1
@@ -1392,7 +1391,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
  	return 0;
 
  err_flow_engine_conf_init:
-	ixgbe_l2_tn_filter_uninit(eth_dev);

remove "err_flow_engine_conf_init:" label too?

Quoted 7 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +0 -0
  err_l2_tn_filter_init:
  	ixgbe_fdir_filter_uninit(eth_dev);
  err_fdir_filter_init:
@@ -1462,25 +1460,6 @@ static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev)
  	return 0;
  }
 

<snip>

Signature · 2 lines
Regards,
Vladimir
#77 lore raw

PATCH v3 8/19 net/ixgbe: reimplement ntuple parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ntuple.

The 5tuple filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c      | 348 +-----------
 drivers/net/intel/ixgbe/ixgbe_ethdev.h      |  28 +-
 drivers/net/intel/ixgbe/ixgbe_flow.c        | 463 +--------------
 drivers/net/intel/ixgbe/ixgbe_flow.h        |   1 +
 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c | 597 ++++++++++++++++++++
 drivers/net/intel/ixgbe/meson.build         |   1 +
 6 files changed, 627 insertions(+), 811 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +23 -325
index 3923a00c284..c8aa050f84a 100644
@@ -149,7 +149,6 @@ static int eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev);
 static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev);
-static int ixgbe_ntuple_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_dev_configure(struct rte_eth_dev *dev);
 static int ixgbe_dev_start(struct rte_eth_dev *dev);
 static int ixgbe_dev_stop(struct rte_eth_dev *dev);
@@ -304,10 +303,6 @@ static int ixgbevf_add_mac_addr(struct rte_eth_dev *dev,
 static void ixgbevf_remove_mac_addr(struct rte_eth_dev *dev, uint32_t index);
 static int ixgbevf_set_default_mac_addr(struct rte_eth_dev *dev,
 					     struct rte_ether_addr *mac_addr);
-static int ixgbe_add_5tuple_filter(struct ixgbe_adapter *adapter,
-			struct ixgbe_5tuple_filter *filter);
-static void ixgbe_remove_5tuple_filter(struct ixgbe_adapter *adapter,
-			struct ixgbe_5tuple_filter *filter);
 static int ixgbe_dev_flow_ops_get(struct rte_eth_dev *dev,
 				  const struct rte_flow_ops **ops);
 static int ixgbevf_dev_set_mtu(struct rte_eth_dev *dev, uint16_t mtu);
@@ -1360,9 +1355,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	memset(filter_info, 0,
 	       sizeof(struct ixgbe_filter_info));
 
-	/* initialize 5tuple filter list */
-	TAILQ_INIT(&filter_info->fivetuple_list);
-
 	/* initialize flow director filter list & hash */
 	ret = ixgbe_fdir_filter_init(eth_dev);
 	if (ret)
@@ -1423,24 +1415,6 @@ eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev)
 	return 0;
 }
 
-static int ixgbe_ntuple_filter_uninit(struct rte_eth_dev *eth_dev)
-{
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(eth_dev->data->dev_private);
-	struct ixgbe_5tuple_filter *p_5tuple;
-
-	while ((p_5tuple = TAILQ_FIRST(&filter_info->fivetuple_list))) {
-		TAILQ_REMOVE(&filter_info->fivetuple_list,
-			     p_5tuple,
-			     entries);
-		rte_free(p_5tuple);
-	}
-	memset(filter_info->fivetuple_mask, 0,
-	       sizeof(uint32_t) * IXGBE_5TUPLE_ARRAY_SIZE);
-
-	return 0;
-}
-
 static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev)
 {
 	struct ixgbe_hw_fdir_info *fdir_info =
@@ -3149,9 +3123,6 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	/* remove all the fdir filters & hash */
 	ixgbe_fdir_filter_uninit(dev);
 
-	/* Remove all ntuple filters of the device */
-	ixgbe_ntuple_filter_uninit(dev);
-
 	/* clear all the filters list */
 	ixgbe_filterlist_flush(dev);
 
@@ -6473,134 +6444,50 @@ ixgbe_syn_filter_program(struct ixgbe_hw *hw, uint32_t synqf)
 	IXGBE_WRITE_FLUSH(hw);
 }
 
-
-static inline enum ixgbe_5tuple_protocol
-convert_protocol_type(uint8_t protocol_value)
+/* program a single ntuple (5-tuple) filter slot into hardware */
+void
+ixgbe_ntuple_filter_program(struct ixgbe_hw *hw, uint16_t index,
+			const struct ixgbe_5tuple_filter_info *filter_info,
+			uint16_t queue)
 {
-	if (protocol_value == IPPROTO_TCP)
-		return IXGBE_FILTER_PROTOCOL_TCP;
-	else if (protocol_value == IPPROTO_UDP)
-		return IXGBE_FILTER_PROTOCOL_UDP;
-	else if (protocol_value == IPPROTO_SCTP)
-		return IXGBE_FILTER_PROTOCOL_SCTP;
-	else
-		return IXGBE_FILTER_PROTOCOL_NONE;
-}
-
-/* inject a 5-tuple filter to HW */
-static inline void
-ixgbe_inject_5tuple_filter(struct ixgbe_adapter *adapter,
-			   struct ixgbe_5tuple_filter *filter)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(adapter);
-	int i;
 	uint32_t ftqf, sdpqf;
 	uint32_t l34timir = 0;
 	uint8_t mask = 0xff;
 
-	i = filter->index;
+	sdpqf = (uint32_t)(filter_info->dst_port << IXGBE_SDPQF_DSTPORT_SHIFT);
+	sdpqf = sdpqf | (filter_info->src_port & IXGBE_SDPQF_SRCPORT);
 
-	sdpqf = (uint32_t)(filter->filter_info.dst_port <<
-				IXGBE_SDPQF_DSTPORT_SHIFT);
-	sdpqf = sdpqf | (filter->filter_info.src_port & IXGBE_SDPQF_SRCPORT);
-
-	ftqf = (uint32_t)(filter->filter_info.proto &
-		IXGBE_FTQF_PROTOCOL_MASK);
-	ftqf |= (uint32_t)((filter->filter_info.priority &
+	ftqf = (uint32_t)(filter_info->proto & IXGBE_FTQF_PROTOCOL_MASK);
+	ftqf |= (uint32_t)((filter_info->priority &
 		IXGBE_FTQF_PRIORITY_MASK) << IXGBE_FTQF_PRIORITY_SHIFT);
-	if (filter->filter_info.src_ip_mask == 0) /* 0 means compare. */
+	if (filter_info->src_ip_mask == 0) /* 0 means compare. */
 		mask &= IXGBE_FTQF_SOURCE_ADDR_MASK;
-	if (filter->filter_info.dst_ip_mask == 0)
+	if (filter_info->dst_ip_mask == 0)
 		mask &= IXGBE_FTQF_DEST_ADDR_MASK;
-	if (filter->filter_info.src_port_mask == 0)
+	if (filter_info->src_port_mask == 0)
 		mask &= IXGBE_FTQF_SOURCE_PORT_MASK;
-	if (filter->filter_info.dst_port_mask == 0)
+	if (filter_info->dst_port_mask == 0)
 		mask &= IXGBE_FTQF_DEST_PORT_MASK;
-	if (filter->filter_info.proto_mask == 0)
+	if (filter_info->proto_mask == 0)
 		mask &= IXGBE_FTQF_PROTOCOL_COMP_MASK;
 	ftqf |= mask << IXGBE_FTQF_5TUPLE_MASK_SHIFT;
 	ftqf |= IXGBE_FTQF_POOL_MASK_EN;
 	ftqf |= IXGBE_FTQF_QUEUE_ENABLE;
 
-	IXGBE_WRITE_REG(hw, IXGBE_DAQF(i), filter->filter_info.dst_ip);
-	IXGBE_WRITE_REG(hw, IXGBE_SAQF(i), filter->filter_info.src_ip);
-	IXGBE_WRITE_REG(hw, IXGBE_SDPQF(i), sdpqf);
-	IXGBE_WRITE_REG(hw, IXGBE_FTQF(i), ftqf);
+	IXGBE_WRITE_REG(hw, IXGBE_DAQF(index), filter_info->dst_ip);
+	IXGBE_WRITE_REG(hw, IXGBE_SAQF(index), filter_info->src_ip);
+	IXGBE_WRITE_REG(hw, IXGBE_SDPQF(index), sdpqf);
+	IXGBE_WRITE_REG(hw, IXGBE_FTQF(index), ftqf);
 
 	l34timir |= IXGBE_L34T_IMIR_RESERVE;
-	l34timir |= (uint32_t)(filter->queue <<
-				IXGBE_L34T_IMIR_QUEUE_SHIFT);
-	IXGBE_WRITE_REG(hw, IXGBE_L34T_IMIR(i), l34timir);
+	l34timir |= (uint32_t)(queue << IXGBE_L34T_IMIR_QUEUE_SHIFT);
+	IXGBE_WRITE_REG(hw, IXGBE_L34T_IMIR(index), l34timir);
 }
 
-/*
- * add a 5tuple filter
- *
- * @param
- * dev: Pointer to struct rte_eth_dev.
- * index: the index the filter allocates.
- * filter: pointer to the filter that will be added.
- * rx_queue: the queue id the filter assigned to.
- *
- * @return
- *    - On success, zero.
- *    - On failure, a negative value.
- */
-static int
-ixgbe_add_5tuple_filter(struct ixgbe_adapter *adapter,
-			struct ixgbe_5tuple_filter *filter)
+/* clear a single ntuple (5-tuple) filter slot from hardware */
+void
+ixgbe_ntuple_filter_clear(struct ixgbe_hw *hw, uint16_t index)
 {
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	int i, idx, shift;
-
-	/*
-	 * look for an unused 5tuple filter index,
-	 * and insert the filter to list.
-	 */
-	for (i = 0; i < IXGBE_MAX_FTQF_FILTERS; i++) {
-		idx = i / (sizeof(uint32_t) * NBBY);
-		shift = i % (sizeof(uint32_t) * NBBY);
-		if (!(filter_info->fivetuple_mask[idx] & (1 << shift))) {
-			filter_info->fivetuple_mask[idx] |= 1 << shift;
-			filter->index = i;
-			TAILQ_INSERT_TAIL(&filter_info->fivetuple_list,
-					  filter,
-					  entries);
-			break;
-		}
-	}
-	if (i >= IXGBE_MAX_FTQF_FILTERS) {
-		PMD_DRV_LOG(ERR, "5tuple filters are full.");
-		return -ENOSYS;
-	}
-
-	ixgbe_inject_5tuple_filter(adapter, filter);
-
-	return 0;
-}
-
-/*
- * remove a 5tuple filter
- *
- * @param
- * dev: Pointer to struct rte_eth_dev.
- * filter: the pointer of the filter will be removed.
- */
-static void
-ixgbe_remove_5tuple_filter(struct ixgbe_adapter *adapter,
-			struct ixgbe_5tuple_filter *filter)
-{
-	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(adapter);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	uint16_t index = filter->index;
-
-	filter_info->fivetuple_mask[index / (sizeof(uint32_t) * NBBY)] &=
-				~(1 << (index % (sizeof(uint32_t) * NBBY)));
-	TAILQ_REMOVE(&filter_info->fivetuple_list, filter, entries);
-	rte_free(filter);
-
 	IXGBE_WRITE_REG(hw, IXGBE_DAQF(index), 0);
 	IXGBE_WRITE_REG(hw, IXGBE_SAQF(index), 0);
 	IXGBE_WRITE_REG(hw, IXGBE_SDPQF(index), 0);
@@ -6653,165 +6540,6 @@ ixgbevf_dev_set_mtu(struct rte_eth_dev *dev, uint16_t mtu)
 	return 0;
 }
 
-static inline struct ixgbe_5tuple_filter *
-ixgbe_5tuple_filter_lookup(struct ixgbe_5tuple_filter_list *filter_list,
-			struct ixgbe_5tuple_filter_info *key)
-{
-	struct ixgbe_5tuple_filter *it;
-
-	TAILQ_FOREACH(it, filter_list, entries) {
-		if (memcmp(key, &it->filter_info,
-			sizeof(struct ixgbe_5tuple_filter_info)) == 0) {
-			return it;
-		}
-	}
-	return NULL;
-}
-
-/* translate elements in struct rte_eth_ntuple_filter to struct ixgbe_5tuple_filter_info*/
-static inline int
-ntuple_filter_to_5tuple(struct rte_eth_ntuple_filter *filter,
-			struct ixgbe_5tuple_filter_info *filter_info)
-{
-	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM ||
-		filter->priority > IXGBE_5TUPLE_MAX_PRI ||
-		filter->priority < IXGBE_5TUPLE_MIN_PRI)
-		return -EINVAL;
-
-	switch (filter->dst_ip_mask) {
-	case UINT32_MAX:
-		filter_info->dst_ip_mask = 0;
-		filter_info->dst_ip = filter->dst_ip;
-		break;
-	case 0:
-		filter_info->dst_ip_mask = 1;
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "invalid dst_ip mask.");
-		return -EINVAL;
-	}
-
-	switch (filter->src_ip_mask) {
-	case UINT32_MAX:
-		filter_info->src_ip_mask = 0;
-		filter_info->src_ip = filter->src_ip;
-		break;
-	case 0:
-		filter_info->src_ip_mask = 1;
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "invalid src_ip mask.");
-		return -EINVAL;
-	}
-
-	switch (filter->dst_port_mask) {
-	case UINT16_MAX:
-		filter_info->dst_port_mask = 0;
-		filter_info->dst_port = filter->dst_port;
-		break;
-	case 0:
-		filter_info->dst_port_mask = 1;
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "invalid dst_port mask.");
-		return -EINVAL;
-	}
-
-	switch (filter->src_port_mask) {
-	case UINT16_MAX:
-		filter_info->src_port_mask = 0;
-		filter_info->src_port = filter->src_port;
-		break;
-	case 0:
-		filter_info->src_port_mask = 1;
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "invalid src_port mask.");
-		return -EINVAL;
-	}
-
-	switch (filter->proto_mask) {
-	case UINT8_MAX:
-		filter_info->proto_mask = 0;
-		filter_info->proto =
-			convert_protocol_type(filter->proto);
-		break;
-	case 0:
-		filter_info->proto_mask = 1;
-		break;
-	default:
-		PMD_DRV_LOG(ERR, "invalid protocol mask.");
-		return -EINVAL;
-	}
-
-	filter_info->priority = (uint8_t)filter->priority;
-	return 0;
-}
-
-/*
- * add or delete a ntuple filter
- *
- * @param
- * dev: Pointer to struct rte_eth_dev.
- * ntuple_filter: Pointer to struct rte_eth_ntuple_filter
- * add: if true, add filter, if false, remove filter
- *
- * @return
- *    - On success, zero.
- *    - On failure, a negative value.
- */
-int
-ixgbe_add_del_ntuple_filter(struct ixgbe_adapter *adapter,
-			struct rte_eth_ntuple_filter *ntuple_filter,
-			bool add)
-{
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	struct ixgbe_5tuple_filter_info filter_5tuple;
-	struct ixgbe_5tuple_filter *filter;
-	int ret;
-
-	if (ntuple_filter->flags != RTE_5TUPLE_FLAGS) {
-		PMD_DRV_LOG(ERR, "only 5tuple is supported.");
-		return -EINVAL;
-	}
-
-	memset(&filter_5tuple, 0, sizeof(struct ixgbe_5tuple_filter_info));
-	ret = ntuple_filter_to_5tuple(ntuple_filter, &filter_5tuple);
-	if (ret < 0)
-		return ret;
-
-	filter = ixgbe_5tuple_filter_lookup(&filter_info->fivetuple_list,
-					 &filter_5tuple);
-	if (filter != NULL && add) {
-		PMD_DRV_LOG(ERR, "filter exists.");
-		return -EEXIST;
-	}
-	if (filter == NULL && !add) {
-		PMD_DRV_LOG(ERR, "filter doesn't exist.");
-		return -ENOENT;
-	}
-
-	if (add) {
-		filter = rte_zmalloc("ixgbe_5tuple_filter",
-				sizeof(struct ixgbe_5tuple_filter), 0);
-		if (filter == NULL)
-			return -ENOMEM;
-		memcpy(&filter->filter_info,
-				 &filter_5tuple,
-				 sizeof(struct ixgbe_5tuple_filter_info));
-		filter->queue = ntuple_filter->queue;
-		ret = ixgbe_add_5tuple_filter(adapter, filter);
-		if (ret < 0) {
-			rte_free(filter);
-			return ret;
-		}
-	} else
-		ixgbe_remove_5tuple_filter(adapter, filter);
-
-	return 0;
-}
-
 void
 ixgbe_ethertype_filter_program(struct ixgbe_hw *hw, uint8_t idx,
 			uint32_t etqf, uint32_t etqs)
@@ -8155,21 +7883,6 @@ int ixgbe_enable_sec_tx_path_generic(struct ixgbe_hw *hw)
 	return IXGBE_SUCCESS;
 }
 
-/* restore n-tuple filter */
-static inline void
-ixgbe_ntuple_filter_restore(struct rte_eth_dev *dev)
-{
-	struct ixgbe_adapter *adapter =
-		IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-	struct ixgbe_5tuple_filter *node;
-
-	TAILQ_FOREACH(node, &filter_info->fivetuple_list, entries) {
-		ixgbe_inject_5tuple_filter(adapter, node);
-	}
-}
-
 /* restore rss filter */
 static inline void
 ixgbe_rss_filter_restore(struct rte_eth_dev *dev)
@@ -8187,7 +7900,6 @@ ixgbe_rss_filter_restore(struct rte_eth_dev *dev)
 static int
 ixgbe_filter_restore(struct rte_eth_dev *dev)
 {
-	ixgbe_ntuple_filter_restore(dev);
 	ixgbe_fdir_filter_restore(dev);
 	ixgbe_rss_filter_restore(dev);
 
@@ -8210,20 +7922,6 @@ ixgbe_l2_tunnel_conf(struct rte_eth_dev *dev)
 	(void)ixgbe_update_e_tag_eth_type(hw, l2_tn_info->e_tag_ether_type);
 }
 
-/* remove all the n-tuple filters */
-void
-ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev)
-{
-	struct ixgbe_adapter *adapter =
-		IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
-	struct ixgbe_5tuple_filter *p_5tuple;
-
-	while ((p_5tuple = TAILQ_FIRST(&filter_info->fivetuple_list)))
-		ixgbe_remove_5tuple_filter(adapter, p_5tuple);
-}
-
 void
 ixgbe_dev_macsec_setting_save(struct rte_eth_dev *dev,
 				struct ixgbe_macsec_setting *macsec_setting)
drivers/net/intel/ixgbe/ixgbe_ethdev.h +4 -24
index b252fd39731..82c4b10b61d 100644
@@ -99,8 +99,6 @@
 #define IXGBE_L34T_IMIR_LLI             0x00100000
 #define IXGBE_L34T_IMIR_QUEUE           0x0FE00000
 #define IXGBE_L34T_IMIR_QUEUE_SHIFT     21
-#define IXGBE_5TUPLE_MAX_PRI            7
-#define IXGBE_5TUPLE_MIN_PRI            1
 
 /* The overhead from MTU to max frame size. */
 #define IXGBE_ETH_OVERHEAD (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN)
@@ -269,8 +267,6 @@ enum ixgbe_5tuple_protocol {
 	IXGBE_FILTER_PROTOCOL_NONE,
 };
 
-TAILQ_HEAD(ixgbe_5tuple_filter_list, ixgbe_5tuple_filter);
-
 struct ixgbe_5tuple_filter_info {
 	uint32_t dst_ip;
 	uint32_t src_ip;
@@ -286,18 +282,6 @@ struct ixgbe_5tuple_filter_info {
 		proto_mask:1;    /* if mask is 1b, do not compare protocol. */
 };
 
-/* 5tuple filter structure */
-struct ixgbe_5tuple_filter {
-	TAILQ_ENTRY(ixgbe_5tuple_filter) entries;
-	uint16_t index;       /* the index of 5tuple filter */
-	struct ixgbe_5tuple_filter_info filter_info;
-	uint16_t queue;       /* rx queue assigned to */
-};
-
-#define IXGBE_5TUPLE_ARRAY_SIZE \
-	(RTE_ALIGN(IXGBE_MAX_FTQF_FILTERS, (sizeof(uint32_t) * NBBY)) / \
-	 (sizeof(uint32_t) * NBBY))
-
 /* Shared EtherType (ETQF) filter table. */
 struct ixgbe_ethertype_table {
 	uint32_t mask; /* bitmask of used ETQF slots */
@@ -312,9 +296,6 @@ struct ixgbe_ethertype_table {
  * Structure to store filters' info.
  */
 struct ixgbe_filter_info {
-	/* Bit mask for every used 5tuple filter */
-	uint32_t fivetuple_mask[IXGBE_5TUPLE_ARRAY_SIZE];
-	struct ixgbe_5tuple_filter_list fivetuple_list;
 	/* store the rss filter info */
 	struct ixgbe_rte_flow_rss_conf rss_info;
 	/* shared EtherType (ETQF) slot table */
@@ -663,9 +644,10 @@ uint32_t ixgbe_rssrk_reg_get(enum ixgbe_mac_type mac_type, uint8_t i);
 
 bool ixgbe_rss_update_sp(enum ixgbe_mac_type mac_type);
 
-int ixgbe_add_del_ntuple_filter(struct ixgbe_adapter *adapter,
-			struct rte_eth_ntuple_filter *filter,
-			bool add);
+void ixgbe_ntuple_filter_program(struct ixgbe_hw *hw, uint16_t index,
+			const struct ixgbe_5tuple_filter_info *filter_info,
+			uint16_t queue);
+void ixgbe_ntuple_filter_clear(struct ixgbe_hw *hw, uint16_t index);
 
 void ixgbe_ethertype_filter_program(struct ixgbe_hw *hw, uint8_t idx,
 			uint32_t etqf, uint32_t etqs);
@@ -743,8 +725,6 @@ int ixgbe_clear_all_fdir_filter(struct rte_eth_dev *dev);
 
 extern const struct rte_flow_ops ixgbe_flow_ops;
 
-void ixgbe_clear_all_ntuple_filter(struct rte_eth_dev *dev);
-
 int ixgbe_disable_sec_tx_path_generic(struct ixgbe_hw *hw);
 
 int ixgbe_enable_sec_tx_path_generic(struct ixgbe_hw *hw);
drivers/net/intel/ixgbe/ixgbe_flow.c +1 -462

553 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +1 -0
index ba0486b2c0e..6f082e9402f 100644
@@ -18,5 +18,6 @@ extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
 extern const struct ci_flow_engine ixgbe_ethertype_flow_engine;
 extern const struct ci_flow_engine ixgbe_syn_flow_engine;
 extern const struct ci_flow_engine ixgbe_l2_tunnel_flow_engine;
+extern const struct ci_flow_engine ixgbe_ntuple_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +597 -0

597 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 91ec2611543..90cc88f0027 100644
@@ -29,6 +29,7 @@ sources += files(
         'ixgbe_flow_ethertype.c',
         'ixgbe_flow_syn.c',
         'ixgbe_flow_l2tun.c',
+        'ixgbe_flow_ntuple.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
#78 lore raw

Re: [PATCH v3 08/19] net/ixgbe: reimplement ntuple parser

Quoted 8 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ntuple.

The 5tuple filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---

<snip>

drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c line 263

Quoted 9 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +9 -0
+static const struct flow_graph ixgbe_ntuple_graph = {
+	.nodes = (struct flow_graph_node[]) {
+		[IXGBE_NTUPLE_NODE_START] = {
+			.name = "START",
+		},
+		[IXGBE_NTUPLE_NODE_ETH] = {
+			.name = "ETH",
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY,

legacy code accepts patterns with non null spec/mask containing all zeros. Same for VLAN.

drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c line 273

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +10 -0
+		},
+		[IXGBE_NTUPLE_NODE_VLAN] = {
+			.name = "VLAN",
+			.type = RTE_FLOW_ITEM_TYPE_VLAN,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY,
+		},
+		[IXGBE_NTUPLE_NODE_IPV4] = {
+			.name = "IPV4",
+			.type = RTE_FLOW_ITEM_TYPE_IPV4,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,

mask was optional

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +4 -0
+			.validate = ixgbe_validate_ntuple_ipv4,
+			.process = ixgbe_process_ntuple_ipv4,
+		},
+		[IXGBE_NTUPLE_NODE_TCP] = {

drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c line 287

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +10 -0
+			.name = "TCP",
+			.type = RTE_FLOW_ITEM_TYPE_TCP,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+			.validate = ixgbe_validate_ntuple_tcp,
+			.process = ixgbe_process_ntuple_tcp,
+		},
+		[IXGBE_NTUPLE_NODE_UDP] = {
+			.name = "UDP",
+			.type = RTE_FLOW_ITEM_TYPE_UDP,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,

spec and mask was optional

Quoted 15 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +15 -0
+			.validate = ixgbe_validate_ntuple_udp,
+			.process = ixgbe_process_ntuple_udp,
+		},
+		[IXGBE_NTUPLE_NODE_SCTP] = {
+			.name = "SCTP",
+			.type = RTE_FLOW_ITEM_TYPE_SCTP,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_SPEC_MASK,
+			.validate = ixgbe_validate_ntuple_sctp,
+			.process = ixgbe_process_ntuple_sctp,
+		},
+		[IXGBE_NTUPLE_NODE_END] = {
+			.name = "END",
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	},

<snip>

drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c line 414

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +4 -0
+	priority = RTE_MIN(IXGBE_MAX_N_TUPLE_PRIO - 1, (uint16_t)attr->priority);
+	priority = IXGBE_MAX_N_TUPLE_PRIO - 1 - priority;
+	priority += IXGBE_MIN_N_TUPLE_PRIO;
+	ntuple_ctx->ntuple.priority = priority;

priority = RTE_MIN(IXGBE_MAX_N_TUPLE_PRIO, RTE_MAX(IXGBE_MIN_N_TUPLE_PRIO, attr->priority))

otherwise priority 0 maps to 7

Quoted 25 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +25 -0
+
+	/* fixed value for ixgbe */
+	ntuple_ctx->ntuple.flags = RTE_5TUPLE_FLAGS;
+
+	return 0;
+}
+
+static enum ixgbe_5tuple_protocol
+convert_protocol_type(uint8_t protocol_value)
+{
+	if (protocol_value == IPPROTO_TCP)
+		return IXGBE_FILTER_PROTOCOL_TCP;
+	else if (protocol_value == IPPROTO_UDP)
+		return IXGBE_FILTER_PROTOCOL_UDP;
+	else if (protocol_value == IPPROTO_SCTP)
+		return IXGBE_FILTER_PROTOCOL_SCTP;
+	else
+		return IXGBE_FILTER_PROTOCOL_NONE;
+}
+
+static int
+ixgbe_flow_ntuple_ctx_to_flow(const struct ci_flow_engine_ctx *ctx,
+		struct ci_flow *flow,
+		struct rte_flow_error *error __rte_unused)
+{

drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c line 449

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +10 -0
+	const struct ixgbe_ntuple_ctx *ntuple_ctx = (const struct ixgbe_ntuple_ctx *)ctx;
+	struct ixgbe_ntuple_flow *ntuple_flow = (struct ixgbe_ntuple_flow *)flow;
+	const struct rte_eth_ntuple_filter *ntuple = &ntuple_ctx->ntuple;
+	struct ixgbe_5tuple_filter_info *key = &ntuple_flow->key;
+
+	/* mask shape (0 or all-ones) is already guaranteed by the graph */
+	memset(key, 0, sizeof(*key));
+
+	key->dst_ip_mask = ntuple->dst_ip_mask == 0;
+	key->dst_ip = ntuple->dst_ip;

I'm not sure we need to copy from the flow if the corresponding mask is set. For example, in ixgbe_process_ntuple_ipv4(), dst_ip is copied from an item independently of the mask. Thus, a user may install two or more semantically identical rules with different values in masked-out fields and consume all available filters.

Quoted 14 lines
drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c new file +14 -0
+	key->src_ip_mask = ntuple->src_ip_mask == 0;
+	key->src_ip = ntuple->src_ip;
+	key->dst_port_mask = ntuple->dst_port_mask == 0;
+	key->dst_port = ntuple->dst_port;
+	key->src_port_mask = ntuple->src_port_mask == 0;
+	key->src_port = ntuple->src_port;
+	key->proto_mask = ntuple->proto_mask == 0;
+	key->proto = convert_protocol_type(ntuple->proto);
+	key->priority = (uint8_t)ntuple->priority;
+
+	ntuple_flow->queue = ntuple->queue;
+
+	return 0;
+}

<snip>

Signature · 2 lines
Regards,
Vladimir
#79 lore raw

Re: [PATCH v3 08/19] net/ixgbe: reimplement ntuple parser

Quoted 10 lines · Vladimir
Quoted 8 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for ntuple.

The 5tuple filter tracking infrastructure is moved completely inside the new engine and is removed from the rest of the driver.

---

<snip>

Quoted 9 lines
Quoted 5 lines

+    priority = RTE_MIN(IXGBE_MAX_N_TUPLE_PRIO - 1, (uint16_t)attr-

Quoted 1 line

priority);

+    priority = IXGBE_MAX_N_TUPLE_PRIO - 1 - priority; +    priority += IXGBE_MIN_N_TUPLE_PRIO;
+    ntuple_ctx->ntuple.priority = priority;

priority = RTE_MIN(IXGBE_MAX_N_TUPLE_PRIO, RTE_MAX(IXGBE_MIN_N_TUPLE_PRIO, attr->priority))

otherwise priority 0 maps to 7

According to data sheet and rte_flow API semantics, this should be correct. Priority 0 is highest in rte_flow, priority 7 is highest as far as ntuple filters are concerned. The old priority assignment was semantically incorrect. This probably should be a separate fix.

Signature · 2 lines
Thanks,
Anatoly
#80 lore raw

PATCH v3 9/19 net/ixgbe: reimplement security parser

In reply to Anatoly Burakov

Use the new flow graph API and common flow engine infrastructure to implement flow parser for security filter. As a result, flow item checks have become more stringent:

- Mask is now explicitly validated to not have unsupported items in it, when previously they were ignored
- Mask is also validated to mask src/dst addresses, as otherwise it is inconsistent with rte_flow API

Previously, security parser was a special case, now it is a first class citizen. All decryption SA tracking has been moved into the engine, as the engine is now the authoritative source of new Rx flows and the state of the Rx SA table (the Tx SA table is still up to IPsec code to manage).

Because IPsec code does not manage the Rx SA table now, a synchronization mechanism is needed to prevent IPsec code from deallocating a security session when it is still referenced by security flows, so the security session is now atomically refcounted. For Tx, the refcount is effectively a noop, whereas for Rx it is now managed by rte_flow security engine.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.h        |   2 -
 drivers/net/intel/ixgbe/ixgbe_flow.c          | 120 +---
 drivers/net/intel/ixgbe/ixgbe_flow.h          |   1 +
 drivers/net/intel/ixgbe/ixgbe_flow_security.c | 536 ++++++++++++++++++
 drivers/net/intel/ixgbe/ixgbe_ipsec.c         | 359 ++++++------
 drivers/net/intel/ixgbe/ixgbe_ipsec.h         |  50 +-
 drivers/net/intel/ixgbe/meson.build           |   1 +
 7 files changed, 722 insertions(+), 347 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_security.c
drivers/net/intel/ixgbe/ixgbe_ethdev.h +0 -2
index 82c4b10b61d..82a55e32304 100644
@@ -317,8 +317,6 @@ struct ixgbe_l2_tn_info {
 struct rte_flow {
 	struct ci_flow flow;
 	enum rte_filter_type filter_type;
-	/* security flows are not rte_filter_type */
-	bool is_security;
 	void *rule;
 };
 
drivers/net/intel/ixgbe/ixgbe_flow.c +2 -118

174 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +1 -0
index 6f082e9402f..87cf0282454 100644
@@ -19,5 +19,6 @@ extern const struct ci_flow_engine ixgbe_ethertype_flow_engine;
 extern const struct ci_flow_engine ixgbe_syn_flow_engine;
 extern const struct ci_flow_engine ixgbe_l2_tunnel_flow_engine;
 extern const struct ci_flow_engine ixgbe_ntuple_flow_engine;
+extern const struct ci_flow_engine ixgbe_security_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_security.c new file +536 -0

536 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_ipsec.c +160 -199

443 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_ipsec.h +22 -28

91 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 90cc88f0027..12ba639b702 100644
@@ -30,6 +30,7 @@ sources += files(
         'ixgbe_flow_syn.c',
         'ixgbe_flow_l2tun.c',
         'ixgbe_flow_ntuple.c',
+        'ixgbe_flow_security.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
#81 lore raw

Re: [PATCH v3 09/19] net/ixgbe: reimplement security parser

Quoted 22 lines · Anatoly Burakov

Use the new flow graph API and common flow engine infrastructure to implement flow parser for security filter. As a result, flow item checks have become more stringent:

- Mask is now explicitly validated to not have unsupported items in it, when previously they were ignored
- Mask is also validated to mask src/dst addresses, as otherwise it is inconsistent with rte_flow API

Previously, security parser was a special case, now it is a first class citizen. All decryption SA tracking has been moved into the engine, as the engine is now the authoritative source of new Rx flows and the state of the Rx SA table (the Tx SA table is still up to IPsec code to manage).

Because IPsec code does not manage the Rx SA table now, a synchronization mechanism is needed to prevent IPsec code from deallocating a security session when it is still referenced by security flows, so the security session is now atomically refcounted. For Tx, the refcount is effectively a noop, whereas for Rx it is now managed by rte_flow security engine.

---

<snip>

Quoted 3 lines

+/**
+ * Ntuple security filter graph implementation
+ * Pattern: START -> IPV4 | IPV6 -> END

Previously, pattern support was not as restrictive. At least, we could have an Ethernet header in the pattern. With the current implementation, ipsec-secgw could fail, since it can create an ETH/IPV4/ESP/END pattern (please refer to flow_init_single() in ipsec-secgw).

Quoted 2 lines

+ */
+

<snip>

Signature · 2 lines
Regards,
Vladimir
#82 lore raw

Re: [PATCH v3 09/19] net/ixgbe: reimplement security parser

Quoted 22 lines · Vladimir
Quoted 20 lines · Anatoly Burakov

Use the new flow graph API and common flow engine infrastructure to implement flow parser for security filter. As a result, flow item checks have become more stringent:

- Mask is now explicitly validated to not have unsupported items in it, when previously they were ignored
- Mask is also validated to mask src/dst addresses, as otherwise it is inconsistent with rte_flow API

Previously, security parser was a special case, now it is a first class citizen. All decryption SA tracking has been moved into the engine, as the engine is now the authoritative source of new Rx flows and the state of the Rx SA table (the Tx SA table is still up to IPsec code to manage).

Because IPsec code does not manage the Rx SA table now, a synchronization mechanism is needed to prevent IPsec code from deallocating a security session when it is still referenced by security flows, so the security session is now atomically refcounted. For Tx, the refcount is effectively a noop, whereas for Rx it is now managed by rte_flow security engine.

Quoted 7 lines
Quoted 2 lines

---

<snip>

Quoted 3 lines

+/**
+ * Ntuple security filter graph implementation
+ * Pattern: START -> IPV4 | IPV6 -> END

Previously, pattern support was not as restrictive. At least, we could have an Ethernet header in the pattern. With the current implementation, ipsec-secgw could fail, since it can create an ETH/IPV4/ESP/END pattern (please refer to flow_init_single() in ipsec-secgw).

Previous code was not "not as restrictive", it just ignored everything before and after IPv4/IPv6 items. So, I'll add ETH in the beginning, and ESP/UDP after.

Quoted 4 lines
Quoted 2 lines

+ */
+

<snip>

Signature · 2 lines
Thanks,
Anatoly
#83 lore raw

PATCH v3 10/19 net/ixgbe: reimplement FDIR parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for flow director.

The FDIR flow tracking is moved inside the new engine, and the FDIR code is refactored to not mix software tracking with HW writes.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c    |   90 +-
 drivers/net/intel/ixgbe/ixgbe_ethdev.h    |   60 +-
 drivers/net/intel/ixgbe/ixgbe_fdir.c      |  304 +---
 drivers/net/intel/ixgbe/ixgbe_flow.c      | 1591 +------------------
 drivers/net/intel/ixgbe/ixgbe_flow.h      |    2 +
 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c | 1704 +++++++++++++++++++++
 drivers/net/intel/ixgbe/meson.build       |    1 +
 7 files changed, 1830 insertions(+), 1922 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +36 -54
index c8aa050f84a..eaa19f041aa 100644
@@ -146,8 +146,6 @@ static const char * const ixgbevf_valid_arguments[] = {
 
 static int eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params);
 static int eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev);
-static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev);
-static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev);
 static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev);
 static int ixgbe_dev_configure(struct rte_eth_dev *dev);
 static int ixgbe_dev_start(struct rte_eth_dev *dev);
@@ -1355,11 +1353,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	memset(filter_info, 0,
 	       sizeof(struct ixgbe_filter_info));
 
-	/* initialize flow director filter list & hash */
-	ret = ixgbe_fdir_filter_init(eth_dev);
-	if (ret)
-		goto err_fdir_filter_init;
-
 	/* initialize l2 tunnel filter list & hash */
 	ret = ixgbe_l2_tn_filter_init(eth_dev);
 	if (ret)
@@ -1384,8 +1377,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 
 err_flow_engine_conf_init:
 err_l2_tn_filter_init:
-	ixgbe_fdir_filter_uninit(eth_dev);
-err_fdir_filter_init:
 	ixgbe_disable_intr(hw);
 	rte_intr_disable(intr_handle);
 	rte_intr_callback_unregister(intr_handle,
@@ -1415,31 +1406,25 @@ eth_ixgbe_dev_uninit(struct rte_eth_dev *eth_dev)
 	return 0;
 }
 
-static int ixgbe_fdir_filter_uninit(struct rte_eth_dev *eth_dev)
+void
+ixgbe_fdir_state_detach(struct ixgbe_fdir_state *state)
 {
-	struct ixgbe_hw_fdir_info *fdir_info =
-		IXGBE_DEV_PRIVATE_TO_FDIR_INFO(eth_dev->data->dev_private);
-	struct ixgbe_fdir_filter *fdir_filter;
+	if (!ci_refcount_release(&state->ref))
+		return;
 
-	rte_free(fdir_info->hash_map);
-	rte_hash_free(fdir_info->hash_handle);
+	rte_hash_free(state->hash_handle);
 
-	while ((fdir_filter = TAILQ_FIRST(&fdir_info->fdir_list))) {
-		TAILQ_REMOVE(&fdir_info->fdir_list,
-			     fdir_filter,
-			     entries);
-		rte_free(fdir_filter);
-	}
-
-	return 0;
+	/* erase mask state as well */
+	*state = (struct ixgbe_fdir_state){0};
 }
 
-static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev)
+struct ixgbe_fdir_state *
+ixgbe_fdir_state_attach(struct rte_eth_dev_data *dev_data)
 {
-	struct ixgbe_hw_fdir_info *fdir_info =
-		IXGBE_DEV_PRIVATE_TO_FDIR_INFO(eth_dev->data->dev_private);
+	struct ixgbe_adapter *adapter = IXGBE_DEV_PRIVATE_TO_ADAPTER(dev_data->dev_private);
+	struct ixgbe_fdir_state *state = &adapter->fdir_state;
 	char fdir_hash_name[RTE_HASH_NAMESIZE];
-	struct rte_hash_parameters fdir_hash_params = {
+	struct rte_hash_parameters hash_params = {
 		.name = fdir_hash_name,
 		.entries = IXGBE_MAX_FDIR_FILTER_NUM,
 		.key_len = sizeof(union ixgbe_atr_input),
@@ -1448,31 +1433,29 @@ static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev)
 		.socket_id = rte_socket_id(),
 	};
 
-	TAILQ_INIT(&fdir_info->fdir_list);
-	snprintf(fdir_hash_name, RTE_HASH_NAMESIZE,
-		 "fdir_%s", eth_dev->device->name);
-	fdir_info->hash_handle = rte_hash_create(&fdir_hash_params);
-	if (!fdir_info->hash_handle) {
-		PMD_INIT_LOG(ERR, "Failed to create fdir hash table!");
-		return -EINVAL;
-	}
-	fdir_info->hash_map = rte_zmalloc("ixgbe",
-					  sizeof(struct ixgbe_fdir_filter *) *
-					  IXGBE_MAX_FDIR_FILTER_NUM,
-					  0);
-	if (!fdir_info->hash_map) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for fdir hash map!");
-		rte_hash_free(fdir_info->hash_handle);
-		return -ENOMEM;
-	}
-	fdir_info->n_flows = 0;
-	fdir_info->mask_added = FALSE;
+	snprintf(fdir_hash_name, RTE_HASH_NAMESIZE, "ixgbe_fdir_hash_%s",
+			dev_data->name);
+
+	if (ci_refcount_acquire(&state->ref)) {
+		struct rte_hash *hash_handle;
+
+		hash_handle = rte_hash_create(&hash_params);
 
-	/* drop queue is always fixed */
-	IXGBE_DEV_FDIR_CONF(eth_dev)->drop_queue = IXGBE_FDIR_DROP_QUEUE;
+		if (hash_handle == NULL) {
+			PMD_INIT_LOG(ERR, "Failed to create fdir hash table!");
+			rte_hash_free(hash_handle);
+			ci_refcount_release(&state->ref);
+			return NULL;
+		}
+
+		state->hash_handle = hash_handle;
+		state->mask_conf.mode = RTE_FDIR_MODE_NONE;
+
+		/* drop queue is always fixed */
+		IXGBE_DEV_PRIVATE_TO_FDIR_CONF(adapter)->drop_queue = IXGBE_FDIR_DROP_QUEUE;
+	}
 
-	return 0;
+	return state;
 }
 
 static int ixgbe_l2_tn_filter_init(struct rte_eth_dev *eth_dev)
@@ -2976,6 +2959,9 @@ ixgbe_dev_stop(struct rte_eth_dev *dev)
 	/* reset hierarchy commit */
 	tm_conf->committed = false;
 
+	/* the reset above wiped the flow director setup, kept flows are replayed on start */
+	ixgbe_fdir_hw_invalidate(dev);
+
 	adapter->rss_reta_updated = 0;
 
 	hw->adapter_stopped = true;
@@ -3120,9 +3106,6 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	/* uninitialize PF if max_vfs not zero */
 	ixgbe_pf_host_uninit(dev);
 
-	/* remove all the fdir filters & hash */
-	ixgbe_fdir_filter_uninit(dev);
-
 	/* clear all the filters list */
 	ixgbe_filterlist_flush(dev);
 
@@ -7900,7 +7883,6 @@ ixgbe_rss_filter_restore(struct rte_eth_dev *dev)
 static int
 ixgbe_filter_restore(struct rte_eth_dev *dev)
 {
-	ixgbe_fdir_filter_restore(dev);
 	ixgbe_rss_filter_restore(dev);
 
 	return 0;
drivers/net/intel/ixgbe/ixgbe_ethdev.h +38 -22

104 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_fdir.c +46 -258

354 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.c +3 -1588

1,660 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +2 -0
index 87cf0282454..256e0478f58 100644
@@ -20,5 +20,7 @@ extern const struct ci_flow_engine ixgbe_syn_flow_engine;
 extern const struct ci_flow_engine ixgbe_l2_tunnel_flow_engine;
 extern const struct ci_flow_engine ixgbe_ntuple_flow_engine;
 extern const struct ci_flow_engine ixgbe_security_flow_engine;
+extern const struct ci_flow_engine ixgbe_fdir_flow_engine;
+extern const struct ci_flow_engine ixgbe_fdir_tunnel_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +1704 -0

728 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 12ba639b702..2487f6a522f 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.52.0

Part of the diff is not shown here. View the raw message.

#84 lore raw

Re: [PATCH v3 10/19] net/ixgbe: reimplement FDIR parser

Quoted 8 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for flow director.

The FDIR flow tracking is moved inside the new engine, and the FDIR code is refactored to not mix software tracking with HW writes.

---

<snip>

drivers/net/intel/ixgbe/ixgbe_ethdev.c static int ixgbe_fdir_filter_init(struct rte_eth_dev *eth_dev) · line 1446

Quoted 7 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +4 -2
+		hash_handle = rte_hash_create(&hash_params);
 
-	/* drop queue is always fixed */
-	IXGBE_DEV_FDIR_CONF(eth_dev)->drop_queue = IXGBE_FDIR_DROP_QUEUE;
+		if (hash_handle == NULL) {
+			PMD_INIT_LOG(ERR, "Failed to create fdir hash table!");
+			rte_hash_free(hash_handle);

no need to free NULL

Quoted 15 lines
drivers/net/intel/ixgbe/ixgbe_ethdev.c +11 -1
+			ci_refcount_release(&state->ref);
+			return NULL;
+		}
+
+		state->hash_handle = hash_handle;
+		state->mask_conf.mode = RTE_FDIR_MODE_NONE;
+
+		/* drop queue is always fixed */
+		IXGBE_DEV_PRIVATE_TO_FDIR_CONF(adapter)->drop_queue = IXGBE_FDIR_DROP_QUEUE;
+	}
 
-	return 0;
+	return state;
  }

<snip>

drivers/net/intel/ixgbe/ixgbe_fdir.c fdir_erase_filter_82599(struct ixgbe_hw *hw, uint32_t fdirhash) · line 993

Quoted 10 lines

+/* hardware loses its flow director setup across a stop/start cycle
*/ +void +ixgbe_fdir_hw_invalidate(struct rte_eth_dev *dev) { - int ret; + struct ixgbe_adapter *adapter = IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);

drivers/net/intel/ixgbe/ixgbe_fdir.c +2 -5
+	struct ixgbe_fdir_state *state = &adapter->fdir_state;
 
-	ret = rte_hash_lookup(fdir_info->hash_handle, (const void *)key);
-	if (ret < 0)
-		return NULL;
-
-	return fdir_info->hash_map[ret];
+	if (state != NULL) {

nit: can the state be NULL at all?

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_fdir.c +3 -0
+		state->mask_conf.hw_configured = false;
+		state->mask_conf.mask_programmed = false;
+	}
  }

<snip>

Quoted 7 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +7 -0
+
+static int
+ixgbe_flow_fdir_flow_unregister(struct ci_flow *flow, struct rte_flow_error *error)
+{
+	struct ixgbe_fdir_flow *fdir_flow = (struct ixgbe_fdir_flow *)flow;
+	struct ixgbe_fdir_priv *priv = flow->engine_priv;
+	struct ixgbe_fdir_state *state = priv->state;

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1480

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +10 -0
+	int ret;
+
+	ret = ixgbe_fdir_table_del(state->hash_handle, fdir_flow);
+	if (ret == -ENOENT) {
+		return rte_flow_error_set(error, ENOENT,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Flow director filter is missing from the filter table");
+	}
+
+	ixgbe_fdir_mask_release(&state->mask_conf);

Do we also need to check if there are any other rules and, if there are none, reset hw_configured as well as global_fdir_conf->mode, and restore all HW state, such as the adjusted packet buffer (pballoc from ixgbe_fdir_configure())?

I mean, should we do a similar cleanup to ixgbe_flow_fdir_flow_uninstall()?

We can end up in a situation where ixgbe_fdir_filter_program() fails while installing the first flow, after the HW and masks have already been configured. Based on the ci_flow_create() logic, if the installation fails, only ci_flow_unregister() is called. This leaves us in an inconsistent state where we have no flows installed, but FDIR remains configured.

Quoted 6 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +6 -0
+
+	return 0;
+}
+
+static int
+ixgbe_flow_fdir_configure_hw(struct ixgbe_adapter *adapter,

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1496

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +10 -0
+		struct ixgbe_fdir_mask_state *mask_state,
+		struct rte_flow_error *error)
+{
+	struct rte_eth_fdir_conf *global_fdir_conf = IXGBE_DEV_PRIVATE_TO_FDIR_CONF(adapter);
+	struct rte_eth_fdir_conf local_fdir_conf = *global_fdir_conf;
+	int ret;
+
+	local_fdir_conf.mode = mask_state->mode;
+
+	ret = ixgbe_fdir_configure(adapter, &local_fdir_conf, &mask_state->mask);

There is a problem with calling ixgbe_fdir_configure() multiple times. This call changes the HW state:

/* ixgbe_fdir.c:ixgbe_fdir_configure() */ IXGBE_WRITE_REG(hw, IXGBE_RXPBSIZE(0), (IXGBE_READ_REG(hw, IXGBE_RXPBSIZE(0)) - pbsize));

With every invocation, IXGBE_RXPBSIZE(0) is decreased by pbsize.

Could we configure FDIR only once during device start, without configuring it with the first rule? Instead, for the first rule, we could just call ixgbe_fdir_set_input_mask() / ixgbe_set_fdir_flex_conf().

Quoted 48 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +48 -0
+	if (ret != 0) {
+		return rte_flow_error_set(error, -ret,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"Failed to configure flow director");
+	}
+
+	global_fdir_conf->mode = mask_state->mode;
+	mask_state->hw_configured = true;
+
+	return 0;
+}
+
+static int
+ixgbe_flow_fdir_program_mask(struct ixgbe_adapter *adapter,
+		struct ixgbe_fdir_mask_state *mask_state,
+		struct rte_flow_error *error)
+{
+	struct ixgbe_hw_fdir_info *global_fdir_info = IXGBE_DEV_PRIVATE_TO_FDIR_INFO(adapter);
+	int ret;
+
+	if (mask_state->mask.flex_bytes_mask != 0) {
+		ret = ixgbe_fdir_set_flexbytes_offset(adapter, mask_state->flex_bytes_offset);
+		if (ret != 0) {
+			return rte_flow_error_set(error, -ret,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				"Failed to set flex bytes offset");
+		}
+	}
+
+	ret = ixgbe_fdir_set_input_mask(adapter, &mask_state->mask, mask_state->mode);
+	if (ret != 0) {
+		return rte_flow_error_set(error, -ret,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"Failed to set input mask");
+	}
+
+	/* record what is now in hardware for ixgbe_fdir_info_get() */
+	global_fdir_info->mask = mask_state->mask;
+	global_fdir_info->flex_bytes_offset = mask_state->flex_bytes_offset;
+	mask_state->mask_programmed = true;
+
+	return 0;
+}
+
+static int
+ixgbe_flow_fdir_flow_install(struct ci_flow *flow,
+		struct rte_flow_error *error)
+{

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1554

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +10 -0
+	struct ixgbe_adapter *adapter = IXGBE_DEV_PRIVATE_TO_ADAPTER(flow->dev_data->dev_private);
+	struct ixgbe_fdir_flow *fdir_flow = (struct ixgbe_fdir_flow *)flow;
+	struct ixgbe_fdir_priv *priv = flow->engine_priv;
+	struct ixgbe_fdir_mask_state *mask_conf = &priv->state->mask_conf;
+	int ret;
+
+	if (!mask_conf->hw_configured) {
+		ret = ixgbe_flow_fdir_configure_hw(adapter, mask_conf, error);
+		if (ret != 0)
+			return ret;

return rte_flow_error_set() instead?

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1557

Quoted 3 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +3 -0
+	}
+
+	if (!mask_conf->mask_programmed) {

the previous invocation of the ixgbe_flow_fdir_configure_hw() have already programmed mask (->ixgbe_fdir_configure->ixgbe_fdir_set_input_mask). Do we really need to do this one more time?

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1559

Quoted 2 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +2 -0
+		ret = ixgbe_flow_fdir_program_mask(adapter, mask_conf, error);
+		if (ret != 0)

do we need to call ixgbe_fdir_hw_invalidate() on failure?

Quoted 35 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +35 -0
+			return ret;
+	}
+
+	ret = ixgbe_fdir_filter_program(adapter, &fdir_flow->rule, fdir_flow->queue,
+			fdir_flow->fdircmd_flags, fdir_flow->fdirhash);
+	if (ret != 0) {
+		return rte_flow_error_set(error, -ret,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"Failed to program flow director filter");
+	}
+
+	return 0;
+}
+
+static int
+ixgbe_flow_fdir_flow_uninstall(struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	struct ixgbe_adapter *adapter = IXGBE_DEV_PRIVATE_TO_ADAPTER(flow->dev_data->dev_private);
+	struct rte_eth_fdir_conf *global_fdir_conf = IXGBE_DEV_PRIVATE_TO_FDIR_CONF(adapter);
+	struct ixgbe_fdir_flow *fdir_flow = (struct ixgbe_fdir_flow *)flow;
+	struct ixgbe_fdir_priv *priv = flow->engine_priv;
+	struct ixgbe_fdir_mask_state *mask_conf = &priv->state->mask_conf;
+	int ret;
+
+	ret = ixgbe_fdir_filter_clear(adapter, fdir_flow->fdirhash);
+	if (ret != 0) {
+		return rte_flow_error_set(error, -ret,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"Failed to remove flow director filter");
+	}
+
+	/* unregister has not run yet, so this filter is still counted */
+	if (mask_conf->ref.count > 1)
+		return 0;

drivers/net/intel/ixgbe/ixgbe_flow_fdir.c line 1604

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +10 -0
+
+	mask_conf->hw_configured = false;
+	mask_conf->mask_programmed = false;
+	global_fdir_conf->mode = RTE_FDIR_MODE_NONE;
+
+	ret = ixgbe_fdir_reset_tables(adapter);
+	if (ret != 0) {
+		return rte_flow_error_set(error, -ret,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"Failed to reset flow director tables");

Would it be worth doing this in a separate function, such as ixgbe_flow_fdir_configure_hw(), and also resetting the masks to their defaults?

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_flow_fdir.c new file +4 -0
+	}
+
+	return 0;
+}

<snip>

Signature · 2 lines
Regards,
Vladimir
#85 lore raw

PATCH v3 11/19 net/ixgbe: reimplement hash parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS configuration.

RSS flow parser doesn't really parse any "flows", it was completely ignoring the flow patterns and was only looking at actions. It will therefore not specify a pattern graph and will match NULL pattern, empty patterns (START -> END), and ANY patterns (START -> ANY -> END).

RSS was the last engine using "filter list", so that is now removed. The RSS engine filter tracking was also moved completely into the new engine, and all of the "filter list" infrastructure is removed for good.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c    |  30 --
 drivers/net/intel/ixgbe/ixgbe_ethdev.h    |  18 +-
 drivers/net/intel/ixgbe/ixgbe_flow.c      | 415 +---------------------
 drivers/net/intel/ixgbe/ixgbe_flow.h      |   1 +
 drivers/net/intel/ixgbe/ixgbe_flow_hash.c | 212 +++++++++++
 drivers/net/intel/ixgbe/ixgbe_rxtx.c      |  48 +--
 drivers/net/intel/ixgbe/meson.build       |   1 +
 7 files changed, 235 insertions(+), 490 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_hash.c
drivers/net/intel/ixgbe/ixgbe_ethdev.c +0 -30
index eaa19f041aa..78f3ab57aea 100644
@@ -347,7 +347,6 @@ static int ixgbe_dev_udp_tunnel_port_add(struct rte_eth_dev *dev,
 					 struct rte_eth_udp_tunnel *udp_tunnel);
 static int ixgbe_dev_udp_tunnel_port_del(struct rte_eth_dev *dev,
 					 struct rte_eth_udp_tunnel *udp_tunnel);
-static int ixgbe_filter_restore(struct rte_eth_dev *dev);
 static void ixgbe_l2_tunnel_conf(struct rte_eth_dev *dev);
 static int ixgbe_wait_for_link_up(struct ixgbe_hw *hw);
 static int devarg_handle_int(__rte_unused const char *key, const char *value,
@@ -1364,9 +1363,6 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	if (ret)
 		goto err_flow_engine_conf_init;
 
-	/* initialize flow filter lists */
-	ixgbe_filterlist_init(eth_dev);
-
 	/* initialize bandwidth configuration info */
 	memset(bw_conf, 0, sizeof(struct ixgbe_bw_conf));
 
@@ -2845,7 +2841,6 @@ ixgbe_dev_start(struct rte_eth_dev *dev)
 	/* resume enabled intr since hw reset */
 	ixgbe_enable_intr(dev);
 	ixgbe_l2_tunnel_conf(dev);
-	ixgbe_filter_restore(dev);
 
 	if (tm_conf->root && !tm_conf->committed)
 		PMD_DRV_LOG(WARNING,
@@ -3106,9 +3101,6 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	/* uninitialize PF if max_vfs not zero */
 	ixgbe_pf_host_uninit(dev);
 
-	/* clear all the filters list */
-	ixgbe_filterlist_flush(dev);
-
 	/* Remove all Traffic Manager configuration */
 	ixgbe_tm_conf_uninit(dev);
 
@@ -7866,28 +7858,6 @@ int ixgbe_enable_sec_tx_path_generic(struct ixgbe_hw *hw)
 	return IXGBE_SUCCESS;
 }
 
-/* restore rss filter */
-static inline void
-ixgbe_rss_filter_restore(struct rte_eth_dev *dev)
-{
-	struct ixgbe_adapter *adapter =
-		IXGBE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-
-	if (filter_info->rss_info.conf.queue_num)
-		ixgbe_config_rss_filter(adapter,
-			&filter_info->rss_info, TRUE);
-}
-
-static int
-ixgbe_filter_restore(struct rte_eth_dev *dev)
-{
-	ixgbe_rss_filter_restore(dev);
-
-	return 0;
-}
-
 static void
 ixgbe_l2_tunnel_conf(struct rte_eth_dev *dev)
 {
drivers/net/intel/ixgbe/ixgbe_ethdev.h +4 -14
index e20f94a9621..4143fffa814 100644
@@ -305,8 +305,6 @@ struct ixgbe_ethertype_table {
  * Structure to store filters' info.
  */
 struct ixgbe_filter_info {
-	/* store the rss filter info */
-	struct ixgbe_rte_flow_rss_conf rss_info;
 	/* shared EtherType (ETQF) slot table */
 	struct ixgbe_ethertype_table ethertype_table;
 	/* 1588 timestamping ETQF slot (valid when timesync_installed) */
@@ -323,10 +321,9 @@ struct ixgbe_l2_tn_info {
 	uint16_t e_tag_ether_type; /* ether type for e-tag */
 };
 
+/* no driver-specific data needed */
 struct rte_flow {
 	struct ci_flow flow;
-	enum rte_filter_type filter_type;
-	void *rule;
 };
 
 struct ixgbe_macsec_setting {
@@ -434,9 +431,6 @@ struct ixgbe_tm_conf {
 	bool committed;
 };
 
-struct ixgbe_filter_ele_base;
-TAILQ_HEAD(ixgbe_filter_ele_list, ixgbe_filter_ele_base);
-
 /*
  * Structure to store private data for each driver instance (for each port).
  */
@@ -459,7 +453,6 @@ struct ixgbe_adapter {
 	struct ixgbe_bypass_info    bps;
 #endif /* RTE_LIBRTE_IXGBE_BYPASS */
 	struct ixgbe_filter_info    filter;
-	struct ixgbe_filter_ele_list flow_list;
 	struct ixgbe_l2_tn_info     l2_tn;
 	struct ixgbe_bw_conf        bw_conf;
 	struct ixgbe_ipsec          ipsec;
@@ -681,8 +674,6 @@ ixgbe_e_tag_filter_add(struct ixgbe_adapter *adapter,
 int
 ixgbe_e_tag_filter_del(struct ixgbe_adapter *adapter,
 		       struct ixgbe_l2_tunnel_conf *l2_tunnel);
-void ixgbe_filterlist_init(struct rte_eth_dev *dev);
-void ixgbe_filterlist_flush(struct rte_eth_dev *dev);
 /*
  * Flow director function prototypes
  */
@@ -754,10 +745,9 @@ int ixgbe_set_queue_rate_limit(struct rte_eth_dev *dev, uint16_t queue_idx,
 			       uint32_t tx_rate);
 int ixgbe_rss_conf_init(struct ixgbe_rte_flow_rss_conf *out,
 			const struct rte_flow_action_rss *in);
-int ixgbe_action_rss_same(const struct rte_flow_action_rss *comp,
-			  const struct rte_flow_action_rss *with);
-int ixgbe_config_rss_filter(struct ixgbe_adapter *adapter,
-		struct ixgbe_rte_flow_rss_conf *conf, bool add);
+void ixgbe_hw_rss_filter_program(struct ixgbe_hw *hw,
+		struct ixgbe_rte_flow_rss_conf *conf);
+void ixgbe_hw_rss_filter_clear(struct ixgbe_hw *hw);
 
 void ixgbe_dev_macsec_register_enable(struct rte_eth_dev *dev,
 		struct ixgbe_macsec_setting *macsec_setting);
drivers/net/intel/ixgbe/ixgbe_flow.c +8 -407

476 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h +1 -0
index 256e0478f58..959b2c95947 100644
@@ -22,5 +22,6 @@ extern const struct ci_flow_engine ixgbe_ntuple_flow_engine;
 extern const struct ci_flow_engine ixgbe_security_flow_engine;
 extern const struct ci_flow_engine ixgbe_fdir_flow_engine;
 extern const struct ci_flow_engine ixgbe_fdir_tunnel_flow_engine;
+extern const struct ci_flow_engine ixgbe_hash_flow_engine;
 
 #endif /*  _IXGBE_FLOW_H_ */
drivers/net/intel/ixgbe/ixgbe_flow_hash.c new file +212 -0

212 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_rxtx.c +9 -39

75 lines not on this page · raw message

drivers/net/intel/ixgbe/meson.build +1 -0
index 2487f6a522f..7e267008351 100644
@@ -32,6 +32,7 @@ sources += files(
         'ixgbe_flow_ntuple.c',
         'ixgbe_flow_security.c',
         'ixgbe_flow_fdir.c',
+        'ixgbe_flow_hash.c',
         'ixgbe_ipsec.c',
         'ixgbe_pf.c',
         'ixgbe_rxtx.c',
Signature · 1 line
2.52.0
#86 lore raw

Re: [PATCH v3 11/19] net/ixgbe: reimplement hash parser

Quoted 14 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS configuration.

RSS flow parser doesn't really parse any "flows", it was completely ignoring the flow patterns and was only looking at actions. It will therefore not specify a pattern graph and will match NULL pattern, empty patterns (START -> END), and ANY patterns (START -> ANY -> END).

RSS was the last engine using "filter list", so that is now removed. The RSS engine filter tracking was also moved completely into the new engine, and all of the "filter list" infrastructure is removed for good.

---

<snip>

drivers/net/intel/ixgbe/ixgbe_flow_hash.c line 131

Quoted 10 lines
drivers/net/intel/ixgbe/ixgbe_flow_hash.c new file +10 -0
+
+static int
+ixgbe_flow_hash_ctx_to_flow(const struct ci_flow_engine_ctx *ctx,
+		struct ci_flow *flow,
+		struct rte_flow_error *error __rte_unused)
+{
+	const struct ixgbe_hash_ctx *hash_ctx = (const struct ixgbe_hash_ctx *)ctx;
+	struct ixgbe_hash_flow *hash_flow = (struct ixgbe_hash_flow *)flow;
+
+	hash_flow->rss_conf = hash_ctx->rss_conf;

We cannot just copy this structure, as it contains self-referential fields. For example, hash_ctx->rss_conf.conf.key points to hash_ctx->rss_conf.key within the same structure. You may use ixgbe_rss_conf_init()

Quoted 4 lines
drivers/net/intel/ixgbe/ixgbe_flow_hash.c new file +4 -0
+
+	return 0;
+}
+

<snip>

Signature · 2 lines
Regards,
Vladimir
#87 lore raw

Re: [PATCH v3 11/19] net/ixgbe: reimplement hash parser

Quoted 17 lines · Vladimir
Quoted 14 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS configuration.

RSS flow parser doesn't really parse any "flows", it was completely ignoring the flow patterns and was only looking at actions. It will therefore not specify a pattern graph and will match NULL pattern, empty patterns (START -> END), and ANY patterns (START -> ANY -> END).

RSS was the last engine using "filter list", so that is now removed. The RSS engine filter tracking was also moved completely into the new engine, and all of the "filter list" infrastructure is removed for good.

---

<snip>

Quoted 13 lines
Quoted 11 lines
drivers/net/intel/ixgbe/ixgbe_flow_hash.c new file +7 -0
+
+static int
+ixgbe_flow_hash_ctx_to_flow(const struct ci_flow_engine_ctx *ctx,
+        struct ci_flow *flow,
+        struct rte_flow_error *error __rte_unused)
+{
+    const struct ixgbe_hash_ctx *hash_ctx = (const struct 

ixgbe_hash_ctx *)ctx;
+    struct ixgbe_hash_flow *hash_flow = (struct ixgbe_hash_flow *)flow; +
+    hash_flow->rss_conf = hash_ctx->rss_conf;

We cannot just copy this structure, as it contains self-referential fields. For example, hash_ctx->rss_conf.conf.key points to hash_ctx-

Quoted 1 line

rss_conf.key within the same structure. You may use ixgbe_rss_conf_init()

I'd rather get rid of the struct embedding same way I did in i40e :) this footgun should not exist.

Signature · 2 lines
Thanks,
Anatoly
#88 lore raw

PATCH v3 12/19 net/ixgbe: advertise flow keep capability

In reply to Anatoly Burakov

The ixgbe driver's rte_flow implementation has always endeavored to keep flows across device restart, however it never advertised this capability. Advertise it explicitly.

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c | 3 +++
 1 file changed, 3 insertions(+)
drivers/net/intel/ixgbe/ixgbe_ethdev.c +3 -0
index 78f3ab57aea..48b1957ea94 100644
@@ -4002,6 +4002,9 @@ ixgbe_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->reta_size = ixgbe_reta_size_get(hw->mac.type);
 	dev_info->flow_type_rss_offloads = IXGBE_RSS_OFFLOAD_ALL;
 
+	/* rte_flow rules are kept across dev_stop/dev_start and replayed on start */
+	dev_info->dev_capa |= RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+
 	dev_info->speed_capa = RTE_ETH_LINK_SPEED_1G | RTE_ETH_LINK_SPEED_10G;
 	if (hw->device_id == IXGBE_DEV_ID_X550EM_A_1G_T ||
 			hw->device_id == IXGBE_DEV_ID_X550EM_A_1G_T_L)
Signature · 1 line
2.52.0
Acked-by on v3 12/19
#89 lore raw

Re: [PATCH v3 12/19] net/ixgbe: advertise flow keep capability

consider reflecting this in release notes

Quoted 23 lines · Anatoly Burakov

The ixgbe driver's rte_flow implementation has always endeavored to keep flows across device restart, however it never advertised this capability. Advertise it explicitly.

---
  drivers/net/intel/ixgbe/ixgbe_ethdev.c | 3 +++
  1 file changed, 3 insertions(+)
drivers/net/intel/ixgbe/ixgbe_ethdev.c +3 -0
index 78f3ab57aea..48b1957ea94 100644
@@ -4002,6 +4002,9 @@ ixgbe_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
  	dev_info->reta_size = ixgbe_reta_size_get(hw->mac.type);
  	dev_info->flow_type_rss_offloads = IXGBE_RSS_OFFLOAD_ALL;
 
+	/* rte_flow rules are kept across dev_stop/dev_start and replayed on start */
+	dev_info->dev_capa |= RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+
  	dev_info->speed_capa = RTE_ETH_LINK_SPEED_1G | RTE_ETH_LINK_SPEED_10G;
  	if (hw->device_id == IXGBE_DEV_ID_X550EM_A_1G_T ||
  			hw->device_id == IXGBE_DEV_ID_X550EM_A_1G_T_L)
Signature · 2 lines
Regards,
Vladimir
#90 lore raw

Re: [PATCH v3 12/19] net/ixgbe: advertise flow keep capability

Quoted 4 lines · Vladimir

consider reflecting this in release notes

Arguably, there's nothing that actually changes - the driver was behaving this way from the get go. It just didn't advertise this flag properly. I'll add a note to the release notes in any case. Thanks for review!

Signature · 2 lines
Thanks,
Anatoly
#91 lore raw

PATCH v3 13/19 net/i40e: add support for common flow parsing

In reply to Anatoly Burakov

Implement support for common flow parsing infrastructure in preparation for migration of flow engines.

Currently, i40e explicitly clears KEEP_FLOW capability flag, meaning that it advertises that all flows are flushed on dev stop. However, in practice i40e actually restores all flows on dev start, so while we will not (yet) advertise the KEEP_FLOW capability flag, we will also not flush the flows on dev stop with the new engines, and instead replay them.

---
 drivers/net/intel/i40e/i40e_ethdev.c | 21 +++++++++++
 drivers/net/intel/i40e/i40e_ethdev.h |  5 +++
 drivers/net/intel/i40e/i40e_flow.c   | 53 +++++++++++++++++++++++++++-
 drivers/net/intel/i40e/i40e_flow.h   | 12 +++++++
 4 files changed, 90 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow.h
drivers/net/intel/i40e/i40e_ethdev.c +21 -0
index d64d8dd7b56..a7fc82f67d6 100644
@@ -42,6 +42,9 @@
 #include "i40e_regs.h"
 #include "rte_pmd_i40e.h"
 #include "i40e_hash.h"
+#include "i40e_flow.h"
+
+#include "../common/flow_engine.h"
 
 #define ETH_I40E_FLOATING_VEB_ARG	"enable_floating_veb"
 #define ETH_I40E_FLOATING_VEB_LIST_ARG	"floating_veb_list"
@@ -1838,6 +1841,12 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	if (ret < 0)
 		goto err_init_fdir_filter_list;
 
+	/* initialize flow engine configuration */
+	ret = ci_flow_engine_conf_init(&pf->flow_engine_conf,
+			&i40e_flow_engine_list, dev->data);
+	if (ret < 0)
+		goto err_flow_engine_conf_init;
+
 	/* initialize queue region configuration */
 	i40e_init_queue_region_conf(dev);
 
@@ -1846,6 +1855,12 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 
 	return 0;
 
+err_flow_engine_conf_init:
+	rte_free(pf->fdir.fdir_flow_pool.bitmap);
+	rte_free(pf->fdir.fdir_flow_pool.pool);
+	rte_free(pf->fdir.fdir_filter_array);
+	rte_free(pf->fdir.hash_map);
+	rte_hash_free(pf->fdir.hash_table);
 err_init_fdir_filter_list:
 	rte_hash_free(pf->tunnel.hash_table);
 	rte_free(pf->tunnel.hash_map);
@@ -2613,6 +2628,8 @@ i40e_dev_start(struct rte_eth_dev *dev)
 	/* Set the max frame size to HW*/
 	i40e_aq_set_mac_config(hw, max_frame_size, TRUE, false, 0, NULL);
 
+	ci_flow_replay(&pf->flow_engine_conf);
+
 	return I40E_SUCCESS;
 
 tx_err:
@@ -2715,6 +2732,10 @@ i40e_dev_close(struct rte_eth_dev *dev)
 
 	ret = i40e_dev_stop(dev);
 
+	/* free the flows and reset flow config */
+	ci_flow_cleanup(&pf->flow_engine_conf);
+	ci_flow_engine_conf_reset(&pf->flow_engine_conf);
+
 	i40e_dev_free_queues(dev);
 
 	/* Disable interrupt */
drivers/net/intel/i40e/i40e_ethdev.h +5 -0
index 1e64a2d2800..16b67268f7f 100644
@@ -21,6 +21,8 @@
 #include "base/i40e_type.h"
 #include "base/virtchnl.h"
 
+#include "../common/flow_engine.h"
+
 #define I40E_AQ_LEN               32
 #define I40E_AQ_BUF_SZ            4096
 /* Number of queues per TC should be one of 1, 2, 4, 8, 16, 32, 64 */
@@ -278,6 +280,7 @@ enum i40e_flxpld_layer_idx {
  * Struct to store flow created.
  */
 struct rte_flow {
+	struct ci_flow base;
 	TAILQ_ENTRY(rte_flow) node;
 	enum rte_filter_type filter_type;
 	void *rule;
@@ -1182,6 +1185,8 @@ struct i40e_pf {
 	/* The floating enable flag for the specific VF */
 	bool floating_veb_list[I40E_MAX_VF];
 	struct i40e_flow_list flow_list;
+	/* flow engine configuration */
+	struct ci_flow_engine_conf flow_engine_conf;
 	bool mpls_replace_flag;  /* 1 - MPLS filter replace is done */
 	bool gtp_replace_flag;   /* 1 - GTP-C/U filter replace is done */
 	bool qinq_replace_flag;  /* QINQ filter replace is done */
drivers/net/intel/i40e/i40e_flow.c +52 -1

107 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h new file +12 -0
new file mode 100644
index 00000000000..c9588686619
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _I40E_FLOW_H_
+#define _I40E_FLOW_H_
+
+#include "../common/flow_engine.h"
+
+extern const struct ci_flow_engine_list i40e_flow_engine_list;
+
+#endif /* _I40E_FLOW_H_ */
Signature · 1 line
2.52.0
Acked-by on v3 13/19
#92 lore raw

Re: [PATCH v3 13/19] net/i40e: add support for common flow parsing

Quoted 226 lines · Anatoly Burakov

Implement support for common flow parsing infrastructure in preparation for migration of flow engines.

Currently, i40e explicitly clears KEEP_FLOW capability flag, meaning that it advertises that all flows are flushed on dev stop. However, in practice i40e actually restores all flows on dev start, so while we will not (yet) advertise the KEEP_FLOW capability flag, we will also not flush the flows on dev stop with the new engines, and instead replay them.

---
  drivers/net/intel/i40e/i40e_ethdev.c | 21 +++++++++++
  drivers/net/intel/i40e/i40e_ethdev.h |  5 +++
  drivers/net/intel/i40e/i40e_flow.c   | 53 +++++++++++++++++++++++++++-
  drivers/net/intel/i40e/i40e_flow.h   | 12 +++++++
  4 files changed, 90 insertions(+), 1 deletion(-)

create mode 100644 drivers/net/intel/i40e/i40e_flow.h

drivers/net/intel/i40e/i40e_ethdev.c +21 -0
index d64d8dd7b56..a7fc82f67d6 100644
@@ -42,6 +42,9 @@
  #include "i40e_regs.h"
  #include "rte_pmd_i40e.h"
  #include "i40e_hash.h"
+#include "i40e_flow.h"
+
+#include "../common/flow_engine.h"
 
  #define ETH_I40E_FLOATING_VEB_ARG	"enable_floating_veb"
  #define ETH_I40E_FLOATING_VEB_LIST_ARG	"floating_veb_list"
@@ -1838,6 +1841,12 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
  	if (ret < 0)
  		goto err_init_fdir_filter_list;
 
+	/* initialize flow engine configuration */
+	ret = ci_flow_engine_conf_init(&pf->flow_engine_conf,
+			&i40e_flow_engine_list, dev->data);
+	if (ret < 0)
+		goto err_flow_engine_conf_init;
+
  	/* initialize queue region configuration */
  	i40e_init_queue_region_conf(dev);
 
@@ -1846,6 +1855,12 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 
  	return 0;
 
+err_flow_engine_conf_init:
+	rte_free(pf->fdir.fdir_flow_pool.bitmap);
+	rte_free(pf->fdir.fdir_flow_pool.pool);
+	rte_free(pf->fdir.fdir_filter_array);
+	rte_free(pf->fdir.hash_map);
+	rte_hash_free(pf->fdir.hash_table);
  err_init_fdir_filter_list:
  	rte_hash_free(pf->tunnel.hash_table);
  	rte_free(pf->tunnel.hash_map);
@@ -2613,6 +2628,8 @@ i40e_dev_start(struct rte_eth_dev *dev)
  	/* Set the max frame size to HW*/
  	i40e_aq_set_mac_config(hw, max_frame_size, TRUE, false, 0, NULL);
 
+	ci_flow_replay(&pf->flow_engine_conf);
+
  	return I40E_SUCCESS;
 
  tx_err:
@@ -2715,6 +2732,10 @@ i40e_dev_close(struct rte_eth_dev *dev)
 
  	ret = i40e_dev_stop(dev);
 
+	/* free the flows and reset flow config */
+	ci_flow_cleanup(&pf->flow_engine_conf);
+	ci_flow_engine_conf_reset(&pf->flow_engine_conf);
+
  	i40e_dev_free_queues(dev);
 
  	/* Disable interrupt */
drivers/net/intel/i40e/i40e_ethdev.h +1 -0
index 1e64a2d2800..16b67268f7f 100644
@@ -21,6 +21,8 @@
  #include "base/i40e_type.h"
  #include "base/virtchnl.h"
 
+#include "../common/flow_engine.h" + #define I40E_AQ_LEN 32 #define I40E_AQ_BUF_SZ 4096 /* Number of 

queues per TC should be one of 1, 2, 4, 8, 16, 32, 64 */ @@ -278,6
+280,7 @@ enum i40e_flxpld_layer_idx { * Struct to store flow created.
*/ struct rte_flow { + struct ci_flow base; TAILQ_ENTRY(rte_flow) node; enum rte_filter_type filter_type; void *rule; @@ -1182,6 +1185,8
@@ struct i40e_pf { /* The floating enable flag for the specific VF */
bool floating_veb_list[I40E_MAX_VF]; struct i40e_flow_list flow_list;
+ /* flow engine configuration */ + struct ci_flow_engine_conf
flow_engine_conf; bool mpls_replace_flag; /* 1 - MPLS filter replace is done */ bool gtp_replace_flag; /* 1 - GTP-C/U filter replace is done */ bool qinq_replace_flag; /* QINQ filter replace is done */ diff --git a/drivers/net/intel/i40e/i40e_flow.c
b/drivers/net/intel/i40e/i40e_flow.c index a017d3cd448..7d410b6c33b 100644 --- a/drivers/net/intel/i40e/i40e_flow.c +++ b/drivers/net/intel/i40e/i40e_flow.c @@ -26,9 +26,12 @@ #include "base/i40e_prototype.h"
#include "i40e_ethdev.h"
#include "i40e_hash.h"

drivers/net/intel/i40e/i40e_ethdev.h +52 -1
+#include "i40e_flow.h"
 
  #include "../common/flow_check.h"
 
+const struct ci_flow_engine_list i40e_flow_engine_list = {0};
+
  #define I40E_IPV6_TC_MASK	(0xFF << I40E_FDIR_IPv6_TC_OFFSET)
  #define I40E_IPV6_FRAG_HEADER	44
  #define I40E_TENANT_ARRAY_NUM	3
@@ -1268,6 +1271,25 @@ i40e_flow_dev_dump(struct rte_eth_dev *dev,
  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
  	struct rte_flow *p_flow;
  	bool found = false;
+	int ret;
+
+	/* try the new flow engine first */
+	ret = ci_flow_dump(&pf->flow_engine_conf, flow, file, error);
+
+	/*
+	 * There are multiple possible situations here:
+	 *
+	 * - User requested to dump all flows
+	 * - User requested to dump a specific flow
+	 *
+	 * For the first case, we keep going because legacy engines might still
+	 * have flows we want to dump.
+	 *
+	 * For the second case, we only keep going if the flow we were asked to
+	 * dump was not found in the new engines.
+	 */
+	if (flow != NULL && ret == 0)
+		return 0;
 
  	TAILQ_FOREACH(p_flow, &pf->flow_list, node) {
  		size_t rule_size = 0;
@@ -3892,8 +3914,15 @@ i40e_flow_validate(struct rte_eth_dev *dev,
  		   const struct rte_flow_action actions[],
  		   struct rte_flow_error *error)
  {
+	struct i40e_pf *pf = dev->data->dev_private;
  	/* creates dummy context */
  	struct i40e_filter_ctx filter_ctx = {0};
+	int ret;
+
+	/* try the new engine first */
+	ret = ci_flow_validate(&pf->flow_engine_conf, attr, pattern, actions, error);
+	if (ret == 0)
+		return 0;
 
  	return i40e_flow_check(dev, attr, pattern, actions, &filter_ctx, error);
  }
@@ -3911,6 +3940,11 @@ i40e_flow_create(struct rte_eth_dev *dev,
  	struct i40e_fdir_info *fdir_info = &pf->fdir;
  	int ret;
 
+	/* try the new engine first */
+	flow = ci_flow_create(&pf->flow_engine_conf, attr, pattern, actions, error);
+	if (flow != NULL)
+		return flow;
+
  	ret = i40e_flow_check(dev, attr, pattern, actions, &filter_ctx, error);
  	if (ret < 0)
  		return NULL;
@@ -4017,6 +4051,11 @@ i40e_flow_destroy(struct rte_eth_dev *dev,
  	struct i40e_fdir_info *fdir_info = &pf->fdir;
  	int ret = 0;
 
+	/* try the new engine first */
+	ret = ci_flow_destroy(&pf->flow_engine_conf, flow, error);
+	if (ret == 0)
+		return 0;
+
  	switch (filter_type) {
  	case RTE_ETH_FILTER_ETHERTYPE:
  		ret = i40e_flow_destroy_ethertype_filter(pf,
@@ -4161,6 +4200,11 @@ i40e_flow_flush(struct rte_eth_dev *dev, struct rte_flow_error *error)
  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
  	int ret;
 
+	/* flush the new engine first */
+	ret = ci_flow_flush(&pf->flow_engine_conf, error);
+	if (ret != 0)
+		return ret;
+
  	ret = i40e_flow_flush_fdir_filter(pf);
  	if (ret) {
  		rte_flow_error_set(error, -ret,
@@ -4310,14 +4354,21 @@ i40e_flow_flush_tunnel_filter(struct i40e_pf *pf)
  }
 
  static int
-i40e_flow_query(struct rte_eth_dev *dev __rte_unused,
+i40e_flow_query(struct rte_eth_dev *dev,
  		struct rte_flow *flow,
  		const struct rte_flow_action *actions,
  		void *data, struct rte_flow_error *error)
  {
+	struct i40e_pf *pf = dev->data->dev_private;
  	struct i40e_rss_filter *rss_rule = (struct i40e_rss_filter *)flow->rule;
  	enum rte_filter_type filter_type = flow->filter_type;
  	struct rte_flow_action_rss *rss_conf = data;
+	int ret;
+
+	/* try the new engine first */
+	ret = ci_flow_query(&pf->flow_engine_conf, flow, actions, data, error);
+	if (ret == 0)
+		return 0;
 
  	if (!rss_rule) {
  		rte_flow_error_set(error, EINVAL,
drivers/net/intel/i40e/i40e_flow.h new file +12 -0
new file mode 100644
index 00000000000..c9588686619
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _I40E_FLOW_H_
+#define _I40E_FLOW_H_
+
+#include "../common/flow_engine.h"
+
+extern const struct ci_flow_engine_list i40e_flow_engine_list;
+
+#endif /* _I40E_FLOW_H_ */
Signature · 2 lines
Regards,
Vladimir
#93 lore raw

PATCH v3 14/19 net/i40e: reimplement ethertype parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for Ethertype.

The ethertype filter tracking has been moved completely inside the new engine, and the ethertype code is refactored to enabled that use case by decoupling hardware writes from software tracking.

---
 drivers/net/intel/i40e/i40e_ethdev.c         | 247 +------------
 drivers/net/intel/i40e/i40e_ethdev.h         |  30 +-
 drivers/net/intel/i40e/i40e_flow.c           | 298 +---------------
 drivers/net/intel/i40e/i40e_flow.h           |   4 +
 drivers/net/intel/i40e/i40e_flow_ethertype.c | 351 +++++++++++++++++++
 drivers/net/intel/i40e/meson.build           |   1 +
 6 files changed, 370 insertions(+), 561 deletions(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow_ethertype.c
drivers/net/intel/i40e/i40e_ethdev.c +2 -245
index a7fc82f67d6..b881b36749a 100644
@@ -392,12 +392,6 @@ static int i40e_set_default_mac_addr(struct rte_eth_dev *dev,
 
 static int i40e_dev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu);
 
-static int i40e_ethertype_filter_convert(
-	const struct rte_eth_ethertype_filter *input,
-	struct i40e_ethertype_filter *filter);
-static int i40e_sw_ethertype_filter_insert(struct i40e_pf *pf,
-				   struct i40e_ethertype_filter *filter);
-
 static int i40e_tunnel_filter_convert(
 	struct i40e_aqc_cloud_filters_element_bb *cld_filter,
 	struct i40e_tunnel_filter *tunnel_filter);
@@ -405,7 +399,6 @@ static int i40e_sw_tunnel_filter_insert(struct i40e_pf *pf,
 				struct i40e_tunnel_filter *tunnel_filter);
 static int i40e_cloud_filter_qinq_create(struct i40e_pf *pf);
 
-static void i40e_ethertype_filter_restore(struct i40e_pf *pf);
 static void i40e_tunnel_filter_restore(struct i40e_pf *pf);
 static void i40e_filter_restore(struct i40e_pf *pf);
 static void i40e_notify_all_vfs_link_status(struct rte_eth_dev *dev);
@@ -1003,51 +996,6 @@ config_floating_veb(struct rte_eth_dev *dev)
 #define I40E_L2_TAGS_S_TAG_SHIFT 1
 #define I40E_L2_TAGS_S_TAG_MASK I40E_MASK(0x1, I40E_L2_TAGS_S_TAG_SHIFT)
 
-static int
-i40e_init_ethtype_filter_list(struct rte_eth_dev *dev)
-{
-	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
-	struct i40e_ethertype_rule *ethertype_rule = &pf->ethertype;
-	char ethertype_hash_name[RTE_HASH_NAMESIZE];
-	int ret;
-
-	struct rte_hash_parameters ethertype_hash_params = {
-		.name = ethertype_hash_name,
-		.entries = I40E_MAX_ETHERTYPE_FILTER_NUM,
-		.key_len = sizeof(struct i40e_ethertype_filter_input),
-		.hash_func = rte_hash_crc,
-		.hash_func_init_val = 0,
-		.socket_id = rte_socket_id(),
-	};
-
-	/* Initialize ethertype filter rule list and hash */
-	TAILQ_INIT(&ethertype_rule->ethertype_list);
-	snprintf(ethertype_hash_name, RTE_HASH_NAMESIZE,
-		 "ethertype_%s", dev->device->name);
-	ethertype_rule->hash_table = rte_hash_create(&ethertype_hash_params);
-	if (!ethertype_rule->hash_table) {
-		PMD_INIT_LOG(ERR, "Failed to create ethertype hash table!");
-		return -EINVAL;
-	}
-	ethertype_rule->hash_map = rte_zmalloc("i40e_ethertype_hash_map",
-				       sizeof(struct i40e_ethertype_filter *) *
-				       I40E_MAX_ETHERTYPE_FILTER_NUM,
-				       0);
-	if (!ethertype_rule->hash_map) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for ethertype hash map!");
-		ret = -ENOMEM;
-		goto err_ethertype_hash_map_alloc;
-	}
-
-	return 0;
-
-err_ethertype_hash_map_alloc:
-	rte_hash_free(ethertype_rule->hash_table);
-
-	return ret;
-}
-
 static int
 i40e_init_tunnel_filter_list(struct rte_eth_dev *dev)
 {
@@ -1831,9 +1779,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	/* Initialize the filter invalidation configuration */
 	i40e_init_filter_invalidation(pf);
 
-	ret = i40e_init_ethtype_filter_list(dev);
-	if (ret < 0)
-		goto err_init_ethtype_filter_list;
 	ret = i40e_init_tunnel_filter_list(dev);
 	if (ret < 0)
 		goto err_init_tunnel_filter_list;
@@ -1865,9 +1810,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	rte_hash_free(pf->tunnel.hash_table);
 	rte_free(pf->tunnel.hash_map);
 err_init_tunnel_filter_list:
-	rte_hash_free(pf->ethertype.hash_table);
-	rte_free(pf->ethertype.hash_map);
-err_init_ethtype_filter_list:
 	rte_intr_callback_unregister(intr_handle,
 		i40e_dev_interrupt_handler, dev);
 	rte_free(dev->data->mac_addrs);
@@ -1890,24 +1832,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	return ret;
 }
 
-static void
-i40e_rm_ethtype_filter_list(struct i40e_pf *pf)
-{
-	struct i40e_ethertype_filter *p_ethertype;
-	struct i40e_ethertype_rule *ethertype_rule;
-
-	ethertype_rule = &pf->ethertype;
-	/* Remove all ethertype filter rules and hash */
-	rte_free(ethertype_rule->hash_map);
-	rte_hash_free(ethertype_rule->hash_table);
-
-	while ((p_ethertype = TAILQ_FIRST(&ethertype_rule->ethertype_list))) {
-		TAILQ_REMOVE(&ethertype_rule->ethertype_list,
-			     p_ethertype, rules);
-		rte_free(p_ethertype);
-	}
-}
-
 static void
 i40e_rm_tunnel_filter_list(struct i40e_pf *pf)
 {
@@ -2805,7 +2729,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 		i40e_msec_delay(500);
 	} while (retries++ < 5);
 
-	i40e_rm_ethtype_filter_list(pf);
 	i40e_rm_tunnel_filter_list(pf);
 	i40e_rm_fdir_filter_list(pf);
 
@@ -9909,130 +9832,16 @@ i40e_set_hash_inset(struct i40e_hw *hw, uint64_t input_set,
 	return 0;
 }
 
-/* Convert ethertype filter structure */
-static int
-i40e_ethertype_filter_convert(const struct rte_eth_ethertype_filter *input,
-			      struct i40e_ethertype_filter *filter)
-{
-	memcpy(&filter->input.mac_addr, &input->mac_addr,
-		RTE_ETHER_ADDR_LEN);
-	filter->input.ether_type = input->ether_type;
-	filter->flags = input->flags;
-	filter->queue = input->queue;
-
-	return 0;
-}
-
-/* Check if there exists the ethertype filter */
-struct i40e_ethertype_filter *
-i40e_sw_ethertype_filter_lookup(struct i40e_ethertype_rule *ethertype_rule,
-				const struct i40e_ethertype_filter_input *input)
-{
-	int ret;
-
-	ret = rte_hash_lookup(ethertype_rule->hash_table, (const void *)input);
-	if (ret < 0)
-		return NULL;
-
-	return ethertype_rule->hash_map[ret];
-}
-
-/* Add ethertype filter in SW list */
-static int
-i40e_sw_ethertype_filter_insert(struct i40e_pf *pf,
-				struct i40e_ethertype_filter *filter)
-{
-	struct i40e_ethertype_rule *rule = &pf->ethertype;
-	int ret;
-
-	ret = rte_hash_add_key(rule->hash_table, &filter->input);
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "Failed to insert ethertype filter"
-			    " to hash table %d!",
-			    ret);
-		return ret;
-	}
-	rule->hash_map[ret] = filter;
-
-	TAILQ_INSERT_TAIL(&rule->ethertype_list, filter, rules);
-
-	return 0;
-}
-
-/* Delete ethertype filter in SW list */
 int
-i40e_sw_ethertype_filter_del(struct i40e_pf *pf,
-			     struct i40e_ethertype_filter_input *input)
-{
-	struct i40e_ethertype_rule *rule = &pf->ethertype;
-	struct i40e_ethertype_filter *filter;
-	int ret;
-
-	ret = rte_hash_del_key(rule->hash_table, input);
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "Failed to delete ethertype filter"
-			    " to hash table %d!",
-			    ret);
-		return ret;
-	}
-	filter = rule->hash_map[ret];
-	rule->hash_map[ret] = NULL;
-
-	TAILQ_REMOVE(&rule->ethertype_list, filter, rules);
-	rte_free(filter);
-
-	return 0;
-}
-
-/*
- * Configure ethertype filter, which can director packet by filtering
- * with mac address and ether_type or only ether_type
- */
-int
-i40e_ethertype_filter_set(struct i40e_pf *pf,
+i40e_ethertype_filter_program(struct i40e_pf *pf,
 			struct rte_eth_ethertype_filter *filter,
 			bool add)
 {
 	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
-	struct i40e_ethertype_rule *ethertype_rule = &pf->ethertype;
-	struct i40e_ethertype_filter *ethertype_filter, *node;
-	struct i40e_ethertype_filter check_filter;
 	struct i40e_control_filter_stats stats;
 	uint16_t flags = 0;
 	int ret;
 
-	if (filter->queue >= pf->dev_data->nb_rx_queues) {
-		PMD_DRV_LOG(ERR, "Invalid queue ID");
-		return -EINVAL;
-	}
-	if (filter->ether_type == RTE_ETHER_TYPE_IPV4 ||
-		filter->ether_type == RTE_ETHER_TYPE_IPV6) {
-		PMD_DRV_LOG(ERR,
-			"unsupported ether_type(0x%04x) in control packet filter.",
-			filter->ether_type);
-		return -EINVAL;
-	}
-	if (filter->ether_type == RTE_ETHER_TYPE_VLAN)
-		PMD_DRV_LOG(WARNING,
-			"filter vlan ether_type in first tag is not supported.");
-
-	/* Check if there is the filter in SW list */
-	memset(&check_filter, 0, sizeof(check_filter));
-	i40e_ethertype_filter_convert(filter, &check_filter);
-	node = i40e_sw_ethertype_filter_lookup(ethertype_rule,
-					       &check_filter.input);
-	if (add && node) {
-		PMD_DRV_LOG(ERR, "Conflict with existing ethertype rules!");
-		return -EINVAL;
-	}
-
-	if (!add && !node) {
-		PMD_DRV_LOG(ERR, "There's no corresponding ethertype filter!");
-		return -EINVAL;
-	}
-
 	if (!(filter->flags & RTE_ETHTYPE_FLAGS_MAC))
 		flags |= I40E_AQC_ADD_CONTROL_PACKET_FLAGS_IGNORE_MAC;
 	if (filter->flags & RTE_ETHTYPE_FLAGS_DROP)
@@ -10053,25 +9862,7 @@ i40e_ethertype_filter_set(struct i40e_pf *pf,
 	if (ret < 0)
 		return -ENOSYS;
 
-	/* Add or delete a filter in SW list */
-	if (add) {
-		ethertype_filter = rte_zmalloc("ethertype_filter",
-				       sizeof(*ethertype_filter), 0);
-		if (ethertype_filter == NULL) {
-			PMD_DRV_LOG(ERR, "Failed to alloc memory.");
-			return -ENOMEM;
-		}
-
-		memcpy(ethertype_filter, &check_filter,
-			   sizeof(check_filter));
-		ret = i40e_sw_ethertype_filter_insert(pf, ethertype_filter);
-		if (ret < 0)
-			rte_free(ethertype_filter);
-	} else {
-		ret = i40e_sw_ethertype_filter_del(pf, &node->input);
-	}
-
-	return ret;
+	return 0;
 }
 
 static int
@@ -11642,39 +11433,6 @@ i40e_dev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu __rte_unused)
 	return 0;
 }
 
-/* Restore ethertype filter */
-static void
-i40e_ethertype_filter_restore(struct i40e_pf *pf)
-{
-	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
-	struct i40e_ethertype_filter_list
-		*ethertype_list = &pf->ethertype.ethertype_list;
-	struct i40e_ethertype_filter *f;
-	struct i40e_control_filter_stats stats;
-	uint16_t flags;
-
-	TAILQ_FOREACH(f, ethertype_list, rules) {
-		flags = 0;
-		if (!(f->flags & RTE_ETHTYPE_FLAGS_MAC))
-			flags |= I40E_AQC_ADD_CONTROL_PACKET_FLAGS_IGNORE_MAC;
-		if (f->flags & RTE_ETHTYPE_FLAGS_DROP)
-			flags |= I40E_AQC_ADD_CONTROL_PACKET_FLAGS_DROP;
-		flags |= I40E_AQC_ADD_CONTROL_PACKET_FLAGS_TO_QUEUE;
-
-		memset(&stats, 0, sizeof(stats));
-		i40e_aq_add_rem_control_packet_filter(hw,
-					    f->input.mac_addr.addr_bytes,
-					    f->input.ether_type,
-					    flags, pf->main_vsi->seid,
-					    f->queue, 1, &stats, NULL);
-	}
-	PMD_DRV_LOG(INFO, "Ethertype filter:"
-		    " mac_etype_used = %u, etype_used = %u,"
-		    " mac_etype_free = %u, etype_free = %u",
-		    stats.mac_etype_used, stats.etype_used,
-		    stats.mac_etype_free, stats.etype_free);
-}
-
 /* Restore tunnel filter */
 static void
 i40e_tunnel_filter_restore(struct i40e_pf *pf)
@@ -11733,7 +11491,6 @@ i40e_tunnel_filter_restore(struct i40e_pf *pf)
 static void
 i40e_filter_restore(struct i40e_pf *pf)
 {
-	i40e_ethertype_filter_restore(pf);
 	i40e_tunnel_filter_restore(pf);
 	i40e_fdir_filter_restore(pf);
 	(void)i40e_hash_filter_restore(pf);
drivers/net/intel/i40e/i40e_ethdev.h +1 -29
index 16b67268f7f..f674f7995d8 100644
@@ -805,27 +805,6 @@ struct i40e_fdir_info {
 /* Ethertype filter number HW supports */
 #define I40E_MAX_ETHERTYPE_FILTER_NUM 768
 
-/* Ethertype filter struct */
-struct i40e_ethertype_filter_input {
-	struct rte_ether_addr mac_addr;   /* Mac address to match */
-	uint16_t ether_type;          /* Ether type to match */
-};
-
-struct i40e_ethertype_filter {
-	TAILQ_ENTRY(i40e_ethertype_filter) rules;
-	struct i40e_ethertype_filter_input input;
-	uint16_t flags;              /* Flags from RTE_ETHTYPE_FLAGS_* */
-	uint16_t queue;              /* Queue assigned to when match */
-};
-
-TAILQ_HEAD(i40e_ethertype_filter_list, i40e_ethertype_filter);
-
-struct i40e_ethertype_rule {
-	struct i40e_ethertype_filter_list ethertype_list;
-	struct i40e_ethertype_filter  **hash_map;
-	struct rte_hash *hash_table;
-};
-
 /* queue region info */
 struct i40e_queue_region_info {
 	/* the region id for this configuration */
@@ -1176,7 +1155,6 @@ struct i40e_pf {
 	struct i40e_vmdq_info *vmdq;
 
 	struct i40e_fdir_info fdir; /* flow director info */
-	struct i40e_ethertype_rule ethertype; /* Ethertype filter rule */
 	struct i40e_tunnel_rule tunnel; /* Tunnel filter rule */
 	struct i40e_rss_conf_list rss_config_list; /* RSS rule list */
 	struct i40e_queue_regions queue_region; /* queue region info */
@@ -1325,7 +1303,6 @@ extern const struct rte_flow_ops i40e_flow_ops;
 
 struct i40e_filter_ctx {
 	union {
-		struct rte_eth_ethertype_filter ethertype_filter;
 		struct i40e_fdir_filter_conf fdir_filter;
 		struct i40e_tunnel_filter_conf consistent_tunnel_filter;
 		struct i40e_rte_flow_rss_conf rss_conf;
@@ -1407,11 +1384,6 @@ int i40e_rx_burst_mode_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			   struct rte_eth_burst_mode *mode);
 int i40e_tx_burst_mode_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			   struct rte_eth_burst_mode *mode);
-struct i40e_ethertype_filter *
-i40e_sw_ethertype_filter_lookup(struct i40e_ethertype_rule *ethertype_rule,
-			const struct i40e_ethertype_filter_input *input);
-int i40e_sw_ethertype_filter_del(struct i40e_pf *pf,
-				 struct i40e_ethertype_filter_input *input);
 int i40e_sw_fdir_filter_del(struct i40e_pf *pf,
 			    struct i40e_fdir_input *input);
 struct i40e_tunnel_filter *
@@ -1420,7 +1392,7 @@ i40e_sw_tunnel_filter_lookup(struct i40e_tunnel_rule *tunnel_rule,
 int i40e_sw_tunnel_filter_del(struct i40e_pf *pf,
 			      struct i40e_tunnel_filter_input *input);
 uint64_t i40e_get_default_input_set(uint16_t pctype);
-int i40e_ethertype_filter_set(struct i40e_pf *pf,
+int i40e_ethertype_filter_program(struct i40e_pf *pf,
 			      struct rte_eth_ethertype_filter *filter,
 			      bool add);
 struct rte_flow *
drivers/net/intel/i40e/i40e_flow.c +11 -287

398 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h +4 -0
index c9588686619..11d13a76fe9 100644
@@ -7,6 +7,10 @@
 
 #include "../common/flow_engine.h"
 
+int i40e_get_outer_vlan(struct i40e_pf *pf, uint16_t *tpid);
+
 extern const struct ci_flow_engine_list i40e_flow_engine_list;
 
+extern const struct ci_flow_engine i40e_flow_engine_ethertype;
+
 #endif /* _I40E_FLOW_H_ */
drivers/net/intel/i40e/i40e_flow_ethertype.c new file +351 -0

351 lines not on this page · raw message

drivers/net/intel/i40e/meson.build +1 -0
index 3229233f501..ddc97f9b3bb 100644
@@ -33,6 +33,7 @@ sources += files(
         'i40e_pf.c',
         'i40e_fdir.c',
         'i40e_flow.c',
+        'i40e_flow_ethertype.c',
         'i40e_tm.c',
         'i40e_hash.c',
         'i40e_vf_representor.c',
Signature · 1 line
2.52.0
Acked-by on v3 14/19
#94 lore raw

Re: [PATCH v3 14/19] net/i40e: reimplement ethertype parser

Quoted 9 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for Ethertype.

The ethertype filter tracking has been moved completely inside the new engine, and the ethertype code is refactored to enabled that use case by decoupling hardware writes from software tracking.

---

<snip>

Signature · 2 lines
Regards,
Vladimir
#95 lore raw

PATCH v3 15/19 net/i40e: refactor FDIR engine infrastructure

In reply to Anatoly Burakov

Currently, there are multiple problems with how i40e flow directory feature is implemented, both in terms of how it works with rte_flow, and how it integrates with the PMD-specific packet template API.

For one, these two subsystems, while using shared infrastructure, do not really interact or cooperate, and are built on top of special cases in FDIR path. More specifically, the packet template code does not store its packet in the hash map, and has a different hashing scheme, yet it still registers itself in FDIR flow list, hash table, and hash map. This list is then used by dev_start to restore FDIR filters that user has inserted into the list. These filters, as written, cannot be reprogrammed that way because the information a filter restore function would need is lost on insert (the packet pointer is not added to the hash map).

Another issue is that while rte_flow FDIR code does lazy FDIR init on first added flow, the packet template API does not, even though it too relies on the same hardware feature, nor does it ever do teardown on last FDIR flow.

Yet another issue is how the "filter restore" code itself is implemented, namely that currently it simply does not work. When doing filter restore, the driver will walk every FDIR filter stored in the TAILQ, and attempt to program it. However, inside the program function, there is a deduplication check (to see if flow being installed is already present in the flow hash table), which fails because the flows we are programming come from the same list that is being checked for deduplication, which makes the entire filter restore a no-op.

The FDIR filter programming code itself also has a number of readability problems as well as being otherwise hard to use - SW bookkeeping, validation, and flow programming is interspersed within the code, and it is difficult to reason about what happens when the code is called from this or that context.

So, this refactor does the following:

- Reorganize FDIR internals to track packet templates and rte_flow FDIR flows separately
- Refactor FDIR init/teardown to always happen on first/last rule, so that whichever API happens to call FDIR first, the state is consistent
- Rework the FDIR code to disentangle FDIR flow rule programming, Flex PIT checks, SW bookkeeping, etc. from each other
- Remove both the rte_flow FDIR TAILQ and the hash map (filter array) structure, because they are redundant (information about the flow is already stored in the rte_flow flow list, and hash_map structure only stored pointers to data we also have in that same list)
- Fix FDIR filter restore to replay all configuration correctly, as well as re-init the FDIR queue enablement tracking
- Rework the internal FDIR global state data structure to make a little more sense by grouping things that belong together into structures

Additionally, there was a delay mechanism at flow director rule program time, as when programming a rule we might not know if it's actually possible to install the rule, because space for the rules may come either from our own pool, or it may come from a pool that is shared with other VSI's. However, it only makes sense to wait on rule create (i.e. when it is programmed into the hardware for the first time), but not when we are replaying or removing these rules. So, adjust the waiting mechanism to only wait on FDIR rule creation.

---
 drivers/net/intel/i40e/i40e_ethdev.c  |  168 +---
 drivers/net/intel/i40e/i40e_ethdev.h  |  126 ++-
 drivers/net/intel/i40e/i40e_fdir.c    | 1134 +++++++++++++++++--------
 drivers/net/intel/i40e/i40e_flow.c    |  131 +--
 drivers/net/intel/i40e/rte_pmd_i40e.c |    4 +-
 5 files changed, 928 insertions(+), 635 deletions(-)
drivers/net/intel/i40e/i40e_ethdev.c +13 -155
index b881b36749a..9272c15e468 100644
@@ -1041,129 +1041,6 @@ i40e_init_tunnel_filter_list(struct rte_eth_dev *dev)
 	return ret;
 }
 
-static int
-i40e_init_fdir_filter_list(struct rte_eth_dev *dev)
-{
-	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
-	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
-	struct i40e_fdir_info *fdir_info = &pf->fdir;
-	char fdir_hash_name[RTE_HASH_NAMESIZE];
-	uint32_t alloc = hw->func_caps.fd_filters_guaranteed;
-	uint32_t best = hw->func_caps.fd_filters_best_effort;
-	enum i40e_filter_pctype pctype;
-	struct rte_bitmap *bmp = NULL;
-	uint32_t bmp_size;
-	void *mem = NULL;
-	uint32_t i = 0;
-	int ret;
-
-	struct rte_hash_parameters fdir_hash_params = {
-		.name = fdir_hash_name,
-		.entries = I40E_MAX_FDIR_FILTER_NUM,
-		.key_len = sizeof(struct i40e_fdir_input),
-		.hash_func = rte_hash_crc,
-		.hash_func_init_val = 0,
-		.socket_id = rte_socket_id(),
-	};
-
-	/* Initialize flow director filter rule list and hash */
-	TAILQ_INIT(&fdir_info->fdir_list);
-	snprintf(fdir_hash_name, RTE_HASH_NAMESIZE,
-		 "fdir_%s", dev->device->name);
-	fdir_info->hash_table = rte_hash_create(&fdir_hash_params);
-	if (!fdir_info->hash_table) {
-		PMD_INIT_LOG(ERR, "Failed to create fdir hash table!");
-		return -EINVAL;
-	}
-
-	fdir_info->hash_map = rte_zmalloc("i40e_fdir_hash_map",
-					  sizeof(struct i40e_fdir_filter *) *
-					  I40E_MAX_FDIR_FILTER_NUM,
-					  0);
-	if (!fdir_info->hash_map) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for fdir hash map!");
-		ret = -ENOMEM;
-		goto err_fdir_hash_map_alloc;
-	}
-
-	fdir_info->fdir_filter_array = rte_zmalloc("fdir_filter",
-			sizeof(struct i40e_fdir_filter) *
-			I40E_MAX_FDIR_FILTER_NUM,
-			0);
-
-	if (!fdir_info->fdir_filter_array) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for fdir filter array!");
-		ret = -ENOMEM;
-		goto err_fdir_filter_array_alloc;
-	}
-
-	for (pctype = I40E_FILTER_PCTYPE_NONF_IPV4_UDP;
-	     pctype <= I40E_FILTER_PCTYPE_L2_PAYLOAD; pctype++)
-		pf->fdir.flow_count[pctype] = 0;
-
-	fdir_info->fdir_space_size = alloc + best;
-	fdir_info->fdir_actual_cnt = 0;
-	fdir_info->fdir_guarantee_total_space = alloc;
-	fdir_info->fdir_guarantee_free_space =
-		fdir_info->fdir_guarantee_total_space;
-
-	PMD_DRV_LOG(INFO, "FDIR guarantee space: %u, best_effort space %u.", alloc, best);
-
-	fdir_info->fdir_flow_pool.pool =
-			rte_zmalloc("i40e_fdir_entry",
-				sizeof(struct i40e_fdir_entry) *
-				fdir_info->fdir_space_size,
-				0);
-
-	if (!fdir_info->fdir_flow_pool.pool) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for bitmap flow!");
-		ret = -ENOMEM;
-		goto err_fdir_bitmap_flow_alloc;
-	}
-
-	for (i = 0; i < fdir_info->fdir_space_size; i++)
-		fdir_info->fdir_flow_pool.pool[i].idx = i;
-
-	bmp_size =
-		rte_bitmap_get_memory_footprint(fdir_info->fdir_space_size);
-	mem = rte_zmalloc("fdir_bmap", bmp_size, RTE_CACHE_LINE_SIZE);
-	if (mem == NULL) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for fdir bitmap!");
-		ret = -ENOMEM;
-		goto err_fdir_mem_alloc;
-	}
-	bmp = rte_bitmap_init(fdir_info->fdir_space_size, mem, bmp_size);
-	if (bmp == NULL) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to initialization fdir bitmap!");
-		ret = -ENOMEM;
-		goto err_fdir_bmp_alloc;
-	}
-	for (i = 0; i < fdir_info->fdir_space_size; i++)
-		rte_bitmap_set(bmp, i);
-
-	fdir_info->fdir_flow_pool.bitmap = bmp;
-
-	return 0;
-
-err_fdir_bmp_alloc:
-	rte_free(mem);
-err_fdir_mem_alloc:
-	rte_free(fdir_info->fdir_flow_pool.pool);
-err_fdir_bitmap_flow_alloc:
-	rte_free(fdir_info->fdir_filter_array);
-err_fdir_filter_array_alloc:
-	rte_free(fdir_info->hash_map);
-err_fdir_hash_map_alloc:
-	rte_hash_free(fdir_info->hash_table);
-
-	return ret;
-}
-
 static void
 i40e_init_customized_info(struct i40e_pf *pf)
 {
@@ -1782,9 +1659,9 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	ret = i40e_init_tunnel_filter_list(dev);
 	if (ret < 0)
 		goto err_init_tunnel_filter_list;
-	ret = i40e_init_fdir_filter_list(dev);
+	ret = i40e_fdir_flow_store_init(dev);
 	if (ret < 0)
-		goto err_init_fdir_filter_list;
+		goto err_init_fdir_flow_store;
 
 	/* initialize flow engine configuration */
 	ret = ci_flow_engine_conf_init(&pf->flow_engine_conf,
@@ -1801,12 +1678,8 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	return 0;
 
 err_flow_engine_conf_init:
-	rte_free(pf->fdir.fdir_flow_pool.bitmap);
-	rte_free(pf->fdir.fdir_flow_pool.pool);
-	rte_free(pf->fdir.fdir_filter_array);
-	rte_free(pf->fdir.hash_map);
-	rte_hash_free(pf->fdir.hash_table);
-err_init_fdir_filter_list:
+	i40e_fdir_flow_store_free(&pf->fdir);
+err_init_fdir_flow_store:
 	rte_hash_free(pf->tunnel.hash_table);
 	rte_free(pf->tunnel.hash_map);
 err_init_tunnel_filter_list:
@@ -1849,32 +1722,13 @@ i40e_rm_tunnel_filter_list(struct i40e_pf *pf)
 	}
 }
 
-static void
-i40e_rm_fdir_filter_list(struct i40e_pf *pf)
-{
-	struct i40e_fdir_filter *p_fdir;
-	struct i40e_fdir_info *fdir_info;
-
-	fdir_info = &pf->fdir;
-
-	/* Remove all flow director rules */
-	while ((p_fdir = TAILQ_FIRST(&fdir_info->fdir_list)))
-		TAILQ_REMOVE(&fdir_info->fdir_list, p_fdir, rules);
-}
-
 static void
 i40e_fdir_memory_cleanup(struct i40e_pf *pf)
 {
-	struct i40e_fdir_info *fdir_info;
+	struct i40e_fdir_info *fdir_info = &pf->fdir;
 
-	fdir_info = &pf->fdir;
-
-	/* flow director memory cleanup */
-	rte_free(fdir_info->hash_map);
-	rte_hash_free(fdir_info->hash_table);
-	rte_free(fdir_info->fdir_flow_pool.bitmap);
-	rte_free(fdir_info->fdir_flow_pool.pool);
-	rte_free(fdir_info->fdir_filter_array);
+	i40e_fdir_flow_store_free(fdir_info);
+	i40e_fdir_tmpl_store_free(fdir_info);
 }
 
 void i40e_flex_payload_reg_set_default(struct i40e_hw *hw)
@@ -2730,7 +2584,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 	} while (retries++ < 5);
 
 	i40e_rm_tunnel_filter_list(pf);
-	i40e_rm_fdir_filter_list(pf);
 
 	/* Remove all flows */
 	while ((p_flow = TAILQ_FIRST(&pf->flow_list))) {
@@ -9776,7 +9629,12 @@ i40e_filter_input_set_init(struct i40e_pf *pf)
 		/* store the default input set */
 		if (!pf->support_multi_driver)
 			pf->hash_input_set[pctype] = input_set;
-		pf->fdir.input_set[pctype] = input_set;
+		{
+			struct i40e_fdir_pctype_state *state =
+				&pf->fdir.flows.pctype[pctype];
+
+			state->input_set = input_set;
+		}
 	}
 }
 
drivers/net/intel/i40e/i40e_ethdev.h +87 -39

183 lines not on this page · raw message

drivers/net/intel/i40e/i40e_fdir.c +783 -351

1,446 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.c +43 -88

205 lines not on this page · raw message

drivers/net/intel/i40e/rte_pmd_i40e.c +2 -2
index 2e7943ef8b3..5b9234f8e53 100644
@@ -3044,7 +3044,7 @@ int rte_pmd_i40e_flow_add_del_packet_template(
 			uint8_t add)
 {
 	struct rte_eth_dev *dev = &rte_eth_devices[port];
-	struct i40e_fdir_filter_conf filter_conf;
+	struct i40e_fdir_filter filter_conf;
 
 	RTE_ETH_VALID_PORTID_OR_ERR_RET(port, -ENODEV);
 
@@ -3068,7 +3068,7 @@ int rte_pmd_i40e_flow_add_del_packet_template(
 		(enum i40e_fdir_status)conf->action.report_status;
 	filter_conf.action.flex_off = conf->action.flex_off;
 
-	return i40e_flow_add_del_fdir_filter(dev, &filter_conf, add);
+	return i40e_fdir_tmpl_add_del(dev, &filter_conf, add);
 }
 
 RTE_EXPORT_SYMBOL(rte_pmd_i40e_inset_get)
Signature · 1 line
2.52.0
#96 lore raw

Re: [PATCH v3 15/19] net/i40e: refactor FDIR engine infrastructure

Quoted 33 lines · Anatoly Burakov

Currently, there are multiple problems with how i40e flow directory feature is implemented, both in terms of how it works with rte_flow, and how it integrates with the PMD-specific packet template API.

For one, these two subsystems, while using shared infrastructure, do not really interact or cooperate, and are built on top of special cases in FDIR path. More specifically, the packet template code does not store its packet in the hash map, and has a different hashing scheme, yet it still registers itself in FDIR flow list, hash table, and hash map. This list is then used by dev_start to restore FDIR filters that user has inserted into the list. These filters, as written, cannot be reprogrammed that way because the information a filter restore function would need is lost on insert (the packet pointer is not added to the hash map).

Another issue is that while rte_flow FDIR code does lazy FDIR init on first added flow, the packet template API does not, even though it too relies on the same hardware feature, nor does it ever do teardown on last FDIR flow.

Yet another issue is how the "filter restore" code itself is implemented, namely that currently it simply does not work. When doing filter restore, the driver will walk every FDIR filter stored in the TAILQ, and attempt to program it. However, inside the program function, there is a deduplication check (to see if flow being installed is already present in the flow hash table), which fails because the flows we are programming come from the same list that is being checked for deduplication, which makes the entire filter restore a no-op.

The FDIR filter programming code itself also has a number of readability problems as well as being otherwise hard to use - SW bookkeeping, validation, and flow programming is interspersed within the code, and it is difficult to reason about what happens when the code is called from this or that context.

Quoted 6 lines

So, this refactor does the following:

- Reorganize FDIR internals to track packet templates and rte_flow FDIR flows separately
- Refactor FDIR init/teardown to always happen on first/last rule, so that whichever API happens to call FDIR first, the state is consistent

nit: it seems you've moved it to the next patch

Quoted 22 lines

- Rework the FDIR code to disentangle FDIR flow rule programming, Flex PIT checks, SW bookkeeping, etc. from each other
- Remove both the rte_flow FDIR TAILQ and the hash map (filter array) structure, because they are redundant (information about the flow is already stored in the rte_flow flow list, and hash_map structure only stored pointers to data we also have in that same list)
- Fix FDIR filter restore to replay all configuration correctly, as well as re-init the FDIR queue enablement tracking
- Rework the internal FDIR global state data structure to make a little more sense by grouping things that belong together into structures

Additionally, there was a delay mechanism at flow director rule program time, as when programming a rule we might not know if it's actually possible to install the rule, because space for the rules may come either from our own pool, or it may come from a pool that is shared with other VSI's. However, it only makes sense to wait on rule create (i.e. when it is programmed into the hardware for the first time), but not when we are replaying or removing these rules. So, adjust the waiting mechanism to only wait on FDIR rule creation.

---

<snip>

Quoted 6 lines
drivers/net/intel/i40e/i40e_flow.c +0 -2
@@ -3860,16 +3851,15 @@ i40e_flow_destroy(struct rte_eth_dev *dev,
  		ret = i40e_flow_destroy_tunnel_filter(pf,
  			      (struct i40e_tunnel_filter *)flow->rule);
  		break;
-	case RTE_ETH_FILTER_FDIR:
-		ret = i40e_flow_add_del_fdir_filter(dev,

drivers/net/intel/i40e/i40e_flow.c i40e_flow_destroy(struct rte_eth_dev *dev, · line 3857

Quoted 10 lines
drivers/net/intel/i40e/i40e_flow.c +3 -6
-				&((struct i40e_fdir_filter *)flow->rule)->fdir,
-				0);
+	case RTE_ETH_FILTER_FDIR: {
+		struct i40e_fdir_filter *node = flow->rule;
 
-		/* If the last flow is destroyed, disable fdir. */
-		if (!ret && TAILQ_EMPTY(&pf->fdir.fdir_list)) {
-			i40e_fdir_rx_proc_enable(dev, 0);
-		}
+		ret = i40e_fdir_filter_program(dev, node, 0, false);

previous implementation set wait_status depending on fdir_info->fdir_invalprio. Do we really need to wait here?

Quoted 7 lines
drivers/net/intel/i40e/i40e_flow.c +4 -0
+		if (ret)
+			break;
+		ret = i40e_fdir_filter_unregister(dev, node);
  		break;
+	}
  	case RTE_ETH_FILTER_HASH:
  		ret = i40e_hash_filter_destroy(pf, flow->rule);

<snip>

Signature · 2 lines
Regards,
Vladimir
#97 lore raw

Re: [PATCH v3 15/19] net/i40e: refactor FDIR engine infrastructure

Quoted 71 lines · Vladimir
Quoted 33 lines · Anatoly Burakov

Currently, there are multiple problems with how i40e flow directory feature is implemented, both in terms of how it works with rte_flow, and how it integrates with the PMD-specific packet template API.

For one, these two subsystems, while using shared infrastructure, do not really interact or cooperate, and are built on top of special cases in FDIR path. More specifically, the packet template code does not store its packet in the hash map, and has a different hashing scheme, yet it still registers itself in FDIR flow list, hash table, and hash map. This list is then used by dev_start to restore FDIR filters that user has inserted into the list. These filters, as written, cannot be reprogrammed that way because the information a filter restore function would need is lost on insert (the packet pointer is not added to the hash map).

Another issue is that while rte_flow FDIR code does lazy FDIR init on first added flow, the packet template API does not, even though it too relies on the same hardware feature, nor does it ever do teardown on last FDIR flow.

Yet another issue is how the "filter restore" code itself is implemented, namely that currently it simply does not work. When doing filter restore, the driver will walk every FDIR filter stored in the TAILQ, and attempt to program it. However, inside the program function, there is a deduplication check (to see if flow being installed is already present in the flow hash table), which fails because the flows we are programming come from the same list that is being checked for deduplication, which makes the entire filter restore a no-op.

The FDIR filter programming code itself also has a number of readability problems as well as being otherwise hard to use - SW bookkeeping, validation, and flow programming is interspersed within the code, and it is difficult to reason about what happens when the code is called from this or that context.

Quoted 7 lines

So, this refactor does the following:

- Reorganize FDIR internals to track packet templates and rte_flow FDIR flows separately
- Refactor FDIR init/teardown to always happen on first/last rule, so that
   whichever API happens to call FDIR first, the state is consistent

nit: it seems you've moved it to the next patch

Quoted 24 lines

- Rework the FDIR code to disentangle FDIR flow rule programming, Flex PIT
   checks, SW bookkeeping, etc. from each other
- Remove both the rte_flow FDIR TAILQ and the hash map (filter array) structure, because they are redundant (information about the flow is already stored in the rte_flow flow list, and hash_map structure only stored pointers to data we also have in that same list)
- Fix FDIR filter restore to replay all configuration correctly, as well as
   re-init the FDIR queue enablement tracking
- Rework the internal FDIR global state data structure to make a little more sense by grouping things that belong together into structures

Additionally, there was a delay mechanism at flow director rule program time, as when programming a rule we might not know if it's actually possible to install the rule, because space for the rules may come either from our own pool, or it may come from a pool that is shared with other VSI's. However, it only makes sense to wait on rule create (i.e. when it is programmed into the hardware for the first time), but not when we are replaying or removing these rules. So, adjust the waiting mechanism to only wait on FDIR rule creation.

---

<snip>

Quoted 17 lines
Quoted 5 lines
drivers/net/intel/i40e/i40e_flow.c +0 -1
@@ -3860,16 +3851,15 @@ i40e_flow_destroy(struct rte_eth_dev *dev,
           ret = i40e_flow_destroy_tunnel_filter(pf,
                     (struct i40e_tunnel_filter *)flow->rule);
           break;
-    case RTE_ETH_FILTER_FDIR:

drivers/net/intel/i40e/i40e_flow.c i40e_flow_destroy(struct rte_eth_dev *dev, · line 3856

Quoted 10 lines
drivers/net/intel/i40e/i40e_flow.c +3 -7
-        ret = i40e_flow_add_del_fdir_filter(dev,
-                &((struct i40e_fdir_filter *)flow->rule)->fdir,
-                0);
+    case RTE_ETH_FILTER_FDIR: {
+        struct i40e_fdir_filter *node = flow->rule;
-        /* If the last flow is destroyed, disable fdir. */
-        if (!ret && TAILQ_EMPTY(&pf->fdir.fdir_list)) {
-            i40e_fdir_rx_proc_enable(dev, 0);
-        }
+        ret = i40e_fdir_filter_program(dev, node, 0, false);

previous implementation set wait_status depending on fdir_info-

Quoted 1 line

fdir_invalprio. Do we really need to wait here?

We do not, and wait is false here. We used to set wait_status here depending on fdir_info->fdir_invalprio, but in actuality we can avoid waiting here (see commit message).

Signature · 2 lines
Thanks,
Anatoly
#98 lore raw

PATCH v3 16/19 net/i40e: reimplement FDIR parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for flow director.

As a result of transitioning to more formalized validation, some checks have become more stringent. In particular, some protocols (such as SCTP) were previously accepting non-zero or invalid masks and either ignoring them or misinterpreting them to mean "fully masked". This has now been corrected.

The FDIR engine in i40e has also relied on a custom memory allocation scheme for fdir flows - also migrated to the new infrastructure.

Finally, some of the FDIR infrastructure has been migrated into the new flow engine, so that the engine itself is managing its own allocator and its own flow deduplication scheme.

---
 drivers/net/intel/i40e/i40e_ethdev.c    |   11 +-
 drivers/net/intel/i40e/i40e_ethdev.h    |   64 +-
 drivers/net/intel/i40e/i40e_fdir.c      |  351 +---
 drivers/net/intel/i40e/i40e_flow.c      | 1916 +--------------------
 drivers/net/intel/i40e/i40e_flow.h      |    5 +
 drivers/net/intel/i40e/i40e_flow_fdir.c | 2056 +++++++++++++++++++++++
 drivers/net/intel/i40e/meson.build      |    1 +
 7 files changed, 2126 insertions(+), 2278 deletions(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow_fdir.c
drivers/net/intel/i40e/i40e_ethdev.c +2 -9
index 9272c15e468..39182aabc39 100644
@@ -1659,9 +1659,7 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	ret = i40e_init_tunnel_filter_list(dev);
 	if (ret < 0)
 		goto err_init_tunnel_filter_list;
-	ret = i40e_fdir_flow_store_init(dev);
-	if (ret < 0)
-		goto err_init_fdir_flow_store;
+	i40e_fdir_flow_store_init(dev);
 
 	/* initialize flow engine configuration */
 	ret = ci_flow_engine_conf_init(&pf->flow_engine_conf,
@@ -1678,8 +1676,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	return 0;
 
 err_flow_engine_conf_init:
-	i40e_fdir_flow_store_free(&pf->fdir);
-err_init_fdir_flow_store:
 	rte_hash_free(pf->tunnel.hash_table);
 	rte_free(pf->tunnel.hash_map);
 err_init_tunnel_filter_list:
@@ -1727,7 +1723,6 @@ i40e_fdir_memory_cleanup(struct i40e_pf *pf)
 {
 	struct i40e_fdir_info *fdir_info = &pf->fdir;
 
-	i40e_fdir_flow_store_free(fdir_info);
 	i40e_fdir_tmpl_store_free(fdir_info);
 }
 
@@ -2588,9 +2583,7 @@ i40e_dev_close(struct rte_eth_dev *dev)
 	/* Remove all flows */
 	while ((p_flow = TAILQ_FIRST(&pf->flow_list))) {
 		TAILQ_REMOVE(&pf->flow_list, p_flow, node);
-		/* Do not free FDIR flows since they are static allocated */
-		if (p_flow->filter_type != RTE_ETH_FILTER_FDIR)
-			rte_free(p_flow);
+		rte_free(p_flow);
 	}
 
 	/* release the fdir static allocated memory */
drivers/net/intel/i40e/i40e_ethdev.h +22 -42

115 lines not on this page · raw message

drivers/net/intel/i40e/i40e_fdir.c +30 -321

526 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.c +10 -1906

2,047 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h +5 -0
index 11d13a76fe9..6823dbef332 100644
@@ -8,9 +8,14 @@
 #include "../common/flow_engine.h"
 
 int i40e_get_outer_vlan(struct i40e_pf *pf, uint16_t *tpid);
+uint8_t
+i40e_flow_fdir_get_pctype_value(struct i40e_pf *pf,
+		enum rte_flow_item_type item_type,
+		struct i40e_fdir_filter *filter);
 
 extern const struct ci_flow_engine_list i40e_flow_engine_list;
 
 extern const struct ci_flow_engine i40e_flow_engine_ethertype;
+extern const struct ci_flow_engine i40e_flow_engine_fdir;
 
 #endif /* _I40E_FLOW_H_ */
drivers/net/intel/i40e/i40e_flow_fdir.c new file +2056 -0

263 lines not on this page · raw message

drivers/net/intel/i40e/meson.build +1 -0
index ddc97f9b3bb..c07257cb807 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.52.0

Part of the diff is not shown here. View the raw message.

Acked-by on v3 16/19
#99 lore raw

Re: [PATCH v3 16/19] net/i40e: reimplement FDIR parser

Quoted 18 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for flow director.

As a result of transitioning to more formalized validation, some checks have become more stringent. In particular, some protocols (such as SCTP) were previously accepting non-zero or invalid masks and either ignoring them or misinterpreting them to mean "fully masked". This has now been corrected.

The FDIR engine in i40e has also relied on a custom memory allocation scheme for fdir flows - also migrated to the new infrastructure.

Finally, some of the FDIR infrastructure has been migrated into the new flow engine, so that the engine itself is managing its own allocator and its own flow deduplication scheme.

---

<snip>

Signature · 2 lines
Regards,
Vladimir
#100 lore raw

PATCH v3 17/19 net/i40e: reimplement tunnel parsers

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for tunnel filters: QinQ, VXLAN, NVGRE, MPLS, GTP, and L4.

As a result of transitioning to more formalized validation, some checks have become more stringent:

- VLAN TCI mask is now required to be fully masked (all-ones); previously the mask was only checked for eth_proto and any non-zero vlan_tci mask value was silently accepted

In addition to using the new graph infrastructure, some of the checks were made more stringent and/or more correct. In particular:

- old code did not check for whether fields other than ports are masked (they are now rejected)
- old code did not check for whether src/ports are fully masked (masks other than full are now rejected)
- old code used spec to decide which port to copy (as a result, it was not possible to match port 0 - this is now allowed)

Tunnel engine now also share a refcounted global state, and track all flows and do deduplication inside the engine.

---
 drivers/net/intel/i40e/i40e_ethdev.c      |  554 +++----
 drivers/net/intel/i40e/i40e_ethdev.h      |   53 +-
 drivers/net/intel/i40e/i40e_flow.c        | 1702 +--------------------
 drivers/net/intel/i40e/i40e_flow.h        |    6 +
 drivers/net/intel/i40e/i40e_flow_tunnel.c | 1590 +++++++++++++++++++
 drivers/net/intel/i40e/meson.build        |    1 +
 6 files changed, 1834 insertions(+), 2072 deletions(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow_tunnel.c
drivers/net/intel/i40e/i40e_ethdev.c +215 -339
index 39182aabc39..fb03eaba7dc 100644
@@ -392,14 +392,8 @@ static int i40e_set_default_mac_addr(struct rte_eth_dev *dev,
 
 static int i40e_dev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu);
 
-static int i40e_tunnel_filter_convert(
-	struct i40e_aqc_cloud_filters_element_bb *cld_filter,
-	struct i40e_tunnel_filter *tunnel_filter);
-static int i40e_sw_tunnel_filter_insert(struct i40e_pf *pf,
-				struct i40e_tunnel_filter *tunnel_filter);
 static int i40e_cloud_filter_qinq_create(struct i40e_pf *pf);
 
-static void i40e_tunnel_filter_restore(struct i40e_pf *pf);
 static void i40e_filter_restore(struct i40e_pf *pf);
 static void i40e_notify_all_vfs_link_status(struct rte_eth_dev *dev);
 static int i40e_fec_get_capability(struct rte_eth_dev *dev,
@@ -996,49 +990,43 @@ config_floating_veb(struct rte_eth_dev *dev)
 #define I40E_L2_TAGS_S_TAG_SHIFT 1
 #define I40E_L2_TAGS_S_TAG_MASK I40E_MASK(0x1, I40E_L2_TAGS_S_TAG_SHIFT)
 
-static int
-i40e_init_tunnel_filter_list(struct rte_eth_dev *dev)
+struct i40e_tunnel_state *
+i40e_tunnel_state_attach(struct rte_eth_dev_data *dev_data)
 {
-	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
-	struct i40e_tunnel_rule *tunnel_rule = &pf->tunnel;
-	char tunnel_hash_name[RTE_HASH_NAMESIZE];
-	int ret;
-
-	struct rte_hash_parameters tunnel_hash_params = {
+	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev_data->dev_private);
+	struct i40e_tunnel_state *state = &pf->tunnel_state;
+	char tunnel_hash_name[RTE_HASH_NAMESIZE] = {0};
+	struct rte_hash_parameters hash_params = {
 		.name = tunnel_hash_name,
 		.entries = I40E_MAX_TUNNEL_FILTER_NUM,
-		.key_len = sizeof(struct i40e_tunnel_filter_input),
+		.key_len = sizeof(struct i40e_tunnel_filter_match_key),
 		.hash_func = rte_hash_crc,
 		.hash_func_init_val = 0,
 		.socket_id = rte_socket_id(),
 	};
 
-	/* Initialize tunnel filter rule list and hash */
-	TAILQ_INIT(&tunnel_rule->tunnel_list);
-	snprintf(tunnel_hash_name, RTE_HASH_NAMESIZE,
-		 "tunnel_%s", dev->device->name);
-	tunnel_rule->hash_table = rte_hash_create(&tunnel_hash_params);
-	if (!tunnel_rule->hash_table) {
-		PMD_INIT_LOG(ERR, "Failed to create tunnel hash table!");
-		return -EINVAL;
-	}
-	tunnel_rule->hash_map = rte_zmalloc("i40e_tunnel_hash_map",
-				    sizeof(struct i40e_tunnel_filter *) *
-				    I40E_MAX_TUNNEL_FILTER_NUM,
-				    0);
-	if (!tunnel_rule->hash_map) {
-		PMD_INIT_LOG(ERR,
-			     "Failed to allocate memory for tunnel hash map!");
-		ret = -ENOMEM;
-		goto err_tunnel_hash_map_alloc;
+	snprintf(tunnel_hash_name, RTE_HASH_NAMESIZE, "i40e_tunnel_hash_%s", dev_data->name);
+
+	if (ci_refcount_acquire(&state->ref)) {
+		state->hash_table = rte_hash_create(&hash_params);
+		if (state->hash_table == NULL) {
+			PMD_DRV_LOG(ERR, "Failed to create tunnel hash table");
+			ci_refcount_release(&state->ref);
+			return NULL;
+		}
 	}
 
-	return 0;
+	return state;
+}
 
-err_tunnel_hash_map_alloc:
-	rte_hash_free(tunnel_rule->hash_table);
+void
+i40e_tunnel_state_detach(struct i40e_tunnel_state *state)
+{
+	if (!ci_refcount_release(&state->ref))
+		return;
 
-	return ret;
+	rte_hash_free(state->hash_table);
+	*state = (struct i40e_tunnel_state){0};
 }
 
 static void
@@ -1656,9 +1644,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	/* Initialize the filter invalidation configuration */
 	i40e_init_filter_invalidation(pf);
 
-	ret = i40e_init_tunnel_filter_list(dev);
-	if (ret < 0)
-		goto err_init_tunnel_filter_list;
 	i40e_fdir_flow_store_init(dev);
 
 	/* initialize flow engine configuration */
@@ -1676,9 +1661,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	return 0;
 
 err_flow_engine_conf_init:
-	rte_hash_free(pf->tunnel.hash_table);
-	rte_free(pf->tunnel.hash_map);
-err_init_tunnel_filter_list:
 	rte_intr_callback_unregister(intr_handle,
 		i40e_dev_interrupt_handler, dev);
 	rte_free(dev->data->mac_addrs);
@@ -1701,23 +1683,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	return ret;
 }
 
-static void
-i40e_rm_tunnel_filter_list(struct i40e_pf *pf)
-{
-	struct i40e_tunnel_filter *p_tunnel;
-	struct i40e_tunnel_rule *tunnel_rule;
-
-	tunnel_rule = &pf->tunnel;
-	/* Remove all tunnel director rules and hash */
-	rte_free(tunnel_rule->hash_map);
-	rte_hash_free(tunnel_rule->hash_table);
-
-	while ((p_tunnel = TAILQ_FIRST(&tunnel_rule->tunnel_list))) {
-		TAILQ_REMOVE(&tunnel_rule->tunnel_list, p_tunnel, rules);
-		rte_free(p_tunnel);
-	}
-}
-
 static void
 i40e_fdir_memory_cleanup(struct i40e_pf *pf)
 {
@@ -2578,8 +2543,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 		i40e_msec_delay(500);
 	} while (retries++ < 5);
 
-	i40e_rm_tunnel_filter_list(pf);
-
 	/* Remove all flows */
 	while ((p_flow = TAILQ_FIRST(&pf->flow_list))) {
 		TAILQ_REMOVE(&pf->flow_list, p_flow, node);
@@ -7747,96 +7710,6 @@ i40e_dev_get_filter_type(uint16_t filter_type, uint16_t *flag)
 	return 0;
 }
 
-/* Convert tunnel filter structure */
-static int
-i40e_tunnel_filter_convert(
-	struct i40e_aqc_cloud_filters_element_bb *cld_filter,
-	struct i40e_tunnel_filter *tunnel_filter)
-{
-	rte_ether_addr_copy((struct rte_ether_addr *)
-			&cld_filter->element.outer_mac,
-		(struct rte_ether_addr *)&tunnel_filter->input.outer_mac);
-	rte_ether_addr_copy((struct rte_ether_addr *)
-			&cld_filter->element.inner_mac,
-		(struct rte_ether_addr *)&tunnel_filter->input.inner_mac);
-	tunnel_filter->input.inner_vlan = cld_filter->element.inner_vlan;
-	if ((rte_le_to_cpu_16(cld_filter->element.flags) &
-	     I40E_AQC_ADD_CLOUD_FLAGS_IPV6) ==
-	    I40E_AQC_ADD_CLOUD_FLAGS_IPV6)
-		tunnel_filter->input.ip_type = I40E_TUNNEL_IPTYPE_IPV6;
-	else
-		tunnel_filter->input.ip_type = I40E_TUNNEL_IPTYPE_IPV4;
-	tunnel_filter->input.flags = cld_filter->element.flags;
-	tunnel_filter->input.tenant_id = cld_filter->element.tenant_id;
-	tunnel_filter->queue = cld_filter->element.queue_number;
-	memcpy(tunnel_filter->input.general_fields,
-		   cld_filter->general_fields,
-		   sizeof(cld_filter->general_fields));
-
-	return 0;
-}
-
-/* Check if there exists the tunnel filter */
-struct i40e_tunnel_filter *
-i40e_sw_tunnel_filter_lookup(struct i40e_tunnel_rule *tunnel_rule,
-			     const struct i40e_tunnel_filter_input *input)
-{
-	int ret;
-
-	ret = rte_hash_lookup(tunnel_rule->hash_table, (const void *)input);
-	if (ret < 0)
-		return NULL;
-
-	return tunnel_rule->hash_map[ret];
-}
-
-/* Add a tunnel filter into the SW list */
-static int
-i40e_sw_tunnel_filter_insert(struct i40e_pf *pf,
-			     struct i40e_tunnel_filter *tunnel_filter)
-{
-	struct i40e_tunnel_rule *rule = &pf->tunnel;
-	int ret;
-
-	ret = rte_hash_add_key(rule->hash_table, &tunnel_filter->input);
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "Failed to insert tunnel filter to hash table %d!",
-			    ret);
-		return ret;
-	}
-	rule->hash_map[ret] = tunnel_filter;
-
-	TAILQ_INSERT_TAIL(&rule->tunnel_list, tunnel_filter, rules);
-
-	return 0;
-}
-
-/* Delete a tunnel filter from the SW list */
-int
-i40e_sw_tunnel_filter_del(struct i40e_pf *pf,
-			  struct i40e_tunnel_filter_input *input)
-{
-	struct i40e_tunnel_rule *rule = &pf->tunnel;
-	struct i40e_tunnel_filter *tunnel_filter;
-	int ret;
-
-	ret = rte_hash_del_key(rule->hash_table, input);
-	if (ret < 0) {
-		PMD_DRV_LOG(ERR,
-			    "Failed to delete tunnel filter to hash table %d!",
-			    ret);
-		return ret;
-	}
-	tunnel_filter = rule->hash_map[ret];
-	rule->hash_map[ret] = NULL;
-
-	TAILQ_REMOVE(&rule->tunnel_list, tunnel_filter, rules);
-	rte_free(tunnel_filter);
-
-	return 0;
-}
-
 #define I40E_AQC_REPLACE_CLOUD_CMD_INPUT_TR_WORD0 0x48
 #define I40E_TR_VXLAN_GRE_KEY_MASK		0x4
 #define I40E_TR_GENEVE_KEY_MASK			0x8
@@ -8275,41 +8148,35 @@ i40e_replace_port_cloud_filter(struct i40e_pf *pf,
 	return status;
 }
 
-int
-i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
-		      struct i40e_tunnel_filter_conf *tunnel_filter,
-		      uint8_t add)
+static int
+i40e_tunnel_filter_convert_conf(struct i40e_pf *pf,
+		struct i40e_tunnel_filter_conf *tunnel_filter,
+		struct i40e_aqc_cloud_filters_element_bb *cld_filter,
+		struct i40e_vsi **vsi, bool *big_buffer)
 {
 	uint16_t ip_type;
 	uint32_t ipv4_addr, ipv4_addr_le;
 	uint8_t i, tun_type = 0;
 	/* internal variable to convert ipv6 byte order */
 	uint32_t convert_ipv6[4];
-	int val, ret = 0;
+	int val;
 	struct i40e_pf_vf *vf = NULL;
-	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
-	struct i40e_vsi *vsi;
-	struct i40e_aqc_cloud_filters_element_bb cld_filter = {0};
-	struct i40e_tunnel_rule *tunnel_rule = &pf->tunnel;
-	struct i40e_tunnel_filter *node;
-	struct i40e_tunnel_filter check_filter; /* Check if filter exists */
 	uint32_t teid_le;
-	bool big_buffer = 0;
 
 	rte_ether_addr_copy(&tunnel_filter->outer_mac,
-			(struct rte_ether_addr *)&cld_filter.element.outer_mac);
+			(struct rte_ether_addr *)&cld_filter->element.outer_mac);
 	rte_ether_addr_copy(&tunnel_filter->inner_mac,
-			(struct rte_ether_addr *)&cld_filter.element.inner_mac);
+			(struct rte_ether_addr *)&cld_filter->element.inner_mac);
 
-	cld_filter.element.inner_vlan =
+	cld_filter->element.inner_vlan =
 		rte_cpu_to_le_16(tunnel_filter->inner_vlan);
 	if (tunnel_filter->ip_type == I40E_TUNNEL_IPTYPE_IPV4) {
 		ip_type = I40E_AQC_ADD_CLOUD_FLAGS_IPV4;
 		ipv4_addr = rte_be_to_cpu_32(tunnel_filter->ip_addr.ipv4_addr);
 		ipv4_addr_le = rte_cpu_to_le_32(ipv4_addr);
-		memcpy(&cld_filter.element.ipaddr.v4.data,
+		memcpy(&cld_filter->element.ipaddr.v4.data,
 				&ipv4_addr_le,
-				sizeof(cld_filter.element.ipaddr.v4.data));
+				sizeof(cld_filter->element.ipaddr.v4.data));
 	} else {
 		ip_type = I40E_AQC_ADD_CLOUD_FLAGS_IPV6;
 		for (i = 0; i < 4; i++) {
@@ -8317,9 +8184,9 @@ i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
 			rte_cpu_to_le_32(rte_be_to_cpu_32(
 					 tunnel_filter->ip_addr.ipv6_addr[i]));
 		}
-		memcpy(&cld_filter.element.ipaddr.v6.data,
+		memcpy(&cld_filter->element.ipaddr.v6.data,
 			   &convert_ipv6,
-			   sizeof(cld_filter.element.ipaddr.v6.data));
+			   sizeof(cld_filter->element.ipaddr.v6.data));
 	}
 
 	/* check tunneled type */
@@ -8334,139 +8201,96 @@ i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
 		tun_type = I40E_AQC_ADD_CLOUD_TNL_TYPE_IP;
 		break;
 	case I40E_TUNNEL_TYPE_MPLSoUDP:
-		if (!pf->mpls_replace_flag) {
-			i40e_replace_mpls_l1_filter(pf);
-			i40e_replace_mpls_cloud_filter(pf);
-			pf->mpls_replace_flag = 1;
-		}
 		teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
 			teid_le >> 4;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
 			(teid_le & 0xF) << 12;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
 			0x40;
-		big_buffer = 1;
+		*big_buffer = 1;
 		tun_type = I40E_AQC_ADD_CLOUD_TNL_TYPE_MPLSOUDP;
 		break;
 	case I40E_TUNNEL_TYPE_MPLSoGRE:
-		if (!pf->mpls_replace_flag) {
-			i40e_replace_mpls_l1_filter(pf);
-			i40e_replace_mpls_cloud_filter(pf);
-			pf->mpls_replace_flag = 1;
-		}
 		teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
 			teid_le >> 4;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
 			(teid_le & 0xF) << 12;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
 			0x0;
-		big_buffer = 1;
+		*big_buffer = 1;
 		tun_type = I40E_AQC_ADD_CLOUD_TNL_TYPE_MPLSOGRE;
 		break;
 	case I40E_TUNNEL_TYPE_GTPC:
-		if (!pf->gtp_replace_flag) {
-			i40e_replace_gtp_l1_filter(pf);
-			i40e_replace_gtp_cloud_filter(pf);
-			pf->gtp_replace_flag = 1;
-		}
 		teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD0] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD0] =
 			(teid_le >> 16) & 0xFFFF;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD1] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD1] =
 			teid_le & 0xFFFF;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD2] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X12_WORD2] =
 			0x0;
-		big_buffer = 1;
+		*big_buffer = 1;
 		break;
 	case I40E_TUNNEL_TYPE_GTPU:
-		if (!pf->gtp_replace_flag) {
-			i40e_replace_gtp_l1_filter(pf);
-			i40e_replace_gtp_cloud_filter(pf);
-			pf->gtp_replace_flag = 1;
-		}
 		teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD0] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD0] =
 			(teid_le >> 16) & 0xFFFF;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD1] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD1] =
 			teid_le & 0xFFFF;
-		cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD2] =
+		cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X13_WORD2] =
 			0x0;
-		big_buffer = 1;
+		*big_buffer = 1;
 		break;
 	case I40E_TUNNEL_TYPE_QINQ:
-		if (!pf->qinq_replace_flag) {
-			ret = i40e_cloud_filter_qinq_create(pf);
-			if (ret < 0) {
-				PMD_DRV_LOG(ERR, "Failed to create QinQ tunnel filter.");
-				/* ret is a raw i40e_status_code, not an errno */
-				return -ENOTSUP;
-			}
-			pf->qinq_replace_flag = 1;
-		}
 		/*	Add in the General fields the values of
 		 *	the Outer and Inner VLAN
 		 *	Big Buffer should be set, see changes in
 		 *	i40e_aq_add_cloud_filters
 		 */
-		cld_filter.general_fields[0] = tunnel_filter->inner_vlan;
-		cld_filter.general_fields[1] = tunnel_filter->outer_vlan;
-		big_buffer = 1;
+		cld_filter->general_fields[0] = tunnel_filter->inner_vlan;
+		cld_filter->general_fields[1] = tunnel_filter->outer_vlan;
+		*big_buffer = 1;
 		break;
 	case I40E_CLOUD_TYPE_UDP:
 	case I40E_CLOUD_TYPE_TCP:
 	case I40E_CLOUD_TYPE_SCTP:
 		if (tunnel_filter->l4_port_type == I40E_L4_PORT_TYPE_SRC) {
-			if (!pf->sport_replace_flag) {
-				i40e_replace_port_l1_filter(pf,
-						tunnel_filter->l4_port_type);
-				i40e_replace_port_cloud_filter(pf,
-						tunnel_filter->l4_port_type);
-				pf->sport_replace_flag = 1;
-			}
 			teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-			cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
+			cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD0] =
 				I40E_DIRECTION_INGRESS_KEY;
 
 			if (tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_UDP)
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
 					I40E_TR_L4_TYPE_UDP;
 			else if (tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_TCP)
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
 					I40E_TR_L4_TYPE_TCP;
 			else
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD1] =
 					I40E_TR_L4_TYPE_SCTP;
 
-			cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
+			cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X11_WORD2] =
 				(teid_le >> 16) & 0xFFFF;
-			big_buffer = 1;
+			*big_buffer = 1;
 		} else {
-			if (!pf->dport_replace_flag) {
-				i40e_replace_port_l1_filter(pf,
-						tunnel_filter->l4_port_type);
-				i40e_replace_port_cloud_filter(pf,
-						tunnel_filter->l4_port_type);
-				pf->dport_replace_flag = 1;
-			}
 			teid_le = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-			cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD0] =
+			cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD0] =
 				I40E_DIRECTION_INGRESS_KEY;
 
 			if (tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_UDP)
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
 					I40E_TR_L4_TYPE_UDP;
 			else if (tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_TCP)
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
 					I40E_TR_L4_TYPE_TCP;
 			else
-				cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
+				cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD1] =
 					I40E_TR_L4_TYPE_SCTP;
 
-			cld_filter.general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD2] =
+			cld_filter->general_fields[I40E_AQC_ADD_CLOUD_FV_FLU_0X10_WORD2] =
 				(teid_le >> 16) & 0xFFFF;
-			big_buffer = 1;
+			*big_buffer = 1;
 		}
 
 		break;
@@ -8477,74 +8301,193 @@ i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
 	}
 
 	if (tunnel_filter->tunnel_type == I40E_TUNNEL_TYPE_MPLSoUDP)
-		cld_filter.element.flags =
+		cld_filter->element.flags =
 			I40E_AQC_ADD_CLOUD_FILTER_0X11;
 	else if (tunnel_filter->tunnel_type == I40E_TUNNEL_TYPE_MPLSoGRE)
-		cld_filter.element.flags =
+		cld_filter->element.flags =
 			I40E_AQC_ADD_CLOUD_FILTER_0X12;
 	else if (tunnel_filter->tunnel_type == I40E_TUNNEL_TYPE_GTPC)
-		cld_filter.element.flags =
+		cld_filter->element.flags =
 			I40E_AQC_ADD_CLOUD_FILTER_0X11;
 	else if (tunnel_filter->tunnel_type == I40E_TUNNEL_TYPE_GTPU)
-		cld_filter.element.flags =
+		cld_filter->element.flags =
 			I40E_AQC_ADD_CLOUD_FILTER_0X12;
 	else if (tunnel_filter->tunnel_type == I40E_TUNNEL_TYPE_QINQ)
-		cld_filter.element.flags |=
+		cld_filter->element.flags |=
 			I40E_AQC_ADD_CLOUD_FILTER_0X10;
 	else if (tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_UDP ||
 		 tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_TCP ||
 		 tunnel_filter->tunnel_type == I40E_CLOUD_TYPE_SCTP) {
 		if (tunnel_filter->l4_port_type == I40E_L4_PORT_TYPE_SRC)
-			cld_filter.element.flags |=
+			cld_filter->element.flags |=
 				I40E_AQC_ADD_CLOUD_FILTER_0X11;
 		else
-			cld_filter.element.flags |=
+			cld_filter->element.flags |=
 				I40E_AQC_ADD_CLOUD_FILTER_0X10;
 	} else {
 		val = i40e_dev_get_filter_type(tunnel_filter->filter_type,
-						&cld_filter.element.flags);
+						&cld_filter->element.flags);
 		if (val < 0) {
 			return -EINVAL;
 		}
 	}
 
-	cld_filter.element.flags |=
+	cld_filter->element.flags |=
 			rte_cpu_to_le_16(I40E_AQC_ADD_CLOUD_FLAGS_TO_QUEUE | ip_type |
 				(tun_type << I40E_AQC_ADD_CLOUD_TNL_TYPE_SHIFT));
-	cld_filter.element.tenant_id = rte_cpu_to_le_32(tunnel_filter->tenant_id);
-	cld_filter.element.queue_number =
+	cld_filter->element.tenant_id = rte_cpu_to_le_32(tunnel_filter->tenant_id);
+	cld_filter->element.queue_number =
 		rte_cpu_to_le_16(tunnel_filter->queue_id);
 
 	if (!tunnel_filter->is_to_vf)
-		vsi = pf->main_vsi;
+		*vsi = pf->main_vsi;
 	else {
 		if (tunnel_filter->vf_id >= pf->vf_num) {
 			PMD_DRV_LOG(ERR, "Invalid argument.");
 			return -EINVAL;
 		}
 		vf = &pf->vfs[tunnel_filter->vf_id];
-		vsi = vf->vsi;
+		*vsi = vf->vsi;
 	}
 
-	/* Check if there is the filter in SW list */
-	memset(&check_filter, 0, sizeof(check_filter));
-	i40e_tunnel_filter_convert(&cld_filter, &check_filter);
-	check_filter.is_to_vf = tunnel_filter->is_to_vf;
-	check_filter.vf_id = tunnel_filter->vf_id;
-	node = i40e_sw_tunnel_filter_lookup(tunnel_rule, &check_filter.input);
-	if (add && node) {
-		PMD_DRV_LOG(ERR, "Conflict with existing tunnel rules!");
-		return -EINVAL;
-	}
+	return 0;
+}
+
+static int
+i40e_tunnel_filter_prepare_hw(struct i40e_pf *pf,
+		struct i40e_tunnel_filter_conf *tunnel_filter)
+{
+	int ret;
 
-	if (!add && !node) {
-		PMD_DRV_LOG(ERR, "There's no corresponding tunnel filter!");
-		return -EINVAL;
+	switch (tunnel_filter->tunnel_type) {
+	case I40E_TUNNEL_TYPE_MPLSoUDP:
+	case I40E_TUNNEL_TYPE_MPLSoGRE:
+		if (!pf->mpls_replace_flag) {
+			ret = i40e_replace_mpls_l1_filter(pf);
+			if (ret < 0)
+				return ret;
+			ret = i40e_replace_mpls_cloud_filter(pf);
+			if (ret < 0)
+				return ret;
+			pf->mpls_replace_flag = 1;
+		}
+		break;
+	case I40E_TUNNEL_TYPE_GTPC:
+	case I40E_TUNNEL_TYPE_GTPU:
+		if (!pf->gtp_replace_flag) {
+			ret = i40e_replace_gtp_l1_filter(pf);
+			if (ret < 0)
+				return ret;
+			ret = i40e_replace_gtp_cloud_filter(pf);
+			if (ret < 0)
+				return ret;
+			pf->gtp_replace_flag = 1;
+		}
+		break;
+	case I40E_TUNNEL_TYPE_QINQ:
+		if (!pf->qinq_replace_flag) {
+			ret = i40e_cloud_filter_qinq_create(pf);
+			if (ret < 0) {
+				PMD_DRV_LOG(ERR, "Failed to create QinQ tunnel filter.");
+				/* ret is a raw i40e_status_code, not an errno */
+				return -ENOTSUP;
+			}
+			pf->qinq_replace_flag = 1;
+		}
+		break;
+	case I40E_CLOUD_TYPE_UDP:
+	case I40E_CLOUD_TYPE_TCP:
+	case I40E_CLOUD_TYPE_SCTP:
+		if (tunnel_filter->l4_port_type == I40E_L4_PORT_TYPE_SRC) {
+			if (!pf->sport_replace_flag) {
+				ret = i40e_replace_port_l1_filter(pf,
+						tunnel_filter->l4_port_type);
+				if (ret < 0)
+					return ret;
+				ret = i40e_replace_port_cloud_filter(pf,
+						tunnel_filter->l4_port_type);
+				if (ret < 0)
+					return ret;
+				pf->sport_replace_flag = 1;
+			}
+		} else if (!pf->dport_replace_flag) {
+			ret = i40e_replace_port_l1_filter(pf,
+					tunnel_filter->l4_port_type);
+			if (ret < 0)
+				return ret;
+			ret = i40e_replace_port_cloud_filter(pf,
+					tunnel_filter->l4_port_type);
+			if (ret < 0)
+				return ret;
+			pf->dport_replace_flag = 1;
+		}
+		break;
+	default:
+		break;
 	}
 
+	return 0;
+}
+
+int
+i40e_tunnel_filter_match_key_get(struct i40e_pf *pf,
+		struct i40e_tunnel_filter_conf *tunnel_filter,
+		struct i40e_tunnel_filter_match_key *input)
+{
+	struct i40e_aqc_cloud_filters_element_bb cld_filter = {0};
+	struct i40e_vsi *vsi;
+	bool big_buffer = 0;
+	int ret;
+
+	ret = i40e_tunnel_filter_convert_conf(pf, tunnel_filter, &cld_filter,
+			&vsi, &big_buffer);
+	if (ret != 0)
+		return ret;
+
+	rte_ether_addr_copy((struct rte_ether_addr *)
+			&cld_filter.element.outer_mac,
+		(struct rte_ether_addr *)&input->outer_mac);
+	rte_ether_addr_copy((struct rte_ether_addr *)
+			&cld_filter.element.inner_mac,
+		(struct rte_ether_addr *)&input->inner_mac);
+	input->inner_vlan = cld_filter.element.inner_vlan;
+	if ((rte_le_to_cpu_16(cld_filter.element.flags) &
+	     I40E_AQC_ADD_CLOUD_FLAGS_IPV6) ==
+	    I40E_AQC_ADD_CLOUD_FLAGS_IPV6)
+		input->ip_type = I40E_TUNNEL_IPTYPE_IPV6;
+	else
+		input->ip_type = I40E_TUNNEL_IPTYPE_IPV4;
+	input->flags = cld_filter.element.flags;
+	input->tenant_id = cld_filter.element.tenant_id;
+	memcpy(input->general_fields, cld_filter.general_fields,
+			sizeof(cld_filter.general_fields));
+
+	return 0;
+}
+
+int
+i40e_tunnel_filter_program(struct i40e_pf *pf,
+		struct i40e_tunnel_filter_conf *tunnel_filter,
+		uint8_t add)
+{
+	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
+	struct i40e_aqc_cloud_filters_element_bb cld_filter = {0};
+	struct i40e_vsi *vsi;
+	bool big_buffer = 0;
+	int ret;
+
+	ret = i40e_tunnel_filter_convert_conf(pf, tunnel_filter, &cld_filter,
+			&vsi, &big_buffer);
+	if (ret != 0)
+		return ret;
+
 	if (add) {
-		struct i40e_tunnel_filter *tunnel;
+		ret = i40e_tunnel_filter_prepare_hw(pf, tunnel_filter);
+		if (ret < 0)
+			return ret;
+	}
 
+	if (add) {
 		if (big_buffer)
 			ret = i40e_aq_add_cloud_filters_bb(hw,
 						   vsi->seid, &cld_filter, 1);
@@ -8555,16 +8498,6 @@ i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
 			PMD_DRV_LOG(ERR, "Failed to add a tunnel filter.");
 			return -ENOTSUP;
 		}
-		tunnel = rte_zmalloc("tunnel_filter", sizeof(*tunnel), 0);
-		if (tunnel == NULL) {
-			PMD_DRV_LOG(ERR, "Failed to alloc memory.");
-			return -ENOMEM;
-		}
-
-		memcpy(tunnel, &check_filter, sizeof(check_filter));
-		ret = i40e_sw_tunnel_filter_insert(pf, tunnel);
-		if (ret < 0)
-			rte_free(tunnel);
 	} else {
 		if (big_buffer)
 			ret = i40e_aq_rem_cloud_filters_bb(
@@ -8576,7 +8509,6 @@ i40e_dev_consistent_tunnel_filter_set(struct i40e_pf *pf,
 			PMD_DRV_LOG(ERR, "Failed to delete a tunnel filter.");
 			return -ENOTSUP;
 		}
-		ret = i40e_sw_tunnel_filter_del(pf, &node->input);
 	}
 
 	return ret;
@@ -11284,65 +11216,9 @@ i40e_dev_mtu_set(struct rte_eth_dev *dev, uint16_t mtu __rte_unused)
 	return 0;
 }
 
-/* Restore tunnel filter */
-static void
-i40e_tunnel_filter_restore(struct i40e_pf *pf)
-{
-	struct i40e_hw *hw = I40E_PF_TO_HW(pf);
-	struct i40e_vsi *vsi;
-	struct i40e_pf_vf *vf;
-	struct i40e_tunnel_filter_list
-		*tunnel_list = &pf->tunnel.tunnel_list;
-	struct i40e_tunnel_filter *f;
-	struct i40e_aqc_cloud_filters_element_bb cld_filter;
-	bool big_buffer = 0;
-
-	TAILQ_FOREACH(f, tunnel_list, rules) {
-		if (!f->is_to_vf)
-			vsi = pf->main_vsi;
-		else {
-			vf = &pf->vfs[f->vf_id];
-			vsi = vf->vsi;
-		}
-		memset(&cld_filter, 0, sizeof(cld_filter));
-		rte_ether_addr_copy((struct rte_ether_addr *)
-				&f->input.outer_mac,
-			(struct rte_ether_addr *)&cld_filter.element.outer_mac);
-		rte_ether_addr_copy((struct rte_ether_addr *)
-				&f->input.inner_mac,
-			(struct rte_ether_addr *)&cld_filter.element.inner_mac);
-		cld_filter.element.inner_vlan = f->input.inner_vlan;
-		cld_filter.element.flags = f->input.flags;
-		cld_filter.element.tenant_id = f->input.tenant_id;
-		cld_filter.element.queue_number = f->queue;
-		memcpy(cld_filter.general_fields,
-			   f->input.general_fields,
-			   sizeof(f->input.general_fields));
-
-		if (((f->input.flags &
-		     I40E_AQC_ADD_CLOUD_FILTER_0X11) ==
-		     I40E_AQC_ADD_CLOUD_FILTER_0X11) ||
-		    ((f->input.flags &
-		     I40E_AQC_ADD_CLOUD_FILTER_0X12) ==
-		     I40E_AQC_ADD_CLOUD_FILTER_0X12) ||
-		    ((f->input.flags &
-		     I40E_AQC_ADD_CLOUD_FILTER_0X10) ==
-		     I40E_AQC_ADD_CLOUD_FILTER_0X10))
-			big_buffer = 1;
-
-		if (big_buffer)
-			i40e_aq_add_cloud_filters_bb(hw,
-					vsi->seid, &cld_filter, 1);
-		else
-			i40e_aq_add_cloud_filters(hw, vsi->seid,
-						  &cld_filter.element, 1);
-	}
-}
-
 static void
 i40e_filter_restore(struct i40e_pf *pf)
 {
-	i40e_tunnel_filter_restore(pf);
 	i40e_fdir_filter_restore(pf);
 	(void)i40e_hash_filter_restore(pf);
 }
drivers/net/intel/i40e/i40e_ethdev.h +15 -38

98 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.c +7 -1695

1,782 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h +6 -0
index 6823dbef332..28e342210ce 100644
@@ -17,5 +17,11 @@ extern const struct ci_flow_engine_list i40e_flow_engine_list;
 
 extern const struct ci_flow_engine i40e_flow_engine_ethertype;
 extern const struct ci_flow_engine i40e_flow_engine_fdir;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_qinq;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_vxlan;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_nvgre;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_mpls;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_gtp;
+extern const struct ci_flow_engine i40e_flow_engine_tunnel_l4;
 
 #endif /* _I40E_FLOW_H_ */
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +1590 -0

335 lines not on this page · raw message

drivers/net/intel/i40e/meson.build +1 -0
index c07257cb807..0db60c1e99e 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.52.0

Part of the diff is not shown here. View the raw message.

#101 lore raw

Re: [PATCH v3 17/19] net/i40e: reimplement tunnel parsers

Quoted 25 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for tunnel filters: QinQ, VXLAN, NVGRE, MPLS, GTP, and L4.

As a result of transitioning to more formalized validation, some checks have become more stringent:

- VLAN TCI mask is now required to be fully masked (all-ones); previously the mask was only checked for eth_proto and any non-zero vlan_tci mask value was silently accepted

In addition to using the new graph infrastructure, some of the checks were made more stringent and/or more correct. In particular:

- old code did not check for whether fields other than ports are masked (they are now rejected)
- old code did not check for whether src/ports are fully masked (masks other than full are now rejected)
- old code used spec to decide which port to copy (as a result, it was not possible to match port 0 - this is now allowed)

Tunnel engine now also share a refcounted global state, and track all flows and do deduplication inside the engine.

---

<snip>

Quoted 5 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +5 -0
+static int
+i40e_tunnel_node_vlan_validate(const void *ctx __rte_unused, const struct rte_flow_item *item,
+		struct rte_flow_error *error)
+{
+	const struct rte_flow_item_vlan *vlan_mask = item->mask;

drivers/net/intel/i40e/i40e_flow_tunnel.c line 75

Quoted 10 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +10 -0
+
+	/* matching eth proto not supported */
+	if (vlan_mask->hdr.eth_proto) {
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM, item,
+				"Invalid VLAN mask");
+	}
+
+	/* VLAN TCI must be fully masked */
+	if (!CI_FIELD_IS_MASKED(&vlan_mask->hdr.vlan_tci)) {

isn't VLAN TCI optional?

Quoted 8 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +8 -0
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM, item,
+				"Invalid VLAN mask");
+	}
+
+	return 0;
+}
+

<snip>

drivers/net/intel/i40e/i40e_flow_tunnel.c line 1089

Quoted 12 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +12 -0
+static int
+i40e_tunnel_node_tcp_process(void *ctx, const struct rte_flow_item *item,
+		struct rte_flow_error *error __rte_unused)
+{
+	struct i40e_tunnel_ctx *tunnel_ctx = ctx;
+	struct i40e_tunnel_filter_conf *tunnel_filter = &tunnel_ctx->filter;
+	const struct rte_flow_item_tcp *tcp_spec = item->spec;
+	const struct rte_flow_item_tcp *tcp_mask = item->mask;
+
+	if (tcp_mask->hdr.src_port) {
+		tunnel_filter->l4_port_type = I40E_L4_PORT_TYPE_SRC;
+		tunnel_filter->tenant_id = rte_be_to_cpu_32(tcp_spec->hdr.src_port);

nit: just as a thought for future improvement: here and for some other protocols, use rte_be_to_cpu_16 instead of _32 and fix i40e_tunnel_filter_convert_conf?

Quoted 9 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +9 -0
+	} else if (tcp_mask->hdr.dst_port) {
+		tunnel_filter->l4_port_type = I40E_L4_PORT_TYPE_DST;
+		tunnel_filter->tenant_id = rte_be_to_cpu_32(tcp_spec->hdr.dst_port);
+	}
+	tunnel_filter->tunnel_type = I40E_CLOUD_TYPE_TCP;
+
+	return 0;
+}
+

<snip>

Signature · 2 lines
Regards,
Vladimir
#102 lore raw

Re: [PATCH v3 17/19] net/i40e: reimplement tunnel parsers

Quoted 68 lines · Vladimir
Quoted 26 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for tunnel filters: QinQ, VXLAN, NVGRE, MPLS, GTP, and L4.

As a result of transitioning to more formalized validation, some checks have become more stringent:

- VLAN TCI mask is now required to be fully masked (all-ones); previously the mask was only checked for eth_proto and any non-zero vlan_tci mask value was silently accepted

In addition to using the new graph infrastructure, some of the checks were made more stringent and/or more correct. In particular:

- old code did not check for whether fields other than ports are masked (they are now rejected)
- old code did not check for whether src/ports are fully masked (masks other than full are now rejected)
- old code used spec to decide which port to copy (as a result, it was not
   possible to match port 0 - this is now allowed)

Tunnel engine now also share a refcounted global state, and track all flows and do deduplication inside the engine.

---

<snip>

Quoted 6 lines
(quoted diff) +2 -0
+static int
+i40e_tunnel_node_vlan_validate(const void *ctx __rte_unused, const 

struct rte_flow_item *item,
+        struct rte_flow_error *error)

drivers/net/intel/i40e/i40e_flow_tunnel.c new file +2 -0
+{
+    const struct rte_flow_item_vlan *vlan_mask = item->mask;

drivers/net/intel/i40e/i40e_flow_tunnel.c line 75

Quoted 10 lines
drivers/net/intel/i40e/i40e_flow_tunnel.c new file +10 -0
+
+    /* matching eth proto not supported */
+    if (vlan_mask->hdr.eth_proto) {
+        return rte_flow_error_set(error, EINVAL,
+                RTE_FLOW_ERROR_TYPE_ITEM, item,
+                "Invalid VLAN mask");
+    }
+
+    /* VLAN TCI must be fully masked */
+    if (!CI_FIELD_IS_MASKED(&vlan_mask->hdr.vlan_tci)) {

isn't VLAN TCI optional?

Quoted 8 lines

+        return rte_flow_error_set(error, EINVAL,
+                RTE_FLOW_ERROR_TYPE_ITEM, item,
+                "Invalid VLAN mask");
+    }

drivers/net/intel/i40e/i40e_ethdev.c +4 -0
+
+    return 0;
+}
+

<snip>

Quoted 13 lines
(quoted diff) +2 -0
+static int
+i40e_tunnel_node_tcp_process(void *ctx, const struct rte_flow_item 

*item,
+        struct rte_flow_error *error __rte_unused)

drivers/net/intel/i40e/i40e_flow_tunnel.c new file +9 -0
+{
+    struct i40e_tunnel_ctx *tunnel_ctx = ctx;
+    struct i40e_tunnel_filter_conf *tunnel_filter = &tunnel_ctx->filter;
+    const struct rte_flow_item_tcp *tcp_spec = item->spec;
+    const struct rte_flow_item_tcp *tcp_mask = item->mask;
+
+    if (tcp_mask->hdr.src_port) {
+        tunnel_filter->l4_port_type = I40E_L4_PORT_TYPE_SRC;
+        tunnel_filter->tenant_id = rte_be_to_cpu_32(tcp_spec- 
Quoted 2 lines
Quoted 1 line
Quoted 1 line

hdr.src_port);

nit: just as a thought for future improvement: here and for some other protocols, use rte_be_to_cpu_16 instead of _32 and fix i40e_tunnel_filter_convert_conf?

Maybe for future improvement, I'm not going to touch this now.

Signature · 2 lines
Thanks,
Anatoly
#103 lore raw

PATCH v3 18/19 net/i40e: reimplement hash parser

In reply to Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS.

The RSS parser was bypassing the other generic infrastructure, probably because it was very convoluted and did not map onto that model very well. It has now been made a first-class citizen.

The hash parser is multiple engines that share state, so each engine will attach to the shared refcounted RSS configuration state.

Additionally, this is the final engine in i40e, so remaining legacy flow infrastructure has been removed.

---
 drivers/net/intel/i40e/i40e_ethdev.c    |   34 +-
 drivers/net/intel/i40e/i40e_ethdev.h    |   34 +-
 drivers/net/intel/i40e/i40e_flow.c      |  289 +----
 drivers/net/intel/i40e/i40e_flow.h      |    3 +
 drivers/net/intel/i40e/i40e_flow_hash.c | 1441 +++++++++++++++++++++++
 drivers/net/intel/i40e/i40e_hash.c      | 1208 +------------------
 drivers/net/intel/i40e/i40e_hash.h      |   19 +-
 drivers/net/intel/i40e/meson.build      |    1 +
 8 files changed, 1549 insertions(+), 1480 deletions(-)
 create mode 100644 drivers/net/intel/i40e/i40e_flow_hash.c
drivers/net/intel/i40e/i40e_ethdev.c +21 -13
index fb03eaba7dc..cca60e2889f 100644
@@ -1029,6 +1029,27 @@ i40e_tunnel_state_detach(struct i40e_tunnel_state *state)
 	*state = (struct i40e_tunnel_state){0};
 }
 
+struct i40e_rss_state *
+i40e_rss_state_attach(struct rte_eth_dev_data *dev_data)
+{
+	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev_data->dev_private);
+	struct i40e_rss_state *state = &pf->rss_state;
+
+	if (ci_refcount_acquire(&state->ref))
+		TAILQ_INIT(&state->list);
+
+	return state;
+}
+
+void
+i40e_rss_state_detach(struct i40e_rss_state *state)
+{
+	if (!ci_refcount_release(&state->ref))
+		return;
+
+	*state = (struct i40e_rss_state){0};
+}
+
 static void
 i40e_init_customized_info(struct i40e_pf *pf)
 {
@@ -1632,9 +1653,6 @@ eth_i40e_dev_init(struct rte_eth_dev *dev, void *init_params __rte_unused)
 	 */
 	i40e_add_tx_flow_control_drop_filter(pf);
 
-	/* initialize RSS rule list */
-	TAILQ_INIT(&pf->rss_config_list);
-
 	/* initialize Traffic Manager configuration */
 	i40e_tm_conf_init(dev);
 
@@ -1783,8 +1801,6 @@ i40e_dev_configure(struct rte_eth_dev *dev)
 		}
 	}
 
-	TAILQ_INIT(&pf->flow_list);
-
 	return 0;
 
 err_dcb:
@@ -2452,7 +2468,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
 	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
 	struct i40e_filter_control_settings settings;
-	struct rte_flow *p_flow;
 	uint32_t reg;
 	int i;
 	int ret;
@@ -2543,12 +2558,6 @@ i40e_dev_close(struct rte_eth_dev *dev)
 		i40e_msec_delay(500);
 	} while (retries++ < 5);
 
-	/* Remove all flows */
-	while ((p_flow = TAILQ_FIRST(&pf->flow_list))) {
-		TAILQ_REMOVE(&pf->flow_list, p_flow, node);
-		rte_free(p_flow);
-	}
-
 	/* release the fdir static allocated memory */
 	i40e_fdir_memory_cleanup(pf);
 
@@ -11220,7 +11229,6 @@ static void
 i40e_filter_restore(struct i40e_pf *pf)
 {
 	i40e_fdir_filter_restore(pf);
-	(void)i40e_hash_filter_restore(pf);
 }
 
 bool
drivers/net/intel/i40e/i40e_ethdev.h +17 -17
index 1a15916e925..cd1e27ff8ae 100644
@@ -282,9 +282,6 @@ enum i40e_flxpld_layer_idx {
  */
 struct rte_flow {
 	struct ci_flow base;
-	TAILQ_ENTRY(rte_flow) node;
-	enum rte_filter_type filter_type;
-	void *rule;
 };
 
 /**
@@ -954,8 +951,6 @@ struct i40e_tunnel_filter_conf {
 	uint16_t vf_id;         /**< VF id, available when is_to_vf is 1. */
 };
 
-TAILQ_HEAD(i40e_flow_list, rte_flow);
-
 /* Struct to store Traffic Manager shaper profile. */
 struct i40e_tm_shaper_profile {
 	TAILQ_ENTRY(i40e_tm_shaper_profile) node;
@@ -1077,14 +1072,23 @@ struct i40e_rss_filter_data {
 	uint64_t reset_symmetric_pctypes;
 };
 
-/* RSS filter list structure */
-struct i40e_rss_filter {
-	TAILQ_ENTRY(i40e_rss_filter) next;
-	struct i40e_rte_flow_rss_conf rss_filter_info;
+/*
+ * Tracking entry for a registered RSS flow: points back to the flow that
+ * owns the rss_conf, plus the metadata recording which HW state it owns.
+ */
+struct i40e_rss_flow_node {
+	TAILQ_ENTRY(i40e_rss_flow_node) next;
+	struct ci_flow *flow;
 	struct i40e_rss_filter_data filter_data;
 };
 
-TAILQ_HEAD(i40e_rss_conf_list, i40e_rss_filter);
+TAILQ_HEAD(i40e_rss_node_list, i40e_rss_flow_node);
+
+/* RSS flow engine shared state, attached/detached by the RSS engines */
+struct i40e_rss_state {
+	struct ci_refcount ref;
+	struct i40e_rss_node_list list;
+};
 
 struct i40e_vf_msg_cfg {
 	/* maximal VF message during a statistic period */
@@ -1161,13 +1165,12 @@ struct i40e_pf {
 
 	struct i40e_fdir_info fdir; /* flow director info */
 	struct i40e_tunnel_state tunnel_state; /* tunnel flow engine state */
-	struct i40e_rss_conf_list rss_config_list; /* RSS rule list */
+	struct i40e_rss_state rss_state; /* RSS flow engine state */
 	struct i40e_queue_regions queue_region; /* queue region info */
 	struct i40e_fc_conf fc_conf; /* Flow control conf */
 	bool floating_veb; /* The flag to use the floating VEB */
 	/* The floating enable flag for the specific VF */
 	bool floating_veb_list[I40E_MAX_VF];
-	struct i40e_flow_list flow_list;
 	/* flow engine configuration */
 	struct ci_flow_engine_conf flow_engine_conf;
 	bool mpls_replace_flag;  /* 1 - MPLS filter replace is done */
@@ -1306,11 +1309,6 @@ struct i40e_vf_representor {
 
 extern const struct rte_flow_ops i40e_flow_ops;
 
-struct i40e_filter_ctx {
-	struct i40e_rte_flow_rss_conf rss_conf;
-	enum rte_filter_type type;
-};
-
 int i40e_dev_switch_queues(struct i40e_pf *pf, bool on);
 int i40e_vsi_release(struct i40e_vsi *vsi);
 struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf,
@@ -1410,6 +1408,8 @@ int i40e_tunnel_filter_match_key_get(struct i40e_pf *pf,
 				 struct i40e_tunnel_filter_match_key *out);
 struct i40e_tunnel_state *i40e_tunnel_state_attach(struct rte_eth_dev_data *dev_data);
 void i40e_tunnel_state_detach(struct i40e_tunnel_state *state);
+struct i40e_rss_state *i40e_rss_state_attach(struct rte_eth_dev_data *dev_data);
+void i40e_rss_state_detach(struct i40e_rss_state *state);
 int i40e_tunnel_filter_program(struct i40e_pf *pf,
 			       struct i40e_tunnel_filter_conf *tunnel_filter,
 			       uint8_t add);
drivers/net/intel/i40e/i40e_flow.c +10 -279

347 lines not on this page · raw message

drivers/net/intel/i40e/i40e_flow.h +3 -0
index 28e342210ce..31269d4d93a 100644
@@ -23,5 +23,8 @@ extern const struct ci_flow_engine i40e_flow_engine_tunnel_nvgre;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_mpls;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_gtp;
 extern const struct ci_flow_engine i40e_flow_engine_tunnel_l4;
+extern const struct ci_flow_engine i40e_flow_engine_hash_pattern;
+extern const struct ci_flow_engine i40e_flow_engine_hash_vlan;
+extern const struct ci_flow_engine i40e_flow_engine_hash_empty;
 
 #endif /* _I40E_FLOW_H_ */
drivers/net/intel/i40e/i40e_flow_hash.c new file +1441 -0

1,441 lines not on this page · raw message

drivers/net/intel/i40e/i40e_hash.c +50 -1158

1,056 lines not on this page · raw message

drivers/net/intel/i40e/i40e_hash.h +6 -13
index 3bf30cdee55..c1ce372b6ea 100644

Lines of this file are not shown. View the raw message.

drivers/net/intel/i40e/meson.build +1 -0
index 0db60c1e99e..07162a93b30 100644

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.52.0

Part of the diff is not shown here. View the raw message.

#104 lore raw

Re: [PATCH v3 18/19] net/i40e: reimplement hash parser

Quoted 15 lines · Anatoly Burakov

Use the new flow graph API and the common parsing framework to implement flow parser for RSS.

The RSS parser was bypassing the other generic infrastructure, probably because it was very convoluted and did not map onto that model very well. It has now been made a first-class citizen.

The hash parser is multiple engines that share state, so each engine will attach to the shared refcounted RSS configuration state.

Additionally, this is the final engine in i40e, so remaining legacy flow infrastructure has been removed.

---

<snip>

Quoted 6 lines
drivers/net/intel/i40e/i40e_flow_hash.c new file +6 -0
+static int
+i40e_hash_flow_query(struct ci_flow *flow,
+		const struct rte_flow_action *action,
+		void *data,
+		struct rte_flow_error *error __rte_unused)
+{

drivers/net/intel/i40e/i40e_flow_hash.c line 1356

Quoted 10 lines
drivers/net/intel/i40e/i40e_flow_hash.c new file +10 -0
+	struct i40e_flow_engine_hash_flow *hash_flow = (struct i40e_flow_engine_hash_flow *)flow;
+	struct i40e_rte_flow_rss_conf *rss_conf = data;
+
+	if (action->type != RTE_FLOW_ACTION_TYPE_RSS) {
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, action,
+				"Unsupported action for query");
+	}
+
+	memcpy(rss_conf, &hash_flow->rss_conf, sizeof(*rss_conf));

seems like a buffer overflow. Destination (*data) is of the sizeof(struct rte_flow_action_rss) (splz ref to testpmd port_flow_query())

Quoted 3 lines
drivers/net/intel/i40e/i40e_flow_hash.c new file +3 -0
+	return 0;
+}
+

<snip>

Signature · 2 lines
Regards,
Vladimir
#105 lore raw

PATCH v3 19/19 net/i40e: advertise flow keep capability

In reply to Anatoly Burakov

The i40e driver's rte_flow implementation has always endeavored to keep flows across device restart, however it never advertised this capability. Advertise it explicitly.

---
 drivers/net/intel/i40e/i40e_ethdev.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/net/intel/i40e/i40e_ethdev.c +2 -1
index cca60e2889f..4e8a6a83b15 100644
@@ -3664,7 +3664,8 @@ i40e_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->dev_capa =
 		RTE_ETH_DEV_CAPA_RUNTIME_RX_QUEUE_SETUP |
 		RTE_ETH_DEV_CAPA_RUNTIME_TX_QUEUE_SETUP;
-	dev_info->dev_capa &= ~RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+	/* rte_flow rules are kept across dev_stop/dev_start and replayed on start */
+	dev_info->dev_capa |= RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
 
 	dev_info->hash_key_size = (I40E_PFQF_HKEY_MAX_INDEX + 1) *
 						sizeof(uint32_t);
Signature · 1 line
2.52.0
Acked-by on v3 19/19
#106 lore raw

Re: [PATCH v3 19/19] net/i40e: advertise flow keep capability

Quoted 23 lines · Anatoly Burakov

The i40e driver's rte_flow implementation has always endeavored to keep flows across device restart, however it never advertised this capability. Advertise it explicitly.

---
  drivers/net/intel/i40e/i40e_ethdev.c | 3 ++-
  1 file changed, 2 insertions(+), 1 deletion(-)
drivers/net/intel/i40e/i40e_ethdev.c +2 -1
index cca60e2889f..4e8a6a83b15 100644
@@ -3664,7 +3664,8 @@ i40e_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
  	dev_info->dev_capa =
  		RTE_ETH_DEV_CAPA_RUNTIME_RX_QUEUE_SETUP |
  		RTE_ETH_DEV_CAPA_RUNTIME_TX_QUEUE_SETUP;
-	dev_info->dev_capa &= ~RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
+	/* rte_flow rules are kept across dev_stop/dev_start and replayed on start */
+	dev_info->dev_capa |= RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP;
 
  	dev_info->hash_key_size = (I40E_PFQF_HKEY_MAX_INDEX + 1) *
  						sizeof(uint32_t);
Signature · 2 lines
Regards,
Vladimir
#107 lore raw
In reply to Anatoly Burakov
Quoted 36 lines · On Wed, 16 Sep 2026 13:18:06 +0100 Anatoly Burakov <[email protected]>…

Most rte_flow parsers in DPDK suffer from huge implementation complexity because even though 99% of what people use rte_flow parsers for is parsing protocol graphs, no parser is written explicitly as a graph. This patchset attempts to suggest a viable model to build rte_flow parsers as graphs, by offering a lightweight header only library to build rte_flow parsering graphs without too much boilerplate and complexity.

Most of the patchset is about Intel drivers, but they are meant as reimplementations as well as examples for the rest of the community to assess how to build parsers using this new infrastructure. I expect the first two patches will be of most interest to non-Intel reviewers, as they deal with building two reusable parser architecture pieces.

The first piece is a new flow graph helper in ethdev. Its purpose is deliberately narrow: it targets the protocol-graph part of rte_flow pattern parsing, where drivers walk packet headers and validate legal item sequences and parameters. That does not cover all possible rte_flow features, especially more exotic flow items, but it does cover a large and widely shared part of what existing drivers need to do. Or, to put it in other words, the only flow items this infrastructure *doesn't* cover is things that do not lend themselves well to be parsed as a graph of protocol headers (e.g. conntrack items). Everything else should be covered or cover-able. In practice, just about all drivers will benefit from graph parsing as all but one of them implement only the protocol stack parts, which are the ones targeted by the graph helper.

The second piece is a reusable flow engine framework for Intel Ethernet drivers. This is kept Intel-local because I do not feel it is even appropriate to define such a framework for all drivers to use in the first place. Even so, the intent is to establish a cleaner parser architecture with a defined interaction model, explicit memory ownership rules, locking, initialization sequence, implementations of rte_flow API entry points, flow replay and memory cleanup, and engine definitions that do not block secondary-process-safe usage. It is my hope that this would serve as a model for other drivers to follow, expand on, rework, and improve, so that maybe down the line we *might* have a common rte_flow infrastructure for drivers to use.

Quoted 8 lines

Most of the rest of the series is parser reimplementation, but that is mainly the vehicle for demonstrating and validating those two pieces. ixgbe and i40e are wired into the new common parsing path, and their existing parsers are migrated incrementally to the graph-based model. Besides reducing ad hoc parser code, this also makes validation more explicit and more consistent. In a few places that means invalid inputs that were previously ignored, deferred, or interpreted loosely are now rejected earlier and more strictly, without any increase in code complexity (in fact, with marked *decrease* of it!).

AI review has lots of feedback (as always treat with skepticism)

Patch 1/19 (ethdev: add flow graph API)

Error

flow_graph.h breaks the build with -Dcheck_includes=true, in both the C and C++ driver header checks:

ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,

rte_flow_conv() is experimental, and chkincs-drv builds without ALLOW_EXPERIMENTAL_API:

flow_graph.h:54:9: error: 'rte_flow_conv' is deprecated: Symbol is not yet part of stable ABI [-Werror=deprecated-declarations]

Either promote rte_flow_conv() to stable (it has been experimental since 18.11), or do not call it from an installed inline helper.

Warning

The "non-END nodes must have edges" check never fires for the start node:

  if (node->type != RTE_FLOW_ITEM_TYPE_END &&
          graph->edges[node_idx].next == NULL) {

The start node has no .type (per the doc and every graph in the series), so its type is RTE_FLOW_ITEM_TYPE_END (0) and the check is skipped. A graph with no edge list for START then dereferences NULL here:

  next_nodes = graph->edges[cur_idx].next;
  for (edge_idx = 0; next_nodes[edge_idx] != FLOW_GRAPH_NODE_EDGE_END;

Test node_idx == FLOW_GRAPH_NODE_FIRST as well as the END type.

Info

FLOW_GRAPH_LOG declares
extern int RTE_CONCAT(RTE_COMPONENT_NAME, _logtype_driver);
in an installed driver SDK header. Any driver that includes it but registers its logtype under another name fails to link. Consider taking the logtype from the caller or using RTE_LOG_DEFAULT_LOGTYPE.

Installed header exports unprefixed names (struct flow_graph, flow_graph_parse, FLOW_GRAPH_*) and file-scope identifiers with a leading underscore (_flow_graph_*), which are reserved.

struct flow_graph and struct flow_graph_edge use non-const pointers (struct flow_graph_node *nodes, size_t *next), so driver node tables holding function pointers cannot be placed in read-only data.

When _flow_graph_find_next_node() fails because the graph is malformed, flow_graph_parse() overwrites that error with "Pattern item not supported", which hides the real cause.

flow_graph.rst: "The the ``END`` node can also have callbacks".

Patch 2/19 (net/intel/common: add flow engines infrastructure)

Warning

The commit message and header claim secondary-process compatibility, but the engine list pointer is stored in shared memory:

  const struct ci_flow_engine_list *engines;
  ...
  engine_conf->engines = engine_list;

ci_flow_engine_conf is embedded in dev_private (e.g.
struct ci_flow_engine_conf flow_engine_conf;
in struct ixgbe_adapter), and engine_list is the address of driver static data in the primary process. ci_flow_engine_ref_from_idx() dereferences engine_conf->engines->engines[engine_idx], which is not valid in a secondary process mapped at a different address. Pass the engine list from the driver on each call, or resolve it per process, instead of storing it in the conf.

Info

ci_flow_replay(), ci_flow_cleanup() and ci_flow_engine_conf_reset() log error.message after a callback failure:

CI_DRV_LOG(DEBUG, "engine '%s': failed to install flow: %s",

          engine_ref.engine->name, error.message);

error is zero-initialised, so a callback that returns non-zero without calling rte_flow_error_set() passes NULL to %s.

ci_flow_flush() and ci_flow_cleanup() leave a flow on the list, and leak it, when ci_flow_is_valid() fails:

if (!ci_flow_is_valid(flow, engine_conf))

          continue;

ci_flow_flush() returns 0 after an uninstall failure while leaving the caller's rte_flow_error populated.

Patch 3/19 (net/intel/common: add utility functions)

Info

Include guard comment does not match the guard:
#endif /* _INTEL_COMMON_FLOW_UTIL_H_ */
guard is _COMMON_INTEL_FLOW_UTIL_H_.

Patch 5/19 (net/ixgbe: reimplement ethertype parser)

Warning

ixgbe_dev_stop() now turns off PTP timestamping:

/* disable timestamping; the application must re-enable it after restart */ if (adapter->filter.timesync_installed)

          ixgbe_timesync_disable(dev);

This is a user-visible behaviour change (timesync no longer survives stop/start) with no release note in the patch.

Series

Patches 4-11 and 18 do not apply to main; the index lines reference blobs not in the upstream history. Please state the base tree and any dependent series (cover letter or base-commit).

Review-Result: ERROR

#108 lore raw

v4 cover Building a better rte_flow parser

In reply to Anatoly Burakov

Most rte_flow parsers in DPDK suffer from huge implementation complexity because even though 99% of what people use rte_flow parsers for is parsing protocol graphs, no parser is written explicitly as a graph. This patchset attempts to suggest a viable model to build rte_flow parsers as graphs, by offering a lightweight header only library to build rte_flow parsering graphs without too much boilerplate and complexity.

Most of the patchset is about Intel drivers, but they are meant as reimplementations as well as examples for the rest of the community to assess how to build parsers using this new infrastructure. I expect the first two patches will be of most interest to non-Intel reviewers, as they deal with building two reusable parser architecture pieces.

The first piece is a new flow graph helper in ethdev. Its purpose is deliberately narrow: it targets the protocol-graph part of rte_flow pattern parsing, where drivers walk packet headers and validate legal item sequences and parameters. That does not cover all possible rte_flow features, especially more exotic flow items, but it does cover a large and widely shared part of what existing drivers need to do. Or, to put it in other words, the only flow items this infrastructure *doesn't* cover is things that do not lend themselves well to be parsed as a graph of protocol headers (e.g. conntrack items). Everything else should be covered or cover-able. In practice, just about all drivers will benefit from graph parsing as all but one of them implement only the protocol stack parts, which are the ones targeted by the graph helper.

The second piece is a reusable flow engine framework for Intel Ethernet drivers. This is kept Intel-local because I do not feel it is even appropriate to define such a framework for all drivers to use in the first place. Even so, the intent is to establish a cleaner parser architecture with a defined interaction model, explicit memory ownership rules, locking, initialization sequence, implementations of rte_flow API entry points, flow replay and memory cleanup, and engine definitions that do not block secondary-process-safe usage. It is my hope that this would serve as a model for other drivers to follow, expand on, rework, and improve, so that maybe down the line we *might* have a common rte_flow infrastructure for drivers to use.

Most of the rest of the series is parser reimplementation, but that is mainly the vehicle for demonstrating and validating those two pieces. ixgbe and i40e are wired into the new common parsing path, and their existing parsers are migrated incrementally to the graph-based model. Besides reducing ad hoc parser code, this also makes validation more explicit and more consistent. In a few places that means invalid inputs that were previously ignored, deferred, or interpreted loosely are now rejected earlier and more strictly, without any increase in code complexity (in fact, with marked *decrease* of it!).

v3 -> v4:
- Added a "get default mask" flow API
- Simplified graph constraints implementation
- Reworked all graph engines to use default flow mask if user didn't provide one
- Split out ixgbe ethertype refactor into a separate patch
- Split out SYN and ntuple priority fixes as separate patches
- Refactored ixgbe RSS configuration structure
- Address Vladimir's feedback for specific engines

v2 -> v3:
- Rebase on top of latest next-net-intel
- Cleanup pass on naming of engines and hash tables
- Added more logging to flow engine initializations
- Introduced a lightweight refcount to initialize shared resources
- Some small refactors in how ixgbe flow director engine keeps its state
- Fixed typos in documentation

v1 -> v2:
- Renamed rte_flow_graph to flow_graph
- Addressed some comments from Stephen's AI review
- Removed the rte_flow_conv hack on account of Stephen's upcoming patches making the API stable
- Split up "flow_install" into register and install steps, which enabled two new use cases: cleanup of memory without HW deregistration, and flow configuration replay after dev_start
- More aggressive refactoring of all engines on account of new pieces of infrastructure, which allows for reduced complexity of implementation
- Combined i40e tunnel engine patches into one patch
- Added i40e flow director refactoring to cleanly support the new API as well as the legacy PMD-specific API

RFC -> v1:
- Fixed a lot of bugs, concurrency issues, and init/uninit sequence problems in Intel common flow engine implementation
- Fixed a few copypaste errors in various engines
- Improved comment documentation and flow engine docs

Anatoly Burakov (24):
ethdev: add default mask query to flow
ethdev: add flow graph API net/intel/common: add flow engines infrastructure net/intel/common: add utility functions net/ixgbe: add support for common flow parsing net/ixgbe: make ethertype filter table dynamic net/ixgbe: reimplement ethertype parser net/ixgbe: fix syn filter priority net/ixgbe: reimplement syn parser net/ixgbe: reimplement L2 tunnel parser net/ixgbe: fix ntuple filter priority net/ixgbe: reimplement ntuple parser net/ixgbe: reimplement security parser net/ixgbe: reimplement FDIR parser net/ixgbe: don't embed RSS conf in filter structs net/ixgbe: reimplement hash parser net/ixgbe: advertise flow keep capability net/i40e: add support for common flow parsing net/i40e: reimplement ethertype parser net/i40e: refactor FDIR engine infrastructure net/i40e: reimplement FDIR parser net/i40e: reimplement tunnel parsers net/i40e: reimplement hash parser net/i40e: advertise flow keep capability

 doc/guides/prog_guide/ethdev/flow_graph.rst   |  750 +++
 doc/guides/prog_guide/ethdev/index.rst        |    1 +
 doc/guides/rel_notes/release_26_11.rst        |   22 +-
 drivers/net/intel/common/flow_engine.h        | 1429 ++++++
 drivers/net/intel/common/flow_util.h          |  263 ++
 drivers/net/intel/i40e/i40e_ethdev.c          | 1523 +-----
 drivers/net/intel/i40e/i40e_ethdev.h          |  322 +-
 drivers/net/intel/i40e/i40e_fdir.c            | 1126 +++--
 drivers/net/intel/i40e/i40e_flow.c            | 4185 +----------------
 drivers/net/intel/i40e/i40e_flow.h            |   30 +
 drivers/net/intel/i40e/i40e_flow_ethertype.c  |  351 ++
 drivers/net/intel/i40e/i40e_flow_fdir.c       | 2154 +++++++++
 drivers/net/intel/i40e/i40e_flow_hash.c       | 1454 ++++++
 drivers/net/intel/i40e/i40e_flow_tunnel.c     | 1958 ++++++++
 drivers/net/intel/i40e/i40e_hash.c            | 1212 +----
 drivers/net/intel/i40e/i40e_hash.h            |   19 +-
 drivers/net/intel/i40e/meson.build            |    4 +
 drivers/net/intel/i40e/rte_pmd_i40e.c         |   12 +-
 drivers/net/intel/ixgbe/ixgbe_ethdev.c        | 1015 +---
 drivers/net/intel/ixgbe/ixgbe_ethdev.h        |  289 +-
 drivers/net/intel/ixgbe/ixgbe_fdir.c          |  606 +--
 drivers/net/intel/ixgbe/ixgbe_flow.c          | 3233 +------------
 drivers/net/intel/ixgbe/ixgbe_flow.h          |   27 +
 .../net/intel/ixgbe/ixgbe_flow_ethertype.c    |  299 ++
 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c     | 1626 +++++++
 drivers/net/intel/ixgbe/ixgbe_flow_hash.c     |  216 +
 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c    |  322 ++
 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c   |  694 +++
 drivers/net/intel/ixgbe/ixgbe_flow_security.c |  586 +++
 drivers/net/intel/ixgbe/ixgbe_flow_syn.c      |  312 ++
 drivers/net/intel/ixgbe/ixgbe_ipsec.c         |  361 +-
 drivers/net/intel/ixgbe/ixgbe_ipsec.h         |   50 +-
 drivers/net/intel/ixgbe/ixgbe_pf.c            |   49 +-
 drivers/net/intel/ixgbe/ixgbe_rxtx.c          |   80 +-
 .../net/intel/ixgbe/ixgbe_rxtx_vec_common.c   |    4 +-
 drivers/net/intel/ixgbe/meson.build           |    7 +
 lib/ethdev/flow_graph.c                       |  263 ++
 lib/ethdev/flow_graph.h                       |  149 +
 lib/ethdev/meson.build                        |    2 +
 lib/ethdev/rte_flow.c                         |  207 +-
 lib/ethdev/rte_flow.h                         |   15 +
 41 files changed, 14878 insertions(+), 12349 deletions(-)
 create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst
 create mode 100644 drivers/net/intel/common/flow_engine.h
 create mode 100644 drivers/net/intel/common/flow_util.h
 create mode 100644 drivers/net/intel/i40e/i40e_flow.h
 create mode 100644 drivers/net/intel/i40e/i40e_flow_ethertype.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_fdir.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_hash.c
 create mode 100644 drivers/net/intel/i40e/i40e_flow_tunnel.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow.h
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ethertype.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_fdir.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_hash.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_l2tun.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_ntuple.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_security.c
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow_syn.c
 create mode 100644 lib/ethdev/flow_graph.c
 create mode 100644 lib/ethdev/flow_graph.h
Signature · 1 line
2.52.0
#109 lore raw

PATCH v4 1/24 ethdev: add default mask query to flow

The rte_flow API defines that if the mask provided with the flow item is NULL, a default mask should be used where applicable. There is no API to get such mask, so add this API to rte_flow.

---
 doc/guides/rel_notes/release_26_11.rst |   5 +
 lib/ethdev/rte_flow.c                  | 207 ++++++++++++++++++-------
 lib/ethdev/rte_flow.h                  |  15 ++
 3 files changed, 167 insertions(+), 60 deletions(-)
doc/guides/rel_notes/release_26_11.rst +5 -0
index 3fb7eac25db..c1656895996 100644
@@ -64,6 +64,11 @@ New Features
 
   Added ``rte_vlan_insert_tpid()`` to the net library.
 
+* **Added flow item default mask lookup.**
+
+  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` conversion operation
+  to ``rte_flow_conv()`` to retrieve the default mask of a flow item type.
+
 * **Updated AF_XDP driver.**
 
   * Changed the default device plugin endpoint path used when
lib/ethdev/rte_flow.c +147 -60

246 lines not on this page · raw message

lib/ethdev/rte_flow.h +15 -0
index 1bfe1b3f619..df8101e4395 100644
@@ -4411,6 +4411,21 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert item type to pointer to its default mask.
+	 *
+	 * Writes a pointer to rte_flow_item_{name}_mask to @p dst,
+	 * or NULL if the item type has no default mask. The mask itself is not
+	 * copied; instead, a unique pointer to an internal static constant
+	 * storage is written to @p dst.
+	 *
+	 * - @p src type:
+	 *   @code (const void *)enum rte_flow_item_type @endcode
+	 * - @p dst type:
+	 *   @code const void ** @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
 };
 
 /**
Signature · 1 line
2.52.0
#110 lore raw

PATCH v4 2/24 ethdev: add flow graph API

In reply to Anatoly Burakov

This commit adds a flow graph parsing API. This is a helper API intended to help ethdev drivers implement rte_flow parsers, as common usages map to graph traversal problem very well.

Features provided by the API:
- Flow graph, edge, and node definitions
- Graph traversal logic
- Declarative validation against common flow item types
- Per-node validation and state processing callbacks

---
 doc/guides/prog_guide/ethdev/flow_graph.rst | 750 ++++++++++++++++++++
 doc/guides/prog_guide/ethdev/index.rst      |   1 +
 doc/guides/rel_notes/release_26_11.rst      |   5 +
 lib/ethdev/flow_graph.c                     | 263 +++++++
 lib/ethdev/flow_graph.h                     | 149 ++++
 lib/ethdev/meson.build                      |   2 +
 6 files changed, 1170 insertions(+)
 create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst
 create mode 100644 lib/ethdev/flow_graph.c
 create mode 100644 lib/ethdev/flow_graph.h
doc/guides/prog_guide/ethdev/flow_graph.rst new file +750 -0
new file mode 100644
index 00000000000..daa5d86f511
@@ -0,0 +1,750 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Intel Corporation
+
+Flow Graph Parser
+=================
+
+Introduction
+------------
+
+The flow graph parser is a helper library for PMD drivers that implements ``rte_flow`` pattern matching.
+It lets a driver declare the protocol sequences it supports as a directed graph of nodes and edges.
+It then validates and extracts fields from an ``rte_flow_item`` pattern in a single traversal.
+
+The library is declared in ``flow_graph.h`` and built as part of ethdev.
+It is an internal API, available to drivers only.
+Parse diagnostics are logged at debug level using the ethdev log type.
+
+Scope and Limitations
+~~~~~~~~~~~~~~~~~~~~~
+
+Because the parser is graph-based, it is well suited for matching *protocol stacks*.
+These are sequences of protocol headers such as ``ETH / IPv4 / TCP``.
+Any pattern that can be expressed as "from protocol A, transitions to protocol B or C are allowed" fits naturally into the graph model.
+
+The library is **not** designed to cover every ``rte_flow`` item type.
+Items that do not represent a position in a protocol stack do not have a natural place in a protocol graph.
+This includes conntrack state, meter color, and other metadata items.
+Such items are best handled outside the graph, either before or after the graph parse call.
+
+Defining a Graph
+----------------
+
+A graph consists of three parts:
+
+1. An **enum** that assigns a numeric index to every node.
+2. A **node array** (``struct flow_graph_node[]``) indexed by that enum.
+3. An **edge array** (``struct flow_graph_edge[]``) also indexed by that enum, describing allowed transitions.
+
+These parts are bundled together in a ``struct flow_graph``.
+
+The running example used throughout this guide models the following protocol graph::
+
+   START -> ETH -> [VLAN] -> (IPv4 | IPv6) -> [(TCP | UDP | SCTP)] -> END
+
+Brackets ``[...]`` denote optional items.
+Parentheses ``(...)`` denote a required choice between alternatives.
+The key ideas are:
+
+* ``ETH`` is required after ``START``.
+* After ``ETH``, an optional ``VLAN`` may appear, but the pattern must then see an IP layer.
+* After an IP layer, an optional transport layer may appear; it may be TCP, UDP, or SCTP, after which the pattern reaches ``END``.
+
+Node Enum
+~~~~~~~~~
+
+Every node needs a stable index.
+The first node **must** be at index ``FLOW_GRAPH_NODE_FIRST`` (which is 0).
+This is the *start node*.
+It is used only as a traversal anchor and must not carry callbacks.
+
+.. code-block:: c
+
+   enum example_node_id {
+       EXAMPLE_NODE_START = FLOW_GRAPH_NODE_FIRST,
+       EXAMPLE_NODE_ETH,
+       EXAMPLE_NODE_VLAN,
+       EXAMPLE_NODE_IPV4,
+       EXAMPLE_NODE_IPV6,
+       EXAMPLE_NODE_TCP,
+       EXAMPLE_NODE_UDP,
+       EXAMPLE_NODE_SCTP,
+       EXAMPLE_NODE_END,
+       /* keep last */
+       EXAMPLE_NODE_MAX,
+   };
+
+Node Definitions
+~~~~~~~~~~~~~~~~
+
+Each node maps to one ``rte_flow_item_type``.
+It can also carry a *validate* callback, a *process* callback, and a set of *constraints*.
+The the ``END`` node can also have callbacks to perform end-of-match processing.
+
+A minimal skeleton (callbacks and constraints are added in later sections):
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+               /* Start node: no type, no callbacks */
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name  = "ETH",
+               .type  = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name  = "VLAN",
+               .type  = RTE_FLOW_ITEM_TYPE_VLAN,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name  = "IPV4",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name  = "IPV6",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV6,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name  = "TCP",
+               .type  = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name  = "UDP",
+               .type  = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name  = "SCTP",
+               .type  = RTE_FLOW_ITEM_TYPE_SCTP,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name  = "END",
+               .type  = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+   };
+
+Edge Definitions
+~~~~~~~~~~~~~~~~
+
+Edges express which nodes may follow the current one.
+Every edge list is terminated by the ``FLOW_GRAPH_NODE_EDGE_END`` sentinel.
+All non-``END`` nodes **must** have an edge list.
+The ``END`` node itself does not need one.
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           /* ... same nodes as above ... */
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+Reading the edges back:
+
+* From ``START``, the parser can only reach ``ETH``, which makes ``ETH`` required.
+* From ``ETH``, the parser can reach ``VLAN``, ``IPV4``, or ``IPV6``, which makes ``VLAN`` optional.
+* From ``IPV4`` or ``IPV6``, the parser can reach ``TCP``, ``UDP``, ``SCTP``, or ``END``, which makes the transport layer optional.
+
+Assembling the Graph
+~~~~~~~~~~~~~~~~~~~~
+
+With nodes and edges defined inline, assembling the graph is just a matter of
+combining the two arrays into a single compound literal:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START"
+           },
+           [EXAMPLE_NODE_ETH]   = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH
+           },
+           /* ... remaining nodes ... */
+           [EXAMPLE_NODE_END]   = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END
+               }
+           },
+           /* ... remaining edges ... */
+       },
+   };
+
+Callbacks
+---------
+
+The graph calls up to two callbacks on every visited node: *validate* and *process*.
+
+Both callbacks share the same return convention.
+On success they must return ``0``.
+On failure they must call ``rte_flow_error_set`` to record a descriptive error and return its result.
+
+Validate Callback
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*flow_graph_node_validate_fn)(
+       const void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **read-only** context pointer.
+The canonical intent is that it should check whether the item's spec, mask, and last values are acceptable for the driver.
+On failure it returns the result of ``rte_flow_error_set`` (see the return convention above).
+
+It is recommended to use this callback for **all checks that can reject a rule**.
+This includes unsupported mask bits, conflicting field combinations, hardware limitations, and other applicable criteria.
+
+Process Callback
+~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*flow_graph_node_process_fn)(
+       void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **mutable** context pointer.
+The canonical expectation is that it should extract the fields needed for hardware programming.
+It should then store extracted data in the driver's context structure.
+On the rare failure path it returns the result of ``rte_flow_error_set`` (see the return convention above).
+
+It is recommended to use this callback for the **happy path**.
+For example, it can copy addresses, ports, and protocol IDs into the driver context so they can be programmed later.
+
+Defining a Context Structure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The opaque ``ctx`` pointer passed to every callback is driver-defined.
+A typical context accumulates the parsed protocol fields:
+
+.. code-block:: c
+
+   struct example_parsed_flow {
+       /* L2 */
+       struct rte_ether_addr dst_mac;
+       bool has_vlan;
+       uint16_t vlan_tci;
+
+       /* L3 */
+       bool is_ipv6;
+       rte_be32_t ipv4_src;
+       rte_be32_t ipv4_dst;
+       uint8_t ipv6_src[16];
+       uint8_t ipv6_dst[16];
+
+       /* L4 */
+       enum rte_flow_item_type l4_proto;
+       rte_be16_t src_port;
+       rte_be16_t dst_port;
+   };
+
+These fields are meant to reflect the structure used by the driver to programming hardware with.
+
+Callback Example
+~~~~~~~~~~~~~~~~
+
+Below is a validate/process pair for the IPv4 node.
+The validate callback rejects unsupported mask bits.
+The process callback copies addresses into the context:
+
+.. code-block:: c
+
+   static int
+   example_validate_ipv4(const void *ctx __rte_unused,
+                         const struct rte_flow_item *item,
+                         struct rte_flow_error *error)
+   {
+       const struct rte_flow_item_ipv4 *mask = item->mask;
+
+       /* spec is guaranteed by node constraints, mask may be NULL */
+       if (mask == NULL)
+           rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask, sizeof(mask),
+                   (const void *)(uintptr_t)item->type, NULL);
+
+       if (mask->hdr.version_ihl ||
+           mask->hdr.type_of_service ||
+           mask->hdr.total_length ||
+           mask->hdr.packet_id ||
+           mask->hdr.fragment_offset ||
+           mask->hdr.time_to_live ||
+           mask->hdr.next_proto_id ||
+           mask->hdr.hdr_checksum) {
+           return rte_flow_error_set(error, EINVAL,
+                   RTE_FLOW_ERROR_TYPE_ITEM, item,
+                   "Only src/dst addresses supported");
+       }
+       return 0;
+   }
+
+   static int
+   example_process_ipv4(void *ctx,
+                        const struct rte_flow_item *item,
+                        struct rte_flow_error *error __rte_unused)
+   {
+       struct example_parsed_flow *parsed = ctx;
+       const struct rte_flow_item_ipv4 *spec = item->spec;
+
+       parsed->is_ipv6 = false;
+       if (spec != NULL) {
+           parsed->ipv4_src = spec->hdr.src_addr;
+           parsed->ipv4_dst = spec->hdr.dst_addr;
+       }
+       return 0;
+   }
+
+Add the callbacks to the node definition:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name      = "IPV4",
+       .type      = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate  = example_validate_ipv4,
+       .process   = example_process_ipv4,
+   },
+
+Node Constraints
+----------------
+
+Many nodes share common requirements about which combination of ``spec``, ``mask``, and ``last`` pointers an item must carry.
+Instead of checking these in every validate callback, they can be declared via the ``constraints`` field.
+The field uses ``flow_graph_node_expect`` flags.
+
+Available constraint flags (may be ORed together):
+
+``FLOW_GRAPH_NODE_EXPECT_NONE``
+   Default. Any valid combination is accepted (empty, spec with optional mask,
+   or spec and last with optional mask).
+
+``FLOW_GRAPH_NODE_EXPECT_EMPTY``
+   The item must have ``spec == NULL``, ``mask == NULL``, and
+   ``last == NULL``.
+
+``FLOW_GRAPH_NODE_EXPECT_SPEC``
+   ``spec`` is required, ``mask`` is optional; ``last`` must be NULL.
+
+``FLOW_GRAPH_NODE_EXPECT_RANGE``
+   ``spec`` and ``last`` are required, ``mask`` is optional.
+
+Items that are invalid per the rte_flow API (``mask`` or ``last`` without ``spec``)
+are always rejected, regardless of node constraints.
+When ``spec`` is set but ``mask`` is NULL, the item type's default mask applies
+(see ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR``).
+
+Multiple flags can be ORed together.
+The item is accepted if **any one** of the flagged constraints is satisfied.
+The purpose is to express the difference between "must" (i.e. accept ONLY this), and "may" (i.e. accept this OR that).
+
+For example, an IPv4 node that requires a spec, with or without an explicit mask:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name        = "IPV4",
+       .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate    = example_validate_ipv4,
+       .process     = example_process_ipv4,
+       .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+   },
+
+An Ethernet node that may appear empty (no spec/mask) or with spec+mask:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_ETH] = {
+       .name        = "ETH",
+       .type        = RTE_FLOW_ITEM_TYPE_ETH,
+       .validate    = example_validate_eth,
+       .process     = example_process_eth,
+       .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY |
+                      FLOW_GRAPH_NODE_EXPECT_SPEC,
+   },
+
+Constraints are checked **before** the validate callback is invoked.
+
+Ignoring Item Types
+~~~~~~~~~~~~~~~~~~~
+
+The ``ignore_nodes`` field on ``struct flow_graph`` is an optional complement to node constraints.
+When the pattern may contain item types that are irrelevant to the driver, list them in ``ignore_nodes``.
+For example, metadata items like ``RTE_FLOW_ITEM_TYPE_MARK`` do not represent a protocol header.
+The parser skips ignored items silently without advancing the current graph position:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       /* ... nodes and edges ... */
+       .ignore_nodes = (enum rte_flow_item_type[]){
+           RTE_FLOW_ITEM_TYPE_MARK,
+           RTE_FLOW_ITEM_TYPE_END,
+       },
+   };
+
+``RTE_FLOW_ITEM_TYPE_VOID`` is always ignored regardless of this list.
+Omit ``ignore_nodes`` entirely when no additional item types need to be skipped.
+
+Calling the Parser
+------------------
+
+``flow_graph_parse`` walks the pattern against the graph:
+
+.. code-block:: c
+
+   int
+   flow_graph_parse(const struct flow_graph *graph,
+                        const struct rte_flow_item *pattern,
+                        struct rte_flow_error *error,
+                        void *ctx);
+
+A typical call site looks like this:
+
+.. code-block:: c
+
+   struct example_parsed_flow parsed;
+   int ret;
+
+   memset(&parsed, 0, sizeof(parsed));
+
+   ret = flow_graph_parse(&example_graph, pattern, error, &parsed);
+   if (ret != 0)
+       return ret;
+
+   /* 'parsed' now contains the extracted protocol fields */
+
+The function returns success or failure, with ``error`` populated.
+
+Error conditions:
+
+* **Graph is NULL** — for example, when the graph pointer itself is not provided.
+* **Pattern is NULL**.
+* **Unsupported transition** — when an item type has no matching edge from the current node.
+    This is the primary way the graph rejects unsupported protocol sequences.
+* **Constraint failure** — when the spec, mask, and last combination does not satisfy the node's declared constraints.
+* **Validate callback failure** — when driver-specific validation rejects the item.
+* **Process callback failure** — when driver-specific extraction path fails.
+
+.. warning::
+
+    Malformed graph tables (for example invalid node indices, missing sentinels,
+    or otherwise inconsistent driver-defined graph structures) are considered to be a driver implementation bug.
+    Graphs are trusted by default: driver-owned graph structures are expected to be valid and are not fully validated.
+
+The traversal processes items in order, skipping ignored types.
+After the last non-``END`` item, the parser looks for an ``END`` node reachable from the current position.
+It then visits that node and runs its callbacks, if any.
+This means drivers can attach a process callback to the ``END`` node for post-traversal finalization.
+
+Putting It All Together
+-----------------------
+
+The complete graph definition with callbacks and constraints:
+
+.. code-block:: c
+
+   const struct flow_graph example_graph = {
+       .nodes = (struct flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name        = "ETH",
+               .type        = RTE_FLOW_ITEM_TYPE_ETH,
+               .validate    = example_validate_eth,
+               .process     = example_process_eth,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name        = "VLAN",
+               .type        = RTE_FLOW_ITEM_TYPE_VLAN,
+               .process     = example_process_vlan,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name        = "IPV4",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+               .validate    = example_validate_ipv4,
+               .process     = example_process_ipv4,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name        = "IPV6",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV6,
+               .validate    = example_validate_ipv6,
+               .process     = example_process_ipv6,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name        = "TCP",
+               .type        = RTE_FLOW_ITEM_TYPE_TCP,
+               .validate    = example_validate_tcp,
+               .process     = example_process_tcp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name        = "UDP",
+               .type        = RTE_FLOW_ITEM_TYPE_UDP,
+               .validate    = example_validate_udp,
+               .process     = example_process_udp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name        = "SCTP",
+               .type        = RTE_FLOW_ITEM_TYPE_SCTP,
+               .process     = example_process_sctp,
+               .constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY
+                            | FLOW_GRAPH_NODE_EXPECT_SPEC,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (size_t[]){
+                   EXAMPLE_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+
+Tunnel Graphs and Repeated Item Types
+-------------------------------------
+
+Tunneled patterns often repeat the same ``rte_flow_item_type`` in outer and inner headers.
+A simple representative example is a TCP-IPv4 over GTP-U pattern::
+
+   ETH -> IPV4 -> UDP -> GTPU -> IPV4 -> TCP
+
+The graph library supports this naturally.
+Multiple nodes may use the same ``type`` value, as long as they are distinct nodes in the graph and reached through different edges.
+
+For tunnel parsing, the recommended style is to model repeated protocol types as separate inner/outer nodes, for example ``OUTER_IPV4`` and ``INNER_IPV4``.
+This makes the graph intent explicit, keeps callback logic clear, and avoids unexpected graph paths due to loops.
+
+.. code-block:: c
+
+   enum tunnel_node_id {
+       TUNNEL_NODE_START = FLOW_GRAPH_NODE_FIRST,
+       TUNNEL_NODE_ETH,
+       TUNNEL_NODE_OUTER_IPV4,
+       TUNNEL_NODE_TCP,
+       TUNNEL_NODE_UDP,
+       TUNNEL_NODE_GTPU,
+       TUNNEL_NODE_INNER_IPV4,
+       TUNNEL_NODE_END,
+       TUNNEL_NODE_MAX,
+   };
+
+   const struct flow_graph tunnel_graph = {
+       .nodes = (struct flow_graph_node[]){
+           /* Minimal topology example: callbacks and constraints are omitted. */
+           [TUNNEL_NODE_START] = { .name = "START" },
+           [TUNNEL_NODE_ETH] = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .name = "OUTER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_UDP] = {
+               .name = "UDP",
+               .type = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .name = "GTPU",
+               .type = RTE_FLOW_ITEM_TYPE_GTPU,
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .name = "INNER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_TCP] = {
+               .name = "TCP",
+               .type = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [TUNNEL_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct flow_graph_edge[]){
+           [TUNNEL_NODE_START] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_ETH,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_ETH] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_OUTER_IPV4,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_UDP,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_UDP] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_GTPU,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_INNER_IPV4,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_TCP,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_TCP] = {
+               .next = (size_t[]){
+                   TUNNEL_NODE_END,
+                   FLOW_GRAPH_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+In other words, traversal follows graph edges (node-to-node), while node matching is done against each candidate node's ``rte_flow_item_type``.
+That combination allows repeated protocol layers to be represented cleanly with separate nodes for different parsing contexts.
+
+Although arbitrary loops are possible in the graph, tunnel protocol graphs are usually easier to reason about when repeated item types are split into explicit inner/outer nodes.
+It is not recommended to create loops in the graph, as these loops will be unbounded.
doc/guides/prog_guide/ethdev/index.rst +1 -0
index 392ced0a2ef..b41fd045bb3 100644
@@ -10,6 +10,7 @@ Ethernet Device Library
     ethdev
     switch_representation
     flow_offload
+    flow_graph
     traffic_metering_and_policing
     traffic_management
     qos_framework
doc/guides/rel_notes/release_26_11.rst +5 -0
index c1656895996..b23eb389f7d 100644
@@ -69,6 +69,11 @@ New Features
   Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` conversion operation
   to ``rte_flow_conv()`` to retrieve the default mask of a flow item type.
 
+* **Added internal ethdev flow graph parser helper API.**
+
+  Added internal ``flow_graph`` API in ethdev
+  for PMD drivers to build graph-based flow pattern parsers.
+
 * **Updated AF_XDP driver.**
 
   * Changed the default device plugin endpoint path used when
lib/ethdev/flow_graph.c new file +263 -0

263 lines not on this page · raw message

lib/ethdev/flow_graph.h new file +149 -0

149 lines not on this page · raw message

lib/ethdev/meson.build +2 -0
index 8ba6c708a24..069bb7ec792 100644
@@ -6,6 +6,7 @@ sources = files(
         'ethdev_private.c',
         'ethdev_profile.c',
         'ethdev_trace_points.c',
+        'flow_graph.c',
         'rte_class_eth.c',
         'rte_ethdev.c',
         'rte_ethdev_cman.c',
@@ -40,6 +41,7 @@ driver_sdk_headers += files(
         'ethdev_pci.h',
         'ethdev_vdev.h',
         'rte_flow_driver.h',
+        'flow_graph.h',
         'rte_mtr_driver.h',
         'rte_tm_driver.h',
 )
Signature · 1 line
2.52.0
#111 lore raw

PATCH v4 3/24 net/intel/common: add flow engines infrastructure

In reply to Anatoly Burakov

Current implementation of flow engines in various drivers have a few issues that need to be corrected.

For one, some of them are fundamentally incompatible with secondary processes, because the flow engine registration and creation will allocate structures in shared memory but use process-local pointers to point to flow engines and pattern tables.

For another, a lot of them are needlessly complicated and rely on a separation between patterns and parsing that is hard to reason about and
maintain: they do not define memory ownership model, they do not define the way in which we approach parameter and pattern parsing, and they occasionally do weird things like passing around pointers-to-void-pointers or even using pointers as integer values.

Another common problem is extremely convoluted internal tracking, flow installation, flow replay, and cleanup code. This infrastructure is usually done in an ad-hoc manner that has a lot of boilerplate.

These issues can be corrected, but because of how much code there is to the current infrastructure and how tightly coupled it is, it would be easier to just build new one from scratch, and gradually migrate all engines to use it. This patch is intended as a first step towards that goal, and defines both common data types to be used by all rte_flow parsers, as well as the interaction model that is to be followed by all drivers.

We define a set of structures that will represent:

- Defined rte_flow parsing interaction model and code flow (ops struct)
- Defined memory allocation and ownership model for all engines
- Scratch space format for all engines (variably allocated typed struct)
- Flow rule format for all engines (variably allocated typed struct)
- Engine definitions that are compatible with secondary process model
- Implementations of common rte_flow operations
- Various supporting infrastructure for parser customization, e.g. hooks
- Support for using custom allocation (e.g. for mempool-based alloc)
- Support for replaying all flows to restore HW state
- Support for removing all flows without modifying HW state

The design intent is heavily documented right inside the header and is to be considered authoritative design document for how to build rte_flow parsers for Intel Ethernet drivers going forward.

---
 drivers/net/intel/common/flow_engine.h | 1429 ++++++++++++++++++++++++
 1 file changed, 1429 insertions(+)
 create mode 100644 drivers/net/intel/common/flow_engine.h
drivers/net/intel/common/flow_engine.h new file +1429 -0
new file mode 100644
index 00000000000..01df2728ccc
@@ -0,0 +1,1429 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_FLOW_ENGINE_H_
+#define _COMMON_INTEL_FLOW_ENGINE_H_
+
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/queue.h>
+
+#include <rte_bitops.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+
+#include <ethdev_driver.h>
+#include <rte_flow.h>
+#include <flow_graph.h>
+#include <rte_tailq.h>
+#include <rte_rwlock.h>
+#include <rte_hexdump.h>
+
+#include "log.h"
+
+/*
+ * This is a common header for Intel Ethernet drivers' flow engine
+ * implementations. It defines the interfaces and data structures required to
+ * implement flow rule engines that plug into the drivers' flow handling logic.
+ *
+ * Design considerations:
+ *
+ * 1. Ease of implementation
+ *
+ * The flow engine interface is kept simple, with obvious defaults (not
+ * specifying something leads to the most expected behavior in context). The
+ * point is not to produce a monstrous driver-within-a-driver framework, but to
+ * make engine definitions follow semantic expectations of what the engine does.
+ *
+ * All boilerplate (flow management, engine enablement tracking, etc.) is
+ * handled by the common flow infrastructure, so an engine only needs to parse
+ * and install/uninstall flow rules. Drivers are expected to use other common
+ * flow utilities where applicable (e.g. flow_util.h, flow_check.h).
+ *
+ * 2. Full secondary process compatibility
+ *
+ * To support rte_flow operations in secondary processes, the engines enabled
+ * for a driver instance are tracked and resolved at runtime. The engine index
+ * (its position in the engine list) is used as a bit position in a
+ * driver-specific 64-bit field of enabled engines, so engine definitions can
+ * live in read-only memory shared by primary and secondary processes. For this
+ * to be safe, engine lists and definitions must be immutable for process
+ * lifetime (declare them as const).
+ *
+ * This does not mean every driver supports rte_flow in secondary processes -
+ * that is still up to each driver. It only ensures the framework itself does not
+ * prevent it.
+ *
+ * Engine callbacks must not access or retain a `struct rte_eth_dev *`, as it is
+ * process-local; use the process-independent `struct rte_eth_dev_data *`
+ * provided by the framework instead.
+ *
+ * The per-instance engine configuration is set up and torn down exclusively by
+ * `ci_flow_engine_conf_init()` and `ci_flow_engine_conf_reset()`, which should
+ * only be called by the primary process at device setup/teardown.
+ *
+ * 3. Flow object lifecycle is framework-owned
+ *
+ * Engines should treat framework-provided context and flow objects as storage
+ * they fill in, not storage they own: engine logic focuses on the contents of
+ * flow data, while object lifetime is managed by the framework. Engines may
+ * still allocate auxiliary data, but only where the framework guarantees a
+ * matching teardown call that lets the engine release it.
+ *
+ * 4. Pattern parsing: flow_graph and pattern_parse callback
+ *
+ * The flow engine framework works hand-in-hand with the `flow_graph` parsing
+ * infrastructure. Each engine may provide a pattern graph that is used to match
+ * the flow pattern and extract relevant data into the engine context provided
+ * by the framework.
+ *
+ * Engines may also provide a `pattern_parse` callback that is invoked before
+ * the graph parser runs. This allows engines to handle pattern items that don't
+ * fit neatly into the graph model (e.g. FUZZY items that can appear at any
+ * position), or to bypass the graph parser entirely with custom parsing.
+ *
+ * Pattern contents cannot be ignored. This is by design, as ignoring them is
+ * considered rte_flow API misuse. By default, even in the empty fallback case,
+ * a meaningful pattern (one that is not empty or ANY) is treated as an error.
+ *
+ * 5. Setup, teardown, and flow list lifecycle ordering
+ *
+ * The expected sequence of calls for a driver instance is:
+ *
+ * - `ci_flow_engine_conf_init()` should run from the driver's `dev_init` path.
+ *
+ * - At `dev_close`, `ci_flow_cleanup()` should run first to drop all flows and
+ *   their internal tracking, followed by `ci_flow_engine_conf_reset()`.
+ *
+ * - Devices may or may not advertise `RTE_ETH_DEV_CAPA_FLOW_RULE_KEEP`, i.e.
+ *   support for keeping flow rules across a `dev_stop`/`dev_start` cycle.
+ *
+ * - If the device does not advertise this capability, flows must be flushed
+ *   via `ci_flow_flush()` as the first step of `dev_stop()` (flushing later
+ *   may interfere with flow uninstall).
+ *
+ * - If the flows are kept but  device loses its hardware state across a
+ *   `dev_stop`/`dev_start` cycle, the driver should call `ci_flow_replay()` as
+ *   the last step of `dev_start`.
+ */
+
+struct ci_flow_engine_ops;
+struct ci_flow_engine_ctx;
+struct ci_flow_engine;
+struct ci_flow_engine_ref;
+struct ci_flow_engine_list;
+struct ci_flow_engine_conf;
+struct ci_flow;
+
+/*
+ * Flow engine ops.
+ *
+ * Each flow engine provides a set of operations that make up the flow engine
+ * pipeline. Each stage has a setup step (run when a flow is created) and,
+ * where applicable, a teardown step (run when a flow is removed). Operations
+ * are compositions of stages: setup steps run in forward order, teardown steps
+ * in reverse. Optional callbacks are shown in [brackets].
+ *
+ * - Allocation stage
+ *     setup:    [flow_alloc]      teardown: [flow_free]
+ *   Engine-specific flow object allocation. Skipped on the validate path.
+ *
+ * - Parse stage
+ *     setup:    ctx_init -> [pattern_parse] -> [graph parser] ->
+ *               [ctx_finalize] -> [ctx_to_flow]
+ *     teardown: (none)
+ *   Interprets the request into the flow structure. Must NOT allocate.
+ *
+ * - Track stage
+ *     setup:    [flow_register]   teardown: [flow_unregister]
+ *   Updates driver-internal state. Permitted to allocate.
+ *
+ * - Apply stage
+ *     setup:    [flow_install]    teardown: [flow_uninstall]
+ *   Programs the hardware. Must NOT allocate.
+ *
+ * Typical operation sequences:
+ *
+ * - rte_flow_create:   Allocation -> Parse -> Track -> Apply   (setup)
+ * - rte_flow_validate: Parse
+ * - rte_flow_destroy:  Apply -> Track -> Allocation            (teardown)
+ * - rte_flow_flush:    Apply -> Track -> Allocation            (teardown, per flow)
+ * - ci_flow_replay:    Apply                                   (setup, per flow)
+ * - ci_flow_cleanup:   Track -> Allocation                     (teardown, per flow)
+ *
+ * 1) Engine availability and lifecycle
+ *
+ * Availability is checked by the driver at init time. The criteria are up to
+ * the driver (hardware capability bits, PHY type, devargs, etc.). If
+ * `engine_init` is not implemented, the engine is always available.
+ *
+ * If `priv_size` is non-zero, the framework allocates a zeroed private block
+ * and passes it to engine_init/engine_uninit. Each allocated flow also carries
+ * a pointer to this per-device data in `flow->engine_priv`.
+ *
+ * 2) Input parsing
+ *
+ * `ctx_init` is mandatory and is the gateway for processing actions and
+ * attributes into engine context.
+ *
+ * Patterns are matched against the pattern graph in `ci_flow_engine`, the
+ * `pattern_parse` callback, or both:
+ *
+ * - graph only: graph parser runs
+ * - callback only: pattern_parse runs
+ * - callback + graph: pattern_parse runs, then graph parser runs
+ * - neither: empty fallback matcher is used
+ *
+ * The empty fallback matcher accepts only a NULL pattern, an empty pattern
+ * (start -> end), or an ANY-only pattern (start -> any -> end).
+ *
+ * In callback + graph mode, the callback does not consume items: the graph
+ * parser sees the same pattern, so the graph should list the relevant
+ * `ignore_nodes`.
+ *
+ * 3) Rule finalization
+ *
+ * `ctx_finalize` is an optional final consistency check between parsed
+ * action/attribute data and pattern-derived data, and a place for any other
+ * post-parse work.
+ *
+ * 4) Rule materialization
+ *
+ * `ctx_to_flow` translates the parsed context into the engine-specific flow
+ * rule. The rule should contain only what is needed for installation or
+ * querying; temporary data belongs in the build context.
+ *
+ * 5) Rule lifecycle hooks
+ *
+ * `flow_alloc`/`flow_free` are optional. If `flow_alloc` is absent or returns
+ * NULL, the framework falls back to rte_zmalloc. `flow_free` is required if
+ * `flow_alloc` is provided.
+ *
+ * `flow_register`/`flow_unregister` (Track) and `flow_install`/`flow_uninstall`
+ * (Apply) are optional. Register/unregister must be both defined or both NULL.
+ *
+ * Engines own only engine-specific fields. Common `ci_flow` fields (engine_idx,
+ * engine_priv, fallback_alloc, etc.) belong to the framework and must not be
+ * modified.
+ *
+ * IMPORTANT: by the time Track and Apply run, the engine has already accepted
+ * the flow. A failure in `flow_register` or `flow_install` (e.g. resource
+ * exhaustion) is a hard failure: no other engines are tried. Any check
+ * intrinsic to the pattern that would prevent tracking or installation must
+ * therefore be done at the parse stage.
+ *
+ * 6) Memory ownership and allocation policy
+ *
+ * An engine may allocate in exactly three places:
+ *
+ * - engine init: engine-wide resources used by other callbacks (e.g. a mempool
+ *   from which `flow_alloc` takes flow objects)
+ * - flow alloc: memory for the flow itself (e.g. a mempool object), plus
+ *   allocator-associated bookkeeping. It must not produce anything the parse
+ *   stage depends on, because `rte_flow_validate()` bypasses `flow_alloc`.
+ * - flow register: the only per-flow callback that may allocate memory and
+ *   store the pointer in the flow object or driver-internal structures. This is
+ *   where "parsing the flow" ends and "updating driver state" begins.
+ *
+ * Parse callbacks (`ctx_init`, `pattern_parse`, `ctx_finalize`, `ctx_to_flow`)
+ * and `flow_install` must not allocate. These steps may fail with no
+ * teardown.
+ *
+ * 7) Flow query
+ *
+ * `flow_query` is optional. If not provided, querying the rule fails.
+ *
+ * 8) Concurrency model
+ *
+ * The framework serializes access to each driver instance's flow state with a
+ * single rwlock. Create, destroy and flush take it exclusively; validate and
+ * query take it shared. Parse callbacks (ctx_init, pattern_parse,
+ * ctx_finalize, ctx_to_flow) can therefore run concurrently on the validate
+ * path, as can flow_query, and must treat shared engine state (including
+ * engine_priv) as read-only. State mutation belongs in the write-locked
+ * callbacks (engine_init/engine_uninit, flow_alloc/flow_free,
+ * flow_register/flow_unregister, flow_install/flow_uninstall).
+ */
+struct ci_flow_engine_ops {
+	/* engine init callback - can be NULL */
+	int (*engine_init)(const struct ci_flow_engine *engine,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* engine uninit callback - can be NULL */
+	void (*engine_uninit)(const struct ci_flow_engine *engine,
+			void *priv);
+	/* allocation callback for flow rules - can be NULL */
+	struct ci_flow *(*flow_alloc)(const struct ci_flow_engine *engine,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* deallocation callback for flow rules - can be NULL */
+	void (*flow_free)(struct ci_flow *flow,
+			struct rte_eth_dev_data *dev_data,
+			void *priv);
+	/* initialize engine context from flow attr/actions - mandatory */
+	int (*ctx_init)(const struct rte_flow_action actions[],
+			const struct rte_flow_attr *attr,
+			struct ci_flow_engine_ctx *ctx,
+			struct rte_flow_error *error);
+	/* pattern parsing callback - can be NULL */
+	int (*pattern_parse)(struct ci_flow_engine_ctx *ctx,
+			const struct rte_flow_item pattern[],
+			struct rte_flow_error *error);
+	/* final pass before converting context to flow - can be NULL */
+	int (*ctx_finalize)(struct ci_flow_engine_ctx *ctx,
+			struct rte_flow_error *error);
+	/* initialize flow rule from parsed context - can be NULL */
+	int (*ctx_to_flow)(const struct ci_flow_engine_ctx *ctx,
+			struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* track a flow rule in driver-internal state - can be NULL */
+	int (*flow_register)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* untrack a flow rule from driver-internal state - can be NULL */
+	int (*flow_unregister)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* install a flow rule to hardware - can be NULL */
+	int (*flow_install)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* uninstall a flow rule from hardware - can be NULL */
+	int (*flow_uninstall)(struct ci_flow *flow,
+			struct rte_flow_error *error);
+	/* query flow - can be NULL */
+	int (*flow_query)(struct ci_flow *flow,
+			const struct rte_flow_action *action,
+			void *data,
+			struct rte_flow_error *error);
+};
+
+/* base parse context; engine contexts must start with this struct */
+struct ci_flow_engine_ctx {
+	/* ethernet device this context belongs to */
+	struct rte_eth_dev_data *dev_data;
+	/* per-engine private data, same pointer as flow->engine_priv */
+	void *engine_priv;
+	/* original flow attributes, as passed by the caller */
+	const struct rte_flow_attr *attr;
+	/* original flow pattern, as passed by the caller */
+	const struct rte_flow_item *pattern;
+	/* original flow actions, as passed by the caller */
+	const struct rte_flow_action *actions;
+};
+
+/*
+ * Common definition for flow rules.
+ *
+ * A flow rule has three layers of data:
+ *
+ * 1) Common data (`ci_flow`, defined here)
+ * 2) Driver-specific data
+ * 3) Engine-specific data
+ *
+ * `ci_flow` holds the fields common to all drivers and engines. Some are used
+ * internally by the framework (e.g. list linkage), others are provided as a
+ * basic service for engines' convenience (e.g. a reference to the owning
+ * device).
+ *
+ * Each driver defines its own `rte_flow` structure, which must start with
+ * `ci_flow`, followed by driver-specific fields. Each engine may in turn define
+ * its own flow structure, which must start with the driver's `rte_flow`,
+ * followed by engine-specific fields.
+ *
+ * IMPORTANT:
+ *
+ * All of these structures are referred to by the same pointer and can be safely
+ * cast between each other *as long as* each one has its parent structure as its
+ * first member:
+ *
+ *     struct rte_flow {
+ *         struct ci_flow base;
+ *         ...driver-specific fields...
+ *     };
+ *
+ *     struct ixgbe_fdir_flow {
+ *         struct rte_flow base;
+ *         ...engine-specific fields...
+ *     };
+ *
+ *     struct ci_flow *flow = ...;
+ *     struct rte_flow *rte_flow = (struct rte_flow *)flow;
+ *     struct ixgbe_fdir_flow *es_flow = (struct ixgbe_fdir_flow *)flow;
+ *
+ * Each engine provides a `flow_size` field giving the memory required for its
+ * flow structure. The driver must set it for every engine, as it is used to size
+ * flow allocations. An engine that needs nothing beyond the driver's `rte_flow`
+ * should use `sizeof(struct rte_flow)`.
+ *
+ * Engine references and engine_idx:
+ *
+ * Every flow stores its engine index as the engine type discriminator. Runtime
+ * code rebuilds a `ci_flow_engine_ref` (engine pointer + index) from the
+ * immutable engine list and this index. This means:
+ *
+ * - The framework only ever works with immutable engine definitions/lists.
+ * - No pointer-to-index lookups are needed after flow creation.
+ * - Secondary processes work correctly: the index is the stable identity, while
+ *   pointer values are only meaningful within the shared immutable engine list.
+ *
+ * Treat engine references as the basic building block when handling engine
+ * pointers and indices.
+ *
+ * Per-engine private data (engine_priv):
+ *
+ * Each flow carries a pointer to engine-private data, such as custom allocation
+ * structures used with flow_alloc/flow_free.
+ *
+ * Fallback allocation:
+ *
+ * If a driver-provided `flow_alloc` returns NULL, the framework falls back to
+ * rte_zmalloc(). The `fallback_alloc` flag then routes deallocation to
+ * rte_free(). Consequently, `flow_alloc` returning NULL is *not* treated as an
+ * error.
+ */
+struct ci_flow {
+	/* linkage in the per-device flow list */
+	TAILQ_ENTRY(ci_flow) node;
+	/* device this flow belongs to */
+	struct rte_eth_dev_data *dev_data;
+	/* index of engine this flow was created by */
+	size_t engine_idx;
+	/* per-engine private data pointer, set by the framework at alloc time */
+	void *engine_priv;
+	/* set if flow_alloc returned NULL and rte_zmalloc() was used instead */
+	bool fallback_alloc;
+};
+
+/* flow engine definition */
+struct ci_flow_engine {
+	/* engine name */
+	const char *name;
+	/* size of engine parse context, can be omitted */
+	size_t ctx_size;
+	/* size of engine flow struct, at least sizeof(struct ci_flow) */
+	size_t flow_size;
+	/* size of per-device engine private data, can be omitted */
+	size_t priv_size;
+	/* ops for this flow engine */
+	const struct ci_flow_engine_ops *ops;
+	/* pattern graph this engine supports, can be omitted */
+	const struct flow_graph *graph;
+};
+
+/* engine reference: immutable engine pointer paired with its list index */
+struct ci_flow_engine_ref {
+	/* engine definition */
+	const struct ci_flow_engine *engine;
+	/* index of the engine in the driver's engine list */
+	size_t engine_idx;
+};
+
+#define CI_FLOW_ENGINE_MAX	64
+
+/* flow engine list definition */
+struct ci_flow_engine_list {
+	/* NULL-terminated immutable array of flow engine pointers */
+	const struct ci_flow_engine * const engines[CI_FLOW_ENGINE_MAX];
+};
+
+/* per-device engine configuration */
+struct ci_flow_engine_conf {
+	/* lock protecting the config and the flow list */
+	rte_rwlock_t config_lock;
+	/* list of flows created on this device */
+	TAILQ_HEAD(ci_flow_list, ci_flow) flows;
+	/* bitmask of enabled engines, bit N set if engine at index N is enabled */
+	uint64_t enabled_engines;
+	/* back-reference to device data */
+	struct rte_eth_dev_data *dev_data;
+	/* reference to the driver's engine list */
+	const struct ci_flow_engine_list *engines;
+	/* per-engine private data pointers, indexed by engine index */
+	void *engine_priv[CI_FLOW_ENGINE_MAX];
+};
+
+/* caller must hold config lock */
+static inline struct ci_flow_engine_ref
+ci_flow_engine_ref_from_idx(const struct ci_flow_engine_conf *engine_conf,
+		const size_t engine_idx)
+{
+	/* check if valid reference can be constructed at all */
+	if (engine_conf == NULL || engine_conf->engines == NULL ||
+			engine_idx >= CI_FLOW_ENGINE_MAX) {
+		return (struct ci_flow_engine_ref) {
+			.engine = NULL,
+			.engine_idx = CI_FLOW_ENGINE_MAX
+		};
+	}
+	/* the inputs are valid, but there may be no engine */
+	if (engine_conf->engines->engines[engine_idx] == NULL) {
+		return (struct ci_flow_engine_ref) {
+			.engine = NULL,
+			.engine_idx = CI_FLOW_ENGINE_MAX
+		};
+	}
+
+	return (struct ci_flow_engine_ref) {
+		.engine = engine_conf->engines->engines[engine_idx],
+		.engine_idx = engine_idx,
+	};
+}
+
+#define CI_FLOW_ENGINE_REF_FOREACH(engine_ref, engine_conf)                         \
+	for (size_t __ci_flow_engine_idx = 0;                                        \
+		__ci_flow_engine_idx < CI_FLOW_ENGINE_MAX &&                         \
+			(((engine_ref) = ci_flow_engine_ref_from_idx((engine_conf),  \
+		__ci_flow_engine_idx)).engine != NULL);                              \
+		__ci_flow_engine_idx++)
+
+static inline bool
+ci_flow_engine_is_valid(const struct ci_flow_engine *engine)
+{
+	if (engine == NULL)
+		return false;
+	if (engine->name == NULL)
+		return false;
+	if (engine->ops == NULL)
+		return false;
+	if (engine->ops->ctx_init == NULL)
+		return false;
+	/* flow size cannot be less than ci_flow */
+	if (engine->flow_size < sizeof(struct ci_flow))
+		return false;
+	/* alloc and free must both be defined or NULL */
+	if ((engine->ops->flow_alloc == NULL) != (engine->ops->flow_free == NULL))
+		return false;
+	/* register and unregister must both be defined or NULL */
+	if ((engine->ops->flow_register == NULL) != (engine->ops->flow_unregister == NULL))
+		return false;
+	return true;
+}
+
+/* caller must hold config lock */
+static inline bool
+ci_flow_engine_is_enabled(const struct ci_flow_engine_conf *conf,
+		const size_t engine_idx)
+{
+	return (conf->enabled_engines & RTE_BIT64(engine_idx)) != 0;
+}
+
+/* caller must hold config lock */
+static inline void
+ci_flow_engine_set_enabled(struct ci_flow_engine_conf *conf, const size_t engine_idx,
+		bool enabled)
+{
+	if (enabled)
+		conf->enabled_engines |= RTE_BIT64(engine_idx);
+	else
+		conf->enabled_engines &= ~RTE_BIT64(engine_idx);
+}
+
+static inline struct ci_flow *
+ci_flow_alloc(const struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv = engine_conf->engine_priv[engine_ref.engine_idx];
+	struct ci_flow *flow = NULL;
+	bool fallback = false;
+
+	/* if engine has an allocator callback, try it first */
+	if (engine->ops->flow_alloc != NULL)
+		flow = engine->ops->flow_alloc(engine, engine_conf->dev_data, priv);
+	/* if allocator callback is not defined or failed, use default allocator */
+	if (flow == NULL) {
+		flow = (struct ci_flow *)rte_zmalloc(NULL, engine->flow_size, 0);
+
+		/* if callback exists and we're here, allocation has fallen back */
+		if (flow != NULL && engine->ops->flow_alloc != NULL)
+			fallback = true;
+	}
+	if (flow != NULL) {
+		/* initialize the commont part */
+		memset(flow, 0, sizeof(struct ci_flow));
+		flow->fallback_alloc = fallback;
+		flow->engine_idx = engine_ref.engine_idx;
+		flow->dev_data = engine_conf->dev_data;
+		flow->engine_priv = priv;
+	}
+	return flow;
+}
+
+static inline void
+ci_flow_free(struct ci_flow_engine_ref engine_ref, struct ci_flow *flow)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_free != NULL && !flow->fallback_alloc)
+		engine->ops->flow_free(flow, flow->dev_data, flow->engine_priv);
+	else
+		rte_free(flow);
+}
+
+/* Track stage - caller must hold config lock */
+static inline int
+ci_flow_register(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_register != NULL)
+		return engine->ops->flow_register(flow, error);
+
+	return 0;
+}
+
+/* Track stage - caller must hold config lock */
+static inline int
+ci_flow_unregister(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_unregister != NULL)
+		return engine->ops->flow_unregister(flow, error);
+
+	return 0;
+}
+
+/* caller must hold config lock */
+static inline int
+ci_flow_engine_init(struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv = NULL;
+	int ret;
+
+	if (engine->priv_size > 0) {
+		priv = rte_zmalloc(engine->name, engine->priv_size, 0);
+		if (priv == NULL) {
+			ret = -ENOMEM;
+			goto err;
+		}
+	}
+
+	if (engine->ops->engine_init != NULL) {
+		ret = engine->ops->engine_init(engine, engine_conf->dev_data, priv);
+		if (ret != 0)
+			goto err;
+	}
+	engine_conf->engine_priv[engine_ref.engine_idx] = priv;
+	return 0;
+err:
+	rte_free(priv);
+	return ret;
+}
+
+/* caller must hold config lock */
+static inline void
+ci_flow_engine_uninit(struct ci_flow_engine_conf *engine_conf,
+		struct ci_flow_engine_ref engine_ref)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+	void *priv;
+
+	priv = engine_conf->engine_priv[engine_ref.engine_idx];
+
+	CI_DRV_LOG(DEBUG, "engine '%s': uninit", engine->name);
+	/* ignore uninit errors */
+	if (engine->ops->engine_uninit != NULL)
+		engine->ops->engine_uninit(engine, priv);
+
+	rte_free(priv);
+	engine_conf->engine_priv[engine_ref.engine_idx] = NULL;
+}
+
+/* caller must serialize teardown */
+static inline void
+ci_flow_engine_conf_reset(struct ci_flow_engine_conf *engine_conf)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *flow, *tmp;
+
+	/* should be empty by now */
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct rte_flow_error unreg_error = {0};
+		int unreg_ret;
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		/* only valid flows are ever in the list */
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+		unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+		if (unreg_ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister flow: %s",
+					engine_ref.engine->name, unreg_error.message);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	CI_FLOW_ENGINE_REF_FOREACH(engine_ref, engine_conf) {
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+		/* ignore errors */
+		ci_flow_engine_uninit(engine_conf, engine_ref);
+		ci_flow_engine_set_enabled(engine_conf,
+				engine_ref.engine_idx, false);
+	}
+
+	engine_conf->dev_data = NULL;
+	engine_conf->engines = NULL;
+}
+
+/* caller must serialize initialization */
+static inline int
+ci_flow_engine_conf_init(struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine_list *engine_list,
+		struct rte_eth_dev_data *dev_data)
+{
+	struct ci_flow_engine_ref engine_ref;
+
+	if (engine_conf == NULL || engine_list == NULL || dev_data == NULL)
+		return -1;
+
+	rte_rwlock_init(&engine_conf->config_lock);
+
+	engine_conf->dev_data = dev_data;
+	engine_conf->engines = engine_list;
+
+	TAILQ_INIT(&engine_conf->flows);
+
+	CI_FLOW_ENGINE_REF_FOREACH(engine_ref, engine_conf) {
+		if (!ci_flow_engine_is_valid(engine_ref.engine)) {
+			CI_DRV_LOG(DEBUG, "engine[%zu]: invalid, skipping",
+					engine_ref.engine_idx);
+			continue;
+		}
+		int ret = ci_flow_engine_init(engine_conf, engine_ref);
+
+		if (ret != 0) {
+			if (ret == -ENOTSUP)
+				CI_DRV_LOG(DEBUG, "engine '%s': not supported, skipping",
+						engine_ref.engine->name);
+			else
+				CI_DRV_LOG(ERR, "engine '%s': init failed (%d: %s), skipping",
+						engine_ref.engine->name, ret, rte_strerror(-ret));
+			continue;
+		}
+
+		ci_flow_engine_set_enabled(engine_conf,
+				engine_ref.engine_idx, true);
+		CI_DRV_LOG(DEBUG, "engine '%s': enabled", engine_ref.engine->name);
+	}
+	return 0;
+}
+
+/* caller must hold config lock */
+static inline bool
+ci_flow_is_valid(const struct ci_flow *flow,
+		const struct ci_flow_engine_conf *engine_conf)
+{
+	if (flow == NULL)
+		return false;
+	if (engine_conf == NULL || engine_conf->dev_data == NULL ||
+			engine_conf->engines == NULL)
+		return false;
+	if (flow->dev_data != engine_conf->dev_data)
+		return false;
+	if (flow->engine_idx >= CI_FLOW_ENGINE_MAX)
+		return false;
+	if (!ci_flow_engine_is_enabled(engine_conf, flow->engine_idx))
+		return false;
+	return true;
+}
+
+/* accepts only empty and ANY-only patterns */
+enum ci_flow_empty_graph_node_id {
+	CI_FLOW_EMPTY_GRAPH_NODE_START = FLOW_GRAPH_NODE_FIRST,
+	CI_FLOW_EMPTY_GRAPH_NODE_ANY,
+	CI_FLOW_EMPTY_GRAPH_NODE_END,
+};
+
+static const struct flow_graph ci_flow_empty_graph = {
+	.nodes = (struct flow_graph_node []) {
+		[CI_FLOW_EMPTY_GRAPH_NODE_START] = {
+			.name = "START",
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_ANY] = {
+			.name = "ANY",
+			.type = RTE_FLOW_ITEM_TYPE_ANY,
+			.constraints = FLOW_GRAPH_NODE_EXPECT_EMPTY,
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_END] = {
+			.name = "END",
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	},
+	.edges = (struct flow_graph_edge []) {
+		[CI_FLOW_EMPTY_GRAPH_NODE_START] = {
+			.next = (size_t []) {
+				CI_FLOW_EMPTY_GRAPH_NODE_ANY,
+				CI_FLOW_EMPTY_GRAPH_NODE_END,
+				FLOW_GRAPH_NODE_EDGE_END,
+			},
+		},
+		[CI_FLOW_EMPTY_GRAPH_NODE_ANY] = {
+			.next = (size_t []) {
+				CI_FLOW_EMPTY_GRAPH_NODE_END,
+				FLOW_GRAPH_NODE_EDGE_END,
+			},
+		},
+	}
+};
+
+/* concrete pattern-matching mode selected from graph/callback presence */
+enum ci_match_type {
+	CI_MATCH_EMPTY,    /* no graph, no callback */
+	CI_MATCH_CALLBACK, /* callback only */
+	CI_MATCH_GRAPH,    /* graph only */
+	CI_MATCH_ALL,      /* callback + graph */
+};
+
+/* caller must hold config lock */
+static inline int
+ci_flow_match(const struct ci_flow_engine *engine,
+		const struct rte_flow_item pattern[],
+		struct ci_flow_engine_ctx *ctx,
+		enum ci_match_type match_type,
+		struct rte_flow_error *error)
+{
+	switch (match_type) {
+	case CI_MATCH_EMPTY:
+		/* for empty matching, NULL pattern is not an error */
+		if (pattern != NULL) {
+			return flow_graph_parse(&ci_flow_empty_graph,
+					pattern, error, ctx);
+		}
+		return 0;
+
+	case CI_MATCH_CALLBACK:
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		return engine->ops->pattern_parse(ctx, pattern, error);
+
+	case CI_MATCH_GRAPH:
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		return flow_graph_parse(engine->graph, pattern, error, ctx);
+
+	case CI_MATCH_ALL:
+	{
+		int ret;
+
+		if (pattern == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					"Pattern cannot be NULL");
+		}
+		ret = engine->ops->pattern_parse(ctx, pattern, error);
+		if (ret != 0)
+			return ret;
+		return flow_graph_parse(engine->graph, pattern, error, ctx);
+	}
+
+	default:
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+				"Invalid match type");
+	}
+}
+
+/* caller must hold config lock */
+static inline int
+ci_flow_parse(const struct ci_flow_engine_conf *engine_conf,
+		const struct ci_flow_engine *engine,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	enum ci_match_type match_type;
+	struct ci_flow_engine_ctx *ctx;
+	int ret = 0;
+
+	/*
+	 * Matching mode depends on whether a pattern graph and/or a
+	 * pattern_parse callback are provided:
+	 *
+	 * - graph only: match against the graph. This is the expected default;
+	 *   no special handling of pattern items.
+	 *
+	 * - graph + callback: the callback preprocesses the pattern, then the
+	 *   graph engine does the matching. The graph is expected to have an
+	 *   ignore list to skip nodes not meant for it.
+	 *
+	 * - callback only: fully custom pattern parsing.
+	 *
+	 * - neither: match against an empty graph, for engines that don't care
+	 *   about the pattern. Default behavior accepts a NULL pattern and
+	 *   END or ANY -> END patterns, and rejects anything else (so the
+	 *   pattern is still checked, not ignored).
+	 */
+	match_type = engine->graph == NULL ?
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_EMPTY : CI_MATCH_CALLBACK) :
+		(engine->ops->pattern_parse == NULL ? CI_MATCH_GRAPH : CI_MATCH_ALL);
+
+	CI_DRV_LOG(DEBUG, "engine '%s': parsing flow", engine->name);
+
+	ctx = (struct ci_flow_engine_ctx *)calloc(1,
+			RTE_MAX(engine->ctx_size, sizeof(struct ci_flow_engine_ctx)));
+	if (ctx == NULL) {
+		return rte_flow_error_set(error, ENOMEM,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Failed to allocate memory for rule engine context");
+	}
+	ctx->dev_data = engine_conf->dev_data;
+	ctx->engine_priv = flow->engine_priv;
+	ctx->attr = attr;
+	ctx->pattern = pattern;
+	ctx->actions = actions;
+	flow->dev_data = engine_conf->dev_data;
+
+	ret = engine->ops->ctx_init(actions, attr, ctx, error);
+	if (ret != 0)
+		goto free_ctx;
+
+	ret = ci_flow_match(engine, pattern, ctx, match_type, error);
+	if (ret != 0)
+		goto free_ctx;
+
+	if (engine->ops->ctx_finalize != NULL)
+		ret = engine->ops->ctx_finalize(ctx, error);
+	if (ret != 0)
+		goto free_ctx;
+
+	if (engine->ops->ctx_to_flow != NULL) {
+		ret = engine->ops->ctx_to_flow(ctx, flow, error);
+		if (ret != 0)
+			goto free_ctx;
+	}
+	ret = 0;
+
+free_ctx:
+	free(ctx);
+	return ret;
+}
+
+/* Apply stage - caller must hold config lock */
+static inline int
+ci_flow_install(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_install != NULL)
+		return engine->ops->flow_install(flow, error);
+
+	return 0;
+}
+
+/* Apply stage - caller must hold config lock */
+static inline int
+ci_flow_uninstall(struct ci_flow_engine_ref engine_ref,
+		struct ci_flow *flow,
+		struct rte_flow_error *error)
+{
+	const struct ci_flow_engine *engine = engine_ref.engine;
+
+	if (engine->ops->flow_uninstall != NULL)
+		return engine->ops->flow_uninstall(flow, error);
+
+	return 0;
+}
+
+/* Default rte_flow op implementations. These take the config lock internally. */
+
+static inline struct rte_flow *
+ci_flow_create(struct ci_flow_engine_conf *engine_conf,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *flow = NULL;
+	int ret;
+
+	if (attr == NULL || actions == NULL) {
+		CI_DRV_LOG(DEBUG, "attr or actions is NULL");
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ATTR, NULL,
+				"Attributes and actions cannot be NULL");
+		return NULL;
+	}
+
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	CI_FLOW_ENGINE_REF_FOREACH(engine_ref, engine_conf) {
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+
+		flow = ci_flow_alloc(engine_conf, engine_ref);
+		if (flow == NULL) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to allocate flow",
+					engine_ref.engine->name);
+			rte_flow_error_set(error, ENOMEM,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to allocate memory for flow rule");
+			/* this is a serious error so don't continue */
+			goto unlock;
+		}
+
+		ret = ci_flow_parse(engine_conf, engine_ref.engine, attr, pattern,
+				actions, flow, error);
+
+		/* parsing failed - free the flow and try next engine */
+		if (ret != 0) {
+			ci_flow_free(engine_ref, flow);
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' rejected flow: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' rejected flow",
+						engine_ref.engine->name);
+			continue;
+		}
+
+		CI_DRV_LOG(DEBUG, "engine '%s' accepted flow, committing",
+				engine_ref.engine->name);
+
+		ret = ci_flow_register(engine_ref, flow, error);
+		if (ret != 0) {
+			/* track failed after parse accepted - do not try other engines */
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' register failed: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' register failed",
+						engine_ref.engine->name);
+			ci_flow_free(engine_ref, flow);
+			flow = NULL;
+			goto unlock;
+		}
+
+		ret = ci_flow_install(engine_ref, flow, error);
+		if (ret != 0) {
+			struct rte_flow_error unreg_error = { 0 };
+			int unreg_ret;
+
+			/* install failed after parse accepted - do not try other engines */
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s' install failed: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s' install failed",
+						engine_ref.engine->name);
+			/* separate error struct to preserve the install error */
+			unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+			if (unreg_ret != 0)
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister: %s",
+						engine_ref.engine->name, unreg_error.message);
+			ci_flow_free(engine_ref, flow);
+			flow = NULL;
+			goto unlock;
+		}
+
+		CI_DRV_LOG(DEBUG, "flow installed by engine '%s'",
+				engine_ref.engine->name);
+		TAILQ_INSERT_TAIL(&engine_conf->flows, flow, node);
+		goto unlock;
+	}
+
+	CI_DRV_LOG(DEBUG, "no engine accepted the flow");
+	flow = NULL;
+	rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"No flow engine could handle the requested flow");
+unlock:
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return (struct rte_flow *)flow;
+}
+
+static inline int
+ci_flow_validate(struct ci_flow_engine_conf *engine_conf,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[],
+		const struct rte_flow_action actions[],
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	int ret;
+
+	if (attr == NULL || actions == NULL) {
+		CI_DRV_LOG(DEBUG, "attr or actions is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ATTR, NULL,
+				"Attributes and actions cannot be NULL");
+	}
+
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	CI_FLOW_ENGINE_REF_FOREACH(engine_ref, engine_conf) {
+		struct ci_flow *flow;
+
+		if (!ci_flow_engine_is_enabled(engine_conf, engine_ref.engine_idx))
+			continue;
+
+		/* flow is not kept, so skip the engine allocator */
+		flow = (struct ci_flow *)calloc(1, engine_ref.engine->flow_size);
+		if (flow == NULL) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to allocate flow",
+					engine_ref.engine->name);
+			ret = rte_flow_error_set(error, ENOMEM,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to allocate memory for flow rule");
+			goto unlock;
+		}
+		flow->fallback_alloc = false;
+		flow->engine_idx = engine_ref.engine_idx;
+		flow->dev_data = engine_conf->dev_data;
+		flow->engine_priv = engine_conf->engine_priv[engine_ref.engine_idx];
+
+		ret = ci_flow_parse(engine_conf, engine_ref.engine, attr, pattern,
+				actions, flow, error);
+		free(flow);
+
+		if (ret == 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s' accepted flow",
+					engine_ref.engine->name);
+			goto unlock;
+		} else if (error != NULL) {
+			CI_DRV_LOG(DEBUG, "engine '%s' rejected flow: %s",
+					engine_ref.engine->name, error->message);
+		} else {
+			CI_DRV_LOG(DEBUG, "engine '%s' rejected flow",
+					engine_ref.engine->name);
+		}
+	}
+	CI_DRV_LOG(DEBUG, "no engine accepted the flow");
+	ret = rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"No flow engine could handle the requested flow");
+unlock:
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+	return ret;
+}
+
+static inline int
+ci_flow_destroy(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *rte_flow,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow = (struct ci_flow *)rte_flow;
+	struct ci_flow_engine_ref engine_ref;
+	struct rte_flow_error unreg_error = {0};
+	int unreg_ret, ret = 0;
+
+	if (rte_flow == NULL) {
+		CI_DRV_LOG(DEBUG, "flow handle is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Flow handle cannot be NULL");
+	}
+
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	if (!ci_flow_is_valid(flow, engine_conf)) {
+		CI_DRV_LOG(DEBUG, "invalid flow handle");
+		ret = rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Invalid flow handle");
+		goto unlock;
+	}
+	engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+	CI_DRV_LOG(DEBUG, "uninstalling flow (engine '%s')",
+			engine_ref.engine->name);
+
+	ret = ci_flow_uninstall(engine_ref, flow, error);
+
+	if (ret != 0) {
+		if (error != NULL)
+			CI_DRV_LOG(DEBUG, "uninstall failed: %s", error->message);
+		else
+			CI_DRV_LOG(DEBUG, "uninstall failed");
+		goto unlock;
+	}
+
+	/* hardware is already clean, so untrack failure is not fatal */
+	unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+	if (unreg_ret != 0)
+		CI_DRV_LOG(DEBUG, "engine '%s': failed to unregister flow: %s",
+				engine_ref.engine->name, unreg_error.message);
+
+	TAILQ_REMOVE(&engine_conf->flows, flow, node);
+	ci_flow_free(engine_ref, flow);
+unlock:
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return ret;
+}
+
+static inline int
+ci_flow_flush(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow, *tmp;
+
+	CI_DRV_LOG(DEBUG, "removing all flows");
+
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct ci_flow_engine_ref engine_ref;
+		struct rte_flow_error unreg_error = {0};
+		int uninstall_ret;
+		int unreg_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		uninstall_ret = ci_flow_uninstall(engine_ref, flow, error);
+
+		/* flush must not stop halfway, so failures are only logged */
+		if (uninstall_ret != 0) {
+			if (error != NULL)
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to uninstall flow: %s",
+						engine_ref.engine->name, error->message);
+			else
+				CI_DRV_LOG(DEBUG, "engine '%s': failed to uninstall flow",
+						engine_ref.engine->name);
+		}
+
+		unreg_ret = ci_flow_unregister(engine_ref, flow, &unreg_error);
+		if (unreg_ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to untrack flow: %s",
+					engine_ref.engine->name, unreg_error.message);
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+static inline int
+ci_flow_query(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *rte_flow,
+		const struct rte_flow_action *action,
+		void *data,
+		struct rte_flow_error *error)
+{
+	struct ci_flow *flow = (struct ci_flow *)rte_flow;
+	struct ci_flow_engine_ref engine_ref;
+	int ret;
+
+	if (action == NULL || data == NULL) {
+		CI_DRV_LOG(DEBUG, "action or data is NULL");
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Action or data cannot be NULL");
+	}
+
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	if (!ci_flow_is_valid(flow, engine_conf)) {
+		CI_DRV_LOG(DEBUG, "invalid flow handle");
+		ret = rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Invalid flow handle");
+		goto unlock;
+	}
+	engine_ref = ci_flow_engine_ref_from_idx(engine_conf, flow->engine_idx);
+	if (engine_ref.engine->ops->flow_query != NULL) {
+		ret = engine_ref.engine->ops->flow_query(flow, action, data, error);
+
+		if (ret != 0 && error != NULL)
+			CI_DRV_LOG(DEBUG, "engine '%s' query failed: %s",
+					engine_ref.engine->name, error->message);
+		else if (ret != 0)
+			CI_DRV_LOG(DEBUG, "engine '%s' query failed",
+					engine_ref.engine->name);
+	} else {
+		CI_DRV_LOG(DEBUG, "engine '%s' does not support querying",
+				engine_ref.engine->name);
+		ret = rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"Flow engine does not support querying");
+	}
+unlock:
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+
+	return ret;
+}
+
+#define CI_FLOW_DUMP_CHUNK_BYTES 32
+
+static inline void
+ci_flow_dump_one(FILE *file, const char *driver, const char *engine,
+		const void *data, size_t data_len)
+{
+	const uint8_t *raw = (const uint8_t *)data;
+	const size_t nchunks =
+		(data_len + CI_FLOW_DUMP_CHUNK_BYTES - 1) /
+		CI_FLOW_DUMP_CHUNK_BYTES;
+	char title[64];
+	size_t ci;
+
+	fprintf(file, "FLOW DUMP: driver=%s engine=%s\n", driver, engine);
+	fprintf(file, "FLOW DUMP: DATA size=%zu chunks=%zu chunk_bytes=%d\n",
+		data_len, nchunks, CI_FLOW_DUMP_CHUNK_BYTES);
+
+	for (ci = 0; ci < nchunks; ci++) {
+		const size_t off = ci * CI_FLOW_DUMP_CHUNK_BYTES;
+		const size_t clen =
+			RTE_MIN((size_t)CI_FLOW_DUMP_CHUNK_BYTES,
+				data_len - off);
+		snprintf(title, sizeof(title), "FLOW DUMP: chunk %03zu/%03zu",
+			 ci + 1, nchunks);
+		rte_memdump(file, title, raw + off, clen);
+	}
+}
+
+/* rte_flow_dev_dump implementation */
+static inline int
+ci_flow_dump(struct ci_flow_engine_conf *engine_conf,
+		struct rte_flow *flow,
+		FILE *file,
+		struct rte_flow_error *error)
+{
+	struct ci_flow_engine_ref engine_ref;
+	struct ci_flow *cur;
+	bool found = false;
+	const char *driver_name =
+#ifdef RTE_COMPONENT_NAME
+			RTE_STR(RTE_COMPONENT_NAME);
+#else
+			"unknown";
+#endif
+
+	rte_rwlock_read_lock(&engine_conf->config_lock);
+
+	TAILQ_FOREACH(cur, &engine_conf->flows, node) {
+		const void *data;
+		size_t data_len;
+
+		if (flow != NULL && (struct rte_flow *)cur != flow)
+			continue;
+
+		if (!ci_flow_is_valid(cur, engine_conf)) {
+			CI_DRV_LOG(DEBUG, "invalid flow handle: %p, skipping", cur);
+			continue;
+		}
+
+		found = true;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf, cur->engine_idx);
+
+		data = RTE_PTR_ADD(cur, sizeof(struct ci_flow));
+		data_len = engine_ref.engine->flow_size - sizeof(struct ci_flow);
+
+		/* skipping explicitly keeps static analyzers quiet */
+		if (data_len == 0) {
+			CI_DRV_LOG(DEBUG, "flow data length is 0, skipping dump");
+			continue;
+		}
+
+		ci_flow_dump_one(file, driver_name, engine_ref.engine->name,
+				data, data_len);
+	}
+
+	rte_rwlock_read_unlock(&engine_conf->config_lock);
+
+	if (flow != NULL && !found) {
+		return rte_flow_error_set(error, ENOENT,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"Flow not found");
+	}
+
+	return 0;
+}
+
+/* Driver lifecycle helpers. These take the config lock internally. */
+
+/* reinstall kept flows to hardware */
+static inline int
+ci_flow_replay(struct ci_flow_engine_conf *engine_conf)
+{
+	struct rte_flow_error error = {0};
+	struct ci_flow *flow;
+
+	CI_DRV_LOG(DEBUG, "replaying all flows");
+
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	TAILQ_FOREACH(flow, &engine_conf->flows, node) {
+		struct ci_flow_engine_ref engine_ref;
+		int install_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		install_ret = ci_flow_install(engine_ref, flow, &error);
+		if (install_ret != 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to install flow: %s",
+					engine_ref.engine->name, error.message);
+		}
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+/* drop all flows without touching the hardware */
+static inline int
+ci_flow_cleanup(struct ci_flow_engine_conf *engine_conf)
+{
+	struct rte_flow_error error = {0};
+	struct ci_flow *flow, *tmp;
+
+	CI_DRV_LOG(DEBUG, "cleaning up all flows");
+
+	rte_rwlock_write_lock(&engine_conf->config_lock);
+
+	RTE_TAILQ_FOREACH_SAFE(flow, &engine_conf->flows, node, tmp) {
+		struct ci_flow_engine_ref engine_ref;
+		int unregister_ret;
+
+		/* this shouldn't happen */
+		if (!ci_flow_is_valid(flow, engine_conf))
+			continue;
+
+		engine_ref = ci_flow_engine_ref_from_idx(engine_conf,
+				flow->engine_idx);
+
+		unregister_ret = ci_flow_unregister(engine_ref, flow, &error);
+		if (unregister_ret != 0) {
+			CI_DRV_LOG(DEBUG, "engine '%s': failed to untrack flow: %s",
+					engine_ref.engine->name, error.message);
+		}
+
+		TAILQ_REMOVE(&engine_conf->flows, flow, node);
+		ci_flow_free(engine_ref, flow);
+	}
+
+	rte_rwlock_write_unlock(&engine_conf->config_lock);
+
+	return 0;
+}
+
+#endif /* _COMMON_INTEL_FLOW_ENGINE_H_ */
Signature · 1 line
2.52.0
#112 lore raw

PATCH v4 4/24 net/intel/common: add utility functions

In reply to Anatoly Burakov

A lot of parsers will rely on doing the same things over and over, so create a header with utility functions to aid in writing parsers.

---
 drivers/net/intel/common/flow_util.h | 263 +++++++++++++++++++++++++++
 1 file changed, 263 insertions(+)
 create mode 100644 drivers/net/intel/common/flow_util.h
drivers/net/intel/common/flow_util.h new file +263 -0
new file mode 100644
index 00000000000..96493ba7631
@@ -0,0 +1,263 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_FLOW_UTIL_H_
+#define _COMMON_INTEL_FLOW_UTIL_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <rte_debug.h>
+#include <rte_flow.h>
+
+/*
+ * Utility functions primarily intended for flow parsers.
+ */
+
+/**
+ * Get the effective mask of a flow item.
+ *
+ * @param item
+ *   Flow item.
+ * @return
+ *   - NULL if spec is NULL
+ *   - item->mask if mask is not NULL
+ *   - default mask for the item type if mask is NULL
+ *   - NULL if the item type has no default mask
+ */
+static inline const void *
+ci_flow_item_mask(const struct rte_flow_item *item)
+{
+	const void *mask;
+
+	if (item->spec == NULL)
+		return NULL;
+	if (item->mask != NULL)
+		return item->mask;
+	if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask, sizeof(mask),
+			(const void *)(uintptr_t)item->type, NULL) < 0)
+		return NULL;
+	return mask;
+}
+
+/*
+ * Simplistic usage counter. Not thread-safe, not atomic.
+ */
+typedef uint32_t ci_usage_count_t;
+
+/**
+ * Claim a usage. Returns true if this is the first usage.
+ *
+ * @param counter
+ *   Usage counter to claim.
+ * @return
+ *   true if this is the first usage, false otherwise.
+ */
+static inline bool
+ci_usage_count_claim(ci_usage_count_t *counter)
+{
+#ifdef CI_DEBUG
+	if (*counter == UINT32_MAX)
+		rte_panic("ci usage counter overflow\n");
+#endif
+	return (*counter)++ == 0;
+}
+
+/**
+ * Release a usage. Returns true if this was the last usage.
+ *
+ * @param counter
+ *   Usage counter to release.
+ * @return
+ *   true if this was the last usage, false otherwise.
+ */
+static inline bool
+ci_usage_count_release(ci_usage_count_t *counter)
+{
+#ifdef CI_DEBUG
+	if (*counter == 0)
+		rte_panic("ci usage counter underflow\n");
+#endif
+	return --(*counter) == 0;
+}
+
+/**
+ * Test whether the currently claimed usage is the last one.
+ *
+ * @param counter
+ *   Usage counter to test.
+ * @return
+ *   true if the current usage is the last one, false otherwise.
+ */
+static inline bool
+ci_usage_count_is_last(const ci_usage_count_t *counter)
+{
+	return *counter == 1;
+}
+
+/**
+ * Check if memory region is filled with a specific byte value.
+ *
+ * @param ptr
+ *   Pointer to memory region.
+ * @param len
+ *   Length in bytes.
+ * @param val
+ *   Byte value to check (e.g. 0x00 or 0xFF).
+ * @return
+ *   true if all bytes equal val, false otherwise.
+ */
+static inline bool
+ci_is_all_byte(const void *ptr, size_t len, uint8_t val)
+{
+	const uint8_t *bytes = (const uint8_t *)ptr;
+	const uint32_t pattern32 = 0x01010101U * val;
+	size_t i = 0;
+
+	/* Process 4-byte chunks using memcpy */
+	for (; i + 4 <= len; i += 4) {
+		uint32_t chunk;
+		memcpy(&chunk, bytes + i, 4);
+		if (chunk != pattern32)
+			return false;
+	}
+
+	/* Process remaining bytes */
+	for (; i < len; i++) {
+		if (bytes[i] != val)
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * Check if bytes are all 0x00 OR all 0xFF.
+ *
+ * @param ptr
+ *   Pointer to memory region.
+ * @param len
+ *   Length in bytes.
+ * @return
+ *   true if all bytes are 0x00 OR all bytes are 0xFF, false otherwise.
+ */
+static inline bool
+ci_is_all_zero_or_masked(const void *ptr, size_t len)
+{
+	const uint8_t *bytes = (const uint8_t *)ptr;
+	uint8_t first_val;
+
+	/* zero length cannot be valid */
+	if (len == 0)
+		return false;
+
+	first_val = bytes[0];
+
+	if (first_val != 0x00 && first_val != 0xFF)
+		return false;
+
+	return ci_is_all_byte(ptr, len, first_val);
+}
+
+/**
+ * Check if a value has no bits outside the mask, and within the mask is
+ * either all-zero or all-one.
+ *
+ * This is intended for bitfields e.g. VLAN_TCI. For byte-aligned fields,
+ * use CI_FIELD_IS_ZERO_OR_MASKED below.
+ *
+ * @param value
+ *   Data value to check.
+ * @param mask
+ *   Mask to compare against.
+ * @return
+ *   true if the value is either all-zero or fully masked, false otherwise.
+ */
+static inline bool
+ci_is_zero_or_masked(uint64_t value, uint64_t mask)
+{
+	return value == 0 || value == mask;
+}
+
+/**
+ * Check if a struct field is fully masked or unmasked.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_ZERO_OR_MASKED(field_ptr) \
+	ci_is_all_zero_or_masked((field_ptr), sizeof(*(field_ptr)))
+
+/**
+ * Check if a struct field is all 0x00.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_ZERO(field_ptr) \
+	ci_is_all_byte((field_ptr), sizeof(*(field_ptr)), 0x00)
+
+/**
+ * Check if a struct field is all 0xFF.
+ *
+ * @param field_ptr
+ *   Pointer to the mask field (e.g. &eth_mask->hdr.src_addr).
+ */
+#define CI_FIELD_IS_MASKED(field_ptr) \
+	ci_is_all_byte((field_ptr), sizeof(*(field_ptr)), 0xFF)
+
+/**
+ * Convert 24-bit big-endian value to host byte order.
+ *
+ * Used to extract 24-bit big-endian values (e.g. VXLAN VNI).
+ *
+ * @param val
+ *   Pointer to 3-byte big-endian value.
+ * @return
+ *   Value in host byte order.
+ */
+static inline uint32_t
+ci_be24_to_cpu(const uint8_t val[3])
+{
+	return (val[0] << 16) | (val[1] << 8) | val[2];
+}
+
+/**
+ * Check if a character is a valid hexadecimal digit.
+ *
+ * @param c
+ *   Character to check.
+ * @return
+ *   true if c is in [0-9a-fA-F], false otherwise.
+ */
+static inline bool
+ci_is_hex_char(unsigned char c)
+{
+	return ((c >= '0' && c <= '9') ||
+		(c >= 'a' && c <= 'f') ||
+		(c >= 'A' && c <= 'F'));
+}
+
+/**
+ * Convert hex character to 4-bit value.
+ *
+ * @param c
+ *   Hex character ('0'-'9', 'a'-'f', 'A'-'F').
+ * @return
+ *   Value 0-15, or 0 if invalid.
+ */
+static inline unsigned char
+ci_hex_char_to_nibble(unsigned char c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	return 0;
+}
+
+#endif /* _COMMON_INTEL_FLOW_UTIL_H_ */
Signature · 1 line
2.52.0
#113 lore raw

PATCH v4 5/24 net/ixgbe: add support for common flow parsing

In reply to Anatoly Burakov

Implement support for common flow parsing infrastructure in preparation for migration of flow engines. The following features are enabled:

- Conf init on dev_init
- Conf uninit on dev_close
- Flow cleanup on dev_close
- Flow replay on dev_start

---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c | 22 ++++++++++-
 drivers/net/intel/ixgbe/ixgbe_ethdev.h |  5 +++
 drivers/net/intel/ixgbe/ixgbe_flow.c   | 54 +++++++++++++++++++++++++-
 drivers/net/intel/ixgbe/ixgbe_flow.h   | 12 ++++++
 4 files changed, 90 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/intel/ixgbe/ixgbe_flow.h
drivers/net/intel/ixgbe/ixgbe_ethdev.c +20 -2
index 1745070e595..14e4bb7c1d3 100644
@@ -47,6 +47,7 @@
 #include "base/ixgbe_phy.h"
 #include "ixgbe_osdep.h"
 #include "ixgbe_regs.h"
+#include "ixgbe_flow.h"
 
 /*
  * High threshold controlling when to start sending XOFF frames. Must be at
@@ -1279,6 +1280,12 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 	if (ret)
 		goto err_l2_tn_filter_init;
 
+	/* initialize flow engine configuration */
+	ret = ci_flow_engine_conf_init(&ad->flow_engine_conf,
+			&ixgbe_flow_engine_list, eth_dev->data);
+	if (ret)
+		goto err_flow_engine_conf_init;
+
 	/* initialize flow filter lists */
 	ixgbe_filterlist_init(eth_dev);
 
@@ -1290,6 +1297,8 @@ eth_ixgbe_dev_init(struct rte_eth_dev *eth_dev, void *init_params __rte_unused)
 
 	return 0;
 
+err_flow_engine_conf_init:
+	ixgbe_l2_tn_filter_uninit(eth_dev);
 err_l2_tn_filter_init:
 	ixgbe_fdir_filter_uninit(eth_dev);
 err_fdir_filter_init:
@@ -2856,6 +2865,9 @@ ixgbe_dev_start(struct rte_eth_dev *dev)
 	if (macsec_setting->offload_en)
 		ixgbe_dev_macsec_register_enable(dev, macsec_setting);
 
+	/* re-install kept flows to hardware */
+	ci_flow_replay(&adapter->flow_engine_conf);
+
 	return 0;
 
 error:
@@ -3035,8 +3047,8 @@ ixgbe_dev_set_link_down(struct rte_eth_dev *dev)
 static int
 ixgbe_dev_close(struct rte_eth_dev *dev)
 {
-	struct ixgbe_hw *hw =
-		IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_adapter *ad = dev->data->dev_private;
+	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(ad);
 	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
 	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
 	int retries = 0;
@@ -3103,6 +3115,12 @@ ixgbe_dev_close(struct rte_eth_dev *dev)
 	rte_free(dev->security_ctx);
 	dev->security_ctx = NULL;
 
+	/* drop all flows */
+	ci_flow_cleanup(&ad->flow_engine_conf);
+
+	/* reset flow engines */
+	ci_flow_engine_conf_reset(&ad->flow_engine_conf);
+
 	return ret;
 }
 
drivers/net/intel/ixgbe/ixgbe_ethdev.h +5 -0
index 2e9d166e295..04b449fb70d 100644
@@ -22,6 +22,8 @@
 #include <bus_pci_driver.h>
 #include <rte_tm_driver.h>
 
+#include "../common/flow_engine.h"
+
 /* need update link, bit flag */
 #define IXGBE_FLAG_NEED_LINK_UPDATE (uint32_t)(1 << 0)
 #define IXGBE_FLAG_MAILBOX          (uint32_t)(1 << 1)
@@ -342,6 +344,7 @@ struct ixgbe_l2_tn_info {
 };
 
 struct rte_flow {
+	struct ci_flow flow;
 	enum rte_filter_type filter_type;
 	/* security flows are not rte_filter_type */
 	bool is_security;
@@ -487,6 +490,8 @@ struct ixgbe_adapter {
 	struct rte_timecounter      tx_tstamp_tc;
  	struct ixgbe_tm_conf        tm_conf;
 
+	struct ci_flow_engine_conf flow_engine_conf;
+
 	/* For RSS reta table update */
 	uint8_t rss_reta_updated;
 
drivers/net/intel/ixgbe/ixgbe_flow.c +53 -1

104 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_flow.h new file +12 -0
new file mode 100644
index 00000000000..5e68c9886c1
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _IXGBE_FLOW_H_
+#define _IXGBE_FLOW_H_
+
+#include "../common/flow_engine.h"
+
+extern const struct ci_flow_engine_list ixgbe_flow_engine_list;
+
+#endif /*  _IXGBE_FLOW_H_ */
Signature · 1 line
2.52.0
#114 lore raw

PATCH v4 6/24 net/ixgbe: make ethertype filter table dynamic

In reply to Anatoly Burakov

Currently, when installing Ethertype filters, some of the filters will be installed at a fixed offset into the Ethertype filter table (such as timesync filter), which interferes with rte_flow-provided Ethertype flows which install into "first available slot" without checking if the slot was actually "available".

Rework the Ethertype filter infrastructure to reserve Ethertype filter slots dynamically in a centralized manner, so that timesync, anti-spoof, and rte_flow provided Ethertype filters do not step on each others' toes.

Fixes 72c135a89f80 ("net/ixgbe: create consistent filter")
---
 drivers/net/intel/ixgbe/ixgbe_ethdev.c | 205 +++++++++++++++----------
 drivers/net/intel/ixgbe/ixgbe_ethdev.h |  85 +++-------
 drivers/net/intel/ixgbe/ixgbe_pf.c     |  49 +++---
 3 files changed, 172 insertions(+), 167 deletions(-)
drivers/net/intel/ixgbe/ixgbe_ethdev.c +124 -81
index 14e4bb7c1d3..56dbf288a94 100644
@@ -6841,77 +6841,99 @@ ixgbe_add_del_ntuple_filter(struct ixgbe_adapter *adapter,
 	return 0;
 }
 
+void
+ixgbe_ethertype_filter_program(struct ixgbe_hw *hw, uint8_t idx,
+			uint32_t etqf, uint32_t etqs)
+{
+	IXGBE_WRITE_REG(hw, IXGBE_ETQF(idx), etqf);
+	IXGBE_WRITE_REG(hw, IXGBE_ETQS(idx), etqs);
+	IXGBE_WRITE_FLUSH(hw);
+}
+
+static int
+ixgbe_ethertype_table_lookup(const struct ixgbe_ethertype_table *table,
+			uint16_t ethertype)
+{
+	int idx;
+
+	for (idx = 0; idx < IXGBE_MAX_ETQF_FILTERS; idx++) {
+		if (table->entries[idx].used &&
+				table->entries[idx].ethertype == ethertype)
+			return idx;
+	}
+	return -ENOENT;
+}
+
+int
+ixgbe_ethertype_table_add(struct ixgbe_ethertype_table *table,
+			uint16_t ethertype, uint32_t etqf, uint32_t etqs)
+{
+	int free_idx = -1;
+	int i;
+
+	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
+		if (table->entries[i].used) {
+			if (table->entries[i].ethertype == ethertype)
+				return -EEXIST;
+		} else if (free_idx < 0) {
+			free_idx = i;
+		}
+	}
+	if (free_idx < 0)
+		return -ENOSPC;
+
+	table->entries[free_idx].ethertype = ethertype;
+	table->entries[free_idx].etqf = etqf;
+	table->entries[free_idx].etqs = etqs;
+	table->entries[free_idx].used = true;
+
+	return free_idx;
+}
+
+int
+ixgbe_ethertype_table_del(struct ixgbe_ethertype_table *table, uint8_t idx)
+{
+	if (idx >= IXGBE_MAX_ETQF_FILTERS || !table->entries[idx].used)
+		return -ENOENT;
+
+	table->entries[idx] = (struct ixgbe_ethertype_entry){0};
+
+	return 0;
+}
+
 int
 ixgbe_add_del_ethertype_filter(struct ixgbe_adapter *adapter,
-			struct rte_eth_ethertype_filter *filter,
-			bool add)
+			struct rte_eth_ethertype_filter *filter, bool add)
 {
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(adapter);
-	struct ixgbe_filter_info *filter_info =
-		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter);
-	uint32_t etqf = 0;
-	uint32_t etqs = 0;
-	int ret;
-	struct ixgbe_ethertype_filter ethertype_filter;
+	struct ixgbe_ethertype_table *table =
+		&IXGBE_DEV_PRIVATE_TO_FILTER_INFO(adapter)->ethertype_table;
+	uint32_t etqf, etqs;
+	int idx;
 
-	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM)
+	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM ||
+		filter->ether_type == RTE_ETHER_TYPE_IPV4 ||
+		filter->ether_type == RTE_ETHER_TYPE_IPV6 ||
+		filter->flags & (RTE_ETHTYPE_FLAGS_MAC | RTE_ETHTYPE_FLAGS_DROP))
 		return -EINVAL;
 
-	if (filter->ether_type == RTE_ETHER_TYPE_IPV4 ||
-		filter->ether_type == RTE_ETHER_TYPE_IPV6) {
-		PMD_DRV_LOG(ERR, "unsupported ether_type(0x%04x) in"
-			" ethertype filter.", filter->ether_type);
-		return -EINVAL;
-	}
-
-	if (filter->flags & RTE_ETHTYPE_FLAGS_MAC) {
-		PMD_DRV_LOG(ERR, "mac compare is unsupported.");
-		return -EINVAL;
-	}
-	if (filter->flags & RTE_ETHTYPE_FLAGS_DROP) {
-		PMD_DRV_LOG(ERR, "drop option is unsupported.");
-		return -EINVAL;
-	}
-
-	ret = ixgbe_ethertype_filter_lookup(filter_info, filter->ether_type);
-	if (ret >= 0 && add) {
-		PMD_DRV_LOG(ERR, "ethertype (0x%04x) filter exists.",
-			    filter->ether_type);
-		return -EEXIST;
-	}
-	if (ret < 0 && !add) {
-		PMD_DRV_LOG(ERR, "ethertype (0x%04x) filter doesn't exist.",
-			    filter->ether_type);
-		return -ENOENT;
-	}
-
 	if (add) {
-		etqf = IXGBE_ETQF_FILTER_EN;
-		etqf |= (uint32_t)filter->ether_type;
-		etqs |= (uint32_t)((filter->queue <<
-				    IXGBE_ETQS_RX_QUEUE_SHIFT) &
-				    IXGBE_ETQS_RX_QUEUE);
-		etqs |= IXGBE_ETQS_QUEUE_EN;
-
-		ethertype_filter.ethertype = filter->ether_type;
-		ethertype_filter.etqf = etqf;
-		ethertype_filter.etqs = etqs;
-		ethertype_filter.conf = FALSE;
-		ret = ixgbe_ethertype_filter_insert(filter_info,
-						    &ethertype_filter);
-		if (ret < 0) {
-			PMD_DRV_LOG(ERR, "ethertype filters are full.");
-			return -ENOSPC;
-		}
+		etqf = IXGBE_ETQF_FILTER_EN | filter->ether_type;
+		etqs = IXGBE_ETQS_QUEUE_EN |
+			(((uint32_t)filter->queue << IXGBE_ETQS_RX_QUEUE_SHIFT) &
+			IXGBE_ETQS_RX_QUEUE);
+		idx = ixgbe_ethertype_table_add(table, filter->ether_type, etqf, etqs);
+		if (idx < 0)
+			return idx;
 	} else {
-		ret = ixgbe_ethertype_filter_remove(filter_info, (uint8_t)ret);
-		if (ret < 0)
-			return -ENOSYS;
+		idx = ixgbe_ethertype_table_lookup(table, filter->ether_type);
+		if (idx < 0)
+			return idx;
+		ixgbe_ethertype_table_del(table, idx);
+		etqf = 0;
+		etqs = 0;
 	}
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(ret), etqf);
-	IXGBE_WRITE_REG(hw, IXGBE_ETQS(ret), etqs);
-	IXGBE_WRITE_FLUSH(hw);
-
+	ixgbe_ethertype_filter_program(hw, idx, etqf, etqs);
 	return 0;
 }
 
@@ -7148,12 +7170,29 @@ static int
 ixgbe_timesync_enable(struct rte_eth_dev *dev)
 {
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
 	uint32_t tsync_ctl;
 	uint32_t tsauxc;
+	uint32_t etqf;
 	struct timespec ts;
+	int idx;
 
 	memset(&ts, 0, sizeof(struct timespec));
 
+	/* Reserve the shared ETQF slot before changing timestamp hardware. */
+	etqf = RTE_ETHER_TYPE_1588 | IXGBE_ETQF_FILTER_EN | IXGBE_ETQF_1588;
+	if (!filter_info->timesync_installed) {
+		idx = ixgbe_ethertype_table_add(&filter_info->ethertype_table,
+				RTE_ETHER_TYPE_1588, etqf, 0);
+		if (idx < 0) {
+			PMD_DRV_LOG(ERR, "no free ETQF slot for 1588 timestamping");
+			return idx;
+		}
+		filter_info->timesync_idx = idx;
+		filter_info->timesync_installed = true;
+	}
+
 	/* get current system time */
 	clock_gettime(CLOCK_REALTIME, &ts);
 
@@ -7171,10 +7210,7 @@ ixgbe_timesync_enable(struct rte_eth_dev *dev)
 	ixgbe_start_timecounters(dev);
 
 	/* Enable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588),
-			(RTE_ETHER_TYPE_1588 |
-			 IXGBE_ETQF_FILTER_EN |
-			 IXGBE_ETQF_1588));
+	ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, etqf, 0);
 
 	/* Enable timestamping of received PTP packets. */
 	tsync_ctl = IXGBE_READ_REG(hw, IXGBE_TSYNCRXCTL);
@@ -7198,6 +7234,8 @@ static int
 ixgbe_timesync_disable(struct rte_eth_dev *dev)
 {
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ixgbe_filter_info *filter_info =
+		IXGBE_DEV_PRIVATE_TO_FILTER_INFO(dev->data->dev_private);
 	uint32_t tsync_ctl;
 
 	/* Disable timestamping of transmitted PTP packets. */
@@ -7211,7 +7249,12 @@ ixgbe_timesync_disable(struct rte_eth_dev *dev)
 	IXGBE_WRITE_REG(hw, IXGBE_TSYNCRXCTL, tsync_ctl);
 
 	/* Disable L2 filtering of IEEE1588/802.1AS Ethernet frame types. */
-	IXGBE_WRITE_REG(hw, IXGBE_ETQF(IXGBE_ETQF_FILTER_1588), 0);
+	if (filter_info->timesync_installed) {
+		ixgbe_ethertype_filter_program(hw, filter_info->timesync_idx, 0, 0);
+		ixgbe_ethertype_table_del(&filter_info->ethertype_table,
+				filter_info->timesync_idx);
+		filter_info->timesync_installed = false;
+	}
 
 	/* Stop incrementing the System Time registers. */
 	IXGBE_WRITE_REG(hw, IXGBE_TIMINCA, 0);
@@ -8366,13 +8409,14 @@ ixgbe_ethertype_filter_restore(struct rte_eth_dev *dev)
 	int i;
 
 	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
-		if (filter_info->ethertype_mask & (1 << i)) {
-			IXGBE_WRITE_REG(hw, IXGBE_ETQF(i),
-					filter_info->ethertype_filters[i].etqf);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQS(i),
-					filter_info->ethertype_filters[i].etqs);
-			IXGBE_WRITE_FLUSH(hw);
-		}
+		/* skip over entries not managed by rte_flow */
+		if (i == filter_info->timesync_idx ||
+				i == filter_info->antispoof_idx||
+				!filter_info->ethertype_table.entries[i].used)
+			continue;
+		ixgbe_ethertype_filter_program(hw, i,
+			filter_info->ethertype_table.entries[i].etqf,
+			filter_info->ethertype_table.entries[i].etqs);
 	}
 }
 
@@ -8480,14 +8524,13 @@ ixgbe_clear_all_ethertype_filter(struct rte_eth_dev *dev)
 	int i;
 
 	for (i = 0; i < IXGBE_MAX_ETQF_FILTERS; i++) {
-		if (filter_info->ethertype_mask & (1 << i) &&
-		    !filter_info->ethertype_filters[i].conf) {
-			(void)ixgbe_ethertype_filter_remove(filter_info,
-							    (uint8_t)i);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQF(i), 0);
-			IXGBE_WRITE_REG(hw, IXGBE_ETQS(i), 0);
-			IXGBE_WRITE_FLUSH(hw);
-		}
+		/* only clear rte_flow filters */
+		if (!filter_info->ethertype_table.entries[i].used ||
+			(filter_info->timesync_installed && i == filter_info->timesync_idx) ||
+			(filter_info->antispoof_installed && i == filter_info->antispoof_idx))
+			continue;
+		ixgbe_ethertype_table_del(&filter_info->ethertype_table, i);
+		ixgbe_ethertype_filter_program(hw, i, 0, 0);
 	}
 }
 
drivers/net/intel/ixgbe/ixgbe_ethdev.h +22 -63

113 lines not on this page · raw message

drivers/net/intel/ixgbe/ixgbe_pf.c +26 -23

76 lines not on this page · raw message

Signature · 1 line
2.52.0