Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <aplattner@nvidia.com>
Acked-by: Timur Tabi <ttabi@nvidia.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20250721230640.2244915-1-aplattner@nvidia.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>

authored by

Aaron Plattner and committed by
Wim Van Sebroeck
48defdf6 ac3dbb91

+47 -3
+47 -3
drivers/watchdog/sbsa_gwdt.c
··· 75 75 #define SBSA_GWDT_VERSION_MASK 0xF 76 76 #define SBSA_GWDT_VERSION_SHIFT 16 77 77 78 + #define SBSA_GWDT_IMPL_MASK 0x7FF 79 + #define SBSA_GWDT_IMPL_SHIFT 0 80 + #define SBSA_GWDT_IMPL_MEDIATEK 0x426 81 + 78 82 /** 79 83 * struct sbsa_gwdt - Internal representation of the SBSA GWDT 80 84 * @wdd: kernel watchdog_device structure 81 85 * @clk: store the System Counter clock frequency, in Hz. 82 86 * @version: store the architecture version 87 + * @need_ws0_race_workaround: 88 + * indicate whether to adjust wdd->timeout to avoid a race with WS0 83 89 * @refresh_base: Virtual address of the watchdog refresh frame 84 90 * @control_base: Virtual address of the watchdog control frame 85 91 */ ··· 93 87 struct watchdog_device wdd; 94 88 u32 clk; 95 89 int version; 90 + bool need_ws0_race_workaround; 96 91 void __iomem *refresh_base; 97 92 void __iomem *control_base; 98 93 }; ··· 168 161 */ 169 162 sbsa_gwdt_reg_write(((u64)gwdt->clk / 2) * timeout, gwdt); 170 163 164 + /* 165 + * Some watchdog hardware has a race condition where it will ignore 166 + * sbsa_gwdt_keepalive() if it is called at the exact moment that a 167 + * timeout occurs and WS0 is being asserted. Unfortunately, the default 168 + * behavior of the watchdog core is very likely to trigger this race 169 + * when action=0 because it programs WOR to be half of the desired 170 + * timeout, and watchdog_next_keepalive() chooses the exact same time to 171 + * send keepalive pings. 172 + * 173 + * This triggers a race where sbsa_gwdt_keepalive() can be called right 174 + * as WS0 is being asserted, and affected hardware will ignore that 175 + * write and continue to assert WS0. After another (timeout / 2) 176 + * seconds, the same race happens again. If the driver wins then the 177 + * explicit refresh will reset WS0 to false but if the hardware wins, 178 + * then WS1 is asserted and the system resets. 179 + * 180 + * Avoid the problem by scheduling keepalive heartbeats one second later 181 + * than the WOR timeout. 182 + * 183 + * This workaround might not be needed in a future revision of the 184 + * hardware. 185 + */ 186 + if (gwdt->need_ws0_race_workaround) 187 + wdd->min_hw_heartbeat_ms = timeout * 500 + 1000; 188 + 171 189 return 0; 172 190 } 173 191 ··· 234 202 static void sbsa_gwdt_get_version(struct watchdog_device *wdd) 235 203 { 236 204 struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); 237 - int ver; 205 + int iidr, ver, impl; 238 206 239 - ver = readl(gwdt->control_base + SBSA_GWDT_W_IIDR); 240 - ver = (ver >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK; 207 + iidr = readl(gwdt->control_base + SBSA_GWDT_W_IIDR); 208 + ver = (iidr >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK; 209 + impl = (iidr >> SBSA_GWDT_IMPL_SHIFT) & SBSA_GWDT_IMPL_MASK; 241 210 242 211 gwdt->version = ver; 212 + gwdt->need_ws0_race_workaround = 213 + !action && (impl == SBSA_GWDT_IMPL_MEDIATEK); 243 214 } 244 215 245 216 static int sbsa_gwdt_start(struct watchdog_device *wdd) ··· 333 298 wdd->max_hw_heartbeat_ms = U32_MAX / gwdt->clk * 1000; 334 299 else 335 300 wdd->max_hw_heartbeat_ms = GENMASK_ULL(47, 0) / gwdt->clk * 1000; 301 + 302 + if (gwdt->need_ws0_race_workaround) { 303 + /* 304 + * A timeout of 3 seconds means that WOR will be set to 1.5 305 + * seconds and the heartbeat will be scheduled every 2.5 306 + * seconds. 307 + */ 308 + wdd->min_timeout = 3; 309 + } 336 310 337 311 status = readl(cf_base + SBSA_GWDT_WCS); 338 312 if (status & SBSA_GWDT_WCS_WS1) {