Skip to content

arch/arm/stm32, sched, net: hardware PTP timestamping, ADJFREQ/ADJPHASE, and tickless fixes - #20148

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

daniel-p-carvalho wants to merge 13 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

Why change is necessary (fix, update, new feature)?

Getting apps/netutils/ptpd hardware-syntonized to a real,
GPS-disciplined PTP Grandmaster (Toradex Colibri iMX7 running
linuxptp's ptp4l, IEC/IEEE 61850-9-3 P2P profile) against an
STM32F4Discovery required several pieces of kernel/driver support that
didn't exist yet: hardware TX timestamping (RX timestamping already
existed but had an ordering bug), a way for the PTP daemon to trim the
MAC's own hardware clock frequency and phase (not just the software
CLOCK_REALTIME), and a couple of unrelated bugs that only surfaced
once the board was driven hard enough by this workload (a tickless
timer race, a network self-transmit loopback false positive, and a
gap in CONFIG_SCHED_TICKLESS + CONFIG_CLOCK_TIMEKEEPING wall-time
slewing). This PR is the kernel/driver side; the PTP daemon side
(peer-delay support, ioctl consumption) is a companion, not-yet-opened
apache/nuttx-apps PR building on the already-open #3779.

What functional part of the code is being changed?

  • arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c (+ Kconfig.eth):
    hardware TX timestamping, direct hardware-counter RX/TX timestamp
    delivery, SIOCS_PTP_ADJFREQ/SIOCS_PTP_ADJPHASE support,
    self-transmit reception disable, RX timestamp/pkt_input() ordering
  • net/netdev/netdev_ioctl.c + include/nuttx/net/ioctl.h: three new
    socket ioctls (SIOCG_TX_HW_TIMESTAMP, SIOCS_PTP_ADJFREQ,
    SIOCS_PTP_ADJPHASE)
  • net/pkt/*: self-transmit loopback filter false-positive fix
  • arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c: TIM2
    compare-match race fix
  • sched/clock/clock_timekeeping.c, sched/sched/sched_processtickless.c:
    drive wall-clock adjtime slewing from the tickless tick handler
  • drivers/timers/Make.defs: build path for PTP clock drivers

How does the change exactly work (what will change and how)? Twelve
commits, each independently described in its own message:

  1. drivers/timers: Add TMRDEPPATH/TMRVPATH for PTP clock drivers.
  2. arch/arm/stm32: Convert RX hardware timestamp before pkt_input().
    — fixes every packet-socket RX timestamp being off by one frame
  3. net/pkt: Require matching length for self-transmit loopback filter.
  4. arch/arm/stm32: Disable reception of self-transmitted frames.
    — sets ETH_MACCR_ROD, no-op in full-duplex
  5. sched: Drive wall-clock adjtime slewing from the tickless tick handler.
    — builds on top of already-merged upstream fixes for the
    adjtime() clamp sign/consumption bug (5db3aa9, e37509e);
    this adds the missing periodic driver for CONFIG_SCHED_TICKLESS
  6. arch/arm/stm32: Fix TIM2 compare-match race in tickless timer driver.
    — could hang any blocking wait depending on a tickless wakeup
  7. net/netdev: Add SIOCG_TX_HW_TIMESTAMP socket ioctl command.
  8. arch/arm/stm32: Support hardware transmit timestamping.
  9. net/netdev: Add SIOCS_PTP_ADJFREQ socket ioctl command.
  10. arch/arm/stm32: Support PTP hardware frequency trim via SIOCS_PTP_ADJFREQ.
  11. net/netdev: Add SIOCS_PTP_ADJPHASE socket ioctl command.
  12. arch/arm/stm32: Support PTP hardware phase alignment via SIOCS_PTP_ADJPHASE.
  13. fix(stm32_eth): deliver direct hardware counter timestamps for PTP.
    — RX/TX timestamps for CONFIG_STM32_ETH_TIMESTAMP_RX/_TX now
    reflect the MAC's own hardware counter directly instead of being
    synthesized against CLOCK_REALTIME (which starts at an arbitrary
    boot-time phase when CONFIG_STM32_ETH_PTP_RTC_HIRES is not set),
    letting a PTP daemon actually phase-lock the hardware counter (and
    therefore the physical PPS output pin) to a master

Impact

  • New ioctls (SIOCG_TX_HW_TIMESTAMP, SIOCS_PTP_ADJFREQ,
    SIOCS_PTP_ADJPHASE) are additive; no behavior change for existing
    callers.
  • stm32_eth_m3m4_v1.c RX/TX timestamp semantics change only for
    boards with CONFIG_STM32_ETH_TIMESTAMP_RX/_TX enabled without
    CONFIG_STM32_ETH_PTP_RTC_HIRES (this basic PTP core is shared by
    several STM32 F1/F2/F4-family Ethernet MACs).
  • ETH_MACCR_ROD is now set unconditionally; verified no-op in
    full-duplex (the only mode available on the hardware used to test
    this), behavior changes only for half-duplex users of this driver.
  • net/pkt loopback filter change only tightens an existing
    false-positive-prone check (additive length comparison).
  • CONFIG_SCHED_TICKLESS + CONFIG_CLOCK_TIMEKEEPING combination:
    wall-clock adjtime slewing now actually progresses; no effect
    outside that specific Kconfig combination.
  • STM32 tickless driver (stm32_tickless_m3m4_v1.c): fixes a race
    that could hang indefinitely; affects all users of that tickless
    timer driver family.

Testing

HIL bench: STM32F4Discovery (custom out-of-tree board
stm32f4discovery-ext, STM32F407VGT6, DP83848C RMII PHY on an
Ethernet baseboard) running as PTP slave via apps/netutils/ptpd,
against a real GPS-disciplined PTP Grandmaster (Toradex Colibri iMX7 +
X-NUCLEO-GNSS1A1 GNSS module, linuxptp's ptp4l, IEC/IEEE
61850-9-3 P2P delay-mechanism profile) as master.

Verified with a digital oscilloscope comparing the GPS Grandmaster's
1PPS output against the STM32's own hardware-generated ETH_PPS_OUT
signal (PB5): before this series, the STM32's PPS edge landed at an
arbitrary, permanently-frozen phase offset from the master's edge;
after, it phase-locks onto the master's edge following the daemon's
initial clock jump and stays aligned. Also queried
pmc -u -b 0 "GET PORT_STATS_NP" on the Grandmaster to confirm
tx_Pdelay_Resp/rx_Pdelay_Req (and, for the E2E path validated by
#3779, Delay_Req/Delay_Resp) counters incrementing as expected.

Host: Ubuntu 24.04, arm-none-eabi-gcc toolchain, built and flashed
stm32f4discovery-ext:ethraw (custom out-of-tree board config, not
part of this PR) via NuttX's standard make/make flash flow.

Known limitations

  • TX hardware timestamp: one pending capture per interface, not per
    frame.
    stm32_eth_m3m4_v1.c's CONFIG_STM32_ETH_TIMESTAMP_TX
    tracks the MAC-captured timestamp through a single
    tx_ptp_sem/tx_last_ts/tx_last_valid triple per interface, not
    per frame. If two PTP event frames were transmitted back-to-back
    before the first one's timestamp was collected via
    SIOCG_TX_HW_TIMESTAMP, the second transmit's nxsem_reset() would
    overwrite the first's in-flight state, and a reader could get the
    wrong frame's timestamp. Not an issue with the current consumer:
    apps/netutils/ptpd/ptpd.c's ptp_sendmsg() is synchronous - it
    sends and immediately ioctls for the timestamp before returning, and
    ptpd only ever has one event message in flight at a time. Would
    become a real bug for any future consumer that sends PTP event
    frames concurrently/asynchronously (e.g. a multi-port Transparent
    Clock or Boundary Clock). Fix, if ever needed: key the pending
    timestamp by something like the frame's PTP sequenceId instead of
    keeping a single "most recent" slot.

@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

/* PTP Timestamping *********************************************************/

