<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
<style type="text/css" style="display:none;"> P {margin-top:0;margin-bottom:0;} </style>
</head>
<body dir="ltr">
<div class="elementToProof" style="font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 11pt; color: rgb(0, 0, 0);">
<br>
</div>
<div><br>
</div>
<div style="font-family: Calibri, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
<br>
</div>
<hr style="display: inline-block; width: 98%;">
<div style="font-family: Calibri, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
<b>From:</b> Rajesh Kumar <rajesh3.kumar@intel.com><br>
<b>Sent:</b> Tuesday, August 18, 2026 12:54 AM<br>
<b>To:</b> dev@dpdk.org <dev@dpdk.org><br>
<b>Cc:</b> 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><br>
<b>Subject:</b> [RFC 1/1] ethdev: add per-packet Tx timestamp slot APIs </div>
<div style="font-family: Calibri, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
<br>
</div>
<div style="font-size: 11pt;">Add ethdev public and driver-facing APIs for per-packet Tx<br>
hardware timestamp slot management.<br>
<br>
The existing `rte_eth_timesync_read_tx_timestamp()` exposes a<br>
single shared latch, making it unreliable when multiple Tx<br>
timestamps are outstanding concurrently. PMDs with per-packet<br>
slot hardware cannot be exploited through this interface.<br>
<br>
Introduce a slot lifecycle API:<br>
- `rte_eth_timesync_tx_timestamp_slot_alloc()`: reserve a<br>
  hardware slot before transmit<br>
- `rte_eth_timesync_tx_timestamp_stamp_mbuf()`: embed slot<br>
  handle into mbuf dynfield for per-packet NIC steering<br>
- `rte_eth_timesync_read_tx_timestamp_slot()`: poll slot for<br>
  a captured timestamp; returns -EAGAIN if not ready<br>
- `rte_eth_timesync_tx_timestamp_slot_release()`: return slot<br>
  to PMD after readback or timeout<br>
<br>
Introduce `rte_eth_timesync_dual_domain_timestamp` to carry<br>
both the adjusted PHC time (`adjusted_ns`) and free-running<br>
cycles-domain time (`cycles_ns`), each with an individual<br>
validity bit in `valid_mask`.<br>
<br>
Signed-off-by: Rajesh Kumar <rajesh3.kumar@intel.com><br>
---<br>
 lib/ethdev/ethdev_driver.h |  19 ++++++<br>
 lib/ethdev/rte_ethdev.c    | 121 +++++++++++++++++++++++++++++++++++++<br>
 lib/ethdev/rte_ethdev.h    | 108 +++++++++++++++++++++++++++++++++<br>
 3 files changed, 248 insertions(+)<br>
