From e3dfc9875d71b2bf0cdb7d340a5c80fe1917d08c Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Sun, 20 Sep 2026 20:51:45 -0300 Subject: [PATCH] arch/arm/stm32h7: timestamp the received PTP frames. Add STM32_ETH_TIMESTAMP_RX for the STM32H7, as the one of the legacy STM32 that provides the timestamp of the frames received. Timestamp the PTP version 2 messages, over Ethernet and over UDP, except for the announce, management and signaling messages. The MAC writes the timestamp in a context descriptor after the last descriptor of the frame. The driver reads it before giving the frame to the network stack and passes it in d_rxtime, in the time of the system time of the MAC, the same as /dev/ptp0. A frame that is not timestamped has a time of zero. The timestamp goes over the address of the buffer of the context descriptor, and the code that dropped the context descriptors used a pointer that was never set. Keep the address of the buffer of each RX descriptor, and restore it when a context descriptor is given back. The timestamps of the PTP frames of a grandmaster clock were checked on hardware against the system time of the MAC, and were within the delay of the reads. Signed-off-by: Daniel P. Carvalho Assisted-by: Claude:claude-sonnet-5 --- arch/arm/src/common/stm32/Kconfig.eth | 2 +- .../arm/src/stm32h7/hardware/stm32_ethernet.h | 1 + arch/arm/src/stm32h7/stm32_ethernet.c | 166 +++++++++++++++++- 3 files changed, 166 insertions(+), 3 deletions(-) diff --git a/arch/arm/src/common/stm32/Kconfig.eth b/arch/arm/src/common/stm32/Kconfig.eth index 827b721a269..ea8a9cf7c57 100644 --- a/arch/arm/src/common/stm32/Kconfig.eth +++ b/arch/arm/src/common/stm32/Kconfig.eth @@ -187,7 +187,7 @@ config STM32_ETH_PTP_RTC_HIRES config STM32_ETH_TIMESTAMP_RX bool "Hardware timestamping of received packets" - depends on STM32_COMMON_LEGACY && STM32_ETH_PTP && NET_TIMESTAMP && STM32_ETH_ENHANCEDDESC + depends on STM32_ETH_PTP && NET_TIMESTAMP && (STM32_COMMON_H7_H5 || (STM32_COMMON_LEGACY && STM32_ETH_ENHANCEDDESC)) default n ---help--- Timestamp all received Ethernet packets. diff --git a/arch/arm/src/stm32h7/hardware/stm32_ethernet.h b/arch/arm/src/stm32h7/hardware/stm32_ethernet.h index c8b04b4c475..5d4fca932e7 100644 --- a/arch/arm/src/stm32h7/hardware/stm32_ethernet.h +++ b/arch/arm/src/stm32h7/hardware/stm32_ethernet.h @@ -363,6 +363,7 @@ #define ETH_MACTSCR_TSMSTRENA (1 << 15) /* Bit 15: Enable snapshot for messages relevant to master */ #define ETH_MACTSCR_SNAPTYPSEL_SHIFT (16) /* Bits 16-17: Select PTP packets for taking snapshots */ #define ETH_MACTSCR_SNAPTYPSEL_MASK (0x3 << ETH_MACTSCR_SNAPTYPSEL_SHIFT) +# define ETH_MACTSCR_SNAPTYPSEL_1 (1 << ETH_MACTSCR_SNAPTYPSEL_SHIFT) #define ETH_MACTSCR_TSENMACADDR (1 << 18) /* Bit 18: Enable MAC address for PTP packet filtering */ #define ETH_MACTSCR_CSC (1 << 19) /* Bit 19: Checksum correction for PTP over UDP/IPv4 (one-step) */ #define ETH_MACTSCR_TXTSSTSM (1 << 24) /* Bit 24: Transmit timestamp status mode */ diff --git a/arch/arm/src/stm32h7/stm32_ethernet.c b/arch/arm/src/stm32h7/stm32_ethernet.c index 388ca220666..0e454806170 100644 --- a/arch/arm/src/stm32h7/stm32_ethernet.c +++ b/arch/arm/src/stm32h7/stm32_ethernet.c @@ -275,6 +275,15 @@ # define STM32_PTP_UPDATE_USTIMEOUT (1000) +# ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + +/* Time to wait for the DMA to write the context descriptor that follows + * the last descriptor of a timestamped frame, in microseconds. + */ + +# define STM32_PTP_CTX_USTIMEOUT (10) +# endif + /* The addend can be trimmed by up to 50% each way from its nominal value, * in parts per billion. */ @@ -756,6 +765,8 @@ struct stm32_ethmac_s struct eth_desc_s *txchbase; /* TX descriptor ring base address */ struct eth_desc_s *rxchbase; /* RX descriptor ring base address */ + uint32_t rxbuf[CONFIG_STM32_ETH_NRXDESC]; /* Buffer of each RX descriptor */ + struct eth_desc_s *txtail; /* First "in_flight" TX descriptor */ struct eth_desc_s *rxcurr; /* First RX descriptor of the segment */ uint16_t segments; /* RX segment count */ @@ -1762,6 +1773,124 @@ static void stm32_freesegment(struct stm32_ethmac_s *priv, } } +/**************************************************************************** + * Function: stm32_rxindex + * + * Description: + * Get the position of an RX descriptor in the ring. The descriptors of + * the ring are the size of the union that also holds a TX descriptor, + * not of struct eth_desc_s. + * + ****************************************************************************/ + +static inline int stm32_rxindex(struct stm32_ethmac_s *priv, + struct eth_desc_s *rxdesc) +{ + return (union stm32_desc_u *)rxdesc - (union stm32_desc_u *)priv->rxchbase; +} + +/**************************************************************************** + * Function: stm32_freectxdesc + * + * Description: + * Give a context descriptor back to the DMA. The DMA writes the + * timestamp over the buffer address, so the address is restored from the + * copy that the driver keeps. + * + * Parameters: + * priv - Reference to the driver state structure + * ctxdesc - The context descriptor + * + * Assumptions: + * Global interrupts are disabled by interrupt handling logic. + * + ****************************************************************************/ + +static void stm32_freectxdesc(struct stm32_ethmac_s *priv, + struct eth_desc_s *ctxdesc) +{ + ctxdesc->des0 = priv->rxbuf[stm32_rxindex(priv, ctxdesc)]; + ctxdesc->des1 = 0; + ctxdesc->des2 = 0; + stm32_freesegment(priv, ctxdesc, 1); +} + +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX +/**************************************************************************** + * Function: stm32_rxtimestamp + * + * Description: + * Get the hardware timestamp of a received frame, and put it in d_rxtime. + * The timestamp is in the context descriptor that the DMA writes right + * after the last descriptor of the frame. A frame without a timestamp + * gets a time of zero. + * + * Parameters: + * priv - Reference to the driver state structure + * last - The last descriptor of the frame + * + * Returned Value: + * The context descriptor, that the caller has to give back to the DMA + * after the frame, or NULL if the frame has none. A context descriptor + * that the DMA does not write in time is found later by the scan of the + * descriptors, and dropped. + * + * Assumptions: + * Global interrupts are disabled by interrupt handling logic. + * + ****************************************************************************/ + +static struct eth_desc_s *stm32_rxtimestamp(struct stm32_ethmac_s *priv, + struct eth_desc_s *last) +{ + struct net_driver_s *dev = &priv->dev; + struct eth_desc_s *ctxdesc; + int i; + + dev->d_rxtime.tv_sec = 0; + dev->d_rxtime.tv_nsec = 0; + + if ((last->des3 & ETH_RDES3_WB_RS1V) == 0 || + (last->des1 & ETH_RDES1_WB_TSA) == 0) + { + return NULL; + } + + ctxdesc = stm32_get_next_rxdesc(priv, last); + + for (i = 0; i < STM32_PTP_CTX_USTIMEOUT; i++) + { + up_invalidate_dcache((uintptr_t)ctxdesc, + (uintptr_t)ctxdesc + sizeof(struct eth_desc_s)); + + if ((ctxdesc->des3 & ETH_RDES3_WB_OWN) == 0) + { + break; + } + + up_udelay(1); + } + + if ((ctxdesc->des3 & ETH_RDES3_WB_OWN) != 0 || + (ctxdesc->des3 & ETH_RDES3_WB_CTXT) == 0) + { + return NULL; + } + + /* The nanoseconds are in the first word and the seconds in the second, + * and all ones is a timestamp that is not valid. + */ + + if (ctxdesc->des0 != UINT32_MAX || ctxdesc->des1 != UINT32_MAX) + { + dev->d_rxtime.tv_sec = ctxdesc->des1; + dev->d_rxtime.tv_nsec = ctxdesc->des0; + } + + return ctxdesc; +} +#endif /* CONFIG_STM32_ETH_TIMESTAMP_RX */ + /**************************************************************************** * Function: stm32_recvframe * @@ -1787,6 +1916,9 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv) { struct eth_desc_s *rxdesc; struct eth_desc_s *rxcurr = NULL; +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + struct eth_desc_s *ctxdesc; +#endif uint8_t *buffer; int i; @@ -1910,6 +2042,8 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv) DEBUGASSERT(dev->d_buf == NULL); dev->d_buf = (uint8_t *)rxcurr->des0; rxcurr->des0 = (uint32_t)buffer; + priv->rxbuf[stm32_rxindex(priv, rxcurr)] = + (uint32_t)buffer; /* Make sure that the modified RX descriptor is written * to physical memory. @@ -1924,8 +2058,22 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv) */ priv->rxhead = stm32_get_next_rxdesc(priv, rxdesc); + +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + ctxdesc = stm32_rxtimestamp(priv, rxdesc); +#endif + stm32_freesegment(priv, rxcurr, priv->segments); +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + if (ctxdesc != NULL) + { + priv->rxhead = + stm32_get_next_rxdesc(priv, ctxdesc); + stm32_freectxdesc(priv, ctxdesc); + } +#endif + /* Force the completed RX DMA buffer to be re-read from * physical memory. */ @@ -1960,8 +2108,7 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv) { /* Drop the context descriptors, we are not interested */ - DEBUGASSERT(rxcurr != NULL); - stm32_freesegment(priv, rxcurr, 1); + stm32_freectxdesc(priv, rxdesc); } /* Try the next descriptor */ @@ -3053,6 +3200,7 @@ static void stm32_rxdescinit(struct stm32_ethmac_s *priv, /* Set Buffer1 address pointer */ rxdesc->des0 = (uint32_t)&rxbuffer[i * ALIGNED_BUFSIZE]; + priv->rxbuf[i] = rxdesc->des0; /* Set Buffer1 address high bytes */ @@ -4044,6 +4192,16 @@ static int stm32_eth_ptp_init(void) */ tscr = ETH_MACTSCR_TSENA | ETH_MACTSCR_TSCFUPDT | ETH_MACTSCR_TSCTRLSSR; + +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + /* Timestamp the received PTP version 2 messages, over Ethernet and over + * UDP, except for the announce, management and signaling messages. + */ + + tscr |= ETH_MACTSCR_TSVER2ENA | ETH_MACTSCR_TSIPENA | + ETH_MACTSCR_TSIPV4ENA | ETH_MACTSCR_TSIPV6ENA | + ETH_MACTSCR_SNAPTYPSEL_1; +#endif stm32_putreg(tscr, STM32_ETH_MACTSCR); /* Set the increment of the system time */ @@ -4831,6 +4989,10 @@ static inline int stm32_ethinitialize(int intf) stm32_ifdown(&priv->dev); +#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX + priv->dev.d_features |= NETDEV_RX_STAMP; +#endif + /* Register the device with the OS so that socket IOCTLs can be performed */ netdev_register(&priv->dev, NET_LL_ETHERNET);