* [PATCH iwl-net v5 1/8] ice: Unlink the PTP port before destroying its ps_lock
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 2/8] ice: Protect the control PF pointer with RCU and a rwsem Sergey Temerkhanov
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
err_clean_pf destroys ptp->port.ps_lock before ice_ptp_cleanup_pf()
removes the port from adapter->ports.list and drains the outstanding
references, so while the mutex is being destroyed the port is still
reachable by every other PF on the adapter:
ice_ptp_settime64()
ice_ptp_restart_all_phy()
list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node)
ice_ptp_port_phy_restart(port)
mutex_lock(&ptp_port->ps_lock);
ice_ptp_setup_pf() publishes the port before ice_ptp_init_port() runs,
and a link event can set port.link_up at any point after that, so the
walk does not necessarily skip it.
Call ice_ptp_cleanup_pf() first and destroy the mutex once no other PF
can reach the port, matching the order already used by the
ICE_PTP_READY path in ice_ptp_release().
Release the Tx timestamp tracker here as well, so the label frees
everything the port owns. ice_ptp_init_port() allocates it through
ice_ptp_alloc_tx_tracker(), and any failure unwinding through
err_clean_pf after that point would otherwise leak tx->tstamps,
tx->in_use and tx->stale. The tx.init guard is needed because
err_clean_pf is also reached when ice_ptp_init_port() itself fails, and
ice_ptp_alloc_tx_tracker() leaves tx->lock uninitialized in that case.
Fixes: 23a5b9b12de9 ("ice: fix PTP cleanup on driver removal in error path")
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index a735dfa2b03e..5104ccc70d4c 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -3587,8 +3587,14 @@ void ice_ptp_init(struct ice_pf *pf)
return;
err_clean_pf:
- mutex_destroy(&ptp->port.ps_lock);
+ /* Unlink the port before tearing down anything it still shares with
+ * the other PFs on the adapter: ice_ptp_restart_all_phy() can be
+ * walking adapter->ports.list and taking ps_lock until this returns.
+ */
ice_ptp_cleanup_pf(pf);
+ if (ptp->port.tx.init)
+ ice_ptp_release_tx_tracker(pf, &ptp->port.tx);
+ mutex_destroy(&ptp->port.ps_lock);
err_exit:
/* If we registered a PTP clock, release it */
if (pf->ptp.clock) {
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 2/8] ice: Protect the control PF pointer with RCU and a rwsem
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 1/8] ice: Unlink the PTP port before destroying its ps_lock Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 3/8] ice: Cache struct ice_hw pointer for split register reads Sergey Temerkhanov
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
Mark adapter->ctrl_pf as __rcu and route every reader through
rcu_dereference_check(), publishing it with rcu_assign_pointer(). Readers
that can run in an RCU read-side critical section are wrapped
accordingly.
Add adapter->ctrl_pf_lock for the sleepable users which cannot sit in an
RCU read-side critical section, namely the PTP hardware semaphore in
ice_ptp_lock()/ice_ptp_unlock() and the TX reference clock paths in
ice_txclk.c. Pass the resolved control PF into ice_txclk_enable_peer()
so it is looked up once under that lock.
The pointer is still only ever published and never cleared, so there is
no functional change here. This only puts the annotations and the
locking in place for the lifetime handling that follows.
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 25 +++++-
drivers/net/ethernet/intel/ice/ice_adapter.c | 1 +
drivers/net/ethernet/intel/ice/ice_adapter.h | 5 +-
drivers/net/ethernet/intel/ice/ice_ptp.c | 83 +++++++++++++-------
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 23 ++++++
drivers/net/ethernet/intel/ice/ice_txclk.c | 20 +++--
6 files changed, 120 insertions(+), 37 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 117391f62848..08b40626b876 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -41,6 +41,7 @@
#include <linux/cpu_rmap.h>
#include <linux/dim.h>
#include <linux/gnss.h>
+#include <linux/rcupdate.h>
#include <net/pkt_cls.h>
#include <net/pkt_sched.h>
#include <net/tc_act/tc_mirred.h>
@@ -1141,26 +1142,44 @@ static inline bool ice_pf_src_tmr_owned(struct ice_pf *pf)
* ice_get_primary_hw - Get pointer to primary ice_hw structure
* @pf: pointer to PF structure
*
+ * The function must be called from an RCU read-side critical section or
+ * while holding adapter->ctrl_pf_lock.
+ * hw is embedded in struct ice_pf, so either mechanism protects its lifetime.
+ *
* Return: A pointer to ice_hw structure with access to timesync
* register space.
*/
static inline struct ice_hw *ice_get_primary_hw(struct ice_pf *pf)
{
- if (!pf->adapter->ctrl_pf)
+ struct ice_pf *ctrl_pf;
+
+ ctrl_pf = rcu_dereference_check(pf->adapter->ctrl_pf,
+ lockdep_is_held(&pf->adapter->ctrl_pf_lock));
+
+ if (!ctrl_pf)
return &pf->hw;
else
- return &pf->adapter->ctrl_pf->hw;
+ return &ctrl_pf->hw;
}
/**
* ice_get_ctrl_pf - Get pointer to Control PF of the adapter
* @pf: pointer to the current PF structure
*
+ * The control PF is the PF which owns the PTP clock for the adapter.
+ * Only the control PF is allowed to perform certain operations on the
+ * PTP clock such as adjusting the time or configuring the pins.
+ *
+ * This function must be called from an RCU read-side critical section or
+ * while holding adapter->ctrl_pf_lock.
+ *
* Return: A pointer to ice_pf structure which is Control PF,
* NULL if it's not initialized yet.
*/
static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf)
{
- return !pf->adapter ? NULL : pf->adapter->ctrl_pf;
+ return !pf->adapter ? NULL :
+ rcu_dereference_check(pf->adapter->ctrl_pf,
+ lockdep_is_held(&pf->adapter->ctrl_pf_lock));
}
#endif /* _ICE_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c
index ba2a50f0da95..5dc4e5f1c6aa 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.c
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c
@@ -69,6 +69,7 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
spin_lock_init(&adapter->ports.lock);
INIT_LIST_HEAD(&adapter->ports.list);
+ init_rwsem(&adapter->ctrl_pf_lock);
return adapter;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 6f94c5fd6a88..9de00435bf2b 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -6,6 +6,7 @@
#include <linux/types.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/spinlock_types.h>
#include <linux/refcount_types.h>
@@ -36,6 +37,7 @@ struct ice_port_list {
* @txq_ctx_lock: Spinlock protecting access to the GLCOMM_QTX_CNTX_CTL register
* @cpi_phy_lock: Per-PHY mutex serializing CPI REQ/ACK transactions.
* Index 0 = PHY0, index 1 = PHY1. Used on E825C devices.
+ * @ctrl_pf_lock: Protect control PF lifetime for sleepable users
* @ctrl_pf: Control PF of the adapter
* @rebuild_lock: serialize PFR recovery across PFs of the same adapter
* @ports: Ports list
@@ -52,7 +54,8 @@ struct ice_adapter {
/* Serialize PFR recovery touching shared FW global state */
struct mutex rebuild_lock;
- struct ice_pf *ctrl_pf;
+ struct rw_semaphore ctrl_pf_lock;
+ struct ice_pf __rcu *ctrl_pf;
struct ice_port_list ports;
u64 index;
};
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 5104ccc70d4c..455e3af30abe 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2,6 +2,7 @@
/* Copyright (C) 2021, Intel Corporation. */
#include <linux/rculist.h>
+#include <linux/rcupdate.h>
#include <linux/wait_bit.h>
#include "ice.h"
#include "ice_lib.h"
@@ -60,6 +61,19 @@ static const struct ice_ptp_pin_desc ice_pin_desc_dpll[] = {
{ SDP3, { 3, -1 }, { 0, 0 }},
};
+/**
+ * ice_get_ctrl_ptp - Get the PTP structure for the control PF
+ * @pf: The PF pointer to look up at
+ *
+ * The control PF is the PF which owns the PTP clock for the adapter.
+ * Only the control PF is allowed to perform certain operations on the
+ * PTP clock such as adjusting the time or configuring the pins.
+ *
+ * This function must be called from an RCU read-side critical section or
+ * while holding adapter->ctrl_pf_lock.
+ *
+ * Return: Pointer to the PTP structure of the control PF, or NULL if not found
+ */
static struct ice_ptp *ice_get_ctrl_ptp(struct ice_pf *pf)
{
struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
@@ -208,39 +222,42 @@ u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
u32 hi, lo, lo2;
u8 tmr_idx;
- if (!ice_is_primary(hw))
- hw = ice_get_primary_hw(pf);
-
- tmr_idx = ice_get_ptp_src_clock_index(hw);
- guard(spinlock)(&pf->adapter->ptp_gltsyn_time_lock);
- /* Read the system timestamp pre PHC read */
- ptp_read_system_prets(sts);
-
- if (hw->mac_type == ICE_MAC_E830) {
- u64 clk_time = rd64(hw, E830_GLTSYN_TIME_L(tmr_idx));
+ scoped_guard(rcu) {
+ if (!ice_is_primary(hw))
+ hw = ice_get_primary_hw(pf);
- /* Read the system timestamp post PHC read */
- ptp_read_system_postts(sts);
-
- return clk_time;
- }
+ tmr_idx = ice_get_ptp_src_clock_index(hw);
+ guard(spinlock)(&pf->adapter->ptp_gltsyn_time_lock);
+ /* Read the system timestamp pre PHC read */
+ ptp_read_system_prets(sts);
- lo = rd32(hw, GLTSYN_TIME_L(tmr_idx));
+ if (hw->mac_type == ICE_MAC_E830) {
+ u64 clk_time = rd64(hw, E830_GLTSYN_TIME_L(tmr_idx));
- /* Read the system timestamp post PHC read */
- ptp_read_system_postts(sts);
+ /* Read the system timestamp post PHC read */
+ ptp_read_system_postts(sts);
- hi = rd32(hw, GLTSYN_TIME_H(tmr_idx));
- lo2 = rd32(hw, GLTSYN_TIME_L(tmr_idx));
+ return clk_time;
+ }
- if (lo2 < lo) {
- /* if TIME_L rolled over read TIME_L again and update
- * system timestamps
- */
- ptp_read_system_prets(sts);
lo = rd32(hw, GLTSYN_TIME_L(tmr_idx));
+
+ /* Read the system timestamp post PHC read */
ptp_read_system_postts(sts);
+
hi = rd32(hw, GLTSYN_TIME_H(tmr_idx));
+ lo2 = rd32(hw, GLTSYN_TIME_L(tmr_idx));
+
+ if (lo2 < lo) {
+ /* if TIME_L rolled over read TIME_L again and update
+ * system timestamps
+ */
+ ptp_read_system_prets(sts);
+ lo = rd32(hw, GLTSYN_TIME_L(tmr_idx));
+ ptp_read_system_postts(sts);
+ hi = rd32(hw, GLTSYN_TIME_H(tmr_idx));
+ }
+
}
return ((u64)hi << 32) | lo;
@@ -3245,14 +3262,19 @@ void ice_ptp_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
static void ice_ptp_setup_adapter(struct ice_pf *pf)
{
- pf->adapter->ctrl_pf = pf;
+ guard(rwsem_write)(&pf->adapter->ctrl_pf_lock);
+
+ rcu_assign_pointer(pf->adapter->ctrl_pf, pf);
}
static int ice_ptp_setup_pf(struct ice_pf *pf)
{
- struct ice_ptp *ctrl_ptp = ice_get_ctrl_ptp(pf);
struct ice_ptp *ptp = &pf->ptp;
+ struct ice_ptp *ctrl_ptp;
+
+ guard(rwsem_read)(&pf->adapter->ctrl_pf_lock);
+ ctrl_ptp = ice_get_ctrl_ptp(pf);
if (!ctrl_ptp) {
dev_info(ice_pf_to_dev(pf),
"PTP unavailable: no controlling PF\n");
@@ -3327,11 +3349,15 @@ static void ice_ptp_cleanup_pf(struct ice_pf *pf)
*/
int ice_ptp_clock_index(struct ice_pf *pf)
{
- struct ice_ptp *ctrl_ptp = ice_get_ctrl_ptp(pf);
+ struct ice_ptp *ctrl_ptp;
struct ptp_clock *clock;
+ guard(rcu)();
+
+ ctrl_ptp = ice_get_ctrl_ptp(pf);
if (!ctrl_ptp)
return -1;
+
clock = ctrl_ptp->clock;
return clock ? ptp_clock_index(clock) : -1;
@@ -3595,6 +3621,7 @@ void ice_ptp_init(struct ice_pf *pf)
if (ptp->port.tx.init)
ice_ptp_release_tx_tracker(pf, &ptp->port.tx);
mutex_destroy(&ptp->port.ps_lock);
+
err_exit:
/* If we registered a PTP clock, release it */
if (pf->ptp.clock) {
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 881f002daa74..0e138390f8c8 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021, Intel Corporation. */
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include "ice_common.h"
@@ -335,6 +336,8 @@ void ice_ptp_src_cmd(struct ice_hw *hw, enum ice_ptp_tmr_cmd cmd)
struct ice_pf *pf = container_of(hw, struct ice_pf, hw);
u32 cmd_val = ice_ptp_tmr_cmd_to_src_reg(hw, cmd);
+ guard(rcu)();
+
if (!ice_is_primary(hw))
hw = ice_get_primary_hw(pf);
@@ -353,6 +356,8 @@ static void ice_ptp_exec_tmr_cmd(struct ice_hw *hw)
{
struct ice_pf *pf = container_of(hw, struct ice_pf, hw);
+ guard(rcu)();
+
if (!ice_is_primary(hw))
hw = ice_get_primary_hw(pf);
@@ -2009,6 +2014,8 @@ static int ice_read_phy_and_phc_time_eth56g(struct ice_hw *hw, u8 port,
zo = rd32(hw, GLTSYN_SHTIME_0(tmr_idx));
lo = rd32(hw, GLTSYN_SHTIME_L(tmr_idx));
} else {
+ guard(rcu)();
+
zo = rd32(ice_get_primary_hw(pf), GLTSYN_SHTIME_0(tmr_idx));
lo = rd32(ice_get_primary_hw(pf), GLTSYN_SHTIME_L(tmr_idx));
}
@@ -2180,8 +2187,13 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
lo = rd32(hw, GLTSYN_INCVAL_L(tmr_idx));
hi = rd32(hw, GLTSYN_INCVAL_H(tmr_idx));
} else {
+ /* cleanup.h advises against mixing goto with scoped helpers,
+ * and this function unwinds the PTP semaphore with goto below.
+ */
+ rcu_read_lock();
lo = rd32(ice_get_primary_hw(pf), GLTSYN_INCVAL_L(tmr_idx));
hi = rd32(ice_get_primary_hw(pf), GLTSYN_INCVAL_H(tmr_idx));
+ rcu_read_unlock();
}
incval = (u64)hi << 32 | lo;
@@ -5321,6 +5333,9 @@ static void ice_ptp_init_phy_e830(struct ice_ptp_hw *ptp)
*
* Software must clear the busy bit with a write to release the lock for other
* functions when done.
+ *
+ * A successful call holds adapter->ctrl_pf_lock for read until
+ * ice_ptp_unlock() is called.
*/
bool ice_ptp_lock(struct ice_hw *hw)
{
@@ -5328,6 +5343,8 @@ bool ice_ptp_lock(struct ice_hw *hw)
u32 hw_lock;
int i;
+ down_read(&pf->adapter->ctrl_pf_lock);
+
if (!ice_is_primary(hw))
hw = ice_get_primary_hw(pf);
@@ -5345,6 +5362,9 @@ bool ice_ptp_lock(struct ice_hw *hw)
break;
}
+ if (hw_lock)
+ up_read(&pf->adapter->ctrl_pf_lock);
+
return !hw_lock;
}
@@ -5359,10 +5379,13 @@ void ice_ptp_unlock(struct ice_hw *hw)
{
struct ice_pf *pf = container_of(hw, struct ice_pf, hw);
+ lockdep_assert_held(&pf->adapter->ctrl_pf_lock);
+
if (!ice_is_primary(hw))
hw = ice_get_primary_hw(pf);
wr32(hw, PFTSYN_SEM + (PFTSYN_SEM_BYTES * hw->pf_id), 0);
+ up_read(&pf->adapter->ctrl_pf_lock);
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_txclk.c b/drivers/net/ethernet/intel/ice/ice_txclk.c
index 48459f971cbf..4a59ad729547 100644
--- a/drivers/net/ethernet/intel/ice/ice_txclk.c
+++ b/drivers/net/ethernet/intel/ice/ice_txclk.c
@@ -41,6 +41,7 @@ ice_txclk_get_pin(struct ice_pf *pf, enum ice_e825c_ref_clk ref_clk)
/**
* ice_txclk_enable_peer - Enable required TX reference clock on peer PHY
* @pf: pointer to the PF structure
+ * @ctrl_pf: control PF protected by adapter->ctrl_pf_lock
* @clk: TX reference clock that must be enabled
*
* Some TX reference clocks on E825-class devices (SyncE and EREF0) must
@@ -54,13 +55,15 @@ ice_txclk_get_pin(struct ice_pf *pf, enum ice_e825c_ref_clk ref_clk)
*
* Return: 0 on success or negative error code on failure.
*/
-static int ice_txclk_enable_peer(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
+static int ice_txclk_enable_peer(struct ice_pf *pf, struct ice_pf *ctrl_pf,
+ enum ice_e825c_ref_clk clk)
{
- struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
bool peer_clk_in_use;
u8 port_num, phy;
int err;
+ lockdep_assert_held(&pf->adapter->ctrl_pf_lock);
+
if (clk == ICE_REF_CLK_ENET)
return 0;
@@ -118,12 +121,15 @@ static int ice_txclk_enable_peer(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
*/
int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
{
- struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
struct ice_port_info *port_info;
+ struct ice_pf *ctrl_pf;
bool clk_in_use;
u8 port_num, phy;
int err;
+ guard(rwsem_read)(&pf->adapter->ctrl_pf_lock);
+ ctrl_pf = ice_get_ctrl_pf(pf);
+
if (pf->ptp.port.tx_clk == clk)
return 0;
@@ -164,7 +170,7 @@ int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
mutex_unlock(&ctrl_pf->dplls.lock);
if (!clk_in_use) {
- err = ice_txclk_enable_peer(pf, clk);
+ err = ice_txclk_enable_peer(pf, ctrl_pf, clk);
if (err)
return err;
}
@@ -215,15 +221,18 @@ int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
void ice_txclk_update_and_notify(struct ice_pf *pf)
{
struct ice_ptp_port *ptp_port = &pf->ptp.port;
- struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
struct dpll_pin *old_pin = NULL;
struct dpll_pin *new_pin = NULL;
+ struct ice_pf *ctrl_pf;
struct ice_hw *hw = &pf->hw;
enum ice_e825c_ref_clk clk;
bool notify_dpll = false;
int err;
u8 phy;
+ down_read(&pf->adapter->ctrl_pf_lock);
+ ctrl_pf = ice_get_ctrl_pf(pf);
+
phy = ptp_port->port_num / hw->ptp.ports_per_phy;
/* Hold txclk_notify_rwsem for read across the entire critical
@@ -351,4 +360,5 @@ void ice_txclk_update_and_notify(struct ice_pf *pf)
out:
up_read(&pf->dplls.txclk_notify_rwsem);
+ up_read(&pf->adapter->ctrl_pf_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 3/8] ice: Cache struct ice_hw pointer for split register reads
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 1/8] ice: Unlink the PTP port before destroying its ps_lock Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 2/8] ice: Protect the control PF pointer with RCU and a rwsem Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 4/8] ice: Reject PTP access without a control PF Sergey Temerkhanov
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
Cache the primary ice_hw pointer to ensure consistency between
calls (both parts of a value will be read from the same NAC).
ice_get_primary_hw() will never return NULL, but during the
ctrl_pf cleanup there may be a case when one call will return
the pointer to the ctrl_pf->hw and the subsequent one - to the
pf->hw which generally are not the same.
Struct ice_hw is embedded in the struct ice_pf so it is protected
by the same critical section - no additional synchronization is
needed.
Fixes: e2193f9f9ec9 ("ice: enable timesync operation on 2xNAC E825 devices")
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Tested-by: Frederick Lawler <fred@cloudflare.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 0e138390f8c8..38e90a68ba78 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -4,6 +4,7 @@
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
+#include "ice.h"
#include "ice_common.h"
#include "ice_ptp_hw.h"
#include "ice_ptp_consts.h"
@@ -2014,10 +2015,14 @@ static int ice_read_phy_and_phc_time_eth56g(struct ice_hw *hw, u8 port,
zo = rd32(hw, GLTSYN_SHTIME_0(tmr_idx));
lo = rd32(hw, GLTSYN_SHTIME_L(tmr_idx));
} else {
+ struct ice_hw *pri_hw;
+
guard(rcu)();
- zo = rd32(ice_get_primary_hw(pf), GLTSYN_SHTIME_0(tmr_idx));
- lo = rd32(ice_get_primary_hw(pf), GLTSYN_SHTIME_L(tmr_idx));
+ pri_hw = ice_get_primary_hw(pf);
+
+ zo = rd32(pri_hw, GLTSYN_SHTIME_0(tmr_idx));
+ lo = rd32(pri_hw, GLTSYN_SHTIME_L(tmr_idx));
}
*phc_time = (u64)lo << 32 | zo;
@@ -2187,12 +2192,16 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
lo = rd32(hw, GLTSYN_INCVAL_L(tmr_idx));
hi = rd32(hw, GLTSYN_INCVAL_H(tmr_idx));
} else {
+ struct ice_hw *pri_hw;
+
/* cleanup.h advises against mixing goto with scoped helpers,
* and this function unwinds the PTP semaphore with goto below.
*/
rcu_read_lock();
- lo = rd32(ice_get_primary_hw(pf), GLTSYN_INCVAL_L(tmr_idx));
- hi = rd32(ice_get_primary_hw(pf), GLTSYN_INCVAL_H(tmr_idx));
+ pri_hw = ice_get_primary_hw(pf);
+
+ lo = rd32(pri_hw, GLTSYN_INCVAL_L(tmr_idx));
+ hi = rd32(pri_hw, GLTSYN_INCVAL_H(tmr_idx));
rcu_read_unlock();
}
incval = (u64)hi << 32 | lo;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 4/8] ice: Reject PTP access without a control PF
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
` (2 preceding siblings ...)
2026-09-24 12:59 ` [PATCH iwl-net v5 3/8] ice: Cache struct ice_hw pointer for split register reads Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 5/8] ice: Clear the control PF pointer when the control PF is removed Sergey Temerkhanov
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
The timesync register space is not accessible through a secondary NAC.
ice_get_primary_hw() currently falls back to the caller's hardware when
the control PF is absent, causing secondary PFs to access the wrong
register space after the control PF is removed.
Return NULL instead and propagate the missing control PF through PHC
reads and timer setup paths. This leaves secondary PFs operational while
PTP requests fail safely until a control PF is available again.
Fixes: e2193f9f9ec9 ("ice: enable timesync operation on 2xNAC E825 devices")
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 9 +--
drivers/net/ethernet/intel/ice/ice_ptp.c | 71 ++++++++++++++-----
drivers/net/ethernet/intel/ice/ice_ptp.h | 11 +--
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 23 +++++-
.../net/ethernet/intel/ice/virt/virtchnl.c | 5 +-
5 files changed, 85 insertions(+), 34 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 08b40626b876..c454f19a2cb2 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1146,8 +1146,8 @@ static inline bool ice_pf_src_tmr_owned(struct ice_pf *pf)
* while holding adapter->ctrl_pf_lock.
* hw is embedded in struct ice_pf, so either mechanism protects its lifetime.
*
- * Return: A pointer to ice_hw structure with access to timesync
- * register space.
+ * Return: A pointer to ice_hw structure with access to timesync register
+ * space, or NULL if the control PF is not available.
*/
static inline struct ice_hw *ice_get_primary_hw(struct ice_pf *pf)
{
@@ -1156,10 +1156,7 @@ static inline struct ice_hw *ice_get_primary_hw(struct ice_pf *pf)
ctrl_pf = rcu_dereference_check(pf->adapter->ctrl_pf,
lockdep_is_held(&pf->adapter->ctrl_pf_lock));
- if (!ctrl_pf)
- return &pf->hw;
- else
- return &ctrl_pf->hw;
+ return ctrl_pf ? &ctrl_pf->hw : NULL;
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 455e3af30abe..3ee29c0cd726 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -214,17 +214,23 @@ void ice_ptp_restore_timestamp_mode(struct ice_pf *pf)
* @pf: Board private structure
* @sts: Optional parameter for holding a pair of system timestamps from
* the system clock. Will be ignored if NULL is given.
+ * @time: Storage for the source clock time
+ *
+ * Return: 0 on success, negative error code otherwise.
*/
-u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
- struct ptp_system_timestamp *sts)
+int ice_ptp_read_src_clk_reg(struct ice_pf *pf,
+ struct ptp_system_timestamp *sts, u64 *time)
{
struct ice_hw *hw = &pf->hw;
u32 hi, lo, lo2;
u8 tmr_idx;
scoped_guard(rcu) {
- if (!ice_is_primary(hw))
+ if (!ice_is_primary(hw)) {
hw = ice_get_primary_hw(pf);
+ if (!hw)
+ return -ENODEV;
+ }
tmr_idx = ice_get_ptp_src_clock_index(hw);
guard(spinlock)(&pf->adapter->ptp_gltsyn_time_lock);
@@ -232,12 +238,12 @@ u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
ptp_read_system_prets(sts);
if (hw->mac_type == ICE_MAC_E830) {
- u64 clk_time = rd64(hw, E830_GLTSYN_TIME_L(tmr_idx));
+ *time = rd64(hw, E830_GLTSYN_TIME_L(tmr_idx));
/* Read the system timestamp post PHC read */
ptp_read_system_postts(sts);
- return clk_time;
+ return 0;
}
lo = rd32(hw, GLTSYN_TIME_L(tmr_idx));
@@ -260,7 +266,9 @@ u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
}
- return ((u64)hi << 32) | lo;
+ *time = ((u64)hi << 32) | lo;
+
+ return 0;
}
/**
@@ -997,13 +1005,15 @@ static int ice_ptp_init_tx(struct ice_pf *pf, struct ice_ptp_tx *tx, u8 port)
* Return:
* * 0 - OK, successfully updated
* * -EAGAIN - PF was busy, need to reschedule the update
+ * * -ENODEV - no control PF owns the source clock register space
+ * * -EOPNOTSUPP - PTP support is not compiled in
*/
static int ice_ptp_update_cached_phctime(struct ice_pf *pf)
{
struct device *dev = ice_pf_to_dev(pf);
unsigned long update_before;
u64 systime;
- int i;
+ int err, i;
update_before = pf->ptp.cached_phc_jiffies + msecs_to_jiffies(2000);
if (pf->ptp.cached_phc_time &&
@@ -1016,11 +1026,22 @@ static int ice_ptp_update_cached_phctime(struct ice_pf *pf)
}
/* Read the current PHC time */
- systime = ice_ptp_read_src_clk_reg(pf, NULL);
-
- /* Update the cached PHC time stored in the PF structure */
- WRITE_ONCE(pf->ptp.cached_phc_time, systime);
- WRITE_ONCE(pf->ptp.cached_phc_jiffies, jiffies);
+ err = ice_ptp_read_src_clk_reg(pf, NULL, &systime);
+ if (err) {
+ /* The source clock is gone, so the cache can no longer be
+ * refreshed. Publish zero instead of leaving the last good
+ * value behind: ice_ptp_get_rx_hwts() has no staleness check
+ * and would keep extending Rx timestamps against it.
+ * cached_phc_jiffies is deliberately left alone so that
+ * ice_ptp_extend_40b_ts() still discards Tx timestamps.
+ */
+ systime = 0;
+ WRITE_ONCE(pf->ptp.cached_phc_time, 0);
+ } else {
+ /* Update the cached PHC time stored in the PF structure */
+ WRITE_ONCE(pf->ptp.cached_phc_time, systime);
+ WRITE_ONCE(pf->ptp.cached_phc_jiffies, jiffies);
+ }
if (test_and_set_bit(ICE_CFG_BUSY, pf->state))
return -EAGAIN;
@@ -1043,7 +1064,7 @@ static int ice_ptp_update_cached_phctime(struct ice_pf *pf)
}
clear_bit(ICE_CFG_BUSY, pf->state);
- return 0;
+ return err;
}
/**
@@ -1073,8 +1094,8 @@ static void ice_ptp_reset_cached_phctime(struct ice_pf *pf)
* properly reset them here. This could lead to reporting of
* invalid timestamps, but there isn't much we can do.
*/
- dev_warn(dev, "%s: ICE_CFG_BUSY, unable to immediately update cached PHC time\n",
- __func__);
+ dev_warn(dev, "%s: unable to immediately update cached PHC time, err %d\n",
+ __func__, err);
/* Queue the work item to update the Rx rings when possible */
kthread_queue_delayed_work(pf->ptp.kworker, &pf->ptp.work,
@@ -1811,6 +1832,7 @@ static int ice_ptp_cfg_perout(struct ice_pf *pf, struct ptp_perout_request *rq,
u64 clk, period, start, phase;
struct ice_hw *hw = &pf->hw;
int pin_desc_idx;
+ int err;
pin_desc_idx = ice_ptp_find_pin_idx(pf, PTP_PF_PEROUT, rq->index);
if (pin_desc_idx < 0)
@@ -1849,7 +1871,10 @@ static int ice_ptp_cfg_perout(struct ice_pf *pf, struct ptp_perout_request *rq,
* at the next multiple of period, maintaining phase at least 0.5 second
* from now, so we have time to write it to HW.
*/
- clk = ice_ptp_read_src_clk_reg(pf, NULL) + NSEC_PER_MSEC * 500;
+ err = ice_ptp_read_src_clk_reg(pf, NULL, &clk);
+ if (err)
+ return err;
+ clk += NSEC_PER_MSEC * 500;
if (rq->flags & PTP_PEROUT_PHASE || start <= clk - prop_delay_ns)
start = div64_u64(clk + period - 1, period) * period + phase;
@@ -1992,8 +2017,12 @@ ice_ptp_gettimex64(struct ptp_clock_info *info, struct timespec64 *ts,
{
struct ice_pf *pf = ptp_info_to_pf(info);
u64 time_ns;
+ int err;
+
+ err = ice_ptp_read_src_clk_reg(pf, sts, &time_ns);
+ if (err)
+ return err;
- time_ns = ice_ptp_read_src_clk_reg(pf, sts);
*ts = ns_to_timespec64(time_ns);
return 0;
}
@@ -3041,9 +3070,13 @@ static void ice_ptp_periodic_work(struct kthread_work *work)
ice_ptp_maybe_trigger_tx_interrupt(pf);
- /* Run twice a second or reschedule if phc update failed */
+ /* Run twice a second, or retry soon if another thread was busy
+ * updating the Rx rings. Other errors, such as a missing control PF,
+ * do not clear up on their own and must not turn the periodic worker
+ * into a busy loop.
+ */
kthread_queue_delayed_work(ptp->kworker, &ptp->work,
- msecs_to_jiffies(err ? 10 : 500));
+ msecs_to_jiffies(err == -EAGAIN ? 10 : 500));
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index 8a341e3cba17..562822b106d5 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -331,8 +331,8 @@ void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx);
void ice_ptp_process_ts(struct ice_pf *pf);
irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf);
bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq);
-u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
- struct ptp_system_timestamp *sts);
+int ice_ptp_read_src_clk_reg(struct ice_pf *pf,
+ struct ptp_system_timestamp *sts, u64 *time);
u64 ice_ptp_get_rx_hwts(const union ice_32b_rx_flex_desc *rx_desc,
const struct ice_pkt_ctx *pkt_ctx);
@@ -384,10 +384,11 @@ ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
return false;
}
-static inline u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
- struct ptp_system_timestamp *sts)
+static inline int ice_ptp_read_src_clk_reg(struct ice_pf *pf,
+ struct ptp_system_timestamp *sts,
+ u64 *time)
{
- return 0;
+ return -EOPNOTSUPP;
}
static inline u64
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 38e90a68ba78..a85aa8f99057 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -339,8 +339,11 @@ void ice_ptp_src_cmd(struct ice_hw *hw, enum ice_ptp_tmr_cmd cmd)
guard(rcu)();
- if (!ice_is_primary(hw))
+ if (!ice_is_primary(hw)) {
hw = ice_get_primary_hw(pf);
+ if (!hw)
+ return;
+ }
wr32(hw, GLTSYN_CMD, cmd_val);
}
@@ -359,8 +362,11 @@ static void ice_ptp_exec_tmr_cmd(struct ice_hw *hw)
guard(rcu)();
- if (!ice_is_primary(hw))
+ if (!ice_is_primary(hw)) {
hw = ice_get_primary_hw(pf);
+ if (!hw)
+ return;
+ }
guard(spinlock)(&pf->adapter->ptp_gltsyn_time_lock);
wr32(hw, GLTSYN_CMD_SYNC, SYNC_EXEC_CMD);
@@ -2020,6 +2026,8 @@ static int ice_read_phy_and_phc_time_eth56g(struct ice_hw *hw, u8 port,
guard(rcu)();
pri_hw = ice_get_primary_hw(pf);
+ if (!pri_hw)
+ return -ENODEV;
zo = rd32(pri_hw, GLTSYN_SHTIME_0(tmr_idx));
lo = rd32(pri_hw, GLTSYN_SHTIME_L(tmr_idx));
@@ -2199,6 +2207,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
*/
rcu_read_lock();
pri_hw = ice_get_primary_hw(pf);
+ if (!pri_hw) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
lo = rd32(pri_hw, GLTSYN_INCVAL_L(tmr_idx));
hi = rd32(pri_hw, GLTSYN_INCVAL_H(tmr_idx));
@@ -5354,8 +5366,13 @@ bool ice_ptp_lock(struct ice_hw *hw)
down_read(&pf->adapter->ctrl_pf_lock);
- if (!ice_is_primary(hw))
+ if (!ice_is_primary(hw)) {
hw = ice_get_primary_hw(pf);
+ if (!hw) {
+ up_read(&pf->adapter->ctrl_pf_lock);
+ return false;
+ }
+ }
#define MAX_TRIES 15
diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.c b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
index ca8018e3dd42..66332d02c5ba 100644
--- a/drivers/net/ethernet/intel/ice/virt/virtchnl.c
+++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
@@ -2485,7 +2485,10 @@ static int ice_vc_get_phc_time(struct ice_vf *vf)
len = sizeof(*phc_time);
- phc_time->time = ice_ptp_read_src_clk_reg(pf, NULL);
+ if (ice_ptp_read_src_clk_reg(pf, NULL, &phc_time->time)) {
+ v_ret = VIRTCHNL_STATUS_ERR_NOT_SUPPORTED;
+ len = 0;
+ }
err:
/* send the response back to the VF */
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 5/8] ice: Clear the control PF pointer when the control PF is removed
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
` (3 preceding siblings ...)
2026-09-24 12:59 ` [PATCH iwl-net v5 4/8] ice: Reject PTP access without a control PF Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 6/8] ice: Document control PF lock ordering Sergey Temerkhanov
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
Zero adapter->ctrl_pf when the PF owning it is removed and wait for
pre-existing RCU readers before its storage can be released. Without
this the pointer outlives the control PF, so sibling PFs keep
dereferencing it after it has been torn down.
The preceding patches make this safe to do: readers already resolve the
pointer once per critical section, and the PTP paths already reject a
missing control PF instead of falling back to the caller's own register
space.
Fixes: e2193f9f9ec9 ("ice: enable timesync operation on 2xNAC E825 devices")
Fixes: e800654e85b5 ("ice: Use ice_adapter for PTP shared data instead of auxdev")
Reported-by: Frederick Lawler <fred@cloudflare.com>
Closes: https://lore.kernel.org/all/aIKWoZzEPoa1omlw@CMGLRV3/
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Frederick Lawler <fred@cloudflare.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 3ee29c0cd726..3ed37bbae161 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -3300,6 +3300,20 @@ static void ice_ptp_setup_adapter(struct ice_pf *pf)
rcu_assign_pointer(pf->adapter->ctrl_pf, pf);
}
+static void ice_ptp_cleanup_adapter(struct ice_pf *pf)
+{
+ guard(rwsem_write)(&pf->adapter->ctrl_pf_lock);
+
+ /* Zero out adapter->ctrl_pf pointer when the ctrl_pf itself
+ * is being removed to prevent any secondary PFs from accessing
+ * it after it is deleted.
+ */
+ if (ice_get_ctrl_pf(pf) == pf) {
+ rcu_assign_pointer(pf->adapter->ctrl_pf, NULL);
+ synchronize_rcu();
+ }
+}
+
static int ice_ptp_setup_pf(struct ice_pf *pf)
{
struct ice_ptp *ptp = &pf->ptp;
@@ -3656,6 +3670,7 @@ void ice_ptp_init(struct ice_pf *pf)
mutex_destroy(&ptp->port.ps_lock);
err_exit:
+ ice_ptp_cleanup_adapter(pf);
/* If we registered a PTP clock, release it */
if (pf->ptp.clock) {
ptp_clock_unregister(ptp->clock);
@@ -3683,6 +3698,7 @@ void ice_ptp_release(struct ice_pf *pf)
if (pf->ptp.state != ICE_PTP_READY) {
mutex_destroy(&pf->ptp.port.ps_lock);
ice_ptp_cleanup_pf(pf);
+ ice_ptp_cleanup_adapter(pf);
if (pf->ptp.clock) {
ptp_clock_unregister(pf->ptp.clock);
pf->ptp.clock = NULL;
@@ -3697,6 +3713,8 @@ void ice_ptp_release(struct ice_pf *pf)
ice_ptp_cleanup_pf(pf);
+ ice_ptp_cleanup_adapter(pf);
+
ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
ice_ptp_disable_all_extts(pf);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 6/8] ice: Document control PF lock ordering
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
` (4 preceding siblings ...)
2026-09-24 12:59 ` [PATCH iwl-net v5 5/8] ice: Clear the control PF pointer when the control PF is removed Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 7/8] ice: Annotate PTP control PF lock handoff Sergey Temerkhanov
2026-09-24 12:59 ` [PATCH iwl-net v5 8/8] ice: Release control PF lock before RCU wait Sergey Temerkhanov
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
Document adapter->ctrl_pf_lock as the outer lifetime lock for TX clock
state and PTP hardware semaphore operations. This makes the existing
ordering constraints visible to future callers and helps avoid nesting
the rwsem below a DPLL lock.
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.h | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h b/drivers/net/ethernet/intel/ice/ice_dpll.h
index f7e6680d124c..67f2fc6e79e4 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.h
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.h
@@ -156,10 +156,21 @@ struct ice_dpll {
* Locking:
* Acquisition order (top to bottom):
*
- * txclk_notify_rwsem (read)
- * -> pf->dplls.lock
- * -> ctrl_pf->dplls.lock
+ * adapter->ctrl_pf_lock (read)
+ * -> txclk_notify_rwsem (read)
+ * -> pf->dplls.lock
+ * -> ctrl_pf->dplls.lock
*
+ * PTP hardware semaphore path:
+ *
+ * ptp_port->ps_lock
+ * -> adapter->ctrl_pf_lock (read)
+ * -> PFTSYN_SEM
+ *
+ * - ice_ptp_port_phy_restart() takes @ps_lock before reaching
+ * ice_ptp_lock() via ice_start_phy_timer_eth56g() or
+ * ice_start_phy_timer_e82x(), so ctrl_pf_lock is not the outermost
+ * lock on that path. Never take @ps_lock with ctrl_pf_lock held.
* - @lock serializes all DPLL state mutations on this PF. When the
* controlling PF's lock must also be taken (e.g. updating the shared
* tx_refclks usage map), acquire pf->dplls.lock first, then
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH iwl-net v5 7/8] ice: Annotate PTP control PF lock handoff
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
` (5 preceding siblings ...)
2026-09-24 12:59 ` [PATCH iwl-net v5 6/8] ice: Document control PF lock ordering Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
2026-09-25 20:36 ` Nathan Chancellor
2026-09-24 12:59 ` [PATCH iwl-net v5 8/8] ice: Release control PF lock before RCU wait Sergey Temerkhanov
7 siblings, 1 reply; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
ice_ptp_lock() conditionally retains ctrl_pf_lock for read when it
successfully acquires the hardware semaphore. The matching unlock occurs
in ice_ptp_unlock(). Describe this cross-function handoff with context
analysis annotations so static analysis can verify callers.
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 12 ++++++++++
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 26 +++++++++++++--------
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 6 +++--
3 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index c454f19a2cb2..8b7891ac25b0 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1179,4 +1179,16 @@ static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf)
rcu_dereference_check(pf->adapter->ctrl_pf,
lockdep_is_held(&pf->adapter->ctrl_pf_lock));
}
+
+/* container_of() expands to a statement expression, which clang cannot parse
+ * inside a context analysis attribute argument, so open-code the cast here.
+ */
+#define ice_hw_ctrl_pf_lock(_hw) \
+ (&((struct ice_pf *)((void *)(_hw) - \
+ offsetof(struct ice_pf, hw)))->adapter->ctrl_pf_lock)
+
+bool ice_ptp_lock(struct ice_hw *hw)
+ __cond_acquires_shared(true, ice_hw_ctrl_pf_lock(hw));
+void ice_ptp_unlock(struct ice_hw *hw)
+ __releases_shared(ice_hw_ctrl_pf_lock(hw));
#endif /* _ICE_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index a85aa8f99057..eb7fdd000cf2 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -5361,15 +5361,16 @@ static void ice_ptp_init_phy_e830(struct ice_ptp_hw *ptp)
bool ice_ptp_lock(struct ice_hw *hw)
{
struct ice_pf *pf = container_of(hw, struct ice_pf, hw);
+ struct ice_hw *pri_hw = hw;
u32 hw_lock;
int i;
- down_read(&pf->adapter->ctrl_pf_lock);
+ down_read(ice_hw_ctrl_pf_lock(hw));
if (!ice_is_primary(hw)) {
- hw = ice_get_primary_hw(pf);
- if (!hw) {
- up_read(&pf->adapter->ctrl_pf_lock);
+ pri_hw = ice_get_primary_hw(pf);
+ if (!pri_hw) {
+ up_read(ice_hw_ctrl_pf_lock(hw));
return false;
}
}
@@ -5377,7 +5378,8 @@ bool ice_ptp_lock(struct ice_hw *hw)
#define MAX_TRIES 15
for (i = 0; i < MAX_TRIES; i++) {
- hw_lock = rd32(hw, PFTSYN_SEM + (PFTSYN_SEM_BYTES * hw->pf_id));
+ hw_lock = rd32(pri_hw,
+ PFTSYN_SEM + (PFTSYN_SEM_BYTES * pri_hw->pf_id));
hw_lock = hw_lock & PFTSYN_SEM_BUSY_M;
if (hw_lock) {
/* Somebody is holding the lock */
@@ -5389,7 +5391,7 @@ bool ice_ptp_lock(struct ice_hw *hw)
}
if (hw_lock)
- up_read(&pf->adapter->ctrl_pf_lock);
+ up_read(ice_hw_ctrl_pf_lock(hw));
return !hw_lock;
}
@@ -5404,14 +5406,18 @@ bool ice_ptp_lock(struct ice_hw *hw)
void ice_ptp_unlock(struct ice_hw *hw)
{
struct ice_pf *pf = container_of(hw, struct ice_pf, hw);
+ struct ice_hw *pri_hw = hw;
- lockdep_assert_held(&pf->adapter->ctrl_pf_lock);
+ lockdep_assert_held(ice_hw_ctrl_pf_lock(hw));
+ /* ctrl_pf cannot be cleared while ctrl_pf_lock is held for read, so a
+ * successful ice_ptp_lock() guarantees a primary hw is still there.
+ */
if (!ice_is_primary(hw))
- hw = ice_get_primary_hw(pf);
+ pri_hw = ice_get_primary_hw(pf);
- wr32(hw, PFTSYN_SEM + (PFTSYN_SEM_BYTES * hw->pf_id), 0);
- up_read(&pf->adapter->ctrl_pf_lock);
+ wr32(pri_hw, PFTSYN_SEM + (PFTSYN_SEM_BYTES * pri_hw->pf_id), 0);
+ up_read(ice_hw_ctrl_pf_lock(hw));
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 16b1988e993d..ce8eb0672e71 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -293,8 +293,10 @@ extern const struct ice_vernier_info_e82x e822_vernier[NUM_ICE_PTP_LNK_SPD];
/* Device agnostic functions */
u8 ice_get_ptp_src_clock_index(struct ice_hw *hw);
-bool ice_ptp_lock(struct ice_hw *hw);
-void ice_ptp_unlock(struct ice_hw *hw);
+/* ice_ptp_lock()/ice_ptp_unlock() are declared in ice.h, where struct ice_pf
+ * and struct ice_adapter are complete and ice_hw_ctrl_pf_lock() can be used
+ * in their context analysis annotations.
+ */
void ice_ptp_src_cmd(struct ice_hw *hw, enum ice_ptp_tmr_cmd cmd);
int ice_ptp_init_time(struct ice_hw *hw, u64 time);
int ice_ptp_write_incval(struct ice_hw *hw, u64 incval);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH iwl-net v5 7/8] ice: Annotate PTP control PF lock handoff
2026-09-24 12:59 ` [PATCH iwl-net v5 7/8] ice: Annotate PTP control PF lock handoff Sergey Temerkhanov
@ 2026-09-25 20:36 ` Nathan Chancellor
0 siblings, 0 replies; 10+ messages in thread
From: Nathan Chancellor @ 2026-09-25 20:36 UTC (permalink / raw)
To: Sergey Temerkhanov; +Cc: intel-wired-lan, netdev
On Thu, Sep 24, 2026 at 12:59:15PM +0000, Sergey Temerkhanov wrote:
> ice_ptp_lock() conditionally retains ctrl_pf_lock for read when it
> successfully acquires the hardware semaphore. The matching unlock occurs
> in ice_ptp_unlock(). Describe this cross-function handoff with context
> analysis annotations so static analysis can verify callers.
>
> Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
> Reviewed-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice.h | 12 ++++++++++
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 26 +++++++++++++--------
> drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 6 +++--
> 3 files changed, 32 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
> index c454f19a2cb2..8b7891ac25b0 100644
> --- a/drivers/net/ethernet/intel/ice/ice.h
> +++ b/drivers/net/ethernet/intel/ice/ice.h
> @@ -1179,4 +1179,16 @@ static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf)
> rcu_dereference_check(pf->adapter->ctrl_pf,
> lockdep_is_held(&pf->adapter->ctrl_pf_lock));
> }
> +
> +/* container_of() expands to a statement expression, which clang cannot parse
> + * inside a context analysis attribute argument, so open-code the cast here.
> + */
> +#define ice_hw_ctrl_pf_lock(_hw) \
> + (&((struct ice_pf *)((void *)(_hw) - \
> + offsetof(struct ice_pf, hw)))->adapter->ctrl_pf_lock)
FWIW, another alternative to open coding container_of(), which I don't
love to see, is using a static inline function, which would likely read
better as well.
https://lore.kernel.org/f147ad227b85439d56183e60e0332547b84441bc.1790360262.git.bvanassche@acm.org/
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH iwl-net v5 8/8] ice: Release control PF lock before RCU wait
2026-09-24 12:59 [PATCH iwl-net v5 0/8] Rework usage of the control PF pointer in struct ice_adapter Sergey Temerkhanov
` (6 preceding siblings ...)
2026-09-24 12:59 ` [PATCH iwl-net v5 7/8] ice: Annotate PTP control PF lock handoff Sergey Temerkhanov
@ 2026-09-24 12:59 ` Sergey Temerkhanov
7 siblings, 0 replies; 10+ messages in thread
From: Sergey Temerkhanov @ 2026-09-24 12:59 UTC (permalink / raw)
To: intel-wired-lan; +Cc: netdev
Clear ctrl_pf while holding ctrl_pf_lock for write, but release the
rwsem before waiting for pre-existing RCU readers. The published NULL
already prevents new readers from acquiring the retiring PF, so holding
the writer lock through synchronize_rcu() only stalls sleepable users.
Signed-off-by: Sergey Temerkhanov <sergey.temerkhanov@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 3ed37bbae161..24d4ab2197c3 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -3302,16 +3302,21 @@ static void ice_ptp_setup_adapter(struct ice_pf *pf)
static void ice_ptp_cleanup_adapter(struct ice_pf *pf)
{
- guard(rwsem_write)(&pf->adapter->ctrl_pf_lock);
+ bool synchronize = false;
- /* Zero out adapter->ctrl_pf pointer when the ctrl_pf itself
- * is being removed to prevent any secondary PFs from accessing
- * it after it is deleted.
- */
- if (ice_get_ctrl_pf(pf) == pf) {
- rcu_assign_pointer(pf->adapter->ctrl_pf, NULL);
- synchronize_rcu();
+ scoped_guard(rwsem_write, &pf->adapter->ctrl_pf_lock) {
+ /* Zero out adapter->ctrl_pf pointer when the ctrl_pf itself
+ * is being removed to prevent any secondary PFs from accessing
+ * it after it is deleted.
+ */
+ if (ice_get_ctrl_pf(pf) == pf) {
+ rcu_assign_pointer(pf->adapter->ctrl_pf, NULL);
+ synchronize = true;
+ }
}
+
+ if (synchronize)
+ synchronize_rcu();
}
static int ice_ptp_setup_pf(struct ice_pf *pf)
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread