Skip to contentSkip to conversation

watchdog: sbsa_wdog: Make sure the timeout programming is within the limits

PATCH In review Tyler Hicks (Microsoft) 2 participants 2 messages Apr 13, 2023 – Apr 14, 2023

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 watchdog: sbsa_wdog: Make sure the timeout programming is within the limits

From: George Cherian <[email protected]>

[ Upstream commit 000987a38b53c172f435142a4026dd71378ca464 ]

Make sure to honour the max_hw_heartbeat_ms while programming the timeout value to WOR. Clamp the timeout passed to sbsa_gwdt_set_timeout() to make sure the programmed value is within the permissible range.

Fixes abd3ac7902fb ("watchdog: sbsa: Support architecture version 1")

---

The Fixes line in the original commit is incorrect. This commit fixes a bug that goes all the way back to v4.6 commit 57d2caaabfc7 ("Watchdog:
introduce ARM SBSA watchdog driver") when only 32-bit Watchdog Offset Registers (WOR) were supported.

Without this fix, there's a truncation on the first argument, of u32 type, passed to writel() in the following situation situation:

Generic Watchdog architecture version is 1 (WOR is 32-bit) action is 1
timeout is 240s
CNTFRQ_EL0 is 25000050 Hz
wdd.max_hw_heartbeat_ms is 171s

25000050 * 240 = 6000012000 <--- requires 33 bits to store 6000012000 & 0xFFFFFFFF = 1705044704 <--- truncated value written to WOR 1705044704 / 25000050 = 68.2s <--- timeout incorrectly set to 68.2s

The timeout from userspace is greater than wdd.max_hw_heartbeat_ms so the watchdog core pings at 69s (240 - 171) which results in intermittent and unexpected panics (action=1).

With this patch applied, the timeout passed to writel() never exceeds 32-bits and the watchdog core + systemd keeps the watchdog happy.

I've validated this fix on real hardware running a linux-5.10.y stable kernel. Please apply this patch to 5.10 through 4.14. Thanks!

Tyler

 drivers/watchdog/sbsa_gwdt.c | 1 +
 1 file changed, 1 insertion(+)
drivers/watchdog/sbsa_gwdt.c +1 -0
index f0f1e3b2e463..4cbe6ba52754 100644
@@ -121,6 +121,7 @@ static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
 	struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
 
 	wdd->timeout = timeout;
+	timeout = clamp_t(unsigned int, timeout, 1, wdd->max_hw_heartbeat_ms / 1000);
 
 	if (action)
 		writel(gwdt->clk * timeout,
Signature · 1 line
2.34.1
maintainer Reviewed-by on v1 1/1
#1 lore raw
Quoted 8 lines · Tyler Hicks (Microsoft)

From: George Cherian <[email protected]>

[ Upstream commit 000987a38b53c172f435142a4026dd71378ca464 ]

Make sure to honour the max_hw_heartbeat_ms while programming the timeout value to WOR. Clamp the timeout passed to sbsa_gwdt_set_timeout() to make sure the programmed value is within the permissible range.

Quoted 8 lines
Fixes abd3ac7902fb ("watchdog: sbsa: Support architecture version 1")
Quoted 47 lines

---

The Fixes line in the original commit is incorrect. This commit fixes a bug that goes all the way back to v4.6 commit 57d2caaabfc7 ("Watchdog:
introduce ARM SBSA watchdog driver") when only 32-bit Watchdog Offset Registers (WOR) were supported.

Without this fix, there's a truncation on the first argument, of u32 type, passed to writel() in the following situation situation:

Generic Watchdog architecture version is 1 (WOR is 32-bit) action is 1
timeout is 240s
CNTFRQ_EL0 is 25000050 Hz
wdd.max_hw_heartbeat_ms is 171s

25000050 * 240 = 6000012000 <--- requires 33 bits to store 6000012000 & 0xFFFFFFFF = 1705044704 <--- truncated value written to WOR 1705044704 / 25000050 = 68.2s <--- timeout incorrectly set to 68.2s

The timeout from userspace is greater than wdd.max_hw_heartbeat_ms so the watchdog core pings at 69s (240 - 171) which results in intermittent and unexpected panics (action=1).

With this patch applied, the timeout passed to writel() never exceeds 32-bits and the watchdog core + systemd keeps the watchdog happy.

I've validated this fix on real hardware running a linux-5.10.y stable kernel. Please apply this patch to 5.10 through 4.14. Thanks!

Tyler

  drivers/watchdog/sbsa_gwdt.c | 1 +
  1 file changed, 1 insertion(+)
drivers/watchdog/sbsa_gwdt.c +1 -0
index f0f1e3b2e463..4cbe6ba52754 100644
@@ -121,6 +121,7 @@ static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
  	struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
 
  	wdd->timeout = timeout;
+	timeout = clamp_t(unsigned int, timeout, 1, wdd->max_hw_heartbeat_ms / 1000);
 
  	if (action)
  		writel(gwdt->clk * timeout,