diff --git a/drivers/staging/greybus/Documentation/sysfs-bus-greybus b/drivers/staging/greybus/Documentation/sysfs-bus-greybus index 1550b7f8d776..4493605186b0 100644 --- a/drivers/staging/greybus/Documentation/sysfs-bus-greybus +++ b/drivers/staging/greybus/Documentation/sysfs-bus-greybus @@ -163,3 +163,11 @@ KernelVersion: 4.XX Contact: Greg Kroah-Hartman Description: The version number of the firmware in the SVC device. + +What: /sys/bus/greybus/device/N-svc/watchdog +Date: October 2016 +KernelVersion: 4.XX +Contact: Greg Kroah-Hartman +Description: + If the SVC watchdog is enabled or not. Writing 0 to this + file will disable the watchdog, writing 1 will enable it. diff --git a/drivers/staging/greybus/svc.c b/drivers/staging/greybus/svc.c index b9e5b8565010..eeb251e28441 100644 --- a/drivers/staging/greybus/svc.c +++ b/drivers/staging/greybus/svc.c @@ -70,10 +70,42 @@ static ssize_t intf_eject_store(struct device *dev, } static DEVICE_ATTR_WO(intf_eject); +static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gb_svc *svc = to_gb_svc(dev); + + return sprintf(buf, "%s\n", + gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled"); +} + +static ssize_t watchdog_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct gb_svc *svc = to_gb_svc(dev); + int retval; + bool user_request; + + retval = strtobool(buf, &user_request); + if (retval) + return retval; + + if (user_request) + retval = gb_svc_watchdog_enable(svc); + else + retval = gb_svc_watchdog_disable(svc); + if (retval) + return retval; + return len; +} +static DEVICE_ATTR_RW(watchdog); + static struct attribute *svc_attrs[] = { &dev_attr_endo_id.attr, &dev_attr_ap_intf_id.attr, &dev_attr_intf_eject.attr, + &dev_attr_watchdog.attr, NULL, }; ATTRIBUTE_GROUPS(svc); diff --git a/drivers/staging/greybus/svc.h b/drivers/staging/greybus/svc.h index 0f81e97a2e88..b9fb93ea56be 100644 --- a/drivers/staging/greybus/svc.h +++ b/drivers/staging/greybus/svc.h @@ -61,6 +61,9 @@ int gb_svc_intf_set_power_mode(struct gb_svc *svc, u8 intf_id, u8 hs_series, 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); +bool gb_svc_watchdog_enabled(struct gb_svc *svc); +int gb_svc_watchdog_enable(struct gb_svc *svc); +int gb_svc_watchdog_disable(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 index edb73efd53f7..9bd68f4f4cf7 100644 --- a/drivers/staging/greybus/svc_watchdog.c +++ b/drivers/staging/greybus/svc_watchdog.c @@ -15,7 +15,7 @@ struct gb_svc_watchdog { struct delayed_work work; struct gb_svc *svc; - bool finished; + bool enabled; }; static struct delayed_work reset_work; @@ -65,11 +65,16 @@ static void do_work(struct work_struct *work) INIT_DELAYED_WORK(&reset_work, greybus_reset); queue_delayed_work(system_wq, &reset_work, HZ/2); - return; + + /* + * Disable ourselves, we don't want to trip again unless + * userspace wants us to. + */ + watchdog->enabled = false; } /* resubmit our work to happen again, if we are still "alive" */ - if (!watchdog->finished) + if (watchdog->enabled) queue_delayed_work(system_wq, &watchdog->work, SVC_WATCHDOG_PERIOD); } @@ -85,14 +90,12 @@ int gb_svc_watchdog_create(struct gb_svc *svc) if (!watchdog) return -ENOMEM; - watchdog->finished = false; + watchdog->enabled = 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; + return gb_svc_watchdog_enable(svc); } void gb_svc_watchdog_destroy(struct gb_svc *svc) @@ -102,8 +105,47 @@ void gb_svc_watchdog_destroy(struct gb_svc *svc) if (!watchdog) return; - watchdog->finished = true; - cancel_delayed_work_sync(&watchdog->work); + gb_svc_watchdog_disable(svc); svc->watchdog = NULL; kfree(watchdog); } + +bool gb_svc_watchdog_enabled(struct gb_svc *svc) +{ + if (!svc || !svc->watchdog) + return false; + return svc->watchdog->enabled; +} + +int gb_svc_watchdog_enable(struct gb_svc *svc) +{ + struct gb_svc_watchdog *watchdog; + + if (!svc->watchdog) + return -ENODEV; + + watchdog = svc->watchdog; + if (watchdog->enabled) + return 0; + + watchdog->enabled = true; + queue_delayed_work(system_wq, &watchdog->work, + SVC_WATCHDOG_PERIOD); + return 0; +} + +int gb_svc_watchdog_disable(struct gb_svc *svc) +{ + struct gb_svc_watchdog *watchdog; + + if (!svc->watchdog) + return -ENODEV; + + watchdog = svc->watchdog; + if (!watchdog->enabled) + return 0; + + watchdog->enabled = false; + cancel_delayed_work_sync(&watchdog->work); + return 0; +}