linux/drivers/greybus/svc_watchdog.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * SVC Greybus "watchdog" driver.
   4 *
   5 * Copyright 2016 Google Inc.
   6 */
   7
   8#include <linux/delay.h>
   9#include <linux/suspend.h>
  10#include <linux/workqueue.h>
  11#include <linux/greybus.h>
  12
  13#define SVC_WATCHDOG_PERIOD     (2 * HZ)
  14
  15struct gb_svc_watchdog {
  16        struct delayed_work     work;
  17        struct gb_svc           *svc;
  18        bool                    enabled;
  19        struct notifier_block pm_notifier;
  20};
  21
  22static struct delayed_work reset_work;
  23
  24static 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
  44static void greybus_reset(struct work_struct *work)
  45{
  46        static char const start_path[] = "/system/bin/start";
  47        static char *envp[] = {
  48                "HOME=/",
  49                "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
  50                NULL,
  51        };
  52        static char *argv[] = {
  53                (char *)start_path,
  54                "unipro_reset",
  55                NULL,
  56        };
  57
  58        pr_err("svc_watchdog: calling \"%s %s\" to reset greybus network!\n",
  59               argv[0], argv[1]);
  60        call_usermodehelper(start_path, argv, envp, UMH_WAIT_EXEC);
  61}
  62
  63static 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);
  85
  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");
  90
  91                        INIT_DELAYED_WORK(&reset_work, greybus_reset);
  92                        schedule_delayed_work(&reset_work, HZ / 2);
  93
  94                        /*
  95                         * Disable ourselves, we don't want to trip again unless
  96                         * userspace wants us to.
  97                         */
  98                        watchdog->enabled = false;
  99                }
 100        }
 101
 102        /* resubmit our work to happen again, if we are still "alive" */
 103        if (watchdog->enabled)
 104                schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
 105}
 106
 107int gb_svc_watchdog_create(struct gb_svc *svc)
 108{
 109        struct gb_svc_watchdog *watchdog;
 110        int retval;
 111
 112        if (svc->watchdog)
 113                return 0;
 114
 115        watchdog = kmalloc(sizeof(*watchdog), GFP_KERNEL);
 116        if (!watchdog)
 117                return -ENOMEM;
 118
 119        watchdog->enabled = false;
 120        watchdog->svc = svc;
 121        INIT_DELAYED_WORK(&watchdog->work, do_work);
 122        svc->watchdog = watchdog;
 123
 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
 140svc_watchdog_create_err:
 141        svc->watchdog = NULL;
 142        kfree(watchdog);
 143
 144        return retval;
 145}
 146
 147void gb_svc_watchdog_destroy(struct gb_svc *svc)
 148{
 149        struct gb_svc_watchdog *watchdog = svc->watchdog;
 150
 151        if (!watchdog)
 152                return;
 153
 154        unregister_pm_notifier(&watchdog->pm_notifier);
 155        gb_svc_watchdog_disable(svc);
 156        svc->watchdog = NULL;
 157        kfree(watchdog);
 158}
 159
 160bool gb_svc_watchdog_enabled(struct gb_svc *svc)
 161{
 162        if (!svc || !svc->watchdog)
 163                return false;
 164        return svc->watchdog->enabled;
 165}
 166
 167int 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;
 179        schedule_delayed_work(&watchdog->work, SVC_WATCHDOG_PERIOD);
 180        return 0;
 181}
 182
 183int 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}
 198