Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
alim7101_wdt.c
Go to the documentation of this file.
1 /*
2  * ALi M7101 PMU Computer Watchdog Timer driver
3  *
4  * Based on w83877f_wdt.c by Scott Jennings <[email protected]>
5  * and the Cobalt kernel WDT timer driver by Tim Hockin
7  *
8  * (c)2002 Steve Hill <[email protected]>
9  *
10  * This WDT driver is different from most other Linux WDT
11  * drivers in that the driver will ping the watchdog by itself,
12  * because this particular WDT has a very short timeout (1.6
13  * seconds) and it would be insane to count on any userspace
14  * daemon always getting scheduled within that time frame.
15  *
16  * Additions:
17  * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
18  * found on very old cobalt hardware.
19  * -- Mike Waychison <[email protected]>
20  */
21 
22 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
23 
24 #include <linux/module.h>
25 #include <linux/moduleparam.h>
26 #include <linux/types.h>
27 #include <linux/timer.h>
28 #include <linux/miscdevice.h>
29 #include <linux/watchdog.h>
30 #include <linux/ioport.h>
31 #include <linux/notifier.h>
32 #include <linux/reboot.h>
33 #include <linux/init.h>
34 #include <linux/fs.h>
35 #include <linux/pci.h>
36 #include <linux/io.h>
37 #include <linux/uaccess.h>
38 
39 
40 #define WDT_ENABLE 0x9C
41 #define WDT_DISABLE 0x8C
42 
43 #define ALI_7101_WDT 0x92
44 #define ALI_7101_GPIO 0x7D
45 #define ALI_7101_GPIO_O 0x7E
46 #define ALI_WDT_ARM 0x01
47 
48 /*
49  * We're going to use a 1 second timeout.
50  * If we reset the watchdog every ~250ms we should be safe. */
51 
52 #define WDT_INTERVAL (HZ/4+1)
53 
54 /*
55  * We must not require too good response from the userspace daemon.
56  * Here we require the userspace daemon to send us a heartbeat
57  * char to /dev/watchdog every 30 seconds.
58  */
59 
60 #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
61 /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
62 static int timeout = WATCHDOG_TIMEOUT;
63 module_param(timeout, int, 0);
64 MODULE_PARM_DESC(timeout,
65  "Watchdog timeout in seconds. (1<=timeout<=3600, default="
67 
68 static int use_gpio; /* Use the pic (for a1d revision alim7101) */
69 module_param(use_gpio, int, 0);
70 MODULE_PARM_DESC(use_gpio,
71  "Use the gpio watchdog (required by old cobalt boards).");
72 
73 static void wdt_timer_ping(unsigned long);
74 static DEFINE_TIMER(timer, wdt_timer_ping, 0, 1);
75 static unsigned long next_heartbeat;
76 static unsigned long wdt_is_open;
77 static char wdt_expect_close;
78 static struct pci_dev *alim7101_pmu;
79 
80 static bool nowayout = WATCHDOG_NOWAYOUT;
81 module_param(nowayout, bool, 0);
82 MODULE_PARM_DESC(nowayout,
83  "Watchdog cannot be stopped once started (default="
85 
86 /*
87  * Whack the dog
88  */
89 
90 static void wdt_timer_ping(unsigned long data)
91 {
92  /* If we got a heartbeat pulse within the WDT_US_INTERVAL
93  * we agree to ping the WDT
94  */
95  char tmp;
96 
97  if (time_before(jiffies, next_heartbeat)) {
98  /* Ping the WDT (this is actually a disarm/arm sequence) */
99  pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
100  pci_write_config_byte(alim7101_pmu,
101  ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
102  pci_write_config_byte(alim7101_pmu,
103  ALI_7101_WDT, (tmp | ALI_WDT_ARM));
104  if (use_gpio) {
105  pci_read_config_byte(alim7101_pmu,
106  ALI_7101_GPIO_O, &tmp);
107  pci_write_config_byte(alim7101_pmu,
108  ALI_7101_GPIO_O, tmp | 0x20);
109  pci_write_config_byte(alim7101_pmu,
110  ALI_7101_GPIO_O, tmp & ~0x20);
111  }
112  } else {
113  pr_warn("Heartbeat lost! Will not ping the watchdog\n");
114  }
115  /* Re-set the timer interval */
116  mod_timer(&timer, jiffies + WDT_INTERVAL);
117 }
118 
119 /*
120  * Utility routines
121  */
122 
123 static void wdt_change(int writeval)
124 {
125  char tmp;
126 
127  pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
128  if (writeval == WDT_ENABLE) {
129  pci_write_config_byte(alim7101_pmu,
130  ALI_7101_WDT, (tmp | ALI_WDT_ARM));
131  if (use_gpio) {
132  pci_read_config_byte(alim7101_pmu,
133  ALI_7101_GPIO_O, &tmp);
134  pci_write_config_byte(alim7101_pmu,
135  ALI_7101_GPIO_O, tmp & ~0x20);
136  }
137 
138  } else {
139  pci_write_config_byte(alim7101_pmu,
140  ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
141  if (use_gpio) {
142  pci_read_config_byte(alim7101_pmu,
143  ALI_7101_GPIO_O, &tmp);
144  pci_write_config_byte(alim7101_pmu,
145  ALI_7101_GPIO_O, tmp | 0x20);
146  }
147  }
148 }
149 
150 static void wdt_startup(void)
151 {
152  next_heartbeat = jiffies + (timeout * HZ);
153 
154  /* We must enable before we kick off the timer in case the timer
155  occurs as we ping it */
156 
158 
159  /* Start the timer */
160  mod_timer(&timer, jiffies + WDT_INTERVAL);
161 
162  pr_info("Watchdog timer is now enabled\n");
163 }
164 
165 static void wdt_turnoff(void)
166 {
167  /* Stop the timer */
170  pr_info("Watchdog timer is now disabled...\n");
171 }
172 
173 static void wdt_keepalive(void)
174 {
175  /* user land ping */
176  next_heartbeat = jiffies + (timeout * HZ);
177 }
178 
179 /*
180  * /dev/watchdog handling
181  */
182 
183 static ssize_t fop_write(struct file *file, const char __user *buf,
184  size_t count, loff_t *ppos)
185 {
186  /* See if we got the magic character 'V' and reload the timer */
187  if (count) {
188  if (!nowayout) {
189  size_t ofs;
190 
191  /* note: just in case someone wrote the magic character
192  * five months ago... */
193  wdt_expect_close = 0;
194 
195  /* now scan */
196  for (ofs = 0; ofs != count; ofs++) {
197  char c;
198  if (get_user(c, buf + ofs))
199  return -EFAULT;
200  if (c == 'V')
201  wdt_expect_close = 42;
202  }
203  }
204  /* someone wrote to us, we should restart timer */
205  wdt_keepalive();
206  }
207  return count;
208 }
209 
210 static int fop_open(struct inode *inode, struct file *file)
211 {
212  /* Just in case we're already talking to someone... */
213  if (test_and_set_bit(0, &wdt_is_open))
214  return -EBUSY;
215  /* Good, fire up the show */
216  wdt_startup();
217  return nonseekable_open(inode, file);
218 }
219 
220 static int fop_close(struct inode *inode, struct file *file)
221 {
222  if (wdt_expect_close == 42)
223  wdt_turnoff();
224  else {
225  /* wim: shouldn't there be a: del_timer(&timer); */
226  pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
227  }
228  clear_bit(0, &wdt_is_open);
229  wdt_expect_close = 0;
230  return 0;
231 }
232 
233 static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
234 {
235  void __user *argp = (void __user *)arg;
236  int __user *p = argp;
237  static const struct watchdog_info ident = {
240  .firmware_version = 1,
241  .identity = "ALiM7101",
242  };
243 
244  switch (cmd) {
245  case WDIOC_GETSUPPORT:
246  return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
247  case WDIOC_GETSTATUS:
248  case WDIOC_GETBOOTSTATUS:
249  return put_user(0, p);
250  case WDIOC_SETOPTIONS:
251  {
252  int new_options, retval = -EINVAL;
253 
254  if (get_user(new_options, p))
255  return -EFAULT;
256  if (new_options & WDIOS_DISABLECARD) {
257  wdt_turnoff();
258  retval = 0;
259  }
260  if (new_options & WDIOS_ENABLECARD) {
261  wdt_startup();
262  retval = 0;
263  }
264  return retval;
265  }
266  case WDIOC_KEEPALIVE:
267  wdt_keepalive();
268  return 0;
269  case WDIOC_SETTIMEOUT:
270  {
271  int new_timeout;
272 
273  if (get_user(new_timeout, p))
274  return -EFAULT;
275  /* arbitrary upper limit */
276  if (new_timeout < 1 || new_timeout > 3600)
277  return -EINVAL;
278  timeout = new_timeout;
279  wdt_keepalive();
280  /* Fall through */
281  }
282  case WDIOC_GETTIMEOUT:
283  return put_user(timeout, p);
284  default:
285  return -ENOTTY;
286  }
287 }
288 
289 static const struct file_operations wdt_fops = {
290  .owner = THIS_MODULE,
291  .llseek = no_llseek,
292  .write = fop_write,
293  .open = fop_open,
294  .release = fop_close,
295  .unlocked_ioctl = fop_ioctl,
296 };
297 
298 static struct miscdevice wdt_miscdev = {
299  .minor = WATCHDOG_MINOR,
300  .name = "watchdog",
301  .fops = &wdt_fops,
302 };
303 
304 /*
305  * Notifier for system down
306  */
307 
308 static int wdt_notify_sys(struct notifier_block *this,
309  unsigned long code, void *unused)
310 {
311  if (code == SYS_DOWN || code == SYS_HALT)
312  wdt_turnoff();
313 
314  if (code == SYS_RESTART) {
315  /*
316  * Cobalt devices have no way of rebooting themselves other
317  * than getting the watchdog to pull reset, so we restart the
318  * watchdog on reboot with no heartbeat
319  */
321  pr_info("Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second\n");
322  }
323  return NOTIFY_DONE;
324 }
325 
326 /*
327  * The WDT needs to learn about soft shutdowns in order to
328  * turn the timebomb registers off.
329  */
330 
331 static struct notifier_block wdt_notifier = {
332  .notifier_call = wdt_notify_sys,
333 };
334 
335 static void __exit alim7101_wdt_unload(void)
336 {
337  wdt_turnoff();
338  /* Deregister */
339  misc_deregister(&wdt_miscdev);
340  unregister_reboot_notifier(&wdt_notifier);
341  pci_dev_put(alim7101_pmu);
342 }
343 
344 static int __init alim7101_wdt_init(void)
345 {
346  int rc = -EBUSY;
347  struct pci_dev *ali1543_south;
348  char tmp;
349 
350  pr_info("Steve Hill <[email protected]>\n");
352  NULL);
353  if (!alim7101_pmu) {
354  pr_info("ALi M7101 PMU not present - WDT not set\n");
355  return -EBUSY;
356  }
357 
358  /* Set the WDT in the PMU to 1 second */
359  pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
360 
362  NULL);
363  if (!ali1543_south) {
364  pr_info("ALi 1543 South-Bridge not present - WDT not set\n");
365  goto err_out;
366  }
367  pci_read_config_byte(ali1543_south, 0x5e, &tmp);
368  pci_dev_put(ali1543_south);
369  if ((tmp & 0x1e) == 0x00) {
370  if (!use_gpio) {
371  pr_info("Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n");
372  goto err_out;
373  }
374  nowayout = 1;
375  } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
376  pr_info("ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
377  goto err_out;
378  }
379 
380  if (timeout < 1 || timeout > 3600) {
381  /* arbitrary upper limit */
383  pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
384  timeout);
385  }
386 
387  rc = register_reboot_notifier(&wdt_notifier);
388  if (rc) {
389  pr_err("cannot register reboot notifier (err=%d)\n", rc);
390  goto err_out;
391  }
392 
393  rc = misc_register(&wdt_miscdev);
394  if (rc) {
395  pr_err("cannot register miscdev on minor=%d (err=%d)\n",
396  wdt_miscdev.minor, rc);
397  goto err_out_reboot;
398  }
399 
400  if (nowayout)
401  __module_get(THIS_MODULE);
402 
403  pr_info("WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
404  timeout, nowayout);
405  return 0;
406 
407 err_out_reboot:
408  unregister_reboot_notifier(&wdt_notifier);
409 err_out:
410  pci_dev_put(alim7101_pmu);
411  return rc;
412 }
413 
414 module_init(alim7101_wdt_init);
415 module_exit(alim7101_wdt_unload);
416 
417 static DEFINE_PCI_DEVICE_TABLE(alim7101_pci_tbl) __used = {
420  { }
421 };
422 
423 MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl);
424 
425 MODULE_AUTHOR("Steve Hill");
426 MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
427 MODULE_LICENSE("GPL");