/* -*- linux-c -*-
|
/* -*- linux-c -*-
|
* APM BIOS driver for Linux
|
* APM BIOS driver for Linux
|
* Copyright 1994-1999 Stephen Rothwell
|
* Copyright 1994-1999 Stephen Rothwell
|
* (Stephen.Rothwell@canb.auug.org.au)
|
* (Stephen.Rothwell@canb.auug.org.au)
|
* Development of this driver was funded by NEC Australia P/L
|
* Development of this driver was funded by NEC Australia P/L
|
* and NEC Corporation
|
* and NEC Corporation
|
*
|
*
|
* This program is free software; you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the
|
* Free Software Foundation; either version 2, or (at your option) any
|
* Free Software Foundation; either version 2, or (at your option) any
|
* later version.
|
* later version.
|
*
|
*
|
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* General Public License for more details.
|
* General Public License for more details.
|
*
|
*
|
* October 1995, Rik Faith (faith@cs.unc.edu):
|
* October 1995, Rik Faith (faith@cs.unc.edu):
|
* Minor enhancements and updates (to the patch set) for 1.3.x
|
* Minor enhancements and updates (to the patch set) for 1.3.x
|
* Documentation
|
* Documentation
|
* January 1996, Rik Faith (faith@cs.unc.edu):
|
* January 1996, Rik Faith (faith@cs.unc.edu):
|
* Make /proc/apm easy to format (bump driver version)
|
* Make /proc/apm easy to format (bump driver version)
|
* March 1996, Rik Faith (faith@cs.unc.edu):
|
* March 1996, Rik Faith (faith@cs.unc.edu):
|
* Prohibit APM BIOS calls unless apm_enabled.
|
* Prohibit APM BIOS calls unless apm_enabled.
|
* (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
|
* (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
|
* April 1996, Stephen Rothwell (Stephen.Rothwell@canb.auug.org.au)
|
* April 1996, Stephen Rothwell (Stephen.Rothwell@canb.auug.org.au)
|
* Version 1.0 and 1.1
|
* Version 1.0 and 1.1
|
* May 1996, Version 1.2
|
* May 1996, Version 1.2
|
*
|
*
|
* History:
|
* History:
|
* 0.6b: first version in official kernel, Linux 1.3.46
|
* 0.6b: first version in official kernel, Linux 1.3.46
|
* 0.7: changed /proc/apm format, Linux 1.3.58
|
* 0.7: changed /proc/apm format, Linux 1.3.58
|
* 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
|
* 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
|
* 0.9: only call bios if bios is present, Linux 1.3.72
|
* 0.9: only call bios if bios is present, Linux 1.3.72
|
* 1.0: use fixed device number, consolidate /proc/apm into this file,
|
* 1.0: use fixed device number, consolidate /proc/apm into this file,
|
* Linux 1.3.85
|
* Linux 1.3.85
|
* 1.1: support user-space standby and suspend, power off after system
|
* 1.1: support user-space standby and suspend, power off after system
|
* halted, Linux 1.3.98
|
* halted, Linux 1.3.98
|
* 1.2: When resetting RTC after resume, take care so that the time
|
* 1.2: When resetting RTC after resume, take care so that the time
|
* is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth
|
* is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth
|
* <jtoth@princeton.edu>); improve interaction between
|
* <jtoth@princeton.edu>); improve interaction between
|
* screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4
|
* screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4
|
* 1.2a: Fix OOPs on power off with no APM BIOS
|
* 1.2a: Fix OOPs on power off with no APM BIOS
|
* Jan Echternach <echter@informatik.uni-rostock.de>
|
* Jan Echternach <echter@informatik.uni-rostock.de>
|
*
|
*
|
* Reference:
|
* Reference:
|
*
|
*
|
* Intel Corporation, Microsoft Corporation. Advanced Power Management
|
* Intel Corporation, Microsoft Corporation. Advanced Power Management
|
* (APM) BIOS Interface Specification, Revision 1.1, September 1993.
|
* (APM) BIOS Interface Specification, Revision 1.1, September 1993.
|
* Intel Order Number 241704-001. Microsoft Part Number 781-110-X01.
|
* Intel Order Number 241704-001. Microsoft Part Number 781-110-X01.
|
*
|
*
|
* [This document is available free from Intel by calling 800.628.8686 (fax
|
* [This document is available free from Intel by calling 800.628.8686 (fax
|
* 916.356.6100) or 800.548.4725; or via anonymous ftp from
|
* 916.356.6100) or 800.548.4725; or via anonymous ftp from
|
* ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc. It is also
|
* ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc. It is also
|
* available from Microsoft by calling 206.882.8080.]
|
* available from Microsoft by calling 206.882.8080.]
|
*
|
*
|
*/
|
*/
|
|
|
#include <linux/config.h>
|
#include <linux/config.h>
|
#include <linux/module.h>
|
#include <linux/module.h>
|
|
|
#include <asm/system.h>
|
#include <asm/system.h>
|
#include <asm/segment.h>
|
#include <asm/segment.h>
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/stddef.h>
|
#include <linux/stddef.h>
|
#include <linux/timer.h>
|
#include <linux/timer.h>
|
#include <linux/fcntl.h>
|
#include <linux/fcntl.h>
|
#include <linux/malloc.h>
|
#include <linux/malloc.h>
|
#include <linux/linkage.h>
|
#include <linux/linkage.h>
|
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
#include <linux/stat.h>
|
#include <linux/stat.h>
|
#include <linux/proc_fs.h>
|
#include <linux/proc_fs.h>
|
#endif
|
#endif
|
#include <linux/miscdevice.h>
|
#include <linux/miscdevice.h>
|
#include <linux/apm_bios.h>
|
#include <linux/apm_bios.h>
|
|
|
static struct symbol_table apm_syms = {
|
static struct symbol_table apm_syms = {
|
#include <linux/symtab_begin.h>
|
#include <linux/symtab_begin.h>
|
X(apm_register_callback),
|
X(apm_register_callback),
|
X(apm_unregister_callback),
|
X(apm_unregister_callback),
|
#include <linux/symtab_end.h>
|
#include <linux/symtab_end.h>
|
};
|
};
|
|
|
extern unsigned long get_cmos_time(void);
|
extern unsigned long get_cmos_time(void);
|
|
|
/*
|
/*
|
* The apm_bios device is one of the misc char devices.
|
* The apm_bios device is one of the misc char devices.
|
* This is its minor number.
|
* This is its minor number.
|
*/
|
*/
|
#define APM_MINOR_DEV 134
|
#define APM_MINOR_DEV 134
|
|
|
/* Configurable options:
|
/* Configurable options:
|
*
|
*
|
* CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
|
* CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
|
* This is necessary on the NEC Versa M series, which generates these when
|
* This is necessary on the NEC Versa M series, which generates these when
|
* resuming from SYSTEM SUSPEND. However, enabling this on other laptops
|
* resuming from SYSTEM SUSPEND. However, enabling this on other laptops
|
* will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
|
* will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
|
* USER SUSPEND is ignored -- this may prevent the APM driver from updating
|
* USER SUSPEND is ignored -- this may prevent the APM driver from updating
|
* the system time on a RESUME.
|
* the system time on a RESUME.
|
*
|
*
|
* CONFIG_APM_DO_ENABLE: enable APM features at boot time. From page 36 of
|
* CONFIG_APM_DO_ENABLE: enable APM features at boot time. From page 36 of
|
* the specification: "When disabled, the APM BIOS does not automatically
|
* the specification: "When disabled, the APM BIOS does not automatically
|
* power manage devices, enter the Standby State, enter the Suspend State,
|
* power manage devices, enter the Standby State, enter the Suspend State,
|
* or take power saving steps in response to CPU Idle calls." This driver
|
* 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
|
* will make CPU Idle calls when Linux is idle (unless this feature is
|
* turned off -- see below). This should always save battery power, but
|
* turned off -- see below). This should always save battery power, but
|
* more complicated APM features will be dependent on your BIOS
|
* more complicated APM features will be dependent on your BIOS
|
* implementation. You may need to turn this option off if your computer
|
* 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
|
* 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
|
* 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
|
* 33/C or a Toshiba T400CDT. This is off by default since most machines
|
* do fine without this feature.
|
* do fine without this feature.
|
*
|
*
|
* CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the
|
* 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,
|
* 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
|
* 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.,
|
* 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
|
* 333 mS). On some machines, this will cause a hang at boot time or
|
* whenever the CPU becomes idle.
|
* whenever the CPU becomes idle.
|
*
|
*
|
* CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM. Some
|
* 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
|
* 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 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
|
* 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
|
* problems have been reported when using this option with gpm (if you'd
|
* like to debug this, please do so).
|
* like to debug this, please do so).
|
*
|
*
|
* CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist
|
* CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist
|
* on returning multiple suspend/standby events whenever one occurs. We
|
* on returning multiple suspend/standby events whenever one occurs. We
|
* really only need one at a time, so just ignore any beyond the first.
|
* really only need one at a time, so just ignore any beyond the first.
|
* This is probably safe on most laptops.
|
* This is probably safe on most laptops.
|
*
|
*
|
* If you are debugging the APM support for your laptop, note that code for
|
* 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
|
* 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.
|
* #undef these on the next line to avoid recompiling the whole kernel.
|
*
|
*
|
*/
|
*/
|
|
|
/* KNOWN PROBLEM MACHINES:
|
/* KNOWN PROBLEM MACHINES:
|
*
|
*
|
* U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
|
* U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
|
* [Confirmed by TI representative]
|
* [Confirmed by TI representative]
|
* U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
|
* U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
|
* [Confirmed by BIOS disassembly]
|
* [Confirmed by BIOS disassembly]
|
* P: Toshiba 1950S: battery life information only gets updated after resume
|
* P: Toshiba 1950S: battery life information only gets updated after resume
|
*
|
*
|
* Legend: U = unusable with APM patches
|
* Legend: U = unusable with APM patches
|
* P = partially usable with APM patches
|
* P = partially usable with APM patches
|
*/
|
*/
|
|
|
/*
|
/*
|
* Define to have debug messages.
|
* Define to have debug messages.
|
*/
|
*/
|
#undef APM_DEBUG
|
#undef APM_DEBUG
|
|
|
/*
|
/*
|
* Define to always call the APM BIOS busy routine even if the clock was
|
* Define to always call the APM BIOS busy routine even if the clock was
|
* not slowed by the idle routine.
|
* not slowed by the idle routine.
|
*/
|
*/
|
#define ALWAYS_CALL_BUSY
|
#define ALWAYS_CALL_BUSY
|
|
|
/*
|
/*
|
* Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call
|
* Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call
|
* should turn interrupts on before it does a 'hlt').
|
* should turn interrupts on before it does a 'hlt').
|
*/
|
*/
|
#define APM_NOINTS
|
#define APM_NOINTS
|
|
|
/*
|
/*
|
* Define to make the APM BIOS calls zero all data segment registers (do
|
* Define to make the APM BIOS calls zero all data segment registers (do
|
* that if an incorrect BIOS implementation will cause a kernel panic if it
|
* that if an incorrect BIOS implementation will cause a kernel panic if it
|
* tries to write to arbitrary memory).
|
* tries to write to arbitrary memory).
|
*/
|
*/
|
#define APM_ZERO_SEGS
|
#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
|
* supposed to provide limit information that it recognizes. Many machines
|
* do this correctly, but many others do not restrict themselves to their
|
* do this correctly, but many others do not restrict themselves to their
|
* claimed limit. When this happens, they will cause a segmentation
|
* claimed limit. When this happens, they will cause a segmentation
|
* violation in the kernel at boot time. Most BIOS's, however, will
|
* violation in the kernel at boot time. Most BIOS's, however, will
|
* respect a 64k limit, so we use that. If you want to be pedantic and
|
* respect a 64k limit, so we use that. If you want to be pedantic and
|
* hold your BIOS to its claims, then undefine this.
|
* hold your BIOS to its claims, then undefine this.
|
*/
|
*/
|
#define APM_RELAX_SEGMENTS
|
#define APM_RELAX_SEGMENTS
|
|
|
/*
|
/*
|
* Need to poll the APM BIOS every second
|
* Need to poll the APM BIOS every second
|
*/
|
*/
|
#define APM_CHECK_TIMEOUT (HZ)
|
#define APM_CHECK_TIMEOUT (HZ)
|
|
|
/*
|
/*
|
* These are the actual BIOS calls in assembler. Depending on
|
* These are the actual BIOS calls in assembler. Depending on
|
* APM_ZERO_SEGS and APM_NOINTS, we are being really paranoid here! Not
|
* APM_ZERO_SEGS and APM_NOINTS, we are being really paranoid here! Not
|
* only are interrupts disabled, but all the segment registers (except SS)
|
* 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
|
* are saved and zeroed this means that if the BIOS tries to reference any
|
* data without explicitly loading the segment registers, the kernel will
|
* data without explicitly loading the segment registers, the kernel will
|
* fault immediately rather than have some unforeseen circumstances for the
|
* fault immediately rather than have some unforeseen circumstances for the
|
* rest of the kernel. And it will be very obvious! :-) Doing this
|
* 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
|
* 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
|
* be zeroed before the call. Unfortunately, we can't do anything about the
|
* stack segment/pointer. Also, we tell the compiler that everything could
|
* stack segment/pointer. Also, we tell the compiler that everything could
|
* change.
|
* change.
|
*/
|
*/
|
#ifdef APM_NOINTS
|
#ifdef APM_NOINTS
|
# define APM_DO_CLI "cli\n\t"
|
# define APM_DO_CLI "cli\n\t"
|
#else
|
#else
|
# define APM_DO_CLI
|
# define APM_DO_CLI
|
#endif
|
#endif
|
#ifdef APM_ZERO_SEGS
|
#ifdef APM_ZERO_SEGS
|
# define APM_DO_ZERO_SEGS \
|
# define APM_DO_ZERO_SEGS \
|
"pushl %%ds\n\t" \
|
"pushl %%ds\n\t" \
|
"pushl %%es\n\t" \
|
"pushl %%es\n\t" \
|
"pushl %%fs\n\t" \
|
"pushl %%fs\n\t" \
|
"pushl %%gs\n\t" \
|
"pushl %%gs\n\t" \
|
"xorl %%edx, %%edx\n\t" \
|
"xorl %%edx, %%edx\n\t" \
|
"mov %%dx, %%ds\n\t" \
|
"mov %%dx, %%ds\n\t" \
|
"mov %%dx, %%es\n\t" \
|
"mov %%dx, %%es\n\t" \
|
"mov %%dx, %%fs\n\t" \
|
"mov %%dx, %%fs\n\t" \
|
"mov %%dx, %%gs\n\t"
|
"mov %%dx, %%gs\n\t"
|
# define APM_DO_RESTORE_SEGS \
|
# define APM_DO_RESTORE_SEGS \
|
"popl %%gs\n\t" \
|
"popl %%gs\n\t" \
|
"popl %%fs\n\t" \
|
"popl %%fs\n\t" \
|
"popl %%es\n\t" \
|
"popl %%es\n\t" \
|
"popl %%ds\n\t"
|
"popl %%ds\n\t"
|
#else
|
#else
|
# define APM_DO_ZERO_SEGS
|
# define APM_DO_ZERO_SEGS
|
# define APM_DO_RESTORE_SEGS
|
# define APM_DO_RESTORE_SEGS
|
#endif
|
#endif
|
|
|
#define APM_BIOS_CALL(error_reg) \
|
#define APM_BIOS_CALL(error_reg) \
|
__asm__ __volatile__( \
|
__asm__ __volatile__( \
|
APM_DO_ZERO_SEGS \
|
APM_DO_ZERO_SEGS \
|
"pushfl\n\t" \
|
"pushfl\n\t" \
|
APM_DO_CLI \
|
APM_DO_CLI \
|
"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" \
|
"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" \
|
"setc %%" # error_reg "\n\t" \
|
"setc %%" # error_reg "\n\t" \
|
"popfl\n\t" \
|
"popfl\n\t" \
|
APM_DO_RESTORE_SEGS
|
APM_DO_RESTORE_SEGS
|
#define APM_BIOS_CALL_END \
|
#define APM_BIOS_CALL_END \
|
: "ax", "bx", "cx", "dx", "si", "di", "bp", "memory")
|
: "ax", "bx", "cx", "dx", "si", "di", "bp", "memory")
|
|
|
#ifdef CONFIG_APM_CPU_IDLE
|
#ifdef CONFIG_APM_CPU_IDLE
|
#define APM_SET_CPU_IDLE(error) \
|
#define APM_SET_CPU_IDLE(error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x5305) \
|
: "a" (0x5305) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
#endif
|
#endif
|
|
|
#define APM_SET_CPU_BUSY(error) \
|
#define APM_SET_CPU_BUSY(error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x5306) \
|
: "a" (0x5306) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
#define APM_SET_POWER_STATE(state, error) \
|
#define APM_SET_POWER_STATE(state, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x5307), "b" (0x0001), "c" (state) \
|
: "a" (0x5307), "b" (0x0001), "c" (state) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
#define APM_SET_DISPLAY_POWER_STATE(state, error) \
|
#define APM_SET_DISPLAY_POWER_STATE(state, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x5307), "b" (0x01ff), "c" (state) \
|
: "a" (0x5307), "b" (0x01ff), "c" (state) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
#endif
|
#endif
|
|
|
#ifdef CONFIG_APM_DO_ENABLE
|
#ifdef CONFIG_APM_DO_ENABLE
|
#define APM_ENABLE_POWER_MANAGEMENT(device, error) \
|
#define APM_ENABLE_POWER_MANAGEMENT(device, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x5308), "b" (device), "c" (1) \
|
: "a" (0x5308), "b" (device), "c" (1) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
#endif
|
#endif
|
|
|
#define APM_GET_POWER_STATUS(bx, cx, dx, error) \
|
#define APM_GET_POWER_STATUS(bx, cx, dx, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx) \
|
: "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx) \
|
: "a" (0x530a), "b" (1) \
|
: "a" (0x530a), "b" (1) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
#define APM_GET_EVENT(event, error) \
|
#define APM_GET_EVENT(event, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error), "=b" (event) \
|
: "=a" (error), "=b" (event) \
|
: "a" (0x530b) \
|
: "a" (0x530b) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
#define APM_DRIVER_VERSION(ver, ax, error) \
|
#define APM_DRIVER_VERSION(ver, ax, error) \
|
APM_BIOS_CALL(bl) \
|
APM_BIOS_CALL(bl) \
|
: "=a" (ax), "=b" (error) \
|
: "=a" (ax), "=b" (error) \
|
: "a" (0x530e), "b" (0), "c" (ver) \
|
: "a" (0x530e), "b" (0), "c" (ver) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
#define APM_ENGAGE_POWER_MANAGEMENT(device, error) \
|
#define APM_ENGAGE_POWER_MANAGEMENT(device, error) \
|
APM_BIOS_CALL(al) \
|
APM_BIOS_CALL(al) \
|
: "=a" (error) \
|
: "=a" (error) \
|
: "a" (0x530f), "b" (device), "c" (1) \
|
: "a" (0x530f), "b" (device), "c" (1) \
|
APM_BIOS_CALL_END
|
APM_BIOS_CALL_END
|
|
|
/*
|
/*
|
* Forward declarations
|
* Forward declarations
|
*/
|
*/
|
static void suspend(void);
|
static void suspend(void);
|
static void standby(void);
|
static void standby(void);
|
static void set_time(void);
|
static void set_time(void);
|
|
|
static void check_events(void);
|
static void check_events(void);
|
static void do_apm_timer(unsigned long);
|
static void do_apm_timer(unsigned long);
|
|
|
static int do_open(struct inode *, struct file *);
|
static int do_open(struct inode *, struct file *);
|
static void do_release(struct inode *, struct file *);
|
static void do_release(struct inode *, struct file *);
|
static int do_read(struct inode *, struct file *, char *, int);
|
static int do_read(struct inode *, struct file *, char *, int);
|
static int do_select(struct inode *, struct file *, int,
|
static int do_select(struct inode *, struct file *, int,
|
select_table *);
|
select_table *);
|
static int do_ioctl(struct inode *, struct file *, u_int, u_long);
|
static int do_ioctl(struct inode *, struct file *, u_int, u_long);
|
|
|
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
static int apm_get_info(char *, char **, off_t, int, int);
|
static int apm_get_info(char *, char **, off_t, int, int);
|
#endif
|
#endif
|
|
|
extern int apm_register_callback(int (*)(apm_event_t));
|
extern int apm_register_callback(int (*)(apm_event_t));
|
extern void apm_unregister_callback(int (*)(apm_event_t));
|
extern void apm_unregister_callback(int (*)(apm_event_t));
|
|
|
/*
|
/*
|
* Local variables
|
* Local variables
|
*/
|
*/
|
static asmlinkage struct {
|
static asmlinkage struct {
|
unsigned long offset;
|
unsigned long offset;
|
unsigned short segment;
|
unsigned short segment;
|
} apm_bios_entry;
|
} apm_bios_entry;
|
static int apm_enabled = 0;
|
static int apm_enabled = 0;
|
#ifdef CONFIG_APM_CPU_IDLE
|
#ifdef CONFIG_APM_CPU_IDLE
|
static int clock_slowed = 0;
|
static int clock_slowed = 0;
|
#endif
|
#endif
|
static int suspends_pending = 0;
|
static int suspends_pending = 0;
|
static int standbys_pending = 0;
|
static int standbys_pending = 0;
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
static int waiting_for_resume = 0;
|
static int waiting_for_resume = 0;
|
#endif
|
#endif
|
|
|
static long clock_cmos_diff;
|
static long clock_cmos_diff;
|
static int got_clock_diff = 0;
|
static int got_clock_diff = 0;
|
|
|
static struct wait_queue * process_list = NULL;
|
static struct wait_queue * process_list = NULL;
|
static struct apm_bios_struct * user_list = NULL;
|
static struct apm_bios_struct * user_list = NULL;
|
|
|
static struct timer_list apm_timer;
|
static struct timer_list apm_timer;
|
|
|
static char driver_version[] = "1.2";/* no spaces */
|
static char driver_version[] = "1.2";/* no spaces */
|
|
|
#ifdef APM_DEBUG
|
#ifdef APM_DEBUG
|
static char * apm_event_name[] = {
|
static char * apm_event_name[] = {
|
"system standby",
|
"system standby",
|
"system suspend",
|
"system suspend",
|
"normal resume",
|
"normal resume",
|
"critical resume",
|
"critical resume",
|
"low battery",
|
"low battery",
|
"power status change",
|
"power status change",
|
"update time",
|
"update time",
|
"critical suspend",
|
"critical suspend",
|
"user standby",
|
"user standby",
|
"user suspend",
|
"user suspend",
|
"system standby resume"
|
"system standby resume"
|
};
|
};
|
#define NR_APM_EVENT_NAME \
|
#define NR_APM_EVENT_NAME \
|
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
|
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
|
#endif
|
#endif
|
|
|
static struct file_operations apm_bios_fops = {
|
static struct file_operations apm_bios_fops = {
|
NULL, /* lseek */
|
NULL, /* lseek */
|
do_read,
|
do_read,
|
NULL, /* write */
|
NULL, /* write */
|
NULL, /* readdir */
|
NULL, /* readdir */
|
do_select,
|
do_select,
|
do_ioctl,
|
do_ioctl,
|
NULL, /* mmap */
|
NULL, /* mmap */
|
do_open,
|
do_open,
|
do_release,
|
do_release,
|
NULL, /* fsync */
|
NULL, /* fsync */
|
NULL /* fasync */
|
NULL /* fasync */
|
};
|
};
|
|
|
static struct miscdevice apm_device = {
|
static struct miscdevice apm_device = {
|
APM_MINOR_DEV,
|
APM_MINOR_DEV,
|
"apm",
|
"apm",
|
&apm_bios_fops
|
&apm_bios_fops
|
};
|
};
|
|
|
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
static struct proc_dir_entry apm_proc_entry = {
|
static struct proc_dir_entry apm_proc_entry = {
|
0, 3, "apm", S_IFREG | S_IRUGO, 1, 0, 0, 0, 0, apm_get_info
|
0, 3, "apm", S_IFREG | S_IRUGO, 1, 0, 0, 0, 0, apm_get_info
|
};
|
};
|
#endif
|
#endif
|
|
|
typedef struct callback_list_t {
|
typedef struct callback_list_t {
|
int (* callback)(apm_event_t);
|
int (* callback)(apm_event_t);
|
struct callback_list_t * next;
|
struct callback_list_t * next;
|
} callback_list_t;
|
} callback_list_t;
|
|
|
static callback_list_t * callback_list = NULL;
|
static callback_list_t * callback_list = NULL;
|
|
|
typedef struct lookup_t {
|
typedef struct lookup_t {
|
int key;
|
int key;
|
char * msg;
|
char * msg;
|
} lookup_t;
|
} lookup_t;
|
|
|
static const lookup_t error_table[] = {
|
static const lookup_t error_table[] = {
|
/* N/A { APM_SUCCESS, "Operation succeeded" }, */
|
/* N/A { APM_SUCCESS, "Operation succeeded" }, */
|
{ APM_DISABLED, "Power management disabled" },
|
{ APM_DISABLED, "Power management disabled" },
|
{ APM_CONNECTED, "Real mode interface already connected" },
|
{ APM_CONNECTED, "Real mode interface already connected" },
|
{ APM_NOT_CONNECTED, "Interface not connected" },
|
{ APM_NOT_CONNECTED, "Interface not connected" },
|
{ APM_16_CONNECTED, "16 bit interface already connected" },
|
{ APM_16_CONNECTED, "16 bit interface already connected" },
|
/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
|
/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
|
{ APM_32_CONNECTED, "32 bit interface already connected" },
|
{ APM_32_CONNECTED, "32 bit interface already connected" },
|
{ APM_32_UNSUPPORTED, "32 bit interface not supported" },
|
{ APM_32_UNSUPPORTED, "32 bit interface not supported" },
|
{ APM_BAD_DEVICE, "Unrecognized device ID" },
|
{ APM_BAD_DEVICE, "Unrecognized device ID" },
|
{ APM_BAD_PARAM, "Parameter out of range" },
|
{ APM_BAD_PARAM, "Parameter out of range" },
|
{ APM_NOT_ENGAGED, "Interface not engaged" },
|
{ APM_NOT_ENGAGED, "Interface not engaged" },
|
{ APM_BAD_STATE, "Unable to enter requested state" },
|
{ APM_BAD_STATE, "Unable to enter requested state" },
|
/* N/A { APM_NO_EVENTS, "No events pending" }, */
|
/* N/A { APM_NO_EVENTS, "No events pending" }, */
|
{ APM_NOT_PRESENT, "No APM present" }
|
{ APM_NOT_PRESENT, "No APM present" }
|
};
|
};
|
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
|
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
|
|
|
static int apm_driver_version(u_short *val)
|
static int apm_driver_version(u_short *val)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_DRIVER_VERSION(*val, *val, error);
|
APM_DRIVER_VERSION(*val, *val, error);
|
|
|
if (error & 0xff)
|
if (error & 0xff)
|
return (*val >> 8);
|
return (*val >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
|
|
static int apm_get_event(apm_event_t *event)
|
static int apm_get_event(apm_event_t *event)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_GET_EVENT(*event, error);
|
APM_GET_EVENT(*event, error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
|
|
static int apm_set_power_state(u_short state)
|
static int apm_set_power_state(u_short state)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_SET_POWER_STATE(state, error);
|
APM_SET_POWER_STATE(state, error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
|
|
#ifdef CONFIG_APM_POWER_OFF
|
#ifdef CONFIG_APM_POWER_OFF
|
void apm_power_off(void)
|
void apm_power_off(void)
|
{
|
{
|
if (apm_enabled)
|
if (apm_enabled)
|
(void) apm_set_power_state(APM_STATE_OFF);
|
(void) apm_set_power_state(APM_STATE_OFF);
|
}
|
}
|
#endif
|
#endif
|
|
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
|
/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
|
static int apm_set_display_power_state(u_short state)
|
static int apm_set_display_power_state(u_short state)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_SET_DISPLAY_POWER_STATE(state, error);
|
APM_SET_DISPLAY_POWER_STATE(state, error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
#endif
|
#endif
|
|
|
#ifdef CONFIG_APM_DO_ENABLE
|
#ifdef CONFIG_APM_DO_ENABLE
|
/* Called by apm_setup if apm_enabled will be true. */
|
/* Called by apm_setup if apm_enabled will be true. */
|
static int apm_enable_power_management(void)
|
static int apm_enable_power_management(void)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_ENABLE_POWER_MANAGEMENT((apm_bios_info.version > 0x100)
|
APM_ENABLE_POWER_MANAGEMENT((apm_bios_info.version > 0x100)
|
? 0x0001 : 0xffff,
|
? 0x0001 : 0xffff,
|
error);
|
error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
#endif
|
#endif
|
|
|
static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
|
static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_GET_POWER_STATUS(*status, *bat, *life, error);
|
APM_GET_POWER_STATUS(*status, *bat, *life, error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
|
|
static int apm_engage_power_management(u_short device)
|
static int apm_engage_power_management(u_short device)
|
{
|
{
|
u_short error;
|
u_short error;
|
|
|
APM_ENGAGE_POWER_MANAGEMENT(device, error);
|
APM_ENGAGE_POWER_MANAGEMENT(device, error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return (error >> 8);
|
return (error >> 8);
|
return APM_SUCCESS;
|
return APM_SUCCESS;
|
}
|
}
|
|
|
static void apm_error(char *str, int err)
|
static void apm_error(char *str, int err)
|
{
|
{
|
int i;
|
int i;
|
|
|
for (i = 0; i < ERROR_COUNT; i++)
|
for (i = 0; i < ERROR_COUNT; i++)
|
if (error_table[i].key == err) break;
|
if (error_table[i].key == err) break;
|
if (i < ERROR_COUNT)
|
if (i < ERROR_COUNT)
|
printk("apm_bios: %s: %s\n", str, error_table[i].msg);
|
printk("apm_bios: %s: %s\n", str, error_table[i].msg);
|
else
|
else
|
printk("apm_bios: %s: unknown error code %#2.2x\n", str, err);
|
printk("apm_bios: %s: unknown error code %#2.2x\n", str, err);
|
}
|
}
|
|
|
/* Called from console driver -- must make sure apm_enabled. */
|
/* Called from console driver -- must make sure apm_enabled. */
|
int apm_display_blank(void)
|
int apm_display_blank(void)
|
{
|
{
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
int error;
|
int error;
|
|
|
if (!apm_enabled)
|
if (!apm_enabled)
|
return 0;
|
return 0;
|
error = apm_set_display_power_state(APM_STATE_STANDBY);
|
error = apm_set_display_power_state(APM_STATE_STANDBY);
|
if (error == APM_SUCCESS)
|
if (error == APM_SUCCESS)
|
return 1;
|
return 1;
|
apm_error("set display standby", error);
|
apm_error("set display standby", error);
|
#endif
|
#endif
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Called from console driver -- must make sure apm_enabled. */
|
/* Called from console driver -- must make sure apm_enabled. */
|
int apm_display_unblank(void)
|
int apm_display_unblank(void)
|
{
|
{
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
#ifdef CONFIG_APM_DISPLAY_BLANK
|
int error;
|
int error;
|
|
|
if (!apm_enabled)
|
if (!apm_enabled)
|
return 0;
|
return 0;
|
error = apm_set_display_power_state(APM_STATE_READY);
|
error = apm_set_display_power_state(APM_STATE_READY);
|
if (error == APM_SUCCESS)
|
if (error == APM_SUCCESS)
|
return 1;
|
return 1;
|
apm_error("set display ready", error);
|
apm_error("set display ready", error);
|
#endif
|
#endif
|
return 0;
|
return 0;
|
}
|
}
|
|
|
int apm_register_callback(int (*callback)(apm_event_t))
|
int apm_register_callback(int (*callback)(apm_event_t))
|
{
|
{
|
callback_list_t * new;
|
callback_list_t * new;
|
|
|
new = kmalloc(sizeof(callback_list_t), GFP_KERNEL);
|
new = kmalloc(sizeof(callback_list_t), GFP_KERNEL);
|
if (new == NULL)
|
if (new == NULL)
|
return -ENOMEM;
|
return -ENOMEM;
|
new->callback = callback;
|
new->callback = callback;
|
new->next = callback_list;
|
new->next = callback_list;
|
callback_list = new;
|
callback_list = new;
|
return 0;
|
return 0;
|
}
|
}
|
|
|
void apm_unregister_callback(int (*callback)(apm_event_t))
|
void apm_unregister_callback(int (*callback)(apm_event_t))
|
{
|
{
|
callback_list_t ** ptr;
|
callback_list_t ** ptr;
|
callback_list_t * old;
|
callback_list_t * old;
|
|
|
ptr = &callback_list;
|
ptr = &callback_list;
|
for (ptr = &callback_list; *ptr != NULL; ptr = &(*ptr)->next)
|
for (ptr = &callback_list; *ptr != NULL; ptr = &(*ptr)->next)
|
if ((*ptr)->callback == callback)
|
if ((*ptr)->callback == callback)
|
break;
|
break;
|
old = *ptr;
|
old = *ptr;
|
*ptr = old->next;
|
*ptr = old->next;
|
kfree_s(old, sizeof(callback_list_t));
|
kfree_s(old, sizeof(callback_list_t));
|
}
|
}
|
|
|
static int queue_empty(struct apm_bios_struct * as)
|
static int queue_empty(struct apm_bios_struct * as)
|
{
|
{
|
return as->event_head == as->event_tail;
|
return as->event_head == as->event_tail;
|
}
|
}
|
|
|
static apm_event_t get_queued_event(struct apm_bios_struct * as)
|
static apm_event_t get_queued_event(struct apm_bios_struct * as)
|
{
|
{
|
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
return as->events[as->event_tail];
|
return as->events[as->event_tail];
|
}
|
}
|
|
|
static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
|
static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
|
|
if (user_list == NULL)
|
if (user_list == NULL)
|
return 0;
|
return 0;
|
for (as = user_list; as != NULL; as = as->next) {
|
for (as = user_list; as != NULL; as = as->next) {
|
if (as == sender)
|
if (as == sender)
|
continue;
|
continue;
|
as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
|
as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
|
if (as->event_head == as->event_tail) {
|
if (as->event_head == as->event_tail) {
|
static int notified;
|
static int notified;
|
|
|
if (notified == 0) {
|
if (notified == 0) {
|
printk( "apm_bios: an event queue overflowed\n" );
|
printk( "apm_bios: an event queue overflowed\n" );
|
notified = 1;
|
notified = 1;
|
}
|
}
|
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
}
|
}
|
as->events[as->event_head] = event;
|
as->events[as->event_head] = event;
|
if (!as->suser)
|
if (!as->suser)
|
continue;
|
continue;
|
switch (event) {
|
switch (event) {
|
case APM_SYS_SUSPEND:
|
case APM_SYS_SUSPEND:
|
case APM_USER_SUSPEND:
|
case APM_USER_SUSPEND:
|
as->suspends_pending++;
|
as->suspends_pending++;
|
suspends_pending++;
|
suspends_pending++;
|
break;
|
break;
|
|
|
case APM_SYS_STANDBY:
|
case APM_SYS_STANDBY:
|
case APM_USER_STANDBY:
|
case APM_USER_STANDBY:
|
as->standbys_pending++;
|
as->standbys_pending++;
|
standbys_pending++;
|
standbys_pending++;
|
break;
|
break;
|
}
|
}
|
}
|
}
|
wake_up_interruptible(&process_list);
|
wake_up_interruptible(&process_list);
|
return 1;
|
return 1;
|
}
|
}
|
|
|
static void set_time(void)
|
static void set_time(void)
|
{
|
{
|
unsigned long flags;
|
unsigned long flags;
|
|
|
if (!got_clock_diff) /* Don't know time zone, can't set clock */
|
if (!got_clock_diff) /* Don't know time zone, can't set clock */
|
return;
|
return;
|
|
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
|
CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
|
restore_flags(flags);
|
restore_flags(flags);
|
}
|
}
|
|
|
static void suspend(void)
|
static void suspend(void)
|
{
|
{
|
unsigned long flags;
|
unsigned long flags;
|
int err;
|
int err;
|
|
|
/* Estimate time zone so that set_time can
|
/* Estimate time zone so that set_time can
|
update the clock */
|
update the clock */
|
save_flags(flags);
|
save_flags(flags);
|
clock_cmos_diff = -get_cmos_time();
|
clock_cmos_diff = -get_cmos_time();
|
cli();
|
cli();
|
clock_cmos_diff += CURRENT_TIME;
|
clock_cmos_diff += CURRENT_TIME;
|
got_clock_diff = 1;
|
got_clock_diff = 1;
|
restore_flags(flags);
|
restore_flags(flags);
|
|
|
err = apm_set_power_state(APM_STATE_SUSPEND);
|
err = apm_set_power_state(APM_STATE_SUSPEND);
|
if (err)
|
if (err)
|
apm_error("suspend", err);
|
apm_error("suspend", err);
|
set_time();
|
set_time();
|
}
|
}
|
|
|
static void standby(void)
|
static void standby(void)
|
{
|
{
|
int err;
|
int err;
|
|
|
err = apm_set_power_state(APM_STATE_STANDBY);
|
err = apm_set_power_state(APM_STATE_STANDBY);
|
if (err)
|
if (err)
|
apm_error("standby", err);
|
apm_error("standby", err);
|
}
|
}
|
|
|
static apm_event_t get_event(void)
|
static apm_event_t get_event(void)
|
{
|
{
|
int error;
|
int error;
|
apm_event_t event;
|
apm_event_t event;
|
|
|
static int notified = 0;
|
static int notified = 0;
|
|
|
error = apm_get_event(&event);
|
error = apm_get_event(&event);
|
if (error == APM_SUCCESS)
|
if (error == APM_SUCCESS)
|
return event;
|
return event;
|
|
|
if ((error != APM_NO_EVENTS) && (notified++ == 0))
|
if ((error != APM_NO_EVENTS) && (notified++ == 0))
|
apm_error("get_event", error);
|
apm_error("get_event", error);
|
|
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static void send_event(apm_event_t event, apm_event_t undo,
|
static void send_event(apm_event_t event, apm_event_t undo,
|
struct apm_bios_struct *sender)
|
struct apm_bios_struct *sender)
|
{
|
{
|
callback_list_t * call;
|
callback_list_t * call;
|
callback_list_t * fix;
|
callback_list_t * fix;
|
|
|
for (call = callback_list; call != NULL; call = call->next) {
|
for (call = callback_list; call != NULL; call = call->next) {
|
if (call->callback(event) && undo) {
|
if (call->callback(event) && undo) {
|
for (fix = callback_list; fix != call; fix = fix->next)
|
for (fix = callback_list; fix != call; fix = fix->next)
|
fix->callback(undo);
|
fix->callback(undo);
|
if (apm_bios_info.version > 0x100)
|
if (apm_bios_info.version > 0x100)
|
apm_set_power_state(APM_STATE_REJECT);
|
apm_set_power_state(APM_STATE_REJECT);
|
return;
|
return;
|
}
|
}
|
}
|
}
|
|
|
queue_event(event, sender);
|
queue_event(event, sender);
|
}
|
}
|
|
|
static void check_events(void)
|
static void check_events(void)
|
{
|
{
|
apm_event_t event;
|
apm_event_t event;
|
|
|
while ((event = get_event()) != 0) {
|
while ((event = get_event()) != 0) {
|
#ifdef APM_DEBUG
|
#ifdef APM_DEBUG
|
if (event <= NR_APM_EVENT_NAME)
|
if (event <= NR_APM_EVENT_NAME)
|
printk("APM BIOS received %s notify\n",
|
printk("APM BIOS received %s notify\n",
|
apm_event_name[event - 1]);
|
apm_event_name[event - 1]);
|
else
|
else
|
printk("APM BIOS received unknown event 0x%02x\n",
|
printk("APM BIOS received unknown event 0x%02x\n",
|
event);
|
event);
|
#endif
|
#endif
|
switch (event) {
|
switch (event) {
|
case APM_SYS_STANDBY:
|
case APM_SYS_STANDBY:
|
case APM_USER_STANDBY:
|
case APM_USER_STANDBY:
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
if (waiting_for_resume) {
|
if (waiting_for_resume) {
|
return;
|
return;
|
}
|
}
|
waiting_for_resume = 1;
|
waiting_for_resume = 1;
|
#endif
|
#endif
|
send_event(event, APM_STANDBY_RESUME, NULL);
|
send_event(event, APM_STANDBY_RESUME, NULL);
|
if (standbys_pending <= 0)
|
if (standbys_pending <= 0)
|
standby();
|
standby();
|
break;
|
break;
|
|
|
case APM_USER_SUSPEND:
|
case APM_USER_SUSPEND:
|
#ifdef CONFIG_APM_IGNORE_USER_SUSPEND
|
#ifdef CONFIG_APM_IGNORE_USER_SUSPEND
|
if (apm_bios_info.version > 0x100)
|
if (apm_bios_info.version > 0x100)
|
apm_set_power_state(APM_STATE_REJECT);
|
apm_set_power_state(APM_STATE_REJECT);
|
break;
|
break;
|
#endif
|
#endif
|
case APM_SYS_SUSPEND:
|
case APM_SYS_SUSPEND:
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
if (waiting_for_resume) {
|
if (waiting_for_resume) {
|
return;
|
return;
|
}
|
}
|
waiting_for_resume = 1;
|
waiting_for_resume = 1;
|
#endif
|
#endif
|
send_event(event, APM_NORMAL_RESUME, NULL);
|
send_event(event, APM_NORMAL_RESUME, NULL);
|
if (suspends_pending <= 0)
|
if (suspends_pending <= 0)
|
suspend();
|
suspend();
|
break;
|
break;
|
|
|
case APM_NORMAL_RESUME:
|
case APM_NORMAL_RESUME:
|
case APM_CRITICAL_RESUME:
|
case APM_CRITICAL_RESUME:
|
case APM_STANDBY_RESUME:
|
case APM_STANDBY_RESUME:
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
|
waiting_for_resume = 0;
|
waiting_for_resume = 0;
|
#endif
|
#endif
|
set_time();
|
set_time();
|
send_event(event, 0, NULL);
|
send_event(event, 0, NULL);
|
break;
|
break;
|
|
|
case APM_LOW_BATTERY:
|
case APM_LOW_BATTERY:
|
case APM_POWER_STATUS_CHANGE:
|
case APM_POWER_STATUS_CHANGE:
|
send_event(event, 0, NULL);
|
send_event(event, 0, NULL);
|
break;
|
break;
|
|
|
case APM_UPDATE_TIME:
|
case APM_UPDATE_TIME:
|
set_time();
|
set_time();
|
break;
|
break;
|
|
|
case APM_CRITICAL_SUSPEND:
|
case APM_CRITICAL_SUSPEND:
|
suspend();
|
suspend();
|
break;
|
break;
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
static void do_apm_timer(unsigned long unused)
|
static void do_apm_timer(unsigned long unused)
|
{
|
{
|
int err;
|
int err;
|
|
|
static int pending_count = 0;
|
static int pending_count = 0;
|
|
|
if (((standbys_pending > 0) || (suspends_pending > 0))
|
if (((standbys_pending > 0) || (suspends_pending > 0))
|
&& (apm_bios_info.version > 0x100)
|
&& (apm_bios_info.version > 0x100)
|
&& (pending_count-- <= 0)) {
|
&& (pending_count-- <= 0)) {
|
pending_count = 4;
|
pending_count = 4;
|
|
|
err = apm_set_power_state(APM_STATE_BUSY);
|
err = apm_set_power_state(APM_STATE_BUSY);
|
if (err)
|
if (err)
|
apm_error("busy", err);
|
apm_error("busy", err);
|
}
|
}
|
|
|
if (!(((standbys_pending > 0) || (suspends_pending > 0))
|
if (!(((standbys_pending > 0) || (suspends_pending > 0))
|
&& (apm_bios_info.version == 0x100)))
|
&& (apm_bios_info.version == 0x100)))
|
check_events();
|
check_events();
|
|
|
init_timer(&apm_timer);
|
init_timer(&apm_timer);
|
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
|
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
|
add_timer(&apm_timer);
|
add_timer(&apm_timer);
|
}
|
}
|
|
|
/* Called from sys_idle, must make sure apm_enabled. */
|
/* Called from sys_idle, must make sure apm_enabled. */
|
int apm_do_idle(void)
|
int apm_do_idle(void)
|
{
|
{
|
#ifdef CONFIG_APM_CPU_IDLE
|
#ifdef CONFIG_APM_CPU_IDLE
|
unsigned short error;
|
unsigned short error;
|
|
|
if (!apm_enabled)
|
if (!apm_enabled)
|
return 0;
|
return 0;
|
|
|
APM_SET_CPU_IDLE(error);
|
APM_SET_CPU_IDLE(error);
|
if (error & 0xff)
|
if (error & 0xff)
|
return 0;
|
return 0;
|
|
|
clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
|
clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
|
return 1;
|
return 1;
|
#else
|
#else
|
return 0;
|
return 0;
|
#endif
|
#endif
|
}
|
}
|
|
|
/* Called from sys_idle, must make sure apm_enabled. */
|
/* Called from sys_idle, must make sure apm_enabled. */
|
void apm_do_busy(void)
|
void apm_do_busy(void)
|
{
|
{
|
#ifdef CONFIG_APM_CPU_IDLE
|
#ifdef CONFIG_APM_CPU_IDLE
|
unsigned short error;
|
unsigned short error;
|
|
|
if (!apm_enabled)
|
if (!apm_enabled)
|
return;
|
return;
|
|
|
#ifndef ALWAYS_CALL_BUSY
|
#ifndef ALWAYS_CALL_BUSY
|
if (!clock_slowed)
|
if (!clock_slowed)
|
return;
|
return;
|
#endif
|
#endif
|
|
|
APM_SET_CPU_BUSY(error);
|
APM_SET_CPU_BUSY(error);
|
|
|
clock_slowed = 0;
|
clock_slowed = 0;
|
#endif
|
#endif
|
}
|
}
|
|
|
static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
|
static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
|
{
|
{
|
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
|
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
|
printk("apm_bios: %s passed bad filp", func);
|
printk("apm_bios: %s passed bad filp", func);
|
return 1;
|
return 1;
|
}
|
}
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static int do_read(struct inode *inode, struct file *fp, char *buf, int count)
|
static int do_read(struct inode *inode, struct file *fp, char *buf, int count)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
int i;
|
int i;
|
apm_event_t event;
|
apm_event_t event;
|
struct wait_queue wait = { current, NULL };
|
struct wait_queue wait = { current, NULL };
|
|
|
as = fp->private_data;
|
as = fp->private_data;
|
if (check_apm_bios_struct(as, "read"))
|
if (check_apm_bios_struct(as, "read"))
|
return -EIO;
|
return -EIO;
|
if (count < sizeof(apm_event_t))
|
if (count < sizeof(apm_event_t))
|
return -EINVAL;
|
return -EINVAL;
|
if (queue_empty(as)) {
|
if (queue_empty(as)) {
|
if (fp->f_flags & O_NONBLOCK)
|
if (fp->f_flags & O_NONBLOCK)
|
return -EAGAIN;
|
return -EAGAIN;
|
add_wait_queue(&process_list, &wait);
|
add_wait_queue(&process_list, &wait);
|
repeat:
|
repeat:
|
current->state = TASK_INTERRUPTIBLE;
|
current->state = TASK_INTERRUPTIBLE;
|
if (queue_empty(as)
|
if (queue_empty(as)
|
&& !(current->signal & ~current->blocked)) {
|
&& !(current->signal & ~current->blocked)) {
|
schedule();
|
schedule();
|
goto repeat;
|
goto repeat;
|
}
|
}
|
current->state = TASK_RUNNING;
|
current->state = TASK_RUNNING;
|
remove_wait_queue(&process_list, &wait);
|
remove_wait_queue(&process_list, &wait);
|
}
|
}
|
i = count;
|
i = count;
|
while ((i >= sizeof(event)) && !queue_empty(as)) {
|
while ((i >= sizeof(event)) && !queue_empty(as)) {
|
event = get_queued_event(as);
|
event = get_queued_event(as);
|
memcpy_tofs(buf, &event, sizeof(event));
|
memcpy_tofs(buf, &event, sizeof(event));
|
switch (event) {
|
switch (event) {
|
case APM_SYS_SUSPEND:
|
case APM_SYS_SUSPEND:
|
case APM_USER_SUSPEND:
|
case APM_USER_SUSPEND:
|
as->suspends_read++;
|
as->suspends_read++;
|
break;
|
break;
|
|
|
case APM_SYS_STANDBY:
|
case APM_SYS_STANDBY:
|
case APM_USER_STANDBY:
|
case APM_USER_STANDBY:
|
as->standbys_read++;
|
as->standbys_read++;
|
break;
|
break;
|
}
|
}
|
buf += sizeof(event);
|
buf += sizeof(event);
|
i -= sizeof(event);
|
i -= sizeof(event);
|
}
|
}
|
if (i < count)
|
if (i < count)
|
return count - i;
|
return count - i;
|
if (current->signal & ~current->blocked)
|
if (current->signal & ~current->blocked)
|
return -ERESTARTSYS;
|
return -ERESTARTSYS;
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static int do_select(struct inode *inode, struct file *fp, int sel_type,
|
static int do_select(struct inode *inode, struct file *fp, int sel_type,
|
select_table * wait)
|
select_table * wait)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
|
|
as = fp->private_data;
|
as = fp->private_data;
|
if (check_apm_bios_struct(as, "select"))
|
if (check_apm_bios_struct(as, "select"))
|
return 0;
|
return 0;
|
if (sel_type != SEL_IN)
|
if (sel_type != SEL_IN)
|
return 0;
|
return 0;
|
if (!queue_empty(as))
|
if (!queue_empty(as))
|
return 1;
|
return 1;
|
select_wait(&process_list, wait);
|
select_wait(&process_list, wait);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static int do_ioctl(struct inode * inode, struct file *filp,
|
static int do_ioctl(struct inode * inode, struct file *filp,
|
u_int cmd, u_long arg)
|
u_int cmd, u_long arg)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
|
|
as = filp->private_data;
|
as = filp->private_data;
|
if (check_apm_bios_struct(as, "ioctl"))
|
if (check_apm_bios_struct(as, "ioctl"))
|
return -EIO;
|
return -EIO;
|
if (!as->suser)
|
if (!as->suser)
|
return -EPERM;
|
return -EPERM;
|
switch (cmd) {
|
switch (cmd) {
|
case APM_IOC_STANDBY:
|
case APM_IOC_STANDBY:
|
if (as->standbys_read > 0) {
|
if (as->standbys_read > 0) {
|
as->standbys_read--;
|
as->standbys_read--;
|
as->standbys_pending--;
|
as->standbys_pending--;
|
standbys_pending--;
|
standbys_pending--;
|
}
|
}
|
else
|
else
|
send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as);
|
send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as);
|
if (standbys_pending <= 0)
|
if (standbys_pending <= 0)
|
standby();
|
standby();
|
break;
|
break;
|
case APM_IOC_SUSPEND:
|
case APM_IOC_SUSPEND:
|
if (as->suspends_read > 0) {
|
if (as->suspends_read > 0) {
|
as->suspends_read--;
|
as->suspends_read--;
|
as->suspends_pending--;
|
as->suspends_pending--;
|
suspends_pending--;
|
suspends_pending--;
|
}
|
}
|
else
|
else
|
send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as);
|
send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as);
|
if (suspends_pending <= 0)
|
if (suspends_pending <= 0)
|
suspend();
|
suspend();
|
break;
|
break;
|
default:
|
default:
|
return -EINVAL;
|
return -EINVAL;
|
}
|
}
|
return 0;
|
return 0;
|
}
|
}
|
|
|
static void do_release(struct inode * inode, struct file * filp)
|
static void do_release(struct inode * inode, struct file * filp)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
|
|
as = filp->private_data;
|
as = filp->private_data;
|
filp->private_data = NULL;
|
filp->private_data = NULL;
|
if (check_apm_bios_struct(as, "release"))
|
if (check_apm_bios_struct(as, "release"))
|
return;
|
return;
|
if (as->standbys_pending > 0) {
|
if (as->standbys_pending > 0) {
|
standbys_pending -= as->standbys_pending;
|
standbys_pending -= as->standbys_pending;
|
if (standbys_pending <= 0)
|
if (standbys_pending <= 0)
|
standby();
|
standby();
|
}
|
}
|
if (as->suspends_pending > 0) {
|
if (as->suspends_pending > 0) {
|
suspends_pending -= as->suspends_pending;
|
suspends_pending -= as->suspends_pending;
|
if (suspends_pending <= 0)
|
if (suspends_pending <= 0)
|
suspend();
|
suspend();
|
}
|
}
|
if (user_list == as)
|
if (user_list == as)
|
user_list = as->next;
|
user_list = as->next;
|
else {
|
else {
|
struct apm_bios_struct * as1;
|
struct apm_bios_struct * as1;
|
|
|
for (as1 = user_list;
|
for (as1 = user_list;
|
(as1 != NULL) && (as1->next != as);
|
(as1 != NULL) && (as1->next != as);
|
as1 = as1->next)
|
as1 = as1->next)
|
;
|
;
|
if (as1 == NULL)
|
if (as1 == NULL)
|
printk("apm_bios: filp not in user list");
|
printk("apm_bios: filp not in user list");
|
else
|
else
|
as1->next = as->next;
|
as1->next = as->next;
|
}
|
}
|
kfree_s(as, sizeof(*as));
|
kfree_s(as, sizeof(*as));
|
}
|
}
|
|
|
static int do_open(struct inode * inode, struct file * filp)
|
static int do_open(struct inode * inode, struct file * filp)
|
{
|
{
|
struct apm_bios_struct * as;
|
struct apm_bios_struct * as;
|
|
|
as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL);
|
as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL);
|
if (as == NULL) {
|
if (as == NULL) {
|
printk("apm_bios: cannot allocate struct of size %d bytes",
|
printk("apm_bios: cannot allocate struct of size %d bytes",
|
sizeof(*as));
|
sizeof(*as));
|
return -ENOMEM;
|
return -ENOMEM;
|
}
|
}
|
as->magic = APM_BIOS_MAGIC;
|
as->magic = APM_BIOS_MAGIC;
|
as->event_tail = as->event_head = 0;
|
as->event_tail = as->event_head = 0;
|
as->suspends_pending = as->standbys_pending = 0;
|
as->suspends_pending = as->standbys_pending = 0;
|
as->suspends_read = as->standbys_read = 0;
|
as->suspends_read = as->standbys_read = 0;
|
as->suser = suser();
|
as->suser = suser();
|
as->next = user_list;
|
as->next = user_list;
|
user_list = as;
|
user_list = as;
|
filp->private_data = as;
|
filp->private_data = as;
|
return 0;
|
return 0;
|
}
|
}
|
|
|
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
|
int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
|
{
|
{
|
char * p;
|
char * p;
|
unsigned short bx;
|
unsigned short bx;
|
unsigned short cx;
|
unsigned short cx;
|
unsigned short dx;
|
unsigned short dx;
|
unsigned short error;
|
unsigned short error;
|
unsigned short ac_line_status = 0xff;
|
unsigned short ac_line_status = 0xff;
|
unsigned short battery_status = 0xff;
|
unsigned short battery_status = 0xff;
|
unsigned short battery_flag = 0xff;
|
unsigned short battery_flag = 0xff;
|
int percentage = -1;
|
int percentage = -1;
|
int time_units = -1;
|
int time_units = -1;
|
char *units = "?";
|
char *units = "?";
|
|
|
if (!apm_enabled)
|
if (!apm_enabled)
|
return 0;
|
return 0;
|
p = buf;
|
p = buf;
|
|
|
if (!(error = apm_get_power_status(&bx, &cx, &dx))) {
|
if (!(error = apm_get_power_status(&bx, &cx, &dx))) {
|
ac_line_status = (bx >> 8) & 0xff;
|
ac_line_status = (bx >> 8) & 0xff;
|
battery_status = bx & 0xff;
|
battery_status = bx & 0xff;
|
if ((cx & 0xff) != 0xff)
|
if ((cx & 0xff) != 0xff)
|
percentage = cx & 0xff;
|
percentage = cx & 0xff;
|
|
|
if (apm_bios_info.version > 0x100) {
|
if (apm_bios_info.version > 0x100) {
|
battery_flag = (cx >> 8) & 0xff;
|
battery_flag = (cx >> 8) & 0xff;
|
if (dx != 0xffff) {
|
if (dx != 0xffff) {
|
if ((dx & 0x8000) == 0x8000) {
|
if ((dx & 0x8000) == 0x8000) {
|
units = "min";
|
units = "min";
|
time_units = dx & 0x7ffe;
|
time_units = dx & 0x7ffe;
|
} else {
|
} else {
|
units = "sec";
|
units = "sec";
|
time_units = dx & 0x7fff;
|
time_units = dx & 0x7fff;
|
}
|
}
|
}
|
}
|
}
|
}
|
}
|
}
|
/* Arguments, with symbols from linux/apm_bios.h. Information is
|
/* Arguments, with symbols from linux/apm_bios.h. Information is
|
from the Get Power Status (0x0a) call unless otherwise noted.
|
from the Get Power Status (0x0a) call unless otherwise noted.
|
|
|
0) Linux driver version (this will change if format changes)
|
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 or 1.1.
|
2) APM flags from APM Installation Check (0x00):
|
2) APM flags from APM Installation Check (0x00):
|
bit 0: APM_16_BIT_SUPPORT
|
bit 0: APM_16_BIT_SUPPORT
|
bit 1: APM_32_BIT_SUPPORT
|
bit 1: APM_32_BIT_SUPPORT
|
bit 2: APM_IDLE_SLOWS_CLOCK
|
bit 2: APM_IDLE_SLOWS_CLOCK
|
bit 3: APM_BIOS_DISABLED
|
bit 3: APM_BIOS_DISABLED
|
bit 4: APM_BIOS_DISENGAGED
|
bit 4: APM_BIOS_DISENGAGED
|
3) AC line status
|
3) AC line status
|
0x00: Off-line
|
0x00: Off-line
|
0x01: On-line
|
0x01: On-line
|
0x02: On backup power (APM BIOS 1.1 only)
|
0x02: On backup power (APM BIOS 1.1 only)
|
0xff: Unknown
|
0xff: Unknown
|
4) Battery status
|
4) Battery status
|
0x00: High
|
0x00: High
|
0x01: Low
|
0x01: Low
|
0x02: Critical
|
0x02: Critical
|
0x03: Charging
|
0x03: Charging
|
0xff: Unknown
|
0xff: Unknown
|
5) Battery flag
|
5) Battery flag
|
bit 0: High
|
bit 0: High
|
bit 1: Low
|
bit 1: Low
|
bit 2: Critical
|
bit 2: Critical
|
bit 3: Charging
|
bit 3: Charging
|
bit 7: No system battery
|
bit 7: No system battery
|
0xff: Unknown
|
0xff: Unknown
|
6) Remaining battery life (percentage of charge):
|
6) Remaining battery life (percentage of charge):
|
0-100: valid
|
0-100: valid
|
-1: Unknown
|
-1: Unknown
|
7) Remaining battery life (time units):
|
7) Remaining battery life (time units):
|
Number of remaining minutes or seconds
|
Number of remaining minutes or seconds
|
-1: Unknown
|
-1: Unknown
|
8) min = minutes; sec = seconds */
|
8) min = minutes; sec = seconds */
|
|
|
p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
|
p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
|
driver_version,
|
driver_version,
|
(apm_bios_info.version >> 8) & 0xff,
|
(apm_bios_info.version >> 8) & 0xff,
|
apm_bios_info.version & 0xff,
|
apm_bios_info.version & 0xff,
|
apm_bios_info.flags,
|
apm_bios_info.flags,
|
ac_line_status,
|
ac_line_status,
|
battery_status,
|
battery_status,
|
battery_flag,
|
battery_flag,
|
percentage,
|
percentage,
|
time_units,
|
time_units,
|
units);
|
units);
|
|
|
return p - buf;
|
return p - buf;
|
}
|
}
|
#endif
|
#endif
|
|
|
static int apm_disabled = 0;
|
static int apm_disabled = 0;
|
|
|
void apm_setup(char *str, int *ints)
|
void apm_setup(char *str, int *ints)
|
{
|
{
|
if(strcmp(str,"off")==0)
|
if(strcmp(str,"off")==0)
|
apm_disabled=1;
|
apm_disabled=1;
|
if(strcmp(str,"on")==0)
|
if(strcmp(str,"on")==0)
|
apm_disabled=0;
|
apm_disabled=0;
|
}
|
}
|
|
|
void apm_bios_init(void)
|
void apm_bios_init(void)
|
{
|
{
|
unsigned short bx;
|
unsigned short bx;
|
unsigned short cx;
|
unsigned short cx;
|
unsigned short dx;
|
unsigned short dx;
|
unsigned short error;
|
unsigned short error;
|
char * power_stat;
|
char * power_stat;
|
char * bat_stat;
|
char * bat_stat;
|
|
|
if (apm_disabled == 1)
|
if (apm_disabled == 1)
|
{
|
{
|
printk("APM disabled.\n");
|
printk("APM disabled.\n");
|
return;
|
return;
|
}
|
}
|
|
|
if (apm_bios_info.version == 0) {
|
if (apm_bios_info.version == 0) {
|
printk("APM BIOS not found.\n");
|
printk("APM BIOS not found.\n");
|
return;
|
return;
|
}
|
}
|
printk("APM BIOS version %c.%c Flags 0x%02x (Driver version %s)\n",
|
printk("APM BIOS version %c.%c Flags 0x%02x (Driver version %s)\n",
|
((apm_bios_info.version >> 8) & 0xff) + '0',
|
((apm_bios_info.version >> 8) & 0xff) + '0',
|
(apm_bios_info.version & 0xff) + '0',
|
(apm_bios_info.version & 0xff) + '0',
|
apm_bios_info.flags,
|
apm_bios_info.flags,
|
driver_version);
|
driver_version);
|
if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
|
if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
|
printk(" No 32 bit BIOS support\n");
|
printk(" No 32 bit BIOS support\n");
|
return;
|
return;
|
}
|
}
|
|
|
/*
|
/*
|
* Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
|
* Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
|
* but is reportedly a 1.0 BIOS.
|
* but is reportedly a 1.0 BIOS.
|
*/
|
*/
|
if (apm_bios_info.version == 0x001)
|
if (apm_bios_info.version == 0x001)
|
apm_bios_info.version = 0x100;
|
apm_bios_info.version = 0x100;
|
|
|
printk(" Entry %x:%lx cseg16 %x dseg %x",
|
printk(" Entry %x:%lx cseg16 %x dseg %x",
|
apm_bios_info.cseg, apm_bios_info.offset,
|
apm_bios_info.cseg, apm_bios_info.offset,
|
apm_bios_info.cseg_16, apm_bios_info.dseg);
|
apm_bios_info.cseg_16, apm_bios_info.dseg);
|
if (apm_bios_info.version > 0x100)
|
if (apm_bios_info.version > 0x100)
|
printk(" cseg len %x, dseg len %x",
|
printk(" cseg len %x, dseg len %x",
|
apm_bios_info.cseg_len, apm_bios_info.dseg_len);
|
apm_bios_info.cseg_len, apm_bios_info.dseg_len);
|
printk("\n");
|
printk("\n");
|
|
|
apm_bios_entry.offset = apm_bios_info.offset;
|
apm_bios_entry.offset = apm_bios_info.offset;
|
apm_bios_entry.segment = APM_CS;
|
apm_bios_entry.segment = APM_CS;
|
set_base(gdt[APM_CS >> 3],
|
set_base(gdt[APM_CS >> 3],
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg << 4));
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg << 4));
|
set_base(gdt[APM_CS_16 >> 3],
|
set_base(gdt[APM_CS_16 >> 3],
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg_16 << 4));
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg_16 << 4));
|
set_base(gdt[APM_DS >> 3],
|
set_base(gdt[APM_DS >> 3],
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.dseg << 4));
|
__PAGE_OFFSET + ((unsigned long)apm_bios_info.dseg << 4));
|
if (apm_bios_info.version == 0x100) {
|
if (apm_bios_info.version == 0x100) {
|
set_limit(gdt[APM_CS >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
set_limit(gdt[APM_DS >> 3], 64 * 1024);
|
set_limit(gdt[APM_DS >> 3], 64 * 1024);
|
} else {
|
} else {
|
#ifdef APM_RELAX_SEGMENTS
|
#ifdef APM_RELAX_SEGMENTS
|
/* For ASUS motherboard, Award BIOS rev 110 (and others?) */
|
/* For ASUS motherboard, Award BIOS rev 110 (and others?) */
|
set_limit(gdt[APM_CS >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS >> 3], 64 * 1024);
|
/* For some unknown machine. */
|
/* For some unknown machine. */
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
/* For the DEC Hinote Ultra CT475 (and others?) */
|
/* For the DEC Hinote Ultra CT475 (and others?) */
|
set_limit(gdt[APM_DS >> 3], 64 * 1024);
|
set_limit(gdt[APM_DS >> 3], 64 * 1024);
|
#else
|
#else
|
set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
|
set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
|
set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
|
set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
|
#endif
|
#endif
|
apm_bios_info.version = 0x0101;
|
apm_bios_info.version = 0x0101;
|
error = apm_driver_version(&apm_bios_info.version);
|
error = apm_driver_version(&apm_bios_info.version);
|
if (error != 0)
|
if (error != 0)
|
apm_bios_info.version = 0x100;
|
apm_bios_info.version = 0x100;
|
else {
|
else {
|
apm_engage_power_management(0x0001);
|
apm_engage_power_management(0x0001);
|
printk( " Connection version %d.%d\n",
|
printk( " Connection version %d.%d\n",
|
(apm_bios_info.version >> 8) & 0xff,
|
(apm_bios_info.version >> 8) & 0xff,
|
apm_bios_info.version & 0xff );
|
apm_bios_info.version & 0xff );
|
apm_bios_info.version = 0x0101;
|
apm_bios_info.version = 0x0101;
|
}
|
}
|
}
|
}
|
|
|
error = apm_get_power_status(&bx, &cx, &dx);
|
error = apm_get_power_status(&bx, &cx, &dx);
|
if (error)
|
if (error)
|
printk(" Power status not available\n");
|
printk(" Power status not available\n");
|
else {
|
else {
|
switch ((bx >> 8) & 0xff) {
|
switch ((bx >> 8) & 0xff) {
|
case 0: power_stat = "off line"; break;
|
case 0: power_stat = "off line"; break;
|
case 1: power_stat = "on line"; break;
|
case 1: power_stat = "on line"; break;
|
case 2: power_stat = "on backup power"; break;
|
case 2: power_stat = "on backup power"; break;
|
default: power_stat = "unknown"; break;
|
default: power_stat = "unknown"; break;
|
}
|
}
|
switch (bx & 0xff) {
|
switch (bx & 0xff) {
|
case 0: bat_stat = "high"; break;
|
case 0: bat_stat = "high"; break;
|
case 1: bat_stat = "low"; break;
|
case 1: bat_stat = "low"; break;
|
case 2: bat_stat = "critical"; break;
|
case 2: bat_stat = "critical"; break;
|
case 3: bat_stat = "charging"; break;
|
case 3: bat_stat = "charging"; break;
|
default: bat_stat = "unknown"; break;
|
default: bat_stat = "unknown"; break;
|
}
|
}
|
printk(" AC %s, battery status %s, battery life ",
|
printk(" AC %s, battery status %s, battery life ",
|
power_stat, bat_stat);
|
power_stat, bat_stat);
|
if ((cx & 0xff) == 0xff)
|
if ((cx & 0xff) == 0xff)
|
printk("unknown\n");
|
printk("unknown\n");
|
else
|
else
|
printk("%d%%\n", cx & 0xff);
|
printk("%d%%\n", cx & 0xff);
|
if (apm_bios_info.version > 0x100) {
|
if (apm_bios_info.version > 0x100) {
|
printk(" battery flag 0x%02x, battery life ",
|
printk(" battery flag 0x%02x, battery life ",
|
(cx >> 8) & 0xff);
|
(cx >> 8) & 0xff);
|
if (dx == 0xffff)
|
if (dx == 0xffff)
|
printk("unknown\n");
|
printk("unknown\n");
|
else {
|
else {
|
if ((dx & 0x8000))
|
if ((dx & 0x8000))
|
printk("%d minutes\n", dx & 0x7ffe );
|
printk("%d minutes\n", dx & 0x7ffe );
|
else
|
else
|
printk("%d seconds\n", dx & 0x7fff );
|
printk("%d seconds\n", dx & 0x7fff );
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
#ifdef CONFIG_APM_DO_ENABLE
|
#ifdef CONFIG_APM_DO_ENABLE
|
/*
|
/*
|
* This call causes my NEC UltraLite Versa 33/C to hang if it is
|
* This call causes my NEC UltraLite Versa 33/C to hang if it is
|
* booted with PM disabled but not in the docking station.
|
* booted with PM disabled but not in the docking station.
|
* Unfortunate ...
|
* Unfortunate ...
|
*/
|
*/
|
error = apm_enable_power_management();
|
error = apm_enable_power_management();
|
if (error)
|
if (error)
|
apm_error("enable power management", error);
|
apm_error("enable power management", error);
|
if (error == APM_DISABLED)
|
if (error == APM_DISABLED)
|
return;
|
return;
|
#endif
|
#endif
|
|
|
init_timer(&apm_timer);
|
init_timer(&apm_timer);
|
apm_timer.function = do_apm_timer;
|
apm_timer.function = do_apm_timer;
|
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
|
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
|
add_timer(&apm_timer);
|
add_timer(&apm_timer);
|
|
|
register_symtab(&apm_syms);
|
register_symtab(&apm_syms);
|
|
|
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
proc_register_dynamic(&proc_root, &apm_proc_entry);
|
proc_register_dynamic(&proc_root, &apm_proc_entry);
|
#endif
|
#endif
|
|
|
misc_register(&apm_device);
|
misc_register(&apm_device);
|
|
|
apm_enabled = 1;
|
apm_enabled = 1;
|
}
|
}
|
|
|