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
next prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.