From: Jacob Keller <jacob.e.keller@intel.com>
To: Jakub Kicinski <kuba@kernel.org>, <anthony.l.nguyen@intel.com>
Cc: <davem@davemloft.net>, <pabeni@redhat.com>, <edumazet@google.com>,
<andrew+netdev@lunn.ch>, <netdev@vger.kernel.org>,
<maciej.machnikowski@intel.com>, <przemyslaw.korba@intel.com>,
<grzegorz.nitka@intel.com>, <sergey.temerkhanov@intel.com>,
<arkadiusz.kubalewski@intel.com>, <poros@redhat.com>,
<richardcochran@gmail.com>, <horms@kernel.org>,
<pmenzel@molgen.mpg.de>, <aleksandr.loktionov@intel.com>,
<alexander.nowlin@intel.com>
Subject: Re: [PATCH net 01/15] ice: use reference counting and RCU for PTP port access
Date: Thu, 17 Sep 2026 09:13:38 -0700 [thread overview]
Message-ID: <5fbaadd4-9da0-4075-9fcd-476ec054e6f1@intel.com> (raw)
In-Reply-To: <20260916011211.1632220-1-kuba@kernel.org>
On 9/15/2026 6:12 PM, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> ice: use reference counting and RCU for PTP port access
>
> The adapter-wide PTP port list is converted from a mutex-protected list to
> an RCU list plus a kref on each struct ice_ptp_port. Iterators either stay
> inside a single RCU read section, or take a reference and drop RCU across
> the per-port work. PF teardown unlinks the port, drops the primary
> reference, waits (with a 15 second timeout) for the remaining references,
> and then calls synchronize_rcu().
>
> A couple of questions below about the new iteration pattern and about a
> few lifetime claims in the commit message.
>
>> The kref reference counter ensures that we can
>> safely acquire pointers with a guarantee of their lifetime, ensuring the
>> associated PF will not be removed until the reference is released.
>
> [Severity: High]
>
> This isn't a bug introduced by this patch, but does that sentence read
> wider than the change actually is? The new kref covers the port list
> only; the other cross-PF pointer on the adapter is still unprotected:
> > drivers/net/ethernet/intel/ice/ice_ptp.c:
> static void ice_ptp_setup_adapter(struct ice_pf *pf)
> {
> pf->adapter->ctrl_pf = pf;
> }
>
> drivers/net/ethernet/intel/ice/ice.h:
> static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf)
> {
> return !pf->adapter ? NULL : pf->adapter->ctrl_pf;
> }
>
> grep over drivers/net/ethernet/intel/ice/ finds exactly one assignment of
> ctrl_pf and no site that ever clears it.
>
> If the clock owner PF is unbound while a sibling PF of the same adapter
> stays bound, the ice_adapter survives on the sibling's refcount, but
> ctrl_pf still points at the freed struct ice_pf. Can a later tx-clk
> change then dereference it?
>
> drivers/net/ethernet/intel/ice/ice_txclk.c:ice_txclk_set_clk()
> mutex_lock(&ctrl_pf->dplls.lock);
> clk_in_use = ice_txclk_any_port_uses(ctrl_pf, phy, clk);
>
> The IS_ERR_OR_NULL(ctrl_pf) checks in that file cannot detect a stale
> non-NULL pointer. Would it be worth stating in the commit message that
> the new reference counting is scoped to the port list, so that ctrl_pf
> isn't assumed to be covered?
>
Yes, I'll update it. We have another developer working on fixing ctrl_pf.
>> diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
>> index 4f695f32da3d8..39923dedd534e 100644
>> --- a/drivers/net/ethernet/intel/ice/ice_adapter.h
>> +++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
>> @@ -19,13 +19,13 @@ struct ice_pf;
>> *
>> * This structure contains data used to maintain a list of adapter ports
>> *
>> - * @ports: list of ports
>> + * @list: list of ports
>> * @lock: protect access to the ports list
>> */
>> struct ice_port_list {
>> - struct list_head ports;
>> + struct list_head list;
>> /* To synchronize the ports list operations */
>> - struct mutex lock;
>> + spinlock_t lock;
>> };
>
> [Severity: Low]
>
> This isn't a bug, but should the kernel-doc be updated along with the
> locking model? After this patch, ports.lock is taken only around
> list_add_rcu() in ice_ptp_setup_pf() and list_del_rcu() in
> ice_ptp_cleanup_pf(); all five traversal sites in ice_ptp.c use
> rcu_read_lock() plus list_for_each_entry_rcu() and never take the lock.
>
> The comments still say "@lock: protect access to the ports list" and "To
> synchronize the ports list operations", which reads as if holding the lock
> were required (or sufficient) for iteration. Would it help to note that
> the list is RCU-protected for readers, and that readers which leave the
> RCU section must hold a port reference?
>
Yep, I'll update this.
>> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
>> index eaec36ab6ae3f..b12181b8c8434 100644
>> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
>> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
>
> [ ... ]
>
>> @@ -673,20 +675,33 @@ 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_rcu(struct kref *ref)
>> +{
>> + wake_up_var(ref);
>> +}
>> +
>> static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
>> {
>> struct ice_ptp_port *port;
>>
>> - mutex_lock(&pf->adapter->ports.lock);
>> - list_for_each_entry(port, &pf->adapter->ports.ports, list_node) {
>> + rcu_read_lock();
>> + list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
>> struct ice_ptp_tx *tx = &port->tx;
>>
>> - if (!tx || !tx->init)
>> + if (!tx->init)
>> continue;
>>
>> + if (!kref_get_unless_zero(&port->ref))
>> + continue;
>> +
>> + rcu_read_unlock();
>> +
>> ice_ptp_process_tx_tstamp(tx);
>> +
>> + rcu_read_lock();
>> + kref_put(&port->ref, ice_ptp_release_port_rcu);
>> }
>> - mutex_unlock(&pf->adapter->ports.lock);
>> + rcu_read_unlock();
>> }
>
> [Severity: High]
>
> Is the loop advance still safe here? The kref is held on the current port
> only, and the for-loop advance reads port->list_node.next after the RCU
> read section has been reopened. list_del_rcu() only updates the
> neighbours, so the removed entry's next pointer keeps pointing at its old
> successor, and ice_ptp_cleanup_pf() unlinks before it drains references:
>
> spin_lock(&pf->adapter->ports.lock);
> list_del_rcu(&ptp->port.list_node);
> spin_unlock(&pf->adapter->ports.lock);
>
> ref = &ptp->port.ref;
> kref_put(ref, ice_ptp_release_port_rcu);
>
> Can this sequence happen with two functions of the same adapter, where
> port B follows port A in the list?
>
> CPU0 (clock owner, ice_ptp_tx_tstamp_owner())
> kref_get_unless_zero(&portA->ref);
> rcu_read_unlock();
> ice_ptp_process_tx_tstamp(&portA->tx); /* long PHY access */
>
> CPU1 (unbind of the PF owning port B)
> ice_ptp_cleanup_pf()
> list_del_rcu(&portB->list_node);
> kref_put(&portB->ref, ...); /* refcount reaches 0 */
> wait_var_event_timeout(...) /* returns immediately */
> synchronize_rcu(); /* CPU0 holds no RCU lock */
> ... struct ice_pf of B (which embeds portB) is freed
>
> CPU0
> rcu_read_lock();
> kref_put(&portA->ref, ice_ptp_release_port_rcu);
> /* loop advance: */
> pos = list_entry_rcu(portA->list_node.next, ...); /* freed portB */
> if (!tx->init) /* read of freed memory */
> kref_get_unless_zero(&portB->ref) /* cmpxchg on freed memory */
>
> The same drop-RCU-inside-the-loop pattern appears in
> ice_ptp_flush_all_tx_tracker(), ice_ptp_restart_all_phy() and
> ice_ptp_prepare_rebuild_sec() below, and the windows there are wide
> (ice_ptp_port_phy_restart() takes ps_lock and reprograms the PHY).
>
> Note that ice_ptp_tx_tstamp_owner() previously held ports.lock across the
> entire iteration, so this particular hazard is new for that function.
> Would taking a reference on the next entry before dropping RCU, or keeping
> the whole iteration inside one RCU read section, close this?
>
I'm switching to SRCU to allow holding the RCU critical section over the
entire sequences instead. We can't use regular rcu_read_lock() for that
because operations sleep.
>> /**
>> @@ -808,8 +823,16 @@ ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)
>> {
>> struct ice_ptp_port *port;
>>
>> - list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
>> + rcu_read_lock();
>> + list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
>> + if (!kref_get_unless_zero(&port->ref))
>> + continue;
>> + rcu_read_unlock();
>> ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
>> + rcu_read_lock();
>> + kref_put(&port->ref, ice_ptp_release_port_rcu);
>> + }
>> + rcu_read_unlock();
>> }
>
> [Severity: Medium]
>
> This is a pre-existing issue, not something this patch introduces, but
> does the new port reference also cover the Tx tracker allocations reached
> through the port? At this commit a peer reset frees them while the port
> stays linked in the list:
>
> ice_ptp_prepare_for_reset()
> ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
>
Thats actually a bug that is fixed in this series, as we modified the
reset flow to avoid needing to release the tracker but accidentally
forgot to remove this cleanup.
> ice_ptp_release_tx_tracker()
> synchronize_irq(pf->oicr_irq.virq);
> ice_ptp_flush_tx_tracker(pf, tx);
> kfree(tx->tstamps);
>
> The synchronize_irq() is on the peer's own OICR vector, which does not
> exclude the clock owner's IRQ thread in ICE_PTP_TX_INTERRUPT_ALL mode, and
> the owner samples port->tx.init without tx->lock before calling
> ice_ptp_process_tx_tstamp() or ice_ptp_flush_tx_tracker() on that peer
> port. Since the port is never unlinked in this path, the kref drain in
> ice_ptp_cleanup_pf() does not apply.
>
> The triggering call is removed later in the series by "ice: fix removal of
> PTP timestamp tracker during reset", after which
> ice_ptp_release_tx_tracker() only runs from ice_ptp_release() once
> ice_ptp_cleanup_pf() has unlinked the port and drained references.
>
Yep this is correct. We could re-order the series I suppose but I think
its ok as-is.
>> /**
>
> [ ... ]
>
>> @@ -1424,16 +1453,21 @@ static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)
>> */
>> static void ice_ptp_restart_all_phy(struct ice_pf *pf)
>> {
>> - struct list_head *entry;
>> + struct ice_ptp_port *port;
>>
>> - list_for_each(entry, &pf->adapter->ports.ports) {
>> - struct ice_ptp_port *port = list_entry(entry,
>> - struct ice_ptp_port,
>> - list_node);
>> + rcu_read_lock();
>> + list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
>> + if (!kref_get_unless_zero(&port->ref))
>> + continue;
>> + rcu_read_unlock();
>>
>> if (port->link_up)
>> ice_ptp_port_phy_restart(port);
>> +
>> + rcu_read_lock();
>> + kref_put(&port->ref, ice_ptp_release_port_rcu);
>> }
>> + rcu_read_unlock();
>> }
>
> [ ... ]
>
>> @@ -2890,14 +2924,16 @@ 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_ptp_port *port;
>>
>> - list_for_each(entry, &pf->adapter->ports.ports) {
>> - struct ice_ptp_port *port = list_entry(entry,
>> - struct ice_ptp_port,
>> - list_node);
>> + rcu_read_lock();
>> + list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
>> struct ice_pf *peer_pf = ptp_port_to_pf(port);
>>
>> + if (!kref_get_unless_zero(&port->ref))
>> + continue;
>> + rcu_read_unlock();
>> +
>
> [ ... ]
>
>> @@ -3086,11 +3126,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);
>
> [Severity: Medium]
>
> This isn't introduced by this patch, but is every successful
> ice_ptp_setup_pf() matched by an ice_ptp_cleanup_pf() at this commit? In
> ice_ptp_init():
>
> ptp->state = ICE_PTP_READY;
>
> err = ice_ptp_init_work(pf, ptp);
> if (err)
> goto err_exit;
> ...
> err_clean_pf:
> mutex_destroy(&ptp->port.ps_lock);
> ice_ptp_cleanup_pf(pf);
> err_exit:
> ...
> ptp->state = ICE_PTP_UNINIT;
>
> The err_exit path skips ice_ptp_cleanup_pf() and leaves the state at
> ICE_PTP_UNINIT, and ice_ptp_release() then does:
>
> if (pf->ptp.state == ICE_PTP_UNINIT)
> return;
>
> So if kthread_run_worker() in ice_ptp_init_work() fails, does the port stay
> linked on adapter->ports.list with its primary reference held, inside
> memory that is freed at PF removal? That would also leave
> kref_get_unless_zero() succeeding on freed memory for every peer iterating
> the list, and should trip WARN_ON(!list_empty(&adapter->ports.list)) in
> ice_adapter_free().
>
> This is fixed later in the series by "ice: call PTP link change only from
> link events", which moves ice_ptp_init_work() before the state assignment
> and sends its failure path to err_clean_pf.
>
>>
>> /* Seed the per-PHY Tx reference clock usage map for this port.
>> * Only meaningful on E825 (other MAC types don't expose tx-clk
>> @@ -3113,12 +3153,32 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
>> static void ice_ptp_cleanup_pf(struct ice_pf *pf)
>> {
>> 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;
>> +
>> + /* The PF cannot be removed until there are no more remaining
>> + * outstanding references to the PTP port. To make sure this is true,
>> + * first remove the port from the list, then drop the primary
>> + * reference this PF holds on the port. Once done, wait until all
>> + * existing references are dropped. Finally, synchronize_rcu() to
>> + * ensure that all RCU critical sections that might attempt to
>> + * dereference the port are finished.
>> + */
>
> [Severity: Low]
>
> The commit message describes the 15 second timeout and the tradeoff it
> accepts, but this comment says the PF "cannot be removed until there are no
> more remaining outstanding references" and that the code will "wait until
> all existing references are dropped". Should the comment mention the
> timeout, since teardown continues after the dev_WARN_ONCE() with a nonzero
> reference count?
>
I can update the comment.
>> +
>> + spin_lock(&pf->adapter->ports.lock);
>> + list_del_rcu(&ptp->port.list_node);
>> + spin_unlock(&pf->adapter->ports.lock);
>> +
>> + ref = &ptp->port.ref;
>> + kref_put(ref, ice_ptp_release_port_rcu);
>> +
>> + dev_WARN_ONCE(ice_pf_to_dev(pf),
>> + !wait_var_event_timeout(ref, !kref_read(ref), 15 * HZ),
>> + "Timed out waiting for port references to release. Continuing to unload anyways.");
>> +
>> + synchronize_rcu();
>> }
>
> [Severity: Low]
>
> This is a pre-existing ordering issue, not something this patch changes,
> but now that ice_ptp_cleanup_pf() is the reference/RCU barrier, would it
> make sense to call it before destroying port members? Both teardown paths
> currently do the opposite:
>
> ice_ptp_init():
> err_clean_pf:
> mutex_destroy(&ptp->port.ps_lock);
> ice_ptp_cleanup_pf(pf);
>
> ice_ptp_release():
> if (pf->ptp.state != ICE_PTP_READY) {
> mutex_destroy(&pf->ptp.port.ps_lock);
> ice_ptp_cleanup_pf(pf);
>
> In that window the port is still published on adapter->ports.list with its
> primary reference held, so a peer in ice_ptp_restart_all_phy() or
> ice_ptp_prepare_rebuild_sec() can take a reference and reach
> mutex_lock(&ptp_port->ps_lock) on the destroyed mutex. With
> CONFIG_DEBUG_MUTEXES that is a DEBUG_LOCKS_WARN_ON splat rather than
> corruption, since mutex_destroy() only clears lock->magic, but swapping the
> two calls would keep cleanup_pf() as the single teardown point.
>
> [ ... ]
Yes that makes sense. Will fix.
next prev parent reply other threads:[~2026-09-17 16:13 UTC|newest]
Thread overview: 45+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-11 0:34 [PATCH net 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
2026-09-11 0:34 ` [PATCH net 01/15] ice: use reference counting and RCU for PTP port access Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:13 ` Jacob Keller [this message]
2026-09-11 0:34 ` [PATCH net 02/15] ice: fix removal of PTP timestamp tracker during reset Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:19 ` Jacob Keller
2026-09-18 0:22 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 03/15] ice: set in_use only after preparing Tx timestamp index Tony Nguyen
2026-09-11 0:34 ` [PATCH net 04/15] ice: E822: keep Tx timestamps disabled during offset calibration Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:25 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 05/15] ice: E822: cancel offset verification work during reset preparation Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:31 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 06/15] ice: call PTP link change only from link events Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:37 ` Jacob Keller
2026-09-18 1:31 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 07/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:41 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 08/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:46 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 09/15] ice: E825: perform a soft reset when starting the PHY timer Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 16:58 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 10/15] ice: wait for in-flight Tx timestamps before flushing the tracker Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 17:00 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 11/15] ice: keep Tx timestamp slots tracked until completion or timeout Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 17:47 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 12/15] ice: remove unnecessary discarding of timestamps after clock adjust Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 17:53 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 13/15] ice: skip reading Tx ready bitmap on ports with no timestamps Tony Nguyen
2026-09-11 0:34 ` [PATCH net 14/15] ice: don't clear in_use until HW clears ready bitmap Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 17:56 ` Jacob Keller
2026-09-11 0:34 ` [PATCH net 15/15] ice: Recalibrate PHY after settime64 on E825-C Tony Nguyen
2026-09-16 1:12 ` Jakub Kicinski
2026-09-17 18:02 ` Jacob Keller
2026-09-16 21:46 ` [PATCH net 00/15][pull request] ice: E82x: timestamp processing logic fixes Jacob Keller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5fbaadd4-9da0-4075-9fcd-476ec054e6f1@intel.com \
--to=jacob.e.keller@intel.com \
--cc=aleksandr.loktionov@intel.com \
--cc=alexander.nowlin@intel.com \
--cc=andrew+netdev@lunn.ch \
--cc=anthony.l.nguyen@intel.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=grzegorz.nitka@intel.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=maciej.machnikowski@intel.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pmenzel@molgen.mpg.de \
--cc=poros@redhat.com \
--cc=przemyslaw.korba@intel.com \
--cc=richardcochran@gmail.com \
--cc=sergey.temerkhanov@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox