DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH v2 1/2] net/ice: properly handle TM hierarchy deletion
From: Bruce Richardson @ 2026-05-07 13:39 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, Mukul Katiyar, stable
In-Reply-To: <afyPdAwI6_ClTYFK@bricha3-mobl1.ger.corp.intel.com>

On Thu, May 07, 2026 at 02:11:16PM +0100, Bruce Richardson wrote:
> On Thu, May 07, 2026 at 12:59:29PM +0000, Ciara Loftus wrote:
> > From: Mukul Katiyar <mukul@versa-networks.com>
> > 
> > When a TM hierarchy is fully deleted and then committed, the hardware
> > scheduler nodes may be left with any bandwidth limits that were
> > programmed by the previous hierarchy commit. These stale limits may
> > remain in effect the next time the device starts, permanently throttling
> > traffic even though the TM hierarchy was removed.
> > 
> > Fix this by resetting all descendant hardware scheduler nodes to their
> > default state when committing an empty hierarchy. Also restore the port
> > queue count to its hardware default and clear the committed flag so the
> > port starts cleanly without any TM configuration applied.
> > 
> > Fixes: 715d449a965b ("net/ice: enhance Tx scheduler hierarchy support")
> > Cc: stable@dpdk.org
> > 
> > Signed-off-by: Mukul Katiyar <mukul@versa-networks.com>
> > Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> > ---
> > v2:
> > * Ensure restore is only performed if a hierarchy has already been
> > committed
> > ---
> >  .mailmap                       |  1 +
> >  drivers/net/intel/ice/ice_tm.c | 24 ++++++++++++++++++++++--
> >  2 files changed, 23 insertions(+), 2 deletions(-)
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>

Series applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* Re: [PATCH v2 1/2] net/ice: properly handle TM hierarchy deletion
From: Bruce Richardson @ 2026-05-07 13:11 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, Mukul Katiyar, stable
In-Reply-To: <20260507125930.1668860-1-ciara.loftus@intel.com>

On Thu, May 07, 2026 at 12:59:29PM +0000, Ciara Loftus wrote:
> From: Mukul Katiyar <mukul@versa-networks.com>
> 
> When a TM hierarchy is fully deleted and then committed, the hardware
> scheduler nodes may be left with any bandwidth limits that were
> programmed by the previous hierarchy commit. These stale limits may
> remain in effect the next time the device starts, permanently throttling
> traffic even though the TM hierarchy was removed.
> 
> Fix this by resetting all descendant hardware scheduler nodes to their
> default state when committing an empty hierarchy. Also restore the port
> queue count to its hardware default and clear the committed flag so the
> port starts cleanly without any TM configuration applied.
> 
> Fixes: 715d449a965b ("net/ice: enhance Tx scheduler hierarchy support")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Mukul Katiyar <mukul@versa-networks.com>
> Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> ---
> v2:
> * Ensure restore is only performed if a hierarchy has already been
> committed
> ---
>  .mailmap                       |  1 +
>  drivers/net/intel/ice/ice_tm.c | 24 ++++++++++++++++++++++--
>  2 files changed, 23 insertions(+), 2 deletions(-)
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

^ permalink raw reply

* [PATCH v2 2/2] net/ice: fix shaper profile reference count tracking
From: Ciara Loftus @ 2026-05-07 12:59 UTC (permalink / raw)
  To: dev; +Cc: Ciara Loftus, stable, Bruce Richardson
In-Reply-To: <20260507125930.1668860-1-ciara.loftus@intel.com>

Currently, when a TM node is added with a shaper profile assigned,
the profile's reference count is not incremented. Equally, when a node
is deleted, the count is not decremented. As a result, the guard that
blocks deletion of an in-use profile never triggers, allowing the profile
to be freed while nodes still hold a pointer to it.

Fix by maintaining the reference count correctly when nodes take or
release a shaper profile, so that deletion of an in-use profile is
properly rejected.

