198 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * SVC Greybus "watchdog" driver.
 | |
|  *
 | |
|  * Copyright 2016 Google Inc.
 | |
|  */
 | |
| 
 | |
| #include <linux/delay.h>
 | |
| #include <linux/suspend.h>
 | |
| #include <linux/workqueue.h>
 | |
| #include <linux/greybus.h>
 | |
| 
 | |
| #define SVC_WATCHDOG_PERIOD	(2 * HZ)
 | |
| 
 | |
| struct gb_svc_watchdog {
 | |
| 	struct delayed_work	work;
 | |
| 	struct gb_svc		*svc;
 | |
| 	bool			enabled;
 | |
| 	struct notifier_block pm_notifier;
 | |
| };
 | |
| 
 | |
| static struct delayed_work reset_work;
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| static void greybus_reset(struct work_struct *work)
 | |
| {
 | |
| 	static char const start_path[] = "/system/bin/start";
 | |
| 	static char *envp[] = {
 | |
| 		"HOME=/",
 | |
| 		"PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
 | |
| 		NULL,
 | |
| 	};
 | |
| 	static char *argv[] = {
 | |
| 		(char *)start_path,
 | |
| 		"unipro_reset",
 | |
| 		NULL,
 | |
| 	};
 | |
| 
 | |
| 	pr_err("svc_watchdog: calling \"%s %s\" to reset greybus network!\n",
 | |
| 	       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);
 | |
| 
 | |
| 		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");
 | |
| 
 | |
| 			INIT_DELAYED_WORK(&reset_work, greybus_reset);
 | |
| 			schedule_delayed_work(&reset_work, HZ / 2);
 | |
| 
 | |
| 			/*
 | |
| 			 * Disable ourselves, we don't want to trip again unless
 | |
| 			 * userspace wants us to.
 | |
| 			 */
 | |
| 			watchdog->enabled = false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* resubmit our work to happen again, if we are still "alive" */
 | |
| 	if (watchdog->enabled)
 | |
| 		schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
 | |
| }
 | |
| 
 | |
| int gb_svc_watchdog_create(struct gb_svc *svc)
 | |
| {
 | |
| 	struct gb_svc_watchdog *watchdog;
 | |
| 	int retval;
 | |
| 
 | |
| 	if (svc->watchdog)
 | |
| 		return 0;
 | |
| 
 | |
| 	watchdog = kmalloc(sizeof(*watchdog), GFP_KERNEL);
 | |
| 	if (!watchdog)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	watchdog->enabled = false;
 | |
| 	watchdog->svc = svc;
 | |
| 	INIT_DELAYED_WORK(&watchdog->work, do_work);
 | |
| 	svc->watchdog = watchdog;
 | |
| 
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| void gb_svc_watchdog_destroy(struct gb_svc *svc)
 | |
| {
 | |
| 	struct gb_svc_watchdog *watchdog = svc->watchdog;
 | |
| 
 | |
| 	if (!watchdog)
 | |
| 		return;
 | |
| 
 | |
| 	unregister_pm_notifier(&watchdog->pm_notifier);
 | |
| 	gb_svc_watchdog_disable(svc);
 | |
| 	svc->watchdog = NULL;
 | |
| 	kfree(watchdog);
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 	schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
 | |
| 	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;
 | |
| }
 |