#define SIOCG_TX_HW_TIMESTAMP _SIOC(0x0046) /* Get hardware TX timestamp */

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.

does Linux define the similar ioctl?

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.

No, Linux doesn't have a direct equivalent for any of the three - it exposes each NIC's PTP hardware clock as its own /dev/ptpN character device with a clockid_t, disciplined via the standard POSIX clock_adjtime() syscall rather than a dedicated network ioctl. TX timestamp retrieval similarly goes through SO_TIMESTAMPING + the socket error queue (MSG_ERRQUEUE/SCM_TIMESTAMPING), which NuttX's socket layer doesn't implement (MSG_ERRQUEUE is defined for API compat but unused, and there's a comment in inet_sockif.c noting SO_TIMESTAMPING as future work).

While looking into this I found drivers/timers/ptp_clock.c already implements a generic upper-half PTP hardware clock framework matching Linux's /dev/ptpN model closely - PTP_CLOCK_ADJTIME handling ADJ_FREQUENCY/ADJ_OFFSET via struct timex, the dynamic-clockid-to-fd conversion, etc. It has no registered lower-half driver anywhere in the tree yet, and its adjfine/adjphase ops map almost directly onto what this PR already implements in stm32_eth_ptp_adjust()/stm32_eth_ptp_adjphase(). I didn't know about it when I wrote this PR; migrating to it as a lower-half driver looks like the right direction for SIOCS_PTP_ADJFREQ/SIOCS_PTP_ADJPHASE, dropping both in favor of /dev/ptp0. SIOCG_TX_HW_TIMESTAMP is unrelated to that framework though - it's per-packet TX timestamp retrieval, not clock discipline, and there's no equivalent facility in ptp_clock.c for it; the real Linux equivalent there is SO_TIMESTAMPING/MSG_ERRQUEUE, which as noted isn't implemented in NuttX, so I don't see a smaller-scope alternative to keeping that one as a plain ioctl for now.

Would you rather I do the /dev/ptp0 migration for ADJFREQ/ADJPHASE in this PR, or land the current ioctls now and follow up separately?

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.

While looking into this I found drivers/timers/ptp_clock.c already implements a generic upper-half PTP hardware clock framework matching Linux's /dev/ptpN model closely - PTP_CLOCK_ADJTIME handling ADJ_FREQUENCY/ADJ_OFFSET via struct timex, the dynamic-clockid-to-fd conversion, etc. It has no registered lower-half driver anywhere in the tree yet, and its adjfine/adjphase ops map almost directly onto what this PR already implements in stm32_eth_ptp_adjust()/stm32_eth_ptp_adjphase(). I didn't know about it when I wrote this PR;

ptp driver framework is implemented by @Donny9 to let eth driver provide ptp clock in a portable way.

migrating to it as a lower-half driver looks like the right direction for SIOCS_PTP_ADJFREQ/SIOCS_PTP_ADJPHASE, dropping both in favor of /dev/ptp0.

yes, I think so.

SIOCG_TX_HW_TIMESTAMP is unrelated to that framework though - it's per-packet TX timestamp retrieval, not clock discipline, and there's no equivalent facility in ptp_clock.c for it; the real Linux equivalent there is SO_TIMESTAMPING/MSG_ERRQUEUE, which as noted isn't implemented in NuttX, so I don't see a smaller-scope alternative to keeping that one as a plain ioctl for now.

@wenquan2015 do some improvement in this area, which may simplify your code.

Would you rather I do the /dev/ptp0 migration for ADJFREQ/ADJPHASE in this PR, or land the current ioctls now and follow up separately?

it's better to algin your improvement inside both kernel and userspace to Linux design, so we can use other ptp library and utility in the future.

@wenquan2015 wenquan2015 Sep 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@daniel-p-carvalho Hi, I've already implemented the SO_TIMESTAMPING + MSG_ERRQUEUE TX timestamp delivery and will submitt PRs to NuttX mainline this week after resolving the conflict. The full Linux-style flow is in place: sender tags iob->io_conn, driver captures the timestamp and loops the IOB back via RX path, userspace retrieves it with recvmsg(MSG_ERRQUEUE) + SCM_TIMESTAMPING cmsg.
@xiaoxiang781216 #20161

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.

Sounds good, I'll migrate SIOCS_PTP_ADJFREQ/SIOCS_PTP_ADJPHASE to /dev/ptp0 (registering a lower-half driver against drivers/timers/ptp_clock.c) in this PR.

For TX timestamp delivery, I'd rather hold off on migrating SIOCG_TX_HW_TIMESTAMP to SO_TIMESTAMPING until apache/nuttx#20161 merges. Should I keep this PR open as-is for that follow-up once apache/nuttx#20161 merges, mark it as draft in the meantime, or close it and submit a fresh PR later instead? Happy to do whichever fits your workflow better.

@wenquan2015 nice work, looking forward to building on top of it once it's in.

@xiaoxiang781216 xiaoxiang781216 Sep 17, 2026

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.

it's better to split your pr into there new pr:

  1. the general network improvement or bugfix
  2. implement stm32 eth ptp driver
  3. implement stm32 eth hardware timestamp

it's fine to reuse this pr for item 2 or 3 if you want.

Comment thread arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c Outdated
Comment thread include/nuttx/net/ioctl.h
/* PTP Timestamping *********************************************************/

#define SIOCG_TX_HW_TIMESTAMP _SIOC(0x0046) /* Get hardware TX timestamp */
#define SIOCS_PTP_ADJFREQ _SIOC(0x0047) /* Set PTP hardware frequency adjustment (ppb) */

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.

ditto

Comment thread arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c Outdated
Comment thread include/nuttx/net/ioctl.h

#define SIOCG_TX_HW_TIMESTAMP _SIOC(0x0046) /* Get hardware TX timestamp */
#define SIOCS_PTP_ADJFREQ _SIOC(0x0047) /* Set PTP hardware frequency adjustment (ppb) */
#define SIOCS_PTP_ADJPHASE _SIOC(0x0048) /* Set PTP hardware phase adjustment (ns) */

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.

ditto

Comment thread net/pkt/pkt.h
@daniel-p-carvalho
daniel-p-carvalho force-pushed the feat/ptp-hw-timestamp-sync branch 2 times, most recently from 3f45f30 to 84e534c Compare September 16, 2026 21:53
Every other timer driver block in this Make.defs sets TMRDEPPATH and
TMRVPATH so DEPPATH/VPATH include this directory. CONFIG_PTP_CLOCK
and CONFIG_PTP_CLOCK_DUMMY were the only two missing it, leaving
ptp_clock.c/ptp_clock_dummy.c unreachable via VPATH and without a
generated dependency file when no other timer driver in this file is
also selected.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 6d38122)
stm32_receive() called pkt_input() before
stm32_eth_ptp_convert_rxtime(), so every packet handed to a packet
socket carried the previous frame's RX timestamp instead of its own
in dev->d_rxtime. Reorder so the timestamp is converted first.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 9bfa20d)
pkt_in() suppresses delivery of a received packet whose iob pointer
matches the connection's last transmitted iob (pendiob), to avoid
delivering a frame the driver echoes back to its own receiver. The
iob pool is small and recycled quickly, so a genuinely different
incoming packet can reuse the same iob slot our last transmission
used, and gets silently dropped as a false positive. Track the
transmitted length alongside the iob pointer and require both to
match before treating a received packet as a self-echo.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 4dbf181)
Set ETH_MACCR_ROD unconditionally when configuring the MAC. In
half-duplex mode the MAC otherwise reflects every frame it transmits
back to its own receiver, flooding the receive path with our own
traffic right as a genuine reply arrives. The bit has no effect in
full-duplex (confirmed on our hardware: fduplex=1), so setting it
unconditionally is safe and changes nothing observable for boards
already running full-duplex.

