blob: a4eb4af8b2f45eab8a01c936882941b23ea09779 [file] [log] [blame]
/*
* Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device
* for Intel part #(s):
* - AF82MP20 PCH
*
* Copyright (C) 2009-2010 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/compiler.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/notifier.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/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/atomic.h>
#include <linux/wakelock.h>
#include <linux/rpmsg.h>
#include <linux/intel_mid_pm.h>
#include <linux/nmi.h>
#include <asm/irq.h>
#include <asm/intel_scu_ipc.h>
#include <asm/intel_scu_ipcutil.h>
#include <asm/apb_timer.h>
#include <asm/intel_mid_rpmsg.h>
#include <asm/intel-mid.h>
#include "intel_scu_watchdog.h"
/* 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 IPC_SET_SUB_COLDOFF 0x03
#define IPC_SET_SUB_COLDRESET 0x04
#define IPC_SET_SUB_COLDBOOT 0x05
#define IPC_SET_SUB_DONOTHING 0x06
#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"
#ifdef CONFIG_DEBUG_FS
#define SECURITY_WATCHDOG_ADDR 0xFF222230
#define STRING_NONE "NONE"
#endif
#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;
static int reset_type_to_string(int reset_type, char *string);
static int string_to_reset_type(const char *string, int *reset_type);
/* 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;
/* 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;
#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;
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");
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");
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;
#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 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;
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 */
static int check_timeouts(int pre_time, int timer_time, int timeout_time)
{
if (timer_time+pre_time < timeout_time)
return 0;
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[3];
int ret = 0;
u32 freq = watchdog_device.timer7_tbl_ptr->freq_hz;
ipc_wbuf[0] = timer_threshold * freq;
ipc_wbuf[1] = warning_pretimeout * freq;
ipc_wbuf[2] = (reset_timeout - timer_threshold - warning_pretimeout)
* freq;
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 = rpmsg_send_command(watchdog_instance,
IPC_SET_WATCHDOG_TIMER,
IPC_SET_SUB_LOAD_THRES,
(u8 *) ipc_wbuf, NULL, 12, 0);
if (ret)
pr_crit(PFX "Error Setting SCU Watchdog Timer: %x\n", ret);
return ret;
}
/* Provisioning function for future enhancement : allow to fine tune timing
according to watchdog action settings */
static int watchdog_set_appropriate_timeouts(void)
{
pr_debug(PFX "Setting shutdown timeouts\n");
return watchdog_set_timeouts(timer_timeout, pre_timeout, timeout);
}
/* Keep alive */
static int watchdog_keepalive(void)
{
int ret;
pr_err(PFX "%s\n", __func__);
if (unlikely(!kicking_active)) {
/* Close our eyes */
pr_err(PFX "Transparent kicking\n");
return 0;
}
/* Really kick it */
ret = rpmsg_send_command(watchdog_instance,
IPC_SET_WATCHDOG_TIMER,
IPC_SET_SUB_KEEPALIVE,
NULL, NULL, 0, 0);
if (ret)
pr_err(PFX "Error sending keepalive ipc: %x\n", ret);
return ret;
}
/* stops the timer */
static int intel_scu_stop(void)
{
int ret;
pr_crit(PFX "%s\n", __func__);
ret = rpmsg_send_command(watchdog_instance,
IPC_SET_WATCHDOG_TIMER,
IPC_SET_SUB_DISABLE,
NULL, NULL, 0, 0);
if (ret) {
pr_crit(PFX "Error sending disable ipc: %x\n", ret);
goto err;
}
watchdog_device.started = false;
err:
return ret;
}
/* tasklet */
static void watchdog_interrupt_tasklet_body(unsigned long data)
{
int ret;
pr_warn(PFX "interrupt tasklet body start\n");
if (disable_kernel_watchdog) {
/* disable the timer */
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 CONFIG_INTEL_SCU_SOFT_LOCKUP
{
int i;
/*start timer for softlock detection */
beattime = jiffies;
for_each_possible_cpu(i) {
heartbeats[i] = kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM];
}
mod_timer(&softlock_timer, jiffies + SOFT_LOCK_TIME * HZ);
}
#endif
/* 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)
{
if (watchdog_device.started) {
pr_warn(PFX "Expected SW WDT warning irq received\n");
} else {
/* Unexpected, but we'd better to handle it anyway */
/* and so try to avoid a ColdReset */
pr_warn(PFX "Unexpected SW WDT warning irq received\n");
}
tasklet_schedule(&watchdog_device.interrupt_tasklet);
return IRQ_HANDLED;
}
/* warning interrupt handler */
static irqreturn_t watchdog_warning_interrupt(int irq, void *dev_id)
{
pr_warn("[SHTDWN] %s, WATCHDOG TIMEOUT!\n", __func__);
/* Let's reset the platform after dumping some data */
trigger_all_cpu_backtrace();
/* let the watchdog expire to reset the platform */
set_reboot_force(REBOOT_FORCE_ON);
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_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;
}
/* 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(PFX "watchdog device is busy\n");
return -EBUSY;
}
/* Check for reopen of driver. Reopens are not allowed */
if (watchdog_device.driver_closed) {
pr_err(PFX "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(PFX "intel_scu_release, without open\n");
return -ENOTTY;
}
if (!watchdog_device.started) {
/* Just close, since timer has not been started */
pr_err(PFX "Closed, without starting timer\n");
return 0;
}
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 */
watchdog_keepalive();
/* 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");
return 0;
}
/* 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.shutdown_flag == true)
/* do nothing if we are shutting down */
return len;
if (watchdog_device.started) {
/* Watchdog already started, keep it alive */
watchdog_keepalive();
wake_unlock(&watchdog_wake_lock);
} else {
/* Start watchdog with timer value set by init */
watchdog_config_and_start(timeout, pre_timeout);
}
return len;
}
/* Read */
static ssize_t intel_scu_read(struct file *file, char __user *user_data,
size_t len, loff_t *user_ppos)
{
int ret;
const 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 */
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 */
ret = copy_to_user(user_data, (const void *)buf, 1);
if (ret)
return -EFAULT;
return 1;
}
/* Poll */
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;
return mask;
}
/* 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;
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_warn(PFX "%s: KeepAlive ioctl\n", __func__);
if (!watchdog_device.started)
return -EINVAL;
watchdog_keepalive();
return 0;
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;
timer_timeout = val;
return 0;
case WDIOC_SETPRETIMEOUT:
pr_warn(PFX "%s: SetPreTimeout ioctl\n", __func__);
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(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(timer_timeout,
pre_timeout, timeout)) {
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;
}
}
static int watchdog_set_reset_type(int reset_type)
{
int ret;
ret = rpmsg_send_command(watchdog_instance,
IPC_SET_WATCHDOG_TIMER,
reset_type,
NULL, NULL, 0, 0);
if (ret) {
pr_crit(PFX "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(PFX "Reboot notifier\n");
if (watchdog_set_appropriate_timeouts())
pr_crit(PFX "reboot notifier cant set time\n");
switch (code) {
case SYS_RESTART:
watchdog_device.reboot_flag = true;
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(PFX "%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) {
/* let the watchdog expire to reset the platform */
set_reboot_force(REBOOT_FORCE_ON);
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(PFX "%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 write_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(PFX "cannot open secwd's debugfile\n");
ret = -ENODEV;
goto error;
}
value = readl(ptr); /* trigger */
pr_err(PFX "%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 = nonseekable_open,
.write = write_security,
.llseek = no_llseek,
};
static int kwd_trigger_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
pr_debug("kwd_trigger_write\n");
/* let the watchdog expire to reset the platform */
set_reboot_force(REBOOT_FORCE_ON);
BUG();
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(PFX "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(PFX "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(PFX "%s: copy to user failed\n", __func__);
return -EINVAL;
}
pr_debug(PFX "writing reset_type of %s\n", str);
ret = string_to_reset_type(str, &reset_type);
if (ret) {
pr_err(PFX "Invalid value\n");
return -EINVAL;
}
ret = watchdog_set_reset_type(reset_type);
if (ret) {
pr_err(PFX "%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(PFX "%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(PFX "%s: Error, cannot create main dir\n", __func__);
goto error;
}
/* /sys/kernel/debug/watchdog/sc_watchdog */
dev->dfs_secwd = debugfs_create_dir("sc_watchdog", dev->dfs_wd);
if (!dev->dfs_secwd) {
pr_err(PFX "%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_IWUSR | S_IWGRP,
dev->dfs_secwd, NULL,
&security_watchdog_fops);
if (!dev->dfs_secwd_trigger) {
pr_err(PFX "%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(PFX "%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(PFX "%s: Error, cannot create kwd trigger file\n",
__func__);
goto error;
}
/* /sys/kernel/debug/watchdog/kernel_watchdog/reset_type */
dev->dfs_kwd_reset_type = 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(PFX "%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(PFX "%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,
.read = intel_scu_read,
.unlocked_ioctl = intel_scu_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = intel_scu_ioctl,
#endif
.open = intel_scu_open,
.poll = intel_scu_poll,
.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 = intel_scu_stop();
if (ret)
pr_err(PFX "cannot disable the timer\n");
} else {
ret = watchdog_config_and_start(timeout, pre_timeout);
if (ret)
return -EINVAL;
}
} else {
pr_err(PFX "got invalid value\n");
return -EINVAL;
}
return size;
}
static ssize_t disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_debug(PFX "%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(PFX "%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(PFX "%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 IPC_SET_SUB_COLDBOOT:
strcpy(string, STRING_COLD_BOOT);
break;
case IPC_SET_SUB_COLDRESET:
strcpy(string, STRING_COLD_RESET);
break;
case IPC_SET_SUB_COLDOFF:
strcpy(string, STRING_COLD_OFF);
break;
#ifdef CONFIG_DEBUG_FS
case IPC_SET_SUB_DONOTHING:
/* 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 = IPC_SET_SUB_COLDRESET;
return 0;
}
if (strncmp(string, STRING_COLD_BOOT,
sizeof(STRING_COLD_BOOT) - 1) == 0) {
*reset_type = IPC_SET_SUB_COLDBOOT;
return 0;
}
if (strncmp(string, STRING_COLD_OFF,
sizeof(STRING_COLD_OFF) - 1) == 0) {
*reset_type = IPC_SET_SUB_COLDOFF;
return 0;
}
/* 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(PFX "%s\n", __func__);
/* reprogram timeouts. if error : continue */
ret = watchdog_set_appropriate_timeouts();
if (ret)
pr_err(PFX "%s: could not set timeouts\n", __func__);
/* restore reset type */
watchdog_set_reset_type(watchdog_device.reboot_wd_action);
if (ret) {
pr_err(PFX "%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(PFX "%s\n", __func__);
/* reprogram timeouts. if error : continue */
ret = watchdog_set_appropriate_timeouts();
if (ret)
pr_err(PFX "%s: could not set timeouts\n", __func__);
/* restore reset type */
watchdog_set_reset_type(watchdog_device.shutdown_wd_action);
if (ret) {
pr_err(PFX "%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;
}
void decode_reboot_force(int watchdog_action)
{
switch (watchdog_action) {
case IPC_SET_SUB_COLDRESET:
set_reboot_force(REBOOT_FORCE_COLD_RESET);
break;
case IPC_SET_SUB_COLDBOOT:
set_reboot_force(REBOOT_FORCE_COLD_BOOT);
break;
case IPC_SET_SUB_COLDOFF:
set_reboot_force(REBOOT_FORCE_OFF);
break;
case IPC_SET_SUB_DONOTHING:
set_reboot_force(REBOOT_FORCE_ON);
break;
default:
set_reboot_force(REBOOT_FORCE_COLD_RESET);
}
}
/* This is the callback function launched when kernel panic() function */
/* is executed. */
static int watchdog_panic_handler(struct notifier_block *this,
unsigned long event,
void *unused)
{
/* don't allow kicking anymore */
kicking_active = false;
if (disable_kernel_watchdog == true) {
set_reboot_force(REBOOT_FORCE_ON);
return NOTIFY_OK;
}
if (get_reboot_force() == REBOOT_FORCE_ON)
return NOTIFY_OK;
if (watchdog_device.reboot_flag == true)
decode_reboot_force(watchdog_device.reboot_wd_action);
else if (watchdog_device.shutdown_flag == true)
decode_reboot_force(watchdog_device.shutdown_wd_action);
else
decode_reboot_force(watchdog_device.normal_wd_action);
return NOTIFY_OK;
}
static struct notifier_block watchdog_panic_notifier = {
.notifier_call = watchdog_panic_handler,
.next = NULL,
.priority = 150 /* priority: INT_MAX >= x >= 0 */
};
/* Init code */
static int intel_scu_watchdog_init(void)
{
int ret = 0;
watchdog_device.normal_wd_action = IPC_SET_SUB_COLDRESET;
watchdog_device.reboot_wd_action = IPC_SET_SUB_COLDRESET;
watchdog_device.shutdown_wd_action = IPC_SET_SUB_COLDOFF;
#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;
watchdog_device.reboot_flag = false;
/* Check timeouts boot parameter */
if (check_timeouts(timer_timeout, pre_timeout, timeout)) {
pr_err(PFX "%s: Invalid timeouts\n", __func__);
return -EINVAL;
}
/* 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;
}
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.timer7_tbl_ptr->irq == 0) {
pr_debug(PFX "Watchdog timer: timer %d invalid irq\n",
sfi_mtimer_num);
return -ENODEV;
}
/* 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;
}
/* 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(PFX "cannot register reboot notifier %d\n", ret);
goto error_stop_timer;
}
ret = atomic_notifier_chain_register(&panic_notifier_list,
&watchdog_panic_notifier);
if (ret) {
pr_crit("cannot register panic notifier %d\n", ret);
goto error_reboot_notifier;
}
/* 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(PFX "Cannot register miscdev %d err =%d\n",
WATCHDOG_MINOR, ret);
goto error_panic_notifier;
}
}
wake_lock_init(&watchdog_wake_lock, WAKE_LOCK_SUSPEND,
"intel_scu_watchdog");
/* 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);
if (ret) {
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 CONFIG_INTEL_SCU_SOFT_LOCKUP
init_timer(&softlock_timer);
#endif
if (disable_kernel_watchdog) {
pr_err(PFX "%s: Disable kernel watchdog\n", __func__);
/* Make sure timer is stopped */
ret = intel_scu_stop();
if (ret != 0)
pr_debug(PFX "cant disable timer\n");
}
#ifdef CONFIG_DEBUG_FS
ret = create_debugfs_entries();
if (ret) {
pr_err(PFX "%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(PFX "%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_request_irq:
free_irq(watchdog_device.timer7_tbl_ptr->irq, NULL);
error_misc_register:
misc_deregister(&watchdog_device.miscdev);
error_panic_notifier:
atomic_notifier_chain_unregister(&panic_notifier_list,
&watchdog_panic_notifier);
error_reboot_notifier:
unregister_reboot_notifier(&watchdog_device.reboot_notifier);
error_stop_timer:
intel_scu_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 = intel_scu_stop();
if (ret != 0)
pr_err(PFX "cant disable timer\n");
misc_deregister(&watchdog_device.miscdev);
unregister_reboot_notifier(&watchdog_device.reboot_notifier);
atomic_notifier_chain_unregister(&panic_notifier_list,
&watchdog_panic_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 struct rpmsg_driver watchdog_rpmsg = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = watchdog_rpmsg_id_table,
.probe = watchdog_rpmsg_probe,
.callback = watchdog_rpmsg_cb,
.remove = watchdog_rpmsg_remove,
};
static int __init watchdog_rpmsg_init(void)
{
if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL ||
intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_CLOVERVIEW)
return register_rpmsg_driver(&watchdog_rpmsg);
else {
pr_err(PFX "%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);