stm32: Add support for Ethernet packet timestamping and PTP timer

Adds support for hardware timestamping of received Ethernet packets.
The timestamp is available to applications using socket option SO_TIMESTAMP.

Optionally, the Ethernet PTP timer can be used as system high-resolution RTC.
In this mode it supports fine resolution rate adjustment.

Alternatively other time source for CLOCK_REALTIME can be used, and the
PTP timestamps are converted by sampling the clocks and computing the
difference. This results in a few microseconds of uncertainty.
This commit is contained in:
Petteri Aimonen 2023-11-17 16:39:12 +02:00 committed by Xiang Xiao
parent cb161940c2
commit 2cf56e0fb3
2 changed files with 491 additions and 12 deletions

View File

@ -10899,12 +10899,45 @@ config STM32_PHYSR_100FD
This must be provided if STM32_AUTONEG is defined. This is the value
under the bit mask that represents the 100Mbps, full duplex setting.
config STM32_ETH_ENHANCEDDESC
bool "Enable enhanced RX/TX descriptors"
default n
---help---
Enables double-length DMA descriptors that have space for packet
timestamps and checksum offloading.
config STM32_ETH_PTP
bool "Precision Time Protocol (PTP)"
default n
---help---
Precision Time Protocol (PTP). Not supported but some hooks are indicated
with this condition.
Enables Precision Time Protocol (PTP) hardware timer.
config STM32_ETH_PTP_GPIO
bool "PTP pulse-per-second output signal"
depends on STM32_ETH_PTP
default n
---help---
Enables pulse-per-second output on GPIO pin.
config STM32_ETH_PTP_RTC_HIRES
bool "Use PTP timer as system high-resolution RTC"
depends on STM32_ETH_PTP
default n
---help---
Uses the Ethernet peripheral PTP timer as the CONFIG_RTC_HIRES source.
This provides high resolution timestamps to clock_gettime().
Note that PTP timer is disabled when Ethernet interface is down or
being reset. During this time g_rtc_enabled is set to false and system
uses the lower resolution system tick counter.
config STM32_ETH_TIMESTAMP_RX
bool "Hardware timestamping of received packets"
depends on STM32_ETH_PTP && NET_TIMESTAMP && STM32_ETH_ENHANCEDDESC
select ARCH_HAVE_NETDEV_TIMESTAMP
default n
---help---
Timestamp all received Ethernet packets.
Timestamp is available to application through SO_TIMESTAMP socket option.
config STM32_RMII
bool

View File

