1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Xen Watchdog Driver |
4 | * |
5 | * (c) Copyright 2010 Novell, Inc. |
6 | */ |
7 | |
8 | #define DRV_NAME "xen_wdt" |
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> |
17 | #include <linux/module.h> |
18 | #include <linux/moduleparam.h> |
19 | #include <linux/platform_device.h> |
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; |
26 | static struct sched_watchdog wdt; |
27 | static time64_t wdt_expires; |
28 | |
29 | #define WATCHDOG_TIMEOUT 60 /* in seconds */ |
30 | static unsigned int timeout; |
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 | |
40 | static inline time64_t set_timeout(struct watchdog_device *wdd) |
41 | { |
42 | wdt.timeout = wdd->timeout; |
43 | return ktime_get_seconds() + wdd->timeout; |
44 | } |
45 | |
46 | static int xen_wdt_start(struct watchdog_device *wdd) |
47 | { |
48 | time64_t expires; |
49 | int err; |
50 | |
51 | expires = set_timeout(wdd); |
52 | if (!wdt.id) |
53 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, arg: &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 | |
63 | return err; |
64 | } |
65 | |
66 | static int xen_wdt_stop(struct watchdog_device *wdd) |
67 | { |
68 | int err = 0; |
69 | |
70 | wdt.timeout = 0; |
71 | if (wdt.id) |
72 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, arg: &wdt); |
73 | if (!err) |
74 | wdt.id = 0; |
75 | |
76 | return err; |
77 | } |
78 | |
79 | static int xen_wdt_kick(struct watchdog_device *wdd) |
80 | { |
81 | time64_t expires; |
82 | int err; |
83 | |
84 | expires = set_timeout(wdd); |
85 | if (wdt.id) |
86 | err = HYPERVISOR_sched_op(SCHEDOP_watchdog, arg: &wdt); |
87 | else |
88 | err = -ENXIO; |
89 | if (!err) |
90 | wdt_expires = expires; |
91 | |
92 | return err; |
93 | } |
94 | |
95 | static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd) |
96 | { |
97 | return wdt_expires - ktime_get_seconds(); |
98 | } |
99 | |
100 | static struct watchdog_info xen_wdt_info = { |
101 | .identity = DRV_NAME, |
102 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
103 | }; |
104 | |
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, |
111 | }; |
112 | |
113 | static struct watchdog_device xen_wdt_dev = { |
114 | .info = &xen_wdt_info, |
115 | .ops = &xen_wdt_ops, |
116 | .timeout = WATCHDOG_TIMEOUT, |
117 | }; |
118 | |
119 | static int xen_wdt_probe(struct platform_device *pdev) |
120 | { |
121 | struct device *dev = &pdev->dev; |
122 | struct sched_watchdog wd = { .id = ~0 }; |
123 | int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, arg: &wd); |
124 | |
125 | if (ret == -ENOSYS) { |
126 | dev_err(dev, "watchdog not supported by hypervisor\n" ); |
127 | return -ENODEV; |
128 | } |
129 | |
130 | if (ret != -EINVAL) { |
131 | dev_err(dev, "unexpected hypervisor error (%d)\n" , ret); |
132 | return -ENODEV; |
133 | } |
134 | |
135 | watchdog_init_timeout(wdd: &xen_wdt_dev, timeout_parm: timeout, NULL); |
136 | watchdog_set_nowayout(wdd: &xen_wdt_dev, nowayout); |
137 | watchdog_stop_on_reboot(wdd: &xen_wdt_dev); |
138 | watchdog_stop_on_unregister(wdd: &xen_wdt_dev); |
139 | |
140 | ret = devm_watchdog_register_device(dev, &xen_wdt_dev); |
141 | if (ret) |
142 | return ret; |
143 | |
144 | dev_info(dev, "initialized (timeout=%ds, nowayout=%d)\n" , |
145 | xen_wdt_dev.timeout, nowayout); |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state) |
151 | { |
152 | typeof(wdt.id) id = wdt.id; |
153 | int rc = xen_wdt_stop(wdd: &xen_wdt_dev); |
154 | |
155 | wdt.id = id; |
156 | return rc; |
157 | } |
158 | |
159 | static int xen_wdt_resume(struct platform_device *dev) |
160 | { |
161 | if (!wdt.id) |
162 | return 0; |
163 | wdt.id = 0; |
164 | return xen_wdt_start(wdd: &xen_wdt_dev); |
165 | } |
166 | |
167 | static struct platform_driver xen_wdt_driver = { |
168 | .probe = xen_wdt_probe, |
169 | .suspend = xen_wdt_suspend, |
170 | .resume = xen_wdt_resume, |
171 | .driver = { |
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 | |
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 | id: -1, NULL, num: 0); |
189 | if (IS_ERR(ptr: platform_device)) { |
190 | err = PTR_ERR(ptr: 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); |
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" ); |
208 | MODULE_LICENSE("GPL" ); |
209 | |