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");
40
41static int margin = 60;
42module_param(margin, int, 0);
43MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
44
45static bool nowayout = WATCHDOG_NOWAYOUT;
46module_param(nowayout, bool, 0);
47MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
48
49static u16 wdto_restart;
50static char expect_close;
51static unsigned long open_lock;
52static DEFINE_SPINLOCK(scx_lock);
53
54
55#define W_ENABLE 0x00fa
56#define W_DISABLE 0x0000
57
58
59#define W_SCALE (32768/1024)
60
61static void scx200_wdt_ping(void)
62{
63 spin_lock(&scx_lock);
64 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
65 spin_unlock(&scx_lock);
66}
67
68static void scx200_wdt_update_margin(void)
69{
70 pr_info("timer margin %d seconds\n", margin);
71 wdto_restart = margin * W_SCALE;
72}
73
74static void scx200_wdt_enable(void)
75{
76 pr_debug("enabling watchdog timer, wdto_restart = %d\n", 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 pr_debug("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 pr_warn("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 WDIOF_MAGICCLOSE,
168 };
169 int new_margin;
170
171 switch (cmd) {
172 case WDIOC_GETSUPPORT:
173 if (copy_to_user(argp, &ident, sizeof(ident)))
174 return -EFAULT;
175 return 0;
176 case WDIOC_GETSTATUS:
177 case WDIOC_GETBOOTSTATUS:
178 if (put_user(0, p))
179 return -EFAULT;
180 return 0;
181 case WDIOC_KEEPALIVE:
182 scx200_wdt_ping();
183 return 0;
184 case WDIOC_SETTIMEOUT:
185 if (get_user(new_margin, p))
186 return -EFAULT;
187 if (new_margin < 1)
188 return -EINVAL;
189 margin = new_margin;
190 scx200_wdt_update_margin();
191 scx200_wdt_ping();
192 case WDIOC_GETTIMEOUT:
193 if (put_user(margin, p))
194 return -EFAULT;
195 return 0;
196 default:
197 return -ENOTTY;
198 }
199}
200
201static const struct file_operations scx200_wdt_fops = {
202 .owner = THIS_MODULE,
203 .llseek = no_llseek,
204 .write = scx200_wdt_write,
205 .unlocked_ioctl = scx200_wdt_ioctl,
206 .open = scx200_wdt_open,
207 .release = scx200_wdt_release,
208};
209
210static struct miscdevice scx200_wdt_miscdev = {
211 .minor = WATCHDOG_MINOR,
212 .name = "watchdog",
213 .fops = &scx200_wdt_fops,
214};
215
216static int __init scx200_wdt_init(void)
217{
218 int r;
219
220 pr_debug("NatSemi SCx200 Watchdog Driver\n");
221
222
223 if (!scx200_cb_present())
224 return -ENODEV;
225
226 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
227 SCx200_WDT_SIZE,
228 "NatSemi SCx200 Watchdog")) {
229 pr_warn("watchdog I/O region busy\n");
230 return -EBUSY;
231 }
232
233 scx200_wdt_update_margin();
234 scx200_wdt_disable();
235
236 r = register_reboot_notifier(&scx200_wdt_notifier);
237 if (r) {
238 pr_err("unable to register reboot notifier\n");
239 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
240 SCx200_WDT_SIZE);
241 return r;
242 }
243
244 r = misc_register(&scx200_wdt_miscdev);
245 if (r) {
246 unregister_reboot_notifier(&scx200_wdt_notifier);
247 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
248 SCx200_WDT_SIZE);
249 return r;
250 }
251
252 return 0;
253}
254
255static void __exit scx200_wdt_cleanup(void)
256{
257 misc_deregister(&scx200_wdt_miscdev);
258 unregister_reboot_notifier(&scx200_wdt_notifier);
259 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
260 SCx200_WDT_SIZE);
261}
262
263module_init(scx200_wdt_init);
264module_exit(scx200_wdt_cleanup);
265