* [RFC 0/1] ethdev: per-packet Tx timestamp slot management
@ 2026-08-17 19:24 Rajesh Kumar
2026-08-17 19:24 ` [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs Rajesh Kumar
` (6 more replies)
0 siblings, 7 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-17 19:24 UTC (permalink / raw)
To: dev; +Cc: thomas, bruce.richardson, andrew.rybchenko, Rajesh Kumar
The current DPDK ethdev time synchronization framework is architected
around a single, shared hardware latch. The existing API,
`rte_eth_timesync_read_tx_timestamp()`, assumes a serialization model
where only one TX timestamp is outstanding at any given time.
This model creates severe limitations for modern high-throughput network
interface cards (NICs). When multiple packets requiring precise
transmit timestamps are sent concurrently, the shared latch becomes a
race-condition bottleneck. It makes timestamp retrieval unreliable and
drops accuracy. Furthermore, Poll Mode Drivers (PMDs) backed by
hardware that supports independent, per-packet timestamping slots have
no way to expose this capability to the user.
To solve this, this RFC introduces a formal slot-based lifecycle API
for per-packet Tx timestamp management. The API decouples timestamp
tracking from the global latch model, enabling true asynchronous,
parallel hardware timestamping.
Key Components of the Proposal:
================================
1. **Slot Lifecycle Management APIs**:
- `rte_eth_timesync_tx_timestamp_slot_alloc()`: Allocates and locks
a unique hardware slot prior to frame transmission.
- `rte_eth_timesync_tx_timestamp_stamp_mbuf()`: Embeds the allocated
slot handle into an mbuf dynamic field, allowing the PMD to
program the specific hardware descriptor during the Tx burst.
- `rte_eth_timesync_read_tx_timestamp_slot()`: Asynchronously polls
a specific slot for its captured value, safely returning `-EAGAIN`
if the hardware has not yet written back the timestamp.
- `rte_eth_timesync_tx_timestamp_slot_release()`: Recycles the
hardware slot back to the PMD resource pool after successful
retrieval or an application timeout.
2. **Dual-Domain Timing Mechanics**:
- Introduces `struct rte_eth_timesync_dual_domain_timestamp` to
simultaneously capture both the adjusted PTP Hardware Clock (PHC)
domain (`adjusted_ns`) and the raw, free-running cycles domain
(`cycles_ns`).
- A dedicated `valid_mask` tracks the validity of each time domain
independently, giving applications granular telemetry options.
Scope of this RFC & Open Questions for the Community:
======================================================
This RFC establishes the structural definition of the APIs and driver
interfaces. Before proceeding to a full `v1` patch with active PMD
implementations, we would highly appreciate the community's feedback
on the following design choices:
- **Mbuf Dynamic Field Layout**: Is the encapsulation of the slot
handle into an mbuf dynamic field using standard dynamic registration
the preferred mechanism for passing steering context to the Tx
descriptor path?
- **Dual-Domain Structure**: Does tracking both the adjusted PHC and
the cycle counter within `rte_eth_timesync_dual_domain_timestamp`
cover the needs of other hardware vendors supporting concurrent
domains?
- **Error States**: Is `-EAGAIN` an acceptable return code for
non-blocking polling of incomplete timestamps, or should we consider
an explicit bitmask status?
Please review the proposed design. We welcome your feedback, design
suggestions, and critique.
Rajesh Kumar (1):
ethdev: add per-packet Tx timestamp slot APIs
lib/ethdev/ethdev_driver.h | 19 ++++++
lib/ethdev/rte_ethdev.c | 121 +++++++++++++++++++++++++++++++++++++
lib/ethdev/rte_ethdev.h | 108 +++++++++++++++++++++++++++++++++
3 files changed, 248 insertions(+)
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
@ 2026-08-17 19:24 ` Rajesh Kumar
2026-08-20 4:51 ` Naga Harish K, S V
2026-08-18 2:23 ` [RFC 0/1] ethdev: per-packet Tx timestamp slot management Stephen Hemminger
` (5 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-17 19:24 UTC (permalink / raw)
To: dev; +Cc: thomas, bruce.richardson, andrew.rybchenko, Rajesh Kumar
Add ethdev public and driver-facing APIs for per-packet Tx
hardware timestamp slot management.
The existing `rte_eth_timesync_read_tx_timestamp()` exposes a
single shared latch, making it unreliable when multiple Tx
timestamps are outstanding concurrently. PMDs with per-packet
slot hardware cannot be exploited through this interface.
Introduce a slot lifecycle API:
- `rte_eth_timesync_tx_timestamp_slot_alloc()`: reserve a
hardware slot before transmit
- `rte_eth_timesync_tx_timestamp_stamp_mbuf()`: embed slot
handle into mbuf dynfield for per-packet NIC steering
- `rte_eth_timesync_read_tx_timestamp_slot()`: poll slot for
a captured timestamp; returns -EAGAIN if not ready
- `rte_eth_timesync_tx_timestamp_slot_release()`: return slot
to PMD after readback or timeout
Introduce `rte_eth_timesync_dual_domain_timestamp` to carry
both the adjusted PHC time (`adjusted_ns`) and free-running
cycles-domain time (`cycles_ns`), each with an individual
validity bit in `valid_mask`.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
lib/ethdev/ethdev_driver.h | 19 ++++++
lib/ethdev/rte_ethdev.c | 121 +++++++++++++++++++++++++++++++++++++
lib/ethdev/rte_ethdev.h | 108 +++++++++++++++++++++++++++++++++
3 files changed, 248 insertions(+)
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..61dadcea49 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,19 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint16_t tx_queue_id, uint32_t *slot_id);
+
+/** @internal Read TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1574,12 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..b955784594 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -6699,6 +6699,127 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint16_t tx_queue_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev,
+ tx_queue_id, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+static int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag"});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag", NULL);
+ if (flag_bit >= 0)
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id __rte_unused,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..339c39fcdd 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5528,6 +5528,114 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.cycles_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_CYCLES_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t cycles_ns;
+ uint32_t valid_mask;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet correlation.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param tx_queue_id
+ * TX queue used by the packet to be transmitted.
+ * @param slot_id
+ * Output handle identifying the allocated slot.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint16_t tx_queue_id, uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Stamp an mbuf with a TX timestamp slot handle so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier (unused; reserved for future PMD use).
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return 0 on success, -ENOTSUP if dynfield registration failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [RFC 0/1] ethdev: per-packet Tx timestamp slot management
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
2026-08-17 19:24 ` [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-18 2:23 ` Stephen Hemminger
2026-08-20 4:41 ` Naga Harish K, S V
` (4 subsequent siblings)
6 siblings, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-08-18 2:23 UTC (permalink / raw)
To: Rajesh Kumar; +Cc: dev, thomas, bruce.richardson, andrew.rybchenko
On Tue, 18 Aug 2026 00:54:14 +0530
Rajesh Kumar <rajesh3.kumar@intel.com> wrote:
> The current DPDK ethdev time synchronization framework is architected
> around a single, shared hardware latch. The existing API,
> `rte_eth_timesync_read_tx_timestamp()`, assumes a serialization model
> where only one TX timestamp is outstanding at any given time.
>
> This model creates severe limitations for modern high-throughput network
> interface cards (NICs). When multiple packets requiring precise
> transmit timestamps are sent concurrently, the shared latch becomes a
> race-condition bottleneck. It makes timestamp retrieval unreliable and
> drops accuracy. Furthermore, Poll Mode Drivers (PMDs) backed by
> hardware that supports independent, per-packet timestamping slots have
> no way to expose this capability to the user.
>
> To solve this, this RFC introduces a formal slot-based lifecycle API
> for per-packet Tx timestamp management. The API decouples timestamp
> tracking from the global latch model, enabling true asynchronous,
> parallel hardware timestamping.
Lots of reasonable AI feedback to the design.
Review of the RFC. Design issues first since that's what they're asking for, then code defects.
Design
No capability discovery or exhaustion semantics. Nothing reports how many slots exist, whether they're per-port or per-queue, and slot_alloc() doesn't document what it returns when slots run out (-ENOSPC? -EAGAIN?). That's the first thing an application hits. Needs a rte_eth_dev_info field or query, and a defined out-of-slots errno.
Queue asymmetry: alloc() takes tx_queue_id but read() and release() don't. Either slot_id is port-global (then why does alloc need the queue?) or it's per-queue (then read/release are ambiguous). Pick one and document it. Also tx_queue_id is never validated against nb_tx_queues in the ethdev layer.
Interaction with the existing mechanism is undefined. Does the app still set RTE_MBUF_F_TX_IEEE1588_TMST? Can the legacy latch API and the slot API coexist on one port? PMDs today key tx timestamping off that flag; the RFC needs to say what supersedes what.
Fast-path cost contradicts the stated motivation. The cover letter argues high-throughput concurrent timestamping, but the lifecycle is three dev_ops indirect calls plus a dynfield write per packet, all through the slow path. Fine for PTP rates; if the claim is more than that, alloc/release want burst variants or the intended rate should be stated.
cycles_ns is self-contradictory: is it raw counter cycles or nanoseconds from the free-running clock? If cycles, drop the _ns and expose the frequency; if ns, call it raw_ns or free_ns. Also this struct switches to int64 ns while every other timesync call uses struct timespec; probably the right move but justify it in the cover letter.
PMDs can't consume the dynfield as written. The offset and flag are static in rte_ethdev.c and not exposed to drivers. A PMD has to re-lookup by name, and the dynflag name only exists as a string concat inside the .c file, so drivers would hardcode "..._flag". Define the flag name macro in the header and provide a lookup helper, following the RTE_MBUF_DYNFIELD_TIMESTAMP_NAME pattern.
Naming: rte_eth_timesync_tx_timestamp_stamp_mbuf stutters. ..._tx_slot_set_mbuf or similar.
Defects
Silent dynflag failure in rte_eth_timesync_tx_slot_dynfield_register(). If both rte_mbuf_dynflag_register() and the lookup fail, rte_eth_timesync_tx_slot_dynflag stays 0, the function returns 0, and stamp_mbuf() ORs 0 into ol_flags and reports success. The PMD never sees the request. Must return error. Worse, the early return on offset >= 0 means the flag is never retried on subsequent calls, so one transient failure is permanent.
Likely doesn't compile as posted: the diff adds no includes, but uses struct rte_mbuf_dynfield, rte_mbuf_dynflag_register() (needs rte_mbuf_dyn.h) and alignof (needs stdalign.h pre-C23). Check whether rte_ethdev.c already pulls those in; I don't believe it does.
stamp_mbuf() takes port_id and ignores it, documented as "reserved for future PMD use". Either validate it or drop it; a parameter whose semantics arrive later is an API trap. Dropping it also removes the false implication that the call is port-scoped.
Nits
stamp_mbuf() doxygen deviates from the file's param style, omits the -EINVAL return the code actually produces, and contains an em-dash. The dual_domain_timestamp struct fields lack doxygen comments.
v1 needs rel_notes and prog_guide (ptp section) updates; RFC is fine without.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC 0/1] ethdev: per-packet Tx timestamp slot management
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
2026-08-17 19:24 ` [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs Rajesh Kumar
2026-08-18 2:23 ` [RFC 0/1] ethdev: per-packet Tx timestamp slot management Stephen Hemminger
@ 2026-08-20 4:41 ` Naga Harish K, S V
2026-08-27 11:09 ` Kumar, Rajesh
2026-08-27 12:13 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
` (3 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Naga Harish K, S V @ 2026-08-20 4:41 UTC (permalink / raw)
To: Kumar, Rajesh3, dev@dpdk.org
Cc: thomas@monjalon.net, Richardson, Bruce,
andrew.rybchenko@oktetlabs.ru
[-- Attachment #1: Type: text/plain, Size: 4149 bytes --]
________________________________
From: Rajesh Kumar <rajesh3.kumar@intel.com>
Sent: Tuesday, August 18, 2026 12:54 AM
To: dev@dpdk.org <dev@dpdk.org>
Cc: thomas@monjalon.net <thomas@monjalon.net>; Richardson, Bruce <bruce.richardson@intel.com>; andrew.rybchenko@oktetlabs.ru <andrew.rybchenko@oktetlabs.ru>; Kumar, Rajesh3 <rajesh3.kumar@intel.com>
Subject: [RFC 0/1] ethdev: per-packet Tx timestamp slot management
The current DPDK ethdev time synchronization framework is architected
around a single, shared hardware latch. The existing API,
`rte_eth_timesync_read_tx_timestamp()`, assumes a serialization model
where only one TX timestamp is outstanding at any given time.
This model creates severe limitations for modern high-throughput network
interface cards (NICs). When multiple packets requiring precise
transmit timestamps are sent concurrently, the shared latch becomes a
race-condition bottleneck. It makes timestamp retrieval unreliable and
drops accuracy. Furthermore, Poll Mode Drivers (PMDs) backed by
hardware that supports independent, per-packet timestamping slots have
no way to expose this capability to the user.
To solve this, this RFC introduces a formal slot-based lifecycle API
for per-packet Tx timestamp management. The API decouples timestamp
tracking from the global latch model, enabling true asynchronous,
parallel hardware timestamping.
Key Components of the Proposal:
================================
1. **Slot Lifecycle Management APIs**:
- `rte_eth_timesync_tx_timestamp_slot_alloc()`: Allocates and locks
a unique hardware slot prior to frame transmission.
- `rte_eth_timesync_tx_timestamp_stamp_mbuf()`: Embeds the allocated
slot handle into an mbuf dynamic field, allowing the PMD to
program the specific hardware descriptor during the Tx burst.
- `rte_eth_timesync_read_tx_timestamp_slot()`: Asynchronously polls
a specific slot for its captured value, safely returning `-EAGAIN`
if the hardware has not yet written back the timestamp.
- `rte_eth_timesync_tx_timestamp_slot_release()`: Recycles the
hardware slot back to the PMD resource pool after successful
retrieval or an application timeout.
2. **Dual-Domain Timing Mechanics**:
- Introduces `struct rte_eth_timesync_dual_domain_timestamp` to
simultaneously capture both the adjusted PTP Hardware Clock (PHC)
domain (`adjusted_ns`) and the raw, free-running cycles domain
(`cycles_ns`).
- A dedicated `valid_mask` tracks the validity of each time domain
independently, giving applications granular telemetry options.
Scope of this RFC & Open Questions for the Community:
======================================================
This RFC establishes the structural definition of the APIs and driver
interfaces. Before proceeding to a full `v1` patch with active PMD
implementations, we would highly appreciate the community's feedback
on the following design choices:
- **Mbuf Dynamic Field Layout**: Is the encapsulation of the slot
handle into an mbuf dynamic field using standard dynamic registration
the preferred mechanism for passing steering context to the Tx
descriptor path?
rte_mbuf structure has "timesync" field, which can be used for the slot.
This way mbuf dynamic field use can be avoided.
- **Dual-Domain Structure**: Does tracking both the adjusted PHC and
the cycle counter within `rte_eth_timesync_dual_domain_timestamp`
cover the needs of other hardware vendors supporting concurrent
domains?
- **Error States**: Is `-EAGAIN` an acceptable return code for
non-blocking polling of incomplete timestamps, or should we consider
an explicit bitmask status?
Please review the proposed design. We welcome your feedback, design
suggestions, and critique.
Rajesh Kumar (1):
ethdev: add per-packet Tx timestamp slot APIs
lib/ethdev/ethdev_driver.h | 19 ++++++
lib/ethdev/rte_ethdev.c | 121 +++++++++++++++++++++++++++++++++++++
lib/ethdev/rte_ethdev.h | 108 +++++++++++++++++++++++++++++++++
3 files changed, 248 insertions(+)
--
2.54.0
[-- Attachment #2: Type: text/html, Size: 5977 bytes --]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs
2026-08-17 19:24 ` [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-20 4:51 ` Naga Harish K, S V
0 siblings, 0 replies; 20+ messages in thread
From: Naga Harish K, S V @ 2026-08-20 4:51 UTC (permalink / raw)
To: Kumar, Rajesh3, dev@dpdk.org
Cc: thomas@monjalon.net, Richardson, Bruce,
andrew.rybchenko@oktetlabs.ru
[-- Attachment #1: Type: text/plain, Size: 13063 bytes --]
________________________________
From: Rajesh Kumar <rajesh3.kumar@intel.com>
Sent: Tuesday, August 18, 2026 12:54 AM
To: dev@dpdk.org <dev@dpdk.org>
Cc: thomas@monjalon.net <thomas@monjalon.net>; Richardson, Bruce <bruce.richardson@intel.com>; andrew.rybchenko@oktetlabs.ru <andrew.rybchenko@oktetlabs.ru>; Kumar, Rajesh3 <rajesh3.kumar@intel.com>
Subject: [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs
Add ethdev public and driver-facing APIs for per-packet Tx
hardware timestamp slot management.
The existing `rte_eth_timesync_read_tx_timestamp()` exposes a
single shared latch, making it unreliable when multiple Tx
timestamps are outstanding concurrently. PMDs with per-packet
slot hardware cannot be exploited through this interface.
Introduce a slot lifecycle API:
- `rte_eth_timesync_tx_timestamp_slot_alloc()`: reserve a
hardware slot before transmit
- `rte_eth_timesync_tx_timestamp_stamp_mbuf()`: embed slot
handle into mbuf dynfield for per-packet NIC steering
- `rte_eth_timesync_read_tx_timestamp_slot()`: poll slot for
a captured timestamp; returns -EAGAIN if not ready
- `rte_eth_timesync_tx_timestamp_slot_release()`: return slot
to PMD after readback or timeout
Introduce `rte_eth_timesync_dual_domain_timestamp` to carry
both the adjusted PHC time (`adjusted_ns`) and free-running
cycles-domain time (`cycles_ns`), each with an individual
validity bit in `valid_mask`.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
lib/ethdev/ethdev_driver.h | 19 ++++++
lib/ethdev/rte_ethdev.c | 121 +++++++++++++++++++++++++++++++++++++
lib/ethdev/rte_ethdev.h | 108 +++++++++++++++++++++++++++++++++
3 files changed, 248 insertions(+)
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..61dadcea49 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,19 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint16_t tx_queue_id, uint32_t *slot_id);
+
+/** @internal Read TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1574,12 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..b955784594 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -6699,6 +6699,127 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint16_t tx_queue_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev,
+ tx_queue_id, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+static int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag"});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag", NULL);
+ if (flag_bit >= 0)
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id __rte_unused,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..339c39fcdd 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5528,6 +5528,114 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.cycles_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_CYCLES_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t cycles_ns;
+ uint32_t valid_mask;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet correlation.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param tx_queue_id
+ * TX queue used by the packet to be transmitted.
+ * @param slot_id
+ * Output handle identifying the allocated slot.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint16_t tx_queue_id, uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
How is the egress port information conveyed?
The timestamp slot is a resource of the physical egress port.
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Stamp an mbuf with a TX timestamp slot handle so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier (unused; reserved for future PMD use).
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return 0 on success, -ENOTSUP if dynfield registration failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.54.0
[-- Attachment #2: Type: text/html, Size: 23332 bytes --]
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [RFC 0/1] ethdev: per-packet Tx timestamp slot management
2026-08-20 4:41 ` Naga Harish K, S V
@ 2026-08-27 11:09 ` Kumar, Rajesh
0 siblings, 0 replies; 20+ messages in thread
From: Kumar, Rajesh @ 2026-08-27 11:09 UTC (permalink / raw)
To: Naga Harish K, S V, dev@dpdk.org
Cc: thomas@monjalon.net, Richardson, Bruce,
andrew.rybchenko@oktetlabs.ru
On 20-08-2026 10:11 am, Naga Harish K, S V wrote:
> rte_mbuf structure has "timesync" field, which can be used for the slot.
> This way mbuf dynamic field use can be avoided.
"timesync" in rte_mbuf is already an established RX-side metadata field
and not a generic TX slot carrier. As it is used as RX-side timestamp
metadata, not as a generic TX slot ID. If the same field suddenly meant
“TX slot handle” for one PMD and “RX queue/timestamp tag” for another,
the ABI would become ambiguous and driver-specific, while the
dynamic-field approach keeps the per-packet slot metadata explicit and
portable. For that reason, we prefer the dynamic-field design for this
feature.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
` (2 preceding siblings ...)
2026-08-20 4:41 ` Naga Harish K, S V
@ 2026-08-27 12:13 ` Rajesh Kumar
2026-08-27 12:13 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-08-27 12:18 ` [RFC PATCH v3 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
` (2 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:13 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ethdev timesync API currently exposes Tx timestamps through a shared
hardware register. This requires applications to serialize timestamped
packets and does not allow correlation when multiple packets are in
flight.
This RFC proposes an ethdev interface for hardware with independent Tx
timestamp slots. The interface reports the supported timestamping
mechanism, provides a port-global slot lifecycle, and lets applications
poll each slot asynchronously after transmission.
The proposal includes the following components:
* Capability reporting for shared-register and per-packet timestamping.
* Slot allocation, asynchronous timestamp retrieval, and slot release.
* A dual-domain timestamp structure for adjusted PHC and raw hardware
time.
* Mbuf dynamic field and dynflag support for passing slot handles to Tx.
* Registration and process-local disabling of slot metadata.
* A compatibility alias for the mbuf stamping helper.
* Programmer-guide and NIC feature documentation.
The legacy rte_eth_timesync_read_tx_timestamp() API remains available on
devices using a shared timestamp register. This RFC adds the ethdev and
PMD interfaces but does not add a hardware-specific PMD implementation.
The following areas would benefit from review:
* Is the capability model sufficient for devices with different slot
allocation or completion mechanisms?
* Is an mbuf dynamic field and dynflag the appropriate way to pass the
slot handle into the Tx datapath?
* Is -EAGAIN the appropriate result while a slot timestamp is pending?
* Should the adjusted and raw timestamp domains use nanoseconds in the
public structure, or should one domain expose hardware cycles instead?
Rajesh Kumar (1):
ethdev: add Tx timestamp slot management APIs
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 217 +++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 158 ++++++++++++++
lib/ethdev/rte_ethdev.h | 242 ++++++++++++++++++++++
7 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs
2026-08-27 12:13 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-27 12:13 ` Rajesh Kumar
0 siblings, 0 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:13 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
Extend ethdev timesync with a capability model for selecting between
shared-register and per-packet Tx timestamping.
Add public and PMD interfaces to query timestamp capabilities, allocate
timestamp slots, retrieve timestamps asynchronously, and release slots.
Slots have port-global scope and can be used across Tx queues.
Add a dual-domain timestamp structure for reporting adjusted PHC time
and raw hardware time independently through validity flags.
Add APIs to register and unregister the mbuf dynamic field and dynflag
used to pass slot handles to the Tx datapath. Add helpers to associate
a slot handle with an mbuf before transmission.
Keep the legacy Tx timestamp API for shared-register hardware and provide
a compatibility alias for the mbuf stamping helper.
Document the timestamp capability model, slot lifecycle, and application
workflow.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 217 +++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 158 ++++++++++++++
lib/ethdev/rte_ethdev.h | 242 ++++++++++++++++++++++
7 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
diff --git a/doc/guides/nics/features.rst b/doc/guides/nics/features.rst
index 0b0c69e7cd..040a996156 100644
--- a/doc/guides/nics/features.rst
+++ b/doc/guides/nics/features.rst
@@ -692,14 +692,22 @@ Timesync
Supports IEEE1588/802.1AS timestamping.
-* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``
+* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``,
``timesync_read_rx_timestamp``, ``timesync_read_tx_timestamp``,
+ ``timesync_tx_ts_get_capabilities``, ``timesync_tx_timestamp_slot_alloc``,
+ ``timesync_read_tx_timestamp_slot``, ``timesync_tx_timestamp_slot_release``,
``timesync_adjust_time``, ``timesync_adjust_freq``,
``timesync_read_time``, ``timesync_write_time``.
* **[related] API**: ``rte_eth_timesync_enable()``, ``rte_eth_timesync_disable()``,
- ``rte_eth_timesync_read_rx_timestamp()``,
- ``rte_eth_timesync_read_tx_timestamp``, ``rte_eth_timesync_adjust_time()``,
- ``rte_eth_timesync_adjust_freq()``,
+ ``rte_eth_timesync_read_rx_timestamp()``, ``rte_eth_timesync_read_tx_timestamp()``,
+ ``rte_eth_timesync_tx_timestamp_slot_get_capabilities()``,
+ ``rte_eth_timesync_tx_timestamp_slot_alloc()``,
+ ``rte_eth_timesync_read_tx_timestamp_slot()``,
+ ``rte_eth_timesync_tx_timestamp_slot_release()``,
+ ``rte_eth_timesync_tx_slot_dynfield_register()``,
+ ``rte_eth_timesync_tx_slot_dynfield_unregister()``,
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()``,
+ ``rte_eth_timesync_adjust_time()``, ``rte_eth_timesync_adjust_freq()``,
``rte_eth_timesync_read_time()``, ``rte_eth_timesync_write_time()``.
diff --git a/doc/guides/prog_guide/ethdev/index.rst b/doc/guides/prog_guide/ethdev/index.rst
index 392ced0a2e..bc21f28091 100644
--- a/doc/guides/prog_guide/ethdev/index.rst
+++ b/doc/guides/prog_guide/ethdev/index.rst
@@ -13,3 +13,4 @@ Ethernet Device Library
traffic_metering_and_policing
traffic_management
qos_framework
+ timesync
diff --git a/doc/guides/prog_guide/ethdev/timesync.rst b/doc/guides/prog_guide/ethdev/timesync.rst
new file mode 100644
index 0000000000..73ca771248
--- /dev/null
+++ b/doc/guides/prog_guide/ethdev/timesync.rst
@@ -0,0 +1,217 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+IEEE 1588 / PTP Timesync API
+============================
+
+Overview
+--------
+
+The DPDK IEEE 1588 / Precision Time Protocol (PTP) Timesync API provides
+a standardized framework for managing PTP Hardware Clocks (PHCs) and retrieving
+precise hardware transmit (Tx) and receive (Rx) timestamps.
+
+The Timesync framework encompasses three core capabilities:
+
+1. **Clock Control & Adjustment**: Enabling/disabling hardware timestamping, reading/setting clock time, and adjusting phase/frequency.
+2. **Receive Timestamping**: Hardware capture of incoming PTP packet arrival timestamps.
+3. **Transmit Timestamping**: Hardware capture of outbound PTP packet departure timestamps.
+
+
+Clock Management & Control
+--------------------------
+
+To initialize and discipline a port's PTP Hardware Clock (PHC), the API provides:
+
+* **Enable / Disable**:
+ ``rte_eth_timesync_enable(port_id)`` enables hardware timestamping on the specified port.
+ ``rte_eth_timesync_disable(port_id)`` disables timesync offloads.
+
+* **Clock Time Read / Write**:
+ ``rte_eth_timesync_read_time(port_id, &ts)`` reads the current PHC wall-clock time as a ``struct timespec``.
+ ``rte_eth_timesync_write_time(port_id, &ts)`` sets the PHC wall-clock time.
+
+* **Clock Adjustments**:
+ ``rte_eth_timesync_adjust_time(port_id, delta_ns)`` adjusts the clock phase by a delta offset in nanoseconds.
+ ``rte_eth_timesync_adjust_freq(port_id, scaled_ppm)`` adjusts the clock frequency in scaled parts-per-million (1 ppm = 1 << 16).
+
+
+Receive (Rx) Timestamping
+-------------------------
+
+When receive timestamping is enabled, the hardware identifies incoming PTP packets (e.g. IEEE 1588 EtherType ``0x88F7`` or UDP destination ports 319/320) and latches their arrival time.
+
+Rx Timestamp Extraction Workflow
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. On packet reception via ``rte_eth_rx_burst()``, the PMD checks if the received mbuf represents a PTP packet.
+2. The PMD sets the ``RTE_MBUF_F_RX_IEEE1588_PTP`` flag in ``mbuf->ol_flags``.
+3. Depending on the PMD and hardware capability, the Rx timestamp is either:
+ * **Extracted via API**: Application calls ``rte_eth_timesync_read_rx_timestamp(port_id, &ts, flags)``.
+ * **Inlined in Mbuf**: Stored in a registered mbuf dynamic field (e.g. ``rte_mbuf_dyn_rx_timestamp_register()``).
+
+
+Transmit (Tx) Timestamping Architectures
+----------------------------------------
+
+The framework supports two hardware transmit timestamping architectures:
+
+* **Single Shared Register** (``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG``):
+ The hardware contains a single shared transmit timestamp latch register.
+ Only one outbound packet can be timestamped at a time across the entire port.
+ The application calls ``rte_eth_timesync_read_tx_timestamp(port_id, &ts)`` to retrieve the departure time.
+
+* **Per-Packet Slot Bank** (``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET``):
+ The hardware provides a bank of independent transmit timestamp slots or
+ descriptors. Multiple outbound PTP packets can be timestamped concurrently and
+ correlated asynchronously on a per-packet basis using slot handles.
+
+
+Dual-Domain Timestamps
+~~~~~~~~~~~~~~~~~~~~~~
+
+When retrieving transmit timestamps using slot handles, the API returns
+a dual-domain timestamp structure:
+
+.. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns; /**< PHC adjusted time (wall-clock nanoseconds) */
+ int64_t raw_ns; /**< Free-running hardware cycle counter or raw nanoseconds */
+ uint32_t valid_mask; /**< Validity bits for the adjusted/raw domains */
+ };
+
+* **Adjusted Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID``):
+ Represents the wall-clock time after frequency adjustments (``rte_eth_timesync_adjust_freq``)
+ or phase steps (``rte_eth_timesync_adjust_time``) have been applied.
+
+* **Raw Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID``):
+ Represents the unadjusted free-running hardware cycle counter or raw timestamp.
+ This domain is required when correlating adjusted wall-clock time with the
+ underlying hardware timebase or when performing cross-timestamp analysis.
+
+
+Per-Packet Tx Timestamp Workflow
+--------------------------------
+
+To use per-packet transmit timestamping, applications follow this sequence:
+
+1. **Query Port Capabilities**
+ Determine whether the PMD supports slot-based per-packet timestamping:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_tx_ts_caps caps;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_get_capabilities(port_id, &caps);
+ if (ret == 0 && caps.type == RTE_ETH_TIMESYNC_TX_TS_PER_PACKET) {
+ printf("Port %u supports per-packet timestamping with %u max slots\n",
+ port_id, caps.max_slots);
+ }
+
+2. **Register Mbuf Dynamic Fields**
+ Register the dynamic field and dynamic flag used to pass slot handles to the Tx datapath:
+
+ .. code-block:: c
+
+ ret = rte_eth_timesync_tx_slot_dynfield_register();
+ if (ret < 0) {
+ /* Dynamic field space exhausted or registration failed */
+ }
+
+ .. note::
+
+ ``rte_eth_timesync_enable()`` registers the dynamic field automatically.
+ Call ``rte_eth_timesync_tx_slot_dynfield_register()`` explicitly only if creating
+ mempools before enabling timesync on the port.
+
+3. **Allocate a Timestamp Slot**
+ Before transmitting a PTP packet requiring a transmit timestamp, allocate a slot handle:
+
+ .. code-block:: c
+
+ uint32_t slot_id;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_alloc(port_id, &slot_id);
+ if (ret != 0) {
+ /* Handle allocation error (e.g. -ENOSPC if all slots are in flight) */
+ }
+
+4. **Stamp the Mbuf**
+ Attach the allocated slot handle to the mbuf:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_stamp_mbuf(port_id, slot_id, mbuf);
+ mbuf->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST;
+
+5. **Transmit the Packet**
+ Send the packet via ``rte_eth_tx_burst()`` as usual.
+
+6. **Poll for Timestamp Completion**
+ Read the captured timestamp using the allocated slot handle:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp ts;
+
+ ret = rte_eth_timesync_read_tx_timestamp_slot(port_id, slot_id, &ts);
+ if (ret == 0) {
+ /* Timestamp is ready */
+ if (ts.valid_mask & RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID) {
+ /* Process ts.adjusted_ns */
+ }
+ } else if (ret == -EAGAIN) {
+ /* Timestamp hardware processing is still pending; retry later */
+ }
+
+7. **Release the Slot**
+ After successfully reading the timestamp or timing out, release the slot handle:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_slot_release(port_id, slot_id);
+
+8. **Unregister Dynfield State on Shutdown (Optional)**
+ When shutting down timesync offloads, the application can unregister the cached dynfield state:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_slot_dynfield_unregister();
+
+ .. note::
+
+ This resets process-local dynfield state so subsequent
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()`` calls return ``-ENOTSUP``
+ and PMD Tx datapaths fall back to port-level legacy mode.
+ Note that underlying mbuf dynfield bytes remain allocated in DPDK layout as DPDK does not
+ support dynamic field deallocation.
+
+
+PMD Implementation Requirements
+-------------------------------
+
+To support full timesync capabilities, a Poll Mode Driver (PMD) implements the following driver contract:
+
+1. **Clock Operations** (``timesync_enable``, ``timesync_disable``, ``timesync_read_time``, ``timesync_write_time``, ``timesync_adjust_time``, ``timesync_adjust_freq``)
+ * Controls hardware timestamp generation and disciplines the hardware clock registers.
+
+2. **Rx Timestamping** (``timesync_read_rx_timestamp``)
+ * Configures Rx filters to latch incoming PTP arrival times and flags received mbufs with ``RTE_MBUF_F_RX_IEEE1588_PTP``.
+
+3. **Tx Slot Capability Reporting** (``timesync_tx_ts_get_capabilities``)
+ * Reports ``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG`` or ``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET`` in `caps->type` and sets `caps->max_slots`.
+
+4. **Slot Allocation & Release** (``timesync_tx_timestamp_slot_alloc`` / ``timesync_tx_timestamp_slot_release``)
+ * Maintains a port-global pool or bitmap of hardware timestamp slots.
+ * `timesync_tx_timestamp_slot_alloc` returns a port-unique slot identifier and returns ``-ENOSPC`` when no slots are free.
+ * `timesync_tx_timestamp_slot_release` clears hardware slot state and returns the slot handle to the free pool.
+
+5. **Tx Datapath Integration**
+ * Checks if ``RTE_MBUF_F_TX_IEEE1588_TMST`` is set on `mbuf->ol_flags`.
+ * For per-packet slot mode, retrieves `slot_id` from mbuf dynamic field via ``*RTE_MBUF_DYNFIELD(m, dynfield_offset, uint32_t *)``.
+ * Configures hardware Tx descriptors to capture departure timestamps into the specified slot.
+
+6. **Tx Slot Timestamp Retrieval** (``timesync_read_tx_timestamp_slot``)
+ * Queries hardware slot or descriptor completion ring corresponding to `slot_id`.
+ * Populates ``struct rte_eth_timesync_dual_domain_timestamp`` and returns ``0`` when ready, or ``-EAGAIN`` if pending.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..122d44afeb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,13 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **ethdev: Added experimental per-packet Tx timestamp slot APIs.**
+
+ Added slot-based TX timestamp allocation, mbuf stamping, and per-packet
+ timestamp reads for timesync-capable Ethernet devices. The new APIs support
+ both shared-register and slot-bank usage models through the
+ ``rte_eth_timesync_tx_timestamp_slot_*`` interface family.
+
Removed Items
-------------
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..9d981995ea 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,23 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Query TX timestamp hardware capability (single-register vs per-packet slot bank). */
+typedef int (*eth_timesync_tx_ts_get_caps_t)(struct rte_eth_dev *dev,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint32_t *slot_id);
+
+/** @internal Read a dual-domain TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1578,14 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Query TX timestamp hardware capability (single-reg vs per-packet) */
+ eth_timesync_tx_ts_get_caps_t timesync_tx_ts_get_capabilities;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..22bebb6e3d 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -21,6 +21,7 @@
#include <rte_mempool.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
#include <rte_errno.h>
#include <rte_spinlock.h>
#include <rte_string_fns.h>
@@ -6699,6 +6700,163 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_get_capabilities, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (caps == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot get ethdev port %u Tx timestamp capabilities to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_ts_get_capabilities == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_ts_get_capabilities(dev, caps));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+/* Internal process-local cache for Tx timestamp slot mbuf metadata. */
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_register, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME, NULL);
+ if (flag_bit < 0)
+ return -ENOTSUP;
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_unregister, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_unregister(void)
+{
+ /* Reset cached state without freeing dynamic-field bytes. */
+ rte_eth_timesync_tx_slot_dynfield_offset = -1;
+ rte_eth_timesync_tx_slot_dynflag = 0;
+ return 0;
+}
+
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..c7af668718 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5513,6 +5513,19 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
/**
* Read an IEEE1588/802.1AS Tx timestamp from an Ethernet device.
*
+ * This is the legacy Tx timestamp API and is intended for register-based
+ * timestamp reads. It does not provide per-packet correlation.
+ *
+ * Applications requiring per-packet Tx timestamp correlation should use the
+ * slot-based APIs:
+ * - Setup: rte_eth_timesync_tx_slot_dynfield_register()
+ * - Runtime per-packet loop:
+ * - rte_eth_timesync_tx_timestamp_slot_alloc()
+ * - rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * - rte_eth_timesync_read_tx_timestamp_slot()
+ * - rte_eth_timesync_tx_timestamp_slot_release()
+ * - Teardown: rte_eth_timesync_tx_slot_dynfield_unregister()
+ *
* @param port_id
* The port identifier of the Ethernet device.
* @param timestamp
@@ -5528,6 +5541,235 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.raw_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload in nanoseconds.
+ *
+ * `adjusted_ns` is the synchronized/adjusted domain.
+ * `raw_ns` is the free-running raw hardware clock domain.
+ *
+ * Scalar `int64_t` nanoseconds are used (instead of `struct timespec`) to
+ * keep both domains compact in one payload and to avoid extra split/merge
+ * conversions when processing per-packet timestamp correlation data.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t raw_ns;
+ uint32_t valid_mask;
+};
+
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.max_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_MAX_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.free_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_FREE_VALID RTE_BIT32(1)
+
+/** TX timestamp retrieval mechanism supported by a port. */
+enum rte_eth_timesync_tx_ts_type {
+ RTE_ETH_TIMESYNC_TX_TS_NONE = 0, /**< not supported */
+ /** One hardware latch register shared across all packets. */
+ RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG = 1,
+ /** Per-packet slot bank supports concurrent in-flight correlation. */
+ RTE_ETH_TIMESYNC_TX_TS_PER_PACKET = 2,
+};
+
+/**
+ * TX timestamp capabilities returned by
+ * rte_eth_timesync_tx_timestamp_slot_get_capabilities().
+ */
+struct rte_eth_timesync_tx_ts_caps {
+ enum rte_eth_timesync_tx_ts_type type; /**< mechanism supported by this port */
+ uint32_t max_slots; /**< concurrent slots available; valid only for PER_PACKET */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Query the TX timestamp capability of a port.
+ *
+ * Reports whether the hardware uses a single shared latch register
+ * (RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG) or a per-packet slot bank
+ * (RTE_ETH_TIMESYNC_TX_TS_PER_PACKET), and how many concurrent slots exist.
+ *
+ * Use this to choose between:
+ * - Slot-based: rte_eth_timesync_tx_timestamp_slot_alloc() +
+ * rte_eth_timesync_read_tx_timestamp_slot()
+ * - Legacy: rte_eth_timesync_read_tx_timestamp()
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param caps
+ * Output TX timestamp capability structure.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet timestamp correlation.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * Slots are allocated from a port-global pool and can be used across any
+ * TX queue on the port. The application stamps an mbuf with the slot handle
+ * using rte_eth_timesync_tx_timestamp_stamp_mbuf() before transmission.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Output handle identifying the allocated slot (port-global scope).
+ *
+ * @return
+ * - 0: Success.
+ * - -ENOSPC: No free slots are available.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+/** Mbuf dynflag name indicating TX timestamp slot handle is present. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME "rte_eth_timesync_tx_slot_flag"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Register the per-packet TX timestamp slot dynfield and dynflag in the mbuf
+ * layout.
+ *
+ * Must be called before the first rte_pktmbuf_pool_create() when the
+ * application intends to use rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * for per-packet TX timestamp correlation. Calling it after pool creation
+ * may still succeed if the default dynfield area has not been exhausted.
+ *
+ * rte_eth_timesync_enable() calls this automatically, so explicit calls are
+ * only needed when the application creates pools before enabling timesync.
+ *
+ * Note: dynfields and dynflags cannot be unregistered in DPDK. Once
+ * registered they remain allocated for the lifetime of the process, whether
+ * or not the application ultimately uses per-packet slot correlation.
+ *
+ * @return
+ * - 0: Success (or already registered).
+ * - -ENOTSUP: Registration and lookup both failed (no dynfield space).
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_register(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Disable per-packet TX timestamp slot correlation for this process.
+ *
+ * Resets the cached dynfield offset and dynflag to their unregistered state.
+ * After this call rte_eth_timesync_tx_timestamp_stamp_mbuf() returns
+ * -ENOTSUP and the PMD TX path falls back to the port-level ptp_tx_index
+ * (legacy mode).
+ *
+ * The underlying DPDK dynfield bytes are NOT freed — DPDK provides no dynfield
+ * deallocation. The 4 bytes per mbuf remain allocated but dormant.
+ *
+ * @return Always 0.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_unregister(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Set TX timestamp slot metadata in an mbuf so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier of the Ethernet device.
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EINVAL: Invalid parameters.
+ * - -ENOTSUP: Registration/lookup failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [RFC PATCH v3 0/1] ethdev: add Tx timestamp slot APIs
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
` (3 preceding siblings ...)
2026-08-27 12:13 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-27 12:18 ` Rajesh Kumar
2026-08-27 12:21 ` Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
6 siblings, 0 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:18 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ethdev timesync API currently exposes Tx timestamps through a shared
hardware register. This requires applications to serialize timestamped
packets and does not allow correlation when multiple packets are in
flight.
This RFC proposes an ethdev interface for hardware with independent Tx
timestamp slots. The interface reports the supported timestamping
mechanism, provides a port-global slot lifecycle, and lets applications
poll each slot asynchronously after transmission.
The proposal includes the following components:
* Capability reporting for shared-register and per-packet timestamping.
* Slot allocation, asynchronous timestamp retrieval, and slot release.
* A dual-domain timestamp structure for adjusted PHC and raw hardware
time.
* Mbuf dynamic field and dynflag support for passing slot handles to Tx.
* Registration and process-local disabling of slot metadata.
* A compatibility alias for the mbuf stamping helper.
* Programmer-guide and NIC feature documentation.
The legacy rte_eth_timesync_read_tx_timestamp() API remains available on
devices using a shared timestamp register. This RFC adds the ethdev and
PMD interfaces but does not add a hardware-specific PMD implementation.
The following areas would benefit from review:
* Is the capability model sufficient for devices with different slot
allocation or completion mechanisms?
* Is an mbuf dynamic field and dynflag the appropriate way to pass the
slot handle into the Tx datapath?
* Is -EAGAIN the appropriate result while a slot timestamp is pending?
* Should the adjusted and raw timestamp domains use nanoseconds in the
public structure, or should one domain expose hardware cycles instead?
Rajesh Kumar (1):
ethdev: add Tx timestamp slot management APIs
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 217 +++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 158 ++++++++++++++
lib/ethdev/rte_ethdev.h | 242 ++++++++++++++++++++++
7 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v3 0/1] ethdev: add Tx timestamp slot APIs
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
` (4 preceding siblings ...)
2026-08-27 12:18 ` [RFC PATCH v3 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-27 12:21 ` Rajesh Kumar
2026-08-27 12:21 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
6 siblings, 1 reply; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:21 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ethdev timesync API currently exposes Tx timestamps through a shared
hardware register. This requires applications to serialize timestamped
packets and does not allow correlation when multiple packets are in
flight.
This RFC proposes an ethdev interface for hardware with independent Tx
timestamp slots. The interface reports the supported timestamping
mechanism, provides a port-global slot lifecycle, and lets applications
poll each slot asynchronously after transmission.
The proposal includes the following components:
* Capability reporting for shared-register and per-packet timestamping.
* Slot allocation, asynchronous timestamp retrieval, and slot release.
* A dual-domain timestamp structure for adjusted PHC and raw hardware
time.
* Mbuf dynamic field and dynflag support for passing slot handles to Tx.
* Registration and process-local disabling of slot metadata.
* A compatibility alias for the mbuf stamping helper.
* Programmer-guide and NIC feature documentation.
The legacy rte_eth_timesync_read_tx_timestamp() API remains available on
devices using a shared timestamp register. This RFC adds the ethdev and
PMD interfaces but does not add a hardware-specific PMD implementation.
The following areas would benefit from review:
* Is the capability model sufficient for devices with different slot
allocation or completion mechanisms?
* Is an mbuf dynamic field and dynflag the appropriate way to pass the
slot handle into the Tx datapath?
* Is -EAGAIN the appropriate result while a slot timestamp is pending?
* Should the adjusted and raw timestamp domains use nanoseconds in the
public structure, or should one domain expose hardware cycles instead?
Rajesh Kumar (1):
ethdev: add Tx timestamp slot management APIs
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 217 +++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 158 ++++++++++++++
lib/ethdev/rte_ethdev.h | 242 ++++++++++++++++++++++
7 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs
2026-08-27 12:21 ` Rajesh Kumar
@ 2026-08-27 12:21 ` Rajesh Kumar
2026-08-27 21:45 ` Stephen Hemminger
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
0 siblings, 2 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:21 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
Extend ethdev timesync with a capability model for selecting between
shared-register and per-packet Tx timestamping.
Add public and PMD interfaces to query timestamp capabilities, allocate
timestamp slots, retrieve timestamps asynchronously, and release slots.
Slots have port-global scope and can be used across Tx queues.
Add a dual-domain timestamp structure for reporting adjusted PHC time
and raw hardware time independently through validity flags.
Add APIs to register and unregister the mbuf dynamic field and dynflag
used to pass slot handles to the Tx datapath. Add helpers to associate
a slot handle with an mbuf before transmission.
Keep the legacy Tx timestamp API for shared-register hardware and provide
a compatibility alias for the mbuf stamping helper.
Document the timestamp capability model, slot lifecycle, and application
workflow.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 217 +++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 158 ++++++++++++++
lib/ethdev/rte_ethdev.h | 242 ++++++++++++++++++++++
7 files changed, 662 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
diff --git a/doc/guides/nics/features.rst b/doc/guides/nics/features.rst
index 0b0c69e7cd..040a996156 100644
--- a/doc/guides/nics/features.rst
+++ b/doc/guides/nics/features.rst
@@ -692,14 +692,22 @@ Timesync
Supports IEEE1588/802.1AS timestamping.
-* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``
+* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``,
``timesync_read_rx_timestamp``, ``timesync_read_tx_timestamp``,
+ ``timesync_tx_ts_get_capabilities``, ``timesync_tx_timestamp_slot_alloc``,
+ ``timesync_read_tx_timestamp_slot``, ``timesync_tx_timestamp_slot_release``,
``timesync_adjust_time``, ``timesync_adjust_freq``,
``timesync_read_time``, ``timesync_write_time``.
* **[related] API**: ``rte_eth_timesync_enable()``, ``rte_eth_timesync_disable()``,
- ``rte_eth_timesync_read_rx_timestamp()``,
- ``rte_eth_timesync_read_tx_timestamp``, ``rte_eth_timesync_adjust_time()``,
- ``rte_eth_timesync_adjust_freq()``,
+ ``rte_eth_timesync_read_rx_timestamp()``, ``rte_eth_timesync_read_tx_timestamp()``,
+ ``rte_eth_timesync_tx_timestamp_slot_get_capabilities()``,
+ ``rte_eth_timesync_tx_timestamp_slot_alloc()``,
+ ``rte_eth_timesync_read_tx_timestamp_slot()``,
+ ``rte_eth_timesync_tx_timestamp_slot_release()``,
+ ``rte_eth_timesync_tx_slot_dynfield_register()``,
+ ``rte_eth_timesync_tx_slot_dynfield_unregister()``,
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()``,
+ ``rte_eth_timesync_adjust_time()``, ``rte_eth_timesync_adjust_freq()``,
``rte_eth_timesync_read_time()``, ``rte_eth_timesync_write_time()``.
diff --git a/doc/guides/prog_guide/ethdev/index.rst b/doc/guides/prog_guide/ethdev/index.rst
index 392ced0a2e..bc21f28091 100644
--- a/doc/guides/prog_guide/ethdev/index.rst
+++ b/doc/guides/prog_guide/ethdev/index.rst
@@ -13,3 +13,4 @@ Ethernet Device Library
traffic_metering_and_policing
traffic_management
qos_framework
+ timesync
diff --git a/doc/guides/prog_guide/ethdev/timesync.rst b/doc/guides/prog_guide/ethdev/timesync.rst
new file mode 100644
index 0000000000..73ca771248
--- /dev/null
+++ b/doc/guides/prog_guide/ethdev/timesync.rst
@@ -0,0 +1,217 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+IEEE 1588 / PTP Timesync API
+============================
+
+Overview
+--------
+
+The DPDK IEEE 1588 / Precision Time Protocol (PTP) Timesync API provides
+a standardized framework for managing PTP Hardware Clocks (PHCs) and retrieving
+precise hardware transmit (Tx) and receive (Rx) timestamps.
+
+The Timesync framework encompasses three core capabilities:
+
+1. **Clock Control & Adjustment**: Enabling/disabling hardware timestamping, reading/setting clock time, and adjusting phase/frequency.
+2. **Receive Timestamping**: Hardware capture of incoming PTP packet arrival timestamps.
+3. **Transmit Timestamping**: Hardware capture of outbound PTP packet departure timestamps.
+
+
+Clock Management & Control
+--------------------------
+
+To initialize and discipline a port's PTP Hardware Clock (PHC), the API provides:
+
+* **Enable / Disable**:
+ ``rte_eth_timesync_enable(port_id)`` enables hardware timestamping on the specified port.
+ ``rte_eth_timesync_disable(port_id)`` disables timesync offloads.
+
+* **Clock Time Read / Write**:
+ ``rte_eth_timesync_read_time(port_id, &ts)`` reads the current PHC wall-clock time as a ``struct timespec``.
+ ``rte_eth_timesync_write_time(port_id, &ts)`` sets the PHC wall-clock time.
+
+* **Clock Adjustments**:
+ ``rte_eth_timesync_adjust_time(port_id, delta_ns)`` adjusts the clock phase by a delta offset in nanoseconds.
+ ``rte_eth_timesync_adjust_freq(port_id, scaled_ppm)`` adjusts the clock frequency in scaled parts-per-million (1 ppm = 1 << 16).
+
+
+Receive (Rx) Timestamping
+-------------------------
+
+When receive timestamping is enabled, the hardware identifies incoming PTP packets (e.g. IEEE 1588 EtherType ``0x88F7`` or UDP destination ports 319/320) and latches their arrival time.
+
+Rx Timestamp Extraction Workflow
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. On packet reception via ``rte_eth_rx_burst()``, the PMD checks if the received mbuf represents a PTP packet.
+2. The PMD sets the ``RTE_MBUF_F_RX_IEEE1588_PTP`` flag in ``mbuf->ol_flags``.
+3. Depending on the PMD and hardware capability, the Rx timestamp is either:
+ * **Extracted via API**: Application calls ``rte_eth_timesync_read_rx_timestamp(port_id, &ts, flags)``.
+ * **Inlined in Mbuf**: Stored in a registered mbuf dynamic field (e.g. ``rte_mbuf_dyn_rx_timestamp_register()``).
+
+
+Transmit (Tx) Timestamping Architectures
+----------------------------------------
+
+The framework supports two hardware transmit timestamping architectures:
+
+* **Single Shared Register** (``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG``):
+ The hardware contains a single shared transmit timestamp latch register.
+ Only one outbound packet can be timestamped at a time across the entire port.
+ The application calls ``rte_eth_timesync_read_tx_timestamp(port_id, &ts)`` to retrieve the departure time.
+
+* **Per-Packet Slot Bank** (``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET``):
+ The hardware provides a bank of independent transmit timestamp slots or
+ descriptors. Multiple outbound PTP packets can be timestamped concurrently and
+ correlated asynchronously on a per-packet basis using slot handles.
+
+
+Dual-Domain Timestamps
+~~~~~~~~~~~~~~~~~~~~~~
+
+When retrieving transmit timestamps using slot handles, the API returns
+a dual-domain timestamp structure:
+
+.. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns; /**< PHC adjusted time (wall-clock nanoseconds) */
+ int64_t raw_ns; /**< Free-running hardware cycle counter or raw nanoseconds */
+ uint32_t valid_mask; /**< Validity bits for the adjusted/raw domains */
+ };
+
+* **Adjusted Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID``):
+ Represents the wall-clock time after frequency adjustments (``rte_eth_timesync_adjust_freq``)
+ or phase steps (``rte_eth_timesync_adjust_time``) have been applied.
+
+* **Raw Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID``):
+ Represents the unadjusted free-running hardware cycle counter or raw timestamp.
+ This domain is required when correlating adjusted wall-clock time with the
+ underlying hardware timebase or when performing cross-timestamp analysis.
+
+
+Per-Packet Tx Timestamp Workflow
+--------------------------------
+
+To use per-packet transmit timestamping, applications follow this sequence:
+
+1. **Query Port Capabilities**
+ Determine whether the PMD supports slot-based per-packet timestamping:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_tx_ts_caps caps;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_get_capabilities(port_id, &caps);
+ if (ret == 0 && caps.type == RTE_ETH_TIMESYNC_TX_TS_PER_PACKET) {
+ printf("Port %u supports per-packet timestamping with %u max slots\n",
+ port_id, caps.max_slots);
+ }
+
+2. **Register Mbuf Dynamic Fields**
+ Register the dynamic field and dynamic flag used to pass slot handles to the Tx datapath:
+
+ .. code-block:: c
+
+ ret = rte_eth_timesync_tx_slot_dynfield_register();
+ if (ret < 0) {
+ /* Dynamic field space exhausted or registration failed */
+ }
+
+ .. note::
+
+ ``rte_eth_timesync_enable()`` registers the dynamic field automatically.
+ Call ``rte_eth_timesync_tx_slot_dynfield_register()`` explicitly only if creating
+ mempools before enabling timesync on the port.
+
+3. **Allocate a Timestamp Slot**
+ Before transmitting a PTP packet requiring a transmit timestamp, allocate a slot handle:
+
+ .. code-block:: c
+
+ uint32_t slot_id;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_alloc(port_id, &slot_id);
+ if (ret != 0) {
+ /* Handle allocation error (e.g. -ENOSPC if all slots are in flight) */
+ }
+
+4. **Stamp the Mbuf**
+ Attach the allocated slot handle to the mbuf:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_stamp_mbuf(port_id, slot_id, mbuf);
+ mbuf->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST;
+
+5. **Transmit the Packet**
+ Send the packet via ``rte_eth_tx_burst()`` as usual.
+
+6. **Poll for Timestamp Completion**
+ Read the captured timestamp using the allocated slot handle:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp ts;
+
+ ret = rte_eth_timesync_read_tx_timestamp_slot(port_id, slot_id, &ts);
+ if (ret == 0) {
+ /* Timestamp is ready */
+ if (ts.valid_mask & RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID) {
+ /* Process ts.adjusted_ns */
+ }
+ } else if (ret == -EAGAIN) {
+ /* Timestamp hardware processing is still pending; retry later */
+ }
+
+7. **Release the Slot**
+ After successfully reading the timestamp or timing out, release the slot handle:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_slot_release(port_id, slot_id);
+
+8. **Unregister Dynfield State on Shutdown (Optional)**
+ When shutting down timesync offloads, the application can unregister the cached dynfield state:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_slot_dynfield_unregister();
+
+ .. note::
+
+ This resets process-local dynfield state so subsequent
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()`` calls return ``-ENOTSUP``
+ and PMD Tx datapaths fall back to port-level legacy mode.
+ Note that underlying mbuf dynfield bytes remain allocated in DPDK layout as DPDK does not
+ support dynamic field deallocation.
+
+
+PMD Implementation Requirements
+-------------------------------
+
+To support full timesync capabilities, a Poll Mode Driver (PMD) implements the following driver contract:
+
+1. **Clock Operations** (``timesync_enable``, ``timesync_disable``, ``timesync_read_time``, ``timesync_write_time``, ``timesync_adjust_time``, ``timesync_adjust_freq``)
+ * Controls hardware timestamp generation and disciplines the hardware clock registers.
+
+2. **Rx Timestamping** (``timesync_read_rx_timestamp``)
+ * Configures Rx filters to latch incoming PTP arrival times and flags received mbufs with ``RTE_MBUF_F_RX_IEEE1588_PTP``.
+
+3. **Tx Slot Capability Reporting** (``timesync_tx_ts_get_capabilities``)
+ * Reports ``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG`` or ``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET`` in `caps->type` and sets `caps->max_slots`.
+
+4. **Slot Allocation & Release** (``timesync_tx_timestamp_slot_alloc`` / ``timesync_tx_timestamp_slot_release``)
+ * Maintains a port-global pool or bitmap of hardware timestamp slots.
+ * `timesync_tx_timestamp_slot_alloc` returns a port-unique slot identifier and returns ``-ENOSPC`` when no slots are free.
+ * `timesync_tx_timestamp_slot_release` clears hardware slot state and returns the slot handle to the free pool.
+
+5. **Tx Datapath Integration**
+ * Checks if ``RTE_MBUF_F_TX_IEEE1588_TMST`` is set on `mbuf->ol_flags`.
+ * For per-packet slot mode, retrieves `slot_id` from mbuf dynamic field via ``*RTE_MBUF_DYNFIELD(m, dynfield_offset, uint32_t *)``.
+ * Configures hardware Tx descriptors to capture departure timestamps into the specified slot.
+
+6. **Tx Slot Timestamp Retrieval** (``timesync_read_tx_timestamp_slot``)
+ * Queries hardware slot or descriptor completion ring corresponding to `slot_id`.
+ * Populates ``struct rte_eth_timesync_dual_domain_timestamp`` and returns ``0`` when ready, or ``-EAGAIN`` if pending.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..122d44afeb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,13 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **ethdev: Added experimental per-packet Tx timestamp slot APIs.**
+
+ Added slot-based TX timestamp allocation, mbuf stamping, and per-packet
+ timestamp reads for timesync-capable Ethernet devices. The new APIs support
+ both shared-register and slot-bank usage models through the
+ ``rte_eth_timesync_tx_timestamp_slot_*`` interface family.
+
Removed Items
-------------
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..9d981995ea 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,23 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Query TX timestamp hardware capability (single-register vs per-packet slot bank). */
+typedef int (*eth_timesync_tx_ts_get_caps_t)(struct rte_eth_dev *dev,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint32_t *slot_id);
+
+/** @internal Read a dual-domain TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1578,14 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Query TX timestamp hardware capability (single-reg vs per-packet) */
+ eth_timesync_tx_ts_get_caps_t timesync_tx_ts_get_capabilities;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..22bebb6e3d 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -21,6 +21,7 @@
#include <rte_mempool.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
#include <rte_errno.h>
#include <rte_spinlock.h>
#include <rte_string_fns.h>
@@ -6699,6 +6700,163 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_get_capabilities, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (caps == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot get ethdev port %u Tx timestamp capabilities to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_ts_get_capabilities == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_ts_get_capabilities(dev, caps));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+/* Internal process-local cache for Tx timestamp slot mbuf metadata. */
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_register, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME, NULL);
+ if (flag_bit < 0)
+ return -ENOTSUP;
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_unregister, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_unregister(void)
+{
+ /* Reset cached state without freeing dynamic-field bytes. */
+ rte_eth_timesync_tx_slot_dynfield_offset = -1;
+ rte_eth_timesync_tx_slot_dynflag = 0;
+ return 0;
+}
+
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..c7af668718 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5513,6 +5513,19 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
/**
* Read an IEEE1588/802.1AS Tx timestamp from an Ethernet device.
*
+ * This is the legacy Tx timestamp API and is intended for register-based
+ * timestamp reads. It does not provide per-packet correlation.
+ *
+ * Applications requiring per-packet Tx timestamp correlation should use the
+ * slot-based APIs:
+ * - Setup: rte_eth_timesync_tx_slot_dynfield_register()
+ * - Runtime per-packet loop:
+ * - rte_eth_timesync_tx_timestamp_slot_alloc()
+ * - rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * - rte_eth_timesync_read_tx_timestamp_slot()
+ * - rte_eth_timesync_tx_timestamp_slot_release()
+ * - Teardown: rte_eth_timesync_tx_slot_dynfield_unregister()
+ *
* @param port_id
* The port identifier of the Ethernet device.
* @param timestamp
@@ -5528,6 +5541,235 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.raw_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload in nanoseconds.
+ *
+ * `adjusted_ns` is the synchronized/adjusted domain.
+ * `raw_ns` is the free-running raw hardware clock domain.
+ *
+ * Scalar `int64_t` nanoseconds are used (instead of `struct timespec`) to
+ * keep both domains compact in one payload and to avoid extra split/merge
+ * conversions when processing per-packet timestamp correlation data.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t raw_ns;
+ uint32_t valid_mask;
+};
+
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.max_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_MAX_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.free_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_FREE_VALID RTE_BIT32(1)
+
+/** TX timestamp retrieval mechanism supported by a port. */
+enum rte_eth_timesync_tx_ts_type {
+ RTE_ETH_TIMESYNC_TX_TS_NONE = 0, /**< not supported */
+ /** One hardware latch register shared across all packets. */
+ RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG = 1,
+ /** Per-packet slot bank supports concurrent in-flight correlation. */
+ RTE_ETH_TIMESYNC_TX_TS_PER_PACKET = 2,
+};
+
+/**
+ * TX timestamp capabilities returned by
+ * rte_eth_timesync_tx_timestamp_slot_get_capabilities().
+ */
+struct rte_eth_timesync_tx_ts_caps {
+ enum rte_eth_timesync_tx_ts_type type; /**< mechanism supported by this port */
+ uint32_t max_slots; /**< concurrent slots available; valid only for PER_PACKET */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Query the TX timestamp capability of a port.
+ *
+ * Reports whether the hardware uses a single shared latch register
+ * (RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG) or a per-packet slot bank
+ * (RTE_ETH_TIMESYNC_TX_TS_PER_PACKET), and how many concurrent slots exist.
+ *
+ * Use this to choose between:
+ * - Slot-based: rte_eth_timesync_tx_timestamp_slot_alloc() +
+ * rte_eth_timesync_read_tx_timestamp_slot()
+ * - Legacy: rte_eth_timesync_read_tx_timestamp()
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param caps
+ * Output TX timestamp capability structure.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet timestamp correlation.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * Slots are allocated from a port-global pool and can be used across any
+ * TX queue on the port. The application stamps an mbuf with the slot handle
+ * using rte_eth_timesync_tx_timestamp_stamp_mbuf() before transmission.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Output handle identifying the allocated slot (port-global scope).
+ *
+ * @return
+ * - 0: Success.
+ * - -ENOSPC: No free slots are available.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+/** Mbuf dynflag name indicating TX timestamp slot handle is present. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME "rte_eth_timesync_tx_slot_flag"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Register the per-packet TX timestamp slot dynfield and dynflag in the mbuf
+ * layout.
+ *
+ * Must be called before the first rte_pktmbuf_pool_create() when the
+ * application intends to use rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * for per-packet TX timestamp correlation. Calling it after pool creation
+ * may still succeed if the default dynfield area has not been exhausted.
+ *
+ * rte_eth_timesync_enable() calls this automatically, so explicit calls are
+ * only needed when the application creates pools before enabling timesync.
+ *
+ * Note: dynfields and dynflags cannot be unregistered in DPDK. Once
+ * registered they remain allocated for the lifetime of the process, whether
+ * or not the application ultimately uses per-packet slot correlation.
+ *
+ * @return
+ * - 0: Success (or already registered).
+ * - -ENOTSUP: Registration and lookup both failed (no dynfield space).
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_register(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Disable per-packet TX timestamp slot correlation for this process.
+ *
+ * Resets the cached dynfield offset and dynflag to their unregistered state.
+ * After this call rte_eth_timesync_tx_timestamp_stamp_mbuf() returns
+ * -ENOTSUP and the PMD TX path falls back to the port-level ptp_tx_index
+ * (legacy mode).
+ *
+ * The underlying DPDK dynfield bytes are NOT freed — DPDK provides no dynfield
+ * deallocation. The 4 bytes per mbuf remain allocated but dormant.
+ *
+ * @return Always 0.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_unregister(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Set TX timestamp slot metadata in an mbuf so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier of the Ethernet device.
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EINVAL: Invalid parameters.
+ * - -ENOTSUP: Registration/lookup failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
` (5 preceding siblings ...)
2026-08-27 12:21 ` Rajesh Kumar
@ 2026-08-27 12:34 ` Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-09-02 14:16 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Stephen Hemminger
6 siblings, 2 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:34 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ethdev timesync API currently exposes Tx timestamps through a shared
hardware register. This requires applications to serialize timestamped
packets and does not allow correlation when multiple packets are in
flight.
This RFC proposes an ethdev interface for hardware with independent Tx
timestamp slots. The interface reports the supported timestamping
mechanism, provides a port-global slot lifecycle, and lets applications
poll each slot asynchronously after transmission.
The proposal includes the following components:
* Capability reporting for shared-register and per-packet timestamping.
* Slot allocation, asynchronous timestamp retrieval, and slot release.
* A dual-domain timestamp structure for adjusted PHC and raw hardware
time.
* Mbuf dynamic field and dynflag support for passing slot handles to Tx.
* Registration and process-local disabling of slot metadata.
* A compatibility alias for the mbuf stamping helper.
* Programmer-guide and NIC feature documentation.
The legacy rte_eth_timesync_read_tx_timestamp() API remains available on
devices using a shared timestamp register. This RFC adds the ethdev and
PMD interfaces but does not add a hardware-specific PMD implementation.
The following areas would benefit from review:
* Is the capability model sufficient for devices with different slot
allocation or completion mechanisms?
* Is an mbuf dynamic field and dynflag the appropriate way to pass the
slot handle into the Tx datapath?
* Is -EAGAIN the appropriate result while a slot timestamp is pending?
* Should the adjusted and raw timestamp domains use nanoseconds in the
public structure, or should one domain expose hardware cycles instead?
Rajesh Kumar (1):
ethdev: add Tx timestamp slot management APIs
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 216 +++++++++++++++++++
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 152 +++++++++++++
lib/ethdev/rte_ethdev.h | 251 ++++++++++++++++++++++
6 files changed, 657 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v2 1/1] ethdev: add Tx timestamp slot management APIs
2026-08-27 12:34 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
@ 2026-08-27 12:34 ` Rajesh Kumar
2026-09-02 14:16 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Stephen Hemminger
1 sibling, 0 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-08-27 12:34 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
Extend ethdev timesync with a capability model for selecting between
shared-register and per-packet Tx timestamping.
Add public and PMD interfaces to query timestamp capabilities, allocate
timestamp slots, retrieve timestamps asynchronously, and release slots.
Slots have port-global scope and can be used across Tx queues.
Add a dual-domain timestamp structure for reporting adjusted PHC time
and raw hardware time independently through validity flags.
Add APIs to register and unregister the mbuf dynamic field and dynflag
used to pass slot handles to the Tx datapath. Add helpers to associate
a slot handle with an mbuf before transmission.
Keep the legacy Tx timestamp API for shared-register hardware and provide
a compatibility alias for the mbuf stamping helper.
Document the timestamp capability model, slot lifecycle, and application
workflow.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 216 +++++++++++++++++++
lib/ethdev/ethdev_driver.h | 25 +++
lib/ethdev/rte_ethdev.c | 152 +++++++++++++
lib/ethdev/rte_ethdev.h | 251 ++++++++++++++++++++++
6 files changed, 657 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
diff --git a/doc/guides/nics/features.rst b/doc/guides/nics/features.rst
index 0b0c69e7cd..171e2aabba 100644
--- a/doc/guides/nics/features.rst
+++ b/doc/guides/nics/features.rst
@@ -692,14 +692,22 @@ Timesync
Supports IEEE1588/802.1AS timestamping.
-* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``
+* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``,
``timesync_read_rx_timestamp``, ``timesync_read_tx_timestamp``,
+ ``timesync_tx_ts_get_capabilities``, ``timesync_tx_timestamp_slot_alloc``,
+ ``timesync_read_tx_timestamp_slot``, ``timesync_tx_timestamp_slot_release``,
``timesync_adjust_time``, ``timesync_adjust_freq``,
``timesync_read_time``, ``timesync_write_time``.
* **[related] API**: ``rte_eth_timesync_enable()``, ``rte_eth_timesync_disable()``,
- ``rte_eth_timesync_read_rx_timestamp()``,
- ``rte_eth_timesync_read_tx_timestamp``, ``rte_eth_timesync_adjust_time()``,
- ``rte_eth_timesync_adjust_freq()``,
+ ``rte_eth_timesync_read_rx_timestamp()``, ``rte_eth_timesync_read_tx_timestamp()``,
+ ``rte_eth_timesync_tx_timestamp_slot_get_capabilities()``,
+ ``rte_eth_timesync_tx_timestamp_slot_alloc()``,
+ ``rte_eth_timesync_read_tx_timestamp_slot()``,
+ ``rte_eth_timesync_tx_timestamp_slot_release()``,
+ ``rte_eth_timesync_tx_slot_dynfield_register()``,
+ ``rte_eth_timesync_tx_slot_dynfield_unregister()``,
+ ``rte_eth_timesync_tx_slot_set_mbuf()``,
+ ``rte_eth_timesync_adjust_time()``, ``rte_eth_timesync_adjust_freq()``,
``rte_eth_timesync_read_time()``, ``rte_eth_timesync_write_time()``.
diff --git a/doc/guides/prog_guide/ethdev/index.rst b/doc/guides/prog_guide/ethdev/index.rst
index 392ced0a2e..bc21f28091 100644
--- a/doc/guides/prog_guide/ethdev/index.rst
+++ b/doc/guides/prog_guide/ethdev/index.rst
@@ -13,3 +13,4 @@ Ethernet Device Library
traffic_metering_and_policing
traffic_management
qos_framework
+ timesync
diff --git a/doc/guides/prog_guide/ethdev/timesync.rst b/doc/guides/prog_guide/ethdev/timesync.rst
new file mode 100644
index 0000000000..74405732ae
--- /dev/null
+++ b/doc/guides/prog_guide/ethdev/timesync.rst
@@ -0,0 +1,216 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+IEEE 1588 / PTP Timesync API
+============================
+
+Overview
+--------
+
+The DPDK IEEE 1588 / Precision Time Protocol (PTP) Timesync API provides
+a standardized framework for managing PTP Hardware Clocks (PHCs) and retrieving
+precise hardware transmit (Tx) and receive (Rx) timestamps.
+
+The Timesync framework encompasses three core capabilities:
+
+1. **Clock Control & Adjustment**: Enabling/disabling hardware timestamping, reading/setting clock time, and adjusting phase/frequency.
+2. **Receive Timestamping**: Hardware capture of incoming PTP packet arrival timestamps.
+3. **Transmit Timestamping**: Hardware capture of outbound PTP packet departure timestamps.
+
+
+Clock Management & Control
+--------------------------
+
+To initialize and discipline a port's PTP Hardware Clock (PHC), the API provides:
+
+* **Enable / Disable**:
+ ``rte_eth_timesync_enable(port_id)`` enables hardware timestamping on the specified port.
+ ``rte_eth_timesync_disable(port_id)`` disables timesync offloads.
+
+* **Clock Time Read / Write**:
+ ``rte_eth_timesync_read_time(port_id, &ts)`` reads the current PHC wall-clock time as a ``struct timespec``.
+ ``rte_eth_timesync_write_time(port_id, &ts)`` sets the PHC wall-clock time.
+
+* **Clock Adjustments**:
+ ``rte_eth_timesync_adjust_time(port_id, delta_ns)`` adjusts the clock phase by a delta offset in nanoseconds.
+ ``rte_eth_timesync_adjust_freq(port_id, scaled_ppm)`` adjusts the clock frequency in scaled parts-per-million (1 ppm = 1 << 16).
+
+
+Receive (Rx) Timestamping
+-------------------------
+
+When receive timestamping is enabled, the hardware identifies incoming PTP packets (e.g. IEEE 1588 EtherType ``0x88F7`` or UDP destination ports 319/320) and latches their arrival time.
+
+Rx Timestamp Extraction Workflow
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. On packet reception via ``rte_eth_rx_burst()``, the PMD checks if the received mbuf represents a PTP packet.
+2. The PMD sets the ``RTE_MBUF_F_RX_IEEE1588_PTP`` flag in ``mbuf->ol_flags``.
+3. Depending on the PMD and hardware capability, the Rx timestamp is either:
+ * **Extracted via API**: Application calls ``rte_eth_timesync_read_rx_timestamp(port_id, &ts, flags)``.
+ * **Inlined in Mbuf**: Stored in a registered mbuf dynamic field (e.g. ``rte_mbuf_dyn_rx_timestamp_register()``).
+
+
+Transmit (Tx) Timestamping Architectures
+----------------------------------------
+
+The framework supports two hardware transmit timestamping architectures:
+
+* **Single Shared Register** (``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG``):
+ The hardware contains a single shared transmit timestamp latch register.
+ Only one outbound packet can be timestamped at a time across the entire port.
+ The application calls ``rte_eth_timesync_read_tx_timestamp(port_id, &ts)`` to retrieve the departure time.
+
+* **Per-Packet Slot Bank** (``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET``):
+ The hardware provides a bank of independent transmit timestamp slots or
+ descriptors. Multiple outbound PTP packets can be timestamped concurrently and
+ correlated asynchronously on a per-packet basis using slot handles.
+
+
+Dual-Domain Timestamps
+~~~~~~~~~~~~~~~~~~~~~~
+
+When retrieving transmit timestamps using slot handles, the API returns
+a dual-domain timestamp structure:
+
+.. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns; /**< PHC adjusted time (wall-clock nanoseconds) */
+ int64_t raw_ns; /**< Free-running hardware cycle counter or raw nanoseconds */
+ uint32_t valid_mask; /**< Validity bits for the adjusted/raw domains */
+ };
+
+* **Adjusted Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID``):
+ Represents the wall-clock time after frequency adjustments (``rte_eth_timesync_adjust_freq``)
+ or phase steps (``rte_eth_timesync_adjust_time``) have been applied.
+
+* **Raw Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID``):
+ Represents the unadjusted free-running hardware cycle counter or raw timestamp.
+ This domain is required when correlating adjusted wall-clock time with the
+ underlying hardware timebase or when performing cross-timestamp analysis.
+
+
+Per-Packet Tx Timestamp Workflow
+--------------------------------
+
+To use per-packet transmit timestamping, applications follow this sequence:
+
+1. **Query Port Capabilities**
+ Determine whether the PMD supports slot-based per-packet timestamping:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_tx_ts_caps caps;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_get_capabilities(port_id, &caps);
+ if (ret == 0 && caps.type == RTE_ETH_TIMESYNC_TX_TS_PER_PACKET) {
+ printf("Port %u supports per-packet timestamping with %u max slots\n",
+ port_id, caps.max_slots);
+ }
+
+2. **Register Mbuf Dynamic Fields**
+ Register the dynamic field and dynamic flag used to pass slot handles to the Tx datapath:
+
+ .. code-block:: c
+
+ ret = rte_eth_timesync_tx_slot_dynfield_register();
+ if (ret < 0) {
+ /* Dynamic field space exhausted or registration failed */
+ }
+
+ .. note::
+
+ ``rte_eth_timesync_enable()`` registers the dynamic field automatically.
+ Call ``rte_eth_timesync_tx_slot_dynfield_register()`` explicitly only if creating
+ mempools before enabling timesync on the port.
+
+3. **Allocate a Timestamp Slot**
+ Before transmitting a PTP packet requiring a transmit timestamp, allocate a slot handle:
+
+ .. code-block:: c
+
+ uint32_t slot_id;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_alloc(port_id, &slot_id);
+ if (ret != 0) {
+ /* Handle allocation error (e.g. -ENOSPC if all slots are in flight) */
+ }
+
+4. **Stamp the Mbuf**
+ Attach the allocated slot handle to the mbuf:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_slot_set_mbuf(port_id, slot_id, mbuf);
+ mbuf->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST;
+
+5. **Transmit the Packet**
+ Send the packet via ``rte_eth_tx_burst()`` as usual.
+
+6. **Poll for Timestamp Completion**
+ Read the captured timestamp using the allocated slot handle:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp ts;
+
+ ret = rte_eth_timesync_read_tx_timestamp_slot(port_id, slot_id, &ts);
+ if (ret == 0) {
+ /* Timestamp is ready */
+ if (ts.valid_mask & RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID) {
+ /* Process ts.adjusted_ns */
+ }
+ } else if (ret == -EAGAIN) {
+ /* Timestamp hardware processing is still pending; retry later */
+ }
+
+7. **Release the Slot**
+ After successfully reading the timestamp or timing out, release the slot handle:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_slot_release(port_id, slot_id);
+
+8. **Unregister Dynfield State on Shutdown (Optional)**
+ When shutting down timesync offloads, the application can unregister the cached dynfield state:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_slot_dynfield_unregister();
+
+ .. note::
+
+ This resets process-local dynfield state so subsequent ``rte_eth_timesync_tx_slot_set_mbuf()``
+ calls return ``-ENOTSUP`` and PMD Tx datapaths fall back to port-level legacy mode.
+ Note that underlying mbuf dynfield bytes remain allocated in DPDK layout as DPDK does not
+ support dynamic field deallocation.
+
+
+PMD Implementation Requirements
+-------------------------------
+
+To support full timesync capabilities, a Poll Mode Driver (PMD) implements the following driver contract:
+
+1. **Clock Operations** (``timesync_enable``, ``timesync_disable``, ``timesync_read_time``, ``timesync_write_time``, ``timesync_adjust_time``, ``timesync_adjust_freq``)
+ * Controls hardware timestamp generation and disciplines the hardware clock registers.
+
+2. **Rx Timestamping** (``timesync_read_rx_timestamp``)
+ * Configures Rx filters to latch incoming PTP arrival times and flags received mbufs with ``RTE_MBUF_F_RX_IEEE1588_PTP``.
+
+3. **Tx Slot Capability Reporting** (``timesync_tx_ts_get_capabilities``)
+ * Reports ``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG`` or ``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET`` in `caps->type` and sets `caps->max_slots`.
+
+4. **Slot Allocation & Release** (``timesync_tx_timestamp_slot_alloc`` / ``timesync_tx_timestamp_slot_release``)
+ * Maintains a port-global pool or bitmap of hardware timestamp slots.
+ * `timesync_tx_timestamp_slot_alloc` returns a port-unique slot identifier and returns ``-ENOSPC`` when no slots are free.
+ * `timesync_tx_timestamp_slot_release` clears hardware slot state and returns the slot handle to the free pool.
+
+5. **Tx Datapath Integration**
+ * Checks if ``RTE_MBUF_F_TX_IEEE1588_TMST`` is set on `mbuf->ol_flags`.
+ * For per-packet slot mode, retrieves `slot_id` from mbuf dynamic field via ``*RTE_MBUF_DYNFIELD(m, dynfield_offset, uint32_t *)``.
+ * Configures hardware Tx descriptors to capture departure timestamps into the specified slot.
+
+6. **Tx Slot Timestamp Retrieval** (``timesync_read_tx_timestamp_slot``)
+ * Queries hardware slot or descriptor completion ring corresponding to `slot_id`.
+ * Populates ``struct rte_eth_timesync_dual_domain_timestamp`` and returns ``0`` when ready, or ``-EAGAIN`` if pending.
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..9d981995ea 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,23 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Query TX timestamp hardware capability (single-register vs per-packet slot bank). */
+typedef int (*eth_timesync_tx_ts_get_caps_t)(struct rte_eth_dev *dev,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint32_t *slot_id);
+
+/** @internal Read a dual-domain TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1578,14 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Query TX timestamp hardware capability (single-reg vs per-packet) */
+ eth_timesync_tx_ts_get_caps_t timesync_tx_ts_get_capabilities;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..204e1db2b7 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -21,6 +21,7 @@
#include <rte_mempool.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
#include <rte_errno.h>
#include <rte_spinlock.h>
#include <rte_string_fns.h>
@@ -6699,6 +6700,157 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_get_capabilities, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (caps == NULL)
+ return -EINVAL;
+
+ if (dev->dev_ops->timesync_tx_ts_get_capabilities == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_ts_get_capabilities(dev, caps));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_register, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag"});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag", NULL);
+ if (flag_bit >= 0)
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_unregister, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_unregister(void)
+{
+ /* Reset cached state without freeing dynamic-field bytes. */
+ rte_eth_timesync_tx_slot_dynfield_offset = -1;
+ rte_eth_timesync_tx_slot_dynflag = 0;
+ return 0;
+}
+
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id __rte_unused,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..a4e8fc2c24 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5513,6 +5513,19 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
/**
* Read an IEEE1588/802.1AS Tx timestamp from an Ethernet device.
*
+ * This is the legacy Tx timestamp API and is intended for register-based
+ * timestamp reads. It does not provide per-packet correlation.
+ *
+ * Applications requiring per-packet Tx timestamp correlation should use the
+ * slot-based APIs:
+ * - Setup: rte_eth_timesync_tx_slot_dynfield_register()
+ * - Runtime per-packet loop:
+ * - rte_eth_timesync_tx_timestamp_slot_alloc()
+ * - rte_eth_timesync_tx_slot_set_mbuf()
+ * - rte_eth_timesync_read_tx_timestamp_slot()
+ * - rte_eth_timesync_tx_timestamp_slot_release()
+ * - Teardown: rte_eth_timesync_tx_slot_dynfield_unregister()
+ *
* @param port_id
* The port identifier of the Ethernet device.
* @param timestamp
@@ -5528,6 +5541,244 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.raw_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload in nanoseconds.
+ *
+ * `adjusted_ns` is the synchronized/adjusted domain.
+ * `raw_ns` is the free-running raw hardware clock domain.
+ *
+ * Scalar `int64_t` nanoseconds are used (instead of `struct timespec`) to
+ * keep both domains compact in one payload and to avoid extra split/merge
+ * conversions when processing per-packet timestamp correlation data.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t raw_ns;
+ uint32_t valid_mask;
+};
+
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.max_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_MAX_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_tx_timestamp_slot_info.free_slots. */
+#define RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_FREE_VALID RTE_BIT32(1)
+
+/** TX timestamp retrieval mechanism supported by a port. */
+enum rte_eth_timesync_tx_ts_type {
+ RTE_ETH_TIMESYNC_TX_TS_NONE = 0, /**< not supported */
+ /** One hardware latch register shared across all packets. */
+ RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG = 1,
+ /** Per-packet slot bank supports concurrent in-flight correlation. */
+ RTE_ETH_TIMESYNC_TX_TS_PER_PACKET = 2,
+};
+
+/**
+ * TX timestamp capabilities returned by
+ * rte_eth_timesync_tx_timestamp_slot_get_capabilities().
+ */
+struct rte_eth_timesync_tx_ts_caps {
+ enum rte_eth_timesync_tx_ts_type type; /**< mechanism supported by this port */
+ uint32_t max_slots; /**< concurrent slots available; valid only for PER_PACKET */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Query the TX timestamp capability of a port.
+ *
+ * Reports whether the hardware uses a single shared latch register
+ * (RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG) or a per-packet slot bank
+ * (RTE_ETH_TIMESYNC_TX_TS_PER_PACKET), and how many concurrent slots exist.
+ *
+ * Use this to choose between:
+ * - Slot-based: rte_eth_timesync_tx_timestamp_slot_alloc() +
+ * rte_eth_timesync_read_tx_timestamp_slot()
+ * - Legacy: rte_eth_timesync_read_tx_timestamp()
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param caps
+ * Output TX timestamp capability structure.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet timestamp correlation.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * Slots are allocated from a port-global pool and can be used across any
+ * TX queue on the port. The application stamps an mbuf with the slot handle
+ * using rte_eth_timesync_tx_slot_set_mbuf() before transmission.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Output handle identifying the allocated slot (port-global scope).
+ *
+ * @return
+ * - 0: Success.
+ * - -ENOSPC: No free slots are available.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+/** Mbuf dynflag name indicating TX timestamp slot handle is present. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME "rte_eth_timesync_tx_slot_flag"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Register the per-packet TX timestamp slot dynfield and dynflag in the mbuf
+ * layout.
+ *
+ * Must be called before the first rte_pktmbuf_pool_create() when the
+ * application intends to use rte_eth_timesync_tx_slot_set_mbuf() for
+ * per-packet TX timestamp correlation. Calling it after pool creation may
+ * still succeed if the default dynfield area has not been exhausted.
+ *
+ * rte_eth_timesync_enable() calls this automatically, so explicit calls are
+ * only needed when the application creates pools before enabling timesync.
+ *
+ * Note: dynfields and dynflags cannot be unregistered in DPDK. Once
+ * registered they remain allocated for the lifetime of the process, whether
+ * or not the application ultimately uses per-packet slot correlation.
+ *
+ * @return
+ * - 0: Success (or already registered).
+ * - -ENOTSUP: Registration and lookup both failed (no dynfield space).
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_register(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Disable per-packet TX timestamp slot correlation for this process.
+ *
+ * Resets the cached dynfield offset and dynflag to their unregistered state.
+ * After this call rte_eth_timesync_tx_slot_set_mbuf() returns -ENOTSUP and
+ * the PMD TX path falls back to the port-level ptp_tx_index (legacy mode).
+ *
+ * The underlying DPDK dynfield bytes are NOT freed — DPDK provides no dynfield
+ * deallocation. The 4 bytes per mbuf remain allocated but dormant.
+ *
+ * @return Always 0.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_unregister(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Set TX timestamp slot metadata in an mbuf so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier of the Ethernet device.
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EINVAL: Invalid parameters.
+ * - -ENOTSUP: Registration/lookup failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_set_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Compatibility alias for rte_eth_timesync_tx_slot_set_mbuf().
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs
2026-08-27 12:21 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
@ 2026-08-27 21:45 ` Stephen Hemminger
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
1 sibling, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-08-27 21:45 UTC (permalink / raw)
To: Rajesh Kumar
Cc: dev, thomas, bruce.richardson, andrew.rybchenko, aman.deep.singh
On Thu, 27 Aug 2026 17:51:59 +0530
Rajesh Kumar <rajesh3.kumar@intel.com> wrote:
> Extend ethdev timesync with a capability model for selecting between
> shared-register and per-packet Tx timestamping.
>
> Add public and PMD interfaces to query timestamp capabilities, allocate
> timestamp slots, retrieve timestamps asynchronously, and release slots.
> Slots have port-global scope and can be used across Tx queues.
>
> Add a dual-domain timestamp structure for reporting adjusted PHC time
> and raw hardware time independently through validity flags.
>
> Add APIs to register and unregister the mbuf dynamic field and dynflag
> used to pass slot handles to the Tx datapath. Add helpers to associate
> a slot handle with an mbuf before transmission.
>
> Keep the legacy Tx timestamp API for shared-register hardware and provide
> a compatibility alias for the mbuf stamping helper.
>
> Document the timestamp capability model, slot lifecycle, and application
> workflow.
>
> Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
> ---
Lots of feedback for AI review that needs addressing (Claude Fable).
Also a feature like this needs some form of test coverage. Perhaps
mocking up something in null PMD or related.
Review of [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs
Applied cleanly to current main and read post-apply. No build
performed (meson not available here); nothing in the diff looks like
it would fail to compile.
Errors:
1. Documentation and Doxygen claim rte_eth_timesync_enable() registers
the dynfield/dynflag automatically. It does not. The patch does not
touch rte_eth_timesync_enable(); post-apply it still just calls the
PMD op. So the .. note:: in timesync.rst ("registers the dynamic
field automatically ... Call ... explicitly only if creating
mempools before enabling timesync") and the same statement in the
header Doxygen for rte_eth_timesync_tx_slot_dynfield_register() are
false. An application that follows the doc and relies on
timesync_enable will get -ENOTSUP from stamp_mbuf (or worse, a late
registration that fails after pools are created). Either add the
call in rte_eth_timesync_enable() or drop the claim; given the
"must register before pool create" constraint, dropping the claim
and making the explicit call mandatory is the safer contract.
2. Commit message says "provide a compatibility alias for the mbuf
stamping helper". No such alias exists in the diff. Either the
alias was dropped between v2 and v3 and the message is stale, or
it is missing. Fix one or the other.
3. Dead macros referencing a nonexistent structure:
RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_MAX_VALID
RTE_ETH_TIMESYNC_TX_TIMESTAMP_SLOT_INFO_FREE_VALID
Doxygen says they are valid bits for
rte_eth_timesync_tx_timestamp_slot_info.max_slots / .free_slots,
but that struct is not defined anywhere. Leftover from an earlier
revision; remove them.
4. The PMD contract is incomplete: the datapath needs the dynfield
offset and dynflag bit, but they are file-static in rte_ethdev.c
with no accessor in ethdev_driver.h. A PMD is forced to do its own
rte_mbuf_dynfield_lookup()/dynflag_lookup() by name, which then
cannot observe rte_eth_timesync_tx_slot_dynfield_unregister()
resetting the library-side cache. So the documented behaviour that
after unregister "PMD Tx datapaths fall back to port-level legacy
mode" cannot actually happen: the PMD keeps testing the dynflag it
looked up, and mbufs stamped before unregister still carry it.
Either export an internal accessor (offset + flag) in
ethdev_driver.h that PMDs must use, or drop the unregister API and
its fallback claim. As written, the unregister function only
changes library-local state and cannot deliver what its Doxygen
promises.
Warnings:
5. rte_ethdev.h Doxygen for rte_eth_timesync_tx_slot_dynfield_unregister
refers to "the port-level ptp_tx_index". That is an Intel driver
internal, not an ethdev concept; a generic header should not
reference it.
6. rte_eth_timesync_tx_timestamp_stamp_mbuf() calls
rte_eth_timesync_tx_slot_dynfield_register() on every invocation.
Post-registration this is just an int compare, but it also means
the first stamp_mbuf call can silently register the dynfield after
pools exist, which the register() Doxygen says may fail. It is also
inconsistent with the "-ENOTSUP after unregister" contract: after
unregister, stamp_mbuf will simply re-register (lookup succeeds)
and go on working. Do the offset check inline and return -ENOTSUP
if the offset is < 0 rather than re-registering.
7. Slot handles are uint32_t, but rte_eth_timesync_tx_ts_caps has no
way to express the free-slot count, and slot_release() has no
documented behaviour for double-release or release of a slot whose
timestamp was never read. For an RFC that is acceptable, but the
PMD contract section in timesync.rst should say what a PMD must do
for an invalid or already-free slot_id (-EINVAL is the obvious
answer, and the ethdev wrapper could enforce slot_id < max_slots
if caps are cached).
8. Release notes and header call these experimental, but the new
eth_dev_ops members are inserted in the middle of struct
eth_dev_ops rather than at the end. eth_dev_ops is internal so
this is not an ABI issue, but the ordering in the struct
(alloc, get_capabilities, read_slot, release) does not match the
typedef order or the order in features.rst
(get_capabilities, alloc, read_slot, release). Make them consistent.
9. features.rst adds the ops and API names, but there is no PMD
implementing them in this series and no testpmd hook or unit test
exercising the new API. Per contributing guidelines a new ethdev
API needs at least one driver implementation and a testpmd hook
before it can be merged out of RFC.
Info:
10. timesync.rst: the "Clock Management & Control", "Rx Timestamp
Extraction Workflow" and "PMD Implementation Requirements"
sections use bullet lists with bold term + description; RST
definition lists would render better. Several lines in the .rst
are well over 100 columns; wrap at sentence boundaries.
11. In rte_eth_timesync_tx_slot_dynfield_register(), the inner
braced block for flag_bit is unusual style in DPDK; declare
flag_bit at function top or at point of use without the block.
12. Blank line separating the slot_release function from the
"Internal process-local cache" comment is missing; there is a
double blank line before stamp_mbuf.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs
2026-08-27 12:21 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-08-27 21:45 ` Stephen Hemminger
@ 2026-09-02 5:51 ` Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
` (2 more replies)
1 sibling, 3 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-09-02 5:51 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ethdev timesync API currently exposes Tx timestamps through a shared
hardware register. This requires applications to serialize timestamped
packets and does not allow correlation when multiple packets are in
flight.
This RFC proposes an ethdev interface for hardware with independent Tx
timestamp slots. The interface reports the supported timestamping
mechanism, provides a port-global slot lifecycle, and lets applications
poll each slot asynchronously after transmission.
The proposal includes the following components:
* Capability reporting for shared-register and per-packet timestamping.
* Slot allocation, asynchronous timestamp retrieval, and slot release.
* A dual-domain timestamp structure for adjusted PHC and raw hardware
time.
* Mbuf dynamic field and dynflag support for passing slot handles to Tx,
with a helper that stamps a slot handle onto an mbuf.
* Registration and process-local reset of the cached dynfield state.
* An ice PMD implementation using the 64 per-port PHY timestamp slots.
* A testpmd command to report the slot capabilities of a port.
* Programmer-guide and NIC feature documentation.
The legacy rte_eth_timesync_read_tx_timestamp() API remains available on
devices using a shared timestamp register.
Changes since v3:
* Added an ice PMD implementation of the new slot ops. The ice PHY
exposes 64 Tx timestamp slots per port, which are now tracked in an
atomic per-adapter bitmap instead of the single static slot the driver
used before. On E822 the 64 quad slots are split evenly between the
PFs sharing the quad so allocations never overlap.
* The ice Tx context descriptor now takes the timestamp index from the
mbuf dynamic field when the packet carries the slot dynflag, and falls
back to the legacy static index otherwise.
* The ice legacy single-slot read path now uses ptp_tx_index instead of
a hardcoded slot 0, and clears the PHY timestamp on E810 after a read
or a timeout so a stale entry cannot block later requests.
* Added a testpmd command, "show port <port_id> tx_timestamp_caps", that
reports the slot capabilities of a port and performs an allocate and
release round-trip to exercise the API.
Comments and suggestions on the interface and the ice implementation are
welcome.
Rajesh Kumar (3):
ethdev: add Tx timestamp slot management APIs
net/ice: support per-packet Tx timestamp slots
app/testpmd: add Tx timestamp capabilities command
app/test-pmd/cmdline.c | 79 ++++++
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 223 +++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
drivers/net/intel/ice/ice_ethdev.c | 210 +++++++++++++++-
drivers/net/intel/ice/ice_ethdev.h | 2 +
drivers/net/intel/ice/ice_rxtx.c | 11 +-
lib/ethdev/ethdev_driver.h | 25 ++
lib/ethdev/rte_ethdev.c | 202 ++++++++++++++++
lib/ethdev/rte_ethdev.h | 278 ++++++++++++++++++++++
11 files changed, 1047 insertions(+), 7 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
@ 2026-09-02 5:51 ` Rajesh Kumar
2026-09-02 14:13 ` Stephen Hemminger
2026-09-02 5:51 ` [RFC PATCH v4 2/3] net/ice: support per-packet Tx timestamp slots Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 3/3] app/testpmd: add Tx timestamp capabilities command Rajesh Kumar
2 siblings, 1 reply; 20+ messages in thread
From: Rajesh Kumar @ 2026-09-02 5:51 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
Extend ethdev timesync with a capability model for selecting between
shared-register and per-packet Tx timestamping.
Add public and PMD interfaces to query timestamp capabilities, allocate
timestamp slots, retrieve timestamps asynchronously, and release slots.
Slots have port-global scope and can be used across Tx queues.
Add a dual-domain timestamp structure for reporting adjusted PHC time
and raw hardware time independently through validity flags.
Add APIs to register and unregister the mbuf dynamic field and dynflag
used to pass slot handles to the Tx datapath. Add helpers to associate
a slot handle with an mbuf before transmission.
Keep the legacy Tx timestamp API for shared-register hardware.
Document the timestamp capability model, slot lifecycle, and application
workflow.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
doc/guides/nics/features.rst | 16 +-
doc/guides/prog_guide/ethdev/index.rst | 1 +
doc/guides/prog_guide/ethdev/timesync.rst | 223 +++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/ethdev_driver.h | 25 ++
lib/ethdev/rte_ethdev.c | 202 ++++++++++++++++
lib/ethdev/rte_ethdev.h | 278 ++++++++++++++++++++++
7 files changed, 748 insertions(+), 4 deletions(-)
create mode 100644 doc/guides/prog_guide/ethdev/timesync.rst
diff --git a/doc/guides/nics/features.rst b/doc/guides/nics/features.rst
index 0b0c69e7cd..040a996156 100644
--- a/doc/guides/nics/features.rst
+++ b/doc/guides/nics/features.rst
@@ -692,14 +692,22 @@ Timesync
Supports IEEE1588/802.1AS timestamping.
-* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``
+* **[implements] eth_dev_ops**: ``timesync_enable``, ``timesync_disable``,
``timesync_read_rx_timestamp``, ``timesync_read_tx_timestamp``,
+ ``timesync_tx_ts_get_capabilities``, ``timesync_tx_timestamp_slot_alloc``,
+ ``timesync_read_tx_timestamp_slot``, ``timesync_tx_timestamp_slot_release``,
``timesync_adjust_time``, ``timesync_adjust_freq``,
``timesync_read_time``, ``timesync_write_time``.
* **[related] API**: ``rte_eth_timesync_enable()``, ``rte_eth_timesync_disable()``,
- ``rte_eth_timesync_read_rx_timestamp()``,
- ``rte_eth_timesync_read_tx_timestamp``, ``rte_eth_timesync_adjust_time()``,
- ``rte_eth_timesync_adjust_freq()``,
+ ``rte_eth_timesync_read_rx_timestamp()``, ``rte_eth_timesync_read_tx_timestamp()``,
+ ``rte_eth_timesync_tx_timestamp_slot_get_capabilities()``,
+ ``rte_eth_timesync_tx_timestamp_slot_alloc()``,
+ ``rte_eth_timesync_read_tx_timestamp_slot()``,
+ ``rte_eth_timesync_tx_timestamp_slot_release()``,
+ ``rte_eth_timesync_tx_slot_dynfield_register()``,
+ ``rte_eth_timesync_tx_slot_dynfield_unregister()``,
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()``,
+ ``rte_eth_timesync_adjust_time()``, ``rte_eth_timesync_adjust_freq()``,
``rte_eth_timesync_read_time()``, ``rte_eth_timesync_write_time()``.
diff --git a/doc/guides/prog_guide/ethdev/index.rst b/doc/guides/prog_guide/ethdev/index.rst
index 392ced0a2e..bc21f28091 100644
--- a/doc/guides/prog_guide/ethdev/index.rst
+++ b/doc/guides/prog_guide/ethdev/index.rst
@@ -13,3 +13,4 @@ Ethernet Device Library
traffic_metering_and_policing
traffic_management
qos_framework
+ timesync
diff --git a/doc/guides/prog_guide/ethdev/timesync.rst b/doc/guides/prog_guide/ethdev/timesync.rst
new file mode 100644
index 0000000000..6608486213
--- /dev/null
+++ b/doc/guides/prog_guide/ethdev/timesync.rst
@@ -0,0 +1,223 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+IEEE 1588 / PTP Timesync API
+============================
+
+Overview
+--------
+
+The DPDK IEEE 1588 / Precision Time Protocol (PTP) Timesync API provides
+a standardized framework for managing PTP Hardware Clocks (PHCs) and retrieving
+precise hardware transmit (Tx) and receive (Rx) timestamps.
+
+The Timesync framework encompasses three core capabilities:
+
+1. **Clock Control & Adjustment**: Enabling/disabling hardware timestamping, reading/setting clock time, and adjusting phase/frequency.
+2. **Receive Timestamping**: Hardware capture of incoming PTP packet arrival timestamps.
+3. **Transmit Timestamping**: Hardware capture of outbound PTP packet departure timestamps.
+
+
+Clock Management & Control
+--------------------------
+
+To initialize and discipline a port's PTP Hardware Clock (PHC), the API provides:
+
+* **Enable / Disable**:
+ ``rte_eth_timesync_enable(port_id)`` enables hardware timestamping on the specified port.
+ ``rte_eth_timesync_disable(port_id)`` disables timesync offloads.
+
+* **Clock Time Read / Write**:
+ ``rte_eth_timesync_read_time(port_id, &ts)`` reads the current PHC wall-clock time as a ``struct timespec``.
+ ``rte_eth_timesync_write_time(port_id, &ts)`` sets the PHC wall-clock time.
+
+* **Clock Adjustments**:
+ ``rte_eth_timesync_adjust_time(port_id, delta_ns)`` adjusts the clock phase by a delta offset in nanoseconds.
+ ``rte_eth_timesync_adjust_freq(port_id, scaled_ppm)`` adjusts the clock frequency in scaled parts-per-million (1 ppm = 1 << 16).
+
+
+Receive (Rx) Timestamping
+-------------------------
+
+When receive timestamping is enabled, the hardware identifies incoming PTP packets (e.g. IEEE 1588 EtherType ``0x88F7`` or UDP destination ports 319/320) and latches their arrival time.
+
+Rx Timestamp Extraction Workflow
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. On packet reception via ``rte_eth_rx_burst()``, the PMD checks if the received mbuf represents a PTP packet.
+2. The PMD sets the ``RTE_MBUF_F_RX_IEEE1588_PTP`` flag in ``mbuf->ol_flags``.
+3. Depending on the PMD and hardware capability, the Rx timestamp is either:
+ * **Extracted via API**: Application calls ``rte_eth_timesync_read_rx_timestamp(port_id, &ts, flags)``.
+ * **Inlined in Mbuf**: Stored in a registered mbuf dynamic field (e.g. ``rte_mbuf_dyn_rx_timestamp_register()``).
+
+
+Transmit (Tx) Timestamping Architectures
+----------------------------------------
+
+The framework supports two hardware transmit timestamping architectures:
+
+* **Single Shared Register** (``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG``):
+ The hardware contains a single shared transmit timestamp latch register.
+ Only one outbound packet can be timestamped at a time across the entire port.
+ The application calls ``rte_eth_timesync_read_tx_timestamp(port_id, &ts)`` to retrieve the departure time.
+
+* **Per-Packet Slot Bank** (``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET``):
+ The hardware provides a bank of independent transmit timestamp slots or
+ descriptors. Multiple outbound PTP packets can be timestamped concurrently and
+ correlated asynchronously on a per-packet basis using slot handles.
+
+
+Dual-Domain Timestamps
+~~~~~~~~~~~~~~~~~~~~~~
+
+When retrieving transmit timestamps using slot handles, the API returns
+a dual-domain timestamp structure:
+
+.. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns; /**< PHC adjusted time (wall-clock nanoseconds) */
+ int64_t raw_ns; /**< Free-running hardware cycle counter or raw nanoseconds */
+ uint32_t valid_mask; /**< Validity bits for the adjusted/raw domains */
+ };
+
+* **Adjusted Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID``):
+ Represents the wall-clock time after frequency adjustments (``rte_eth_timesync_adjust_freq``)
+ or phase steps (``rte_eth_timesync_adjust_time``) have been applied.
+
+* **Raw Domain** (``RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID``):
+ Represents the unadjusted free-running hardware cycle counter or raw timestamp.
+ This domain is required when correlating adjusted wall-clock time with the
+ underlying hardware timebase or when performing cross-timestamp analysis.
+
+
+Per-Packet Tx Timestamp Workflow
+--------------------------------
+
+To use per-packet transmit timestamping, applications follow this sequence:
+
+1. **Query Port Capabilities**
+ Determine whether the PMD supports slot-based per-packet timestamping:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_tx_ts_caps caps;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_get_capabilities(port_id, &caps);
+ if (ret == 0 && caps.type == RTE_ETH_TIMESYNC_TX_TS_PER_PACKET) {
+ printf("Port %u supports per-packet timestamping with %u max slots\n",
+ port_id, caps.max_slots);
+ }
+
+2. **Register Mbuf Dynamic Fields**
+ Register the dynamic field and dynamic flag used to pass slot handles to the Tx datapath:
+
+ .. code-block:: c
+
+ ret = rte_eth_timesync_tx_slot_dynfield_register();
+ if (ret < 0) {
+ /* Dynamic field space exhausted or registration failed */
+ }
+
+ .. note::
+
+ This should be called before the first ``rte_pktmbuf_pool_create()`` so that
+ the dynamic field is guaranteed space in the mbuf layout. It may be called
+ before or after ``rte_eth_timesync_enable()``: both refresh the per-port slot
+ metadata that PMD Tx datapaths read.
+
+3. **Allocate a Timestamp Slot**
+ Before transmitting a PTP packet requiring a transmit timestamp, allocate a slot handle:
+
+ .. code-block:: c
+
+ uint32_t slot_id;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_alloc(port_id, &slot_id);
+ if (ret != 0) {
+ /* Handle allocation error (e.g. -ENOSPC if all slots are in flight) */
+ }
+
+4. **Stamp the Mbuf**
+ Attach the allocated slot handle to the mbuf:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_stamp_mbuf(port_id, slot_id, mbuf);
+ mbuf->ol_flags |= RTE_MBUF_F_TX_IEEE1588_TMST;
+
+5. **Transmit the Packet**
+ Send the packet via ``rte_eth_tx_burst()`` as usual.
+
+6. **Poll for Timestamp Completion**
+ Read the captured timestamp using the allocated slot handle:
+
+ .. code-block:: c
+
+ struct rte_eth_timesync_dual_domain_timestamp ts;
+
+ ret = rte_eth_timesync_read_tx_timestamp_slot(port_id, slot_id, &ts);
+ if (ret == 0) {
+ /* Timestamp is ready */
+ if (ts.valid_mask & RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID) {
+ /* Process ts.adjusted_ns */
+ }
+ } else if (ret == -EAGAIN) {
+ /* Timestamp hardware processing is still pending; retry later */
+ }
+
+7. **Release the Slot**
+ After successfully reading the timestamp or timing out, release the slot handle:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_timestamp_slot_release(port_id, slot_id);
+
+8. **Unregister Dynfield State on Shutdown (Optional)**
+ When shutting down timesync offloads, the application can unregister the cached dynfield state:
+
+ .. code-block:: c
+
+ rte_eth_timesync_tx_slot_dynfield_unregister();
+
+ .. note::
+
+ This resets process-local dynfield state so subsequent
+ ``rte_eth_timesync_tx_timestamp_stamp_mbuf()`` calls return ``-ENOTSUP``
+ and PMD Tx datapaths fall back to port-level legacy mode.
+ Note that underlying mbuf dynfield bytes remain allocated in DPDK layout as DPDK does not
+ support dynamic field deallocation.
+
+
+PMD Implementation Requirements
+-------------------------------
+
+To support full timesync capabilities, a Poll Mode Driver (PMD) implements the following driver contract:
+
+1. **Clock Operations** (``timesync_enable``, ``timesync_disable``, ``timesync_read_time``, ``timesync_write_time``, ``timesync_adjust_time``, ``timesync_adjust_freq``)
+ * Controls hardware timestamp generation and disciplines the hardware clock registers.
+
+2. **Rx Timestamping** (``timesync_read_rx_timestamp``)
+ * Configures Rx filters to latch incoming PTP arrival times and flags received mbufs with ``RTE_MBUF_F_RX_IEEE1588_PTP``.
+
+3. **Tx Slot Capability Reporting** (``timesync_tx_ts_get_capabilities``)
+ * Reports ``RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG`` or ``RTE_ETH_TIMESYNC_TX_TS_PER_PACKET`` in `caps->type` and sets `caps->max_slots`.
+
+4. **Slot Allocation & Release** (``timesync_tx_timestamp_slot_alloc`` / ``timesync_tx_timestamp_slot_release``)
+ * Maintains a port-global pool or bitmap of hardware timestamp slots.
+ * `timesync_tx_timestamp_slot_alloc` returns a port-unique slot identifier and returns ``-ENOSPC`` when no slots are free.
+ * `timesync_tx_timestamp_slot_release` clears hardware slot state and returns the slot handle to the free pool.
+
+5. **Tx Datapath Integration**
+ * Checks if ``RTE_MBUF_F_TX_IEEE1588_TMST`` is set on `mbuf->ol_flags`.
+ * For per-packet slot mode, obtains the port slot metadata with
+ ``rte_eth_timesync_tx_slot_info_get(port_id)`` and, when `ol_flags` carries the
+ reported dynflag, retrieves `slot_id` from the mbuf dynamic field via
+ ``*RTE_MBUF_DYNFIELD(m, info->offset, uint32_t *)``.
+ A zero dynflag means the port is not using per-packet slots, so no offset
+ validation is needed.
+ * Configures hardware Tx descriptors to capture departure timestamps into the specified slot.
+
+6. **Tx Slot Timestamp Retrieval** (``timesync_read_tx_timestamp_slot``)
+ * Queries hardware slot or descriptor completion ring corresponding to `slot_id`.
+ * Populates ``struct rte_eth_timesync_dual_domain_timestamp`` and returns ``0`` when ready, or ``-EAGAIN`` if pending.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..122d44afeb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,13 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **ethdev: Added experimental per-packet Tx timestamp slot APIs.**
+
+ Added slot-based TX timestamp allocation, mbuf stamping, and per-packet
+ timestamp reads for timesync-capable Ethernet devices. The new APIs support
+ both shared-register and slot-bank usage models through the
+ ``rte_eth_timesync_tx_timestamp_slot_*`` interface family.
+
Removed Items
-------------
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
index 0f336f9567..36c48727f6 100644
--- a/lib/ethdev/ethdev_driver.h
+++ b/lib/ethdev/ethdev_driver.h
@@ -795,6 +795,23 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,
typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,
struct timespec *timestamp);
+/** @internal Query TX timestamp hardware capability (single-register vs per-packet slot bank). */
+typedef int (*eth_timesync_tx_ts_get_caps_t)(struct rte_eth_dev *dev,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/** @internal Allocate a per-packet TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,
+ uint32_t *slot_id);
+
+/** @internal Read a dual-domain TX timestamp by slot handle. */
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/** @internal Release a previously allocated TX timestamp slot handle. */
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,
+ uint32_t slot_id);
+
/** @internal Function used to adjust the device clock. */
typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);
@@ -1561,6 +1578,14 @@ struct eth_dev_ops {
eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;
/** Read the IEEE1588/802.1AS Tx timestamp */
eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;
+ /** Query TX timestamp hardware capability (single-reg vs per-packet) */
+ eth_timesync_tx_ts_get_caps_t timesync_tx_ts_get_capabilities;
+ /** Allocate a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;
+ /** Read a TX timestamp using a slot handle */
+ eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;
+ /** Release a TX timestamp slot handle */
+ eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;
/** Adjust the device clock */
eth_timesync_adjust_time timesync_adjust_time;
/** Adjust the clock frequency */
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 9efeaf77cb..50089af381 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -21,6 +21,7 @@
#include <rte_mempool.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
#include <rte_errno.h>
#include <rte_spinlock.h>
#include <rte_string_fns.h>
@@ -5801,6 +5802,9 @@ RTE_INIT(eth_dev_init_fp_ops)
for (i = 0; i != RTE_DIM(rte_eth_fp_ops); i++)
eth_dev_fp_ops_reset(rte_eth_fp_ops + i);
+
+ for (i = 0; i != RTE_DIM(rte_eth_timesync_tx_slot_infos); i++)
+ rte_eth_timesync_tx_slot_infos[i].offset = -1;
}
RTE_INIT(eth_dev_init_cb_lists)
@@ -6603,6 +6607,8 @@ rte_eth_dev_set_mc_addr_list(uint16_t port_id,
return ret;
}
+static void eth_timesync_tx_slot_info_refresh(uint16_t port_id);
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_enable)
int
rte_eth_timesync_enable(uint16_t port_id)
@@ -6616,6 +6622,8 @@ rte_eth_timesync_enable(uint16_t port_id)
if (dev->dev_ops->timesync_enable == NULL)
return -ENOTSUP;
ret = eth_err(port_id, dev->dev_ops->timesync_enable(dev));
+ if (ret == 0)
+ eth_timesync_tx_slot_info_refresh(port_id);
rte_eth_trace_timesync_enable(port_id, ret);
@@ -6699,6 +6707,200 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (slot_id == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev, slot_id));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_get_capabilities, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (caps == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot get ethdev port %u Tx timestamp capabilities to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_tx_ts_get_capabilities == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_ts_get_capabilities(dev, caps));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)
+int
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (timestamp == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR,
+ "Cannot read ethdev port %u Tx timestamp slot to NULL",
+ port_id);
+ return -EINVAL;
+ }
+
+ if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_read_tx_timestamp_slot(dev,
+ slot_id, timestamp));
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)
+int
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)
+{
+ struct rte_eth_dev *dev;
+
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ dev = &rte_eth_devices[port_id];
+
+ if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)
+ return -ENOTSUP;
+
+ return eth_err(port_id,
+ dev->dev_ops->timesync_tx_timestamp_slot_release(dev,
+ slot_id));
+}
+/* Internal process-local cache for Tx timestamp slot mbuf metadata. */
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;
+static uint64_t rte_eth_timesync_tx_slot_dynflag;
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_infos, 26.11)
+struct rte_eth_timesync_tx_slot_info
+rte_eth_timesync_tx_slot_infos[RTE_MAX_ETHPORTS];
+
+/*
+ * Publish the slot metadata for a port, gated on the driver reporting
+ * per-packet slots so unrelated ports keep a zero dynflag.
+ */
+static void
+eth_timesync_tx_slot_info_refresh(uint16_t port_id)
+{
+ struct rte_eth_timesync_tx_slot_info *info;
+ struct rte_eth_timesync_tx_ts_caps caps;
+
+ info = &rte_eth_timesync_tx_slot_infos[port_id];
+ info->offset = -1;
+ info->dynflag = 0;
+
+ if (rte_eth_timesync_tx_timestamp_slot_get_capabilities(port_id, &caps) != 0 ||
+ caps.type != RTE_ETH_TIMESYNC_TX_TS_PER_PACKET)
+ return;
+
+ info->offset = rte_eth_timesync_tx_slot_dynfield_offset;
+ info->dynflag = rte_eth_timesync_tx_slot_dynflag;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_register, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_register(void)
+{
+ const struct rte_mbuf_dynfield slot_dynfield = {
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
+ .size = sizeof(uint32_t),
+ .align = alignof(uint32_t),
+ };
+ uint16_t port_id;
+
+ if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
+ return 0;
+
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_register(&slot_dynfield);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ rte_eth_timesync_tx_slot_dynfield_offset =
+ rte_mbuf_dynfield_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
+ if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
+ return -ENOTSUP;
+
+ {
+ int flag_bit = rte_mbuf_dynflag_register(
+ &(const struct rte_mbuf_dynflag){
+ .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME});
+ if (flag_bit < 0)
+ flag_bit = rte_mbuf_dynflag_lookup(
+ RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME, NULL);
+ if (flag_bit < 0)
+ return -ENOTSUP;
+ rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
+ }
+
+ RTE_ETH_FOREACH_VALID_DEV(port_id)
+ eth_timesync_tx_slot_info_refresh(port_id);
+
+ return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_unregister, 26.11)
+int
+rte_eth_timesync_tx_slot_dynfield_unregister(void)
+{
+ uint16_t port_id;
+
+ /* Reset cached state without freeing dynamic-field bytes. */
+ rte_eth_timesync_tx_slot_dynfield_offset = -1;
+ rte_eth_timesync_tx_slot_dynflag = 0;
+
+ RTE_ETH_FOREACH_VALID_DEV(port_id)
+ eth_timesync_tx_slot_info_refresh(port_id);
+
+ return 0;
+}
+
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
+int
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m)
+{
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+ if (m == NULL)
+ return -EINVAL;
+ if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
+ return -ENOTSUP;
+ *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
+ uint32_t *) = slot_id;
+ m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
+ return 0;
+}
+
RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)
int
rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ee400b386f..bde391dea4 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5513,6 +5513,19 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
/**
* Read an IEEE1588/802.1AS Tx timestamp from an Ethernet device.
*
+ * This is the legacy Tx timestamp API and is intended for register-based
+ * timestamp reads. It does not provide per-packet correlation.
+ *
+ * Applications requiring per-packet Tx timestamp correlation should use the
+ * slot-based APIs:
+ * - Setup: rte_eth_timesync_tx_slot_dynfield_register()
+ * - Runtime per-packet loop:
+ * - rte_eth_timesync_tx_timestamp_slot_alloc()
+ * - rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * - rte_eth_timesync_read_tx_timestamp_slot()
+ * - rte_eth_timesync_tx_timestamp_slot_release()
+ * - Teardown: rte_eth_timesync_tx_slot_dynfield_unregister()
+ *
* @param port_id
* The port identifier of the Ethernet device.
* @param timestamp
@@ -5528,6 +5541,271 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,
struct timespec *timestamp);
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID RTE_BIT32(0)
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.raw_ns. */
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_RAW_VALID RTE_BIT32(1)
+
+/**
+ * Dual-domain TX timestamp payload in nanoseconds.
+ *
+ * `adjusted_ns` is the synchronized/adjusted domain.
+ * `raw_ns` is the free-running raw hardware clock domain.
+ *
+ * Scalar `int64_t` nanoseconds are used (instead of `struct timespec`) to
+ * keep both domains compact in one payload and to avoid extra split/merge
+ * conversions when processing per-packet timestamp correlation data.
+ */
+struct rte_eth_timesync_dual_domain_timestamp {
+ int64_t adjusted_ns;
+ int64_t raw_ns;
+ uint32_t valid_mask;
+};
+
+/** TX timestamp retrieval mechanism supported by a port. */
+enum rte_eth_timesync_tx_ts_type {
+ RTE_ETH_TIMESYNC_TX_TS_NONE = 0, /**< not supported */
+ /** One hardware latch register shared across all packets. */
+ RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG = 1,
+ /** Per-packet slot bank supports concurrent in-flight correlation. */
+ RTE_ETH_TIMESYNC_TX_TS_PER_PACKET = 2,
+};
+
+/**
+ * TX timestamp capabilities returned by
+ * rte_eth_timesync_tx_timestamp_slot_get_capabilities().
+ */
+struct rte_eth_timesync_tx_ts_caps {
+ enum rte_eth_timesync_tx_ts_type type; /**< mechanism supported by this port */
+ uint32_t max_slots; /**< concurrent slots available; valid only for PER_PACKET */
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Query the TX timestamp capability of a port.
+ *
+ * Reports whether the hardware uses a single shared latch register
+ * (RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG) or a per-packet slot bank
+ * (RTE_ETH_TIMESYNC_TX_TS_PER_PACKET), and how many concurrent slots exist.
+ *
+ * Use this to choose between:
+ * - Slot-based: rte_eth_timesync_tx_timestamp_slot_alloc() +
+ * rte_eth_timesync_read_tx_timestamp_slot()
+ * - Legacy: rte_eth_timesync_read_tx_timestamp()
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param caps
+ * Output TX timestamp capability structure.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Allocate a TX timestamp slot handle for per-packet timestamp correlation.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * Slots are allocated from a port-global pool and can be used across any
+ * TX queue on the port. The application stamps an mbuf with the slot handle
+ * using rte_eth_timesync_tx_timestamp_stamp_mbuf() before transmission.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Output handle identifying the allocated slot (port-global scope).
+ *
+ * @return
+ * - 0: Success.
+ * - -ENOSPC: No free slots are available.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
+ uint32_t *slot_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Read a per-packet TX timestamp using a previously allocated slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param timestamp
+ * Output dual-domain timestamp payload.
+ *
+ * @return
+ * - 0: Success.
+ * - -EAGAIN: Timestamp is not ready yet.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
+ uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Release a previously allocated TX timestamp slot handle.
+ *
+ * Intended for PTP/event timestamping rates.
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device.
+ * @param slot_id
+ * Slot handle to release.
+ *
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EIO: if device is removed.
+ * - -ENOTSUP: The function is not supported by the Ethernet driver.
+ * - -EINVAL: Invalid parameters.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,
+ uint32_t slot_id);
+
+/** Mbuf dynfield name for the TX timestamp slot handle. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"
+/** Mbuf dynflag name indicating TX timestamp slot handle is present. */
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME "rte_eth_timesync_tx_slot_flag"
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this structure may change without prior notice.
+ *
+ * Per-port TX timestamp slot mbuf metadata, for use by PMD TX datapaths.
+ *
+ * `dynflag` is zero unless the port reports RTE_ETH_TIMESYNC_TX_TS_PER_PACKET
+ * and the dynfield has been registered in this process, so a PMD can steer on
+ * `dynflag` alone without validating `offset`.
+ */
+struct rte_eth_timesync_tx_slot_info {
+ int32_t offset; /**< mbuf dynfield byte offset, -1 if unregistered */
+ uint64_t dynflag; /**< mbuf dynflag mask, 0 if unused by this port */
+};
+
+/** @internal Backing storage for rte_eth_timesync_tx_slot_info_get(). */
+extern struct rte_eth_timesync_tx_slot_info
+ rte_eth_timesync_tx_slot_infos[RTE_MAX_ETHPORTS];
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Get the TX timestamp slot mbuf metadata of a port.
+ *
+ * Intended for PMD TX datapaths. The returned pointer is stable for the
+ * lifetime of the process; its contents are updated by
+ * rte_eth_timesync_enable() and rte_eth_timesync_tx_slot_dynfield_register().
+ *
+ * @param port_id
+ * The port identifier of the Ethernet device. Not validated.
+ * @return
+ * Pointer to the slot metadata of the port.
+ */
+__rte_experimental
+static inline const struct rte_eth_timesync_tx_slot_info *
+rte_eth_timesync_tx_slot_info_get(uint16_t port_id)
+{
+ return &rte_eth_timesync_tx_slot_infos[port_id];
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Register the per-packet TX timestamp slot dynfield and dynflag in the mbuf
+ * layout.
+ *
+ * Must be called before the first rte_pktmbuf_pool_create() when the
+ * application intends to use rte_eth_timesync_tx_timestamp_stamp_mbuf()
+ * for per-packet TX timestamp correlation. Calling it after pool creation
+ * may still succeed if the default dynfield area has not been exhausted.
+ *
+ * It may be called before or after rte_eth_timesync_enable(): both refresh
+ * the per-port metadata returned by rte_eth_timesync_tx_slot_info_get().
+ *
+ * Note: dynfields and dynflags cannot be unregistered in DPDK. Once
+ * registered they remain allocated for the lifetime of the process, whether
+ * or not the application ultimately uses per-packet slot correlation.
+ *
+ * @return
+ * - 0: Success (or already registered).
+ * - -ENOTSUP: Registration and lookup both failed (no dynfield space).
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_register(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Disable per-packet TX timestamp slot correlation for this process.
+ *
+ * Resets the cached dynfield offset and dynflag to their unregistered state.
+ * After this call rte_eth_timesync_tx_timestamp_stamp_mbuf() returns
+ * -ENOTSUP and the PMD TX path falls back to the port-level ptp_tx_index
+ * (legacy mode) on every port.
+ *
+ * The underlying DPDK dynfield bytes are NOT freed — DPDK provides no dynfield
+ * deallocation. The 4 bytes per mbuf remain allocated but dormant.
+ *
+ * @return Always 0.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_slot_dynfield_unregister(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Set TX timestamp slot metadata in an mbuf so the TX path steers the
+ * NIC to capture the timestamp in the correct per-packet slot.
+ *
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.
+ *
+ * @param port_id The port identifier of the Ethernet device.
+ * @param slot_id Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().
+ * @param m Mbuf to stamp.
+ * @return
+ * - 0: Success.
+ * - -ENODEV: The port ID is invalid.
+ * - -EINVAL: Invalid parameters.
+ * - -ENOTSUP: Registration/lookup failed.
+ */
+__rte_experimental
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
+ uint32_t slot_id, struct rte_mbuf *m);
+
/**
* Adjust the timesync clock on an Ethernet device.
*
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [RFC PATCH v4 2/3] net/ice: support per-packet Tx timestamp slots
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
@ 2026-09-02 5:51 ` Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 3/3] app/testpmd: add Tx timestamp capabilities command Rajesh Kumar
2 siblings, 0 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-09-02 5:51 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
The ice PHY provides 64 Tx timestamp slots per port, but the driver only
ever used a single static slot (ptp_tx_index), which limits PTP Tx
timestamping to one in-flight packet at a time.
Implement the ethdev Tx timestamp slot management ops on top of an atomic
per-adapter slot bitmap:
- timesync_tx_ts_get_capabilities reports the per-packet slot type and
the number of slots,
- timesync_tx_timestamp_slot_alloc reserves a free slot using a
compare-exchange loop; on E822 the 64 quad slots are split evenly
between the PFs sharing the quad so allocations never overlap,
- timesync_read_tx_timestamp_slot polls the PHY ready bitmap for the
given slot and returns the adjusted timestamp,
- timesync_tx_timestamp_slot_release clears the PHY timestamp (E810)
and frees the bitmap bit.
The Tx context descriptor now takes the timestamp index from the mbuf
dynamic field registered by the ethdev layer when the packet carries the
Tx timestamp slot dynamic flag, and falls back to the legacy static index
otherwise.
While at it, make the legacy single-slot read path use ptp_tx_index
instead of a hardcoded slot 0, and clear the PHY timestamp on E810 after
a read or a timeout so a stale entry cannot block later requests.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
drivers/net/intel/ice/ice_ethdev.c | 210 ++++++++++++++++++++++++++++-
drivers/net/intel/ice/ice_ethdev.h | 2 +
drivers/net/intel/ice/ice_rxtx.c | 11 +-
3 files changed, 220 insertions(+), 3 deletions(-)
diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
index 76b8ff0a72..0109dc2621 100644
--- a/drivers/net/intel/ice/ice_ethdev.c
+++ b/drivers/net/intel/ice/ice_ethdev.c
@@ -196,6 +196,14 @@ static int ice_timesync_read_rx_timestamp(struct rte_eth_dev *dev,
uint32_t flags);
static int ice_timesync_read_tx_timestamp(struct rte_eth_dev *dev,
struct timespec *timestamp);
+static int ice_timesync_tx_timestamp_slot_alloc(struct rte_eth_dev *dev,
+ uint32_t *slot_id);
+static int ice_timesync_tx_ts_get_capabilities(struct rte_eth_dev *dev,
+ struct rte_eth_timesync_tx_ts_caps *caps);
+static int ice_timesync_read_tx_timestamp_slot(struct rte_eth_dev *dev, uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp);
+static int ice_timesync_tx_timestamp_slot_release(struct rte_eth_dev *dev,
+ uint32_t slot_id);
static int ice_timesync_adjust_time(struct rte_eth_dev *dev, int64_t delta);
static int ice_timesync_adjust_freq(struct rte_eth_dev *dev, int64_t ppm);
static int ice_timesync_read_time(struct rte_eth_dev *dev,
@@ -340,6 +348,10 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
.timesync_enable = ice_timesync_enable,
.timesync_read_rx_timestamp = ice_timesync_read_rx_timestamp,
.timesync_read_tx_timestamp = ice_timesync_read_tx_timestamp,
+ .timesync_tx_timestamp_slot_alloc = ice_timesync_tx_timestamp_slot_alloc,
+ .timesync_tx_ts_get_capabilities = ice_timesync_tx_ts_get_capabilities,
+ .timesync_read_tx_timestamp_slot = ice_timesync_read_tx_timestamp_slot,
+ .timesync_tx_timestamp_slot_release = ice_timesync_tx_timestamp_slot_release,
.timesync_adjust_time = ice_timesync_adjust_time,
.timesync_adjust_freq = ice_timesync_adjust_freq,
.timesync_read_time = ice_timesync_read_time,
@@ -7157,6 +7169,188 @@ static int ice_ptp_write_init(struct ice_hw *hw)
return ice_ptp_init_time(hw, ns, true);
}
+/*
+ * Allocate one TX timestamp slot from the per-port 64-slot bitmap using CAS.
+ * Returns slot index [0..max-1] or -ENOSPC if all slots are taken.
+ * For E822, max is capped so PFs sharing a quad use non-overlapping ranges.
+ */
+static int
+ice_ptp_alloc_tx_slot(struct ice_adapter *ad)
+{
+ uint64_t old, new_bm, range_mask, free_in_range;
+ uint8_t slot, max_slots, base_slot;
+ bool swapped;
+
+ /*
+ * E822: multiple PFs share one quad's 64 slots.
+ * Divide evenly: each PF occupies (64 / ports_per_quad) slots starting
+ * at (pf_offset_in_quad * slots_per_pf).
+ */
+ if (ad->hw.phy_model == ICE_PHY_E822) {
+ uint8_t ppq = ICE_PORTS_PER_QUAD;
+ uint8_t slots_per_pf = 64 / ppq;
+ uint8_t pf_offset = ad->hw.pf_id % ppq;
+
+ base_slot = pf_offset * slots_per_pf;
+ max_slots = slots_per_pf;
+ } else {
+ /* E810, E830, ETH56G: full 64 slots per PF BAR / per lport */
+ base_slot = 0;
+ max_slots = 64;
+ }
+
+ range_mask = (max_slots == 64) ? UINT64_MAX :
+ (((uint64_t)1 << max_slots) - 1) << base_slot;
+
+ do {
+ old = rte_atomic_load_explicit(&ad->ts_slot_bitmap,
+ rte_memory_order_relaxed);
+ free_in_range = ~old & range_mask;
+ if (free_in_range == 0)
+ return -ENOSPC;
+
+ slot = (uint8_t)rte_ctz64(free_in_range);
+ new_bm = old | RTE_BIT64(slot);
+ swapped = rte_atomic_compare_exchange_weak_explicit(&ad->ts_slot_bitmap,
+ &old, new_bm, rte_memory_order_acquire,
+ rte_memory_order_relaxed);
+ } while (!swapped);
+ return slot;
+}
+
+/* Release a TX timestamp slot back to the bitmap. */
+static void
+ice_ptp_release_tx_slot(struct ice_adapter *ad, uint8_t slot)
+{
+ rte_atomic_fetch_and_explicit(&ad->ts_slot_bitmap, ~RTE_BIT64(slot),
+ rte_memory_order_release);
+}
+
+/* Allocate a TX timestamp slot from the per-port bitmap for the per-packet slot API. */
+static int
+ice_get_next_tx_desc_idx(struct rte_eth_dev *dev)
+{
+ struct ice_adapter *ad;
+ int slot;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ ad = ICE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
+ slot = ice_ptp_alloc_tx_slot(ad);
+ if (slot < 0) {
+ PMD_DRV_LOG(DEBUG, "PTP TX: all 64 timestamp slots busy");
+ return slot;
+ }
+
+ /*
+ * Do NOT update ptp_tx_index: that field belongs to the legacy
+ * single-inflight path and is set statically in ice_ptp_init_info().
+ */
+ return slot;
+}
+
+static int
+ice_ptp_read_tx_dual_timestamp(struct rte_eth_dev *dev, uint8_t slot,
+ struct rte_eth_timesync_dual_domain_timestamp *dual)
+{
+ struct ice_adapter *ad;
+ struct ice_hw *hw;
+ uint64_t tstamp_ready, tstamp, adjusted_ns;
+ int ret;
+
+ if (dev == NULL || dual == NULL)
+ return -EINVAL;
+
+ ad = ICE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
+ hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+
+ ret = ice_get_phy_tx_tstamp_ready(hw, ad->ptp_tx_block, &tstamp_ready);
+ if (ret)
+ return -EAGAIN;
+
+ if (!(tstamp_ready & RTE_BIT64(slot)))
+ return -EAGAIN;
+
+ ret = ice_read_phy_tstamp(hw, ad->ptp_tx_block, slot, &tstamp);
+ if (ret || tstamp == 0)
+ return -EAGAIN;
+
+ adjusted_ns = ice_tstamp_convert_32b_64b(hw, ad, 1,
+ (tstamp >> 8) & 0xFFFFFFFF);
+
+ dual->adjusted_ns = (int64_t)adjusted_ns;
+ dual->raw_ns = 0;
+ dual->valid_mask = RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID;
+
+ return 0;
+}
+
+static void
+ice_ptp_free_tx_slot(struct rte_eth_dev *dev, uint8_t slot)
+{
+ struct ice_adapter *ad;
+ struct ice_hw *hw;
+
+ if (dev == NULL)
+ return;
+
+ ad = ICE_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
+ hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+
+ if (hw->phy_model == ICE_PHY_E810)
+ (void)ice_clear_phy_tstamp(hw, ad->ptp_tx_block, slot);
+ ice_ptp_release_tx_slot(ad, slot);
+}
+
+static int
+ice_timesync_tx_ts_get_capabilities(struct rte_eth_dev *dev __rte_unused,
+ struct rte_eth_timesync_tx_ts_caps *caps)
+{
+ caps->type = RTE_ETH_TIMESYNC_TX_TS_PER_PACKET;
+ caps->max_slots = 64;
+ return 0;
+}
+
+static int
+ice_timesync_tx_timestamp_slot_alloc(struct rte_eth_dev *dev,
+ uint32_t *slot_id)
+{
+ int idx;
+
+ if (dev == NULL || slot_id == NULL)
+ return -EINVAL;
+
+ idx = ice_get_next_tx_desc_idx(dev);
+ if (idx < 0)
+ return idx;
+
+ *slot_id = (uint32_t)(uint16_t)idx;
+ return 0;
+}
+
+static int
+ice_timesync_read_tx_timestamp_slot(struct rte_eth_dev *dev, uint32_t slot_id,
+ struct rte_eth_timesync_dual_domain_timestamp *timestamp)
+{
+ if (dev == NULL || timestamp == NULL || slot_id > 63)
+ return -EINVAL;
+
+ memset(timestamp, 0, sizeof(*timestamp));
+
+ return ice_ptp_read_tx_dual_timestamp(dev, (uint8_t)slot_id, timestamp);
+}
+
+static int
+ice_timesync_tx_timestamp_slot_release(struct rte_eth_dev *dev, uint32_t slot_id)
+{
+ if (dev == NULL || slot_id > 63)
+ return -EINVAL;
+
+ ice_ptp_free_tx_slot(dev, (uint8_t)slot_id);
+ return 0;
+}
+
static int
ice_timesync_enable(struct rte_eth_dev *dev)
{
@@ -7239,6 +7433,11 @@ ice_timesync_read_tx_timestamp(struct rte_eth_dev *dev,
/* Set the end time with a delay of 10 microseconds */
end_time = rte_get_timer_cycles() + (rte_get_timer_hz() / 100000);
+ /*
+ * ptp_tx_index is a static slot set in ice_ptp_init_info(); it is NOT
+ * allocated via ice_ptp_alloc_tx_slot() so the bitmap must not be
+ * touched.
+ */
do {
ret = ice_get_phy_tx_tstamp_ready(hw, ad->ptp_tx_block, &tstamp_ready);
if (ret) {
@@ -7246,11 +7445,15 @@ ice_timesync_read_tx_timestamp(struct rte_eth_dev *dev,
return -1;
}
- if ((tstamp_ready & BIT_ULL(0)) == 0 && rte_get_timer_cycles() > end_time) {
+ if (!(tstamp_ready & BIT_ULL(ad->ptp_tx_index)) &&
+ rte_get_timer_cycles() > end_time) {
PMD_DRV_LOG(ERR, "Timeout to get phy ready for timestamp");
+ if (hw->phy_model == ICE_PHY_E810)
+ (void)ice_clear_phy_tstamp(hw, ad->ptp_tx_block,
+ ad->ptp_tx_index);
return -1;
}
- } while ((tstamp_ready & BIT_ULL(0)) == 0);
+ } while (!(tstamp_ready & BIT_ULL(ad->ptp_tx_index)));
ret = ice_read_phy_tstamp(hw, ad->ptp_tx_block, ad->ptp_tx_index, &tstamp);
if (ret || tstamp == 0) {
@@ -7261,6 +7464,9 @@ ice_timesync_read_tx_timestamp(struct rte_eth_dev *dev,
ts_ns = ice_tstamp_convert_32b_64b(hw, ad, 1, (tstamp >> 8) & mask);
*timestamp = rte_ns_to_timespec(ts_ns);
+ if (hw->phy_model == ICE_PHY_E810)
+ (void)ice_clear_phy_tstamp(hw, ad->ptp_tx_block, ad->ptp_tx_index);
+
return 0;
}
diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
index 7ee3ea8a70..d5fed14fae 100644
--- a/drivers/net/intel/ice/ice_ethdev.h
+++ b/drivers/net/intel/ice/ice_ethdev.h
@@ -684,6 +684,8 @@ struct ice_adapter {
/* For PTP */
uint8_t ptp_tx_block;
uint8_t ptp_tx_index;
+ /* Atomic bitmask of in-use TX timestamp slots (bit N = slot N occupied). */
+ RTE_ATOMIC(uint64_t)ts_slot_bitmap;
bool ptp_ena;
bool txpp_ena; /* For TxPP */
uint64_t time_hw;
diff --git a/drivers/net/intel/ice/ice_rxtx.c b/drivers/net/intel/ice/ice_rxtx.c
index c4b5454c53..25e51099e5 100644
--- a/drivers/net/intel/ice/ice_rxtx.c
+++ b/drivers/net/intel/ice/ice_rxtx.c
@@ -3038,10 +3038,19 @@ get_context_desc(uint64_t ol_flags, const struct rte_mbuf *tx_pkt,
const union ci_tx_offload *tx_offload, const struct ci_tx_queue *txq,
uint64_t *qw0, uint64_t *qw1)
{
+ const struct rte_eth_timesync_tx_slot_info *ts_info;
uint16_t cd_l2tag2 = 0;
uint64_t cd_type_cmd_tso_mss = ICE_TX_DESC_DTYPE_CTX;
uint32_t cd_tunneling_params = 0;
- uint64_t ptp_tx_index = txq->ice_vsi->adapter->ptp_tx_index;
+ uint64_t ptp_tx_index;
+
+ /* A zero dynflag means the port is not using per-packet slots. */
+ ts_info = rte_eth_timesync_tx_slot_info_get(txq->port_id);
+ if (ol_flags & ts_info->dynflag)
+ ptp_tx_index = *RTE_MBUF_DYNFIELD(tx_pkt, ts_info->offset,
+ uint32_t *);
+ else
+ ptp_tx_index = txq->ice_vsi->adapter->ptp_tx_index;
if (ice_calc_context_desc(ol_flags) == 0)
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [RFC PATCH v4 3/3] app/testpmd: add Tx timestamp capabilities command
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 2/3] net/ice: support per-packet Tx timestamp slots Rajesh Kumar
@ 2026-09-02 5:51 ` Rajesh Kumar
2 siblings, 0 replies; 20+ messages in thread
From: Rajesh Kumar @ 2026-09-02 5:51 UTC (permalink / raw)
To: dev
Cc: thomas, bruce.richardson, andrew.rybchenko, stephen,
aman.deep.singh, Rajesh Kumar
Add "show port <port_id> tx_timestamp_caps" to report the Tx timestamp
slot capabilities of a port: the slot type reported by the driver and the
number of available slots.
When the port supports per-packet slots, the command also performs an
allocate and release round-trip so the slot management API can be
exercised from the command line.
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com>
---
app/test-pmd/cmdline.c | 79 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 10ee7c5179..66276cee58 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -14213,6 +14213,84 @@ static cmdline_parse_inst_t cmd_set_dev_led = {
},
};
+/* *** show port tx_timestamp capabilities *** */
+struct cmd_show_port_tx_ts_caps_result {
+ cmdline_fixed_string_t show;
+ cmdline_fixed_string_t port;
+ portid_t port_id;
+ cmdline_fixed_string_t tx_timestamp_caps;
+};
+
+static void cmd_show_port_tx_ts_caps_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_show_port_tx_ts_caps_result *res = parsed_result;
+ struct rte_eth_timesync_tx_ts_caps caps;
+ uint32_t slot_id;
+ int ret;
+
+ ret = rte_eth_timesync_tx_timestamp_slot_get_capabilities(
+ res->port_id, &caps);
+ if (ret == -ENOTSUP) {
+ printf("Port %u: TX timestamp slot API not supported\n",
+ res->port_id);
+ return;
+ }
+ if (ret < 0) {
+ printf("Port %u: get capabilities failed (%d)\n",
+ res->port_id, ret);
+ return;
+ }
+
+ printf("Port %u TX timestamp capabilities:\n", res->port_id);
+ printf(" Type : %s\n",
+ caps.type == RTE_ETH_TIMESYNC_TX_TS_PER_PACKET ? "per-packet" :
+ caps.type == RTE_ETH_TIMESYNC_TX_TS_SINGLE_REG ? "single-reg" :
+ "none");
+ printf(" Max slots : %u\n", caps.max_slots);
+
+ if (caps.type != RTE_ETH_TIMESYNC_TX_TS_PER_PACKET)
+ return;
+
+ /* Quick alloc/release round-trip to prove the API works */
+ ret = rte_eth_timesync_tx_timestamp_slot_alloc(res->port_id, &slot_id);
+ if (ret == 0) {
+ printf(" Alloc test: slot_id=%u OK\n", slot_id);
+ ret = rte_eth_timesync_tx_timestamp_slot_release(
+ res->port_id, slot_id);
+ printf(" Release : %s\n", ret == 0 ? "OK" : "FAILED");
+ } else {
+ printf(" Alloc test: failed (%d)\n", ret);
+ }
+}
+
+static cmdline_parse_token_string_t cmd_show_port_tx_ts_caps_show =
+ TOKEN_STRING_INITIALIZER(struct cmd_show_port_tx_ts_caps_result,
+ show, "show");
+static cmdline_parse_token_string_t cmd_show_port_tx_ts_caps_port =
+ TOKEN_STRING_INITIALIZER(struct cmd_show_port_tx_ts_caps_result,
+ port, "port");
+static cmdline_parse_token_num_t cmd_show_port_tx_ts_caps_port_id =
+ TOKEN_NUM_INITIALIZER(struct cmd_show_port_tx_ts_caps_result,
+ port_id, RTE_UINT16);
+static cmdline_parse_token_string_t cmd_show_port_tx_ts_caps_keyword =
+ TOKEN_STRING_INITIALIZER(struct cmd_show_port_tx_ts_caps_result,
+ tx_timestamp_caps, "tx_timestamp_caps");
+
+static cmdline_parse_inst_t cmd_show_port_tx_ts_caps = {
+ .f = cmd_show_port_tx_ts_caps_parsed,
+ .data = NULL,
+ .help_str = "show port <port_id> tx_timestamp_caps",
+ .tokens = {
+ (void *)&cmd_show_port_tx_ts_caps_show,
+ (void *)&cmd_show_port_tx_ts_caps_port,
+ (void *)&cmd_show_port_tx_ts_caps_port_id,
+ (void *)&cmd_show_port_tx_ts_caps_keyword,
+ NULL,
+ },
+};
+
/* ******************************************************************************** */
/* list of instructions */
@@ -14469,6 +14547,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_set_port_cman_config,
&cmd_config_tx_affinity_map,
&cmd_set_dev_led,
+ &cmd_show_port_tx_ts_caps,
NULL,
};
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs
2026-09-02 5:51 ` [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
@ 2026-09-02 14:13 ` Stephen Hemminger
0 siblings, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-09-02 14:13 UTC (permalink / raw)
To: Rajesh Kumar
Cc: dev, thomas, bruce.richardson, andrew.rybchenko, aman.deep.singh
On Wed, 2 Sep 2026 11:21:22 +0530
Rajesh Kumar <rajesh3.kumar@intel.com> wrote:
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)
> +int
> +rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,
> + uint32_t *slot_id)
Could join to one line, max line line is now 100
> +{
> + struct rte_eth_dev *dev;
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + dev = &rte_eth_devices[port_id];
> +
> + if (slot_id == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR,
> + "Cannot allocate ethdev port %u Tx timestamp slot to NULL",
> + port_id);
Minor nit the wording of that error message is awkward.
Similar problem in other messages.
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_register, 26.11)
> +int
> +rte_eth_timesync_tx_slot_dynfield_register(void)
> +{
> + const struct rte_mbuf_dynfield slot_dynfield = {
> + .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,
> + .size = sizeof(uint32_t),
> + .align = alignof(uint32_t),
> + };
> + uint16_t port_id;
> +
> + if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)
> + return 0;
> +
> + rte_eth_timesync_tx_slot_dynfield_offset =
> + rte_mbuf_dynfield_register(&slot_dynfield);
> + if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
> + rte_eth_timesync_tx_slot_dynfield_offset =
> + rte_mbuf_dynfield_lookup(
> + RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);
> + if (rte_eth_timesync_tx_slot_dynfield_offset < 0)
> + return -ENOTSUP;
> +
> + {
> + int flag_bit = rte_mbuf_dynflag_register(
> + &(const struct rte_mbuf_dynflag){
> + .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME});
> + if (flag_bit < 0)
> + flag_bit = rte_mbuf_dynflag_lookup(
> + RTE_ETH_TIMESYNC_TX_SLOT_DYNFLAG_NAME, NULL);
> + if (flag_bit < 0)
> + return -ENOTSUP;
> + rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);
> + }
No need for basic block {} here.
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_dynfield_unregister, 26.11)
> +int
> +rte_eth_timesync_tx_slot_dynfield_unregister(void)
> +{
> + uint16_t port_id;
> +
> + /* Reset cached state without freeing dynamic-field bytes. */
> + rte_eth_timesync_tx_slot_dynfield_offset = -1;
> + rte_eth_timesync_tx_slot_dynflag = 0;
> +
> + RTE_ETH_FOREACH_VALID_DEV(port_id)
> + eth_timesync_tx_slot_info_refresh(port_id);
> +
> + return 0;
> +}
> +
If it always returns 0 why not void.
Not sure what the point of this function is. It doesn't really do anything.
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)
> +int
> +rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
> + uint32_t slot_id, struct rte_mbuf *m)
> +{
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + if (m == NULL)
> + return -EINVAL;
> + if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
> + return -ENOTSUP;
> + *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,
> + uint32_t *) = slot_id;
> + m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;
> + return 0;
> +}
This is possibly in data path, use unlikely() here.
> diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
> index ee400b386f..bde391dea4 100644
> --- a/lib/ethdev/rte_ethdev.h
> +++ b/lib/ethdev/rte_ethdev.h
> @@ -5513,6 +5513,19 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,
> /**
> * Read an IEEE1588/802.1AS Tx timestamp from an Ethernet device.
> *
> + * This is the legacy Tx timestamp API and is intended for register-based
> + * timestamp reads. It does not provide per-packet correlation.
> + *
Rather than weak guidance which will get ignored and stale.
1. Convert all in-tree uses of old API
2. Announce deprecation in this release
3. Mark legacy API as deprecated
AI had even more observations (Fable 5.1)
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_slot_infos, 26.11)
> +struct rte_eth_timesync_tx_slot_info
> +rte_eth_timesync_tx_slot_infos[RTE_MAX_ETHPORTS];
Exporting a RTE_MAX_ETHPORTS sized array from the public header bakes
build config into ABI. rte_eth_fp_ops lives in ethdev_driver.h, this
should too; only PMDs read it.
The per-port array also has no per-port content. offset and dynflag are
process globals; the only per-port part is "caps say PER_PACKET". Put
the two globals in ethdev_driver.h and let the PMD that implements
slots check them. Drops the array, the refresh loop, and the forward
declaration.
> +static void eth_timesync_tx_slot_info_refresh(uint16_t port_id);
Move the definitions above first use instead.
> + ret = eth_err(port_id, dev->dev_ops->timesync_enable(dev));
> + if (ret == 0)
> + eth_timesync_tx_slot_info_refresh(port_id);
No matching reset in timesync_disable. Info stays stale after disable.
> +int
> +rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,
> + uint32_t slot_id, struct rte_mbuf *m)
> +{
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> + if (m == NULL)
> + return -EINVAL;
> + if (rte_eth_timesync_tx_slot_dynfield_register() != 0)
> + return -ENOTSUP;
Calling register from the per-packet path is wrong. First call takes
the mbuf dyn lock and walks every port calling into driver dev_ops.
Header says "safe for concurrent callers"; it is not, two threads
racing on first stamp both run registration on plain globals.
port_id is validated but otherwise unused. Stamping a SINGLE_REG port
succeeds and sets a flag nothing reads. Check the port's slot info,
return -ENOTSUP if dynflag == 0, and require the app to have called
register up front (which the doc already says it must, before pool
create).
> + rte_eth_timesync_tx_slot_dynfield_offset = -1;
> + rte_eth_timesync_tx_slot_dynflag = 0;
Written unlocked, read from Tx datapath on other cores. Also cannot
free the dynfield. Agree with dropping unregister entirely.
> + * -ENOTSUP and the PMD TX path falls back to the port-level ptp_tx_index
> + * (legacy mode) on every port.
ptp_tx_index is an Intel driver internal. Does not belong in rte_ethdev.h.
> + * The underlying DPDK dynfield bytes are NOT freed — DPDK provides no dynfield
Non-ASCII dash in source.
> +typedef int (*eth_timesync_tx_ts_get_caps_t)(struct rte_eth_dev *dev,
...
> + eth_timesync_tx_ts_get_caps_t timesync_tx_ts_get_capabilities;
...
> +int rte_eth_timesync_tx_timestamp_slot_get_capabilities(uint16_t port_id,
...
> +int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,
Three spellings of the same op, and the read function breaks the
rte_eth_timesync_tx_timestamp_slot_* prefix the release note
advertises. One prefix for all of it, rte_eth_timesync_tx_slot_{caps,
alloc,read,release,stamp} is shorter and consistent.
Also TX/Tx mixed throughout comments and docs. Tx.
> +struct rte_eth_timesync_dual_domain_timestamp {
> + int64_t adjusted_ns;
> + int64_t raw_ns;
> + uint32_t valid_mask;
> +};
4 byte tail hole. Fine for experimental, but say so or reorder before
it goes stable.
> +++ b/doc/guides/prog_guide/ethdev/timesync.rst
Lines up to 150+ chars. Doc guideline is one sentence per line. Half
of this file documents existing clock/Rx API, which is a separate
patch from the slot feature.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs
2026-08-27 12:34 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
@ 2026-09-02 14:16 ` Stephen Hemminger
1 sibling, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-09-02 14:16 UTC (permalink / raw)
To: Rajesh Kumar
Cc: dev, thomas, bruce.richardson, andrew.rybchenko, aman.deep.singh
On Thu, 27 Aug 2026 18:04:33 +0530
Rajesh Kumar <rajesh3.kumar@intel.com> wrote:
> The ethdev timesync API currently exposes Tx timestamps through a shared
> hardware register. This requires applications to serialize timestamped
> packets and does not allow correlation when multiple packets are in
> flight.
>
> This RFC proposes an ethdev interface for hardware with independent Tx
> timestamp slots. The interface reports the supported timestamping
> mechanism, provides a port-global slot lifecycle, and lets applications
> poll each slot asynchronously after transmission.
Later review with AI assistance also reached conclusion:
Overall, this adds seven new experimental functions plus an exported
array to solve one problem. Before going further, cut it down:
- drop rte_eth_timesync_tx_slot_dynfield_unregister(), it does nothing
- drop rte_eth_timesync_tx_slot_dynfield_register(), do it inside
rte_eth_timesync_enable() and fail enable if there is no dynfield space
- drop rte_eth_timesync_tx_timestamp_stamp_mbuf(), export the offset
and flag the same way rte_mbuf_dyn_rx_timestamp does and let the app
write the field
- drop the rte_eth_timesync_tx_slot_infos[] array and the static inline,
PMDs get the two globals from ethdev_driver.h
That leaves caps, alloc, read, release.
For the legacy API: rather than a doc comment pointing at the new one,
have ethdev synthesize SINGLE_REG / max_slots=1 from
timesync_read_tx_timestamp for PMDs without slot ops, convert testpmd
ieee1588fwd and examples/ptpclient in this series, and add a
deprecation notice with a removal release. Otherwise we end up carrying
both forever.
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-09-02 14:16 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 19:24 [RFC 0/1] ethdev: per-packet Tx timestamp slot management Rajesh Kumar
2026-08-17 19:24 ` [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs Rajesh Kumar
2026-08-20 4:51 ` Naga Harish K, S V
2026-08-18 2:23 ` [RFC 0/1] ethdev: per-packet Tx timestamp slot management Stephen Hemminger
2026-08-20 4:41 ` Naga Harish K, S V
2026-08-27 11:09 ` Kumar, Rajesh
2026-08-27 12:13 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-08-27 12:13 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-08-27 12:18 ` [RFC PATCH v3 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-08-27 12:21 ` Rajesh Kumar
2026-08-27 12:21 ` [RFC PATCH v3 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-08-27 21:45 ` Stephen Hemminger
2026-09-02 5:51 ` [RFC PATCH v4 0/3] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 1/3] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-09-02 14:13 ` Stephen Hemminger
2026-09-02 5:51 ` [RFC PATCH v4 2/3] net/ice: support per-packet Tx timestamp slots Rajesh Kumar
2026-09-02 5:51 ` [RFC PATCH v4 3/3] app/testpmd: add Tx timestamp capabilities command Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Rajesh Kumar
2026-08-27 12:34 ` [RFC PATCH v2 1/1] ethdev: add Tx timestamp slot management APIs Rajesh Kumar
2026-09-02 14:16 ` [RFC PATCH v2 0/1] ethdev: add Tx timestamp slot APIs Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox