| // 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; | 
 | } |