Skip to content

Commit 48defdf

Browse files
aaronp24Wim Van Sebroeck
authored andcommitted
watchdog: sbsa: Adjust keepalive timeout to avoid MediaTek WS0 race condition
The MediaTek implementation of the sbsa_gwdt watchdog has a race condition where a write to SBSA_GWDT_WRR is ignored if it occurs while the hardware is processing a timeout refresh that asserts WS0. Detect this based on the hardware implementer and adjust wdd->min_hw_heartbeat_ms to avoid the race by forcing the keepalive ping to be one second later. Signed-off-by: Aaron Plattner <[email protected]> Acked-by: Timur Tabi <[email protected]> Reviewed-by: Guenter Roeck <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Guenter Roeck <[email protected]> Signed-off-by: Wim Van Sebroeck <[email protected]>
1 parent ac3dbb9 commit 48defdf

File tree

1 file changed

+47
-3
lines changed

1 file changed

+47
-3
lines changed

drivers/watchdog/sbsa_gwdt.c

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,25 @@
7575
#define SBSA_GWDT_VERSION_MASK 0xF
7676
#define SBSA_GWDT_VERSION_SHIFT 16
7777

78+
#define SBSA_GWDT_IMPL_MASK 0x7FF
79+
#define SBSA_GWDT_IMPL_SHIFT 0
80+
#define SBSA_GWDT_IMPL_MEDIATEK 0x426
81+
7882
/**
7983
* struct sbsa_gwdt - Internal representation of the SBSA GWDT
8084
* @wdd: kernel watchdog_device structure
8185
* @clk: store the System Counter clock frequency, in Hz.
8286
* @version: store the architecture version
87+
* @need_ws0_race_workaround:
88+
* indicate whether to adjust wdd->timeout to avoid a race with WS0
8389
* @refresh_base: Virtual address of the watchdog refresh frame
8490
* @control_base: Virtual address of the watchdog control frame
8591
*/
8692
struct sbsa_gwdt {
8793
struct watchdog_device wdd;
8894
u32 clk;
8995
int version;
96+
bool need_ws0_race_workaround;
9097
void __iomem *refresh_base;
9198
void __iomem *control_base;
9299
};
@@ -161,6 +168,31 @@ static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
161168
*/
162169
sbsa_gwdt_reg_write(((u64)gwdt->clk / 2) * timeout, gwdt);
163170

171+
/*
172+
* Some watchdog hardware has a race condition where it will ignore
173+
* sbsa_gwdt_keepalive() if it is called at the exact moment that a
174+
* timeout occurs and WS0 is being asserted. Unfortunately, the default
175+
* behavior of the watchdog core is very likely to trigger this race
176+
* when action=0 because it programs WOR to be half of the desired
177+
* timeout, and watchdog_next_keepalive() chooses the exact same time to
178+
* send keepalive pings.
179+
*
180+
* This triggers a race where sbsa_gwdt_keepalive() can be called right
181+
* as WS0 is being asserted, and affected hardware will ignore that
182+
* write and continue to assert WS0. After another (timeout / 2)
183+
* seconds, the same race happens again. If the driver wins then the
184+
* explicit refresh will reset WS0 to false but if the hardware wins,
185+
* then WS1 is asserted and the system resets.
186+
*
187+
* Avoid the problem by scheduling keepalive heartbeats one second later
188+
* than the WOR timeout.
189+
*
190+
* This workaround might not be needed in a future revision of the
191+
* hardware.
192+
*/
193+
if (gwdt->need_ws0_race_workaround)
194+
wdd->min_hw_heartbeat_ms = timeout * 500 + 1000;
195+
164196
return 0;
165197
}
166198

@@ -202,12 +234,15 @@ static int sbsa_gwdt_keepalive(struct watchdog_device *wdd)
202234
static void sbsa_gwdt_get_version(struct watchdog_device *wdd)
203235
{
204236
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
205-
int ver;
237+
int iidr, ver, impl;
206238

207-
ver = readl(gwdt->control_base + SBSA_GWDT_W_IIDR);
208-
ver = (ver >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK;
239+
iidr = readl(gwdt->control_base + SBSA_GWDT_W_IIDR);
240+
ver = (iidr >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK;
241+
impl = (iidr >> SBSA_GWDT_IMPL_SHIFT) & SBSA_GWDT_IMPL_MASK;
209242

210243
gwdt->version = ver;
244+
gwdt->need_ws0_race_workaround =
245+
!action && (impl == SBSA_GWDT_IMPL_MEDIATEK);
211246
}
212247

213248
static int sbsa_gwdt_start(struct watchdog_device *wdd)
@@ -299,6 +334,15 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
299334
else
300335
wdd->max_hw_heartbeat_ms = GENMASK_ULL(47, 0) / gwdt->clk * 1000;
301336

337+
if (gwdt->need_ws0_race_workaround) {
338+
/*
339+
* A timeout of 3 seconds means that WOR will be set to 1.5
340+
* seconds and the heartbeat will be scheduled every 2.5
341+
* seconds.
342+
*/
343+
wdd->min_timeout = 3;
344+
}
345+
302346
status = readl(cf_base + SBSA_GWDT_WCS);
303347
if (status & SBSA_GWDT_WCS_WS1) {
304348
dev_warn(dev, "System reset by WDT.\n");

0 commit comments

Comments
 (0)