217 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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_core.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 += sysfs_emit_at(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 = sysfs_emit(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 (!watchdog_have_pretimeout(wdd))
 | |
| 		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 (!watchdog_have_pretimeout(wdd))
 | |
| 		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);
 | |
| }
 |