// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2015-2016 Mentor Graphics */ #include #include #include #include #include #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 (!(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); }