2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-10-07 15:39:54 +03:00
/*
* Copyright ( C ) 2015 - 2016 Mentor Graphics
*/
# include <linux/list.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
2016-10-07 15:37:00 +03:00
# include <linux/string.h>
2016-10-07 15:39:54 +03: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 15:37:00 +03: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 15:39:57 +03: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 15:39:54 +03: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 15:37:00 +03: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 15:39:54 +03: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 15:37:00 +03: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 15:39:54 +03:00
2016-10-07 15:39:56 +03:00
if ( ! strncmp ( gov - > name , WATCHDOG_PRETIMEOUT_DEFAULT_GOV ,
WATCHDOG_GOV_NAME_MAXLEN ) ) {
2016-10-07 15:39:54 +03: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 15:37:00 +03:00
mutex_unlock ( & governor_lock ) ;
2016-10-07 15:39:54 +03:00
return 0 ;
}
EXPORT_SYMBOL ( watchdog_register_governor ) ;
void watchdog_unregister_governor ( struct watchdog_governor * gov )
{
struct watchdog_pretimeout * p ;
2016-10-07 15:37:00 +03: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 15:39:54 +03: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 15:37:00 +03:00
mutex_unlock ( & governor_lock ) ;
2016-10-07 15:39:54 +03: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 ) ;
}