From 15dd40cfd54116ae0b0707a0ee8394a144005058 Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Fri, 18 Sep 2026 12:39:13 -0300 Subject: [PATCH 1/2] 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->tx_meta[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->tx_tstampq - 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 --- 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 7a028c19fd855..dabdbc784587a 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 56947eb3a2a63..0cf1a6ffbf60d 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 tx_tstampq; /* Pending TX timestamp loopback packets */ + + /* Per-descriptor TX clone */ + + struct iob_s *tx_meta[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_tx_tstamp_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->tx_meta[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->tx_meta[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_tx_tstamp_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_tx_tstamp_flush(struct stm32_ethmac_s *priv) +{ + struct net_driver_s *dev = &priv->dev; + + while (!IOB_QEMPTY(&priv->tx_tstampq)) + { + struct iob_s *iob = iob_remove_queue(&priv->tx_tstampq); + + 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_tx_tstamp_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->tx_meta[tail]; + + priv->tx_meta[tail] = NULL; + if (clone != NULL) + { + if ((txdesc->tdes0 & ETH_TDES0_TTSS) != 0) + { + uint64_t hw_time = ((uint64_t)txdesc->tdes7 << 32) + | ((txdesc->tdes6 & ETH_PTPTSLR_MASK) + << 1); + + ptp_to_timespec(hw_time, &clone->io_time); + iob_add_queue(clone, &priv->tx_tstampq); + } + 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_tx_tstamp_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->tx_tstampq); + for (i = 0; i < CONFIG_STM32_ETH_NTXDESC; i++) + { + if (priv->tx_meta[i] != NULL) + { + iob_free_chain(priv->tx_meta[i]); + priv->tx_meta[i] = NULL; + } + } +#endif + /* Mark the device "down" */ priv->ifup = false; From 28283ae37a03184aba853ecc5bc00a8a2a32e2cc Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Sat, 19 Sep 2026 11:59:57 -0300 Subject: [PATCH 2/2] arch/arm/stm32: fix PTP multicast filter and RX/TX frame routing - Under CONFIG_NET_PROMISCUOUS, forward all control frames (ETH_MACFFR_PCF_ALL) instead of only non-PAUSE ones, so link-local PTP multicast reaches the DMA. - Move ptp_to_timespec() above its first user so the TX timestamp path can call it. - stm32_receive(): do not log frames already delivered to packet sockets (PTP, IPv6) as "Dropped, Unknown type". - stm32_tx_tstamp_flush(): clear io_conn before freeing the looped-back IOB. Signed-off-by: Daniel P. Carvalho Assisted-by: Claude:claude-sonnet-5 --- arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) 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 0cf1a6ffbf60d..a4a730dd81f95 100644 --- a/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c +++ b/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c @@ -417,7 +417,7 @@ */ #ifdef CONFIG_NET_PROMISCUOUS -# define MACFFR_SET_BITS (ETH_MACFFR_PCF_PAUSE | ETH_MACFFR_PM) +# define MACFFR_SET_BITS (ETH_MACFFR_PCF_ALL | ETH_MACFFR_PM) #else # define MACFFR_SET_BITS (ETH_MACFFR_PCF_PAUSE) #endif @@ -808,6 +808,14 @@ static void stm32_eth_ptp_convert_rxtime(struct stm32_ethmac_s *priv); * Private Functions ****************************************************************************/ +#ifdef CONFIG_STM32_ETH_PTP +static inline void ptp_to_timespec(uint64_t timestamp, struct timespec *ts) +{ + ts->tv_sec = (timestamp >> 32); + ts->tv_nsec = ((uint32_t)timestamp * (uint64_t)NSEC_PER_SEC) >> 32; +} +#endif + /**************************************************************************** * Name: stm32_getreg * @@ -1187,6 +1195,10 @@ static int stm32_transmit(struct stm32_ethmac_s *priv) priv->tx_meta[txindex] = clone; txdesc->tdes0 |= ETH_TDES0_TTSE; } + else + { + nerr("ERROR: Failed to clone IOB for TX timestamp\n"); + } } } #endif @@ -1756,16 +1768,15 @@ static void stm32_tx_tstamp_flush(struct stm32_ethmac_s *priv) { struct iob_s *iob = iob_remove_queue(&priv->tx_tstampq); - if (iob != NULL) - { - dev->d_iob = iob; - dev->d_len = iob->io_pktlen; + dev->d_iob = iob; + dev->d_len = iob->io_pktlen; #ifdef CONFIG_NET_PKT - pkt_input(dev); + pkt_input(dev); #endif - dev->d_iob = NULL; - dev->d_len = 0; - } + dev->d_iob = NULL; + dev->d_len = 0; + iob->io_conn = NULL; + iob_free_chain(iob); } } #endif @@ -1909,7 +1920,18 @@ static void stm32_receive(struct stm32_ethmac_s *priv) else #endif { - nerr("ERROR: Dropped, Unknown type: %04x\n", BUF->type); +#ifdef CONFIG_NET_PKT + /* Frames that packet sockets consume directly (PTP, Ethertype + * 0x88f7, and IPv6) were already delivered via pkt_input() + * above, so they are not "unknown" and must not be logged as + * dropped. + */ + + if (BUF->type != HTONS(0x88f7) && BUF->type != HTONS(ETHTYPE_IP6)) +#endif + { + nerr("ERROR: Dropped, Unknown type: %04x\n", BUF->type); + } } /* We are finished with the RX buffer. NOTE: If the buffer is @@ -4103,12 +4125,6 @@ static uint64_t stm32_eth_ptp_gettime(void) } #endif -static inline void ptp_to_timespec(uint64_t timestamp, struct timespec *ts) -{ - ts->tv_sec = (timestamp >> 32); - ts->tv_nsec = ((uint32_t)timestamp * (uint64_t)NSEC_PER_SEC) >> 32; -} - /* Convert RX timestamp to CLOCK_REALTIME */ #ifdef CONFIG_STM32_ETH_TIMESTAMP_RX static void stm32_eth_ptp_convert_rxtime(struct stm32_ethmac_s *priv)