.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * HPE WatchDog Driver |
---|
3 | 4 | * based on |
---|
.. | .. |
---|
6 | 7 | * |
---|
7 | 8 | * (c) Copyright 2018 Hewlett Packard Enterprise Development LP |
---|
8 | 9 | * Thomas Mingarelli <thomas.mingarelli@hpe.com> |
---|
9 | | - * |
---|
10 | | - * This program is free software; you can redistribute it and/or |
---|
11 | | - * modify it under the terms of the GNU General Public License |
---|
12 | | - * version 2 as published by the Free Software Foundation |
---|
13 | | - * |
---|
14 | 10 | */ |
---|
15 | 11 | |
---|
16 | 12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
---|
.. | .. |
---|
26 | 22 | #include <linux/watchdog.h> |
---|
27 | 23 | #include <asm/nmi.h> |
---|
28 | 24 | |
---|
29 | | -#define HPWDT_VERSION "2.0.0" |
---|
| 25 | +#define HPWDT_VERSION "2.0.3" |
---|
30 | 26 | #define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) |
---|
31 | 27 | #define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) |
---|
32 | | -#define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) |
---|
| 28 | +#define HPWDT_MAX_TICKS 65535 |
---|
| 29 | +#define HPWDT_MAX_TIMER TICKS_TO_SECS(HPWDT_MAX_TICKS) |
---|
33 | 30 | #define DEFAULT_MARGIN 30 |
---|
34 | 31 | #define PRETIMEOUT_SEC 9 |
---|
35 | 32 | |
---|
.. | .. |
---|
37 | 34 | static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ |
---|
38 | 35 | static bool nowayout = WATCHDOG_NOWAYOUT; |
---|
39 | 36 | static bool pretimeout = IS_ENABLED(CONFIG_HPWDT_NMI_DECODING); |
---|
| 37 | +static int kdumptimeout = -1; |
---|
40 | 38 | |
---|
41 | 39 | static void __iomem *pci_mem_addr; /* the PCI-memory address */ |
---|
42 | 40 | static unsigned long __iomem *hpwdt_nmistat; |
---|
.. | .. |
---|
50 | 48 | }; |
---|
51 | 49 | MODULE_DEVICE_TABLE(pci, hpwdt_devices); |
---|
52 | 50 | |
---|
| 51 | +static const struct pci_device_id hpwdt_blacklist[] = { |
---|
| 52 | + { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP, 0x1979) }, /* auxilary iLO */ |
---|
| 53 | + { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP_3PAR, 0x0289) }, /* CL */ |
---|
| 54 | + {0}, /* terminate list */ |
---|
| 55 | +}; |
---|
53 | 56 | |
---|
| 57 | +static struct watchdog_device hpwdt_dev; |
---|
54 | 58 | /* |
---|
55 | 59 | * Watchdog operations |
---|
56 | 60 | */ |
---|
| 61 | +static int hpwdt_hw_is_running(void) |
---|
| 62 | +{ |
---|
| 63 | + return ioread8(hpwdt_timer_con) & 0x01; |
---|
| 64 | +} |
---|
| 65 | + |
---|
57 | 66 | static int hpwdt_start(struct watchdog_device *wdd) |
---|
58 | 67 | { |
---|
59 | 68 | int control = 0x81 | (pretimeout ? 0x4 : 0); |
---|
60 | | - int reload = SECS_TO_TICKS(wdd->timeout); |
---|
| 69 | + int reload = SECS_TO_TICKS(min(wdd->timeout, wdd->max_hw_heartbeat_ms/1000)); |
---|
61 | 70 | |
---|
62 | | - dev_dbg(wdd->parent, "start watchdog 0x%08x:0x%02x\n", reload, control); |
---|
| 71 | + dev_dbg(wdd->parent, "start watchdog 0x%08x:0x%08x:0x%02x\n", wdd->timeout, reload, control); |
---|
63 | 72 | iowrite16(reload, hpwdt_timer_reg); |
---|
64 | 73 | iowrite8(control, hpwdt_timer_con); |
---|
65 | 74 | |
---|
.. | .. |
---|
84 | 93 | return 0; |
---|
85 | 94 | } |
---|
86 | 95 | |
---|
| 96 | +static void hpwdt_ping_ticks(int val) |
---|
| 97 | +{ |
---|
| 98 | + val = min(val, HPWDT_MAX_TICKS); |
---|
| 99 | + iowrite16(val, hpwdt_timer_reg); |
---|
| 100 | +} |
---|
| 101 | + |
---|
87 | 102 | static int hpwdt_ping(struct watchdog_device *wdd) |
---|
88 | 103 | { |
---|
89 | | - int reload = SECS_TO_TICKS(wdd->timeout); |
---|
| 104 | + int reload = SECS_TO_TICKS(min(wdd->timeout, wdd->max_hw_heartbeat_ms/1000)); |
---|
90 | 105 | |
---|
91 | | - dev_dbg(wdd->parent, "ping watchdog 0x%08x\n", reload); |
---|
92 | | - iowrite16(reload, hpwdt_timer_reg); |
---|
| 106 | + dev_dbg(wdd->parent, "ping watchdog 0x%08x:0x%08x\n", wdd->timeout, reload); |
---|
| 107 | + hpwdt_ping_ticks(reload); |
---|
93 | 108 | |
---|
94 | 109 | return 0; |
---|
95 | 110 | } |
---|
.. | .. |
---|
162 | 177 | if (ilo5 && ulReason == NMI_UNKNOWN && !mynmi) |
---|
163 | 178 | return NMI_DONE; |
---|
164 | 179 | |
---|
165 | | - if (ilo5 && !pretimeout) |
---|
| 180 | + if (ilo5 && !pretimeout && !mynmi) |
---|
166 | 181 | return NMI_DONE; |
---|
167 | 182 | |
---|
168 | | - hpwdt_stop(); |
---|
| 183 | + if (kdumptimeout < 0) |
---|
| 184 | + hpwdt_stop(); |
---|
| 185 | + else if (kdumptimeout == 0) |
---|
| 186 | + ; |
---|
| 187 | + else { |
---|
| 188 | + unsigned int val = max((unsigned int)kdumptimeout, hpwdt_dev.timeout); |
---|
| 189 | + hpwdt_ping_ticks(SECS_TO_TICKS(val)); |
---|
| 190 | + } |
---|
169 | 191 | |
---|
170 | 192 | hex_byte_pack(panic_msg, mynmi); |
---|
171 | 193 | nmi_panic(regs, panic_msg); |
---|
.. | .. |
---|
203 | 225 | .info = &ident, |
---|
204 | 226 | .ops = &hpwdt_ops, |
---|
205 | 227 | .min_timeout = 1, |
---|
206 | | - .max_timeout = HPWDT_MAX_TIMER, |
---|
207 | 228 | .timeout = DEFAULT_MARGIN, |
---|
208 | | -#ifdef CONFIG_HPWDT_NMI_DECODING |
---|
209 | 229 | .pretimeout = PRETIMEOUT_SEC, |
---|
210 | | -#endif |
---|
| 230 | + .max_hw_heartbeat_ms = HPWDT_MAX_TIMER * 1000, |
---|
211 | 231 | }; |
---|
212 | 232 | |
---|
213 | 233 | |
---|
.. | .. |
---|
276 | 296 | return -ENODEV; |
---|
277 | 297 | } |
---|
278 | 298 | |
---|
279 | | - /* |
---|
280 | | - * Ignore all auxilary iLO devices with the following PCI ID |
---|
281 | | - */ |
---|
282 | | - if (dev->subsystem_vendor == PCI_VENDOR_ID_HP && |
---|
283 | | - dev->subsystem_device == 0x1979) |
---|
| 299 | + if (pci_match_id(hpwdt_blacklist, dev)) { |
---|
| 300 | + dev_dbg(&dev->dev, "Not supported on this device\n"); |
---|
284 | 301 | return -ENODEV; |
---|
| 302 | + } |
---|
285 | 303 | |
---|
286 | 304 | if (pci_enable_device(dev)) { |
---|
287 | 305 | dev_warn(&dev->dev, |
---|
.. | .. |
---|
301 | 319 | hpwdt_timer_reg = pci_mem_addr + 0x70; |
---|
302 | 320 | hpwdt_timer_con = pci_mem_addr + 0x72; |
---|
303 | 321 | |
---|
304 | | - /* Make sure that timer is disabled until /dev/watchdog is opened */ |
---|
305 | | - hpwdt_stop(); |
---|
| 322 | + /* Have the core update running timer until user space is ready */ |
---|
| 323 | + if (hpwdt_hw_is_running()) { |
---|
| 324 | + dev_info(&dev->dev, "timer is running\n"); |
---|
| 325 | + set_bit(WDOG_HW_RUNNING, &hpwdt_dev.status); |
---|
| 326 | + } |
---|
306 | 327 | |
---|
307 | 328 | /* Initialize NMI Decoding functionality */ |
---|
308 | 329 | retval = hpwdt_init_nmi_decoding(dev); |
---|
309 | 330 | if (retval != 0) |
---|
310 | 331 | goto error_init_nmi_decoding; |
---|
311 | 332 | |
---|
| 333 | + watchdog_stop_on_unregister(&hpwdt_dev); |
---|
312 | 334 | watchdog_set_nowayout(&hpwdt_dev, nowayout); |
---|
313 | | - if (watchdog_init_timeout(&hpwdt_dev, soft_margin, NULL)) |
---|
314 | | - dev_warn(&dev->dev, "Invalid soft_margin: %d.\n", soft_margin); |
---|
| 335 | + watchdog_init_timeout(&hpwdt_dev, soft_margin, NULL); |
---|
| 336 | + |
---|
| 337 | + if (pretimeout && hpwdt_dev.timeout <= PRETIMEOUT_SEC) { |
---|
| 338 | + dev_warn(&dev->dev, "timeout <= pretimeout. Setting pretimeout to zero\n"); |
---|
| 339 | + pretimeout = 0; |
---|
| 340 | + } |
---|
| 341 | + hpwdt_dev.pretimeout = pretimeout ? PRETIMEOUT_SEC : 0; |
---|
| 342 | + kdumptimeout = min(kdumptimeout, HPWDT_MAX_TIMER); |
---|
315 | 343 | |
---|
316 | 344 | hpwdt_dev.parent = &dev->dev; |
---|
317 | 345 | retval = watchdog_register_device(&hpwdt_dev); |
---|
318 | | - if (retval < 0) { |
---|
319 | | - dev_err(&dev->dev, "watchdog register failed: %d.\n", retval); |
---|
| 346 | + if (retval < 0) |
---|
320 | 347 | goto error_wd_register; |
---|
321 | | - } |
---|
322 | 348 | |
---|
323 | | - dev_info(&dev->dev, "HPE Watchdog Timer Driver: %s" |
---|
324 | | - ", timer margin: %d seconds (nowayout=%d).\n", |
---|
325 | | - HPWDT_VERSION, hpwdt_dev.timeout, nowayout); |
---|
| 349 | + dev_info(&dev->dev, "HPE Watchdog Timer Driver: Version: %s\n", |
---|
| 350 | + HPWDT_VERSION); |
---|
| 351 | + dev_info(&dev->dev, "timeout: %d seconds (nowayout=%d)\n", |
---|
| 352 | + hpwdt_dev.timeout, nowayout); |
---|
| 353 | + dev_info(&dev->dev, "pretimeout: %s.\n", |
---|
| 354 | + pretimeout ? "on" : "off"); |
---|
| 355 | + dev_info(&dev->dev, "kdumptimeout: %d.\n", kdumptimeout); |
---|
326 | 356 | |
---|
327 | 357 | if (dev->subsystem_vendor == PCI_VENDOR_ID_HP_3PAR) |
---|
328 | 358 | ilo5 = true; |
---|
.. | .. |
---|
340 | 370 | |
---|
341 | 371 | static void hpwdt_exit(struct pci_dev *dev) |
---|
342 | 372 | { |
---|
343 | | - if (!nowayout) |
---|
344 | | - hpwdt_stop(); |
---|
345 | | - |
---|
346 | 373 | watchdog_unregister_device(&hpwdt_dev); |
---|
347 | 374 | hpwdt_exit_nmi_decoding(); |
---|
348 | 375 | pci_iounmap(dev, pci_mem_addr); |
---|
.. | .. |
---|
364 | 391 | module_param(soft_margin, int, 0); |
---|
365 | 392 | MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); |
---|
366 | 393 | |
---|
| 394 | +module_param_named(timeout, soft_margin, int, 0); |
---|
| 395 | +MODULE_PARM_DESC(timeout, "Alias of soft_margin"); |
---|
| 396 | + |
---|
367 | 397 | module_param(nowayout, bool, 0); |
---|
368 | 398 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
---|
369 | 399 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
---|
370 | 400 | |
---|
| 401 | +module_param(kdumptimeout, int, 0444); |
---|
| 402 | +MODULE_PARM_DESC(kdumptimeout, "Timeout applied for crash kernel transition in seconds"); |
---|
| 403 | + |
---|
371 | 404 | #ifdef CONFIG_HPWDT_NMI_DECODING |
---|
372 | 405 | module_param(pretimeout, bool, 0); |
---|
373 | 406 | MODULE_PARM_DESC(pretimeout, "Watchdog pretimeout enabled"); |
---|