* [PATCH net 0/2] sfc: fix PTP synchronization races
@ 2026-10-09 18:32 Alex Austin
2026-10-09 18:32 ` [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions Alex Austin
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Alex Austin @ 2026-10-09 18:32 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba,
linux-kernel, linux-net-drivers, pabeni, richardcochran,
smhodgson, alucero, pieter.jansen-van-vuuren, Alex Austin
This series fixes PTP synchronization races found by code inspection.
efx_ptp_pps_worker() and the SIOCSHWTSTAMP path through
efx_ptp_change_mode() both call efx_ptp_synchronize(). Concurrent calls
can interfere with the shared DMA handshake and synchronization state.
PHC adjustments can also change the clock during a capture. Patch 1
serializes synchronization transactions and PHC operations. Patch 2
returns the PPS timestamp in caller-owned storage to prevent concurrent
synchronization from overwriting it while the PPS worker reads it.
Alex Austin (2):
sfc: ptp: serialize host and MC synchronization transactions
sfc: ptp: avoid racing PPS timestamp updates
drivers/net/ethernet/sfc/ptp.c | 136 ++++++++++++++++++++++-----------
1 file changed, 90 insertions(+), 46 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions 2026-10-09 18:32 [PATCH net 0/2] sfc: fix PTP synchronization races Alex Austin @ 2026-10-09 18:32 ` Alex Austin 2026-10-10 19:10 ` netdev-bot+sashiko 2026-10-09 18:32 ` [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates Alex Austin 2026-10-09 18:35 ` [PATCH net 0/2] sfc: fix PTP synchronization races netdev-bot+sinfo 2 siblings, 1 reply; 6+ messages in thread From: Alex Austin @ 2026-10-09 18:32 UTC (permalink / raw) To: netdev Cc: andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba, linux-kernel, linux-net-drivers, pabeni, richardcochran, smhodgson, alucero, pieter.jansen-van-vuuren, Alex Austin, Alejandro Lucero Protect the DMA handshake and result processing against concurrent synchronization callers. Serialize clock adjustments with the same mutex so they cannot change the PHC during a capture. Fixes: 7c236c43b838 ("sfc: Add support for IEEE-1588 PTP") Signed-off-by: Alex Austin <alex.austin@amd.com> Reviewed-by: Alejandro Lucero <alucerop@amd.com> Reviewed-by: Pieter Jansen van Vuuren <pieter.jansen-van-vuuren@amd.com> --- drivers/net/ethernet/sfc/ptp.c | 102 +++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c index 6ca9a75..ac9951f 100644 --- a/drivers/net/ethernet/sfc/ptp.c +++ b/drivers/net/ethernet/sfc/ptp.c @@ -36,6 +36,7 @@ #include <linux/errno.h> #include <linux/ktime.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/pps_kernel.h> #include <linux/ptp_clock_kernel.h> #include "net_driver.h" @@ -272,6 +273,7 @@ struct efx_ptp_rxfilter { * frequency adjustment into a fixed point fractional nanosecond format. * @current_adjfreq: Current ppb adjustment. * @phc_clock: Pointer to registered phc device (if primary function) + * @phc_lock: Serializes PHC commands and synchronization * @phc_clock_info: Registration structure for phc device * @pps_work: pps work task for handling pps events * @pps_workwq: pps work queue @@ -331,6 +333,7 @@ struct efx_ptp_data { unsigned int adjfreq_ppb_shift; s64 current_adjfreq; struct ptp_clock *phc_clock; + struct mutex phc_lock; struct ptp_clock_info phc_clock_info; struct work_struct pps_work; struct workqueue_struct *pps_workwq; @@ -1027,11 +1030,14 @@ static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) MCDI_SET_QWORD(synch_buf, PTP_IN_SYNCHRONIZE_START_ADDR, ptp->start.dma_addr); + mutex_lock(&ptp->phc_lock); + /* Clear flag that signals MC ready */ WRITE_ONCE(*start, 0); rc = efx_mcdi_rpc_start(efx, MC_CMD_PTP, synch_buf, MC_CMD_PTP_IN_SYNCHRONIZE_LEN); - EFX_WARN_ON_ONCE_PARANOID(rc); + if (rc) + goto out; /* Wait for start from MCDI (or timeout) */ timeout = jiffies + msecs_to_jiffies(MAX_SYNCHRONISE_WAIT_MS); @@ -1062,11 +1068,13 @@ static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) ++ptp->no_time_syncs; } +out: /* Increment the bad syncs counter if the synchronize fails, whatever * the reason. */ if (rc != 0) ++ptp->bad_syncs; + mutex_unlock(&ptp->phc_lock); return rc; } @@ -1570,6 +1578,7 @@ int efx_ptp_probe(struct efx_nic *efx, struct efx_channel *channel) if (!efx->ptp_data) return -ENOMEM; + mutex_init(&ptp->phc_lock); ptp->efx = efx; ptp->channel = channel; @@ -1641,6 +1650,7 @@ fail2: efx_nic_free_buffer(efx, &ptp->start); fail1: + mutex_destroy(&ptp->phc_lock); kfree(efx->ptp_data); efx->ptp_data = NULL; @@ -1695,6 +1705,7 @@ void efx_ptp_remove(struct efx_nic *efx) destroy_workqueue(efx->ptp_data->workwq); efx_nic_free_buffer(efx, &efx->ptp_data->start); + mutex_destroy(&efx->ptp_data->phc_lock); kfree(efx->ptp_data); efx->ptp_data = NULL; } @@ -2106,23 +2117,23 @@ static int efx_phc_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) MCDI_SET_QWORD(inadj, PTP_IN_ADJUST_FREQ, adjustment_ns); MCDI_SET_DWORD(inadj, PTP_IN_ADJUST_SECONDS, 0); MCDI_SET_DWORD(inadj, PTP_IN_ADJUST_NANOSECONDS, 0); + mutex_lock(&ptp_data->phc_lock); rc = efx_mcdi_rpc(efx, MC_CMD_PTP, inadj, sizeof(inadj), NULL, 0, NULL); - if (rc != 0) - return rc; - - ptp_data->current_adjfreq = adjustment_ns; - return 0; + if (!rc) + ptp_data->current_adjfreq = adjustment_ns; + mutex_unlock(&ptp_data->phc_lock); + return rc; } -static int efx_phc_adjtime(struct ptp_clock_info *ptp, s64 delta) +static int _efx_phc_adjtime(struct efx_ptp_data *ptp_data, s64 delta) + __must_hold(&ptp_data->phc_lock) { - u32 nic_major, nic_minor; - struct efx_ptp_data *ptp_data = container_of(ptp, - struct efx_ptp_data, - phc_clock_info); - struct efx_nic *efx = ptp_data->efx; MCDI_DECLARE_BUF(inbuf, MC_CMD_PTP_IN_ADJUST_LEN); + struct efx_nic *efx = ptp_data->efx; + u32 nic_major, nic_minor; + + lockdep_assert_held(&ptp_data->phc_lock); efx->ptp_data->ns_to_nic_time(delta, &nic_major, &nic_minor); @@ -2135,16 +2146,29 @@ static int efx_phc_adjtime(struct ptp_clock_info *ptp, s64 delta) NULL, 0, NULL); } -static int efx_phc_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +static int efx_phc_adjtime(struct ptp_clock_info *ptp, s64 delta) { - struct efx_ptp_data *ptp_data = container_of(ptp, - struct efx_ptp_data, - phc_clock_info); - struct efx_nic *efx = ptp_data->efx; - MCDI_DECLARE_BUF(inbuf, MC_CMD_PTP_IN_READ_NIC_TIME_LEN); - MCDI_DECLARE_BUF(outbuf, MC_CMD_PTP_OUT_READ_NIC_TIME_LEN); + struct efx_ptp_data *ptp_data = container_of(ptp, struct efx_ptp_data, + phc_clock_info); int rc; + + mutex_lock(&ptp_data->phc_lock); + rc = _efx_phc_adjtime(ptp_data, delta); + mutex_unlock(&ptp_data->phc_lock); + return rc; +} + +static int _efx_phc_gettime(struct efx_ptp_data *ptp_data, + struct timespec64 *ts) + __must_hold(&ptp_data->phc_lock) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_PTP_OUT_READ_NIC_TIME_LEN); + MCDI_DECLARE_BUF(inbuf, MC_CMD_PTP_IN_READ_NIC_TIME_LEN); + struct efx_nic *efx = ptp_data->efx; ktime_t kt; + int rc; + + lockdep_assert_held(&ptp_data->phc_lock); MCDI_SET_DWORD(inbuf, PTP_IN_OP, MC_CMD_PTP_OP_READ_NIC_TIME); MCDI_SET_DWORD(inbuf, PTP_IN_PERIPH_ID, 0); @@ -2161,28 +2185,34 @@ static int efx_phc_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) return 0; } +static int efx_phc_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct efx_ptp_data *ptp_data = container_of(ptp, struct efx_ptp_data, + phc_clock_info); + int rc; + + mutex_lock(&ptp_data->phc_lock); + rc = _efx_phc_gettime(ptp_data, ts); + mutex_unlock(&ptp_data->phc_lock); + return rc; +} + static int efx_phc_settime(struct ptp_clock_info *ptp, const struct timespec64 *e_ts) { - /* Get the current NIC time, efx_phc_gettime. - * Subtract from the desired time to get the offset - * call efx_phc_adjtime with the offset - */ + struct efx_ptp_data *ptp_data = container_of(ptp, struct efx_ptp_data, + phc_clock_info); + struct timespec64 time_now, delta; int rc; - struct timespec64 time_now; - struct timespec64 delta; - rc = efx_phc_gettime(ptp, &time_now); - if (rc != 0) - return rc; - - delta = timespec64_sub(*e_ts, time_now); - - rc = efx_phc_adjtime(ptp, timespec64_to_ns(&delta)); - if (rc != 0) - return rc; - - return 0; + mutex_lock(&ptp_data->phc_lock); + rc = _efx_phc_gettime(ptp_data, &time_now); + if (!rc) { + delta = timespec64_sub(*e_ts, time_now); + rc = _efx_phc_adjtime(ptp_data, timespec64_to_ns(&delta)); + } + mutex_unlock(&ptp_data->phc_lock); + return rc; } static int efx_phc_enable(struct ptp_clock_info *ptp, -- 2.34.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions 2026-10-09 18:32 ` [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions Alex Austin @ 2026-10-10 19:10 ` netdev-bot+sashiko 0 siblings, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-10-10 19:10 UTC (permalink / raw) To: alex.austin Cc: netdev, andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba, linux-kernel, linux-net-drivers, pabeni, richardcochran, smhodgson, alucero, pieter.jansen-van-vuuren, alucerop Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 3 (1) · Low: 2 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] At this commit, ptp->host_time_pps is written by efx_ptp_process_times() while phc_lock is held. - [Medium] phc_lock is created separately in each PCI function's efx_ptp_data: efx_ptp_probe() calls mutex_init(&ptp->phc_lock) for… - [Medium] sfc: data race on current_adjfreq in efx_ptp_start - [Low] The patch changes how efx_ptp_synchronize() (drivers/net/ethernet/sfc/ptp.c) handles a failed… - [Low] The commit message says clock adjustments are serialized with phc_lock "so they cannot change the PHC during a capture". Pre-existing issues: - [Medium] efx_ptp_start() resets ptp->current_adjfreq = 0 (line 1457) without phc_lock. -- sfc: ptp: serialize host and MC synchronization transactions This commit adds a phc_lock mutex to struct efx_ptp_data and holds it across the SYNCHRONIZE DMA handshake in efx_ptp_synchronize(). The PHC adjfine, adjtime, gettime and settime callbacks are serialized with the same mutex, and efx_phc_settime() now reads and adjusts under one lock hold. > Protect the DMA handshake and result processing against concurrent > synchronization callers. Serialize clock adjustments with the same mutex > so they cannot change the PHC during a capture. [Severity: Low] Is this rationale accurate for adjustments made on the same function? efx_ptp_synchronize() acquires the MCDI interface in efx_mcdi_rpc_start()->efx_mcdi_acquire_sync(). It only releases it at the end of efx_mcdi_rpc_finish(): drivers/net/ethernet/sfc/mcdi.c:efx_mcdi_acquire_sync() { wait_event(mcdi->wq, cmpxchg(&mcdi->state, MCDI_STATE_QUIESCENT, MCDI_STATE_RUNNING_SYNC) == MCDI_STATE_QUIESCENT); } efx_phc_adjfine(), efx_phc_adjtime() and efx_phc_gettime() all go through efx_mcdi_rpc()->_efx_mcdi_rpc()->efx_mcdi_rpc_start(). So even before this patch, they could not send an MC command while SYNCHRONIZE was in flight. After rpc_finish, efx_ptp_process_times() only uses the response buffer and nic_to_kernel_time(). It does not use current_adjfreq. The commit message doesn't mention what the new PHC locking actually adds: - efx_phc_settime() now does its gettime and adjtime atomically. - The current_adjfreq cached by efx_phc_adjfine() now stays consistent with the PTP_IN_ADJUST_FREQ value that _efx_phc_adjtime() sends. Could the commit message describe these changes instead? > diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c > index 6ca9a75af01d8..ac9951fee5ddd 100644 > --- a/drivers/net/ethernet/sfc/ptp.c > +++ b/drivers/net/ethernet/sfc/ptp.c [ ... ] > @@ -1027,11 +1030,14 @@ static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) > MCDI_SET_QWORD(synch_buf, PTP_IN_SYNCHRONIZE_START_ADDR, > ptp->start.dma_addr); > > + mutex_lock(&ptp->phc_lock); > + [Severity: Medium] Can a per-function phc_lock protect a PHC that is shared across PCI functions? efx_ptp_probe() calls mutex_init(&ptp->phc_lock) for every function. Only the primary function registers the clock with ptp_clock_register(). efx_ptp_get_ts_info() reports the primary's clock for secondaries too: drivers/net/ethernet/sfc/ptp.c:efx_ptp_get_ts_info() { ... if (primary && primary->ptp_data && primary->ptp_data->phc_clock) ts_info->phc_index = ptp_clock_index(primary->ptp_data->phc_clock); ... } A secondary function still captures against that clock through efx_ptp_change_mode()->efx_ptp_synchronize(). While it does so it holds only secondary->ptp_data->phc_lock. On the primary, efx_phc_adjtime(), efx_phc_settime() and efx_phc_adjfine() take primary->ptp_data->phc_lock, which is a different mutex. They send MC_CMD_PTP_OP_ADJUST over the primary's own MCDI interface, so per-function MCDI serialization doesn't cover this case either. If ptp4l on the primary steps the PHC while SIOCSHWTSTAMP runs on a secondary, could the secondary's timesets become inconsistent? In that case efx_ptp_process_times() would return -EAGAIN. The baseline synchronisation in efx_ptp_change_mode() would then fail, and PTP would be stopped on that function: if (rc == 0) { rc = efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS * 2); if (rc != 0) efx_ptp_stop(efx); } Whether this actually goes wrong depends on whether the MC firmware processes the primary's ADJUST while it is servicing the secondary's SYNCHRONIZE. That can't be confirmed from the driver. Would a lock scoped to the clock be needed to fully cover the case the commit message describes? One option is a lock shared through efx->primary->ptp_data. > /* Clear flag that signals MC ready */ > WRITE_ONCE(*start, 0); > rc = efx_mcdi_rpc_start(efx, MC_CMD_PTP, synch_buf, > MC_CMD_PTP_IN_SYNCHRONIZE_LEN); > - EFX_WARN_ON_ONCE_PARANOID(rc); > + if (rc) > + goto out; [Severity: Low] This change looks correct, but it fixes a separate bug that the commit message doesn't mention. efx_mcdi_rpc_start() can fail before it calls efx_mcdi_acquire_sync(). This happens when efx_mcdi_check_supported() fails, when mc_bist_for_other_fn is set, or when mcdi->mode is MCDI_MODE_FAIL: drivers/net/ethernet/sfc/mcdi.c:efx_mcdi_rpc_start() { ... if (efx->mc_bist_for_other_fn) return -ENETDOWN; if (mcdi->mode == MCDI_MODE_FAIL) return -ENETDOWN; efx_mcdi_acquire_sync(mcdi); ... } Without paranoid checks, EFX_WARN_ON_ONCE_PARANOID() compiled to nothing. The old code then busy-waited for up to MAX_SYNCHRONISE_WAIT_MS and called efx_mcdi_rpc_finish() anyway. That path waited for a completion that never came and called efx_mcdi_abandon(). It then called efx_mcdi_release() on an interface this caller had never acquired. Could this be described in the commit message, or split into its own patch with its own justification? [ ... ] > @@ -1062,11 +1068,13 @@ static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) > ++ptp->no_time_syncs; > } > > +out: > /* Increment the bad syncs counter if the synchronize fails, whatever > * the reason. > */ > if (rc != 0) > ++ptp->bad_syncs; > + mutex_unlock(&ptp->phc_lock); > > return rc; > } [Severity: Medium] Is ptp->host_time_pps still racy after this unlock? efx_ptp_process_times() writes ptp->host_time_pps while phc_lock is held. efx_ptp_pps_worker() reads it only after efx_ptp_synchronize() has dropped the mutex: drivers/net/ethernet/sfc/ptp.c:efx_ptp_pps_worker() { ... if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS)) return; ptp_evt.type = PTP_CLOCK_PPSUSR; ptp_evt.pps_times = ptp->host_time_pps; ptp_clock_event(ptp->phc_clock, &ptp_evt); } A concurrent SIOCSHWTSTAMP can go through efx_ptp_change_mode()->efx_ptp_synchronize() and overwrite host_time_pps while the worker is copying it. The PPS event passed to ptp_clock_event() could then carry a torn timestamp. This race existed before this commit. The next patch in this series, "sfc: ptp: avoid racing PPS timestamp updates", removes host_time_pps from struct efx_ptp_data. That patch returns the result through a caller-owned struct efx_ptp_sync_result filled in under phc_lock, and efx_ptp_change_mode() passes NULL. The race is gone by the end of the series. [ ... ] > @@ -2106,23 +2117,23 @@ static int efx_phc_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) > MCDI_SET_QWORD(inadj, PTP_IN_ADJUST_FREQ, adjustment_ns); > MCDI_SET_DWORD(inadj, PTP_IN_ADJUST_SECONDS, 0); > MCDI_SET_DWORD(inadj, PTP_IN_ADJUST_NANOSECONDS, 0); > + mutex_lock(&ptp_data->phc_lock); > rc = efx_mcdi_rpc(efx, MC_CMD_PTP, inadj, sizeof(inadj), > NULL, 0, NULL); > - if (rc != 0) > - return rc; > - > - ptp_data->current_adjfreq = adjustment_ns; > - return 0; > + if (!rc) > + ptp_data->current_adjfreq = adjustment_ns; > + mutex_unlock(&ptp_data->phc_lock); > + return rc; > } [Severity: Medium] efx_ptp_start() still writes ptp->current_adjfreq without taking phc_lock: drivers/net/ethernet/sfc/ptp.c:efx_ptp_start() { ... ptp->evt_frag_idx = 0; ptp->current_adjfreq = 0; ... } current_adjfreq is an s64. _efx_phc_adjtime() reads it under phc_lock and passes it to MCDI_SET_QWORD(). That macro evaluates its value argument twice, once for the low dword and once for the high dword. Even on 64-bit the compiler is free to emit two separate loads. On 32-bit, the store in efx_ptp_start() is also two separate 32-bit writes. efx_ptp_start() is reached in two ways that don't hold phc_lock: - SIOCSHWTSTAMP via efx_ptp_change_mode() - efx_ptp_worker(), when reset_required is set So a concurrent clock_adjtime() on the PHC can run _efx_phc_adjtime() at the same moment. Could the MC then receive a PTP_IN_ADJUST_FREQ built from the low half of one value and the high half of another? Suppose current_adjfreq held a small negative value. The low dword could read the new 0 while the high dword still reads 0xffffffff. The result is a frequency adjustment of roughly -4.3e9, which is neither the old value nor 0. How the firmware reacts to such a value can't be determined from the driver. This is the same unlocked write raised above for efx_phc_adjfine(), but the consequence here is a corrupted value rather than just a lost one. This patch makes phc_lock the lock for current_adjfreq. Should efx_ptp_start() also take phc_lock around the reset? efx_ptp_start() runs in process context. It doesn't appear to be called with phc_lock already held: efx_ptp_change_mode() calls efx_ptp_synchronize() only after efx_ptp_start() returns. So taking the mutex there looks straightforward. [Severity: Medium] This is a pre-existing issue and was not introduced by this patch. efx_ptp_start() still resets current_adjfreq without taking phc_lock: drivers/net/ethernet/sfc/ptp.c:efx_ptp_start() { ... ptp->evt_frag_idx = 0; ptp->current_adjfreq = 0; ... } efx_ptp_start() is reached from three places: - efx_ptp_worker(), when reset_required is set - efx_ptp_start_datapath()->efx_ptp_restart() - efx_ptp_change_mode() None of them take phc_lock, so efx_ptp_start() can run alongside the PHC clock ioctls. Could this interleaving drop the servo's frequency correction? efx_phc_adjfine() sends frequency X to the MC current_adjfreq = X efx_ptp_start() current_adjfreq = 0 efx_phc_adjtime() _efx_phc_adjtime() sends PTP_IN_ADJUST_FREQ = 0 The next adjfine would correct this, so the frequency error is transient. Now that phc_lock protects the other accesses to current_adjfreq, should efx_ptp_start() take it as well? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009183240.1898762-1-alex.austin%40amd.com ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates 2026-10-09 18:32 [PATCH net 0/2] sfc: fix PTP synchronization races Alex Austin 2026-10-09 18:32 ` [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions Alex Austin @ 2026-10-09 18:32 ` Alex Austin 2026-10-10 19:10 ` netdev-bot+sashiko 2026-10-09 18:35 ` [PATCH net 0/2] sfc: fix PTP synchronization races netdev-bot+sinfo 2 siblings, 1 reply; 6+ messages in thread From: Alex Austin @ 2026-10-09 18:32 UTC (permalink / raw) To: netdev Cc: andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba, linux-kernel, linux-net-drivers, pabeni, richardcochran, smhodgson, alucero, pieter.jansen-van-vuuren, Alex Austin, Alejandro Lucero efx_ptp_pps_worker() can race with SIOCHWTSTAMP via efx_ptp_change_mode(). After the worker completes synchronization, the ioctl can start another synchronization and update host_time_pps while the worker copies it into the PPS event. This can report a partially updated timestamp. Serializing synchronization transactions alone does not protect this copy, which happens after phc_lock is released. Fixes: 7c236c43b838 ("sfc: Add support for IEEE-1588 PTP") Signed-off-by: Alex Austin <alex.austin@amd.com> Reviewed-by: Alejandro Lucero <alucerop@amd.com> Reviewed-by: Pieter Jansen van Vuuren <pieter.jansen-van-vuuren@amd.com> --- drivers/net/ethernet/sfc/ptp.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c index ac9951f..103df1f 100644 --- a/drivers/net/ethernet/sfc/ptp.c +++ b/drivers/net/ethernet/sfc/ptp.c @@ -205,6 +205,14 @@ struct efx_ptp_timeset { u32 window; /* Derived: end - start, allowing for wrap */ }; +/** + * struct efx_ptp_sync_result - Result of host and MC synchronisation + * @host_time_pps: Host time at the NIC top of second + */ +struct efx_ptp_sync_result { + struct pps_event_time host_time_pps; +}; + /** * struct efx_ptp_rxfilter - Filter for PTP packets * @list: Node of the list where the filter is added @@ -268,7 +276,6 @@ struct efx_ptp_rxfilter { * @evt_frag_idx: Current fragment number * @evt_code: Last event code * @start: Address at which MC indicates ready for synchronisation - * @host_time_pps: Host time at last PPS * @adjfreq_ppb_shift: Shift required to convert scaled parts-per-billion * frequency adjustment into a fixed point fractional nanosecond format. * @current_adjfreq: Current ppb adjustment. @@ -329,7 +336,6 @@ struct efx_ptp_data { int evt_frag_idx; int evt_code; struct efx_buffer start; - struct pps_event_time host_time_pps; unsigned int adjfreq_ppb_shift; s64 current_adjfreq; struct ptp_clock *phc_clock; @@ -911,7 +917,8 @@ static void efx_ptp_read_timeset(MCDI_DECLARE_STRUCT_PTR(data), static int efx_ptp_process_times(struct efx_nic *efx, MCDI_DECLARE_STRUCT_PTR(synch_buf), size_t response_length, - const struct pps_event_time *last_time) + const struct pps_event_time *last_time, + struct efx_ptp_sync_result *result) { unsigned number_readings = MCDI_VAR_ARRAY_LEN(response_length, @@ -989,6 +996,10 @@ efx_ptp_process_times(struct efx_nic *efx, MCDI_DECLARE_STRUCT_PTR(synch_buf), "PTP bad synchronisation seconds\n"); return -EAGAIN; } + + if (!result) + return 0; + delta.tv_sec = (last_sec - start_sec) & 1; delta.tv_nsec = last_time->ts_real.tv_nsec - @@ -1005,14 +1016,15 @@ efx_ptp_process_times(struct efx_nic *efx, MCDI_DECLARE_STRUCT_PTR(synch_buf), delta.tv_nsec += ktime_to_timespec64(mc_time).tv_nsec; /* Set PPS timestamp to match NIC top of second */ - ptp->host_time_pps = *last_time; - pps_sub_ts(&ptp->host_time_pps, delta); + result->host_time_pps = *last_time; + pps_sub_ts(&result->host_time_pps, delta); return 0; } /* Synchronize times between the host and the MC */ -static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) +static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings, + struct efx_ptp_sync_result *result) { struct efx_ptp_data *ptp = efx->ptp_data; MCDI_DECLARE_BUF(synch_buf, MC_CMD_PTP_OUT_SYNCHRONIZE_LENMAX); @@ -1061,7 +1073,7 @@ static int efx_ptp_synchronize(struct efx_nic *efx, unsigned int num_readings) &response_length); if (rc == 0) { rc = efx_ptp_process_times(efx, synch_buf, response_length, - &last_time); + &last_time, result); if (rc == 0) ++ptp->good_syncs; else @@ -1494,14 +1506,15 @@ static void efx_ptp_pps_worker(struct work_struct *work) { struct efx_ptp_data *ptp = container_of(work, struct efx_ptp_data, pps_work); + struct efx_ptp_sync_result result = {}; struct efx_nic *efx = ptp->efx; struct ptp_clock_event ptp_evt; - if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS)) + if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS, &result)) return; ptp_evt.type = PTP_CLOCK_PPSUSR; - ptp_evt.pps_times = ptp->host_time_pps; + ptp_evt.pps_times = result.host_time_pps; ptp_clock_event(ptp->phc_clock, &ptp_evt); } @@ -1837,7 +1850,8 @@ int efx_ptp_change_mode(struct efx_nic *efx, bool enable_wanted, rc = efx_ptp_start(efx); if (rc == 0) { rc = efx_ptp_synchronize(efx, - PTP_SYNC_ATTEMPTS * 2); + PTP_SYNC_ATTEMPTS * 2, + NULL); if (rc != 0) efx_ptp_stop(efx); } -- 2.34.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates 2026-10-09 18:32 ` [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates Alex Austin @ 2026-10-10 19:10 ` netdev-bot+sashiko 0 siblings, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-10-10 19:10 UTC (permalink / raw) To: alex.austin Cc: netdev, andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba, linux-kernel, linux-net-drivers, pabeni, richardcochran, smhodgson, alucero, pieter.jansen-van-vuuren, alucerop Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · Low: 0 (1) If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] The Siena PTP driver still has the PPS timestamp race that this patch fixes in sfc. Pre-existing issues: - [Low] The comment above efx_ptp_process_times() in drivers/net/ethernet/sfc/ptp.c (lines 909-916) says the function will… -- sfc: ptp: avoid racing PPS timestamp updates This patch removes the shared host_time_pps field from struct efx_ptp_data in the sfc driver. The computed PPS timestamp is now returned through a caller-owned struct efx_ptp_sync_result. efx_ptp_pps_worker() uses a stack-local result, and efx_ptp_change_mode() passes NULL. > diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c > index ac9951fee5ddd..103df1f8fa92d 100644 > --- a/drivers/net/ethernet/sfc/ptp.c > +++ b/drivers/net/ethernet/sfc/ptp.c [ ... ] > @@ -911,7 +917,8 @@ static void efx_ptp_read_timeset(MCDI_DECLARE_STRUCT_PTR(data), > static int > efx_ptp_process_times(struct efx_nic *efx, MCDI_DECLARE_STRUCT_PTR(synch_buf), > size_t response_length, > - const struct pps_event_time *last_time) > + const struct pps_event_time *last_time, > + struct efx_ptp_sync_result *result) > { [Severity: Low] This isn't a bug introduced by this patch, but the comment above efx_ptp_process_times() doesn't match what the function does: * Extract times from returned results, and establish the minimum value * seen. The minimum value represents the "best" possible time and events * too much greater than this are rejected - the machine is, perhaps, too * busy. The loop never computes an observed minimum. It checks each reading against SYNCHRONISATION_GRANULARITY_NS, MAX_SYNCHRONISATION_NS and ptp->min_synchronisation_ns, and every reading that passes updates last_good: } else if (corrected < ptp->min_synchronisation_ns) { ++ptp->undersize_sync_windows; } else { ngood++; last_good = i; } When several readings pass, the function uses the last one, not the one with the smallest window. Could the comment be updated to describe the fixed thresholds and the last-good selection? [ ... ] > @@ -1494,14 +1506,15 @@ static void efx_ptp_pps_worker(struct work_struct *work) > { > struct efx_ptp_data *ptp = > container_of(work, struct efx_ptp_data, pps_work); > + struct efx_ptp_sync_result result = {}; > struct efx_nic *efx = ptp->efx; > struct ptp_clock_event ptp_evt; > > - if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS)) > + if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS, &result)) > return; > > ptp_evt.type = PTP_CLOCK_PPSUSR; > - ptp_evt.pps_times = ptp->host_time_pps; > + ptp_evt.pps_times = result.host_time_pps; > ptp_clock_event(ptp->phc_clock, &ptp_evt); > } [Severity: Medium] Does the Siena driver still have the same race? drivers/net/ethernet/sfc/siena/ptp.c is a near copy of this file. It still keeps host_time_pps as a shared field in its struct efx_ptp_data. Its efx_ptp_process_times() writes that field on every successful synchronization: drivers/net/ethernet/sfc/siena/ptp.c:efx_ptp_process_times() { ... /* Set PPS timestamp to match NIC top of second */ ptp->host_time_pps = *last_time; pps_sub_ts(&ptp->host_time_pps, delta); ... } Its efx_ptp_pps_worker() copies the field after the synchronization returns: drivers/net/ethernet/sfc/siena/ptp.c:efx_ptp_pps_worker() { ... if (efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS)) return; ptp_evt.type = PTP_CLOCK_PPSUSR; ptp_evt.pps_times = ptp->host_time_pps; ... } The SIOCSHWTSTAMP path can rewrite the field at the same time: efx_siena_ptp_change_mode() efx_ptp_synchronize(efx, PTP_SYNC_ATTEMPTS * 2) efx_ptp_process_times() ptp->host_time_pps = *last_time; The Siena efx_ptp_synchronize() also takes no lock, because siena/ptp.c has no phc_lock. So Siena has neither the serialization from "sfc: ptp: serialize host and MC synchronization transactions" nor the caller-owned result added here. Concurrent synchronizations can overwrite ptp->timeset[] and host_time_pps while the worker reads them. Both copies come from the Fixes: commit 7c236c43b838, and the commit message does not mention Siena. Should siena/ptp.c get the same fix, either in this patch or as a follow-up? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009183240.1898762-1-alex.austin%40amd.com ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net 0/2] sfc: fix PTP synchronization races 2026-10-09 18:32 [PATCH net 0/2] sfc: fix PTP synchronization races Alex Austin 2026-10-09 18:32 ` [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions Alex Austin 2026-10-09 18:32 ` [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates Alex Austin @ 2026-10-09 18:35 ` netdev-bot+sinfo 2 siblings, 0 replies; 6+ messages in thread From: netdev-bot+sinfo @ 2026-10-09 18:35 UTC (permalink / raw) To: Alex Austin Cc: netdev, andrew+netdev, bhutchings, davem, ecree.xilinx, edumazet, kuba, linux-kernel, linux-net-drivers, pabeni, richardcochran, smhodgson, alucero, pieter.jansen-van-vuuren Hi! This is an automated message. This series looks like a fix, but its commit messages seem to be missing some information: - What hardware the change was tested on. For driver fixes please mention the device (and if relevant firmware version) used for testing, or say that the change was not tested on real hardware. Please do not repost the series just to address the above. Instead, reply to this email with the missing information, so that reviewers can take it into account. If the series needs another revision for other reasons, please include the information in the commit messages then. The evaluation is done by an LLM so it may be wrong, if you think that is the case please reply and explain. ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-10 19:10 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-09 18:32 [PATCH net 0/2] sfc: fix PTP synchronization races Alex Austin 2026-10-09 18:32 ` [PATCH net 1/2] sfc: ptp: serialize host and MC synchronization transactions Alex Austin 2026-10-10 19:10 ` netdev-bot+sashiko 2026-10-09 18:32 ` [PATCH net 2/2] sfc: ptp: avoid racing PPS timestamp updates Alex Austin 2026-10-10 19:10 ` netdev-bot+sashiko 2026-10-09 18:35 ` [PATCH net 0/2] sfc: fix PTP synchronization races netdev-bot+sinfo
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®