1 | /* |
2 | * Watchdog driver for SiByte SB1 SoCs |
3 | * |
4 | * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@lsi.com> |
5 | * |
6 | * This driver is intended to make the second of two hardware watchdogs |
7 | * on the Sibyte 12XX and 11XX SoCs available to the user. There are two |
8 | * such devices available on the SoC, but it seems that there isn't an |
9 | * enumeration class for watchdogs in Linux like there is for RTCs. |
10 | * The second is used rather than the first because it uses IRQ 1, |
11 | * thereby avoiding all that IRQ 0 problematic nonsense. |
12 | * |
13 | * I have not tried this driver on a 1480 processor; it might work |
14 | * just well enough to really screw things up. |
15 | * |
16 | * It is a simple timer, and there is an interrupt that is raised the |
17 | * first time the timer expires. The second time it expires, the chip |
18 | * is reset and there is no way to redirect that NMI. Which could |
19 | * be problematic in some cases where this chip is sitting on the HT |
20 | * bus and has just taken responsibility for providing a cache block. |
21 | * Since the reset can't be redirected to the external reset pin, it is |
22 | * possible that other HT connected processors might hang and not reset. |
23 | * For Linux, a soft reset would probably be even worse than a hard reset. |
24 | * There you have it. |
25 | * |
26 | * The timer takes 23 bits of a 64 bit register (?) as a count value, |
27 | * and decrements the count every microsecond, for a max value of |
28 | * 0x7fffff usec or about 8.3ish seconds. |
29 | * |
30 | * This watchdog borrows some user semantics from the softdog driver, |
31 | * in that if you close the fd, it leaves the watchdog running, unless |
32 | * you previously wrote a 'V' to the fd, in which case it disables |
33 | * the watchdog when you close the fd like some other drivers. |
34 | * |
35 | * Based on various other watchdog drivers, which are probably all |
36 | * loosely based on something Alan Cox wrote years ago. |
37 | * |
38 | * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, |
39 | * All Rights Reserved. |
40 | * |
41 | * This program is free software; you can redistribute it and/or |
42 | * modify it under the terms of the GNU General Public License |
43 | * version 1 or 2 as published by the Free Software Foundation. |
44 | * |
45 | */ |
46 | |
47 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
48 | |
49 | #include <linux/module.h> |
50 | #include <linux/io.h> |
51 | #include <linux/uaccess.h> |
52 | #include <linux/fs.h> |
53 | #include <linux/reboot.h> |
54 | #include <linux/miscdevice.h> |
55 | #include <linux/watchdog.h> |
56 | #include <linux/interrupt.h> |
57 | |
58 | #include <asm/sibyte/sb1250.h> |
59 | #include <asm/sibyte/sb1250_regs.h> |
60 | #include <asm/sibyte/sb1250_int.h> |
61 | #include <asm/sibyte/sb1250_scd.h> |
62 | |
63 | static DEFINE_SPINLOCK(sbwd_lock); |
64 | |
65 | /* |
66 | * set the initial count value of a timer |
67 | * |
68 | * wdog is the iomem address of the cfg register |
69 | */ |
70 | static void sbwdog_set(char __iomem *wdog, unsigned long t) |
71 | { |
72 | spin_lock(lock: &sbwd_lock); |
73 | __raw_writeb(val: 0, addr: wdog); |
74 | __raw_writeq(val: t & 0x7fffffUL, addr: wdog - 0x10); |
75 | spin_unlock(lock: &sbwd_lock); |
76 | } |
77 | |
78 | /* |
79 | * cause the timer to [re]load it's initial count and start counting |
80 | * all over again |
81 | * |
82 | * wdog is the iomem address of the cfg register |
83 | */ |
84 | static void sbwdog_pet(char __iomem *wdog) |
85 | { |
86 | spin_lock(lock: &sbwd_lock); |
87 | __raw_writeb(__raw_readb(addr: wdog) | 1, addr: wdog); |
88 | spin_unlock(lock: &sbwd_lock); |
89 | } |
90 | |
91 | static unsigned long sbwdog_gate; /* keeps it to one thread only */ |
92 | static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0)); |
93 | static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1)); |
94 | static unsigned long timeout = 0x7fffffUL; /* useconds: 8.3ish secs. */ |
95 | static int expect_close; |
96 | |
97 | static const struct watchdog_info ident = { |
98 | .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | |
99 | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
100 | .identity = "SiByte Watchdog" , |
101 | }; |
102 | |
103 | /* |
104 | * Allow only a single thread to walk the dog |
105 | */ |
106 | static int sbwdog_open(struct inode *inode, struct file *file) |
107 | { |
108 | stream_open(inode, filp: file); |
109 | if (test_and_set_bit(nr: 0, addr: &sbwdog_gate)) |
110 | return -EBUSY; |
111 | __module_get(THIS_MODULE); |
112 | |
113 | /* |
114 | * Activate the timer |
115 | */ |
116 | sbwdog_set(wdog: user_dog, t: timeout); |
117 | __raw_writeb(val: 1, addr: user_dog); |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | /* |
123 | * Put the dog back in the kennel. |
124 | */ |
125 | static int sbwdog_release(struct inode *inode, struct file *file) |
126 | { |
127 | if (expect_close == 42) { |
128 | __raw_writeb(val: 0, addr: user_dog); |
129 | module_put(THIS_MODULE); |
130 | } else { |
131 | pr_crit("%s: Unexpected close, not stopping watchdog!\n" , |
132 | ident.identity); |
133 | sbwdog_pet(wdog: user_dog); |
134 | } |
135 | clear_bit(nr: 0, addr: &sbwdog_gate); |
136 | expect_close = 0; |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | /* |
142 | * 42 - the answer |
143 | */ |
144 | static ssize_t sbwdog_write(struct file *file, const char __user *data, |
145 | size_t len, loff_t *ppos) |
146 | { |
147 | int i; |
148 | |
149 | if (len) { |
150 | /* |
151 | * restart the timer |
152 | */ |
153 | expect_close = 0; |
154 | |
155 | for (i = 0; i != len; i++) { |
156 | char c; |
157 | |
158 | if (get_user(c, data + i)) |
159 | return -EFAULT; |
160 | if (c == 'V') |
161 | expect_close = 42; |
162 | } |
163 | sbwdog_pet(wdog: user_dog); |
164 | } |
165 | |
166 | return len; |
167 | } |
168 | |
169 | static long sbwdog_ioctl(struct file *file, unsigned int cmd, |
170 | unsigned long arg) |
171 | { |
172 | int ret = -ENOTTY; |
173 | unsigned long time; |
174 | void __user *argp = (void __user *)arg; |
175 | int __user *p = argp; |
176 | |
177 | switch (cmd) { |
178 | case WDIOC_GETSUPPORT: |
179 | ret = copy_to_user(to: argp, from: &ident, n: sizeof(ident)) ? -EFAULT : 0; |
180 | break; |
181 | |
182 | case WDIOC_GETSTATUS: |
183 | case WDIOC_GETBOOTSTATUS: |
184 | ret = put_user(0, p); |
185 | break; |
186 | |
187 | case WDIOC_KEEPALIVE: |
188 | sbwdog_pet(wdog: user_dog); |
189 | ret = 0; |
190 | break; |
191 | |
192 | case WDIOC_SETTIMEOUT: |
193 | ret = get_user(time, p); |
194 | if (ret) |
195 | break; |
196 | |
197 | time *= 1000000; |
198 | if (time > 0x7fffffUL) { |
199 | ret = -EINVAL; |
200 | break; |
201 | } |
202 | timeout = time; |
203 | sbwdog_set(wdog: user_dog, t: timeout); |
204 | sbwdog_pet(wdog: user_dog); |
205 | fallthrough; |
206 | |
207 | case WDIOC_GETTIMEOUT: |
208 | /* |
209 | * get the remaining count from the ... count register |
210 | * which is 1*8 before the config register |
211 | */ |
212 | ret = put_user((u32)__raw_readq(user_dog - 8) / 1000000, p); |
213 | break; |
214 | } |
215 | return ret; |
216 | } |
217 | |
218 | /* |
219 | * Notifier for system down |
220 | */ |
221 | static int sbwdog_notify_sys(struct notifier_block *this, unsigned long code, |
222 | void *erf) |
223 | { |
224 | if (code == SYS_DOWN || code == SYS_HALT) { |
225 | /* |
226 | * sit and sit |
227 | */ |
228 | __raw_writeb(val: 0, addr: user_dog); |
229 | __raw_writeb(val: 0, addr: kern_dog); |
230 | } |
231 | |
232 | return NOTIFY_DONE; |
233 | } |
234 | |
235 | static const struct file_operations sbwdog_fops = { |
236 | .owner = THIS_MODULE, |
237 | .llseek = no_llseek, |
238 | .write = sbwdog_write, |
239 | .unlocked_ioctl = sbwdog_ioctl, |
240 | .compat_ioctl = compat_ptr_ioctl, |
241 | .open = sbwdog_open, |
242 | .release = sbwdog_release, |
243 | }; |
244 | |
245 | static struct miscdevice sbwdog_miscdev = { |
246 | .minor = WATCHDOG_MINOR, |
247 | .name = "watchdog" , |
248 | .fops = &sbwdog_fops, |
249 | }; |
250 | |
251 | static struct notifier_block sbwdog_notifier = { |
252 | .notifier_call = sbwdog_notify_sys, |
253 | }; |
254 | |
255 | /* |
256 | * interrupt handler |
257 | * |
258 | * doesn't do a whole lot for user, but oh so cleverly written so kernel |
259 | * code can use it to re-up the watchdog, thereby saving the kernel from |
260 | * having to create and maintain a timer, just to tickle another timer, |
261 | * which is just so wrong. |
262 | */ |
263 | irqreturn_t sbwdog_interrupt(int irq, void *addr) |
264 | { |
265 | unsigned long wd_init; |
266 | char *wd_cfg_reg = (char *)addr; |
267 | u8 cfg; |
268 | |
269 | cfg = __raw_readb(addr: wd_cfg_reg); |
270 | wd_init = __raw_readq(addr: wd_cfg_reg - 8) & 0x7fffff; |
271 | |
272 | /* |
273 | * if it's the second watchdog timer, it's for those users |
274 | */ |
275 | if (wd_cfg_reg == user_dog) |
276 | pr_crit("%s in danger of initiating system reset " |
277 | "in %ld.%01ld seconds\n" , |
278 | ident.identity, |
279 | wd_init / 1000000, (wd_init / 100000) % 10); |
280 | else |
281 | cfg |= 1; |
282 | |
283 | __raw_writeb(val: cfg, addr: wd_cfg_reg); |
284 | |
285 | return IRQ_HANDLED; |
286 | } |
287 | |
288 | static int __init sbwdog_init(void) |
289 | { |
290 | int ret; |
291 | |
292 | /* |
293 | * register a reboot notifier |
294 | */ |
295 | ret = register_reboot_notifier(&sbwdog_notifier); |
296 | if (ret) { |
297 | pr_err("%s: cannot register reboot notifier (err=%d)\n" , |
298 | ident.identity, ret); |
299 | return ret; |
300 | } |
301 | |
302 | /* |
303 | * get the resources |
304 | */ |
305 | |
306 | ret = request_irq(irq: 1, handler: sbwdog_interrupt, IRQF_SHARED, |
307 | name: ident.identity, dev: (void *)user_dog); |
308 | if (ret) { |
309 | pr_err("%s: failed to request irq 1 - %d\n" , |
310 | ident.identity, ret); |
311 | goto out; |
312 | } |
313 | |
314 | ret = misc_register(misc: &sbwdog_miscdev); |
315 | if (ret == 0) { |
316 | pr_info("%s: timeout is %ld.%ld secs\n" , |
317 | ident.identity, |
318 | timeout / 1000000, (timeout / 100000) % 10); |
319 | return 0; |
320 | } |
321 | free_irq(1, (void *)user_dog); |
322 | out: |
323 | unregister_reboot_notifier(&sbwdog_notifier); |
324 | |
325 | return ret; |
326 | } |
327 | |
328 | static void __exit sbwdog_exit(void) |
329 | { |
330 | misc_deregister(misc: &sbwdog_miscdev); |
331 | free_irq(1, (void *)user_dog); |
332 | unregister_reboot_notifier(&sbwdog_notifier); |
333 | } |
334 | |
335 | module_init(sbwdog_init); |
336 | module_exit(sbwdog_exit); |
337 | |
338 | MODULE_AUTHOR("Andrew Sharp <andy.sharp@lsi.com>" ); |
339 | MODULE_DESCRIPTION("SiByte Watchdog" ); |
340 | |
341 | module_param(timeout, ulong, 0); |
342 | MODULE_PARM_DESC(timeout, |
343 | "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)" ); |
344 | |
345 | MODULE_LICENSE("GPL" ); |
346 | |
347 | /* |
348 | * example code that can be put in a platform code area to utilize the |
349 | * first watchdog timer for the kernels own purpose. |
350 | |
351 | void platform_wd_setup(void) |
352 | { |
353 | int ret; |
354 | |
355 | ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED, |
356 | "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0)); |
357 | if (ret) { |
358 | pr_crit("Watchdog IRQ zero(0) failed to be requested - %d\n", ret); |
359 | } |
360 | } |
361 | |
362 | |
363 | */ |
364 | |