Skip to content

Commit a737c00

Browse files
committed
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 <[email protected]>
1 parent 2437f53 commit a737c00

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

drivers/watchdog/Kconfig

+5
Original file line numberDiff line numberDiff line change
@@ -2028,6 +2028,11 @@ config XEN_WDT
20282028
by Xen 4.0 and newer. The watchdog timeout period is normally one
20292029
minute but can be changed with a boot-time parameter.
20302030

2031+
config VIRTIO_WDT
2032+
tristate "Virtio Watchdog support"
2033+
depends on VIRTIO
2034+
select WATCHDOG_CORE
2035+
20312036
config UML_WATCHDOG
20322037
tristate "UML watchdog"
20332038
depends on UML || COMPILE_TEST

drivers/watchdog/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,4 @@ obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
224224
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
225225
obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
226226
obj-$(CONFIG_SL28CPLD_WATCHDOG) += sl28cpld_wdt.o
227+
obj-$(CONFIG_VIRTIO_WDT) += virtio_wdt.o

drivers/watchdog/virtio_wdt.c

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Watchdog driver for virtio. Derived from virto-rng.c and i6300esb.c
4+
* Copyright 2007, 2008 Rusty Russell IBM Corporation
5+
* Copyright 2004 Google Inc.
6+
* Copyright 2005 David Härdeman <[email protected]>
7+
* Copyright 2020 Intel Corporation
8+
*/
9+
10+
#include <linux/err.h>
11+
#include <linux/scatterlist.h>
12+
#include <linux/spinlock.h>
13+
#include <linux/virtio.h>
14+
#include <linux/module.h>
15+
#include <linux/slab.h>
16+
#include <linux/watchdog.h>
17+
#include <linux/virtio_ids.h>
18+
#include <linux/virtio_config.h>
19+
20+
static DEFINE_IDA(watchdog_index_ida);
21+
22+
#define to_info(wptr) container_of(wptr, struct virtio_watchdog_info, wdd)
23+
24+
/* Only support an interval of 15s */
25+
#define VW_HEARTBEAT_DEFAULT 15
26+
27+
struct virtio_watchdog_info {
28+
struct watchdog_device wdd;
29+
struct virtio_device *vdev;
30+
struct virtqueue *vq;
31+
struct completion have_data;
32+
char name[25];
33+
unsigned int data_avail;
34+
int index;
35+
bool busy;
36+
bool wdd_register_done;
37+
};
38+
39+
static void virtio_watchdog_recv_done(struct virtqueue *vq)
40+
{
41+
struct virtio_watchdog_info *vi = vq->vdev->priv;
42+
43+
/* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */
44+
if (!virtqueue_get_buf(vi->vq, &vi->data_avail))
45+
return;
46+
47+
complete(&vi->have_data);
48+
}
49+
50+
/* Host will change the buffer from 0->1 */
51+
static void register_buffer(struct virtio_watchdog_info *vi, u8 *buf,
52+
size_t size)
53+
{
54+
struct scatterlist sg;
55+
56+
sg_init_one(&sg, buf, size);
57+
virtqueue_add_inbuf(vi->vq, &sg, 1, buf, GFP_KERNEL);
58+
59+
virtqueue_kick(vi->vq);
60+
}
61+
62+
63+
static int virtio_watchdog_ping(struct watchdog_device *wdd)
64+
{
65+
struct virtio_watchdog_info *vi = to_info(wdd);
66+
struct virtio_device *vdev = vi->vdev;
67+
int ret;
68+
u8 *buf;
69+
70+
if (!vi->wdd_register_done)
71+
return -ENODEV;
72+
73+
buf = kzalloc(sizeof(u8), GFP_KERNEL);
74+
if (!vi->busy) {
75+
vi->busy = true;
76+
reinit_completion(&vi->have_data);
77+
register_buffer(vi, (void *)buf, sizeof(u64));
78+
}
79+
80+
ret = wait_for_completion_killable(&vi->have_data);
81+
if (ret < 0)
82+
goto err;
83+
84+
if (*buf != 1) {
85+
dev_err(&vdev->dev,
86+
"Host did not acknowledge buffer correctly");
87+
ret = -EINVAL;
88+
}
89+
90+
err:
91+
vi->busy = false;
92+
kfree(buf);
93+
94+
return ret;
95+
}
96+
97+
static int virtio_watchdog_start(struct watchdog_device *wdd)
98+
{
99+
struct virtio_watchdog_info *vi = to_info(wdd);
100+
struct virtio_device *vdev = vi->vdev;
101+
102+
dev_info(&vdev->dev, "Watchdog started");
103+
104+
return 0;
105+
}
106+
107+
static int virtio_watchdog_stop(struct watchdog_device *wdd)
108+
{
109+
struct virtio_watchdog_info *vi = to_info(wdd);
110+
struct virtio_device *vdev = vi->vdev;
111+
112+
dev_info(&vdev->dev, "Watchdog stop request ignored");
113+
114+
return 0;
115+
}
116+
117+
static struct watchdog_info vw_info = {
118+
.identity = "virtio-watchdog",
119+
.options = WDIOF_KEEPALIVEPING,
120+
};
121+
122+
static const struct watchdog_ops vw_ops = {
123+
.owner = THIS_MODULE,
124+
.start = virtio_watchdog_start,
125+
.stop = virtio_watchdog_stop,
126+
.ping = virtio_watchdog_ping,
127+
};
128+
129+
static int probe_common(struct virtio_device *vdev)
130+
{
131+
int err, index;
132+
struct virtio_watchdog_info *vi = NULL;
133+
134+
vi = kzalloc(sizeof(struct virtio_watchdog_info), GFP_KERNEL);
135+
if (!vi)
136+
return -ENOMEM;
137+
138+
vi->index = index =
139+
ida_simple_get(&watchdog_index_ida, 0, 0, GFP_KERNEL);
140+
if (index < 0) {
141+
err = index;
142+
goto err_ida;
143+
}
144+
sprintf(vi->name, "virtio_watchdog.%d", index);
145+
init_completion(&vi->have_data);
146+
147+
vdev->priv = vi;
148+
vi->vdev = vdev;
149+
150+
vi->vq =
151+
virtio_find_single_vq(vdev, virtio_watchdog_recv_done, "input");
152+
if (IS_ERR(vi->vq)) {
153+
err = PTR_ERR(vi->vq);
154+
goto err_find;
155+
}
156+
157+
vi->wdd.info = &vw_info;
158+
vi->wdd.ops = &vw_ops;
159+
vi->wdd.min_timeout = VW_HEARTBEAT_DEFAULT;
160+
vi->wdd.max_timeout = VW_HEARTBEAT_DEFAULT;
161+
vi->wdd.timeout = VW_HEARTBEAT_DEFAULT;
162+
163+
err = watchdog_register_device(&vi->wdd);
164+
if (err != 0)
165+
goto err_find;
166+
vi->wdd_register_done = true;
167+
168+
return 0;
169+
170+
err_find:
171+
ida_simple_remove(&watchdog_index_ida, index);
172+
err_ida:
173+
kfree(vi);
174+
return err;
175+
}
176+
177+
static void remove_common(struct virtio_device *vdev)
178+
{
179+
struct virtio_watchdog_info *vi = vdev->priv;
180+
181+
if (vi->busy) {
182+
wait_for_completion(&vi->have_data);
183+
vi->data_avail = 0;
184+
complete(&vi->have_data);
185+
vi->busy = false;
186+
}
187+
vdev->config->reset(vdev);
188+
if (vi->wdd_register_done) {
189+
watchdog_unregister_device(&vi->wdd);
190+
vi->wdd_register_done = false;
191+
}
192+
vdev->config->del_vqs(vdev);
193+
ida_simple_remove(&watchdog_index_ida, vi->index);
194+
kfree(vi);
195+
}
196+
197+
static int virtio_watchdog_probe(struct virtio_device *vdev)
198+
{
199+
return probe_common(vdev);
200+
}
201+
202+
static void virtio_watchdog_remove(struct virtio_device *vdev)
203+
{
204+
remove_common(vdev);
205+
}
206+
207+
#ifdef CONFIG_PM_SLEEP
208+
static int virtio_watchdog_freeze(struct virtio_device *vdev)
209+
{
210+
remove_common(vdev);
211+
return 0;
212+
}
213+
214+
static int virtio_watchdog_restore(struct virtio_device *vdev)
215+
{
216+
return probe_common(vdev);
217+
}
218+
#endif
219+
220+
static struct virtio_device_id id_table[] = {
221+
{ VIRTIO_ID_WATCHDOG, VIRTIO_DEV_ANY_ID },
222+
{ 0 },
223+
};
224+
225+
static struct virtio_driver virtio_watchdog_driver = {
226+
.driver.name = KBUILD_MODNAME,
227+
.driver.owner = THIS_MODULE,
228+
.id_table = id_table,
229+
.probe = virtio_watchdog_probe,
230+
.remove = virtio_watchdog_remove,
231+
#ifdef CONFIG_PM_SLEEP
232+
.freeze = virtio_watchdog_freeze,
233+
.restore = virtio_watchdog_restore,
234+
#endif
235+
};
236+
237+
module_virtio_driver(virtio_watchdog_driver);
238+
MODULE_DEVICE_TABLE(virtio, id_table);
239+
MODULE_DESCRIPTION("Virtio watchdog driver");
240+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)