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