Linux Watchdog driver development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Artur Kowalski" <arturkow2000@gmail.com>
Cc: linux-watchdog@vger.kernel.org
Subject: Re: [PATCH 6/6] watchdog: tegra_wdt: Stop on Tegra20 VDE power off
Date: Tue, 10 Nov 2026 12:56:40 +0000	[thread overview]
Message-ID: <sashiko-outbox-207056@kernel.org> (raw)
In-Reply-To: <20261110-t20-for-upstream-v1-6-5923449951fe@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] watchdog: tegra_wdt: refcount leak in
  tegra20_wdt_powergate_notify()
- [Medium] watchdog: tegra_wdt: stale WDOG_ACTIVE after
  tegra20_wdt_powergate_notify()
- [Medium] watchdog: tegra_wdt: race on WDOG_HW_RUNNING in
  tegra20_wdt_powergate_notify()
--

commit 7bc831b226455fa0dc8d2f0743ce747806275870
Author: Artur Kowalski <arturkow2000@gmail.com>

watchdog: tegra_wdt: Stop on Tegra20 VDE power off

This commit registers a Tegra PMC powergate notifier on Tegra20 to stop the
watchdog before the VDE partition is powered off and prevent it from being
started afterwards.

> diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
> index 7c2c21f90de52..d36310c50f782 100644
> --- a/drivers/watchdog/tegra_wdt.c
> +++ b/drivers/watchdog/tegra_wdt.c

[ ... ]

> @@ -256,6 +276,35 @@ static int tegra20_wdt_stop(struct watchdog_device *wdd)
>  	writel(0, wdt->tmr_regs + TIMER_PTV);
>  	writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
> +}
> +
> +static int tegra20_wdt_start(struct watchdog_device *wdd)
> +{
> +	struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
> +	int ret = 0;
> +
> +	mutex_lock(&wdt->lock);
> +
> +	if (wdt->vde_off) {
> +		dev_warn(wdd->parent,
> +			 "not starting, a reset would hang with VDE powered off\n");
> +		ret = -EBUSY;
> +	} else {
> +		__tegra20_wdt_start(wdt);
> +	}
> +
> +	mutex_unlock(&wdt->lock);
> +
> +	return ret;
> +}

[Severity: Medium]
Can tegra20_wdt_powergate_notify() race with watchdog_open() ->
watchdog_start() on WDOG_HW_RUNNING and WDOG_ACTIVE?

tegra20_wdt_powergate_notify() serializes on wdt->lock, whereas
watchdog_open() and watchdog_start() inspect and update wdd->status under
wd_data->lock:

CPU0                                  CPU1
watchdog_open()
  watchdog_start()
    tegra20_wdt_start()
      mutex_lock(&wdt->lock)
      __tegra20_wdt_start(wdt)
      mutex_unlock(&wdt->lock)
                                      tegra20_wdt_powergate_notify()
                                        mutex_lock(&wdt->lock)
                                        wdt->vde_off = true
                                        __tegra20_wdt_stop(wdt)
                                        clear_bit(WDOG_HW_RUNNING, ...)
                                        mutex_unlock(&wdt->lock)
    set_bit(WDOG_ACTIVE, ...)
    set_bit(WDOG_HW_RUNNING, ...)  <-- set after watchdog was stopped

This leaves WDOG_ACTIVE and WDOG_HW_RUNNING set and allows open() to
succeed after the VDE partition was powered off and the hardware timer was
stopped.

If WDOG_HW_RUNNING was already set at boot, a similar race in
watchdog_open() either calls __watchdog_ping() -> tegra20_wdt_ping() (which
does not check wdt->vde_off and returns 0) or sees WDOG_HW_RUNNING cleared
before watchdog_start() and calls module_put() at out_mod without a
matching try_module_get() in watchdog_open().

