1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include <linux/module.h>
21#include <linux/moduleparam.h>
22#include <linux/init.h>
23#include <linux/miscdevice.h>
24#include <linux/watchdog.h>
25#include <linux/notifier.h>
26#include <linux/reboot.h>
27#include <linux/fs.h>
28#include <linux/ioport.h>
29#include <linux/scx200.h>
30#include <linux/uaccess.h>
31#include <linux/io.h>
32
33#define NAME "scx200_wdt"
34
35MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
36MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
37MODULE_LICENSE("GPL");
38MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
39
40static int margin = 60;
41module_param(margin, int, 0);
42MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
43
44static int nowayout = WATCHDOG_NOWAYOUT;
45module_param(nowayout, int, 0);
46MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
47
48static u16 wdto_restart;
49static char expect_close;
50static unsigned long open_lock;
51static DEFINE_SPINLOCK(scx_lock);
52
53
54#define W_ENABLE 0x00fa
55#define W_DISABLE 0x0000
56
57
58#define W_SCALE (32768/1024)
59
60static void scx200_wdt_ping(void)
61{
62 spin_lock(&scx_lock);
63 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
64 spin_unlock(&scx_lock);
65}
66
67static void scx200_wdt_update_margin(void)
68{
69 printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
70 wdto_restart = margin * W_SCALE;
71}
72
73static void scx200_wdt_enable(void)
74{
75 printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n",
76 wdto_restart);
77
78 spin_lock(&scx_lock);
79 outw(0, scx200_cb_base + SCx200_WDT_WDTO);
80 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
81 outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
82 spin_unlock(&scx_lock);
83
84 scx200_wdt_ping();
85}
86
87static void scx200_wdt_disable(void)
88{
89 printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
90
91 spin_lock(&scx_lock);
92 outw(0, scx200_cb_base + SCx200_WDT_WDTO);
93 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
94 outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
95 spin_unlock(&scx_lock);
96}
97
98static int scx200_wdt_open(struct inode *inode, struct file *file)
99{
100
101 if (test_and_set_bit(0, &open_lock))
102 return -EBUSY;
103 scx200_wdt_enable();
104
105 return nonseekable_open(inode, file);
106}
107
108static int scx200_wdt_release(struct inode *inode, struct file *file)
109{
110 if (expect_close != 42)
111 printk(KERN_WARNING NAME
112 ": watchdog device closed unexpectedly, "
113 "will not disable the watchdog timer\n");
114 else if (!nowayout)
115 scx200_wdt_disable();
116 expect_close = 0;
117 clear_bit(0, &open_lock);
118
119 return 0;
120}
121
122static int scx200_wdt_notify_sys(struct notifier_block *this,
123 unsigned long code, void *unused)
124{
125 if (code == SYS_HALT || code == SYS_POWER_OFF)
126 if (!nowayout)
127 scx200_wdt_disable();
128
129 return NOTIFY_DONE;
130}
131
132static struct notifier_block scx200_wdt_notifier = {
133 .notifier_call = scx200_wdt_notify_sys,
134};
135
136static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
137 size_t len, loff_t *ppos)
138{
139
140 if (len) {
141 size_t i;
142
143 scx200_wdt_ping();
144
145 expect_close = 0;
146 for (i = 0; i < len; ++i) {
147 char c;
148 if (get_user(c, data + i))
149 return -EFAULT;
150 if (c == 'V')
151 expect_close = 42;
152 }
153
154 return len;
155 }
156
157 return 0;
158}
159
160static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
161 unsigned long arg)
162{
163 void __user *argp = (void __user *)arg;
164 int __user *p = argp;
165 static const struct watchdog_info ident = {
166 .identity = "NatSemi SCx200 Watchdog",
167 .firmware_version = 1,
168 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
169 WDIOF_MAGICCLOSE,
170 };
171 int new_margin;
172
173 switch (cmd) {
174 case WDIOC_GETSUPPORT:
175 if (copy_to_user(argp, &ident, sizeof(ident)))
176 return -EFAULT;
177 return 0;
178 case WDIOC_GETSTATUS:
179 case WDIOC_GETBOOTSTATUS:
180 if (put_user(0, p))
181 return -EFAULT;
182 return 0;
183 case WDIOC_KEEPALIVE:
184 scx200_wdt_ping();
185 return 0;
186 case WDIOC_SETTIMEOUT:
187 if (get_user(new_margin, p))
188 return -EFAULT;
189 if (new_margin < 1)
190 return -EINVAL;
191 margin = new_margin;
192 scx200_wdt_update_margin();
193 scx200_wdt_ping();
194 case WDIOC_GETTIMEOUT:
195 if (put_user(margin, p))
196 return -EFAULT;
197 return 0;
198 default:
199 return -ENOTTY;
200 }
201}
202
203static const struct file_operations scx200_wdt_fops = {
204 .owner = THIS_MODULE,
205 .llseek = no_llseek,
206 .write = scx200_wdt_write,
207 .unlocked_ioctl = scx200_wdt_ioctl,
208 .open = scx200_wdt_open,
209 .release = scx200_wdt_release,
210};
211
212static struct miscdevice scx200_wdt_miscdev = {
213 .minor = WATCHDOG_MINOR,
214 .name = "watchdog",
215 .fops = &scx200_wdt_fops,
216};
217
218static int __init scx200_wdt_init(void)
219{
220 int r;
221
222 printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
223
224
225 if (!scx200_cb_present())
226 return -ENODEV;
227
228 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
229 SCx200_WDT_SIZE,
230 "NatSemi SCx200 Watchdog")) {
231 printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
232 return -EBUSY;
233 }
234
235 scx200_wdt_update_margin();
236 scx200_wdt_disable();
237
238 r = register_reboot_notifier(&scx200_wdt_notifier);
239 if (r) {
240 printk(KERN_ERR NAME ": unable to register reboot notifier");
241 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
242 SCx200_WDT_SIZE);
243 return r;
244 }
245
246 r = misc_register(&scx200_wdt_miscdev);
247 if (r) {
248 unregister_reboot_notifier(&scx200_wdt_notifier);
249 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
250 SCx200_WDT_SIZE);
251 return r;
252 }
253
254 return 0;
255}
256
257static void __exit scx200_wdt_cleanup(void)
258{
259 misc_deregister(&scx200_wdt_miscdev);
260 unregister_reboot_notifier(&scx200_wdt_notifier);
261 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
262 SCx200_WDT_SIZE);
263}
264
265module_init(scx200_wdt_init);
266module_exit(scx200_wdt_cleanup);
267
268
269
270
271
272
273
274