@ -217,16 +217,8 @@
# endif
#endif
#ifdef CONFIG_STM32_ETH_PTP
# warning "CONFIG_STM32_ETH_PTP is not yet supported"
#endif
/* This driver does not use IPv4 checksum offloading. */
/* This driver does not use enhanced descriptors. Enhanced descriptors must
* be used, however, if time stamping or and/or IPv4 checksum offload is
* supported.
*/
#undef CONFIG_STM32_ETH_ENHANCEDDESC
#undef CONFIG_STM32_ETH_HWCHECKSUM
/* Add 4 to the configured buffer size to account for the 2 byte checksum
@ -629,6 +621,11 @@ struct stm32_ethmac_s
uint16_t segments; /* RX segment count */
uint16_t inflight; /* Number of TX transfers "in_flight" */
sq_queue_t freeb; /* The free buffer list */
#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX
uint32_t rxtimelow; /* Received packet timestamp subsecond */
uint32_t rxtimehigh; /* Received packet timestamp seconds */
#endif
};
/****************************************************************************
@ -651,6 +648,11 @@ static uint8_t g_alloc[STM32_ETH_NFREEBUFFERS *
static struct stm32_ethmac_s g_stm32ethmac[STM32_NETHERNET];
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
volatile bool g_rtc_enabled;
static struct timespec g_stm32_eth_ptp_basetime;
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@ -765,6 +767,18 @@ static void stm32_ipv6multicast(struct stm32_ethmac_s *priv);
static int stm32_macenable(struct stm32_ethmac_s *priv);
static int stm32_ethconfig(struct stm32_ethmac_s *priv);
/* PTP initialization and access */
#ifdef CONFIG_STM32_ETH_PTP
static int stm32_eth_ptp_adjust(long ppb);
static void stm32_eth_ptp_init(uint64_t timestamp);
static uint64_t stm32_eth_ptp_gettime(void);
#endif
#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX
static void stm32_eth_ptp_convert_rxtime(struct stm32_ethmac_s *priv);
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
@ -1623,6 +1637,11 @@ static int stm32_recvframe(struct stm32_ethmac_s *priv)
dev->d_buf = (uint8_t *)rxcurr->rdes2;
rxcurr->rdes2 = (uint32_t)buffer;
#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX
priv->rxtimelow = rxcurr->rdes6;
priv->rxtimehigh = rxcurr->rdes7;
#endif
/* Return success, remembering where we should re-start
* scanning and resetting the segment scanning logic
*/
@ -1719,6 +1738,10 @@ static void stm32_receive(struct stm32_ethmac_s *priv)
continue;
}
#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX
stm32_eth_ptp_convert_rxtime(priv);
#endif
/* We only accept IP packets of the configured type and ARP packets */
#ifdef CONFIG_NET_IPv4
@ -2241,6 +2264,25 @@ static int stm32_ifup(struct net_driver_s *dev)
return ret;
}
#ifdef CONFIG_STM32_ETH_PTP
/* Enable PTP timer */
stm32_eth_ptp_init(0);
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
if (!g_rtc_enabled)
{
/* Transfer time from system low-resolution timer to PTP basetime */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
up_rtc_settime(&ts);
g_rtc_enabled = true;
}
#endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */
#endif /* CONFIG_STM32_ETH_PTP */
/* Enable the Ethernet interrupt */
priv->ifup = true;
@ -2285,6 +2327,18 @@ static int stm32_ifdown(struct net_driver_s *dev)
wd_cancel(&priv->txtimeout);
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
if (g_rtc_enabled)
{
/* Transfer back to system low-resolution timer */
struct timespec ts;
up_rtc_gettime(&ts);
g_rtc_enabled = false;
clock_settime(CLOCK_REALTIME, &ts);
}
#endif
/* Put the EMAC in its reset, non-operational state. This should be
* a known configuration that will guarantee the stm32_ifup() always
* successfully brings the interface back up.
@ -3483,13 +3537,267 @@ static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv)
# endif
#endif
#ifdef CONFIG_STM32_ETH_PTP
#ifdef CONFIG_STM32_ETH_PTP_GPIO
/* Enable pulse-per-second (PPS) output signal */
stm32_configgpio(GPIO_ETH_PPS_OUT);
#endif
}
#ifdef CONFIG_STM32_ETH_PTP
/****************************************************************************
* Function: stm32_eth_ptp_adjust
*
* Description:
* Adjust PTP timer run rate.
*
* Input Parameters:
* ppb - Adjustment in parts per billion (nanoseconds per second).
* Zero is default rate, positive value makes clock run faster
* and negative value slower.
*
* Returned Value:
* OK on success, negated errno on failure.
*
* Assumptions:
* Adjustment is between -0.5e9 and +0.5e9 (+- 50%)
*
****************************************************************************/
static int stm32_eth_ptp_adjust(long ppb)
{
uint32_t regval;
uint64_t addend;
uint32_t increment;
/* Compute addend value to achieve nominal timer rate.
* Increment is set by stm32_eth_ptp_init() and remains constants after
* that.
*/
increment = stm32_getreg(STM32_ETH_PTPSSIR) & ETH_PTPSSIR_MASK;
addend = ((uint64_t)1 << (32 + 31)) / (STM32_SYSCLK_FREQUENCY * increment);
/* Apply rate adjustment, if any */
if (ppb != 0)
{
addend += (int64_t)addend * ppb / NSEC_PER_SEC;
}
/* Check for overflows */
if (addend == 0 || (uint32_t)addend != addend)
{
nerr("PTP adjustment out of range: ppb=%ld, addend=%lld\n",
ppb, addend);
return -EINVAL;
}
/* Perform addend register update */
stm32_putreg((uint32_t)addend, STM32_ETH_PTPTSAR);
regval = stm32_getreg(STM32_ETH_PTPTSCR);
stm32_putreg(regval | ETH_PTPTSCR_TSARU, STM32_ETH_PTPTSCR);
up_udelay(1);
if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSARU)
{
/* This can happen if Ethernet PHY clock is stopped */
nerr("PTP addend update failed\n");
return -EBUSY;
}
return OK;
}
/****************************************************************************
* Function: stm32_eth_ptp_init
*
* Description:
* Configure the PTP timestamp counter of the Ethernet peripheral.
*
* Input Parameters:
* timestamp: Initial timestamp
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static void stm32_eth_ptp_init(uint64_t timestamp)
{
uint32_t regval;
uint32_t increment;
/* The PPS timestamp counter consists of a 32-bit seconds counter and
* 31-bit subsecond counter. The PTP input clock (SYSCLK) is divided by
* 2^32 / ADDEND and multiplied by INCREMENT. This calculation aims for
* ADDEND of 2^31 to provide +- 50% rate adjustment range.
*
* ADDEND value is then adjusted to compensate for rounding errors in
* the 8-bit INCREMENT value. The final rounding error will be less than
* 1 ppb. The timer frequency is approximately half of SYSCLK frequency,
* with phase jitter of one SYSCLK period.
*/
increment = ((uint32_t)1 << 31) / (STM32_SYSCLK_FREQUENCY / 2);
DEBUGASSERT(increment > 0 && (increment & ETH_PTPSSIR_MASK) == increment);
/* Timestamp counter initialization process
* (STM32F407 reference manual section 33.5.9
* "Programming steps for system time generation initialization")
*/
regval = ETH_PTPTSCR_TSE;
stm32_putreg(regval, STM32_ETH_PTPTSCR);
stm32_putreg(increment, STM32_ETH_PTPSSIR);
/* Update addend value to default rate */
stm32_eth_ptp_adjust(0);
/* Enable fine update mode */
regval |= ETH_PTPTSCR_TSFCU;
stm32_putreg(regval, STM32_ETH_PTPTSCR);
/* Initialize counter value */
stm32_putreg((uint32_t)(timestamp >> 32), STM32_ETH_PTPTSHUR);
stm32_putreg((uint32_t)(timestamp >> 1), STM32_ETH_PTPTSLUR);
stm32_putreg(regval | ETH_PTPTSCR_TSSTI, STM32_ETH_PTPTSCR);
up_udelay(1);
/* Initialization should complete within a few clock cycles.
* If not, there is probably something wrong with the PHY clock domain.
*/
if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSSTI)
{
nerr("PTP timestamp initialization failed\n");
}
/* Enable packet timestamping */
#ifdef CONFIG_STM32_ETH_TIMESTAMP_RX
regval |= ETH_PTPTSCR_TSSARFE;
stm32_putreg(regval, STM32_ETH_PTPTSCR);
#endif
}
/****************************************************************************
* Name: stm32_eth_ptp_gettime
*
* Description:
* Read PTP timestamp registers. The 64-bit timestamp consists of two
* registers that are updated continuously. This function employs
* double-read pattern to correctly handle overflow of the lower register.
*
* Input Parameters:
* None
*
* Returned Value:
* 64-bit timestamp, where upper 32 bits are the second count and lower
* 32-bits are the subsecond count.
* If timer is not yet initialized, returns 0.
*
* Assumptions:
* Can be called from interrupt or task context.
*
****************************************************************************/
static uint64_t stm32_eth_ptp_gettime(void)
{
uint32_t high1;
uint32_t low;
uint32_t high2;
high1 = getreg32(STM32_ETH_PTPTSHR);
low = getreg32(STM32_ETH_PTPTSLR);
high2 = getreg32(STM32_ETH_PTPTSHR);
if (high1 == high2)
{
return ((uint64_t)high2 << 32) | ((low & ETH_PTPTSLR_MASK) << 1);
}
else
{
/* Lower counter overflowed between the two register reads.
* Take its value as 0.
*/
return ((uint64_t)high2 << 32);
}
}
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)
{
uint64_t timestamp;
struct timespec rxtime;
timestamp = ((uint64_t)priv->rxtimehigh << 32)
| ((priv->rxtimelow & ETH_PTPTSLR_MASK) << 1);
/* Timestamp of 0 indicates that Ethernet peripheral didn't store the
* timestamp. Timestamp of all ones indicates "corrupt timestamp"
* according to reference manual. In either case, we pass along
* a timestamp of all zeros to application.
*/
if (timestamp == 0 || timestamp >= UINT64_MAX - 1)
{
nerr("Packet RX timestamp is invalid\n");
priv->dev.d_rxtime.tv_sec = 0;
priv->dev.d_rxtime.tv_nsec = 0;
return;
}
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
/* PTP is the system time reference, just add the base time */
ptp_to_timespec(timestamp, &rxtime);
clock_timespec_add(&rxtime, &g_stm32_eth_ptp_basetime,
&priv->dev.d_rxtime);
#else
{
struct timespec realtime;
uint64_t ptptime;
irqstate_t flags;
/* Sample PTP and CLOCK_REALTIME close to each other */
flags = enter_critical_section();
clock_gettime(CLOCK_REALTIME, &realtime);
ptptime = stm32_eth_ptp_gettime();
leave_critical_section(flags);
/* Compute how much time has elapsed since packet reception
* and add that to current time.
*/
timestamp = ptptime - timestamp;
ptp_to_timespec(timestamp, &rxtime);
clock_timespec_add(&rxtime, &realtime, &priv->dev.d_rxtime);
}
#endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */
}
#endif /* CONFIG_STM32_ETH_TIMESTAMP_RX */
#endif /* CONFIG_STM32_ETH_PTP */
/****************************************************************************
* Function: stm32_ethreset
*
@ -4029,5 +4337,143 @@ void arm_netinitialize(void)
}
#endif
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
/****************************************************************************
* Name: up_rtc_initialize
*
* Description:
* Initialize the builtin, MCU hardware RTC per the selected
* configuration. This function is called once very early in the OS
* initialization sequence.
*
* NOTE that initialization of external RTC hardware that depends on the
* availability of OS resources (such as SPI or I2C) must be deferred
* until the system has fully booted. Other, RTC-specific initialization
* functions are used in that case.
*
* Input Parameters:
* None
*
* Returned Value:
* Zero (OK) on success; a negated errno on failure
*
****************************************************************************/
int up_rtc_initialize(void)
{
/* Nothing to do, the PTP RTC is not available until Ethernet peripheral
* is enabled.
*/
return OK;
}
/****************************************************************************
* Name: up_rtc_gettime
*
* Description:
* Get the current time from the high resolution RTC clock/counter. This
* interface is only supported by the high-resolution RTC/counter hardware
* implementation.
* It is used to replace the system timer.
*
* Input Parameters:
* tp - The location to return the high resolution time value.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int up_rtc_gettime(FAR struct timespec *tp)
{
uint64_t timestamp;
timestamp = stm32_eth_ptp_gettime();
if (timestamp == 0)
{
/* PTP timer is not initialized yet.
* Normally we shouldn't end up here because g_rtc_enabled is false.
*/
DEBUGASSERT(!g_rtc_enabled);
return -EBUSY;
}
ptp_to_timespec(timestamp, tp);
clock_timespec_add(tp, &g_stm32_eth_ptp_basetime, tp);
return OK;
}
/****************************************************************************
* Name: up_rtc_settime
*
* Description:
* Set the RTC to the provided time. All RTC implementations must be able
* to set their time based on a standard timespec.
*
* Input Parameters:
* tp - the time to use
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int up_rtc_settime(FAR const struct timespec *tp)
{
struct timespec ptptime;
uint64_t timestamp;
timestamp = stm32_eth_ptp_gettime();
if (timestamp == 0)
{
/* PTP timer is not initialized yet.
* Normally we shouldn't end up here because g_rtc_enabled is false.
*/
DEBUGASSERT(!g_rtc_enabled);
return -EBUSY;
}
/* Compute new basetime to get from PTP timestamp to wall clock time.
* We keep the PTP timer 0-based to avoid 32-bit seconds count
* overflow issues.
*/
ptp_to_timespec(timestamp, &ptptime);
clock_timespec_subtract(tp, &ptptime, &g_stm32_eth_ptp_basetime);
return OK;
}
/****************************************************************************
* Name: up_rtc_adjtime
*
* Description:
* Adjust RTC frequency (running rate). Used by adjtime() when RTC is used
* as system time source.
*
* Input Parameters:
* ppb - Adjustment in parts per billion (nanoseconds per second).
* Zero is default rate, positive value makes clock run faster
* and negative value slower.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
* Assumptions:
* Called from within a critical section.
****************************************************************************/
int up_rtc_adjtime(long ppb)
{
return stm32_eth_ptp_adjust(ppb);
}
#endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */
#endif /* STM32_NETHERNET > 0 */
#endif /* CONFIG_NET && CONFIG_STM32_ETHMAC */