> @@ -296,6 +350,45 @@ static const struct watchdog_ops tegra20_wdt_ops = {
>  	.set_timeout = tegra20_wdt_set_timeout,
>  };
>  
> +static int tegra20_wdt_powergate_notify(struct notifier_block *nb,
> +					unsigned long event, void *data)
> +{
> +	struct tegra_wdt *wdt = container_of(nb, struct tegra_wdt,
> +					     powergate_nb);
> +	unsigned int id = *(unsigned int *)data;
> +
> +	if (event != TEGRA_PMC_POWERGATE_POWER_OFF ||
> +	    id != TEGRA_POWERGATE_VDEC)
> +		return NOTIFY_DONE;
> +
> +	mutex_lock(&wdt->lock);
> +
> +	if (!wdt->vde_off) {
> +		wdt->vde_off = true;
> +		__tegra20_wdt_stop(wdt);
> +
> +		/*
> +		 * Stop the core's pings. If user space has the device open,
> +		 * its pings don't restart the watchdog either.
> +		 */
> +		clear_bit(WDOG_HW_RUNNING, &wdt->wdd.status);

[Severity: Medium]
Does clearing only WDOG_HW_RUNNING here leave WDOG_ACTIVE set when
/dev/watchdog is already open?

If user space later closes /dev/watchdog without the magic close character
(or with nowayout enabled), watchdog_release() leaves WDOG_ACTIVE set while
dropping the module and device references because WDOG_HW_RUNNING is false:

drivers/watchdog/watchdog_dev.c:watchdog_release() {
    ...
done:
	running = wdd && watchdog_hw_running(wdd);
	mutex_unlock(&wd_data->lock);
	if (!running) {
		module_put(wd_data->cdev.owner);
		put_device(&wd_data->dev);
	}
	return 0;
}

On a subsequent open(/dev/watchdog), watchdog_start() sees
watchdog_active(wdd) == true and returns 0 immediately without calling
tegra20_wdt_start(), so opening the stopped watchdog succeeds instead of
failing with -EBUSY.  While the file remains open, WDIOC_SETTIMEOUT also
sees watchdog_active(wdd) in tegra20_wdt_set_timeout(), updates
wdd->timeout, and then fails with -EBUSY in tegra20_wdt_start().

> +
> +		dev_info(wdt->wdd.parent,
> +			 "VDE powered off, watchdog stopped\n");
> +	}
> +
> +	mutex_unlock(&wdt->lock);
> +
> +	return NOTIFY_OK;
> +}

[ ... ]

> @@ -326,18 +419,46 @@ static int tegra20_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
>  	/*
>  	 * The bootloader may leave the watchdog running. Take it over with
>  	 * our own timeout and timer 1, and let the core ping it until user
>  	 * space opens the device.
>  	 */
> -	if (readl(wdt->rst_source) & T20_RST_SOURCE_WDT_EN) {
> +	mutex_lock(&wdt->lock);
> +
> +	if (!wdt->vde_off &&
> +	    (readl(wdt->rst_source) & T20_RST_SOURCE_WDT_EN)) {
>  		dev_info(dev, "watchdog left running by the bootloader, taking it over (timeout %u sec)\n",
>  			 wdt->wdd.timeout);
> -		tegra20_wdt_start(&wdt->wdd);
> +		__tegra20_wdt_start(wdt);
>  		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
>  	}

[Severity: Medium]
Does clearing WDOG_HW_RUNNING in tegra20_wdt_powergate_notify() leak the
module and wd_data->dev references acquired for a boot-enabled watchdog?

When WDOG_HW_RUNNING is set here before devm_watchdog_register_device(),
watchdog_cdev_register() takes a module reference and a device reference:

drivers/watchdog/watchdog_dev.c:watchdog_cdev_register() {
    ...
	if (watchdog_hw_running(wdd)) {
		__module_get(wdd->ops->owner);
		get_device(&wd_data->dev);
    ...
}

The watchdog core only drops those references after user space opens the
device while WDOG_HW_RUNNING is still set and later closes it with
WDOG_HW_RUNNING cleared.  If the VDE partition powers off before
/dev/watchdog is opened, tegra20_wdt_powergate_notify() clears
WDOG_HW_RUNNING and sets wdt->vde_off = true, so any later watchdog_open()
fails with -EBUSY in tegra20_wdt_start() and those references are never
released, preventing the module from being unloaded.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-t20-for-upstream-v1-0-5923449951fe@gmail.com?part=6

      reply	other threads:[~2026-11-10 12:56 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 12:39 [PATCH 0/6] watchdog: tegra_wdt: Add Tegra20 support Artur Kowalski
2026-11-10 12:39 ` [PATCH 1/6] clocksource/drivers/timer-tegra: Keep Tegra20's watchdog timers free Artur Kowalski
2026-11-10 12:48   ` sashiko-bot
2026-11-10 12:39 ` [PATCH 2/6] watchdog: tegra_wdt: Move SoC specific parts into match data Artur Kowalski
2026-11-10 12:39 ` [PATCH 3/6] watchdog: tegra_wdt: Support Tegra20 Artur Kowalski
2026-11-10 16:21   ` Krzysztof Kozlowski
2026-11-10 12:39 ` [PATCH 4/6] soc/tegra: pmc: Add an option to keep the VDE partition on Artur Kowalski
2026-11-10 12:39 ` [PATCH 5/6] soc/tegra: pmc: Add a partition power off notifier Artur Kowalski
2026-11-10 12:39 ` [PATCH 6/6] watchdog: tegra_wdt: Stop on Tegra20 VDE power off Artur Kowalski
2026-11-10 12:56   ` sashiko-bot [this message]

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-207056@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=arturkow2000@gmail.com \
    --cc=linux-watchdog@vger.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