Merge branch 'mailbox-for-next' of git://git.linaro.org/landing-teams/working/fujitsu...
[cascardo/linux.git] / drivers / staging / greybus / svc_watchdog.c
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
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
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
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
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
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
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
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
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 }