summaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/apm.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-01-29 01:41:54 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-01-29 01:41:54 +0000
commitf969d69ba9f952e5bdd38278e25e26a3e4a61a70 (patch)
treeb3530d803df59d726afaabebc6626987dee1ca05 /arch/i386/kernel/apm.c
parenta10ce7ef2066b455d69187643ddf2073bfc4db24 (diff)
Merge with 2.3.27.
Diffstat (limited to 'arch/i386/kernel/apm.c')
-rw-r--r--arch/i386/kernel/apm.c494
1 files changed, 243 insertions, 251 deletions
diff --git a/arch/i386/kernel/apm.c b/arch/i386/kernel/apm.c
index a54994667..cdeaf9a35 100644
--- a/arch/i386/kernel/apm.c
+++ b/arch/i386/kernel/apm.c
@@ -1,8 +1,8 @@
/* -*- linux-c -*-
* APM BIOS driver for Linux
- * Copyright 1994-1998 Stephen Rothwell
- * (Stephen.Rothwell@canb.auug.org.au)
- * Development of this driver was funded by NEC Australia P/L
+ * Copyright 1994-1999 Stephen Rothwell (sfr@linuxcare.com)
+ *
+ * Initial development of this driver was funded by NEC Australia P/L
* and NEC Corporation
*
* This program is free software; you can redistribute it and/or modify it
@@ -33,6 +33,7 @@
* Nov 1998, Version 1.7
* Jan 1999, Version 1.8
* Jan 1999, Version 1.9
+ * Oct 1999, Version 1.10
*
* History:
* 0.6b: first version in official kernel, Linux 1.3.46
@@ -90,6 +91,16 @@
* Use CONFIG_SMP instead of __SMP__
* Ignore BOUNCES for three seconds.
* Stephen Rothwell
+ * 1.10: Fix for Thinkpad return code.
+ * Merge 2.2 and 2.3 drivers.
+ * Remove APM dependencies in arch/i386/kernel/process.c
+ * Remove APM dependencies in drivers/char/sysrq.c
+ * Reset time across standby.
+ * Allow more inititialisation on SMP.
+ * Remove CONFIG_APM_POWER_OFF and make it boot time
+ * configurable (default on).
+ * Make debug only a boot time parameter (remove APM_DEBUG).
+ * Try to blank all devices on any error.
*
* APM 1.1 Reference:
*
@@ -106,11 +117,8 @@
* Intel Corporation, Microsoft Corporation. Advanced Power Management
* (APM) BIOS Interface Specification, Revision 1.2, February 1996.
*
- * [This document is available from Intel at:
- * http://www.intel.com/IAL/powermgm
- * or Microsoft at
- * http://www.microsoft.com/windows/thirdparty/hardware/pcfuture.htm
- * ]
+ * [This document is available from Microsoft at:
+ * http://www.microsoft.com/hwdev/busbios/amp_12.htm]
*/
#include <linux/config.h>
@@ -122,7 +130,6 @@
#include <linux/timer.h>
#include <linux/fcntl.h>
#include <linux/malloc.h>
-#include <linux/linkage.h>
#include <linux/stat.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
@@ -133,62 +140,32 @@
#include <asm/uaccess.h>
#include <asm/desc.h>
+/*
+ * Make APM look as much as just another ACPI module as possible..
+ */
+#include <linux/acpi.h>
+
EXPORT_SYMBOL(apm_register_callback);
EXPORT_SYMBOL(apm_unregister_callback);
extern unsigned long get_cmos_time(void);
+extern void (*sysrq_power_off)(void);
+
/*
* The apm_bios device is one of the misc char devices.
* This is its minor number.
*/
#define APM_MINOR_DEV 134
-/* Configurable options:
- *
- * CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
- * This is necessary on the NEC Versa M series, which generates these when
- * resuming from SYSTEM SUSPEND. However, enabling this on other laptops
- * will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
- * USER SUSPEND is ignored -- this may prevent the APM driver from updating
- * the system time on a RESUME.
- *
- * CONFIG_APM_DO_ENABLE: enable APM features at boot time. From page 36 of
- * the specification: "When disabled, the APM BIOS does not automatically
- * power manage devices, enter the Standby State, enter the Suspend State,
- * or take power saving steps in response to CPU Idle calls." This driver
- * will make CPU Idle calls when Linux is idle (unless this feature is
- * turned off -- see below). This should always save battery power, but
- * more complicated APM features will be dependent on your BIOS
- * implementation. You may need to turn this option off if your computer
- * hangs at boot time when using APM support, or if it beeps continuously
- * instead of suspending. Turn this off if you have a NEC UltraLite Versa
- * 33/C or a Toshiba T400CDT. This is off by default since most machines
- * do fine without this feature.
- *
- * CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the
- * idle loop. On some machines, this can activate improved power savings,
- * such as a slowed CPU clock rate, when the machine is idle. These idle
- * call is made after the idle loop has run for some length of time (e.g.,
- * 333 mS). On some machines, this will cause a hang at boot time or
- * whenever the CPU becomes idle.
- *
- * CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM. Some
- * laptops can use this to turn of the LCD backlight when the VC screen
- * blanker blanks the screen. Note that this is only used by the VC screen
- * blanker, and probably won't turn off the backlight when using X11. Some
- * problems have been reported when using this option with gpm (if you'd
- * like to debug this, please do so).
- *
- * CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist
- * on returning multiple suspend/standby events whenever one occurs. We
- * really only need one at a time, so just ignore any beyond the first.
- * This is probably safe on most laptops.
- *
- * If you are debugging the APM support for your laptop, note that code for
- * all of these options is contained in this file, so you can #define or
- * #undef these on the next line to avoid recompiling the whole kernel.
+/*
+ * See Documentation/Config.help for the configuration options.
*
+ * Various options can be changed at boot time as follows:
+ * apm=on/off enable/disable APM
+ * [no-]debug log some debugging messages
+ * [no-]power-off power off on shutdown
+ * [no-]smp-power-off allow power off even for SMP
*/
/* KNOWN PROBLEM MACHINES:
@@ -207,11 +184,6 @@ extern unsigned long get_cmos_time(void);
*/
/*
- * Define to have debug messages.
- */
-#undef APM_DEBUG
-
-/*
* Define to always call the APM BIOS busy routine even if the clock was
* not slowed by the idle routine.
*/
@@ -266,26 +238,6 @@ extern unsigned long get_cmos_time(void);
__asm__ __volatile__("movl %%" #seg ",%0" : "=m" (where))
/*
- * Forward declarations
- */
-static void suspend(void);
-static void standby(void);
-static void set_time(void);
-
-static void check_events(void);
-
-static int do_open(struct inode *, struct file *);
-static int do_release(struct inode *, struct file *);
-static ssize_t do_read(struct file *, char *, size_t , loff_t *);
-static unsigned int do_poll(struct file *, poll_table *);
-static int do_ioctl(struct inode *, struct file *, u_int, u_long);
-
-static int apm_get_info(char *, char **, off_t, int, int);
-
-extern int apm_register_callback(int (*)(apm_event_t));
-extern void apm_unregister_callback(int (*)(apm_event_t));
-
-/*
* Local variables
*/
static struct {
@@ -312,13 +264,13 @@ static int got_clock_diff = 0;
#endif
static int debug = 0;
static int apm_disabled = 0;
+static int power_off_enabled = 1;
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
static struct apm_bios_struct * user_list = NULL;
-static char driver_version[] = "1.9"; /* no spaces */
+static char driver_version[] = "1.10"; /* no spaces */
-#ifdef APM_DEBUG
static char * apm_event_name[] = {
"system standby",
"system suspend",
@@ -335,28 +287,6 @@ static char * apm_event_name[] = {
};
#define NR_APM_EVENT_NAME \
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
-#endif
-
-static struct file_operations apm_bios_fops = {
- NULL, /* lseek */
- do_read,
- NULL, /* write */
- NULL, /* readdir */
- do_poll,
- do_ioctl,
- NULL, /* mmap */
- do_open,
- NULL, /* flush */
- do_release,
- NULL, /* fsync */
- NULL /* fasync */
-};
-
-static struct miscdevice apm_device = {
- APM_MINOR_DEV,
- "apm",
- &apm_bios_fops
-};
typedef struct callback_list_t {
int (* callback)(apm_event_t);
@@ -386,6 +316,7 @@ static const lookup_t error_table[] = {
{ APM_RESUME_DISABLED, "Resume timer disabled" },
{ APM_BAD_STATE, "Unable to enter requested state" },
/* N/A { APM_NO_EVENTS, "No events pending" }, */
+ { APM_NO_ERROR, "BIOS did not set a return code" },
{ APM_NOT_PRESENT, "No APM present" }
};
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
@@ -412,9 +343,10 @@ static const lookup_t error_table[] = {
# define APM_DO_CLI
#endif
#ifdef APM_ZERO_SEGS
+# define APM_DECL_SEGS \
+ unsigned int saved_fs; unsigned int saved_gs;
# define APM_DO_SAVE_SEGS \
- savesegment(fs, saved_fs); \
- savesegment(gs, saved_gs)
+ savesegment(fs, saved_fs); savesegment(gs, saved_gs)
# define APM_DO_ZERO_SEGS \
"pushl %%ds\n\t" \
"pushl %%es\n\t" \
@@ -427,20 +359,19 @@ static const lookup_t error_table[] = {
"popl %%es\n\t" \
"popl %%ds\n\t"
# define APM_DO_RESTORE_SEGS \
- loadsegment(fs, saved_fs); \
- loadsegment(gs, saved_gs)
+ loadsegment(fs, saved_fs); loadsegment(gs, saved_gs)
#else
+# define APM_DECL_SEGS
# define APM_DO_SAVE_SEGS
# define APM_DO_ZERO_SEGS
# define APM_DO_POP_SEGS
# define APM_DO_RESTORE_SEGS
#endif
-static u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
+static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
{
- unsigned int saved_fs;
- unsigned int saved_gs;
+ APM_DECL_SEGS
unsigned long flags;
__save_flags(flags);
@@ -456,7 +387,7 @@ static u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
APM_DO_POP_SEGS
: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
"=S" (*esi)
- : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+ : "a" (func), "b" (ebx_in), "c" (ecx_in)
: "memory", "cc");
APM_DO_RESTORE_SEGS;
__restore_flags(flags);
@@ -467,12 +398,10 @@ static u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
* This version only returns one value (usually an error code)
*/
-static u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
- u32 *eax)
+static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
{
u8 error;
- unsigned int saved_fs;
- unsigned int saved_gs;
+ APM_DECL_SEGS
unsigned long flags;
__save_flags(flags);
@@ -491,7 +420,7 @@ static u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
APM_DO_POP_SEGS
: "=a" (*eax), "=b" (error), "=c" (cx), "=d" (dx),
"=S" (si)
- : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+ : "a" (func), "b" (ebx_in), "c" (ecx_in)
: "memory", "cc");
}
APM_DO_RESTORE_SEGS;
@@ -499,11 +428,11 @@ static u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
return error;
}
-static int apm_driver_version(u_short *val)
+static int __init apm_driver_version(u_short *val)
{
u32 eax;
- if (apm_bios_call_simple(0x530e, 0, *val, &eax))
+ if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax))
return (eax >> 8) & 0xff;
*val = eax;
return APM_SUCCESS;
@@ -516,7 +445,8 @@ static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
u32 ecx;
u32 dummy;
- if (apm_bios_call(0x530b, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy))
+ if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx,
+ &dummy, &dummy))
return (eax >> 8) & 0xff;
*event = ebx;
if (apm_bios_info.version < 0x0102)
@@ -530,16 +460,44 @@ static int set_power_state(u_short what, u_short state)
{
u32 eax;
- if (apm_bios_call_simple(0x5307, what, state, &eax))
+ if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax))
return (eax >> 8) & 0xff;
return APM_SUCCESS;
}
static int apm_set_power_state(u_short state)
{
- return set_power_state(0x0001, state);
+ return set_power_state(APM_DEVICE_ALL, state);
}
+#ifdef CONFIG_APM_CPU_IDLE
+static int apm_do_idle(void)
+{
+ u32 dummy;
+
+ if (apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &dummy))
+ return 0;
+
+#ifdef ALWAYS_CALL_BUSY
+ clock_slowed = 1;
+#else
+ clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
+#endif
+ return 1;
+}
+
+static void apm_do_busy(void)
+{
+ u32 dummy;
+
+ if (clock_slowed) {
+ (void) apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy);
+ clock_slowed = 0;
+ }
+}
+
+extern int hlt_counter;
+
/*
* If no process has been interested in this
* CPU for some time, we want to wake up the
@@ -551,12 +509,10 @@ static int apm_set_power_state(u_short state)
/* This should wake up kapmd and ask it to slow the CPU */
#define powermanagement_idle() do { } while (0)
-extern int hlt_counter;
-
/*
* This is the idle thing.
*/
-void apm_cpu_idle(void)
+static void apm_cpu_idle(void)
{
unsigned int start_idle;
@@ -583,8 +539,9 @@ void apm_cpu_idle(void)
start_idle = jiffies;
}
}
+#endif
-void apm_power_off(void)
+static void apm_power_off(void)
{
/*
* smp_hack == 2 means that we would have enabled APM support
@@ -606,7 +563,7 @@ static int apm_set_display_power_state(u_short state)
/* Blank the first display device */
error = set_power_state(0x0100, state);
- if (error == APM_BAD_DEVICE)
+ if (error != APM_SUCCESS)
/* try to blank them all instead */
error = set_power_state(0x01ff, state);
return error;
@@ -618,8 +575,7 @@ static int __init apm_enable_power_management(void)
{
u32 eax;
- if (apm_bios_call_simple(0x5308,
- (apm_bios_info.version > 0x100) ? 0x0001 : 0xffff,
+ if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
1, &eax))
return (eax >> 8) & 0xff;
apm_bios_info.flags &= ~APM_BIOS_DISABLED;
@@ -635,7 +591,8 @@ static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
u32 edx;
u32 dummy;
- if (apm_bios_call(0x530a, 1, 0, &eax, &ebx, &ecx, &edx, &dummy))
+ if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0,
+ &eax, &ebx, &ecx, &edx, &dummy))
return (eax >> 8) & 0xff;
*status = ebx;
*bat = ecx;
@@ -643,11 +600,40 @@ static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
return APM_SUCCESS;
}
+#if 0
+static int apm_get_battery_status(u_short which, u_short *status,
+ u_short *bat, u_short *life, u_short *nbat)
+{
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+ u32 esi;
+
+ if (apm_bios_info.version < 0x0102) {
+ /* pretend we only have one battery. */
+ if (which != 1)
+ return APM_BAD_DEVICE;
+ *nbat = 1;
+ return apm_get_power_status(status, bat, life);
+ }
+
+ if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax,
+ &ebx, &ecx, &edx, &esi))
+ return (eax >> 8) & 0xff;
+ *status = ebx;
+ *bat = ecx;
+ *life = edx;
+ *nbat = esi;
+ return APM_SUCCESS;
+}
+#endif
+
static int __init apm_engage_power_management(u_short device)
{
u32 eax;
- if (apm_bios_call_simple(0x530f, device, 1, &eax))
+ if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, 1, &eax))
return (eax >> 8) & 0xff;
return APM_SUCCESS;
}
@@ -669,14 +655,12 @@ static void apm_error(char *str, int err)
int apm_display_blank(void)
{
#ifdef CONFIG_APM_DISPLAY_BLANK
- int error;
-
- if (!apm_enabled)
- return 0;
- error = apm_set_display_power_state(APM_STATE_STANDBY);
- if (error == APM_SUCCESS)
- return 1;
- apm_error("set display standby", error);
+ if (apm_enabled) {
+ int error = apm_set_display_power_state(APM_STATE_STANDBY);
+ if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
+ return 1;
+ apm_error("set display standby", error);
+ }
#endif
return 0;
}
@@ -685,14 +669,12 @@ int apm_display_blank(void)
int apm_display_unblank(void)
{
#ifdef CONFIG_APM_DISPLAY_BLANK
- int error;
-
- if (!apm_enabled)
- return 0;
- error = apm_set_display_power_state(APM_STATE_READY);
- if (error == APM_SUCCESS)
- return 1;
- apm_error("set display ready", error);
+ if (apm_enabled) {
+ int error = apm_set_display_power_state(APM_STATE_READY);
+ if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
+ return 1;
+ apm_error("set display ready", error);
+ }
#endif
return 0;
}
@@ -734,12 +716,12 @@ static apm_event_t get_queued_event(struct apm_bios_struct * as)
return as->events[as->event_tail];
}
-static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
+static void queue_event(apm_event_t event, struct apm_bios_struct *sender)
{
struct apm_bios_struct * as;
if (user_list == NULL)
- return 0;
+ return;
for (as = user_list; as != NULL; as = as->next) {
if (as == sender)
continue;
@@ -747,10 +729,8 @@ static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
if (as->event_head == as->event_tail) {
static int notified;
- if (notified == 0) {
+ if (notified++ == 0)
printk(KERN_ERR "apm: an event queue overflowed\n");
- notified = 1;
- }
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
}
as->events[as->event_head] = event;
@@ -771,28 +751,25 @@ static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
}
}
wake_up_interruptible(&apm_waitqueue);
- return 1;
}
static void set_time(void)
{
unsigned long flags;
- if (!got_clock_diff) /* Don't know time zone, can't set clock */
- return;
-
- save_flags(flags);
- cli();
- CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
- restore_flags(flags);
+ if (got_clock_diff) { /* Must know time zone in order to set clock */
+ save_flags(flags);
+ cli();
+ CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
+ restore_flags(flags);
+ }
}
-static void suspend(void)
+static void get_time_diff(void)
{
+#ifndef CONFIG_APM_RTC_IS_GMT
unsigned long flags;
- int err;
-#ifndef CONFIG_APM_RTC_IS_GMT
/*
* Estimate time zone so that set_time can update the clock
*/
@@ -803,9 +780,18 @@ static void suspend(void)
got_clock_diff = 1;
restore_flags(flags);
#endif
+}
+static void suspend(void)
+{
+ int err;
+#ifdef INIT_TIMER_AFTER_SUSPEND
+ unsigned long flags;
+#endif
+
+ get_time_diff();
err = apm_set_power_state(APM_STATE_SUSPEND);
- if (err)
+ if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
apm_error("suspend", err);
#ifdef INIT_TIMER_AFTER_SUSPEND
save_flags(flags);
@@ -826,8 +812,9 @@ static void standby(void)
{
int err;
+ get_time_diff();
err = apm_set_power_state(APM_STATE_STANDBY);
- if (err)
+ if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
apm_error("standby", err);
}
@@ -850,7 +837,7 @@ static apm_event_t get_event(void)
return 0;
}
-static void send_event(apm_event_t event, apm_event_t undo,
+static int send_event(apm_event_t event, apm_event_t undo,
struct apm_bios_struct *sender)
{
callback_list_t * call;
@@ -862,11 +849,12 @@ static void send_event(apm_event_t event, apm_event_t undo,
fix->callback(undo);
if (apm_bios_info.version > 0x100)
apm_set_power_state(APM_STATE_REJECT);
- return;
+ return 0;
}
}
queue_event(event, sender);
+ return 1;
}
static void check_events(void)
@@ -878,14 +866,14 @@ static void check_events(void)
#endif
while ((event = get_event()) != 0) {
-#ifdef APM_DEBUG
- if (event <= NR_APM_EVENT_NAME)
- printk(KERN_DEBUG "apm: received %s notify\n",
- apm_event_name[event - 1]);
- else
- printk(KERN_DEBUG "apm: received unknown "
- "event 0x%02x\n", event);
-#endif
+ if (debug) {
+ if (event <= NR_APM_EVENT_NAME)
+ printk(KERN_DEBUG "apm: received %s notify\n",
+ apm_event_name[event - 1]);
+ else
+ printk(KERN_DEBUG "apm: received unknown "
+ "event 0x%02x\n", event);
+ }
#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
if (ignore_bounce
&& ((jiffies - last_resume) > BOUNCE_INTERVAL))
@@ -899,8 +887,8 @@ static void check_events(void)
return;
waiting_for_resume = 1;
#endif
- send_event(event, APM_STANDBY_RESUME, NULL);
- if (standbys_pending <= 0)
+ if (send_event(event, APM_STANDBY_RESUME, NULL)
+ && (standbys_pending <= 0))
standby();
break;
@@ -920,8 +908,8 @@ static void check_events(void)
return;
waiting_for_resume = 1;
#endif
- send_event(event, APM_NORMAL_RESUME, NULL);
- if (suspends_pending <= 0)
+ if (send_event(event, APM_NORMAL_RESUME, NULL)
+ && (suspends_pending <= 0))
suspend();
break;
@@ -939,9 +927,9 @@ static void check_events(void)
send_event(event, 0, NULL);
break;
+ case APM_CAPABILITY_CHANGE:
case APM_LOW_BATTERY:
case APM_POWER_STATUS_CHANGE:
- case APM_CAPABILITY_CHANGE:
send_event(event, 0, NULL);
break;
@@ -958,56 +946,22 @@ static void check_events(void)
static void apm_event_handler(void)
{
- static int pending_count = 0;
+ static int pending_count = 0;
if (((standbys_pending > 0) || (suspends_pending > 0))
- && (apm_bios_info.version > 0x100)
- && (pending_count-- <= 0)) {
- int err;
- pending_count = 4;
-
- err = apm_set_power_state(APM_STATE_BUSY);
- if (err)
- apm_error("busy", err);
- }
-
- if (!(((standbys_pending > 0) || (suspends_pending > 0))
- && (apm_bios_info.version == 0x100)))
+ && (apm_bios_info.version > 0x100)) {
+ if (pending_count-- <= 0) {
+ int err;
+
+ pending_count = 4;
+ err = apm_set_power_state(APM_STATE_BUSY);
+ if (err)
+ apm_error("busy", err);
+ }
+ } else {
+ pending_count = 0;
check_events();
-}
-
-static int apm_do_idle(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
- u32 dummy;
-
- if (!apm_enabled)
- return 0;
-
- if (apm_bios_call_simple(0x5305, 0, 0, &dummy))
- return 0;
-
-#ifdef ALWAYS_CALL_BUSY
- clock_slowed = 1;
-#else
- clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
-#endif
- return 1;
-#else
- return 0;
-#endif
-}
-
-static void apm_do_busy(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
- u32 dummy;
-
- if (clock_slowed) {
- (void) apm_bios_call_simple(0x5306, 0, 0, &dummy);
- clock_slowed = 0;
}
-#endif
}
/*
@@ -1040,6 +994,7 @@ static void apm_mainloop(void)
*/
current->state = TASK_INTERRUPTIBLE;
apm_event_handler();
+#ifdef CONFIG_APM_CPU_IDLE
if (!system_idle())
continue;
if (apm_do_idle()) {
@@ -1052,6 +1007,7 @@ static void apm_mainloop(void)
apm_do_busy();
apm_event_handler();
}
+#endif
}
}
@@ -1069,7 +1025,7 @@ static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
struct apm_bios_struct * as;
int i;
apm_event_t event;
- DECLARE_WAITQUEUE(wait, current);
+ DECLARE_WAITQUEUE(wait, current);
as = fp->private_data;
if (check_apm_bios_struct(as, "read"))
@@ -1092,7 +1048,11 @@ repeat:
i = count;
while ((i >= sizeof(event)) && !queue_empty(as)) {
event = get_queued_event(as);
- copy_to_user(buf, &event, sizeof(event));
+ if (copy_to_user(buf, &event, sizeof(event))) {
+ if (i < count)
+ break;
+ return -EFAULT;
+ }
switch (event) {
case APM_SYS_SUSPEND:
case APM_USER_SUSPEND:
@@ -1131,6 +1091,7 @@ static int do_ioctl(struct inode * inode, struct file *filp,
u_int cmd, u_long arg)
{
struct apm_bios_struct * as;
+ int send_ok = 1;
as = filp->private_data;
if (check_apm_bios_struct(as, "ioctl"))
@@ -1145,8 +1106,9 @@ static int do_ioctl(struct inode * inode, struct file *filp,
standbys_pending--;
}
else
- send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as);
- if (standbys_pending <= 0)
+ send_ok = send_event(APM_USER_STANDBY,
+ APM_STANDBY_RESUME, as);
+ if (send_ok && (standbys_pending <= 0))
standby();
break;
case APM_IOC_SUSPEND:
@@ -1156,8 +1118,9 @@ static int do_ioctl(struct inode * inode, struct file *filp,
suspends_pending--;
}
else
- send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as);
- if (suspends_pending <= 0)
+ send_ok = send_event(APM_USER_SUSPEND,
+ APM_NORMAL_RESUME, as);
+ if (send_ok && (suspends_pending <= 0))
suspend();
break;
default:
@@ -1171,9 +1134,9 @@ static int do_release(struct inode * inode, struct file * filp)
struct apm_bios_struct * as;
as = filp->private_data;
- filp->private_data = NULL;
if (check_apm_bios_struct(as, "release"))
return 0;
+ filp->private_data = NULL;
if (as->standbys_pending > 0) {
standbys_pending -= as->standbys_pending;
if (standbys_pending <= 0)
@@ -1266,7 +1229,7 @@ int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
from the Get Power Status (0x0a) call unless otherwise noted.
0) Linux driver version (this will change if format changes)
- 1) APM BIOS Version. Usually 1.0 or 1.1.
+ 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
2) APM flags from APM Installation Check (0x00):
bit 0: APM_16_BIT_SUPPORT
bit 1: APM_32_BIT_SUPPORT
@@ -1276,13 +1239,14 @@ int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
3) AC line status
0x00: Off-line
0x01: On-line
- 0x02: On backup power (APM BIOS 1.1 only)
+ 0x02: On backup power (BIOS >= 1.1 only)
0xff: Unknown
4) Battery status
0x00: High
0x01: Low
0x02: Critical
0x03: Charging
+ 0x04: Selected battery not present (BIOS >= 1.2 only)
0xff: Unknown
5) Battery flag
bit 0: High
@@ -1340,7 +1304,7 @@ static int apm(void *unused)
if (debug) {
printk(KERN_INFO "apm: Connection version %d.%d\n",
(apm_bios_info.version >> 8) & 0xff,
- apm_bios_info.version & 0xff );
+ apm_bios_info.version & 0xff);
error = apm_get_power_status(&bx, &cx, &dx);
if (error)
@@ -1400,6 +1364,13 @@ static int apm(void *unused)
apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
}
+ /* Install our power off handler.. */
+ if (power_off_enabled)
+ acpi_power_off = apm_power_off;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+ sysrq_power_off = apm_power_off;
+#endif
apm_mainloop();
return 0;
}
@@ -1418,6 +1389,8 @@ static int __init apm_setup(char *str)
str += 3;
if (strncmp(str, "debug", 5) == 0)
debug = !invert;
+ if (strncmp(str, "power-off", 9) == 0)
+ power_off_enabled = !invert;
if (strncmp(str, "smp-power-off", 13) == 0)
smp_hack = !invert;
str = strchr(str, ',');
@@ -1429,6 +1402,29 @@ static int __init apm_setup(char *str)
__setup("apm=", apm_setup);
+static struct file_operations apm_bios_fops = {
+ NULL, /* lseek */
+ do_read,
+ NULL, /* write */
+ NULL, /* readdir */
+ do_poll,
+ do_ioctl,
+ NULL, /* mmap */
+ do_open,
+ NULL, /* flush */
+ do_release,
+ NULL, /* fsync */
+ NULL /* fasync */
+};
+
+static struct miscdevice apm_device = {
+ APM_MINOR_DEV,
+ "apm",
+ &apm_bios_fops
+};
+
+#define APM_INIT_ERROR_RETURN return -1
+
/*
* Just start the APM thread. We do NOT want to do APM BIOS
* calls from anything but the APM thread, if for no other reason
@@ -1441,11 +1437,9 @@ __setup("apm=", apm_setup);
*/
static int __init apm_init(void)
{
- static struct proc_dir_entry *ent;
-
if (apm_bios_info.version == 0) {
printk(KERN_INFO "apm: BIOS not found.\n");
- return -1;
+ APM_INIT_ERROR_RETURN;
}
printk(KERN_INFO
"apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n",
@@ -1455,7 +1449,7 @@ static int __init apm_init(void)
driver_version);
if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
printk(KERN_INFO "apm: no 32 bit BIOS support\n");
- return -1;
+ APM_INIT_ERROR_RETURN;
}
/*
@@ -1484,17 +1478,8 @@ static int __init apm_init(void)
if (apm_disabled) {
printk(KERN_NOTICE "apm: disabled on user request.\n");
- return -1;
- }
-
-#ifdef CONFIG_SMP
- if (smp_num_cpus > 1) {
- printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
- if (smp_hack)
- smp_hack = 2;
- return -1;
+ APM_INIT_ERROR_RETURN;
}
-#endif
/*
* Set up a segment that references the real mode segment 0x40
@@ -1536,9 +1521,16 @@ static int __init apm_init(void)
}
#endif
- ent = create_proc_entry("apm", 0, 0);
- if (ent != NULL)
- ent->get_info = apm_get_info;
+#ifdef CONFIG_SMP
+ if (smp_num_cpus > 1) {
+ printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
+ if (smp_hack)
+ smp_hack = 2;
+ APM_INIT_ERROR_RETURN;
+ }
+#endif
+
+ create_proc_info_entry("apm", 0, 0, apm_get_info);
misc_register(&apm_device);