// SPDX-License-Identifier: GPL-2.0+ /* * An ar7 wrapper for standard watchdog devices * * Copyright (C) 2018 AVM GmbH * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include #define DRV_NAME "ar7wdt_hw_shim" enum wdt_target { AR7WDT_TARGET_READY = 0, AR7WDT_TARGET_ACTIVE, AR7WDT_TARGET_DISABLED }; struct ar7wdt_shim_info { struct watchdog_device *watchdog; enum wdt_target wdt_target; struct notifier_block wdt_device_notifier; struct work_struct wdt_ping_worker; struct mutex lock; }; static struct ar7wdt_shim_info ar7wdt_shim_info = { .lock = __MUTEX_INITIALIZER(ar7wdt_shim_info.lock), }; static struct ar7wdt_shim_info *ar7wdt_hw_get_shim(void) { return &ar7wdt_shim_info; } static int ar7wdt_hw_shim_get_device(struct ar7wdt_shim_info *shim, struct watchdog_device *wdd) { /* Currently this just grabs the first watchdog device seen. */ if (shim->watchdog) { dev_info(wdd->dev, "%s: Ignoring extra watchdog device (%s)\n", DRV_NAME, wdd->info->identity); return -EEXIST; } if (!try_module_get(wdd->ops->owner)) { dev_err(wdd->dev, "%s: Can't get watchdog module %s\n", DRV_NAME, wdd->ops->owner->name); return -ENODEV; } if (wdd->ops->ref) wdd->ops->ref(wdd); shim->watchdog = wdd; dev_info(wdd->dev, "%s: Device attached (%s)\n", DRV_NAME, wdd->info->identity); return 0; } static int ar7wdt_hw_shim_start_watchdog(struct ar7wdt_shim_info *shim) { int ret; dev_info(shim->watchdog->dev, "%s: Starting watchdog\n", DRV_NAME); ret = watchdog_start(shim->watchdog); if (ret) dev_err(shim->watchdog->dev, "%s: Starting watchdog failed: %d\n", DRV_NAME, ret); return ret; } static int ar7wdt_hw_shim_stop_watchdog(struct ar7wdt_shim_info *shim) { int ret; if (!shim->watchdog) return 0; dev_info(shim->watchdog->dev, "%s: Stopping watchdog\n", DRV_NAME); ret = watchdog_stop(shim->watchdog); if (ret) dev_err(shim->watchdog->dev, "%s: Error stopping watchdog: %d\n", DRV_NAME, ret); return ret; } static int ar7wdt_hw_shim_put_device(struct ar7wdt_shim_info *shim) { struct watchdog_device *wdd; wdd = shim->watchdog; if (!wdd) return 0; if (wdd->ops->unref) wdd->ops->unref(wdd); module_put(wdd->ops->owner); shim->watchdog = NULL; return 0; } static int wdt_device_notify(struct notifier_block *nb, unsigned long action, void *data) { struct ar7wdt_shim_info *shim = container_of(nb, struct ar7wdt_shim_info, wdt_device_notifier); struct watchdog_device *wdd = data; int notified = NOTIFY_OK; int ret; mutex_lock(&shim->lock); switch (action) { case WATCHDOG_NOTIFY_ADD_DEVICE: ret = ar7wdt_hw_shim_get_device(shim, wdd); if (!ret) { if (shim->wdt_target == AR7WDT_TARGET_ACTIVE) ar7wdt_hw_shim_start_watchdog(shim); else ar7wdt_hw_shim_stop_watchdog(shim); } break; case WATCHDOG_NOTIFY_DEL_DEVICE: WARN(1, "%s: Unsolicited removal of watchdog device!\n", DRV_NAME); ar7wdt_hw_shim_stop_watchdog(shim); ar7wdt_hw_shim_put_device(shim); break; default: notified = NOTIFY_DONE; break; } mutex_unlock(&shim->lock); return notified; } static void ar7wdt_hw_shim_do_ping(struct work_struct *work) { struct ar7wdt_shim_info *shim = container_of(work, struct ar7wdt_shim_info, wdt_ping_worker); int ret; mutex_lock(&shim->lock); if (!shim->watchdog) { pr_err("%s: Can't kick watchdog: No device attached\n", DRV_NAME); goto out; } ret = watchdog_ping(shim->watchdog); if (ret) dev_err(shim->watchdog->dev, "%s: Error kicking watchdog: %d\n", DRV_NAME, ret); out: mutex_unlock(&shim->lock); } static int __init ar7wdt_hw_shim_init(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); INIT_WORK(&shim->wdt_ping_worker, ar7wdt_hw_shim_do_ping); shim->wdt_device_notifier.notifier_call = wdt_device_notify; watchdog_register_notifier(&shim->wdt_device_notifier); return 0; } /***** ar7wdt interface functions *****/ void ar7wdt_hw_init(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); mutex_lock(&shim->lock); if (shim->wdt_target == AR7WDT_TARGET_DISABLED) goto out; shim->wdt_target = AR7WDT_TARGET_ACTIVE; if (shim->watchdog) ar7wdt_hw_shim_start_watchdog(shim); else pr_info("%s: %s: No watchdog device available (yet)\n", DRV_NAME, __func__); out: mutex_unlock(&shim->lock); } int ar7wdt_hw_is_wdt_running(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); bool active; mutex_lock(&shim->lock); if (shim->watchdog) active = watchdog_active(shim->watchdog); else active = false; mutex_unlock(&shim->lock); return active; } void ar7wdt_hw_secure_wdt_disable(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); mutex_lock(&shim->lock); ar7wdt_hw_shim_stop_watchdog(shim); shim->wdt_target = AR7WDT_TARGET_DISABLED; mutex_unlock(&shim->lock); } void ar7wdt_hw_deinit(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); mutex_lock(&shim->lock); ar7wdt_hw_shim_stop_watchdog(shim); ar7wdt_hw_shim_put_device(shim); mutex_unlock(&shim->lock); } void ar7wdt_hw_reboot(void) { panic("%s: soft reboot\n", __func__); } void ar7wdt_hw_trigger(void) { struct ar7wdt_shim_info *shim = ar7wdt_hw_get_shim(); if (shim->watchdog) schedule_work(&shim->wdt_ping_worker); } module_init(ar7wdt_hw_shim_init);