| /* | 
 |  * Copyright (C) 2015-2016 Mentor Graphics | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  */ | 
 |  | 
 | #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); | 
 | } |