summaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/apm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/kernel/apm.c')
-rw-r--r--arch/i386/kernel/apm.c206
1 files changed, 124 insertions, 82 deletions
diff --git a/arch/i386/kernel/apm.c b/arch/i386/kernel/apm.c
index 1cbc824f5..6eba89025 100644
--- a/arch/i386/kernel/apm.c
+++ b/arch/i386/kernel/apm.c
@@ -31,6 +31,8 @@
* Aug 1998, Version 1.5
* Sep 1998, Version 1.6
* Nov 1998, Version 1.7
+ * Jan 1999, Version 1.8
+ * Jan 1999, Version 1.9
*
* History:
* 0.6b: first version in official kernel, Linux 1.3.46
@@ -72,6 +74,22 @@
* Make boot messages far less verbose by default
* Make asm safer
* Stephen Rothwell
+ * 1.8: Add CONFIG_APM_RTC_IS_GMT
+ * Richard Gooch <rgooch@atnf.csiro.au>
+ * change APM_NOINTS to CONFIG_APM_ALLOW_INTS
+ * remove dependency on CONFIG_PROC_FS
+ * Stephen Rothwell
+ * 1.9: Fix small typo. <laslo@ilo.opole.pl>
+ * Try to cope with BIOS's that need to have all display
+ * devices blanked and not just the first one.
+ * Ross Paterson <ross@soi.city.ac.uk>
+ * Fix segment limit setting it has always been wrong as
+ * the segments needed to have byte granularity.
+ * Mark a few things __init.
+ * Add hack to allow power off of SMP systems by popular request.
+ * Use CONFIG_SMP instead of __SMP__
+ * Ignore BOUNCES for three seconds.
+ * Stephen Rothwell
*
* APM 1.1 Reference:
*
@@ -105,10 +123,8 @@
#include <linux/fcntl.h>
#include <linux/malloc.h>
#include <linux/linkage.h>
-#ifdef CONFIG_PROC_FS
#include <linux/stat.h>
#include <linux/proc_fs.h>
-#endif
#include <linux/miscdevice.h>
#include <linux/apm_bios.h>
#include <linux/init.h>
@@ -202,13 +218,6 @@ extern unsigned long get_cmos_time(void);
#define ALWAYS_CALL_BUSY
/*
- * Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call
- * should turn interrupts on before it does a 'hlt').
- * This reportedly needs undefining for the ThinkPad 600.
- */
-#define APM_NOINTS
-
-/*
* Define to make the APM BIOS calls zero all data segment registers (so
* that an incorrect BIOS implementation will cause a kernel panic if it
* tries to write to arbitrary memory).
@@ -216,7 +225,7 @@ extern unsigned long get_cmos_time(void);
#define APM_ZERO_SEGS
/*
- * Define to make all set_limit calls use 64k limits. The APM 1.1 BIOS is
+ * Define to make all _set_limit calls use 64k limits. The APM 1.1 BIOS is
* supposed to provide limit information that it recognizes. Many machines
* do this correctly, but many others do not restrict themselves to their
* claimed limit. When this happens, they will cause a segmentation
@@ -245,6 +254,12 @@ extern unsigned long get_cmos_time(void);
#define APM_CHECK_TIMEOUT (HZ)
/*
+ * If CONFIG_APM_IGNORE_SUSPEND_BOUNCE is defined then
+ * ignore suspend events for this amount of time
+ */
+#define BOUNCE_INTERVAL (3 * HZ)
+
+/*
* Save a segment register away
*/
#define savesegment(seg, where) \
@@ -266,9 +281,7 @@ 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);
-#ifdef CONFIG_PROC_FS
static int apm_get_info(char *, char **, off_t, int, int);
-#endif
extern int apm_register_callback(int (*)(apm_event_t));
extern void apm_unregister_callback(int (*)(apm_event_t));
@@ -281,6 +294,7 @@ static asmlinkage struct {
unsigned short segment;
} apm_bios_entry;
static int apm_enabled = 0;
+static int smp_hack = 0;
#ifdef CONFIG_APM_CPU_IDLE
static int clock_slowed = 0;
#endif
@@ -290,8 +304,13 @@ static int standbys_pending = 0;
static int waiting_for_resume = 0;
#endif
+#ifdef CONFIG_APM_RTC_IS_GMT
+# define clock_cmos_diff 0
+# define got_clock_diff 1
+#else
static long clock_cmos_diff;
static int got_clock_diff = 0;
+#endif
static int debug = 0;
static int apm_disabled = 0;
@@ -300,7 +319,7 @@ static struct apm_bios_struct * user_list = NULL;
static struct timer_list apm_timer;
-static char driver_version[] = "1.7"; /* no spaces */
+static char driver_version[] = "1.9"; /* no spaces */
#ifdef APM_DEBUG
static char * apm_event_name[] = {
@@ -375,22 +394,22 @@ static const lookup_t error_table[] = {
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
/*
- * These are the actual BIOS calls. Depending on APM_ZERO_SEGS
- * and APM_NOINTS, we are being really paranoid here! Not only are
- * interrupts disabled, but all the segment registers (except SS) are
- * saved and zeroed this means that if the BIOS tries to reference any
- * data without explicitly loading the segment registers, the kernel will
- * fault immediately rather than have some unforeseen circumstances for
- * the rest of the kernel. And it will be very obvious! :-) Doing this
- * depends on CS referring to the same physical memory as DS so that DS
- * can be zeroed before the call. Unfortunately, we can't do anything
+ * These are the actual BIOS calls. Depending on APM_ZERO_SEGS and
+ * CONFIG_APM_ALLOW_INTS, we are being really paranoid here! Not only
+ * are interrupts disabled, but all the segment registers (except SS)
+ * are saved and zeroed this means that if the BIOS tries to reference
+ * any data without explicitly loading the segment registers, the kernel
+ * will fault immediately rather than have some unforeseen circumstances
+ * for the rest of the kernel. And it will be very obvious! :-) Doing
+ * this depends on CS referring to the same physical memory as DS so that
+ * DS can be zeroed before the call. Unfortunately, we can't do anything
* about the stack segment/pointer. Also, we tell the compiler that
* everything could change.
*
* Also, we KNOW that for the non error case of apm_bios_call, there
* is no useful data returned in the low order 8 bits of eax.
*/
-#ifdef APM_NOINTS
+#ifndef CONFIG_APM_ALLOW_INTS
# define APM_DO_CLI __cli()
#else
# define APM_DO_CLI
@@ -526,7 +545,15 @@ static int apm_set_power_state(u_short state)
void apm_power_off(void)
{
- if (apm_enabled)
+ /*
+ * smp_hack == 2 means that we would have enabled APM support
+ * except there is more than one processor and so most of
+ * the APM stuff is unsafe. We will still try power down
+ * because is is useful to some people and they know what
+ * they are doing because they booted with the smp-power-off
+ * kernel option.
+ */
+ if (apm_enabled || (smp_hack == 2))
(void) apm_set_power_state(APM_STATE_OFF);
}
@@ -534,12 +561,19 @@ void apm_power_off(void)
/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
static int apm_set_display_power_state(u_short state)
{
- return set_power_state(0x0100, state);
+ int error;
+
+ /* Blank the first display device */
+ error = set_power_state(0x0100, state);
+ if (error == APM_BAD_DEVICE)
+ /* try to blank them all instead */
+ error = set_power_state(0x01ff, state);
+ return error;
}
#endif
#ifdef CONFIG_APM_DO_ENABLE
-static int apm_enable_power_management(void)
+static int __init apm_enable_power_management(void)
{
u32 eax;
@@ -568,12 +602,9 @@ static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
return APM_SUCCESS;
}
-#if 0
-/* not used anywhere */
-static int apm_get_battery_status(u_short which,
+static int apm_get_battery_status(u_short which, u_short *status,
u_short *bat, u_short *life, u_short *nbat)
{
- u_short status;
u32 eax;
u32 ebx;
u32 ecx;
@@ -585,20 +616,20 @@ static int apm_get_battery_status(u_short which,
if (which != 1)
return APM_BAD_DEVICE;
*nbat = 1;
- return apm_get_power_status(&status, bat, life);
+ return apm_get_power_status(status, bat, life);
}
if (apm_bios_call(0x530a, (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 apm_engage_power_management(u_short device)
+static int __init apm_engage_power_management(u_short device)
{
u32 eax;
@@ -747,14 +778,17 @@ static void suspend(void)
unsigned long flags;
int err;
- /* Estimate time zone so that set_time can
- update the clock */
+#ifndef CONFIG_APM_RTC_IS_GMT
+ /*
+ * Estimate time zone so that set_time can update the clock
+ */
save_flags(flags);
clock_cmos_diff = -get_cmos_time();
cli();
clock_cmos_diff += CURRENT_TIME;
got_clock_diff = 1;
restore_flags(flags);
+#endif
err = apm_set_power_state(APM_STATE_SUSPEND);
if (err)
@@ -826,7 +860,7 @@ static void check_events(void)
apm_event_t event;
#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
static unsigned long last_resume = 0;
- static int did_resume = 0;
+ static int ignore_bounce = 0;
#endif
while ((event = get_event()) != 0) {
@@ -838,6 +872,11 @@ static void check_events(void)
printk(KERN_DEBUG "apm: received unknown "
"event 0x%02x\n", event);
#endif
+#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
+ if (ignore_bounce
+ && ((jiffies - last_resume) > BOUNCE_INTERVAL))
+ ignore_bounce = 0;
+#endif
switch (event) {
case APM_SYS_STANDBY:
case APM_USER_STANDBY:
@@ -859,7 +898,7 @@ static void check_events(void)
#endif
case APM_SYS_SUSPEND:
#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
- if (did_resume && ((jiffies - last_resume) < HZ))
+ if (ignore_bounce)
break;
#endif
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
@@ -880,7 +919,7 @@ static void check_events(void)
#endif
#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
last_resume = jiffies;
- did_resume = 1;
+ ignore_bounce = 1;
#endif
set_time();
send_event(event, 0, NULL);
@@ -1139,13 +1178,13 @@ static int do_open(struct inode * inode, struct file * filp)
return 0;
}
-#ifdef CONFIG_PROC_FS
int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
{
char * p;
unsigned short bx;
unsigned short cx;
unsigned short dx;
+ unsigned short nbat;
unsigned short error;
unsigned short ac_line_status = 0xff;
unsigned short battery_status = 0xff;
@@ -1167,13 +1206,8 @@ int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
if (apm_bios_info.version > 0x100) {
battery_flag = (cx >> 8) & 0xff;
if (dx != 0xffff) {
- if ((dx & 0x8000) == 0x8000) {
- units = "min";
- time_units = dx & 0x7ffe;
- } else {
- units = "sec";
- time_units = dx & 0x7fff;
- }
+ units = (dx & 0x8000) ? "min" : "sec";
+ time_units = dx & 0x7fff;
}
}
}
@@ -1228,7 +1262,6 @@ int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
return p - buf;
}
-#endif
void __init apm_setup(char *str, int *dummy)
{
@@ -1244,6 +1277,8 @@ void __init apm_setup(char *str, int *dummy)
str += 3;
if (strncmp(str, "debug", 5) == 0)
debug = !invert;
+ if (strncmp(str, "smp-power-off", 13) == 0)
+ smp_hack = !invert;
str = strchr(str, ',');
if (str != NULL)
str += strspn(str, ", \t");
@@ -1284,17 +1319,18 @@ void __init apm_bios_init(void)
/* BIOS < 1.2 doesn't set cseg_16_len */
if (apm_bios_info.version < 0x102)
- apm_bios_info.cseg_16_len = 0xFFFF; /* 64k */
+ apm_bios_info.cseg_16_len = 0; /* 64k */
if (debug) {
printk(KERN_INFO "apm: entry %x:%lx cseg16 %x dseg %x",
apm_bios_info.cseg, apm_bios_info.offset,
apm_bios_info.cseg_16, apm_bios_info.dseg);
if (apm_bios_info.version > 0x100)
- printk(" cseg len %x, cseg16 len %x, dseg len %x",
+ printk(" cseg len %x, dseg len %x",
apm_bios_info.cseg_len,
- apm_bios_info.cseg_16_len,
apm_bios_info.dseg_len);
+ if (apm_bios_info.version > 0x101)
+ printk(" cseg16 len %x", apm_bios_info.cseg_16_len);
printk("\n");
}
@@ -1302,12 +1338,6 @@ void __init apm_bios_init(void)
printk(KERN_NOTICE "apm: disabled on user request.\n");
return;
}
-#ifdef __SMP__
- if (smp_num_cpus > 1) {
- printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
- return;
- }
-#endif
/*
* Set up a segment that references the real mode segment 0x40
@@ -1317,7 +1347,7 @@ void __init apm_bios_init(void)
*/
set_base(gdt[APM_40 >> 3],
__va((unsigned long)0x40 << 4));
- set_limit(gdt[APM_40 >> 3], 4096 - (0x40 << 4));
+ _set_limit((char *)&gdt[APM_40 >> 3], 4095 - (0x40 << 4));
apm_bios_entry.offset = apm_bios_info.offset;
apm_bios_entry.segment = APM_CS;
@@ -1327,23 +1357,36 @@ void __init apm_bios_init(void)
__va((unsigned long)apm_bios_info.cseg_16 << 4));
set_base(gdt[APM_DS >> 3],
__va((unsigned long)apm_bios_info.dseg << 4));
- if (apm_bios_info.version == 0x100) {
- set_limit(gdt[APM_CS >> 3], 64 * 1024);
- set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
- set_limit(gdt[APM_DS >> 3], 64 * 1024);
- } else {
-#ifdef APM_RELAX_SEGMENTS
+#ifndef APM_RELAX_SEGMENTS
+ if (apm_bios_info.version == 0x100)
+#endif
+ {
/* For ASUS motherboard, Award BIOS rev 110 (and others?) */
- set_limit(gdt[APM_CS >> 3], 64 * 1024);
+ _set_limit((char *)&gdt[APM_CS >> 3], 64 * 1024 - 1);
/* For some unknown machine. */
- set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
+ _set_limit((char *)&gdt[APM_CS_16 >> 3], 64 * 1024 - 1);
/* For the DEC Hinote Ultra CT475 (and others?) */
- set_limit(gdt[APM_DS >> 3], 64 * 1024);
-#else
- set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
- set_limit(gdt[APM_CS_16 >> 3], apm_bios_info.cseg_16_len);
- set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
+ _set_limit((char *)&gdt[APM_DS >> 3], 64 * 1024 - 1);
+ }
+#ifndef APM_RELAX_SEGMENTS
+ else {
+ _set_limit((char *)&gdt[APM_CS >> 3],
+ (apm_bios_info.cseg_len - 1) & 0xffff);
+ _set_limit((char *)&gdt[APM_CS_16 >> 3],
+ (apm_bios_info.cseg_16_len - 1) & 0xffff);
+ _set_limit((char *)&gdt[APM_DS >> 3],
+ (apm_bios_info.dseg_len - 1) & 0xffff);
+ }
+#endif
+#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;
+ }
#endif
+ if (apm_bios_info.version > 0x100) {
/*
* We only support BIOSs up to version 1.2
*/
@@ -1355,7 +1398,7 @@ void __init apm_bios_init(void)
}
}
if (debug) {
- printk(KERN_INFO "apm: onnection version %d.%d\n",
+ printk(KERN_INFO "apm: Connection version %d.%d\n",
(apm_bios_info.version >> 8) & 0xff,
apm_bios_info.version & 0xff );
@@ -1376,23 +1419,23 @@ void __init apm_bios_init(void)
case 3: bat_stat = "charging"; break;
default: bat_stat = "unknown"; break;
}
- printk(KERN_INFO "apm: AC %s, battery status %s, battery life ",
+ printk(KERN_INFO
+ "apm: AC %s, battery status %s, battery life ",
power_stat, bat_stat);
if ((cx & 0xff) == 0xff)
printk("unknown\n");
else
printk("%d%%\n", cx & 0xff);
if (apm_bios_info.version > 0x100) {
- printk("apm: battery flag 0x%02x, battery life ",
+ printk(KERN_INFO
+ "apm: battery flag 0x%02x, battery life ",
(cx >> 8) & 0xff);
if (dx == 0xffff)
printk("unknown\n");
- else {
- if ((dx & 0x8000))
- printk("%d minutes\n", dx & 0x7ffe );
- else
- printk("%d seconds\n", dx & 0x7fff );
- }
+ else
+ printk("%d %s\n", dx & 0x7fff,
+ (dx & 0x8000) ?
+ "minutes" : "seconds");
}
}
}
@@ -1422,10 +1465,9 @@ void __init apm_bios_init(void)
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
add_timer(&apm_timer);
-#ifdef CONFIG_PROC_FS
ent = create_proc_entry("apm", 0, 0);
- ent->get_info = apm_get_info;
-#endif
+ if (ent != NULL)
+ ent->get_info = apm_get_info;
misc_register(&apm_device);