Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
066d6c7f JB |
2 | /* |
3 | * Xen Watchdog Driver | |
4 | * | |
5 | * (c) Copyright 2010 Novell, Inc. | |
066d6c7f JB |
6 | */ |
7 | ||
18cffd68 | 8 | #define DRV_NAME "xen_wdt" |
066d6c7f JB |
9 | |
10 | #include <linux/bug.h> | |
11 | #include <linux/errno.h> | |
12 | #include <linux/fs.h> | |
13 | #include <linux/hrtimer.h> | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/ktime.h> | |
16 | #include <linux/init.h> | |
066d6c7f JB |
17 | #include <linux/module.h> |
18 | #include <linux/moduleparam.h> | |
19 | #include <linux/platform_device.h> | |
066d6c7f JB |
20 | #include <linux/watchdog.h> |
21 | #include <xen/xen.h> | |
22 | #include <asm/xen/hypercall.h> | |
23 | #include <xen/interface/sched.h> | |
24 | ||
25 | static struct platform_device *platform_device; | |
066d6c7f | 26 | static struct sched_watchdog wdt; |
b6c84c9f | 27 | static time64_t wdt_expires; |
066d6c7f JB |
28 | |
29 | #define WATCHDOG_TIMEOUT 60 /* in seconds */ | |
18cffd68 | 30 | static unsigned int timeout; |
066d6c7f JB |
31 | module_param(timeout, uint, S_IRUGO); |
32 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " | |
33 | "(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | |
34 | ||
35 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
36 | module_param(nowayout, bool, S_IRUGO); | |
37 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " | |
38 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
39 | ||
18cffd68 | 40 | static inline time64_t set_timeout(struct watchdog_device *wdd) |
066d6c7f | 41 | { |
18cffd68 RR |
42 | wdt.timeout = wdd->timeout; |
43 | return ktime_get_seconds() + wdd->timeout; | |
066d6c7f JB |
44 | } |
45 | ||
18cffd68 | 46 | static int xen_wdt_start(struct watchdog_device *wdd) |
066d6c7f | 47 | { |
b6c84c9f | 48 | time64_t expires; |
066d6c7f JB |
49 | int err; |
50 | ||
18cffd68 | 51 | expires = set_timeout(wdd); |
066d6c7f JB |
52 | if (!wdt.id) |
53 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | |
54 | else | |
55 | err = -EBUSY; | |
56 | if (err > 0) { | |
57 | wdt.id = err; | |
58 | wdt_expires = expires; | |
59 | err = 0; | |
60 | } else | |
61 | BUG_ON(!err); | |
62 | ||
066d6c7f JB |
63 | return err; |
64 | } | |
65 | ||
18cffd68 | 66 | static int xen_wdt_stop(struct watchdog_device *wdd) |
066d6c7f JB |
67 | { |
68 | int err = 0; | |
69 | ||
066d6c7f JB |
70 | wdt.timeout = 0; |
71 | if (wdt.id) | |
72 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | |
73 | if (!err) | |
74 | wdt.id = 0; | |
75 | ||
066d6c7f JB |
76 | return err; |
77 | } | |
78 | ||
18cffd68 | 79 | static int xen_wdt_kick(struct watchdog_device *wdd) |
066d6c7f | 80 | { |
b6c84c9f | 81 | time64_t expires; |
066d6c7f JB |
82 | int err; |
83 | ||
18cffd68 | 84 | expires = set_timeout(wdd); |
066d6c7f JB |
85 | if (wdt.id) |
86 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | |
87 | else | |
88 | err = -ENXIO; | |
89 | if (!err) | |
90 | wdt_expires = expires; | |
91 | ||
066d6c7f JB |
92 | return err; |
93 | } | |
94 | ||
18cffd68 | 95 | static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd) |
066d6c7f | 96 | { |
18cffd68 | 97 | return wdt_expires - ktime_get_seconds(); |
066d6c7f JB |
98 | } |
99 | ||
18cffd68 RR |
100 | static struct watchdog_info xen_wdt_info = { |
101 | .identity = DRV_NAME, | |
102 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
103 | }; | |
066d6c7f | 104 | |
18cffd68 RR |
105 | static const struct watchdog_ops xen_wdt_ops = { |
106 | .owner = THIS_MODULE, | |
107 | .start = xen_wdt_start, | |
108 | .stop = xen_wdt_stop, | |
109 | .ping = xen_wdt_kick, | |
110 | .get_timeleft = xen_wdt_get_timeleft, | |
066d6c7f JB |
111 | }; |
112 | ||
18cffd68 RR |
113 | static struct watchdog_device xen_wdt_dev = { |
114 | .info = &xen_wdt_info, | |
115 | .ops = &xen_wdt_ops, | |
116 | .timeout = WATCHDOG_TIMEOUT, | |
066d6c7f JB |
117 | }; |
118 | ||
18cffd68 | 119 | static int xen_wdt_probe(struct platform_device *pdev) |
066d6c7f | 120 | { |
b90abaac | 121 | struct device *dev = &pdev->dev; |
066d6c7f JB |
122 | struct sched_watchdog wd = { .id = ~0 }; |
123 | int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd); | |
124 | ||
18cffd68 | 125 | if (ret == -ENOSYS) { |
b90abaac | 126 | dev_err(dev, "watchdog not supported by hypervisor\n"); |
18cffd68 | 127 | return -ENODEV; |
066d6c7f JB |
128 | } |
129 | ||
18cffd68 | 130 | if (ret != -EINVAL) { |
b90abaac | 131 | dev_err(dev, "unexpected hypervisor error (%d)\n", ret); |
18cffd68 RR |
132 | return -ENODEV; |
133 | } | |
066d6c7f | 134 | |
b74d6461 | 135 | watchdog_init_timeout(&xen_wdt_dev, timeout, NULL); |
18cffd68 RR |
136 | watchdog_set_nowayout(&xen_wdt_dev, nowayout); |
137 | watchdog_stop_on_reboot(&xen_wdt_dev); | |
138 | watchdog_stop_on_unregister(&xen_wdt_dev); | |
139 | ||
b90abaac | 140 | ret = devm_watchdog_register_device(dev, &xen_wdt_dev); |
e1465135 | 141 | if (ret) |
18cffd68 | 142 | return ret; |
066d6c7f | 143 | |
b90abaac GR |
144 | dev_info(dev, "initialized (timeout=%ds, nowayout=%d)\n", |
145 | xen_wdt_dev.timeout, nowayout); | |
066d6c7f JB |
146 | |
147 | return 0; | |
148 | } | |
149 | ||
066d6c7f JB |
150 | static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state) |
151 | { | |
83448bf7 | 152 | typeof(wdt.id) id = wdt.id; |
18cffd68 | 153 | int rc = xen_wdt_stop(&xen_wdt_dev); |
83448bf7 JB |
154 | |
155 | wdt.id = id; | |
156 | return rc; | |
066d6c7f JB |
157 | } |
158 | ||
159 | static int xen_wdt_resume(struct platform_device *dev) | |
160 | { | |
83448bf7 JB |
161 | if (!wdt.id) |
162 | return 0; | |
163 | wdt.id = 0; | |
18cffd68 | 164 | return xen_wdt_start(&xen_wdt_dev); |
066d6c7f JB |
165 | } |
166 | ||
167 | static struct platform_driver xen_wdt_driver = { | |
168 | .probe = xen_wdt_probe, | |
066d6c7f JB |
169 | .suspend = xen_wdt_suspend, |
170 | .resume = xen_wdt_resume, | |
171 | .driver = { | |
066d6c7f JB |
172 | .name = DRV_NAME, |
173 | }, | |
174 | }; | |
175 | ||
176 | static int __init xen_wdt_init_module(void) | |
177 | { | |
178 | int err; | |
179 | ||
180 | if (!xen_domain()) | |
181 | return -ENODEV; | |
182 | ||
066d6c7f JB |
183 | err = platform_driver_register(&xen_wdt_driver); |
184 | if (err) | |
185 | return err; | |
186 | ||
187 | platform_device = platform_device_register_simple(DRV_NAME, | |
188 | -1, NULL, 0); | |
189 | if (IS_ERR(platform_device)) { | |
190 | err = PTR_ERR(platform_device); | |
191 | platform_driver_unregister(&xen_wdt_driver); | |
192 | } | |
193 | ||
194 | return err; | |
195 | } | |
196 | ||
197 | static void __exit xen_wdt_cleanup_module(void) | |
198 | { | |
199 | platform_device_unregister(platform_device); | |
200 | platform_driver_unregister(&xen_wdt_driver); | |
066d6c7f JB |
201 | } |
202 | ||
203 | module_init(xen_wdt_init_module); | |
204 | module_exit(xen_wdt_cleanup_module); | |
205 | ||
206 | MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>"); | |
207 | MODULE_DESCRIPTION("Xen WatchDog Timer Driver"); | |
066d6c7f | 208 | MODULE_LICENSE("GPL"); |