2019-05-27 14:55:01 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2016-10-07 20:39:54 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2015-2016 Mentor Graphics
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/spinlock.h>
|
2016-10-07 20:37:00 +08:00
|
|
|
#include <linux/string.h>
|
2016-10-07 20:39:54 +08:00
|
|
|
#include <linux/watchdog.h>
|
|
|
|
|
|
|
|
#include "watchdog_pretimeout.h"
|
|
|
|
|
|
|
|
/* Default watchdog pretimeout governor */
|
|
|
|
static struct watchdog_governor *default_gov;
|
|
|
|
|
|
|
|
/* The spinlock protects default_gov, wdd->gov and pretimeout_list */
|
|
|
|
static DEFINE_SPINLOCK(pretimeout_lock);
|
|
|
|
|
|
|
|
/* List of watchdog devices, which can generate a pretimeout event */
|
|
|
|
static LIST_HEAD(pretimeout_list);
|
|
|
|
|
|
|
|
struct watchdog_pretimeout {
|
|
|
|
struct watchdog_device *wdd;
|
|
|
|
struct list_head entry;
|
|
|
|
};
|
|
|
|
|
2016-10-07 20:37:00 +08:00
|
|
|
/* The mutex protects governor list and serializes external interfaces */
|
|
|
|
static DEFINE_MUTEX(governor_lock);
|
|
|
|
|
|
|
|
/* List of the registered watchdog pretimeout governors */
|
|
|
|
static LIST_HEAD(governor_list);
|
|
|
|
|
|
|
|
struct governor_priv {
|
|
|
|
struct watchdog_governor *gov;
|
|
|
|
struct list_head entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct governor_priv *find_governor_by_name(const char *gov_name)
|
|
|
|
{
|
|
|
|
struct governor_priv *priv;
|
|
|
|
|
|
|
|
list_for_each_entry(priv, &governor_list, entry)
|
|
|
|
if (sysfs_streq(gov_name, priv->gov->name))
|
|
|
|
return priv;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-10-07 20:39:57 +08:00
|
|
|
int watchdog_pretimeout_available_governors_get(char *buf)
|
|
|
|
{
|
|
|
|
struct governor_priv *priv;
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
mutex_lock(&governor_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(priv, &governor_list, entry)
|
|
|
|
count += sprintf(buf + count, "%s\n", priv->gov->name);
|
|
|
|
|
|
|
|
mutex_unlock(&governor_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2016-10-07 20:39:54 +08:00
|
|
|
int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
if (wdd->gov)
|
|
|
|
count = sprintf(buf, "%s\n", wdd->gov->name);
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2016-10-07 20:37:00 +08:00
|
|
|
int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
|
|
|
|
const char *buf)
|
|
|
|
{
|
|
|
|
struct governor_priv *priv;
|
|
|
|
|
|
|
|
mutex_lock(&governor_lock);
|
|
|
|
|
|
|
|
priv = find_governor_by_name(buf);
|
|
|
|
if (!priv) {
|
|
|
|
mutex_unlock(&governor_lock);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
wdd->gov = priv->gov;
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
|
|
|
|
|
|
|
mutex_unlock(&governor_lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-10-07 20:39:54 +08:00
|
|
|
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&pretimeout_lock, flags);
|
|
|
|
if (!wdd->gov) {
|
|
|
|
spin_unlock_irqrestore(&pretimeout_lock, flags);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wdd->gov->pretimeout(wdd);
|
|
|
|
spin_unlock_irqrestore(&pretimeout_lock, flags);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout);
|
|
|
|
|
|
|
|
int watchdog_register_governor(struct watchdog_governor *gov)
|
|
|
|
{
|
|
|
|
struct watchdog_pretimeout *p;
|
2016-10-07 20:37:00 +08:00
|
|
|
struct governor_priv *priv;
|
|
|
|
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
|
|
if (!priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
mutex_lock(&governor_lock);
|
|
|
|
|
|
|
|
if (find_governor_by_name(gov->name)) {
|
|
|
|
mutex_unlock(&governor_lock);
|
|
|
|
kfree(priv);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->gov = gov;
|
|
|
|
list_add(&priv->entry, &governor_list);
|
2016-10-07 20:39:54 +08:00
|
|
|
|
2016-10-07 20:39:56 +08:00
|
|
|
if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV,
|
|
|
|
WATCHDOG_GOV_NAME_MAXLEN)) {
|
2016-10-07 20:39:54 +08:00
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
default_gov = gov;
|
|
|
|
|
|
|
|
list_for_each_entry(p, &pretimeout_list, entry)
|
|
|
|
if (!p->wdd->gov)
|
|
|
|
p->wdd->gov = default_gov;
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
|
|
|
}
|
|
|
|
|
2016-10-07 20:37:00 +08:00
|
|
|
mutex_unlock(&governor_lock);
|
|
|
|
|
2016-10-07 20:39:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(watchdog_register_governor);
|
|
|
|
|
|
|
|
void watchdog_unregister_governor(struct watchdog_governor *gov)
|
|
|
|
{
|
|
|
|
struct watchdog_pretimeout *p;
|
2016-10-07 20:37:00 +08:00
|
|
|
struct governor_priv *priv, *t;
|
|
|
|
|
|
|
|
mutex_lock(&governor_lock);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(priv, t, &governor_list, entry) {
|
|
|
|
if (priv->gov == gov) {
|
|
|
|
list_del(&priv->entry);
|
|
|
|
kfree(priv);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-10-07 20:39:54 +08:00
|
|
|
|
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
list_for_each_entry(p, &pretimeout_list, entry)
|
|
|
|
if (p->wdd->gov == gov)
|
|
|
|
p->wdd->gov = default_gov;
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
2016-10-07 20:37:00 +08:00
|
|
|
|
|
|
|
mutex_unlock(&governor_lock);
|
2016-10-07 20:39:54 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(watchdog_unregister_governor);
|
|
|
|
|
|
|
|
int watchdog_register_pretimeout(struct watchdog_device *wdd)
|
|
|
|
{
|
|
|
|
struct watchdog_pretimeout *p;
|
|
|
|
|
|
|
|
if (!(wdd->info->options & WDIOF_PRETIMEOUT))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
|
|
|
if (!p)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
list_add(&p->entry, &pretimeout_list);
|
|
|
|
p->wdd = wdd;
|
|
|
|
wdd->gov = default_gov;
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
|
|
|
|
{
|
|
|
|
struct watchdog_pretimeout *p, *t;
|
|
|
|
|
|
|
|
if (!(wdd->info->options & WDIOF_PRETIMEOUT))
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_irq(&pretimeout_lock);
|
|
|
|
wdd->gov = NULL;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(p, t, &pretimeout_list, entry) {
|
|
|
|
if (p->wdd == wdd) {
|
|
|
|
list_del(&p->entry);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&pretimeout_lock);
|
|
|
|
|
|
|
|
kfree(p);
|
|
|
|
}
|