Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed7279ae authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman
Browse files

greybus: svc: add a "watchdog" to check the network health



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: default avatarGreg Kroah-Hartman <gregkh@google.com>
Reviewed-by: default avatarRui Miguel Silva <rui.silva@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 0f65fb1e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ greybus-y := core.o \
		protocol.o	\
		control.o	\
		svc.o		\
		svc_watchdog.o	\
		firmware.o	\
		operation.o	\
		legacy.o
+8 −0
Original line number Diff line number Diff line
@@ -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);
	}
+5 −0
Original line number Diff line number Diff line
@@ -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);
+109 −0
Original line number Diff line number Diff line
/*
 * 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);
}