scx200_wdt.c 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /* drivers/char/watchdog/scx200_wdt.c
  3. National Semiconductor SCx200 Watchdog support
  4. Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
  5. Some code taken from:
  6. National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
  7. (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
  8. The author(s) of this software shall not be held liable for damages
  9. of any nature resulting due to the use of this software. This
  10. software is provided AS-IS with no warranties. */
  11. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  12. #include <linux/module.h>
  13. #include <linux/moduleparam.h>
  14. #include <linux/init.h>
  15. #include <linux/miscdevice.h>
  16. #include <linux/watchdog.h>
  17. #include <linux/notifier.h>
  18. #include <linux/reboot.h>
  19. #include <linux/fs.h>
  20. #include <linux/ioport.h>
  21. #include <linux/scx200.h>
  22. #include <linux/uaccess.h>
  23. #include <linux/io.h>
  24. #define DEBUG
  25. MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
  26. MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
  27. MODULE_LICENSE("GPL");
  28. static int margin = 60; /* in seconds */
  29. module_param(margin, int, 0);
  30. MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  31. static bool nowayout = WATCHDOG_NOWAYOUT;
  32. module_param(nowayout, bool, 0);
  33. MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  34. static u16 wdto_restart;
  35. static char expect_close;
  36. static unsigned long open_lock;
  37. static DEFINE_SPINLOCK(scx_lock);
  38. /* Bits of the WDCNFG register */
  39. #define W_ENABLE 0x00fa /* Enable watchdog */
  40. #define W_DISABLE 0x0000 /* Disable watchdog */
  41. /* The scaling factor for the timer, this depends on the value of W_ENABLE */
  42. #define W_SCALE (32768/1024)
  43. static void scx200_wdt_ping(void)
  44. {
  45. spin_lock(&scx_lock);
  46. outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
  47. spin_unlock(&scx_lock);
  48. }
  49. static void scx200_wdt_update_margin(void)
  50. {
  51. pr_info("timer margin %d seconds\n", margin);
  52. wdto_restart = margin * W_SCALE;
  53. }
  54. static void scx200_wdt_enable(void)
  55. {
  56. pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
  57. spin_lock(&scx_lock);
  58. outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  59. outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  60. outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  61. spin_unlock(&scx_lock);
  62. scx200_wdt_ping();
  63. }
  64. static void scx200_wdt_disable(void)
  65. {
  66. pr_debug("disabling watchdog timer\n");
  67. spin_lock(&scx_lock);
  68. outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  69. outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  70. outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  71. spin_unlock(&scx_lock);
  72. }
  73. static int scx200_wdt_open(struct inode *inode, struct file *file)
  74. {
  75. /* only allow one at a time */
  76. if (test_and_set_bit(0, &open_lock))
  77. return -EBUSY;
  78. scx200_wdt_enable();
  79. return stream_open(inode, file);
  80. }
  81. static int scx200_wdt_release(struct inode *inode, struct file *file)
  82. {
  83. if (expect_close != 42)
  84. pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
  85. else if (!nowayout)
  86. scx200_wdt_disable();
  87. expect_close = 0;
  88. clear_bit(0, &open_lock);
  89. return 0;
  90. }
  91. static int scx200_wdt_notify_sys(struct notifier_block *this,
  92. unsigned long code, void *unused)
  93. {
  94. if (code == SYS_HALT || code == SYS_POWER_OFF)
  95. if (!nowayout)
  96. scx200_wdt_disable();
  97. return NOTIFY_DONE;
  98. }
  99. static struct notifier_block scx200_wdt_notifier = {
  100. .notifier_call = scx200_wdt_notify_sys,
  101. };
  102. static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
  103. size_t len, loff_t *ppos)
  104. {
  105. /* check for a magic close character */
  106. if (len) {
  107. size_t i;
  108. scx200_wdt_ping();
  109. expect_close = 0;
  110. for (i = 0; i < len; ++i) {
  111. char c;
  112. if (get_user(c, data + i))
  113. return -EFAULT;
  114. if (c == 'V')
  115. expect_close = 42;
  116. }
  117. return len;
  118. }
  119. return 0;
  120. }
  121. static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
  122. unsigned long arg)
  123. {
  124. void __user *argp = (void __user *)arg;
  125. int __user *p = argp;
  126. static const struct watchdog_info ident = {
  127. .identity = "NatSemi SCx200 Watchdog",
  128. .firmware_version = 1,
  129. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  130. WDIOF_MAGICCLOSE,
  131. };
  132. int new_margin;
  133. switch (cmd) {
  134. case WDIOC_GETSUPPORT:
  135. if (copy_to_user(argp, &ident, sizeof(ident)))
  136. return -EFAULT;
  137. return 0;
  138. case WDIOC_GETSTATUS:
  139. case WDIOC_GETBOOTSTATUS:
  140. if (put_user(0, p))
  141. return -EFAULT;
  142. return 0;
  143. case WDIOC_KEEPALIVE:
  144. scx200_wdt_ping();
  145. return 0;
  146. case WDIOC_SETTIMEOUT:
  147. if (get_user(new_margin, p))
  148. return -EFAULT;
  149. if (new_margin < 1)
  150. return -EINVAL;
  151. margin = new_margin;
  152. scx200_wdt_update_margin();
  153. scx200_wdt_ping();
  154. fallthrough;
  155. case WDIOC_GETTIMEOUT:
  156. if (put_user(margin, p))
  157. return -EFAULT;
  158. return 0;
  159. default:
  160. return -ENOTTY;
  161. }
  162. }
  163. static const struct file_operations scx200_wdt_fops = {
  164. .owner = THIS_MODULE,
  165. .llseek = no_llseek,
  166. .write = scx200_wdt_write,
  167. .unlocked_ioctl = scx200_wdt_ioctl,
  168. .compat_ioctl = compat_ptr_ioctl,
  169. .open = scx200_wdt_open,
  170. .release = scx200_wdt_release,
  171. };
  172. static struct miscdevice scx200_wdt_miscdev = {
  173. .minor = WATCHDOG_MINOR,
  174. .name = "watchdog",
  175. .fops = &scx200_wdt_fops,
  176. };
  177. static int __init scx200_wdt_init(void)
  178. {
  179. int r;
  180. pr_debug("NatSemi SCx200 Watchdog Driver\n");
  181. /* check that we have found the configuration block */
  182. if (!scx200_cb_present())
  183. return -ENODEV;
  184. if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
  185. SCx200_WDT_SIZE,
  186. "NatSemi SCx200 Watchdog")) {
  187. pr_warn("watchdog I/O region busy\n");
  188. return -EBUSY;
  189. }
  190. scx200_wdt_update_margin();
  191. scx200_wdt_disable();
  192. r = register_reboot_notifier(&scx200_wdt_notifier);
  193. if (r) {
  194. pr_err("unable to register reboot notifier\n");
  195. release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  196. SCx200_WDT_SIZE);
  197. return r;
  198. }
  199. r = misc_register(&scx200_wdt_miscdev);
  200. if (r) {
  201. unregister_reboot_notifier(&scx200_wdt_notifier);
  202. release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  203. SCx200_WDT_SIZE);
  204. return r;
  205. }
  206. return 0;
  207. }
  208. static void __exit scx200_wdt_cleanup(void)
  209. {
  210. misc_deregister(&scx200_wdt_miscdev);
  211. unregister_reboot_notifier(&scx200_wdt_notifier);
  212. release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  213. SCx200_WDT_SIZE);
  214. }
  215. module_init(scx200_wdt_init);
  216. module_exit(scx200_wdt_cleanup);