mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: arouhi@sitime.com
Cc: Jakub Kicinski <kuba@kernel.org>,
	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,
	prabhakar.mahadev-lad.rj@bp.renesas.com, dev@kael-k.io,
	devicetree@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v10 07/14] dpll: sit9531x: add support to get and set priority on input pins
Date: Fri, 25 Sep 2026 19:34:46 -0700	[thread overview]
Message-ID: <20260926023446.1567764-1-kuba@kernel.org> (raw)
In-Reply-To: <20260921201108.42676-8-arouhi@sitime.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
dpll: sit9531x: add support to get and set priority on input pins

This adds prio_get and prio_set callbacks for the sit9531x input pins.
They are backed by a cached copy of each PLL's priority table, which
sit9531x_prio_mask_build() now maintains. prio_set moves an input to the
requested slot and notifies the other pins whose slots shifted, and
sit9531x_prio_table_commit() gains a rollback of partially written slots.

> diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
> index 84c1f86e6d4cf..ac184c93258cb 100644
> --- a/drivers/dpll/sit9531x/core.c
> +++ b/drivers/dpll/sit9531x/core.c
> @@ -543,34 +543,87 @@ 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

[Severity: Low]
This isn't a bug, but is "last slot" accurate here?

sit9531x_prio_mask_build() fills prio_last from first[], which holds the
first (highest priority) slot a source occupies among its duplicates. It
also clears the entry to 0 as soon as the source leaves the table:

		if (!first[src_canon])
			first[src_canon] = slot + 1;

The "last-slot-seen array" wording in the sit9531x_prio_mask_build()
comment below has the same problem. The @prio_last description in core.h
does match the code.

[ ... ]

> +/*
> + * 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().
> + *
> + * 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, u8 written)
>  {
> +	struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
> +	u8 first[SIT9531X_PRIO_NUM_SRC] = { 0 };
>  	u16 mask = 0;
> -	u8 slot;
> -
> -	/*
> -	 * A table written only in part is not described by the values the
> -	 * request carried, and there is nothing here to describe it with
> -	 * instead, so the mask it had stands until the next poll reads the
> -	 * table back.
> -	 */
> -	if (written < SIT9531X_PRIO_MAX_SLOTS)
> -		return;
> +	u8 slot, src, src_canon;
>  
>  	for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
> -		u8 src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
> +		/*
> +		 * A slot the caller could not write still holds what it
> +		 * held before, so take that rather than the value the
> +		 * request wanted to put there.
> +		 */
> +		src = slot < written ? srcs[slot] : chan->prio_srcs[slot];
> +		src &= SIT9531X_PRIO_NIBBLE_MASK;
> +		chan->prio_srcs[slot] = src;
> +		src_canon = sit9531x_prio_src_canon(sitdev, src);
> +		if (!sit9531x_prio_src_usable(src))
> +			continue;
>  
> -		if (sit9531x_prio_src_usable(src))
> -			mask |= BIT(src);
> +		mask |= BIT(src_canon);
> +		if (!first[src_canon])
> +			first[src_canon] = slot + 1;
>  	}

[Severity: Low]
This isn't a bug, but this hunk changes sit9531x_prio_mask_build() in two
ways that the commit message doesn't mention. Both affect existing users:
the pin state getter, sit9531x_input_prio_present(),
sit9531x_ref_pll_mask_rebuild() and the periodic poll.

First, the mask is now built from BIT(src_canon) instead of BIT(src).
Before this patch, when a differential pair's table entry held the N-lane
code, the P-lane pin was reported as disconnected and prio_present()
returned false. Now the same entry reports the pin as listed.

Second, the early return on a partial write is gone. The requested values
for the written slots are now merged with chan->prio_srcs for the rest.
That changes what prio_present() tells the rollback branches of
sit9531x_dpll_input_pin_state_on_dpll_set() after a failed commit.

Should these changes be described in the commit message, or moved into
the commit that introduced sit9531x_prio_mask_build()?

[ ... ]

> @@ -629,9 +683,34 @@ static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,
>  
>  	written = SIT9531X_PRIO_MAX_SLOTS;
>  
> +	if (rc && written) {
> +		/*
> +		 * Put the slots that did reach the device back the way they
> +		 * were.  Latching a table that is neither the previous order
> +		 * nor the requested one hands the reference selection loop
> +		 * a priority list nobody asked for.  The cache is the table
> +		 * as last read, which is what those slots held.
> +		 */
> +		for (slot = 0; slot < written; slot += 2) {
> +			u8 old;
> +
> +			old = sit9531x_prio_slot_set(0, slot,
> +						     chan->prio_srcs[slot]);
> +			old = sit9531x_prio_slot_set(old, slot + 1,
> +						     chan->prio_srcs[slot + 1]);
> +			if (sit9531x_write_u8(sitdev,
> +					      sit9531x_prio_reg(pll_idx, slot),
> +					      old))
> +				break;
> +
> +			restored = slot + 2;
> +		}
> +		written = restored;
> +	}