Fixes: 8c481c3bb65b ("net/ice: support queue and queue group bandwidth limit")
Cc: stable@dpdk.org

Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 drivers/net/intel/ice/ice_tm.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/intel/ice/ice_tm.c b/drivers/net/intel/ice/ice_tm.c
index 2bb684bb02..015a827d7a 100644
--- a/drivers/net/intel/ice/ice_tm.c
+++ b/drivers/net/intel/ice/ice_tm.c
@@ -456,6 +456,8 @@ ice_tm_node_add(struct rte_eth_dev *dev, uint32_t node_id,
 		tm_node->parent = NULL;
 		tm_node->reference_count = 0;
 		tm_node->shaper_profile = shaper_profile;
+		if (shaper_profile != NULL)
+			shaper_profile->reference_count++;
 		tm_node->children = RTE_PTR_ADD(tm_node, sizeof(struct ice_tm_node));
 		tm_node->params = *params;
 		pf->tm_conf.root = tm_node;
@@ -518,6 +520,8 @@ ice_tm_node_add(struct rte_eth_dev *dev, uint32_t node_id,
 	tm_node->parent = parent_node;
 	tm_node->level = level_id;
 	tm_node->shaper_profile = shaper_profile;
+	if (shaper_profile != NULL)
+		shaper_profile->reference_count++;
 	tm_node->children = RTE_PTR_ADD(tm_node, sizeof(struct ice_tm_node));
 	tm_node->parent->children[tm_node->parent->reference_count++] = tm_node;
 	tm_node->params = *params;
@@ -568,6 +572,8 @@ ice_tm_node_delete(struct rte_eth_dev *dev, uint32_t node_id,
 
 	/* root node */
 	if (tm_node->level == 0) {
+		if (tm_node->shaper_profile != NULL)
+			tm_node->shaper_profile->reference_count--;
 		rte_free(tm_node);
 		pf->tm_conf.root = NULL;
 		return 0;
@@ -582,6 +588,8 @@ ice_tm_node_delete(struct rte_eth_dev *dev, uint32_t node_id,
 		tm_node->parent->children[j] = tm_node->parent->children[j + 1];
 
 	tm_node->parent->reference_count--;
+	if (tm_node->shaper_profile != NULL)
+		tm_node->shaper_profile->reference_count--;
 	rte_free(tm_node);
 
 	return 0;
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 1/2] net/ice: properly handle TM hierarchy deletion
From: Ciara Loftus @ 2026-05-07 12:59 UTC (permalink / raw)
  To: dev; +Cc: Mukul Katiyar, stable, Ciara Loftus

From: Mukul Katiyar <mukul@versa-networks.com>

When a TM hierarchy is fully deleted and then committed, the hardware
scheduler nodes may be left with any bandwidth limits that were
programmed by the previous hierarchy commit. These stale limits may
remain in effect the next time the device starts, permanently throttling
traffic even though the TM hierarchy was removed.

Fix this by resetting all descendant hardware scheduler nodes to their
default state when committing an empty hierarchy. Also restore the port
queue count to its hardware default and clear the committed flag so the
port starts cleanly without any TM configuration applied.

Fixes: 715d449a965b ("net/ice: enhance Tx scheduler hierarchy support")
Cc: stable@dpdk.org

Signed-off-by: Mukul Katiyar <mukul@versa-networks.com>
Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
---
v2:
* Ensure restore is only performed if a hierarchy has already been
committed
---
 .mailmap                       |  1 +
 drivers/net/intel/ice/ice_tm.c | 24 ++++++++++++++++++++++--
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/.mailmap b/.mailmap
index 22476860ed..2f96e80f16 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1132,6 +1132,7 @@ Moti Haimovsky <motih@mellanox.com>
 Muhammad Ahmad <muhammad.ahmad@emumba.com>
 Muhammad Bilal <m.bilal@emumba.com>
 Mukesh Dua <mukesh.dua81@gmail.com>
+Mukul Katiyar <mukul@versa-networks.com>
 Murphy Yang <murphyx.yang@intel.com>
 Murthy NSSR <nidadavolu.murthy@caviumnetworks.com>
 Muthurajan Jayakumar <muthurajan.jayakumar@intel.com>
diff --git a/drivers/net/intel/ice/ice_tm.c b/drivers/net/intel/ice/ice_tm.c
index ff53f2acfd..2bb684bb02 100644
--- a/drivers/net/intel/ice/ice_tm.c
+++ b/drivers/net/intel/ice/ice_tm.c
@@ -805,6 +805,19 @@ create_sched_node_recursive(struct ice_pf *pf, struct ice_port_info *pi,
 	return 0;
 }
 
+static void
+reset_hw_node_recursive(struct ice_hw *hw, struct ice_sched_node *node)
+{
+	uint16_t i;
+
+	for (i = 0; i < node->num_children; i++) {
+		reset_hw_node_recursive(hw, node->children[i]);
+		if (ice_cfg_hw_node(hw, NULL, node->children[i]))
+			PMD_DRV_LOG(WARNING, "Failed to reset node %u to default configuration",
+					node->children[i]->info.node_teid);
+	}
+}
+
 static int
 commit_new_hierarchy(struct rte_eth_dev *dev)
 {
@@ -820,8 +833,15 @@ commit_new_hierarchy(struct rte_eth_dev *dev)
 	struct ice_sched_node *new_vsi_root = hw->vsi_ctx[pf->main_vsi->idx]->sched.vsi_node[0];
 
 	if (sw_root == NULL) {
-		PMD_DRV_LOG(ERR, "No root node defined in TM hierarchy");
-		return -1;
+		if (!pf->tm_conf.committed) {
+			PMD_DRV_LOG(ERR, "No root node defined in TM hierarchy");
+			return -EINVAL;
+		}
+		/* TM hierarchy deleted. Restore default scheduler state. */
+		reset_hw_node_recursive(hw, hw->vsi_ctx[pf->main_vsi->idx]->sched.vsi_node[0]);
+		pf->main_vsi->nb_qps = pf->lan_nb_qps;
+		pf->tm_conf.committed = false;
+		return ice_alloc_lan_q_ctx(hw, 0, 0, pf->main_vsi->nb_qps);
 	}
 
 	/* handle case where VSI node needs to move DOWN the hierarchy */
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v12 0/6] flow_parser: add shared parser library
From: Lukáš Šišmiš @ 2026-05-07 12:29 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, orika, thomas
In-Reply-To: <20260505145957.73f12027@stephen-xps.local>

[-- Attachment #1: Type: text/plain, Size: 29043 bytes --]

st 6. 5. 2026 v 0:00 odesílatel Stephen Hemminger <
stephen@networkplumber.org> napsal:

> On Tue,  5 May 2026 20:39:07 +0200
> Lukas Sismis <sismis@dyna-nic.com> wrote:
>
> > This series extracts the testpmd flow CLI parser into a reusable library,
> > enabling external applications to parse rte_flow rules using testpmd
> syntax.
> >
> > Motivation
> > ----------
> > External applications like Suricata IDS [1] need to express hardware
> filtering
> > rules in a consistent, human-readable format. Rather than inventing
> custom
> > syntax, reusing testpmd's well-tested flow grammar provides immediate
> > compatibility with existing documentation and user knowledge.
> >
> > Note: This library provides only one way to create rte_flow structures.
> > Applications can also construct rte_flow_attr, rte_flow_item[], and
> > rte_flow_action[] directly in C code.
> >
> > Design
> > ------
> > The library (librte_flow_parser) exposes the following APIs:
> > - rte_flow_parser_parse_attr_str(): Parse attributes only
> > - rte_flow_parser_parse_pattern_str(): Parse patterns only
> > - rte_flow_parser_parse_actions_str(): Parse actions only
> >
> > Testpmd is updated to use the library, ensuring a single
> > maintained parser implementation.
>
> I wish it was not just a port of testpmd code to a library but had
> been done as a clean implementation; that said the current version is
> much better.
>
> AI had lots of feedback. The part that matters to me is the new dependency
> chain; and also having a syntax that looks too much like testpmd.
>
> Would prefer that only the flow part of the string was passed.
>
>
Thanks for the feedback.

Clean implementation would be best, but it is also worth noting that it
would encompass great engineering effort to get there. Porting/extracting
testpmd's flow parser to a separate library was the original intention, to
test the waters and see if it would be used by the community and work well
in the DPDK environment. More specifically, the very first original
intention was to reuse the simple network pattern language to define
rte_flow rule structures (eth / ipv4 / ...). Since carving out just the
network layer pattern parser seemed not feasible (the pattern section
contains extra network-independent information, e.g.,
port_id/conntrack/represented ports/encaps), the scope extended to the full
"flow create" command of the testpmd. That then included an even larger
scope of features to consider in the library.

To comment on the dependency problem, I would like to mention that if this
were a separate library, there would be no changes in dependencies in the
core libraries.

---

I don't quite understand your wish of "only the flow part of the string was
passed". If you mean to e.g. pass only the pattern string to get pattern
structures returned, then the simple API is for that purpose, e.g.:

/**
 * Parse a flow pattern from a CLI snippet.
 *
 * Parses pattern strings as used inside a flow command, such as
 * "eth / ipv4 src is 192.168.1.1 / tcp dst is 80 / end".
 *
 */
__rte_experimental
int rte_flow_parser_parse_pattern_str(const char *src,
     const struct rte_flow_item **pattern,
     uint32_t *pattern_n);

If you meant to say you want to pass the full "flow create ... " command to
output attribute + patterns + actions structures in one call *only*, then
it is possible. But while iterating on this, I came to the conclusion that
"flow create" is truly a control directive of the testpmd application and
should not be dragged to the rte_flow parser library. So I don't think it
is a good idea. I envision that custom applications could define their own
rule-specification syntax, whether it would be the usual "flow create X",
YAML-structure, or something else.

---

On the related note, to comment on your concern about "having a syntax that
looks too much like testpmd". Having testpmd syntax is exactly the intent
of this work: to have a single parser for both the testpmd and the rte_flow
parser library itself. With all this work, I thought about dailing back and
e.g. reducing the scope of the patch to only introduce a string parser for
the limited set of network-layer patterns (withot the tokenizer). But this
would lead to code duplication (which I wanted to avoid with all this
work), as I didn't find a straightforward way to have part of the testpmd
parser internally and the other in the library.

Perhaps, this can be a good point to decide if a port of testpmd rte_flow
parser would be a welcome contribution by DPDK maintainers, especially at
this early stage of the library creation. I would greatly appreciate "a
clean library rewrite," but at this moment, I estimate the complexity to be
higher than the capacity that I have currently.







> ---
>
> I have enough material for a comprehensive architectural review. Let me
> write it up.
>
> ---
>
> Below is my deep-dive review of the v12 series. Architecturally, this is a
> major step beyond earlier versions in terms of polish (header split, doc
> page, real example, real tests), but the underlying structure is still
> essentially the testpmd `cmdline_flow.c` lifted wholesale into
> `lib/ethdev`, with a thin "simple API" wrapper that synthesizes fake
> testpmd commands. Several of the deeper structural problems remain, plus
> there are concrete bugs.
>
> ```
> Series-level architectural review
> =================================
>
> Context
> -------
>
> The series moves the ~14,400-line app/test-pmd/cmdline_flow.c into a
> new lib/ethdev/rte_flow_parser.c, exposes a "simple" string-to-rte_flow
> API in lib/ethdev/rte_flow_parser.h, exposes a fuller cmdline-aware API
> in lib/ethdev/rte_flow_parser_config.h, and adds cmdline glue in
> lib/cmdline/rte_flow_parser_cmdline.[ch]. testpmd (patch 4) is then
> ported to consume the library via a dispatch callback that maps
> RTE_FLOW_PARSER_CMD_* enum values back onto its existing port_flow_*
> functions.
>
> The main functional change versus past versions is that the simple API
> (parse_attr_str / parse_pattern_str / parse_actions_str /
> parse_flow_rule) now exists separately from the full command grammar.
> Internally however the simple API is still implemented by string-
> synthesizing a fake "flow validate 0 ..." command, running it through
> the full parser, and harvesting one output field. So the architectural
> center of gravity is unchanged: this is the testpmd grammar exposed as
> a library.
>
> Errors
> ======
>
> Patch 3/6 -- ethdev: add flow parser library
> ---------------------------------------------
>
> A1. lib/cmdline now depends on lib/ethdev (layer inversion).
>
>   lib/cmdline/meson.build:
>     -deps += ['net']
>     +deps += ['net', 'ethdev']
>
>   lib/cmdline is a foundational utility used by examples, internal
>   tools, and tests that have nothing to do with networking. Pulling
>   ethdev into it just so rte_flow_parser_cmdline.c can call into
>   rte_flow_parser_* exports inverts the layering. Every consumer of
>   libcmdline now links libethdev.
>
>   The cmdline/flow-parser glue belongs in either lib/ethdev (and the
>   header in lib/ethdev too, with ethdev depending on cmdline -- the
>   natural direction) or in a new top-level lib/flow_parser that
>   depends on both. It does not belong inside lib/cmdline.
>
> A2. The "simple API" returns aliased pointers into a single 4096-byte
>     static buffer, with no way for a caller to retain a result.
>
>   lib/ethdev/rte_flow_parser.c:
>     #define FLOW_PARSER_SIMPLE_BUF_SIZE 4096
>     static uint8_t
> flow_parser_simple_parse_buf[FLOW_PARSER_SIMPLE_BUF_SIZE];
>     ...
>     static int parser_simple_parse(const char *cmd, ... **out) {
>         memset(flow_parser_simple_parse_buf, 0, sizeof(...));
>         ret = rte_flow_parser_parse(cmd,
>                 (struct rte_flow_parser_output
> *)flow_parser_simple_parse_buf,
>                 sizeof(flow_parser_simple_parse_buf));
>         ...
>         *out = (struct rte_flow_parser_output
> *)flow_parser_simple_parse_buf;
>     }
>
>   Then rte_flow_parser_parse_pattern_str() does:
>         *pattern = out->args.vc.pattern;     /* points into buf */
>         *pattern_n = out->args.vc.pattern_n;
>
>   Three resulting problems:
>
>   (a) Two consecutive calls alias. Any caller that wants to hold two
>       parsed patterns simultaneously (e.g. parse two flows and call
>       rte_flow_create() on each) cannot, without writing their own
>       deep-copy via rte_flow_conv(). This is a pervasive footgun and
>       is only loosely documented as "Points to internal storage valid
>       until the next parse call."
>
>   (b) The 4096-byte cap silently rejects any flow rule whose
>       serialized output exceeds the buffer (returns -ENOBUFS), with
>       no way for the caller to predict what fits.
>
>   (c) The simple API itself uses parse_pattern_str inside a setter
>       example flow in the example program:
>
>         ret = rte_flow_parser_parse_pattern_str(..., &items, &items_n);
>         if (ret == 0)
>             ret = rte_flow_parser_raw_encap_conf_set(0, items, items_n);
>
>       This particular sequence is safe today because raw_encap_conf_set
>       doesn't re-enter the parser, but nothing in the API contract
>       prevents a future parser call between (A) and (B), and the
>       example pattern teaches users a fragile idiom.
>
>   Suggested direction: provide a caller-supplied output mode -- e.g.
>   rte_flow_parser_parse_pattern_str(src, items, items_cap, &items_n)
>   where the caller provides storage. Or return an opaque handle owning
>   the storage (rte_flow_parser_pattern_new / _free), modeled on
>   rte_flow_conv().
>
> A3. All cmdline tab-completion hooks for dynamic IDs are stubbed out
>     to return zero candidates, with no override mechanism.
>
>   lib/ethdev/rte_flow_parser.c:
>     static inline int
>     parser_port_id_is_invalid(uint16_t port_id)
>     {
>         (void)port_id;
>         return 0;            /* always "valid" */
>     }
>     static inline uint16_t
>     parser_flow_rule_count(uint16_t port_id) { return 0; }
>     static inline int
>     parser_flow_rule_id_get(...) { return -ENOENT; }
>     /* same pattern for pattern/actions templates, tables, queues,
>      * RSS queues, indirect actions */
>
>   These are the routines comp_rule_id, comp_pattern_template_id,
>   comp_actions_template_id, comp_table_id, comp_queue_id, etc. all
>   call into when cmdline asks for tab-completion candidates. With
>   these stubs in place, the library version of testpmd interactive
>   mode can never tab-complete a rule ID, template ID, table ID,
>   queue ID, or indirect action ID -- a regression versus the
>   current testpmd cmdline_flow.c which queries port_flow_list,
>   port_flow_template_list, etc. directly.
>
>   patch 4 (testpmd integration) does not register or override these
>   stubs anywhere. There is no callback registration interface for
>   the application to provide "give me rule IDs for port N" / "give
>   me table IDs for port N" / etc.
>
>   A complete library extraction needs an introspection ops struct
>   that the application registers alongside rte_flow_parser_config,
>   e.g.:
>
>     struct rte_flow_parser_introspect_ops {
>         uint16_t (*flow_rule_count)(uint16_t port_id);
>         int      (*flow_rule_id_get)(uint16_t port_id, unsigned idx,
>                                      uint64_t *rule_id);
>         /* templates, tables, queues, indirect actions, ... */
>     };
>     int rte_flow_parser_introspect_register(
>             const struct rte_flow_parser_introspect_ops *ops);
>
> A4. testpmd dispatch repurposes rte_flow_attr.reserved as a side
>     channel for relaxed_matching.
>
>   app/test-pmd/flow_parser.c (patch 4):
>     case RTE_FLOW_PARSER_CMD_PATTERN_TEMPLATE_CREATE:
>         port_flow_pattern_template_create(in->port,
>             in->args.vc.pat_templ_id,
>             &((const struct rte_flow_pattern_template_attr) {
>                 .relaxed_matching = in->args.vc.attr.reserved,
>                 .ingress  = in->args.vc.attr.ingress,
>                 .egress   = in->args.vc.attr.egress,
>                 .transfer = in->args.vc.attr.transfer,
>             }),
>             in->args.vc.pattern);
>
>   rte_flow_attr.reserved is documented in lib/ethdev/rte_flow.h as
>   reserved and required to be zero. Smuggling
>   pattern_template_attr.relaxed_matching through that field couples
>   the library output to a hack and breaks the moment anyone sets
>   rte_flow_attr.reserved for any other purpose, or starts validating
>   it. The output struct already has a vc.{pat_templ,act_templ,table}
>   arm -- relaxed_matching belongs there, not overlaid on attr.reserved.
>
> A5. Public preprocessor macros in rte_flow_parser_config.h are
>     unprefixed and one of them collides with a generic name.
>
>   lib/ethdev/rte_flow_parser_config.h:
>     #define ACTION_RAW_ENCAP_MAX_DATA   512
>     #define RAW_ENCAP_CONFS_MAX_NUM     8
>     #define ACTION_RSS_QUEUE_NUM        128
>     #define ACTION_VXLAN_ENCAP_ITEMS_NUM 6
>     #define ACTION_NVGRE_ENCAP_ITEMS_NUM 5
>     #define ACTION_IPV6_EXT_PUSH_MAX_DATA 512
>     #define IPV6_EXT_PUSH_CONFS_MAX_NUM 8
>     #define ACTION_SAMPLE_ACTIONS_NUM   10
>     #define RAW_SAMPLE_CONFS_MAX_NUM    8
>     #ifndef RSS_HASH_KEY_LENGTH
>     #define RSS_HASH_KEY_LENGTH         64
>     #endif
>
>   Every one of these is now exported by an installed public header
>   with no RTE_FLOW_PARSER_ prefix. RSS_HASH_KEY_LENGTH in particular
>   is a generic name almost guaranteed to collide -- any application
>   that defined its own RSS_HASH_KEY_LENGTH=40 (matching its hardware)
>   before including this header would silently get 40 inside flow
>   parser slots, with the parser's slot layout corrupted by mismatched
>   array sizes.
>
>   All of these need an RTE_FLOW_PARSER_ prefix and the #ifndef guard
>   on RSS_HASH_KEY_LENGTH should be removed -- it is an invitation to
>   define-mismatch bugs.
>
> A6. Doc/header inconsistency: parser_parse() declared in config.h
>     but documented as living in cmdline.h.
>
>   doc/guides/prog_guide/flow_parser_lib.rst:
>     "rte_flow_parser_parse() from rte_flow_parser_cmdline.h parses
>     complete flow CLI commands ..."
>
>   but the declaration is in lib/ethdev/rte_flow_parser_config.h
>   (line 15342 of the diff). The doc is also internally inconsistent:
>   the same .rst earlier says "Additional functions for full command
>   parsing and cmdline integration are available in
>   rte_flow_parser_cmdline.h. These include rte_flow_parser_parse()
>   ..." -- which is wrong twice.
>
> A7. local_cmd_flow->help_str = ... mutates the cmdline instruction.
>
>   lib/cmdline/rte_flow_parser_cmdline.c:
>     if (local_cmd_flow != NULL)
>         local_cmd_flow->help_str = help ? help : name;
>
>   This mutates the cmdline_parse_inst_t passed to
>   rte_flow_parser_cmdline_register(). Idiomatic cmdline usage in DPDK
>   declares cmdline_parse_inst_t variables as static const aggregates
>   (see lib/cmdline/cmdline_parse.h examples and the rest of testpmd).
>   Passing such an instance here writes to read-only memory and
>   segfaults. The .rst note ("The library writes to inst->help_str
>   dynamically ... must remain valid for the lifetime of the cmdline
>   session") flags the lifetime question but does not flag the
>   mutability requirement, which is the actually fatal one.
>
>   The fix is to keep help_str storage internal to the library and
>   return it via a side channel (e.g. an out-pointer in the get_help
>   callback) rather than mutating the caller's instruction.
>
> Patch 4/6 -- app/testpmd: use flow parser from ethdev
> ------------------------------------------------------
>
> A8. Tab completion regression for dynamic IDs.
>
>   As described in A3, removing app/test-pmd/cmdline_flow.c and
>   replacing it with the library means testpmd's interactive flow
>   command-line loses tab completion on rule IDs, pattern/actions
>   template IDs, table IDs, queue IDs, RSS queue IDs, and indirect
>   action IDs. Today's cmdline_flow.c calls port_flow_list,
>   port_flow_template_list, port_flow_template_table_list, etc. The
>   replacement library calls parser_flow_rule_count, etc., which
>   return 0.
>
>   Until the introspection callback (A3) is in place, this patch
>   needs at minimum a release note entry explicitly calling out the
>   loss of tab completion. As written, the change description in the
>   patch does not mention it.
>
> Warnings
> ========
>
> Patch 2/6 -- ethdev: add RSS type helper APIs
> ----------------------------------------------
>
> W1. The "all" entry hardcodes the OR of every RTE_ETH_RSS_* protocol
>     bit and will silently go stale.
>
>   lib/ethdev/rte_ethdev.c:
>     { "all", RTE_ETH_RSS_ETH | RTE_ETH_RSS_VLAN | RTE_ETH_RSS_IP |
>             RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP | RTE_ETH_RSS_SCTP |
>             RTE_ETH_RSS_L2_PAYLOAD | RTE_ETH_RSS_L2TPV3 |
>             RTE_ETH_RSS_ESP | RTE_ETH_RSS_AH | RTE_ETH_RSS_PFCP |
>             RTE_ETH_RSS_GTPU | RTE_ETH_RSS_ECPRI | RTE_ETH_RSS_MPLS |
>             RTE_ETH_RSS_L2TPV2 | RTE_ETH_RSS_IB_BTH },
>
>   Whenever a new RTE_ETH_RSS_* protocol bit is added, this list will
>   drift unless the contributor remembers this table. There is an
>   existing convention RTE_ETH_RSS_PROTO_MASK in rte_ethdev.h that
>   collects these; consider using it (or extending it) so the table
>   tracks the canonical mask automatically.
>
> W2. rte_eth_rss_type_to_str(0) returns "none" but
>     rte_eth_rss_type_to_str(RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_IPV6)
>     returns NULL.
>
>   The "to_str" function uses == on the table, so it succeeds only
>   for values exactly present as a table entry. The Doxygen says
>   "RSS type value (RTE_ETH_RSS_*)" which a caller will reasonably
>   read as accepting any combination of RTE_ETH_RSS_* bits. The
>   doc should explicitly state that only single-entry table values
>   round-trip; arbitrary OR combinations return NULL.
>
> Patch 3/6 -- ethdev: add flow parser library
> ---------------------------------------------
>
> W3. enum rte_flow_parser_command + enum parser_token + token-to-cmd
>     switch is a three-way invariant.
>
>   Adding a new flow command requires:
>     - new entry in enum parser_token         (private)
>     - new entry in enum rte_flow_parser_command (public)
>     - new case in parser_token_to_command()
>   with the comment in flow parser .c file flagging this explicitly.
>
>   This is a maintenance burden and an ABI risk -- forgetting the
>   third step silently maps the new command to
>   RTE_FLOW_PARSER_CMD_UNKNOWN. Consider whether the public enum
>   could be derived from the private one (table-driven) so there is
>   a single source of truth.
>
> W4. rte_flow_parser_parse_attr_str() synthesizes a full validate
>     command including pattern and actions just to harvest the attr.
>
>   lib/ethdev/rte_flow_parser.c:
>     ret = parser_format_cmd(&cmd, "flow validate 0 ",
>                 src, " pattern eth / end actions drop / end");
>
>   This wraps the user input with a hardcoded port id 0 and a
>   default pattern/actions that the simple API immediately throws
>   away. If the testpmd grammar ever cross-validates attr against
>   pattern/actions (e.g. "drop" not allowed on egress + transfer),
>   the simple API breaks for combinations that should be valid in
>   isolation. This is the architectural fragility of the synthesize-
>   and-strip approach in concrete form.
>
> W5. parser_format_cmd uses libc malloc on every simple-API call.
>
>   lib/ethdev/rte_flow_parser.c:
>     static int parser_format_cmd(char **dst, ...) {
>         len = strlen(prefix) + strlen(body) + strlen(suffix) + 1;
>         *dst = malloc(len);
>         ...
>         snprintf(*dst, len, "%s%s%s", prefix, body, suffix);
>
>   Plain malloc, not rte_malloc, is appropriate here since the simple
>   API claims to work without rte_eal_init. But the cost is a malloc/
>   free pair per parse call. For an API that may be used to bulk-load
>   flow rules from a config file or remote control plane, this is
>   pessimal. Consider a stack-or-VLA-based formatter, since the input
>   string length is already known.
>
> W6. parser_str_strip_trailing_end heuristic strips at most one
>     "/ end".
>
>   lib/ethdev/rte_flow_parser.c:
>     /* parser_str_strip_trailing_end ... */
>     if (strncmp(p - 3, "end", 3) != 0)
>         return strlen(src);
>
>   Inputs like "drop / end / end" or "drop / end\t/end " strip only
>   the outermost. The function's comment claims tolerance for any
>   whitespace placement; it does not flag that only one trailing
>   "/ end" is stripped. This is fine in practice for human input but
>   surprising for programmatically generated input.
>
> W7. No rte_flow_parser_config_unregister().
>
>   rte_flow_parser_config_register replaces the previous registration
>   and frees indirect action list configurations created by prior
>   parsing sessions, but there is no unregister entry point. A test
>   harness that wants a clean shutdown -- or a long-lived process
>   that wants to release the SLIST of indlst_conf entries -- has to
>   re-register a zeroed config to flush. Add an unregister API and
>   call it from indlst_conf_cleanup at the same time.
>
> W8. struct rte_flow_parser_vxlan_encap_conf and friends mix bit-
>     fields and uint8_t arrays in a public header.
>
>   struct rte_flow_parser_vxlan_encap_conf {
>       uint32_t select_ipv4:1;
>       uint32_t select_vlan:1;
>       uint32_t select_tos_ttl:1;
>       uint8_t  vni[3];
>       ...
>   };
>
>   C bit-field layout is implementation-defined (order, alignment,
>   signedness of unnamed bit-fields). For a public ABI this is
>   tolerable on DPDK's supported toolchains but fragile across them.
>   At minimum, reserve the remaining 29 bits explicitly:
>       uint32_t reserved:29;
>   to lock in the layout. A more conservative choice is plain
>   uint8_t flags; with named bits.
>
> W9. char type[16] in struct rte_flow_parser_tunnel_ops and char
>     file[128]/filename[128] in rte_flow_parser_output use unnamed
>     magic constants.
>
>   struct rte_flow_parser_tunnel_ops {
>       uint32_t id;
>       char     type[16];
>       ...
>   };
>   ... struct { char file[128]; ... } dump;
>   ... struct { ... char filename[128]; } flex;
>
>   These bake fixed maxima into the ABI. Define and document
>   RTE_FLOW_PARSER_TUNNEL_TYPE_LEN, RTE_FLOW_PARSER_DUMP_FILE_LEN,
>   etc. so contributors don't have to grep to see "is 16 enough for
>   any future tunnel name?".
>
> W10. The output struct's union arm vc has multiple raw pointer
>      fields whose ownership is undocumented.
>
>   struct rte_flow_parser_output {
>       ...
>       union {
>           struct {
>               ...
>               struct rte_flow_item   *pattern;
>               struct rte_flow_action *actions;
>               struct rte_flow_action *masks;
>               uint8_t                *data;
>               ...
>           } vc;
>           struct {
>               uint64_t *rule;
>               uint64_t  rule_n;
>               ...
>           } destroy;
>           ...
>       } args;
>   };
>
>   All these pointers point either into the caller-supplied output
>   buffer (rte_flow_parser_parse) or into the static simple-API
>   buffer (parser_simple_parse). None of this is documented per
>   field. Add a per-field comment ("points into the result_size
>   buffer; valid until the next call") so a caller can see the
>   contract without reading the implementation.
>
> W11. parser_token_to_command() default branch logs ERR with no rate
>      limit.
>
>   static enum rte_flow_parser_command
>   parser_token_to_command(enum parser_token token) {
>       switch (token) {
>       ...
>       default:
>           RTE_LOG_LINE(ERR, ETHDEV, "unknown parser token %u",
>                   (unsigned int)token);
>           return RTE_FLOW_PARSER_CMD_UNKNOWN;
>       }
>   }
>
>   An attacker-controlled or fuzzed input that lands a parse on an
>   unmapped token can flood the log. Use RTE_LOG_LINE_DP or downgrade
>   to DEBUG.
>
> W12. parser_ctx_set_raw_common uses 0x06 / 0x11 instead of
>      IPPROTO_TCP / IPPROTO_UDP.
>
>   case RTE_FLOW_ITEM_TYPE_UDP:
>       size = sizeof(struct rte_udp_hdr);
>       proto = 0x11;
>       break;
>   case RTE_FLOW_ITEM_TYPE_TCP:
>       size = sizeof(struct rte_tcp_hdr);
>       proto = 0x06;
>       break;
>
>   Other branches in the same function correctly use named constants
>   (RTE_ETHER_TYPE_IPV4, IPPROTO_ROUTING). Use IPPROTO_TCP /
>   IPPROTO_UDP for consistency.
>
> Patch 4/6 -- app/test-pmd: use flow parser from ethdev
> -------------------------------------------------------
>
> W13. testpmd_flow_dispatch() (app/test-pmd/flow_parser.c) is missing
>      a release note for the dropped 14k-line app/test-pmd/cmdline_flow.c.
>
>   The release_notes for 26.07 mention the new flow parser library
>   but should also note the testpmd-internal restructuring, since
>   third-party patches against cmdline_flow.c will need rebasing.
>
> Info
> ====
>
> I1. Patch 1 ordering. The cmdline-stddef.h patch (patch 1/6) is
>     independent of the rest of the series. It would land cleanly on
>     its own and shorten this series's review surface.
>
> I2. Series cover letter is missing from the bundle (no [PATCH v12 0/6]
>     visible). For a 14k+ line series, a v12 cover letter summarizing
>     what changed since v11 helps reviewers focus.
>
> I3. Test coverage. test_flow_parser.c and test_flow_parser_simple.c
>     exercise the parse path well, but I do not see a test that
>     deliberately triggers the -ENOBUFS path on the simple API
>     (large pattern overflowing the 4096-byte static buffer) or the
>     aliasing pattern in A2(a). A negative test confirming that two
>     consecutive simple-API calls invalidate the first result would
>     document the contract concretely.
>
> I4. enum rte_flow_parser_command places
>     RTE_FLOW_PARSER_CMD_INDIRECT_ACTION_FLOW_CONF_CREATE alone at
>     the end after the meter-policy block, breaking the per-section
>     grouping established earlier. Move it into the indirect-action
>     block.
>
> I5. struct rte_flow_parser_action_vxlan_encap_data uses an anonymous
>     union for ipv4/ipv6 items. This is the right size optimization
>     but readers have to know to spell .item_ipv4 / .item_ipv6
>     without a containing union name. Either name the union or add
>     a comment.
>
> Summary
> =======
>
> The split into rte_flow_parser.h (simple) and rte_flow_parser_config.h
> (full) is the right direction and a real improvement over earlier
> versions. The remaining structural issues are:
>
>   - cmdline -> ethdev layer inversion (A1)
>   - aliased pointer return semantics with a 4KB static buffer (A2)
>   - tab-completion stubs with no override hook (A3, A8)
>   - hack repurposing of rte_flow_attr.reserved in testpmd glue (A4)
>   - unprefixed/colliding public macro names (A5)
>   - mutated cmdline instruction (A7)
>
> A2 and A3 are the two that most directly justify the "cut/paste"
> critique: they reflect that the parser was extracted from testpmd
> without designing a context-handle ownership model or a callback
> seam for application-side state. Resolving them likely needs the
> parser to take a context handle (alloc/free) and an introspection
> ops struct in addition to the existing config registration.
> ```
>
> A few notes on findings I considered but did not include:
>
> - I did not flag the `RTE_LOG_LINE` calls in
> `indirect_action_flow_conf_create` for stale severity since they are
> correct uses.
> - The `for (i = n - 1; i >= 0; --i)` loop with `int i` and `uint32_t n` is
> safe for the realistic input range so I did not flag it as integer
> truncation.
> - The `select_ipv4:1`-style bit-fields are also present in existing public
> DPDK structs (`rte_flow_attr`), so I downgraded that to a Warning rather
> than an Error.
> - Skipped patches 1/6 (trivial stddef.h include), 5/6 (example) and 6/6
> (tests) since I had no findings beyond I3 and the test framework usage
> looked correct (`REGISTER_FAST_TEST` with `NOHUGE_OK, ASAN_OK`).
>
> No `Reviewed-by` is given since the series has multiple Errors and
> Warnings outstanding.
>

[-- Attachment #2: Type: text/html, Size: 32507 bytes --]

^ permalink raw reply

* [PATCH] power: fix duplicated typedef for setting uncore freq
From: Huisong Li @ 2026-05-07 11:27 UTC (permalink / raw)
  To: anatoly.burakov, sivaprasad.tummala, stephen
  Cc: dev, thomas, fengchengwen, yangxingui, zhanjie9, lihuisong

Remove a duplicated rte_power_set_uncore_freq_t definition.
And this ops is intended to set any available uncore frequency instead
of minimum and maximum one.

Fixes: ebe99d351a3f ("power: refactor uncore power management")
Cc: stable@dpdk.org

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 lib/power/power_uncore_ops.h | 24 +-----------------------
 1 file changed, 1 insertion(+), 23 deletions(-)

diff --git a/lib/power/power_uncore_ops.h b/lib/power/power_uncore_ops.h
index 783860ee5b..025a55cb97 100644
--- a/lib/power/power_uncore_ops.h
+++ b/lib/power/power_uncore_ops.h
@@ -76,8 +76,7 @@ typedef int (*rte_power_uncore_exit_t)(unsigned int pkg, unsigned int die);
 typedef uint32_t (*rte_power_get_uncore_freq_t)(unsigned int pkg, unsigned int die);
 
 /**
- * Set minimum and maximum uncore frequency for specified die on a package
- * to specified index value.
+ * Set uncore frequency for specified die on a package to specified index value.
  * It should be protected outside of this function for threadsafe.
  *
  * This function should NOT be called in the fast path.
@@ -98,27 +97,6 @@ typedef uint32_t (*rte_power_get_uncore_freq_t)(unsigned int pkg, unsigned int d
  */
 typedef int (*rte_power_set_uncore_freq_t)(unsigned int pkg, unsigned int die, uint32_t index);
 
-/**
- * Return the list length of available frequencies in the index array.
- *
- * This function should NOT be called in the fast path.
- *
- * @param pkg
- *  Package number.
- *  Each physical CPU in a system is referred to as a package.
- * @param die
- *  Die number.
- *  Each package can have several dies connected together via the uncore mesh.
- * @param index
- *  The index of available frequencies.
- *
- * @return
- *  - 1 on success with frequency changed.
- *  - 0 on success without frequency changed.
- *  - Negative on error.
- */
-typedef int (*rte_power_set_uncore_freq_t)(unsigned int pkg, unsigned int die, uint32_t index);
-
 /**
  * Return the list length of available frequencies in the index array.
  *
-- 
2.33.0


^ permalink raw reply related

* Re: [PATCH v1 03/15] net/ixgbe: fix non-shared data in IPsec session
From: Radu Nicolau @ 2026-05-07 10:50 UTC (permalink / raw)
  To: Anatoly Burakov, dev, Vladimir Medvedkin, Declan Doherty
In-Reply-To: <60ae27a2babb44f7a0301109728aaa2742254734.1777547413.git.anatoly.burakov@intel.com>


On 30-Apr-26 12:14 PM, Anatoly Burakov wrote:
> Currently, ixgbe IPsec session private data stores an ethdev pointer.
> That pointer is process local, but the session private data is shared,
> so a secondary process can read an invalid pointer value.
>
> Fix this by storing ethdev data pointer in session private data instead,
> and using it for session/device binding checks and dev_private lookups
> when adding SAs.
>
> Fixes: 9a0752f498d2 ("net/ixgbe: enable inline IPsec")
> Cc: radu.nicolau@intel.com
> Cc: stable@dpdk.org
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
Acked-by: Radu Nicolau <radu.nicolau@intel.com>

^ permalink raw reply

* [RFC 3/3] fib: drop redundant tbl8 reservation counter
From: Maxime Leroy @ 2026-05-07  9:50 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, Maxime Leroy
In-Reply-To: <cover.1778146229.git.maxime@leroys.fr>

In dir24_8, rsvd_tbl8s mirrors cur_tbl8s: both are bumped when
depth > 24 and no /24+ cover exists, and decremented under the
same condition. Removing rsvd_tbl8s leaves cur_tbl8s as the
single source of truth for the pre-check.

Move the DQ reclaim retry from tbl8_alloc() into the pre-check,
mirroring the layout used by the trie path. Behavior is unchanged
for non-DQ modes; in DQ mode the pre-check now performs the
reclaim that the in-allocator retry used to perform.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 lib/fib/dir24_8.c | 31 ++++++++++---------------------
 lib/fib/dir24_8.h |  1 -
 2 files changed, 10 insertions(+), 22 deletions(-)

diff --git a/lib/fib/dir24_8.c b/lib/fib/dir24_8.c
index 489d2ef427..80215b93b0 100644
--- a/lib/fib/dir24_8.c
+++ b/lib/fib/dir24_8.c
@@ -207,12 +207,6 @@ tbl8_alloc(struct dir24_8_tbl *dp, uint64_t nh)
 	uint8_t	*tbl8_ptr;
 
 	tbl8_idx = tbl8_get_idx(dp);
-
-	/* If there are no tbl8 groups try to reclaim one. */
-	if (unlikely(tbl8_idx == -ENOSPC && dp->dq &&
-			!rte_rcu_qsbr_dq_reclaim(dp->dq, 1, NULL, NULL, NULL)))
-		tbl8_idx = tbl8_get_idx(dp);
-
 	if (tbl8_idx < 0)
 		return tbl8_idx;
 	tbl8_ptr = (uint8_t *)dp->tbl8 +
@@ -504,9 +498,14 @@ dir24_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
 			tmp = rte_rib_get_nxt(rib, ip, 24, NULL,
 				RTE_RIB_GET_NXT_COVER);
 			if ((tmp == NULL) &&
-				(dp->rsvd_tbl8s >= dp->number_tbl8s))
-				return -ENOSPC;
-
+			    (dp->cur_tbl8s >= dp->number_tbl8s)) {
+				/* Reclaim deferred tbl8s before failing. */
+				if (dp->dq != NULL)
+					rte_rcu_qsbr_dq_reclaim(dp->dq, 1,
+						NULL, NULL, NULL);
+				if (dp->cur_tbl8s >= dp->number_tbl8s)
+					return -ENOSPC;
+			}
 		}
 		node = rte_rib_insert(rib, ip, depth);
 		if (node == NULL)
@@ -516,16 +515,13 @@ dir24_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
 		if (parent != NULL) {
 			rte_rib_get_nh(parent, &par_nh);
 			if (par_nh == next_hop)
-				goto successfully_added;
+				return 0;
 		}
 		ret = modify_fib(dp, rib, ip, depth, next_hop);
 		if (ret != 0) {
 			rte_rib_remove(rib, ip, depth);
 			return ret;
 		}
-successfully_added:
-		if ((depth > 24) && (tmp == NULL))
-			dp->rsvd_tbl8s++;
 		return 0;
 	case RTE_FIB_DEL:
 		if (node == NULL)
@@ -539,15 +535,8 @@ dir24_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
 				ret = modify_fib(dp, rib, ip, depth, par_nh);
 		} else
 			ret = modify_fib(dp, rib, ip, depth, dp->def_nh);
-		if (ret == 0) {
+		if (ret == 0)
 			rte_rib_remove(rib, ip, depth);
-			if (depth > 24) {
-				tmp = rte_rib_get_nxt(rib, ip, 24, NULL,
-					RTE_RIB_GET_NXT_COVER);
-				if (tmp == NULL)
-					dp->rsvd_tbl8s--;
-			}
-		}
 		return ret;
 	default:
 		break;
diff --git a/lib/fib/dir24_8.h b/lib/fib/dir24_8.h
index b343b5d686..502540173c 100644
--- a/lib/fib/dir24_8.h
+++ b/lib/fib/dir24_8.h
@@ -30,7 +30,6 @@
 
 struct dir24_8_tbl {
 	uint32_t	number_tbl8s;	/**< Total number of tbl8s */
-	uint32_t	rsvd_tbl8s;	/**< Number of reserved tbl8s */
 	uint32_t	cur_tbl8s;	/**< Current number of tbl8s */
 	enum rte_fib_dir24_8_nh_sz	nh_sz;	/**< Size of nexthop entry */
 	/* RCU config. */
-- 
2.43.0


^ permalink raw reply related

* [RFC 2/3] test/fib6: add reproducer for tbl8 reservation drift
From: Maxime Leroy @ 2026-05-07  9:50 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, Maxime Leroy
In-Reply-To: <cover.1778146229.git.maxime@leroys.fr>

Regression test for the rsvd_tbl8s drift bug fixed in
"fib6: fix tbl8 reservation drift in trie".

The test installs a /28 parent and three /48 children, deletes the
/28 parent while children are alive, then deletes each /48 child.
One asymmetric cycle wraps rsvd_tbl8s past zero (net -2 per cycle).

The probe is a /28 ADD (depth_diff=1) so UINT32_MAX-1 + 1 exceeds
the pool size without further uint32_t overflow.

Without the fix, the final ADD /28 fails with -ENOSPC.
With the fix, it succeeds.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 app/test/test_fib6.c | 83 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)

diff --git a/app/test/test_fib6.c b/app/test/test_fib6.c
index fffb590dbf..85a6c0abc3 100644
--- a/app/test/test_fib6.c
+++ b/app/test/test_fib6.c
@@ -25,6 +25,7 @@ static int32_t test_get_invalid(void);
 static int32_t test_lookup(void);
 static int32_t test_invalid_rcu(void);
 static int32_t test_fib_rcu_sync_rw(void);
+static int32_t test_drift(void);
 
 #define MAX_ROUTES	(1 << 16)
 /** Maximum number of tbl8 for 2-byte entries */
@@ -599,6 +600,87 @@ test_fib_rcu_sync_rw(void)
 	return status == 0 ? TEST_SUCCESS : TEST_FAILED;
 }
 
+/*
+ * Reproducer for the rsvd_tbl8s drift bug. depth_diff used to maintain
+ * rsvd_tbl8s is computed from the current RIB state, so it is not
+ * invariant between the ADD of a prefix and its later DEL when a
+ * covering parent prefix is removed in between.
+ *
+ * Layout: one /28 parent (fcde::/28) and three /48 siblings under it
+ * (fcde:0:6000::/48, fcde:1:6000::/48, fcde:2:6000::/48). The second
+ * hextet's high 12 bits are zero, so the three /48 IPs all fall inside
+ * the /28.
+ *
+ * One asymmetric sequence is enough to wrap the counter:
+ *   ADD /28                                  rsvd_tbl8s += 1
+ *   ADD /48 child_0,1,2 (with /28 parent)    rsvd_tbl8s += 2 each (+6)
+ *   DEL /28 (sibling /48 found)              rsvd_tbl8s -= 0
+ *   DEL /48 child_0,1,2 (no parent left)     rsvd_tbl8s -= 3 each (-9)
+ *
+ * Net: -2. Starting from 0, rsvd_tbl8s wraps to UINT32_MAX-1. The
+ * next ADD of a prefix longer than /24 then unconditionally fails the
+ * pre-check (rsvd_tbl8s + depth_diff > number_tbl8s), even though the
+ * pool is empty.
+ */
+static int32_t
+test_drift(void)
+{
+	struct rte_fib6_conf config = { 0 };
+	struct rte_fib6 *fib;
+	struct rte_ipv6_addr parent =
+		RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr child[3] = {
+		RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 1, 0x6000, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 2, 0x6000, 0, 0, 0, 0, 0),
+	};
+	unsigned int c;
+	int ret;
+
+	config.max_routes = 1024;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 256;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	ret = rte_fib6_add(fib, &parent, 28, 0xa);
+	RTE_TEST_ASSERT(ret == 0, "ADD /28 failed (ret=%d)\n", ret);
+
+	for (c = 0; c < 3; c++) {
+		ret = rte_fib6_add(fib, &child[c], 48, 0xb + c);
+		RTE_TEST_ASSERT(ret == 0,
+			"ADD /48 child %u failed (ret=%d)\n", c, ret);
+	}
+
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 failed (ret=%d)\n", ret);
+
+	for (c = 0; c < 3; c++) {
+		ret = rte_fib6_delete(fib, &child[c], 48);
+		RTE_TEST_ASSERT(ret == 0,
+			"DEL /48 child %u failed (ret=%d)\n", c, ret);
+	}
+
+	/*
+	 * Pre-fix: -ENOSPC because rsvd_tbl8s wrapped to UINT32_MAX-1.
+	 * Post-fix: succeeds; the pre-check uses tbl8_pool_pos which
+	 * accurately reflects the (empty) pool.
+	 */
+	ret = rte_fib6_add(fib, &parent, 28, 0xe);
+	RTE_TEST_ASSERT(ret == 0,
+		"Fresh ADD /28 spuriously failed (ret=%d)\n", ret);
+
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "Final DEL /28 failed (ret=%d)\n", ret);
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite fib6_fast_tests = {
 	.suite_name = "fib6 autotest",
 	.setup = NULL,
@@ -611,6 +693,7 @@ static struct unit_test_suite fib6_fast_tests = {
 	TEST_CASE(test_lookup),
 	TEST_CASE(test_invalid_rcu),
 	TEST_CASE(test_fib_rcu_sync_rw),
+	TEST_CASE(test_drift),
 	TEST_CASES_END()
 	}
 };
-- 
2.43.0


^ permalink raw reply related

* [RFC 1/3] fib6: fix tbl8 reservation drift in trie
From: Maxime Leroy @ 2026-05-07  9:50 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, Maxime Leroy, stable
In-Reply-To: <cover.1778146229.git.maxime@leroys.fr>

trie_modify() updates rsvd_tbl8s by depth_diff computed from the
current RIB state. The RIB is not invariant between the ADD of a
prefix and its later DEL (a covering parent may be added or removed
in between), so depth_diff at DEL time may not match depth_diff at
ADD time. Repeated over asymmetric pairs, rsvd_tbl8s drifts and
eventually wraps to UINT32_MAX, after which the pre-check rejects
all long-prefix ADDs with -ENOSPC even when the pool is empty.

Replace rsvd_tbl8s with tbl8_pool_pos, which tbl8_get()/tbl8_put()
maintain exactly. To preserve the QSBR_MODE_DQ safety net previously
provided by the retry-with-reclaim inside tbl8_alloc(), the pre-check
now calls rte_rcu_qsbr_dq_reclaim(depth_diff) before returning
-ENOSPC.

The single-tbl8 retry inside tbl8_alloc() is removed: depth_diff is
the algorithmic upper bound for new tbl8 allocations, and the
pre-check now performs the DQ reclaim before allocation, so the
retry inside the allocator is no longer needed.

Fixes: c3e12e0f0354 ("fib: add dataplane algorithm for IPv6")
Cc: stable@dpdk.org
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 lib/fib/trie.c | 23 ++++++++++-------------
 lib/fib/trie.h |  3 +--
 2 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/lib/fib/trie.c b/lib/fib/trie.c
index fa5d9ec6b0..52f25d499c 100644
--- a/lib/fib/trie.c
+++ b/lib/fib/trie.c
@@ -161,12 +161,6 @@ tbl8_alloc(struct rte_trie_tbl *dp, uint64_t nh)
 	uint8_t		*tbl8_ptr;
 
 	tbl8_idx = tbl8_get(dp);
-
-	/* If there are no tbl8 groups try to reclaim one. */
-	if (unlikely(tbl8_idx == -ENOSPC && dp->dq &&
-			!rte_rcu_qsbr_dq_reclaim(dp->dq, 1, NULL, NULL, NULL)))
-		tbl8_idx = tbl8_get(dp);
-
 	if (tbl8_idx < 0)
 		return tbl8_idx;
 	tbl8_ptr = get_tbl_p_by_idx(dp->tbl8,
@@ -603,8 +597,15 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 			return 0;
 		}
 
-		if ((depth > 24) && (dp->rsvd_tbl8s + depth_diff > dp->number_tbl8s))
-			return -ENOSPC;
+		if ((depth > 24) &&
+		    (dp->tbl8_pool_pos + depth_diff > dp->number_tbl8s)) {
+			/* Reclaim deferred tbl8s before failing. */
+			if (dp->dq != NULL)
+				rte_rcu_qsbr_dq_reclaim(dp->dq, depth_diff,
+					NULL, NULL, NULL);
+			if (dp->tbl8_pool_pos + depth_diff > dp->number_tbl8s)
+				return -ENOSPC;
+		}
 
 		node = rte_rib6_insert(rib, &ip_masked, depth);
 		if (node == NULL)
@@ -614,15 +615,13 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 		if (parent != NULL) {
 			rte_rib6_get_nh(parent, &par_nh);
 			if (par_nh == next_hop)
-				goto successfully_added;
+				return 0;
 		}
 		ret = modify_dp(dp, rib, &ip_masked, depth, next_hop);
 		if (ret != 0) {
 			rte_rib6_remove(rib, &ip_masked, depth);
 			return ret;
 		}
-successfully_added:
-		dp->rsvd_tbl8s += depth_diff;
 		return 0;
 	case RTE_FIB6_DEL:
 		if (node == NULL)
@@ -641,8 +640,6 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 		if (ret != 0)
 			return ret;
 		rte_rib6_remove(rib, ip, depth);
-
-		dp->rsvd_tbl8s -= depth_diff;
 		return 0;
 	default:
 		break;
diff --git a/lib/fib/trie.h b/lib/fib/trie.h
index c34cc2c057..b42a28f84e 100644
--- a/lib/fib/trie.h
+++ b/lib/fib/trie.h
@@ -31,8 +31,7 @@
 
 struct rte_trie_tbl {
 	uint32_t	number_tbl8s;	/**< Total number of tbl8s */
-	uint32_t	rsvd_tbl8s;	/**< Number of reserved tbl8s */
-	uint32_t	cur_tbl8s;	/**< Current cumber of tbl8s */
+	uint32_t	cur_tbl8s;	/**< Current number of tbl8s */
 	uint64_t	def_nh;		/**< Default next hop */
 	enum rte_fib_trie_nh_sz	nh_sz;	/**< Size of nexthop entry */
 	uint64_t	*tbl8;		/**< tbl8 table. */
-- 
2.43.0


^ permalink raw reply related

* [RFC 0/3] fib: tbl8 reservation drift reproducer and proposed fix
From: Maxime Leroy @ 2026-05-07  9:50 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, Maxime Leroy

Asymmetric ADD/DEL sequences in FIB6 trie (a covering parent
removed between the ADD and DEL of a longer prefix) eventually
make ADD fail with -ENOSPC even when the tbl8 pool is empty.

Patch 2/3 is a small reproducer.

Root cause: rsvd_tbl8s is updated by depth_diff recomputed from
the current RIB, so increments at ADD and decrements at DEL do
not cancel when the RIB state changes in between. The counter
drifts and wraps to UINT32_MAX.

The simplest fix I could find (1/3, 3/3) is to drop rsvd_tbl8s
and use the pool counters already maintained by alloc/free:
tbl8_pool_pos in trie, cur_tbl8s in dir24_8. The DQ reclaim
inside tbl8_alloc() is moved into the pre-check.

I am not sure I understood the original intent of keeping
rsvd_tbl8s separate from the pool counters. In dir24_8 the two
mirror each other 1:1 and rsvd_tbl8s looks redundant; in trie,
depth_diff gives it a worst-case-reservation flavor but the
recomputation from the RIB is exactly what makes it drift. If
there was a deliberate reason, please point it out.

Patch 3/3 is a no-op cleanup that aligns dir24_8 with the trie
pattern.

Maxime Leroy (3):
  fib6: fix tbl8 reservation drift in trie
  test/fib6: add reproducer for tbl8 reservation drift
  fib: drop redundant tbl8 reservation counter

 app/test/test_fib6.c | 83 ++++++++++++++++++++++++++++++++++++++++++++
 lib/fib/dir24_8.c    | 31 ++++++-----------
 lib/fib/dir24_8.h    |  1 -
 lib/fib/trie.c       | 23 ++++++------
 lib/fib/trie.h       |  3 +-
 5 files changed, 104 insertions(+), 37 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH v7 4/4] examples/ptpclient: use shared PTP library definitions
From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507134529.2573300-1-rajesh3.kumar@intel.com>

Update ptpclient to use the lib/net/rte_ptp.h library instead of
duplicated local PTP header structures and message type constants.

Changes:
  - Add #include <rte_ptp.h>
  - Replace local struct ptp_header with struct rte_ptp_hdr
  - Replace local struct tstamp with struct rte_ptp_timestamp
  - Replace local struct clock_id with uint8_t[8] arrays
  - Use RTE_PTP_MSGTYPE_* constants instead of local message types
  - Use rte_ptp_msg_type(), rte_ptp_seq_id() inline helpers
  - Remove local PTP_PROTOCOL definition (use RTE_ETHER_TYPE_1588)

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 examples/ptpclient/meson.build |   1 +
 examples/ptpclient/ptpclient.c | 204 +++++++++++++--------------------
 2 files changed, 81 insertions(+), 124 deletions(-)

diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build
index 2e9b7625fc..9087c987d5 100644
--- a/examples/ptpclient/meson.build
+++ b/examples/ptpclient/meson.build
@@ -7,6 +7,7 @@
 # DPDK instance, use 'make'
 
 allow_experimental_apis = true
+deps += ['net']
 sources = files(
         'ptpclient.c',
 )
diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c
index 174ca5dd70..ce3798d1d3 100644
--- a/examples/ptpclient/ptpclient.c
+++ b/examples/ptpclient/ptpclient.c
@@ -17,6 +17,7 @@
 #include <rte_lcore.h>
 #include <rte_mbuf.h>
 #include <rte_ip.h>
+#include <rte_ptp.h>
 #include <limits.h>
 #include <sys/time.h>
 #include <getopt.h>
@@ -30,21 +31,8 @@ static volatile bool force_quit;
 #define NUM_MBUFS            8191
 #define MBUF_CACHE_SIZE       250
 
-/* Values for the PTP messageType field. */
-#define SYNC                  0x0
-#define DELAY_REQ             0x1
-#define PDELAY_REQ            0x2
-#define PDELAY_RESP           0x3
-#define FOLLOW_UP             0x8
-#define DELAY_RESP            0x9
-#define PDELAY_RESP_FOLLOW_UP 0xA
-#define ANNOUNCE              0xB
-#define SIGNALING             0xC
-#define MANAGEMENT            0xD
-
 #define NSEC_PER_SEC        1000000000L
 #define KERNEL_TIME_ADJUST_LIMIT  20000
-#define PTP_PROTOCOL             0x88F7
 
 struct rte_mempool *mbuf_pool;
 uint32_t ptp_enabled_port_mask;
@@ -55,69 +43,39 @@ static const struct rte_ether_addr ether_multicast = {
 	.addr_bytes = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0}
 };
 
