AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/16 00:51 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies the Intel E800 series Ethernet driver (drivers/net/ethernet/intel/ice), specifically adapter port locking and PTP timestamping synchronization for E810/E825 hardware. This driver requires physical Intel E800-series PCIe hardware that is not emulated in standard QEMU or GCE virtualized environments, making the modified code structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/10/16 00:51 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 813f7d55075a31916916cd59ffbcfde1f91c5295\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Oct 16 00:51:15 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c\nindex 2dc3629d6d0f6..572862fcd2474 100644\n--- a/drivers/net/ethernet/intel/ice/ice_adapter.c\n+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c\n@@ -6,6 +6,7 @@\n #include \u003clinux/pci.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/srcu.h\u003e\n #include \u003clinux/xarray.h\u003e\n #include \"ice_adapter.h\"\n #include \"ice.h\"\n@@ -54,11 +55,18 @@ static unsigned long ice_adapter_xa_index(struct pci_dev *pdev)\n static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)\n {\n \tstruct ice_adapter *adapter;\n+\tint err;\n \n \tadapter = kzalloc_obj(*adapter);\n \tif (!adapter)\n \t\treturn NULL;\n \n+\terr = init_srcu_struct(\u0026adapter-\u003eports.srcu);\n+\tif (err) {\n+\t\tkfree(adapter);\n+\t\treturn NULL;\n+\t}\n+\n \tadapter-\u003eindex = ice_adapter_index(pdev);\n \tspin_lock_init(\u0026adapter-\u003eptp_gltsyn_time_lock);\n \tspin_lock_init(\u0026adapter-\u003etxq_ctx_lock);\n@@ -66,18 +74,22 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)\n \t\tmutex_init(\u0026adapter-\u003ecpi_phy_lock[i]);\n \trefcount_set(\u0026adapter-\u003erefcount, 1);\n \n-\tmutex_init(\u0026adapter-\u003eports.lock);\n-\tINIT_LIST_HEAD(\u0026adapter-\u003eports.ports);\n+\tspin_lock_init(\u0026adapter-\u003eports.lock);\n+\tINIT_LIST_HEAD(\u0026adapter-\u003eports.list);\n+\n+\tmutex_init(\u0026adapter-\u003eps_lock);\n \n \treturn adapter;\n }\n \n static void ice_adapter_free(struct ice_adapter *adapter)\n {\n-\tWARN_ON(!list_empty(\u0026adapter-\u003eports.ports));\n+\tWARN_ON(!list_empty(\u0026adapter-\u003eports.list));\n \tfor (int i = 0; i \u003c ARRAY_SIZE(adapter-\u003ecpi_phy_lock); i++)\n \t\tmutex_destroy(\u0026adapter-\u003ecpi_phy_lock[i]);\n-\tmutex_destroy(\u0026adapter-\u003eports.lock);\n+\n+\tcleanup_srcu_struct(\u0026adapter-\u003eports.srcu);\n+\tmutex_destroy(\u0026adapter-\u003eps_lock);\n \n \tkfree(adapter);\n }\ndiff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h\nindex 4f695f32da3d8..5f39166795b6f 100644\n--- a/drivers/net/ethernet/intel/ice/ice_adapter.h\n+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h\n@@ -17,15 +17,19 @@ struct ice_pf;\n /**\n  * struct ice_port_list - data used to store the list of adapter ports\n  *\n- * This structure contains data used to maintain a list of adapter ports\n+ * This structure contains data used to maintain a list of adapter ports.\n+ * Writers modifying the list *must* acquire the lock, and use SRCU safe list\n+ * operations. Readers should use srcu_read_lock() on the provided domain.\n  *\n- * @ports: list of ports\n- * @lock: protect access to the ports list\n+ * @list: list of ports\n+ * @lock: protect write access to the list\n+ * @srcu: Sleepable RCU domain for this adapter\n  */\n struct ice_port_list {\n-\tstruct list_head ports;\n-\t/* To synchronize the ports list operations */\n-\tstruct mutex lock;\n+\tstruct list_head list;\n+\t/* To synchronize write operations on the port list */\n+\tspinlock_t lock;\n+\tstruct srcu_struct srcu;\n };\n \n /**\n@@ -36,6 +40,7 @@ struct ice_port_list {\n  * @txq_ctx_lock: Spinlock protecting access to the GLCOMM_QTX_CNTX_CTL register\n  * @cpi_phy_lock: Per-PHY mutex serializing CPI REQ/ACK transactions.\n  *               Index 0 = PHY0, index 1 = PHY1. Used on E825C devices.\n+ * @ps_lock: Mutex to serialize PHY port start/stop across adapter.\n  * @ctrl_pf: Control PF of the adapter\n  * @ports: Ports list\n  * @index: 64-bit index cached for collision detection on 32bit systems\n@@ -49,6 +54,9 @@ struct ice_adapter {\n \t/* Serialize CPI REQ/ACK transactions per PHY (E825C only) */\n \tstruct mutex cpi_phy_lock[ICE_E825_MAX_PHYS];\n \n+\t/* For serializing PHY port start/stop sequences */\n+\tstruct mutex ps_lock;\n+\n \tstruct ice_pf *ctrl_pf;\n \tstruct ice_port_list ports;\n \tu64 index;\ndiff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c\nindex d88835482d3aa..b48b9c4551667 100644\n--- a/drivers/net/ethernet/intel/ice/ice_main.c\n+++ b/drivers/net/ethernet/intel/ice/ice_main.c\n@@ -3248,7 +3248,7 @@ static irqreturn_t ice_misc_intr_thread_fn(int __always_unused irq, void *data)\n \tice_irq_dynamic_ena(hw, NULL, NULL);\n \tice_flush(hw);\n \n-\tif (ice_ptp_tx_tstamps_pending(pf)) {\n+\tif (ice_ptp_tx_tstamps_pending(pf, true)) {\n \t\t/* If any new Tx timestamps happened while in interrupt,\n \t\t * re-arm the interrupt to trigger it again.\n \t\t */\n@@ -4810,6 +4810,14 @@ static void ice_init_link(struct ice_pf *pf)\n \n \tice_init_link_dflt_override(pf-\u003ehw.port_info);\n \n+\t/* Seed PTP link status from current PHY status, since link may\n+\t * already be up (e.g. after PXE boot) without a link-change edge to\n+\t * catch it later.\n+\t */\n+\tWRITE_ONCE(pf-\u003eptp.port.link_up,\n+\t\t   !!(pf-\u003ehw.port_info-\u003ephy.link_info.link_info \u0026\n+\t\t      ICE_AQ_LINK_UP));\n+\n \tice_check_link_cfg_err(pf,\n \t\t\t       pf-\u003ehw.port_info-\u003ephy.link_info.link_cfg_err);\n \n@@ -6745,7 +6753,6 @@ static int ice_up_complete(struct ice_vsi *vsi)\n \t\tice_print_link_msg(vsi, true);\n \t\tnetif_tx_start_all_queues(vsi-\u003enetdev);\n \t\tnetif_carrier_on(vsi-\u003enetdev);\n-\t\tice_ptp_link_change(pf, true);\n \t}\n \n \t/* Perform an initial read of the statistics registers now to\n@@ -7273,7 +7280,6 @@ int ice_down(struct ice_vsi *vsi)\n \n \tif (vsi-\u003enetdev) {\n \t\tvlan_err = ice_vsi_del_vlan_zero(vsi);\n-\t\tice_ptp_link_change(vsi-\u003eback, false);\n \t\tnetif_carrier_off(vsi-\u003enetdev);\n \t\tnetif_tx_disable(vsi-\u003enetdev);\n \t}\n@@ -7794,6 +7800,14 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)\n \n \tice_update_pf_netdev_link(pf);\n \n+\tif (test_bit(ICE_FLAG_PTP_SUPPORTED, pf-\u003eflags) \u0026\u0026 pf-\u003ehw.port_info) {\n+\t\tbool link_up;\n+\n+\t\tlink_up = !!(pf-\u003ehw.port_info-\u003ephy.link_info.link_info \u0026\n+\t\t\t     ICE_AQ_LINK_UP);\n+\t\tice_ptp_link_change(pf, link_up);\n+\t}\n+\n \t/* tell the firmware we are up */\n \terr = ice_send_version(pf);\n \tif (err) {\ndiff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c\nindex eaec36ab6ae3f..74e0c0138c501 100644\n--- a/drivers/net/ethernet/intel/ice/ice_ptp.c\n+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c\n@@ -1,6 +1,9 @@\n // SPDX-License-Identifier: GPL-2.0\n /* Copyright (C) 2021, Intel Corporation. */\n \n+#include \u003clinux/rculist.h\u003e\n+#include \u003clinux/srcu.h\u003e\n+#include \u003clinux/wait_bit.h\u003e\n #include \"ice.h\"\n #include \"ice_lib.h\"\n #include \"ice_trace.h\"\n@@ -362,9 +365,12 @@ static u64 ice_ptp_extend_40b_ts(struct ice_pf *pf, u64 in_tstamp)\n static bool\n ice_ptp_is_tx_tracker_up(struct ice_ptp_tx *tx)\n {\n+\tstruct ice_ptp_port *ptp_port =\n+\t\tcontainer_of(tx, struct ice_ptp_port, tx);\n+\n \tlockdep_assert_held(\u0026tx-\u003elock);\n \n-\treturn tx-\u003einit \u0026\u0026 !tx-\u003ecalibrating;\n+\treturn tx-\u003einit \u0026\u0026 !tx-\u003ecalibrating \u0026\u0026 READ_ONCE(ptp_port-\u003elink_up);\n }\n \n /**\n@@ -551,7 +557,9 @@ void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx)\n  * extremely unlikely that a packet will ever take this long to timestamp. If\n  * we detect a Tx timestamp request that has waited for this long we assume\n  * the packet will never be sent by hardware and discard it without reading\n- * the timestamp register.\n+ * the timestamp register. Note that in the unusual case where the PHY\n+ * continuously fails to clear the ready bitmap index, the slot may remain\n+ * locked for more than two seconds.\n  */\n static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n {\n@@ -561,7 +569,6 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \tstruct ice_pf *pf;\n \tstruct ice_hw *hw;\n \tu64 tstamp_ready;\n-\tbool link_up;\n \tint err;\n \tu8 idx;\n \n@@ -569,7 +576,7 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \tpf = ptp_port_to_pf(ptp_port);\n \thw = \u0026pf-\u003ehw;\n \n-\tif (!tx-\u003einit)\n+\tif (!tx-\u003einit || bitmap_empty(tx-\u003ein_use, tx-\u003elen))\n \t\treturn;\n \n \t/* Read the Tx ready status first */\n@@ -579,31 +586,27 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \t\t\treturn;\n \t}\n \n-\t/* Drop packets if the link went down */\n-\tlink_up = ptp_port-\u003elink_up;\n-\n \tfor_each_set_bit(idx, tx-\u003ein_use, tx-\u003elen) {\n \t\tstruct skb_shared_hwtstamps shhwtstamps = {};\n+\t\tbool drop_ts = false, timeout = false;\n \t\tu8 phy_idx = idx + tx-\u003eoffset;\n \t\tu64 raw_tstamp = 0, tstamp;\n-\t\tbool drop_ts = !link_up;\n \t\tstruct sk_buff *skb;\n \n+\t\t/* Paired with smp_mb__before_atomic() in ice_ptp_request_ts()\n+\t\t * to ensure ordering the in_use bitmap and the indexed fields\n+\t\t * in the tstamps array.\n+\t\t */\n+\t\tsmp_rmb();\n+\n \t\t/* Drop packets which have waited for more than 2 seconds */\n \t\tif (time_is_before_jiffies(tx-\u003etstamps[idx].start + 2 * HZ)) {\n \t\t\tdrop_ts = true;\n-\n-\t\t\t/* Count the number of Tx timestamps that timed out */\n-\t\t\tpf-\u003eptp.tx_hwtstamp_timeouts++;\n+\t\t\ttimeout = true;\n \t\t}\n \n-\t\t/* Only read a timestamp from the PHY if its marked as ready\n-\t\t * by the tstamp_ready register. This avoids unnecessary\n-\t\t * reading of timestamps which are not yet valid. This is\n-\t\t * important as we must read all timestamps which are valid\n-\t\t * and only timestamps which are valid during each interrupt.\n-\t\t * If we do not, the hardware logic for generating a new\n-\t\t * interrupt can get stuck on some devices.\n+\t\t/* Only read a timestamp from the PHY if it is marked as ready\n+\t\t * by the timestamp_ready register.\n \t\t */\n \t\tif (tx-\u003ehas_ready_bitmap \u0026\u0026\n \t\t    !(tstamp_ready \u0026 BIT_ULL(phy_idx))) {\n@@ -619,6 +622,27 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \t\tif (err \u0026\u0026 !drop_ts)\n \t\t\tcontinue;\n \n+\t\t/* verify ready bit cleared */\n+\t\tif (tx-\u003ehas_ready_bitmap) {\n+\t\t\terr = ice_get_phy_tx_tstamp_ready(hw, tx-\u003eblock, \u0026tstamp_ready);\n+\t\t\tif (err || tstamp_ready \u0026 BIT_ULL(phy_idx)) {\n+\t\t\t\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\t\t\t\tif (test_bit(idx, tx-\u003ein_use) \u0026\u0026\n+\t\t\t\t    !test_and_set_bit(idx, tx-\u003estale))\n+\t\t\t\t\tdev_dbg(ice_pf_to_dev(pf), \"PHY port %u failed to clear ready bit for idx %u\\n\",\n+\t\t\t\t\t\tptp_port-\u003eport_num, phy_idx);\n+\t\t\t\tskb = tx-\u003etstamps[idx].skb;\n+\t\t\t\ttx-\u003etstamps[idx].skb = NULL;\n+\t\t\t\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n+\t\t\t\t/* Free the SKB immediately since we're never\n+\t\t\t\t * going to report a timestamp for the stale\n+\t\t\t\t * packet.\n+\t\t\t\t */\n+\t\t\t\tdev_kfree_skb_any(skb);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n \t\tice_trace(tx_tstamp_fw_done, tx-\u003etstamps[idx].skb, idx);\n \n \t\t/* For PHYs which don't implement a proper timestamp ready\n@@ -635,6 +659,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \t\t\tdrop_ts = true;\n \n skip_ts_read:\n+\t\tif (timeout)\n+\t\t\tpf-\u003eptp.tx_hwtstamp_timeouts++;\n+\n \t\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n \t\tif (!tx-\u003ehas_ready_bitmap \u0026\u0026 raw_tstamp)\n \t\t\ttx-\u003etstamps[idx].cached_tstamp = raw_tstamp;\n@@ -645,8 +672,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \t\t\tdrop_ts = true;\n \t\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n \n-\t\t/* It is unlikely but possible that the SKB will have been\n-\t\t * flushed at this point due to link change or teardown.\n+\t\t/* The SKB may have already been freed such as due to failure\n+\t\t * to clear the ready bit, or being flushed due to link change\n+\t\t * or teardown.\n \t\t */\n \t\tif (!skb)\n \t\t\tcontinue;\n@@ -673,20 +701,37 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)\n \tpf-\u003eptp.tx_hwtstamp_good += tstamp_good;\n }\n \n+static void ice_ptp_release_port_srcu(struct kref *ref)\n+{\n+\t/* wake_up_var() requires full ordering but kref_put only provides\n+\t * release ordering.\n+\t */\n+\tsmp_mb__after_atomic();\n+\twake_up_var(ref);\n+}\n+\n static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)\n {\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n \tstruct ice_ptp_port *port;\n+\tint srcu_idx;\n \n-\tmutex_lock(\u0026pf-\u003eadapter-\u003eports.lock);\n-\tlist_for_each_entry(port, \u0026pf-\u003eadapter-\u003eports.ports, list_node) {\n+\tsrcu_idx = srcu_read_lock(\u0026ports-\u003esrcu);\n+\tlist_for_each_entry_srcu(port, \u0026ports-\u003elist, list_node,\n+\t\t\t\t srcu_read_lock_held(\u0026ports-\u003esrcu)) {\n \t\tstruct ice_ptp_tx *tx = \u0026port-\u003etx;\n \n-\t\tif (!tx || !tx-\u003einit)\n+\t\tif (!tx-\u003einit)\n+\t\t\tcontinue;\n+\n+\t\tif (!kref_get_unless_zero(\u0026port-\u003eref))\n \t\t\tcontinue;\n \n \t\tice_ptp_process_tx_tstamp(tx);\n+\n+\t\tkref_put(\u0026port-\u003eref, ice_ptp_release_port_srcu);\n \t}\n-\tmutex_unlock(\u0026pf-\u003eadapter-\u003eports.lock);\n+\tsrcu_read_unlock(\u0026ports-\u003esrcu, srcu_idx);\n }\n \n /**\n@@ -725,12 +770,75 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)\n \treturn 0;\n }\n \n+/**\n+ * ice_ptp_is_tracker_drained - Check for outstanding timestamps\n+ * @pf: Board private structure\n+ * @tx: Timestamp tracker structure\n+ *\n+ * Return: False if there are any timestamps still waiting for hardware;\n+ *         otherwise true, including when unable to read the ready bitmap.\n+ */\n+static bool\n+ice_ptp_is_tracker_drained(struct ice_pf *pf, struct ice_ptp_tx *tx)\n+{\n+\tstruct ice_hw *hw = \u0026pf-\u003ehw;\n+\tbool pending = false;\n+\tunsigned long flags;\n+\tu64 tstamp_ready;\n+\tu8 idx;\n+\n+\t/* If HW reset is ongoing, we can't access SBQ */\n+\tif (hw-\u003ereset_ongoing)\n+\t\treturn true;\n+\n+\tif (ice_get_phy_tx_tstamp_ready(hw, tx-\u003eblock, \u0026tstamp_ready))\n+\t\treturn true;\n+\n+\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\tfor_each_set_bit(idx, tx-\u003ein_use, tx-\u003elen) {\n+\t\tif (!(tstamp_ready \u0026 BIT_ULL(idx + tx-\u003eoffset))) {\n+\t\t\tpending = true;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n+\n+\treturn !pending;\n+}\n+\n+/**\n+ * ice_ptp_wait_for_tracker_drain - Wait for PHY to complete timestamps\n+ * @pf: Board private structure\n+ * @tx: Timestamp tracker structure\n+ *\n+ * Wait for up to 10 milliseconds for the PHY to complete any outstanding\n+ * timestamps before flushing.\n+ */\n+static void\n+ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)\n+{\n+\tbool drained;\n+\tint err;\n+\n+\tif (!tx-\u003ehas_ready_bitmap)\n+\t\treturn;\n+\n+\terr = read_poll_timeout(ice_ptp_is_tracker_drained,\n+\t\t\t\tdrained, drained, 500, 10 * USEC_PER_MSEC, false,\n+\t\t\t\tpf, tx);\n+\tif (err) {\n+\t\tdev_dbg(ice_pf_to_dev(pf), \"Timed out waiting for in-flight Tx timestamps on block %u\\n\",\n+\t\t\ttx-\u003eblock);\n+\t}\n+}\n+\n /**\n  * ice_ptp_flush_tx_tracker - Flush any remaining timestamps from the tracker\n  * @pf: Board private structure\n  * @tx: the tracker to flush\n  *\n- * Called during teardown when a Tx tracker is being removed.\n+ * Called to immediately discard any outstanding Tx timestamps, including when\n+ * tearing down the Tx tracker, and on E825 when restarting the PHY.\n  */\n static void\n ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)\n@@ -761,14 +869,20 @@ ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)\n \t\t\tice_clear_phy_tstamp(hw, tx-\u003eblock, phy_idx);\n \n \t\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\n+\t\t/* Count the number of Tx timestamps flushed */\n+\t\tpf-\u003eptp.tx_hwtstamp_flushed++;\n+\n+\t\t/* Also count timeouts if we flush a request that timed out */\n+\t\tif (time_is_before_jiffies(tx-\u003etstamps[idx].start + 2 * HZ))\n+\t\t\tpf-\u003eptp.tx_hwtstamp_timeouts++;\n+\n \t\tskb = tx-\u003etstamps[idx].skb;\n \t\ttx-\u003etstamps[idx].skb = NULL;\n \t\tclear_bit(idx, tx-\u003ein_use);\n \t\tclear_bit(idx, tx-\u003estale);\n-\t\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n \n-\t\t/* Count the number of Tx timestamps flushed */\n-\t\tpf-\u003eptp.tx_hwtstamp_flushed++;\n+\t\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n \n \t\t/* Free the SKB after we've cleared the bit */\n \t\tdev_kfree_skb_any(skb);\n@@ -792,7 +906,8 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)\n \tunsigned long flags;\n \n \tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n-\tbitmap_or(tx-\u003estale, tx-\u003estale, tx-\u003ein_use, tx-\u003elen);\n+\tif (tx-\u003einit)\n+\t\tbitmap_or(tx-\u003estale, tx-\u003estale, tx-\u003ein_use, tx-\u003elen);\n \tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n }\n \n@@ -806,10 +921,19 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)\n static void\n ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)\n {\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n \tstruct ice_ptp_port *port;\n+\tint srcu_idx;\n \n-\tlist_for_each_entry(port, \u0026pf-\u003eadapter-\u003eports.ports, list_node)\n+\tsrcu_idx = srcu_read_lock(\u0026ports-\u003esrcu);\n+\tlist_for_each_entry_srcu(port, \u0026ports-\u003elist, list_node,\n+\t\t\t\t srcu_read_lock_held(\u0026ports-\u003esrcu)) {\n+\t\tif (!kref_get_unless_zero(\u0026port-\u003eref))\n+\t\t\tcontinue;\n \t\tice_ptp_flush_tx_tracker(ptp_port_to_pf(port), \u0026port-\u003etx);\n+\t\tkref_put(\u0026port-\u003eref, ice_ptp_release_port_srcu);\n+\t}\n+\tsrcu_read_unlock(\u0026ports-\u003esrcu, srcu_idx);\n }\n \n /**\n@@ -831,6 +955,8 @@ ice_ptp_release_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)\n \t/* wait for potentially outstanding interrupt to complete */\n \tsynchronize_irq(pf-\u003eoicr_irq.virq);\n \n+\tice_ptp_wait_for_tracker_drain(pf, tx);\n+\n \tice_ptp_flush_tx_tracker(pf, tx);\n \n \tkfree(tx-\u003etstamps);\n@@ -1133,6 +1259,7 @@ static int ice_ptp_check_tx_fifo(struct ice_ptp_port *port)\n static void ice_ptp_wait_for_offsets(struct kthread_work *work)\n {\n \tstruct ice_ptp_port *port;\n+\tunsigned long flags;\n \tstruct ice_pf *pf;\n \tstruct ice_hw *hw;\n \tint tx_err;\n@@ -1153,9 +1280,26 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)\n \ttx_err = ice_ptp_check_tx_fifo(port);\n \tif (!tx_err)\n \t\ttx_err = ice_phy_cfg_tx_offset_e82x(hw, port-\u003eport_num);\n+\tif (!tx_err) {\n+\t\t/* Tx offset has been configured, re-enable Tx timestamps */\n+\t\tspin_lock_irqsave(\u0026port-\u003etx.lock, flags);\n+\t\tif (port-\u003etx.calibrating) {\n+\t\t\tport-\u003etx.calibrating = false;\n+\t\t\tdev_dbg(ice_pf_to_dev(pf), \"PTP Tx offset valid for port %u, Tx timestamps enabled\\n\",\n+\t\t\t\tport-\u003eport_num);\n+\t\t}\n+\t\tspin_unlock_irqrestore(\u0026port-\u003etx.lock, flags);\n+\t}\n+\n \trx_err = ice_phy_cfg_rx_offset_e82x(hw, port-\u003eport_num);\n \tif (tx_err || rx_err) {\n-\t\t/* Tx and/or Rx offset not yet configured, try again later */\n+\t\t/* Tx and/or Rx offset not yet configured, try again later.\n+\t\t * This is expected during normal link-up: the vernier offset\n+\t\t * calibration cannot complete until at least one packet has\n+\t\t * been transmitted, so the first retries routinely land here.\n+\t\t */\n+\t\tdev_dbg(ice_pf_to_dev(pf), \"PTP offset not yet valid for port %u (tx_err=%d rx_err=%d)\\n\",\n+\t\t\tport-\u003eport_num, tx_err, rx_err);\n \t\tkthread_queue_delayed_work(pf-\u003eptp.kworker,\n \t\t\t\t\t   \u0026port-\u003eov_work,\n \t\t\t\t\t   msecs_to_jiffies(100));\n@@ -1166,6 +1310,8 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)\n /**\n  * ice_ptp_port_phy_stop - Stop timestamping for a PHY port\n  * @ptp_port: PTP port to stop\n+ *\n+ * Context: must hold the adapter ps_lock.\n  */\n static int\n ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)\n@@ -1173,9 +1319,10 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)\n \tstruct ice_pf *pf = ptp_port_to_pf(ptp_port);\n \tu8 port = ptp_port-\u003eport_num;\n \tstruct ice_hw *hw = \u0026pf-\u003ehw;\n+\tunsigned long flags;\n \tint err;\n \n-\tmutex_lock(\u0026ptp_port-\u003eps_lock);\n+\tlockdep_assert_held(\u0026pf-\u003eadapter-\u003eps_lock);\n \n \tswitch (hw-\u003emac_type) {\n \tcase ICE_MAC_E810:\n@@ -1188,7 +1335,14 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)\n \t\terr = ice_stop_phy_timer_e82x(hw, port, true);\n \t\tbreak;\n \tcase ICE_MAC_GENERIC_3K_E825:\n-\t\terr = ice_stop_phy_timer_eth56g(hw, port, true);\n+\t\t/* Disable new Tx timestamp requests */\n+\t\tspin_lock_irqsave(\u0026ptp_port-\u003etx.lock, flags);\n+\t\tptp_port-\u003etx.calibrating = true;\n+\t\tspin_unlock_irqrestore(\u0026ptp_port-\u003etx.lock, flags);\n+\n+\t\tice_ptp_mark_tx_tracker_stale(\u0026ptp_port-\u003etx);\n+\n+\t\terr = ice_stop_phy_timer_eth56g(hw, port);\n \t\tbreak;\n \tdefault:\n \t\terr = -ENODEV;\n@@ -1197,8 +1351,6 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)\n \t\tdev_err(ice_pf_to_dev(pf), \"PTP failed to set PHY port %d down, err %d\\n\",\n \t\t\tport, err);\n \n-\tmutex_unlock(\u0026ptp_port-\u003eps_lock);\n-\n \treturn err;\n }\n \n@@ -1209,6 +1361,8 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)\n  * Start the PHY timestamping block, and initiate Vernier timestamping\n  * calibration. If timestamping cannot be calibrated (such as if link is down)\n  * then disable the timestamping block instead.\n+ *\n+ * Context: must hold the adapter ps_lock.\n  */\n static int\n ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)\n@@ -1219,10 +1373,10 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)\n \tunsigned long flags;\n \tint err;\n \n-\tif (!ptp_port-\u003elink_up)\n-\t\treturn ice_ptp_port_phy_stop(ptp_port);\n+\tlockdep_assert_held(\u0026pf-\u003eadapter-\u003eps_lock);\n \n-\tmutex_lock(\u0026ptp_port-\u003eps_lock);\n+\tif (!READ_ONCE(ptp_port-\u003elink_up))\n+\t\treturn ice_ptp_port_phy_stop(ptp_port);\n \n \tswitch (hw-\u003emac_type) {\n \tcase ICE_MAC_E810:\n@@ -1246,26 +1400,43 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)\n \t\tif (err)\n \t\t\tbreak;\n \n-\t\t/* Enable Tx timestamps right away */\n-\t\tspin_lock_irqsave(\u0026ptp_port-\u003etx.lock, flags);\n-\t\tptp_port-\u003etx.calibrating = false;\n-\t\tspin_unlock_irqrestore(\u0026ptp_port-\u003etx.lock, flags);\n-\n+\t\t/* Do not clear calibrating flag here. Tx timestamp requests\n+\t\t * remain disabled until ice_ptp_wait_for_offsets() has\n+\t\t * verified that the Tx offset calibration has completed.\n+\t\t */\n \t\tkthread_queue_delayed_work(pf-\u003eptp.kworker, \u0026ptp_port-\u003eov_work,\n \t\t\t\t\t   0);\n \t\tbreak;\n \tcase ICE_MAC_GENERIC_3K_E825:\n+\t\t/* ice_ptp_port_phy_stop() may have already disabled\n+\t\t * timestamps, but some restarts occur without first stopping\n+\t\t * the timer, so we ensure that new requests are disabled\n+\t\t * here.\n+\t\t */\n+\t\tspin_lock_irqsave(\u0026ptp_port-\u003etx.lock, flags);\n+\t\tptp_port-\u003etx.calibrating = true;\n+\t\tspin_unlock_irqrestore(\u0026ptp_port-\u003etx.lock, flags);\n+\n+\t\tice_ptp_flush_tx_tracker(pf, \u0026ptp_port-\u003etx);\n+\n \t\terr = ice_start_phy_timer_eth56g(hw, port);\n+\t\tif (err)\n+\t\t\tbreak;\n+\n+\t\tspin_lock_irqsave(\u0026ptp_port-\u003etx.lock, flags);\n+\t\tptp_port-\u003etx.calibrating = false;\n+\t\tspin_unlock_irqrestore(\u0026ptp_port-\u003etx.lock, flags);\n+\n \t\tbreak;\n \tdefault:\n-\t\terr = -ENODEV;\n+\t\tdev_dbg(ice_pf_to_dev(pf), \"PTP failed to restart PHY port %u with unknown MAC type %d\\n\",\n+\t\t\tport, hw-\u003emac_type);\n+\t\treturn -ENODEV;\n \t}\n \n \tif (err)\n-\t\tdev_err(ice_pf_to_dev(pf), \"PTP failed to set PHY port %d up, err %d\\n\",\n-\t\t\tport, err);\n-\n-\tmutex_unlock(\u0026ptp_port-\u003eps_lock);\n+\t\tdev_err(ice_pf_to_dev(pf), \"PTP failed to restart PHY port %u with err %pe; Timestamping remains disabled; A link-toggle may recover.\\n\",\n+\t\t\tport, ERR_PTR(err));\n \n \treturn err;\n }\n@@ -1280,17 +1451,17 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)\n \tstruct ice_ptp_port *ptp_port;\n \tstruct ice_hw *hw = \u0026pf-\u003ehw;\n \n-\tif (pf-\u003eptp.state != ICE_PTP_READY)\n-\t\treturn;\n-\n \tptp_port = \u0026pf-\u003eptp.port;\n \n-\t/* Update cached link status for this port immediately */\n-\tptp_port-\u003elink_up = linkup;\n+\tmutex_lock(\u0026pf-\u003eadapter-\u003eps_lock);\n \n-\t/* Skip HW writes if reset is in progress */\n-\tif (pf-\u003ehw.reset_ongoing)\n-\t\treturn;\n+\tWRITE_ONCE(ptp_port-\u003elink_up, linkup);\n+\n+\tif (!linkup)\n+\t\tice_ptp_mark_tx_tracker_stale(\u0026ptp_port-\u003etx);\n+\n+\tif (pf-\u003eptp.state != ICE_PTP_READY || pf-\u003ehw.reset_ongoing)\n+\t\tgoto out_unlock;\n \n \tif (hw-\u003emac_type == ICE_MAC_GENERIC_3K_E825 \u0026\u0026\n \t    test_bit(ICE_FLAG_DPLL, pf-\u003eflags)) {\n@@ -1333,17 +1504,20 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)\n \tcase ICE_MAC_E810:\n \tcase ICE_MAC_E830:\n \t\t/* Do not reconfigure E810 or E830 PHY */\n-\t\treturn;\n+\t\tbreak;\n \tcase ICE_MAC_GENERIC:\n \t\tice_ptp_port_phy_restart(ptp_port);\n-\t\treturn;\n+\t\tbreak;\n \tcase ICE_MAC_GENERIC_3K_E825:\n \t\tif (linkup)\n \t\t\tice_ptp_port_phy_restart(ptp_port);\n-\t\treturn;\n+\t\tbreak;\n \tdefault:\n \t\tdev_warn(ice_pf_to_dev(pf), \"%s: Unknown PHY type\\n\", __func__);\n \t}\n+\n+out_unlock:\n+\tmutex_unlock(\u0026pf-\u003eadapter-\u003eps_lock);\n }\n \n /**\n@@ -1409,31 +1583,34 @@ static int ice_ptp_cfg_phy_interrupt(struct ice_pf *pf, bool ena, u32 threshold)\n \t}\n }\n \n-/**\n- * ice_ptp_reset_phy_timestamping - Reset PHY timestamping block\n- * @pf: Board private structure\n- */\n-static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)\n-{\n-\tice_ptp_port_phy_restart(\u0026pf-\u003eptp.port);\n-}\n-\n /**\n  * ice_ptp_restart_all_phy - Restart all PHYs to recalibrate timestamping\n  * @pf: Board private structure\n+ *\n+ * Context: acquires the adapter ps_lock\n  */\n static void ice_ptp_restart_all_phy(struct ice_pf *pf)\n {\n-\tstruct list_head *entry;\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n+\tstruct ice_ptp_port *port;\n+\tint srcu_idx;\n \n-\tlist_for_each(entry, \u0026pf-\u003eadapter-\u003eports.ports) {\n-\t\tstruct ice_ptp_port *port = list_entry(entry,\n-\t\t\t\t\t\t       struct ice_ptp_port,\n-\t\t\t\t\t\t       list_node);\n+\tmutex_lock(\u0026pf-\u003eadapter-\u003eps_lock);\n \n-\t\tif (port-\u003elink_up)\n+\tsrcu_idx = srcu_read_lock(\u0026ports-\u003esrcu);\n+\tlist_for_each_entry_srcu(port, \u0026ports-\u003elist, list_node,\n+\t\t\t\t srcu_read_lock_held(\u0026ports-\u003esrcu)) {\n+\t\tif (!kref_get_unless_zero(\u0026port-\u003eref))\n+\t\t\tcontinue;\n+\n+\t\tif (READ_ONCE(port-\u003elink_up))\n \t\t\tice_ptp_port_phy_restart(port);\n+\n+\t\tkref_put(\u0026port-\u003eref, ice_ptp_release_port_srcu);\n \t}\n+\tsrcu_read_unlock(\u0026ports-\u003esrcu, srcu_idx);\n+\n+\tmutex_unlock(\u0026pf-\u003eadapter-\u003eps_lock);\n }\n \n /**\n@@ -2638,11 +2815,13 @@ s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb)\n \t\t * a reference to the skb and the start time to allow discarding old\n \t\t * requests.\n \t\t */\n-\t\tset_bit(idx, tx-\u003ein_use);\n-\t\tclear_bit(idx, tx-\u003estale);\n \t\ttx-\u003etstamps[idx].start = jiffies;\n \t\ttx-\u003etstamps[idx].skb = skb_get(skb);\n \t\tskb_shinfo(skb)-\u003etx_flags |= SKBTX_IN_PROGRESS;\n+\t\tclear_bit(idx, tx-\u003estale);\n+\t\t/* Ensure index is setup before marking it as used */\n+\t\tsmp_mb__before_atomic();\n+\t\tset_bit(idx, tx-\u003ein_use);\n \t\tice_trace(tx_tstamp_request, skb, idx);\n \t}\n \n@@ -2678,74 +2857,65 @@ void ice_ptp_process_ts(struct ice_pf *pf)\n \t}\n }\n \n-static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)\n+static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)\n {\n-\tbool more_timestamps;\n+\tDECLARE_BITMAP(tstamps, INDEX_PER_PORT_MAX) = {};\n \n \tscoped_guard(spinlock_irqsave, \u0026tx-\u003elock) {\n \t\tif (!tx-\u003einit)\n \t\t\treturn false;\n \n-\t\tmore_timestamps = !bitmap_empty(tx-\u003ein_use, tx-\u003elen);\n-\t}\n+\t\tif (in_irq) {\n+\t\t\tif (!ice_ptp_is_tx_tracker_up(tx))\n+\t\t\t\treturn false;\n \n-\treturn more_timestamps;\n+\t\t\treturn bitmap_andnot(tstamps, tx-\u003ein_use, tx-\u003estale, tx-\u003elen);\n+\t\t} else {\n+\t\t\treturn !bitmap_empty(tx-\u003ein_use, tx-\u003elen);\n+\t\t}\n+\t}\n }\n \n-static bool ice_any_port_has_timestamps(struct ice_pf *pf)\n+static bool ice_any_port_has_timestamps(struct ice_pf *pf, bool in_irq)\n {\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n+\tbool have_tstamps = false;\n \tstruct ice_ptp_port *port;\n+\tint srcu_idx;\n+\n+\tsrcu_idx = srcu_read_lock(\u0026ports-\u003esrcu);\n+\tlist_for_each_entry_srcu(port, \u0026ports-\u003elist, list_node,\n+\t\t\t\t srcu_read_lock_held(\u0026ports-\u003esrcu)) {\n+\t\tif (!kref_get_unless_zero(\u0026port-\u003eref))\n+\t\t\tcontinue;\n \n-\tscoped_guard(mutex, \u0026pf-\u003eadapter-\u003eports.lock) {\n-\t\tlist_for_each_entry(port, \u0026pf-\u003eadapter-\u003eports.ports,\n-\t\t\t\t    list_node) {\n-\t\t\tstruct ice_ptp_tx *tx = \u0026port-\u003etx;\n+\t\tif (ice_port_has_timestamps(\u0026port-\u003etx, in_irq))\n+\t\t\thave_tstamps = true;\n \n-\t\t\tif (ice_port_has_timestamps(tx))\n-\t\t\t\treturn true;\n-\t\t}\n+\t\tkref_put(\u0026port-\u003eref, ice_ptp_release_port_srcu);\n+\n+\t\tif (have_tstamps)\n+\t\t\tbreak;\n \t}\n+\tsrcu_read_unlock(\u0026ports-\u003esrcu, srcu_idx);\n \n-\treturn false;\n+\treturn have_tstamps;\n }\n \n-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)\n+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)\n {\n-\tstruct ice_hw *hw = \u0026pf-\u003ehw;\n-\tint ret;\n-\n-\t/* Check software indicator */\n \tswitch (pf-\u003eptp.tx_interrupt_mode) {\n \tcase ICE_PTP_TX_INTERRUPT_NONE:\n \t\treturn false;\n \tcase ICE_PTP_TX_INTERRUPT_SELF:\n-\t\tif (ice_port_has_timestamps(\u0026pf-\u003eptp.port.tx))\n-\t\t\treturn true;\n-\t\tbreak;\n+\t\treturn ice_port_has_timestamps(\u0026pf-\u003eptp.port.tx, in_irq);\n \tcase ICE_PTP_TX_INTERRUPT_ALL:\n-\t\tif (ice_any_port_has_timestamps(pf))\n-\t\t\treturn true;\n-\t\tbreak;\n+\t\treturn ice_any_port_has_timestamps(pf, in_irq);\n \tdefault:\n \t\tWARN_ONCE(1, \"Unexpected Tx timestamp interrupt mode %u\\n\",\n \t\t\t  pf-\u003eptp.tx_interrupt_mode);\n-\t\tbreak;\n-\t}\n-\n-\t/* Check hardware indicator */\n-\tret = ice_check_phy_tx_tstamp_ready(hw);\n-\tif (ret \u003c 0) {\n-\t\tdev_dbg(ice_pf_to_dev(pf), \"Unable to read PHY Tx timestamp ready bitmap, err %d\\n\",\n-\t\t\tret);\n-\t\t/* Stop triggering IRQs if we're unable to read PHY */\n \t\treturn false;\n \t}\n-\n-\t/* ice_check_phy_tx_tstamp_ready() returns 1 if there are timestamps\n-\t * available, 0 if there are no waiting timestamps, and a negative\n-\t * value if there was an error (which we checked for above).\n-\t */\n-\treturn ret \u003e 0;\n }\n \n /**\n@@ -2799,7 +2969,7 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)\n \t\t/* E830 can read timestamps in the top half using rd32() */\n \t\tice_ptp_process_ts(pf);\n \n-\t\tif (ice_ptp_tx_tstamps_pending(pf)) {\n+\t\tif (ice_ptp_tx_tstamps_pending(pf, true)) {\n \t\t\t/* Process outstanding Tx timestamps. If there\n \t\t\t * is more work, re-arm the interrupt to trigger again.\n \t\t\t */\n@@ -2821,9 +2991,9 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)\n  * current timestamp data is read. In rare circumstances, it is possible that\n  * the driver fails to read all outstanding data.\n  *\n- * To avoid getting permanently stuck, periodically check if the PHY has\n- * outstanding timestamp data. If so, trigger an interrupt from software to\n- * process this data.\n+ * To avoid getting permanently stuck, periodically check if there is any\n+ * outstanding timestamps or if the PHY has marked any index as ready. If so,\n+ * trigger an interrupt from software to process this data.\n  */\n static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)\n {\n@@ -2831,17 +3001,23 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)\n \tstruct ice_hw *hw = \u0026pf-\u003ehw;\n \tint ret;\n \n-\tif (!pf-\u003eptp.port.tx.has_ready_bitmap)\n+\t/* Avoid re-triggering OICR on E810 with low latency interrupt path */\n+\tif (hw-\u003edev_caps.ts_dev_info.ts_ll_int_read)\n \t\treturn;\n \n-\tif (!ice_pf_src_tmr_owned(pf))\n+\tif (pf-\u003eptp.tx_interrupt_mode != ICE_PTP_TX_INTERRUPT_SELF \u0026\u0026\n+\t    !ice_pf_src_tmr_owned(pf))\n \t\treturn;\n \n \tret = ice_check_phy_tx_tstamp_ready(hw);\n \tif (ret \u003c 0) {\n-\t\tdev_dbg(dev, \"PTP periodic task unable to read PHY timestamp ready bitmap, err %d\\n\",\n-\t\t\tret);\n-\t} else if (ret) {\n+\t\tdev_dbg(dev, \"Unable to read PHY Tx timestamp ready bitmap, err %pe\\n\",\n+\t\t\tERR_PTR(ret));\n+\t\t/* Don't trigger an IRQ if we are unable to access the PHY */\n+\t\treturn;\n+\t}\n+\n+\tif (ret \u003e 0 || ice_ptp_tx_tstamps_pending(pf, false)) {\n \t\tdev_dbg(dev, \"PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\\n\");\n \n \t\twr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);\n@@ -2890,14 +3066,18 @@ void ice_ptp_queue_work(struct ice_pf *pf)\n static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,\n \t\t\t\t\tenum ice_reset_req reset_type)\n {\n-\tstruct list_head *entry;\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n+\tstruct ice_ptp_port *port;\n+\tint srcu_idx;\n \n-\tlist_for_each(entry, \u0026pf-\u003eadapter-\u003eports.ports) {\n-\t\tstruct ice_ptp_port *port = list_entry(entry,\n-\t\t\t\t\t\t       struct ice_ptp_port,\n-\t\t\t\t\t\t       list_node);\n+\tsrcu_idx = srcu_read_lock(\u0026ports-\u003esrcu);\n+\tlist_for_each_entry_srcu(port, \u0026ports-\u003elist, list_node,\n+\t\t\t\t srcu_read_lock_held(\u0026ports-\u003esrcu)) {\n \t\tstruct ice_pf *peer_pf = ptp_port_to_pf(port);\n \n+\t\tif (!kref_get_unless_zero(\u0026port-\u003eref))\n+\t\t\tcontinue;\n+\n \t\tif (!ice_is_primary(\u0026peer_pf-\u003ehw)) {\n \t\t\tif (rebuild) {\n \t\t\t\t/* TODO: When implementing rebuild=true:\n@@ -2909,7 +3089,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,\n \t\t\t\tice_ptp_prepare_for_reset(peer_pf, reset_type);\n \t\t\t}\n \t\t}\n+\n+\t\tkref_put(\u0026port-\u003eref, ice_ptp_release_port_srcu);\n \t}\n+\tsrcu_read_unlock(\u0026ports-\u003esrcu, srcu_idx);\n }\n \n /**\n@@ -2933,14 +3116,15 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)\n \n \tkthread_cancel_delayed_work_sync(\u0026ptp-\u003ework);\n \n+\tif (hw-\u003emac_type == ICE_MAC_GENERIC)\n+\t\tkthread_cancel_delayed_work_sync(\u0026ptp-\u003eport.ov_work);\n+\n \tif (reset_type == ICE_RESET_PFR)\n \t\treturn;\n \n \tif (ice_pf_src_tmr_owned(pf) \u0026\u0026 hw-\u003emac_type == ICE_MAC_GENERIC_3K_E825)\n \t\tice_ptp_prepare_rebuild_sec(pf, false, reset_type);\n \n-\tice_ptp_release_tx_tracker(pf, \u0026pf-\u003eptp.port.tx);\n-\n \t/* Disable periodic outputs */\n \tice_ptp_disable_all_perout(pf);\n \n@@ -3086,11 +3270,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)\n \t\treturn -ENODEV;\n \n \tINIT_LIST_HEAD(\u0026ptp-\u003eport.list_node);\n-\tmutex_lock(\u0026pf-\u003eadapter-\u003eports.lock);\n+\tkref_init(\u0026ptp-\u003eport.ref);\n \n-\tlist_add(\u0026ptp-\u003eport.list_node,\n-\t\t \u0026pf-\u003eadapter-\u003eports.ports);\n-\tmutex_unlock(\u0026pf-\u003eadapter-\u003eports.lock);\n+\tspin_lock(\u0026pf-\u003eadapter-\u003eports.lock);\n+\tlist_add_rcu(\u0026ptp-\u003eport.list_node, \u0026pf-\u003eadapter-\u003eports.list);\n+\tspin_unlock(\u0026pf-\u003eadapter-\u003eports.lock);\n \n \t/* Seed the per-PHY Tx reference clock usage map for this port.\n \t * Only meaningful on E825 (other MAC types don't expose tx-clk\n@@ -3112,13 +3296,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)\n \n static void ice_ptp_cleanup_pf(struct ice_pf *pf)\n {\n+\tstruct ice_port_list *ports = \u0026pf-\u003eadapter-\u003eports;\n \tstruct ice_ptp *ptp = \u0026pf-\u003eptp;\n+\tstruct kref *ref;\n \n-\tif (pf-\u003ehw.mac_type != ICE_MAC_UNKNOWN) {\n-\t\tmutex_lock(\u0026pf-\u003eadapter-\u003eports.lock);\n-\t\tlist_del(\u0026ptp-\u003eport.list_node);\n-\t\tmutex_unlock(\u0026pf-\u003eadapter-\u003eports.lock);\n-\t}\n+\tif (pf-\u003ehw.mac_type == ICE_MAC_UNKNOWN)\n+\t\treturn;\n+\n+\tspin_lock(\u0026ports-\u003elock);\n+\tlist_del_rcu(\u0026ptp-\u003eport.list_node);\n+\tspin_unlock(\u0026ports-\u003elock);\n+\n+\tref = \u0026ptp-\u003eport.ref;\n+\tkref_put(ref, ice_ptp_release_port_srcu);\n+\n+\twait_var_event(ref, !kref_read(ref));\n+\n+\tsynchronize_srcu(\u0026ports-\u003esrcu);\n }\n \n /**\n@@ -3210,9 +3404,13 @@ static int ice_ptp_init_owner(struct ice_pf *pf)\n }\n \n /**\n- * ice_ptp_init_work - Initialize PTP work threads\n+ * ice_ptp_init_work - Initialize the PTP kworker\n  * @pf: Board private structure\n  * @ptp: PF PTP structure\n+ *\n+ * Allocate the kworker and initialize the periodic work function. The\n+ * periodic work is not queued here; Instead, it will be scheduled once the\n+ * PTP state is set to ICE_PTP_READY.\n  */\n static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)\n {\n@@ -3231,9 +3429,6 @@ static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)\n \n \tptp-\u003ekworker = kworker;\n \n-\t/* Start periodic work going */\n-\tkthread_queue_delayed_work(ptp-\u003ekworker, \u0026ptp-\u003ework, 0);\n-\n \treturn 0;\n }\n \n@@ -3248,8 +3443,6 @@ static int ice_ptp_init_port(struct ice_pf *pf, struct ice_ptp_port *ptp_port)\n {\n \tstruct ice_hw *hw = \u0026pf-\u003ehw;\n \n-\tmutex_init(\u0026ptp_port-\u003eps_lock);\n-\n \tswitch (hw-\u003emac_type) {\n \tcase ICE_MAC_E810:\n \tcase ICE_MAC_E830:\n@@ -3347,32 +3540,40 @@ void ice_ptp_init(struct ice_pf *pf)\n \t\t}\n \t}\n \n-\terr = ice_ptp_setup_pf(pf);\n+\terr = ice_ptp_init_work(pf, ptp);\n \tif (err)\n \t\tgoto err_exit;\n \n \terr = ice_ptp_init_port(pf, \u0026ptp-\u003eport);\n \tif (err)\n-\t\tgoto err_clean_pf;\n+\t\tgoto err_release_kworker;\n \n-\t/* Start the PHY timestamping block */\n-\tice_ptp_reset_phy_timestamping(pf);\n+\terr = ice_ptp_setup_pf(pf);\n+\tif (err)\n+\t\tgoto err_release_tx_tracker;\n+\n+\tptp-\u003estate = ICE_PTP_READY;\n+\n+\t/* (Re)start the PHY timestamping block */\n+\tmutex_lock(\u0026pf-\u003eadapter-\u003eps_lock);\n+\tice_ptp_port_phy_restart(\u0026ptp-\u003eport);\n+\tmutex_unlock(\u0026pf-\u003eadapter-\u003eps_lock);\n \n \t/* Configure initial Tx interrupt settings */\n \tice_ptp_cfg_tx_interrupt(pf);\n \n-\tptp-\u003estate = ICE_PTP_READY;\n-\n-\terr = ice_ptp_init_work(pf, ptp);\n-\tif (err)\n-\t\tgoto err_exit;\n+\t/* Start periodic work now that the state is READY */\n+\tkthread_queue_delayed_work(ptp-\u003ekworker, \u0026ptp-\u003ework, 0);\n \n \tdev_info(ice_pf_to_dev(pf), \"PTP init successful\\n\");\n \treturn;\n \n-err_clean_pf:\n-\tmutex_destroy(\u0026ptp-\u003eport.ps_lock);\n-\tice_ptp_cleanup_pf(pf);\n+err_release_tx_tracker:\n+\tice_ptp_release_tx_tracker(pf, \u0026pf-\u003eptp.port.tx);\n+err_release_kworker:\n+\tkthread_cancel_delayed_work_sync(\u0026ptp-\u003ework);\n+\tkthread_destroy_worker(ptp-\u003ekworker);\n+\tptp-\u003ekworker = NULL;\n err_exit:\n \t/* If we registered a PTP clock, release it */\n \tif (pf-\u003eptp.clock) {\n@@ -3399,12 +3600,21 @@ void ice_ptp_release(struct ice_pf *pf)\n \t\treturn;\n \n \tif (pf-\u003eptp.state != ICE_PTP_READY) {\n-\t\tmutex_destroy(\u0026pf-\u003eptp.port.ps_lock);\n-\t\tice_ptp_cleanup_pf(pf);\n \t\tif (pf-\u003eptp.clock) {\n \t\t\tptp_clock_unregister(pf-\u003eptp.clock);\n \t\t\tpf-\u003eptp.clock = NULL;\n \t\t}\n+\t\tice_ptp_cleanup_pf(pf);\n+\t\tice_ptp_release_tx_tracker(pf, \u0026pf-\u003eptp.port.tx);\n+\t\tif (pf-\u003eptp.kworker) {\n+\t\t\tkthread_cancel_delayed_work_sync(\u0026pf-\u003eptp.work);\n+\t\t\tif (pf-\u003ehw.mac_type == ICE_MAC_GENERIC)\n+\t\t\t\tkthread_cancel_delayed_work_sync(\u0026pf-\u003eptp.port.ov_work);\n+\t\t\tkthread_destroy_worker(pf-\u003eptp.kworker);\n+\t\t\tpf-\u003eptp.kworker = NULL;\n+\t\t}\n+\n+\t\tpf-\u003eptp.state = ICE_PTP_UNINIT;\n \t\treturn;\n \t}\n \n@@ -3421,8 +3631,10 @@ void ice_ptp_release(struct ice_pf *pf)\n \n \tkthread_cancel_delayed_work_sync(\u0026pf-\u003eptp.work);\n \n+\tmutex_lock(\u0026pf-\u003eadapter-\u003eps_lock);\n \tice_ptp_port_phy_stop(\u0026pf-\u003eptp.port);\n-\tmutex_destroy(\u0026pf-\u003eptp.port.ps_lock);\n+\tmutex_unlock(\u0026pf-\u003eadapter-\u003eps_lock);\n+\n \tif (pf-\u003eptp.kworker) {\n \t\tkthread_destroy_worker(pf-\u003eptp.kworker);\n \t\tpf-\u003eptp.kworker = NULL;\ndiff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h\nindex c4b0da7ce20ed..7810fefc546e3 100644\n--- a/drivers/net/ethernet/intel/ice/ice_ptp.h\n+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h\n@@ -5,6 +5,8 @@\n #define _ICE_PTP_H_\n \n #include \u003clinux/ptp_clock_kernel.h\u003e\n+#include \u003clinux/rculist.h\u003e\n+#include \u003clinux/kref.h\u003e\n #include \u003clinux/kthread.h\u003e\n \n #include \"ice_ptp_hw.h\"\n@@ -105,7 +107,7 @@ enum ice_tx_tstamp_work {\n  * @len: length of the tstamps and in_use fields.\n  * @init: if true, the tracker is initialized;\n  * @calibrating: if true, the PHY is calibrating the Tx offset. During this\n- *               window, timestamps are temporarily disabled.\n+ *               window, timestamp requests are disabled.\n  * @has_ready_bitmap: if true, the hardware has a valid Tx timestamp ready\n  *                    bitmap register. If false, fall back to verifying new\n  *                    timestamp values against previously cached copy.\n@@ -130,6 +132,9 @@ struct ice_ptp_tx {\n #define INDEX_PER_PORT_E82X\t\t16\n #define INDEX_PER_PORT\t\t\t64\n \n+/* Maximum number of timestamp indexes across all devices */\n+#define INDEX_PER_PORT_MAX              INDEX_PER_PORT\n+\n /**\n  * struct ice_ptp_port - data used to initialize an external port for PTP\n  *\n@@ -138,9 +143,9 @@ struct ice_ptp_tx {\n  * and determine when the port's PHY offset is valid.\n  *\n  * @list_node: list member structure\n+ * @ref: reference counter for use with adapter ports list\n  * @tx: Tx timestamp tracking for this port\n  * @ov_work: delayed work task for tracking when PHY offset is valid\n- * @ps_lock: mutex used to protect the overall PTP PHY start procedure\n  * @link_up: indicates whether the link is up\n  * @tx_fifo_busy_cnt: number of times the Tx FIFO was busy\n  * @port_num: the port number this structure represents\n@@ -149,9 +154,9 @@ struct ice_ptp_tx {\n  */\n struct ice_ptp_port {\n \tstruct list_head list_node;\n+\tstruct kref ref;\n \tstruct ice_ptp_tx tx;\n \tstruct kthread_delayed_work ov_work;\n-\tstruct mutex ps_lock; /* protects overall PTP PHY start procedure */\n \tbool link_up;\n \tu8 tx_fifo_busy_cnt;\n \tu8 port_num;\n@@ -312,7 +317,7 @@ void ice_ptp_req_tx_single_tstamp(struct ice_ptp_tx *tx, u8 idx);\n void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx);\n void ice_ptp_process_ts(struct ice_pf *pf);\n irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf);\n-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf);\n+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq);\n u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,\n \t\t\t     struct ptp_system_timestamp *sts);\n \n@@ -360,7 +365,8 @@ static inline irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)\n \treturn IRQ_HANDLED;\n }\n \n-static inline bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)\n+static inline bool\n+ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)\n {\n \treturn false;\n }\ndiff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c\nindex 3a41c711e751b..bd14aa3a77f62 100644\n--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c\n+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c\n@@ -737,24 +737,6 @@ static int ice_read_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,\n \treturn ice_read_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);\n }\n \n-/**\n- * ice_write_port_mem_eth56g - Write a PHY port memory location\n- * @hw: pointer to the HW struct\n- * @port: Port number to be read\n- * @offset: Offset from PHY port register base\n- * @val: Pointer to the value to read (out param)\n- *\n- * Return:\n- * * %0      - success\n- * * %EINVAL - invalid port number or resource type\n- * * %other  - failed to write to PHY\n- */\n-static int ice_write_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,\n-\t\t\t\t     u32 val)\n-{\n-\treturn ice_write_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);\n-}\n-\n /**\n  * ice_write_quad_ptp_reg_eth56g - Write a PHY quad register\n  * @hw: pointer to the HW struct\n@@ -1129,47 +1111,35 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,\n }\n \n /**\n- * ice_clear_ptp_tstamp_eth56g - Clear a timestamp from the quad block\n+ * ice_clear_ptp_tstamp_eth56g - Clear a timestamp from PHY memory\n  * @hw: pointer to the HW struct\n- * @port: the quad to read from\n+ * @port: the port to read from\n  * @idx: the timestamp index to reset\n  *\n- * Read and then forcibly clear the timestamp index to ensure the valid bit is\n- * cleared and the timestamp status bit is reset in the PHY port memory of\n- * internal PHYs of the 56G devices.\n+ * Read the timestamp index to ensure that the valid bit is cleared and the\n+ * timestamp status bit is reset in the PHY port memory.\n  *\n- * To directly clear the contents of the timestamp block entirely, discarding\n- * all timestamp data at once, software should instead use\n- * ice_ptp_reset_ts_memory_quad_eth56g().\n- *\n- * This function should only be called on an idx whose bit is set according to\n- * ice_get_phy_tx_tstamp_ready().\n+ * This function should only be called on an index whose bit is set according\n+ * to ice_get_phy_tx_tstamp_ready(), or as part of a full sweep when paired\n+ * with a PHY soft reset via ice_ptp_phy_soft_reset_eth56g().\n  *\n  * Return:\n  * * %0     - success\n- * * %other - failed to write to PHY\n+ * * %other - failed to read from PHY\n  */\n static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)\n {\n \tu64 unused_tstamp;\n-\tu16 lo_addr;\n \tint err;\n \n-\t/* Read the timestamp register to ensure the timestamp status bit is\n-\t * cleared.\n+\t/* Per the PHY spec, reading the timestamp memory location is what\n+\t * clears the entry's valid bit and its corresponding (read-only)\n+\t * ts_memory_status bit.\n \t */\n \terr = ice_read_ptp_tstamp_eth56g(hw, port, idx, \u0026unused_tstamp);\n \tif (err) {\n \t\tice_debug(hw, ICE_DBG_PTP, \"Failed to read the PHY timestamp register for port %u, idx %u, err %d\\n\",\n \t\t\t  port, idx, err);\n-\t}\n-\n-\tlo_addr = (u16)PHY_TSTAMP_L(idx);\n-\n-\terr = ice_write_port_mem_eth56g(hw, port, lo_addr, 0);\n-\tif (err) {\n-\t\tice_debug(hw, ICE_DBG_PTP, \"Failed to clear low PTP timestamp register for port %u, idx %u, err %d\\n\",\n-\t\t\t  port, idx, err);\n \t\treturn err;\n \t}\n \n@@ -1177,19 +1147,43 @@ static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)\n }\n \n /**\n- * ice_ptp_reset_ts_memory_eth56g - Clear all timestamps from the port block\n+ * ice_ptp_clear_tx_memory_status_eth56g - Reset one port's Tx timestamp memory\n  * @hw: pointer to the HW struct\n- */\n-static void ice_ptp_reset_ts_memory_eth56g(struct ice_hw *hw)\n-{\n-\tunsigned int port;\n-\n-\tfor (port = 0; port \u003c hw-\u003eptp.num_lports; port++) {\n-\t\tice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_L,\n-\t\t\t\t\t 0);\n-\t\tice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_U,\n-\t\t\t\t\t 0);\n+ * @port: port number to clear\n+ *\n+ * Fully reset a single PHY port's Tx timestamp memory. Per the PHY spec, the\n+ * only way to clear a timestamp valid bit (and its read-only ts_memory_status\n+ * bit) is to read the timestamp memory location, so read every entry for the\n+ * port (two 32-bit reads each). This discards all timestamp data on the port,\n+ * so it must only be used for a full reset; callers that must preserve\n+ * in-flight timestamps clear individual indices via ice_clear_phy_tstamp().\n+ *\n+ * Due to interactions with an internal HW counter for the number of\n+ * outstanding Tx timestamps, this *must* only be called as part of the\n+ * ice_ptp_phy_soft_reset_eth56g() procedure. Otherwise, the internal counter\n+ * may become out of sync and prevent new timestamp interrupts.\n+ *\n+ * Failure to read a given index (i.e. due to a transient sideband queue\n+ * failure) is not considered fatal as the PHY port is about to be soft reset.\n+ * While the soft reset does not clear the timestamp memory, software\n+ * shouldn't be reading the timestamp memory without already knowing it is\n+ * valid via ice_get_phy_tx_tstamp_ready(), and this sweep is just\n+ * a precaution to ensure the memory is in a known state.\n+ */\n+static void ice_ptp_clear_tx_memory_status_eth56g(struct ice_hw *hw, u8 port)\n+{\n+\tint err, failed = 0;\n+\tu8 idx;\n+\n+\tfor (idx = 0; idx \u003c INDEX_PER_PORT; idx++) {\n+\t\terr = ice_clear_ptp_tstamp_eth56g(hw, port, idx);\n+\t\tif (err)\n+\t\t\tfailed++;\n \t}\n+\n+\tif (failed)\n+\t\tdev_warn(ice_hw_to_dev(hw), \"Failed to clear %d PHY timestamp registers for port %u\\n\",\n+\t\t\t failed, port);\n }\n \n /**\n@@ -2098,32 +2092,33 @@ static int ice_sync_phy_timer_eth56g(struct ice_hw *hw, u8 port)\n }\n \n /**\n- * ice_stop_phy_timer_eth56g - Stop the PHY clock timer\n+ * ice_stop_phy_timer_eth56g - Clear PHY Rx offset ready flag\n  * @hw: pointer to the HW struct\n  * @port: the PHY port to stop\n- * @soft_reset: if true, hold the SOFT_RESET bit of PHY_REG_PS\n  *\n- * Stop the clock of a PHY port. This must be done as part of the flow to\n- * re-calibrate Tx and Rx timestamping offsets whenever the clock time is\n- * initialized or when link speed changes.\n+ * Disable Rx timestamping by clearing the PHY_REG_RX_OFFSET_READY. This\n+ * causes Rx timestamps to be captured with their valid bit clear, ensuring we\n+ * discard any timestamp captured while the PHY is being recalibrated.\n+ *\n+ * Note this does *not* clear PHY_REG_TX_OFFSET_READY. Clearing it would\n+ * cause the Tx timestamps to be captured with their valid bit clear.\n+ * Unfortunately the captured timestamps still increment the internal counter\n+ * and result in off-by-one accounting. Instead, Tx timestamp requests should\n+ * be disabled by other means.\n  *\n  * Return:\n  * * %0     - success\n  * * %other - failed to write to PHY\n  */\n-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)\n+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port)\n {\n \tint err;\n \n-\terr = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_OFFSET_READY, 0);\n-\tif (err)\n-\t\treturn err;\n-\n \terr = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);\n \tif (err)\n \t\treturn err;\n \n-\tice_debug(hw, ICE_DBG_PTP, \"Disabled clock on PHY port %u\\n\", port);\n+\tice_debug(hw, ICE_DBG_PTP, \"Disabled Rx timestamps on PHY port %u\\n\", port);\n \n \treturn 0;\n }\n@@ -2133,9 +2128,11 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)\n  * @hw: pointer to the HW struct\n  * @port: the PHY port to start\n  *\n- * Start the clock of a PHY port. This must be done as part of the flow to\n- * re-calibrate Tx and Rx timestamping offsets whenever the clock time is\n- * initialized or when link speed changes.\n+ * Perform a PHY soft reset and then start the clock for the PHY port.\n+ *\n+ * This must be done as part of the flow to re-calibrate Tx and Rx\n+ * timestamping offsets whenever the clock time is initialized or when link\n+ * speed changes.\n  *\n  * Return:\n  * * %0     - success\n@@ -2151,7 +2148,11 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)\n \n \ttmr_idx = ice_get_ptp_src_clock_index(hw);\n \n-\terr = ice_stop_phy_timer_eth56g(hw, port, false);\n+\terr = ice_stop_phy_timer_eth56g(hw, port);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = ice_ptp_phy_soft_reset_eth56g(hw, port);\n \tif (err)\n \t\treturn err;\n \n@@ -2294,6 +2295,7 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)\n  *\n  * Trigger a soft reset of the ETH56G PHY by toggling the soft reset\n  * bit in the PHY global register. The reset sequence consists of:\n+ *   0. Reading every timestamp memory register to clear its valid bit\n  *   1. Clearing the soft reset bit\n  *   2. Asserting the soft reset bit\n  *   3. Clearing the soft reset bit again\n@@ -2302,6 +2304,12 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)\n  * to settle. This provides a controlled way to reinitialize the PHY\n  * without requiring a full device reset.\n  *\n+ * To ensure that the internal counter matches the contents of the\n+ * PHY_TSTAMP memory region, read every timestamp index prior to performing\n+ * the soft reset. The read side effects ensure that the timestamp memory is\n+ * cleared while the soft reset ensures that the PHY_REG_TX_MEMORY_STATUS and\n+ * internal counter are reset to zero.\n+ *\n  * Return: 0 on success, or a negative error code on failure when\n  *         reading or writing the PHY register.\n  */\n@@ -2310,6 +2318,8 @@ int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)\n \tu32 global_val;\n \tint err;\n \n+\tice_ptp_clear_tx_memory_status_eth56g(hw, port);\n+\n \terr = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, \u0026global_val);\n \tif (err) {\n \t\tice_debug(hw, ICE_DBG_PTP, \"Failed to read PHY_REG_GLOBAL for port %d, err %d\\n\",\n@@ -4991,8 +5001,8 @@ ice_get_phy_tx_tstamp_ready_e810(struct ice_hw *hw, u8 port, u64 *tstamp_ready)\n  * The E810 devices do not have a Tx memory status register. Note this is\n  * intentionally different behavior from ice_get_phy_tx_tstamp_ready_e810\n  * which always says that all bits are ready. This function is called in cases\n- * where code will trigger interrupts if timestamps are waiting, and should\n- * not be called for E810 hardware.\n+ * where code will trigger interrupts if timestamps are waiting, and which\n+ * should not trigger for E810.\n  *\n  * Return: 0.\n  */\n@@ -5801,8 +5811,9 @@ void ice_ptp_reset_ts_memory(struct ice_hw *hw)\n \t\tice_ptp_reset_ts_memory_e82x(hw);\n \t\tbreak;\n \tcase ICE_MAC_GENERIC_3K_E825:\n-\t\tice_ptp_reset_ts_memory_eth56g(hw);\n-\t\tbreak;\n+\t\t/* E825 hardware must only reset timestamp memory as part of\n+\t\t * the soft reset procedure.\n+\t\t */\n \tcase ICE_MAC_E810:\n \tdefault:\n \t\treturn;\ndiff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h\nindex 16b1988e993d2..17000df77ce95 100644\n--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h\n+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h\n@@ -378,7 +378,7 @@ int ice_cgu_get_output_pin_state_caps(struct ice_hw *hw, u8 pin_id,\n \n /* ETH56G family functions */\n int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status);\n-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset);\n+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port);\n int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port);\n int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold);\n int ice_phy_cfg_ptp_1step_eth56g(struct ice_hw *hw, u8 port);\n"
}

2/1 2026/10/16 00:51 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the Intel E800 series Ethernet driver (drivers/net/ethernet/intel/ice), specifically adapter port locking and PTP timestamping synchronization for E810/E825 hardware. This driver requires physical Intel E800-series PCIe hardware that is not emulated in standard QEMU or GCE virtualized environments, making the modified code structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 813f7d55075a31916916cd59ffbcfde1f91c5295
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Oct 16 00:51:15 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c
index 2dc3629d6d0f6..572862fcd2474 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.c
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c
@@ -6,6 +6,7 @@
 #include <linux/pci.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/srcu.h>
 #include <linux/xarray.h>
 #include "ice_adapter.h"
 #include "ice.h"
@@ -54,11 +55,18 @@ static unsigned long ice_adapter_xa_index(struct pci_dev *pdev)
 static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
 {
 	struct ice_adapter *adapter;
+	int err;
 
 	adapter = kzalloc_obj(*adapter);
 	if (!adapter)
 		return NULL;
 
+	err = init_srcu_struct(&adapter->ports.srcu);
+	if (err) {
+		kfree(adapter);
+		return NULL;
+	}
+
 	adapter->index = ice_adapter_index(pdev);
 	spin_lock_init(&adapter->ptp_gltsyn_time_lock);
 	spin_lock_init(&adapter->txq_ctx_lock);
@@ -66,18 +74,22 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
 		mutex_init(&adapter->cpi_phy_lock[i]);
 	refcount_set(&adapter->refcount, 1);
 
-	mutex_init(&adapter->ports.lock);
-	INIT_LIST_HEAD(&adapter->ports.ports);
+	spin_lock_init(&adapter->ports.lock);
+	INIT_LIST_HEAD(&adapter->ports.list);
+
+	mutex_init(&adapter->ps_lock);
 
 	return adapter;
 }
 
 static void ice_adapter_free(struct ice_adapter *adapter)
 {
-	WARN_ON(!list_empty(&adapter->ports.ports));
+	WARN_ON(!list_empty(&adapter->ports.list));
 	for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++)
 		mutex_destroy(&adapter->cpi_phy_lock[i]);