[Severity: Medium]
Can this block ever run? Every failing register access earlier in
sit9531x_prio_table_commit() jumps straight to the label:

		rc = sit9531x_write_u8(sitdev, reg, val);
		if (rc)
			goto commit;

The read and the write of the last register use the same goto. The only
way to reach "if (rc && written)" is to fall through after the last write
succeeded, and rc is 0 at that point.

So when a table write fails partway through prio_set, prio_add or
prio_remove, sit9531x_prio_prg_commit() still latches the half-written
table and the forced holdover is still released.

For example, take [A,B,C,D,E,F,G,H,X,I,J] and move X to slot 0. If the
write for slots 4 and 5 fails, the device latches
[X,A,B,C,E,F,G,H,X,I,J], and D drops out of this PLL's selection list.

The partial latch itself comes from the earlier "dpll: sit9531x: implement
input pin state on a DPLL" commit. The comment here, though, says this
block prevents it.

If the gotos were pointed at this block, would a few more things need
fixing?

With written == SIT9531X_PRIO_MAX_SLOTS (11), the loop reaches slot 10 and
reads chan->prio_srcs[11], one past the end of the 11-entry array.

The last register shares its other nibble with CLK_ACTIVESEL. This block
rewrites that register from a 0 base, not with the read-modify-write the
forward path uses.

After "written = restored", sit9531x_prio_mask_build(sitdev, pll_idx,
srcs, written) would cache the requested srcs[] for exactly the slots that
were just put back to their old values.

The commit message also doesn't mention this change to a helper shared by
prio_add, prio_remove and prio_set.

[ ... ]

> diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
> index 033e1059bde9b..2c5d0100b450e 100644
> --- a/drivers/dpll/sit9531x/core.h
> +++ b/drivers/dpll/sit9531x/core.h
> @@ -126,6 +126,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
>   * @prio_mask:		bit per hardware source code present in this PLL's
>   *			priority table, i.e. the sources it may select.  Read

[Severity: Low]
This isn't a bug, but the @prio_mask description still says "bit per
hardware source code".

sit9531x_prio_mask_build() now sets BIT(src_canon). An N-lane code in the
table therefore sets the P-lane bit and leaves its own bit clear.

Could this be updated to say the mask is indexed by the canonical source?

[ ... ]

> diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
> index 4437c95f578f8..56a8213dee077 100644
> --- a/drivers/dpll/sit9531x/dpll.c
> +++ b/drivers/dpll/sit9531x/dpll.c
> @@ -489,10 +489,139 @@ sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin,

[ ... ]

