Re: [PATCH net v2 2/2] dpll: zl3073x: notify sibling pin when shared output config changes

From: Ivan Vecera

Date: Tue Oct 06 2026 - 11:19:51 EST


On 10/6/26 10:08, netdev-bot+sashiko@xxxxxxxxxx wrote:
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 1 · Low: 0

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] In zl3073x_dpll_output_pin_frequency_set()
(drivers/dpll/zl3073x/dpll.c), the N-div case is excluded from the new…
- [Medium] Pins are on zldpll->pins only for part of the time they are
registered with the DPLL core, so the new sibling lookup can miss a…

--

dpll: zl3073x: notify sibling pin when shared output config changes

This commit adds zl3073x_dpll_output_pin_sibling_get(). The output pin
frequency, esync and phase adjust setters use it to send a change
notification for the other pin of a P/N output pair. The setters move from
guard(mutex) to explicit lock/unlock so the notification is sent after
zldpll->lock is dropped, and all accesses to zldpll->pins are now
serialized with zldpll->lock.

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 19456fbeeea84..b60d3280574ad 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c

[ ... ]

@@ -1001,7 +1053,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)) {
@@ -1013,8 +1074,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;
- if (!out.esync_n_period)
- return -EINVAL;
+ if (!out.esync_n_period) {
+ rc = -EINVAL;
+ goto unlock;
+ }
/* Update the output divisor */
out.div = new_div;

[Severity: High]
Can the N-pin's frequency still change on this path without a
notification?

In N-div mode, zl3073x_dev_output_pin_freq_get() computes the N-pin
frequency from both the shared divisor and esync_n_period:

freq = zl3073x_synth_freq_get(synth) / out->div;

if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id))
freq /= out->esync_n_period;

When the P-pin frequency is set, the esync_n_period rescale above
truncates whenever old_period * old_div is not a multiple of new_div.

For example, take a 1 GHz synth with div = 10 (P = 100 MHz) and period = 4
(N = 25 MHz). Setting P to 40 MHz gives new_div = 25 and period = 40 / 25 =
1, so N becomes 40 MHz. Both frequencies pass zl3073x_pin_check_freq()
because they divide the synth frequency.

zl3073x_out_state_set() then commits the new DIV and ESYNC_PERIOD values.
However, sibling is only assigned in the non-N-div branch, so it is still
NULL at the tail of the function:

if (!rc && sibling)
__dpll_pin_change_ntf(sibling->dpll_pin);

No notification is sent for the N-pin, even though it is registered in
N-div mode.

The truncation itself predates this patch. The missing notification,
though, is the case this patch sets out to fix. The commit message says:

share a single HW output and, outside N-pin divide mode, share the
output's divisor, ...

and:

... to notify the sibling pin, if it is registered, whenever the
shared HW state actually changes.

Is that accurate, given that the divisor is also part of the N-pin
frequency in N-div mode?

Should this branch either notify the N-pin when its effective frequency
changes, or reject a rescale that is not exact?

I will take the second option and reject a rescale that is not exact. For
the next version I plan to add a preparatory patch ("dpll: zl3073x:
reject inexact frequencies for N-divided outputs"), ordered before this
notification patch, that changes the P-pin branch to

prod = esync_n_period * div;
esync_n_period = div_u64_rem(prod, new_div, &rem);
if (rem) -> reject (-EINVAL)
if (esync_n_period < 2) -> reject (-EINVAL)

so the request is refused whenever the rescale is not exact. When the
P-pin set succeeds, the N-pin frequency is

synth / (new_div * new_period)
= synth / (new_div * (old_period * old_div / new_div))
= synth / (old_div * old_period)

i.e. exactly the old N-pin frequency. So on this path the N-pin's
effective frequency never changes and no notification is required. With
that patch in front, the claim in this commit message ("to notify the
sibling pin ... whenever the shared HW state actually changes") holds,
and I will adjust the wording to make the N-div case explicit.

[ ... ]

@@ -1954,7 +2049,9 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
goto error;
}
+ mutex_lock(&zldpll->lock);
list_add(&pin->list, &zldpll->pins);
+ mutex_unlock(&zldpll->lock);
}

[Severity: Medium]
Can zl3073x_dpll_output_pin_sibling_get() miss a sibling that is already
registered with the DPLL core?

zl3073x_dpll_pin_register()->dpll_pin_register() publishes the pin. It
takes and drops dpll_lock and sends PIN_CREATE before the pin is added to
zldpll->pins here. P (even id) is registered before N (odd id), so this
window exists:

CPU1 (probe)
zl3073x_dpll_pins_register()
zl3073x_dpll_pin_register(N)
dpll_pin_register()
N is now visible, PIN_CREATE sent

CPU2 (netlink PIN_SET on P, dpll_lock held)
zl3073x_dpll_output_pin_phase_adjust_set()
zl3073x_out_state_set()
zl3073x_dpll_output_pin_sibling_get()
N is not on zldpll->pins yet, returns NULL

CPU1
list_add(&pin->list, &zldpll->pins);

At that point userspace still has the CREATE values for N, but the shared
div, esync or phase_comp has already changed, and no change notification
is sent for N.

Teardown has the opposite window. zl3073x_dpll_pins_unregister() empties
the list before any dpll_pin_unregister() call:

mutex_lock(&zldpll->lock);
list_splice_init(&zldpll->pins, &pin_list);
mutex_unlock(&zldpll->lock);

A PIN_SET on a P-pin that is still registered therefore also skips an
N-pin that is still registered. A DELETE notification follows in that
case.

This cannot cause a use-after-free. The sibling is freed only after
dpll_pin_unregister(), which needs dpll_lock, and the PIN_SET path holds
dpll_lock.

However, list membership does not match DPLL registration state, so the
notification this patch adds can still be lost. Should the lookup be tied
to registration state instead of list membership?

You are right, and I will tie the list membership to the registration
state. For the next version I plan to:

- add the pin to zldpll->pins *before* dpll_pin_register() (removing it
again on a registration failure), and
- remove it from zldpll->pins only *after* dpll_pin_unregister(),
instead of splicing the whole list away before unregistering any pin,

both under zldpll->lock. That closes both windows you described: a pin
that is registered - and thus reachable by a PIN_SET on its sibling - is
always present on the list, so the lookup will find it.

The opposite transient state (a pin on the list that is not yet, or no
longer, registered) is harmless:

- __dpll_pin_change_ntf() is a no-op for such a pin, because
dpll_pin_event_send() bails out on !dpll_pin_available(), and
- there is no use-after-free, as you also noted: the sibling is freed
only after dpll_pin_unregister(), which takes dpll_lock, and the
PIN_SET path holds dpll_lock across the lookup and the notification.

Thanks,
Ivan