linux/drivers/staging/greybus/svc_watchdog.c
<<
>>
Prefs
   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
  16struct gb_svc_watchdog {
  17        struct delayed_work     work;
  18        struct gb_svc           *svc;
  19        bool                    enabled;
  20        struct notifier_block pm_notifier;
  21};
  22
  23static struct delayed_work reset_work;
  24
  25static 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
  45static 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
  64static 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
 108int 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
 141svc_watchdog_create_err:
 142        svc->watchdog = NULL;
 143        kfree(watchdog);
 144
 145        return retval;
 146}
 147
 148void 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
 161bool gb_svc_watchdog_enabled(struct gb_svc *svc)
 162{
 163        if (!svc || !svc->watchdog)
 164                return false;
 165        return svc->watchdog->enabled;
 166}
 167
 168int 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
 184int 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