| .. | .. |
|---|
| 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"); |
|---|