This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit b850fc02059c761d53f3b53ec4b70475256843c8
Author: Daniel P. Carvalho <[email protected]>
AuthorDate: Fri Sep 18 12:39:13 2026 -0300

    arch/arm/stm32: implement hardware TX timestamping via SO_TIMESTAMPING 
loopback
    
    Implement hardware TX timestamping support for STM32 Ethernet MAC
    (stm32_eth_m3m4_v1.c) following the upstream SO_TIMESTAMPING loopback
    architecture (PR #20161).
    
    When an outgoing packet is flagged with SO_TIMESTAMPING 
(dev->d_iob->io_conn != NULL):
    - Clone the IOB and hold a reference in priv->txmeta[txindex]
    - Set ETH_TDES0_TTSE on the transmit DMA descriptor
    - On transmission completion (stm32_freeframe), retrieve the hardware
      timestamp from TDES6/TDES7, convert to timespec via ptp_to_timespec(),
      and enqueue the clone onto priv->txtstampq
    - Deliver pending TX timestamp clones back to netdev RX path in 
stm32_receive
      using pkt_input(), where net/pkt intercepts the frame and delivers it to
      userspace via recvmsg(..., MSG_ERRQUEUE)
    - Properly drain pending queues and clones on interface down (stm32_ifdown)
    
    Assisted-by: Gemini:gemini-3.8-pro
    Signed-off-by: Daniel P. Carvalho <[email protected]>
---
 arch/arm/src/common/stm32/Kconfig.eth         |   8 ++
 arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c | 122 ++++++++++++++++++++++++++
 2 files changed, 130 insertions(+)

diff --git a/arch/arm/src/common/stm32/Kconfig.eth 
b/arch/arm/src/common/stm32/Kconfig.eth
index 7a028c19fd8..dabdbc78458 100644
--- a/arch/arm/src/common/stm32/Kconfig.eth
+++ b/arch/arm/src/common/stm32/Kconfig.eth
@@ -193,6 +193,14 @@ config STM32_ETH_TIMESTAMP_RX
                Timestamp all received Ethernet packets.
                Timestamp is available to application through SO_TIMESTAMP 
socket option.
 
+config STM32_ETH_TIMESTAMP_TX
+       bool "Hardware timestamping of transmitted packets"
+       depends on STM32_COMMON_LEGACY && STM32_ETH_PTP && NET_TIMESTAMP && 
STM32_ETH_ENHANCEDDESC
+       default n
+       ---help---
+               Enables hardware timestamp capture on transmitted Ethernet 
frames.
+               Timestamp is looped back to userspace via MSG_ERRQUEUE with 
SO_TIMESTAMPING.
+
 config STM32_RMII
        bool
        default !STM32_MII
diff --git a/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c 
b/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c
index d68038d85d1..c072fc0ff0c 100644
--- a/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c
+++ b/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c
@@ -635,6 +635,13 @@ struct stm32_ethmac_s
   uint32_t             rxtimelow;   /* Received packet timestamp subsecond */
   uint32_t             rxtimehigh;  /* Received packet timestamp seconds */
 #endif
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+  struct iob_queue_s   txtstampq;   /* Pending TX timestamp loopback packets */
+
+  /* Per-descriptor TX clone */
+
+  struct iob_s        *txmeta[CONFIG_STM32_ETH_NTXDESC];
+#endif
 #if defined(CONFIG_STM32_ETH_PTP) && defined(CONFIG_PTP_CLOCK)
   struct ptp_lowerhalf_s ptp_lower; /* PTP hardware clock lower half */
 #endif
@@ -709,6 +716,9 @@ static int  stm32_recvframe(struct stm32_ethmac_s *priv);
 static void stm32_receive(struct stm32_ethmac_s *priv);
 static void stm32_freeframe(struct stm32_ethmac_s *priv);
 static void stm32_txdone(struct stm32_ethmac_s *priv);
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+static void stm32_txtstamp_flush(struct stm32_ethmac_s *priv);
+#endif
 
 static void stm32_interrupt_work(void *arg);
 static int  stm32_interrupt(int irq, void *context, void *arg);
@@ -1162,6 +1172,25 @@ static int stm32_transmit(struct stm32_ethmac_s *priv)
 
       txdesc->tdes0 |= (ETH_TDES0_FS | ETH_TDES0_LS | ETH_TDES0_IC);
 
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+      {
+        uint32_t txindex = txdesc - g_txtable;
+
+        priv->txmeta[txindex] = NULL;
+        if (priv->dev.d_iob != NULL && priv->dev.d_iob->io_conn != NULL)
+          {
+            struct iob_s *clone = netdev_iob_clone(&priv->dev, false);
+
+            if (clone != NULL)
+              {
+                clone->io_conn = priv->dev.d_iob->io_conn;
+                priv->txmeta[txindex] = clone;
+                txdesc->tdes0 |= ETH_TDES0_TTSE;
+              }
+          }
+      }
+#endif
+
       /* Set frame size */
 
       DEBUGASSERT(priv->dev.d_len <= CONFIG_STM32_ETH_BUFSIZE);
@@ -1700,6 +1729,47 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv)
   return -EAGAIN;
 }
 