<br>
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h<br>
index 0f336f9567..61dadcea49 100644<br>
--- a/lib/ethdev/ethdev_driver.h<br>
+++ b/lib/ethdev/ethdev_driver.h<br>
@@ -795,6 +795,19 @@ typedef int (*eth_timesync_read_rx_timestamp_t)(struct rte_eth_dev *dev,<br>
 typedef int (*eth_timesync_read_tx_timestamp_t)(struct rte_eth_dev *dev,<br>
                                                 struct timespec *timestamp);<br>
 <br>
+/** @internal Allocate a per-packet TX timestamp slot handle. */<br>
+typedef int (*eth_timesync_tx_timestamp_slot_alloc_t)(struct rte_eth_dev *dev,<br>
+               uint16_t tx_queue_id, uint32_t *slot_id);<br>
+<br>
+/** @internal Read TX timestamp by slot handle. */<br>
+typedef int (*eth_timesync_read_tx_timestamp_slot_t)(struct rte_eth_dev *dev,<br>
+               uint32_t slot_id,<br>
+               struct rte_eth_timesync_dual_domain_timestamp *timestamp);<br>
+<br>
+/** @internal Release a previously allocated TX timestamp slot handle. */<br>
+typedef int (*eth_timesync_tx_timestamp_slot_release_t)(struct rte_eth_dev *dev,<br>
+               uint32_t slot_id);<br>
+<br>
 /** @internal Function used to adjust the device clock. */<br>
 typedef int (*eth_timesync_adjust_time)(struct rte_eth_dev *dev, int64_t);<br>
 <br>
@@ -1561,6 +1574,12 @@ struct eth_dev_ops {<br>
         eth_timesync_read_rx_timestamp_t timesync_read_rx_timestamp;<br>
         /** Read the IEEE1588/802.1AS Tx timestamp */<br>
         eth_timesync_read_tx_timestamp_t timesync_read_tx_timestamp;<br>
+       /** Allocate a TX timestamp slot handle */<br>
+       eth_timesync_tx_timestamp_slot_alloc_t timesync_tx_timestamp_slot_alloc;<br>
+       /** Read a TX timestamp using a slot handle */<br>
+       eth_timesync_read_tx_timestamp_slot_t timesync_read_tx_timestamp_slot;<br>
+       /** Release a TX timestamp slot handle */<br>
+       eth_timesync_tx_timestamp_slot_release_t timesync_tx_timestamp_slot_release;<br>
         /** Adjust the device clock */<br>
         eth_timesync_adjust_time   timesync_adjust_time;<br>
         /** Adjust the clock frequency */<br>
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c<br>
index 9efeaf77cb..b955784594 100644<br>
--- a/lib/ethdev/rte_ethdev.c<br>
+++ b/lib/ethdev/rte_ethdev.c<br>
@@ -6699,6 +6699,127 @@ rte_eth_timesync_read_tx_timestamp(uint16_t port_id,<br>
 <br>
 }<br>
 <br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_alloc, 26.11)<br>
+int<br>
+rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,<br>
+                                        uint16_t tx_queue_id,<br>
+                                        uint32_t *slot_id)<br>
+{<br>
+       struct rte_eth_dev *dev;<br>
+<br>
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+       dev = &rte_eth_devices[port_id];<br>
+<br>
+       if (slot_id == NULL) {<br>
+               RTE_ETHDEV_LOG_LINE(ERR,<br>
+                       "Cannot allocate ethdev port %u Tx timestamp slot to NULL",<br>
+                       port_id);<br>
+               return -EINVAL;<br>
+       }<br>
+<br>
+       if (dev->dev_ops->timesync_tx_timestamp_slot_alloc == NULL)<br>
+               return -ENOTSUP;<br>
+<br>
+       return eth_err(port_id,<br>
+                       dev->dev_ops->timesync_tx_timestamp_slot_alloc(dev,<br>
+                               tx_queue_id, slot_id));<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_read_tx_timestamp_slot, 26.11)<br>
+int<br>
+rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,<br>
+                                       uint32_t slot_id,<br>
+                                       struct rte_eth_timesync_dual_domain_timestamp *timestamp)<br>
+{<br>
+       struct rte_eth_dev *dev;<br>
+<br>
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+       dev = &rte_eth_devices[port_id];<br>
+<br>
+       if (timestamp == NULL) {<br>
+               RTE_ETHDEV_LOG_LINE(ERR,<br>
+                       "Cannot read ethdev port %u Tx timestamp slot to NULL",<br>
+                       port_id);<br>
+               return -EINVAL;<br>
+       }<br>
+<br>
+       if (dev->dev_ops->timesync_read_tx_timestamp_slot == NULL)<br>
+               return -ENOTSUP;<br>
+<br>
+       return eth_err(port_id,<br>
+                       dev->dev_ops->timesync_read_tx_timestamp_slot(dev,<br>
+                               slot_id, timestamp));<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_slot_release, 26.11)<br>
+int<br>
+rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id, uint32_t slot_id)<br>
+{<br>
+       struct rte_eth_dev *dev;<br>
+<br>
+       RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+       dev = &rte_eth_devices[port_id];<br>
+<br>
+       if (dev->dev_ops->timesync_tx_timestamp_slot_release == NULL)<br>
+               return -ENOTSUP;<br>
+<br>
+       return eth_err(port_id,<br>
+                       dev->dev_ops->timesync_tx_timestamp_slot_release(dev,<br>
+                               slot_id));<br>
+}<br>
+<br>
+static int rte_eth_timesync_tx_slot_dynfield_offset = -1;<br>
+static uint64_t rte_eth_timesync_tx_slot_dynflag;<br>
+<br>
+static int<br>
+rte_eth_timesync_tx_slot_dynfield_register(void)<br>
+{<br>
+       const struct rte_mbuf_dynfield slot_dynfield = {<br>
+               .name  = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME,<br>
+               .size  = sizeof(uint32_t),<br>
+               .align = alignof(uint32_t),<br>
+       };<br>
+<br>
+       if (rte_eth_timesync_tx_slot_dynfield_offset >= 0)<br>
+               return 0;<br>
+<br>
+       rte_eth_timesync_tx_slot_dynfield_offset =<br>
+                       rte_mbuf_dynfield_register(&slot_dynfield);<br>
+       if (rte_eth_timesync_tx_slot_dynfield_offset < 0)<br>
+               rte_eth_timesync_tx_slot_dynfield_offset =<br>
+                               rte_mbuf_dynfield_lookup(<br>
+                                       RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME, NULL);<br>
+       if (rte_eth_timesync_tx_slot_dynfield_offset < 0)<br>
+               return -ENOTSUP;<br>
+<br>
+       {<br>
+               int flag_bit = rte_mbuf_dynflag_register(<br>
+                       &(const struct rte_mbuf_dynflag){<br>
+                               .name = RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag"});<br>
+               if (flag_bit < 0)<br>
+                       flag_bit = rte_mbuf_dynflag_lookup(<br>
+                               RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "_flag", NULL);<br>
+               if (flag_bit >= 0)<br>
+                       rte_eth_timesync_tx_slot_dynflag = RTE_BIT64(flag_bit);<br>
+       }<br>
+       return 0;<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_timesync_tx_timestamp_stamp_mbuf, 26.11)<br>
+int<br>
+rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id __rte_unused,<br>
+                                        uint32_t slot_id, struct rte_mbuf *m)<br>
+{<br>
+       if (m == NULL)<br>
+               return -EINVAL;<br>
+       if (rte_eth_timesync_tx_slot_dynfield_register() != 0)<br>
+               return -ENOTSUP;<br>
+       *RTE_MBUF_DYNFIELD(m, rte_eth_timesync_tx_slot_dynfield_offset,<br>
+                          uint32_t *) = slot_id;<br>
+       m->ol_flags |= rte_eth_timesync_tx_slot_dynflag;<br>
+       return 0;<br>
+}<br>
+<br>
 RTE_EXPORT_SYMBOL(rte_eth_timesync_adjust_time)<br>
 int<br>
 rte_eth_timesync_adjust_time(uint16_t port_id, int64_t delta)<br>
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h<br>
index ee400b386f..339c39fcdd 100644<br>
--- a/lib/ethdev/rte_ethdev.h<br>
+++ b/lib/ethdev/rte_ethdev.h<br>
@@ -5528,6 +5528,114 @@ int rte_eth_timesync_read_rx_timestamp(uint16_t port_id,<br>
 int rte_eth_timesync_read_tx_timestamp(uint16_t port_id,<br>
                 struct timespec *timestamp);<br>
 <br>
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.adjusted_ns. */<br>
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_ADJUSTED_VALID  RTE_BIT32(0)<br>
+/** Valid bit for rte_eth_timesync_dual_domain_timestamp.cycles_ns. */<br>
+#define RTE_ETH_TIMESYNC_DUAL_DOMAIN_TIMESTAMP_CYCLES_VALID    RTE_BIT32(1)<br>
+<br>
+/**<br>
+ * Dual-domain TX timestamp payload.<br>
+ */<br>
+struct rte_eth_timesync_dual_domain_timestamp {<br>
+       int64_t adjusted_ns;<br>
+       int64_t cycles_ns;<br>
+       uint32_t valid_mask;<br>
+};<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change without prior notice.<br>
+ *<br>
+ * Allocate a TX timestamp slot handle for per-packet correlation.<br>
+ *<br>
+ * @param port_id<br>
+ *   The port identifier of the Ethernet device.<br>
+ * @param tx_queue_id<br>
+ *   TX queue used by the packet to be transmitted.<br>
+ * @param slot_id<br>
+ *   Output handle identifying the allocated slot.<br>
+ *<br>
+ * @return<br>
+ *   - 0: Success.<br>
+ *   - -ENODEV: The port ID is invalid.<br>
+ *   - -EIO: if device is removed.<br>
+ *   - -ENOTSUP: The function is not supported by the Ethernet driver.<br>
+ *   - -EINVAL: Invalid parameters.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_timesync_tx_timestamp_slot_alloc(uint16_t port_id,<br>
+               uint16_t tx_queue_id, uint32_t *slot_id);<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change without prior notice.<br>
+ *<br>
+ * Read a per-packet TX timestamp using a previously allocated slot handle.<br>
+ *<br>
+ * @param port_id<br>
+ *   The port identifier of the Ethernet device.<br>
+ * @param slot_id<br>
+ *   Slot handle returned by rte_eth_timesync_tx_timestamp_slot_alloc().<br>
+ * @param timestamp<br>
+ *   Output dual-domain timestamp payload.<br>
+ *<br>
+ * @return<br>
+ *   - 0: Success.<br>
+ *   - -EAGAIN: Timestamp is not ready yet.<br>
+ *   - -ENODEV: The port ID is invalid.<br>
+ *   - -EIO: if device is removed.<br>
+ *   - -ENOTSUP: The function is not supported by the Ethernet driver.<br>
+ *   - -EINVAL: Invalid parameters.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_timesync_read_tx_timestamp_slot(uint16_t port_id,<br>
+               uint32_t slot_id,<br>
+               struct rte_eth_timesync_dual_domain_timestamp *timestamp);</div>
<div style="font-size: 11pt;"><br>
</div>
<div class="elementToProof" style="font-size: 11pt;">How is the egress port information conveyed?</div>
<div class="elementToProof" style="font-family: "Segoe UI", "Segoe UI Web (West European)", "Helvetica Neue", sans-serif; font-size: 11pt; color: rgb(0, 0, 0);">
The timestamp slot is a resource of the physical egress port.</div>
<div class="elementToProof" style="font-size: 11pt;"><br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change without prior notice.<br>
+ *<br>
+ * Release a previously allocated TX timestamp slot handle.<br>
+ *<br>
+ * @param port_id<br>
+ *   The port identifier of the Ethernet device.<br>
+ * @param slot_id<br>
+ *   Slot handle to release.<br>
+ *<br>
+ * @return<br>
+ *   - 0: Success.<br>
+ *   - -ENODEV: The port ID is invalid.<br>
+ *   - -EIO: if device is removed.<br>
+ *   - -ENOTSUP: The function is not supported by the Ethernet driver.<br>
+ *   - -EINVAL: Invalid parameters.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_timesync_tx_timestamp_slot_release(uint16_t port_id,<br>
+               uint32_t slot_id);<br>
+<br>
+/** Mbuf dynfield name for the TX timestamp slot handle. */<br>
+#define RTE_ETH_TIMESYNC_TX_SLOT_DYNFIELD_NAME "rte_eth_timesync_tx_slot"<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change without prior notice.<br>
+ *<br>
+ * Stamp an mbuf with a TX timestamp slot handle so the TX path steers the<br>
+ * NIC to capture the timestamp in the correct per-packet slot.<br>
+ *<br>
+ * Must be called after rte_eth_timesync_tx_timestamp_slot_alloc() and before<br>
+ * rte_eth_tx_burst(). Safe for concurrent callers — slot is stored per-mbuf.<br>
+ *<br>
+ * @param port_id  The port identifier (unused; reserved for future PMD use).<br>
+ * @param slot_id  Slot handle from rte_eth_timesync_tx_timestamp_slot_alloc().<br>
+ * @param m        Mbuf to stamp.<br>
+ * @return 0 on success, -ENOTSUP if dynfield registration failed.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_timesync_tx_timestamp_stamp_mbuf(uint16_t port_id,<br>
+               uint32_t slot_id, struct rte_mbuf *m);<br>
+<br>
 /**<br>
  * Adjust the timesync clock on an Ethernet device.<br>
  *<br>
--<br>
2.54.0<br>
<br>
</div>
</body>
</html>