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 <danieloak@gmail.com>
Assisted-by: Claude:claude-sonnet-5
This commit is contained in:
Daniel P. Carvalho 2026-09-20 20:51:45 -03:00 • committed by Xiang Xiao
parent 4e6b805a0d
commit e3dfc9875d
3 changed files with 166 additions and 3 deletions

View file

@ -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.

View file

@ -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 */

View file

@ -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);