Linux cryptographic layer development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: lianfeng.ouyang <lianfeng.ouyang@starfivetech.com>,
	"Olivia Mackall" <olivia@selenic.com>,
	"Herbert Xu" <herbert@gondor.apana.org.au>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	"Philipp Zabel" <p.zabel@pengutronix.de>
Cc: linux-crypto@vger.kernel.org, devicetree@vger.kernel.org
Subject: Re: [PATCH v10 3/6] hwrng: starfive - move the SEU reseed out of the interrupt handler
Date: Fri, 23 Oct 2026 02:31:12 +0000	[thread overview]
Message-ID: <sashiko-outbox-182235@kernel.org> (raw)
In-Reply-To: <20261023021248.3763-4-lianfeng.ouyang@starfivetech.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] hwrng: starfive - premature completion race in starfive_trng_cmd()
- [High] hwrng: starfive - uncancelled reseed work in starfive_trng_irq_clear()
--

commit 23b2ffaa5e6bbeefc785bc18a08d6497566a4c9b
Author: Lianfeng Ouyang <lianfeng.ouyang@starfivetech.com>

hwrng: starfive - move the SEU reseed out of the interrupt handler

This commit reworks command handling in the StarFive TRNG driver around a
mutex that serialises command sequences and moves LFSR lockup reseeding
from the interrupt handler to a work item and the read path. It also manages
the interrupt lifecycle across init, cleanup, and runtime suspend/resume.

> diff --git a/drivers/char/hw_random/jh7110-trng.c b/drivers/char/hw_random/jh7110-trng.c
> index 17932cc6d7aa4..8e0e7865453b0 100644
> --- a/drivers/char/hw_random/jh7110-trng.c
> +++ b/drivers/char/hw_random/jh7110-trng.c
[ ... ]
> @@ -122,22 +141,38 @@ MODULE_PARM_DESC(autoage, "Auto-reseeding after specified timer countdowns to 0:
>  				" 0 - disable timer\n"
>  				" other - reload value for internal timer");
>  
> -static inline int starfive_trng_wait_idle(struct starfive_trng *trng)
> +static bool starfive_trng_idle(struct starfive_trng *trng, bool wait)
>  {
> -	u32 stat;
> +	u32 stat, busy = STARFIVE_STAT_RAND_GENERATING | STARFIVE_STAT_RAND_SEEDING;
>  
> -	return readl_relaxed_poll_timeout(trng->base + STARFIVE_STAT, stat,
> -					  !(stat & (STARFIVE_STAT_RAND_GENERATING |
> -						    STARFIVE_STAT_RAND_SEEDING)),
> -					  10, 100000);
> +	if (!wait)
> +		return !(readl_relaxed(trng->base + STARFIVE_STAT) & busy);
> +
> +	return !readl_relaxed_poll_timeout(trng->base + STARFIVE_STAT, stat,
> +					   !(stat & busy),
> +					   10, 100000);
>  }
>  
> -static inline void starfive_trng_irq_mask_clear(struct starfive_trng *trng)
> +/*
> + * Record a pending LFSR lockup and make sure it gets serviced. queue_work()
> + * is a no-op while the work is already pending, so this is safe to call
> + * from any context that notices the lockup.
> + */
> +static void starfive_trng_request_reseed(struct starfive_trng *trng)
>  {
> -	/* clear register: ISTAT */
> -	u32 data = readl(trng->base + STARFIVE_ISTAT);
> +	WRITE_ONCE(trng->need_reseed, true);
> +	queue_work(system_freezable_wq, &trng->work);
> +}
> +
> +static void starfive_trng_irq_clear(struct starfive_trng *trng)
> +{
> +	u32 istat = readl(trng->base + STARFIVE_ISTAT);
> +
> +	/* Do not lose an LFSR lockup that has not been serviced yet. */
> +	if (istat & STARFIVE_ISTAT_LFSR_LOCKUP)
> +		starfive_trng_request_reseed(trng);
>  
> -	writel(data, trng->base + STARFIVE_ISTAT);
> +	writel(istat, trng->base + STARFIVE_ISTAT);
>  }
[ ... ]
> @@ -194,42 +229,71 @@ static void starfive_trng_power_off_action(void *data)
>  	starfive_trng_power_off(data, true);
>  }
>  
> -static int starfive_trng_cmd(struct starfive_trng *trng, u32 cmd, bool wait)
> +static int starfive_trng_cmd(struct starfive_trng *trng, u32 cmd, unsigned long timeout, bool wait)
>  {
> -	int wait_time = 1000;
> +	struct completion *done;
>  
> -	/* allow up to 40 us for wait == 0 */
> -	if (!wait)
> -		wait_time = 40;
> +	lockdep_assert_held(&trng->lock);
> +
> +	if (!starfive_trng_idle(trng, wait))
> +		return wait ? -ETIMEDOUT : -EBUSY;
>  
>  	switch (cmd) {
>  	case STARFIVE_CTRL_GENE_RANDNUM:
> -		reinit_completion(&trng->random_done);
> -		spin_lock_irq(&trng->write_lock);
> -		writel(cmd, trng->base + STARFIVE_CTRL);
> -		spin_unlock_irq(&trng->write_lock);
> -		if (!wait_for_completion_timeout(&trng->random_done, usecs_to_jiffies(wait_time)))
> -			return -ETIMEDOUT;
> +		done = &trng->random_done;
>  		break;
>  	case STARFIVE_CTRL_EXEC_RANDRESEED:
> -		reinit_completion(&trng->reseed_done);
> -		spin_lock_irq(&trng->write_lock);
> -		writel(cmd, trng->base + STARFIVE_CTRL);
> -		spin_unlock_irq(&trng->write_lock);
> -		if (!wait_for_completion_timeout(&trng->reseed_done, usecs_to_jiffies(wait_time)))
> -			return -ETIMEDOUT;
> +		done = &trng->reseed_done;
>  		break;
>  	default:
>  		return -EINVAL;
>  	}
>  
> +	reinit_completion(done);
> +	writel(cmd, trng->base + STARFIVE_CTRL);
> +	if (!wait_for_completion_timeout(done, timeout)) {
> +		synchronize_irq(trng->irq);
> +		if (!try_wait_for_completion(done))
> +			return -ETIMEDOUT;
> +	}
>  
>  	return 0;
>  }

