1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * AMD Elan SC520 processor Watchdog Timer driver |
4 | * |
5 | * Based on acquirewdt.c by Alan Cox, |
6 | * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> |
7 | * |
8 | * The authors do NOT admit liability nor provide warranty for |
9 | * any of this software. This material is provided "AS-IS" in |
10 | * the hope that it may be useful for others. |
11 | * |
12 | * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> |
13 | * 9/27 - 2001 [Initial release] |
14 | * |
15 | * Additional fixes Alan Cox |
16 | * - Fixed formatting |
17 | * - Removed debug printks |
18 | * - Fixed SMP built kernel deadlock |
19 | * - Switched to private locks not lock_kernel |
20 | * - Used ioremap/writew/readw |
21 | * - Added NOWAYOUT support |
22 | * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> |
23 | * - Change comments |
24 | * - Eliminate fop_llseek |
25 | * - Change CONFIG_WATCHDOG_NOWAYOUT semantics |
26 | * - Add KERN_* tags to printks |
27 | * - fix possible wdt_is_open race |
28 | * - Report proper capabilities in watchdog_info |
29 | * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, |
30 | * GETTIMEOUT, SETOPTIONS} ioctls |
31 | * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> |
32 | * - cleanup of trailing spaces |
33 | * - added extra printk's for startup problems |
34 | * - use module_param |
35 | * - made timeout (the emulated heartbeat) a module_param |
36 | * - made the keepalive ping an internal subroutine |
37 | * 3/27 - 2004 Changes by Sean Young <sean@mess.org> |
38 | * - set MMCR_BASE to 0xfffef000 |
39 | * - CBAR does not need to be read |
40 | * - removed debugging printks |
41 | * |
42 | * This WDT driver is different from most other Linux WDT |
43 | * drivers in that the driver will ping the watchdog by itself, |
44 | * because this particular WDT has a very short timeout (1.6 |
45 | * seconds) and it would be insane to count on any userspace |
46 | * daemon always getting scheduled within that time frame. |
47 | * |
48 | * This driver uses memory mapped IO, and spinlock. |
49 | */ |
50 | |
51 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
52 | |
53 | #include <linux/module.h> |
54 | #include <linux/moduleparam.h> |
55 | #include <linux/types.h> |
56 | #include <linux/timer.h> |
57 | #include <linux/miscdevice.h> |
58 | #include <linux/watchdog.h> |
59 | #include <linux/fs.h> |
60 | #include <linux/ioport.h> |
61 | #include <linux/notifier.h> |
62 | #include <linux/reboot.h> |
63 | #include <linux/init.h> |
64 | #include <linux/jiffies.h> |
65 | #include <linux/io.h> |
66 | #include <linux/uaccess.h> |
67 | |
68 | |
69 | /* |
70 | * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) |
71 | * |
72 | * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s |
73 | * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s |
74 | * |
75 | * We will program the SC520 watchdog for a timeout of 2.01s. |
76 | * If we reset the watchdog every ~250ms we should be safe. |
77 | */ |
78 | |
79 | #define WDT_INTERVAL (HZ/4+1) |
80 | |
81 | /* |
82 | * We must not require too good response from the userspace daemon. |
83 | * Here we require the userspace daemon to send us a heartbeat |
84 | * char to /dev/watchdog every 30 seconds. |
85 | */ |
86 | |
87 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ |
88 | /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ |
89 | static int timeout = WATCHDOG_TIMEOUT; |
90 | module_param(timeout, int, 0); |
91 | MODULE_PARM_DESC(timeout, |
92 | "Watchdog timeout in seconds. (1 <= timeout <= 3600, default=" |
93 | __MODULE_STRING(WATCHDOG_TIMEOUT) ")" ); |
94 | |
95 | static bool nowayout = WATCHDOG_NOWAYOUT; |
96 | module_param(nowayout, bool, 0); |
97 | MODULE_PARM_DESC(nowayout, |
98 | "Watchdog cannot be stopped once started (default=" |
99 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
100 | |
101 | /* |
102 | * AMD Elan SC520 - Watchdog Timer Registers |
103 | */ |
104 | #define MMCR_BASE 0xfffef000 /* The default base address */ |
105 | #define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ |
106 | |
107 | /* WDT Control Register bit definitions */ |
108 | #define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ |
109 | #define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ |
110 | #define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ |
111 | #define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ |
112 | #define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ |
113 | #define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ |
114 | #define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ |
115 | #define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ |
116 | #define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ |
117 | #define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ |
118 | #define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ |
119 | |
120 | static __u16 __iomem *wdtmrctl; |
121 | |
122 | static void wdt_timer_ping(struct timer_list *); |
123 | static DEFINE_TIMER(timer, wdt_timer_ping); |
124 | static unsigned long next_heartbeat; |
125 | static unsigned long wdt_is_open; |
126 | static char wdt_expect_close; |
127 | static DEFINE_SPINLOCK(wdt_spinlock); |
128 | |
129 | /* |
130 | * Whack the dog |
131 | */ |
132 | |
133 | static void wdt_timer_ping(struct timer_list *unused) |
134 | { |
135 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL |
136 | * we agree to ping the WDT |
137 | */ |
138 | if (time_before(jiffies, next_heartbeat)) { |
139 | /* Ping the WDT */ |
140 | spin_lock(lock: &wdt_spinlock); |
141 | writew(val: 0xAAAA, addr: wdtmrctl); |
142 | writew(val: 0x5555, addr: wdtmrctl); |
143 | spin_unlock(lock: &wdt_spinlock); |
144 | |
145 | /* Re-set the timer interval */ |
146 | mod_timer(timer: &timer, expires: jiffies + WDT_INTERVAL); |
147 | } else |
148 | pr_warn("Heartbeat lost! Will not ping the watchdog\n" ); |
149 | } |
150 | |
151 | /* |
152 | * Utility routines |
153 | */ |
154 | |
155 | static void wdt_config(int writeval) |
156 | { |
157 | unsigned long flags; |
158 | |
159 | /* buy some time (ping) */ |
160 | spin_lock_irqsave(&wdt_spinlock, flags); |
161 | readw(addr: wdtmrctl); /* ensure write synchronization */ |
162 | writew(val: 0xAAAA, addr: wdtmrctl); |
163 | writew(val: 0x5555, addr: wdtmrctl); |
164 | /* unlock WDT = make WDT configuration register writable one time */ |
165 | writew(val: 0x3333, addr: wdtmrctl); |
166 | writew(val: 0xCCCC, addr: wdtmrctl); |
167 | /* write WDT configuration register */ |
168 | writew(val: writeval, addr: wdtmrctl); |
169 | spin_unlock_irqrestore(lock: &wdt_spinlock, flags); |
170 | } |
171 | |
172 | static int wdt_startup(void) |
173 | { |
174 | next_heartbeat = jiffies + (timeout * HZ); |
175 | |
176 | /* Start the timer */ |
177 | mod_timer(timer: &timer, expires: jiffies + WDT_INTERVAL); |
178 | |
179 | /* Start the watchdog */ |
180 | wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); |
181 | |
182 | pr_info("Watchdog timer is now enabled\n" ); |
183 | return 0; |
184 | } |
185 | |
186 | static int wdt_turnoff(void) |
187 | { |
188 | /* Stop the timer */ |
189 | del_timer_sync(timer: &timer); |
190 | |
191 | /* Stop the watchdog */ |
192 | wdt_config(writeval: 0); |
193 | |
194 | pr_info("Watchdog timer is now disabled...\n" ); |
195 | return 0; |
196 | } |
197 | |
198 | static int wdt_keepalive(void) |
199 | { |
200 | /* user land ping */ |
201 | next_heartbeat = jiffies + (timeout * HZ); |
202 | return 0; |
203 | } |
204 | |
205 | static int wdt_set_heartbeat(int t) |
206 | { |
207 | if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ |
208 | return -EINVAL; |
209 | |
210 | timeout = t; |
211 | return 0; |
212 | } |
213 | |
214 | /* |
215 | * /dev/watchdog handling |
216 | */ |
217 | |
218 | static ssize_t fop_write(struct file *file, const char __user *buf, |
219 | size_t count, loff_t *ppos) |
220 | { |
221 | /* See if we got the magic character 'V' and reload the timer */ |
222 | if (count) { |
223 | if (!nowayout) { |
224 | size_t ofs; |
225 | |
226 | /* note: just in case someone wrote the magic character |
227 | * five months ago... */ |
228 | wdt_expect_close = 0; |
229 | |
230 | /* now scan */ |
231 | for (ofs = 0; ofs != count; ofs++) { |
232 | char c; |
233 | if (get_user(c, buf + ofs)) |
234 | return -EFAULT; |
235 | if (c == 'V') |
236 | wdt_expect_close = 42; |
237 | } |
238 | } |
239 | |
240 | /* Well, anyhow someone wrote to us, we should |
241 | return that favour */ |
242 | wdt_keepalive(); |
243 | } |
244 | return count; |
245 | } |
246 | |
247 | static int fop_open(struct inode *inode, struct file *file) |
248 | { |
249 | /* Just in case we're already talking to someone... */ |
250 | if (test_and_set_bit(nr: 0, addr: &wdt_is_open)) |
251 | return -EBUSY; |
252 | if (nowayout) |
253 | __module_get(THIS_MODULE); |
254 | |
255 | /* Good, fire up the show */ |
256 | wdt_startup(); |
257 | return stream_open(inode, filp: file); |
258 | } |
259 | |
260 | static int fop_close(struct inode *inode, struct file *file) |
261 | { |
262 | if (wdt_expect_close == 42) |
263 | wdt_turnoff(); |
264 | else { |
265 | pr_crit("Unexpected close, not stopping watchdog!\n" ); |
266 | wdt_keepalive(); |
267 | } |
268 | clear_bit(nr: 0, addr: &wdt_is_open); |
269 | wdt_expect_close = 0; |
270 | return 0; |
271 | } |
272 | |
273 | static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
274 | { |
275 | void __user *argp = (void __user *)arg; |
276 | int __user *p = argp; |
277 | static const struct watchdog_info ident = { |
278 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
279 | | WDIOF_MAGICCLOSE, |
280 | .firmware_version = 1, |
281 | .identity = "SC520" , |
282 | }; |
283 | |
284 | switch (cmd) { |
285 | case WDIOC_GETSUPPORT: |
286 | return copy_to_user(to: argp, from: &ident, n: sizeof(ident)) ? -EFAULT : 0; |
287 | case WDIOC_GETSTATUS: |
288 | case WDIOC_GETBOOTSTATUS: |
289 | return put_user(0, p); |
290 | case WDIOC_SETOPTIONS: |
291 | { |
292 | int new_options, retval = -EINVAL; |
293 | |
294 | if (get_user(new_options, p)) |
295 | return -EFAULT; |
296 | |
297 | if (new_options & WDIOS_DISABLECARD) { |
298 | wdt_turnoff(); |
299 | retval = 0; |
300 | } |
301 | |
302 | if (new_options & WDIOS_ENABLECARD) { |
303 | wdt_startup(); |
304 | retval = 0; |
305 | } |
306 | |
307 | return retval; |
308 | } |
309 | case WDIOC_KEEPALIVE: |
310 | wdt_keepalive(); |
311 | return 0; |
312 | case WDIOC_SETTIMEOUT: |
313 | { |
314 | int new_timeout; |
315 | |
316 | if (get_user(new_timeout, p)) |
317 | return -EFAULT; |
318 | |
319 | if (wdt_set_heartbeat(t: new_timeout)) |
320 | return -EINVAL; |
321 | |
322 | wdt_keepalive(); |
323 | } |
324 | fallthrough; |
325 | case WDIOC_GETTIMEOUT: |
326 | return put_user(timeout, p); |
327 | default: |
328 | return -ENOTTY; |
329 | } |
330 | } |
331 | |
332 | static const struct file_operations wdt_fops = { |
333 | .owner = THIS_MODULE, |
334 | .llseek = no_llseek, |
335 | .write = fop_write, |
336 | .open = fop_open, |
337 | .release = fop_close, |
338 | .unlocked_ioctl = fop_ioctl, |
339 | .compat_ioctl = compat_ptr_ioctl, |
340 | }; |
341 | |
342 | static struct miscdevice wdt_miscdev = { |
343 | .minor = WATCHDOG_MINOR, |
344 | .name = "watchdog" , |
345 | .fops = &wdt_fops, |
346 | }; |
347 | |
348 | /* |
349 | * Notifier for system down |
350 | */ |
351 | |
352 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, |
353 | void *unused) |
354 | { |
355 | if (code == SYS_DOWN || code == SYS_HALT) |
356 | wdt_turnoff(); |
357 | return NOTIFY_DONE; |
358 | } |
359 | |
360 | /* |
361 | * The WDT needs to learn about soft shutdowns in order to |
362 | * turn the timebomb registers off. |
363 | */ |
364 | |
365 | static struct notifier_block wdt_notifier = { |
366 | .notifier_call = wdt_notify_sys, |
367 | }; |
368 | |
369 | static void __exit sc520_wdt_unload(void) |
370 | { |
371 | if (!nowayout) |
372 | wdt_turnoff(); |
373 | |
374 | /* Deregister */ |
375 | misc_deregister(misc: &wdt_miscdev); |
376 | unregister_reboot_notifier(&wdt_notifier); |
377 | iounmap(addr: wdtmrctl); |
378 | } |
379 | |
380 | static int __init sc520_wdt_init(void) |
381 | { |
382 | int rc = -EBUSY; |
383 | |
384 | /* Check that the timeout value is within it's range ; |
385 | if not reset to the default */ |
386 | if (wdt_set_heartbeat(t: timeout)) { |
387 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); |
388 | pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n" , |
389 | WATCHDOG_TIMEOUT); |
390 | } |
391 | |
392 | wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, size: 2); |
393 | if (!wdtmrctl) { |
394 | pr_err("Unable to remap memory\n" ); |
395 | rc = -ENOMEM; |
396 | goto err_out_region2; |
397 | } |
398 | |
399 | rc = register_reboot_notifier(&wdt_notifier); |
400 | if (rc) { |
401 | pr_err("cannot register reboot notifier (err=%d)\n" , rc); |
402 | goto err_out_ioremap; |
403 | } |
404 | |
405 | rc = misc_register(misc: &wdt_miscdev); |
406 | if (rc) { |
407 | pr_err("cannot register miscdev on minor=%d (err=%d)\n" , |
408 | WATCHDOG_MINOR, rc); |
409 | goto err_out_notifier; |
410 | } |
411 | |
412 | pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n" , |
413 | timeout, nowayout); |
414 | |
415 | return 0; |
416 | |
417 | err_out_notifier: |
418 | unregister_reboot_notifier(&wdt_notifier); |
419 | err_out_ioremap: |
420 | iounmap(addr: wdtmrctl); |
421 | err_out_region2: |
422 | return rc; |
423 | } |
424 | |
425 | module_init(sc520_wdt_init); |
426 | module_exit(sc520_wdt_unload); |
427 | |
428 | MODULE_AUTHOR("Scott and Bill Jennings" ); |
429 | MODULE_DESCRIPTION( |
430 | "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor" ); |
431 | MODULE_LICENSE("GPL" ); |
432 | |