|
|
@@ -1,22 +1,3 @@
|
|
|
-// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
-/*
|
|
|
- * Watchdog Timer Driver
|
|
|
- * for ITE IT87xx Environment Control - Low Pin Count Input / Output
|
|
|
- *
|
|
|
- * (c) Copyright 2007 Oliver Schuster <olivers137@aol.com>
|
|
|
- *
|
|
|
- * Based on softdog.c by Alan Cox,
|
|
|
- * 83977f_wdt.c by Jose Goncalves,
|
|
|
- * it87.c by Chris Gauthron, Jean Delvare
|
|
|
- *
|
|
|
- * Data-sheets: Publicly available at the ITE website
|
|
|
- * http://www.ite.com.tw/
|
|
|
- *
|
|
|
- * Support of the watchdog timers, which are available on
|
|
|
- * IT8607, IT8620, IT8622, IT8625, IT8628, IT8655, IT8665, IT8686,
|
|
|
- * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721, IT8726, IT8728,
|
|
|
- * IT8772, IT8783 and IT8784.
|
|
|
- */
|
|
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
|
@@ -26,14 +7,19 @@
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/moduleparam.h>
|
|
|
#include <linux/types.h>
|
|
|
-#include <linux/watchdog.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/cdev.h>
|
|
|
+#include <linux/device.h>
|
|
|
+#include <linux/uaccess.h>
|
|
|
+#include <linux/timer.h>
|
|
|
|
|
|
#define WATCHDOG_NAME "IT87 WDT"
|
|
|
+#define DEVICE_NAME "watchdog"
|
|
|
+#define CLASS_NAME "watchdog_class"
|
|
|
|
|
|
/* Defaults for Module Parameter */
|
|
|
#define DEFAULT_TIMEOUT 60
|
|
|
#define DEFAULT_TESTMODE 0
|
|
|
-#define DEFAULT_NOWAYOUT WATCHDOG_NOWAYOUT
|
|
|
|
|
|
/* IO Ports */
|
|
|
#define REG 0x4e
|
|
|
@@ -88,7 +74,6 @@ static unsigned int max_units, chip_type;
|
|
|
|
|
|
static unsigned int timeout = DEFAULT_TIMEOUT;
|
|
|
static int testmode = DEFAULT_TESTMODE;
|
|
|
-static bool nowayout = DEFAULT_NOWAYOUT;
|
|
|
|
|
|
module_param(timeout, int, 0);
|
|
|
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default="
|
|
|
@@ -96,9 +81,20 @@ MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default="
|
|
|
module_param(testmode, int, 0);
|
|
|
MODULE_PARM_DESC(testmode, "Watchdog test mode (1 = no reboot), default="
|
|
|
__MODULE_STRING(DEFAULT_TESTMODE));
|
|
|
-module_param(nowayout, bool, 0);
|
|
|
-MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started, default="
|
|
|
- __MODULE_STRING(WATCHDOG_NOWAYOUT));
|
|
|
+
|
|
|
+/* Character device variables */
|
|
|
+static int major_number;
|
|
|
+static struct class *watchdog_class = NULL;
|
|
|
+static struct device *watchdog_device = NULL;
|
|
|
+
|
|
|
+/* Watchdog state: 0 = OFF, 1 = ON */
|
|
|
+static int watchdog_state = 0;
|
|
|
+static DEFINE_MUTEX(watchdog_mutex);
|
|
|
+
|
|
|
+/* Software timer for servicing watchdog */
|
|
|
+static struct timer_list watchdog_timer;
|
|
|
+
|
|
|
+extern struct kobject *vfiec_kobj;
|
|
|
|
|
|
/* Superio Chip */
|
|
|
|
|
|
@@ -198,74 +194,268 @@ static int wdt_round_time(int t)
|
|
|
return t;
|
|
|
}
|
|
|
|
|
|
-/* watchdog timer handling */
|
|
|
+/* Software timer callback - service the watchdog */
|
|
|
+static void watchdog_timer_callback(struct timer_list *t)
|
|
|
+{
|
|
|
+ /* Re-trigger the hardware watchdog by writing timeout again */
|
|
|
+ wdt_update_timeout(timeout);
|
|
|
+
|
|
|
+ /* Restart timer (periodic servicing, e.g., every half timeout) */
|
|
|
+ mod_timer(&watchdog_timer, jiffies + (timeout * HZ / 2));
|
|
|
+}
|
|
|
|
|
|
-static int wdt_start(struct watchdog_device *wdd)
|
|
|
+/* Enable watchdog - start hardware and software timer */
|
|
|
+static int watchdog_enable(void)
|
|
|
{
|
|
|
- return wdt_update_timeout(wdd->timeout);
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ pr_info("Enabling watchdog timer\n");
|
|
|
+
|
|
|
+ ret = wdt_update_timeout(timeout);
|
|
|
+ if (ret) {
|
|
|
+ pr_err("Failed to set watchdog timeout\n");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Start periodic software timer to service watchdog */
|
|
|
+ timer_setup(&watchdog_timer, watchdog_timer_callback, 0);
|
|
|
+ mod_timer(&watchdog_timer, jiffies + (timeout * HZ / 2));
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-static int wdt_stop(struct watchdog_device *wdd)
|
|
|
+/* Disable watchdog - stop hardware and software timer */
|
|
|
+static int watchdog_disable(void)
|
|
|
{
|
|
|
- printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
|
|
|
+ pr_info("Disabling watchdog timer\n");
|
|
|
+
|
|
|
+ /* Stop and delete software timer */
|
|
|
+ del_timer_sync(&watchdog_timer);
|
|
|
+
|
|
|
+ /* Disable hardware watchdog by setting timeout to 0 */
|
|
|
return wdt_update_timeout(0);
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * wdt_set_timeout - set a new timeout value with watchdog ioctl
|
|
|
- * @t: timeout value in seconds
|
|
|
- *
|
|
|
- * The hardware device has a 8 or 16 bit watchdog timer (depends on
|
|
|
- * chip version) that can be configured to count seconds or minutes.
|
|
|
- *
|
|
|
- * Used within WDIOC_SETTIMEOUT watchdog device ioctl.
|
|
|
- */
|
|
|
-
|
|
|
-static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
|
|
|
+/* Parse user command: supports "ON", "OFF", "1", "0" */
|
|
|
+static int parse_watchdog_command(const char *buf, size_t count)
|
|
|
+{
|
|
|
+ /* Skip whitespace */
|
|
|
+ while (count > 0 && (*buf == ' ' || *buf == '\t' || *buf == '\n' || *buf == '\r')) {
|
|
|
+ buf++;
|
|
|
+ count--;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (count == 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ /* Check for numeric value */
|
|
|
+ if (count == 1 && (buf[0] == '0' || buf[0] == '1')) {
|
|
|
+ return buf[0] - '0';
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check for "ON" or "OFF" (case insensitive) */
|
|
|
+ if (count >= 2) {
|
|
|
+ if ((buf[0] == 'O' || buf[0] == 'o') &&
|
|
|
+ (buf[1] == 'N' || buf[1] == 'n')) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ if (count >= 3 &&
|
|
|
+ (buf[0] == 'O' || buf[0] == 'o') &&
|
|
|
+ (buf[1] == 'F' || buf[1] == 'f') &&
|
|
|
+ (buf[2] == 'F' || buf[2] == 'f')) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/* Character device file operations */
|
|
|
+static ssize_t watchdog_read(struct file *file, char __user *user_buf,
|
|
|
+ size_t count, loff_t *ppos)
|
|
|
+{
|
|
|
+ char status_buf[64];
|
|
|
+ int len;
|
|
|
+
|
|
|
+ if (*ppos > 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ mutex_lock(&watchdog_mutex);
|
|
|
+ if (watchdog_state)
|
|
|
+ len = snprintf(status_buf, sizeof(status_buf),
|
|
|
+ "The watchdog is on\nThe args value is 1\n");
|
|
|
+ else
|
|
|
+ len = snprintf(status_buf, sizeof(status_buf),
|
|
|
+ "The watchdog is off\nThe args value is 0\n");
|
|
|
+ mutex_unlock(&watchdog_mutex);
|
|
|
+
|
|
|
+ if (copy_to_user(user_buf, status_buf, len))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ *ppos += len;
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t watchdog_write(struct file *file, const char __user *user_buf,
|
|
|
+ size_t count, loff_t *ppos)
|
|
|
{
|
|
|
- int ret = 0;
|
|
|
+ char buf[16];
|
|
|
+ int cmd;
|
|
|
+ int ret = count;
|
|
|
+
|
|
|
+ if (count == 0 || count >= sizeof(buf))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (copy_from_user(buf, user_buf, count))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ buf[count] = '\0';
|
|
|
+
|
|
|
+ cmd = parse_watchdog_command(buf, count);
|
|
|
+ if (cmd < 0)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ mutex_lock(&watchdog_mutex);
|
|
|
+
|
|
|
+ if (cmd == watchdog_state) {
|
|
|
+ /* No state change */
|
|
|
+ if (cmd)
|
|
|
+ pr_info("Watchdog is already on\n");
|
|
|
+ else
|
|
|
+ pr_info("Watchdog is already off\n");
|
|
|
+ } else {
|
|
|
+ if (cmd) {
|
|
|
+ /* Turn ON */
|
|
|
+ if (watchdog_enable() == 0) {
|
|
|
+ watchdog_state = 1;
|
|
|
+ pr_info("Watchdog turned on successfully\n");
|
|
|
+ } else {
|
|
|
+ ret = -EIO;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* Turn OFF */
|
|
|
+ if (watchdog_disable() == 0) {
|
|
|
+ watchdog_state = 0;
|
|
|
+ pr_info("Watchdog turned off successfully\n");
|
|
|
+ } else {
|
|
|
+ ret = -EIO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&watchdog_mutex);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
|
|
|
- if (t > max_units)
|
|
|
- t = wdt_round_time(t);
|
|
|
+static int watchdog_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- wdd->timeout = t;
|
|
|
+static int watchdog_release(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- if (watchdog_hw_running(wdd))
|
|
|
- ret = wdt_update_timeout(t);
|
|
|
+static const struct file_operations watchdog_fops = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .read = watchdog_read,
|
|
|
+ .write = watchdog_write,
|
|
|
+ .open = watchdog_open,
|
|
|
+ .release = watchdog_release,
|
|
|
+};
|
|
|
+static char *my_devnode(struct device *dev, umode_t *mode) {
|
|
|
+ if (mode) {
|
|
|
+ *mode = 0666;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
|
|
|
+static ssize_t watchdog_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t watchdog_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
|
+ const char *buf, size_t count)
|
|
|
+{
|
|
|
+ int cmd;
|
|
|
+ int ret = count;
|
|
|
+
|
|
|
+ // if (count == 0 || count >= sizeof(buf))
|
|
|
+ // return -EINVAL;
|
|
|
+
|
|
|
+ // if (strscpy(buf, user_buf, count) < 0)
|
|
|
+ // return -EFAULT;
|
|
|
+
|
|
|
+ // buf[count] = '\0';
|
|
|
+
|
|
|
+ cmd = parse_watchdog_command(buf, count);
|
|
|
+ if (cmd < 0)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ mutex_lock(&watchdog_mutex);
|
|
|
+
|
|
|
+ if (cmd == watchdog_state) {
|
|
|
+ /* No state change */
|
|
|
+ if (cmd)
|
|
|
+ pr_info("Watchdog is already on\n");
|
|
|
+ else
|
|
|
+ pr_info("Watchdog is already off\n");
|
|
|
+ } else {
|
|
|
+ if (cmd) {
|
|
|
+ /* Turn ON */
|
|
|
+ if (watchdog_enable() == 0) {
|
|
|
+ watchdog_state = 1;
|
|
|
+ pr_info("Watchdog turned on successfully\n");
|
|
|
+ } else {
|
|
|
+ ret = -EIO;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* Turn OFF */
|
|
|
+ if (watchdog_disable() == 0) {
|
|
|
+ watchdog_state = 0;
|
|
|
+ pr_info("Watchdog turned off successfully\n");
|
|
|
+ } else {
|
|
|
+ ret = -EIO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&watchdog_mutex);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static const struct watchdog_info ident = {
|
|
|
- .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
|
|
|
- .firmware_version = 1,
|
|
|
- .identity = WATCHDOG_NAME,
|
|
|
-};
|
|
|
+static struct kobj_attribute watchdog_attr =
|
|
|
+ __ATTR(watchdog, 0644, watchdog_show, watchdog_store);
|
|
|
|
|
|
-static const struct watchdog_ops wdt_ops = {
|
|
|
- .owner = THIS_MODULE,
|
|
|
- .start = wdt_start,
|
|
|
- .stop = wdt_stop,
|
|
|
- .set_timeout = wdt_set_timeout,
|
|
|
+static struct attribute *watchdog_attrs[] = {
|
|
|
+ &watchdog_attr.attr,
|
|
|
+ NULL,
|
|
|
};
|
|
|
|
|
|
-static struct watchdog_device wdt_dev = {
|
|
|
- .info = &ident,
|
|
|
- .ops = &wdt_ops,
|
|
|
- .min_timeout = 1,
|
|
|
+static struct attribute_group watchdog_attr_group = {
|
|
|
+ .attrs = watchdog_attrs,
|
|
|
};
|
|
|
|
|
|
int watchdog_init(void)
|
|
|
-{
|
|
|
+{
|
|
|
u8 chip_rev;
|
|
|
int rc;
|
|
|
|
|
|
+ rc = sysfs_create_group(vfiec_kobj, &watchdog_attr_group);
|
|
|
+ if (rc)
|
|
|
+ {
|
|
|
+ pr_err("Faiec_version to create sysfs group: %d\n", rc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
rc = superio_enter();
|
|
|
if (rc)
|
|
|
return rc;
|
|
|
|
|
|
chip_type = superio_inw(CHIPID);
|
|
|
- // chip_type = 0x8786;
|
|
|
chip_rev = superio_inb(CHIPREV) & 0x0f;
|
|
|
superio_exit();
|
|
|
|
|
|
@@ -329,25 +519,63 @@ int watchdog_init(void)
|
|
|
if (timeout > max_units)
|
|
|
timeout = wdt_round_time(timeout);
|
|
|
|
|
|
- wdt_dev.timeout = timeout;
|
|
|
- wdt_dev.max_timeout = max_units * 60;
|
|
|
+ /* Register character device */
|
|
|
+ major_number = register_chrdev(0, DEVICE_NAME, &watchdog_fops);
|
|
|
+ if (major_number < 0) {
|
|
|
+ pr_err("Failed to register character device\n");
|
|
|
+ return major_number;
|
|
|
+ }
|
|
|
|
|
|
- watchdog_stop_on_reboot(&wdt_dev);
|
|
|
- rc = watchdog_register_device(&wdt_dev);
|
|
|
- if (rc) {
|
|
|
- pr_err("Cannot register watchdog device (err=%d)\n", rc);
|
|
|
- return rc;
|
|
|
+ /* Create device class */
|
|
|
+ watchdog_class = class_create(THIS_MODULE, CLASS_NAME);
|
|
|
+ if (IS_ERR(watchdog_class)) {
|
|
|
+ unregister_chrdev(major_number, DEVICE_NAME);
|
|
|
+ pr_err("Failed to create device class\n");
|
|
|
+ return PTR_ERR(watchdog_class);
|
|
|
}
|
|
|
|
|
|
- pr_info("Chip IT%04x revision %d initialized. timeout=%d sec (nowayout=%d testmode=%d)\n",
|
|
|
- chip_type, chip_rev, timeout, nowayout, testmode);
|
|
|
+ watchdog_class->devnode = my_devnode;
|
|
|
+
|
|
|
+ /* Create device node /dev/watchdog */
|
|
|
+ watchdog_device = device_create(watchdog_class, NULL,
|
|
|
+ MKDEV(major_number, 0),
|
|
|
+ NULL, DEVICE_NAME);
|
|
|
+ if (IS_ERR(watchdog_device)) {
|
|
|
+ class_destroy(watchdog_class);
|
|
|
+ unregister_chrdev(major_number, DEVICE_NAME);
|
|
|
+ pr_err("Failed to create device node\n");
|
|
|
+ return PTR_ERR(watchdog_device);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Initialize state */
|
|
|
+ watchdog_state = 0;
|
|
|
+
|
|
|
+ /* Initialize timer */
|
|
|
+ timer_setup(&watchdog_timer, watchdog_timer_callback, 0);
|
|
|
+
|
|
|
+ pr_info("Chip IT%04x revision %d initialized. timeout=%d sec (testmode=%d)\n",
|
|
|
+ chip_type, chip_rev, timeout, testmode);
|
|
|
+ pr_info("Device /dev/watchdog created. Default state: OFF\n");
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void watchdog_exit(void)
|
|
|
-{
|
|
|
- watchdog_unregister_device(&wdt_dev);
|
|
|
+{
|
|
|
+ /* Ensure watchdog is disabled before exit */
|
|
|
+ if (watchdog_state) {
|
|
|
+ watchdog_disable();
|
|
|
+ watchdog_state = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Delete timer */
|
|
|
+ del_timer_sync(&watchdog_timer);
|
|
|
+
|
|
|
+ /* Remove device */
|
|
|
+ device_destroy(watchdog_class, MKDEV(major_number, 0));
|
|
|
+ class_destroy(watchdog_class);
|
|
|
+ unregister_chrdev(major_number, DEVICE_NAME);
|
|
|
+
|
|
|
+ pr_info("Watchdog driver exited\n");
|
|
|
}
|
|
|
|
|
|
-
|