forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
4.5 KiB
215 lines
4.5 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2015-2016 Mentor Graphics |
|
*/ |
|
|
|
#include <linux/list.h> |
|
#include <linux/slab.h> |
|
#include <linux/spinlock.h> |
|
#include <linux/string.h> |
|
#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; |
|
}; |
|
|
|
/* 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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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; |
|
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); |
|
|
|
if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV, |
|
WATCHDOG_GOV_NAME_MAXLEN)) { |
|
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); |
|
} |
|
|
|
mutex_unlock(&governor_lock); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(watchdog_register_governor); |
|
|
|
void watchdog_unregister_governor(struct watchdog_governor *gov) |
|
{ |
|
struct watchdog_pretimeout *p; |
|
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; |
|
} |
|
} |
|
|
|
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); |
|
|
|
mutex_unlock(&governor_lock); |
|
} |
|
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); |
|
}
|
|
|