-	mutex_destroy(&adapter->ports.lock);
+
+	cleanup_srcu_struct(&adapter->ports.srcu);
+	mutex_destroy(&adapter->ps_lock);
 
 	kfree(adapter);
 }
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 4f695f32da3d8..5f39166795b6f 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -17,15 +17,19 @@ struct ice_pf;
 /**
  * struct ice_port_list - data used to store the list of adapter ports
  *
- * This structure contains data used to maintain a list of adapter ports
+ * This structure contains data used to maintain a list of adapter ports.
+ * Writers modifying the list *must* acquire the lock, and use SRCU safe list
+ * operations. Readers should use srcu_read_lock() on the provided domain.
  *
- * @ports: list of ports
- * @lock: protect access to the ports list
+ * @list: list of ports
+ * @lock: protect write access to the list
+ * @srcu: Sleepable RCU domain for this adapter
  */
 struct ice_port_list {
-	struct list_head ports;
-	/* To synchronize the ports list operations */
-	struct mutex lock;
+	struct list_head list;
+	/* To synchronize write operations on the port list */
+	spinlock_t lock;
+	struct srcu_struct srcu;
 };
 
 /**
@@ -36,6 +40,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.
+ * @ps_lock: Mutex to serialize PHY port start/stop across adapter.
  * @ctrl_pf: Control PF of the adapter
  * @ports: Ports list
  * @index: 64-bit index cached for collision detection on 32bit systems
@@ -49,6 +54,9 @@ struct ice_adapter {
 	/* Serialize CPI REQ/ACK transactions per PHY (E825C only) */
 	struct mutex cpi_phy_lock[ICE_E825_MAX_PHYS];
 
