greybus: svc: use EREMOTEIO for remote errors
[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                 dev_err(&svc->dev, "Resetting the greybus network, watch out!!!\n");
87
88                 INIT_DELAYED_WORK(&reset_work, greybus_reset);
89                 queue_delayed_work(system_wq, &reset_work, HZ/2);
90
91                 /*
92                  * Disable ourselves, we don't want to trip again unless
93                  * userspace wants us to.
94                  */
95                 watchdog->enabled = false;
96         }
97
98         /* resubmit our work to happen again, if we are still "alive" */
99         if (watchdog->enabled)
100                 queue_delayed_work(system_wq, &watchdog->work,
101                                    SVC_WATCHDOG_PERIOD);
102 }
103
104 int gb_svc_watchdog_create(struct gb_svc *svc)
105 {
106         struct gb_svc_watchdog *watchdog;
107         int retval;
108
109         if (svc->watchdog)
110                 return 0;
111
112         watchdog = kmalloc(sizeof(*watchdog), GFP_KERNEL);
113         if (!watchdog)
114                 return -ENOMEM;
115
116         watchdog->enabled = false;
117         watchdog->svc = svc;
118         INIT_DELAYED_WORK(&watchdog->work, do_work);
119         svc->watchdog = watchdog;
120
121         watchdog->pm_notifier.notifier_call = svc_watchdog_pm_notifier;
122         retval = register_pm_notifier(&watchdog->pm_notifier);
123         if (retval) {
124                 dev_err(&svc->dev, "error registering pm notifier(%d)\n",
125                         retval);
126                 goto svc_watchdog_create_err;
127         }
128
129         retval = gb_svc_watchdog_enable(svc);
130         if (retval) {
131                 dev_err(&svc->dev, "error enabling watchdog (%d)\n", retval);
132                 unregister_pm_notifier(&watchdog->pm_notifier);
133                 goto svc_watchdog_create_err;
134         }
135         return retval;
136
137 svc_watchdog_create_err:
138         svc->watchdog = NULL;
139         kfree(watchdog);
140
141         return retval;
142 }
143
144 void gb_svc_watchdog_destroy(struct gb_svc *svc)
145 {
146         struct gb_svc_watchdog *watchdog = svc->watchdog;
147
148         if (!watchdog)
149                 return;
150
151         unregister_pm_notifier(&watchdog->pm_notifier);
152         gb_svc_watchdog_disable(svc);
153         svc->watchdog = NULL;
154         kfree(watchdog);
155 }
156
157 bool gb_svc_watchdog_enabled(struct gb_svc *svc)
158 {
159         if (!svc || !svc->watchdog)
160                 return false;
161         return svc->watchdog->enabled;
162 }
163
164 int gb_svc_watchdog_enable(struct gb_svc *svc)
165 {
166         struct gb_svc_watchdog *watchdog;
167
168         if (!svc->watchdog)
169                 return -ENODEV;
170
171         watchdog = svc->watchdog;
172         if (watchdog->enabled)
173                 return 0;
174
175         watchdog->enabled = true;
176         queue_delayed_work(system_wq, &watchdog->work,
177                            SVC_WATCHDOG_PERIOD);
178         return 0;
179 }
180
181 int gb_svc_watchdog_disable(struct gb_svc *svc)
182 {
183         struct gb_svc_watchdog *watchdog;
184
185         if (!svc->watchdog)
186                 return -ENODEV;
187
188         watchdog = svc->watchdog;
189         if (!watchdog->enabled)
190                 return 0;
191
192         watchdog->enabled = false;
193         cancel_delayed_work_sync(&watchdog->work);
194         return 0;
195 }