2017-11-07 14:58:41 +01:00
// SPDX-License-Identifier: GPL-2.0
2016-01-20 22:51:49 -08:00
/*
* SVC Greybus " watchdog " driver .
*
* Copyright 2016 Google Inc .
*/
# include <linux/delay.h>
2016-04-16 01:15:16 +05:30
# include <linux/suspend.h>
2016-01-20 22:51:49 -08:00
# include <linux/workqueue.h>
2019-08-25 07:54:27 +02:00
# include <linux/greybus.h>
2016-01-20 22:51:49 -08:00
2016-12-22 16:22:02 +01:00
# define SVC_WATCHDOG_PERIOD (2 * HZ)
2016-01-20 22:51:49 -08:00
struct gb_svc_watchdog {
struct delayed_work work ;
struct gb_svc * svc ;
2016-01-26 15:17:08 -08:00
bool enabled ;
2016-04-16 01:15:16 +05:30
struct notifier_block pm_notifier ;
2016-01-20 22:51:49 -08:00
} ;
static struct delayed_work reset_work ;
2016-04-16 01:15:16 +05:30
static int svc_watchdog_pm_notifier ( struct notifier_block * notifier ,
unsigned long pm_event , void * unused )
{
struct gb_svc_watchdog * watchdog =
container_of ( notifier , struct gb_svc_watchdog , pm_notifier ) ;
switch ( pm_event ) {
case PM_SUSPEND_PREPARE :
gb_svc_watchdog_disable ( watchdog - > svc ) ;
break ;
case PM_POST_SUSPEND :
gb_svc_watchdog_enable ( watchdog - > svc ) ;
break ;
default :
break ;
}
return NOTIFY_DONE ;
}
2016-01-20 22:51:49 -08:00
static void greybus_reset ( struct work_struct * work )
{
2016-12-11 18:00:43 +01:00
static char const start_path [ ] = " /system/bin/start " ;
2016-01-20 22:51:49 -08:00
static char * envp [ ] = {
" HOME=/ " ,
" PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin " ,
NULL ,
} ;
static char * argv [ ] = {
2016-12-11 18:00:43 +01:00
( char * ) start_path ,
2016-01-20 22:51:49 -08:00
" unipro_reset " ,
NULL ,
} ;
2016-12-22 16:22:03 +01:00
pr_err ( " svc_watchdog: calling \" %s %s \" to reset greybus network! \n " ,
2016-01-20 22:51:49 -08:00
argv [ 0 ] , argv [ 1 ] ) ;
call_usermodehelper ( start_path , argv , envp , UMH_WAIT_EXEC ) ;
}
static void do_work ( struct work_struct * work )
{
struct gb_svc_watchdog * watchdog ;
struct gb_svc * svc ;
int retval ;
watchdog = container_of ( work , struct gb_svc_watchdog , work . work ) ;
svc = watchdog - > svc ;
dev_dbg ( & svc - > dev , " %s: ping. \n " , __func__ ) ;
retval = gb_svc_ping ( svc ) ;
if ( retval ) {
/*
* Something went really wrong , let ' s warn userspace and then
* pull the plug and reset the whole greybus network .
* We need to do this outside of this workqueue as we will be
* tearing down the svc device itself . So queue up
* yet - another - callback to do that .
*/
dev_err ( & svc - > dev ,
" SVC ping has returned %d, something is wrong!!! \n " ,
retval ) ;
2016-07-26 16:27:28 -07:00
if ( svc - > action = = GB_SVC_WATCHDOG_BITE_PANIC_KERNEL ) {
panic ( " SVC is not responding \n " ) ;
} else if ( svc - > action = = GB_SVC_WATCHDOG_BITE_RESET_UNIPRO ) {
dev_err ( & svc - > dev , " Resetting the greybus network, watch out!!! \n " ) ;
2016-01-26 15:17:08 -08:00
2016-07-26 16:27:28 -07:00
INIT_DELAYED_WORK ( & reset_work , greybus_reset ) ;
2016-08-01 20:51:38 -07:00
schedule_delayed_work ( & reset_work , HZ / 2 ) ;
2016-07-26 16:27:28 -07:00
/*
* Disable ourselves , we don ' t want to trip again unless
* userspace wants us to .
*/
watchdog - > enabled = false ;
}
2016-01-20 22:51:49 -08:00
}
/* resubmit our work to happen again, if we are still "alive" */
2016-01-26 15:17:08 -08:00
if ( watchdog - > enabled )
2016-08-01 20:51:38 -07:00
schedule_delayed_work ( & watchdog - > work , SVC_WATCHDOG_PERIOD ) ;
2016-01-20 22:51:49 -08:00
}
int gb_svc_watchdog_create ( struct gb_svc * svc )
{
struct gb_svc_watchdog * watchdog ;
2016-04-16 01:15:16 +05:30
int retval ;
2016-01-20 22:51:49 -08:00
if ( svc - > watchdog )
return 0 ;
watchdog = kmalloc ( sizeof ( * watchdog ) , GFP_KERNEL ) ;
if ( ! watchdog )
return - ENOMEM ;
2016-01-26 15:17:08 -08:00
watchdog - > enabled = false ;
2016-01-20 22:51:49 -08:00
watchdog - > svc = svc ;
INIT_DELAYED_WORK ( & watchdog - > work , do_work ) ;
svc - > watchdog = watchdog ;
2016-04-16 01:15:16 +05:30
watchdog - > pm_notifier . notifier_call = svc_watchdog_pm_notifier ;
retval = register_pm_notifier ( & watchdog - > pm_notifier ) ;
if ( retval ) {
dev_err ( & svc - > dev , " error registering pm notifier(%d) \n " ,
retval ) ;
goto svc_watchdog_create_err ;
}
retval = gb_svc_watchdog_enable ( svc ) ;
if ( retval ) {
dev_err ( & svc - > dev , " error enabling watchdog (%d) \n " , retval ) ;
unregister_pm_notifier ( & watchdog - > pm_notifier ) ;
goto svc_watchdog_create_err ;
}
return retval ;
svc_watchdog_create_err :
svc - > watchdog = NULL ;
kfree ( watchdog ) ;
return retval ;
2016-01-20 22:51:49 -08:00
}
void gb_svc_watchdog_destroy ( struct gb_svc * svc )
{
struct gb_svc_watchdog * watchdog = svc - > watchdog ;
if ( ! watchdog )
return ;
2016-04-16 01:15:16 +05:30
unregister_pm_notifier ( & watchdog - > pm_notifier ) ;
2016-01-26 15:17:08 -08:00
gb_svc_watchdog_disable ( svc ) ;
2016-01-20 22:51:49 -08:00
svc - > watchdog = NULL ;
kfree ( watchdog ) ;
}
2016-01-26 15:17:08 -08:00
bool gb_svc_watchdog_enabled ( struct gb_svc * svc )
{
if ( ! svc | | ! svc - > watchdog )
return false ;
return svc - > watchdog - > enabled ;
}
int gb_svc_watchdog_enable ( struct gb_svc * svc )
{
struct gb_svc_watchdog * watchdog ;
if ( ! svc - > watchdog )
return - ENODEV ;
watchdog = svc - > watchdog ;
if ( watchdog - > enabled )
return 0 ;
watchdog - > enabled = true ;
2016-08-01 20:51:38 -07:00
schedule_delayed_work ( & watchdog - > work , SVC_WATCHDOG_PERIOD ) ;
2016-01-26 15:17:08 -08:00
return 0 ;
}
int gb_svc_watchdog_disable ( struct gb_svc * svc )
{
struct gb_svc_watchdog * watchdog ;
if ( ! svc - > watchdog )
return - ENODEV ;
watchdog = svc - > watchdog ;
if ( ! watchdog - > enabled )
return 0 ;
watchdog - > enabled = false ;
cancel_delayed_work_sync ( & watchdog - > work ) ;
return 0 ;
}