+	/* For serializing PHY port start/stop sequences */
+	struct mutex ps_lock;
+
 	struct ice_pf *ctrl_pf;
 	struct ice_port_list ports;
 	u64 index;
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index d88835482d3aa..b48b9c4551667 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -3248,7 +3248,7 @@ static irqreturn_t ice_misc_intr_thread_fn(int __always_unused irq, void *data)
 	ice_irq_dynamic_ena(hw, NULL, NULL);
 	ice_flush(hw);
 
-	if (ice_ptp_tx_tstamps_pending(pf)) {
+	if (ice_ptp_tx_tstamps_pending(pf, true)) {
 		/* If any new Tx timestamps happened while in interrupt,
 		 * re-arm the interrupt to trigger it again.
 		 */
@@ -4810,6 +4810,14 @@ static void ice_init_link(struct ice_pf *pf)
 
 	ice_init_link_dflt_override(pf->hw.port_info);
 
+	/* Seed PTP link status from current PHY status, since link may
+	 * already be up (e.g. after PXE boot) without a link-change edge to
+	 * catch it later.
+	 */
+	WRITE_ONCE(pf->ptp.port.link_up,
+		   !!(pf->hw.port_info->phy.link_info.link_info &
+		      ICE_AQ_LINK_UP));
+
 	ice_check_link_cfg_err(pf,
 			       pf->hw.port_info->phy.link_info.link_cfg_err);
 
@@ -6745,7 +6753,6 @@ static int ice_up_complete(struct ice_vsi *vsi)
 		ice_print_link_msg(vsi, true);
 		netif_tx_start_all_queues(vsi->netdev);
 		netif_carrier_on(vsi->netdev);
-		ice_ptp_link_change(pf, true);
 	}
 
 	/* Perform an initial read of the statistics registers now to
@@ -7273,7 +7280,6 @@ int ice_down(struct ice_vsi *vsi)
 
 	if (vsi->netdev) {
 		vlan_err = ice_vsi_del_vlan_zero(vsi);
-		ice_ptp_link_change(vsi->back, false);
 		netif_carrier_off(vsi->netdev);
 		netif_tx_disable(vsi->netdev);
 	}
@@ -7794,6 +7800,14 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
 
 	ice_update_pf_netdev_link(pf);
 
+	if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags) && pf->hw.port_info) {
+		bool link_up;
+
+		link_up = !!(pf->hw.port_info->phy.link_info.link_info &
+			     ICE_AQ_LINK_UP);
+		ice_ptp_link_change(pf, link_up);
+	}
+
 	/* tell the firmware we are up */
 	err = ice_send_version(pf);
 	if (err) {
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index eaec36ab6ae3f..74e0c0138c501 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (C) 2021, Intel Corporation. */
 
+#include <linux/rculist.h>
+#include <linux/srcu.h>
+#include <linux/wait_bit.h>
 #include "ice.h"
 #include "ice_lib.h"
 #include "ice_trace.h"
@@ -362,9 +365,12 @@ static u64 ice_ptp_extend_40b_ts(struct ice_pf *pf, u64 in_tstamp)
 static bool
 ice_ptp_is_tx_tracker_up(struct ice_ptp_tx *tx)
 {
+	struct ice_ptp_port *ptp_port =
+		container_of(tx, struct ice_ptp_port, tx);
+
 	lockdep_assert_held(&tx->lock);
 
-	return tx->init && !tx->calibrating;
+	return tx->init && !tx->calibrating && READ_ONCE(ptp_port->link_up);
 }
 
 /**
@@ -551,7 +557,9 @@ void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx)
  * extremely unlikely that a packet will ever take this long to timestamp. If
  * we detect a Tx timestamp request that has waited for this long we assume
  * the packet will never be sent by hardware and discard it without reading
- * the timestamp register.
+ * the timestamp register. Note that in the unusual case where the PHY
+ * continuously fails to clear the ready bitmap index, the slot may remain
+ * locked for more than two seconds.
  */
 static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 {
@@ -561,7 +569,6 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	u64 tstamp_ready;
-	bool link_up;
 	int err;
 	u8 idx;
 
@@ -569,7 +576,7 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	pf = ptp_port_to_pf(ptp_port);
 	hw = &pf->hw;
 
-	if (!tx->init)
+	if (!tx->init || bitmap_empty(tx->in_use, tx->len))
 		return;
 
 	/* Read the Tx ready status first */
@@ -579,31 +586,27 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 			return;
 	}
 
-	/* Drop packets if the link went down */
-	link_up = ptp_port->link_up;
-
 	for_each_set_bit(idx, tx->in_use, tx->len) {
 		struct skb_shared_hwtstamps shhwtstamps = {};
+		bool drop_ts = false, timeout = false;
 		u8 phy_idx = idx + tx->offset;
 		u64 raw_tstamp = 0, tstamp;
-		bool drop_ts = !link_up;
 		struct sk_buff *skb;
 
+		/* Paired with smp_mb__before_atomic() in ice_ptp_request_ts()
+		 * to ensure ordering the in_use bitmap and the indexed fields
+		 * in the tstamps array.
+		 */
+		smp_rmb();
+
 		/* Drop packets which have waited for more than 2 seconds */
 		if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ)) {
 			drop_ts = true;
-
-			/* Count the number of Tx timestamps that timed out */
-			pf->ptp.tx_hwtstamp_timeouts++;
+			timeout = true;
 		}
 
-		/* Only read a timestamp from the PHY if its marked as ready
-		 * by the tstamp_ready register. This avoids unnecessary
-		 * reading of timestamps which are not yet valid. This is
-		 * important as we must read all timestamps which are valid
-		 * and only timestamps which are valid during each interrupt.
-		 * If we do not, the hardware logic for generating a new
-		 * interrupt can get stuck on some devices.
+		/* Only read a timestamp from the PHY if it is marked as ready
+		 * by the timestamp_ready register.
 		 */
 		if (tx->has_ready_bitmap &&
 		    !(tstamp_ready & BIT_ULL(phy_idx))) {
@@ -619,6 +622,27 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 		if (err && !drop_ts)
 			continue;
 
+		/* verify ready bit cleared */
+		if (tx->has_ready_bitmap) {
+			err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
+			if (err || tstamp_ready & BIT_ULL(phy_idx)) {
+				spin_lock_irqsave(&tx->lock, flags);
+				if (test_bit(idx, tx->in_use) &&
+				    !test_and_set_bit(idx, tx->stale))
+					dev_dbg(ice_pf_to_dev(pf), "PHY port %u failed to clear ready bit for idx %u\n",
+						ptp_port->port_num, phy_idx);
+				skb = tx->tstamps[idx].skb;
+				tx->tstamps[idx].skb = NULL;
+				spin_unlock_irqrestore(&tx->lock, flags);
+				/* Free the SKB immediately since we're never
+				 * going to report a timestamp for the stale
+				 * packet.
+				 */
+				dev_kfree_skb_any(skb);
+				continue;
+			}
+		}
+
 		ice_trace(tx_tstamp_fw_done, tx->tstamps[idx].skb, idx);
 
 		/* For PHYs which don't implement a proper timestamp ready
@@ -635,6 +659,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 			drop_ts = true;
 
 skip_ts_read:
+		if (timeout)
+			pf->ptp.tx_hwtstamp_timeouts++;
+
 		spin_lock_irqsave(&tx->lock, flags);
 		if (!tx->has_ready_bitmap && raw_tstamp)
 			tx->tstamps[idx].cached_tstamp = raw_tstamp;
@@ -645,8 +672,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 			drop_ts = true;
 		spin_unlock_irqrestore(&tx->lock, flags);
 
-		/* It is unlikely but possible that the SKB will have been
-		 * flushed at this point due to link change or teardown.
+		/* The SKB may have already been freed such as due to failure
+		 * to clear the ready bit, or being flushed due to link change
+		 * or teardown.
 		 */
 		if (!skb)
 			continue;
@@ -673,20 +701,37 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	pf->ptp.tx_hwtstamp_good += tstamp_good;
 }
 
+static void ice_ptp_release_port_srcu(struct kref *ref)
+{
+	/* wake_up_var() requires full ordering but kref_put only provides
+	 * release ordering.
+	 */
+	smp_mb__after_atomic();
+	wake_up_var(ref);
+}
+
 static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
 {
+	struct ice_port_list *ports = &pf->adapter->ports;
 	struct ice_ptp_port *port;
+	int srcu_idx;
 
-	mutex_lock(&pf->adapter->ports.lock);
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node) {
+	srcu_idx = srcu_read_lock(&ports->srcu);
+	list_for_each_entry_srcu(port, &ports->list, list_node,
+				 srcu_read_lock_held(&ports->srcu)) {
 		struct ice_ptp_tx *tx = &port->tx;
 
-		if (!tx || !tx->init)
+		if (!tx->init)
+			continue;
+
+		if (!kref_get_unless_zero(&port->ref))
 			continue;
 
 		ice_ptp_process_tx_tstamp(tx);
+
+		kref_put(&port->ref, ice_ptp_release_port_srcu);
 	}
-	mutex_unlock(&pf->adapter->ports.lock);
+	srcu_read_unlock(&ports->srcu, srcu_idx);
 }
 
 /**
@@ -725,12 +770,75 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)
 	return 0;
 }
 
+/**
+ * ice_ptp_is_tracker_drained - Check for outstanding timestamps
+ * @pf: Board private structure
+ * @tx: Timestamp tracker structure
+ *
+ * Return: False if there are any timestamps still waiting for hardware;
+ *         otherwise true, including when unable to read the ready bitmap.
+ */
+static bool
+ice_ptp_is_tracker_drained(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+	struct ice_hw *hw = &pf->hw;
+	bool pending = false;
+	unsigned long flags;
+	u64 tstamp_ready;
+	u8 idx;
+
+	/* If HW reset is ongoing, we can't access SBQ */
+	if (hw->reset_ongoing)
+		return true;
+
+	if (ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready))
+		return true;
+
+	spin_lock_irqsave(&tx->lock, flags);
+	for_each_set_bit(idx, tx->in_use, tx->len) {
+		if (!(tstamp_ready & BIT_ULL(idx + tx->offset))) {
+			pending = true;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&tx->lock, flags);
+
+	return !pending;
+}
+
+/**
+ * ice_ptp_wait_for_tracker_drain - Wait for PHY to complete timestamps
+ * @pf: Board private structure
+ * @tx: Timestamp tracker structure
+ *
+ * Wait for up to 10 milliseconds for the PHY to complete any outstanding
+ * timestamps before flushing.
+ */
+static void
+ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+	bool drained;
+	int err;
+
+	if (!tx->has_ready_bitmap)
+		return;
+
+	err = read_poll_timeout(ice_ptp_is_tracker_drained,
+				drained, drained, 500, 10 * USEC_PER_MSEC, false,
+				pf, tx);
+	if (err) {
+		dev_dbg(ice_pf_to_dev(pf), "Timed out waiting for in-flight Tx timestamps on block %u\n",
+			tx->block);
+	}
+}
+
 /**
  * ice_ptp_flush_tx_tracker - Flush any remaining timestamps from the tracker
  * @pf: Board private structure
  * @tx: the tracker to flush
  *
- * Called during teardown when a Tx tracker is being removed.
+ * Called to immediately discard any outstanding Tx timestamps, including when
+ * tearing down the Tx tracker, and on E825 when restarting the PHY.
  */
 static void
 ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
