From 08a23fbb63f3c947265fc8b1a67212bfef0df0da Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 22 Sep 2020 18:16:41 +0100 Subject: [PATCH] drivers/watchdog: Add paravirtualized virtio-watchdog This driver is derived from virtio-rng and i6300esb drivers. It is a simple single queue virtio driver with a single queue where the driver "pings" the device by making a descriptor available. The device acknowledges the ping by writing to the passed in descriptor and returning it to the driver. Once the device sees a ping it starts checking that it receives a ping once every 15s. If it does not then it will reboot the VM. Signed-off-by: Rob Bradford --- drivers/watchdog/Kconfig | 5 + drivers/watchdog/Makefile | 2 + drivers/watchdog/virtio_wdt.c | 240 ++++++++++++++++++++++++++++++++ include/uapi/linux/virtio_ids.h | 1 + 4 files changed, 248 insertions(+) create mode 100644 drivers/watchdog/virtio_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4f4687c46d38e5..34159126cbe878 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2037,6 +2037,11 @@ config XEN_WDT by Xen 4.0 and newer. The watchdog timeout period is normally one minute but can be changed with a boot-time parameter. +config VIRTIO_WDT + tristate "Virtio Watchdog support" + depends on VIRTIO + select WATCHDOG_CORE + config UML_WATCHDOG tristate "UML watchdog" depends on UML || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 97bed1d3d97cb1..1239804e57c7b8 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -225,3 +225,5 @@ obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o + +obj-$(CONFIG_VIRTIO_WDT) += virtio_wdt.o \ No newline at end of file diff --git a/drivers/watchdog/virtio_wdt.c b/drivers/watchdog/virtio_wdt.c new file mode 100644 index 00000000000000..85b8b610800663 --- /dev/null +++ b/drivers/watchdog/virtio_wdt.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Watchdog driver for virtio. Derived from virto-rng.c and i6300esb.c + * Copyright 2007, 2008 Rusty Russell IBM Corporation + * Copyright 2004 Google Inc. + * Copyright 2005 David Härdeman + * Copyright 2020 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(watchdog_index_ida); + +#define to_info(wptr) container_of(wptr, struct virtio_watchdog_info, wdd) + +/* Only support an interval of 15s */ +#define VW_HEARTBEAT_DEFAULT 15 + +struct virtio_watchdog_info { + struct watchdog_device wdd; + struct virtio_device *vdev; + struct virtqueue *vq; + struct completion have_data; + char name[25]; + unsigned int data_avail; + int index; + bool busy; + bool wdd_register_done; +}; + +static void virtio_watchdog_recv_done(struct virtqueue *vq) +{ + struct virtio_watchdog_info *vi = vq->vdev->priv; + + /* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */ + if (!virtqueue_get_buf(vi->vq, &vi->data_avail)) + return; + + complete(&vi->have_data); +} + +/* Host will change the buffer from 0->1 */ +static void register_buffer(struct virtio_watchdog_info *vi, u8 *buf, + size_t size) +{ + struct scatterlist sg; + + sg_init_one(&sg, buf, size); + virtqueue_add_inbuf(vi->vq, &sg, 1, buf, GFP_KERNEL); + + virtqueue_kick(vi->vq); +} + + +static int virtio_watchdog_ping(struct watchdog_device *wdd) +{ + struct virtio_watchdog_info *vi = to_info(wdd); + struct virtio_device *vdev = vi->vdev; + int ret; + u8 *buf; + + if (!vi->wdd_register_done) + return -ENODEV; + + buf = kzalloc(sizeof(u8), GFP_KERNEL); + if (!vi->busy) { + vi->busy = true; + reinit_completion(&vi->have_data); + register_buffer(vi, (void *)buf, sizeof(u64)); + } + + ret = wait_for_completion_killable(&vi->have_data); + if (ret < 0) + goto err; + + if (*buf != 1) { + dev_err(&vdev->dev, + "Host did not acknowledge buffer correctly"); + ret = -EINVAL; + } + +err: + vi->busy = false; + kfree(buf); + + return ret; +} + +static int virtio_watchdog_start(struct watchdog_device *wdd) +{ + struct virtio_watchdog_info *vi = to_info(wdd); + struct virtio_device *vdev = vi->vdev; + + dev_info(&vdev->dev, "Watchdog started"); + + return 0; +} + +static int virtio_watchdog_stop(struct watchdog_device *wdd) +{ + struct virtio_watchdog_info *vi = to_info(wdd); + struct virtio_device *vdev = vi->vdev; + + dev_info(&vdev->dev, "Watchdog stop request ignored"); + + return 0; +} + +static struct watchdog_info vw_info = { + .identity = "virtio-watchdog", + .options = WDIOF_KEEPALIVEPING, +}; + +static const struct watchdog_ops vw_ops = { + .owner = THIS_MODULE, + .start = virtio_watchdog_start, + .stop = virtio_watchdog_stop, + .ping = virtio_watchdog_ping, +}; + +static int probe_common(struct virtio_device *vdev) +{ + int err, index; + struct virtio_watchdog_info *vi = NULL; + + vi = kzalloc(sizeof(struct virtio_watchdog_info), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vi->index = index = + ida_simple_get(&watchdog_index_ida, 0, 0, GFP_KERNEL); + if (index < 0) { + err = index; + goto err_ida; + } + sprintf(vi->name, "virtio_watchdog.%d", index); + init_completion(&vi->have_data); + + vdev->priv = vi; + vi->vdev = vdev; + + vi->vq = + virtio_find_single_vq(vdev, virtio_watchdog_recv_done, "input"); + if (IS_ERR(vi->vq)) { + err = PTR_ERR(vi->vq); + goto err_find; + } + + vi->wdd.info = &vw_info; + vi->wdd.ops = &vw_ops; + vi->wdd.min_timeout = VW_HEARTBEAT_DEFAULT; + vi->wdd.max_timeout = VW_HEARTBEAT_DEFAULT; + vi->wdd.timeout = VW_HEARTBEAT_DEFAULT; + + err = watchdog_register_device(&vi->wdd); + if (err != 0) + goto err_find; + vi->wdd_register_done = true; + + return 0; + +err_find: + ida_simple_remove(&watchdog_index_ida, index); +err_ida: + kfree(vi); + return err; +} + +static void remove_common(struct virtio_device *vdev) +{ + struct virtio_watchdog_info *vi = vdev->priv; + + if (vi->busy) { + wait_for_completion(&vi->have_data); + vi->data_avail = 0; + complete(&vi->have_data); + vi->busy = false; + } + vdev->config->reset(vdev); + if (vi->wdd_register_done) { + watchdog_unregister_device(&vi->wdd); + vi->wdd_register_done = false; + } + vdev->config->del_vqs(vdev); + ida_simple_remove(&watchdog_index_ida, vi->index); + kfree(vi); +} + +static int virtio_watchdog_probe(struct virtio_device *vdev) +{ + return probe_common(vdev); +} + +static void virtio_watchdog_remove(struct virtio_device *vdev) +{ + remove_common(vdev); +} + +#ifdef CONFIG_PM_SLEEP +static int virtio_watchdog_freeze(struct virtio_device *vdev) +{ + remove_common(vdev); + return 0; +} + +static int virtio_watchdog_restore(struct virtio_device *vdev) +{ + return probe_common(vdev); +} +#endif + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_WATCHDOG, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_watchdog_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = virtio_watchdog_probe, + .remove = virtio_watchdog_remove, +#ifdef CONFIG_PM_SLEEP + .freeze = virtio_watchdog_freeze, + .restore = virtio_watchdog_restore, +#endif +}; + +module_virtio_driver(virtio_watchdog_driver); +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("Virtio watchdog driver"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index b052355ac7a324..5930dc48c8b9d4 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -48,5 +48,6 @@ #define VIRTIO_ID_FS 26 /* virtio filesystem */ #define VIRTIO_ID_PMEM 27 /* virtio pmem */ #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */ +#define VIRTIO_ID_WATCHDOG 35 /* (temporary) virtio-watchdog */ #endif /* _LINUX_VIRTIO_IDS_H */