+/****************************************************************************
+ * Function: stm32_txtstamp_flush
+ *
+ * Description:
+ *   Deliver pending TX hardware timestamp loopback packets to the network
+ *   stack.
+ *
+ * Input Parameters:
+ *   priv - Reference to the driver state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The network is locked.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+static void stm32_txtstamp_flush(struct stm32_ethmac_s *priv)
+{
+  struct net_driver_s *dev = &priv->dev;
+
+  while (!IOB_QEMPTY(&priv->txtstampq))
+    {
+      struct iob_s *iob = iob_remove_queue(&priv->txtstampq);
+
+      if (iob != NULL)
+        {
+          dev->d_iob = iob;
+          dev->d_len = iob->io_pktlen;
+#ifdef CONFIG_NET_PKT
+          pkt_input(dev);
+#endif
+          dev->d_iob = NULL;
+          dev->d_len = 0;
+        }
+    }
+}
+#endif
+
 /****************************************************************************
  * Function: stm32_receive
  *
@@ -1721,6 +1791,12 @@ static void stm32_receive(struct stm32_ethmac_s *priv)
 {
   struct net_driver_s *dev = &priv->dev;
 
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+  /* Return any TX hardware timestamp loopback packets first */
+
+  stm32_txtstamp_flush(priv);
+#endif
+
   /* Loop while while stm32_recvframe() successfully retrieves valid
    * Ethernet frames.
    */
@@ -1912,6 +1988,29 @@ static void stm32_freeframe(struct stm32_ethmac_s *priv)
 
           if ((txdesc->tdes0 & ETH_TDES0_LS) != 0)
             {
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+              uint32_t tail = txdesc - g_txtable;
+              struct iob_s *clone = priv->txmeta[tail];
+
+              priv->txmeta[tail] = NULL;
+              if (clone != NULL)
+                {
+                  if ((txdesc->tdes0 & ETH_TDES0_TTSS) != 0)
+                    {
+                      uint64_t hwtime = ((uint64_t)txdesc->tdes7 << 32)
+                                      | ((txdesc->tdes6 & ETH_PTPTSLR_MASK)
+                                         << 1);
+
+                      ptp_to_timespec(hwtime, &clone->io_time);
+                      iob_add_queue(clone, &priv->txtstampq);
+                    }
+                  else
+                    {
+                      iob_free_chain(clone);
+                    }
+                }
+#endif
+
               /* Yes.. Decrement the number of frames "in-flight". */
 
               priv->inflight--;
@@ -1975,6 +2074,12 @@ static void stm32_txdone(struct stm32_ethmac_s *priv)
 
   stm32_freeframe(priv);
 
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+  /* Deliver any pending TX hardware timestamp loopback packets */
+
+  stm32_txtstamp_flush(priv);
+#endif
+
   /* If no further xmits are pending, then cancel the TX timeout */
 
   if (priv->inflight <= 0)
@@ -2341,6 +2446,9 @@ static int stm32_ifdown(struct net_driver_s *dev)
     (struct stm32_ethmac_s *)dev->d_private;
   irqstate_t flags;
   int ret = OK;
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+  int i;
+#endif
 
   ninfo("Taking the network down\n");
 
@@ -2378,6 +2486,20 @@ static int stm32_ifdown(struct net_driver_s *dev)
            "still assuming it's going down.\n");
     }
 
+#ifdef CONFIG_STM32_ETH_TIMESTAMP_TX
+  /* Drain any pending TX timestamp loopback packets and clones */
+
+  iob_free_queue(&priv->txtstampq);
+  for (i = 0; i < CONFIG_STM32_ETH_NTXDESC; i++)
+    {
+      if (priv->txmeta[i] != NULL)
+        {
+          iob_free_chain(priv->txmeta[i]);
+          priv->txmeta[i] = NULL;
+        }
+    }
+#endif
+
   /* Mark the device "down" */
 
   priv->ifup = false;

Reply via email to