@@ -761,14 +869,20 @@ ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
 			ice_clear_phy_tstamp(hw, tx->block, phy_idx);
 
 		spin_lock_irqsave(&tx->lock, flags);
+
+		/* Count the number of Tx timestamps flushed */
+		pf->ptp.tx_hwtstamp_flushed++;
+
+		/* Also count timeouts if we flush a request that timed out */
+		if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ))
+			pf->ptp.tx_hwtstamp_timeouts++;
+
 		skb = tx->tstamps[idx].skb;
 		tx->tstamps[idx].skb = NULL;
 		clear_bit(idx, tx->in_use);
 		clear_bit(idx, tx->stale);
-		spin_unlock_irqrestore(&tx->lock, flags);
 
-		/* Count the number of Tx timestamps flushed */
-		pf->ptp.tx_hwtstamp_flushed++;
+		spin_unlock_irqrestore(&tx->lock, flags);
 
 		/* Free the SKB after we've cleared the bit */
 		dev_kfree_skb_any(skb);
@@ -792,7 +906,8 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
 	unsigned long flags;
 
 	spin_lock_irqsave(&tx->lock, flags);
-	bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len);
+	if (tx->init)
+		bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len);
 	spin_unlock_irqrestore(&tx->lock, flags);
 }
 
@@ -806,10 +921,19 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
 static void
 ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)
 {
+	struct ice_port_list *ports = &pf->adapter->ports;
 	struct ice_ptp_port *port;
+	int srcu_idx;
 
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
+	srcu_idx = srcu_read_lock(&ports->srcu);
+	list_for_each_entry_srcu(port, &ports->list, list_node,
+				 srcu_read_lock_held(&ports->srcu)) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
 		ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
+		kref_put(&port->ref, ice_ptp_release_port_srcu);
+	}
+	srcu_read_unlock(&ports->srcu, srcu_idx);
 }
 
 /**
@@ -831,6 +955,8 @@ ice_ptp_release_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
 	/* wait for potentially outstanding interrupt to complete */
 	synchronize_irq(pf->oicr_irq.virq);
 
