[PORT FROM R2] drivers: intel_scu_watchdog: use scu access for timer; don't use regs
authorMark Allyn <mark.a.allyn@intel.com>
Fri, 6 Jan 2012 23:02:22 +0000 (15:02 -0800)
committerbuildbot <buildbot@intel.com>
Thu, 26 Jan 2012 12:08:22 +0000 (04:08 -0800)
BZ: 19331

- Remove all regiser access; use scu commands discussed at 1/6/12 meeting
- Create ioctl to set all the thresholds, stop/start the WD
- Add support for MSI #6
- Cleaning

Change-Id: I5d88e9b92e567b4e6393bdac9d0b79d87d65b9b7
Reviewed-on: http://android.intel.com:8080/32817
Reviewed-by: Allyn, Mark A <mark.a.allyn@intel.com>
Reviewed-by: Chotard, Celine <celine.chotard@intel.com>
Tested-by: Chotard, Celine <celine.chotard@intel.com>
Reviewed-by: buildbot <buildbot@intel.com>
Tested-by: buildbot <buildbot@intel.com>
Documentation/watchdog/intel-scu-watchdog.txt [new file with mode: 0644]
drivers/watchdog/intel_scu_watchdog.c
drivers/watchdog/intel_scu_watchdog.h

diff --git a/Documentation/watchdog/intel-scu-watchdog.txt b/Documentation/watchdog/intel-scu-watchdog.txt
new file mode 100644 (file)
index 0000000..0e7afd9
--- /dev/null
@@ -0,0 +1,70 @@
+Last reviewed: 19/01/2012
+
+          Intel_SCU 0.2:  An Intel SCU IOH Based Watchdog Device
+                          for Intel part #(s):
+                            - AF82MP20 PCH
+           Documentation and Driver by Yann Puech and Mark Allyn
+             <yannx.puech@intel.com> <mark.a.allyn@intel.com>
+
+Principle
+=========
+
+This watchdog device is handled throught external timer 7 of the SCU.
+The timer timeout is defined here to allow the watchdog daemon to write
+sometimes to the watchdog device. We use this non standard technique because
+the timer used for the watchdog is stopped when the platform enters C6 or
+higher.
+
+Here is depicted the time line of the watchdog timer:
+
+0s                         50s        60s           75s (*)
+|___________________________|__________|_____________|
+|<----- timer_timeout ----->|<schedule>|<pre_timeout>|
+|<--------------------- timeout -------------------->|
+
+(*) The given values are default ones and can be overridden using ioctl.
+
+timer_timeout: The daemon is waiting for MSI #7
+schedule:      The daemon will write to the device when scheduled
+pre_timeout:   This range is reached if the device has not been kicked by the
+               daemon (so the system is down). We are going to dump kernel
+               stacks and CPU regs.
+timeout:       When the timer expires, the platform issues a cold_reset.
+
+
+module params
+=============
+
+Production
+----------
+  pre_timeout:   accessor for the pre timeout
+  timeout:       accessor for the timeout
+  timer_timeout: accessor for the timer timeout
+
+debug purpose
+-------------
+  disable_kernel_watchdog: disable the watchdog at boot time (useful with gdb)
+  reset_on_release:        avoid to reset straight away after killing the daemon
+  kicking_active:          disable the kicking at low level (so a cold reset will
+                           occur soon)
+
+ioctl
+=====
+
+standard
+--------
+
+  WDIOC_GETSUPPORT:
+  WDIOC_GETSTATUS:
+  WDIOC_GETBOOTSTATUS:
+  WDIOC_KEEPALIVE:
+  WDIOC_SETPRETIMEOUT:
+  WDIOC_SETTIMEOUT:
+  WDIOC_GETTIMEOUT:
+  WDIOC_SETOPTIONS:
+    WDIOS_DISABLECARD
+    WDIOS_ENABLECARD
+
+custom
+------
+  WDIOC_SETTIMERTIMEOUT: sets the timeout of the timer for deblocking the daemon.
index 630e9c1..804ed92 100644 (file)
@@ -22,7 +22,7 @@
  *
  */
 
-#define DEBUG  1
+/* See Documentation/watchdog/intel-scu-watchdog.txt */
 
 #include <linux/compiler.h>
 #include <linux/module.h>
 #include <linux/wakelock.h>
 #include <asm/irq.h>
 #include <asm/atomic.h>
-
-/* See arch/x86/kernel/ipc_mrst.c */
+#include <linux/intel_mid_pm.h>
 #include <asm/intel_scu_ipc.h>
 #include <asm/apb_timer.h>
 
 #include "intel_scu_watchdog.h"
 
-/* Bounds number of times we will retry loading time count */
-/* This retry is a work around for a silicon bug.         */
-#define MAX_RETRY 16
+/* Adjustment flags */
+/* from config file */
+/* #define CONFIG_DISABLE_SCU_WATCHDOG */
+/* local */
+#define CONFIG_INTEL_SCU_SOFT_LOCKUP
+#define CONFIG_DEBUG_WATCHDOG
 
+/* Defines */
 #define IPC_SET_WATCHDOG_TIMER 0xF8
+#define IPC_SET_SUB_LOAD_THRES  0x00
+#define IPC_SET_SUB_DISABLE     0x01
+#define IPC_SET_SUB_KEEPALIVE   0x02
+
+#define WDIOC_SETTIMERTIMEOUT     _IOW(WATCHDOG_IOCTL_BASE, 11, int)
+#define WDIOC_GETTIMERTIMEOUT     _IOW(WATCHDOG_IOCTL_BASE, 12, int)
 
+/* Statics */
+static struct intel_scu_watchdog_dev watchdog_device;
+static struct wake_lock watchdog_wake_lock;
 static DECLARE_WAIT_QUEUE_HEAD(read_wq);
+static unsigned char osnib_reset = OSNIB_WRITE_VALUE;
 
 /* The read function (intel_scu_read) waits for the warning_flag to */
 /* be set by the watchdog interrupt handler. */
 /* When warning_flag is set intel_scu_read wakes up the user level */
 /* process, which is responsible for refreshing the watchdog timer */
 static int warning_flag;
-static unsigned char osnib_reset = OSNIB_WRITE_VALUE;
 
+/* Module params */
 static bool disable_kernel_watchdog;
-
+#ifdef CONFIG_DISABLE_SCU_WATCHDOG
 /*
- * heartbeats: cpu last kstat.system times
- * beattime : jeffies at the sample time of heartbeats.
- * SOFT_LOCK_TIME : some time out in sec after warning interrupt.
- * dump_softloc_debug : called on SOFT_LOCK_TIME time out after scu
- *     interrupt to log data to logbuffer and emmc-panic code,
- *     SOFT_LOCK_TIME needs to be < SCU warn to reset time
- *     which is currently thats 15 sec.
- *
- * The soft lock works be taking a snapshot of kstat_cpu(i).cpustat.system at
- * the time of the warning interrupt for each cpu.  Then at SOFT_LOCK_TIME the
- * amount of time spend in system is computed and if its within 10 ms of the
- * total SOFT_LOCK_TIME on any cpu it will dump the stack on that cpu and then
- * calls panic.
- *
- */
-#ifdef DEBUG
-static cputime64_t heartbeats[NR_CPUS];
-static cputime64_t beattime;
-#define SOFT_LOCK_TIME 10
-static void dump_softlock_debug(unsigned long data);
-DEFINE_TIMER(softlock_timer, dump_softlock_debug, 0, 0);
-#endif /* DEBUG */
-
-/**
  * Please note that we are using a config CONFIG_DISABLE_SCU_WATCHDOG
- * because this boot parameter should only be settable in a development
- * environment and that customer devices should not have this capability
+ * because this boot parameter should only be settable in a developement
  */
-#if defined(CONFIG_DISABLE_SCU_WATCHDOG)
 module_param(disable_kernel_watchdog, bool, S_IRUGO);
 MODULE_PARM_DESC(disable_kernel_watchdog,
                "Disable kernel watchdog"
@@ -118,71 +105,69 @@ MODULE_PARM_DESC(disable_kernel_watchdog,
                "driver if disable_kernel_watchdog is set");
 #endif
 
-static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN;
-module_param(timer_margin, int, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(timer_margin,
-               "Watchdog timer margin"
+static int pre_timeout = DEFAULT_PRETIMEOUT;
+module_param(pre_timeout, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pre_timeout,
+               "Watchdog pre timeout"
                "Time between interrupt and resetting the system"
-               "The range is from 1 to 160"
-               "This is the time for all keep alives to arrive");
+               "The range is from 1 to 160");
 
-static int timer_set = DEFAULT_TIME;
-module_param(timer_set, int, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(timer_set,
+static int timeout = DEFAULT_TIMEOUT;
+module_param(timeout, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(timeout,
                "Default Watchdog timer setting"
                "Complete cycle time"
                "The range is from 1 to 170"
                "This is the time for all keep alives to arrive");
 
-/* After watchdog device is closed, check force_reset. If:
- * force_reset is false, then force boot after time expires after close,
- * force_reset is true, then force boot immediately when device is closed.
- */
-static bool force_reset = true;
-module_param(force_reset, bool, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(force_reset,
+static int timer_timeout = DEFAULT_TIMER_DURATION;
+module_param(timer_timeout, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(timer_timeout,
+               "Watchdog timer timeout"
+               "Time between timer interrupt and resetting the system");
+
+static bool reset_on_release = true;
+static bool kicking_active = true;
+#ifdef CONFIG_DEBUG_WATCHDOG
+module_param(reset_on_release, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(reset_on_release,
                "A true means that the driver will reboot"
                "the system immediately if the /dev/watchdog device is closed"
                "A false means that when /dev/watchdog device is closed"
                "the watchdog timer will be refreshed for one more interval"
-               "of length: timer_set. At the end of this interval, the"
+               "of length: timeout. At the end of this interval, the"
                "watchdog timer will reset the system."
                );
 
-/* there is only one device in the system now; this can be made into
- * an array in the future if we have more than one device */
-
-static struct intel_scu_watchdog_dev watchdog_device;
-
-static struct wake_lock watchdog_wake_lock;
-
-/* Forces restart, if force_reboot is set */
-static void watchdog_fire(void)
-{
-       if (force_reset) {
-               printk(KERN_CRIT PFX "Initiating system reboot.\n");
-               emergency_restart();
-               printk(KERN_CRIT PFX "Reboot didn't ?????\n");
-       }
-
-       else {
-               printk(KERN_CRIT PFX "Immediate Reboot Disabled\n");
-               printk(KERN_CRIT PFX
-                       "System will reset when watchdog timer times out!\n");
-       }
-}
+module_param(kicking_active, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(kicking_active,
+                "Deactivating the kicking will result in a cold reset"
+                "after a while"
+                );
+#endif
 
-static int check_timer_margin(int new_margin)
-{
-       if ((new_margin < MIN_TIME_CYCLE) ||
-           (new_margin > MAX_TIME - timer_set)) {
-               pr_debug("Watchdog timer: Value of new_margin %d is "
-                         "out of the range %d to %d\n",
-                         new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set);
-                       return -EINVAL;
-       }
-       return 0;
-}
+#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
+/*
+ * heartbeats: cpu last kstat.system times
+ * beattime : jeffies at the sample time of heartbeats.
+ * SOFT_LOCK_TIME : some time out in sec after warning interrupt.
+ * dump_softloc_debug : called on SOFT_LOCK_TIME time out after scu
+ *     interrupt to log data to logbuffer and emmc-panic code,
+ *     SOFT_LOCK_TIME needs to be < SCU warn to reset time
+ *     which is currently thats 15 sec.
+ *
+ * The soft lock works be taking a snapshot of kstat_cpu(i).cpustat.system at
+ * the time of the warning interrupt for each cpu.  Then at SOFT_LOCK_TIME the
+ * amount of time spend in system is computed and if its within 10 ms of the
+ * total SOFT_LOCK_TIME on any cpu it will dump the stack on that cpu and then
+ * calls panic.
+ *
+ */
+static cputime64_t heartbeats[NR_CPUS];
+static cputime64_t beattime;
+#define SOFT_LOCK_TIME 10
+static void dump_softlock_debug(unsigned long data);
+DEFINE_TIMER(softlock_timer, dump_softlock_debug, 0, 0);
 
 /* time is about to run out and the scu will reset soon.  quickly
  * dump debug data to logbuffer and emmc via calling panic before lights
@@ -195,7 +180,6 @@ static void smp_dumpstack(void *info)
 
 static void dump_softlock_debug(unsigned long data)
 {
-#ifdef DEBUG
        int i, reboot;
        cputime64_t system[NR_CPUS], num_jifs;
 
@@ -220,115 +204,119 @@ static void dump_softlock_debug(unsigned long data)
                panic_timeout = 10;
                panic("Soft lock on CPUs\n");
        }
-#else /* DEBUG */
-       return;
-#endif /* DEBUG */
-
 }
+#endif /* CONFIG_INTEL_SCU_SOFT_LOCKUP */
 
+/* Check current timeouts */
+static int check_timeouts(void)
+{
+       if (timer_timeout+pre_timeout < timeout)
+               return 0;
 
-/*
- * IPC operations
- */
-static int watchdog_set_ipc(int soft_threshold, int threshold)
+       return -EINVAL;
+}
+
+/* Set the different timeouts needed by the SCU FW */
+static int watchdog_set_timeouts(int timer_threshold, int warning_pretimeout,
+                           int reset_timeout)
 {
        u32     *ipc_wbuf;
-       u8       cbuf[16] = { '\0' };
-       int      ipc_ret = 0;
+       u8      cbuf[16] = { '\0' };
+       int     ret = 0;
+       u32     freq = watchdog_device.timer7_tbl_ptr->freq_hz;
 
        ipc_wbuf = (u32 *)&cbuf;
-       ipc_wbuf[0] = soft_threshold;
-       ipc_wbuf[1] = threshold;
-
-       ipc_ret = intel_scu_ipc_command(
-                       IPC_SET_WATCHDOG_TIMER,
-                       0,
-                       ipc_wbuf,
-                       2,
-                       NULL,
-                       0);
-
-       if (ipc_ret != 0)
-               printk(KERN_CRIT PFX "Error Setting SCU Watchdog Timer: %x\n",
-                       ipc_ret);
-
-       return ipc_ret;
-};
+       ipc_wbuf[0] = timer_threshold * freq;
+       ipc_wbuf[1] = warning_pretimeout * freq;
+       ipc_wbuf[2] = (reset_timeout - timer_threshold - warning_pretimeout)
+                       * freq;
 
-/*
- *      Intel_SCU operations
- */
+       pr_debug(PFX "Watchdog ipc_buff[0]%x\n", ipc_wbuf[0]);
+       pr_debug(PFX "Watchdog ipc_buff[1]%x\n", ipc_wbuf[1]);
+       pr_debug(PFX "Watchdog ipc_buff[2]%x\n", ipc_wbuf[2]);
 
+       ret = intel_scu_ipc_command(IPC_SET_WATCHDOG_TIMER,
+                                   IPC_SET_SUB_LOAD_THRES,
+                                   ipc_wbuf, 3, NULL, 0);
 
-static int intel_scu_keepalive(void)
-{
+       if (ret)
+               pr_crit(PFX "Error Setting SCU Watchdog Timer: %x\n", ret);
 
-       pr_debug("Watchdog timer: keepalive: soft_threshold %x\n",
-               watchdog_device.soft_threshold);
+       return ret;
+};
 
-       /* read eoi register - clears interrupt */
-       ioread32(watchdog_device.timer_clear_interrupt_addr);
+/* Keep alive  */
+static int watchdog_keepalive(void)
+{
+int ret;
 
-       /* temporarily disable the timer */
-       iowrite32(0x00000002, watchdog_device.timer_control_addr);
+       pr_err(PFX "%s\n", __func__);
 
-       /* set the timer to the soft_threshold */
-       iowrite32(watchdog_device.soft_threshold,
-                 watchdog_device.timer_load_count_addr);
+       if (unlikely(!kicking_active)) {
+               /* Close our eyes */
+               pr_err(PFX "Transparent kicking\n");
+               return 0;
+       }
 
-       /* allow the timer to run */
-       iowrite32(0x00000003, watchdog_device.timer_control_addr);
+       /* Really kick it */
+       ret = intel_scu_ipc_command(IPC_SET_WATCHDOG_TIMER,
+                                   IPC_SET_SUB_KEEPALIVE, NULL, 0, NULL, 0);
+       if (ret)
+               pr_err(PFX "Error sending keepalive ipc: %x\n", ret);
 
-       return 0;
+       return ret;
 }
 
+/* stops the timer */
 static int intel_scu_stop(void)
 {
-       iowrite32(0, watchdog_device.timer_control_addr);
-       return 0;
+int ret;
+
+       pr_err(PFX "%s\n", __func__);
+
+       ret = intel_scu_ipc_command(IPC_SET_WATCHDOG_TIMER,
+                                   IPC_SET_SUB_DISABLE, NULL, 0, NULL, 0);
+       if (ret) {
+               pr_crit(PFX "Error sending disable ipc: %x\n", ret);
+               goto err;
+       }
+
+       watchdog_device.started = false;
+
+err:
+       return ret;
 }
 
-/* tasklet function for interrupt; keep interupt itself simple */
+/* tasklet */
 static void watchdog_interrupt_tasklet_body(unsigned long data)
 {
-       int i, int_status;
+int ret;
 
-       pr_debug("Watchdog: interrupt tasklet body start\n");
+       pr_warn(PFX "interrupt tasklet body start\n");
 
        if (disable_kernel_watchdog) {
-               pr_debug("Watchdog: interrupt tasklet body disable set\n");
                /* disable the timer */
-               /* Set all thresholds to 0 to disable timeouts */
-               watchdog_device.soft_threshold = 0;
-               watchdog_device.threshold = 0;
-
-               /* send the threshold and soft_threshold via IPC */
-               int_status = watchdog_set_ipc(watchdog_device.soft_threshold,
-                                  watchdog_device.threshold);
-
-               if (int_status != 0) {
-                       /* Make sure the watchdog timer is stopped */
-                       pr_warn("can't set ipc to disable at start\n");
-                       intel_scu_stop();
-                       return;
-               }
-
-               iowrite32(0x00000002, watchdog_device.timer_control_addr);
-               intel_scu_stop();
+               pr_warn(PFX "interrupt tasklet body disable set\n");
+               ret = intel_scu_stop();
+               if (ret)
+                       pr_err(PFX "cannot disable the timer\n");
                return;
        }
 
        /* wake up read to send data to user (reminder for keep alive */
        warning_flag = 1;
 
-#ifdef DEBUG
-       /*start timer for softlock detection */
-       beattime = jiffies;
-       for_each_possible_cpu(i) {
-               heartbeats[i] = kstat_cpu(i).cpustat.system;
+#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
+       {
+               int i;
+               /*start timer for softlock detection */
+               beattime = jiffies;
+               for_each_possible_cpu(i) {
+                       heartbeats[i] = kstat_cpu(i).cpustat.system;
+               }
+               mod_timer(&softlock_timer, jiffies + SOFT_LOCK_TIME * HZ);
        }
-       mod_timer(&softlock_timer, jiffies + SOFT_LOCK_TIME * HZ);
-#endif /* DEBUG */
+#endif
 
        /* Wake up the daemon */
        wake_up_interruptible(&read_wq);
@@ -343,10 +331,9 @@ static void watchdog_interrupt_tasklet_body(unsigned long data)
 /* timer interrupt handler */
 static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id)
 {
-
        /* has the timer been started? If not, then this is spurious */
-       if (watchdog_device.timer_started == 0) {
-               pr_debug("Watchdog timer: spurious interrupt received\n");
+       if (!watchdog_device.started) {
+               pr_warn(PFX "Spurious interrupt received\n");
                return IRQ_HANDLED;
        }
 
@@ -355,64 +342,49 @@ static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-static int intel_scu_set_heartbeat(u32 t)
+/* warning interrupt handler */
+static irqreturn_t watchdog_warning_interrupt(int irq, void *dev_id)
 {
-       int                      ipc_ret;
-
-       watchdog_device.timer_set = t;
-       watchdog_device.threshold =
-               timer_margin * watchdog_device.timer_tbl_ptr->freq_hz;
-       watchdog_device.soft_threshold =
-               (watchdog_device.timer_set - timer_margin)
-               * watchdog_device.timer_tbl_ptr->freq_hz;
-
-       pr_debug("Watchdog timer: set_heartbeat: timer freq is %d\n",
-               watchdog_device.timer_tbl_ptr->freq_hz);
-       pr_debug("Watchdog timer: set_heartbeat: timer_set is %x (hex)\n",
-               watchdog_device.timer_set);
-       pr_debug("Watchdog timer: set_hearbeat: timer_margin is %x (hex)\n",
-               timer_margin);
-       pr_debug("Watchdog timer: set_heartbeat: threshold is %x (hex)\n",
-               watchdog_device.threshold);
-       pr_debug("Watchdog timer: set_heartbeat: soft_threshold is %x (hex)\n",
-               watchdog_device.soft_threshold);
-
-
-       /* temporarily disable the timer */
-       iowrite32(0x00000002, watchdog_device.timer_control_addr);
-
-       /* send the threshold and soft_threshold via IPC to the processor */
-       ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold,
-                                  watchdog_device.threshold);
-
-       if (ipc_ret != 0) {
-               /* Make sure the watchdog timer is stopped */
-               intel_scu_stop();
-               return ipc_ret;
-       }
+       pr_warn("[SHTDWN] %s, WATCHDOG TIMEOUT!\n", __func__);
 
-       /* Make sure timer is stopped */
-       intel_scu_stop();
+       /* Let's reset the platform after dumping some data */
+       panic("Kernel Watchdog");
 
-       /* set the timer to the soft threshold */
-       iowrite32(watchdog_device.soft_threshold,
-               watchdog_device.timer_load_count_addr);
+       /* This code should not be reached */
+       return IRQ_HANDLED;
+}
 
-       /* Start the timer */
-       iowrite32(0x00000003, watchdog_device.timer_control_addr);
+/* Program and starts the timer */
+static int watchdog_config_and_start(u32 newtimeout, u32 newpretimeout)
+{
+int ret;
 
-       watchdog_device.timer_started = 1;
+       timeout = newtimeout;
+       pre_timeout = newpretimeout;
+
+       pr_warn(PFX "Configuration: %dkHz, timeout=%ds, pre_timeout=%ds, timer=%ds\n",
+               watchdog_device.timer7_tbl_ptr->freq_hz / 1000, timeout,
+               pre_timeout, timer_timeout);
+
+       /* Configure the watchdog */
+       ret = watchdog_set_timeouts(timer_timeout, pre_timeout, timeout);
+       if (ret) {
+               pr_err(PFX "%s: Cannot configure the watchdog\n", __func__);
+
+               /* Make sure the watchdog timer is stopped */
+               intel_scu_stop();
+               return ret;
+       }
+
+       watchdog_device.started = true;
 
        return 0;
 }
 
-/*
- * /dev/watchdog handling
- */
-
+/* Open */
 static int intel_scu_open(struct inode *inode, struct file *file)
 {
-       int result;
+int ret;
 
        /* Set flag to indicate that watchdog device is open */
        if (test_and_set_bit(0, &watchdog_device.driver_open))
@@ -423,82 +395,86 @@ static int intel_scu_open(struct inode *inode, struct file *file)
                return -EPERM;
 
        /* Let shared OSNIB (sram) know we are open */
-       result = intel_scu_ipc_write_osnib(
-               &osnib_reset,
-               OSNIB_WRITE_SIZE,
-               OSNIB_WDOG_OFFSET,
-               OSNIB_WRITE_MASK);
-
-       if (result != 0) {
-               pr_warn("cant write OSNIB\n");
+       /* To publish a proc and ioctl to do this and leave userland decide */
+       /* when it is sensible to do it (boot completed intent) */
+       ret = intel_scu_ipc_write_osnib(&osnib_reset, OSNIB_WRITE_SIZE,
+               OSNIB_WDOG_OFFSET, OSNIB_WRITE_MASK);
+
+       if (ret != 0) {
+               pr_err(PFX "cannot write OSNIB\n");
                return -EINVAL;
        }
 
        return nonseekable_open(inode, file);
 }
 
+/* Release */
 static int intel_scu_release(struct inode *inode, struct file *file)
 {
        /*
         * This watchdog should not be closed, after the timer
         * is started with the WDIPC_SETTIMEOUT ioctl
-        * If force_reset is set watchdog_fire() will cause an
-        * immediate reset. If force_reset is not set, the watchdog
+        * If reset_on_release is set this  will cause an
+        * immediate reset. If reset_on_release is not set, the watchdog
         * timer is refreshed for one more interval. At the end
         * of that interval, the watchdog timer will reset the system.
         */
 
        if (!test_bit(0, &watchdog_device.driver_open)) {
-               pr_debug("Watchdog timer: intel_scu_release, without open\n");
+               pr_err(PFX "intel_scu_release, without open\n");
                return -ENOTTY;
        }
 
-       if (!watchdog_device.timer_started) {
+       if (!watchdog_device.started) {
                /* Just close, since timer has not been started */
-               pr_debug("Watchdog timer: Closed, without starting timer\n");
+               pr_err(PFX "Closed, without starting timer\n");
                return 0;
        }
 
-       printk(KERN_CRIT PFX
-              "Unexpected close of /dev/watchdog!\n");
+       pr_crit(PFX "Unexpected close of /dev/watchdog!\n");
 
        /* Since the timer was started, prevent future reopens */
        watchdog_device.driver_closed = 1;
 
        /* Refresh the timer for one more interval */
-       intel_scu_keepalive();
+       watchdog_keepalive();
 
-       /* Reboot system (if force_reset is set) */
-       watchdog_fire();
+       /* Reboot system if requested */
+       if (reset_on_release) {
+               pr_crit(PFX "Initiating system reboot.\n");
+               emergency_restart();
+       }
+
+       pr_crit(PFX "Immediate Reboot Disabled\n");
+       pr_crit(PFX "System will reset when watchdog timer expire!\n");
 
-       /* We should only reach this point if force_reset is not set */
        return 0;
 }
 
-static ssize_t intel_scu_write(struct file *file,
-                             char const *data,
-                             size_t len,
+/* Write */
+static ssize_t intel_scu_write(struct file *file, char const *data, size_t len,
                              loff_t *ppos)
 {
+       pr_debug(PFX "watchdog %s\n", __func__);
 
-       if (watchdog_device.timer_started) {
+       if (watchdog_device.started) {
                /* Watchdog already started, keep it alive */
-               intel_scu_keepalive();
+               watchdog_keepalive();
                wake_unlock(&watchdog_wake_lock);
-       } else
+       } else {
                /* Start watchdog with timer value set by init */
-               intel_scu_set_heartbeat(watchdog_device.timer_set);
+               watchdog_config_and_start(timeout, pre_timeout);
+       }
 
        return len;
 }
 
-static ssize_t intel_scu_read(struct file *file,
-                            char __user *user_data,
-                            size_t len,
-                            loff_t *user_ppos)
+/* Read */
+static ssize_t intel_scu_read(struct file *file, char __user *user_data,
+                            size_t len, loff_t *user_ppos)
 {
-       int result;
-       u8 buf = 0;
+int ret;
+const u8 *buf = "0";
 
        /* we wait for the next interrupt; if more than one */
        /* interrupt has occurred since the last read, we */
@@ -509,100 +485,154 @@ static ssize_t intel_scu_read(struct file *file,
        /* awaiting a keep alive and that a limited time */
        /* is available for the keep alive before the system */
        /* is rebooted by the timer */
-       /* if (wait_event_interruptible(read_wq, warning_flag != 0))
-               return -ERESTARTSYS; */
 
        warning_flag = 0;
 
        /* Please note that the content of the data is irrelevent */
        /* All that matters is that the read is available to the user */
-       result = copy_to_user(user_data, (void *)&buf, 1);
+       ret = copy_to_user(user_data, (const void *)buf, 1);
 
-       if (result != 0)
+       if (ret)
                return -EFAULT;
-       else
-               return 1;
 
+       return 1;
 }
 
+/* Poll */
 static unsigned int intel_scu_poll(struct file *file, poll_table *wait)
 {
-       unsigned int mask = 0;
+unsigned int mask;
+
        poll_wait(file, &read_wq, wait);
 
+       mask = 0;
        if (warning_flag == 1)
                mask |= POLLIN | POLLRDNORM;
-       else
-               mask = 0;
 
        return mask;
 }
 
-static long intel_scu_ioctl(struct file *file,
-                          unsigned int cmd,
-                          unsigned long arg)
+/* ioctl */
+static long intel_scu_ioctl(struct file *file, unsigned int cmd,
+                           unsigned long arg)
 {
-       void __user *argp = (void __user *)arg;
-       u32 __user *p = argp;
-       u32 new_margin;
-
+void __user *argp = (void __user *)arg;
+u32 __user *p = argp;
+u32 val;
+int options;
 
        static const struct watchdog_info ident = {
-               .options =          WDIOF_SETTIMEOUT
-                                   | WDIOF_KEEPALIVEPING,
-               .firmware_version = 0,  /* @todo Get from SCU via
-                                                ipc_get_scu_fw_version()? */
-               .identity =         "Intel_SCU IOH Watchdog"  /* len < 32 */
+               .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+               /* @todo Get from SCU via ipc_get_scu_fw_version()? */
+               .firmware_version = 0,
+               /* len < 32 */
+               .identity = "Intel_SCU IOH Watchdog"
        };
 
        switch (cmd) {
        case WDIOC_GETSUPPORT:
-               return copy_to_user(argp,
-                                   &ident,
+               return copy_to_user(argp, &ident,
                                    sizeof(ident)) ? -EFAULT : 0;
        case WDIOC_GETSTATUS:
        case WDIOC_GETBOOTSTATUS:
                return put_user(0, p);
        case WDIOC_KEEPALIVE:
-               intel_scu_keepalive();
+               pr_warn(PFX "%s: KeepAlive ioctl\n", __func__);
+               if (!watchdog_device.started)
+                       return -EINVAL;
 
+               watchdog_keepalive();
                return 0;
-       case WDIOC_SETTIMEOUT:
-               if (get_user(new_margin, p))
+       case WDIOC_SETTIMERTIMEOUT:
+               pr_warn(PFX "%s: SetTimerTimeout ioctl\n", __func__);
+
+               if (watchdog_device.started)
+                       return -EBUSY;
+
+               /* Timeout to start scheduling the daemon */
+               if (get_user(val, p))
                        return -EFAULT;
 
-               if (check_timer_margin(new_margin))
-                       return -EINVAL;
+               timer_timeout = val;
+               return 0;
+       case WDIOC_SETPRETIMEOUT:
+               pr_warn(PFX "%s: SetPreTimeout ioctl\n", __func__);
 
-               if (intel_scu_set_heartbeat(new_margin))
-                       return -EINVAL;
+               if (watchdog_device.started)
+                       return -EBUSY;
+
+               /* Timeout to warn */
+               if (get_user(val, p))
+                       return -EFAULT;
+
+               pre_timeout = val;
+               return 0;
+       case WDIOC_SETTIMEOUT:
+               pr_warn(PFX "%s: SetTimeout ioctl\n", __func__);
+
+               if (watchdog_device.started)
+                       return -EBUSY;
+
+               if (get_user(val, p))
+                       return -EFAULT;
+
+               timeout = val;
                return 0;
        case WDIOC_GETTIMEOUT:
-               return put_user(watchdog_device.soft_threshold, p);
+               return put_user(timeout, p);
+       case WDIOC_SETOPTIONS:
+               if (get_user(options, p))
+                       return -EFAULT;
 
+               if (options & WDIOS_DISABLECARD) {
+                       pr_warn(PFX "%s: Stopping the watchdog\n", __func__);
+                       intel_scu_stop();
+                       return 0;
+               }
+
+               if (options & WDIOS_ENABLECARD) {
+                       pr_warn(PFX "%s: Starting the watchdog\n", __func__);
+
+                       if (watchdog_device.started)
+                               return -EBUSY;
+
+                       if (check_timeouts()) {
+                               pr_warn(PFX "%s: Invalid thresholds\n",
+                                       __func__);
+                               return -EINVAL;
+                       }
+                       if (watchdog_config_and_start(timeout, pre_timeout))
+                               return -EINVAL;
+                       return 0;
+               }
+               return 0;
        default:
                return -ENOTTY;
        }
 }
 
-/*
- *      Notifier for system down
- */
-static int intel_scu_notify_sys(struct notifier_block *this,
-                              unsigned long code,
-                              void *another_unused)
+/* Reboot notifier */
+static int reboot_notifier(struct notifier_block *this,
+                          unsigned long code,
+                          void *another_unused)
 {
-       if (code == SYS_DOWN || code == SYS_HALT) {
+int ret;
+
+       if (code == SYS_RESTART || code == SYS_HALT || code == SYS_POWER_OFF) {
+               pr_warn(PFX "Reboot notifier\n");
+
                /* Don't do instant reset on close */
-               pr_debug("Watchdog timer - HALT or RESET notification\n");
-               force_reset = false;
+               reset_on_release = false;
+
+               /* Kick once again */
+               ret = watchdog_keepalive();
+               if (ret)
+                       pr_warn(PFX "%s: cannot keep timer alive\n", __func__);
        }
        return NOTIFY_DONE;
 }
 
-/*
- *      Kernel Interfaces
- */
+/* Kernel Interfaces */
 static const struct file_operations intel_scu_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
@@ -614,73 +644,58 @@ static const struct file_operations intel_scu_fops = {
        .release        = intel_scu_release,
 };
 
+/* Init code */
 static int __init intel_scu_watchdog_init(void)
 {
        int ret;
-       u32 __iomem *tmp_addr;
-
 
-       /* Check boot parameters to verify that their initial values */
-       /* are in range. */
-       /* Check value of timer_set boot parameter */
-       if ((timer_set < MIN_TIME_CYCLE) ||
-           (timer_set > MAX_TIME - MIN_TIME_CYCLE)) {
-               pr_err("Watchdog timer: Value of timer_set %x (hex) "
-                 "is out of range from %x to %x (hex)\n",
-                 timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE);
+       /* Check timeouts boot parameter */
+       if (check_timeouts()) {
+               pr_err(PFX "%s: Invalid timeouts\n", __func__);
                return -EINVAL;
        }
 
-       /* Check value of timer_margin boot parameter */
-       if (check_timer_margin(timer_margin))
-               return -EINVAL;
-
-       watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1);
-
-       if (watchdog_device.timer_tbl_ptr == NULL) {
-               pr_debug("Watchdog timer - Intel SCU watchdog: Timer is"
+       /* Acquire timer 7 */
+       watchdog_device.timer7_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1);
+       if (watchdog_device.timer7_tbl_ptr == NULL) {
+               pr_debug(PFX "Watchdog timer - Intel SCU watchdog: Timer is"
                        " not available\n");
                return -ENODEV;
        }
-       /* make sure the timer exists */
-       if (watchdog_device.timer_tbl_ptr->phys_addr == 0) {
-               pr_debug("Watchdog timer - Intel SCU watchdog - timer %d does"
+       if (watchdog_device.timer7_tbl_ptr->phys_addr == 0) {
+               pr_debug(PFX "Watchdog timer - Intel SCU watchdog - timer %d does"
                  " not have valid physical memory\n", sfi_mtimer_num);
                return -ENODEV;
        }
-
-       if (watchdog_device.timer_tbl_ptr->irq == 0) {
-               pr_debug("Watchdog timer: timer %d invalid irq\n",
+       if (watchdog_device.timer7_tbl_ptr->irq == 0) {
+               pr_debug(PFX "Watchdog timer: timer %d invalid irq\n",
                  sfi_mtimer_num);
                return -ENODEV;
        }
 
-       tmp_addr = ioremap_nocache(watchdog_device.timer_tbl_ptr->phys_addr,
-                       20);
-
-       if (tmp_addr == NULL) {
-               pr_debug("Watchdog timer: timer unable to ioremap\n");
-               return -ENOMEM;
+       /* Acquire timer 6 */
+       watchdog_device.timer6_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-2);
+       if (watchdog_device.timer6_tbl_ptr == NULL) {
+               pr_debug(PFX "Watchdog timer - Intel SCU watchdog: Timer is"
+                       " not available\n");
+               return -ENODEV;
+       }
+       if (watchdog_device.timer6_tbl_ptr->irq == 0) {
+               pr_debug(PFX "Watchdog timer: timer %d invalid irq\n",
+                 sfi_mtimer_num);
+               return -ENODEV;
        }
 
-       watchdog_device.timer_load_count_addr = tmp_addr++;
-       watchdog_device.timer_current_value_addr = tmp_addr++;
-       watchdog_device.timer_control_addr = tmp_addr++;
-       watchdog_device.timer_clear_interrupt_addr = tmp_addr++;
-       watchdog_device.timer_interrupt_status_addr = tmp_addr++;
-
-       /* Set the default time values in device structure */
-
-       watchdog_device.intel_scu_notifier.notifier_call =
-               intel_scu_notify_sys;
-
-       ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier);
+       /* Reboot notifier */
+       watchdog_device.reboot_notifier.notifier_call = reboot_notifier;
+       watchdog_device.reboot_notifier.priority = 1;
+       ret = register_reboot_notifier(&watchdog_device.reboot_notifier);
        if (ret) {
-               printk(KERN_ERR PFX
-                       "Watchdog timer: cannot register notifier %d)\n", ret);
-               goto register_reboot_error;
+               pr_crit(PFX "cannot register reboot notifier %d\n", ret);
+               goto error_stop_timer;
        }
 
+       /* Do not publish the watchdog device when disable (TO BE REMOVED) */
        if (!disable_kernel_watchdog) {
                watchdog_device.miscdev.minor = WATCHDOG_MINOR;
                watchdog_device.miscdev.name = "watchdog";
@@ -688,94 +703,88 @@ static int __init intel_scu_watchdog_init(void)
 
                ret = misc_register(&watchdog_device.miscdev);
                if (ret) {
-                       printk(KERN_ERR PFX
-                              "Watchdog timer: cannot register miscdev %d err =%d\n",
-                              WATCHDOG_MINOR,
-                              ret);
-                       goto misc_register_error;
+                       pr_crit(PFX "Cannot register miscdev %d err =%d\n",
+                               WATCHDOG_MINOR, ret);
+                       goto error_reboot_notifier;
                }
        }
 
        wake_lock_init(&watchdog_wake_lock, WAKE_LOCK_SUSPEND,
                        "intel_scu_watchdog");
 
-       ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq,
+       /* MSI #7 handler for timer interrupts */
+       ret = request_irq((unsigned int)watchdog_device.timer7_tbl_ptr->irq,
                watchdog_timer_interrupt,
+               IRQF_SHARED|IRQF_NO_SUSPEND, "watchdog timer",
+               &watchdog_device);
+       if (ret) {
+               pr_err(PFX "error requesting irq %d\n",
+                      watchdog_device.timer7_tbl_ptr->irq);
+               pr_err(PFX "error value returned is %d\n", ret);
+               goto error_misc_register;
+       }
+
+       /* MSI #6 handler to dump registers */
+       ret = request_irq((unsigned int)watchdog_device.timer6_tbl_ptr->irq,
+               watchdog_warning_interrupt,
                IRQF_SHARED|IRQF_NO_SUSPEND, "watchdog",
-               &watchdog_device.timer_load_count_addr);
+               &watchdog_device);
        if (ret) {
-               printk(KERN_ERR "Watchdog timer: error requesting irq\n");
-               printk(KERN_ERR "Watchdog timer: error value returned is %d\n",
-                       ret);
-               goto request_irq_error;
+               pr_err(PFX "error requesting warning irq %d\n",
+                      watchdog_device.timer6_tbl_ptr->irq);
+               pr_err(PFX "error value returned is %d\n", ret);
+               goto error_request_irq;
        }
 
        /* set up the tasklet for handling interrupt duties */
        tasklet_init(&watchdog_device.interrupt_tasklet,
                watchdog_interrupt_tasklet_body, (unsigned long)0);
 
-#ifdef DEBUG
+#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
        init_timer(&softlock_timer);
-#endif /* DEBUG */
+#endif
 
        if (disable_kernel_watchdog) {
-               pr_debug("disabling the timer\n");
-
-               /* temporarily disable the timer */
-               iowrite32(0x00000002, watchdog_device.timer_control_addr);
-
-               /* Set all thresholds to 0 to disable timeouts */
-               watchdog_device.soft_threshold = 0;
-               watchdog_device.threshold = 0;
-
-               /* send the threshold and soft_threshold via IPC */
-               ret = watchdog_set_ipc(watchdog_device.soft_threshold,
-                                  watchdog_device.threshold);
-
-               if (ret != 0) {
-                       /* Make sure the watchdog timer is stopped */
-                       pr_warn("can't set ipc to disable at start\n");
-                       intel_scu_stop();
-                       return ret;
-               }
+               pr_debug(PFX "disabling the timer\n");
 
                /* Make sure timer is stopped */
-               intel_scu_stop();
-
+               ret = intel_scu_stop();
+               if (ret != 0)
+                       pr_debug(PFX "cant disable timer\n");
        }
-       return 0;
-
-/* error cleanup */
 
-request_irq_error:
+       watchdog_device.started = false;
 
-       misc_deregister(&watchdog_device.miscdev);
+       return 0;
 
-misc_register_error:
+error_request_irq:
+       free_irq(watchdog_device.timer7_tbl_ptr->irq, NULL);
 
-       pr_debug("Watchdog timer: misc_register_error\n");
-       unregister_reboot_notifier(&watchdog_device.intel_scu_notifier);
+error_misc_register:
+       misc_deregister(&watchdog_device.miscdev);
 
-register_reboot_error:
+error_reboot_notifier:
+       unregister_reboot_notifier(&watchdog_device.reboot_notifier);
 
+error_stop_timer:
        intel_scu_stop();
 
-       iounmap(watchdog_device.timer_load_count_addr);
-
        return ret;
 }
 
 static void __exit intel_scu_watchdog_exit(void)
 {
-#ifdef DEBUG
+       int ret = 0;
+#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
        del_timer_sync(&softlock_timer);
-#endif /* DEBUG */
+#endif
+
+       ret = intel_scu_stop();
+       if (ret != 0)
+               pr_err(PFX "cant disable timer\n");
 
        misc_deregister(&watchdog_device.miscdev);
-       unregister_reboot_notifier(&watchdog_device.intel_scu_notifier);
-       /* disable the timer */
-       iowrite32(0x00000002, watchdog_device.timer_control_addr);
-       iounmap(watchdog_device.timer_load_count_addr);
+       unregister_reboot_notifier(&watchdog_device.reboot_notifier);
 }
 
 #ifdef MODULE
@@ -787,8 +796,9 @@ rootfs_initcall(intel_scu_watchdog_init);
 module_exit(intel_scu_watchdog_exit);
 
 MODULE_AUTHOR("Intel Corporation");
+MODULE_AUTHOR("mark.a.allyn@intel.com");
+MODULE_AUTHOR("yannx.puech@intel.com");
 MODULE_DESCRIPTION("Intel SCU Watchdog Device Driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 MODULE_VERSION(WDT_VER);
-
index 2753987..42234f1 100644 (file)
 #ifndef __INTEL_SCU_WATCHDOG_H
 #define __INTEL_SCU_WATCHDOG_H
 
-#define PFX "Intel_SCU: "
+#define PFX "intel_scu_watchdog: "
 #define WDT_VER "0.3"
 
+#define DEFAULT_PRETIMEOUT 15
+#define DEFAULT_TIMEOUT 75
+#define DEFAULT_SCHEDULE_MARGIN 10
+#define DEFAULT_TIMER_DURATION (DEFAULT_TIMEOUT - \
+                               DEFAULT_PRETIMEOUT - DEFAULT_SCHEDULE_MARGIN)
+
 /* minimum time between interrupts */
 #define MIN_TIME_CYCLE 1
-
-#define DEFAULT_SOFT_TO_HARD_MARGIN 15
-
 #define MAX_TIME 170
-
-#define DEFAULT_TIME 75
-
 #define MAX_SOFT_TO_HARD_MARGIN (MAX_TIME-MIN_TIME_CYCLE)
 
-/**
- * Offset in OSNIB for letting firmware know that are are up and running
- * This is byte 7, which is offset 6
- */
+/* Offset of the reset counter in  OSNIB */
 #define OSNIB_WDOG_OFFSET 1
 
-/**
- * The value to put into OSNOB to let the firmware know we are
- * up and running
- */
+/* Value 0 to reset the reset counter */
 #define OSNIB_WRITE_VALUE 0
 
-/* The number of bytes to write to OSNOB */
+/* The number of bytes to write the value 0 to OSNIB */
 #define OSNIB_WRITE_SIZE 1
 
-/* The mask for OSNOB */
+/* The mask for OSNIB */
 #define OSNIB_WRITE_MASK 0x02
 
-/* Ajustment to clock tick frequency to make timing come out right */
-#define FREQ_ADJUSTMENT 8
-
 struct intel_scu_watchdog_dev {
        ulong driver_open;
        ulong driver_closed;
-       u32 timer_started;
-       u32 timer_set;
-       u32 threshold;
-       u32 soft_threshold;
-       u32 __iomem *timer_load_count_addr;
-       u32 __iomem *timer_current_value_addr;
-       u32 __iomem *timer_control_addr;
-       u32 __iomem *timer_clear_interrupt_addr;
-       u32 __iomem *timer_interrupt_status_addr;
-       struct sfi_timer_table_entry *timer_tbl_ptr;
-       struct notifier_block intel_scu_notifier;
+       bool started;
+       struct sfi_timer_table_entry *timer7_tbl_ptr;
+       struct sfi_timer_table_entry *timer6_tbl_ptr;
+       struct notifier_block reboot_notifier;
        struct miscdevice miscdev;
        struct tasklet_struct interrupt_tasklet;
 };
 
-extern int sfi_mtimer_num;
-
-/* extern struct sfi_timer_table_entry *sfi_get_mtmr(int hint); */
 #endif /* __INTEL_SCU_WATCHDOG_H */