summaryrefslogtreecommitdiffstats
path: root/drivers/char/apm_bios.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/apm_bios.c')
-rw-r--r--drivers/char/apm_bios.c375
1 files changed, 200 insertions, 175 deletions
diff --git a/drivers/char/apm_bios.c b/drivers/char/apm_bios.c
index e9e964c43..08b3a78a2 100644
--- a/drivers/char/apm_bios.c
+++ b/drivers/char/apm_bios.c
@@ -1,7 +1,7 @@
/* -*- linux-c -*-
* APM BIOS driver for Linux
- * Copyright 1994, 1995, 1996 Stephen Rothwell
- * (Stephen.Rothwell@canb.auug.org.au)
+ * Copyright 1994-1998 Stephen Rothwell
+ * (Stephen.Rothwell@canb.auug.org.au)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -13,8 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
- * $Id: apm_bios.c,v 0.22 1995/03/09 14:12:02 sfr Exp $
- *
* October 1995, Rik Faith (faith@cs.unc.edu):
* Minor enhancements and updates (to the patch set) for 1.3.x
* Documentation
@@ -28,6 +26,7 @@
* May 1996, Version 1.2
* Feb 1998, Version 1.3
* Feb 1998, Version 1.4
+ * Aug 1998, Version 1.5
*
* History:
* 0.6b: first version in official kernel, Linux 1.3.46
@@ -50,6 +49,9 @@
* 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by
* Dean Gaudet <dgaudet@arctic.org>.
* C. Scott Ananian <cananian@alumni.princeton.edu> Linux 2.1.87
+ * 1.5: Fix segment register reloading (in case of bad segments saved
+ * across BIOS call).
+ * Stephen ROthwell
*
* APM 1.1 Reference:
*
@@ -158,6 +160,7 @@ extern unsigned long get_cmos_time(void);
* [Confirmed by TI representative]
* U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
* [Confirmed by BIOS disassembly]
+ * [This may work now ...]
* P: Toshiba 1950S: battery life information only gets updated after resume
* P: Midwest Micro Soundbook Elite DX2/66 monochrome: screen blanking
* broken in BIOS [Reported by Garst R. Reese <reese@isn.net>]
@@ -180,6 +183,7 @@ extern unsigned long get_cmos_time(void);
/*
* 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
@@ -207,121 +211,9 @@ extern unsigned long get_cmos_time(void);
#define APM_CHECK_TIMEOUT (HZ)
/*
- * These are the actual BIOS calls in assembler. 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 about the
- * stack segment/pointer. Also, we tell the compiler that everything could
- * change.
+ * Save a segment register away
*/
-#ifdef APM_NOINTS
-# define APM_DO_CLI "cli\n\t"
-#else
-# define APM_DO_CLI
-#endif
-#ifdef APM_ZERO_SEGS
-# define APM_DO_ZERO_SEGS \
- "pushl %%ds\n\t" \
- "pushl %%es\n\t" \
- "pushl %%fs\n\t" \
- "pushl %%gs\n\t" \
- "xorl %%edx, %%edx\n\t" \
- "movl %%dx, %%ds\n\t" \
- "movl %%dx, %%es\n\t" \
- "movl %%dx, %%fs\n\t" \
- "movl %%dx, %%gs\n\t"
-# define APM_DO_RESTORE_SEGS \
- "popl %%gs\n\t" \
- "popl %%fs\n\t" \
- "popl %%es\n\t" \
- "popl %%ds\n\t"
-#else
-# define APM_DO_ZERO_SEGS
-# define APM_DO_RESTORE_SEGS
-#endif
-
-#define APM_BIOS_CALL(error_reg) \
- __asm__ __volatile__( \
- APM_DO_ZERO_SEGS \
- "pushfl\n\t" \
- APM_DO_CLI \
- "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" \
- "setc %%" # error_reg "\n\t" \
- "popfl\n\t" \
- APM_DO_RESTORE_SEGS
-#define APM_BIOS_CALL_END \
- : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory")
-
-#ifdef CONFIG_APM_CPU_IDLE
-#define APM_SET_CPU_IDLE(error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x5305) \
- APM_BIOS_CALL_END
-#endif
-
-#define APM_SET_CPU_BUSY(error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x5306) \
- APM_BIOS_CALL_END
-
-#define APM_SET_POWER_STATE(state, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x5307), "b" (0x0001), "c" (state) \
- APM_BIOS_CALL_END
-
-#ifdef CONFIG_APM_DISPLAY_BLANK
-#define APM_SET_DISPLAY_POWER_STATE(state, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x5307), "b" (0x01ff), "c" (state) \
- APM_BIOS_CALL_END
-#endif
-
-#ifdef CONFIG_APM_DO_ENABLE
-#define APM_ENABLE_POWER_MANAGEMENT(device, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x5308), "b" (device), "c" (1) \
- APM_BIOS_CALL_END
-#endif
-
-#define APM_GET_POWER_STATUS(bx, cx, dx, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx) \
- : "a" (0x530a), "b" (1) \
- APM_BIOS_CALL_END
-
-#define APM_GET_BATTERY_STATUS(which, bx, cx, dx, si, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx), "=S" (si) \
- : "a" (0x530a), "b" (0x8000 | (which)) \
- APM_BIOS_CALL_END
-
-#define APM_GET_EVENT(event, info, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error), "=b" (event), "=c" (info) \
- : "a" (0x530b) \
- APM_BIOS_CALL_END
-
-#define APM_DRIVER_VERSION(ver, ax, error) \
- APM_BIOS_CALL(bl) \
- : "=a" (ax), "=b" (error) \
- : "a" (0x530e), "b" (0), "c" (ver) \
- APM_BIOS_CALL_END
-
-#define APM_ENGAGE_POWER_MANAGEMENT(device, error) \
- APM_BIOS_CALL(al) \
- : "=a" (error) \
- : "a" (0x530f), "b" (device), "c" (1) \
- APM_BIOS_CALL_END
+#define savesegment(seg, where) __asm__ __volatile__("movw %%" #seg ", %0\n" : "=m" (where))
/*
* Forward declarations
@@ -371,7 +263,7 @@ static struct apm_bios_struct * user_list = NULL;
static struct timer_list apm_timer;
-static char driver_version[] = "1.4"; /* no spaces */
+static char driver_version[] = "1.5"; /* no spaces */
#ifdef APM_DEBUG
static char * apm_event_name[] = {
@@ -401,6 +293,7 @@ static struct file_operations apm_bios_fops = {
do_ioctl,
NULL, /* mmap */
do_open,
+ NULL, /* flush */
do_release,
NULL, /* fsync */
NULL /* fasync */
@@ -444,74 +337,197 @@ static const lookup_t error_table[] = {
};
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
-static int apm_driver_version(u_short *val)
+/*
+ * 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
+ * about the stack segment/pointer. Also, we tell the compiler that
+ * everything could change.
+ */
+
+static inline int apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
+ u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
{
- u_short error;
+ u16 old_fs;
+ u16 old_gs;
+ int error;
+
+#ifdef APM_ZERO_SEGS
+ savesegment(fs, old_fs);
+ savesegment(gs, old_gs);
+#endif
+ __asm__ __volatile__(
+ "pushfl\n\t"
+#ifdef APM_NOINTS
+ "cli\n\t"
+#endif
+#ifdef APM_ZERO_SEGS
+ "pushl %%ds\n\t"
+ "pushl %%es\n\t"
+ "movw %9, %%ds\n\t"
+ "movw %9, %%es\n\t"
+ "movw %9, %%fs\n\t"
+ "movw %9, %%gs\n\t"
+#endif
+ "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
+ "movl $0, %%edi\n\t"
+ "jnc 1f\n\t"
+ "movl $1, %%edi\n"
+ "1:\tpopl %%es\n\t"
+ "popl %%ds\n\t"
+ "popfl\n\t"
+ : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
+ "=S" (*esi), "=D" (error)
+ : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+#ifdef APM_ZERO_SEGS
+ , "r" (0)
+#endif
+ : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory");
+#ifdef APM_ZERO_SEGS
+ loadsegment(fs, old_fs);
+ loadsegment(gs, old_gs);
+#endif
+ return error;
+}
+
+/*
+ * This version only returns one value (usually an error code)
+ */
- APM_DRIVER_VERSION(*val, *val, error);
+static inline int apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in, u32 *eax)
+{
+ u16 old_fs;
+ u16 old_gs;
+ int error;
- if (error & 0xff)
- return (*val >> 8);
+#ifdef APM_ZERO_SEGS
+ savesegment(fs, old_fs);
+ savesegment(gs, old_gs);
+#endif
+ __asm__ __volatile__(
+ "pushfl\n\t"
+#ifdef APM_NOINTS
+ "cli\n\t"
+#endif
+#ifdef APM_ZERO_SEGS
+ "pushl %%ds\n\t"
+ "pushl %%es\n\t"
+ "movw %5, %%ds\n\t"
+ "movw %5, %%es\n\t"
+ "movw %5, %%fs\n\t"
+ "movw %5, %%gs\n\t"
+#endif
+ "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
+ "movl $0, %%edi\n\t"
+ "jnc 1f\n\t"
+ "movl $1, %%edi\n"
+ "1:\tpopl %%es\n\t"
+ "popl %%ds\n\t"
+ "popfl\n\t"
+ : "=a" (*eax), "=D" (error)
+ : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+#ifdef APM_ZERO_SEGS
+ , "r" (0)
+#endif
+ : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory");
+#ifdef APM_ZERO_SEGS
+ loadsegment(fs, old_fs);
+ loadsegment(gs, old_gs);
+#endif
+ return error;
+}
+
+static int apm_driver_version(u_short *val)
+{
+ int error;
+ u32 eax;
+
+ error = apm_bios_call_simple(0x530e, 0, *val, &eax);
+ if (error)
+ return (eax >> 8) & 0xff;
+ *val = eax;
return APM_SUCCESS;
}
static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
{
- u_short error;
+ int error;
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 dummy;
- APM_GET_EVENT(*event, *info, error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call(0x530b, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy);
+ if (error)
+ return (eax >> 8) & 0xff;
+ *event = ebx;
if (apm_bios_info.version < 0x0102)
*info = ~0; /* indicate info not valid */
+ else
+ *info = ecx;
return APM_SUCCESS;
}
-int apm_set_power_state(u_short state)
+static int set_power_state(u_short what, u_short state)
{
- u_short error;
+ int error;
+ u32 eax;
- APM_SET_POWER_STATE(state, error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call_simple(0x5307, what, state, &eax);
+ if (error)
+ return (eax >> 8) & 0xff;
return APM_SUCCESS;
}
+int apm_set_power_state(u_short state)
+{
+ return set_power_state(0x0001, state);
+}
+
#ifdef CONFIG_APM_DISPLAY_BLANK
/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
static int apm_set_display_power_state(u_short state)
{
- u_short error;
-
- APM_SET_DISPLAY_POWER_STATE(state, error);
- if (error & 0xff)
- return (error >> 8);
- return APM_SUCCESS;
+ return set_power_state(0x01ff, state);
}
#endif
#ifdef CONFIG_APM_DO_ENABLE
/* Called by apm_setup if apm_enabled will be true. */
-static inline int apm_enable_power_management(void)
+static int apm_enable_power_management(void)
{
- u_short error;
+ int error;
+ u32 eax;
- APM_ENABLE_POWER_MANAGEMENT((apm_bios_info.version > 0x100)
- ? 0x0001 : 0xffff,
- error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call_simple(0x5308,
+ (apm_bios_info.version > 0x100) ? 0x0001 : 0xffff, 1, &eax);
+ if (error)
+ return (eax >> 8) & 0xff;
return APM_SUCCESS;
}
#endif
static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
{
- u_short error;
+ int error;
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+ u32 dummy;
- APM_GET_POWER_STATUS(*status, *bat, *life, error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call(0x530a, 1, 0, &eax, &ebx, &ecx, &edx, &dummy);
+ if (error)
+ return (eax >> 8) & 0xff;
+ *status = ebx;
+ *bat = ecx;
+ *life = edx;
return APM_SUCCESS;
}
@@ -520,29 +536,40 @@ static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
static int apm_get_battery_status(u_short which,
u_short *bat, u_short *life, u_short *nbat)
{
- u_short status, error;
+ u_short status;
+ int error;
+ 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;
+ if (which != 1)
+ return APM_BAD_DEVICE;
*nbat = 1;
return apm_get_power_status(&status, bat, life);
}
- APM_GET_BATTERY_STATUS(which, status, *bat, *life, *nbat, error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call(0x530a, (0x8000 | (which)), 0, &eax, &ebx, &ecx, &edx, &esi);
+ if (error)
+ return (eax >> 8) & 0xff;
+ *bat = ecx;
+ *life = edx;
+ *nbat = esi;
return APM_SUCCESS;
}
#endif
-static inline int apm_engage_power_management(u_short device)
+static int apm_engage_power_management(u_short device)
{
- u_short error;
+ int error;
+ u32 eax;
- APM_ENGAGE_POWER_MANAGEMENT(device, error);
- if (error & 0xff)
- return (error >> 8);
+ error = apm_bios_call_simple(0x530f, device, 1, &eax);
+ if (error)
+ return (eax >> 8) & 0xff;
return APM_SUCCESS;
}
@@ -849,13 +876,14 @@ static void do_apm_timer(unsigned long unused)
int apm_do_idle(void)
{
#ifdef CONFIG_APM_CPU_IDLE
- unsigned short error;
+ int error;
+ u32 dummy;
if (!apm_enabled)
return 0;
- APM_SET_CPU_IDLE(error);
- if (error & 0xff)
+ error = apm_bios_call_simple(0x5305, 0, 0, &dummy);
+ if (error)
return 0;
clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
@@ -869,19 +897,16 @@ int apm_do_idle(void)
void apm_do_busy(void)
{
#ifdef CONFIG_APM_CPU_IDLE
- unsigned short error;
-
- if (!apm_enabled)
- return;
+ u32 dummy;
+ if (apm_enabled
#ifndef ALWAYS_CALL_BUSY
- if (!clock_slowed)
- return;
+ && clock_slowed
#endif
-
- APM_SET_CPU_BUSY(error);
-
- clock_slowed = 0;
+ ) {
+ (void) apm_bios_call_simple(0x5306, 0, 0, &dummy);
+ clock_slowed = 0;
+ }
#endif
}
@@ -1241,11 +1266,11 @@ __initfunc(void apm_bios_init(void))
*/
apm_bios_info.version = 0x0102;
error = apm_driver_version(&apm_bios_info.version);
- if (error != 0) { /* Fall back to an APM 1.1 connection. */
+ if (error != APM_SUCCESS) { /* Fall back to an APM 1.1 connection. */
apm_bios_info.version = 0x0101;
error = apm_driver_version(&apm_bios_info.version);
}
- if (error != 0) /* Fall back to an APM 1.0 connection. */
+ if (error != APM_SUCCESS) /* Fall back to an APM 1.0 connection. */
apm_bios_info.version = 0x100;
else {
apm_engage_power_management(0x0001);