Author: imp
Date: Thu Oct 26 22:52:51 2017
New Revision: 325024
URL: https://svnweb.freebsd.org/changeset/base/325024

Log:
  Various IPMI watchdog timer improvements
  
  o Make hw.ipmi.on a tuneable
  o Changes to keep shutdown from hanging indefinitately after the wd
    would normally have been disabled.
  o Add support for setting pretimeout (which fires an interrupt
    some time before the actual watchdog expires)
  o Allow refinement of the actions to take when the watchdog expires
  o Allow special startup timeout to keep us from hanging in boot
    before watchdogd is started, but after we've loaded the kernel.
  
  Obtained From: Netflix OCA Firmware

Modified:
  head/sys/dev/ipmi/ipmi.c
  head/sys/dev/ipmi/ipmivars.h
  head/sys/sys/ipmi.h

Modified: head/sys/dev/ipmi/ipmi.c
==============================================================================
--- head/sys/dev/ipmi/ipmi.c    Thu Oct 26 22:19:28 2017        (r325023)
+++ head/sys/dev/ipmi/ipmi.c    Thu Oct 26 22:52:51 2017        (r325024)
@@ -81,10 +81,28 @@ static void ipmi_dtor(void *arg);
 int ipmi_attached = 0;
 
 static int on = 1;
+static bool wd_in_shutdown = false;
+static int wd_timer_actions = IPMI_SET_WD_ACTION_POWER_CYCLE;
+static int wd_shutdown_countdown = 420; /* sec */
+static int wd_startup_countdown = 420; /* sec */
+static int wd_pretimeout_countdown = 120; /* sec */
+
 static SYSCTL_NODE(_hw, OID_AUTO, ipmi, CTLFLAG_RD, 0,
     "IPMI driver parameters");
-SYSCTL_INT(_hw_ipmi, OID_AUTO, on, CTLFLAG_RW,
+SYSCTL_INT(_hw_ipmi, OID_AUTO, on, CTLFLAG_RWTUN,
        &on, 0, "");
+SYSCTL_INT(_hw_ipmi, OID_AUTO, wd_timer_actions, CTLFLAG_RW,
+       &wd_timer_actions, 0,
+       "IPMI watchdog timer actions (including pre-timeout interrupt)");
+SYSCTL_INT(_hw_ipmi, OID_AUTO, wd_shutdown_countdown, CTLFLAG_RW,
+       &wd_shutdown_countdown, 0,
+       "IPMI watchdog countdown for shutdown (seconds)");
+SYSCTL_INT(_hw_ipmi, OID_AUTO, wd_startup_countdown, CTLFLAG_RDTUN,
+       &wd_startup_countdown, 0,
+       "IPMI watchdog countdown initialized during startup (seconds)");
+SYSCTL_INT(_hw_ipmi, OID_AUTO, wd_pretimeout_countdown, CTLFLAG_RW,
+       &wd_pretimeout_countdown, 0,
+       "IPMI watchdog pre-timeout countdown (seconds)");
 
 static struct cdevsw ipmi_cdevsw = {
        .d_version =    D_VERSION,
@@ -631,8 +649,8 @@ ipmi_set_watchdog(struct ipmi_softc *sc, unsigned int 
        if (sec) {
                req->ir_request[0] = IPMI_SET_WD_TIMER_DONT_STOP
                    | IPMI_SET_WD_TIMER_SMS_OS;
-               req->ir_request[1] = IPMI_SET_WD_ACTION_RESET;
-               req->ir_request[2] = 0;
+               req->ir_request[1] = (wd_timer_actions & 0xff);
+               req->ir_request[2] = (wd_pretimeout_countdown & 0xff);
                req->ir_request[3] = 0; /* Timer use */
                req->ir_request[4] = (sec * 10) & 0xff;
                req->ir_request[5] = (sec * 10) >> 8;
@@ -657,21 +675,40 @@ ipmi_wd_event(void *arg, unsigned int cmd, int *error)
        unsigned int timeout;
        int e;
 
-       if (dumping)
+       /* Ignore requests while disabled. */
+       if (!on)
                return;
 
+       /*
+        * To prevent infinite hangs, we don't let anyone pat or change
+        * the watchdog when we're shutting down. (See ipmi_shutdown_event().)
+        * However, we do want to keep patting the watchdog while we are doing
+        * a coredump.
+        */
+       if (wd_in_shutdown) {
+               if (dumping && sc->ipmi_watchdog_active)
+                       ipmi_reset_watchdog(sc);
+               return;
+       }
+
        cmd &= WD_INTERVAL;
        if (cmd > 0 && cmd <= 63) {
                timeout = ((uint64_t)1 << cmd) / 1000000000;
                if (timeout == 0)
                        timeout = 1;
-               if (timeout != sc->ipmi_watchdog_active) {
+               if (timeout != sc->ipmi_watchdog_active ||
+                   wd_timer_actions != sc->ipmi_watchdog_actions ||
+                   wd_pretimeout_countdown != sc->ipmi_watchdog_pretimeout) {
                        e = ipmi_set_watchdog(sc, timeout);
                        if (e == 0) {
                                sc->ipmi_watchdog_active = timeout;
+                               sc->ipmi_watchdog_actions = wd_timer_actions;
+                               sc->ipmi_watchdog_pretimeout = 
wd_pretimeout_countdown;
                        } else {
                                (void)ipmi_set_watchdog(sc, 0);
                                sc->ipmi_watchdog_active = 0;
+                               sc->ipmi_watchdog_actions = 0;
+                               sc->ipmi_watchdog_pretimeout = 0;
                        }
                }
                if (sc->ipmi_watchdog_active != 0) {
@@ -681,9 +718,14 @@ ipmi_wd_event(void *arg, unsigned int cmd, int *error)
                        } else {
                                (void)ipmi_set_watchdog(sc, 0);
                                sc->ipmi_watchdog_active = 0;
+                               sc->ipmi_watchdog_actions = 0;
+                               sc->ipmi_watchdog_pretimeout = 0;
                        }
                }
        } else if (atomic_readandclear_int(&sc->ipmi_watchdog_active) != 0) {
+               sc->ipmi_watchdog_actions = 0;
+               sc->ipmi_watchdog_pretimeout = 0;
+
                e = ipmi_set_watchdog(sc, 0);
                if (e != 0 && cmd == 0)
                        *error = EOPNOTSUPP;
@@ -691,6 +733,40 @@ ipmi_wd_event(void *arg, unsigned int cmd, int *error)
 }
 
 static void
+ipmi_shutdown_event(void *arg, unsigned int cmd, int *error)
+{
+       struct ipmi_softc *sc = arg;
+
+       /* Ignore event if disabled. */
+       if (!on)
+               return;
+
+       /*
+        * Positive wd_shutdown_countdown value will re-arm watchdog;
+        * Zero value in wd_shutdown_countdown will disable watchdog;
+        * Negative value in wd_shutdown_countdown will keep existing state;
+        *
+        * Revert to using a power cycle to ensure that the watchdog will
+        * do something useful here.  Having the watchdog send an NMI
+        * instead is useless during shutdown, and might be ignored if an
+        * NMI already triggered.
+        */
+
+       wd_in_shutdown = true;
+       if (wd_shutdown_countdown == 0) {
+               /* disable watchdog */
+               ipmi_set_watchdog(sc, 0);
+               sc->ipmi_watchdog_active = 0;
+       } else if (wd_shutdown_countdown > 0) {
+               /* set desired action and time, and, reset watchdog */
+               wd_timer_actions = IPMI_SET_WD_ACTION_POWER_CYCLE;
+               ipmi_set_watchdog(sc, wd_shutdown_countdown);
+               sc->ipmi_watchdog_active = wd_shutdown_countdown;
+               ipmi_reset_watchdog(sc);
+       }
+}
+
+static void
 ipmi_power_cycle(void *arg, int howto)
 {
        struct ipmi_softc *sc = arg;
@@ -823,7 +899,10 @@ ipmi_startup(void *arg)
                        device_printf(dev, "Attached watchdog\n");
                        /* register the watchdog event handler */
                        sc->ipmi_watchdog_tag = EVENTHANDLER_REGISTER(
-                           watchdog_list, ipmi_wd_event, sc, 0);
+                               watchdog_list, ipmi_wd_event, sc, 0);
+                       sc->ipmi_shutdown_tag = EVENTHANDLER_REGISTER(
+                               shutdown_pre_sync, ipmi_shutdown_event,
+                               sc, 0);
                }
        }
 
@@ -836,6 +915,23 @@ ipmi_startup(void *arg)
        sc->ipmi_cdev->si_drv1 = sc;
 
        /*
+        * Set initial watchdog state. If desired, set an initial
+        * watchdog on startup. Or, if the watchdog device is
+        * disabled, clear any existing watchdog.
+        */
+       if (on && wd_startup_countdown > 0) {
+               wd_timer_actions = IPMI_SET_WD_ACTION_POWER_CYCLE;
+               if (ipmi_set_watchdog(sc, wd_startup_countdown) == 0 &&
+                   ipmi_reset_watchdog(sc) == 0) {
+                       sc->ipmi_watchdog_active = wd_startup_countdown;
+                       sc->ipmi_watchdog_actions = wd_timer_actions;
+                       sc->ipmi_watchdog_pretimeout = wd_pretimeout_countdown;
+               } else
+                       (void)ipmi_set_watchdog(sc, 0);
+               ipmi_reset_watchdog(sc);
+       } else if (!on)
+               (void)ipmi_set_watchdog(sc, 0);
+       /*
         * Power cycle the system off using IPMI. We use last - 1 since we don't
         * handle all the other kinds of reboots. We'll let others handle them.
         * We only try to do this if the BMC supports the Chassis device.
@@ -892,6 +988,9 @@ ipmi_detach(device_t dev)
                destroy_dev(sc->ipmi_cdev);
 
        /* Detach from watchdog handling and turn off watchdog. */
+       if (sc->ipmi_shutdown_tag)
+               EVENTHANDLER_DEREGISTER(shutdown_pre_sync,
+               sc->ipmi_shutdown_tag);
        if (sc->ipmi_watchdog_tag) {
                EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_watchdog_tag);
                ipmi_set_watchdog(sc, 0);

Modified: head/sys/dev/ipmi/ipmivars.h
==============================================================================
--- head/sys/dev/ipmi/ipmivars.h        Thu Oct 26 22:19:28 2017        
(r325023)
+++ head/sys/dev/ipmi/ipmivars.h        Thu Oct 26 22:52:51 2017        
(r325024)
@@ -109,7 +109,10 @@ struct ipmi_softc {
        int                     ipmi_driver_requests_polled;
        eventhandler_tag        ipmi_power_cycle_tag;
        eventhandler_tag        ipmi_watchdog_tag;
+       eventhandler_tag        ipmi_shutdown_tag;
        int                     ipmi_watchdog_active;
+       int                     ipmi_watchdog_actions;
+       int                     ipmi_watchdog_pretimeout;
        struct intr_config_hook ipmi_ich;
        struct mtx              ipmi_requests_lock;
        struct cv               ipmi_request_added;

Modified: head/sys/sys/ipmi.h
==============================================================================
--- head/sys/sys/ipmi.h Thu Oct 26 22:19:28 2017        (r325023)
+++ head/sys/sys/ipmi.h Thu Oct 26 22:52:51 2017        (r325024)
@@ -89,7 +89,14 @@
 
 #define IPMI_SET_WD_TIMER_SMS_OS       0x04
 #define IPMI_SET_WD_TIMER_DONT_STOP    0x40
+#define IPMI_SET_WD_ACTION_NONE                0x00
 #define IPMI_SET_WD_ACTION_RESET       0x01
+#define IPMI_SET_WD_ACTION_POWER_DOWN  0x02
+#define IPMI_SET_WD_ACTION_POWER_CYCLE 0x03
+#define IPMI_SET_WD_PREACTION_NONE     (0x00 << 4)
+#define IPMI_SET_WD_PREACTION_SMI      (0x01 << 4)
+#define IPMI_SET_WD_PREACTION_NMI      (0x02 << 4)
+#define IPMI_SET_WD_PREACTION_MI       (0x03 << 4)
 
 struct ipmi_msg {
        unsigned char   netfn;
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to