greybus: svc: add a "watchdog" to check the network health
authorGreg Kroah-Hartman <gregkh@google.com>
Thu, 21 Jan 2016 06:51:49 +0000 (22:51 -0800)
committerGreg Kroah-Hartman <gregkh@google.com>
Sun, 24 Jan 2016 01:38:33 +0000 (17:38 -0800)
Now that we have a svc ping command, let's add a watchdog to call it
every so often (1 second at the moment.)  If it finds something went
wrong, post a stern message to the kernel log and call:
start unipro_reset
to reset the whole greybus hardware subsystem.

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Reviewed-by: Rui Miguel Silva <rui.silva@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/Makefile
drivers/staging/greybus/svc.c
drivers/staging/greybus/svc.h
drivers/staging/greybus/svc_watchdog.c [new file with mode: 0644]

index 1e24509..1e45416 100644 (file)
@@ -8,6 +8,7 @@ greybus-y :=    core.o          \
                protocol.o      \
                control.o       \
                svc.o           \
+               svc_watchdog.o  \
                firmware.o      \
                operation.o     \
                legacy.o
index 85eb7ea..fc57470 100644 (file)
@@ -436,6 +436,13 @@ static int gb_svc_hello(struct gb_operation *op)
                return ret;
        }
 
+       ret = gb_svc_watchdog_create(svc);
+       if (ret) {
+               dev_err(&svc->dev, "failed to create watchdog: %d\n", ret);
+               input_unregister_device(svc->input);
+               device_del(&svc->dev);
+       }
+
        return 0;
 }
 
@@ -963,6 +970,7 @@ void gb_svc_del(struct gb_svc *svc)
         * from the request handler.
         */
        if (device_is_registered(&svc->dev)) {
+               gb_svc_watchdog_destroy(svc);
                input_unregister_device(svc->input);
                device_del(&svc->dev);
        }
index f3e8479..0f81e97 100644 (file)
@@ -16,6 +16,8 @@ enum gb_svc_state {
        GB_SVC_STATE_SVC_HELLO,
 };
 
+struct gb_svc_watchdog;
+
 struct gb_svc {
        struct device           dev;
 
@@ -33,6 +35,7 @@ struct gb_svc {
 
        struct input_dev        *input;
        char                    *input_phys;
+       struct gb_svc_watchdog  *watchdog;
 };
 #define to_gb_svc(d) container_of(d, struct gb_svc, d)
 
@@ -56,6 +59,8 @@ int gb_svc_intf_set_power_mode(struct gb_svc *svc, u8 intf_id, u8 hs_series,
                               u8 rx_mode, u8 rx_gear, u8 rx_nlanes,
                               u8 flags, u32 quirks);
 int gb_svc_ping(struct gb_svc *svc);
+int gb_svc_watchdog_create(struct gb_svc *svc);
+void gb_svc_watchdog_destroy(struct gb_svc *svc);
 
 int gb_svc_protocol_init(void);
 void gb_svc_protocol_exit(void);
diff --git a/drivers/staging/greybus/svc_watchdog.c b/drivers/staging/greybus/svc_watchdog.c
new file mode 100644 (file)
index 0000000..edb73ef
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SVC Greybus "watchdog" driver.
+ *
+ * Copyright 2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include "greybus.h"
+
+#define SVC_WATCHDOG_PERIOD    (2*HZ)
+
+struct gb_svc_watchdog {
+       struct delayed_work     work;
+       struct gb_svc           *svc;
+       bool                    finished;
+};
+
+static struct delayed_work reset_work;
+
+static void greybus_reset(struct work_struct *work)
+{
+       static char start_path[256] = "/system/bin/start";
+       static char *envp[] = {
+               "HOME=/",
+               "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
+               NULL,
+       };
+       static char *argv[] = {
+               start_path,
+               "unipro_reset",
+               NULL,
+       };
+
+       printk(KERN_ERR "svc_watchdog: calling \"%s %s\" to reset greybus network!\n",
+              argv[0], argv[1]);
+       call_usermodehelper(start_path, argv, envp, UMH_WAIT_EXEC);
+}
+
+static void do_work(struct work_struct *work)
+{
+       struct gb_svc_watchdog *watchdog;
+       struct gb_svc *svc;
+       int retval;
+
+       watchdog = container_of(work, struct gb_svc_watchdog, work.work);
+       svc = watchdog->svc;
+
+       dev_dbg(&svc->dev, "%s: ping.\n", __func__);
+       retval = gb_svc_ping(svc);
+       if (retval) {
+               /*
+                * Something went really wrong, let's warn userspace and then
+                * pull the plug and reset the whole greybus network.
+                * We need to do this outside of this workqueue as we will be
+                * tearing down the svc device itself.  So queue up
+                * yet-another-callback to do that.
+                */
+               dev_err(&svc->dev,
+                       "SVC ping has returned %d, something is wrong!!!\n",
+                       retval);
+               dev_err(&svc->dev, "Resetting the greybus network, watch out!!!\n");
+
+               INIT_DELAYED_WORK(&reset_work, greybus_reset);
+               queue_delayed_work(system_wq, &reset_work, HZ/2);
+               return;
+       }
+
+       /* resubmit our work to happen again, if we are still "alive" */
+       if (!watchdog->finished)
+               queue_delayed_work(system_wq, &watchdog->work,
+                                  SVC_WATCHDOG_PERIOD);
+}
+
+int gb_svc_watchdog_create(struct gb_svc *svc)
+{
+       struct gb_svc_watchdog *watchdog;
+
+       if (svc->watchdog)
+               return 0;
+
+       watchdog = kmalloc(sizeof(*watchdog), GFP_KERNEL);
+       if (!watchdog)
+               return -ENOMEM;
+
+       watchdog->finished = false;
+       watchdog->svc = svc;
+       INIT_DELAYED_WORK(&watchdog->work, do_work);
+       svc->watchdog = watchdog;
+
+       queue_delayed_work(system_wq, &watchdog->work,
+                          SVC_WATCHDOG_PERIOD);
+       return 0;
+}
+
+void gb_svc_watchdog_destroy(struct gb_svc *svc)
+{
+       struct gb_svc_watchdog *watchdog = svc->watchdog;
+
+       if (!watchdog)
+               return;
+
+       watchdog->finished = true;
+       cancel_delayed_work_sync(&watchdog->work);
+       svc->watchdog = NULL;
+       kfree(watchdog);
+}