Skip to content

arch/arm/stm32: hardware TX timestamping via SO_TIMESTAMPING - #20148

Open
daniel-p-carvalho wants to merge 2 commits into
apache:masterfrom
daniel-p-carvalho:feat/ptp-hw-timestamp-sync
Open

daniel-p-carvalho wants to merge 2 commits into
apache:masterfrom
daniel-p-carvalho:feat/ptp-hw-timestamp-sync

Conversation

@daniel-p-carvalho

@daniel-p-carvalho daniel-p-carvalho commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

The STM32 Ethernet MAC can capture a timestamp for every transmitted frame, but the driver only timestamped received frames. A PTP daemon that measures the path delay with the peer-to-peer mechanism needs the transmit time of its Pdelay_Req from the hardware. Taking it in software, after sendmsg() returns, gave a negative peer delay on the board I tested with, which the daemon rejects.

This PR adds hardware TX timestamping to the legacy STM32 Ethernet driver, delivered through the generic SO_TIMESTAMPING mechanism that #20161 added to the network stack: the driver clones the transmitted frame, attaches the hardware timestamp and loops it back to the AF_PACKET socket, where recvmsg(..., MSG_ERRQUEUE) returns it. No new socket option, ioctl or change in the network stack is needed. The TX timestamp is the raw MAC counter, the same time base as the RX timestamp and /dev/ptp0 from #20180.

This PR was reworked after review of its first version. The custom socket ioctls (SIOCG_TX_HW_TIMESTAMP, SIOCS_PTP_ADJFREQ, SIOCS_PTP_ADJPHASE) are gone, replaced by SO_TIMESTAMPING for TX and by the PTP hardware clock in #20180. The other parts of the original 13 commits were merged separately (#20172, #20173, #20180, #20195).

Commits

  1. arch/arm/stm32: implement hardware TX timestamping via SO_TIMESTAMPING loopback. Adds CONFIG_STM32_ETH_TIMESTAMP_TX. When an outgoing frame is flagged for timestamping, the driver keeps a clone of it, sets ETH_TDES0_TTSE in the transmit descriptor and, when the transmission completes, reads the timestamp from TDES6/TDES7, converts it with ptp_to_timespec() and queues the clone. The queued clones are handed back to the network stack from stm32_receive() through pkt_input(), where net/pkt delivers them to the error queue of the socket. Pending clones are released when the interface goes down.
  2. arch/arm/stm32: fix PTP multicast filter and RX/TX frame routing. Fixes found while running PTP with the change above. With CONFIG_NET_PROMISCUOUS all control frames are forwarded (ETH_MACFFR_PCF_ALL), so the link-local PTP multicast reaches the DMA. Frames already delivered to packet sockets are no longer logged as Dropped, Unknown type. The io_conn of a looped-back clone is cleared before its IOB is freed. ptp_to_timespec() is moved above its first user.

Impact

Testing

Built for stm32f4discovery:ethraw, a custom board configuration with the STM32F407 MAC and a DP83848 PHY, with CONFIG_STM32_ETH_TIMESTAMP_TX=y, without errors or new warnings. ./tools/checkpatch.sh -g upstream/master..HEAD passes.

On hardware, against a GNSS-referenced ptp4l Grandmaster using the IEC/IEEE 61850-9-3 profile, over AF_PACKET and through a Fast Ethernet switch, with ptpd -2 -s -H -p /dev/ptp0 -B -P -i eth0 from apache/nuttx-apps#3782 plus the follow-up that reads the TX timestamps (#3791). The 10 minute run below was made on this branch on top of the current master (after #20180), the earlier ones on the same commits before the rebase:

  • 10 minute run on the rebased branch: 46 samples, no failed queries, no fault registers set afterwards. path_delay 9276 to 9292 ns (mean 9281.6 ns, σ = 3.5 ns), phase offset mean -2.4 µs (σ = 0.4 µs), drift about -78.9 ppm.
  • 20 minute run before the rebase: 159 samples, no failed queries, path_delay 9265.3 ns (σ = 2.5 ns), phase offset about -2.2 µs, drift about -79.95 ppm.
  • Several 5 minute runs before the rebase: path_delay between 9.26 and 9.33 µs.
  • The PPS output of the MAC, measured on an oscilloscope against the Grandmaster's PPS, stayed within about 20 µs (measured before the rebase).
  • Without this PR, and with ptpd taking the transmit time in software, the peer delay was negative (about -18 µs) and was rejected, so the path delay stayed at 0.