-/* Structs used for PTP handling. */
-struct __rte_packed_begin tstamp {
-	uint16_t   sec_msb;
-	uint32_t   sec_lsb;
-	uint32_t   ns;
-} __rte_packed_end;
-
-struct clock_id {
-	uint8_t id[8];
-};
-
-struct __rte_packed_begin port_id {
-	struct clock_id        clock_id;
-	uint16_t               port_number;
-} __rte_packed_end;
-
-struct __rte_packed_begin ptp_header {
-	uint8_t              msg_type;
-	uint8_t              ver;
-	uint16_t             message_length;
-	uint8_t              domain_number;
-	uint8_t              reserved1;
-	uint8_t              flag_field[2];
-	int64_t              correction;
-	uint32_t             reserved2;
-	struct port_id       source_port_id;
-	uint16_t             seq_id;
-	uint8_t              control;
-	int8_t               log_message_interval;
-} __rte_packed_end;
-
+/* Structs used for PTP handling - using library definitions from rte_ptp.h */
 struct __rte_packed_begin sync_msg {
-	struct ptp_header   hdr;
-	struct tstamp       origin_tstamp;
+	struct rte_ptp_hdr       hdr;
+	struct rte_ptp_timestamp origin_tstamp;
 } __rte_packed_end;
 
 struct __rte_packed_begin follow_up_msg {
-	struct ptp_header   hdr;
-	struct tstamp       precise_origin_tstamp;
+	struct rte_ptp_hdr       hdr;
+	struct rte_ptp_timestamp precise_origin_tstamp;
 	uint8_t             suffix[];
 } __rte_packed_end;
 
 struct __rte_packed_begin delay_req_msg {
-	struct ptp_header   hdr;
-	struct tstamp       origin_tstamp;
+	struct rte_ptp_hdr       hdr;
+	struct rte_ptp_timestamp origin_tstamp;
 } __rte_packed_end;
 
 struct __rte_packed_begin delay_resp_msg {
-	struct ptp_header    hdr;
-	struct tstamp        rx_tstamp;
-	struct port_id       req_port_id;
-	uint8_t              suffix[];
+	struct rte_ptp_hdr       hdr;
+	struct rte_ptp_timestamp rx_tstamp;
+	struct rte_ptp_port_id   req_port_id;
+	uint8_t             suffix[];
 } __rte_packed_end;
 