+	ice_ptp_wait_for_tracker_drain(pf, tx);
+
 	ice_ptp_flush_tx_tracker(pf, tx);
 
 	kfree(tx->tstamps);
@@ -1133,6 +1259,7 @@ static int ice_ptp_check_tx_fifo(struct ice_ptp_port *port)
 static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 {
 	struct ice_ptp_port *port;
+	unsigned long flags;
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	int tx_err;
@@ -1153,9 +1280,26 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 	tx_err = ice_ptp_check_tx_fifo(port);
 	if (!tx_err)
 		tx_err = ice_phy_cfg_tx_offset_e82x(hw, port->port_num);
+	if (!tx_err) {
+		/* Tx offset has been configured, re-enable Tx timestamps */
+		spin_lock_irqsave(&port->tx.lock, flags);
+		if (port->tx.calibrating) {
+			port->tx.calibrating = false;
+			dev_dbg(ice_pf_to_dev(pf), "PTP Tx offset valid for port %u, Tx timestamps enabled\n",
+				port->port_num);
+		}
+		spin_unlock_irqrestore(&port->tx.lock, flags);
+	}
+
 	rx_err = ice_phy_cfg_rx_offset_e82x(hw, port->port_num);
 	if (tx_err || rx_err) {
-		/* Tx and/or Rx offset not yet configured, try again later */
+		/* Tx and/or Rx offset not yet configured, try again later.
+		 * This is expected during normal link-up: the vernier offset
+		 * calibration cannot complete until at least one packet has
+		 * been transmitted, so the first retries routinely land here.
+		 */
+		dev_dbg(ice_pf_to_dev(pf), "PTP offset not yet valid for port %u (tx_err=%d rx_err=%d)\n",
+			port->port_num, tx_err, rx_err);
 		kthread_queue_delayed_work(pf->ptp.kworker,
 					   &port->ov_work,
 					   msecs_to_jiffies(100));
@@ -1166,6 +1310,8 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 /**
  * ice_ptp_port_phy_stop - Stop timestamping for a PHY port
  * @ptp_port: PTP port to stop
+ *
+ * Context: must hold the adapter ps_lock.
  */
 static int
 ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
@@ -1173,9 +1319,10 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
 	struct ice_pf *pf = ptp_port_to_pf(ptp_port);
 	u8 port = ptp_port->port_num;
 	struct ice_hw *hw = &pf->hw;
+	unsigned long flags;
 	int err;
 
-	mutex_lock(&ptp_port->ps_lock);
+	lockdep_assert_held(&pf->adapter->ps_lock);
 
 	switch (hw->mac_type) {
 	case ICE_MAC_E810:
@@ -1188,7 +1335,14 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
 		err = ice_stop_phy_timer_e82x(hw, port, true);
 		break;
 	case ICE_MAC_GENERIC_3K_E825:
-		err = ice_stop_phy_timer_eth56g(hw, port, true);
+		/* Disable new Tx timestamp requests */
+		spin_lock_irqsave(&ptp_port->tx.lock, flags);
+		ptp_port->tx.calibrating = true;
+		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
+		ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
+		err = ice_stop_phy_timer_eth56g(hw, port);
 		break;
 	default:
 		err = -ENODEV;
@@ -1197,8 +1351,6 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
 		dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d down, err %d\n",
 			port, err);
 
-	mutex_unlock(&ptp_port->ps_lock);
-
 	return err;
 }
 
@@ -1209,6 +1361,8 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
  * Start the PHY timestamping block, and initiate Vernier timestamping
  * calibration. If timestamping cannot be calibrated (such as if link is down)
  * then disable the timestamping block instead.
+ *
+ * Context: must hold the adapter ps_lock.
  */
 static int
 ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
@@ -1219,10 +1373,10 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
 	unsigned long flags;
 	int err;
 
-	if (!ptp_port->link_up)
-		return ice_ptp_port_phy_stop(ptp_port);
+	lockdep_assert_held(&pf->adapter->ps_lock);
 
-	mutex_lock(&ptp_port->ps_lock);
+	if (!READ_ONCE(ptp_port->link_up))
+		return ice_ptp_port_phy_stop(ptp_port);
 
 	switch (hw->mac_type) {
 	case ICE_MAC_E810:
@@ -1246,26 +1400,43 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
 		if (err)
 			break;
 
-		/* Enable Tx timestamps right away */
-		spin_lock_irqsave(&ptp_port->tx.lock, flags);
-		ptp_port->tx.calibrating = false;
-		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
-
+		/* Do not clear calibrating flag here. Tx timestamp requests
+		 * remain disabled until ice_ptp_wait_for_offsets() has
+		 * verified that the Tx offset calibration has completed.
+		 */
 		kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
 					   0);
 		break;
 	case ICE_MAC_GENERIC_3K_E825:
+		/* ice_ptp_port_phy_stop() may have already disabled
+		 * timestamps, but some restarts occur without first stopping
+		 * the timer, so we ensure that new requests are disabled
+		 * here.
+		 */
+		spin_lock_irqsave(&ptp_port->tx.lock, flags);
+		ptp_port->tx.calibrating = true;
+		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
+		ice_ptp_flush_tx_tracker(pf, &ptp_port->tx);
+
 		err = ice_start_phy_timer_eth56g(hw, port);
+		if (err)
+			break;
+
+		spin_lock_irqsave(&ptp_port->tx.lock, flags);
+		ptp_port->tx.calibrating = false;
+		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
 		break;
 	default:
-		err = -ENODEV;
+		dev_dbg(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u with unknown MAC type %d\n",
+			port, hw->mac_type);
+		return -ENODEV;
 	}
 
 	if (err)
-		dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d up, err %d\n",
-			port, err);
-
-	mutex_unlock(&ptp_port->ps_lock);
+		dev_err(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u with err %pe; Timestamping remains disabled; A link-toggle may recover.\n",
+			port, ERR_PTR(err));
 
 	return err;
 }
@@ -1280,17 +1451,17 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	struct ice_ptp_port *ptp_port;
 	struct ice_hw *hw = &pf->hw;
 
-	if (pf->ptp.state != ICE_PTP_READY)
-		return;
-
 	ptp_port = &pf->ptp.port;
 
-	/* Update cached link status for this port immediately */
-	ptp_port->link_up = linkup;
+	mutex_lock(&pf->adapter->ps_lock);
 
-	/* Skip HW writes if reset is in progress */
-	if (pf->hw.reset_ongoing)
-		return;
+	WRITE_ONCE(ptp_port->link_up, linkup);
+
+	if (!linkup)
+		ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
+	if (pf->ptp.state != ICE_PTP_READY || pf->hw.reset_ongoing)
+		goto out_unlock;
 
 	if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 &&
 	    test_bit(ICE_FLAG_DPLL, pf->flags)) {
@@ -1333,17 +1504,20 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	case ICE_MAC_E810:
 	case ICE_MAC_E830:
 		/* Do not reconfigure E810 or E830 PHY */
-		return;
+		break;
 	case ICE_MAC_GENERIC:
 		ice_ptp_port_phy_restart(ptp_port);
-		return;
+		break;
 	case ICE_MAC_GENERIC_3K_E825:
 		if (linkup)
 			ice_ptp_port_phy_restart(ptp_port);
-		return;
+		break;
 	default:
 		dev_warn(ice_pf_to_dev(pf), "%s: Unknown PHY type\n", __func__);
 	}
+
+out_unlock:
+	mutex_unlock(&pf->adapter->ps_lock);
 }
 
 /**
@@ -1409,31 +1583,34 @@ static int ice_ptp_cfg_phy_interrupt(struct ice_pf *pf, bool ena, u32 threshold)
 	}
 }
 
-/**
- * ice_ptp_reset_phy_timestamping - Reset PHY timestamping block
- * @pf: Board private structure
- */
-static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)
-{
-	ice_ptp_port_phy_restart(&pf->ptp.port);
-}
-
 /**
  * ice_ptp_restart_all_phy - Restart all PHYs to recalibrate timestamping
  * @pf: Board private structure
+ *
+ * Context: acquires the adapter ps_lock
  */
 static void ice_ptp_restart_all_phy(struct ice_pf *pf)
 {
-	struct list_head *entry;
+	struct ice_port_list *ports = &pf->adapter->ports;
+	struct ice_ptp_port *port;
+	int srcu_idx;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	mutex_lock(&pf->adapter->ps_lock);
 
-		if (port->link_up)
+	srcu_idx = srcu_read_lock(&ports->srcu);
+	list_for_each_entry_srcu(port, &ports->list, list_node,
+				 srcu_read_lock_held(&ports->srcu)) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+
+		if (READ_ONCE(port->link_up))
 			ice_ptp_port_phy_restart(port);
+
+		kref_put(&port->ref, ice_ptp_release_port_srcu);
 	}
+	srcu_read_unlock(&ports->srcu, srcu_idx);
+
+	mutex_unlock(&pf->adapter->ps_lock);
 }
 
 /**
@@ -2638,11 +2815,13 @@ s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb)
 		 * a reference to the skb and the start time to allow discarding old
 		 * requests.
 		 */
-		set_bit(idx, tx->in_use);
-		clear_bit(idx, tx->stale);
 		tx->tstamps[idx].start = jiffies;
 		tx->tstamps[idx].skb = skb_get(skb);
 		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		clear_bit(idx, tx->stale);
+		/* Ensure index is setup before marking it as used */
+		smp_mb__before_atomic();
+		set_bit(idx, tx->in_use);
 		ice_trace(tx_tstamp_request, skb, idx);
 	}
 
@@ -2678,74 +2857,65 @@ void ice_ptp_process_ts(struct ice_pf *pf)
 	}
 }
 
-static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)
+static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)
 {
-	bool more_timestamps;
+	DECLARE_BITMAP(tstamps, INDEX_PER_PORT_MAX) = {};
 
 	scoped_guard(spinlock_irqsave, &tx->lock) {
 		if (!tx->init)
 			return false;
 
-		more_timestamps = !bitmap_empty(tx->in_use, tx->len);
-	}
+		if (in_irq) {
+			if (!ice_ptp_is_tx_tracker_up(tx))
+				return false;
 
-	return more_timestamps;
+			return bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len);
+		} else {
+			return !bitmap_empty(tx->in_use, tx->len);
+		}
+	}
 }
 
-static bool ice_any_port_has_timestamps(struct ice_pf *pf)
+static bool ice_any_port_has_timestamps(struct ice_pf *pf, bool in_irq)
 {
+	struct ice_port_list *ports = &pf->adapter->ports;
+	bool have_tstamps = false;
 	struct ice_ptp_port *port;
+	int srcu_idx;
+
+	srcu_idx = srcu_read_lock(&ports->srcu);
+	list_for_each_entry_srcu(port, &ports->list, list_node,
+				 srcu_read_lock_held(&ports->srcu)) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
 
-	scoped_guard(mutex, &pf->adapter->ports.lock) {
-		list_for_each_entry(port, &pf->adapter->ports.ports,
-				    list_node) {
-			struct ice_ptp_tx *tx = &port->tx;
+		if (ice_port_has_timestamps(&port->tx, in_irq))
+			have_tstamps = true;
 
-			if (ice_port_has_timestamps(tx))
-				return true;
-		}
+		kref_put(&port->ref, ice_ptp_release_port_srcu);
+
+		if (have_tstamps)
+			break;
 	}
+	srcu_read_unlock(&ports->srcu, srcu_idx);
 
-	return false;
+	return have_tstamps;
 }
 
-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
-	struct ice_hw *hw = &pf->hw;
-	int ret;
-
-	/* Check software indicator */
 	switch (pf->ptp.tx_interrupt_mode) {
 	case ICE_PTP_TX_INTERRUPT_NONE:
 		return false;
 	case ICE_PTP_TX_INTERRUPT_SELF:
-		if (ice_port_has_timestamps(&pf->ptp.port.tx))
-			return true;
-		break;
+		return ice_port_has_timestamps(&pf->ptp.port.tx, in_irq);
 	case ICE_PTP_TX_INTERRUPT_ALL:
-		if (ice_any_port_has_timestamps(pf))
-			return true;
-		break;
+		return ice_any_port_has_timestamps(pf, in_irq);
 	default:
 		WARN_ONCE(1, "Unexpected Tx timestamp interrupt mode %u\n",
 			  pf->ptp.tx_interrupt_mode);
-		break;
-	}
-
-	/* Check hardware indicator */
-	ret = ice_check_phy_tx_tstamp_ready(hw);
-	if (ret < 0) {
-		dev_dbg(ice_pf_to_dev(pf), "Unable to read PHY Tx timestamp ready bitmap, err %d\n",
-			ret);
-		/* Stop triggering IRQs if we're unable to read PHY */
 		return false;
 	}
-
-	/* ice_check_phy_tx_tstamp_ready() returns 1 if there are timestamps
-	 * available, 0 if there are no waiting timestamps, and a negative
-	 * value if there was an error (which we checked for above).
-	 */
-	return ret > 0;
 }
 
 /**
@@ -2799,7 +2969,7 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)
 		/* E830 can read timestamps in the top half using rd32() */
 		ice_ptp_process_ts(pf);
 