> +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)
> +{

[ ... ]

> +	mutex_lock(&sitdev->multiop_lock);
> +	rc = sit9531x_input_prio_set(sitdev, sitdpll->id,
> +				     sit9531x_input_hw_src(dpin->id),
> +				     (u8)prio);
> +	if (!rc) {

[Severity: Low]
If sit9531x_input_prio_set() returns an error after the table has already
changed, is the sibling update skipped entirely?

In sit9531x_prio_table_commit(), the table is latched and
sit9531x_prio_mask_build() refreshes prio_last before the holdover release
is retried. A failure there still becomes the return value:

	if (ho_rc) {
		...
		if (!rc)
			rc = ho_rc;

A partially written table is also latched and cached on the write failure
paths.

In both cases this callback returns an error, so dpll_pin_prio_set() in
the core also skips __dpll_pin_change_ntf() for the named pin. Several
pins' reported priorities can then change with no PIN_CHANGE_NTF for any
of them, and sibling->prio stays stale.

> +		list_for_each_entry(sibling, &sitdpll->pins, list) {
> +			if (!sit9531x_dpll_is_input_pin(sibling) ||
> +			    sit9531x_dpll_is_xo_pin(sibling))
> +				continue;
> +
> +			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;
> +
> +			/*
> +			 * The core notifies the pin the request named, so
> +			 * only the others are collected here.  A pin whose
> +			 * dpll_pin is already NULL is mid-unregister: that
> +			 * runs with the device lock dropped between the
> +			 * unregister and the free, so it can be seen from
> +			 * here, and notifying through it would follow a
> +			 * pointer that is on its way out.
> +			 */
> +			if (sibling == dpin || !sibling->dpll_pin)
> +				continue;
> +
> +			if (changed_cnt < ARRAY_SIZE(changed))
> +				changed[changed_cnt++] = sibling->dpll_pin;
> +		}
> +	}
> +	mutex_unlock(&sitdev->multiop_lock);

[Severity: High]
Is it safe to walk sitdpll->pins and read sibling->dpll_pin here while
holding only multiop_lock and the core's dpll_lock? The teardown path
takes neither lock for the list or for this field:

drivers/dpll/sit9531x/core.c:sit9531x_dpll_pin_unregister() {
	...
	dpll_pin_unregister(sitdpll->dpll_dev, pin->dpll_pin, ops, pin);
	dpll_pin_put(pin->dpll_pin, &pin->tracker);
	pin->dpll_pin = NULL;
	...
}

drivers/dpll/sit9531x/core.c:sit9531x_dpll_pins_unregister() {
	...
	list_for_each_entry_safe(pin, next, &sitdpll->pins, list) {
		sit9531x_dpll_pin_unregister(pin);
		list_del(&pin->list);
		kfree(pin);
	}
}

dpll_pin_unregister() and dpll_pin_put() each take and drop dpll_lock.
The NULL store only happens after both.

Suppose a PIN_SET prio request arrives for a pin that is still registered
on the same DPLL while sit9531x_dev_stop() or a probe error path is
running. Could this loop pick up a sibling's dpll_pin after the last
dpll_pin_put() has freed it, and pass it to __dpll_pin_change_ntf()?

Could the loop also be standing on a sit9531x_dpll_pin that another CPU
is list_del()'ing and kfree()'ing? It would then write sibling->prio into
freed memory or follow LIST_POISON1.

sit9531x_dpll_pins_register() has a similar problem. It does
list_add(&pin->list, &sitdpll->pins) without the lock, after earlier pins
are already reachable from netlink.

The !sibling->dpll_pin check added in v10 reads the pointer without any
lock that orders it against teardown. It doesn't seem to close the window
the comment describes.

[ ... ]

> +	/*
> +	 * The core notifies only the pin the request named, so the ones whose
> +	 * slots moved are notified here.  This runs inside a pin op, where
> +	 * the core already holds the lock the notification needs, so it is
> +	 * the underscore helper rather than the wrapper that takes it.
> +	 */
> +	while (changed_cnt--)
> +		__dpll_pin_change_ntf(changed[changed_cnt]);
> +
> +	return 0;
> +}

[Severity: Medium]
Sibling notifications are only sent from this prio_set path. What about
sit9531x_dpll_input_pin_state_on_dpll_set() with
DPLL_PIN_STATE_DISCONNECTED?

That path calls sit9531x_input_prio_remove(). It rebuilds the table from
the sources it keeps and backfills the tail:

	} else {
		/* Backfill freed tail slots with the lowest-priority src */
		while (count < SIT9531X_PRIO_MAX_SLOTS) {
			kept[count] = kept[count - 1];
			count++;
		}
	}

Every later source moves up a slot, and sit9531x_prio_mask_build() updates
prio_last, which is what prio_get now reports. For example, removing A
from [A,B,C,...] moves B from 1 to 0 and C from 2 to 1.

dpll_pin_state_set() in the core only notifies the named pin. The
disconnect path neither notifies the siblings nor refreshes their
sibling->prio.

sit9531x_dpll_changes_check() doesn't catch this later either, because it
only compares pin state:

		if (state != pin->pin_state) {
			...
			pin->pin_state = state;
			dpll_pin_change_ntf(pin->dpll_pin);
		}

Would userspace listening for PIN_CHANGE_NTF miss the priority changes of
the other inputs after a disconnect?

[ ... ]

  reply	other threads:[~2026-09-26  2:34 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-21 20:11 [PATCH v10 00/14] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 01/14] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 02/14] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 03/14] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 04/14] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 05/14] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 06/14] dpll: sit9531x: implement input pin state on a DPLL Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 07/14] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski [this message]
2026-09-21 20:11 ` [PATCH v10 08/14] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 09/14] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 11/14] dpll: sit9531x: add support to get phase offset on the connected input pin Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 10/14] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 12/14] dpll: sit9531x: add support to get fractional frequency offset Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 13/14] dpll: sit9531x: model the inter-PLL sync net as a pair of pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 14/14] dpll: sit9531x: allow the device tree to override two board facts Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski

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=20260926023446.1567764-1-kuba@kernel.org \
    --to=kuba@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=dev@kael-k.io \
    --cc=devicetree@vger.kernel.org \
    --cc=ivecera@redhat.com \
    --cc=jiri@resnulli.us \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=prabhakar.mahadev-lad.rj@bp.renesas.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®