From: netdev-bot+sashiko@kernel.org
To: arouhi@sitime.com
Cc: jiri@resnulli.us, vadim.fedorenko@linux.dev,
arkadiusz.kubalewski@intel.com, ivecera@redhat.com,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
cjubran@nvidia.com, pabeni@redhat.com,
Oleg.Zadorozhnyi@devoxsoftware.com, devicetree@vger.kernel.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
kuba@kernel.org
Subject: Re: [PATCH net-next v9 07/15] dpll: sit9531x: add support to get and set priority on input pins
Date: Thu, 17 Sep 2026 15:01:49 +0000 [thread overview]
Message-ID: <178965730954.22033.15674954043478310598@kernel.org> (raw)
In-Reply-To: <20260915000015.80480-8-arouhi@sitime.com>
Thank you for your contribution! Sashiko AI review found 7 potential
issue(s) to consider:
Critical: 0 · High: 2 · Medium: 2 · Low: 3
- [High] Self-deadlock: sit9531x_dpll_input_pin_prio_set()
(drivers/dpll/sit9531x/dpll.c) ends with `while (changed_cnt--)…
- [High] Use-after-free / NULL dereference window:
sit9531x_dpll_input_pin_prio_set() is the first pin op that walks the…
- [Medium] sit9531x_input_prio_get() (drivers/dpll/sit9531x/core.c)
collapses the documented 'absent' encoding (chan->prio_last[src] == 0,…
- [Medium] Silent no-op priority set: sit9531x_input_prio_set()
(drivers/dpll/sit9531x/core.c:671) locates only the FIRST slot holding…
- [Low] The new struct member sit9531x_chan.prio_srcs[] (core.h:152) is
written by sit9531x_prio_mask_build() ('chan->prio_srcs[slot] = src;')…
- [Low] sit9531x_dpll_input_pin_prio_set() notifies the pin the request
was made for, in addition to the shifted siblings: the traversal…
- [Low] sit9531x_prio_table_commit() rebuilds the newly extended cache
(chan->prio_srcs / chan->prio_last, alongside prio_mask) from the…
--
dpll: sit9531x: add support to get and set priority on input pins
This adds .prio_get and .prio_set to the sit9531x input pin ops, backed by a
new cached view of the hardware priority table (prio_srcs[] and prio_last[])
that is refreshed whenever the table is written or polled. Getting a
priority looks up the cached slot for the pin's source code; setting one
moves the source to the requested slot through sit9531x_input_prio_set().
A few questions below.
> diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
> index 25418485e9757..484c94def578c 100644
> --- a/drivers/dpll/sit9531x/core.c
> +++ b/drivers/dpll/sit9531x/core.c
> @@ -521,21 +521,81 @@ bool sit9531x_input_prio_present(struct sit9531x_dev *sitdev, u8 pll_idx,
> }
>
> /*
> - * Rebuild a PLL's membership mask from the source codes of its priority
> - * table. The mask is what the pin state getters test, so it is refreshed
> - * from exactly the values the table holds -- here after a write, and once
> - * per poll from the read-back in sit9531x_chan_state_fetch().
> + * sit9531x_input_prio_get - read an input's priority slot for a PLL
> + * @input_idx: input source in hardware encoding (see
> + * sit9531x_input_hw_src())
> + * @prio: output slot position (0 = highest)
> + *
> + * Reports the last slot this source occupied on this PLL. The value is
> + * cached from the hardware table read at startup and refreshed after every
> + * table write and poll read-back, so pin-get reflects hardware state without
> + * issuing synchronous register reads per pin. A source with no known slot
> + * falls back to the lowest-priority valid slot.
> + *
> + * Caller must hold sitdev->multiop_lock.
> + */
> +int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx,
> + u8 input_idx, u8 *prio)
> +{
> + const struct sit9531x_chan *chan;
> + u8 slot;
> +
> + lockdep_assert_held(&sitdev->multiop_lock);
> +
> + if (pll_idx >= SIT9531X_NUM_PLLS)
> + return -EINVAL;
> + input_idx = sit9531x_prio_src_canon(sitdev, input_idx);
> + if (input_idx >= SIT9531X_PRIO_NUM_SRC)
> + return -EINVAL;
> +
> + chan = &sitdev->chan[pll_idx];
> + slot = chan->prio_last[input_idx];
> + if (!slot)
> + slot = SIT9531X_PRIO_MAX_SLOTS;
> +
> + *prio = slot - 1;
> +
> + return 0;
> +}
[Severity: Medium]
The commit message says:
"An input absent from the table has no priority to report, and the core is
told so rather than handed a made-up number."
Does sit9531x_input_prio_get() actually tell the core that? The absent
encoding documented for prio_last[] in core.h (0 = the source is not in the
table) is turned into SIT9531X_PRIO_MAX_SLOTS and the function returns 0:
slot = chan->prio_last[input_idx];
if (!slot)
slot = SIT9531X_PRIO_MAX_SLOTS;
*prio = slot - 1;
Since SIT9531X_PRIO_MAX_SLOTS is 11, *prio becomes 10, which is a valid
slot. dpll_msg_add_pin_prio() puts whatever prio_get returns into
DPLL_A_PIN_PRIO, and dpll_msg_add_pin_dplls() calls it for every pin/dpll
reference regardless of connection state.
Can userspace then tell "input not in the priority table" apart from "input
programmed at slot 10"?
The value also does not round-trip: writing 10 back reaches
sit9531x_input_prio_set(), which returns -EINVAL when the source is not
found in the table, so
sit9531x_dpll_input_pin_prio_set() answers with "Pin is not a reference of
this DPLL; connect it first".
Would it be clearer either to return an error from prio_get for an absent
source, or to reword the changelog to match the fallback the code
implements?
> +
> +/*
> + * Refresh a PLL's cached view of its priority table from the source codes
> + * the table holds -- here after a write, and once per poll from the
> + * read-back in sit9531x_chan_state_fetch().
[Severity: Low]
sit9531x_prio_table_commit() calls sit9531x_prio_mask_build() with the
intended srcs[] array even when a slot read or write failed mid-sequence
(the "goto commit" path). With this patch prio_last[] becomes the source of
truth for the priority reported by the new .prio_get op, so a get issued
after a failed set can report a slot the device never accepted, and after a
partial shift-write the device may hold the source in two slots and select
it at a higher priority than the driver reports.
The existing comment at the rebuild site documents this choice, and
sit9531x_chan_state_fetch() calls sit9531x_prio_table_read() plus
sit9531x_prio_mask_build() on every poll, so the window is bounded by the
poll period. Is it worth noting in the kernel-doc for
sit9531x_input_prio_get() that the cached slot may be an intended rather
than a confirmed one until the next poll?
> + *
> + * The membership mask is what the pin state getters test; the per-slot
> + * copy and the last-slot-seen array are what priority get answers from,
> + * so neither costs a register read per pin.
> */
> static void sit9531x_prio_mask_build(struct sit9531x_dev *sitdev, u8 pll_idx,
> const u8 *srcs)
> {
> + struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
> + u8 first[SIT9531X_PRIO_NUM_SRC] = { 0 };
> u16 mask = 0;
> - u8 slot;
> + u8 slot, src, src_canon;
>
> - for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++)
> - mask |= BIT(srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK);
> + for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
> + src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
> + chan->prio_srcs[slot] = src;
[ ... ]
> diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
> index f628bd5b7a975..1ab8ebfd78da1 100644
> --- a/drivers/dpll/sit9531x/core.h
> +++ b/drivers/dpll/sit9531x/core.h
> @@ -129,6 +129,12 @@ struct sit9531x_out {
> * @ho_freeze: holdover freeze active
> * @ho_valid: holdover memory acquired, i.e. the holdover window
> * holds a valid estimate to fall back on
> + * @prio_srcs: cached copy of the priority table, one source code
> + * per slot; refreshed together with @prio_mask, so
> + * priority reads generate no register traffic
> + * @prio_last: slot each source occupies, plus one (0 = the source
> + * is not in the table); refreshed from the same scan
> + * as @prio_mask, so the two never disagree
[ ... ]
> @@ -143,6 +149,8 @@ struct sit9531x_chan {
> bool inner_lol;
> bool ho_freeze;
> bool ho_valid;
> + u8 prio_srcs[SIT9531X_PRIO_MAX_SLOTS];
[Severity: Low]
Is prio_srcs[] read anywhere? Grepping drivers/dpll/sit9531x/ turns up only
this declaration and the single store in sit9531x_prio_mask_build()
("chan->prio_srcs[slot] = src;"), both at this commit and at the end of the
series. sit9531x_input_prio_get() reads prio_last[] only.
The kernel-doc above ("so priority reads generate no register traffic") and
the comment on sit9531x_prio_mask_build() ("the per-slot copy and the
last-slot-seen array are what priority get answers from") describe a
consumer that does not exist. Was a user of this array dropped, or can the
member and those comment lines go?
> + u8 prio_last[SIT9531X_PRIO_NUM_SRC];
> u16 prio_mask;
> };
>
> diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
> index ab9aa7db1839c..e2ff21adfdb23 100644
> --- a/drivers/dpll/sit9531x/dpll.c
> +++ b/drivers/dpll/sit9531x/dpll.c
> @@ -469,10 +469,112 @@ sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin,
> return rc;
> }
>
[ ... ]
> +/*
> + * sit9531x_dpll_input_pin_prio_set - set input pin priority
> + *
> + * writes input priority table on Page 1 via
> + * core.c sit9531x_input_prio_set(). Forces holdover during update.
> + */
> +static int
> +sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,
> + const struct dpll_device *dpll, void *dpll_priv,
> + u32 prio, struct netlink_ext_ack *extack)
> +{
> + struct dpll_pin *changed[SIT9531X_MAX_INPUTS + 1];
> + struct sit9531x_dpll_pin *sibling;
> + struct sit9531x_dpll_pin *dpin = pin_priv;
> + struct sit9531x_dpll *sitdpll = dpll_priv;
> + struct sit9531x_dev *sitdev = sitdpll->dev;
> + u8 changed_cnt = 0, hw_src, slot;
> + int get_rc, rc;
[ ... ]
> + mutex_lock(&sitdev->multiop_lock);
> + rc = sit9531x_input_prio_set(sitdev, sitdpll->id,
> + sit9531x_input_hw_src(dpin->id),
> + (u8)prio);
[Severity: Medium]
This is the first caller of sit9531x_input_prio_set(); in the parent commit
the helper has none. Can a set through it report success without changing
the effective priority?
sit9531x_input_prio_set() locates only the first slot holding the source:
for (from = 0; from < SIT9531X_PRIO_MAX_SLOTS; from++)
if (sit9531x_prio_src_canon(sitdev, srcs[from]) == input_idx)
break;
...
srcs[prio] = input_idx;
if (!memcmp(srcs, orig_srcs, sizeof(srcs)))
return 0;
Duplicated source codes are the driver's own representation of an unused
slot -- sit9531x_input_prio_remove() backfills the freed tail:
/* Backfill freed tail slots with the lowest-priority remaining src */
while (count < SIT9531X_PRIO_MAX_SLOTS) {
kept[count] = kept[count - 1];
count++;
}
So for a table like [A,B,B,B,...,B], moving B down to slot 5 shifts
duplicates onto each other, the array comes out bit-identical, the memcmp
shortcut returns 0 and no register is written. The device keeps selecting B
at its old, higher slot, while sit9531x_prio_mask_build() records the first
occurrence, so a following get reports the old slot and not the one just
written.
The changelog says "setting it moves the input to the requested slot" --
should sit9531x_input_prio_set() clear or account for the other slots
holding the same source code?
> + if (!rc) {
> + list_for_each_entry(sibling, &sitdpll->pins, list) {
> + if (!sit9531x_dpll_is_input_pin(sibling) ||
> + sit9531x_dpll_is_xo_pin(sibling))
> + continue;
[Severity: High]
This is the first pin op that walks the other pins of the DPLL, and
sibling->dpll_pin is collected into changed[] without a NULL check. Can it
race with teardown?
sit9531x_dpll_pins_unregister() in core.c does, per pin:
list_for_each_entry_safe(pin, next, &sitdpll->pins, list) {
sit9531x_dpll_pin_unregister(pin);
list_del(&pin->list);
kfree(pin);
}
sit9531x_dpll_pin_unregister() calls dpll_pin_unregister(), which takes and
then drops dpll_lock:
drivers/dpll/dpll_core.c:dpll_pin_unregister() {
mutex_lock(&dpll_lock);
__dpll_pin_unregister(dpll, pin, ops, priv, NULL);
mutex_unlock(&dpll_lock);
}
and then sets pin->dpll_pin = NULL, while list_del() and kfree() run with
dpll_lock dropped. In that gap a netlink pin-set on a still-registered
sibling holds dpll_lock and walks &sitdpll->pins, so it can store a NULL
dpll_pin into changed[] and later hand it to dpll_pin_change_ntf() ->
__dpll_pin_change_ntf() -> dpll_pin_notify(pin, pin->clock_id, ...), or
follow a list node that is being list_del()'d and kfree()'d.
The pre-existing poll worker does a similar traversal, but
kthread_cancel_delayed_work_sync() in sit9531x_dev_stop() runs before any
unregister. Is anything serializing this new traversal against
sit9531x_dpll_pins_unregister()?
> +
> + hw_src = sit9531x_input_hw_src(sibling->id);
> + get_rc = sit9531x_input_prio_get(sitdev, sitdpll->id, hw_src, &slot);
> + if (get_rc)
> + continue;
> +
> + if (sibling->prio == slot)
> + continue;
> +
> + sibling->prio = slot;
> + if (changed_cnt < ARRAY_SIZE(changed))
> + changed[changed_cnt++] = sibling->dpll_pin;
[Severity: Low]
The traversal filter skips only non-input and XO pins, so dpin itself is
collected here too. The core already emits a notification for that pin
after a successful callback:
drivers/dpll/dpll_netlink.c:dpll_pin_prio_set() {
ret = ops->prio_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll,
dpll_priv(dpll), prio, extack);
if (ret)
return ret;
__dpll_pin_change_ntf(pin);
}
Does one priority change end up sending two DPLL_CMD_PIN_CHANGE_NTF
messages for the requested pin? Skipping sibling == dpin in the loop would
leave that notification to the core.
> + }
> + }
> + mutex_unlock(&sitdev->multiop_lock);
[ ... ]
> + while (changed_cnt--)
> + dpll_pin_change_ntf(changed[changed_cnt]);
[Severity: High]
Can this deadlock on dpll_lock? The .prio_set callback is invoked with
dpll_lock already held:
dpll_pin_pre_doit()
mutex_lock(&dpll_lock);
...
dpll_pin_set_from_nlattr()
dpll_pin_prio_set()
ret = ops->prio_set(...);
if (ret)
return ret;
__dpll_pin_change_ntf(pin); /* lockdep_assert_held(&dpll_lock) */
and dpll_pin_change_ntf() is the wrapper that takes the same non-recursive
mutex again:
drivers/dpll/dpll_netlink.c:dpll_pin_change_ntf() {
mutex_lock(&dpll_lock);
ret = __dpll_pin_change_ntf(pin);
mutex_unlock(&dpll_lock);
}
changed_cnt is non-zero in the ordinary case: sibling->prio starts at 0 from
the kzalloc_obj() in sit9531x_dpll_pin_alloc() and is never seeded, and the
loop does not exclude the pin being set, so the first "pin-set ... prio N"
with N different from the cached value queues at least one entry.
Should this use __dpll_pin_change_ntf(), the way the core does from this
context, instead of dpll_pin_change_ntf()?
> static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915000015.80480-1-arouhi%40sitime.com
next prev parent reply other threads:[~2026-09-17 15:01 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-15 0:00 [PATCH net-next v9 00/15] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-09-15 0:00 ` [PATCH net-next v9 01/15] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-09-15 0:00 ` [PATCH net-next v9 02/15] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 03/15] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-09-17 8:42 ` Ivan Vecera
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 04/15] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-09-17 9:42 ` Ivan Vecera
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 05/15] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 06/15] dpll: sit9531x: implement input pin state on a DPLL Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 07/15] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko [this message]
2026-09-15 0:00 ` [PATCH net-next v9 08/15] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 09/15] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 10/15] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-09-17 9:55 ` Ivan Vecera
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 11/15] dpll: sit9531x: add support to get and set esync on pins Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 12/15] dpll: sit9531x: add support to get phase offset on the connected input pin Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 14/15] dpll: sit9531x: model the inter-PLL sync net as a pair of pins Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 13/15] dpll: sit9531x: add support to get fractional frequency offset Ali Rouhi
2026-09-17 15:01 ` netdev-bot+sashiko
2026-09-15 0:00 ` [PATCH net-next v9 15/15] dpll: sit9531x: allow the device tree to override two board facts Ali Rouhi
2026-09-17 15:02 ` 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=178965730954.22033.15674954043478310598@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=Oleg.Zadorozhnyi@devoxsoftware.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=arouhi@sitime.com \
--cc=cjubran@nvidia.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=ivecera@redhat.com \
--cc=jiri@resnulli.us \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--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®