The sibling stm32f7 driver has the same gap (ETH_MACCR_ROD cleared
but never set) and stm32h7's equivalent ETH_MACCR_DO bit has the same
issue; both are left out of scope here since only m3m4_v1 hardware
was available to validate against.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 41536cb)
Upstream already fixed the adjtime() clamp sign and consumption bug
under CONFIG_CLOCK_TIMEKEEPING (5db3aa9, e37509e). What remained
missing for CONFIG_SCHED_TICKLESS: clock_update_wall_time() was never
called on a schedule, so a pending adjtime() correction only applied
whenever something else happened to call clock_timekeeping_get_wall_time()
or clock_update_wall_time() directly, with no periodic progress
otherwise.

- nxsched_process_timer() (sched_processtickless.c) now calls
  clock_update_wall_time() every tick under CONFIG_CLOCK_TIMEKEEPING,
  mirroring what the non-tickless sched_processtick.c path already does
- clock_timekeeping_get_wall_time() calls clock_update_wall_time()
  itself, so a reader gets the current value instead of depending on
  the tick handler having already run
- Honor CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM as the slew limit instead of
  a fixed 500 usec/s
- uint64_t -> clock_t type cleanups to match the counter's actual width

Assisted-by: Gemini:gemini-3.8-flash-medium
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
The STM32 timer only generates a compare-match interrupt on an exact
CNT == CCR equality. up_timer_start() could arm a compare value the
free-running counter had already passed by the time the register
write took effect, missing that equality entirely and leaving the
interrupt pending forever until the 32-bit counter wraps (~71 minutes
at 1MHz) - observed to hang any blocking wait in the system (network
TX, ifup) that depends on a tickless-driven wakeup.