[Severity: High]
Can a command that times out while the hardware is still busy leave a late
completion for the next command in starfive_trng_cmd()?

In starfive_trng_read(), non-blocking reads (wait == false) pass
timeout = STARFIVE_RANDGEN_NOWAIT_TIMEOUT (1 jiffy) to starfive_trng_cmd().
Because wait_for_completion_timeout(done, 1) expires on the very next timer
tick, a call issued right before a tick can time out in microseconds while
the hardware is still executing STARFIVE_CTRL_GENE_RANDNUM
(STARFIVE_STAT_RAND_GENERATING is still set and STARFIVE_ISTAT_RAND_RDY has
not been asserted yet).

When wait_for_completion_timeout() returns 0 while the hardware command is
still in flight, synchronize_irq(trng->irq) returns immediately because the
IRQ line has not been asserted yet, try_wait_for_completion(done) returns
false, and starfive_trng_cmd() returns -ETIMEDOUT and drops trng->lock while
the hardware continues running.

When the next caller enters starfive_trng_cmd(), starfive_trng_idle() only
polls STARFIVE_STAT until STARFIVE_STAT_RAND_GENERATING and
STARFIVE_STAT_RAND_SEEDING clear:

  starfive_trng_cmd()
    starfive_trng_idle() -> returns true once previous command finishes
    reinit_completion(done)
    writel(cmd, trng->base + STARFIVE_CTRL)

If starfive_trng_irq() services the first command's STARFIVE_ISTAT_RAND_RDY
interrupt after reinit_completion(done), it completes trng->random_done for
the second command before the new generation finishes, causing
starfive_trng_read() to copy stale or in-progress data from STARFIVE_RAND0.

A similar race can also affect trng->reseed_done when autoreq or autoage is
configured: an automatic hardware reseed sets STARFIVE_ISTAT_SEED_DONE when
STARFIVE_STAT_RAND_SEEDING clears, which can fire after
starfive_trng_idle() and race with reinit_completion(&trng->reseed_done).

