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 ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
112 else if (!nowayout)
113 scx200_wdt_disable();
114 expect_close = 0;
115 clear_bit(0, &open_lock);
116
117 return 0;
118}
119
120static int scx200_wdt_notify_sys(struct notifier_block *this,
121 unsigned long code, void *unused)
122{
123 if (code == SYS_HALT || code == SYS_POWER_OFF)
124 if (!nowayout)
125 scx200_wdt_disable();
126
127 return NOTIFY_DONE;
128}
129
130static struct notifier_block scx200_wdt_notifier = {
131 .notifier_call = scx200_wdt_notify_sys,
132};
133
134static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
135 size_t len, loff_t *ppos)
136{
137
138 if (len) {
139 size_t i;
140
141 scx200_wdt_ping();
142
143 expect_close = 0;
144 for (i = 0; i < len; ++i) {
145 char c;
146 if (get_user(c, data + i))
147 return -EFAULT;
148 if (c == 'V')
149 expect_close = 42;
150 }
151
152 return len;
153 }
154
155 return 0;
156}
157
158static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
159 unsigned long arg)
160{
161 void __user *argp = (void __user *)arg;
162 int __user *p = argp;
163 static const struct watchdog_info ident = {
164 .identity = "NatSemi SCx200 Watchdog",
165 .firmware_version = 1,
166 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
167 };
168 int new_margin;
169
170 switch (cmd) {
171 case WDIOC_GETSUPPORT:
172 if (copy_to_user(argp, &ident, sizeof(ident)))
173 return -EFAULT;
174 return 0;
175 case WDIOC_GETSTATUS:
176 case WDIOC_GETBOOTSTATUS:
177 if (put_user(0, p))
178 return -EFAULT;
179 return 0;
180 case WDIOC_KEEPALIVE:
181 scx200_wdt_ping();
182 return 0;
183 case WDIOC_SETTIMEOUT:
184 if (get_user(new_margin, p))
185 return -EFAULT;
186 if (new_margin < 1)
187 return -EINVAL;
188 margin = new_margin;
189 scx200_wdt_update_margin();
190 scx200_wdt_ping();
191 case WDIOC_GETTIMEOUT:
192 if (put_user(margin, p))
193 return -EFAULT;
194 return 0;
195 default:
196 return -ENOTTY;
197 }
198}
199
200static const struct file_operations scx200_wdt_fops = {
201 .owner = THIS_MODULE,
202 .llseek = no_llseek,
203 .write = scx200_wdt_write,
204 .unlocked_ioctl = scx200_wdt_ioctl,
205 .open = scx200_wdt_open,
206 .release = scx200_wdt_release,
207};
208
209static struct miscdevice scx200_wdt_miscdev = {
210 .minor = WATCHDOG_MINOR,
211 .name = "watchdog",
212 .fops = &scx200_wdt_fops,
213};
214
215static int __init scx200_wdt_init(void)
216{
217 int r;
218
219 printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
220
221
222 if (!scx200_cb_present())
223 return -ENODEV;
224
225 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
226 SCx200_WDT_SIZE,
227 "NatSemi SCx200 Watchdog")) {
228 printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
229 return -EBUSY;
230 }
231
232 scx200_wdt_update_margin();
233 scx200_wdt_disable();
234
235 r = register_reboot_notifier(&scx200_wdt_notifier);
236 if (r) {
237 printk(KERN_ERR NAME ": unable to register reboot notifier");
238 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
239 SCx200_WDT_SIZE);
240 return r;
241 }
242
243 r = misc_register(&scx200_wdt_miscdev);
244 if (r) {
245 unregister_reboot_notifier(&scx200_wdt_notifier);
246 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
247 SCx200_WDT_SIZE);
248 return r;
249 }
250
251 return 0;
252}
253
254static void __exit scx200_wdt_cleanup(void)
255{
256 misc_deregister(&scx200_wdt_miscdev);
257 unregister_reboot_notifier(&scx200_wdt_notifier);
258 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
259 SCx200_WDT_SIZE);
260}
261
262module_init(scx200_wdt_init);
263module_exit(scx200_wdt_cleanup);
264
265
266
267
268
269
270
271