-struct ptp_message {
+struct __rte_packed_begin ptp_message {
 	union __rte_packed_begin {
-		struct ptp_header          header;
-		struct sync_msg            sync;
-		struct delay_req_msg       delay_req;
-		struct follow_up_msg       follow_up;
-		struct delay_resp_msg      delay_resp;
+		struct rte_ptp_hdr         header;
+		struct sync_msg      sync;
+		struct delay_req_msg delay_req;
+		struct follow_up_msg follow_up;
+		struct delay_resp_msg delay_resp;
 	} __rte_packed_end;
-};
+} __rte_packed_end;
 
 struct ptpv2_time_receiver_ordinary {
 	struct rte_mbuf *m;
@@ -125,8 +83,8 @@ struct ptpv2_time_receiver_ordinary {
 	struct timespec tstamp2;
 	struct timespec tstamp3;
 	struct timespec tstamp4;
-	struct clock_id client_clock_id;
-	struct clock_id transmitter_clock_id;
+	uint8_t client_clock_id[8];
+	uint8_t transmitter_clock_id[8];
 	struct timeval new_adj;
 	int64_t delta;
 	uint16_t portid;
@@ -272,14 +230,14 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp_data)
 	struct timespec net_time, sys_time;
 
 	printf("time transmitter clock id: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
-		ptp_data->transmitter_clock_id.id[0],
-		ptp_data->transmitter_clock_id.id[1],
-		ptp_data->transmitter_clock_id.id[2],
-		ptp_data->transmitter_clock_id.id[3],
-		ptp_data->transmitter_clock_id.id[4],
-		ptp_data->transmitter_clock_id.id[5],
-		ptp_data->transmitter_clock_id.id[6],
-		ptp_data->transmitter_clock_id.id[7]);
+		ptp_data->transmitter_clock_id[0],
+		ptp_data->transmitter_clock_id[1],
+		ptp_data->transmitter_clock_id[2],
+		ptp_data->transmitter_clock_id[3],
+		ptp_data->transmitter_clock_id[4],
+		ptp_data->transmitter_clock_id[5],
+		ptp_data->transmitter_clock_id[6],
+		ptp_data->transmitter_clock_id[7]);
 
 	printf("\nT2 - time receiver clock.  %lds %ldns",
 			(ptp_data->tstamp2.tv_sec),
@@ -356,20 +314,21 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp_data)
 static void
 parse_sync(struct ptpv2_time_receiver_ordinary *ptp_data, uint16_t rx_tstamp_idx)
 {
-	struct ptp_header *ptp_hdr;
+	struct rte_ptp_hdr *ptp_hdr;
 
-	ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct ptp_header *,
+	ptp_hdr = rte_pktmbuf_mtod_offset(ptp_data->m, struct rte_ptp_hdr *,
 					  sizeof(struct rte_ether_hdr));
-	ptp_data->seqID_SYNC = rte_be_to_cpu_16(ptp_hdr->seq_id);
+	ptp_data->seqID_SYNC = rte_ptp_seq_id(ptp_hdr);
 
 	if (ptp_data->ptpset == 0) {
-		ptp_data->transmitter_clock_id = ptp_hdr->source_port_id.clock_id;
+		memcpy(ptp_data->transmitter_clock_id,
+		       ptp_hdr->source_port_id.clock_id, 8);
 		ptp_data->ptpset = 1;
 	}
 
-	if (memcmp(&ptp_data->transmitter_clock_id,
-			&ptp_hdr->source_port_id.clock_id,
-			sizeof(struct clock_id)) == 0) {
+	if (memcmp(ptp_data->transmitter_clock_id,
+			ptp_hdr->source_port_id.clock_id,
+			8) == 0) {
 
 		if (ptp_data->ptpset == 1)
 			rte_eth_timesync_read_rx_timestamp(ptp_data->portid,
@@ -386,12 +345,11 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data)
 {
 	struct rte_ether_hdr *eth_hdr;
 	struct rte_ether_addr eth_addr;
-	struct ptp_header *ptp_hdr;
-	struct clock_id *client_clkid;
+	struct rte_ptp_hdr *ptp_hdr;
 	struct ptp_message *ptp_msg;
 	struct delay_req_msg *req_msg;
 	struct rte_mbuf *created_pkt;
-	struct tstamp *origin_tstamp;
+	struct rte_ptp_timestamp *origin_tstamp;
 	struct rte_ether_addr eth_multicast = ether_multicast;
 	size_t pkt_size;
 	int wait_us;
@@ -399,22 +357,22 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data)
 	int ret;
 
 	eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
-	ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *,
+	ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *,
 					  sizeof(struct rte_ether_hdr));
-	if (memcmp(&ptp_data->transmitter_clock_id,
-			&ptp_hdr->source_port_id.clock_id,
-			sizeof(struct clock_id)) != 0)
+	if (memcmp(ptp_data->transmitter_clock_id,
+			ptp_hdr->source_port_id.clock_id,
+			8) != 0)
 		return;
 
-	ptp_data->seqID_FOLLOWUP = rte_be_to_cpu_16(ptp_hdr->seq_id);
+	ptp_data->seqID_FOLLOWUP = rte_ptp_seq_id(ptp_hdr);
 	ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *,
 					  sizeof(struct rte_ether_hdr));
 
 	origin_tstamp = &ptp_msg->follow_up.precise_origin_tstamp;
-	ptp_data->tstamp1.tv_nsec = ntohl(origin_tstamp->ns);
+	ptp_data->tstamp1.tv_nsec = rte_be_to_cpu_32(origin_tstamp->nanoseconds);
 	ptp_data->tstamp1.tv_sec =
-		((uint64_t)ntohl(origin_tstamp->sec_lsb)) |
-		(((uint64_t)ntohs(origin_tstamp->sec_msb)) << 32);
+		((uint64_t)rte_be_to_cpu_32(origin_tstamp->seconds_lo)) |
+		(((uint64_t)rte_be_to_cpu_16(origin_tstamp->seconds_hi)) << 32);
 
 	if (ptp_data->seqID_FOLLOWUP == ptp_data->seqID_SYNC) {
 		ret = rte_eth_macaddr_get(ptp_data->portid, &eth_addr);
@@ -441,34 +399,30 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data)
 		/* Set multicast address 01-1B-19-00-00-00. */
 		rte_ether_addr_copy(&eth_multicast, &eth_hdr->dst_addr);
 
-		eth_hdr->ether_type = htons(PTP_PROTOCOL);
+		eth_hdr->ether_type = htons(RTE_ETHER_TYPE_1588);
 		req_msg = rte_pktmbuf_mtod_offset(created_pkt,
 			struct delay_req_msg *, sizeof(struct
 			rte_ether_hdr));
 
-		req_msg->hdr.seq_id = htons(ptp_data->seqID_SYNC);
-		req_msg->hdr.msg_type = DELAY_REQ;
-		req_msg->hdr.ver = 2;
-		req_msg->hdr.control = 1;
-		req_msg->hdr.log_message_interval = 127;
-		req_msg->hdr.message_length =
+		req_msg->hdr.sequence_id = htons(ptp_data->seqID_SYNC);
+		req_msg->hdr.msg_type = RTE_PTP_MSGTYPE_DELAY_REQ;
+		req_msg->hdr.version = 2;
+		req_msg->hdr.msg_length =
 			htons(sizeof(struct delay_req_msg));
 		req_msg->hdr.domain_number = ptp_hdr->domain_number;
 
 		/* Set up clock id. */
-		client_clkid =
-			&req_msg->hdr.source_port_id.clock_id;
-
-		client_clkid->id[0] = eth_hdr->src_addr.addr_bytes[0];
-		client_clkid->id[1] = eth_hdr->src_addr.addr_bytes[1];
-		client_clkid->id[2] = eth_hdr->src_addr.addr_bytes[2];
-		client_clkid->id[3] = 0xFF;
-		client_clkid->id[4] = 0xFE;
-		client_clkid->id[5] = eth_hdr->src_addr.addr_bytes[3];
-		client_clkid->id[6] = eth_hdr->src_addr.addr_bytes[4];
-		client_clkid->id[7] = eth_hdr->src_addr.addr_bytes[5];
-
-		ptp_data->client_clock_id = *client_clkid;
+		ptp_data->client_clock_id[0] = eth_hdr->src_addr.addr_bytes[0];
+		ptp_data->client_clock_id[1] = eth_hdr->src_addr.addr_bytes[1];
+		ptp_data->client_clock_id[2] = eth_hdr->src_addr.addr_bytes[2];
+		ptp_data->client_clock_id[3] = 0xFF;
+		ptp_data->client_clock_id[4] = 0xFE;
+		ptp_data->client_clock_id[5] = eth_hdr->src_addr.addr_bytes[3];
+		ptp_data->client_clock_id[6] = eth_hdr->src_addr.addr_bytes[4];
+		ptp_data->client_clock_id[7] = eth_hdr->src_addr.addr_bytes[5];
+
+		memcpy(req_msg->hdr.source_port_id.clock_id,
+		       ptp_data->client_clock_id, 8);
 
 		/* Enable flag for hardware timestamping. */
 		created_pkt->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST;
@@ -534,21 +488,21 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data)
 {
 	struct rte_mbuf *m = ptp_data->m;
 	struct ptp_message *ptp_msg;
-	struct tstamp *rx_tstamp;
+	struct rte_ptp_timestamp *rx_tstamp;
 	uint16_t seq_id;
 
 	ptp_msg = rte_pktmbuf_mtod_offset(m, struct ptp_message *,
 					  sizeof(struct rte_ether_hdr));
-	seq_id = rte_be_to_cpu_16(ptp_msg->delay_resp.hdr.seq_id);
-	if (memcmp(&ptp_data->client_clock_id,
-		   &ptp_msg->delay_resp.req_port_id.clock_id,
-		   sizeof(struct clock_id)) == 0) {
+	seq_id = rte_ptp_seq_id(&ptp_msg->delay_resp.hdr);
+	if (memcmp(ptp_data->client_clock_id,
+		   ptp_msg->delay_resp.req_port_id.clock_id,
+		   8) == 0) {
 		if (seq_id == ptp_data->seqID_FOLLOWUP) {
 			rx_tstamp = &ptp_msg->delay_resp.rx_tstamp;
-			ptp_data->tstamp4.tv_nsec = ntohl(rx_tstamp->ns);
+			ptp_data->tstamp4.tv_nsec = rte_be_to_cpu_32(rx_tstamp->nanoseconds);
 			ptp_data->tstamp4.tv_sec =
-				((uint64_t)ntohl(rx_tstamp->sec_lsb)) |
-				(((uint64_t)ntohs(rx_tstamp->sec_msb)) << 32);
+				((uint64_t)rte_be_to_cpu_32(rx_tstamp->seconds_lo)) |
+				(((uint64_t)rte_be_to_cpu_16(rx_tstamp->seconds_hi)) << 32);
 
 			/* Evaluate the delta for adjustment. */
 			ptp_data->delta = delta_eval(ptp_data);
@@ -575,27 +529,29 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp_data)
 /* Parse ptp frames. 8< */
 static void
 parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) {
-	struct ptp_header *ptp_hdr;
+	struct rte_ptp_hdr *ptp_hdr;
 	struct rte_ether_hdr *eth_hdr;
 	uint16_t eth_type;
+	uint8_t msg_type;
 
 	eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
 	eth_type = rte_be_to_cpu_16(eth_hdr->ether_type);
 
-	if (eth_type == PTP_PROTOCOL) {
+	if (eth_type == RTE_ETHER_TYPE_1588) {
 		ptp_data.m = m;
 		ptp_data.portid = portid;
-		ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *,
+		ptp_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ptp_hdr *,
 						  sizeof(struct rte_ether_hdr));
 
-		switch (ptp_hdr->msg_type) {
-		case SYNC:
+		msg_type = rte_ptp_msg_type(ptp_hdr);
+		switch (msg_type) {
+		case RTE_PTP_MSGTYPE_SYNC:
 			parse_sync(&ptp_data, m->timesync);
 			break;
-		case FOLLOW_UP:
+		case RTE_PTP_MSGTYPE_FOLLOW_UP:
 			parse_fup(&ptp_data);
 			break;
-		case DELAY_RESP:
+		case RTE_PTP_MSGTYPE_DELAY_RESP:
 			parse_drsp(&ptp_data);
 			print_clock_info(&ptp_data);
 			break;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 3/4] doc: update release notes for PTP protocol library
From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507134529.2573300-1-rajesh3.kumar@intel.com>

Update release notes with IEEE 1588 PTP additions:
  - PTP protocol definitions in lib/net/rte_ptp.h (header-only library
    with inline helpers and wire-format structures)
  - PTP software relay example application (ptp_tap_relay_sw)

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 doc/guides/rel_notes/release_26_07.rst | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index f012d47a4b..c64e6e141b 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -63,6 +63,18 @@ New Features
     ``rte_eal_init`` and the application is responsible for probing each device,
   * ``--auto-probing`` enables the initial bus probing, which is the current default behavior.
 
+* **Added PTP protocol definitions (rte_ptp.h).**
+
+  Added IEEE 1588 Precision Time Protocol header structures, constants,
+  and inline helpers to ``lib/net/rte_ptp.h``.  Provides wire-format
+  structures with endian-annotated types and correctionField manipulation
+  for Transparent Clock implementations.
+
+* **Added PTP software relay example application.**
+
+  Added a new example application ``ptp_tap_relay_sw`` demonstrating a
+  software PTP Transparent Clock relay between a DPDK port and a kernel
+  TAP interface.
 
 Removed Items
 -------------
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay
From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507134529.2573300-1-rajesh3.kumar@intel.com>

Add a new example application demonstrating a software PTP Transparent
Clock relay between a DPDK-bound physical NIC and a Linux kernel TAP
virtual interface.

The relay uses software timestamps (CLOCK_MONOTONIC) to measure residence
time and accumulates it into the PTP correctionField per IEEE 1588-2019
§10.2, enabling synchronized time distribution via standard linuxptp
(ptp4l) on both sides.

Features:
  - Handles L2, VLAN/QinQ, and UDP/IPv4/IPv6 PTP encapsulations
  - Supports PTP v2 event messages (Sync, Delay_Req, PDelay_Req,
    PDelay_Resp)
  - Two-pass burst processing: classify before TX, timestamp before
    relay
  - Unmodified Linux kernel and stock DPDK (no kernel patches required)
  - Bidirectional relay: PHY ↔ TAP

Includes:
  - ptp_tap_relay_sw.c: Main relay logic with burst processing
  - ptp_parse.h: Local DPI parser for PTP classification
    (not a library API)
  - Sample app guide with topology, command-line options, and
    example output

Uses lib/net/rte_ptp.h inline helpers for correctionField manipulation
and header parsing.

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 doc/guides/sample_app_ug/index.rst            |   1 +
 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++
 examples/ptp_tap_relay_sw/Makefile            |  41 ++
 examples/ptp_tap_relay_sw/meson.build         |  13 +
 examples/ptp_tap_relay_sw/ptp_parse.h         | 211 +++++++++
 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c  | 432 ++++++++++++++++++
 6 files changed, 910 insertions(+)
 create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
 create mode 100644 examples/ptp_tap_relay_sw/Makefile
 create mode 100644 examples/ptp_tap_relay_sw/meson.build
 create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h
 create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c

diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index e895f692f9..f12623bb66 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -51,6 +51,7 @@ Sample Applications User Guides
     dist_app
     vm_power_management
     ptpclient
+    ptp_tap_relay_sw
     fips_validation
     ipsec_secgw
     bbdev_app
diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
new file mode 100644
index 0000000000..15727383c1
--- /dev/null
+++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
@@ -0,0 +1,212 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Intel Corporation.
+
+PTP Software Relay Sample Application
+======================================
+
+The PTP Software Relay sample application demonstrates how to build a
+minimal PTP Transparent Clock relay between a DPDK-bound physical NIC
+and a kernel TAP interface using **software timestamps only**.  It uses
+the PTP definitions from ``rte_ptp.h`` (in ``lib/net/``) together with a
+local packet parser.
+
+The application works with an unmodified Linux kernel and stock DPDK.
+
+For background on PTP see:
+`Precision Time Protocol
+<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_.
+
+
+Limitations
+-----------
+
+* Tested with L2 PTP (EtherType 0x88F7) on the wire.
+   The local parser also classifies VLAN/QinQ and UDP/IPv4/IPv6.
+* Only PTP v2 messages are processed.
+* Software timestamps have microsecond-class jitter; sub-microsecond
+  precision depends on system load and NIC-to-TAP forwarding latency.
+* The PTP time transmitter must be reachable on the physical NIC's L2 network.
+* Only one physical port and one TAP port are supported.
+
+
+How the Application Works
+-------------------------
+
+Topology
+~~~~~~~~
+
+::
+
+    PTP Time Transmitter  Physical NIC             TAP (kernel)
+    (ptp4l -H)  ──L2──  (DPDK vfio-pci)  ──────  dtap0
+                              │                      │
+                        ptp_tap_relay_sw            ptp4l -S
+                     (correctionField +=        (SW timestamps,
+                      residence time)           adjusts CLOCK_REALTIME)
+
+The relay sits between a DPDK-owned physical NIC and a kernel TAP
+virtual interface.  ``ptp4l`` runs on the TAP interface in software
+timestamp mode (``-S``) as a PTP time receiver.
+
+Packet Flow
+~~~~~~~~~~~
+
+1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX.
+2. A software RX timestamp is recorded using
+   ``clock_gettime(CLOCK_MONOTONIC)``.
+3. Each packet is parsed to locate the PTP header.
+4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp),
+   a TX software timestamp is taken just before transmission.
+5. The residence time (``tx_ts − rx_ts``) is added to the PTP
+   ``correctionField`` via ``rte_ptp_add_correction()`` — standard
+   IEEE 1588-2019 Transparent Clock behaviour (§10.2).
+6. Packets are forwarded bidirectionally:
+
+   * PHY → TAP  (network → ptp4l)
+   * TAP → PHY  (ptp4l → network)
+
+A two-pass design is used: first all packets are classified and PTP
+header pointers saved, then a single TX timestamp is taken immediately
+before applying corrections and calling ``rte_eth_tx_burst()``.
+This minimises the gap between the measured timestamp and the actual
+wire egress.
+
+
+Compiling the Application
+-------------------------
+
+To compile the sample application see :doc:`compiling`.
+
+The application is located in the ``ptp_tap_relay_sw`` sub-directory.
+
+.. note::
+
+   The application uses ``rte_ptp.h`` from ``lib/net/`` (built by default)
+   and a local ``ptp_parse.h`` header for packet classification.
+
+
+Running the Application
+-----------------------
+
+Prerequisites
+~~~~~~~~~~~~~
+
+* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``).
+* ``linuxptp`` (``ptp4l``) installed on the system.
+* A PTP time transmitter reachable on the same L2 network.
+
+Start the relay
+~~~~~~~~~~~~~~~~
+
+.. code-block:: console
+
+   ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \
+       -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \
+       -p 0 -t 1 -T 10
+
+Command-line Options
+~~~~~~~~~~~~~~~~~~~~
+
+* ``-p PORT`` — Physical NIC port ID (default: 0).
+* ``-t PORT`` — TAP port ID (default: 1).
+* ``-T SECS`` — Statistics print interval in seconds (default: 10).
+
+Start PTP time transmitter
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On a separate terminal or remote host, start ``ptp4l`` as time
+transmitter with hardware timestamps on the physical NIC:
+
+.. code-block:: console
+
+   ptp4l -i <iface> -m -2 -H --serverOnly=1 \
+       --logSyncInterval=-4 --logMinDelayReqInterval=-4
+
+Start PTP time receiver
+~~~~~~~~~~~~~~~~~~~~~~~
+
+On the TAP interface, start ``ptp4l`` in software timestamp mode:
+
+.. code-block:: console
+
+   ptp4l -i dtap0 -m -2 -s -S \
+       --delay_filter=moving_median --delay_filter_length=10
+
+The time receiver will enter UNCALIBRATED state for approximately 60
+seconds while the PI servo estimates the frequency offset, then step
+the clock and enter time-receiver (synchronized) state.
+Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded
+system with a hardware-timestamped time transmitter.
+
+Example Output
+~~~~~~~~~~~~~~
+
+Relay statistics printed every ``-T`` seconds:
+
+::
+
+   [PTP-SW] === Statistics ===
+   [PTP-SW]   PHY RX total:   5646
+   [PTP-SW]   PHY RX PTP:     5598
+   [PTP-SW]   TAP TX:         5646
+   [PTP-SW]   TAP RX total:   1800
+   [PTP-SW]   TAP RX PTP:     1788
+   [PTP-SW]   PHY TX:         1800
+   [PTP-SW]   Corrections:    3635
+
+Time receiver ``ptp4l`` output after convergence:
+
+::
+
+   ptp4l[451534.520]: rms  630 max 1166 freq -44365 +/- 100 delay 37668 +/-  71
+   ptp4l[451539.525]: rms  602 max 1177 freq -44339 +/- 119 delay 37517 +/-  43
+   ptp4l[451544.530]: rms  535 max 1194 freq -44345 +/- 103 delay 37410 +/-  81
+
+
+Code Explanation
+----------------
+
+The following sections explain the main components of the application.
+
+Relay Burst Function
+~~~~~~~~~~~~~~~~~~~~
+
+The core relay logic is in ``relay_burst()``, which handles one direction
+(PHY→TAP or TAP→PHY) per call:
+
+**Pass 1 — Classify:**
+
+For each received packet, ``ptp_hdr_find()`` locates the PTP header
+(if present).  For event messages, the header pointer is saved for the
+second pass.
+
+**Pass 2 — Timestamp and correct:**
+
+A single software TX timestamp is taken via
+``clock_gettime(CLOCK_MONOTONIC)``.  The residence time
+(``tx_ts − rx_ts``) is added to each saved PTP header's
+``correctionField`` using ``rte_ptp_add_correction()``.
+The burst is then transmitted with ``rte_eth_tx_burst()``.
+
+Main Loop
+~~~~~~~~~
+
+The ``relay_loop()`` function polls both directions in a tight loop:
+
+.. code-block:: c
+
+   while (!force_quit) {
+       relay_burst(phy_port, tap_port, ...);   /* PHY → TAP */
+       relay_burst(tap_port, phy_port, ...);   /* TAP → PHY */
+   }
+
+Statistics are printed at the interval specified by ``-T``.
+
+Timestamp Source
+~~~~~~~~~~~~~~~~
+
+``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because
+the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``.
+Using ``CLOCK_REALTIME`` would corrupt residence time measurements
+during clock stepping or frequency slewing.  ``CLOCK_MONOTONIC`` is
+portable across Linux and FreeBSD.
diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile
new file mode 100644
index 0000000000..fd178f46ae
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/Makefile
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Intel Corporation
+
+# binary name
+APP = dpdk-ptp_tap_relay_sw
+
+# all source are stored in SRCS-y
+SRCS-y := ptp_tap_relay_sw.c
+
+PKGCONF ?= pkg-config
+
+# Build using pkg-config variables if possible
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+	ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+	ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+	@mkdir -p $@
+
+.PHONY: clean
+clean:
+	rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+	test -d build && rmdir -p build || true
diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build
new file mode 100644
index 0000000000..34a4d86439
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Intel Corporation
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+sources = files(
+        'ptp_tap_relay_sw.c',
+)
+deps += ['net']
+cflags += no_shadow_cflag
diff --git a/examples/ptp_tap_relay_sw/ptp_parse.h b/examples/ptp_tap_relay_sw/ptp_parse.h
new file mode 100644
index 0000000000..db0dcfe5c1
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/ptp_parse.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ *
+ * PTP packet parser — locates PTP headers through L2, VLAN, and UDP
+ * encapsulations. This is a DPI helper for use within example
+ * applications; it does not belong in the core library.
+ */
+
+#ifndef _PTP_PARSE_H_
+#define _PTP_PARSE_H_
+
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_udp.h>
+#include <rte_ptp.h>
+
+/** Not a PTP packet. */
+#define PTP_MSGTYPE_INVALID  (-1)
+
+/**
+ * Locate the PTP header within a packet.
+ *
+ * Handles L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double,
+ * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6,
+ * and VLAN-tagged UDP variants.
+ *
+ * @param m
+ *   Pointer to the mbuf.
+ * @return
+ *   Pointer to the PTP header, or NULL if not a PTP packet.
+ */
+static inline struct rte_ptp_hdr *
+ptp_hdr_find(const struct rte_mbuf *m)
+{
+	const struct rte_ether_hdr *eth;
+	uint16_t ether_type;
+	uint32_t offset;
+
+	if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr))
+		return NULL;
+
+	eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *);
+	ether_type = rte_be_to_cpu_16(eth->ether_type);
+	offset = sizeof(struct rte_ether_hdr);
+
+	/* Strip VLAN / QinQ tags */
+	if (ether_type == RTE_ETHER_TYPE_VLAN ||
+	    ether_type == RTE_ETHER_TYPE_QINQ) {
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr))
+			return NULL;
+		const struct rte_vlan_hdr *vlan =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_vlan_hdr *, offset);
+		ether_type = rte_be_to_cpu_16(vlan->eth_proto);
+		offset += sizeof(struct rte_vlan_hdr);
+
+		/* Second tag (QinQ inner or stacked VLAN) */
+		if (ether_type == RTE_ETHER_TYPE_VLAN ||
+		    ether_type == RTE_ETHER_TYPE_QINQ) {
+			if (rte_pktmbuf_data_len(m) <
+			    offset + sizeof(struct rte_vlan_hdr))
+				return NULL;
+			vlan = rte_pktmbuf_mtod_offset(m,
+				const struct rte_vlan_hdr *, offset);
+			ether_type = rte_be_to_cpu_16(vlan->eth_proto);
+			offset += sizeof(struct rte_vlan_hdr);
+		}
+	}
+
+	/* L2 PTP: EtherType 0x88F7 */
+	if (ether_type == RTE_ETHER_TYPE_1588) {
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	/* PTP over UDP/IPv4 */
+	if (ether_type == RTE_ETHER_TYPE_IPV4) {
+		const struct rte_ipv4_hdr *iph;
+		uint16_t ihl;
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr))
+			return NULL;
+
+		iph = rte_pktmbuf_mtod_offset(m,
+			const struct rte_ipv4_hdr *, offset);
+		if (iph->next_proto_id != IPPROTO_UDP)
+			return NULL;
+
+		ihl = (iph->version_ihl & 0x0F) * 4;
+		if (ihl < 20)
+			return NULL;
+		offset += ihl;
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr))
+			return NULL;
+
+		const struct rte_udp_hdr *udp =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_udp_hdr *, offset);
+		uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port);
+
+		if (dst_port != RTE_PTP_EVENT_PORT &&
+		    dst_port != RTE_PTP_GENERAL_PORT)
+			return NULL;
+
+		offset += sizeof(struct rte_udp_hdr);
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	/* PTP over UDP/IPv6 */
+	if (ether_type == RTE_ETHER_TYPE_IPV6) {
+		const struct rte_ipv6_hdr *ip6h;
+
+		if (rte_pktmbuf_data_len(m) <
+		    offset + sizeof(struct rte_ipv6_hdr))
+			return NULL;
+
+		ip6h = rte_pktmbuf_mtod_offset(m,
+			const struct rte_ipv6_hdr *, offset);
+		if (ip6h->proto != IPPROTO_UDP)
+			return NULL;
+
+		offset += sizeof(struct rte_ipv6_hdr);
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr))
+			return NULL;
+
+		const struct rte_udp_hdr *udp =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_udp_hdr *, offset);
+		uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port);
+
+		if (dst_port != RTE_PTP_EVENT_PORT &&
+		    dst_port != RTE_PTP_GENERAL_PORT)
+			return NULL;
+
+		offset += sizeof(struct rte_udp_hdr);
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	return NULL;
+}
+
+/**
+ * Classify a packet as PTP and return the message type.
+ *
+ * @param m
+ *   Pointer to the mbuf to classify.
+ * @return
+ *   PTP message type (0x0-0xF) on success, PTP_MSGTYPE_INVALID (-1)
+ *   if the packet is not PTP.
+ */
+static inline int
+ptp_classify(const struct rte_mbuf *m)
+{
+	struct rte_ptp_hdr *hdr = ptp_hdr_find(m);
+
+	if (hdr == NULL)
+		return PTP_MSGTYPE_INVALID;
+
+	return rte_ptp_msg_type(hdr);
+}
+
+/** PTP message type name table. */
+static const char * const ptp_msg_names[] = {
+	[RTE_PTP_MSGTYPE_SYNC]           = "Sync",
+	[RTE_PTP_MSGTYPE_DELAY_REQ]      = "Delay_Req",
+	[RTE_PTP_MSGTYPE_PDELAY_REQ]     = "PDelay_Req",
+	[RTE_PTP_MSGTYPE_PDELAY_RESP]    = "PDelay_Resp",
+	[0x4]                            = "Reserved_4",
+	[0x5]                            = "Reserved_5",
+	[0x6]                            = "Reserved_6",
+	[0x7]                            = "Reserved_7",
+	[RTE_PTP_MSGTYPE_FOLLOW_UP]      = "Follow_Up",
+	[RTE_PTP_MSGTYPE_DELAY_RESP]     = "Delay_Resp",
+	[RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up",
+	[RTE_PTP_MSGTYPE_ANNOUNCE]       = "Announce",
+	[RTE_PTP_MSGTYPE_SIGNALING]      = "Signaling",
+	[RTE_PTP_MSGTYPE_MANAGEMENT]     = "Management",
+	[0xE]                            = "Reserved_E",
+	[0xF]                            = "Reserved_F",
+};
+
+/**
+ * Get a human-readable name for a PTP message type.
+ *
+ * @param msg_type
+ *   PTP message type (0x0-0xF or PTP_MSGTYPE_INVALID).
+ * @return
+ *   Static string with the message type name.
+ */
+static inline const char *
+ptp_msg_type_str(int msg_type)
+{
+	if (msg_type < 0 || msg_type > 0xF)
+		return "Not_PTP";
+	return ptp_msg_names[msg_type];
+}
+
+#endif /* _PTP_PARSE_H_ */
diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
new file mode 100644
index 0000000000..998df2ac3b
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+/*
+ * PTP Software Relay
+ *
+ * A minimal PTP relay between a DPDK-bound physical NIC and a kernel
+ * TAP interface using software timestamps only.
+ *
+ * How it works:
+ *   1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX.
+ *   2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp)
+ *      the relay records an RX software timestamp (clock_gettime).
+ *   3. Just before TX on the other side it records a TX software timestamp.
+ *   4. The relay residence time (tx_ts − rx_ts) is added to the PTP
+ *      correctionField via rte_ptp_add_correction() — standard
+ *      Transparent Clock behaviour (IEEE 1588-2019 §10.2).
+ *   5. Packets are forwarded bi-directionally:
+ *        PHY → TAP   (network → ptp4l)
+ *        TAP → PHY   (ptp4l → network)
+ *
+ * ptp4l runs in software-timestamping mode on the TAP interface:
+ *
+ *   ptp4l -i dtap0 -m -s -S   # -S = software timestamps
+ *
+ * Topology:
+ *
+ *   Time Transmitter (remote) ──L2── Physical NIC (DPDK)
+ *                                      │
+ *                                PTP SW Relay  ← correctionField update
+ *                                      │
+ *                                TAP (kernel) ── ptp4l -S (time receiver)
+ *
+ * Usage:
+ *   dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \
+ *       -p 0 -t 1
+ *
+ * Parameters:
+ *   -p PORT    Physical NIC port ID (default: 0)
+ *   -t PORT    TAP port ID (default: 1)
+ *   -T SECS    Stats print interval in seconds (default: 10)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <rte_eal.h>
+#include <rte_ethdev.h>
+#include <rte_mbuf.h>
+#include <rte_cycles.h>
+#include <rte_lcore.h>
+
+#include "ptp_parse.h"
+
+/* Ring sizes */
+#define RX_RING_SIZE  1024
+#define TX_RING_SIZE  1024
+
+/* Mempool */
+#define NUM_MBUFS     8191
+#define MBUF_CACHE    250
+#define BURST_SIZE    32
+
+#define NSEC_PER_SEC  1000000000ULL
+
+/* Logging helpers */
+#define LOG_INFO(fmt, ...) \
+	fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__)
+#define LOG_ERR(fmt, ...) \
+	fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__)
+
+static volatile bool force_quit;
+
+/* Port IDs */
+static uint16_t phy_port;
+static uint16_t tap_port = 1;
+static unsigned int stats_interval = 10;  /* seconds */
+
+/* Statistics */
+static struct {
+	uint64_t phy_rx;        /* total packets from PHY */
+	uint64_t phy_rx_ptp;    /* PTP packets from PHY */
+	uint64_t tap_tx;        /* packets forwarded to TAP */
+	uint64_t tap_rx;        /* total packets from TAP */
+	uint64_t tap_rx_ptp;    /* PTP packets from TAP */
+	uint64_t phy_tx;        /* packets forwarded to PHY */
+	uint64_t corrections;   /* correctionField updates */
+} stats;
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		LOG_INFO("Signal %d received, shutting down...", signum);
+		force_quit = true;
+	}
+}
+
+/* Helpers */
+
+/* Read monotonic clock in nanoseconds (for residence time). */
+static inline uint64_t
+sw_timestamp_ns(void)
+{
+	struct timespec ts;
+
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec;
+}
+
+/* Port Init */
+
+static int
+port_init(uint16_t port, struct rte_mempool *mp)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_dev_info dev_info;
+	uint16_t nb_rxd = RX_RING_SIZE;
+	uint16_t nb_txd = TX_RING_SIZE;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(port_conf));
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret);
+		return ret;
+	}
+
+	if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)
+		port_conf.txmode.offloads |=
+			RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret != 0)
+		return ret;
+
+	ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd);
+	if (ret != 0)
+		return ret;
+
+	ret = rte_eth_rx_queue_setup(port, 0, nb_rxd,
+			rte_eth_dev_socket_id(port), NULL, mp);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_tx_queue_setup(port, 0, nb_txd,
+			rte_eth_dev_socket_id(port), NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		LOG_ERR("Failed to enable promiscuous on port %u: %s",
+			port, rte_strerror(-ret));
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Relay one direction */
+
+/*
+ * Forward packets from src_port to dst_port.
+ * For PTP event messages, record SW timestamps around the
+ * relay path and add the residence time to the correctionField.
+ *
+ * This implements a Transparent Clock (IEEE 1588-2019 §10.2):
+ *   correctionField += (t_egress − t_ingress)
+ *
+ * Note: a single rx_ts / tx_ts pair is used for the entire burst.
+ * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s)
+ * bursts contain at most one packet, so this is exact.  At higher
+ * rates, early packets in a burst are slightly under-corrected and
+ * late ones over-corrected by up to one poll-loop iteration.
+ */
+static void
+relay_burst(uint16_t src_port, uint16_t dst_port,
+	    uint64_t *rx_cnt, uint64_t *rx_ptp_cnt,
+	    uint64_t *tx_cnt, uint64_t *corr_cnt)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+	struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE];
+	uint64_t rx_ts;
+	uint16_t nb_rx, nb_tx, i;
+
+	nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE);
+	if (nb_rx == 0)
+		return;
+
+	/* Record a single RX software timestamp for the whole burst.
+	 * All packets in one burst arrived at essentially the same instant
+	 * from rte_eth_rx_burst()'s perspective.
+	 */
+	rx_ts = sw_timestamp_ns();
+
+	*rx_cnt += nb_rx;
+
+	/*
+	 * Pass 1: Parse each packet once and remember PTP event headers.
+	 * This avoids taking the TX timestamp too early — we want it as
+	 * close to the actual rte_eth_tx_burst() call as possible.
+	 */
+	memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx);
+	for (i = 0; i < nb_rx; i++) {
+		struct rte_ptp_hdr *hdr = ptp_hdr_find(bufs[i]);
+
+		if (hdr == NULL)
+			continue;
+
+		(*rx_ptp_cnt)++;
+
+		/* Only event messages carry timestamps that need correction */
+		if (!rte_ptp_is_event(rte_ptp_msg_type(hdr)))
+			continue;
+
+		ptp_hdrs[i] = hdr;
+	}
+
+	/*
+	 * Pass 2: Take a single TX timestamp right before transmission.
+	 * This minimises the gap between the measured tx_ts and the
+	 * actual kernel write inside rte_eth_tx_burst(), giving the
+	 * most accurate residence time we can achieve with SW timestamps.
+	 *
+	 * residence_time = tx_ts − rx_ts
+	 *
+	 * Remaining untracked delays:
+	 *   - Pre-RX:  NIC DMA → rx_burst return  (~1-5 µs, unavoidable)
+	 *   - Post-TX:  tx_ts → kernel TAP write   (~1-2 µs)
+	 * Both are symmetric for Sync and Delay_Req so they largely
+	 * cancel in the ptp4l offset calculation.
+	 */
+	uint64_t tx_ts = sw_timestamp_ns();
+	int64_t residence_ns = (int64_t)(tx_ts - rx_ts);
+
+	for (i = 0; i < nb_rx; i++) {
+		if (ptp_hdrs[i] == NULL)
+			continue;
+		rte_ptp_add_correction(ptp_hdrs[i], residence_ns);
+		(*corr_cnt)++;
+	}
+
+	/* Forward the burst */
+	nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx);
+	*tx_cnt += nb_tx;
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < nb_rx; i++)
+		rte_pktmbuf_free(bufs[i]);
+}
+
+/* Print statistics */
+
+static void
+print_stats(void)
+{
+	LOG_INFO("=== Statistics ===");
+	LOG_INFO("  PHY RX total:   %"PRIu64, stats.phy_rx);
+	LOG_INFO("  PHY RX PTP:     %"PRIu64, stats.phy_rx_ptp);
+	LOG_INFO("  TAP TX:         %"PRIu64, stats.tap_tx);
+	LOG_INFO("  TAP RX total:   %"PRIu64, stats.tap_rx);
+	LOG_INFO("  TAP RX PTP:     %"PRIu64, stats.tap_rx_ptp);
+	LOG_INFO("  PHY TX:         %"PRIu64, stats.phy_tx);
+	LOG_INFO("  Corrections:    %"PRIu64, stats.corrections);
+}
+
+/* Main relay loop */
+
+static int
+relay_loop(__rte_unused void *arg)
+{
+	uint64_t last_stats = rte_rdtsc();
+	uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval;
+
+	LOG_INFO("Relay loop started on lcore %u", rte_lcore_id());
+	LOG_INFO("  PHY port %u  <-->  TAP port %u", phy_port, tap_port);
+	LOG_INFO("  Correction field updates: enabled for event messages");
+
+	while (!force_quit) {
+		/* PHY → TAP */
+		relay_burst(phy_port, tap_port,
+			    &stats.phy_rx, &stats.phy_rx_ptp,
+			    &stats.tap_tx, &stats.corrections);
+
+		/* TAP → PHY */
+		relay_burst(tap_port, phy_port,
+			    &stats.tap_rx, &stats.tap_rx_ptp,
+			    &stats.phy_tx, &stats.corrections);
+
+		/* Periodic stats */
+		if (rte_rdtsc() - last_stats > stats_tsc) {
+			print_stats();
+			last_stats = rte_rdtsc();
+		}
+	}
+
+	print_stats();
+	return 0;
+}
+
+/* Argument parsing */
+
+static void
+usage(const char *prog)
+{
+	fprintf(stderr,
+		"Usage: %s [EAL options] -- [options]\n"
+		"  -p PORT   Physical NIC port ID (default: 0)\n"
+		"  -t PORT   TAP port ID (default: 1)\n"
+		"  -T SECS   Stats interval in seconds (default: 10)\n"
+		"\n"
+		"Example:\n"
+		"  %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n"
+		"\n"
+		"Then run ptp4l with software timestamps:\n"
+		"  ptp4l -i dtap0 -m -s -S\n",
+		prog, prog);
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+	int opt;
+
+	while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) {
+		switch (opt) {
+		case 'p':
+			phy_port = (uint16_t)atoi(optarg);
+			break;
+		case 't':
+			tap_port = (uint16_t)atoi(optarg);
+			break;
+		case 'T':
+			stats_interval = (unsigned int)atoi(optarg);
+			break;
+		case 'h':
+		default:
+			usage(argv[0]);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/* Main */
+
+int
+main(int argc, char **argv)
+{
+	struct rte_mempool *mp;
+	uint16_t nb_ports;
+	int ret;
+
+	/* EAL init */
+	ret = rte_eal_init(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "EAL init failed\n");
+	argc -= ret;
+	argv += ret;
+
+	/* App args */
+	ret = parse_args(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "Invalid arguments\n");
+
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	nb_ports = rte_eth_dev_count_avail();
+	if (nb_ports < 2)
+		rte_exit(EXIT_FAILURE,
+			 "Need at least 2 ports (PHY + TAP).\n"
+			 "Use --vdev=net_tap0,iface=dtap0\n");
+
+	if (!rte_eth_dev_is_valid_port(phy_port))
+		rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port);
+	if (!rte_eth_dev_is_valid_port(tap_port))
+		rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port);
+
+	mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
+				     MBUF_CACHE, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	if (mp == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");
+
+	LOG_INFO("Initializing PHY port %u...", phy_port);
+	ret = port_init(phy_port, mp);
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n",
+			 phy_port, ret);
+
+	LOG_INFO("Initializing TAP port %u...", tap_port);
+	ret = port_init(tap_port, mp);
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n",
+			 tap_port, ret);
+
+	LOG_INFO("PTP Software Relay ready");
+	LOG_INFO("  PHY port:     %u", phy_port);
+	LOG_INFO("  TAP port:     %u", tap_port);
+	LOG_INFO("  Stats every:  %u seconds", stats_interval);
+	LOG_INFO("  Correction:   Transparent Clock (SW timestamps)");
+	LOG_INFO("");
+	LOG_INFO("Run ptp4l:  ptp4l -i dtap0 -m -s -S");
+
+	/* Run relay on main lcore */
+	relay_loop(NULL);
+
+	/* Cleanup */
+	LOG_INFO("Stopping ports...");
+	rte_eth_dev_stop(phy_port);
+	rte_eth_dev_stop(tap_port);
+	rte_eth_dev_close(phy_port);
+	rte_eth_dev_close(tap_port);
+	rte_eal_cleanup();
+
+	return 0;
+}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions
From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507134529.2573300-1-rajesh3.kumar@intel.com>

Add PTP (Precision Time Protocol) header structures and inline helper
functions to lib/net following DPDK conventions for protocol libraries
(similar to rte_tcp.h, rte_ip.h).

Provides wire-format structures with endian-annotated types:
  - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID
  - rte_ptp_hdr: 34-byte common message header with correctionField
  - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp

PTP message type constants for all IEEE 1588-2019 message types
(Sync, Delay_Req, Announce, Management, etc.) and flag field bits
(two-step, unicast, leap indicator).

Inline accessor and utility functions for performance:
  - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain()
  - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step()
  - rte_ptp_correction_ns(), rte_ptp_add_correction()
  - rte_ptp_timestamp_to_ns() for timestamp conversion

Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ,
UDP/IPv4, and UDP/IPv6 (ports 319/320).

All inline functions — zero function call overhead, suitable for
real-time packet processing.

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 MAINTAINERS         |   6 +
 lib/net/meson.build |   1 +
 lib/net/rte_ptp.h   | 264 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 271 insertions(+)
 create mode 100644 lib/net/rte_ptp.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 0f5539f851..da31ada871 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst
 M: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
 F: app/test-sad/
 
+PTP - lib/net
+M: Rajesh Kumar <rajesh3.kumar@intel.com>
+F: lib/net/rte_ptp.h
+F: examples/ptp_tap_relay_sw/
+F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
+
 PDCP - EXPERIMENTAL
 M: Anoob Joseph <anoobj@marvell.com>
 M: Volodymyr Fialko <vfialko@marvell.com>
diff --git a/lib/net/meson.build b/lib/net/meson.build
index 3fad5edc5b..63d13719f3 100644
--- a/lib/net/meson.build
+++ b/lib/net/meson.build
@@ -28,6 +28,7 @@ headers = files(
         'rte_geneve.h',
         'rte_l2tpv2.h',
         'rte_ppp.h',
+        'rte_ptp.h',
         'rte_ib.h',
 )
 
diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h
new file mode 100644
index 0000000000..649b944d29
--- /dev/null
+++ b/lib/net/rte_ptp.h
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _RTE_PTP_H_
+#define _RTE_PTP_H_
+
+/**
+ * @file
+ *
+ * PTP (IEEE 1588) protocol definitions
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <rte_byteorder.h>
+#include <rte_common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * PTP Constants
+ */
+
+/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp). */
+#define RTE_PTP_EVENT_PORT        319
+
+/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). */
+#define RTE_PTP_GENERAL_PORT      320
+
+/** PTP multicast MAC address: 01:1B:19:00:00:00. */
+#define RTE_PTP_MULTICAST_MAC     { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 }
+
+/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */
+#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E }
+
+/*
+ * PTP Message Types (IEEE 1588-2019 Table 36)
+ */
+
+#define RTE_PTP_MSGTYPE_SYNC            0x0  /**< Sync (event). */
+#define RTE_PTP_MSGTYPE_DELAY_REQ       0x1  /**< Delay_Req (event). */
+#define RTE_PTP_MSGTYPE_PDELAY_REQ      0x2  /**< Peer_Delay_Req (event). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP     0x3  /**< Peer_Delay_Resp (event). */
+#define RTE_PTP_MSGTYPE_FOLLOW_UP       0x8  /**< Follow_Up (general). */
+#define RTE_PTP_MSGTYPE_DELAY_RESP      0x9  /**< Delay_Resp (general). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU  0xA  /**< Peer_Delay_Resp_Follow_Up. */
+#define RTE_PTP_MSGTYPE_ANNOUNCE        0xB  /**< Announce (general). */
+#define RTE_PTP_MSGTYPE_SIGNALING       0xC  /**< Signaling (general). */
+#define RTE_PTP_MSGTYPE_MANAGEMENT      0xD  /**< Management (general). */
+
+/*
+ * PTP Flag Field Bits (IEEE 1588-2019 Table 37)
+ *
+ * These constants are for use after rte_be_to_cpu_16(hdr->flags).
+ * flagField[0] (octet 6) maps to host bits 8-15.
+ * flagField[1] (octet 7) maps to host bits 0-7.
+ */
+
+#define RTE_PTP_FLAG_TWO_STEP    (1 << 9)   /**< Two-step flag. */
+#define RTE_PTP_FLAG_UNICAST     (1 << 10)  /**< Unicast flag. */
+#define RTE_PTP_FLAG_LI_61       (1 << 0)   /**< Leap indicator 61. */
+#define RTE_PTP_FLAG_LI_59       (1 << 1)   /**< Leap indicator 59. */
+
+/*
+ * PTP Header Structures (IEEE 1588-2019)
+ */
+
+/**
+ * PTP Port Identity (10 bytes).
+ */
+struct __rte_packed_begin rte_ptp_port_id {
+	uint8_t    clock_id[8]; /**< clockIdentity (EUI-64). */
+	rte_be16_t port_number; /**< portNumber. */
+} __rte_packed_end;
+
+/**
+ * PTP Common Message Header (34 bytes).
+ */
+struct __rte_packed_begin rte_ptp_hdr {
+	uint8_t    msg_type;       /**< transportSpecific (4) | messageType (4). */
+	uint8_t    version;        /**< minorVersionPTP (4) | versionPTP (4). */
+	rte_be16_t msg_length;     /**< Total message length in bytes. */
+	uint8_t    domain_number;  /**< PTP domain (0-255). */
+	uint8_t    minor_sdo_id;   /**< minorSdoId (IEEE 1588-2019). */
+	rte_be16_t flags;          /**< Flag field (see RTE_PTP_FLAG_*). */
+	rte_be64_t correction;     /**< correctionField (scaled ns, 48.16 fixed). */
+	rte_be32_t msg_type_specific; /**< messageTypeSpecific. */
+	struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. */
+	rte_be16_t sequence_id;    /**< sequenceId. */
+	uint8_t    control;        /**< controlField (deprecated in 1588-2019). */
+	int8_t     log_msg_interval; /**< logMessageInterval. */
+} __rte_packed_end;
+
+/**
+ * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies).
+ */
+struct __rte_packed_begin rte_ptp_timestamp {
+	rte_be16_t seconds_hi;   /**< Upper 16 bits of seconds. */
+	rte_be32_t seconds_lo;   /**< Lower 32 bits of seconds. */
+	rte_be32_t nanoseconds;  /**< Nanoseconds (0-999999999). */
+} __rte_packed_end;
+
+/*
+ * Inline Helpers
+ */
+
+/**
+ * Extract PTP message type from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Message type (0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_msg_type(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->msg_type & 0x0F;
+}
+
+/**
+ * Extract transport-specific field from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Transport-specific value (upper nibble, 0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr)
+{
+	return (hdr->msg_type >> 4) & 0x0F;
+}
+
+/**
+ * Extract PTP version from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   PTP version number (typically 2).
+ */
+static inline uint8_t
+rte_ptp_version(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->version & 0x0F;
+}
+
+/**
+ * Get sequence ID from PTP header (host byte order).
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Sequence ID in host byte order.
+ */
+static inline uint16_t
+rte_ptp_seq_id(const struct rte_ptp_hdr *hdr)
+{
+	return rte_be_to_cpu_16(hdr->sequence_id);
+}
+
+/**
+ * Get PTP domain number.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Domain number (0-255).
+ */
+static inline uint8_t
+rte_ptp_domain(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->domain_number;
+}
+
+/**
+ * Check if PTP message type is an event message.
+ * Event messages (msg_type 0x0-0x3) require timestamps.
+ *
+ * @param msg_type
+ *   PTP message type value (0x0-0xF).
+ * @return
+ *   true if event message, false otherwise.
+ */
+static inline bool
+rte_ptp_is_event(int msg_type)
+{
+	return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP;
+}
+
+/**
+ * Check if the two-step flag is set in a PTP header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   true if two-step flag is set.
+ */
+static inline bool
+rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr)
+{
+	return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0;
+}
+
+/**
+ * Get correctionField value in nanoseconds (from 48.16 fixed-point).
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Correction value in nanoseconds.
+ */
+static inline int64_t
+rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr)
+{
+	return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16;
+}
+
+/**
+ * Add a residence time (in nanoseconds) to the correctionField.
+ * Used by Transparent Clocks to account for relay transit delay.
+ * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point).
+ *
+ * @param hdr
+ *   Pointer to PTP header (will be modified in-place).
+ * @param residence_ns
+ *   Residence time in nanoseconds to add.
+ */
+static inline void
+rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns)
+{
+	int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction);
+
+	cf += (int64_t)((uint64_t)residence_ns << 16);
+	hdr->correction = rte_cpu_to_be_64(cf);
+}
+
+/**
+ * Convert a PTP timestamp structure to nanoseconds since epoch.
+ *
+ * @param ts
+ *   Pointer to PTP timestamp.
+ * @return
+ *   Time in nanoseconds since epoch.
+ */
+static inline uint64_t
+rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts)
+{
+	uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) |
+		       rte_be_to_cpu_32(ts->seconds_lo);
+
+	return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_PTP_H_ */
-- 
2.53.0


^ permalink raw reply related

* [PATCH v7 0/4] IEEE 1588 PTP v2 protocol support in lib/net
From: Rajesh Kumar @ 2026-05-07 13:45 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260428010117.692626-1-rajesh3.kumar@intel.com>

Add IEEE 1588-2019 Precision Time Protocol (PTP) support to DPDK via a
new header library in lib/net/. This patchset provides wire-format packet
structure definitions and inline helpers for Transparent Clock and
ordinary clock applications.

Design Rationale
================

Following DPDK conventions for protocol libraries (rte_tcp.h, rte_ip.h),
PTP definitions are provided as a header-only library with inline functions
for zero-overhead packet processing. All functions are suitable for
real-time, performance-critical code paths.

Contents
========

Patch 1: lib/net/rte_ptp.h - Header structures and inline helpers
  - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID
  - rte_ptp_hdr: 34-byte PTP v2 common message header
  - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp
  - Message type constants (Sync, Delay_Req, Announce, etc.)
  - Flag field bits (two-step, unicast, leap indicator)
  - Inline helpers: rte_ptp_msg_type(), rte_ptp_seq_id(),
    rte_ptp_is_event(), rte_ptp_correction_ns(), rte_ptp_add_correction(),
    rte_ptp_timestamp_to_ns()

Patch 2: examples/ptp_tap_relay_sw - Software PTP Transparent Clock relay
  - Relays PTP packets between a DPDK-bound physical NIC and Linux TAP
  - Software timestamp-based residence time measurement (CLOCK_MONOTONIC)
  - Accumulates residence time to correctionField per IEEE 1588-2019 §10.2
  - Handles L2, VLAN/QinQ, UDP/IPv4, UDP/IPv6 PTP encapsulations
  - Works with unmodified Linux kernel and stock DPDK (no patches required)
  - Compatible with standard linuxptp (ptp4l) tooling

Patch 3: doc/guides/rel_notes/release_26_07.rst - Release notes update

Patch 4: examples/ptpclient - Update to use lib/net PTP definitions
  - Replace local struct ptp_header with struct rte_ptp_hdr
  - Replace local struct tstamp with struct rte_ptp_timestamp
  - Replace local struct clock_id with uint8_t[8] arrays
  - Use RTE_PTP_MSGTYPE_* constants instead of local defines
  - Use rte_ptp_msg_type(), rte_ptp_seq_id() inline helpers
  - Remove local PTP_PROTOCOL macro (use RTE_ETHER_TYPE_1588)
  - Add lib/net dependency in meson.build

Testing
=======

- Build: `ninja -C build` - clean compilation
- Examples: Both ptpclient and ptp_tap_relay_sw compile and link correctly
- No stale references to old patterns or removed functions

Changelog
=========

v7 changes:
  - Added sample app guide to sample_app_ug toctree to fix Sphinx warning
  - Fixed all checkpatch warnings (commit message line lengths <= 75 chars)
  - Refactored ptpclient to use shared lib/net/rte_ptp.h definitions
  - All 5 patches (cover + 4 functional) pass checkpatch validation

v6 changes:
  - Restructured to lib/net (header-only, following lib/net conventions)
  - Removed separate DPI library functions (moved to example local parser)
  - Removed app/test unit tests (header-only, example-driven testing)
  - Removed programmer's guide (lib/net headers use Doxygen API docs only)

Rajesh Kumar (4):
  lib/net: add IEEE 1588 PTP v2 protocol header definitions
  examples/ptp_tap_relay_sw: add PTP software transparent clock relay
  doc: update release notes for PTP protocol library
  examples/ptpclient: use shared PTP library definitions

 MAINTAINERS                                   |   6 +
 doc/guides/rel_notes/release_26_07.rst        |  12 +
 doc/guides/sample_app_ug/index.rst            |   1 +
 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++
 examples/ptp_tap_relay_sw/Makefile            |  41 ++
 examples/ptp_tap_relay_sw/meson.build         |  13 +
 examples/ptp_tap_relay_sw/ptp_parse.h         | 211 +++++++++
 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c  | 432 ++++++++++++++++++
 examples/ptpclient/meson.build                |   1 +
 examples/ptpclient/ptpclient.c                | 204 ++++-----
 lib/net/meson.build                           |   1 +
 lib/net/rte_ptp.h                             | 264 +++++++++++
 12 files changed, 1274 insertions(+), 124 deletions(-)
 create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
 create mode 100644 examples/ptp_tap_relay_sw/Makefile
 create mode 100644 examples/ptp_tap_relay_sw/meson.build
 create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h
 create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
 create mode 100644 lib/net/rte_ptp.h

-- 
2.53.0


^ permalink raw reply

* Re: [RFC v2 0/4] flow_compile: textual flow rule compiler
From: Bruce Richardson @ 2026-05-07  8:10 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev
In-Reply-To: <20260507001501.608724-1-stephen@networkplumber.org>

On Wed, May 06, 2026 at 05:06:47PM -0700, Stephen Hemminger wrote:
> Background
> ----------
> 
> Multiple efforts over the past few cycles have tried to make
> testpmd's flow rule grammar reusable from outside testpmd.
> External applications that need rte_flow want a documented way
> to turn human-written rules into the rte_flow_attr/item/action
> arrays accepted by rte_flow_create().
> 
> The most recent attempt is Lukas Sismis's series, currently at
> v12:
> 
>   http://patches.dpdk.org/project/dpdk/list/?series=37384
> 
> That series factors testpmd's existing cmdline_flow.c into a
> library and updates testpmd to consume it.  It works, but
> inherits two properties of cmdline_flow.c that I think are worth
> avoiding in a reusable library:
> 
>   - Coupling to librte_cmdline.  Even after the v12 split into
>     a "simple" part and a "cmdline" part, the parser is still
>     organized around testpmd's command interpreter, and v12 has
>     cmdline depending on ethdev to break a previous circular
>     dependency.  A library used by daemons, control planes, or
>     unit tests should not need that.
> 
>   - Ad-hoc grammar.  cmdline_flow.c implements parsing per-token
>     in long dispatch logic; the grammar emerges from the code
>     rather than being stated, and adding a new flow item
>     requires touching the parser.
> 
> This RFC explores a different shape and is posted to ask the
> list which one is preferred before more work goes into either.
> 
> I started a new green-field library for parsing flow rules
> (with AI assistance for the boilerplate).  It is young but
> passes tests and reviews clean under the project's AI review
> guidelines.
> 
> This series
> -----------
> 
> lib/flow_compile -- a small new library providing the same
> service via a pcap_compile()-style API:
> 
>     char errbuf[RTE_FLOW_COMPILE_ERRBUF_SIZE];
>     struct rte_flow_compile *fc = rte_flow_compile(rule, errbuf);
>     if (fc == NULL)
>             fail(errbuf);            /* "line:col: message" */
> 
>     rte_flow_compile_create(port_id, fc, &flow_error);
>     rte_flow_compile_free(fc);
> 
> Design properties:
> 
>   - Flex lexer plus bison grammar.  Both are reentrant
>     (%option reentrant, %define api.pure full), so multiple
>     compilations may run concurrently and the parser holds no
>     static mutable state.  The grammar itself is short
>     (~200 lines) because all per-type knowledge lives in
>     descriptor tables, not in productions.
> 
>   - Parser is driven entirely by descriptor tables of items and
>     actions.  Adding a new flow item is a table edit, not a
>     grammar change.  A custom-setter hook on each field is the
>     escape valve for layouts that don't fit a plain byte range
>     (bitfields, indirect arrays).
> 
>   - Dependencies: rte_ethdev (for rte_flow.h) and rte_net (for
>     MAC parsing).  No librte_cmdline.  Flex and bison are
>     required at build time to regenerate the lexer and parser;
>     if either tool is missing the library is silently skipped
>     via meson's has_flex_bison check, the same pattern other
>     DPDK components use for optional generators.
> 
>   - Per-allocation rte_zmalloc for spec/mask/last/conf payloads;
>     rte_flow_compile_free() walks the pattern and action arrays
>     and releases every non-NULL slot before freeing the arrays.
>     Parse-error paths use the same walker, so partially
>     constructed rules clean up uniformly.  ASan/LSan run clean
>     on the autotest, including the failure cases.
> 
> The grammar follows testpmd's syntax closely so familiar rules
> carry over:
> 
>     ingress pattern eth / ipv4 src is 10.0.0.1 / end
>     actions queue index 3 / count / end
> 
> and is documented as a formal BNF in the programmer's guide
> chapter (patch 2).
> 
> Initial coverage: eth, vlan, ipv4, ipv6, tcp, udp, vxlan,
> port_id, port_representor, represented_port items; drop,
> passthru, queue, mark, jump, count, port_id and representor
> variants, of_pop_vlan, vxlan_decap actions.  Variable-conf
> items and actions (RSS, RAW) need custom setters and are
> deferred to a follow-up.
> 
> What this RFC is *not*
> ----------------------
> 
> Not a replacement for cmdline_flow.c in testpmd.  If the shape
> here is acceptable, the next step is a separate series adding a
> "flow compile <port> <rule>" command in testpmd alongside the
> existing parser, so users can adopt the library incrementally
> without breaking scripts that depend on the current syntax.
> 
> What I'd like feedback on
> -------------------------
> 
> 1. API shape.  pcap_compile-style (one string -> opaque object ->
>    arrays) versus the three-call attr/pattern/actions form
>    Sismis's v12 exposes.  What does your application actually
>    want?
> 

For this, I wonder if we also could do with a second API for the creation
which takes a list of tokens rather than just a single string. Thinking
about integration with testpmd, or with apps which already have some
commandline interface which produces a list of tokens, having to re-stitch
the tokens together into one string seems awkward.

Also, have you already investigated how this might be integrated into
testpmd? Do we have the capability to pass multi-token strings via cmdline?

> 2. Library placement.  Stand-alone at lib/flow_compile/ versus
>    addition to lib/ethdev.  This series treats it as a
>    control-path parser layered on top of ethdev rather than
>    part of ethdev itself; v12 places its parser inside ethdev.
> 

+1 to external to ethdev

> 3. Table-driven extension model.  Is "to add a new flow item,
>    add a row to the descriptor table" the right contract?
>    Should the tables live alongside each rte_flow_item_*
>    definition in rte_flow.h, or in their own file as here?
> 
> 4. Build-tool dependency.  Flex and bison are not currently
>    required to build DPDK.  Adding a library that needs them
>    (with a clean has_flex_bison fallback so the rest of DPDK
>    still builds without them) is the cleanest way I see to get
>    a real grammar. If this gets used by testpmd then
>    what is now an optional dependency would get hardened in.
> 

Flex and bison are very common build tools. I don't see an issue with this
dependency.

> 5. Convergence.  If this design is preferred, I'm happy to
>    coordinate with Lukas to fold in the testpmd-side changes
>    from his series.
> 
> 6. Readability. AI generated code like this tends to be
>    either opaque or too verbose for humans. Often have to
>    nudge it into submission.
> 

For readability, can you (or the AI's working for you :-) ) split the main
patch into a couple of patches for easier review and comment. It's a very
large single patch to go through in one go.

/Bruce

^ permalink raw reply

* Re: [PATCH v1 1/1] net/iavf: fix large VF IRQ mapping
From: Burakov, Anatoly @ 2026-05-07  8:08 UTC (permalink / raw)
  To: David Marchand; +Cc: dev, Vladimir Medvedkin, Bruce Richardson
In-Reply-To: <CAJFAV8yNKxSXjmjQ0sM21-6hZDTVrGT9cpZ6aQU_FNTepTP4nw@mail.gmail.com>

On 5/6/2026 5:58 PM, David Marchand wrote:
> On Wed, 6 May 2026 at 16:07, Anatoly Burakov <anatoly.burakov@intel.com> wrote:
>>
>> The PF will check buffer size for being too big, and the chunk sizing code
>> correctly calls that out. However, the size was actually still too big
>> because `struct virtchnl_queue_vector_maps` already had one queue vector
>> as part of its definition, so `chunk_sz` was too big by 1.
>>
>> Fixes: 292d3b781ac4 ("net/iavf: replace unnecessary hugepage memory allocations")
>>
>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>> ---
>>   drivers/net/intel/iavf/iavf_vchnl.c | 2 +-
>>   1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/net/intel/iavf/iavf_vchnl.c b/drivers/net/intel/iavf/iavf_vchnl.c
>> index c2f340db81..dd09b0fa61 100644
>> --- a/drivers/net/intel/iavf/iavf_vchnl.c
>> +++ b/drivers/net/intel/iavf/iavf_vchnl.c
>> @@ -1528,7 +1528,7 @@ iavf_config_irq_map_lv_chunk(struct iavf_adapter *adapter,
>>
>>          /* for some reason PF side checks for buffer being too big, so adjust it down */
> 
> The comment above can be removed?

No, it's still relevant, because it refers to the fact that we're 
adjusting the total length downwards as opposed to leaving it at max size.

> 
>>          buf_len = sizeof(struct virtchnl_queue_vector_maps) +
>> -                 sizeof(struct virtchnl_queue_vector) * chunk_sz;
>> +                 sizeof(struct virtchnl_queue_vector) * (chunk_sz - 1);
> 
> - did you make sure you did not break compat with previous version of
> Intel out of tree PF driver (since this concerns configuring "Large
> VF")?

The commit in question *did* break things with previous out of tree PF 
driver. This commit fixes the breakage introduced in that commit. The 
commit being fixed was a refactor, which specified size as N-1.

> 
> - all those virtchnl list struct have the same elems[1] issue.
> Kernel side did some cleanups some time ago, maybe time for DPDK to do
> the same...?
> 

Yes, it is indeed time to do the same, but not as part of this patchset, 
and not before the base driver code is updated to do the same. There is 
some background work happening on that front already, but there are a 
lot of dependencies and moving parts, so we can't just change this willy 
nilly.

-- 
Thanks,
Anatoly

^ permalink raw reply

* DPDK Tech Board meeting minutes 29-April-2026
From: Morten Brørup @ 2026-05-07  6:03 UTC (permalink / raw)
  To: dev; +Cc: techboard

Members Attending
=================
Bruce Richardson
Hemant Agrawal
Jerin Jacob
Konstantin Ananyev
Maxime Coquelin
Morten Brørup (chair)
Stephen Hemminger
Thomas Monjalon

NOTE
====
The technical board meetings are on every second Wednesday at 3 pm UTC.
Meetings are public. DPDK community members are welcome to attend on Zoom:
https://zoom-lfx.platform.linuxfoundation.org/meeting/96459488340?password=d808f1f6-0a28-4165-929e-5a5bcae7efeb
Agenda: https://annuel.framapad.org/p/r.0c3cc4d1e011214183872a98f6b5c7db
Minutes of previous meetings: http://core.dpdk.org/techboard/minutes

Next meeting will be on: Wednesday 27-May-2026 at 3pm UTC, and will be chaired by: Konstantin Ananyev.

Agenda Items
============

1. Patch backlog and lack of reviews
------------------------------------
The patch backlog has grown from 300 to 500 items.
Although the backlog isn't worse than usual, it still needs addressing.
Currently, Thomas is the only person reviewing and merging patches for many directories, which sometimes leads to delays in merging.
The board agreed to merge patches without full consensus during the early release cycle, as issues could be addressed in later release candidates.
It was noted that simple comments and informal feedback on patches is also valuable when considering merging patches without Acked-by or Reviewed-by tags.
The board discussed various incentives to get more reviews: gamification, recognition of top reviewers, requiring code contributors to also contribute reviews.
To proceed experimenting with some of this, we will try making a dashboard showing the balance of code contributed vs. reviews contributed per contributor.

Many of the patches in the backlog lacking review are documentation updates from January.
The board discussed creating a documentation maintainer role as a webmaster-like role: responsible for collecting and consolidating content, but not for the content itself. We will seek candidates on the announcement mailing list.

2. Wish List review
-------------------
The board reviewed the list of new wishes in Bugzilla: https://issues.dpdk.org/reports?component=wishes&status=UNCONFIRMED
The board decided on a process where wishes accepted (possibly for further consideration) by the tech board would be marked with status Confirmed, with a reference to the tech board minutes where discussed.

#1905: Reduce memory footprint by using rte_max_lcore instead of RTE_MAX_LCORE
Some build-time constants have a large effect on how much memory is allocated by DPDK.
The board noted that the max number of lcores is not the only one; e.g. the max number of queues per port has a high effect on memory usage.
Getting completely rid of these, and making arrays etc. fully dynamic would be the optimal solution. But replacing them by startup-time variables may be easier to implement.
There is no need for individual wishes per build-time constant affecting memory usage. This wish will be considered in broad context, covering all the relevant build-time constants, with flexibility on implementation order, also considering the impact of getting rid of each one.
This wish was accepted for the roadmap.

#1906: Use DPDK memory for lcore variables
The board identified some disadavantages with this proposal; mainly that it would probably consume an additional hugepage TLB entry per thread, instead of a number of ordinary TLB entries.
This wish was kept on the wish list for further investigation.

#1912: free mbufs should not have next and nb_segs initialized
The concept of not requiring any non-constant mbuf fields to be initialized when freeing an mbuf into its mempool, and instead leave initialization to the Rx path of the drivers was discussed.
The board was concerned about how such a change would affect performance.
The store buffers in many CPUs can handle up to 32 (consecutive) bytes each. So, if the Rx path needs to write more than 32 bytes into the mbuf, it requires more store buffers, which may then become a bottleneck.
In the context of performance, an alternative was discussed: The "nb_segs" field (residing in the mbuf's first cache line) can be used as a gate to determine if the "next" field (residing in the mbuf's second cache line) is valid or not.
Jerin also emphasized that hardware implementations typically handle memory pooling differently, with automatic freeing on the TX side.
This wish was kept on the wish list for further investigation.

#1913: Introduce RTE_EAL_INIT_PRIO() alternative to RTE_INIT_PRIO()
Introducing a macro to allow libraries (and applications and drivers) to install their initialization functions into hooks at various stages during EAL initialization may help decorrelate library dependencies.
This wish was accepted for the roadmap.

#1929: Ethdev Tx Completion API
Adding a Tx completion capability - with separate transmit stages for descriptor setup and cleanup phases - would be useful for post-processing features, such as packet capture and time stamping.
The board was concerned about performance, particularly regarding per-packet completions versus bulk completions.
Enabling per-packet notifications would require additional PCI bandwidth and thus impact throughput.
Bruce suggested adding a per-packet flag as a potential implementation approach.
This wish was kept on the wish list for further investigation.


Venlig hilsen / Kind regards,
-Morten Brørup


^ permalink raw reply

* DPDK Tech Board meeting minutes 01-April-2026
From: Hemant Agrawal @ 2026-05-07  5:42 UTC (permalink / raw)
  To: dev; +Cc: dpdk-techboard
In-Reply-To: <GV1PR04MB1075079DC70ECD65A4C3E900E89312@GV1PR04MB10750.eurprd04.prod.outlook.com>

Members Attending
=================

Aaron Conole
Bruce Richardson
Hemant Agrawal
Jerin Jacob Kollanukkaran
Kevin Traynor
Konstantin Ananyev
Morten Brørup
Stephen Hemminger
Thomas Monjalon

NOTE
====
The Technical Board meetings take place every second Wednesday at 3 pm UTC.
Meetings are public, and DPDK community members are welcome to attend.
Agenda and previous minutes:
http://core.dpdk.org/techboard/minutes
The next meeting will follow the regular schedule.

---------------------------------------
1. Summit and Governance Updates
---------------------------------------------------
	- Nathan provided an update on the DPDK Summit in Stockholm.
	- ~52 in‑person registrations so far, expecting in 70–80 range. Speaker agenda has been finalized.
	- separate Tech Board–only meeting will be replaced by a joint Tech Board / Governing Board meeting

2. Personnel and Maintenance Updates
---------------------------------------------------
	- Patrick Robb announced his role transition away from UNH.
	- Lincoln Lavoie will take on lab maintainer responsibilities in the medium term.
	- Patrick expressed interest in continuing as a DTS maintainer in a personal capacity. 
	Action: Patrick to submit a maintainers file update.

3. Release Status
-----------------------
	- DPDK 26.03 was released successfully with no outstanding carry‑over items.
	- The 26.07 development cycle is starting; maintainers noted deferred patch backlogs that will now be addressed.

4. AI‑Assisted Code Review
------------------------------------
	- The board discussed the recently enabled AI code reviews in CI:
	- Main cost drivers are review frequency and repeated submissions rather than patch size alone.
	- Stephen to explore extracting token metrics from the review tooling.
	- Workflow discussions covered: Series‑level vs per‑patch AI reviews.


^ permalink raw reply

* Re: [PATCH] maintainers: update RISC-V maintainer
From: Stanisław Kardach @ 2026-05-07  4:52 UTC (permalink / raw)
  To: sunyuechi; +Cc: dev, Thomas Monjalon
In-Reply-To: <7c10b2b5-71e9-4ef7-be7e-02da5bda7aaf@iscas.ac.cn>

Kind ping Thomas, would you be able to pull in this change so that Sun
can be unblocked?

On Thu, Apr 2, 2026 at 6:08 AM sunyuechi <sunyuechi@iscas.ac.cn> wrote:
>
> On 4/2/26 3:24 AM, Stanislaw Kardach wrote:
>
> > Change the RISC-V maintainer to Sun Yuechi and remove myself as
> > I'm unable to properly perform maintainer duties due to time
> > constraints.
> >
> > Signed-off-by: Stanislaw Kardach <stanislaw.kardach@gmail.com>
> > ---
> >   MAINTAINERS | 3 +--
> >   1 file changed, 1 insertion(+), 2 deletions(-)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 0f5539f851..eeb55951b4 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -334,11 +334,10 @@ F: examples/*/*_altivec.*
> >   F: examples/common/altivec/
> >
> >   RISC-V
> > -M: Stanisław Kardach <stanislaw.kardach@gmail.com>
> > +M: Sun Yuechi <sunyuechi@iscas.ac.cn>
> >   F: config/riscv/
> >   F: doc/guides/linux_gsg/cross_build_dpdk_for_riscv.rst
> >   F: lib/eal/riscv/
> > -M: Sun Yuechi <sunyuechi@iscas.ac.cn>
> >   F: lib/**/*rvv*
> >
> >   Intel x86
> Acked-by: Sun Yuechi <sunyuechi@iscas.ac.cn>
>

^ permalink raw reply

* [PATCH v6 4/4] examples/ptpclient: use shared PTP library definitions
From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507101314.2456467-1-rajesh3.kumar@intel.com>

Update ptpclient example to use IEEE 1588 PTP definitions from lib/net:
  - Replace PTP_PROTOCOL macro with standard RTE_ETHER_TYPE_1588
  - Remove local duplicate PTP header definitions
  - Add net library dependency in meson.build
  - Leverage rte_ptp.h inline helpers for header access

This aligns with DPDK library patterns and reduces code duplication.
The example remains compatible with standard linuxptp (ptp4l) tooling
for time synchronization.

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 examples/ptpclient/meson.build | 1 +
 examples/ptpclient/ptpclient.c | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/examples/ptpclient/meson.build b/examples/ptpclient/meson.build
index 2e9b7625fc..9087c987d5 100644
--- a/examples/ptpclient/meson.build
+++ b/examples/ptpclient/meson.build
@@ -7,6 +7,7 @@
 # DPDK instance, use 'make'
 
 allow_experimental_apis = true
+deps += ['net']
 sources = files(
         'ptpclient.c',
 )
diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c
index 174ca5dd70..ec6f316139 100644
--- a/examples/ptpclient/ptpclient.c
+++ b/examples/ptpclient/ptpclient.c
@@ -441,7 +441,7 @@ parse_fup(struct ptpv2_time_receiver_ordinary *ptp_data)
 		/* Set multicast address 01-1B-19-00-00-00. */
 		rte_ether_addr_copy(&eth_multicast, &eth_hdr->dst_addr);
 
-		eth_hdr->ether_type = htons(PTP_PROTOCOL);
+		eth_hdr->ether_type = htons(RTE_ETHER_TYPE_1588);
 		req_msg = rte_pktmbuf_mtod_offset(created_pkt,
 			struct delay_req_msg *, sizeof(struct
 			rte_ether_hdr));
@@ -582,7 +582,7 @@ parse_ptp_frames(uint16_t portid, struct rte_mbuf *m) {
 	eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
 	eth_type = rte_be_to_cpu_16(eth_hdr->ether_type);
 
-	if (eth_type == PTP_PROTOCOL) {
+	if (eth_type == RTE_ETHER_TYPE_1588) {
 		ptp_data.m = m;
 		ptp_data.portid = portid;
 		ptp_hdr = rte_pktmbuf_mtod_offset(m, struct ptp_header *,
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 3/4] doc: update release notes for PTP protocol library
From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507101314.2456467-1-rajesh3.kumar@intel.com>

Update release notes with IEEE 1588 PTP additions:
  - PTP protocol definitions in lib/net/rte_ptp.h (header-only library
    with inline helpers and wire-format structures)
  - PTP software relay example application (ptp_tap_relay_sw)

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 doc/guides/rel_notes/release_26_07.rst | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index f012d47a4b..c64e6e141b 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -63,6 +63,18 @@ New Features
     ``rte_eal_init`` and the application is responsible for probing each device,
   * ``--auto-probing`` enables the initial bus probing, which is the current default behavior.
 
+* **Added PTP protocol definitions (rte_ptp.h).**
+
+  Added IEEE 1588 Precision Time Protocol header structures, constants,
+  and inline helpers to ``lib/net/rte_ptp.h``.  Provides wire-format
+  structures with endian-annotated types and correctionField manipulation
+  for Transparent Clock implementations.
+
+* **Added PTP software relay example application.**
+
+  Added a new example application ``ptp_tap_relay_sw`` demonstrating a
+  software PTP Transparent Clock relay between a DPDK port and a kernel
+  TAP interface.
 
 Removed Items
 -------------
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 2/4] examples/ptp_tap_relay_sw: add PTP software transparent clock relay
From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507101314.2456467-1-rajesh3.kumar@intel.com>

Add a new example application demonstrating a software PTP Transparent
Clock relay between a DPDK-bound physical NIC and a Linux kernel TAP
virtual interface.

The relay uses software timestamps (CLOCK_MONOTONIC) to measure residence
time and accumulates it into the PTP correctionField per IEEE 1588-2019
§10.2, enabling synchronized time distribution via standard linuxptp
(ptp4l) on both sides.

Features:
  - Handles L2, VLAN/QinQ, and UDP/IPv4/IPv6 PTP encapsulations
  - Supports PTP v2 event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp)
  - Two-pass burst processing: classify then timestamp immediately before TX
  - Unmodified Linux kernel and stock DPDK (no kernel patches required)
  - Bidirectional relay: PHY ↔ TAP

Includes:
  - ptp_tap_relay_sw.c: Main relay logic with burst processing
  - ptp_parse.h: Local DPI parser for PTP classification (not a library API)
  - Sample app guide with topology, command-line options, and example output

Uses lib/net/rte_ptp.h inline helpers for correctionField manipulation
and header parsing.

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst | 212 +++++++++
 examples/ptp_tap_relay_sw/Makefile            |  41 ++
 examples/ptp_tap_relay_sw/meson.build         |  13 +
 examples/ptp_tap_relay_sw/ptp_parse.h         | 211 +++++++++
 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c  | 432 ++++++++++++++++++
 5 files changed, 909 insertions(+)
 create mode 100644 doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
 create mode 100644 examples/ptp_tap_relay_sw/Makefile
 create mode 100644 examples/ptp_tap_relay_sw/meson.build
 create mode 100644 examples/ptp_tap_relay_sw/ptp_parse.h
 create mode 100644 examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c

diff --git a/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
new file mode 100644
index 0000000000..15727383c1
--- /dev/null
+++ b/doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
@@ -0,0 +1,212 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Intel Corporation.
+
+PTP Software Relay Sample Application
+======================================
+
+The PTP Software Relay sample application demonstrates how to build a
+minimal PTP Transparent Clock relay between a DPDK-bound physical NIC
+and a kernel TAP interface using **software timestamps only**.  It uses
+the PTP definitions from ``rte_ptp.h`` (in ``lib/net/``) together with a
+local packet parser.
+
+The application works with an unmodified Linux kernel and stock DPDK.
+
+For background on PTP see:
+`Precision Time Protocol
+<https://en.wikipedia.org/wiki/Precision_Time_Protocol>`_.
+
+
+Limitations
+-----------
+
+* Tested with L2 PTP (EtherType 0x88F7) on the wire.
+   The local parser also classifies VLAN/QinQ and UDP/IPv4/IPv6.
+* Only PTP v2 messages are processed.
+* Software timestamps have microsecond-class jitter; sub-microsecond
+  precision depends on system load and NIC-to-TAP forwarding latency.
+* The PTP time transmitter must be reachable on the physical NIC's L2 network.
+* Only one physical port and one TAP port are supported.
+
+
+How the Application Works
+-------------------------
+
+Topology
+~~~~~~~~
+
+::
+
+    PTP Time Transmitter  Physical NIC             TAP (kernel)
+    (ptp4l -H)  ──L2──  (DPDK vfio-pci)  ──────  dtap0
+                              │                      │
+                        ptp_tap_relay_sw            ptp4l -S
+                     (correctionField +=        (SW timestamps,
+                      residence time)           adjusts CLOCK_REALTIME)
+
+The relay sits between a DPDK-owned physical NIC and a kernel TAP
+virtual interface.  ``ptp4l`` runs on the TAP interface in software
+timestamp mode (``-S``) as a PTP time receiver.
+
+Packet Flow
+~~~~~~~~~~~
+
+1. The physical NIC receives PTP (and non-PTP) packets via DPDK RX.
+2. A software RX timestamp is recorded using
+   ``clock_gettime(CLOCK_MONOTONIC)``.
+3. Each packet is parsed to locate the PTP header.
+4. For PTP **event** messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp),
+   a TX software timestamp is taken just before transmission.
+5. The residence time (``tx_ts − rx_ts``) is added to the PTP
+   ``correctionField`` via ``rte_ptp_add_correction()`` — standard
+   IEEE 1588-2019 Transparent Clock behaviour (§10.2).
+6. Packets are forwarded bidirectionally:
+
+   * PHY → TAP  (network → ptp4l)
+   * TAP → PHY  (ptp4l → network)
+
+A two-pass design is used: first all packets are classified and PTP
+header pointers saved, then a single TX timestamp is taken immediately
+before applying corrections and calling ``rte_eth_tx_burst()``.
+This minimises the gap between the measured timestamp and the actual
+wire egress.
+
+
+Compiling the Application
+-------------------------
+
+To compile the sample application see :doc:`compiling`.
+
+The application is located in the ``ptp_tap_relay_sw`` sub-directory.
+
+.. note::
+
+   The application uses ``rte_ptp.h`` from ``lib/net/`` (built by default)
+   and a local ``ptp_parse.h`` header for packet classification.
+
+
+Running the Application
+-----------------------
+
+Prerequisites
+~~~~~~~~~~~~~
+
+* A PTP-capable physical NIC bound to DPDK (e.g. via ``vfio-pci``).
+* ``linuxptp`` (``ptp4l``) installed on the system.
+* A PTP time transmitter reachable on the same L2 network.
+
+Start the relay
+~~~~~~~~~~~~~~~~
+
+.. code-block:: console
+
+   ./<build_dir>/examples/dpdk-ptp_tap_relay_sw \
+       -l 18-19 -a 0000:cc:00.1 --vdev=net_tap0,iface=dtap0 -- \
+       -p 0 -t 1 -T 10
+
+Command-line Options
+~~~~~~~~~~~~~~~~~~~~
+
+* ``-p PORT`` — Physical NIC port ID (default: 0).
+* ``-t PORT`` — TAP port ID (default: 1).
+* ``-T SECS`` — Statistics print interval in seconds (default: 10).
+
+Start PTP time transmitter
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On a separate terminal or remote host, start ``ptp4l`` as time
+transmitter with hardware timestamps on the physical NIC:
+
+.. code-block:: console
+
+   ptp4l -i <iface> -m -2 -H --serverOnly=1 \
+       --logSyncInterval=-4 --logMinDelayReqInterval=-4
+
+Start PTP time receiver
+~~~~~~~~~~~~~~~~~~~~~~~
+
+On the TAP interface, start ``ptp4l`` in software timestamp mode:
+
+.. code-block:: console
+
+   ptp4l -i dtap0 -m -2 -s -S \
+       --delay_filter=moving_median --delay_filter_length=10
+
+The time receiver will enter UNCALIBRATED state for approximately 60
+seconds while the PI servo estimates the frequency offset, then step
+the clock and enter time-receiver (synchronized) state.
+Steady-state RMS offset of 500–1000 ns is typical on a lightly loaded
+system with a hardware-timestamped time transmitter.
+
+Example Output
+~~~~~~~~~~~~~~
+
+Relay statistics printed every ``-T`` seconds:
+
+::
+
+   [PTP-SW] === Statistics ===
+   [PTP-SW]   PHY RX total:   5646
+   [PTP-SW]   PHY RX PTP:     5598
+   [PTP-SW]   TAP TX:         5646
+   [PTP-SW]   TAP RX total:   1800
+   [PTP-SW]   TAP RX PTP:     1788
+   [PTP-SW]   PHY TX:         1800
+   [PTP-SW]   Corrections:    3635
+
+Time receiver ``ptp4l`` output after convergence:
+
+::
+
+   ptp4l[451534.520]: rms  630 max 1166 freq -44365 +/- 100 delay 37668 +/-  71
+   ptp4l[451539.525]: rms  602 max 1177 freq -44339 +/- 119 delay 37517 +/-  43
+   ptp4l[451544.530]: rms  535 max 1194 freq -44345 +/- 103 delay 37410 +/-  81
+
+
+Code Explanation
+----------------
+
+The following sections explain the main components of the application.
+
+Relay Burst Function
+~~~~~~~~~~~~~~~~~~~~
+
+The core relay logic is in ``relay_burst()``, which handles one direction
+(PHY→TAP or TAP→PHY) per call:
+
+**Pass 1 — Classify:**
+
+For each received packet, ``ptp_hdr_find()`` locates the PTP header
+(if present).  For event messages, the header pointer is saved for the
+second pass.
+
+**Pass 2 — Timestamp and correct:**
+
+A single software TX timestamp is taken via
+``clock_gettime(CLOCK_MONOTONIC)``.  The residence time
+(``tx_ts − rx_ts``) is added to each saved PTP header's
+``correctionField`` using ``rte_ptp_add_correction()``.
+The burst is then transmitted with ``rte_eth_tx_burst()``.
+
+Main Loop
+~~~~~~~~~
+
+The ``relay_loop()`` function polls both directions in a tight loop:
+
+.. code-block:: c
+
+   while (!force_quit) {
+       relay_burst(phy_port, tap_port, ...);   /* PHY → TAP */
+       relay_burst(tap_port, phy_port, ...);   /* TAP → PHY */
+   }
+
+Statistics are printed at the interval specified by ``-T``.
+
+Timestamp Source
+~~~~~~~~~~~~~~~~
+
+``CLOCK_MONOTONIC`` is used rather than ``CLOCK_REALTIME`` because
+the PTP time receiver's servo continuously adjusts ``CLOCK_REALTIME``.
+Using ``CLOCK_REALTIME`` would corrupt residence time measurements
+during clock stepping or frequency slewing.  ``CLOCK_MONOTONIC`` is
+portable across Linux and FreeBSD.
diff --git a/examples/ptp_tap_relay_sw/Makefile b/examples/ptp_tap_relay_sw/Makefile
new file mode 100644
index 0000000000..fd178f46ae
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/Makefile
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Intel Corporation
+
+# binary name
+APP = dpdk-ptp_tap_relay_sw
+
+# all source are stored in SRCS-y
+SRCS-y := ptp_tap_relay_sw.c
+
+PKGCONF ?= pkg-config
+
+# Build using pkg-config variables if possible
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+	ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+	ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+	@mkdir -p $@
+
+.PHONY: clean
+clean:
+	rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+	test -d build && rmdir -p build || true
diff --git a/examples/ptp_tap_relay_sw/meson.build b/examples/ptp_tap_relay_sw/meson.build
new file mode 100644
index 0000000000..34a4d86439
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Intel Corporation
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+sources = files(
+        'ptp_tap_relay_sw.c',
+)
+deps += ['net']
+cflags += no_shadow_cflag
diff --git a/examples/ptp_tap_relay_sw/ptp_parse.h b/examples/ptp_tap_relay_sw/ptp_parse.h
new file mode 100644
index 0000000000..db0dcfe5c1
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/ptp_parse.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ *
+ * PTP packet parser — locates PTP headers through L2, VLAN, and UDP
+ * encapsulations. This is a DPI helper for use within example
+ * applications; it does not belong in the core library.
+ */
+
+#ifndef _PTP_PARSE_H_
+#define _PTP_PARSE_H_
+
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_udp.h>
+#include <rte_ptp.h>
+
+/** Not a PTP packet. */
+#define PTP_MSGTYPE_INVALID  (-1)
+
+/**
+ * Locate the PTP header within a packet.
+ *
+ * Handles L2 (EtherType 0x88F7), VLAN-tagged L2 (single/double,
+ * TPIDs 0x8100/0x88A8), PTP over UDP/IPv4, PTP over UDP/IPv6,
+ * and VLAN-tagged UDP variants.
+ *
+ * @param m
+ *   Pointer to the mbuf.
+ * @return
+ *   Pointer to the PTP header, or NULL if not a PTP packet.
+ */
+static inline struct rte_ptp_hdr *
+ptp_hdr_find(const struct rte_mbuf *m)
+{
+	const struct rte_ether_hdr *eth;
+	uint16_t ether_type;
+	uint32_t offset;
+
+	if (rte_pktmbuf_data_len(m) < sizeof(struct rte_ether_hdr))
+		return NULL;
+
+	eth = rte_pktmbuf_mtod(m, const struct rte_ether_hdr *);
+	ether_type = rte_be_to_cpu_16(eth->ether_type);
+	offset = sizeof(struct rte_ether_hdr);
+
+	/* Strip VLAN / QinQ tags */
+	if (ether_type == RTE_ETHER_TYPE_VLAN ||
+	    ether_type == RTE_ETHER_TYPE_QINQ) {
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_vlan_hdr))
+			return NULL;
+		const struct rte_vlan_hdr *vlan =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_vlan_hdr *, offset);
+		ether_type = rte_be_to_cpu_16(vlan->eth_proto);
+		offset += sizeof(struct rte_vlan_hdr);
+
+		/* Second tag (QinQ inner or stacked VLAN) */
+		if (ether_type == RTE_ETHER_TYPE_VLAN ||
+		    ether_type == RTE_ETHER_TYPE_QINQ) {
+			if (rte_pktmbuf_data_len(m) <
+			    offset + sizeof(struct rte_vlan_hdr))
+				return NULL;
+			vlan = rte_pktmbuf_mtod_offset(m,
+				const struct rte_vlan_hdr *, offset);
+			ether_type = rte_be_to_cpu_16(vlan->eth_proto);
+			offset += sizeof(struct rte_vlan_hdr);
+		}
+	}
+
+	/* L2 PTP: EtherType 0x88F7 */
+	if (ether_type == RTE_ETHER_TYPE_1588) {
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	/* PTP over UDP/IPv4 */
+	if (ether_type == RTE_ETHER_TYPE_IPV4) {
+		const struct rte_ipv4_hdr *iph;
+		uint16_t ihl;
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ipv4_hdr))
+			return NULL;
+
+		iph = rte_pktmbuf_mtod_offset(m,
+			const struct rte_ipv4_hdr *, offset);
+		if (iph->next_proto_id != IPPROTO_UDP)
+			return NULL;
+
+		ihl = (iph->version_ihl & 0x0F) * 4;
+		if (ihl < 20)
+			return NULL;
+		offset += ihl;
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr))
+			return NULL;
+
+		const struct rte_udp_hdr *udp =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_udp_hdr *, offset);
+		uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port);
+
+		if (dst_port != RTE_PTP_EVENT_PORT &&
+		    dst_port != RTE_PTP_GENERAL_PORT)
+			return NULL;
+
+		offset += sizeof(struct rte_udp_hdr);
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	/* PTP over UDP/IPv6 */
+	if (ether_type == RTE_ETHER_TYPE_IPV6) {
+		const struct rte_ipv6_hdr *ip6h;
+
+		if (rte_pktmbuf_data_len(m) <
+		    offset + sizeof(struct rte_ipv6_hdr))
+			return NULL;
+
+		ip6h = rte_pktmbuf_mtod_offset(m,
+			const struct rte_ipv6_hdr *, offset);
+		if (ip6h->proto != IPPROTO_UDP)
+			return NULL;
+
+		offset += sizeof(struct rte_ipv6_hdr);
+
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_udp_hdr))
+			return NULL;
+
+		const struct rte_udp_hdr *udp =
+			rte_pktmbuf_mtod_offset(m,
+				const struct rte_udp_hdr *, offset);
+		uint16_t dst_port = rte_be_to_cpu_16(udp->dst_port);
+
+		if (dst_port != RTE_PTP_EVENT_PORT &&
+		    dst_port != RTE_PTP_GENERAL_PORT)
+			return NULL;
+
+		offset += sizeof(struct rte_udp_hdr);
+		if (rte_pktmbuf_data_len(m) < offset + sizeof(struct rte_ptp_hdr))
+			return NULL;
+
+		return rte_pktmbuf_mtod_offset(m,
+			struct rte_ptp_hdr *, offset);
+	}
+
+	return NULL;
+}
+
+/**
+ * Classify a packet as PTP and return the message type.
+ *
+ * @param m
+ *   Pointer to the mbuf to classify.
+ * @return
+ *   PTP message type (0x0-0xF) on success, PTP_MSGTYPE_INVALID (-1)
+ *   if the packet is not PTP.
+ */
+static inline int
+ptp_classify(const struct rte_mbuf *m)
+{
+	struct rte_ptp_hdr *hdr = ptp_hdr_find(m);
+
+	if (hdr == NULL)
+		return PTP_MSGTYPE_INVALID;
+
+	return rte_ptp_msg_type(hdr);
+}
+
+/** PTP message type name table. */
+static const char * const ptp_msg_names[] = {
+	[RTE_PTP_MSGTYPE_SYNC]           = "Sync",
+	[RTE_PTP_MSGTYPE_DELAY_REQ]      = "Delay_Req",
+	[RTE_PTP_MSGTYPE_PDELAY_REQ]     = "PDelay_Req",
+	[RTE_PTP_MSGTYPE_PDELAY_RESP]    = "PDelay_Resp",
+	[0x4]                            = "Reserved_4",
+	[0x5]                            = "Reserved_5",
+	[0x6]                            = "Reserved_6",
+	[0x7]                            = "Reserved_7",
+	[RTE_PTP_MSGTYPE_FOLLOW_UP]      = "Follow_Up",
+	[RTE_PTP_MSGTYPE_DELAY_RESP]     = "Delay_Resp",
+	[RTE_PTP_MSGTYPE_PDELAY_RESP_FU] = "PDelay_Resp_Follow_Up",
+	[RTE_PTP_MSGTYPE_ANNOUNCE]       = "Announce",
+	[RTE_PTP_MSGTYPE_SIGNALING]      = "Signaling",
+	[RTE_PTP_MSGTYPE_MANAGEMENT]     = "Management",
+	[0xE]                            = "Reserved_E",
+	[0xF]                            = "Reserved_F",
+};
+
+/**
+ * Get a human-readable name for a PTP message type.
+ *
+ * @param msg_type
+ *   PTP message type (0x0-0xF or PTP_MSGTYPE_INVALID).
+ * @return
+ *   Static string with the message type name.
+ */
+static inline const char *
+ptp_msg_type_str(int msg_type)
+{
+	if (msg_type < 0 || msg_type > 0xF)
+		return "Not_PTP";
+	return ptp_msg_names[msg_type];
+}
+
+#endif /* _PTP_PARSE_H_ */
diff --git a/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
new file mode 100644
index 0000000000..998df2ac3b
--- /dev/null
+++ b/examples/ptp_tap_relay_sw/ptp_tap_relay_sw.c
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+/*
+ * PTP Software Relay
+ *
+ * A minimal PTP relay between a DPDK-bound physical NIC and a kernel
+ * TAP interface using software timestamps only.
+ *
+ * How it works:
+ *   1. Physical NIC receives PTP (and non-PTP) packets via DPDK RX.
+ *   2. For PTP event messages (Sync, Delay_Req, PDelay_Req, PDelay_Resp)
+ *      the relay records an RX software timestamp (clock_gettime).
+ *   3. Just before TX on the other side it records a TX software timestamp.
+ *   4. The relay residence time (tx_ts − rx_ts) is added to the PTP
+ *      correctionField via rte_ptp_add_correction() — standard
+ *      Transparent Clock behaviour (IEEE 1588-2019 §10.2).
+ *   5. Packets are forwarded bi-directionally:
+ *        PHY → TAP   (network → ptp4l)
+ *        TAP → PHY   (ptp4l → network)
+ *
+ * ptp4l runs in software-timestamping mode on the TAP interface:
+ *
+ *   ptp4l -i dtap0 -m -s -S   # -S = software timestamps
+ *
+ * Topology:
+ *
+ *   Time Transmitter (remote) ──L2── Physical NIC (DPDK)
+ *                                      │
+ *                                PTP SW Relay  ← correctionField update
+ *                                      │
+ *                                TAP (kernel) ── ptp4l -S (time receiver)
+ *
+ * Usage:
+ *   dpdk-ptp_tap_relay_sw -l 0-1 --vdev=net_tap0,iface=dtap0 -- \
+ *       -p 0 -t 1
+ *
+ * Parameters:
+ *   -p PORT    Physical NIC port ID (default: 0)
+ *   -t PORT    TAP port ID (default: 1)
+ *   -T SECS    Stats print interval in seconds (default: 10)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <rte_eal.h>
+#include <rte_ethdev.h>
+#include <rte_mbuf.h>
+#include <rte_cycles.h>
+#include <rte_lcore.h>
+
+#include "ptp_parse.h"
+
+/* Ring sizes */
+#define RX_RING_SIZE  1024
+#define TX_RING_SIZE  1024
+
+/* Mempool */
+#define NUM_MBUFS     8191
+#define MBUF_CACHE    250
+#define BURST_SIZE    32
+
+#define NSEC_PER_SEC  1000000000ULL
+
+/* Logging helpers */
+#define LOG_INFO(fmt, ...) \
+	fprintf(stdout, "[PTP-SW] " fmt "\n", ##__VA_ARGS__)
+#define LOG_ERR(fmt, ...) \
+	fprintf(stderr, "[PTP-SW ERROR] " fmt "\n", ##__VA_ARGS__)
+
+static volatile bool force_quit;
+
+/* Port IDs */
+static uint16_t phy_port;
+static uint16_t tap_port = 1;
+static unsigned int stats_interval = 10;  /* seconds */
+
+/* Statistics */
+static struct {
+	uint64_t phy_rx;        /* total packets from PHY */
+	uint64_t phy_rx_ptp;    /* PTP packets from PHY */
+	uint64_t tap_tx;        /* packets forwarded to TAP */
+	uint64_t tap_rx;        /* total packets from TAP */
+	uint64_t tap_rx_ptp;    /* PTP packets from TAP */
+	uint64_t phy_tx;        /* packets forwarded to PHY */
+	uint64_t corrections;   /* correctionField updates */
+} stats;
+
+static void
+signal_handler(int signum)
+{
+	if (signum == SIGINT || signum == SIGTERM) {
+		LOG_INFO("Signal %d received, shutting down...", signum);
+		force_quit = true;
+	}
+}
+
+/* Helpers */
+
+/* Read monotonic clock in nanoseconds (for residence time). */
+static inline uint64_t
+sw_timestamp_ns(void)
+{
+	struct timespec ts;
+
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec;
+}
+
+/* Port Init */
+
+static int
+port_init(uint16_t port, struct rte_mempool *mp)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_dev_info dev_info;
+	uint16_t nb_rxd = RX_RING_SIZE;
+	uint16_t nb_txd = TX_RING_SIZE;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(port_conf));
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		LOG_ERR("rte_eth_dev_info_get(port %u) failed: %d", port, ret);
+		return ret;
+	}
+
+	if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)
+		port_conf.txmode.offloads |=
+			RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret != 0)
+		return ret;
+
+	ret = rte_eth_dev_adjust_nb_rx_tx_desc(port, &nb_rxd, &nb_txd);
+	if (ret != 0)
+		return ret;
+
+	ret = rte_eth_rx_queue_setup(port, 0, nb_rxd,
+			rte_eth_dev_socket_id(port), NULL, mp);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_tx_queue_setup(port, 0, nb_txd,
+			rte_eth_dev_socket_id(port), NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0)
+		return ret;
+
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		LOG_ERR("Failed to enable promiscuous on port %u: %s",
+			port, rte_strerror(-ret));
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Relay one direction */
+
+/*
+ * Forward packets from src_port to dst_port.
+ * For PTP event messages, record SW timestamps around the
+ * relay path and add the residence time to the correctionField.
+ *
+ * This implements a Transparent Clock (IEEE 1588-2019 §10.2):
+ *   correctionField += (t_egress − t_ingress)
+ *
+ * Note: a single rx_ts / tx_ts pair is used for the entire burst.
+ * At typical PTP rates (logSyncInterval >= -4, i.e. <= 16 pkt/s)
+ * bursts contain at most one packet, so this is exact.  At higher
+ * rates, early packets in a burst are slightly under-corrected and
+ * late ones over-corrected by up to one poll-loop iteration.
+ */
+static void
+relay_burst(uint16_t src_port, uint16_t dst_port,
+	    uint64_t *rx_cnt, uint64_t *rx_ptp_cnt,
+	    uint64_t *tx_cnt, uint64_t *corr_cnt)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+	struct rte_ptp_hdr *ptp_hdrs[BURST_SIZE];
+	uint64_t rx_ts;
+	uint16_t nb_rx, nb_tx, i;
+
+	nb_rx = rte_eth_rx_burst(src_port, 0, bufs, BURST_SIZE);
+	if (nb_rx == 0)
+		return;
+
+	/* Record a single RX software timestamp for the whole burst.
+	 * All packets in one burst arrived at essentially the same instant
+	 * from rte_eth_rx_burst()'s perspective.
+	 */
+	rx_ts = sw_timestamp_ns();
+
+	*rx_cnt += nb_rx;
+
+	/*
+	 * Pass 1: Parse each packet once and remember PTP event headers.
+	 * This avoids taking the TX timestamp too early — we want it as
+	 * close to the actual rte_eth_tx_burst() call as possible.
+	 */
+	memset(ptp_hdrs, 0, sizeof(ptp_hdrs[0]) * nb_rx);
+	for (i = 0; i < nb_rx; i++) {
+		struct rte_ptp_hdr *hdr = ptp_hdr_find(bufs[i]);
+
+		if (hdr == NULL)
+			continue;
+
+		(*rx_ptp_cnt)++;
+
+		/* Only event messages carry timestamps that need correction */
+		if (!rte_ptp_is_event(rte_ptp_msg_type(hdr)))
+			continue;
+
+		ptp_hdrs[i] = hdr;
+	}
+
+	/*
+	 * Pass 2: Take a single TX timestamp right before transmission.
+	 * This minimises the gap between the measured tx_ts and the
+	 * actual kernel write inside rte_eth_tx_burst(), giving the
+	 * most accurate residence time we can achieve with SW timestamps.
+	 *
+	 * residence_time = tx_ts − rx_ts
+	 *
+	 * Remaining untracked delays:
+	 *   - Pre-RX:  NIC DMA → rx_burst return  (~1-5 µs, unavoidable)
+	 *   - Post-TX:  tx_ts → kernel TAP write   (~1-2 µs)
+	 * Both are symmetric for Sync and Delay_Req so they largely
+	 * cancel in the ptp4l offset calculation.
+	 */
+	uint64_t tx_ts = sw_timestamp_ns();
+	int64_t residence_ns = (int64_t)(tx_ts - rx_ts);
+
+	for (i = 0; i < nb_rx; i++) {
+		if (ptp_hdrs[i] == NULL)
+			continue;
+		rte_ptp_add_correction(ptp_hdrs[i], residence_ns);
+		(*corr_cnt)++;
+	}
+
+	/* Forward the burst */
+	nb_tx = rte_eth_tx_burst(dst_port, 0, bufs, nb_rx);
+	*tx_cnt += nb_tx;
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < nb_rx; i++)
+		rte_pktmbuf_free(bufs[i]);
+}
+
+/* Print statistics */
+
+static void
+print_stats(void)
+{
+	LOG_INFO("=== Statistics ===");
+	LOG_INFO("  PHY RX total:   %"PRIu64, stats.phy_rx);
+	LOG_INFO("  PHY RX PTP:     %"PRIu64, stats.phy_rx_ptp);
+	LOG_INFO("  TAP TX:         %"PRIu64, stats.tap_tx);
+	LOG_INFO("  TAP RX total:   %"PRIu64, stats.tap_rx);
+	LOG_INFO("  TAP RX PTP:     %"PRIu64, stats.tap_rx_ptp);
+	LOG_INFO("  PHY TX:         %"PRIu64, stats.phy_tx);
+	LOG_INFO("  Corrections:    %"PRIu64, stats.corrections);
+}
+
+/* Main relay loop */
+
+static int
+relay_loop(__rte_unused void *arg)
+{
+	uint64_t last_stats = rte_rdtsc();
+	uint64_t stats_tsc = rte_get_tsc_hz() * stats_interval;
+
+	LOG_INFO("Relay loop started on lcore %u", rte_lcore_id());
+	LOG_INFO("  PHY port %u  <-->  TAP port %u", phy_port, tap_port);
+	LOG_INFO("  Correction field updates: enabled for event messages");
+
+	while (!force_quit) {
+		/* PHY → TAP */
+		relay_burst(phy_port, tap_port,
+			    &stats.phy_rx, &stats.phy_rx_ptp,
+			    &stats.tap_tx, &stats.corrections);
+
+		/* TAP → PHY */
+		relay_burst(tap_port, phy_port,
+			    &stats.tap_rx, &stats.tap_rx_ptp,
+			    &stats.phy_tx, &stats.corrections);
+
+		/* Periodic stats */
+		if (rte_rdtsc() - last_stats > stats_tsc) {
+			print_stats();
+			last_stats = rte_rdtsc();
+		}
+	}
+
+	print_stats();
+	return 0;
+}
+
+/* Argument parsing */
+
+static void
+usage(const char *prog)
+{
+	fprintf(stderr,
+		"Usage: %s [EAL options] -- [options]\n"
+		"  -p PORT   Physical NIC port ID (default: 0)\n"
+		"  -t PORT   TAP port ID (default: 1)\n"
+		"  -T SECS   Stats interval in seconds (default: 10)\n"
+		"\n"
+		"Example:\n"
+		"  %s -l 0-1 --vdev=net_tap0,iface=dtap0 -- -p 0 -t 1\n"
+		"\n"
+		"Then run ptp4l with software timestamps:\n"
+		"  ptp4l -i dtap0 -m -s -S\n",
+		prog, prog);
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+	int opt;
+
+	while ((opt = getopt(argc, argv, "p:t:T:h")) != -1) {
+		switch (opt) {
+		case 'p':
+			phy_port = (uint16_t)atoi(optarg);
+			break;
+		case 't':
+			tap_port = (uint16_t)atoi(optarg);
+			break;
+		case 'T':
+			stats_interval = (unsigned int)atoi(optarg);
+			break;
+		case 'h':
+		default:
+			usage(argv[0]);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/* Main */
+
+int
+main(int argc, char **argv)
+{
+	struct rte_mempool *mp;
+	uint16_t nb_ports;
+	int ret;
+
+	/* EAL init */
+	ret = rte_eal_init(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "EAL init failed\n");
+	argc -= ret;
+	argv += ret;
+
+	/* App args */
+	ret = parse_args(argc, argv);
+	if (ret < 0)
+		rte_exit(EXIT_FAILURE, "Invalid arguments\n");
+
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	nb_ports = rte_eth_dev_count_avail();
+	if (nb_ports < 2)
+		rte_exit(EXIT_FAILURE,
+			 "Need at least 2 ports (PHY + TAP).\n"
+			 "Use --vdev=net_tap0,iface=dtap0\n");
+
+	if (!rte_eth_dev_is_valid_port(phy_port))
+		rte_exit(EXIT_FAILURE, "Invalid PHY port %u\n", phy_port);
+	if (!rte_eth_dev_is_valid_port(tap_port))
+		rte_exit(EXIT_FAILURE, "Invalid TAP port %u\n", tap_port);
+
+	mp = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
+				     MBUF_CACHE, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	if (mp == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");
+
+	LOG_INFO("Initializing PHY port %u...", phy_port);
+	ret = port_init(phy_port, mp);
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "Cannot init PHY port %u (%d)\n",
+			 phy_port, ret);
+
+	LOG_INFO("Initializing TAP port %u...", tap_port);
+	ret = port_init(tap_port, mp);
+	if (ret != 0)
+		rte_exit(EXIT_FAILURE, "Cannot init TAP port %u (%d)\n",
+			 tap_port, ret);
+
+	LOG_INFO("PTP Software Relay ready");
+	LOG_INFO("  PHY port:     %u", phy_port);
+	LOG_INFO("  TAP port:     %u", tap_port);
+	LOG_INFO("  Stats every:  %u seconds", stats_interval);
+	LOG_INFO("  Correction:   Transparent Clock (SW timestamps)");
+	LOG_INFO("");
+	LOG_INFO("Run ptp4l:  ptp4l -i dtap0 -m -s -S");
+
+	/* Run relay on main lcore */
+	relay_loop(NULL);
+
+	/* Cleanup */
+	LOG_INFO("Stopping ports...");
+	rte_eth_dev_stop(phy_port);
+	rte_eth_dev_stop(tap_port);
+	rte_eth_dev_close(phy_port);
+	rte_eth_dev_close(tap_port);
+	rte_eal_cleanup();
+
+	return 0;
+}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 1/4] lib/net: add IEEE 1588 PTP v2 protocol header definitions
From: Rajesh Kumar @ 2026-05-07 10:13 UTC (permalink / raw)
  To: dev; +Cc: bruce.richardson, aman.deep.singh, stephen, mb, Rajesh Kumar
In-Reply-To: <20260507101314.2456467-1-rajesh3.kumar@intel.com>

Add PTP (Precision Time Protocol) header structures and inline helper
functions to lib/net following DPDK conventions for protocol libraries
(similar to rte_tcp.h, rte_ip.h).

Provides wire-format structures with endian-annotated types:
  - rte_ptp_port_id: 10-byte port identity with EUI-64 clock ID
  - rte_ptp_hdr: 34-byte common message header with correctionField
  - rte_ptp_timestamp: 10-byte nanosecond-precision timestamp

PTP message type constants for all IEEE 1588-2019 message types
(Sync, Delay_Req, Announce, Management, etc.) and flag field bits
(two-step, unicast, leap indicator).

Inline accessor and utility functions for performance:
  - rte_ptp_msg_type(), rte_ptp_version(), rte_ptp_domain()
  - rte_ptp_seq_id(), rte_ptp_is_event(), rte_ptp_is_two_step()
  - rte_ptp_correction_ns(), rte_ptp_add_correction()
  - rte_ptp_timestamp_to_ns() for timestamp conversion

Supports all PTP encapsulations: L2 (EtherType 0x88F7), VLAN/QinQ,
UDP/IPv4, and UDP/IPv6 (ports 319/320).

All inline functions — zero function call overhead, suitable for
real-time packet processing.

Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
 MAINTAINERS         |   6 +
 lib/net/meson.build |   1 +
 lib/net/rte_ptp.h   | 264 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 271 insertions(+)
 create mode 100644 lib/net/rte_ptp.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 0f5539f851..da31ada871 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1665,6 +1665,12 @@ F: doc/guides/prog_guide/ipsec_lib.rst
 M: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
 F: app/test-sad/
 
+PTP - lib/net
+M: Rajesh Kumar <rajesh3.kumar@intel.com>
+F: lib/net/rte_ptp.h
+F: examples/ptp_tap_relay_sw/
+F: doc/guides/sample_app_ug/ptp_tap_relay_sw.rst
+
 PDCP - EXPERIMENTAL
 M: Anoob Joseph <anoobj@marvell.com>
 M: Volodymyr Fialko <vfialko@marvell.com>
diff --git a/lib/net/meson.build b/lib/net/meson.build
index 3fad5edc5b..63d13719f3 100644
--- a/lib/net/meson.build
+++ b/lib/net/meson.build
@@ -28,6 +28,7 @@ headers = files(
         'rte_geneve.h',
         'rte_l2tpv2.h',
         'rte_ppp.h',
+        'rte_ptp.h',
         'rte_ib.h',
 )
 
diff --git a/lib/net/rte_ptp.h b/lib/net/rte_ptp.h
new file mode 100644
index 0000000000..da7b29ab0e
--- /dev/null
+++ b/lib/net/rte_ptp.h
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _RTE_PTP_H_
+#define _RTE_PTP_H_
+
+/**
+ * @file
+ *
+ * PTP (IEEE 1588) protocol definitions
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <rte_byteorder.h>
+#include <rte_common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ============================================================
+ *  PTP Constants
+ * ============================================================ */
+
+/** PTP over UDP event port (Sync, Delay_Req, PDelay_Req, PDelay_Resp). */
+#define RTE_PTP_EVENT_PORT        319
+
+/** PTP over UDP general port (Follow_Up, Delay_Resp, Announce, etc.). */
+#define RTE_PTP_GENERAL_PORT      320
+
+/** PTP multicast MAC address: 01:1B:19:00:00:00. */
+#define RTE_PTP_MULTICAST_MAC     { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 }
+
+/** PTP peer delay multicast MAC: 01:80:C2:00:00:0E. */
+#define RTE_PTP_PDELAY_MULTICAST_MAC { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E }
+
+/* ============================================================
+ *  PTP Message Types (IEEE 1588-2019 Table 36)
+ * ============================================================ */
+
+#define RTE_PTP_MSGTYPE_SYNC            0x0  /**< Sync (event). */
+#define RTE_PTP_MSGTYPE_DELAY_REQ       0x1  /**< Delay_Req (event). */
+#define RTE_PTP_MSGTYPE_PDELAY_REQ      0x2  /**< Peer_Delay_Req (event). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP     0x3  /**< Peer_Delay_Resp (event). */
+#define RTE_PTP_MSGTYPE_FOLLOW_UP       0x8  /**< Follow_Up (general). */
+#define RTE_PTP_MSGTYPE_DELAY_RESP      0x9  /**< Delay_Resp (general). */
+#define RTE_PTP_MSGTYPE_PDELAY_RESP_FU  0xA  /**< Peer_Delay_Resp_Follow_Up. */
+#define RTE_PTP_MSGTYPE_ANNOUNCE        0xB  /**< Announce (general). */
+#define RTE_PTP_MSGTYPE_SIGNALING       0xC  /**< Signaling (general). */
+#define RTE_PTP_MSGTYPE_MANAGEMENT      0xD  /**< Management (general). */
+
+/* ============================================================
+ *  PTP Flag Field Bits (IEEE 1588-2019 Table 37)
+ *
+ *  These constants are for use after rte_be_to_cpu_16(hdr->flags).
+ *  flagField[0] (octet 6) maps to host bits 8-15.
+ *  flagField[1] (octet 7) maps to host bits 0-7.
+ * ============================================================ */
+
+#define RTE_PTP_FLAG_TWO_STEP    (1 << 9)   /**< Two-step flag. */
+#define RTE_PTP_FLAG_UNICAST     (1 << 10)  /**< Unicast flag. */
+#define RTE_PTP_FLAG_LI_61       (1 << 0)   /**< Leap indicator 61. */
+#define RTE_PTP_FLAG_LI_59       (1 << 1)   /**< Leap indicator 59. */
+
+/* ============================================================
+ *  PTP Header Structures (IEEE 1588-2019)
+ * ============================================================ */
+
+/**
+ * PTP Port Identity (10 bytes).
+ */
+struct __rte_packed_begin rte_ptp_port_id {
+	uint8_t    clock_id[8]; /**< clockIdentity (EUI-64). */
+	rte_be16_t port_number; /**< portNumber. */
+} __rte_packed_end;
+
+/**
+ * PTP Common Message Header (34 bytes).
+ */
+struct __rte_packed_begin rte_ptp_hdr {
+	uint8_t    msg_type;       /**< transportSpecific (4) | messageType (4). */
+	uint8_t    version;        /**< minorVersionPTP (4) | versionPTP (4). */
+	rte_be16_t msg_length;     /**< Total message length in bytes. */
+	uint8_t    domain_number;  /**< PTP domain (0-255). */
+	uint8_t    minor_sdo_id;   /**< minorSdoId (IEEE 1588-2019). */
+	rte_be16_t flags;          /**< Flag field (see RTE_PTP_FLAG_*). */
+	rte_be64_t correction;     /**< correctionField (scaled ns, 48.16 fixed). */
+	rte_be32_t msg_type_specific; /**< messageTypeSpecific. */
+	struct rte_ptp_port_id source_port_id; /**< sourcePortIdentity. */
+	rte_be16_t sequence_id;    /**< sequenceId. */
+	uint8_t    control;        /**< controlField (deprecated in 1588-2019). */
+	int8_t     log_msg_interval; /**< logMessageInterval. */
+} __rte_packed_end;
+
+/**
+ * PTP Timestamp (10 bytes, used in Sync/Delay_Req/Follow_Up bodies).
+ */
+struct __rte_packed_begin rte_ptp_timestamp {
+	rte_be16_t seconds_hi;   /**< Upper 16 bits of seconds. */
+	rte_be32_t seconds_lo;   /**< Lower 32 bits of seconds. */
+	rte_be32_t nanoseconds;  /**< Nanoseconds (0-999999999). */
+} __rte_packed_end;
+
+/* ============================================================
+ *  Inline Helpers
+ * ============================================================ */
+
+/**
+ * Extract PTP message type from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Message type (0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_msg_type(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->msg_type & 0x0F;
+}
+
+/**
+ * Extract transport-specific field from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Transport-specific value (upper nibble, 0x0-0xF).
+ */
+static inline uint8_t
+rte_ptp_transport_specific(const struct rte_ptp_hdr *hdr)
+{
+	return (hdr->msg_type >> 4) & 0x0F;
+}
+
+/**
+ * Extract PTP version from header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   PTP version number (typically 2).
+ */
+static inline uint8_t
+rte_ptp_version(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->version & 0x0F;
+}
+
+/**
+ * Get sequence ID from PTP header (host byte order).
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Sequence ID in host byte order.
+ */
+static inline uint16_t
+rte_ptp_seq_id(const struct rte_ptp_hdr *hdr)
+{
+	return rte_be_to_cpu_16(hdr->sequence_id);
+}
+
+/**
+ * Get PTP domain number.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Domain number (0-255).
+ */
+static inline uint8_t
+rte_ptp_domain(const struct rte_ptp_hdr *hdr)
+{
+	return hdr->domain_number;
+}
+
+/**
+ * Check if PTP message type is an event message.
+ * Event messages (msg_type 0x0-0x3) require timestamps.
+ *
+ * @param msg_type
+ *   PTP message type value (0x0-0xF).
+ * @return
+ *   true if event message, false otherwise.
+ */
+static inline bool
+rte_ptp_is_event(int msg_type)
+{
+	return msg_type >= 0 && msg_type <= RTE_PTP_MSGTYPE_PDELAY_RESP;
+}
+
+/**
+ * Check if the two-step flag is set in a PTP header.
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   true if two-step flag is set.
+ */
+static inline bool
+rte_ptp_is_two_step(const struct rte_ptp_hdr *hdr)
+{
+	return (rte_be_to_cpu_16(hdr->flags) & RTE_PTP_FLAG_TWO_STEP) != 0;
+}
+
+/**
+ * Get correctionField value in nanoseconds (from 48.16 fixed-point).
+ *
+ * @param hdr
+ *   Pointer to PTP header.
+ * @return
+ *   Correction value in nanoseconds.
+ */
+static inline int64_t
+rte_ptp_correction_ns(const struct rte_ptp_hdr *hdr)
+{
+	return (int64_t)rte_be_to_cpu_64(hdr->correction) >> 16;
+}
+
+/**
+ * Add a residence time (in nanoseconds) to the correctionField.
+ * Used by Transparent Clocks to account for relay transit delay.
+ * The correctionField uses IEEE 1588 scaled nanoseconds (48.16 fixed-point).
+ *
+ * @param hdr
+ *   Pointer to PTP header (will be modified in-place).
+ * @param residence_ns
+ *   Residence time in nanoseconds to add.
+ */
+static inline void
+rte_ptp_add_correction(struct rte_ptp_hdr *hdr, int64_t residence_ns)
+{
+	int64_t cf = (int64_t)rte_be_to_cpu_64(hdr->correction);
+
+	cf += (int64_t)((uint64_t)residence_ns << 16);
+	hdr->correction = rte_cpu_to_be_64(cf);
+}
+
+/**
+ * Convert a PTP timestamp structure to nanoseconds since epoch.
+ *
+ * @param ts
+ *   Pointer to PTP timestamp.
+ * @return
+ *   Time in nanoseconds since epoch.
+ */
+static inline uint64_t
+rte_ptp_timestamp_to_ns(const struct rte_ptp_timestamp *ts)
+{
+	uint64_t sec = ((uint64_t)rte_be_to_cpu_16(ts->seconds_hi) << 32) |
+		       rte_be_to_cpu_32(ts->seconds_lo);
+
+	return sec * 1000000000ULL + rte_be_to_cpu_32(ts->nanoseconds);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_PTP_H_ */
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox