From: Ivan Vecera <ivecera@redhat.com>
To: netdev-bot+sashiko@kernel.org, Jakub Kicinski <kuba@kernel.org>
Cc: netdev@vger.kernel.org, Chris.duQuesnay@microchip.com,
arkadiusz.kubalewski@intel.com, jiri@resnulli.us,
min.li@microchip.com, pabeni@redhat.com, poros@redhat.com,
richardcochran@gmail.com, vadim.fedorenko@linux.dev,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes
Date: Wed, 30 Sep 2026 09:49:21 +0200 [thread overview]
Message-ID: <d631d2ed-63b5-4785-80ae-b16deb3edd1a@redhat.com> (raw)
In-Reply-To: <179075143120.434549.1576015918203645155@kernel.org>
On 9/30/26 8:57 AM, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider.
>
> Critical: 0 · High: 1 · Medium: 1 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] The patch adds zl3073x_dpll_output_pin_sibling_get()
> (drivers/dpll/zl3073x/dpll.c:146).
> - [Medium] The commit says it closes the case where the sibling pin's
> frequency changes without a notification.
> - [Low] The commit message presents the missing sibling notification as a
> user-visible defect: userspace listening on the sibling pin "is never…
>
> --
>
> dpll: zl3073x: notify sibling pin when shared output config changes
>
> This patch adds zl3073x_dpll_output_pin_sibling_get(), which finds the
> other pin of a P/N output pair. The esync, frequency and phase adjust
> setters use it to send a pin change notification for the sibling after
> the shared output registers are written, and they now use explicit
> mutex_lock()/mutex_unlock() so the notification goes out after
> zldpll->lock is dropped.
>
>> 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.
>
> [Severity: Low]
> This describes a user-visible defect, but the trailers only have
> Tested-by: and Signed-off-by:.
>
> The dpll core only calls __dpll_pin_change_ntf() for the pin in the
> request (for example in dpll_pin_freq_set()). The sibling has not been
> notified since the output setters were added.
>
> Should this carry Fixes: tags such as these?
>
> 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")
Agreed, this is a pre-existing user-visible defect. I will add the three
Fixes: tags in v2:
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")
Jakub, a process question: this patch is both the fix and a prerequisite
for the rest of the series - it introduces
zl3073x_dpll_output_pin_sibling_get() and the notify-outside-lock
handling that the output enable/disable and PTP perout patches build on.
The defect itself is only a missing change notification (no crash, no
data corruption). Is it OK to keep it as patch 1 of this net-next series,
or would you prefer it split out and sent to net first?
>> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
>> index f2e137475b40..2c6de4dab8b4 100644
>> --- a/drivers/dpll/zl3073x/dpll.c
>> +++ b/drivers/dpll/zl3073x/dpll.c
>> @@ -132,6 +132,30 @@ 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;
>> +
>> + 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;
>> +}
>> +
>
> [ ... ]
>
>> @@ -934,27 +962,44 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>
> [ ... ]
>
>> + /* Commit output configuration */
>> + 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.
>> */
>> - out.esync_n_width = out.div / 2;
>> + sibling = zl3073x_dpll_output_pin_sibling_get(pin);
>>
>> - /* Commit output configuration */
>> - return zl3073x_out_state_set(zldev, out_id, &out);
>> + mutex_unlock(&zldpll->lock);
>> +
>> + if (sibling)
>> + __dpll_pin_change_ntf(sibling->dpll_pin);
>
> [Severity: High]
> Can sibling->dpll_pin be NULL, or the sibling already freed, by the time
> it is dereferenced here?
>
> zl3073x_dpll_output_pin_sibling_get() returns any output pin on
> zldpll->pins with the matching id. It does not check that
> sibling->dpll_pin is still set. The lookup runs under zldpll->lock, but
> the dereference happens after mutex_unlock().
>
> The teardown path does not take zldpll->lock:
>
> drivers/dpll/zl3073x/dpll.c:zl3073x_dpll_pins_unregister() {
> list_for_each_entry_safe(pin, next, &zldpll->pins, list) {
> zl3073x_dpll_pin_unregister(pin);
> list_del(&pin->list);
> zl3073x_dpll_pin_free(pin);
> }
> }
>
> zl3073x_dpll_pin_unregister() ends with:
>
> dpll_pin_put(pin->dpll_pin, &pin->tracker);
> pin->dpll_pin = NULL;
>
> and dpll_pin_put() has already dropped dpll_lock at that point.
>
> zl3073x_dpll_pins_register() uses list_add(), so the N pin (2k+1) sits
> before its P pin (2k) on the list. That means N is torn down while P is
> still registered and reachable over netlink.
>
> One possible interleaving:
>
> CPU1 (devlink reload, flash_prepare or unbind)
> zl3073x_dev_stop()
> zl3073x_dpll_unregister()
> zl3073x_dpll_pins_unregister()
> dpll_pin_put(N) /* dpll_lock released */
>
> CPU0 (DPLL_CMD_PIN_SET on P, dpll_lock held)
> dpll_pin_freq_set()
> zl3073x_dpll_output_pin_frequency_set(P)
> mutex_lock(&zldpll->lock)
> sibling = zl3073x_dpll_output_pin_sibling_get(P) /* finds N */
> mutex_unlock(&zldpll->lock)
>
> CPU1
> N->dpll_pin = NULL
> list_del(&N->list)
> zl3073x_dpll_pin_free(N)
>
> CPU0
> __dpll_pin_change_ntf(sibling->dpll_pin)
>
> In that case, would __dpll_pin_change_ntf() get NULL and oops on
> pin->id and pin->clock_id in dpll_pin_notify()? Or would CPU0 read the
> freed zl3073x_dpll_pin?
>
> The list walk in zl3073x_dpll_output_pin_sibling_get() can also run
> while list_del() is in progress, because the writer never takes
> zldpll->lock.
>
> zl3073x_dpll_output_pin_frequency_set() and
> zl3073x_dpll_output_pin_phase_adjust_set() have the same pattern.
>
> The later patch "dpll: zl3073x: add PTP periodic output support" also
> calls zl3073x_dpll_output_pin_sibling_get() from
> zl3073x_dpll_ptp_enable() without holding zldpll->lock at all.
This cannot happen; the notification is already serialized against pin
teardown.
The output setters (esync_set / frequency_set / phase_adjust_set) run
with dpll_lock held: the dpll core invokes the pin ops under dpll_lock,
and they use __dpll_pin_change_ntf(), whose contract is exactly "caller
must hold dpll_lock" (dpll_netlink.c: lockdep_assert_held(&dpll_lock),
"suitable for use inside pin callbacks which are already invoked under
dpll_lock"). So sibling_get() and the __dpll_pin_change_ntf() call - even
though it runs after mutex_unlock(&zldpll->lock) - are all covered by
dpll_lock.
Pin teardown frees the sibling via zl3073x_dpll_pin_unregister() ->
dpll_pin_unregister(), which takes dpll_lock. While a setter holds
dpll_lock for the whole callback, teardown cannot even reach
dpll_pin_unregister(N), let alone the following list_del()/kfree(). The
proposed interleaving (CPU1 freeing N while CPU0 notifies) is therefore
impossible: CPU0 holds dpll_lock throughout.
The PTP enable() path (zl3073x_dpll_ptp_enable(), added in patch 6) does
call sibling_get() outside dpll_lock, but it is serialized differently:
zl3073x_dpll_unregister() unregisters the PTP clock *before* the pins
(zl3073x_dpll_ptp_unregister() then zl3073x_dpll_pins_unregister()), and
ptp_clock_unregister() quiesces in-flight enable() callbacks. No
enable() can run while the pins are being freed.
>
> [ ... ]
>
>> @@ -1010,7 +1058,16 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>> out.width = new_div;
>>
>> /* 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)) {
>> @@ -1022,8 +1079,10 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>> * Update divisor for N-pin to keep N-pin frequency.
>> */
>> out.esync_n_period = (out.esync_n_period * out.div) / new_div;
>
> [Severity: Medium]
> Does the N pin also need a notification on this path?
>
> In the N-div formats OUTPUT_DIV is still shared. The N-pin frequency is
> computed from both values:
>
> drivers/dpll/zl3073x/core.h:zl3073x_dev_output_pin_freq_get() {
> ...
> freq = zl3073x_synth_freq_get(synth) / out->div;
>
> if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id))
> freq /= out->esync_n_period;
> ...
> }
>
> The rescale above truncates and only rejects 0. zl3073x_pin_check_freq()
> only requires the new P frequency to divide the synth frequency.
>
> For example, take a 500 MHz synth with P = 10 MHz (div 50) and
> N = 2 MHz (period 5). Setting P to 25 MHz gives new_div 20 and period
> 250 / 20 = 12. N then becomes 500M / (20 * 12), about 2.083 MHz.
>
> This path reaches the unlock label with sibling still NULL, so the N
> pin gets no DPLL_CMD_PIN_CHANGE_NTF. The commit message says the sibling
> is notified "whenever the shared HW state actually changes". Should this
> case be covered too?
>
> The later commit "dpll: zl3073x: factor out output pin frequency helper"
> does not change this. The truncation moves into
> zl3073x_dpll_output_pin_freq_set(), and frequency_set still calls
> zl3073x_dpll_output_pin_sibling_get() only when
> !zl3073x_out_is_ndiv(&out).
By design the N-pin frequency is meant to be preserved when the P-pin
frequency changes - that is the purpose of rescaling esync_n_period. With
an exact rescale, div_new * period_new == div_old * period_old, so
N = synth / (div * period) is unchanged and no sibling notification is
needed.
The only thing that can actually perturb N here is the truncation in that
rescale, which is the separate issue Sashiko raised on patch 5 ("does this
rescale really keep the N-pin frequency unchanged?"). I will fix it there
by rejecting an inexact rescale with -EINVAL (i.e. when div_old * period_old
is not divisible by div_new), so N is guaranteed to stay unchanged. With that
fix there is nothing to notify on this path, so no sibling notification is added
here.
Thanks,
Ivan
next prev parent reply other threads:[~2026-09-30 7:49 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-09-30 7:49 ` Ivan Vecera [this message]
2026-09-30 14:24 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-09-30 8:28 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling " Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-09-30 10:13 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 4/6] dpll: zl3073x: consolidate pin capabilities into bitmap Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 5/6] dpll: zl3073x: factor out output pin frequency helper Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-09-30 10:43 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
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=d631d2ed-63b5-4785-80ae-b16deb3edd1a@redhat.com \
--to=ivecera@redhat.com \
--cc=Chris.duQuesnay@microchip.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=jiri@resnulli.us \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=min.li@microchip.com \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=poros@redhat.com \
--cc=richardcochran@gmail.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®