Add stm32_tickless_trigint(), which forces the compare event via the
timer's EGR software-trigger bit, and call it whenever up_timer_start()
detects (after arming) that the counter has already reached or passed
the target. Also handle a zero or negative requested delay by
triggering immediately instead of arming a stale compare value, and
unify up_timer_cancel()'s rollover-safe "already elapsed" comparison
between the 16-bit and 32-bit tickless variants.

Assisted-by: Gemini:gemini-3.8-flash-medium
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit ad2732b)
Define SIOCG_TX_HW_TIMESTAMP to allow user-space applications (such as
PTP daemons) to synchronously retrieve the hardware transmit timestamp
captured by Ethernet MAC controllers.

Add handling in net_ioctl_ifreq_arglen() and dispatch to dev->d_ioctl()
via netdev_ifr_ioctl(), passing the user timespec pointer stored in
ifr_data.

Assisted-by: Gemini:gemini-3.8-flash-medium
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit df528c7)
Implement hardware TX timestamping in the STM32 Ethernet MAC driver
under CONFIG_STM32_ETH_TIMESTAMP_TX (Milestone M3):

- Unify hardware timestamp to CLOCK_REALTIME conversion across RX and TX
  via stm32_eth_ptp_convert_hwtime().
- Inspect outgoing frames in stm32_transmit() to recognize PTP event
  frames (Layer 2 EtherType 0x88F7 and IPv4 UDP port 319 with event
  message types <= 3).
- Enable hardware timestamp capture on the transmit descriptor via
  ETH_TDES0_TTSE and track the descriptor in a parallel tx_meta table
  indexed by pointer arithmetic against g_txtable.
- In stm32_freeframe(), verify ETH_TDES0_TTSS upon transmission completion,
  extract raw timestamps from tdes7 (seconds) and tdes6 (subseconds),
  convert to CLOCK_REALTIME, and post the waiting semaphore.
- Implement SIOCG_TX_HW_TIMESTAMP in stm32_ioctl() with a 50 ms
  timeout to deliver t3/t1 to user space synchronously.

Assisted-by: Gemini:gemini-3.8-flash-medium
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 81d4241)
Define SIOCS_PTP_ADJFREQ to allow user-space applications (such as
PTP daemons) to apply a continuous frequency (rate) correction, in
parts per billion, to a PTP-capable Ethernet MAC's hardware counter.

Add handling in net_ioctl_ifreq_arglen() and dispatch to dev->d_ioctl()
via netdev_ifr_ioctl(), mirroring SIOCG_TX_HW_TIMESTAMP.

Assisted-by: Gemini:gemini-3.8-flash-medium
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 287e21f)
…FREQ.

Implement SIOCS_PTP_ADJFREQ in stm32_ioctl(), calling the existing
stm32_eth_ptp_adjust() to write the PTP addend register (ETH_PTPTSAR).