-		if (ice_ptp_tx_tstamps_pending(pf)) {
+		if (ice_ptp_tx_tstamps_pending(pf, true)) {
 			/* Process outstanding Tx timestamps. If there
 			 * is more work, re-arm the interrupt to trigger again.
 			 */
@@ -2821,9 +2991,9 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)
  * current timestamp data is read. In rare circumstances, it is possible that
  * the driver fails to read all outstanding data.
  *
- * To avoid getting permanently stuck, periodically check if the PHY has
- * outstanding timestamp data. If so, trigger an interrupt from software to
- * process this data.
+ * To avoid getting permanently stuck, periodically check if there is any
+ * outstanding timestamps or if the PHY has marked any index as ready. If so,
+ * trigger an interrupt from software to process this data.
  */
 static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 {
@@ -2831,17 +3001,23 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 	struct ice_hw *hw = &pf->hw;
 	int ret;
 
-	if (!pf->ptp.port.tx.has_ready_bitmap)
+	/* Avoid re-triggering OICR on E810 with low latency interrupt path */
+	if (hw->dev_caps.ts_dev_info.ts_ll_int_read)
 		return;
 
-	if (!ice_pf_src_tmr_owned(pf))
+	if (pf->ptp.tx_interrupt_mode != ICE_PTP_TX_INTERRUPT_SELF &&
+	    !ice_pf_src_tmr_owned(pf))
 		return;
 
 	ret = ice_check_phy_tx_tstamp_ready(hw);
 	if (ret < 0) {
-		dev_dbg(dev, "PTP periodic task unable to read PHY timestamp ready bitmap, err %d\n",
-			ret);
-	} else if (ret) {
+		dev_dbg(dev, "Unable to read PHY Tx timestamp ready bitmap, err %pe\n",
+			ERR_PTR(ret));
+		/* Don't trigger an IRQ if we are unable to access the PHY */
+		return;
+	}
+
+	if (ret > 0 || ice_ptp_tx_tstamps_pending(pf, false)) {
 		dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
 
 		wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
@@ -2890,14 +3066,18 @@ void ice_ptp_queue_work(struct ice_pf *pf)
 static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
 					enum ice_reset_req reset_type)
 {
-	struct list_head *entry;
+	struct ice_port_list *ports = &pf->adapter->ports;
+	struct ice_ptp_port *port;
+	int srcu_idx;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	srcu_idx = srcu_read_lock(&ports->srcu);
+	list_for_each_entry_srcu(port, &ports->list, list_node,
+				 srcu_read_lock_held(&ports->srcu)) {
 		struct ice_pf *peer_pf = ptp_port_to_pf(port);
 
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+
 		if (!ice_is_primary(&peer_pf->hw)) {
 			if (rebuild) {
 				/* TODO: When implementing rebuild=true:
@@ -2909,7 +3089,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
 				ice_ptp_prepare_for_reset(peer_pf, reset_type);
 			}
 		}
+
+		kref_put(&port->ref, ice_ptp_release_port_srcu);
 	}
+	srcu_read_unlock(&ports->srcu, srcu_idx);
 }
 
 /**
@@ -2933,14 +3116,15 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
 
 	kthread_cancel_delayed_work_sync(&ptp->work);
 
+	if (hw->mac_type == ICE_MAC_GENERIC)
+		kthread_cancel_delayed_work_sync(&ptp->port.ov_work);
+
 	if (reset_type == ICE_RESET_PFR)
 		return;
 
 	if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
 
-	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
-
 	/* Disable periodic outputs */
 	ice_ptp_disable_all_perout(pf);
 
@@ -3086,11 +3270,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 		return -ENODEV;
 
 	INIT_LIST_HEAD(&ptp->port.list_node);
-	mutex_lock(&pf->adapter->ports.lock);
+	kref_init(&ptp->port.ref);
 
-	list_add(&ptp->port.list_node,
-		 &pf->adapter->ports.ports);
-	mutex_unlock(&pf->adapter->ports.lock);
+	spin_lock(&pf->adapter->ports.lock);
+	list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list);
+	spin_unlock(&pf->adapter->ports.lock);
 
 	/* Seed the per-PHY Tx reference clock usage map for this port.
 	 * Only meaningful on E825 (other MAC types don't expose tx-clk
@@ -3112,13 +3296,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 
 static void ice_ptp_cleanup_pf(struct ice_pf *pf)
 {
+	struct ice_port_list *ports = &pf->adapter->ports;
 	struct ice_ptp *ptp = &pf->ptp;
+	struct kref *ref;
 
-	if (pf->hw.mac_type != ICE_MAC_UNKNOWN) {
-		mutex_lock(&pf->adapter->ports.lock);
-		list_del(&ptp->port.list_node);
-		mutex_unlock(&pf->adapter->ports.lock);
-	}
+	if (pf->hw.mac_type == ICE_MAC_UNKNOWN)
+		return;
+
+	spin_lock(&ports->lock);
+	list_del_rcu(&ptp->port.list_node);
+	spin_unlock(&ports->lock);
+
+	ref = &ptp->port.ref;
+	kref_put(ref, ice_ptp_release_port_srcu);
+
+	wait_var_event(ref, !kref_read(ref));
+
+	synchronize_srcu(&ports->srcu);
 }
 
 /**
@@ -3210,9 +3404,13 @@ static int ice_ptp_init_owner(struct ice_pf *pf)
 }
 
 /**
- * ice_ptp_init_work - Initialize PTP work threads
+ * ice_ptp_init_work - Initialize the PTP kworker
  * @pf: Board private structure
  * @ptp: PF PTP structure
+ *
+ * Allocate the kworker and initialize the periodic work function. The
+ * periodic work is not queued here; Instead, it will be scheduled once the
+ * PTP state is set to ICE_PTP_READY.
  */
 static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)
 {
@@ -3231,9 +3429,6 @@ static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)
 
 	ptp->kworker = kworker;
 
-	/* Start periodic work going */
-	kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0);
-
 	return 0;
 }
 
@@ -3248,8 +3443,6 @@ static int ice_ptp_init_port(struct ice_pf *pf, struct ice_ptp_port *ptp_port)
 {
 	struct ice_hw *hw = &pf->hw;
 
-	mutex_init(&ptp_port->ps_lock);
-
 	switch (hw->mac_type) {
 	case ICE_MAC_E810:
 	case ICE_MAC_E830:
@@ -3347,32 +3540,40 @@ void ice_ptp_init(struct ice_pf *pf)
 		}
 	}
 
-	err = ice_ptp_setup_pf(pf);
+	err = ice_ptp_init_work(pf, ptp);
 	if (err)
 		goto err_exit;
 
 	err = ice_ptp_init_port(pf, &ptp->port);
 	if (err)
-		goto err_clean_pf;
+		goto err_release_kworker;
 
-	/* Start the PHY timestamping block */
-	ice_ptp_reset_phy_timestamping(pf);
+	err = ice_ptp_setup_pf(pf);
+	if (err)
+		goto err_release_tx_tracker;
+
+	ptp->state = ICE_PTP_READY;
+
+	/* (Re)start the PHY timestamping block */
+	mutex_lock(&pf->adapter->ps_lock);
+	ice_ptp_port_phy_restart(&ptp->port);
+	mutex_unlock(&pf->adapter->ps_lock);
 
 	/* Configure initial Tx interrupt settings */
 	ice_ptp_cfg_tx_interrupt(pf);
 
-	ptp->state = ICE_PTP_READY;
-
-	err = ice_ptp_init_work(pf, ptp);
-	if (err)
-		goto err_exit;
+	/* Start periodic work now that the state is READY */
+	kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0);
 
 	dev_info(ice_pf_to_dev(pf), "PTP init successful\n");
 	return;
 
-err_clean_pf:
-	mutex_destroy(&ptp->port.ps_lock);
-	ice_ptp_cleanup_pf(pf);
+err_release_tx_tracker:
+	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
+err_release_kworker:
+	kthread_cancel_delayed_work_sync(&ptp->work);
+	kthread_destroy_worker(ptp->kworker);
+	ptp->kworker = NULL;
 err_exit:
 	/* If we registered a PTP clock, release it */
 	if (pf->ptp.clock) {
@@ -3399,12 +3600,21 @@ void ice_ptp_release(struct ice_pf *pf)
 		return;
 
 	if (pf->ptp.state != ICE_PTP_READY) {
-		mutex_destroy(&pf->ptp.port.ps_lock);
-		ice_ptp_cleanup_pf(pf);
 		if (pf->ptp.clock) {
 			ptp_clock_unregister(pf->ptp.clock);
 			pf->ptp.clock = NULL;
 		}
+		ice_ptp_cleanup_pf(pf);
+		ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
+		if (pf->ptp.kworker) {
+			kthread_cancel_delayed_work_sync(&pf->ptp.work);
+			if (pf->hw.mac_type == ICE_MAC_GENERIC)
+				kthread_cancel_delayed_work_sync(&pf->ptp.port.ov_work);
+			kthread_destroy_worker(pf->ptp.kworker);
+			pf->ptp.kworker = NULL;
+		}
+
+		pf->ptp.state = ICE_PTP_UNINIT;
 		return;
 	}
 
@@ -3421,8 +3631,10 @@ void ice_ptp_release(struct ice_pf *pf)
 
 	kthread_cancel_delayed_work_sync(&pf->ptp.work);
 
+	mutex_lock(&pf->adapter->ps_lock);
 	ice_ptp_port_phy_stop(&pf->ptp.port);
-	mutex_destroy(&pf->ptp.port.ps_lock);
+	mutex_unlock(&pf->adapter->ps_lock);
+
 	if (pf->ptp.kworker) {
 		kthread_destroy_worker(pf->ptp.kworker);
 		pf->ptp.kworker = NULL;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index c4b0da7ce20ed..7810fefc546e3 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -5,6 +5,8 @@
 #define _ICE_PTP_H_
 
 #include <linux/ptp_clock_kernel.h>
+#include <linux/rculist.h>
+#include <linux/kref.h>
 #include <linux/kthread.h>
 
 #include "ice_ptp_hw.h"
@@ -105,7 +107,7 @@ enum ice_tx_tstamp_work {
  * @len: length of the tstamps and in_use fields.
  * @init: if true, the tracker is initialized;
  * @calibrating: if true, the PHY is calibrating the Tx offset. During this
- *               window, timestamps are temporarily disabled.
+ *               window, timestamp requests are disabled.
  * @has_ready_bitmap: if true, the hardware has a valid Tx timestamp ready
  *                    bitmap register. If false, fall back to verifying new
  *                    timestamp values against previously cached copy.
@@ -130,6 +132,9 @@ struct ice_ptp_tx {
 #define INDEX_PER_PORT_E82X		16
 #define INDEX_PER_PORT			64
 
+/* Maximum number of timestamp indexes across all devices */
+#define INDEX_PER_PORT_MAX              INDEX_PER_PORT
+
 /**
  * struct ice_ptp_port - data used to initialize an external port for PTP
  *
@@ -138,9 +143,9 @@ struct ice_ptp_tx {
  * and determine when the port's PHY offset is valid.
  *
  * @list_node: list member structure
+ * @ref: reference counter for use with adapter ports list
  * @tx: Tx timestamp tracking for this port
  * @ov_work: delayed work task for tracking when PHY offset is valid
- * @ps_lock: mutex used to protect the overall PTP PHY start procedure
  * @link_up: indicates whether the link is up
  * @tx_fifo_busy_cnt: number of times the Tx FIFO was busy
  * @port_num: the port number this structure represents
@@ -149,9 +154,9 @@ struct ice_ptp_tx {
  */
 struct ice_ptp_port {
 	struct list_head list_node;
+	struct kref ref;
 	struct ice_ptp_tx tx;
 	struct kthread_delayed_work ov_work;
-	struct mutex ps_lock; /* protects overall PTP PHY start procedure */
 	bool link_up;
 	u8 tx_fifo_busy_cnt;
 	u8 port_num;
@@ -312,7 +317,7 @@ void ice_ptp_req_tx_single_tstamp(struct ice_ptp_tx *tx, u8 idx);
 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 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);
 
@@ -360,7 +365,8 @@ static inline irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)
 	return IRQ_HANDLED;
 }
 
-static inline bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
+static inline bool
+ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
 	return false;
 }
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 3a41c711e751b..bd14aa3a77f62 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -737,24 +737,6 @@ static int ice_read_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,
 	return ice_read_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);
 }
 
-/**
- * ice_write_port_mem_eth56g - Write a PHY port memory location
- * @hw: pointer to the HW struct
- * @port: Port number to be read
- * @offset: Offset from PHY port register base
- * @val: Pointer to the value to read (out param)
- *
- * Return:
- * * %0      - success
- * * %EINVAL - invalid port number or resource type
- * * %other  - failed to write to PHY
- */
-static int ice_write_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,
-				     u32 val)
-{
-	return ice_write_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);
-}
-
 /**
  * ice_write_quad_ptp_reg_eth56g - Write a PHY quad register
  * @hw: pointer to the HW struct
@@ -1129,47 +1111,35 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
 }
 
 /**
- * ice_clear_ptp_tstamp_eth56g - Clear a timestamp from the quad block
+ * ice_clear_ptp_tstamp_eth56g - Clear a timestamp from PHY memory
  * @hw: pointer to the HW struct
- * @port: the quad to read from
+ * @port: the port to read from
  * @idx: the timestamp index to reset
  *
- * Read and then forcibly clear the timestamp index to ensure the valid bit is
- * cleared and the timestamp status bit is reset in the PHY port memory of
- * internal PHYs of the 56G devices.
+ * Read the timestamp index to ensure that the valid bit is cleared and the
+ * timestamp status bit is reset in the PHY port memory.
  *
- * To directly clear the contents of the timestamp block entirely, discarding
- * all timestamp data at once, software should instead use
- * ice_ptp_reset_ts_memory_quad_eth56g().
- *
- * This function should only be called on an idx whose bit is set according to
- * ice_get_phy_tx_tstamp_ready().
+ * This function should only be called on an index whose bit is set according
+ * to ice_get_phy_tx_tstamp_ready(), or as part of a full sweep when paired
+ * with a PHY soft reset via ice_ptp_phy_soft_reset_eth56g().
  *
  * Return:
  * * %0     - success
- * * %other - failed to write to PHY
+ * * %other - failed to read from PHY
  */
 static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 {
 	u64 unused_tstamp;
-	u16 lo_addr;
 	int err;
 
-	/* Read the timestamp register to ensure the timestamp status bit is
-	 * cleared.
+	/* Per the PHY spec, reading the timestamp memory location is what
+	 * clears the entry's valid bit and its corresponding (read-only)
+	 * ts_memory_status bit.
 	 */
 	err = ice_read_ptp_tstamp_eth56g(hw, port, idx, &unused_tstamp);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read the PHY timestamp register for port %u, idx %u, err %d\n",
 			  port, idx, err);
-	}
-
-	lo_addr = (u16)PHY_TSTAMP_L(idx);
-
-	err = ice_write_port_mem_eth56g(hw, port, lo_addr, 0);
-	if (err) {
-		ice_debug(hw, ICE_DBG_PTP, "Failed to clear low PTP timestamp register for port %u, idx %u, err %d\n",
-			  port, idx, err);
 		return err;
 	}
 
@@ -1177,19 +1147,43 @@ static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 }
 
 /**
- * ice_ptp_reset_ts_memory_eth56g - Clear all timestamps from the port block
+ * ice_ptp_clear_tx_memory_status_eth56g - Reset one port's Tx timestamp memory
  * @hw: pointer to the HW struct
- */
-static void ice_ptp_reset_ts_memory_eth56g(struct ice_hw *hw)
-{
-	unsigned int port;
-
-	for (port = 0; port < hw->ptp.num_lports; port++) {
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_L,
-					 0);
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_U,
-					 0);
+ * @port: port number to clear
+ *
+ * Fully reset a single PHY port's Tx timestamp memory. Per the PHY spec, the
+ * only way to clear a timestamp valid bit (and its read-only ts_memory_status
+ * bit) is to read the timestamp memory location, so read every entry for the
+ * port (two 32-bit reads each). This discards all timestamp data on the port,
+ * so it must only be used for a full reset; callers that must preserve
+ * in-flight timestamps clear individual indices via ice_clear_phy_tstamp().
+ *
+ * Due to interactions with an internal HW counter for the number of
+ * outstanding Tx timestamps, this *must* only be called as part of the
+ * ice_ptp_phy_soft_reset_eth56g() procedure. Otherwise, the internal counter
+ * may become out of sync and prevent new timestamp interrupts.
+ *
+ * Failure to read a given index (i.e. due to a transient sideband queue
+ * failure) is not considered fatal as the PHY port is about to be soft reset.
+ * While the soft reset does not clear the timestamp memory, software
+ * shouldn't be reading the timestamp memory without already knowing it is
+ * valid via ice_get_phy_tx_tstamp_ready(), and this sweep is just
+ * a precaution to ensure the memory is in a known state.
+ */
+static void ice_ptp_clear_tx_memory_status_eth56g(struct ice_hw *hw, u8 port)
+{
+	int err, failed = 0;
+	u8 idx;
+
+	for (idx = 0; idx < INDEX_PER_PORT; idx++) {
+		err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);
+		if (err)
+			failed++;
 	}
+
+	if (failed)
+		dev_warn(ice_hw_to_dev(hw), "Failed to clear %d PHY timestamp registers for port %u\n",
+			 failed, port);
 }
 
 /**
@@ -2098,32 +2092,33 @@ static int ice_sync_phy_timer_eth56g(struct ice_hw *hw, u8 port)
 }
 
 /**
- * ice_stop_phy_timer_eth56g - Stop the PHY clock timer
+ * ice_stop_phy_timer_eth56g - Clear PHY Rx offset ready flag
  * @hw: pointer to the HW struct
  * @port: the PHY port to stop
- * @soft_reset: if true, hold the SOFT_RESET bit of PHY_REG_PS
  *
- * Stop the clock of a PHY port. This must be done as part of the flow to
- * re-calibrate Tx and Rx timestamping offsets whenever the clock time is
- * initialized or when link speed changes.
+ * Disable Rx timestamping by clearing the PHY_REG_RX_OFFSET_READY. This
+ * causes Rx timestamps to be captured with their valid bit clear, ensuring we
+ * discard any timestamp captured while the PHY is being recalibrated.
+ *
+ * Note this does *not* clear PHY_REG_TX_OFFSET_READY. Clearing it would
+ * cause the Tx timestamps to be captured with their valid bit clear.
+ * Unfortunately the captured timestamps still increment the internal counter
+ * and result in off-by-one accounting. Instead, Tx timestamp requests should
+ * be disabled by other means.
  *
  * Return:
  * * %0     - success
  * * %other - failed to write to PHY
  */
-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port)
 {
 	int err;
 
-	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_OFFSET_READY, 0);
-	if (err)
-		return err;
-
 	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);
 	if (err)
 		return err;
 
-	ice_debug(hw, ICE_DBG_PTP, "Disabled clock on PHY port %u\n", port);
+	ice_debug(hw, ICE_DBG_PTP, "Disabled Rx timestamps on PHY port %u\n", port);
 
 	return 0;
 }
@@ -2133,9 +2128,11 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
  * @hw: pointer to the HW struct
  * @port: the PHY port to start
  *
- * Start the clock of a PHY port. This must be done as part of the flow to
- * re-calibrate Tx and Rx timestamping offsets whenever the clock time is
- * initialized or when link speed changes.
+ * Perform a PHY soft reset and then start the clock for the PHY port.
+ *
+ * This must be done as part of the flow to re-calibrate Tx and Rx
+ * timestamping offsets whenever the clock time is initialized or when link
+ * speed changes.
  *
  * Return:
  * * %0     - success
@@ -2151,7 +2148,11 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
 
 	tmr_idx = ice_get_ptp_src_clock_index(hw);
 
-	err = ice_stop_phy_timer_eth56g(hw, port, false);
+	err = ice_stop_phy_timer_eth56g(hw, port);
+	if (err)
+		return err;
+
+	err = ice_ptp_phy_soft_reset_eth56g(hw, port);
 	if (err)
 		return err;
 
@@ -2294,6 +2295,7 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
  *
  * Trigger a soft reset of the ETH56G PHY by toggling the soft reset
  * bit in the PHY global register. The reset sequence consists of:
+ *   0. Reading every timestamp memory register to clear its valid bit
  *   1. Clearing the soft reset bit
  *   2. Asserting the soft reset bit
  *   3. Clearing the soft reset bit again
@@ -2302,6 +2304,12 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
  * to settle. This provides a controlled way to reinitialize the PHY
  * without requiring a full device reset.
  *
+ * To ensure that the internal counter matches the contents of the
+ * PHY_TSTAMP memory region, read every timestamp index prior to performing
+ * the soft reset. The read side effects ensure that the timestamp memory is
+ * cleared while the soft reset ensures that the PHY_REG_TX_MEMORY_STATUS and
+ * internal counter are reset to zero.
+ *
  * Return: 0 on success, or a negative error code on failure when
  *         reading or writing the PHY register.
  */
@@ -2310,6 +2318,8 @@ int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
 	u32 global_val;
 	int err;
 
+	ice_ptp_clear_tx_memory_status_eth56g(hw, port);
+
 	err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, &global_val);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read PHY_REG_GLOBAL for port %d, err %d\n",
@@ -4991,8 +5001,8 @@ ice_get_phy_tx_tstamp_ready_e810(struct ice_hw *hw, u8 port, u64 *tstamp_ready)
  * The E810 devices do not have a Tx memory status register. Note this is
  * intentionally different behavior from ice_get_phy_tx_tstamp_ready_e810
  * which always says that all bits are ready. This function is called in cases
- * where code will trigger interrupts if timestamps are waiting, and should
- * not be called for E810 hardware.
+ * where code will trigger interrupts if timestamps are waiting, and which
+ * should not trigger for E810.
  *
  * Return: 0.
  */
@@ -5801,8 +5811,9 @@ void ice_ptp_reset_ts_memory(struct ice_hw *hw)
 		ice_ptp_reset_ts_memory_e82x(hw);
 		break;
 	case ICE_MAC_GENERIC_3K_E825:
-		ice_ptp_reset_ts_memory_eth56g(hw);
-		break;
+		/* E825 hardware must only reset timestamp memory as part of
+		 * the soft reset procedure.
+		 */
 	case ICE_MAC_E810:
 	default:
 		return;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 16b1988e993d2..17000df77ce95 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -378,7 +378,7 @@ int ice_cgu_get_output_pin_state_caps(struct ice_hw *hw, u8 pin_id,
 
 /* ETH56G family functions */
 int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status);
-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset);
+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port);
 int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port);
 int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold);
 int ice_phy_cfg_ptp_1step_eth56g(struct ice_hw *hw, u8 port);

3/2 2026/10/16 00:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23058
							output: 34
							thoughts: 611
						

Thoughts:
**Fuzzing Evaluation: Intel `ice` Driver Patch**

Okay, so I've got this patch here and the question is: does it warrant syzkaller fuzzing? First, I need to understand what code it actually touches. Alright, the patch modifies a bunch of files in `drivers/net/ethernet/intel/ice/`: `ice_adapter.c`, `ice_adapter.h`, `ice_main.c`, `ice_ptp.c`, `ice_ptp.h`, `ice_ptp_hw.c`, and `ice_ptp_hw.h`. Ah, right, `ice`... that's the Intel Ethernet Connection E800 Series driver!

Now, the crucial question: can we even *reach* this code with fuzzing? I need to check the fuzzing guidelines. Section 1 and 2 are clear: *reachability is key*. If the code isn't executed in standard virtualized environments (like GCE or QEMU), then fuzzing is a no-go. We're talking about QEMU, using software-emulated devices like netdevs, tun/tap, etc. Section 2 further clarifies that vendor-specific PCIe switches, SmartNICs, or GPU drivers that require physical ASICs are unreachable.

Hold on… is the Intel `ice` hardware emulated in QEMU? QEMU has support for e1000, e1000e, igb (in recent qemu), maybe intel 82540EM, 82574L, 82576. But does it emulate the `ice` (Intel 800 Series / 100GbE / 200GbE Ethernet) NICs? That's the real question. I need to determine if `ice` falls into the category of vendor-specific hardware requiring physical ASIC or PCIe cards, which would make it structurally unreachable for fuzzing. Time to dive into Kconfig and the driver details to find out.



4/2 2026/10/16 00:51 tool grepper
0m Args:
{
  "Expression": "config ICE\\b",
  "PathPrefixes": [
    "drivers/net/ethernet/intel/"
  ]
}

Results:
{
  "Output": "drivers/net/ethernet/intel/Kconfig=268=config I40EVF\n--\ndrivers/net/ethernet/intel/Kconfig-290-\ndrivers/net/ethernet/intel/Kconfig:291:config ICE\ndrivers/net/ethernet/intel/Kconfig-292-\ttristate \"Intel(R) Ethernet Connection E800 Series Support\"\n"
}

5/2 2026/10/16 00:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24250
							output: 39
							thoughts: 24
						

Thoughts:
Okay, let's dive into this. Here's my thought process on the matter:

**Delving into the Intel Ethernet Kconfig**

Alright, time to get my hands dirty. I need to understand something specific about the Intel Ethernet driver configuration, so I'm heading directly to `drivers/net/ethernet/intel/Kconfig`. Based on my experience, I'm guessing that line 291 is a good starting point for the section I'm interested in, given my prior understanding of this codebase. Let's see what gems we can find there. This should be interesting... I'm expecting some specific configuration options to become apparent. Let's see what's on offer.



6/2 2026/10/16 00:51 tool read-file
0m Args:
{
  "File": "drivers/net/ethernet/intel/Kconfig",
  "FirstLine": 290,
  "LineCount": 40
}

Results:
{
  "Contents": " 290:\t\n 291:\tconfig ICE\n 292:\t\ttristate \"Intel(R) Ethernet Connection E800 Series Support\"\n 293:\t\tdefault n\n 294:\t\tdepends on PCI_MSI\n 295:\t\tdepends on PTP_1588_CLOCK_OPTIONAL\n 296:\t\tdepends on GNSS || GNSS = n\n 297:\t\tselect AUXILIARY_BUS\n 298:\t\tselect DIMLIB\n 299:\t\tselect LIBETH_XDP\n 300:\t\tselect LIBIE\n 301:\t\tselect LIBIE_ADMINQ\n 302:\t\tselect LIBIE_FWLOG if DEBUG_FS\n 303:\t\tselect NET_DEVLINK\n 304:\t\tselect PACKING\n 305:\t\tselect PLDMFW\n 306:\t\tselect DPLL\n 307:\t\thelp\n 308:\t\t  This driver supports Intel(R) Ethernet Connection E800 Series of\n 309:\t\t  devices.  For more information on how to identify your adapter, go\n 310:\t\t  to the Adapter \u0026 Driver ID Guide that can be located at:\n 311:\t\n 312:\t\t  \u003chttp://support.intel.com\u003e\n 313:\t\n 314:\t\t  More specific information on configuring the driver is in\n 315:\t\t  \u003cfile:Documentation/networking/device_drivers/ethernet/intel/ice.rst\u003e.\n 316:\t\n 317:\t\t  To compile this driver as a module, choose M here. The module\n 318:\t\t  will be called ice.\n 319:\t\n 320:\tconfig ICE_HWMON\n 321:\t\tbool \"Intel(R) Ethernet Connection E800 Series Support HWMON support\"\n 322:\t\tdefault y\n 323:\t\tdepends on ICE \u0026\u0026 HWMON \u0026\u0026 !(ICE=y \u0026\u0026 HWMON=m)\n 324:\t\thelp\n 325:\t\t  Say Y if you want to expose thermal sensor data on Intel devices.\n 326:\t\n 327:\t\t  Some of our devices contain internal thermal sensors.\n 328:\t\t  This data is available via the hwmon sysfs interface and exposes\n 329:\t\t  the onboard sensors.\n"
}

7/2 2026/10/16 00:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25053
							output: 38
							thoughts: 111
						

Thoughts:
**Analyzing the ICE Driver for Virtualization Potential**

Okay, so I'm looking at the `CONFIG_ICE` driver here. That rings a bell – Intel's E800 series, right? High-speed enterprise NICs, specifically talking 100GbE, 200GbE, the whole E800, E810, E820, and E830 family. Now, given my experience, my immediate thought is to check for virtualization capabilities. I want to see if this driver includes any virtual bus or software emulation features for `ice`. You know, like, does it support any kind of mock or virtual device functionality? The real question I want to answer is if I can use this driver to simulate the hardware at any level. My next step will be to dive into the source code and grep for things like `ice_probe` or specific PCI device IDs. That should quickly reveal if there are any hints about a virtualized environment or simulated hardware support within this driver.



8/2 2026/10/16 00:51 tool grepper
0m Args:
{
  "Expression": "ice_pci_tbl",
  "PathPrefixes": [
    "drivers/net/ethernet/intel/ice/"
  ]
}

Results:
{
  "Output": "drivers/net/ethernet/intel/ice/ice_main.c=5125=static int ice_probe_recovery_mode(struct ice_pf *pf)\n--\ndrivers/net/ethernet/intel/ice/ice_main.c-5157- * @pdev: PCI device information struct\ndrivers/net/ethernet/intel/ice/ice_main.c:5158: * @ent: entry in ice_pci_tbl\ndrivers/net/ethernet/intel/ice/ice_main.c-5159- *\n--\ndrivers/net/ethernet/intel/ice/ice_main.c=5779=static void ice_pci_err_reset_done(struct pci_dev *pdev)\n--\ndrivers/net/ethernet/intel/ice/ice_main.c-5783-\ndrivers/net/ethernet/intel/ice/ice_main.c:5784:/* ice_pci_tbl - PCI Device ID Table\ndrivers/net/ethernet/intel/ice/ice_main.c-5785- *\n--\ndrivers/net/ethernet/intel/ice/ice_main.c-5791- */\ndrivers/net/ethernet/intel/ice/ice_main.c:5792:static const struct pci_device_id ice_pci_tbl[] = {\ndrivers/net/ethernet/intel/ice/ice_main.c-5793-\t{ PCI_VDEVICE(INTEL, ICE_DEV_ID_E810C_BACKPLANE) },\n--\ndrivers/net/ethernet/intel/ice/ice_main.c-5844-};\ndrivers/net/ethernet/intel/ice/ice_main.c:5845:MODULE_DEVICE_TABLE(pci, ice_pci_tbl);\ndrivers/net/ethernet/intel/ice/ice_main.c-5846-\n--\ndrivers/net/ethernet/intel/ice/ice_main.c=5857=static struct pci_driver ice_driver = {\ndrivers/net/ethernet/intel/ice/ice_main.c-5858-\t.name = KBUILD_MODNAME,\ndrivers/net/ethernet/intel/ice/ice_main.c:5859:\t.id_table = ice_pci_tbl,\ndrivers/net/ethernet/intel/ice/ice_main.c-5860-\t.probe = ice_probe,\n"
}

9/2 2026/10/16 00:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26033
							output: 117
							thoughts: 953
						

Thoughts:
**Triage Decision: WorthFuzzing = False**

Okay, here's the breakdown of my thinking. This `ice_driver` is specifically for Intel's E800 series network cards – we're talking about hardware like the E810, E825, and so on. These aren't just virtual NICs; they're physical PCIe cards, and they need the physical ASIC or FPGA.

I know that standard QEMU and GCE, which are our typical fuzzing environments, *don't* emulate these E800 series devices. They handle the e1000, virtio-net, and a few others, but no `ice` devices. They don't have the hardware emulation required.

Now, let's go back to the guidelines. I see a direct hit for returning `WorthFuzzing=false` under "Structurally unreachable hardware." It explicitly lists "vendor-specific PCIe switches, SmartNICs, or GPU drivers... requiring physical ASIC/PCIe cards not emulated in standard QEMU". That's precisely what we have here. The E800 series is a vendor-specific NIC, and it requires physical hardware that's not available in our standard fuzzing environment.

All the modified files – `ice_adapter.c`, `ice_main.c`, `ice_ptp.c`, `ice_ptp_hw.c`, and their respective headers – are deep within the `drivers/net/ethernet/intel/ice/` directory. They are all part of the `ice` driver for the Intel E800 series.

Therefore, because this driver targets hardware that is not emulated in standard fuzzing environments, the code modifications are unreachable for syzkaller.

I'm confident in setting:
*   `WorthFuzzing`: false
*   `FocusSymbols`: \[ ]
*   `EnableConfigs`: \[ ]

My reasoning is straightforward: The patch concerns the Intel E800 series ethernet driver (`drivers/net/ethernet/intel/ice/`), which relies on physical Intel E800-series PCIe NICs (like the E810 and E825). Since these physical components are not simulated in standard QEMU/GCE environments, the code being modified is structurally unreachable, making it unsuitable for fuzzing. And the results will be reported as:

*   `EnableConfigs`: \[ ]
*   `FocusSymbols`: \[ ]
*   `WorthFuzzing`: false
*   `Reasoning`: "The patch modifies the Intel E800 series Ethernet driver (drivers/net/ethernet/intel/ice), specifically handling PTP timestamping and adapter port locking on E810/E825 hardware. This driver requires physical Intel E800-series PCIe NIC hardware that is not emulated in standard QEMU or GCE virtualization environments, making the code structurally unreachable for syzkaller."



10/2 2026/10/16 00:51 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the Intel E800 series Ethernet driver (drivers/net/ethernet/intel/ice), specifically adapter port locking and PTP timestamping synchronization for E810/E825 hardware. This driver requires physical Intel E800-series PCIe hardware that is not emulated in standard QEMU or GCE virtualized environments, making the modified code structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the Intel E800 series Ethernet driver (drivers/net/ethernet/intel/ice), specifically adapter port locking and PTP timestamping synchronization for E810/E825 hardware. This driver requires physical Intel E800-series PCIe hardware that is not emulated in standard QEMU or GCE virtualized environments, making the modified code structurally unreachable for fuzzing.",
  "WorthFuzzing": false
}

11/1 2026/10/16 00:51 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)