blob: 1e04d73dda8d590151eb5e8c87b47c66873f1219 [file] [log] [blame]
/*
* Intel_SCU 0.3: An Intel SCU IOH Based Watchdog Device
* for Intel part #(s):
* - AF82MP20 PCH
*
* Copyright (C) 2009-2013 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General
* Public License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
* The full GNU General Public License is included in this
* distribution in the file called COPYING.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* See Documentation/watchdog/intel-scu-watchdog.txt */
#include <linux/module.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/reboot.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/rpmsg.h>
#include <linux/nmi.h>
#include <asm/intel_scu_ipcutil.h>
#include <asm/intel_mid_rpmsg.h>
#include <asm/intel-mid.h>
#include "intel_scu_watchdog_evo.h"
/* Adjustment flags */
#define CONFIG_INTEL_SCU_SOFT_LOCKUP
#define CONFIG_DEBUG_WATCHDOG
/* Defines */
#define STRING_RESET_TYPE_MAX_LEN 11
#define STRING_COLD_OFF "COLD_OFF"
#define STRING_COLD_RESET "COLD_RESET"
#define STRING_COLD_BOOT "COLD_BOOT"
#define EXT_TIMER0_MSI 12
#define IPC_WATCHDOG 0xF8
enum {
SCU_WATCHDOG_START = 0,
SCU_WATCHDOG_STOP,
SCU_WATCHDOG_KEEPALIVE,
SCU_WATCHDOG_SET_ACTION_ON_TIMEOUT
};
enum {
SCU_COLD_OFF_ON_TIMEOUT = 0,
SCU_COLD_RESET_ON_TIMEOUT,
SCU_COLD_BOOT_ON_TIMEOUT,
SCU_DO_NOTHING_ON_TIMEOUT
};
#ifdef CONFIG_DEBUG_FS
#define SECURITY_WATCHDOG_ADDR 0xFF222230
#define STRING_NONE "NONE"
#endif
/* Statics */
static int reset_type_to_string(int reset_type, char *string);
static int string_to_reset_type(const char *string, int *reset_type);
static struct intel_scu_watchdog_dev watchdog_device;
static unsigned char osnib_reset = OSNIB_WRITE_VALUE;
/* Module params */
static bool kicking_active = true;
#ifdef CONFIG_DEBUG_WATCHDOG
module_param(kicking_active, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(kicking_active,
"Deactivate the kicking will result in a cold reset"
"after a while");
#endif
static bool disable_kernel_watchdog = false;
#ifdef CONFIG_DISABLE_SCU_WATCHDOG
/*
* Please note that we are using a config CONFIG_DISABLE_SCU_WATCHDOG
* because this boot parameter should only be settable in a developement
*/
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 pre_timeout = DEFAULT_PRETIMEOUT;
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 35 to 170"
"This is the time for all keep alives to arrive");
static bool reset_on_release = true;
#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
/*
* heartbeats: cpu last kstat.system times
* beattime : jiffies 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 u64 heartbeats[NR_CPUS];
static u64 beattime;
#define SOFT_LOCK_TIME 10
static void dump_softlock_debug(unsigned long data);
DEFINE_TIMER(softlock_timer, dump_softlock_debug, 0, 0);
static struct rpmsg_instance *watchdog_instance;
/* 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;
u64 system[NR_CPUS], num_jifs;
memset(system, 0, NR_CPUS*sizeof(u64));
num_jifs = jiffies - beattime;
for_each_possible_cpu(i) {
system[i] = kcpustat_cpu(i).cpustat[CPUTIME_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;
trigger_all_cpu_backtrace();
panic("Soft lock on CPUs\n");
}
}
#endif /* CONFIG_INTEL_SCU_SOFT_LOCKUP */
/* Check current timeouts */
/* Timeout bounds come from the MODULE_PARAM_DESC description */
static int check_timeouts(int pre_timeout_time, int timeout_time)
{
if (pre_timeout_time >= timeout_time)
return -EINVAL;
if (pre_timeout_time > 155 || pre_timeout_time < 1)
return -EINVAL;
if (timeout_time > 170 || timeout_time < 35)
return -EINVAL;
return 0;
}
/* Set the different timeouts needed by the SCU FW and start the
* kernel watchdog */
static int watchdog_set_timeouts_and_start(int pretimeout,
int timeout)
{
int ret, error = 0;
struct ipc_wd_start {
u32 pretimeout;
u32 timeout;
} ipc_wd_start = { pretimeout, timeout };
ret = rpmsg_send_command(watchdog_instance, IPC_WATCHDOG,
SCU_WATCHDOG_START, (u8 *)&ipc_wd_start,
NULL, sizeof(ipc_wd_start), 0);
if (ret) {
pr_crit("Error configuring and starting watchdog: %d\n",
ret);
error = -EIO;
}
return error;
}
/* Provisioning function for future enhancement : allow to fine tune timing
according to watchdog action settings */
static int watchdog_set_appropriate_timeouts(void)
{
pr_debug("Setting shutdown timeouts\n");
return watchdog_set_timeouts_and_start(pre_timeout, timeout);
}
/* Keep alive */
static int watchdog_keepalive(void)
{
int ret, error = 0;
pr_debug("%s\n", __func__);
if (unlikely(!kicking_active)) {
/* Close our eyes */
pr_err("Transparent kicking\n");
return 0;
}
/* Really kick it */
ret = rpmsg_send_simple_command(watchdog_instance, IPC_WATCHDOG,
SCU_WATCHDOG_KEEPALIVE);
if (ret) {
pr_crit("Error executing keepalive: %x\n", ret);
error = -EIO;
}
return error;
}
/* stops the timer */
static int watchdog_stop(void)
{
int ret = 0;
int error = 0;
pr_crit("%s\n", __func__);
ret = rpmsg_send_simple_command(watchdog_instance, IPC_WATCHDOG,
SCU_WATCHDOG_STOP);
if (ret) {
pr_crit("Error stopping watchdog: %x\n", ret);
error = -EIO;
}
watchdog_device.started = false;
return error;
}
/* warning interrupt handler */
static irqreturn_t watchdog_warning_interrupt(int irq, void *dev_id)
{
if (unlikely(!kicking_active))
pr_warn("[SHTDWN] WATCHDOG TIMEOUT for test!, %s\n", __func__);
else
pr_warn("[SHTDWN] %s, WATCHDOG TIMEOUT!\n", __func__);
/* Let's reset the platform after dumping some data */
trigger_all_cpu_backtrace();
panic("Kernel Watchdog");
/* This code should not be reached */
return IRQ_HANDLED;
}
/* Program and starts the timer */
static int watchdog_config_and_start(u32 newtimeout, u32 newpretimeout)
{
int ret;
timeout = newtimeout;
pre_timeout = newpretimeout;
pr_debug("timeout=%ds, pre_timeout=%ds\n", timeout, pre_timeout);
/* Configure the watchdog */
ret = watchdog_set_timeouts_and_start(pre_timeout, timeout);
if (ret) {
pr_err("%s: Cannot configure the watchdog\n", __func__);
/* Make sure the watchdog timer is stopped */
watchdog_stop();
return ret;
}
watchdog_device.started = true;
return 0;
}
/* Open */
static int intel_scu_open(struct inode *inode, struct file *file)
{
/* Set flag to indicate that watchdog device is open */
if (test_and_set_bit(0, &watchdog_device.driver_open)) {
pr_err("watchdog device is busy\n");
return -EBUSY;
}
/* Check for reopen of driver. Reopens are not allowed */
if (watchdog_device.driver_closed) {
pr_err("watchdog device has been closed\n");
return -EPERM;
}
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 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_err("intel_scu_release, without open\n");
return -ENOTTY;
}
if (!watchdog_device.started) {
/* Just close, since timer has not been started */
pr_err("Closed, without starting timer\n");
return 0;
}
pr_crit("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 */
watchdog_keepalive();
/* Reboot system if requested */
if (reset_on_release) {
pr_crit("Initiating system reboot.\n");
emergency_restart();
}
pr_crit("Immediate Reboot Disabled\n");
pr_crit("System will reset when watchdog timer expire!\n");
return 0;
}
/* Write */
static ssize_t intel_scu_write(struct file *file, char const *data, size_t len,
loff_t *ppos)
{
pr_debug("watchdog %s\n", __func__);
if (watchdog_device.shutdown_flag == true)
/* do nothing if we are shutting down */
return len;
if (watchdog_device.started) {
/* Watchdog already started, keep it alive */
watchdog_keepalive();
}
return len;
}
/* 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 val;
u32 new_pre_timeout;
int options;
static const struct watchdog_info ident = {
.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,
sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
pr_debug("%s: KeepAlive ioctl\n", __func__);
if (!watchdog_device.started)
return -EINVAL;
watchdog_keepalive();
return 0;
case WDIOC_SETTIMEOUT:
pr_debug("%s: SetTimeout ioctl\n", __func__);
if (watchdog_device.started)
return -EBUSY;
if (get_user(val, p))
return -EFAULT;
new_pre_timeout = val-15;
if (check_timeouts(new_pre_timeout, val)) {
pr_warn("%s: Invalid timeout thresholds (timeout: %d, pretimeout: %d) \n", __func__, val, new_pre_timeout);
return -EINVAL;
}
pre_timeout = new_pre_timeout;
timeout = val;
return 0;
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
case WDIOC_SETOPTIONS:
if (get_user(options, p))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
pr_debug("%s: Stopping the watchdog\n", __func__);
watchdog_stop();
return 0;
}
if (options & WDIOS_ENABLECARD) {
pr_debug("%s: Starting the watchdog\n", __func__);
if (watchdog_device.started)
return -EBUSY;
if (check_timeouts(pre_timeout, timeout)) {
pr_warn("%s: Invalid thresholds\n",
__func__);
return -EINVAL;
}
if (watchdog_config_and_start(timeout, pre_timeout))
return -EINVAL;
return 0;
}
return 0;
default:
return -ENOTTY;
}
}
static int watchdog_set_reset_type(int reset_type)
{
int ret;
struct ipc_wd_on_timeout {
u32 reset_type;
} ipc_wd_on_timeout = { reset_type };
ret = rpmsg_send_command(watchdog_instance, IPC_WATCHDOG,
SCU_WATCHDOG_SET_ACTION_ON_TIMEOUT,
(u8 *)&ipc_wd_on_timeout, NULL,
sizeof(ipc_wd_on_timeout), 0);
if (ret) {
pr_crit("Error setting watchdog action: %d\n", ret);
return -EIO;
}
watchdog_device.normal_wd_action = reset_type;
return 0;
}
/* Reboot notifier */
static int reboot_notifier(struct notifier_block *this,
unsigned long code,
void *another_unused)
{
int ret;
if (code == SYS_RESTART || code == SYS_HALT || code == SYS_POWER_OFF) {
pr_warn("Reboot notifier\n");
if (watchdog_set_appropriate_timeouts())
pr_crit("reboot notifier cant set time\n");
switch (code) {
case SYS_RESTART:
ret = watchdog_set_reset_type(
watchdog_device.reboot_wd_action);
break;
case SYS_HALT:
case SYS_POWER_OFF:
ret = watchdog_set_reset_type(
watchdog_device.shutdown_wd_action);
break;
}
if (ret)
pr_err("%s: could not set reset type\n", __func__);
#ifdef CONFIG_DEBUG_FS
/* debugfs entry to generate a BUG during
any shutdown/reboot call */
if (watchdog_device.panic_reboot_notifier)
BUG();
#endif
/* Don't do instant reset on close */
reset_on_release = false;
/* Kick once again */
if (disable_kernel_watchdog == false) {
ret = watchdog_keepalive();
if (ret)
pr_warn("%s: no keep alive\n", __func__);
/* Don't allow any more keep-alives */
watchdog_device.shutdown_flag = true;
}
}
return NOTIFY_DONE;
}
#ifdef CONFIG_DEBUG_FS
/* This code triggers a Security Watchdog */
int open_security(struct inode *i, struct file *f)
{
int ret = 0;
u64 *ptr;
u32 value;
ptr = ioremap_nocache(SECURITY_WATCHDOG_ADDR, sizeof(u32));
if (!ptr) {
pr_err("cannot open secwd's debugfile\n");
ret = -ENODEV;
goto error;
}
value = readl(ptr); /* trigger */
pr_err("%s: This code should never be reached but it got %x\n",
__func__, (unsigned int)value);
error:
return ret;
}
static const struct file_operations security_watchdog_fops = {
.open = open_security,
};
static int kwd_trigger_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
pr_debug("kwd_trigger_write\n");
panic("Kernel watchdog triggered\n");
return 0;
}
static const struct file_operations kwd_trigger_fops = {
.open = nonseekable_open,
.write = kwd_trigger_write,
.llseek = no_llseek,
};
static int kwd_reset_type_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t kwd_reset_type_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
ssize_t len;
int ret;
char str[STRING_RESET_TYPE_MAX_LEN + 1];
pr_debug("reading reset_type of %x\n",
watchdog_device.normal_wd_action);
if (*ppos > 0)
return 0;
ret = reset_type_to_string(watchdog_device.normal_wd_action, str);
if (ret)
return -EINVAL;
else {
for (len = 0; len < (STRING_RESET_TYPE_MAX_LEN - 1)
&& str[len] != '\0'; len++)
;
str[len++] = '\n';
ret = copy_to_user(buff, str, len);
}
*ppos += len;
return len;
}
static ssize_t kwd_reset_type_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
char str[STRING_RESET_TYPE_MAX_LEN];
unsigned long res;
int ret, reset_type;
if (count > STRING_RESET_TYPE_MAX_LEN) {
pr_err("Invalid size: count=%d\n", count);
return -EINVAL;
}
memset(str, 0x00, STRING_RESET_TYPE_MAX_LEN);
res = copy_from_user((void *)str,
(void __user *)buff,
(unsigned long)min((unsigned long)(count-1),
(unsigned long)(STRING_RESET_TYPE_MAX_LEN-1)));
if (res) {
pr_err("%s: copy to user failed\n", __func__);
return -EINVAL;
}
pr_debug("writing reset_type of %s\n", str);
ret = string_to_reset_type(str, &reset_type);
if (ret) {
pr_err("Invalid value\n");
return -EINVAL;
}
ret = watchdog_set_reset_type(reset_type);
if (ret) {
pr_err("%s: could not set reset type\n", __func__);
return -EINVAL;
}
return count;
}
static const struct file_operations kwd_reset_type_fops = {
.open = nonseekable_open,
.release = kwd_reset_type_release,
.read = kwd_reset_type_read,
.write = kwd_reset_type_write,
.llseek = no_llseek,
};
static ssize_t kwd_panic_reboot_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
# define RET_SIZE 3 /* prints only 2 chars : '0' or '1', plus '\n' */
char str[RET_SIZE];
int res;
if (*ppos > 0)
return 0;
strcpy(str, watchdog_device.panic_reboot_notifier ? "1\n" : "0\n");
res = copy_to_user(buff, str, RET_SIZE);
if (res) {
pr_err("%s: copy to user failed\n", __func__);
return -EINVAL;
}
*ppos += RET_SIZE-1;
return RET_SIZE-1;
}
static ssize_t kwd_panic_reboot_write(struct file *file,
const char __user *buff, size_t count, loff_t *ppos)
{
/* whatever is written, simply set flag to TRUE */
watchdog_device.panic_reboot_notifier = true;
return count;
}
static const struct file_operations kwd_panic_reboot_fops = {
.open = nonseekable_open,
.read = kwd_panic_reboot_read,
.write = kwd_panic_reboot_write,
.llseek = no_llseek,
};
static int remove_debugfs_entries(void)
{
struct intel_scu_watchdog_dev *dev = &watchdog_device;
/* /sys/kernel/debug/watchdog */
debugfs_remove_recursive(dev->dfs_wd);
return 0;
}
static int create_debugfs_entries(void)
{
struct intel_scu_watchdog_dev *dev = &watchdog_device;
/* /sys/kernel/debug/watchdog */
dev->dfs_wd = debugfs_create_dir("watchdog", NULL);
if (!dev->dfs_wd) {
pr_err("%s: Error, cannot create main dir\n", __func__);
goto error;
}
/* /sys/kernel/debug/watchdog/security_watchdog */
dev->dfs_secwd = debugfs_create_dir("security_watchdog", dev->dfs_wd);
if (!dev->dfs_secwd) {
pr_err("%s: Error, cannot create sec dir\n", __func__);
goto error;
}
/* /sys/kernel/debug/watchdog/security_watchdog/trigger */
dev->dfs_secwd_trigger = debugfs_create_file("trigger",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
dev->dfs_secwd, NULL,
&security_watchdog_fops);
if (!dev->dfs_secwd_trigger) {
pr_err("%s: Error, cannot create sec file\n", __func__);
goto error;
}
/* /sys/kernel/debug/watchdog/kernel_watchdog */
dev->dfs_kwd = debugfs_create_dir("kernel_watchdog", dev->dfs_wd);
if (!dev->dfs_kwd) {
pr_err("%s: Error, cannot create kwd dir\n", __func__);
goto error;
}
/* /sys/kernel/debug/watchdog/kernel_watchdog/trigger */
dev->dfs_kwd_trigger = debugfs_create_file("trigger",
S_IFREG | S_IWUSR | S_IWGRP,
dev->dfs_kwd, NULL,
&kwd_trigger_fops);
if (!dev->dfs_kwd_trigger) {
pr_err("%s: Error, cannot create kwd trigger file\n",
__func__);
goto error;
}
/* /sys/kernel/debug/watchdog/kernel_watchdog/reset_type */
dev->dfs_kwd_trigger = debugfs_create_file("reset_type",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
dev->dfs_kwd, NULL,
&kwd_reset_type_fops);
if (!dev->dfs_kwd_trigger) {
pr_err("%s: Error, cannot create kwd trigger file\n",
__func__);
goto error;
}
/* /sys/kernel/debug/watchdog/kernel_watchdog/panic_reboot_notifier */
dev->dfs_kwd_panic_reboot = debugfs_create_file("panic_reboot_notifier",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
dev->dfs_kwd, NULL,
&kwd_panic_reboot_fops);
if (!dev->dfs_kwd_panic_reboot) {
pr_err("%s: Error, cannot create kwd panic_reboot_notifier file\n",
__func__);
goto error;
}
return 0;
error:
remove_debugfs_entries();
return 1;
}
#endif /* CONFIG_DEBUG_FS*/
/* Kernel Interfaces */
static const struct file_operations intel_scu_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = intel_scu_write,
.unlocked_ioctl = intel_scu_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = intel_scu_ioctl,
#endif
.open = intel_scu_open,
.release = intel_scu_release,
};
/* sysfs entry to disable watchdog */
#ifdef CONFIG_DISABLE_SCU_WATCHDOG
static ssize_t disable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int ret;
if (!strtobool(buf, &disable_kernel_watchdog)) {
if (disable_kernel_watchdog) {
ret = watchdog_stop();
if (ret)
pr_err("cannot disable the timer\n");
} else {
ret = watchdog_config_and_start(timeout, pre_timeout);
if (ret)
return -EINVAL;
}
} else {
pr_err("got invalid value\n");
return -EINVAL;
}
return size;
}
static ssize_t disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_debug("%s\n", __func__);
if (disable_kernel_watchdog)
return sprintf(buf, "1\n");
return sprintf(buf, "0\n");
}
static DEVICE_ATTR(disable, S_IWUSR | S_IRUGO,
disable_show, disable_store);
#endif
#define OSNIB_WDOG_COUNTER_MASK 0xF0
#define OSNIB_WDOG_COUNTER_SHIFT 4
#define WDOG_COUNTER_MAX_VALUE 3
static ssize_t counter_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int ret;
pr_debug("%s\n", __func__);
ret = sscanf(buf, "%hhu", &osnib_reset);
if (ret != 1) {
pr_err(PFX "cannot get counter value\n");
if (ret == 0)
ret = -EINVAL;
return ret;
}
if (osnib_reset > WDOG_COUNTER_MAX_VALUE)
osnib_reset = WDOG_COUNTER_MAX_VALUE;
osnib_reset = ((osnib_reset << OSNIB_WDOG_COUNTER_SHIFT) &
OSNIB_WDOG_COUNTER_MASK);
ret = intel_scu_ipc_write_osnib_wd(&osnib_reset);
if (ret != 0) {
pr_err(PFX "cannot write OSNIB\n");
return -EINVAL;
}
return size;
}
static ssize_t counter_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned char osnib_read = (unsigned char)0;
int ret;
pr_debug("%s\n", __func__);
ret = intel_scu_ipc_read_osnib_wd(&osnib_read);
if (ret != 0)
return -EIO;
return sprintf(buf, "%d\n", (int)((osnib_read & OSNIB_WDOG_COUNTER_MASK)
>> OSNIB_WDOG_COUNTER_SHIFT));
}
static int reset_type_to_string(int reset_type, char *string)
{
switch (reset_type) {
case SCU_COLD_BOOT_ON_TIMEOUT:
strcpy(string, STRING_COLD_BOOT);
break;
case SCU_COLD_RESET_ON_TIMEOUT:
strcpy(string, STRING_COLD_RESET);
break;
case SCU_COLD_OFF_ON_TIMEOUT:
strcpy(string, STRING_COLD_OFF);
break;
#ifdef CONFIG_DEBUG_FS
case SCU_DO_NOTHING_ON_TIMEOUT:
/* The IPC command DONOTHING is provided */
/* for debug purpose only. */
strcpy(string, STRING_NONE);
break;
#endif
default:
return 1;
}
return 0;
}
static int string_to_reset_type(const char *string, int *reset_type)
{
if (!reset_type || !string)
return 1;
if (strncmp(string, STRING_COLD_RESET,
sizeof(STRING_COLD_RESET) - 1) == 0) {
*reset_type = SCU_COLD_RESET_ON_TIMEOUT;
return 0;
}
if (strncmp(string, STRING_COLD_BOOT,
sizeof(STRING_COLD_BOOT) - 1) == 0) {
*reset_type = SCU_COLD_BOOT_ON_TIMEOUT;
return 0;
}
if (strncmp(string, STRING_COLD_OFF,
sizeof(STRING_COLD_OFF) - 1) == 0) {
*reset_type = SCU_COLD_OFF_ON_TIMEOUT;
return 0;
}
#ifdef CONFIG_DEBUG_FS
if (strncmp(string, STRING_NONE,
sizeof(STRING_NONE) - 1) == 0) {
*reset_type = SCU_DO_NOTHING_ON_TIMEOUT;
return 0;
}
#endif
/* We should not be here, this is an error case */
pr_debug("Invalid reset type value\n");
return 1;
}
static ssize_t reboot_ongoing_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int ret;
pr_debug("%s\n", __func__);
/* reprogram timeouts. if error : continue */
ret = watchdog_set_appropriate_timeouts();
if (ret)
pr_err("%s: could not set timeouts\n", __func__);
/* restore reset type */
watchdog_set_reset_type(watchdog_device.reboot_wd_action);
if (ret) {
pr_err("%s: could not set reset type\n", __func__);
return -EINVAL;
}
return size;
}
static ssize_t shutdown_ongoing_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int ret;
pr_debug("%s\n", __func__);
/* reprogram timeouts. if error : continue */
ret = watchdog_set_appropriate_timeouts();
if (ret)
pr_err("%s: could not set timeouts\n", __func__);
/* restore reset type */
watchdog_set_reset_type(watchdog_device.shutdown_wd_action);
if (ret) {
pr_err("%s: could not set reset type\n", __func__);
return -EINVAL;
}
return size;
}
static ssize_t normal_config_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (reset_type_to_string(watchdog_device.normal_wd_action, buf) != 0)
return -EINVAL;
strcat(buf, "\n");
return strlen(buf);
}
static ssize_t normal_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (string_to_reset_type(buf, &watchdog_device.normal_wd_action) != 0)
return -EINVAL;
if (watchdog_set_reset_type(watchdog_device.normal_wd_action) != 0)
return -EINVAL;
return size;
}
static ssize_t reboot_config_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (reset_type_to_string(watchdog_device.reboot_wd_action, buf) != 0)
return -EINVAL;
strcat(buf, "\n");
return strlen(buf);
}
static ssize_t reboot_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (string_to_reset_type(buf, &watchdog_device.reboot_wd_action) != 0)
return -EINVAL;
return size;
}
static ssize_t shutdown_config_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (reset_type_to_string(watchdog_device.shutdown_wd_action, buf) != 0)
return -EINVAL;
strcat(buf, "\n");
return strlen(buf);
}
static ssize_t shutdown_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (string_to_reset_type(buf, &watchdog_device.shutdown_wd_action) != 0)
return -EINVAL;
return size;
}
/* Watchdog behavior depending on system phase */
static DEVICE_ATTR(normal_config, S_IWUSR | S_IRUGO,
normal_config_show, normal_config_store);
static DEVICE_ATTR(reboot_config, S_IWUSR | S_IRUGO,
reboot_config_show, reboot_config_store);
static DEVICE_ATTR(shutdown_config, S_IWUSR | S_IRUGO,
shutdown_config_show, shutdown_config_store);
static DEVICE_ATTR(reboot_ongoing, S_IWUSR,
NULL, reboot_ongoing_store);
static DEVICE_ATTR(shutdown_ongoing, S_IWUSR,
NULL, shutdown_ongoing_store);
/* Reset counter watchdog entry */
static DEVICE_ATTR(counter, S_IWUSR | S_IRUGO,
counter_show, counter_store);
int create_watchdog_sysfs_files(void)
{
int ret;
#ifdef CONFIG_DISABLE_SCU_WATCHDOG
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_disable);
if (ret) {
pr_warn("cant register dev file for disable\n");
return ret;
}
#endif
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_normal_config);
if (ret) {
pr_warn("cant register dev file for normal_config\n");
return ret;
}
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_reboot_config);
if (ret) {
pr_warn("cant register dev file for reboot_config\n");
return ret;
}
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_shutdown_config);
if (ret) {
pr_warn("cant register dev file for shutdown_config\n");
return ret;
}
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_counter);
if (ret) {
pr_warn("cant register dev file for counter\n");
return ret;
}
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_reboot_ongoing);
if (ret) {
pr_warn("cant register dev file for reboot_ongoing\n");
return ret;
}
ret = device_create_file(watchdog_device.miscdev.this_device,
&dev_attr_shutdown_ongoing);
if (ret) {
pr_warn("cant register dev file for shutdown_ongoing\n");
return ret;
}
return 0;
}
int remove_watchdog_sysfs_files(void)
{
#ifdef CONFIG_DISABLE_SCU_WATCHDOG
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_disable);
#endif
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_normal_config);
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_reboot_config);
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_shutdown_config);
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_counter);
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_reboot_ongoing);
device_remove_file(watchdog_device.miscdev.this_device,
&dev_attr_shutdown_ongoing);
return 0;
}
static int handle_mrfl_dev_ioapic(int irq)
{
int ret = 0;
int ioapic;
struct io_apic_irq_attr irq_attr;
ioapic = mp_find_ioapic(irq);
if (ioapic >= 0) {
irq_attr.ioapic = ioapic;
irq_attr.ioapic_pin = irq;
irq_attr.trigger = 1;
irq_attr.polarity = 0; /* Active high */
io_apic_set_pci_routing(NULL, irq, &irq_attr);
} else {
pr_warn("can not find interrupt %d in ioapic\n", irq);
ret = -EINVAL;
}
return ret;
}
/* Init code */
static int intel_scu_watchdog_init(void)
{
int ret = 0;
watchdog_device.normal_wd_action = SCU_COLD_RESET_ON_TIMEOUT;
watchdog_device.reboot_wd_action = SCU_COLD_RESET_ON_TIMEOUT;
watchdog_device.shutdown_wd_action = SCU_COLD_OFF_ON_TIMEOUT;
#ifdef CONFIG_DEBUG_FS
watchdog_device.panic_reboot_notifier = false;
#endif /* CONFIG_DEBUG_FS */
/* Initially, we are not in shutdown mode */
watchdog_device.shutdown_flag = false;
/* Since timeout can be set by MODULE_PARAM, need to reset pre_timeout */
pre_timeout = timeout-15;
/* Check timeouts boot parameter */
if (check_timeouts(pre_timeout, timeout)) {
pr_err("%s: Invalid timeouts\n", __func__);
return -EINVAL;
}
/* 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) {
pr_crit("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";
watchdog_device.miscdev.fops = &intel_scu_fops;
ret = misc_register(&watchdog_device.miscdev);
if (ret) {
pr_crit("Cannot register miscdev %d err =%d\n",
WATCHDOG_MINOR, ret);
goto error_reboot_notifier;
}
}
/* MSI #12 handler to dump registers */
handle_mrfl_dev_ioapic(EXT_TIMER0_MSI);
ret = request_irq((unsigned int)EXT_TIMER0_MSI,
watchdog_warning_interrupt,
IRQF_SHARED|IRQF_NO_SUSPEND, "watchdog",
&watchdog_device);
if (ret) {
pr_err("error requesting warning irq %d\n",
EXT_TIMER0_MSI);
pr_err("error value returned is %d\n", ret);
goto error_misc_register;
}
#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
init_timer(&softlock_timer);
#endif
if (disable_kernel_watchdog) {
pr_err("%s: Disable kernel watchdog\n", __func__);
/* Make sure timer is stopped */
ret = watchdog_stop();
if (ret != 0)
pr_debug("cant disable timer\n");
}
#ifdef CONFIG_DEBUG_FS
ret = create_debugfs_entries();
if (ret) {
pr_err("%s: Error creating debugfs entries\n", __func__);
goto error_debugfs_entry;
}
#endif
watchdog_device.started = false;
ret = create_watchdog_sysfs_files();
if (ret) {
pr_err("%s: Error creating debugfs entries\n", __func__);
goto error_sysfs_entry;
}
return ret;
error_sysfs_entry:
/* Nothing special to do */
#ifdef CONFIG_DEBUG_FS
error_debugfs_entry:
/* Remove entries done by create function */
#endif
error_misc_register:
misc_deregister(&watchdog_device.miscdev);
error_reboot_notifier:
unregister_reboot_notifier(&watchdog_device.reboot_notifier);
error_stop_timer:
watchdog_stop();
return ret;
}
static void intel_scu_watchdog_exit(void)
{
int ret = 0;
remove_watchdog_sysfs_files();
#ifdef CONFIG_DEBUG_FS
remove_debugfs_entries();
#endif
#ifdef CONFIG_INTEL_SCU_SOFT_LOCKUP
del_timer_sync(&softlock_timer);
#endif
ret = watchdog_stop();
if (ret != 0)
pr_err("cant disable timer\n");
misc_deregister(&watchdog_device.miscdev);
unregister_reboot_notifier(&watchdog_device.reboot_notifier);
}
static int watchdog_rpmsg_probe(struct rpmsg_channel *rpdev)
{
int ret = 0;
if (rpdev == NULL) {
pr_err("rpmsg channel not created\n");
ret = -ENODEV;
goto out;
}
dev_info(&rpdev->dev, "Probed watchdog rpmsg device\n");
/* Allocate rpmsg instance for watchdog*/
ret = alloc_rpmsg_instance(rpdev, &watchdog_instance);
if (!watchdog_instance) {
dev_err(&rpdev->dev, "kzalloc watchdog instance failed\n");
goto out;
}
/* Initialize rpmsg instance */
init_rpmsg_instance(watchdog_instance);
/* Init scu watchdog */
ret = intel_scu_watchdog_init();
if (ret)
free_rpmsg_instance(rpdev, &watchdog_instance);
out:
return ret;
}
static void watchdog_rpmsg_remove(struct rpmsg_channel *rpdev)
{
intel_scu_watchdog_exit();
free_rpmsg_instance(rpdev, &watchdog_instance);
dev_info(&rpdev->dev, "Removed watchdog rpmsg device\n");
}
static void watchdog_rpmsg_cb(struct rpmsg_channel *rpdev, void *data,
int len, void *priv, u32 src)
{
dev_warn(&rpdev->dev, "unexpected, message\n");
print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
data, len, true);
}
static struct rpmsg_device_id watchdog_rpmsg_id_table[] = {
{ .name = "rpmsg_watchdog" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, watchdog_rpmsg_id_table);
static int watchdog_rpmsg_suspend(struct device *dev)
{
int ret;
pr_debug("%s\n", __func__);
ret = watchdog_stop();
if (ret)
pr_err("cannot stop the watchdog\n");
return 0;
}
static int watchdog_rpmsg_resume(struct device *dev)
{
int ret;
pr_debug("%s\n", __func__);
ret = watchdog_config_and_start(timeout, pre_timeout);
if (ret)
pr_err("cannot start the watchdog\n");
return 0;
}
static const struct dev_pm_ops wd_pm_ops = {
.suspend = watchdog_rpmsg_suspend,
.resume = watchdog_rpmsg_resume,
};
static struct rpmsg_driver watchdog_rpmsg = {
.id_table = watchdog_rpmsg_id_table,
.probe = watchdog_rpmsg_probe,
.callback = watchdog_rpmsg_cb,
.remove = watchdog_rpmsg_remove,
.drv = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.pm = &wd_pm_ops,
},
};
static int __init watchdog_rpmsg_init(void)
{
if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_TANGIER)
return register_rpmsg_driver(&watchdog_rpmsg);
else {
pr_err("%s: watchdog driver: bad platform\n", __func__);
return -ENODEV;
}
}
#ifdef MODULE
module_init(watchdog_rpmsg_init);
#else
rootfs_initcall(watchdog_rpmsg_init);
#endif
static void __exit watchdog_rpmsg_exit(void)
{
return unregister_rpmsg_driver(&watchdog_rpmsg);
}
module_exit(watchdog_rpmsg_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);