Fix a sign bug in stm32_eth_ptp_adjust() found while validating this
path on hardware: addend was uint64_t, so "addend * ppb" promoted a
negative ppb to a huge unsigned value before the division, corrupting
the result for any negative (clock running fast) adjustment - exactly
the sign our board's crystal needs. Change addend to int64_t and cast
ppb explicitly so the multiply and divide stay in signed arithmetic.

This disciplines the MAC's own internal PTP counter - used for RX/TX
hardware timestamp precision and the PPS output on PB5
(CONFIG_STM32_ETH_PTP_GPIO) - not CLOCK_REALTIME, which under
CLOCK_TIMEKEEPING is sourced from TIM2 and unaffected by this
register. Register writes were confirmed correct via live diagnostic
log (TTSE/TTSS/tdes6/tdes7 all valid); PPS phase accuracy against the
GNSS Grandmaster still needs oscilloscope validation before this is
considered fully proven.

Assisted-by: Gemini:gemini-3.8-flash-medium
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 144bb4e)
Define SIOCS_PTP_ADJPHASE to let user-space applications nudge a
PTP-capable Ethernet MAC's hardware counter phase by a signed
nanosecond delta, without resetting any frequency correction already
in effect.

Add handling in net_ioctl_ifreq_arglen() and dispatch to dev->d_ioctl()
via netdev_ifr_ioctl(), mirroring SIOCS_PTP_ADJFREQ.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 0b8a770)
…JPHASE.

Implement SIOCS_PTP_ADJPHASE in stm32_ioctl(), calling a new
stm32_eth_ptp_adjphase() that writes a signed nanosecond delta to
ETH_PTPTSHUR/ETH_PTPTSLUR and triggers the System Time Update
(ETH_PTPTSCR_TSSTU) bit - unlike stm32_eth_ptp_init()'s TSSTI path,
this does not reset the addend register, so any rate correction
already applied via SIOCS_PTP_ADJFREQ is preserved.

This closes the gap left by stm32_eth_ptp_init(0): the MAC's PTP
counter starts phase-arbitrary every time the interface comes up, and
nothing previously ever realigned it to the master's time - only its
rate got corrected. This affects the PPS output pin (PB5,
CONFIG_STM32_ETH_PTP_GPIO) directly.

Register math verified by hand (round-trip through
ptp_to_timespec()'s conversion for both positive and negative
deltas); build and checkpatch clean. Oscilloscope validation of PPS
phase alignment against the GNSS Grandmaster still pending.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
(cherry picked from commit 3cc6e89)
The MAC hardware counter is the PTP clock reference. Delivering its
raw timestamp directly (instead of synthesizing one against
CLOCK_REALTIME, which starts at an arbitrary boot-time phase) lets the
PTP daemon close the feedback loop and phase-lock the MAC counter -
and therefore the physical PPS output - to the master. This folds in
and supersedes two earlier attempts at the same fix that are dropped
from this series (g_rtc_lock scope, elapsed-time subtraction), both
made moot by this rewrite.

Assisted-by: Gemini:gemini-3.8-flash-medium
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 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>
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 Area: Drivers Drivers issues Size: L The size of the change in this PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants