Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "James Hilliard" <james.hilliard1@gmail.com>
Cc: linux-clk@vger.kernel.org, devicetree@vger.kernel.org,
	linux-sunxi@lists.linux.dev, robh@kernel.org,
	conor+dt@kernel.org
Subject: Re: [PATCH v11 2/5] pwm: sun8i: Add H616 PWM support
Date: Tue, 03 Nov 2026 09:08:54 +0000	[thread overview]
Message-ID: <sashiko-outbox-194932@kernel.org> (raw)
In-Reply-To: <20261103-h616-pwm-v8-v11-2-07abb59575c3@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] pwm: sun8i: rate_exclusive leak in sun8i_pwm_prepare_update() on error
- [Medium] pwm: sun8i: false -EOPNOTSUPP in sun8i_pwm_clear_idle_deadzone()
- [Medium] pwm: sun8i: race in sun8i_pwm_put_rate() clearing rate_exclusive early
--

commit 2ea581bca416bf89c24368c5ae4769a990d47584
Author: Richard Genoud <richard.genoud@bootlin.com>

pwm: sun8i: Add H616 PWM support

This commit adds waveform support for the six-channel Allwinner H616 PWM
controller, modeling shared pair clocks in CCF and programming channel-local
dividers and period/duty registers directly.

> diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
> new file mode 100644
> index 0000000000000..bcc2c57443780
> --- /dev/null
> +++ b/drivers/pwm/pwm-sun8i.c

[ ... ]

> +static void sun8i_pwm_put_rate(struct sun8i_pwm_chip *sun8i_chip,
> +			       unsigned int idx)
> +{
> +	struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx];
> +	bool exclusive;
> +
> +	scoped_guard(spinlock_irqsave, &sun8i_chip->lock) {
> +		exclusive = chan->rate_exclusive;
> +		chan->rate_exclusive = false;
> +	}
> +	/* CCF takes the prepare mutex and may call back into this driver. */
> +	if (exclusive)
> +		clk_rate_exclusive_put(chan->pair_clk);
> +}

[ ... ]

> +static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
> +	struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
> +
> +	scoped_guard(spinlock_irqsave, &sun8i_chip->lock) {
> +		sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false);
> +		sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false);
> +	}
> +
> +	sun8i_pwm_put_rate(sun8i_chip, pwm->hwpwm);
> +	clk_disable_unprepare(chan->pair_clk);
> +	clk_put(chan->pair_clk);
> +	chan->pair_clk = NULL;
> +}

[Severity: Medium]
Can clearing chan->rate_exclusive before clk_rate_exclusive_put() in
drivers/pwm/pwm-sun8i.c:sun8i_pwm_put_rate() race with a concurrent
waveform update on the sibling channel?

In the PWM core, __pwm_put() calls ops->free() (sun8i_pwm_free()) under
pwm_lock without holding pwmchip_lock(chip), while waveform callbacks
(sun8i_pwm_round_waveform_tohw() and sun8i_pwm_write_waveform()) run under
pwmchip_lock(chip) without pwm_lock.

When an enabled channel is freed on one CPU while its sibling channel is
being updated on another CPU:

CPU0                                     CPU1
sun8i_pwm_free(idx)
  sun8i_pwm_set_enabled_locked(idx, false)
  sun8i_pwm_put_rate(idx)
    chan->rate_exclusive = false
                                         sun8i_pwm_round_waveform_tohw(idx ^ 1)
                                           sun8i_pwm_pair_rate_constrained()
                                             -> returns false
                                         sun8i_pwm_write_waveform(idx ^ 1)
                                           sun8i_pwm_prepare_update()
                                             sun8i_pwm_set_enabled_locked(false)
                                             clk_set_rate(pair_clk, new_rate)
                                               -> fails with -EBUSY
    clk_rate_exclusive_put(chan->pair_clk)

Because both SUN8I_PWM_ENABLE(idx) and chan->rate_exclusive are already
cleared before clk_rate_exclusive_put() drops CCF rate protection,
sun8i_pwm_pair_rate_constrained() on the sibling channel sees the pair as
unconstrained and attempts to change pair_rate, causing clk_set_rate() to
fail with -EBUSY while the freeing channel's protect_count is still held.

[ ... ]

> +static bool
> +sun8i_pwm_pair_rate_constrained(struct sun8i_pwm_chip *sun8i_chip,
> +				unsigned int idx)
> +{
> +	unsigned int sibling = idx ^ 1;
> +
> +	guard(spinlock_irqsave)(&sun8i_chip->lock);
> +	return sun8i_chip->channels[sibling].rate_exclusive ||
> +	       sun8i_pwm_channel_is_enabled_locked(sun8i_chip, sibling);
> +}
> +
> +static int sun8i_pwm_clear_idle_deadzone(struct sun8i_pwm_chip *sun8i_chip,
> +					 unsigned int idx)
> +{
> +	unsigned int pair = SUN8I_PWM_PAIR_IDX(idx);
> +	u32 pdzcr, per;
> +
> +	guard(spinlock_irqsave)(&sun8i_chip->lock);
> +	pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair));
> +	if (!(pdzcr & SUN8I_PWM_PDZCR_ENABLE))
> +		return 0;
> +
> +	per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
> +	if (per & sun8i_pwm_pair_enable_mask(pair))
> +		return -EOPNOTSUPP;
> +
> +	pdzcr &= ~SUN8I_PWM_PDZCR_ENABLE;
> +	sun8i_pwm_writel(sun8i_chip, pdzcr, SUN8I_PWM_PDZCR(pair));
> +
> +	return 0;
> +}

[Severity: Medium]
In drivers/pwm/pwm-sun8i.c:sun8i_pwm_clear_idle_deadzone(), can checking
only SUN8I_PWM_PER return a false -EOPNOTSUPP when a channel in the pair is
enabled in bypass mode?

In sun8i_pwm_read_waveform(), SUN8I_PWM_PDZCR_ENABLE is ignored when
wfhw->bypass_en is true:

	if (wfhw->enabled && !wfhw->bypass_en &&
	    ((pdzcr & SUN8I_PWM_PDZCR_ENABLE) || (pcr & SUN8I_PWM_PCR_MODE)))
		return -EOPNOTSUPP;

Similarly, sun8i_pwm_write_waveform() skips sun8i_pwm_clear_idle_deadzone()
when wfhw->bypass_en is true and sets SUN8I_PWM_ENABLE(pwm->hwpwm) in
SUN8I_PWM_PER.

If a pair has a dormant SUN8I_PWM_PDZCR_ENABLE bit set and a channel in that
pair is enabled in bypass mode (either by firmware or a prior
sun8i_pwm_write_waveform() call), a subsequent call to
sun8i_pwm_write_waveform() with !wfhw->bypass_en calls
sun8i_pwm_clear_idle_deadzone() before sun8i_pwm_prepare_update() stops the
channel. Because sun8i_pwm_clear_idle_deadzone() checks per &
sun8i_pwm_pair_enable_mask(pair) without checking whether the enabled
channel is in bypass mode (or is the channel being reconfigured out of
bypass mode), won't it reject the update with -EOPNOTSUPP instead of
clearing the dormant dead-zone bit?

[ ... ]

> +static int sun8i_pwm_prepare_update(struct pwm_chip *chip, unsigned int idx,
> +				    const struct sun8i_pwm_waveform *wfhw,
> +				  struct sun8i_pwm_waveform *old)
> +{
> +	struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
> +	struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx];
> +	unsigned long rate, bus_rate;
> +	bool had_exclusive, stop;
> +	u32 pccr;
> +	int ret, restore_ret;
> +
> +	scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
> +		had_exclusive = chan->rate_exclusive;
> +
> +	if (!had_exclusive) {
> +		ret = clk_rate_exclusive_get(chan->pair_clk);
> +		if (ret)
> +			return ret;
> +		scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
> +			chan->rate_exclusive = true;
> +	}

[ ... ]

> +	/* Live PPR updates require unchanged configuration and a faster PCLK. */
> +	bus_rate = clk_get_rate(sun8i_chip->bus_clk);
> +	stop = old->enabled &&
> +		(old->bypass_en != wfhw->bypass_en || rate != wfhw->pair_rate ||
> +		 (!wfhw->bypass_en &&
> +		  (((old->pcr ^ wfhw->pcr) &
> +		    (SUN8I_PWM_PCR_WAVEFORM_MASK | SUN8I_PWM_PCR_MODE)) ||
> +		   bus_rate <= DIV_ROUND_UP_ULL(wfhw->pair_rate,
> +			FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, wfhw->pcr) + 1))));
> +	if (stop) {
> +		scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
> +			sun8i_pwm_set_enabled_locked(sun8i_chip, idx, false);
> +	}
> +
> +	if (rate == wfhw->pair_rate)
> +		return 0;
> +
> +	ret = clk_set_rate(chan->pair_clk, wfhw->pair_rate);
> +	if (!ret && sun8i_pwm_get_pair_rate(sun8i_chip, idx) == wfhw->pair_rate)
> +		return 0;
> +	if (!ret)
> +		ret = -EINVAL;
> +
> +	if (sun8i_pwm_get_pair_rate(sun8i_chip, idx) != rate) {
> +		restore_ret = clk_set_rate(chan->pair_clk, rate);
> +		if (restore_ret || sun8i_pwm_get_pair_rate(sun8i_chip, idx) != rate) {
> +			dev_err(pwmchip_parent(chip),
> +				"pwm#%u: failed to restore pair clock rate %lu\n",
> +				idx, rate);
> +			goto put_rate;
> +		}
> +	}
> +	if (stop) {
> +		scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
> +			sun8i_pwm_set_enabled_locked(sun8i_chip, idx, true);
> +	}
> +put_rate:
> +	if (!had_exclusive)
> +		sun8i_pwm_put_rate(sun8i_chip, idx);
> +
> +	return ret;
> +}

[Severity: Medium]
Does drivers/pwm/pwm-sun8i.c:sun8i_pwm_prepare_update() leak
chan->rate_exclusive (and leave SUN8I_PWM_PCCR_BYPASS_BIT(idx) set) when
restoring the pair clock rate fails on an already-enabled channel?

When updating an active channel (old->enabled is true, so had_exclusive is
true) that requires changing pair_rate, stop is true and the channel is
disabled in SUN8I_PWM_PER via sun8i_pwm_set_enabled_locked(sun8i_chip, idx,
false).

If clk_set_rate(chan->pair_clk, wfhw->pair_rate) fails (or rounds to a
different rate) and the rollback clk_set_rate(chan->pair_clk, rate) also
fails to restore the original rate, the error path jumps to put_rate without
re-enabling the channel in hardware.

At put_rate, sun8i_pwm_put_rate() is only called when !had_exclusive. Since
had_exclusive is true, chan->rate_exclusive remains true and
clk_rate_exclusive_put(chan->pair_clk) is not called even though the channel
is now disabled in hardware (and if old->bypass_en was true, its bypass bit
in SUN8I_PWM_PCCR is also left set), preventing the sibling channel from
changing the shared pair clock rate.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103-h616-pwm-v8-v11-0-07abb59575c3@gmail.com?part=2

  reply	other threads:[~2026-11-03  9:08 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03  8:40 [PATCH v11 0/5] Introduce Allwinner H616 PWM controller James Hilliard
2026-11-03  8:40 ` [PATCH v11 1/5] dt-bindings: pwm: allwinner: add h616 pwm compatible James Hilliard
2026-11-03  8:47   ` sashiko-bot
2026-11-03  8:40 ` [PATCH v11 2/5] pwm: sun8i: Add H616 PWM support James Hilliard
2026-11-03  9:08   ` sashiko-bot [this message]
2026-11-03  8:40 ` [PATCH v11 3/5] pwm: sun8i: export the bypass outputs as clocks James Hilliard
2026-11-03  9:28   ` sashiko-bot
2026-11-03  8:40 ` [PATCH v11 4/5] arm64: dts: allwinner: h616: add PWM controller James Hilliard
2026-11-03  8:40 ` [PATCH v11 5/5] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver James Hilliard

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=sashiko-outbox-194932@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=james.hilliard1@gmail.com \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-sunxi@lists.linux.dev \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.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