mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Petr Oros <poros@redhat.com>
To: Ivan Vecera <ivecera@redhat.com>, netdev@vger.kernel.org
Cc: Min Li <min.li@microchip.com>,
	Vadim Fedorenko <vadim.fedorenko@linux.dev>,
	Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>,
	Jiri Pirko <jiri@resnulli.us>, Jakub Kicinski <kuba@kernel.org>,
	Prathosh Satish <Prathosh.Satish@microchip.com>,
	Paolo Abeni <pabeni@redhat.com>,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net v3 4/4] dpll: zl3073x: notify sibling pin when shared output config changes
Date: Wed, 7 Oct 2026 20:55:48 +0200	[thread overview]
Message-ID: <2a263f32-82a9-42b7-8aeb-8ac51a801fd9@redhat.com> (raw)
In-Reply-To: <20261006153116.347497-5-ivecera@redhat.com>



On 10/6/26 5:31 PM, Ivan Vecera wrote:
> Each zl3073x output has a P-pin and an N-pin that share a single HW
> output and, outside N-pin divide mode, share the output's divisor,
> clock type, esync period/width and phase compensation registers.
> Changing one of these settings through one pin's dpll_pin therefore
> also changes the other (sibling) pin's effective configuration, but
> only the pin the change was requested on gets a dpll_pin_change_ntf()
> notification - userspace listening on the sibling pin is never told
> its frequency, esync configuration or phase adjustment changed.
> 
> This was found by code inspection rather than triggered at runtime: a
> dpll pin-get on the sibling pin returns the correct current values, but
> because no change notification is emitted for it, userspace is never
> told that its configuration changed asynchronously through the other
> pin.
> 
> Add zl3073x_dpll_output_pin_sibling_get() to look up the other pin of
> an output pair, and use it in frequency_set() (for the non-N-divided
> signal formats, where the output divisor is shared), esync_set() and
> phase_adjust_set() to notify the sibling pin, if it is registered,
> whenever the shared HW state actually changes.
> 
> The three callbacks are switched from guard(mutex) to explicit
> mutex_lock()/mutex_unlock() with goto-based unwinding, because the
> notification must run after zldpll->lock is released: the notification
> re-enters the pin get callbacks, which take zldpll->lock again, so
> calling it under the lock would deadlock. The DPLL subsystem's own
> dpll_lock is still held across the callback, so the __ (lock-held)
> notification variant is used.
> 
> The sibling lookup walks the zldpll->pins list, so the list membership
> has to match the DPLL registration state. dpll_pin_register() publishes
> a pin (and P is registered before N) before it used to be added to the
> list, and teardown detached the whole list before unregistering any
> pin, so a sibling that is registered - and thus reachable by a PIN_SET
> on the other pin - could be missing from the list and never notified.
> Add the pin to the list before dpll_pin_register() and remove it only
> after dpll_pin_unregister(), both under zldpll->lock, and assert the
> lock in the lookup helper. A pin that is transiently on the list while
> not registered is harmless: __dpll_pin_change_ntf() is a no-op for a
> pin that is not available, and the sibling cannot be freed under the
> lookup because the freeing goes through dpll_pin_unregister(), which
> takes dpll_lock that the notification path holds.
> 
> Fixes: 634ca2cb06d2 ("dpll: zl3073x: Add support to get/set esync on pins")
> Fixes: ce26d7ca50a5 ("dpll: zl3073x: Add support to get/set frequency on pins")
> Fixes: 6287262f761e ("dpll: zl3073x: Add support to adjust phase")
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
>   drivers/dpll/zl3073x/dpll.c | 187 ++++++++++++++++++++++++++++--------
>   1 file changed, 146 insertions(+), 41 deletions(-)
> 
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 65107b4cc4f8..9c678acc3e77 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -123,6 +123,8 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
>   {
>   	struct zl3073x_dpll_pin *pin;
>   
> +	lockdep_assert_held(&zldpll->lock);
> +
>   	list_for_each_entry(pin, &zldpll->pins, list) {
>   		if (zl3073x_dpll_is_input_pin(pin) &&
>   		    zl3073x_input_pin_ref_get(pin->id) == ref_id)
> @@ -132,11 +134,39 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
>   	return NULL;
>   }
>   
> +/**
> + * zl3073x_dpll_output_pin_sibling_get - get the other pin of an output pair
> + * @pin: output pin whose sibling is sought
> + *
> + * Output pin ids are allocated in P/N pairs (P even, N odd) that share a
> + * single HW output. Looks up the other pin of the pair, if it is
> + * registered as a dpll_pin on this DPLL.
> + *
> + * Return: pointer to sibling pin, or NULL if it is not registered
> + */
> +static struct zl3073x_dpll_pin *
> +zl3073x_dpll_output_pin_sibling_get(struct zl3073x_dpll_pin *pin)
> +{
> +	struct zl3073x_dpll_pin *sibling;
> +
> +	lockdep_assert_held(&pin->dpll->lock);
> +
> +	list_for_each_entry(sibling, &pin->dpll->pins, list) {
> +		if (!zl3073x_dpll_is_input_pin(sibling) &&
> +		    sibling->id == (pin->id ^ 1))
> +			return sibling;
> +	}
> +
> +	return NULL;
> +}
> +
>   static struct zl3073x_dpll_pin *
>   zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll)
>   {
>   	struct zl3073x_dpll_pin *pin;
>   
> +	lockdep_assert_held(&zldpll->lock);
> +
>   	list_for_each_entry(pin, &zldpll->pins, list) {
>   		if (zl3073x_dpll_is_nco_pin(pin))
>   			return pin;
> @@ -899,12 +929,14 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>   	struct zl3073x_dpll *zldpll = dpll_priv;
>   	struct zl3073x_dev *zldev = zldpll->dev;
>   	struct zl3073x_dpll_pin *pin = pin_priv;
> +	struct zl3073x_dpll_pin *sibling = NULL;
>   	const struct zl3073x_synth *synth;
>   	struct zl3073x_out out;
>   	u32 synth_freq;
>   	u8 out_id;
> +	int rc;
>   
> -	guard(mutex)(&zldpll->lock);
> +	mutex_lock(&zldpll->lock);
>   
>   	out_id = zl3073x_output_pin_out_get(pin->id);
>   	out = *zl3073x_out_state_get(zldev, out_id);
> @@ -913,12 +945,14 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>   	 * for N-division is also used for the esync divider so both cannot
>   	 * be used.
>   	 */
> -	if (zl3073x_out_is_ndiv(&out))
> -		return -EOPNOTSUPP;
> +	if (zl3073x_out_is_ndiv(&out)) {
> +		rc = -EOPNOTSUPP;
> +		goto unlock;
> +	}
>   
>   	if (!freq) {
>   		zl3073x_out_esync_disable(&out);
> -		return zl3073x_out_state_set(zldev, out_id, &out);
> +		goto commit;
>   	}
>   
>   	/* Get attached synth frequency */
> @@ -927,9 +961,24 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>   
>   	/* Enable 1PPS eSync for this pin frequency */
>   	zl3073x_out_esync_enable(&out, synth_freq / out.div);
> -
> +commit:
>   	/* Commit output configuration */
> -	return zl3073x_out_state_set(zldev, out_id, &out);
> +	rc = zl3073x_out_state_set(zldev, out_id, &out);
> +	if (rc)
> +		goto unlock;
> +
> +	/* The clock type, esync period and esync width are all shared by
> +	 * both pins of the output pair, so the sibling pin's esync
> +	 * configuration changes too and userspace has to be notified.
> +	 */
> +	sibling = zl3073x_dpll_output_pin_sibling_get(pin);
> +unlock:
> +	mutex_unlock(&zldpll->lock);
> +
> +	if (!rc && sibling)
> +		__dpll_pin_change_ntf(sibling->dpll_pin);
> +
> +	return rc;
>   }
>   
>   static int
> @@ -959,12 +1008,14 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>   	struct zl3073x_dpll *zldpll = dpll_priv;
>   	struct zl3073x_dev *zldev = zldpll->dev;
>   	struct zl3073x_dpll_pin *pin = pin_priv;
> +	struct zl3073x_dpll_pin *sibling = NULL;
>   	const struct zl3073x_synth *synth;
>   	u32 new_div, synth_freq;
>   	struct zl3073x_out out;
>   	u8 out_id;
> +	int rc;
>   
> -	guard(mutex)(&zldpll->lock);
> +	mutex_lock(&zldpll->lock);
>   
>   	out_id = zl3073x_output_pin_out_get(pin->id);
>   	out = *zl3073x_out_state_get(zldev, out_id);
> @@ -977,7 +1028,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>   	/* Check signal format */
>   	if (!zl3073x_out_is_ndiv(&out)) {
>   		/* For non N-divided signal formats the frequency is computed
> -		 * as division of synth frequency and output divisor.
> +		 * as division of synth frequency and output divisor, which
> +		 * is shared by both pins of the output pair.
>   		 */
>   		out.div = new_div;
>   
> @@ -1002,7 +1054,16 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>   		}
>   
>   		/* Commit output configuration */
> -		return zl3073x_out_state_set(zldev, out_id, &out);
> +		rc = zl3073x_out_state_set(zldev, out_id, &out);
> +		if (rc)
> +			goto unlock;
> +
> +		/* The other pin's frequency changed too - it has to be
> +		 * notified about the change.
> +		 */
> +		sibling = zl3073x_dpll_output_pin_sibling_get(pin);
> +
> +		goto unlock;
>   	}
>   
>   	if (zl3073x_dpll_is_p_pin(pin)) {
> @@ -1017,18 +1078,10 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>   		u32 rem;
>   
>   		out.esync_n_period = div_u64_rem(prod, new_div, &rem);
> -		if (rem != 0) {
> -			NL_SET_ERR_MSG_FMT(extack,
> -					   "OUT%uN freq must divide OUT%uP freq",
> -					   out_id, out_id);
> -			return -EINVAL;
> -		}
> -		if (out.esync_n_period < 2) {
> -			NL_SET_ERR_MSG_FMT(extack,
> -					   "OUT%uN freq must be less than OUT%uP freq",
> -					   out_id, out_id);
> -			return -EINVAL;
> -		}
> +		if (rem != 0)
> +			goto err_n_nondiv;
> +		if (out.esync_n_period < 2)
> +			goto err_n_toohigh;
>   
>   		/* Update the output divisor */
>   		out.div = new_div;
> @@ -1046,25 +1099,36 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>   		u64 rem, prod = frequency * out.div;
>   
>   		out.esync_n_period = div64_u64_rem(synth_freq, prod, &rem);
> -		if (rem != 0) {
> -			NL_SET_ERR_MSG_FMT(extack,
> -					   "OUT%uN freq must divide OUT%uP freq",
> -					   out_id, out_id);
> -			return -EINVAL;
> -		}
> -		if (out.esync_n_period < 2) {
> -			NL_SET_ERR_MSG_FMT(extack,
> -					   "OUT%uN freq must be less than OUT%uP freq",
> -					   out_id, out_id);
> -			return -EINVAL;
> -		}
> +		if (rem != 0)
> +			goto err_n_nondiv;
> +		if (out.esync_n_period < 2)
> +			goto err_n_toohigh;
>   	}
>   
>   	/* For 50/50 duty cycle the divisor is equal to width */
>   	out.esync_n_width = out.esync_n_period;
>   
>   	/* Commit output configuration */
> -	return zl3073x_out_state_set(zldev, out_id, &out);
> +	rc = zl3073x_out_state_set(zldev, out_id, &out);
> +unlock:
> +	mutex_unlock(&zldpll->lock);
> +
> +	if (!rc && sibling)
> +		__dpll_pin_change_ntf(sibling->dpll_pin);
> +
> +	return rc;
> +err_n_nondiv:
> +	NL_SET_ERR_MSG_FMT(extack,
> +			   "OUT%uN freq must divide OUT%uP freq",
> +			   out_id, out_id);
> +	rc = -EINVAL;
> +	goto unlock;
> +err_n_toohigh:
> +	NL_SET_ERR_MSG_FMT(extack,
> +			   "OUT%uN freq must be less than OUT%uP freq",
> +			   out_id, out_id);
> +	rc = -EINVAL;
> +	goto unlock;
>   }
>   
>   static int
> @@ -1103,10 +1167,12 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
>   	struct zl3073x_dpll *zldpll = dpll_priv;
>   	struct zl3073x_dev *zldev = zldpll->dev;
>   	struct zl3073x_dpll_pin *pin = pin_priv;
> +	struct zl3073x_dpll_pin *sibling = NULL;
>   	struct zl3073x_out out;
>   	u8 out_id;
> +	int rc;
>   
> -	guard(mutex)(&zldpll->lock);
> +	mutex_lock(&zldpll->lock);
>   
>   	out_id = zl3073x_output_pin_out_get(pin->id);
>   	out = *zl3073x_out_state_get(zldev, out_id);
> @@ -1115,7 +1181,21 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
>   	out.phase_comp = phase_adjust / pin->phase_gran;
>   
>   	/* Update output configuration from mailbox */
> -	return zl3073x_out_state_set(zldev, out_id, &out);
> +	rc = zl3073x_out_state_set(zldev, out_id, &out);
> +	if (rc)
> +		goto unlock;
> +
> +	/* The phase compensation register is shared by both pins of the
> +	 * output pair, so the sibling pin's phase adjustment changes too.
> +	 */
> +	sibling = zl3073x_dpll_output_pin_sibling_get(pin);
> +unlock:
> +	mutex_unlock(&zldpll->lock);
> +
> +	if (!rc && sibling)
> +		__dpll_pin_change_ntf(sibling->dpll_pin);
> +
> +	return rc;
>   }
>   
>   static int
> @@ -1739,6 +1819,13 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
>   	else
>   		ops = &zl3073x_dpll_output_pin_ops;
>   
> +	/* Add the pin to the list before registering it with the DPLL core so
> +	 * that it is findable as a sibling as soon as the core publishes it.
> +	 */
> +	mutex_lock(&zldpll->lock);
> +	list_add(&pin->list, &zldpll->pins);
> +	mutex_unlock(&zldpll->lock);
> +
>   	/* Register the pin */
>   	rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, ops, pin);
>   	if (rc)
> @@ -1750,6 +1837,9 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
>   	return 0;
>   
>   err_register:
> +	mutex_lock(&zldpll->lock);
> +	list_del(&pin->list);
> +	mutex_unlock(&zldpll->lock);
>   	dpll_pin_put(pin->dpll_pin, &pin->tracker);
>   err_pin_get:
>   	pin->dpll_pin = NULL;
> @@ -1784,6 +1874,13 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
>   	/* Unregister the pin */
>   	dpll_pin_unregister(zldpll->dpll_dev, pin->dpll_pin, ops, pin);
>   
> +	/* Remove the pin from the list only after it has been unregistered so
> +	 * that a still-registered pin is always findable as a sibling.
> +	 */
> +	mutex_lock(&zldpll->lock);
> +	list_del(&pin->list);
> +	mutex_unlock(&zldpll->lock);
> +
>   	dpll_pin_put(pin->dpll_pin, &pin->tracker);
>   	pin->dpll_pin = NULL;
>   
> @@ -1803,9 +1900,11 @@ zl3073x_dpll_pins_unregister(struct zl3073x_dpll *zldpll)
>   {
>   	struct zl3073x_dpll_pin *pin, *next;
>   
> +	/* Unregister each pin before removing it from the list so that a
> +	 * still-registered pin is always findable as a sibling.
> +	 */
>   	list_for_each_entry_safe(pin, next, &zldpll->pins, list) {
>   		zl3073x_dpll_pin_unregister(pin);
> -		list_del(&pin->list);
>   		zl3073x_dpll_pin_free(pin);
>   	}
>   }
> @@ -1918,16 +2017,24 @@ zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll)
>   		goto err_pin_get;
>   	}
>   
> +	/* Add the pin to the list before registering it with the DPLL core so
> +	 * that the list reflects the DPLL registration state.
> +	 */
> +	mutex_lock(&zldpll->lock);
> +	list_add(&pin->list, &zldpll->pins);
> +	mutex_unlock(&zldpll->lock);
> +
>   	rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin,
>   			       &zl3073x_dpll_nco_pin_ops, pin);
>   	if (rc)
>   		goto err_register;
>   
> -	list_add(&pin->list, &zldpll->pins);
> -
>   	return 0;
>   
>   err_register:
> +	mutex_lock(&zldpll->lock);
> +	list_del(&pin->list);
> +	mutex_unlock(&zldpll->lock);
>   	dpll_pin_put(pin->dpll_pin, &pin->tracker);
>   err_pin_get:
>   	pin->dpll_pin = NULL;
> @@ -1979,8 +2086,6 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
>   			zl3073x_dpll_pin_free(pin);
>   			goto error;
>   		}
> -
> -		list_add(&pin->list, &zldpll->pins);
>   	}
>   
>   	/* Register NCO virtual input pin */

Reviewed-by: Petr Oros <poros@redhat.com>


      reply	other threads:[~2026-10-07 18:55 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06 15:31 [PATCH net v3 0/4] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
2026-10-06 15:31 ` [PATCH net v3 1/4] dpll: zl3073x: reject output frequencies with too small divisor Ivan Vecera
2026-10-07 18:51   ` Petr Oros
2026-10-06 15:31 ` [PATCH net v3 2/4] dpll: zl3073x: make embedded sync follow the output carrier frequency Ivan Vecera
2026-10-07 18:54   ` Petr Oros
2026-10-06 15:31 ` [PATCH net v3 3/4] dpll: zl3073x: reject inexact frequencies for N-divided outputs Ivan Vecera
2026-10-07 18:55   ` Petr Oros
2026-10-06 15:31 ` [PATCH net v3 4/4] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
2026-10-07 18:55   ` Petr Oros [this message]

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=2a263f32-82a9-42b7-8aeb-8ac51a801fd9@redhat.com \
    --to=poros@redhat.com \
    --cc=Prathosh.Satish@microchip.com \
    --cc=arkadiusz.kubalewski@intel.com \
    --cc=ivecera@redhat.com \
    --cc=jiri@resnulli.us \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=min.li@microchip.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=vadim.fedorenko@linux.dev \
    /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

all inboxes | Powered by JetHome®