> @@ -238,37 +302,62 @@ static int starfive_trng_init(struct hwrng *rng)
>  		return ret;
>  	}
>  
> -	/* setup Auto Request/Age register */
> -	writel(autoage, trng->base + STARFIVE_AUTO_AGE);
> -	writel(autoreq, trng->base + STARFIVE_AUTO_RQSTS);
> +	mutex_lock(&trng->lock);
> +
> +	/*
> +	 * The hwrng core can call init again without an intervening
> +	 * cleanup: deselecting and quickly reselecting the device makes
> +	 * cleanup_rng_work() skip ->cleanup() once the refcount is back up.
> +	 * Only bring up the interrupt line once in that case.
> +	 */
> +	if (!trng->initialized) {
> +		starfive_trng_irq_clear(trng);
> +		starfive_trng_hw_setup(trng);
> +
> +		enable_irq(trng->irq);
> +		writel(STARFIVE_IE_ALL, trng->base + STARFIVE_IE);
> +		trng->initialized = true;
> +	}
> +
> +	ret = starfive_trng_reseed(trng);
> +	if (ret) {
> +		trng->initialized = false;
> +		writel(0, trng->base + STARFIVE_IE);
> +		starfive_trng_irq_clear(trng);
> +		disable_irq(trng->irq);
> +	}
[ ... ]
> @@ -301,15 +387,36 @@ static irqreturn_t starfive_trng_irq(int irq, void *priv)
>  static void starfive_trng_cleanup(struct hwrng *rng)
>  {
>  	struct starfive_trng *trng = to_trng(rng);
> +	bool initialized;
>  	int ret;
>  
>  	ret = pm_runtime_resume_and_get(trng->dev);
> -	if (ret < 0) {
> +	if (ret < 0)
>  		dev_err(trng->dev, "Failed to wake device for cleanup: %d\n", ret);
> -		return;
> +
> +	mutex_lock(&trng->lock);
> +	initialized = trng->initialized;
> +	trng->initialized = false;
> +	if (ret >= 0 && initialized) {
> +		writel(0, trng->base + STARFIVE_IE);
> +		starfive_trng_irq_clear(trng);
>  	}
> +	mutex_unlock(&trng->lock);
> +
> +	/*
> +	 * cleanup also runs after a failed init, which has already undone
> +	 * its enable_irq(). Only disable the line if init left it enabled.
> +	 */
> +	if (initialized)
> +		disable_irq(trng->irq);
> +	cancel_work_sync(&trng->work);
>  
> +	if (ret < 0)
> +		return;
> +
> +	mutex_lock(&trng->lock);
>  	writel(STARFIVE_CTRL_EXEC_NOP, trng->base + STARFIVE_CTRL);
> +	mutex_unlock(&trng->lock);
>  
>  	pm_runtime_put_sync_suspend(trng->dev);
>  }
[ ... ]
> @@ -420,6 +566,15 @@ static int starfive_trng_probe(struct platform_device *pdev)
>  	if (ret)
>  		return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
>  
> +	ret = devm_work_autocancel(&pdev->dev, &trng->work, starfive_trng_reseed_work);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_request_irq(&pdev->dev, trng->irq, starfive_trng_irq, IRQF_NO_AUTOEN,
> +			       pdev->name, (void *)trng);
> +	if (ret)
> +		return ret;
> +
>  	ret = devm_hwrng_register(&pdev->dev, &trng->rng);
>  	if (ret)
>  		return dev_err_probe(&pdev->dev, ret, "Failed to register hwrng\n");
> @@ -432,8 +587,16 @@ static int starfive_trng_probe(struct platform_device *pdev)
>  static int starfive_trng_suspend(struct device *dev)
>  {
>  	struct starfive_trng *trng = dev_get_drvdata(dev);
> +	bool assert_rst;
> +
> +	mutex_lock(&trng->lock);
> +	assert_rst = !trng->initialized;
> +	writel(0, trng->base + STARFIVE_IE);
> +	starfive_trng_irq_clear(trng);
> +	mutex_unlock(&trng->lock);
>  
> -	starfive_trng_power_off(trng, false);
> +	synchronize_irq(trng->irq);
> +	starfive_trng_power_off(trng, assert_rst);
>  
>  	return 0;
>  }

[Severity: High]
Does calling starfive_trng_irq_clear() when trng->initialized is false allow
trng->work to be queued after it has already been cancelled, leading to a
use-after-free of struct starfive_trng during teardown?

In starfive_trng_irq_clear(), if STARFIVE_ISTAT_LFSR_LOCKUP is set in
STARFIVE_ISTAT, starfive_trng_request_reseed() unconditionally queues
trng->work on system_freezable_wq even when trng->initialized is false.

In starfive_trng_probe(), devm_pm_runtime_set_active_enabled() is registered
before devm_work_autocancel() and devm_request_irq(). During devres teardown
(for example, if devm_hwrng_register() fails after starfive_trng_init()
leaves the device RPM_ACTIVE via pm_runtime_put_autosuspend(), or on driver
unbind after __device_release_driver() resumes the device), devres actions
run in reverse order:

  devm_irq_release() -> frees trng->irq
  devm_work_drop() -> cancel_work_sync(&trng->work)
  pm_runtime_disable_action()
    pm_runtime_dont_use_autosuspend()
      rpm_idle() -> rpm_suspend()
        starfive_trng_suspend()
          starfive_trng_irq_clear() -> queues trng->work
          synchronize_irq(trng->irq)

Because starfive_trng_suspend() runs after devm_irq_release() and
devm_work_drop(), it calls synchronize_irq() on an already-freed IRQ and can
re-queue trng->work right before devres frees struct starfive_trng, causing
starfive_trng_reseed_work() to access freed memory.

Similarly, in starfive_trng_cleanup(), cancel_work_sync(&trng->work) runs
before pm_runtime_put_sync_suspend(trng->dev), which synchronously invokes
starfive_trng_suspend() -> starfive_trng_irq_clear() and can re-queue
trng->work after cleanup cancelled it. And if starfive_trng_reseed() fails
in starfive_trng_init(), the error path calls starfive_trng_irq_clear()
without cancelling trng->work (while hwrng_init() in
drivers/char/hw_random/core.c does not call ->cleanup() on normal ->init()
failure), allowing starfive_trng_reseed_work() to run while
!trng->initialized and call pm_runtime_resume_and_get() ->
starfive_trng_resume() -> starfive_trng_power_on(), deasserting the hardware
reset on an uninitialized device.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261023021248.3763-1-lianfeng.ouyang@starfivetech.com?part=3

  reply	other threads:[~2026-10-23  2:31 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23  2:12 [PATCH v10 0/6] hwrng: starfive - fixes and JHB100 support lianfeng.ouyang
2026-10-23  2:12 ` [PATCH v10 1/6] dt-bindings: rng: starfive,jh7110-trng: add jhb100, drop jh8100 lianfeng.ouyang
2026-10-23  2:12 ` [PATCH v10 2/6] hwrng: starfive - fix runtime PM and power teardown handling lianfeng.ouyang
2026-10-23  2:12 ` [PATCH v10 3/6] hwrng: starfive - move the SEU reseed out of the interrupt handler lianfeng.ouyang
2026-10-23  2:31   ` sashiko-bot [this message]
2026-10-23  2:12 ` [PATCH v10 4/6] hwrng: starfive - tidy up includes and drop unused code lianfeng.ouyang
2026-10-23  2:12 ` [PATCH v10 5/6] hwrng: starfive - add JHB100 support lianfeng.ouyang
2026-10-23  2:12 ` [PATCH v10 6/6] MAINTAINERS: update the StarFive TRNG driver maintainer lianfeng.ouyang

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-182235@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=herbert@gondor.apana.org.au \
    --cc=krzk+dt@kernel.org \
    --cc=lianfeng.ouyang@starfivetech.com \
    --cc=linux-crypto@vger.kernel.org \
    --cc=olivia@selenic.com \
    --cc=p.zabel@pengutronix.de \
    --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