*
*/
+#define DEBUG 1
+
#include <linux/compiler.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/jiffies.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
-#include <linux/delay.h>
#include <linux/sched.h>
+#include <linux/kernel_stat.h>
+#include <linux/delay.h>
#include <linux/signal.h>
#include <linux/sfi.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/wakelock.h>
#include <asm/irq.h>
#include <asm/atomic.h>
+
+/* See arch/x86/kernel/ipc_mrst.c */
#include <asm/intel_scu_ipc.h>
#include <asm/apb_timer.h>
-#include <asm/mrst.h>
#include "intel_scu_watchdog.h"
#define IPC_SET_WATCHDOG_TIMER 0xF8
+static DECLARE_WAIT_QUEUE_HEAD(read_wq);
+
+/* 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 bool disable_kernel_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.
+ *
+ */
+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);
+
+/**
+ * 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
+ */
+#if defined(CONFIG_DISABLE_SCU_WATCHDOG)
+module_param(disable_kernel_watchdog, bool, S_IRUGO);
+MODULE_PARM_DESC(disable_kernel_watchdog,
+ "Disable kernel watchdog"
+ "Set to 0, watchdog started at boot"
+ "and left running; Set to 1; watchdog"
+ "is not started until user space"
+ "watchdog daemon is started; also if the"
+ "timer is started by the iafw firmware, it"
+ "will be disabled upon initialization of this"
+ "driver if disable_kernel_watchdog is set");
+#endif
+
static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN;
-module_param(timer_margin, int, 0);
+module_param(timer_margin, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(timer_margin,
"Watchdog timer margin"
"Time between interrupt and resetting the system"
"This is the time for all keep alives to arrive");
static int timer_set = DEFAULT_TIME;
-module_param(timer_set, int, 0);
+module_param(timer_set, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(timer_set,
"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_boot. If:
- * force_boot == 0, then force boot on next watchdog interrupt after close,
- * force_boot == 1, then force boot immediately when device is closed.
+/* 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 int force_boot;
-module_param(force_boot, int, 0);
-MODULE_PARM_DESC(force_boot,
- "A value of 1 means that the driver will reboot"
+static bool force_reset = true;
+module_param(force_reset, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(force_reset,
+ "A true means that the driver will reboot"
"the system immediately if the /dev/watchdog device is closed"
- "A value of 0 means that when /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"
"watchdog timer will reset the system."
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_boot) {
+ if (force_reset) {
printk(KERN_CRIT PFX "Initiating system reboot.\n");
emergency_restart();
printk(KERN_CRIT PFX "Reboot didn't ?????\n");
{
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",
+ 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 -EINVAL;
}
return 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
+ * go out.
+ */
+static void smp_dumpstack(void *info)
+{
+ dump_stack();
+}
+
+static void dump_softlock_debug(unsigned long data)
+{
+ int i, reboot;
+ cputime64_t system[NR_CPUS], num_jifs;
+
+ num_jifs = jiffies - beattime;
+ for_each_possible_cpu(i) {
+ system[i] = cputime64_sub(kstat_cpu(i).cpustat.system,
+ heartbeats[i]);
+ }
+
+ reboot = 0;
+
+ for_each_possible_cpu(i) {
+ if ((num_jifs - cputime_to_jiffies(system[i])) <
+ msecs_to_jiffies(10)) {
+ WARN(1, "cpu %d wedged\n", i);
+ smp_call_function_single(i, smp_dumpstack, NULL, 1);
+ reboot = 1;
+ }
+ }
+
+ if (reboot) {
+ panic_timeout = 10;
+ panic("Soft lock on CPUs\n");
+ }
+
+}
+
+
/*
* IPC operations
*/
0);
if (ipc_ret != 0)
- pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret);
+ printk(KERN_CRIT PFX "Error Setting SCU Watchdog Timer: %x\n",
+ ipc_ret);
return ipc_ret;
};
* Intel_SCU operations
*/
-/* timer interrupt handler */
-static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id)
-{
- int int_status;
- int_status = ioread32(watchdog_device.timer_interrupt_status_addr);
-
- pr_debug("Watchdog timer: irq, int_status: %x\n", int_status);
-
- if (int_status != 0)
- return IRQ_NONE;
-
- /* 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");
- return IRQ_HANDLED;
- }
-
- /* temporarily disable the timer */
- iowrite32(0x00000002, watchdog_device.timer_control_addr);
-
- /* set the timer to the threshold */
- iowrite32(watchdog_device.threshold,
- watchdog_device.timer_load_count_addr);
-
- /* allow the timer to run */
- iowrite32(0x00000003, watchdog_device.timer_control_addr);
-
- return IRQ_HANDLED;
-}
static int intel_scu_keepalive(void)
{
+ pr_debug("Watchdog timer: keepalive: soft_threshold %x\n",
+ watchdog_device.soft_threshold);
+
/* read eoi register - clears interrupt */
ioread32(watchdog_device.timer_clear_interrupt_addr);
return 0;
}
+/* tasklet function for interrupt; keep interupt itself simple */
+static void watchdog_interrupt_tasklet_body(unsigned long data)
+{
+ int i, int_status;
+
+ pr_debug("Watchdog: interrupt tasklet body start\n");
+
+ if (!disable_kernel_watchdog) {
+ pr_debug("Watchdog: interrupt tasklet body disable clear\n");
+ if (!test_bit(0, &watchdog_device.driver_open)) {
+ /* if not yet open; keep alive here in kernel */
+ int_status = intel_scu_keepalive();
+ if (int_status) {
+ pr_debug("Watchdog timer: set do keepalive"
+ " from interrupt\n");
+ }
+ return;
+ }
+ } else {
+ pr_debug("Watchdog: interrupt tasklet body disable set\n");
+ /* disable the timer */
+ iowrite32(0x00000002, watchdog_device.timer_control_addr);
+ intel_scu_stop();
+ return;
+ }
+
+ /* wake up read to send data to user (reminder for keep alive */
+ warning_flag = 1;
+
+ /*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);
+
+ /* Wake up the daemon */
+ wake_up_interruptible(&read_wq);
+
+ /*
+ * Hold a timeout wakelock so user space watchdogd has a chance
+ * to run after waking up from s3
+ */
+ wake_lock_timeout(&watchdog_wake_lock, 5 * HZ);
+}
+
+/* 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");
+ return IRQ_HANDLED;
+ }
+
+ tasklet_schedule(&watchdog_device.interrupt_tasklet);
+
+ return IRQ_HANDLED;
+}
+
static int intel_scu_set_heartbeat(u32 t)
{
int ipc_ret;
- int retry_count;
- u32 soft_value;
- u32 hw_pre_value;
- u32 hw_value;
watchdog_device.timer_set = t;
watchdog_device.threshold =
pr_debug("Watchdog timer: set_heartbeat: soft_threshold is %x (hex)\n",
watchdog_device.soft_threshold);
- /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */
- /* watchdog timing come out right. */
- watchdog_device.threshold =
- watchdog_device.threshold / FREQ_ADJUSTMENT;
- watchdog_device.soft_threshold =
- watchdog_device.soft_threshold / FREQ_ADJUSTMENT;
/* temporarily disable the timer */
iowrite32(0x00000002, watchdog_device.timer_control_addr);
return ipc_ret;
}
- /* Soft Threshold set loop. Early versions of silicon did */
- /* not always set this count correctly. This loop checks */
- /* the value and retries if it was not set correctly. */
-
- retry_count = 0;
- soft_value = watchdog_device.soft_threshold & 0xFFFF0000;
- do {
-
- /* Make sure timer is stopped */
- intel_scu_stop();
-
- if (MAX_RETRY < retry_count++) {
- /* Unable to set timer value */
- pr_err("Watchdog timer: Unable to set timer\n");
- return -ENODEV;
- }
-
- /* set the timer to the soft threshold */
- iowrite32(watchdog_device.soft_threshold,
- watchdog_device.timer_load_count_addr);
-
- /* read count value before starting timer */
- hw_pre_value = ioread32(watchdog_device.timer_load_count_addr);
- hw_pre_value = hw_pre_value & 0xFFFF0000;
-
- /* Start the timer */
- iowrite32(0x00000003, watchdog_device.timer_control_addr);
-
- /* read the value the time loaded into its count reg */
- hw_value = ioread32(watchdog_device.timer_load_count_addr);
- hw_value = hw_value & 0xFFFF0000;
+ /* Make sure timer is stopped */
+ intel_scu_stop();
+ /* set the timer to the soft threshold */
+ iowrite32(watchdog_device.soft_threshold,
+ watchdog_device.timer_load_count_addr);
- } while (soft_value != hw_value);
+ /* Start the timer */
+ iowrite32(0x00000003, watchdog_device.timer_control_addr);
watchdog_device.timer_started = 1;
/*
* This watchdog should not be closed, after the timer
* is started with the WDIPC_SETTIMEOUT ioctl
- * If force_boot is set watchdog_fire() will cause an
- * immediate reset. If force_boot is not set, the watchdog
+ * If force_reset is set watchdog_fire() will cause an
+ * immediate reset. If force_reset 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_and_clear_bit(0, &watchdog_device.driver_open)) {
+ if (!test_bit(0, &watchdog_device.driver_open)) {
pr_debug("Watchdog timer: intel_scu_release, without open\n");
return -ENOTTY;
}
if (!watchdog_device.timer_started) {
/* Just close, since timer has not been started */
- pr_debug("Watchdog timer: closed, without starting timer\n");
+ pr_debug("Watchdog timer: Closed, without starting timer\n");
return 0;
}
/* Refresh the timer for one more interval */
intel_scu_keepalive();
- /* Reboot system (if force_boot is set) */
+ /* Reboot system (if force_reset is set) */
watchdog_fire();
- /* We should only reach this point if force_boot is not set */
+ /* We should only reach this point if force_reset is not set */
return 0;
}
loff_t *ppos)
{
- if (watchdog_device.timer_started)
+ if (watchdog_device.timer_started) {
/* Watchdog already started, keep it alive */
intel_scu_keepalive();
- else
+ wake_unlock(&watchdog_wake_lock);
+ } else
/* Start watchdog with timer value set by init */
intel_scu_set_heartbeat(watchdog_device.timer_set);
return len;
}
+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;
+
+ /* we wait for the next interrupt; if more than one */
+ /* interrupt has occurred since the last read, we */
+ /* dont care. The data is not critical. We will do */
+ /* a copy to user each time we get and interrupt */
+ /* It is up to the Watchdog daemon to be ready to */
+ /* do the read (which signifies that the driver is */
+ /* 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);
+
+ if (result != 0)
+ return -EFAULT;
+ else
+ return 1;
+
+}
+
+static unsigned int intel_scu_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ poll_wait(file, &read_wq, wait);
+
+ 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)
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = intel_scu_write,
+ .read = intel_scu_read,
.unlocked_ioctl = intel_scu_ioctl,
.open = intel_scu_open,
+ .poll = intel_scu_poll,
.release = intel_scu_release,
};
int ret;
u32 __iomem *tmp_addr;
- /*
- * We don't really need to check this as the SFI timer get will fail
- * but if we do so we can exit with a clearer reason and no noise.
- *
- * If it isn't an intel MID device then it doesn't have this watchdog
- */
- if (!mrst_identify_cpu())
- return -ENODEV;
/* 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) "
+ 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);
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 not available\n");
+ pr_debug("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 not have valid physical memory\n",
- sfi_mtimer_num);
+ pr_debug("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",
- sfi_mtimer_num);
+ sfi_mtimer_num);
return -ENODEV;
}
/* Set the default time values in device structure */
- watchdog_device.timer_set = timer_set;
- 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;
-
-
watchdog_device.intel_scu_notifier.notifier_call =
intel_scu_notify_sys;
ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier);
if (ret) {
- pr_err("Watchdog timer: cannot register notifier %d)\n", ret);
+ printk(KERN_ERR PFX
+ "Watchdog timer: cannot register notifier %d)\n", ret);
goto register_reboot_error;
}
- watchdog_device.miscdev.minor = WATCHDOG_MINOR;
- watchdog_device.miscdev.name = "watchdog";
- watchdog_device.miscdev.fops = &intel_scu_fops;
-
- ret = misc_register(&watchdog_device.miscdev);
- if (ret) {
- pr_err("Watchdog timer: cannot register miscdev %d err =%d\n",
- WATCHDOG_MINOR, ret);
- goto misc_register_error;
+ if (!disable_kernel_watchdog) {
+ watchdog_device.miscdev.minor = WATCHDOG_MINOR;
+ watchdog_device.miscdev.name = "watchdog";
+ watchdog_device.miscdev.fops = &intel_scu_fops;
+
+ 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;
+ }
}
+ wake_lock_init(&watchdog_wake_lock, WAKE_LOCK_SUSPEND,
+ "intel_scu_watchdog");
+
ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq,
watchdog_timer_interrupt,
- IRQF_SHARED, "watchdog",
+ IRQF_SHARED|IRQF_NO_SUSPEND, "watchdog",
&watchdog_device.timer_load_count_addr);
if (ret) {
- pr_err("Watchdog timer: error requesting irq %d\n", 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;
}
- /* Make sure timer is disabled before returning */
- intel_scu_stop();
+
+ /* set up the tasklet for handling interrupt duties */
+ tasklet_init(&watchdog_device.interrupt_tasklet,
+ watchdog_interrupt_tasklet_body, (unsigned long)0);
+
+ /* set and start timer */
+ if (!disable_kernel_watchdog) {
+ ret = intel_scu_set_heartbeat(timer_set);
+
+ if (ret)
+ pr_debug("cannot set watchdog during init\n");
+
+ } else {
+ iowrite32(0x00000002, watchdog_device.timer_control_addr);
+ intel_scu_stop();
+ }
+
+ init_timer(&softlock_timer);
+
return 0;
/* error cleanup */
request_irq_error:
+
misc_deregister(&watchdog_device.miscdev);
+
misc_register_error:
+
+ pr_debug("Watchdog timer: misc_register_error\n");
unregister_reboot_notifier(&watchdog_device.intel_scu_notifier);
+
register_reboot_error:
+
intel_scu_stop();
+
iounmap(watchdog_device.timer_load_count_addr);
+
return ret;
}
static void __exit intel_scu_watchdog_exit(void)
{
+ del_timer_sync(&softlock_timer);
misc_deregister(&watchdog_device.miscdev);
unregister_reboot_notifier(&watchdog_device.intel_scu_notifier);
iounmap(watchdog_device.timer_load_count_addr);
}
-late_initcall(intel_scu_watchdog_init);
+#ifdef MODULE
+module_init(intel_scu_watchdog_init);
+#else
+rootfs_initcall(intel_scu_watchdog_init);
+#endif
+
module_exit(intel_scu_watchdog_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_VERSION(WDT_VER);
+