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