linux/drivers/watchdog/hpwdt.c
<<
>>
Prefs
   1/*
   2 *      HPE WatchDog Driver
   3 *      based on
   4 *
   5 *      SoftDog 0.05:   A Software Watchdog Device
   6 *
   7 *      (c) Copyright 2018 Hewlett Packard Enterprise Development LP
   8 *      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 */
  15
  16#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  17
  18#include <linux/device.h>
  19#include <linux/io.h>
  20#include <linux/kernel.h>
  21#include <linux/module.h>
  22#include <linux/moduleparam.h>
  23#include <linux/pci.h>
  24#include <linux/pci_ids.h>
  25#include <linux/types.h>
  26#include <linux/watchdog.h>
  27#include <asm/nmi.h>
  28
  29#define HPWDT_VERSION                   "2.0.2"
  30#define SECS_TO_TICKS(secs)             ((secs) * 1000 / 128)
  31#define TICKS_TO_SECS(ticks)            ((ticks) * 128 / 1000)
  32#define HPWDT_MAX_TIMER                 TICKS_TO_SECS(65535)
  33#define DEFAULT_MARGIN                  30
  34#define PRETIMEOUT_SEC                  9
  35
  36static bool ilo5;
  37static unsigned int soft_margin = DEFAULT_MARGIN;       /* in seconds */
  38static bool nowayout = WATCHDOG_NOWAYOUT;
  39static bool pretimeout = IS_ENABLED(CONFIG_HPWDT_NMI_DECODING);
  40
  41static void __iomem *pci_mem_addr;              /* the PCI-memory address */
  42static unsigned long __iomem *hpwdt_nmistat;
  43static unsigned long __iomem *hpwdt_timer_reg;
  44static unsigned long __iomem *hpwdt_timer_con;
  45
  46static const struct pci_device_id hpwdt_devices[] = {
  47        { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) },   /* iLO2 */
  48        { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) },       /* iLO3 */
  49        {0},                    /* terminate list */
  50};
  51MODULE_DEVICE_TABLE(pci, hpwdt_devices);
  52
  53static const struct pci_device_id hpwdt_blacklist[] = {
  54        { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP, 0x1979) }, /* auxilary iLO */
  55        { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP_3PAR, 0x0289) },  /* CL */
  56        {0},                    /* terminate list */
  57};
  58
  59/*
  60 *      Watchdog operations
  61 */
  62static int hpwdt_start(struct watchdog_device *wdd)
  63{
  64        int control = 0x81 | (pretimeout ? 0x4 : 0);
  65        int reload = SECS_TO_TICKS(wdd->timeout);
  66
  67        dev_dbg(wdd->parent, "start watchdog 0x%08x:0x%02x\n", reload, control);
  68        iowrite16(reload, hpwdt_timer_reg);
  69        iowrite8(control, hpwdt_timer_con);
  70
  71        return 0;
  72}
  73
  74static void hpwdt_stop(void)
  75{
  76        unsigned long data;
  77
  78        pr_debug("stop  watchdog\n");
  79
  80        data = ioread8(hpwdt_timer_con);
  81        data &= 0xFE;
  82        iowrite8(data, hpwdt_timer_con);
  83}
  84
  85static int hpwdt_stop_core(struct watchdog_device *wdd)
  86{
  87        hpwdt_stop();
  88
  89        return 0;
  90}
  91
  92static int hpwdt_ping(struct watchdog_device *wdd)
  93{
  94        int reload = SECS_TO_TICKS(wdd->timeout);
  95
  96        dev_dbg(wdd->parent, "ping  watchdog 0x%08x\n", reload);
  97        iowrite16(reload, hpwdt_timer_reg);
  98
  99        return 0;
 100}
 101
 102static unsigned int hpwdt_gettimeleft(struct watchdog_device *wdd)
 103{
 104        return TICKS_TO_SECS(ioread16(hpwdt_timer_reg));
 105}
 106
 107static int hpwdt_settimeout(struct watchdog_device *wdd, unsigned int val)
 108{
 109        dev_dbg(wdd->parent, "set_timeout = %d\n", val);
 110
 111        wdd->timeout = val;
 112        if (val <= wdd->pretimeout) {
 113                dev_dbg(wdd->parent, "pretimeout < timeout. Setting to zero\n");
 114                wdd->pretimeout = 0;
 115                pretimeout = 0;
 116                if (watchdog_active(wdd))
 117                        hpwdt_start(wdd);
 118        }
 119        hpwdt_ping(wdd);
 120
 121        return 0;
 122}
 123
 124#ifdef CONFIG_HPWDT_NMI_DECODING
 125static int hpwdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req)
 126{
 127        unsigned int val = 0;
 128
 129        dev_dbg(wdd->parent, "set_pretimeout = %d\n", req);
 130        if (req) {
 131                val = PRETIMEOUT_SEC;
 132                if (val >= wdd->timeout)
 133                        return -EINVAL;
 134        }
 135
 136        if (val != req)
 137                dev_dbg(wdd->parent, "Rounding pretimeout to: %d\n", val);
 138
 139        wdd->pretimeout = val;
 140        pretimeout = !!val;
 141
 142        if (watchdog_active(wdd))
 143                hpwdt_start(wdd);
 144
 145        return 0;
 146}
 147
 148static int hpwdt_my_nmi(void)
 149{
 150        return ioread8(hpwdt_nmistat) & 0x6;
 151}
 152
 153/*
 154 *      NMI Handler
 155 */
 156static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs)
 157{
 158        unsigned int mynmi = hpwdt_my_nmi();
 159        static char panic_msg[] =
 160                "00: An NMI occurred. Depending on your system the reason "
 161                "for the NMI is logged in any one of the following resources:\n"
 162                "1. Integrated Management Log (IML)\n"
 163                "2. OA Syslog\n"
 164                "3. OA Forward Progress Log\n"
 165                "4. iLO Event Log";
 166
 167        if (ilo5 && ulReason == NMI_UNKNOWN && !mynmi)
 168                return NMI_DONE;
 169
 170        if (ilo5 && !pretimeout && !mynmi)
 171                return NMI_DONE;
 172
 173        hpwdt_stop();
 174
 175        hex_byte_pack(panic_msg, mynmi);
 176        nmi_panic(regs, panic_msg);
 177
 178        return NMI_HANDLED;
 179}
 180#endif /* CONFIG_HPWDT_NMI_DECODING */
 181
 182
 183static const struct watchdog_info ident = {
 184        .options = WDIOF_PRETIMEOUT    |
 185                   WDIOF_SETTIMEOUT    |
 186                   WDIOF_KEEPALIVEPING |
 187                   WDIOF_MAGICCLOSE,
 188        .identity = "HPE iLO2+ HW Watchdog Timer",
 189};
 190
 191/*
 192 *      Kernel interfaces
 193 */
 194
 195static const struct watchdog_ops hpwdt_ops = {
 196        .owner          = THIS_MODULE,
 197        .start          = hpwdt_start,
 198        .stop           = hpwdt_stop_core,
 199        .ping           = hpwdt_ping,
 200        .set_timeout    = hpwdt_settimeout,
 201        .get_timeleft   = hpwdt_gettimeleft,
 202#ifdef CONFIG_HPWDT_NMI_DECODING
 203        .set_pretimeout = hpwdt_set_pretimeout,
 204#endif
 205};
 206
 207static struct watchdog_device hpwdt_dev = {
 208        .info           = &ident,
 209        .ops            = &hpwdt_ops,
 210        .min_timeout    = 1,
 211        .max_timeout    = HPWDT_MAX_TIMER,
 212        .timeout        = DEFAULT_MARGIN,
 213        .pretimeout     = PRETIMEOUT_SEC,
 214};
 215
 216
 217/*
 218 *      Init & Exit
 219 */
 220
 221static int hpwdt_init_nmi_decoding(struct pci_dev *dev)
 222{
 223#ifdef CONFIG_HPWDT_NMI_DECODING
 224        int retval;
 225        /*
 226         * Only one function can register for NMI_UNKNOWN
 227         */
 228        retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, 0, "hpwdt");
 229        if (retval)
 230                goto error;
 231        retval = register_nmi_handler(NMI_SERR, hpwdt_pretimeout, 0, "hpwdt");
 232        if (retval)
 233                goto error1;
 234        retval = register_nmi_handler(NMI_IO_CHECK, hpwdt_pretimeout, 0, "hpwdt");
 235        if (retval)
 236                goto error2;
 237
 238        dev_info(&dev->dev,
 239                "HPE Watchdog Timer Driver: NMI decoding initialized\n");
 240
 241        return 0;
 242
 243error2:
 244        unregister_nmi_handler(NMI_SERR, "hpwdt");
 245error1:
 246        unregister_nmi_handler(NMI_UNKNOWN, "hpwdt");
 247error:
 248        dev_warn(&dev->dev,
 249                "Unable to register a die notifier (err=%d).\n",
 250                retval);
 251        return retval;
 252#endif  /* CONFIG_HPWDT_NMI_DECODING */
 253        return 0;
 254}
 255
 256static void hpwdt_exit_nmi_decoding(void)
 257{
 258#ifdef CONFIG_HPWDT_NMI_DECODING
 259        unregister_nmi_handler(NMI_UNKNOWN, "hpwdt");
 260        unregister_nmi_handler(NMI_SERR, "hpwdt");
 261        unregister_nmi_handler(NMI_IO_CHECK, "hpwdt");
 262#endif
 263}
 264
 265static int hpwdt_init_one(struct pci_dev *dev,
 266                                        const struct pci_device_id *ent)
 267{
 268        int retval;
 269
 270        /*
 271         * First let's find out if we are on an iLO2+ server. We will
 272         * not run on a legacy ASM box.
 273         * So we only support the G5 ProLiant servers and higher.
 274         */
 275        if (dev->subsystem_vendor != PCI_VENDOR_ID_HP &&
 276            dev->subsystem_vendor != PCI_VENDOR_ID_HP_3PAR) {
 277                dev_warn(&dev->dev,
 278                        "This server does not have an iLO2+ ASIC.\n");
 279                return -ENODEV;
 280        }
 281
 282        if (pci_match_id(hpwdt_blacklist, dev)) {
 283                dev_dbg(&dev->dev, "Not supported on this device\n");
 284                return -ENODEV;
 285        }
 286
 287        if (pci_enable_device(dev)) {
 288                dev_warn(&dev->dev,
 289                        "Not possible to enable PCI Device: 0x%x:0x%x.\n",
 290                        ent->vendor, ent->device);
 291                return -ENODEV;
 292        }
 293
 294        pci_mem_addr = pci_iomap(dev, 1, 0x80);
 295        if (!pci_mem_addr) {
 296                dev_warn(&dev->dev,
 297                        "Unable to detect the iLO2+ server memory.\n");
 298                retval = -ENOMEM;
 299                goto error_pci_iomap;
 300        }
 301        hpwdt_nmistat   = pci_mem_addr + 0x6e;
 302        hpwdt_timer_reg = pci_mem_addr + 0x70;
 303        hpwdt_timer_con = pci_mem_addr + 0x72;
 304
 305        /* Make sure that timer is disabled until /dev/watchdog is opened */
 306        hpwdt_stop();
 307
 308        /* Initialize NMI Decoding functionality */
 309        retval = hpwdt_init_nmi_decoding(dev);
 310        if (retval != 0)
 311                goto error_init_nmi_decoding;
 312
 313        watchdog_set_nowayout(&hpwdt_dev, nowayout);
 314        if (watchdog_init_timeout(&hpwdt_dev, soft_margin, NULL))
 315                dev_warn(&dev->dev, "Invalid soft_margin: %d.\n", soft_margin);
 316
 317        if (pretimeout && hpwdt_dev.timeout <= PRETIMEOUT_SEC) {
 318                dev_warn(&dev->dev, "timeout <= pretimeout. Setting pretimeout to zero\n");
 319                pretimeout = 0;
 320        }
 321        hpwdt_dev.pretimeout = pretimeout ? PRETIMEOUT_SEC : 0;
 322
 323        hpwdt_dev.parent = &dev->dev;
 324        retval = watchdog_register_device(&hpwdt_dev);
 325        if (retval < 0) {
 326                dev_err(&dev->dev, "watchdog register failed: %d.\n", retval);
 327                goto error_wd_register;
 328        }
 329
 330        dev_info(&dev->dev, "HPE Watchdog Timer Driver: Version: %s\n",
 331                                HPWDT_VERSION);
 332        dev_info(&dev->dev, "timeout: %d seconds (nowayout=%d)\n",
 333                                hpwdt_dev.timeout, nowayout);
 334        dev_info(&dev->dev, "pretimeout: %s.\n",
 335                                pretimeout ? "on" : "off");
 336
 337        if (dev->subsystem_vendor == PCI_VENDOR_ID_HP_3PAR)
 338                ilo5 = true;
 339
 340        return 0;
 341
 342error_wd_register:
 343        hpwdt_exit_nmi_decoding();
 344error_init_nmi_decoding:
 345        pci_iounmap(dev, pci_mem_addr);
 346error_pci_iomap:
 347        pci_disable_device(dev);
 348        return retval;
 349}
 350
 351static void hpwdt_exit(struct pci_dev *dev)
 352{
 353        if (!nowayout)
 354                hpwdt_stop();
 355
 356        watchdog_unregister_device(&hpwdt_dev);
 357        hpwdt_exit_nmi_decoding();
 358        pci_iounmap(dev, pci_mem_addr);
 359        pci_disable_device(dev);
 360}
 361
 362static struct pci_driver hpwdt_driver = {
 363        .name = "hpwdt",
 364        .id_table = hpwdt_devices,
 365        .probe = hpwdt_init_one,
 366        .remove = hpwdt_exit,
 367};
 368
 369MODULE_AUTHOR("Tom Mingarelli");
 370MODULE_DESCRIPTION("hpe watchdog driver");
 371MODULE_LICENSE("GPL");
 372MODULE_VERSION(HPWDT_VERSION);
 373
 374module_param(soft_margin, int, 0);
 375MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds");
 376
 377module_param_named(timeout, soft_margin, int, 0);
 378MODULE_PARM_DESC(timeout, "Alias of soft_margin");
 379
 380module_param(nowayout, bool, 0);
 381MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
 382                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 383
 384#ifdef CONFIG_HPWDT_NMI_DECODING
 385module_param(pretimeout, bool, 0);
 386MODULE_PARM_DESC(pretimeout, "Watchdog pretimeout enabled");
 387#endif
 388
 389module_pci_driver(hpwdt_driver);
 390