summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ibm-acpi.txt20
-rw-r--r--drivers/acpi/ibm_acpi.c70
2 files changed, 87 insertions, 3 deletions
diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt
index cbd3a603a7ef..0132d363feb5 100644
--- a/Documentation/ibm-acpi.txt
+++ b/Documentation/ibm-acpi.txt
@@ -670,6 +670,26 @@ example:
modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable
+The ibm-acpi kernel driver can be programmed to revert the fan level
+to a safe setting if userspace does not issue one of the fan commands:
+"enable", "disable", "level" or "watchdog" within a configurable
+ammount of time. To do this, use the "watchdog" command.
+
+ echo 'watchdog <interval>' > /proc/acpi/ibm/fan
+
+Interval is the ammount of time in seconds to wait for one of the
+above mentioned fan commands before reseting the fan level to a safe
+one. If set to zero, the watchdog is disabled (default). When the
+watchdog timer runs out, it does the exact equivalent of the "enable"
+fan command.
+
+Note that the watchdog timer stops after it enables the fan. It will
+be rearmed again automatically (using the same interval) when one of
+the above mentioned fan commands is received. The fan watchdog is,
+therefore, not suitable to protect against fan mode changes made
+through means other than the "enable", "disable", and "level" fan
+commands.
+
Example Configuration
---------------------
diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c
index 56743c5a0439..e5b8745b9070 100644
--- a/drivers/acpi/ibm_acpi.c
+++ b/drivers/acpi/ibm_acpi.c
@@ -82,6 +82,8 @@
#include <linux/backlight.h>
#include <asm/uaccess.h>
#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acnamesp.h>
@@ -348,7 +350,8 @@ enum fan_control_access_mode {
enum fan_control_commands {
IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */
IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */
- IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd */
+ IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd,
+ * and also watchdog cmd */
};
enum { /* Fan control constants */
@@ -1797,12 +1800,17 @@ static enum fan_control_commands fan_control_commands;
static int fan_control_status_known;
static u8 fan_control_initial_status;
+static void fan_watchdog_fire(void *ignored);
+static int fan_watchdog_maxinterval;
+static DECLARE_WORK(fan_watchdog_task, fan_watchdog_fire, NULL);
+
static int fan_init(void)
{
fan_status_access_mode = IBMACPI_FAN_NONE;
fan_control_access_mode = IBMACPI_FAN_WR_NONE;
fan_control_commands = 0;
fan_control_status_known = 1;
+ fan_watchdog_maxinterval = 0;
if (gfan_handle) {
/* 570, 600e/x, 770e, 770x */
@@ -1934,6 +1942,31 @@ static int fan_get_speed(unsigned int *speed)
return 0;
}
+static void fan_exit(void)
+{
+ cancel_delayed_work(&fan_watchdog_task);
+ flush_scheduled_work();
+}
+
+static void fan_watchdog_reset(void)
+{
+ static int fan_watchdog_active = 0;
+
+ if (fan_watchdog_active)
+ cancel_delayed_work(&fan_watchdog_task);
+
+ if (fan_watchdog_maxinterval > 0) {
+ fan_watchdog_active = 1;
+ if (!schedule_delayed_work(&fan_watchdog_task,
+ msecs_to_jiffies(fan_watchdog_maxinterval
+ * 1000))) {
+ printk(IBM_ERR "failed to schedule the fan watchdog, "
+ "watchdog will not trigger\n");
+ }
+ } else
+ fan_watchdog_active = 0;
+}
+
static int fan_read(char *p)
{
int len = 0;
@@ -2007,7 +2040,9 @@ static int fan_read(char *p)
}
if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE)
- len += sprintf(p + len, "commands:\tenable, disable\n");
+ len += sprintf(p + len, "commands:\tenable, disable\n"
+ "commands:\twatchdog <timeout> (<timeout> is 0 (off), "
+ "1-120 (seconds))\n");
if (fan_control_commands & IBMACPI_FAN_CMD_SPEED)
len += sprintf(p + len, "commands:\tspeed <speed>"
@@ -2186,6 +2221,21 @@ static int fan_write_cmd_speed(const char *cmd, int *rc)
return 1;
}
+static int fan_write_cmd_watchdog(const char *cmd, int *rc)
+{
+ int interval;
+
+ if (sscanf(cmd, "watchdog %d", &interval) != 1)
+ return 0;
+
+ if (interval < 0 || interval > 120)
+ *rc = -EINVAL;
+ else
+ fan_watchdog_maxinterval = interval;
+
+ return 1;
+}
+
static int fan_write(char *buf)
{
char *cmd;
@@ -2196,16 +2246,29 @@ static int fan_write(char *buf)
fan_write_cmd_level(cmd, &rc)) &&
!((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) &&
(fan_write_cmd_enable(cmd, &rc) ||
- fan_write_cmd_disable(cmd, &rc))) &&
+ fan_write_cmd_disable(cmd, &rc) ||
+ fan_write_cmd_watchdog(cmd, &rc))) &&
!((fan_control_commands & IBMACPI_FAN_CMD_SPEED) &&
fan_write_cmd_speed(cmd, &rc))
)
rc = -EINVAL;
+ else if (!rc)
+ fan_watchdog_reset();
}
return rc;
}
+static void fan_watchdog_fire(void *ignored)
+{
+ printk(IBM_NOTICE "fan watchdog: enabling fan\n");
+ if (fan_set_enable()) {
+ printk(IBM_ERR "fan watchdog: error while enabling fan\n");
+ /* reschedule for later */
+ fan_watchdog_reset();
+ }
+}
+
static struct ibm_struct ibms[] = {
{
.name = "driver",
@@ -2317,6 +2380,7 @@ static struct ibm_struct ibms[] = {
.read = fan_read,
.write = fan_write,
.init = fan_init,
+ .exit = fan_exit,
.experimental = 1,
},
};