Other STM32 boards that use this driver were not tested.

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch from df6136c to 70cf835 Compare September 14, 2026 23:34
Comment thread include/nuttx/net/ioctl.h Outdated
Comment thread arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c Outdated
Comment thread include/nuttx/net/ioctl.h Outdated
Comment thread arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c Outdated
Comment thread include/nuttx/net/ioctl.h Outdated
Comment thread net/pkt/pkt.h Outdated
@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch 3 times, most recently from 84e534c to 67293c8 Compare September 17, 2026 00:24
daniel-p-carvalho added a commit to daniel-p-carvalho/apache-nuttx that referenced this pull request Sep 18, 2026
Implement lower-half PTP hardware clock operations
(struct ptp_lowerhalf_s and struct ptp_ops_s) in the STM32 Ethernet
driver and register it with the generic PTP clock framework
(drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in frequency trim where uint64_t addend promoted
negative ppb adjustments to unsigned, corrupting frequency trim for
crystals running faster than nominal.

Follow-up to apache#20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
daniel-p-carvalho added a commit to daniel-p-carvalho/apache-nuttx that referenced this pull request Sep 18, 2026
Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to apache#20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
daniel-p-carvalho added a commit to daniel-p-carvalho/apache-nuttx that referenced this pull request Sep 18, 2026
Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to apache#20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
daniel-p-carvalho added a commit to daniel-p-carvalho/apache-nuttx that referenced this pull request Sep 19, 2026
Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to apache#20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
@daniel-p-carvalho

Copy link
Copy Markdown
Contributor Author

Status update, and a heads-up on what will change in this PR.

Following the review here (replace the custom socket ioctls with the generic PTP hardware clock, and use SO_TIMESTAMPING for TX timestamps), the original 13 commits have been split up. Everything they contained is now either merged, in its own PR, or dropped:

Original content Where it went
tickless compare-match race, adjtime() slewing from the tickless tick handler merged as #20173
self-transmit loopback false positive in net/pkt merged as #20172
SIOCS_PTP_ADJFREQ / SIOCS_PTP_ADJPHASE dropped; replaced by the /dev/ptp0 driver in #20180
SIOCG_TX_HW_TIMESTAMP dropped; replaced by SO_TIMESTAMPING + MSG_ERRQUEUE (#20161, merged)
RX timestamp ordering, ETH_MACCR_ROD, direct hardware counter timestamps, TMRDEPPATH/TMRVPATH #20180
stale io_conn in recycled IOBs (CONFIG_NET_TIMESTAMPING typo left behind by #20161) #20195

What is left for this PR is the STM32 hardware TX timestamping itself, delivered through SO_TIMESTAMPING as defined in #20161. It depends on #20180 (the TX and RX timestamps have to come from the same hardware counter), so I am converting this PR to a draft for now. Once #20180 and #20195 are merged I will rebase the remaining commits onto master, force-push this branch, and update the title and description, so the current diff and the older ioctl-related threads will be outdated by then.

The comments above about the ioctl definitions and the FAR/cast style in the ioctl code no longer apply, since that code is gone. The FAR and cast feedback has been applied to the code that replaced it in #20180.

@daniel-p-carvalho
daniel-p-carvalho marked this pull request as draft September 19, 2026 15:18
daniel-p-carvalho added a commit to daniel-p-carvalho/apache-nuttx that referenced this pull request Sep 19, 2026
Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to apache#20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch from 67293c8 to ba8190d Compare September 19, 2026 21:11
@github-actions github-actions Bot added Size: M The size of the change in this PR is medium and removed Size: L The size of the change in this PR is large labels Sep 19, 2026
@daniel-p-carvalho daniel-p-carvalho changed the title arch/arm/stm32, sched, net: hardware PTP timestamping, ADJFREQ/ADJPHASE, and tickless fixes arch/arm/stm32: hardware TX timestamping via SO_TIMESTAMPING Sep 19, 2026
@daniel-p-carvalho

daniel-p-carvalho commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

I have updated this PR as announced above, so the diff and the older ioctl-related threads are now outdated.

It now contains only the STM32 hardware TX timestamping, delivered through SO_TIMESTAMPING and MSG_ERRQUEUE as defined in #20161, and it is rebased on current master (the mm/iob fix from #20195 is already in there). The title and description have been rewritten.

This PR must be merged after #20180. The first six commits are the same commits as #20180, which this PR needs because the TX and RX timestamps have to come from the same hardware counter, so they show up in this diff until #20180 is merged. Only the last two commits are new here. I will rebase it once #20180 is in, and only the last two commits need review here.

@daniel-p-carvalho
daniel-p-carvalho marked this pull request as ready for review September 19, 2026 21:22
acassis pushed a commit that referenced this pull request Sep 19, 2026
Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to #20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch from ba8190d to 28283ae Compare September 19, 2026 22:20
@github-actions github-actions Bot removed the Area: Drivers Drivers issues label Sep 19, 2026
@daniel-p-carvalho

Copy link
Copy Markdown
Contributor Author

#20180 is merged, so I have rebased this branch on master. The six commits that came from #20180 were dropped as already applied, and the PR now has only the two commits to review: the STM32 hardware TX timestamping and the PTP multicast filter and frame routing fix. The earlier note about waiting for #20180 no longer applies, and the description is updated.

I also ran the rebased branch on the board again (10 minutes, 46 samples, no failed queries), with results equivalent to the earlier runs; the numbers are in the description.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txtstampq

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, the field is txtstampq now.


/* Per-descriptor TX clone */

struct iob_s *tx_meta[CONFIG_STM32_ETH_NTXDESC];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txmeta

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, the array is txmeta now.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stm32_txtstamp_flush

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, the function is stm32_txtstamp_flush() now. I also renamed the local hw_time to hwtime in stm32_freeframe() for the same reason, and updated the commit messages that mention these names.

@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch from 28283ae to 173e57e Compare September 20, 2026 16:26
…G loopback

Implement hardware TX timestamping support for STM32 Ethernet MAC
(stm32_eth_m3m4_v1.c) following the upstream SO_TIMESTAMPING loopback
architecture (PR apache#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 <danieloak@gmail.com>
- 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_txtstamp_flush(): clear io_conn before freeing the looped-back
  IOB.

Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
Assisted-by: Claude:claude-sonnet-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants