From d7c68dede3e66d7d90ed8ab017fd1dcdbdf9943d Mon Sep 17 00:00:00 2001 From: Mark Allyn Date: Fri, 6 Jan 2012 15:02:22 -0800 Subject: [PATCH] [PORT FROM R2] drivers: intel_scu_watchdog: use scu access for timer; don't use regs 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 Reviewed-by: Chotard, Celine Tested-by: Chotard, Celine Reviewed-by: buildbot Tested-by: buildbot --- Documentation/watchdog/intel-scu-watchdog.txt | 70 +++ drivers/watchdog/intel_scu_watchdog.c | 774 +++++++++++++------------- drivers/watchdog/intel_scu_watchdog.h | 49 +- 3 files changed, 477 insertions(+), 416 deletions(-) create mode 100644 Documentation/watchdog/intel-scu-watchdog.txt diff --git a/Documentation/watchdog/intel-scu-watchdog.txt b/Documentation/watchdog/intel-scu-watchdog.txt new file mode 100644 index 0000000..0e7afd9 --- /dev/null +++ b/Documentation/watchdog/intel-scu-watchdog.txt @@ -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 + + +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 ----->||| +|<--------------------- 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. diff --git a/drivers/watchdog/intel_scu_watchdog.c b/drivers/watchdog/intel_scu_watchdog.c index 630e9c1..804ed92 100644 --- a/drivers/watchdog/intel_scu_watchdog.c +++ b/drivers/watchdog/intel_scu_watchdog.c @@ -22,7 +22,7 @@ * */ -#define DEBUG 1 +/* See Documentation/watchdog/intel-scu-watchdog.txt */ #include #include @@ -52,60 +52,47 @@ #include #include #include - -/* See arch/x86/kernel/ipc_mrst.c */ +#include #include #include #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); - diff --git a/drivers/watchdog/intel_scu_watchdog.h b/drivers/watchdog/intel_scu_watchdog.h index 2753987..42234f1 100644 --- a/drivers/watchdog/intel_scu_watchdog.h +++ b/drivers/watchdog/intel_scu_watchdog.h @@ -25,60 +25,41 @@ #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 */ -- 2.7.4