• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * SVC Greybus "watchdog" driver.
3  *
4  * Copyright 2016 Google Inc.
5  *
6  * Released under the GPLv2 only.
7  */
8 
9 #include <linux/delay.h>
10 #include <linux/suspend.h>
11 #include <linux/workqueue.h>
12 #include "greybus.h"
13 
14 #define SVC_WATCHDOG_PERIOD	(2*HZ)
15 
16 struct gb_svc_watchdog {
17 	struct delayed_work	work;
18 	struct gb_svc		*svc;
19 	bool			enabled;
20 	struct notifier_block pm_notifier;
21 };
22 
23 static struct delayed_work reset_work;
24 
svc_watchdog_pm_notifier(struct notifier_block * notifier,unsigned long pm_event,void * unused)25 static int svc_watchdog_pm_notifier(struct notifier_block *notifier,
26 				    unsigned long pm_event, void *unused)
27 {
28 	struct gb_svc_watchdog *watchdog =
29 		container_of(notifier, struct gb_svc_watchdog, pm_notifier);
30 
31 	switch (pm_event) {
32 	case PM_SUSPEND_PREPARE:
33 		gb_svc_watchdog_disable(watchdog->svc);
34 		break;
35 	case PM_POST_SUSPEND:
36 		gb_svc_watchdog_enable(watchdog->svc);
37 		break;
38 	default:
39 		break;
40 	}
41 
42 	return NOTIFY_DONE;
43 }
44 
greybus_reset(struct work_struct * work)45 static void greybus_reset(struct work_struct *work)
46 {
47 	static char start_path[256] = "/system/bin/start";
48 	static char *envp[] = {
49 		"HOME=/",
50 		"PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
51 		NULL,
52 	};
53 	static char *argv[] = {
54 		start_path,
55 		"unipro_reset",
56 		NULL,
57 	};
58 
59 	printk(KERN_ERR "svc_watchdog: calling \"%s %s\" to reset greybus network!\n",
60 	       argv[0], argv[1]);
61 	call_usermodehelper(start_path, argv, envp, UMH_WAIT_EXEC);
62 }
63 
do_work(struct work_struct * work)64 static void do_work(struct work_struct *work)
65 {
66 	struct gb_svc_watchdog *watchdog;
67 	struct gb_svc *svc;
68 	int retval;
69 
70 	watchdog = container_of(work, struct gb_svc_watchdog, work.work);
71 	svc = watchdog->svc;
72 
73 	dev_dbg(&svc->dev, "%s: ping.\n", __func__);
74 	retval = gb_svc_ping(svc);
75 	if (retval) {
76 		/*
77 		 * Something went really wrong, let's warn userspace and then
78 		 * pull the plug and reset the whole greybus network.
79 		 * We need to do this outside of this workqueue as we will be
80 		 * tearing down the svc device itself.  So queue up
81 		 * yet-another-callback to do that.
82 		 */
83 		dev_err(&svc->dev,
84 			"SVC ping has returned %d, something is wrong!!!\n",
85 			retval);
86 
87 		if (svc->action == GB_SVC_WATCHDOG_BITE_PANIC_KERNEL) {
88 			panic("SVC is not responding\n");
89 		} else if (svc->action == GB_SVC_WATCHDOG_BITE_RESET_UNIPRO) {
90 			dev_err(&svc->dev, "Resetting the greybus network, watch out!!!\n");
91 
92 			INIT_DELAYED_WORK(&reset_work, greybus_reset);
93 			schedule_delayed_work(&reset_work, HZ / 2);
94 
95 			/*
96 			 * Disable ourselves, we don't want to trip again unless
97 			 * userspace wants us to.
98 			 */
99 			watchdog->enabled = false;
100 		}
101 	}
102 
103 	/* resubmit our work to happen again, if we are still "alive" */
104 	if (watchdog->enabled)
105 		schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
106 }
107 
gb_svc_watchdog_create(struct gb_svc * svc)108 int gb_svc_watchdog_create(struct gb_svc *svc)
109 {
110 	struct gb_svc_watchdog *watchdog;
111 	int retval;
112 
113 	if (svc->watchdog)
114 		return 0;
115 
116 	watchdog = kmalloc(sizeof(*watchdog), GFP_KERNEL);
117 	if (!watchdog)
118 		return -ENOMEM;
119 
120 	watchdog->enabled = false;
121 	watchdog->svc = svc;
122 	INIT_DELAYED_WORK(&watchdog->work, do_work);
123 	svc->watchdog = watchdog;
124 
125 	watchdog->pm_notifier.notifier_call = svc_watchdog_pm_notifier;
126 	retval = register_pm_notifier(&watchdog->pm_notifier);
127 	if (retval) {
128 		dev_err(&svc->dev, "error registering pm notifier(%d)\n",
129 			retval);
130 		goto svc_watchdog_create_err;
131 	}
132 
133 	retval = gb_svc_watchdog_enable(svc);
134 	if (retval) {
135 		dev_err(&svc->dev, "error enabling watchdog (%d)\n", retval);
136 		unregister_pm_notifier(&watchdog->pm_notifier);
137 		goto svc_watchdog_create_err;
138 	}
139 	return retval;
140 
141 svc_watchdog_create_err:
142 	svc->watchdog = NULL;
143 	kfree(watchdog);
144 
145 	return retval;
146 }
147 
gb_svc_watchdog_destroy(struct gb_svc * svc)148 void gb_svc_watchdog_destroy(struct gb_svc *svc)
149 {
150 	struct gb_svc_watchdog *watchdog = svc->watchdog;
151 
152 	if (!watchdog)
153 		return;
154 
155 	unregister_pm_notifier(&watchdog->pm_notifier);
156 	gb_svc_watchdog_disable(svc);
157 	svc->watchdog = NULL;
158 	kfree(watchdog);
159 }
160 
gb_svc_watchdog_enabled(struct gb_svc * svc)161 bool gb_svc_watchdog_enabled(struct gb_svc *svc)
162 {
163 	if (!svc || !svc->watchdog)
164 		return false;
165 	return svc->watchdog->enabled;
166 }
167 
gb_svc_watchdog_enable(struct gb_svc * svc)168 int gb_svc_watchdog_enable(struct gb_svc *svc)
169 {
170 	struct gb_svc_watchdog *watchdog;
171 
172 	if (!svc->watchdog)
173 		return -ENODEV;
174 
175 	watchdog = svc->watchdog;
176 	if (watchdog->enabled)
177 		return 0;
178 
179 	watchdog->enabled = true;
180 	schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
181 	return 0;
182 }
183 
gb_svc_watchdog_disable(struct gb_svc * svc)184 int gb_svc_watchdog_disable(struct gb_svc *svc)
185 {
186 	struct gb_svc_watchdog *watchdog;
187 
188 	if (!svc->watchdog)
189 		return -ENODEV;
190 
191 	watchdog = svc->watchdog;
192 	if (!watchdog->enabled)
193 		return 0;
194 
195 	watchdog->enabled = false;
196 	cancel_delayed_work_sync(&watchdog->work);
197 	return 0;
198 }
199