Re: [Intel-wired-lan] [PATCH v2 iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF pin for E825
"Nitka, Grzegorz" <[email protected]> Mon, 3 Aug 2026 09:43:25 +0000
| Newsgroups | org.osuosl.intel-wired-lan,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <IA1PR11MB6219D6278D446431875B4AE092D52@IA1PR11MB6219.namprd11.prod.outlook.com> |
> -----Original Message----- > From: Nitka, Grzegorz <[email protected]> > Sent: Friday, July 31, 2026 2:12 PM > To: [email protected] > Cc: [email protected]; [email protected]; > [email protected]; [email protected]; Kitszel, Przemyslaw > <[email protected]>; Nguyen, Anthony L > <[email protected]>; Kubalewski, Arkadiusz > <[email protected]>; [email protected]; [email protected]; > [email protected]; [email protected]; Nitka, Grzegorz > <[email protected]>; Korba, Przemyslaw > <[email protected]> > Subject: [PATCH v2 iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF = pin > for E825 >=20 > This extends the E825 advanced sync-timing support introduced by the > tx-clk series, which added the TXC DPLL device for TX reference clock > control. The TSPLL, the source timer PLL, is now also exposed through > the dpll subsystem so that its lock status and clock source selection > are visible and controllable from userspace. >=20 > On E825 devices the TSPLL is the source timer PLL, distinct from the > EEC and PPS DPLLs used on E810. Register it as a DPLL_TYPE_GENERIC > device for owner PFs. >=20 > Add struct ice_dplls::tspll_in, a fwnode-backed input pin named > "time_ref". The state_on_dpll_get callback queries ICE_CGU_R23 via > ice_tspll_get_clk_src() and returns CONNECTED when TIME_REF is > selected as clock source, DISCONNECTED otherwise. The state_on_dpll_set > callback switches the source between TIME_REF and TCXO via the new > ice_tspll_set_cfg() helper. Registration is deferred via the dpll > notifier path if the pin is not yet visible in the subsystem at probe > time. >=20 > Initialize TSPLL DPLL state from direct clock-source/lock reads so the > first published state reflects hardware and prev_dpll_state matches. > During periodic polling, the DPLL worker consumes > READ_ONCE(pf->ptp.tspll_locked), maintained and recovered by the PTP > periodic worker. When the TSPLL clock source is TCXO (TIME_REF pin not > selected), UNLOCKED is reported unconditionally to reflect the > free-running state of the oscillator regardless of the raw lock bit. > To avoid stale lock-status reads after synchronous source changes, the > set callback now refreshes tspll.dpll_state immediately and emits a DPLL > change notification when the cached state changed. >=20 > If a TSPLL reconfiguration is applied but the PLL has not yet > re-acquired lock, treat the internal -EAGAIN result as success so the > PTP periodic worker can complete recovery, while real -EBUSY failures > from reset/SBQ paths still propagate to userspace. >=20 > The TSPLL userspace reconfiguration path (state_on_dpll_set) and the > PTP periodic worker's TSPLL restart on lost lock both access CGU > registers, so serialize them with pf->dplls.lock. The mutex lifetime > is lifted to PF-features scope (initialized in ice_init_features() > before ice_ptp_init(), destroyed in ice_deinit_features() after > ice_ptp_release()) so the PTP monitor can take it unconditionally > regardless of DPLL init state. >=20 > Extend ice_dpll_deinit_txclk_pins() with a "flush" parameter so the > E825 init error path for the TSPLL fwnode pin can tear down TXCLK > pins without flushing pf->dplls.wq. If the flush ran here, notifier > work items queued during earlier init steps would be blocked on > pf->dplls.dpll_init, which is only completed at the unregister_pins > label reached after this teardown. destroy_workqueue() at that label > drains the queued items safely. Existing full-teardown callers pass > flush=3Dtrue and keep current behavior. >=20 > Reviewed-by: Przemyslaw Korba <[email protected]> > Signed-off-by: Grzegorz Nitka <[email protected]> > --- > drivers/net/ethernet/intel/ice/ice_dpll.c | 416 ++++++++++++++++++--- > drivers/net/ethernet/intel/ice/ice_dpll.h | 4 + > drivers/net/ethernet/intel/ice/ice_main.c | 9 + > drivers/net/ethernet/intel/ice/ice_ptp.c | 9 + > drivers/net/ethernet/intel/ice/ice_tspll.c | 34 +- > drivers/net/ethernet/intel/ice/ice_tspll.h | 2 + > 6 files changed, 419 insertions(+), 55 deletions(-) >=20 Sashiko says: > @@ -2891,6 +3000,43 @@ static int > +ice_dpll_tspll_state_on_dpll_set(const struct dpll_pin *pin, void *pin_p= riv, > + const struct dpll_device *dpll, > + void *dpll_priv, enum dpll_pin_state state, > + struct netlink_ext_ack *extack) > +{ [ ... ] > + /* Serialize multi-register TSPLL reconfiguration with restart path. */ > + mutex_lock(&pf->dplls.lock); > + ret =3D ice_tspll_set_cfg(&pf->hw, ICE_TSPLL_FREQ_156_250, clk_src); > + if (!ret || ret =3D=3D -EAGAIN) { > + enum dpll_lock_status new_state; > + > + /* Reconfiguration can transiently drop lock; clear cached lock > + * before PTP monitor refresh to avoid spurious LOCKED reports. > + */ > + WRITE_ONCE(pf->ptp.tspll_locked, false); Will unconditionally invalidating the cached state cause spurious flapping = if the lock is successfully acquired synchronously? If ice_tspll_set_cfg() returns 0, the lock has been acquired and new_state will be DPLL_LOCK_STATUS_LOCKED. However, we still set tspll_locked to fals= e. When ice_dpll_periodic_work_e825() runs next, it will read this false value and report the state as unlocked to userspace, until the PTP monitor runs a= nd corrects it. > + > + new_state =3D ice_dpll_tspll_lock_status_get(pf, false); > + if (tp->prev_dpll_state !=3D new_state) { > + tp->dpll_state =3D new_state; > + tp->prev_dpll_state =3D new_state; > + changed =3D true; > + } > + } > + mutex_unlock(&pf->dplls.lock); It's a valid concern. To be fixed in v3. Sashiko says: > @@ -4093,8 +4367,25 @@ static int ice_dpll_init_pins_e825(struct ice_pf *= pf) > goto unregister_pins; > =20 > ret =3D ice_dpll_init_txclk_pins(pf, 0); > - if (ret) > + if (ret) { > ice_dpll_deinit_rclk_pin(pf); > + goto unregister_pins; > + } > + > + if (ice_pf_src_tmr_owned(pf) && > + test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) { > + ret =3D ice_dpll_init_tspll_pins(pf); > + if (ret) { > + /* > + * Avoid flushing pf->dplls.wq here: notifier work > + * items block on pf->dplls.dpll_init which is > + * completed by the unregister_pins path below. > + * destroy_workqueue() there drains queued items. > + */ This is a pre-existing issue, but does this error path lead to a deadlock during driver initialization? The comment here mentions that notifier work items are blocked on pf->dplls.dpll_init, and expects the completion to happen in the unregister_pins path before destroy_workqueue() is called. However, if an error occurs and we jump to unregister_pins, we call ice_dpll_deinit_fwnode_pins(), which calls destroy_workqueue(pf->dplls.wq). Because complete_all(&pf->dplls.dpll_init) is never called on this error path, destroy_workqueue() will wait forever for the blocked workers to finish. > + ice_dpll_deinit_txclk_pins(pf, false); > + ice_dpll_deinit_rclk_pin(pf); > + } > + } This isn't a deadlock - complete_all(&pf->dplls.dpll_init) is called at the unregister_pins: label immediately before ice_dpll_deinit_fwnode_pins()= , which is what wakes the blocked notifier workers so destroy_workqueue() (invoked at the tail of ice_dpll_deinit_fwnode_pins()) can drain them. The flush=3Dfalse argument to ice_dpll_deinit_txclk_pins() is precisely to = avoid flushing before the completion is signalled; deferring the flush to the sha= red flush_workqueue() inside ice_dpll_deinit_fwnode_pins() - which runs after complete_all() - drains all queued notifier work safely. Sashiko says: > --- a/drivers/net/ethernet/intel/ice/ice_ptp.c > +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c > @@ -2917,7 +2917,16 @@ static void ice_ptp_tspll_monitor(struct ice_pf *p= f) > if (!(pf->ptp.tspll_lock_retries % ICE_TSPLL_LOG_INTERVAL)) > dev_warn(ice_pf_to_dev(pf), > "TimeSync PLL lock lost. Retrying to acquire lock.\n"); > + /* Serialize R23 restart against TSPLL userspace reconfig > + * (ice_dpll_tspll_state_on_dpll_set()). pf->dplls.lock is > + * initialized in ice_init_features() before the PTP kworker > + * starts and destroyed in ice_deinit_features() only after > + * ice_ptp_release() has drained the kworker, so it is always > + * valid to take here regardless of DPLL init state. > + */ > + mutex_lock(&pf->dplls.lock); Is there a race condition here between the PTP monitor and the DPLL set callback? Looking at ice_ptp_tspll_monitor(), the hardware lock status is checked without holding pf->dplls.lock: err =3D ice_tspll_lost_lock_e825c(&pf->hw, &lock_lost); If this thread is preempted after reading a false (no loss of lock) status, and userspace concurrently requests a DPLL state change via ice_dpll_tspll_state_on_dpll_set(), the DPLL callback writes false to pf->ptp.tspll_locked while holding the lock. Once the DPLL callback finishes, if this thread resumes in the else branch, it will unconditionally write true to pf->ptp.tspll_locked: } else { ... WRITE_ONCE(pf->ptp.tspll_locked, true); } Could this overwrite the accurately-set unlocked state and cause the periodic worker to report a spurious locked state to userspace? > err =3D ice_tspll_restart_e825c(&pf->hw); > + mutex_unlock(&pf->dplls.lock); > if (err) > dev_err_ratelimited(ice_pf_to_dev(pf), > "Failed to restart TimeSync PLL (err: %d).\n", It's a valid concern. To be fixed in v3. > -- > 2.39.3