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